diff options
author | Alon Zakai <azakai@google.com> | 2023-06-13 12:10:18 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-13 12:10:18 -0700 |
commit | 00211a90878cf0f37fbfb124332c4abc9863032b (patch) | |
tree | f7e460fe8eeebebc434d8e349baa551abbbe1207 /src | |
parent | 491f596fd13cdba5b4230ebf5a5c076bcafdab6f (diff) | |
download | binaryen-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.h | 51 |
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, |