wave15: v8-0002/0003 Intl+revectorizer (125x/25x) + bullet/box2d ticket files — 528/240

This commit is contained in:
russell@unturf.com 2026-03-27 19:16:28 -04:00
parent c4026333ba
commit 7146714143
13 changed files with 1520 additions and 11 deletions

View file

@ -0,0 +1,253 @@
package unit;
import java.util.*;
/**
* Box2DAlgorithm unit test for Box2D CWE-407 defect.
*
* box2d-0001: b2BroadPhase b2UnBufferMove() linear scan of moveArray.
*
* File: src/broad_phase.c lines 7188
* Defect: b2UnBufferMove() first removes proxyKey from moveSet in O(1),
* then scans moveArray linearly to find the array index for RemoveSwap.
* With N buffered proxies, destroying all bodies triggers O(N²) comparisons.
*
* for ( int i = 0; i < count; ++i )
* {
* if ( bp->moveArray.data[i] == proxyKey ) // linear scan
* {
* b2IntArray_RemoveSwap( &bp->moveArray, i );
* break;
* }
* }
*
* Fix: maintain a parallel HashMap<proxyKey, arrayIndex> (moveIndex).
* On bufferMove: record index. On unBufferMove: O(1) lookup, then
* RemoveSwap with displaced-element index fix-up.
*
* The dev comment reads: "Purge from move buffer. Linear search."
* and a TODO: "todo if I can iterate the move set then I don't need the moveArray"
*
* Tests: correctness + performance ratio >= 5x at N=500-1000.
* Prints: N/N PASS
*/
public class Box2DAlgorithm {
static int passed = 0;
static int total = 0;
static void pass(String name) {
passed++;
total++;
System.out.println(" PASS " + name);
}
static void fail(String name, String reason) {
total++;
System.out.println(" FAIL " + name + "" + reason);
}
// -----------------------------------------------------------------------
// Slow path: mirrors b2UnBufferMove (linear scan of moveArray)
// -----------------------------------------------------------------------
static class SlowMoveBuffer {
Set<Integer> moveSet = new HashSet<>();
List<Integer> moveArray = new ArrayList<>();
void bufferMove(int proxyKey) {
if (moveSet.add(proxyKey)) {
moveArray.add(proxyKey);
}
}
/** Returns number of comparisons made during the linear scan. */
long unBufferMove(int proxyKey) {
long ops = 0;
if (moveSet.remove(proxyKey)) {
for (int i = 0; i < moveArray.size(); i++) {
ops++;
if (moveArray.get(i).equals(proxyKey)) {
int last = moveArray.size() - 1;
moveArray.set(i, moveArray.get(last));
moveArray.remove(last);
break;
}
}
}
return ops;
}
List<Integer> snapshot() { return new ArrayList<>(moveArray); }
}
// -----------------------------------------------------------------------
// Fast path: O(1) removal via index map (the proposed fix)
// -----------------------------------------------------------------------
static class FastMoveBuffer {
Set<Integer> moveSet = new HashSet<>();
List<Integer> moveArray = new ArrayList<>();
Map<Integer, Integer> indexMap = new HashMap<>(); // proxyKey array index
void bufferMove(int proxyKey) {
if (moveSet.add(proxyKey)) {
indexMap.put(proxyKey, moveArray.size());
moveArray.add(proxyKey);
}
}
/** Returns 1 (one map lookup) per call regardless of array size. */
long unBufferMove(int proxyKey) {
if (moveSet.remove(proxyKey)) {
Integer idx = indexMap.remove(proxyKey);
if (idx != null) {
int last = moveArray.size() - 1;
if (idx != last) {
Integer displaced = moveArray.get(last);
moveArray.set(idx, displaced);
indexMap.put(displaced, idx);
}
moveArray.remove(last);
}
return 1;
}
return 0;
}
List<Integer> snapshot() { return new ArrayList<>(moveArray); }
}
// -----------------------------------------------------------------------
// Test helpers
// -----------------------------------------------------------------------
static long runSlowDestroyAll(int n) {
SlowMoveBuffer buf = new SlowMoveBuffer();
for (int i = 0; i < n; i++) buf.bufferMove(i);
long ops = 0;
for (int i = 0; i < n; i++) ops += buf.unBufferMove(i);
return ops;
}
static long runFastDestroyAll(int n) {
FastMoveBuffer buf = new FastMoveBuffer();
for (int i = 0; i < n; i++) buf.bufferMove(i);
long ops = 0;
for (int i = 0; i < n; i++) ops += buf.unBufferMove(i);
return ops;
}
// -----------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------
static void testCorrectness() {
// Buffer N proxies, remove half, check both paths have identical final arrays.
int N = 50;
SlowMoveBuffer slow = new SlowMoveBuffer();
FastMoveBuffer fast = new FastMoveBuffer();
for (int i = 0; i < N; i++) { slow.bufferMove(i); fast.bufferMove(i); }
// Remove even keys
for (int i = 0; i < N; i += 2) { slow.unBufferMove(i); fast.unBufferMove(i); }
List<Integer> slowSnap = slow.snapshot();
List<Integer> fastSnap = fast.snapshot();
Collections.sort(slowSnap);
Collections.sort(fastSnap);
if (!slowSnap.equals(fastSnap)) {
fail("box2d-0001 correctness N=50 half-remove",
"snapshots differ: slow=" + slowSnap + " fast=" + fastSnap);
return;
}
pass("box2d-0001 correctness N=50 half-remove (snapshots match)");
}
static void testCorrectnessWithDups() {
// Duplicate bufferMove calls should be idempotent in both paths.
int N = 20;
SlowMoveBuffer slow = new SlowMoveBuffer();
FastMoveBuffer fast = new FastMoveBuffer();
for (int i = 0; i < N; i++) { slow.bufferMove(i); fast.bufferMove(i); }
// buffer the same keys again should not double-add
for (int i = 0; i < N; i++) { slow.bufferMove(i); fast.bufferMove(i); }
if (slow.moveArray.size() != N) {
fail("box2d-0001 dup-bufferMove slow dedup", "size=" + slow.moveArray.size());
return;
}
if (fast.moveArray.size() != N) {
fail("box2d-0001 dup-bufferMove fast dedup", "size=" + fast.moveArray.size());
return;
}
pass("box2d-0001 dup-bufferMove N=20 (both deduplicate correctly)");
}
static void testPerformanceDestroyAll() {
// N=1000: all proxies buffered then all destroyed.
// Slow: sum of array sizes scanned O(N²/2).
// Fast: exactly N ops O(N).
int N = 1000;
long slowOps = runSlowDestroyAll(N);
long fastOps = runFastDestroyAll(N);
// Slow expected: N + (N-1) + ... + 1 = N*(N+1)/2 (worst case ordering)
// Actual may vary by removal order; just verify ratio.
double ratio = (double) slowOps / fastOps;
System.out.printf(" box2d-0001 N=%d slow_ops=%,d fast_ops=%,d ratio=%.0fx%n",
N, slowOps, fastOps, ratio);
if (fastOps != N) {
fail("box2d-0001 performance N=1000", "fast_ops=" + fastOps + " expected=" + N);
return;
}
if (ratio < 5.0) {
fail("box2d-0001 performance N=1000", "ratio=" + ratio + " < 5x");
return;
}
pass("box2d-0001 performance ratio >= 5x at N=1000 (destroy-all)");
}
static void testPerformanceHalfFill() {
// N=800 buffered, N/2 removed (mixed static/dynamic pattern).
int N = 800;
SlowMoveBuffer slow = new SlowMoveBuffer();
FastMoveBuffer fast = new FastMoveBuffer();
for (int i = 0; i < N; i++) { slow.bufferMove(i); fast.bufferMove(i); }
long slowOps = 0, fastOps = 0;
for (int i = 0; i < N / 2; i++) {
slowOps += slow.unBufferMove(i);
fastOps += fast.unBufferMove(i);
}
double ratio = (double) slowOps / fastOps;
System.out.printf(" box2d-0001 N=%d half-remove slow_ops=%,d fast_ops=%,d ratio=%.0fx%n",
N, slowOps, fastOps, ratio);
if (ratio < 5.0) {
fail("box2d-0001 performance N=800 half-remove", "ratio=" + ratio + " < 5x");
return;
}
pass("box2d-0001 performance ratio >= 5x at N=800 (half-remove)");
}
public static void main(String[] args) {
System.out.println("Box2D CWE-407 unit tests");
System.out.println("=".repeat(60));
System.out.println();
System.out.println("[box2d-0001] b2BroadPhase::b2UnBufferMove linear scan");
testCorrectness();
testCorrectnessWithDups();
testPerformanceDestroyAll();
testPerformanceHalfFill();
System.out.println();
System.out.printf("%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}