Two JavaScript object literal mappings from #1799 broke packages.
A property initialized to `[]` has the checker type `never[]`, and never's
element maps to f64. A literal with such a property builds as a
checked-dynamic object when it lowers on its own. A literal nested in one
that maps to a record (`{ common: { issues: [] }, path: params?.path || [] }`,
zod 3's parse context) lowers against its record type, so the property was a
`number[]`. `ctx.common.issues.push(issue)` then threw "TypeError: expected
number at $, got object", where Node pushes the record. Such a property now
maps to the checked-dynamic array, as a property initialized to `undefined`
or `null` maps to the checked-dynamic value.
That `undefined`/`null` rule also matched the properties the checker adds
when it normalizes a union of literals: `{ _tag, value } | { _tag, error }`
gains an optional `error?: undefined` and `value?: undefined`. Mapping those
to the checked-dynamic value let `{ _tag, value }` fit both arms, and Effect
4's CLI parser (`consumeFlagValue`) failed with SC2003 "union conversion has
no unambiguous layout mapping". The rule now skips optional properties, so
they keep their unit-only type and each literal converts to its own arm.
The native compiler wrote the module's LLVM text, 150 MB on a large
program such as tsc-ts, by encoding the whole string at once; its frontend
cache entry by first escaping the whole text into a second string and then
encoding that; and it read the written file back whole to key the object
cache. Those copies were the build's peak.
Text parts are now encoded and written in slices of at most 1 MiB UTF-16
units that never split a surrogate pair. The frontend cache entry is
generated while it is written: the LLVM text is escaped slice by slice,
which is the same JSON as escaping it whole, the entry is hashed in the
same pass, and an entry past one native string abandons its write as it
was skipped before. The object cache key hashes the file in 1 MiB blocks.
The files and cache keys are unchanged.
With while-loop versions, `while (pos < end && s.charCodeAt(pos) === 32)
pos++` took a version whose guard could never hold. The counter scan
records only typed-array accesses, so the string read fell back to the
statically ranged check. For a counter held in an int32 slot, that check
asks for a length above 2^31 - 2. The loop paid for the guard and ran its
checked copy.
The counter scan now records `s.charCodeAt(i)` and `s.charAt(i)` of a
string like typed-array reads. Their guard compares the counter's bound
with the string's length, as for a typed array. Corpus 7302 adds while
loops over counters below a string's length.
An array or typed-array index that is an exact integer or NaN, such as an
element read that may miss (`t[i]!`), a `charCodeAt`, or a sum of those, is
a valid index when it is ordered and below the length. The index check now
tests that with one ordered compare and the integer, taken from the value's
32-bit view or from one conversion, instead of converting the index back to a
double and comparing it.
Number locals that are declared once with an initializer, are not captured
or boxed, and are only written such values carry the same fact, so
`const c = t[i]!; table[c]` and `(h * 31 + c) | 0` take the integer paths
that the inline read already takes.
ToInt32 and ToUint32 of a double that no range proves an integer took the
i64 conversion only up to 2^53 in magnitude, and decoded the double's bits
above that. Every double between 2^53 and 2^63 is an integer, so the i64
conversion is exact there too, and its low 32 bits are the result. The bit
decoding now handles only NaN, the infinities and magnitudes from 2^63.
Multiplicative hashes such as `((h ^ b) * 16777619) >>> 0` and
`(h * 1000003 + b) | 0`, whose products usually pass 2^53, convert on the
fast path.
request.end(body) on a request whose head was not sent yet wrote the
head and then the body. On a socket that was already connected (a
reused keep-alive connection), each write is sent at once, so the two
went out as separate segments, and the server could handle the head and
answer before the body arrived. Node sends the head together with the
body. When the client and the server run in one program, the server's
'end' and 'close' for the request and the client's reading of the
response then depended on which socket the poller reported first: corpus
5409-http-held-request-body printed the client's line before the
request's 'end' and 'close' in a macOS dev build.
The head and a body known at end() now go out as one write, as Node
sends them (the server's response already did).
The backend gives every function that returns a complete record literal a
result-slot entry, `%into.<fn>(...params, dst)`, which stores the literal's
fields into caller frame storage. The cycle analysis counted those stores
as writes after construction, so a record type that refers to itself and
is never written after construction carried the cycle collector's header,
its live count and its candidate checks in release builds, even when
nothing called the entry. Dev builds, which add no entries, left it
acyclic.
The destination is frame storage of a caller that only projects the value
(stack-objects.ts): no object can point to it, so its fields cannot lie on
a cycle. computeCycleMutability now skips those stores, under the same
condition emit-statements.ts already uses to emit them as initializations
(resultSlotStore in the new result-slots.ts). Every other store still
makes its field mutable. The same facts mark such fields as never
replaced, so reads of them borrow instead of retaining.
A program that rebuilds a tree of 2^21 - 1 object literals 12 times runs
in 1.19 s instead of 1.35 s and peaks at 68 MB instead of 84 MB: each node
is 32 bytes instead of 40.