summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2018-03-13 09:29:38 -0700
committerGitHub <noreply@github.com>2018-03-13 09:29:38 -0700
commitd52213c3f5e96bb3450721d96aa68d3c5e0865b6 (patch)
treec585377fb842da0c4b87657f5047272196af9e6c
parent0008b795d11d65d0759c3c0a71ee74b536f1ecf8 (diff)
downloadbinaryen-d52213c3f5e96bb3450721d96aa68d3c5e0865b6.tar.gz
binaryen-d52213c3f5e96bb3450721d96aa68d3c5e0865b6.tar.bz2
binaryen-d52213c3f5e96bb3450721d96aa68d3c5e0865b6.zip
Function pointer cast emulation (#1468)
This adds a pass that implements "function pointer cast emulation" - allows indirect calls to go through even if the number of arguments or their types is incorrect. That is undefined behavior in C/C++ but in practice somehow works in native archs. It is even relied upon in e.g. Python. Emscripten already has such emulation for asm.js, which also worked for asm2wasm. This implements something like it in binaryen which also allows the wasm backend to use it. As a result, Python should now be portable using the wasm backend. The mechanism used for the emulation is to make all indirect calls use a fixed number of arguments, all of type i64, and a return type of also i64. Thunks are then placed in the table which translate the arguments properly for the target, basically by reinterpreting to i64 and back. As a result, receiving an i64 when an i32 is sent will have the upper bits all zero, and the reverse would truncate the upper bits, etc. (Note that this is different than emscripten's existing emulation, which converts (as signed) to a double. That makes sense for JS where double's can contain all numeric values, but in wasm we have i64s. Also, bitwise conversion may be more like what native archs do anyhow. It is enough for Python.) Also adds validation for a function's type matching the function's actual params and result (surprised we didn't have that before, but we didn't, and there was even a place in the test suite where that was wrong). Also simplifies the build script by moving two cpp files into the wasm/ subdir, so they can be built once and shared between the various tools.
-rw-r--r--CMakeLists.txt10
-rwxr-xr-xbuild-js.sh2
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/FuncCastEmulation.cpp235
-rw-r--r--src/passes/pass.cpp1
-rw-r--r--src/passes/passes.h1
-rw-r--r--src/wasm-builder.h16
-rw-r--r--src/wasm-linker.cpp8
-rw-r--r--src/wasm/CMakeLists.txt2
-rw-r--r--src/wasm/wasm-binary.cpp10
-rw-r--r--src/wasm/wasm-emscripten.cpp (renamed from src/wasm-emscripten.cpp)0
-rw-r--r--src/wasm/wasm-interpreter.cpp (renamed from src/wasm-interpreter.cpp)0
-rw-r--r--src/wasm/wasm-validator.cpp6
-rw-r--r--test/passes/dce.txt6
-rw-r--r--test/passes/dce.wast6
-rw-r--r--test/passes/fpcast-emu.txt294
-rw-r--r--test/passes/fpcast-emu.wast48
17 files changed, 623 insertions, 23 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 433205f44..2c502812e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -172,7 +172,6 @@ INSTALL(FILES bin/binaryen.js DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAM
SET(wasm-shell_SOURCES
src/tools/wasm-shell.cpp
- src/wasm-interpreter.cpp
)
ADD_EXECUTABLE(wasm-shell
${wasm-shell_SOURCES})
@@ -183,11 +182,10 @@ INSTALL(TARGETS wasm-shell DESTINATION ${CMAKE_INSTALL_BINDIR})
SET(wasm-opt_SOURCES
src/tools/wasm-opt.cpp
- src/wasm-interpreter.cpp
)
ADD_EXECUTABLE(wasm-opt
${wasm-opt_SOURCES})
-TARGET_LINK_LIBRARIES(wasm-opt wasm asmjs emscripten-optimizer passes ir cfg support)
+TARGET_LINK_LIBRARIES(wasm-opt wasm asmjs emscripten-optimizer passes ir cfg support wasm)
SET_PROPERTY(TARGET wasm-opt PROPERTY CXX_STANDARD 11)
SET_PROPERTY(TARGET wasm-opt PROPERTY CXX_STANDARD_REQUIRED ON)
INSTALL(TARGETS wasm-opt DESTINATION ${CMAKE_INSTALL_BINDIR})
@@ -197,7 +195,7 @@ SET(wasm-merge_SOURCES
)
ADD_EXECUTABLE(wasm-merge
${wasm-merge_SOURCES})
-TARGET_LINK_LIBRARIES(wasm-merge wasm asmjs emscripten-optimizer passes ir cfg support)
+TARGET_LINK_LIBRARIES(wasm-merge wasm asmjs emscripten-optimizer passes ir cfg support wasm)
SET_PROPERTY(TARGET wasm-merge PROPERTY CXX_STANDARD 11)
SET_PROPERTY(TARGET wasm-merge PROPERTY CXX_STANDARD_REQUIRED ON)
INSTALL(TARGETS wasm-merge DESTINATION bin)
@@ -214,7 +212,6 @@ INSTALL(TARGETS wasm-metadce DESTINATION bin)
SET(asm2wasm_SOURCES
src/tools/asm2wasm.cpp
- src/wasm-emscripten.cpp
)
ADD_EXECUTABLE(asm2wasm
${asm2wasm_SOURCES})
@@ -235,7 +232,6 @@ INSTALL(TARGETS wasm2asm DESTINATION ${CMAKE_INSTALL_BINDIR})
SET(s2wasm_SOURCES
src/tools/s2wasm.cpp
- src/wasm-emscripten.cpp
src/wasm-linker.cpp
)
ADD_EXECUTABLE(s2wasm
@@ -247,7 +243,6 @@ INSTALL(TARGETS s2wasm DESTINATION ${CMAKE_INSTALL_BINDIR})
SET(wasm-emscripten-finalize_SOURCES
src/tools/wasm-emscripten-finalize.cpp
- src/wasm-emscripten.cpp
)
ADD_EXECUTABLE(wasm-emscripten-finalize
${wasm-emscripten-finalize_SOURCES})
@@ -290,7 +285,6 @@ IF (UNIX) # TODO: port to windows
SET(wasm-reduce_SOURCES
src/tools/wasm-reduce.cpp
- src/wasm-interpreter.cpp
)
ADD_EXECUTABLE(wasm-reduce
${wasm-reduce_SOURCES})
diff --git a/build-js.sh b/build-js.sh
index 11e2ebe24..2f1c4b92d 100755
--- a/build-js.sh
+++ b/build-js.sh
@@ -134,7 +134,7 @@ echo "building shared bitcode"
$BINARYEN_SRC/wasm/wasm-type.cpp \
$BINARYEN_SRC/wasm/wasm-validator.cpp \
$BINARYEN_SRC/wasm/wasm.cpp \
- $BINARYEN_SRC/wasm-emscripten.cpp \
+ $BINARYEN_SRC/wasm/wasm-emscripten.cpp \
-I$BINARYEN_SRC \
-o shared.bc
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index e5334fff1..d4fff78df 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -8,6 +8,7 @@ SET(passes_SOURCES
DuplicateFunctionElimination.cpp
ExtractFunction.cpp
Flatten.cpp
+ FuncCastEmulation.cpp
Inlining.cpp
LegalizeJSInterface.cpp
LocalCSE.cpp
diff --git a/src/passes/FuncCastEmulation.cpp b/src/passes/FuncCastEmulation.cpp
new file mode 100644
index 000000000..59a2588da
--- /dev/null
+++ b/src/passes/FuncCastEmulation.cpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2017 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.
+ */
+
+//
+// Instruments all indirect calls so that they work even if a function
+// pointer was cast incorrectly. For example, if you cast an int (int, float)
+// to an int (int, float, int) and call it natively, on most archs it will
+// happen to work, ignoring the extra param, whereas in wasm it will trap.
+// When porting code that relies on such casts working (like e.g. Python),
+// this pass may be useful. It sets a new "ABI" for indirect calls, in which
+// they all return an i64 and they have a fixed number of i64 params, and
+// the pass converts everything to go through that.
+//
+// This should work even with dynamic linking, however, the number of
+// params must be identical, i.e., the "ABI" must match.
+
+#include <wasm.h>
+#include <wasm-builder.h>
+#include <asm_v_wasm.h>
+#include <pass.h>
+#include <wasm-emscripten.h>
+#include <ir/literal-utils.h>
+
+namespace wasm {
+
+// This should be enough for everybody. (As described above, we need this
+// to match when dynamically linking, and also dynamic linking is why we
+// can't just detect this automatically in the module we see.)
+static const int NUM_PARAMS = 15;
+
+// Converts a value to the ABI type of i64.
+static Expression* toABI(Expression* value, Module* module) {
+ Builder builder(*module);
+ switch (value->type) {
+ case i32: {
+ value = builder.makeUnary(ExtendUInt32, value);
+ break;
+ }
+ case i64: {
+ // already good
+ break;
+ }
+ case f32: {
+ value = builder.makeUnary(
+ ExtendUInt32,
+ builder.makeUnary(ReinterpretFloat32, value)
+ );
+ break;
+ }
+ case f64: {
+ value = builder.makeUnary(ReinterpretFloat64, value);
+ break;
+ }
+ case none: {
+ // the value is none, but we need a value here
+ value = builder.makeSequence(
+ value,
+ LiteralUtils::makeZero(i64, *module)
+ );
+ break;
+ }
+ case unreachable: {
+ // can leave it, the call isn't taken anyhow
+ break;
+ }
+ default: {
+ // SIMD may be interesting some day
+ WASM_UNREACHABLE();
+ }
+ }
+ return value;
+}
+
+// Converts a value from the ABI type of i64 to the expected type
+static Expression* fromABI(Expression* value, Type type, Module* module) {
+ Builder builder(*module);
+ switch (type) {
+ case i32: {
+ value = builder.makeUnary(WrapInt64, value);
+ break;
+ }
+ case i64: {
+ // already good
+ break;
+ }
+ case f32: {
+ value = builder.makeUnary(
+ ReinterpretInt32,
+ builder.makeUnary(WrapInt64, value)
+ );
+ break;
+ }
+ case f64: {
+ value = builder.makeUnary(ReinterpretInt64, value);
+ break;
+ }
+ case none: {
+ value = builder.makeDrop(value);
+ }
+ case unreachable: {
+ // can leave it, the call isn't taken anyhow
+ break;
+ }
+ default: {
+ // SIMD may be interesting some day
+ WASM_UNREACHABLE();
+ }
+ }
+ return value;
+}
+
+struct ParallelFuncCastEmulation : public WalkerPass<PostWalker<ParallelFuncCastEmulation>> {
+ bool isFunctionParallel() override { return true; }
+
+ Pass* create() override { return new ParallelFuncCastEmulation(ABIType); }
+
+ ParallelFuncCastEmulation(Name ABIType) : ABIType(ABIType) {}
+
+ void visitCallIndirect(CallIndirect* curr) {
+ if (curr->operands.size() > NUM_PARAMS) {
+ Fatal() << "FuncCastEmulation::NUM_PARAMS needs to be at least " <<
+ curr->operands.size();
+ }
+ for (Expression*& operand : curr->operands) {
+ operand = toABI(operand, getModule());
+ }
+ // Add extra operands as needed.
+ while (curr->operands.size() < NUM_PARAMS) {
+ curr->operands.push_back(LiteralUtils::makeZero(i64, *getModule()));
+ }
+ // Set the new types
+ auto oldType = curr->type;
+ curr->type = i64;
+ curr->fullType = ABIType;
+ // Fix up return value
+ replaceCurrent(fromABI(curr, oldType, getModule()));
+ }
+
+private:
+ // the name of a type for a call with the right params and return
+ Name ABIType;
+};
+
+struct FuncCastEmulation : public Pass {
+ void run(PassRunner* runner, Module* module) override {
+ // we just need the one ABI function type for all indirect calls
+ std::string sig = "j";
+ for (Index i = 0; i < NUM_PARAMS; i++) {
+ sig += 'j';
+ }
+ ABIType = ensureFunctionType(sig, module)->name;
+ // Add a way for JS to call into the table (as our i64 ABI means an i64
+ // is returned when there is a return value, which JS engines will fail on),
+ // using dynCalls
+ EmscriptenGlueGenerator generator(*module);
+ generator.generateDynCallThunks();
+ // Add a thunk for each function in the table, and do the call through it.
+ std::unordered_map<Name, Name> funcThunks;
+ for (auto& segment : module->table.segments) {
+ for (auto& name : segment.data) {
+ auto iter = funcThunks.find(name);
+ if (iter == funcThunks.end()) {
+ auto thunk = makeThunk(name, module);
+ funcThunks[name] = thunk;
+ name = thunk;
+ } else {
+ name = iter->second;
+ }
+ }
+ }
+ // update call_indirects
+ PassRunner subRunner(module, runner->options);
+ subRunner.setIsNested(true);
+ subRunner.add<ParallelFuncCastEmulation>(ABIType);
+ subRunner.run();
+ }
+
+private:
+ // the name of a type for a call with the right params and return
+ Name ABIType;
+
+ // Creates a thunk for a function, casting args and return value as needed.
+ Name makeThunk(Name name, Module* module) {
+ Name thunk = std::string("byn$fpcast-emu$") + name.str;
+ if (module->getFunctionOrNull(thunk)) {
+ Fatal() << "FuncCastEmulation::makeThunk seems a thunk name already in use. Was the pass already run on this code?";
+ }
+ // The item in the table may be a function or a function import.
+ auto* func = module->getFunctionOrNull(name);
+ Import* imp = nullptr;
+ if (!func) imp = module->getImport(name);
+ std::vector<Type>& params = func ? func->params : module->getFunctionType(imp->functionType)->params;
+ Type type = func ? func->result : module->getFunctionType(imp->functionType)->result;
+ Builder builder(*module);
+ std::vector<Expression*> callOperands;
+ for (Index i = 0; i < params.size(); i++) {
+ callOperands.push_back(fromABI(builder.makeGetLocal(i, i64), params[i], module));
+ }
+ Expression* call = func ? (Expression*)builder.makeCall(name, callOperands, type)
+ : (Expression*)builder.makeCallImport(name, callOperands, type);
+ std::vector<Type> thunkParams;
+ for (Index i = 0; i < NUM_PARAMS; i++) {
+ thunkParams.push_back(i64);
+ }
+ auto* thunkFunc = builder.makeFunction(
+ thunk,
+ std::move(thunkParams),
+ i64,
+ {}, // no vars
+ toABI(call, module)
+ );
+ thunkFunc->type = ABIType;
+ module->addFunction(thunkFunc);
+ return thunk;
+ }
+};
+
+Pass* createFuncCastEmulationPass() {
+ return new FuncCastEmulation();
+}
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 37c59f570..6518a6f3d 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -74,6 +74,7 @@ void PassRegistry::registerPasses() {
registerPass("duplicate-function-elimination", "removes duplicate functions", createDuplicateFunctionEliminationPass);
registerPass("extract-function", "leaves just one function (useful for debugging)", createExtractFunctionPass);
registerPass("flatten", "flattens out code, removing nesting", createFlattenPass);
+ registerPass("fpcast-emu", "emulates function pointer casts, allowing incorrect indirect calls to (sometimes) work", createFuncCastEmulationPass);
registerPass("func-metrics", "reports function metrics", createFunctionMetricsPass);
registerPass("inlining", "inline functions (you probably want inlining-optimizing)", createInliningPass);
registerPass("inlining-optimizing", "inline functions and optimizes where we inlined", createInliningOptimizingPass);
diff --git a/src/passes/passes.h b/src/passes/passes.h
index 230cdfd86..2eaf1049e 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -31,6 +31,7 @@ Pass* createDeadCodeEliminationPass();
Pass* createDuplicateFunctionEliminationPass();
Pass* createExtractFunctionPass();
Pass* createFlattenPass();
+Pass* createFuncCastEmulationPass();
Pass* createFullPrinterPass();
Pass* createFunctionMetricsPass();
Pass* createI64ToI32LoweringPass();
diff --git a/src/wasm-builder.h b/src/wasm-builder.h
index 94699df7e..02304acec 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -43,6 +43,20 @@ public:
// make* functions, create nodes
Function* makeFunction(Name name,
+ std::vector<Type>&& params,
+ Type resultType,
+ std::vector<Type>&& vars,
+ Expression* body = nullptr) {
+ auto* func = new Function;
+ func->name = name;
+ func->result = resultType;
+ func->body = body;
+ func->params.swap(params);
+ func->vars.swap(vars);
+ return func;
+ }
+
+ Function* makeFunction(Name name,
std::vector<NameType>&& params,
Type resultType,
std::vector<NameType>&& vars,
@@ -51,7 +65,6 @@ public:
func->name = name;
func->result = resultType;
func->body = body;
-
for (auto& param : params) {
func->params.push_back(param.type);
Index index = func->localNames.size();
@@ -64,7 +77,6 @@ public:
func->localIndices[var.name] = index;
func->localNames[index] = var.name;
}
-
return func;
}
diff --git a/src/wasm-linker.cpp b/src/wasm-linker.cpp
index c284de81f..df51d85c6 100644
--- a/src/wasm-linker.cpp
+++ b/src/wasm-linker.cpp
@@ -382,7 +382,13 @@ void Linker::makeDummyFunction() {
if (!create) return;
wasm::Builder wasmBuilder(out.wasm);
Expression *unreachable = wasmBuilder.makeUnreachable();
- Function *dummy = wasmBuilder.makeFunction(Name(dummyFunction), {}, Type::none, {}, unreachable);
+ Function *dummy = wasmBuilder.makeFunction(
+ Name(dummyFunction),
+ std::vector<Type>{},
+ Type::none,
+ std::vector<Type>{},
+ unreachable
+ );
out.wasm.addFunction(dummy);
getFunctionIndex(dummy->name);
}
diff --git a/src/wasm/CMakeLists.txt b/src/wasm/CMakeLists.txt
index 1a8a9b8ba..da876b56f 100644
--- a/src/wasm/CMakeLists.txt
+++ b/src/wasm/CMakeLists.txt
@@ -2,6 +2,8 @@ SET(wasm_SOURCES
literal.cpp
wasm.cpp
wasm-binary.cpp
+ wasm-emscripten.cpp
+ wasm-interpreter.cpp
wasm-io.cpp
wasm-s-parser.cpp
wasm-type.cpp
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 56b35b712..69e939c44 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -1730,11 +1730,11 @@ void WasmBinaryBuilder::readFunctions() {
}
}
auto func = Builder(wasm).makeFunction(
- Name::fromInt(i),
- std::move(params),
- type->result,
- std::move(vars)
- );
+ Name::fromInt(i),
+ std::move(params),
+ type->result,
+ std::move(vars)
+ );
func->type = type->name;
currFunction = func;
{
diff --git a/src/wasm-emscripten.cpp b/src/wasm/wasm-emscripten.cpp
index 9a393db43..9a393db43 100644
--- a/src/wasm-emscripten.cpp
+++ b/src/wasm/wasm-emscripten.cpp
diff --git a/src/wasm-interpreter.cpp b/src/wasm/wasm-interpreter.cpp
index e7df785ac..e7df785ac 100644
--- a/src/wasm-interpreter.cpp
+++ b/src/wasm/wasm-interpreter.cpp
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index 8ff2fb9b8..d6c6e7bd0 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -806,6 +806,12 @@ void FunctionValidator::visitFunction(Function* curr) {
shouldBeTrue(breakTargets.empty(), curr->body, "all named break targets must exist");
returnType = unreachable;
labelNames.clear();
+ // if function has a named type, it must match up with the function's params and result
+ if (info.validateGlobally && curr->type.is()) {
+ auto* ft = getModule()->getFunctionType(curr->type);
+ shouldBeTrue(ft->params == curr->params, curr->name, "function params must match its declared type");
+ shouldBeTrue(ft->result == curr->result, curr->name, "function result must match its declared type");
+ }
// expressions must not be seen more than once
struct Walker : public PostWalker<Walker, UnifiedExpressionVisitor<Walker>> {
std::unordered_set<Expression*>& seen;
diff --git a/test/passes/dce.txt b/test/passes/dce.txt
index 6f2422b2a..b2976432b 100644
--- a/test/passes/dce.txt
+++ b/test/passes/dce.txt
@@ -322,7 +322,7 @@
(unreachable)
)
)
- (func $unreachable-block-ends-br_if (; 11 ;) (type $1) (result i32)
+ (func $unreachable-block-ends-br_if (; 11 ;) (type $2) (result i32)
(block $label$2
(nop)
(unreachable)
@@ -377,13 +377,13 @@
)
)
)
- (func $br-gone-means-block-type-changes-then-refinalize-at-end-is-too-late (; 15 ;) (type $1) (param $var$0 i32) (result i32)
+ (func $br-gone-means-block-type-changes-then-refinalize-at-end-is-too-late (; 15 ;) (type $3) (param $var$0 i32) (result i32)
(block $block
(nop)
(unreachable)
)
)
- (func $br-with-unreachable-value-should-not-give-a-block-a-value (; 16 ;) (type $1) (param $var$0 i32) (result i32)
+ (func $br-with-unreachable-value-should-not-give-a-block-a-value (; 16 ;) (type $3) (param $var$0 i32) (result i32)
(block $label$0 (result i32)
(block $block
(drop
diff --git a/test/passes/dce.wast b/test/passes/dce.wast
index 93c98ce4d..18f7db012 100644
--- a/test/passes/dce.wast
+++ b/test/passes/dce.wast
@@ -466,7 +466,7 @@
(i32.const 19)
)
)
- (func $unreachable-block-ends-br_if (type $1) (result i32)
+ (func $unreachable-block-ends-br_if (result i32)
(block $label$0 (result i32)
(block $label$2
(nop)
@@ -538,7 +538,7 @@
)
)
)
- (func $br-gone-means-block-type-changes-then-refinalize-at-end-is-too-late (type $1) (param $var$0 i32) (result i32)
+ (func $br-gone-means-block-type-changes-then-refinalize-at-end-is-too-late (param $var$0 i32) (result i32)
(block $label$0 (result i32)
(br $label$0
(block (result i32)
@@ -554,7 +554,7 @@
)
)
)
- (func $br-with-unreachable-value-should-not-give-a-block-a-value (type $1) (param $var$0 i32) (result i32)
+ (func $br-with-unreachable-value-should-not-give-a-block-a-value (param $var$0 i32) (result i32)
(block $label$0 (result i32)
(br $label$0
(block (result i32) ;; turns into unreachable when refinalized
diff --git a/test/passes/fpcast-emu.txt b/test/passes/fpcast-emu.txt
new file mode 100644
index 000000000..94b92ad17
--- /dev/null
+++ b/test/passes/fpcast-emu.txt
@@ -0,0 +1,294 @@
+(module
+ (type $vijfd (func (param i32 i64 f32 f64)))
+ (type $jii (func (param i32 i32) (result i64)))
+ (type $fjj (func (param i64 i64) (result f32)))
+ (type $dff (func (param f32 f32) (result f64)))
+ (type $idd (func (param f64 f64) (result i32)))
+ (type $FUNCSIG$fijfd (func (param i32 i64 f32 f64) (result f32)))
+ (type $FUNCSIG$jjjjjjjjjjjjjjjj (func (param i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64) (result i64)))
+ (type $FUNCSIG$vijfd (func (param i32 i64 f32 f64)))
+ (type $FUNCSIG$jii (func (param i32 i32) (result i64)))
+ (type $FUNCSIG$fjj (func (param i64 i64) (result f32)))
+ (type $FUNCSIG$dff (func (param f32 f32) (result f64)))
+ (type $FUNCSIG$idd (func (param f64 f64) (result i32)))
+ (import "env" "imported_func" (func $imported-func (param i32 i64 f32 f64) (result f32)))
+ (table 10 10 anyfunc)
+ (elem (i32.const 0) $byn$fpcast-emu$a $byn$fpcast-emu$b $byn$fpcast-emu$c $byn$fpcast-emu$d $byn$fpcast-emu$e $byn$fpcast-emu$e $byn$fpcast-emu$imported-func)
+ (export "dynCall_dff" (func $dynCall_dff))
+ (export "dynCall_idd" (func $dynCall_idd))
+ (func $a (; 1 ;) (type $vijfd) (param $x i32) (param $y i64) (param $z f32) (param $w f64)
+ (drop
+ (call_indirect (type $FUNCSIG$jjjjjjjjjjjjjjjj)
+ (i64.extend_u/i32
+ (i32.const 1)
+ )
+ (i64.const 2)
+ (i64.extend_u/i32
+ (i32.reinterpret/f32
+ (f32.const 3)
+ )
+ )
+ (i64.reinterpret/f64
+ (f64.const 4)
+ )
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i32.const 1337)
+ )
+ )
+ )
+ (func $b (; 2 ;) (type $jii) (param $x i32) (param $y i32) (result i64)
+ (call_indirect (type $FUNCSIG$jjjjjjjjjjjjjjjj)
+ (i64.extend_u/i32
+ (i32.const 1)
+ )
+ (i64.extend_u/i32
+ (i32.const 2)
+ )
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i32.const 1337)
+ )
+ )
+ (func $c (; 3 ;) (type $fjj) (param $x i64) (param $y i64) (result f32)
+ (f32.reinterpret/i32
+ (i32.wrap/i64
+ (call_indirect (type $FUNCSIG$jjjjjjjjjjjjjjjj)
+ (i64.const 1)
+ (i64.const 2)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i32.const 1337)
+ )
+ )
+ )
+ )
+ (func $d (; 4 ;) (type $dff) (param $x f32) (param $y f32) (result f64)
+ (f64.reinterpret/i64
+ (call_indirect (type $FUNCSIG$jjjjjjjjjjjjjjjj)
+ (i64.extend_u/i32
+ (i32.reinterpret/f32
+ (f32.const 1)
+ )
+ )
+ (i64.extend_u/i32
+ (i32.reinterpret/f32
+ (f32.const 2)
+ )
+ )
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i32.const 1337)
+ )
+ )
+ )
+ (func $e (; 5 ;) (type $idd) (param $x f64) (param $y f64) (result i32)
+ (i32.wrap/i64
+ (call_indirect (type $FUNCSIG$jjjjjjjjjjjjjjjj)
+ (i64.reinterpret/f64
+ (f64.const 1)
+ )
+ (i64.reinterpret/f64
+ (f64.const 2)
+ )
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i32.const 1337)
+ )
+ )
+ )
+ (func $dynCall_dff (; 6 ;) (param $fptr i32) (param $0 f32) (param $1 f32) (result f64)
+ (f64.reinterpret/i64
+ (call_indirect (type $FUNCSIG$jjjjjjjjjjjjjjjj)
+ (i64.extend_u/i32
+ (i32.reinterpret/f32
+ (get_local $0)
+ )
+ )
+ (i64.extend_u/i32
+ (i32.reinterpret/f32
+ (get_local $1)
+ )
+ )
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (get_local $fptr)
+ )
+ )
+ )
+ (func $dynCall_idd (; 7 ;) (param $fptr i32) (param $0 f64) (param $1 f64) (result i32)
+ (i32.wrap/i64
+ (call_indirect (type $FUNCSIG$jjjjjjjjjjjjjjjj)
+ (i64.reinterpret/f64
+ (get_local $0)
+ )
+ (i64.reinterpret/f64
+ (get_local $1)
+ )
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (get_local $fptr)
+ )
+ )
+ )
+ (func $byn$fpcast-emu$a (; 8 ;) (type $FUNCSIG$jjjjjjjjjjjjjjjj) (param $0 i64) (param $1 i64) (param $2 i64) (param $3 i64) (param $4 i64) (param $5 i64) (param $6 i64) (param $7 i64) (param $8 i64) (param $9 i64) (param $10 i64) (param $11 i64) (param $12 i64) (param $13 i64) (param $14 i64) (result i64)
+ (call $a
+ (i32.wrap/i64
+ (get_local $0)
+ )
+ (get_local $1)
+ (f32.reinterpret/i32
+ (i32.wrap/i64
+ (get_local $2)
+ )
+ )
+ (f64.reinterpret/i64
+ (get_local $3)
+ )
+ )
+ (i64.const 0)
+ )
+ (func $byn$fpcast-emu$b (; 9 ;) (type $FUNCSIG$jjjjjjjjjjjjjjjj) (param $0 i64) (param $1 i64) (param $2 i64) (param $3 i64) (param $4 i64) (param $5 i64) (param $6 i64) (param $7 i64) (param $8 i64) (param $9 i64) (param $10 i64) (param $11 i64) (param $12 i64) (param $13 i64) (param $14 i64) (result i64)
+ (call $b
+ (i32.wrap/i64
+ (get_local $0)
+ )
+ (i32.wrap/i64
+ (get_local $1)
+ )
+ )
+ )
+ (func $byn$fpcast-emu$c (; 10 ;) (type $FUNCSIG$jjjjjjjjjjjjjjjj) (param $0 i64) (param $1 i64) (param $2 i64) (param $3 i64) (param $4 i64) (param $5 i64) (param $6 i64) (param $7 i64) (param $8 i64) (param $9 i64) (param $10 i64) (param $11 i64) (param $12 i64) (param $13 i64) (param $14 i64) (result i64)
+ (i64.extend_u/i32
+ (i32.reinterpret/f32
+ (call $c
+ (get_local $0)
+ (get_local $1)
+ )
+ )
+ )
+ )
+ (func $byn$fpcast-emu$d (; 11 ;) (type $FUNCSIG$jjjjjjjjjjjjjjjj) (param $0 i64) (param $1 i64) (param $2 i64) (param $3 i64) (param $4 i64) (param $5 i64) (param $6 i64) (param $7 i64) (param $8 i64) (param $9 i64) (param $10 i64) (param $11 i64) (param $12 i64) (param $13 i64) (param $14 i64) (result i64)
+ (i64.reinterpret/f64
+ (call $d
+ (f32.reinterpret/i32
+ (i32.wrap/i64
+ (get_local $0)
+ )
+ )
+ (f32.reinterpret/i32
+ (i32.wrap/i64
+ (get_local $1)
+ )
+ )
+ )
+ )
+ )
+ (func $byn$fpcast-emu$e (; 12 ;) (type $FUNCSIG$jjjjjjjjjjjjjjjj) (param $0 i64) (param $1 i64) (param $2 i64) (param $3 i64) (param $4 i64) (param $5 i64) (param $6 i64) (param $7 i64) (param $8 i64) (param $9 i64) (param $10 i64) (param $11 i64) (param $12 i64) (param $13 i64) (param $14 i64) (result i64)
+ (i64.extend_u/i32
+ (call $e
+ (f64.reinterpret/i64
+ (get_local $0)
+ )
+ (f64.reinterpret/i64
+ (get_local $1)
+ )
+ )
+ )
+ )
+ (func $byn$fpcast-emu$imported-func (; 13 ;) (type $FUNCSIG$jjjjjjjjjjjjjjjj) (param $0 i64) (param $1 i64) (param $2 i64) (param $3 i64) (param $4 i64) (param $5 i64) (param $6 i64) (param $7 i64) (param $8 i64) (param $9 i64) (param $10 i64) (param $11 i64) (param $12 i64) (param $13 i64) (param $14 i64) (result i64)
+ (i64.extend_u/i32
+ (i32.reinterpret/f32
+ (call $imported-func
+ (i32.wrap/i64
+ (get_local $0)
+ )
+ (get_local $1)
+ (f32.reinterpret/i32
+ (i32.wrap/i64
+ (get_local $2)
+ )
+ )
+ (f64.reinterpret/i64
+ (get_local $3)
+ )
+ )
+ )
+ )
+ )
+)
diff --git a/test/passes/fpcast-emu.wast b/test/passes/fpcast-emu.wast
new file mode 100644
index 000000000..cef0d0deb
--- /dev/null
+++ b/test/passes/fpcast-emu.wast
@@ -0,0 +1,48 @@
+(module
+ (type $vijfd (func (param i32) (param i64) (param f32) (param f64)))
+ (type $jii (func (param i32) (param i32) (result i64)))
+ (type $fjj (func (param i64) (param i64) (result f32)))
+ (type $dff (func (param f32) (param f32) (result f64)))
+ (type $idd (func (param f64) (param f64) (result i32)))
+ (import "env" "imported_func" (func $imported-func (param i32 i64 f32 f64) (result f32)))
+ (table 10 10 anyfunc)
+ (elem (i32.const 0) $a $b $c $d $e $e $imported-func)
+ (func $a (param $x i32) (param $y i64) (param $z f32) (param $w f64)
+ (call_indirect (type $vijfd)
+ (i32.const 1)
+ (i64.const 2)
+ (f32.const 3)
+ (f64.const 4)
+ (i32.const 1337)
+ )
+ )
+ (func $b (param $x i32) (param $y i32) (result i64)
+ (call_indirect (type $jii)
+ (i32.const 1)
+ (i32.const 2)
+ (i32.const 1337)
+ )
+ )
+ (func $c (param $x i64) (param $y i64) (result f32)
+ (call_indirect (type $fjj)
+ (i64.const 1)
+ (i64.const 2)
+ (i32.const 1337)
+ )
+ )
+ (func $d (param $x f32) (param $y f32) (result f64)
+ (call_indirect (type $dff)
+ (f32.const 1)
+ (f32.const 2)
+ (i32.const 1337)
+ )
+ )
+ (func $e (param $x f64) (param $y f64) (result i32)
+ (call_indirect (type $idd)
+ (f64.const 1)
+ (f64.const 2)
+ (i32.const 1337)
+ )
+ )
+)
+