diff options
-rwxr-xr-x | scripts/fuzz_opt.py | 1 | ||||
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/TupleOptimization.cpp | 359 | ||||
-rw-r--r-- | src/passes/pass.cpp | 9 | ||||
-rw-r--r-- | src/passes/passes.h | 1 | ||||
-rw-r--r-- | test/lit/ctor-eval/multivalue-local.wast | 11 | ||||
-rw-r--r-- | test/lit/help/wasm-opt.test | 2 | ||||
-rw-r--r-- | test/lit/help/wasm2js.test | 2 | ||||
-rw-r--r-- | test/lit/passes/tuple-optimization.wast | 1009 |
9 files changed, 1385 insertions, 10 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index c50411831..7b8688c5e 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1544,6 +1544,7 @@ opt_choices = [ ("--simplify-locals-notee",), ("--simplify-locals-notee-nostructure",), ("--ssa",), + ("--tuple-optimization",), ("--type-refining",), ("--type-merging",), ("--type-ssa",), diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 302cd7c7a..a47333cf6 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -113,6 +113,7 @@ set(passes_SOURCES StackCheck.cpp StripEH.cpp SSAify.cpp + TupleOptimization.cpp Untee.cpp Vacuum.cpp ${CMAKE_CURRENT_BINARY_DIR}/WasmIntrinsics.cpp diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp new file mode 100644 index 000000000..f4fd0910b --- /dev/null +++ b/src/passes/TupleOptimization.cpp @@ -0,0 +1,359 @@ +/* + * Copyright 2023 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. + */ + +// +// Optimize away trivial tuples. When values are bundled together in a tuple, we +// are limited in how we can optimize then in the various local-related passes, +// like this: +// +// (local.set $tuple +// (tuple.make (A) (B) (C))) +// (use +// (tuple.extract 0 +// (local.get $tuple))) +// +// If there are no other uses, then we just need one of the three lanes. By +// lowing them to three separate locals, other passes can remove the other two. +// +// Specifically, this pass seeks out tuple locals that have these properties: +// +// * They are always written either a tuple.make or another tuple local with +// these properties. +// * They are always used either in tuple.extract or they are copied to another +// tuple local with these properties. +// +// The set of those tuple locals can be easily optimized into individual locals, +// as the tuple does not "escape" into, say, a return value. +// +// TODO: Blocks etc. might be handled here, but it's not clear if we want to: +// there are situations where multivalue leads to smaller code using +// those constructs. Atm this pass should only remove things that are +// definitely worth lowering. +// + +#include <pass.h> +#include <support/unique_deferring_queue.h> +#include <wasm-builder.h> +#include <wasm.h> + +namespace wasm { + +struct TupleOptimization : public WalkerPass<PostWalker<TupleOptimization>> { + bool isFunctionParallel() override { return true; } + + std::unique_ptr<Pass> create() override { + return std::make_unique<TupleOptimization>(); + } + + // Track the number of uses for each tuple local. We consider a use as a + // local.get, a set, or a tee. A tee counts as two uses (since it both sets + // and gets, and so we must see that it is both used and uses properly). + std::vector<Index> uses; + + // Tracks which tuple local uses are valid, that is, follow the properties + // above. If we have more uses than valid uses then we must have an invalid + // one, and the local cannot be optimized. + std::vector<Index> validUses; + + // When one tuple local copies the value of another, we need to track the + // index that was copied, as if the source ends up bad then the target is bad + // as well. + // + // This is a symmetrical map, that is, we consider copies to work both ways: + // + // x \in copiedIndexed[y] <==> y \in copiedIndexed[x] + // + std::vector<std::unordered_set<Index>> copiedIndexes; + + void doWalkFunction(Function* func) { + // If tuples are not enabled, or there are no tuple locals, then there is no + // work to do. + if (!getModule()->features.hasMultivalue()) { + return; + } + bool hasTuple = false; + for (auto var : func->vars) { + if (var.isTuple()) { + hasTuple = true; + break; + } + } + if (!hasTuple) { + return; + } + + // Prepare global data structures before we collect info. + auto numLocals = func->getNumLocals(); + uses.resize(numLocals); + validUses.resize(numLocals); + copiedIndexes.resize(numLocals); + + // Walk the code to collect info. + super::doWalkFunction(func); + + // Analyze and optimize. + optimize(func); + } + + void visitLocalGet(LocalGet* curr) { + if (curr->type.isTuple()) { + uses[curr->index]++; + } + } + + void visitLocalSet(LocalSet* curr) { + if (getFunction()->getLocalType(curr->index).isTuple()) { + // See comment above about tees (we consider their set and get each a + // separate use). + uses[curr->index] += curr->isTee() ? 2 : 1; + auto* value = curr->value; + + // We need the input to the local to be another such local (from a tee, or + // a get), or a tuple.make. + if (auto* tee = value->dynCast<LocalSet>()) { + assert(tee->isTee()); + validUses[tee->index]++; + validUses[curr->index]++; + copiedIndexes[tee->index].insert(curr->index); + copiedIndexes[curr->index].insert(tee->index); + } else if (auto* get = value->dynCast<LocalGet>()) { + validUses[get->index]++; + validUses[curr->index]++; + copiedIndexes[get->index].insert(curr->index); + copiedIndexes[curr->index].insert(get->index); + } else if (value->is<TupleMake>()) { + validUses[curr->index]++; + } + } + } + + void visitTupleExtract(TupleExtract* curr) { + // We need the input to be a local, either from a tee or a get. + if (auto* set = curr->tuple->dynCast<LocalSet>()) { + validUses[set->index]++; + } else if (auto* get = curr->tuple->dynCast<LocalGet>()) { + validUses[get->index]++; + } + } + + void optimize(Function* func) { + auto numLocals = func->getNumLocals(); + + // Find the set of bad indexes. We add each such candidate to a worklist + // that we will then flow to find all those corrupted. + std::vector<bool> bad(numLocals); + UniqueDeferredQueue<Index> work; + + for (Index i = 0; i < uses.size(); i++) { + assert(validUses[i] <= uses[i]); + if (uses[i] > 0 && validUses[i] < uses[i]) { + // This is a bad tuple. + work.push(i); + } + } + + // Flow badness forward. + while (!work.empty()) { + auto i = work.pop(); + if (bad[i]) { + continue; + } + bad[i] = true; + for (auto target : copiedIndexes[i]) { + work.push(target); + } + } + + // Good indexes we can optimize are tuple locals with uses that are not bad. + std::vector<bool> good(numLocals); + bool hasGood = false; + for (Index i = 0; i < uses.size(); i++) { + if (uses[i] > 0 && !bad[i]) { + good[i] = true; + hasGood = true; + } + } + + if (!hasGood) { + return; + } + + // We found things to optimize! Create new non-tuple locals for their + // contents, and then rewrite the code to use those according to the + // mapping from tuple locals to normal ones. The mapping maps a tuple local + // to the base index used for its contents: an index and several others + // right after it, depending on the tuple size. + std::unordered_map<Index, Index> tupleToNewBaseMap; + for (Index i = 0; i < good.size(); i++) { + if (!good[i]) { + continue; + } + + auto newBase = func->getNumLocals(); + tupleToNewBaseMap[i] = newBase; + Index lastNewIndex = 0; + for (auto t : func->getLocalType(i)) { + Index newIndex = Builder::addVar(func, t); + if (lastNewIndex == 0) { + // This is the first new local we added (0 is an impossible value, + // since tuple locals exist, hence index 0 was already taken), so it + // must be equal to the base. + assert(newIndex == newBase); + } else { + // This must be right after the former. + assert(newIndex == lastNewIndex + 1); + } + lastNewIndex = newIndex; + } + } + + MapApplier mapApplier(tupleToNewBaseMap); + mapApplier.walkFunctionInModule(func, getModule()); + } + + struct MapApplier : public PostWalker<MapApplier> { + std::unordered_map<Index, Index>& tupleToNewBaseMap; + + MapApplier(std::unordered_map<Index, Index>& tupleToNewBaseMap) + : tupleToNewBaseMap(tupleToNewBaseMap) {} + + // Gets the new base index if there is one, or 0 if not (0 is an impossible + // value for a new index, as local index 0 was taken before, as tuple + // locals existed). + Index getNewBaseIndex(Index i) { + auto iter = tupleToNewBaseMap.find(i); + if (iter == tupleToNewBaseMap.end()) { + return 0; + } + return iter->second; + } + + // Given a local.get or local.set, return the new base index for the local + // index used there. Returns 0 (an impossible value, see above) otherwise. + Index getSetOrGetBaseIndex(Expression* setOrGet) { + Index index; + if (auto* set = setOrGet->dynCast<LocalSet>()) { + index = set->index; + } else if (auto* get = setOrGet->dynCast<LocalGet>()) { + index = get->index; + } else { + return 0; + } + + return getNewBaseIndex(index); + } + + // Replacing a local.tee requires some care, since we might have + // + // (local.set + // (local.tee + // .. + // + // We replace the local.tee with a block of sets of the new non-tuple + // locals, and the outer set must then (1) keep those around and also (2) + // identify the local that was tee'd, so we know what to get (which has been + // replaced by the block). To make that simple keep a map of the things that + // replaced tees. + std::unordered_map<Expression*, LocalSet*> replacedTees; + + void visitLocalSet(LocalSet* curr) { + auto replace = [&](Expression* replacement) { + if (curr->isTee()) { + replacedTees[replacement] = curr; + } + replaceCurrent(replacement); + }; + + if (auto targetBase = getNewBaseIndex(curr->index)) { + Builder builder(*getModule()); + auto type = getFunction()->getLocalType(curr->index); + + auto* value = curr->value; + if (auto* make = value->dynCast<TupleMake>()) { + // Write each of the tuple.make fields into the proper local. + std::vector<Expression*> sets; + for (Index i = 0; i < type.size(); i++) { + auto* value = make->operands[i]; + sets.push_back(builder.makeLocalSet(targetBase + i, value)); + } + replace(builder.makeBlock(sets)); + return; + } + + std::vector<Expression*> contents; + + auto iter = replacedTees.find(value); + if (iter != replacedTees.end()) { + // The input to us was a tee that has been replaced. The actual value + // we read from (the tee) can be found in replacedTees. Also, we + // need to keep around the replacement of the tee. + contents.push_back(value); + value = iter->second; + } + + // This is a copy of a tuple local into another. Copy all the fields + // between them. + Index sourceBase = getSetOrGetBaseIndex(value); + + // The target is being optimized, so the source must be as well, or else + // we were confused earlier and the target should not be. + assert(sourceBase); + + for (Index i = 0; i < type.size(); i++) { + auto* get = builder.makeLocalGet(sourceBase + i, type[i]); + contents.push_back(builder.makeLocalSet(targetBase + i, get)); + } + replace(builder.makeBlock(contents)); + } + } + + void visitTupleExtract(TupleExtract* curr) { + auto* value = curr->tuple; + Expression* extraContents = nullptr; + + auto iter = replacedTees.find(value); + if (iter != replacedTees.end()) { + // The input to us was a tee that has been replaced. Handle it as in + // visitLocalSet. + extraContents = value; + value = iter->second; + } + + auto type = value->type; + if (type == Type::unreachable) { + return; + } + + Index sourceBase = getSetOrGetBaseIndex(value); + if (!sourceBase) { + return; + } + + Builder builder(*getModule()); + auto i = curr->index; + auto* get = builder.makeLocalGet(sourceBase + i, type[i]); + if (extraContents) { + replaceCurrent(builder.makeSequence(extraContents, get)); + } else { + replaceCurrent(get); + } + } + }; +}; + +Pass* createTupleOptimizationPass() { return new TupleOptimization(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 0d47cd78e..15e8c1f26 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -480,6 +480,9 @@ void PassRegistry::registerPasses() { registerPass("trap-mode-js", "replace trapping operations with js semantics", createTrapModeJS); + registerPass("tuple-optimization", + "optimize trivial tuples away", + createTupleOptimizationPass); registerPass("type-merging", "merge types to their supertypes where possible", createTypeMergingPass); @@ -558,6 +561,12 @@ void PassRunner::addDefaultFunctionOptimizationPasses() { if (options.optimizeLevel >= 2 || options.shrinkLevel >= 2) { addIfNoDWARFIssues("code-pushing"); } + if (wasm->features.hasMultivalue()) { + // Optimize tuples before local opts (as splitting tuples can help local + // opts), but also not too early, as we want to be after + // optimize-instructions at least (which can remove tuple-related things). + addIfNoDWARFIssues("tuple-optimization"); + } // don't create if/block return values yet, as coalesce can remove copies that // that could inhibit addIfNoDWARFIssues("simplify-locals-nostructure"); diff --git a/src/passes/passes.h b/src/passes/passes.h index fa6d98111..1cd2a5672 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -153,6 +153,7 @@ Pass* createSSAifyPass(); Pass* createSSAifyNoMergePass(); Pass* createTrapModeClamp(); Pass* createTrapModeJS(); +Pass* createTupleOptimizationPass(); Pass* createTypeRefiningPass(); Pass* createTypeMergingPass(); Pass* createTypeSSAPass(); diff --git a/test/lit/ctor-eval/multivalue-local.wast b/test/lit/ctor-eval/multivalue-local.wast index f83d3ea86..0f35dbd61 100644 --- a/test/lit/ctor-eval/multivalue-local.wast +++ b/test/lit/ctor-eval/multivalue-local.wast @@ -45,21 +45,12 @@ ;; CHECK: (func $multivalue-local_2 (type $1) (result i32) ;; CHECK-NEXT: (local $0 i32) -;; CHECK-NEXT: (local $1 (i32 i32)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.set $1 -;; CHECK-NEXT: (tuple.make -;; CHECK-NEXT: (i32.const 42) -;; CHECK-NEXT: (i32.const 1000) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: (tuple.extract 0 -;; CHECK-NEXT: (local.get $1) -;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 681128b03..563a885f0 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -476,6 +476,8 @@ ;; CHECK-NEXT: --trap-mode-js replace trapping operations with ;; CHECK-NEXT: js semantics ;; CHECK-NEXT: +;; CHECK-NEXT: --tuple-optimization optimize trivial tuples away +;; CHECK-NEXT: ;; CHECK-NEXT: --type-merging merge types to their supertypes ;; CHECK-NEXT: where possible ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 1e34b3617..54a593b75 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -435,6 +435,8 @@ ;; CHECK-NEXT: --trap-mode-js replace trapping operations with ;; CHECK-NEXT: js semantics ;; CHECK-NEXT: +;; CHECK-NEXT: --tuple-optimization optimize trivial tuples away +;; CHECK-NEXT: ;; CHECK-NEXT: --type-merging merge types to their supertypes ;; CHECK-NEXT: where possible ;; CHECK-NEXT: diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast new file mode 100644 index 000000000..1788b91e9 --- /dev/null +++ b/test/lit/passes/tuple-optimization.wast @@ -0,0 +1,1009 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --tuple-optimization -all -S -o - | filecheck %s + +(module + ;; CHECK: (func $just-set (type $0) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $just-set + (local $tuple (i32 i32)) + ;; This tuple local can be optimized into separate locals per lane. The + ;; tuple local itself then has no uses and other passes will remove it. + (local.set $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + + ;; CHECK: (func $just-get (type $0) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $just-get + (local $tuple (i32 i32)) + ;; The default value of the tuple lanes is used here in the new locals we + ;; add. + (drop + (tuple.extract 0 + (local.get $tuple) + ) + ) + (drop + (tuple.extract 1 + (local.get $tuple) + ) + ) + ) + + ;; CHECK: (func $just-get-bad (type $1) (result i32 i32) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 0 + ;; CHECK-NEXT: (local.get $tuple) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 1 + ;; CHECK-NEXT: (local.get $tuple) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $tuple) + ;; CHECK-NEXT: ) + (func $just-get-bad (result i32 i32) + (local $tuple (i32 i32)) + (drop + (tuple.extract 0 + (local.get $tuple) + ) + ) + (drop + (tuple.extract 1 + (local.get $tuple) + ) + ) + ;; This get is not used by something we can handle (it escapes from the + ;; function), so we should not try to optimize this tuple. + (local.get $tuple) + ) + + ;; CHECK: (func $set-and-gets (type $0) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-and-gets + (local $tuple (i32 i32)) + (local.set $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + (drop + (tuple.extract 0 + (local.get $tuple) + ) + ) + (drop + (tuple.extract 1 + (local.get $tuple) + ) + ) + ;; Add another get for more coverage + (drop + (tuple.extract 0 + (local.get $tuple) + ) + ) + ) + + ;; CHECK: (func $tee (type $0) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $tuple2 (i32 i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $tee + (local $tuple (i32 i32)) + (local $tuple2 (i32 i32)) + (local.set $tuple + (local.tee $tuple2 + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + ;; Read the first tuple. + (drop + (tuple.extract 0 + (local.get $tuple) + ) + ) + (drop + (tuple.extract 1 + (local.get $tuple) + ) + ) + ;; Read the second tuple. + (drop + (tuple.extract 0 + (local.get $tuple2) + ) + ) + (drop + (tuple.extract 1 + (local.get $tuple2) + ) + ) + ) + + ;; CHECK: (func $just-tee (type $0) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $just-tee + (local $tuple (i32 i32)) + (drop + (tuple.extract 0 + (local.tee $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + ) + ) + + ;; CHECK: (func $just-tee-bad (type $1) (result i32 i32) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local.tee $tuple + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $just-tee-bad (result i32 i32) + (local $tuple (i32 i32)) + ;; This tee goes somewhere we cannot handle, so we do not optimize here. + (local.tee $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + + ;; CHECK: (func $no-uses (type $0) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $tuple2 (i32 i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $no-uses + (local $tuple (i32 i32)) + (local $tuple2 (i32 i32)) + ;; The set has no uses, and the tee only has an immediate use. We can + ;; still optimize both. + (local.set $tuple + (local.tee $tuple2 + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + ) + + ;; CHECK: (func $corruption-tee (type $1) (result i32 i32) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $tuple2 (i32 i32)) + ;; CHECK-NEXT: (local.set $tuple + ;; CHECK-NEXT: (local.tee $tuple2 + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $tuple2) + ;; CHECK-NEXT: ) + (func $corruption-tee (result i32 i32) + (local $tuple (i32 i32)) + (local $tuple2 (i32 i32)) + ;; As above, but the tee's tuple is bad and it prevents the other from + ;; being optimized too, due to the copy between them. + (local.set $tuple + (local.tee $tuple2 + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + (local.get $tuple2) + ) + + ;; CHECK: (func $corruption-set (type $1) (result i32 i32) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $tuple2 (i32 i32)) + ;; CHECK-NEXT: (local.set $tuple + ;; CHECK-NEXT: (local.tee $tuple2 + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $tuple) + ;; CHECK-NEXT: ) + (func $corruption-set (result i32 i32) + (local $tuple (i32 i32)) + (local $tuple2 (i32 i32)) + ;; As above, but now the set is bad. + (local.set $tuple + (local.tee $tuple2 + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + (local.get $tuple) ;; this changed from $tuple2; same outcome. + ) + + ;; CHECK: (func $set-after-set (type $0) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $tuple2 (i32 i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-after-set + (local $tuple (i32 i32)) + (local $tuple2 (i32 i32)) + ;; We can optimize both these tuples. + (local.set $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + (local.set $tuple2 + (local.get $tuple) + ) + ;; Add a copy of a tuple to itself for extra coverage. + (local.set $tuple + (local.get $tuple) + ) + ) + + ;; CHECK: (func $corruption-first-set (type $1) (result i32 i32) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $tuple2 (i32 i32)) + ;; CHECK-NEXT: (local.set $tuple + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $tuple2 + ;; CHECK-NEXT: (local.get $tuple) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $tuple) + ;; CHECK-NEXT: ) + (func $corruption-first-set (result i32 i32) + (local $tuple (i32 i32)) + (local $tuple2 (i32 i32)) + (local.set $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + (local.set $tuple2 + (local.get $tuple) + ) + ;; This local.get prevents both locals from being optimized. + (local.get $tuple) + ) + + ;; CHECK: (func $corruption-second-set (type $1) (result i32 i32) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $tuple2 (i32 i32)) + ;; CHECK-NEXT: (local.set $tuple + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $tuple2 + ;; CHECK-NEXT: (local.get $tuple) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $tuple2) + ;; CHECK-NEXT: ) + (func $corruption-second-set (result i32 i32) + (local $tuple (i32 i32)) + (local $tuple2 (i32 i32)) + (local.set $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + (local.set $tuple2 + (local.get $tuple) + ) + (local.get $tuple2) ;; this changed from $tuple; same outcome. + ) + + ;; CHECK: (func $other (type $0) + ;; CHECK-NEXT: (local $other f64) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local.set $other + ;; CHECK-NEXT: (local.tee $other + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other + ;; A non-tuple local and all operations on it should be ignored. + (local $other f64) + ;; A tuple local with no uses at all should be ignored. + (local $tuple (i32 i32)) + (local.set $other + (local.tee $other + (local.get $other) + ) + ) + ) + + ;; CHECK: (func $make-extract-no-local (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 0 + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $make-extract-no-local + ;; Tuple operations without locals. We do nothing here; other passes can + ;; help on this kind of thing. + (drop + (tuple.extract 0 + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + ) + + ;; CHECK: (func $make-extract-no-local-but-other (type $0) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 0 + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $make-extract-no-local-but-other + (local $tuple (i32 i32)) + (local.set $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ;; The code below is as in the previous testcase, but now before us there + ;; is an unrelated local that can be optimized. We should remain as before. + (drop + (tuple.extract 0 + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + ) + + ;; CHECK: (func $set-of-block (type $0) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local.set $tuple + ;; CHECK-NEXT: (block (result i32 i32) + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-of-block + (local $tuple (i32 i32)) + ;; We do not handle blocks yet, so this is not optimized. + (local.set $tuple + (block (result i32 i32) + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + ) + + ;; CHECK: (func $unreachability (type $0) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local.set $tuple + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $tuple + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 0 + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 1 + ;; CHECK-NEXT: (local.tee $tuple + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachability + (local $tuple (i32 i32)) + (local.set $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ;; We should not error here, and do nothing. + (local.set $tuple + (unreachable) + ) + (drop + (tuple.extract 0 + (unreachable) + ) + ) + (drop + (tuple.extract 1 + (local.tee $tuple + (unreachable) + ) + ) + ) + ) + + ;; CHECK: (func $tee-chain (type $0) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local $tuple2 (i32 i32)) + ;; CHECK-NEXT: (local $tuple3 (i32 i32)) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (local $8 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $tee-chain + (local $tuple (i32 i32)) + (local $tuple2 (i32 i32)) + (local $tuple3 (i32 i32)) + (drop + (tuple.extract 0 + (local.tee $tuple + (local.tee $tuple2 + (local.tee $tuple3 + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + ) + ) + ) + ) + + ;; CHECK: (func $chain-3 (type $0) + ;; CHECK-NEXT: (local $tuple (i32 i32 i32)) + ;; CHECK-NEXT: (local $tuple2 (i32 i32 i32)) + ;; CHECK-NEXT: (local $tuple3 (i32 i32 i32)) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (local $8 i32) + ;; CHECK-NEXT: (local $9 i32) + ;; CHECK-NEXT: (local $10 i32) + ;; CHECK-NEXT: (local $11 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $10 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $11 + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $chain-3 + (local $tuple (i32 i32 i32)) + (local $tuple2 (i32 i32 i32)) + (local $tuple3 (i32 i32 i32)) + ;; A chain of 3 copied tuples. + (local.set $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + (i32.const 3) + ) + ) + (local.set $tuple2 + (local.get $tuple) + ) + (local.set $tuple3 + (local.get $tuple2) + ) + ;; Read from each. + (drop + (tuple.extract 0 + (local.get $tuple) + ) + ) + (drop + (tuple.extract 1 + (local.get $tuple2) + ) + ) + (drop + (tuple.extract 2 + (local.get $tuple3) + ) + ) + ) + + ;; CHECK: (func $chain-3-corruption (type $2) (result i32 i32 i32) + ;; CHECK-NEXT: (local $tuple (i32 i32 i32)) + ;; CHECK-NEXT: (local $tuple2 (i32 i32 i32)) + ;; CHECK-NEXT: (local $tuple3 (i32 i32 i32)) + ;; CHECK-NEXT: (local.set $tuple + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $tuple2 + ;; CHECK-NEXT: (local.get $tuple) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $tuple3 + ;; CHECK-NEXT: (local.get $tuple2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 0 + ;; CHECK-NEXT: (local.get $tuple) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 1 + ;; CHECK-NEXT: (local.get $tuple2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 + ;; CHECK-NEXT: (local.get $tuple3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $tuple) + ;; CHECK-NEXT: ) + (func $chain-3-corruption (result i32 i32 i32) + (local $tuple (i32 i32 i32)) + (local $tuple2 (i32 i32 i32)) + (local $tuple3 (i32 i32 i32)) + ;; As above, but a get at the very end prevents the entire chain from being + ;; optimized. + (local.set $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + (i32.const 3) + ) + ) + (local.set $tuple2 + (local.get $tuple) + ) + (local.set $tuple3 + (local.get $tuple2) + ) + ;; Read from each. + (drop + (tuple.extract 0 + (local.get $tuple) + ) + ) + (drop + (tuple.extract 1 + (local.get $tuple2) + ) + ) + (drop + (tuple.extract 2 + (local.get $tuple3) + ) + ) + (local.get $tuple) ;; this was added + ) + + ;; CHECK: (func $set-call (type $1) (result i32 i32) + ;; CHECK-NEXT: (local $tuple (i32 i32)) + ;; CHECK-NEXT: (local.set $tuple + ;; CHECK-NEXT: (call $set-call) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 0 + ;; CHECK-NEXT: (local.get $tuple) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-call (result i32 i32) + (local $tuple (i32 i32)) + ;; Setting from a call prevents optimization. + (local.set $tuple + (call $set-call) + ) + (drop + (tuple.extract 0 + (local.get $tuple) + ) + ) + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + + ;; CHECK: (func $two-2-three (type $0) + ;; CHECK-NEXT: (local $tuple2 (i32 i32)) + ;; CHECK-NEXT: (local $tuple3 (i32 i32 i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $two-2-three + (local $tuple2 (i32 i32)) + (local $tuple3 (i32 i32 i32)) + (local.set $tuple2 + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + (local.set $tuple3 + (tuple.make + (tuple.extract 0 + (local.get $tuple2) + ) + (tuple.extract 1 + (local.get $tuple2) + ) + (i32.const 3) + ) + ) + ;; Read from each. + (drop + (tuple.extract 1 + (local.get $tuple2) + ) + ) + (drop + (tuple.extract 2 + (local.get $tuple3) + ) + ) + ) + + ;; CHECK: (func $three-2-two (type $0) + ;; CHECK-NEXT: (local $tuple2 (i32 i32)) + ;; CHECK-NEXT: (local $tuple3 (i32 i32 i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $three-2-two + (local $tuple2 (i32 i32)) + (local $tuple3 (i32 i32 i32)) + (local.set $tuple3 + (tuple.make + (i32.const 1) + (i32.const 2) + (i32.const 3) + ) + ) + (local.set $tuple2 + (tuple.make + (tuple.extract 0 + (local.get $tuple3) + ) + (tuple.extract 1 + (local.get $tuple3) + ) + ) + ) + ;; Read from each. + (drop + (tuple.extract 1 + (local.get $tuple2) + ) + ) + (drop + (tuple.extract 2 + (local.get $tuple3) + ) + ) + ) +) |