diff options
author | Alon Zakai <alonzakai@gmail.com> | 2018-11-21 08:59:13 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-11-21 08:59:13 -0800 |
commit | 6cc2bb302d5729c76da42dc0815d6dbba645d952 (patch) | |
tree | 11e60856b2028e73df98eeaea2f0b789c50c8cd1 /src | |
parent | 44335674936254ef6f8695883e4376a9d5fd1521 (diff) | |
download | binaryen-6cc2bb302d5729c76da42dc0815d6dbba645d952.tar.gz binaryen-6cc2bb302d5729c76da42dc0815d6dbba645d952.tar.bz2 binaryen-6cc2bb302d5729c76da42dc0815d6dbba645d952.zip |
Relooper CFG optimizations (#1759)
Previously the relooper would do some optimizations when deciding when to use an if vs a switch, how to group blocks, etc. This PR adds an additional pre-optimization phase with some basic but useful simplify-cfg style passes,
* Skip empty blocks when they have just one exit.
* Merge exiting branches when they are equivalent.
* Canonicalize block contents to make such comparisons more useful.
* Turn a trivial one-target switch into a simple branch.
This can help in noticeable ways when running the rereloop pass, e.g. on LLVM wasm backend output.
Also:
* Binaryen C API changes to the relooper, which now gets a Module for its constructor. It needs it for the optimizations, as it may construct new nodes.
* Many relooper-fuzzer improvements.
* Clean up HashType usage.
Diffstat (limited to 'src')
-rw-r--r-- | src/binaryen-c.cpp | 13 | ||||
-rw-r--r-- | src/binaryen-c.h | 4 | ||||
-rw-r--r-- | src/cfg/Relooper.cpp | 481 | ||||
-rw-r--r-- | src/cfg/Relooper.h | 32 | ||||
-rw-r--r-- | src/ir/ExpressionAnalyzer.cpp | 10 | ||||
-rw-r--r-- | src/ir/hashed.h | 4 | ||||
-rw-r--r-- | src/ir/utils.h | 2 | ||||
-rw-r--r-- | src/js/binaryen.js-post.js | 175 | ||||
-rw-r--r-- | src/passes/ReReloop.cpp | 13 |
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 |