Jonathan Blow - Jai Demo and Design Explanation (KEYNOTE) - Updated
Jonathan Blow, creator of Braid and The Witness, discusses the design philosophy behind 'jai', a new systems programming language. He explains how 'jai' re-evaluates the cost-benefit analysis of manual memory management by providing powerful, low-friction tools for metaprogramming, introspection, and debugging, inspired by principles from functional programming.
In his keynote, Jonathan Blow presents the design philosophy behind ‘jai’, a new systems programming language born from over a decade of development and the practical challenges of building complex software like video games. The core thesis is a re-evaluation of the cost-benefit analysis of systems programming, particularly concerning manual memory management. Blow argues that the conventional wisdom that dismisses manual memory management is based on the high costs (e.g., frustrating bugs, lack of visibility) associated with languages like C++ from the 1980s and 90s. The goal of ‘jai’ is not to invent entirely new features, but to drastically reduce the costs of powerful, existing ones, thereby making them viable and productive again.
Core Principles and Functional Inspirations
While being a systems language, ‘jai’ draws two key inspirations from the functional programming world:
-
A Program is Defined by its Text: Unlike C/C++, where the meaning of a program is fragmented across source files and external build systems (like Makefiles), ‘jai’ is designed to be self-contained. The goal is to eliminate the complexity of needing “five programming languages just to make your programming language build,” ensuring that a program’s text has a single, unambiguous interpretation that can be taken to a new environment and “just work.”
-
Code as Data: This Lisp-inspired principle is central to ‘jai’. The language provides powerful mechanisms to treat the program’s code as manipulable data at compile time. This is not a separate meta-language; the same ‘jai’ language used for application logic is used for metaprogramming, allowing for seamless sharing of functions, data types, and memory.
Compiler Architecture and Metaprogramming
The ‘jai’ compiler’s architecture is a direct embodiment of the “code as data” principle. When a compilation is initiated, the first thing the compiler does is load and execute a user-defined “meta program,” written in ‘jai’. This meta program controls the entire compilation process.
It operates on a message loop, receiving detailed messages from the compiler as it progresses through phases like parsing, type-checking, and code generation. These messages contain rich, structured data about the program, such as:
typecheck_success: This message includes the fully type-annotated Abstract Syntax Tree (AST) for a piece of code.import_module: Notifications about module loading.compilation_complete: A signal that the process is finished.
Because this information is provided after crucial steps like type-checking and macro expansion, it’s unambiguous. The meta program knows exactly what identifier refers to what entity. This allows developers to write powerful tools that can inspect, analyze, and even generate code with full confidence. For instance, one can scan for all functions tagged with a specific note (e.g., @register_command), collect them, and generate new code that registers them with a game’s command system.
Practical Applications: Visualization and Debugging
Blow demonstrated several powerful tools built using this metaprogramming framework, showcasing how it reduces the friction of systems development.
Code Introspection Visualizer
A tool was shown that visualizes the entire codebase as a treemap, where each rectangle is a function sized by its complexity (expression count). The color of each rectangle represents a customizable “code quality” metric, which is a linear combination of factors such as:
- Nesting depth of loops and
ifstatements. - Number of assignments or global variable accesses.
- Use of dynamic function pointers.
- Heap allocations and frees.
This visualization makes it immediately obvious where the most complex parts of the code are, how much polymorphism is costing in terms of code size, and can reveal unexpected issues. Blow cited an example of finding an enormous function that, upon inspection, was the result of dozens of macro expansions, a fact not obvious from reading the source code alone.
Low-Friction Memory Debugging
To tackle the high cost of memory bugs, ‘jai’ has a built-in memory debugger that is both powerful and extremely fast. It’s enabled with a single boolean flag and integrates with the language’s context-based allocator system.
The tool provides a real-time, in-game visualization of memory usage. In a live demo, Blow showed the game running at 45 FPS (down from 60 FPS) with full memory analysis active. The visualizer displays:
- Total heap size over time.
- Allocations per frame, grouped by call stack.
- The ability to scrub through a timeline of memory activity.
This makes debugging memory leaks trivial. By filtering for memory that was “allocated this frame but not freed this frame,” a leak was identified and traced to the exact line of code in seconds. This tool also helps find performance inefficiencies, such as excessive temporary allocations that lead to memory fragmentation.
Performance and Ergonomics
Compiler Performance: To help developers understand and manage compile times, a -pf flag generates a detailed performance report. This report breaks down time spent in compiler phases and, crucially, provides a polymorphism report. This report details every polymorphic function, how many times it was called, and lists every unique instantiation that generated new code. This gives developers visibility into the concrete costs of their abstractions.
Allocation Ergonomics: ‘jai’ avoids the pitfalls of a single global allocator. Instead, the allocator is part of a context struct. Code can easily and temporarily push a new allocator onto the context (e.g., a fast, temporary “arena” allocator for the scope of a function) and all subsequent allocations within that scope will use it. This provides fine-grained control over memory strategies in a clean and ergonomic way.
By building these powerful, low-friction tools directly into the language ecosystem, ‘jai’ aims to change the cost-benefit equation, making the power and performance of manual memory management an attractive and productive choice for modern software development.