Commit Graph
4931 Commits
Author SHA1 Message Date
Malte Ubl caee8dfef2 Merge the restacked base into ni/i2-handles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	packages/compiler/src/executable/executable-cache.ts
#	packages/compiler/src/index.ts
2026-10-27 05:41:01 -07:00
Malte UblandClaude Opus 5.5 02a322e712 Merge ni/13-libnode-packs (with ni/1-libnode-loader)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 05:40:40 -07:00
Malte Ubl f9419b5d47 Merge remote-tracking branch 'origin/ni/1-libnode-loader' into HEAD
# Conflicts:
#	packages/compiler/src/index.ts
2026-10-27 05:40:18 -07:00
Chris Tate f4a4cf8652 Merge main 2026-10-27 04:32:00 -05:00
Chris Tate 549bec79f8 Name the source file in empty split codegen parts (#2397) 2026-10-27 04:12:57 -05:00
Chris Tate 7bd12ea89f Add a representation report and a heap census for ranking value-layout work (#2390) 2026-10-27 04:01:41 -05:00
Chris Tate 7c770d0887 Name the source file in empty split codegen parts
When --split codegen keeps the module whole, the extra outputs are new
empty modules, and their source file name defaulted to the module
identifier: the path of the private copy of the LLVM input, which has a
random name. That name reached each part's STT_FILE symbol, so two
release builds of one program differed in .strtab. The empty parts now
take the module's source file name, as the first part has it.
2026-10-27 03:30:15 -05:00
Chris Tate 60372c09a4 Declare the heap census writer in scr_lib.h
The LLVM backend can emit scr_census_write, which a census program
defines and the runtime calls through a weak reference, so the header
declares it as it does scr_detached_exit.
2026-10-27 03:28:32 -05:00
Chris Tate 9179a881a4 Skip the helper version probe and unchanged files' digests on rebuilds
Every native build started the LLVM helper once for `version` before
emitting, and hashed every file the frontend read, including the supplied
declaration files that do not change between builds.

- verifyNativeHelper takes an optional record of helpers that already
  answered `version` as expected. The native compiler keeps it in its cache
  (family "helper"), keyed by the helper's and the compiler's file identity,
  the compiler version and the target. The probe runs again when the helper
  file changes. The package name and version are still checked every time.
- FrontendInputTracker takes the snapshot of the cache entry a build
  replaces. A file whose settled identity has not changed since that
  snapshot keeps the digest recorded there, which is the trust
  frontendInputsStillMatch already gives an unchanged identity.

Builds produce the same executables.
2026-10-27 03:24:16 -05:00
Chris Tate 429aa8273d Keep the heap census hook out of library archives
Libraries never build the census (heapCensusOf), but the runtime's weak
scr_census_write reference made library archives list an external
symbol outside their declared set. Declare and call it only in programs.
2026-10-27 02:49:05 -05:00
Chris Tate 659a74acc4 Merge main 2026-10-27 02:27:10 -05:00
Chris Tate 6451e846c6 Reset the prefault helper's state in forked children (#2391) 2026-10-27 02:10:51 -05:00
Chris Tate 60d5dc8ab1 Count class-value constructions in the census, and key caches on both diagnostics
- The heap census also counts instances a class value constructs: the
  construct thunk that `newValue` calls counts them after its allocation,
  as `new` does at its own.
- A build with SCRIPTC_HEAP_CENSUS set is its own executable cache entry
  (`heapCensus` in the early cache's key and route key), and one with
  SCRIPTC_REPR_REPORT set does not read the early cache, since a hit
  emits no module and so writes no report. The CLI's startup route defers
  both to the full compiler.
- Test: a warm cache still builds the census executable, counting
  `new` and class-value constructions, and still writes the report.
2026-10-27 01:54:48 -05:00
Chris Tate 773eb15a52 Spread a string into its code points array (#2389) 2026-10-27 01:51:43 -05:00
Chris Tate 331d3804a8 Decode short non-ASCII strings directly in charCodeAt (#2388) 2026-10-27 01:44:17 -05:00
Chris Tate 59ec7998fe Reset the prefault helper's state in forked children
fork() copies only the calling thread, so a child forked while the
allocator's prefault helper held scr_sa_pf_lock waited on that lock forever
at its first fault-in request, and a child that released the range the helper
was faulting in waited forever for the helper to finish it. Native builds'
emit workers hung this way, with the parent waiting in waitpid.

The helper's first start now registers pthread_atfork handlers. The prepare
handler takes the lock across the fork and the parent handler releases it.
The child handler resets the ring, the busy range, the waiter state and the
heap polling state, reinitializes the lock and both conditions, and marks the
helper not started, so the child's first request starts a helper of its own.

check_fork_while_prefaulting in scr_alloc.test.c forks while the helper
faults in 64 MiB, over 64 rounds; each child releases that range, faults in a
range of its own and must exit within five seconds. ThreadSanitizer builds
skip it.
2026-10-27 01:35:32 -05:00
Chris Tate 8b256639f4 Add a representation report and a heap census for ranking value-layout work 2026-10-27 01:31:00 -05:00
Chris Tate 4b8ed48a8a Spread a string into its code points array
`[...text]` lowered to an array literal that spread the array of text's
code points: the literal's array copied the code point strings, retaining
each one, and the code point array then released them. An array literal
whose only element is a spread of a string is now the code points array
itself, as `Array.from(text)` already was. Both are fresh arrays.
2026-10-27 01:19:32 -05:00
Chris Tate 618243caa8 Generate code concurrently for optimized executables that stay one partition (#2387) 2026-10-27 01:10:09 -05:00
Chris Tate 47904df3c2 Decode short non-ASCII strings directly in charCodeAt
charCodeAt on a non-ASCII string mapped the UTF-16 index to a byte offset
through a string index entry, which the first read claimed and the
string's release purged. For a string of one character, such as each
character of a for-of loop that codePointAt(0) classifies, the entry cost
more than the read.

A non-ASCII receiver of up to 16 bytes is now decoded from its start on
each read, as scr_str_utf16_len already counts short receivers. Longer
receivers keep the index entry and its cursor.
2026-10-27 01:07:00 -05:00
Chris Tate 31fe5c5a8d Convert the base of an offset typed-array index once (#2385) 2026-10-27 00:56:23 -05:00
Chris Tate f64b25d103 Add --large-pages to move executable code onto huge pages (#2386) 2026-10-27 00:36:40 -05:00
Chris Tate 72732110ff Check offset typed-array indexes against the sum for fractional bases and keep the base the sum read
An index `base + c` converted `base` and took a fractional base as an
invalid index. A fraction smaller than the sum's precision is lost in the
sum, as in `1e-20 + 1`, which is the index 1. A base that is not an
integer now goes to the exact check of the sum itself.

The conversion also read the base local again when the access was
emitted. A write evaluates its right-hand side between the index and the
store, so `values[at + 1] = ((at = 5), 31)` wrote at the new base. The
sum now records the base value it was computed from, and the index
converts that value.

The corpus program covers both cases, and the emission test checks that
a write converts the base its sum read.
2026-10-27 00:28:04 -05:00
Chris Tate 640001b260 Generate code concurrently for optimized executables that stay one partition
Optimized executables with at most 1 MiB of LLVM text were compiled as one
module on one thread. With more than 128 KiB of LLVM text they now pass
several outputs and `--split codegen` to the LLVM helper. The helper
optimizes the module whole, as for a single output, then divides its
functions in module order into runs of about the same instruction count and
generates code for each run on its own thread.

- The output count is 4 or 8 and depends only on the module's size
  (nativeCodegenParts), so artifacts never vary with the host. Node and
  native compiler builds pass the same outputs.
- The helper writes the optimized module to bitcode once with its use-list
  order. Every output after the first parses it and turns the other
  outputs' definitions into declarations; the first output does the same to
  the optimized module in place. Each function's machine code matches a
  single-output build except for jumps to a definition that another output
  also uses.
- Only local definitions that another output uses become hidden. Dead
  constant users are dropped before unused declarations are removed, so no
  output names a thread-local variable it does not use.
- When one run would hold more than three quarters of the instructions, the
  first output is generated from the optimized module as for a single
  output and the others are empty objects.
- Dev builds, partitioned builds and program-object validation are
  unchanged.
2026-10-27 00:22:17 -05:00
Chris Tate 98cc35de09 Find the code segment that holds the runtime under musl too
The constructor took the program object with an empty name for the main
program, which is how glibc reports it. musl reports the main program
with its path, or "/proc/self/exe" in a static executable, so on musl
targets --large-pages found no segment and left the code file-mapped.
The constructor now takes the executable segment that holds its own code:
the runtime links into the executable, so that is the main program's
code under either C library.

scr_text_pages.test.ts runs its check against a static musl build made
with zig as well as the glibc build.
2026-10-26 23:55:27 -05:00
Chris Tate 6d2e72367c Keep element reads through xs[xs.length - 1]! borrowed (#2383) 2026-10-26 23:54:36 -05:00
Chris Tate 140990d257 Add --large-pages: move executable code onto transparent huge pages
A large program's hot code spans hundreds of 4 KiB pages, more than the
instruction TLB holds. tsc-ts has 12 MB of code, and 90% of its
main-thread samples at --checkers 1 fall in 274 distinct code pages.

`scriptc build --large-pages` links the new runtime unit
scr_text_pages.c, whose constructor moves the executable's code onto
2 MiB transparent huge pages before main. It copies the executable
segment, from its first to its last 2 MiB boundary, into anonymous memory
advised MADV_HUGEPAGE, makes the copy read-only and executable, and moves
it over the original range with one mremap, so addresses and instructions
stay the same. The move runs only on Linux, only when THP is "always" or
"madvise", and only in executables. If a step fails, the code stays
file-mapped. Like Node's --use-largepages, it is off by default: without
the flag the unit is not linked, and executables are unchanged.

The move costs about 3 ms per 10 MiB of code at startup, and all of the
moved code stays resident as private memory. The flag is part of the
executable cache key and the runtime-pack selection, and is refused for
non-executable output, like --detached-exit.
2026-10-26 23:41:46 -05:00
Chris Tate 7c295a2b32 Version while loops on the integer range of locals they enter with (#2382) 2026-10-26 23:32:40 -05:00
Chris Tate e6eebcc517 Convert the base of an offset typed-array index once
A typed array read or write at `base + c`, `c + base` or `base - c`,
where `base` is a number local and `c` an integer literal of magnitude
below 2^31, now converts `base` to an integer and adds `c` as an
integer, instead of converting the double sum. Accesses at several
offsets from one base, such as `x[at]` through `x[at + 7]` with a number
parameter `at`, then share one conversion and integer test. Below a
length of at most 2^52 the sum is an integer index exactly when `base`
is an integer, so the accesses read and write the same elements as
before.
2026-10-26 23:32:08 -05:00
Chris Tate fc8fe7e619 Rename each shared libCall once when switching to child property tables (#2381) 2026-10-26 23:22:58 -05:00
Chris Tate 775a317edd Keep element reads through xs[xs.length - 1]! borrowed
A member read through a non-null asserted element read evaluates the
element's presence and then reads it, so a missing element throws Node's
TypeError. An index other than a local or a field read was held in a
hidden local first, which turned the receiver into a sequence: the
element was then read with a reference (retain and release) instead of
borrowed, and the fallback `genes[genes.length - 1]!.sc` of a search
made fasta's lookup loop 12% slower.

An index computed only from locals, field reads, number literals and the
lengths of arrays read through them (`xs.length - 1`, `i + 1`, `-i`)
has no effect and yields the same value when evaluated again with
nothing in between, so it is evaluated for the presence test and again
for the read, and the element stays a borrowed read. Other indexes keep
their hidden local.
2026-10-26 23:20:59 -05:00
Chris Tate df77671e33 Merge main 2026-10-26 23:13:34 -05:00
Chris Tate 4046b17c65 Version while loops on the integer range of locals they enter with
A while loop whose remainder reads number locals that enter it without
range facts, such as copies of module globals, parameters or fields
(`const base = column;` before `(count + base - breaks) % 60`), now runs
as two copies. On entry, a test checks that each such local holds an
integer in [-2^48, 2^48] other than -0. The fast copy uses the facts the
integer range analysis proves when the function is analyzed again with
those entry ranges assumed: the loop's counters become bounded, and the
sums and remainders run as i64 arithmetic. When the test fails, the loop
runs as it did before.

- ir/loop-entry-ranges.ts picks innermost while loops with a remainder
  that the assumed ranges prove and the function's own analysis does
  not. The test covers only the locals the remainder depends on, directly,
  through the loop's writes or through its condition.
- ir/integer-ranges.ts takes the assumed entry ranges per loop, and keeps
  no fact about the assumed locals or the loop's writes after it.
- backend/llvm/loop-entry-versions.ts emits the test and both copies.
  The test is marked likely. A function called only from the loop gets
  `alwaysinline` on the fast copy's call, because the second copy would
  otherwise cost it the inlining of a single call site.

reverse-complement (25M bases): 2.08 s -> 1.19 s; Node 3.97 s.
2026-10-26 23:02:32 -05:00
Chris Tate 80b851d65f Write untyped stores into live records and keep one object across prefix casts (#2366) 2026-10-26 22:56:27 -05:00
Chris Tate f436bf5ede Give every native function an unwind table entry (#2377) 2026-10-26 22:54:13 -05:00
Chris Tate 7646ab5a6b Rename each shared libCall once when switching to child property tables
applyHandleProperties collects the libCalls to rename with a walk of
every function body and renames each one in place. A node the IR reaches
from several statements was collected once per statement, so its second
rename looked up the already renamed name and set fn to undefined. The
calls are now collected in a set, so each node is renamed once.

The lowering does not share such a node today, because devirtualization
rebuilds every function body before this step; the lowered IR of current
programs is unchanged.
2026-10-26 22:48:01 -05:00
Chris Tate 6131540bb2 Trace Array subclass instances in array-typed slots and test their class on element reads (#2372) 2026-10-26 22:36:57 -05:00
Chris Tate 79f456d665 Keep the static hello binary small under GNU ld (#2373) 2026-10-26 22:36:03 -05:00
Chris Tate 47fd397395 Expect the unwind table attribute in the --target-cpu test's attribute group 2026-10-26 22:24:00 -05:00
Malte UblandClaude Opus 5.5 d2abf522c8 Give the dynamic-dispatch test lowerer a source lookup
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:16:41 -07:00
Malte UblandClaude Opus 5.5 70c39cca49 Keep frozen records frozen for untyped code and ignore refused sloppy writes
Object.freeze of a fresh record or array literal now sets the native
frozen bit instead of compiling to the literal, so an untyped reference to
the value sees it frozen: Object.isFrozen answers true and strict-mode
writes throw Node's TypeErrors.

Untyped property writes in sloppy-mode code (IrModule.sloppyDynWrites,
decided per write by isStrictCode) call scr_dyn_key_set_sloppy and its
computed form, which drop exactly the TypeError a refused assignment raises
(a read-only or frozen property, a non-extensible object, a frozen array
element) while a setter's exception still propagates. Computed-write
dispatch helpers are shared only by writes of the same mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:16:30 -07:00
Chris Tate 31f07e2d73 Read class instance fields inline at dynamic-tier property sites (#2374) 2026-10-26 22:13:38 -05:00
Chris Tate f7168d3fca Merge main 2026-10-26 22:04:34 -05:00
Chris Tate 80138011ed Allocate template literals at their exact length (#2369) 2026-10-26 21:55:50 -05:00
Chris Tate c9585ae48f Fail type checking when the generated IR field readers miss a field (#2370) 2026-10-26 21:52:07 -05:00
Chris Tate e1eead091f Give every native function an unwind table entry
LLVM emits an unwind table entry for a function that may unwind or that
has the uwtable attribute. Functions it infers nounwind, such as the
number-key Map probe sc_map_entry_f64_hashed, got none, so profilers and
debuggers could not unwind past them. The function attribute groups now
carry uwtable on native targets; wasm32-wasi modules are unchanged.
2026-10-26 21:49:14 -05:00
Chris Tate 079bad80be Leave out a header the compiler rejects in the implicit toolchain probe (#2371) 2026-10-26 21:47:29 -05:00
Chris Tate ad29d598f5 Merge main into the untyped-store branch 2026-10-26 21:44:47 -05:00
Chris Tate 2cbce33cd3 Read class instance fields inline at dynamic-tier property sites
An instance of a compiled class reaches untyped code as a capsule, which
a property site sent to its helper on every read: the helper normalizes
the capsule, tests each class's key in turn, and loads the field.

A read site now emits the helper's plain field branches inline. A
capsule whose key pointer is one of those classes' keys loads the field
from its instance and shares the inline hits' conversion. A branch is
emitted only when its field holds a plain dyn cell and every subclass
the helper's normalization can rebox the key to reads the same field
from the same slot. Getters, bag properties, unset fields and other
capsules keep the helper.
2026-10-26 21:39:18 -05:00
Chris Tate 2795957867 Step untyped loops over the program's generators on the typed path (#2302) 2026-10-26 21:36:11 -05:00