summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ir/effects.h47
-rw-r--r--src/pass.h19
2 files changed, 40 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); }
diff --git a/src/pass.h b/src/pass.h
index 041b80321..623c1f971 100644
--- a/src/pass.h
+++ b/src/pass.h
@@ -145,6 +145,25 @@ struct PassOptions {
// neither of those can happen (and it is undefined behavior if they do).
//
// TODO: deprecate and remove ignoreImplicitTraps.
+ //
+ // Since trapsNeverHappen assumes a trap is never reached, it can in principle
+ // remove code like this:
+ //
+ // (i32.store ..)
+ // (unreachable)
+ //
+ // The trap isn't reached, by assumption, and if we reach the store then we'd
+ // reach the trap, so we can assume that isn't reached either, and TNH can
+ // remove both. We do have a specific limitation here, however, which is that
+ // trapsNeverHappen cannot remove calls to *imports*. We assume that an import
+ // might do things we cannot understand, so we never eliminate it. For
+ // example, in LLVM output we might see this:
+ //
+ // (call $abort) ;; a noreturn import - the process is halted with an error
+ // (unreachable)
+ //
+ // That trap is never actually reached since the abort halts execution. In TNH
+ // we can remove the trap but not the call right before it.
bool trapsNeverHappen = false;
// Optimize assuming that the low 1K of memory is not valid memory for the
// application to use. In that case, we can optimize load/store offsets in