Commit Graph
6750 Commits
Author SHA1 Message Date
Chris Tate 491480e1ad Convert clamped stores inline and test thread-owned views once in typed-array loops
A Uint8ClampedArray store called scr_bytes_to_u8_clamp for every element,
which kept loops of clamped stores scalar. The store now computes
ToUint8Clamp in line: NaN and values at most 0 store 0, values at least
255 store 255, and values between round through (v + 2^52) - 2^52, which
rounds a value in (0, 255) to the nearest integer, ties to even, as the
runtime function does.

In worker-enabled programs, every typed-array element access tests the
view's shared storage and branches between a relaxed atomic access and
the ordinary one, so loops over thread-owned views kept one branch per
access. A view's shared storage is fixed at construction, like its length,
so the typed-array loop version guard now tests it once for each invariant
receiver, and the fast copy reads and writes those views directly. In
worker programs a loop is versioned for this even when no bound needs a
guard. When any receiver is shared, the checked copy runs unchanged.
Programs without workers emit the same code as before.

Adds a Uint8ClampedArray brighten kernel to the typed-array-kernels
workload.
2026-10-22 02:09:43 -05:00
Chris Tate a69fe6f737 Accept only nested scans that highestCount walks pass by pass (#1765) 2026-10-22 02:03:02 -05:00
Chris Tate 2fbfc1b270 Keep each build's translation unit private until the build ends
The kept LLVM text and IR are named after the entry (`<stem>.ll`,
`<stem>.ir.json`) in the output directory, so two programs whose entries
share a name and are built into one directory share those files. The
executable cache published the translation unit by copying the shared
`.ll` after the native build, so when the other program wrote it in
between, the stamp recorded the other program's text, and a later cache
hit relinked the wrong program without any error.

A build now writes, compiles and caches private copies beside the shared
names and renames them into place when it ends. A cache hit that relinks
compiles a private copy of the cached translation unit, and leaves a
shared file that already holds those bytes as it is. The native build
reads the private copy and keeps the shared name as the spelling clang
sees, so native cache keys and debug names do not change.
2026-10-22 02:00:30 -05:00
Chris Tate e7e943a1d3 Accept only nested scans that highestCount walks pass by pass
The first pass of guardedLoopCounterFacts accepted a nested while loop
anywhere outside an opaque region, and highestCount then left counters
unbounded where it does not walk such a loop. The first pass now
accepts the same loops highestCount walks: statements of the body, its
blocks and its branches. A loop in a labeled block or among an
expression's statements is an ordinary loop region, whose counter
writes are refused.
2026-10-22 02:00:06 -05:00
Chris Tate d426960f45 Make Cipheriv and Decipheriv Transforms and accept function stages anywhere in pipeline()
Cipheriv and Decipheriv values are Transforms, as in Node: they can be
written and ended, piped into, used as pipeline() and stream/promises
pipeline stages, composed, read, iterated with for await, read by
stream/consumers, passed to finished(), and listened to, and their stream
properties read as in Node. Each chunk goes through the cipher's
update(), and the end pushes final() or reports its error through
'error'; writing after final() throws out of write(). The stream unit
builds a cipher's Transform on its first stream use from operations the
cipher's handle provides (ScrDynHandleOps.transform), so programs that
use ciphers without streams link as before. The cipher is a
collector-headered object behind a traced handle: its Transform's steps
hold it, and the pair is reclaimed as a cycle. Through untyped values, in
a program that uses streams, a cipher's stream properties and its on,
once, off, pipe, unpipe, read, pause, resume, setEncoding, write, end,
destroy, cork and uncork members run on its Transform; other stream
members throw a coded SC2020 error.

A Transform holds a write's callback while its readable side is at the
high-water mark and runs it at the next read, and runs it on a tick once
the readable side has ended, as Transform._write does. write() on a
PassThrough, a Transform, a Hash or a cipher used to return true however
much the readable side buffered. zlib streams applied the rule already
and keep their timing.

pipeline() accepts function stages anywhere among its streams, so runs of
async generator stages can sit on both sides of a stream. A stage can be
named by a function declaration, or a const bound to a function
expression, that nothing reassigns; its body lowers again at the
pipeline with its source typed by the pipeline. The source can also be
an array, a string, a synchronous generator object, or a synchronous
generator function without parameters: an async generator steps the
source's iterator and yields each element, and the next stage receives
each element on the same tick as with Node's for await over the source.
The runtime's function-stage entry points take a bitmask of the function
positions and one (next, read, return) helper triple for each generator
pumped into a stream, so RUNTIME_ABI_VERSION is now 25. In JavaScript,
the chunks a stage receives from the previous stage's generator read as
the strings or Buffers it yields, so their string and Buffer methods
compile.

Corpus 7055-7059 cover cipher streams in TypeScript and JavaScript, their
errors, close order and backpressure, the pipeline stages and sources
with their tick order, and Transform backpressure.
2026-10-22 01:55:38 -05:00
Chris Tate eb63f3f174 Revert "Name lowered helpers and closures independently of lowering order" (#1763) 2026-10-22 01:43:05 -05:00
Chris Tate 777bfb43f2 Revert "Name lowered helpers and closures independently of lowering order"
This reverts #1750 (05e2f404d), which made main CI fail.
2026-10-22 01:40:17 -05:00
Chris Tate c081d5a3a7 Apply the byte offset in Buffer.from over untyped ArrayBuffers
Buffer.from(arrayBuffer, byteOffset) with an untyped ArrayBuffer or
SharedArrayBuffer now returns a Buffer from that offset to the end, as
Node's fromArrayBuffer does, where the offset was dropped and the Buffer
covered the whole buffer.
2026-10-22 01:16:55 -05:00
Chris Tate 271c178d21 Version loops to a typed array's length on every typed array they index (#1762) 2026-10-22 01:15:26 -05:00
Chris Tate 0763610e93 Version loops to a typed array's length on every typed array they index
A loop such as `for (let i = 0; i < out.length; i++) out[i] = x[i] * y[i]`
keeps its integer induction, and the version guard now also receives the
loop's counted form, so it covers the accesses of `x` and `y` at the
counter. Before, only counted loops (`i < n`) passed a counter to the
guard, and loops to a length kept one bounds check per access of every
other typed array, which kept LLVM from vectorizing them.

Adds the typed-array-kernels runtime workload: element-wise loops over
Float64Array, Float32Array, Uint8Array and Int32Array, and RGBA to gray.
2026-10-22 01:11:37 -05:00
Chris Tate 05e2f404dc Name lowered helpers and closures independently of lowering order (#1750) 2026-10-22 01:10:58 -05:00
Chris Tateandblurname d68b73e3b4 Extend a generic factory's local class returned through a constructor type
A generic factory declared to return a constructor type (`Struct<S> =
(abstract new () => S) & { decode }`) whose every return gives back one of
its own local classes (`return Base as Struct<S>`) is now that local
class's factory: each instance has its own class, the result maps to the
class value while the instance lowers and at its calls, and `class X
extends struct(...)` extends it like a factory returning the class
directly. Before, the result type mapped to nothing (SC2008) and the
extends clause refused as a computed expression.

Static methods of a function-local class in a TypeScript file now lower as
the class value's runtime statics, as they already do in JavaScript files:
they read the enclosing function's bindings, call each other through
`this`, and inherit into a subclass. Before, they refused with SC1090.
Static fields and static blocks of such classes keep the fence.

Fixes #1372

Co-authored-by: blurname <50226652+blurname@users.noreply.github.com>
2026-10-22 01:10:25 -05:00
Chris Tate 93e13f49a0 Keep structural names apart by declaration kind and name recursive types by position
Structural positions named an enclosing declaration by its name and its
ordinal among the declarations of the same kind and name, without the kind.
A getter and the setter of the same property, or a class and the namespace
merged with it, therefore had the same path, and local classes declared in
both shared one class. The path now includes the kind, and interfaces and
type aliases anchor the positions inside them.

Recursive record and union placeholders were named by their printed type
and the byte offset of their declaration. A comment or blank line above the
type renamed them, and two instantiations that print alike (type arguments
that are type parameters or classes of one name from different
declarations) threw an InternalCompilerError. Placeholders are now named by
the printed type and the structural position of the declaration, and the
same for up to three levels of type arguments. A description that two types
still share gets a numbered suffix for the later one.
2026-10-22 01:06:31 -05:00
Chris Tate 2e7db5da9e Encode messages into one block sized to their contents (#1760) 2026-10-22 01:03:36 -05:00
Chris Tate 5a414300a9 Store references to ProgramLifetime instances in 4 bytes on Linux (#1748) 2026-10-22 01:02:43 -05:00
Chris Tate 1a51223cd6 Say where the region's trap and memoryUsage accounting come from
Comments only: scr_trap is the runtime's failure path and the
standalone allocator test defines its own, and the region's chunks
count in process.memoryUsage() through scr_heap_stats.
2026-10-22 01:01:01 -05:00
Chris Tate f1e6addc81 Encode messages into one block sized to their contents
An encoded message allocated a node array (four nodes of 184 bytes to
start, trimmed when encoding ended), a property array per object, a copy
of every key and string, and the encoder's visit, identity and stack
arrays. A port that queues many messages kept all of it until delivery:
a {id, name} object took 15 allocations and 1.9 KB, and most of
postMessage's time went into page faults on that memory.

- The encoder builds nodes, properties and visits in arrays that start on
  the stack (8 entries), keeps borrowed references to keys and strings,
  and copies the result into one allocation: the header, the nodes, the
  properties grouped by node, the typed records and the text. A node is
  40 bytes; the per-kind fields share a union. ArrayBuffer contents stay
  in their own allocation.
- Decoding uses stack arrays for messages of up to 8 nodes and allocates
  the storage table only when the message has ArrayBuffer contents.
- Freeing a message frees the block, and walks the nodes only when the
  message holds ports, native handles or ArrayBuffer contents.
- The publication check reads the root before collecting the graph, so
  a message whose root is not frozen allocates nothing for it.

The same object message now takes 4 allocations and 302 bytes, and a
number 3 allocations and 129 bytes. bench/channel-messages runs in 161 ms
instead of 250 ms (Node: 340 ms), with a peak RSS of 45 MB instead of
115 MB, and bench/worker-messages in 273 ms instead of 348 ms.
2026-10-22 00:51:27 -05:00
Chris Tate 5f3fab5e7e Bound while-loop counters that nested scans advance (#1753) 2026-10-22 00:48:08 -05:00
Chris Tate 728dc6cd51 Count program-lifetime objects in memoryUsage and fix the runtime tests
- process.memoryUsage(): heapTotal counts the region's chunks that
  threads hold, minus the chunks discarded workers returned, and
  heapUsed counts them less the tails threads left unused when they
  moved to a new chunk.
- The region builds only where it can be used: Linux runtimes with
  the size-class allocator, which excludes the sanitizer runtimes
  (scriptc keeps 8-byte references there). The standalone allocator,
  cycle and young-nursery tests link again: the allocator test
  defines the scr_trap its region needs, and checks the region (below
  32 GiB, aligned, zeroed, chunks larger than one, heap statistics,
  threads).
- scr_plr_chunk has an initializer, like the other runtime globals.
2026-10-22 00:47:17 -05:00
Chris Tate b59da7529e Leave counters unbounded when a nested scan is not walked pass by pass
highestCount walks a nested while loop from its bound only when the loop
is a statement of the body or of its blocks and branches. A nested scan
inside a labeled block or an expression reached the generic branch,
which counted its increments once although the loop can repeat them.
Such a loop now leaves the counter without a range.
2026-10-22 00:44:22 -05:00
Chris Tate ed9ecca540 Read and write shared typed-array elements inline (#1759) 2026-10-22 00:42:51 -05:00
Chris Tate e597e2d64c Keep the .d.ts-only types of a package compiled from its JavaScript (#1729) 2026-10-22 00:38:08 -05:00
Chris Tate 8bbccee4f6 Read and write shared typed-array elements inline 2026-10-22 00:37:37 -05:00
Chris Tate e75286c5a0 Compute Math.pow(2, n) for integer exponents from the result's bits (#1758) 2026-10-22 00:37:19 -05:00
Chris Tate 140b1f397f Reject DiffieHellman secrets of 0, 1 and p - 1
computeSecret now fails with ERR_CRYPTO_OPERATION_FAILED ("Failed to
compute shared secret") when the exponentiation yields 0, 1 or p - 1,
as Node's OpenSSL derivation does: such a secret is predictable, from a
peer key of small order or a composite modulus. 6708 covers a secret of
1 and of p - 1 (peer keys of order 3 and 6 modulo a 512-bit prime with
p ≡ 1 mod 6) and of 0 (a peer key sharing the factor of a square
modulus).
2026-10-22 00:35:17 -05:00
Chris Tate 47ba594c3f Check every type a package type reaches before re-exporting it as a data shape
The data-shape check answered a type that refers back to a type still
being checked as a data shape and recorded that answer before the
outer check finished. With `Branch { child: Tree; handle: Handle }` and
`Tree { parent: Branch | null }`, checking Branch first recorded Tree as
a data shape, and Tree was re-exported although Branch reaches an
interface with a method. The check now walks every type reachable from
the queried one before it records any of them as a data shape, and
records the types on the path to a non-data type as non-data.
2026-10-22 00:34:51 -05:00
Chris Tate 9e252e1700 Declare runtime ABI 24 in the native toolchain test's runtime pack fixture (#1757) 2026-10-22 00:34:27 -05:00
Chris Tate 2d353e6bbe Compute Math.pow(2, n) for integer exponents from the result's bits 2026-10-22 00:33:54 -05:00
Chris Tate 71dccbb8a8 Declare runtime ABI 24 in the native toolchain test's runtime pack fixture
The toolchain integrity test builds a runtime pack manifest by hand. It
still declared ABI 23 after the runtime ABI moved to 24, so the staging
check rejected the pack instead of staging it.
2026-10-22 00:31:48 -05:00
Chris Tate 16a218d6a3 Reuse deferred initializer scans while only bindings are pending (#1755) 2026-10-22 00:19:08 -05:00
Chris Tate 321873b957 Compile JavaScript-path calls that Node runs 2026-10-22 00:18:36 -05:00
Chris Tate 561f66cf5b Reuse deferred initializer scans while only bindings are pending 2026-10-22 00:16:11 -05:00
Chris Tate e003e6abc1 Await a suspending stackless callee without its promise (#1754) 2026-10-22 00:01:22 -05:00
Chris Tate 63001cbc13 Await a suspending stackless callee without its promise 2026-10-21 23:55:37 -05:00
Chris Tate 956bdf4ec0 Bound while-loop counters that nested scans advance
A while loop's counter gets an integer range when the loop only adds
positive literals to it and its condition bounds it. A counter written
inside a nested loop was refused, which is every hand-written scanner:
`while (pos < end) { ... while (pos < end && isDigit(...)) pos++; ... }`.

A nested while loop may now advance the counter when its own condition
bounds it the same way and does not write it. Its passes start at most
at that bound, so it leaves the counter at its entry value or at the
highest value one of its passes reaches, and the outer loop's range
follows from there. Other nested loops, and nested loops that do not
bound the counter, still refuse it.
2026-10-21 23:49:13 -05:00
Chris Tate 393201e693 Run Promise.all of never-suspending async calls in place from stackless async bodies (#1752) 2026-10-21 23:45:29 -05:00
Chris Tate 42d4ca27f9 Run Promise.all of never-suspending async calls in place from stackless async bodies 2026-10-21 23:40:42 -05:00
Chris Tate 97e85bf238 Compile js-yaml and smol-toml statically and match Node 2026-10-21 23:35:02 -05:00
Chris Tate ed84e11dfe Name function expressions by their source names in SC6101 and skip type names and generated helpers (#1749) 2026-10-21 23:28:37 -05:00
Chris Tate 61f1329b32 Name lowered helpers and closures independently of lowering order
Lowering named helpers by the size of their registry at first use
(%union.retag.N, %arr.map.N, ...), lifted closures and optional-chain
locals by compiler-wide counters (%fnN, chain.N), generic instances by
their instance count, and records and unions by creation order (rN, uN).
Class expressions, mixin instances, function-local generics, non-exported
namespace segments and some module globals carried the byte offset of their
declaration. A name therefore depended on which unit lowered first and on
the positions of unrelated code.

Helpers, instances, records and unions are now named by a hash of the key
they are interned by (keyedHelperName), and recursive records and unions by
their printed type and declaration. Closures and optional-chain ids are
named by their lowering unit, their structural position and a count per
key within the unit. Offset-based names use the structural position: the
chain of enclosing named declarations and the node's ordinal among
same-kind nodes inside the nearest one, so an edit outside that
declaration leaves the name unchanged. Two different keys with the same
name throw an InternalCompilerError. The module lists its records and
unions by id. The generated program is unchanged apart from these names
and that order, and test:lowering-order now fails on any name difference.
2026-10-21 23:26:29 -05:00
Chris Tate e790de2f68 Keep the slots of short known-count arrays in the array's block (#1746) 2026-10-21 23:26:08 -05:00
Chris Tate 5bdc3be385 Name function expressions by their source names in SC6101 and skip type names and generated helpers 2026-10-21 23:25:48 -05:00
Chris Tate ecee5fdaaf Store references to ProgramLifetime instances in 4 bytes on Linux 2026-10-21 23:25:33 -05:00
Chris Tate 46bee64635 Test runtime classes' dispatch branches before their subclasses' (#1747) 2026-10-21 23:25:16 -05:00
Chris Tate c22302cfae Test runtime classes' dispatch branches before their subclasses' 2026-10-21 23:22:03 -05:00
Chris Tate 56880e110c Keep the slots of short known-count arrays in the array's block
An array constructed with a known element count of up to 16 (array
literals, copies, slices and the arrays the runtime builds) keeps its
slots in its own block, as empty arrays built for appends already do, so
it costs one allocation and one free.

scr_arr_make_ops is inlined into its two constructors, so the size class
of scr_arr_new_ops_small folds to a table index. It writes the element
ops id with the other header fields and records ops past the registry
in the array's ext after the storage is set up: the ext pointer precedes
the dense allocation, so inline slots must move out before the ext is
created.
2026-10-21 23:19:57 -05:00
Chris Tate f686669aa4 Renumber the fiberless plain-call corpus program to 7103 (#1745) 2026-10-21 22:36:24 -05:00
Chris Tate 3e8f1bc608 Renumber the fiberless plain-call corpus program to 7103 2026-10-21 22:34:17 -05:00
Chris Tate f5d6f7aed7 Run never-suspending async calls in place from stackless async bodies (#1744) 2026-10-21 22:24:51 -05:00
Chris Tate c36c414065 Re-export only data shapes from package declarations, through imports and .mjs entries
- A local `export { Kind }` of a name the declaration file imports
  (`import type { Kind } from "./kinds"`) now resolves like
  `export { Kind } from "./kinds"`; it was dropped.
- The typedefs now reach a runtime entry outside the .js/.cjs rewrite
  path (an .mjs file).
- Only data shapes are re-exported: an interface, type alias or enum
  with no method, call or construct signature, function type or other
  computed type, whose referenced types are data shapes too. An
  interface with methods describes the package's JavaScript objects and
  stays as the JavaScript types it (@opentelemetry/api's Counter).
- NPM_STATIC_TYPES_SUFFIX moves to npm-static.ts, so that module does
  not import the syntax modules (the self-hosted frontend tests compile
  it natively).
2026-10-21 22:23:04 -05:00