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
|
/*
* 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 {
bool isOnceFunction(Function* f) { return f->name.hasSubstring("_<once>_"); }
using AssignmentCountMap = std::unordered_map<Name, Index>;
// 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 value however just in
// case if other passes in bineryen does something 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)
: assignmentCounts(assignmentCounts) {}
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) {
// We introduced "nop" instruction. Run the vacuum to cleanup.
// TODO: maybe we should not introduce "nop" in the first place and try
// removing instead.
PassRunner runner(getModule());
runner.add("precompute");
runner.add("remove-unused-brs");
runner.add("vacuum");
runner.setIsNested(true);
runner.runOnFunction(curr);
}
}
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;
};
// A visitor that marks trivial once functions for removal.
// TODO: parallelize
class EnableInline : public WalkerPass<PostWalker<EnableInline>> {
public:
void visitFunction(Function* curr) {
if (!isOnceFunction(curr)) {
return;
}
auto* body = curr->body;
if (Measurer::measure(body) <= 2) {
// Once function has become trivial: enable inlining so it could be
// removed.
//
// Note that the size of a trivial function is set to 2 to cover things
// like
// - nop (e.g. a clinit that is emptied)
// - call (e.g. a clinit just calling another one; note that clinits take
// no parameters)
// - return global.get abc (e.g. a string literal moved to global)
// - global.set abc 1 (e.g. partially inlined clinit that is empty)
// while rejecting more complex scenario like condition checks. Optimizing
// complex functions could change the shape of "once" functions and make
// the ConstantHoister in this pass and OnceReducer ineffective.
curr->noFullInline = false;
curr->noPartialInline = false;
}
}
};
struct J2CLOpts : public Pass {
void hoistConstants(Module* module) {
AssignmentCountMap assignmentCounts;
GlobalAssignmentCollector collector(assignmentCounts);
collector.run(getPassRunner(), module);
// TODO surprisingly looping help some but not a lot. Maybe we are missing
// something that helps globals to be considered immutable.
while (1) {
ConstantHoister hoister(assignmentCounts);
hoister.run(getPassRunner(), module);
int optimized = hoister.optimized;
#ifdef J2CL_OPT_DEBUG
std::cout << "Optimized " << optimized << " global fields\n";
#endif
if (optimized == 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);
EnableInline enableInline;
enableInline.run(getPassRunner(), 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
|