summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/abi/stack.h115
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/SpillPointers.cpp206
-rw-r--r--src/passes/pass.cpp3
-rw-r--r--src/passes/passes.h1
-rw-r--r--test/lit/help/wasm-opt.test3
-rw-r--r--test/lit/help/wasm2js.test3
-rw-r--r--test/passes/spill-pointers.txt1293
-rw-r--r--test/passes/spill-pointers.wast338
9 files changed, 1963 insertions, 0 deletions
diff --git a/src/abi/stack.h b/src/abi/stack.h
new file mode 100644
index 000000000..cc678b6e8
--- /dev/null
+++ b/src/abi/stack.h
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+#ifndef wasm_abi_stack_h
+#define wasm_abi_stack_h
+
+#include "asmjs/shared-constants.h"
+#include "ir/find_all.h"
+#include "ir/global-utils.h"
+#include "shared-constants.h"
+#include "wasm-builder.h"
+#include "wasm-emscripten.h"
+#include "wasm.h"
+
+namespace wasm {
+
+namespace ABI {
+
+enum { StackAlign = 16 };
+
+inline Index stackAlign(Index size) {
+ return (size + StackAlign - 1) & -StackAlign;
+}
+
+// Allocate some space on the stack, and assign it to a local.
+// The local will have the same constant value in all the function, so you can
+// just local.get it anywhere there.
+//
+// FIXME: This function assumes that the stack grows upward, per the convention
+// used by fastcomp. The stack grows downward when using the WASM backend.
+
+inline void
+getStackSpace(Index local, Function* func, Index size, Module& wasm) {
+ auto* stackPointer = getStackPointerGlobal(wasm);
+ if (!stackPointer) {
+ Fatal() << "getStackSpace: failed to find the stack pointer";
+ }
+ // align the size
+ size = stackAlign(size);
+ auto pointerType = wasm.memory.indexType;
+ // TODO: find existing stack usage, and add on top of that - carefully
+ Builder builder(wasm);
+ auto* block = builder.makeBlock();
+ block->list.push_back(builder.makeLocalSet(
+ local, builder.makeGlobalGet(stackPointer->name, pointerType)));
+ // TODO: add stack max check
+ Expression* added;
+ if (pointerType == Type::i32) {
+ // The stack goes downward in the LLVM wasm backend.
+ added = builder.makeBinary(SubInt32,
+ builder.makeLocalGet(local, pointerType),
+ builder.makeConst(int32_t(size)));
+ } else {
+ WASM_UNREACHABLE("unhandled pointerType");
+ }
+ block->list.push_back(builder.makeGlobalSet(stackPointer->name, added));
+ auto makeStackRestore = [&]() {
+ return builder.makeGlobalSet(stackPointer->name,
+ builder.makeLocalGet(local, pointerType));
+ };
+ // add stack restores to the returns
+ FindAllPointers<Return> finder(func->body);
+ for (auto** ptr : finder.list) {
+ auto* ret = (*ptr)->cast<Return>();
+ if (ret->value && ret->value->type != Type::unreachable) {
+ // handle the returned value
+ auto* block = builder.makeBlock();
+ auto temp = builder.addVar(func, ret->value->type);
+ block->list.push_back(builder.makeLocalSet(temp, ret->value));
+ block->list.push_back(makeStackRestore());
+ block->list.push_back(
+ builder.makeReturn(builder.makeLocalGet(temp, ret->value->type)));
+ block->finalize();
+ *ptr = block;
+ } else {
+ // restore, then return
+ *ptr = builder.makeSequence(makeStackRestore(), ret);
+ }
+ }
+ // add stack restores to the body
+ if (func->body->type == Type::none) {
+ block->list.push_back(func->body);
+ block->list.push_back(makeStackRestore());
+ } else if (func->body->type == Type::unreachable) {
+ block->list.push_back(func->body);
+ // no need to restore the old stack value, we're gone anyhow
+ } else {
+ // save the return value
+ auto temp = builder.addVar(func, func->getResults());
+ block->list.push_back(builder.makeLocalSet(temp, func->body));
+ block->list.push_back(makeStackRestore());
+ block->list.push_back(builder.makeLocalGet(temp, func->getResults()));
+ }
+ block->finalize();
+ func->body = block;
+}
+
+} // namespace ABI
+
+} // namespace wasm
+
+#endif // wasm_abi_stack_h
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index f74dd4f0b..715590951 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -96,6 +96,7 @@ set(passes_SOURCES
SimplifyGlobals.cpp
SimplifyLocals.cpp
Souperify.cpp
+ SpillPointers.cpp
StackCheck.cpp
SSAify.cpp
Untee.cpp
diff --git a/src/passes/SpillPointers.cpp b/src/passes/SpillPointers.cpp
new file mode 100644
index 000000000..46b1fb47c
--- /dev/null
+++ b/src/passes/SpillPointers.cpp
@@ -0,0 +1,206 @@
+/*
+ * 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.
+ */
+
+//
+// Spills values that might be pointers to the C stack. This allows
+// Boehm-style GC to see them properly.
+//
+// To reduce the overhead of the extra operations added here, you
+// should probably run optimizations after doing it.
+// TODO: add a dead store elimination pass, which would help here
+//
+// * There is currently no check that there is enough stack space.
+//
+
+#include "abi/stack.h"
+#include "cfg/liveness-traversal.h"
+#include "pass.h"
+#include "wasm-builder.h"
+#include "wasm.h"
+
+namespace wasm {
+
+struct SpillPointers
+ : public WalkerPass<LivenessWalker<SpillPointers, Visitor<SpillPointers>>> {
+ bool isFunctionParallel() override { return true; }
+
+ Pass* create() override { return new SpillPointers; }
+
+ // a mapping of the pointers to all the spillable things. We need to know
+ // how to replace them, and as we spill we may modify them. This map
+ // gives us, for an Expression** seen during the walk (and placed in the
+ // basic block, which is what we iterate on for efficiency) => the
+ // current actual pointer, which may have moded
+ std::unordered_map<Expression**, Expression**> actualPointers;
+
+ // note calls in basic blocks
+ template<typename T> void visitSpillable(T* curr) {
+ // if in unreachable code, ignore
+ if (!currBasicBlock) {
+ return;
+ }
+ auto* pointer = getCurrentPointer();
+ currBasicBlock->contents.actions.emplace_back(pointer);
+ // starts out as correct, may change later
+ actualPointers[pointer] = pointer;
+ }
+
+ void visitCall(Call* curr) { visitSpillable(curr); }
+ void visitCallIndirect(CallIndirect* curr) { visitSpillable(curr); }
+
+ // main entry point
+
+ void doWalkFunction(Function* func) {
+ super::doWalkFunction(func);
+ spillPointers();
+ }
+
+ // map pointers to their offset in the spill area
+ typedef std::unordered_map<Index, Index> PointerMap;
+
+ Type pointerType;
+
+ void spillPointers() {
+ pointerType = getModule()->memory.indexType;
+
+ // we only care about possible pointers
+ auto* func = getFunction();
+ PointerMap pointerMap;
+ for (Index i = 0; i < func->getNumLocals(); i++) {
+ if (func->getLocalType(i) == pointerType) {
+ auto offset = pointerMap.size() * pointerType.getByteSize();
+ pointerMap[i] = offset;
+ }
+ }
+ // find calls and spill around them
+ bool spilled = false;
+ Index spillLocal = -1;
+ for (auto& curr : basicBlocks) {
+ if (liveBlocks.count(curr.get()) == 0) {
+ continue; // ignore dead blocks
+ }
+ auto& liveness = curr->contents;
+ auto& actions = liveness.actions;
+ Index lastCall = -1;
+ for (Index i = 0; i < actions.size(); i++) {
+ auto& action = liveness.actions[i];
+ if (action.isOther()) {
+ lastCall = i;
+ }
+ }
+ if (lastCall == Index(-1)) {
+ continue; // nothing to see here
+ }
+ // scan through the block, spilling around the calls
+ // TODO: we can filter on pointerMap everywhere
+ SetOfLocals live = liveness.end;
+ for (int i = int(actions.size()) - 1; i >= 0; i--) {
+ auto& action = actions[i];
+ if (action.isGet()) {
+ live.insert(action.index);
+ } else if (action.isSet()) {
+ live.erase(action.index);
+ } else if (action.isOther()) {
+ std::vector<Index> toSpill;
+ for (auto index : live) {
+ if (pointerMap.count(index) > 0) {
+ toSpill.push_back(index);
+ }
+ }
+ if (!toSpill.empty()) {
+ // we now have a call + the information about which locals
+ // should be spilled
+ if (!spilled) {
+ // prepare stack support: get a pointer to stack space big enough
+ // for all our data
+ spillLocal = Builder::addVar(func, pointerType);
+ spilled = true;
+ }
+ // the origin was seen at walk, but the thing may have moved
+ auto* pointer = actualPointers[action.origin];
+ spillPointersAroundCall(
+ pointer, toSpill, spillLocal, pointerMap, func, getModule());
+ }
+ } else {
+ WASM_UNREACHABLE("unexpected action");
+ }
+ }
+ }
+ if (spilled) {
+ // get the stack space, and set the local to it
+ ABI::getStackSpace(spillLocal,
+ func,
+ pointerType.getByteSize() * pointerMap.size(),
+ *getModule());
+ }
+ }
+
+ void spillPointersAroundCall(Expression** origin,
+ std::vector<Index>& toSpill,
+ Index spillLocal,
+ PointerMap& pointerMap,
+ Function* func,
+ Module* module) {
+ auto* call = *origin;
+ if (call->type == Type::unreachable) {
+ return; // the call is never reached anyhow, ignore
+ }
+ Builder builder(*module);
+ auto* block = builder.makeBlock();
+ // move the operands into locals, as we must spill after they are executed
+ auto handleOperand = [&](Expression*& operand) {
+ auto temp = builder.addVar(func, operand->type);
+ auto* set = builder.makeLocalSet(temp, operand);
+ block->list.push_back(set);
+ block->finalize();
+ if (actualPointers.count(&operand) > 0) {
+ // this is something we track, and it's moving - update
+ actualPointers[&operand] = &set->value;
+ }
+ operand = builder.makeLocalGet(temp, operand->type);
+ };
+ if (call->is<Call>()) {
+ for (auto*& operand : call->cast<Call>()->operands) {
+ handleOperand(operand);
+ }
+ } else if (call->is<CallIndirect>()) {
+ for (auto*& operand : call->cast<CallIndirect>()->operands) {
+ handleOperand(operand);
+ }
+ handleOperand(call->cast<CallIndirect>()->target);
+ } else {
+ WASM_UNREACHABLE("unexpected expr");
+ }
+ // add the spills
+ for (auto index : toSpill) {
+ block->list.push_back(
+ builder.makeStore(pointerType.getByteSize(),
+ pointerMap[index],
+ pointerType.getByteSize(),
+ builder.makeLocalGet(spillLocal, pointerType),
+ builder.makeLocalGet(index, pointerType),
+ pointerType));
+ }
+ // add the (modified) call
+ block->list.push_back(call);
+ block->finalize();
+ *origin = block;
+ }
+};
+
+Pass* createSpillPointersPass() { return new SpillPointers(); }
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 85a240dc3..8ff55eb1a 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -389,6 +389,9 @@ void PassRegistry::registerPasses() {
registerPass("souperify-single-use",
"emit Souper IR in text form (single-use nodes only)",
createSouperifySingleUsePass);
+ 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);
diff --git a/src/passes/passes.h b/src/passes/passes.h
index 2c73ed91e..c30867c20 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -129,6 +129,7 @@ Pass* createStripProducersPass();
Pass* createStripTargetFeaturesPass();
Pass* createSouperifyPass();
Pass* createSouperifySingleUsePass();
+Pass* createSpillPointersPass();
Pass* createStubUnsupportedJSOpsPass();
Pass* createSSAifyPass();
Pass* createSSAifyNoMergePass();
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test
index 1985d70ec..bb111d92e 100644
--- a/test/lit/help/wasm-opt.test
+++ b/test/lit/help/wasm-opt.test
@@ -384,6 +384,9 @@
;; CHECK-NEXT: --souperify-single-use emit Souper IR in text form
;; CHECK-NEXT: (single-use nodes only)
;; CHECK-NEXT:
+;; CHECK-NEXT: --spill-pointers spill pointers to the C stack
+;; CHECK-NEXT: (useful for Boehm-style GC)
+;; CHECK-NEXT:
;; CHECK-NEXT: --ssa ssa-ify variables so that they
;; CHECK-NEXT: have a single assignment
;; CHECK-NEXT:
diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test
index 5c0f249e3..917b8750a 100644
--- a/test/lit/help/wasm2js.test
+++ b/test/lit/help/wasm2js.test
@@ -346,6 +346,9 @@
;; CHECK-NEXT: --souperify-single-use emit Souper IR in text form
;; CHECK-NEXT: (single-use nodes only)
;; CHECK-NEXT:
+;; CHECK-NEXT: --spill-pointers spill pointers to the C stack
+;; CHECK-NEXT: (useful for Boehm-style GC)
+;; CHECK-NEXT:
;; CHECK-NEXT: --ssa ssa-ify variables so that they
;; CHECK-NEXT: have a single assignment
;; CHECK-NEXT:
diff --git a/test/passes/spill-pointers.txt b/test/passes/spill-pointers.txt
new file mode 100644
index 000000000..dcd8b2cbf
--- /dev/null
+++ b/test/passes/spill-pointers.txt
@@ -0,0 +1,1293 @@
+(module
+ (type $none_=>_none (func))
+ (type $i32_=>_i32 (func (param i32) (result i32)))
+ (type $none_=>_i32 (func (result i32)))
+ (type $ii (func (param i32 i32)))
+ (type $i32_=>_none (func (param i32)))
+ (type $f64_=>_none (func (param f64)))
+ (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32))
+ (import "env" "segfault" (func $segfault (param i32)))
+ (global $stack_ptr (mut i32) (global.get $STACKTOP$asm2wasm$import))
+ (memory $0 10)
+ (table $0 1 1 funcref)
+ (elem (i32.const 0))
+ (func $nothing
+ (nop)
+ )
+ (func $not-alive
+ (local $x i32)
+ (local.set $x
+ (i32.const 1)
+ )
+ (call $nothing)
+ )
+ (func $spill
+ (local $x i32)
+ (local $1 i32)
+ (local.set $1
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $1)
+ (i32.const 16)
+ )
+ )
+ (block
+ (block
+ (i32.store
+ (local.get $1)
+ (local.get $x)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ )
+ (func $ignore-non-pointers
+ (local $x i32)
+ (local $y i64)
+ (local $z f32)
+ (local $w f64)
+ (local $4 i32)
+ (local.set $4
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $4)
+ (i32.const 16)
+ )
+ )
+ (block
+ (local.set $x
+ (i32.const 1)
+ )
+ (local.set $y
+ (i64.const 1)
+ )
+ (local.set $z
+ (f32.const 1)
+ )
+ (local.set $w
+ (f64.const 1)
+ )
+ (block
+ (i32.store
+ (local.get $4)
+ (local.get $x)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ (drop
+ (local.get $y)
+ )
+ (drop
+ (local.get $z)
+ )
+ (drop
+ (local.get $w)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $4)
+ )
+ )
+ (func $spill4
+ (local $x i32)
+ (local $y i32)
+ (local $z i32)
+ (local $w i32)
+ (local $4 i32)
+ (local.set $4
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $4)
+ (i32.const 16)
+ )
+ )
+ (block
+ (local.set $x
+ (i32.const 1)
+ )
+ (local.set $y
+ (i32.const 1)
+ )
+ (local.set $z
+ (i32.const 1)
+ )
+ (local.set $w
+ (i32.const 1)
+ )
+ (block
+ (i32.store
+ (local.get $4)
+ (local.get $x)
+ )
+ (i32.store offset=4
+ (local.get $4)
+ (local.get $y)
+ )
+ (i32.store offset=8
+ (local.get $4)
+ (local.get $z)
+ )
+ (i32.store offset=12
+ (local.get $4)
+ (local.get $w)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ (drop
+ (local.get $y)
+ )
+ (drop
+ (local.get $z)
+ )
+ (drop
+ (local.get $w)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $4)
+ )
+ )
+ (func $spill5
+ (local $x i32)
+ (local $y i32)
+ (local $z i32)
+ (local $w i32)
+ (local $a i32)
+ (local $5 i32)
+ (local.set $5
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $5)
+ (i32.const 32)
+ )
+ )
+ (block
+ (local.set $x
+ (i32.const 1)
+ )
+ (local.set $y
+ (i32.const 1)
+ )
+ (local.set $z
+ (i32.const 1)
+ )
+ (local.set $w
+ (i32.const 1)
+ )
+ (local.set $a
+ (i32.const 1)
+ )
+ (block
+ (i32.store
+ (local.get $5)
+ (local.get $x)
+ )
+ (i32.store offset=4
+ (local.get $5)
+ (local.get $y)
+ )
+ (i32.store offset=8
+ (local.get $5)
+ (local.get $z)
+ )
+ (i32.store offset=12
+ (local.get $5)
+ (local.get $w)
+ )
+ (i32.store offset=16
+ (local.get $5)
+ (local.get $a)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ (drop
+ (local.get $y)
+ )
+ (drop
+ (local.get $z)
+ )
+ (drop
+ (local.get $w)
+ )
+ (drop
+ (local.get $a)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $5)
+ )
+ )
+ (func $some-alive
+ (local $x i32)
+ (local $y i32)
+ (local $2 i32)
+ (local.set $2
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $2)
+ (i32.const 16)
+ )
+ )
+ (block
+ (block
+ (i32.store
+ (local.get $2)
+ (local.get $x)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $2)
+ )
+ )
+ (func $spill-args (param $p i32) (param $q i32)
+ (local $x i32)
+ (local $3 i32)
+ (local $4 i32)
+ (local $5 i32)
+ (local.set $3
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $3)
+ (i32.const 16)
+ )
+ )
+ (block
+ (block
+ (local.set $4
+ (i32.const 1)
+ )
+ (local.set $5
+ (i32.const 2)
+ )
+ (i32.store offset=8
+ (local.get $3)
+ (local.get $x)
+ )
+ (call $spill-args
+ (local.get $4)
+ (local.get $5)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $3)
+ )
+ )
+ (func $spill-ret (result i32)
+ (local $x i32)
+ (local $1 i32)
+ (local $2 i32)
+ (local $3 i32)
+ (local $4 i32)
+ (local.set $1
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $1)
+ (i32.const 16)
+ )
+ )
+ (local.set $4
+ (block (result i32)
+ (block
+ (i32.store
+ (local.get $1)
+ (local.get $x)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ (if
+ (i32.const 1)
+ (block
+ (local.set $2
+ (i32.const 2)
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (return
+ (local.get $2)
+ )
+ )
+ (block
+ (local.set $3
+ (i32.const 3)
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (return
+ (local.get $3)
+ )
+ )
+ )
+ (i32.const 4)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (local.get $4)
+ )
+ (func $spill-unreachable (result i32)
+ (local $x i32)
+ (local $1 i32)
+ (local $2 i32)
+ (local.set $1
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $1)
+ (i32.const 16)
+ )
+ )
+ (local.set $2
+ (block (result i32)
+ (block
+ (i32.store
+ (local.get $1)
+ (local.get $x)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ (unreachable)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (local.get $2)
+ )
+ (func $spill-call-call0 (param $p i32) (result i32)
+ (unreachable)
+ )
+ (func $spill-call-call1 (param $p i32) (result i32)
+ (local $x i32)
+ (local $2 i32)
+ (local $3 i32)
+ (local $4 i32)
+ (local $5 i32)
+ (local.set $2
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $2)
+ (i32.const 16)
+ )
+ )
+ (local.set $5
+ (block (result i32)
+ (drop
+ (block (result i32)
+ (local.set $3
+ (block (result i32)
+ (local.set $4
+ (i32.const 1)
+ )
+ (i32.store offset=4
+ (local.get $2)
+ (local.get $x)
+ )
+ (call $spill-call-call1
+ (local.get $4)
+ )
+ )
+ )
+ (i32.store offset=4
+ (local.get $2)
+ (local.get $x)
+ )
+ (call $spill-call-call0
+ (local.get $3)
+ )
+ )
+ )
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $2)
+ )
+ (local.get $5)
+ )
+ (func $spill-call-ret (param $p i32) (result i32)
+ (local $x i32)
+ (drop
+ (call $spill-call-call0
+ (return
+ (i32.const 1)
+ )
+ )
+ )
+ (i32.const 0)
+ )
+ (func $spill-ret-call (param $p i32) (result i32)
+ (local $x i32)
+ (drop
+ (return
+ (call $spill-call-call0
+ (i32.const 1)
+ )
+ )
+ )
+ (i32.const 0)
+ )
+ (func $spill-ret-ret (result i32)
+ (local $x i32)
+ (local $1 i32)
+ (local $2 i32)
+ (local $3 i32)
+ (local.set $1
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $1)
+ (i32.const 16)
+ )
+ )
+ (local.set $3
+ (block (result i32)
+ (block
+ (i32.store
+ (local.get $1)
+ (local.get $x)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ (drop
+ (block
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (return
+ (block
+ (local.set $2
+ (i32.const 1)
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (return
+ (local.get $2)
+ )
+ )
+ )
+ )
+ )
+ (i32.const 0)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (local.get $3)
+ )
+ (func $spill-call-othertype (param $y f64)
+ (local $x i32)
+ (local $2 i32)
+ (local $3 f64)
+ (local.set $2
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $2)
+ (i32.const 16)
+ )
+ )
+ (block
+ (block
+ (local.set $3
+ (f64.const 1)
+ )
+ (i32.store
+ (local.get $2)
+ (local.get $x)
+ )
+ (call $spill-call-othertype
+ (local.get $3)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $2)
+ )
+ )
+ (func $spill-call_indirect
+ (local $x i32)
+ (local $1 i32)
+ (local $2 i32)
+ (local $3 i32)
+ (local $4 i32)
+ (local.set $1
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $1)
+ (i32.const 16)
+ )
+ )
+ (block
+ (block
+ (local.set $2
+ (i32.const 123)
+ )
+ (local.set $3
+ (i32.const 456)
+ )
+ (local.set $4
+ (i32.const 789)
+ )
+ (i32.store
+ (local.get $1)
+ (local.get $x)
+ )
+ (call_indirect (type $ii)
+ (local.get $2)
+ (local.get $3)
+ (local.get $4)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ )
+ (func $spill-call_import
+ (local $x i32)
+ (local $1 i32)
+ (local $2 i32)
+ (local.set $1
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $1)
+ (i32.const 16)
+ )
+ )
+ (block
+ (block
+ (local.set $2
+ (i32.const 200)
+ )
+ (i32.store
+ (local.get $1)
+ (local.get $x)
+ )
+ (call $segfault
+ (local.get $2)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ )
+)
+(module
+ (type $none_=>_none (func))
+ (type $none_=>_i32 (func (result i32)))
+ (type $i32_=>_i32 (func (param i32) (result i32)))
+ (type $ii (func (param i32 i32)))
+ (type $i32_=>_none (func (param i32)))
+ (type $f64_=>_none (func (param f64)))
+ (import "env" "segfault" (func $segfault (param i32)))
+ (global $stack_ptr (mut i32) (i32.const 1716592))
+ (memory $0 10)
+ (table $0 1 1 funcref)
+ (elem (i32.const 0))
+ (export "stackSave" (func $stack_save))
+ (func $stack_save (result i32)
+ (global.get $stack_ptr)
+ )
+ (func $nothing
+ (nop)
+ )
+ (func $not-alive
+ (local $x i32)
+ (local.set $x
+ (i32.const 1)
+ )
+ (call $nothing)
+ )
+ (func $spill
+ (local $x i32)
+ (local $1 i32)
+ (local.set $1
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $1)
+ (i32.const 16)
+ )
+ )
+ (block
+ (block
+ (i32.store
+ (local.get $1)
+ (local.get $x)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ )
+ (func $ignore-non-pointers
+ (local $x i32)
+ (local $y i64)
+ (local $z f32)
+ (local $w f64)
+ (local $4 i32)
+ (local.set $4
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $4)
+ (i32.const 16)
+ )
+ )
+ (block
+ (local.set $x
+ (i32.const 1)
+ )
+ (local.set $y
+ (i64.const 1)
+ )
+ (local.set $z
+ (f32.const 1)
+ )
+ (local.set $w
+ (f64.const 1)
+ )
+ (block
+ (i32.store
+ (local.get $4)
+ (local.get $x)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ (drop
+ (local.get $y)
+ )
+ (drop
+ (local.get $z)
+ )
+ (drop
+ (local.get $w)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $4)
+ )
+ )
+ (func $spill4
+ (local $x i32)
+ (local $y i32)
+ (local $z i32)
+ (local $w i32)
+ (local $4 i32)
+ (local.set $4
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $4)
+ (i32.const 16)
+ )
+ )
+ (block
+ (local.set $x
+ (i32.const 1)
+ )
+ (local.set $y
+ (i32.const 1)
+ )
+ (local.set $z
+ (i32.const 1)
+ )
+ (local.set $w
+ (i32.const 1)
+ )
+ (block
+ (i32.store
+ (local.get $4)
+ (local.get $x)
+ )
+ (i32.store offset=4
+ (local.get $4)
+ (local.get $y)
+ )
+ (i32.store offset=8
+ (local.get $4)
+ (local.get $z)
+ )
+ (i32.store offset=12
+ (local.get $4)
+ (local.get $w)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ (drop
+ (local.get $y)
+ )
+ (drop
+ (local.get $z)
+ )
+ (drop
+ (local.get $w)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $4)
+ )
+ )
+ (func $spill5
+ (local $x i32)
+ (local $y i32)
+ (local $z i32)
+ (local $w i32)
+ (local $a i32)
+ (local $5 i32)
+ (local.set $5
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $5)
+ (i32.const 32)
+ )
+ )
+ (block
+ (local.set $x
+ (i32.const 1)
+ )
+ (local.set $y
+ (i32.const 1)
+ )
+ (local.set $z
+ (i32.const 1)
+ )
+ (local.set $w
+ (i32.const 1)
+ )
+ (local.set $a
+ (i32.const 1)
+ )
+ (block
+ (i32.store
+ (local.get $5)
+ (local.get $x)
+ )
+ (i32.store offset=4
+ (local.get $5)
+ (local.get $y)
+ )
+ (i32.store offset=8
+ (local.get $5)
+ (local.get $z)
+ )
+ (i32.store offset=12
+ (local.get $5)
+ (local.get $w)
+ )
+ (i32.store offset=16
+ (local.get $5)
+ (local.get $a)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ (drop
+ (local.get $y)
+ )
+ (drop
+ (local.get $z)
+ )
+ (drop
+ (local.get $w)
+ )
+ (drop
+ (local.get $a)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $5)
+ )
+ )
+ (func $some-alive
+ (local $x i32)
+ (local $y i32)
+ (local $2 i32)
+ (local.set $2
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $2)
+ (i32.const 16)
+ )
+ )
+ (block
+ (block
+ (i32.store
+ (local.get $2)
+ (local.get $x)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $2)
+ )
+ )
+ (func $spill-args (param $p i32) (param $q i32)
+ (local $x i32)
+ (local $3 i32)
+ (local $4 i32)
+ (local $5 i32)
+ (local.set $3
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $3)
+ (i32.const 16)
+ )
+ )
+ (block
+ (block
+ (local.set $4
+ (i32.const 1)
+ )
+ (local.set $5
+ (i32.const 2)
+ )
+ (i32.store offset=8
+ (local.get $3)
+ (local.get $x)
+ )
+ (call $spill-args
+ (local.get $4)
+ (local.get $5)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $3)
+ )
+ )
+ (func $spill-ret (result i32)
+ (local $x i32)
+ (local $1 i32)
+ (local $2 i32)
+ (local $3 i32)
+ (local $4 i32)
+ (local.set $1
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $1)
+ (i32.const 16)
+ )
+ )
+ (local.set $4
+ (block (result i32)
+ (block
+ (i32.store
+ (local.get $1)
+ (local.get $x)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ (if
+ (i32.const 1)
+ (block
+ (local.set $2
+ (i32.const 2)
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (return
+ (local.get $2)
+ )
+ )
+ (block
+ (local.set $3
+ (i32.const 3)
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (return
+ (local.get $3)
+ )
+ )
+ )
+ (i32.const 4)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (local.get $4)
+ )
+ (func $spill-unreachable (result i32)
+ (local $x i32)
+ (local $1 i32)
+ (local $2 i32)
+ (local.set $1
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $1)
+ (i32.const 16)
+ )
+ )
+ (local.set $2
+ (block (result i32)
+ (block
+ (i32.store
+ (local.get $1)
+ (local.get $x)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ (unreachable)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (local.get $2)
+ )
+ (func $spill-call-call0 (param $p i32) (result i32)
+ (unreachable)
+ )
+ (func $spill-call-call1 (param $p i32) (result i32)
+ (local $x i32)
+ (local $2 i32)
+ (local $3 i32)
+ (local $4 i32)
+ (local $5 i32)
+ (local.set $2
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $2)
+ (i32.const 16)
+ )
+ )
+ (local.set $5
+ (block (result i32)
+ (drop
+ (block (result i32)
+ (local.set $3
+ (block (result i32)
+ (local.set $4
+ (i32.const 1)
+ )
+ (i32.store offset=4
+ (local.get $2)
+ (local.get $x)
+ )
+ (call $spill-call-call1
+ (local.get $4)
+ )
+ )
+ )
+ (i32.store offset=4
+ (local.get $2)
+ (local.get $x)
+ )
+ (call $spill-call-call0
+ (local.get $3)
+ )
+ )
+ )
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $2)
+ )
+ (local.get $5)
+ )
+ (func $spill-call-ret (param $p i32) (result i32)
+ (local $x i32)
+ (drop
+ (call $spill-call-call0
+ (return
+ (i32.const 1)
+ )
+ )
+ )
+ (i32.const 0)
+ )
+ (func $spill-ret-call (param $p i32) (result i32)
+ (local $x i32)
+ (drop
+ (return
+ (call $spill-call-call0
+ (i32.const 1)
+ )
+ )
+ )
+ (i32.const 0)
+ )
+ (func $spill-ret-ret (result i32)
+ (local $x i32)
+ (local $1 i32)
+ (local $2 i32)
+ (local $3 i32)
+ (local.set $1
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $1)
+ (i32.const 16)
+ )
+ )
+ (local.set $3
+ (block (result i32)
+ (block
+ (i32.store
+ (local.get $1)
+ (local.get $x)
+ )
+ (call $nothing)
+ )
+ (drop
+ (local.get $x)
+ )
+ (drop
+ (block
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (return
+ (block
+ (local.set $2
+ (i32.const 1)
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (return
+ (local.get $2)
+ )
+ )
+ )
+ )
+ )
+ (i32.const 0)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ (local.get $3)
+ )
+ (func $spill-call-othertype (param $y f64)
+ (local $x i32)
+ (local $2 i32)
+ (local $3 f64)
+ (local.set $2
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $2)
+ (i32.const 16)
+ )
+ )
+ (block
+ (block
+ (local.set $3
+ (f64.const 1)
+ )
+ (i32.store
+ (local.get $2)
+ (local.get $x)
+ )
+ (call $spill-call-othertype
+ (local.get $3)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $2)
+ )
+ )
+ (func $spill-call_indirect
+ (local $x i32)
+ (local $1 i32)
+ (local $2 i32)
+ (local $3 i32)
+ (local $4 i32)
+ (local.set $1
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $1)
+ (i32.const 16)
+ )
+ )
+ (block
+ (block
+ (local.set $2
+ (i32.const 123)
+ )
+ (local.set $3
+ (i32.const 456)
+ )
+ (local.set $4
+ (i32.const 789)
+ )
+ (i32.store
+ (local.get $1)
+ (local.get $x)
+ )
+ (call_indirect (type $ii)
+ (local.get $2)
+ (local.get $3)
+ (local.get $4)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ )
+ (func $spill-call_import
+ (local $x i32)
+ (local $1 i32)
+ (local $2 i32)
+ (local.set $1
+ (global.get $stack_ptr)
+ )
+ (global.set $stack_ptr
+ (i32.sub
+ (local.get $1)
+ (i32.const 16)
+ )
+ )
+ (block
+ (block
+ (local.set $2
+ (i32.const 200)
+ )
+ (i32.store
+ (local.get $1)
+ (local.get $x)
+ )
+ (call $segfault
+ (local.get $2)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+ (global.set $stack_ptr
+ (local.get $1)
+ )
+ )
+)
diff --git a/test/passes/spill-pointers.wast b/test/passes/spill-pointers.wast
new file mode 100644
index 000000000..4eb05a721
--- /dev/null
+++ b/test/passes/spill-pointers.wast
@@ -0,0 +1,338 @@
+(module
+ (memory 10)
+ (type $ii (func (param i32 i32)))
+ (table 1 1 funcref)
+ (elem (i32.const 0))
+ (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32))
+ (import "env" "segfault" (func $segfault (param i32)))
+ (global $stack_ptr (mut i32) (global.get $STACKTOP$asm2wasm$import))
+
+ (func $nothing
+ )
+ (func $not-alive
+ (local $x i32)
+ (local.set $x (i32.const 1))
+ (call $nothing)
+ )
+ (func $spill
+ (local $x i32)
+ (call $nothing)
+ (drop (local.get $x))
+ )
+ (func $ignore-non-pointers
+ (local $x i32)
+ (local $y i64)
+ (local $z f32)
+ (local $w f64)
+ (local.set $x (i32.const 1))
+ (local.set $y (i64.const 1))
+ (local.set $z (f32.const 1))
+ (local.set $w (f64.const 1))
+ (call $nothing)
+ (drop (local.get $x))
+ (drop (local.get $y))
+ (drop (local.get $z))
+ (drop (local.get $w))
+ )
+ (func $spill4
+ (local $x i32)
+ (local $y i32)
+ (local $z i32)
+ (local $w i32)
+ (local.set $x (i32.const 1))
+ (local.set $y (i32.const 1))
+ (local.set $z (i32.const 1))
+ (local.set $w (i32.const 1))
+ (call $nothing)
+ (drop (local.get $x))
+ (drop (local.get $y))
+ (drop (local.get $z))
+ (drop (local.get $w))
+ )
+ (func $spill5
+ (local $x i32)
+ (local $y i32)
+ (local $z i32)
+ (local $w i32)
+ (local $a i32)
+ (local.set $x (i32.const 1))
+ (local.set $y (i32.const 1))
+ (local.set $z (i32.const 1))
+ (local.set $w (i32.const 1))
+ (local.set $a (i32.const 1))
+ (call $nothing)
+ (drop (local.get $x))
+ (drop (local.get $y))
+ (drop (local.get $z))
+ (drop (local.get $w))
+ (drop (local.get $a))
+ )
+ (func $some-alive
+ (local $x i32)
+ (local $y i32)
+ (call $nothing)
+ (drop (local.get $x))
+ )
+ (func $spill-args (param $p i32) (param $q i32)
+ (local $x i32)
+ (call $spill-args (i32.const 1) (i32.const 2))
+ (drop (local.get $x))
+ )
+ (func $spill-ret (result i32)
+ (local $x i32)
+ (call $nothing)
+ (drop (local.get $x))
+ (if (i32.const 1)
+ (return (i32.const 2))
+ (return (i32.const 3))
+ )
+ (i32.const 4)
+ )
+ (func $spill-unreachable (result i32)
+ (local $x i32)
+ (call $nothing)
+ (drop (local.get $x))
+ (unreachable)
+ )
+ (func $spill-call-call0 (param $p i32) (result i32)
+ (unreachable)
+ )
+ (func $spill-call-call1 (param $p i32) (result i32)
+ (local $x i32)
+ (drop
+ (call $spill-call-call0
+ (call $spill-call-call1
+ (i32.const 1)
+ )
+ )
+ )
+ (local.get $x)
+ )
+ (func $spill-call-ret (param $p i32) (result i32)
+ (local $x i32)
+ (drop
+ (call $spill-call-call0
+ (return
+ (i32.const 1)
+ )
+ )
+ )
+ (local.get $x)
+ )
+ (func $spill-ret-call (param $p i32) (result i32)
+ (local $x i32)
+ (drop
+ (return
+ (call $spill-call-call0
+ (i32.const 1)
+ )
+ )
+ )
+ (local.get $x)
+ )
+ (func $spill-ret-ret (result i32)
+ (local $x i32)
+ (call $nothing)
+ (drop (local.get $x))
+ (drop
+ (return
+ (return
+ (i32.const 1)
+ )
+ )
+ )
+ (local.get $x)
+ )
+ (func $spill-call-othertype (param $y f64)
+ (local $x i32)
+ (call $spill-call-othertype (f64.const 1))
+ (drop (local.get $x))
+ )
+ (func $spill-call_indirect
+ (local $x i32)
+ (call_indirect (type $ii)
+ (i32.const 123)
+ (i32.const 456)
+ (i32.const 789)
+ )
+ (drop (local.get $x))
+ )
+ (func $spill-call_import
+ (local $x i32)
+ (call $segfault
+ (i32.const 200)
+ )
+ (drop (local.get $x))
+ )
+)
+
+(module
+ (memory 10)
+ (type $ii (func (param i32 i32)))
+ (table 1 1 funcref)
+ (elem (i32.const 0))
+ (global $stack_ptr (mut i32) (i32.const 1716592))
+ (export "stackSave" (func $stack_save))
+ (import "env" "segfault" (func $segfault (param i32)))
+ (func $stack_save (result i32)
+ (global.get $stack_ptr)
+ )
+
+ (func $nothing
+ )
+ (func $not-alive
+ (local $x i32)
+ (local.set $x (i32.const 1))
+ (call $nothing)
+ )
+ (func $spill
+ (local $x i32)
+ (call $nothing)
+ (drop (local.get $x))
+ )
+ (func $ignore-non-pointers
+ (local $x i32)
+ (local $y i64)
+ (local $z f32)
+ (local $w f64)
+ (local.set $x (i32.const 1))
+ (local.set $y (i64.const 1))
+ (local.set $z (f32.const 1))
+ (local.set $w (f64.const 1))
+ (call $nothing)
+ (drop (local.get $x))
+ (drop (local.get $y))
+ (drop (local.get $z))
+ (drop (local.get $w))
+ )
+ (func $spill4
+ (local $x i32)
+ (local $y i32)
+ (local $z i32)
+ (local $w i32)
+ (local.set $x (i32.const 1))
+ (local.set $y (i32.const 1))
+ (local.set $z (i32.const 1))
+ (local.set $w (i32.const 1))
+ (call $nothing)
+ (drop (local.get $x))
+ (drop (local.get $y))
+ (drop (local.get $z))
+ (drop (local.get $w))
+ )
+ (func $spill5
+ (local $x i32)
+ (local $y i32)
+ (local $z i32)
+ (local $w i32)
+ (local $a i32)
+ (local.set $x (i32.const 1))
+ (local.set $y (i32.const 1))
+ (local.set $z (i32.const 1))
+ (local.set $w (i32.const 1))
+ (local.set $a (i32.const 1))
+ (call $nothing)
+ (drop (local.get $x))
+ (drop (local.get $y))
+ (drop (local.get $z))
+ (drop (local.get $w))
+ (drop (local.get $a))
+ )
+ (func $some-alive
+ (local $x i32)
+ (local $y i32)
+ (call $nothing)
+ (drop (local.get $x))
+ )
+ (func $spill-args (param $p i32) (param $q i32)
+ (local $x i32)
+ (call $spill-args (i32.const 1) (i32.const 2))
+ (drop (local.get $x))
+ )
+ (func $spill-ret (result i32)
+ (local $x i32)
+ (call $nothing)
+ (drop (local.get $x))
+ (if (i32.const 1)
+ (return (i32.const 2))
+ (return (i32.const 3))
+ )
+ (i32.const 4)
+ )
+ (func $spill-unreachable (result i32)
+ (local $x i32)
+ (call $nothing)
+ (drop (local.get $x))
+ (unreachable)
+ )
+ (func $spill-call-call0 (param $p i32) (result i32)
+ (unreachable)
+ )
+ (func $spill-call-call1 (param $p i32) (result i32)
+ (local $x i32)
+ (drop
+ (call $spill-call-call0
+ (call $spill-call-call1
+ (i32.const 1)
+ )
+ )
+ )
+ (local.get $x)
+ )
+ (func $spill-call-ret (param $p i32) (result i32)
+ (local $x i32)
+ (drop
+ (call $spill-call-call0
+ (return
+ (i32.const 1)
+ )
+ )
+ )
+ (local.get $x)
+ )
+ (func $spill-ret-call (param $p i32) (result i32)
+ (local $x i32)
+ (drop
+ (return
+ (call $spill-call-call0
+ (i32.const 1)
+ )
+ )
+ )
+ (local.get $x)
+ )
+ (func $spill-ret-ret (result i32)
+ (local $x i32)
+ (call $nothing)
+ (drop (local.get $x))
+ (drop
+ (return
+ (return
+ (i32.const 1)
+ )
+ )
+ )
+ (local.get $x)
+ )
+ (func $spill-call-othertype (param $y f64)
+ (local $x i32)
+ (call $spill-call-othertype (f64.const 1))
+ (drop (local.get $x))
+ )
+ (func $spill-call_indirect
+ (local $x i32)
+ (call_indirect (type $ii)
+ (i32.const 123)
+ (i32.const 456)
+ (i32.const 789)
+ )
+ (drop (local.get $x))
+ )
+ (func $spill-call_import
+ (local $x i32)
+ (call $segfault
+ (i32.const 200)
+ )
+ (drop (local.get $x))
+ )
+)