summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2023-06-13 12:10:18 -0700
committerGitHub <noreply@github.com>2023-06-13 12:10:18 -0700
commit00211a90878cf0f37fbfb124332c4abc9863032b (patch)
treef7e460fe8eeebebc434d8e349baa551abbbe1207 /src
parent491f596fd13cdba5b4230ebf5a5c076bcafdab6f (diff)
downloadbinaryen-00211a90878cf0f37fbfb124332c4abc9863032b.tar.gz
binaryen-00211a90878cf0f37fbfb124332c4abc9863032b.tar.bz2
binaryen-00211a90878cf0f37fbfb124332c4abc9863032b.zip
EffectAnalyzer: Assume we execute the two things whose effects we compare (#5764)
EffectAnalyzer::canReorder/invalidate now assume that the things from whom we generated the effects both execute (or, rather, that if the first of them doesn't transfer control flow then they execute). If they both execute then we can do more work in TrapsNeverHappen mode, since we can then reorder this for example: (global.set ..) (i32.load ..) The load may trap, but in TNH mode we assume it won't. So we can reorder those two. However, if they did not both execute then we could be in this situation: (global.set ..) (br_if ..) (i32.load) Reordering the load and the set here would be invalid, because we could make the load execute when it didn't execute before, and it could now start to actually trap at runtime. This new assumption seems obvious, since we don't compare the effects of things unless they are adjacent and with no control flow between them - otherwise, why compare them? To be sure, I manually reviewed every single use of EffectAnalyzer::canReorder/invalidate in the entire codebase. I've also been fuzzing this for several days (hundreds of thousands of iterations), and have not seen any problem. This was motivated by seeing that #5744 should be able to do more work in TNH mode, but it wasn't. New tests show the benefits there in OptimizeCasts as well as in SimplifyLocals.
Diffstat (limited to 'src')
-rw-r--r--src/ir/effects.h51
1 files changed, 45 insertions, 6 deletions
diff --git a/src/ir/effects.h b/src/ir/effects.h
index 29a6375ad..5670e3ef6 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -217,8 +217,39 @@ public:
// check if we break to anything external from ourselves
bool hasExternalBreakTargets() const { return !breakTargets.empty(); }
- // checks if these effects would invalidate another set (e.g., if we write, we
- // invalidate someone that reads, they can't be moved past us)
+ // Checks if these effects would invalidate another set of effects (e.g., if
+ // we write, we invalidate someone that reads).
+ //
+ // This assumes the things whose effects we are comparing will both execute,
+ // at least if neither of them transfers control flow away. That is, we assume
+ // that there is no transfer of control flow *between* them: we are comparing
+ // things appear after each other, perhaps with some other code in the middle,
+ // but that code does not transfer control flow. It is not valid to call this
+ // method in other situations, like this:
+ //
+ // A
+ // (br_if 0 (local.get 0)) ;; this may transfer control flow away
+ // B
+ //
+ // Calling this method in that situation is invalid because only A may
+ // execute and not B. The following are examples of situations where it is
+ // valid to call this method:
+ //
+ // A
+ // ;; nothing in between them at all
+ // B
+ //
+ // A
+ // (local.set 0 (i32.const 0)) ;; something in between without a possible
+ // ;; control flow transfer
+ // B
+ //
+ // That the things being compared both execute only matters in the case of
+ // traps-never-happen: in that mode we can move traps but only if doing so
+ // would not make them start to appear when they did not. In the second
+ // example we can't reorder A and B if B traps, but in the first example we
+ // can reorder them even if B traps (even if A has a global effect like a
+ // global.set, since we assume B does not trap in traps-never-happen).
bool invalidates(const EffectAnalyzer& other) {
if ((transfersControlFlow() && other.hasSideEffects()) ||
(other.transfersControlFlow() && hasSideEffects()) ||
@@ -271,10 +302,17 @@ public:
// anything.
assert(!((trap && other.throws()) || (throws() && other.trap)));
// We can't reorder an implicit trap in a way that could alter what global
- // state is modified.
- if ((trap && other.writesGlobalState()) ||
- (other.trap && writesGlobalState())) {
- return true;
+ // state is modified. However, in trapsNeverHappen mode we assume traps do
+ // not occur in practice, which lets us ignore this, at least in the case
+ // that the code executes. As mentioned above, we assume that there is no
+ // transfer of control flow between the things we are comparing, so all we
+ // need to do is check for such transfers in them.
+ if (!trapsNeverHappen || transfersControlFlow() ||
+ other.transfersControlFlow()) {
+ if ((trap && other.writesGlobalState()) ||
+ (other.trap && writesGlobalState())) {
+ return true;
+ }
}
return false;
}
@@ -921,6 +959,7 @@ private:
public:
// Helpers
+ // See comment on invalidate() for the assumptions on the inputs here.
static bool canReorder(const PassOptions& passOptions,
Module& module,
Expression* a,