From 2ed4684cc344ee5e4ecb13f5ed7c7e2aadf6a576 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 5 Oct 2026 16:09:48 -0700 Subject: [PATCH 1/3] Do not read overwritten locals in TupleOptimization When TupleOptimization splits a tuple local.set into several local.sets, if a set's value contained a get of a prior tuple element, that value would previously have incorrectly been the updated rather than original element. Avoid reading trampled values by copying the original values to scratch locals before starting to emit the sequence of local sets. Fixes #9210. --- src/passes/TupleOptimization.cpp | 17 +- test/lit/passes/tuple-optimization.wast | 249 +++++++++++++++++++++--- 2 files changed, 232 insertions(+), 34 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 0a9584e09fd..b81959fb90b 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -294,11 +294,22 @@ struct TupleOptimization : public WalkerPass> { auto* value = curr->value; if (auto* make = value->dynCast()) { - // Write each of the tuple.make fields into the proper local. + // Write each of the tuple.make fields into a temporary first, then + // copy to the target locals, so that if a later operand reads an + // earlier field of this tuple, it sees the old value. + std::vector tempIndexes; + tempIndexes.reserve(type.size()); + for (Index i = 0; i < type.size(); i++) { + tempIndexes.push_back(Builder::addVar(getFunction(), type[i])); + } std::vector sets; for (Index i = 0; i < type.size(); i++) { - auto* value = make->operands[i]; - sets.push_back(builder.makeLocalSet(targetBase + i, value)); + sets.push_back( + builder.makeLocalSet(tempIndexes[i], make->operands[i])); + } + for (Index i = 0; i < type.size(); i++) { + sets.push_back(builder.makeLocalSet( + targetBase + i, builder.makeLocalGet(tempIndexes[i], type[i]))); } replace(builder.makeBlock(sets)); return; diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index fce17ba0ba4..5fb7b384a83 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -6,12 +6,20 @@ ;; CHECK-NEXT: (local $tuple (tuple i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) - ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $just-set (local $tuple (tuple i32 i32)) @@ -87,13 +95,21 @@ ;; CHECK-NEXT: (local $tuple (tuple i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $1) @@ -138,14 +154,22 @@ ;; 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: (block ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $4) @@ -206,15 +230,23 @@ ;; CHECK-NEXT: (local $tuple (tuple i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -261,13 +293,21 @@ ;; 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: (block - ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $4) @@ -355,13 +395,21 @@ ;; 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: (block - ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (local.set $4 @@ -506,13 +554,21 @@ ;; CHECK-NEXT: (local $tuple (tuple i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (tuple.extract 2 0 @@ -647,17 +703,25 @@ ;; 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: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.set $9 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (local.set $10 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (local.get $7) @@ -710,16 +774,28 @@ ;; CHECK-NEXT: (local $9 i32) ;; CHECK-NEXT: (local $10 i32) ;; CHECK-NEXT: (local $11 i32) + ;; CHECK-NEXT: (local $12 i32) + ;; CHECK-NEXT: (local $13 i32) + ;; CHECK-NEXT: (local $14 i32) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.set $12 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.set $13 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.set $14 ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $14) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (local.set $6 @@ -901,24 +977,44 @@ ;; 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 $2 + ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.set $8 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.set $9 ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.set $10 ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.set $11 ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $3) @@ -968,24 +1064,44 @@ ;; 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 $4 + ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.set $8 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.set $9 ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.set $10 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.set $11 ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $3) @@ -1034,13 +1150,21 @@ ;; CHECK-NEXT: (local $3 nullref) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 eqref) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 nullref) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $2) @@ -1083,4 +1207,67 @@ ) ) ) + + ;; CHECK: (func $swap (type $4) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (local $t (tuple 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: (block + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $y) + ;; 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: (block + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + (func $swap (param $x i32) (param $y i32) (result i32) + ;; Swapping elements of a tuple must not overwrite the earlier element + ;; before the later element reads it. + (local $t (tuple i32 i32)) + (local.set $t + (tuple.make 2 + (local.get $x) + (local.get $y) + ) + ) + (local.set $t + (tuple.make 2 + (tuple.extract 2 1 + (local.get $t) + ) + (tuple.extract 2 0 + (local.get $t) + ) + ) + ) + (tuple.extract 2 1 + (local.get $t) + ) + ) ) From f6bd49510cf484d7e7a2ed94592f62691ef6d059 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 6 Oct 2026 17:42:21 -0700 Subject: [PATCH 2/3] Add utility for detecting interference --- src/passes/TupleOptimization.cpp | 115 +++++++-- test/lit/passes/tuple-optimization.wast | 315 +++++++++++------------- 2 files changed, 242 insertions(+), 188 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index b81959fb90b..aaebe2c8698 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -44,6 +44,7 @@ // definitely worth lowering. // +#include #include #include #include @@ -51,6 +52,77 @@ namespace wasm { +namespace { + +// Helper class to analyze interferences between tuple.make operands and their +// target locals. When lowering a tuple local.set to individual local.sets: +// +// (local.set $t (tuple.make op0 op1 ... opN-1)) +// +// into: +// +// (local.set $t0 op0) +// (local.set $t1 op1) +// ... +// +// an operand op_i cannot be written directly to target local $ti if doing so +// interferes with the evaluation of any subsequent operand op_j (j > i). An +// interference occurs if: +// 1. op_j reads $ti (op_j would see the new value of $ti instead of the old +// value). +// 2. op_j writes $ti (op_j's write would be overwritten by op_i's value later +// in the original code, but would overwrite op_i's value here). +// 3. op_j transfers control flow (e.g. branches to an enclosing block or +// throws; in the original code, no target locals are written if control +// flow transfers out to an enclosing scope in the same function). +// +// Any operand that interferes must be written to a scratch local first and then +// copied to its target local after all operands have been evaluated. +class TupleInterferenceFinder { + std::vector interfering; + +public: + TupleInterferenceFinder(const ExpressionList& operands, + Index targetBase, + const PassOptions& passOptions, + const Module& wasm) + : interfering(operands.size(), false) { + Index numOperands = operands.size(); + if (numOperands <= 1) { + return; + } + + std::unordered_set subsequentReads; + std::unordered_set subsequentWrites; + bool subsequentTransfersControlFlow = false; + + for (Index i = numOperands; i > 0; i--) { + Index opIndex = i - 1; + Index targetLocal = targetBase + opIndex; + + if (subsequentTransfersControlFlow || + subsequentReads.contains(targetLocal) || + subsequentWrites.contains(targetLocal)) { + interfering[opIndex] = true; + } + + EffectAnalyzer effects(passOptions, wasm, operands[opIndex]); + subsequentReads.insert(effects.localsRead.begin(), + effects.localsRead.end()); + subsequentWrites.insert(effects.localsWritten.begin(), + effects.localsWritten.end()); + if (operands[opIndex]->type == Type::unreachable || + effects.transfersControlFlow()) { + subsequentTransfersControlFlow = true; + } + } + } + + bool interferes(Index i) const { return interfering[i]; } +}; + +} // anonymous namespace + struct TupleOptimization : public WalkerPass> { bool isFunctionParallel() override { return true; } @@ -231,15 +303,17 @@ struct TupleOptimization : public WalkerPass> { } } - MapApplier mapApplier(tupleToNewBaseMap); + MapApplier mapApplier(tupleToNewBaseMap, getPassOptions()); mapApplier.walkFunctionInModule(func, getModule()); } struct MapApplier : public PostWalker { std::unordered_map& tupleToNewBaseMap; + const PassOptions& passOptions; - MapApplier(std::unordered_map& tupleToNewBaseMap) - : tupleToNewBaseMap(tupleToNewBaseMap) {} + MapApplier(std::unordered_map& tupleToNewBaseMap, + const PassOptions& passOptions) + : tupleToNewBaseMap(tupleToNewBaseMap), passOptions(passOptions) {} // 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 @@ -294,22 +368,31 @@ struct TupleOptimization : public WalkerPass> { auto* value = curr->value; if (auto* make = value->dynCast()) { - // Write each of the tuple.make fields into a temporary first, then - // copy to the target locals, so that if a later operand reads an - // earlier field of this tuple, it sees the old value. - std::vector tempIndexes; - tempIndexes.reserve(type.size()); - for (Index i = 0; i < type.size(); i++) { - tempIndexes.push_back(Builder::addVar(getFunction(), type[i])); + // If writing an operand directly to its target local would interfere + // with any subsequent operand (e.g. in a tuple swap), write it to a + // temporary local first and copy it at the end. + Index numOperands = type.size(); + TupleInterferenceFinder interferences( + make->operands, targetBase, passOptions, *getModule()); + + std::vector tempIndexes(numOperands); + for (Index i = 0; i < numOperands; i++) { + if (interferences.interferes(i)) { + tempIndexes[i] = Builder::addVar(getFunction(), type[i]); + } } + std::vector sets; - for (Index i = 0; i < type.size(); i++) { - sets.push_back( - builder.makeLocalSet(tempIndexes[i], make->operands[i])); + for (Index i = 0; i < numOperands; i++) { + Index dest = + interferences.interferes(i) ? tempIndexes[i] : targetBase + i; + sets.push_back(builder.makeLocalSet(dest, make->operands[i])); } - for (Index i = 0; i < type.size(); i++) { - sets.push_back(builder.makeLocalSet( - targetBase + i, builder.makeLocalGet(tempIndexes[i], type[i]))); + for (Index i = 0; i < numOperands; i++) { + if (interferences.interferes(i)) { + sets.push_back(builder.makeLocalSet( + targetBase + i, builder.makeLocalGet(tempIndexes[i], type[i]))); + } } replace(builder.makeBlock(sets)); return; diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 5fb7b384a83..1fef33bc753 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -6,19 +6,11 @@ ;; CHECK-NEXT: (local $tuple (tuple i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) - ;; CHECK-NEXT: (local $3 i32) - ;; CHECK-NEXT: (local $4 i32) - ;; 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 $1 - ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $just-set @@ -95,20 +87,12 @@ ;; CHECK-NEXT: (local $tuple (tuple i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) - ;; CHECK-NEXT: (local $3 i32) - ;; CHECK-NEXT: (local $4 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 $1 - ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -154,21 +138,13 @@ ;; 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: (block ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $6 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $7 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 @@ -230,22 +206,14 @@ ;; CHECK-NEXT: (local $tuple (tuple i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) - ;; CHECK-NEXT: (local $3 i32) - ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result 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 $1 - ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $1) @@ -293,20 +261,12 @@ ;; 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: (block - ;; CHECK-NEXT: (local.set $6 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $7 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 @@ -395,20 +355,12 @@ ;; 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: (block - ;; CHECK-NEXT: (local.set $6 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $7 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block @@ -554,20 +506,12 @@ ;; CHECK-NEXT: (local $tuple (tuple i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) - ;; CHECK-NEXT: (local $3 i32) - ;; CHECK-NEXT: (local $4 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 $1 - ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -703,24 +647,16 @@ ;; 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: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $9 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $10 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $7 - ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $8 - ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 @@ -774,27 +710,15 @@ ;; CHECK-NEXT: (local $9 i32) ;; CHECK-NEXT: (local $10 i32) ;; CHECK-NEXT: (local $11 i32) - ;; CHECK-NEXT: (local $12 i32) - ;; CHECK-NEXT: (local $13 i32) - ;; CHECK-NEXT: (local $14 i32) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $12 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $13 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $14 - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $12) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (local.get $13) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (local.get $14) + ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block @@ -977,43 +901,23 @@ ;; 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 $7 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $8 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $9 - ;; CHECK-NEXT: (local.get $2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $10 - ;; CHECK-NEXT: (local.get $3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $11 - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $6 - ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -1064,43 +968,23 @@ ;; 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 $7 - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $8 - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $9 - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $6 - ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $10 - ;; CHECK-NEXT: (local.get $4) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $11 - ;; CHECK-NEXT: (local.get $5) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -1150,20 +1034,12 @@ ;; CHECK-NEXT: (local $3 nullref) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 eqref) - ;; CHECK-NEXT: (local $6 i32) - ;; CHECK-NEXT: (local $7 nullref) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $6 - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $7 - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 @@ -1213,35 +1089,23 @@ ;; 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: (block - ;; CHECK-NEXT: (local.set $5 - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $6 - ;; CHECK-NEXT: (local.get $y) - ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 - ;; CHECK-NEXT: (local.get $7) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $4 - ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $4) @@ -1270,4 +1134,111 @@ (local.get $t) ) ) + + ;; CHECK: (func $swap-3 (type $5) (param $x i32) (param $y i32) (param $z i32) (result i32) + ;; CHECK-NEXT: (local $t (tuple i32 i32 i32)) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + (func $swap-3 (param $x i32) (param $y i32) (param $z i32) (result i32) + ;; Rotating 3 elements of a tuple (0->1, 1->2, 2->0) requires saving only + ;; the element that would be overwritten before being read. + (local $t (tuple i32 i32 i32)) + (local.set $t + (tuple.make 3 + (local.get $x) + (local.get $y) + (local.get $z) + ) + ) + (local.set $t + (tuple.make 3 + (tuple.extract 3 1 + (local.get $t) + ) + (tuple.extract 3 2 + (local.get $t) + ) + (tuple.extract 3 0 + (local.get $t) + ) + ) + ) + (tuple.extract 3 2 + (local.get $t) + ) + ) + + ;; CHECK: (func $branch-out (type $6) (param $cond i32) (result i32) + ;; CHECK-NEXT: (local $t (tuple i32 i32)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (block $b + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (br_if $b + ;; CHECK-NEXT: (local.get $cond) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + (func $branch-out (param $cond i32) (result i32) + ;; If a later operand transfers control flow to an enclosing scope in the + ;; function, earlier operands must not prematurely overwrite target locals. + (local $t (tuple i32 i32)) + (block $b + (local.set $t + (tuple.make 2 + (i32.const 1) + (block (result i32) + (br_if $b (local.get $cond)) + (i32.const 2) + ) + ) + ) + ) + (tuple.extract 2 0 + (local.get $t) + ) + ) ) From 990582beb2e4e7795ca743d7b44acb580ac166d0 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 7 Oct 2026 11:41:48 -0700 Subject: [PATCH 3/3] address comments --- src/passes/TupleOptimization.cpp | 7 +--- test/lit/passes/tuple-optimization.wast | 55 ++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index aaebe2c8698..4aac390e42f 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -88,9 +88,7 @@ class TupleInterferenceFinder { const Module& wasm) : interfering(operands.size(), false) { Index numOperands = operands.size(); - if (numOperands <= 1) { - return; - } + assert(numOperands > 1); std::unordered_set subsequentReads; std::unordered_set subsequentWrites; @@ -111,8 +109,7 @@ class TupleInterferenceFinder { effects.localsRead.end()); subsequentWrites.insert(effects.localsWritten.begin(), effects.localsWritten.end()); - if (operands[opIndex]->type == Type::unreachable || - effects.transfersControlFlow()) { + if (effects.transfersControlFlow()) { subsequentTransfersControlFlow = true; } } diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 1fef33bc753..b9781159766 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -1065,7 +1065,7 @@ ) ) - ;; CHECK: (func $unreachable.tuple.extract (type $3) (result i32) + ;; CHECK: (func $unreachable.tuple.extract (type $4) (result i32) ;; CHECK-NEXT: (local $tuple (tuple i32 i64)) ;; CHECK-NEXT: (local $non-tuple i32) ;; CHECK-NEXT: (tuple.extract 2 0 @@ -1084,7 +1084,7 @@ ) ) - ;; CHECK: (func $swap (type $4) (param $x i32) (param $y i32) (result i32) + ;; CHECK: (func $swap (type $3) (param $x i32) (param $y i32) (result i32) ;; CHECK-NEXT: (local $t (tuple i32 i32)) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 i32) @@ -1135,6 +1135,57 @@ ) ) + ;; CHECK: (func $no-swap (type $3) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (local $t (tuple i32 i32)) + ;; CHECK-NEXT: (local $t' (tuple i32 i32)) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + (func $no-swap (param $x i32) (param $y i32) (result i32) + ;; Like $swap, but this time we are copying the elements to a different + ;; tuple local, so we don't need temp locals. + (local $t (tuple i32 i32)) + (local $t' (tuple i32 i32)) + (local.set $t + (tuple.make 2 + (local.get $x) + (local.get $y) + ) + ) + (local.set $t' + (tuple.make 2 + (tuple.extract 2 1 + (local.get $t) + ) + (tuple.extract 2 0 + (local.get $t) + ) + ) + ) + (tuple.extract 2 1 + (local.get $t') + ) + ) + ;; CHECK: (func $swap-3 (type $5) (param $x i32) (param $y i32) (param $z i32) (result i32) ;; CHECK-NEXT: (local $t (tuple i32 i32 i32)) ;; CHECK-NEXT: (local $4 i32)