summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/binaryen-c.cpp13
-rw-r--r--src/binaryen-c.h4
-rw-r--r--src/cfg/Relooper.cpp481
-rw-r--r--src/cfg/Relooper.h32
-rw-r--r--src/ir/ExpressionAnalyzer.cpp10
-rw-r--r--src/ir/hashed.h4
-rw-r--r--src/ir/utils.h2
-rw-r--r--src/js/binaryen.js-post.js175
-rw-r--r--src/passes/ReReloop.cpp13
9 files changed, 619 insertions, 115 deletions
diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp
index 8e9ea8589..b0250a270 100644
--- a/src/binaryen-c.cpp
+++ b/src/binaryen-c.cpp
@@ -2373,12 +2373,13 @@ const char* BinaryenExportGetValue(BinaryenExportRef export_) {
// ========== CFG / Relooper ==========
//
-RelooperRef RelooperCreate(void) {
+RelooperRef RelooperCreate(BinaryenModuleRef module) {
if (tracing) {
- std::cout << " the_relooper = RelooperCreate();\n";
+ std::cout << " the_relooper = RelooperCreate(the_module);\n";
}
- return RelooperRef(new CFG::Relooper());
+ auto* wasm = (Module*)module;
+ return RelooperRef(new CFG::Relooper(wasm));
}
RelooperBlockRef RelooperAddBlock(RelooperRef relooper, BinaryenExpressionRef code) {
@@ -2440,15 +2441,15 @@ void RelooperAddBranchForSwitch(RelooperBlockRef from, RelooperBlockRef to, Bina
fromBlock->AddSwitchBranchTo(toBlock, std::move(values), (Expression*)code);
}
-BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, RelooperBlockRef entry, BinaryenIndex labelHelper, BinaryenModuleRef module) {
+BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, RelooperBlockRef entry, BinaryenIndex labelHelper) {
auto* R = (CFG::Relooper*)relooper;
R->Calculate((CFG::Block*)entry);
- CFG::RelooperBuilder builder(*(Module*)module, labelHelper);
+ CFG::RelooperBuilder builder(*R->Module, labelHelper);
auto* ret = R->Render(builder);
if (tracing) {
auto id = noteExpression(ret);
- std::cout << " expressions[" << id << "] = RelooperRenderAndDispose(the_relooper, relooperBlocks[" << relooperBlocks[entry] << "], " << labelHelper << ", the_module);\n";
+ std::cout << " expressions[" << id << "] = RelooperRenderAndDispose(the_relooper, relooperBlocks[" << relooperBlocks[entry] << "], " << labelHelper << ");\n";
relooperBlocks.clear();
}
diff --git a/src/binaryen-c.h b/src/binaryen-c.h
index 6f244e533..f2027fd1c 100644
--- a/src/binaryen-c.h
+++ b/src/binaryen-c.h
@@ -806,7 +806,7 @@ typedef void* RelooperRef;
typedef void* RelooperBlockRef;
// Create a relooper instance
-RelooperRef RelooperCreate(void);
+RelooperRef RelooperCreate(BinaryenModuleRef module);
// Create a basic block that ends with nothing, or with some simple branching
RelooperBlockRef RelooperAddBlock(RelooperRef relooper, BinaryenExpressionRef code);
@@ -827,7 +827,7 @@ void RelooperAddBranchForSwitch(RelooperBlockRef from, RelooperBlockRef to, Bina
// @param labelHelper To render irreducible control flow, we may need a helper variable to
// guide us to the right target label. This value should be an index of
// an i32 local variable that is free for us to use.
-BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, RelooperBlockRef entry, BinaryenIndex labelHelper, BinaryenModuleRef module);
+BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, RelooperBlockRef entry, BinaryenIndex labelHelper);
//
// ========= Other APIs =========
diff --git a/src/cfg/Relooper.cpp b/src/cfg/Relooper.cpp
index 2387922a8..74fe8e7b5 100644
--- a/src/cfg/Relooper.cpp
+++ b/src/cfg/Relooper.cpp
@@ -23,6 +23,7 @@
#include <stack>
#include <string>
+#include "ir/branch-utils.h"
#include "ir/utils.h"
#include "parsing.h"
@@ -100,7 +101,7 @@ static wasm::Expression* HandleFollowupMultiples(wasm::Expression* Ret, Shape* P
Branch::Branch(wasm::Expression* ConditionInit, wasm::Expression* CodeInit) : Ancestor(nullptr), Condition(ConditionInit), Code(CodeInit) {}
-Branch::Branch(std::vector<wasm::Index>&& ValuesInit, wasm::Expression* CodeInit) : Ancestor(nullptr), Code(CodeInit) {
+Branch::Branch(std::vector<wasm::Index>&& ValuesInit, wasm::Expression* CodeInit) : Ancestor(nullptr), Condition(nullptr), Code(CodeInit) {
if (ValuesInit.size() > 0) {
SwitchValues = wasm::make_unique<std::vector<wasm::Index>>(ValuesInit);
}
@@ -427,7 +428,9 @@ wasm::Expression* LoopShape::Render(RelooperBuilder& Builder, bool InLoop) {
// Relooper
-Relooper::Relooper() : Root(nullptr), MinSize(false), BlockIdCounter(1), ShapeIdCounter(0) { // block ID 0 is reserved for clearings
+Relooper::Relooper(wasm::Module* ModuleInit) :
+ Module(ModuleInit), Root(nullptr), MinSize(false),
+ BlockIdCounter(1), ShapeIdCounter(0) { // block ID 0 is reserved for clearings
}
Relooper::~Relooper() {
@@ -468,9 +471,448 @@ struct Liveness : public RelooperRecursor {
}
};
+typedef std::pair<Branch*, Block*> BranchBlock;
+
+struct Optimizer : public RelooperRecursor {
+ Optimizer(Relooper* Parent) : RelooperRecursor(Parent) {
+ // TODO: there are likely some rare but possible O(N^2) cases with this looping
+ bool More = true;
+#if RELOOPER_OPTIMIZER_DEBUG
+ std::cout << "pre-optimize\n";
+ for (auto* Block : Parent->Blocks) {
+ DebugDump(Block, "pre-block");
+ }
+#endif
+
+ // First, run one-time preparatory passes.
+ CanonicalizeCode();
+
+ // Loop over passes that allow further reduction.
+ while (More) {
+ More = false;
+ More = SkipEmptyBlocks() || More;
+ More = MergeEquivalentBranches() || More;
+ More = UnSwitch() || More;
+ // TODO: Merge identical blocks. This would avoid taking into account their
+ // position / how they are reached, which means that the merging
+ // may add overhead, so we do it carefully:
+ // * Merging a large-enough block is good for size, and we do it
+ // in we are in MinSize mode, which means we can tolerate slightly
+ // slower throughput.
+ // TODO: Fuse a non-empty block with a single successor.
+ }
+
+ // Finally, run one-time final passes.
+ // TODO
+
+#if RELOOPER_OPTIMIZER_DEBUG
+ std::cout << "post-optimize\n";
+ for (auto* Block : Parent->Blocks) {
+ DebugDump(Block, "post-block");
+ }
+#endif
+ }
+
+ // We will be performing code comparisons, so do some basic canonicalization
+ // to avoid things being unequal for silly reasons.
+ void CanonicalizeCode() {
+ for (auto* Block : Parent->Blocks) {
+ Block->Code = Canonicalize(Block->Code);
+ for (auto& iter : Block->BranchesOut) {
+ auto* Branch = iter.second;
+ if (Branch->Code) {
+ Branch->Code = Canonicalize(Branch->Code);
+ }
+ }
+ }
+ }
+
+ // If a branch goes to an empty block which has one target,
+ // and there is no phi or switch to worry us, just skip through.
+ bool SkipEmptyBlocks() {
+ bool Worked = false;
+ for (auto* CurrBlock : Parent->Blocks) {
+ // Generate a new set of branches out TODO optimize
+ BlockBranchMap NewBranchesOut;
+ for (auto& iter : CurrBlock->BranchesOut) {
+ auto* Next = iter.first;
+ auto* NextBranch = iter.second;
+ auto* First = Next;
+ auto* Replacement = First;
+#if RELOOPER_OPTIMIZER_DEBUG
+ std::cout << " maybeskip from " << Block->Id << " to next=" << Next->Id << '\n';
+#endif
+ std::unordered_set<decltype(Replacement)> Seen;
+ while (1) {
+ if (IsEmpty(Next) &&
+ Next->BranchesOut.size() == 1) {
+ auto iter = Next->BranchesOut.begin();
+ Block* NextNext = iter->first;
+ Branch* NextNextBranch = iter->second;
+ assert(!NextNextBranch->Condition && !NextNextBranch->SwitchValues);
+ if (!NextNextBranch->Code) { // TODO: handle extra code too
+ // We can skip through!
+ Next = Replacement = NextNext;
+ // If we've already seen this, stop - it's an infinite loop of empty
+ // blocks we can skip through.
+ if (Seen.count(Replacement)) {
+ // Stop here. Note that if we started from X and ended up with X once
+ // more, then Replacement == First and so lower down we will not
+ // report that we did any work, avoiding an infinite loop due to
+ // always thinking there is more work to do.
+ break;
+ } else {
+ // Otherwise, keep going.
+ Seen.insert(Replacement);
+ continue;
+ }
+ }
+ }
+ break;
+ }
+ if (Replacement != First) {
+#if RELOOPER_OPTIMIZER_DEBUG
+ std::cout << " skip to replacement! " << CurrBlock->Id << " -> " << First->Id << " -> " << Replacement->Id << '\n';
+#endif
+ Worked = true;
+ }
+ // Add a branch to the target (which may be the unchanged original) in the set of new branches.
+ // If it's a replacement, it may collide, and we need to merge.
+ if (NewBranchesOut.count(Replacement)) {
+#if RELOOPER_OPTIMIZER_DEBUG
+ std::cout << " merge\n";
+#endif
+ MergeBranchInto(NextBranch, NewBranchesOut[Replacement]);
+ } else {
+ NewBranchesOut[Replacement] = NextBranch;
+ }
+ }
+ CurrBlock->BranchesOut.swap(NewBranchesOut); // FIXME do we leak old unused Branches?
+ }
+ return Worked;
+ }
+
+ // Our IR has one Branch from each block to one of its targets, so there
+ // is nothing to reduce there, but different targets may in fact be
+ // equivalent in their *contents*.
+ bool MergeEquivalentBranches() {
+ bool Worked = false;
+ for (auto* ParentBlock : Parent->Blocks) {
+#if RELOOPER_OPTIMIZER_DEBUG
+ std::cout << "at parent " << ParentBlock->Id << '\n';
+#endif
+ if (ParentBlock->BranchesOut.size() >= 2) {
+ std::unordered_map<wasm::HashType, std::vector<BranchBlock>> HashedBranchesOut;
+ std::vector<Block*> BlocksToErase;
+ for (auto& iter : ParentBlock->BranchesOut) {
+ Block* CurrBlock = iter.first;
+#if RELOOPER_OPTIMIZER_DEBUG
+ std::cout << " consider child " << CurrBlock->Id << '\n';
+#endif
+ Branch* CurrBranch = iter.second;
+ if (CurrBranch->Code) {
+ // We can't merge code; ignore
+ continue;
+ }
+ auto HashValue = Hash(CurrBlock);
+ auto& HashedSiblings = HashedBranchesOut[HashValue];
+ // Check if we are equivalent to any of them - if so, merge us.
+ bool Merged = false;
+ for (auto& Pair : HashedSiblings) {
+ Branch* SiblingBranch = Pair.first;
+ Block* SiblingBlock = Pair.second;
+ if (HaveEquivalentContents(CurrBlock, SiblingBlock)) {
+#if RELOOPER_OPTIMIZER_DEBUG
+ std::cout << " equiv! to " << SiblingBlock->Id << '\n';
+#endif
+ MergeBranchInto(CurrBranch, SiblingBranch);
+ BlocksToErase.push_back(CurrBlock);
+ Merged = true;
+ Worked = true;
+ }
+#if RELOOPER_OPTIMIZER_DEBUG
+ else {
+ std::cout << " same hash, but not equiv to " << SiblingBlock->Id << '\n';
+ }
+#endif
+ }
+ if (!Merged) {
+ HashedSiblings.emplace_back(CurrBranch, CurrBlock);
+ }
+ }
+ for (auto* Curr : BlocksToErase) {
+ ParentBlock->BranchesOut.erase(Curr);
+ }
+ }
+ }
+ return Worked;
+ }
+
+ // Removes unneeded switches - if only one branch is left, the default, then
+ // no switch is needed.
+ bool UnSwitch() {
+ bool Worked = false;
+ for (auto* ParentBlock : Parent->Blocks) {
+#if RELOOPER_OPTIMIZER_DEBUG
+ std::cout << "un-switching at " << ParentBlock->Id << ' ' << !!ParentBlock->SwitchCondition << ' ' << ParentBlock->BranchesOut.size() << '\n';
+#endif
+ if (ParentBlock->SwitchCondition) {
+ if (ParentBlock->BranchesOut.size() <= 1) {
+#if RELOOPER_OPTIMIZER_DEBUG
+ std::cout << " un-switching!: " << ParentBlock->Id << '\n';
+#endif
+ ParentBlock->SwitchCondition = nullptr;
+ if (!ParentBlock->BranchesOut.empty()) {
+ assert(!ParentBlock->BranchesOut.begin()->second->SwitchValues);
+ }
+ Worked = true;
+ }
+ } else {
+ // If the block has no switch, the branches must not as well.
+ for (auto& iter : ParentBlock->BranchesOut) {
+ assert(!iter.second->SwitchValues);
+ }
+ }
+ }
+ return Worked;
+ }
+
+private:
+ wasm::Expression* Canonicalize(wasm::Expression* Curr) {
+ wasm::Builder Builder(*Parent->Module);
+ // Our preferred form is a block with no name and a flat list
+ // with Nops removed, and extra Unreachables removed as well.
+ // If the block would contain one item, return just the item.
+ wasm::Block* Outer = Curr->dynCast<wasm::Block>();
+ if (!Outer) {
+ Outer = Builder.makeBlock(Curr);
+ } else if (Outer->name.is()) {
+ // Perhaps the name can be removed.
+ if (!wasm::BranchUtils::BranchSeeker::hasNamed(Outer, Outer->name)) {
+ Outer->name = wasm::Name();
+ } else {
+ Outer = Builder.makeBlock(Curr);
+ }
+ }
+ Flatten(Outer);
+ if (Outer->list.size() == 1) {
+ return Outer->list[0];
+ } else {
+ return Outer;
+ }
+ }
+
+ void Flatten(wasm::Block* Outer) {
+ wasm::ExpressionList NewList(Parent->Module->allocator);
+ bool SeenUnreachableType = false;
+ auto Add = [&](wasm::Expression* Curr) {
+ if (Curr->is<wasm::Nop>()) {
+ // Do nothing with it.
+ return;
+ } else if (Curr->is<wasm::Unreachable>()) {
+ // If we already saw an unreachable-typed item, emit no
+ // Unreachable nodes after it.
+ if (SeenUnreachableType) {
+ return;
+ }
+ }
+ NewList.push_back(Curr);
+ if (Curr->type == wasm::unreachable) {
+ SeenUnreachableType = true;
+ }
+ };
+ std::function<void (wasm::Block*)> FlattenIntoNewList = [&](wasm::Block* Curr) {
+ assert(!Curr->name.is());
+ for (auto* Item : Curr->list) {
+ if (auto* Block = Item->dynCast<wasm::Block>()) {
+ if (Block->name.is()) {
+ // Leave it whole, it's not a trivial block.
+ Add(Block);
+ } else {
+ FlattenIntoNewList(Block);
+ }
+ } else {
+ // A random item.
+ Add(Item);
+ }
+ }
+ // All the items have been moved out.
+ Curr->list.clear();
+ };
+ FlattenIntoNewList(Outer);
+ assert(Outer->list.empty());
+ Outer->list.swap(NewList);
+ }
+
+ bool IsEmpty(Block* Curr) {
+ if (Curr->SwitchCondition) {
+ // This is non-trivial, so treat it as a non-empty block.
+ return false;
+ }
+ return IsEmpty(Curr->Code);
+ }
+
+ bool IsEmpty(wasm::Expression* Code) {
+ if (Code->is<wasm::Nop>()) {
+ return true; // a nop
+ }
+ if (auto* WasmBlock = Code->dynCast<wasm::Block>()) {
+ for (auto* Item : WasmBlock->list) {
+ if (!IsEmpty(Item)) {
+ return false;
+ }
+ }
+ return true; // block with no non-empty contents
+ }
+ return false;
+ }
+
+ // Checks functional equivalence, namely: the Code and SwitchCondition.
+ // We also check the branches out, *non-recursively*: that is, we check
+ // that they are literally identical, not that they can be computed to
+ // be equivalent.
+ bool HaveEquivalentContents(Block* A, Block* B) {
+ if (!IsPossibleCodeEquivalent(A->SwitchCondition, B->SwitchCondition)) {
+ return false;
+ }
+ if (!IsCodeEquivalent(A->Code, B->Code)) {
+ return false;
+ }
+ if (A->BranchesOut.size() != B->BranchesOut.size()) {
+ return false;
+ }
+ for (auto& aiter : A->BranchesOut) {
+ Block* ABlock = aiter.first;
+ Branch* ABranch = aiter.second;
+ if (B->BranchesOut.count(ABlock) == 0) {
+ return false;
+ }
+ auto* BBranch = B->BranchesOut[ABlock];
+ if (!IsPossibleCodeEquivalent(ABranch->Condition, BBranch->Condition)) {
+ return false;
+ }
+ if (!IsPossibleUniquePtrEquivalent(ABranch->SwitchValues, BBranch->SwitchValues)) {
+ return false;
+ }
+ if (!IsPossibleCodeEquivalent(ABranch->Code, BBranch->Code)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Checks if values referred to by pointers are identical, allowing the code to also be nullptr
+ template<typename T>
+ static bool IsPossibleUniquePtrEquivalent(std::unique_ptr<T>& A, std::unique_ptr<T>& B) {
+ if (A == B) return true;
+ if (!A || !B) return false;
+ return *A == *B;
+ }
+
+ // Checks if code is equivalent, allowing the code to also be nullptr
+ static bool IsPossibleCodeEquivalent(wasm::Expression* A, wasm::Expression* B) {
+ if (A == B) return true;
+ if (!A || !B) return false;
+ return IsCodeEquivalent(A, B);
+ }
+
+ static bool IsCodeEquivalent(wasm::Expression* A, wasm::Expression* B) {
+ return wasm::ExpressionAnalyzer::equal(A, B);
+ }
+
+ // Merges one branch into another. Valid under the assumption that the
+ // blocks they reach are identical, and so one branch is enough for both
+ // with a unified condition.
+ // Only one is allowed to have code, as the code may have side effects,
+ // and we don't have a way to order or resolve those, unless the code
+ // is equivalent.
+ void MergeBranchInto(Branch* Curr, Branch* Into) {
+ assert(Curr != Into);
+ if (Curr->SwitchValues) {
+ if (!Into->SwitchValues) {
+ assert(!Into->Condition);
+ // Merging into the already-default, nothing to do.
+ } else {
+ Into->SwitchValues->insert(
+ Into->SwitchValues->end(),
+ Curr->SwitchValues->begin(), Curr->SwitchValues->end());
+ }
+ } else {
+ if (!Curr->Condition) {
+ // This is now the new default. Whether Into has a condition
+ // or switch values, remove them all to make us the default.
+ Into->Condition = nullptr;
+ Into->SwitchValues.reset();
+ } else if (!Into->Condition) {
+ // Nothing to do, already the default.
+ } else {
+ assert(!Into->SwitchValues);
+ // Merge them, checking both.
+ Into->Condition = wasm::Builder(*Parent->Module).makeBinary(
+ wasm::OrInt32,
+ Into->Condition,
+ Curr->Condition
+ );
+ }
+ }
+ if (!Curr->Code) {
+ // No code to merge in.
+ } else if (!Into->Code) {
+ // Just use the code being merged in.
+ Into->Code = Curr->Code;
+ } else {
+ assert(IsCodeEquivalent(Into->Code, Curr->Code));
+ // Keep the code already there, either is fine.
+ }
+ }
+
+ // Hashes the direct block contents, but not Relooper internals
+ // (like Shapes). Only partially hashes the branches out, no
+ // recursion: hashes the branch infos, looks at raw pointers
+ // for the blocks.
+ wasm::HashType Hash(Block* Curr) {
+ wasm::HashType Ret = wasm::ExpressionAnalyzer::hash(Curr->Code);
+ Ret = wasm::rehash(Ret, 1);
+ if (Curr->SwitchCondition) {
+ Ret = wasm::ExpressionAnalyzer::hash(Curr->SwitchCondition);
+ }
+ Ret = wasm::rehash(Ret, 2);
+ for (auto& Pair : Curr->BranchesOut) {
+ // Hash the Block* as a pointer TODO: full hash?
+ Ret = wasm::rehash(Ret, wasm::HashType(reinterpret_cast<size_t>(Pair.first)));
+ // Hash the Branch info properly
+ Ret = wasm::rehash(Ret, Hash(Pair.second));
+ }
+ return Ret;
+ }
+
+ // Hashes the direct block contents, but not Relooper internals
+ // (like Shapes).
+ wasm::HashType Hash(Branch* Curr) {
+ wasm::HashType Ret = 0;
+ if (Curr->SwitchValues) {
+ for (auto i : *Curr->SwitchValues) {
+ Ret = wasm::rehash(Ret, i); // TODO hash i
+ }
+ } else {
+ if (Curr->Condition) {
+ Ret = wasm::ExpressionAnalyzer::hash(Curr->Condition);
+ }
+ }
+ Ret = wasm::rehash(Ret, 1);
+ if (Curr->Code) {
+ Ret = wasm::ExpressionAnalyzer::hash(Curr->Code);
+ }
+ return Ret;
+ }
+};
+
} // namespace
void Relooper::Calculate(Block* Entry) {
+ // Optimize.
+ Optimizer(this);
+
// Find live blocks.
Liveness Live(this);
Live.FindLive(Entry);
@@ -979,20 +1421,39 @@ wasm::Expression* Relooper::Render(RelooperBuilder& Builder) {
#ifdef RELOOPER_DEBUG
// Debugging
+void Debugging::Dump(Block* Curr, const char *prefix) {
+ if (prefix) std::cout << prefix << ": ";
+ std::cout << Curr->Id << " [code " << *Curr->Code << "] [switch? " << !!Curr->SwitchCondition << "]\n";
+ for (auto iter2 = Curr->BranchesOut.begin(); iter2 != Curr->BranchesOut.end(); iter2++) {
+ Block* Other = iter2->first;
+ Branch* Br = iter2->second;
+ std::cout << " -> " << Other->Id << ' ';
+ if (Br->Condition) {
+ std::cout << "[if " << *Br->Condition << "] ";
+ } else if (Br->SwitchValues) {
+ std::cout << "[cases ";
+ for (auto x : *Br->SwitchValues) {
+ std::cout << x << ' ';
+ }
+ std::cout << "] ";
+ } else {
+ std::cout << "[default] ";
+ }
+ if (Br->Code) std::cout << "[phi " << *Br->Code << "] ";
+ std::cout << '\n';
+ }
+ std::cout << '\n';
+}
+
void Debugging::Dump(BlockSet &Blocks, const char *prefix) {
- if (prefix) printf("%s ", prefix);
+ if (prefix) std::cout << prefix << ": ";
for (auto* Curr : Blocks) {
- printf("%d:\n", Curr->Id);
- for (auto iter2 = Curr->BranchesOut.begin(); iter2 != Curr->BranchesOut.end(); iter2++) {
- Block* Other = iter2->first;
- printf(" -> %d\n", Other->Id);
- assert(contains(Other->BranchesIn, Curr));
- }
+ Dump(Curr);
}
}
void Debugging::Dump(Shape* S, const char *prefix) {
- if (prefix) printf("%s ", prefix);
+ if (prefix) std::cout << prefix << ": ";
if (!S) {
printf(" (null)\n");
return;
diff --git a/src/cfg/Relooper.h b/src/cfg/Relooper.h
index 5af55a744..b3a2b65f6 100644
--- a/src/cfg/Relooper.h
+++ b/src/cfg/Relooper.h
@@ -86,11 +86,17 @@ struct Branch {
Branch::FlowType Type; // If Ancestor is not NULL, this says whether to break or continue
// A branch either has a condition expression if the block ends in ifs, or if the block ends in a switch, then a list of indexes, which
- // becomes the indexes in the table of the switch. If not a switch, the condition can be any expression.
+ // becomes the indexes in the table of the switch. If not a switch, the condition can be any expression (or nullptr for the
+ // branch taken when no other condition is true)
+ // A condition must not have side effects, as the Relooper can reorder or eliminate condition checking.
+ // This must not have side effects.
wasm::Expression* Condition;
- std::unique_ptr<std::vector<wasm::Index>> SwitchValues; // switches are rare, so have just a pointer here
+ // Switches are rare, so have just a pointer for their values. This contains the values
+ // for which the branch will be taken, or for the default it is simply not present.
+ std::unique_ptr<std::vector<wasm::Index>> SwitchValues;
- wasm::Expression* Code; // If provided, code that is run right before the branch is taken. This is useful for phis
+ // If provided, code that is run right before the branch is taken. This is useful for phis.
+ wasm::Expression* Code;
Branch(wasm::Expression* ConditionInit, wasm::Expression* CodeInit = nullptr);
@@ -194,6 +200,16 @@ struct InsertOrderedMap
erase(position->first);
}
+ void clear() {
+ Map.clear();
+ List.clear();
+ }
+
+ void swap(InsertOrderedMap<Key, T>& Other) {
+ Map.swap(Other.Map);
+ List.swap(Other.List);
+ }
+
size_t size() const { return Map.size(); }
bool empty() const { return Map.empty(); }
size_t count(const Key& k) const { return Map.count(k); }
@@ -205,6 +221,12 @@ struct InsertOrderedMap
InsertOrderedMap& operator=(const InsertOrderedMap& other) {
abort(); // TODO, watch out for iterators
}
+ bool operator==(const InsertOrderedMap& other) {
+ return Map == other.Map && List == other.List;
+ }
+ bool operator!=(const InsertOrderedMap& other) {
+ return !(*this == other);
+ }
};
@@ -333,6 +355,7 @@ struct LoopShape : public Shape {
// Implementation details: The Relooper instance has
// ownership of the blocks and shapes, and frees them when done.
struct Relooper {
+ wasm::Module* Module;
std::deque<Block*> Blocks;
std::deque<Shape*> Shapes;
Shape* Root;
@@ -340,7 +363,7 @@ struct Relooper {
int BlockIdCounter;
int ShapeIdCounter;
- Relooper();
+ Relooper(wasm::Module* ModuleInit);
~Relooper();
void AddBlock(Block* New, int Id=-1);
@@ -359,6 +382,7 @@ typedef InsertOrderedMap<Block*, BlockSet> BlockBlockSetMap;
#ifdef RELOOPER_DEBUG
struct Debugging {
+ static void Dump(Block* Curr, const char *prefix=NULL);
static void Dump(BlockSet &Blocks, const char *prefix=NULL);
static void Dump(Shape* S, const char *prefix=NULL);
};
diff --git a/src/ir/ExpressionAnalyzer.cpp b/src/ir/ExpressionAnalyzer.cpp
index 44abc2f64..f9b981129 100644
--- a/src/ir/ExpressionAnalyzer.cpp
+++ b/src/ir/ExpressionAnalyzer.cpp
@@ -305,14 +305,14 @@ bool ExpressionAnalyzer::flexibleEqual(Expression* left, Expression* right, Expr
// hash an expression, ignoring superficial details like specific internal names
-uint32_t ExpressionAnalyzer::hash(Expression* curr) {
- uint32_t digest = 0;
+HashType ExpressionAnalyzer::hash(Expression* curr) {
+ HashType digest = 0;
- auto hash = [&digest](uint32_t hash) {
+ auto hash = [&digest](HashType hash) {
digest = rehash(digest, hash);
};
auto hash64 = [&digest](uint64_t hash) {
- digest = rehash(rehash(digest, uint32_t(hash >> 32)), uint32_t(hash));
+ digest = rehash(rehash(digest, HashType(hash >> 32)), HashType(hash));
};
std::vector<Name> nameStack;
@@ -498,7 +498,7 @@ uint32_t ExpressionAnalyzer::hash(Expression* curr) {
hash(c->type);
auto bits = c->value.getBits();
if (getTypeSize(c->type) == 4) {
- hash(uint32_t(bits));
+ hash(HashType(bits));
} else {
hash64(bits);
}
diff --git a/src/ir/hashed.h b/src/ir/hashed.h
index b15342aba..61bf19478 100644
--- a/src/ir/hashed.h
+++ b/src/ir/hashed.h
@@ -26,7 +26,7 @@ namespace wasm {
// An expression with a cached hash value
struct HashedExpression {
Expression* expr;
- size_t hash;
+ HashType hash;
HashedExpression(Expression* expr) : expr(expr) {
if (expr) {
@@ -38,7 +38,7 @@ struct HashedExpression {
};
struct ExpressionHasher {
- size_t operator()(const HashedExpression value) const {
+ HashType operator()(const HashedExpression value) const {
return value.hash;
}
};
diff --git a/src/ir/utils.h b/src/ir/utils.h
index 446e2c24a..8b4e028f4 100644
--- a/src/ir/utils.h
+++ b/src/ir/utils.h
@@ -80,7 +80,7 @@ struct ExpressionAnalyzer {
}
// hash an expression, ignoring superficial details like specific internal names
- static uint32_t hash(Expression* curr);
+ static HashType hash(Expression* curr);
};
// Re-Finalizes all node types. This can be run after code was modified in
diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js
index 8e4d67482..6f23867f5 100644
--- a/src/js/binaryen.js-post.js
+++ b/src/js/binaryen.js-post.js
@@ -206,35 +206,48 @@ Module['AtomicRMWXchg'] = Module['_BinaryenAtomicRMWXchg']();
// 'Module' interface
Module['Module'] = function(module) {
- if (!module) module = Module['_BinaryenModuleCreate']();
- this['ptr'] = module;
+ assert(!module); // guard against incorrect old API usage
+ var module = Module['_BinaryenModuleCreate']();
+
+ wrapModule(module, this);
+};
+
+// Receives a C pointer to a C Module and a JS object, and creates
+// the JS wrappings on the object to access the C data.
+// This is meant for internal use only, and is necessary as we
+// want to access Module from JS that were perhaps not created
+// from JS.
+function wrapModule(module, self) {
+ assert(module); // guard against incorrect old API usage
+ if (!self) self = {};
+
+ self['ptr'] = module;
// 'Expression' creation
- this['block'] = function(name, children, type) {
+ self['block'] = function(name, children, type) {
return preserveStack(function() {
return Module['_BinaryenBlock'](module, name ? strToStack(name) : 0,
i32sToStack(children), children.length,
typeof type !== 'undefined' ? type : Module['none']);
});
};
- this['if'] = function(condition, ifTrue, ifFalse) {
+ self['if'] = function(condition, ifTrue, ifFalse) {
return Module['_BinaryenIf'](module, condition, ifTrue, ifFalse);
};
- this['loop'] = function(label, body) {
+ self['loop'] = function(label, body) {
return preserveStack(function() {
return Module['_BinaryenLoop'](module, strToStack(label), body);
});
};
- this['break'] = this['br'] = function(label, condition, value) {
+ self['break'] = self['br'] = function(label, condition, value) {
return preserveStack(function() {
return Module['_BinaryenBreak'](module, strToStack(label), condition, value);
});
};
- this['br_if'] = function(label, condition, value) {
- assert(condition);
- return this['br'](label, condition, value);
+ self['br_if'] = function(label, condition, value) {
+ return self['br'](label, condition, value);
};
- this['switch'] = function(names, defaultName, condition, value) {
+ self['switch'] = function(names, defaultName, condition, value) {
return preserveStack(function() {
var namei32s = [];
names.forEach(function(name) {
@@ -244,35 +257,35 @@ Module['Module'] = function(module) {
strToStack(defaultName), condition, value);
});
};
- this['call'] = function(name, operands, type) {
+ self['call'] = function(name, operands, type) {
return preserveStack(function() {
return Module['_BinaryenCall'](module, strToStack(name), i32sToStack(operands), operands.length, type);
});
};
- this['callIndirect'] = this['call_indirect'] = function(target, operands, type) {
+ self['callIndirect'] = self['call_indirect'] = function(target, operands, type) {
return preserveStack(function() {
return Module['_BinaryenCallIndirect'](module, target, i32sToStack(operands), operands.length, strToStack(type));
});
};
- this['getLocal'] = this['get_local'] = function(index, type) {
+ self['getLocal'] = self['get_local'] = function(index, type) {
return Module['_BinaryenGetLocal'](module, index, type);
};
- this['setLocal'] = this['set_local'] = this['set_local'] = function(index, value) {
+ self['setLocal'] = self['set_local'] = self['set_local'] = function(index, value) {
return Module['_BinaryenSetLocal'](module, index, value);
};
- this['teeLocal'] = this['tee_local'] = function(index, value) {
+ self['teeLocal'] = self['tee_local'] = function(index, value) {
return Module['_BinaryenTeeLocal'](module, index, value);
};
- this['getGlobal'] = this['get_global'] = function(name, type) {
+ self['getGlobal'] = self['get_global'] = function(name, type) {
return Module['_BinaryenGetGlobal'](module, strToStack(name), type);
}
- this['setGlobal'] = this['set_global'] = function(name, value) {
+ self['setGlobal'] = self['set_global'] = function(name, value) {
return Module['_BinaryenSetGlobal'](module, strToStack(name), value);
}
- this['currentMemory'] = this['current_memory'] = function() {
+ self['currentMemory'] = self['current_memory'] = function() {
return Module['_BinaryenHost'](module, Module['CurrentMemory']);
}
- this['growMemory'] = this['grow_memory'] = function(value) {
+ self['growMemory'] = self['grow_memory'] = function(value) {
return Module['_BinaryenHost'](module, Module['GrowMemory'], null, i32sToStack([value]), 1);
}
@@ -283,7 +296,7 @@ Module['Module'] = function(module) {
var temp = _malloc(16); // a single literal in memory. the LLVM C ABI
// makes us pass pointers to this.
- this['i32'] = {
+ self['i32'] = {
'load': function(offset, align, ptr) {
return Module['_BinaryenLoad'](module, 4, true, offset, align, Module['i32'], ptr);
},
@@ -521,7 +534,7 @@ Module['Module'] = function(module) {
},
};
- this['i64'] = {
+ self['i64'] = {
'load': function(offset, align, ptr) {
return Module['_BinaryenLoad'](module, 8, true, offset, align, Module['i64'], ptr);
},
@@ -803,7 +816,7 @@ Module['Module'] = function(module) {
},
};
- this['f32'] = {
+ self['f32'] = {
'load': function(offset, align, ptr) {
return Module['_BinaryenLoad'](module, 4, true, offset, align, Module['f32'], ptr);
},
@@ -902,7 +915,7 @@ Module['Module'] = function(module) {
},
};
- this['f64'] = {
+ self['f64'] = {
'load': function(offset, align, ptr) {
return Module['_BinaryenLoad'](module, 8, true, offset, align, Module['f64'], ptr);
},
@@ -1001,123 +1014,123 @@ Module['Module'] = function(module) {
},
};
- this['select'] = function(condition, ifTrue, ifFalse) {
+ self['select'] = function(condition, ifTrue, ifFalse) {
return Module['_BinaryenSelect'](module, condition, ifTrue, ifFalse);
};
- this['drop'] = function(value) {
+ self['drop'] = function(value) {
return Module['_BinaryenDrop'](module, value);
};
- this['return'] = function(value) {
+ self['return'] = function(value) {
return Module['_BinaryenReturn'](module, value);
};
- this['host'] = function(op, name, operands) {
+ self['host'] = function(op, name, operands) {
if (!operands) operands = [];
return preserveStack(function() {
return Module['_BinaryenHost'](module, op, strToStack(name), i32sToStack(operands), operands.length);
});
};
- this['nop'] = function() {
+ self['nop'] = function() {
return Module['_BinaryenNop'](module);
};
- this['unreachable'] = function() {
+ self['unreachable'] = function() {
return Module['_BinaryenUnreachable'](module);
};
- this['wake'] = function(ptr, wakeCount) {
+ self['wake'] = function(ptr, wakeCount) {
return Module['_BinaryenAtomicWake'](module, ptr, wakeCount);
};
// 'Module' operations
- this['addFunctionType'] = function(name, result, paramTypes) {
+ self['addFunctionType'] = function(name, result, paramTypes) {
if (!paramTypes) paramTypes = [];
return preserveStack(function() {
return Module['_BinaryenAddFunctionType'](module, strToStack(name), result,
i32sToStack(paramTypes), paramTypes.length);
});
};
- this['getFunctionTypeBySignature'] = function(result, paramTypes) {
+ self['getFunctionTypeBySignature'] = function(result, paramTypes) {
if (!paramTypes) paramTypes = [];
return preserveStack(function() {
return Module['_BinaryenGetFunctionTypeBySignature'](module, result,
i32sToStack(paramTypes), paramTypes.length);
});
};
- this['removeFunctionType'] = function(name) {
+ self['removeFunctionType'] = function(name) {
return preserveStack(function () {
return Module['_BinaryenRemoveFunctionType'](module, strToStack(name));
});
};
- this['addFunction'] = function(name, functionType, varTypes, body) {
+ self['addFunction'] = function(name, functionType, varTypes, body) {
return preserveStack(function() {
return Module['_BinaryenAddFunction'](module, strToStack(name), functionType, i32sToStack(varTypes), varTypes.length, body);
});
};
- this['getFunction'] = function(name) {
+ self['getFunction'] = function(name) {
return preserveStack(function() {
return Module['_BinaryenGetFunction'](module, strToStack(name));
});
};
- this['removeFunction'] = function(name) {
+ self['removeFunction'] = function(name) {
return preserveStack(function() {
return Module['_BinaryenRemoveFunction'](module, strToStack(name));
});
};
- this['addGlobal'] = function(name, type, mutable, init) {
+ self['addGlobal'] = function(name, type, mutable, init) {
return preserveStack(function() {
return Module['_BinaryenAddGlobal'](module, strToStack(name), type, mutable, init);
});
}
- this['removeGlobal'] = function(name) {
+ self['removeGlobal'] = function(name) {
return preserveStack(function () {
return Module['_BinaryenRemoveGlobal'](module, strToStack(name));
});
}
- this['addFunctionImport'] = function(internalName, externalModuleName, externalBaseName, functionType) {
+ self['addFunctionImport'] = function(internalName, externalModuleName, externalBaseName, functionType) {
return preserveStack(function() {
return Module['_BinaryenAddFunctionImport'](module, strToStack(internalName), strToStack(externalModuleName), strToStack(externalBaseName), functionType);
});
};
- this['addTableImport'] = function(internalName, externalModuleName, externalBaseName) {
+ self['addTableImport'] = function(internalName, externalModuleName, externalBaseName) {
return preserveStack(function() {
return Module['_BinaryenAddTableImport'](module, strToStack(internalName), strToStack(externalModuleName), strToStack(externalBaseName));
});
};
- this['addMemoryImport'] = function(internalName, externalModuleName, externalBaseName, shared) {
+ self['addMemoryImport'] = function(internalName, externalModuleName, externalBaseName, shared) {
return preserveStack(function() {
return Module['_BinaryenAddMemoryImport'](module, strToStack(internalName), strToStack(externalModuleName), strToStack(externalBaseName), shared);
});
};
- this['addGlobalImport'] = function(internalName, externalModuleName, externalBaseName, globalType) {
+ self['addGlobalImport'] = function(internalName, externalModuleName, externalBaseName, globalType) {
return preserveStack(function() {
return Module['_BinaryenAddGlobalImport'](module, strToStack(internalName), strToStack(externalModuleName), strToStack(externalBaseName), globalType);
});
};
- this['addExport'] = // deprecated
- this['addFunctionExport'] = function(internalName, externalName) {
+ self['addExport'] = // deprecated
+ self['addFunctionExport'] = function(internalName, externalName) {
return preserveStack(function() {
return Module['_BinaryenAddFunctionExport'](module, strToStack(internalName), strToStack(externalName));
});
};
- this['addTableExport'] = function(internalName, externalName) {
+ self['addTableExport'] = function(internalName, externalName) {
return preserveStack(function() {
return Module['_BinaryenAddTableExport'](module, strToStack(internalName), strToStack(externalName));
});
};
- this['addMemoryExport'] = function(internalName, externalName) {
+ self['addMemoryExport'] = function(internalName, externalName) {
return preserveStack(function() {
return Module['_BinaryenAddMemoryExport'](module, strToStack(internalName), strToStack(externalName));
});
};
- this['addGlobalExport'] = function(internalName, externalName) {
+ self['addGlobalExport'] = function(internalName, externalName) {
return preserveStack(function() {
return Module['_BinaryenAddGlobalExport'](module, strToStack(internalName), strToStack(externalName));
});
};
- this['removeExport'] = function(externalName) {
+ self['removeExport'] = function(externalName) {
return preserveStack(function() {
return Module['_BinaryenRemoveExport'](module, strToStack(externalName));
});
};
- this['setFunctionTable'] = function(initial, maximum, funcNames) {
+ self['setFunctionTable'] = function(initial, maximum, funcNames) {
return preserveStack(function() {
return Module['_BinaryenSetFunctionTable'](module, initial, maximum,
i32sToStack(funcNames.map(strToStack)),
@@ -1125,7 +1138,7 @@ Module['Module'] = function(module) {
);
});
};
- this['setMemory'] = function(initial, maximum, exportName, segments, shared) {
+ self['setMemory'] = function(initial, maximum, exportName, segments, shared) {
// segments are assumed to be { offset: expression ref, data: array of 8-bit data }
if (!segments) segments = [];
return preserveStack(function() {
@@ -1151,10 +1164,10 @@ Module['Module'] = function(module) {
);
});
};
- this['setStart'] = function(start) {
+ self['setStart'] = function(start) {
return Module['_BinaryenSetStart'](module, start);
};
- this['emitText'] = function() {
+ self['emitText'] = function() {
var old = out;
var ret = '';
out = function(x) { ret += x + '\n' };
@@ -1162,17 +1175,17 @@ Module['Module'] = function(module) {
out = old;
return ret;
};
- this['emitStackIR'] = function(optimize) {
- this['runPasses'](['generate-stack-ir']);
- if (optimize) this['runPasses'](['optimize-stack-ir']);
+ self['emitStackIR'] = function(optimize) {
+ self['runPasses'](['generate-stack-ir']);
+ if (optimize) self['runPasses'](['optimize-stack-ir']);
var old = out;
var ret = '';
out = function(x) { ret += x + '\n' };
- this['runPasses'](['print-stack-ir']);
+ self['runPasses'](['print-stack-ir']);
out = old;
return ret;
};
- this['emitAsmjs'] = function() {
+ self['emitAsmjs'] = function() {
var old = out;
var ret = '';
out = function(x) { ret += x + '\n' };
@@ -1180,38 +1193,38 @@ Module['Module'] = function(module) {
out = old;
return ret;
};
- this['validate'] = function() {
+ self['validate'] = function() {
return Module['_BinaryenModuleValidate'](module);
};
- this['optimize'] = function() {
+ self['optimize'] = function() {
return Module['_BinaryenModuleOptimize'](module);
};
- this['optimizeFunction'] = function(func) {
- if (typeof func === 'string') func = this['getFunction'](func);
+ self['optimizeFunction'] = function(func) {
+ if (typeof func === 'string') func = self['getFunction'](func);
return Module['_BinaryenFunctionOptimize'](func, module);
};
- this['runPasses'] = function(passes) {
+ self['runPasses'] = function(passes) {
return preserveStack(function() {
return Module['_BinaryenModuleRunPasses'](module, i32sToStack(
passes.map(strToStack)
), passes.length);
});
};
- this['runPassesOnFunction'] = function(func, passes) {
- if (typeof func === 'string') func = this['getFunction'](func);
+ self['runPassesOnFunction'] = function(func, passes) {
+ if (typeof func === 'string') func = self['getFunction'](func);
return preserveStack(function() {
return Module['_BinaryenFunctionRunPasses'](func, module, i32sToStack(
passes.map(strToStack)
), passes.length);
});
};
- this['autoDrop'] = function() {
+ self['autoDrop'] = function() {
return Module['_BinaryenModuleAutoDrop'](module);
};
- this['dispose'] = function() {
+ self['dispose'] = function() {
Module['_BinaryenModuleDispose'](module);
};
- this['emitBinary'] = function(sourceMapUrl) {
+ self['emitBinary'] = function(sourceMapUrl) {
return preserveStack(function() {
Module['_BinaryenModuleAllocateAndWrite'](temp, module, strToStack(sourceMapUrl));
var binaryPtr = HEAPU32[ temp >>> 2 ];
@@ -1229,26 +1242,30 @@ Module['Module'] = function(module) {
}
});
};
- this['interpret'] = function() {
+ self['interpret'] = function() {
return Module['_BinaryenModuleInterpret'](module);
};
- this['addDebugInfoFileName'] = function(filename) {
+ self['addDebugInfoFileName'] = function(filename) {
return preserveStack(function() {
return Module['_BinaryenModuleAddDebugInfoFileName'](module, strToStack(filename));
});
};
- this['getDebugInfoFileName'] = function(index) {
+ self['getDebugInfoFileName'] = function(index) {
return Pointer_stringify(Module['_BinaryenModuleGetDebugInfoFileName'](module, index));
};
- this['setDebugLocation'] = function(func, expr, fileIndex, lineNumber, columnNumber) {
+ self['setDebugLocation'] = function(func, expr, fileIndex, lineNumber, columnNumber) {
return Module['_BinaryenFunctionSetDebugLocation'](func, expr, fileIndex, lineNumber, columnNumber);
};
-};
+
+ return self;
+}
+Module['wrapModule'] = wrapModule;
// 'Relooper' interface
-Module['Relooper'] = function(relooper) {
- if (!relooper) relooper = Module['_RelooperCreate']();
- this.ptr = relooper;
+Module['Relooper'] = function(module) {
+ assert(module && typeof module === 'object' && module['ptr'] && module['block'] && module['if']); // guard against incorrect old API usage
+ var relooper = Module['_RelooperCreate'](module['ptr']);
+ this['ptr'] = relooper;
this['addBlock'] = function(code) {
return Module['_RelooperAddBlock'](relooper, code);
@@ -1264,8 +1281,8 @@ Module['Relooper'] = function(relooper) {
return Module['_RelooperAddBranchForSwitch'](from, to, i32sToStack(indexes), indexes.length, code);
});
};
- this['renderAndDispose'] = function(entry, labelHelper, module) {
- return Module['_RelooperRenderAndDispose'](relooper, entry, labelHelper, module['ptr']);
+ this['renderAndDispose'] = function(entry, labelHelper) {
+ return Module['_RelooperRenderAndDispose'](relooper, entry, labelHelper);
};
};
@@ -1558,7 +1575,7 @@ Module['readBinary'] = function(data) {
var buffer = allocate(data, 'i8', ALLOC_NORMAL);
var ptr = Module['_BinaryenModuleRead'](buffer, data.length);
_free(buffer);
- return new Module['Module'](ptr);
+ return wrapModule(ptr);
};
// Parses text format to a module
@@ -1567,7 +1584,7 @@ Module['parseText'] = function(text) {
writeAsciiToMemory(text, buffer);
var ptr = Module['_BinaryenModuleParse'](buffer);
_free(buffer);
- return new Module['Module'](ptr);
+ return wrapModule(ptr);
};
// Gets the currently set optimize level. 0, 1, 2 correspond to -O0, -O1, -O2, etc.
diff --git a/src/passes/ReReloop.cpp b/src/passes/ReReloop.cpp
index d96b95715..aae0ea262 100644
--- a/src/passes/ReReloop.cpp
+++ b/src/passes/ReReloop.cpp
@@ -41,7 +41,7 @@ struct ReReloop final : public Pass {
Pass* create() override { return new ReReloop; }
- CFG::Relooper relooper;
+ std::unique_ptr<CFG::Relooper> relooper;
std::unique_ptr<Builder> builder;
// block handling
@@ -50,7 +50,7 @@ struct ReReloop final : public Pass {
CFG::Block* makeCFGBlock() {
auto* ret = new CFG::Block(builder->makeBlock());
- relooper.AddBlock(ret);
+ relooper->AddBlock(ret);
return ret;
}
@@ -301,6 +301,7 @@ struct ReReloop final : public Pass {
// first, traverse the function body. note how we don't need to traverse
// into expressions, as we know they contain no control flow
builder = make_unique<Builder>(*module);
+ relooper = make_unique<CFG::Relooper>(module);
auto* entry = startCFGBlock();
stack.push_back(TaskPtr(new TriageTask(*this, function->body)));
// main loop
@@ -314,7 +315,7 @@ struct ReReloop final : public Pass {
// blocks that do not have any exits are dead ends in the relooper. we need
// to make sure that are in fact dead ends, and do not flow control anywhere.
// add a return as needed
- for (auto* cfgBlock : relooper.Blocks) {
+ for (auto* cfgBlock : relooper->Blocks) {
auto* block = cfgBlock->Code->cast<Block>();
if (cfgBlock->BranchesOut.empty() && block->type != unreachable) {
block->list.push_back(
@@ -326,7 +327,7 @@ struct ReReloop final : public Pass {
}
#ifdef RERELOOP_DEBUG
std::cout << "rerelooping " << function->name << '\n';
- for (auto* block : relooper.Blocks) {
+ for (auto* block : relooper->Blocks) {
std::cout << block << " block:\n" << block->Code << '\n';
for (auto& pair : block->BranchesOut) {
auto* target = pair.first;
@@ -339,12 +340,12 @@ struct ReReloop final : public Pass {
}
#endif
// run the relooper to recreate control flow
- relooper.Calculate(entry);
+ relooper->Calculate(entry);
// render
{
auto temp = builder->addVar(function, i32);
CFG::RelooperBuilder builder(*module, temp);
- function->body = relooper.Render(builder);
+ function->body = relooper->Render(builder);
// if the function has a result, and the relooper emitted
// something that seems like it flows out without a value
// (but that path is never reached; it just has a br to it