1841 Commits
Author SHA1 Message Date
Chris Tate 8c88a8f87f Add fs.opendirSync, fs.promises.opendir and fs.Dir (#1044) v0.3.3 2026-10-15 12:24:47 -05:00
Chris Tate 7bc9c78f00 Keep corpus 5407's requests within one read (#1043) 2026-10-15 12:24:25 -05:00
Chris Tate c53f864e61 v0.3.3 (#1042) 2026-10-15 12:13:13 -05:00
Chris Tate 9377dab890 Add fs.opendirSync, fs.promises.opendir and fs.Dir
opendirSync(path[, options]) and fs.promises.opendir(path[, options])
return a Dir that reads one Dirent at a time in OS order, as libuv's
uv_fs_readdir does. Dir supports path, readSync(), read(), closeSync(),
close() and for await. readSync and read answer null at the end. A
closed Dir throws ERR_DIR_CLOSED from readSync and closeSync, and
rejects with it from read and close.

for await over a Dir reads with read() until null and closes the Dir
in a finally, as Node's Dir.entries() does: normal exhaustion, break
and a throw all close it, and a loop whose body closes the Dir ends
with ERR_DIR_CLOSED.

A Dir also crosses 'unknown' slots as a native handle (Dir), so a
closure returning one can be passed to untyped JavaScript functions.
Through such a slot, path and closeSync() work, and other members throw
the "not supported yet" Error.

Open failures carry the opendir syscall and no path. options.bufferSize
is checked after the directory opens, with Node's ERR_OUT_OF_RANGE
messages. The options must be an object literal with a numeric
bufferSize and a utf8 encoding. Recursive walks, buffer encodings and
the callback forms are not lowered. TypeScript sources get a compile
diagnostic for them; in JavaScript sources, recursive walks and the
callback forms throw the fence error when the call runs.

Corpus 5544 covers both forms against Node 24.22.
2026-10-15 11:34:00 -05:00
Chris Tate b03600d328 Find fs callbacks where Node's signatures put them (#1041) 2026-10-15 11:29:40 -05:00
Chris Tate d7a7409277 Keep corpus 5407's requests within one read
The header-limit program sent heads of up to about 19 KB with long field
names. macOS loopback delivers that in more than one segment, so Node's
server rejected the head with bytes still unread and its close reset the
connection; the client socket had no 'error' listener, so Node exited on
the ECONNRESET after printing the first 431 case.

Every field is now a minimal `h:` line, which keeps each request under
7 KB, and the client socket ignores a reset so the case still ends. The
cases and their output are unchanged.
2026-10-15 11:28:15 -05:00
Chris Tate 2f8aea3279 v0.3.3 2026-10-15 11:23:16 -05:00
Chris Tate 374f9c51cd Inline string retains and dynamic value reference counting in release builds (#1030) 2026-10-15 11:18:39 -05:00
Chris Tate 8f350a5441 Find fs callbacks where Node's signatures put them 2026-10-15 10:56:41 -05:00
Chris Tate b9169a86a3 Limit inline dynamic value releases per function and in sanitized builds
A function body with more than 256 inline dyn release sites now calls
scr_dyn_release at every site. Generated dispatch tables and the module
initializers and exit paths of large packages have thousands of release
sites, and LLVM's optimization time grows faster than their number:
published Effect schema programs took over 9 minutes to compile.

Sanitized builds compile the program in one AddressSanitizer pass, so
they define the inline reference counting helpers noinline and call
them at each site.
2026-10-15 10:44:20 -05:00
Chris Tate 27db9f46e0 Give dynamic Dirent values Node's prototype and inspect form (#1037) 2026-10-15 09:56:23 -05:00
Chris Tate 29d5fafa84 Add the missing fs/promises functions and Node's fs argument checks (#1039) 2026-10-15 09:42:05 -05:00
Chris Tate 36d45882bd Close Node 24 gaps in util.types keys, HTTP header limits and unread requests (#1021) 2026-10-15 09:29:01 -05:00
Chris Tate f325b5f116 Add the missing fs/promises functions and Node's fs argument checks 2026-10-15 09:13:46 -05:00
Chris Tate 1996d250c9 Compute remainders by powers of two without fmod (#1038) 2026-10-15 09:06:36 -05:00
Chris Tate b7dddbbb39 Compute remainders by powers of two without fmod
`x % c` for a constant c whose magnitude is a power of two of at least 1
now computes `x - trunc(x / c) * c`, which is exact for every double, and
gives a zero result the dividend's sign. Divisors written as `2 ** k`
count as constants. Other divisors keep frem, and a fractional constant
divisor skips the integer check it can never pass.

The inline integer path for unproven operands now accepts dividends up
to 2^62 instead of 2^53. The remainder of two doubles is a double, so
the conversion back is exact, and products such as
`seed * 1103515245 + 12345` no longer call fmod.
2026-10-15 08:36:53 -05:00
Chris Tate 51c2420a78 Inline record result-slot entries and keep array headers out of loops (#1035) 2026-10-15 08:36:39 -05:00
Chris Tate 97cd12d98a Give dynamic Dirent values Node's prototype and inspect form
A Dirent row that becomes a dynamic value (untyped JavaScript, unknown
slots, rows passed from a typed module) was a plain object: its type
probes were missing, so `readdirSync(dir, { withFileTypes: true })
.map((d) => d.isFile())` in a .mjs program threw "d.isFile is not a
function", and Object.keys, JSON.stringify and util.inspect showed the
internal %dtype field.

Both record-to-dyn converters now finish Dirent rows with
scr_dyn_dirent_finish. The row gets a shared Dirent prototype that holds
isDirectory, isFile, isBlockDevice, isCharacterDevice, isSymbolicLink,
isFIFO and isSocket as non-enumerable methods. The entry kind is stored
under an own Symbol(type) key, as in Node, and %dtype becomes
non-enumerable, so a dynamic Dirent still converts back to the typed
row. Live record ops are not emitted for Dirent rows, so key walks read
the finished view. A method called with a nullish receiver throws Node's
TypeError.

util.inspect of a dynamic object now skips non-enumerable properties and
lists own enumerable symbol keys after the string keys, as Node's
default view does. Typed Dirent rows render through the dynamic view, so
both print `Dirent { name: 'a.txt', parentPath: '.', Symbol(type): 1 }`.

The finisher lives in scr_symbol.c. Programs that have the Dirent record
shape link that unit.

Corpus 5565 and 5566 check the untyped and cross-module forms against
Node.
2026-10-15 08:35:14 -05:00
Chris Tate c4e5c3d451 Add fs.globSync, fs.glob and fs/promises glob (#1034) 2026-10-15 08:30:46 -05:00
Chris Tate 6b7e9539fb Inline record result-slot entries and keep array headers out of loops
A function that returns a record its caller only projects fills the
caller's frame storage through a %into.<name> entry. These entries are
small and called once per use, but LLVM kept them out of line in loops:
record-boundaries' pathLength called %into.pointAt twice per iteration.
They now carry inlinehint, like the indexed-read and interface-dispatch
helpers.

Once inlined, the scalar array reads in them still blocked hoisting the
arrays' header loads: the slow path's scr_arr_state and getter calls had
no memory attributes. They are now read-only cold calls, as the dense
read fallbacks already are (the getter runs only for a present element,
so it does not trap).
2026-10-15 08:02:39 -05:00
Chris Tate 0b468e40a1 Add fs.globSync, fs.glob and fs/promises glob
The three glob entry points run a port of Node 24's
lib/internal/fs/glob.js walk in scr_glob.c: the same LIFO queue of
(path, patterns) items over the sorted directory listings, the same
lstat, stat, readdir, realpath and seen-pattern caches, the same result
set in insertion order, and the same exclusion checks, so matches come
out in Node's order. Patterns compile with the existing minimatch port.
glob_match is split into a compile step and a test step so the walk can
reuse each compiled pattern and test it segment by segment.
nocaseMagicOnly is now an option, because exclude patterns turn it off.

Patterns (a string or string array) and options (cwd as a path or
file: URL, exclude as patterns or a function, followSymlinks,
withFileTypes) are validated in Node's order with Node's errors,
including the internal assertion Node hits for a non-string cwd with
exclude patterns. withFileTypes results are Dirent rows: name,
parentPath and the type probes. parentPath follows Node's rule for each
form: the joined path for globSync, the resolved path for fs/promises
glob.

fs.glob checks the callback and validates synchronously, then delivers
string[] or the walk's error through the filesystem callback queue.
fs/promises glob lowers as the source of a for await loop. The walk runs
when the loop starts, and the matches found before an error bind before
the error is thrown, as Node's async iterator yields them first. The
older glob(3)-based fs.glob branch is removed.

Corpus 5448-5452 and a node-types fixture check the behavior against
Node.
2026-10-15 07:59:19 -05:00
Chris Tate 31ba299f87 Complete fs.Stats in both lanes (#1025) 2026-10-15 07:54:30 -05:00
Chris Tate 54bc586764 Convert nullable records into widened record slots (#1033) 2026-10-15 07:21:16 -05:00
Chris Tate e10ea93fd3 Support socket and server ref() and unref() (#1032) 2026-10-15 06:54:33 -05:00
Chris Tate 3b379bf623 Convert nullable records into widened record slots
A record parameter of a function the record-flow analysis does not track
widens to the fields its own arguments can leave undefined, and since the
flow-class change those arguments include object literals and call
results. Another call can pass that parameter a value stored as
`R | undefined`: a for-of element whose loop the element analysis cannot
prove free of holes binds `R | undefined` while the checker types it `R`.
Coercing that union into the widened layout of R found no mapping and
failed with SC2003, where the declared parameter used to take the checked
extraction of the record arm.

A union of one record arm and unit arms flowing into a record that arm
lifts to now takes the same checked extraction, then the record's width
lift into the destination layout.
2026-10-15 06:50:27 -05:00
Chris Tate 167101b4fa Append to strings in place for fields, concat chains and character codes (#1031) 2026-10-15 06:48:21 -05:00
Chris Tate 740bd38e0f Support socket and server ref() and unref()
socket.ref()/unref() and server.ref()/unref() decide whether the handle
keeps the event loop alive, on typed handles and through checked-dynamic
handles. The loop's net liveness now counts only ref'd sockets and
servers; unref'd ones still deliver their events while other work keeps
the loop running, because the sleep polls the net unit's fd whenever any
handle exists.

Corpus 5595 covers the server, client and accepted sockets.
2026-10-15 06:24:48 -05:00
Chris Tate 416311f2c5 Append to strings in place for fields, concat chains and character codes
The runtime appends to a string in place when the left operand is
uniquely owned, but three common forms never got there:

- `obj.field += s` on a plain class or record field kept a second
  reference to the old value, so every append copied the whole string.
  The compound assignment now reads a stored string field into a mutable
  local, stores "" into the field (no code runs in between: both operands
  are already strings), runs the self-append `old = old + rhs` and stores
  the result.
- `s = s + a + b` copied per append: the self-append recognized only
  `s = s + x`. A concat chain whose leftmost operand is the target now
  takes the remaining operands as the suffix, in the same order.
- `s += String.fromCharCode(c)` built a one-character string first.
  scr_str_concat_move_char_code appends the character, in place for ASCII.

Self-appends also borrow the suffix instead of retaining it, and a
discarded compound assignment no longer retains and releases its result.
2026-10-15 06:17:38 -05:00
Chris Tate 97a9257fa5 Inline string retains and dynamic value reference counting in release builds
Release builds now emit the string retain and the dyn retain and release
fast paths inline. The dyn release decides the cases scr_dyn_release
decides by kind and calls scr_dyn_release_survivor for capsules and
handles. String releases and the other families stay runtime calls, as
string-heavy programs have many release sites. The speed posture keeps
every fast path and gains the dyn release. The runtime ABI version moves
to 21 because program objects call the new runtime entry point.
2026-10-15 05:15:33 -05:00
Chris Tate 42e15185f2 Split the Limitations page into partials (#1029) 2026-10-15 05:00:30 -05:00
Chris Tate bbc1d6d0b0 Merge main 2026-10-15 04:52:43 -05:00
Chris Tate 7e99752246 Split the Limitations page into partials
docs/content/docs/limitations.mdx now holds the frontmatter, the
headings, the anchor spans and one <include> line per partial. The text
moves unchanged to docs/content/limitations/: one file per labeled
bullet (<section>/<label>.mdx) and one per section's other text
(<section>.mdx). Edits to different bullets no longer touch adjacent
lines of one file.

remarkDocsConventions splices the included partials into the page and
joins the lists that consecutive bullet partials contribute, so the page,
its Markdown twin and the search index render as before. The route tests
read the page with its partials inlined. A page that includes partials
takes its last-modified date, used by the sitemap, from the newest commit
to the page or any of its partials.

docs/scripts/limitations-partials.mjs joins the partials back into one
file, splits a single-file page, and merges a branch's edits to the
single-file page into the partials.
2026-10-15 04:31:49 -05:00
Chris Tate f3cd85ea17 Pass results from fs.mkdir, readFile and write callbacks (#1020) 2026-10-15 04:29:45 -05:00
Chris Tate 54b240266c List readdir entries in byte order on POSIX hosts (#1023) 2026-10-15 04:25:48 -05:00
Chris Tate 7a96feb962 Merge main 2026-10-15 04:11:04 -05:00
Chris Tate 9c205c69fc Merge main 2026-10-15 04:04:17 -05:00
Chris Tate c85f08cb7b Search arrays of objects and strings inline (#1027) 2026-10-15 04:03:09 -05:00
Chris Tate fd69a3046d Merge main 2026-10-15 03:56:22 -05:00
Chris Tate d9c71fe72b Check corpus 5442's fractional-offset fill over eight runs (#1026) 2026-10-15 03:54:33 -05:00
Chris Tate 1df4778052 Merge main 2026-10-15 03:54:12 -05:00
Chris Tate 1dedd5eff0 Search arrays of objects and strings inline
indexOf and includes on arrays of class instances, records, arrays, maps,
sets, typed arrays and strings call an internal helper that LLVM inlines.
When the array's hole-free prefix covers its whole length, the helper
scans the dense slots itself: references compare as 64-bit slots, four
per branch, and strings compare by pointer, then length, then bytes, with
strings of up to 16 bytes compared as two words and longer ones through
scr_str_eq. Arrays with holes or sparse entries, and every other element
type, keep the runtime search.
2026-10-15 03:35:00 -05:00
Chris Tate a06112c82d Split scr_runtime.h into per-module runtime headers (#1019) 2026-10-15 03:34:00 -05:00
Chris Tate 6232b532c7 Check corpus 5442's fractional-offset fill over eight runs
randomFillSync(new Uint16Array(32), 8.5, 8) fills bytes 17 to 32, so the
last 4-byte group the check prints holds one random byte, which is zero
1 time in 256, in Node as well. The check now ORs the bytes of eight
fills before it reports the groups. The expected output is unchanged.
2026-10-15 03:19:50 -05:00
Chris Tate 9a1effd421 Complete fs.Stats in the typed and callback lanes 2026-10-15 03:03:32 -05:00
Chris Tate b249cc6bae Regenerate the runtime header split on current main 2026-10-15 03:01:21 -05:00
Chris Tate 15202442be Merge main 2026-10-15 02:52:25 -05:00
Chris Tate 72cd278f17 Run the Mach-O library and arm64 string oracle tests in the pull request macOS suite (#994) 2026-10-15 02:34:39 -05:00
Chris Tate 04ca0618dd List readdir entries in byte order on POSIX hosts
libuv's uv_fs_scandir sorts entries with strcmp on POSIX hosts, so Node's
readdirSync, readdir and fs/promises readdir list names in byte order
(with and without withFileTypes). The runtime returned the directory's
raw order, which differs from Node on most filesystems. The POSIX
scandir listing now sorts its names and entry kinds together, and
scr_fs_readdir reads names from that listing as the Windows path already
did. Windows keeps the directory's own order, as libuv does.

Corpus 5447 checks the listings against Node.
2026-10-15 02:31:42 -05:00
Chris Tate 278be10650 Compare ASCII localeCompare strings eight bytes at a time (#1018) 2026-10-15 02:30:41 -05:00
Chris Tate 07ce602de5 Close Node 24 gaps in util.types keys, HTTP header limits and unread requests
- Object.keys(util.types) lists Node's 43 keys in Node's order, and
  Object.keys over the node:util/types namespace lists them sorted with
  "default". Object.values and Object.entries over util.types are refused,
  because some members do not compile yet.
- The HTTP server answers 431 Request Header Fields Too Large when a head
  or trailer block exceeds 1000 fields or the header byte bound, and the
  trailer block counts its fields on its own, like Node. Parser errors send
  Node's fixed responses without a Content-Length header.
- Server requests start paused like Node's IncomingMessage: body bytes and
  'end' wait for a 'data' listener, pipe() or resume(). When the response
  finishes, an unread request is dumped and emits 'end', before the
  response's 'close' for an empty body and after it otherwise. A body that
  is still arriving when the response finishes keeps reaching its
  listeners. req.complete reads true once the body has arrived.
- Document that small Buffers own exact-size ArrayBuffers instead of
  sharing Node's 64 KiB pool.

Corpus 5405-5409 cover each case.
2026-10-15 02:26:31 -05:00