Commit Graph
2002 Commits
Author SHA1 Message Date
Chris Tate 8981b5c0a2 Resolve computed createRequire specifiers at run time (#2079) 2026-10-25 20:29:51 -05:00
Chris Tate 8e4837f94a Run untyped pipeline function stages and later iterable stages (#2276) 2026-10-25 20:17:26 -05:00
Chris Tate 5dfa62b9fc Merge main 2026-10-25 18:54:10 -05:00
Chris Tate a848a7b349 Resolve the pipeline promise to undefined for an untyped iterable destination
When an untyped destination function returned an async iterable, the
promise form settled its result without a payload, and code that read the
awaited value as an untyped value dereferenced NULL. The promise form for
an untyped destination now resolves to undefined when the destination
returned no promise, or its promise carries no value, as Node does.
2026-10-25 18:44:18 -05:00
Chris Tate 417e3a6d08 Call function tables without boxing or reference counting (#2173) 2026-10-25 18:17:54 -05:00
Chris Tate 30cf205e90 Run untyped pipeline function stages and later iterable stages
A pipeline stage that arrives as an untyped value now follows Node's
stage ladder for more shapes. As the first stage, a function is a source
function: the pipeline calls it with { signal } while it is set up and
pumps its result, which must be iterable (ERR_INVALID_RETURN_VALUE
otherwise), and a { readable, writable } pair pumps its readable side.

After the first stage, the pipeline call is lowered once per combination
of its first three untyped stages being functions or not, and a typeof
test picks the branch at run time. A function stage is called with the
previous stage and { signal }; before the last stage its result must be
async iterable, and as the last stage a promise resolves the pipeline's
value and an async iterable feeds the PassThrough pipeline() returns
(SCR_PIPE_DEST_DYN). Any other value that is not a stream becomes
Duplex.from of it, which the next stage reads from without the previous
stage being piped into it, and Duplex.from's ERR_INVALID_ARG_TYPE for
the values it rejects.

An untyped stage followed by an untyped function stage hands its value to
that stage through a cell created per pipeline call, so the function
receives the previous stage's own iterator, as Node's makeAsyncIterable
answers it, instead of an async generator over it.

Untyped web streams, pairs after the first stage, and function stages
that return a stream throw the not-supported error.

RUNTIME_ABI_VERSION goes from 45 to 46 for the new stream exports.
2026-10-25 18:10:32 -05:00
Chris Tate 9067f06c02 Cache the zone offset at UTC midnights for local-time conversions (#2272) 2026-10-25 18:01:58 -05:00
Chris Tate 27c00f6577 util: runtime format strings, inspect.custom and colors, untyped promisify (#1911) 2026-10-25 17:46:31 -05:00
Chris Tate d33b083a3b Cache the zone offset at UTC midnights for local-time conversions
Local setters and the field constructor convert a local time value to
UTC by bracketing it with the zone offset a day before and after, then
checking the candidate instants: three to five localtime(3) calls per
conversion. In glibc each call takes the zone lock and, for dates past
the zone file's last transition, parses the zone's rule string again.

The offsets now come from a per-thread cache of offsets at UTC midnights:
a day whose two midnights agree has that offset throughout, given at
most one transition per day, which the conversion already assumes. A
day with a transition still asks localtime for the instant. A change of
process.env.TZ clears the cache.
2026-10-25 17:30:44 -05:00
Chris Tate 5f76d41a71 Keep U+11A7 after a Hangul LV syllable in NFC and NFKC
libunicode composes an LV syllable with U+11A7 as if it were a trailing
consonant (U+11A8..U+11C2), which drops it: "가ᆧ".normalize()
gave "가" where Node gives "가ᆧ". U+11A7 is a class-0 vowel
that composes with nothing, so for the composing forms the code points
between occurrences of it normalize on their own and it is kept as it
is. The guard sits in front of the vendored library (scr_unicode_normalize),
for String.prototype.normalize and the NFC step of IDNA mapping.

8009-normalize-hangul-jungseong-o-yae prints the code points of all four
forms for U+11A7 after LV and LVT syllables, conjoining jamo, other
starters and combining marks.
2026-10-25 16:57:11 -05:00
Malte UblandClaude Opus 5.5 5a3d6d7903 Call function tables without boxing or reference counting
Values typed `F | undefined` for a function type F are now plain closure
pointers (nullable-pointer unions), so a keyed read of a
`Record<string, F>` or a Map.get no longer allocates a union box. Keyed
record reads, string-keyed Map.get and checked extractions lend the stored
closure as a receiver or callee when ReferenceWrites proves the call cannot
write the locations it was read from, and optional-chain receivers lend
under the same proof. Owned record overflow reads use the inline entry
lookup.

Indirect closure calls skip the ambient receiver bracket for closures
whose bodies never observe it (new ScrClosure.function_kind bit
SCR_FN_RECEIVER_FREE; signature adapters that only forward the receiver
copy it from their original), and a callValue that forwards `dyn.this`
needs no bracket at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:49:00 -07:00
Chris Tate 68942a8a1c Convert Buffers to and from latin1 and hex text a word at a time (#2183) 2026-10-25 14:38:27 -05:00
Chris Tate d477cd45cd Run global substitutions with a literal replacement through build-time replacers (#2240) 2026-10-25 14:19:37 -05:00
Chris Tate ed125cc384 Wait for mailbox events in ppoll on Linux (#2234) 2026-10-25 14:16:18 -05:00
Chris Tate a5663bdd73 Iterate strings over their bytes (#2233) 2026-10-25 14:14:06 -05:00
Chris Tate f82f71e6c5 Iterate and read native arrays from untyped code without type-name scans (#2239) 2026-10-25 14:10:07 -05:00
Chris Tate cab0968510 Keep holes and values written through unknown[] views in typed arrays (#1866) 2026-10-25 14:01:03 -05:00
Chris Tate 90605110a5 Adapt the nursery trigger to where cyclic garbage dies (#2231) 2026-10-25 14:00:41 -05:00
Chris Tate 96f69414be Send every dyn kind that can be a cycle candidate through one release path (#2236) 2026-10-25 13:56:15 -05:00
Chris Tate dec6be6ebd Run global substitutions with a literal replacement through build-time replacers
A global replace or replaceAll of a regex literal called the compiled
matcher once per match: each call set up its frame and seek again, and the
runtime then copied the gap and the replacement. A substitution of a
literal inside the compiled subset with an ASCII replacement without $
patterns now gets a replacer built for that pattern and replacement. It
appends each gap and the replacement to the output and continues the
search after the match, as the runtime's loop does (one past an empty
match), without returning. Gaps of up to 16 bytes are one 16-byte copy,
and the search after a match is a 16-byte scan instead of a memchr call.

scr_regex_replace_with runs the replacer on an ASCII subject; a subject
outside ASCII keeps the runtime's loop. Plain-text patterns, which the
runtime searches as strings, get no replacer.

New corpus program: 8351-regex-literal-replacers.ts.
2026-10-25 13:35:04 -05:00
Chris Tate 4ee477d11e Prepare v0.4.5 release (#2226) 2026-10-25 13:33:57 -05:00
Chris Tate 15a183026f Allocate the shared two-byte characters from the system allocator
The one-character strings of U+0080..U+07FF that string iteration shares
between threads were allocated from the calling thread's memory. On Linux a
worker that exits without sharing anything returns that memory, so the table
kept strings that a later read found discarded. They now come from the system
allocator, as other state that outlives a thread does.
2026-10-25 13:27:58 -05:00
Chris Tate ea599922a4 Iterate and read native arrays from untyped code without type-name scans
Untyped code that loops over a native array (`for (const ch of checks)` in
zod, over arrays the compiled code built) paid for the loop before its
first element:

- Every check of whether a typed capsule is a generator compared its type
  name against "async-generator<" with strncmp, and native array type
  names also start with 'a'. Iterator creation made several such checks.
  The test now looks at the second byte before comparing.
- scr_dyn_iterator called the builtin values() through the general call
  path for a native array; it now creates the native iterator directly,
  as it already did for untyped arrays.
- Read sites of `length` and `next` call scr_js_get_probe_native, which
  answers a native array's `length` and a native iterator's `next`, where
  the generic read built the array's checked view or looked the method up
  by name. Other read sites keep scr_js_get_probe, so their receivers that
  are not ordinary objects still miss without a further test.
2026-10-25 13:23:22 -05:00
Chris Tate e8a64267cb Call toJSON on foreign elements and merge main 2026-10-25 13:13:02 -05:00
Chris Tate ffbbf1590f Send every dyn kind that can be a cycle candidate through one release path
The inline dyn release sorted a surviving value into three groups by
kind: arrays and objects tested the candidate-buffer word and then called
scr_dyn_release_survivor, capsules and handles called it directly, and
functions and proxies appended to the candidate buffer inline. All six
kinds now take one path: a value already in a candidate buffer, or never
one, returns inline, and scr_dyn_release_survivor, which now also covers
functions and proxies, decides the rest. Behavior is unchanged, and each
inlined release site keeps one kind test and one call.

Effect's executable text is 10% smaller and it runs 5.8% faster; zod
runs 3.8% faster, and bench:runtime's dynamic-properties 14% faster.
2026-10-25 13:11:47 -05:00
Chris Tate 13a9476df9 Fix the latin1 decode bounds and merge main 2026-10-25 13:11:33 -05:00
Chris Tate 8f425a8a1b Wait for mailbox events in ppoll on Linux
A thread waiting for its mailbox slept on a condition variable. The
kernel then places the woken thread without a hint, so with every CPU
busy it can queue behind a running thread until that thread is preempted
or blocks: a worker program's phase barrier (post to each worker, wait
for every reply) lost about 1 ms per phase, and up to 5 ms, on 4 CPUs.

On Linux the wait now sleeps in ppoll(2) on the mailbox's wake pipe,
which every post already writes. A pipe write is a synchronous wakeup:
the woken thread can run on the poster's CPU, which the poster frees
when it waits in turn. Bytes left by signals whose events were already
taken are drained under the lock first. Other platforms keep the
condition variable.
2026-10-25 13:01:23 -05:00
Chris Tate a88fdacbe2 Iterate strings over their bytes
for-of, spread, Array.from, yield* and [Symbol.iterator]() over a string
walked a UTF-16 index. Each step mapped the index to a byte offset, built
the character, and measured its UTF-16 length to advance.

They now walk a byte cursor over the WTF-8 data:
- The cpAtByte intrinsic reads the character that starts at the cursor.
  ASCII bytes take the one-character table inline; other characters call
  scr_str_cp_at_byte. byteLength advances the cursor by the character's
  byte count. A lone surrogate is its own three-byte character, so it
  stays a one-unit character, as in Node.
- Spread and Array.from of a string call scr_str_code_points, which
  counts the characters, allocates the array once and fills it.
- The one-character strings of U+0080..U+07FF are made immortal on first
  use and shared, as the ASCII ones are static.

The cpAt intrinsic, which no lowering emits any more, is removed.
2026-10-25 13:00:18 -05:00
Chris Tate e24ed12933 Decode string storage within its length in Buffer encodings
String storage can hold bytes that are not well-formed UTF-8: an HTTP
header value keeps the raw bytes a client sent. Encoding such a string
read past its end.

- scr_bytes_next_cp decoded any high byte as the lead of a sequence and
  read up to three bytes after it without a bound. It now takes the
  storage length, decodes only complete, well-formed sequences, and
  returns any other byte as U+0000..U+00FF, advancing by one. That is
  each such byte's value in Node, which decodes header values as latin1.
  This covers latin1, ascii, binary and utf16le encoding, hex and base64
  of non-ASCII text (which narrow through latin1), Buffer#write and
  TextEncoder#encodeInto.
- base64 decoding read one byte past its input when the eight-character
  groups ended exactly at the end.

The runtime's bytes unit test (built with ASan) encodes ill-formed
storage at every offset within a word and random high bytes, and
compares latin1, ascii and utf16le with a scalar reference. A corpus
program sends such header bytes to a server and encodes them as Node
does.
2026-10-25 12:48:21 -05:00
Chris Tate b0c01dc341 Adapt the nursery trigger to where cyclic garbage dies
A program's nursery trigger starts at 256 candidates and doubles, up to
4096, after each scheduled mature or full pass whose dead set held more
mature and old objects than an eighth of what the nursery passes freed
since the previous one; it halves after one that held less than a
thirty-second of that. SCR_CYCLE_THRESHOLD and
scr_cyc_set_nursery_threshold pin the trigger as before. The
scheduled-age rule keeps counting collection points in the pinned or
default trigger.
2026-10-25 12:48:07 -05:00
Chris Tate 7a175c374f Prepare v0.4.5 release 2026-10-25 11:26:13 -05:00
Chris Tate 8a22e0f515 Answer net.Socket write() with Node's backpressure boolean, and fire 'drain' (#2198) 2026-10-25 11:04:01 -05:00
Chris Tate eca815da02 Merge main 2026-10-25 10:55:32 -05:00
Chris Tate f0add87f10 Crypto parity: RSA key generation lengths, RSA-PSS keys and encrypted private keys (#2103) 2026-10-25 09:45:21 -05:00
Chris Tate 64e350edb6 Drop a socket's 'drain' listeners when it closes
The close step drops a socket's listener lists so that a listener which
captures its socket does not keep it alive; the 'drain' list was not
among them. A once('drain') listener still pending when the peer
disconnected, or an on('drain') listener, held its socket and the
socket's buffers after 'close'. The close step now drops the 'drain'
listeners with the others.
2026-10-25 09:25:45 -05:00
Chris Tate 0488bef1a9 Describe RSA-PSS AUTO verification against restricted keys as Node does
RSA_PSS_SALTLEN_AUTO recovers the salt length from the signature and
does not enforce the key's minimum salt length, as in Node (OpenSSL).
The runtime's exemption now says so, and corpus 8178's header, which
said the minimum was still enforced, describes what it checks.
2026-10-25 09:00:40 -05:00
Chris Tate fba4d6553e Add no empty chunk to a byte-mode Readable's buffer (#2217) 2026-10-25 08:55:59 -05:00
Chris Tate 1c73f7735f Miss non-object receivers early in the property probes and inline the entry check (#2214) 2026-10-25 08:55:01 -05:00
Chris Tate adf97e900f Merge main 2026-10-25 08:50:59 -05:00
Chris Tate 3f013df4f9 Skip the weak-key table when freeing objects that were never keys (#2215) 2026-10-25 08:50:03 -05:00
Chris Tate 14fa2aedd3 Merge main 2026-10-25 08:28:00 -05:00
Chris Tate c6679d43fb Add no empty chunk to a byte-mode Readable's buffer
push() of an empty Buffer outside object mode put a zero-length entry
into the read buffer, and the chunks pushed after it stalled behind it:
a 'data' listener saw nothing more and a readline interface never
closed. Node's readableAddChunk adds no empty chunk outside object mode;
it clears the reading flag and may read more. The runtime now does the
same, as it already did for an encoded push that decodes to nothing.
2026-10-25 08:18:46 -05:00
Chris Tate 3e1af48ea3 Convert a loop's string receiver to UTF-16 at once only when the loop can read enough of it (#2212) 2026-10-25 08:17:39 -05:00
Chris Tate 4d27b1a000 Call replace callbacks with unannotated parameters directly (#2207) 2026-10-25 08:05:35 -05:00
Chris Tate b28af56ba8 Skip the weak-key table when freeing objects that were never keys
Once a program creates a weak collection, every object freed afterwards
called into the weak-key table to remove entries keyed by it, although
almost none are keys: zod's validation freed 16.6 million objects against
23 keys. A per-thread bit filter indexed by the object's address now marks
every address that entered the table (cleared when the table empties),
and the free paths test it inline, so an object whose bit is clear makes
no call.
2026-10-25 07:53:02 -05:00
Chris Tate 2823840134 Miss non-object receivers early in the property probes and inline the entry check
Since the probes began caching absent properties (#2186), an untyped
property read that reaches the load or get probe costs more than before:

- Receivers that are not ordinary objects, such as class instances, have
  no shape and always miss, but they set up the probe's frame first. The
  exported probes now return the miss for them before calling the probe
  body, which stays out of line.
- Each matching entry was answered through an out-of-line call
  (scr_js_entry_answer). It is inlined into the probe again.

dynamic-properties executed 3.1% more instructions than before #2186; it
now executes 0.4% fewer.
2026-10-25 07:49:06 -05:00
Chris Tate 63a99cc4d9 Convert a loop's string receiver to UTF-16 at once only when the loop can read enough of it
A versioned loop over a non-ASCII string passes the range of indexes it
reads: a counted loop's start and limit, or a while loop's counter and
bound. The runtime converts the string at once when that range has at
least one unit per 16 bytes of it, and otherwise counts the loop as one
indexed read, so a loop over a line of a long source text no longer
keeps a UTF-16 copy of all of it. The publish call is weighted as
unlikely, since loops re-entered over one string skip it.
2026-10-25 07:37:50 -05:00
Chris Tate b7915b92a7 Merge main 2026-10-25 06:38:44 -05:00
Chris Tate c8b40bd73e Move small number-key maps to the direct index from the second key (#2204) 2026-10-25 06:34:30 -05:00
Chris Tate 1a0d58d1eb Merge main and move to ABI 45 2026-10-25 06:31:07 -05:00