mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
be72da8b48
We now reduce the number of cases in which we restart the collection of the `DivergenceDescriptor`s after a IDS modification on the `ScopeGraph` is performed. We previously restarted the collection after each IDS transformation. Now, restart is only performed after IDS is applied on a `Conditional` node whose all successors are divergent exits which are `goto` exits. After such `Conditional` is processed, it may happen that a new `goto` exit becomes divergent for another `Conditional` upwards in the `ScopeGraph`. Therefore, if we do not restart, it may happen that a non-`goto` exit is processed before a `goto` exit, which may lead to suboptimal situations in the emitted number of `goto`s, due to how IDS interacts with `MaterializeTrivialGotos`.
687 lines
27 KiB
C++
687 lines
27 KiB
C++
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/IR/BasicBlock.h"
|
|
#include "llvm/IR/CFG.h"
|
|
#include "llvm/IR/Function.h"
|
|
#include "llvm/IR/IRBuilder.h"
|
|
#include "llvm/IR/Instructions.h"
|
|
#include "llvm/IR/LLVMContext.h"
|
|
#include "llvm/Support/GenericDomTree.h"
|
|
|
|
#include "revng/ADT/Concepts.h"
|
|
#include "revng/RestructureCFG/InlineDivergentScopesPass.h"
|
|
#include "revng/RestructureCFG/ScopeGraphAlgorithms.h"
|
|
#include "revng/RestructureCFG/ScopeGraphGraphTraits.h"
|
|
#include "revng/RestructureCFG/ScopeGraphUtils.h"
|
|
#include "revng/Support/Assert.h"
|
|
#include "revng/Support/GraphAlgorithms.h"
|
|
#include "revng/Support/IRHelpers.h"
|
|
|
|
using namespace llvm;
|
|
|
|
/// Manager class to automatically handle the creation and clean up of the
|
|
/// `PlaceHolderTarget`
|
|
class PlaceHolderTargetManager {
|
|
private:
|
|
BasicBlock *PlaceHolderTarget;
|
|
|
|
public:
|
|
PlaceHolderTargetManager(Function &F) {
|
|
LLVMContext &Context = getContext(&F);
|
|
PlaceHolderTarget = BasicBlock::Create(Context,
|
|
"placeholder_destination",
|
|
&F);
|
|
}
|
|
|
|
~PlaceHolderTargetManager() { PlaceHolderTarget->eraseFromParent(); }
|
|
|
|
BasicBlock *operator*() const { return PlaceHolderTarget; }
|
|
};
|
|
|
|
static bool isConditional(BasicBlock *Node) {
|
|
auto Successors = children<Scope<BasicBlock *>>(Node);
|
|
size_t NumSuccessors = std::distance(Successors.begin(), Successors.end());
|
|
if (NumSuccessors >= 2) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Helper function which detects if `Node` is an exit for the `ScopeGraph`
|
|
static bool isExit(BasicBlock *Node) {
|
|
auto Successors = children<Scope<BasicBlock *>>(Node);
|
|
return std::ranges::empty(Successors);
|
|
}
|
|
|
|
/// Helper function to retrieve the target BasicBlock of a `GotoBlock`
|
|
static BasicBlock *getGotoTarget(const BasicBlock *BB) {
|
|
revng_assert(isGotoBlock(BB));
|
|
const Instruction *Terminator = BB->getTerminator();
|
|
const BranchInst *Branch = llvm::cast<BranchInst>(Terminator);
|
|
revng_assert(Branch->isUnconditional());
|
|
return Branch->getSuccessor(0);
|
|
}
|
|
|
|
/// Helper function to collect all the exit nodes on the `ScopeGraph`
|
|
static SmallVector<BasicBlock *> getExits(Function &F,
|
|
BasicBlock *PlaceHolderTarget) {
|
|
SmallVector<BasicBlock *> TrueExits;
|
|
SmallVector<BasicBlock *> GotoExits;
|
|
Scope<Function *> ScopeGraph(&F);
|
|
for (BasicBlock *BB : nodes(ScopeGraph)) {
|
|
|
|
// We need to exclude the `PlaceHolderTarget` which is a temporary block
|
|
// which incidentally happens to be an exit, but will be removed before the
|
|
// end of the pass
|
|
if (BB != PlaceHolderTarget and isExit(BB)) {
|
|
if (not isGotoBlock(BB)) {
|
|
TrueExits.push_back(BB);
|
|
} else {
|
|
GotoExits.push_back(BB);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We now order the `GotoExits` by placing first the ones whose `goto` target
|
|
// comes first in `post_order` in the graph. This criterion should maximize
|
|
// the opportunities of performing `MaterializeTrivialGoto` transformations.
|
|
|
|
// Create a map to store the `post_order` indexes, that we can later reuse
|
|
// when performing the comparison
|
|
llvm::DenseMap<const BasicBlock *, unsigned> POIndexes;
|
|
|
|
// Fill the map
|
|
unsigned Index = 0;
|
|
for (BasicBlock *BB : post_order(ScopeGraph)) {
|
|
POIndexes[BB] = Index;
|
|
Index++;
|
|
}
|
|
|
|
// Sort the `GotoExits` according to the above criterion
|
|
std::sort(GotoExits.begin(),
|
|
GotoExits.end(),
|
|
[&POIndexes](const BasicBlock *A, const BasicBlock *B) {
|
|
return POIndexes[getGotoTarget(A)] < POIndexes[getGotoTarget(B)];
|
|
});
|
|
|
|
// Merge the `TrueExits` with the `GotoExits`
|
|
GotoExits.append(TrueExits);
|
|
return GotoExits;
|
|
}
|
|
|
|
/// Helper to obtain the immediate dominator `BasicBlock`, if present
|
|
static BasicBlock *
|
|
getImmediateDominator(BasicBlock *N,
|
|
DomTreeOnView<BasicBlock, Scope> &DomTree) {
|
|
auto *Node = DomTree.getNode(N)->getIDom();
|
|
if (Node) {
|
|
return Node->getBlock();
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
/// Define a concept to restrict the usage of `replaceSuccessors` over a
|
|
/// `SuccessorsToRemove` parameter of type the `SmallSet`s and `SmallVector`
|
|
template<typename T, typename ValueType>
|
|
concept IterableOfBasicBlockPtrs = requires(T Container) {
|
|
{ std::begin(Container) } -> std::input_iterator;
|
|
{ std::end(Container) };
|
|
{ *std::begin(Container) } -> std::convertible_to<ValueType>;
|
|
};
|
|
|
|
/// Helper function which substitutes some successors in the `Terminator` with
|
|
/// `NewTarget`
|
|
template<IterableOfBasicBlockPtrs<BasicBlock *> Container>
|
|
static void replaceSuccessors(Instruction *Terminator,
|
|
Container &SuccessorsToRemove,
|
|
BasicBlock *NewTarget) {
|
|
for (BasicBlock *Successor : SuccessorsToRemove) {
|
|
Terminator->replaceSuccessorWith(Successor, NewTarget);
|
|
}
|
|
}
|
|
|
|
/// Helper function which simplifies all the terminators containing
|
|
/// `PlaceHolderTarget`, by removing it
|
|
static void simplifyTerminator(BasicBlock *BB,
|
|
const BasicBlock *PlaceHolderTarget) {
|
|
Instruction *Terminator = BB->getTerminator();
|
|
|
|
if (auto *Branch = dyn_cast<BranchInst>(Terminator)) {
|
|
if (Branch->isConditional()) {
|
|
|
|
// We want to transform a conditional branch with one of the destination
|
|
// set to `PlaceHolderTarget` to a non conditional branch
|
|
BasicBlock *SingleDestination = nullptr;
|
|
|
|
if (Branch->getSuccessor(0) == PlaceHolderTarget) {
|
|
SingleDestination = Branch->getSuccessor(1);
|
|
revng_assert(SingleDestination != PlaceHolderTarget);
|
|
} else if (Branch->getSuccessor(1) == PlaceHolderTarget) {
|
|
SingleDestination = Branch->getSuccessor(0);
|
|
revng_assert(SingleDestination != PlaceHolderTarget);
|
|
}
|
|
|
|
// If we found a `BranchInst` candidate for promotion, we substitute it
|
|
// with an unconditional branch
|
|
if (SingleDestination) {
|
|
IRBuilder<> Builder(Terminator);
|
|
Builder.CreateBr(SingleDestination);
|
|
|
|
// We remove the old conditional branch
|
|
Terminator->eraseFromParent();
|
|
}
|
|
}
|
|
} else if (auto *Switch = dyn_cast<SwitchInst>(Terminator)) {
|
|
|
|
// Handle the simplification when `PlaceHolderTager` is the default
|
|
// destination of the `SwitchInst`
|
|
BasicBlock *DefaultTarget = Switch->getDefaultDest();
|
|
if (DefaultTarget == PlaceHolderTarget) {
|
|
|
|
// We promote the first case, not pointing to `PlaceHolderTarget`. If we
|
|
// promote a case already pointing to `PlaceHolderTarget`, this would, in
|
|
// turn, cause the `default` case to not be simplified ever.
|
|
for (auto CaseIt = Switch->case_begin(); CaseIt != Switch->case_end();
|
|
++CaseIt) {
|
|
if (CaseIt->getCaseSuccessor() != PlaceHolderTarget) {
|
|
Switch->setDefaultDest(CaseIt->getCaseSuccessor());
|
|
Switch->removeCase(CaseIt);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle the simplification when `PlaceHolderTarget` is part the standard
|
|
// `case`s
|
|
for (auto CaseIt = Switch->case_begin(); CaseIt != Switch->case_end();) {
|
|
if (CaseIt->getCaseSuccessor() == PlaceHolderTarget) {
|
|
|
|
// We do not want to have a situation where the `PlaceHolderTarget` is
|
|
// both the `default` successor of a `switch` and one of its standard
|
|
// case
|
|
CaseIt = Switch->removeCase(CaseIt);
|
|
} else {
|
|
++CaseIt;
|
|
}
|
|
}
|
|
|
|
// It should never be the case that we end up with a `switch` having only
|
|
// `PlaceHolderTarget` as its successor
|
|
if (Switch->getNumCases() == 0
|
|
and Switch->getDefaultDest() == PlaceHolderTarget) {
|
|
revng_abort();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Helper function which collects all the nodes reachable from a vector of
|
|
/// nodes (`Successors`)
|
|
static MapVector<BasicBlock *, SmallSet<BasicBlock *, 4>>
|
|
getReachablesFromSuccessor(BasicBlock *N) {
|
|
|
|
using ScopeBlock = Scope<BasicBlock *>;
|
|
SetVector<BasicBlock *> Successors;
|
|
for (BasicBlock *Successor : children<ScopeBlock>(N)) {
|
|
Successors.insert(Successor);
|
|
}
|
|
|
|
MapVector<BasicBlock *, SmallSet<BasicBlock *, 4>> ReachablesFromSuccessor;
|
|
for (auto *Successor : Successors) {
|
|
SmallSet<BasicBlock *, 4> ReachableExits;
|
|
|
|
// Explore all the exits that are reachable from each Successor.
|
|
// For having a divergent exit, we need to find a set of
|
|
// successors that reach only the exit under analysis
|
|
auto &Reachables = ReachablesFromSuccessor[Successor];
|
|
for (auto *DFSNode : depth_first(Scope<BasicBlock *>(Successor))) {
|
|
if (isExit(DFSNode)) {
|
|
Reachables.insert(DFSNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ReachablesFromSuccessor;
|
|
}
|
|
|
|
/// We define a `Descriptor` for the information needed to identify a
|
|
/// `Divergence`. Specifically, the divergence is composed by the conditional
|
|
/// where divergence originates, a set of divergent successors, and by the exit
|
|
/// node which is divergent wrt. those divergent successors.
|
|
struct DivergenceDescriptor {
|
|
BasicBlock *Conditional;
|
|
SmallSet<BasicBlock *, 4> DivergentSuccessors;
|
|
BasicBlock *Exit;
|
|
};
|
|
|
|
/// Helper function that tries to identify a `DivergenceDescriptor`
|
|
static std::optional<DivergenceDescriptor>
|
|
electDivergence(BasicBlock *Candidate,
|
|
BasicBlock *Exit,
|
|
DomTreeOnView<BasicBlock, Scope> &DomTree) {
|
|
|
|
// Check if it is the conditional making it a divergent node
|
|
if (isConditional(Candidate)) {
|
|
revng_assert(DomTree.dominates(Candidate, Exit));
|
|
|
|
MapVector<BasicBlock *, SmallSet<BasicBlock *, 4>>
|
|
ReachablesFromSuccessor = getReachablesFromSuccessor(Candidate);
|
|
size_t SuccessorsSize = ReachablesFromSuccessor.size();
|
|
|
|
SmallSet<BasicBlock *, 4> DivergentSuccessors;
|
|
for (const auto &[Successor, ReachableExits] : ReachablesFromSuccessor) {
|
|
if (ReachableExits.size() == 1 and *ReachableExits.begin() == Exit) {
|
|
DivergentSuccessors.insert(Successor);
|
|
} else if (ReachableExits.contains(Exit)) {
|
|
|
|
// If we reach `Exit` from a successor which is not a candidate for
|
|
// being divergent, it means that `Exit` is reached also by a non
|
|
// divergent successor, and this contradicts the definition, so we
|
|
// cannot find a divergence here, and we return a `nullopt`
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
// We proceed only if there are some divergent exits and some
|
|
// non divergent exits, it doesn't make sense to transform a situation where
|
|
// all the edges are all divergent or all non-divergent
|
|
if (DivergentSuccessors.size() < SuccessorsSize) {
|
|
|
|
// We employ this `std::optional` as return value, in order to signal if
|
|
// we identified a candidate for IDS
|
|
return DivergenceDescriptor{ Candidate, DivergentSuccessors, Exit };
|
|
}
|
|
}
|
|
|
|
// When no divergence was found, we signal it by returning `nullopt`
|
|
return std::nullopt;
|
|
}
|
|
|
|
/// Helper function which create a single entry point block where a divergence
|
|
/// is entered into by multiple successors of a `Conditional`
|
|
static void createHead(BasicBlock *Conditional,
|
|
DivergenceDescriptor &Divergence,
|
|
BasicBlock *PlaceHolderTarget) {
|
|
LLVMContext &Context = getContext(Conditional);
|
|
Function *F = Conditional->getParent();
|
|
Instruction *ConditionalTerminator = Conditional->getTerminator();
|
|
BasicBlock *Head = BasicBlock::Create(Context,
|
|
Conditional->getName() + "_head_ids",
|
|
F);
|
|
Instruction *HeadTerminator = ConditionalTerminator->clone();
|
|
IRBuilder<> HeadBuilder(Head);
|
|
HeadBuilder.Insert(HeadTerminator);
|
|
|
|
// Collect the `Successor`s composing the local `Divergence`
|
|
SmallSet<BasicBlock *, 4> LocalDivergentSuccessors = Divergence
|
|
.DivergentSuccessors;
|
|
|
|
// And collect all the `Successor`s that do not make up the local
|
|
// `Divergence`
|
|
SmallVector<BasicBlock *> LocalNonDivergentSuccessors;
|
|
for (BasicBlock *Successor : children<Scope<BasicBlock *>>(Head)) {
|
|
if (not LocalDivergentSuccessors.contains(Successor)) {
|
|
LocalNonDivergentSuccessors.push_back(Successor);
|
|
}
|
|
}
|
|
|
|
// The `Head` only connects the `LocalDivergentSuccessors`, we simplify
|
|
// away the other successors
|
|
replaceSuccessors(HeadTerminator,
|
|
LocalNonDivergentSuccessors,
|
|
PlaceHolderTarget);
|
|
simplifyTerminator(Head, PlaceHolderTarget);
|
|
|
|
// If we insert the `Head`, we replace all the edges going to the
|
|
// `DivergentSuccessors` in the conditional so that they go to `Head`
|
|
replaceSuccessors(ConditionalTerminator, LocalDivergentSuccessors, Head);
|
|
}
|
|
|
|
/// Helper function that performs the IDS transformation
|
|
static void
|
|
performMultipleIDS(const ScopeGraphBuilder &SGBuilder,
|
|
SmallVector<DivergenceDescriptor> &MultipleDivergences,
|
|
BasicBlock *PlaceHolderTarget) {
|
|
|
|
// Create the new `BasicBlock` representing the `C'` conditional
|
|
// inserted by the IDS transformation. We will refer to this block as the
|
|
// `Tail` (of the IDS group of nodes).
|
|
BasicBlock *Conditional = MultipleDivergences[0].Conditional;
|
|
LLVMContext &Context = getContext(Conditional);
|
|
Function *F = Conditional->getParent();
|
|
BasicBlock *Tail = BasicBlock::Create(Context,
|
|
Conditional->getName() + "_ids",
|
|
F);
|
|
|
|
revng_assert(Tail->empty());
|
|
|
|
// We clone the terminator already present in `BasicBlock` `Conditional`, so
|
|
// that a superset of the correct final successors are already connected to
|
|
// `Tail`.
|
|
Instruction *ConditionalTerminator = Conditional->getTerminator();
|
|
Instruction *TailTerminator = ConditionalTerminator->clone();
|
|
IRBuilder<> TailBuilder(Tail);
|
|
TailBuilder.Insert(TailTerminator);
|
|
|
|
// Populate the `DivergentSuccessors` summing all ones present in each
|
|
// `DivergenceDescriptor`
|
|
SmallSet<BasicBlock *, 4> DivergentSuccessors;
|
|
for (auto Divergence : MultipleDivergences) {
|
|
DivergentSuccessors.insert(Divergence.DivergentSuccessors.begin(),
|
|
Divergence.DivergentSuccessors.end());
|
|
}
|
|
|
|
// We connect the `Conditional` to `Tail`, by making sure that all
|
|
// the previous slots and cases going to the nondivergent exits, are now
|
|
// connected to the `Tail` block, in order to preserve the original semantics.
|
|
// The original paths going to the nondivergent exits, are preserved by the
|
|
// the fact that the `Terminator` has been cloned into `Tail`.
|
|
SmallVector<BasicBlock *> NonDivergentSuccessors;
|
|
for (BasicBlock *Successor : children<Scope<BasicBlock *>>(Conditional)) {
|
|
if (not DivergentSuccessors.contains(Successor)) {
|
|
NonDivergentSuccessors.push_back(Successor);
|
|
}
|
|
}
|
|
|
|
revng_assert(not DivergentSuccessors.empty());
|
|
|
|
// It may be that for a specific `Conditional` we elected all the exits as
|
|
// divergent. In this case, we need to arbitrarily elect one exit as the `one
|
|
// true exit`, and we do this by electing the last element in
|
|
// `MultipleDivergences`.
|
|
if (NonDivergentSuccessors.empty()) {
|
|
revng_assert(MultipleDivergences.size() > 1);
|
|
|
|
// The fact that we elect the last element in `MultipleDivergences` as the
|
|
// only non divergent exit, is very important, due to how the `exit` blocks
|
|
// are enqueued in `MupltipleDivergences`. If present, the `Goto` exit will
|
|
// be in the first positions, followed by the original exit blocks. In this
|
|
// way, if present, we always elect a original exit as the non divergent
|
|
// exit.
|
|
size_t LastElementIndex = MultipleDivergences.size() - 1;
|
|
|
|
// The first element in `MultipleDivergencies` is arbitrarily elected as the
|
|
// component not going to a divergent exit
|
|
NonDivergentSuccessors
|
|
.append(MultipleDivergences[LastElementIndex].DivergentSuccessors.begin(),
|
|
MultipleDivergences[LastElementIndex].DivergentSuccessors.end());
|
|
|
|
// We remove the `Successor`s from the `DivergentSuccessors`
|
|
for (BasicBlock *Successor :
|
|
MultipleDivergences[LastElementIndex].DivergentSuccessors) {
|
|
DivergentSuccessors.erase(Successor);
|
|
}
|
|
|
|
// We erase the first `DivergenceDescriptor` from the `MultipleDivergences`
|
|
MultipleDivergences.erase(MultipleDivergences.begin() + LastElementIndex);
|
|
}
|
|
|
|
// Decide if we need a new `Head` that collects the entry of the divergent
|
|
// scope. This is needed if one `Divergence` is composed by multiple
|
|
// `Successor`s, since in that case there is no clear entry to the inlined
|
|
// divergent scope. We therefore add a `Head` block that collects the entry
|
|
// point, so that this new node will dominate the eventual postdominator
|
|
// common to the multiple `Successor`s.
|
|
for (auto Divergence : MultipleDivergences) {
|
|
if (Divergence.DivergentSuccessors.size() > 1) {
|
|
createHead(Conditional, Divergence, PlaceHolderTarget);
|
|
}
|
|
}
|
|
|
|
// Move the `NonDivergentSuccessors` outgoing edges from `Conditional` so that
|
|
// they point to `Tail`
|
|
replaceSuccessors(ConditionalTerminator, NonDivergentSuccessors, Tail);
|
|
|
|
// We remove from the `Terminator` of `Tail`, all the edges that
|
|
// target `DivergentSuccessor`, since it will be only reached by
|
|
// `Conditional`. We do this in two steps, we first substitute the original
|
|
// successor with `PlaceHolderTarget`, and then we invoke
|
|
// `simplifyTerminator`, which will take care of simplifying away the
|
|
// unnecessary successors, both for `brcond`s and `switch`es.
|
|
replaceSuccessors(TailTerminator, DivergentSuccessors, PlaceHolderTarget);
|
|
simplifyTerminator(Tail, PlaceHolderTarget);
|
|
|
|
// We add a `scope_closer` edge between the divergent exit node and
|
|
// the `Tail` node for all the exits
|
|
for (auto Divergence : MultipleDivergences) {
|
|
SGBuilder.addScopeCloser(Divergence.Exit, Tail);
|
|
}
|
|
}
|
|
|
|
/// Helper function which returns if a `DivergenceDescriptor` is compatible with
|
|
/// all the ones already present in a `Collection`. Being compatible means that
|
|
/// they insist on the same conditional, have non-overlapping `Exit` blocks, and
|
|
/// do not have any overlapping `DivergentSuccessors`. We currently use this
|
|
/// helper only in an assertion to double check that the `Divergencies`
|
|
/// collected respect the compatibility criterion.
|
|
static bool isCompatible(const SmallVector<DivergenceDescriptor> &Collection,
|
|
const DivergenceDescriptor &NewDescriptor) {
|
|
|
|
// A new element is always compatible with an empty `Collection`
|
|
if (Collection.empty())
|
|
return true;
|
|
|
|
// A new element is compatible when it shares the `Conditional` with the ones
|
|
// already in the `Collection`
|
|
if (Collection[0].Conditional != NewDescriptor.Conditional)
|
|
return false;
|
|
|
|
// All the `DivergenceDescriptor`s involving a certain `Conditional`, must
|
|
// partition, so that there is no overlap: 1: the divergent `Exit` reached by
|
|
// each `DivergenceDescriptor` 2: the set of the `DivergentSuccessors`
|
|
for (const auto &Existing : Collection) {
|
|
if (Existing.Exit == NewDescriptor.Exit) {
|
|
return false;
|
|
}
|
|
|
|
for (BasicBlock *ExistingSucc : Existing.DivergentSuccessors) {
|
|
for (BasicBlock *NewSucc : NewDescriptor.DivergentSuccessors) {
|
|
if (ExistingSucc == NewSucc) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Helper function which attempts to perform multiple IDS transformations
|
|
static void
|
|
tryMultipleIDS(const ScopeGraphBuilder &SGBuilder,
|
|
const SmallVector<DivergenceDescriptor> &DivergenceDescriptors,
|
|
BasicBlock *PlaceHolderTarget) {
|
|
|
|
// We attempt to perform multiple IDS transformations all at once
|
|
// We start by collecting all the `Conditional`s involved in at least one
|
|
// divergence
|
|
SmallSetVector<BasicBlock *, 4> Conditionals;
|
|
for (auto DivergenceDescriptor : DivergenceDescriptors) {
|
|
Conditionals.insert(DivergenceDescriptor.Conditional);
|
|
}
|
|
|
|
// For each `Conditional`, we collect all the multiple divergences involving
|
|
// it
|
|
for (BasicBlock *Conditional : Conditionals) {
|
|
|
|
// Support variables used to understand when we need to restart the
|
|
// collection
|
|
// of the `DivergentDescriptor`s
|
|
bool AllExitGotos = true;
|
|
size_t CoveredSuccessors = 0;
|
|
size_t ConditionalSuccessors = getScopeGraphSuccessors(Conditional).size();
|
|
|
|
SmallVector<DivergenceDescriptor> CompatibleDivergenceDescriptors;
|
|
for (auto DivergenceDescriptor : DivergenceDescriptors) {
|
|
if (DivergenceDescriptor.Conditional == Conditional) {
|
|
|
|
// As a safeguard, we assert that all the `DivergenceDescriptor`s are
|
|
// compatible, i.e., they partition correctly the divergencies insisting
|
|
// on a certain `Conditional`
|
|
revng_assert(isCompatible(CompatibleDivergenceDescriptors,
|
|
DivergenceDescriptor));
|
|
CompatibleDivergenceDescriptors.push_back(DivergenceDescriptor);
|
|
|
|
// Update the `AllExitGotos` state with the information of the current
|
|
// `Exit`
|
|
AllExitGotos = AllExitGotos and isGotoBlock(DivergenceDescriptor.Exit);
|
|
|
|
// Update the `CoveredSuccessors` information with the current
|
|
// divergence
|
|
CoveredSuccessors += DivergenceDescriptor.DivergentSuccessors.size();
|
|
}
|
|
}
|
|
|
|
// We must find at least one `DivergenceDescriptor` for each `Conditional`,
|
|
// since we previously selected only `Conditional`s involved in at least
|
|
// one `Divergence`
|
|
revng_assert(CompatibleDivergenceDescriptors.size() >= 1);
|
|
|
|
performMultipleIDS(SGBuilder,
|
|
CompatibleDivergenceDescriptors,
|
|
PlaceHolderTarget);
|
|
|
|
// Criterion which restarts the collection and processing of the divergent
|
|
// exits before every already collected divergence is processed.
|
|
// This must happen when:
|
|
// 1) The `Conditional` has only divergent successors.
|
|
// 2) All the divergent successors lead to a `goto` exit.
|
|
// The reasoning is the following: if a `Conditional` has all `goto`
|
|
// divergent successors, once we perform IDS for such `Conditional` one of
|
|
// the successors will be elected as the non divergent one, and the
|
|
// corresponding exit node, which is a `goto` exit, may become a new
|
|
// divergent exit for another `Conditional` upper in the `ScopeGraph`. In
|
|
// such case, if we do not restart the collection of the divergent exits, we
|
|
// may process divergences already collected, before the newly introduced
|
|
// one. And this violates the invariant that we always process divergent
|
|
// `goto` exits before the non `goto` exits. And this in turn may cause
|
|
// suboptimalities when `IDS` is combined with `MaterializeTrivialGoto` in
|
|
// order to reduce the number of emitted `goto`s.
|
|
if (AllExitGotos and CoveredSuccessors == ConditionalSuccessors)
|
|
return;
|
|
}
|
|
}
|
|
|
|
/// This helper function is used to attempt the IDS process, and returns `true`
|
|
/// or `false` depending on whether a change is performed
|
|
static bool tryIDS(const ScopeGraphBuilder &SGBuilder,
|
|
Function &F,
|
|
BasicBlock *PlaceHolderTarget) {
|
|
|
|
// The main need for recomputing the `DomTree` is that we insert the new
|
|
// `IDS` block and redirect edges over the `ScopeGraph`
|
|
DomTreeOnView<BasicBlock, Scope> DomTree;
|
|
DomTree.recalculate(F);
|
|
|
|
SmallVector<DivergenceDescriptor> DivergenceDescriptors;
|
|
|
|
// Collect the exit nodes
|
|
SmallVector<BasicBlock *> Exits = getExits(F, PlaceHolderTarget);
|
|
|
|
// Collect all the IDS opportunities.
|
|
// We iterate over all the `Exit`s, and then search for a `Candidate`
|
|
// conditional node for which such `Exit` is divergent wrt. We do this by
|
|
// walking upward the dominator tree until we find (if it is present) the
|
|
// divergent `Conditional`. An alternative, which would also make the code
|
|
// more straightforward, would be to iterate over the `Conditional`s and start
|
|
// the search from there, but this would mean to perform multiple visits to
|
|
// the `Exit`s. With this techniques instead, even if less intuitive, we
|
|
// collect all the `DivergenceDescriptor`s in one sweep.
|
|
for (BasicBlock *Exit : Exits) {
|
|
|
|
// Here we go up in the `ScopeGraph`, hopping through the immediate
|
|
// dominators of the `Exit` block with the goal of finding the divergent
|
|
// conditional
|
|
BasicBlock *Candidate = Exit;
|
|
while ((Candidate = getImmediateDominator(Candidate, DomTree))) {
|
|
|
|
// We use the following `std::optional` in order to contain the
|
|
// divergent node candidate, with all the object needed to perform the
|
|
// transformation
|
|
std::optional<DivergenceDescriptor>
|
|
DivergenceDescriptor = electDivergence(Candidate, Exit, DomTree);
|
|
|
|
// If we have found a divergence for the exit under analysis, we add it to
|
|
// the `DivergenceDescriptors`
|
|
if (DivergenceDescriptor) {
|
|
DivergenceDescriptors.push_back(*DivergenceDescriptor);
|
|
|
|
// We have found a `Divergence` for the current `Exit`, so we can move
|
|
// to the next one
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we did not collect any `DivergenceDescriptor`, we return `false` in
|
|
// order to signal that no change was performed at the last round
|
|
if (DivergenceDescriptors.empty()) {
|
|
return false;
|
|
} else {
|
|
tryMultipleIDS(SGBuilder, DivergenceDescriptors, PlaceHolderTarget);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// Implementation class used to run the `IDS` transformation
|
|
class InlineDivergentScopesImpl {
|
|
Function &F;
|
|
const ScopeGraphBuilder SGBuilder;
|
|
|
|
public:
|
|
InlineDivergentScopesImpl(Function &F) : F(F), SGBuilder(&F) {}
|
|
|
|
public:
|
|
bool run() {
|
|
|
|
// We keep a boolean variable to track whether the `Function` was modified
|
|
bool FunctionModified = false;
|
|
|
|
// Manager object for the creation and deletion of the `PlaceHolderTarget`
|
|
PlaceHolderTargetManager PlaceHolderTarget(F);
|
|
|
|
// Every time we perform a change due to the IDS restructuring, we may have
|
|
// unlocked the potential to perform new IDS closures in nested subtree
|
|
// where the last modification was performed, therefore we need to retry
|
|
// IDS. Empirically, we have found that restarting the analysis, by
|
|
// recollecting the exit nodes in the `ScopeGraph`, is faster than
|
|
// continuing with the processing of all the exits already collected.
|
|
// Therefore, we should not really try to optimize this, unless we find new
|
|
// evidence that this is better.
|
|
while (tryIDS(SGBuilder, F, *PlaceHolderTarget)) {
|
|
|
|
// As soon as one IDS change is performed, we mark the current `Function`
|
|
// as modified
|
|
FunctionModified = true;
|
|
}
|
|
|
|
return FunctionModified;
|
|
}
|
|
};
|
|
|
|
char InlineDivergentScopesPass::ID = 0;
|
|
static constexpr const char *Flag = "inline-divergent-scopes";
|
|
using Reg = llvm::RegisterPass<InlineDivergentScopesPass>;
|
|
static Reg X(Flag,
|
|
"Perform the inline of divergent scopes canonicalization process");
|
|
|
|
bool InlineDivergentScopesPass::runOnFunction(llvm::Function &F) {
|
|
|
|
// Instantiate and call the `Impl` class
|
|
InlineDivergentScopesImpl IDSImpl(F);
|
|
bool FunctionModified = IDSImpl.run();
|
|
|
|
// This pass may transform the CFG by assign some blocks to perform the IDS
|
|
// canonicalization and by redirecting edges on the `ScopeGraph`
|
|
return FunctionModified;
|
|
}
|
|
|
|
void InlineDivergentScopesPass::getAnalysisUsage(llvm::AnalysisUsage &AU)
|
|
const {
|
|
// This pass does not preserve the CFG
|
|
}
|