A class carries the collector header when any class in its hierarchy can
be on a cycle, so instances of classes whose own fields hold nothing
cycle-capable were buffered as candidates and walked by every level. Such
an object has no traced children and is never on a cycle.
Their descriptors now name scr_cyc_no_children, and they are allocated
with buf set to SCR_CYC_ACYCLIC. That value has the buffered bit set, so
a release that leaves the object alive makes no call; scr_cyc_is_buffered
is false for it, and an emitted death or publication mark takes the object
out of a candidate buffer only when buf is below -1. A pass that walks
such an object as a child restores the value when it survives.
Chains of global one-character replacements (the HTML-escape idiom) run
as one scr_str_replace_chars call. It searched for the next match eight
bytes at a time and started the search over after each replacement.
It now reads 16-byte blocks with SSE2 or NEON (8-byte words elsewhere on
little-endian hosts) and replaces every match of a block before reading
the next one. The table of characters and replacements is kept for the
next call with the same immortal operands, which are the literals the
compiler passes, instead of being rebuilt from the replacement text on
every call.
- The number and whitespace scanners keep the cursor in locals and write
p->pos once: the runtime builds without strict aliasing, so a loop on
p->pos stored it after every byte.
- Typed arrays gather parsed elements in a 32-slot run and append them
with scr_arr_push_many instead of one push per element.
- The string scan shared by parse and stringify takes the index of the
first quote, backslash or control byte from the marked word on
little-endian hosts, and checks the last 1-7 bytes of a string as one
word, so a short plain string needs no byte loop.
- Typed record parsing computes once per record schema and parse which
field names hold no byte JSON would escape, instead of testing the
name on every direct key match.
- The mantissa converts to double through int64, and the positioned
error report stays out of line.
Number() of a string with an optional sign and 1 to 15 digits returns
the integer directly; every other string keeps the full StringToNumber
grammar, out of line.
split() with a separator collects its first batch of pieces before
creating the result, so the result is an inline array of exactly that
size, filled in place. A split's default limit skips the uint32
conversion, and pieces of a proven-ASCII string are created ASCII,
including the scratch pieces of split loops.
indexOf() from the start of a proven-ASCII string searches bytes
directly, and slice() of one skips the UTF-16 index map. The one-byte
case of the byte search reaches memchr without a stack frame.
- Port messages that a 'message' listener posts while more worker
messages are queued are delivered after those messages and before
setImmediate callbacks, as in Node. The ports' poll phase waits while a
batch's next message is queued, and the loop pass continues once more
after the batch when a port has something to deliver.
- A mailbox counts its queued events per kind as they are posted and
taken, so starting a batch reads the count instead of scanning the
queue under the lock. Messages that alternate with a worker's captured
stdout or stderr start a batch each, which made delivery quadratic:
20000 such messages took 5.6 s to drain, and now take 0.5 s (Node 0.57 s).
- The continue hook passes on that the batch's next message is at the
head, so the dispatch that follows does not lock the mailbox again.
- Corpus 6212 checks the order of worker messages, a MessageChannel
message posted by the first listener, and setImmediate against Node.
6211's costs are now taken from its CI timings.
markGray's edge visitor kept a stack frame on every call, though most
edges end at an object that is skipped or already gray. Those now return
without one, and the first visit and skipped cross-generation edges
tail-call out of line, as the scan visitors already do.
The walks no longer call the dense-array walker for an empty array. The
routing of nursery candidates past bypassed levels inlines its bypass
test, and the end of each pass tests its work vectors' capacity inline.
What is collected, and when, is unchanged.
tls.createServer({ cert, key, SNICallback }) hands each ClientHello's
servername to the callback and serves the SecureContext it answers,
synchronously or later, or the default cert and key when it answers none.
An error answer closes that connection. This uses the SNI handling that
http2.createSecureServer already has. A callback expression with side
effects, or SNICallback combined with ALPNProtocols, minVersion, maxVersion
or the other options the runtime walk reads, fails with SC2020.
A tls.connect or https.request client whose peer closes or resets the
connection before the handshake completes now fails with Node's ECONNRESET
"Client network socket disconnected before secure TLS connection was
established" instead of closing without an error.
Refs #1438
Co-authored-by: saboursepideh <25549001+saboursepideh@users.noreply.github.com>
tls.createServer validated minVersion and maxVersion with its own loop,
which threw ERR_TLS_INVALID_PROTOCOL_VERSION for null. It now uses the
same check as tls.connect, where null and undefined select the default
bound, as in Node.
An array's element ops are a registry id in `spare` instead of a pointer
field: descriptors get their id on their first array (emitted descriptors
are writable for it), and a program with more element types than the id
holds keeps the rest in the array's out-of-line storage.
The out-of-line storage (sparse indices, noncanonical numeric properties,
match metadata) is no longer a header field. An array that has it keeps
its pointer in the 8 bytes before its dense allocation and sets
SCR_ARR_HAS_EXT; such an array always has a dense allocation of its own
(inline slots move out, an empty array gets the prefix alone), and every
allocation, reallocation and free of the dense storage accounts for the
prefix.
Loops over elements look the ops up once, and the state lookup below the
dense capacity reads no out-of-line storage.
The first Object.freeze, Object.seal or Object.preventExtensions of a typed
function creates its property table from the function's dynamic box. The
box had no name, so later `f.name` reads answered "". The box now carries
the function's JS name, as a closure flowing into any checked-dynamic slot
does.
The runtime's own callers (published values, os and node constants, the
private-range block list, Wasm exports) freeze data they built, and now
call scr_dyn_freeze_data, the freeze for values that are not functions.
Object.isFrozen, Object.isSealed and Object.isExtensible read a
function's existing table and answer false, false and true when it has
none. Fully typed programs therefore link no dynamic-tier object code
for these calls again.
A thread's event loop delivered one message from its workers or its
parent per loop turn, so timers and setImmediate callbacks ran between
messages that were already queued. Node's MessagePort delivers, in one
callback, the messages queued when it starts and those that arrive
meanwhile until it has taken 1000, with a microtask checkpoint after
each, before the loop goes on to the check phase.
- scr_worker_dispatch starts a batch when it delivers a message: the
messages queued then, and at least 1000 in all.
- scr_workers_more continues the loop pass while the next event in the
thread's mailbox is a message and the batch has room; other events
(online, exit, error, stdio) keep their order and their turns.
- scr_mailbox_head_kind and scr_mailbox_count_kind read the mailbox's
first event kind and the number of queued events of a kind.
- Corpus 6211 checks the order of messages, nextTick, microtasks and
setImmediate for worker-to-parent and parent-to-worker messages
against Node.
Revert 3a27a92cb ("Forward at most four emit arguments through
EventEmitter method values") and 0cf62ebec ("Answer EventEmitter methods
read as values off compiled emitters"). Under the scope-B plan,
EventEmitter methods read as values, `EventEmitter.prototype.on` in
JavaScript and constructor-function emitters belong to Node islands, not
the native compiler. The typed/untyped listener sharing and the
catchable throw in place of the trap stay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fs.promises.rm(path, options) accepts the { recursive, force, maxRetries,
retryDelay } literal options that rmSync accepts. It runs the same removal
and rejects with its errors.
Removing a directory without recursive now throws Node's ERR_FS_EISDIR
SystemError from rmSync and fs.promises.rm. A retryDelay outside int32 >= 0
or a maxRetries outside uint32 throws ERR_OUT_OF_RANGE. rmSync checks these
options before the path, and fs.promises.rm checks the path first, as Node
does.
Fixes#1615
Co-authored-by: jlucaso1 <55464917+jlucaso1@users.noreply.github.com>
The runtime's ToIntegerOrInfinity for string index arguments (slice,
substring, charAt, indexOf, startsWith, padStart, repeat and the others
in scr_string.c) converts through int64 instead of calling trunc, which
is a libm call on baseline x86-64. Each slice made two such calls.
The frontend's syntax trees and program state refer to each other, so
reference counting frees almost none of them when a build disposes the
frontend. Whether a cycle pass reclaimed them before the build ended
depended on where the collector's schedule happened to be. Passes during
the frontend found its data live and widened the growth fractions, so the
next pass waited until the heap had grown well past its size at disposal,
and the dead frontend and the emitter's garbage stayed until the build
ended.
The native compiler now runs scr_collect_cycles_phase right after it
disposes the frontend, through a host function. The runtime entry point
sweeps, then restarts the growth fractions and the death rule's backoff
from the heap the sweep leaves, as after a productive pass. Under Node
the hook is unset.
When a script thread parks its allocator arena, each class slice still
holds the pages past its bump pointer that were faulted in but never
carved: the rest of the huge page the bump pointer is in, and the pages
the pre-fault helper faulted in ahead of it. The same applies to region
chunks when the thread exits. Until another thread claims the arena,
nothing carves there, so a program that continues on threads that never
claim it (parse threads ahead of a check on the main thread) kept them.
- scr_sa_park_arena returns those pages with MADV_DONTNEED. The next
thread to claim the arena faults them in again as it carves, and they
read as zero, as fresh pages do.
- scr_rgn_thread_exit returns each chunk's pages past its bump on worker
threads; nothing carves in an exited thread's regions.
- scr_mem_release_ahead drops pre-fault requests that overlap the range
and waits for one in progress before returning it, so the helper does
not fault the pages in again.
- scr_alloc.test.c checks that a worker that shares and carves past the
huge-page switch leaves no resident pages past its frontier, keeps its
blocks, and that the next thread carves on from the frontier, zeroed.