blob: db7fca85abf429d902e2d3c538cc5c4ca3ab09d4 [file] [log] [blame] [edit]
/*
* 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; }
// Adds writes to memory.
bool addsEffects() override { return true; }
std::unique_ptr<Pass> create() override {
return std::make_unique<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
using PointerMap = std::unordered_map<Index, Index>;
Type pointerType;
void spillPointers() {
pointerType = getModule()->memories[0]->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,
getModule()->memories[0]->name));
}
// add the (modified) call
block->list.push_back(call);
block->finalize();
*origin = block;
}
};
Pass* createSpillPointersPass() { return new SpillPointers(); }
} // namespace wasm