summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2017-06-13 16:05:01 -0700
committerGitHub <noreply@github.com>2017-06-13 16:05:01 -0700
commitb5b40c9ab0c35ed74e97a6491e15651382091b2e (patch)
treed2ec6c2006089d8385b850a730af4be936874314
parent61b409bc845f385f1d7ea7ac81d1649b63435828 (diff)
downloadbinaryen-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.h3
-rw-r--r--src/ast/literal-utils.h46
-rw-r--r--src/passes/CMakeLists.txt2
-rw-r--r--src/passes/CoalesceLocals.cpp30
-rw-r--r--src/passes/InstrumentLocals.cpp140
-rw-r--r--src/passes/PickLoadSigns.cpp4
-rw-r--r--src/passes/SSAify.cpp395
-rw-r--r--src/passes/pass.cpp2
-rw-r--r--src/passes/passes.h2
-rw-r--r--src/support/permutations.h55
-rw-r--r--src/tools/tool-utils.h37
-rw-r--r--src/tools/wasm-as.cpp7
-rw-r--r--src/wasm-traversal.h4
-rw-r--r--test/passes/instrument-locals.txt118
-rw-r--r--test/passes/instrument-locals.wast29
-rw-r--r--test/passes/pick-load-signs.txt16
-rw-r--r--test/passes/pick-load-signs.wast16
-rw-r--r--test/passes/ssa.txt694
-rw-r--r--test/passes/ssa.wast315
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
+ )
+)
+