Commit Graph
13 Commits
Author SHA1 Message Date
Chris Tate 92032a1b11 Shrink Map and Set headers and small entry arrays
A Map or Set header held six adapter pointers, the union key table and
size_t counts: 168 bytes, 176 in its size class. The adapters and union
key kinds now live in an immutable per-type ScrMapOps descriptor that the
compiler emits once per collection type, a traced byte replaces the two
trace-pointer tests, and the counts and capacities are 32-bit. The header
is 88 bytes.

The entries array starts with one slot and grows to four, then doubles,
and maps of up to 8 entries scan their cached hashes instead of keeping a
bucket table. Number-key maps still move to the direct index from four
entries.

One million maps held at once (RSS per map): empty 188 -> 100 bytes,
1 entry 286 -> 125, 4 entries 286 -> 198, 8 entries 450 -> 295.
2026-10-16 07:27:17 -05:00
Malte Ubl f04681f0b6 Reject absent map keys through a filter in the map header
Every live entry's hash sets two bits of a new 64-bit ScrMap.filter, so a
lookup whose bits are not both set is absent without loading the bucket
table or the entries. String-keyed lookups in tsc-ts are mostly misses
(Map.get 67% on Excalidraw, has 87%), and the filter rejects 84-88% of
the misses on non-empty maps. Deletions leave their bits; rebuilds,
compaction and clear recompute the filter from the live entries.

Lookups also return the entry index straight from the probe instead of
reading the bucket again. The filter sits after nlive, so the backend's
%ScrMapIx fields for entries and the direct index move by one.
2026-10-12 07:22:52 -07:00
Chris Tate 72cc828ab0 Merge main into perf/compact-array-header-2 2026-10-12 01:12:21 -05:00
Malte Ubl fa55ce6b07 Keep the RC entry points of array elements in one shared descriptor
Every SCR_ELEM_REF array stored its element type's retain, release and trace entry points in its own header (24 bytes), so ScrArr was 80 bytes: an 80-byte block for arrays of acyclic elements and a 96-byte block for cycle-capable ones. The three pointers now live in one constant ScrArrOps descriptor per element type, which the compiler emits once per module (@sc_arrops.*) and the runtime keeps static, and the header holds a pointer to it plus a 4-byte `cyc` flag beside `elem` that the inline RC fast paths test. ScrArr is 64 bytes: arrays take 64- and 80-byte blocks.

scr_arr_new_ref(retain, release, trace, cap) becomes scr_arr_new_ops(ops, cap); scr_set_to_arr_ref takes the result's descriptor from its caller instead of building an array from the set's key adapters. The backend names the %ScrArr fields in SCR_ARR_FIELD. The layout change bumps the runtime ABI marker to scr_runtime_abi_v13.
2026-10-11 19:46:48 -07:00
Malte Ubl 9b98571b4a Retain string-keyed map reads inline
Map.get of a reference value under a string key called scr_map_get_str_ref,
which retained the hit through the collection's value adapter: an indirect
tail call into an out-of-line retain function on every hit (in tsc-ts the
symbol tables' Symbol and Type retains, 0.4% of samples, were only reached
that way). Number keys already read the entry and retain with the value
type's own retain. String keys now do the same: scr_map_entry_str returns the
entry, and the generated code loads the value slot and retains it inline.
Both the ordinary and the stack-boxed (local) reads take the entry path.

Corpus 4651 keeps string-keyed results alive past delete, overwrite, clear
and the map itself for instances, records, arrays, nested maps, cycles and
union values; dropping the inline retain makes it (and 523) fail under ASan.
2026-10-11 06:42:21 -07:00
Malte Ubl 3955982b41 Index number-keyed Maps and Sets directly when keys are small integers
Maps and Sets with number keys used as id tables (tsc-ts keeps node and
symbol links in Map<number, Links>) hashed every key as a double. They now
keep a direct index, dense[k] = entry index + 1, while every live key is an
integer in [0, ndense) and the table stays within 8x the live count (64
slots minimum). The entries array, tombstones and iteration order are
unchanged; only the lookup index differs.

