diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 57420a1733f..edc9a1b48f6 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -77,6 +77,12 @@ Result provesPair(const Constraint& a, const Constraint& b) { } // 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 Constraint& condition) const { } 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 @@ Result AndedConstraintSet::proves(const AndedConstraintSet& other) const { } 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,19 @@ void AndedConstraintSet::approximateAnd(const Constraint& c) { } 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; + } + + // If one proves nothing, neither does the combination. + if (provesNothing() || other.provesNothing()) { + clear(); + assert(provesNothing()); return; } @@ -153,4 +180,114 @@ void AndedConstraintSet::approximateOr(const AndedConstraintSet& other) { clear(); } +std::optional LocalConstraint::parse(Expression* curr) { + auto parseEqZArgument = + [&](Expression* value) -> std::optional { + if (auto* get = value->dynCast()) { + // 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()) { + if (Abstract::getUnary(unary->type, Abstract::EqZ) == unary->op) { + return parseEqZArgument(unary->value); + } + return {}; + } + + if (auto* refIsNull = curr->dynCast()) { + return parseEqZArgument(refIsNull->value); + } + + // Parse a get or a constant. + auto parseTerm = [&](Expression* expr) -> std::optional { + if (auto* get = expr->dynCast()) { + 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 { + // The left must be a get. + if (auto* get = left->dynCast()) { + // The right can be any term. + if (auto value = parseTerm(right)) { + return LocalConstraint{get->index, Constraint{op, *value}}; + } + } + return {}; + }; + + if (auto* binary = curr->dynCast()) { + // 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()) { + return parseBinaryArguments(Abstract::Eq, refEq->left, refEq->right); + } + + return {}; +} + +void LocalConstraintMap::approximateOr(const LocalConstraintMap& other) { + for (auto& [local, constraints] : other) { + if (auto iter = find(local); iter != end()) { + // This is in both: OR them. + iter->second.approximateOr(constraints); + } else { + // This is only in other, so apply it. + (*this)[local] = constraints; + } + } + + // Remove things only in us. + std::erase_if(*this, [&](const auto& item) { + const auto& [local, constraints] = item; + return !other.contains(local); + }); +} + +std::ostream& operator<<(std::ostream& o, const Constraint& constraint) { + o << "Constraint{" << /*constraint.op <<*/ ", "; + if (auto* c = std::get_if(&constraint.term)) { + o << *c; + } else if (auto* i = std::get_if(&constraint.term)) { + o << "Index(" << *i << ')'; + } + o << '}'; + return o; +} + +std::ostream& operator<<(std::ostream& o, + const AndedConstraintSet& constraints) { + 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 4a689ceec3a..07563a5d0be 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -60,6 +60,30 @@ enum Result { True, False, Unknown }; // 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 { + // 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 + // lattice, if one wants to think of things that way.) + bool provesEverything() const { return isContradiction; } + + // 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 +143,44 @@ struct AndedConstraintSet : inplace_vector { // // 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 parse(Expression* curr); +}; + +// A map of locals and their constraints. +struct LocalConstraintMap + : public std::unordered_map { + // 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 2dfda96d0e5..684077171c8 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -26,6 +26,7 @@ set(passes_SOURCES 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 00000000000..040111b18e8 --- /dev/null +++ b/src/passes/ConstraintAnalysis.cpp @@ -0,0 +1,205 @@ +/* + * 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 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, Info>> { + bool isFunctionParallel() override { return true; } + + // Locals are not modified here. + bool requiresNonNullableLocalFixups() override { return false; } + + std::unique_ptr create() override { + return std::make_unique(); + } + + // 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; + entryConstraints.emplace(); + auto* func = getFunction(); + auto numLocals = func->getNumLocals(); + for (Index i = 0; i < numLocals; i++) { + // We know nothing, by default. + entryConstraints[i].setProvesNothing(); + if (func->isParam(i)) { + continue; + } + + auto type = func->getLocalType(i); + // TODO: support tuples + if (type.size() == 1 && LiteralUtils::canMakeZero(type)) { + auto value = Literal::makeZero(type); + entryConstraints[i].set(Constraint{Abstract::Eq, {value}}); + } + } + + // Starting from the entry, keep going while we find something new. + UniqueDeferredQueue 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()) { + 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 672936f3444..06339ca5a6b 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -129,6 +129,9 @@ void PassRegistry::registerPasses() { 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 caeaf207a42..3e289460eec 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -33,6 +33,7 @@ Pass* createCodePushingPass(); 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 a8bb66f75d6..1ee15d40006 100644 --- a/test/gtest/constraint.cpp +++ b/test/gtest/constraint.cpp @@ -7,15 +7,17 @@ using namespace wasm::Abstract; 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 @@ TEST(ConstraintTest, TestEq) { 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 @@ TEST(ConstraintTest, TestNe) { 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 @@ TEST(ConstraintTest, TestMulti) { // 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 @@ TEST(ConstraintTest, TestSets) { 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 @@ TEST(ConstraintTest, TestSetsUnknown) { // 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, TestOrTrivial) { 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 @@ TEST(ConstraintTest, TestMaxCapacity) { 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 aadf987cf33..c658c715677 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 4dec96a4104..d7000a2cbb1 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 550677a376f..4ef39e73fae 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 00000000000..cac599c30be --- /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) + ) + ) + ) +)