From 0ec11123706e4d313c4508c5c16318b68314325a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 12:49:31 -0700 Subject: [PATCH 001/120] work --- src/passes/ConstraintAnalysis.cpp | 56 +++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 5eb6a24fcf4..9e5b9b21dcc 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -187,7 +187,8 @@ struct ConstraintAnalysis } computeRelevantLocals(); - flow(); + flow(Mode::Normal); + flow(Mode::Loops); optimize(); } @@ -219,7 +220,58 @@ struct ConstraintAnalysis // Flow infos around until we have inferred all we can about the constraints // in each location. - void flow() { + // + // We flow in one of two modes: Normal, and Loops. Normal infers constraints + // in the most precise way that we can. Loops does an analysis that is worse + // in some ways, but allows us to handle loop variable overflows. For example: + // + // x = 0 + // do { + // print(x >= 0 & x < 100) + // x++ + // } while (x < 100) + // + // This prints true 100 times. We want to be able to infer the value sent to + // print(). The second part, x < 100, is trivial: at the loop top, either x == 0 + // from before the loop, or x < 100 from the loop backedge, and both prove + // x < 100. However x >= 0 is non-obvious: if x is *signed*, then we must rule + // out the possibility of it getting incremented so many times that it overflows + // and becomes negative. Proving that requires actually seeing that the loop + // variable x is incremented from 0 to 100, and no more, and that involves an + // interaction of the initial value, the increment, and the condition on the + // loop backedge. + // + // A naive approach is to just interpret this code. x starts as x == 0, then + // x++ means x == 1, then at the loop top we have x >= 0 && x < 1, and so + // forth - but this is not what we want! This would literally interpret the + // code 100 times, which is slow and will not work if the top bound is non- + // constant. So we must operate at a more abstract level. + // + // Even at an abstract level this is not trivial. We must consider three things, + // as mentioned above: the initial value, the bounds, and the increment, and how + // they connect. So this requires some kind of flow or graph analysis - we + // cannot do this as a peephole optimization. + // + // To handle this situation, we do a pessimistic span analysis, expanding the + // span of possible values eagerly, basically to what would "naturally" happen + // in a typical loop. For example, if we see x == 0 && x < 100 then we + // immediately expand this into [0, 100) (i.e., x >= 0 && x < 100), even though + // it is actually only x == 0. The specific rules we follow are: + // + // * x == 0 && x < C => x in [0, C) + // * x in [0, C) && x++ => x in [0, C+1) (we could increment the lower side, + // but this is not needed for loops, see below). + // * x in [0, C) || x == 0 => x in [0, C) + // + // This is enough to handle the above loop: it will quickly converge on x in + // [0, 100). Because we pessimistically expand spans, we are an upper bound on + // possible values, and we can then apply our findings to the main constraint + // analysis where useful - specifically, where x++ happens. + enum FlowMode { + Normal, + Loops + }; + void flow(FlowMode mode) { // Start from the entry as the only reachable block. That block has incoming // values - defaults - for each var. entry->contents.startConstraints.setReachable(); From 279c29f5589e2dd664300bd5e20a75c3c1ac71ed Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 12:49:36 -0700 Subject: [PATCH 002/120] work --- src/passes/ConstraintAnalysis.cpp | 36 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 9e5b9b21dcc..fd23956569a 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -229,17 +229,17 @@ struct ConstraintAnalysis // do { // print(x >= 0 & x < 100) // x++ - // } while (x < 100) + // } while (x < 100) // // This prints true 100 times. We want to be able to infer the value sent to - // print(). The second part, x < 100, is trivial: at the loop top, either x == 0 - // from before the loop, or x < 100 from the loop backedge, and both prove + // print(). The second part, x < 100, is trivial: at the loop top, either x == + // 0 from before the loop, or x < 100 from the loop backedge, and both prove // x < 100. However x >= 0 is non-obvious: if x is *signed*, then we must rule - // out the possibility of it getting incremented so many times that it overflows - // and becomes negative. Proving that requires actually seeing that the loop - // variable x is incremented from 0 to 100, and no more, and that involves an - // interaction of the initial value, the increment, and the condition on the - // loop backedge. + // out the possibility of it getting incremented so many times that it + // overflows and becomes negative. Proving that requires actually seeing that + // the loop variable x is incremented from 0 to 100, and no more, and that + // involves an interaction of the initial value, the increment, and the + // condition on the loop backedge. // // A naive approach is to just interpret this code. x starts as x == 0, then // x++ means x == 1, then at the loop top we have x >= 0 && x < 1, and so @@ -247,19 +247,20 @@ struct ConstraintAnalysis // code 100 times, which is slow and will not work if the top bound is non- // constant. So we must operate at a more abstract level. // - // Even at an abstract level this is not trivial. We must consider three things, - // as mentioned above: the initial value, the bounds, and the increment, and how - // they connect. So this requires some kind of flow or graph analysis - we - // cannot do this as a peephole optimization. + // Even at an abstract level this is not trivial. We must consider three + // things, as mentioned above: the initial value, the bounds, and the + // increment, and how they connect. So this requires some kind of flow or + // graph analysis - we cannot do this as a peephole optimization. // // To handle this situation, we do a pessimistic span analysis, expanding the // span of possible values eagerly, basically to what would "naturally" happen // in a typical loop. For example, if we see x == 0 && x < 100 then we - // immediately expand this into [0, 100) (i.e., x >= 0 && x < 100), even though - // it is actually only x == 0. The specific rules we follow are: + // immediately expand this into [0, 100) (i.e., x >= 0 && x < 100), even + // though it is actually only x == 0. The specific rules we follow are: // // * x == 0 && x < C => x in [0, C) - // * x in [0, C) && x++ => x in [0, C+1) (we could increment the lower side, + // * x in [0, C) && x++ => x in [0, C+1) (we could increment the lower + // side, // but this is not needed for loops, see below). // * x in [0, C) || x == 0 => x in [0, C) // @@ -267,10 +268,7 @@ struct ConstraintAnalysis // [0, 100). Because we pessimistically expand spans, we are an upper bound on // possible values, and we can then apply our findings to the main constraint // analysis where useful - specifically, where x++ happens. - enum FlowMode { - Normal, - Loops - }; + enum FlowMode { Normal, Loops }; void flow(FlowMode mode) { // Start from the entry as the only reachable block. That block has incoming // values - defaults - for each var. From f9ec4e9b270b5d8eed2fd2487cbdcde7e8f736ed Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 12:52:09 -0700 Subject: [PATCH 003/120] work --- src/passes/ConstraintAnalysis.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index fd23956569a..1a67f189995 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -244,8 +244,11 @@ struct ConstraintAnalysis // A naive approach is to just interpret this code. x starts as x == 0, then // x++ means x == 1, then at the loop top we have x >= 0 && x < 1, and so // forth - but this is not what we want! This would literally interpret the - // code 100 times, which is slow and will not work if the top bound is non- - // constant. So we must operate at a more abstract level. + // code 100 times. To avoid this in "Normal" mode, we do not do anything for + // x++ - we assume the value is unknown after the increment, so x does not go + // from 0 to 1 to 2 and so forth. + // + // In "Loops" mode, // // Even at an abstract level this is not trivial. We must consider three // things, as mentioned above: the initial value, the bounds, and the From 978217b4d637b26713d362a6a0cc0fc86a2fa310 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 13:10:21 -0700 Subject: [PATCH 004/120] work --- src/passes/ConstraintAnalysis.cpp | 42 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 1a67f189995..dc41f03a097 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -248,29 +248,29 @@ struct ConstraintAnalysis // x++ - we assume the value is unknown after the increment, so x does not go // from 0 to 1 to 2 and so forth. // - // In "Loops" mode, + // In "Loops" mode, we do the following things differently: // - // Even at an abstract level this is not trivial. We must consider three - // things, as mentioned above: the initial value, the bounds, and the - // increment, and how they connect. So this requires some kind of flow or - // graph analysis - we cannot do this as a peephole optimization. + // * x == 0, x++ => x == 1. This happens on the first loop iteration. + // * When we see the loop backedge x < 100, which would normally be ANDed on + // top of the value of x, we instead pessimistically extend the spane of + // values to everything that would be possible in an incrementing loop. + // Specifically: + // * x == 1 && x < 100 => x > 0 && x < 100 + // (If we did a normal AND, we would end up with x == 1 here, and the loop + // would then increment x to 2 and so forth.) + // * We then run through the loop again, now starting with + // x >= 0 && x < 100 (after we merge in the x == 0 from before the loop), + // and do this: + // * x >= 0 && x < 100, x++ => x > 0 && x <= 100 + // * x > 0 && x <= 100 && x < 100 => x > 0 && x < 100 + // No further changes occur, and this is the final stable state. // - // To handle this situation, we do a pessimistic span analysis, expanding the - // span of possible values eagerly, basically to what would "naturally" happen - // in a typical loop. For example, if we see x == 0 && x < 100 then we - // immediately expand this into [0, 100) (i.e., x >= 0 && x < 100), even - // though it is actually only x == 0. The specific rules we follow are: - // - // * x == 0 && x < C => x in [0, C) - // * x in [0, C) && x++ => x in [0, C+1) (we could increment the lower - // side, - // but this is not needed for loops, see below). - // * x in [0, C) || x == 0 => x in [0, C) - // - // This is enough to handle the above loop: it will quickly converge on x in - // [0, 100). Because we pessimistically expand spans, we are an upper bound on - // possible values, and we can then apply our findings to the main constraint - // analysis where useful - specifically, where x++ happens. + // This is valid because the only imprecise operation we do is + // * x == 1 && x < 100 => x > 0 && x < 100 + // That is a valid inference, even if it is pessimistic and hence causes us to + // be able to prove less things. But this is useful because this pessimistic + // outcome is the common situation in a loop, so we find the proper bound on + // the loop variable here in just two iterations of the loop. enum FlowMode { Normal, Loops }; void flow(FlowMode mode) { // Start from the entry as the only reachable block. That block has incoming From 3e6cc0e7476ed3a80d7e586adac85b59bf99d9d9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 13:12:12 -0700 Subject: [PATCH 005/120] work --- src/passes/ConstraintAnalysis.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index dc41f03a097..7684646a014 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -271,6 +271,10 @@ struct ConstraintAnalysis // be able to prove less things. But this is useful because this pessimistic // outcome is the common situation in a loop, so we find the proper bound on // the loop variable here in just two iterations of the loop. + // + // At a high level, we first do the Normal flow, which is as precise as we can + // be. We then do the Loops flow afterwards, adding more information but not + // making anything worse. enum FlowMode { Normal, Loops }; void flow(FlowMode mode) { // Start from the entry as the only reachable block. That block has incoming From aa3788d59867c8260f77f09fc9e8c3488e4e7385 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 13:28:11 -0700 Subject: [PATCH 006/120] work --- src/passes/ConstraintAnalysis.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 7684646a014..a4de0d30394 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -187,8 +187,8 @@ struct ConstraintAnalysis } computeRelevantLocals(); - flow(Mode::Normal); - flow(Mode::Loops); + flowNormally(); + //flowLoops(); optimize(); } @@ -275,8 +275,7 @@ struct ConstraintAnalysis // At a high level, we first do the Normal flow, which is as precise as we can // be. We then do the Loops flow afterwards, adding more information but not // making anything worse. - enum FlowMode { Normal, Loops }; - void flow(FlowMode mode) { + void flowNormally() { // Start from the entry as the only reachable block. That block has incoming // values - defaults - for each var. entry->contents.startConstraints.setReachable(); @@ -304,6 +303,16 @@ struct ConstraintAnalysis // Starting from the entry, keep going while we find something new. UniqueDeferredQueue work; work.push(entry); + doFlow(work, [](const LocalConstraint& branchConstraint, BasicBlockConstraintMap& constraints) { + sentConstraints.approximateAnd(branch.local, branch.constraint); + }); + } + + // Given a worklist initialized to the starting point, keep processing it + // until nothing remains. A lambda is provided to control how we handle branch + // constraints. + template // can we template on the function itself? is this already fast? + void flow(UniqueDeferredQueue& work, T& handleBranch) { while (!work.empty()) { auto* block = work.pop(); @@ -323,7 +332,7 @@ struct ConstraintAnalysis if (auto branch = getBranchConstraints(block, out); branch && checkRelevancy(*branch)) { auto sentConstraints = constraints; - sentConstraints.approximateAnd(branch->local, branch->constraint); + handleBranch(*branch, sentConstraints); // If anything changed at the start of the target block, flow onwards. if (outStartConstraints.approximateOr(sentConstraints)) { work.push(out); From 66799fbdc4b45076e96471ca8912c15806c2f673 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 13:29:20 -0700 Subject: [PATCH 007/120] work --- src/passes/ConstraintAnalysis.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index a4de0d30394..6baebe2287e 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -303,8 +303,10 @@ struct ConstraintAnalysis // Starting from the entry, keep going while we find something new. UniqueDeferredQueue work; work.push(entry); - doFlow(work, [](const LocalConstraint& branchConstraint, BasicBlockConstraintMap& constraints) { - sentConstraints.approximateAnd(branch.local, branch.constraint); + doFlow(work, [](const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { + // The normal flow behavior, given a branch that applies a constraint to a + // local, is to simply AND that constraint onto everything else we know. + constraints.approximateAnd(branch.local, branch.constraint); }); } @@ -312,7 +314,7 @@ struct ConstraintAnalysis // until nothing remains. A lambda is provided to control how we handle branch // constraints. template // can we template on the function itself? is this already fast? - void flow(UniqueDeferredQueue& work, T& handleBranch) { + void doFlow(UniqueDeferredQueue& work, T& handleBranch) { while (!work.empty()) { auto* block = work.pop(); From 9d7c472dd45f75bc49fda37da593cbe00c05f19d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 13:56:21 -0700 Subject: [PATCH 008/120] work --- src/passes/ConstraintAnalysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 6baebe2287e..16edfdc2859 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -314,7 +314,7 @@ struct ConstraintAnalysis // until nothing remains. A lambda is provided to control how we handle branch // constraints. template // can we template on the function itself? is this already fast? - void doFlow(UniqueDeferredQueue& work, T& handleBranch) { + void doFlow(UniqueDeferredQueue& work, const T& handleBranch) { while (!work.empty()) { auto* block = work.pop(); From 9c707af2f19301d888ee539beefa7a6af684c382 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 13:56:50 -0700 Subject: [PATCH 009/120] work --- src/passes/ConstraintAnalysis.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 16edfdc2859..4255aef4f02 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -188,7 +188,7 @@ struct ConstraintAnalysis computeRelevantLocals(); flowNormally(); - //flowLoops(); + flowLoops(); optimize(); } @@ -350,6 +350,9 @@ struct ConstraintAnalysis } } + void flowLoops() { + } + // After inferring all we can, apply it to optimize the code. void optimize() { // If we make things unreachable, we must refinalize. From 83976d45f357f85ef1fc4ee584fdceeeaf1de2be Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 14:04:34 -0700 Subject: [PATCH 010/120] work --- src/passes/ConstraintAnalysis.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 4255aef4f02..70b3e00c448 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -303,6 +303,7 @@ struct ConstraintAnalysis // Starting from the entry, keep going while we find something new. UniqueDeferredQueue work; work.push(entry); + doFlow(work, [](const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { // The normal flow behavior, given a branch that applies a constraint to a // local, is to simply AND that constraint onto everything else we know. @@ -351,6 +352,26 @@ struct ConstraintAnalysis } void flowLoops() { + // As described above, we do two things differently in the Loop flow: we + // process x++ operations, and we apply branch constraints in a + // "pessimistic" way, which can help those x++s expand into the full range + // for the loop. All this only helps if we actually have x++s, so we find + // those and flow from their blocks. + UniqueDeferredQueue work; + for (auto& block : basicBlocks) { + for (auto** currp : block->contents.actions) { + if (isIncrement(*currp)) { + work.push(block.get()); + break; + } + } + } + + doFlow(work, [](const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { + // Extend ranges pessimistically: + // * x == 0 && x < 100 => x >= 0 && x < 100 + constraints.approximateAnd(branch.local, branch.constraint); + }); } // After inferring all we can, apply it to optimize the code. From 35b44de4d049e8cd8829b21b3e0d3a0ff90cdb96 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 14:17:50 -0700 Subject: [PATCH 011/120] work --- src/passes/ConstraintAnalysis.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 70b3e00c448..3d445dad788 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -368,8 +368,27 @@ struct ConstraintAnalysis } doFlow(work, [](const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { - // Extend ranges pessimistically: - // * x == 0 && x < 100 => x >= 0 && x < 100 + // Extend ranges pessimistically. If the branch is x < M, and we were + // x == N where N < M, then extend to x >= N && x < M + if (auto* N = std::get_if(&branch.constraint.term)) { + auto localConstraints = constraints.get(branch.local); + if (localConstraints.size() == 1 && localConstraints[0].op == Abstract::Eq) { + if (auto* M = std::get_if(&localConstraints[0].term)) { + if (branch.constraint.op == Abstract::LtS && + N->ltS(*M).getUnsigned()) { + constraints.approximateAnd(branch.local, {GeS, *N}); + return; + } + if (branch.constraint.op == Abstract::LtU && + N->ltU(*M).getUnsigned()) { + constraints.approximateAnd(branch.local, {GeU, *N}); + return; + } + } + } + } + + // Otherwise, AND normally constraints.approximateAnd(branch.local, branch.constraint); }); } From e166af5130a8e9d5143842b37337e15bab961f7b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 14:18:46 -0700 Subject: [PATCH 012/120] work --- src/passes/ConstraintAnalysis.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 3d445dad788..0efa11c427d 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -376,11 +376,13 @@ struct ConstraintAnalysis if (auto* M = std::get_if(&localConstraints[0].term)) { if (branch.constraint.op == Abstract::LtS && N->ltS(*M).getUnsigned()) { + constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeS, *N}); return; } if (branch.constraint.op == Abstract::LtU && N->ltU(*M).getUnsigned()) { + constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeU, *N}); return; } From db350c7bd5d64f0f3dbf01c49f72b34ca9c5ee78 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 14:26:22 -0700 Subject: [PATCH 013/120] work --- src/passes/ConstraintAnalysis.cpp | 35 ++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 0efa11c427d..6cbcdfa2cf2 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -304,25 +304,37 @@ struct ConstraintAnalysis UniqueDeferredQueue work; work.push(entry); - doFlow(work, [](const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { - // The normal flow behavior, given a branch that applies a constraint to a - // local, is to simply AND that constraint onto everything else we know. - constraints.approximateAnd(branch.local, branch.constraint); - }); + struct Handler { + bool doApplyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) { + // Nothing custom here; use the default behavior. + return false; + } + + bool doBranch(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { + // Nothing custom here; use the default behavior. + return false; + } + }; + + doFlow(work, Handler()); } // Given a worklist initialized to the starting point, keep processing it - // until nothing remains. A lambda is provided to control how we handle branch - // constraints. + // until nothing remains. A handler is provided with two hooks, + // doApplyToConstraints and doBranch, each of which returns true if it handled + // the inputs (if not, we run the default behavior). template // can we template on the function itself? is this already fast? - void doFlow(UniqueDeferredQueue& work, const T& handleBranch) { + void doFlow(UniqueDeferredQueue& work, const T& handler) { while (!work.empty()) { auto* block = work.pop(); // Start at the top of the block, then go through, applying things. BasicBlockConstraintMap constraints = block->contents.startConstraints; for (auto** currp : block->contents.actions) { - applyToConstraints(*currp, constraints); + // Try the handler first. + if (!handler.doApplyToConstraints(*currp, constraints)) { + applyToConstraints(*currp, constraints); + } } // We now know the values at the end of the block. Flow it onward, and @@ -335,7 +347,9 @@ struct ConstraintAnalysis if (auto branch = getBranchConstraints(block, out); branch && checkRelevancy(*branch)) { auto sentConstraints = constraints; - handleBranch(*branch, sentConstraints); + if (!handler.doBranch(*branch, sentConstraints)) { + sentConstraints.approximateAnd(branch->local, branch->constraint); + } // If anything changed at the start of the target block, flow onwards. if (outStartConstraints.approximateOr(sentConstraints)) { work.push(out); @@ -370,6 +384,7 @@ struct ConstraintAnalysis doFlow(work, [](const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { // Extend ranges pessimistically. If the branch is x < M, and we were // x == N where N < M, then extend to x >= N && x < M + // TODO: move helper matching stuff out of constraint.cpp? if (auto* N = std::get_if(&branch.constraint.term)) { auto localConstraints = constraints.get(branch.local); if (localConstraints.size() == 1 && localConstraints[0].op == Abstract::Eq) { From 4918d3228fadbcfb8f0c58fddff0335b29574e9e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 14:28:10 -0700 Subject: [PATCH 014/120] work --- src/passes/ConstraintAnalysis.cpp | 56 +++++++++++++++++++------------ 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 6cbcdfa2cf2..1a094c82f47 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -381,33 +381,45 @@ struct ConstraintAnalysis } } - doFlow(work, [](const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { - // Extend ranges pessimistically. If the branch is x < M, and we were - // x == N where N < M, then extend to x >= N && x < M - // TODO: move helper matching stuff out of constraint.cpp? - if (auto* N = std::get_if(&branch.constraint.term)) { - auto localConstraints = constraints.get(branch.local); - if (localConstraints.size() == 1 && localConstraints[0].op == Abstract::Eq) { - if (auto* M = std::get_if(&localConstraints[0].term)) { - if (branch.constraint.op == Abstract::LtS && - N->ltS(*M).getUnsigned()) { - constraints.set(branch.local, branch.constraint); - constraints.approximateAnd(branch.local, {GeS, *N}); - return; - } - if (branch.constraint.op == Abstract::LtU && - N->ltU(*M).getUnsigned()) { - constraints.set(branch.local, branch.constraint); - constraints.approximateAnd(branch.local, {GeU, *N}); - return; + struct Handler { + bool doApplyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) { + // Operate on x++s. + +.. + + return false; + } + + bool doBranch(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { + // Extend ranges pessimistically. If the branch is x < M, and we were + // x == N where N < M, then extend to x >= N && x < M + // TODO: move helper matching stuff out of constraint.cpp? + if (auto* N = std::get_if(&branch.constraint.term)) { + auto localConstraints = constraints.get(branch.local); + if (localConstraints.size() == 1 && localConstraints[0].op == Abstract::Eq) { + if (auto* M = std::get_if(&localConstraints[0].term)) { + if (branch.constraint.op == Abstract::LtS && + N->ltS(*M).getUnsigned()) { + constraints.set(branch.local, branch.constraint); + constraints.approximateAnd(branch.local, {GeS, *N}); + return true; + } + if (branch.constraint.op == Abstract::LtU && + N->ltU(*M).getUnsigned()) { + constraints.set(branch.local, branch.constraint); + constraints.approximateAnd(branch.local, {GeU, *N}); + return true; + } } } } + + // We did nothing custom; use the default behavior. + return false; } + }; - // Otherwise, AND normally - constraints.approximateAnd(branch.local, branch.constraint); - }); + doFlow(work, Handler()); } // After inferring all we can, apply it to optimize the code. From 7458b6a2a85843b5569a44d7a1f40e9fa831d636 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 14:45:02 -0700 Subject: [PATCH 015/120] work --- src/ir/match.h | 17 +++++++++++++++++ src/passes/ConstraintAnalysis.cpp | 6 ++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/ir/match.h b/src/ir/match.h index 383ff8d057a..4b1568bb0e9 100644 --- a/src/ir/match.h +++ b/src/ir/match.h @@ -613,6 +613,18 @@ SelectMatcher(Select** binder, S1&& s1, S2&& s2, S3&& s3) { return Matcher(binder, {}, s1, s2, s3); } +// LocalGet +template<> struct NumComponents { + static constexpr size_t value = 1; +}; +template<> struct GetComponent { + Index operator()(LocalGet* curr) { return curr->index; } +}; +template +inline decltype(auto) LocalGetMatcher(LocalGet** binder, S&& s) { + return Matcher(binder, {}, s); +} + } // namespace Internal // Public matching API @@ -878,6 +890,11 @@ inline decltype(auto) select(Select** binder, S1&& s1, S2&& s2, S3&& s3) { return Internal::SelectMatcher(binder, s1, s2, s3); } +inline decltype(auto) local(Index* binder) { + return Internal::LocalGetMatcher( + nullptr, Internal::Any(binder)); +} + } // namespace wasm::Match #endif // wasm_ir_match_h diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 1a094c82f47..68c24455823 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -28,6 +28,7 @@ #include "ir/eh-utils.h" #include "ir/literal-utils.h" #include "ir/local-graph.h" +#include "ir/match.h" #include "ir/properties.h" #include "ir/utils.h" #include "pass.h" @@ -374,7 +375,8 @@ struct ConstraintAnalysis UniqueDeferredQueue work; for (auto& block : basicBlocks) { for (auto** currp : block->contents.actions) { - if (isIncrement(*currp)) { + // x = y + 1 + if (matches(*currp, binary(Abstract::Add, local(), ival(1)))) { work.push(block.get()); break; } @@ -385,7 +387,7 @@ struct ConstraintAnalysis bool doApplyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) { // Operate on x++s. -.. +//.. return false; } From 5695b31c707f1cf964f4adb8f5b2695d91ddfbf9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 14:47:31 -0700 Subject: [PATCH 016/120] work --- src/ir/match.h | 4 ++++ src/passes/ConstraintAnalysis.cpp | 1 + 2 files changed, 5 insertions(+) diff --git a/src/ir/match.h b/src/ir/match.h index 4b1568bb0e9..96d082e4c5b 100644 --- a/src/ir/match.h +++ b/src/ir/match.h @@ -890,6 +890,10 @@ inline decltype(auto) select(Select** binder, S1&& s1, S2&& s2, S3&& s3) { return Internal::SelectMatcher(binder, s1, s2, s3); } +inline decltype(auto) local() { + return Internal::LocalGetMatcher( + nullptr, Internal::Any()); +} inline decltype(auto) local(Index* binder) { return Internal::LocalGetMatcher( nullptr, Internal::Any(binder)); diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 68c24455823..dae7427dd43 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -376,6 +376,7 @@ struct ConstraintAnalysis for (auto& block : basicBlocks) { for (auto** currp : block->contents.actions) { // x = y + 1 + using namespace Match; if (matches(*currp, binary(Abstract::Add, local(), ival(1)))) { work.push(block.get()); break; From d5deb268f0468777e7fe0433b1c135ee06ee784e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 14:50:11 -0700 Subject: [PATCH 017/120] work --- src/ir/match.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/match.h b/src/ir/match.h index 96d082e4c5b..9f89845cc1c 100644 --- a/src/ir/match.h +++ b/src/ir/match.h @@ -892,7 +892,7 @@ inline decltype(auto) select(Select** binder, S1&& s1, S2&& s2, S3&& s3) { inline decltype(auto) local() { return Internal::LocalGetMatcher( - nullptr, Internal::Any()); + nullptr, Internal::Any(nullptr)); } inline decltype(auto) local(Index* binder) { return Internal::LocalGetMatcher( From 07e8fa5d3eacaeda7c117ae151503c3c66533375 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 14:50:50 -0700 Subject: [PATCH 018/120] work --- src/passes/ConstraintAnalysis.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index dae7427dd43..464ea1e0656 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -397,6 +397,7 @@ struct ConstraintAnalysis // Extend ranges pessimistically. If the branch is x < M, and we were // x == N where N < M, then extend to x >= N && x < M // TODO: move helper matching stuff out of constraint.cpp? + using namespace Abstract; if (auto* N = std::get_if(&branch.constraint.term)) { auto localConstraints = constraints.get(branch.local); if (localConstraints.size() == 1 && localConstraints[0].op == Abstract::Eq) { From 95430f46993d28cffe045af2935c0627f3975e1f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 14:55:41 -0700 Subject: [PATCH 019/120] work --- src/passes/ConstraintAnalysis.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 464ea1e0656..eccacd02624 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -386,9 +386,14 @@ struct ConstraintAnalysis struct Handler { bool doApplyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) { - // Operate on x++s. + using namespace Match; -//.. + // Operate on x++s. + // x = y + 1 + Index y; + if (matches(*currp, binary(Abstract::Add, local(&y), ival(1)))) { + // .. + } return false; } From 019faf5ae4f0157e9ca8394fb50430c9d548ce43 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 15:07:04 -0700 Subject: [PATCH 020/120] work --- src/passes/ConstraintAnalysis.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index eccacd02624..7e8c88c3557 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -429,6 +429,8 @@ struct ConstraintAnalysis }; doFlow(work, Handler()); + + // TODO: copy old flow data, only merge us in when we actually improve? } // After inferring all we can, apply it to optimize the code. @@ -444,6 +446,7 @@ struct ConstraintAnalysis for (auto** currp : block->contents.actions) { if (!constraints.unreachable) { applyToConstraints(*currp, constraints); + // TODO: can apply x++ here too optimizeExpression(currp, constraints); } else { // This is unreachable code: just mark it so. From 147e989735058494ef6cc7f59058effe014ac6b4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 15:25:02 -0700 Subject: [PATCH 021/120] work --- src/passes/ConstraintAnalysis.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 7e8c88c3557..dbbbb31eb37 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -306,12 +306,12 @@ struct ConstraintAnalysis work.push(entry); struct Handler { - bool doApplyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) { + bool doApplyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) const { // Nothing custom here; use the default behavior. return false; } - bool doBranch(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { + bool doBranch(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) const { // Nothing custom here; use the default behavior. return false; } @@ -385,20 +385,20 @@ struct ConstraintAnalysis } struct Handler { - bool doApplyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) { + bool doApplyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) const { using namespace Match; // Operate on x++s. // x = y + 1 Index y; - if (matches(*currp, binary(Abstract::Add, local(&y), ival(1)))) { + if (matches(curr, binary(Abstract::Add, local(&y), ival(1)))) { // .. } return false; } - bool doBranch(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { + bool doBranch(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) const { // Extend ranges pessimistically. If the branch is x < M, and we were // x == N where N < M, then extend to x >= N && x < M // TODO: move helper matching stuff out of constraint.cpp? @@ -410,13 +410,13 @@ struct ConstraintAnalysis if (branch.constraint.op == Abstract::LtS && N->ltS(*M).getUnsigned()) { constraints.set(branch.local, branch.constraint); - constraints.approximateAnd(branch.local, {GeS, *N}); + constraints.approximateAnd(branch.local, {GeS, {*N}}); return true; } if (branch.constraint.op == Abstract::LtU && N->ltU(*M).getUnsigned()) { constraints.set(branch.local, branch.constraint); - constraints.approximateAnd(branch.local, {GeU, *N}); + constraints.approximateAnd(branch.local, {GeU, {*N}}); return true; } } From 4aecba54de07b1d7d305bbb430ca52fb1c056dea Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 15:25:22 -0700 Subject: [PATCH 022/120] work --- src/ir/match.h | 6 ++---- src/passes/ConstraintAnalysis.cpp | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/ir/match.h b/src/ir/match.h index 9f89845cc1c..a60bad43235 100644 --- a/src/ir/match.h +++ b/src/ir/match.h @@ -891,12 +891,10 @@ inline decltype(auto) select(Select** binder, S1&& s1, S2&& s2, S3&& s3) { } inline decltype(auto) local() { - return Internal::LocalGetMatcher( - nullptr, Internal::Any(nullptr)); + return Internal::LocalGetMatcher(nullptr, Internal::Any(nullptr)); } inline decltype(auto) local(Index* binder) { - return Internal::LocalGetMatcher( - nullptr, Internal::Any(binder)); + return Internal::LocalGetMatcher(nullptr, Internal::Any(binder)); } } // namespace wasm::Match diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index dbbbb31eb37..832e1b8add1 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -306,12 +306,14 @@ struct ConstraintAnalysis work.push(entry); struct Handler { - bool doApplyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) const { + bool doApplyToConstraints(Expression* curr, + BasicBlockConstraintMap& constraints) const { // Nothing custom here; use the default behavior. return false; } - bool doBranch(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) const { + bool doBranch(const LocalConstraint& branch, + BasicBlockConstraintMap& constraints) const { // Nothing custom here; use the default behavior. return false; } @@ -324,8 +326,10 @@ struct ConstraintAnalysis // until nothing remains. A handler is provided with two hooks, // doApplyToConstraints and doBranch, each of which returns true if it handled // the inputs (if not, we run the default behavior). - template // can we template on the function itself? is this already fast? - void doFlow(UniqueDeferredQueue& work, const T& handler) { + template // can we template on the function itself? is this + // already fast? + void doFlow(UniqueDeferredQueue& work, + const T& handler) { while (!work.empty()) { auto* block = work.pop(); @@ -385,7 +389,8 @@ struct ConstraintAnalysis } struct Handler { - bool doApplyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) const { + bool doApplyToConstraints(Expression* curr, + BasicBlockConstraintMap& constraints) const { using namespace Match; // Operate on x++s. @@ -398,14 +403,16 @@ struct ConstraintAnalysis return false; } - bool doBranch(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) const { + bool doBranch(const LocalConstraint& branch, + BasicBlockConstraintMap& constraints) const { // Extend ranges pessimistically. If the branch is x < M, and we were // x == N where N < M, then extend to x >= N && x < M // TODO: move helper matching stuff out of constraint.cpp? using namespace Abstract; if (auto* N = std::get_if(&branch.constraint.term)) { auto localConstraints = constraints.get(branch.local); - if (localConstraints.size() == 1 && localConstraints[0].op == Abstract::Eq) { + if (localConstraints.size() == 1 && + localConstraints[0].op == Abstract::Eq) { if (auto* M = std::get_if(&localConstraints[0].term)) { if (branch.constraint.op == Abstract::LtS && N->ltS(*M).getUnsigned()) { From c1342f9de05fb01bd1d050256afdc1848a06b241 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 15:42:02 -0700 Subject: [PATCH 023/120] work --- src/passes/ConstraintAnalysis.cpp | 48 +++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 832e1b8add1..6ac20a413a3 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -392,12 +392,56 @@ struct ConstraintAnalysis bool doApplyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) const { using namespace Match; + using namespace Abstract; + + auto* set = curr->dynCast(); + if (!set) { + return false; + } // Operate on x++s. // x = y + 1 Index y; - if (matches(curr, binary(Abstract::Add, local(&y), ival(1)))) { - // .. + if (matches(set->value, binary(Abstract::Add, local(&y), ival(1)))) { + auto old = constraints.get(y); + if (old.empty()) { + // Nothing we know how to increment. + return false; + } + + for (auto& c : old) { + if (auto* N = std::get_if(&c.term)) { + switch (c.op) { + // x == N, x++ => x == N+1. + case Eq: + // TODO: overflows here and below + c.term = N->add(Literal::fromInt32(1, N->type)); + continue; + // x >= N, x++ => x > N + case GeS: + c.term = GtS; + continue; + case GeU: + c.term = GtU; + continue; + // x < N, x++ => x <= N + case LtS: + c.term = LeS; + continue; + case LtU: + c.term = GeU; + continue; + default: + // Something we don't recognize. + return false; + } + } + } + + // We processed the old constraints into their new forms without + // problems. Apply them and we are done. + constraints.set(set->index, old); + return true; } return false; From 3bb360bfe1c5c19cc2d8e46a565ef58bdd083837 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 15:42:43 -0700 Subject: [PATCH 024/120] work --- src/passes/ConstraintAnalysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 6ac20a413a3..70fc2c52fa1 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -415,7 +415,7 @@ struct ConstraintAnalysis // x == N, x++ => x == N+1. case Eq: // TODO: overflows here and below - c.term = N->add(Literal::fromInt32(1, N->type)); + c.term = N->add(Literal::makeFromInt32(1, N->type)); continue; // x >= N, x++ => x > N case GeS: From 5343be174ae7a816cc529a90798acea9cffbfbf0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 15:47:16 -0700 Subject: [PATCH 025/120] work --- src/ir/constraint.cpp | 17 +++++++++++++++++ src/ir/constraint.h | 5 ++++- src/passes/ConstraintAnalysis.cpp | 10 +++++----- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index eb4f65e9a9d..7dc6c44b0b2 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -467,6 +467,23 @@ void BasicBlockConstraintMap::set(Index index, const Constraint& c) { approximateAnd(index, c); } +void BasicBlockConstraintMap::set(Index index, + const AndedConstraintSet& constraints) { + // As above, but with a loop after. + assert(!unreachable); + eraseStaleRefs(index); + map.erase(index); + + // Apply the constraints, if there are any. + if (constraints.provesNothing()) { + setProvesNothing(index); + } else { + for (auto& c : constraints) { + approximateAnd(index, c); + } + } +} + void BasicBlockConstraintMap::setProvesNothing(Index index) { assert(!unreachable); eraseStaleRefs(index); diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 9fea231fbc7..b0a8b8c4129 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -251,9 +251,12 @@ struct BasicBlockConstraintMap { assert(map.empty()); } - // Apply a constraint to a local. + // Apply a constraint to a local, replacing anything before. void set(Index index, const Constraint& c); + // Apply a set of constraints to a local, replacing anything before. + void set(Index index, const AndedConstraintSet& constraints); + // Mark a local as unknown and able to prove nothing. void setProvesNothing(Index index); diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 70fc2c52fa1..79ea529f735 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -415,21 +415,21 @@ struct ConstraintAnalysis // x == N, x++ => x == N+1. case Eq: // TODO: overflows here and below - c.term = N->add(Literal::makeFromInt32(1, N->type)); + c.term = Term(N->add(Literal::makeFromInt32(1, N->type))); continue; // x >= N, x++ => x > N case GeS: - c.term = GtS; + c.op = GtS; continue; case GeU: - c.term = GtU; + c.op = GtU; continue; // x < N, x++ => x <= N case LtS: - c.term = LeS; + c.op = LeS; continue; case LtU: - c.term = GeU; + c.op = GeU; continue; default: // Something we don't recognize. From 13a9ded9ef0daabccfb6701e38f11e7777d71cba Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 15:47:40 -0700 Subject: [PATCH 026/120] work --- src/passes/ConstraintAnalysis.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 79ea529f735..aef50b05e52 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -328,8 +328,7 @@ struct ConstraintAnalysis // the inputs (if not, we run the default behavior). template // can we template on the function itself? is this // already fast? - void doFlow(UniqueDeferredQueue& work, - const T& handler) { + void doFlow(UniqueDeferredQueue& work, const T& handler) { while (!work.empty()) { auto* block = work.pop(); From eb4d1879a313efe2dde168b33f639f7758dd717b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 29 Jul 2026 16:38:58 -0700 Subject: [PATCH 027/120] work --- .../lit/passes/constraint-analysis-loops.wast | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 5d90de1096f..484213694be 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -226,4 +226,80 @@ ) ) ) + + ;; CHECK: (func $bound-incremented (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.lt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $bound-incremented + (local $x i32) + (loop $loop + ;; A realistic loop, with an $x++ and a single bounds check. We must infer + ;; that no overflow happens in order to prove these two checks are true. + ;; + ;; The first is trivially true, as x starts at 0 - fulfilling x < 100 - + ;; and the branch back to the loop top arrives with x < 100. + (drop + (i32.lt_s + (local.get $x) + (i32.const 100) + ) + ) + ;; This is non-trivial, as we must rule out a possible overflow. The + ;; only reason that x never gets incremented so many times that it becomes + ;; negative is that the incrementation process is stopped at 100. + (drop + (i32.ge_s + (local.get $x) + (i32.const 0) + ) + ) + ;; This changed compared to previous testcases: now we have x++. + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (if + (i32.lt_s + (local.get $x) + (i32.const 100) + ) + (then + (br $loop) + ) + ) + ) + ) + + ;; CHECK: (func $bound-incremented-2-no (type $0) + ;; CHECK-NEXT: ) + (func $bound-incremented-2-no + ) ) From 3aa5e8f2293cab8eefd700d397e0592c0f86451d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 10:14:08 -0700 Subject: [PATCH 028/120] work --- src/passes/ConstraintAnalysis.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index aef50b05e52..eb3e4195f15 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -334,17 +334,20 @@ struct ConstraintAnalysis // Start at the top of the block, then go through, applying things. BasicBlockConstraintMap constraints = block->contents.startConstraints; +std::cout << "block start: " << constraints << "\n"; for (auto** currp : block->contents.actions) { // Try the handler first. if (!handler.doApplyToConstraints(*currp, constraints)) { applyToConstraints(*currp, constraints); } } +std::cout << "block end: " << constraints << "\n"; // We now know the values at the end of the block. Flow it onward, and // where it causes changes, queue more work. for (auto* out : block->out) { auto& outStartConstraints = out->contents.startConstraints; +std::cout << " flowing to out with start: " << outStartConstraints << "\n"; // Find the constraints sent to this specific successor, if there is a // branch, and use them. @@ -353,7 +356,9 @@ struct ConstraintAnalysis auto sentConstraints = constraints; if (!handler.doBranch(*branch, sentConstraints)) { sentConstraints.approximateAnd(branch->local, branch->constraint); +std::cout << " branch " << branch->local << ":" << branch->constraint << " ANDED to: " << sentConstraints << "\n"; } +else std::cout << " custom branch handling led to: " << sentConstraints << "\n"; // If anything changed at the start of the target block, flow onwards. if (outStartConstraints.approximateOr(sentConstraints)) { work.push(out); @@ -365,11 +370,15 @@ struct ConstraintAnalysis work.push(out); } } + +std::cout << " flowed to out, now start: " << outStartConstraints << "\n\n"; } } } void flowLoops() { +std::cout << "fl\n"; + // As described above, we do two things differently in the Loop flow: we // process x++ operations, and we apply branch constraints in a // "pessimistic" way, which can help those x++s expand into the full range @@ -381,6 +390,7 @@ struct ConstraintAnalysis // x = y + 1 using namespace Match; if (matches(*currp, binary(Abstract::Add, local(), ival(1)))) { +std::cout << "fl1\n"; work.push(block.get()); break; } @@ -400,9 +410,12 @@ struct ConstraintAnalysis // Operate on x++s. // x = y + 1 +std::cout << "f2 9doApplyToConstraints" << *curr << "\n"; Index y; if (matches(set->value, binary(Abstract::Add, local(&y), ival(1)))) { +std::cout << "f3\n"; auto old = constraints.get(y); +std::cout << "f3 " << old << "\n"; if (old.empty()) { // Nothing we know how to increment. return false; @@ -440,6 +453,7 @@ struct ConstraintAnalysis // We processed the old constraints into their new forms without // problems. Apply them and we are done. constraints.set(set->index, old); +std::cout << "fl3.5 " << old << "\n"; return true; } @@ -448,12 +462,14 @@ struct ConstraintAnalysis bool doBranch(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) const { +std::cout << "f4 (doBranch)\n"; // Extend ranges pessimistically. If the branch is x < M, and we were // x == N where N < M, then extend to x >= N && x < M // TODO: move helper matching stuff out of constraint.cpp? using namespace Abstract; if (auto* N = std::get_if(&branch.constraint.term)) { auto localConstraints = constraints.get(branch.local); +std::cout << "fl5 " << branch.constraint << " vs " << localConstraints << "\n"; if (localConstraints.size() == 1 && localConstraints[0].op == Abstract::Eq) { if (auto* M = std::get_if(&localConstraints[0].term)) { @@ -461,12 +477,14 @@ struct ConstraintAnalysis N->ltS(*M).getUnsigned()) { constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeS, {*N}}); +std::cout << "fl5.3\n"; // TODO return true; } if (branch.constraint.op == Abstract::LtU && N->ltU(*M).getUnsigned()) { constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeU, {*N}}); +std::cout << "fl5.7\n"; // TODO return true; } } @@ -478,6 +496,7 @@ struct ConstraintAnalysis } }; +std::cout << "fl1.5: now doing Loop flow!!1\n"; doFlow(work, Handler()); // TODO: copy old flow data, only merge us in when we actually improve? From 15c0431d80d087c14bd022df638fd03a2cbb6672 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 10:16:58 -0700 Subject: [PATCH 029/120] work --- src/passes/ConstraintAnalysis.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index eb3e4195f15..106bd71d9cf 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -188,7 +188,7 @@ struct ConstraintAnalysis } computeRelevantLocals(); - flowNormally(); + //flowNormally(); flowLoops(); optimize(); } @@ -497,6 +497,7 @@ std::cout << "fl5.7\n"; // TODO }; std::cout << "fl1.5: now doing Loop flow!!1\n"; +// XXX can't continue flow, must start from scratchh before was ruinnedd doFlow(work, Handler()); // TODO: copy old flow data, only merge us in when we actually improve? From b7a49e7de1a461a8f4817aa95dd4f1d6d8194ca1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 10:18:10 -0700 Subject: [PATCH 030/120] work --- src/passes/ConstraintAnalysis.cpp | 69 +++++++------------ .../lit/passes/constraint-analysis-loops.wast | 5 +- 2 files changed, 30 insertions(+), 44 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 106bd71d9cf..207e3ac164c 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -277,6 +277,31 @@ struct ConstraintAnalysis // be. We then do the Loops flow afterwards, adding more information but not // making anything worse. void flowNormally() { + struct Handler { + bool doApplyToConstraints(Expression* curr, + BasicBlockConstraintMap& constraints) const { + // Nothing custom here; use the default behavior. + return false; + } + + bool doBranch(const LocalConstraint& branch, + BasicBlockConstraintMap& constraints) const { + // Nothing custom here; use the default behavior. + return false; + } + }; + + doFlow(Handler()); + } + + // Given a worklist initialized to the starting point, keep processing it + // until nothing remains. A handler is provided with two hooks, + // doApplyToConstraints and doBranch, each of which returns true if it handled + // the inputs (if not, we run the default behavior). + template // can we template on the function itself? is this + // already fast? + void doFlow(const T& handler) { + // Start from the entry as the only reachable block. That block has incoming // values - defaults - for each var. entry->contents.startConstraints.setReachable(); @@ -305,30 +330,6 @@ struct ConstraintAnalysis UniqueDeferredQueue work; work.push(entry); - struct Handler { - bool doApplyToConstraints(Expression* curr, - BasicBlockConstraintMap& constraints) const { - // Nothing custom here; use the default behavior. - return false; - } - - bool doBranch(const LocalConstraint& branch, - BasicBlockConstraintMap& constraints) const { - // Nothing custom here; use the default behavior. - return false; - } - }; - - doFlow(work, Handler()); - } - - // Given a worklist initialized to the starting point, keep processing it - // until nothing remains. A handler is provided with two hooks, - // doApplyToConstraints and doBranch, each of which returns true if it handled - // the inputs (if not, we run the default behavior). - template // can we template on the function itself? is this - // already fast? - void doFlow(UniqueDeferredQueue& work, const T& handler) { while (!work.empty()) { auto* block = work.pop(); @@ -379,24 +380,6 @@ std::cout << " flowed to out, now start: " << outStartConstraints << "\n\n"; void flowLoops() { std::cout << "fl\n"; - // As described above, we do two things differently in the Loop flow: we - // process x++ operations, and we apply branch constraints in a - // "pessimistic" way, which can help those x++s expand into the full range - // for the loop. All this only helps if we actually have x++s, so we find - // those and flow from their blocks. - UniqueDeferredQueue work; - for (auto& block : basicBlocks) { - for (auto** currp : block->contents.actions) { - // x = y + 1 - using namespace Match; - if (matches(*currp, binary(Abstract::Add, local(), ival(1)))) { -std::cout << "fl1\n"; - work.push(block.get()); - break; - } - } - } - struct Handler { bool doApplyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) const { @@ -498,7 +481,7 @@ std::cout << "fl5.7\n"; // TODO std::cout << "fl1.5: now doing Loop flow!!1\n"; // XXX can't continue flow, must start from scratchh before was ruinnedd - doFlow(work, Handler()); + doFlow(Handler()); // TODO: copy old flow data, only merge us in when we actually improve? } diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 484213694be..c29f809a098 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -231,7 +231,10 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.lt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.ge_s From 9a407005c9c520e620dac2c74e580b7932a85d21 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 10:23:49 -0700 Subject: [PATCH 031/120] work --- src/passes/ConstraintAnalysis.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 207e3ac164c..69d4523af65 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -450,12 +450,14 @@ std::cout << "f4 (doBranch)\n"; // x == N where N < M, then extend to x >= N && x < M // TODO: move helper matching stuff out of constraint.cpp? using namespace Abstract; - if (auto* N = std::get_if(&branch.constraint.term)) { + if (auto* M = std::get_if(&branch.constraint.term)) { auto localConstraints = constraints.get(branch.local); std::cout << "fl5 " << branch.constraint << " vs " << localConstraints << "\n"; if (localConstraints.size() == 1 && localConstraints[0].op == Abstract::Eq) { - if (auto* M = std::get_if(&localConstraints[0].term)) { +std::cout << "fl5.1\n"; + if (auto* N = std::get_if(&localConstraints[0].term)) { +std::cout << "fl5.2 " << (branch.constraint.op == Abstract::LtS) << " : " << *N << " ltS(*M).getUnsigned()) { constraints.set(branch.local, branch.constraint); From 5e3db802693c4a07673ae108cd3af62136463e7e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 10:56:11 -0700 Subject: [PATCH 032/120] work --- src/ir/constraint.cpp | 29 +++++++++++++++++++ src/passes/ConstraintAnalysis.cpp | 3 +- test/gtest/constraint.cpp | 8 +++++ .../lit/passes/constraint-analysis-loops.wast | 5 +--- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 7dc6c44b0b2..b0aaa5548ee 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -271,6 +271,23 @@ std::optional approximateOrTermEqualPair(const Abstract::Op aOp, return {}; } +// Do an OR of a pair of constraints where the terms are adjacent constants: a +// operations on N, and b on N+1. +std::optional approximateOrAdjacentConstantPair(const Abstract::Op aOp, + const Literal& aConstant, + const Abstract::Op bOp) { + using namespace Abstract; + + // x == C || x >= C+1 === x >= C + if (aOp == Eq && bOp == GeS) { + return Constraint{GeS, aConstant}; + } + + // TODO: all the rest + + return {}; +} + // Do an OR of a pair of constraints. If we can't find a good way to express // their ORing, return nullopt. std::optional approximateOrPair(const Constraint& a, @@ -282,6 +299,18 @@ std::optional approximateOrPair(const Constraint& a, } } + // See if we operate on constants N, N+1. + if (auto* ac = std::get_if(&a.term)) { + if (auto* bc = std::get_if(&b.term)) { + if (ac->type == bc->type && ac->type.isInteger() && + ac->add(Literal::makeFromInt32(1, ac->type)) == *bc) { + if (auto result = approximateOrAdjacentConstantPair(a.op, *ac, b.op)) { + return result; + } + } + } + } + // If a proves b, e.g. x = 5 proves x >= 0 is true, then the OR is b. if (provesPair(a, b) == True) { return b; diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 69d4523af65..72e11de245d 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -342,7 +342,7 @@ std::cout << "block start: " << constraints << "\n"; applyToConstraints(*currp, constraints); } } -std::cout << "block end: " << constraints << "\n"; +std::cout << "block end : " << constraints << "\n"; // We now know the values at the end of the block. Flow it onward, and // where it causes changes, queue more work. @@ -470,6 +470,7 @@ std::cout << "fl5.3\n"; // TODO constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeU, {*N}}); std::cout << "fl5.7\n"; // TODO +abort(); // TODO: update like abovve return true; } } diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index c1e23650dd6..1ec437050be 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -265,6 +265,14 @@ TEST(ConstraintTest, TestOrInequality) { // x == 5 || x >= 5 => x >= 5 checkOr(eq5, ges5, ges5); + + // x == 5 || x >= 6 => x >= 5 + AndedConstraintSet ges6{{GeS, {Literal(int32_t(6))}}}; + checkOr(eq5, ges6, ges5); + + // TODO: x == 5 || x >= 7 => x >= 5 TODO + //AndedConstraintSet ges6{{GeS, {Literal(int32_t(6))}}}; + //checkOr(eq5, ges6, ges5); } TEST(ConstraintTest, TestOrLoop) { diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index c29f809a098..484213694be 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -231,10 +231,7 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.lt_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.ge_s From 0b04e23ba9b868e1b15419d7735e16d60292ba17 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 10:57:19 -0700 Subject: [PATCH 033/120] work --- test/gtest/constraint.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 1ec437050be..968b7aa409d 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -271,8 +271,9 @@ TEST(ConstraintTest, TestOrInequality) { checkOr(eq5, ges6, ges5); // TODO: x == 5 || x >= 7 => x >= 5 TODO - //AndedConstraintSet ges6{{GeS, {Literal(int32_t(6))}}}; - //checkOr(eq5, ges6, ges5); + AndedConstraintSet ges7{{GeS, {Literal(int32_t(7))}}}; + auto empty = AndedConstraintSet::makeProvesNothing(); + checkOr(eq5, ges7, empty); } TEST(ConstraintTest, TestOrLoop) { From cfd704fb11277ea5dcaaa07663fb2a543f6b817f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 11:07:02 -0700 Subject: [PATCH 034/120] work --- src/ir/constraint.cpp | 7 ++++++- test/gtest/constraint.cpp | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index b0aaa5548ee..c6b534a1f7d 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -272,7 +272,7 @@ std::optional approximateOrTermEqualPair(const Abstract::Op aOp, } // Do an OR of a pair of constraints where the terms are adjacent constants: a -// operations on N, and b on N+1. +// operations on C, and b on C+1. std::optional approximateOrAdjacentConstantPair(const Abstract::Op aOp, const Literal& aConstant, const Abstract::Op bOp) { @@ -283,6 +283,11 @@ std::optional approximateOrAdjacentConstantPair(const Abstract::Op a return Constraint{GeS, aConstant}; } + // x > C || x >= C+1 === x > C + if (aOp == GtS && bOp == GeS) { + return Constraint{GtS, aConstant}; + } + // TODO: all the rest return {}; diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 968b7aa409d..e13b909a471 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -274,6 +274,9 @@ TEST(ConstraintTest, TestOrInequality) { AndedConstraintSet ges7{{GeS, {Literal(int32_t(7))}}}; auto empty = AndedConstraintSet::makeProvesNothing(); checkOr(eq5, ges7, empty); + + // x > 5 || x >= 6 => x > 5 + checkOr(gts5, ges6, gts5); } TEST(ConstraintTest, TestOrLoop) { From d5e54e96bb1d15952ca2dd2a1e079d67d8b1a583 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 11:18:33 -0700 Subject: [PATCH 035/120] work --- src/ir/constraint.cpp | 5 +++++ test/gtest/constraint.cpp | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index c6b534a1f7d..7178d5b4ee2 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -266,6 +266,11 @@ std::optional approximateOrTermEqualPair(const Abstract::Op aOp, return Constraint{GeS, term}; } + // x >= C || x > C === x >= C + if (aOp == GtS && bOp == GeS) { + return Constraint{GeS, term}; + } + // TODO: all the rest return {}; diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index e13b909a471..51e58499575 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -277,6 +277,9 @@ TEST(ConstraintTest, TestOrInequality) { // x > 5 || x >= 6 => x > 5 checkOr(gts5, ges6, gts5); + + // x > 5 || x >= 5 => x >= 5 + checkOr(gts5, ges5, ges5); } TEST(ConstraintTest, TestOrLoop) { From 71daf85f994f588bce734dac3de85e3aff7d3ae2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 11:30:04 -0700 Subject: [PATCH 036/120] work --- src/passes/ConstraintAnalysis.cpp | 21 +------------------ .../lit/passes/constraint-analysis-loops.wast | 5 +---- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 72e11de245d..99b9dbd6cc0 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -335,20 +335,17 @@ struct ConstraintAnalysis // Start at the top of the block, then go through, applying things. BasicBlockConstraintMap constraints = block->contents.startConstraints; -std::cout << "block start: " << constraints << "\n"; for (auto** currp : block->contents.actions) { // Try the handler first. if (!handler.doApplyToConstraints(*currp, constraints)) { applyToConstraints(*currp, constraints); } } -std::cout << "block end : " << constraints << "\n"; // We now know the values at the end of the block. Flow it onward, and // where it causes changes, queue more work. for (auto* out : block->out) { auto& outStartConstraints = out->contents.startConstraints; -std::cout << " flowing to out with start: " << outStartConstraints << "\n"; // Find the constraints sent to this specific successor, if there is a // branch, and use them. @@ -357,9 +354,8 @@ std::cout << " flowing to out with start: " << outStartConstraints << "\n"; auto sentConstraints = constraints; if (!handler.doBranch(*branch, sentConstraints)) { sentConstraints.approximateAnd(branch->local, branch->constraint); -std::cout << " branch " << branch->local << ":" << branch->constraint << " ANDED to: " << sentConstraints << "\n"; } -else std::cout << " custom branch handling led to: " << sentConstraints << "\n"; + // If anything changed at the start of the target block, flow onwards. if (outStartConstraints.approximateOr(sentConstraints)) { work.push(out); @@ -372,13 +368,11 @@ else std::cout << " custom branch handling led to: " << sentConstraints << "\n" } } -std::cout << " flowed to out, now start: " << outStartConstraints << "\n\n"; } } } void flowLoops() { -std::cout << "fl\n"; struct Handler { bool doApplyToConstraints(Expression* curr, @@ -393,12 +387,9 @@ std::cout << "fl\n"; // Operate on x++s. // x = y + 1 -std::cout << "f2 9doApplyToConstraints" << *curr << "\n"; Index y; if (matches(set->value, binary(Abstract::Add, local(&y), ival(1)))) { -std::cout << "f3\n"; auto old = constraints.get(y); -std::cout << "f3 " << old << "\n"; if (old.empty()) { // Nothing we know how to increment. return false; @@ -436,7 +427,6 @@ std::cout << "f3 " << old << "\n"; // We processed the old constraints into their new forms without // problems. Apply them and we are done. constraints.set(set->index, old); -std::cout << "fl3.5 " << old << "\n"; return true; } @@ -445,32 +435,25 @@ std::cout << "fl3.5 " << old << "\n"; bool doBranch(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) const { -std::cout << "f4 (doBranch)\n"; // Extend ranges pessimistically. If the branch is x < M, and we were // x == N where N < M, then extend to x >= N && x < M // TODO: move helper matching stuff out of constraint.cpp? using namespace Abstract; if (auto* M = std::get_if(&branch.constraint.term)) { auto localConstraints = constraints.get(branch.local); -std::cout << "fl5 " << branch.constraint << " vs " << localConstraints << "\n"; if (localConstraints.size() == 1 && localConstraints[0].op == Abstract::Eq) { -std::cout << "fl5.1\n"; if (auto* N = std::get_if(&localConstraints[0].term)) { -std::cout << "fl5.2 " << (branch.constraint.op == Abstract::LtS) << " : " << *N << " ltS(*M).getUnsigned()) { constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeS, {*N}}); -std::cout << "fl5.3\n"; // TODO return true; } if (branch.constraint.op == Abstract::LtU && N->ltU(*M).getUnsigned()) { constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeU, {*N}}); -std::cout << "fl5.7\n"; // TODO -abort(); // TODO: update like abovve return true; } } @@ -482,8 +465,6 @@ abort(); // TODO: update like abovve } }; -std::cout << "fl1.5: now doing Loop flow!!1\n"; -// XXX can't continue flow, must start from scratchh before was ruinnedd doFlow(Handler()); // TODO: copy old flow data, only merge us in when we actually improve? diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 484213694be..8294f51e2e6 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -234,10 +234,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ge_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.add From 8f07a3f3024b75636ba1be34e1a9493c04a5fb45 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 11:31:09 -0700 Subject: [PATCH 037/120] work --- src/ir/constraint.cpp | 5 ++--- src/passes/ConstraintAnalysis.cpp | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 7178d5b4ee2..5c26b1e1921 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -278,9 +278,8 @@ std::optional approximateOrTermEqualPair(const Abstract::Op aOp, // Do an OR of a pair of constraints where the terms are adjacent constants: a // operations on C, and b on C+1. -std::optional approximateOrAdjacentConstantPair(const Abstract::Op aOp, - const Literal& aConstant, - const Abstract::Op bOp) { +std::optional approximateOrAdjacentConstantPair( + const Abstract::Op aOp, const Literal& aConstant, const Abstract::Op bOp) { using namespace Abstract; // x == C || x >= C+1 === x >= C diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 99b9dbd6cc0..b3f2fec27eb 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -367,7 +367,6 @@ struct ConstraintAnalysis work.push(out); } } - } } } From dd1d128b277bfb5e7ce0743015e9021ac1a0ad48 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 11:31:29 -0700 Subject: [PATCH 038/120] work --- src/passes/ConstraintAnalysis.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index b3f2fec27eb..bca67a81e3a 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -188,7 +188,9 @@ struct ConstraintAnalysis } computeRelevantLocals(); - //flowNormally(); + // Wait, why is loop flow good enough for all our tests..? is it good enough + // for real? + // flowNormally(); flowLoops(); optimize(); } From c32a8707d50df32b97135e681099218f2fb6bad8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 12:39:18 -0700 Subject: [PATCH 039/120] ConstraintAnalysis: Add more simple inequalities --- src/ir/constraint.cpp | 38 ++++++++++++++++++++++++++++++++++++++ test/gtest/constraint.cpp | 15 +++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index eb4f65e9a9d..2c60bdc1cd9 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -266,6 +266,32 @@ std::optional approximateOrTermEqualPair(const Abstract::Op aOp, return Constraint{GeS, term}; } + // x >= C || x > C === x >= C + if (aOp == GtS && bOp == GeS) { + return Constraint{GeS, term}; + } + + // TODO: all the rest + + return {}; +} + +// Do an OR of a pair of constraints where the terms are adjacent constants: a +// operations on C, and b on C+1. +std::optional approximateOrAdjacentConstantPair( + const Abstract::Op aOp, const Literal& aConstant, const Abstract::Op bOp) { + using namespace Abstract; + + // x == C || x >= C+1 === x >= C + if (aOp == Eq && bOp == GeS) { + return Constraint{GeS, aConstant}; + } + + // x > C || x >= C+1 === x > C + if (aOp == GtS && bOp == GeS) { + return Constraint{GtS, aConstant}; + } + // TODO: all the rest return {}; @@ -282,6 +308,18 @@ std::optional approximateOrPair(const Constraint& a, } } + // See if we operate on constants N, N+1. + if (auto* ac = std::get_if(&a.term)) { + if (auto* bc = std::get_if(&b.term)) { + if (ac->type == bc->type && ac->type.isInteger() && + ac->add(Literal::makeFromInt32(1, ac->type)) == *bc) { + if (auto result = approximateOrAdjacentConstantPair(a.op, *ac, b.op)) { + return result; + } + } + } + } + // If a proves b, e.g. x = 5 proves x >= 0 is true, then the OR is b. if (provesPair(a, b) == True) { return b; diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index c1e23650dd6..51e58499575 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -265,6 +265,21 @@ TEST(ConstraintTest, TestOrInequality) { // x == 5 || x >= 5 => x >= 5 checkOr(eq5, ges5, ges5); + + // x == 5 || x >= 6 => x >= 5 + AndedConstraintSet ges6{{GeS, {Literal(int32_t(6))}}}; + checkOr(eq5, ges6, ges5); + + // TODO: x == 5 || x >= 7 => x >= 5 TODO + AndedConstraintSet ges7{{GeS, {Literal(int32_t(7))}}}; + auto empty = AndedConstraintSet::makeProvesNothing(); + checkOr(eq5, ges7, empty); + + // x > 5 || x >= 6 => x > 5 + checkOr(gts5, ges6, gts5); + + // x > 5 || x >= 5 => x >= 5 + checkOr(gts5, ges5, ges5); } TEST(ConstraintTest, TestOrLoop) { From 3118ba0ad2f65330ed23c615dec889f44a41cc24 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 12:42:24 -0700 Subject: [PATCH 040/120] fix --- src/ir/constraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 2c60bdc1cd9..d0828c8bce6 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -266,7 +266,7 @@ std::optional approximateOrTermEqualPair(const Abstract::Op aOp, return Constraint{GeS, term}; } - // x >= C || x > C === x >= C + // x > C || x >= C === x >= C if (aOp == GtS && bOp == GeS) { return Constraint{GeS, term}; } From 5fe2c9ead749038169ba563eb58ca150c5f05af8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 12:42:24 -0700 Subject: [PATCH 041/120] fix --- src/ir/constraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 5c26b1e1921..33790f506d0 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -266,7 +266,7 @@ std::optional approximateOrTermEqualPair(const Abstract::Op aOp, return Constraint{GeS, term}; } - // x >= C || x > C === x >= C + // x > C || x >= C === x >= C if (aOp == GtS && bOp == GeS) { return Constraint{GeS, term}; } From d61d1d44779a9ebc150a812aebabbc3443730563 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Jul 2026 12:51:45 -0700 Subject: [PATCH 042/120] fix warning --- src/ir/constraint.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index d0828c8bce6..c0b1e315e82 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -284,12 +284,12 @@ std::optional approximateOrAdjacentConstantPair( // x == C || x >= C+1 === x >= C if (aOp == Eq && bOp == GeS) { - return Constraint{GeS, aConstant}; + return Constraint{GeS, {aConstant}}; } // x > C || x >= C+1 === x > C if (aOp == GtS && bOp == GeS) { - return Constraint{GtS, aConstant}; + return Constraint{GtS, {aConstant}}; } // TODO: all the rest From 48354874ef7d41c650c0d51c4de8b4c71639ec7e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 09:30:58 -0700 Subject: [PATCH 043/120] handle overflow --- src/ir/constraint.cpp | 10 +++++----- test/gtest/constraint.cpp | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index c0b1e315e82..afe76703a91 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -277,18 +277,18 @@ std::optional approximateOrTermEqualPair(const Abstract::Op aOp, } // Do an OR of a pair of constraints where the terms are adjacent constants: a -// operations on C, and b on C+1. +// operates on C, and b on C+1. std::optional approximateOrAdjacentConstantPair( const Abstract::Op aOp, const Literal& aConstant, const Abstract::Op bOp) { using namespace Abstract; - // x == C || x >= C+1 === x >= C - if (aOp == Eq && bOp == GeS) { + // x == C || x >= C+1 === x >= C, if C+1 does not overflow. + if (aOp == Eq && bOp == GeS && !aConstant.isSignedMax()) { return Constraint{GeS, {aConstant}}; } - // x > C || x >= C+1 === x > C - if (aOp == GtS && bOp == GeS) { + // x > C || x >= C+1 === x > C, if C+1 does not overflow. + if (aOp == GtS && bOp == GeS && !aConstant.isSignedMax()) { return Constraint{GtS, {aConstant}}; } diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 51e58499575..da1f05f2f66 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -280,6 +280,22 @@ TEST(ConstraintTest, TestOrInequality) { // x > 5 || x >= 5 => x >= 5 checkOr(gts5, ges5, ges5); + + // Careful of overflow: + // x == signed_max || x >= (signed_max + 1 === signed_min) != x >= signed_max + AndedConstraintSet eqMax{ + {Eq, {Literal(std::numeric_limits::max())}}}; + AndedConstraintSet gesMin{ + {GeS, {Literal(std::numeric_limits::min())}}}; + // TODO: x >= signed_min is always true, so this could be empty + checkOr(eqMax, gesMin, gesMin); + + // Careful of overflow: + // x > signed_max || x >= (signed_max + 1 === signed_min) != x > signed_max + AndedConstraintSet gtsMax{ + {GtS, {Literal(std::numeric_limits::max())}}}; + // TODO: x > signed_max is always false, so this could return a contradiction + checkOr(gtsMax, gesMin, empty); } TEST(ConstraintTest, TestOrLoop) { From 871d132f8ea2eaa9e362d98faf6d37952ad46bc0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 11:33:24 -0700 Subject: [PATCH 044/120] work --- src/passes/ConstraintAnalysis.cpp | 53 +++++----- .../lit/passes/constraint-analysis-loops.wast | 98 ++++++++++++++++++- 2 files changed, 124 insertions(+), 27 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index bca67a81e3a..7e5532f7066 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -397,31 +397,34 @@ struct ConstraintAnalysis } for (auto& c : old) { - if (auto* N = std::get_if(&c.term)) { - switch (c.op) { - // x == N, x++ => x == N+1. - case Eq: - // TODO: overflows here and below - c.term = Term(N->add(Literal::makeFromInt32(1, N->type))); - continue; - // x >= N, x++ => x > N - case GeS: - c.op = GtS; - continue; - case GeU: - c.op = GtU; - continue; - // x < N, x++ => x <= N - case LtS: - c.op = LeS; - continue; - case LtU: - c.op = GeU; - continue; - default: - // Something we don't recognize. - return false; - } + auto* N = std::get_if(&c.term); + if (!N) { + // A non-constant term, which we don't know how to increment. + return false; + } + switch (c.op) { + // x == N, x++ => x == N+1. + case Eq: + // TODO: overflows here and below + c.term = Term(N->add(Literal::makeFromInt32(1, N->type))); + continue; + // x >= N, x++ => x > N + case GeS: + c.op = GtS; + continue; + case GeU: + c.op = GtU; + continue; + // x < N, x++ => x <= N + case LtS: + c.op = LeS; + continue; + case LtU: + c.op = GeU; + continue; + default: + // Something we don't recognize. + return false; } } diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 8294f51e2e6..17b30c8a2e9 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -3,7 +3,7 @@ ;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s (module - ;; CHECK: (import "a" "b" (func $import (type $1) (result i32))) + ;; CHECK: (import "a" "b" (func $import (type $2) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (func $bound (type $0) @@ -150,7 +150,7 @@ ) ) - ;; CHECK: (func $bound-nonconstant-no (type $2) (param $p i32) + ;; CHECK: (func $bound-nonconstant-no (type $1) (param $p i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (drop @@ -298,5 +298,99 @@ ;; CHECK: (func $bound-incremented-2-no (type $0) ;; CHECK-NEXT: ) (func $bound-incremented-2-no + ;; TODO + ) + + ;; CHECK: (func $increment-non-constant (type $1) (param $p i32) + ;; CHECK-NEXT: (local $a i32) + ;; CHECK-NEXT: (local $b i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.lt_s + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.le_s + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $p + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $a + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $increment-non-constant (param $p i32) + (local $a i32) + (local $b i32) + (drop + (i32.lt_s + (local.get $p) + (i32.const 0) + ) + (if + (i32.le_s + (local.get $a) + (local.get $b) + ) + (then + ;; Before this set, this is what we know about $a: + ;; $a == 0 && $a <= $b + (local.set $p + (local.get $a) + ) + ;; We just set $p to $a, so now we know this about $a: + ;; $a == 0 && $a <= $b, $a == $p + ;; We then proceed to do $a++, trying to increment each of those three + ;; constraints. We should not hit an internal error on trying to increment + ;; any of the three, ending up failing on the third (though, with a higher- + ;; level view, we could use the fact that $p == 0). + (local.set $a + (i32.add + (local.get $a) + (i32.const 1) + ) + ) + ;; Since we failed to know things about $a after $a++, we cannot + ;; prove this. + (drop + (i32.eq + (local.get $a) + (i32.const 1) + ) + ) + ;; But we did not forget about $b. + (drop + (i32.eq + (local.get $b) + (i32.const 0) + ) + ) + ) + ) + ) ) ) From 2f28f67f20bb2e0274605fde46285fa91a174be2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 13:03:34 -0700 Subject: [PATCH 045/120] work --- src/passes/ConstraintAnalysis.cpp | 10 ++++++++-- src/passes/pass.cpp | 3 +++ src/passes/passes.h | 1 + test/lit/passes/constraint-analysis-loops.wast | 9 +++++++-- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 7e5532f7066..b1671181a60 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -75,9 +75,14 @@ struct ConstraintAnalysis bool requiresNonNullableLocalFixups() override { return false; } std::unique_ptr create() override { - return std::make_unique(); + return std::make_unique(loops); } + // Whether we are in "loops" mode, see above TODO move it + bool loops; + + ConstraintAnalysis(bool loops) : loops(loops) {} + using Super = WalkerPass< CFGWalker, Info>>; @@ -669,6 +674,7 @@ struct ConstraintAnalysis } // anonymous namespace -Pass* createConstraintAnalysisPass() { return new ConstraintAnalysis(); } +Pass* createConstraintAnalysisPass() { return new ConstraintAnalysis(false); } +Pass* createConstraintAnalysisLoopsPass() { return new ConstraintAnalysis(true); } } // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 34bc0c7dd40..b0a73e34b2f 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -132,6 +132,9 @@ void PassRegistry::registerPasses() { registerPass("constraint-analysis", "finds and uses mathematical constraints on locals", createConstraintAnalysisPass); + registerPass("constraint-analysis-loops", + "constraint-analysis that also optimizes loops", + createConstraintAnalysisLoopsPass); registerPass( "dce", "removes unreachable code", createDeadCodeEliminationPass); registerPass("dealign", diff --git a/src/passes/passes.h b/src/passes/passes.h index 86e506b44b8..eb7c5c22b94 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -34,6 +34,7 @@ Pass* createConstHoistingPass(); Pass* createConstantFieldPropagationPass(); Pass* createConstantFieldPropagationRefTestPass(); Pass* createConstraintAnalysisPass(); +Pass* createConstraintAnalysisLoopsPass(); Pass* createDAEPass(); Pass* createDAEOptimizingPass(); Pass* createDAE2Pass(); diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 17b30c8a2e9..3bd0e778c06 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -1,6 +1,10 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s +;; Run both normally and in the "loops" mode. Many optimizations work in both +;; modes, but some require "loops" (mentioned below where that occurs). + +;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s +;; RUN: wasm-opt %s --constraint-analysis-loops -all -S -o - | filecheck %s --check-prefix=LOOPS (module ;; CHECK: (import "a" "b" (func $import (type $2) (result i32))) @@ -269,7 +273,8 @@ ) ;; This is non-trivial, as we must rule out a possible overflow. The ;; only reason that x never gets incremented so many times that it becomes - ;; negative is that the incrementation process is stopped at 100. + ;; negative is that the incrementation process is stopped at 100. We only + ;; manage to optimize this in "loops" mode. (drop (i32.ge_s (local.get $x) From fac861bf477d7114abef618e8f9e205b15081d15 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 13:09:51 -0700 Subject: [PATCH 046/120] work --- src/passes/ConstraintAnalysis.cpp | 122 ++++++++++++++++-------------- 1 file changed, 67 insertions(+), 55 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index b1671181a60..759b708ed25 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -21,6 +21,72 @@ // assert(x != 0); // redundant and can be removed. // } // +// The normal version of this pass flows constraints around in the most precise +// way that we can. However, while doing so, it must avoid optimizing loop +// variables, because of the following problem: +// +// x = 0 +// do { +// print(x >= 0 & x < 100) +// x++ +// } while (x < 100) +// +// Say that we flow information around precisely. Then initially x is 0 at the +// top of the loop, and x++ turns it into 1. 1 < 100 so we return to the top of +// the loop, and now x can be 0 or 1. We will then interpret this loop for 100 +// iterations at compile time, which is obviously not a good idea. +// +// Instead, in "normal" mode we just don't increment variables when we see x++. +// This does limit us, but only on loops, in practice - if x is not in a loop, +// and we know its constant value, then x++ would be optimized away by other +// passes. And, by avoiding a precise execution of x++, we avoid the problem of +// interpreting loops at compile time. +// +// But we do want to optimize loops like the example above. The second part, +// x < 100, is trivial: at the loop top, either x == 0 from before the loop, or +// x < 100 from the loop backedge, and both prove x < 100. However x >= 0 is +// non-obvious: if x is *signed*, then we must rule out the possibility of it +// getting incremented so many times that it overflows and becomes negative. +// Proving that requires actually seeing that the loop variable x is incremented +// from 0 to 100, and no more, and that involves an interaction of the initial +// value, the increment, and the condition on the loop backedge. +// +// To optimize this, in "loops" mode +// A naive approach is to just interpret this code. x starts as x == 0, then +// x++ means x == 1, then at the loop top we have x >= 0 && x < 1, and so +// forth - but this is not what we want! This would literally interpret the +// code 100 times. To avoid this in "Normal" mode, we do not do anything for +// x++ - we assume the value is unknown after the increment, so x does not go +// from 0 to 1 to 2 and so forth. +// +// In "Loops" mode, we do the following things differently: +// +// * x == 0, x++ => x == 1. This happens on the first loop iteration. +// * When we see the loop backedge x < 100, which would normally be ANDed on +// top of the value of x, we instead pessimistically extend the spane of +// values to everything that would be possible in an incrementing loop. +// Specifically: +// * x == 1 && x < 100 => x > 0 && x < 100 +// (If we did a normal AND, we would end up with x == 1 here, and the loop +// would then increment x to 2 and so forth.) +// * We then run through the loop again, now starting with +// x >= 0 && x < 100 (after we merge in the x == 0 from before the loop), +// and do this: +// * x >= 0 && x < 100, x++ => x > 0 && x <= 100 +// * x > 0 && x <= 100 && x < 100 => x > 0 && x < 100 +// No further changes occur, and this is the final stable state. +// +// This is valid because the only imprecise operation we do is +// * x == 1 && x < 100 => x > 0 && x < 100 +// That is a valid inference, even if it is pessimistic and hence causes us to +// be able to prove less things. But this is useful because this pessimistic +// outcome is the common situation in a loop, so we find the proper bound on +// the loop variable here in just two iterations of the loop. +// +// At a high level, we first do the Normal flow, which is as precise as we can +// be. We then do the Loops flow afterwards, adding more information but not +// making anything worse. + #include "cfg/cfg-traversal.h" #include "ir/constraint.h" @@ -78,7 +144,7 @@ struct ConstraintAnalysis return std::make_unique(loops); } - // Whether we are in "loops" mode, see above TODO move it + // Whether we are in "loops" mode, see above. bool loops; ConstraintAnalysis(bool loops) : loops(loops) {} @@ -229,60 +295,6 @@ struct ConstraintAnalysis // Flow infos around until we have inferred all we can about the constraints // in each location. // - // We flow in one of two modes: Normal, and Loops. Normal infers constraints - // in the most precise way that we can. Loops does an analysis that is worse - // in some ways, but allows us to handle loop variable overflows. For example: - // - // x = 0 - // do { - // print(x >= 0 & x < 100) - // x++ - // } while (x < 100) - // - // This prints true 100 times. We want to be able to infer the value sent to - // print(). The second part, x < 100, is trivial: at the loop top, either x == - // 0 from before the loop, or x < 100 from the loop backedge, and both prove - // x < 100. However x >= 0 is non-obvious: if x is *signed*, then we must rule - // out the possibility of it getting incremented so many times that it - // overflows and becomes negative. Proving that requires actually seeing that - // the loop variable x is incremented from 0 to 100, and no more, and that - // involves an interaction of the initial value, the increment, and the - // condition on the loop backedge. - // - // A naive approach is to just interpret this code. x starts as x == 0, then - // x++ means x == 1, then at the loop top we have x >= 0 && x < 1, and so - // forth - but this is not what we want! This would literally interpret the - // code 100 times. To avoid this in "Normal" mode, we do not do anything for - // x++ - we assume the value is unknown after the increment, so x does not go - // from 0 to 1 to 2 and so forth. - // - // In "Loops" mode, we do the following things differently: - // - // * x == 0, x++ => x == 1. This happens on the first loop iteration. - // * When we see the loop backedge x < 100, which would normally be ANDed on - // top of the value of x, we instead pessimistically extend the spane of - // values to everything that would be possible in an incrementing loop. - // Specifically: - // * x == 1 && x < 100 => x > 0 && x < 100 - // (If we did a normal AND, we would end up with x == 1 here, and the loop - // would then increment x to 2 and so forth.) - // * We then run through the loop again, now starting with - // x >= 0 && x < 100 (after we merge in the x == 0 from before the loop), - // and do this: - // * x >= 0 && x < 100, x++ => x > 0 && x <= 100 - // * x > 0 && x <= 100 && x < 100 => x > 0 && x < 100 - // No further changes occur, and this is the final stable state. - // - // This is valid because the only imprecise operation we do is - // * x == 1 && x < 100 => x > 0 && x < 100 - // That is a valid inference, even if it is pessimistic and hence causes us to - // be able to prove less things. But this is useful because this pessimistic - // outcome is the common situation in a loop, so we find the proper bound on - // the loop variable here in just two iterations of the loop. - // - // At a high level, we first do the Normal flow, which is as precise as we can - // be. We then do the Loops flow afterwards, adding more information but not - // making anything worse. void flowNormally() { struct Handler { bool doApplyToConstraints(Expression* curr, From b55fa2a7221656f11ba2b95d15ddefacc5a38410 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 13:14:38 -0700 Subject: [PATCH 047/120] work --- src/passes/ConstraintAnalysis.cpp | 48 ++++++++++--------------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 759b708ed25..110d720eb84 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -51,42 +51,24 @@ // from 0 to 100, and no more, and that involves an interaction of the initial // value, the increment, and the condition on the loop backedge. // -// To optimize this, in "loops" mode -// A naive approach is to just interpret this code. x starts as x == 0, then -// x++ means x == 1, then at the loop top we have x >= 0 && x < 1, and so -// forth - but this is not what we want! This would literally interpret the -// code 100 times. To avoid this in "Normal" mode, we do not do anything for -// x++ - we assume the value is unknown after the increment, so x does not go -// from 0 to 1 to 2 and so forth. +// To optimize this, in "loops" mode we "jump ahead" to what is likely a loop +// limit: if we see a loop variable that is branched on, we expand the range of +// values the variable can take up to that bound. Concretely, we do this: // -// In "Loops" mode, we do the following things differently: +// * We do implement x++, turning x from 0 to 1 in the example above, in the +// first iteration of the loop. +// * When we then see x == 1 that branches with x < 100, we turn that into +// x >= 1 && x < 100. This is "imprecise", because perhaps the local will +// not actually get incremented all the way to 100, but it is an upper +// bound that ends up getting us to the result we want in common loop +// shapes. (And it is safe to do because we allow more values for x, meaning +// we can prove fewer things, so we won't prove anything false.) // -// * x == 0, x++ => x == 1. This happens on the first loop iteration. -// * When we see the loop backedge x < 100, which would normally be ANDed on -// top of the value of x, we instead pessimistically extend the spane of -// values to everything that would be possible in an incrementing loop. -// Specifically: -// * x == 1 && x < 100 => x > 0 && x < 100 -// (If we did a normal AND, we would end up with x == 1 here, and the loop -// would then increment x to 2 and so forth.) -// * We then run through the loop again, now starting with -// x >= 0 && x < 100 (after we merge in the x == 0 from before the loop), -// and do this: -// * x >= 0 && x < 100, x++ => x > 0 && x <= 100 -// * x > 0 && x <= 100 && x < 100 => x > 0 && x < 100 -// No further changes occur, and this is the final stable state. +// After doing that, we return to the top of the loop, where now we can see +// x >= 0 && x < 100. After running that through the loop a second time, no more +// changes will happen: we successfully "jumped ahead" to the end state of the +// loop variable. // -// This is valid because the only imprecise operation we do is -// * x == 1 && x < 100 => x > 0 && x < 100 -// That is a valid inference, even if it is pessimistic and hence causes us to -// be able to prove less things. But this is useful because this pessimistic -// outcome is the common situation in a loop, so we find the proper bound on -// the loop variable here in just two iterations of the loop. -// -// At a high level, we first do the Normal flow, which is as precise as we can -// be. We then do the Loops flow afterwards, adding more information but not -// making anything worse. - #include "cfg/cfg-traversal.h" #include "ir/constraint.h" From 03a82c541c4f4ccfaf3e2d15140d55e7b92eb471 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 13:15:20 -0700 Subject: [PATCH 048/120] work --- src/passes/ConstraintAnalysis.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 110d720eb84..fb68ccf48ca 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -241,10 +241,11 @@ struct ConstraintAnalysis } computeRelevantLocals(); - // Wait, why is loop flow good enough for all our tests..? is it good enough - // for real? - // flowNormally(); - flowLoops(); + if (loops) { + flowLoops(); + } else { + flowNormally(); + } optimize(); } From 71a24e8739f6c13bab55917a0234b6a15f1683d2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 13:16:53 -0700 Subject: [PATCH 049/120] work --- .../lit/passes/constraint-analysis-loops.wast | 175 +++++++++++++++++- 1 file changed, 174 insertions(+), 1 deletion(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 3bd0e778c06..7d310771b9b 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -8,6 +8,7 @@ (module ;; CHECK: (import "a" "b" (func $import (type $2) (result i32))) + ;; LOOPS: (import "a" "b" (func $import (type $2) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (func $bound (type $0) @@ -41,6 +42,37 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (call $import) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.gt_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 0) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.le_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) (func $bound (local $x i32) (loop $loop @@ -115,6 +147,37 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-flipped-ifs (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (call $import) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.le_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.gt_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 0) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) (func $bound-flipped-ifs (local $x i32) ;; As above, but with the ifs flipped. We optimize the same way. @@ -191,6 +254,43 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-nonconstant-no (type $1) (param $p i32) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.ge_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (local.get $p) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.le_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (local.get $p) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (call $import) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.gt_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (local.get $p) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.le_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) (func $bound-nonconstant-no (param $p i32) (local $x i32) ;; As above, but rather than zero we have an unknown param $p. @@ -238,7 +338,10 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.add @@ -257,6 +360,32 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-incremented (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.lt_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) (func $bound-incremented (local $x i32) (loop $loop @@ -302,6 +431,8 @@ ;; CHECK: (func $bound-incremented-2-no (type $0) ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-incremented-2-no (type $0) + ;; LOOPS-NEXT: ) (func $bound-incremented-2-no ;; TODO ) @@ -348,6 +479,48 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LOOPS: (func $increment-non-constant (type $1) (param $p i32) + ;; LOOPS-NEXT: (local $a i32) + ;; LOOPS-NEXT: (local $b i32) + ;; LOOPS-NEXT: (local $scratch i32) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (block (result i32) + ;; LOOPS-NEXT: (local.set $scratch + ;; LOOPS-NEXT: (i32.lt_s + ;; LOOPS-NEXT: (local.get $p) + ;; LOOPS-NEXT: (i32.const 0) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.le_s + ;; LOOPS-NEXT: (local.get $a) + ;; LOOPS-NEXT: (local.get $b) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (local.set $p + ;; LOOPS-NEXT: (local.get $a) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $a + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $a) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.eq + ;; LOOPS-NEXT: (local.get $a) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.get $scratch) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) (func $increment-non-constant (param $p i32) (local $a i32) (local $b i32) From 1fba05000e8dce3ff7744cfd4e6bb870a2a25b21 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 13:17:25 -0700 Subject: [PATCH 050/120] work --- src/passes/ConstraintAnalysis.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index fb68ccf48ca..a57419ffc29 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -670,6 +670,8 @@ struct ConstraintAnalysis } // anonymous namespace Pass* createConstraintAnalysisPass() { return new ConstraintAnalysis(false); } -Pass* createConstraintAnalysisLoopsPass() { return new ConstraintAnalysis(true); } +Pass* createConstraintAnalysisLoopsPass() { + return new ConstraintAnalysis(true); +} } // namespace wasm From 480e5c6e591a2a9065e37acc71b99e6e174f3903 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 13:58:15 -0700 Subject: [PATCH 051/120] work --- src/passes/ConstraintAnalysis.cpp | 258 ++++++++++++++---------------- 1 file changed, 118 insertions(+), 140 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index a57419ffc29..bd7dd213aae 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -241,11 +241,7 @@ struct ConstraintAnalysis } computeRelevantLocals(); - if (loops) { - flowLoops(); - } else { - flowNormally(); - } + flow(); optimize(); } @@ -277,33 +273,7 @@ struct ConstraintAnalysis // Flow infos around until we have inferred all we can about the constraints // in each location. - // - void flowNormally() { - struct Handler { - bool doApplyToConstraints(Expression* curr, - BasicBlockConstraintMap& constraints) const { - // Nothing custom here; use the default behavior. - return false; - } - - bool doBranch(const LocalConstraint& branch, - BasicBlockConstraintMap& constraints) const { - // Nothing custom here; use the default behavior. - return false; - } - }; - - doFlow(Handler()); - } - - // Given a worklist initialized to the starting point, keep processing it - // until nothing remains. A handler is provided with two hooks, - // doApplyToConstraints and doBranch, each of which returns true if it handled - // the inputs (if not, we run the default behavior). - template // can we template on the function itself? is this - // already fast? - void doFlow(const T& handler) { - + void flow() { // Start from the entry as the only reachable block. That block has incoming // values - defaults - for each var. entry->contents.startConstraints.setReachable(); @@ -338,10 +308,7 @@ struct ConstraintAnalysis // Start at the top of the block, then go through, applying things. BasicBlockConstraintMap constraints = block->contents.startConstraints; for (auto** currp : block->contents.actions) { - // Try the handler first. - if (!handler.doApplyToConstraints(*currp, constraints)) { - applyToConstraints(*currp, constraints); - } + applyToConstraints(*currp, constraints); } // We now know the values at the end of the block. Flow it onward, and @@ -354,9 +321,7 @@ struct ConstraintAnalysis if (auto branch = getBranchConstraints(block, out); branch && checkRelevancy(*branch)) { auto sentConstraints = constraints; - if (!handler.doBranch(*branch, sentConstraints)) { - sentConstraints.approximateAnd(branch->local, branch->constraint); - } + applyBranchConstraints(*branch, sentConstraints); // If anything changed at the start of the target block, flow onwards. if (outStartConstraints.approximateOr(sentConstraints)) { @@ -373,107 +338,6 @@ struct ConstraintAnalysis } } - void flowLoops() { - - struct Handler { - bool doApplyToConstraints(Expression* curr, - BasicBlockConstraintMap& constraints) const { - using namespace Match; - using namespace Abstract; - - auto* set = curr->dynCast(); - if (!set) { - return false; - } - - // Operate on x++s. - // x = y + 1 - Index y; - if (matches(set->value, binary(Abstract::Add, local(&y), ival(1)))) { - auto old = constraints.get(y); - if (old.empty()) { - // Nothing we know how to increment. - return false; - } - - for (auto& c : old) { - auto* N = std::get_if(&c.term); - if (!N) { - // A non-constant term, which we don't know how to increment. - return false; - } - switch (c.op) { - // x == N, x++ => x == N+1. - case Eq: - // TODO: overflows here and below - c.term = Term(N->add(Literal::makeFromInt32(1, N->type))); - continue; - // x >= N, x++ => x > N - case GeS: - c.op = GtS; - continue; - case GeU: - c.op = GtU; - continue; - // x < N, x++ => x <= N - case LtS: - c.op = LeS; - continue; - case LtU: - c.op = GeU; - continue; - default: - // Something we don't recognize. - return false; - } - } - - // We processed the old constraints into their new forms without - // problems. Apply them and we are done. - constraints.set(set->index, old); - return true; - } - - return false; - } - - bool doBranch(const LocalConstraint& branch, - BasicBlockConstraintMap& constraints) const { - // Extend ranges pessimistically. If the branch is x < M, and we were - // x == N where N < M, then extend to x >= N && x < M - // TODO: move helper matching stuff out of constraint.cpp? - using namespace Abstract; - if (auto* M = std::get_if(&branch.constraint.term)) { - auto localConstraints = constraints.get(branch.local); - if (localConstraints.size() == 1 && - localConstraints[0].op == Abstract::Eq) { - if (auto* N = std::get_if(&localConstraints[0].term)) { - if (branch.constraint.op == Abstract::LtS && - N->ltS(*M).getUnsigned()) { - constraints.set(branch.local, branch.constraint); - constraints.approximateAnd(branch.local, {GeS, {*N}}); - return true; - } - if (branch.constraint.op == Abstract::LtU && - N->ltU(*M).getUnsigned()) { - constraints.set(branch.local, branch.constraint); - constraints.approximateAnd(branch.local, {GeU, {*N}}); - return true; - } - } - } - } - - // We did nothing custom; use the default behavior. - return false; - } - }; - - doFlow(Handler()); - - // TODO: copy old flow data, only merge us in when we actually improve? - } - // After inferring all we can, apply it to optimize the code. void optimize() { // If we make things unreachable, we must refinalize. @@ -620,11 +484,17 @@ struct ConstraintAnalysis // sets the value for that local. void applyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) { + // In "loops" mode, apply an increment if this is one. + if (loops && applyIncrementToConstraints(curr, constraints)) { + return; + } + if (auto* set = curr->dynCast()) { if (!relevantLocals[set->index]) { // No point to apply a constraint to an irrelevant local. return; } + if (Properties::isSingleConstantExpression(set->value)) { // Apply a constraint to this value. auto value = Properties::getLiteral(set->value); @@ -665,6 +535,114 @@ struct ConstraintAnalysis } return true; } + + // Apply an increment, in loops mode. Returns true if we found and applied + // one. + bool applyIncrementToConstraints(Expression* curr, + BasicBlockConstraintMap& constraints) const { + assert(loops); + + using namespace Match; + using namespace Abstract; + + auto* set = curr->dynCast(); + if (!set) { + return false; + } + + // x = y + 1 + Index y; + if (!matches(set->value, binary(Abstract::Add, local(&y), ival(1)))) { + return false; + } + + auto old = constraints.get(y); + if (old.empty()) { + // Nothing we know how to increment. + return false; + } + + for (auto& c : old) { + auto* N = std::get_if(&c.term); + if (!N) { + // A non-constant term, which we don't know how to increment. + return false; + } + + switch (c.op) { + // x == N, x++ => x == N+1. + case Eq: + // TODO: overflows here and below + c.term = Term(N->add(Literal::makeFromInt32(1, N->type))); + continue; + // x >= N, x++ => x > N + case GeS: + c.op = GtS; + continue; + case GeU: + c.op = GtU; + continue; + // x < N, x++ => x <= N + case LtS: + c.op = LeS; + continue; + case LtU: + c.op = GeU; + continue; + default: + // Something we don't recognize. + return false; + } + } + + // We processed the old constraints into their new forms without + // problems. Apply them and we are done. + constraints.set(set->index, old); + return true; + } + + // Apply branch constraints to the current set of constraints. + void applyBranchConstraints(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { + // In "loops" mode, extend the range of values in the "jump ahead" manner. + if (loops && applyBranchRangeExtensionToConstraints(branch, constraints)) { + return; + } + + constraints.approximateAnd(branch.local, branch.constraint); + } + + bool applyBranchRangeExtensionToConstraints(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { + assert(loops); + + using namespace Abstract; + + // "Jump ahead" and extend ranges. If the branch is x < M, and we were + // x == N where N < M, then extend to x >= N && x < M, as described in the + // top level comment. + // TODO: move helper matching stuff out of constraint.cpp? + if (auto* M = std::get_if(&branch.constraint.term)) { + auto localConstraints = constraints.get(branch.local); + if (localConstraints.size() == 1 && + localConstraints[0].op == Abstract::Eq) { + if (auto* N = std::get_if(&localConstraints[0].term)) { + if (branch.constraint.op == Abstract::LtS && + N->ltS(*M).getUnsigned()) { + constraints.set(branch.local, branch.constraint); + constraints.approximateAnd(branch.local, {GeS, {*N}}); + return true; + } + if (branch.constraint.op == Abstract::LtU && + N->ltU(*M).getUnsigned()) { + constraints.set(branch.local, branch.constraint); + constraints.approximateAnd(branch.local, {GeU, {*N}}); + return true; + } + } + } + } + + return false; + } }; } // anonymous namespace From c3d65f37ae3a98bcada1de923ae5daf06a144512 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 13:58:23 -0700 Subject: [PATCH 052/120] work --- src/passes/ConstraintAnalysis.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index bd7dd213aae..d587e2511ac 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -539,7 +539,7 @@ struct ConstraintAnalysis // Apply an increment, in loops mode. Returns true if we found and applied // one. bool applyIncrementToConstraints(Expression* curr, - BasicBlockConstraintMap& constraints) const { + BasicBlockConstraintMap& constraints) const { assert(loops); using namespace Match; @@ -602,7 +602,8 @@ struct ConstraintAnalysis } // Apply branch constraints to the current set of constraints. - void applyBranchConstraints(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { + void applyBranchConstraints(const LocalConstraint& branch, + BasicBlockConstraintMap& constraints) { // In "loops" mode, extend the range of values in the "jump ahead" manner. if (loops && applyBranchRangeExtensionToConstraints(branch, constraints)) { return; @@ -611,7 +612,9 @@ struct ConstraintAnalysis constraints.approximateAnd(branch.local, branch.constraint); } - bool applyBranchRangeExtensionToConstraints(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { + bool + applyBranchRangeExtensionToConstraints(const LocalConstraint& branch, + BasicBlockConstraintMap& constraints) { assert(loops); using namespace Abstract; From dd6730c5be9662e558c9d440352d29f217ecccf2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 14:00:07 -0700 Subject: [PATCH 053/120] work --- src/passes/ConstraintAnalysis.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index d587e2511ac..723021edc48 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -351,7 +351,6 @@ struct ConstraintAnalysis for (auto** currp : block->contents.actions) { if (!constraints.unreachable) { applyToConstraints(*currp, constraints); - // TODO: can apply x++ here too optimizeExpression(currp, constraints); } else { // This is unreachable code: just mark it so. From 634b0d3e55bd250c9d0e59182b4ad3ee3cad7c09 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 14:07:04 -0700 Subject: [PATCH 054/120] work --- src/passes/ConstraintAnalysis.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 723021edc48..e75024f9aa2 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -69,6 +69,14 @@ // changes will happen: we successfully "jumped ahead" to the end state of the // loop variable. // +// In theory, "normal" mode may optimize some things better than "loops" mode, +// as the "jump ahead" behavior extends ranges of variables eagerly, before we +// know they actually can fill out that range. In practice, however, it is rare +// to see a constant to which is applied a bound like x < 100, unless it is +// actually a loop variable: if it isn't a loop variable, then other passes +// would propagate the constant and remove the bounds check. Still, both modes +// of this pass are kept for comparison purposes. +// #include "cfg/cfg-traversal.h" #include "ir/constraint.h" From b60cf18bc6ecb35589208564049231a04ed3af37 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 14:07:39 -0700 Subject: [PATCH 055/120] work --- src/passes/ConstraintAnalysis.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index e75024f9aa2..9186b80b01a 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -629,7 +629,6 @@ struct ConstraintAnalysis // "Jump ahead" and extend ranges. If the branch is x < M, and we were // x == N where N < M, then extend to x >= N && x < M, as described in the // top level comment. - // TODO: move helper matching stuff out of constraint.cpp? if (auto* M = std::get_if(&branch.constraint.term)) { auto localConstraints = constraints.get(branch.local); if (localConstraints.size() == 1 && From 45484f1c80f70f5d4119a1a5ea8fa8ed32111390 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 14:08:29 -0700 Subject: [PATCH 056/120] work --- src/passes/ConstraintAnalysis.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 9186b80b01a..51581d1e1ef 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -640,12 +640,14 @@ struct ConstraintAnalysis constraints.approximateAnd(branch.local, {GeS, {*N}}); return true; } +#if 0 if (branch.constraint.op == Abstract::LtU && N->ltU(*M).getUnsigned()) { constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeU, {*N}}); return true; } +#endif } } } From ac6653e21814c07b3dec2a4da00908b90300e1c2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 14:12:01 -0700 Subject: [PATCH 057/120] work --- src/passes/ConstraintAnalysis.cpp | 2 - .../lit/passes/constraint-analysis-loops.wast | 90 ++++++++++++++++++- 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 51581d1e1ef..9186b80b01a 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -640,14 +640,12 @@ struct ConstraintAnalysis constraints.approximateAnd(branch.local, {GeS, {*N}}); return true; } -#if 0 if (branch.constraint.op == Abstract::LtU && N->ltU(*M).getUnsigned()) { constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeU, {*N}}); return true; } -#endif } } } diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 7d310771b9b..644f216baa2 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -429,12 +429,94 @@ ) ) - ;; CHECK: (func $bound-incremented-2-no (type $0) + ;; CHECK: (func $bound-incremented-unsigned (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ge_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.lt_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented-2-no (type $0) + ;; LOOPS: (func $bound-incremented-unsigned (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.ge_u + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 0) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.const 0) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) ;; LOOPS-NEXT: ) - (func $bound-incremented-2-no - ;; TODO + (func $bound-incremented-unsigned + ;; As above, but with unsigned operations. This is simpler, and we optimize + ;; it even without "loops" mode. + (local $x i32) + (loop $loop + (drop + (i32.lt_u + (local.get $x) + (i32.const 100) + ) + ) + (drop + (i32.ge_u + (local.get $x) + (i32.const 0) + ) + ) + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (if + (i32.lt_u + (local.get $x) + (i32.const 100) + ) + (then + (br $loop) + ) + ) + ) ) ;; CHECK: (func $increment-non-constant (type $1) (param $p i32) From 0c0f908b3642911606f2f88d17a24f7d638a07af Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 14:14:46 -0700 Subject: [PATCH 058/120] work --- src/passes/ConstraintAnalysis.cpp | 2 +- test/lit/passes/constraint-analysis-loops.wast | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 9186b80b01a..e54a7a07b3d 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -594,7 +594,7 @@ struct ConstraintAnalysis c.op = LeS; continue; case LtU: - c.op = GeU; + c.op = LeU; continue; default: // Something we don't recognize. diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 644f216baa2..51d18aa5d57 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -477,7 +477,10 @@ ;; LOOPS-NEXT: ) ;; LOOPS-NEXT: ) ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.const 0) + ;; LOOPS-NEXT: (i32.lt_u + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) ;; LOOPS-NEXT: (then ;; LOOPS-NEXT: (br $loop) ;; LOOPS-NEXT: ) From 3e9c83ef5abc2e0795b6774791d6337c51f9499f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 14:17:44 -0700 Subject: [PATCH 059/120] work --- src/ir/constraint.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index ee4b5715a8f..f27a0fd38ee 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -265,11 +265,17 @@ std::optional approximateOrTermEqualPair(const Abstract::Op aOp, if (aOp == Eq && bOp == GtS) { return Constraint{GeS, term}; } + if (aOp == Eq && bOp == GtU) { + return Constraint{GeU, term}; + } // x > C || x >= C === x >= C if (aOp == GtS && bOp == GeS) { return Constraint{GeS, term}; } + if (aOp == GtS && bOp == GeU) { + return Constraint{GeU, term}; + } // TODO: all the rest @@ -286,11 +292,17 @@ std::optional approximateOrAdjacentConstantPair( if (aOp == Eq && bOp == GeS && !aConstant.isSignedMax()) { return Constraint{GeS, {aConstant}}; } + if (aOp == Eq && bOp == GeU && !aConstant.isUnsignedMax()) { + return Constraint{GeU, {aConstant}}; + } // x > C || x >= C+1 === x > C, if C+1 does not overflow. if (aOp == GtS && bOp == GeS && !aConstant.isSignedMax()) { return Constraint{GtS, {aConstant}}; } + if (aOp == GtU && bOp == GeU && !aConstant.isUnsignedMax()) { + return Constraint{GtU, {aConstant}}; + } // TODO: all the rest From 5a261aace8700162c8a13bbec99f8b030d021430 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 14:25:29 -0700 Subject: [PATCH 060/120] work --- test/gtest/constraint.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index da1f05f2f66..e3faa032895 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -348,10 +348,12 @@ TEST(ConstraintTest, TestOrLoop) { checkOr(leftNe, right, empty); // Change the GtS on the right to GtU: - // { x == 5 } || { x >U 5 && x <= 42 } ==> { x <= 42 } + // { x == 5 } || { x >U 5 && x <= 42 } ==> { x >=U 5 && x <= 42 } AndedConstraintSet rightGtU( {{GtU, {Literal(int32_t(5))}}, {LeS, {Literal(int32_t(42))}}}); - checkOr(left, rightGtU, rightOnly42); + AndedConstraintSet resultMixed( + {{GeU, {Literal(int32_t(5))}}, {LeS, {Literal(int32_t(42))}}}); + checkOr(left, rightGtU, resultMixed); // Change the LeS on the right to LeU: // { x == 5 } || { x > 5 && x <=U 42 } ==> { x >= 5 && x <=U 42 } From 762c612ec95e98a6e895aa032069face2f907b96 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 15:05:33 -0700 Subject: [PATCH 061/120] work --- src/passes/ConstraintAnalysis.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index e54a7a07b3d..586a76f69fb 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -93,6 +93,12 @@ #include "wasm-builder.h" #include "wasm.h" +#define CONSTRAINT_DEBUG 1 + +#ifndef CONSTRAINT_DEBUG +#define CONSTRAINT_DEBUG 0 +#endif + namespace wasm { using namespace wasm::constraint; @@ -315,10 +321,19 @@ struct ConstraintAnalysis // Start at the top of the block, then go through, applying things. BasicBlockConstraintMap constraints = block->contents.startConstraints; + +#if CONSTRAINT_DEBUG + std::cout << block << " start constraints: " << constraints << '\n'; +#endif + for (auto** currp : block->contents.actions) { applyToConstraints(*currp, constraints); } +#if CONSTRAINT_DEBUG + std::cout << block << " end constraints: " << constraints << '\n'; +#endif + // We now know the values at the end of the block. Flow it onward, and // where it causes changes, queue more work. for (auto* out : block->out) { From cd6f9f15907af05d450c073ee6f9f916e3b1ec62 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 15:15:34 -0700 Subject: [PATCH 062/120] work --- src/passes/ConstraintAnalysis.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 586a76f69fb..27cf30d4863 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -345,15 +345,24 @@ struct ConstraintAnalysis branch && checkRelevancy(*branch)) { auto sentConstraints = constraints; applyBranchConstraints(*branch, sentConstraints); +#if CONSTRAINT_DEBUG + std::cout << block << " sending branch to " << out << " with sent constraints: " << sentConstraints << '\n'; +#endif // If anything changed at the start of the target block, flow onwards. if (outStartConstraints.approximateOr(sentConstraints)) { +#if CONSTRAINT_DEBUG + std::cout << block << " branch-modified " << out << " to start with: " << outStartConstraints << '\n'; +#endif work.push(out); } } else { // There are no specific branch constraints, so send the unmodified // |constraints|, avoiding a copy. if (outStartConstraints.approximateOr(constraints)) { +#if CONSTRAINT_DEBUG + std::cout << block << " modified " << out << " to start with: " << outStartConstraints << '\n'; +#endif work.push(out); } } From 8fadc2bf73b097221c0c6d1c60b8b6917ee49be5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 15:32:33 -0700 Subject: [PATCH 063/120] work --- src/ir/constraint.cpp | 2 +- src/passes/ConstraintAnalysis.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index f27a0fd38ee..e11bff05446 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -273,7 +273,7 @@ std::optional approximateOrTermEqualPair(const Abstract::Op aOp, if (aOp == GtS && bOp == GeS) { return Constraint{GeS, term}; } - if (aOp == GtS && bOp == GeU) { + if (aOp == GtU && bOp == GeU) { return Constraint{GeU, term}; } diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 27cf30d4863..8f223e741dc 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -381,6 +381,9 @@ struct ConstraintAnalysis // of course not needed at this stage.) auto& constraints = block->contents.startConstraints; for (auto** currp : block->contents.actions) { +#if CONSTRAINT_DEBUG + std::cout << block << " trying to optimize " << **currp << '\n'; +#endif if (!constraints.unreachable) { applyToConstraints(*currp, constraints); optimizeExpression(currp, constraints); From 4c27988599206014663610c22ce4b7e90e9dc6e4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 15:32:42 -0700 Subject: [PATCH 064/120] work --- src/passes/ConstraintAnalysis.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 8f223e741dc..da5181ea87e 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -346,13 +346,15 @@ struct ConstraintAnalysis auto sentConstraints = constraints; applyBranchConstraints(*branch, sentConstraints); #if CONSTRAINT_DEBUG - std::cout << block << " sending branch to " << out << " with sent constraints: " << sentConstraints << '\n'; + std::cout << block << " sending branch to " << out + << " with sent constraints: " << sentConstraints << '\n'; #endif // If anything changed at the start of the target block, flow onwards. if (outStartConstraints.approximateOr(sentConstraints)) { #if CONSTRAINT_DEBUG - std::cout << block << " branch-modified " << out << " to start with: " << outStartConstraints << '\n'; + std::cout << block << " branch-modified " << out + << " to start with: " << outStartConstraints << '\n'; #endif work.push(out); } @@ -361,7 +363,8 @@ struct ConstraintAnalysis // |constraints|, avoiding a copy. if (outStartConstraints.approximateOr(constraints)) { #if CONSTRAINT_DEBUG - std::cout << block << " modified " << out << " to start with: " << outStartConstraints << '\n'; + std::cout << block << " modified " << out + << " to start with: " << outStartConstraints << '\n'; #endif work.push(out); } From 778a64a0c0c941f383191b7aab1034e3df11a4a3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 15:32:53 -0700 Subject: [PATCH 065/120] work --- test/lit/passes/constraint-analysis-loops.wast | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 51d18aa5d57..fb2faeb1fa2 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -465,10 +465,7 @@ ;; LOOPS-NEXT: (i32.const 1) ;; LOOPS-NEXT: ) ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.ge_u - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 0) - ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (i32.const 1) ;; LOOPS-NEXT: ) ;; LOOPS-NEXT: (local.set $x ;; LOOPS-NEXT: (i32.add From 741ee9aae3bd68c92a7c569f069b7f7361c34c70 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 15:44:59 -0700 Subject: [PATCH 066/120] more --- src/ir/constraint.cpp | 12 ++++++++ test/gtest/constraint.cpp | 65 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index afe76703a91..b2b018500d3 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -265,11 +265,17 @@ std::optional approximateOrTermEqualPair(const Abstract::Op aOp, if (aOp == Eq && bOp == GtS) { return Constraint{GeS, term}; } + if (aOp == Eq && bOp == GtU) { + return Constraint{GeU, term}; + } // x > C || x >= C === x >= C if (aOp == GtS && bOp == GeS) { return Constraint{GeS, term}; } + if (aOp == GtU && bOp == GeU) { + return Constraint{GeU, term}; + } // TODO: all the rest @@ -286,11 +292,17 @@ std::optional approximateOrAdjacentConstantPair( if (aOp == Eq && bOp == GeS && !aConstant.isSignedMax()) { return Constraint{GeS, {aConstant}}; } + if (aOp == Eq && bOp == GeU && !aConstant.isUnsignedMax()) { + return Constraint{GeU, {aConstant}}; + } // x > C || x >= C+1 === x > C, if C+1 does not overflow. if (aOp == GtS && bOp == GeS && !aConstant.isSignedMax()) { return Constraint{GtS, {aConstant}}; } + if (aOp == GtU && bOp == GeU && !aConstant.isUnsignedMax()) { + return Constraint{GtU, {aConstant}}; + } // TODO: all the rest diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index da1f05f2f66..0b534d12efb 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -348,10 +348,12 @@ TEST(ConstraintTest, TestOrLoop) { checkOr(leftNe, right, empty); // Change the GtS on the right to GtU: - // { x == 5 } || { x >U 5 && x <= 42 } ==> { x <= 42 } + // { x == 5 } || { x >U 5 && x <= 42 } ==> { x >=U 5 && x <= 42 } AndedConstraintSet rightGtU( {{GtU, {Literal(int32_t(5))}}, {LeS, {Literal(int32_t(42))}}}); - checkOr(left, rightGtU, rightOnly42); + AndedConstraintSet resultMixed( + {{GeU, {Literal(int32_t(5))}}, {LeS, {Literal(int32_t(42))}}}); + checkOr(left, rightGtU, resultMixed); // Change the LeS on the right to LeU: // { x == 5 } || { x > 5 && x <=U 42 } ==> { x >= 5 && x <=U 42 } @@ -373,6 +375,65 @@ TEST(ConstraintTest, TestOrLoop) { checkOr(left, rightAdded, resultAdded); } +TEST(ConstraintTest, TestOrLoopUnsigned) { + // As above, but unsigned. + + // { x == 5 } || { x > 5 && x <= 42 } ==> { x >= 5 && x <= 42 } + AndedConstraintSet left{{Eq, {Literal(int32_t(5))}}}; + AndedConstraintSet right( + {{GtU, {Literal(int32_t(5))}}, {LeU, {Literal(int32_t(42))}}}); + AndedConstraintSet result( + {{GeU, {Literal(int32_t(5))}}, {LeU, {Literal(int32_t(42))}}}); + checkOr(left, right, result); + + // Changes to constants: + + // Change 5 on the left to 7: + // { x == 7 } || { x > 5 && x <= 42 } ==> { x > 5 && x <= 42} + AndedConstraintSet left7{{Eq, {Literal(int32_t(7))}}}; + checkOr(left7, right, right); + + // Change 5 on the left to 99: + // { x == 99 } || { x > 5 && x <= 42 } ==> { x > 5 } + // TODO: we could emit a range (5, 99] + AndedConstraintSet left99{{Eq, {Literal(int32_t(99))}}}; + AndedConstraintSet rightOnly5{{GtU, {Literal(int32_t(5))}}}; + checkOr(left99, right, rightOnly5); + + // Change 5 on the left to 4: + // { x == 4 } || { x > 5 && x <= 42 } ==> { x <= 42 } + // TODO: we could emit a range [4, 42] + AndedConstraintSet left4{{Eq, {Literal(int32_t(4))}}}; + AndedConstraintSet rightOnly42({{LeU, {Literal(int32_t(42))}}}); + checkOr(left4, right, rightOnly42); + + // Change 5 on the right to 6: + // { x == 5 } || { x > 6 && x <= 42 } ==> { x <= 42 } + AndedConstraintSet right6( + {{GtU, {Literal(int32_t(6))}}, {LeU, {Literal(int32_t(42))}}}); + checkOr(left, right6, rightOnly42); + + // Changes to operations: + + // Change the Eq on the left to Ne. We fail to find anything for the OR. + // { x != 5 } || { x > 5 && x <= 42 } ==> {} + // TODO: we could emit x != 5 + AndedConstraintSet leftNe{{Ne, {Literal(int32_t(5))}}}; + auto empty = AndedConstraintSet::makeProvesNothing(); + checkOr(leftNe, right, empty); + + // Add an operation on the right, x != 21: + // { x == 5 } || { x > 5 && x <= 42 && x != 21 } ==> + // { x >= 5 && x <= 42 && x != 21 } + AndedConstraintSet rightAdded({{GtU, {Literal(int32_t(5))}}, + {LeU, {Literal(int32_t(42))}}, + {Ne, {Literal(int32_t(21))}}}); + AndedConstraintSet resultAdded({{GeU, {Literal(int32_t(5))}}, + {LeU, {Literal(int32_t(42))}}, + {Ne, {Literal(int32_t(21))}}}); + checkOr(left, rightAdded, resultAdded); +} + static void checkAnd(const AndedConstraintSet& a, const AndedConstraintSet& b, const AndedConstraintSet& result) { From fd1fcb41a56564fa7506512a53dc5d75ab181737 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 16:26:12 -0700 Subject: [PATCH 067/120] work --- .../lit/passes/constraint-analysis-loops.wast | 107 +++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index fb2faeb1fa2..62c5d5286c7 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -389,7 +389,7 @@ (func $bound-incremented (local $x i32) (loop $loop - ;; A realistic loop, with an $x++ and a single bounds check. We must infer + ;; A realistic do-while loop, with $x++ and a bounds check. We must infer ;; that no overflow happens in order to prove these two checks are true. ;; ;; The first is trivially true, as x starts at 0 - fulfilling x < 100 - @@ -653,4 +653,109 @@ ) ) ) + + ;; CHECK: (func $bound-incremented-while (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $out) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-incremented-while (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (block $out + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.ge_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $out) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $bound-incremented-while + ;; Similar to above, but before we had a do-while loop (loop condition at + ;; the bottom) and now it is at the top. + (local $x i32) + (block $out + (loop $loop + ;; Conditional branch at the top. + (if + (i32.ge_s + (local.get $x) + (i32.const 100) + ) + (then + (br $out) + ) + ) + ;; We can infer both of these to be true in loops mode (in normal mode, + ;; only the easy one, the first). + (drop + (i32.lt_s + (local.get $x) + (i32.const 100) + ) + ) + (drop + (i32.ge_s + (local.get $x) + (i32.const 0) + ) + ) + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + ;; Unconditional branch at the bottom. + (br $loop) + ) + ) + ) + + ;; TODO: with the increment before the condition ) From 1916871b7271858571ec19e8fa0912defa2ee3ac Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 16:31:36 -0700 Subject: [PATCH 068/120] work --- src/passes/ConstraintAnalysis.cpp | 2 + .../lit/passes/constraint-analysis-loops.wast | 102 +++++++++++++++++- 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index da5181ea87e..c7874818127 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -691,4 +691,6 @@ Pass* createConstraintAnalysisLoopsPass() { return new ConstraintAnalysis(true); } +// see a.txt + } // namespace wasm diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 62c5d5286c7..0b21c5d4796 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -757,5 +757,105 @@ ) ) - ;; TODO: with the increment before the condition + ;; CHECK: (func $bound-incremented-inc-first (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $out) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.gt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-incremented-inc-first (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (block $out + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.ge_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $out) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $bound-incremented-inc-first + ;; Similar to the above "while" loop, but now with the increment before the + ;; if. + (local $x i32) + (block $out + (loop $loop + ;; Increment at the top. + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + ;; If after the increment. + (if + (i32.ge_s + (local.get $x) + (i32.const 100) + ) + (then + (br $out) + ) + ) + ;; x > 0 && x < 100 here (0 is impossible, compared to before). + (drop + (i32.lt_s + (local.get $x) + (i32.const 100) + ) + ) + (drop + (i32.gt_s + (local.get $x) + (i32.const 0) + ) + ) + (br $loop) + ) + ) + ) ) From b1e29341fdc75c891dfe754368fc2a32848370e1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Jul 2026 16:35:15 -0700 Subject: [PATCH 069/120] work --- .../lit/passes/constraint-analysis-loops.wast | 110 +++++++++++++++++- 1 file changed, 107 insertions(+), 3 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 0b21c5d4796..25ba33cfab5 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -777,14 +777,14 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.gt_s ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $loop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -842,18 +842,122 @@ ) ) ;; x > 0 && x < 100 here (0 is impossible, compared to before). + (drop + (i32.gt_s + (local.get $x) + (i32.const 0) + ) + ) (drop (i32.lt_s (local.get $x) (i32.const 100) ) ) + (br $loop) + ) + ) + ) + + ;; CHECK: (func $bound-incremented-inc-first-less (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $out) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.gt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-incremented-inc-first-less (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (block $out + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.gt_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $out) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.gt_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 0) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $bound-incremented-inc-first-less + ;; As in the last testcase, but the if's condition changed. + (local $x i32) + (block $out + (loop $loop + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + ;; Before we left the loop when x >= 100. Now we leave then x > 100, + ;; so we do actually reach 100 in the code below. + (if + (i32.gt_s + (local.get $x) + (i32.const 100) + ) + (then + (br $out) + ) + ) + ;; x > 0 && x <= 100 here. (drop (i32.gt_s (local.get $x) (i32.const 0) ) ) + (drop + (i32.le_s + (local.get $x) + (i32.const 100) + ) + ) (br $loop) ) ) From 6502d51281b838b0eb9e885bd99f846bb6bcc498 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 3 Aug 2026 09:55:08 -0700 Subject: [PATCH 070/120] work --- src/passes/ConstraintAnalysis.cpp | 3 ++- test/lit/passes/constraint-analysis-loops.wast | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index c7874818127..e3c23e7772d 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -349,10 +349,11 @@ struct ConstraintAnalysis std::cout << block << " sending branch to " << out << " with sent constraints: " << sentConstraints << '\n'; #endif - +std::cout << "out's start before " << outStartConstraints << '\n'; // If anything changed at the start of the target block, flow onwards. if (outStartConstraints.approximateOr(sentConstraints)) { #if CONSTRAINT_DEBUG +std::cout << "out's start after " << outStartConstraints << '\n'; std::cout << block << " branch-modified " << out << " to start with: " << outStartConstraints << '\n'; #endif diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 25ba33cfab5..87a6d15b952 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -934,7 +934,7 @@ (i32.const 1) ) ) - ;; Before we left the loop when x >= 100. Now we leave then x > 100, + ;; Before we left the loop when x >= 100. Now we leave when x > 100, ;; so we do actually reach 100 in the code below. (if (i32.gt_s From 144440608c89eba3257b30acbc08dd60afbf1f12 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 3 Aug 2026 11:47:54 -0700 Subject: [PATCH 071/120] work --- src/passes/ConstraintAnalysis.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index e3c23e7772d..045180078c9 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -665,14 +665,18 @@ std::cout << "out's start after " << outStartConstraints << '\n'; if (localConstraints.size() == 1 && localConstraints[0].op == Abstract::Eq) { if (auto* N = std::get_if(&localConstraints[0].term)) { - if (branch.constraint.op == Abstract::LtS && - N->ltS(*M).getUnsigned()) { + // We can handle both x < M as the branch, as described above, or + // x <= M (if N <= M). + if ((branch.constraint.op == Abstract::LtS && + N->ltS(*M).getUnsigned()) || (branch.constraint.op == Abstract::LeS && + N->leS(*M).getUnsigned())) { constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeS, {*N}}); return true; } - if (branch.constraint.op == Abstract::LtU && - N->ltU(*M).getUnsigned()) { + if ((branch.constraint.op == Abstract::LtU && + N->ltU(*M).getUnsigned()) || (branch.constraint.op == Abstract::LeU && + N->leU(*M).getUnsigned())) { constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeU, {*N}}); return true; From ae5d9ee6b9e5c94b14de643b1bb6bc6117ccdefc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 3 Aug 2026 11:48:00 -0700 Subject: [PATCH 072/120] work --- src/passes/ConstraintAnalysis.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 045180078c9..79b2427bf7f 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -349,11 +349,11 @@ struct ConstraintAnalysis std::cout << block << " sending branch to " << out << " with sent constraints: " << sentConstraints << '\n'; #endif -std::cout << "out's start before " << outStartConstraints << '\n'; + std::cout << "out's start before " << outStartConstraints << '\n'; // If anything changed at the start of the target block, flow onwards. if (outStartConstraints.approximateOr(sentConstraints)) { #if CONSTRAINT_DEBUG -std::cout << "out's start after " << outStartConstraints << '\n'; + std::cout << "out's start after " << outStartConstraints << '\n'; std::cout << block << " branch-modified " << out << " to start with: " << outStartConstraints << '\n'; #endif @@ -668,15 +668,17 @@ std::cout << "out's start after " << outStartConstraints << '\n'; // We can handle both x < M as the branch, as described above, or // x <= M (if N <= M). if ((branch.constraint.op == Abstract::LtS && - N->ltS(*M).getUnsigned()) || (branch.constraint.op == Abstract::LeS && + N->ltS(*M).getUnsigned()) || + (branch.constraint.op == Abstract::LeS && N->leS(*M).getUnsigned())) { constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeS, {*N}}); return true; } if ((branch.constraint.op == Abstract::LtU && - N->ltU(*M).getUnsigned()) || (branch.constraint.op == Abstract::LeU && - N->leU(*M).getUnsigned())) { + N->ltU(*M).getUnsigned()) || + (branch.constraint.op == Abstract::LeU && + N->leU(*M).getUnsigned())) { constraints.set(branch.local, branch.constraint); constraints.approximateAnd(branch.local, {GeU, {*N}}); return true; From 24ae2109c37cfdda1c102426a24e9e7257144cb7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 3 Aug 2026 11:49:00 -0700 Subject: [PATCH 073/120] work --- test/lit/passes/constraint-analysis-loops.wast | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 87a6d15b952..33feb565d23 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -962,4 +962,6 @@ ) ) ) + + ;; TODO: unsigned of the latter ) From d3c997be8f4994407990f86988afd37d4b64c507 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 09:17:44 -0700 Subject: [PATCH 074/120] work --- src/passes/ConstraintAnalysis.cpp | 13 +++++++++++++ test/lit/passes/constraint-analysis-loops.wast | 5 +---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 79b2427bf7f..7e53bff5330 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -627,6 +627,19 @@ struct ConstraintAnalysis case LtU: c.op = LeU; continue; + // x <= N, x++ => x <= N+1 if no overflow + case LeS: + if (N->isSignedMax()) { + return false; + } + *N = N->add(Literal::makeFromInt32(1, N->type)); + continue; + case LeU: + if (N->isUnsignedMax()) { + return false; + } + *N = N->add(Literal::makeFromInt32(1, N->type)); + continue; default: // Something we don't recognize. return false; diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 33feb565d23..8064c023645 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -911,10 +911,7 @@ ;; LOOPS-NEXT: ) ;; LOOPS-NEXT: ) ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.gt_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 0) - ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (i32.const 1) ;; LOOPS-NEXT: ) ;; LOOPS-NEXT: (drop ;; LOOPS-NEXT: (i32.const 1) From 15af30f8a535159600b469c61d4725b0501003f1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 09:18:53 -0700 Subject: [PATCH 075/120] work --- .../lit/passes/constraint-analysis-loops.wast | 101 +++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 8064c023645..7751874d5ba 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -942,7 +942,7 @@ (br $out) ) ) - ;; x > 0 && x <= 100 here. + ;; x > 0 && x <= 100 here (but we need loops mode to get both). (drop (i32.gt_s (local.get $x) @@ -960,5 +960,102 @@ ) ) - ;; TODO: unsigned of the latter + ;; CHECK: (func $bound-incremented-inc-first-less-unsigned (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $out) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-incremented-inc-first-less-unsigned (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (block $out + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.gt_u + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $out) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $bound-incremented-inc-first-less-unsigned + ;; As in the last testcase, but unsigned. + (local $x i32) + (block $out + (loop $loop + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (if + (i32.gt_u + (local.get $x) + (i32.const 100) + ) + (then + (br $out) + ) + ) + ;; x > 0 && x <= 100 here (but we need loops mode to get both). + (drop + (i32.gt_u + (local.get $x) + (i32.const 0) + ) + ) + (drop + (i32.le_u + (local.get $x) + (i32.const 100) + ) + ) + (br $loop) + ) + ) + ) ) From d76f8982027e43451b2ecf029671dfcd1d9e385d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 09:19:04 -0700 Subject: [PATCH 076/120] work --- src/passes/ConstraintAnalysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 7e53bff5330..2b3d2786796 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -93,7 +93,7 @@ #include "wasm-builder.h" #include "wasm.h" -#define CONSTRAINT_DEBUG 1 +#define CONSTRAINT_DEBUG 0 #ifndef CONSTRAINT_DEBUG #define CONSTRAINT_DEBUG 0 From 8cd6b92e74ce77261ce88320ef6ddbbd106d9b5f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 09:19:49 -0700 Subject: [PATCH 077/120] work --- src/passes/ConstraintAnalysis.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 2b3d2786796..6d61452a279 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -288,6 +288,10 @@ struct ConstraintAnalysis // Flow infos around until we have inferred all we can about the constraints // in each location. void flow() { +#if CONSTRAINT_DEBUG + dumpCFG("flow"); +#endif + // Start from the entry as the only reachable block. That block has incoming // values - defaults - for each var. entry->contents.startConstraints.setReachable(); @@ -349,7 +353,6 @@ struct ConstraintAnalysis std::cout << block << " sending branch to " << out << " with sent constraints: " << sentConstraints << '\n'; #endif - std::cout << "out's start before " << outStartConstraints << '\n'; // If anything changed at the start of the target block, flow onwards. if (outStartConstraints.approximateOr(sentConstraints)) { #if CONSTRAINT_DEBUG From 183a59458ad6c5c3ca6e51fdf4a09e118c4aeed9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 13:34:15 -0700 Subject: [PATCH 078/120] work --- .../lit/passes/constraint-analysis-loops.wast | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 7751874d5ba..8b6ee87a408 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -1058,4 +1058,88 @@ ) ) ) + + ;; CHECK: (func $infinite-loop (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $infinite-loop (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $infinite-loop + (local $x i32) + ;; An infinite loop. We should not hang, but nothing can be optimized. + (loop $loop + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (br $loop) + ) + ) + + ;; CHECK: (func $almost-infinite-loop (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $almost-infinite-loop (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br_if $loop + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $almost-infinite-loop + (local $x i32) + ;; A loop that continues until an overflow happens. We should not hang, but + ;; nothing can be optimized. + (loop $loop + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + ;; Stop looping after we go all the way back to 0. + (br_if $loop + (local.get $x) + ) + ) + ) ) From c0f966a967a7de7332fd812b2820894dea61b5e3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 13:51:42 -0700 Subject: [PATCH 079/120] work --- src/ir/constraint.cpp | 87 +++++++++++++++++++++++++++ src/ir/constraint.h | 3 + src/passes/ConstraintAnalysis.cpp | 98 ++----------------------------- 3 files changed, 96 insertions(+), 92 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index e11bff05446..5bf45eb895d 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -534,6 +534,93 @@ void BasicBlockConstraintMap::set(Index index, } } + +// Set the value in an expression to a local, replacing anything before. +void BasicBlockConstraintMap::set(Index index, Expression* value) { + using namespace Match; + using namespace Abstract; + + // Apply a constraint to a value, x = C. + if (Properties::isSingleConstantExpression(value)) { + auto c = Properties::getLiteral(value); + set(index, Constraint{Abstract::Eq, {c}}); + return; + } + + // Apply a constraint to a local, x = y. + if (auto* get = value->dynCast()) { + set(index, Constraint{Abstract::Eq, {get->index}}); + return; + } + + // Apply an increment of a local, x = y + 1. + Index y; + if (matches(value, binary(Abstract::Add, local(&y), ival(1)))) { + // The local y must have old constraints that we know how to increment. + auto old = get(y); + + // Iterate over the old constraints and increment each one. + auto success = true; + for (auto& c : old) { + auto* N = std::get_if(&c.term); + if (!N) { + // A non-constant term, which we don't know how to increment. + success = false; + break; + } + + switch (c.op) { + // x == N, x++ => x == N+1. + case Eq: + // TODO: overflows here and below + c.term = Term(N->add(Literal::makeFromInt32(1, N->type))); + continue; + // x >= N, x++ => x > N + case GeS: + c.op = GtS; + continue; + case GeU: + c.op = GtU; + continue; + // x < N, x++ => x <= N + case LtS: + c.op = LeS; + continue; + case LtU: + c.op = LeU; + continue; + // x <= N, x++ => x <= N+1 if no overflow + case LeS: + if (N->isSignedMax()) { + success = false; + break; + } + *N = N->add(Literal::makeFromInt32(1, N->type)); + continue; + case LeU: + if (N->isUnsignedMax()) { + success = false; + break; + } + *N = N->add(Literal::makeFromInt32(1, N->type)); + continue; + default: + // Something we don't recognize. + success = false; + break; + } + } + + if (success) { + set(index, old); + return; + } + } + + // We know and can prove nothing. + setProvesNothing(index); +} + void BasicBlockConstraintMap::setProvesNothing(Index index) { assert(!unreachable); eraseStaleRefs(index); diff --git a/src/ir/constraint.h b/src/ir/constraint.h index b0a8b8c4129..07d4254cb7e 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -257,6 +257,9 @@ struct BasicBlockConstraintMap { // Apply a set of constraints to a local, replacing anything before. void set(Index index, const AndedConstraintSet& constraints); + // Set the value in an expression to a local, replacing anything before. + void set(Index index, Expression* value); + // Mark a local as unknown and able to prove nothing. void setProvesNothing(Index index); diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 6d61452a279..3b53c2033e0 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -525,28 +525,20 @@ struct ConstraintAnalysis // sets the value for that local. void applyToConstraints(Expression* curr, BasicBlockConstraintMap& constraints) { - // In "loops" mode, apply an increment if this is one. - if (loops && applyIncrementToConstraints(curr, constraints)) { - return; - } - if (auto* set = curr->dynCast()) { if (!relevantLocals[set->index]) { // No point to apply a constraint to an irrelevant local. return; } - if (Properties::isSingleConstantExpression(set->value)) { - // Apply a constraint to this value. - auto value = Properties::getLiteral(set->value); - constraints.set(set->index, Constraint{Abstract::Eq, {value}}); - } else if (auto* get = set->value->dynCast()) { - // Apply a constraint to this local. - constraints.set(set->index, Constraint{Abstract::Eq, {get->index}}); - } else { - // We know and can prove nothing. + // We only apply an increment in "loops" mode. Such increments are the + // only binary operations we match, so just disallow them all here. + if (!loops && set->value->is()) { constraints.setProvesNothing(set->index); + return; } + + constraints.set(set->index, set->value); } } @@ -577,84 +569,6 @@ struct ConstraintAnalysis return true; } - // Apply an increment, in loops mode. Returns true if we found and applied - // one. - bool applyIncrementToConstraints(Expression* curr, - BasicBlockConstraintMap& constraints) const { - assert(loops); - - using namespace Match; - using namespace Abstract; - - auto* set = curr->dynCast(); - if (!set) { - return false; - } - - // x = y + 1 - Index y; - if (!matches(set->value, binary(Abstract::Add, local(&y), ival(1)))) { - return false; - } - - auto old = constraints.get(y); - if (old.empty()) { - // Nothing we know how to increment. - return false; - } - - for (auto& c : old) { - auto* N = std::get_if(&c.term); - if (!N) { - // A non-constant term, which we don't know how to increment. - return false; - } - - switch (c.op) { - // x == N, x++ => x == N+1. - case Eq: - // TODO: overflows here and below - c.term = Term(N->add(Literal::makeFromInt32(1, N->type))); - continue; - // x >= N, x++ => x > N - case GeS: - c.op = GtS; - continue; - case GeU: - c.op = GtU; - continue; - // x < N, x++ => x <= N - case LtS: - c.op = LeS; - continue; - case LtU: - c.op = LeU; - continue; - // x <= N, x++ => x <= N+1 if no overflow - case LeS: - if (N->isSignedMax()) { - return false; - } - *N = N->add(Literal::makeFromInt32(1, N->type)); - continue; - case LeU: - if (N->isUnsignedMax()) { - return false; - } - *N = N->add(Literal::makeFromInt32(1, N->type)); - continue; - default: - // Something we don't recognize. - return false; - } - } - - // We processed the old constraints into their new forms without - // problems. Apply them and we are done. - constraints.set(set->index, old); - return true; - } - // Apply branch constraints to the current set of constraints. void applyBranchConstraints(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { From b19bb0b60259c6c44d0f04406ec7bd6eefd0c173 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 13:51:47 -0700 Subject: [PATCH 080/120] work --- src/ir/constraint.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 5bf45eb895d..0489d87b618 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -534,7 +534,6 @@ void BasicBlockConstraintMap::set(Index index, } } - // Set the value in an expression to a local, replacing anything before. void BasicBlockConstraintMap::set(Index index, Expression* value) { using namespace Match; From 62f0ae95d37cdb1b396810155712a5a556807889 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 13:52:29 -0700 Subject: [PATCH 081/120] work --- .../lit/passes/constraint-analysis-loops.wast | 168 +++++++++--------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 8b6ee87a408..14731b99a45 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -11,6 +11,90 @@ ;; LOOPS: (import "a" "b" (func $import (type $2) (result i32))) (import "a" "b" (func $import (result i32))) + ;; CHECK: (func $infinite-loop (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $infinite-loop (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $infinite-loop + (local $x i32) + ;; An infinite loop. We should not hang, but nothing can be optimized. + (loop $loop + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (br $loop) + ) + ) + + ;; CHECK: (func $almost-infinite-loop (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $almost-infinite-loop (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br_if $loop + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $almost-infinite-loop + (local $x i32) + ;; A loop that continues until an overflow happens. We should not hang, but + ;; nothing can be optimized. + (loop $loop + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + ;; Stop looping after we go all the way back to 0. + (br_if $loop + (local.get $x) + ) + ) + ) + ;; CHECK: (func $bound (type $0) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop @@ -1058,88 +1142,4 @@ ) ) ) - - ;; CHECK: (func $infinite-loop (type $0) - ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (loop $loop - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $loop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; LOOPS: (func $infinite-loop (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - (func $infinite-loop - (local $x i32) - ;; An infinite loop. We should not hang, but nothing can be optimized. - (loop $loop - (local.set $x - (i32.add - (local.get $x) - (i32.const 1) - ) - ) - (br $loop) - ) - ) - - ;; CHECK: (func $almost-infinite-loop (type $0) - ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (loop $loop - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br_if $loop - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; LOOPS: (func $almost-infinite-loop (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br_if $loop - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - (func $almost-infinite-loop - (local $x i32) - ;; A loop that continues until an overflow happens. We should not hang, but - ;; nothing can be optimized. - (loop $loop - (local.set $x - (i32.add - (local.get $x) - (i32.const 1) - ) - ) - ;; Stop looping after we go all the way back to 0. - (br_if $loop - (local.get $x) - ) - ) - ) ) From 37d5efd7fa6310266fdf6df706af56cbcba0d005 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 14:03:46 -0700 Subject: [PATCH 082/120] work --- test/gtest/constraint.cpp | 83 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 0b534d12efb..e1e4d204149 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -503,3 +503,86 @@ TEST(ConstraintTest, TestAndLoop) { // x <= y && { x < y && x != 42 } => x < y && x != 42 checkAnd(ley, {lty[0], ne42}, {lty[0], ne42}); } + +TEST(ConstraintTest, TestBasicBlockConstraintMap) { + // Maps begin unreachable. + BasicBlockConstraintMap map; + + EXPECT_TRUE(map.unreachable); + map.setReachable(); + EXPECT_FALSE(map.unreachable); +} + +// Check that a set is equal to a constraint. +static void check(const AndedConstraintSet& s, const Constraint& c) { +std::cout << "chak " << s << " vs " << c << '\n'; + EXPECT_EQ(s.size(), 1); + EXPECT_EQ(s[0], c); +} + +TEST(ConstraintTest, TestBasicBlockConstraintMap_Set) { + Constraint eq0{Eq, {Literal(int32_t(0))}}; + Constraint eq1{Eq, {Literal(int32_t(1))}}; + Constraint eq2{Eq, {Literal(int32_t(2))}}; + + BasicBlockConstraintMap map; + map.setReachable(); + + // Set local 0 to 0. It should read back the same. + map.set(0, eq0); + check(map.get(0), eq0); + + // Set another value, replacing the first. + map.set(0, eq1); + check(map.get(0), eq1); + + // Set a value using an expression. + Const c; + c.value = Literal(int32_t(2)); + c.type = Type::i32; + map.set(0, &c); + check(map.get(0), eq2); + + // Set an unfamiliar expression, leading to us knowing nothing. + Nop nop; + map.set(0, &nop); + EXPECT_TRUE(map.get(0).provesNothing()); +} + +TEST(ConstraintTest, TestIncrement) { + BasicBlockConstraintMap map; + map.setReachable(); + + // Set up an increment operation, an add which does x + 1 + LocalGet get; + get.index = 0; + get.type = Type::i32; + + Const c; + c.value = Literal(int32_t(1)); + c.type = Type::i32; + + Binary add; + add.op = AddInt32; + add.type = Type::i32; + add.left = &get; + add.right = &c; + + // Local 0 starts out less than 5. + Constraint lts_c5{LtS, {Literal(int32_t(5))}}; + map.set(0, lts_c5); + check(map.get(0), lts_c5); + + // Local 1 is equal to local $0 plus 1. That means it is less than, or equal + // to, 5. + map.set(1, &add); + Constraint les_c5{LeS, {Literal(int32_t(5))}}; + check(map.get(1), les_c5); + + // Local 0 did not change. + check(map.get(0), lts_c5); + + // Setting 0 to an add of itself also works. + map.set(0, &add); + check(map.get(0), les_c5); +} From 2ea981d4d30f420744cdf8787762cdee5a61766d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 14:06:02 -0700 Subject: [PATCH 083/120] work --- test/gtest/constraint.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index e1e4d204149..84e65b15436 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -553,7 +553,7 @@ TEST(ConstraintTest, TestIncrement) { BasicBlockConstraintMap map; map.setReachable(); - // Set up an increment operation, an add which does x + 1 + // Set up an increment operation, an add which does $0 + 1 LocalGet get; get.index = 0; get.type = Type::i32; @@ -568,6 +568,19 @@ TEST(ConstraintTest, TestIncrement) { add.left = &get; add.right = &c; + // $0 = 0, $1 = $0 + 1, so $1 = 1 (and $0 is unchanged). + map.set(0, {Eq, Literal(int32_t(0))}); + map.set(1, &add); + check(map.get(0), {Eq, Literal(int32_t(0))}); + check(map.get(1), {Eq, Literal(int32_t(1))}); + + + + + + + + // Local 0 starts out less than 5. Constraint lts_c5{LtS, {Literal(int32_t(5))}}; map.set(0, lts_c5); From c6b1904280d2dff05ade86fd8379a9a23e94cdd7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 14:06:45 -0700 Subject: [PATCH 084/120] work --- test/gtest/constraint.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 84e65b15436..c5ab94a80e5 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -574,6 +574,9 @@ TEST(ConstraintTest, TestIncrement) { check(map.get(0), {Eq, Literal(int32_t(0))}); check(map.get(1), {Eq, Literal(int32_t(1))}); + // $0 = $0 + 1, where $0 was 0, so it is now 1. + map.set(0, &add); + check(map.get(0), {Eq, Literal(int32_t(1))}); From 75d76fc798b16466c3b28862efa981c31cb8a23c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 14:08:26 -0700 Subject: [PATCH 085/120] work --- test/gtest/constraint.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index c5ab94a80e5..14e34207f6a 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -578,7 +578,15 @@ TEST(ConstraintTest, TestIncrement) { map.set(0, &add); check(map.get(0), {Eq, Literal(int32_t(1))}); + // $0 >= 5, $0++ => $0 > 5 (signed) + map.set(0, {GeS, Literal(int32_t(5))}); + map.set(0, &add); + check(map.get(0), {GtS, Literal(int32_t(5))}); + // Ditto, unsigned + map.set(0, {GeU, Literal(int32_t(5))}); + map.set(0, &add); + check(map.get(0), {GtU, Literal(int32_t(5))}); From 42681fe70457a18475c942aaf6b5b61b4cd1c933 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 14:10:05 -0700 Subject: [PATCH 086/120] work --- test/gtest/constraint.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 14e34207f6a..76081f5b18d 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -588,6 +588,15 @@ TEST(ConstraintTest, TestIncrement) { map.set(0, &add); check(map.get(0), {GtU, Literal(int32_t(5))}); + // $0 < 5, $0++ => $0 <= 5 (signed) + map.set(0, {LtS, Literal(int32_t(5))}); + map.set(0, &add); + check(map.get(0), {LeS, Literal(int32_t(5))}); + + // Ditto, unsigned + map.set(0, {LtU, Literal(int32_t(5))}); + map.set(0, &add); + check(map.get(0), {LeU, Literal(int32_t(5))}); From bb0b742e47e01299270bd9e6668d0f202d51449a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 14:10:56 -0700 Subject: [PATCH 087/120] work --- test/gtest/constraint.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 76081f5b18d..eaf2cda8c21 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -598,6 +598,15 @@ TEST(ConstraintTest, TestIncrement) { map.set(0, &add); check(map.get(0), {LeU, Literal(int32_t(5))}); + // $0 <= 5, $0++ => $0 <= 6 (signed) + map.set(0, {LeS, Literal(int32_t(5))}); + map.set(0, &add); + check(map.get(0), {LeS, Literal(int32_t(6))}); + + // Ditto, unsigned + map.set(0, {LeU, Literal(int32_t(5))}); + map.set(0, &add); + check(map.get(0), {LeU, Literal(int32_t(6))}); From 418ee1840124cb25331c1d391c459cfd9200a976 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 14:14:46 -0700 Subject: [PATCH 088/120] work --- test/gtest/constraint.cpp | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index eaf2cda8c21..2341ebcbf96 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -608,23 +608,10 @@ TEST(ConstraintTest, TestIncrement) { map.set(0, &add); check(map.get(0), {LeU, Literal(int32_t(6))}); - - - // Local 0 starts out less than 5. - Constraint lts_c5{LtS, {Literal(int32_t(5))}}; - map.set(0, lts_c5); - check(map.get(0), lts_c5); - - // Local 1 is equal to local $0 plus 1. That means it is less than, or equal - // to, 5. - map.set(1, &add); - Constraint les_c5{LeS, {Literal(int32_t(5))}}; - check(map.get(1), les_c5); - - // Local 0 did not change. - check(map.get(0), lts_c5); - - // Setting 0 to an add of itself also works. + // Multiple constraints at once: + // $0 >= 10 && $0 < 20, $0++ => $0 > 10 && $0 <= 20 + map.set(0, {GeS, Literal(int32_t(10))}); + map.approximateAnd(0, {LtS, Literal(int32_t(20)) }); map.set(0, &add); - check(map.get(0), les_c5); + EXPECT_EQ(map.get(0), AndedConstraintSet{{GtS, Literal(int32_t(10))}, {LeS, Literal(int32_t(20))}}); } From 19157ee719dfada637f5ba47fd5453f243be64e1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 14:14:55 -0700 Subject: [PATCH 089/120] work --- test/gtest/constraint.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 2341ebcbf96..ca411c60828 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -515,7 +515,7 @@ TEST(ConstraintTest, TestBasicBlockConstraintMap) { // Check that a set is equal to a constraint. static void check(const AndedConstraintSet& s, const Constraint& c) { -std::cout << "chak " << s << " vs " << c << '\n'; + std::cout << "chak " << s << " vs " << c << '\n'; EXPECT_EQ(s.size(), 1); EXPECT_EQ(s[0], c); } @@ -611,7 +611,9 @@ TEST(ConstraintTest, TestIncrement) { // Multiple constraints at once: // $0 >= 10 && $0 < 20, $0++ => $0 > 10 && $0 <= 20 map.set(0, {GeS, Literal(int32_t(10))}); - map.approximateAnd(0, {LtS, Literal(int32_t(20)) }); + map.approximateAnd(0, {LtS, Literal(int32_t(20))}); map.set(0, &add); - EXPECT_EQ(map.get(0), AndedConstraintSet{{GtS, Literal(int32_t(10))}, {LeS, Literal(int32_t(20))}}); + EXPECT_EQ(map.get(0), + AndedConstraintSet{{GtS, Literal(int32_t(10))}, + {LeS, Literal(int32_t(20))}}); } From e02ab01d30749c9a9af591755b2d702869f278c1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 14:16:01 -0700 Subject: [PATCH 090/120] clean --- test/gtest/constraint.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index ca411c60828..5d2231181a4 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -515,7 +515,6 @@ TEST(ConstraintTest, TestBasicBlockConstraintMap) { // Check that a set is equal to a constraint. static void check(const AndedConstraintSet& s, const Constraint& c) { - std::cout << "chak " << s << " vs " << c << '\n'; EXPECT_EQ(s.size(), 1); EXPECT_EQ(s[0], c); } From dc398779b8f20ed0ef7dd1ca1264b3458560fbdf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 14:17:40 -0700 Subject: [PATCH 091/120] work --- test/gtest/constraint.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 5d2231181a4..260f3cd7dfd 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -613,6 +613,6 @@ TEST(ConstraintTest, TestIncrement) { map.approximateAnd(0, {LtS, Literal(int32_t(20))}); map.set(0, &add); EXPECT_EQ(map.get(0), - AndedConstraintSet{{GtS, Literal(int32_t(10))}, - {LeS, Literal(int32_t(20))}}); + (AndedConstraintSet{{GtS, Literal(int32_t(10))}, + {LeS, Literal(int32_t(20))}})); } From 67e7be9116450cce36d4d22f849de0f3a52ec15a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:05:04 -0700 Subject: [PATCH 092/120] work --- src/passes/ConstraintAnalysis.cpp | 43 ++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 3b53c2033e0..314af7ea058 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -255,6 +255,7 @@ struct ConstraintAnalysis } computeRelevantLocals(); + prepareToFlow(); flow(); optimize(); } @@ -285,6 +286,35 @@ struct ConstraintAnalysis } } + // Maintain a maximum amount of operations. The one non-linear thing that can + // happen is when we increment a local in a loop: it may go from 0 to 1, then + // branch back to the top and merge, making it in the range [0, 1], then get + // incremented and loop again, leading to [0, 2] and so forth, only stopping + // when it reaches the loop bound, which may be very high. We don't want to + // spend significant time on such constant operations, as other passes will + // propagate them anyhow, so we keep our time bounded. When this reaches 0, + // we will not do loop operations that might lead to such incrementing. + Index maxWorkLeft = 0; + + void prepareToFlow() { + // Compute a bound for maxOperations. flow() will spend time on each block, + // operation in a block, and branch, so add all those up. + for (auto& block : basicBlocks) { + maxWorkLeft += 1 + block->contents.actions.size() + block->out.size(); + } + + // We also allow a multiple of all the above: loop optimization generally + // requires us to process it twice (so that we see the merge at the top). + // Use a constant of 3 to make sure to work enough. + maxWorkLeft *= 3; + } + + void decMaxOperations() { + if (maxWorkLeft > 0) { + maxWorkLeft--; + } + } + // Flow infos around until we have inferred all we can about the constraints // in each location. void flow() { @@ -323,6 +353,8 @@ struct ConstraintAnalysis while (!work.empty()) { auto* block = work.pop(); + decMaxOperations(); + // Start at the top of the block, then go through, applying things. BasicBlockConstraintMap constraints = block->contents.startConstraints; @@ -332,6 +364,8 @@ struct ConstraintAnalysis for (auto** currp : block->contents.actions) { applyToConstraints(*currp, constraints); + + decMaxOperations(); } #if CONSTRAINT_DEBUG @@ -341,6 +375,8 @@ struct ConstraintAnalysis // We now know the values at the end of the block. Flow it onward, and // where it causes changes, queue more work. for (auto* out : block->out) { + decMaxOperations(); + auto& outStartConstraints = out->contents.startConstraints; // Find the constraints sent to this specific successor, if there is a @@ -531,9 +567,10 @@ struct ConstraintAnalysis return; } - // We only apply an increment in "loops" mode. Such increments are the - // only binary operations we match, so just disallow them all here. - if (!loops && set->value->is()) { + // The only binary operation we match is an increment (x + 1), and we do + // not always want to apply it: only in loops mode, and even then, only + // when we are allowed to keep working (see above). + if (set->value->is() && (!loops || !maxWorkLeft)) { constraints.setProvesNothing(set->index); return; } From 4c48d1a8a8f6ea9e50a249e38d87bc9097b5cf87 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:06:16 -0700 Subject: [PATCH 093/120] work --- src/passes/ConstraintAnalysis.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 314af7ea058..3301f7f18a5 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -309,7 +309,7 @@ struct ConstraintAnalysis maxWorkLeft *= 3; } - void decMaxOperations() { + void decMaxWork() { if (maxWorkLeft > 0) { maxWorkLeft--; } @@ -353,7 +353,7 @@ struct ConstraintAnalysis while (!work.empty()) { auto* block = work.pop(); - decMaxOperations(); + decMaxWork(); // Start at the top of the block, then go through, applying things. BasicBlockConstraintMap constraints = block->contents.startConstraints; @@ -365,7 +365,7 @@ struct ConstraintAnalysis for (auto** currp : block->contents.actions) { applyToConstraints(*currp, constraints); - decMaxOperations(); + decMaxWork(); } #if CONSTRAINT_DEBUG @@ -375,7 +375,7 @@ struct ConstraintAnalysis // We now know the values at the end of the block. Flow it onward, and // where it causes changes, queue more work. for (auto* out : block->out) { - decMaxOperations(); + decMaxWork(); auto& outStartConstraints = out->contents.startConstraints; From cc480f7a477d1238aeb7f30f79d6615d86d07fef Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:07:12 -0700 Subject: [PATCH 094/120] work --- src/passes/ConstraintAnalysis.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 3301f7f18a5..666231c6ad0 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -84,7 +84,6 @@ #include "ir/eh-utils.h" #include "ir/literal-utils.h" #include "ir/local-graph.h" -#include "ir/match.h" #include "ir/properties.h" #include "ir/utils.h" #include "pass.h" From 4ff74fafb2f0185a57ab482c40ab7a6028ae722e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:09:53 -0700 Subject: [PATCH 095/120] go --- src/ir/constraint.cpp | 103 ++ src/ir/constraint.h | 8 +- src/ir/match.h | 19 + src/passes/ConstraintAnalysis.cpp | 91 +- test/gtest/constraint.cpp | 113 +++ .../lit/passes/constraint-analysis-loops.wast | 922 +++++++++++++++++- 6 files changed, 1242 insertions(+), 14 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index b2b018500d3..0489d87b618 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -517,6 +517,109 @@ void BasicBlockConstraintMap::set(Index index, const Constraint& c) { approximateAnd(index, c); } +void BasicBlockConstraintMap::set(Index index, + const AndedConstraintSet& constraints) { + // As above, but with a loop after. + assert(!unreachable); + eraseStaleRefs(index); + map.erase(index); + + // Apply the constraints, if there are any. + if (constraints.provesNothing()) { + setProvesNothing(index); + } else { + for (auto& c : constraints) { + approximateAnd(index, c); + } + } +} + +// Set the value in an expression to a local, replacing anything before. +void BasicBlockConstraintMap::set(Index index, Expression* value) { + using namespace Match; + using namespace Abstract; + + // Apply a constraint to a value, x = C. + if (Properties::isSingleConstantExpression(value)) { + auto c = Properties::getLiteral(value); + set(index, Constraint{Abstract::Eq, {c}}); + return; + } + + // Apply a constraint to a local, x = y. + if (auto* get = value->dynCast()) { + set(index, Constraint{Abstract::Eq, {get->index}}); + return; + } + + // Apply an increment of a local, x = y + 1. + Index y; + if (matches(value, binary(Abstract::Add, local(&y), ival(1)))) { + // The local y must have old constraints that we know how to increment. + auto old = get(y); + + // Iterate over the old constraints and increment each one. + auto success = true; + for (auto& c : old) { + auto* N = std::get_if(&c.term); + if (!N) { + // A non-constant term, which we don't know how to increment. + success = false; + break; + } + + switch (c.op) { + // x == N, x++ => x == N+1. + case Eq: + // TODO: overflows here and below + c.term = Term(N->add(Literal::makeFromInt32(1, N->type))); + continue; + // x >= N, x++ => x > N + case GeS: + c.op = GtS; + continue; + case GeU: + c.op = GtU; + continue; + // x < N, x++ => x <= N + case LtS: + c.op = LeS; + continue; + case LtU: + c.op = LeU; + continue; + // x <= N, x++ => x <= N+1 if no overflow + case LeS: + if (N->isSignedMax()) { + success = false; + break; + } + *N = N->add(Literal::makeFromInt32(1, N->type)); + continue; + case LeU: + if (N->isUnsignedMax()) { + success = false; + break; + } + *N = N->add(Literal::makeFromInt32(1, N->type)); + continue; + default: + // Something we don't recognize. + success = false; + break; + } + } + + if (success) { + set(index, old); + return; + } + } + + // We know and can prove nothing. + setProvesNothing(index); +} + void BasicBlockConstraintMap::setProvesNothing(Index index) { assert(!unreachable); eraseStaleRefs(index); diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 9fea231fbc7..07d4254cb7e 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -251,9 +251,15 @@ struct BasicBlockConstraintMap { assert(map.empty()); } - // Apply a constraint to a local. + // Apply a constraint to a local, replacing anything before. void set(Index index, const Constraint& c); + // Apply a set of constraints to a local, replacing anything before. + void set(Index index, const AndedConstraintSet& constraints); + + // Set the value in an expression to a local, replacing anything before. + void set(Index index, Expression* value); + // Mark a local as unknown and able to prove nothing. void setProvesNothing(Index index); diff --git a/src/ir/match.h b/src/ir/match.h index 383ff8d057a..a60bad43235 100644 --- a/src/ir/match.h +++ b/src/ir/match.h @@ -613,6 +613,18 @@ SelectMatcher(Select** binder, S1&& s1, S2&& s2, S3&& s3) { return Matcher(binder, {}, s1, s2, s3); } +// LocalGet +template<> struct NumComponents { + static constexpr size_t value = 1; +}; +template<> struct GetComponent { + Index operator()(LocalGet* curr) { return curr->index; } +}; +template +inline decltype(auto) LocalGetMatcher(LocalGet** binder, S&& s) { + return Matcher(binder, {}, s); +} + } // namespace Internal // Public matching API @@ -878,6 +890,13 @@ inline decltype(auto) select(Select** binder, S1&& s1, S2&& s2, S3&& s3) { return Internal::SelectMatcher(binder, s1, s2, s3); } +inline decltype(auto) local() { + return Internal::LocalGetMatcher(nullptr, Internal::Any(nullptr)); +} +inline decltype(auto) local(Index* binder) { + return Internal::LocalGetMatcher(nullptr, Internal::Any(binder)); +} + } // namespace wasm::Match #endif // wasm_ir_match_h diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 5eb6a24fcf4..da557d0b21f 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -36,6 +36,12 @@ #include "wasm-builder.h" #include "wasm.h" +#define CONSTRAINT_DEBUG 0 + +#ifndef CONSTRAINT_DEBUG +#define CONSTRAINT_DEBUG 0 +#endif + namespace wasm { using namespace wasm::constraint; @@ -187,6 +193,7 @@ struct ConstraintAnalysis } computeRelevantLocals(); + prepareToFlow(); flow(); optimize(); } @@ -217,9 +224,42 @@ struct ConstraintAnalysis } } + // Maintain a maximum amount of operations. The one non-linear thing that can + // happen is when we increment a local in a loop: it may go from 0 to 1, then + // branch back to the top and merge, making it in the range [0, 1], then get + // incremented and loop again, leading to [0, 2] and so forth, only stopping + // when it reaches the loop bound, which may be very high. We don't want to + // spend significant time on such constant operations, as other passes will + // propagate them anyhow, so we keep our time bounded. When this reaches 0, + // we will not do loop operations that might lead to such incrementing. + Index maxWorkLeft = 0; + + void prepareToFlow() { + // Compute a bound for maxOperations. flow() will spend time on each block, + // operation in a block, and branch, so add all those up. + for (auto& block : basicBlocks) { + maxWorkLeft += 1 + block->contents.actions.size() + block->out.size(); + } + + // We also allow a multiple of all the above: loop optimization generally + // requires us to process it twice (so that we see the merge at the top). + // Use a constant of 3 to make sure to work enough. + maxWorkLeft *= 3; + } + + void decMaxWork() { + if (maxWorkLeft > 0) { + maxWorkLeft--; + } + } + // Flow infos around until we have inferred all we can about the constraints // in each location. void flow() { +#if CONSTRAINT_DEBUG + dumpCFG("flow"); +#endif + // Start from the entry as the only reachable block. That block has incoming // values - defaults - for each var. entry->contents.startConstraints.setReachable(); @@ -247,18 +287,34 @@ struct ConstraintAnalysis // Starting from the entry, keep going while we find something new. UniqueDeferredQueue work; work.push(entry); + while (!work.empty()) { auto* block = work.pop(); + decMaxWork(); + // Start at the top of the block, then go through, applying things. BasicBlockConstraintMap constraints = block->contents.startConstraints; + +#if CONSTRAINT_DEBUG + std::cout << block << " start constraints: " << constraints << '\n'; +#endif + for (auto** currp : block->contents.actions) { applyToConstraints(*currp, constraints); + + decMaxWork(); } +#if CONSTRAINT_DEBUG + std::cout << block << " end constraints: " << constraints << '\n'; +#endif + // We now know the values at the end of the block. Flow it onward, and // where it causes changes, queue more work. for (auto* out : block->out) { + decMaxWork(); + auto& outStartConstraints = out->contents.startConstraints; // Find the constraints sent to this specific successor, if there is a @@ -266,15 +322,28 @@ struct ConstraintAnalysis if (auto branch = getBranchConstraints(block, out); branch && checkRelevancy(*branch)) { auto sentConstraints = constraints; - sentConstraints.approximateAnd(branch->local, branch->constraint); + applyBranchConstraints(*branch, sentConstraints); +#if CONSTRAINT_DEBUG + std::cout << block << " sending branch to " << out + << " with sent constraints: " << sentConstraints << '\n'; +#endif // If anything changed at the start of the target block, flow onwards. if (outStartConstraints.approximateOr(sentConstraints)) { +#if CONSTRAINT_DEBUG + std::cout << "out's start after " << outStartConstraints << '\n'; + std::cout << block << " branch-modified " << out + << " to start with: " << outStartConstraints << '\n'; +#endif work.push(out); } } else { // There are no specific branch constraints, so send the unmodified // |constraints|, avoiding a copy. if (outStartConstraints.approximateOr(constraints)) { +#if CONSTRAINT_DEBUG + std::cout << block << " modified " << out + << " to start with: " << outStartConstraints << '\n'; +#endif work.push(out); } } @@ -293,6 +362,9 @@ struct ConstraintAnalysis // of course not needed at this stage.) auto& constraints = block->contents.startConstraints; for (auto** currp : block->contents.actions) { +#if CONSTRAINT_DEBUG + std::cout << block << " trying to optimize " << **currp << '\n'; +#endif if (!constraints.unreachable) { applyToConstraints(*currp, constraints); optimizeExpression(currp, constraints); @@ -432,17 +504,16 @@ struct ConstraintAnalysis // No point to apply a constraint to an irrelevant local. return; } - if (Properties::isSingleConstantExpression(set->value)) { - // Apply a constraint to this value. - auto value = Properties::getLiteral(set->value); - constraints.set(set->index, Constraint{Abstract::Eq, {value}}); - } else if (auto* get = set->value->dynCast()) { - // Apply a constraint to this local. - constraints.set(set->index, Constraint{Abstract::Eq, {get->index}}); - } else { - // We know and can prove nothing. + + // The only binary operation we match is an increment (x + 1), and we do + // not always want to apply it: only in loops mode, and even then, only + // when we are allowed to keep working (see above). + if (set->value->is() && (!loops || !maxWorkLeft)) { constraints.setProvesNothing(set->index); + return; } + + constraints.set(set->index, set->value); } } diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 0b534d12efb..260f3cd7dfd 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -503,3 +503,116 @@ TEST(ConstraintTest, TestAndLoop) { // x <= y && { x < y && x != 42 } => x < y && x != 42 checkAnd(ley, {lty[0], ne42}, {lty[0], ne42}); } + +TEST(ConstraintTest, TestBasicBlockConstraintMap) { + // Maps begin unreachable. + BasicBlockConstraintMap map; + + EXPECT_TRUE(map.unreachable); + map.setReachable(); + EXPECT_FALSE(map.unreachable); +} + +// Check that a set is equal to a constraint. +static void check(const AndedConstraintSet& s, const Constraint& c) { + EXPECT_EQ(s.size(), 1); + EXPECT_EQ(s[0], c); +} + +TEST(ConstraintTest, TestBasicBlockConstraintMap_Set) { + Constraint eq0{Eq, {Literal(int32_t(0))}}; + Constraint eq1{Eq, {Literal(int32_t(1))}}; + Constraint eq2{Eq, {Literal(int32_t(2))}}; + + BasicBlockConstraintMap map; + map.setReachable(); + + // Set local 0 to 0. It should read back the same. + map.set(0, eq0); + check(map.get(0), eq0); + + // Set another value, replacing the first. + map.set(0, eq1); + check(map.get(0), eq1); + + // Set a value using an expression. + Const c; + c.value = Literal(int32_t(2)); + c.type = Type::i32; + map.set(0, &c); + check(map.get(0), eq2); + + // Set an unfamiliar expression, leading to us knowing nothing. + Nop nop; + map.set(0, &nop); + EXPECT_TRUE(map.get(0).provesNothing()); +} + +TEST(ConstraintTest, TestIncrement) { + BasicBlockConstraintMap map; + map.setReachable(); + + // Set up an increment operation, an add which does $0 + 1 + LocalGet get; + get.index = 0; + get.type = Type::i32; + + Const c; + c.value = Literal(int32_t(1)); + c.type = Type::i32; + + Binary add; + add.op = AddInt32; + add.type = Type::i32; + add.left = &get; + add.right = &c; + + // $0 = 0, $1 = $0 + 1, so $1 = 1 (and $0 is unchanged). + map.set(0, {Eq, Literal(int32_t(0))}); + map.set(1, &add); + check(map.get(0), {Eq, Literal(int32_t(0))}); + check(map.get(1), {Eq, Literal(int32_t(1))}); + + // $0 = $0 + 1, where $0 was 0, so it is now 1. + map.set(0, &add); + check(map.get(0), {Eq, Literal(int32_t(1))}); + + // $0 >= 5, $0++ => $0 > 5 (signed) + map.set(0, {GeS, Literal(int32_t(5))}); + map.set(0, &add); + check(map.get(0), {GtS, Literal(int32_t(5))}); + + // Ditto, unsigned + map.set(0, {GeU, Literal(int32_t(5))}); + map.set(0, &add); + check(map.get(0), {GtU, Literal(int32_t(5))}); + + // $0 < 5, $0++ => $0 <= 5 (signed) + map.set(0, {LtS, Literal(int32_t(5))}); + map.set(0, &add); + check(map.get(0), {LeS, Literal(int32_t(5))}); + + // Ditto, unsigned + map.set(0, {LtU, Literal(int32_t(5))}); + map.set(0, &add); + check(map.get(0), {LeU, Literal(int32_t(5))}); + + // $0 <= 5, $0++ => $0 <= 6 (signed) + map.set(0, {LeS, Literal(int32_t(5))}); + map.set(0, &add); + check(map.get(0), {LeS, Literal(int32_t(6))}); + + // Ditto, unsigned + map.set(0, {LeU, Literal(int32_t(5))}); + map.set(0, &add); + check(map.get(0), {LeU, Literal(int32_t(6))}); + + // Multiple constraints at once: + // $0 >= 10 && $0 < 20, $0++ => $0 > 10 && $0 <= 20 + map.set(0, {GeS, Literal(int32_t(10))}); + map.approximateAnd(0, {LtS, Literal(int32_t(20))}); + map.set(0, &add); + EXPECT_EQ(map.get(0), + (AndedConstraintSet{{GtS, Literal(int32_t(10))}, + {LeS, Literal(int32_t(20))}})); +} diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 5d90de1096f..14731b99a45 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -1,11 +1,100 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s +;; Run both normally and in the "loops" mode. Many optimizations work in both +;; modes, but some require "loops" (mentioned below where that occurs). + +;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s +;; RUN: wasm-opt %s --constraint-analysis-loops -all -S -o - | filecheck %s --check-prefix=LOOPS (module - ;; CHECK: (import "a" "b" (func $import (type $1) (result i32))) + ;; CHECK: (import "a" "b" (func $import (type $2) (result i32))) + ;; LOOPS: (import "a" "b" (func $import (type $2) (result i32))) (import "a" "b" (func $import (result i32))) + ;; CHECK: (func $infinite-loop (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $infinite-loop (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $infinite-loop + (local $x i32) + ;; An infinite loop. We should not hang, but nothing can be optimized. + (loop $loop + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (br $loop) + ) + ) + + ;; CHECK: (func $almost-infinite-loop (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $loop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $almost-infinite-loop (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br_if $loop + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $almost-infinite-loop + (local $x i32) + ;; A loop that continues until an overflow happens. We should not hang, but + ;; nothing can be optimized. + (loop $loop + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + ;; Stop looping after we go all the way back to 0. + (br_if $loop + (local.get $x) + ) + ) + ) + ;; CHECK: (func $bound (type $0) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop @@ -37,6 +126,37 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (call $import) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.gt_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 0) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.le_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) (func $bound (local $x i32) (loop $loop @@ -111,6 +231,37 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-flipped-ifs (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (call $import) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.le_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.gt_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 0) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) (func $bound-flipped-ifs (local $x i32) ;; As above, but with the ifs flipped. We optimize the same way. @@ -150,7 +301,7 @@ ) ) - ;; CHECK: (func $bound-nonconstant-no (type $2) (param $p i32) + ;; CHECK: (func $bound-nonconstant-no (type $1) (param $p i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (drop @@ -187,6 +338,43 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-nonconstant-no (type $1) (param $p i32) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.ge_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (local.get $p) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.le_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (local.get $p) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (call $import) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.gt_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (local.get $p) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.le_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) (func $bound-nonconstant-no (param $p i32) (local $x i32) ;; As above, but rather than zero we have an unknown param $p. @@ -226,4 +414,732 @@ ) ) ) + + ;; CHECK: (func $bound-incremented (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.lt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-incremented (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.lt_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $bound-incremented + (local $x i32) + (loop $loop + ;; A realistic do-while loop, with $x++ and a bounds check. We must infer + ;; that no overflow happens in order to prove these two checks are true. + ;; + ;; The first is trivially true, as x starts at 0 - fulfilling x < 100 - + ;; and the branch back to the loop top arrives with x < 100. + (drop + (i32.lt_s + (local.get $x) + (i32.const 100) + ) + ) + ;; This is non-trivial, as we must rule out a possible overflow. The + ;; only reason that x never gets incremented so many times that it becomes + ;; negative is that the incrementation process is stopped at 100. We only + ;; manage to optimize this in "loops" mode. + (drop + (i32.ge_s + (local.get $x) + (i32.const 0) + ) + ) + ;; This changed compared to previous testcases: now we have x++. + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (if + (i32.lt_s + (local.get $x) + (i32.const 100) + ) + (then + (br $loop) + ) + ) + ) + ) + + ;; CHECK: (func $bound-incremented-unsigned (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ge_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.lt_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-incremented-unsigned (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.lt_u + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $bound-incremented-unsigned + ;; As above, but with unsigned operations. This is simpler, and we optimize + ;; it even without "loops" mode. + (local $x i32) + (loop $loop + (drop + (i32.lt_u + (local.get $x) + (i32.const 100) + ) + ) + (drop + (i32.ge_u + (local.get $x) + (i32.const 0) + ) + ) + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (if + (i32.lt_u + (local.get $x) + (i32.const 100) + ) + (then + (br $loop) + ) + ) + ) + ) + + ;; CHECK: (func $increment-non-constant (type $1) (param $p i32) + ;; CHECK-NEXT: (local $a i32) + ;; CHECK-NEXT: (local $b i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $scratch + ;; CHECK-NEXT: (i32.lt_s + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.le_s + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $p + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $a + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $scratch) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $increment-non-constant (type $1) (param $p i32) + ;; LOOPS-NEXT: (local $a i32) + ;; LOOPS-NEXT: (local $b i32) + ;; LOOPS-NEXT: (local $scratch i32) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (block (result i32) + ;; LOOPS-NEXT: (local.set $scratch + ;; LOOPS-NEXT: (i32.lt_s + ;; LOOPS-NEXT: (local.get $p) + ;; LOOPS-NEXT: (i32.const 0) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.le_s + ;; LOOPS-NEXT: (local.get $a) + ;; LOOPS-NEXT: (local.get $b) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (local.set $p + ;; LOOPS-NEXT: (local.get $a) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $a + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $a) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.eq + ;; LOOPS-NEXT: (local.get $a) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.get $scratch) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $increment-non-constant (param $p i32) + (local $a i32) + (local $b i32) + (drop + (i32.lt_s + (local.get $p) + (i32.const 0) + ) + (if + (i32.le_s + (local.get $a) + (local.get $b) + ) + (then + ;; Before this set, this is what we know about $a: + ;; $a == 0 && $a <= $b + (local.set $p + (local.get $a) + ) + ;; We just set $p to $a, so now we know this about $a: + ;; $a == 0 && $a <= $b, $a == $p + ;; We then proceed to do $a++, trying to increment each of those three + ;; constraints. We should not hit an internal error on trying to increment + ;; any of the three, ending up failing on the third (though, with a higher- + ;; level view, we could use the fact that $p == 0). + (local.set $a + (i32.add + (local.get $a) + (i32.const 1) + ) + ) + ;; Since we failed to know things about $a after $a++, we cannot + ;; prove this. + (drop + (i32.eq + (local.get $a) + (i32.const 1) + ) + ) + ;; But we did not forget about $b. + (drop + (i32.eq + (local.get $b) + (i32.const 0) + ) + ) + ) + ) + ) + ) + + ;; CHECK: (func $bound-incremented-while (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $out) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-incremented-while (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (block $out + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.ge_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $out) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $bound-incremented-while + ;; Similar to above, but before we had a do-while loop (loop condition at + ;; the bottom) and now it is at the top. + (local $x i32) + (block $out + (loop $loop + ;; Conditional branch at the top. + (if + (i32.ge_s + (local.get $x) + (i32.const 100) + ) + (then + (br $out) + ) + ) + ;; We can infer both of these to be true in loops mode (in normal mode, + ;; only the easy one, the first). + (drop + (i32.lt_s + (local.get $x) + (i32.const 100) + ) + ) + (drop + (i32.ge_s + (local.get $x) + (i32.const 0) + ) + ) + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + ;; Unconditional branch at the bottom. + (br $loop) + ) + ) + ) + + ;; CHECK: (func $bound-incremented-inc-first (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $out) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.gt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-incremented-inc-first (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (block $out + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.ge_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $out) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $bound-incremented-inc-first + ;; Similar to the above "while" loop, but now with the increment before the + ;; if. + (local $x i32) + (block $out + (loop $loop + ;; Increment at the top. + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + ;; If after the increment. + (if + (i32.ge_s + (local.get $x) + (i32.const 100) + ) + (then + (br $out) + ) + ) + ;; x > 0 && x < 100 here (0 is impossible, compared to before). + (drop + (i32.gt_s + (local.get $x) + (i32.const 0) + ) + ) + (drop + (i32.lt_s + (local.get $x) + (i32.const 100) + ) + ) + (br $loop) + ) + ) + ) + + ;; CHECK: (func $bound-incremented-inc-first-less (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $out) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.gt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-incremented-inc-first-less (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (block $out + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.gt_s + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $out) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $bound-incremented-inc-first-less + ;; As in the last testcase, but the if's condition changed. + (local $x i32) + (block $out + (loop $loop + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + ;; Before we left the loop when x >= 100. Now we leave when x > 100, + ;; so we do actually reach 100 in the code below. + (if + (i32.gt_s + (local.get $x) + (i32.const 100) + ) + (then + (br $out) + ) + ) + ;; x > 0 && x <= 100 here (but we need loops mode to get both). + (drop + (i32.gt_s + (local.get $x) + (i32.const 0) + ) + ) + (drop + (i32.le_s + (local.get $x) + (i32.const 100) + ) + ) + (br $loop) + ) + ) + ) + + ;; CHECK: (func $bound-incremented-inc-first-less-unsigned (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (block $out + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $out) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; LOOPS: (func $bound-incremented-inc-first-less-unsigned (type $0) + ;; LOOPS-NEXT: (local $x i32) + ;; LOOPS-NEXT: (block $out + ;; LOOPS-NEXT: (loop $loop + ;; LOOPS-NEXT: (local.set $x + ;; LOOPS-NEXT: (i32.add + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (if + ;; LOOPS-NEXT: (i32.gt_u + ;; LOOPS-NEXT: (local.get $x) + ;; LOOPS-NEXT: (i32.const 100) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (then + ;; LOOPS-NEXT: (br $out) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (drop + ;; LOOPS-NEXT: (i32.const 1) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: (br $loop) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + ;; LOOPS-NEXT: ) + (func $bound-incremented-inc-first-less-unsigned + ;; As in the last testcase, but unsigned. + (local $x i32) + (block $out + (loop $loop + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (if + (i32.gt_u + (local.get $x) + (i32.const 100) + ) + (then + (br $out) + ) + ) + ;; x > 0 && x <= 100 here (but we need loops mode to get both). + (drop + (i32.gt_u + (local.get $x) + (i32.const 0) + ) + ) + (drop + (i32.le_u + (local.get $x) + (i32.const 100) + ) + ) + (br $loop) + ) + ) + ) ) From 9b38ac68598678904f6b3c549d3ba681aad87ec0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:10:28 -0700 Subject: [PATCH 096/120] simpl --- .../lit/passes/constraint-analysis-loops.wast | 1078 ----------------- 1 file changed, 1078 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 14731b99a45..fcc88a3316a 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -1,10 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; Run both normally and in the "loops" mode. Many optimizations work in both -;; modes, but some require "loops" (mentioned below where that occurs). - ;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s -;; RUN: wasm-opt %s --constraint-analysis-loops -all -S -o - | filecheck %s --check-prefix=LOOPS (module ;; CHECK: (import "a" "b" (func $import (type $2) (result i32))) @@ -23,18 +19,6 @@ ;; CHECK-NEXT: (br $loop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $infinite-loop (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $infinite-loop (local $x i32) ;; An infinite loop. We should not hang, but nothing can be optimized. @@ -63,20 +47,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $almost-infinite-loop (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br_if $loop - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $almost-infinite-loop (local $x i32) ;; A loop that continues until an overflow happens. We should not hang, but @@ -94,1052 +64,4 @@ ) ) ) - - ;; CHECK: (func $bound (type $0) - ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (loop $loop - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (call $import) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.gt_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.le_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $loop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (call $import) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.gt_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 0) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.le_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - (func $bound - (local $x i32) - (loop $loop - ;; We arrive at the loop top with {x == 0} || {x > 0 && x <= 100}. Those - ;; OR into {x >= 0 && x <= 100} - that is, the x == 0 and x > 0 combine - ;; into x >= 0. - (drop - (i32.ge_s - (local.get $x) - (i32.const 0) - ) - ) - (drop - (i32.le_s - (local.get $x) - (i32.const 100) - ) - ) - ;; Set $x to an unknown value before applying the constraints below on the - ;; way back to the loop top. - (local.set $x - (call $import) - ) - (if - (i32.gt_s - (local.get $x) - (i32.const 0) - ) - (then - (if - (i32.le_s - (local.get $x) - (i32.const 100) - ) - (then - (br $loop) - ) - ) - ) - ) - ) - ) - - ;; CHECK: (func $bound-flipped-ifs (type $0) - ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (loop $loop - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (call $import) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.le_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.gt_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $loop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-flipped-ifs (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (call $import) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.le_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.gt_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 0) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - (func $bound-flipped-ifs - (local $x i32) - ;; As above, but with the ifs flipped. We optimize the same way. - (loop $loop - (drop - (i32.ge_s - (local.get $x) - (i32.const 0) - ) - ) - (drop - (i32.le_s - (local.get $x) - (i32.const 100) - ) - ) - (local.set $x - (call $import) - ) - (if - (i32.le_s - (local.get $x) - (i32.const 100) - ) - (then - (if - (i32.gt_s - (local.get $x) - (i32.const 0) - ) - (then - (br $loop) - ) - ) - ) - ) - ) - ) - - ;; CHECK: (func $bound-nonconstant-no (type $1) (param $p i32) - ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (loop $loop - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ge_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (local.get $p) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.le_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (local.get $p) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (call $import) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.gt_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (local.get $p) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.le_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $loop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-nonconstant-no (type $1) (param $p i32) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.ge_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (local.get $p) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.le_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (local.get $p) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (call $import) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.gt_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (local.get $p) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.le_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - (func $bound-nonconstant-no (param $p i32) - (local $x i32) - ;; As above, but rather than zero we have an unknown param $p. - (loop $loop - ;; We can infer nothing here, as p is unknown, and x might be 0 or <= 100. - (drop - (i32.ge_s - (local.get $x) - (local.get $p) - ) - ) - (drop - (i32.le_s - (local.get $x) - (local.get $p) - ) - ) - (local.set $x - (call $import) - ) - (if - (i32.gt_s - (local.get $x) - (local.get $p) - ) - (then - (if - (i32.le_s - (local.get $x) - (i32.const 100) - ) - (then - (br $loop) - ) - ) - ) - ) - ) - ) - - ;; CHECK: (func $bound-incremented (type $0) - ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (loop $loop - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ge_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.lt_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $loop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.lt_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - (func $bound-incremented - (local $x i32) - (loop $loop - ;; A realistic do-while loop, with $x++ and a bounds check. We must infer - ;; that no overflow happens in order to prove these two checks are true. - ;; - ;; The first is trivially true, as x starts at 0 - fulfilling x < 100 - - ;; and the branch back to the loop top arrives with x < 100. - (drop - (i32.lt_s - (local.get $x) - (i32.const 100) - ) - ) - ;; This is non-trivial, as we must rule out a possible overflow. The - ;; only reason that x never gets incremented so many times that it becomes - ;; negative is that the incrementation process is stopped at 100. We only - ;; manage to optimize this in "loops" mode. - (drop - (i32.ge_s - (local.get $x) - (i32.const 0) - ) - ) - ;; This changed compared to previous testcases: now we have x++. - (local.set $x - (i32.add - (local.get $x) - (i32.const 1) - ) - ) - (if - (i32.lt_s - (local.get $x) - (i32.const 100) - ) - (then - (br $loop) - ) - ) - ) - ) - - ;; CHECK: (func $bound-incremented-unsigned (type $0) - ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (loop $loop - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ge_u - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.lt_u - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $loop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented-unsigned (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.lt_u - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - (func $bound-incremented-unsigned - ;; As above, but with unsigned operations. This is simpler, and we optimize - ;; it even without "loops" mode. - (local $x i32) - (loop $loop - (drop - (i32.lt_u - (local.get $x) - (i32.const 100) - ) - ) - (drop - (i32.ge_u - (local.get $x) - (i32.const 0) - ) - ) - (local.set $x - (i32.add - (local.get $x) - (i32.const 1) - ) - ) - (if - (i32.lt_u - (local.get $x) - (i32.const 100) - ) - (then - (br $loop) - ) - ) - ) - ) - - ;; CHECK: (func $increment-non-constant (type $1) (param $p i32) - ;; CHECK-NEXT: (local $a i32) - ;; CHECK-NEXT: (local $b i32) - ;; CHECK-NEXT: (local $scratch i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $scratch - ;; CHECK-NEXT: (i32.lt_s - ;; CHECK-NEXT: (local.get $p) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.le_s - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: (local.get $b) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (local.set $p - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $a - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $scratch) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; LOOPS: (func $increment-non-constant (type $1) (param $p i32) - ;; LOOPS-NEXT: (local $a i32) - ;; LOOPS-NEXT: (local $b i32) - ;; LOOPS-NEXT: (local $scratch i32) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (block (result i32) - ;; LOOPS-NEXT: (local.set $scratch - ;; LOOPS-NEXT: (i32.lt_s - ;; LOOPS-NEXT: (local.get $p) - ;; LOOPS-NEXT: (i32.const 0) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.le_s - ;; LOOPS-NEXT: (local.get $a) - ;; LOOPS-NEXT: (local.get $b) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (local.set $p - ;; LOOPS-NEXT: (local.get $a) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $a - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $a) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.eq - ;; LOOPS-NEXT: (local.get $a) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.get $scratch) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - (func $increment-non-constant (param $p i32) - (local $a i32) - (local $b i32) - (drop - (i32.lt_s - (local.get $p) - (i32.const 0) - ) - (if - (i32.le_s - (local.get $a) - (local.get $b) - ) - (then - ;; Before this set, this is what we know about $a: - ;; $a == 0 && $a <= $b - (local.set $p - (local.get $a) - ) - ;; We just set $p to $a, so now we know this about $a: - ;; $a == 0 && $a <= $b, $a == $p - ;; We then proceed to do $a++, trying to increment each of those three - ;; constraints. We should not hit an internal error on trying to increment - ;; any of the three, ending up failing on the third (though, with a higher- - ;; level view, we could use the fact that $p == 0). - (local.set $a - (i32.add - (local.get $a) - (i32.const 1) - ) - ) - ;; Since we failed to know things about $a after $a++, we cannot - ;; prove this. - (drop - (i32.eq - (local.get $a) - (i32.const 1) - ) - ) - ;; But we did not forget about $b. - (drop - (i32.eq - (local.get $b) - (i32.const 0) - ) - ) - ) - ) - ) - ) - - ;; CHECK: (func $bound-incremented-while (type $0) - ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (block $out - ;; CHECK-NEXT: (loop $loop - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.ge_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $out) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ge_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $loop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented-while (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (block $out - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.ge_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $out) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - (func $bound-incremented-while - ;; Similar to above, but before we had a do-while loop (loop condition at - ;; the bottom) and now it is at the top. - (local $x i32) - (block $out - (loop $loop - ;; Conditional branch at the top. - (if - (i32.ge_s - (local.get $x) - (i32.const 100) - ) - (then - (br $out) - ) - ) - ;; We can infer both of these to be true in loops mode (in normal mode, - ;; only the easy one, the first). - (drop - (i32.lt_s - (local.get $x) - (i32.const 100) - ) - ) - (drop - (i32.ge_s - (local.get $x) - (i32.const 0) - ) - ) - (local.set $x - (i32.add - (local.get $x) - (i32.const 1) - ) - ) - ;; Unconditional branch at the bottom. - (br $loop) - ) - ) - ) - - ;; CHECK: (func $bound-incremented-inc-first (type $0) - ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (block $out - ;; CHECK-NEXT: (loop $loop - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.ge_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $out) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.gt_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $loop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented-inc-first (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (block $out - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.ge_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $out) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - (func $bound-incremented-inc-first - ;; Similar to the above "while" loop, but now with the increment before the - ;; if. - (local $x i32) - (block $out - (loop $loop - ;; Increment at the top. - (local.set $x - (i32.add - (local.get $x) - (i32.const 1) - ) - ) - ;; If after the increment. - (if - (i32.ge_s - (local.get $x) - (i32.const 100) - ) - (then - (br $out) - ) - ) - ;; x > 0 && x < 100 here (0 is impossible, compared to before). - (drop - (i32.gt_s - (local.get $x) - (i32.const 0) - ) - ) - (drop - (i32.lt_s - (local.get $x) - (i32.const 100) - ) - ) - (br $loop) - ) - ) - ) - - ;; CHECK: (func $bound-incremented-inc-first-less (type $0) - ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (block $out - ;; CHECK-NEXT: (loop $loop - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.gt_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $out) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.gt_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $loop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented-inc-first-less (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (block $out - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.gt_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $out) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - (func $bound-incremented-inc-first-less - ;; As in the last testcase, but the if's condition changed. - (local $x i32) - (block $out - (loop $loop - (local.set $x - (i32.add - (local.get $x) - (i32.const 1) - ) - ) - ;; Before we left the loop when x >= 100. Now we leave when x > 100, - ;; so we do actually reach 100 in the code below. - (if - (i32.gt_s - (local.get $x) - (i32.const 100) - ) - (then - (br $out) - ) - ) - ;; x > 0 && x <= 100 here (but we need loops mode to get both). - (drop - (i32.gt_s - (local.get $x) - (i32.const 0) - ) - ) - (drop - (i32.le_s - (local.get $x) - (i32.const 100) - ) - ) - (br $loop) - ) - ) - ) - - ;; CHECK: (func $bound-incremented-inc-first-less-unsigned (type $0) - ;; CHECK-NEXT: (local $x i32) - ;; CHECK-NEXT: (block $out - ;; CHECK-NEXT: (loop $loop - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.gt_u - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (br $out) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.gt_u - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $loop) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented-inc-first-less-unsigned (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (block $out - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.gt_u - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $out) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - (func $bound-incremented-inc-first-less-unsigned - ;; As in the last testcase, but unsigned. - (local $x i32) - (block $out - (loop $loop - (local.set $x - (i32.add - (local.get $x) - (i32.const 1) - ) - ) - (if - (i32.gt_u - (local.get $x) - (i32.const 100) - ) - (then - (br $out) - ) - ) - ;; x > 0 && x <= 100 here (but we need loops mode to get both). - (drop - (i32.gt_u - (local.get $x) - (i32.const 0) - ) - ) - (drop - (i32.le_u - (local.get $x) - (i32.const 100) - ) - ) - (br $loop) - ) - ) - ) ) From a319bc1628506c525a50f99b633728f1025f01b4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:11:43 -0700 Subject: [PATCH 097/120] simpl --- src/passes/ConstraintAnalysis.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index da557d0b21f..e2dddb9d67d 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -322,7 +322,7 @@ struct ConstraintAnalysis if (auto branch = getBranchConstraints(block, out); branch && checkRelevancy(*branch)) { auto sentConstraints = constraints; - applyBranchConstraints(*branch, sentConstraints); + sentConstraints.approximateAnd(branch->local, branch->constraint); #if CONSTRAINT_DEBUG std::cout << block << " sending branch to " << out << " with sent constraints: " << sentConstraints << '\n'; @@ -506,9 +506,9 @@ struct ConstraintAnalysis } // The only binary operation we match is an increment (x + 1), and we do - // not always want to apply it: only in loops mode, and even then, only - // when we are allowed to keep working (see above). - if (set->value->is() && (!loops || !maxWorkLeft)) { + // not always want to apply it: only when we are allowed to keep working + // (see above). + if (set->value->is() && !maxWorkLeft) { constraints.setProvesNothing(set->index); return; } From 9cd4af1c51906b8d0d3b73e922857fc9814e3399 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:19:14 -0700 Subject: [PATCH 098/120] FIX --- src/ir/constraint.cpp | 1 - test/gtest/constraint.cpp | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 0489d87b618..bd7270e78dc 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -571,7 +571,6 @@ void BasicBlockConstraintMap::set(Index index, Expression* value) { switch (c.op) { // x == N, x++ => x == N+1. case Eq: - // TODO: overflows here and below c.term = Term(N->add(Literal::makeFromInt32(1, N->type))); continue; // x >= N, x++ => x > N diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 260f3cd7dfd..5cbb0688b40 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -607,6 +607,22 @@ TEST(ConstraintTest, TestIncrement) { map.set(0, &add); check(map.get(0), {LeU, Literal(int32_t(6))}); + // $0 <= max_signed, $0++ => nothing, because it would overflow + map.set(0, {LeS, Literal::makeSignedMax(Type::i32)}); + map.set(0, &add); + EXPECT_TRUE(map.get(0).provesNothing()); + + // $0 <= max_unsigned, $0++ => nothing, because it would overflow + map.set(0, {LeU, Literal::makeUnsignedMax(Type::i32)}); + map.set(0, &add); + EXPECT_TRUE(map.get(0).provesNothing()); + + // However, an unsigned operation on the signed max is fine. + map.set(0, {LeU, Literal::makeSignedMax(Type::i32)}); + map.set(0, &add); + auto one = Literal::makeFromInt32(1, Type::i32); + check(map.get(0), {LeU, Literal::makeSignedMax(Type::i32).add(one)}); + // Multiple constraints at once: // $0 >= 10 && $0 < 20, $0++ => $0 > 10 && $0 <= 20 map.set(0, {GeS, Literal(int32_t(10))}); From fa4dbca83072fa45c65475ece6d14c824d0e8d29 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:19:14 -0700 Subject: [PATCH 099/120] FIX --- src/ir/constraint.cpp | 1 - test/gtest/constraint.cpp | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 0489d87b618..bd7270e78dc 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -571,7 +571,6 @@ void BasicBlockConstraintMap::set(Index index, Expression* value) { switch (c.op) { // x == N, x++ => x == N+1. case Eq: - // TODO: overflows here and below c.term = Term(N->add(Literal::makeFromInt32(1, N->type))); continue; // x >= N, x++ => x > N diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 260f3cd7dfd..5cbb0688b40 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -607,6 +607,22 @@ TEST(ConstraintTest, TestIncrement) { map.set(0, &add); check(map.get(0), {LeU, Literal(int32_t(6))}); + // $0 <= max_signed, $0++ => nothing, because it would overflow + map.set(0, {LeS, Literal::makeSignedMax(Type::i32)}); + map.set(0, &add); + EXPECT_TRUE(map.get(0).provesNothing()); + + // $0 <= max_unsigned, $0++ => nothing, because it would overflow + map.set(0, {LeU, Literal::makeUnsignedMax(Type::i32)}); + map.set(0, &add); + EXPECT_TRUE(map.get(0).provesNothing()); + + // However, an unsigned operation on the signed max is fine. + map.set(0, {LeU, Literal::makeSignedMax(Type::i32)}); + map.set(0, &add); + auto one = Literal::makeFromInt32(1, Type::i32); + check(map.get(0), {LeU, Literal::makeSignedMax(Type::i32).add(one)}); + // Multiple constraints at once: // $0 >= 10 && $0 < 20, $0++ => $0 > 10 && $0 <= 20 map.set(0, {GeS, Literal(int32_t(10))}); From a9a9405a459491bb20245fdece032c0d4f1bdcce Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:25:19 -0700 Subject: [PATCH 100/120] FIX2 --- src/ir/constraint.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index bd7270e78dc..9359f4693e2 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -534,7 +534,6 @@ void BasicBlockConstraintMap::set(Index index, } } -// Set the value in an expression to a local, replacing anything before. void BasicBlockConstraintMap::set(Index index, Expression* value) { using namespace Match; using namespace Abstract; From 9d8fe3aa9626d2a5a500ac6697cbaabd154587e6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:26:36 -0700 Subject: [PATCH 101/120] FIX --- .../lit/passes/constraint-analysis-loops.wast | 226 +++++++++++++++++- 1 file changed, 223 insertions(+), 3 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index fcc88a3316a..8545fa734e1 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -1,10 +1,9 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s +;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s (module - ;; CHECK: (import "a" "b" (func $import (type $2) (result i32))) - ;; LOOPS: (import "a" "b" (func $import (type $2) (result i32))) + ;; CHECK: (import "a" "b" (func $import (type $1) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (func $infinite-loop (type $0) @@ -64,4 +63,225 @@ ) ) ) + + ;; CHECK: (func $bound (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.le_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $bound + (local $x i32) + (loop $loop + ;; We arrive at the loop top with {x == 0} || {x > 0 && x <= 100}. Those + ;; OR into {x >= 0 && x <= 100} - that is, the x == 0 and x > 0 combine + ;; into x >= 0. + (drop + (i32.ge_s + (local.get $x) + (i32.const 0) + ) + ) + (drop + (i32.le_s + (local.get $x) + (i32.const 100) + ) + ) + ;; Set $x to an unknown value before applying the constraints below on the + ;; way back to the loop top. + (local.set $x + (call $import) + ) + (if + (i32.gt_s + (local.get $x) + (i32.const 0) + ) + (then + (if + (i32.le_s + (local.get $x) + (i32.const 100) + ) + (then + (br $loop) + ) + ) + ) + ) + ) + ) + + ;; CHECK: (func $bound-flipped-ifs (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.le_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $bound-flipped-ifs + (local $x i32) + ;; As above, but with the ifs flipped. We optimize the same way. + (loop $loop + (drop + (i32.ge_s + (local.get $x) + (i32.const 0) + ) + ) + (drop + (i32.le_s + (local.get $x) + (i32.const 100) + ) + ) + (local.set $x + (call $import) + ) + (if + (i32.le_s + (local.get $x) + (i32.const 100) + ) + (then + (if + (i32.gt_s + (local.get $x) + (i32.const 0) + ) + (then + (br $loop) + ) + ) + ) + ) + ) + ) + + ;; CHECK: (func $bound-nonconstant-no (type $2) (param $p i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ge_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.le_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.le_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $bound-nonconstant-no (param $p i32) + (local $x i32) + ;; As above, but rather than zero we have an unknown param $p. + (loop $loop + ;; We can infer nothing here, as p is unknown, and x might be 0 or <= 100. + (drop + (i32.ge_s + (local.get $x) + (local.get $p) + ) + ) + (drop + (i32.le_s + (local.get $x) + (local.get $p) + ) + ) + (local.set $x + (call $import) + ) + (if + (i32.gt_s + (local.get $x) + (local.get $p) + ) + (then + (if + (i32.le_s + (local.get $x) + (i32.const 100) + ) + (then + (br $loop) + ) + ) + ) + ) + ) + ) ) From 1d17f8357f25ba16fbce489b7f6271c08f5a8320 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:25:19 -0700 Subject: [PATCH 102/120] FIX2 --- src/ir/constraint.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index bd7270e78dc..9359f4693e2 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -534,7 +534,6 @@ void BasicBlockConstraintMap::set(Index index, } } -// Set the value in an expression to a local, replacing anything before. void BasicBlockConstraintMap::set(Index index, Expression* value) { using namespace Match; using namespace Abstract; From eb4fc78d33a6715c4b7e2bbe2d6956f5e1c4056f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:42:21 -0700 Subject: [PATCH 103/120] FIX --- test/gtest/constraint.cpp | 48 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 5cbb0688b40..cad0b0b678e 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -568,67 +568,67 @@ TEST(ConstraintTest, TestIncrement) { add.right = &c; // $0 = 0, $1 = $0 + 1, so $1 = 1 (and $0 is unchanged). - map.set(0, {Eq, Literal(int32_t(0))}); + map.set(0, {Eq, {Literal(int32_t(0))}}); map.set(1, &add); - check(map.get(0), {Eq, Literal(int32_t(0))}); - check(map.get(1), {Eq, Literal(int32_t(1))}); + check(map.get(0), {Eq, {Literal(int32_t(0))}}); + check(map.get(1), {Eq, {Literal(int32_t(1))}}); // $0 = $0 + 1, where $0 was 0, so it is now 1. map.set(0, &add); - check(map.get(0), {Eq, Literal(int32_t(1))}); + check(map.get(0), {Eq, {Literal(int32_t(1))}}); // $0 >= 5, $0++ => $0 > 5 (signed) - map.set(0, {GeS, Literal(int32_t(5))}); + map.set(0, {GeS, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {GtS, Literal(int32_t(5))}); + check(map.get(0), {GtS, {Literal(int32_t(5))}}); // Ditto, unsigned - map.set(0, {GeU, Literal(int32_t(5))}); + map.set(0, {GeU, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {GtU, Literal(int32_t(5))}); + check(map.get(0), {GtU, {Literal(int32_t(5))}}); // $0 < 5, $0++ => $0 <= 5 (signed) - map.set(0, {LtS, Literal(int32_t(5))}); + map.set(0, {LtS, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {LeS, Literal(int32_t(5))}); + check(map.get(0), {LeS, {Literal(int32_t(5))}}); // Ditto, unsigned - map.set(0, {LtU, Literal(int32_t(5))}); + map.set(0, {LtU, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {LeU, Literal(int32_t(5))}); + check(map.get(0), {LeU, {Literal(int32_t(5))}}); // $0 <= 5, $0++ => $0 <= 6 (signed) - map.set(0, {LeS, Literal(int32_t(5))}); + map.set(0, {LeS, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {LeS, Literal(int32_t(6))}); + check(map.get(0), {LeS, {Literal(int32_t(6))}}); // Ditto, unsigned - map.set(0, {LeU, Literal(int32_t(5))}); + map.set(0, {LeU, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {LeU, Literal(int32_t(6))}); + check(map.get(0), {LeU, {Literal(int32_t(6))}}); // $0 <= max_signed, $0++ => nothing, because it would overflow - map.set(0, {LeS, Literal::makeSignedMax(Type::i32)}); + map.set(0, {LeS, {Literal::makeSignedMax(Type::i32)}}); map.set(0, &add); EXPECT_TRUE(map.get(0).provesNothing()); // $0 <= max_unsigned, $0++ => nothing, because it would overflow - map.set(0, {LeU, Literal::makeUnsignedMax(Type::i32)}); + map.set(0, {LeU, {Literal::makeUnsignedMax(Type::i32)}}); map.set(0, &add); EXPECT_TRUE(map.get(0).provesNothing()); // However, an unsigned operation on the signed max is fine. - map.set(0, {LeU, Literal::makeSignedMax(Type::i32)}); + map.set(0, {LeU, {Literal::makeSignedMax(Type::i32)}}); map.set(0, &add); auto one = Literal::makeFromInt32(1, Type::i32); - check(map.get(0), {LeU, Literal::makeSignedMax(Type::i32).add(one)}); + check(map.get(0), {LeU, {Literal::makeSignedMax(Type::i32).add(one)}}); // Multiple constraints at once: // $0 >= 10 && $0 < 20, $0++ => $0 > 10 && $0 <= 20 - map.set(0, {GeS, Literal(int32_t(10))}); - map.approximateAnd(0, {LtS, Literal(int32_t(20))}); + map.set(0, {GeS, {Literal(int32_t(10))}}); + map.approximateAnd(0, {LtS, {Literal(int32_t(20))}}); map.set(0, &add); EXPECT_EQ(map.get(0), - (AndedConstraintSet{{GtS, Literal(int32_t(10))}, - {LeS, Literal(int32_t(20))}})); + (AndedConstraintSet{{GtS, {Literal(int32_t(10))}}, + {LeS, {Literal(int32_t(20))}}})); } From 3e88a200b0abf5351d04fd4a3f54203addf557e1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 15:42:21 -0700 Subject: [PATCH 104/120] FIX --- test/gtest/constraint.cpp | 48 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp index 5cbb0688b40..cad0b0b678e 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -568,67 +568,67 @@ TEST(ConstraintTest, TestIncrement) { add.right = &c; // $0 = 0, $1 = $0 + 1, so $1 = 1 (and $0 is unchanged). - map.set(0, {Eq, Literal(int32_t(0))}); + map.set(0, {Eq, {Literal(int32_t(0))}}); map.set(1, &add); - check(map.get(0), {Eq, Literal(int32_t(0))}); - check(map.get(1), {Eq, Literal(int32_t(1))}); + check(map.get(0), {Eq, {Literal(int32_t(0))}}); + check(map.get(1), {Eq, {Literal(int32_t(1))}}); // $0 = $0 + 1, where $0 was 0, so it is now 1. map.set(0, &add); - check(map.get(0), {Eq, Literal(int32_t(1))}); + check(map.get(0), {Eq, {Literal(int32_t(1))}}); // $0 >= 5, $0++ => $0 > 5 (signed) - map.set(0, {GeS, Literal(int32_t(5))}); + map.set(0, {GeS, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {GtS, Literal(int32_t(5))}); + check(map.get(0), {GtS, {Literal(int32_t(5))}}); // Ditto, unsigned - map.set(0, {GeU, Literal(int32_t(5))}); + map.set(0, {GeU, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {GtU, Literal(int32_t(5))}); + check(map.get(0), {GtU, {Literal(int32_t(5))}}); // $0 < 5, $0++ => $0 <= 5 (signed) - map.set(0, {LtS, Literal(int32_t(5))}); + map.set(0, {LtS, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {LeS, Literal(int32_t(5))}); + check(map.get(0), {LeS, {Literal(int32_t(5))}}); // Ditto, unsigned - map.set(0, {LtU, Literal(int32_t(5))}); + map.set(0, {LtU, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {LeU, Literal(int32_t(5))}); + check(map.get(0), {LeU, {Literal(int32_t(5))}}); // $0 <= 5, $0++ => $0 <= 6 (signed) - map.set(0, {LeS, Literal(int32_t(5))}); + map.set(0, {LeS, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {LeS, Literal(int32_t(6))}); + check(map.get(0), {LeS, {Literal(int32_t(6))}}); // Ditto, unsigned - map.set(0, {LeU, Literal(int32_t(5))}); + map.set(0, {LeU, {Literal(int32_t(5))}}); map.set(0, &add); - check(map.get(0), {LeU, Literal(int32_t(6))}); + check(map.get(0), {LeU, {Literal(int32_t(6))}}); // $0 <= max_signed, $0++ => nothing, because it would overflow - map.set(0, {LeS, Literal::makeSignedMax(Type::i32)}); + map.set(0, {LeS, {Literal::makeSignedMax(Type::i32)}}); map.set(0, &add); EXPECT_TRUE(map.get(0).provesNothing()); // $0 <= max_unsigned, $0++ => nothing, because it would overflow - map.set(0, {LeU, Literal::makeUnsignedMax(Type::i32)}); + map.set(0, {LeU, {Literal::makeUnsignedMax(Type::i32)}}); map.set(0, &add); EXPECT_TRUE(map.get(0).provesNothing()); // However, an unsigned operation on the signed max is fine. - map.set(0, {LeU, Literal::makeSignedMax(Type::i32)}); + map.set(0, {LeU, {Literal::makeSignedMax(Type::i32)}}); map.set(0, &add); auto one = Literal::makeFromInt32(1, Type::i32); - check(map.get(0), {LeU, Literal::makeSignedMax(Type::i32).add(one)}); + check(map.get(0), {LeU, {Literal::makeSignedMax(Type::i32).add(one)}}); // Multiple constraints at once: // $0 >= 10 && $0 < 20, $0++ => $0 > 10 && $0 <= 20 - map.set(0, {GeS, Literal(int32_t(10))}); - map.approximateAnd(0, {LtS, Literal(int32_t(20))}); + map.set(0, {GeS, {Literal(int32_t(10))}}); + map.approximateAnd(0, {LtS, {Literal(int32_t(20))}}); map.set(0, &add); EXPECT_EQ(map.get(0), - (AndedConstraintSet{{GtS, Literal(int32_t(10))}, - {LeS, Literal(int32_t(20))}})); + (AndedConstraintSet{{GtS, {Literal(int32_t(10))}}, + {LeS, {Literal(int32_t(20))}}})); } From b368c1c6ab663f91afd9a33fbe633916a37035a9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 16:16:53 -0700 Subject: [PATCH 105/120] SIMPL --- src/ir/constraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 9359f4693e2..e837e21ca71 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -570,7 +570,7 @@ void BasicBlockConstraintMap::set(Index index, Expression* value) { switch (c.op) { // x == N, x++ => x == N+1. case Eq: - c.term = Term(N->add(Literal::makeFromInt32(1, N->type))); + *N = N->add(Literal::makeFromInt32(1, N->type)); continue; // x >= N, x++ => x > N case GeS: From 461c972acedb2842e03d904cc101d85f229a8ee2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Aug 2026 16:16:53 -0700 Subject: [PATCH 106/120] SIMPL --- src/ir/constraint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 9359f4693e2..e837e21ca71 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -570,7 +570,7 @@ void BasicBlockConstraintMap::set(Index index, Expression* value) { switch (c.op) { // x == N, x++ => x == N+1. case Eq: - c.term = Term(N->add(Literal::makeFromInt32(1, N->type))); + *N = N->add(Literal::makeFromInt32(1, N->type)); continue; // x >= N, x++ => x > N case GeS: From c79ac0701207ea247411c9592f2df0ab6fe811b3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Aug 2026 11:21:21 -0700 Subject: [PATCH 107/120] simpler --- src/passes/ConstraintAnalysis.cpp | 59 +++++++++++-------------------- 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index e2dddb9d67d..d2f4b2b6c29 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -193,7 +193,6 @@ struct ConstraintAnalysis } computeRelevantLocals(); - prepareToFlow(); flow(); optimize(); } @@ -224,35 +223,6 @@ struct ConstraintAnalysis } } - // Maintain a maximum amount of operations. The one non-linear thing that can - // happen is when we increment a local in a loop: it may go from 0 to 1, then - // branch back to the top and merge, making it in the range [0, 1], then get - // incremented and loop again, leading to [0, 2] and so forth, only stopping - // when it reaches the loop bound, which may be very high. We don't want to - // spend significant time on such constant operations, as other passes will - // propagate them anyhow, so we keep our time bounded. When this reaches 0, - // we will not do loop operations that might lead to such incrementing. - Index maxWorkLeft = 0; - - void prepareToFlow() { - // Compute a bound for maxOperations. flow() will spend time on each block, - // operation in a block, and branch, so add all those up. - for (auto& block : basicBlocks) { - maxWorkLeft += 1 + block->contents.actions.size() + block->out.size(); - } - - // We also allow a multiple of all the above: loop optimization generally - // requires us to process it twice (so that we see the merge at the top). - // Use a constant of 3 to make sure to work enough. - maxWorkLeft *= 3; - } - - void decMaxWork() { - if (maxWorkLeft > 0) { - maxWorkLeft--; - } - } - // Flow infos around until we have inferred all we can about the constraints // in each location. void flow() { @@ -291,8 +261,6 @@ struct ConstraintAnalysis while (!work.empty()) { auto* block = work.pop(); - decMaxWork(); - // Start at the top of the block, then go through, applying things. BasicBlockConstraintMap constraints = block->contents.startConstraints; @@ -302,8 +270,6 @@ struct ConstraintAnalysis for (auto** currp : block->contents.actions) { applyToConstraints(*currp, constraints); - - decMaxWork(); } #if CONSTRAINT_DEBUG @@ -313,8 +279,6 @@ struct ConstraintAnalysis // We now know the values at the end of the block. Flow it onward, and // where it causes changes, queue more work. for (auto* out : block->out) { - decMaxWork(); - auto& outStartConstraints = out->contents.startConstraints; // Find the constraints sent to this specific successor, if there is a @@ -495,6 +459,19 @@ struct ConstraintAnalysis return parsed; } + // When applying constraints for a binary operation like x = y + 1, we may + // end up with lots of nonlinear work, in a loop: x may go from 0 to 1, then + // branch back to the top and merge, making it in the range [0, 1], then get + // incremented and loop again, leading to [0, 2] and so forth, only stopping + // when it reaches the loop bound, which may be very high. We don't want to + // spend significant time on such constant operations, as other passes will + // propagate them anyhow, so we limit how many times we apply such x = y + 1 + // operations before marking them as unknown values. + static const Index MaxBinaryActions = 5; + + // How many times we processed each Binary action. + std::unordered_map binaryActionCounts; + // Given an expression, apply it to the constraints. For example, a local.set // sets the value for that local. void applyToConstraints(Expression* curr, @@ -508,9 +485,13 @@ struct ConstraintAnalysis // The only binary operation we match is an increment (x + 1), and we do // not always want to apply it: only when we are allowed to keep working // (see above). - if (set->value->is() && !maxWorkLeft) { - constraints.setProvesNothing(set->index); - return; + if (auto* binary = set->value->dynCast()) { + auto& count = binaryActionCounts[binary]; + if (count >= MaxBinaryActions) { + constraints.setProvesNothing(set->index); + return; + } + count++; } constraints.set(set->index, set->value); From bb8f7423535f810d52ea163d385a664828292470 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Aug 2026 11:43:31 -0700 Subject: [PATCH 108/120] work --- test/lit/passes/constraint-analysis-loops.wast | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 14731b99a45..3e8cfbde93a 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -419,7 +419,10 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.lt_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.ge_s @@ -517,7 +520,10 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.lt_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.ge_u From 7516199dac6d0ee198a813c96f9af92b56ed197b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Aug 2026 11:48:33 -0700 Subject: [PATCH 109/120] simpler --- src/passes/ConstraintAnalysis.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index d2f4b2b6c29..c90f25323cc 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -465,12 +465,14 @@ struct ConstraintAnalysis // incremented and loop again, leading to [0, 2] and so forth, only stopping // when it reaches the loop bound, which may be very high. We don't want to // spend significant time on such constant operations, as other passes will - // propagate them anyhow, so we limit how many times we apply such x = y + 1 - // operations before marking them as unknown values. + // propagate them anyhow, so we verify that we don't apply such x = y + 1 + // operations too many times. +#ifndef NDEBUG static const Index MaxBinaryActions = 5; // How many times we processed each Binary action. std::unordered_map binaryActionCounts; +#endif // Given an expression, apply it to the constraints. For example, a local.set // sets the value for that local. @@ -482,17 +484,12 @@ struct ConstraintAnalysis return; } - // The only binary operation we match is an increment (x + 1), and we do - // not always want to apply it: only when we are allowed to keep working - // (see above). +#ifndef NDEBUG + // See above on binary action counting limits. if (auto* binary = set->value->dynCast()) { - auto& count = binaryActionCounts[binary]; - if (count >= MaxBinaryActions) { - constraints.setProvesNothing(set->index); - return; - } - count++; + assert(binaryActionCounts[binary]++ <= MaxBinaryActions); } +#endif constraints.set(set->index, set->value); } From 231835c82f76276a2c1fefad1be6724f4278ef0d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Aug 2026 11:57:20 -0700 Subject: [PATCH 110/120] work --- test/lit/passes/constraint-analysis-loops.wast | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 3e8cfbde93a..65f955b0292 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -477,20 +477,14 @@ (local $x i32) (loop $loop ;; A realistic do-while loop, with $x++ and a bounds check. We must infer - ;; that no overflow happens in order to prove these two checks are true. - ;; - ;; The first is trivially true, as x starts at 0 - fulfilling x < 100 - - ;; and the branch back to the loop top arrives with x < 100. + ;; that no overflow happens in order to prove these two checks are true, + ;; and only loops mode manages that. (drop (i32.lt_s (local.get $x) (i32.const 100) ) ) - ;; This is non-trivial, as we must rule out a possible overflow. The - ;; only reason that x never gets incremented so many times that it becomes - ;; negative is that the incrementation process is stopped at 100. We only - ;; manage to optimize this in "loops" mode. (drop (i32.ge_s (local.get $x) @@ -575,8 +569,8 @@ ;; LOOPS-NEXT: ) ;; LOOPS-NEXT: ) (func $bound-incremented-unsigned - ;; As above, but with unsigned operations. This is simpler, and we optimize - ;; it even without "loops" mode. + ;; As above, but with unsigned operations. Again, we need loops mode to + ;; optimize. (local $x i32) (loop $loop (drop From d1b8260eddef2e0484af1714367719661cc42924 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 7 Aug 2026 10:49:24 -0700 Subject: [PATCH 111/120] share code in ::set() --- src/ir/constraint.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index e837e21ca71..7d42523ba2f 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -506,21 +506,15 @@ void LocalConstraint::flip() { } void BasicBlockConstraintMap::set(Index index, const Constraint& c) { - // We should not set values in unreachable code. - assert(!unreachable); - - // Clear the old state. - eraseStaleRefs(index); - map.erase(index); - - // Apply the constraint. - approximateAnd(index, c); + set(index, AndedConstraintSet{c}); } void BasicBlockConstraintMap::set(Index index, const AndedConstraintSet& constraints) { - // As above, but with a loop after. + // We should not set values in unreachable code. assert(!unreachable); + + // Clear the old state. eraseStaleRefs(index); map.erase(index); From ddf9d4014f71d4eba1c8891b840230ec2e8b4ea2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 7 Aug 2026 11:08:49 -0700 Subject: [PATCH 112/120] simpl --- src/passes/ConstraintAnalysis.cpp | 80 +++++++++++-------------------- src/passes/pass.cpp | 3 -- src/passes/passes.h | 1 - 3 files changed, 28 insertions(+), 56 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 01dc9fdc96e..4ea0563068e 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -21,9 +21,7 @@ // assert(x != 0); // redundant and can be removed. // } // -// The normal version of this pass flows constraints around in the most precise -// way that we can. However, while doing so, it must avoid optimizing loop -// variables, because of the following problem: +// For loops, we must avoid the following problem: // // x = 0 // do { @@ -34,48 +32,34 @@ // Say that we flow information around precisely. Then initially x is 0 at the // top of the loop, and x++ turns it into 1. 1 < 100 so we return to the top of // the loop, and now x can be 0 or 1. We will then interpret this loop for 100 -// iterations at compile time, which is obviously not a good idea. +// iterations at compile time, going from [0] to [0, 1] to [0, 2] and so forth, +// which is obviously not a good idea. // -// Instead, in "normal" mode we just don't increment variables when we see x++. -// This does limit us, but only on loops, in practice - if x is not in a loop, -// and we know its constant value, then x++ would be optimized away by other -// passes. And, by avoiding a precise execution of x++, we avoid the problem of -// interpreting loops at compile time. +// Instead, we do something similar to "widening" in abstract interpretation +// (which at a loop header, where a merge occurs, widen the range of values +// based on the bounds check that it sees elsewhere). We do something even +// simpler here, which can be accomplished in an eager way as follows: // -// But we do want to optimize loops like the example above. The second part, -// x < 100, is trivial: at the loop top, either x == 0 from before the loop, or -// x < 100 from the loop backedge, and both prove x < 100. However x >= 0 is -// non-obvious: if x is *signed*, then we must rule out the possibility of it -// getting incremented so many times that it overflows and becomes negative. -// Proving that requires actually seeing that the loop variable x is incremented -// from 0 to 100, and no more, and that involves an interaction of the initial -// value, the increment, and the condition on the loop backedge. -// -// To optimize this, in "loops" mode we "jump ahead" to what is likely a loop -// limit: if we see a loop variable that is branched on, we expand the range of -// values the variable can take up to that bound. Concretely, we do this: -// -// * We do implement x++, turning x from 0 to 1 in the example above, in the -// first iteration of the loop. +// * x++ turns x from 0 to 1 in the example above, in the first iteration of +// the loop. // * When we then see x == 1 that branches with x < 100, we turn that into // x >= 1 && x < 100. This is "imprecise", because perhaps the local will // not actually get incremented all the way to 100, but it is an upper // bound that ends up getting us to the result we want in common loop // shapes. (And it is safe to do because we allow more values for x, meaning // we can prove fewer things, so we won't prove anything false.) +// * After doing that, we return to the top of the loop, where now we can see +// x >= 0 && x < 100. After running that through the loop a second time, no +// more happen: we successfully "jumped ahead" to the end state of the +// loop variable. // -// After doing that, we return to the top of the loop, where now we can see -// x >= 0 && x < 100. After running that through the loop a second time, no more -// changes will happen: we successfully "jumped ahead" to the end state of the -// loop variable. -// -// In theory, "normal" mode may optimize some things better than "loops" mode, -// as the "jump ahead" behavior extends ranges of variables eagerly, before we -// know they actually can fill out that range. In practice, however, it is rare -// to see a constant to which is applied a bound like x < 100, unless it is -// actually a loop variable: if it isn't a loop variable, then other passes -// would propagate the constant and remove the bounds check. Still, both modes -// of this pass are kept for comparison purposes. +// Doing this eagerly when we see a branch, rather than identifying specific +// loop headers and analyzing their bounds more precisely, is good enough for +// us: the only imprecision we add is "x == C, branch with x < D => x >= C && +// x < D". While imprecise, if we see "x == C, branch with x < D", then this is +// a situation inside a loop: if it were not, then x would get constant- +// propagatated to the branch anyhow by other passes. And, if this is in a loop, +// then this widening is exactly what we want. // #include "cfg/cfg-traversal.h" @@ -136,14 +120,9 @@ struct ConstraintAnalysis bool requiresNonNullableLocalFixups() override { return false; } std::unique_ptr create() override { - return std::make_unique(loops); + return std::make_unique(); } - // Whether we are in "loops" mode, see above. - bool loops; - - ConstraintAnalysis(bool loops) : loops(loops) {} - using Super = WalkerPass< CFGWalker, Info>>; @@ -586,24 +565,24 @@ struct ConstraintAnalysis // Apply branch constraints to the current set of constraints. void applyBranchConstraints(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { - // In "loops" mode, extend the range of values in the "jump ahead" manner. - if (loops && applyBranchRangeExtensionToConstraints(branch, constraints)) { + // Extend the range of values in the "jump ahead" manner described in the + // top-level comment. + if (applyBranchRangeExtensionToConstraints(branch, constraints)) { return; } + // Otherwise, apply the constraint normally. constraints.approximateAnd(branch.local, branch.constraint); } bool applyBranchRangeExtensionToConstraints(const LocalConstraint& branch, BasicBlockConstraintMap& constraints) { - assert(loops); - using namespace Abstract; // "Jump ahead" and extend ranges. If the branch is x < M, and we were - // x == N where N < M, then extend to x >= N && x < M, as described in the - // top level comment. + // x == N where N < M, then extend to x >= N && x < M (see top-level + // comment). if (auto* M = std::get_if(&branch.constraint.term)) { auto localConstraints = constraints.get(branch.local); if (localConstraints.size() == 1 && @@ -637,10 +616,7 @@ struct ConstraintAnalysis } // anonymous namespace -Pass* createConstraintAnalysisPass() { return new ConstraintAnalysis(false); } -Pass* createConstraintAnalysisLoopsPass() { - return new ConstraintAnalysis(true); -} +Pass* createConstraintAnalysisPass() { return new ConstraintAnalysis(); } // see a.txt diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index b0a73e34b2f..34bc0c7dd40 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -132,9 +132,6 @@ void PassRegistry::registerPasses() { registerPass("constraint-analysis", "finds and uses mathematical constraints on locals", createConstraintAnalysisPass); - registerPass("constraint-analysis-loops", - "constraint-analysis that also optimizes loops", - createConstraintAnalysisLoopsPass); registerPass( "dce", "removes unreachable code", createDeadCodeEliminationPass); registerPass("dealign", diff --git a/src/passes/passes.h b/src/passes/passes.h index eb7c5c22b94..86e506b44b8 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -34,7 +34,6 @@ Pass* createConstHoistingPass(); Pass* createConstantFieldPropagationPass(); Pass* createConstantFieldPropagationRefTestPass(); Pass* createConstraintAnalysisPass(); -Pass* createConstraintAnalysisLoopsPass(); Pass* createDAEPass(); Pass* createDAEOptimizingPass(); Pass* createDAE2Pass(); From 08e7dcb5d32defc82899d33399984c70ef93fe39 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 7 Aug 2026 11:09:49 -0700 Subject: [PATCH 113/120] short --- .../lit/passes/constraint-analysis-loops.wast | 340 ------------------ 1 file changed, 340 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 65f955b0292..17f1cecd1d8 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -1,14 +1,9 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; Run both normally and in the "loops" mode. Many optimizations work in both -;; modes, but some require "loops" (mentioned below where that occurs). - ;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s -;; RUN: wasm-opt %s --constraint-analysis-loops -all -S -o - | filecheck %s --check-prefix=LOOPS (module ;; CHECK: (import "a" "b" (func $import (type $2) (result i32))) - ;; LOOPS: (import "a" "b" (func $import (type $2) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (func $infinite-loop (type $0) @@ -23,18 +18,6 @@ ;; CHECK-NEXT: (br $loop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $infinite-loop (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $infinite-loop (local $x i32) ;; An infinite loop. We should not hang, but nothing can be optimized. @@ -63,20 +46,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $almost-infinite-loop (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br_if $loop - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $almost-infinite-loop (local $x i32) ;; A loop that continues until an overflow happens. We should not hang, but @@ -126,37 +95,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (call $import) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.gt_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 0) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.le_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $bound (local $x i32) (loop $loop @@ -231,37 +169,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-flipped-ifs (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (call $import) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.le_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.gt_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 0) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $bound-flipped-ifs (local $x i32) ;; As above, but with the ifs flipped. We optimize the same way. @@ -338,43 +245,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-nonconstant-no (type $1) (param $p i32) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.ge_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (local.get $p) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.le_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (local.get $p) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (call $import) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.gt_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (local.get $p) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.le_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $bound-nonconstant-no (param $p i32) (local $x i32) ;; As above, but rather than zero we have an unknown param $p. @@ -447,32 +317,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.lt_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $bound-incremented (local $x i32) (loop $loop @@ -542,32 +386,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented-unsigned (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.lt_u - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $bound-incremented-unsigned ;; As above, but with unsigned operations. Again, we need loops mode to ;; optimize. @@ -645,48 +463,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $increment-non-constant (type $1) (param $p i32) - ;; LOOPS-NEXT: (local $a i32) - ;; LOOPS-NEXT: (local $b i32) - ;; LOOPS-NEXT: (local $scratch i32) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (block (result i32) - ;; LOOPS-NEXT: (local.set $scratch - ;; LOOPS-NEXT: (i32.lt_s - ;; LOOPS-NEXT: (local.get $p) - ;; LOOPS-NEXT: (i32.const 0) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.le_s - ;; LOOPS-NEXT: (local.get $a) - ;; LOOPS-NEXT: (local.get $b) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (local.set $p - ;; LOOPS-NEXT: (local.get $a) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $a - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $a) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.eq - ;; LOOPS-NEXT: (local.get $a) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.get $scratch) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $increment-non-constant (param $p i32) (local $a i32) (local $b i32) @@ -770,35 +546,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented-while (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (block $out - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.ge_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $out) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $bound-incremented-while ;; Similar to above, but before we had a do-while loop (loop condition at ;; the bottom) and now it is at the top. @@ -873,35 +620,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented-inc-first (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (block $out - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.ge_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $out) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $bound-incremented-inc-first ;; Similar to the above "while" loop, but now with the increment before the ;; if. @@ -975,35 +693,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented-inc-first-less (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (block $out - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.gt_s - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $out) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $bound-incremented-inc-first-less ;; As in the last testcase, but the if's condition changed. (local $x i32) @@ -1076,35 +765,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; LOOPS: (func $bound-incremented-inc-first-less-unsigned (type $0) - ;; LOOPS-NEXT: (local $x i32) - ;; LOOPS-NEXT: (block $out - ;; LOOPS-NEXT: (loop $loop - ;; LOOPS-NEXT: (local.set $x - ;; LOOPS-NEXT: (i32.add - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (if - ;; LOOPS-NEXT: (i32.gt_u - ;; LOOPS-NEXT: (local.get $x) - ;; LOOPS-NEXT: (i32.const 100) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (then - ;; LOOPS-NEXT: (br $out) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (drop - ;; LOOPS-NEXT: (i32.const 1) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: (br $loop) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) - ;; LOOPS-NEXT: ) (func $bound-incremented-inc-first-less-unsigned ;; As in the last testcase, but unsigned. (local $x i32) From a32d60610cb79c94e5a8db37456ac0a50c774aca Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 7 Aug 2026 11:13:17 -0700 Subject: [PATCH 114/120] fix --- .../lit/passes/constraint-analysis-loops.wast | 55 +++++-------------- 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 17f1cecd1d8..b08f891cd6b 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -289,16 +289,10 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.lt_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ge_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.add @@ -320,9 +314,8 @@ (func $bound-incremented (local $x i32) (loop $loop - ;; A realistic do-while loop, with $x++ and a bounds check. We must infer - ;; that no overflow happens in order to prove these two checks are true, - ;; and only loops mode manages that. + ;; A realistic do-while loop, with $x++ and a bounds check. We can infer + ;; that no overflow happens, and prove these two checks are true. (drop (i32.lt_s (local.get $x) @@ -358,16 +351,10 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.lt_u - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 100) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ge_u - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.add @@ -387,8 +374,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $bound-incremented-unsigned - ;; As above, but with unsigned operations. Again, we need loops mode to - ;; optimize. + ;; As above, but with unsigned operations. Again, we optimize these to 1. (local $x i32) (loop $loop (drop @@ -487,7 +473,7 @@ ;; We then proceed to do $a++, trying to increment each of those three ;; constraints. We should not hit an internal error on trying to increment ;; any of the three, ending up failing on the third (though, with a higher- - ;; level view, we could use the fact that $p == 0). + ;; level view, we could use the fact that $p == 0). TODO (local.set $a (i32.add (local.get $a) @@ -531,10 +517,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.ge_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.add @@ -562,8 +545,7 @@ (br $out) ) ) - ;; We can infer both of these to be true in loops mode (in normal mode, - ;; only the easy one, the first). + ;; We can infer both of these to be 1. (drop (i32.lt_s (local.get $x) @@ -608,10 +590,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.gt_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) @@ -681,10 +660,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.gt_s - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) @@ -715,7 +691,7 @@ (br $out) ) ) - ;; x > 0 && x <= 100 here (but we need loops mode to get both). + ;; x > 0 && x <= 100 here. (drop (i32.gt_s (local.get $x) @@ -753,10 +729,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.gt_u - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) From ae76769b517bfedad6e84bb22f4f314282a8bf07 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 7 Aug 2026 11:15:12 -0700 Subject: [PATCH 115/120] fix --- test/lit/passes/constraint-analysis-loops.wast | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index b08f891cd6b..758b0f5158f 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -472,8 +472,11 @@ ;; $a == 0 && $a <= $b, $a == $p ;; We then proceed to do $a++, trying to increment each of those three ;; constraints. We should not hit an internal error on trying to increment - ;; any of the three, ending up failing on the third (though, with a higher- - ;; level view, we could use the fact that $p == 0). TODO + ;; any of the three, ending up failing on the third. + ;; TODO: We should use the fact that $a == 0 to infer $a == 1, as we + ;; do not even need the others. We should not add constraints on + ;; $a when it is a constant, and apply constraints to the others + ;; ($b >= 0 and $p == 0 can be inferred for them). (local.set $a (i32.add (local.get $a) From 048ca7f09daf3e532ce64618593e67c26fd5ed54 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 7 Aug 2026 11:35:36 -0700 Subject: [PATCH 116/120] merg --- src/passes/ConstraintAnalysis.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 4ea0563068e..fb46d8d7c8c 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -36,7 +36,7 @@ // which is obviously not a good idea. // // Instead, we do something similar to "widening" in abstract interpretation -// (which at a loop header, where a merge occurs, widen the range of values +// (which at a loop header, where a merge occurs, widens the range of values // based on the bounds check that it sees elsewhere). We do something even // simpler here, which can be accomplished in an eager way as follows: // @@ -59,7 +59,11 @@ // x < D". While imprecise, if we see "x == C, branch with x < D", then this is // a situation inside a loop: if it were not, then x would get constant- // propagatated to the branch anyhow by other passes. And, if this is in a loop, -// then this widening is exactly what we want. +// then this widening is exactly what we want. This eager approach avoids us +// needing to analyze loops shapes specifically and/or to consider branch +// conditions "from afar" (seeing a branch on "x < D", but *not* applying it +// eagerly, and instead using it later at the loop header or in some whole- +// function analysis). // #include "cfg/cfg-traversal.h" From 9fe2a9258c3c83bae8090be6a23ab68312d06b47 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 7 Aug 2026 11:38:33 -0700 Subject: [PATCH 117/120] merg --- src/passes/ConstraintAnalysis.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index fb46d8d7c8c..824e48ca2dd 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -622,6 +622,4 @@ struct ConstraintAnalysis Pass* createConstraintAnalysisPass() { return new ConstraintAnalysis(); } -// see a.txt - } // namespace wasm From 20413563070c6d235d00f8bb76a16074bbc5a8b0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 7 Aug 2026 11:38:53 -0700 Subject: [PATCH 118/120] merg --- test/lit/passes/constraint-analysis-loops.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 758b0f5158f..feaf149a38e 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -1,6 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s +;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s (module ;; CHECK: (import "a" "b" (func $import (type $2) (result i32))) From 9ca789d891179d6408001fbfb4822aee07d43244 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 7 Aug 2026 11:42:09 -0700 Subject: [PATCH 119/120] merg --- .../lit/passes/constraint-analysis-loops.wast | 96 ------------------- 1 file changed, 96 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index feaf149a38e..748eb2fc917 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -407,102 +407,6 @@ ) ) - ;; CHECK: (func $increment-non-constant (type $1) (param $p i32) - ;; CHECK-NEXT: (local $a i32) - ;; CHECK-NEXT: (local $b i32) - ;; CHECK-NEXT: (local $scratch i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.set $scratch - ;; CHECK-NEXT: (i32.lt_s - ;; CHECK-NEXT: (local.get $p) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (i32.le_s - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: (local.get $b) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (local.set $p - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $a - ;; CHECK-NEXT: (i32.add - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.get $scratch) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $increment-non-constant (param $p i32) - (local $a i32) - (local $b i32) - (drop - (i32.lt_s - (local.get $p) - (i32.const 0) - ) - (if - (i32.le_s - (local.get $a) - (local.get $b) - ) - (then - ;; Before this set, this is what we know about $a: - ;; $a == 0 && $a <= $b - (local.set $p - (local.get $a) - ) - ;; We just set $p to $a, so now we know this about $a: - ;; $a == 0 && $a <= $b, $a == $p - ;; We then proceed to do $a++, trying to increment each of those three - ;; constraints. We should not hit an internal error on trying to increment - ;; any of the three, ending up failing on the third. - ;; TODO: We should use the fact that $a == 0 to infer $a == 1, as we - ;; do not even need the others. We should not add constraints on - ;; $a when it is a constant, and apply constraints to the others - ;; ($b >= 0 and $p == 0 can be inferred for them). - (local.set $a - (i32.add - (local.get $a) - (i32.const 1) - ) - ) - ;; Since we failed to know things about $a after $a++, we cannot - ;; prove this. - (drop - (i32.eq - (local.get $a) - (i32.const 1) - ) - ) - ;; But we did not forget about $b. - (drop - (i32.eq - (local.get $b) - (i32.const 0) - ) - ) - ) - ) - ) - ) - ;; CHECK: (func $bound-incremented-while (type $0) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (block $out From 5717dd6f9eac9a0afb2e69f4f9b1d985e941bb7d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 7 Aug 2026 12:56:38 -0700 Subject: [PATCH 120/120] update test outputs --- test/lit/passes/constraint-analysis-loops.wast | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 748eb2fc917..792e7df454e 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -3,7 +3,7 @@ ;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s (module - ;; CHECK: (import "a" "b" (func $import (type $2) (result i32))) + ;; CHECK: (import "a" "b" (func $import (type $1) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (func $infinite-loop (type $0) @@ -208,7 +208,7 @@ ) ) - ;; CHECK: (func $bound-nonconstant-no (type $1) (param $p i32) + ;; CHECK: (func $bound-nonconstant-no (type $2) (param $p i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (drop