1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
|
/*
* Copyright 2023 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.
*/
//
// Optimize J2CL specifics construct to simplify them and enable further
// optimizations by other passes.
//
#include "ir/global-utils.h"
#include "ir/utils.h"
#include "opt-utils.h"
#include "pass.h"
#include "passes.h"
#include "wasm.h"
namespace wasm {
namespace {
using AssignmentCountMap = std::unordered_map<Name, Index>;
using TrivialFunctionMap = std::unordered_map<Name, Expression*>;
bool isOnceFunction(Name name) { return name.hasSubstring("_<once>_"); }
bool isOnceFunction(Function* func) { return isOnceFunction(func->name); }
// Returns the function body if it is a trivial function, null otherwise.
Expression* getTrivialFunctionBody(Function* func) {
auto* body = func->body;
// Only consider trivial the following instructions which can be safely
// inlined and note that their size is at most 2.
if (body->is<Nop>() || body->is<GlobalGet>() || body->is<Const>() ||
(body->is<Block>() && body->cast<Block>()->list.empty()) ||
// Call with no arguments.
(body->is<Call>() && body->dynCast<Call>()->operands.size() == 0) ||
// Simple global.set with a constant.
(body->is<GlobalSet>() &&
body->dynCast<GlobalSet>()->value->is<Const>())) {
return body;
}
return nullptr;
}
// Adds the function to the map if it is trivial.
void maybeCollectTrivialFunction(Function* func,
TrivialFunctionMap& trivialFunctionMap) {
auto* body = getTrivialFunctionBody(func);
if (body == nullptr) {
return;
}
trivialFunctionMap[func->name] = body;
}
// Cleans up a once function that has been modified in the hopes it
// becomes trivial.
void cleanupFunction(Module* module, Function* func) {
PassRunner runner(module);
runner.add("precompute");
runner.add("vacuum");
// Run after vacuum to remove the extra returns that vacuum might
// leave when reducing a block that ends up with just one instruction.
runner.add("remove-unused-brs");
runner.setIsNested(true);
runner.runOnFunction(func);
}
// A visitor to count the number of GlobalSets of each global so we can later
// identify the number of assignments of the global.
// TODO: parallelize
class GlobalAssignmentCollector
: public WalkerPass<PostWalker<GlobalAssignmentCollector>> {
public:
GlobalAssignmentCollector(AssignmentCountMap& assignmentCounts)
: assignmentCounts(assignmentCounts) {}
void visitGlobal(Global* curr) {
if (isInitialValue(curr->init)) {
return;
}
// J2CL normally doesn't set non-default initial values, however, just in
// case other passes in binaryen do something and set a value to the global
// we should back off by recording this as an assignment.
recordGlobalAssignment(curr->name);
}
void visitGlobalSet(GlobalSet* curr) { recordGlobalAssignment(curr->name); }
private:
bool isInitialValue(Expression* expr) {
if (auto* constExpr = expr->dynCast<Const>()) {
return constExpr->value.isZero();
} else {
return expr->is<RefNull>();
}
}
void recordGlobalAssignment(Name name) {
// Avoid optimizing class initialization condition variable itself. If we
// were optimizing it then it would become "true" and would defeat its
// functionality and the clinit would never trigger during runtime.
if (name.startsWith("$class-initialized@")) {
return;
}
assignmentCounts[name]++;
}
AssignmentCountMap& assignmentCounts;
};
// A visitor that moves initialization of constant-like globals from "once"
// functions to global init.
// TODO: parallelize
class ConstantHoister : public WalkerPass<PostWalker<ConstantHoister>> {
public:
ConstantHoister(AssignmentCountMap& assignmentCounts,
TrivialFunctionMap& trivialFunctionMap)
: assignmentCounts(assignmentCounts),
trivialFunctionMap(trivialFunctionMap) {}
int optimized = 0;
void visitFunction(Function* curr) {
if (!isOnceFunction(curr)) {
return;
}
Name enclosingClassName = getEnclosingClass(curr->name);
int optimizedBefore = optimized;
if (auto* block = curr->body->dynCast<Block>()) {
for (auto*& expr : block->list) {
maybeHoistConstant(expr, enclosingClassName);
}
} else {
maybeHoistConstant(curr->body, enclosingClassName);
}
if (optimized != optimizedBefore) {
cleanupFunction(getModule(), curr);
maybeCollectTrivialFunction(curr, trivialFunctionMap);
}
}
private:
void maybeHoistConstant(Expression* expr, Name enclosingClassName) {
auto set = expr->dynCast<GlobalSet>();
if (!set) {
return;
}
if (assignmentCounts[set->name] != 1) {
// The global assigned in multiple places, so it is not safe to
// hoist them as global constants.
return;
}
if (getEnclosingClass(set->name) != enclosingClassName) {
// Only hoist fields initialized by its own class.
// If it is only initialized once but by another class (although it is
// very uncommon / edge scenario) then we cannot be sure if the clinit was
// triggered before the field access so it is better to leave it alone.
return;
}
if (!GlobalUtils::canInitializeGlobal(*getModule(), set->value)) {
// It is not a valid constant expression so cannot be hoisted to
// global init.
return;
}
// Move it to global init and mark it as immutable.
auto global = getModule()->getGlobal(set->name);
global->init = set->value;
global->mutable_ = false;
ExpressionManipulator::nop(expr);
optimized++;
}
Name getEnclosingClass(Name name) {
return Name(name.str.substr(name.str.find_last_of('@')));
}
AssignmentCountMap& assignmentCounts;
TrivialFunctionMap& trivialFunctionMap;
};
// Class to collect functions that are already trivial before the pass is run.
// When this pass is run, other optimizations that preceded it might have left
// the body of some of these functions trivial.
// Since the loop in this pass will only inline the functions that are made
// trivial by this pass, the functions that were already trivial before would
// not be inlined if they were not collected by this visitor.
class TrivialOnceFunctionCollector
: public WalkerPass<PostWalker<TrivialOnceFunctionCollector>> {
public:
TrivialOnceFunctionCollector(TrivialFunctionMap& trivialFunctionMap)
: trivialFunctionMap(trivialFunctionMap) {}
void visitFunction(Function* curr) {
if (!isOnceFunction(curr)) {
return;
}
maybeCollectTrivialFunction(curr, trivialFunctionMap);
}
private:
TrivialFunctionMap& trivialFunctionMap;
};
// A visitor that inlines trivial once functions.
// TODO: parallelize
class InlineTrivialOnceFunctions
: public WalkerPass<PostWalker<InlineTrivialOnceFunctions>> {
public:
InlineTrivialOnceFunctions(TrivialFunctionMap& trivialFunctionMap)
: trivialFunctionMap(trivialFunctionMap) {}
void visitCall(Call* curr) {
if (curr->operands.size() != 0 || !isOnceFunction(curr->target)) {
return;
}
auto iter = trivialFunctionMap.find(curr->target);
if (iter == trivialFunctionMap.end()) {
return;
}
auto* expr = iter->second;
// The call was to a trivial once function which consists of the expression
// in <expr>; replace the call with it.
Builder builder(*getModule());
auto* replacement = ExpressionManipulator::copy(expr, *getModule());
replaceCurrent(replacement);
lastModifiedFunction = getFunction();
inlined++;
}
void visitFunction(Function* curr) {
// Since the traversal is in post-order, we only need to check if the
// current function is the function that was last inlined into.
// We also do not want to do any cleanup for a non-once function (we leave
// that for other passes, as it will not end up helping further work here).
if (lastModifiedFunction != curr || !isOnceFunction(curr)) {
return;
}
cleanupFunction(getModule(), curr);
maybeCollectTrivialFunction(curr, trivialFunctionMap);
}
int inlined = 0;
private:
TrivialFunctionMap& trivialFunctionMap;
Function* lastModifiedFunction = nullptr;
};
struct J2CLOpts : public Pass {
void hoistConstants(Module* module) {
AssignmentCountMap assignmentCounts;
TrivialFunctionMap trivialFunctionMap;
GlobalAssignmentCollector collector(assignmentCounts);
collector.run(getPassRunner(), module);
TrivialOnceFunctionCollector trivialFunctionCollector(trivialFunctionMap);
trivialFunctionCollector.run(getPassRunner(), module);
while (1) {
ConstantHoister hoister(assignmentCounts, trivialFunctionMap);
hoister.run(getPassRunner(), module);
int optimized = hoister.optimized;
InlineTrivialOnceFunctions inliner(trivialFunctionMap);
inliner.run(getPassRunner(), module);
int inlined = inliner.inlined;
#ifdef J2CL_OPT_DEBUG
std::cout << "Optimized " << optimized << " global fields\n";
#endif
if (optimized == 0 && inlined == 0) {
break;
}
}
}
void run(Module* module) override {
if (!module->features.hasGC()) {
return;
}
// Move constant like properties set by the once functions to global
// initialization.
hoistConstants(module);
// We might have introduced new globals depending on other globals. Reorder
// order them so they follow initialization order.
// TODO: do this only if have introduced a new global.
PassRunner runner(module);
runner.add("reorder-globals-always");
runner.setIsNested(true);
runner.run();
}
};
} // anonymous namespace
Pass* createJ2CLOptsPass() { return new J2CLOpts(); }
} // namespace wasm
|