47
47
// This pass works well with TypeMerging. See notes there for more.
48
48
//
49
49
50
+ #include " ir/child-typer.h"
50
51
#include " ir/find_all.h"
51
52
#include " ir/module-utils.h"
52
53
#include " ir/names.h"
@@ -147,14 +148,138 @@ std::vector<HeapType> ensureTypesAreInNewRecGroup(RecGroup recGroup,
147
148
// A vector of struct.new or one of the variations on array.new.
148
149
using News = std::vector<Expression*>;
149
150
150
- struct NewFinder : public PostWalker <NewFinder> {
151
+ // A set of types for which the exactness of allocations may be observed.
152
+ using TypeSet = std::unordered_set<HeapType>;
153
+
154
+ struct Analyzer
155
+ : public PostWalker<Analyzer, UnifiedExpressionVisitor<Analyzer>> {
156
+ // Find allocations we can potentially optimize.
151
157
News news;
152
158
153
- void visitStructNew (StructNew* curr) { news.push_back (curr); }
154
- void visitArrayNew (ArrayNew* curr) { news.push_back (curr); }
155
- void visitArrayNewData (ArrayNewData* curr) { news.push_back (curr); }
156
- void visitArrayNewElem (ArrayNewElem* curr) { news.push_back (curr); }
157
- void visitArrayNewFixed (ArrayNewFixed* curr) { news.push_back (curr); }
159
+ // Also find heap types for which the exactness of allocations is observed. We
160
+ // will not be able to optimize allocations of these types without an analysis
161
+ // proving that an allocation does not flow into any location where its
162
+ // exactness is observed.
163
+ TypeSet disallowedTypes;
164
+
165
+ // Find allocations we can potentially optimize.
166
+ void visitStructNew (StructNew* curr) {
167
+ news.push_back (curr);
168
+ visitExpression (curr);
169
+ }
170
+ void visitArrayNew (ArrayNew* curr) {
171
+ news.push_back (curr);
172
+ visitExpression (curr);
173
+ }
174
+ void visitArrayNewData (ArrayNewData* curr) {
175
+ news.push_back (curr);
176
+ visitExpression (curr);
177
+ }
178
+ void visitArrayNewElem (ArrayNewElem* curr) {
179
+ news.push_back (curr);
180
+ visitExpression (curr);
181
+ }
182
+ void visitArrayNewFixed (ArrayNewFixed* curr) {
183
+ news.push_back (curr);
184
+ visitExpression (curr);
185
+ }
186
+
187
+ // Find casts to exact types. Allocations of these types will not be able to
188
+ // be optimized.
189
+ template <typename Cast> void visitCast (Cast* cast) {
190
+ if (auto type = cast->getCastType (); type.isExact ()) {
191
+ disallowedTypes.insert (type.getHeapType ());
192
+ }
193
+ }
194
+ void visitRefTest (RefTest* curr) { visitCast (curr); }
195
+ void visitRefCast (RefCast* curr) { visitCast (curr); }
196
+ void visitBrOn (BrOn* curr) {
197
+ if (curr->op == BrOnCast || curr->op == BrOnCastFail) {
198
+ visitCast (curr);
199
+ }
200
+ }
201
+
202
+ void visitExpression (Expression* curr) {
203
+ // Look at the constraints on this expression's operands to see if it
204
+ // requires an exact operand. If it does, we cannot optimize allocations of
205
+ // that type. As an optimization, do not let control flow structures or
206
+ // branches inhibit optimization since they can safely be refinalized to use
207
+ // new types as long as no other instruction expected the original exact
208
+ // type. Also allow optimizing if the instruction that would inhibit
209
+ // optimizing will not be written in the final output. Skipping further
210
+ // analysis for these instructions also ensures that the ChildTyper below
211
+ // sees the type information it expects in the instructions it analyzes.
212
+ if (Properties::isControlFlowStructure (curr) ||
213
+ Properties::isBranch (curr) ||
214
+ Properties::hasUnwritableTypeImmediate (curr)) {
215
+ return ;
216
+ }
217
+
218
+ // Also do not let unreachable instructions inhibit optimization, as long as
219
+ // they are unreachable because of an unreachable child. (Some other
220
+ // unreachable instructions, such as a return_call, can still require an
221
+ // exact operand and may inhibit optimization.)
222
+ if (curr->type == Type::unreachable) {
223
+ for (auto * child : ChildIterator (curr)) {
224
+ if (child->type == Type::unreachable) {
225
+ return ;
226
+ }
227
+ }
228
+ }
229
+
230
+ struct ExactChildTyper : ChildTyper<ExactChildTyper> {
231
+ Analyzer& parent;
232
+ ExactChildTyper (Analyzer& parent)
233
+ : ChildTyper(*parent.getModule(), parent.getFunction()),
234
+ parent (parent) {}
235
+
236
+ void noteSubtype (Expression**, Type type) {
237
+ for (Type t : type) {
238
+ if (t.isExact ()) {
239
+ parent.disallowedTypes .insert (t.getHeapType ());
240
+ }
241
+ }
242
+ }
243
+
244
+ // Other constraints do not matter to us.
245
+ void noteAnyType (Expression**) {}
246
+ void noteAnyReferenceType (Expression**) {}
247
+ void noteAnyTupleType (Expression**, size_t ) {}
248
+ void noteAnyI8ArrayReferenceType (Expression**) {}
249
+ void noteAnyI16ArrayReferenceType (Expression**) {}
250
+
251
+ Type getLabelType (Name label) { WASM_UNREACHABLE (" unexpected branch" ); }
252
+ } typer(*this );
253
+ typer.visit(curr);
254
+ }
255
+
256
+ void visitFunction (Function* func) {
257
+ // Returned exact references must remain exact references to the original
258
+ // heap types.
259
+ for (auto type : func->getSig ().results ) {
260
+ if (type.isExact ()) {
261
+ disallowedTypes.insert (type.getHeapType ());
262
+ }
263
+ }
264
+ }
265
+
266
+ void visitGlobal (Global* global) {
267
+ // This could be more precise by checking that the init expression is not
268
+ // null before inhibiting optimization, or by just inhibiting optmization of
269
+ // the allocations used in the initialization, but this is simpler.
270
+ for (auto type : global->type ) {
271
+ if (type.isExact ()) {
272
+ disallowedTypes.insert (type.getHeapType ());
273
+ }
274
+ }
275
+ }
276
+
277
+ void visitElementSegment (ElementSegment* segment) {
278
+ assert (!segment->type .isTuple ());
279
+ if (segment->type .isExact ()) {
280
+ disallowedTypes.insert (segment->type .getHeapType ());
281
+ }
282
+ }
158
283
};
159
284
160
285
struct TypeSSA : public Pass {
@@ -170,28 +295,48 @@ struct TypeSSA : public Pass {
170
295
return ;
171
296
}
172
297
173
- // First, find all the struct/array.news.
298
+ struct Info {
299
+ News news;
300
+ TypeSet disallowedTypes;
301
+ };
174
302
175
- ModuleUtils::ParallelFunctionAnalysis<News> analysis (
176
- *module , [&](Function* func, News& news) {
303
+ // First, analyze the function to find struct/array.news and disallowed
304
+ // types.
305
+ ModuleUtils::ParallelFunctionAnalysis<Info> analysis (
306
+ *module , [&](Function* func, Info& info) {
177
307
if (func->imported ()) {
178
308
return ;
179
309
}
180
310
181
- NewFinder finder;
182
- finder.walk (func->body );
183
- news = std::move (finder.news );
311
+ Analyzer analyzer;
312
+ analyzer.walkFunctionInModule (func, module );
313
+ info.news = std::move (analyzer.news );
314
+ info.disallowedTypes = std::move (analyzer.disallowedTypes );
184
315
});
185
316
186
317
// Also find news in the module scope.
187
- NewFinder moduleFinder;
188
- moduleFinder.walkModuleCode (module );
318
+ Analyzer moduleAnalyzer;
319
+ moduleAnalyzer.walkModuleCode (module );
320
+ for (auto & global : module ->globals ) {
321
+ moduleAnalyzer.visitGlobal (global.get ());
322
+ }
323
+ for (auto & segment : module ->elementSegments ) {
324
+ moduleAnalyzer.visitElementSegment (segment.get ());
325
+ }
326
+ // TODO: Visit tables with initializers once we support those.
327
+
328
+ // Find all the types that are unoptimizable because the exactness of their
329
+ // allocations may be observed.
330
+ ModuleUtils::iterDefinedFunctions (*module , [&](Function* func) {
331
+ processDisallowedTypes (analysis.map [func].disallowedTypes );
332
+ });
333
+ processDisallowedTypes (moduleAnalyzer.disallowedTypes );
189
334
190
335
// Process all the news to find the ones we want to modify, adding them to
191
336
// newsToModify. Note that we must do so in a deterministic order.
192
337
ModuleUtils::iterDefinedFunctions (
193
- *module , [&](Function* func) { processNews (analysis.map [func]); });
194
- processNews (moduleFinder .news );
338
+ *module , [&](Function* func) { processNews (analysis.map [func]. news ); });
339
+ processNews (moduleAnalyzer .news );
195
340
196
341
// Modify the ones we found are relevant. We must modify them all at once as
197
342
// in the isorecursive type system we want to create a single new rec group
@@ -203,14 +348,24 @@ struct TypeSSA : public Pass {
203
348
ReFinalize ().runOnModuleCode (getPassRunner (), module );
204
349
}
205
350
351
+ TypeSet disallowedTypes;
352
+
353
+ void processDisallowedTypes (const TypeSet& types) {
354
+ disallowedTypes.insert (types.begin (), types.end ());
355
+ }
356
+
206
357
News newsToModify;
207
358
208
359
// As we generate new names, use a consistent index.
209
360
Index nameCounter = 0 ;
210
361
211
362
void processNews (const News& news) {
212
363
for (auto * curr : news) {
213
- if (isInteresting (curr)) {
364
+ bool disallowed = false ;
365
+ if (curr->type .isRef ()) {
366
+ disallowed = disallowedTypes.count (curr->type .getHeapType ());
367
+ }
368
+ if (!disallowed && isInteresting (curr)) {
214
369
newsToModify.push_back (curr);
215
370
}
216
371
}
0 commit comments