Commit Graph
24 Commits
Author SHA1 Message Date
Chris Tate 970a53c1ba Store (T | undefined)[] elements as T so widened arrays stay one array
A number[], string[] or boolean[] seen as (T | undefined)[] through a
cast, a binding or a parameter is the same array in Node: undefined
written through either name is visible through both. The type mapper
now stores (T | undefined)[] of a number, string or boolean T as T
elements, which hold a present undefined by their slot state, so the
wider name shares the storage instead of converting to a separate
union array.

Writers of these arrays store undefined as a present undefined:
element writes, push, unshift and splice, literals, Array.from (with a
length or a mapper), Array.of, generator drains, flatMap, rest packs,
copies from Map and Set values and Object.values, and checked
conversions from dynamic values. Promise.all over Promise<T |
undefined>[] fills the result inside the combinator's settlement, so it
settles on Node's turn. The element-state analysis counts callbacks and
iterables whose checker type admits undefined.

Readers keep their optional reads: index reads through a union of
arrays, searches with a direct needle, and asserted reads, which throw
a TypeError for undefined. Comparators and callbacks that receive
present elements take the element type, without an adapter.
2026-10-24 00:05:48 -05:00
Chris Tate 8b6d3329f4 Hold an element assignment's receiver against its index's effects
When the index assigns the receiver's binding (`a[(a = b, 0)] = c[0]!`),
holding the index alone left the receiver read at the store, after the
index ran, so the value landed in the replacement array. The receiver is
now held whenever the index or the value could change what it reads.
2026-10-23 13:00:50 -05:00
Chris Tate d8816b4731 Evaluate an element assignment's receiver and index before its value
An element assignment whose value is an optional element read or call,
such as `out[n++] = src[n]!`, held the value in a temporary and then
evaluated the receiver and index inside the store, after the value. So
`n++` ran after `src[n]` was read, and `src[0]` was stored where Node
stores `src[1]`. The receiver and index are now evaluated first whenever
the value could change what they read: when it assigns a binding they
read, or runs code that can assign their `let` and `var` bindings or
fields. A literal, or a binding the value cannot change, is still read
at the store.
2026-10-23 12:05:34 -05:00
Chris Tate 79684d0ef4 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). 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 (each with its kind, name and
ordinal) and the node's ordinal among same-kind nodes inside the nearest
one, so an edit outside that declaration leaves the name unchanged.
Interfaces and type aliases anchor the positions inside them, and default
library files are named by their base name, so tsgo and tsc-ts, which
install them in different directories, give the same positions.

Recursive record and union placeholders are named by the printed type, 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. A generic class instance keyed by a
literal type argument is named by the literal's text or the argument's
description instead of its checker type id. Two different keys with the
same name throw an InternalCompilerError.

The SC6101 performance warning recognizes the new closure names
(fn.<hash>, with _<name> for a named function expression) and lists the
functions that rank alike in source order. 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-22 03:44:38 -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 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
Malte UblandClaude Opus 5.5 52f9bf284f Destructure a match-or-empty-string union by code point
`const [hit] = re.exec(s) || ""` destructures `RegExpExecArray | string`:
the string arm answers its code point at each position, or undefined past
its end, as the string iterator does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:40:56 -07:00
Malte UblandClaude Opus 5.5 33588981ae Bake mixed JSON tables and destructure unions of arrays and tuples
JSON documents whose rows mix strings and numbers (`(string | number)[][]`,
`(string | number[])[][]`, the character-set and code-point tables of
iconv-lite and tr46) now bake: comptime literals take union-typed elements
and pick each value's arm from its JSON kind and contents.

Array destructuring of a union of array and tuple types
(`const [m, a, b] = re.exec(s) || ["", "", ""]`) reads each position from
the arm the value holds: a tuple arm answers its field or undefined past
its end, an array arm its element or undefined past its end.

Unions of tuple types no longer pick the checker's `length` literal as a
discriminant: tuple shapes have no such slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:59:40 -07: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 c6a720c1e0 Update array elements natively and share optional returns across overrides 2026-10-12 20:57:04 -05:00
Chris Tate 7e6294ca0f Make scriptc compilation native-only 2026-10-12 12:25:30 -05:00
Malte Ubl 7d08a7441c Truncate inline on x86-64 and test typed-read presence without trunc
Baseline x86-64 has no rounding instruction, so every llvm.trunc became
a call to libm's trunc. The typed-array read helper paid that call on
every read: it decided presence with `i >= 0 && i < length &&
Math.trunc(i) === i` before the inline element load.

- Math.trunc (and the DataView offset truncation) now take an integer
  round trip on x86-64 targets: values of magnitude below 2^52 go through
  fptosi/sitofp with the sign restored by copysign (so -0.5 and -0 give
  -0); larger values, NaN and the infinities are already integral and
  keep their value. Other targets keep llvm.trunc, which is one
  instruction there.
