Commit Graph
3704 Commits
Author SHA1 Message Date
Chris Tateandejc3 45d3d02bf0 Compile generic arrow bindings inside functions that use no enclosing frame
A generic arrow or function expression bound inside a function or method
compiles when it uses nothing from the enclosing function's frame: no
`this`, `super`, `arguments` or `new.target`, and no local, parameter or
type parameter of an enclosing function. Its instantiations are module
functions, as for a module-scope binding, and may call the binding itself
and read module-scope bindings. Instances of bindings in functions and
blocks take the declaration's position in their names, so two functions
may declare bindings of the same name. Bindings that use the enclosing
frame keep their SC1090 fence.

Fixes #1624

Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
2026-10-21 01:58:22 -05:00
Chris Tate 7ae0445cb6 Partition programs with multi-line dispatch tables as text (#1626) 2026-10-21 01:11:31 -05:00
Chris Tate 267b77c994 Partition programs with multi-line dispatch tables as text
The emitter writes interface dispatch tables one element per line. The
helper's text partitioner accepted only single-line globals, so any
program with such a table (every partitioned program with class
interfaces) fell back to parsing the whole module before splitting it
with LLVM. The text partitioner now reads a global whose aggregate
initializer opens at the end of its line and closes on a line that holds
only the closing bracket.

Those programs now parse their partitions concurrently and keep the text
partitioner's placement, so an edit to one function changes only that
function's partition again.
2026-10-21 01:08:13 -05:00
Chris Tate f7044b0812 Store number-key map entries in 16 bytes and size the direct index near the largest key (#1614) 2026-10-21 00:36:58 -05:00
Chris Tate 3434aacec4 Pick the iteration step from the key kind for checked collections
A typed number-key Map or Set that is boxed and cast back to
Map<unknown, unknown> or Set<unknown> keeps its own storage, with
16-byte entries. Inline iteration over a checked collection used the
24-byte step, so it tested the wrong words for liveness and read past
the entry array: a Set skipped entries and a Map aborted with "map
iteration index out of range".

The iteration step for a collection with checked keys now reads the
map's key kind and takes the number-key step for SCR_MAP_KEY_F64 and the
24-byte step otherwise. Keys and values are at bytes 0 and 8 in both
layouts, so the slot reads are unchanged. Typed collections keep their
static step, and their IR is unchanged. scr_map.h asserts the key kind's
offset and SCR_MAP_KEY_F64's value.

set-checked-storage.ts iterates a number-key Set and a hashed number-key
Map through their checked views, with deleted entries.
2026-10-21 00:35:00 -05:00
Chris Tate 0bb7411b62 Keep properties JavaScript adds to a child process, and await it as a thenable (#1606) 2026-10-21 00:18:33 -05:00
Chris Tate faa5cb601a Route defineProperty replacements of child methods through the child's box, and size the getter-only error to the name
- The scan for properties JavaScript adds to children now also records `Reflect.defineProperty`, `Object.defineProperty` and `Object.defineProperties` on a child, and element writes (`child["kill"] = ...`). A key that is not a literal sends every member through the child's box. A typed call to a method replaced through `Reflect.defineProperty` then calls the replacement instead of the native method.
- The "Cannot set property x of #<ChildProcess> which has only a getter" message is built at its exact length. Before, a name that overflowed the 160-byte buffer passed the untruncated length to the throw, which read past the buffer.
- Corpus 6611 adds a module that replaces kill through Reflect.defineProperty and writes to a getter-only property with a 300-character name; 6610 and 6611 get their cost files.
2026-10-21 00:15:04 -05:00
Chris Tate 009e73e873 Cache zone offsets and UTC date fields, and return small integers' text without allocating (#1475) 2026-10-21 00:13:25 -05:00
Chris Tate acfa46a919 Decide a Date's zone name at the instant, not from the offset cache
- Date toString and toTimeString now decide the zone name's daylight flag at the instant itself, as V8's LocalTimezone does. The offset cache's segments hold offsets only, as V8's do. Before, a merged segment kept the DST flag of the read that created it, so a zone that left daylight time without changing its offset could print the summer name after the change: Africa/Algiers on 21 October 1977, at +0100 on both sides.
- Corpus date-zone-intervals.ts reads Algiers across that day in three orders; the names match Node.
2026-10-21 00:11:47 -05:00
Chris Tate 2c502c0a27 Answer 511 for an empty event-loop-delay histogram's percentiles (#1618) 2026-10-21 00:10:13 -05:00
Chris Tate 00840aaca6 Merge main 2026-10-21 00:08:02 -05:00
Chris Tate 4004d4c9d0 Answer 511 for an empty event-loop-delay histogram's percentiles
Node creates monitorEventLoopDelay's histogram with a lowest discernible
value of 1000 ns, so an empty histogram's percentile() returns 511, the
highest value its first bucket holds. The runtime returned 0.

6397-event-loop-metrics.ts now skips a sampling window that has no
recorded delta. A delta needs two samples after reset(), and a loop that
wakes late runs only one sample in a window, so on a loaded machine the
program printed an empty window under Node and compiled alike. The
program also prints percentile(50) after each reset.
2026-10-21 00:06:54 -05:00
Chris Tate 0c27def467 Run child accessors on the child, and find child property writes in every JavaScript file
- A getter or setter that JavaScript defines on a child process now runs with the child as `this`, instead of the hidden object that holds the child's added properties, for reads, writes and member calls. A write to a getter-only accessor throws "Cannot set property x of #<ChildProcess> which has only a getter".
- The scan for properties JavaScript writes onto children now covers every JavaScript file of a program that loads child_process, so a module that patches a child it received counts even when it never names child_process.
- The scan's cache and the program's "writes onto children" flag live on the Lowerer instead of a WeakMap and a WeakSet.
- Corpus 6611 covers a module that adds a replaced method, an accessor pair and a getter-only accessor to a child it did not spawn.
2026-10-20 23:41:21 -05:00
Chris Tate 19c88fedb4 Iterate untyped values with for await, and delegate with yield* in async generators (#1564) 2026-10-20 23:25:24 -05:00
Chris Tate a1c55207fc Merge main 2026-10-20 23:21:29 -05:00
Chris Tate 99c63271e9 Cache zone offsets with V8's date-cache algorithm
- The zone-offset cache now follows Node's V8 date cache (DateCache::LocalOffsetInMs): 32 segments of instants with one offset each, the 19-day lookahead, merges of segments with the same offset, a binary search of up to five probes for a change point, and least-recently-used replacement. Local Date getters then answer as Node's do in every order of reads, including the order-dependent answers around Tucumán's two offset changes in June 2004. The previous single-interval cache differed from Node in about half of the seeded orders tried.
- Scattered reads hit a cached segment more often: a getter benchmark over consecutive and scattered instants runs in 169 ms instead of 314 ms (America/New_York; Node 567 ms).
- Corpus date-zone-intervals.ts adds seeded orders of reads around the Tucumán changes and across three years in four zones.
2026-10-20 23:12:10 -05:00
Chris Tate ddc7fe8b0d Store number-key map entries in 16 bytes and size the direct index near the largest key 2026-10-20 23:01:43 -05:00
Chris Tate 4dd62802c8 Bound loop counters compared as i + c with a bound (#1463) 2026-10-20 22:56:44 -05:00
Chris Tate 2783b96100 Round a loop bound to an integer before taking the counter's offset off it
- Narrowing `x + c < b` and its other comparison forms subtracted the offset from the bound in floating point. A fractional bound could then round across an integer: `x + 2147483648 > -(2 ** -60)` gave the minimum -2147483647, which leaves out -2147483648. The bound now rounds to an integer first, and the offset comes off in exact integer arithmetic. Integer bounds give the same facts as before.
- A unit test covers that comparison.
2026-10-20 22:54:32 -05:00
Chris Tate f31b97ee64 Pin the zone-offset cache's answers across a double offset change to Node's
- The zone-offset cache keeps an offset over 19 days when both ends agree, as V8's date cache does. America/Argentina/Tucuman changed its offset twice within 12 days in June 2004, so in Node the answers between the two changes depend on the order of the reads: ascending reads keep the May offset through June 12, and descending reads see the June offset.
- The cache's comment now states that behavior, and corpus date-zone-intervals.ts reads those days in both orders. Its output matches Node for each order.
2026-10-20 22:32:08 -05:00
Chris Tate 43f1c445e8 Keep counters a for loop bounds in integer shadows (#1491) 2026-10-20 22:31:47 -05:00
Chris Tate 6bf550e11f Treat bindings a for loop's condition or update writes as unbounded
- The bounded-loop analysis collected writes from the loop body only. A binding the condition writes (`i < ((source = values[i]), values).length`) then passed as stable, so a cursor copied from it took the source's entry range, and its integer shadow truncated fractional values. The condition and the update are now scanned too, and a binding either one writes is neither a stable bound nor a copy source.
- Corpus 5686 adds a copy whose source the condition writes with fractional values.
2026-10-20 22:29:56 -05:00
Chris Tate 7ef3011f6c Start small arrays with three inline slots (#1613) 2026-10-20 22:06:12 -05:00
Chris Tate b602cdc76e Start small arrays with three inline slots 2026-10-20 22:01:41 -05:00
Chris Tate e5af4def98 Promisify untyped functions at run time (#1502) 2026-10-20 21:54:51 -05:00
Chris Tate 012fdb3fb0 Merge main 2026-10-20 21:52:19 -05:00
Chris Tate 69ef7da7f8 Run death-triggered full collections at the event loop's idle moments (#1522) 2026-10-20 21:48:02 -05:00
Chris Tate 44c586b1ee Run a waiting death pass at the idle moment even after the heap shrank 2026-10-20 21:44:46 -05:00
Chris Tate 0cb4c3ffea Give the deep fs.cp test a per-process scratch directory (#1612) 2026-10-20 21:32:43 -05:00
Chris Tate a4f9b1f66e Give the deep fs.cp test a per-process scratch directory 2026-10-20 21:30:01 -05:00
Chris Tate 24eb054395 Compile the hot-shots StatsD client over UDP (#1568) 2026-10-20 21:29:48 -05:00
Chris Tate 79f98f4684 Regenerate the compatibility files offline, and after merges (#1609) 2026-10-20 21:25:47 -05:00
Chris Tate b811317f26 Keep String method arguments read before another argument's conversion runs
concat, substr and the other typed String and Number extra methods
convert their arguments after evaluating all of them. When an argument's
conversion can run program code (an object's toString or valueOf), a
variable read of the receiver or another argument is now kept in a local
as well, so a conversion that assigns the variable leaves the
already-evaluated argument unchanged, as in Node.
2026-10-20 21:22:29 -05:00
Chris Tate 100e7738a0 Keep generated-file conflicts until regeneration succeeds, and take the side for the merged pin
regen-generated.mjs resolved each conflicted generated file with
`git checkout <ref> --`, which also staged it, so a failing generator left
the conflict resolved with the branch's stale file. It now writes the
chosen side's file to the working tree only and stages it after both
generators succeed.

It also always took MERGE_HEAD's ledger, so merging a branch with an older
Node pin into one with a newer pin regenerated from a ledger for the older
pin and failed. It now takes the side (index stage 2 or 3) whose ledger is
for the merged pin in node-v24.json, and when neither is, stops and says
to run pnpm node-compat, which fetches the pinned docs.
2026-10-20 21:21:14 -05:00
Chris Tate 7dd9fc3b9f Put each limitations sentence on its own line (#1611) 2026-10-20 21:15:00 -05:00
Chris Tate bde7afa980 Compile undici from a built-in implementation (#1425) 2026-10-20 21:14:37 -05:00
Chris Tate 36a5ff72d2 Put each limitations sentence on its own line 2026-10-20 21:12:48 -05:00
Chris Tate 3abf43affd Use the events module and EventEmitter as untyped constructor values (#1544) 2026-10-20 21:12:06 -05:00
Chris Tate d59d8ff6f2 Merge main and count declaration writes to parameters 2026-10-20 21:05:19 -05:00
Chris Tate eb8a148605 Merge main 2026-10-20 21:01:40 -05:00
Chris Tate 115a763df8 Check that the limitations partials keep one sentence per line (#1571) 2026-10-20 20:59:02 -05:00
Chris Tate adf4912d83 Run the partials check before oxlint in pnpm lint
pnpm appends forwarded arguments to the end of a script, so with the
partials check last, `pnpm lint:fix` and `pnpm lint --format=json` passed
their flags to the check instead of oxlint. The check now runs first; it
prints nothing when the partials are split, so oxlint's output, JSON
included, is unchanged.
2026-10-20 20:56:56 -05:00
Chris Tate 53171c282d Read and write dynamic-tier properties through shapes and inline caches (#1396) 2026-10-20 20:52:12 -05:00
Chris Tate 9c24e64ee2 Regenerate the compatibility files offline, and after merges
pnpm node-compat fetched Node's docs on every run, even though only the
classification changes between pins. pnpm node-compat --local now
reclassifies the committed ledger rows from the current sources and
rewrites the ledger and the public artifact, the same files a fetching
run writes while the pin is unchanged. scripts/regen-generated.mjs uses
it after a merge: for each generated file still in conflict it takes the
merged-in branch's version, regenerates the surface manifest and the
compatibility files from the merged sources, and stages them, so these
files never need a hand merge.
2026-10-20 20:50:04 -05:00
Chris Tate 9a802fe635 Leave the per-chapter status counts out of the compatibility files (#1608) 2026-10-20 20:48:27 -05:00
Chris Tate 06e202a126 Leave the per-chapter status counts out of the compatibility files
The ledger and the public artifact held a block of status counts for
every chapter. Nothing reads them (the docs page uses a chapter's slug
and title), and every classification change rewrote its chapter's block,
so concurrent compatibility changes conflicted there. The rows they were
counted from are unchanged.
2026-10-20 20:46:25 -05:00
Chris Tate 5e2e65a805 Build the Node compatibility backlog from the ledger instead of committing it (#1576) 2026-10-20 20:44:36 -05:00
Chris Tate 70e0af8094 Keep shapes off objects that threads share
A published graph is immortal and shared by pointer across threads, but a
shape names a node of one thread's transition tree, which ends with that
thread. A worker that read a published object through a cache left its
shape in the object; after the worker exited, the main thread's reads
followed it into freed memory. Immortal objects now stay unshaped, and
publishing (postMessage's shared graphs and @scriptc/threads publish)
clears a shape the object already has.
2026-10-20 20:44:14 -05:00
Chris Tate 3ef1b9f7be Merge main 2026-10-20 20:40:58 -05:00
Malte UblandClaude Opus 5.5 3b4ddd4c8d Keep object literals' key order through IR rewrites and on shapes typed JSON.parse also builds
The key-order witness pass chose a shape's enumeration table from the
order its object literals write their keys, but two gaps sent literal
values back to the declaration order (JSON.stringify, Object.keys and
inspect then differed from Node, silently):

- Witness notes are keyed by node identity, and frontend passes that run
  before the witness pass (the caught-value dispatch, the class dynamic
  dispatch) rebuild whole function bodies through mapExprChildren, so
  every record literal in such a function became an unnoted construction
  that pins its shape. The mapper now reports rebuilt record literals to
  an observer, and the notes follow them to the rebuilt node.
- A shape a checked conversion (typed JSON.parse) also builds stayed
  pinned to today's table even when the program's own literals build it
  in another order, such as a message sent to a child and parsed back. It
  now takes its literals' order; parsed values in another order keep
  their own through the record-source view, and a shape only checked
  conversions build keeps today's table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 18:34:38 -07:00