diff options
author | Alon Zakai <alonzakai@gmail.com> | 2017-06-13 16:05:01 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-13 16:05:01 -0700 |
commit | b5b40c9ab0c35ed74e97a6491e15651382091b2e (patch) | |
tree | d2ec6c2006089d8385b850a730af4be936874314 | |
parent | 61b409bc845f385f1d7ea7ac81d1649b63435828 (diff) | |
download | binaryen-b5b40c9ab0c35ed74e97a6491e15651382091b2e.tar.gz binaryen-b5b40c9ab0c35ed74e97a6491e15651382091b2e.tar.bz2 binaryen-b5b40c9ab0c35ed74e97a6491e15651382091b2e.zip |
SSA pass (#1049)
* Add SSA pass which ensures a single assign for each local, except for merged locals where we ensure exactly a single assign from one of the paths leading to that use
* Also add InstrumentLocals pass, useful for debugging locals (similar to InstrumentMemory but for locals)
* Fix a PickLoadSigns bug with tees not being ignored, which was not noticed until now because we ran it on flatter output by default, but the ssa pass uncovered the bug
-rw-r--r-- | src/asm2wasm.h | 3 | ||||
-rw-r--r-- | src/ast/literal-utils.h | 46 | ||||
-rw-r--r-- | src/passes/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/passes/CoalesceLocals.cpp | 30 | ||||
-rw-r--r-- | src/passes/InstrumentLocals.cpp | 140 | ||||
-rw-r--r-- | src/passes/PickLoadSigns.cpp | 4 | ||||
-rw-r--r-- | src/passes/SSAify.cpp | 395 | ||||
-rw-r--r-- | src/passes/pass.cpp | 2 | ||||
-rw-r--r-- | src/passes/passes.h | 2 | ||||
-rw-r--r-- | src/support/permutations.h | 55 | ||||
-rw-r--r-- | src/tools/tool-utils.h | 37 | ||||
-rw-r--r-- | src/tools/wasm-as.cpp | 7 | ||||
-rw-r--r-- | src/wasm-traversal.h | 4 | ||||
-rw-r--r-- | test/passes/instrument-locals.txt | 118 | ||||
-rw-r--r-- | test/passes/instrument-locals.wast | 29 | ||||
-rw-r--r-- | test/passes/pick-load-signs.txt | 16 | ||||
-rw-r--r-- | test/passes/pick-load-signs.wast | 16 | ||||
-rw-r--r-- | test/passes/ssa.txt | 694 | ||||
-rw-r--r-- | test/passes/ssa.wast | 315 |
19 files changed, 1886 insertions, 29 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index b8439bdbf..f6d0443ec 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -1162,6 +1162,9 @@ void Asm2WasmBuilder::processAsm(Ref ast) { } else if (curr[0] == DEFUN) { // function auto* func = processFunction(curr); + if (wasm.getFunctionOrNull(func->name)) { + Fatal() << "duplicate function: " << func->name; + } if (runOptimizationPasses) { optimizingBuilder->addFunction(func); } else { diff --git a/src/ast/literal-utils.h b/src/ast/literal-utils.h new file mode 100644 index 000000000..7e75e8bc8 --- /dev/null +++ b/src/ast/literal-utils.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 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. + */ + +#ifndef wasm_ast_literl_utils_h +#define wasm_ast_literl_utils_h + +#include "wasm.h" + +namespace wasm { + +namespace LiteralUtils { + +inline Expression* makeZero(WasmType type, Module& wasm) { + Literal value; + switch (type) { + case i32: value = Literal(int32_t(0)); break; + case i64: value = Literal(int64_t(0)); break; + case f32: value = Literal(float(0)); break; + case f64: value = Literal(double(0)); break; + default: WASM_UNREACHABLE(); + } + auto* ret = wasm.allocator.alloc<Const>(); + ret->value = value; + ret->type = value.type; + return ret; +} + +} // namespace LiteralUtils + +} // namespace wasm + +#endif // wasm_ast_literl_utils_h + diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 66f86a02f..f1411f190 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -10,6 +10,7 @@ SET(passes_SOURCES LegalizeJSInterface.cpp LocalCSE.cpp LogExecution.cpp + InstrumentLocals.cpp InstrumentMemory.cpp MemoryPacking.cpp MergeBlocks.cpp @@ -32,6 +33,7 @@ SET(passes_SOURCES ReorderLocals.cpp ReorderFunctions.cpp SimplifyLocals.cpp + SSAify.cpp Vacuum.cpp ) ADD_LIBRARY(passes STATIC ${passes_SOURCES}) diff --git a/src/passes/CoalesceLocals.cpp b/src/passes/CoalesceLocals.cpp index 2828c9955..fb81a5981 100644 --- a/src/passes/CoalesceLocals.cpp +++ b/src/passes/CoalesceLocals.cpp @@ -32,6 +32,7 @@ #include "cfg/cfg-traversal.h" #include "wasm-builder.h" #include "support/learning.h" +#include "support/permutations.h" #ifdef CFG_PROFILE #include "support/timing.h" #endif @@ -533,35 +534,6 @@ void CoalesceLocals::pickIndicesFromOrder(std::vector<Index>& order, std::vector } } -// Utilities for operating on permutation vectors - -static std::vector<Index> makeIdentity(Index num) { - std::vector<Index> ret; - ret.resize(num); - for (Index i = 0; i < num; i++) { - ret[i] = i; - } - return ret; -} - -static void setIdentity(std::vector<Index>& ret) { - auto num = ret.size(); - assert(num > 0); // must already be of the right size - for (Index i = 0; i < num; i++) { - ret[i] = i; - } -} - -static std::vector<Index> makeReversed(std::vector<Index>& original) { - std::vector<Index> ret; - auto num = original.size(); - ret.resize(num); - for (Index i = 0; i < num; i++) { - ret[original[i]] = i; - } - return ret; -} - // given a baseline order, adjust it based on an important order of priorities (higher values // are higher priority). The priorities take precedence, unless they are equal and then // the original order should be kept. diff --git a/src/passes/InstrumentLocals.cpp b/src/passes/InstrumentLocals.cpp new file mode 100644 index 000000000..22b8ebf70 --- /dev/null +++ b/src/passes/InstrumentLocals.cpp @@ -0,0 +1,140 @@ +/* + * Copyright 2017 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. + */ + +// +// Instruments the build with code to intercept all local reads and writes. +// +// gets: +// +// Before: +// (get_local $x) +// +// After: +// (call $get_TYPE +// (i32.const n) // call id +// (i32.const n) // local id +// (get_local $x) +// ) +// +// sets: +// +// Before: +// (set_local $x (i32.const 1)) +// +// After: +// (set_local $x +// (call $set_TYPE +// (i32.const n) // call id +// (i32.const n) // local id +// (i32.const 1) // value +// ) +// ) + +#include <wasm.h> +#include <wasm-builder.h> +#include <pass.h> +#include "shared-constants.h" +#include "asmjs/shared-constants.h" +#include "asm_v_wasm.h" + +namespace wasm { + +Name get_i32("get_i32"); +Name get_i64("get_i64"); +Name get_f32("get_f32"); +Name get_f64("get_f64"); + +Name set_i32("set_i32"); +Name set_i64("set_i64"); +Name set_f32("set_f32"); +Name set_f64("set_f64"); + +struct InstrumentLocals : public WalkerPass<PostWalker<InstrumentLocals>> { + void visitGetLocal(GetLocal* curr) { + Builder builder(*getModule()); + Name import; + switch (curr->type) { + case i32: import = get_i32; break; + case i64: return; // TODO + case f32: import = get_f32; break; + case f64: import = get_f64; break; + default: WASM_UNREACHABLE(); + } + replaceCurrent( + builder.makeCallImport( + import, + { + builder.makeConst(Literal(int32_t(id++))), + builder.makeConst(Literal(int32_t(curr->index))), + curr + }, + curr->type + ) + ); + } + + void visitSetLocal(SetLocal* curr) { + Builder builder(*getModule()); + Name import; + switch (curr->value->type) { + case i32: import = set_i32; break; + case i64: return; // TODO + case f32: import = set_f32; break; + case f64: import = set_f64; break; + case unreachable: return; // nothing to do here + default: WASM_UNREACHABLE(); + } + curr->value = builder.makeCallImport( + import, + { + builder.makeConst(Literal(int32_t(id++))), + builder.makeConst(Literal(int32_t(curr->index))), + curr->value + }, + curr->value->type + ); + } + + void visitModule(Module* curr) { + addImport(curr, get_i32, "iiii"); + addImport(curr, get_i64, "jiij"); + addImport(curr, get_f32, "fiif"); + addImport(curr, get_f64, "diid"); + addImport(curr, set_i32, "iiii"); + addImport(curr, set_i64, "jiij"); + addImport(curr, set_f32, "fiif"); + addImport(curr, set_f64, "diid"); + } + +private: + Index id = 0; + + void addImport(Module* wasm, Name name, std::string sig) { + auto import = new Import; + import->name = name; + import->module = INSTRUMENT; + import->base = name; + import->functionType = ensureFunctionType(sig, wasm)->name; + import->kind = ExternalKind::Function; + wasm->addImport(import); + } +}; + +Pass* createInstrumentLocalsPass() { + return new InstrumentLocals(); +} + +} // namespace wasm diff --git a/src/passes/PickLoadSigns.cpp b/src/passes/PickLoadSigns.cpp index 6ead0cc4e..d7947960c 100644 --- a/src/passes/PickLoadSigns.cpp +++ b/src/passes/PickLoadSigns.cpp @@ -94,6 +94,10 @@ struct PickLoadSigns : public WalkerPass<ExpressionStackWalker<PickLoadSigns>> { } void visitSetLocal(SetLocal* curr) { + if (curr->isTee()) { + // we can't modify a tee, the value is used elsewhere + return; + } if (auto* load = curr->value->dynCast<Load>()) { loads[load] = curr->index; } diff --git a/src/passes/SSAify.cpp b/src/passes/SSAify.cpp new file mode 100644 index 000000000..9c9aeb573 --- /dev/null +++ b/src/passes/SSAify.cpp @@ -0,0 +1,395 @@ +/* + * Copyright 2016 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. + */ + +// +// Transforms code into SSA form. That ensures each variable has a +// single assignment. +// +// Note that "SSA form" usually means SSA + phis. This pass does not +// create phis, we still emit something in our AST, which does not +// have a phi instruction. What we emit when control flow joins +// require more than one input to a value is multiple assignments +// to the same local, with the SSA guarantee that one and only one +// of those assignments will arrive at the uses of that "merge local". +// TODO: consider adding a "proper" phi node to the AST, that passes +// can utilize +// + +#include <iterator> + +#include "wasm.h" +#include "pass.h" +#include "wasm-builder.h" +#include "support/permutations.h" +#include "ast/literal-utils.h" + +namespace wasm { + +// A set we know is impossible / not in the ast +SetLocal IMPOSSIBLE_SET; + +// Tracks assignments to locals, assuming single-assignment form, i.e., +// each assignment creates a new variable. + +struct SSAify : public WalkerPass<PostWalker<SSAify>> { + bool isFunctionParallel() override { return true; } + + Pass* create() override { return new SSAify; } + + // the set_locals relevant for an index or a get. we use + // as set as merges of control flow mean more than 1 may + // be relevant; we create a phi on demand when necessary for those + typedef std::set<SetLocal*> Sets; + + // we map (old local index) => the set_locals for that index. + // a nullptr set means there is a virtual set, from a param + // initial value or the zero init initial value. + typedef std::vector<Sets> Mapping; + + Index numLocals; + Mapping currMapping; + Index nextIndex; + std::vector<Mapping> mappingStack; // used in ifs, loops + std::map<Name, std::vector<Mapping>> breakMappings; // break target => infos that reach it + std::vector<std::vector<GetLocal*>> loopGetStack; // stack of loops, all the gets in each, so we can update them for back branches + std::vector<Expression*> functionPrepends; // things we add to the function prologue + std::map<GetLocal*, Sets> getSetses; // the sets for each get + std::map<GetLocal*, Expression**> getLocations; + + void doWalkFunction(Function* func) { + numLocals = func->getNumLocals(); + if (numLocals == 0) return; // nothing to do + // We begin with each param being assigned from the incoming value, and the zero-init for the locals, + // so the initial state is the identity permutation + currMapping.resize(numLocals); + for (auto& set : currMapping) { + set = { nullptr }; + } + nextIndex = numLocals; + WalkerPass<PostWalker<SSAify>>::walk(func->body); + // apply - we now know the sets for each get + computeGetsAndPhis(); + // add prepends + if (functionPrepends.size() > 0) { + Builder builder(*getModule()); + auto* block = builder.makeBlock(); + for (auto* pre : functionPrepends) { + block->list.push_back(pre); + } + block->list.push_back(func->body); + block->finalize(func->body->type); + func->body = block; + } + } + + // control flow + + void visitBlock(Block* curr) { + if (curr->name.is() && breakMappings.find(curr->name) != breakMappings.end()) { + auto& infos = breakMappings[curr->name]; + infos.emplace_back(std::move(currMapping)); + currMapping = std::move(merge(infos)); + breakMappings.erase(curr->name); + } + } + + void finishIf() { + // that's it for this if, merge + std::vector<Mapping> breaks; + breaks.emplace_back(std::move(currMapping)); + breaks.emplace_back(std::move(mappingStack.back())); + mappingStack.pop_back(); + currMapping = std::move(merge(breaks)); + } + + static void afterIfCondition(SSAify* self, Expression** currp) { + self->mappingStack.push_back(self->currMapping); + } + static void afterIfTrue(SSAify* self, Expression** currp) { + auto* curr = (*currp)->cast<If>(); + if (curr->ifFalse) { + auto afterCondition = std::move(self->mappingStack.back()); + self->mappingStack.back() = std::move(self->currMapping); + self->currMapping = std::move(afterCondition); + } else { + self->finishIf(); + } + } + static void afterIfFalse(SSAify* self, Expression** currp) { + self->finishIf(); + } + static void beforeLoop(SSAify* self, Expression** currp) { + // save the state before entering the loop, for calculation later of the merge at the loop top + self->mappingStack.push_back(self->currMapping); + self->loopGetStack.push_back({}); + } + void visitLoop(Loop* curr) { + if (curr->name.is() && breakMappings.find(curr->name) != breakMappings.end()) { + auto& infos = breakMappings[curr->name]; + infos.emplace_back(std::move(mappingStack.back())); + auto before = infos.back(); + auto& merged = merge(infos); + // every local we created a phi for requires us to update get_local operations in + // the loop - the branch back has means that gets in the loop have potentially + // more sets reaching them. + // we can detect this as follows: if a get of oldIndex has the same sets + // as the sets at the entrance to the loop, then it is affected by the loop + // header sets, and we can add to there sets that looped back + auto linkLoopTop = [&](Index i, Sets& getSets) { + auto& beforeSets = before[i]; + if (getSets.size() < beforeSets.size()) { + // the get trivially has fewer sets, so it overrode the loop entry sets + return; + } + std::vector<SetLocal*> intersection; + std::set_intersection(beforeSets.begin(), beforeSets.end(), + getSets.begin(), getSets.end(), + std::back_inserter(intersection)); + if (intersection.size() < beforeSets.size()) { + // the get has not the same sets as in the loop entry + return; + } + // the get has the entry sets, so add any new ones + for (auto* set : merged[i]) { + getSets.insert(set); + } + }; + auto& gets = loopGetStack.back(); + for (auto* get : gets) { + linkLoopTop(get->index, getSetses[get]); + } + // and the same for the loop fallthrough: any local that still has the + // entry sets should also have the loop-back sets as well + for (Index i = 0; i < numLocals; i++) { + linkLoopTop(i, currMapping[i]); + } + // finally, breaks still in flight must be updated too + for (auto& iter : breakMappings) { + auto name = iter.first; + if (name == curr->name) continue; // skip our own (which is still in use) + auto& mappings = iter.second; + for (auto& mapping : mappings) { + for (Index i = 0; i < numLocals; i++) { + linkLoopTop(i, mapping[i]); + } + } + } + // now that we are done with using the mappings, erase our own + breakMappings.erase(curr->name); + } + mappingStack.pop_back(); + loopGetStack.pop_back(); + } + void visitBreak(Break* curr) { + if (curr->condition) { + breakMappings[curr->name].emplace_back(currMapping); + } else { + breakMappings[curr->name].emplace_back(std::move(currMapping)); + setUnreachable(currMapping); + } + } + void visitSwitch(Switch* curr) { + std::set<Name> all; + for (auto target : curr->targets) { + all.insert(target); + } + all.insert(curr->default_); + for (auto target : all) { + breakMappings[target].emplace_back(currMapping); + } + setUnreachable(currMapping); + } + void visitReturn(Return *curr) { + setUnreachable(currMapping); + } + void visitUnreachable(Unreachable *curr) { + setUnreachable(currMapping); + } + + // local usage + + void visitGetLocal(GetLocal* curr) { + assert(currMapping.size() == numLocals); + assert(curr->index < numLocals); + for (auto& loopGets : loopGetStack) { + loopGets.push_back(curr); + } + // current sets are our sets + getSetses[curr] = currMapping[curr->index]; + getLocations[curr] = getCurrentPointer(); + } + void visitSetLocal(SetLocal* curr) { + assert(currMapping.size() == numLocals); + assert(curr->index < numLocals); + // current sets are just this set + currMapping[curr->index] = { curr }; // TODO optimize? + curr->index = addLocal(getFunction()->getLocalType(curr->index)); + } + + // traversal + + static void scan(SSAify* self, Expression** currp) { + if (auto* iff = (*currp)->dynCast<If>()) { + // if needs special handling + if (iff->ifFalse) { + self->pushTask(SSAify::afterIfFalse, currp); + self->pushTask(SSAify::scan, &iff->ifFalse); + } + self->pushTask(SSAify::afterIfTrue, currp); + self->pushTask(SSAify::scan, &iff->ifTrue); + self->pushTask(SSAify::afterIfCondition, currp); + self->pushTask(SSAify::scan, &iff->condition); + } else { + WalkerPass<PostWalker<SSAify>>::scan(self, currp); + } + + // loops need pre-order visiting too + if ((*currp)->is<Loop>()) { + self->pushTask(SSAify::beforeLoop, currp); + } + } + + // helpers + + void setUnreachable(Mapping& mapping) { + mapping.resize(numLocals); // may have been emptied by a move + mapping[0].clear(); + } + + bool isUnreachable(Mapping& mapping) { + // we must have some set for each index, if only the zero init, so empty means we emptied it for unreachable code + return mapping[0].empty(); + } + + // merges a bunch of infos into one. + // if we need phis, writes them into the provided vector. the caller should + // ensure those are placed in the right location + Mapping& merge(std::vector<Mapping>& mappings) { + assert(mappings.size() > 0); + auto& out = mappings[0]; + if (mappings.size() == 1) { + return out; + } + // merge into the first + for (Index j = 1; j < mappings.size(); j++) { + auto& other = mappings[j]; + for (Index i = 0; i < numLocals; i++) { + auto& outSets = out[i]; + for (auto* set : other[i]) { + outSets.insert(set); + } + } + } + return out; + } + + // After we traversed it all, we can compute gets and phis + void computeGetsAndPhis() { + for (auto& iter : getSetses) { + auto* get = iter.first; + auto& sets = iter.second; + if (sets.size() == 0) { + continue; // unreachable, ignore + } + if (sets.size() == 1) { + // TODO: add tests for this case + // easy, just one set, use it's index + auto* set = *sets.begin(); + if (set) { + get->index = set->index; + } else { + // no set, assign param or zero + if (getFunction()->isParam(get->index)) { + // leave it, it's fine + } else { + // zero it out + (*getLocations[get]) = LiteralUtils::makeZero(get->type, *getModule()); + } + } + continue; + } + // more than 1 set, need a phi: a new local written to at each of the sets + // if there is already a local with that property, reuse it + auto gatherIndexes = [](SetLocal* set) { + std::set<Index> ret; + while (set) { + ret.insert(set->index); + set = set->value->dynCast<SetLocal>(); + } + return ret; + }; + auto indexes = gatherIndexes(*sets.begin()); + for (auto* set : sets) { + if (set == *sets.begin()) continue; + auto currIndexes = gatherIndexes(set); + std::vector<Index> intersection; + std::set_intersection(indexes.begin(), indexes.end(), + currIndexes.begin(), currIndexes.end(), + std::back_inserter(intersection)); + indexes.clear(); + if (intersection.empty()) break; + // TODO: or keep sorted vectors? + for (Index i : intersection) { + indexes.insert(i); + } + } + if (!indexes.empty()) { + // we found an index, use it + get->index = *indexes.begin(); + } else { + // we need to create a local for this phi'ing + auto new_ = addLocal(get->type); + auto old = get->index; + get->index = new_; + Builder builder(*getModule()); + // write to the local in each of our sets + for (auto* set : sets) { + if (set) { + // a set exists, just add a tee of its value + set->value = builder.makeTeeLocal( + new_, + set->value + ); + } else { + // this is a param or the zero init value. + if (getFunction()->isParam(old)) { + // we add a set with the proper + // param value at the beginning of the function + auto* set = builder.makeSetLocal( + new_, + builder.makeGetLocal(old, getFunction()->getLocalType(old)) + ); + functionPrepends.push_back(set); + } else { + // this is a zero init, so we don't need to do anything actually + } + } + } + } + } + } + + Index addLocal(WasmType type) { + return Builder::addVar(getFunction(), type); + } +}; + +Pass *createSSAifyPass() { + return new SSAify(); +} + +} // namespace wasm + diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 2fb0d5c3b..f494378cb 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -75,6 +75,7 @@ void PassRegistry::registerPasses() { registerPass("legalize-js-interface", "legalizes i64 types on the import/export boundary", createLegalizeJSInterfacePass); registerPass("local-cse", "common subexpression elimination inside basic blocks", createLocalCSEPass); registerPass("log-execution", "instrument the build with logging of where execution goes", createLogExecutionPass); + registerPass("instrument-locals", "instrument the build with code to intercept all loads and stores", createInstrumentLocalsPass); registerPass("instrument-memory", "instrument the build with code to intercept all loads and stores", createInstrumentMemoryPass); registerPass("memory-packing", "packs memory into separate segments, skipping zeros", createMemoryPackingPass); registerPass("merge-blocks", "merges blocks to their parents", createMergeBlocksPass); @@ -101,6 +102,7 @@ void PassRegistry::registerPasses() { registerPass("simplify-locals-notee", "miscellaneous locals-related optimizations", createSimplifyLocalsNoTeePass); registerPass("simplify-locals-nostructure", "miscellaneous locals-related optimizations", createSimplifyLocalsNoStructurePass); registerPass("simplify-locals-notee-nostructure", "miscellaneous locals-related optimizations", createSimplifyLocalsNoTeeNoStructurePass); + registerPass("ssa", "ssa-ify variables so that they have a single assignment", createSSAifyPass); registerPass("vacuum", "removes obviously unneeded code", createVacuumPass); registerPass("precompute", "computes compile-time evaluatable expressions", createPrecomputePass); // registerPass("lower-i64", "lowers i64 into pairs of i32s", createLowerInt64Pass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 6322aad3c..e1ba286ad 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -34,6 +34,7 @@ Pass *createInliningPass(); Pass *createLegalizeJSInterfacePass(); Pass *createLocalCSEPass(); Pass *createLogExecutionPass(); +Pass *createInstrumentLocalsPass(); Pass *createInstrumentMemoryPass(); Pass *createMemoryPackingPass(); Pass *createMergeBlocksPass(); @@ -59,6 +60,7 @@ Pass *createSimplifyLocalsPass(); Pass *createSimplifyLocalsNoTeePass(); Pass *createSimplifyLocalsNoStructurePass(); Pass *createSimplifyLocalsNoTeeNoStructurePass(); +Pass *createSSAifyPass(); Pass *createVacuumPass(); Pass *createPrecomputePass(); //Pass *createLowerInt64Pass(); diff --git a/src/support/permutations.h b/src/support/permutations.h new file mode 100644 index 000000000..214063058 --- /dev/null +++ b/src/support/permutations.h @@ -0,0 +1,55 @@ +/* + * Copyright 2016 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. + */ + +// Utilities for operating on permutation vectors + +#ifndef wasm_support_permutations_h +#define wasm_support_permutations_h + +#include "wasm.h" + +namespace wasm { + +inline std::vector<Index> makeIdentity(Index num) { + std::vector<Index> ret; + ret.resize(num); + for (Index i = 0; i < num; i++) { + ret[i] = i; + } + return ret; +} + +inline void setIdentity(std::vector<Index>& ret) { + auto num = ret.size(); + assert(num > 0); // must already be of the right size + for (Index i = 0; i < num; i++) { + ret[i] = i; + } +} + +inline std::vector<Index> makeReversed(std::vector<Index>& original) { + std::vector<Index> ret; + auto num = original.size(); + ret.resize(num); + for (Index i = 0; i < num; i++) { + ret[original[i]] = i; + } + return ret; +} + +} // namespace wasm + +#endif // permutations diff --git a/src/tools/tool-utils.h b/src/tools/tool-utils.h new file mode 100644 index 000000000..a897e01f0 --- /dev/null +++ b/src/tools/tool-utils.h @@ -0,0 +1,37 @@ +/* + * Copyright 2017 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. + */ + +// +// Shared utilities for commandline tools +// + +#include <string> + +namespace wasm { + +// Removes a specific suffix if it is present, otherwise no-op +inline std::string removeSpecificSuffix(std::string str, std::string suffix) { + if (str.size() <= suffix.size()) { + return str; + } + if (str.substr(str.size() - suffix.size()).compare(suffix) == 0) { + return str.substr(0, str.size() - suffix.size()); + } + return str; +} + +} // namespace wasm + diff --git a/src/tools/wasm-as.cpp b/src/tools/wasm-as.cpp index e8003a5ca..d4b495562 100644 --- a/src/tools/wasm-as.cpp +++ b/src/tools/wasm-as.cpp @@ -24,6 +24,8 @@ #include "wasm-binary.h" #include "wasm-s-parser.h" +#include "tool-utils.h" + using namespace cashew; using namespace wasm; @@ -68,6 +70,11 @@ int main(int argc, const char *argv[]) { }); options.parse(argc, argv); + // default output is infile with changed suffix + if (options.extra.find("output") == options.extra.end()) { + options.extra["output"] = removeSpecificSuffix(options.extra["infile"], ".wast") + ".wasm"; + } + auto input(read_file<std::string>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release)); Module wasm; diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index f49407cad..aa8e008ad 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -164,6 +164,10 @@ struct Walker : public VisitorType { return *replacep; } + Expression** getCurrentPointer() { + return replacep; + } + // Get the current module Module* getModule() { return currModule; diff --git a/test/passes/instrument-locals.txt b/test/passes/instrument-locals.txt new file mode 100644 index 000000000..5cc8a98fe --- /dev/null +++ b/test/passes/instrument-locals.txt @@ -0,0 +1,118 @@ +(module + (type $0 (func)) + (type $FUNCSIG$iiii (func (param i32 i32 i32) (result i32))) + (type $FUNCSIG$jiij (func (param i32 i32 i64) (result i64))) + (type $FUNCSIG$fiif (func (param i32 i32 f32) (result f32))) + (type $FUNCSIG$diid (func (param i32 i32 f64) (result f64))) + (import "instrument" "get_i32" (func $get_i32 (param i32 i32 i32) (result i32))) + (import "instrument" "get_i64" (func $get_i64 (param i32 i32 i64) (result i64))) + (import "instrument" "get_f32" (func $get_f32 (param i32 i32 f32) (result f32))) + (import "instrument" "get_f64" (func $get_f64 (param i32 i32 f64) (result f64))) + (import "instrument" "set_i32" (func $set_i32 (param i32 i32 i32) (result i32))) + (import "instrument" "set_i64" (func $set_i64 (param i32 i32 i64) (result i64))) + (import "instrument" "set_f32" (func $set_f32 (param i32 i32 f32) (result f32))) + (import "instrument" "set_f64" (func $set_f64 (param i32 i32 f64) (result f64))) + (memory $0 0) + (func $A (type $0) + (local $x i32) + (local $y i64) + (local $z f32) + (local $w f64) + (drop + (call $get_i32 + (i32.const 0) + (i32.const 0) + (get_local $x) + ) + ) + (drop + (get_local $y) + ) + (drop + (call $get_f32 + (i32.const 1) + (i32.const 2) + (get_local $z) + ) + ) + (drop + (call $get_f64 + (i32.const 2) + (i32.const 3) + (get_local $w) + ) + ) + (drop + (call $get_i32 + (i32.const 3) + (i32.const 0) + (get_local $x) + ) + ) + (drop + (get_local $y) + ) + (drop + (call $get_f32 + (i32.const 4) + (i32.const 2) + (get_local $z) + ) + ) + (drop + (call $get_f64 + (i32.const 5) + (i32.const 3) + (get_local $w) + ) + ) + (set_local $x + (call $set_i32 + (i32.const 6) + (i32.const 0) + (i32.const 1) + ) + ) + (set_local $y + (i64.const 2) + ) + (set_local $z + (call $set_f32 + (i32.const 7) + (i32.const 2) + (f32.const 3.2100000381469727) + ) + ) + (set_local $w + (call $set_f64 + (i32.const 8) + (i32.const 3) + (f64.const 4.321) + ) + ) + (set_local $x + (call $set_i32 + (i32.const 9) + (i32.const 0) + (i32.const 11) + ) + ) + (set_local $y + (i64.const 22) + ) + (set_local $z + (call $set_f32 + (i32.const 10) + (i32.const 2) + (f32.const 33.209999084472656) + ) + ) + (set_local $w + (call $set_f64 + (i32.const 11) + (i32.const 3) + (f64.const 44.321) + ) + ) + ) +) diff --git a/test/passes/instrument-locals.wast b/test/passes/instrument-locals.wast new file mode 100644 index 000000000..b7e34c6d6 --- /dev/null +++ b/test/passes/instrument-locals.wast @@ -0,0 +1,29 @@ +(module + (func $A + (local $x i32) + (local $y i64) + (local $z f32) + (local $w f64) + + (drop (get_local $x)) + (drop (get_local $y)) + (drop (get_local $z)) + (drop (get_local $w)) + + (drop (get_local $x)) + (drop (get_local $y)) + (drop (get_local $z)) + (drop (get_local $w)) + + (set_local $x (i32.const 1)) + (set_local $y (i64.const 2)) + (set_local $z (f32.const 3.21)) + (set_local $w (f64.const 4.321)) + + (set_local $x (i32.const 11)) + (set_local $y (i64.const 22)) + (set_local $z (f32.const 33.21)) + (set_local $w (f64.const 44.321)) + ) +) + diff --git a/test/passes/pick-load-signs.txt b/test/passes/pick-load-signs.txt index 707bb50e9..e8507fa29 100644 --- a/test/passes/pick-load-signs.txt +++ b/test/passes/pick-load-signs.txt @@ -260,4 +260,20 @@ (i32.const 1024) ) ) + (func $tees (type $0) + (local $y i32) + (drop + (tee_local $y + (i32.load8_s + (i32.const 1024) + ) + ) + ) + (drop + (i32.and + (get_local $y) + (i32.const 255) + ) + ) + ) ) diff --git a/test/passes/pick-load-signs.wast b/test/passes/pick-load-signs.wast index 49105e497..33f814926 100644 --- a/test/passes/pick-load-signs.wast +++ b/test/passes/pick-load-signs.wast @@ -257,4 +257,20 @@ (i32.const 1024) ) ) + (func $tees + (local $y i32) + (drop ;; a "use", so we can't alter the value + (tee_local $y + (i32.load8_s + (i32.const 1024) + ) + ) + ) + (drop + (i32.and + (get_local $y) + (i32.const 255) + ) + ) + ) ) diff --git a/test/passes/ssa.txt b/test/passes/ssa.txt new file mode 100644 index 000000000..3d410057c --- /dev/null +++ b/test/passes/ssa.txt @@ -0,0 +1,694 @@ +(module + (type $0 (func (param i32))) + (type $1 (func)) + (memory $0 0) + (func $basics (type $0) (param $x i32) + (local $y i32) + (local $z f32) + (local $w i64) + (local $t f64) + (local $5 i32) + (local $6 f64) + (local $7 f64) + (drop + (get_local $x) + ) + (drop + (i32.const 0) + ) + (drop + (f32.const 0) + ) + (drop + (i64.const 0) + ) + (drop + (f64.const 0) + ) + (set_local $5 + (i32.const 100) + ) + (drop + (get_local $5) + ) + (set_local $6 + (f64.const 2) + ) + (drop + (get_local $6) + ) + (set_local $7 + (f64.const 33) + ) + (drop + (get_local $7) + ) + (drop + (get_local $7) + ) + ) + (func $if (type $0) (param $p i32) + (local $x i32) + (local $y i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (local $7 i32) + (local $8 i32) + (local $9 i32) + (local $10 i32) + (local $11 i32) + (local $12 i32) + (local $13 i32) + (local $14 i32) + (local $15 i32) + (local $16 i32) + (local $17 i32) + (set_local $13 + (get_local $p) + ) + (block + (drop + (if i32 + (i32.const 1) + (i32.const 0) + (i32.const 0) + ) + ) + (if + (i32.const 1) + (set_local $3 + (tee_local $15 + (tee_local $14 + (tee_local $12 + (i32.const 1) + ) + ) + ) + ) + ) + (drop + (get_local $12) + ) + (if + (i32.const 1) + (set_local $4 + (tee_local $13 + (i32.const 1) + ) + ) + ) + (drop + (get_local $13) + ) + (if + (i32.const 1) + (set_local $5 + (tee_local $15 + (tee_local $14 + (i32.const 2) + ) + ) + ) + (nop) + ) + (drop + (get_local $14) + ) + (if + (i32.const 1) + (nop) + (set_local $6 + (tee_local $15 + (i32.const 3) + ) + ) + ) + (drop + (get_local $15) + ) + (if + (i32.const 1) + (set_local $7 + (tee_local $16 + (i32.const 4) + ) + ) + (set_local $8 + (tee_local $16 + (i32.const 5) + ) + ) + ) + (drop + (get_local $16) + ) + (if + (i32.const 1) + (set_local $9 + (tee_local $17 + (i32.const 6) + ) + ) + (block $block + (set_local $10 + (i32.const 7) + ) + (set_local $11 + (tee_local $17 + (i32.const 8) + ) + ) + ) + ) + (drop + (get_local $17) + ) + ) + ) + (func $if2 (type $0) (param $x i32) + (local $1 i32) + (local $2 i32) + (set_local $2 + (get_local $x) + ) + (block + (if + (i32.const 1) + (block $block + (set_local $1 + (tee_local $2 + (i32.const 1) + ) + ) + (drop + (get_local $1) + ) + ) + ) + (drop + (get_local $2) + ) + ) + ) + (func $block (type $0) (param $x i32) + (local $1 i32) + (local $2 i32) + (set_local $2 + (get_local $x) + ) + (block + (block $out + (br_if $out + (i32.const 2) + ) + (set_local $1 + (tee_local $2 + (i32.const 1) + ) + ) + ) + (drop + (get_local $2) + ) + ) + ) + (func $block2 (type $0) (param $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (block $out + (set_local $1 + (tee_local $6 + (i32.const 1) + ) + ) + (drop + (get_local $1) + ) + (br_if $out + (i32.const 2) + ) + (drop + (get_local $1) + ) + (if + (i32.const 3) + (block $block + (set_local $2 + (tee_local $6 + (i32.const 1) + ) + ) + (drop + (get_local $2) + ) + (br $out) + ) + ) + (drop + (get_local $1) + ) + (set_local $3 + (tee_local $6 + (i32.const 4) + ) + ) + (drop + (get_local $3) + ) + (if + (i32.const 5) + (br $out) + ) + (drop + (get_local $3) + ) + (if + (i32.const 6) + (nop) + ) + (if + (i32.const 7) + (nop) + (nop) + ) + (block $in + (set_local $4 + (tee_local $6 + (i32.const 8) + ) + ) + (drop + (get_local $4) + ) + (br_table $in $out + (i32.const 9) + ) + ) + (drop + (get_local $4) + ) + (block $in2 + (set_local $5 + (tee_local $6 + (i32.const 10) + ) + ) + (drop + (get_local $5) + ) + (br_table $out $in2 + (i32.const 11) + ) + ) + (drop + (get_local $5) + ) + ) + (drop + (get_local $6) + ) + ) + (func $loop (type $0) (param $x i32) + (local $1 i32) + (local $2 i32) + (set_local $2 + (get_local $x) + ) + (block + (drop + (get_local $x) + ) + (loop $moar + (drop + (get_local $2) + ) + (set_local $1 + (tee_local $2 + (i32.const 1) + ) + ) + (br_if $moar + (i32.const 2) + ) + ) + (drop + (get_local $1) + ) + ) + ) + (func $loop2 (type $0) (param $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (set_local $4 + (get_local $x) + ) + (block + (drop + (get_local $x) + ) + (loop $moar + (drop + (get_local $4) + ) + (set_local $1 + (i32.const 1) + ) + (drop + (get_local $1) + ) + (set_local $2 + (tee_local $4 + (i32.const 123) + ) + ) + (drop + (get_local $2) + ) + (br_if $moar + (i32.const 2) + ) + (drop + (get_local $2) + ) + (set_local $3 + (i32.const 3) + ) + (drop + (get_local $3) + ) + ) + (drop + (get_local $3) + ) + ) + ) + (func $loop2-zeroinit (type $1) + (local $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (drop + (i32.const 0) + ) + (loop $moar + (drop + (get_local $4) + ) + (set_local $1 + (i32.const 1) + ) + (drop + (get_local $1) + ) + (set_local $2 + (tee_local $4 + (i32.const 123) + ) + ) + (drop + (get_local $2) + ) + (br_if $moar + (i32.const 2) + ) + (drop + (get_local $2) + ) + (set_local $3 + (i32.const 3) + ) + (drop + (get_local $3) + ) + ) + (drop + (get_local $3) + ) + ) + (func $real-loop (type $0) (param $param i32) + (local $loopvar i32) + (local $inc i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (set_local $3 + (tee_local $6 + (get_local $param) + ) + ) + (loop $more + (block $stop + (if + (i32.const 1) + (br $stop) + ) + (set_local $4 + (i32.add + (get_local $6) + (i32.const 1) + ) + ) + (set_local $5 + (tee_local $6 + (get_local $4) + ) + ) + (br $more) + ) + ) + (drop + (get_local $6) + ) + ) + (func $real-loop-outblock (type $0) (param $param i32) + (local $loopvar i32) + (local $inc i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (set_local $3 + (tee_local $6 + (get_local $param) + ) + ) + (block $stop + (loop $more + (if + (i32.const 1) + (br $stop) + ) + (set_local $4 + (i32.add + (get_local $6) + (i32.const 1) + ) + ) + (set_local $5 + (tee_local $6 + (get_local $4) + ) + ) + (br $more) + ) + ) + (drop + (get_local $6) + ) + ) + (func $loop-loop-param (type $0) (param $param i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (set_local $3 + (get_local $param) + ) + (set_local $4 + (get_local $param) + ) + (block + (loop $loop1 + (block $out1 + (if + (get_local $3) + (br $out1) + ) + (set_local $1 + (tee_local $4 + (tee_local $3 + (i32.const 1) + ) + ) + ) + (br $loop1) + ) + ) + (loop $loop2 + (block $out2 + (if + (get_local $4) + (br $out2) + ) + (set_local $2 + (tee_local $4 + (i32.const 2) + ) + ) + (br $loop2) + ) + ) + ) + ) + (func $loop-loop-param-nomerge (type $0) (param $param i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (loop $loop1 + (block $out1 + (set_local $1 + (tee_local $3 + (i32.const 1) + ) + ) + (if + (get_local $1) + (br $out1) + ) + (br $loop1) + ) + ) + (loop $loop2 + (block $out2 + (if + (get_local $3) + (br $out2) + ) + (set_local $2 + (tee_local $3 + (i32.const 2) + ) + ) + (br $loop2) + ) + ) + ) + (func $loop-nesting (type $0) (param $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (set_local $3 + (get_local $x) + ) + (set_local $4 + (get_local $x) + ) + (set_local $5 + (get_local $x) + ) + (block + (block $out + (loop $loop1 + (if + (get_local $3) + (br $out) + ) + (loop $loop2 + (if + (get_local $4) + (br $out) + ) + (set_local $1 + (tee_local $5 + (tee_local $4 + (i32.const 1) + ) + ) + ) + (br $loop2) + ) + (set_local $2 + (tee_local $5 + (tee_local $4 + (tee_local $3 + (i32.const 2) + ) + ) + ) + ) + (br $loop1) + ) + ) + (drop + (get_local $5) + ) + ) + ) + (func $loop-nesting-2 (type $0) (param $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (set_local $3 + (get_local $x) + ) + (set_local $4 + (get_local $x) + ) + (set_local $5 + (get_local $x) + ) + (block + (block $out + (loop $loop1 + (if + (get_local $3) + (br $out) + ) + (loop $loop2 + (if + (get_local $4) + (br $out) + ) + (set_local $1 + (tee_local $5 + (tee_local $4 + (i32.const 1) + ) + ) + ) + (br_if $loop2 + (i32.const 3) + ) + ) + (set_local $2 + (tee_local $5 + (tee_local $4 + (tee_local $3 + (i32.const 2) + ) + ) + ) + ) + (br $loop1) + ) + ) + (drop + (get_local $5) + ) + ) + ) +) diff --git a/test/passes/ssa.wast b/test/passes/ssa.wast new file mode 100644 index 000000000..fe78aecb5 --- /dev/null +++ b/test/passes/ssa.wast @@ -0,0 +1,315 @@ +(module + (func $basics (param $x i32) + (local $y i32) + (local $z f32) + (local $w i64) + (local $t f64) + (drop (get_local $x)) ;; keep as param get + (drop (get_local $y)) ;; turn into get of 0-init + (drop (get_local $z)) + (drop (get_local $w)) + (drop (get_local $t)) + (set_local $x (i32.const 100)) ;; overwrite param + (drop (get_local $x)) ;; no longer a param! + (set_local $t (f64.const 2)) ;; overwrite local + (drop (get_local $t)) + (set_local $t (f64.const 33)) ;; overwrite local AGAIN + (drop (get_local $t)) + (drop (get_local $t)) ;; use twice + ) + (func $if (param $p i32) + (local $x i32) + (local $y i32) + (drop + (if i32 + (i32.const 1) + (get_local $x) + (get_local $y) + ) + ) + (if + (i32.const 1) + (set_local $x (i32.const 1)) + ) + (drop (get_local $x)) + ;; same but with param + (if + (i32.const 1) + (set_local $p (i32.const 1)) + ) + (drop (get_local $p)) + ;; if-else + (if + (i32.const 1) + (set_local $x (i32.const 2)) + (nop) + ) + (drop (get_local $x)) + (if + (i32.const 1) + (nop) + (set_local $x (i32.const 3)) + ) + (drop (get_local $x)) + (if + (i32.const 1) + (set_local $x (i32.const 4)) + (set_local $x (i32.const 5)) + ) + (drop (get_local $x)) + (if + (i32.const 1) + (set_local $x (i32.const 6)) + (block + (set_local $x (i32.const 7)) + (set_local $x (i32.const 8)) + ) + ) + (drop (get_local $x)) + ) + (func $if2 (param $x i32) + (if + (i32.const 1) + (block + (set_local $x (i32.const 1)) + (drop (get_local $x)) ;; use between phi set and use + ) + ) + (drop (get_local $x)) + ) + (func $block (param $x i32) + (block $out + (br_if $out (i32.const 2)) + (set_local $x (i32.const 1)) + ) + (drop (get_local $x)) + ) + (func $block2 (param $x i32) + (block $out + (set_local $x (i32.const 1)) + (drop (get_local $x)) + (br_if $out (i32.const 2)) + (drop (get_local $x)) + (if (i32.const 3) + (block + (set_local $x (i32.const 1)) + (drop (get_local $x)) + (br $out) + ) + ) + (drop (get_local $x)) + (set_local $x (i32.const 4)) + (drop (get_local $x)) + (if (i32.const 5) + (br $out) + ) + (drop (get_local $x)) + (if (i32.const 6) + (nop) + ) + (if (i32.const 7) + (nop) + (nop) + ) + ;; finally, switching + (block $in + (set_local $x (i32.const 8)) + (drop (get_local $x)) + (br_table $in $out (i32.const 9)) + ) + (drop (get_local $x)) + (block $in2 + (set_local $x (i32.const 10)) + (drop (get_local $x)) + (br_table $out $in2 (i32.const 11)) + ) + (drop (get_local $x)) + ) + (drop (get_local $x)) + ) + (func $loop (param $x i32) + (drop (get_local $x)) + (loop $moar + (drop (get_local $x)) + (set_local $x (i32.const 1)) + (br_if $moar (i32.const 2)) + ) + (drop (get_local $x)) + ) + (func $loop2 (param $x i32) + (drop (get_local $x)) + (loop $moar + (drop (get_local $x)) + (set_local $x (i32.const 1)) + (drop (get_local $x)) + (set_local $x (i32.const 123)) + (drop (get_local $x)) + (br_if $moar (i32.const 2)) + (drop (get_local $x)) ;; add use in loop before it ends, we should update this to the phi + (set_local $x (i32.const 3)) + (drop (get_local $x)) ;; another use, but should *not* be phi'd + ) + (drop (get_local $x)) + ) + (func $loop2-zeroinit + (local $x i32) + (drop (get_local $x)) + (loop $moar + (drop (get_local $x)) + (set_local $x (i32.const 1)) + (drop (get_local $x)) + (set_local $x (i32.const 123)) + (drop (get_local $x)) + (br_if $moar (i32.const 2)) + (drop (get_local $x)) ;; add use in loop before it ends, we should update this to the phi + (set_local $x (i32.const 3)) + (drop (get_local $x)) ;; another use, but should *not* be phi'd + ) + (drop (get_local $x)) + ) + (func $real-loop + (param $param i32) + (local $loopvar i32) + (local $inc i32) + (set_local $loopvar + (get_local $param) + ) + (loop $more + (block $stop + (if + (i32.const 1) + (br $stop) + ) + (set_local $inc + (i32.add + (get_local $loopvar) ;; this var should be written to from before the loop and the inc at the end + (i32.const 1) + ) + ) + (set_local $loopvar + (get_local $inc) + ) + (br $more) + ) + ) + (drop (get_local $loopvar)) + ) + (func $real-loop-outblock + (param $param i32) + (local $loopvar i32) + (local $inc i32) + (set_local $loopvar + (get_local $param) + ) + (block $stop + (loop $more + (if + (i32.const 1) + (br $stop) + ) + (set_local $inc + (i32.add + (get_local $loopvar) ;; this var should be written to from before the loop and the inc at the end + (i32.const 1) + ) + ) + (set_local $loopvar + (get_local $inc) + ) + (br $more) + ) + ) + (drop (get_local $loopvar)) + ) + (func $loop-loop-param + (param $param i32) + (loop $loop1 + (block $out1 + (if + (get_local $param) + (br $out1) + ) + (set_local $param (i32.const 1)) + (br $loop1) + ) + ) + (loop $loop2 + (block $out2 + (if + (get_local $param) + (br $out2) + ) + (set_local $param (i32.const 2)) + (br $loop2) + ) + ) + ) + (func $loop-loop-param-nomerge + (param $param i32) + (loop $loop1 + (block $out1 + (set_local $param (i32.const 1)) + (if + (get_local $param) + (br $out1) + ) + (br $loop1) + ) + ) + (loop $loop2 + (block $out2 + (if + (get_local $param) + (br $out2) + ) + (set_local $param (i32.const 2)) + (br $loop2) + ) + ) + ) + (func $loop-nesting + (param $x i32) + (block $out + (loop $loop1 + (if + (get_local $x) + (br $out) + ) + (loop $loop2 + (if + (get_local $x) + (br $out) + ) + (set_local $x (i32.const 1)) + (br $loop2) + ) + (set_local $x (i32.const 2)) + (br $loop1) + ) + ) + (drop (get_local $x)) ;; can receive from either set, or input param + ) + (func $loop-nesting-2 + (param $x i32) + (block $out + (loop $loop1 + (if + (get_local $x) + (br $out) + ) + (loop $loop2 + (if + (get_local $x) + (br $out) + ) + (set_local $x (i32.const 1)) + (br_if $loop2 (i32.const 3)) ;; add fallthrough + ) + (set_local $x (i32.const 2)) + (br $loop1) + ) + ) + (drop (get_local $x)) ;; can receive from either set, or input param + ) +) + |