- The ToUint32 slow path is only reached for magnitudes above 2^53 (and
  non-finite values), which are integers, so its truncation was a no-op
  and is gone.
- For integer element kinds, the read helper performs the element read
  that yields NaN for an invalid index (one inline index check) and
  answers undefined exactly when that read is NaN: an integer element is
  never NaN. Float element kinds keep the range and integrality test.
2026-10-10 14:03:20 -07:00
Chris Tate 860f9339ec Keep shared read and format helpers out of source line tables
Indexed reads that can miss lower through shared helpers (`%arr.idxOr`,
`%bytes.idxOr`), and printing a value that can be undefined goes through a
shared formatter (`%util.fmtv`). Each helper took the location of the read
or print that first created it, so its body carried that line's debug
locations. A breakpoint on such a line then resolved to every helper as
well, and the debugger stopped inside helper code where the user's
variables are not in scope.

The helpers are compiler code, so they now get a location without a
source position (helperLoc). They emit no debug info, while each call
still carries the user's location, so a breakpoint on the line stops only
at the read or print itself.
2026-10-09 14:50:28 -05:00
Chris Tate cd28e4d6ca Answer undefined for out-of-range typed array reads like Node (#797) 2026-10-09 11:37:16 -05:00
Malte Ubl 605618eae1 Fuse Number(strings[i]) with the element read
Number() of an optional string element read no longer boxes string | undefined
into a dynamic value: plain-operand reads test the slot state and convert the
element with num.fromString, answering NaN for missing slots.
2026-10-08 15:06:48 -05:00
Malte Ubl 312928b100 Hoistable array guards and fused truthiness of scalar element reads
Read-only fallbacks (get_f64/bool/number, state, has, borrow) carry
memory(read, inaccessiblemem: readwrite) at the call site and the guard loads
the storage bases before branching, so LLVM hoists header loads out of
loops. Conditions on number[]/boolean[] element reads no longer box
T | undefined: numbers reuse getNumber (missing is NaN, falsy like undefined),
booleans test the slot state then read the value.
2026-10-08 15:06:48 -05:00
Chris Tate 1f10e83720 Promote canary to main (#738)
- Preserve callback identity, receiver binding, and class references across unions and collections.
- Track record field presence independently of undefined values and improve native runtime behavior.
- Expand differential coverage and streamline CI bootstrap and compiler rebuild checks.
2026-10-08 11:00:13 -05:00
Chris Tate c08ab955ae Enforce linting and consistent source formatting
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks.
- Preserve compiler import boundaries and protected fixtures while cleaning up lint findings.
- Standardize source formatting and validate import, fixture, and embedded-source protections.
2026-10-05 17:51:24 -05:00
Chris Tate 665db543e2 Organize compiler conversions and builtin dispatch
- Separate conversion planning, helper generation, and timer lowering into focused modules.
- Share array, closure, record projection, and builtin dispatch logic, including timer namespace calls.
- Validate shared helpers with native correctness and differential coverage.
2026-10-05 16:21:22 -05:00
Chris Tate 3f56576644 feat(compiler): rebuild the frontend with native compiler stages (#568)
- Share frontend loading, package selection, contracts, and FFI parsing between Node and native clients.
- Preserve collection layouts and optional method returns while lowering long nullish chains without recursive stack growth.
- Rebuild the frontend from TypeScript through native lowering and C emission with matching output.
2026-09-29 07:45:18 -05:00
Chris Tate 3af05d9fbc feat(compiler): lower the complete frontend statically
- Complete frontend lowering with zero rejected statements, dynamic islands, or skipped functions.
- Support contextual construction, presence guards, and union record copies used by the compiler.
- Add native CommonJS resolution and cross-platform file URL handling while preserving Node host behavior.
2026-09-28 23:49:13 -05:00
Chris Tate bf21f47827 Compile collection metadata and heterogeneous JavaScript tables natively
- Preserve async factory results and mixed table layouts through inference and destructuring.
- Support checked Map and Object.fromEntries construction with native weak collections.
- Keep native weak-key lifetimes and engine-backed collection identities intact.
2026-09-28 22:20:24 -05:00
Chris Tate 2bd2e6f54c Refactor container lowering into focused modules (#325)
* refactor(compiler): split container lowering modules

* chore: clear remaining lint errors
2026-09-14 21:33:20 -05:00
Chris Tate 94fd361b54 Implement sparse native arrays and missing-value parity (#297)
Represent ordinary JavaScript arrays with hole, value, and explicit-undefined states; add sparse side storage and carry missing values through the frontend, native backends, runtime, and Node-facing consumers.
2026-09-12 22:39:07 -05:00