summaryrefslogtreecommitdiff
path: root/src/ir/effects.h
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2022-09-16 14:11:25 -0700
committerGitHub <noreply@github.com>2022-09-16 21:11:25 +0000
commit4e24fdfd74347dc40ba32efee6b154e6ec9827c0 (patch)
tree42413adab127314c4b0bd5035db4aae55359ff08 /src/ir/effects.h
parent241dee74dd8e58e166a4aa64c15e0f71ed1819bf (diff)
downloadbinaryen-4e24fdfd74347dc40ba32efee6b154e6ec9827c0.tar.gz
binaryen-4e24fdfd74347dc40ba32efee6b154e6ec9827c0.tar.bz2
binaryen-4e24fdfd74347dc40ba32efee6b154e6ec9827c0.zip
Effects: Clarify trap effect meaning, and consider infinite loops to trap due to timeout (#5039)
I think this simplifies the logic behind what we consider to trap. Before we had kind of a hack in visitLoop that now has a more clear reasoning behind it: we consider as trapping things that trap in all VMs all the time, or will eventually. So a single allocation doesn't trap, but an unbounded amount can, and an infinite loop is considered to trap as well (a timeout in a VM will be hit eventually, somehow). This means we cannot optimize way a trivial infinite loop with no effects in it, while (1) {} But we can optimize it out in trapsNeverHappen mode. In any event, such a loop is not a realistic situation; an infinite loop with some other effect in it, like a call to an import, will not be optimized out, of course. Also clarify some other things regarding traps and trapsNeverHappen following recent discussions in https://github.com/emscripten-core/emscripten/issues/17732 Specifically, TNH will never be allowed to remove calls to imports.
Diffstat (limited to 'src/ir/effects.h')
-rw-r--r--src/ir/effects.h47
1 files changed, 21 insertions, 26 deletions
diff --git a/src/ir/effects.h b/src/ir/effects.h
index 5578b3b3b..aa8a458d0 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -90,18 +90,20 @@ public:
// each other, but it is not ok to remove them or reorder them with other
// effects in a noticeable way.
//
- // Note also that we ignore runtime-dependent traps, such as hitting a
- // recursion limit or running out of memory. Such traps are not part of wasm's
- // official semantics, and they can occur anywhere: *any* instruction could in
- // theory be implemented by a VM call (as will be the case when running in an
- // interpreter), and such a call could run out of stack or memory in
- // principle. To put it another way, an i32 division by zero is the program
- // doing something bad that causes a trap, but the VM running out of memory is
- // the VM doing something bad - and therefore the VM behaving in a way that is
- // not according to the wasm semantics - and we do not model such things. Note
- // that as a result we do *not* mark things like GC allocation instructions as
- // having side effects, which has the nice benefit of making it possible to
- // eliminate an allocation whose result is not captured.
+ // Note also that we ignore *optional* runtime-specific traps: we only
+ // consider as trapping something that will trap in *all* VMs, and *all* the
+ // time. For example, a single allocation might trap in a VM in a particular
+ // execution, if it happens to run out of memory just there, but that is not
+ // enough for us to mark it as having a trap effect. (Note that not marking
+ // each allocation as possibly trapping has the nice benefit of making it
+ // possible to eliminate an allocation whose result is not captured.) OTOH, we
+ // *do* mark a potentially infinite number of allocations as trapping, as all
+ // VMs would trap eventually, and the same for potentially infinite recursion,
+ // etc.
+ // * We assume that VMs will timeout eventually, so any loop that we cannot
+ // prove terminates is considered to trap. (Some VMs might not have
+ // such timeouts, but even they will error before the heat death of the
+ // universe, which is a kind of trap.)
bool trap = false;
// A trap from an instruction like a load or div/rem, which may trap on corner
// cases. If we do not ignore implicit traps then these are counted as a trap.
@@ -394,20 +396,13 @@ private:
}
void visitIf(If* curr) {}
void visitLoop(Loop* curr) {
- if (curr->name.is()) {
- parent.breakTargets.erase(curr->name); // these were internal breaks
- }
- // if the loop is unreachable, then there is branching control flow:
- // (1) if the body is unreachable because of a (return), uncaught (br)
- // etc., then we already noted branching, so it is ok to mark it
- // again (if we have *caught* (br)s, then they did not lead to the
- // loop body being unreachable). (same logic applies to blocks)
- // (2) if the loop is unreachable because it only has branches up to the
- // loop top, but no way to get out, then it is an infinite loop, and
- // we consider that a branching side effect (note how the same logic
- // does not apply to blocks).
- if (curr->type == Type::unreachable) {
- parent.branchesOut = true;
+ if (curr->name.is() && parent.breakTargets.erase(curr->name) > 0) {
+ // Breaks to this loop exist, which we just removed as they do not have
+ // further effect outside of this loop. One additional thing we need to
+ // take into account is infinite looping, which is a noticeable side
+ // effect we can't normally remove - eventually the VM will time out and
+ // error (see more details in the comment on trapping above).
+ parent.implicitTrap = true;
}
}
void visitBreak(Break* curr) { parent.breakTargets.insert(curr->name); }