Skip to content

Commit 40f26ee

Browse files
committed
Stash all effects inline in the effect analyzer
1 parent e88a86a commit 40f26ee

File tree

2 files changed

+95
-104
lines changed

2 files changed

+95
-104
lines changed

src/ir/effects.h

Lines changed: 94 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -23,66 +23,12 @@
2323

2424
namespace 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-
394394
private:
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) {}

src/pass.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ struct InliningOptions {
9898
};
9999

100100
// Forward declaration for FuncEffectsMap.
101-
class EffectAnalyzer;
101+
struct EffectAnalyzer;
102102

103103
using FuncEffectsMap = std::unordered_map<Name, EffectAnalyzer>;
104104

0 commit comments

Comments
 (0)