Commit Graph
3704 Commits
Author SHA1 Message Date
Chris Tate 97f2875f3f Merge main 2026-10-17 23:21:38 -05:00
Chris Tate aa19f948c5 Settle async generator completions on Node's microtask turns
An async generator that fell off its end, with a return type other than
void, settled its last next() one microtask turn after Node, so for await
loops over it also ended a turn late. The appended implicit return now
marks the completion as settled, like a bare `return;`.

return() at a suspended yield ran the generator's finally blocks before
awaiting the return value. The request now awaits the value first and
resumes the body on the next turn, and the completion that follows settles
without a further turn, also when a finally block yields or awaits first.
An explicit `return x` reached during that unwind still takes its own turn
to await x.

Array.fromAsync accepts typed async generator sources: each next() result
is awaited and its value used as is, a throwing or rejecting mapping
function closes the generator, and a rejected next() rejects the result on
the turn Node rejects it.
2026-10-17 23:19:15 -05:00
Chris Tate 982316d9e6 Merge main 2026-10-17 23:18:09 -05:00
Chris Tate f5c87fb191 Merge main 2026-10-17 23:15:19 -05:00
Chris Tate 9c900862c3 Add corpus costs for recently added programs (#1242) 2026-10-17 22:56:28 -05:00
Chris Tate dd44148528 Merge main 2026-10-17 22:52:07 -05:00
Chris Tate ed817c017d Name the rejected value in the Object prototype TypeError
Object.setPrototypeOf and Object.create threw "Object prototype may only be
an Object or null" without the value Node appends. The message now ends
with the value's string form, as in Node: "...: 7", "...: undefined",
"...: Symbol(tag)".

A prototype or receiver that is a valid object of a kind these functions
do not handle yet (a function or Map prototype for Object.create, a Map
prototype or receiver for Object.setPrototypeOf) threw the same TypeError.
It now throws an Error that says the operation is not supported for that
value.
2026-10-17 22:50:45 -05:00
Chris Tate 2f93c46d2a Merge main 2026-10-17 22:49:18 -05:00
Chris Tate 33cab6f591 Prove generic classes acyclic like other classes (#1246) 2026-10-17 22:48:29 -05:00
Chris Tate 32061e8ba3 Add Float16Array
Float16Array is a native typed-array kind with IEEE 754 binary16 storage.
Stores round the number to nearest-even in one step from the double, as
Node does, including overflow to Infinity, subnormals, -0 and NaN; reads
widen exactly. The runtime converts with integer operations, so no target
needs half-precision hardware or compiler-rt support.

Construction, element reads and writes, ArrayBuffer views, fill, set,
subarray, slice, both sort orders, constructor values and checked-dynamic
storage work as for the other typed arrays. Math.f16round,
DataView.prototype.getFloat16 and setFloat16, and util.types.isFloat16Array
compile.
2026-10-17 22:45:32 -05:00
Chris Tate 43d9b02e8e Merge main 2026-10-17 22:45:12 -05:00
Chris Tate eeffa294e5 Merge main 2026-10-17 22:42:10 -05:00
Chris Tate 82c699b437 Add DisposableStack
new DisposableStack() compiles to a native stack handle. use, adopt, defer,
move, dispose, disposed and [Symbol.dispose] are members of its dynamic
view, and the stack works with `using`. Disposal runs the resources in
reverse order, calls use() resources' dispose methods with the resource as
`this`, and raises a SuppressedError chain when more than one disposal
throws; a single failure is rethrown as thrown. Calls on a disposed stack
throw Node's ReferenceError, and invalid receivers, resources and
callbacks throw Node's TypeErrors.

use() of a class instance registers a function that calls the class's
[Symbol.dispose] method, and returns the instance at its static type; use()
of a union that includes class instances is refused. The stack is
collected with its callbacks when a callback captures the stack.
x instanceof DisposableStack compiles. Handles can expose symbol-keyed
members through a new symbol_get hook.
2026-10-17 22:40:13 -05:00
Chris Tate effe6ed5c2 Merge main 2026-10-17 22:39:14 -05:00
Chris Tate 4fb38f0765 Merge main 2026-10-17 22:35:49 -05:00
Chris Tate bb04c2112e Merge main 2026-10-17 22:32:25 -05:00
Chris Tate e0998f0172 Prove generic classes acyclic like other classes
Each instantiation of a generic class names the generic class as its base,
and the generic class has no constructor function. The cycle analysis
treated a base without a constructor function as one whose constructor let
`this` escape, so every store in an instantiation's constructor counted as
a write after construction. A generic class could not be declared Acyclic,
and its constructor-only references were never treated as immutable.

A program class without a constructor function runs no code at
construction, so it now lets nothing escape. Runtime classes keep the
conservative answer.
2026-10-17 22:25:55 -05:00
Chris Tate f4daa00d5c Merge main 2026-10-17 21:59:33 -05:00
Malte UblandClaude Opus 5.5 43fb612438 Compile CommonJS requires placed after code that can reach them, and JSON requires
A top-level require binding that earlier code could reach was refused
(SC1013). The reachability scan now follows Node's evaluation order:
hoisted functions and classes run only when reachable code names them, a
class statement runs only its heritage, computed keys, decorators and
static initializers, timer and promise callbacks run after the module
body, and publishing a value through module.exports, exports.n,
Object.defineProperty(exports, ...) or F.prototype.m runs nothing until
reachable code reads it back. This admits the export-first layouts of
semver, glob@7, fs.realpath, on-finished, readable-stream and
tsc-emitted CommonJS.

A const or let binding that code can still reach before its require now
compiles with an initialization flag set right after the require and a
module.tdzCheck before the outermost expression of every reference that
can run earlier, so an early read throws Node's ReferenceError before
any argument evaluates. A reference whose lowering leaves no expression
to check, a var binding (Node reads undefined there) and createRequire
bindings keep SC1013.

require() of a JSON document now answers one parsed object per document,
created in the requiring module's %init prelude unless another module
already created it, so repeated requires return the same object and
mutations are visible to every requirer. Require bindings of JSON
documents are ordinary storage, so destructuring (nested patterns,
defaults, missing keys) copies at the require as in Node.

Nested destructuring of a builtin require binds the members of the
sub-module the key names (const { types: { isPromise } } =
require('util')).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:58:21 -07:00
Chris Tate 0d300f22eb Shift the runtime in bench:runtime layouts (#1245) 2026-10-17 21:56:05 -05:00
Chris Tate 3d2c834125 Add corpus costs for the mock programs
The corpus programs 5810 to 5814 had no measured cost, which put the
measured share of the corpus below shard.test.ts's 0.9 floor. Their
plain and SCRIPTC_SAN=1 costs are cold local timings scaled to the CI
runner by programs with recorded costs: 1440, 1441, 1442 and 1463, and
5133 for 5814's sanitized run, which links the AbortSignal runtime.
2026-10-17 21:55:00 -05:00
Chris Tate 84b74c0269 Keep declared methods of function-local classes after a computed-key write
observedPrototypeMethod skipped classPrototypeData for function-local
classes, which also marks such a class as needing prototype data, so its
instances got no prototype behind their property bag. A function-local
class value's prototype holds only methods the program replaced there, so
reads of a marked method name on its instances now check for the
property, own or inherited, and otherwise use the declared method, as for
generic class instances. The prototype data is built again, so a method
replaced on the class's prototype stays visible.
2026-10-17 21:50:39 -05:00
Malte UblandClaude Opus 5.5 1e5079a23a Format the array length loop changes and record their order baselines
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:44:08 -07:00
Malte UblandClaude Opus 5.5 cf872eaba5 Convert only int32 loop counters from their low half
A uint32 counter converted through `uitofp i32` folds an inlined callee's
index conversion, but the zero extension it adds on x86-64 slowed loops
that only convert the counter for a cold fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:43:56 -07:00
Malte UblandClaude Opus 5.5 8c8ee28413 Compile bodies with thousands of capture boxes unoptimized
Every box pointer of a captured binding is a value live from its
binding to its last use. A factory whose methods share thousands of
bindings hands LLVM thousands of such ranges, each crossing every call
in between, in the factory and in each method that receives them, and
the inliner, instruction selection and register allocation grow
quadratically: Bun's large-environment AOT program (8,701 captured
bindings) spent about three minutes in native code generation.

A function holding at least 512 capture boxes (the self-hosted compiler's
largest holds under 100) is now emitted `noinline optnone`, and its box
slots are loaded and stored volatile so no IR pass promotes them, which
the native helper's pipeline would otherwise still do for optnone
bodies. Each use reloads its box pointer from the frame. Boxes,
environments and semantics are unchanged; only those bodies' machine
code is unoptimized.

large-environment.ts now compiles in 16 s instead of 227 s (peak RSS
543 MB instead of 777 MB) and runs in the same 0.18 s. Corpus 5973 pins
such a factory's behavior (shared mutation, per-iteration closures, a
nested closure, a forward-captured read in its TDZ), and a harness test
bounds compile-time growth between 500 and 4,000 bindings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:43:03 -07:00
Malte UblandClaude Opus 5.5 eb0d09fb81 Keep capture analyses linear in the size of a closure environment
Three analyses did work proportional to the whole function once per
captured binding, so a factory with thousands of bindings shared by its
methods spent most of its compile time in them (48 s of LLVM emission
at 8,000 bindings):

- LazyCaptures walked the body again for every capture it checked and
  rebuilt a locals map for every capture of every creation site. Each
  function's rebound bindings are now collected in one walk and the
  unchanged-parameter sets are memoized.
- int32-slots walked the declaring function once per box to find its
  initializer and scanned capture lists per member. Both are now
  per-function sets built on first use.
- CaptureFlow scanned all capture edges, capture lists and entry copies
  per binding and searched statement lists per hoisted or TDZ binding.
  Edges are grouped by source, captures, parameters and entry copies are
  indexed, and hoisted and TDZ initializers are found in one pass.

The results are unchanged: the candidate compiler emits byte-identical
LLVM for the self-hosted compiler's source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:40:45 -07:00
Malte UblandClaude Opus 5.5 e79380ce17 Name the runtime package in npm refusals and add transitive --npm-static
- A package typed only by an @types twin was judged under the twin's name
  and refused with "its import resolves to '@types/x/index.d.ts', not to
  runtime JavaScript". Selection now resolves the runtime package, and the
  refusal names it with the --npm-static option that compiles it anyway.
- Refusals the declaration and shipped-JS heuristics make carry the same
  remedy as a diagnostic hint; a dependency of a named package suggests
  the transitive form.
- `--npm-static=pkg...` (and `"pkg..."` in a library profile) selects the
  package and every package its files import, nested require() calls
  included, without the eligibility heuristics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:39:21 -07:00
Malte UblandClaude Opus 5.5 d1639298ff Erase only import type edges, following Node's type stripping
Node runs TypeScript with verbatimModuleSyntax semantics: `import type` and
`export type … from` vanish, while `import { type A }` and
`export { type A } from` keep an empty clause and still load their module.

- Package selection skipped nothing: `import type { X } from "type-fest"`
  selected type-fest and failed with SC2013, and type imports pulled whole
  packages into the compile. Erased edges no longer select packages.
- Files reached only through erased edges are no longer program modules,
  so their imports (a `node:vm` import, say) no longer fence the build.
- Inline type-only clauses are real module edges again (their modules'
  side effects ran under Node but not in the binary); a cycle closed by
  one binds nothing and is admitted like a side-effect import.
- Interfaces, type aliases and type literals from an installed package's
  declarations map structurally, so values the program builds with a
  type-only package's types compile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:39:16 -07:00
Chris Tate de4e10d542 Shift the runtime in bench:runtime layouts
The --layouts padding object was linked directly before the program
object. The runtime is linked before the program object, so every layout
moved only the program's code and left each runtime function at the same
address. A change to the runtime's code size then moved its functions in
all layouts at once, and the verdicts could not tell that placement
change from a real one.

The padding object now goes before every other input on the link line,
so each layout shifts the runtime and the program code.
2026-10-17 21:34:52 -05:00
Malte UblandClaude Opus 5.5 611e814200 Doom unreferenced cycle components without a scan walk
A collector pass used to decide once for all its candidates: if any gray
object was still externally referenced after markGray, scan walked every
candidate's subgraph again, including the dead structures. A pass almost
always has one live structure in it (the one under construction when the
trigger fires), so most passes paid a second walk over their garbage.

markGray now walks each candidate that no earlier walk reached as its own
component and keeps the externally referenced count per component, using
the header's buffer word as the component number (gray objects are never
buffered during a pass). A component that ends its walk with no externally
referenced member is garbage: every reference to its members came from
inside it, and no later walk can add one. It is doomed at once, and scan
runs only from the candidates of referenced components. Limited passes,
whose untaken candidates keep their buffer positions, keep the single
whole-pass decision. The freed set of every pass is unchanged.

