Architecture

How Larva turns a source file into a running program - the pipeline, the forms a program takes on the way (HIR, IR, IL, machine code), and which part of Larva does what.

Last updated 11 min read Edit on GitHub

On this page

This part of the book explains how Larva works inside. It is written for people who build a language on Larva, work on Larva itself, or want to know what happens between larva build and a running program. The other books say how to use the tools; these pages say how they are made.

Larva is a compiler platform. It owns everything a program needs except the language it is written in: a typed object model (Larva HIR), the lowering of that model to a low-level IR, an IL and a VM, native back ends (LLVM, a direct x86-64 one, WebAssembly), a native runtime with several garbage collectors, project files, a package manager and native bindings. A language is a language pack: a frontend that turns source text into HIR, and a view that turns HIR back into source text. Larva never parses a language itself.

The pipeline

  hello.ant           Program.ant  …          (source files of any installed language)
      │                    │
      └────────┬───────────┘
               ▼
  ┌──────────────────────────┐   compile request (JSON)   ┌───────────────────────────────┐
  │ language pack (frontend) │ ◄───────────────────────── │ larva (Larva.Cli, SourceBuild)│
  │ lexer, parser, binder    │ ─────────────────────────► │ reads .queen, picks options   │
  └──────────────────────────┘    HIR assembly (.lva)     └───────────────────────────────┘
               │
               ▼                       System.Core.dll, libraries (.dll = PE + .lva section),
  ┌──────────────────────────┐         bindings, dependencies with another GC (own domain)
  │ HIR link (HirLinker)     │ ◄───────────────────────────────────────────────
  └──────────────────────────┘   one closed HIR assembly: the whole program
               │
               ▼
  ┌──────────────────────────┐   classes become structs + vtables, generics get requirements,
  │ lowering (HirLowerer)    │   closures/iterators/async become objects and state machines,
  └──────────────────────────┘   try/catch becomes unwind edges, imports become native calls
               │  Larva IR module (in memory; a .lvo when written)
               ▼
  ┌──────────────────────────┐   borrow check (enforce / warn / off), type check,
  │ IR checks (Compiler)     │   region check, safepoints, bounds facts
  └──────────────────────────┘
               │
               ▼
  ┌──────────────────────────┐   modules joined, imports resolved, link-once code shared,
  │ IR link (Linker)         │   GC domains assigned, entry wrapper chosen
  └──────────────────────────┘
               │
     ┌─────────┼──────────────┬──────────────────┬─────────────────────┐
     ▼         ▼              ▼                  ▼                     ▼
  LLVM      x86-64         WebAssembly        WebAssembly           IL (.lvm)
  (Larva.   (Larva.X64)    via LLVM           own back end          (IlLowering)
   Llvm)       │           (wasm-llvm)        (Larva.Wasm)              │
     │         │              │                  │                      ▼
     ▼         ▼              ▼                  ▼                  the VM
  object file + larva_rt   app.wasm + app.js (larva_rt.js) + app.html (Larva.Runtime)
     │
     ▼
  system linker (MSVC link)  →  app.exe / lib.dll

Every arrow but the first and the last happens inside the larva process, in memory. larva build writes no intermediate file: the HIR, the IR module and the linked program never touch the disk unless a tool asks for them.

You can see the phases with --timings. Each name is one step of the picture:

Terminal
larva build --timings -o hello.exe hello.ant
hello.exe (llvm back end, O3, entry hello::Hello.$Module.Main)
timings: System.Core (compiled library) 83 ms, frontend 1536 ms, HIR link 54 ms, lowering 619 ms, IR checks 226 ms, IR link 93 ms, native (llvm) 1551 ms; total 4173 ms
hello.exe
Hello from Larva!
Phase What runs Where
System.Core (compiled library) Reads the standard library's HIR from System.Core.dll (or compiles it from source in a working copy, System.Core (compiled now), and caches it). SourceBuild.CoreLibrary
<Name> (<Mode>) A dependency whose gc: differs from the program's, compiled and lowered on its own as a separate GC domain. SourceBuild.DomainLibrary
frontend The language pack's compile call: source text to an HIR assembly. the pack, in larva's process
HIR link System.Core, libraries, bindings and the program merged into one assembly. HirLinker.Link
lowering HIR to Larva IR. HirLowerer.Lower
borrow check Only with --check and a mode other than off. BorrowChecker.Check
IR checks Type, region and borrow checks of the module, ready to link. Compiler.CompileObject
IR link One linked program with an entry. Linker.Link
native (llvm), native (x64), native (wasm-llvm), native (wasm) Code generation, the runtime, the system linker. ExecutableBuilder, WasmBuilder

