Post

Perry Comprehensive Review: The V8-less Revolution from TypeScript to Native Machine Code

🇬🇧 No Node.js, no V8, no Electron! We look under the hood of the Perry compiler, which compiles your TypeScript code directly into native executables in seconds. Meet the NaN-boxing architecture, LLVM optimizations, and the 10-platform native UI revolution.

Perry Comprehensive Review: The V8-less Revolution from TypeScript to Native Machine Code

In the modern software development world, the dominance of JavaScript and TypeScript is indisputable. However, when we want to run these languages on the desktop or server, we pay a massive price: The V8 engine and runtime environments like Node.js/Deno/Bun.

It is no secret that these architectures are “clunky”, requiring tens of megabytes of memory just to print a simple “Hello World”, or embedding the entire Chromium engine into our project when we build desktop applications with tools like Electron or Tauri.

But what if I told you there is a compiler that can translate your TypeScript code directly into machine code (native binary) understood by the operating system, just like C, C++, or Rust, with no runtime dependencies, producing executable files as small as ~330 KB?

Meet Perry!, the native compiler that breaks the “speed and size” chains of modern web developers, backed by the incredible power of Rust and LLVM, compiling TypeScript to machine code.

In this comprehensive technical review, we won’t just give a superficial introduction to Perry; we will dive deep into the codebase and witness line-by-line how NaN-boxing architecture, thread-local Arena allocation, and LLVM optimizations work under the hood. Just as Gea.js buried the Virtual DOM, Perry is preparing to bury the V8 engine in desktop and server applications.

1. Beyond the Era of V8 and Node.js: What is Perry? đŸ€”

TypeScript is essentially a language that transpiles to JavaScript. In the traditional flow, tsc or esbuild converts your code to JavaScript, and then a JIT (Just-In-Time) compiler like V8 or JavaScriptCore converts this code into machine code at runtime.

The core philosophy advocated by Perry is this: “Why run a massive compiler (V8) on the user’s computer to convert code to machine language? Why not do this on the developer’s computer (Ahead-Of-Time)?”

Perry is not an interpreter or a virtual machine. It is a pure, real, and ruthless compiler. It takes your TypeScript code and transforms it directly into executable binary files for Windows (.exe), macOS, Linux, and even iOS/Android.

The core advantages this gives you are:

  • Zero Dependency: There is no need for Node.js, JRE, or .NET to be installed on the user’s system. It produces a single executable, just like Go or Rust.
  • Instant Startup: There is no JIT “warmup” time. Your application starts in milliseconds.
  • Small Size: While classic Electron applications start at 150 MB, Perry’s Hello World applications featuring native UI are hundreds of kilobytes in size.
  • Resource Consumption: Because there is no massive Garbage Collector and optimization threads running in the background by V8, RAM usage is exceptionally low.

2. Under the Hood: The Rust, SWC, and LLVM Triangle đŸ«€

Perry’s power comes from being built on the most solid foundation of the modern software ecosystem. The compiler itself is written entirely in Rust.

The Compilation Pipeline consists of three main stages:

  1. Parsing - SWC: Perry uses the blazingly fast Rust-based SWC library, which Next.js also uses, to parse your TypeScript code and create an Abstract Syntax Tree (AST).
  2. HIR (High-Level Intermediate Representation): The AST from SWC is converted into Perry’s internal architecture, HIR (perry-hir). At this stage, variable scopes, closure captures are calculated, and type inference is performed.
  3. LLVM Codegen: The biggest magic starts here. In Perry’s early versions, Cranelift was used for fast compilation. However, Cranelift was a JIT compiler focused on “fast code generation”. To break the optimizer ceiling of the application and provide full support for architectures like Apple Watch (arm64_32), the Perry team undertook a massive architectural migration and fully transitioned to the LLVM infrastructure. LLVM is the very infrastructure that compiles Apple’s Swift, C++ (Clang), and Rust. Thus, Perry gets all of the 30-year “industry standard” code optimization techniques (Loop vectorization, LICM, GVN, etc.) for free!

3. The Secret of Data Representation: “NaN-Boxing” Magic đŸȘ„

JavaScript is a dynamically typed language. A variable can be a number one moment and an object the next. In traditional C/C++ structures, you have to keep variables in large structs and on the Heap to manage this. But this is very slow.

Perry uses a brilliant trick that V8 and JavaScriptCore also use: NaN-Boxing. The files crates/perry-runtime/src/value.rs and crates/perry-codegen/src/nanbox.rs that we reviewed form the heart of this architecture.

How Does NaN-Boxing Work?

According to the IEEE 754 standard, the space reserved for 64-bit double floats is so huge that there are trillions of invalid combinations marked as “NaN” (Not a Number).

