package unit; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; /** * JSC-0002: DFGGraph::handleSuccessor() O(E×P) predecessor dedup via Vector::contains. * * Models DFGGraph.cpp lines 738-746: * - SlowGraph: predecessor dedup using List::contains — O(P) per edge * - FastGraph: predecessor dedup using HashSet — O(1) per edge * * Compile: javac -d . *.java (from defects/jsc/unit/) * Run: java -ea unit.JSCDFGGraphPredecessorTest */ public class JSCDFGGraphPredecessorTest { static class Block { final int id; boolean isReachable = false; final List successors; Block(int id, List successors) { this.id = id; this.successors = successors; } } // ---- slow path: List::contains O(P) per edge ---------------------------- static class SlowGraph { static long opCount; final List blocks; final List> predecessors; // per block SlowGraph(List blocks) { this.blocks = blocks; predecessors = new ArrayList<>(); for (int i = 0; i < blocks.size(); i++) predecessors.add(new ArrayList<>()); } void handleSuccessor(Queue worklist, int blockId, int succId) { Block succ = blocks.get(succId); if (!succ.isReachable) { succ.isReachable = true; worklist.add(succId); } List preds = predecessors.get(succId); // O(P) linear scan — the defect boolean found = false; for (int pred : preds) { opCount++; if (pred == blockId) { found = true; break; } } if (!found) preds.add(blockId); } void determineReachability() { Queue worklist = new LinkedList<>(); blocks.get(0).isReachable = true; worklist.add(0); while (!worklist.isEmpty()) { int blockId = worklist.poll(); for (int succ : blocks.get(blockId).successors) handleSuccessor(worklist, blockId, succ); } } } // ---- fast path: HashSet O(1) dedup -------------------------------------- static class FastGraph { static long opCount; final List blocks; final List> predecessors; final List> predSeen; // dedup set FastGraph(List blocks) { this.blocks = blocks; predecessors = new ArrayList<>(); predSeen = new ArrayList<>(); for (int i = 0; i < blocks.size(); i++) { predecessors.add(new ArrayList<>()); predSeen.add(new HashSet<>()); } } void handleSuccessor(Queue worklist, int blockId, int succId) { Block succ = blocks.get(succId); if (!succ.isReachable) { succ.isReachable = true; worklist.add(succId); } opCount++; // one hash lookup+insert if (predSeen.get(succId).add(blockId)) { predecessors.get(succId).add(blockId); } } void determineReachability() { Queue worklist = new LinkedList<>(); blocks.get(0).isReachable = true; worklist.add(0); while (!worklist.isEmpty()) { int blockId = worklist.poll(); for (int succ : blocks.get(blockId).successors) handleSuccessor(worklist, blockId, succ); } } } // ---- graph builder: switch with N arms all targeting block N+1 ---------- // Block 0: entry, edges to blocks 1..N // Blocks 1..N: arms, each edges to block N+1 // Block N+1: join/merge block static List buildSwitchGraph(int N) { List blocks = new ArrayList<>(); // block 0: switch, targets 1..N List arms = new ArrayList<>(); for (int i = 1; i <= N; i++) arms.add(i); blocks.add(new Block(0, arms)); // blocks 1..N: each targets join block N+1 for (int i = 1; i <= N; i++) { List succ = new ArrayList<>(); succ.add(N + 1); blocks.add(new Block(i, succ)); } // block N+1: join (no successors) blocks.add(new Block(N + 1, new ArrayList<>())); return blocks; } // ---- tests -------------------------------------------------------------- static int passed = 0; static int total = 0; static void check(String name, boolean cond) { total++; if (cond) { passed++; } else { System.out.println("FAIL: " + name); } } public static void main(String[] args) { // correctness: same predecessor list for small switch { int N = 5; List slowBlocks = buildSwitchGraph(N); List fastBlocks = buildSwitchGraph(N); SlowGraph slow = new SlowGraph(slowBlocks); FastGraph fast = new FastGraph(fastBlocks); slow.determineReachability(); fast.determineReachability(); // join block (N+1) should have exactly N predecessors check("slow: join preds == N", slow.predecessors.get(N + 1).size() == N); check("fast: join preds == N", fast.predecessors.get(N + 1).size() == N); } // op-count scaling int[] switchSizes = {10, 50, 100, 200, 500}; System.out.println(); System.out.printf("%-8s %12s %12s %8s%n", "N_arms", "slow_ops", "fast_ops", "ratio"); for (int N : switchSizes) { List slowBlocks = buildSwitchGraph(N); List fastBlocks = buildSwitchGraph(N); SlowGraph.opCount = 0; FastGraph.opCount = 0; new SlowGraph(slowBlocks).determineReachability(); new FastGraph(fastBlocks).determineReachability(); long slow = SlowGraph.opCount; long fast = FastGraph.opCount; double ratio = (double) slow / fast; System.out.printf("%-8d %12d %12d %8.1f%n", N, slow, fast, ratio); check("slow > fast for N=" + N, slow > fast); // slow O(N²) for the join block: sum 0..N-1 ~ N²/2 // fast O(N): exactly N lookups for join block // ratio should grow with N; actual ratio ≈ N/2 - small constant if (N >= 50) { check("ratio >= N/5 for N=" + N, ratio >= (double) N / 5); } } System.out.println(); System.out.printf("%d/%d PASS%n", passed, total); if (passed != total) System.exit(1); } }