mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-04 08:04:37 +08:00
A large program's hot code spans hundreds of 4 KiB pages, more than the instruction TLB holds. tsc-ts has 12 MB of code, and 90% of its main-thread samples at --checkers 1 fall in 274 distinct code pages. `scriptc build --large-pages` links the new runtime unit scr_text_pages.c, whose constructor moves the executable's code onto 2 MiB transparent huge pages before main. It copies the executable segment, from its first to its last 2 MiB boundary, into anonymous memory advised MADV_HUGEPAGE, makes the copy read-only and executable, and moves it over the original range with one mremap, so addresses and instructions stay the same. The move runs only on Linux, only when THP is "always" or "madvise", and only in executables. If a step fails, the code stays file-mapped. Like Node's --use-largepages, it is off by default: without the flag the unit is not linked, and executables are unchanged. The move costs about 3 ms per 10 MiB of code at startup, and all of the moved code stays resident as private memory. The flag is part of the executable cache key and the runtime-pack selection, and is refused for non-executable output, like --detached-exit.
279 lines
11 KiB
JavaScript
279 lines
11 KiB
JavaScript
import { withWorkerRuntimeVariants } from "./worker-matrix.mjs";
|
|
import { withLibraryRuntimeFlavors } from "./library-matrix.mjs";
|
|
|
|
/**
|
|
* Shared release-runtime pack matrix. Platform package scripts provide the
|
|
* target descriptor, then this module fixes feature reachability and the
|
|
* compile/link flags required by every generated runtime object.
|
|
*/
|
|
const any = (...features) => ({ any: features });
|
|
const all = (...features) => ({ all: features });
|
|
|
|
/* Every runtime pack matrix uses these lists, so a new runtime source is
|
|
* one line here. Their order is the link order of the units an executable
|
|
* selects: keep a new unit next to the units it references, and treat any
|
|
* reordering as a change to every executable's layout. */
|
|
export const BASE_RUNTIME_SOURCES = [
|
|
"scr_context.c",
|
|
"scr_shared.c",
|
|
"scr_number.c",
|
|
"scr_bigint.c",
|
|
"scr_string.c",
|
|
"scr_grapheme.c",
|
|
"scr_array.c",
|
|
"scr_bytes.c",
|
|
"scr_bytes_io.c",
|
|
"scr_fs_cp.c",
|
|
"scr_map.c",
|
|
"scr_closure.c",
|
|
"scr_ffi.c",
|
|
"scr_object.c",
|
|
"scr_union.c",
|
|
"scr_exception.c",
|
|
"scr_unwind.c",
|
|
"scr_error.c",
|
|
"scr_console.c",
|
|
"scr_lib.c",
|
|
"scr_hash.c",
|
|
"scr_path.c",
|
|
"scr_os.c",
|
|
"scr_url.c",
|
|
"scr_url_params.c",
|
|
"scr_json.c",
|
|
"scr_node_builtin.c",
|
|
"scr_async.c",
|
|
"scr_crypto_async.c",
|
|
"scr_child.c",
|
|
"scr_cycle.c",
|
|
"scr_publish.c",
|
|
"scr_region.c",
|
|
"scr_arena.c",
|
|
"scr_itable.c",
|
|
"scr_slots.c",
|
|
"scr_alloc.c",
|
|
// crypto.getCipherInfo's table; it links only into programs that call it.
|
|
"scr_cipher_info.c",
|
|
// Math's transcendental functions as V8 computes them, and on glibc the
|
|
// exact libm functions that let a program link without libm.so.
|
|
"scr_ieee754.c",
|
|
"scr_glibc_math.c",
|
|
];
|
|
|
|
export const OPTIONAL_RUNTIME_SOURCES = [
|
|
["scr_copying.c", "copying"],
|
|
["scr_file_handle.c", "fileHandle"],
|
|
["scr_regex.c", "regex"],
|
|
["scr_glob.c", "regex"],
|
|
["scr_idna.c", "regex"],
|
|
["scr_assert.c", any("assert", "regex", "symbol")],
|
|
["scr_inspect.c", "inspect"],
|
|
["scr_console_native.c", "inspect"],
|
|
["scr_dyn_invoke.c", any("dynInvoke", "nativeFetch")],
|
|
// Buffer.prototype methods called on checked-dynamic values.
|
|
["scr_bytes_dyn.c", all("dynBuffer", "dynInvoke")],
|
|
// Builtin functions read as values in JavaScript dependencies.
|
|
["scr_builtin_values.c", "builtinValues"],
|
|
// createRequire's computed require and require.resolve.
|
|
["scr_require.c", "computedRequire"],
|
|
["scr_dc.c", "dc"],
|
|
// The dynamic tier's jsval values (two-tier design I3): linked only into
|
|
// programs whose IR has dynamic-tier operations.
|
|
["scr_js_value.c", "dynTier"],
|
|
["scr_js_object.c", "dynTier"],
|
|
["scr_event_target.c", any("eventTarget", "nativeFetch")],
|
|
["scr_async_dyn.c", any("dynAsync", "dynInvoke", "dc", "fileHandle", "nativeFetch")],
|
|
// The crypto global's object extends scr_async_dyn.c's crypto object.
|
|
["scr_webcrypto_object.c", all("webcrypto", "dynAsync")],
|
|
// The performance timeline emits its buffer warning through scr_async_dyn.c.
|
|
["scr_perf.c", "dynAsync"],
|
|
// perf_hooks event-loop metrics, only for programs that read them.
|
|
["scr_perf_loop.c", "perfLoop"],
|
|
// Module namespace objects as checked-dynamic values.
|
|
["scr_module_namespace.c", "moduleNamespace"],
|
|
// Blob and File box their reads as dyn promises through scr_async_dyn.c.
|
|
["scr_blob.c", "dynAsync"],
|
|
["scr_zlib.c", "zlib"],
|
|
["scr_brotli.c", all("zlib", "brotli")],
|
|
["scr_events.c", "events"],
|
|
["scr_readline.c", "events"],
|
|
["scr_readline_tty.c", all("events", "regex")],
|
|
["scr_events_emitter.c", "emitter"],
|
|
// events.once with options registers through the emitter and event-target units.
|
|
["scr_events_once_signal.c", "eventsOnceSignal"],
|
|
["scr_mime.c", "mime"],
|
|
// `scriptc build --large-pages`: executable code on transparent huge pages (Linux).
|
|
["scr_text_pages.c", "largePages"],
|
|
["scr_util_timing.c", "utilTiming"],
|
|
["scr_dyn_handle.c", any("emitter", "netEffective")],
|
|
["scr_symbol.c", "symbol"],
|
|
["scr_bigint_assert.c", all("assert", "bigint")],
|
|
["scr_qs.c", "qs"],
|
|
// The legacy url unit lowercases and normalizes through the regex unit
|
|
// and warns through the dyn-async one.
|
|
["scr_url_legacy.c", all("regex", "qs", "dynAsync")],
|
|
["scr_util.c", "parseArgs"],
|
|
// Utility objects must only reference runtime families selected with them.
|
|
["scr_util_compare.c", all("parseArgs", "symbol")],
|
|
["scr_util_style.c", all("parseArgs", "inspect", "dynAsync")],
|
|
["scr_util_callable.c", all("parseArgs", "inspect", "dynAsync", "dynInvoke", "regex")],
|
|
// util.promisify over an untyped function creates dyn promises and calls.
|
|
["scr_util_promisify.c", all("utilPromisify", "dynAsync", "dynInvoke")],
|
|
// Untyped EventEmitter and stream constructor values call listeners
|
|
// through the dyn-invoke unit, inspect unhandled 'error' payloads and
|
|
// create EventEmitter's private symbols.
|
|
["scr_dyn_builtin_class.c", all("dynBuiltinClass", "inspect", "dynInvoke", "symbol", "emitter")],
|
|
// Their stream half also references the stream unit.
|
|
[
|
|
"scr_dyn_builtin_stream.c",
|
|
all(
|
|
"dynBuiltinStream",
|
|
"dynBuiltinClass",
|
|
"stream",
|
|
"inspect",
|
|
"dynInvoke",
|
|
"symbol",
|
|
"emitter",
|
|
),
|
|
],
|
|
["scr_stream.c", "stream"],
|
|
// The zlib stream classes reference both the zlib engine and the stream unit.
|
|
["scr_zlib_stream.c", all("zlib", "stream")],
|
|
// The Readable operators await through the dyn-async unit.
|
|
["scr_stream_ops.c", all("stream", "dynAsync")],
|
|
// Checked-dynamic stream slots call JavaScript members through the dyn-invoke unit.
|
|
["scr_stream_dyn.c", "streamDyn"],
|
|
// Own properties JavaScript adds to children.
|
|
["scr_dyn_handle_props.c", "handleProps"],
|
|
["scr_decl_adopt.c", "declAdopt"],
|
|
["scr_type_origin.c", "typeOrigins"],
|
|
// for await over checked-dynamic values.
|
|
["scr_dyn_for_await.c", "dynForAwait"],
|
|
["scr_dyn_for_await_stream.c", all("dynForAwait", "stream", "dynAsync")],
|
|
// fs.ReadStream / fs.WriteStream reference only the stream unit and base units.
|
|
["scr_fs_stream.c", "stream"],
|
|
// FileHandle streams reference both the stream unit and FileHandle.
|
|
["scr_fs_stream_fh.c", all("stream", "fileHandle")],
|
|
["scr_loop_kqueue.c", any("netEffective", "dgram")],
|
|
["scr_loop_epoll.c", any("netEffective", "dgram")],
|
|
["scr_loop_wsapoll.c", any("netEffective", "dgram")],
|
|
["scr_net.c", "netEffective"],
|
|
["scr_http.c", "httpEffective"],
|
|
["scr_http2.c", "http2"],
|
|
["scr_dgram.c", "dgram"],
|
|
// The dns promise forms run over the dgram unit's callback forms.
|
|
["scr_dns_promises.c", all("dgram", "dnsPromises")],
|
|
// Sockets as untyped values run over the dgram unit.
|
|
["scr_dgram_dyn.c", all("dgram", "dgramDyn")],
|
|
// TextEncoder and TextDecoder records boxed as untyped values.
|
|
["scr_text_codec_dyn.c", "textCodecDyn"],
|
|
["scr_watch.c", "watch"],
|
|
["scr_ffi_queue.c", "foreignFfi"],
|
|
["scr_test.c", "nodeTest"],
|
|
["scr_tls_ca.c", "tlsCaEffective"],
|
|
["scr_tls.c", "tlsEffective"],
|
|
["scr_cipher.c", "cipher"],
|
|
["scr_ecdh.c", "ecdh"],
|
|
["scr_dh.c", "dh"],
|
|
["scr_pki.c", "pki"],
|
|
["scr_wasm.c", "wasm"],
|
|
["scr_webcrypto.c", "webcrypto"],
|
|
["scr_webcrypto_pki.c", all("webcrypto", "pki")],
|
|
["scr_ed25519.c", all("pki", "ed25519")],
|
|
["scr_x509.c", "x509"],
|
|
// The TLS socket certificate accessors read scr_tls.c and build with scr_x509.c.
|
|
["scr_tls_x509.c", all("tlsEffective", "x509")],
|
|
// WebCrypto's AES ciphers run over the cipher feature's mbedTLS archive.
|
|
["scr_webcrypto_aes.c", all("cipher", "webcrypto")],
|
|
["scr_fetch.c", "nativeFetch"],
|
|
["scr_stream_from_web.c", all("stream", "nativeFetch")],
|
|
];
|
|
|
|
export function variantsFor(source) {
|
|
const variants = [{ id: "default", when: {}, defines: [] }];
|
|
// Programs with dynamic-tier code link the build that exports the
|
|
// primitives the tier's inline caches use (two-tier design S4); every
|
|
// other program links the ordinary object, byte for byte.
|
|
if (source === "scr_json.c") {
|
|
variants.push({ id: "dyn-tier", when: { dynTier: true }, defines: ["SCR_JS_TIER"] });
|
|
}
|
|
if (source === "scr_bytes.c") {
|
|
variants.push({
|
|
id: "text-decoder-legacy",
|
|
when: { textDecoderLegacy: true },
|
|
defines: ["SCR_TEXT_DECODER_LEGACY"],
|
|
});
|
|
}
|
|
// Programs that assign process.emitWarning link the replacement-aware
|
|
// warning paths; every other program keeps the default object.
|
|
if (source === "scr_async_dyn.c") {
|
|
variants.push({
|
|
id: "warning-replace",
|
|
when: { warningReplace: true },
|
|
defines: ["SCR_WARNING_REPLACE"],
|
|
});
|
|
}
|
|
// stdin setEncoding, string data listeners and stdio 'resize' events.
|
|
if (source === "scr_events.c") {
|
|
variants.push({
|
|
id: "stdio-extended",
|
|
when: { stdioExtended: true },
|
|
defines: ["SCR_STDIO_EXTENDED"],
|
|
});
|
|
}
|
|
if (source === "scr_cycle.c") {
|
|
// The young-in nursery (SCRIPTC_YOUNG_NURSERY=1): the collector with
|
|
// nursery collections; programs built without it link the default.
|
|
variants.push({
|
|
id: "young-nursery",
|
|
when: { youngNursery: true },
|
|
defines: ["SCR_YIN"],
|
|
});
|
|
}
|
|
return variants;
|
|
}
|
|
|
|
export function createRuntimePackMatrix({
|
|
target,
|
|
compileFlags,
|
|
systemLibraries,
|
|
extraRuntimeSources = [],
|
|
omitRuntimeSources = [],
|
|
omitOptionalSources = [],
|
|
omitArchives = [],
|
|
libraryOnly = false,
|
|
}) {
|
|
const vendorArchives = [
|
|
{ id: "quickjs", predicate: false },
|
|
{ id: "libregexp", predicate: "regex" },
|
|
{ id: "zlib", predicate: "zlibEffective" },
|
|
{ id: "brotli", predicate: all("zlib", "brotli") },
|
|
{ id: "mbedtls", predicate: any("tlsEffective", "cipher", "ecdh", "dh", "pki") },
|
|
];
|
|
const matrix = withLibraryRuntimeFlavors(
|
|
withWorkerRuntimeVariants({
|
|
schema: "scriptc.runtime-pack-matrix.v1",
|
|
target,
|
|
flavors: { release: { optimization: "-O2" }, dev: { optimization: "-O0" } },
|
|
executable_section_elimination: { compile_flags: compileFlags },
|
|
runtime_units: [
|
|
...BASE_RUNTIME_SOURCES.filter((source) => !omitRuntimeSources.includes(source)).map(
|
|
(source) => ({ source, predicate: true }),
|
|
),
|
|
...extraRuntimeSources.map((source) => ({ source, predicate: true })),
|
|
...OPTIONAL_RUNTIME_SOURCES.filter(([source]) => !omitOptionalSources.includes(source)).map(
|
|
([source, predicate]) => ({ source, predicate }),
|
|
),
|
|
].map((unit) => ({ ...unit, variants: variantsFor(unit.source) })),
|
|
archives: vendorArchives.filter(({ id }) => !omitArchives.includes(id)),
|
|
system_libraries: systemLibraries,
|
|
}),
|
|
);
|
|
if (libraryOnly) {
|
|
matrix.flavors = Object.fromEntries(
|
|
Object.entries(matrix.flavors).filter(([name]) => name.startsWith("library-")),
|
|
);
|
|
matrix.archives = matrix.archives.filter(({ id }) => id === "libregexp" || id === "zlib");
|
|
}
|
|
return matrix;
|
|
}
|