mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
A build now names the functions of the program's own files that compile
to the dynamic tier and likely run often, so a slow program with a little
untyped code says where the time goes:
warning SC6101: 3 functions that likely run often use untyped values, which runs slower than typed code:
advance (nbody.js:22): untyped JavaScript file, in a loop
...
- The functions come from the tier assignment lowering already makes
(Lowerer.dynamicFunctions). Without a profile, a function counts as
likely hot when a dynamic operation runs inside one of its loops, when
a loop calls it, or when it calls itself (lowering/dynamic-tier.ts).
- A function is named only when its source says why its values are
untyped: an `any` (for a for...of variable, the value it iterates), an
untyped JSON.parse result, or an untyped JavaScript file. Generated
%dyn.* helpers, module top levels and functions that are dynamic for an
operation without a static lowering are left out.
- At most five functions are named, most likely hot first, with the
total; dynamic-tier code in npm packages is counted per package on one
line. All of the warning's text is in diagnostics/dynamic-tier.ts.
- SC6101 opens the SC61xx band, performance notes, with a new
"performance" category in the diagnostics envelope; SC60xx stay the
divergences.
- scriptc build prints it after the divergence warnings, and the
executable cache stores it with the entry, so a cached build (including
the CLI's routed fast path) repeats it. scriptc coverage reports it, and
a comment-only edit no longer replays a verdict that carries it, since
its text holds line numbers.
- --no-dynamic-tier-warning leaves it out of build and coverage;
coverage --fail-on=dynamic fails on it.
- Docs: coverage.mdx (Dynamic tier, Gating builds and CI, the envelope
categories) and cli.mdx.
128 lines
4.4 KiB
TypeScript
128 lines
4.4 KiB
TypeScript
import type { CompileFailure, CompileRequestOptions } from "../compile-types.js";
|
|
import type { FfiProfile } from "../ffi/ffi-manifest.js";
|
|
import { frontendDisposed } from "../frontend/dispose-hook.js";
|
|
import {
|
|
checkerPanicDiag,
|
|
npmStaticRefusalDiag,
|
|
iceDiag,
|
|
isCheckerPanic,
|
|
type ScrDiagnostic,
|
|
} from "../diagnostics/diagnostic.js";
|
|
import type { IrModule, SrcLoc } from "../ir/ir.js";
|
|
import { validateModule } from "../ir/validate.js";
|
|
import { checkContracts } from "../contracts/check.js";
|
|
import {
|
|
fenceSpeculativeWasiFunctions,
|
|
moduleWasiUnavailableSurface,
|
|
targetRefusalDiag,
|
|
} from "../backend/target-diagnostics.js";
|
|
import type { LowerResult, LowerStats } from "../frontend/lowering/lowerer.js";
|
|
import type { FrontendFactory } from "../frontend/pipeline.js";
|
|
import type { CompilationTiming } from "../timing.js";
|
|
|
|
export interface PreparedExecutableModule {
|
|
ok: true;
|
|
mod: IrModule;
|
|
sourceTexts: Map<string, string>;
|
|
stats: LowerStats;
|
|
/** Divergence warnings (SC6xxx): the build succeeds; these sites can
|
|
* behave differently from Node. */
|
|
warnings: ScrDiagnostic[];
|
|
}
|
|
|
|
/** Release the parser/checker before native object generation begins. */
|
|
export function prepareExecutableModule(
|
|
entryPath: string,
|
|
opts: CompileRequestOptions,
|
|
ffi: FfiProfile | null,
|
|
buildPlatform: string,
|
|
createFrontend: FrontendFactory,
|
|
timing: CompilationTiming = () => {},
|
|
): PreparedExecutableModule | CompileFailure {
|
|
const fe = createFrontend(entryPath, opts.npmStatic);
|
|
timing("frontend-load");
|
|
let lowered: LowerResult;
|
|
let sourceTexts: Map<string, string>;
|
|
// The frontend (and its tsgo server) is released as soon as lowering
|
|
// ends — clang and the link never hold it open.
|
|
try {
|
|
const fail = (diagnostics: ScrDiagnostic[]): CompileFailure => ({
|
|
ok: false,
|
|
diagnostics,
|
|
sourceTexts: fe.sourceTexts(),
|
|
});
|
|
|
|
const refused = fe.npmStatic.filter((entry) => entry.status === "refused");
|
|
if (refused.length > 0) {
|
|
return fail(
|
|
refused.map((entry) =>
|
|
npmStaticRefusalDiag(
|
|
entry.package,
|
|
entry.detail ?? "its static compilation was refused",
|
|
fe.npmImportSites.get(entry.package) ?? { file: entryPath, start: 0, end: 0 },
|
|
entry.hint,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
if (fe.preflight.length > 0) return fail(fe.preflight);
|
|
|
|
try {
|
|
lowered = fe.lower({
|
|
targetPlatform: buildPlatform,
|
|
...(ffi !== null ? { ffiImports: ffi.functions } : {}),
|
|
...(opts.tier !== undefined ? { tierMode: opts.tier } : {}),
|
|
});
|
|
timing("lower");
|
|
} catch (e) {
|
|
// The last-resort panic fence: an upstream tsgo panic that crossed a
|
|
// checker call no statement/collection fence wrapped still becomes a
|
|
// clean failed compile (anchored at the entry), never a crashed CLI.
|
|
if (!isCheckerPanic(e)) throw e;
|
|
return fail([
|
|
checkerPanicDiag(e.message.split("\n", 1)[0]!, { file: entryPath, start: 0, end: 0 }),
|
|
]);
|
|
}
|
|
if (lowered.module === null) return fail(lowered.diagnostics);
|
|
|
|
const validation = validateModule(lowered.module);
|
|
timing("ir-validate");
|
|
if (validation.length > 0) {
|
|
return fail(validation.map((v) => iceDiag(v.message, v.loc)));
|
|
}
|
|
if (buildPlatform === "wasi") {
|
|
const entryLoc: SrcLoc = { file: entryPath, start: 0, end: 0 };
|
|
if (opts.sanitize) {
|
|
return fail([targetRefusalDiag("wasm32-wasi", "--sanitize", entryLoc)]);
|
|
}
|
|
if (ffi !== null) {
|
|
return fail([targetRefusalDiag("wasm32-wasi", "native FFI manifests", entryLoc)]);
|
|
}
|
|
fenceSpeculativeWasiFunctions(lowered.module);
|
|
const unavailable = moduleWasiUnavailableSurface(lowered.module);
|
|
if (unavailable !== null) {
|
|
return fail([targetRefusalDiag("wasm32-wasi", unavailable.surface, unavailable.loc)]);
|
|
}
|
|
}
|
|
sourceTexts = fe.sourceTexts();
|
|
if (lowered.contracts) {
|
|
const checked = checkContracts(lowered.module, lowered.contracts, sourceTexts);
|
|
timing("contracts");
|
|
if (checked.diagnostics.length > 0) return fail(checked.diagnostics);
|
|
if (checked.contracts) lowered.module.contracts = checked.contracts;
|
|
}
|
|
} finally {
|
|
fe.dispose();
|
|
frontendDisposed();
|
|
timing("frontend-dispose");
|
|
}
|
|
|
|
return {
|
|
ok: true,
|
|
mod: lowered.module!,
|
|
sourceTexts,
|
|
stats: lowered.stats,
|
|
warnings: [...(lowered.divergences ?? []), ...(lowered.performance ?? [])],
|
|
};
|
|
}
|