Bug #22546
openYJIT panics in arm64 `add` when compiling a method with 495 or more locals
Description
Versions:
- ruby 4.0.9 (2026-10-23 revision 3e93b23afc) +PRISM [arm64-darwin25]
- ruby 4.1.0dev (2026-11-13 master acc624e47b) +PRISM [arm64-darwin25]
macOS on Apple silicon.
Also reproduces on Linux:
- ruby 4.0.9 (2026-10-23 revision 3e93b23afc) +PRISM [aarch64-linux] (
ruby:4.0.9-slimDocker image, native arm64): same threshold, same panic at yjit/src/asm/arm64/mod.rs:53:78. - ruby 4.0.9 (2026-10-23 revision 3e93b23afc) +PRISM [x86_64-linux] (same image, x86_64 emulated with Rosetta on Apple silicon): a similar but different failure. 495 locals and up to 4089 compile and return correct values; at 4090 locals the process segfaults (exit 139) with no
[BUG]output. Without--yjitit runs.
Repro (run withruby --yjit):
count = 600
body = (1..count).map { |n| " v#{n} = a + #{n}" }.join("\n")
eval("def many_locals(a)\n#{body}\n v1\nend")
100.times { many_locals(1) }
A local table of 494 entries compiles; 495 panics, the same on both versions. Stack depth does not move it (300 locals with a 220-element array literal compiles). Without --yjit every count runs. The attached yjit_repro.rb bisects the threshold, running each count in a child ruby --yjit.
Panic (4.1.0dev; 4.0.9 shows the same frames and the same line):
ruby: YJIT has panicked. More info to follow...
thread '<unnamed>' panicked at ./yjit/src/asm/arm64/mod.rs:53:78:
called `Result::unwrap()` on an `Err` value: ()
3: yjit::asm::arm64::add
4: <yjit::backend::ir::Assembler>::arm64_emit
5: <yjit::backend::ir::Assembler>::compile_with_regs
6: <yjit::backend::ir::Assembler>::compile
7: yjit::codegen::gen_single_block
8: yjit::core::gen_block_series
9: yjit::core::branch_stub_hit_body
12: yjit::core::branch_stub_hit
[BUG] YJIT: panicked at ./yjit/src/asm/arm64/mod.rs:53:78
Expected: no panic.
ZJIT on the same 4.1.0dev build compiles these methods (with --zjit-call-threshold=1) and runs them correctly up to 2048 locals; at 5000 it declines to compile and the interpreter runs it.
Use case. This came from a tool that compiles a small language to Ruby. Its output is long straight-line methods, and each intermediate result gets its own local, so one source function can produce a few hundred locals. Other generated code can reach similar counts: #22074 was found in production through a large Slim template with more than 256 locals. The method is valid Ruby and runs correctly in the interpreter, but under YJIT it aborts the whole process with [BUG]. Splitting the generated methods into smaller ones avoids it. The generator works around it now, but other generated code will hit the same limit.
Files
No data to display