Catching NaN at the MLIR Pass Boundary An MLIR pass can fold 0 * Inf into a quiet NaN attribute during constant folding, producing structurally valid but numerically meaningless IR that surfaces later as a distant accuracy regression in an NPU runtime, according to a technical account of the failure. The IEEE 754 standard defines 0 * Inf as NaN, and because NaN compares unequal to itself, a range check such as x < min || x > max accepts it as in-range. The recommended fix is to trace backward to the first bad pass, correct its arithmetic, and add a local operation verifier that enforces the numeric contract after every pass, since a runtime check observes only the symptom after the compiler/runtime boundary. A Multi-Level Intermediate Representation MLIR pass can create a not-a-number NaN attribute during constant folding. This can happen even when every operand starts as a valid value. The compiler should reject that value at the earliest intermediate representation IR boundary. Otherwise, the runtime exposes it later as a distant accuracy regression. The shortest reliable workflow traces the first bad pass and fixes its arithmetic. A local operation verifier then turns the operation's numeric contract into a check after every pass. I write fusion passes that reduce work in a neural processing unit NPU runtime. A later regression reports a lower accuracy metric on a held-out evaluation set, but the report does not identify the fusion pass. I trace the bad values backward through the model and runtime. A fused operation's output attribute already contains a quiet NaN before the runtime touches it. The fusion pass folds 0 Inf into that attribute. The runtime consumes the attribute and propagates the NaN through the remaining computation. The operation still type-checks, and its operands and results still align. The compiler therefore produces structurally valid but numerically meaningless IR. LLVM represents compile-time floating-point values with APFloat https://llvm.org/doxygen/classllvm 1 1APFloat.html . This type supports multiple floating-point formats and explicit rounding modes, so compiler code does not need to depend on the host machine's native floating-point behavior. The Institute of Electrical and Electronics Engineers IEEE 754 standard defines 0 Inf as NaN. Each operand can carry a valid meaning on its own, but their product has no numeric result. Other arithmetic paths can create the same class of value: Inf - Inf produces NaN because the difference has no defined value. Inf / Inf and 0 / 0 produce NaN because neither ratio has a defined value. Common arithmetic operations propagate an existing NaN. One bad fold can therefore spread through many downstream operations before the runtime reports a visible failure. NaN also compares unequal to itself. Every ordered comparison against NaN returns false, so a range check such as x < min || x max accepts NaN as though it falls inside the range. A direct finiteness check catches both NaN and infinity. A range check cannot replace that contract. A hardware description language HDL simulator uses X to represent an unknown logic state. An uninitialized register or timing violation can introduce one X , and downstream logic can propagate it far from its source. A NaN follows the same debugging shape. The final observation provides propagation evidence, while the first transition from a valid value to NaN identifies the defect. The analogy stops at propagation. X represents simulator uncertainty rather than a physical third logic value, while IEEE 754 defines NaN as a floating-point value with specified comparison and arithmetic behavior. This distinction does not change the debugging rule. Trace backward to the first source. Synopsys describes https://www.synopsys.com/blogs/chip-design/debugging-x-can-be-difficult.html the same process for register-transfer-level RTL and gate-level X propagation. An engineer follows drivers and fan-in signals until the earliest X occurs. The quiet NaN sits in an operation attribute before the graph reaches the runtime. The compiler can inspect the value at the exact boundary where the fusion pass creates it. A runtime numeric check observes the symptom after the value crosses the compiler/runtime boundary. It cannot identify which compiler pass first writes the attribute. The MLIR developer guide https://mlir.llvm.org/getting started/DeveloperGuide/ ir-verifier defines a contract for every pass. Each pass can assume valid input IR, and each pass must return valid output IR. The pass manager enforces this contract between passes by default. Callers can disable per-pass verification, but the valid-input, valid-output convention still defines correct pass behavior. Pass-boundary verification checks a pass's final output. A rewrite can use a transient invalid state internally, but it must restore all invariants before it returns. MLIR also tells operation verifiers to inspect local properties. A verifier can check the value of the operation's own attribute without following producers or consumers. This local rule preserves transformation freedom. It also limits rejection to an invariant that the operation itself defines. An MLIR pass pipeline runs in a fixed order. The first pass whose output contains NaN marks the source boundary. During initial triage, -mlir-print-ir-after-all prints IR after every pass. Once the output reveals the suspect pass, targeted flags show the two states that matter: -mlir-print-ir-before=