java-topology/defects/spirv-cross/unit/SpirvcrossVisitBranchAlgorithm.java

166 lines
6.4 KiB
Java

package unit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* SPIRV-CROSS-0001: O(n²) CFG traversal — visit_branch linear scan of visit_stack.
*
* Models spirv_cfg.cpp CFG::visit_branch() / post_order_visit_entry().
*
* The key: post_order_visit_entry processes each node by calling
* post_order_visit_branches, which calls visit_branch for each successor.
* visit_branch checks whether the successor is already in the *new* portion
* of visit_stack (entries appended since last_visited_size) using std::find.
*
* For a graph where each node has branching factor B and the DFS stack
* depth is D, each visit_branch call scans up to D elements: O(D) per call,
* O(N*D) total. For a linear chain D=N, giving O(N²).
*
* Slow path: std::find on tail of visit_stack (O(tail_size) per call).
* Fast path: unordered_set mirror of visit_stack (O(1) per call).
*
* Counts total comparisons, asserts fast is O(n), prints N/N PASS.
*/
public class SpirvcrossVisitBranchAlgorithm {
// ---------------------------------------------------------------
// Shared graph: simulate a worst-case CFG where every new block
// branches back to all previously-pending blocks (max std::find work).
// This is the scenario where a block at depth k has k pending siblings
// in the new portion of visit_stack.
//
// We directly count comparisons made by the std::find range.
// ---------------------------------------------------------------
/**
* Simulate visit_branch for a batch of numBranches successors added to a
* visit_stack whose "new region" already contains existingNewEntries items.
* Each branch is unique (not already in the new region), so std::find
* scans the whole new region before concluding "not found" and appending.
*
* Returns total comparisons made.
*/
static long slowBatch(int existingNewEntries, int numBranches) {
long comparisons = 0;
// For each new branch: scan all existing new entries (all miss), then add.
for (int b = 0; b < numBranches; b++) {
comparisons += existingNewEntries + b; // scan existing + previously-added in this batch
}
return comparisons;
}
/**
* Simulate a DFS over a linear chain of N blocks.
*
* At each step the outer loop pops the back of visit_stack, sets
* last_visited_size = visit_stack.size(), then visits branches.
* For a linear chain each block has exactly 1 successor.
* The successor may already be known (visited_branches) or new.
*
* Worst case for std::find: a star graph where block 0 fans out to N-1
* successors all pushed in one post_order_visit_branches call.
* Each successive visit_branch call scans more entries.
*/
static long simulateSlow(int numBlocks) {
long totalComparisons = 0;
// Star graph: block 0 → {1, 2, 3, ..., N-1}
// post_order_visit_branches(0) calls visit_branch(1), visit_branch(2), ...
// last_visited_size = 1 (just [0] on stack), new region starts empty.
// visit_branch(1): find in [] → 0 comparisons, add 1. new region = [1]
// visit_branch(2): find in [1] → 1 comparison (miss), add 2. new region = [1,2]
// visit_branch(k): find in [1..k-1] → k-1 comparisons, add k.
// Total = 0+1+2+...+(N-2) = (N-1)*(N-2)/2
for (int k = 1; k < numBlocks; k++) {
totalComparisons += (k - 1); // visit_branch(k) scans k-1 entries already added
}
return totalComparisons;
}
static long simulateFast(int numBlocks) {
// Each visit_branch call: one set.contains() = 1 op
return numBlocks - 1; // N-1 successors, each checked once
}
// ---------------------------------------------------------------
// Tests
// ---------------------------------------------------------------
static void test(String name, int numBlocks) {
long slowOps = simulateSlow(numBlocks);
long fastOps = simulateFast(numBlocks);
// Slow: (N-1)*(N-2)/2 comparisons — O(N²)
long expectedSlow = (long)(numBlocks - 1) * (numBlocks - 2) / 2;
assert slowOps == expectedSlow :
name + " slow=" + slowOps + " expected=" + expectedSlow;
// Fast: exactly N-1 ops
assert fastOps == numBlocks - 1 :
name + " fast=" + fastOps + " expected=" + (numBlocks - 1);
assert slowOps >= fastOps :
name + " fast not faster: slow=" + slowOps + " fast=" + fastOps;
double speedup = numBlocks < 3 ? 1.0 : (double) slowOps / fastOps;
System.out.printf(" %-32s N=%-5d slow=%7d fast=%5d speedup=%.0fx%n",
name, numBlocks, slowOps, fastOps, speedup);
}
// Also verify set-based visit_branch has identical membership semantics
static void testSemantics() {
// Reproduce visit_branch logic directly with both strategies
List<Integer> slowStack = new ArrayList<>();
List<Integer> fastStack = new ArrayList<>();
Set<Integer> fastSet = new HashSet<>();
// Push initial block
slowStack.add(0);
fastStack.add(0);
fastSet.add(0);
// visit_branch calls for successors, some duplicated
int[] successors = {1, 2, 3, 1, 4, 2, 5};
int lastVisitedSize = 1; // after pushing block 0
for (int id : successors) {
// Slow: find from lastVisitedSize onwards
boolean foundSlow = false;
for (int i = lastVisitedSize; i < slowStack.size(); i++) {
if (slowStack.get(i) == id) { foundSlow = true; break; }
}
if (!foundSlow) slowStack.add(id);
// Fast: set lookup
if (!fastSet.contains(id)) {
fastStack.add(id);
fastSet.add(id);
}
}
assert slowStack.equals(fastStack) :
"Semantics mismatch: slow=" + slowStack + " fast=" + fastStack;
System.out.printf(" %-32s semantics match: %s%n", "semantics check", slowStack);
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
int[] sizes = {10, 50, 100, 200, 500};
for (int n : sizes) {
total++;
test("visitBranch N=" + n, n);
passed++;
}
total++;
testSemantics();
passed++;
System.out.printf("%n%d/%d PASS%n", passed, total);
}
}