mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-22 05:59:31 +08:00
Load array lengths with the range of a JS array length (#1121)
This commit is contained in:
@@ -63,7 +63,7 @@ export function emitArraySearchHelpers(
|
||||
`define internal double @${arraySearchHelperName(kind)}(ptr %a, ptr %v) ${FN_ATTRS} {`,
|
||||
`entry:`,
|
||||
` %lenp = getelementptr inbounds %ScrArr, ptr %a, i32 0, i32 1`,
|
||||
` %len = load ${sz}, ptr %lenp`,
|
||||
` %len = load ${sz}, ptr %lenp${sz === "i64" ? ", !range !{i64 0, i64 4294967296}" : ""}`,
|
||||
` %fp = getelementptr inbounds %ScrArr, ptr %a, i32 0, i32 6`,
|
||||
` %f32 = load i32, ptr %fp`,
|
||||
widen,
|
||||
|
||||
@@ -97,7 +97,9 @@ function emitDenseGuard(
|
||||
const loadChecked = (): void => {
|
||||
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${array}, i32 0, i32 1`);
|
||||
host.markMemoryPointer(lenPtr, "array:header");
|
||||
B.line(`${len} = load ${size}, ptr ${lenPtr}${host.fieldAliasAttachment(lenPtr)}`);
|
||||
B.line(
|
||||
`${len} = load ${size}, ptr ${lenPtr}${host.fieldAliasAttachment(lenPtr)}${host.arrayLengthRange}`,
|
||||
);
|
||||
B.line(`${capPtr} = getelementptr inbounds %ScrArr, ptr ${array}, i32 0, i32 2`);
|
||||
host.markMemoryPointer(capPtr, "array:header");
|
||||
B.line(`${cap} = load ${size}, ptr ${capPtr}${host.fieldAliasAttachment(capPtr)}`);
|
||||
|
||||
@@ -565,6 +565,13 @@ export class LlEmitter {
|
||||
return `, !tbaa !${tag}`;
|
||||
}
|
||||
|
||||
/** The `!range` attachment of an array length load: a length is at most
|
||||
* 2^32 - 1 (the runtime's SCR_ARR_MAX_LENGTH), so conversions to double
|
||||
* and back fold. 32-bit targets load the full range anyway. */
|
||||
get arrayLengthRange(): string {
|
||||
return this.sizeType === "i64" ? ", !range !{i64 0, i64 4294967296}" : "";
|
||||
}
|
||||
|
||||
fieldAliasAttachment(ptr: string): string {
|
||||
const tag = this.fieldPointerTags.get(ptr);
|
||||
return tag === undefined ? "" : `, !tbaa !${tag}`;
|
||||
@@ -7145,7 +7152,9 @@ export class LlEmitter {
|
||||
done = B.newLabel("arr.len.done");
|
||||
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
||||
this.markMemoryPointer(lenPtr, "array:header");
|
||||
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
||||
B.line(
|
||||
`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`,
|
||||
);
|
||||
B.line(`${lenNumber} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${len} to double`);
|
||||
B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`);
|
||||
B.condBr(same, done, resize);
|
||||
@@ -7819,7 +7828,7 @@ export class LlEmitter {
|
||||
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr!.name}, i32 0, i32 1`);
|
||||
this.markMemoryPointer(lenPtr, "array:header");
|
||||
B.line(
|
||||
`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`,
|
||||
`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`,
|
||||
);
|
||||
B.line(`${len} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${rawLen} to double`);
|
||||
B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
|
||||
|
||||
@@ -557,7 +557,9 @@ export function emitArrIntrinsic(
|
||||
t = B.tmp();
|
||||
B.line(`${p} = getelementptr inbounds %ScrArr, ptr ${r.name}, i32 0, i32 1`);
|
||||
host.markMemoryPointer(p, "array:header");
|
||||
B.line(`${len} = load ${host.sizeType}, ptr ${p}${host.fieldAliasAttachment(p)}`);
|
||||
B.line(
|
||||
`${len} = load ${host.sizeType}, ptr ${p}${host.fieldAliasAttachment(p)}${host.arrayLengthRange}`,
|
||||
);
|
||||
B.line(`${t} = ${sizeToDouble(host.sizeType)} ${host.sizeType} ${len} to double`);
|
||||
return { name: t, type: e.type };
|
||||
}
|
||||
|
||||
@@ -523,7 +523,9 @@ function emitDenseReferenceArrayRead(
|
||||
belowLen = B.tmp();
|
||||
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${array.name}, i32 0, i32 1`);
|
||||
host.markMemoryPointer(lenPtr, "array:header");
|
||||
B.line(`${len} = load ${host.sizeType}, ptr ${lenPtr}${host.fieldAliasAttachment(lenPtr)}`);
|
||||
B.line(
|
||||
`${len} = load ${host.sizeType}, ptr ${lenPtr}${host.fieldAliasAttachment(lenPtr)}${host.arrayLengthRange}`,
|
||||
);
|
||||
B.line(`${belowLen} = icmp ult ${host.sizeType} ${offset}, ${len}`);
|
||||
const stateLabel = B.newLabel("local.array.state");
|
||||
B.condBr(belowLen, stateLabel, no);
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { expect, test } from "vitest";
|
||||
import { compile } from "../src/index.js";
|
||||
|
||||
async function emit(source: string, outputKind: "llvm" | "asm"): Promise<string> {
|
||||
const dir = await mkdtemp(join(tmpdir(), "scriptc-array-length-range-"));
|
||||
try {
|
||||
const entry = join(dir, "main.ts");
|
||||
const outPath = join(dir, outputKind === "llvm" ? "main.ll" : "main.s");
|
||||
await writeFile(entry, source);
|
||||
const result = await compile(entry, { outDir: dir, outPath, outputKind });
|
||||
expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true);
|
||||
return await readFile(outPath, "utf8");
|
||||
} finally {
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
const source = `
|
||||
class Signature {
|
||||
parameters: string[];
|
||||
rest: boolean;
|
||||
constructor(parameters: string[], rest: boolean) {
|
||||
this.parameters = parameters;
|
||||
this.rest = rest;
|
||||
}
|
||||
}
|
||||
function count(signature: Signature): number {
|
||||
const length = signature.parameters.length;
|
||||
if (signature.rest) return length + signature.parameters[length - 1]!.length;
|
||||
return length;
|
||||
}
|
||||
const signature = new Signature(["a", "bb"], process.argv.length > 1);
|
||||
console.log(count(signature), count(new Signature(["x"], false)));
|
||||
`;
|
||||
|
||||
test.runIf(process.arch === "x64" || process.arch === "arm64")(
|
||||
"array length loads carry the range of a JS array length",
|
||||
async () => {
|
||||
const llvm = await emit(source, "llvm");
|
||||
const body = /^define internal double @sc_bf_count\([^]*?^}/m.exec(llvm)?.[0] ?? "";
|
||||
expect(body).toMatch(/= load i64, ptr %\S+(, !tbaa !\d+)?, !range !\{i64 0, i64 4294967296\}/);
|
||||
},
|
||||
);
|
||||
|
||||
test.runIf(process.arch === "x64")(
|
||||
"a length used as an index is not converted to double and back",
|
||||
async () => {
|
||||
const asm = await emit(source, "asm");
|
||||
const body = /^sc_bf_count:[^]*?\.cfi_endproc/m.exec(asm)?.[0] ?? "";
|
||||
expect(body).not.toMatch(/cvtsi2sd|cvttsd2si/);
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user