mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-03 07:54:40 +08:00
Number hoisted speculative leaves per function (#2179)
This commit is contained in:
@@ -66,6 +66,7 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string {
|
||||
emitter.usesLandingPads = false;
|
||||
emitter.aliasScopedVersions = 0;
|
||||
emitter.findInt32Allocations(fn);
|
||||
emitter.speculativeLeaves = 0;
|
||||
emitter.currentTier = fn.tier;
|
||||
// Fiber switches restore the active exception cell before control
|
||||
// returns to a synchronous frame (emitPendingCheck).
|
||||
|
||||
@@ -1550,8 +1550,11 @@ export class LlEmitter {
|
||||
speculationResume: Map<IrStmt, { label: string; placed: boolean }> | null = null;
|
||||
/** The rewritten values of the writes `speculationResume` resumes at. */
|
||||
speculationResumeValues: ReadonlyMap<IrStmt, SpeculativeValue> | null = null;
|
||||
/** Hoisted speculative leaves numbered so far in the module. */
|
||||
private speculativeLeaves = 0;
|
||||
/** Hoisted speculative leaves numbered so far in the function being
|
||||
* emitted (emitFunction resets it): a function's local names must not
|
||||
* depend on which functions the same emitter emitted before it, since
|
||||
* function workers each emit a share of the module. */
|
||||
speculativeLeaves = 0;
|
||||
private readonly inlinableFunctions = new Map<string, InlinableFunction | undefined>();
|
||||
|
||||
/** What speculative loop planning may assume here (SpeculationContext). */
|
||||
|
||||
@@ -59,7 +59,7 @@ console.log(work(new Int32Array(buffer), new Uint8Array(buffer), new Int16Array(
|
||||
// Every runtime call is the cold path for an index out of range.
|
||||
const calls = work.match(/call double @scr_atomics_op\([^\n]*/g) ?? [];
|
||||
expect(calls).toHaveLength(9);
|
||||
for (const call of calls) expect(call).toMatch(/\) cold$/);
|
||||
for (const call of calls) expect(call).toMatch(/\)(?: nounwind)? cold$/);
|
||||
});
|
||||
|
||||
test("an unchanged local array is borrowed across Atomics operations", async () => {
|
||||
|
||||
@@ -71,7 +71,7 @@ describe("interface method dispatch", () => {
|
||||
test("calls through an interface reach the implementing methods with typed values", async () => {
|
||||
const llvm = await emittedLlvm("direct", shapes(""));
|
||||
const run = bodies(llvm, "sc_bf_run");
|
||||
expect(run).toMatch(/call double @sc_bf__x25_dyn_class_protocol_\d+/);
|
||||
expect(run).toMatch(/(?:call|invoke) double @sc_bf__x25_dyn_class_protocol_\d+/);
|
||||
expect(run).not.toContain("@scr_dyn_call");
|
||||
expect(run).not.toContain("@sc_bf__x25_dyn_class_property_");
|
||||
const dispatch = llvm.slice(llvm.indexOf("; %dyn.class.protocol.0"));
|
||||
@@ -90,7 +90,7 @@ describe("interface method dispatch", () => {
|
||||
console.log(sum([circle, new Rect(1)]));`),
|
||||
);
|
||||
const sum = bodies(llvm, "sc_bf_sum");
|
||||
expect(sum).toMatch(/call double @sc_bf__x25_dyn_class_protocol_\d+/);
|
||||
expect(sum).toMatch(/(?:call|invoke) double @sc_bf__x25_dyn_class_protocol_\d+/);
|
||||
expect(sum).not.toContain("@scr_dyn_iterator");
|
||||
expect(sum).not.toContain("@sc_bf__x25_dyn_class_computed_");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/* Speculative integer loops (behavior is in the differential corpus, 7757):
|
||||
* the locals that hold hoisted calls are numbered per function, so a
|
||||
* function's text does not depend on the functions emitted before it (the
|
||||
* native compiler's function workers each emit a share of the module). */
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { expect, test } from "vitest";
|
||||
import { compile } from "@scriptc/compiler";
|
||||
|
||||
const repoRoot = join(import.meta.dirname, "../..");
|
||||
const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests");
|
||||
|
||||
async function emittedLlvm(name: string, source: string): Promise<string> {
|
||||
const key = createHash("sha256").update(source).digest("hex").slice(0, 16);
|
||||
const outDir = join(cacheDir, `spec-leaves-${key}`);
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
const file = join(outDir, `${name}.ts`);
|
||||
writeFileSync(file, source);
|
||||
const result = await compile(file, {
|
||||
outPath: join(outDir, name),
|
||||
outDir,
|
||||
backend: "llvm",
|
||||
outputKind: "llvm",
|
||||
});
|
||||
if (!result.ok)
|
||||
throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
|
||||
return readFileSync(result.artifact.path, "utf8");
|
||||
}
|
||||
|
||||
function body(llvm: string, name: string): string {
|
||||
const start = llvm.search(new RegExp(`^define internal [^\\n]*@${name}[.(]`, "m"));
|
||||
expect(start).toBeGreaterThanOrEqual(0);
|
||||
return llvm.slice(start, llvm.indexOf("\n}\n", start));
|
||||
}
|
||||
|
||||
test("hoisted speculative leaves are numbered per function", async () => {
|
||||
const llvm = await emittedLlvm(
|
||||
"leaves",
|
||||
`let calls = 0;
|
||||
function noisy(x: number): number {
|
||||
calls++;
|
||||
return x % 5 === 0 ? x / 2 : x * 2;
|
||||
}
|
||||
function first(n: number): number {
|
||||
let acc = 1;
|
||||
for (let i = 0; i < n; i++) acc = (acc * 7 + noisy(i)) % 1000003;
|
||||
return acc;
|
||||
}
|
||||
function second(n: number): number {
|
||||
let acc = 3;
|
||||
for (let i = 0; i < n; i++) acc = (acc * 5 + noisy(i)) % 1000003;
|
||||
return acc;
|
||||
}
|
||||
console.log(first(20), second(20), calls);
|
||||
`,
|
||||
);
|
||||
for (const name of ["sc_f_first", "sc_f_second"]) {
|
||||
const leaves = body(llvm, name).match(/%\w*spec_leaf_\d+ = alloca double/g) ?? [];
|
||||
expect(leaves).toHaveLength(1);
|
||||
expect(leaves[0]).toMatch(/_spec_leaf_0 = /);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user