Perry uses the 64-bit register as follows: If the bits are a valid number, they are processed directly on the CPU. If not, the top 16 bits (Tag bits) indicate the “type” of the data (Undefined, Null, Boolean, String, Object). The remaining 48 bits (Payload) hold the value of the data or its pointer on the Heap.

What does this mean? When you do simple integer arithmetic, no object is created, and no memory allocation is made. Everything happens directly in the CPU’s fastest hardware registers.

4. Memory Management: The Arena Allocator and GC Challenging V8 đŸ—ïž

JavaScript means millions of objects constantly created and destroyed. Because Perry doesn’t use a massive memory engine like Node.js, it houses its own wonderful memory manager inside perry-runtime.

Thread-Local Bump Allocator and mimalloc (Zero-Cost Allocation)

What happens when you create a new object (new ClassName())? The crates/perry-runtime/src/arena.rs file gives us the secret: While Perry uses the legendary mimalloc as the global memory manager, it allocates a special memory pool (Arena) for each operating system thread. When a new object is needed, complex OS calls (malloc) are not made. The pointer is simply shifted forward a few bytes (Bump Allocation), and this operation is coded “inline” directly at the LLVM IR level. This is literally a “zero-cost” operation at the hardware level and is as fast as stack allocation in C.

Conservative Mark-Sweep GC

So how is the filled memory cleaned up? The Mark-Sweep Garbage Collector implemented in crates/perry-runtime/src/gc.rs performs “conservative stack scanning”. That is, it reads the CPU’s stack, finds values that could be pointers, marks the objects connected to them as “Alive” (Mark), and wipes away everything else (Sweep). Moreover, thanks to the Adaptive Malloc-Trigger logic, the GC smartly determines when the next cleanup should be done based on the efficiency of the last deletion. This minimizes CPU pause times.

5. Advanced Compiler Optimizations (Performance Secrets) 🚀

We can see in detail why Perry is so fast in the PERF_ROADMAP.md documents. The compiler doesn’t just translate code into machine language; it also makes the code “smarter”:

  1. Escape Analysis and Scalar Replacement: If you use an object (or array) only within a function and finish its job, Perry doesn’t create that object on the Heap! It breaks down the object and places its properties directly into CPU registers (Scalar Replacement). Since the object is never created, the Garbage Collector doesn’t get tired.
  2. i32 Fast Path and Fast-Math Flags: If your code has mathematical operations (or loop counters), Perry translates this to native 32-bit integer (i32) math instead of JavaScript’s standard double float calculation. Thanks to the reassoc contract (fast-math) flags passed to LLVM, it runs a full 24.6 times (24.6x) faster than Node.js in heavy mathematical operations like factorial (24ms vs Node’s 591ms)!
  3. Monomorphic Inline Cache (PIC): The Inline Cache structure, which is the heart of modern JS engines, exists natively in Perry. When you repeatedly access a property of an object of the same type (PropertyGet), Perry caches the memory offset directly instead of dynamically searching for the method and loads it straight from memory.
  4. Typed Buffer Locals (noalias metadata): Normally, when reading data structures like Buffer or Uint8Array, a “NaN-Box unbox” must be performed at each step. Perry statically determines buffer types, tags them with LLVM’s noalias metadata, and emits pointer loads directly. In image processing (e.g., 4K 5x5 blur) tests, this optimization allows it to achieve exactly the same performance (zero overhead) as Zig or C.
  5. SIMD Vectorization: Independent mathematical operations within loops (Loop-Invariant Code Motion) and arrays are automatically converted into machine instructions that can process multiple data points simultaneously (SIMD - Single Instruction, Multiple Data) by LLVM’s smart analyses.

Future Performance Goals: Catching Up to Zig

Currently, although Perry overwhelmingly beats Node.js in almost all tests (up to 24.6x in the factorial test), some optimizations are expected to be completed to fully reach pure C/Zig speeds. For example, in the image_conv (5x5 pixel blur process at 4K resolution) benchmark, Perry currently runs around 457ms, while Zig is at the top with 246ms. To close this architectural gap, “Typed Buffer Locals” (using pure i64 slots to skip unboxing in buffer accesses), “Interior/border loop splitting” (removing unnecessary bounds checks that block vectorization), and “Double-ABI FNV-1a hash” optimizations are the focus of the next release (PERF_ROADMAP).

6. Throw Chromium in the Trash: 100% Native UI with SwiftUI Elegance đŸ“±

When you wanted to bring your Node.js application to the desktop, Electron was your only choice, and your application’s size instantly ballooned to 150 MB, with RAM usage skyrocketing to 300 MB.

Perry offers a revolutionary approach: Perry UI (@perry/ui). Its syntax is incredibly similar to Apple’s SwiftUI architecture: you write interfaces with TypeScript using declarative components like VStack, HStack, ZStack, State, and Button.

1
2
3
4
5
6
7
8
9
10
import { App, Text, Button, VStack, State } from "perry/ui";

function Counter() {
  const count = State(0);
  
  return VStack(16, [
    Text(`Counter: ${count.value}`).font("title"),
    Button("Increment", () => count.value++)
  ]);
}

However, this interface is not rendered to the DOM using HTML/CSS or WebView in the background! Perry’s genius lies here: these components you write are directly mapped to the operating system’s 25+ real native widgets (buttons, text fields, tables, canvas, scrollable areas, QR codes, splash screens, etc.):

  • On macOS: VStack transforms directly into NSStackView, buttons are real AppKit/Cocoa buttons.
  • On Windows: Real Win32 / WinUI components.
  • On Linux: VStack translates directly to GtkBox, buttons are native GTK4 elements.
  • iOS/Android: UIStackView and native platform elements.

This way, with just a 5-10 MB .exe or .app file, you can write desktop programs that are 100% compatible with the operating system, run smoothly at 60 FPS, and have almost “zero” RAM consumption.

7. Real OS Threads: parallelMap and spawn đŸ§”

Multiprocessing in the Node.js world is a nightmare; you have to spin up Web Workers, copy data with postMessage, and pay the serialization cost.

In Perry, you have access to the operating system’s Real OS Threads. Moreover, you do this with a single line of code:

1
2
3
4
import { parallelMap } from "perry/thread";

// Distributes the data across all cores of the operating system!
const results = parallelMap(data, (item) => heavyComputation(item));

The perry/thread package manages C/C++ threads in the background. Closures are copied to memory (with deep-copy logic), and mutation errors are prevented at compile-time. The parallel processing architecture (parallelFilter, spawn) that data scientists or image processing engines dream of is at the center of Perry.

8. A World First: Built-in UI Testing with Geisterhand (Ghost Hand) đŸ‘»

End-to-End (E2E) testing desktop applications is painful. Perry radically solves this problem with an incredible plugin called “Geisterhand” (German for Ghost Hand).

If you compile your project with the --enable-geisterhand flag:

1
perry app.ts -o app --enable-geisterhand

Perry embeds a hidden HTTP Server (default: port 7676) inside your executable file. While your application is running, you can send HTTP requests from the outside to click any button in your program, write text into TextFields, drag sliders, or take screenshots. Clunky testing tools like Selenium or Puppeteer are no longer needed. With Python, cURL, or any REST tool, you can perform full Chaos Fuzzing or UI automation on your native desktop application!

9. The Mind-Blowing Feature: NPM Packages Turning into Native Rust Crates 📩

For a compiler to rival Node.js, it must support the NPM ecosystem. If you are asking, “Will the NPM packages I wrote work?”, Perry literally puts on a show here.

According to the project’s native-libraries.md documentation, Perry maps the most popular 30+ NPM packages to native Rust crates! When you import an NPM package normally in your TypeScript code, Perry compiles it with Compile-Time Plugin logic; meaning instead of running the Node.js module in the background, it embeds its high-performance Rust equivalent directly into the machine code (Zero runtime plugin overhead, zero IPC boundaries):

  • Database: Popular drivers like mysql2, pg, mongodb, better-sqlite3, ioredis use C/Rust systems in the background.
  • Security: bcrypt, argon2, jsonwebtoken, and crypto operations are processed instantly at native speed.
  • HTTP/Network: When you use http, https, axios, node-fetch, ws, and nodemailer, Rust’s famous reqwest and tungstenite crates kick in.
  • Data & Utils: Dozens of packages like cheerio (Rust scraper), sharp, zlib, lodash, dayjs, uuid, dotenv, validator run completely native.

Additionally, Perry natively hosts core Node.js APIs from scratch such as fs, path, crypto, os, and child_process through perry-stdlib. All this translates to a ~0 ms startup time the moment your application launches.

For Those Who Can’t Give Up V8: Optional V8 Engine Support

If you strictly need to use a specific NPM package in your application that only runs with pure JavaScript and doesn’t have a native equivalent yet, Perry still won’t let you down. If you add the --enable-js-runtime flag at compile time, Perry embeds a hidden V8 engine into your application. In this case, your binary size jumps from the standard 2-5 MB levels to 15-20 MB levels, but you instantly gain the massive compatibility of the pure JavaScript NPM ecosystem.

Real-World Test: Hono, tRPC, and Strapi 🚀

What we’ve explained is not theoretical. As announced on Perry’s official blogs; massive frameworks like Hono, tRPC, and Strapi can currently be compiled smoothly with Perry. Perry takes hundreds of modules that make up these frameworks, links them together, and inlines them into a single native ARM64 executable file under 2 MB in under 1 second. Architectures that create massive runtime overhead in Node.js turn into a “zero-cost” composition in Perry’s machine code.

