From c6c92bdce3ad2714a3da5f1b1d7f083b2b579be3 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 6 Nov 2025 10:50:58 -0800 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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; }