B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
247 lines
9.9 KiB
Java
247 lines
9.9 KiB
Java
package unit;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
|
|
/**
|
|
* V8RegisterAllocatorTest
|
|
*
|
|
* Models the CWE-407 defect in ConstraintBuilder::MeetConstraintsBefore():
|
|
*
|
|
* Defective: ZoneVector<TopLevelLiveRange*> used for deduplication via std::find,
|
|
* producing O(k) membership test → O(k^2) total per instruction.
|
|
*
|
|
* Fixed: ZoneUnorderedSet<TopLevelLiveRange*> with .count(), O(1) membership
|
|
* test → O(k) total per instruction.
|
|
*
|
|
* Each "range pointer" is modelled as a Long ID. Comparison counts are instrumented
|
|
* explicitly — not wall-clock timing — to isolate the algorithmic difference.
|
|
*/
|
|
public class V8RegisterAllocatorTest {
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Instrumented defective implementation: ArrayList + linear scan
|
|
// -----------------------------------------------------------------------
|
|
|
|
static long defectiveDedup(long[] inputRangeIds) {
|
|
ArrayList<Long> spilledConsts = null;
|
|
long comparisons = 0;
|
|
for (long rangeId : inputRangeIds) {
|
|
boolean alreadySpilled = false;
|
|
if (spilledConsts == null) {
|
|
spilledConsts = new ArrayList<>();
|
|
} else {
|
|
// O(k) linear scan — the defect
|
|
for (Long existing : spilledConsts) {
|
|
comparisons++;
|
|
if (existing.equals(rangeId)) {
|
|
alreadySpilled = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!alreadySpilled) {
|
|
spilledConsts.add(rangeId);
|
|
}
|
|
}
|
|
return comparisons;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Instrumented fixed implementation: HashSet + O(1) contains
|
|
// -----------------------------------------------------------------------
|
|
|
|
static long fixedDedup(long[] inputRangeIds) {
|
|
HashSet<Long> spilledConsts = null;
|
|
long lookups = 0;
|
|
for (long rangeId : inputRangeIds) {
|
|
boolean alreadySpilled = false;
|
|
if (spilledConsts == null) {
|
|
spilledConsts = new HashSet<>();
|
|
} else {
|
|
lookups++; // one O(1) hash lookup per non-first input
|
|
alreadySpilled = spilledConsts.contains(rangeId);
|
|
}
|
|
if (!alreadySpilled) {
|
|
spilledConsts.add(rangeId);
|
|
}
|
|
}
|
|
return lookups;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Helper: build an input array where all k inputs map to the same k/2
|
|
// distinct range IDs, maximising the average scan length in the defect.
|
|
// -----------------------------------------------------------------------
|
|
|
|
static long[] makeInputs(int k) {
|
|
long[] ids = new long[k];
|
|
int distinct = Math.max(1, k / 2);
|
|
for (int i = 0; i < k; i++) {
|
|
ids[i] = i % distinct;
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 1 — Single instruction, k=50 constant-spill inputs: defect > fixed
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test1_singleInstructionRatio() {
|
|
int k = 50;
|
|
long[] inputs = makeInputs(k);
|
|
long defectOps = defectiveDedup(inputs);
|
|
long fixedOps = fixedDedup(inputs);
|
|
|
|
System.out.printf("test1: k=%d defect_comparisons=%d fixed_lookups=%d%n",
|
|
k, defectOps, fixedOps);
|
|
|
|
assert defectOps > fixedOps
|
|
: "defect must do more work than fix at k=" + k;
|
|
assert defectOps >= (k / 2) * ((k / 2) - 1) / 2
|
|
: "defect comparison count must be at least triangular for k/2 distinct ranges";
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 2 — Ratio > 10x at k=50 across 100 instructions
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test2_ratioExceedsTenX() {
|
|
int k = 50;
|
|
int instructions = 100;
|
|
long[] inputs = makeInputs(k);
|
|
|
|
long totalDefect = 0;
|
|
long totalFixed = 0;
|
|
for (int i = 0; i < instructions; i++) {
|
|
totalDefect += defectiveDedup(inputs);
|
|
totalFixed += fixedDedup(inputs);
|
|
}
|
|
|
|
double ratio = (double) totalDefect / Math.max(1, totalFixed);
|
|
System.out.printf("test2: instructions=%d total_defect=%d total_fixed=%d ratio=%.1fx%n",
|
|
instructions, totalDefect, totalFixed, ratio);
|
|
|
|
assert ratio > 10.0
|
|
: "expected ratio > 10x, got " + ratio;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 3 — All inputs are unique (worst case: every input is a cache miss)
|
|
// defect is still O(k^2); fixed is O(k)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test3_allUniqueInputs() {
|
|
int k = 60;
|
|
long[] inputs = new long[k];
|
|
for (int i = 0; i < k; i++) inputs[i] = i; // all distinct
|
|
|
|
long defectOps = defectiveDedup(inputs);
|
|
long fixedOps = fixedDedup(inputs);
|
|
|
|
// All inputs are unique → no hit ever found → no dedup gains.
|
|
// Defect: input 0 → list null, no scan (0).
|
|
// input 1 → list.size()=1, scans 1 item (full miss).
|
|
// input i → list.size()=i, scans i items.
|
|
// Total: 0 + 1 + 2 + ... + (k-1) = k*(k-1)/2
|
|
// Fixed: k-1 lookups (first input builds null→new, no lookup counted; inputs 1..k-1 each +1).
|
|
long expectedDefect = (long) k * (k - 1) / 2;
|
|
double ratio = (double) defectOps / Math.max(1, fixedOps);
|
|
|
|
System.out.printf("test3: k=%d unique defect=%d (expect=%d) fixed=%d ratio=%.1fx%n",
|
|
k, defectOps, expectedDefect, fixedOps, ratio);
|
|
|
|
assert defectOps == expectedDefect
|
|
: "defect comparisons=" + defectOps + " expected=" + expectedDefect;
|
|
assert ratio > 10.0
|
|
: "expected ratio > 10x for all-unique, got " + ratio;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 4 — All inputs map to the same range ID (degenerate: only one
|
|
// unique entry ever appended; every subsequent input hits on
|
|
// the first comparison)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test4_allSameRangeId() {
|
|
int k = 100;
|
|
long[] inputs = new long[k];
|
|
for (int i = 0; i < k; i++) inputs[i] = 42L; // all the same
|
|
|
|
long defectOps = defectiveDedup(inputs);
|
|
long fixedOps = fixedDedup(inputs);
|
|
|
|
// Defect: first input → list is empty, no scan.
|
|
// Inputs 2..k each scan a list of length 1 → 1 comparison each = k-1.
|
|
// Fixed: each non-first input → 1 hash lookup = k-1 lookups.
|
|
// Counts are equal in this degenerate case (list always length 1), but
|
|
// the defect comparison is still pointer-equality vs hash — no asymptote yet.
|
|
System.out.printf("test4: k=%d same-id defect=%d fixed=%d%n",
|
|
k, defectOps, fixedOps);
|
|
|
|
// At minimum, defect ≥ fixed (same list length of 1 throughout)
|
|
assert defectOps >= fixedOps
|
|
: "defect should not be cheaper than fix in any case";
|
|
// Both should be exactly k-1
|
|
assert defectOps == k - 1
|
|
: "expected k-1=" + (k-1) + " defect comparisons, got " + defectOps;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 5 — Scaling: doubling k roughly quadruples defect ops, doubles fixed
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test5_quadraticVsLinearScaling() {
|
|
int k1 = 40;
|
|
int k2 = 80; // double k
|
|
|
|
long d1 = defectiveDedup(makeInputs(k1));
|
|
long d2 = defectiveDedup(makeInputs(k2));
|
|
long f1 = fixedDedup(makeInputs(k1));
|
|
long f2 = fixedDedup(makeInputs(k2));
|
|
|
|
double defectGrowth = (double) d2 / Math.max(1, d1);
|
|
double fixedGrowth = (double) f2 / Math.max(1, f1);
|
|
|
|
System.out.printf("test5: defect growth on 2x k: %.2fx fixed growth: %.2fx%n",
|
|
defectGrowth, fixedGrowth);
|
|
|
|
// Defect should grow super-linearly (>2x when k doubles for quadratic algo)
|
|
assert defectGrowth > 2.0
|
|
: "defect should grow super-linearly, got " + defectGrowth;
|
|
// Fixed should grow at most linearly (≤2.5x for 2x k, allowing hash overhead)
|
|
assert fixedGrowth <= 2.5
|
|
: "fixed should grow at most linearly, got " + fixedGrowth;
|
|
// Defect should grow meaningfully faster than fixed
|
|
assert defectGrowth > fixedGrowth
|
|
: "defect growth should exceed fixed growth";
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Main
|
|
// -----------------------------------------------------------------------
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== V8RegisterAllocatorTest ===");
|
|
System.out.println("Modelling CWE-407: MeetConstraintsBefore spilled_consts deduplication");
|
|
System.out.println();
|
|
|
|
test1_singleInstructionRatio();
|
|
System.out.println(" PASS test1_singleInstructionRatio");
|
|
|
|
test2_ratioExceedsTenX();
|
|
System.out.println(" PASS test2_ratioExceedsTenX");
|
|
|
|
test3_allUniqueInputs();
|
|
System.out.println(" PASS test3_allUniqueInputs");
|
|
|
|
test4_allSameRangeId();
|
|
System.out.println(" PASS test4_allSameRangeId");
|
|
|
|
test5_quadraticVsLinearScaling();
|
|
System.out.println(" PASS test5_quadraticVsLinearScaling");
|
|
|
|
System.out.println();
|
|
System.out.println("All 5 tests PASSED.");
|
|
}
|
|
}
|