wave15: sm-0003/0004 + threejs-0006 + varnish-0002 + mongodb-0008 — 533/240

This commit is contained in:
russell@unturf.com 2026-03-27 19:22:37 -04:00
parent 7146714143
commit 31850d5ef6
13 changed files with 1893 additions and 9 deletions

View file

@ -0,0 +1,309 @@
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");
}
}