Skip to content

Commit 503afcf

Browse files
committed
Update effects.h to handle return calls correctly
As far as their surrounding code is concerned return calls are no different from normal returns. It's only from a caller's perspective that a function containing a return call also has the effects of the return-callee. To model this more precisely in EffectAnalyzer, stash the effects of return-callees on the side and only merge them in at the end when analyzing the effects of a full function body.
1 parent 668f7ec commit 503afcf

File tree

1 file changed

+81
-31
lines changed

1 file changed

+81
-31
lines changed

src/ir/effects.h

Lines changed: 81 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,30 @@ class EffectAnalyzer {
5151
Module& module;
5252
FeatureSet features;
5353

54+
// Return calls appear no different than normal returns to local surrounding
55+
// code, but the effects of the return-called functions are visible to
56+
// callers. Collect effects of return-called functions here and merge them in
57+
// only when analyzing an entire function.
58+
//
59+
// Go through all this boilerplate to define copy constructors for this one
60+
// field so we don't have to explicitly define a copy constructor for the
61+
// entire EffectAnalyzer.
62+
struct ReturnCallEffectPtr : std::unique_ptr<EffectAnalyzer> {
63+
ReturnCallEffectPtr() = default;
64+
ReturnCallEffectPtr(Module& module)
65+
: std::unique_ptr<EffectAnalyzer>(
66+
std::make_unique<EffectAnalyzer>(PassOptions{}, module)) {}
67+
ReturnCallEffectPtr(const ReturnCallEffectPtr& other)
68+
: ReturnCallEffectPtr() {}
69+
ReturnCallEffectPtr(ReturnCallEffectPtr&& other) = default;
70+
ReturnCallEffectPtr& operator=(const ReturnCallEffectPtr& other) {
71+
*((std::unique_ptr<EffectAnalyzer>*)this) = nullptr;
72+
return *this;
73+
}
74+
ReturnCallEffectPtr& operator=(ReturnCallEffectPtr&& other) = default;
75+
};
76+
ReturnCallEffectPtr returnCallEffects;
77+
5478
// Walk an expression and all its children.
5579
void walk(Expression* ast) {
5680
InternalAnalyzer(*this).walk(ast);
@@ -70,6 +94,11 @@ class EffectAnalyzer {
7094
void walk(Function* func) {
7195
walk(func->body);
7296

97+
// Effects of return-called functions will be visible to the caller.
98+
if (returnCallEffects) {
99+
mergeIn(*returnCallEffects);
100+
}
101+
73102
// We can ignore branching out of the function body - this can only be
74103
// a return, and that is only noticeable in the function, not outside.
75104
branchesOut = false;
@@ -354,6 +383,13 @@ class EffectAnalyzer {
354383
}
355384
}
356385

386+
EffectAnalyzer& getReturnCallEffects() {
387+
if (!returnCallEffects) {
388+
returnCallEffects = ReturnCallEffectPtr(module);
389+
}
390+
return *returnCallEffects;
391+
}
392+
357393
// the checks above happen after the node's children were processed, in the
358394
// order of execution we must also check for control flow that happens before
359395
// the children, i.e., loops
@@ -466,44 +502,55 @@ class EffectAnalyzer {
466502
return;
467503
}
468504

505+
const EffectAnalyzer* targetEffects = nullptr;
506+
if (parent.funcEffectsMap) {
507+
auto iter = parent.funcEffectsMap->find(curr->target);
508+
if (iter != parent.funcEffectsMap->end()) {
509+
targetEffects = &iter->second;
510+
}
511+
}
512+
469513
if (curr->isReturn) {
470514
parent.branchesOut = true;
471515
}
472516

473-
if (parent.funcEffectsMap) {
474-
auto iter = parent.funcEffectsMap->find(curr->target);
475-
if (iter != parent.funcEffectsMap->end()) {
476-
// We have effect information for this call target, and can just use
477-
// that. The one change we may want to make is to remove throws_, if
478-
// the target function throws and we know that will be caught anyhow,
479-
// the same as the code below for the general path.
480-
const auto& targetEffects = iter->second;
481-
if (targetEffects.throws_ && parent.tryDepth > 0) {
482-
auto filteredEffects = targetEffects;
483-
filteredEffects.throws_ = false;
484-
parent.mergeIn(filteredEffects);
485-
} else {
486-
// Just merge in all the effects.
487-
parent.mergeIn(targetEffects);
488-
}
489-
return;
517+
auto& effects = curr->isReturn ? parent.getReturnCallEffects() : parent;
518+
519+
if (targetEffects) {
520+
// We have effect information for this call target, and can just use
521+
// that. The one change we may want to make is to remove throws_, if
522+
// the target function throws and we know that will be caught anyhow,
523+
// the same as the code below for the general path. We can't filter
524+
// out throws on return calls because they will return out of the
525+
// handlers before making the call.
526+
if (targetEffects->throws_ && parent.tryDepth > 0 && !curr->isReturn) {
527+
auto filteredEffects = *targetEffects;
528+
filteredEffects.throws_ = false;
529+
effects.mergeIn(filteredEffects);
530+
} else {
531+
// Just merge in all the effects.
532+
effects.mergeIn(*targetEffects);
490533
}
534+
return;
491535
}
492536

493-
parent.calls = true;
537+
effects.calls = true;
494538
// When EH is enabled, any call can throw.
495-
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
496-
parent.throws_ = true;
539+
if (parent.features.hasExceptionHandling() &&
540+
(parent.tryDepth == 0 || curr->isReturn)) {
541+
effects.throws_ = true;
497542
}
498543
}
499544
void visitCallIndirect(CallIndirect* curr) {
500-
parent.calls = true;
501-
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
502-
parent.throws_ = true;
503-
}
504545
if (curr->isReturn) {
505546
parent.branchesOut = true;
506547
}
548+
auto& effects = curr->isReturn ? parent.getReturnCallEffects() : parent;
549+
effects.calls = true;
550+
if (parent.features.hasExceptionHandling() &&
551+
(parent.tryDepth == 0 || curr->isReturn)) {
552+
effects.throws_ = true;
553+
}
507554
}
508555
void visitLocalGet(LocalGet* curr) {
509556
parent.localsRead.insert(curr->index);
@@ -745,21 +792,24 @@ class EffectAnalyzer {
745792
}
746793
}
747794
void visitCallRef(CallRef* curr) {
795+
if (curr->isReturn) {
796+
parent.branchesOut = true;
797+
}
748798
if (curr->target->type.isNull()) {
749799
parent.trap = true;
750800
return;
751801
}
752-
parent.calls = true;
753-
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
754-
parent.throws_ = true;
755-
}
756-
if (curr->isReturn) {
757-
parent.branchesOut = true;
758-
}
759802
// traps when the call target is null
760803
if (curr->target->type.isNullable()) {
761804
parent.implicitTrap = true;
762805
}
806+
807+
auto& effects = curr->isReturn ? parent.getReturnCallEffects() : parent;
808+
effects.calls = true;
809+
if (parent.features.hasExceptionHandling() &&
810+
(parent.tryDepth == 0 || curr->isReturn)) {
811+
effects.throws_ = true;
812+
}
763813
}
764814
void visitRefTest(RefTest* curr) {}
765815
void visitRefCast(RefCast* curr) {

0 commit comments

Comments
 (0)