Issue #22306 has been reported by hsbt (Hiroshi SHIBATA). ---------------------------------------- Bug #22306: Ruby::Box: with_jit hooks do not reach existing boxes https://bugs.ruby-lang.org/issues/22306 * Author: hsbt (Hiroshi SHIBATA) * Status: Open * ruby -v: ruby 4.1.0dev (2026-09-10T03:20:50Z master fe58143f12) +YJIT +MN +PRISM [arm64-darwin27] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- Under `RUBY_BOX=1`, enabling YJIT does not switch `Array#each`, `#map`, `#select` and `Integer#downto` to their Ruby implementations in the root and main boxes. Only boxes created later get them. ``` $ RUBY_BOX=1 ruby --yjit -e 'p Array.instance_method(:each).source_location, Ruby::Box.new.eval("Array.instance_method(:each).source_location")' nil ["<internal:array>", 219] ``` Without `RUBY_BOX` the first line is `["<internal:array>", 219]`. 4.0.6 gives the same output. `TestYJIT#test_yjit_option_uses_array_each_in_ruby` and `#test_yjit_enable_replaces_array_each` fail under `RUBY_BOX=1` for this reason. `with_jit` in `jit_hook.rb` registers its block while the builtins load in the master box, so the `undef` and `def` in it change only the master box. The hooks run after the root and main boxes exist, even with `--yjit` (`ruby.c:1877`). By then loading RubyGems has given both boxes their own copy of `Array`, as any cached method call on it does. Moving the hook call before the boxes are created would cover only `--yjit`, not `RubyVM::YJIT.enable`. I see two designs. The first runs the hooks in every existing box as well as the master box when a JIT is enabled. Each box would then check `rb_builtin_basic_definition_p` against its own definitions, so a box that patched `Array#each` keeps its patch. The blocks take their box from their environment, so this needs a way to run them with another box as the current one. The second stops redefining methods after boot. The Ruby implementations are defined at boot next to the C ones, before any box exists, and the JIT picks the Ruby one when it compiles a call. Nothing changes after boot, which fits a master box that runs no code. It does change how the JIT dispatches these methods and what `source_location` reports. `with_yjit` came with 478e0fc710b, when the Ruby `Array#each` from #20182 was limited to YJIT, and became `with_jit` for ZJIT in 2cd10de3309. Both predate Ruby::Box. -- https://bugs.ruby-lang.org/