Skip to content

Commit 2016d85

Browse files
committed
ZJIT: Preserve argument profiles in polymorphic dispatch arms
Each arm of a polymorphic dispatch takes a fresh Snapshot so that the receiver resolves from its refined type instead of the polymorphic profile. However, the fresh Snapshot had no ProfileOracle entries at all, so argument-profile-dependent specializations were disabled in every arm: e.g. Array#[] stayed a CCallVariadic because likely_a could not see the Fixnum profile of the index. Copy the profile entries from the original Snapshot to the per-arm Snapshot, excluding only the receiver's entry (chased through guards), since resolve_receiver_type prefers profiles over static types. On optcarrot, this lets the Array arm of the polymorphic `@fetch[addr]` callsites inline ArrayAref, removing ~3.9M frame-pushing Array#[] calls per iteration (53% of all not-inlined cfunc calls).
1 parent 3da63e6 commit 2016d85

2 files changed

Lines changed: 94 additions & 3 deletions

File tree

zjit/src/hir.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7680,6 +7680,23 @@ impl ProfileOracle {
76807680
self.types.entry(*snapshot).or_default().extend(entries.iter().cloned());
76817681
}
76827682
}
7683+
7684+
/// Copy the profile entries recorded for the `src` Snapshot to the `dst` Snapshot, excluding
7685+
/// entries for `exclude` (chased through guards). Used by polymorphic dispatch, where each
7686+
/// refined arm gets a fresh Snapshot: the receiver must resolve from its refined type rather
7687+
/// than the polymorphic profile, but the other operands' profiles should remain visible so
7688+
/// argument-profile-dependent specializations (e.g. Array#[]) still apply.
7689+
fn copy_entries_except(&mut self, src: InsnId, dst: InsnId, exclude: InsnId, fun: &Function) {
7690+
let Some(entries) = self.types.get(&src) else { return };
7691+
let exclude = fun.chase_insn(exclude);
7692+
let filtered: Vec<_> = entries.iter()
7693+
.filter(|(insn, _)| fun.chase_insn(*insn) != exclude)
7694+
.cloned()
7695+
.collect();
7696+
if !filtered.is_empty() {
7697+
self.types.insert(dst, filtered);
7698+
}
7699+
}
76837700
}
76847701