The same build with the other back ends differs only in the last step:

Terminal
larva build --backend x64 --timings -o hellox.exe hello.ant
hellox.exe (x64 back end, entry hellox::Hello.$Module.Main)
timings: System.Core (compiled library) 47 ms, frontend 1070 ms, HIR link 62 ms, lowering 632 ms, IR checks 230 ms, IR link 100 ms, native (x64) 979 ms; total 3132 ms
larva build --timings --target wasm-llvm -o hello.js hello.ant
hello.wasm, hello.js, hello.html (LLVM back end, O3, entry hello::Hello.$Module.Main)
timings: System.Core (compiled library) 68 ms, frontend 1077 ms, HIR link 56 ms, lowering 598 ms, IR checks 208 ms, IR link 84 ms, native (wasm-llvm) 1705 ms; total 3808 ms
node hello.js
Hello from Larva!

And --check stops after lowering (and the borrow check, when it is on), writing nothing:

Terminal
larva build --check --borrowcheck enforce hello.ant
no errors (1 file; System.Core (compiled library) 40 ms, frontend 934 ms, HIR link 50 ms, lowering 574 ms, borrow check 134 ms; total 1738 ms)

These timings come from a working copy of larva; the installed one is compiled ahead of time and is several times faster in every phase.

The forms of a program

A program changes shape four times. Each form exists for one reason.

Form What it is Made by Read by On disk
Source Text in some language. You The language pack .ant, …
HIR The program as a language sees it: namespaces, classes, interfaces, generics, attributes, typed bodies with statements and expressions. Names are resolved and every expression has a type, but nothing is decided about how it runs. Frontends Lowering, the catalog, language views, the editor .lva, or a .lva section in a .dll
Larva IR The program as a machine sees it, but still portable: structs, gc handles, places, basic blocks, terminators. Generics are still generic (with requirements). Lowering Checks, the linker, every back end .lvo (one module), .lvm (linked)
IL A compact bytecode lowered from linked IR, encoded in 4-bit units. IlLowering The VM inside a .lvm
Machine code x86-64 or WebAssembly. LLVM, the x64 back end, the own Wasm back end The processor, the system linker, a WebAssembly engine .obj, .exe, .dll, .wasm

The split between HIR and IR is the central decision of Larva's design. Before it, the Antril compiler lowered every feature (classes, closures, async, reflection, boxing) straight to the low-level IR, and every library was read by reparsing the text of its declarations. Now:

  • HIR is the truth of the program. Types, members, generics, attributes and typed bodies, with complete metadata. Every frontend, tool and library speaks HIR.
  • The language does not decide how code runs. A frontend resolves names, overloads, types and inference and emits typed HIR. How a virtual call is dispatched, how a closure is built, how an async method becomes a state machine: Larva decides, in lowering. Two languages that both have classes get the same object layout and can call each other.
  • Libraries are referenced by HIR metadata, not by source. System.Core.dll holds the HIR of the standard library; compiling against it reads a binary table instead of parsing thousands of lines.
  • Everything native is extern. The runtime's services are reached through a fixed table of intrinsics, the same in every back end and in the VM.

IR, below HIR, is where memory is explicit: object headers, handles, borrows. That is why the borrow checker, the region checker and the GC domains work on IR: whatever language a program came from, the same rules hold.

The projects of Larva

Larva is a set of libraries with one command on top. Each depends only on the ones in the rows above it:

Layer Projects Depend on
Models Larva.IL, Larva.Hir, Larva.Native nothing in Larva
IR Larva.IR Larva.IL
Services over the models Larva.Hir.Services Larva.Hir
Larva.Hir.Lowering Larva.Hir, Larva.IR
Larva.Runtime (the VM) Larva.IR
Larva.Build Larva.IR
Back ends Larva.Llvm, Larva.X64 Larva.IR, Larva.Native
Larva.Wasm Larva.IR
Tools Larva.Driver Larva.IR, Larva.Native, Larva.Llvm, Larva.X64, Larva.Wasm
Hive.Core Larva.Build
Larva.Bindings Larva.Hir, Larva.Hir.Services, Larva.Build
Command Larva.Cli (larva) all of the above
Installer Larva.Setup nothing (it copies a published larva)

