Commit Graph
1784 Commits
Author SHA1 Message Date
Chris Tate f716dec756 Convert Buffers to and from latin1 and hex text a word at a time
- toString("latin1"): counts the high bytes eight at a time, copies
  words of ASCII bytes, and writes other bytes from a table of their
  UTF-8 pairs without branches. ASCII-only results are marked ASCII, and
  other results record their UTF-16 length (one unit per byte), so
  `.length` does not walk the string.
- Buffer.from(text, "latin1" | "ascii" | "binary"): copies text known to
  be ASCII, copies words of ASCII bytes, and decodes eight bytes of one-
  and two-byte sequences in one word, storing each output byte without
  branches. Longer sequences take the previous path.
- Buffer.from(text, "hex"): reads digit values from a table.
- toString("hex") and toString("base64"/"base64url") mark their results
  ASCII, so a later decode, length or index read skips the scan for
  non-ASCII bytes.
2026-10-24 23:26:01 -05:00
Chris Tate be878b4eb9 Freeze class instances held in untyped values (#2061) 2026-10-24 22:43:12 -05:00
Chris Tate 0fbb47382d Read elements of native arrays passed to untyped code by index (#2171) 2026-10-24 22:32:09 -05:00
Chris Tate b08efe27e5 Complete consecutive synchronous writes in one afterWrite step (#2169) 2026-10-24 22:24:33 -05:00
Chris Tate 4241ca533f Read elements of native arrays passed to untyped code by index
A typed array such as a number[] reaches untyped code as a capsule of the
native array. A keyed read `xs[i]` there missed the inline cache, which
answers only dyn arrays, and went to the member helper, which converted
the index to a string; the capsule's key lookup then parsed the string
back into a number for every element.

Keyed sites now read an element of a native number[] capsule in place
when the index is an integer below the array's hole-free prefix, and
`length` sites read the native array's length. Other capsule elements
(holes, out-of-range and fractional indices, arrays of other element
types) are answered by the keyed probe through scr_dyn_index_get without
forming the key, or by the helper as before.
2026-10-24 21:59:28 -05:00
Chris Tate d6c8b0f260 Close the afterWrite batch whenever an afterWrite tick runs
Node's afterWriteTick sets afterWriteTickInfo to null before it runs the
callbacks, whichever tick it is. The runtime cleared the queued batch
only when the running tick was that batch, so a write made from an
earlier tick's callback joined a later queued tick with the same
callback and completed ahead of nextTicks queued between them. Any
afterWrite tick now closes the batch, and such a write queues its own.
2026-10-24 21:52:18 -05:00
Chris Tate 7b946ff610 Read Math, JSON and Reflect as real objects in JavaScript (#2006) 2026-10-24 21:50:17 -05:00
Chris Tate 492d1558be Complete consecutive synchronous writes in one afterWrite step
Node's onwrite queues one afterWrite nextTick for a synchronous write
completion and lets later synchronous completions with the same callback
join it (afterWriteTickInfo.count); writes without a callback share one.
The step then checks 'drain' once, calls the callback once per write and
runs finishMaybe once. The runtime queued a stream tick and a nextTick
entry per write, which ran the callbacks of later writes after nextTicks
queued between them. The queued afterWrite tick now counts the writes
that join it, as Node's does.
2026-10-24 21:22:54 -05:00
Chris Tate 8f6f8929dd Merge main 2026-10-24 21:15:49 -05:00
Chris Tate 90f0dd3dc5 Merge main 2026-10-24 21:09:40 -05:00
Chris Tate 0eb7cc12e5 Reuse symbol storage keys and checked-dynamic symbol boxes
Every read or write of a symbol-keyed property on a checked-dynamic value
formatted the symbol's address into a new string to find its storage key,
and every conversion of a symbol to a checked value allocated a new box.
Programs that key hot properties by symbols, such as Effect's type ids
checked on each combinator, spent a large share of their time there.

The storage key is a function of the address, so a 256-entry per-thread
cache by address now returns the existing string, released at exit. A
symbol also keeps a weak pointer to its live box: conversions share it,
and the box clears the pointer when it is freed.
2026-10-24 20:51:37 -05:00
Chris Tate 03af6fb9ff Iterate classes with object-literal iterators and close them on early exits (#1860) 2026-10-24 20:30:36 -05:00
Chris Tate f1bc74f934 Give the module namespace handle its own tag (#2161) 2026-10-24 20:29:29 -05:00
Chris Tate 304612b7e2 Inline destructured literal results and read ASCII slice ranges inline (#2151) 2026-10-24 20:11:44 -05:00
Chris Tate c091adc0e4 Build symbol-keyed fields into frozen class views whatever their attributes
A JavaScript class whose instances track which fields they have copies
the instance's property bag into its view first, attributes included,
and then wrote each symbol-keyed field with an ordinary assignment. Once
the instance was frozen, its symbol entry was read-only and the copy
non-extensible, so any later reflection that rebuilt the view
(Object.isFrozen, a property read) threw "Cannot assign to a read only
symbol property".

String-keyed fields already use scr_dyn_obj_set_field, which replaces a
field's value whatever the attributes copied into the view. The new
scr_dyn_symbol_field_set does the same for symbol-keyed fields, and the
view uses it when it copies the bag or carries integrity attributes.
2026-10-24 20:09:28 -05:00
Chris Tate 50421e1cbd Merge main 2026-10-24 20:05:11 -05:00
Chris Tate 53bddc6ed7 Give the module namespace handle its own tag 2026-10-24 18:46:36 -05:00
Chris Tate 9e55835014 Compile more npm and CommonJS JavaScript dependencies statically (#2122) 2026-10-24 18:30:56 -05:00
Chris Tate 6997d8b211 Allocate stream ticks from the small-block allocator, and emit stream events by immortal names (#2152) 2026-10-24 18:22:03 -05:00
Chris Tate 29e5bacb9c Pump untyped iterable pipeline sources (#2111) 2026-10-24 18:03:36 -05:00
Chris Tate e25d871a34 Allocate stream ticks from the small-block allocator, and emit stream events by immortal names
Each deferred stream step (afterWrite, emitReadable, resume and the
others) queued a ScrStreamTick and a process.nextTick entry, both from
libc calloc and free. They now come from the runtime's small-block
allocator, as do the nextTick entries scheduled through
scr_next_tick and scr_process_write_callback.

The stream unit emitted each event with a newly allocated name string.
The names it emits ("data", "end", "drain", "finish" and the others)
are now immortal static strings, so an emit allocates no name.
2026-10-24 17:45:59 -05:00
Chris Tate 43ff8a1122 Inline destructured literal results and read ASCII slice ranges inline
- A function whose whole body is `return [a, b]` or `return { a, b }`,
  with number, boolean or string fields, is now inlined where its result
  is only destructured (`const [head, tail] = split(text, at)`). The
  parameters are declared from the arguments, then each field straight
  into its destructured binding, in the literal's order; an unread field
  still evaluates into a binding of its own. No record is built, and a
  slice field read only through its length or char codes becomes a
  scalar slice that copies nothing. Arguments may own their value, but
  as for numeric records nothing inside an argument may hold a
  temporary of its own.
- A scalar slice of a proven-ASCII string computes its range inline:
  the bounds clamp over the byte length exactly as scr_str_slice_range
  does, and no boundary can split a surrogate pair. Other strings keep
  the runtime call, which gains the same ASCII fast path.
- value-records' tuples section, `[head, tail] = split(...)` read only
  through lengths, goes from two heap slices per step to none.
2026-10-24 17:35:25 -05:00
Chris Tate aa9664bf8a Propagate exceptions by unwinding from directly called functions on Linux (#2115) 2026-10-24 17:15:36 -05:00
Chris Tate bc9fdb9c08 Answer stream, StringDecoder and handle-object members in untyped code as Node does (#2105) 2026-10-24 17:11:01 -05:00
Chris Tate 8d9705e22d Run CommonJS top-level return, untyped assert forms, and untyped instanceof Object as Node does (#2101) 2026-10-24 17:01:39 -05:00
Chris Tate c37963b8aa Let objects inherit from JavaScript class prototypes (#2078) 2026-10-24 16:58:49 -05:00
Chris Tate 54cb7af31b Settle BigInt quotients from the top limbs, and vectorize limb shifts (#2144) 2026-10-24 16:50:56 -05:00
Chris Tate 12e16cdb32 Find weak collection entries through hashed indexes (#2004) 2026-10-24 16:40:37 -05:00
Chris Tate e22cdf358b Decode an untyped stream unshift's string chunk with its encoding
`s.unshift("e9", "hex")` on a native stream held as an untyped value
dropped the encoding argument and put the string's UTF-8 bytes at the
front. A byte-mode stream now takes a string chunk with an encoding as the
bytes Buffer.from(chunk, encoding) holds, as Node's
readableAddChunkUnshift converts it, and an unknown encoding throws
ERR_UNKNOWN_ENCODING. Object-mode streams keep the string as the entry.
2026-10-24 16:28:43 -05:00
Chris Tate e00d3fd911 Check comparison operands and walk instanceof chains as Node does
- The assert comparison methods read as function values (equal,
  strictEqual, deepEqual, partialDeepStrictEqual and their negations)
  throw Node's ERR_MISSING_ARGS TypeError, `The "actual" and "expected"
  arguments must be specified`, when called with fewer than two
  arguments, where `[assert.equal][0]()` passed.
- Untyped `instanceof Object` starts at the value's prototype, so
  Object.prototype itself, whose prototype is null, is not an Object.
- Untyped `instanceof Function` follows the whole prototype chain through
  objects and functions: an object whose prototype is a function, or a
  function whose prototype was set to such an object, is a Function.
2026-10-24 16:22:03 -05:00
Chris Tate 6a8ba89d9f Make Dates mutable objects with Node's setters and conversions (#2072) 2026-10-24 16:21:06 -05:00
Chris Tate 190fb8d58e Mix weak-collection hashes in 64 bits on 32-bit targets
uintptr_t is 32 bits on wasm32, where the weak-collection hashes shifted it by 33 and 56 bits. The hashes now widen keys, owners and kinds to uint64_t before mixing; 64-bit targets compute the same values as before.
2026-10-24 16:01:33 -05:00
Chris Tate d4fcd787a8 Reference the unwinder weakly from the runtime's raise
scr_unwind.c is linked into every Linux executable, and its references
to _Unwind_ForcedUnwind and _Unwind_GetCFA pulled the unwinder out of
libgcc_eh (or Zig's libunwind) even for programs that never raise, which
grew a static hello world past its size ceiling. The references are now
weak: programs that raise reference the personality routine from their
landing pads, and its unit links the unwinder in.
2026-10-24 15:38:37 -05:00
Chris Tate ab341be202 Settle BigInt quotients from the top limbs, and vectorize limb shifts
A quotient-only BigInt division with a one-limb quotient returns Knuth's
estimate without a pass over the operands when the top limbs prove it:
with rhat * B + u0 - qhat * d0 at least qhat, a - qhat * b stays positive
whatever the lower limbs hold, since they subtract less than
qhat * B^(n-2). Otherwise one pass settles the estimate as before.

The add and subtract loops process eight limbs a pass, which rebuilds
the carry half as often. Left and right shifts, including the operand
normalization of the general division, form each limb from two source
limbs in a restrict-qualified helper with no dependency between limbs,
so the loops vectorize.
2026-10-24 15:19:33 -05:00
Chris Tate ea495056d8 Answer 'in' on native Node handles from their declared members (#1949) 2026-10-24 14:51:03 -05:00
Chris Tate 3896957230 Report JSON.parse syntax errors with V8's messages and positions (#2129) 2026-10-24 13:34:12 -05:00
Chris Tate c244ddc4c5 Answer 'send', 'disconnect' and 'channel' in a child process from its IPC channel
A child process has these members only when it was created with an IPC
channel (fork, or an "ipc" stdio entry), and keeps them after the channel
closes and after the child exits. The 'in' test on native handles answered
true for every declared member, so a child spawned without IPC reported
them and entered IPC-only branches. The three names now answer from the
runtime: scr_child_ipc_present reports whether the child was created with
a channel.

Corpus 8290 tests a forked and a spawned child before and after
disconnect and exit.
2026-10-24 13:18:19 -05:00
Chris Tate 4f0975cbbe Compile more npm and CommonJS JavaScript dependencies statically 2026-10-24 12:01:26 -05:00
Chris Tate ff70c33e38 Divide one-limb BigInt quotients in one pass and chain limb carries
Dividing BigInts of the same limb count, where the quotient is one limb,
takes Knuth's quotient estimate from the top limbs as they read
normalized and settles it with one pass of a - q * b over the operands,
instead of copying both operands shifted and running algorithm D. The
remainder, when wanted, is that pass's result.

The limb loops for add, subtract, multiply by a limb, and multiply-add
and multiply-subtract by a limb process four limbs a pass, with the
carries in add-with-carry chains (__builtin_addcll and __builtin_subcll
where the compiler has them).
2026-10-24 11:59:33 -05:00
Malte UblandClaude Opus 5.5 d037c66e8c Report JSON.parse syntax errors with V8's messages and positions
JSON.parse's SyntaxError messages were shaped like V8's but differed from
Node's in several ways:
- Every positional message now ends with V8's "(line L column C)", where
  \n, \r and \r\n end lines.
- Positions are UTF-16 offsets, as in Node, not byte offsets.
- An object or array cut off after '{', a property name, a value or an
  array element reports the token V8 expected ("Expected property name or
  '}'", "Expected ':' after property name", "Expected ',' or '}' after
  property value", "Expected ',' or ']' after array element") instead of
  "Unexpected end of JSON input".
- After a comma in an object the message is "Expected double-quoted
  property name".
- An unterminated string reports the end of the input, and a truncated
  \u escape is a "Bad Unicode escape" there.
- A leading zero followed by a digit is an "Unexpected number".
- A misspelled literal names the first differing character, or the end of
  the input.
- An unexpected character is named with the source as V8 does: whole when
  it is under 21 units, otherwise ten units of context on each side; the
  sources "NaN", "Infinity", "undefined" and "[object Object]" name only
  themselves; a quote or a number there is an "Unexpected string" or
  "Unexpected number".

The new corpus program 7993-json-parse-error-messages pins 76 inputs
against Node, including non-ASCII text before the error, line breaks, and
long sources with split surrogate pairs at the context edges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 09:55:05 -07:00
Chris Tate 2967a1b8c0 Propagate exceptions by unwinding from directly called functions on Linux
Emitted functions report an exception by returning with the thread's
pending flag set, and every caller tests the flag after each call that
may throw. On Linux executables (glibc and musl), functions reached only
by direct calls now propagate exceptions by unwinding the native stack
instead:

- unwindAbiFunctions selects the may-throw functions that are not async
  or generators, constructors, virtually called or overridden methods,
  closure targets, the entry, library exports, module initializers or
  callees of coroutine bodies.
- A native unwind costs about 2 µs a raise and 0.1 µs a frame, so functions that
  exceptions are likely to cross keep the flag: those with a `throw` of
  their own, those up to three calls above one, and recursive functions,
  counting calls through methods, closures and callbacks.
- Their propagating exits call scr_unwind_raise (scr_unwind.c), a forced
  unwind through _Unwind_ForcedUnwind with the exception still in the
  active cell.
- Direct calls to them are `invoke`s without a pending check. The
  landing pad runs the frame's usual unwind path: it releases the frame's
  values and then enters the enclosing try handler or resumes the unwind.
- Remaining plain calls to them (wrappers, adapters, runtime-facing entry
  points) become invokes whose landing pad continues on the pending flag,
  or resumes when the wrapper is itself invoked. Every other call is
  marked `nounwind`, so inlining a raising function at an invoke keeps its
  calls plain.
- When emitted text breaks these rules (a raising function's address is
  taken, or an invoked function returns with the flag pending), the
  module is emitted again with those functions on the pending flag.

Emitted code does not declare scr_unwind_raise `noreturn`, so sanitized
builds do not run the sanitizer's no-return hook, which needs stack
bounds that fiber stacks do not report. Instead, scr_unwind_raise clears
the shadow of the frames it unwinds.

Executables link a static unwinder: libgcc_eh with the host toolchain and
the glibc runtime packs, and Zig's libunwind with Zig and the musl packs.
macOS, Windows, Android, WASI, libraries and debug builds keep the
pending flag everywhere and emit the same code as before.

The throw-chains runtime workload measures exceptions caught 2, 5 and 20
recursive calls above the throw, through direct calls and a callback,
and a parser that backtracks by throwing.
2026-10-24 11:03:34 -05:00
Chris Tate 53baad4ebe Pump untyped iterable pipeline sources
A pipeline whose first stage is a checked-dynamic value now checks it at
run time, as Node's stage ladder does: a stream is piped as before, and a
string or a value with a Symbol.asyncIterator or Symbol.iterator method is
pumped into the next stage through an async generator over its iterator.
A typed Set, Map, generator or async generator object as the first stage
takes the same path. This covers the callback and stream/promises forms,
with or without function stages and pipeline options.

A write whose callback fails synchronously no longer sets needDrain, so no
'drain' follows the error and a pumped source stops at the failed write,
as in Node.

RUNTIME_ABI_VERSION goes from 37 to 42 for scr_stream_dyn_iterable_source.
2026-10-24 10:12:06 -05:00
Chris Tate aa1c26845c Cache untyped property access in programs that declare classes (reland) (#2102) 2026-10-24 09:55:54 -05:00
Chris Tate 905a290817 Copy typed array sets of the same element kind on shared memory as bytes (#2094) 2026-10-24 09:07:57 -05:00
Chris Tate f2e710d65f Answer stream, StringDecoder and handle-object members in untyped code as Node does 2026-10-24 09:05:16 -05:00
Chris Tate 35a1576ba8 Merge main 2026-10-24 08:46:27 -05:00
Chris Tate 427e383b5e Cache untyped property access in programs that declare classes (reland)
Reapplies #2017, which #2038 reverted after effect's observability-otlp
fixture crashed in plain builds. The crash was the candidate-buffer
corruption that the previous commit fixes: the cached property sites
change when the collector runs, and with enough environment variables a
pass buffered a string box that was later pooled while still buffered.
2026-10-24 08:22:30 -05:00
Chris Tate d9f1457cd5 Run CommonJS top-level return, untyped assert forms, and untyped instanceof Object as Node does 2026-10-24 08:07:52 -05:00
Chris Tate 7ba3827bd4 Merge main 2026-10-24 06:54:25 -05:00
Chris Tate 102c86b6d2 Merge main 2026-10-24 06:50:08 -05:00