java-topology/defects/spidermonkey/unit/SpiderMonkeyRemapperTest.java

166 lines
5.8 KiB
Java

package unit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* SM-0002: MDefinitionRemapper::lookup() O(N) linear scan inside loop-unroll cloning.
*
* Models:
* - SlowRemapper — Vector<Pair> linear lookup, O(V) per lookup → O(V²) per body clone
* - FastRemapper — HashMap<K,V> lookup, O(1) per lookup → O(V) per body clone
*
* Compile: javac -d . *.java (from defects/spidermonkey/unit/)
* Run: java -ea unit.SpiderMonkeyRemapperTest
*/
public class SpiderMonkeyRemapperTest {
// ---- slow path: Vector<Pair> linear scan --------------------------------
static class SlowRemapper {
static long opCount;
static class Pair {
final int original;
int replacement;
Pair(int o) { original = o; replacement = o; }
}
private final List<Pair> pairs = new ArrayList<>();
void enregister(int original) {
pairs.add(new Pair(original));
}
int lookup(int original) {
for (Pair p : pairs) {
opCount++;
if (p.original == original)
return p.replacement;
}
return -1; // not found
}
void update(int original, int replacement) {
for (Pair p : pairs) {
if (p.original == original) {
p.replacement = replacement;
return;
}
}
}
}
// ---- fast path: HashMap O(1) lookup -------------------------------------
static class FastRemapper {
static long opCount;
private final HashMap<Integer, Integer> map = new HashMap<>();
void enregister(int original) {
map.put(original, original);
}
int lookup(int original) {
opCount++; // one hash lookup
Integer v = map.get(original);
return v == null ? -1 : v;
}
void update(int original, int replacement) {
map.put(original, replacement);
}
}
// ---- simulate loop body clone -------------------------------------------
// For each of V values in the mapper, clone K operands that each call lookup().
// Operands reference value (V-1) — the last registered — to hit worst-case
// scan length for SlowRemapper (must scan entire list every time).
static long simulateClone(boolean fast, int V, int K) {
int worstCaseKey = V - 1; // last in the vector → full scan each time
if (fast) {
FastRemapper.opCount = 0;
FastRemapper mapper = new FastRemapper();
for (int i = 0; i < V; i++) mapper.enregister(i);
// clone V instructions, each with K operands
for (int insn = 0; insn < V; insn++) {
for (int op = 0; op < K; op++) {
mapper.lookup(worstCaseKey);
}
}
return FastRemapper.opCount;
} else {
SlowRemapper.opCount = 0;
SlowRemapper mapper = new SlowRemapper();
for (int i = 0; i < V; i++) mapper.enregister(i);
for (int insn = 0; insn < V; insn++) {
for (int op = 0; op < K; op++) {
mapper.lookup(worstCaseKey);
}
}
return SlowRemapper.opCount;
}
}
// ---- 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: both return same result
{
SlowRemapper slow = new SlowRemapper();
FastRemapper fast = new FastRemapper();
int[] vals = {10, 20, 30, 40, 50};
for (int v : vals) { slow.enregister(v); fast.enregister(v); }
slow.update(20, 99); fast.update(20, 99);
check("slow lookup 10", slow.lookup(10) == 10);
check("fast lookup 10", fast.lookup(10) == 10);
check("slow lookup 20 updated", slow.lookup(20) == 99);
check("fast lookup 20 updated", fast.lookup(20) == 99);
check("slow lookup 50", slow.lookup(50) == 50);
check("fast lookup 50", fast.lookup(50) == 50);
check("slow lookup miss", slow.lookup(99) == -1);
check("fast lookup miss", fast.lookup(99) == -1);
}
// op-count scaling: V values, K=3 operands each
int[] sizes = {10, 30, 60, 100, 150};
int K = 3;
System.out.println();
System.out.printf("%-8s %12s %12s %8s%n", "V", "slow_ops", "fast_ops", "ratio");
for (int V : sizes) {
long slowOps = simulateClone(false, V, K);
long fastOps = simulateClone(true, V, K);
double ratio = (double) slowOps / fastOps;
System.out.printf("%-8d %12d %12d %8.1f%n", V, slowOps, fastOps, ratio);
// slow ops should be O(V²): roughly V * K * V/2 on average
// fast ops should be O(V): exactly V * K
long expectedFast = (long) V * K;
// slow worst-case: each lookup scans all V entries (key is last)
long expectedSlow = (long) V * K * V;
check("fast ops == V*K for V=" + V, fastOps == expectedFast);
check("slow ops == V*K*V for V=" + V, slowOps == expectedSlow);
// ratio should be exactly V (slow scans V items per lookup)
check("slow/fast ratio == V for V=" + V, Math.abs(ratio - V) < 0.01);
}
System.out.println();
System.out.printf("%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}