Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/ir/constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 17 additions & 1 deletion src/passes/ConstraintAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,31 @@ 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<Index>(&parsed->constraint.term)) {
relevantLocals[*other] = true;
}
}
}

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).
// TODO: when values are constant and non-NaN, we could optimize even
// without fast-math
return !type.isFloat() || fastMath;
}

void doWalkFunction(Function* func) {
fastMath = getPassOptions().fastMath;

relevantLocals.assign(func->getNumLocals(), false);

Super::doWalkFunction(func);
Expand Down
138 changes: 138 additions & 0 deletions test/lit/passes/constraint-analysis-float.wast
Original file line number Diff line number Diff line change
@@ -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)
)
)
)
54 changes: 0 additions & 54 deletions test/lit/passes/constraint-analysis.wast
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading