5050#include " passes/opt-utils.h"
5151#include " support/sorted_vector.h"
5252#include " wasm-builder.h"
53- #include " wasm-type.h"
5453#include " wasm.h"
5554
5655namespace wasm {
@@ -77,9 +76,12 @@ struct DAEFunctionInfo {
7776 // removed as well.
7877 bool hasTailCalls = false ;
7978 std::unordered_set<Name> tailCallees;
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;
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;
8385
8486 // Clears all data, which marks us as stale and in need of recomputation.
8587 void clear () { *this = DAEFunctionInfo (); }
@@ -141,9 +143,12 @@ struct DAEScanner
141143 // the infoMap).
142144 auto * currInfo = info ? info : &(*infoMap)[Name ()];
143145
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.
144149 // TODO: look for actual escaping?
145150 // TODO: create a thunk for external uses that allow internal optimizations
146- currInfo->hasRef .insert (curr->func );
151+ currInfo->hasUnseenCalls .insert (curr->func );
147152 }
148153
149154 // main entry point
@@ -243,7 +248,7 @@ struct DAE : public Pass {
243248
244249 std::vector<std::vector<Call*>> allCalls (numFunctions);
245250 std::vector<bool > tailCallees (numFunctions);
246- std::vector<bool > hasRef (numFunctions);
251+ std::vector<bool > hasUnseenCalls (numFunctions);
247252
248253 // Track the function in which relevant expressions exist. When we modify
249254 // those expressions we will need to mark the function's info as stale.
@@ -262,17 +267,14 @@ struct DAE : public Pass {
262267 for (auto & [call, dropp] : info.droppedCalls ) {
263268 allDroppedCalls[call] = dropp;
264269 }
265- for (auto & name : info.hasRef ) {
266- hasRef [indexes[name]] = true ;
270+ for (auto & name : info.hasUnseenCalls ) {
271+ hasUnseenCalls [indexes[name]] = true ;
267272 }
268273 }
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);
274+ // Exports are considered unseen calls.
273275 for (auto & curr : module ->exports ) {
274276 if (curr->kind == ExternalKind::Function) {
275- isExported [indexes[*curr->getInternalName ()]] = true ;
277+ hasUnseenCalls [indexes[*curr->getInternalName ()]] = true ;
276278 }
277279 }
278280
@@ -316,48 +318,38 @@ struct DAE : public Pass {
316318 if (func->imported ()) {
317319 continue ;
318320 }
319- // References prevent optimization .
320- if (hasRef [index]) {
321+ // We can only optimize if we see all the calls and can modify them .
322+ if (hasUnseenCalls [index]) {
321323 continue ;
322324 }
323325 auto & calls = allCalls[index];
324- if (calls.empty () && !isExported[index] ) {
326+ if (calls.empty ()) {
325327 // Nothing calls this, so it is not worth optimizing.
326328 continue ;
327329 }
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.
328333 auto name = 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- }
334+ if (refineArgumentTypes (func, calls, module , infoMap[name])) {
335+ worthOptimizing.insert (func);
336+ markStale (func->name );
339337 }
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])) {
338+ // Refine return types as well.
339+ if (refineReturnTypes (func, calls, module )) {
344340 refinedReturnTypes = true ;
345341 markStale (name);
346342 markCallersStale (calls);
347343 }
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- }
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 );
361353 }
362354 }
363355 if (refinedReturnTypes) {
@@ -372,7 +364,7 @@ struct DAE : public Pass {
372364 if (func->imported ()) {
373365 continue ;
374366 }
375- if (hasRef[index] || isExported [index]) {
367+ if (hasUnseenCalls [index]) {
376368 continue ;
377369 }
378370 auto numParams = func->getNumParams ();
@@ -409,7 +401,7 @@ struct DAE : public Pass {
409401 if (func->getResults () == Type::none) {
410402 continue ;
411403 }
412- if (hasRef[index] || isExported [index]) {
404+ if (hasUnseenCalls [index]) {
413405 continue ;
414406 }
415407 auto name = func->name ;
@@ -578,59 +570,22 @@ struct DAE : public Pass {
578570 // the middle, etc.
579571 bool refineReturnTypes (Function* func,
580572 const std::vector<Call*>& calls,
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-
573+ Module* module ) {
589574 auto lub = LUB::getResultsLUB (func, *module );
590575 if (!lub.noted ()) {
591576 return false ;
592577 }
593578 auto newType = lub.getLUB ();
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) {
579+ if (newType != func->getResults ()) {
613580 func->setResults (newType);
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;
581+ for (auto * call : calls) {
582+ if (call->type != Type::unreachable) {
583+ call->type = newType;
584+ }
630585 }
586+ return true ;
631587 }
632-
633- return true ;
588+ return false ;
634589 }
635590};
636591
0 commit comments