diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ir/effects.h | 47 | ||||
-rw-r--r-- | src/pass.h | 19 |
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 |