Commit Graph
8 Commits
Author SHA1 Message Date
Chris Tate de08ab1920 v0.3.5 2026-10-16 10:33:14 -05:00
Chris Tate bf050d1077 v0.3.4 2026-10-16 01:29:54 -05:00
Chris Tate 8dc5dc1249 Refuse InternTable array keys that are too long to encode
Encoding an array key added each element's size to a running length
with unchecked arithmetic, and wrote into a fixed stack buffer whenever
the sum fit. A sparse array with a large length (2^29 elements or more)
wrapped the sum on 32-bit targets, so the write landed before the buffer;
on every target the encoder also visited every index of such an array.

Array keys now have at most 65536 elements, and their encoding at most
2^31 - 1 bytes. The size is computed first with sums that cannot wrap,
the buffer is sized to it, and keys over either limit make the lookup or
insert throw a TypeError before anything is read or written. The Node.js
implementation throws the same TypeError for keys of more than 65536
elements. Sparse elements are walked in one pass in index order, and the
index growth computation checks its size arithmetic too.
2026-10-15 21:44:38 -05:00
Chris Tate ee49946d32 Support array keys in InternTable and free replaced table indexes
InternTable keys can be arrays of numbers or of strings, compared by their
elements: numbers by SameValueZero, strings by content, and holes equal to
each other only. The runtime stores an array key as one byte string that
encodes its elements; the Node.js implementation encodes it into a Map key
the same way.

Replaced table indexes are now freed. Every thread that uses a table
records the epoch it read at the start of each lookup and insert, and an
index retired in an earlier epoch than every recorded one is freed. Worker
threads stop holding retirements back when they exit.

Number indexes keep a tag byte per slot ahead of the key and value pairs,
so a lookup of an absent key reads the dense tag bytes instead of pairs.

getOrInsertComputed lowers an arrow callback with an expression body in
place, with its parameter bound to the key, so interning through
`(key) => new T(...)` creates no closure.
2026-10-15 20:53:19 -05:00
Chris Tate 94b38475b1 Add InternTable, a shared append-only table of published values
@scriptc/experimental exports InternTable<K, V>: a named table that every
thread of a scriptc program shares and that each thread has its own copy
of under Node.js. Keys are strings or numbers with Map equality; values are
published (@scriptc/threads) when stored, and the first stored value of a
key is kept. Members are get, has, size, getOrInsert and
getOrInsertComputed.

The runtime table (scr_itable.c) splits each table into 64 segments.
Lookups take no lock and do not write; inserts take a segment's spin lock
for a probe and a few stores, and callbacks run without a lock. Number
tables keep keys and values in the index; string tables keep entries in
segment-owned chunks behind tagged slots. Replaced indexes are kept.

The compiler lowers the class to a synthetic class with one f64 handle
field and five intrinsics, with interned helpers per member and type and
an in-place lowering of getOrInsertComputed that creates a function
expression callback only on a miss. Stored values go through the publish
intrinsic, so their types get the write guard. scriptc refuses names that
are not string literals, one name opened with different type arguments,
and key or value types a table cannot store.

scr_publish walks small graphs in inline buffers and reads
SCRIPTC_PUBLISH_TRACE once, instead of allocating and calling getenv on
every call.
2026-10-15 19:59:56 -05:00
Chris Tate 2f8aea3279 v0.3.3 2026-10-15 11:23:16 -05:00
Chris Tate 6cc88fbf11 v0.3.2 2026-10-14 09:22:56 -05:00
Chris Tate 090ccabb72 Add LifetimeRegion to @scriptc/experimental 2026-10-13 21:12:20 -05:00