mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-22 05:59:31 +08:00
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks. - Preserve compiler import boundaries and protected fixtures while cleaning up lint findings. - Standardize source formatting and validate import, fixture, and embedded-source protections.
67 lines
2.9 KiB
TypeScript
67 lines
2.9 KiB
TypeScript
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, deserializeModule } from "../src/index.js";
|
|
import { emitLlvmModule } from "../src/backend/llvm/emitter.js";
|
|
|
|
test("integer loop recurrences reuse converted values on both pointer widths and preserve debug storage", async () => {
|
|
const dir = await mkdtemp(join(tmpdir(), "scriptc-integer-views-"));
|
|
try {
|
|
const entry = join(dir, "main.ts");
|
|
const outPath = join(dir, "main.ir.json");
|
|
const source = `
|
|
function mix(input: number, bytes: Uint32Array): number {
|
|
let state = input;
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
state = i % 2 === 0 ? Math.imul(state, 17) : state ^ bytes[i];
|
|
state = (state << 7) | (state >>> 25);
|
|
bytes[i] = state;
|
|
}
|
|
return state;
|
|
}
|
|
function floating(input: number, flag: boolean): number {
|
|
return (flag ? input | 0 : input + 0.5) >>> 0;
|
|
}
|
|
function observedAfter(input: number, count: number): number {
|
|
let value = input;
|
|
for (let i = 0; i < count; i++) value = Math.sqrt(value * value + 0.125);
|
|
return value | 0;
|
|
}
|
|
console.log(mix(-0, new Uint32Array(3)), floating(1.75, false), observedAfter(1.75, 3));
|
|
`;
|
|
await writeFile(entry, source);
|
|
const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" });
|
|
if (!result.ok)
|
|
throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
|
|
const mod = deserializeModule(await readFile(outPath, "utf8"));
|
|
const body = (ll: string, name: string): string =>
|
|
ll.match(
|
|
new RegExp(
|
|
`define internal [^\\n]+ @sc_(?:b)?f_${name}\\([^\\n]*\\) #0 \\{([\\s\\S]*?)\\n\\}`,
|
|
),
|
|
)![1]!;
|
|
for (const pointerBits of [32, 64] as const) {
|
|
const ll = emitLlvmModule(mod, { pointerBits, wasi: pointerBits === 32 });
|
|
const mix = body(ll, "mix");
|
|
expect(mix).toContain("integer view state");
|
|
// The original input is converted once, before entering the loop.
|
|
expect(mix.match(/^uint32\.coerce\.slow\.[^\n]*:/gm)).toHaveLength(1);
|
|
const loopStart = mix.search(/\nloop\.b[._\d]/);
|
|
expect(loopStart).toBeGreaterThan(0);
|
|
const loop = mix.slice(loopStart);
|
|
expect(loop).not.toContain("uint32.coerce.slow");
|
|
expect(loop).not.toContain(" = fptoui double ");
|
|
expect(loop).toContain(" = mul i32 ");
|
|
expect(loop).toMatch(/store i32 %\w+, ptr %\w+, align 1/);
|
|
expect(body(ll, "floating")).toContain("uint32.coerce.slow");
|
|
expect(body(ll, "observedAfter")).not.toContain("integer view");
|
|
}
|
|
const debug = emitLlvmModule(mod, { debugSources: new Map([[entry, source]]) });
|
|
expect(debug).not.toContain("integer view state");
|
|
expect(debug).toContain("@llvm.dbg.declare");
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
});
|