|
// A simplified BranchProbabilityInfo: static branch-probability estimation. |
|
// |
|
// Reproduces the *estimated* heuristic of llvm/lib/Analysis/BranchProbabilityInfo.cpp, |
|
// the only one that is a pure function of the CFG plus loop structure: |
|
// |
|
// Seed a few blocks with known-bad weights (unreachable=0, noreturn=1, cold=0xffff, |
|
// against a normal 0xfffff), flow that backward to the branches that gate them, but |
|
// treat each loop as one unit whose exit edges are divided by the trip count -- so |
|
// staying in a loop is ~31x likelier than leaving. Normalize per branch. |
|
// |
|
// Loops come from a nested cycle forest built with Tao Wei's single-pass DFS (SAS |
|
// 2007), the algorithm llvm::CycleInfo uses after PR #210301. It handles reducible and |
|
// irreducible loops uniformly and gives each block its innermost cycle plus a |
|
// containment relation, so nested loops stay distinct -- a flat maximal-SCC pass would |
|
// merge them and get the inner loop wrong. BPI never asks whether a cycle is |
|
// reducible, only about membership, enter/exit and containment. |
|
// |
|
// Omitted, all needing instruction-level information a bare CFG lacks: |
|
// calcMetadataWeights, calcPointer/Zero/FloatingPointHeuristics, |
|
// computeUnlikelySuccessors. |
|
// |
|
// The propagation is the real one: propagateEstimatedBlockWeight walks up a Semi-NCA |
|
// dominator tree giving the weight to every dominator the block post-dominates, which |
|
// needs a PostDominatorTree (dominators of the reverse CFG from a virtual exit, with |
|
// LLVM's non-trivial roots for infinite loops). Seeds are applied in RPO and |
|
// predecessors iterated in use-list order; both are observable. Matches post-#210301 |
|
// LLVM on every bundled CFG and on LLVM's own BPI lit tests -- see README. |
|
// |
|
// Input (node 0 is the entry): |
|
// n m |
|
// u v (m directed edges u->v) |
|
// b K (0+ lines: block b has kind K in {U unreachable, R noreturn, C cold}) |
|
// P ... (optional: explicit use-list predecessor order, emitted by ll2cfg) |
|
#include <algorithm> |
|
#include <cstdint> |
|
#include <cstdio> |
|
#include <cstdlib> |
|
#include <vector> |
|
using namespace std; |
|
|
|
// --- Internal weight scale (llvm::BlockExecWeight). Weights are meaningful only |
|
// relative to each other; at a branch they are normalized to probabilities. --- |
|
static const uint32_t W_UNREACHABLE = 0x0; // never runs |
|
static const uint32_t W_LOWEST = 0x1; // noreturn / unwind |
|
static const uint32_t W_COLD = 0xffff; // block with a cold call |
|
static const uint32_t W_DEFAULT = 0xfffff; // no information (~16x > cold) |
|
static const uint32_t TC = 124 / 4; // Loop Branch Heuristic trip count = 31 |
|
|
|
static int n; |
|
static vector<vector<int>> succ, pred; |
|
static vector<char> kind; // 'N','U','R','C' |
|
|
|
// --- Nested cycle (loop) forest. --- |
|
static vector<int> cyc; // innermost cycle id of a block, or -1 |
|
static vector<int> parentCyc; // enclosing cycle id, or -1 (top-level) |
|
static vector<vector<int>> entries, exits; // per-cycle, dedup'd |
|
static vector<char> reducible; // per-cycle: exactly one entry? |
|
static int nCyc; |
|
|
|
// --- Estimated weights (the sparse "how doomed is this?" maps). -1 = unknown. --- |
|
static vector<long long> estBlock; // EstimatedBlockWeight |
|
static vector<long long> estLoop; // EstimatedLoopWeight (per cycle) |
|
|
|
// --------------------------------------------------------------------------- |
|
// Semi-NCA dominators (LLVM's DominatorTree algorithm), generic over the graph |
|
// so the same code serves the CFG and the reverse CFG (post-dominators). Needed |
|
// only by propagateEstimatedBlockWeight below; the cycle forest does not use it. |
|
// --------------------------------------------------------------------------- |
|
static const vector<vector<int>> *sncSucc; |
|
static vector<int> dfn, rdfn, uf, best, sdom, sncIdom, sncPost; |
|
static int tick; |
|
static void sncDfs(int u) { |
|
best[u] = dfn[u] = tick; |
|
rdfn[tick++] = u; |
|
for (int v : (*sncSucc)[u]) |
|
if (dfn[v] < 0) { |
|
uf[v] = u; |
|
sncDfs(v); |
|
} |
|
sncPost.push_back(u); // same DFS yields the post-order, hence the RPO |
|
} |
|
static int sncEval(int v, int cur) { |
|
if (dfn[v] <= cur) |
|
return v; |
|
int u = uf[v], r = sncEval(u, cur); |
|
if (best[u] < best[v]) |
|
best[v] = best[u]; |
|
return uf[v] = r; |
|
} |
|
static vector<int> computeIdom(int N, const vector<vector<int>> &gsucc, |
|
const vector<vector<int>> &gpred, int root, |
|
vector<int> *rpoOut = nullptr) { |
|
sncSucc = &gsucc; |
|
dfn.assign(N, -1); |
|
rdfn.assign(N, -1); |
|
uf.assign(N, -1); |
|
best.assign(N, 0); |
|
sdom.assign(N, 0); |
|
sncIdom.assign(N, -1); // stays -1 for unreachable vertices |
|
sncPost.clear(); |
|
tick = 0; |
|
sncDfs(root); |
|
for (int i = tick; --i;) { // semidominators, in reverse preorder |
|
int v = rdfn[i]; |
|
sdom[v] = i; |
|
for (int u : gpred[v]) |
|
if (dfn[u] >= 0) { |
|
sncEval(u, i); |
|
if (best[u] < sdom[v]) |
|
sdom[v] = best[u]; |
|
} |
|
best[v] = sdom[v]; |
|
sncIdom[v] = uf[v]; // provisional: the DFS parent |
|
} |
|
for (int i = 1; i < tick; i++) { // NCA: lift until preorder <= sdom |
|
int v = rdfn[i]; |
|
while (dfn[sncIdom[v]] > sdom[v]) |
|
sncIdom[v] = sncIdom[sncIdom[v]]; |
|
} |
|
sncIdom[root] = root; |
|
if (rpoOut) |
|
*rpoOut = vector<int>(sncPost.rbegin(), sncPost.rend()); |
|
return sncIdom; |
|
} |
|
static vector<int> idom, rpo, ipdom; |
|
static int VEXIT; |
|
// Post-dominators: dominators of the reverse CFG from a virtual exit pointing at |
|
// every root -- blocks with no successors, plus the "furthest away" block of each |
|
// reverse-unreachable region (infinite loop), mirroring LLVM's FindRoots. |
|
static void computePDT() { |
|
VEXIT = n; |
|
vector<char> seen(n, 0); |
|
vector<int> roots; |
|
auto markReaching = [&](int s) { |
|
for (vector<int> stk{s}; !stk.empty();) { |
|
int b = stk.back(); |
|
stk.pop_back(); |
|
if (seen[b]) |
|
continue; |
|
seen[b] = 1; |
|
for (int p : pred[b]) |
|
stk.push_back(p); |
|
} |
|
}; |
|
for (int b = 0; b < n; b++) |
|
if (succ[b].empty()) |
|
roots.push_back(b), markReaching(b); |
|
for (int b = 0; b < n; b++) { |
|
if (seen[b]) |
|
continue; |
|
vector<char> tmp(n, 0); // fresh per region: each search stands alone |
|
vector<int> order; |
|
for (vector<int> stk{b}; !stk.empty();) { |
|
int x = stk.back(); |
|
stk.pop_back(); |
|
if (seen[x] || tmp[x]) |
|
continue; |
|
tmp[x] = 1; |
|
order.push_back(x); |
|
vector<int> ss = succ[x]; |
|
sort(ss.begin(), ss.end()); // LLVM's SuccOrder: stable by block order |
|
for (int s : ss) |
|
stk.push_back(s); |
|
} |
|
roots.push_back(order.back()); |
|
markReaching(order.back()); |
|
} |
|
vector<vector<int>> rsucc(n + 1), rpred(n + 1); |
|
for (int b = 0; b < n; b++) |
|
rsucc[b] = pred[b], rpred[b] = succ[b]; |
|
for (int r : roots) |
|
rsucc[VEXIT].push_back(r), rpred[r].push_back(VEXIT); |
|
ipdom = computeIdom(n + 1, rsucc, rpred, VEXIT); |
|
} |
|
static bool postDominates(int a, int b) { |
|
for (int x = b; x != -1 && x != VEXIT; x = ipdom[x]) |
|
if (x == a) |
|
return true; |
|
return false; |
|
} |
|
|
|
// --------------------------------------------------------------------------- |
|
// Step 1: identify loops with Tao Wei's algorithm (see /tmp/t/loop/wei.cpp). A |
|
// single DFS tags every node with its innermost loop header (ilh) and marks |
|
// headers; the loop-nesting forest is (header, ilh). No dominator tree. |
|
// --------------------------------------------------------------------------- |
|
struct WNode { |
|
int ilh = -1; // innermost loop header, -1 = none |
|
int pos = 0; // 1-based depth on the current DFS path; 0 once off it |
|
bool trav = false; // reached by the DFS? |
|
bool header = false; // header of some loop? |
|
}; |
|
static vector<WNode> wnd; |
|
|
|
static void tagLoopHeader(int b, int h) { |
|
if (h == -1) |
|
return; |
|
while (b != h) { |
|
int ih = wnd[b].ilh; |
|
if (ih == -1) { |
|
wnd[b].ilh = h; |
|
return; |
|
} |
|
if (wnd[ih].pos >= wnd[h].pos) |
|
b = ih; |
|
else { |
|
wnd[b].ilh = h; |
|
b = h; |
|
h = ih; |
|
} |
|
} |
|
} |
|
|
|
static int weiDfs(int b0, int p) { |
|
wnd[b0].trav = true; |
|
wnd[b0].pos = p; |
|
// Visit successors LIFO, matching LLVM's iterative CycleInfo DFS (it pushes |
|
// successors on a stack and pops in reverse). For irreducible loops the cycle |
|
// forest is not canonical -- it depends on this order -- so reproducing it is |
|
// what makes the nesting (and thus the probabilities) match. |
|
for (auto it = succ[b0].rbegin(); it != succ[b0].rend(); ++it) { |
|
int b = *it; |
|
if (!wnd[b].trav) { // tree edge |
|
tagLoopHeader(b0, weiDfs(b, p + 1)); |
|
} else if (wnd[b].pos > 0) { // back edge: b is a header |
|
wnd[b].header = true; |
|
tagLoopHeader(b0, b); |
|
} else { // forward/cross edge: climb b's header chain to the first open one |
|
for (int h = wnd[b].ilh; h >= 0; h = wnd[h].ilh) |
|
if (wnd[h].pos > 0) { // b0 is an interior node of that (still-open) loop |
|
tagLoopHeader(b0, h); |
|
break; |
|
} |
|
// (closed headers on the chain are irreducible re-entries; the generic |
|
// entries computation below recovers them, so we needn't record them.) |
|
} |
|
} |
|
wnd[b0].pos = 0; |
|
return wnd[b0].ilh; |
|
} |
|
|
|
// Non-strict containment over the nesting forest (walking parent chains; real |
|
// LLVM does this as an O(1) interval test over an Euler tour of the forest). |
|
static bool cycHasCyc(int outer, int inner) { // does 'outer' contain 'inner'? |
|
for (int x = inner; x >= 0; x = parentCyc[x]) |
|
if (x == outer) |
|
return true; |
|
return false; |
|
} |
|
static bool cycHasBlock(int c, int b) { |
|
for (int x = cyc[b]; x >= 0; x = parentCyc[x]) |
|
if (x == c) |
|
return true; |
|
return false; |
|
} |
|
|
|
static void findCycles() { |
|
wnd.assign(n, WNode{}); |
|
weiDfs(0, 1); // CycleInfo only covers blocks reachable from the entry |
|
|
|
// Each header becomes a cycle; a block's innermost cycle is its own if it is a |
|
// header, else its innermost header's. A cycle's parent is its header's. |
|
vector<int> cycleOf(n, -1); |
|
nCyc = 0; |
|
for (int h = 0; h < n; h++) |
|
if (wnd[h].header) |
|
cycleOf[h] = nCyc++; |
|
auto cycOf = [&](int h) { return h < 0 ? -1 : cycleOf[h]; }; |
|
cyc.assign(n, -1); |
|
parentCyc.assign(nCyc, -1); |
|
for (int v = 0; v < n; v++) { |
|
cyc[v] = wnd[v].header ? cycleOf[v] : cycOf(wnd[v].ilh); |
|
if (wnd[v].header) |
|
parentCyc[cycleOf[v]] = cycOf(wnd[v].ilh); |
|
} |
|
|
|
// entries[c] = blocks in c with a predecessor outside c; exits[c] = blocks |
|
// outside c reached from within it. "In c" = c is an ancestor-or-self of the |
|
// block's innermost cycle, so this respects nesting. |
|
entries.assign(nCyc, {}); |
|
exits.assign(nCyc, {}); |
|
for (int c = 0; c < nCyc; c++) { |
|
for (int b = 0; b < n; b++) { |
|
if (!cycHasBlock(c, b)) |
|
continue; |
|
for (int p : pred[b]) |
|
if (!cycHasBlock(c, p)) { |
|
entries[c].push_back(b); |
|
break; |
|
} |
|
for (int s : succ[b]) |
|
if (!cycHasBlock(c, s)) |
|
exits[c].push_back(s); |
|
} |
|
sort(exits[c].begin(), exits[c].end()); |
|
exits[c].erase(unique(exits[c].begin(), exits[c].end()), exits[c].end()); |
|
} |
|
reducible.assign(nCyc, 0); |
|
for (int c = 0; c < nCyc; c++) |
|
reducible[c] = entries[c].size() == 1; |
|
} |
|
|
|
// --------------------------------------------------------------------------- |
|
// Step 2: edge classification, via containment of the endpoints' innermost |
|
// cycles (mirrors isLoopEnteringEdge: entering iff dst is in a cycle that does |
|
// NOT contain the src's cycle). |
|
// --------------------------------------------------------------------------- |
|
static bool entering(int u, int v) { |
|
if (cyc[v] < 0) |
|
return false; |
|
if (cyc[u] < 0) |
|
return true; |
|
return !cycHasCyc(cyc[v], cyc[u]); // dst-cycle contains src-cycle? |
|
} |
|
static bool exiting(int u, int v) { return entering(v, u); } |
|
|
|
// getEstimatedEdgeWeight: an edge *entering* a loop takes the loop's weight, not |
|
// the target block's; otherwise it takes the target block's weight. -1=unknown. |
|
static long long edgeWeight(int u, int v) { |
|
return entering(u, v) ? estLoop[cyc[v]] : estBlock[v]; |
|
} |
|
|
|
// getMaxEstimatedEdgeWeight: max over a set of edges, or -1 (unknown) if ANY is |
|
// unknown -- this "all-or-nothing" rule is what makes a block count as low only |
|
// when every path out of it is low. |
|
static long long maxEdge(int u, const vector<int> &dsts) { |
|
long long mx = -1; |
|
for (int v : dsts) { |
|
long long w = edgeWeight(u, v); |
|
if (w < 0) |
|
return -1; |
|
mx = max(mx, w); |
|
} |
|
return mx; |
|
} |
|
|
|
// --------------------------------------------------------------------------- |
|
// Step 3: the worklist fixpoint (estimateBlockWeights). Seeds propagate backward |
|
// until stable. Assigning a block re-examines its predecessors; assigning a loop |
|
// re-examines the blocks that enter it. |
|
// --------------------------------------------------------------------------- |
|
static vector<int> blockWL, loopWL; |
|
|
|
static long long seedWeight(int b) { |
|
switch (kind[b]) { |
|
case 'U': return W_UNREACHABLE; |
|
case 'R': return W_LOWEST; |
|
case 'C': return W_COLD; |
|
default: return -1; |
|
} |
|
} |
|
|
|
// updateEstimatedBlockWeight: first write wins; enqueue what it affects. |
|
static bool updateBlockWeight(int b, long long w) { |
|
if (estBlock[b] >= 0) |
|
return false; |
|
estBlock[b] = w; |
|
for (int p : pred[b]) { |
|
if (exiting(p, b)) { // p is in a loop that exits to b: try its weight |
|
if (estLoop[cyc[p]] < 0) |
|
loopWL.push_back(cyc[p]); |
|
} else if (estBlock[p] < 0) // ordinary predecessor: try to derive its weight |
|
blockWL.push_back(p); |
|
} |
|
return true; |
|
} |
|
// propagateEstimatedBlockWeight: give w to bb and to every dominator of bb that |
|
// bb post-dominates -- those run iff bb runs. Stop when post-dominance breaks, |
|
// when a weight is already set, or when the edge crosses a cycle boundary. |
|
static void propagate(int bb, long long w) { |
|
if (idom[bb] == -1) // unreachable: DT has no node, so LLVM's walk does nothing |
|
return; |
|
for (int d = bb;;) { |
|
if (!postDominates(bb, d)) |
|
break; |
|
if (!entering(d, bb) && !exiting(d, bb)) { |
|
if (!updateBlockWeight(d, w)) |
|
break; |
|
} else if (exiting(d, bb) && cyc[d] >= 0) |
|
loopWL.push_back(cyc[d]); |
|
if (d == 0 || idom[d] == -1) |
|
break; |
|
d = idom[d]; |
|
} |
|
} |
|
|
|
static void estimateWeights() { |
|
estBlock.assign(n, -1); |
|
estLoop.assign(nCyc, -1); |
|
for (int b : rpo) // seed in RPO, as LLVM does |
|
if (long long w = seedWeight(b); w >= 0) |
|
propagate(b, w); |
|
while (!blockWL.empty() || !loopWL.empty()) { |
|
while (!loopWL.empty()) { |
|
int c = loopWL.back(); |
|
loopWL.pop_back(); |
|
if (estLoop[c] >= 0) |
|
continue; |
|
// Loop weight = how hot the loop is ~ max weight over where it exits to. |
|
long long lw = maxEdge(entries[c].empty() ? 0 : entries[c][0], exits[c]); |
|
if (lw < 0) |
|
continue; |
|
if (lw <= (long long)W_UNREACHABLE) |
|
lw = W_LOWEST; // we enter the loop at least once |
|
estLoop[c] = lw; |
|
// Push blocks entering the loop: outside predecessors of every entry. |
|
for (int e : entries[c]) |
|
for (int p : pred[e]) |
|
if (!cycHasBlock(c, p) && estBlock[p] < 0) |
|
blockWL.push_back(p); |
|
} |
|
while (!blockWL.empty()) { |
|
int b = blockWL.back(); |
|
blockWL.pop_back(); |
|
if (estBlock[b] >= 0) |
|
continue; |
|
if (long long mw = maxEdge(b, succ[b]); mw >= 0) |
|
propagate(b, mw); |
|
} |
|
} |
|
} |
|
|
|
// --------------------------------------------------------------------------- |
|
// Step 4: per-branch probabilities (the loop-relevant half of |
|
// calcEstimatedHeuristics). Exit edges are scaled down by TC; unknown weights |
|
// fall back to DEFAULT. |
|
// --------------------------------------------------------------------------- |
|
static void printProbabilities() { |
|
for (int b = 0; b < n; b++) { |
|
if ((int)succ[b].size() < 2) |
|
continue; |
|
vector<uint32_t> val(succ[b].size()); |
|
bool found = false; |
|
for (size_t i = 0; i < succ[b].size(); i++) { |
|
int s = succ[b][i]; |
|
long long w = edgeWeight(b, s); |
|
// Loop-exiting edge: scale by trip count (unless it is exactly ZERO). |
|
if (exiting(b, s) && w != (long long)W_UNREACHABLE) { |
|
uint32_t base = (w < 0) ? W_DEFAULT : (uint32_t)w; |
|
w = max(W_LOWEST, base / TC); |
|
} |
|
if (w >= 0) |
|
found = true; |
|
val[i] = (w < 0) ? W_DEFAULT : (uint32_t)w; |
|
} |
|
uint64_t total = 0; |
|
for (uint32_t v : val) |
|
total += v; |
|
// Matches calcEstimatedHeuristics' bail-out: if nothing was estimated, or |
|
// every successor is weight-0 (all lead to unreachable), the heuristic does |
|
// not apply and the branch stays uniform. LLVM also walks the blocks in |
|
// post-order from the entry, so unreachable blocks are never computed. |
|
bool uniform = idom[b] == -1 || !found || total == 0; |
|
|
|
printf(" block %d ->", b); |
|
if (uniform) |
|
printf(" [no estimate: uniform]"); |
|
printf("\n"); |
|
for (size_t i = 0; i < succ[b].size(); i++) { |
|
int s = succ[b][i]; |
|
const char *tag = entering(b, s) ? " enter" : exiting(b, s) ? " exit " : " "; |
|
double p = uniform ? 100.0 / succ[b].size() : 100.0 * val[i] / (double)total; |
|
printf(" -> %d %s weight=%-8u p=%6.2f%%\n", s, tag, val[i], p); |
|
} |
|
} |
|
} |
|
|
|
int main() { |
|
int m; |
|
if (scanf("%d %d", &n, &m) != 2) |
|
return 0; |
|
succ.assign(n, {}); |
|
pred.assign(n, {}); |
|
kind.assign(n, 'N'); |
|
for (int i = 0; i < m; i++) { |
|
int u, v; |
|
if (scanf("%d %d", &u, &v) != 2) |
|
return 1; |
|
succ[u].push_back(v); |
|
pred[v].push_back(u); |
|
} |
|
int b; |
|
char k; |
|
while (scanf(" %d %c", &b, &k) == 2) |
|
if (b >= 0 && b < n) |
|
kind[b] = k; |
|
// Optional explicit predecessor order (LLVM's use-list order), emitted by |
|
// ll2cfg. Absent for hand-written inputs, which keep the derived order. |
|
char sect; |
|
if (scanf(" %c", §) == 1 && sect == 'P') { |
|
for (int i = 0; i < n; i++) { |
|
int v, c; |
|
if (scanf("%d %d", &v, &c) != 2) |
|
break; |
|
pred[v].clear(); |
|
for (int j = 0; j < c; j++) { |
|
int p; |
|
if (scanf("%d", &p) == 1) |
|
pred[v].push_back(p); |
|
} |
|
} |
|
} else { |
|
// LLVM's predecessors() walks the block's use list, i.e. reverse of |
|
// construction order. Worklist push order depends on it, and that is |
|
// observable: a cycle whose exit edge enters another cycle needs that |
|
// cycle's weight, and is never re-queued once it lands. |
|
for (auto &v : pred) |
|
reverse(v.begin(), v.end()); |
|
} |
|
|
|
idom = computeIdom(n, succ, pred, 0, &rpo); |
|
computePDT(); |
|
findCycles(); |
|
estimateWeights(); |
|
|
|
|
|
printf("cycles (loops), indented by nesting:\n"); |
|
if (nCyc == 0) |
|
printf(" (none)\n"); |
|
// Depth-first over the nesting forest so children print under their parent. |
|
auto printCycle = [&](auto &&self, int c, int depth) -> void { |
|
printf("%*s cycle %d [%s] blocks {", 2 + 2 * depth, "", c, |
|
reducible[c] ? "reducible" : "IRREDUCIBLE"); |
|
// blocks = everything in the cycle, nested sub-cycles included (this is what |
|
// CycleInfo::getBlocks returns); innermost = the blocks whose innermost |
|
// cycle this is. |
|
for (int b = 0; b < n; b++) |
|
if (cycHasBlock(c, b)) |
|
printf(" %d", b); |
|
printf(" } innermost {"); |
|
for (int b = 0; b < n; b++) |
|
if (cyc[b] == c) |
|
printf(" %d", b); |
|
printf(" } entries {"); |
|
for (int e : entries[c]) |
|
printf(" %d", e); |
|
printf(" } exits {"); |
|
for (int x : exits[c]) |
|
printf(" %d", x); |
|
printf(" } loopWeight=%lld\n", estLoop[c]); |
|
for (int k = 0; k < nCyc; k++) |
|
if (parentCyc[k] == c) |
|
self(self, k, depth + 1); |
|
}; |
|
for (int c = 0; c < nCyc; c++) |
|
if (parentCyc[c] == -1) |
|
printCycle(printCycle, c, 0); |
|
printf("estimated block weights (unknown blocks default to %u at a branch):\n", |
|
W_DEFAULT); |
|
for (int i = 0; i < n; i++) |
|
if (estBlock[i] >= 0) |
|
printf(" block %d = %lld\n", i, estBlock[i]); |
|
printf("branch probabilities:\n"); |
|
printProbabilities(); |
|
return 0; |
|
} |