Untangle the logic a bit and specifically:
* catch `gen_entry` failures
* don't set `start_ptr` until all recursive calls succeed
Co-authored-by: Alan Wu <alanwu@ruby-lang.org>
Add support for `--zjit-allowed-iseqs=SomeFile` and
`--zjit-log-compiled-iseqs=SomeFile` so we can restrict and inspect
which ISEQs get compiled.
Then add `jit_bisect.rb` which we can run to try and narrow a failing
script. For example:
plum% ../tool/zjit_bisect.rb ../build-dev/miniruby "test.rb"
I, [2025-07-29T12:41:18.657177 #96899] INFO -- : Starting with JIT list of 4 items.
I, [2025-07-29T12:41:18.657229 #96899] INFO -- : Verifying items
I, [2025-07-29T12:41:18.726213 #96899] INFO -- : step fixed[0] and items[4]
I, [2025-07-29T12:41:18.726246 #96899] INFO -- : 4 candidates
I, [2025-07-29T12:41:18.797212 #96899] INFO -- : 2 candidates
Reduced JIT list:
bar@test.rb:8
plum%
We start with 4 compiled functions and shrink to just one.
Previously, ZJIT miscompiled the following because of native SP
interference.
def a(n1,n2,n3,n4,n5,n6,n7,n8) = [n8]
a(0,0,0,0,0,0,0, :ok)
Commented problematic disassembly:
; call rb_ary_new_capa
mov x0, #1
mov x16, #0x1278
movk x16, #0x4bc, lsl #16
movk x16, #1, lsl #32
blr x16
; call rb_ary_push
mov x1, x0
str x1, [sp, #-0x10]! ; c_push() from alloc_regs()
mov x0, x1 ; arg0, the array
ldur x1, [sp] ; meant to be arg1=n8, but sp just moved!
mov x16, #0x3968
movk x16, #0x4bc, lsl #16
movk x16, #1, lsl #32
blr x16
Since the frame pointer stays constant in the body of the function,
static offsets based on it don't run the risk of being invalidated by SP
movements.
Pass the registers to preserve through Insn::FrameSetup. This allows ARM
to use STP and waste no gaps between EC, SP, and CFP.
x86 now preserves and restores RBP since we use it as the frame pointer.
Since all arches now have a frame pointer, remove offset based SP
movement in the epilogue and restore registers using the frame pointer.
Previously `no_dead_mov_from_vreg` generated:
0x0: ldur x0, [x0]
0x4: mov x0, x0
0x8: ret
Because of phase ordering. Split couldn't recognize that the no-op mov
because at that point it sees a `VReg`.
ZJIT: Support invalidating method redefinition
This commit adds support for the MethodRedefined invariant to be invalidated
when a method is redefined.
Changes:
- Added CME pointer to the MethodRedefined invariant in HIR
- Updated all places where MethodRedefined invariants are created to
include the CME pointer
- Added handling for MethodRedefined invariants in gen_patch_point to
call track_cme_assumption, which registers the patch point for
invalidation when rb_zjit_cme_invalidate is called
This ensures that when a method is redefined, all JIT code that
depends on that method will be properly invalidated.
Use `fixnum_from_isize` instead of `fixnum_from_usize` in
`fold_fixnum_bop` to properly handle negative values. Casting negative
`i64` to `usize` produces large unsigned values that exceed `RUBY_FIXNUM_MAX`.
This issues writebarriers for objects added via gc_offsets or by
profiling. This may be slower than writebarrier_remember, but we would
like it to be more debuggable.
Co-authored-by: Max Bernstein <ruby@bernsteinbear.com>
Co-authored-by: Stan Lo <stan001212@gmail.com>
Fixes `TestZJIT::test_require_rubygems`. It was crashing locally due to
false collection of a live object. See
<https://alanwu.space/post/write-barrier/>.
Co-authored-by: Max Bernstein <max@bernsteinbear.com>
Co-authored-by: Takashi Kokubun <takashi.kokubun@shopify.com>
Co-authored-by: Stan Lo <stan.lo@shopify.com>
Previously, my buggy optimization would turn `asm.sub(imm, reg)`
into `subs out, reg, imm` since it runs through the addition path which
relies on the commutative property. Don't do that because subtraction
does not commute. Good thing no one seems to use this form.
Also, delete the 2 regs match arm for Add because it's already covered
by the fallback arm -- both split_load_operand() and
split_shifted_immediate() are no-op when the input is a register.
Fixes: 1317377fa7 ("ZJIT: A64: Have add/sub to SP be
single-instruction")
The raw bytes didn't disassemble to the disassembly, but we missed this
since CI didn't run `make zjit-test` with the disasm feature.
Fixes: 1317377fa7 ("ZJIT: A64: Have add/sub to SP be
single-instruction")
This is so that e.g. building with `--enable-zjit=dev` will test with the
disassembly feature. It makes more sense, saves on build time and
reveals that
`backend::arm64::tests::sp_movements_are_single_instruction` was in
fact failing with the `disasm` feature.
* ZJIT: Add test exclusions for ZJIT
* ZJIT: Update test targets and documentation
- Rename `zjit-test-all` to `zjit-check`
- Add `zjit-test-all` target to zjit.mk to run all Ruby tests with ZJIT enabled
excluding known failing tests
- Update documentation and CI workflow to reflect the new targets
Previously, gen_param() access slots at `SP-x` for `x≥0` after subtracting from
SP, so it was accessing slots from above the top of the stack. Also, the
slots gen_entry_params() wrote to at entry point did not correspond to
the slots access inside the JIT function.
Redo the stack frame layout so that inside the function slots are at
`SP+x`. Write to those slots in the entry point by anticipating the size
of the frame.
Fixes test_spilled_method_args().
Now that params can be in memory, this particular load_into() was
panicking with "Invalid operands for LDUR" with
test_spilled_method_args() on ARM.
Since it's documented to be for register destinations let's validate it.