v0.1.38530 Oct 2026·Rendering, JavaScript & JIT, Memory & reading
Ahead of the reference runtime on 7 of 8 workloads
A JavaScript speed and memory release with 32 changes. Floati is now faster than the reference JavaScript runtime on 7 of the 8 standard workloads (0.1.384: 6 of 8): building trees of small objects went from 3.8 ms to 0.8 (the reference runtime takes 0.85) because the objects a call leaves dead are handed back a page at a time, and reading the properties of 2,000 objects went from 3.6 ms to 2.8 (reference 2.6, the last workload still behind). Arrow functions called from compiled code take the direct path, loops at the top of a script run compiled, and eight memory and test fixes found along the way are included.
Seven of eight standard workloads faster than the reference runtime
On the eight standard workloads (median of three runs, best of five timings each, same Mac) Floati is ahead on seven: numeric 2.4 vs 3.3 ms, recursion 1.6 vs 2.0, bitwise hash 3.6 vs 4.5, string building 1.0 vs 1.6, building a tree of small objects 0.8 vs 0.85, Map 12.4 vs 13.4, array callbacks 2.1 vs 2.3. The tree row was 4.6 times behind in 0.1.384. The gains came from the optimizing compiler taking on more (factory calls, string and null fields, nested arrays), arrow functions calling directly, and calls handing back their dead objects whole.
Building a tree of small objects3.8 ms -> 0.8 (reference runtime 0.85)Object properties3.6 ms -> 2.8 (reference runtime 2.6)Workloads ahead of the reference runtime6 of 8 -> 7 of 8
Dead objects handed back a page at a time
A function that builds a tree of objects and returns a number leaves the whole tree dead at its return, and freeing it object by object was about three quarters of its time. Floati's optimizing compiler now proves when everything a call builds dies inside it; such a call builds on memory pages of its own and hands the pages back whole when it returns. Anything unusual (an error, a value that escapes, a full log) ends the region the ordinary way, so nothing is freed early. Constants and table strings go in without reference counts, and pages are parked between calls instead of re-prepared.
Build and drop a small tree, 1,000 times6.8 ms -> 2.8Peak memory on that test128 MB -> 125 MB
Still behind on two measurements
Reading the properties of 2,000 objects takes 2.8 ms against the reference runtime's 2.58 (1.09x). The loop itself now matches it element for element; the rest is the cost of each call from the test's driver loop into the compiled function. With ten times as many calls in a row Floati is ahead (30.2 ms vs 35.6). Building the small tree 1,000 times in a row takes 2.8 ms against 2.17. Both are the next work.
Object properties2.8 ms vs 2.58 (1.09x behind; 3.6 in 0.1.384)Small tree, 1,000 times in a row2.8 ms vs 2.17 (1.3x behind)
What changed 32 changes
JavaScript & JIT
Floati's optimizing compiler builds the object a small factory function returns right where it is called (`mk(a, b)` returning `{ x: a, y: b }`): a loop pushing such objects 2.85 -> 1.55 ms. If the name stops pointing at that function, the code falls back.
Rendering
FIX (tests): the source-packing test raced the background packer and failed about one run in six; it now checks the texts are packed, whoever packed them.
JavaScript & JIT
The optimizing compiler builds objects and arrays holding string constants, null, nested array literals and `'t' + i` strings: rows of `{ key: i, cls: 'row', nil: null }` 2.9 -> 2.0 ms, building a small tree 1,000 times 12.9 -> 10.7 ms.
Memory & reading
Freeing a dead tree releases each value in place and frees plain dead arrays directly, without the general path: building and dropping a small tree 1,000 times 10.7 -> 8.6 ms.
JavaScript & JIT
Two instruction-level tightenings in the compiler's back end: a not-and-zero test is one instruction, and a test against zero branches in one instruction. Object properties 3.67 -> 3.53 ms.
JavaScript & JIT
Array reads the loop's own test already keeps in range skip their bounds check: object properties 3.63 -> 3.48 ms.
JavaScript & JIT
FIX: one frozen array, or one array with an index getter, anywhere on a page made every compiled array loop give up on every call. Now only loops over that array do, as the interpreter's own readers do.
JavaScript & JIT
Numbers stored in object fields are checked with a single floating-point compare in compiled code (only a NaN looks further): object properties 3.73 -> 3.37 ms.
JavaScript & JIT
Numbers in array elements get the same one-compare check: an array sum loop 5.22 -> 5.05 ms.
JavaScript & JIT
The compiler no longer shares a branch's compare with a copy in cold code, so the hot path keeps its fused compare-and-branch: object properties 2.88 -> 2.79 ms.
Rendering
FIX (tests): an edge-case map printed its answers on one line, so the tier test matrix flagged it in all 20 configurations; it prints one answer per line now.
Memory & reading
Arrays the cycle collector still held a weak handle to are freed with their elements in one pass instead of one by one: the small-tree row 8.8 -> 7.6 ms, peak memory unchanged.
Memory & reading
Groundwork: array boxes get their own allocator size class, so the allocator can tell an array from an object by its page (neutral on speed; switchable).
JavaScript & JIT
Arrow functions called from compiled code now take the direct call path (they waited for a verdict that never came and went the general way every call): `() => 1` 60 -> 4.4 ns per call, object properties 3.0-3.2 -> 2.8-2.9 ms. Every page uses arrows.
JavaScript & JIT
A compiled call site enters its callee's compiled code directly once the callee has been compiled, instead of going through a helper every time: a two-level call 32 -> 21 ns, building an array from a range 33% faster.
Memory & reading
Call regions: when the optimizing compiler can prove everything a call builds is dead when it returns, the call builds on pages of their own and hands the pages back whole at the return, with no object visited one by one. Building and dropping a small tree: 2.2 -> 1.35 ms (reference runtime 0.85), 1,000 times 6.8 -> 4.4 ms, peak memory 128 -> 125 MB.
Memory & reading
FIX (memory): ending a call region took a fresh page for itself, leaking two pages per call (memory grew to 401 MB and the background collector retired the code). Found while building call regions.
Memory & reading
FIX (memory): each compiled function that used call regions kept its own 64 KB log, leaked again on every recompile. There is now one per thread.
Memory & reading
String constants go into a call region's objects without a reference count, since the running code keeps them alive anyway: the small-tree row 1.3 -> 1.1 ms, 1,000 times 4.4 -> 3.7 ms.
Memory & reading
Call regions park their memory pages between calls instead of retiring all 51 size classes twice per call: the small-tree row 1.15 -> 1.0 ms, 1,000 times 3.75 -> 3.25 ms, peak memory unchanged.
Memory & reading
FIX (tests): the law for parked pages now fails when the guard it checks is removed (it passed either way before).
Memory & reading
Strings a call region reads from a call site's table are borrowed instead of counted (about 7% of the small-tree row).
Memory & reading
FIX (memory): a call region whose constants log filled up still reset at its return, so the counts it had taken were never given back. A full log now ends the region the ordinary way.
Memory & reading
FIX: the mid-loop entry of a call-region function opens no region but stored values as if it did; its stores now count as outside a region.
JavaScript & JIT
A hot loop in a script's top-level code that calls functions runs compiled: the loop is given a function of its own over the script's variables, with its exits and completion value kept. 20,000 calls from such a loop: 31.4 -> 30.2 ms (the reference runtime 35.6).
JavaScript & JIT
Counted loops in the optimizing compiler check the step budget once per stretch of turns instead of every turn (the budget still counts every turn, and the check comes on the same turn as before): 3 fewer instructions per turn, 12% fewer in the object-properties loop.
JavaScript & JIT
A number read's rare NaN check sits outside the loop's straight path, so ordinary numbers take no jump.
Memory & reading
Design notes: what the next memory model trusts about reference counts (weak handles, side tables, in-place reuse) and the fix for each, measured before building.
Memory & reading
Design notes: three free-path tunings measured flat, and the smaller plan that replaced them.
Memory & reading
Design notes: the smaller plan refined (uncount only when an object is built, count again at its holder's first change).
Memory & reading
Design notes: a fresh-bit allocator patch measured 5% slower on its own and parked until it can be measured inside the full change.
Memory & reading
Design notes: call regions, a narrower version of the memory model for code the optimizing compiler owns end to end.