diff options
-rw-r--r-- | src/abi/abi.h | 33 | ||||
-rw-r--r-- | src/abi/stack.h | 124 | ||||
-rw-r--r-- | src/cfg/liveness-traversal.h | 234 | ||||
-rw-r--r-- | src/ir/find_all.h | 24 | ||||
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/CoalesceLocals.cpp | 287 | ||||
-rw-r--r-- | src/passes/SpillPointers.cpp | 198 | ||||
-rw-r--r-- | src/passes/pass.cpp | 1 | ||||
-rw-r--r-- | src/passes/passes.h | 1 | ||||
-rw-r--r-- | src/support/sorted_vector.h | 103 | ||||
-rw-r--r-- | test/passes/spill-pointers.txt | 644 | ||||
-rw-r--r-- | test/passes/spill-pointers.wast | 168 |
12 files changed, 1535 insertions, 283 deletions
diff --git a/src/abi/abi.h b/src/abi/abi.h new file mode 100644 index 000000000..ff1864efa --- /dev/null +++ b/src/abi/abi.h @@ -0,0 +1,33 @@ +/* + * 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_abi_h +#define wasm_abi_abi_h + +#include "wasm.h" + +namespace wasm { + +namespace ABI { + +// The pointer type. Will need to update this for wasm64 +const static WasmType PointerType = WasmType::i32; + +} // namespace ABI + +} // namespace wasm + +#endif // wasm_abi_abi_h diff --git a/src/abi/stack.h b/src/abi/stack.h new file mode 100644 index 000000000..e43be07ec --- /dev/null +++ b/src/abi/stack.h @@ -0,0 +1,124 @@ +/* + * 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 "wasm.h" +#include "wasm-builder.h" +#include "shared-constants.h" +#include "asmjs/shared-constants.h" +#include "ir/find_all.h" +#include "ir/global-utils.h" +#include "abi.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 +// get_local it anywhere there. +inline void getStackSpace(Index local, Function* func, Index size, Module& wasm) { + auto* stackPointer = GlobalUtils::getGlobalInitializedToImport(wasm, ENV, "STACKTOP"); + if (!stackPointer) { + Fatal() << "getStackSpace: failed to find the stack pointer"; + } + // align the size + size = stackAlign(size); + // TODO: find existing stack usage, and add on top of that - carefully + Builder builder(wasm); + auto* block = builder.makeBlock(); + block->list.push_back( + builder.makeSetLocal( + local, + builder.makeGetGlobal(stackPointer->name, PointerType) + ) + ); + // TODO: add stack max check + Expression* added; + if (PointerType == i32) { + added = builder.makeBinary( + AddInt32, + builder.makeGetLocal(local, PointerType), + builder.makeConst(Literal(int32_t(size))) + ); + } else { + WASM_UNREACHABLE(); + } + block->list.push_back( + builder.makeSetGlobal( + stackPointer->name, + added + ) + ); + auto makeStackRestore = [&]() { + return builder.makeSetGlobal( + stackPointer->name, + builder.makeGetLocal(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 != unreachable) { + // handle the returned value + auto* block = builder.makeBlock(); + auto temp = builder.addVar(func, ret->value->type); + block->list.push_back(builder.makeSetLocal(temp, ret->value)); + block->list.push_back(makeStackRestore()); + block->list.push_back(builder.makeReturn( + builder.makeGetLocal(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 == none) { + block->list.push_back(func->body); + block->list.push_back(makeStackRestore()); + } else if (func->body->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->result); + block->list.push_back(builder.makeSetLocal(temp, func->body)); + block->list.push_back(makeStackRestore()); + block->list.push_back(builder.makeGetLocal(temp, func->result)); + } + block->finalize(); + func->body = block; +} + +} // namespace ABI + +} // namespace wasm + +#endif // wasm_abi_stack_h diff --git a/src/cfg/liveness-traversal.h b/src/cfg/liveness-traversal.h new file mode 100644 index 000000000..24578dcd7 --- /dev/null +++ b/src/cfg/liveness-traversal.h @@ -0,0 +1,234 @@ +#include <wasm-printing.h> +/* + * 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. + */ + +// +// Convert the AST to a CFG, while traversing it. +// +// Note that this is not the same as the relooper CFG. The relooper is +// designed for compilation to an AST, this is for processing. There is +// no built-in support for transforming this CFG into the AST back +// again, it is just metadata on the side for computation purposes. +// +// Usage: As the traversal proceeds, you can note information and add it to +// the current basic block using currBasicBlock, on the contents +// property, whose type is user-defined. +// + +#ifndef liveness_traversal_h +#define liveness_traversal_h + +#include "support/sorted_vector.h" +#include "wasm.h" +#include "wasm-builder.h" +#include "wasm-traversal.h" +#include "cfg-traversal.h" + +namespace wasm { + +// A set of locals. This is optimized for comparisons, +// mergings, and iteration on elements, assuming that there +// may be a great many potential elements but actual sets +// may be fairly small. Specifically, we use a sorted +// vector. +typedef SortedVector LocalSet; + +// A liveness-relevant action. Supports a get, a set, or an +// "other" which can be used for other purposes, to mark +// their position in a block +struct Action { + enum What { + Get = 0, + Set = 1, + Other = 2 + }; + What what; + Index index; // the local index read or written + Expression** origin; // the origin + bool effective; // whether a store is actually effective, i.e., may be read + + Action(What what, Index index, Expression** origin) : what(what), index(index), origin(origin), effective(false) { + assert(what != Other); + if (what == Get) assert((*origin)->is<GetLocal>()); + if (what == Set) assert((*origin)->is<SetLocal>()); + } + Action(Expression** origin) : what(Other), origin(origin) {} + + bool isGet() { return what == Get; } + bool isSet() { return what == Set; } + bool isOther() { return what == Other; } +}; + +// information about liveness in a basic block +struct Liveness { + LocalSet start, end; // live locals at the start and end + std::vector<Action> actions; // actions occurring in this block + + void dump(Function* func) { + if (actions.empty()) return; + std::cout << " actions:\n"; + for (auto& action : actions) { + std::cout << " " << (action.isGet() ? "get" : "set") << " " << func->getLocalName(action.index) << "\n"; + } + } +}; + +template<typename SubType, typename VisitorType> +struct LivenessWalker : public CFGWalker<SubType, VisitorType, Liveness> { + typedef typename CFGWalker<SubType, VisitorType, Liveness>::BasicBlock BasicBlock; + + Index numLocals; + std::unordered_set<BasicBlock*> liveBlocks; + std::vector<uint8_t> copies; // canonicalized - accesses should check (low, high) TODO: use a map for high N, as this tends to be sparse? or don't look at copies at all for big N? + std::vector<Index> totalCopies; // total # of copies for each local, with all others + + // cfg traversal work + + static void doVisitGetLocal(SubType* self, Expression** currp) { + auto* curr = (*currp)->cast<GetLocal>(); + // if in unreachable code, ignore + if (!self->currBasicBlock) { + *currp = Builder(*self->getModule()).replaceWithIdenticalType(curr); + return; + } + self->currBasicBlock->contents.actions.emplace_back(Action::Get, curr->index, currp); + } + + static void doVisitSetLocal(SubType* self, Expression** currp) { + auto* curr = (*currp)->cast<SetLocal>(); + // if in unreachable code, we don't need the tee (but might need the value, if it has side effects) + if (!self->currBasicBlock) { + if (curr->isTee()) { + *currp = curr->value; + } else { + *currp = Builder(*self->getModule()).makeDrop(curr->value); + } + return; + } + self->currBasicBlock->contents.actions.emplace_back(Action::Set, curr->index, currp); + // if this is a copy, note it + if (auto* get = self->getCopy(curr)) { + // add 2 units, so that backedge prioritization can decide ties, but not much more + self->addCopy(curr->index, get->index); + self->addCopy(curr->index, get->index); + } + } + + // A simple copy is a set of a get. A more interesting copy + // is a set of an if with a value, where one side a get. + // That can happen when we create an if value in simplify-locals. TODO: recurse into + // nested ifs, and block return values? Those cases are trickier, need to + // count to see if worth it. + // TODO: an if can have two copies + GetLocal* getCopy(SetLocal* set) { + if (auto* get = set->value->dynCast<GetLocal>()) return get; + if (auto* iff = set->value->dynCast<If>()) { + if (auto* get = iff->ifTrue->dynCast<GetLocal>()) return get; + if (iff->ifFalse) { + if (auto* get = iff->ifFalse->dynCast<GetLocal>()) return get; + } + } + return nullptr; + } + + // main entry point + + void doWalkFunction(Function* func) { + numLocals = func->getNumLocals(); + copies.resize(numLocals * numLocals); + std::fill(copies.begin(), copies.end(), 0); + totalCopies.resize(numLocals); + std::fill(totalCopies.begin(), totalCopies.end(), 0); + // create the CFG by walking the IR + CFGWalker<SubType, VisitorType, Liveness>::doWalkFunction(func); + // ignore links to dead blocks, so they don't confuse us and we can see their stores are all ineffective + liveBlocks = CFGWalker<SubType, VisitorType, Liveness>::findLiveBlocks(); + CFGWalker<SubType, VisitorType, Liveness>::unlinkDeadBlocks(liveBlocks); + // flow liveness across blocks + flowLiveness(); + } + + void flowLiveness() { + // keep working while stuff is flowing + std::unordered_set<BasicBlock*> queue; + for (auto& curr : CFGWalker<SubType, VisitorType, Liveness>::basicBlocks) { + if (liveBlocks.count(curr.get()) == 0) continue; // ignore dead blocks + queue.insert(curr.get()); + // do the first scan through the block, starting with nothing live at the end, and updating the liveness at the start + scanLivenessThroughActions(curr->contents.actions, curr->contents.start); + } + // at every point in time, we assume we already noted interferences between things already known alive at the end, and scanned back through the block using that + while (queue.size() > 0) { + auto iter = queue.begin(); + auto* curr = *iter; + queue.erase(iter); + LocalSet live; + if (!mergeStartsAndCheckChange(curr->out, curr->contents.end, live)) continue; + assert(curr->contents.end.size() < live.size()); + curr->contents.end = live; + scanLivenessThroughActions(curr->contents.actions, live); + // liveness is now calculated at the start. if something + // changed, all predecessor blocks need recomputation + if (curr->contents.start == live) continue; + assert(curr->contents.start.size() < live.size()); + curr->contents.start = live; + for (auto* in : curr->in) { + queue.insert(in); + } + } + } + + // merge starts of a list of blocks. return + // whether anything changed vs an old state (which indicates further processing is necessary). + bool mergeStartsAndCheckChange(std::vector<BasicBlock*>& blocks, LocalSet& old, LocalSet& ret) { + if (blocks.size() == 0) return false; + ret = blocks[0]->contents.start; + if (blocks.size() > 1) { + // more than one, so we must merge + for (Index i = 1; i < blocks.size(); i++) { + ret = ret.merge(blocks[i]->contents.start); + } + } + return old != ret; + } + + void scanLivenessThroughActions(std::vector<Action>& actions, LocalSet& live) { + // move towards the front + for (int i = int(actions.size()) - 1; i >= 0; i--) { + auto& action = actions[i]; + if (action.isGet()) { + live.insert(action.index); + } else { + live.erase(action.index); + } + } + } + + void addCopy(Index i, Index j) { + auto k = std::min(i, j) * numLocals + std::max(i, j); + copies[k] = std::min(copies[k], uint8_t(254)) + 1; + totalCopies[i]++; + totalCopies[j]++; + } + + uint8_t getCopies(Index i, Index j) { + return copies[std::min(i, j) * numLocals + std::max(i, j)]; + } +}; + +} // namespace wasm + +#endif // liveness_traversal_h diff --git a/src/ir/find_all.h b/src/ir/find_all.h index 83c751666..44dc7a675 100644 --- a/src/ir/find_all.h +++ b/src/ir/find_all.h @@ -42,6 +42,30 @@ struct FindAll { } }; +// Find all pointers to instances of a certain node type + +struct PointerFinder : public PostWalker<PointerFinder, UnifiedExpressionVisitor<PointerFinder>> { + Expression::Id id; + std::vector<Expression**>* list; + void visitExpression(Expression* curr) { + if (curr->_id == id) { + (*list).push_back(getCurrentPointer()); + } + } +}; + +template<typename T> +struct FindAllPointers { + std::vector<Expression**> list; + + FindAllPointers(Expression* ast) { + PointerFinder finder; + finder.id = T()._id; + finder.list = &list; + finder.walk(ast); + } +}; + } // namespace wasm #endif // wasm_ir_find_all_h diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index e9a157499..1da9b57f7 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -38,6 +38,7 @@ SET(passes_SOURCES TrapMode.cpp SafeHeap.cpp SimplifyLocals.cpp + SpillPointers.cpp SSAify.cpp Untee.cpp Vacuum.cpp diff --git a/src/passes/CoalesceLocals.cpp b/src/passes/CoalesceLocals.cpp index 36c963b08..ae9d9246e 100644 --- a/src/passes/CoalesceLocals.cpp +++ b/src/passes/CoalesceLocals.cpp @@ -29,7 +29,7 @@ #include "wasm.h" #include "pass.h" #include "ir/utils.h" -#include "cfg/cfg-traversal.h" +#include "cfg/liveness-traversal.h" #include "wasm-builder.h" #include "support/learning.h" #include "support/permutations.h" @@ -39,188 +39,21 @@ namespace wasm { -// A set of locals. This is optimized for comparisons, -// mergings, and iteration on elements, assuming that there -// may be a great many potential elements but actual sets -// may be fairly small. Specifically, we use a sorted -// vector. -struct LocalSet : std::vector<Index> { - LocalSet() {} - - LocalSet merge(const LocalSet& other) const { - LocalSet ret; - ret.resize(size() + other.size()); - Index i = 0, j = 0, t = 0; - while (i < size() && j < other.size()) { - auto left = (*this)[i]; - auto right = other[j]; - if (left < right) { - ret[t++] = left; - i++; - } else if (left > right) { - ret[t++] = right; - j++; - } else { - ret[t++] = left; - i++; - j++; - } - } - while (i < size()) { - ret[t++] = (*this)[i]; - i++; - } - while (j < other.size()) { - ret[t++] = other[j]; - j++; - } - ret.resize(t); - return ret; - } - - void insert(Index x) { - auto it = std::lower_bound(begin(), end(), x); - if (it == end()) push_back(x); - else if (*it > x) { - Index i = it - begin(); - resize(size() + 1); - std::move_backward(begin() + i, begin() + size() - 1, end()); - (*this)[i] = x; - } - } - - bool erase(Index x) { - auto it = std::lower_bound(begin(), end(), x); - if (it != end() && *it == x) { - std::move(it + 1, end(), it); - resize(size() - 1); - return true; - } - return false; - } - - bool has(Index x) { - auto it = std::lower_bound(begin(), end(), x); - return it != end() && *it == x; - } - - void verify() const { - for (Index i = 1; i < size(); i++) { - assert((*this)[i - 1] < (*this)[i]); - } - } - - void dump(const char* str = nullptr) const { - std::cout << "LocalSet " << (str ? str : "") << ": "; - for (auto x : *this) std::cout << x << " "; - std::cout << '\n'; - } -}; - -// a liveness-relevant action -struct Action { - enum What { - Get, Set - }; - What what; - Index index; // the local index read or written - Expression** origin; // the origin - bool effective; // whether a store is actually effective, i.e., may be read - - Action(What what, Index index, Expression** origin) : what(what), index(index), origin(origin), effective(false) {} - - bool isGet() { return what == Get; } - bool isSet() { return what == Set; } -}; - -// information about liveness in a basic block -struct Liveness { - LocalSet start, end; // live locals at the start and end - std::vector<Action> actions; // actions occurring in this block - - void dump(Function* func) { - if (actions.empty()) return; - std::cout << " actions:\n"; - for (auto& action : actions) { - std::cout << " " << (action.isGet() ? "get" : "set") << " " << func->getLocalName(action.index) << "\n"; - } - } -}; - -struct CoalesceLocals : public WalkerPass<CFGWalker<CoalesceLocals, Visitor<CoalesceLocals>, Liveness>> { +struct CoalesceLocals : public WalkerPass<LivenessWalker<CoalesceLocals, Visitor<CoalesceLocals>>> { bool isFunctionParallel() override { return true; } Pass* create() override { return new CoalesceLocals; } - Index numLocals; - - // cfg traversal work - - static void doVisitGetLocal(CoalesceLocals* self, Expression** currp) { - auto* curr = (*currp)->cast<GetLocal>(); - // if in unreachable code, ignore - if (!self->currBasicBlock) { - *currp = Builder(*self->getModule()).replaceWithIdenticalType(curr); - return; - } - self->currBasicBlock->contents.actions.emplace_back(Action::Get, curr->index, currp); - } - - static void doVisitSetLocal(CoalesceLocals* self, Expression** currp) { - auto* curr = (*currp)->cast<SetLocal>(); - // if in unreachable code, we don't need the tee (but might need the value, if it has side effects) - if (!self->currBasicBlock) { - if (curr->isTee()) { - *currp = curr->value; - } else { - *currp = Builder(*self->getModule()).makeDrop(curr->value); - } - return; - } - self->currBasicBlock->contents.actions.emplace_back(Action::Set, curr->index, currp); - // if this is a copy, note it - if (auto* get = self->getCopy(curr)) { - // add 2 units, so that backedge prioritization can decide ties, but not much more - self->addCopy(curr->index, get->index); - self->addCopy(curr->index, get->index); - } - } - - // A simple copy is a set of a get. A more interesting copy - // is a set of an if with a value, where one side a get. - // That can happen when we create an if value in simplify-locals. TODO: recurse into - // nested ifs, and block return values? Those cases are trickier, need to - // count to see if worth it. - // TODO: an if can have two copies - GetLocal* getCopy(SetLocal* set) { - if (auto* get = set->value->dynCast<GetLocal>()) return get; - if (auto* iff = set->value->dynCast<If>()) { - if (auto* get = iff->ifTrue->dynCast<GetLocal>()) return get; - if (iff->ifFalse) { - if (auto* get = iff->ifFalse->dynCast<GetLocal>()) return get; - } - } - return nullptr; - } - // main entry point void doWalkFunction(Function* func); void increaseBackEdgePriorities(); - void flowLiveness(); - void calculateInterferences(); void calculateInterferences(const LocalSet& locals); - // merge starts of a list of blocks, adding new interferences as necessary. return - // whether anything changed vs an old state (which indicates further processing is necessary). - bool mergeStartsAndCheckChange(std::vector<BasicBlock*>& blocks, LocalSet& old, LocalSet& ret); - - void scanLivenessThroughActions(std::vector<Action>& actions, LocalSet& live); - void pickIndicesFromOrder(std::vector<Index>& order, std::vector<Index>& indices); void pickIndicesFromOrder(std::vector<Index>& order, std::vector<Index>& indices, Index& removedCopies); @@ -231,7 +64,6 @@ struct CoalesceLocals : public WalkerPass<CFGWalker<CoalesceLocals, Visitor<Coal // interference state std::vector<bool> interferences; // canonicalized - accesses should check (low, high) - std::unordered_set<BasicBlock*> liveBlocks; void interfere(Index i, Index j) { if (i == j) return; @@ -246,50 +78,12 @@ struct CoalesceLocals : public WalkerPass<CFGWalker<CoalesceLocals, Visitor<Coal bool interferes(Index i, Index j) { return interferences[std::min(i, j) * numLocals + std::max(i, j)]; } - - // copying state - - std::vector<uint8_t> copies; // canonicalized - accesses should check (low, high) TODO: use a map for high N, as this tends to be sparse? or don't look at copies at all for big N? - std::vector<Index> totalCopies; // total # of copies for each local, with all others - - void addCopy(Index i, Index j) { - auto k = std::min(i, j) * numLocals + std::max(i, j); - copies[k] = std::min(copies[k], uint8_t(254)) + 1; - totalCopies[i]++; - totalCopies[j]++; - } - - uint8_t getCopies(Index i, Index j) { - return copies[std::min(i, j) * numLocals + std::max(i, j)]; - } }; void CoalesceLocals::doWalkFunction(Function* func) { - numLocals = func->getNumLocals(); - copies.resize(numLocals * numLocals); - std::fill(copies.begin(), copies.end(), 0); - totalCopies.resize(numLocals); - std::fill(totalCopies.begin(), totalCopies.end(), 0); - // collect initial liveness info super::doWalkFunction(func); - // ignore links to dead blocks, so they don't confuse us and we can see their stores are all ineffective - liveBlocks = findLiveBlocks(); - unlinkDeadBlocks(liveBlocks); - // increase the cost of costly backedges + // prioritize back edges increaseBackEdgePriorities(); -#ifdef CFG_DEBUG - dumpCFG("the cfg"); -#endif - // flow liveness across blocks -#ifdef CFG_PROFILE - static Timer timer("flow"); - timer.start(); -#endif - flowLiveness(); -#ifdef CFG_PROFILE - timer.stop(); - timer.dump(); -#endif // use liveness to find interference calculateInterferences(); // pick new indices @@ -321,82 +115,9 @@ void CoalesceLocals::increaseBackEdgePriorities() { } } -void CoalesceLocals::flowLiveness() { +void CoalesceLocals::calculateInterferences() { interferences.resize(numLocals * numLocals); std::fill(interferences.begin(), interferences.end(), 0); - // keep working while stuff is flowing - std::unordered_set<BasicBlock*> queue; - for (auto& curr : basicBlocks) { - if (liveBlocks.count(curr.get()) == 0) continue; // ignore dead blocks - queue.insert(curr.get()); - // do the first scan through the block, starting with nothing live at the end, and updating the liveness at the start - scanLivenessThroughActions(curr->contents.actions, curr->contents.start); - } - // at every point in time, we assume we already noted interferences between things already known alive at the end, and scanned back through the block using that - while (queue.size() > 0) { - auto iter = queue.begin(); - auto* curr = *iter; - queue.erase(iter); - LocalSet live; - if (!mergeStartsAndCheckChange(curr->out, curr->contents.end, live)) continue; -#ifdef CFG_DEBUG - std::cout << "change noticed at end of " << debugIds[curr] << " from " << curr->contents.end.size() << " to " << live.size() << " (out of " << numLocals << ")\n"; -#endif - assert(curr->contents.end.size() < live.size()); - curr->contents.end = live; - scanLivenessThroughActions(curr->contents.actions, live); - // liveness is now calculated at the start. if something - // changed, all predecessor blocks need recomputation - if (curr->contents.start == live) continue; -#ifdef CFG_DEBUG - std::cout << "change noticed at start of " << debugIds[curr] << " from " << curr->contents.start.size() << " to " << live.size() << ", more work to do\n"; -#endif - assert(curr->contents.start.size() < live.size()); - curr->contents.start = live; - for (auto* in : curr->in) { - queue.insert(in); - } - } -#ifdef CFG_DEBUG - std::hash<std::vector<bool>> hasher; - std::cout << getFunction()->name << ": interference hash: " << hasher(*(std::vector<bool>*)&interferences) << "\n"; - for (Index i = 0; i < numLocals; i++) { - std::cout << "int for " << getFunction()->getLocalName(i) << " [" << i << "]: "; - for (Index j = 0; j < numLocals; j++) { - if (interferes(i, j)) std::cout << getFunction()->getLocalName(j) << " "; - } - std::cout << "\n"; - } -#endif -} - -// merge starts of a list of blocks. return -// whether anything changed vs an old state (which indicates further processing is necessary). -bool CoalesceLocals::mergeStartsAndCheckChange(std::vector<BasicBlock*>& blocks, LocalSet& old, LocalSet& ret) { - if (blocks.size() == 0) return false; - ret = blocks[0]->contents.start; - if (blocks.size() > 1) { - // more than one, so we must merge - for (Index i = 1; i < blocks.size(); i++) { - ret = ret.merge(blocks[i]->contents.start); - } - } - return old != ret; -} - -void CoalesceLocals::scanLivenessThroughActions(std::vector<Action>& actions, LocalSet& live) { - // move towards the front - for (int i = int(actions.size()) - 1; i >= 0; i--) { - auto& action = actions[i]; - if (action.isGet()) { - live.insert(action.index); - } else { - live.erase(action.index); - } - } -} - -void CoalesceLocals::calculateInterferences() { for (auto& curr : basicBlocks) { if (liveBlocks.count(curr.get()) == 0) continue; // ignore dead blocks // everything coming in might interfere, as it might come from a different block diff --git a/src/passes/SpillPointers.cpp b/src/passes/SpillPointers.cpp new file mode 100644 index 000000000..492a90501 --- /dev/null +++ b/src/passes/SpillPointers.cpp @@ -0,0 +1,198 @@ +/* + * 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 "wasm.h" +#include "pass.h" +#include "cfg/liveness-traversal.h" +#include "wasm-builder.h" +#include "abi/abi.h" +#include "abi/stack.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); + actualPointers[pointer] = pointer; // starts out as correct, may change later + } + + void visitCall(Call* curr) { + visitSpillable(curr); + } + void visitCallImport(CallImport* 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; + + void spillPointers() { + // we only care about possible pointers + auto* func = getFunction(); + PointerMap pointerMap; + for (Index i = 0; i < func->getNumLocals(); i++) { + if (func->getLocalType(i) == ABI::PointerType) { + auto offset = pointerMap.size() * getWasmTypeSize(ABI::PointerType); + 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 + LocalSet 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, ABI::PointerType); + spilled = true; + } + auto* pointer = actualPointers[action.origin]; // the origin was seen at walk, but the thing may have moved + spillPointersAroundCall(pointer, toSpill, spillLocal, pointerMap, func, getModule()); + } + } else { + WASM_UNREACHABLE(); + } + } + } + if (spilled) { + // get the stack space, and set the local to it + ABI::getStackSpace(spillLocal, func, getWasmTypeSize(ABI::PointerType) * 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 == 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.makeSetLocal(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.makeGetLocal(temp, operand->type); + }; + if (call->is<Call>()) { + for (auto*& operand : call->cast<Call>()->operands) { + handleOperand(operand); + } + } else if (call->is<CallImport>()) { + for (auto*& operand : call->cast<CallImport>()->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(); + } + // add the spills + for (auto index : toSpill) { + block->list.push_back(builder.makeStore( + getWasmTypeSize(ABI::PointerType), + pointerMap[index], + getWasmTypeSize(ABI::PointerType), + builder.makeGetLocal(spillLocal, ABI::PointerType), + builder.makeGetLocal(index, ABI::PointerType), + ABI::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 89467899c..339b1d1da 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -109,6 +109,7 @@ void PassRegistry::registerPasses() { registerPass("simplify-locals-notee", "miscellaneous locals-related optimizations", createSimplifyLocalsNoTeePass); registerPass("simplify-locals-nostructure", "miscellaneous locals-related optimizations", createSimplifyLocalsNoStructurePass); registerPass("simplify-locals-notee-nostructure", "miscellaneous locals-related optimizations", createSimplifyLocalsNoTeeNoStructurePass); + registerPass("spill-pointers", "spill pointers to the C stack (useful for Boehm-style GC)", createSpillPointersPass); registerPass("ssa", "ssa-ify variables so that they have a single assignment", createSSAifyPass); registerPass("trap-mode-clamp", "replace trapping operations with clamping semantics", createTrapModeClamp); registerPass("trap-mode-js", "replace trapping operations with js semantics", createTrapModeJS); diff --git a/src/passes/passes.h b/src/passes/passes.h index 5cefdea17..53198bf56 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -67,6 +67,7 @@ Pass* createSimplifyLocalsPass(); Pass* createSimplifyLocalsNoTeePass(); Pass* createSimplifyLocalsNoStructurePass(); Pass* createSimplifyLocalsNoTeeNoStructurePass(); +Pass* createSpillPointersPass(); Pass* createSSAifyPass(); Pass* createTrapModeClamp(); Pass* createTrapModeJS(); diff --git a/src/support/sorted_vector.h b/src/support/sorted_vector.h new file mode 100644 index 000000000..607a30578 --- /dev/null +++ b/src/support/sorted_vector.h @@ -0,0 +1,103 @@ +/* + * 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. + */ + +// +// Command line helpers. +// + +#ifndef wasm_support_sorted_vector_h +#define wasm_support_sorted_vector_h + +#include <vector> + +namespace wasm { + +struct SortedVector : std::vector<Index> { + SortedVector() {} + + SortedVector merge(const SortedVector& other) const { + SortedVector ret; + ret.resize(size() + other.size()); + Index i = 0, j = 0, t = 0; + while (i < size() && j < other.size()) { + auto left = (*this)[i]; + auto right = other[j]; + if (left < right) { + ret[t++] = left; + i++; + } else if (left > right) { + ret[t++] = right; + j++; + } else { + ret[t++] = left; + i++; + j++; + } + } + while (i < size()) { + ret[t++] = (*this)[i]; + i++; + } + while (j < other.size()) { + ret[t++] = other[j]; + j++; + } + ret.resize(t); + return ret; + } + + void insert(Index x) { + auto it = std::lower_bound(begin(), end(), x); + if (it == end()) push_back(x); + else if (*it > x) { + Index i = it - begin(); + resize(size() + 1); + std::move_backward(begin() + i, begin() + size() - 1, end()); + (*this)[i] = x; + } + } + + bool erase(Index x) { + auto it = std::lower_bound(begin(), end(), x); + if (it != end() && *it == x) { + std::move(it + 1, end(), it); + resize(size() - 1); + return true; + } + return false; + } + + bool has(Index x) { + auto it = std::lower_bound(begin(), end(), x); + return it != end() && *it == x; + } + + void verify() const { + for (Index i = 1; i < size(); i++) { + assert((*this)[i - 1] < (*this)[i]); + } + } + + void dump(const char* str = nullptr) const { + std::cout << "SortedVector " << (str ? str : "") << ": "; + for (auto x : *this) std::cout << x << " "; + std::cout << '\n'; + } +}; + +} // namespace wasm + +#endif // wasm_support_sorted_vector_h diff --git a/test/passes/spill-pointers.txt b/test/passes/spill-pointers.txt new file mode 100644 index 000000000..30febbec6 --- /dev/null +++ b/test/passes/spill-pointers.txt @@ -0,0 +1,644 @@ +(module + (type $ii (func (param i32 i32))) + (type $FUNCSIG$vi (func (param i32))) + (type $2 (func)) + (type $3 (func (result i32))) + (type $4 (func (param i32) (result i32))) + (type $5 (func (param f64))) + (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32)) + (import "env" "segfault" (func $segfault (param i32))) + (global $stack_ptr (mut i32) (get_global $STACKTOP$asm2wasm$import)) + (table 1 1 anyfunc) + (memory $0 10) + (func $nothing (; 1 ;) (type $2) + (nop) + ) + (func $not-alive (; 2 ;) (type $2) + (local $x i32) + (set_local $x + (i32.const 1) + ) + (call $nothing) + ) + (func $spill (; 3 ;) (type $2) + (local $x i32) + (local $1 i32) + (set_local $1 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $1) + (i32.const 16) + ) + ) + (block + (block + (i32.store + (get_local $1) + (get_local $x) + ) + (call $nothing) + ) + (drop + (get_local $x) + ) + ) + (set_global $stack_ptr + (get_local $1) + ) + ) + (func $ignore-non-pointers (; 4 ;) (type $2) + (local $x i32) + (local $y i64) + (local $z f32) + (local $w f64) + (local $4 i32) + (set_local $4 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $4) + (i32.const 16) + ) + ) + (block + (set_local $x + (i32.const 1) + ) + (set_local $y + (i64.const 1) + ) + (set_local $z + (f32.const 1) + ) + (set_local $w + (f64.const 1) + ) + (block + (i32.store + (get_local $4) + (get_local $x) + ) + (call $nothing) + ) + (drop + (get_local $x) + ) + (drop + (get_local $y) + ) + (drop + (get_local $z) + ) + (drop + (get_local $w) + ) + ) + (set_global $stack_ptr + (get_local $4) + ) + ) + (func $spill4 (; 5 ;) (type $2) + (local $x i32) + (local $y i32) + (local $z i32) + (local $w i32) + (local $4 i32) + (set_local $4 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $4) + (i32.const 16) + ) + ) + (block + (set_local $x + (i32.const 1) + ) + (set_local $y + (i32.const 1) + ) + (set_local $z + (i32.const 1) + ) + (set_local $w + (i32.const 1) + ) + (block + (i32.store + (get_local $4) + (get_local $x) + ) + (i32.store offset=4 + (get_local $4) + (get_local $y) + ) + (i32.store offset=8 + (get_local $4) + (get_local $z) + ) + (i32.store offset=12 + (get_local $4) + (get_local $w) + ) + (call $nothing) + ) + (drop + (get_local $x) + ) + (drop + (get_local $y) + ) + (drop + (get_local $z) + ) + (drop + (get_local $w) + ) + ) + (set_global $stack_ptr + (get_local $4) + ) + ) + (func $spill5 (; 6 ;) (type $2) + (local $x i32) + (local $y i32) + (local $z i32) + (local $w i32) + (local $a i32) + (local $5 i32) + (set_local $5 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $5) + (i32.const 32) + ) + ) + (block + (set_local $x + (i32.const 1) + ) + (set_local $y + (i32.const 1) + ) + (set_local $z + (i32.const 1) + ) + (set_local $w + (i32.const 1) + ) + (set_local $a + (i32.const 1) + ) + (block + (i32.store + (get_local $5) + (get_local $x) + ) + (i32.store offset=4 + (get_local $5) + (get_local $y) + ) + (i32.store offset=8 + (get_local $5) + (get_local $z) + ) + (i32.store offset=12 + (get_local $5) + (get_local $w) + ) + (i32.store offset=16 + (get_local $5) + (get_local $a) + ) + (call $nothing) + ) + (drop + (get_local $x) + ) + (drop + (get_local $y) + ) + (drop + (get_local $z) + ) + (drop + (get_local $w) + ) + (drop + (get_local $a) + ) + ) + (set_global $stack_ptr + (get_local $5) + ) + ) + (func $some-alive (; 7 ;) (type $2) + (local $x i32) + (local $y i32) + (local $2 i32) + (set_local $2 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $2) + (i32.const 16) + ) + ) + (block + (block + (i32.store + (get_local $2) + (get_local $x) + ) + (call $nothing) + ) + (drop + (get_local $x) + ) + ) + (set_global $stack_ptr + (get_local $2) + ) + ) + (func $spill-args (; 8 ;) (type $ii) (param $p i32) (param $q i32) + (local $x i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (set_local $3 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $3) + (i32.const 16) + ) + ) + (block + (block + (set_local $4 + (i32.const 1) + ) + (set_local $5 + (i32.const 2) + ) + (i32.store offset=8 + (get_local $3) + (get_local $x) + ) + (call $spill-args + (get_local $4) + (get_local $5) + ) + ) + (drop + (get_local $x) + ) + ) + (set_global $stack_ptr + (get_local $3) + ) + ) + (func $spill-ret (; 9 ;) (type $3) (result i32) + (local $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (set_local $1 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $1) + (i32.const 16) + ) + ) + (set_local $4 + (block (result i32) + (block + (i32.store + (get_local $1) + (get_local $x) + ) + (call $nothing) + ) + (drop + (get_local $x) + ) + (if + (i32.const 1) + (block + (set_local $2 + (i32.const 2) + ) + (set_global $stack_ptr + (get_local $1) + ) + (return + (get_local $2) + ) + ) + (block + (set_local $3 + (i32.const 3) + ) + (set_global $stack_ptr + (get_local $1) + ) + (return + (get_local $3) + ) + ) + ) + (i32.const 4) + ) + ) + (set_global $stack_ptr + (get_local $1) + ) + (get_local $4) + ) + (func $spill-unreachable (; 10 ;) (type $3) (result i32) + (local $x i32) + (local $1 i32) + (local $2 i32) + (set_local $1 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $1) + (i32.const 16) + ) + ) + (set_local $2 + (block (result i32) + (block + (i32.store + (get_local $1) + (get_local $x) + ) + (call $nothing) + ) + (drop + (get_local $x) + ) + (unreachable) + ) + ) + (set_global $stack_ptr + (get_local $1) + ) + (get_local $2) + ) + (func $spill-call-call0 (; 11 ;) (type $4) (param $p i32) (result i32) + (unreachable) + ) + (func $spill-call-call1 (; 12 ;) (type $4) (param $p i32) (result i32) + (local $x i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (set_local $2 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $2) + (i32.const 16) + ) + ) + (set_local $5 + (block (result i32) + (drop + (block (result i32) + (set_local $3 + (block (result i32) + (set_local $4 + (i32.const 1) + ) + (i32.store offset=4 + (get_local $2) + (get_local $x) + ) + (call $spill-call-call1 + (get_local $4) + ) + ) + ) + (i32.store offset=4 + (get_local $2) + (get_local $x) + ) + (call $spill-call-call0 + (get_local $3) + ) + ) + ) + (get_local $x) + ) + ) + (set_global $stack_ptr + (get_local $2) + ) + (get_local $5) + ) + (func $spill-call-ret (; 13 ;) (type $4) (param $p i32) (result i32) + (local $x i32) + (drop + (call $spill-call-call0 + (return + (i32.const 1) + ) + ) + ) + (i32.const 0) + ) + (func $spill-ret-call (; 14 ;) (type $4) (param $p i32) (result i32) + (local $x i32) + (drop + (return + (call $spill-call-call0 + (i32.const 1) + ) + ) + ) + (i32.const 0) + ) + (func $spill-ret-ret (; 15 ;) (type $3) (result i32) + (local $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (set_local $1 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $1) + (i32.const 16) + ) + ) + (set_local $3 + (block (result i32) + (block + (i32.store + (get_local $1) + (get_local $x) + ) + (call $nothing) + ) + (drop + (get_local $x) + ) + (drop + (block + (set_global $stack_ptr + (get_local $1) + ) + (return + (block + (set_local $2 + (i32.const 1) + ) + (set_global $stack_ptr + (get_local $1) + ) + (return + (get_local $2) + ) + ) + ) + ) + ) + (i32.const 0) + ) + ) + (set_global $stack_ptr + (get_local $1) + ) + (get_local $3) + ) + (func $spill-call-othertype (; 16 ;) (type $5) (param $y f64) + (local $x i32) + (local $2 i32) + (local $3 f64) + (set_local $2 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $2) + (i32.const 16) + ) + ) + (block + (block + (set_local $3 + (f64.const 1) + ) + (i32.store + (get_local $2) + (get_local $x) + ) + (call $spill-call-othertype + (get_local $3) + ) + ) + (drop + (get_local $x) + ) + ) + (set_global $stack_ptr + (get_local $2) + ) + ) + (func $spill-call_indirect (; 17 ;) (type $2) + (local $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (set_local $1 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $1) + (i32.const 16) + ) + ) + (block + (block + (set_local $2 + (i32.const 123) + ) + (set_local $3 + (i32.const 456) + ) + (set_local $4 + (i32.const 789) + ) + (i32.store + (get_local $1) + (get_local $x) + ) + (call_indirect (type $ii) + (get_local $2) + (get_local $3) + (get_local $4) + ) + ) + (drop + (get_local $x) + ) + ) + (set_global $stack_ptr + (get_local $1) + ) + ) + (func $spill-call_import (; 18 ;) (type $2) + (local $x i32) + (local $1 i32) + (local $2 i32) + (set_local $1 + (get_global $stack_ptr) + ) + (set_global $stack_ptr + (i32.add + (get_local $1) + (i32.const 16) + ) + ) + (block + (block + (set_local $2 + (i32.const 200) + ) + (i32.store + (get_local $1) + (get_local $x) + ) + (call $segfault + (get_local $2) + ) + ) + (drop + (get_local $x) + ) + ) + (set_global $stack_ptr + (get_local $1) + ) + ) +) diff --git a/test/passes/spill-pointers.wast b/test/passes/spill-pointers.wast new file mode 100644 index 000000000..c9bccd3b5 --- /dev/null +++ b/test/passes/spill-pointers.wast @@ -0,0 +1,168 @@ +(module + (memory 10) + (type $ii (func (param i32 i32))) + (table 1 1 anyfunc) + (elem (i32.const 0)) + (import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32)) + (import "env" "segfault" (func $segfault (param i32))) + (global $stack_ptr (mut i32) (get_global $STACKTOP$asm2wasm$import)) + + (func $nothing + ) + (func $not-alive + (local $x i32) + (set_local $x (i32.const 1)) + (call $nothing) + ) + (func $spill + (local $x i32) + (call $nothing) + (drop (get_local $x)) + ) + (func $ignore-non-pointers + (local $x i32) + (local $y i64) + (local $z f32) + (local $w f64) + (set_local $x (i32.const 1)) + (set_local $y (i64.const 1)) + (set_local $z (f32.const 1)) + (set_local $w (f64.const 1)) + (call $nothing) + (drop (get_local $x)) + (drop (get_local $y)) + (drop (get_local $z)) + (drop (get_local $w)) + ) + (func $spill4 + (local $x i32) + (local $y i32) + (local $z i32) + (local $w i32) + (set_local $x (i32.const 1)) + (set_local $y (i32.const 1)) + (set_local $z (i32.const 1)) + (set_local $w (i32.const 1)) + (call $nothing) + (drop (get_local $x)) + (drop (get_local $y)) + (drop (get_local $z)) + (drop (get_local $w)) + ) + (func $spill5 + (local $x i32) + (local $y i32) + (local $z i32) + (local $w i32) + (local $a i32) + (set_local $x (i32.const 1)) + (set_local $y (i32.const 1)) + (set_local $z (i32.const 1)) + (set_local $w (i32.const 1)) + (set_local $a (i32.const 1)) + (call $nothing) + (drop (get_local $x)) + (drop (get_local $y)) + (drop (get_local $z)) + (drop (get_local $w)) + (drop (get_local $a)) + ) + (func $some-alive + (local $x i32) + (local $y i32) + (call $nothing) + (drop (get_local $x)) + ) + (func $spill-args (param $p i32) (param $q i32) + (local $x i32) + (call $spill-args (i32.const 1) (i32.const 2)) + (drop (get_local $x)) + ) + (func $spill-ret (result i32) + (local $x i32) + (call $nothing) + (drop (get_local $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 (get_local $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) + ) + ) + ) + (get_local $x) + ) + (func $spill-call-ret (param $p i32) (result i32) + (local $x i32) + (drop + (call $spill-call-call0 + (return + (i32.const 1) + ) + ) + ) + (get_local $x) + ) + (func $spill-ret-call (param $p i32) (result i32) + (local $x i32) + (drop + (return + (call $spill-call-call0 + (i32.const 1) + ) + ) + ) + (get_local $x) + ) + (func $spill-ret-ret (result i32) + (local $x i32) + (call $nothing) + (drop (get_local $x)) + (drop + (return + (return + (i32.const 1) + ) + ) + ) + (get_local $x) + ) + (func $spill-call-othertype (param $y f64) + (local $x i32) + (call $spill-call-othertype (f64.const 1)) + (drop (get_local $x)) + ) + (func $spill-call_indirect + (local $x i32) + (call_indirect (type $ii) + (i32.const 123) + (i32.const 456) + (i32.const 789) + ) + (drop (get_local $x)) + ) + (func $spill-call_import + (local $x i32) + (call_import $segfault + (i32.const 200) + ) + (drop (get_local $x)) + ) +) + |