From eead1ca28d2224c8e232ff264eb5a3c59c0460aa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 09:55:51 -0700 Subject: [PATCH 01/67] start --- src/passes/CMakeLists.txt | 1 + src/passes/TupleOptimization.cpp | 121 +++++++++++++++++++++++++++++++ src/passes/pass.cpp | 6 ++ src/passes/passes.h | 1 + 4 files changed, 129 insertions(+) create mode 100644 src/passes/TupleOptimization.cpp diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 302cd7c7a30..a47333cf6d7 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 00000000000..f54e6afc139 --- /dev/null +++ b/src/passes/TupleOptimization.cpp @@ -0,0 +1,121 @@ +/* + * 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 to be 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. +// + +#include +#include + +namespace wasm { + +struct TupleOptimization + : public WalkerPass< + PostWalker> { + bool isFunctionParallel() override { return true; } + + std::unique_ptr create() override { + return std::make_unique(propagate); + } + + // TupleOptimization() {} + + // Track the number of uses for each tuple local. + std::vector 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 validUses; + + 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->type.isTuple()) { + hasTuple = true; + break; + } + } + if (!hasTuple) { + return; + } + + // Walk the code to collect information. + auto numLocals = func->getNumLocals(); + uses.resize(numLocals); + validUses.resize(numLocals); + + super::doWalkFunction(func); + } + + void visitLocalGet(LocalGet* curr) { + if (curr->type.isTuple()) { + validUses[curr->index]++; + } + } + + void visitLocalSet(LocalSet* curr) { + if (getFunction()->getLocalType(curr->index).isTuple()) { + 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 (value->is() || value->is() || + value->is()) { + 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()) { + validUses[set->index]++; + } else if (auto* get = curr->tuple->dynCast()) { + validUses[get->index]++; + } + } +}; + +Pass* createTupleOptimizationPass() { return new TupleOptimization(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 0d47cd78e29..1e2273cc1df 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,9 @@ void PassRunner::addDefaultFunctionOptimizationPasses() { if (options.optimizeLevel >= 2 || options.shrinkLevel >= 2) { addIfNoDWARFIssues("code-pushing"); } + if (wasm->features.hasMultivalue()) { + 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 fa6d98111b2..1cd2a5672d4 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(); From 28f682da46c9b12953c9239b1021d5fbba4f8234 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 10:08:48 -0700 Subject: [PATCH 02/67] work --- src/passes/TupleOptimization.cpp | 70 ++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index f54e6afc139..bb058dc5333 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -40,6 +40,7 @@ // #include +#include #include namespace wasm { @@ -63,6 +64,14 @@ struct TupleOptimization // one, and the local cannot be optimized. std::vector 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 map of the source to the indexes it is copied into (so that we + // can easily flow badness forward). + std::vector> copiedIndexes; + void doWalkFunction(Function* func) { // If tuples are not enabled, or there are no tuple locals, then there is no // work to do. @@ -80,12 +89,17 @@ struct TupleOptimization return; } - // Walk the code to collect information. + // 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) { @@ -99,8 +113,13 @@ struct TupleOptimization 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 (value->is() || value->is() || - value->is()) { + if (auto* set = value->dynCast()) { + validUses[set->index]++; + copiedIndexes[set->index].insert(curr->index); + } else if (auto* get = value->dynCast()) { + validUses[get->index]++; + copiedIndexes[set->index].insert(curr->index); + } else if (value->is()) { validUses[curr->index]++; } } @@ -114,6 +133,51 @@ struct TupleOptimization validUses[get->index]++; } } + + void optimize(Function* func) { + auto numLocals = func->getNumLocals(); + + std::vector bad(numLocals); + UniqueDeferredQueue work; + + for (Index i = 0; i < uses.size(); i++) { + if (uses[i] > 0 && validUses[i] < uses[i]) { + // This is a bad tuple. Note that, and also set it up for the flow to + // corrupt those it is written into. + bad[i] = true; + 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 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! + + } }; Pass* createTupleOptimizationPass() { return new TupleOptimization(); } From 2d5008f5375e61a1a7b0d921bb51e070d652a457 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 10:33:42 -0700 Subject: [PATCH 03/67] work --- src/passes/TupleOptimization.cpp | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index bb058dc5333..7d71ba78bb6 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -41,6 +41,7 @@ #include #include +#include #include namespace wasm { @@ -175,8 +176,31 @@ struct TupleOptimization return; } - // We found things to optimize! - + // 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 first index used for its contents (subsequent indexes are used + // contiguously). + std::unordered_map tupleToNormalMap; + for (Index i = 0; i < good.size(); i++) { + if (good[i]) { + auto normal = func->getNumLocals(); + tupleToNormalMap[i] = normal; + auto lastNewIndex = 0; + for (auto t : func->getLocalType(i)) { + auto 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). + assert(newIndex == normal); + } else { + // This must be right after the former. + assert(newIndex == lastNewIndex + 1); + } + lastNewIndex = newIndex; + } + } + } } }; From 6b9e063ecae502ce35c35dfeccbbfbaac59c23a0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 10:36:09 -0700 Subject: [PATCH 04/67] work --- src/passes/TupleOptimization.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 7d71ba78bb6..0e53eb95655 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -179,20 +179,21 @@ struct TupleOptimization // 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 first index used for its contents (subsequent indexes are used - // contiguously). - std::unordered_map tupleToNormalMap; + // to the base index used for its contents: an index and several others + // right after it, depending on the tuple size. + std::unordered_map tupleToNewBaseMap; for (Index i = 0; i < good.size(); i++) { if (good[i]) { - auto normal = func->getNumLocals(); - tupleToNormalMap[i] = normal; + auto newBase = func->getNumLocals(); + tupleToNewBaseMap[i] = newBase; auto lastNewIndex = 0; for (auto t : func->getLocalType(i)) { auto 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). - assert(newIndex == normal); + // 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); From cf54d39c5eb16197fc45c0bed7c31b24cf93b770 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 10:48:50 -0700 Subject: [PATCH 05/67] work --- src/passes/TupleOptimization.cpp | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 0e53eb95655..60b2e36c7d3 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -202,7 +202,69 @@ struct TupleOptimization } } } + + MapApplier mapApplier(tupleToNewBaseMap); + mapApplier.walkFunctionInModule(func, getModule()); } + + struct MapApplier : public PostWalker { + std::unordered_map& tupleToNewBaseMap; + + MapApplier(std::unordered_map& 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; + } + + void visitLocalSet(LocalSet* curr) { + if (auto targetBase = getNewBaseIndex(curr->index)) { + Builder builder(*getModule()); + auto type = getFunction()->getLocalType(curr->index); + + auto* value = curr->value; + if (auto* make = value->dynCast()) { + .. + return; + } + + // This is a copy of a tuple local into another. Copy all the fields + // between them. + // TODO: test a tee chain. + Index sourceBase; + if (auto* set = value->dynCast()) { + sourceBase = getNewBaseIndex(set->index); + } else if (auto* get = value->dynCast()) { + sourceBase = getNewBaseIndex(get->index); + } else { + WASM_UNREACHABLE("bad set child"); + } + + assert(sourceBase); + std::vector sets; + for (auto i = 0; i < type.size(); i++) { + auto* get = builder.makeLocalGet(sourceBase + i, type[i]); + sets.push_back(builder.makeLocalSet(targetBase + i, get)); + } + replaceCurrent(builder.makeBlock(sets)); + } + } + + 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()) { + validUses[set->index]++; + } else if (auto* get = curr->tuple->dynCast()) { + validUses[get->index]++; + } + } + }; }; Pass* createTupleOptimizationPass() { return new TupleOptimization(); } From 0a576b953729189960456a3de90f0f60eb4eb001 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 10:54:01 -0700 Subject: [PATCH 06/67] work --- src/passes/TupleOptimization.cpp | 47 +++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 60b2e36c7d3..65a3899681c 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -223,6 +223,23 @@ struct TupleOptimization return iter->second; } + // Given a local.get or local.set, return the new base index for the local + // index used there. + Index getSetOrGetBaseIndex(Expression* setOrGet) { + Index base; + if (auto* set = value->dynCast()) { + base = getNewBaseIndex(set->index); + } else if (auto* get = value->dynCast()) { + base = getNewBaseIndex(get->index); + } else { + WASM_UNREACHABLE("bad set child"); + } + + // This must always be called on a local that is being mapped. + assert(base); + return base; + } + void visitLocalSet(LocalSet* curr) { if (auto targetBase = getNewBaseIndex(curr->index)) { Builder builder(*getModule()); @@ -230,23 +247,21 @@ struct TupleOptimization auto* value = curr->value; if (auto* make = value->dynCast()) { - .. + // Write each of the tuple.make fields into the proper local. + std::vector sets; + for (auto i = 0; i < type.size(); i++) { + auto* value = make->operands[i]; + sets.push_back(builder.makeLocalSet(targetBase + i, value)); + } + replaceCurrent(builder.makeBlock(sets)); return; } // This is a copy of a tuple local into another. Copy all the fields // between them. // TODO: test a tee chain. - Index sourceBase; - if (auto* set = value->dynCast()) { - sourceBase = getNewBaseIndex(set->index); - } else if (auto* get = value->dynCast()) { - sourceBase = getNewBaseIndex(get->index); - } else { - WASM_UNREACHABLE("bad set child"); - } + Index sourceBase = getSetOrGetBaseIndex(value); - assert(sourceBase); std::vector sets; for (auto i = 0; i < type.size(); i++) { auto* get = builder.makeLocalGet(sourceBase + i, type[i]); @@ -257,12 +272,12 @@ struct TupleOptimization } 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()) { - validUses[set->index]++; - } else if (auto* get = curr->tuple->dynCast()) { - validUses[get->index]++; - } + Index sourceBase = getSetOrGetBaseIndex(value); + Builder builder(*getModule()); + auto type = getFunction()->getLocalType(curr->index); + auto i = curr->index; + auto* get = builder.makeLocalGet(sourceBase + i, type[i]); + replaceCurrent(get); } }; }; From 03304195bf076a0df6df14d50d75df08b60f7e2a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 10:56:44 -0700 Subject: [PATCH 07/67] work --- src/passes/TupleOptimization.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 65a3899681c..0a45050c6f9 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -52,7 +52,7 @@ struct TupleOptimization bool isFunctionParallel() override { return true; } std::unique_ptr create() override { - return std::make_unique(propagate); + return std::make_unique(); } // TupleOptimization() {} @@ -63,7 +63,7 @@ struct TupleOptimization // 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 validUses; + std::vector 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 @@ -81,7 +81,7 @@ struct TupleOptimization } bool hasTuple = false; for (auto var : func->vars) { - if (var->type.isTuple()) { + if (var.isTuple()) { hasTuple = true; break; } @@ -186,9 +186,9 @@ struct TupleOptimization if (good[i]) { auto newBase = func->getNumLocals(); tupleToNewBaseMap[i] = newBase; - auto lastNewIndex = 0; + Index lastNewIndex = 0; for (auto t : func->getLocalType(i)) { - auto newIndex = Builder::addVar(func, t); + 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 @@ -226,16 +226,17 @@ struct TupleOptimization // Given a local.get or local.set, return the new base index for the local // index used there. Index getSetOrGetBaseIndex(Expression* setOrGet) { - Index base; - if (auto* set = value->dynCast()) { - base = getNewBaseIndex(set->index); - } else if (auto* get = value->dynCast()) { - base = getNewBaseIndex(get->index); + Index index; + if (auto* set = setOrGet->dynCast()) { + index = set->index; + } else if (auto* get = setOrGet->dynCast()) { + index = get->index; } else { WASM_UNREACHABLE("bad set child"); } // This must always be called on a local that is being mapped. + auto base = getNewBaseIndex(index); assert(base); return base; } @@ -249,7 +250,7 @@ struct TupleOptimization if (auto* make = value->dynCast()) { // Write each of the tuple.make fields into the proper local. std::vector sets; - for (auto i = 0; i < type.size(); i++) { + for (Index i = 0; i < type.size(); i++) { auto* value = make->operands[i]; sets.push_back(builder.makeLocalSet(targetBase + i, value)); } @@ -263,7 +264,7 @@ struct TupleOptimization Index sourceBase = getSetOrGetBaseIndex(value); std::vector sets; - for (auto i = 0; i < type.size(); i++) { + for (Index i = 0; i < type.size(); i++) { auto* get = builder.makeLocalGet(sourceBase + i, type[i]); sets.push_back(builder.makeLocalSet(targetBase + i, get)); } @@ -272,7 +273,7 @@ struct TupleOptimization } void visitTupleExtract(TupleExtract* curr) { - Index sourceBase = getSetOrGetBaseIndex(value); + Index sourceBase = getSetOrGetBaseIndex(curr->tuple); Builder builder(*getModule()); auto type = getFunction()->getLocalType(curr->index); auto i = curr->index; From 2cbffd97732f7d181c1f0f025ec66ef910b54c07 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 10:57:44 -0700 Subject: [PATCH 08/67] work --- test/lit/passes/tuple-optimization.wast | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 test/lit/passes/tuple-optimization.wast diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast new file mode 100644 index 00000000000..8b8b3de2c59 --- /dev/null +++ b/test/lit/passes/tuple-optimization.wast @@ -0,0 +1,6 @@ +;; 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 + +) From 28e1f46c0775ef0e019a8809a56a6fb8b4dfad53 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 11:10:54 -0700 Subject: [PATCH 09/67] work --- src/passes/TupleOptimization.cpp | 14 ++++++++++++-- test/lit/passes/tuple-optimization.wast | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 0a45050c6f9..4e2c54f7472 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -57,7 +57,8 @@ struct TupleOptimization // TupleOptimization() {} - // Track the number of uses for each tuple local. + // Track the number of uses for each tuple local. We consider a use as a + // local.get, set, or tee. std::vector uses; // Tracks which tuple local uses are valid, that is, follow the properties @@ -76,6 +77,7 @@ struct TupleOptimization void doWalkFunction(Function* func) { // If tuples are not enabled, or there are no tuple locals, then there is no // work to do. +std::cout << "a1\n"; if (!getModule()->features.hasMultivalue()) { return; } @@ -89,6 +91,7 @@ struct TupleOptimization if (!hasTuple) { return; } +std::cout << "a2\n"; // Prepare global data structures before we collect info. auto numLocals = func->getNumLocals(); @@ -105,12 +108,15 @@ struct TupleOptimization void visitLocalGet(LocalGet* curr) { if (curr->type.isTuple()) { - validUses[curr->index]++; + uses[curr->index]++; } } void visitLocalSet(LocalSet* curr) { +std::cout << "set!\n"; if (getFunction()->getLocalType(curr->index).isTuple()) { +std::cout << " set2\n"; + uses[curr->index]++; 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. @@ -122,6 +128,7 @@ struct TupleOptimization copiedIndexes[set->index].insert(curr->index); } else if (value->is()) { validUses[curr->index]++; +std::cout << " set3: " << curr->index << " now has " << validUses[curr->index] << "uses\n"; } } } @@ -166,15 +173,18 @@ struct TupleOptimization std::vector good(numLocals); bool hasGood = false; for (Index i = 0; i < uses.size(); i++) { +std::cout << "consider " << i << " which has uses=" << uses[i] << " and is bad=" << bad[i] << '\n'; if (uses[i] > 0 && !bad[i]) { good[i] = true; hasGood = true; } } +std::cout << "a3\n"; if (!hasGood) { return; } +std::cout << "a4\n"; // We found things to optimize! Create new non-tuple locals for their // contents, and then rewrite the code to use those according to the diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 8b8b3de2c59..2305c03589f 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -2,5 +2,22 @@ ;; 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.set $tuple + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $just-set + (local $tuple (i32 i32)) + (local.set $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) ) From b9789a5e1948ed48359ac212cefeafef035b9b76 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 11:11:15 -0700 Subject: [PATCH 10/67] work --- src/passes/TupleOptimization.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 4e2c54f7472..8a8beb6e5a8 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -77,7 +77,6 @@ struct TupleOptimization void doWalkFunction(Function* func) { // If tuples are not enabled, or there are no tuple locals, then there is no // work to do. -std::cout << "a1\n"; if (!getModule()->features.hasMultivalue()) { return; } @@ -91,7 +90,6 @@ std::cout << "a1\n"; if (!hasTuple) { return; } -std::cout << "a2\n"; // Prepare global data structures before we collect info. auto numLocals = func->getNumLocals(); @@ -113,9 +111,7 @@ std::cout << "a2\n"; } void visitLocalSet(LocalSet* curr) { -std::cout << "set!\n"; if (getFunction()->getLocalType(curr->index).isTuple()) { -std::cout << " set2\n"; uses[curr->index]++; auto* value = curr->value; // We need the input to the local to be another such local (from a tee, or @@ -128,7 +124,6 @@ std::cout << " set2\n"; copiedIndexes[set->index].insert(curr->index); } else if (value->is()) { validUses[curr->index]++; -std::cout << " set3: " << curr->index << " now has " << validUses[curr->index] << "uses\n"; } } } @@ -173,18 +168,15 @@ std::cout << " set3: " << curr->index << " now has " << validUses[curr->index] std::vector good(numLocals); bool hasGood = false; for (Index i = 0; i < uses.size(); i++) { -std::cout << "consider " << i << " which has uses=" << uses[i] << " and is bad=" << bad[i] << '\n'; if (uses[i] > 0 && !bad[i]) { good[i] = true; hasGood = true; } } -std::cout << "a3\n"; if (!hasGood) { return; } -std::cout << "a4\n"; // We found things to optimize! Create new non-tuple locals for their // contents, and then rewrite the code to use those according to the From 9b776f0d5c6ae47ddf3d5d7c50ef764e5fef5640 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 11:11:31 -0700 Subject: [PATCH 11/67] work --- test/lit/passes/tuple-optimization.wast | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 2305c03589f..117f08e9dac 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -4,11 +4,13 @@ (module ;; CHECK: (func $just-set (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: (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 From 2f4544463b53f44b60d8f2bb6a3b7e2685c138a2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 11:15:11 -0700 Subject: [PATCH 12/67] work --- src/passes/TupleOptimization.cpp | 5 ++++- test/lit/passes/tuple-optimization.wast | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 8a8beb6e5a8..e018bfede69 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -275,9 +275,12 @@ struct TupleOptimization } void visitTupleExtract(TupleExtract* curr) { + auto type = curr->tuple->type; + if (type == Type::unreachable) { + return; + } Index sourceBase = getSetOrGetBaseIndex(curr->tuple); Builder builder(*getModule()); - auto type = getFunction()->getLocalType(curr->index); auto i = curr->index; auto* get = builder.makeLocalGet(sourceBase + i, type[i]); replaceCurrent(get); diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 117f08e9dac..4f004317dd2 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -22,4 +22,29 @@ ) ) ) + + ;; 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)) + (drop + (tuple.extract 0 + (local.get $tuple) + ) + ) + (drop + (tuple.extract 1 + (local.get $tuple) + ) + ) + ) ) From 377a7b3fb81f4cb21d5dbe08ca4fbfc08ac34d02 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 11:16:02 -0700 Subject: [PATCH 13/67] work --- test/lit/passes/tuple-optimization.wast | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 4f004317dd2..feb85e19773 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -47,4 +47,52 @@ ) ) ) + + ;; 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) + ) + ) + ) ) From 8e55f0c3e0e06bdecec1e6ec59238fd49a7d244b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:35:58 -0700 Subject: [PATCH 14/67] fix --- src/passes/TupleOptimization.cpp | 84 ++++++++++++++++++++++--- test/lit/passes/tuple-optimization.wast | 65 +++++++++++++++++++ 2 files changed, 140 insertions(+), 9 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index e018bfede69..6957ef84fc0 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -58,7 +58,8 @@ struct TupleOptimization // TupleOptimization() {} // Track the number of uses for each tuple local. We consider a use as a - // local.get, set, or tee. + // 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 uses; // Tracks which tuple local uses are valid, that is, follow the properties @@ -106,23 +107,32 @@ struct TupleOptimization void visitLocalGet(LocalGet* curr) { if (curr->type.isTuple()) { +std::cout << " use++get\n"; uses[curr->index]++; } } void visitLocalSet(LocalSet* curr) { +std::cout << "pre set: " << curr->index <<"\n"; if (getFunction()->getLocalType(curr->index).isTuple()) { - uses[curr->index]++; +std::cout << " use++set\n"; + 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* set = value->dynCast()) { + assert(set->isTee()); validUses[set->index]++; + validUses[curr->index]++; +std::cout << " valid++set\n"; copiedIndexes[set->index].insert(curr->index); } else if (auto* get = value->dynCast()) { +std::cout << " valid++get\n"; validUses[get->index]++; + validUses[curr->index]++; copiedIndexes[set->index].insert(curr->index); } else if (value->is()) { +std::cout << " valid++make\n"; validUses[curr->index]++; } } @@ -144,11 +154,13 @@ struct TupleOptimization UniqueDeferredQueue work; for (Index i = 0; i < uses.size(); i++) { +std::cout << "consider " << i << " which has use/valid " << uses[i] << ", " << validUses[i] << "\n"; if (uses[i] > 0 && validUses[i] < uses[i]) { // This is a bad tuple. Note that, and also set it up for the flow to // corrupt those it is written into. bad[i] = true; work.push(i); +std::cout << "bad: " << i <<"\n"; } } @@ -159,6 +171,7 @@ struct TupleOptimization continue; } bad[i] = true; +std::cout << "badd: " << i <<"\n"; for (auto target : copiedIndexes[i]) { work.push(target); } @@ -171,6 +184,7 @@ struct TupleOptimization if (uses[i] > 0 && !bad[i]) { good[i] = true; hasGood = true; +std::cout << "good: " << i <<"\n"; } } @@ -243,47 +257,99 @@ struct TupleOptimization return base; } + // 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 set (which has been + // replaced by the block). To make that simple keep a map of the things that + // replaced tees. + std::unordered_map teeReplacements; + void visitLocalSet(LocalSet* curr) { +std::cout << "set " << curr->index << '\n'; + auto replace = [&](Expression* replacement) { + if (curr->isTee()) { + teeReplacements[replacement] = curr; + } + replaceCurrent(replacement); + }; + if (auto targetBase = getNewBaseIndex(curr->index)) { +std::cout << " set a\n"; Builder builder(*getModule()); auto type = getFunction()->getLocalType(curr->index); auto* value = curr->value; if (auto* make = value->dynCast()) { +std::cout << " set b\n"; // Write each of the tuple.make fields into the proper local. std::vector sets; for (Index i = 0; i < type.size(); i++) { auto* value = make->operands[i]; sets.push_back(builder.makeLocalSet(targetBase + i, value)); } - replaceCurrent(builder.makeBlock(sets)); + replace(builder.makeBlock(sets)); return; } + std::vector contents; + + auto iter = teeReplacements.find(value); + if (iter != teeReplacements.end()) { +std::cout << " set c\n"; + // The input to us was a tee that has been replaced. The actual value + // we read from (the tee) can be found in teeReplacements. Also, we + // need to keep around the replacement of the tee. + contents.push_back(value); + value = iter->second; + } +std::cout << " set d\n"; + // This is a copy of a tuple local into another. Copy all the fields // between them. // TODO: test a tee chain. Index sourceBase = getSetOrGetBaseIndex(value); - std::vector sets; for (Index i = 0; i < type.size(); i++) { auto* get = builder.makeLocalGet(sourceBase + i, type[i]); - sets.push_back(builder.makeLocalSet(targetBase + i, get)); + contents.push_back(builder.makeLocalSet(targetBase + i, get)); } - replaceCurrent(builder.makeBlock(sets)); + replace(builder.makeBlock(contents)); } } void visitTupleExtract(TupleExtract* curr) { - auto type = curr->tuple->type; + auto* value = curr->tuple; + auto type = value->type; if (type == Type::unreachable) { return; } - Index sourceBase = getSetOrGetBaseIndex(curr->tuple); + + Expression* contents = nullptr; + + auto iter = teeReplacements.find(value); + if (iter != teeReplacements.end()) { + // The input to us was a tee that has been replaced. Handle it as in + // visitLocalSet. + contents = value; + value = iter->second; + } + + Index sourceBase = getSetOrGetBaseIndex(value); Builder builder(*getModule()); auto i = curr->index; auto* get = builder.makeLocalGet(sourceBase + i, type[i]); - replaceCurrent(get); + if (contents) { + contents = builder.makeSequence(contents, get); + replaceCurrent(contents); + } else { + replaceCurrent(get); + } } }; }; diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index feb85e19773..82c933db8cb 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -95,4 +95,69 @@ ) ) ) + + ;; 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 $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) + ) + ) + ) ) From 554d6e2a9da80173496c37517d0b161debe32ebc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:36:13 -0700 Subject: [PATCH 15/67] fix --- test/lit/passes/tuple-optimization.wast | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 82c933db8cb..223dd709724 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -120,6 +120,12 @@ ;; 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 @@ -137,7 +143,7 @@ ) ) ) - (;; Read the first tuple. + ;; Read the first tuple. (drop (tuple.extract 0 (local.get $tuple) @@ -147,7 +153,7 @@ (tuple.extract 1 (local.get $tuple) ) - ;;) + ) ;; Read the second tuple. (drop (tuple.extract 0 From 52a98d77d2f6ea0787d2cd3cb0934722523f5ae2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:37:08 -0700 Subject: [PATCH 16/67] fix --- test/lit/passes/tuple-optimization.wast | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 223dd709724..b4adbaadaba 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -4,17 +4,19 @@ (module ;; CHECK: (func $just-set (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) - ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $other f64) ;; CHECK-NEXT: (local $2 i32) - ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $just-set (local $tuple (i32 i32)) + (local $other f64) ;; a non-tuple local is ignored (local.set $tuple (tuple.make (i32.const 1) From ddd299330976066a5b89c46a68ca941a60a46e43 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:38:13 -0700 Subject: [PATCH 17/67] fix --- test/lit/passes/tuple-optimization.wast | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index b4adbaadaba..21ab3dcc1c1 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -7,22 +7,35 @@ ;; CHECK-NEXT: (local $other f64) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) - ;; CHECK-NEXT: (local.set $2 - ;; CHECK-NEXT: (i32.const 1) + ;; 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: (local.set $3 - ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (local.set $other + ;; CHECK-NEXT: (local.tee $other + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $just-set (local $tuple (i32 i32)) - (local $other f64) ;; a non-tuple local is ignored + (local $other f64) (local.set $tuple (tuple.make (i32.const 1) (i32.const 2) ) ) + ;; A non-tuple local and all operations on it should be ignored. + (local.set $other + (local.tee $other + (local.get $other) + ) + ) ) ;; CHECK: (func $just-get (type $0) From 9c43ac7f8ed4dba9f9526fac3493cf2e0e1f1d52 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:40:54 -0700 Subject: [PATCH 18/67] fix --- test/lit/passes/tuple-optimization.wast | 31 +++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 21ab3dcc1c1..c6eb4264c4d 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -63,6 +63,37 @@ ) ) + ;; 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, 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) From d80db8098b8f404c422c5d71f59a327503fdd167 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:45:26 -0700 Subject: [PATCH 19/67] work --- src/passes/TupleOptimization.cpp | 20 ++++++++++---------- test/lit/passes/tuple-optimization.wast | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 6957ef84fc0..e088616586c 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -325,28 +325,28 @@ std::cout << " set d\n"; void visitTupleExtract(TupleExtract* curr) { auto* value = curr->tuple; - auto type = value->type; - if (type == Type::unreachable) { - return; - } - - Expression* contents = nullptr; + Expression* extraContents = nullptr; auto iter = teeReplacements.find(value); if (iter != teeReplacements.end()) { // The input to us was a tee that has been replaced. Handle it as in // visitLocalSet. - contents = value; + extraContents = value; value = iter->second; } + auto type = value->type; + if (type == Type::unreachable) { + return; + } + Index sourceBase = getSetOrGetBaseIndex(value); Builder builder(*getModule()); auto i = curr->index; +std::cout << type << " : " << i << '\n'; auto* get = builder.makeLocalGet(sourceBase + i, type[i]); - if (contents) { - contents = builder.makeSequence(contents, get); - replaceCurrent(contents); + if (extraContents) { + replaceCurrent(builder.makeSequence(extraContents, get)); } else { replaceCurrent(get); } diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index c6eb4264c4d..afc3217f38c 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -212,4 +212,18 @@ ) ) ) + + (func $just-tee + (local $tuple (i32 i32)) + (drop + (tuple.extract 0 + (local.tee $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + ) + ) ) From b06d8688eb4b00eaacc54f0ce24ceb72b0b9838e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:46:52 -0700 Subject: [PATCH 20/67] fix --- test/lit/passes/tuple-optimization.wast | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index afc3217f38c..c4039ea66a5 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -213,6 +213,24 @@ ) ) + ;; 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 @@ -226,4 +244,24 @@ ) ) ) + + ;; 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) + ) + ) + ) ) From 148d5c44c24237a87983bb89d4d70ef4d9fe3bec Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:49:00 -0700 Subject: [PATCH 21/67] fix --- test/lit/passes/tuple-optimization.wast | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index c4039ea66a5..f7de39e82fc 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -264,4 +264,41 @@ ) ) ) + + ;; 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) + ) + ) + ) + ) ) From 44b2ddec5d769e00b8e7dec85121ca677609acd7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:54:43 -0700 Subject: [PATCH 22/67] fix --- src/passes/TupleOptimization.cpp | 14 +++++++----- test/lit/passes/tuple-optimization.wast | 29 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index e088616586c..af6e1061367 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -71,8 +71,8 @@ struct TupleOptimization // index that was copied, as if the source ends up bad then the target is bad // as well. // - // This is a map of the source to the indexes it is copied into (so that we - // can easily flow badness forward). + // This is a symmetrical map, that is, we consider copies to work both ways, + // and x \in copiedIndexed[y] <==> y \in copiedIndexed[x] std::vector> copiedIndexes; void doWalkFunction(Function* func) { @@ -126,11 +126,13 @@ std::cout << " use++set\n"; validUses[curr->index]++; std::cout << " valid++set\n"; copiedIndexes[set->index].insert(curr->index); + copiedIndexes[curr->index].insert(set->index); } else if (auto* get = value->dynCast()) { std::cout << " valid++get\n"; validUses[get->index]++; validUses[curr->index]++; - copiedIndexes[set->index].insert(curr->index); + copiedIndexes[get->index].insert(curr->index); + copiedIndexes[curr->index].insert(get->index); } else if (value->is()) { std::cout << " valid++make\n"; validUses[curr->index]++; @@ -150,15 +152,15 @@ std::cout << " valid++make\n"; void optimize(Function* func) { auto numLocals = func->getNumLocals(); + // Find the set of bad indexes. We each each such candidate to a worklist + // that we will then flow to find all those corrupted. std::vector bad(numLocals); UniqueDeferredQueue work; for (Index i = 0; i < uses.size(); i++) { std::cout << "consider " << i << " which has use/valid " << uses[i] << ", " << validUses[i] << "\n"; if (uses[i] > 0 && validUses[i] < uses[i]) { - // This is a bad tuple. Note that, and also set it up for the flow to - // corrupt those it is written into. - bad[i] = true; + // This is a bad tuple. work.push(i); std::cout << "bad: " << i <<"\n"; } diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index f7de39e82fc..828dc5139ec 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -301,4 +301,33 @@ ) ) ) + + ;; 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) + ) ) From 9b9eef09b1e66b80d4be929fb0b7bb8707b4caee Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:55:22 -0700 Subject: [PATCH 23/67] fix --- test/lit/passes/tuple-optimization.wast | 46 ++++++++++++++++++++----- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 828dc5139ec..30558e90f7f 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -2,7 +2,7 @@ ;; RUN: wasm-opt %s --tuple-optimization -all -S -o - | filecheck %s (module - ;; CHECK: (func $just-set (type $0) + ;; CHECK: (func $just-set (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $other f64) ;; CHECK-NEXT: (local $2 i32) @@ -38,7 +38,7 @@ ) ) - ;; CHECK: (func $just-get (type $0) + ;; CHECK: (func $just-get (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -63,7 +63,7 @@ ) ) - ;; CHECK: (func $just-get-bad (type $1) (result i32 i32) + ;; CHECK: (func $just-get-bad (type $0) (result i32 i32) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (tuple.extract 0 @@ -94,7 +94,7 @@ (local.get $tuple) ) - ;; CHECK: (func $set-and-gets (type $0) + ;; CHECK: (func $set-and-gets (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -142,7 +142,7 @@ ) ) - ;; CHECK: (func $tee (type $0) + ;; CHECK: (func $tee (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local $2 i32) @@ -213,7 +213,7 @@ ) ) - ;; CHECK: (func $just-tee (type $0) + ;; CHECK: (func $just-tee (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -245,7 +245,7 @@ ) ) - ;; CHECK: (func $just-tee-bad (type $1) (result i32 i32) + ;; CHECK: (func $just-tee-bad (type $0) (result i32 i32) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local.tee $tuple ;; CHECK-NEXT: (tuple.make @@ -265,7 +265,7 @@ ) ) - ;; CHECK: (func $no-uses (type $0) + ;; CHECK: (func $no-uses (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local $2 i32) @@ -302,7 +302,7 @@ ) ) - ;; CHECK: (func $corruption-tee (type $1) (result i32 i32) + ;; CHECK: (func $corruption-tee (type $0) (result i32 i32) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local.set $tuple @@ -330,4 +330,32 @@ ) (local.get $tuple2) ) + + ;; CHECK: (func $corruption-set (type $0) (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 + ) ) From 8841eed55878474e6b8795e08b0192cb9626e3f7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:56:56 -0700 Subject: [PATCH 24/67] fix --- test/lit/passes/tuple-optimization.wast | 71 +++++++++++++++++++++---- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 30558e90f7f..c568c08706b 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -2,7 +2,7 @@ ;; RUN: wasm-opt %s --tuple-optimization -all -S -o - | filecheck %s (module - ;; CHECK: (func $just-set (type $1) + ;; CHECK: (func $just-set (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $other f64) ;; CHECK-NEXT: (local $2 i32) @@ -38,7 +38,7 @@ ) ) - ;; CHECK: (func $just-get (type $1) + ;; CHECK: (func $just-get (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -63,7 +63,7 @@ ) ) - ;; CHECK: (func $just-get-bad (type $0) (result i32 i32) + ;; CHECK: (func $just-get-bad (type $1) (result i32 i32) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (tuple.extract 0 @@ -94,7 +94,7 @@ (local.get $tuple) ) - ;; CHECK: (func $set-and-gets (type $1) + ;; CHECK: (func $set-and-gets (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -142,7 +142,7 @@ ) ) - ;; CHECK: (func $tee (type $1) + ;; CHECK: (func $tee (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local $2 i32) @@ -213,7 +213,7 @@ ) ) - ;; CHECK: (func $just-tee (type $1) + ;; CHECK: (func $just-tee (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -245,7 +245,7 @@ ) ) - ;; CHECK: (func $just-tee-bad (type $0) (result i32 i32) + ;; 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 @@ -265,7 +265,7 @@ ) ) - ;; CHECK: (func $no-uses (type $1) + ;; CHECK: (func $no-uses (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local $2 i32) @@ -302,7 +302,7 @@ ) ) - ;; CHECK: (func $corruption-tee (type $0) (result i32 i32) + ;; 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 @@ -331,7 +331,7 @@ (local.get $tuple2) ) - ;; CHECK: (func $corruption-set (type $0) (result i32 i32) + ;; 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 @@ -358,4 +358,55 @@ ) (local.get $tuple) ;; this changed from $tuple2 ) + + ;; 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) + ) + ) ) From 59ba4026961d26e13113831316c0a0cec54b6096 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:57:37 -0700 Subject: [PATCH 25/67] fix --- test/lit/passes/tuple-optimization.wast | 53 ++++++++++++++++++++----- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index c568c08706b..3f127780ac4 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -2,7 +2,7 @@ ;; RUN: wasm-opt %s --tuple-optimization -all -S -o - | filecheck %s (module - ;; CHECK: (func $just-set (type $0) + ;; CHECK: (func $just-set (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $other f64) ;; CHECK-NEXT: (local $2 i32) @@ -38,7 +38,7 @@ ) ) - ;; CHECK: (func $just-get (type $0) + ;; CHECK: (func $just-get (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -63,7 +63,7 @@ ) ) - ;; CHECK: (func $just-get-bad (type $1) (result i32 i32) + ;; CHECK: (func $just-get-bad (type $0) (result i32 i32) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (tuple.extract 0 @@ -94,7 +94,7 @@ (local.get $tuple) ) - ;; CHECK: (func $set-and-gets (type $0) + ;; CHECK: (func $set-and-gets (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -142,7 +142,7 @@ ) ) - ;; CHECK: (func $tee (type $0) + ;; CHECK: (func $tee (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local $2 i32) @@ -213,7 +213,7 @@ ) ) - ;; CHECK: (func $just-tee (type $0) + ;; CHECK: (func $just-tee (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -245,7 +245,7 @@ ) ) - ;; CHECK: (func $just-tee-bad (type $1) (result i32 i32) + ;; CHECK: (func $just-tee-bad (type $0) (result i32 i32) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local.tee $tuple ;; CHECK-NEXT: (tuple.make @@ -265,7 +265,7 @@ ) ) - ;; CHECK: (func $no-uses (type $0) + ;; CHECK: (func $no-uses (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local $2 i32) @@ -302,7 +302,7 @@ ) ) - ;; CHECK: (func $corruption-tee (type $1) (result i32 i32) + ;; CHECK: (func $corruption-tee (type $0) (result i32 i32) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local.set $tuple @@ -331,7 +331,7 @@ (local.get $tuple2) ) - ;; CHECK: (func $corruption-set (type $1) (result i32 i32) + ;; CHECK: (func $corruption-set (type $0) (result i32 i32) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local.set $tuple @@ -359,7 +359,7 @@ (local.get $tuple) ;; this changed from $tuple2 ) - ;; CHECK: (func $set-after-set (type $0) + ;; CHECK: (func $set-after-set (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local $2 i32) @@ -409,4 +409,35 @@ (local.get $tuple) ) ) + + ;; CHECK: (func $corruption-first-set (type $0) (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)) + ;; We can optimize both these tuples. + (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) + ) ) From 4cfb2bbc1dd55384ca2fb382ece7afd183cfa6b4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 12:58:30 -0700 Subject: [PATCH 26/67] fix --- test/lit/passes/tuple-optimization.wast | 31 ++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 3f127780ac4..2fcc8128205 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -356,7 +356,7 @@ ) ) ) - (local.get $tuple) ;; this changed from $tuple2 + (local.get $tuple) ;; this changed from $tuple2; same outcome. ) ;; CHECK: (func $set-after-set (type $1) @@ -440,4 +440,33 @@ ;; This local.get prevents both locals from being optimized. (local.get $tuple) ) + + ;; CHECK: (func $corruption-second-set (type $0) (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. + ) ) From 180a98e6062394b46218ced1d0f83d6532728b69 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 13:00:21 -0700 Subject: [PATCH 27/67] fix --- test/lit/passes/tuple-optimization.wast | 34 +++++++++++++++---------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 2fcc8128205..528b77b7ebd 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -4,33 +4,39 @@ (module ;; CHECK: (func $just-set (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) - ;; CHECK-NEXT: (local $other f64) + ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) - ;; CHECK-NEXT: (local $3 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: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $other - ;; CHECK-NEXT: (local.tee $other - ;; CHECK-NEXT: (local.get $other) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $just-set (local $tuple (i32 i32)) - (local $other f64) (local.set $tuple (tuple.make (i32.const 1) (i32.const 2) ) ) + ) + + ;; CHECK: (func $other (type $1) + ;; 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) From 684a01e7661e6ea983feff8af65af1cba04a7335 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 13:01:49 -0700 Subject: [PATCH 28/67] fix --- test/lit/passes/tuple-optimization.wast | 42 ++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 528b77b7ebd..ee0e6061c1c 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -23,27 +23,6 @@ ) ) - ;; CHECK: (func $other (type $1) - ;; 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 $just-get (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) @@ -475,4 +454,25 @@ ) (local.get $tuple2) ;; this changed from $tuple; same outcome. ) + + ;; CHECK: (func $other (type $1) + ;; 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) + ) + ) + ) ) From ed20ca88b30ebcd366fa56232f72ce0d05d7612c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 13:02:52 -0700 Subject: [PATCH 29/67] fix --- test/lit/passes/tuple-optimization.wast | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index ee0e6061c1c..b56246d9d2e 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -475,4 +475,27 @@ ) ) ) + + ;; CHECK: (func $make-extract-no-local (type $1) + ;; 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) + ) + ) + ) + ) ) From 6d1089b3889c92bec750556d8d52f3def876fee2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 13:03:14 -0700 Subject: [PATCH 30/67] fix --- src/passes/TupleOptimization.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index af6e1061367..d378f239228 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -107,15 +107,15 @@ struct TupleOptimization void visitLocalGet(LocalGet* curr) { if (curr->type.isTuple()) { -std::cout << " use++get\n"; +//std::cout << " use++get\n"; uses[curr->index]++; } } void visitLocalSet(LocalSet* curr) { -std::cout << "pre set: " << curr->index <<"\n"; +//std::cout << "pre set: " << curr->index <<"\n"; if (getFunction()->getLocalType(curr->index).isTuple()) { -std::cout << " use++set\n"; +//std::cout << " use++set\n"; 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 @@ -124,17 +124,17 @@ std::cout << " use++set\n"; assert(set->isTee()); validUses[set->index]++; validUses[curr->index]++; -std::cout << " valid++set\n"; +//std::cout << " valid++set\n"; copiedIndexes[set->index].insert(curr->index); copiedIndexes[curr->index].insert(set->index); } else if (auto* get = value->dynCast()) { -std::cout << " valid++get\n"; +//std::cout << " valid++get\n"; validUses[get->index]++; validUses[curr->index]++; copiedIndexes[get->index].insert(curr->index); copiedIndexes[curr->index].insert(get->index); } else if (value->is()) { -std::cout << " valid++make\n"; +//std::cout << " valid++make\n"; validUses[curr->index]++; } } @@ -158,11 +158,11 @@ std::cout << " valid++make\n"; UniqueDeferredQueue work; for (Index i = 0; i < uses.size(); i++) { -std::cout << "consider " << i << " which has use/valid " << uses[i] << ", " << validUses[i] << "\n"; +//std::cout << "consider " << i << " which has use/valid " << uses[i] << ", " << validUses[i] << "\n"; if (uses[i] > 0 && validUses[i] < uses[i]) { // This is a bad tuple. work.push(i); -std::cout << "bad: " << i <<"\n"; +//std::cout << "bad: " << i <<"\n"; } } @@ -173,7 +173,7 @@ std::cout << "bad: " << i <<"\n"; continue; } bad[i] = true; -std::cout << "badd: " << i <<"\n"; +//std::cout << "badd: " << i <<"\n"; for (auto target : copiedIndexes[i]) { work.push(target); } @@ -186,7 +186,7 @@ std::cout << "badd: " << i <<"\n"; if (uses[i] > 0 && !bad[i]) { good[i] = true; hasGood = true; -std::cout << "good: " << i <<"\n"; +//std::cout << "good: " << i <<"\n"; } } @@ -273,7 +273,7 @@ std::cout << "good: " << i <<"\n"; std::unordered_map teeReplacements; void visitLocalSet(LocalSet* curr) { -std::cout << "set " << curr->index << '\n'; +//std::cout << "set " << curr->index << '\n'; auto replace = [&](Expression* replacement) { if (curr->isTee()) { teeReplacements[replacement] = curr; @@ -282,13 +282,13 @@ std::cout << "set " << curr->index << '\n'; }; if (auto targetBase = getNewBaseIndex(curr->index)) { -std::cout << " set a\n"; +//std::cout << " set a\n"; Builder builder(*getModule()); auto type = getFunction()->getLocalType(curr->index); auto* value = curr->value; if (auto* make = value->dynCast()) { -std::cout << " set b\n"; +//std::cout << " set b\n"; // Write each of the tuple.make fields into the proper local. std::vector sets; for (Index i = 0; i < type.size(); i++) { @@ -303,14 +303,14 @@ std::cout << " set b\n"; auto iter = teeReplacements.find(value); if (iter != teeReplacements.end()) { -std::cout << " set c\n"; +//std::cout << " set c\n"; // The input to us was a tee that has been replaced. The actual value // we read from (the tee) can be found in teeReplacements. Also, we // need to keep around the replacement of the tee. contents.push_back(value); value = iter->second; } -std::cout << " set d\n"; +//std::cout << " set d\n"; // This is a copy of a tuple local into another. Copy all the fields // between them. @@ -345,7 +345,7 @@ std::cout << " set d\n"; Index sourceBase = getSetOrGetBaseIndex(value); Builder builder(*getModule()); auto i = curr->index; -std::cout << type << " : " << i << '\n'; +//std::cout << type << " : " << i << '\n'; auto* get = builder.makeLocalGet(sourceBase + i, type[i]); if (extraContents) { replaceCurrent(builder.makeSequence(extraContents, get)); From 561a02aafb73ad7a5e17f3f4fcd483f578c59a57 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 13:05:54 -0700 Subject: [PATCH 31/67] work --- scripts/fuzz_opt.py | 5 +++++ test/lit/help/wasm-opt.test | 2 ++ test/lit/help/wasm2js.test | 2 ++ 3 files changed, 9 insertions(+) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index c504118316c..a38b2a977a0 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1544,6 +1544,11 @@ def write_commands(commands, filename): ("--simplify-locals-notee",), ("--simplify-locals-notee-nostructure",), ("--ssa",), + ("--tuple-optimization"), + ("--tuple-optimization"), + ("--tuple-optimization"), + ("--tuple-optimization"), + ("--tuple-optimization"), ("--type-refining",), ("--type-merging",), ("--type-ssa",), diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 681128b03d9..563a885f0bf 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 1e34b3617f5..54a593b750b 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: From f0a4abd2aaceb749a6d37d4620339a5bbf543961 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 13:14:24 -0700 Subject: [PATCH 32/67] test --- test/lit/passes/tuple-optimization.wast | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index b56246d9d2e..eef6479f311 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -498,4 +498,28 @@ ) ) ) + + ;; CHECK: (func $set-of-block (type $1) + ;; 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) + ) + ) + ) + ) ) From 7a843b6040d3ca6a5b7e88a91890e282ca4a9ab4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 14:10:08 -0700 Subject: [PATCH 33/67] work --- src/passes/TupleOptimization.cpp | 10 ++++-- test/lit/ctor-eval/multivalue-local.wast | 11 +------ test/lit/passes/tuple-optimization.wast | 41 ++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index d378f239228..45a3d108ee1 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -242,7 +242,8 @@ struct TupleOptimization } // Given a local.get or local.set, return the new base index for the local - // index used there. + // index used there. Returns 0 (an impossible value, as the base index + // cannot be 0 - tuple locals exist, so 0 was taken) otherwise. Index getSetOrGetBaseIndex(Expression* setOrGet) { Index index; if (auto* set = setOrGet->dynCast()) { @@ -250,7 +251,7 @@ struct TupleOptimization } else if (auto* get = setOrGet->dynCast()) { index = get->index; } else { - WASM_UNREACHABLE("bad set child"); + return 0; } // This must always be called on a local that is being mapped. @@ -316,6 +317,7 @@ struct TupleOptimization // between them. // TODO: test a tee chain. Index sourceBase = getSetOrGetBaseIndex(value); + assert(sourceBase); for (Index i = 0; i < type.size(); i++) { auto* get = builder.makeLocalGet(sourceBase + i, type[i]); @@ -343,6 +345,10 @@ struct TupleOptimization } Index sourceBase = getSetOrGetBaseIndex(value); + if (!sourceBase) { + return; + } + Builder builder(*getModule()); auto i = curr->index; //std::cout << type << " : " << i << '\n'; diff --git a/test/lit/ctor-eval/multivalue-local.wast b/test/lit/ctor-eval/multivalue-local.wast index f83d3ea86f6..0f35dbd618c 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/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index eef6479f311..fc720ed9f9a 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -499,6 +499,47 @@ ) ) + ;; CHECK: (func $make-extract-no-local-but-other (type $1) + ;; 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 + ;; As above, but now there is an unrelated local as well that can be + ;; optimized. + (local $tuple (i32 i32)) + (local.set $tuple + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + (drop + (tuple.extract 0 + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) + ) + ;; CHECK: (func $set-of-block (type $1) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local.set $tuple From d7c6a09b737693edab5e2b09554468104b0a55ce Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 14:11:34 -0700 Subject: [PATCH 34/67] work --- test/lit/passes/tuple-optimization.wast | 83 ++++++++++++++++++++----- 1 file changed, 66 insertions(+), 17 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index fc720ed9f9a..06f3ba739fd 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -2,7 +2,7 @@ ;; RUN: wasm-opt %s --tuple-optimization -all -S -o - | filecheck %s (module - ;; CHECK: (func $just-set (type $1) + ;; CHECK: (func $just-set (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -23,7 +23,7 @@ ) ) - ;; CHECK: (func $just-get (type $1) + ;; CHECK: (func $just-get (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -48,7 +48,7 @@ ) ) - ;; CHECK: (func $just-get-bad (type $0) (result i32 i32) + ;; CHECK: (func $just-get-bad (type $1) (result i32 i32) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (tuple.extract 0 @@ -79,7 +79,7 @@ (local.get $tuple) ) - ;; CHECK: (func $set-and-gets (type $1) + ;; CHECK: (func $set-and-gets (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -127,7 +127,7 @@ ) ) - ;; CHECK: (func $tee (type $1) + ;; CHECK: (func $tee (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local $2 i32) @@ -198,7 +198,7 @@ ) ) - ;; CHECK: (func $just-tee (type $1) + ;; CHECK: (func $just-tee (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -230,7 +230,7 @@ ) ) - ;; CHECK: (func $just-tee-bad (type $0) (result i32 i32) + ;; 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 @@ -250,7 +250,7 @@ ) ) - ;; CHECK: (func $no-uses (type $1) + ;; CHECK: (func $no-uses (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local $2 i32) @@ -287,7 +287,7 @@ ) ) - ;; CHECK: (func $corruption-tee (type $0) (result i32 i32) + ;; 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 @@ -316,7 +316,7 @@ (local.get $tuple2) ) - ;; CHECK: (func $corruption-set (type $0) (result i32 i32) + ;; 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 @@ -344,7 +344,7 @@ (local.get $tuple) ;; this changed from $tuple2; same outcome. ) - ;; CHECK: (func $set-after-set (type $1) + ;; CHECK: (func $set-after-set (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local $tuple2 (i32 i32)) ;; CHECK-NEXT: (local $2 i32) @@ -395,7 +395,7 @@ ) ) - ;; CHECK: (func $corruption-first-set (type $0) (result i32 i32) + ;; 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 @@ -426,7 +426,7 @@ (local.get $tuple) ) - ;; CHECK: (func $corruption-second-set (type $0) (result i32 i32) + ;; 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 @@ -455,7 +455,7 @@ (local.get $tuple2) ;; this changed from $tuple; same outcome. ) - ;; CHECK: (func $other (type $1) + ;; CHECK: (func $other (type $0) ;; CHECK-NEXT: (local $other f64) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local.set $other @@ -476,7 +476,7 @@ ) ) - ;; CHECK: (func $make-extract-no-local (type $1) + ;; CHECK: (func $make-extract-no-local (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (tuple.extract 0 ;; CHECK-NEXT: (tuple.make @@ -499,7 +499,7 @@ ) ) - ;; CHECK: (func $make-extract-no-local-but-other (type $1) + ;; 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) @@ -540,7 +540,7 @@ ) ) - ;; CHECK: (func $set-of-block (type $1) + ;; CHECK: (func $set-of-block (type $0) ;; CHECK-NEXT: (local $tuple (i32 i32)) ;; CHECK-NEXT: (local.set $tuple ;; CHECK-NEXT: (block (result i32 i32) @@ -563,4 +563,53 @@ ) ) ) + + ;; 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) + ) + ) + (local.set $tuple + (unreachable) + ) + (drop + (tuple.extract 0 + (unreachable) + ) + ) + (drop + (tuple.extract 1 + (local.tee $tuple + (unreachable) + ) + ) + ) + ) ) From 8c90cd12b6b6203b4daf8fdd8a35b627fffbb993 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 14:13:12 -0700 Subject: [PATCH 35/67] work --- scripts/fuzz_opt.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index a38b2a977a0..9b2a3e563b4 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1544,11 +1544,11 @@ def write_commands(commands, filename): ("--simplify-locals-notee",), ("--simplify-locals-notee-nostructure",), ("--ssa",), - ("--tuple-optimization"), - ("--tuple-optimization"), - ("--tuple-optimization"), - ("--tuple-optimization"), - ("--tuple-optimization"), + ("--tuple-optimization",), + ("--tuple-optimization",), + ("--tuple-optimization",), + ("--tuple-optimization",), + ("--tuple-optimization",), ("--type-refining",), ("--type-merging",), ("--type-ssa",), From 6ad33214cc5ca2c93c393f6cadea2c66641c5f7d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 14:19:51 -0700 Subject: [PATCH 36/67] work --- src/passes/TupleOptimization.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 45a3d108ee1..8b8baa1e294 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -242,8 +242,7 @@ struct TupleOptimization } // Given a local.get or local.set, return the new base index for the local - // index used there. Returns 0 (an impossible value, as the base index - // cannot be 0 - tuple locals exist, so 0 was taken) otherwise. + // index used there. Returns 0 (an impossible value, see above) otherwise. Index getSetOrGetBaseIndex(Expression* setOrGet) { Index index; if (auto* set = setOrGet->dynCast()) { @@ -254,10 +253,7 @@ struct TupleOptimization return 0; } - // This must always be called on a local that is being mapped. - auto base = getNewBaseIndex(index); - assert(base); - return base; + return getNewBaseIndex(index); } // Replacing a local.tee requires some care, since we might have @@ -317,6 +313,9 @@ struct TupleOptimization // between them. // TODO: test a tee chain. Index sourceBase = getSetOrGetBaseIndex(value); + + // The target is being optimized, so the source must be as well, or else + // we were confused earlier and target should not be. assert(sourceBase); for (Index i = 0; i < type.size(); i++) { From d5e67f8312488fd1e268baff9e612b84ba4e1b0a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 14:35:01 -0700 Subject: [PATCH 37/67] work --- scripts/fuzz_opt.py | 2 +- src/passes/TupleOptimization.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 9b2a3e563b4..9050d543fc3 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -218,7 +218,7 @@ def init_important_initial_contents(): os.path.join('lit', 'passes', 'inlining_splitting.wast'), os.path.join('heap-types.wast'), ] - RECENT_DAYS = 30 + RECENT_DAYS = 2 # Returns the list of test wast/wat files added or modified within the # RECENT_DAYS number of days counting from the commit time of HEAD diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 8b8baa1e294..447929415c3 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -101,6 +101,8 @@ struct TupleOptimization // Walk the code to collect info. super::doWalkFunction(func); +//std::cout << "opt:" << *func << "\n"; + // Analyze and optimize. optimize(func); } From cff93f2fb3a11c7e997bb4a502df651e9cb26854 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 14:40:49 -0700 Subject: [PATCH 38/67] test --- src/passes/TupleOptimization.cpp | 1 - test/lit/passes/tuple-optimization.wast | 60 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 447929415c3..b4d5f511e0f 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -313,7 +313,6 @@ struct TupleOptimization // This is a copy of a tuple local into another. Copy all the fields // between them. - // TODO: test a tee chain. Index sourceBase = getSetOrGetBaseIndex(value); // The target is being optimized, so the source must be as well, or else diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 06f3ba739fd..f99bc786186 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -612,4 +612,64 @@ ) ) ) + + ;; 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) + ) + ) + ) + ) + ) + ) + ) ) From f254771e97cb09498a473b349734ec74a6ca55b4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:19:14 -0700 Subject: [PATCH 39/67] clean --- src/passes/TupleOptimization.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index b4d5f511e0f..2521aba74c9 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -101,23 +101,18 @@ struct TupleOptimization // Walk the code to collect info. super::doWalkFunction(func); -//std::cout << "opt:" << *func << "\n"; - // Analyze and optimize. optimize(func); } void visitLocalGet(LocalGet* curr) { if (curr->type.isTuple()) { -//std::cout << " use++get\n"; uses[curr->index]++; } } void visitLocalSet(LocalSet* curr) { -//std::cout << "pre set: " << curr->index <<"\n"; if (getFunction()->getLocalType(curr->index).isTuple()) { -//std::cout << " use++set\n"; 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 @@ -126,17 +121,14 @@ struct TupleOptimization assert(set->isTee()); validUses[set->index]++; validUses[curr->index]++; -//std::cout << " valid++set\n"; copiedIndexes[set->index].insert(curr->index); copiedIndexes[curr->index].insert(set->index); } else if (auto* get = value->dynCast()) { -//std::cout << " valid++get\n"; validUses[get->index]++; validUses[curr->index]++; copiedIndexes[get->index].insert(curr->index); copiedIndexes[curr->index].insert(get->index); } else if (value->is()) { -//std::cout << " valid++make\n"; validUses[curr->index]++; } } @@ -160,11 +152,9 @@ struct TupleOptimization UniqueDeferredQueue work; for (Index i = 0; i < uses.size(); i++) { -//std::cout << "consider " << i << " which has use/valid " << uses[i] << ", " << validUses[i] << "\n"; if (uses[i] > 0 && validUses[i] < uses[i]) { // This is a bad tuple. work.push(i); -//std::cout << "bad: " << i <<"\n"; } } @@ -175,7 +165,6 @@ struct TupleOptimization continue; } bad[i] = true; -//std::cout << "badd: " << i <<"\n"; for (auto target : copiedIndexes[i]) { work.push(target); } @@ -188,7 +177,6 @@ struct TupleOptimization if (uses[i] > 0 && !bad[i]) { good[i] = true; hasGood = true; -//std::cout << "good: " << i <<"\n"; } } @@ -272,7 +260,6 @@ struct TupleOptimization std::unordered_map teeReplacements; void visitLocalSet(LocalSet* curr) { -//std::cout << "set " << curr->index << '\n'; auto replace = [&](Expression* replacement) { if (curr->isTee()) { teeReplacements[replacement] = curr; @@ -281,13 +268,11 @@ struct TupleOptimization }; if (auto targetBase = getNewBaseIndex(curr->index)) { -//std::cout << " set a\n"; Builder builder(*getModule()); auto type = getFunction()->getLocalType(curr->index); auto* value = curr->value; if (auto* make = value->dynCast()) { -//std::cout << " set b\n"; // Write each of the tuple.make fields into the proper local. std::vector sets; for (Index i = 0; i < type.size(); i++) { @@ -302,14 +287,12 @@ struct TupleOptimization auto iter = teeReplacements.find(value); if (iter != teeReplacements.end()) { -//std::cout << " set c\n"; // The input to us was a tee that has been replaced. The actual value // we read from (the tee) can be found in teeReplacements. Also, we // need to keep around the replacement of the tee. contents.push_back(value); value = iter->second; } -//std::cout << " set d\n"; // This is a copy of a tuple local into another. Copy all the fields // between them. @@ -351,7 +334,6 @@ struct TupleOptimization Builder builder(*getModule()); auto i = curr->index; -//std::cout << type << " : " << i << '\n'; auto* get = builder.makeLocalGet(sourceBase + i, type[i]); if (extraContents) { replaceCurrent(builder.makeSequence(extraContents, get)); From 73f65e19613850364c5339757b55a45b9b754c53 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:19:31 -0700 Subject: [PATCH 40/67] clean --- scripts/fuzz_opt.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 9050d543fc3..7b8688c5e67 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -218,7 +218,7 @@ def init_important_initial_contents(): os.path.join('lit', 'passes', 'inlining_splitting.wast'), os.path.join('heap-types.wast'), ] - RECENT_DAYS = 2 + RECENT_DAYS = 30 # Returns the list of test wast/wat files added or modified within the # RECENT_DAYS number of days counting from the commit time of HEAD @@ -1545,10 +1545,6 @@ def write_commands(commands, filename): ("--simplify-locals-notee-nostructure",), ("--ssa",), ("--tuple-optimization",), - ("--tuple-optimization",), - ("--tuple-optimization",), - ("--tuple-optimization",), - ("--tuple-optimization",), ("--type-refining",), ("--type-merging",), ("--type-ssa",), From a402d780eb06b0fe9c3c085863fd8830b1c200a7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:21:04 -0700 Subject: [PATCH 41/67] format --- src/passes/TupleOptimization.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 2521aba74c9..dc19e377147 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -46,9 +46,7 @@ namespace wasm { -struct TupleOptimization - : public WalkerPass< - PostWalker> { +struct TupleOptimization : public WalkerPass> { bool isFunctionParallel() override { return true; } std::unique_ptr create() override { @@ -218,7 +216,8 @@ struct TupleOptimization struct MapApplier : public PostWalker { std::unordered_map& tupleToNewBaseMap; - MapApplier(std::unordered_map& tupleToNewBaseMap) : tupleToNewBaseMap(tupleToNewBaseMap) {} + MapApplier(std::unordered_map& 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 From 1ceb35af13bbee2b6ee1f29512c115783e3f2494 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:22:41 -0700 Subject: [PATCH 42/67] better --- src/passes/TupleOptimization.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index dc19e377147..9f0193a9090 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -32,11 +32,16 @@ // // * They are always written either a tuple.make or another tuple local with // these properties. -// * They are always used either in tuple.extract or to be copied to another +// * 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. +// 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 From 794e4111c8b4d3fa5e8d9de644c4c8870f59376e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:22:47 -0700 Subject: [PATCH 43/67] better --- src/passes/TupleOptimization.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 9f0193a9090..ba6d292703f 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -58,8 +58,6 @@ struct TupleOptimization : public WalkerPass> { 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). From 0749fd8deae63ae678c99fd886654b91eed6fb5b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:23:14 -0700 Subject: [PATCH 44/67] better --- src/passes/TupleOptimization.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index ba6d292703f..27c1a82e454 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -59,7 +59,7 @@ struct TupleOptimization : public WalkerPass> { } // 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 + // 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 uses; From 6b7631b95e22394f2af85dbed6904a48778e1d9c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:23:38 -0700 Subject: [PATCH 45/67] better --- src/passes/TupleOptimization.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 27c1a82e454..34931343fd1 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -72,8 +72,10 @@ struct TupleOptimization : public WalkerPass> { // 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, - // and x \in copiedIndexed[y] <==> y \in copiedIndexed[x] + // This is a symmetrical map, that is, we consider copies to work both ways: + // + // x \in copiedIndexed[y] <==> y \in copiedIndexed[x] + // std::vector> copiedIndexes; void doWalkFunction(Function* func) { From 4aad2b48845553443f5fe7928f314fab2712b387 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:25:56 -0700 Subject: [PATCH 46/67] better --- src/passes/TupleOptimization.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 34931343fd1..2011dd1e9b5 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -116,6 +116,8 @@ struct TupleOptimization : public WalkerPass> { 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 From 09bd7f735524a43ef0ec9ba8057a41a51224cffd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:26:30 -0700 Subject: [PATCH 47/67] better --- src/passes/TupleOptimization.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 2011dd1e9b5..572640cd11a 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -120,6 +120,7 @@ struct TupleOptimization : public WalkerPass> { // 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* set = value->dynCast()) { From 29fbf3b21af26e5d0f491a9406e8a81f39571ff7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:27:28 -0700 Subject: [PATCH 48/67] better --- src/passes/TupleOptimization.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 572640cd11a..489aa48aae3 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -152,12 +152,13 @@ struct TupleOptimization : public WalkerPass> { void optimize(Function* func) { auto numLocals = func->getNumLocals(); - // Find the set of bad indexes. We each each such candidate to a worklist + // 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 bad(numLocals); UniqueDeferredQueue 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); From 67de1353a0c888baf5608d26954a65fc838fbd7d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:32:43 -0700 Subject: [PATCH 49/67] more --- test/lit/passes/tuple-optimization.wast | 163 ++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index f99bc786186..fc60e88227d 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -672,4 +672,167 @@ ) ) ) + + ;; CHECK: (func $loop (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 $loop + (local $tuple (i32 i32 i32)) + (local $tuple2 (i32 i32 i32)) + (local $tuple3 (i32 i32 i32)) + (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 $loop-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 $loop-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 + ) ) From 89cf065410020bbc63fae9623a6f5dc92507c09f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:34:39 -0700 Subject: [PATCH 50/67] more --- src/passes/TupleOptimization.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index 489aa48aae3..d3a92aaa29c 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -262,7 +262,7 @@ struct TupleOptimization : public WalkerPass> { // // 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 set (which has been + // 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 teeReplacements; From 3917cac6ba868b17e50e58581e4ef6519458e5fc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:35:21 -0700 Subject: [PATCH 51/67] more --- src/passes/TupleOptimization.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index d3a92aaa29c..edbb4879533 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -307,7 +307,7 @@ struct TupleOptimization : public WalkerPass> { Index sourceBase = getSetOrGetBaseIndex(value); // The target is being optimized, so the source must be as well, or else - // we were confused earlier and target should not be. + // we were confused earlier and the target should not be. assert(sourceBase); for (Index i = 0; i < type.size(); i++) { From e5bd82b250f6baea97f8d6ef22273795618ce0d6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:37:24 -0700 Subject: [PATCH 52/67] more --- test/lit/passes/tuple-optimization.wast | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index fc60e88227d..6c72caddf98 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -15,6 +15,8 @@ ;; 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) From 0d34707147f486bad82a4f7d55c3a9134e161c12 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:38:12 -0700 Subject: [PATCH 53/67] more --- test/lit/passes/tuple-optimization.wast | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 6c72caddf98..365f08b8a49 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -38,6 +38,8 @@ ;; 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) From 48af11dcdd99fdd09fcdc945bb19861d4acdc162 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:38:36 -0700 Subject: [PATCH 54/67] more --- test/lit/passes/tuple-optimization.wast | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 365f08b8a49..df1ee1afa16 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -78,8 +78,8 @@ (local.get $tuple) ) ) - ;; This get is not used by something we can handle, so we should not try to - ;; optimize this 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) ) From 60829ba43e74951162a322a4e0af450f5bf23b2e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:40:37 -0700 Subject: [PATCH 55/67] more --- test/lit/passes/tuple-optimization.wast | 1 - 1 file changed, 1 deletion(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index df1ee1afa16..cb052aa57f1 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -416,7 +416,6 @@ (func $corruption-first-set (result i32 i32) (local $tuple (i32 i32)) (local $tuple2 (i32 i32)) - ;; We can optimize both these tuples. (local.set $tuple (tuple.make (i32.const 1) From 3b5ea6a87481e6b27cdcc06520f418c9a90bb069 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:42:56 -0700 Subject: [PATCH 56/67] more --- test/lit/passes/tuple-optimization.wast | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index cb052aa57f1..afe2620aa99 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -524,8 +524,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $make-extract-no-local-but-other - ;; As above, but now there is an unrelated local as well that can be - ;; optimized. (local $tuple (i32 i32)) (local.set $tuple (tuple.make @@ -533,6 +531,8 @@ (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 From 2ece399ffba1d4d58490e13a5b55fefd5b07b247 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:43:41 -0700 Subject: [PATCH 57/67] more --- test/lit/passes/tuple-optimization.wast | 1 + 1 file changed, 1 insertion(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index afe2620aa99..f813ebd1a07 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -599,6 +599,7 @@ (i32.const 2) ) ) + ;; We should not error here, and do nothing. (local.set $tuple (unreachable) ) From 7af5549072a3dadc2cb9c306be2e79149ecdaf5b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:44:57 -0700 Subject: [PATCH 58/67] more --- test/lit/passes/tuple-optimization.wast | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index f813ebd1a07..6e300834b4e 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -733,10 +733,11 @@ ;; CHECK-NEXT: (local.get $11) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $loop + (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) @@ -802,7 +803,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $tuple) ;; CHECK-NEXT: ) - (func $loop-corruption (result i32 i32 i32) + (func $chain-3-corruption (result i32 i32 i32) (local $tuple (i32 i32 i32)) (local $tuple2 (i32 i32 i32)) (local $tuple3 (i32 i32 i32)) From 57c7c43719b4b4ef3f2649fb9b2b915af30418a0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Sep 2023 15:45:11 -0700 Subject: [PATCH 59/67] rename --- test/lit/passes/tuple-optimization.wast | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 6e300834b4e..4fbd3703a36 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -16,7 +16,7 @@ (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. + ;; tuple local itself then has no uses and other passes will remove it. (local.set $tuple (tuple.make (i32.const 1) @@ -677,7 +677,7 @@ ) ) - ;; CHECK: (func $loop (type $0) + ;; 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)) @@ -769,7 +769,7 @@ ) ) - ;; CHECK: (func $loop-corruption (type $2) (result i32 i32 i32) + ;; 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)) From c5388505f3f1b85fc892f5bb15d6cf74955e0fa9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Sep 2023 12:31:21 -0700 Subject: [PATCH 60/67] add.test --- test/lit/passes/tuple-optimization.wast | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 4fbd3703a36..5d5d3ecc6e7 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -840,4 +840,16 @@ ) (local.get $tuple) ;; this was added ) + + (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) + ) + ) ) From 2640b2ae0d5cb1247c3fc05ba887affbb8bf82d4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Sep 2023 12:31:35 -0700 Subject: [PATCH 61/67] Update src/passes/TupleOptimization.cpp Co-authored-by: Thomas Lively --- src/passes/TupleOptimization.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index edbb4879533..c00e4a9dac3 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -123,12 +123,12 @@ struct TupleOptimization : public WalkerPass> { // We need the input to the local to be another such local (from a tee, or // a get), or a tuple.make. - if (auto* set = value->dynCast()) { - assert(set->isTee()); - validUses[set->index]++; + if (auto* tee = value->dynCast()) { + assert(tee->isTee()); + validUses[tee->index]++; validUses[curr->index]++; - copiedIndexes[set->index].insert(curr->index); - copiedIndexes[curr->index].insert(set->index); + copiedIndexes[tee->index].insert(curr->index); + copiedIndexes[curr->index].insert(tee->index); } else if (auto* get = value->dynCast()) { validUses[get->index]++; validUses[curr->index]++; From 9dc8512479c2ec98068fc5966d3d00bca969a9b7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Sep 2023 12:36:43 -0700 Subject: [PATCH 62/67] feedback --- src/passes/TupleOptimization.cpp | 35 +++++++++++++++++--------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index c00e4a9dac3..c77f051aea0 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -198,23 +198,26 @@ struct TupleOptimization : public WalkerPass> { // right after it, depending on the tuple size. std::unordered_map tupleToNewBaseMap; for (Index i = 0; i < good.size(); i++) { - if (good[i]) { - 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; + 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; + } } } From f736d2f77a23abc35f1746b504b55cf97e0bcd4c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Sep 2023 12:38:28 -0700 Subject: [PATCH 63/67] feedback --- src/passes/TupleOptimization.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index c77f051aea0..ff63ba83580 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -268,12 +268,12 @@ struct TupleOptimization : public WalkerPass> { // 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 teeReplacements; + std::unordered_map replacedTees; void visitLocalSet(LocalSet* curr) { auto replace = [&](Expression* replacement) { if (curr->isTee()) { - teeReplacements[replacement] = curr; + replacedTees[replacement] = curr; } replaceCurrent(replacement); }; @@ -296,10 +296,10 @@ struct TupleOptimization : public WalkerPass> { std::vector contents; - auto iter = teeReplacements.find(value); - if (iter != teeReplacements.end()) { + 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 teeReplacements. Also, we + // 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; @@ -325,8 +325,8 @@ struct TupleOptimization : public WalkerPass> { auto* value = curr->tuple; Expression* extraContents = nullptr; - auto iter = teeReplacements.find(value); - if (iter != teeReplacements.end()) { + 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; From 0230bedffbe33de9813fc97b750c121ff1bbf88e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Sep 2023 12:40:36 -0700 Subject: [PATCH 64/67] feedback --- src/passes/pass.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 1e2273cc1df..15e8c1f2611 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -562,6 +562,9 @@ void PassRunner::addDefaultFunctionOptimizationPasses() { 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 From a53d3f531f42a1bfe327c058ba6be4d4e42aba87 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Sep 2023 12:43:10 -0700 Subject: [PATCH 65/67] fix --- src/passes/TupleOptimization.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/passes/TupleOptimization.cpp b/src/passes/TupleOptimization.cpp index ff63ba83580..f4fd0910bd9 100644 --- a/src/passes/TupleOptimization.cpp +++ b/src/passes/TupleOptimization.cpp @@ -218,7 +218,6 @@ struct TupleOptimization : public WalkerPass> { } lastNewIndex = newIndex; } - } } MapApplier mapApplier(tupleToNewBaseMap); From 958ed9a7589f0036840d7638bcab5952828b8e9e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Sep 2023 12:44:54 -0700 Subject: [PATCH 66/67] test --- test/lit/passes/tuple-optimization.wast | 87 +++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 5d5d3ecc6e7..60cea4b1414 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -841,6 +841,21 @@ (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. @@ -852,4 +867,76 @@ (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) + ) + ) + ) ) From 9cd8e92266bac6073f63ffa955eafee3e458b377 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Sep 2023 12:45:36 -0700 Subject: [PATCH 67/67] test --- test/lit/passes/tuple-optimization.wast | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/test/lit/passes/tuple-optimization.wast b/test/lit/passes/tuple-optimization.wast index 60cea4b1414..1788b91e9af 100644 --- a/test/lit/passes/tuple-optimization.wast +++ b/test/lit/passes/tuple-optimization.wast @@ -939,4 +939,71 @@ ) ) ) + + ;; 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) + ) + ) + ) )