Last active
June 4, 2025 12:47
-
-
Save kassane/85c71043050714d31a966dcb6a871468 to your computer and use it in GitHub Desktop.
A loop recognition benchmark implementation in D
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/+ | |
+ License: ISC | |
+ | |
+ Ref.: | |
+ * https://github.com/camblomquist/loop-recognition-bench | |
+ * https://github.com/hundt98847/multi-language-bench | |
+ * https://blomqu.ist/posts/2025/loop-recognition/ | |
+ | |
+/ | |
// source/app.d | |
module app; | |
import maoloops; | |
import std.stdio; | |
import std.exception; | |
int buildDiamond(MaoCFG cfg, int start) { | |
try { | |
BasicBlockEdge(cfg, start, start + 1); | |
BasicBlockEdge(cfg, start, start + 2); | |
BasicBlockEdge(cfg, start + 1, start + 3); | |
BasicBlockEdge(cfg, start + 2, start + 3); | |
return start + 3; | |
} catch (Exception e) { | |
stderr.writefln("Error in buildDiamond at start=%d: %s", start, e.msg); | |
throw e; | |
} | |
} | |
void buildConnect(MaoCFG cfg, int start, int end) { | |
try { | |
BasicBlockEdge(cfg, start, end); | |
} catch (Exception e) { | |
stderr.writefln("Error in buildConnect from %d to %d: %s", start, end, e.msg); | |
throw e; | |
} | |
} | |
int buildStraight(MaoCFG cfg, int start, int n) { | |
try { | |
foreach (i; 0 .. n) { | |
buildConnect(cfg, start + i, start + i + 1); | |
} | |
return start + n; | |
} catch (Exception e) { | |
stderr.writefln("Error in buildStraight at start=%d, n=%d: %s", start, n, e.msg); | |
throw e; | |
} | |
} | |
int buildBaseLoop(MaoCFG cfg, int from) { | |
try { | |
int header = buildStraight(cfg, from, 1); | |
int diamond1 = buildDiamond(cfg, header); | |
int d11 = buildStraight(cfg, diamond1, 1); | |
int diamond2 = buildDiamond(cfg, d11); | |
int footer = buildStraight(cfg, diamond2, 1); | |
buildConnect(cfg, diamond2, d11); | |
buildConnect(cfg, diamond1, header); | |
buildConnect(cfg, footer, from); | |
footer = buildStraight(cfg, footer, 1); | |
return footer; | |
} catch (Exception e) { | |
stderr.writefln("Error in buildBaseLoop at from=%d: %s", from, e.msg); | |
throw e; | |
} | |
} | |
void main() { | |
try { | |
stderr.writeln("Welcome to LoopTesterApp, D edition"); | |
stderr.writeln("Constructing cfg..."); | |
auto cfg = new MaoCFG; | |
stderr.writeln("Constructing lsg..."); | |
auto lsg = new LoopStructureGraph; | |
stderr.writeln("Constructing Simple CFG..."); | |
cfg.createNode(0); // top | |
// Simplified CFG: single loop | |
BasicBlockEdge(cfg, 0, 1); | |
BasicBlockEdge(cfg, 1, 2); | |
BasicBlockEdge(cfg, 2, 1); // Back-edge for loop | |
BasicBlockEdge(cfg, 2, 3); // Exit | |
cfg.createNode(3); // bottom | |
stderr.writeln("100 dummy loops"); | |
foreach (i; 0 .. 100) { | |
try { | |
auto lsglocal = new LoopStructureGraph; | |
findHavlakLoops(cfg, lsglocal); | |
if (i % 10 == 0) { | |
stderr.writefln("Completed %d dummy loops", i); | |
} | |
} catch (Exception e) { | |
stderr.writefln("Error in dummy loop %d: %s", i, e.msg); | |
stderr.writeln("Stack trace:"); | |
stderr.writeln(e.info); | |
throw e; | |
} catch (Error e) { | |
stderr.writefln("Fatal error in dummy loop %d: %s", i, e.msg); | |
stderr.writeln("Stack trace:"); | |
stderr.writeln(e.info); | |
throw e; | |
} | |
} | |
stderr.writeln("Constructing CFG..."); | |
int n = 3; | |
foreach (parlooptrees; 0 .. 10) { | |
cfg.createNode(n + 1); | |
buildConnect(cfg, 3, n + 1); | |
n++; | |
foreach (i; 0 .. 100) { | |
int top = n; | |
n = buildStraight(cfg, n, 1); | |
foreach (j; 0 .. 25) { | |
n = buildBaseLoop(cfg, n); | |
} | |
int bottom = buildStraight(cfg, n, 1); | |
buildConnect(cfg, n, top); | |
n = bottom; | |
} | |
buildConnect(cfg, n, 3); | |
} | |
stderr.writeln("Performing Loop Recognition\n1 Iteration"); | |
int numLoops = findHavlakLoops(cfg, lsg); | |
stderr.writeln("Another 50 iterations..."); | |
int sum = 0; | |
foreach (i; 0 .. 50) { | |
try { | |
auto lsglocal = new LoopStructureGraph; | |
sum += findHavlakLoops(cfg, lsglocal); | |
stderr.write("."); | |
} catch (Exception e) { | |
stderr.writefln("Error in iteration %d: %s", i, e.msg); | |
stderr.writeln("Stack trace:"); | |
stderr.writeln(e.info); | |
throw e; | |
} catch (Error e) { | |
stderr.writefln("Fatal error in iteration %d: %s", i, e.msg); | |
stderr.writeln("Stack trace:"); | |
stderr.writeln(e.info); | |
throw e; | |
} | |
} | |
stderr.writefln("\nFound %d loops (including artificial root node) (%d)", numLoops, sum); | |
lsg.calculateNestingLevel(); | |
lsg.dump(); | |
} catch (Exception e) { | |
stderr.writefln("Fatal error: %s", e.msg); | |
stderr.writeln("Stack trace:"); | |
stderr.writeln(e.info); | |
exit(1); | |
} catch (Error e) { | |
stderr.writefln("Fatal error (Error): %s", e.msg); | |
stderr.writeln("Stack trace:"); | |
stderr.writeln(e.info); | |
exit(1); | |
} | |
} | |
private void exit(int code) { | |
import core.stdc.stdlib : exit; | |
exit(code); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "loop-recognition", | |
"description": "A loop recognition benchmark implementation in D.", | |
"version": "0.1.0", | |
"targetPath": "bin", | |
"authors": [ | |
"Matheus Catarino França" | |
], | |
"dflags-dmd": [ | |
"-verrors=context" | |
], | |
"dflags-ldc": [ | |
"--verrors-context", | |
"-mcpu=native" | |
] | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/+ | |
+ License: ISC | |
+ | |
+ Ref.: | |
+ * https://github.com/camblomquist/loop-recognition-bench | |
+ * https://github.com/hundt98847/multi-language-bench | |
+ * https://blomqu.ist/posts/2025/loop-recognition/ | |
+ | |
+/ | |
// source/maoloops.d | |
module maoloops; | |
import std.container.array; | |
import std.container.rbtree; | |
import std.range; | |
import std.algorithm; | |
import std.exception; | |
import std.format; | |
import std.stdio; | |
import core.memory; | |
// BasicBlockEdge: Represents an edge between two basic blocks | |
struct BasicBlockEdge { | |
private BasicBlock from_; | |
private BasicBlock to_; | |
this(MaoCFG cfg, int fromName, int toName) { | |
enforce(cfg !is null, "CFG cannot be null"); | |
from_ = cfg.createNode(fromName); | |
to_ = cfg.createNode(toName); | |
enforce(from_ !is null && to_ !is null, "Invalid nodes created"); | |
from_.addOutEdge(to_); | |
to_.addInEdge(from_); | |
cfg.addEdge(this); | |
} | |
@property BasicBlock src() { return from_; } | |
@property BasicBlock dst() { return to_; } | |
} | |
// BasicBlock: A node in the CFG with in/out edges | |
class BasicBlock { | |
private Array!BasicBlock inEdges_, outEdges_; | |
private int name_; | |
this(int name) { | |
this.name_ = name; | |
} | |
@property auto in_edges() { return inEdges_[]; } | |
@property auto out_edges() { return outEdges_[]; } | |
@property size_t numPred() { return inEdges_.length; } | |
@property size_t numSucc() { return outEdges_.length; } | |
void addOutEdge(BasicBlock to) { | |
enforce(to !is null, "Cannot add null out-edge"); | |
outEdges_.insertBack(to); | |
} | |
void addInEdge(BasicBlock from) { | |
enforce(from !is null, "Cannot add null in-edge"); | |
inEdges_.insertBack(from); | |
} | |
int opCmp(const BasicBlock other) const { | |
return name_ - other.name_; | |
} | |
} | |
// MaoCFG: Control flow graph managing nodes and edges | |
class MaoCFG { | |
private BasicBlock[int] basicBlockMap_; | |
private BasicBlockEdge[] edgeList_; | |
private BasicBlock startNode_; | |
~this() { | |
foreach (name, node; basicBlockMap_) { | |
destroy(node); | |
} | |
} | |
BasicBlock createNode(int name) { | |
if (auto node = name in basicBlockMap_) { | |
return *node; | |
} | |
auto node = new BasicBlock(name); | |
basicBlockMap_[name] = node; | |
if (basicBlockMap_.length == 1) { | |
startNode_ = node; | |
} | |
return node; | |
} | |
void addEdge(BasicBlockEdge edge) { | |
edgeList_ ~= edge; | |
} | |
@property size_t numNodes() { return basicBlockMap_.length; } | |
@property BasicBlock startBasicBlock() { return startNode_; } | |
@property BasicBlock dst(BasicBlockEdge edge) { return edge.dst; } | |
@property BasicBlock src(BasicBlockEdge edge) { return edge.src; } | |
@property auto basicBlocks() { return basicBlockMap_.byValue; } | |
} | |
// SimpleLoop: Represents a loop with basic blocks and hierarchy | |
class SimpleLoop { | |
private RedBlackTree!BasicBlock basicBlocks_; | |
private RedBlackTree!SimpleLoop children_; | |
private SimpleLoop parent_; | |
private bool isRoot_; | |
private int counter_, nestingLevel_, depthLevel_; | |
this() { | |
basicBlocks_ = new RedBlackTree!BasicBlock; | |
children_ = new RedBlackTree!SimpleLoop; | |
} | |
void addNode(BasicBlock bb) { | |
enforce(bb !is null, "Cannot add null basic block"); | |
basicBlocks_.insert(bb); | |
} | |
void addChildLoop(SimpleLoop loop) { | |
enforce(loop !is null, "Cannot add null child loop"); | |
children_.insert(loop); | |
} | |
void dump() { | |
writefln("loop-%d, nest: %d, depth: %d", counter_, nestingLevel_, depthLevel_); | |
} | |
@property SimpleLoop parent() { return parent_; } | |
@property int nesting_level() { return nestingLevel_; } | |
@property int depth_level() { return depthLevel_; } | |
@property int counter() { return counter_; } | |
@property bool is_root() { return isRoot_; } | |
@property auto getChildren() { return children_[]; } | |
void set_parent(SimpleLoop p) { | |
enforce(p !is null, "Cannot set null parent"); | |
parent_ = p; | |
p.addChildLoop(this); | |
} | |
void set_is_root() { isRoot_ = true; } | |
void set_counter(int value) { counter_ = value; } | |
void set_nesting_level(int level) { | |
nestingLevel_ = level; | |
if (level == 0) set_is_root(); | |
} | |
void set_depth_level(int level) { depthLevel_ = level; } | |
int opCmp(const SimpleLoop other) const { | |
return counter_ - other.counter_; | |
} | |
} | |
// LoopStructureGraph: Manages the loop hierarchy | |
class LoopStructureGraph { | |
private SimpleLoop root_; | |
private SimpleLoop[] loops_; | |
private int loopCounter_; | |
this() { | |
root_ = new SimpleLoop; | |
root_.set_nesting_level(0); | |
root_.set_counter(loopCounter_++); | |
addLoop(root_); | |
} | |
~this() { killAll(); } | |
SimpleLoop createNewLoop() { | |
auto loop = new SimpleLoop; | |
loop.set_counter(loopCounter_++); | |
return loop; | |
} | |
void killAll() { | |
foreach (loop; loops_) { | |
destroy(loop); | |
} | |
loops_.length = 0; | |
} | |
void addLoop(SimpleLoop loop) { | |
enforce(loop !is null, "Cannot add null loop"); | |
loops_ ~= loop; | |
} | |
void dump() { | |
dumpRec(root_, 0); | |
} | |
private void dumpRec(SimpleLoop loop, int indent) { | |
enforce(loop !is null, "Cannot dump null loop"); | |
writefln("%*s", indent * 2, ""); | |
loop.dump(); | |
foreach (child; loop.getChildren) { | |
dumpRec(child, indent + 1); | |
} | |
} | |
void calculateNestingLevel() { | |
foreach (loop; loops_) { | |
if (loop.is_root || loop.parent) continue; | |
loop.set_parent(root_); | |
} | |
calculateNestingLevelRec(root_, 0); | |
} | |
private void calculateNestingLevelRec(SimpleLoop loop, int depth) { | |
enforce(loop !is null, "Cannot calculate nesting for null loop"); | |
loop.set_depth_level(depth); | |
int maxNest = loop.nesting_level; | |
foreach (child; loop.getChildren) { | |
calculateNestingLevelRec(child, depth + 1); | |
maxNest = max(maxNest, 1 + child.nesting_level); | |
} | |
loop.set_nesting_level(maxNest); | |
} | |
@property size_t numLoops() { return loops_.length; } | |
@property SimpleLoop root() { return root_; } | |
} | |
// UnionFindNode: Union-find structure for loop detection | |
struct UnionFindNode { | |
private UnionFindNode* parent_; | |
private BasicBlock bb_; | |
private SimpleLoop loop_; | |
private int dfsNumber_; | |
void init(BasicBlock bb, int dfsNumber) @nogc { | |
assert(bb !is null, "Cannot initialize with null basic block"); | |
parent_ = &this; | |
this.bb_ = bb; | |
this.dfsNumber_ = dfsNumber; | |
} | |
UnionFindNode* findSet() { | |
UnionFindNode* node = &this; | |
UnionFindNode*[] nodeList; | |
while (node != node.parent_) { | |
if (node.parent_ != node.parent_.parent_) { | |
nodeList ~= node; | |
} | |
node = node.parent_; | |
} | |
foreach (n; nodeList) { | |
n.parent_ = node.parent_; | |
} | |
return node; | |
} | |
void union_(UnionFindNode* b) @nogc { | |
assert(b !is null, "Cannot union with null node"); | |
parent_ = b; | |
} | |
@property UnionFindNode* parent() { return parent_; } | |
@property BasicBlock bb() { return bb_; } | |
@property SimpleLoop loop() { return loop_; } | |
@property int dfs_number() { return dfsNumber_; } | |
void set_parent(UnionFindNode* p) @nogc { | |
assert(p !is null, "Cannot set null parent"); | |
parent_ = p; | |
} | |
void set_loop(SimpleLoop l) { | |
enforce(l !is null, "Cannot set null loop"); | |
loop_ = l; | |
} | |
} | |
// HavlakLoopFinder: Implements the Havlak loop detection algorithm | |
class HavlakLoopFinder { | |
private MaoCFG cfg_; | |
private LoopStructureGraph lsg_; | |
enum BasicBlockClass { BB_TOP, BB_NONHEADER, BB_REDUCIBLE, BB_SELF, BB_IRREDUCIBLE, BB_DEAD, BB_LAST } | |
enum { kUnvisited = -1, kMaxNonBackPreds = 32 * 1024 } | |
this(MaoCFG cfg, LoopStructureGraph lsg) { | |
enforce(cfg !is null && lsg !is null, "CFG and LSG cannot be null"); | |
this.cfg_ = cfg; | |
this.lsg_ = lsg; | |
} | |
private bool isAncestor(int w, int v, int[] last) { | |
enforce(last !is null, "Last array cannot be null"); | |
enforce(w >= 0 && w < last.length && v >= 0, "Invalid ancestor indices"); | |
return w <= v && v <= last[w]; | |
} | |
private int dfs(BasicBlock current, UnionFindNode[] nodes, ref int[BasicBlock] number, int[] depth, int currentId) { | |
enforce(current !is null && nodes !is null && depth !is null, "Invalid DFS inputs"); | |
enforce(currentId >= 0 && currentId < nodes.length, "Invalid currentId"); | |
nodes[currentId].init(current, currentId); | |
number[current] = currentId; | |
int lastId = currentId; | |
foreach (target; current.out_edges) { | |
enforce(target !is null, "Null target in out_edges"); | |
if (target !in number || number[target] == kUnvisited) { | |
lastId = dfs(target, nodes, number, depth, lastId + 1); | |
} | |
} | |
enforce(number[current] >= 0 && number[current] < depth.length, "Invalid number[current]"); | |
depth[number[current]] = lastId; | |
return lastId; | |
} | |
void findLoops() { | |
try { | |
if (!cfg_.startBasicBlock) { | |
stderr.writeln("No start basic block, exiting findLoops"); | |
return; | |
} | |
int size = cast(int)cfg_.numNodes; | |
enforce(size >= 0, "Invalid CFG size"); | |
stderr.writefln("Initializing DFS with %d nodes", size); | |
auto nonBackPreds = new RedBlackTree!int[size]; | |
auto backPreds = new int[][size]; | |
auto header = new int[size]; | |
auto type = new BasicBlockClass[size]; | |
auto last = new int[size]; | |
auto nodes = new UnionFindNode[size]; | |
int[BasicBlock] number; | |
// Initialize nonBackPreds | |
foreach (i; 0 .. size) { | |
nonBackPreds[i] = new RedBlackTree!int; | |
} | |
// Step a: Initialize and run DFS | |
foreach (bb; cfg_.basicBlocks) { | |
enforce(bb !is null, "Null basic block in CFG"); | |
number[bb] = kUnvisited; | |
} | |
dfs(cfg_.startBasicBlock, nodes, number, last, 0); | |
// Step b: Identify back-edges and non-back-edges | |
stderr.writeln("Identifying back-edges..."); | |
foreach (int w; 0 .. size) { | |
try { | |
stderr.writefln("Processing node w=%d", w); | |
header[w] = 0; | |
type[w] = BasicBlockClass.BB_TOP; | |
auto nodeW = nodes[w].bb; | |
if (!nodeW) { | |
type[w] = BasicBlockClass.BB_DEAD; | |
continue; | |
} | |
if (nodeW.numPred) { | |
foreach (nodeV; nodeW.in_edges) { | |
enforce(nodeV !is null, "Null nodeV in in_edges"); | |
int v = nodeV in number ? number[nodeV] : kUnvisited; | |
if (v == kUnvisited) continue; | |
if (isAncestor(w, v, last)) { | |
backPreds[w] ~= v; | |
} else { | |
enforce(nonBackPreds[w] !is null, "Null nonBackPreds[w]"); | |
nonBackPreds[w].insert(v); | |
} | |
} | |
} | |
} catch (Exception e) { | |
stderr.writefln("Error processing node w=%d: %s", w, e.msg); | |
throw e; | |
} | |
} | |
header[0] = 0; | |
// Step c: Process nodes in reverse DFS order | |
stderr.writeln("Processing nodes in reverse DFS order..."); | |
foreach_reverse (int w; 0 .. size) { | |
try { | |
stderr.writefln("Reverse processing node w=%d", w); | |
UnionFindNode*[] nodePool; | |
auto nodeW = nodes[w].bb; | |
if (!nodeW) continue; | |
// Step d: Process back-edges | |
foreach (v; backPreds[w]) { | |
enforce(v >= 0 && v < size, "Invalid backPred v"); | |
if (v != w) { | |
auto findSetResult = nodes[v].findSet; | |
enforce(findSetResult !is null, "Null findSet result"); | |
nodePool ~= findSetResult; | |
} else { | |
type[w] = BasicBlockClass.BB_SELF; | |
} | |
} | |
auto worklist = nodePool.dup; | |
if (!nodePool.empty) { | |
type[w] = BasicBlockClass.BB_REDUCIBLE; | |
} | |
// Step e: Process non-back-edges | |
while (!worklist.empty) { | |
enforce(worklist.length > 0, "Empty worklist mismatch"); | |
auto x = worklist[0]; | |
worklist = worklist[1 .. $]; | |
enforce(x !is null, "Null worklist node x"); | |
enforce(x.dfs_number >= 0 && x.dfs_number < size, "Invalid dfs_number"); | |
auto nonBackSize = nonBackPreds[x.dfs_number].length; | |
if (nonBackSize > kMaxNonBackPreds) { | |
lsg_.killAll(); | |
stderr.writeln("Aborted due to excessive non-back predecessors"); | |
return; | |
} | |
foreach (yId; nonBackPreds[x.dfs_number]) { | |
enforce(yId >= 0 && yId < size, "Invalid yId"); | |
auto y = nodes[yId]; | |
auto yDash = y.findSet; | |
enforce(yDash !is null, "Null yDash from findSet"); | |
if (!isAncestor(w, yDash.dfs_number, last)) { | |
type[w] = BasicBlockClass.BB_IRREDUCIBLE; | |
nonBackPreds[w].insert(yDash.dfs_number); | |
} else if (yDash.dfs_number != w) { | |
if (!nodePool.canFind(yDash)) { | |
worklist ~= yDash; | |
nodePool ~= yDash; | |
} | |
} | |
} | |
} | |
// Create loop for SCC | |
if (!nodePool.empty || type[w] == BasicBlockClass.BB_SELF) { | |
auto loop = lsg_.createNewLoop; | |
enforce(loop !is null, "Null loop created"); | |
nodes[w].set_loop(loop); | |
foreach (node; nodePool) { | |
enforce(node !is null, "Null node in nodePool"); | |
enforce(node.dfs_number >= 0 && node.dfs_number < size, "Invalid node.dfs_number"); | |
header[node.dfs_number] = w; | |
node.union_(&nodes[w]); | |
if (node.loop) { | |
node.loop.set_parent(loop); | |
} else { | |
loop.addNode(node.bb); | |
} | |
} | |
lsg_.addLoop(loop); | |
} | |
} catch (Exception e) { | |
stderr.writefln("Error in reverse processing node w=%d: %s", w, e.msg); | |
throw e; | |
} catch (Error e) { | |
stderr.writefln("Fatal error in reverse processing node w=%d: %s", w, e.msg); | |
stderr.writeln("Stack trace:"); | |
stderr.writeln(e.info); | |
throw e; | |
} | |
} | |
stderr.writeln("Loop detection completed"); | |
} catch (Exception e) { | |
stderr.writefln("Error in findLoops: %s", e.msg); | |
stderr.writeln("Stack trace:"); | |
stderr.writeln(e.info); | |
throw e; | |
} catch (Error e) { | |
stderr.writefln("Fatal error in findLoops: %s", e.msg); | |
stderr.writeln("Stack trace:"); | |
stderr.writeln(e.info); | |
throw e; | |
} | |
} | |
} | |
// External entry point | |
int findHavlakLoops(MaoCFG cfg, LoopStructureGraph lsg) { | |
try { | |
enforce(cfg !is null && lsg !is null, "CFG or LSG is null"); | |
auto finder = new HavlakLoopFinder(cfg, lsg); | |
finder.findLoops(); | |
return cast(int)lsg.numLoops; | |
} catch (Exception e) { | |
stderr.writefln("Error in findHavlakLoops: %s", e.msg); | |
stderr.writeln("Stack trace:"); | |
stderr.writeln(e.info); | |
throw e; | |
} catch (Error e) { | |
stderr.writefln("Fatal error in findHavlakLoops: %s", e.msg); | |
stderr.writeln("Stack trace:"); | |
stderr.writeln(e.info); | |
throw e; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
build-types availables
based on: stats.txt (by @camblomquist)
tokei
poop test: