summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2020-09-30 12:39:05 -0700
committerGitHub <noreply@github.com>2020-09-30 12:39:05 -0700
commit07047103a26e1c17ee995ef3e1358ddb26d8e8c8 (patch)
treeedaf22d19ab7c22c3cff58f9e02d8f41abcb5b3e /src
parent11de8894505d37b7b970a2103bc5b1cfd094b115 (diff)
downloadbinaryen-07047103a26e1c17ee995ef3e1358ddb26d8e8c8.tar.gz
binaryen-07047103a26e1c17ee995ef3e1358ddb26d8e8c8.tar.bz2
binaryen-07047103a26e1c17ee995ef3e1358ddb26d8e8c8.zip
Add --fast-math mode (#3155)
Similar to clang and gcc, --fast-math makes us ignore corner cases of floating-point math like NaN changes and (not done yet) lack of associativity and so forth. In the future we may want to have separate fast math flags for each specific thing, like gcc and clang do. This undoes some changes (#2958 and #3096) where we assumed it was ok to not change NaN bits, but @binji corrected us. We can only do such things in fast math mode. This puts those optimizations behind that flag, adds tests for it, and restores the interpreter to the simpler code from before with no special cases.
Diffstat (limited to 'src')
-rw-r--r--src/pass.h5
-rw-r--r--src/passes/OptimizeInstructions.cpp19
-rw-r--r--src/tools/optimization-options.h8
-rw-r--r--src/wasm/literal.cpp41
4 files changed, 27 insertions, 46 deletions
diff --git a/src/pass.h b/src/pass.h
index a3ee41d61..27e7ee37f 100644
--- a/src/pass.h
+++ b/src/pass.h
@@ -102,6 +102,11 @@ struct PassOptions {
// many cases.
bool lowMemoryUnused = false;
enum { LowMemoryBound = 1024 };
+ // Whether to allow "loose" math semantics, ignoring corner cases with NaNs
+ // and assuming math follows the algebraic rules for associativity and so
+ // forth (which IEEE floats do not, strictly speaking). This is inspired by
+ // gcc/clang's -ffast-math flag.
+ bool fastMath = false;
// Whether to try to preserve debug info through, which are special calls.
bool debugInfo = false;
// Arbitrary string arguments from the commandline, which we forward to
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp
index 36d92e81f..55af9a34d 100644
--- a/src/passes/OptimizeInstructions.cpp
+++ b/src/passes/OptimizeInstructions.cpp
@@ -161,7 +161,10 @@ struct OptimizeInstructions
#endif
}
+ bool fastMath;
+
void doWalkFunction(Function* func) {
+ fastMath = getPassOptions().fastMath;
// first, scan locals
{
LocalScanner scanner(localInfo, getPassOptions());
@@ -1414,14 +1417,15 @@ private:
}
{
double value;
- if (matches(curr, binary(Abstract::Sub, any(), fval(&value))) &&
+ if (fastMath &&
+ matches(curr, binary(Abstract::Sub, any(), fval(&value))) &&
value == 0.0) {
// x - (-0.0) ==> x + 0.0
if (std::signbit(value)) {
curr->op = Abstract::getBinary(type, Abstract::Add);
right->value = right->value.neg();
return curr;
- } else {
+ } else if (fastMath) {
// x - 0.0 ==> x
return curr->left;
}
@@ -1430,19 +1434,18 @@ private:
{
// x + (-0.0) ==> x
double value;
- if (matches(curr, binary(Abstract::Add, any(), fval(&value))) &&
+ if (fastMath &&
+ matches(curr, binary(Abstract::Add, any(), fval(&value))) &&
value == 0.0 && std::signbit(value)) {
return curr->left;
}
}
- // Note that this is correct even on floats with a NaN on the left,
- // as a NaN would skip the computation and just return the NaN,
- // and that is precisely what we do here. but, the same with -1
- // (change to a negation) would be incorrect for that reason.
if (matches(curr, binary(Abstract::Mul, any(&left), constant(1))) ||
matches(curr, binary(Abstract::DivS, any(&left), constant(1))) ||
matches(curr, binary(Abstract::DivU, any(&left), constant(1)))) {
- return left;
+ if (curr->type.isInteger() || fastMath) {
+ return left;
+ }
}
return nullptr;
}
diff --git a/src/tools/optimization-options.h b/src/tools/optimization-options.h
index 72f478329..5b6a643e6 100644
--- a/src/tools/optimization-options.h
+++ b/src/tools/optimization-options.h
@@ -187,7 +187,13 @@ struct OptimizationOptions : public ToolOptions {
Options::Arguments::Zero,
[this](Options*, const std::string&) {
passOptions.lowMemoryUnused = true;
- });
+ })
+ .add(
+ "--fast-math",
+ "-ffm",
+ "Optimize floats without handling corner cases of NaNs and rounding",
+ Options::Arguments::Zero,
+ [this](Options*, const std::string&) { passOptions.fastMath = true; });
// add passes in registry
for (const auto& p : PassRegistry::get()->getRegisteredNames()) {
(*this).add(
diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp
index d309be308..54453b356 100644
--- a/src/wasm/literal.cpp
+++ b/src/wasm/literal.cpp
@@ -934,35 +934,10 @@ Literal Literal::mul(const Literal& other) const {
return Literal(uint32_t(i32) * uint32_t(other.i32));
case Type::i64:
return Literal(uint64_t(i64) * uint64_t(other.i64));
- case Type::f32: {
- // Special-case multiplication by 1. nan * 1 can change nan bits per the
- // wasm spec, but it is ok to just return that original nan, and we
- // do that here so that we are consistent with the optimization of
- // removing the * 1 and leaving just the nan. That is, if we just
- // do a normal multiply and the CPU decides to change the bits, we'd
- // give a different result on optimized code, which would look like
- // it was a bad optimization. So out of all the valid results to
- // return here, return the simplest one that is consistent with
- // our optimization for the case of 1.
- float lhs = getf32(), rhs = other.getf32();
- if (rhs == 1) {
- return Literal(lhs);
- }
- if (lhs == 1) {
- return Literal(rhs);
- }
- return Literal(lhs * rhs);
- }
- case Type::f64: {
- double lhs = getf64(), rhs = other.getf64();
- if (rhs == 1) {
- return Literal(lhs);
- }
- if (lhs == 1) {
- return Literal(rhs);
- }
- return Literal(lhs * rhs);
- }
+ case Type::f32:
+ return Literal(getf32() * other.getf32());
+ case Type::f64:
+ return Literal(getf64() * other.getf64());
case Type::v128:
case Type::funcref:
case Type::externref:
@@ -1002,10 +977,6 @@ Literal Literal::div(const Literal& other) const {
case FP_INFINITE: // fallthrough
case FP_NORMAL: // fallthrough
case FP_SUBNORMAL:
- // Special-case division by 1, similar to multiply from earlier.
- if (rhs == 1) {
- return Literal(lhs);
- }
return Literal(lhs / rhs);
default:
WASM_UNREACHABLE("invalid fp classification");
@@ -1034,10 +1005,6 @@ Literal Literal::div(const Literal& other) const {
case FP_INFINITE: // fallthrough
case FP_NORMAL: // fallthrough
case FP_SUBNORMAL:
- // See above comment on f32.
- if (rhs == 1) {
- return Literal(lhs);
- }
return Literal(lhs / rhs);
default:
WASM_UNREACHABLE("invalid fp classification");