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

309 lines
12 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.ArrayList;
import java.util.HashSet;
/**
* SpiderMonkeySimpleSetTest
*
* Models the CWE-407 defect in UnrollLoops.cpp SimpleSet<T,N,AP>:
*
* Defective: mozilla::Vector<T,N,AP> backed set.
* contains() iterates all N elements linearly.
* add() calls contains() before appending.
* Called inside triple-nested loop (copies × blocks × values).
*
* Fixed: HashSet<T> backed set.
* contains() / add() are O(1) amortised.
*
* Models ValueSet (inline cap = 64) and its use in the unrolling phase.
* "Value" is modelled as an Integer ID. Comparison counts are instrumented
* explicitly — not wall-clock timing.
*
* Run: javac -d . SpiderMonkeySimpleSetTest.java && java -ea unit.SpiderMonkeySimpleSetTest
*/
public class SpiderMonkeySimpleSetTest {
// -----------------------------------------------------------------------
// Defective SimpleSet: ArrayList<Integer> with linear contains()
// -----------------------------------------------------------------------
static class DefectiveSimpleSet {
private final ArrayList<Integer> vec = new ArrayList<>();
long comparisons = 0;
boolean contains(int t) {
for (int existing : vec) {
comparisons++;
if (existing == t) return true;
}
return false;
}
boolean add(int t) {
if (contains(t)) return true;
vec.add(t);
return true;
}
int size() { return vec.size(); }
}
// -----------------------------------------------------------------------
// Fixed SimpleSet: HashSet<Integer> with O(1) contains()
// -----------------------------------------------------------------------
static class FixedSimpleSet {
private final HashSet<Integer> set = new HashSet<>();
long lookups = 0;
boolean contains(int t) {
lookups++;
return set.contains(t);
}
boolean add(int t) {
lookups++;
set.add(t);
return true;
}
int size() { return set.size(); }
}
// -----------------------------------------------------------------------
// Simulate the unrolling inner loop:
// unrollFactor copies × numBlocks blocks × numSuccessors successors
// exitTargetBlocks.contains(succ) called per successor per block per copy
// exitingValues.contains(value) called per value per block per copy
//
// Parameters:
// unrollFactor - number of loop body copies (typically 2-4)
// numBlocks - basic blocks in the loop body
// numSuccessors - successors per block's last instruction
// numExitTargets - size of exitTargetBlocks set (B, up to 8)
// numValues - size of exitingValues set (V, up to 64)
// -----------------------------------------------------------------------
static long simulateUnrollDefective(int unrollFactor, int numBlocks,
int numSuccessors, int numExitTargets,
int numValues) {
DefectiveSimpleSet exitTargetBlocks = new DefectiveSimpleSet();
DefectiveSimpleSet exitingValues = new DefectiveSimpleSet();
// Populate sets
for (int i = 0; i < numExitTargets; i++) exitTargetBlocks.add(i);
for (int i = 0; i < numValues; i++) exitingValues.add(i);
long ops = 0;
for (int cix = 0; cix < unrollFactor; cix++) {
for (int bix = 0; bix < numBlocks; bix++) {
// Successor scan — check if successor is an exit target
for (int s = 0; s < numSuccessors; s++) {
int succ = s % (numExitTargets + 2); // most are not exits
exitTargetBlocks.contains(succ);
}
// Value scan — check if each value is an exiting value
for (int v = 0; v < numValues; v++) {
exitingValues.contains(v);
}
}
}
ops = exitTargetBlocks.comparisons + exitingValues.comparisons;
return ops;
}
static long simulateUnrollFixed(int unrollFactor, int numBlocks,
int numSuccessors, int numExitTargets,
int numValues) {
FixedSimpleSet exitTargetBlocks = new FixedSimpleSet();
FixedSimpleSet exitingValues = new FixedSimpleSet();
for (int i = 0; i < numExitTargets; i++) exitTargetBlocks.add(i);
for (int i = 0; i < numValues; i++) exitingValues.add(i);
long ops = 0;
for (int cix = 0; cix < unrollFactor; cix++) {
for (int bix = 0; bix < numBlocks; bix++) {
for (int s = 0; s < numSuccessors; s++) {
int succ = s % (numExitTargets + 2);
exitTargetBlocks.contains(succ);
}
for (int v = 0; v < numValues; v++) {
exitingValues.contains(v);
}
}
}
ops = exitTargetBlocks.lookups + exitingValues.lookups;
return ops;
}
// -----------------------------------------------------------------------
// Test 1 — correctness: defective and fixed sets agree on membership
// -----------------------------------------------------------------------
static void test1_correctness() {
DefectiveSimpleSet slow = new DefectiveSimpleSet();
FixedSimpleSet fast = new FixedSimpleSet();
int[] values = {10, 20, 30, 10, 40, 20};
for (int v : values) {
slow.add(v);
fast.add(v);
}
// Both should have 4 distinct values
assert slow.size() == 4 : "defective size expected 4, got " + slow.size();
assert fast.size() == 4 : "fixed size expected 4, got " + fast.size();
// Both should contain all inserted values
for (int v : new int[]{10, 20, 30, 40}) {
assert slow.contains(v) : "defective missing " + v;
assert fast.contains(v) : "fixed missing " + v;
}
// Both should not contain absent values
assert !slow.contains(99) : "defective should not contain 99";
assert !fast.contains(99) : "fixed should not contain 99";
System.out.printf("test1: size=%d (both agree), membership checks PASS%n",
slow.size());
}
// -----------------------------------------------------------------------
// Test 2 — ratio >= 5x at V=64, unrollFactor=4, numBlocks=20
// -----------------------------------------------------------------------
static void test2_ratioAtMaxValueSet() {
int unrollFactor = 4;
int numBlocks = 20;
int numSuccessors = 3;
int numExitTargets = 8; // BlockSet inline cap
int numValues = 64; // ValueSet inline cap (MaxValuesForPeel)
long defectOps = simulateUnrollDefective(unrollFactor, numBlocks,
numSuccessors, numExitTargets, numValues);
long fixedOps = simulateUnrollFixed(unrollFactor, numBlocks,
numSuccessors, numExitTargets, numValues);
double ratio = (double) defectOps / Math.max(1, fixedOps);
System.out.printf("test2: unroll=%d blocks=%d V=%d defect=%d fixed=%d ratio=%.1fx%n",
unrollFactor, numBlocks, numValues, defectOps, fixedOps, ratio);
assert ratio >= 5.0 : "expected ratio >= 5x, got " + ratio;
}
// -----------------------------------------------------------------------
// Test 3 — worst-case simulation: V=64, 4 copies, 50 blocks
// -----------------------------------------------------------------------
static void test3_worstCase() {
int unrollFactor = 4;
int numBlocks = 50;
int numSuccessors = 4;
int numExitTargets = 8;
int numValues = 64;
long defectOps = simulateUnrollDefective(unrollFactor, numBlocks,
numSuccessors, numExitTargets, numValues);
long fixedOps = simulateUnrollFixed(unrollFactor, numBlocks,
numSuccessors, numExitTargets, numValues);
double ratio = (double) defectOps / Math.max(1, fixedOps);
System.out.printf("test3: worst-case unroll=%d blocks=%d V=%d defect=%d fixed=%d ratio=%.1fx%n",
unrollFactor, numBlocks, numValues, defectOps, fixedOps, ratio);
assert ratio >= 10.0 : "expected ratio >= 10x at worst case, got " + ratio;
}
// -----------------------------------------------------------------------
// Test 4 — scaling: doubling V roughly doubles defect ops, fixed stays constant
// -----------------------------------------------------------------------
static void test4_linearScalingDefect() {
int unrollFactor = 4;
int numBlocks = 20;
int numSuccessors = 2;
int numExitTargets = 4;
long d32 = simulateUnrollDefective(unrollFactor, numBlocks,
numSuccessors, numExitTargets, 32);
long d64 = simulateUnrollDefective(unrollFactor, numBlocks,
numSuccessors, numExitTargets, 64);
long f32 = simulateUnrollFixed(unrollFactor, numBlocks,
numSuccessors, numExitTargets, 32);
long f64 = simulateUnrollFixed(unrollFactor, numBlocks,
numSuccessors, numExitTargets, 64);
double defectGrowth = (double) d64 / Math.max(1, d32);
double fixedGrowth = (double) f64 / Math.max(1, f32);
System.out.printf("test4: V=32→64 defect %d→%d (%.2fx) fixed %d→%d (%.2fx)%n",
d32, d64, defectGrowth, f32, f64, fixedGrowth);
// Defect ops should grow faster than fixed when V doubles
assert defectGrowth > fixedGrowth
: "defect should grow faster than fixed when V doubles";
// Fixed should grow roughly linearly in V (more lookups, each O(1))
assert fixedGrowth <= 2.5
: "fixed growth when V doubles should be near 2x, got " + fixedGrowth;
}
// -----------------------------------------------------------------------
// Test 5 — add() dedup correctness under repeated inserts
// -----------------------------------------------------------------------
static void test5_addDedup() {
DefectiveSimpleSet slow = new DefectiveSimpleSet();
FixedSimpleSet fast = new FixedSimpleSet();
// Insert values 0..9 three times each
for (int round = 0; round < 3; round++) {
for (int v = 0; v < 10; v++) {
slow.add(v);
fast.add(v);
}
}
// Both sets should contain exactly 10 distinct values
assert slow.size() == 10
: "defective: expected 10 unique values, got " + slow.size();
assert fast.size() == 10
: "fixed: expected 10 unique values, got " + fast.size();
System.out.printf("test5: add() dedup correct, size=%d (both)%n", slow.size());
}
// -----------------------------------------------------------------------
// Main
// -----------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("=== SpiderMonkeySimpleSetTest ===");
System.out.println("Modelling CWE-407 sm-0003: SimpleSet::contains() linear scan vs HashSet O(1)");
System.out.println("Location: js/src/jit/UnrollLoops.cpp SimpleSet<T,N,AP>");
System.out.println();
test1_correctness();
System.out.println(" PASS test1_correctness");
test2_ratioAtMaxValueSet();
System.out.println(" PASS test2_ratioAtMaxValueSet");
test3_worstCase();
System.out.println(" PASS test3_worstCase");
test4_linearScalingDefect();
System.out.println(" PASS test4_linearScalingDefect");
test5_addDedup();
System.out.println(" PASS test5_addDedup");
System.out.println();
System.out.println("5/5 PASS");
}
}