From 8168a36c5379347d68b246f60f6257ac0466b891 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 11:46:47 -0700 Subject: [PATCH 1/3] fix --- src/passes/ConstraintAnalysis.cpp | 16 +- .../lit/passes/constraint-analysis-float.wast | 138 ++++++++++++++++++ test/lit/passes/constraint-analysis.wast | 54 ------- 3 files changed, 153 insertions(+), 55 deletions(-) create mode 100644 test/lit/passes/constraint-analysis-float.wast diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index f1b04cb5a79..0d513dfb3a8 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -146,7 +146,8 @@ struct ConstraintAnalysis void maybeMarkRelevant(Expression* curr) { // If this parses into a constraint on a local, that local is relevant. - if (auto parsed = LocalConstraint::parseCondition(curr)) { + if (auto parsed = LocalConstraint::parseCondition(curr); + parsed && isRelevantType(getFunction()->getLocalType(parsed->local))) { relevantLocals[parsed->local] = true; if (auto* other = std::get_if(&parsed->constraint.term)) { relevantLocals[*other] = true; @@ -154,7 +155,20 @@ struct ConstraintAnalysis } } + bool fastMath; + + bool isRelevantType(Type type) { + // Floating-point math does not follow the basic rules of logic (for + // example, NaN < NaN and NaN >= NaN are both false, despite the law of the + // excluded middle). Constraints follow the rules of logic, so we cannot + // operate on floats unless we have fast-math enabled (which assures us we + // can ignore NaNs). + return !type.isFloat() || fastMath; + } + void doWalkFunction(Function* func) { + fastMath = getPassOptions().fastMath; + relevantLocals.assign(func->getNumLocals(), false); Super::doWalkFunction(func); diff --git a/test/lit/passes/constraint-analysis-float.wast b/test/lit/passes/constraint-analysis-float.wast new file mode 100644 index 00000000000..348ec8d5941 --- /dev/null +++ b/test/lit/passes/constraint-analysis-float.wast @@ -0,0 +1,138 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Floating-point numbers are not optimized without fast-math, as NaN operations +;; do not follow the rules of logic and we might misoptimize them. + +;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s +;; RUN: wasm-opt %s --constraint-analysis --fast-math -all -S -o - | filecheck %s --check-prefix=FASTM + +(module + ;; CHECK: (func $float-zero (type $0) + ;; CHECK-NEXT: (local $f f64) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (f64.eq + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; FASTM: (func $float-zero (type $0) + ;; FASTM-NEXT: (local $f f64) + ;; FASTM-NEXT: (if + ;; FASTM-NEXT: (i32.const 1) + ;; FASTM-NEXT: (then + ;; FASTM-NEXT: (nop) + ;; FASTM-NEXT: ) + ;; FASTM-NEXT: ) + ;; FASTM-NEXT: ) + (func $float-zero + (local $f f64) + ;; When fast-math is enabled, we can optimize f == f to 1, as NaNs are not + ;; important. + (if + (f64.eq + (local.get $f) + (local.get $f) + ) + (then + (nop) + ) + ) + ) + + ;; CHECK: (func $float-negative-zero (type $0) + ;; CHECK-NEXT: (local $f f64) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (f64.eq + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: (f64.const -0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (f64.eq + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; FASTM: (func $float-negative-zero (type $0) + ;; FASTM-NEXT: (local $f f64) + ;; FASTM-NEXT: (if + ;; FASTM-NEXT: (i32.const 1) + ;; FASTM-NEXT: (then + ;; FASTM-NEXT: (nop) + ;; FASTM-NEXT: ) + ;; FASTM-NEXT: ) + ;; FASTM-NEXT: (if + ;; FASTM-NEXT: (i32.const 1) + ;; FASTM-NEXT: (then + ;; FASTM-NEXT: (nop) + ;; FASTM-NEXT: ) + ;; FASTM-NEXT: ) + ;; FASTM-NEXT: ) + (func $float-negative-zero + (local $f f64) + ;; Negative zero is equal to zero, even though it has a different bit + ;; pattern. Both conditions here should be optimized to 1 in fast-math + ;; mode (but not otherwise). + (if + (f64.eq + (local.get $f) + (f64.const -0) + ) + (then + (nop) + ) + ) + (if + (f64.eq + (local.get $f) + (f64.const 0) + ) + (then + (nop) + ) + ) + ) + + ;; CHECK: (func $float-ne-nan (type $1) (result i32) + ;; CHECK-NEXT: (local $nan f64) + ;; CHECK-NEXT: (local.set $nan + ;; CHECK-NEXT: (f64.const nan:0x8000000000000) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.ne + ;; CHECK-NEXT: (local.get $nan) + ;; CHECK-NEXT: (local.get $nan) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; FASTM: (func $float-ne-nan (type $1) (result i32) + ;; FASTM-NEXT: (local $nan f64) + ;; FASTM-NEXT: (local.set $nan + ;; FASTM-NEXT: (f64.const nan:0x8000000000000) + ;; FASTM-NEXT: ) + ;; FASTM-NEXT: (i32.const 0) + ;; FASTM-NEXT: ) + (func $float-ne-nan (result i32) + (local $nan f64) + (local.set $nan + (f64.const nan) + ) + ;; This should be 1: the number is a NaN, and NaN != NaN. However, we + ;; misoptimize this to 0 in fast-math mode (which is fine, as that mode + ;; ignores the possibility of NaNs). In normal mode we do not optimize, + ;; avoiding a misoptimization. + (f64.ne + (local.get $nan) + (local.get $nan) + ) + ) +) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 96f911de30a..d5590395f5b 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -4551,60 +4551,6 @@ ) ) - ;; CHECK: (func $float-negative-zero (type $1) - ;; CHECK-NEXT: (local $f f64) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; OPTIN: (func $float-negative-zero (type $1) - ;; OPTIN-NEXT: (local $f f64) - ;; OPTIN-NEXT: (if - ;; OPTIN-NEXT: (i32.const 1) - ;; OPTIN-NEXT: (then - ;; OPTIN-NEXT: (nop) - ;; OPTIN-NEXT: ) - ;; OPTIN-NEXT: ) - ;; OPTIN-NEXT: (if - ;; OPTIN-NEXT: (i32.const 1) - ;; OPTIN-NEXT: (then - ;; OPTIN-NEXT: (nop) - ;; OPTIN-NEXT: ) - ;; OPTIN-NEXT: ) - ;; OPTIN-NEXT: ) - (func $float-negative-zero - (local $f f64) - ;; Negative zero is equal to zero, even though it has a different bit - ;; pattern. Both conditions here should be optimized to 1. - (if - (f64.eq - (local.get $f) - (f64.const -0) - ) - (then - (nop) - ) - ) - (if - (f64.eq - (local.get $f) - (f64.const 0) - ) - (then - (nop) - ) - ) - ) - ;; CHECK: (func $tee (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $y i32) From f066c8437707a49441e6a641ce0ba2f61c587278 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 11:52:32 -0700 Subject: [PATCH 2/3] comment --- src/ir/constraint.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ir/constraint.h b/src/ir/constraint.h index f9f424949b9..d699fc4c424 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -19,6 +19,11 @@ // inference whether other things are true given a set of constraints, like // { x == 10 } => { x >= 5 }. // +// This code follows the basic rules of logic, like the law of the excluded +// middle and so forth. Things that do not follow basic logic, like floating- +// point NaNs (where both x < y and x >= y are possible, with NaNs), may not be +// handled correctly. The caller must ensure that no such input is possible. +// #ifndef wasm_ir_constraint_h #define wasm_ir_constraint_h From 88f168a3b18c22b91b7594a9dfb4218faa291554 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 27 Aug 2026 12:46:08 -0700 Subject: [PATCH 3/3] tood --- src/passes/ConstraintAnalysis.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 0d513dfb3a8..fff4e4b8ed3 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -163,6 +163,8 @@ struct ConstraintAnalysis // excluded middle). Constraints follow the rules of logic, so we cannot // operate on floats unless we have fast-math enabled (which assures us we // can ignore NaNs). + // TODO: when values are constant and non-NaN, we could optimize even + // without fast-math return !type.isFloat() || fastMath; }