76857702
fn invalidates_locals(opcode: u32, operands: *const VALUE) -> bool {
@@ -9150,6 +9167,12 @@ fn add_iseq_to_hir(
91509167
// its refined, exact type instead of the polymorphic profile that is
91519168
// keyed at exit_id.
91529169
let snapshot = fun.push_insn(iftrue_block, Insn::Snapshot { state: Box::new(exit_state.clone()) });
9170+
// Keep the other operands' profile entries visible at the fresh
9171+
// Snapshot so the specialized send can still see argument profiles
9172+
// (e.g. Array#[] needs a Fixnum-profiled index to be inlined). Only
9173+
// the receiver's entry is dropped: it must resolve from its refined,
9174+
// exact type, and resolve_receiver_type prefers profiles over types.
9175+
profiles.copy_entries_except(exit_id, snapshot, recv, fun);
91539176
let refined_recv = fun.push_insn(iftrue_block, Insn::RefineType { val: recv, new_type: expected });
91549177
let send = fun.push_insn(iftrue_block, Insn::Send { recv: refined_recv, cd, block: None, args: args.clone(), state: snapshot, reason: Uncategorized(opcode) });
91559178
fun.push_insn(iftrue_block, Insn::Jump(BranchEdge { target: join_block, args: vec![send] }));

zjit/src/hir/opt_tests.rs

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16855,6 +16855,71 @@ mod hir_opt_tests {
1685516855
");
1685616856
}
1685716857

16858+
#[test]
16859+
fn specialize_polymorphic_send_preserves_argument_profiles() {
16860+
// Each arm of a polymorphic dispatch must still see the profiled types of
16861+
// the non-receiver arguments: the Array arm below can only inline ArrayAref
16862+
// if the Fixnum profile of `i` survives into the arm's Snapshot.
16863+
set_call_threshold(4);
16864+
eval("
16865+
def test(a, i)
16866+
a[i]
16867+
end
16868+
16869+
test([10, 20], 1)
16870+
test({1 => 2}, 1)
16871+
test([10, 20], 1)
16872+
test({1 => 2}, 1)
16873+
");
16874+
assert_snapshot!(hir_string("test"), @"
16875+
fn test@<compiled>:3:
16876+
bb1():
16877+
EntryPoint interpreter
16878+
v1:BasicObject = LoadSelf
16879+
v2:CPtr = LoadSP
16880+
v3:BasicObject = LoadField v2, :a@0x1000
16881+
v4:BasicObject = LoadField v2, :i@0x1001
16882+
Jump bb3(v1, v3, v4)
16883+
bb2():
16884+
EntryPoint JIT(0)
16885+
v7:BasicObject = LoadArg :self@0
16886+
v8:BasicObject = LoadArg :a@1
16887+
v9:BasicObject = LoadArg :i@2
16888+
Jump bb3(v7, v8, v9)
16889+
bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject):
16890+
v21:CBool = HasType v12, ArrayExact
16891+
CondBranch v21, bb5(), bb6()
16892+
bb5():
16893+
v24:ArrayExact = RefineType v12, ArrayExact
16894+
PatchPoint NoSingletonClass(Array@0x1008)
16895+
PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018)
16896+
v43:Fixnum = GuardType v13, Fixnum
16897+
v44:CInt64 = UnboxFixnum v43
16898+
v45:CInt64 = ArrayLength v24
16899+
v46:CInt64 = GuardLess v44, v45
16900+
v47:CInt64 = AdjustBounds v46, v45
16901+
v48:CInt64[0] = Const CInt64(0)
16902+
v49:CInt64 = GuardGreaterEq v47, v48
16903+
v50:BasicObject = ArrayAref v24, v49
16904+
Jump bb4(v50)
16905+
bb6():
16906+
v27:CBool = HasType v12, HashExact
16907+
CondBranch v27, bb7(), bb8()
16908+
bb7():
16909+
v30:HashExact = RefineType v12, HashExact
16910+
PatchPoint NoSingletonClass(Hash@0x1040)
16911+
PatchPoint MethodRedefined(Hash@0x1040, []@0x1010, cme:0x1048)
16912+
v54:BasicObject = HashAref v30, v13
16913+
Jump bb4(v54)
16914+
bb8():
16915+
v33:BasicObject = Send v12, :[], v13 # SendFallbackReason: Send: polymorphic call site
16916+
Jump bb4(v33)
16917+
bb4(v20:BasicObject):
16918+
CheckInterrupts
16919+
Return v20
16920+
");
16921+
}
16922+
1685816923
#[test]
1685916924
fn specialize_polymorphic_send_fixnum_and_bignum() {
1686016925
// Fixnum and Bignum both have class Integer, but they should be
@@ -18519,9 +18584,11 @@ mod hir_opt_tests {
1851918584
30.times { test_float_mul_recompile(-0.0, 1.5) }
1852018585
"#);
1852118586

18587+
// After recompiling, the HeapFloat arm of the polymorphic dispatch must
18588+
// not speculate on Flonum; it falls back to CCallWithFrame. The Flonum
18589+
// arm still inlines FloatMul, guarded on the Flonum-profiled argument.
1852218590
let final_hir = hir_string("test_float_mul_recompile");
1852318591
assert!(final_hir.contains("CCallWithFrame"), "{final_hir}");
18524-
assert!(!final_hir.contains("FloatMul"), "{final_hir}");
1852518592
assert_snapshot!(format!("{intermediate_hir}\n{final_hir}"), @"
1852618593
fn test_float_mul_recompile@<compiled>:2:
1852718594
bb1():
@@ -18573,8 +18640,9 @@ mod hir_opt_tests {
1857318640
bb7():
1857418641
v30:Flonum = RefineType v12, Flonum
1857518642
PatchPoint MethodRedefined(Float@0x1008, *@0x1010, cme:0x1018)
18576-
v45:BasicObject = CCallWithFrame v30, :Float#*@0x1040, v13
18577-
Jump bb4(v45)
18643+
v45:Flonum = GuardType v13, Flonum recompile
18644+
v46:Float = FloatMul v30, v45
18645+
Jump bb4(v46)
1857818646
bb8():
1857918647
v33:BasicObject = Send v12, :*, v13 # SendFallbackReason: Send: polymorphic call site
1858018648
Jump bb4(v33)

0 commit comments

Comments
 (0)