mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-03 07:54:40 +08:00
A 'message' listener with a typed parameter received each message as a checked-dynamic value: the port decoded the message into dyn objects, the listener's thunk matched the value against each union arm, read every field again to build the record, and the dyn objects were then released. For bench/channel-messages that was about a third of the delivery time. - A closure registered with on, once, addListener, prependListener or prependOnceListener for 'message' gets a typed message entry when its parameter is a number, boolean, string, a record of those without union fields, or a boxed union of those arms with null or undefined. The entry builds the parameter from the message's nodes and calls the closure. - The entry handles a message only when it has the parameter's exact layout: the union arm the checked conversion picks, and a record's declared keys in declaration order with no others. Any other message runs through the thunk on the decoded value, as before, so undeclared keys, other orders and mismatches behave as they did. - Ports, workers and parentPort deliver a message through the entries when no value in it is reached twice and it holds no ports or native handles. Listeners without an entry share one decoded value, made when the first of them runs. - The message readers are runtime entry points, so the runtime ABI is now 27. Corpus 7130 and 7131 check typed listeners on MessageChannel ports, a worker and parentPort against Node: exact layouts, extra keys, shared values, nested records, untyped listeners beside typed ones, once listeners and a transferred port.
388 lines
14 KiB
JavaScript
388 lines
14 KiB
JavaScript
#!/usr/bin/env node
|
|
import { execFile } from "node:child_process";
|
|
import { createHash } from "node:crypto";
|
|
import { availableParallelism } from "node:os";
|
|
import { parallelMap } from "../../runtime-pack-common/scripts/parallel-map.mjs";
|
|
import { copyFile, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
import { dirname, join, relative, sep } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { promisify } from "node:util";
|
|
import { RUNTIME_PACK_MATRIX } from "../runtime-pack-matrix.mjs";
|
|
import {
|
|
buildImportBitcode,
|
|
buildRuntimeUnit,
|
|
resolveRuntimeUnitHelper,
|
|
} from "../../runtime-pack-common/scripts/runtime-unit.mjs";
|
|
import { createDeterministicArchive } from "./archive.mjs";
|
|
import { installRuntimePack, withBuildLock } from "./build-state.mjs";
|
|
|
|
const run = promisify(execFile);
|
|
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
const repoRoot = fileURLToPath(new URL("../../..", import.meta.url));
|
|
const runtimeRoot = join(repoRoot, "packages/runtime");
|
|
const runtimeSrc = join(runtimeRoot, "src");
|
|
const vendorRoot = join(runtimeRoot, "vendor");
|
|
const outputRoot = join(packageRoot, "artifacts");
|
|
const manifestPath = join(packageRoot, "runtime-pack.json");
|
|
|
|
if (
|
|
process.env.SCRIPTC_BUILD_CROSS !== "1" &&
|
|
(process.platform !== "darwin" || process.arch !== "arm64")
|
|
) {
|
|
process.stdout.write("@scriptc/runtime-darwin-arm64: skipped on this host\n");
|
|
process.exit(0);
|
|
}
|
|
|
|
async function build() {
|
|
const buildRoot = join(
|
|
packageRoot,
|
|
`.runtime-pack-build-${process.pid}-${Math.random().toString(36).slice(2)}`,
|
|
);
|
|
const stagedOutputRoot = join(buildRoot, "artifacts");
|
|
const stagedManifestPath = join(buildRoot, "runtime-pack.json");
|
|
const artifactPath = (path) =>
|
|
["artifacts", ...relative(stagedOutputRoot, path).split(sep)].join("/");
|
|
const sourcePathFlags = [
|
|
`-ffile-prefix-map=${buildRoot}=${packageRoot}`,
|
|
`-ffile-prefix-map=${repoRoot}=.`,
|
|
];
|
|
|
|
const packageManifest = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
|
|
const compiler = process.env.CC ?? "clang";
|
|
const archiver = process.env.AR ?? "ar";
|
|
const compilerVersion = (await run(compiler, ["--version"])).stdout.split("\n", 1)[0].trim();
|
|
const commonFlags = [
|
|
"-target",
|
|
RUNTIME_PACK_MATRIX.target.llvm_triple,
|
|
"-std=c11",
|
|
"-pthread",
|
|
"-fno-math-errno",
|
|
"-fno-strict-aliasing",
|
|
...RUNTIME_PACK_MATRIX.executable_section_elimination.compile_flags,
|
|
"-Wno-deprecated-declarations",
|
|
"-I",
|
|
runtimeSrc,
|
|
];
|
|
// Vendored -Os archives start each function on a cache-line boundary so
|
|
// their hot loops keep their placement when unrelated code changes size
|
|
// (see the shared pack builder in runtime-pack-common).
|
|
// libunicode's small table helpers are exempt (see runtime-pack-common).
|
|
const codeAlignment = ["-falign-functions=64"];
|
|
const unalignedVendorSources = new Set(["libunicode.c"]);
|
|
const quickjs = join(vendorRoot, "quickjs-ng");
|
|
const zlib = join(vendorRoot, "zlib");
|
|
const mbedtls = join(vendorRoot, "mbedtls");
|
|
const QJS_SOURCES = ["dtoa.c", "libregexp.c", "libunicode.c", "quickjs.c"];
|
|
const LRE_SOURCES = ["libregexp.c", "libunicode.c"];
|
|
const ZLIB_SOURCES = [
|
|
"adler32.c",
|
|
"compress.c",
|
|
"crc32.c",
|
|
"deflate.c",
|
|
"infback.c",
|
|
"inffast.c",
|
|
"inflate.c",
|
|
"inftrees.c",
|
|
"trees.c",
|
|
"uncompr.c",
|
|
"zutil.c",
|
|
];
|
|
|
|
// Speed-flavor executable units are emitted through the host LLVM helper
|
|
// so their object and import bitcode come from one promoted module. The
|
|
// helper also reduces release units' bitcode to what programs can import.
|
|
const unitHelper = await resolveRuntimeUnitHelper(
|
|
repoRoot,
|
|
RUNTIME_PACK_MATRIX.target.llvm_triple,
|
|
);
|
|
|
|
async function sha256(path) {
|
|
return createHash("sha256")
|
|
.update(await readFile(path))
|
|
.digest("hex");
|
|
}
|
|
|
|
async function compile(source, output, flags) {
|
|
await mkdir(dirname(output), { recursive: true });
|
|
await run(compiler, [...sourcePathFlags, ...flags, "-c", source, "-o", output]);
|
|
}
|
|
|
|
async function parallel(items, task) {
|
|
const width = Math.max(1, Math.min(8, availableParallelism()));
|
|
return parallelMap(items, width, task);
|
|
}
|
|
|
|
async function archive(id, sources, sourceRoot, flags) {
|
|
const root = join(stagedOutputRoot, "vendor", id);
|
|
const objectRoot = join(root, "objects");
|
|
await parallel(sources, async (source) => {
|
|
await compile(
|
|
join(sourceRoot, source),
|
|
join(objectRoot, source.replace(/\.c$/, ".o")),
|
|
unalignedVendorSources.has(source)
|
|
? flags.filter((flag) => !codeAlignment.includes(flag))
|
|
: flags,
|
|
);
|
|
});
|
|
const output = join(root, `libscriptc-${id}.a`);
|
|
const objects = sources.map((source) => join(objectRoot, source.replace(/\.c$/, ".o")));
|
|
await createDeterministicArchive(archiver, output, objects);
|
|
await rm(objectRoot, { recursive: true, force: true });
|
|
return {
|
|
id,
|
|
path: artifactPath(output),
|
|
sha256: await sha256(output),
|
|
size: (await stat(output)).size,
|
|
};
|
|
}
|
|
|
|
await rm(buildRoot, { recursive: true, force: true });
|
|
await mkdir(stagedOutputRoot, { recursive: true });
|
|
|
|
try {
|
|
const flavors = {};
|
|
// The executable `speed` flavor (--optimization=speed) exists only when
|
|
// it differs from release: its static units are emitted through the host
|
|
// LLVM helper with import bitcode.
|
|
const flavorPlan = [
|
|
...Object.entries(RUNTIME_PACK_MATRIX.flavors),
|
|
...("release" in RUNTIME_PACK_MATRIX.flavors && unitHelper !== null
|
|
? [["speed", RUNTIME_PACK_MATRIX.flavors.release]]
|
|
: []),
|
|
];
|
|
for (const [flavor, flavorSpec] of flavorPlan) {
|
|
const units = await parallel(
|
|
flavorSpec.runtime_units ?? RUNTIME_PACK_MATRIX.runtime_units,
|
|
async (unit) => {
|
|
const variants = [];
|
|
for (const baseVariant of unit.variants) {
|
|
const variant = {
|
|
...baseVariant,
|
|
defines: [...(flavorSpec.defines ?? []), ...baseVariant.defines],
|
|
};
|
|
const variantName = variant.id === "default" ? "default" : variant.id;
|
|
const output = join(
|
|
stagedOutputRoot,
|
|
flavor,
|
|
"runtime",
|
|
variantName,
|
|
unit.source.replace(/\.c$/, ".o"),
|
|
);
|
|
const includeFlags = [
|
|
...(unit.source === "scr_regex.c" ? ["-I", quickjs] : []),
|
|
...(unit.source === "scr_tls.c" ||
|
|
unit.source === "scr_cipher.c" ||
|
|
unit.source === "scr_ecdh.c" ||
|
|
unit.source === "scr_dh.c" ||
|
|
unit.source === "scr_pki.c" ||
|
|
unit.source === "scr_webcrypto_aes.c"
|
|
? ["-I", join(mbedtls, "include")]
|
|
: []),
|
|
...(unit.source === "scr_zlib.c" || unit.source === "scr_fetch.c"
|
|
? ["-I", zlib]
|
|
: []),
|
|
];
|
|
const unitFlags = [
|
|
...commonFlags,
|
|
flavorSpec.optimization,
|
|
...variant.defines.map((define) => `-D${define}`),
|
|
...includeFlags,
|
|
];
|
|
let bitcode;
|
|
const bitcodeOutput = output.replace(/\.o$/, ".bc");
|
|
if (flavor === "speed") await mkdir(dirname(output), { recursive: true });
|
|
if (
|
|
flavor === "speed" &&
|
|
(await buildRuntimeUnit({
|
|
helper: unitHelper,
|
|
compileBitcode: (optimized) =>
|
|
run(compiler, [
|
|
...sourcePathFlags,
|
|
...unitFlags,
|
|
"-emit-llvm",
|
|
"-c",
|
|
join(runtimeSrc, unit.source),
|
|
"-o",
|
|
optimized,
|
|
]),
|
|
object: output,
|
|
bitcode: bitcodeOutput,
|
|
triple: RUNTIME_PACK_MATRIX.target.llvm_triple,
|
|
tag: unit.source.replace(/\.c$/, ""),
|
|
sections:
|
|
RUNTIME_PACK_MATRIX.executable_section_elimination.compile_flags.includes(
|
|
"-ffunction-sections",
|
|
),
|
|
}))
|
|
) {
|
|
bitcode = {
|
|
path: artifactPath(bitcodeOutput),
|
|
sha256: await sha256(bitcodeOutput),
|
|
size: (await stat(bitcodeOutput)).size,
|
|
};
|
|
} else {
|
|
await compile(join(runtimeSrc, unit.source), output, unitFlags);
|
|
if (
|
|
flavor === "release" &&
|
|
unitHelper !== null &&
|
|
(await buildImportBitcode({
|
|
helper: unitHelper,
|
|
compileBitcode: (optimized) =>
|
|
run(compiler, [
|
|
...sourcePathFlags,
|
|
...unitFlags,
|
|
"-emit-llvm",
|
|
"-c",
|
|
join(runtimeSrc, unit.source),
|
|
"-o",
|
|
optimized,
|
|
]),
|
|
bitcode: bitcodeOutput,
|
|
tag: unit.source.replace(/\.c$/, ""),
|
|
}))
|
|
) {
|
|
bitcode = {
|
|
path: artifactPath(bitcodeOutput),
|
|
sha256: await sha256(bitcodeOutput),
|
|
size: (await stat(bitcodeOutput)).size,
|
|
};
|
|
}
|
|
}
|
|
const built = {
|
|
id: variant.id,
|
|
when: variant.when,
|
|
defines: variant.defines,
|
|
path: artifactPath(output),
|
|
sha256: await sha256(output),
|
|
size: (await stat(output)).size,
|
|
...(bitcode === undefined ? {} : { bitcode }),
|
|
};
|
|
variants.push(built);
|
|
}
|
|
return { source: unit.source, predicate: unit.predicate, variants };
|
|
},
|
|
);
|
|
flavors[flavor] = { optimization: flavorSpec.optimization, runtime_units: units };
|
|
}
|
|
|
|
const mbedtlsSources = (await readdir(join(mbedtls, "library")))
|
|
.filter((name) => !name.startsWith(".") && name.endsWith(".c"))
|
|
.sort();
|
|
const archives = [
|
|
await archive("quickjs", QJS_SOURCES, quickjs, [
|
|
"-target",
|
|
RUNTIME_PACK_MATRIX.target.llvm_triple,
|
|
"-std=gnu11",
|
|
"-fvisibility=hidden",
|
|
"-funsigned-char",
|
|
"-DQUICKJS_NG_BUILD",
|
|
"-D_GNU_SOURCE",
|
|
"-DNDEBUG",
|
|
"-Os",
|
|
...codeAlignment,
|
|
"-I",
|
|
quickjs,
|
|
]),
|
|
await archive("libregexp", LRE_SOURCES, quickjs, [
|
|
"-target",
|
|
RUNTIME_PACK_MATRIX.target.llvm_triple,
|
|
"-std=c11",
|
|
"-Os",
|
|
...codeAlignment,
|
|
"-I",
|
|
quickjs,
|
|
]),
|
|
await archive("zlib", ZLIB_SOURCES, zlib, [
|
|
"-target",
|
|
RUNTIME_PACK_MATRIX.target.llvm_triple,
|
|
"-std=c11",
|
|
"-Os",
|
|
...codeAlignment,
|
|
"-I",
|
|
zlib,
|
|
]),
|
|
await archive("mbedtls", mbedtlsSources, join(mbedtls, "library"), [
|
|
"-target",
|
|
RUNTIME_PACK_MATRIX.target.llvm_triple,
|
|
"-std=c11",
|
|
"-Os",
|
|
...codeAlignment,
|
|
"-I",
|
|
join(mbedtls, "include"),
|
|
"-I",
|
|
join(mbedtls, "library"),
|
|
]),
|
|
];
|
|
const archiveSpecs = new Map(RUNTIME_PACK_MATRIX.archives.map((entry) => [entry.id, entry]));
|
|
const licensed = [
|
|
[join(runtimeRoot, "LICENSE"), "artifacts/licenses/scriptc-runtime.txt", "Apache-2.0"],
|
|
[join(quickjs, "LICENSE"), "artifacts/licenses/quickjs-ng.txt", "MIT"],
|
|
[join(vendorRoot, "ryu", "LICENSE-Boost"), "artifacts/licenses/ryu.txt", "BSL-1.0"],
|
|
[
|
|
join(vendorRoot, "v8-number-radix", "LICENSE"),
|
|
"artifacts/licenses/v8-number-radix.txt",
|
|
"BSD-3-Clause",
|
|
],
|
|
[join(vendorRoot, "unicode", "LICENSE"), "artifacts/licenses/unicode.txt", "Unicode-3.0"],
|
|
[join(zlib, "LICENSE"), "artifacts/licenses/zlib.txt", "Zlib"],
|
|
[join(mbedtls, "LICENSE"), "artifacts/licenses/mbedtls.txt", "Apache-2.0"],
|
|
];
|
|
await Promise.all(
|
|
licensed.map(async ([source, destination]) => {
|
|
const output = join(buildRoot, destination);
|
|
await mkdir(dirname(output), { recursive: true });
|
|
await copyFile(source, output);
|
|
}),
|
|
);
|
|
const manifest = {
|
|
schema: "scriptc.runtime-pack.v1",
|
|
format: 1,
|
|
package: packageManifest.name,
|
|
version: packageManifest.version,
|
|
target: RUNTIME_PACK_MATRIX.target,
|
|
runtime_abi: { version: 27, marker: "scr_runtime_abi_v27" },
|
|
compiler: {
|
|
command: compiler,
|
|
identity: compilerVersion,
|
|
target: RUNTIME_PACK_MATRIX.target.llvm_triple,
|
|
...(unitHelper === null ? {} : { speed_codegen: unitHelper.identity }),
|
|
},
|
|
macros: {
|
|
executable: [
|
|
"SCR_TEXT_DECODER_LEGACY",
|
|
"SCR_WARNING_REPLACE",
|
|
"SCR_STDIO_EXTENDED",
|
|
"SCR_YIN",
|
|
],
|
|
excluded: ["SCR_RC_AUDIT", "SCR_ASAN_FIBERS"],
|
|
sanitizer: "external-toolchain-required",
|
|
},
|
|
flavors,
|
|
archives: archives.map((entry) => ({
|
|
...entry,
|
|
predicate: archiveSpecs.get(entry.id).predicate,
|
|
})),
|
|
system_libraries: RUNTIME_PACK_MATRIX.system_libraries,
|
|
licenses: licensed.map(([, path, license]) => ({ path, license })),
|
|
};
|
|
await writeFile(stagedManifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
const backupSuffix = `${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
await installRuntimePack({
|
|
outputRoot,
|
|
manifestPath,
|
|
stagedOutputRoot,
|
|
stagedManifestPath,
|
|
backupRoot: join(packageRoot, `.runtime-pack-artifacts-backup-${backupSuffix}`),
|
|
backupManifestPath: join(packageRoot, `.runtime-pack-manifest-backup-${backupSuffix}`),
|
|
});
|
|
process.stdout.write(
|
|
`built ${packageManifest.name}@${packageManifest.version}: ` +
|
|
`${Object.keys(flavors).length} flavors, ${archives.length} vendor archives\n`,
|
|
);
|
|
} finally {
|
|
await rm(buildRoot, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
await withBuildLock(join(packageRoot, ".runtime-pack-build.lock"), build);
|