Skip to content

ZJIT: Dominator-scoped canonicalize for cross-block redundant guard elimination #983

@dak2

Description

@dak2

Background

PR ruby#16828 introduced a block-local HIR canonicalize pass(zjit/src/hir.rs:4977-5006).
For each block in RPO, it walks instructions and rewrites each operand through union-find plus a per-block rewrite_map keyed on the most recent Guard* result for that value.
The map is cleared at every block boundary (zjit/src/hir.rs:4980).

This covers two redundant-guard shapes:

  1. Within-block consecutive guards on the same value.
  2. CFG-join via block parameters: branch-edge args (Jump,CondBranch) get rewritten before the next block's clear(),
    so guard-narrowed values flow into merge-block parameters and infer_types + fold_constants can drop the redundant join-block guards.

Gap

A third shape is not caught: a value defined and guarded in a dominator block, then used directly in a dominated block without going through a block parameter.
ZJIT's HIR (Cranelift-style) allows direct references to values defined in any dominator, so this is a common pattern — early-return chains, nested conditionals, loops, etc.

Example:

def early_return(n)
  return -1 if n < 0    # bb0: GuardType %n (for FixnumLess)
  return  0 if n == 0   # bb2: GuardType %n (redundant)
  n * 2                 # bb4: GuardType %n (redundant)
end

In the HIR for this function, bb2 and bb4 reference %n directly (no block parameter).
I believe the block-local pass clears rewrite_map on entering them, so the %n → %3 rewrite established in bb0 is lost,
and I believe the redundant GuardType instructions in bb2 and bb4 survive all the way to machine code.

Follow-up from PR ruby#16828 review

@tekknolagi suggested this extension during PR review:

This can later (not in this PR, please) be extended to walk the dominator tree using a scoped hashmap or something so that we get cross-block results that don't correspond to block parameters.

(ruby#16828 (comment))

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