50
50
#include " passes/opt-utils.h"
51
51
#include " support/sorted_vector.h"
52
52
#include " wasm-builder.h"
53
+ #include " wasm-type.h"
53
54
#include " wasm.h"
54
55
55
56
namespace wasm {
@@ -76,12 +77,9 @@ struct DAEFunctionInfo {
76
77
// removed as well.
77
78
bool hasTailCalls = false ;
78
79
std::unordered_set<Name> tailCallees;
79
- // The set of functions that have calls from places that limit what we can do.
80
- // For now, any call we don't see inhibits our optimizations, but TODO: an
81
- // export could be worked around by exporting a thunk that adds the parameter.
82
- //
83
- // This is built up in parallel in each function, and combined at the end.
84
- std::unordered_set<Name> hasUnseenCalls;
80
+ // The set of functions that have their reference taken (which means there may
81
+ // be non-direct calls, limiting what we can do).
82
+ std::unordered_set<Name> hasRef;
85
83
86
84
// Clears all data, which marks us as stale and in need of recomputation.
87
85
void clear () { *this = DAEFunctionInfo (); }
@@ -143,12 +141,9 @@ struct DAEScanner
143
141
// the infoMap).
144
142
auto * currInfo = info ? info : &(*infoMap)[Name ()];
145
143
146
- // Treat a ref.func as an unseen call, preventing us from changing the
147
- // function's type. If we did change it, it could be an observable
148
- // difference from the outside, if the reference escapes, for example.
149
144
// TODO: look for actual escaping?
150
145
// TODO: create a thunk for external uses that allow internal optimizations
151
- currInfo->hasUnseenCalls .insert (curr->func );
146
+ currInfo->hasRef .insert (curr->func );
152
147
}
153
148
154
149
// main entry point
@@ -248,7 +243,7 @@ struct DAE : public Pass {
248
243
249
244
std::vector<std::vector<Call*>> allCalls (numFunctions);
250
245
std::vector<bool > tailCallees (numFunctions);
251
- std::vector<bool > hasUnseenCalls (numFunctions);
246
+ std::vector<bool > hasRef (numFunctions);
252
247
253
248
// Track the function in which relevant expressions exist. When we modify
254
249
// those expressions we will need to mark the function's info as stale.
@@ -267,14 +262,17 @@ struct DAE : public Pass {
267
262
for (auto & [call, dropp] : info.droppedCalls ) {
268
263
allDroppedCalls[call] = dropp;
269
264
}
270
- for (auto & name : info.hasUnseenCalls ) {
271
- hasUnseenCalls [indexes[name]] = true ;
265
+ for (auto & name : info.hasRef ) {
266
+ hasRef [indexes[name]] = true ;
272
267
}
273
268
}
274
- // Exports are considered unseen calls.
269
+
270
+ // Exports limit some optimizations, for example, we cannot remove or refine
271
+ // parameters. TODO: we could export a thunk that drops the parameter etc.
272
+ std::vector<bool > isExported (numFunctions);
275
273
for (auto & curr : module ->exports ) {
276
274
if (curr->kind == ExternalKind::Function) {
277
- hasUnseenCalls [indexes[*curr->getInternalName ()]] = true ;
275
+ isExported [indexes[*curr->getInternalName ()]] = true ;
278
276
}
279
277
}
280
278
@@ -318,38 +316,48 @@ struct DAE : public Pass {
318
316
if (func->imported ()) {
319
317
continue ;
320
318
}
321
- // We can only optimize if we see all the calls and can modify them .
322
- if (hasUnseenCalls [index]) {
319
+ // References prevent optimization .
320
+ if (hasRef [index]) {
323
321
continue ;
324
322
}
325
323
auto & calls = allCalls[index];
326
- if (calls.empty ()) {
324
+ if (calls.empty () && !isExported[index] ) {
327
325
// Nothing calls this, so it is not worth optimizing.
328
326
continue ;
329
327
}
330
- // Refine argument types before doing anything else. This does not
331
- // affect whether an argument is used or not, it just refines the type
332
- // where possible.
333
328
auto name = func->name ;
334
- if (refineArgumentTypes (func, calls, module , infoMap[name])) {
335
- worthOptimizing.insert (func);
336
- markStale (func->name );
329
+ // Exports prevent refining of argument types, as we don't see all the
330
+ // calls.
331
+ if (!isExported[index]) {
332
+ // Refine argument types before doing anything else. This does not
333
+ // affect whether an argument is used or not, it just refines the type
334
+ // where possible.
335
+ if (refineArgumentTypes (func, calls, module , infoMap[name])) {
336
+ worthOptimizing.insert (func);
337
+ markStale (func->name );
338
+ }
337
339
}
338
- // Refine return types as well.
339
- if (refineReturnTypes (func, calls, module )) {
340
+ // Refine return types as well. Note that exports do *not* prevent this!
341
+ // It is valid to export a function that returns something even more
342
+ // refined.
343
+ if (refineReturnTypes (func, calls, module , isExported[index])) {
340
344
refinedReturnTypes = true ;
341
345
markStale (name);
342
346
markCallersStale (calls);
343
347
}
344
- auto optimizedIndexes =
345
- ParamUtils::applyConstantValues ({func}, calls, {}, module );
346
- for (auto i : optimizedIndexes) {
347
- // Mark it as unused, which we know it now is (no point to re-scan just
348
- // for that).
349
- infoMap[name].unusedParams .insert (i);
350
- }
351
- if (!optimizedIndexes.empty ()) {
352
- markStale (func->name );
348
+ // Exports prevent applying of constant values, as we don't see all the
349
+ // calls.
350
+ if (!isExported[index]) {
351
+ auto optimizedIndexes =
352
+ ParamUtils::applyConstantValues ({func}, calls, {}, module );
353
+ for (auto i : optimizedIndexes) {
354
+ // Mark it as unused, which we know it now is (no point to re-scan
355
+ // just for that).
356
+ infoMap[name].unusedParams .insert (i);
357
+ }
358
+ if (!optimizedIndexes.empty ()) {
359
+ markStale (func->name );
360
+ }
353
361
}
354
362
}
355
363
if (refinedReturnTypes) {
@@ -364,7 +372,7 @@ struct DAE : public Pass {
364
372
if (func->imported ()) {
365
373
continue ;
366
374
}
367
- if (hasUnseenCalls [index]) {
375
+ if (hasRef[index] || isExported [index]) {
368
376
continue ;
369
377
}
370
378
auto numParams = func->getNumParams ();
@@ -401,7 +409,7 @@ struct DAE : public Pass {
401
409
if (func->getResults () == Type::none) {
402
410
continue ;
403
411
}
404
- if (hasUnseenCalls [index]) {
412
+ if (hasRef[index] || isExported [index]) {
405
413
continue ;
406
414
}
407
415
auto name = func->name ;
@@ -570,22 +578,59 @@ struct DAE : public Pass {
570
578
// the middle, etc.
571
579
bool refineReturnTypes (Function* func,
572
580
const std::vector<Call*>& calls,
573
- Module* module ) {
581
+ Module* module ,
582
+ bool isExported) {
583
+ if (isExported && !func->type .isOpen ()) {
584
+ // We must subtype the current type, so that imports of it work, but it is
585
+ // closed.
586
+ return false ;
587
+ }
588
+
574
589
auto lub = LUB::getResultsLUB (func, *module );
575
590
if (!lub.noted ()) {
576
591
return false ;
577
592
}
578
593
auto newType = lub.getLUB ();
579
- if (newType != func->getResults ()) {
594
+ if (newType == func->getResults ()) {
595
+ return false ;
596
+ }
597
+
598
+ // If this is exported, we cannot refine to an exact type without the
599
+ // custom descriptors feature being enabled.
600
+ if (isExported && !module ->features .hasCustomDescriptors ()) {
601
+ // Remove exactness.
602
+ std::vector<Type> inexact;
603
+ for (auto t : newType) {
604
+ inexact.push_back (t.isRef () ? t.with (Inexact) : t);
605
+ }
606
+ newType = Type (inexact);
607
+ if (newType == func->getResults ()) {
608
+ return false ;
609
+ }
610
+ }
611
+
612
+ if (!isExported) {
580
613
func->setResults (newType);
581
- for (auto * call : calls) {
582
- if (call->type != Type::unreachable) {
583
- call->type = newType;
584
- }
614
+ } else {
615
+ // We must explicitly subtype the old type.
616
+ TypeBuilder builder (1 );
617
+ builder[0 ] = Signature (func->getParams (), newType);
618
+ builder[0 ].subTypeOf (func->type );
619
+ // Make this subtype open like the super. This is not necessary, but might
620
+ // allow more work later after other changes, in theory.
621
+ builder[0 ].setOpen ();
622
+ auto result = builder.build ();
623
+ assert (!result.getError ());
624
+ func->type = (*result)[0 ];
625
+ }
626
+
627
+ for (auto * call : calls) {
628
+ if (call->type != Type::unreachable) {
629
+ call->type = newType;
585
630
}
586
- return true ;
587
631
}
588
- return false ;
632
+
633
+ return true ;
589
634
}
590
635
};
591
636
0 commit comments