An event that typed code only listened to with a listener taking no
parameters had no bridge entry: the scan marked a listener as typed only
when one of its parameters was not a checked-dynamic value. Emitting the
event through an `any` reference then threw instead of calling the
listener. A TypeScript listener with no parameters now marks the event as
typed. A JavaScript function without parameters still registers as before,
since it can read `arguments`.
The first Object.freeze, Object.seal or Object.preventExtensions of a typed
function creates its property table from the function's dynamic box. The
box had no name, so later `f.name` reads answered "". The box now carries
the function's JS name, as a closure flowing into any checked-dynamic slot
does.
The runtime's own callers (published values, os and node constants, the
private-range block list, Wasm exports) freeze data they built, and now
call scr_dyn_freeze_data, the freeze for values that are not functions.
Object.isFrozen, Object.isSealed and Object.isExtensible read a
function's existing table and answer false, false and true when it has
none. Fully typed programs therefore link no dynamic-tier object code
for these calls again.
tsc wraps every CommonJS import in its helpers: `const events_1 =
__importDefault(require("events"))`, `const path =
__importStar(require("path"))`. cjs-tsc-interop.ts reads such bindings of
program modules as plain require bindings, so the helper declarations go
dead, but a builtin module is not a program module: its binding kept the
helpers live, and their probe of the module's top-level `this`
(`(this && this.__importDefault) ||`) failed at load ("property access
through top-level 'this'"). That is the shape of hundreds of tsc-compiled
files in a typical dependency tree.
A builtin carries no `__esModule`, so `events_1.default` is the module and
the namespace copy reads the module's members. When every reference has
that form, a package-file stage now respells the binding as the plain
require() of the builtin and `x.default` as `x`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AstWireFile read every node field with DataView.getUint32 after checking
the index with Number.isInteger and a range compare. In the compiled
compiler the DataView read, Number.isInteger and its trunc are runtime
calls on every access, and parent, kind, next and data run on every child
of every walk.
The node table is now a Uint32Array: a view of the response when the
table starts on a 4-byte boundary on a little-endian host, otherwise a
copy decoded once with astU32. Each accessor checks its index with
index >>> 0 === index and the node count, which accepts the same indices
as before, and throws the same error through an out-of-line function.
Decoded strings are cached in an array indexed by string slot instead of
a Map. The checks of sibling, parent, list and named-child links are
unchanged and still run when each node is read.
The emitter's analyses repeated whole-module work. dynamicFieldStores
walked every function five times on the emitted module (the emitter
constructor, the int32 slot analysis, the final field analysis and both
cycle mutability analyses), findInitializerBindings ran twice,
functionCallFacts walked every function three times, the int32 slot
analysis walked every function four more times before its fixpoint, and
the runtime feature scan ran two to four times.
The constructor now computes dynamic field stores and initializer
bindings once and passes them to the analyses, which compute them
themselves when a caller does not. functionCallFacts and the int32 slot
analysis collect their call, arity, box and reader facts in one walk per
function and apply the facts that depend on earlier steps, in walk
order, where those steps complete. The emitter reads one runtime feature
scan, taken the first time any feature is needed. Emitted LLVM is
unchanged.
Revert 3a27a92cb ("Forward at most four emit arguments through
EventEmitter method values") and 0cf62ebec ("Answer EventEmitter methods
read as values off compiled emitters"). Under the scope-B plan,
EventEmitter methods read as values, `EventEmitter.prototype.on` in
JavaScript and constructor-function emitters belong to Node islands, not
the native compiler. The typed/untyped listener sharing and the
catchable throw in place of the trap stay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A union, unknown or any value is converted to a dynamic value before it
is thrown, and promise rejections and other runtime throws carry dynamic
values too. A catch binding then holds a dynamic payload, which the
native class test does not recognize: `e instanceof C` was false for
every class of a hierarchy, and a read narrowed by it trapped. A test
through a parameter of type unknown was already correct.
When a class or one of its subclasses crosses into unknown, the class
dynamic-dispatch pass now routes the catch-binding tests of that class
(`e instanceof C`, the reads it narrows and `e as C`) through helpers
that keep the native test and answer a dynamic payload with the dynamic
class test, as standalone classes already do. The new "dynamic" caught
test selects that payload, so native payloads are not converted.
Programs whose classes never cross into unknown compile as before.
fs.promises.rm(path, options) accepts the { recursive, force, maxRetries,
retryDelay } literal options that rmSync accepts. It runs the same removal
and rejects with its errors.
Removing a directory without recursive now throws Node's ERR_FS_EISDIR
SystemError from rmSync and fs.promises.rm. A retryDelay outside int32 >= 0
or a maxRetries outside uint32 throws ERR_OUT_OF_RANGE. rmSync checks these
options before the path, and fs.promises.rm checks the path first, as Node
does.
Fixes#1615
Co-authored-by: jlucaso1 <55464917+jlucaso1@users.noreply.github.com>
The runtime's ToIntegerOrInfinity for string index arguments (slice,
substring, charAt, indexOf, startsWith, padStart, repeat and the others
in scr_string.c) converts through int64 instead of calling trunc, which
is a libm call on baseline x86-64. Each slice made two such calls.
The frontend's syntax trees and program state refer to each other, so
reference counting frees almost none of them when a build disposes the
frontend. Whether a cycle pass reclaimed them before the build ended
depended on where the collector's schedule happened to be. Passes during
the frontend found its data live and widened the growth fractions, so the
next pass waited until the heap had grown well past its size at disposal,
and the dead frontend and the emitter's garbage stayed until the build
ended.
The native compiler now runs scr_collect_cycles_phase right after it
disposes the frontend, through a host function. The runtime entry point
sweeps, then restarts the growth fractions and the death rule's backoff
from the heap the sweep leaves, as after a productive pass. Under Node
the hook is unset.
When a script thread parks its allocator arena, each class slice still
holds the pages past its bump pointer that were faulted in but never
carved: the rest of the huge page the bump pointer is in, and the pages
the pre-fault helper faulted in ahead of it. The same applies to region
chunks when the thread exits. Until another thread claims the arena,
nothing carves there, so a program that continues on threads that never
claim it (parse threads ahead of a check on the main thread) kept them.
- scr_sa_park_arena returns those pages with MADV_DONTNEED. The next
thread to claim the arena faults them in again as it carves, and they
read as zero, as fresh pages do.
- scr_rgn_thread_exit returns each chunk's pages past its bump on worker
threads; nothing carves in an exited thread's regions.
- scr_mem_release_ahead drops pre-fault requests that overlap the range
and waits for one in progress before returning it, so the helper does
not fault the pages in again.
- scr_alloc.test.c checks that a worker that shares and carves past the
huge-page switch leaves no resident pages past its frontier, keeps its
blocks, and that the next thread carves on from the frontier, zeroed.
A generic arrow or method held by an object literal is also lowered with
its type parameters erased, for the literal's own value. In that lowering
the object it returns builds as a checked-dynamic value, so a callback
member with an optional or defaulted parameter, such as
`decode(value, at = [])`, met a slot type that maps to a dynamic value.
The completed-signature check refused it with SC1090, although the same
code in a standalone generic function compiled.
A slot whose type maps to a dynamic value now takes the function the same
way an 'any' slot does: the value boxes into a checked-dynamic function,
which completes omitted arguments with undefined.
Fixes#1373
Co-authored-by: blurname <50226652+blurname@users.noreply.github.com>
TypeScript accepts parentheses and non-null assertions around the
reference an assignment, a `++`/`--` update or a for-in/of head writes
(`(x) = 1`, `h.sums[k]! += 10`), and both are erased at run time. The
compiler now reads its source files with the AstFile writeTargets option:
the writer's `left`, `operand` or `initializer` is the reference itself
and the reference's `parent` is the writer, so lowering and every analysis
of a binding's or member's writes see one shape. The reference keeps its
own source span. Files read without the option, such as type nodes from
the checker and the AST conformance tests, keep TypeScript's exact tree.
Fixes#1625
Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
ClassDynamicDispatch.process runs once per reachability wave and builds
dispatch arms for every boxed class. Several lookups inside it grew with
the program and repeated for every dispatch or member:
- propertyReceivers was recomputed at every call site, including once
per property dispatch. It now returns its last plans for a mode while
the boxed classes, the registered classes, the subclass links
(Lowerer.subclassLinks) and the constructed classes
(Lowerer.constructedClassCount) are unchanged, and each boxed class
still maps to the same ClassInfo. In canonical mode it also skips the
subtree of a class it has already seen, which adds no plans.
- The property dispatch loop tests the answered-class set before the
member lookups, and the method and protocol loops test candidates in
a Set instead of with Array.includes. A dynamic method call scans the
boxed constructors for a static method only when no instance candidate
answers the method, the only case that reads the result.
- Lowerer.liftedFnNamed indexes liftedFns by name and replaces the
linear liftedFns.find scans in the dispatch, prototype, descriptor and
method-value helpers.
The emitted IR is unchanged.
A warm build that finds the program's LLVM in the cache read the file into
one string before emitting the program object. A program whose LLVM is
past the engine's string limit (three.js nodes, 816 MB) failed there with
`RangeError: Invalid string length`, though its cold build, which keeps
the LLVM in parts, succeeds. The cache hit now reads the file with
readTextParts: parts of at most 64 Mi UTF-16 units, decoded across reads,
none ending inside a surrogate pair, which the emitter takes as a cold
build hands them over.
A WebAssembly module the build finds but does not translate (one that uses
a proposal outside the translated feature set, an invalid one, or one the
translator rejects) now rejects with a CompileError that says the module
was found and names the reason, for example "it uses simd, outside the
translated WebAssembly feature set". Bytes the build did not see keep the
existing message.
The build emits the hash, length and reason of each such module in a
second table, scr_wasm_untranslated, next to scr_wasm_registry; the runtime
reads it when a lookup misses. WebAssembly.validate still answers false for
these modules, and the error stays a catchable CompileError.
Fixes#1617
Co-authored-by: jlucaso1 <55464917+jlucaso1@users.noreply.github.com>