Relational string operators (`<`, `<=`, `>`, `>=`) compared the UTF-8
bytes, which is code-point order. Node compares UTF-16 code units, so the
results differed for characters above U+FFFF against U+E000..U+FFFF and
for lone surrogates against surrogate pairs. The default `sort()`
comparator, `util.inspect` with `sorted: true` and dynamic comparisons
already used UTF-16 order; the default comparator of a dynamic array's
`sort()` did not.
Typed comparisons and the dynamic array sort now use `scr_str_cmp_u16`,
and the IR's `strCmp` node no longer has a `utf16` flag. The two orders
agree unless both of the first differing bytes are at least 0xED, so:
- the inline wrapper decides differing first words, or last words of
equal lengths up to 16 bytes, in byte order when neither word has a
byte of at least 0xED (a SWAR test). Other differing words, and the
first bytes of shorter strings, decide by their first differing byte:
in byte order when one of the two is below 0xED, and by plane when both
are lead bytes from 0xEE to 0xF4 (a supplementary character orders
below U+E000..U+FFFF);
- otherwise a proven-ASCII string on either side compares with `memcmp`
(`scr_str_cmp`), and other pairs call `scr_str_cmp_u16_bytes`. It finds
the first differing byte with words up to byte 32, then with 32-byte
SSE2 or NEON blocks where the target has them. The call is marked as
only reading its arguments, so repeated comparisons of one pair merge.
- `scr_str_cmp_u16` itself takes the same `memcmp` path for proven-ASCII
strings.
The native compiler sorts record fields with `<` and checks the order
with `sort()`, so it reported SC9001 for 5888-inspect-sorted.ts, whose
object has the keys "\u{1F600}" and "\uffff". 5888 runs in the
native-frontend self-host check. Adds corpus 6023.
The native compiler failed on corpus programs that the Node-hosted
compiler compiles. Four compiler causes:
- A record that width-lifts into several members of a discriminated
union had no conversion plan. As a field of a larger record, that
removed the outer member it belonged to, and the outer record could
then lift into the only member left: a `recordSet` statement built with
a nested `recordGet` literal became a `break` statement, and a switch
on `kind` read it with the `recordSet` layout. The native compiler
stopped at the narrowing guard on 5585-record-results-inlined.ts and
similar programs. Such a record now converts through the discriminant:
each member it width-lifts into is a route, and the value's
discriminant selects the member at run time, as same-layout members
already do.
- Three compiler paths asserted a value that can be null, and the native
compiler threw "null is not representable in the target union":
stableCreateRequireBindingReason7 (`cache.get(decl)!` from a cache that
stores null for stable createRequire bindings; 2631-create-require,
2932-create-require-program), the Int32 contract check (`catchLocalId!`
for a bindingless `catch {}`; 4970-int32-call-abi) and
StringIndexBounds.binding (a memo that stores null). The caches are
read without assertions and the catch test checks for null.
- scriptc adopts alwaysStrict, esModuleInterop and
allowSyntheticDefaultImports from a project's tsconfig, but the TS 7
SDK schema no longer declares them. A native compiler keeps only
declared fields when it builds or parses a typed record, so these
options were dropped, and the default-import check refused
`import fs from "node:fs"` (SC1012, 2391-default-builtin-imports and the
3055 to 3057 module-setting variants). The options a project reports
are parsed with these fields declared, and Ts7CompilerOptions uses the
same type.
- An object-literal method kept its inferred return type where an arrow
function or function expression takes the return type of its
contextual signature. For a method that returns an array of statement
literals, a `varDecl` literal with `init: null` was converted to the
member whose `init` holds only expressions, and the conversion threw
(Array.fromAsync's array loop: 5854-array-from-async,
5855-array-from-async-js). A synchronous object-literal method now
takes its slot from the property of the same name on the literal's
contextual type and adopts its return the same way.
Adds corpus 6022 and 6024. 2391, 2631, 3055, 4970, 5585 and 5854 run in
the native-frontend self-host check, which now also links the dyn-async
unit when the program uses it, as the production build does.
Each record shape's enumeration table now follows the key order its object
literals, spreads, rest bindings and record copies create, and appends
optional keys assigned after construction. Shapes whose values are built
in more than one order keep their declaration order and SC6002.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The self-hosting harnesses link the IR they lower through compileC. Most
hand-picked a subset of runtime feature flags, which lacked dynTier: a
sanitized build compiles exactly the listed runtime sources, so programs
with jsval operations failed to link scr_js_value.c's entry points, and
harnesses passing no flags at all failed in both lanes.
Every such site now spreads moduleLinkFeatures (tests/harness/
link-features.ts), which is executableLinkFeatures, the selection
compile() uses, so a runtime unit added later cannot drift out of the
harness builds. moduleUsesDynTier joins the IR facade's unit gates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
superCallStmt asserted the class base before checking for a callable
base. A class that extends an ordinary function constructor has no base
class info, so the assertion held null. Node ignores the unused value;
the native compiler throws the TypeError of a non-null assertion that
held null, and failed to compile programs with such classes (for example
the Effect stream fixtures). The base is now read after that case
returns.
self-hosting-native-frontend.test.ts was one test of 577 s (plain) and 607 s
(sanitized) in the cost table, and 748 s on san 1/20 in run 38835202089; once
the other long files were spread, it alone bounded the Linux test lane. It
builds the compiler frontend into a native executable, then checks 17 corpus
programs and four other cases with it. Locally the build took about 189 s and
the checks 297 s (14-20 s per program).
The build and the checks move to tests/harness/native-frontend.ts without
changes to what they check; useNativeFrontend builds the executable in a
beforeAll of the calling suite. self-hosting-native-frontend.test.ts,
-programs.test.ts and -checks.test.ts each build once and run programs 1-6,
7-12, and 13-17 plus the four other checks, each check as its own test. The
diagnostics, library contract and FFI checks use project directories of their
own instead of rewriting one shared main.ts.
scripts/ci-test-costs.json replaces the old file's entry with estimates for
the three files (the build share of its measured cost plus each file's share of
the checks); the next refresh measures them, since vitest's file time includes
beforeAll. The largest side-queue share per shard at 20 shards goes from 580 to
367 s (plain) and from 608 to 432 s (sanitized), for two more builds per flavor.