Files
scriptc/scripts/node-island-host.mjs
T
Malte UblandClaude Opus 5.5 9a0a3f1562 Load Node.js in-process on first use: libnode bridge and runtime loader
Scope-B milestone NI-1, runtime side. Programs whose IR has a
`nodeIsland.*` operation link scr_node_island.c and the SCR_NODE_ISLANDS
build of scr_async.c; every other program links neither, byte for byte.

- scr_node_island.c dlopens the bridge of a libnode pack on the first call
  (RTLD_NOW | RTLD_GLOBAL), checks its bridge ABI and that it links exactly
  the Node version recorded at build time, starts one Node environment and
  runs the embedded bootstrap (scr_node_island_host.cjs). A run that never
  calls into Node never maps libnode. The pack directory is SCRIPTC_LIBNODE
  at run time, else the recorded path; a `$ORIGIN/` path resolves against
  the executable's directory (the beside packaging, and the hook for
  shipping Node with the program).
- scr_node_bridge.h is the C ABI between the runtime and the bridge; the
  runtime includes no Node or V8 header. The bridge
  (packages/libnode-common/bridge/scr_node_bridge.cc) initializes Node once
  with no default signal handling, no debug-signal handler or inspector,
  no stdio reinitialization and one platform worker, creates the
  environment from the built-in snapshot, routes Node's process exit to
  the runtime's exit sequence, promotes libnode to the global scope for
  native addons, and runs calls in a CallbackScope with a TryCatch.
  build-pack.mjs assembles a pack (libnode, the bridge, manifest.json) from
  a Node --shared build and the matching headers.
- The SCR_NODE_ISLANDS build of scr_async.c adds a loop slot through which
  the runtime's loop drives Node's until NI-3 hosts the runtime's queues on
  Node's libuv: it keeps the program alive while Node has ref'd work,
  sleeps on Node's backend fd up to its next timer, and runs one
  non-blocking turn per loop turn.
- The `nodeIsland` runtime feature (rule, link features, unit table, pack
  matrix row and scr_async.c variant), the nodeIsland.call/callSync
  libCalls, the install call in main, and the build-time record of the
  pack (node-islands/libnode.ts: --libnode=<pack>|beside or
  SCRIPTC_LIBNODE, SC8005 when none is usable, Node >=24.13.1).
- The zero-cost gate and the bench nm test require that no good-code
  executable defines a Node island symbol.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:54:47 -07:00

54 lines
2.0 KiB
JavaScript

#!/usr/bin/env node
/* Embeds the Node island host (packages/runtime/src/scr_node_island_host.cjs)
* into scr_node_island_host.h as a C string, which scr_node_island.c passes
* to `node -e` when it starts the helper.
*
* node scripts/node-island-host.mjs rewrite the header
* node scripts/node-island-host.mjs --check fail when it is stale */
import { readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
const srcDir = fileURLToPath(new URL("../packages/runtime/src/", import.meta.url));
const SOURCE = `${srcDir}scr_node_island_host.cjs`;
const HEADER = `${srcDir}scr_node_island_host.h`;
/** One C string literal per source line, every byte outside printable
* ASCII as a three-digit octal escape. */
function cLiteral(line) {
let out = "";
for (const byte of Buffer.from(line, "utf8")) {
if (byte === 0x22 || byte === 0x5c) out += `\\${String.fromCharCode(byte)}`;
else if (byte >= 0x20 && byte < 0x7f && byte !== 0x3f) out += String.fromCharCode(byte);
else out += `\\${byte.toString(8).padStart(3, "0")}`;
}
return `"${out}"`;
}
export function renderHeader(source) {
const lines = source.split("\n");
if (lines.at(-1) === "") lines.pop();
const body = lines.map((line) => ` ${cLiteral(`${line}\n`)}`).join("\n");
return `/* Generated by scripts/node-island-host.mjs from scr_node_island_host.cjs.
* Do not edit: change the .cjs file and rerun the script. */
#ifndef SCR_NODE_ISLAND_HOST_H
#define SCR_NODE_ISLAND_HOST_H
static const char scr_node_island_host_js[] =
${body};
#endif /* SCR_NODE_ISLAND_HOST_H */
`;
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const expected = renderHeader(readFileSync(SOURCE, "utf8"));
if (process.argv.includes("--check")) {
if (readFileSync(HEADER, "utf8") !== expected) {
console.error(`${HEADER} is stale: run node scripts/node-island-host.mjs`);
process.exit(1);
}
} else {
writeFileSync(HEADER, expected);
}
}