Files
scriptc/packages/compiler/test/size-conversions.test.ts
T
Chris Tate 9aab9af547 Fix wasm32 array lengths past 2^31 and pending top-level await exit codes
The backend converted size_t lengths and indexes to numbers with sitofp. On
wasm32 size_t is 32 bits, and an array length reaches 2^32 - 1, so a sparse
array of length 4294967295 read -1. sizeToDouble picks uitofp for a 32-bit
size_t and keeps sitofp for 64 bits, whose sign bit a length never reaches,
so native output is unchanged.

An unsettled top-level await always exited 13. Node exits with
`process.exitCode || 13` and runs the exit listeners first, which can set
process.exitCode again. scr_promise_finish_top_level now notes 13 only when
the program set no nonzero exit code, and the pending exit returns the
exit code after the listeners, as the other exits do.

Three WASI tests still exercised the embedded-JS fallback that native-only
compilation removed: an embedded-source fetch scan and two npm fixtures
that every target now refuses. They now check the same behavior with
static programs: fetch named only in text compiles, a global fetch call
reports SC3002, and CommonJS and ESM packages compile and match Node.
2026-10-15 13:39:03 -05:00

36 lines
1.5 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";
import type { IrModule } from "../src/ir/ir.js";
async function lowerSource(source: string): Promise<IrModule> {
const dir = await mkdtemp(join(tmpdir(), "scriptc-size-conversions-"));
try {
const entry = join(dir, "main.ts");
await writeFile(entry, source);
const outPath = join(dir, "main.ir.json");
const result = await compile(entry, { outPath, outDir: dir, outputKind: "ir" });
if (!result.ok) throw new Error(result.diagnostics.map((d) => d.message).join("\n"));
return deserializeModule(await readFile(outPath, "utf8"));
} finally {
await rm(dir, { recursive: true, force: true });
}
}
test("array lengths past 2^31 convert unsigned on 32-bit targets", async () => {
// A sparse write at index 2^32 - 2 leaves the length at 2^32 - 1, past a
// 32-bit size_t's sign bit.
const mod = await lowerSource(
"const numbers = [1];\nnumbers[4294967294] = 2;\nconsole.log(numbers.length);\n",
);
const wasm = emitLlvmModule(mod, { wasi: true, pointerBits: 32 });
expect(wasm).toMatch(/uitofp i32 %\w+ to double/);
expect(wasm).not.toMatch(/sitofp i32 %\w+ to double/);
const native = emitLlvmModule(mod, { pointerBits: 64 });
expect(native).toMatch(/sitofp i64 %\w+ to double/);
expect(native).not.toMatch(/uitofp i64/);
});