Pi-Bolt Docs

Reference

How it works

Pi-Bolt does at build time the work that Bun or Node does at every launch: parsing Pi's code, loading its modules and compiling its hot functions. What is left at launch is mapping the result from the executable and running Pi.

Stock Pi at launch#

Stock Pi is JavaScript run by Bun or Node. Every launch, the runtime reads Pi's code, parses it into bytecode, loads and runs every module, and starts Pi in an interpreter. As Pi runs, a JIT compiler watches which functions are hot and what types flow through them, and compiles those to machine code. Until then they are interpreted, and the JIT itself costs CPU, threads and memory.

Pi-Bolt at build time#

  1. Bundle and compile to bytecode

    Bun bundles Pi's modules into one program and compiles every function to JavaScriptCore bytecode.

  2. Load Pi into a heap

    Pi's modules are loaded and evaluated, and the resulting heap is saved: the prebuilt heap. It holds Pi's functions, classes and data already set up.

  3. Compile every function ahead of time

    An ahead-of-time compiler turns every function into optimized x86-64 or ARM64 machine code, through B3, the back end of JavaScriptCore's top-tier JIT. Without a running program to watch, it works from what it can prove about the code: whole program type inference, inlining of small functions and array callbacks, fast copies of hot loops, and guarded fast paths with a generic path for the unusual case.

  4. Lay it out for startup

    A training profile, recorded from scripted Pi sessions, orders the code and the heap so that a launch touches as few pages as possible. Regular expressions that Pi builds at run time are compiled ahead of time too.

  5. Write one executable

    The Bun runtime, Pi's bundled program, the prebuilt heap and the machine code go into a single executable.

Pi-Bolt at launch#

The executable checks that its compiled code fits the engine and the CPU, maps the prebuilt heap and the code from its own file, and runs Pi's main(). Nothing is parsed and nothing is compiled. Pi's modules run their top-level code, as compiled code.

If a check fails (an older CPU, a tight address-space limit), the executable says so in one line and runs Pi from bytecode instead: correct, but slower. See Notices at startup.

Why it uses less memory#

  • The code and the prebuilt heap are file-backed. Pages that are only read are shared with the system's file cache, and the system can drop them under memory pressure. A process's own memory is what it writes.
  • No JIT in the default build: no compiler threads, no JIT code caches, no profiling data.
  • No bytecode is kept for functions that have machine code.

Why it uses less CPU over a session#

Compiled code accounts for the faster start. Over a session, most of the difference comes from Pi itself: Pi-Bolt carries fixes to Pi's TypeScript where Pi redid work over the whole conversation or the whole streamed output at every step. Examples: a streamed tool call is parsed once, not again for every chunk; a long answer is redrawn incrementally; a command's output is written as it arrives. These fixes are in Pi-Bolt's source, on top of Pi's.

Limitations#

  • Code loaded at run time is not compiled ahead of time. On the default build it is interpreted. Use the JIT build or compile extensions in.
  • No type feedback. A warmed-up JIT specializes on the types it sees; the ahead-of-time compiler uses what it can prove. Code with predictable types gets the fast paths.
  • One executable per Pi release. Each build is made from one Pi release and one engine.

Credits and source#

The ahead-of-time compiler builds on Jarred Sumner's work for Bun, oven-sh/WebKit#743. Pi-Bolt ports it to x86-64, macOS on ARM64 and Windows, and adds its own code generation and runtime work. Pi's source with Pi-Bolt's changes is in packages/ of the repository; the engine changes are in patches/. The full design is in ARCHITECTURE.md.