Skip to content

Commit 1cd2c5d

Browse files
nirvdrumtekknolagi
authored andcommitted
ZJIT: Replace guards we know can't pass with an unconditional side-exit
The resulting type of a `GuardType` we prove cannot pass is `Empty`. Any HIR instructions using the type information from the `GuardType` will also see `Empty` and propagate that type through the compiler. Any generated code would not be executable at run time due to the always failing guard, so there's no point in generating it.
1 parent 2155057 commit 1cd2c5d

2 files changed

Lines changed: 48 additions & 0 deletions

File tree

zjit/src/hir.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5509,6 +5509,19 @@ impl Function {
55095509
let mut new_insns = vec![];
55105510
for insn_id in old_insns {
55115511
let replacement_id = match self.find(insn_id) {
5512+
// TODO (nirvdrum 2026-06-26): Folding the guard to a SideExit is a workaround,
5513+
// not a proper fix. It relies on constant folding to keep an Empty-typed value
5514+
// (see below) from reaching codegen; disabling this pass would let that value
5515+
// through and the program would fail to compile on x86-64. Compilation correctness
5516+
// should not depend on an optimization pass, so this should be replaced by a
5517+
// comprehensive fix.
5518+
Insn::GuardType { val, guard_type, state, recompile } if !self.type_of(val).could_be(guard_type) => {
5519+
// The value's type is disjoint from the guard type, so the guard can never
5520+
// pass. Every execution would side-exit here, so we replace the guard with an
5521+
// unconditional exit. The terminator handling below then drops the rest of
5522+
// the block, which is now unreachable.
5523+
self.new_insn(Insn::SideExit { state, reason: SideExitReason::GuardType(guard_type), recompile })
5524+
}
55125525
Insn::GuardType { val, guard_type, .. } if self.is_a(val, guard_type) => {
55135526
self.make_equal_to(insn_id, val);
55145527
// Don't bother re-inferring the type of val; we already know it.

zjit/src/hir/opt_tests.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,6 +2512,41 @@ mod hir_opt_tests {
25122512
assert!(!insns.contains(&dead_const));
25132513
}
25142514

2515+
// A GuardType whose value type is disjoint from the guard type can never pass, so every
2516+
// execution side-exits there. fold_constants should replace the guard with an unconditional
2517+
// SideExit and drop the now-unreachable instructions that follow.
2518+
#[test]
2519+
fn test_fold_guard_type_that_can_never_pass_into_side_exit() {
2520+
let mut function = Function::new(std::ptr::null());
2521+
let entry = function.entry_block;
2522+
2523+
let state = function.push_insn(entry, Insn::Snapshot { state: FrameState::new(std::ptr::null()) });
2524+
// A nil constant is a NilClass, which is disjoint from Fixnum, so the guard below can
2525+
// never pass and the optimizer infers its result as Empty.
2526+
let nil = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
2527+
let guard = function.push_insn(entry, Insn::GuardType { val: nil, guard_type: types::Fixnum, state, recompile: None });
2528+
function.push_insn(entry, Insn::StoreField { recv: nil, id: FieldName::len, offset: 0, val: guard });
2529+
function.push_insn(entry, Insn::Return { val: guard });
2530+
function.seal_entries();
2531+
2532+
function.infer_types();
2533+
function.fold_constants();
2534+
2535+
let insns: Vec<Insn> = function.blocks[entry.0].insns.iter().map(|&id| function.find(id)).collect();
2536+
assert!(
2537+
insns.iter().any(|insn| matches!(insn, Insn::SideExit { .. })),
2538+
"expected the always-failing guard to be folded into a SideExit, got {insns:?}",
2539+
);
2540+
assert!(
2541+
!insns.iter().any(|insn| matches!(insn, Insn::GuardType { .. })),
2542+
"the always-failing GuardType should have been removed, got {insns:?}",
2543+
);
2544+
assert!(
2545+
!insns.iter().any(|insn| matches!(insn, Insn::StoreField { .. } | Insn::Return { .. })),
2546+
"instructions after the unconditional SideExit are unreachable and should have been dropped, got {insns:?}",
2547+
);
2548+
}
2549+
25152550
#[test]
25162551
fn test_eliminate_new_array() {
25172552
eval("

0 commit comments

Comments
 (0)