summaryrefslogtreecommitdiff
path: root/src/passes/SpillPointers.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/passes/SpillPointers.cpp')
-rw-r--r--src/passes/SpillPointers.cpp198
1 files changed, 198 insertions, 0 deletions
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