Keep scr_lib.c's default object byte-identical for the fs.watchFile poll

Adding scr_stats_of_path and scr_stats_zero to scr_lib.c moved every
program's executable: scr_lib.o is linked everywhere, so its new symbols
and strings shifted addresses even for programs that never watch a file.

The two entry points now live in an SCR_FS_WATCH_FILE build of scr_lib.c,
the matrix's variant for programs whose feature set has watchFile, and the
poll unit calls the renamed scr_stats_poll_path. Programs that do not poll
link the default object unchanged: compiled without the define, scr_lib.o
is byte-identical to the base (verified by comparing the two objects).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Malte Ubl
2026-10-29 05:42:49 -07:00
co-authored by Claude Opus 5.5
parent 60be717d92
commit ad8f8daccd
5 changed files with 26 additions and 9 deletions
@@ -426,6 +426,8 @@ const RUNTIME_DEFINES: readonly {
{ define: "SCR_NATIVE_REACTIONS", when: (opts) => opts.webStreams },
{ define: "SCR_HANDLE_CLASSES", when: (opts) => opts.webStreams },
{ define: "SCR_YIN", when: (opts) => opts.youngNursery },
// scr_lib.c's path stat and zero Stats for the fs.watchFile poll.
{ define: "SCR_FS_WATCH_FILE", when: (opts) => opts.watchFile },
{ define: "SCR_NODE_ISLANDS", when: (opts) => opts.nodeIsland },
];
@@ -274,6 +274,17 @@ export function variantsFor(source) {
defines: ["SCR_TEXT_DECODER_LEGACY"],
});
}
// fs.watchFile's poll needs a path stat that reports the errno (its failed
// poll IS a reportable event). The Stats snapshot is private to scr_lib.c,
// so the two entry points live in a build of that unit only programs that
// poll link; every other program keeps the default object, byte for byte.
if (source === "scr_lib.c") {
variants.push({
id: "fs-watch-file",
when: { watchFile: true },
defines: ["SCR_FS_WATCH_FILE"],
});
}
// Programs that assign process.emitWarning link the replacement-aware
// warning paths; every other program keeps the default object.
if (source === "scr_async_dyn.c") {
+8 -6
View File
@@ -5584,7 +5584,7 @@ static bool scr_stats_link_target(HANDLE h, DWORD tag, double *size_out, ScrStr
return true;
}
static ScrStats *scr_stats_win_handle_stats(const ScrStr *path, const char *op,
static ScrStats *scr_stats_of_path(const ScrStr *path, const char *op,
bool no_follow) {
WCHAR *wide = scr_fs_win_wide(path);
if (!wide) return scr_stats_crt_fallback(path, op);
@@ -5630,7 +5630,7 @@ static ScrStats *scr_stats_win_handle_stats(const ScrStr *path, const char *op,
/* Reparse points are a general interception mechanism. Node/libuv
* follows non-link tags even for lstat, so reopen the resolved target. */
CloseHandle(h);
return scr_stats_win_handle_stats(path, op, false);
return scr_stats_of_path(path, op, false);
}
if (!scr_stats_link_target(h, tagged.ReparseTag, &link_size, NULL)) {
/* A mount-point tag may name a mounted volume rather than a junction.
@@ -5638,7 +5638,7 @@ static ScrStats *scr_stats_win_handle_stats(const ScrStr *path, const char *op,
* ordinary reparse points and retries with the final component
* followed. Do not classify from the tag alone. */
CloseHandle(h);
return scr_stats_win_handle_stats(path, op, false);
return scr_stats_of_path(path, op, false);
}
is_link = true;
}
@@ -6081,7 +6081,7 @@ ScrBytes *scr_fs_readlink_bytes(ScrStr *path) {
ScrStats *scr_fs_stat(ScrStr *path) {
if (!scr_fs_path_ok(path, "path")) return NULL;
#ifdef _WIN32
return scr_stats_win_handle_stats(path, "stat", false);
return scr_stats_of_path(path, "stat", false);
#else
int error = 0;
ScrStats *s = scr_stats_posix(path->data, -1, false, &error); /* follows symlinks */
@@ -6093,7 +6093,7 @@ ScrStats *scr_fs_stat(ScrStr *path) {
ScrStats *scr_fs_lstat(ScrStr *path) {
if (!scr_fs_path_ok(path, "path")) return NULL;
#ifdef _WIN32
return scr_stats_win_handle_stats(path, "lstat", true);
return scr_stats_of_path(path, "lstat", true);
#else
int error = 0;
ScrStats *s = scr_stats_posix(path->data, -1, true, &error); /* no follow */
@@ -6135,10 +6135,11 @@ ScrStats *scr_fs_fstat(double fd) {
return s;
}
#ifdef SCR_FS_WATCH_FILE
/* fs.watchFile's poll (scr_watch_file.c calls this): stat(2) following
* symlinks, libuv's uv_fs_stat — NULL with *error = errno on failure,
* never throwing, because a failed poll IS a reportable event. +1. */
ScrStats *scr_stats_of_path(ScrStr *path, int *error) {
ScrStats *scr_stats_poll_path(ScrStr *path, int *error) {
if (!scr_fs_path_ok(path, "path")) {
*error = EINVAL;
return NULL;
@@ -6161,6 +6162,7 @@ ScrStats *scr_stats_of_path(ScrStr *path, int *error) {
* field 0, so isFile()/isDirectory() and the size/date reads all answer
* zero/false. +1. */
ScrStats *scr_stats_zero(void) { return scr_stats_new(); }
#endif /* SCR_FS_WATCH_FILE */
/* ── Stats objects ───────────────────────────────────────────────────
* Node's Stats and BigIntStats over a snapshot. The integers and the *Ms
+4 -2
View File
@@ -507,8 +507,10 @@ double scr_stats_big_birthtime(ScrStats *s);
/* fstat without throwing: +1, or NULL with *error set to the errno. */
ScrStats *scr_stats_of_fd(int fd, int *error);
/* stat(2) following symlinks without throwing (the fs.watchFile poll):
* +1, or NULL with *error set to the errno. */
ScrStats *scr_stats_of_path(ScrStr *path, int *error);
* +1, or NULL with *error set to the errno. Both this and scr_stats_zero
* exist only in the SCR_FS_WATCH_FILE build of scr_lib.c, which programs
* that poll link; every other program links the default object. */
ScrStats *scr_stats_poll_path(ScrStr *path, int *error);
/* The all-zero snapshot a failed poll reports (Node's kZeroStats). +1. */
ScrStats *scr_stats_zero(void);
/* Node's Stats (or BigIntStats) object for the snapshot: own fields in the
+1 -1
View File
@@ -171,7 +171,7 @@ static void scr_watch_file_poll(ScrClosure *cb) {
if (p == NULL) return;
p->timer = 0; /* the loop consumed this arm */
int err = 0;
ScrStats *cur = scr_stats_of_path(p->path, &err);
ScrStats *cur = scr_stats_poll_path(p->path, &err);
if (cur == NULL) {
if (p->state != -err) {
scr_watch_file_emit(p, NULL, p->last, -err);