Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/src/generated/node-v24-compatibility-meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"schemaVersion": 3,
"nodeVersion": "24.15.0",
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
"artifactVersion": "ae934a592466ef1e67c1",
"artifactVersion": "cb524f2eb043e40eb9f4",
"rowCount": 3662
}
10 changes: 5 additions & 5 deletions docs/src/generated/node-v24-compatibility.json
Original file line number Diff line number Diff line change
Expand Up @@ -746,9 +746,9 @@
"apiEntries": 24,
"static": {
"supported": 0,
"partial": 10,
"partial": 11,
"refused": 0,
"not-implemented": 14,
"not-implemented": 13,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
Expand Down Expand Up @@ -61038,9 +61038,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
},
"dynamic": {
"status": "partial",
Expand Down
18 changes: 10 additions & 8 deletions internal/compatibility/generated/node-v24-backlog.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"tiers": {
"static": {
"replace-refusal": 42,
"verify-gap": 2418,
"audit-partial": 746,
"verify-gap": 2417,
"audit-partial": 747,
"classify": 2,
"implement": 80
},
Expand Down Expand Up @@ -66294,12 +66294,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.availableParallelism",
"tests": [
"tests/corpus/4598-os-available-parallelism.ts"
]
},
"dynamic": {
"status": "partial",
Expand Down
14 changes: 9 additions & 5 deletions internal/compatibility/generated/node-v24-internal.json
Original file line number Diff line number Diff line change
Expand Up @@ -819,9 +819,9 @@
"apiEntries": 24,
"static": {
"supported": 0,
"partial": 10,
"partial": 11,
"refused": 0,
"not-implemented": 14,
"not-implemented": 13,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
Expand Down Expand Up @@ -83033,7 +83033,8 @@
"evidence": "derived:descendants",
"tests": [
"tests/corpus/1480-os-network-interfaces.ts",
"tests/corpus/2917-builtin-callable-widths.ts"
"tests/corpus/2917-builtin-callable-widths.ts",
"tests/corpus/4598-os-available-parallelism.ts"
]
},
"dynamic": {
Expand Down Expand Up @@ -83279,8 +83280,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.availableParallelism",
"tests": [
"tests/corpus/4598-os-available-parallelism.ts"
]
},
"dynamic": {
"status": "partial",
Expand Down
1 change: 1 addition & 0 deletions internal/compatibility/static-support.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"node-builtin.module": ["tests/corpus/1598-cjs-builtin-require/main.js"],
"node-builtin.net": ["tests/corpus/2500-net-autosel-timeout.ts", "tests/corpus/2596-net-arg-ladders.cjs"],
"node-builtin.os": ["tests/corpus/1480-os-network-interfaces.ts", "tests/corpus/2917-builtin-callable-widths.ts"],
"node-builtin.os.availableParallelism": ["tests/corpus/4598-os-available-parallelism.ts"],
"node-builtin.path": ["tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", "tests/corpus/2916-path-function-values-cjs.ts", "tests/corpus/2917-builtin-callable-widths.ts", "tests/corpus/2918-builtin-callable-widths-cjs.ts"],
"node-builtin.perf_hooks": ["tests/corpus/957-builtins-namespace.ts"],
"node-builtin.process": ["tests/corpus/990-process-basics.ts", "tests/corpus/2314-process-introspection.ts"],
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/ambient/scriptc-node-fallback.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1688,6 +1688,7 @@ declare module "os" {
export function type(): string;
/* Total system memory in bytes (sysctl hw.memsize / sysconf). */
export function totalmem(): number;
export function availableParallelism(): number;
/* The passwd-entry snapshot (uv_os_get_passwd): shell is `string |
* null` to match @types/node (POSIX always answers the string arm);
* homedir is pw_dir — NOT os.homedir()'s $HOME-first cascade. The type
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/backend/llvm/lib-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"os.homedir": "scr_os_homedir",
"os.type": "scr_os_type",
"os.totalmem": "scr_os_totalmem",
"os.availableParallelism": "scr_os_available_parallelism",
"os.release": "scr_os_release",
"os.userName": "scr_os_user_name",
"os.userShell": "scr_os_user_shell",
Expand Down
8 changes: 8 additions & 0 deletions packages/compiler/src/frontend/lowering/surfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,14 @@ export const BUILTIN_MODULE_FNS: Record<
type: { fn: "os.type", params: [], result: STRING, valueParams: [] },
// Total physical memory in bytes — same predating-pair story.
totalmem: { fn: "os.totalmem", params: [], result: F64, valueParams: [] },
// uv_available_parallelism: the CPUs this process may use (affinity,
// then cgroup quota on Linux), at least 1.
availableParallelism: {
fn: "os.availableParallelism",
params: [],
result: F64,
valueParams: [],
},
// Entirely special-cased (lowerOsNetworkInterfacesCall): the result is
// the call site's mapped Dict<NetworkInterfaceInfo[]> shape, verified
// structurally there — this entry only routes the dispatch.
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler/src/ir/builtin-signatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
"os.type": { argTypes: [], result: STRING },
/** os.totalmem(): total physical memory in bytes. Never throws. */
"os.totalmem": { argTypes: [], result: F64 },
/** os.availableParallelism(): CPUs the process may use (libuv's answer), >= 1. Never throws. */
"os.availableParallelism": { argTypes: [], result: F64 },
/** umask(2): arg < 0 reads without setting (umask has no read-only form
* — set 0, restore); otherwise sets and answers the PREVIOUS mask.
* Never throws. */
Expand Down
6 changes: 6 additions & 0 deletions packages/compiler/surface-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,12 @@
"status": "static",
"note": "constant value read"
},
{
"id": "node-builtin.os.availableParallelism",
"kind": "node-builtin",
"name": "os.availableParallelism",
"status": "static"
},
{
"id": "node-builtin.os.homedir",
"kind": "node-builtin",
Expand Down
104 changes: 104 additions & 0 deletions packages/runtime/src/scr_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ static ScrStr *scr_fs_win_utf8(const WCHAR *text, size_t length) {
#elif defined(__linux__)
#include <netpacket/packet.h>
#endif
#if defined(__linux__)
#include <sched.h> /* sched_getaffinity, CPU_COUNT (os.availableParallelism) */
#elif defined(__APPLE__)
#include <sys/sysctl.h> /* sysctlbyname (os.availableParallelism) */
#endif

#define scr_sys_mkdir(p, m) mkdir((p), (m))

Expand Down Expand Up @@ -1376,6 +1381,13 @@ ScrStr *scr_os_type(void) {
return scr_str_new("Windows_NT", 10);
}

double scr_os_available_parallelism(void) {
/* uv_available_parallelism on Windows: GetSystemInfo's processor count. */
SYSTEM_INFO info;
GetSystemInfo(&info);
return info.dwNumberOfProcessors < 1 ? 1 : (double)info.dwNumberOfProcessors;
}

double scr_os_totalmem(void) {
MEMORYSTATUSEX ms;
memset(&ms, 0, sizeof ms);
Expand Down Expand Up @@ -1413,6 +1425,8 @@ ScrStr *scr_os_user_homedir(void) { return scr_os_homedir(); }
ScrStr *scr_os_release(void) { return scr_str_new("", 0); }
ScrStr *scr_os_type(void) { return scr_str_new("WASI", 4); }
double scr_os_totalmem(void) { return 0; }
/* One thread of execution, as the dynamic island's os answers. */
double scr_os_available_parallelism(void) { return 1; }
/* The guest temp namespace is stable across hosts. `scriptc run` preopens
* the host's /tmp at this path; other WASI hosts can provide the same
* capability without leaking a host-specific TMPDIR into the module. */
Expand Down Expand Up @@ -1486,6 +1500,96 @@ ScrStr *scr_os_type(void) {
return scr_str_new(u.sysname, strlen(u.sysname));
}

#ifdef __linux__
/* Reads at most cap - 1 bytes of path into buf (NUL-terminated); false when
* the file cannot be read. */
static bool scr_os_slurp(const char *path, char *buf, size_t cap) {
int fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd < 0) return false;
size_t len = 0;
while (len + 1 < cap) {
ssize_t n = read(fd, buf + len, cap - 1 - len);
if (n < 0 && errno == EINTR) continue;
if (n <= 0) break;
len += (size_t)n;
}
close(fd);
buf[len] = '\0';
return true;
}

/* libuv's uv__get_constrained_cpu (1.51): the CPU quota of the process's
* cgroup in whole CPUs. For cgroup v2 it walks cpu.max from the process's
* cgroup up to the root; like libuv, the last level with a numeric limit
* (the outermost) decides. Returns false when there is no answer. */
static bool scr_os_cgroup_cpu_quota(long long *quota) {
char cgroup[1024];
if (!scr_os_slurp("/proc/self/cgroup", cgroup, sizeof cgroup)) return false;
if (strncmp(cgroup, "0::/", 4) == 0) {
static const char mount[] = "/sys/fs/cgroup";
const char *trimmed = cgroup + 4;
int size = (int)strcspn(trimmed, "\n");
char path[256], file[256], buf[1024], value[16];
snprintf(path, sizeof path, "%s/%.*s/cgroup.controllers", mount, size, trimmed);
if (!scr_os_slurp(path, buf, sizeof buf)) return false;
snprintf(path, sizeof path, "%s/%.*s", mount, size, trimmed);
while (strncmp(path, mount, sizeof mount - 1) == 0) {
long long limit, period;
snprintf(file, sizeof file, "%s/cpu.max", path);
if (scr_os_slurp(file, value, sizeof value) && strncmp(value, "max", 3) != 0 &&
sscanf(value, "%lld %lld", &limit, &period) == 2 && period != 0)
*quota = limit / period;
Comment on lines +1540 to +1541

@vercel vercel Bot Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cgroup v2 CPU quota rounding down to 0 (sub-1-CPU container limit) is dropped instead of clamped to 1, so os.availableParallelism() diverges from Node/libuv in exactly the containerized case the feature targets.

Fix on Vercel

char *slash = strrchr(path, '/');
if (slash == NULL || strcmp(path, mount) == 0) break;
*slash = '\0';
}
return true;
}
char *cpu = strstr(cgroup, ":cpu,");
if (cpu == NULL) return false;
cpu += sizeof ":cpu," - 1;
int size = (int)strcspn(cpu, "\n");
char path[256], buf[64];
long long per_period, period;
snprintf(path, sizeof path, "/sys/fs/cgroup/%.*s/cpu.cfs_quota_us", size, cpu);
if (!scr_os_slurp(path, buf, sizeof buf) || sscanf(buf, "%lld", &per_period) != 1) return false;
snprintf(path, sizeof path, "/sys/fs/cgroup/%.*s/cpu.cfs_period_us", size, cpu);
if (!scr_os_slurp(path, buf, sizeof buf) || sscanf(buf, "%lld", &period) != 1 || period == 0)
return false;
*quota = per_period / period;
return true;
}
#endif

double scr_os_available_parallelism(void) {
/* uv_available_parallelism (libuv 1.51, Node's os.availableParallelism):
* the CPUs this process may run on (Linux: its affinity mask, then any
* smaller cgroup CPU quota; Darwin: hw.activecpu, hw.logicalcpu, hw.ncpu),
* else the online processors; at least 1. */
long rc = -1;
#if defined(__linux__)
cpu_set_t set;
memset(&set, 0, sizeof set);
if (sched_getaffinity(0, sizeof set, &set) == 0) rc = CPU_COUNT(&set);
#elif defined(__APPLE__)
static const char *const mib[] = {"hw.activecpu", "hw.logicalcpu", "hw.ncpu"};
for (size_t i = 0; i < sizeof mib / sizeof mib[0]; i++) {
int nprocs = 0;
size_t len = sizeof nprocs;
if (sysctlbyname(mib[i], &nprocs, &len, NULL, 0) == 0 && len == sizeof nprocs && nprocs > 0) {
rc = nprocs;
break;
}
}
#endif
if (rc < 0) rc = sysconf(_SC_NPROCESSORS_ONLN);
#if defined(__linux__)
long long quota = 0;
if (scr_os_cgroup_cpu_quota(&quota) && quota > 0 && quota < rc) rc = (long)quota;
#endif
return rc < 1 ? 1 : (double)rc;
}

double scr_os_totalmem(void) {
/* Total physical memory in bytes (sysconf pages × page size — Darwin
* and Linux both answer _SC_PHYS_PAGES). */
Expand Down
1 change: 1 addition & 0 deletions packages/runtime/src/scr_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -4059,6 +4059,7 @@ ScrStr *scr_os_tmpdir(void);
ScrStr *scr_os_release(void); /* uname(2) release — +1 fresh */
ScrStr *scr_os_type(void); /* uname(2) sysname — +1 fresh */
double scr_os_totalmem(void); /* total physical memory, bytes */
double scr_os_available_parallelism(void); /* uv_available_parallelism: CPUs this process may use, >= 1 */
/* os.userInfo()'s field trio (pw_name / pw_shell / pw_dir — the passwd
* home, not the $HOME cascade). +1 fresh; abort on lookup failure. */
ScrStr *scr_os_user_name(void);
Expand Down
13 changes: 13 additions & 0 deletions tests/corpus/4598-os-available-parallelism.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// os.availableParallelism() — differential against the host's own Node:
// libuv's uv_available_parallelism (the CPU affinity mask, then a smaller
// cgroup CPU quota on Linux; hw.activecpu on Darwin), so both processes
// see the same count.
import * as os from "node:os";
import { availableParallelism } from "node:os";

const n = os.availableParallelism();
console.log(typeof n, Number.isInteger(n), n >= 1);
console.log(n === availableParallelism());
console.log(n);
// A thread count derived from it, like a parallel tool's default.
console.log(Math.max(1, Math.min(n, 64)) === n || n > 64);
Loading