mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-05 08:13:06 +08:00
Start integer loops from the integers their starts carry
This commit is contained in:
@@ -701,7 +701,8 @@ test("a two-counter loop versions the typed arrays it indexes at either counter"
|
||||
};
|
||||
const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(emitLlvmModule(mod))![0];
|
||||
const guard = body.slice(0, body.search(/^bytes\.version\.(fast|slow)\d+:$/m));
|
||||
expect(guard).toMatch(/icmp sge i64 %\S+, 0/);
|
||||
// `i` starts at the literal 0, so its test compares the integer itself.
|
||||
expect(guard).toMatch(/icmp sge i64 0, 0/);
|
||||
expect(guard).toMatch(/icmp slt i64 %\S+, %\S+/);
|
||||
const fast = /^bytes\.version\.fast\d+:$[^]*?^bytes\.version\.done\d+:$/m.exec(body)![0];
|
||||
// The fast copy keeps only the `src[i + 1]` check, and no second guard.
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
emitCountedLoopLimit,
|
||||
emitCountedLoopSafety,
|
||||
emitExactIntegerTest,
|
||||
emitStartIntegerTest,
|
||||
} from "./counted-loops.js";
|
||||
import { emitBytesHeaderLength, emitBytesSharedStorage } from "./expr-bytes.js";
|
||||
import { f64Lit } from "./common.js";
|
||||
@@ -1251,8 +1252,8 @@ export function emitBytesLoopVersionGuard(
|
||||
// access at either one is in bounds when `lo >= 0` and `hi < length`.
|
||||
const { loop } = version.paired;
|
||||
const [low, high] = [loop.low.start, loop.high.start].map((expr) => {
|
||||
const tested = emitExactIntegerTest(host, host.emitExpr(expr).name, -SAFE, SAFE);
|
||||
conditions.push(tested.ok);
|
||||
const tested = emitStartIntegerTest(host, expr, -SAFE, SAFE);
|
||||
if (tested.ok !== "true") conditions.push(tested.ok);
|
||||
return tested.integer;
|
||||
});
|
||||
const nonNegative = B.tmp();
|
||||
|
||||
@@ -7,6 +7,7 @@ import { FOLLOWER_GUARD_LIMIT } from "../../ir/integer-loops.js";
|
||||
import type { IrExpr } from "../../ir/ir.js";
|
||||
import type { LlvmEmitterContext } from "./expr-context.js";
|
||||
import { f64Lit } from "./common.js";
|
||||
import { exactInteger, widenInteger } from "./integer-values.js";
|
||||
|
||||
/** The caller has proved or guarded the direction's finite endpoint. Clamp
|
||||
* the opposite infinity before integer conversion. Fractional limits become
|
||||
@@ -121,6 +122,7 @@ export function emitPairedLoopSafety(host: LlvmEmitterContext, loop: IntegerPair
|
||||
}
|
||||
|
||||
function andSafe(host: LlvmEmitterContext, safe: string, ok: string): string {
|
||||
if (ok === "true") return safe;
|
||||
if (safe === "true") return ok;
|
||||
const both = host.B.tmp();
|
||||
host.B.line(`${both} = and i1 ${safe}, ${ok}`);
|
||||
@@ -129,10 +131,25 @@ function andSafe(host: LlvmEmitterContext, safe: string, ok: string): string {
|
||||
|
||||
/** An exact integer of at most 2^53 - 1 in magnitude, and not -0 (the bit
|
||||
* patterns of the value and its round trip agree). NaN and infinities fail
|
||||
* the ordered range tests. */
|
||||
* the ordered range tests. A start with an integer form (an int32 local,
|
||||
* `p0 - 1` of one) is an exact integer already: its range answers the test,
|
||||
* or two integer compares do. */
|
||||
function emitExactStart(host: LlvmEmitterContext, expr: IrExpr): string {
|
||||
const B = host.B;
|
||||
const start = host.emitExpr(expr);
|
||||
const integer = exactInteger(host, start, expr);
|
||||
if (integer) {
|
||||
const limit = Number.MAX_SAFE_INTEGER;
|
||||
if (integer.range.min >= -limit && integer.range.max <= limit) return "true";
|
||||
const wide = widenInteger(host, integer),
|
||||
low = B.tmp(),
|
||||
high = B.tmp(),
|
||||
ok = B.tmp();
|
||||
B.line(`${low} = icmp sge i64 ${wide}, ${-limit}`);
|
||||
B.line(`${high} = icmp sle i64 ${wide}, ${limit}`);
|
||||
B.line(`${ok} = and i1 ${low}, ${high}`);
|
||||
return ok;
|
||||
}
|
||||
const low = B.tmp(),
|
||||
high = B.tmp(),
|
||||
raw = B.tmp(),
|
||||
@@ -188,3 +205,39 @@ export function emitExactIntegerTest(
|
||||
B.line(`${ok} = and i1 ${inRange}, ${exact}`);
|
||||
return { ok, integer };
|
||||
}
|
||||
|
||||
/** The i64 start of an integer counter: the integer form the start value
|
||||
* carries (an int32 local, `p0 - 1` of one), or the double's conversion,
|
||||
* which the loop's guard or proof makes exact. */
|
||||
export function emitCountedStart(host: LlvmEmitterContext, expr: IrExpr): string {
|
||||
const value = host.emitExpr(expr);
|
||||
const integer = exactInteger(host, value, expr);
|
||||
if (integer) return widenInteger(host, integer);
|
||||
const start = host.B.tmp();
|
||||
host.B.line(`${start} = fptosi double ${value.name} to i64`);
|
||||
return start;
|
||||
}
|
||||
|
||||
/** emitExactIntegerTest of a start expression: when the value carries an
|
||||
* integer form (an exact integer, never -0), its range answers the test or
|
||||
* two integer compares do, and the integer is the form's. */
|
||||
export function emitStartIntegerTest(
|
||||
host: LlvmEmitterContext,
|
||||
expr: IrExpr,
|
||||
min: number,
|
||||
max: number,
|
||||
): { ok: string; integer: string } {
|
||||
const value = host.emitExpr(expr);
|
||||
const integer = exactInteger(host, value, expr);
|
||||
if (!integer) return emitExactIntegerTest(host, value.name, min, max);
|
||||
const wide = widenInteger(host, integer);
|
||||
if (integer.range.min >= min && integer.range.max <= max) return { ok: "true", integer: wide };
|
||||
const B = host.B;
|
||||
const low = B.tmp(),
|
||||
high = B.tmp(),
|
||||
ok = B.tmp();
|
||||
B.line(`${low} = icmp sge i64 ${wide}, ${min}`);
|
||||
B.line(`${high} = icmp sle i64 ${wide}, ${max}`);
|
||||
B.line(`${ok} = and i1 ${low}, ${high}`);
|
||||
return { ok, integer: wide };
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import { everyExprChild, everyStmtChild } from "../../ir/traverse.js";
|
||||
import type { IntegerCountedForLoop, IntegerPairedForLoop } from "../../ir/integer-loops.js";
|
||||
import type { LlvmEmitterContext } from "./expr-context.js";
|
||||
import { loadArrCount, SCR_ARR_FIELD } from "./common.js";
|
||||
import { emitCountedLoopLimit, emitExactIntegerTest } from "./counted-loops.js";
|
||||
import { emitCountedLoopLimit, emitStartIntegerTest } from "./counted-loops.js";
|
||||
|
||||
export const INT32_MAX = 2147483647;
|
||||
|
||||
@@ -453,7 +453,7 @@ function emitPairedDenseGuard(
|
||||
): string {
|
||||
const B = host.B;
|
||||
const start = (expr: IrExpr, min: number, max: number) =>
|
||||
emitExactIntegerTest(host, host.emitExpr(expr).name, min, max);
|
||||
emitStartIntegerTest(host, expr, min, max);
|
||||
const low = start(pair.lowStart, 0, INT32_MAX - 1);
|
||||
const high = start(pair.highStart, -1, INT32_MAX - 1);
|
||||
let valid = B.tmp();
|
||||
|
||||
@@ -61,6 +61,7 @@ import {
|
||||
import {
|
||||
emitCountedLoopLimit,
|
||||
emitCountedLoopSafety,
|
||||
emitCountedStart,
|
||||
emitPairedLoopSafety,
|
||||
} from "./counted-loops.js";
|
||||
import { emitBytesLoopVersionGuard, type BytesLoopVersion } from "./bytes-loop-versions.js";
|
||||
@@ -1112,9 +1113,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
|
||||
B.entryAllocas.push(
|
||||
`${integerSlot} = alloca i64 ; integer induction ${emitter.currentLocals.get(countedLoop.localId)!.name}`,
|
||||
);
|
||||
const startNumber = emitter.emitExpr(countedLoop.start);
|
||||
const start = B.tmp();
|
||||
B.line(`${start} = fptosi double ${startNumber.name} to i64`);
|
||||
const start = emitCountedStart(emitter, countedLoop.start);
|
||||
B.line(`store i64 ${start}, ptr ${integerSlot}`);
|
||||
emitter.integerLoopBindings.set(countedLoop.localId, {
|
||||
slot: integerSlot,
|
||||
@@ -1130,9 +1129,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
|
||||
B.entryAllocas.push(
|
||||
`${slot} = alloca i64 ; integer induction ${emitter.currentLocals.get(follower.localId)!.name}`,
|
||||
);
|
||||
const value = emitter.emitExpr(follower.start);
|
||||
const followerStart = B.tmp();
|
||||
B.line(`${followerStart} = fptosi double ${value.name} to i64`);
|
||||
const followerStart = emitCountedStart(emitter, follower.start);
|
||||
B.line(`store i64 ${followerStart}, ptr ${slot}`);
|
||||
followerSlots.push(slot);
|
||||
emitter.integerLoopBindings.set(follower.localId, {
|
||||
@@ -1149,9 +1146,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
|
||||
B.entryAllocas.push(
|
||||
`${slot} = alloca i64 ; integer induction ${emitter.currentLocals.get(counter.localId)!.name}`,
|
||||
);
|
||||
const value = emitter.emitExpr(counter.start);
|
||||
const start = B.tmp();
|
||||
B.line(`${start} = fptosi double ${value.name} to i64`);
|
||||
const start = emitCountedStart(emitter, counter.start);
|
||||
B.line(`store i64 ${start}, ptr ${slot}`);
|
||||
pairedSlots.push(slot);
|
||||
const dense = LOOPS.of(emitter).denseLoop;
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8196-counted-loops-integer-starts.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Counted, paired and dense array loops whose start is an int32 value
|
||||
// (an int32 array element, `p0 | 0` or one minus either): starts at -1, 0,
|
||||
// inside the array, at its end, and at the int32 extremes, where the loop
|
||||
// must not run or must stop at the array's end as Node does. Number arrays
|
||||
// and an Int32Array both run their paired loops.
|
||||
function reverse(pp: number[], p0: number): number {
|
||||
let swaps = 0;
|
||||
for (let i = 1, j = (p0 | 0) - 1; i < j; i++, j--) {
|
||||
const t = pp[i]!;
|
||||
pp[i] = pp[j]!;
|
||||
pp[j] = t;
|
||||
swaps++;
|
||||
}
|
||||
return swaps;
|
||||
}
|
||||
|
||||
function reverseBytes(values: Int32Array, p0: number): number {
|
||||
let swaps = 0;
|
||||
for (let i = 0, j = (p0 | 0) - 1; i < j; i++, j--) {
|
||||
const t = values[i]!;
|
||||
values[i] = values[j]!;
|
||||
values[j] = t;
|
||||
swaps++;
|
||||
}
|
||||
return swaps;
|
||||
}
|
||||
|
||||
function sumFrom(values: number[], start: number): number {
|
||||
let total = 0;
|
||||
for (let k = start - 1; k < values.length; k++) total += values[k] ?? 100;
|
||||
return total;
|
||||
}
|
||||
|
||||
function countdown(values: Int32Array, from: number): number {
|
||||
let total = 0;
|
||||
for (let k = from; k >= 0; k--) total += values[k] ?? 1000;
|
||||
return total;
|
||||
}
|
||||
|
||||
const starts: number[] = [0, 1, 2, 5, 9, 10, 11, 2147483647, -2147483648];
|
||||
const ints = new Int32Array(starts);
|
||||
for (let s = 0; s < ints.length; s++) {
|
||||
const p0 = ints[s]!;
|
||||
const pp = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
const swaps = p0 <= 11 ? reverse(pp, p0) : 0;
|
||||
const bytes = new Int32Array(pp);
|
||||
const byteSwaps = p0 <= 11 ? reverseBytes(bytes, p0) : 0;
|
||||
console.log(p0, swaps, pp.join(""), p0 <= 11 && p0 >= -1 ? sumFrom(pp, p0) : "-", p0 <= 11 ? countdown(ints, p0) : "-");
|
||||
console.log(" bytes", byteSwaps, bytes.join(""));
|
||||
}
|
||||
Reference in New Issue
Block a user