diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/DeNaN.cpp | 96 | ||||
-rw-r--r-- | src/passes/pass.cpp | 3 | ||||
-rw-r--r-- | src/passes/passes.h | 1 | ||||
-rw-r--r-- | src/tools/fuzzing.h | 158 | ||||
-rw-r--r-- | src/tools/wasm-opt.cpp | 8 |
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(); |