Files
revng-revng/lib/RestructureCFG/RestructureCFG.cpp
T
Andrea Gussoni 935dcdbdec RestructureCFG: improve ShortestPath
Add a dedicated Logger for the `ShortestPath` computation.
2023-10-09 10:34:41 +02:00

1195 lines
46 KiB
C++

//
// Copyright rev.ng Labs Srl. See LICENSE.md for details.
//
#include <iterator>
#include <limits>
#include <sstream>
#include "llvm/ADT/BreadthFirstIterator.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/GenericDomTreeConstruction.h"
#include "llvm/Support/raw_os_ostream.h"
#include "revng/Support/Debug.h"
#include "revng/Support/FunctionTags.h"
#include "revng/Support/GraphAlgorithms.h"
#include "revng/Support/IRHelpers.h"
#include "revng-c/RestructureCFG/ASTTree.h"
#include "revng-c/RestructureCFG/BasicBlockNodeImpl.h"
#include "revng-c/RestructureCFG/GenerateAst.h"
#include "revng-c/RestructureCFG/MetaRegionBB.h"
#include "revng-c/RestructureCFG/RegionCFGTreeBB.h"
#include "revng-c/RestructureCFG/RestructureCFG.h"
#include "revng-c/RestructureCFG/Utils.h"
using namespace llvm;
using namespace llvm::cl;
using std::pair;
using std::string;
using std::to_string;
// TODO: Move the initialization of the logger here from "Utils.h"
// Debug logger.
Logger<> CombLogger("restructure");
Logger<> LogShortestPath("restructure-shortest-path");
// EdgeDescriptor is a handy way to create and manipulate edges on the
// RegionCFG.
using BasicBlockNodeBB = BasicBlockNode<BasicBlock *>;
using EdgeDescriptor = std::pair<BasicBlockNodeBB *, BasicBlockNodeBB *>;
// Explicit instantiation of template classes `Metaregion` and `RegionCFG`.
template class MetaRegion<BasicBlock *>;
template class RegionCFG<BasicBlock *>;
using MetaRegionBB = MetaRegion<BasicBlock *>;
using MetaRegionBBVect = std::vector<MetaRegionBB>;
using MetaRegionBBPtrVect = std::vector<MetaRegionBB *>;
using BackedgeMetaRegionMap = std::map<EdgeDescriptor, MetaRegionBB *>;
static bool mergeSCSStep(MetaRegionBBVect &MetaRegions) {
for (auto RegionIt1 = MetaRegions.begin(); RegionIt1 != MetaRegions.end();
RegionIt1++) {
for (auto RegionIt2 = std::next(RegionIt1); RegionIt2 != MetaRegions.end();
RegionIt2++) {
bool Intersects = (*RegionIt1).intersectsWith(*RegionIt2);
bool IsIncluded = (*RegionIt1).isSubSet(*RegionIt2);
bool IsIncludedReverse = (*RegionIt2).isSubSet(*RegionIt1);
bool AreEquivalent = (*RegionIt1).nodesEquality(*RegionIt2);
if (Intersects
and (((!IsIncluded) and (!IsIncludedReverse)) or AreEquivalent)) {
(*RegionIt1).mergeWith(*RegionIt2);
MetaRegions.erase(RegionIt2);
return true;
}
}
}
return false;
}
static void simplifySCS(MetaRegionBBVect &MetaRegions) {
bool Changes = true;
while (Changes) {
Changes = mergeSCSStep(MetaRegions);
}
}
static bool
mergeSCSAbnormalRetreating(MetaRegionBBVect &MetaRegions,
const llvm::SmallDenseSet<EdgeDescriptor> &Backedges,
BackedgeMetaRegionMap &BackedgeMetaRegionMap,
std::set<MetaRegionBB *> &BlacklistedMetaregions) {
for (auto RegionIt = MetaRegions.begin(); RegionIt != MetaRegions.end();
RegionIt++) {
MetaRegionBB &Region = *RegionIt;
// Do not re-analyze blacklisted metaregions.
if (!BlacklistedMetaregions.contains(&Region)) {
// Iterate over all the backedges present in the graph, if the current
// region contains the source of a backedge, it should contain also the
// the target of that backedge. If not, merge the two SCSs.
for (EdgeDescriptor Backedge : Backedges) {
bool FirstIn = Region.containsNode(Backedge.first);
bool SecondIn = Region.containsNode(Backedge.second);
bool AbnormalIncoming = FirstIn and not SecondIn;
bool AbnormalOutgoing = not FirstIn and SecondIn;
if (AbnormalIncoming or AbnormalOutgoing) {
// Retrieve the Metaregion identified by the backedge with goes
// goes outside the scope of the current Metaregion.
MetaRegionBB *OtherRegion = BackedgeMetaRegionMap.at(Backedge);
Region.mergeWith(*OtherRegion);
// Blacklist the region which we have merged.
BackedgeMetaRegionMap[Backedge] = &Region;
BlacklistedMetaregions.insert(OtherRegion);
return true;
// Abort if we didn't find the metaregion to remove.
revng_abort("Not found the region to merge with.");
}
}
}
}
return false;
}
static void
simplifySCSAbnormalRetreating(MetaRegionBBVect &MetaRegions,
const llvm::SmallDenseSet<EdgeDescriptor>
&Backedges) {
// Temporary map where to store the correspondence between the backedge and
// the SCS it gives origin to.
// HACK: this should be done at the same time of the metaregion creation.
unsigned MetaRegionIndex = 0;
std::map<EdgeDescriptor, MetaRegionBB *> BackedgeMetaRegionMap;
for (EdgeDescriptor Backedge : Backedges) {
BackedgeMetaRegionMap[Backedge] = &MetaRegions.at(MetaRegionIndex);
MetaRegionIndex++;
}
std::set<MetaRegionBB *> BlacklistedMetaregions;
bool Changes = true;
while (Changes) {
Changes = mergeSCSAbnormalRetreating(MetaRegions,
Backedges,
BackedgeMetaRegionMap,
BlacklistedMetaregions);
}
// Remove all the metaregion that have been merged with others, using the
// erase/remove idiom.
MetaRegions.erase(remove_if(MetaRegions.begin(),
MetaRegions.end(),
[&BlacklistedMetaregions](MetaRegionBB &M) {
return BlacklistedMetaregions.count(&M) == 1;
}),
MetaRegions.end());
}
static void sortMetaRegions(MetaRegionBBVect &MetaRegions) {
std::sort(MetaRegions.begin(),
MetaRegions.end(),
[](MetaRegionBB &First, MetaRegionBB &Second) {
return First.getNodes().size() < Second.getNodes().size();
});
}
static bool checkMetaregionConsistency(const MetaRegionBBVect &MetaRegions,
const llvm::SmallDenseSet<EdgeDescriptor>
&Backedges) {
bool ComparisonState = true;
for (const MetaRegionBB &MetaRegion : MetaRegions) {
for (EdgeDescriptor Backedge : Backedges) {
BasicBlockNodeBB *Source = Backedge.first;
BasicBlockNodeBB *Target = Backedge.second;
bool HasSource = MetaRegion.containsNode(Source);
bool HasTarget = MetaRegion.containsNode(Target);
revng_assert(HasSource == HasTarget);
if (HasSource != HasTarget) {
ComparisonState = false;
}
}
}
return ComparisonState;
}
static void computeParents(MetaRegionBBVect &MetaRegions) {
for (MetaRegionBB &MetaRegion1 : MetaRegions) {
bool ParentFound = false;
for (MetaRegionBB &MetaRegion2 : MetaRegions) {
if (&MetaRegion1 != &MetaRegion2) {
if (MetaRegion1.isSubSet(MetaRegion2)) {
if (CombLogger.isEnabled()) {
CombLogger << "For metaregion: " << &MetaRegion1 << "\n";
CombLogger << "parent found\n";
CombLogger << &MetaRegion2 << "\n";
}
MetaRegion1.setParent(&MetaRegion2);
ParentFound = true;
break;
}
}
}
if (!ParentFound) {
if (CombLogger.isEnabled()) {
CombLogger << "For metaregion: " << &MetaRegion1 << "\n";
CombLogger << "no parent found\n";
}
MetaRegion1.setParent(nullptr);
}
}
}
static MetaRegionBBPtrVect applyPartialOrder(MetaRegionBBVect &V) {
MetaRegionBBPtrVect OrderedVector;
std::set<MetaRegionBB *> Processed;
while (V.size() != Processed.size()) {
for (auto RegionIt1 = V.begin(); RegionIt1 != V.end(); RegionIt1++) {
if (!Processed.contains(&*RegionIt1)) {
bool FoundParent = false;
for (auto RegionIt2 = V.begin(); RegionIt2 != V.end(); RegionIt2++) {
if ((RegionIt1 != RegionIt2) and !Processed.contains(&*RegionIt2)) {
if ((*RegionIt1).getParent() == &*RegionIt2) {
FoundParent = true;
break;
}
}
}
if (FoundParent == false) {
OrderedVector.push_back(&*RegionIt1);
Processed.insert(&*RegionIt1);
break;
}
}
}
}
std::reverse(OrderedVector.begin(), OrderedVector.end());
return OrderedVector;
}
static bool alreadyInMetaregion(MetaRegionBBVect &V, BasicBlockNodeBB *N) {
for (MetaRegionBB &Region : V)
if (Region.containsNode(N))
return true;
return false;
}
static MetaRegionBBVect
createMetaRegions(const llvm::SmallDenseSet<EdgeDescriptor> &Backedges) {
std::map<BasicBlockNodeBB *, std::set<BasicBlockNodeBB *>> AdditionalSCSNodes;
std::vector<std::pair<BasicBlockNodeBB *, std::set<BasicBlockNodeBB *>>>
Regions;
for (auto &Backedge : Backedges) {
// Convert the `llvm::SmallSetVector` generated by the `nodesBetween` to a
// `std::set`, whose ordering properties are necessary for the following of
// the restructuring algorithm
auto SCSNodesSmall = nodesBetween(Backedge.second, Backedge.first);
std::set<BasicBlockNodeBB *> SCSNodes;
SCSNodes.insert(SCSNodesSmall.begin(), SCSNodesSmall.end());
AdditionalSCSNodes[Backedge.second].insert(SCSNodes.begin(),
SCSNodes.end());
if (CombLogger.isEnabled()) {
CombLogger << "SCS identified by: ";
CombLogger << Backedge.first->getNameStr() << " -> "
<< Backedge.second->getNameStr() << "\n";
CombLogger << "Is composed of nodes:\n";
for (auto Node : SCSNodes) {
CombLogger << Node->getNameStr() << "\n";
}
}
Regions.push_back(std::make_pair(Backedge.second, SCSNodes));
}
// Include in the regions found before other possible sub-regions, if an edge
// which is the target of a backedge is included in an outer region.
for (auto &Region : Regions) {
BasicBlockNodeBB *Head = Region.first;
std::set<BasicBlockNodeBB *> &Nodes = Region.second;
std::set<BasicBlockNodeBB *> AdditionalNodes;
std::set<BasicBlockNodeBB *> OldNodes;
do {
OldNodes = Nodes;
for (BasicBlockNodeBB *Node : Nodes) {
if ((Node != Head) and (AdditionalSCSNodes.contains(Node))) {
CombLogger << "Adding additional nodes for region with head: ";
CombLogger << Head->getNameStr();
CombLogger << " and relative to node: ";
CombLogger << Node->getNameStr() << "\n";
AdditionalNodes.insert(AdditionalSCSNodes[Node].begin(),
AdditionalSCSNodes[Node].end());
}
}
Nodes.insert(AdditionalNodes.begin(), AdditionalNodes.end());
AdditionalNodes.clear();
} while (Nodes != OldNodes);
}
MetaRegionBBVect MetaRegions;
int SCSIndex = 1;
for (size_t I = 0; I < Regions.size(); ++I) {
auto &SCS = Regions[I].second;
MetaRegions.push_back(MetaRegionBB(SCSIndex, SCS, true));
SCSIndex++;
}
return MetaRegions;
}
static cl::opt<std::string> MetricsOutputPath("restructure-metrics-output-dir",
desc("Restructure metrics dir"),
value_desc("restructure-dir"),
cat(MainCategory));
static void LogMetaRegions(const MetaRegionBBPtrVect &MetaRegions,
const std::string &HeaderMsg) {
if (CombLogger.isEnabled()) {
CombLogger << '\n';
CombLogger << HeaderMsg << '\n';
for (const MetaRegionBB *Meta : MetaRegions) {
CombLogger << '\n';
CombLogger << Meta << '\n';
CombLogger << "With index " << Meta->getIndex() << '\n';
CombLogger << "With size " << Meta->nodes_size() << '\n';
CombLogger << "Is composed of nodes:\n";
for (auto *Node : Meta->nodes())
CombLogger << Node->getNameStr() << '\n';
CombLogger << "Is SCS: " << Meta->isSCS() << '\n';
CombLogger << "Has parent: ";
if (Meta->getParent())
CombLogger << Meta->getParent();
else
CombLogger << "nullptr";
CombLogger << '\n';
}
}
}
static void LogMetaRegions(const MetaRegionBBVect &MetaRegions,
const std::string &HeaderMsg) {
if (CombLogger.isEnabled()) {
CombLogger << '\n';
CombLogger << HeaderMsg << '\n';
for (const MetaRegionBB &Meta : MetaRegions) {
CombLogger << '\n';
CombLogger << &Meta << '\n';
CombLogger << "With index " << Meta.getIndex() << '\n';
CombLogger << "With size " << Meta.nodes_size() << '\n';
CombLogger << "Is composed of nodes:\n";
for (auto *Node : Meta.nodes())
CombLogger << Node->getNameStr() << '\n';
CombLogger << "Is SCS: " << Meta.isSCS() << '\n';
CombLogger << "Has parent: ";
if (Meta.getParent())
CombLogger << Meta.getParent();
else
CombLogger << "nullptr";
CombLogger << '\n';
}
}
}
static debug_function void LogMetaRegions(const MetaRegionBBVect &MetaRegions,
const char *HeaderMsg) {
LogMetaRegions(MetaRegions, std::string(HeaderMsg));
}
static std::map<BasicBlockNodeBB *, size_t>
getCandidateEntries(MetaRegionBB *Meta) {
std::map<BasicBlockNodeBB *, size_t> Result;
std::set<EdgeDescriptor> InEdges = Meta->getInEdges();
for (const auto &[Src, Tgt] : InEdges)
++Result[Tgt];
return Result;
}
bool restructureCFG(Function &F, ASTTree &AST) {
revng_log(CombLogger, "restructuring Function: " << F.getName());
revng_log(CombLogger, "Num basic blocks: " << F.size());
DuplicationCounter = 0;
UntangleTentativeCounter = 0;
UntanglePerformedCounter = 0;
// Skip non-isolated functions
auto FTags = FunctionTags::TagsSet::from(&F);
if (not FTags.contains(FunctionTags::Isolated))
return false;
// Clear graph object from the previous pass.
RegionCFG<BasicBlock *> RootCFG;
// Set names of the CFG region
RootCFG.setFunctionName(F.getName().str());
RootCFG.setRegionName("root");
// Initialize the RegionCFG object
RootCFG.initialize(&F);
if (CombLogger.isEnabled()) {
CombLogger << "Analyzing function: " << F.getName() << "\n";
RootCFG.dumpCFGOnFile(F.getName().str(), "dots", "begin");
}
// Identify SCS regions.
llvm::SmallDenseSet<EdgeDescriptor>
Backedges = getBackedges(&RootCFG.getEntryNode()).takeSet();
revng_log(CombLogger, "Initial Backedges in the graph:");
for (auto &Backedge : Backedges) {
LoggerIndent Indent(CombLogger);
revng_log(CombLogger,
Backedge.first->getNameStr()
<< " -> " << Backedge.second->getNameStr());
}
// Insert a dummy node for each retreating node.
for (EdgeDescriptor Backedge : Backedges) {
BasicBlockNodeBB *OriginalTarget = Backedge.second;
BasicBlockNodeBB *Dummy = RootCFG.addArtificialNode();
moveEdgeTarget(Backedge, Dummy);
addPlainEdge(EdgeDescriptor(Dummy, OriginalTarget));
}
Backedges.clear();
Backedges = getBackedges(&RootCFG.getEntryNode()).takeSet();
// Check that the source node of each retreating edge is a dummy node.
revng_log(CombLogger, "Backedges in the graph after dummy insertion:");
for (auto &Backedge : Backedges) {
LoggerIndent Indent(CombLogger);
revng_log(CombLogger,
Backedge.first->getNameStr()
<< " -> " << Backedge.second->getNameStr());
revng_assert(Backedge.first->isEmpty());
}
// Create meta regions
MetaRegionBBVect MetaRegions = createMetaRegions(Backedges);
LogMetaRegions(MetaRegions, "Metaregions after nothing:");
// Simplify SCS if they contain an edge which goes outside the scope of the
// current region.
simplifySCSAbnormalRetreating(MetaRegions, Backedges);
LogMetaRegions(MetaRegions, "Metaregions after first simplification:");
revng_assert(checkMetaregionConsistency(MetaRegions, Backedges));
// Simplify SCS in a fixed-point fashion.
simplifySCS(MetaRegions);
LogMetaRegions(MetaRegions, "Metaregions after second simplification:");
revng_assert(checkMetaregionConsistency(MetaRegions, Backedges));
// Sort the Metaregions in increasing number of composing nodes order.
sortMetaRegions(MetaRegions);
LogMetaRegions(MetaRegions, "Metaregions after second ordering:");
// Compute parent relations for the identified SCSs.
computeParents(MetaRegions);
// Print metaregions after ordering.
LogMetaRegions(MetaRegions, "Metaregions parent relationship:");
// Find an ordering for the metaregions that satisfies the inclusion
// relationship. We create a new "shadow" vector containing only pointers to
// the "real" metaregions.
MetaRegionBBPtrVect OrderedMetaRegions = applyPartialOrder(MetaRegions);
// Print metaregions after ordering.
LogMetaRegions(OrderedMetaRegions, "Metaregions after partial ordering:");
// Create a std::vector from the reverse post order. We cannot just use the
// regular ReversePostOrderTraversal because later we'll need the removal
// operation.
std::vector<BasicBlockNodeBB *> RPOT;
using RPOTraversal = ReversePostOrderTraversal<BasicBlockNodeBB *>;
llvm::copy(RPOTraversal{ &RootCFG.getEntryNode() }, std::back_inserter(RPOT));
if (CombLogger.isEnabled()) {
CombLogger << "\n";
CombLogger << "Reverse post order is:\n";
for (const BasicBlockNodeBB *BN : RPOT)
CombLogger << BN->getNameStr() << "\n";
CombLogger << "Reverse post order end\n";
}
// Compute shortest path to reach all nodes from Entry.
// Used later for picking the entry point of each region.
std::map<BasicBlockNodeBB *, size_t> ShortestPathFromEntry;
{
revng_log(LogShortestPath, "Computing ShortestPathFromEntry");
LoggerIndent Indent(LogShortestPath);
auto BFSIt = llvm::bf_begin(&RootCFG.getEntryNode());
auto BFSEnd = llvm::bf_end(&RootCFG.getEntryNode());
for (; BFSIt != BFSEnd; ++BFSIt) {
BasicBlockNodeBB *Node = *BFSIt;
size_t Depth = BFSIt.getLevel();
revng_log(LogShortestPath, "Node = " << Node);
auto ShortestIt = ShortestPathFromEntry.lower_bound(Node);
LoggerIndent MoreIndent(LogShortestPath);
if (ShortestIt == ShortestPathFromEntry.end()
or Node < ShortestIt->first) {
revng_log(LogShortestPath, "New shortest path Depth: " << Depth);
ShortestPathFromEntry.insert(ShortestIt, { Node, Depth });
} else {
revng_log(LogShortestPath,
"Known shortest path Depth: " << ShortestIt->second);
revng_assert(ShortestIt->second <= Depth);
}
}
}
// Reserve enough space for all the OrderedMetaRegions.
// The following algorithms stores pointers to the elements of this vector, so
// we need to make sure that no reallocation happens.
std::vector<RegionCFG<BasicBlock *>> Regions(OrderedMetaRegions.size());
for (MetaRegionBB *Meta : OrderedMetaRegions) {
if (CombLogger.isEnabled()) {
CombLogger << "\nAnalyzing region: " << Meta->getIndex() << "\n";
CombLogger << "Which is composed of nodes:\n";
for (auto *Node : Meta->nodes())
CombLogger << Node->getNameStr() << "\n";
CombLogger << "Dumping main graph snapshot before restructuring\n";
RootCFG.dumpCFGOnFile(F.getName().str(),
"dots",
"Out-pre-" + std::to_string(Meta->getIndex()));
}
// Identify all the abnormal retreating edges in a SCS.
for (EdgeDescriptor Backedge : llvm::make_early_inc_range(Backedges)) {
if (Meta->containsNode(Backedge.first)) {
// Check that the target of the backedge falls inside the current SCS.
revng_assert(Meta->containsNode(Backedge.second));
// We need to update the backedges list removing the edges which have
// been considered as retreatings of the SCS under analysis.
bool Erased = Backedges.erase(Backedge);
revng_assert(Erased);
}
}
// A map of candidate entries. The key is a entry candidate, i.e. a node
// that has an incoming edge from the outer region. The mapped value is the
// number of edges incoming on the key from an outer region.
std::map<BasicBlockNodeBB *, size_t> Entries = getCandidateEntries(Meta);
revng_assert(not Entries.empty());
// Elect the Entry as the the candidate entry with the largest number of
// incoming edges from outside the region.
// If there's a tie, i.e. there are 2 or more candidate entries with the
// same number of incoming edges from an outer region, we select the entry
// with the minimal shortest path from entry.
// It it's still a tie, i.e. there are 2 or more candidate entries with the
// same number of incoming edges from an outer region and the same minimal
// shortest path from entry, then we disambiguate by picking the entry that
// comes first in RPOT.
BasicBlockNodeBB *Entry = Entries.begin()->first;
{
size_t MaxNEntries = Entries.begin()->second;
size_t ShortestPath = ShortestPathFromEntry.at(Entry);
auto EntriesEnd = Entries.end();
for (BasicBlockNodeBB *Node : RPOT) {
auto EntriesIt = Entries.find(Node);
if (EntriesIt != EntriesEnd) {
const auto &[EntryCandidate, NumEntries] = *EntriesIt;
if (NumEntries > MaxNEntries) {
Entry = EntryCandidate;
ShortestPath = ShortestPathFromEntry.at(EntryCandidate);
} else if (NumEntries == MaxNEntries) {
size_t SP = ShortestPathFromEntry.at(EntryCandidate);
if (SP < ShortestPath) {
Entry = EntryCandidate;
ShortestPath = SP;
}
}
}
}
}
revng_assert(Entry != nullptr);
// Print the name of the node that has been selected as head of the region
revng_log(CombLogger, "Elected head is: " << Entry->getNameStr());
// Compute the retreating edges and their targets inside the region,
// starting from the new Entry.
// Collect the nodes in the metaregion, so we can use the
// `getBackedgesWhitelist` helper to collect the retreating contained in the
// current metaregion.
llvm::SmallSet<BasicBlockNodeBB *, 4> MetaNodes;
for (BasicBlockNodeBB *Node : Meta->nodes())
MetaNodes.insert(Node);
llvm::SmallDenseSet<EdgeDescriptor>
Retreatings = getBackedgesWhiteList(Entry, MetaNodes).takeSet();
std::set<BasicBlockNodeBB *> RetreatingTargets;
for (const EdgeDescriptor &Retreating : Retreatings) {
revng_log(CombLogger,
"Retreatings found: " << Retreating.first->getNameStr()
<< " -> "
<< Retreating.second->getNameStr());
revng_assert(Meta->containsNode(Retreating.first));
revng_assert(Meta->containsNode(Retreating.second));
RetreatingTargets.insert(Retreating.second);
}
bool NewHeadNeeded = RetreatingTargets.size() > 1;
revng_log(CombLogger, "New head needed: " << NewHeadNeeded);
BasicBlockNodeBB *Head = Entry;
if (NewHeadNeeded) {
// Create the dispatcher.
Head = RootCFG.addEntryDispatcher();
Meta->insertNode(Head);
// For each target of the dispatcher add the edge and add it in the map.
std::map<BasicBlockNodeBB *, unsigned> RetreatingIdxMap;
for (auto &Group : llvm::enumerate(RetreatingTargets)) {
BasicBlockNodeBB *Target = Group.value();
unsigned Idx = Group.index();
RetreatingIdxMap[Target] = Idx;
using edge_label_t = typename BasicBlockNodeBB::edge_label_t;
edge_label_t Labels;
Labels.insert(Idx);
using EdgeInfo = BasicBlockNodeBB::EdgeInfo;
EdgeInfo EI = { Labels, false };
addEdge(EdgeDescriptor(Head, Target), EI);
}
for (EdgeDescriptor R : Retreatings) {
BasicBlockNodeBB *OriginalSource = R.first;
// If the original source is a set node, move it after the entry
// dispatcher.
unsigned Idx = RetreatingIdxMap.at(R.second);
if (OriginalSource->isSet()) {
BasicBlockNodeBB *OldSetNode = OriginalSource;
revng_assert(OldSetNode->predecessor_size() == 1);
BasicBlockNodeBB *Predecessor = *OldSetNode->predecessors().begin();
auto *SetNode = RootCFG.addSetStateNode(Idx, OldSetNode->getName());
Meta->insertNode(SetNode);
moveEdgeTarget(EdgeDescriptor(Predecessor, OldSetNode), Head);
} else {
auto *SetNode = RootCFG.addSetStateNode(Idx, R.second->getName());
Meta->insertNode(SetNode);
moveEdgeTarget(EdgeDescriptor(R.first, R.second), SetNode);
addPlainEdge(EdgeDescriptor(SetNode, Head));
}
}
// Move the incoming edge from the old head to new one.
std::vector<BasicBlockNodeBB *> Predecessors;
for (BasicBlockNodeBB *Predecessor : Entry->predecessors())
Predecessors.push_back(Predecessor);
for (BasicBlockNodeBB *Predecessor : Predecessors)
if (not Meta->containsNode(Predecessor))
moveEdgeTarget(EdgeDescriptor(Predecessor, Entry), Head);
}
revng_assert(Head != nullptr);
revng_log(CombLogger, "New head name is: " << Head->getNameStr());
// Successor refinement step.
std::set<BasicBlockNodeBB *> Successors = Meta->getSuccessors();
revng_log(CombLogger, "Initial region successors are:");
for (BasicBlockNodeBB *Node : Successors) {
LoggerIndent Indent(CombLogger);
revng_log(CombLogger, Node->getNameStr());
}
revng_log(CombLogger, "Successors Address: " << &Successors);
bool AnotherIteration = true;
revng_log(CombLogger, "Adjusting regions successors");
while (AnotherIteration and Successors.size() > 1) {
LoggerIndent Indent(CombLogger);
AnotherIteration = false;
for (BasicBlockNodeBB *S : llvm::make_early_inc_range(Successors)) {
revng_log(CombLogger, "Successor: " << S->getID());
LoggerIndent Indent(CombLogger);
// If S is already in another metaregion, we don't include it in this
// one, because that could disrupt the well-nestedness of the meta
// regions (and possibly force us to recompute the OrderedMetaRegion).
// TODO: this condition is very likely to be overly strict, because it
// prevents some good cases to be handled gracefully. In principle I
// think that we could include any node that is only in the parent
// region (but not in sibling meta regions), making sure that we never
// "ingest" a backedge, but this should be thought through before
// jumping to an implementation.
if (alreadyInMetaregion(MetaRegions, S)) {
revng_log(CombLogger, "AlreadyInMetaRegion");
continue;
}
// If any of the predecessors of S is not in the Meta
// metaregion we don't do anything
if (llvm::any_of(S->predecessors(), [Meta](auto *P) {
return not Meta->containsNode(P);
})) {
revng_log(CombLogger, "PredecessorIsOutside");
continue;
}
// Otherwise we include S in the Meta metaregion, since
// all its predecessors are part of it (which means it's
// dominated by the region).
revng_assert(not Meta->containsNode(S));
Meta->insertNode(S);
revng_log(CombLogger,
"Successor has been included in "
"the metaregion: "
<< S->getNameStr());
// Mark that we want to do another iteration
AnotherIteration = true;
// The following is safe because Successors is a std::set
// and we're using llvm::make_early_inc_range. S has been
// included in the metaregion, so we have to erase it
// from Successors, since it's not a successor of the
// metaregion anymore. Also, all successors of Successor
// that are not in the metaregion now have to be inserted
// in Successors, because they are now new successors.
bool Erased = Successors.erase(S);
revng_assert(Erased);
for (BasicBlockNodeBB *NewSuccessor : S->successors()) {
if (not Meta->containsNode(NewSuccessor)) {
Successors.insert(NewSuccessor);
revng_log(CombLogger,
"New Successor of the "
"metaregion: "
<< NewSuccessor->getNameStr());
}
}
}
revng_log(CombLogger, "AnotherIteration: " << AnotherIteration);
revng_log(CombLogger, "Adjusted region successors are:");
for (BasicBlockNodeBB *Node : Successors) {
LoggerIndent Indent(CombLogger);
revng_log(CombLogger, Node->getNameStr());
}
}
// First Iteration outlining.
std::vector<BasicBlockNodeBB *> OutlinedNodes;
if (Entries.size() > 1) {
// Clone all the nodes of the SCS except for the head.
std::map<BasicBlockNodeBB *, BasicBlockNodeBB *> ClonedMap;
for (BasicBlockNodeBB *Node : Meta->nodes()) {
if (Node != Head) {
BasicBlockNodeBB *Clone = RootCFG.cloneNode(*Node);
// In case we are cloning nodes that may become entry candidates of
// regions, we need to assign to them a value in the
// `ShortestPathFromEntry` map.
if (Node->isCollapsed() or Node->isCode()) {
ShortestPathFromEntry[Clone] = ShortestPathFromEntry.at(Node);
}
Clone->setName(Node->getName().str() + " outlined");
ClonedMap[Node] = Clone;
// Add the nodes to the additional vector
OutlinedNodes.push_back(Clone);
}
}
// Restore edges between cloned nodes.
for (BasicBlockNodeBB *Node : Meta->nodes()) {
if (Node != Head) {
// Handle outgoing edges from SCS nodes.
for (const auto &[Successor, Labels] : Node->labeled_successors()) {
revng_assert(not Backedges.contains(EdgeDescriptor(Node,
Successor)));
using ED = EdgeDescriptor;
auto *NewEdgeSrc = ClonedMap.at(Node);
auto *NewEdgeTgt = Successor;
if (Meta->containsNode(Successor)) {
// Handle edges pointing inside the SCS.
if (Successor == Head) {
// Retreating edges should point to the new head.
NewEdgeTgt = Head;
} else {
// Other edges should be restored between cloned nodes.
NewEdgeTgt = ClonedMap.at(Successor);
}
}
addEdge(ED(NewEdgeSrc, NewEdgeTgt), Labels);
}
// We need this temporary vector to avoid invalidating iterators.
std::vector<BasicBlockNodeBB *> Predecessors;
for (BasicBlockNodeBB *Predecessor : Node->predecessors()) {
Predecessors.push_back(Predecessor);
}
for (BasicBlockNodeBB *Predecessor : Predecessors) {
if (not Meta->containsNode(Predecessor)) {
if (CombLogger.isEnabled()) {
CombLogger << "Index region: " << Meta->getIndex() << "\n";
CombLogger << "Edge that now points to an outlined iteration: "
<< Predecessor->getNameStr() << " -> "
<< Node->getNameStr() << "\n";
}
// Are we moving a backedge with the first iteration outlining?
revng_assert(not Backedges.contains({ Predecessor, Node }));
moveEdgeTarget(EdgeDescriptor(Predecessor, Node),
ClonedMap.at(Node));
}
}
}
}
}
// Vector which contains the additional set nodes that set the default value
// for the entry dispatcher.
std::vector<BasicBlockNodeBB *> DefaultEntrySet;
// Default set node for entry dispatcher.
if (NewHeadNeeded) {
revng_assert(Head->isDispatcher());
llvm::SmallPtrSet<BasicBlockNodeBB *, 8> SetCandidates;
for (BasicBlockNodeBB *Pred : Head->predecessors())
if (not Pred->isSet())
SetCandidates.insert(Pred);
unsigned long Value = RetreatingTargets.size() - 1;
for (BasicBlockNodeBB *Pred : SetCandidates) {
BasicBlockNodeBB *Set = RootCFG.addSetStateNode(Value, Head->getName());
DefaultEntrySet.push_back(Set);
EdgeDescriptor PredToHead = { Pred, Head };
EdgeDescriptor SetToHead = { Set, Head };
moveEdgeTarget(PredToHead, Set);
addPlainEdge(SetToHead);
// Update the backedges set. Basically, when we place the default set
// node in case of an entry dispatcher, we need to take care to verify
// if the edge we are "moving" (inserting the set node before it) is a
// backedge, and in case update the information regarding the backedges
// present in the graph accordingly (the backedge becomes the edge
// departing from the set node).
auto BackEdgeIt = Backedges.find(PredToHead);
if (BackEdgeIt != Backedges.end()) {
Backedges.insert(SetToHead);
Backedges.erase(BackEdgeIt);
}
}
}
// Exit dispatcher creation.
// Deduplicate region successor across backedges. If a region has a dummy
// successor that is a dummy backedge, we want to look across it, so that we
// can detect if two backedges actually jump to the same target, and emit
// only one case in the exit dispatcher. This saves us from having to take
// care later of collapsing the two (or more) dummy branches coming out from
// the exit dispatcher with different labels. With this strategy we already
// emit a single label in the first place.
std::set<BasicBlockNodeBB *> DeduplicatedRegionSuccessors;
std::map<BasicBlockNodeBB *, BasicBlockNodeBB *> DeduplicationMap;
{
std::map<BasicBlockNodeBB *, BasicBlockNodeBB *> BackedgeToSucc;
for (BasicBlockNodeBB *Succ : Successors) {
if (Succ->isEmpty()) {
revng_assert(Succ->successor_size() == 1);
BasicBlockNodeBB *BackedgeTgt = *Succ->successors().begin();
// Lookup if wa have already found this backedge target from another
// exit successor.
const auto &[It, New] = BackedgeToSucc.insert({ BackedgeTgt, Succ });
if (New) {
// If we haven't, add the successor in the deduplicated successors
DeduplicatedRegionSuccessors.insert(Succ);
DeduplicationMap[Succ] = Succ;
} else {
// If we have, map the successor to the old successor we've found
// with the same backedge target.
DeduplicationMap[Succ] = It->second;
// If we are following this way of collapsing the successors edges,
// it means that we are collapsing two different retreating edges
// on a single retreating, so a backedge entry will remain in the
// global `Backedges` set as a ghost entry, and we need to take
// care of removing it now.
Backedges.erase({ Succ, BackedgeTgt });
}
} else {
DeduplicatedRegionSuccessors.insert(Succ);
DeduplicationMap[Succ] = Succ;
}
}
}
bool NewExitNeeded = DeduplicatedRegionSuccessors.size() > 1;
revng_log(CombLogger, "New exit needed: " << NewExitNeeded);
BasicBlockNodeBB *ExitDispatcher = nullptr;
if (NewExitNeeded) {
// Create the dispatcher.
ExitDispatcher = RootCFG.addExitDispatcher();
// For each target of the dispatcher add the edge and add it in the map.
std::map<BasicBlockNodeBB *, unsigned> SuccessorsIdxMap;
for (auto &Group : llvm::enumerate(DeduplicatedRegionSuccessors)) {
BasicBlockNodeBB *Successor = Group.value();
unsigned Idx = Group.index();
SuccessorsIdxMap[Successor] = Idx;
using edge_label_t = typename BasicBlockNodeBB::edge_label_t;
edge_label_t Labels;
Labels.insert(Idx);
using EdgeInfo = typename BasicBlockNodeBB::EdgeInfo;
EdgeInfo EI = { Labels, false };
addEdge(EdgeDescriptor(ExitDispatcher, Successor), EI);
}
std::set<EdgeDescriptor> OutEdges = Meta->getOutEdges();
for (EdgeDescriptor Edge : OutEdges) {
// We should not be adding new backedges.
revng_assert(not Backedges.contains(Edge));
unsigned Idx = SuccessorsIdxMap.at(DeduplicationMap.at(Edge.second));
auto *IdxSetNode = RootCFG.addSetStateNode(Idx, Edge.second->getName());
Meta->insertNode(IdxSetNode);
moveEdgeTarget(Edge, IdxSetNode);
addPlainEdge(EdgeDescriptor(IdxSetNode, Edge.second));
}
revng_log(CombLogger,
"New exit name is: " << ExitDispatcher->getNameStr());
}
// Collapse Region.
// Create a new RegionCFG object for representing the collapsed region and
// populate it with the internal nodes.
Regions.push_back(RegionCFG<BasicBlock *>());
RegionCFG<BasicBlock *> &CollapsedGraph = Regions.back();
RegionCFG<BasicBlock *>::BBNodeMap SubstitutionMap{};
CollapsedGraph.setFunctionName(F.getName().str());
CollapsedGraph.setRegionName(std::to_string(Meta->getIndex()));
revng_assert(Head != nullptr);
// Create the collapsed node in the outer region.
BasicBlockNodeBB *Collapsed = RootCFG.createCollapsedNode(&CollapsedGraph);
// A collapsed node may become a candidate entry for an outer cyclic region
// so we need to assign to it a value in the `ShortestPathFromEntry` map.
ShortestPathFromEntry[Collapsed] = ShortestPathFromEntry[Head];
{
// Update the backedges set, checking that if a backedge of an outer
// region pointed to a node that now has been collapsed, now should point
// to the collapsed node, and that does not exists at this point a
// backedge which has as source a node that will be collapsed.
// We cannot do it in a single iteration because `llvm::SmallDenseSet`
// invalidates the iterators upon an erase and insertion operation.
bool Changed = true;
while (Changed) {
Changed = false;
for (const auto &Backedge : Backedges) {
const auto [Source, Target] = Backedge;
revng_assert(not Meta->containsNode(Source));
if (Meta->containsNode(Target)) {
revng_assert(Target == Head);
Backedges.erase(Backedge);
Backedges.insert({ Source, Collapsed });
Changed = true;
break;
}
}
}
}
// Creation and connection of the break and continue node is now performed
// during the bulk node insertion, in order to avoid errors in edge
// ordering.
std::set<EdgeDescriptor> OutgoingEdges = Meta->getOutEdges();
CollapsedGraph.insertBulkNodes(Meta->getNodes(),
Head,
SubstitutionMap,
OutgoingEdges);
// Connect the old incoming edges to the collapsed node.
std::set<EdgeDescriptor> IncomingEdges = Meta->getInEdges();
for (EdgeDescriptor Edge : IncomingEdges) {
BasicBlockNodeBB *OldSource = Edge.first;
revng_assert(Edge.second == Head);
// Check if the old edge was a backedge edge, and in case update the
// information about backedges accordingly.
if (Backedges.count(Edge) == 1) {
Backedges.erase(Edge);
Backedges.insert(EdgeDescriptor(OldSource, Collapsed));
}
moveEdgeTarget(Edge, Collapsed);
}
// Connect the outgoing edges to the collapsed node.
if (NewExitNeeded) {
revng_assert(ExitDispatcher != nullptr);
addPlainEdge(EdgeDescriptor(Collapsed, ExitDispatcher));
} else {
// Double check that we have at most a single successor
revng_assert(DeduplicatedRegionSuccessors.size() <= 1);
if (DeduplicatedRegionSuccessors.size() == 1) {
// Connect the collapsed node to the unique successor
BasicBlockNodeBB *Successor = *DeduplicatedRegionSuccessors.begin();
addPlainEdge(EdgeDescriptor(Collapsed, Successor));
}
}
// Remove collapsed nodes from the outer region.
for (BasicBlockNodeBB *Node : Meta->nodes()) {
revng_log(CombLogger,
"Removing from main graph node :" << Node->getNameStr());
RootCFG.removeNode(Node);
llvm::erase_value(RPOT, Node);
}
LogMetaRegions(OrderedMetaRegions, "MetaRegions before update");
// Substitute in the other SCSs the nodes of the current SCS with the
// collapsed node and the exit dispatcher structure.
MetaRegionBB *ParentMetaRegion = Meta->getParent();
while (ParentMetaRegion) {
ParentMetaRegion->updateNodes(Meta->getNodes(),
Collapsed,
ExitDispatcher,
DefaultEntrySet,
OutlinedNodes);
ParentMetaRegion = ParentMetaRegion->getParent();
}
// Replace the pointers inside SCS.
Meta->replaceNodes(CollapsedGraph.getNodes());
// Remove useless nodes inside the SCS (like dandling break/continue)
CollapsedGraph.removeNotReachables(OrderedMetaRegions);
// Serialize the newly collapsed SCS region.
if (CombLogger.isEnabled()) {
CombLogger << "Dumping CFG of metaregion " << Meta->getIndex() << "\n";
CollapsedGraph.dumpCFGOnFile(F.getName().str(),
"dots",
"In-" + std::to_string(Meta->getIndex()));
CombLogger << "Dumping main graph snapshot post restructuring\n";
RootCFG.dumpCFGOnFile(F.getName().str(),
"dots",
"Out-post-" + std::to_string(Meta->getIndex()));
}
// Remove not reachables nodes from the graph at each iteration.
RootCFG.removeNotReachables(OrderedMetaRegions);
// Check that the newly created collapsed region is acyclic.
revng_assert(CollapsedGraph.isDAG());
}
// After the restructuring of all the metaregions, we need to ensure that all
// the backedges contained in the `Backedges` global set have been taken care
// of.
revng_assert(Backedges.empty());
// Serialize the newly collapsed SCS region.
if (CombLogger.isEnabled()) {
CombLogger << "Dumping main graph before final purge\n";
RootCFG.dumpCFGOnFile(F.getName().str(), "dots", "Final-before-purge");
}
// Remove not reachables nodes from the main final graph.
RootCFG.removeNotReachables(OrderedMetaRegions);
// Serialize the newly collapsed SCS region.
if (CombLogger.isEnabled()) {
CombLogger << "Dumping main graph after final purge\n";
RootCFG.dumpCFGOnFile(F.getName().str(), "dots", "Final-after-purge");
}
// Print metaregions after ordering.
LogMetaRegions(OrderedMetaRegions, "Metaregions after collapse:");
// Check that the root region is acyclic at this point.
revng_assert(RootCFG.isDAG());
// Collect statistics
unsigned InitialWeight = 0;
if (MetricsOutputPath.getNumOccurrences()) {
revng_assert(MetricsOutputPath.getNumOccurrences() == 1);
// Compute the initial weight of the CFG.
for (BasicBlockNodeBB *BBNode : RootCFG.nodes()) {
InitialWeight += BBNode->getWeight();
}
}
// Invoke the AST generation for the root region.
std::map<RegionCFG<llvm::BasicBlock *> *, ASTTree> CollapsedMap;
generateAst(RootCFG, AST, CollapsedMap);
// Scorporated this part which was previously inside the `generateAst` to
// avoid having it run twice or more (it was run inside the recursive step
// of the `generateAst`, and then another time for the final root AST, which
// now is directly the entire AST, since there's no flattening anymore).
normalize(AST, F.getName().str());
// Serialize final AST on file
if (CombLogger.isEnabled())
AST.dumpASTOnFile(F.getName().str(), "ast", "Final");
// Serialize the collected metrics in the outputfile.
if (MetricsOutputPath.getNumOccurrences()) {
// Compute the increase in weight, on the AST
unsigned FinalWeight = 0;
for (ASTNode *N : AST.nodes()) {
switch (N->getKind()) {
case ASTNode::NK_Scs:
case ASTNode::NK_If:
case ASTNode::NK_Switch: {
// Control-flow nodes emit single constructs, so we just increase the
// weight by one.
// Control-flow nodes would also have nested scopes (then-else for if,
// cases for switch, loop body for scs). However, those nodes are
// visited separately, and will be accounted for later.
++FinalWeight;
} break;
case ASTNode::NK_Set:
case ASTNode::NK_Break:
case ASTNode::NK_SwitchBreak:
case ASTNode::NK_Continue: {
// These AST Nodes are emitted as single instructions.
// Just increase the weight by one.
++FinalWeight;
} break;
case ASTNode::NK_List: {
// Sequence nodes are just scopes, they don't have a real weight.
// Their weight is just sum of the weights of the nodes they contain,
// that will be visited nevertheless.
} break;
case ASTNode::NK_Code: {
auto *BB = cast<CodeNode>(N)->getOriginalBB();
revng_assert(BB);
FinalWeight += WeightTraits<llvm::BasicBlock *>::getWeight(BB);
} break;
default:
revng_abort("unexpected AST node");
}
}
float Increase = float(FinalWeight) / float(InitialWeight);
std::ofstream Output;
const char *FunctionName = F.getName().data();
std::ostream &OutputStream = pathToStream(MetricsOutputPath + "/"
+ FunctionName,
Output);
OutputStream << "function,"
"duplications,percentage,tuntangle,puntangle,iweight\n";
OutputStream << F.getName().data() << "," << DuplicationCounter << ","
<< Increase << "," << UntangleTentativeCounter << ","
<< UntanglePerformedCounter << "," << InitialWeight << "\n";
}
return false;
}