Summary
The current ZJIT HIR optimizer cannot eliminate redundant GuardType instructions at CFG (Control Flow Graph) join blocks.
When a value is already proven to have a narrower type inside each predecessor branch, the merge block re-guards it on every
call, leaving redundant runtime type checks in the JIT-compiled code.
Reproduction
def test(n, cond)
if cond
a = n + 1
else
a = n + 2
end
n + a
end
Optimized HIR (--zjit-dump-hir-opt):
bb3: # cond == true
v69:Fixnum = GuardType v14, Fixnum
v70 = FixnumAdd v69, 1
Jump bb5(v13, v14, ..., v70) # passes raw v14
bb4: # cond == false
v73:Fixnum = GuardType v38, Fixnum
v74 = FixnumAdd v73, 2
Jump bb5(v37, v38, ..., v74) # passes raw v38
bb5(v51, v52:BasicObject, ..., v54): # v52 inferred as BasicObject
v75:Fixnum = GuardType v52, Fixnum # redundant — both predecessors prove Fixnum
v79 = FixnumAdd v75, v54
The GuardType at v75 is guaranteed to succeed at runtime (both predecessors have already proven n is Fixnum),
yet it executes on every call.
Root cause
Each Jump passes the raw pre-guard value (v14 / v38: BasicObject) rather than the GuardType result (v69 / v73: Fixnum). The
merge-block parameter v52 is therefore inferred as union(BasicObject, BasicObject) = BasicObject, and fold_constants cannot
drop a guard whose input type is wider than the guard type.
Summary
The current ZJIT HIR optimizer cannot eliminate redundant GuardType instructions at CFG (Control Flow Graph) join blocks.
When a value is already proven to have a narrower type inside each predecessor branch, the merge block re-guards it on every
call, leaving redundant runtime type checks in the JIT-compiled code.
Reproduction
Optimized HIR (--zjit-dump-hir-opt):
The GuardType at v75 is guaranteed to succeed at runtime (both predecessors have already proven n is Fixnum),
yet it executes on every call.
Root cause
Each Jump passes the raw pre-guard value (v14 / v38: BasicObject) rather than the GuardType result (v69 / v73: Fixnum). The
merge-block parameter v52 is therefore inferred as union(BasicObject, BasicObject) = BasicObject, and fold_constants cannot
drop a guard whose input type is wider than the guard type.