Skip to content

Commit ec88ea6

Browse files
authored
Restore and fix SpillPointers pass (#4570)
We have some possible use cases for this pass, and so are restoring it. This reverts the removal in #3261, fixes compile errors in internal API changes since then, and flips the direction of the stack for the wasm backend.
1 parent b6040f5 commit ec88ea6

File tree

9 files changed

+1963
-0
lines changed

9 files changed

+1963
-0
lines changed

src/abi/stack.h

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2017 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef wasm_abi_stack_h
18+
#define wasm_abi_stack_h
19+
20+
#include "asmjs/shared-constants.h"
21+
#include "ir/find_all.h"
22+
#include "ir/global-utils.h"
23+
#include "shared-constants.h"
24+
#include "wasm-builder.h"
25+
#include "wasm-emscripten.h"
26+
#include "wasm.h"
27+
28+
namespace wasm {
29+
30+
namespace ABI {
31+
32+
enum { StackAlign = 16 };
33+
34+
inline Index stackAlign(Index size) {
35+
return (size + StackAlign - 1) & -StackAlign;
36+
}
37+
38+
// Allocate some space on the stack, and assign it to a local.
39+
// The local will have the same constant value in all the function, so you can
40+
// just local.get it anywhere there.
41+
//
42+
// FIXME: This function assumes that the stack grows upward, per the convention
43+
// used by fastcomp. The stack grows downward when using the WASM backend.
44+
45+
inline void
46+
getStackSpace(Index local, Function* func, Index size, Module& wasm) {
47+
auto* stackPointer = getStackPointerGlobal(wasm);
48+
if (!stackPointer) {
49+
Fatal() << "getStackSpace: failed to find the stack pointer";
50+
}
51+
// align the size
52+
size = stackAlign(size);
53+
auto pointerType = wasm.memory.indexType;
54+
// TODO: find existing stack usage, and add on top of that - carefully
55+
Builder builder(wasm);
56+
auto* block = builder.makeBlock();
57+
block->list.push_back(builder.makeLocalSet(
58+
local, builder.makeGlobalGet(stackPointer->name, pointerType)));
59+
// TODO: add stack max check
60+
Expression* added;
61+
if (pointerType == Type::i32) {
62+
// The stack goes downward in the LLVM wasm backend.
63+
added = builder.makeBinary(SubInt32,
64+
builder.makeLocalGet(local, pointerType),
65+
builder.makeConst(int32_t(size)));
66+
} else {
67+
WASM_UNREACHABLE("unhandled pointerType");
68+
}
69+
block->list.push_back(builder.makeGlobalSet(stackPointer->name, added));
70+
auto makeStackRestore = [&]() {
71+
return builder.makeGlobalSet(stackPointer->name,
72+
builder.makeLocalGet(local, pointerType));
73+
};
74+
// add stack restores to the returns
75+
FindAllPointers<Return> finder(func->body);
76+
for (auto** ptr : finder.list) {
77+
auto* ret = (*ptr)->cast<Return>();
78+
if (ret->value && ret->value->type != Type::unreachable) {
79+
// handle the returned value
80+
auto* block = builder.makeBlock();
81+
auto temp = builder.addVar(func, ret->value->type);
82+
block->list.push_back(builder.makeLocalSet(temp, ret->value));
83+
block->list.push_back(makeStackRestore());
84+
block->list.push_back(
85+
builder.makeReturn(builder.makeLocalGet(temp, ret->value->type)));
86+
block->finalize();
87+
*ptr = block;
88+
} else {
89+
// restore, then return
90+
*ptr = builder.makeSequence(makeStackRestore(), ret);
91+
}
92+
}
93+
// add stack restores to the body
94+
if (func->body->type == Type::none) {
95+
block->list.push_back(func->body);
96+
block->list.push_back(makeStackRestore());
97+
} else if (func->body->type == Type::unreachable) {
98+
block->list.push_back(func->body);
99+
// no need to restore the old stack value, we're gone anyhow
100+
} else {
101+
// save the return value
102+
auto temp = builder.addVar(func, func->getResults());
103+
block->list.push_back(builder.makeLocalSet(temp, func->body));
104+
block->list.push_back(makeStackRestore());
105+
block->list.push_back(builder.makeLocalGet(temp, func->getResults()));
106+
}
107+
block->finalize();
108+
func->body = block;
109+
}
110+
111+
} // namespace ABI
112+
113+
} // namespace wasm
114+
115+
#endif // wasm_abi_stack_h

src/passes/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ set(passes_SOURCES
9696
SimplifyGlobals.cpp
9797
SimplifyLocals.cpp
9898
Souperify.cpp
99+
SpillPointers.cpp
99100
StackCheck.cpp
100101
SSAify.cpp
101102
Untee.cpp

src/passes/SpillPointers.cpp

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/*
2+
* Copyright 2017 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// Spills values that might be pointers to the C stack. This allows
19+
// Boehm-style GC to see them properly.
20+
//
21+
// To reduce the overhead of the extra operations added here, you
22+
// should probably run optimizations after doing it.
23+
// TODO: add a dead store elimination pass, which would help here
24+
//
25+
// * There is currently no check that there is enough stack space.
26+
//
27+
28+
#include "abi/stack.h"
29+
#include "cfg/liveness-traversal.h"
30+
#include "pass.h"
31+
#include "wasm-builder.h"
32+
#include "wasm.h"
33+
34+
namespace wasm {
35+
36+
struct SpillPointers
37+
: public WalkerPass<LivenessWalker<SpillPointers, Visitor<SpillPointers>>> {
38+
bool isFunctionParallel() override { return true; }
39+
40+
Pass* create() override { return new SpillPointers; }
41+
42+
// a mapping of the pointers to all the spillable things. We need to know
43+
// how to replace them, and as we spill we may modify them. This map
44+
// gives us, for an Expression** seen during the walk (and placed in the
45+
// basic block, which is what we iterate on for efficiency) => the
46+
// current actual pointer, which may have moded
47+
std::unordered_map<Expression**, Expression**> actualPointers;
48+
49+
// note calls in basic blocks
50+
template<typename T> void visitSpillable(T* curr) {
51+
// if in unreachable code, ignore
52+
if (!currBasicBlock) {
53+
return;
54+
}
55+
auto* pointer = getCurrentPointer();
56+
currBasicBlock->contents.actions.emplace_back(pointer);
57+
// starts out as correct, may change later
58+
actualPointers[pointer] = pointer;
59+
}
60+
61+
void visitCall(Call* curr) { visitSpillable(curr); }
62+
void visitCallIndirect(CallIndirect* curr) { visitSpillable(curr); }
63+
64+
// main entry point
65+
66+
void doWalkFunction(Function* func) {
67+
super::doWalkFunction(func);
68+
spillPointers();
69+
}
70+
71+
// map pointers to their offset in the spill area
72+
typedef std::unordered_map<Index, Index> PointerMap;
73+
74+
Type pointerType;
75+
76+
void spillPointers() {
77+
pointerType = getModule()->memory.indexType;
78+
79+
// we only care about possible pointers
80+
auto* func = getFunction();
81+
PointerMap pointerMap;
82+
for (Index i = 0; i < func->getNumLocals(); i++) {
83+
if (func->getLocalType(i) == pointerType) {
84+
auto offset = pointerMap.size() * pointerType.getByteSize();
85+
pointerMap[i] = offset;
86+
}
87+
}
88+
// find calls and spill around them
89+
bool spilled = false;
90+
Index spillLocal = -1;
91+
for (auto& curr : basicBlocks) {
92+
if (liveBlocks.count(curr.get()) == 0) {
93+
continue; // ignore dead blocks
94+
}
95+
auto& liveness = curr->contents;
96+
auto& actions = liveness.actions;
97+
Index lastCall = -1;
98+
for (Index i = 0; i < actions.size(); i++) {
99+
auto& action = liveness.actions[i];
100+
if (action.isOther()) {
101+
lastCall = i;
102+
}
103+
}
104+
if (lastCall == Index(-1)) {
105+
continue; // nothing to see here
106+
}
107+
// scan through the block, spilling around the calls
108+
// TODO: we can filter on pointerMap everywhere
109+
SetOfLocals live = liveness.end;
110+
for (int i = int(actions.size()) - 1; i >= 0; i--) {
111+
auto& action = actions[i];
112+
if (action.isGet()) {
113+
live.insert(action.index);
114+
} else if (action.isSet()) {
115+
live.erase(action.index);
116+
} else if (action.isOther()) {
117+
std::vector<Index> toSpill;
118+
for (auto index : live) {
119+
if (pointerMap.count(index) > 0) {
120+
toSpill.push_back(index);
121+
}
122+
}
123+
if (!toSpill.empty()) {
124+
// we now have a call + the information about which locals
125+
// should be spilled
126+
if (!spilled) {
127+
// prepare stack support: get a pointer to stack space big enough
128+
// for all our data
129+
spillLocal = Builder::addVar(func, pointerType);
130+
spilled = true;
131+
}
132+
// the origin was seen at walk, but the thing may have moved
133+
auto* pointer = actualPointers[action.origin];
134+
spillPointersAroundCall(
135+
pointer, toSpill, spillLocal, pointerMap, func, getModule());
136+
}
137+
} else {
138+
WASM_UNREACHABLE("unexpected action");
139+
}
140+
}
141+
}
142+
if (spilled) {
143+
// get the stack space, and set the local to it
144+
ABI::getStackSpace(spillLocal,
145+
func,
146+
pointerType.getByteSize() * pointerMap.size(),
147+
*getModule());
148+
}
149+
}
150+
151+
void spillPointersAroundCall(Expression** origin,
152+
std::vector<Index>& toSpill,
153+
Index spillLocal,
154+
PointerMap& pointerMap,
155+
Function* func,
156+
Module* module) {
157+
auto* call = *origin;
158+
if (call->type == Type::unreachable) {
159+
return; // the call is never reached anyhow, ignore
160+
}
161+
Builder builder(*module);
162+
auto* block = builder.makeBlock();
163+
// move the operands into locals, as we must spill after they are executed
164+
auto handleOperand = [&](Expression*& operand) {
165+
auto temp = builder.addVar(func, operand->type);
166+
auto* set = builder.makeLocalSet(temp, operand);
167+
block->list.push_back(set);
168+
block->finalize();
169+
if (actualPointers.count(&operand) > 0) {
170+
// this is something we track, and it's moving - update
171+
actualPointers[&operand] = &set->value;
172+
}
173+
operand = builder.makeLocalGet(temp, operand->type);
174+
};
175+
if (call->is<Call>()) {
176+
for (auto*& operand : call->cast<Call>()->operands) {
177+
handleOperand(operand);
178+
}
179+
} else if (call->is<CallIndirect>()) {
180+
for (auto*& operand : call->cast<CallIndirect>()->operands) {
181+
handleOperand(operand);
182+
}
183+
handleOperand(call->cast<CallIndirect>()->target);
184+
} else {
185+
WASM_UNREACHABLE("unexpected expr");
186+
}
187+
// add the spills
188+
for (auto index : toSpill) {
189+
block->list.push_back(
190+
builder.makeStore(pointerType.getByteSize(),
191+
pointerMap[index],
192+
pointerType.getByteSize(),
193+
builder.makeLocalGet(spillLocal, pointerType),
194+
builder.makeLocalGet(index, pointerType),
195+
pointerType));
196+
}
197+
// add the (modified) call
198+
block->list.push_back(call);
199+
block->finalize();
200+
*origin = block;
201+
}
202+
};
203+
204+
Pass* createSpillPointersPass() { return new SpillPointers(); }
205+
206+
} // namespace wasm

src/passes/pass.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,9 @@ void PassRegistry::registerPasses() {
389389
registerPass("souperify-single-use",
390390
"emit Souper IR in text form (single-use nodes only)",
391391
createSouperifySingleUsePass);
392+
registerPass("spill-pointers",
393+
"spill pointers to the C stack (useful for Boehm-style GC)",
394+
createSpillPointersPass);
392395
registerPass("stub-unsupported-js",
393396
"stub out unsupported JS operations",
394397
createStubUnsupportedJSOpsPass);

src/passes/passes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ Pass* createStripProducersPass();
129129
Pass* createStripTargetFeaturesPass();
130130
Pass* createSouperifyPass();
131131
Pass* createSouperifySingleUsePass();
132+
Pass* createSpillPointersPass();
132133
Pass* createStubUnsupportedJSOpsPass();
133134
Pass* createSSAifyPass();
134135
Pass* createSSAifyNoMergePass();

test/lit/help/wasm-opt.test

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,9 @@
384384
;; CHECK-NEXT: --souperify-single-use emit Souper IR in text form
385385
;; CHECK-NEXT: (single-use nodes only)
386386
;; CHECK-NEXT:
387+
;; CHECK-NEXT: --spill-pointers spill pointers to the C stack
388+
;; CHECK-NEXT: (useful for Boehm-style GC)
389+
;; CHECK-NEXT:
387390
;; CHECK-NEXT: --ssa ssa-ify variables so that they
388391
;; CHECK-NEXT: have a single assignment
389392
;; CHECK-NEXT:

test/lit/help/wasm2js.test

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,9 @@
346346
;; CHECK-NEXT: --souperify-single-use emit Souper IR in text form
347347
;; CHECK-NEXT: (single-use nodes only)
348348
;; CHECK-NEXT:
349+
;; CHECK-NEXT: --spill-pointers spill pointers to the C stack
350+
;; CHECK-NEXT: (useful for Boehm-style GC)
351+
;; CHECK-NEXT:
349352
;; CHECK-NEXT: --ssa ssa-ify variables so that they
350353
;; CHECK-NEXT: have a single assignment
351354
;; CHECK-NEXT:

0 commit comments

Comments
 (0)