Skip to content

ZJIT: Redundant GuardType remains at CFG join blocks #978

Description

@dak2

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions