Skip to content

ZJIT: Redundant GuardType remains at CFG join blocks #978

@dak2

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions