zerojs: compiled execution tier over the shared realm
Dense integer-indexed array storage in the runtime (1-shifted Luau
array-part with sparse/named fallback, rt.iget/rt.iset fast paths),
a JS-to-Luau compiler covering the full parsed language against the
same realm value model as the interpreter, and an auto engine in the
api that runs every script compiled-first with a loud interpreter
fallback on compile bail. Full suite green compiled-first; 18x on
array-heavy scripts before type-directed emission.
---
zerojs compiler: local type inference for native emission
Per-function prescan classifies locals provably number-typed (every
assignment produces a number, fixpoint over the candidate set, closures
that mutate a name demote it) and array-typed (array-literal bindings).
Number-typed operands compile to native Luau arithmetic and comparisons,
loop counters increment natively, test positions elide the truthiness
call for boolean-typed expressions, and integer-indexed element access
on known arrays dispatches straight to the runtime's dense fast path.
20k-element fill+sum inside a function: 611ms interpreted, 30.6ms
compiled.
---
zerojs: compiled module evaluation
Module bodies compile to Luau chunks taking (rtc, imports): imports bind
as const chunk locals (assignment traps with the interpreter's exact
TypeError, as do const declarations generally), export declarations
lower to plain statements with an export-name snapshot returned at
completion, dynamic import() and import.meta compile against the realm
hook, and the module graph tries the compiled chunk first with the same
loud interpreter fallback the api engine uses. Module-level bindings are
chunk locals, so the function-level type inference applies to module
code.
---
zerojs compiler: register-pool temps
Transient operand temps come from a per-function register pool declared
once at the function head and reassigned, with the counter resetting at
each statement; construct-persistent values (loop step functions, switch
state, try results) keep dedicated locals. Large minified module bodies
previously exhausted Luau's 200-local limit at loadstring and bailed to
the interpreter; they now compile.
---
zerojs compiler: chunk large literals and argument lists, pooled discards
An array literal or call with hundreds of elements compiled to one Luau
table constructor whose element expressions exhaust the register file;
both now build incrementally past 100 elements. Expression statements
discard through a recycled pool slot (or a bare call statement) instead
of minting a fresh throwaway local each time.
---
zerojs: module graph hands its parsed AST to the compiler
The graph already parses each module for import resolution; the compiled
tier now reuses that AST instead of re-parsing multi-megabyte sources.
---
zerojs compiler: pool watermark recycling and chain slot reuse
Element and property temps in large literals rewind the pool counter per
item; logical chains reuse the left result's slot; operands already held
in a temp pass through instead of copying to a fresh slot. A thousand-
element data literal or a two-hundred-term guard chain now costs a
handful of registers instead of exceeding the register file.
---
zerojs compiler: module-binding spill table
A module whose top-level binding count would exhaust Luau's local or
upvalue register files (200 each) keeps every module-scope binding in a
single spill table: nested functions capture one upvalue instead of one
per referenced constant. Literal and argument fast paths drop to 30
elements so nested constructors stay clear of the register file.
---
zerojs compiler: destination-threaded conditionals, template accumulator
A Cond or Logical in another's arm position writes into the shared
destination temp instead of allocating its own, so right-nested ternary
and guard chains cost one register per chain; arm scratch rewinds once
the result lands. Templates past eight interpolations accumulate into a
single temp instead of one concatenation expression.
---
zerojs compiler: budget every compiled body
A compiled function invoked on a thread with no budget state (a timer or
host callback outside any run) seeds one, honouring the realm's
frame-slicing opt-in. The interpreter budgets every body; a runaway
compiled callback now raises RangeError or parks per slice the same
way instead of holding the frame unbounded.
---
zerojs compiler: parking is exclusive to loop-spawned async bodies
A synchronous compiled callback runs inline on the thread that pumps
the timers; parking there suspends the pump its own resume depends on.
Unseeded callback threads now get a plain RangeError guard, matching
the interpreter's synchronous bodies.
---
zerojs compiler: inline dense element access and loop budget back-edges
A proven array binding with a numeric index reads its dense slot inline
(hole/gap/fractional falls back to the runtime read) and writes inline
for overwrites and boundary appends — a gap write marks the array sparse
so appends near unabsorbed entries reconcile through the runtime. Loop
back-edges fetch the coroutine's budget state once and decrement inline
with a rare slow call. 20k fill+sum: 611ms interpreted, 5.1ms compiled.
---
zerojs: Math intrinsics and own-property fast paths
Math.<fn> on the unshadowed global compiles to the native library call
for the methods whose Luau semantics match JS exactly (floor, ceil, abs,
sqrt, trig, exp, log, pow, zero-arg random); non-number operands coerce
through the runtime. Member reads resolve an own data property in two
table reads before full dispatch, and writes to an existing own data
property store directly — own presence shadows prototype setters, and
frozen objects, proxies, typed arrays, and array length bookkeeping keep
the full path.
---
zerojs: Math.imul/max/min intrinsics
imul multiplies exactly in split 16-bit halves (a double product loses
low bits past 2^53 — mulberry32-class PRNG sequences depend on them);
two-argument max/min coerce and propagate NaN per JS. One runtime call
replaces the global-lookup + property-get + method-call chain that
dominated seeded world generation.
---
zerojs: callback guard budget scales with the realm
The unseeded-thread guard defaulted far below the budgets hosts
configure for their runs, so a long (finite) synchronous callback
tripped RangeError where the interpreter's inherited budget survived.
The guard now reads the realm's callbackBudget knob, defaulting to the
same scale hosts use.
---
fix(compiler): ctorRet only applies to the constructor body itself — an arrow nested in a derived constructor returns its own value; regression tests
---
modules: cross-VM compiled-chunk cache — content-hash keyed; a hit skips parse, codegen, and loadstring; import/re-export/export metadata cached alongside the pure chunk
---
errors: thrown-ring entries carry the realm's current stack context
---
compiler: guarded inline arithmetic/compare — number-hot binary ops run as native Luau operators behind inline type guards; literal and proven-number sides skip their runtime check entirely
---
compiler: inline own-property reads and writes (zero-call hot path mirroring the runtime fast paths); generated chunks request O2 + native compilation + sealed env on engines that support loadstring options (gated on luauNativeSupported)
---
runtime+compiler: O(1) key-membership set on every object (kills the linear keys scan on new-key writes); plain object literals construct in one call (props/keys/keySet built directly); untouched dense arrays append inline on push. objLit 7.9x, push 11.6x measured
---
compiler: rtc.set resolves plain-object writes inline — one prototype-chain accessor consult, then a direct own data write with key bookkeeping; only accessors, proxies, typed arrays, frozen objects, and arrays route through the runtime. Constructor field writes 2.3x, entity population 2.9x
---
compiler: calling convention v2 — vararg call intrinsics (callv/mcallv/mcallcv), __direct dispatch slot, positional-param emission; budget seed confined to the call boundary; hole-safe boundary unpack; literal-receiver paren fix in inline member read/write
---
compiler: classNew v2 — rtc.newv vararg construction, __directCtor on base-class constructors (positional ctor bodies via shared directEligible), construct boundary unpacks at declared width
---
compiler+runtime: ctor field-write fast path (per-object hasAccessors + realm accGen sampling), rtc.newv direct construct for plain-function ctors, stdlib defineProperty plain-data descriptor fast path