Real-World Applications Built with Perry (Showcase)

Perry has proven itself not only in “hello world” tests but also in complex real-world projects. Here are some tremendous products featured on the homepage and built end-to-end using TypeScript with Perry:

  • Mango: A native MongoDB database management tool that opens instantly (under 1 second). It has only a ~7 MB binary size and consumes less than 100 MB of RAM (macOS, Windows, Linux, iOS, Android).
  • Hone: A native, AI-first code editor. Features a built-in terminal, Git integration, and LSP (Language Server Protocol) support. Offers sub-second startup, <50 MB binary size, and <100 MB memory consumption (All platforms and Web).
  • dB Meter: A native decibel/sound level measurement app running with 60fps updates and live soundwave animations (iOS, macOS, Android).
  • Pry: A native JSON viewer developed as a reference by the Perry team. Offers tree navigation and fast search directly via the operating system’s native interface without WebView or Electron.
  • Perry Demo: An interactive test platform where you can make live speed and size comparisons between Perry, Node.js, and Bun.
  • Perry Starter: A minimal project template prepared to quickly start Perry projects.

So What’s Missing? (Conscious Constraints)

Perry is not a magician; it’s a compiler with rules. Due to the nature of AOT (Ahead-Of-Time) compilation, some features that require interpreting code at runtime or dynamically changing the runtime memory tree are consciously not supported (typescript-parity-gaps.md):

  • eval() or new Function() are not supported.
  • Proxy and Reflect APIs are deliberately omitted because they would impose a massive “trap” cost on every property access in the CPU.
  • Flat memory layouts are preferred over dynamic import() calls and complex prototype chains.
  • Dynamic property assignment on this: Computed function assignments at runtime like this[methodName] = function() are not yet fully supported (e.g., dynamically injecting route registrations in Hono). These “limitations” are actually the primary reasons Perry can run at the same speed as C++ and Zig.

10. A Growing Ecosystem: Databases, Push Notifications, and React 🌐

Perry is growing from just a compiler into a full-fledged ecosystem. The latest developments in the project’s source codes include:

  • perry-prisma, perry-sqlite, perry-postgres: Native database drivers that are 100% compatible with the Prisma ORM API, but use Rust’s high-performance sqlx library under the hood instead of Node.js, operating at zero cost at the FFI boundary.
  • perry-push (Universal Push Notifications): The ability to send push notifications natively to all APNs (iOS/macOS), FCM (Android), Web Push, and WNS (Windows) platforms through a single library. For cryptography, Rust’s minimal ring crate is used instead of massive OpenSSL packages.
  • perry-verify: An automated built-in testing system (App Verification) that spins up the compiled application. It tests your app, triggers UI events in a cross-platform manner, and performs visual validation via Claude AI integration in non-deterministic scenarios.
  • perry-react: A revolutionary compatibility layer for React developers that supports JSX and Hooks like useState, but instead of creating a Virtual DOM in the background, it binds components directly to native operating system interfaces (AppKit, UIKit, Win32, etc.).

11. Limitless Platform Freedom: 10 Different Targets 🌍

We talked about Native UI capabilities on the desktop. But we see that Perry’s vision is not limited to the desktop alone. You can port your Perry codes to:

  • macOS (x86_64, aarch64)
  • Windows
  • Linux (Ubuntu/GTK4)
  • iOS and iPadOS
  • Android
  • watchOS and tvOS
  • Web (WebAssembly) and Web/JavaScript

target platforms with a single command line via cross-compilation. You can even compile your Windows, macOS, and iOS applications directly from a single Linux machine (for example, on an Ubuntu CI/CD server) without needing a Mac device (thanks to lld-link and ld64.lld support).

12. Long Story Short: How Do We Start?

To try out this whole “V8-less native TypeScript” world, you don’t need to install anything on your system. The development experience is incredibly streamlined. Just open your terminal and type this (you can compile to 9 different platforms):

1
2
npx perry build src/main.ts -o app
./app

That’s it! Your app file is now machine code that acts just like a C/C++ program, needing neither Node.js nor any other dependencies.

In conclusion, Perry is a tremendous engineering marvel that takes the flexibility and ease of the JavaScript world and blends it with Rust’s system programming power and LLVM’s hardware optimizations. If you want to leave behind the “laziness” of Electron and heavy JavaScript runtimes and write true native applications that open in a thousandth of a second, Perry is the future itself.

To inspect the project and the source codes under the hood without wasting any more time, you can check out the Perry GitHub Repository! 🌟

This post is licensed under CC BY 4.0 by the author.