summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/DeNaN.cpp96
-rw-r--r--src/passes/pass.cpp3
-rw-r--r--src/passes/passes.h1
-rw-r--r--src/tools/fuzzing.h158
-rw-r--r--src/tools/wasm-opt.cpp8
6 files changed, 156 insertions, 111 deletions
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index 6de16a5b2..654c1c797 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -18,6 +18,7 @@ set(passes_SOURCES
DataFlowOpts.cpp
DeadArgumentElimination.cpp
DeadCodeElimination.cpp
+ DeNaN.cpp
Directize.cpp
DuplicateImportElimination.cpp
DuplicateFunctionElimination.cpp
diff --git a/src/passes/DeNaN.cpp b/src/passes/DeNaN.cpp
new file mode 100644
index 000000000..044c13c86
--- /dev/null
+++ b/src/passes/DeNaN.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2020 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Instrument the wasm to convert NaN values at runtime into 0s. That is, every
+// operation that might produce a NaN will go through a helper function which
+// filters out NaNs (replacing them with 0). This ensures that NaNs are never
+// consumed by any instructions, which is useful when fuzzing between VMs that
+// differ on wasm's nondeterminism around NaNs.
+//
+
+#include "pass.h"
+#include "wasm-builder.h"
+#include "wasm.h"
+
+namespace wasm {
+
+struct DeNaN : public WalkerPass<
+ ControlFlowWalker<DeNaN, UnifiedExpressionVisitor<DeNaN>>> {
+ void visitExpression(Expression* expr) {
+ // If the expression returns a floating-point value, ensure it is not a
+ // NaN. If we can do this at compile time, do it now, which is useful for
+ // initializations of global (which we can't do a function call in).
+ Builder builder(*getModule());
+ Expression* replacement = nullptr;
+ auto* c = expr->dynCast<Const>();
+ if (expr->type == Type::f32) {
+ if (c && c->value.isNaN()) {
+ replacement = builder.makeConst(Literal(float(0)));
+ } else {
+ replacement = builder.makeCall("deNan32", {expr}, Type::f32);
+ }
+ } else if (expr->type == Type::f64) {
+ if (c && c->value.isNaN()) {
+ replacement = builder.makeConst(Literal(double(0)));
+ } else {
+ replacement = builder.makeCall("deNan64", {expr}, Type::f64);
+ }
+ }
+ if (replacement) {
+ // We can't do this outside of a function, like in a global initializer,
+ // where a call would be illegal.
+ if (replacement->is<Const>() || getFunction()) {
+ replaceCurrent(replacement);
+ } else {
+ std::cerr << "warning: cannot de-nan outside of function context\n";
+ }
+ }
+ }
+
+ void visitModule(Module* module) {
+ // Add helper functions.
+ Builder builder(*module);
+ auto add = [&](Name name, Type type, Literal literal, BinaryOp op) {
+ auto* func = new Function;
+ func->name = name;
+ func->sig = Signature(type, type);
+ // Compare the value to itself to check if it is a NaN, and return 0 if
+ // so:
+ //
+ // (if (result f*)
+ // (f*.eq
+ // (local.get $0)
+ // (local.get $0)
+ // )
+ // (local.get $0)
+ // (f*.const 0)
+ // )
+ func->body = builder.makeIf(
+ builder.makeBinary(
+ op, builder.makeLocalGet(0, type), builder.makeLocalGet(0, type)),
+ builder.makeLocalGet(0, type),
+ builder.makeConst(literal));
+ module->addFunction(func);
+ };
+ add("deNan32", Type::f32, Literal(float(0)), EqFloat32);
+ add("deNan64", Type::f64, Literal(double(0)), EqFloat64);
+ }
+};
+
+Pass* createDeNaNPass() { return new DeNaN(); }
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 733b61467..4b023a46e 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -104,6 +104,9 @@ void PassRegistry::registerPasses() {
createConstHoistingPass);
registerPass(
"dce", "removes unreachable code", createDeadCodeEliminationPass);
+ registerPass("denan",
+ "instrument the wasm to convert NaNs into 0 at runtime",
+ createDeNaNPass);
registerPass(
"directize", "turns indirect calls into direct ones", createDirectizePass);
registerPass(
diff --git a/src/passes/passes.h b/src/passes/passes.h
index 868c5b407..7683bf8c6 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -34,6 +34,7 @@ Pass* createDAEPass();
Pass* createDAEOptimizingPass();
Pass* createDataFlowOptsPass();
Pass* createDeadCodeEliminationPass();
+Pass* createDeNaNPass();
Pass* createDirectizePass();
Pass* createDWARFDumpPass();
Pass* createDuplicateImportEliminationPass();
diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h
index f4188eb08..9788d9f11 100644
--- a/src/tools/fuzzing.h
+++ b/src/tools/fuzzing.h
@@ -183,8 +183,6 @@ public:
std::cout << "shrink level: " << options.passOptions.shrinkLevel << '\n';
}
- void setAllowNaNs(bool allowNaNs_) { allowNaNs = allowNaNs_; }
-
void setAllowMemory(bool allowMemory_) { allowMemory = allowMemory_; }
void setAllowOOB(bool allowOOB_) { allowOOB = allowOOB_; }
@@ -207,9 +205,6 @@ public:
if (HANG_LIMIT > 0) {
addHangLimitSupport();
}
- if (!allowNaNs) {
- addDeNanSupport();
- }
finalizeTable();
}
@@ -253,11 +248,6 @@ private:
// no hang protection.
static const int HANG_LIMIT = 10;
- // Optionally remove NaNs, which are a source of nondeterminism (which makes
- // cross-VM comparisons harder)
- // TODO: de-NaN SIMD values
- bool allowNaNs = true;
-
// Whether to emit memory operations like loads and stores.
bool allowMemory = true;
@@ -499,34 +489,6 @@ private:
builder.makeConst(Literal(int32_t(1))))));
}
- void addDeNanSupport() {
- auto add = [&](Name name, Type type, Literal literal, BinaryOp op) {
- auto* func = new Function;
- func->name = name;
- func->sig = Signature(type, type);
- func->body = builder.makeIf(
- builder.makeBinary(
- op, builder.makeLocalGet(0, type), builder.makeLocalGet(0, type)),
- builder.makeLocalGet(0, type),
- builder.makeConst(literal));
- wasm.addFunction(func);
- };
- add("deNan32", Type::f32, Literal(float(0)), EqFloat32);
- add("deNan64", Type::f64, Literal(double(0)), EqFloat64);
- }
-
- Expression* makeDeNanOp(Expression* expr) {
- if (allowNaNs) {
- return expr;
- }
- if (expr->type == Type::f32) {
- return builder.makeCall("deNan32", {expr}, Type::f32);
- } else if (expr->type == Type::f64) {
- return builder.makeCall("deNan64", {expr}, Type::f64);
- }
- return expr; // unreachable etc. is fine
- }
-
// function generation state
Function* func = nullptr;
@@ -1538,7 +1500,7 @@ private:
return store;
}
- Literal makeArbitraryLiteral(Type type) {
+ Literal makeLiteral(Type type) {
if (type == Type::v128) {
// generate each lane individually for random lane interpretation
switch (upTo(6)) {
@@ -1767,14 +1729,6 @@ private:
WASM_UNREACHABLE("invalide value");
}
- Literal makeLiteral(Type type) {
- auto ret = makeArbitraryLiteral(type);
- if (!allowNaNs && ret.isNaN()) {
- ret = Literal::makeFromInt32(0, type);
- }
- return ret;
- }
-
Expression* makeConst(Type type) {
if (type.isRef()) {
assert(wasm.features.hasReferenceTypes());
@@ -1814,8 +1768,7 @@ private:
assert(!type.isMulti());
if (type == Type::unreachable) {
if (auto* unary = makeUnary(getSingleConcreteType())->dynCast<Unary>()) {
- return makeDeNanOp(
- builder.makeUnary(unary->op, make(Type::unreachable)));
+ return builder.makeUnary(unary->op, make(Type::unreachable));
}
// give up
return makeTrivial(type);
@@ -1923,50 +1876,50 @@ private:
case Type::f32: {
switch (upTo(4)) {
case 0:
- return makeDeNanOp(buildUnary({pick(NegFloat32,
- AbsFloat32,
- CeilFloat32,
- FloorFloat32,
- TruncFloat32,
- NearestFloat32,
- SqrtFloat32),
- make(Type::f32)}));
+ return buildUnary({pick(NegFloat32,
+ AbsFloat32,
+ CeilFloat32,
+ FloorFloat32,
+ TruncFloat32,
+ NearestFloat32,
+ SqrtFloat32),
+ make(Type::f32)});
case 1:
- return makeDeNanOp(buildUnary({pick(ConvertUInt32ToFloat32,
- ConvertSInt32ToFloat32,
- ReinterpretInt32),
- make(Type::i32)}));
+ return buildUnary({pick(ConvertUInt32ToFloat32,
+ ConvertSInt32ToFloat32,
+ ReinterpretInt32),
+ make(Type::i32)});
case 2:
- return makeDeNanOp(
- buildUnary({pick(ConvertUInt64ToFloat32, ConvertSInt64ToFloat32),
- make(Type::i64)}));
+ return buildUnary(
+ {pick(ConvertUInt64ToFloat32, ConvertSInt64ToFloat32),
+ make(Type::i64)});
case 3:
- return makeDeNanOp(buildUnary({DemoteFloat64, make(Type::f64)}));
+ return buildUnary({DemoteFloat64, make(Type::f64)});
}
WASM_UNREACHABLE("invalid value");
}
case Type::f64: {
switch (upTo(4)) {
case 0:
- return makeDeNanOp(buildUnary({pick(NegFloat64,
- AbsFloat64,
- CeilFloat64,
- FloorFloat64,
- TruncFloat64,
- NearestFloat64,
- SqrtFloat64),
- make(Type::f64)}));
+ return buildUnary({pick(NegFloat64,
+ AbsFloat64,
+ CeilFloat64,
+ FloorFloat64,
+ TruncFloat64,
+ NearestFloat64,
+ SqrtFloat64),
+ make(Type::f64)});
case 1:
- return makeDeNanOp(
- buildUnary({pick(ConvertUInt32ToFloat64, ConvertSInt32ToFloat64),
- make(Type::i32)}));
+ return buildUnary(
+ {pick(ConvertUInt32ToFloat64, ConvertSInt32ToFloat64),
+ make(Type::i32)});
case 2:
- return makeDeNanOp(buildUnary({pick(ConvertUInt64ToFloat64,
- ConvertSInt64ToFloat64,
- ReinterpretInt64),
- make(Type::i64)}));
+ return buildUnary({pick(ConvertUInt64ToFloat64,
+ ConvertSInt64ToFloat64,
+ ReinterpretInt64),
+ make(Type::i64)});
case 3:
- return makeDeNanOp(buildUnary({PromoteFloat32, make(Type::f32)}));
+ return buildUnary({PromoteFloat32, make(Type::f32)});
}
WASM_UNREACHABLE("invalid value");
}
@@ -2035,8 +1988,8 @@ private:
if (type == Type::unreachable) {
if (auto* binary =
makeBinary(getSingleConcreteType())->dynCast<Binary>()) {
- return makeDeNanOp(buildBinary(
- {binary->op, make(Type::unreachable), make(Type::unreachable)}));
+ return buildBinary(
+ {binary->op, make(Type::unreachable), make(Type::unreachable)});
}
// give up
return makeTrivial(type);
@@ -2131,26 +2084,26 @@ private:
make(Type::i64)});
}
case Type::f32: {
- return makeDeNanOp(buildBinary({pick(AddFloat32,
- SubFloat32,
- MulFloat32,
- DivFloat32,
- CopySignFloat32,
- MinFloat32,
- MaxFloat32),
- make(Type::f32),
- make(Type::f32)}));
+ return buildBinary({pick(AddFloat32,
+ SubFloat32,
+ MulFloat32,
+ DivFloat32,
+ CopySignFloat32,
+ MinFloat32,
+ MaxFloat32),
+ make(Type::f32),
+ make(Type::f32)});
}
case Type::f64: {
- return makeDeNanOp(buildBinary({pick(AddFloat64,
- SubFloat64,
- MulFloat64,
- DivFloat64,
- CopySignFloat64,
- MinFloat64,
- MaxFloat64),
- make(Type::f64),
- make(Type::f64)}));
+ return buildBinary({pick(AddFloat64,
+ SubFloat64,
+ MulFloat64,
+ DivFloat64,
+ CopySignFloat64,
+ MinFloat64,
+ MaxFloat64),
+ make(Type::f64),
+ make(Type::f64)});
}
case Type::v128: {
assert(wasm.features.hasSIMD());
@@ -2270,8 +2223,7 @@ private:
Expression* makeSelect(Type type) {
Type subType1 = getSubType(type);
Type subType2 = getSubType(type);
- return makeDeNanOp(
- buildSelect({make(Type::i32), make(subType1), make(subType2)}, type));
+ return buildSelect({make(Type::i32), make(subType1), make(subType2)}, type);
}
Expression* makeSwitch(Type type) {
diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp
index a215aca32..41302eac8 100644
--- a/src/tools/wasm-opt.cpp
+++ b/src/tools/wasm-opt.cpp
@@ -82,7 +82,6 @@ int main(int argc, const char* argv[]) {
std::string extraFuzzCommand;
bool translateToFuzz = false;
bool fuzzPasses = false;
- bool fuzzNaNs = true;
bool fuzzMemory = true;
bool fuzzOOB = true;
std::string emitJSWrapper;
@@ -148,12 +147,6 @@ int main(int argc, const char* argv[]) {
"on translate-to-fuzz (it picks the passes from the input)",
Options::Arguments::Zero,
[&](Options* o, const std::string& arguments) { fuzzPasses = true; })
- .add("--no-fuzz-nans",
- "",
- "don't emit NaNs when fuzzing, and remove them at runtime as well "
- "(helps avoid nondeterminism between VMs)",
- Options::Arguments::Zero,
- [&](Options* o, const std::string& arguments) { fuzzNaNs = false; })
.add("--no-fuzz-memory",
"",
"don't emit memory ops when fuzzing",
@@ -266,7 +259,6 @@ int main(int argc, const char* argv[]) {
if (fuzzPasses) {
reader.pickPasses(options);
}
- reader.setAllowNaNs(fuzzNaNs);
reader.setAllowMemory(fuzzMemory);
reader.setAllowOOB(fuzzOOB);
reader.build();