The fast path also stops re-reading the dead set: doomed entries skip
settle's filtering loop, the level statistics are counted in the teardown
loop that reads the type anyway, and the gray-list push is inline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:30:56 -07:00
Chris Tate f1f7302179 Monomorphize destructured options parameters of package functions (#1243) 2026-10-17 21:13:38 -05:00
Chris Tate e72ee037ca Give socket 'data' listeners strings after setEncoding (#1239) 2026-10-17 21:04:33 -05:00
Malte UblandClaude Opus 5.5 39d2a05f88 Renumber the array length loop corpus programs past main's numbers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:03:50 -07:00
Chris Tate aad9b4f140 Merge main 2026-10-17 21:01:15 -05:00
Chris Tate 8d8b96fefa Merge main 2026-10-17 20:59:05 -05:00
Chris Tate a5c7c09ad3 Add mock.timers to node:test 2026-10-17 20:55:17 -05:00
Chris Tate f291f1c275 Monomorphize destructured options parameters of package functions
A package function that destructures its options object, such as
makeClass({ Base, name }), now specializes per call site like positional
parameters: each destructured name binds the static type of the matching
property, so a class passed as { Base } keeps its static type and
class extends Base builds a constructible subclass.

Fixes #938
Co-authored-by: phall <25888173+phall1@users.noreply.github.com>
2026-10-17 20:50:59 -05:00
Chris Tate 694d67724f Add corpus costs for recently added programs
The shard balance test reads measured costs for corpus programs. Add
plain and sanitized costs for 5950, 5951, 5955 and 5956, timed locally
and scaled to the CI runners' existing entries.
2026-10-17 20:47:11 -05:00
Chris Tate 69a36083fc Record corpus costs for the net programs 5970 to 5972
The shard balance check needs measured costs for at least 90% of the
corpus programs. Add plain and sanitized costs for 5970, 5971 and 5972,
timed locally and scaled to the CI runner by comparable net programs.
2026-10-17 20:46:43 -05:00
Chris Tate b68f078fbe Add corpus costs for the missing-key programs
The shard balance test needs measured costs for the new corpus programs
5952-5954 (plain and sanitized), timed locally and scaled to the CI
runners' existing entries.

Fixes #964
Co-authored-by: Quim T. <26749475+espetro@users.noreply.github.com>
2026-10-17 20:16:35 -05:00
Chris Tate dfd19cbea6 Give socket 'data' listeners strings after setEncoding
After socket.setEncoding("utf8"), Node passes strings to 'data'
listeners, while @types/node types the chunk as a Buffer. An inline
listener whose chunk is typed Buffer (annotated or inferred) received a
Buffer. When a program can call setEncoding on a socket, such listeners
now take the chunk as a checked dynamic value, so console.log, typeof,
string methods and concatenation behave as in Node, and Buffer-only
methods throw Node's TypeError. Programs without setEncoding on a socket
compile to the same IR as before.
2026-10-17 20:11:58 -05:00
Chris Tate 24df6bb0cd Mark socket and stream chunks as Buffers (#1238) 2026-10-17 20:06:16 -05:00
Chris Tate 39ef18b2fb Tunnel fetch through env proxies with CONNECT (#1237) 2026-10-17 19:59:21 -05:00
Chris Tate f87b6d7528 Mark socket and stream chunks as Buffers
Chunks from net sockets (including socket.read()), http messages and
upgrade heads, child_process output, dgram messages, http2 streams and
TLS sessions were byte arrays without the Buffer brand. String
conversion of them joined the byte values ("112,105,110,103") instead
of decoding UTF-8, so `body += chunk` and template literals differed
from Node. They are now Buffers, as in Node.

Closes #903
Co-authored-by: João Carlos Magalhães de Castro <88864312+JohnC0de@users.noreply.github.com>
2026-10-17 19:42:11 -05:00
Chris Tate 6d79399862 Tunnel fetch through env proxies with CONNECT
Fixes #1185
Co-authored-by: Andrey Kiselev <35863331+TheAsda@users.noreply.github.com>
2026-10-17 19:38:19 -05:00
Chris Tate 2b6f15c7fa Answer undefined for missing Record and index-signature keys
A keyed or dot read of an index signature that misses now answers
undefined, as in Node, instead of aborting with "record has no key".
The read carries an explicit optional value through the same analysis
that already widens bindings, parameters, returns and fields for missing
array elements; TypeScript's static types are unchanged. Numeric reads
whose consumers treat undefined like NaN lower to plain doubles, and
nested reads and writes through a missing entry throw Node's TypeError.

Fixes #964
Co-authored-by: Quim T. <26749475+espetro@users.noreply.github.com>
2026-10-17 19:35:34 -05:00
Chris Tate b1e22da06d Allow overrides that narrow a union return type (#1235) 2026-10-17 19:34:12 -05:00
Chris Tate 52ddc1e4c6 Call generic members through receivers that are not statically known (#1231) 2026-10-17 19:31:28 -05:00
Chris Tate 2e0f44c8b7 Call valueOf and Symbol.toPrimitive on values held as any (#1234) 2026-10-17 19:24:42 -05:00
Chris Tate 2e7e825e38 Widen ASCII to UTF-16 sixteen bytes per step in Buffer.write (#1233) 2026-10-17 19:21:32 -05:00