Skip to content

Restore and fix SpillPointers pass #4570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions src/abi/stack.h
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions src/passes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ set(passes_SOURCES
SimplifyGlobals.cpp
SimplifyLocals.cpp
Souperify.cpp
SpillPointers.cpp
StackCheck.cpp
SSAify.cpp
Untee.cpp
Expand Down
206 changes: 206 additions & 0 deletions src/passes/SpillPointers.cpp
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions src/passes/pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/passes/passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ Pass* createStripProducersPass();
Pass* createStripTargetFeaturesPass();
Pass* createSouperifyPass();
Pass* createSouperifySingleUsePass();
Pass* createSpillPointersPass();
Pass* createStubUnsupportedJSOpsPass();
Pass* createSSAifyPass();
Pass* createSSAifyNoMergePass();
Expand Down
3 changes: 3 additions & 0 deletions test/lit/help/wasm-opt.test
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions test/lit/help/wasm2js.test
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading