# cranelift-jit-demo: a toy language compiled by Cranelift at runtime

> This demo JIT-compiles a small toy language with Cranelift, walking through parsing, AST construction, IR building and execution. It works on Linux x86-64 and is meant to be read as a tutorial rather than used as a library.

**bytecodealliance/cranelift-jit-demo** — JIT compiler and runtime for a toy language, using Cranelift

- Repository: https://github.com/bytecodealliance/cranelift-jit-demo
- Stars: 766 · Forks: 65
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/bytecodealliance-cranelift-jit-demo

## A walkthrough, not a library to depend on

cranelift-jit-demo is a simple demo that JIT-compiles a toy language using Cranelift, and the README is written as a tutorial rather than as documentation for a published crate. The opening line is Hello, and the text walks through the implementation step by step, linking to the exact source line for each step in files such as src/bin/toy.rs, src/frontend.rs and src/jit.rs.

That framing tells you what this is for. The package version in the manifest is 0.0.0, the description is Toy language implemented using cranelift-jit, and the repository has no GitHub releases. The top level of the tree is a .gitignore, a .rustfmt.toml, the Cargo.toml, the LICENSE, the README and the src directory. There is no library API to speak of.

The stated inspiration is two other projects, Ulysse Carion's llvm-rust-getting-started and Sophia's rustyjit, and the demo is explicitly framed as introducing Cranelift by walking through a JIT demo using the cranelift-jit crate, which the README says is a new JIT interface still in development in the wasmtime repository.

The licence in the manifest is worth noting because it differs from a plain Apache-2.0 grant. The license field reads Apache-2.0 WITH LLVM-exception, which is the form used across the Cranelift project for a licence that carries an explicit exception for LLVM.

The demo currently works on Linux x86-64 platforms. It may also work on Mac x86-64 platforms, though the author says it has not been specifically tested there.

## One integer type, and the grammar that parses it

The toy language is deliberately small. All variables have type isize, and the README explains that Cranelift does have full support for other integer and floating-point types, so the single type is a choice to keep the demo short rather than a limit of the backend.

The first example in the language looks like this:

```
        fn foo(a, b) -> (c) {
            c = if a {
                if b {
                    30
                } else {
                    40
                }
            } else {
                50
            }
            c = c + 2
        }
```

Note the shape of it. The function takes parameters, returns a single value in parentheses, and the if expression is used as a value rather than as a statement, which the README flags as the way to show how Cranelift handles a value-returning conditional.

The grammar for the language is defined in src/frontend.rs, and the demo uses the peg parser generator library to generate the actual parser code from it. The output of parsing is a custom AST type, and it is small enough to read in one sitting:

```rust
pub enum Expr {
    Literal(String),
    Identifier(String),
    Assign(String, Box<Expr>),
    Eq(Box<Expr>, Box<Expr>),
    Ne(Box<Expr>, Box<Expr>),
    Lt(Box<Expr>, Box<Expr>),
    Le(Box<Expr>, Box<Expr>),
    Gt(Box<Expr>, Box<Expr>),
    Ge(Box<Expr>, Box<Expr>),
    Add(Box<Expr>, Box<Expr>),
    Sub(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
    Div(Box<Expr>, Box<Expr>),
    IfElse(Box<Expr>, Vec<Expr>, Vec<Expr>),
    WhileLoop(Box<Expr>, Vec<Expr>),
    Call(String, Vec<Expr>),
    GlobalDataAddr(String),
}
```

The binary operators are variants holding boxed subexpressions rather than an operator enum, which is the simplest thing that works for a tutorial. The presence of Call and GlobalDataAddr means the language can reach other compiled functions and read global data, which is what makes the module model in the next section necessary rather than decorative.

## Functions and data objects, each with its own namespace

The demo's first action is to create the JIT instance, and the code is a single line:

```rust
let mut jit = jit::JIT::new();
```

The JIT class in src/jit.rs holds four fields, and each one maps onto a concept you need to understand. The builder_context is what Cranelift uses to reuse dynamic allocations between compiling multiple functions. The ctx is the main Context object for compiling functions. The data_description is similar to ctx but for data sections. The module is the Module which holds information about all functions and data objects defined in the current JIT.

The underlying model is the part that generalises. The Module class divides the world into two kinds of thing, functions and data objects. Both have names, and each can be imported into a module, defined and referenced only locally, or defined and exported for use by outside code. Functions are immutable, while data objects can be declared either readonly or writable.

The rule that makes this workable is that both functions and data objects can contain references to other functions and data objects, but the low-level parts operate on each function and data object independently, so each one maintains its own individual namespace of imported names. The Module struct takes care of maintaining the set of declarations used across multiple functions and data objects.

The README is clear that these concepts are general enough to apply to native object files as well as JITing, and that Module provides an interface which abstracts over both. That is the point of the cranelift-module, cranelift-jit and cranelift-faerie crate set described earlier in the README: cranelift-module is a common interface for working with multiple functions and data interfaces at once, it can sit on top of cranelift-jit which writes code and data to memory where they can be executed and accessed, or on top of cranelift-faerie which writes code and data to native .o files that can be linked into native executables.

## From AST to IR, one builder and a set of basic blocks

Once the JIT structures are initialized, the compile function takes a string containing a function in the toy language and does three things in sequence. It parses the string into an AST, then translates the AST into Cranelift IR, and then compiles.

The translation is the interesting part, and the README walks through its steps. Because the toy language supports only one type, the first step is declaring that type for convenience. Then the function parameters and the return types are added to the Cranelift function signature. Then a FunctionBuilder is created, which is a utility for building up the contents of a Cranelift IR function and which includes functionality for constructing SSA form automatically so that users do not have to worry about it. Finally an initial basic block is started, which is the entry block of the function and the place where code gets inserted.

A basic block is defined in the README as a sequence of IR instructions with a single entry point and no branches until the very end, so execution always starts at the top and proceeds straight through. Cranelift's basic blocks can have parameters, and those take the place of PHI functions in other IRs. The example given shows branches at the end of the block and demonstrates block parameters:

```
block0(v0: i32, v1: i32, v2: i32, v507: i64):
    v508 = iconst.i32 0
    v509 = iconst.i64 0
    v404 = ifcmp_imm v2, 0
    v10 = iadd_imm v2, -7
    v405 = ifcmp_imm v2, 7
    brif ugt v405, block29(v10)
    jump block29(v508)
```

The FunctionBuilder library takes care of inserting block parameters automatically, so the code that builds the function does not have to thread values by hand.

## Why the new crate split exists, and what it costs you

The README makes a point that is easy to miss and that explains a lot about the crate layout. Cranelift is being architected to allow flexibility in how one uses it, and sometimes that flexibility can be a burden, which the project says it has recently started to address in a new set of crates: cranelift-module, cranelift-jit and cranelift-faerie.

So the current shape of Cranelift is a backend plus a set of front ends, and the demo deliberately uses the newest one. The manifest pins four crates at the same version, cranelift, cranelift-module, cranelift-jit and cranelift-native all at 0.125.3, which is the usual expectation for a workspace of crates released together. The parser generator peg is at 0.8.1, and the edition is 2024.

The cost of using the interface in development is real. The README states plainly that the demo uses the new JIT interface in development, and that it takes care of managing a symbol table, allocating memory and performing relocations while offering a relatively simple API. An interface that is still moving is a poor foundation for anything you intend to maintain, which is the main reason to treat this repository as a tutorial and not as a starting point for a JIT of your own.

For comparison, Cranelift is a compiler backend that is light-weight, supports no_std mode, does not use floating point itself, and makes efficient use of memory. Those are the properties that make it a reasonable backend for this kind of exercise, and the reasons the demo does not need a full LLVM installation to run.

## Conclusion

Adopt this repository as a reading exercise if you want to understand how a JIT is put together on top of the cranelift-jit interface, since the walkthrough names each source file and links to the exact line where each step happens. Do not adopt it as a dependency, because the package version is 0.0.0, the toy language only supports one integer type, and the demo is documented as working on Linux x86-64 with macOS untested. Before you build on the same crates, note that the manifest pins cranelift, cranelift-module, cranelift-jit and cranelift-native all at 0.125.3 and the licence field reads Apache-2.0 WITH LLVM-exception.

## FAQ

### What is cranelift-jit-demo and what does it demonstrate?

It is a Rust demo that JIT-compiles a toy language using Cranelift, walking through parsing, AST construction, translation into Cranelift IR and execution. The README links to the exact source line for each step, so it reads as a tutorial on the cranelift-jit interface rather than as a library.

### Which platforms does the cranelift-jit-demo work on?

The README states that it currently works on Linux x86-64 platforms. It may also work on Mac x86-64 platforms, though the author says it has not been specifically tested there, and Cranelift is being designed to support other platforms in the future.

### What types does the toy language in cranelift-jit-demo support?

Only one, isize, for every variable. The README notes that Cranelift has full support for other integer and floating point types, so the single type is a simplification to keep the demo small rather than a limitation of the backend.

### What licence does cranelift-jit-demo use?

The Cargo.toml license field reads Apache-2.0 WITH LLVM-exception, the licence form used across the Cranelift project that carries an explicit LLVM exception. The repository also has a LICENSE file at its top level.

## Sources

- [bytecodealliance/cranelift-jit-demo on GitHub](https://github.com/bytecodealliance/cranelift-jit-demo)
- [Issues](https://github.com/bytecodealliance/cranelift-jit-demo/issues)
- [License: Apache-2.0](https://github.com/bytecodealliance/cranelift-jit-demo/blob/main/LICENSE)
- [README](https://github.com/bytecodealliance/cranelift-jit-demo/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bytecodealliance-cranelift-jit-demo
