From 63a58c7943603b5774e0d55dadf6035b6235a72a Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 15 Oct 2025 10:24:10 -0400 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] [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 6/6] [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