/* * 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>> { bool isFunctionParallel() override { return true; } // Adds writes to memory. bool addsEffects() override { return true; } std::unique_ptr create() override { return std::make_unique(); } // 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 actualPointers; // note calls in basic blocks template 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; Type pointerType; void spillPointers() { pointerType = getModule()->memories[0]->addressType; // 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 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& 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()) { for (auto*& operand : call->cast()->operands) { handleOperand(operand); } } else if (call->is()) { for (auto*& operand : call->cast()->operands) { handleOperand(operand); } handleOperand(call->cast()->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