Add a ConstraintAnalysis pass (#8853)
This simply parses IR into constraints, flows them around, and sees
if we can prove things.
This is a minimal version, without conditional propagation etc.
diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp
index 57420a1..32d6504 100644
--- a/src/ir/constraint.cpp
+++ b/src/ir/constraint.cpp
@@ -77,6 +77,12 @@
} // anonymous namespace
Result AndedConstraintSet::proves(const Constraint& condition) const {
+ if (provesEverything()) {
+ return True;
+ }
+ // Note we do not need to handle the provesNothing case in a special way: the
+ // loop below finds nothing.
+
// Sometimes a single constraint is enough to determine the condition.
for (auto& c : *this) {
auto result = provesPair(c, condition);
@@ -92,11 +98,15 @@
}
Result AndedConstraintSet::proves(const AndedConstraintSet& other) const {
- if (other.empty()) {
- // The empty set of constraints is always true.
+ if (provesEverything()) {
return True;
}
+ if (other.provesEverything()) {
+ // We are not a contradiction, but other is, so we prove it false.
+ return False;
+ }
+
bool hasUnknown = false;
for (auto& c : other) {
@@ -114,6 +124,17 @@
}
void AndedConstraintSet::approximateAnd(const Constraint& c) {
+ if (provesEverything()) {
+ // Nothing to add.
+ return;
+ }
+
+ if (proves(c) == False) {
+ // We are now a contradiction.
+ isContradiction = true;
+ return;
+ }
+
if (size() < MaxConstraints) {
push_back(c);
return;
@@ -125,13 +146,12 @@
}
void AndedConstraintSet::approximateOr(const AndedConstraintSet& other) {
- // If one is empty (no constraints, everything is true, and we can prove
- // nothing useful) then it does not add anything to the other.
- if (empty()) {
+ // If one proves everything, the only thing that matters is the other.
+ if (provesEverything()) {
*this = other;
return;
}
- if (other.empty()) {
+ if (other.provesEverything()) {
return;
}
@@ -153,4 +173,112 @@
clear();
}
+std::optional<LocalConstraint> LocalConstraint::parse(Expression* curr) {
+ auto parseEqZArgument =
+ [&](Expression* value) -> std::optional<LocalConstraint> {
+ if (auto* get = value->dynCast<LocalGet>()) {
+ // Canonicalize EqZ to Eq of 0.
+ auto value = Literal::makeZero(get->type);
+ return LocalConstraint{get->index, Constraint{Abstract::Eq, {value}}};
+ }
+ // TODO: Recursively parse and reverse a constraint
+ return {};
+ };
+
+ if (auto* unary = curr->dynCast<Unary>()) {
+ if (Abstract::getUnary(unary->type, Abstract::EqZ) == unary->op) {
+ return parseEqZArgument(unary->value);
+ }
+ return {};
+ }
+
+ if (auto* refIsNull = curr->dynCast<RefIsNull>()) {
+ return parseEqZArgument(refIsNull->value);
+ }
+
+ // Parse a get or a constant.
+ auto parseTerm = [&](Expression* expr) -> std::optional<Term> {
+ if (auto* get = expr->dynCast<LocalGet>()) {
+ return Term{get->index};
+ }
+ if (Properties::isSingleConstantExpression(expr)) {
+ return Term{Properties::getLiteral(expr)};
+ }
+ return {};
+ };
+
+ auto parseBinaryArguments =
+ [&](Abstract::Op op,
+ Expression* left,
+ Expression* right) -> std::optional<LocalConstraint> {
+ // The left must be a get.
+ if (auto* get = left->dynCast<LocalGet>()) {
+ // The right can be any term.
+ if (auto value = parseTerm(right)) {
+ return LocalConstraint{get->index, Constraint{op, *value}};
+ }
+ }
+ return {};
+ };
+
+ if (auto* binary = curr->dynCast<Binary>()) {
+ // The operation must be one we recognize.
+ for (auto op : {Abstract::Eq, Abstract::Ne}) {
+ if (Abstract::getBinary(binary->type, op) == binary->op) {
+ return parseBinaryArguments(op, binary->left, binary->right);
+ }
+ }
+ return {};
+ }
+
+ if (auto* refEq = curr->dynCast<RefEq>()) {
+ return parseBinaryArguments(Abstract::Eq, refEq->left, refEq->right);
+ }
+
+ return {};
+}
+
+void LocalConstraintMap::approximateOr(const LocalConstraintMap& other) {
+ // Remove things only in us.
+ std::erase_if(*this, [&](const auto& item) {
+ const auto& [local, constraints] = item;
+ return !other.contains(local);
+ });
+
+ for (auto& [local, constraints] : other) {
+ (*this)[local].approximateOr(constraints);
+ }
+}
+
+std::ostream& operator<<(std::ostream& o, const Constraint& constraint) {
+ o << "Constraint{" << /*constraint.op <<*/ ", ";
+ if (auto* c = std::get_if<Literal>(&constraint.term)) {
+ o << *c;
+ } else if (auto* i = std::get_if<Index>(&constraint.term)) {
+ o << "Index(" << *i << ')';
+ }
+ o << '}';
+ return o;
+}
+
+std::ostream& operator<<(std::ostream& o,
+ const AndedConstraintSet& constraints) {
+ if (constraints.provesEverything()) {
+ o << "AndedConstraintSet(contradiction)";
+ return o;
+ }
+ o << "AndedConstraintSet{";
+ bool first = true;
+ for (auto& constraint : constraints) {
+ if (first) {
+ first = false;
+ } else {
+ o << ", ";
+ }
+ o << constraint;
+ }
+ o << '}';
+ return o;
+}
+
} // namespace wasm::constraint
diff --git a/src/ir/constraint.h b/src/ir/constraint.h
index 4a689ce..cf9da48 100644
--- a/src/ir/constraint.h
+++ b/src/ir/constraint.h
@@ -60,6 +60,36 @@
// about, which looks like a local, but it could be a global or a struct field
// or anything else in general.
struct AndedConstraintSet : inplace_vector<Constraint, MaxConstraints> {
+ // We could represent a contradiction using two constraints that contradict
+ // each other (== 0 && != 0), but for simplicity we mark this explicitly.
+ //
+ // A contradiction is the default value here, because it represents
+ // unreachable code: if (x == 0 && x != 0) { .. unreachable ..}. That is, we
+ // assume we represent the constraints in code that has not been reached,
+ // until something changes.
+ bool isContradiction = true;
+
+ // Proving everything (even contradictions) is equivalent to being a
+ // contradiction. (This and provesNothing can be seen as the top/bottom of a
+ // poset, if one wants to think of things that way.)
+ bool provesEverything() const { return isContradiction; }
+
+ void setProvesEverything() {
+ clear();
+ isContradiction = true;
+ assert(provesEverything());
+ }
+
+ // An empty set of contradictions means we know nothing, and so anything is
+ // possible, and we can prove nothing.
+ bool provesNothing() const { return empty(); }
+
+ void setProvesNothing() {
+ clear();
+ isContradiction = false;
+ assert(provesNothing());
+ }
+
// Check a condition against this set, that is, whether the existing
// constraints prove that it must be true, false, or unknown: whether
//
@@ -119,8 +149,44 @@
//
// If we become too imprecise, we lose the ability to imply anything useful.
void approximateOr(const AndedConstraintSet& other);
+
+ // Set a constraint, replacing all previous state.
+ void set(const Constraint& c) {
+ setProvesNothing();
+ push_back(c);
+ }
};
+// A local plus a constraint on it.
+struct LocalConstraint {
+ Index local;
+ Constraint constraint;
+
+ // Try to parse BinaryenIR into a local to which a constraint is applied. For
+ // example
+ //
+ // (i32.eq (local.get $r) (i32.const 10))
+ //
+ // parses into
+ //
+ // LocalConstraint($r, { x == 10 })
+ //
+ static std::optional<LocalConstraint> parse(Expression* curr);
+};
+
+// A map of locals and their constraints.
+struct LocalConstraintMap
+ : public std::unordered_map<Index, AndedConstraintSet> {
+ // Perform an OR as above. When a local only appears in one map, we treat it
+ // as if it contains a contradiction there, that is, as if the code is
+ // unreachable.
+ void approximateOr(const LocalConstraintMap& other);
+};
+
+std::ostream& operator<<(std::ostream& o, const Constraint& constraint);
+std::ostream& operator<<(std::ostream& o,
+ const AndedConstraintSet& constraints);
+
} // namespace wasm::constraint
#endif // wasm_ir_constraint_h
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index 2dfda96..6840771 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -26,6 +26,7 @@
CodeFolding.cpp
ConstantFieldPropagation.cpp
ConstHoisting.cpp
+ ConstraintAnalysis.cpp
DataFlowOpts.cpp
DeadArgumentElimination.cpp
DeadArgumentElimination2.cpp
diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp
new file mode 100644
index 0000000..7b1a04d
--- /dev/null
+++ b/src/passes/ConstraintAnalysis.cpp
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2026 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Use mathematical constraint solving to optimize. For example:
+//
+// if (x == 10) {
+// assert(x != 0); // redundant and can be removed.
+// }
+//
+
+#include "cfg/cfg-traversal.h"
+#include "ir/constraint.h"
+#include "ir/drop.h"
+#include "ir/literal-utils.h"
+#include "ir/local-graph.h"
+#include "ir/properties.h"
+#include "pass.h"
+#include "support/unique_deferring_queue.h"
+#include "support/utilities.h"
+#include "wasm-builder.h"
+#include "wasm.h"
+
+namespace wasm {
+
+using namespace wasm::constraint;
+
+namespace {
+
+// In each basic block we will store the relevant operations, which are all
+// local gets and sets, branches, and uses of them.
+struct Info {
+ std::vector<Expression**> actions;
+
+ // For each local index, we track the constraints we know about it. We only do
+ // so at the start of each block, which is enough for the analysis below.
+ LocalConstraintMap startConstraints;
+};
+
+struct ConstraintAnalysis
+ : public WalkerPass<
+ CFGWalker<ConstraintAnalysis, Visitor<ConstraintAnalysis>, Info>> {
+ bool isFunctionParallel() override { return true; }
+
+ // Locals are not modified here.
+ bool requiresNonNullableLocalFixups() override { return false; }
+
+ std::unique_ptr<Pass> create() override {
+ return std::make_unique<ConstraintAnalysis>();
+ }
+
+ // Branches outside of the function can be ignored, as we only look at local
+ // state in the function.
+ bool ignoreBranchesOutsideOfFunc = true;
+
+ // Store the actions we care about.
+ void addAction() {
+ if (currBasicBlock) {
+ currBasicBlock->contents.actions.push_back(getCurrentPointer());
+ }
+ }
+
+ void visitLocalSet(LocalSet* curr) { addAction(); }
+ void visitUnary(Unary* curr) { addAction(); }
+ void visitBinary(Binary* curr) { addAction(); }
+ void visitRefEq(RefEq* curr) { addAction(); }
+ void visitRefIsNull(RefIsNull* curr) { addAction(); }
+
+ void visitFunction(Function* curr) {
+ if (!entry) {
+ // Body is unreachable, no entry block.
+ return;
+ }
+ // TODO: optimize for speed, find relevant locals etc.
+ flow();
+ optimize();
+ }
+
+ // Flow infos around until we have inferred all we can about the constraints
+ // in each location.
+ void flow() {
+ // Start from the entry. That block has incoming values - defaults - for
+ // each var.
+ auto& entryConstraints = entry->contents.startConstraints;
+ auto* func = getFunction();
+ auto numLocals = func->getNumLocals();
+ for (Index i = 0; i < numLocals; i++) {
+ auto type = func->getLocalType(i);
+ // TODO: support tuples
+ if (func->isParam(i) || type.size() != 1) {
+ // We can't do anything in this case.
+ entryConstraints[i].setProvesNothing();
+ } else if (LiteralUtils::canMakeZero(type)) {
+ // We have a default value.
+ auto value = Literal::makeZero(type);
+ entryConstraints[i].set(Constraint{Abstract::Eq, {value}});
+ } else {
+ // Otherwise, this is non-nullable, and it is unreachable until set.
+ assert(type.isNonNullable());
+ entryConstraints[i].setProvesEverything();
+ }
+ }
+
+ // Starting from the entry, keep going while we find something new.
+ UniqueDeferredQueue<BasicBlock*> work;
+ work.push(entry);
+ while (!work.empty()) {
+ auto* block = work.pop();
+
+ // Start at the top of the block, then go through, applying things.
+ LocalConstraintMap constraints = block->contents.startConstraints;
+ for (auto** currp : block->contents.actions) {
+ applyToConstraints(*currp, constraints);
+ }
+
+ // 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;
+ auto old = outStartConstraints;
+ outStartConstraints.approximateOr(constraints);
+ if (outStartConstraints != old) {
+ work.push(out);
+ }
+ }
+ }
+ }
+
+ // After inferring all we can, apply it to optimize the code.
+ void optimize() {
+ for (auto& block : basicBlocks) {
+ // Follow the general shape of flow(): we need to see what the state is
+ // at each intermediate point inside the block. (Flowing between blocks is
+ // of course not needed at this stage.)
+ auto& constraints = block->contents.startConstraints;
+ for (auto** currp : block->contents.actions) {
+ applyToConstraints(*currp, constraints);
+ optimizeExpression(currp, constraints);
+ }
+ }
+ }
+
+ // Given an expression and the constraints on it, optimize it.
+ void optimizeExpression(Expression** currp,
+ const LocalConstraintMap& constraints) {
+ auto* curr = *currp;
+ auto parsed = LocalConstraint::parse(curr);
+ if (!parsed) {
+ return;
+ }
+
+ auto iter = constraints.find(parsed->local);
+ if (iter == constraints.end()) {
+ return;
+ }
+ auto& localConstraints = iter->second;
+ Result result = localConstraints.proves(parsed->constraint);
+ if (result == Unknown) {
+ // If we parsed something using two locals, like x != y, we can also look
+ // for the flipped condition among y's constraints TODO
+ return;
+ }
+
+ // We know the result!
+ auto& wasm = *getModule();
+ auto value =
+ LiteralUtils::makeFromInt32(result == True ? 1 : 0, curr->type, wasm);
+ *currp = getDroppedChildrenAndAppend(
+ curr, wasm, getPassOptions(), value, DropMode::IgnoreParentEffects);
+ }
+
+ // Given an expression, apply it to the constraints. For example, a local.set
+ // sets the value for that local.
+ void applyToConstraints(Expression* curr, LocalConstraintMap& constraints) {
+ if (auto* set = curr->dynCast<LocalSet>()) {
+ auto& localConstraints = constraints[set->index];
+ if (Properties::isSingleConstantExpression(set->value)) {
+ // We know this one constraint.
+ auto value = Properties::getLiteral(set->value);
+ localConstraints.set(Constraint{Abstract::Eq, {value}});
+ } else {
+ // We know and can prove nothing.
+ localConstraints.setProvesNothing();
+ }
+ }
+ }
+};
+
+} // anonymous namespace
+
+Pass* createConstraintAnalysisPass() { return new ConstraintAnalysis(); }
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 672936f..06339ca 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -129,6 +129,9 @@
registerPass("cfp-reftest",
"propagate constant struct field values, using ref.test",
createConstantFieldPropagationRefTestPass);
+ registerPass("constraint-analysis",
+ "finds and uses mathematical constraints on locals",
+ createConstraintAnalysisPass);
registerPass(
"dce", "removes unreachable code", createDeadCodeEliminationPass);
registerPass("dealign",
diff --git a/src/passes/passes.h b/src/passes/passes.h
index caeaf20..3e28946 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -33,6 +33,7 @@
Pass* createConstHoistingPass();
Pass* createConstantFieldPropagationPass();
Pass* createConstantFieldPropagationRefTestPass();
+Pass* createConstraintAnalysisPass();
Pass* createDAEPass();
Pass* createDAEOptimizingPass();
Pass* createDAE2Pass();
diff --git a/test/gtest/constraint.cpp b/test/gtest/constraint.cpp
index a8bb66f..1ee15d4 100644
--- a/test/gtest/constraint.cpp
+++ b/test/gtest/constraint.cpp
@@ -7,15 +7,17 @@
using namespace wasm::constraint;
TEST(ConstraintTest, TestEq) {
- // Sets start empty.
- AndedConstraintSet s;
- EXPECT_TRUE(s.empty());
-
// x == 5 (we use "x" for the name of the thing being compared, in these
// comments).
Constraint c{Eq, {Literal(int32_t(5))}};
- // We can't infer anything using an empty set.
+ // Sets start as proving anything, as representing unreachable code.
+ AndedConstraintSet s;
+ EXPECT_TRUE(s.provesEverything());
+ EXPECT_EQ(s.proves(c), True);
+
+ // We can't infer anything if told so.
+ s.setProvesNothing();
EXPECT_EQ(s.proves(c), Unknown);
// If we add it, then things check out: a thing always proves itself true.
@@ -23,6 +25,10 @@
EXPECT_EQ(s.size(), 1);
EXPECT_EQ(s.proves(c), True);
+ // Ditto using set();
+ s.set(c);
+ EXPECT_EQ(s.proves(c), True);
+
// x == 10, a different number: we can infer false.
EXPECT_EQ(s.proves(Constraint{Eq, {Literal(int32_t(10))}}), False);
@@ -37,7 +43,7 @@
AndedConstraintSet s;
// x != 5
Constraint c{Ne, {Literal(int32_t(5))}};
- s.approximateAnd(c);
+ s.set(c);
// Checks out versus itself.
EXPECT_EQ(s.proves(c), True);
@@ -57,7 +63,7 @@
// x != 5 && x != 10
Constraint c{Ne, {Literal(int32_t(5))}};
Constraint d{Ne, {Literal(int32_t(10))}};
- s.approximateAnd(c);
+ s.set(c);
s.approximateAnd(d);
// Each checks out versus itself.
@@ -87,27 +93,22 @@
EXPECT_EQ(s.proves(s), True);
// Ditto after adding something.
- s.approximateAnd(c);
+ s.set(c);
EXPECT_EQ(s.proves(s), True);
// Another set, empty.
AndedConstraintSet t;
- // Any set always proves an empty set to be true.
- EXPECT_EQ(s.proves(t), True);
-
// Make both sets contain the same stuff.
- t.approximateAnd(c);
+ t.set(c);
EXPECT_EQ(s.proves(t), True);
// Now t has *different* stuff, x == 10, which given s is false.
- t.clear();
- t.approximateAnd(Constraint{Eq, {Literal(int32_t(10))}});
+ t.set(Constraint{Eq, {Literal(int32_t(10))}});
EXPECT_EQ(s.proves(t), False);
// Same, with x != 10. Now we know it is true.
- t.clear();
- t.approximateAnd(Constraint{Ne, {Literal(int32_t(10))}});
+ t.set(Constraint{Ne, {Literal(int32_t(10))}});
EXPECT_EQ(s.proves(t), True);
// In reverse, we can infer nothing: knowing x != 10 does not say if x == 5.
@@ -118,36 +119,38 @@
// x != 5
// x != 10
AndedConstraintSet s;
- s.approximateAnd(Constraint{Ne, {Literal(int32_t(5))}});
+ s.set(Constraint{Ne, {Literal(int32_t(5))}});
s.approximateAnd(Constraint{Ne, {Literal(int32_t(10))}});
// x != 20, which is unknown by s.
AndedConstraintSet t;
- t.approximateAnd(Constraint{Ne, {Literal(int32_t(20))}});
+ t.set(Constraint{Ne, {Literal(int32_t(20))}});
EXPECT_EQ(s.proves(t), Unknown);
// Add x == 10, which is false by s, and so the whole thing is false.
- t.approximateAnd(Constraint{Eq, {Literal(int32_t(10))}});
+ t.set(Constraint{Eq, {Literal(int32_t(10))}});
EXPECT_EQ(s.proves(t), False);
}
TEST(ConstraintTest, TestOrTrivial) {
// { x == 5 }
AndedConstraintSet s;
- s.approximateAnd(Constraint{Eq, {Literal(int32_t(5))}});
+ s.set(Constraint{Eq, {Literal(int32_t(5))}});
// { }
AndedConstraintSet empty;
+ empty.setProvesNothing();
- // Anything ORed with the empty set is unchanged.
+ // Anything ORed with the empty set becomes the empty set: if one side can
+ // prove nothing, neither can the result.
auto t = s;
t.approximateOr(empty);
- EXPECT_EQ(t, s);
+ EXPECT_EQ(t, empty);
// Flipped.
t = empty;
t.approximateOr(s);
- EXPECT_EQ(t, s);
+ EXPECT_EQ(t, empty);
// ORing with oneself changes nothing
t = s;
@@ -158,11 +161,11 @@
TEST(ConstraintTest, TestOrImplies) {
// { x == 5 }
AndedConstraintSet s;
- s.approximateAnd(Constraint{Eq, {Literal(int32_t(5))}});
+ s.set(Constraint{Eq, {Literal(int32_t(5))}});
// { x != 10 }
AndedConstraintSet t;
- t.approximateAnd(Constraint{Ne, {Literal(int32_t(10))}});
+ t.set(Constraint{Ne, {Literal(int32_t(10))}});
// ORing these leaves us with x != 10.
auto u = s;
@@ -184,7 +187,7 @@
Constraint not30{Ne, {Literal(int32_t(30))}};
AndedConstraintSet s;
- s.approximateAnd(not10);
+ s.set(not10);
s.approximateAnd(not20);
s.approximateAnd(not30);
diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test
index f106846..f74d904 100644
--- a/test/lit/help/wasm-metadce.test
+++ b/test/lit/help/wasm-metadce.test
@@ -109,6 +109,9 @@
;; CHECK-NEXT: --const-hoisting hoist repeated constants to a
;; CHECK-NEXT: local
;; CHECK-NEXT:
+;; CHECK-NEXT: --constraint-analysis finds and uses mathematical
+;; CHECK-NEXT: constraints on locals
+;; CHECK-NEXT:
;; CHECK-NEXT: --dae removes arguments to calls in an
;; CHECK-NEXT: lto-like manner
;; CHECK-NEXT:
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test
index 903d613..e8c9ff0 100644
--- a/test/lit/help/wasm-opt.test
+++ b/test/lit/help/wasm-opt.test
@@ -145,6 +145,9 @@
;; CHECK-NEXT: --const-hoisting hoist repeated constants to a
;; CHECK-NEXT: local
;; CHECK-NEXT:
+;; CHECK-NEXT: --constraint-analysis finds and uses mathematical
+;; CHECK-NEXT: constraints on locals
+;; CHECK-NEXT:
;; CHECK-NEXT: --dae removes arguments to calls in an
;; CHECK-NEXT: lto-like manner
;; CHECK-NEXT:
diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test
index acfc47b..9192c7e 100644
--- a/test/lit/help/wasm2js.test
+++ b/test/lit/help/wasm2js.test
@@ -73,6 +73,9 @@
;; CHECK-NEXT: --const-hoisting hoist repeated constants to a
;; CHECK-NEXT: local
;; CHECK-NEXT:
+;; CHECK-NEXT: --constraint-analysis finds and uses mathematical
+;; CHECK-NEXT: constraints on locals
+;; CHECK-NEXT:
;; CHECK-NEXT: --dae removes arguments to calls in an
;; CHECK-NEXT: lto-like manner
;; CHECK-NEXT:
diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast
new file mode 100644
index 0000000..cac599c
--- /dev/null
+++ b/test/lit/passes/constraint-analysis.wast
@@ -0,0 +1,798 @@
+;; 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
+
+;; Also run after optimize-instructions, which canonicalizes the order of
+;; things like binary children. We want to see that this pass optimizes the
+;; IR that optimize-instructions emits.
+
+;; RUN: wasm-opt %s --optimize-instructions --constraint-analysis -all -S -o - | filecheck %s --check-prefix=OPTIN
+
+(module
+ ;; CHECK: (func $simple (type $1)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; OPTIN: (func $simple (type $1)
+ ;; OPTIN-NEXT: (local $x i32)
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.const 10)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 0)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 0)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ (func $simple
+ (local $x i32)
+ ;; Set x to 10, and then compare it to 10 and 20 using == and !=, all in a
+ ;; single basic block.
+ (local.set $x
+ (i32.const 10)
+ )
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 10)
+ )
+ )
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 20)
+ )
+ )
+ (drop
+ (i32.ne
+ (local.get $x)
+ (i32.const 10)
+ )
+ )
+ (drop
+ (i32.ne
+ (local.get $x)
+ (i32.const 20)
+ )
+ )
+ )
+
+ ;; CHECK: (func $unknown (type $1)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.eq
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (i32.const 30)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.div_s
+ ;; CHECK-NEXT: (i32.const 1337)
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.eq
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (i32.const 31)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; OPTIN: (func $unknown (type $1)
+ ;; OPTIN-NEXT: (local $x i32)
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.const 30)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.div_u
+ ;; OPTIN-NEXT: (i32.const 1337)
+ ;; OPTIN-NEXT: (i32.const 42)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.eq
+ ;; OPTIN-NEXT: (local.get $x)
+ ;; OPTIN-NEXT: (i32.const 31)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ (func $unknown
+ (local $x i32)
+ ;; Set x to an add. We can only optimize this if optimize-instructions first
+ ;; simplifies it to a constant.
+ (local.set $x
+ (i32.add
+ (i32.const 10)
+ (i32.const 20)
+ )
+ )
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 30)
+ )
+ )
+ ;; When optimize-instructions does not help, we infer nothing.
+ (local.set $x
+ (i32.div_s
+ (i32.const 1337)
+ (i32.const 42)
+ )
+ )
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 31)
+ )
+ )
+ )
+
+ ;; CHECK: (func $multi-local (type $1)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (local $y i32)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $y
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; 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: (i32.const 15)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; OPTIN: (func $multi-local (type $1)
+ ;; OPTIN-NEXT: (local $x i32)
+ ;; OPTIN-NEXT: (local $y i32)
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.const 10)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (local.set $y
+ ;; OPTIN-NEXT: (i32.const 20)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.const 15)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 0)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ (func $multi-local
+ (local $x i32)
+ (local $y i32)
+ ;; x is 10, y is 20
+ (local.set $x
+ (i32.const 10)
+ )
+ (local.set $y
+ (i32.const 20)
+ )
+ ;; Verify those values.
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 10)
+ )
+ )
+ (drop
+ (i32.eq
+ (local.get $y)
+ (i32.const 20)
+ )
+ )
+ ;; Overwrite x to 15.
+ (local.set $x
+ (i32.const 15)
+ )
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 10)
+ )
+ )
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 15)
+ )
+ )
+ ;; y is unchanged.
+ (drop
+ (i32.eq
+ (local.get $y)
+ (i32.const 20)
+ )
+ )
+ )
+
+ ;; CHECK: (func $multi-block (type $0) (param $param i32)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; OPTIN: (func $multi-block (type $0) (param $param i32)
+ ;; OPTIN-NEXT: (local $x i32)
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.const 10)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (if (result i32)
+ ;; OPTIN-NEXT: (local.get $param)
+ ;; OPTIN-NEXT: (then
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (else
+ ;; OPTIN-NEXT: (i32.const 0)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ (func $multi-block (param $param i32)
+ (local $x i32)
+ (local.set $x
+ (i32.const 10)
+ )
+ ;; We can infer into these basic blocks.
+ (if
+ (local.get $param)
+ (then
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 10)
+ )
+ )
+ )
+ (else
+ (drop
+ (i32.ne ;; also test a not-equals here; result is 0
+ (local.get $x)
+ (i32.const 10)
+ )
+ )
+ )
+ )
+ ;; We can infer after the merge.
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 10)
+ )
+ )
+ )
+
+ ;; CHECK: (func $multi-block-split (type $0) (param $param i32)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (else
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.eq
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.eq
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; OPTIN: (func $multi-block-split (type $0) (param $param i32)
+ ;; OPTIN-NEXT: (local $x i32)
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (if (result i32)
+ ;; OPTIN-NEXT: (local.get $param)
+ ;; OPTIN-NEXT: (then
+ ;; OPTIN-NEXT: (i32.const 10)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (else
+ ;; OPTIN-NEXT: (i32.const 20)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.eq
+ ;; OPTIN-NEXT: (local.get $x)
+ ;; OPTIN-NEXT: (i32.const 10)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.eq
+ ;; OPTIN-NEXT: (local.get $x)
+ ;; OPTIN-NEXT: (i32.const 20)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ (func $multi-block-split (param $param i32)
+ (local $x i32)
+ (if
+ (local.get $param)
+ (then
+ (local.set $x
+ (i32.const 10)
+ )
+ )
+ (else
+ (local.set $x
+ (i32.const 20)
+ )
+ )
+ )
+ ;; x is either 10 or 20, but not known to be one or the other.
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 10)
+ )
+ )
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 20)
+ )
+ )
+ ;; TODO: test we can infer x >= 10 etc.
+ )
+
+ ;; CHECK: (func $multi-block-split-2 (type $0) (param $param i32)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.eq
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.eq
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; OPTIN: (func $multi-block-split-2 (type $0) (param $param i32)
+ ;; OPTIN-NEXT: (local $x i32)
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.const 10)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (if
+ ;; OPTIN-NEXT: (local.get $param)
+ ;; OPTIN-NEXT: (then
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.const 20)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.eq
+ ;; OPTIN-NEXT: (local.get $x)
+ ;; OPTIN-NEXT: (i32.const 10)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.eq
+ ;; OPTIN-NEXT: (local.get $x)
+ ;; OPTIN-NEXT: (i32.const 20)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ (func $multi-block-split-2 (param $param i32)
+ (local $x i32)
+ ;; As above, but one if arm, and a set before the if.
+ (local.set $x
+ (i32.const 10)
+ )
+ (if
+ (local.get $param)
+ (then
+ (local.set $x
+ (i32.const 20)
+ )
+ )
+ )
+ ;; Again, we cannot infer.
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 10)
+ )
+ )
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 20)
+ )
+ )
+ )
+
+ ;; CHECK: (func $multi-block-split-yes (type $0) (param $param i32)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; OPTIN: (func $multi-block-split-yes (type $0) (param $param i32)
+ ;; OPTIN-NEXT: (local $x i32)
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.const 10)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (if
+ ;; OPTIN-NEXT: (local.get $param)
+ ;; OPTIN-NEXT: (then
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.const 10)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 0)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ (func $multi-block-split-yes (param $param i32)
+ (local $x i32)
+ ;; As above, but now we set 10 again in the if arm.
+ (local.set $x
+ (i32.const 10)
+ )
+ (if
+ (local.get $param)
+ (then
+ (local.set $x
+ (i32.const 10)
+ )
+ )
+ )
+ ;; Now we can infer 1 and 0 here.
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 10)
+ )
+ )
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 20)
+ )
+ )
+ )
+
+ ;; CHECK: (func $loop (type $0) (param $param i32)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; 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: (br_if $loop
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; OPTIN: (func $loop (type $0) (param $param i32)
+ ;; OPTIN-NEXT: (local $x i32)
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.const 10)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (loop $loop
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (br_if $loop
+ ;; OPTIN-NEXT: (local.get $param)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ (func $loop (param $param i32)
+ (local $x i32)
+ ;; Set $x to 10 before the loop.
+ (local.set $x
+ (i32.const 10)
+ )
+ (loop $loop
+ ;; Despite the backedges, we can infer x is 10 and not 20.
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 10)
+ )
+ )
+ (drop
+ (i32.ne
+ (local.get $x)
+ (i32.const 20)
+ )
+ )
+ (br_if $loop
+ (local.get $param)
+ )
+ )
+ )
+
+ ;; CHECK: (func $loop-no (type $0) (param $param i32)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (loop $loop
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.eq
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.ne
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br_if $loop
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; OPTIN: (func $loop-no (type $0) (param $param i32)
+ ;; OPTIN-NEXT: (local $x i32)
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.const 10)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (loop $loop
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.eq
+ ;; OPTIN-NEXT: (local.get $x)
+ ;; OPTIN-NEXT: (i32.const 10)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.ne
+ ;; OPTIN-NEXT: (local.get $x)
+ ;; OPTIN-NEXT: (i32.const 20)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (local.set $x
+ ;; OPTIN-NEXT: (i32.const 20)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (br_if $loop
+ ;; OPTIN-NEXT: (local.get $param)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ (func $loop-no (param $param i32)
+ (local $x i32)
+ ;; As above, but now with another value set in the loop. We cannot infer.
+ (local.set $x
+ (i32.const 10)
+ )
+ (loop $loop
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 10)
+ )
+ )
+ (drop
+ (i32.ne
+ (local.get $x)
+ (i32.const 20)
+ )
+ )
+ (local.set $x
+ (i32.const 20)
+ )
+ (br_if $loop
+ (local.get $param)
+ )
+ )
+ )
+
+ ;; CHECK: (func $default-var (type $0) (param $param i32)
+ ;; CHECK-NEXT: (local $x i32)
+ ;; CHECK-NEXT: (local $eq eqref)
+ ;; CHECK-NEXT: (local $nn-eq (ref eq))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.eq
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; OPTIN: (func $default-var (type $0) (param $param i32)
+ ;; OPTIN-NEXT: (local $x i32)
+ ;; OPTIN-NEXT: (local $eq eqref)
+ ;; OPTIN-NEXT: (local $nn-eq (ref eq))
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.eqz
+ ;; OPTIN-NEXT: (local.get $param)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: (drop
+ ;; OPTIN-NEXT: (i32.const 1)
+ ;; OPTIN-NEXT: )
+ ;; OPTIN-NEXT: )
+ (func $default-var (param $param i32)
+ (local $x i32)
+ (local $eq eqref)
+ ;; A non-nullable local. We cannot add a get for it (it would not validate),
+ ;; but check we do not error.
+ (local $nn-eq (ref eq))
+ ;; locals begin with default values, so we can infer here.
+ (drop
+ (i32.eq
+ (local.get $x)
+ (i32.const 0)
+ )
+ )
+ (drop
+ (i32.eqz
+ (local.get $x)
+ )
+ )
+ ;; We can infer nothing for a param.
+ (drop
+ (i32.eq
+ (local.get $param)
+ (i32.const 0)
+ )
+ )
+ ;; We can infer a null reference.
+ (drop
+ (ref.eq
+ (local.get $eq)
+ (ref.null eq)
+ )
+ )
+ (drop
+ (ref.is_null
+ (local.get $eq)
+ )
+ )
+ )
+)