import "../../runtime/eltest.el" import "../../runtime/elbench.el" // test_lexer_scaling.el — THE ARMED GATE. // // This is the regression test that would have caught el #132. // // #132 was a strlen() inside str_char_code() and str_slice(). The lexer walks // source one character at a time, so every character access rescanned the whole // remaining input: O(n) per character over n characters = O(n^2). It shipped for // months. It was found by a geometric sweep, not by reading code. // // So this test IS a geometric sweep. It scans a string of length n, character by // character, at four doubling sizes, and asserts the cost is linear. If anyone // reintroduces a per-character rescan — in str_char_code, in str_slice, in any // accessor the lexer leans on — the measured curve becomes O(n^2) and this fails. // // The value is in it being ARMED, not in it currently failing. It passes today // because #132 is fixed. That is the correct state for a regression gate. // // Note the deliberate `let c: Int = str_char_code(...)` binding in the scan loop. // Inlining it as `total + str_char_code(s, i)` lowers to el_str_concat() on // integers — the Plus arm of the operator-typing family, still open at the time // of writing. Binding first is the safe form. // _mk_string — build a string of length >= n by DOUBLING. // // Deliberately not `s = s + "x"` n times: that is itself quadratic in bytes and // would contaminate the very measurement this test exists to take. Doubling // allocates ~2n total. fn _mk_string(n: Int) -> String { let s: String = "abcdefgh" while str_len(s) < n { let s = s + s } return s } // _scan — walk the string one character at a time, REPS times. // // This is the lexer's access pattern reduced to its essential shape. The // repetitions lift the measurement clear of timer resolution; without them the // smaller sizes land in noise and the classifier correctly reports // INDETERMINATE rather than guessing. fn _scan(s: String, n: Int, reps: Int) -> Int { let total: Int = 0 let r: Int = 0 while r < reps { let i: Int = 0 while i < n { let c: Int = str_char_code(s, i) let total = total + c let i = i + 1 } let r = r + 1 } return total } // _measure_scan — microseconds for a full scan sweep point. fn _measure_scan(n: Int, reps: Int) -> Int { let s: String = _mk_string(n) // WARMUP, discarded. Without it the small-n end of the sweep is dominated // by cold caches and reads as superlinear on genuinely linear work -- // measured ratios 3.37 2.92 1.76 1.65 on exactly this workload. let w: Int = _scan(s, n, 2) let wj: Int = el_black_box(w) let t0: Int = el_now_instant() let got: Int = _scan(s, n, reps) let t1: Int = el_now_instant() // Feed the result through the barrier so the scan cannot be elided. let sink: Int = el_black_box(got) if sink == 0 { println("") } return (t1 - t0) / 1000 } fn _series4(a: Int, b: Int, c: Int, d: Int) -> [Int] { let l: [Int] = native_list_empty() let l = native_list_append(l, a) let l = native_list_append(l, b) let l = native_list_append(l, c) let l = native_list_append(l, d) return l } test "character scan is LINEAR in time -- regression gate for el #132" { let reps: Int = 40 let t1: Int = _measure_scan(16384, reps) let t2: Int = _measure_scan(32768, reps) let t3: Int = _measure_scan(65536, reps) let t4: Int = _measure_scan(131072, reps) let series: [Int] = _series4(t1, t2, t3, t4) let verdict: Int = elb_gate(series, 2, 50) let measured: Int = elb_measured_curve(series, 50) // Report the actual numbers regardless of outcome. A gate that fires // without showing its evidence is just an assertion. println(" scan us: " + int_to_str(t1) + " " + int_to_str(t2) + " " + int_to_str(t3) + " " + int_to_str(t4) + " -> " + elb_curve_name(measured) + " [" + elb_verdict_name(verdict) + "]") // PASS (0) or BETTER (4) are both acceptable. FAIL (1) means someone // reintroduced superlinear per-character cost. REFUSED (3) or // INDETERMINATE (2) mean the measurement is untrustworthy -- which is // also a failure of this test, deliberately: a gate that cannot measure // must not report success. assert verdict == 0 || verdict == 4, "character scan must measure O(n) or better" } test "string building by doubling stays linear in allocated bytes" { let b1: Int = el_alloc_bytes() let s1: String = _mk_string(8192) let b2: Int = el_alloc_bytes() let s2: String = _mk_string(16384) let b3: Int = el_alloc_bytes() let s3: String = _mk_string(32768) let b4: Int = el_alloc_bytes() let s4: String = _mk_string(65536) let b5: Int = el_alloc_bytes() let series: [Int] = _series4(b2 - b1, b3 - b2, b4 - b3, b5 - b4) let verdict: Int = elb_gate(series, 2, 1000) let measured: Int = elb_measured_curve(series, 1000) println(" bytes: " + int_to_str(b2 - b1) + " " + int_to_str(b3 - b2) + " " + int_to_str(b4 - b3) + " " + int_to_str(b5 - b4) + " -> " + elb_curve_name(measured) + " [" + elb_verdict_name(verdict) + "]") assert verdict == 0 || verdict == 4, "doubling build must be O(n) in bytes" assert str_len(s4) >= 65536, "final string reached the requested size" } // _scan_quadratic — a DELIBERATELY quadratic scan: for each position, rescan // from the start. This is precisely what el #132 did — strlen() from offset 0 // on every character access — reproduced here so the gate can be proven to // FIRE, not merely to pass on healthy code. An unproven gate is decoration. fn _scan_quadratic(s: String, n: Int) -> Int { let total: Int = 0 let i: Int = 0 while i < n { let j: Int = 0 while j < i { let c: Int = str_char_code(s, j) let total = total + c let j = j + 1 } let i = i + 1 } return total } fn _measure_quadratic(n: Int) -> Int { let s: String = _mk_string(n) let w: Int = _scan_quadratic(s, 64) let wj: Int = el_black_box(w) let t0: Int = el_now_instant() let got: Int = _scan_quadratic(s, n) let t1: Int = el_now_instant() let sink: Int = el_black_box(got) return (t1 - t0) / 1000 } test "the gate FIRES on a live quadratic scan -- proves it is armed" { let q1: Int = _measure_quadratic(1024) let q2: Int = _measure_quadratic(2048) let q3: Int = _measure_quadratic(4096) let q4: Int = _measure_quadratic(8192) let series: [Int] = _series4(q1, q2, q3, q4) let verdict: Int = elb_gate(series, 2, 50) let measured: Int = elb_measured_curve(series, 50) println(" quad us: " + int_to_str(q1) + " " + int_to_str(q2) + " " + int_to_str(q3) + " " + int_to_str(q4) + " -> " + elb_curve_name(measured) + " [" + elb_verdict_name(verdict) + "]") assert measured == 4, "a rescan-from-zero workload must classify O(n^2)" assert verdict == 1, "declared O(n) against measured O(n^2) must FAIL the gate" }