Commit Graph
1537 Commits
Author SHA1 Message Date
Chris Tate a9ee38611e Register the global object's names again after a library re-init 2026-10-22 20:20:56 -05:00
Malte UblandClaude Opus 5.5 122c98cf4f Read the global object without rebuilding its name table
Every evaluation of `globalThis` as a value built an array literal of
every name the global type declares (hundreds of strings) and registered
each one in the known-name table again. A server that reads a singleton
through `globalThis` per request spent about a tenth of its time there.

Emit the name table as one immortal static array per program (the
tagged-template site array, keyed by its contents), and return the
global object at once when the runtime sees a table it has already
registered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:10:05 -07:00
Chris Tate 1660531c7b Support FinalizationRegistry with bookkeeping and no cleanup callbacks (#1854) 2026-10-22 20:03:31 -05:00
Chris Tate 62a0426193 Keep Linux poller timers in a heap behind one timerfd (#1896) 2026-10-22 20:02:08 -05:00
Chris Tate 636ffa21be Merge main 2026-10-22 19:59:51 -05:00
Chris Tate 56149fde04 Count each epoll test deadline from its own arm call
The poller test recorded every initial deadline as the same start time
plus the delay, while the poller reads the clock at each arm call; when
arming the 2,000 timers took over 1 ms, the recorded order could differ
from the delivered one by more than the assertion's slack. Each deadline
now counts from the time read just before its scrp_timer_arm call, as
the re-arm loop's do, and a 3 ms pause halfway through arming keeps the
test exercising that case.
2026-10-22 19:56:53 -05:00
Chris Tate b7d355ed13 Stringify live typed records and arrays without a key list (#1897) 2026-10-22 19:52:19 -05:00
Chris Tate d4a7b82dd8 Free arrays whose elements need no release without a teardown
When the last reference to an array dropped, the runtime ran the generic
teardown: scr_rc_destroy's depth bookkeeping, an indirect call to the array
destroy, and a loop over the element slots. Arrays of numbers, booleans and
compact ProgramLifetime references release nothing, and neither do arrays of
other ProgramLifetime instances, whose release does nothing.

- scr_arr_release frees an acyclic array without an ext directly when its
  elements are numbers, booleans or compact references, or its descriptor
  has SCR_ARR_OPS_RELEASE_NOTHING. Every other array keeps the full
  teardown.
- The backend sets SCR_ARR_OPS_RELEASE_NOTHING in the descriptor's flags
  word on 64-bit targets for ProgramLifetime element types that are not
  compact, plain or as the pointer arm of a nullable union, in programs
  without lifetime regions. Registration copies it into a table indexed by
  the descriptor's registry id before the id is published. Descriptors
  without the flag read 0 and keep the full teardown.

Corpus 7401 drops arrays of ProgramLifetime instances, numbers and booleans
through every array operation, with holes and far indices, next to arrays of
ordinary objects, and matches Node.
2026-10-22 19:22:55 -05:00
Chris Tate 3024e57eeb Revert "Enumerate each record in the key order its object literal wrote (#1802)" 2026-10-22 19:02:41 -05:00
Chris Tate 53c43dd140 Merge main 2026-10-22 18:44:10 -05:00
Malte UblandClaude Opus 5.5 481f9a5e31 Stringify live typed records and arrays without a key list
JSON.stringify of a value reached through `unknown` walks native records
and arrays through their live capsules. The protocol walk built a
key-string array for every record and formatted every array index with
snprintf into a fresh string, only to look the member up again by that
key. A JSON API response of a few records spent most of its
serialization time there.

Without a replacer, walk a live record by field index over a snapshot of
its present fields (its keys are the immortal field names) and a live
array by element index, forming an index string only for a toJSON call
that receives it. Members are still read as scr_json_member reads them,
so callbacks that mutate or materialize the container see the same
results. Index keys elsewhere use the integer formatter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:43:54 -07:00
Chris Tate 81891d55f7 Fix boxed record field hooks and moving key writes, and merge main 2026-10-22 18:19:14 -05:00
Chris Tate b955ea3817 Give arrays state bytes only once they hold a hole or undefined (#1827) 2026-10-22 18:09:47 -05:00
Chris Tate 6fe5612e48 Merge main 2026-10-22 18:05:53 -05:00
Chris Tate dc2b238df7 Revert "Dispatch checked-dynamic method calls on method ids (#1851)" 2026-10-22 18:02:51 -05:00
Chris Tate 78c9317a2c Construct builtins through stored constructors and fix function-local class names and dispatch (#1807) 2026-10-22 17:56:42 -05:00
Chris Tate db7bd1111f Find Buffer search candidates with memchr (#1830) 2026-10-22 17:56:21 -05:00
Chris Tate 5372c69459 Pass { signal } to stream operator callbacks and Duplex.from bodies 2026-10-22 17:50:29 -05:00
Chris Tate 98712c364a Merge main 2026-10-22 17:49:58 -05:00
Malte UblandClaude Opus 5.5 a4287e9122 Keep Linux poller timers in a heap behind one timerfd
The epoll poller gave every armed timer its own timerfd. A keep-alive
HTTP server re-arms and cancels the socket idle timer on every request,
which cost six system calls per request (timerfd_create,
timerfd_settime twice, epoll_ctl twice, close) on top of the read and
the send; Node makes two.

Armed keys now live in a deadline min-heap with a pointer hash, and one
timerfd wakes the loop at the earliest deadline. Arming a later
deadline, re-arming and cancelling make no system call; the timerfd is
reprogrammed only when an earlier deadline arrives or when it fires.
The fd table is indexed by fd number instead of scanned. Delivery is
unchanged: each expired timer reports once (one-shot), now in deadline
order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:48:23 -07:00
Chris Tate 5195e2b63a Merge main 2026-10-22 17:19:37 -05:00
Chris Tate b2bb7be0b4 Support the env option of spawnSync (#1825) 2026-10-22 17:00:17 -05:00
Chris Tate 57c10fc767 Build Sets, Maps and weak collections from checked-dynamic iterables (#1856) 2026-10-22 16:53:29 -05:00
Chris Tate b03cb004ad Send an http client request's head and body in one write (#1867) 2026-10-22 16:50:41 -05:00
Chris Tate 74a958408f Compile chalk 5 and hot-shots 8 from built-in shims with callable class instances (#1836) 2026-10-22 16:49:19 -05:00
Chris Tate 36db0613cc Bound the compiler's cyclic garbage to twice its live heap (#1863) 2026-10-22 16:46:32 -05:00
Chris Tate 7a5fdae1bd Send an http client request's head and body in one write
request.end(body) on a request whose head was not sent yet wrote the
head and then the body. On a socket that was already connected (a
reused keep-alive connection), each write is sent at once, so the two
went out as separate segments, and the server could handle the head and
answer before the body arrived. Node sends the head together with the
body. When the client and the server run in one program, the server's
'end' and 'close' for the request and the client's reading of the
response then depended on which socket the poller reported first: corpus
5409-http-held-request-body printed the client's line before the
request's 'end' and 'close' in a macOS dev build.

The head and a body known at end() now go out as one write, as Node
sends them (the server's response already did).
2026-10-22 16:45:59 -05:00
Chris Tate d7717ff1e9 Type empty array literals by their slots, and report package diagnostics accurately (#1799) 2026-10-22 16:40:40 -05:00
Chris Tate ee865bcb0d Support the legacy createCipheriv modes and crypto.checkPrime/generatePrime (#1832) 2026-10-22 16:32:12 -05:00
Chris Tate 48a08dce8e Store arrays of ProgramLifetime instances in 4-byte slots on Linux (#1847) 2026-10-22 16:29:42 -05:00
Chris Tate 5c29845909 Support Readable.fromWeb over web ReadableStreams 2026-10-22 15:23:28 -05:00
Chris Tate f4365b7eea Cache each constant method name's id in a slot
A checked-dynamic method call looked its name up in the method table on
every call: a strlen, a hash and a byte comparison of the name.

The compiler now gives each method-name constant an i32 slot that starts
at -1 and passes it with the call (scr_dyn_invoke_prepared_id, and the
Buffer method entries, which forward it). The first call stores the
name's id in the slot, and later calls read it. Ids stay the runtime's
own, so the slot holds whatever this runtime's table says. Names are
still matched by content on the first call, and the slot is written with
a relaxed atomic store, since every thread writes the same id.

The runtime ABI is now 28.
2026-10-22 15:02:41 -05:00
Chris Tate f5a3faf18d Bound the compiler's cyclic garbage to twice its live heap
The cycle collector widens the growth fraction that schedules full passes
up to 16 times the heap left by the last pass while passes find little.
Lowering a large program leaves hundreds of megabytes of cyclic garbage,
and whether a scheduled pass reclaims it before the build's peak depends
on where the growth rule fires: small changes to the compiler moved the
peak of a tsc-ts build between 1.66 and 2.05 GB.

The native compiler now sets the growth cap to 2 at startup, as
SCR_CYCLE_GROWTH_CAP=2 does, so a full pass runs once the heap has grown
to three times its size after the last one. scr_cyc_set_growth_cap sets
the cap for a program from its own code; the environment variable still
takes precedence, and child processes are not affected.
2026-10-22 14:25:55 -05:00
Chris Tate 83b2b34b6e Inline Map retains and Map.size in release builds
Release builds no longer import runtime bitcode, so program code called scr_map_retain_v for every Map read out of a container and scr_map_size for every size read. A Map retain now uses the emitted increment helper, as string retains do, and Map.size reads the live count through an internal helper.
2026-10-22 14:19:04 -05:00
Chris Tate d83c676705 Store compact values of string-key maps in 4-byte slots on Linux 2026-10-22 14:08:00 -05:00
Chris Tate d8d75dfa18 Support Duplex.from over streams, pairs, promises and async functions 2026-10-22 13:32:25 -05:00
Chris Tate 8f08cbbec5 Build Sets, Maps and weak collections from checked-dynamic iterables
`new Set(x)`, `new Map(x)`, `new WeakSet(x)`, `new WeakMap(x)` and
`Object.fromEntries(x)` now accept any checked-dynamic iterable, as in
Node: a native iterator (`map.keys()`, `set.values()`), a compiled
generator, an object with a Symbol.iterator method, or a Set. Before, a
Set seed that was not an array or a string threw "is not iterable", and a
Map, weak collection or Object.fromEntries seed that was a generator or a
collection other than a Map refused with SC2020.

The runtime drains native iterators, packs arrays, strings and
collections, and steps every other iterable through its iterator
(scr_dyn_seed_values). A program that builds these from an untyped seed
registers the compiled generators' next and return methods with the
runtime, as destructuring rest already did, so a generator seed can be
stepped.

The seed's values are all taken before the first one is added, so when an
entry of a Map seed is not an object the TypeError comes after the
iterable is exhausted; Node closes the iterator at that entry. The
limitations page says so.
2026-10-22 13:09:01 -05:00
Chris Tate b4a3bace60 Read compact array slots in dense loop fast copies, and their flag only on 64-bit targets 2026-10-22 13:08:42 -05:00
Chris Tate 317ec98fa5 Close async generators on early for await exits and iterate untyped native streams 2026-10-22 12:31:21 -05:00
Chris Tate 83d426f9be Support FinalizationRegistry with bookkeeping and no cleanup callbacks
wasm-bindgen output constructs a FinalizationRegistry per exported class
when its module loads, so every wasm-bindgen package stopped at load with
SC2020. `new FinalizationRegistry(cleanup)` now creates a native registry:
- register(target, held, token) and unregister(token) check their
  arguments and answer as Node does, with Node's TypeError messages;
- the unregister tokens are keys of an internal weak map, so the registry
  keeps no token alive;
- the cleanup callback never runs, which ECMAScript allows, so the
  registry keeps neither the callback nor the held values.
The global answers typeof "function", instanceof, Object.prototype.toString
and util.inspect as Node does. Symbol targets and tokens are refused with
SC2020.

`"encodeInto" in encoder` on a TextEncoder, which wasm-bindgen's loaders
test at load, answered false because the encoder is a brand record without
fields for its methods. `in` over such a builtin brand record now answers
from the members the standard library declares, plus Object.prototype's.

The limitations page says that memory a package frees only from a cleanup
callback, such as the WebAssembly memory behind wasm-bindgen wrappers the
program drops without free(), is not reclaimed until the program exits.

Fixes #1678.
2026-10-22 12:14:23 -05:00
Chris Tate 7cce36354f Dispatch checked-dynamic method calls on method ids
A method call on a value of unknown type (untyped JavaScript, `any`)
picked its builtin by comparing the method name with strcmp against each
candidate, in long ladders per receiver kind: a string method could test
dozens of names before it matched.

- The name is now looked up once per call in a generated hash table
  (scr_dyn_methods.h, from scripts/gen-dyn-methods.mjs), and each ladder
  compares the resulting id. The table verifies the name's bytes, so a
  name is matched by its content, as before.
- The worker event methods compare the first character before strcmp.
- bench/dynamic-methods calls string, array, Map, Set, number and own
  methods from untyped JavaScript.
2026-10-22 11:21:10 -05:00
Chris Tate f37dca56f0 Merge main 2026-10-22 11:20:04 -05:00
Chris Tate fd40a8c84a Try each PATH entry with exec when spawning with a replacement env
The PATH lookup for spawns with an `env` option picked the first
executable regular file and stopped there, so a script whose interpreter
is missing ended the search with ENOENT. Node's libuv runs execvp in the
child, which moves on to the next PATH entry when an exec fails with
ENOENT, ENOTDIR or EACCES. Each candidate now runs through posix_spawn in
order with the same rule, and relative entries are resolved by the child
in its cwd.
2026-10-22 11:18:30 -05:00
Chris Tate a6a1eb2b95 Support Duplex.from, compose stages, readable.compose and pipeline function destinations 2026-10-22 10:57:53 -05:00
Chris Tate 4d7de03054 Store arrays of ProgramLifetime instances in 4-byte slots on Linux 2026-10-22 10:46:16 -05:00
Chris Tate ccfca67aec Fix chalk builder field calls and hot-shots send failures and tcp completion 2026-10-22 10:30:56 -05:00
Chris Tate 5bbf2c97b8 Encode posted records from their fields, and queue a message in one block (#1824) 2026-10-22 10:25:40 -05:00
Chris Tate 08081ca1a2 Encode each field of a posted record completely before the next
A typed record encoded from its field hooks queued a plain-object field on
the visit stack and went on to the next field, so getters on later fields
ran before the properties of earlier ones were read. Node's serializer
finishes each field, nested properties included, before the next.

The record path now drains the visit stack down to where it started after
each field, so properties are read in Node's order. Corpus 7139 posts a
record whose fields hold getter-bearing objects and prints the read order.
2026-10-22 10:22:22 -05:00
Chris Tate 3a3610d12e Look up spawn and execFileSync commands on the replacement environment's PATH
spawn and execFileSync with an `env` option searched the parent's PATH
for a command without a '/'. They now resolve it the way spawnSync does,
as Node (libuv) does: on the replacement environment's PATH, or the
default path when it has none, with relative entries read from the cwd
option's directory.
2026-10-22 10:15:10 -05:00
Chris Tate affe386bbf Throw for every object on the right of an arrow instanceof, and give blocked bodies their this
- An arrow function or method on the right of an untyped `instanceof`
  has no prototype. Node throws "Function has non-object prototype
  'undefined' in instanceof check" for any object, but the runtime threw
  only for plain objects, arrays and functions, and answered false for
  typed arrays, native handles, promises and typed references. It now
  answers false only for primitives.
- Coverage lowers the body of a signature-blocked function for its
  diagnostics. A declared `this` parameter now gives that body its
  receiver, as lowerFunction does, so `this.label` reports nothing.
  Before, each use reported SC1080.
2026-10-22 10:09:43 -05:00