Larva.Hir depends on nothing so that a frontend can be built on the HIR model and its services (the Antril pack uses Larva.Hir, Larva.Hir.Services and Larva.IR), without the back ends, the runtime or the command.

Project What it does Pages
Larva.Hir The HIR model, its text form, the .lva encoding, linking HIR assemblies. Depends on nothing. HIR, The .lva format
Larva.Hir.Services The catalog of types and members, language views (HIR written back as source), the editor services. Catalog and views
Larva.Hir.Lowering HIR to IR: layouts, vtables and itables, generics, closures, iterators, async, exceptions, reflection, interop, static constructors. Lowering, Lowering language features
Larva.IR The IR: types, places, rvalues, terminators, modules; the checkers (types, regions, borrows); the monomorphizer and the coroutine pass; .lvo/.lvm encoding; the linker; GC configs. Larva IR, Borrow checker, Objects and linking
Larva.IL The IL: opcodes, the 4-bit encoding, the peephole pass, the disassembler. IL and VM
Larva.Runtime The VM that runs IL, with its own heap and collectors. IL and VM
Larva.X64 Larva's own x86-64 code generator and COFF writer. x86-64 back end
Larva.Llvm Code generation through LLVM. LLVM back end
Larva.Wasm Larva's own WebAssembly back end. WebAssembly
Larva.Native The native runtime larva_rt.c, its in-process twin NativeRuntime, and finding the system toolchain. Runtime, Garbage collection
Larva.Driver The builders: executables and libraries, WebAssembly output. Native builds
Larva.Build .queen files: syntax, schema, projects, machine defaults. Projects and builds
Hive.Core Packages: archive, manifest, keys, cache, registry. Hive internals
Larva.Bindings Native export tables, .bind descriptions, the C header reader, the binding generator. Bindings internals
Larva.Cli The larva command, language pack loading, the language server, source builds, binding profiles. Language packs, Language server
Larva.Setup The Windows installer. Installers
Library System.Core and System.Bindings, written in Antril, compiled into .dll libraries when an installer is built. System.Core overview

The language packs are not in this list: each lives in its own repository and is loaded by larva at run time (see Language packs).

One request, four runtimes

Code compiled by Larva can run in four places, and each has a runtime that implements the same object layout and the same table of intrinsics:

Where the code runs Runtime Used by
A native executable or library larva_rt.c, compiled once per machine and linked in, with no C library larva build, larva run
larva's own process NativeRuntime, a managed twin of larva_rt.c the x64 back end's in-memory images, binding profiles, the LLVM in-memory path
A WebAssembly engine larva_rt.js (with --target wasm-llvm, also wasm_heap.ll linked into the module for inline allocation) --target wasm, --target wasm-llvm
The VM Larva.Runtime (Interpreter, Heap) larva run <module.lvm>

Because all four agree, a program gives the same output wherever it runs; the VM is the reference the other three are compared with.

The files Larva reads and writes

Extension What it is Page
.queen A project: what to build and how. Projects and builds
.lva An HIR assembly. The .lva format
.dll (Larva library) A PE file whose .lva section holds an HIR assembly: System.Core.dll, larva lib and larva bind output. The .lva format
.lvo One verified IR module, before linking. Objects and linking
.lvm A linked program: IR, IL, or both. Objects and linking, IL and VM
.bind A description of a native library's functions and types. Bindings internals
.hive A signed package. Hive internals
language.json A language pack's manifest. Language packs

What is not implemented yet

These pages describe the code as it is. Where it falls short of the design, the page says so. The main gaps today:

  • gc: custom is accepted but not usable: the runtime has hooks for an installed collector, but the library has no IGarbageCollector interface and no GC.Install yet.
  • Larva's own WebAssembly back end emits invalid modules for some programs (any program that builds text, for example), and does not support 256-bit integers; use --target wasm-llvm.
  • Main(str[] args) cannot be a program's entry, and Main() : i32 prints its result instead of exiting with it.
  • Native builds need the MSVC tools: the Linux and macOS builders exist but no command reaches them yet.
  • larva build recompiles everything on each build. An incremental cache per assembly is planned.

Was this page helpful?

No address is stored; your user name is, if you are signed in.

Esc