//===-- Relooper.cpp - Top-level interface for WebAssembly ----*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===---------------------------------------------------------------------===// /// /// \file /// \brief This implements the Relooper algorithm. This implementation includes /// optimizations added since the original academic paper [1] was published. /// /// [1] Alon Zakai. 2011. Emscripten: an LLVM-to-JavaScript compiler. In /// Proceedings of the ACM international conference companion on Object /// oriented programming systems languages and applications companion /// (SPLASH '11). ACM, New York, NY, USA, 301-312. DOI=10.1145/2048147.2048224 /// http://doi.acm.org/10.1145/2048147.2048224 /// //===-------------------------------------------------------------------===// #include "Relooper.h" #include "WebAssembly.h" #include "llvm/ADT/STLExtras.h" #include "llvm/IR/CFG.h" #include "llvm/IR/Function.h" #include "llvm/Pass.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include #include #include #define DEBUG_TYPE "relooper" using namespace llvm; using namespace Relooper; static cl::opt RelooperSplittingFactor( "relooper-splitting-factor", cl::desc( "How much to discount code size when deciding whether to split a node"), cl::init(5)); static cl::opt RelooperMultipleSwitchThreshold( "relooper-multiple-switch-threshold", cl::desc( "How many entries to allow in a multiple before we use a switch"), cl::init(10)); static cl::opt RelooperNestingLimit( "relooper-nesting-limit", cl::desc( "How much nesting is acceptable"), cl::init(20)); namespace { /// /// Implements the relooper algorithm for a function's blocks. /// /// Implementation details: The Relooper instance has /// ownership of the blocks and shapes, and frees them when done. /// struct RelooperAlgorithm { std::deque Blocks; std::deque Shapes; Shape *Root; bool MinSize; int BlockIdCounter; int ShapeIdCounter; RelooperAlgorithm(); ~RelooperAlgorithm(); void AddBlock(Block *New, int Id = -1); // Calculates the shapes void Calculate(Block *Entry); // Sets us to try to minimize size void SetMinSize(bool MinSize_) { MinSize = MinSize_; } }; struct RelooperAnalysis final : public FunctionPass { static char ID; RelooperAnalysis() : FunctionPass(ID) {} const char *getPassName() const override { return "relooper"; } void getAnalysisUsage(AnalysisUsage &AU) const override { AU.setPreservesAll(); } bool runOnFunction(Function &F) override; }; } // RelooperAnalysis char RelooperAnalysis::ID = 0; FunctionPass *llvm::createWebAssemblyRelooper() { return new RelooperAnalysis(); } bool RelooperAnalysis::runOnFunction(Function &F) { DEBUG(dbgs() << "Relooping function '" << F.getName() << "'\n"); RelooperAlgorithm R; // FIXME: remove duplication between relooper's and LLVM's BBs. std::map BB2B; std::map B2BB; for (const BasicBlock &BB : F) { // FIXME: getName is wrong here, Code is meant to represent amount of code. // FIXME: use BranchVarInit for switch. Block *B = new Block(BB.getName().str().data(), /*BranchVarInit=*/nullptr); R.AddBlock(B); assert(BB2B.find(&BB) == BB2B.end() && "Inserting the same block twice"); assert(B2BB.find(B) == B2BB.end() && "Inserting the same block twice"); BB2B[&BB] = B; B2BB[B] = &BB; } for (Block *B : R.Blocks) { const BasicBlock *BB = B2BB[B]; for (const BasicBlock *Successor : successors(BB)) // FIXME: add branch's Condition and Code below. B->AddBranchTo(BB2B[Successor], /*Condition=*/nullptr, /*Code=*/nullptr); } R.Calculate(BB2B[&F.getEntryBlock()]); return false; // Analysis passes don't modify anything. } // Helpers typedef MapVector BlockBlockSetMap; typedef std::list BlockList; template static bool contains(const T &container, const U &contained) { return container.count(contained); } // Branch Branch::Branch(const char *ConditionInit, const char *CodeInit) : Ancestor(nullptr), Labeled(true) { // FIXME: move from char* to LLVM data structures Condition = ConditionInit ? strdup(ConditionInit) : nullptr; Code = CodeInit ? strdup(CodeInit) : nullptr; } Branch::~Branch() { // FIXME: move from char* to LLVM data structures free(static_cast(const_cast(Condition))); free(static_cast(const_cast(Code))); } // Block Block::Block(const char *CodeInit, const char *BranchVarInit) : Parent(nullptr), Id(-1), IsCheckedMultipleEntry(false) { // FIXME: move from char* to LLVM data structures Code = strdup(CodeInit); BranchVar = BranchVarInit ? strdup(BranchVarInit) : nullptr; } Block::~Block() { // FIXME: move from char* to LLVM data structures free(static_cast(const_cast(Code))); free(static_cast(const_cast(BranchVar))); } void Block::AddBranchTo(Block *Target, const char *Condition, const char *Code) { assert(!contains(BranchesOut, Target) && "cannot add more than one branch to the same target"); BranchesOut[Target] = make_unique(Condition, Code); } // Relooper RelooperAlgorithm::RelooperAlgorithm() : Root(nullptr), MinSize(false), BlockIdCounter(1), ShapeIdCounter(0) { // block ID 0 is reserved for clearings } RelooperAlgorithm::~RelooperAlgorithm() { for (auto Curr : Blocks) delete Curr; for (auto Curr : Shapes) delete Curr; } void RelooperAlgorithm::AddBlock(Block *New, int Id) { New->Id = Id == -1 ? BlockIdCounter++ : Id; Blocks.push_back(New); } struct RelooperRecursor { RelooperAlgorithm *Parent; RelooperRecursor(RelooperAlgorithm *ParentInit) : Parent(ParentInit) {} }; void RelooperAlgorithm::Calculate(Block *Entry) { // Scan and optimize the input struct PreOptimizer : public RelooperRecursor { PreOptimizer(RelooperAlgorithm *Parent) : RelooperRecursor(Parent) {} BlockSet Live; void FindLive(Block *Root) { BlockList ToInvestigate; ToInvestigate.push_back(Root); while (!ToInvestigate.empty()) { Block *Curr = ToInvestigate.front(); ToInvestigate.pop_front(); if (contains(Live, Curr)) continue; Live.insert(Curr); for (const auto &iter : Curr->BranchesOut) ToInvestigate.push_back(iter.first); } } // If a block has multiple entries but no exits, and it is small enough, it // is useful to split it. A common example is a C++ function where // everything ends up at a final exit block and does some RAII cleanup. // Without splitting, we will be forced to introduce labelled loops to // allow reaching the final block void SplitDeadEnds() { unsigned TotalCodeSize = 0; for (const auto &Curr : Live) { TotalCodeSize += strlen(Curr->Code); } BlockSet Splits; BlockSet Removed; for (const auto &Original : Live) { if (Original->BranchesIn.size() <= 1 || !Original->BranchesOut.empty()) continue; // only dead ends, for now if (contains(Original->BranchesOut, Original)) continue; // cannot split a looping node if (strlen(Original->Code) * (Original->BranchesIn.size() - 1) > TotalCodeSize / RelooperSplittingFactor) continue; // if splitting increases raw code size by a significant // amount, abort // Split the node (for simplicity, we replace all the blocks, even // though we could have reused the original) DEBUG(dbgs() << " Splitting '" << Original->Code << "'\n"); for (const auto &Prior : Original->BranchesIn) { Block *Split = new Block(Original->Code, Original->BranchVar); Parent->AddBlock(Split, Original->Id); Split->BranchesIn.insert(Prior); std::unique_ptr Details; Details.swap(Prior->BranchesOut[Original]); Prior->BranchesOut[Split] = make_unique(Details->Condition, Details->Code); for (const auto &iter : Original->BranchesOut) { Block *Post = iter.first; Branch *Details = iter.second.get(); Split->BranchesOut[Post] = make_unique(Details->Condition, Details->Code); Post->BranchesIn.insert(Split); } Splits.insert(Split); Removed.insert(Original); } for (const auto &iter : Original->BranchesOut) { Block *Post = iter.first; Post->BranchesIn.remove(Original); } } for (const auto &iter : Splits) Live.insert(iter); for (const auto &iter : Removed) Live.remove(iter); } }; PreOptimizer Pre(this); Pre.FindLive(Entry); // Add incoming branches from live blocks, ignoring dead code for (unsigned i = 0; i < Blocks.size(); i++) { Block *Curr = Blocks[i]; if (!contains(Pre.Live, Curr)) continue; for (const auto &iter : Curr->BranchesOut) iter.first->BranchesIn.insert(Curr); } if (!MinSize) Pre.SplitDeadEnds(); // Recursively process the graph struct Analyzer : public RelooperRecursor { Analyzer(RelooperAlgorithm *Parent) : RelooperRecursor(Parent) {} // Add a shape to the list of shapes in this Relooper calculation void Notice(Shape *New) { New->Id = Parent->ShapeIdCounter++; Parent->Shapes.push_back(New); } // Create a list of entries from a block. If LimitTo is provided, only // results in that set will appear void GetBlocksOut(Block *Source, BlockSet &Entries, BlockSet *LimitTo = nullptr) { for (const auto &iter : Source->BranchesOut) if (!LimitTo || contains(*LimitTo, iter.first)) Entries.insert(iter.first); } // Converts/processes all branchings to a specific target void Solipsize(Block *Target, Branch::FlowType Type, Shape *Ancestor, BlockSet &From) { DEBUG(dbgs() << " Solipsize '" << Target->Code << "' type " << Type << "\n"); for (auto iter = Target->BranchesIn.begin(); iter != Target->BranchesIn.end();) { Block *Prior = *iter; if (!contains(From, Prior)) { iter++; continue; } std::unique_ptr PriorOut; PriorOut.swap(Prior->BranchesOut[Target]); PriorOut->Ancestor = Ancestor; PriorOut->Type = Type; if (MultipleShape *Multiple = dyn_cast(Ancestor)) Multiple->Breaks++; // We are breaking out of this Multiple, so need a // loop iter++; // carefully increment iter before erasing Target->BranchesIn.remove(Prior); Target->ProcessedBranchesIn.insert(Prior); Prior->ProcessedBranchesOut[Target].swap(PriorOut); } } Shape *MakeSimple(BlockSet &Blocks, Block *Inner, BlockSet &NextEntries) { DEBUG(dbgs() << " MakeSimple inner block '" << Inner->Code << "'\n"); SimpleShape *Simple = new SimpleShape; Notice(Simple); Simple->Inner = Inner; Inner->Parent = Simple; if (Blocks.size() > 1) { Blocks.remove(Inner); GetBlocksOut(Inner, NextEntries, &Blocks); BlockSet JustInner; JustInner.insert(Inner); for (const auto &iter : NextEntries) Solipsize(iter, Branch::Direct, Simple, JustInner); } return Simple; } Shape *MakeLoop(BlockSet &Blocks, BlockSet &Entries, BlockSet &NextEntries) { // Find the inner blocks in this loop. Proceed backwards from the entries // until // you reach a seen block, collecting as you go. BlockSet InnerBlocks; BlockSet Queue = Entries; while (!Queue.empty()) { Block *Curr = *(Queue.begin()); Queue.remove(*Queue.begin()); if (!contains(InnerBlocks, Curr)) { // This element is new, mark it as inner and remove from outer InnerBlocks.insert(Curr); Blocks.remove(Curr); // Add the elements prior to it for (const auto &iter : Curr->BranchesIn) Queue.insert(iter); } } assert(!InnerBlocks.empty()); for (const auto &Curr : InnerBlocks) { for (const auto &iter : Curr->BranchesOut) { Block *Possible = iter.first; if (!contains(InnerBlocks, Possible)) NextEntries.insert(Possible); } } LoopShape *Loop = new LoopShape(); Notice(Loop); // Solipsize the loop, replacing with break/continue and marking branches // as Processed (will not affect later calculations) // A. Branches to the loop entries become a continue to this shape for (const auto &iter : Entries) Solipsize(iter, Branch::Continue, Loop, InnerBlocks); // B. Branches to outside the loop (a next entry) become breaks on this // shape for (const auto &iter : NextEntries) Solipsize(iter, Branch::Break, Loop, InnerBlocks); // Finish up Shape *Inner = Process(InnerBlocks, Entries, nullptr); Loop->Inner = Inner; return Loop; } // For each entry, find the independent group reachable by it. The // independent group is the entry itself, plus all the blocks it can // reach that cannot be directly reached by another entry. Note that we // ignore directly reaching the entry itself by another entry. // @param Ignore - previous blocks that are irrelevant void FindIndependentGroups(BlockSet &Entries, BlockBlockSetMap &IndependentGroups, BlockSet *Ignore = nullptr) { typedef std::map BlockBlockMap; struct HelperClass { BlockBlockSetMap &IndependentGroups; BlockBlockMap Ownership; // For each block, which entry it belongs to. // We have reached it from there. HelperClass(BlockBlockSetMap &IndependentGroupsInit) : IndependentGroups(IndependentGroupsInit) {} void InvalidateWithChildren(Block *New) { // Being in the list means you need to be invalidated BlockList ToInvalidate; ToInvalidate.push_back(New); while (!ToInvalidate.empty()) { Block *Invalidatee = ToInvalidate.front(); ToInvalidate.pop_front(); Block *Owner = Ownership[Invalidatee]; // Owner may have been invalidated, do not add to // IndependentGroups! if (contains(IndependentGroups, Owner)) IndependentGroups[Owner].remove(Invalidatee); if (Ownership[Invalidatee]) { // may have been seen before and // invalidated already Ownership[Invalidatee] = nullptr; for (const auto &iter : Invalidatee->BranchesOut) { Block *Target = iter.first; BlockBlockMap::iterator Known = Ownership.find(Target); if (Known != Ownership.end()) { Block *TargetOwner = Known->second; if (TargetOwner) ToInvalidate.push_back(Target); } } } } } }; HelperClass Helper(IndependentGroups); // We flow out from each of the entries, simultaneously. // When we reach a new block, we add it as belonging to the one we got to // it from. // If we reach a new block that is already marked as belonging to someone, // it is reachable by two entries and is not valid for any of them. // Remove it and all it can reach that have been visited. // Being in the queue means we just added this item, and // we need to add its children BlockList Queue; for (const auto &Entry : Entries) { Helper.Ownership[Entry] = Entry; IndependentGroups[Entry].insert(Entry); Queue.push_back(Entry); } while (!Queue.empty()) { Block *Curr = Queue.front(); Queue.pop_front(); Block *Owner = Helper.Ownership[Curr]; // Curr must be in the ownership // map if we are in the queue if (!Owner) continue; // we have been invalidated meanwhile after being reached // from two entries // Add all children for (const auto &iter : Curr->BranchesOut) { Block *New = iter.first; BlockBlockMap::iterator Known = Helper.Ownership.find(New); if (Known == Helper.Ownership.end()) { // New node. Add it, and put it in the queue Helper.Ownership[New] = Owner; IndependentGroups[Owner].insert(New); Queue.push_back(New); continue; } Block *NewOwner = Known->second; if (!NewOwner) continue; // We reached an invalidated node if (NewOwner != Owner) // Invalidate this and all reachable that we have seen - we reached // this from two locations Helper.InvalidateWithChildren(New); // otherwise, we have the same owner, so do nothing } } // Having processed all the interesting blocks, we remain with just one // potential issue: // If a->b, and a was invalidated, but then b was later reached by // someone else, we must invalidate b. To check for this, we go over all // elements in the independent groups, if an element has a parent which // does *not* have the same owner, we/ must remove it and all its // children. for (const auto &iter : Entries) { BlockSet &CurrGroup = IndependentGroups[iter]; BlockList ToInvalidate; for (const auto &iter : CurrGroup) { Block *Child = iter; for (const auto &iter : Child->BranchesIn) { Block *Parent = iter; if (Ignore && contains(*Ignore, Parent)) continue; if (Helper.Ownership[Parent] != Helper.Ownership[Child]) ToInvalidate.push_back(Child); } } while (!ToInvalidate.empty()) { Block *Invalidatee = ToInvalidate.front(); ToInvalidate.pop_front(); Helper.InvalidateWithChildren(Invalidatee); } } // Remove empty groups for (const auto &iter : Entries) if (IndependentGroups[iter].empty()) IndependentGroups.erase(iter); } Shape *MakeMultiple(BlockSet &Blocks, BlockSet &Entries, BlockBlockSetMap &IndependentGroups, Shape *Prev, BlockSet &NextEntries) { bool Fused = isa(Prev); MultipleShape *Multiple = new MultipleShape(); Notice(Multiple); BlockSet CurrEntries; for (auto &iter : IndependentGroups) { Block *CurrEntry = iter.first; BlockSet &CurrBlocks = iter.second; // Create inner block CurrEntries.clear(); CurrEntries.insert(CurrEntry); for (const auto &CurrInner : CurrBlocks) { // Remove the block from the remaining blocks Blocks.remove(CurrInner); // Find new next entries and fix branches to them for (auto iter = CurrInner->BranchesOut.begin(); iter != CurrInner->BranchesOut.end();) { Block *CurrTarget = iter->first; auto Next = iter; Next++; if (!contains(CurrBlocks, CurrTarget)) { NextEntries.insert(CurrTarget); Solipsize(CurrTarget, Branch::Break, Multiple, CurrBlocks); } iter = Next; // increment carefully because Solipsize can remove us } } Multiple->InnerMap[CurrEntry->Id] = Process(CurrBlocks, CurrEntries, nullptr); // If we are not fused, then our entries will actually be checked if (!Fused) CurrEntry->IsCheckedMultipleEntry = true; } // Add entries not handled as next entries, they are deferred for (const auto &Entry : Entries) if (!contains(IndependentGroups, Entry)) NextEntries.insert(Entry); // The multiple has been created, we can decide how to implement it if (Multiple->InnerMap.size() >= RelooperMultipleSwitchThreshold) { Multiple->UseSwitch = true; Multiple->Breaks++; // switch captures breaks } return Multiple; } // Main function. // Process a set of blocks with specified entries, returns a shape // The Make* functions receive a NextEntries. If they fill it with data, // those are the entries for the ->Next block on them, and the blocks // are what remains in Blocks (which Make* modify). In this way // we avoid recursing on Next (imagine a long chain of Simples, if we // recursed we could blow the stack). Shape *Process(BlockSet &Blocks, BlockSet &InitialEntries, Shape *Prev) { BlockSet *Entries = &InitialEntries; BlockSet TempEntries[2]; int CurrTempIndex = 0; BlockSet *NextEntries; Shape *Ret = nullptr; auto Make = [&](Shape *Temp) { if (Prev) Prev->Next = Temp; if (!Ret) Ret = Temp; Prev = Temp; Entries = NextEntries; }; while (1) { CurrTempIndex = 1 - CurrTempIndex; NextEntries = &TempEntries[CurrTempIndex]; NextEntries->clear(); if (Entries->empty()) return Ret; if (Entries->size() == 1) { Block *Curr = *(Entries->begin()); if (Curr->BranchesIn.empty()) { // One entry, no looping ==> Simple Make(MakeSimple(Blocks, Curr, *NextEntries)); if (NextEntries->empty()) return Ret; continue; } // One entry, looping ==> Loop Make(MakeLoop(Blocks, *Entries, *NextEntries)); if (NextEntries->empty()) return Ret; continue; } // More than one entry, try to eliminate through a Multiple groups of // independent blocks from an entry/ies. It is important to remove // through multiples as opposed to looping since the former is more // performant. BlockBlockSetMap IndependentGroups; FindIndependentGroups(*Entries, IndependentGroups); if (!IndependentGroups.empty()) { // We can handle a group in a multiple if its entry cannot be reached // by another group. // Note that it might be reachable by itself - a loop. But that is // fine, we will create a loop inside the multiple block (which // is the performant order to do it). for (auto iter = IndependentGroups.begin(); iter != IndependentGroups.end();) { Block *Entry = iter->first; BlockSet &Group = iter->second; auto curr = iter++; // iterate carefully, we may delete for (BlockSet::iterator iterBranch = Entry->BranchesIn.begin(); iterBranch != Entry->BranchesIn.end(); iterBranch++) { Block *Origin = *iterBranch; if (!contains(Group, Origin)) { // Reached from outside the group, so we cannot handle this IndependentGroups.erase(curr); break; } } } // As an optimization, if we have 2 independent groups, and one is a // small dead end, we can handle only that dead end. // The other then becomes a Next - without nesting in the code and // recursion in the analysis. // TODO: if the larger is the only dead end, handle that too // TODO: handle >2 groups // TODO: handle not just dead ends, but also that do not branch to the // NextEntries. However, must be careful there since we create a // Next, and that Next can prevent eliminating a break (since we no // longer naturally reach the same place), which may necessitate a // one-time loop, which makes the unnesting pointless. if (IndependentGroups.size() == 2) { // Find the smaller one auto iter = IndependentGroups.begin(); Block *SmallEntry = iter->first; auto SmallSize = iter->second.size(); iter++; Block *LargeEntry = iter->first; auto LargeSize = iter->second.size(); if (SmallSize != LargeSize) { // ignore the case where they are // identical - keep things symmetrical // there if (SmallSize > LargeSize) { Block *Temp = SmallEntry; SmallEntry = LargeEntry; LargeEntry = Temp; // Note: we did not flip the Sizes too, they // are now invalid. TODO: use the smaller // size as a limit? } // Check if dead end bool DeadEnd = true; BlockSet &SmallGroup = IndependentGroups[SmallEntry]; for (const auto &Curr : SmallGroup) { for (const auto &iter : Curr->BranchesOut) { Block *Target = iter.first; if (!contains(SmallGroup, Target)) { DeadEnd = false; break; } } if (!DeadEnd) break; } if (DeadEnd) IndependentGroups.erase(LargeEntry); } } if (!IndependentGroups.empty()) // Some groups removable ==> Multiple Make(MakeMultiple(Blocks, *Entries, IndependentGroups, Prev, *NextEntries)); if (NextEntries->empty()) return Ret; continue; } // No independent groups, must be loopable ==> Loop Make(MakeLoop(Blocks, *Entries, *NextEntries)); if (NextEntries->empty()) return Ret; continue; } } }; // Main BlockSet AllBlocks; for (const auto &Curr : Pre.Live) { AllBlocks.insert(Curr); } BlockSet Entries; Entries.insert(Entry); Root = Analyzer(this).Process(AllBlocks, Entries, nullptr); assert(Root); /// /// Relooper post-optimizer /// struct PostOptimizer { RelooperAlgorithm *Parent; std::stack LoopStack; PostOptimizer(RelooperAlgorithm *ParentInit) : Parent(ParentInit) {} void ShapeSwitch(Shape* var, std::function simple, std::function multiple, std::function loop) { switch (var->getKind()) { case Shape::SK_Simple: { simple(cast(var)); break; } case Shape::SK_Multiple: { multiple(cast(var)); break; } case Shape::SK_Loop: { loop(cast(var)); break; } } } // Find the blocks that natural control flow can get us directly to, or // through a multiple that we ignore void FollowNaturalFlow(Shape *S, BlockSet &Out) { ShapeSwitch(S, [&](SimpleShape* Simple) { Out.insert(Simple->Inner); }, [&](MultipleShape* Multiple) { for (const auto &iter : Multiple->InnerMap) { FollowNaturalFlow(iter.second, Out); } FollowNaturalFlow(Multiple->Next, Out); }, [&](LoopShape* Loop) { FollowNaturalFlow(Loop->Inner, Out); }); } void FindNaturals(Shape *Root, Shape *Otherwise = nullptr) { if (Root->Next) { Root->Natural = Root->Next; FindNaturals(Root->Next, Otherwise); } else { Root->Natural = Otherwise; } ShapeSwitch(Root, [](SimpleShape* Simple) { }, [&](MultipleShape* Multiple) { for (const auto &iter : Multiple->InnerMap) { FindNaturals(iter.second, Root->Natural); } }, [&](LoopShape* Loop){ FindNaturals(Loop->Inner, Loop->Inner); }); } // Remove unneeded breaks and continues. // A flow operation is trivially unneeded if the shape we naturally get to // by normal code execution is the same as the flow forces us to. void RemoveUnneededFlows(Shape *Root, Shape *Natural = nullptr, LoopShape *LastLoop = nullptr, unsigned Depth = 0) { BlockSet NaturalBlocks; FollowNaturalFlow(Natural, NaturalBlocks); Shape *Next = Root; while (Next) { Root = Next; Next = nullptr; ShapeSwitch( Root, [&](SimpleShape* Simple) { if (Simple->Inner->BranchVar) LastLoop = nullptr; // a switch clears out the loop (TODO: only for // breaks, not continue) if (Simple->Next) { if (!Simple->Inner->BranchVar && Simple->Inner->ProcessedBranchesOut.size() == 2 && Depth < RelooperNestingLimit) { // If there is a next block, we already know at Simple // creation time to make direct branches, and we can do // nothing more in general. But, we try to optimize the // case of a break and a direct: This would normally be // if (break?) { break; } .. // but if we make sure to nest the else, we can save the // break, // if (!break?) { .. } // This is also better because the more canonical nested // form is easier to further optimize later. The // downside is more nesting, which adds to size in builds with // whitespace. // Note that we avoid switches, as it complicates control flow // and is not relevant for the common case we optimize here. bool Found = false; bool Abort = false; for (const auto &iter : Simple->Inner->ProcessedBranchesOut) { Block *Target = iter.first; Branch *Details = iter.second.get(); if (Details->Type == Branch::Break) { Found = true; if (!contains(NaturalBlocks, Target)) Abort = true; } else if (Details->Type != Branch::Direct) Abort = true; } if (Found && !Abort) { for (const auto &iter : Simple->Inner->ProcessedBranchesOut) { Branch *Details = iter.second.get(); if (Details->Type == Branch::Break) { Details->Type = Branch::Direct; if (MultipleShape *Multiple = dyn_cast(Details->Ancestor)) Multiple->Breaks--; } else { assert(Details->Type == Branch::Direct); Details->Type = Branch::Nested; } } } Depth++; // this optimization increases depth, for us and all // our next chain (i.e., until this call returns) } Next = Simple->Next; } else { // If there is no next then Natural is where we will // go to by doing nothing, so we can potentially optimize some // branches to direct. for (const auto &iter : Simple->Inner->ProcessedBranchesOut) { Block *Target = iter.first; Branch *Details = iter.second.get(); if (Details->Type != Branch::Direct && contains(NaturalBlocks, Target)) { // note: cannot handle split blocks Details->Type = Branch::Direct; if (MultipleShape *Multiple = dyn_cast(Details->Ancestor)) Multiple->Breaks--; } else if (Details->Type == Branch::Break && LastLoop && LastLoop->Natural == Details->Ancestor->Natural) { // it is important to simplify breaks, as simpler breaks // enable other optimizations Details->Labeled = false; if (MultipleShape *Multiple = dyn_cast(Details->Ancestor)) Multiple->Breaks--; } } } }, [&](MultipleShape* Multiple) { for (const auto &iter : Multiple->InnerMap) { RemoveUnneededFlows(iter.second, Multiple->Next, Multiple->Breaks ? nullptr : LastLoop, Depth + 1); } Next = Multiple->Next; }, [&](LoopShape* Loop) { RemoveUnneededFlows(Loop->Inner, Loop->Inner, Loop, Depth + 1); Next = Loop->Next; }); } } // After we know which loops exist, we can calculate which need to be // labeled void FindLabeledLoops(Shape *Root) { Shape *Next = Root; while (Next) { Root = Next; Next = nullptr; ShapeSwitch( Root, [&](SimpleShape *Simple) { MultipleShape *Fused = dyn_cast(Root->Next); // If we are fusing a Multiple with a loop into this Simple, then // visit it now if (Fused && Fused->Breaks) LoopStack.push(Fused); if (Simple->Inner->BranchVar) LoopStack.push(nullptr); // a switch means breaks are now useless, // push a dummy if (Fused) { if (Fused->UseSwitch) LoopStack.push(nullptr); // a switch means breaks are now // useless, push a dummy for (const auto &iter : Fused->InnerMap) { FindLabeledLoops(iter.second); } } for (const auto &iter : Simple->Inner->ProcessedBranchesOut) { Branch *Details = iter.second.get(); if (Details->Type == Branch::Break || Details->Type == Branch::Continue) { assert(!LoopStack.empty()); if (Details->Ancestor != LoopStack.top() && Details->Labeled) { if (MultipleShape *Multiple = dyn_cast(Details->Ancestor)) { Multiple->Labeled = true; } else { LoopShape *Loop = cast(Details->Ancestor); Loop->Labeled = true; } } else { Details->Labeled = false; } } if (Fused && Fused->UseSwitch) LoopStack.pop(); if (Simple->Inner->BranchVar) LoopStack.pop(); if (Fused && Fused->Breaks) LoopStack.pop(); if (Fused) Next = Fused->Next; else Next = Root->Next; } } , [&](MultipleShape* Multiple) { if (Multiple->Breaks) LoopStack.push(Multiple); for (const auto &iter : Multiple->InnerMap) FindLabeledLoops(iter.second); if (Multiple->Breaks) LoopStack.pop(); Next = Root->Next; } , [&](LoopShape* Loop) { LoopStack.push(Loop); FindLabeledLoops(Loop->Inner); LoopStack.pop(); Next = Root->Next; }); } } void Process(Shape * Root) { FindNaturals(Root); RemoveUnneededFlows(Root); FindLabeledLoops(Root); } }; PostOptimizer(this).Process(Root); }