feat: loop support via goto labels on each block

- Every block now emits a C label (its id) before its code
- generateNextCode emits goto label; for back-edges (visited targets)
- _emitBranch delegates to generateCode (not generateOwnCode), so labels
  are emitted consistently in both main and branch traversals
- generateCallWithError threads a shared visited set across all three
  emitBranch calls, preventing duplicate label emission for shared blocks
- emitBranch skips roots already visited by previous roots in the same
  branch (prevents spurious gotos from shared-block traversal)
This commit is contained in:
2026-06-23 01:16:14 +02:00
parent f1eed2d4e0
commit 439cc6e7ba
5 changed files with 69 additions and 44 deletions
+17 -17
View File
@@ -46,8 +46,9 @@
/**
* Emits C code for a branch by walking from root blocks, stopping at
* blocks in the stop set and at decision boundaries. Each visited block
* is added to the visited set to prevent double-emission.
* blocks in the stop set and at decision boundaries. When a loop
* back-edge encounters an already-visited block, a `goto label;` is
* emitted instead of silently dropping the edge.
* @param {Array} roots
* @param {Set} stop blocks to exclude (shared between branches)
* @param {Set} visited blocks already emitted
@@ -60,24 +61,20 @@
function (b) {
return b.isDecision;
};
var v = new Set(visited);
var parts = [];
function walk(el) {
if (v.has(el) || (stop && stop.has(el))) return;
v.add(el);
if (isDecision(el)) {
parts.push(el.generateCode(v));
} else {
parts.push(el.generateOwnCode());
var flow = el.wcNextFlow || el.nextFlow || [];
for (var j = 0; j < flow.length; j++) {
walk(flow[j].toElement);
}
if (stop && stop.has(el)) return;
if (visited.has(el)) {
parts.push("\n goto " + (el.id || "block") + ";\n");
return;
}
parts.push(el.generateCode(visited));
}
for (var i = 0; i < roots.length; i++) walk(roots[i]);
for (var i = 0; i < roots.length; i++) {
if (!visited.has(roots[i])) walk(roots[i]);
}
return parts.join("");
};
@@ -104,12 +101,15 @@
if (okAll.has(b)) shared.add(b);
});
var errCode = CodeGen.emitBranch(errTargets, shared, new Set(), isDecision);
var okCode = CodeGen.emitBranch(okTargets, shared, new Set(), isDecision);
// Thread a shared visited set through all three emitBranch calls
// so shared blocks are not re-emitted with duplicate labels.
var visited = new Set();
var errCode = CodeGen.emitBranch(errTargets, shared, visited, isDecision);
var okCode = CodeGen.emitBranch(okTargets, shared, visited, isDecision);
var sharedCode = CodeGen.emitBranch(
Array.from(shared),
new Set(),
new Set(),
visited,
isDecision
);