Commit Graph
2626 Commits
Author SHA1 Message Date
Chris Tate 6790082742 Keep hole-free prefix and int32 parameter upkeep off hot paths 2026-10-13 09:11:57 -05:00
Chris Tate 9153285118 Probe bypassed collector types and tear down typed children directly 2026-10-13 08:50:06 -05:00
Chris Tate 5a7fc7225b Support stream/consumers arrayBuffer
- Support `arrayBuffer()` from `node:stream/consumers`
- Print ArrayBuffer and SharedArrayBuffer values like Node
- Mark the stream consumers as partially supported in the compatibility data
2026-10-13 08:33:55 -05:00
Chris Tate 725812afea Fuse array callback chains into single loops (#919) 2026-10-13 08:18:50 -05:00
Chris Tate ad3dedb546 Reuse regex literal sites and scan plain patterns directly (#918) 2026-10-13 08:17:34 -05:00
Chris Tate 0760cc240d Fuse array callback chains into single loops 2026-10-13 07:55:53 -05:00
Chris Tate 1aeb04241f Reuse regex literal sites and scan plain patterns and string replacements directly 2026-10-13 07:48:24 -05:00
Chris Tate 35cf7efe55 Make short async calls and awaits cheap 2026-10-13 07:29:48 -05:00
Chris Tate 5df589706b Skip array state bytes below a hole-free prefix 2026-10-13 02:13:46 -05:00
Chris Tate 322c225b66 Store int32-closed number locals as i32 2026-10-13 01:23:19 -05:00
Chris Tate db2948762d Keep typed-array reads unboxed in number compares (#911) 2026-10-12 23:39:34 -05:00
Chris Tate c089d83450 Wrap integer sums that feed bitwise conversions in 32 bits (#910) 2026-10-12 23:12:25 -05:00
Chris Tate 5a5bfc279d Keep typed-array reads unboxed in number compares and in-bounds bindings 2026-10-12 23:05:43 -05:00
Chris Tate 291452a1ca Wrap integer sums that feed bitwise conversions in 32 bits 2026-10-12 21:12:36 -05:00
Chris Tate c6a720c1e0 Update array elements natively and share optional returns across overrides 2026-10-12 20:57:04 -05:00
Chris Tate 55626fb233 Merge pull request #897 from vercel-labs/ctate/run-pr-test-f6809a
Run the PR test shards on Linux
2026-10-12 20:22:19 -05:00
Chris Tate 7274dd1436 List defined archive symbols the same way with every nm 2026-10-12 19:29:41 -05:00
Chris Tate ff799bd3c5 Sort dense arrays without the comparator sort snapshot (#908) 2026-10-12 19:25:54 -05:00
Chris Tate 7735493e87 Keep uint32 ranges for loop accumulators (#900) 2026-10-12 19:17:15 -05:00
Chris Tate 6413f73a3c Share the box on identity union retags (#902) 2026-10-12 19:16:01 -05:00
Chris Tate 15ad4b8bc3 Keep expected aborts fast on Ubuntu runners 2026-10-12 19:00:42 -05:00
Chris Tate b10c0af911 Sort dense arrays without the comparator sort snapshot 2026-10-12 18:01:14 -05:00
Chris Tate 4d19acc44c Devirtualize calls by the classes that are constructed 2026-10-12 17:58:04 -05:00
Chris Tate 31c0530337 Keep integers visible to LLVM and take the ASCII fast paths of text conversion (#898) 2026-10-12 16:21:02 -05:00
Chris Tate 41dd62c8f7 Share the box on identity union retags 2026-10-12 16:12:39 -05:00
Chris Tate a25a5ccc03 Keep uint32 ranges for loop accumulators 2026-10-12 15:34:58 -05:00
Malte Ubl 0086716250 Widen utf16le ASCII runs eight bytes at a time
Buffer writes and conversions to UTF-16LE stored each ASCII byte as two
byte stores; the compiler cannot vectorize the loop because source and
destination may alias. On little-endian targets an ASCII run of eight
bytes is now widened with two 64-bit stores.
2026-10-12 13:28:30 -07:00
Malte Ubl bcad9b3892 Inline the ASCII paths of UTF-16 and byte offset conversion
scr_u16_to_byte_c and scr_byte_to_u16_c run on every native charCodeAt,
substring and search result. Most calls are the identity of an ASCII
string or a hit in the cached ASCII run, but every call paid the prologue
of the full walk. Those two cases are now static inline, and the walk is a
separate function. A forward access up to 4 KiB past the cached run first
extends the run word by word (with 256 bytes of lookahead) instead of
walking from the cursor and re-centring a 128-byte window, which made a
scanner reading a long ASCII stretch of a non-ASCII text walk repeatedly.
2026-10-12 13:28:30 -07:00
Malte Ubl b12b5b98c7 Keep NaN-coded integer reads and their compares as integers
A typed-array read at an unproven index joins the element with NaN for an
invalid index. For an integer element that join was `phi double [sitofp
x, NaN]`, which hides the integer from LLVM, and once `| 0` turned the NaN
arm into 0, InstCombine moved the `sitofp` of the result into the join
(`phi double [sitofp x, 0.0]`) where nothing folds it back: a loop over
`const c = units[i] | 0` converted every element to a double and back,
and compared it as a double.

An integer element now joins as `phi i32 [x, 0]` plus a validity bit and
converts after the join (`select valid, sitofp/uitofp x, NaN`). A declared
local whose every store is a bitwise result, and that a loop compares, also
keeps an i32 view: its stores already carry the integer, and compares of
the view stay integer compares, which also leaves the integer join with
more than one user, so the conversion is not folded into it.
2026-10-12 13:28:29 -07:00
Chris Tate 644722e696 Run the PR test shards on Linux 2026-10-12 15:21:39 -05:00
Chris Tate 93fb2060ee Read class property bags without creating them (#894) 2026-10-12 14:27:12 -05:00
Chris Tate bbf2c34778 process.constrainedMemory() and availableMemory() read the process's own cgroup, like libuv (#893) 2026-10-12 14:21:30 -05:00
Chris Tate cca079d705 Find multi-byte needles without a memchr call per first-byte hit (#892) 2026-10-12 14:20:36 -05:00
Chris Tate 8a3d747b1d Merge main into fix/read-only-class-bags 2026-10-12 13:20:11 -05:00
Chris Tate 8c035be7de Merge main into fix/constrained-memory-libuv 2026-10-12 13:18:23 -05:00
Malte Ubl f9368ce8b4 Compute ToUint32 of large magnitudes without frem
The slow path of ToUint32 (NaN, the infinities and magnitudes above 2^53)
reduced the value with `frem`, which lowers to an `fmod` call. The call is
pure, so LLVM sometimes hoisted it above the range test onto the fast path
(65,865 calls in one tsc-ts parse).

A finite double above 2^53 is its integer mantissa shifted left by
exponent - 1075 >= 1, so its low 32 bits are the shifted mantissa's (none
once the shift reaches 64), negated for a negative value. NaN and the
infinities have the maximum exponent and give 0. The slow path is now
integer arithmetic on the double's bits with no branches.
2026-10-12 11:17:04 -07:00
Chris Tate 8c4bb3277c Merge main into perf/string-search-pairs 2026-10-12 13:16:39 -05:00
Chris Tate 829a326bb7 Merge main into perf/string-maps 2026-10-12 13:14:33 -05:00
Chris Tate 5a1638ce55 Update the node-types fence snapshot for native-only hints 2026-10-12 13:10:43 -05:00
Chris Tate 7e6294ca0f Make scriptc compilation native-only 2026-10-12 12:25:30 -05:00
Malte Ubl d5c31a933c Read class property bags without creating them
A class instance keeps its own dynamic properties in a lazily created bag
(`%dynProperties`). Untyped reads went through the bag accessor that
creates and stores the bag on first use: selecting a method checks the bag
for an own property shadowing it, and the named and computed fallbacks,
`in`/own probes, Reflect.get, for-in and accessor reads all looked their key
up there. So a pure read stored into the object. On a published object the
frozen-object guard rejects that store with "Cannot assign to read only
property '%dynProperties'", where Node reads without error.

Read-only bag accessors now never store. The untyped
%dyn.class.readProperties is rebuilt from the settled bag dispatch without
its lazy stores, and typed read helpers return a prototype-backed bag
instead of storing it. Every read path uses the read-only accessor: key
tests treat an absent bag as having no own keys, and lookups that must see
Object.prototype read from a fresh, unstored empty object. Writes keep the
storing accessor.

Found with tsc-ts at --checkers 8 on vercel/eve: a checker thread read
`host.commonSourceDirectory()` through an untyped interface value whose
object is the published Program.
2026-10-12 08:29:56 -07:00
Chris Tate 043c656bbd Merge main into feat/program-lifetime-publication 2026-10-12 09:38:24 -05:00
Malte Ubl 09118a3c25 Read the process's own cgroup in process.constrainedMemory() like libuv
scr_constrained_memory read /sys/fs/cgroup/memory.max, the root of the cgroup mount, and answered
0 for "max". Node's process.constrainedMemory() is libuv's uv_get_constrained_memory (libuv 1.51
in Node 24.15): the cgroup named by /proc/self/cgroup, the smaller of memory.max and memory.high
(v2) or of memory.limit_in_bytes and memory.soft_limit_in_bytes (v1, falling back to the
controller's root), with "max" and v1's page-aligned LONG_MAX read as UINT64_MAX and an unreadable
file making the answer 0. The two only agreed inside a cgroup namespace; in a nested cgroup or a
systemd scope the native build reported the root's limit (17179869184 in a 16 GB sandbox where Node
answers 18446744073709552000, or 2147483648 under memory.high=2G), so programs that size
themselves by it ran out of memory.

process.availableMemory() follows uv_get_available_memory too: under a constraint no larger than the
machine's memory, the constraint minus the cgroup's current usage; otherwise MemAvailable, then
sysinfo's freeram.

The corpus program prints the exact constraint, so Node and the native build must read the same
cgroup.
2026-10-12 07:22:54 -07:00
Malte Ubl 5201ab9340 Find multi-byte needles without a memchr call per first-byte hit
scr_byte_find called memchr for the needle's first byte and memcmp at
every hit. Path searches such as indexOf('/./') or indexOf('//') hit the
first byte at every separator, so each search paid two library calls per
path segment.

Haystacks of up to 256 bytes now test eight candidate starts per step for
the first two needle bytes with a SWAR zero-byte test (no false negatives;
false positives are rejected by the byte checks) and compare the rest only
at real two-byte matches. Longer haystacks keep memchr but check the
second byte inline before memcmp.

The tsc-ts parser (tRPC, parse only) runs 1.1% fewer instructions; string
search there drops from 111M to 82M instructions.

The new corpus program covers both strategies, every match offset around
the 8-byte steps and the 256-byte cut, near misses, the 0x01 false
positive pattern, and non-ASCII needles and haystacks.
2026-10-12 07:22:54 -07:00
Malte Ubl f04681f0b6 Reject absent map keys through a filter in the map header
Every live entry's hash sets two bits of a new 64-bit ScrMap.filter, so a
lookup whose bits are not both set is absent without loading the bucket
table or the entries. String-keyed lookups in tsc-ts are mostly misses
(Map.get 67% on Excalidraw, has 87%), and the filter rejects 84-88% of
the misses on non-empty maps. Deletions leave their bits; rebuilds,
compaction and clear recompute the filter from the live entries.

Lookups also return the entry index straight from the probe instead of
reading the bucket again. The filter sits after nlive, so the backend's
%ScrMapIx fields for entries and the direct index move by one.
2026-10-12 07:22:52 -07:00
Chris Tate bfbe047cbb Read SCR_COLD as an attribute in the runtime ABI guard 2026-10-12 09:21:23 -05:00
Chris Tate 2391b0ebf3 Merge pull request #869 from vercel-labs/perf/parse-round4
Read charCodeAt at proven integer indices without a double round trip
2026-10-12 08:59:12 -05:00
Chris Tate 1f98312881 Merge pull request #880 from vercel-labs/feat/acyclic-contract
Acyclic contract type
2026-10-12 08:58:49 -05:00
Chris Tate 936e9c5dae Merge main into perf/parse-round4 2026-10-12 08:51:31 -05:00
Chris Tate da309e1655 Identity bitwise forms over proven int32 operands return the operand (#878) 2026-10-12 08:49:35 -05:00