1523 Commits

Author SHA1 Message Date
Takashi Kokubun
384c0fd832
ZJIT: Import hir in codegen.rs (#18134) 2026-07-31 17:12:35 -07:00
Max Bernstein
4ac0baa7a3 ZJIT: Reserve space in new_insns
We know the output of the pass will be roughly the same size as the
input of the pass, so don't start from scratch with an empty vec that
will require multiple re-allocations.
2026-07-31 15:23:50 -04:00
Max Bernstein
a89e3b15c7 ZJIT: Remove some uses of find() in codegen 2026-07-31 15:23:50 -04:00
Max Bernstein
7d9cd30b10 ZJIT: Don't use find() in remove_redundant_patch_points
Cuts pass time from 65ms to 35ms on lobsters.
2026-07-31 15:23:50 -04:00
Max Bernstein
dead5c7143 ZJIT: Don't use find() in clean_cfg
Cuts pass time from 68ms to 63ms on lobsters.
2026-07-31 15:23:50 -04:00
Max Bernstein
dee839489d ZJIT: Don't use find() in optimize_load_store
Cut pass time from ~80ms to ~50ms on lobsters.
2026-07-31 15:23:50 -04:00
Max Bernstein
da1b4f7539 ZJIT: Don't use find() in convert_no_profile_sends
Cut strength reduce time from ~200ms to ~180ms on lobsters.
2026-07-31 15:23:50 -04:00
Max Bernstein
dc76e876b4 ZJIT: Don't use find() in fold_constants
Cut constant folding time from ~70ms to ~40ms on lobsters.
2026-07-31 15:23:50 -04:00
Daichi Kamiyama
825c457945
ZJIT: Add test coverage for the ArrayMax and ArrayMin (#18030)
- Codegen tests for `ArrayMin` (mirroring the existing `ArrayMax` ones)
- HIR opt tests asserting that `ArrayMax` / `ArrayMin` are emitted
- An Array#min redefinition test mirroring `test_dont_optimize_array_max_if_redefined`
2026-07-30 17:13:11 -07:00
Daichi Kamiyama
9bff5cc2f5
ZJIT: Add coverage test for polymorphic ISEQ dispatch at one call site (#17467)
A monomorphic-specialized call site (warmed up with class A) must still
return correct results when a second class B with its own ISEQ method of
the same name flows through it. The receiver type guard should send the B
receiver to the side exit rather than dispatching to A#foo.

This side-exit-on-unexpected-class behavior was not covered by an existing
test. Distinct return values (1 vs 2) make a broken guard observable.
2026-07-30 17:12:51 -07:00
Max Bernstein
3e9592ec34
ZJIT: Assert that we never profile Qundef (#18128) 2026-07-30 22:10:36 +00:00
XrXr
f2ff724683 ZJIT: Run doc tests on CI
So code in documentation are tested.
2026-07-30 16:06:37 -04:00
XrXr
41794563e0 ZJIT: Simplify way to disable coercion for PtrPrintMap 2026-07-30 16:06:37 -04:00
Max Bernstein
daab420843
ZJIT: Simplify and optimize ProfiledType::new (#18126)
Reduce the number of branches and only look up the shape once instead of
twice.

Brings a profiling-heavy run (profiling for 1000 calls) of lobsters from
700ms to 660ms profile time. Unfortunately getting the current time for
stats is still 50% of all profile time.
2026-07-30 14:22:38 -04:00
Nozomi Hijikata
03e2f86c0a
ZJIT: Profile caller splat length distributions (#17967)
* ZJIT: Profile caller splat length distributions

Related to: https://github.com/Shopify/ruby/issues/1004

Before specializing sends with caller splats, collect the distribution of
caller splat array lengths.

The splat length determines how many `ArrayAref` operations and positional
arguments a `SendDirect` path needs. Specializing for only one observed length
could cause repeated side exits when a call site sees varying lengths, so we
need to understand whether these profiles are monomorphic or polymorphic first.

This does not change caller splat specialization or fallback behavior yet.

* ZJIT: Refine caller splat length profiling

Store splat length distributions in a side map since only splat call
sites use them. Record unknown lengths instead of dropping those samples
so the length profile stays aligned with the operand type profile.

Co-authored-by: Max Bernstein <tekknolagi@gmail.com>
2026-07-30 11:09:06 -07:00
Peter Zhu
a69bb3ab60 Implement GC fast path for gen_new_range
Implements GC fast path for newrange instruction when we do not need to
call <=> method (when either endpoint is nil or both are fixnums).

Benchmark:

    def run(max)
      i = 0
      while i < max
        a = i..max
        i += 1
      end
    end

    30.times { run(10) }
    run(10_000_000)

Result:

    Benchmark 1: master
      Time (mean ± σ):     221.8 ms ±   5.2 ms    [User: 214.4 ms, System: 5.8 ms]
      Range (min … max):   216.4 ms … 235.8 ms    13 runs

    Benchmark 2: branch
      Time (mean ± σ):      62.4 ms ±   1.6 ms    [User: 56.0 ms, System: 5.1 ms]
      Range (min … max):    60.7 ms …  69.7 ms    46 runs

    Summary
      branch ran
        3.56 ± 0.12 times faster than master
2026-07-29 16:06:56 -04:00
Takashi Kokubun
20449aa178 ZJIT: Use TooManyArgsForLir reason for oversized direct block calls 2026-07-29 11:10:02 -07:00
Takashi Kokubun
6ea07a4824 Fix a comment about too many args
Co-authored-by: Kevin Menard <kevin@nirvdrum.com>
2026-07-29 11:10:02 -07:00
Takashi Kokubun
8b2bd6a500 ZJIT: Add HIR opt tests for oversized direct block calls 2026-07-29 11:10:02 -07:00
Takashi Kokubun
3731d6659f ZJIT: Reject direct block calls with too many args for LIR
The invokeblock fast path (InvokeBlockIseqDirect) passes captured self
plus each block argument in C argument registers, but
block_call_inlinable_iseq never bounded the argument count. Yielding 6+
arguments to a matching-arity block made gen_invoke_block_iseq_direct
emit a CCall with more operands than C_ARG_OPNDS, which aborts the
process in the backend:

  assertion failed: data.opnds.len() <= C_ARG_OPNDS.len()
  at zjit/src/backend/x86_64/mod.rs:302

Reject such blocks in block_call_inlinable_iseq so both the guarded and
the inlined-caller yield paths fall back to VM dispatch.
2026-07-29 11:10:02 -07:00
Takashi Kokubun
72749d7df3 ZJIT: Allowlist pointee types in PtrPrintMap::map_ptr
Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
2026-07-29 11:09:17 -07:00
Takashi Kokubun
ab191be2f5 ZJIT: Only accept raw pointers in PtrPrintMap::map_ptr
Previously map_ptr<T>() took *const T, so a reference like &IseqPtr
silently coerced to *const IseqPtr and mapped the address of the
reference itself instead of the pointer value, which is how the bug
fixed by the previous commit slipped in.

Constrain the parameter with an ActualPtr trait implemented only for
*const T and *mut T, so passing a reference no longer compiles.

Co-authored-by: Max Bernstein <tekknolagi@gmail.com>
2026-07-29 11:09:17 -07:00
Takashi Kokubun
821af045b6 ZJIT: Map pointer values instead of temporary addresses in HIR dump 2026-07-29 11:09:17 -07:00
Max Bernstein
9f6753e683 ZJIT: Use BitSet, not HashSet, for LIR block sets
No need for hashing in LIR RPO.
2026-07-29 14:05:23 -04:00
Max Bernstein
4a6e60e641 ZJIT: Rewrite LIR successors function to avoid cloning edges
We only need the target blocks, not the argument vectors.
2026-07-29 14:05:23 -04:00
Max Bernstein
72cf6fff92
ZJIT: Make UnboxFixnum effectless (#18103)
It's just an integer right shift.
2026-07-29 14:42:03 +00:00
Max Bernstein
a32dded07d
ZJIT: Iterate over state in SetLocal (#18102)
We added state to `SetLocal` but didn't change `for_each_operand_impl`.
2026-07-29 10:09:51 -04:00
Daichi Kamiyama
d13a49fc50
ZJIT: Fix kwargs handling in invokeblock ifunc specialization (#17971)
gen_invokeblock_ifunc drops the callinfo and passes argv straight to
rb_vm_yield_with_cfunc, which hardcodes kw_splat = 0, so keyword
arguments lost their keyword-ness. Gate the ifunc specialization on
block_call_inlinable, the same check used for the inline ISEQ path.
2026-07-28 17:57:20 -07:00
Takashi Kokubun
cf81909161
Reapply "ZJIT: Add counters for throw and exception_handler" (#17945) (#18099)
This reverts commit ff6ac97529962b824d528b4bf32485179bb25805.
2026-07-28 14:31:02 -07:00
Nozomi Hijikata
438674be00
ZJIT: Require trivial inline target to end at leave (#18063)
* ZJIT: Fix trivial inlining for optional argument entries

Use SendDirect's jit_entry_idx when reading the callee bytecode for
trivial inlining, so optional-argument calls inspect the same entry
point that SendDirect selected.

This avoids inlining default-argument initialization code when the
caller actually provided the optional positional argument.

* ZJIT: Require trivial inline target to end at leave

Limit the trivial inliner to whole-ISEQ shapes that start at bytecode
index 0 and end with the second instruction's leave. Optional argument
entries can start in the middle of the ISEQ, so keep those SendDirect
calls for the general inliner, which handles jit_entry_idx.

This avoids folding default-argument initialization code when the caller
actually supplied the optional positional argument.

Co-authored-by: Takashi Kokubun (k0kubun) <takashikkbn@gmail.com>
Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
2026-07-28 10:01:24 -07:00
XrXr
2529d20b7e ZJIT: A64: Delete emit_jmp_ptr_with_invalidation()
It's only for page switching, which is not a thing in ZJIT.
2026-07-27 14:22:57 -07:00
XrXr
28d74f4a11 ZJIT: A64: No padding for LIR Jump as we do not re-patch
Unlike in YJIT, we don't re-patch particular jump instructions, so there
is no need to pad.
2026-07-27 14:22:57 -07:00
Takashi Kokubun
fa14b4c051
ZJIT: Strength-reduce Integer#[] with a constant index (#18056) 2026-07-27 11:29:05 -07:00
XrXr
169e0f13fa ZJIT: Remove unnecessary &dyn usage in gc_fastpath.rs
This forces the caller to write `&` before each closure for no benefits.
It's also weird because another parameter uses `impl Fn...` so you end
up with a noisy asymmetry at the call site.

Use `impl Fn`.
2026-07-23 12:47:34 -07:00
Takashi Kokubun
37a852bdf5
ZJIT: Rename rb_jit_* functions to rb_builtin_* (#18039)
Rename rb_jit_* functions to rb_builtin_*
2026-07-23 15:32:57 -04:00
Kevin Menard
b2cf20fd49
ZJIT: Read block successors by reference instead of cloning the terminator (#18016)
`Function::successors` went through `find()`, which clones the entire terminator `Insn` on every call just to read its `BlockId` targets. Reverse-post-order traversal queries every block's successors many times over the course of optimization, so that clone occurred rather frequently. The new iterator reads the targets from a borrowed terminator and does not allocate at all.

Switching this to an iterator also avoids allocating a `Vec` for callers that just want to iterate through the successors.
2026-07-23 15:00:40 -04:00
Felix Bünemann
3394978813 ZJIT (arm64): reserve one branch for in-range patch points
jmp_ptr_bytes() sets how much room to reserve for a patchable jump -- it
spaces out patch points (PadPatchPoint) and bounds conditional jump width.
It was hardcoded to 5 instructions, an absolute load-address plus br, with
the check for the cheap case left commented out.

An arm64 `b` reaches +/-128MiB, so one 4-byte branch suffices whenever the
code region fits in that range, and the default --zjit-exec-mem-size is
64MiB. emit_jmp_ptr already emitted just a `b` there; only the reservation
was oversized, padding out up to 5x more nops than needed. Enable the check,
adding the CodeBlock::virtual_region_size() accessor it calls (YJIT has the
same one).

Two instructions in an S-byte region are at most S-4 bytes apart, so the
bound tests (S-4)/4. Testing S/4 would give up at exactly 128MiB, where
S/4 is 2^25 and the widest imm26 offset is 2^25-1.

The two emit_je snapshots shrink accordingly: same instructions, fewer
trailing nops. A new unit test pins both sides of the 128MiB boundary.

On a branch-heavy while loop (five `if`s per iteration), --zjit-dump-disasm
goes from 312 nops / 968 instructions to 29 / 685, and the loop runs 1.70x
faster under benchmark-driver; a 3-way if/elsif loop 1.23x. The win is
concentrated in branchy code, which is where the padding was. x86 is
unaffected; its 5-byte jmp reservation is already minimal.

Checked with `make zjit-test` (1817 pass), test_zjit_cli.rb (23 pass), and
bootstraptest under --zjit at call thresholds 1 and 2, both at the default
size and at --zjit-exec-mem-size=128, the boundary case.
2026-07-23 09:06:22 -07:00
Kevin Menard
024f201f76
Avoid a direct function-to-integer cast in a ZJIT test. (#18014)
Rust 1.93 added a warn-by-default lint for casting function items directly to integer types.
2026-07-22 21:15:57 +00:00
Peter Zhu
9438d7fd1e Implement GC fast path for new_range_fixnum
Benchmark:

    i = 0
    def run(max)
      i = 0
      while i < max
        a = i..100
        i += 1
      end
    end

    30.times { run(2) }
    run(10_000_000)

Result:

    Benchmark 1: master
      Time (mean ± σ):     229.8 ms ±   3.2 ms    [User: 221.7 ms, System: 6.2 ms]
      Range (min … max):   226.3 ms … 235.2 ms    12 runs

    Benchmark 2: branch
      Time (mean ± σ):      67.4 ms ±   2.0 ms    [User: 60.4 ms, System: 5.3 ms]
      Range (min … max):    65.1 ms …  75.1 ms    43 runs

    Summary
      branch ran
        3.41 ± 0.11 times faster than master
2026-07-22 15:32:04 -04:00
Takashi Kokubun
a77ea4a4a0
ZJIT: Allow inlining methods with rest parameters (#17995) 2026-07-21 16:29:38 -04:00
Takashi Kokubun
a5c7644944 ZJIT: Keep surviving JIT frame across longjmp
A tag established inside a live ZJIT frame can catch a longjmp without unwinding that native frame. Record that fact on rb_vm_tag and materialize only frames above the target, preserving its JITFrame link and keeping JITFrame saves at a single store.
2026-07-21 14:14:32 -04:00
Jean Boussier
40f105bd55 Change RStruct to match SHAPE_ID_LAYOUT_EXTENDED
THe downside is that we now always reserve one 8B reference for the
IMEMO/fields, even for structs that never have any ivars.

But it is an acceptable tradeoff given that ivars on structs aren't
rare.

Co-Authored-By: John Hawthorn <john@hawthorn.email>
2026-07-21 08:30:12 +02:00
Takashi Kokubun
5628b33431
ZJIT: Fix C function pointer normalization (#17947)
The HIR printer passed references to cfunc fields into PtrPrintMap, so snapshots depended on addresses of cloned instruction storage.

Normalize the stored function pointers themselves and cover all C call instruction variants.
2026-07-16 19:46:26 -07:00
Luke Gruber
1aebcb61d0 ZJIT: duparray allocation fastpath for arrays we embed
We can reuse some of this work for gen_new_array fastpath for
embedded arrays.
2026-07-16 22:32:15 -04:00
Takashi Kokubun
ff6ac97529
Revert "ZJIT: Add counters for throw and exception_handler" (#17945)
Revert "ZJIT: Add counters for throw and exception_handler (#17902)"

This reverts commit 61441f2d7242148cc45efdf6f3ebce4c43eaf7c6.
2026-07-16 11:33:46 -07:00
Max Bernstein
5e92b4b6d2 ZJIT: Measure total amount of stack space reserved
This is useful for seeing general impact of optimizations. For example,
upcoming SSA minimization.
2026-07-16 11:18:10 -07:00
Max Bernstein
bd62a1fa18 ZJIT: Loop, not bail, for large FrameSetup
Sometimes we have too many stack slots to fit in one sub operand on
ARM. That's fine. We can iteratively subtract instead of doing it all in
one instruction.
2026-07-16 11:18:10 -07:00
Max Bernstein
d5970a189a
ZJIT: Trace fallbacks in Perfetto (#17896)
This lets us see stack traces for send, ivar fallbacks.
2026-07-16 17:56:01 +00:00
Takashi Kokubun
61441f2d72
ZJIT: Add counters for throw and exception_handler (#17902) 2026-07-16 10:46:57 -07:00
Takashi Kokubun
c4e1e1cad3
ZJIT: Re-profile whole ISEQ in non-final versions (#17915)
Reset each existing instruction profile and reinstall zjit_* instructions after compiling a non-final version. Do the same for inlined ISEQs so materialized frames can collect fresh profiles.

Let interpreter profiling instructions collect the samples and make recompile exits only observe whether the exiting instruction has finished profiling.
2026-07-16 09:42:53 -07:00