mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
Array HOF helpers (%arr.map/filter/forEach/flatMap/find*/some/every/ reduce*) called every callback through its closure, bracketed by the ambient JS receiver stack, with each element boxed as T | undefined. - Intern a helper per literal callback and admit %arr.* helpers as constant-callback consumers: the callback becomes a guarded direct call that LLVM inlines into the loop. - Replace the scalar-only receiver check with an exact module fixpoint (AmbientReceiverReaders) over direct calls, constructors and virtual dispatch; known callbacks that cannot reach a receiver read skip the push/pop. - For inline arrows over plain element types, lower the callback a second time with the element type and split each visit on the array state: present values take the fast closure with no union box; holes and present undefined keep the original closure and undefined arm. - Resolve factory-made callbacks (every return yields one function) and immutable captured callbacks at their closure creation sites. - Read and write scalar capture boxes in place instead of calling scr_box_get_f64/scr_box_set_*. Corpus 4140-4142 pin the split visit, receiver brackets on the direct path, and factory/captured callbacks. Linux x64 (sandbox, --layouts=4 --runs=16, baseline stack 06 tip): | workload | baseline ms | candidate ms | change | 95% CI | verdict | size | node ms | candidate/node | | --- | ---: | ---: | ---: | --- | --- | ---: | ---: | ---: | | json-records | 192.5 | 193.8 | +0.7% | -2.1% .. +2.4% | neutral | +0.0% | 436.3 | 0.44x | | regex-logs | 166.4 | 165.5 | -0.5% | -1.8% .. +0.9% | neutral | +0.0% | 243.9 | 0.68x | | ast-interp | 264.9 | 243.0 | -8.3% | -9.5% .. -6.9% | faster | +0.0% | 232.2 | 1.05x | | records-sort | 593.2 | 576.4 | -2.8% | -6.6% .. +0.3% | neutral | +3.0% | 210.3 | 2.74x | | template-render | 69.3 | 68.6 | -1.0% | -3.2% .. +2.6% | neutral | +0.0% | 189.0 | 0.36x | | functional-pipeline | 215.8 | 111.0 | -48.6% | -49.6% .. -46.3% | faster | +3.8% | 255.8 | 0.43x | | async-pipeline | 194.8 | 196.8 | +1.0% | -1.1% .. +12.0% | neutral | +0.0% | 85.9 | 2.29x | | word-graph | 271.8 | 270.1 | -0.6% | -7.0% .. +5.5% | neutral | +3.0% | 517.1 | 0.52x | | alloc-trees | 314.4 | 316.6 | +0.7% | -0.4% .. +1.9% | neutral | +0.0% | 124.9 | 2.53x | | numeric-kernels | 46.2 | 45.7 | -1.1% | -2.5% .. +1.5% | neutral | +0.0% | 128.2 | 0.36x | | validate-errors | 179.7 | 174.5 | -2.9% | -5.4% .. +0.7% | neutral | +0.0% | 711.1 | 0.24x | | csv-numbers | 343.0 | 338.9 | -1.2% | -7.3% .. +0.7% | neutral | +0.0% | 519.6 | 0.65x | | log-summary | 166.1 | 168.3 | +1.3% | -4.4% .. +9.1% | neutral | +0.0% | 300.0 | 0.56x | | inventory-report | 298.1 | 290.1 | -2.7% | -14.6% .. +5.4% | neutral | +1.3% | 421.4 | 0.69x | Geomean -5.9%; candidate/node 0.715x. macOS arm64 (shared host): geomean -4.7%, functional-pipeline -44.1%, ast-interp -11.0%, inventory-report -8.0%; none slower (records-sort -0.5% on a calmer rerun).
70 lines
2.3 KiB
TypeScript
70 lines
2.3 KiB
TypeScript
// Callbacks produced by factories and held in captured constants. Every
|
|
// factory call returns the same code with its own environment, so a direct
|
|
// call must still pass the right captures; factories with several shapes
|
|
// stay on the ordinary indirect path.
|
|
interface Row {
|
|
id: number;
|
|
name: string;
|
|
score: number;
|
|
}
|
|
|
|
const rows: Row[] = [
|
|
{ id: 1, name: "cy", score: 30 },
|
|
{ id: 2, name: "ab", score: 10 },
|
|
{ id: 3, name: "ab", score: 30 },
|
|
{ id: 4, name: "dd", score: 20 },
|
|
];
|
|
|
|
function compareBy(keys: string[]): (a: Row, b: Row) => number {
|
|
return (a, b) => {
|
|
for (const key of keys) {
|
|
let d = 0;
|
|
if (key === "name") d = a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
else if (key === "score") d = b.score - a.score;
|
|
else d = a.id - b.id;
|
|
if (d !== 0) return d;
|
|
}
|
|
return 0;
|
|
};
|
|
}
|
|
for (const keys of [["name", "id"], ["score", "name", "id"], ["id"]]) {
|
|
console.log(keys.join(">"), rows.slice().sort(compareBy(keys)).map((row) => row.id).join(","));
|
|
}
|
|
|
|
function scaled(factor: number, offset: number): (row: Row) => number {
|
|
return (row) => row.score * factor + offset;
|
|
}
|
|
let total = 0;
|
|
for (let round = 0; round < 3; round++) {
|
|
const scorer = scaled(round + 1, round * 100);
|
|
const mapped = rows.map((row) => ({ id: row.id, value: scorer(row) }));
|
|
total += mapped.reduce((sum, entry) => sum + entry.value, 0);
|
|
console.log("round", round, mapped.map((entry) => entry.value).join(","));
|
|
}
|
|
console.log("total", total);
|
|
|
|
function pick(ascending: boolean): (a: Row, b: Row) => number {
|
|
if (ascending) return (a, b) => a.score - b.score || a.id - b.id;
|
|
return (a, b) => b.score - a.score || b.id - a.id;
|
|
}
|
|
console.log("asc", rows.slice().sort(pick(true)).map((row) => row.id).join(","));
|
|
console.log("desc", rows.slice().sort(pick(false)).map((row) => row.id).join(","));
|
|
|
|
let comparisons = 0;
|
|
function counting(limit: number): (a: Row, b: Row) => number {
|
|
return (a, b) => {
|
|
comparisons++;
|
|
if (comparisons > limit) throw new Error(`limit ${limit}`);
|
|
return a.id - b.id;
|
|
};
|
|
}
|
|
try {
|
|
rows.slice().reverse().sort(counting(2));
|
|
} catch (error) {
|
|
if (error instanceof Error) console.log("sort threw", error.message, comparisons > 2);
|
|
}
|
|
|
|
const threshold = 15;
|
|
const above = (row: Row): boolean => row.score > threshold;
|
|
console.log("captured", rows.filter((row) => above(row)).map((row) => row.name).join(","));
|