Skip to content

Domain-guard triage: debug remarks at analysis-fact consumers#2655

Open
kimjune01 wants to merge 2 commits into
EnzymeAD:mainfrom
kimjune01:scope/domain-guard-triage
Open

Domain-guard triage: debug remarks at analysis-fact consumers#2655
kimjune01 wants to merge 2 commits into
EnzymeAD:mainfrom
kimjune01:scope/domain-guard-triage

Conversation

@kimjune01

@kimjune01 kimjune01 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

A lightweight, reactive convention to make the next domain-narrowing wrong-code bug in NonNegativeResultAnalysis and friends fast to triage. This is the cheap slice of #2654: not the provenance graph, and not a runtime sanitizer.

The bug class

Several algebraic rewrites are licensed by a lightweight analysis fact (non_negative / no_nan / finite) that can be wrong. When the fact is wrong the rewrite silently narrows a domain, turning a finite value into NaN or dropping a load-bearing abs. Recent instances (#2648 / #2649, #2651 / #2652) were all found by hand. This convention shortens the next such find.

What it adds

A debug remark at each rewrite that trusts a domain-narrowing fact. DOMAIN_GUARD_TRUST(rewrite, predicate, value) is one LLVM_DEBUG line, wired at the consuming sites in EnzymeHLOOpt.cpp: ReduceMaxMinMulPositiveScalar, the two CompareOpCanon non_negative folds, its no_nan fold, and AbsPositiveSimplify.

The predicate and the value are already on the IR (setGuaranteedInIR writes e.g. enzymexla.non_negative = GUARANTEED and it survives to the pass output). The remark adds the only thing that is not on the IR: the name of the consuming rewrite, plus a time-ordered trace. A later domain-narrowing NaN is then one grep ([domain-guard]) from the rewrite that produced it.

  • Zero always-on cost. LLVM_DEBUG compiles out under NDEBUG and is a single DebugFlag branch otherwise. No lit test uses -debug-only, so FileCheck is unaffected. Enable with -debug-only=enzymehloopt.
  • No pass-driver changes. The macro plus five one-line call sites.
  • Self-perpetuating. LogSimplify's log(a*a) / log(a*b) folds consume no fact today (that is log(a*a) → 2*log(a) returns NaN for negative inputs #2570); they become remark sites the moment a guard lands there, and the shared macro makes that a one-liner.

A guard regression test (compare_domain_guard.mlir): an unknown-sign, possibly-NaN operand must not let compare(x, const) collapse to a boolean constant. Guard-only by design (it asserts a stablehlo.compare survives, not an exact folded form), so it is robust to unrelated canonicalizations and forbids only the fold-to-constant that the domain-narrowing failure shape would produce.

Scope

Deliberately just the reactive slice. The archetype fixes live in their own narrow PRs (#2649, #2652). The full decision-provenance system is #2654. A separate soundness gap surfaced while reviewing this (integer overflow, #2656) is filed for the maintainers to weigh, not fixed here.

No local build available (heavy LLVM/MLIR/XLA), so lit CHECK lines are reasoned against the pass logic, not run. CI is the verifier.

Add DOMAIN_GUARD_TRUST, an LLVM_DEBUG one-liner emitted at each rewrite that
trusts a domain-narrowing analysis fact (non_negative / no_nan). Instruments
the five consuming sites on main: ReduceMaxMinMulPositiveScalar, the two
CompareSimplify non_negative folds, the CompareSimplify no_nan fold, and
AbsPositiveSimplify.

The predicate and trusted value are already on the IR via setGuaranteedInIR;
the remark adds the only missing bit -- the consuming rewrite's name -- plus a
grep anchor ([domain-guard]) and a time-ordered trace, so a later
domain-narrowing NaN is one grep from the rewrite that produced it. Zero
always-on cost: LLVM_DEBUG compiles out under NDEBUG; no lit test uses
-debug-only, so FileCheck is unaffected.

LogSimplify's log(a*a)/log(a*b) folds consume no fact on main (that is EnzymeAD#2570);
they become remark sites once their guard lands -- the shared macro makes that
a one-liner.

No local build available (heavy LLVM/MLIR/XLA); this is source-reasoned, CI is
the verifier. See scope/domain-guard-triage/SCOPING.md (gitignored).
The abs (abspositive.mlir test5/test6) and reduce (reduce_max_min_mul_positive_
scalar.mlir reduce_max_mul_no_guarantee) consumers already have negative/
unknown-sign edge coverage. The CompareOpCanon non_negative/no_nan folds did
not. Add compare_domain_guard.mlir: an unknown-sign, possibly-NaN operand must
not let compare(x, const) collapse to a boolean constant.

Guard-only by design -- it asserts a stablehlo.compare survives, not an exact
folded form -- so it is robust to unrelated operand/direction canonicalizations
and forbids only the fold-to-constant that the EnzymeAD#2648/EnzymeAD#2651 failure shape would
produce. Passes on current main (the guard already holds there); it is a
regression tripwire, not a fix.

No local build (heavy LLVM/MLIR/XLA); CHECK lines reasoned, not run. CI verifies.
@kimjune01 kimjune01 changed the title NonNegativeResultAnalysis soundness fixes + domain-guard triage convention Domain-guard triage: debug remarks at analysis-fact consumers Jul 23, 2026
@kimjune01
kimjune01 force-pushed the scope/domain-guard-triage branch from 9f78c7b to bb3cf38 Compare July 23, 2026 02:26
@kimjune01
kimjune01 marked this pull request as ready for review July 23, 2026 02:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant