mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
A while loop whose remainder reads number locals that enter it without range facts, such as copies of module globals, parameters or fields (`const base = column;` before `(count + base - breaks) % 60`), now runs as two copies. On entry, a test checks that each such local holds an integer in [-2^48, 2^48] other than -0. The fast copy uses the facts the integer range analysis proves when the function is analyzed again with those entry ranges assumed: the loop's counters become bounded, and the sums and remainders run as i64 arithmetic. When the test fails, the loop runs as it did before. - ir/loop-entry-ranges.ts picks innermost while loops with a remainder that the assumed ranges prove and the function's own analysis does not. The test covers only the locals the remainder depends on, directly, through the loop's writes or through its condition. - ir/integer-ranges.ts takes the assumed entry ranges per loop, and keeps no fact about the assumed locals or the loop's writes after it. - backend/llvm/loop-entry-versions.ts emits the test and both copies. The test is marked likely. A function called only from the loop gets `alwaysinline` on the fast copy's call, because the second copy would otherwise cost it the inlining of a single call site. reverse-complement (25M bases): 2.08 s -> 1.19 s; Node 3.97 s.
161 lines
3.7 KiB
TypeScript
161 lines
3.7 KiB
TypeScript
// While loops whose remainder reads locals copied from module globals,
|
|
// parameters and fields. Entry values cover integers inside and outside
|
|
// [-2^48, 2^48], fractions, -0, NaN and infinities.
|
|
|
|
let column = 0;
|
|
let lines = 0;
|
|
|
|
// A switch with more cases than a small leaf function has statements.
|
|
function complement(code: number): number {
|
|
switch (code) {
|
|
case 65:
|
|
return 84;
|
|
case 67:
|
|
return 71;
|
|
case 71:
|
|
return 67;
|
|
case 84:
|
|
return 65;
|
|
case 97:
|
|
return 116;
|
|
case 99:
|
|
return 103;
|
|
case 103:
|
|
return 99;
|
|
case 116:
|
|
return 97;
|
|
case 85:
|
|
return 65;
|
|
case 117:
|
|
return 97;
|
|
case 82:
|
|
return 89;
|
|
case 89:
|
|
return 82;
|
|
case 75:
|
|
return 77;
|
|
case 77:
|
|
return 75;
|
|
}
|
|
return 78;
|
|
}
|
|
|
|
// Copies bytes in reverse, complemented, with a newline after every 8
|
|
// columns counted from the module-level column and line counters.
|
|
function wrap(input: Uint8Array, out: Uint8Array): number {
|
|
const base = column;
|
|
let breaks = lines;
|
|
let count = 0;
|
|
let o = 0;
|
|
let i = 0;
|
|
const len = input.length;
|
|
let last = 1;
|
|
let negativeZeros = 0;
|
|
while (i < len) {
|
|
out[o] = complement(input[len - 1 - i]!);
|
|
i++;
|
|
o++;
|
|
count++;
|
|
last = (count + base - breaks) % 8;
|
|
if (Object.is(last, -0)) negativeZeros++;
|
|
if (last === 0) {
|
|
out[o] = 10;
|
|
o++;
|
|
count++;
|
|
breaks++;
|
|
}
|
|
}
|
|
column = base + count;
|
|
lines = breaks;
|
|
console.log(` last ${Object.is(last, -0) ? "-0" : last}, ${negativeZeros} of them -0`);
|
|
return o;
|
|
}
|
|
|
|
function text(out: Uint8Array, n: number): string {
|
|
let s = "";
|
|
for (let i = 0; i < n; i++) s += out[i] === 10 ? "|" : String.fromCharCode(out[i]!);
|
|
return s;
|
|
}
|
|
|
|
const input = new Uint8Array(29);
|
|
const bases = "ACGTTGCAAGCTTCGA";
|
|
for (let i = 0; i < input.length; i++) input[i] = bases.charCodeAt((i * 7) % bases.length);
|
|
const out = new Uint8Array(64);
|
|
|
|
const starts: [number, number][] = [
|
|
[0, 0],
|
|
[5, 2],
|
|
[-13, 0],
|
|
[-16, 0],
|
|
[2 ** 48, 3],
|
|
[-(2 ** 48), 0],
|
|
[2 ** 48 + 1, 0],
|
|
[2 ** 50, 1],
|
|
[2 ** 53 - 5, 0],
|
|
[2 ** 53, 0],
|
|
[2 ** 53 + 2, 1],
|
|
[0.5, 0],
|
|
[3, 0.25],
|
|
[3, 2 ** 49],
|
|
[-0, 0],
|
|
[0, -0],
|
|
[NaN, 0],
|
|
[Infinity, 0],
|
|
[-Infinity, 0],
|
|
[1e300, 0],
|
|
];
|
|
for (const [c, l] of starts) {
|
|
column = c;
|
|
lines = l;
|
|
console.log(`start column ${c} lines ${l}`);
|
|
const n = wrap(input, out);
|
|
console.log(` ${text(out, n)}`);
|
|
console.log(` column ${column} lines ${lines}`);
|
|
}
|
|
|
|
// Two calls in a row: the second starts from the first one's counters.
|
|
column = 3;
|
|
lines = 0;
|
|
wrap(input, out);
|
|
const n = wrap(input, out);
|
|
console.log(`again ${text(out, n)} column ${column} lines ${lines}`);
|
|
|
|
// A parameter and a field as the loop's entry values.
|
|
function ring(values: Int32Array, offset: number, size: number): number {
|
|
let sum = 0;
|
|
let i = 0;
|
|
while (i < values.length) {
|
|
sum += values[(i + offset) % size]!;
|
|
i++;
|
|
}
|
|
return sum;
|
|
}
|
|
const values = new Int32Array([3, 1, 4, 1, 5, 9, 2, 6]);
|
|
for (const offset of [0, 3, 7, 2 ** 40, 2 ** 49, 1.5, -0, -2]) {
|
|
const sum = ring(values, offset, values.length);
|
|
console.log(`ring ${offset} ${Number.isNaN(sum) ? "NaN" : sum}`);
|
|
}
|
|
|
|
class Cursor {
|
|
position: number;
|
|
constructor(position: number) {
|
|
this.position = position;
|
|
}
|
|
stripes(bytes: Uint8Array): string {
|
|
const start = this.position;
|
|
let i = 0;
|
|
let marks = "";
|
|
while (i < bytes.length) {
|
|
const r = (start + i) % 3;
|
|
marks += Object.is(r, -0) ? "z" : String(r);
|
|
i++;
|
|
}
|
|
this.position = start + i;
|
|
return marks;
|
|
}
|
|
}
|
|
for (const position of [0, 4, -5, 2 ** 48 + 7, 0.75, 2 ** 60]) {
|
|
const cursor = new Cursor(position);
|
|
console.log(`stripes ${position} ${cursor.stripes(input.subarray(0, 7))} ${cursor.position}`);
|
|
}
|