Compiler Internals
The key design decisions that make Perry fast
01NaN-Boxing
Values are stored as 64-bit floats with special bit patterns for pointers, enabling union types without runtime overhead. This allows (string | number)[] to work efficiently.
By encoding type information directly in the IEEE 754 NaN payload bits, Perry avoids the need for tagged unions or boxed values. A single 64-bit word can represent any JavaScript value type — numbers use their natural representation, while strings, objects, and other heap-allocated values use pointer bit patterns that fall within the NaN range.
02Monomorphization
Generics are specialized at compile time, like Rust. Each type instantiation generates optimized code, eliminating runtime type checking overhead.
When you write Array<number> and Array<string>, Perry generates two separate, fully optimized implementations. This means array operations on numbers use direct machine arithmetic — no type guards, no dynamic dispatch, no boxing.
03Static Dispatch
No virtual tables. Method calls are resolved at compile time, enabling direct function calls and inlining optimizations.
Traditional OOP runtimes use vtables for method resolution, adding a layer of indirection on every call. Perry resolves all method calls statically during compilation, turning interface method calls into direct jumps. This also unlocks aggressive inlining — small methods are often folded directly into their call sites.
04Zero-Cost Abstractions
TypeScript classes, interfaces, and generics compile to efficient native code with no runtime representation overhead.
TypeScript's type system exists only at compile time — and Perry takes this literally. Interfaces produce zero runtime code. Class hierarchies are flattened. Generic constraints are resolved to concrete types. The compiled binary contains only the actual logic, with none of the abstraction machinery that interpreters typically carry.