summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/I64ToI32Lowering.cpp32
-rw-r--r--src/passes/RemoveNonJSOps.cpp47
-rw-r--r--src/passes/pass.cpp3
-rw-r--r--src/passes/passes.h1
-rw-r--r--test/passes/stub-unsupported-js.txt24
-rw-r--r--test/passes/stub-unsupported-js.wast14
6 files changed, 120 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();
diff --git a/test/passes/stub-unsupported-js.txt b/test/passes/stub-unsupported-js.txt
new file mode 100644
index 000000000..531c767c0
--- /dev/null
+++ b/test/passes/stub-unsupported-js.txt
@@ -0,0 +1,24 @@
+(module
+ (type $none_=>_f32 (func (result f32)))
+ (type $i32_=>_f32 (func (param i32) (result f32)))
+ (type $i64_=>_f32 (func (param i64) (result f32)))
+ (func $yes (param $x i64) (result f32)
+ (drop
+ (local.get $x)
+ )
+ (f32.const 0)
+ )
+ (func $no (param $x i32) (result f32)
+ (f32.convert_i32_u
+ (local.get $x)
+ )
+ )
+ (func $yes-unreach (result f32)
+ (unreachable)
+ )
+ (func $no-unreach (result f32)
+ (f32.convert_i32_u
+ (unreachable)
+ )
+ )
+)
diff --git a/test/passes/stub-unsupported-js.wast b/test/passes/stub-unsupported-js.wast
new file mode 100644
index 000000000..6cfc7bef2
--- /dev/null
+++ b/test/passes/stub-unsupported-js.wast
@@ -0,0 +1,14 @@
+(module
+ (func $yes (param $x i64) (result f32)
+ (f32.convert_i64_u (local.get $x))
+ )
+ (func $no (param $x i32) (result f32)
+ (f32.convert_i32_u (local.get $x))
+ )
+ (func $yes-unreach (result f32)
+ (f32.convert_i64_u (unreachable))
+ )
+ (func $no-unreach (result f32)
+ (f32.convert_i32_u (unreachable))
+ )
+)