Skip to content

Commit b5ee16f

Browse files
authored
SpillPointers pass (#1339)
This is an experiment to help with Boehm-style GC. It will spill things that could be pointers to the C stack, so that they can be seen by conservative garbage collection. The spills add code size and runtime overhead, but actually less than I thought: 10% slower (smaller than the difference between VMs), 15% gzip size larger. We can do even better with more optimizations for this, like a dead store elimination pass. This PR does the following: * Add the new pass. * Create an abi/ dir, with info about the pointer size and stack manipulation utilities. * Separates out the liveness analysis from CoalesceLocals, so that other passes can use it (like SpillPointers). * Refactor out the SortedVector class from the liveness analysis to a separate file (just seems nicer that way).
1 parent 10bf008 commit b5ee16f

File tree

12 files changed

+1535
-283
lines changed

12 files changed

+1535
-283
lines changed

src/abi/abi.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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_abi_h
18+
#define wasm_abi_abi_h
19+
20+
#include "wasm.h"
21+
22+
namespace wasm {
23+
24+
namespace ABI {
25+
26+
// The pointer type. Will need to update this for wasm64
27+
const static WasmType PointerType = WasmType::i32;
28+
29+
} // namespace ABI
30+
31+
} // namespace wasm
32+
33+
#endif // wasm_abi_abi_h

src/abi/stack.h

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 "wasm.h"
21+
#include "wasm-builder.h"
22+
#include "shared-constants.h"
23+
#include "asmjs/shared-constants.h"
24+
#include "ir/find_all.h"
25+
#include "ir/global-utils.h"
26+
#include "abi.h"
27+
28+
namespace wasm {
29+
30+
namespace ABI {
31+
32+
enum {
33+
StackAlign = 16
34+
};
35+
36+
inline Index stackAlign(Index size) {
37+
return (size + StackAlign - 1) & -StackAlign;
38+
}
39+
40+
// Allocate some space on the stack, and assign it to a local.
41+
// The local will have the same constant value in all the function, so you can just
42+
// get_local it anywhere there.
43+
inline void getStackSpace(Index local, Function* func, Index size, Module& wasm) {
44+
auto* stackPointer = GlobalUtils::getGlobalInitializedToImport(wasm, ENV, "STACKTOP");
45+
if (!stackPointer) {
46+
Fatal() << "getStackSpace: failed to find the stack pointer";
47+
}
48+
// align the size
49+
size = stackAlign(size);
50+
// TODO: find existing stack usage, and add on top of that - carefully
51+
Builder builder(wasm);
52+
auto* block = builder.makeBlock();
53+
block->list.push_back(
54+
builder.makeSetLocal(
55+
local,
56+
builder.makeGetGlobal(stackPointer->name, PointerType)
57+
)
58+
);
59+
// TODO: add stack max check
60+
Expression* added;
61+
if (PointerType == i32) {
62+
added = builder.makeBinary(
63+
AddInt32,
64+
builder.makeGetLocal(local, PointerType),
65+
builder.makeConst(Literal(int32_t(size)))
66+
);
67+
} else {
68+
WASM_UNREACHABLE();
69+
}
70+
block->list.push_back(
71+
builder.makeSetGlobal(
72+
stackPointer->name,
73+
added
74+
)
75+
);
76+
auto makeStackRestore = [&]() {
77+
return builder.makeSetGlobal(
78+
stackPointer->name,
79+
builder.makeGetLocal(local, PointerType)
80+
);
81+
};
82+
// add stack restores to the returns
83+
FindAllPointers<Return> finder(func->body);
84+
for (auto** ptr : finder.list) {
85+
auto* ret = (*ptr)->cast<Return>();
86+
if (ret->value && ret->value->type != unreachable) {
87+
// handle the returned value
88+
auto* block = builder.makeBlock();
89+
auto temp = builder.addVar(func, ret->value->type);
90+
block->list.push_back(builder.makeSetLocal(temp, ret->value));
91+
block->list.push_back(makeStackRestore());
92+
block->list.push_back(builder.makeReturn(
93+
builder.makeGetLocal(temp, ret->value->type)
94+
));
95+
block->finalize();
96+
*ptr = block;
97+
} else {
98+
// restore, then return
99+
*ptr = builder.makeSequence(makeStackRestore(), ret);
100+
}
101+
}
102+
// add stack restores to the body
103+
if (func->body->type == none) {
104+
block->list.push_back(func->body);
105+
block->list.push_back(makeStackRestore());
106+
} else if (func->body->type == unreachable) {
107+
block->list.push_back(func->body);
108+
// no need to restore the old stack value, we're gone anyhow
109+
} else {
110+
// save the return value
111+
auto temp = builder.addVar(func, func->result);
112+
block->list.push_back(builder.makeSetLocal(temp, func->body));
113+
block->list.push_back(makeStackRestore());
114+
block->list.push_back(builder.makeGetLocal(temp, func->result));
115+
}
116+
block->finalize();
117+
func->body = block;
118+
}
119+
120+
} // namespace ABI
121+
122+
} // namespace wasm
123+
124+
#endif // wasm_abi_stack_h

src/cfg/liveness-traversal.h

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
#include <wasm-printing.h>
2+
/*
3+
* Copyright 2017 WebAssembly Community Group participants
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
//
19+
// Convert the AST to a CFG, while traversing it.
20+
//
21+
// Note that this is not the same as the relooper CFG. The relooper is
22+
// designed for compilation to an AST, this is for processing. There is
23+
// no built-in support for transforming this CFG into the AST back
24+
// again, it is just metadata on the side for computation purposes.
25+
//
26+
// Usage: As the traversal proceeds, you can note information and add it to
27+
// the current basic block using currBasicBlock, on the contents
28+
// property, whose type is user-defined.
29+
//
30+
31+
#ifndef liveness_traversal_h
32+
#define liveness_traversal_h
33+
34+
#include "support/sorted_vector.h"
35+
#include "wasm.h"
36+
#include "wasm-builder.h"
37+
#include "wasm-traversal.h"
38+
#include "cfg-traversal.h"
39+
40+
namespace wasm {
41+
42+
// A set of locals. This is optimized for comparisons,
43+
// mergings, and iteration on elements, assuming that there
44+
// may be a great many potential elements but actual sets
45+
// may be fairly small. Specifically, we use a sorted
46+
// vector.
47+
typedef SortedVector LocalSet;
48+
49+
// A liveness-relevant action. Supports a get, a set, or an
50+
// "other" which can be used for other purposes, to mark
51+
// their position in a block
52+
struct Action {
53+
enum What {
54+
Get = 0,
55+
Set = 1,
56+
Other = 2
57+
};
58+
What what;
59+
Index index; // the local index read or written
60+
Expression** origin; // the origin
61+
bool effective; // whether a store is actually effective, i.e., may be read
62+
63+
Action(What what, Index index, Expression** origin) : what(what), index(index), origin(origin), effective(false) {
64+
assert(what != Other);
65+
if (what == Get) assert((*origin)->is<GetLocal>());
66+
if (what == Set) assert((*origin)->is<SetLocal>());
67+
}
68+
Action(Expression** origin) : what(Other), origin(origin) {}
69+
70+
bool isGet() { return what == Get; }
71+
bool isSet() { return what == Set; }
72+
bool isOther() { return what == Other; }
73+
};
74+
75+
// information about liveness in a basic block
76+
struct Liveness {
77+
LocalSet start, end; // live locals at the start and end
78+
std::vector<Action> actions; // actions occurring in this block
79+
80+
void dump(Function* func) {
81+
if (actions.empty()) return;
82+
std::cout << " actions:\n";
83+
for (auto& action : actions) {
84+
std::cout << " " << (action.isGet() ? "get" : "set") << " " << func->getLocalName(action.index) << "\n";
85+
}
86+
}
87+
};
88+
89+
template<typename SubType, typename VisitorType>
90+
struct LivenessWalker : public CFGWalker<SubType, VisitorType, Liveness> {
91+
typedef typename CFGWalker<SubType, VisitorType, Liveness>::BasicBlock BasicBlock;
92+
93+
Index numLocals;
94+
std::unordered_set<BasicBlock*> liveBlocks;
95+
std::vector<uint8_t> copies; // canonicalized - accesses should check (low, high) TODO: use a map for high N, as this tends to be sparse? or don't look at copies at all for big N?
96+
std::vector<Index> totalCopies; // total # of copies for each local, with all others
97+
98+
// cfg traversal work
99+
100+
static void doVisitGetLocal(SubType* self, Expression** currp) {
101+
auto* curr = (*currp)->cast<GetLocal>();
102+
// if in unreachable code, ignore
103+
if (!self->currBasicBlock) {
104+
*currp = Builder(*self->getModule()).replaceWithIdenticalType(curr);
105+
return;
106+
}
107+
self->currBasicBlock->contents.actions.emplace_back(Action::Get, curr->index, currp);
108+
}
109+
110+
static void doVisitSetLocal(SubType* self, Expression** currp) {
111+
auto* curr = (*currp)->cast<SetLocal>();
112+
// if in unreachable code, we don't need the tee (but might need the value, if it has side effects)
113+
if (!self->currBasicBlock) {
114+
if (curr->isTee()) {
115+
*currp = curr->value;
116+
} else {
117+
*currp = Builder(*self->getModule()).makeDrop(curr->value);
118+
}
119+
return;
120+
}
121+
self->currBasicBlock->contents.actions.emplace_back(Action::Set, curr->index, currp);
122+
// if this is a copy, note it
123+
if (auto* get = self->getCopy(curr)) {
124+
// add 2 units, so that backedge prioritization can decide ties, but not much more
125+
self->addCopy(curr->index, get->index);
126+
self->addCopy(curr->index, get->index);
127+
}
128+
}
129+
130+
// A simple copy is a set of a get. A more interesting copy
131+
// is a set of an if with a value, where one side a get.
132+
// That can happen when we create an if value in simplify-locals. TODO: recurse into
133+
// nested ifs, and block return values? Those cases are trickier, need to
134+
// count to see if worth it.
135+
// TODO: an if can have two copies
136+
GetLocal* getCopy(SetLocal* set) {
137+
if (auto* get = set->value->dynCast<GetLocal>()) return get;
138+
if (auto* iff = set->value->dynCast<If>()) {
139+
if (auto* get = iff->ifTrue->dynCast<GetLocal>()) return get;
140+
if (iff->ifFalse) {
141+
if (auto* get = iff->ifFalse->dynCast<GetLocal>()) return get;
142+
}
143+
}
144+
return nullptr;
145+
}
146+
147+
// main entry point
148+
149+
void doWalkFunction(Function* func) {
150+
numLocals = func->getNumLocals();
151+
copies.resize(numLocals * numLocals);
152+
std::fill(copies.begin(), copies.end(), 0);
153+
totalCopies.resize(numLocals);
154+
std::fill(totalCopies.begin(), totalCopies.end(), 0);
155+
// create the CFG by walking the IR
156+
CFGWalker<SubType, VisitorType, Liveness>::doWalkFunction(func);
157+
// ignore links to dead blocks, so they don't confuse us and we can see their stores are all ineffective
158+
liveBlocks = CFGWalker<SubType, VisitorType, Liveness>::findLiveBlocks();
159+
CFGWalker<SubType, VisitorType, Liveness>::unlinkDeadBlocks(liveBlocks);
160+
// flow liveness across blocks
161+
flowLiveness();
162+
}
163+
164+
void flowLiveness() {
165+
// keep working while stuff is flowing
166+
std::unordered_set<BasicBlock*> queue;
167+
for (auto& curr : CFGWalker<SubType, VisitorType, Liveness>::basicBlocks) {
168+
if (liveBlocks.count(curr.get()) == 0) continue; // ignore dead blocks
169+
queue.insert(curr.get());
170+
// do the first scan through the block, starting with nothing live at the end, and updating the liveness at the start
171+
scanLivenessThroughActions(curr->contents.actions, curr->contents.start);
172+
}
173+
// at every point in time, we assume we already noted interferences between things already known alive at the end, and scanned back through the block using that
174+
while (queue.size() > 0) {
175+
auto iter = queue.begin();
176+
auto* curr = *iter;
177+
queue.erase(iter);
178+
LocalSet live;
179+
if (!mergeStartsAndCheckChange(curr->out, curr->contents.end, live)) continue;
180+
assert(curr->contents.end.size() < live.size());
181+
curr->contents.end = live;
182+
scanLivenessThroughActions(curr->contents.actions, live);
183+
// liveness is now calculated at the start. if something
184+
// changed, all predecessor blocks need recomputation
185+
if (curr->contents.start == live) continue;
186+
assert(curr->contents.start.size() < live.size());
187+
curr->contents.start = live;
188+
for (auto* in : curr->in) {
189+
queue.insert(in);
190+
}
191+
}
192+
}
193+
194+
// merge starts of a list of blocks. return
195+
// whether anything changed vs an old state (which indicates further processing is necessary).
196+
bool mergeStartsAndCheckChange(std::vector<BasicBlock*>& blocks, LocalSet& old, LocalSet& ret) {
197+
if (blocks.size() == 0) return false;
198+
ret = blocks[0]->contents.start;
199+
if (blocks.size() > 1) {
200+
// more than one, so we must merge
201+
for (Index i = 1; i < blocks.size(); i++) {
202+
ret = ret.merge(blocks[i]->contents.start);
203+
}
204+
}
205+
return old != ret;
206+
}
207+
208+
void scanLivenessThroughActions(std::vector<Action>& actions, LocalSet& live) {
209+
// move towards the front
210+
for (int i = int(actions.size()) - 1; i >= 0; i--) {
211+
auto& action = actions[i];
212+
if (action.isGet()) {
213+
live.insert(action.index);
214+
} else {
215+
live.erase(action.index);
216+
}
217+
}
218+
}
219+
220+
void addCopy(Index i, Index j) {
221+
auto k = std::min(i, j) * numLocals + std::max(i, j);
222+
copies[k] = std::min(copies[k], uint8_t(254)) + 1;
223+
totalCopies[i]++;
224+
totalCopies[j]++;
225+
}
226+
227+
uint8_t getCopies(Index i, Index j) {
228+
return copies[std::min(i, j) * numLocals + std::max(i, j)];
229+
}
230+
};
231+
232+
} // namespace wasm
233+
234+
#endif // liveness_traversal_h

0 commit comments

Comments
 (0)