diff options
author | Alon Zakai <azakai@google.com> | 2020-08-05 17:48:54 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-08-05 17:48:54 -0700 |
commit | e93fbc0c117bcfe645bab1b30d5802e619e68abc (patch) | |
tree | 3f11b6a9e189296988d9954b483f73a9aff5f2a1 /src | |
parent | 9c84d90712c1183f1d9ab3e5068d902eb7d627ea (diff) | |
download | binaryen-e93fbc0c117bcfe645bab1b30d5802e619e68abc.tar.gz binaryen-e93fbc0c117bcfe645bab1b30d5802e619e68abc.tar.bz2 binaryen-e93fbc0c117bcfe645bab1b30d5802e619e68abc.zip |
Add StubUnsupportedJSOps to remove operations that JS does not support (#3024)
This doesn't lower them - it just replaces the unsupported operation
with a drop. This will be useful for fuzzing, where to compare JS to the
correct semantics we must avoid operations where JS is not always
accurate.
Also fully document the i64 -> f32 conversion issue in JS.
Diffstat (limited to 'src')
-rw-r--r-- | src/passes/I64ToI32Lowering.cpp | 32 | ||||
-rw-r--r-- | src/passes/RemoveNonJSOps.cpp | 47 | ||||
-rw-r--r-- | src/passes/pass.cpp | 3 | ||||
-rw-r--r-- | src/passes/passes.h | 1 |
4 files changed, 82 insertions, 1 deletions
diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp index 78f15fcad..dfd0878f8 100644 --- a/src/passes/I64ToI32Lowering.cpp +++ b/src/passes/I64ToI32Lowering.cpp @@ -696,6 +696,38 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> { // Mostly just shuffling things around here with coercions and whatnot! // Note though that all arithmetic is done with f64 to have as much // precision as we can. + // + // NB: this is *not* accurate for i64 -> f32. Using f64s for intermediate + // operations can give slightly inaccurate results in some cases, as we + // round to an f64, then round again to an f32, which is not always the + // same as a single rounding of i64 to f32 directly. Example: + // + // #include <stdio.h> + // int main() { + // unsigned long long x = 18446743523953737727ULL; + // float y = x; + // double z = x; + // float w = z; + // printf("i64 : %llu\n" + // "i64->f32 : %f\n" + // "i64->f64 : %f\n" + // "i64->f64->f32: %f\n", x, y, z, w); + // } + // + // i64 : 18446743523953737727 + // i64->f32 : 18446742974197923840.000000 ;; correct rounding to f32 + // i64->f64 : 18446743523953737728.000000 ;; correct rounding to f64 + // i64->f64->f32: 18446744073709551616.000000 ;; incorrect rounding to f32 + // + // This is even a problem if we use BigInts in JavaScript to represent + // i64s, as Math.fround(BigInt) is not supported - the BigInt must be + // converted to a Number first, so we again have that extra rounding. + // + // A more precise approach could use compiled floatdisf/floatundisf from + // compiler-rt, but that is much larger and slower. (Note that we are in the + // interesting situation of having f32 and f64 operations and only missing + // i64 ones, so we have a different problem to solve than compiler-rt, and + // maybe there is a better solution we haven't found yet.) TempVar highBits = fetchOutParam(curr->value); TempVar lowBits = getTemp(); TempVar highResult = getTemp(); diff --git a/src/passes/RemoveNonJSOps.cpp b/src/passes/RemoveNonJSOps.cpp index e25a834b0..b89ef60c0 100644 --- a/src/passes/RemoveNonJSOps.cpp +++ b/src/passes/RemoveNonJSOps.cpp @@ -15,7 +15,7 @@ */ // -// Removes all operations in a wasm module that aren't inherently implementable +// RemoveNonJSOps removes operations that aren't inherently implementable // in JS. This includes things like 64-bit division, `f32.nearest`, // `f64.copysign`, etc. Most operations are lowered to a call to an injected // intrinsic implementation. Intrinsics don't use themselves to implement @@ -26,6 +26,12 @@ // needed intrinsics from this module into the module that we're optimizing // after walking the current module. // +// StubUnsupportedJSOps stubs out operations that are not fully supported +// even with RemoveNonJSOps. For example, i64->f32 conversions do not have +// perfect rounding in all cases. StubUnsupportedJSOps removes those entirely +// and replaces them with "stub" operations that do nothing. This is only +// really useful for fuzzing as it changes the behavior of the program. +// #include <pass.h> #include <wasm.h> @@ -33,6 +39,7 @@ #include "abi/js.h" #include "asmjs/shared-constants.h" #include "ir/find_all.h" +#include "ir/literal-utils.h" #include "ir/memory-utils.h" #include "ir/module-utils.h" #include "passes/intrinsics-module.h" @@ -324,6 +331,44 @@ struct RemoveNonJSOpsPass : public WalkerPass<PostWalker<RemoveNonJSOpsPass>> { } }; +struct StubUnsupportedJSOpsPass + : public WalkerPass<PostWalker<StubUnsupportedJSOpsPass>> { + bool isFunctionParallel() override { return true; } + + Pass* create() override { return new StubUnsupportedJSOpsPass; } + + void visitUnary(Unary* curr) { + switch (curr->op) { + case ConvertUInt64ToFloat32: + // See detailed comment in lowerConvertIntToFloat in + // I64ToI32Lowering.cpp. + stubOut(curr->value, curr->type); + break; + default: { + } + } + } + + void stubOut(Expression* value, Type outputType) { + Builder builder(*getModule()); + if (outputType == Type::unreachable) { + // This is unreachable anyhow; just leave the value instead of the + // original node. + assert(value->type == Type::unreachable); + replaceCurrent(value); + } else { + // Drop the value, and return something with the right output type. + replaceCurrent( + builder.makeSequence(builder.makeDrop(value), + LiteralUtils::makeZero(outputType, *getModule()))); + } + } +}; + Pass* createRemoveNonJSOpsPass() { return new RemoveNonJSOpsPass(); } +Pass* createStubUnsupportedJSOpsPass() { + return new StubUnsupportedJSOpsPass(); +} + } // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index d94e454c3..5a0dedf37 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -340,6 +340,9 @@ void PassRegistry::registerPasses() { registerPass("spill-pointers", "spill pointers to the C stack (useful for Boehm-style GC)", createSpillPointersPass); + registerPass("stub-unsupported-js", + "stub out unsupported JS operations", + createStubUnsupportedJSOpsPass); registerPass("ssa", "ssa-ify variables so that they have a single assignment", createSSAifyPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 89b0c0d01..5eb6cef3c 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -119,6 +119,7 @@ Pass* createStripTargetFeaturesPass(); Pass* createSouperifyPass(); Pass* createSouperifySingleUsePass(); Pass* createSpillPointersPass(); +Pass* createStubUnsupportedJSOpsPass(); Pass* createSSAifyPass(); Pass* createSSAifyNoMergePass(); Pass* createTrapModeClamp(); |