-0 lands on slot 0 (SameValueZero), and NaN, fractions, negatives and keys
beyond the table are absent by construction. Inserting a key the table
cannot hold moves the map to the bucket table; whenever that table would
grow, compact integer keys move it back, so maps whose ids start far from
zero switch over once their live count catches up. scr_map_entry_f64 and
layout asserts expose the index to generated code.

A randomized C test compares every operation with an ordered reference
model across linear, direct and hashed indexes, and a corpus program pins
the JavaScript-visible semantics around the transitions.

Only growth moves a map to the direct index: an append that
scr_map_reserve_append would absorb by compacting tombstones (a linear map
with a dead entry, a bucket table at most half live, no iteration active)
keeps the map's compact storage, so churn on a small number-keyed map no
longer allocates a table. While an iteration is active nothing compacts,
so a small map promotes to the direct index (integer keys) or the bucket
table (other numbers) without moving entry indices. The runtime map test's
small-storage script, which assumed bucket promotion and failed its two
live-iteration checks, now runs with integer and fractional keys and
checks both transitions, and the corpus program covers small maps growing
and clearing mid-iteration.
2026-10-09 10:10:20 -07:00
Malte Ubl f7ac13e87f Cache string Map key hashes in the string header
On 64-bit little-endian targets ScrStr's capacity word is split into a
32-bit cap and a 32-bit cached Map key hash (header stays 24 bytes).
Heap strings fill the hash on first lookup; every in-place mutation path
(unique concat, split scratch reuse, regrow, spare reuse) clears it.
Immortal strings are never written: compiler literals and the runtime's
one-character/empty statics carry the hash precomputed (string-key-hash.ts
mirrors scr_key_hash; both sides pin the same vectors).

scr_key_hash seeds by length multiplicatively: XORing the length into the
low bits cancelled against the last byte ("ab" and "abc" collided).

Map string lookups inline the probe into each getter and reject linear
entries by hash; set specializes its probe/overwrite by lookup kind with a
shared append path.

Tests: runtime map.test.ts (pinned vectors, statics, in-place append
invalidation), string-key-hash.test.ts, corpus 3141-map-string-key-hash
(plain + sanitized; mutation-checked against both invalidation paths).
2026-10-08 15:40:53 -05:00
Chris Tate c484422113 Optimize split keys and Map storage (#718) 2026-10-07 01:57:01 -05:00
Chris Tate f7bd84ce3f Optimize keyed lookups and string bookkeeping
- Share bounded byte-key handling across collections and checked objects.
- Specialize collection probes and streamline string-index cleanup.
- Cover key boundaries, collisions and cache lifetime behavior.
2026-10-06 20:25:14 -05:00
Chris Tate 0ceb78f886 Reduce ownership traffic and allocation for native collections
- Borrow rooted collection receivers across safe helpers and typed updates.
- Keep small Maps and Sets compact through cloning, growth, and live iteration.
- Preserve ownership at reassignment, exception, and capture boundaries.
2026-10-05 21:50:11 -05:00
Chris Tate 4427f1fb5a Expand native collection keys and construction
- Support boolean, bigint, and mixed value keys in native Maps and Sets.
- Reuse key hashes and copy collections directly into compact storage.
- Preserve collection identity, ordering, iteration, and ownership behavior.
2026-10-05 09:32:14 -05:00
Chris Tate d3ecb0fc9d feat(compiler): advance self-hosting with identity collections (#500)
* feat(compiler): advance self-hosting with identity collections

- Support reference identity in Maps and Sets, including unions and cycle cleanup.
- Run production integer-range analysis natively with typed IR traversal.
- Add differential coverage for native analyses and collection behavior.

* chore: ignore generated output directories

- Exclude generated output directories from version control.
2026-09-27 20:02:13 -05:00
Chris Tate 23d5918a53 init 2026-07-22 18:08:04 -05:00