2323
2424namespace wasm {
2525
26- // Analyze various possible effects.
27-
28- class EffectAnalyzer {
29- public:
30- EffectAnalyzer (const PassOptions& passOptions, Module& module )
31- : ignoreImplicitTraps(passOptions.ignoreImplicitTraps),
32- trapsNeverHappen (passOptions.trapsNeverHappen),
33- funcEffectsMap(passOptions.funcEffectsMap), module(module ),
34- features(module .features) {}
35-
36- EffectAnalyzer (const PassOptions& passOptions,
37- Module& module ,
38- Expression* ast)
39- : EffectAnalyzer(passOptions, module ) {
40- walk (ast);
41- }
42-
43- EffectAnalyzer (const PassOptions& passOptions, Module& module , Function* func)
44- : EffectAnalyzer(passOptions, module ) {
45- walk (func);
46- }
47-
48- bool ignoreImplicitTraps;
26+ // Track various possible effects.
27+ struct EffectSet {
28+ // Options that affect effect tracking
4929 bool trapsNeverHappen;
50- std::shared_ptr<FuncEffectsMap> funcEffectsMap;
51- Module& module ;
52- FeatureSet features;
5330
54- // Walk an expression and all its children.
55- void walk (Expression* ast) {
56- InternalAnalyzer (*this ).walk (ast);
57- post ();
58- }
59-
60- // Visit an expression, without any children.
61- void visit (Expression* ast) {
62- InternalAnalyzer (*this ).visit (ast);
63- post ();
64- }
65-
66- // Walk an entire function body. This will ignore effects that are not
67- // noticeable from the perspective of the caller, that is, effects that are
68- // only noticeable during the call, but "vanish" when the call stack is
69- // unwound.
70- void walk (Function* func) {
71- walk (func->body );
72-
73- // Effects of return-called functions will be visible to the caller.
74- if (hasReturnCallThrow) {
75- throws_ = true ;
76- }
77-
78- // We can ignore branching out of the function body - this can only be
79- // a return, and that is only noticeable in the function, not outside.
80- branchesOut = false ;
81-
82- // When the function exits, changes to locals cannot be noticed any more.
83- localsWritten.clear ();
84- localsRead.clear ();
85- }
31+ EffectSet (bool trapsNeverHappen) : trapsNeverHappen(trapsNeverHappen) {}
8632
8733 // Core effect tracking
8834
@@ -148,15 +94,8 @@ class EffectAnalyzer {
14894 // or a continuation that is never continued, are examples of that.
14995 bool mayNotReturn = false ;
15096
151- // Return calls are indistinguishable from normal returns from the perspective
152- // of their surrounding code, and the return-callee's effects only become
153- // visible when considering the effects of the whole function containing the
154- // return call. To model this correctly, stash the callee's effects on the
155- // side and only merge them in after walking a full function body.
156- //
157- // We currently do this stashing only for the throw effect, but in principle
158- // we could do it for all effects if it made a difference.
159- bool hasReturnCallThrow = false ;
97+ std::set<Name> breakTargets;
98+ std::set<Name> delegateTargets;
16099
161100 // Helper functions to check for various effect types
162101
@@ -265,7 +204,7 @@ class EffectAnalyzer {
265204 // example we can't reorder A and B if B traps, but in the first example we
266205 // can reorder them even if B traps (even if A has a global effect like a
267206 // global.set, since we assume B does not trap in traps-never-happen).
268- bool invalidates (const EffectAnalyzer & other) {
207+ bool invalidates (const EffectSet & other) {
269208 if ((transfersControlFlow () && other.hasSideEffects ()) ||
270209 (other.transfersControlFlow () && hasSideEffects ()) ||
271210 ((writesMemory || calls) && other.accessesMemory ()) ||
@@ -332,7 +271,7 @@ class EffectAnalyzer {
332271 return false ;
333272 }
334273
335- void mergeIn (const EffectAnalyzer & other) {
274+ void mergeIn (const EffectSet & other) {
336275 branchesOut = branchesOut || other.branchesOut ;
337276 calls = calls || other.calls ;
338277 readsMemory = readsMemory || other.readsMemory ;
@@ -368,6 +307,70 @@ class EffectAnalyzer {
368307 delegateTargets.insert (i);
369308 }
370309 }
310+ };
311+
312+ // Analyze various possible effects.
313+
314+ struct EffectAnalyzer : EffectSet {
315+ EffectAnalyzer (const PassOptions& passOptions, Module& module )
316+ : EffectSet(passOptions.trapsNeverHappen),
317+ ignoreImplicitTraps (passOptions.ignoreImplicitTraps),
318+ funcEffectsMap(passOptions.funcEffectsMap), module(module ),
319+ features(module .features), returnCallEffects(*this ) {}
320+
321+ EffectAnalyzer (const PassOptions& passOptions,
322+ Module& module ,
323+ Expression* ast)
324+ : EffectAnalyzer(passOptions, module ) {
325+ walk (ast);
326+ }
327+
328+ EffectAnalyzer (const PassOptions& passOptions, Module& module , Function* func)
329+ : EffectAnalyzer(passOptions, module ) {
330+ walk (func);
331+ }
332+
333+ bool ignoreImplicitTraps;
334+ std::shared_ptr<FuncEffectsMap> funcEffectsMap;
335+ Module& module ;
336+ FeatureSet features;
337+
338+ // Return calls appear no different than normal returns to local surrounding
339+ // code, but the effects of the return-called functions are visible to
340+ // callers. Collect effects of return-called functions here and merge them in
341+ // only when analyzing an entire function.
342+ EffectSet returnCallEffects;
343+
344+ // Walk an expression and all its children.
345+ void walk (Expression* ast) {
346+ InternalAnalyzer (*this ).walk (ast);
347+ post ();
348+ }
349+
350+ // Visit an expression, without any children.
351+ void visit (Expression* ast) {
352+ InternalAnalyzer (*this ).visit (ast);
353+ post ();
354+ }
355+
356+ // Walk an entire function body. This will ignore effects that are not
357+ // noticeable from the perspective of the caller, that is, effects that are
358+ // only noticeable during the call, but "vanish" when the call stack is
359+ // unwound.
360+ void walk (Function* func) {
361+ walk (func->body );
362+
363+ // Effects of return-called functions will be visible to the caller.
364+ mergeIn (returnCallEffects);
365+
366+ // We can ignore branching out of the function body - this can only be
367+ // a return, and that is only noticeable in the function, not outside.
368+ branchesOut = false ;
369+
370+ // When the function exits, changes to locals cannot be noticed any more.
371+ localsWritten.clear ();
372+ localsRead.clear ();
373+ }
371374
372375 // the checks above happen after the node's children were processed, in the
373376 // order of execution we must also check for control flow that happens before
@@ -388,9 +391,6 @@ class EffectAnalyzer {
388391 return hasAnything ();
389392 }
390393
391- std::set<Name> breakTargets;
392- std::set<Name> delegateTargets;
393-
394394private:
395395 struct InternalAnalyzer
396396 : public PostWalker<InternalAnalyzer, OverriddenVisitor<InternalAnalyzer>> {
@@ -491,51 +491,44 @@ class EffectAnalyzer {
491491
492492 if (curr->isReturn ) {
493493 parent.branchesOut = true ;
494- // When EH is enabled, any call can throw.
495- if (parent.features .hasExceptionHandling () &&
496- (!targetEffects || targetEffects->throws ())) {
497- parent.hasReturnCallThrow = true ;
498- }
499494 }
500495
496+ auto & effects = curr->isReturn ? parent.returnCallEffects : parent;
497+
501498 if (targetEffects) {
502499 // We have effect information for this call target, and can just use
503- // that. The one change we may want to make is to remove throws_, if the
504- // target function throws and we know that will be caught anyhow, the
505- // same as the code below for the general path. We can always filter out
506- // throws for return calls because they are already more precisely
507- // captured by `hasReturnCallThrow` .
508- if (targetEffects->throws_ && ( parent.tryDepth > 0 || curr->isReturn ) ) {
500+ // that. The one change we may want to make is to remove throws_, if
501+ // the target function throws and we know that will be caught anyhow,
502+ // the same as the code below for the general path. We can't filter
503+ // out throws on return calls because they will return out of the
504+ // handlers before making the call .
505+ if (targetEffects->throws_ && parent.tryDepth > 0 && ! curr->isReturn ) {
509506 auto filteredEffects = *targetEffects;
510507 filteredEffects.throws_ = false ;
511- parent .mergeIn (filteredEffects);
508+ effects .mergeIn (filteredEffects);
512509 } else {
513510 // Just merge in all the effects.
514- parent .mergeIn (*targetEffects);
511+ effects .mergeIn (*targetEffects);
515512 }
516513 return ;
517514 }
518515
519- parent.calls = true ;
520- // When EH is enabled, any call can throw. Skip this for return calls
521- // because the throw is already more precisely captured by
522- // `hasReturnCallThrow`.
523- if (parent.features .hasExceptionHandling () && parent.tryDepth == 0 &&
524- !curr->isReturn ) {
525- parent.throws_ = true ;
516+ effects.calls = true ;
517+ // When EH is enabled, any call can throw.
518+ if (parent.features .hasExceptionHandling () &&
519+ (parent.tryDepth == 0 || curr->isReturn )) {
520+ effects.throws_ = true ;
526521 }
527522 }
528523 void visitCallIndirect (CallIndirect* curr) {
529- parent.calls = true ;
530524 if (curr->isReturn ) {
531525 parent.branchesOut = true ;
532- if (parent.features .hasExceptionHandling ()) {
533- parent.hasReturnCallThrow = true ;
534- }
535526 }
527+ auto & effects = curr->isReturn ? parent.returnCallEffects : parent;
528+ effects.calls = true ;
536529 if (parent.features .hasExceptionHandling () &&
537- (parent.tryDepth == 0 && ! curr->isReturn )) {
538- parent .throws_ = true ;
530+ (parent.tryDepth == 0 || curr->isReturn )) {
531+ effects .throws_ = true ;
539532 }
540533 }
541534 void visitLocalGet (LocalGet* curr) {
@@ -780,9 +773,6 @@ class EffectAnalyzer {
780773 void visitCallRef (CallRef* curr) {
781774 if (curr->isReturn ) {
782775 parent.branchesOut = true ;
783- if (parent.features .hasExceptionHandling ()) {
784- parent.hasReturnCallThrow = true ;
785- }
786776 }
787777 if (curr->target ->type .isNull ()) {
788778 parent.trap = true ;
@@ -793,10 +783,11 @@ class EffectAnalyzer {
793783 parent.implicitTrap = true ;
794784 }
795785
796- parent.calls = true ;
786+ auto & effects = curr->isReturn ? parent.returnCallEffects : parent;
787+ effects.calls = true ;
797788 if (parent.features .hasExceptionHandling () &&
798789 (parent.tryDepth == 0 || curr->isReturn )) {
799- parent .throws_ = true ;
790+ effects .throws_ = true ;
800791 }
801792 }
802793 void visitRefTest (RefTest* curr) {}
0 commit comments