diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 9e275665e9e..81daa1ba494 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -136,31 +136,18 @@ struct ConstraintAnalysis // state in the function. bool ignoreBranchesOutsideOfFunc = true; - // A relevant local is one that is used as part of an expression that we can - // optimize (often, many locals are irrelevant). + // A relevant local is one that we care about optimizing. std::vector relevantLocals; - // Track local copies too, as if one local is relevant, it can make another - // relevant. We store pairs here of key=target, value=sources, which is the - // direction we will flow in the analysis: if we check x == 10, making it - // relevant, and x = y earlier, then we must track that source, y, so that we - // know what it writes to x. - std::unordered_map> localCopySources; - - void maybeMarkRelevant(Expression* curr) { - // If this parses into a constraint on a local, that local is relevant. - for (auto& pair : ParsedAndedConstraints::parseCondition(curr)) { - if (isRelevantType(getFunction()->getLocalType(pair.local))) { - relevantLocals[pair.local] = true; - if (auto* other = std::get_if(&pair.constraint.term)) { - relevantLocals[*other] = true; - } - } - } - } bool fastMath; bool isRelevantType(Type type) { + if (type == Type::v128) { + // TODO optimize SIMD where it makes sense, but for now we don't want to + // do things like propagate v128 constants, which are large. + return false; + } + // Floating-point math does not follow the basic rules of logic (for // example, NaN < NaN and NaN >= NaN are both false, despite the law of the // excluded middle). Constraints follow the rules of logic, so we cannot @@ -174,73 +161,39 @@ struct ConstraintAnalysis void doWalkFunction(Function* func) { fastMath = getPassOptions().fastMath; + // Mark the relevant locals. relevantLocals.assign(func->getNumLocals(), false); + for (Index i = 0; i < func->getNumLocals(); ++i) { + relevantLocals[i] = isRelevantType(func->getLocalType(i)); + } Super::doWalkFunction(func); } -#ifndef NDEBUG - // We use these in asserts, see below. - std::unordered_set originalActions; -#endif - // Store the actions we care about. void addAction() { if (currBasicBlock) { auto* currp = getCurrentPointer(); currBasicBlock->contents.actions.push_back(currp); -#ifndef NDEBUG - originalActions.insert(*currp); -#endif } } - void visitLocalSet(LocalSet* curr) { - addAction(); - - auto* value = curr->value; - while (true) { - if (auto* get = value->dynCast()) { - localCopySources[curr->index].push_back(get->index); - // No children to look into. - break; - } - - if (auto* tee = value->dynCast()) { - localCopySources[curr->index].push_back(tee->index); - value = tee->value; - continue; - } - - // Look for other possible tees and gets that fall through. - auto* next = Properties::getImmediateFallthrough( - value, getPassOptions(), *getModule()); - if (next == value) { - break; - } - value = next; + void visitLocalGet(LocalGet* curr) { + if (isRelevantType(curr->type)) { + addAction(); } } - void visitUnary(Unary* curr) { - addAction(); - maybeMarkRelevant(curr); - } - - void visitBinary(Binary* curr) { - addAction(); - maybeMarkRelevant(curr); - } - - void visitRefEq(RefEq* curr) { - addAction(); - maybeMarkRelevant(curr); + void visitLocalSet(LocalSet* curr) { + if (isRelevantType(getFunction()->getLocalType(curr->index))) { + addAction(); + } } - void visitRefIsNull(RefIsNull* curr) { - addAction(); - maybeMarkRelevant(curr); - } + void visitUnary(Unary* curr) { addAction(); } + void visitBinary(Binary* curr) { addAction(); } + void visitRefEq(RefEq* curr) { addAction(); } + void visitRefIsNull(RefIsNull* curr) { addAction(); } static void doStartIfTrue(ConstraintAnalysis* self, Expression** currp) { // We are right after the condition, so we are in the block before the If's @@ -248,9 +201,6 @@ struct ConstraintAnalysis if (self->currBasicBlock) { self->currBasicBlock->contents.brancher = *currp; } - if (auto* iff = (*currp)->dynCast()) { - self->maybeMarkRelevant(iff->condition); - } Super::doStartIfTrue(self, currp); } @@ -258,13 +208,6 @@ struct ConstraintAnalysis if (self->currBasicBlock) { self->currBasicBlock->contents.brancher = *currp; } - if (auto* br = (*currp)->dynCast()) { - if (br->condition) { - self->maybeMarkRelevant(br->condition); - } - } else if (auto* brOn = (*currp)->dynCast()) { - self->maybeMarkRelevant(brOn->ref); - } Super::doEndBranch(self, currp); } @@ -274,37 +217,10 @@ struct ConstraintAnalysis return; } - computeRelevantLocals(); flow(); optimize(); } - // Every relevant local makes the things it is copied to relevant as well. - void computeRelevantLocals() { - // We'll start from all relevant locals, and flow from there. - UniqueDeferredQueue work; - for (Index i = 0; i < relevantLocals.size(); i++) { - if (relevantLocals[i]) { - work.push(i); - } - } - - // Flow. - while (!work.empty()) { - auto curr = work.pop(); - assert(relevantLocals[curr]); - if (auto iter = localCopySources.find(curr); - iter != localCopySources.end()) { - for (auto source : iter->second) { - if (!relevantLocals[source]) { - relevantLocals[source] = true; - work.push(source); - } - } - } - } - } - // Flow infos around until we have inferred all we can about the constraints // in each location. void flow() { @@ -405,10 +321,36 @@ struct ConstraintAnalysis } } + // If we change types, we must refinalize. + bool refinalize = false; + // After inferring all we can, apply it to optimize the code. void optimize() { - // If we make things unreachable, we must refinalize. - bool refinalize = false; + + // If we find local.gets that we can optimize, we queue those changes here. + // This order is useful for the following reason: + // + // (i32.eqz + // (local.get $x) + // ) + // + // If we can infer that x is 42, and we do that first, then we end up with + // eqz of 42. That is something Precompute can handle, but not us - this + // pass only looks at constraints on locals. We do not lose any optimization + // power by leaving this to Precompute, but it is less efficient and may + // require more cycles; it is also less convenient for testing, as we must + // avoid inferrable local.gets in order to fully test constraint + // optimization. + // + // Instead, we queue local.get changes to happen later, after the eqz in the + // example above. That is, the eqz gets a chance to get optimized, and if it + // does, the queued local.get change ends up unnoticable (it changes a thing + // not in the IR; a slight waste of work, but less wasteful than waiting for + // Precompute). + // + // This queue of changes contains tuples of currp (the pointer to the + // local.get) and the value to replace it with. + std::vector> getOptimizations; for (auto& block : basicBlocks) { // Follow the general shape of flow(): we need to see what the state is @@ -421,7 +363,11 @@ struct ConstraintAnalysis #endif if (!constraints.unreachable) { applyToConstraints(*currp, constraints); - optimizeExpression(currp, constraints); + if (auto* rep = optimizeLocalGet(currp, constraints)) { + getOptimizations.emplace_back(currp, rep); + } else { + optimizeConstraint(currp, constraints); + } } else { // This is unreachable code: just mark it so. *currp = getDroppedChildrenAndAppend( @@ -434,16 +380,59 @@ struct ConstraintAnalysis } } + // Apply local.get optimizations after all that. + for (auto& [currp, rep] : getOptimizations) { + *currp = rep; + } + if (refinalize) { ReFinalize().walkFunctionInModule(getFunction(), getModule()); EHUtils::handleBlockNestedPops(getFunction(), *getModule()); } } - // Given an expression and the constraints on it, optimize it. - void optimizeExpression(Expression** currp, + // Given an expression and the constraints on it, see if it is a local.get + // that we can optimize, and return the value to optimize to, if so. + Expression* optimizeLocalGet(Expression** currp, + const BasicBlockConstraintMap& constraints) { + // A bare local.get can be optimized, if we know that local is a constant. + if (auto* get = (*currp)->dynCast()) { + if (auto lit = constraints.get(get->index).getLiteral()) { + // Among references, only propagate nulls. Other things, like strings, + // may increase size, so we leave them for passes like Precompute and + // GUFA. + if (lit->type.isRef() && !lit->isNull()) { + return nullptr; + } + + Builder builder(*getModule()); + auto* rep = builder.makeConstantExpression(*lit); + + // See if the type changes. + auto oldType = get->type; + if (!Type::isSubType(rep->type, oldType)) { + // The value we know must exist here is impossible, which means it was + // cast in a way that traps at runtime. This code is unreachable. + rep = builder.makeUnreachable(); + refinalize = true; + } else if (rep->type != oldType) { + // We are refining. + refinalize = true; + } + + return rep; + } + } + + return nullptr; + } + + // Given an expression and the constraints on it, parse it into a constraint + // if we can, and optimize it. + void optimizeConstraint(Expression** currp, const BasicBlockConstraintMap& constraints) { auto* curr = *currp; + // Note that we don't need to try to parse a series of constraints with // ParsedAndedConstraints: if there is a tree of ANDed things, we will // simply optimize it as we walk it, each time handling one. @@ -451,13 +440,7 @@ struct ConstraintAnalysis if (!parsed) { return; } - if (!checkRelevancy(*parsed)) { -#ifndef NDEBUG - // If this is not relevant, then it must be one of the original actions we - // care about, i.e., not the result of optimizations. See the comment - // below on checkRelevancy. - assert(originalActions.contains(curr)); -#endif + if (!relevantLocals[parsed->local]) { return; } @@ -642,39 +625,12 @@ struct ConstraintAnalysis } } - // When we are about to use or apply a constraint to a local, it must be on a - // relevant one - otherwise we misidentified which are relevant, which could - // lead to missed opportunities or misoptimizations. This returns true if we - // are operating on proper, relevant data. Normally this is all that can - // happen, but intermediate optimizations can make things become relevant, - // consider this: - // - // x == (y < 10) - // - // The outer == is initially not relevant: we are comparing x to something we - // can't parse into a constraint's term. However, if we get lucky and optimize - // y < 10 into a constant, then it does become parseable, but because we did - // not consider x as relevant (and so we do not have all the relevant - // information about it), we must return false here and not operate on it - // (later optimization cycles can get to it). - bool checkRelevancy(const LocalConstraint& parsed) { - if (!relevantLocals[parsed.local]) { - return false; - } - if (auto* other = std::get_if(&parsed.constraint.term)) { - if (!relevantLocals[*other]) { - return false; - } - } - return true; - } - // Filters out constraints on irrelevant locals. void filterRelevant(ParsedAndedConstraints& parsed) { parsed.erase(std::remove_if(parsed.begin(), parsed.end(), [&](const LocalConstraint& pair) { - return !checkRelevancy(pair); + return !relevantLocals[pair.local]; }), parsed.end()); } diff --git a/test/ctor-eval/partial-locals.wast.out b/test/ctor-eval/partial-locals.wast.out index a51d389b00f..fa7cefc4288 100644 --- a/test/ctor-eval/partial-locals.wast.out +++ b/test/ctor-eval/partial-locals.wast.out @@ -16,7 +16,7 @@ (i32.const 115) ) (global.set $sp - (local.get $0) + (i32.const 100) ) ) ) diff --git a/test/lit/ctor-eval/multivalue-local.wast b/test/lit/ctor-eval/multivalue-local.wast index 5ae46cf2b4c..2fa0554967b 100644 --- a/test/lit/ctor-eval/multivalue-local.wast +++ b/test/lit/ctor-eval/multivalue-local.wast @@ -49,8 +49,5 @@ ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $import) -;; CHECK-NEXT: (i32.add -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.const 84) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/O.wast b/test/lit/passes/O.wast index baba79ee0c5..c8a07779ba8 100644 --- a/test/lit/passes/O.wast +++ b/test/lit/passes/O.wast @@ -137,7 +137,7 @@ ;; CHECK-NEXT: (call $ret) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 7c66ee84429..392652821e3 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -967,7 +967,7 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.lt_s - ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (local.get $len) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (then @@ -1039,7 +1039,7 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.lt_u - ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (local.get $len) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (then diff --git a/test/lit/passes/constraint-analysis-propagation.wast b/test/lit/passes/constraint-analysis-propagation.wast index 4e953c9f8ab..6164a564567 100644 --- a/test/lit/passes/constraint-analysis-propagation.wast +++ b/test/lit/passes/constraint-analysis-propagation.wast @@ -22,7 +22,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -83,12 +83,10 @@ ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br_if $label - ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (local.set $0 - ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -108,7 +106,8 @@ (local.set $2 (local.get $1)) ;; $2 == $1 (br_if 0 (local.get $0)) ;; this once again applies $0 != 0, and - ;; now we notice the contradiction + ;; now we notice the contradiction, and + ;; this turns unreachable (local.set $0 (local.get $2)) ;; we add an unreachable after this ) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 6ffe9276423..6b96ca8443d 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -13,8 +13,8 @@ ;; OPTIN: (type $array (array (mut i32))) (type $array (array (mut i32))) - ;; CHECK: (import "a" "b" (func $import (type $4) (result i32))) - ;; OPTIN: (import "a" "b" (func $import (type $4) (result i32))) + ;; CHECK: (import "a" "b" (func $import (type $7) (result i32))) + ;; OPTIN: (import "a" "b" (func $import (type $7) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (func $simple (type $1) @@ -1131,10 +1131,16 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1158,10 +1164,16 @@ ;; OPTIN-NEXT: (i32.const 0) ;; OPTIN-NEXT: (then ;; OPTIN-NEXT: (drop - ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: (block + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop - ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: (block + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) @@ -1239,7 +1251,10 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1391,7 +1406,7 @@ ;; CHECK: (func $conditional-binary-contradiction-other-default (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) @@ -1402,7 +1417,7 @@ ;; OPTIN: (func $conditional-binary-contradiction-other-default (type $1) ;; OPTIN-NEXT: (local $x i32) ;; OPTIN-NEXT: (if - ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 0) ;; OPTIN-NEXT: (then ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.const 30) @@ -1415,7 +1430,7 @@ ;; As above, but now with a single if. The contradiction tested is ;; between the default value and the if condition. (if - (local.get $x) + (local.get $x) ;; this is 0, hence the if is not taken (then (drop ;; This is unreachable. @@ -1490,7 +1505,11 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1505,7 +1524,11 @@ ;; OPTIN-NEXT: (i32.const 0) ;; OPTIN-NEXT: (then ;; OPTIN-NEXT: (drop - ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: (block + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) @@ -1735,7 +1758,7 @@ ) ) - ;; CHECK: (func $br_on_null (type $5) (param $param anyref) + ;; CHECK: (func $br_on_null (type $4) (param $param anyref) ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null @@ -1756,7 +1779,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $br_on_null (type $5) (param $param anyref) + ;; OPTIN: (func $br_on_null (type $4) (param $param anyref) ;; OPTIN-NEXT: (block $block ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (ref.is_null @@ -1806,7 +1829,7 @@ ) ) - ;; CHECK: (func $br_on_non_null (type $5) (param $param anyref) + ;; CHECK: (func $br_on_non_null (type $4) (param $param anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result (ref any)) ;; CHECK-NEXT: (drop @@ -1827,7 +1850,7 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $br_on_non_null (type $5) (param $param anyref) + ;; OPTIN: (func $br_on_non_null (type $4) (param $param anyref) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (block $block (result (ref any)) ;; OPTIN-NEXT: (drop @@ -2853,7 +2876,7 @@ ) ) - ;; CHECK: (func $local-changes-if (type $7) (param $x i32) (param $y i32) (param $z i32) (param $w i32) + ;; CHECK: (func $local-changes-if (type $8) (param $x i32) (param $y i32) (param $z i32) (param $w i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.eq ;; CHECK-NEXT: (local.get $x) @@ -2910,7 +2933,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $local-changes-if (type $7) (param $x i32) (param $y i32) (param $z i32) (param $w i32) + ;; OPTIN: (func $local-changes-if (type $8) (param $x i32) (param $y i32) (param $z i32) (param $w i32) ;; OPTIN-NEXT: (if ;; OPTIN-NEXT: (i32.eq ;; OPTIN-NEXT: (local.get $x) @@ -3236,7 +3259,7 @@ ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $y - ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) @@ -3252,7 +3275,7 @@ ;; OPTIN-NEXT: (i32.const 10) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (local.set $y - ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 10) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.const 1) @@ -3300,7 +3323,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $y - ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) @@ -3365,7 +3388,7 @@ (i32.const 42) ) ) - ;; Copy x into y, and see that it is now equal to 42. + ;; Copy x (which is 42) into y, and see that it is now equal to 42. (local.set $y (local.get $x) ) @@ -3418,7 +3441,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.eq - ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (then @@ -3437,7 +3460,7 @@ ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (if ;; OPTIN-NEXT: (i32.eq - ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 42) ;; OPTIN-NEXT: (local.get $y) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (then @@ -3458,7 +3481,7 @@ ) (if (i32.eq - (local.get $x) + (local.get $x) ;; this is 42 (local.get $y) ) (then @@ -3487,7 +3510,7 @@ ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.eq ;; CHECK-NEXT: (local.get $y) - ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (drop @@ -3505,7 +3528,7 @@ ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (if ;; OPTIN-NEXT: (i32.eq - ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 42) ;; OPTIN-NEXT: (local.get $y) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (then @@ -3546,7 +3569,7 @@ ) ) - ;; CHECK: (func $simple-array-sum (type $8) (param $param (ref $array)) (result i32) + ;; CHECK: (func $simple-array-sum (type $9) (param $param (ref $array)) (result i32) ;; CHECK-NEXT: (local $index i32) ;; CHECK-NEXT: (local $sum i32) ;; CHECK-NEXT: (local $len i32) @@ -3591,7 +3614,7 @@ ;; CHECK-NEXT: (br $loop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $simple-array-sum (type $8) (param $param (ref $array)) (result i32) + ;; OPTIN: (func $simple-array-sum (type $9) (param $param (ref $array)) (result i32) ;; OPTIN-NEXT: (local $index i32) ;; OPTIN-NEXT: (local $sum i32) ;; OPTIN-NEXT: (local $len i32) @@ -3766,7 +3789,7 @@ ) ) - ;; CHECK: (func $iloop (type $9) (param $0 f32) + ;; CHECK: (func $iloop (type $10) (param $0 f32) ;; CHECK-NEXT: (local $1 f32) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $1) @@ -3791,7 +3814,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $iloop (type $9) (param $0 f32) + ;; OPTIN: (func $iloop (type $10) (param $0 f32) ;; OPTIN-NEXT: (local $1 f32) ;; OPTIN-NEXT: (local.set $0 ;; OPTIN-NEXT: (local.get $1) @@ -3854,10 +3877,7 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $e eqref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.lt_u - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPTIN: (func $nested-binaries (type $1) @@ -3866,17 +3886,16 @@ ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.gt_u ;; OPTIN-NEXT: (i32.const 1) - ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 0) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) (func $nested-binaries (local $x i32) (local $e eqref) - ;; Nested binaries. The outer one is initially not relevant - we cannot - ;; parse the right hand side - but after optimization it simplifies. We - ;; should not assert here, and only optimize the inner one, leaving the - ;; outer for later. + ;; Nested binaries of different types. We can apply the local.gets and then + ;; optimize the ref.eq to 1 and the lt_u to 1 as well. (This does not fully + ;; work out in OPTIN due to reordering, but Precompute would handle it.) (drop (i32.lt_u (local.get $x) @@ -3892,7 +3911,7 @@ ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $y i32) ;; CHECK-NEXT: (local.set $y - ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) @@ -3902,7 +3921,7 @@ ;; OPTIN-NEXT: (local $x i32) ;; OPTIN-NEXT: (local $y i32) ;; OPTIN-NEXT: (local.set $y - ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 0) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (i32.const 1) @@ -3911,8 +3930,7 @@ (func $relevant-copy (local $x i32) (local $y i32) - ;; x is not relevant, but it is copied to y, which is, so we must track x as - ;; relevant too. + ;; x has no sets or uses but for a copy to $y, but we still optimize here. (local.set $y (local.get $x) ) @@ -4461,11 +4479,13 @@ ) ) - ;; CHECK: (func $flipped-contradiction (type $4) (result i32) - ;; CHECK-NEXT: (local $x i32) + ;; CHECK: (func $flipped-contradiction (type $6) (param $x i32) (result i32) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (br_if $loop - ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.lt_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br_if $loop ;; CHECK-NEXT: (local.get $x) @@ -4473,11 +4493,12 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $flipped-contradiction (type $4) (result i32) - ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN: (func $flipped-contradiction (type $6) (param $x i32) (result i32) ;; OPTIN-NEXT: (loop $loop ;; OPTIN-NEXT: (br_if $loop - ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: (i32.eqz + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (br_if $loop ;; OPTIN-NEXT: (local.get $x) @@ -4485,8 +4506,7 @@ ;; OPTIN-NEXT: (unreachable) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) - (func $flipped-contradiction (result i32) - (local $x i32) + (func $flipped-contradiction (param $x i32) (result i32) (loop $loop (result i32) ;; If we do not branch, we add the constraint x >= 1. (br_if $loop @@ -4507,11 +4527,13 @@ ) ) - ;; CHECK: (func $flipped-contradiction-no (type $4) (result i32) - ;; CHECK-NEXT: (local $x i32) + ;; CHECK: (func $flipped-contradiction-no (type $6) (param $x i32) (result i32) ;; CHECK-NEXT: (loop $loop (result i32) ;; CHECK-NEXT: (br_if $loop - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br_if $loop ;; CHECK-NEXT: (local.get $x) @@ -4521,11 +4543,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $flipped-contradiction-no (type $4) (result i32) - ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN: (func $flipped-contradiction-no (type $6) (param $x i32) (result i32) ;; OPTIN-NEXT: (loop $loop (result i32) ;; OPTIN-NEXT: (br_if $loop - ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: (i32.gt_u + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (br_if $loop ;; OPTIN-NEXT: (local.get $x) @@ -4535,10 +4559,9 @@ ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) - (func $flipped-contradiction-no (result i32) + (func $flipped-contradiction-no (param $x i32) (result i32) ;; As above, but with lt replaced by gt. Now the constraints are x <= 1 and ;; x == 0, which do not contradict, and nothing becomes unreachable. - (local $x i32) (loop $loop (result i32) (br_if $loop (i32.gt_u @@ -4655,7 +4678,7 @@ ;; CHECK-NEXT: (local $w i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $z @@ -4663,7 +4686,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $w ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -4676,7 +4699,7 @@ ;; OPTIN-NEXT: (local $w i32) ;; OPTIN-NEXT: (local.set $x ;; OPTIN-NEXT: (block (result i32) - ;; OPTIN-NEXT: (local.get $z) + ;; OPTIN-NEXT: (i32.const 0) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (local.set $z @@ -4684,7 +4707,7 @@ ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (local.set $w ;; OPTIN-NEXT: (block (result i32) - ;; OPTIN-NEXT: (local.get $z) + ;; OPTIN-NEXT: (i32.const 42) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop @@ -4731,7 +4754,7 @@ ;; CHECK-NEXT: (local $w i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (local.tee $param - ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $z @@ -4739,7 +4762,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $w ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -4752,7 +4775,7 @@ ;; OPTIN-NEXT: (local $w i32) ;; OPTIN-NEXT: (local.set $x ;; OPTIN-NEXT: (local.tee $param - ;; OPTIN-NEXT: (local.get $z) + ;; OPTIN-NEXT: (i32.const 0) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (local.set $z @@ -4760,7 +4783,7 @@ ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (local.set $w ;; OPTIN-NEXT: (block (result i32) - ;; OPTIN-NEXT: (local.get $z) + ;; OPTIN-NEXT: (i32.const 42) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (drop @@ -4838,7 +4861,7 @@ ) ) - ;; CHECK: (func $eqz-condition-64 (type $10) (param $x i64) + ;; CHECK: (func $eqz-condition-64 (type $11) (param $x i64) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i64.eqz ;; CHECK-NEXT: (local.get $x) @@ -4850,7 +4873,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $eqz-condition-64 (type $10) (param $x i64) + ;; OPTIN: (func $eqz-condition-64 (type $11) (param $x i64) ;; OPTIN-NEXT: (if ;; OPTIN-NEXT: (i64.eqz ;; OPTIN-NEXT: (local.get $x) @@ -5566,7 +5589,7 @@ ) ) - ;; CHECK: (func $several (type $11) (param $a i32) (param $b i32) (param $c i32) (param $d i32) (param $e i32) + ;; CHECK: (func $several (type $12) (param $a i32) (param $b i32) (param $c i32) (param $d i32) (param $e i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.and ;; CHECK-NEXT: (i32.and @@ -5616,7 +5639,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $several (type $11) (param $a i32) (param $b i32) (param $c i32) (param $d i32) (param $e i32) + ;; OPTIN: (func $several (type $12) (param $a i32) (param $b i32) (param $c i32) (param $d i32) (param $e i32) ;; OPTIN-NEXT: (if ;; OPTIN-NEXT: (i32.and ;; OPTIN-NEXT: (i32.and @@ -5758,7 +5781,10 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -5782,7 +5808,10 @@ ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: (then ;; OPTIN-NEXT: (drop - ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: (block + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) @@ -6876,7 +6905,7 @@ ) ) - ;; CHECK: (func $eqz-ref-is-null (type $5) (param $x anyref) + ;; CHECK: (func $eqz-ref-is-null (type $4) (param $x anyref) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (ref.is_null @@ -6895,7 +6924,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $eqz-ref-is-null (type $5) (param $x anyref) + ;; OPTIN: (func $eqz-ref-is-null (type $4) (param $x anyref) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (ref.is_null ;; OPTIN-NEXT: (local.get $x) @@ -6927,5 +6956,249 @@ ) ) ) -) + ;; CHECK: (func $local.get (type $0) (param $x i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $local.get (type $0) (param $x i32) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $local.get (param $x i32) + (if + (local.get $x) + (then + (drop + ;; This is non-zero, but we can't optimze. + (local.get $x) + ) + ) + (else + (drop + ;; This is zero. + (local.get $x) + ) + ) + ) + ) + + ;; CHECK: (func $local.get.refinalize (type $1) + ;; CHECK-NEXT: (local $x anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $local.get.refinalize (type $1) + ;; OPTIN-NEXT: (local $x anyref) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (block (result nullref) + ;; OPTIN-NEXT: (ref.null none) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $local.get.refinalize + (local $x anyref) + ;; The local.get below is null. After we apply a null there, we refinalize + ;; the block's type to nullref. + (drop + (block (result anyref) + (local.get $x) + ) + ) + ) + + ;; CHECK: (func $local.get.impossible.cast (type $13) (result (ref func)) + ;; CHECK-NEXT: (local $x (ref func)) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (ref.cast (ref nofunc) + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $local.get.impossible.cast (type $13) (result (ref func)) + ;; OPTIN-NEXT: (local $x (ref func)) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + (func $local.get.impossible.cast (result (ref func)) + (local $x (ref func)) + ;; The cast here traps at runtime. We do not have a valid value to put in + ;; place of the local.get (it is not refined enough), but we know it is + ;; unreachable. (In OPTIN, we figure out the set's value is unreachable even + ;; earlier.) + (local.set $x + (ref.cast (ref func) + (ref.null func) + ) + ) + (local.get $x) + ) + + ;; CHECK: (func $local.get.no.v128 (type $1) + ;; CHECK-NEXT: (local $x v128) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $local.get.no.v128 (type $1) + ;; OPTIN-NEXT: (local $x v128) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $local.get.no.v128 + (local $x v128) + ;; We know the value here, but do not copy v128 constants, which are large. + ;; TODO: should we optimize this? + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $local.get.unreachable (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $local.get.unreachable (type $1) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $local.get.unreachable + (local $x i32) + (if + (local.get $x) + (then + ;; $x is 0, so we never get here, and this is unreachable. + (drop + (local.get $x) + ) + ) + ) + ) + + ;; CHECK: (func $local.get.float (type $1) + ;; CHECK-NEXT: (local $x f64) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (f64.eq + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (f64.const nan:0x8000000000000) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $local.get.float (type $1) + ;; OPTIN-NEXT: (local $x f64) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (f64.eq + ;; OPTIN-NEXT: (local.tee $x + ;; OPTIN-NEXT: (f64.const nan:0x8000000000000) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (nop) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $local.get.float + (local $x f64) + ;; The condition here ends up comparing $x to itself. That is normally 1, + ;; but not with a nan. We do not optimize floats for this reason (without + ;; --fast-math, see constraint-analysis-float.wast). + (if + (f64.eq + (local.tee $x + (f64.const nan) + ) + (local.get $x) + ) + (then + (nop) + ) + ) + ) + + ;; CHECK: (func $local.get.internalized-string (type $1) + ;; CHECK-NEXT: (local $x anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (any.convert_extern + ;; CHECK-NEXT: (string.const "foo") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $local.get.internalized-string (type $1) + ;; OPTIN-NEXT: (local $x anyref) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (ref.null none) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (any.convert_extern + ;; OPTIN-NEXT: (string.const "foo") + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $local.get.internalized-string + (local $x anyref) + ;; A null can be propagated. + (drop + (local.get $x) + ) + ;; A non-null value, like an internalized string, is not optimized (we could + ;; emit an any.convert_extern of a strong.const, but it increases size, so + ;; we leave this for passes like Precompute and GUFA). + ;; TODO: should we optimize this? + (local.set $x + (any.convert_extern + (string.const "foo") + ) + ) + (drop + (local.get $x) + ) + ) +) diff --git a/test/lit/passes/global-effects-O.wast b/test/lit/passes/global-effects-O.wast index ef2cf62e06c..ce0507d1509 100644 --- a/test/lit/passes/global-effects-O.wast +++ b/test/lit/passes/global-effects-O.wast @@ -362,7 +362,7 @@ ;; CHECK_3-NEXT: ) ;; CHECK_3-NEXT: ) ;; CHECK_3-NEXT: ) - ;; CHECK_3-NEXT: (local.get $0) + ;; CHECK_3-NEXT: (i32.const 0) ;; CHECK_3-NEXT: ) ;; CHECK_s: (func $infinite-work (type $1) (param $0 i32) (result i32) ;; CHECK_s-NEXT: (loop $loop @@ -375,7 +375,7 @@ ;; CHECK_s-NEXT: ) ;; CHECK_s-NEXT: ) ;; CHECK_s-NEXT: ) - ;; CHECK_s-NEXT: (local.get $0) + ;; CHECK_s-NEXT: (i32.const 0) ;; CHECK_s-NEXT: ) ;; CHECK_O: (func $infinite-work (type $1) (param $0 i32) (result i32) ;; CHECK_O-NEXT: (loop $loop @@ -388,7 +388,7 @@ ;; CHECK_O-NEXT: ) ;; CHECK_O-NEXT: ) ;; CHECK_O-NEXT: ) - ;; CHECK_O-NEXT: (local.get $0) + ;; CHECK_O-NEXT: (i32.const 0) ;; CHECK_O-NEXT: ) (func $infinite-work (param $x i32) (result i32) ;; Some work with no side effects aside from that it appears to potentially diff --git a/test/wasm2js/br_table_hoisting.2asm.js.opt b/test/wasm2js/br_table_hoisting.2asm.js.opt index c4d825421f0..192a6801b2e 100644 --- a/test/wasm2js/br_table_hoisting.2asm.js.opt +++ b/test/wasm2js/br_table_hoisting.2asm.js.opt @@ -117,40 +117,9 @@ function asmFunc(imports) { function foo4($0) { $0 = $0 | 0; - a : { - b : { - c : { - if ($0) { - break c - } - d : { - switch ($0 | 0) { - default: - if ($0) { - break c - } - zed(-1); - zed(-2); - break; - case 0: - break a; - case 1: - break b; - case 2: - break c; - case 3: - break d; - }; - } - zed(-3); - zed(-4); - } - zed(-5); - zed(-6); - break a; - } - zed(-7); - zed(-8); + if ($0) { + zed(-5); + zed(-6); } zed(-9); zed(-10);