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.
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.
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.
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.
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.
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.
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>
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.
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.
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.
- 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.
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.
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).
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.
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.
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.
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.
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.
- 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).