summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/fuzz_opt.py1
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/TupleOptimization.cpp359
-rw-r--r--src/passes/pass.cpp9
-rw-r--r--src/passes/passes.h1
-rw-r--r--test/lit/ctor-eval/multivalue-local.wast11
-rw-r--r--test/lit/help/wasm-opt.test2
-rw-r--r--test/lit/help/wasm2js.test2
-rw-r--r--test/lit/passes/tuple-optimization.wast1009
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)
+ )
+ )
+ )
+)