forked from OSchip/llvm-project
985 lines
36 KiB
C++
985 lines
36 KiB
C++
//===-- 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 <cstring>
|
|
#include <cstdlib>
|
|
#include <functional>
|
|
#include <list>
|
|
#include <stack>
|
|
#include <string>
|
|
|
|
#define DEBUG_TYPE "relooper"
|
|
|
|
using namespace llvm;
|
|
using namespace Relooper;
|
|
|
|
static cl::opt<int> 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<unsigned> 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<unsigned> 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<Block *> Blocks;
|
|
std::deque<Shape *> 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<const BasicBlock *, Block *> BB2B;
|
|
std::map<const Block *, const BasicBlock *> 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<Block *, BlockSet> BlockBlockSetMap;
|
|
typedef std::list<Block *> BlockList;
|
|
|
|
template <class T, class U>
|
|
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<void *>(const_cast<char *>(Condition)));
|
|
free(static_cast<void *>(const_cast<char *>(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<void *>(const_cast<char *>(Code)));
|
|
free(static_cast<void *>(const_cast<char *>(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<Branch>(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<Branch> Details;
|
|
Details.swap(Prior->BranchesOut[Original]);
|
|
Prior->BranchesOut[Split] = make_unique<Branch>(Details->Condition,
|
|
Details->Code);
|
|
for (const auto &iter : Original->BranchesOut) {
|
|
Block *Post = iter.first;
|
|
Branch *Details = iter.second.get();
|
|
Split->BranchesOut[Post] = make_unique<Branch>(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<Branch> PriorOut;
|
|
PriorOut.swap(Prior->BranchesOut[Target]);
|
|
PriorOut->Ancestor = Ancestor;
|
|
PriorOut->Type = Type;
|
|
if (MultipleShape *Multiple = dyn_cast<MultipleShape>(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<Block *, Block *> 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<SimpleShape>(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<Shape *> LoopStack;
|
|
|
|
PostOptimizer(RelooperAlgorithm *ParentInit) : Parent(ParentInit) {}
|
|
|
|
void ShapeSwitch(Shape* var,
|
|
std::function<void (SimpleShape*)> simple,
|
|
std::function<void (MultipleShape*)> multiple,
|
|
std::function<void (LoopShape*)> loop) {
|
|
switch (var->getKind()) {
|
|
case Shape::SK_Simple: {
|
|
simple(cast<SimpleShape>(var));
|
|
break;
|
|
}
|
|
case Shape::SK_Multiple: {
|
|
multiple(cast<MultipleShape>(var));
|
|
break;
|
|
}
|
|
case Shape::SK_Loop: {
|
|
loop(cast<LoopShape>(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<MultipleShape>(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<MultipleShape>(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<MultipleShape>(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<MultipleShape>(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<MultipleShape>(Details->Ancestor)) {
|
|
Multiple->Labeled = true;
|
|
} else {
|
|
LoopShape *Loop = cast<LoopShape>(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);
|
|
}
|