TypeScript
Projects type checked with each struct seen as a class, and shared struct field access made direct where the types prove the layout.
compile type checks a project as TypeScript sees it with each struct spelled
as a class. It drops the errors that hold for a class but not for a struct, such
as a derived constructor without super(). Then, where the checker proves an
expression is an instance of a shared struct whose fields are all known, o.x
and o.x = v skip the Proxy:
const dx = a.x - b.x;
// compiles to
const dx = $struct.getNumber(a, $L0, 0) - $struct.getNumber(b, $L0, 0);
The types are not trusted. The runtime checks that the object is a Shared Struct with that layout, and reads it through the Proxy if not, so a structurally typed plain object still works.
Compiled code holds heap addresses rather than Proxies where it can. A binding
that keeps its value (a const, or a let or a parameter that is never
assigned) is checked once, beside the binding. A struct taken from a
SharedArray<S> element or from a struct-typed field is the address that slot
already holds, read without making its Proxy at all; the binding itself is made
only on the slow path. Each function keeps the heap's views in locals, since the
heap's size is fixed:
const b = bodies[j];
const bx = b.x;
// compiles to: b's address from bodies' slot, then the Float64 if it holds a Number
const $a1 = $struct.elem($a0, j, $L0),
b = $a1 < 0 ? bodies[j] : undefined;
const bx =
$a1 < 0 || $I[($a1 >> 2) + 4] !== 1
? $struct.getNumberAt(b, $a1, $L0, 0)
: $F[($a1 >> 3) + 3];
On examples/nbody
(1,000,000 steps, Node 24, Apple Silicon), which keeps its bodies in a
SharedArray<Body>, this takes 0.14 s. The same program on plain JavaScript
objects takes 0.045 s, and through the Proxy alone 7.5 s. Most of what is left
is the check of each slot's tag: a field may hold any shareable value, so a read
checks that it holds a Number.
The code is in src/compiler/project.ts (the type check) and src/compiler/fast.ts (the direct access).