<h1 align="center">
<img width="99" alt="Rust logo" src="https://raw.githubusercontent.com/jamesgober/rust-collection/72baabd71f00e14aa9184efcb16fa3deddda3a0a/assets/rust-logo.svg">
<br>
<b>jit-lang</b>
<br>
<sub><sup>JIT COMPILER</sup></sub>
</h1>
<div align="center">
<a href="https://crates.io/crates/jit-lang"><img alt="Crates.io" src="https://img.shields.io/crates/v/jit-lang"></a>
<a href="https://crates.io/crates/jit-lang"><img alt="Downloads" src="https://img.shields.io/crates/d/jit-lang?color=%230099ff"></a>
<a href="https://docs.rs/jit-lang"><img alt="docs.rs" src="https://img.shields.io/docsrs/jit-lang"></a>
<a href="https://github.com/jamesgober/jit-lang/actions"><img alt="CI" src="https://github.com/jamesgober/jit-lang/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://github.com/rust-lang/rfcs/blob/master/text/2495-min-rust-version.md"><img alt="MSRV" src="https://img.shields.io/badge/MSRV-1.94%2B-blue"></a>
</div>
<br>
<div align="left">
<p>
jit-lang is the CODE-tier crate: it lowers the <a href="https://docs.rs/ir-lang"><code>ir-lang</code></a> intermediate representation to native machine code via <a href="https://cranelift.dev">Cranelift</a>, places that code in executable memory, and hands back a callable function — so a front-end can run what it compiled, immediately. Part of the -lang language-construction family; see _strategy/LANG_COLLECTION.md for the master plan.
</p>
<br>
<hr>
<p>
<strong>MSRV is 1.94+</strong> (Rust 2024 edition). The native code generator sets this floor; the rest of the family targets 1.85.
</p>
<blockquote>
<strong>Status: pre-1.0, in active development.</strong> The public API is being settled across the 0.x series and frozen at <code>1.0.0</code>. See <a href="./docs/API.md"><code>docs/API.md</code></a> and <a href="./CHANGELOG.md"><code>CHANGELOG.md</code></a>.
</blockquote>
</div>
<hr>
<br>
## Overview
A compiler's middle end produces a function in [SSA form](https://en.wikipedia.org/wiki/Static_single-assignment_form). The usual back end writes an object file; a JIT instead generates machine code in memory and jumps into it. jit-lang is that last step for the family: it takes an [`ir_lang::Function`](https://docs.rs/ir-lang), compiles it for the machine it is running on, and returns a handle you can call.
The surface is three types and a function. [`Jit`](./docs/API.md#jit) is the engine; [`Jit::compile`](./docs/API.md#jitcompile) turns a function into a [`Compiled`](./docs/API.md#compiled); [`Compiled::entry`](./docs/API.md#compiledentry) reinterprets the compiled code as a function pointer you can call. The free function [`compile`](./docs/API.md#compile) does all of that in one step for the one-off case. Failures are reported through [`JitError`](./docs/API.md#jiterror).
Under the hood, a validated function is translated to [Cranelift](https://cranelift.dev) IR — an almost one-to-one mapping, since both are SSA control-flow graphs whose values cross blocks as block parameters rather than through phi nodes — Cranelift generates optimized native code, and the bytes are copied into a guard-flanked, read-execute region from [`pager-lang`](https://docs.rs/pager-lang).
<br>
<hr>
<br>
## Installation
```toml
[dependencies]
jit-lang = "0.2"
ir-lang = "1"
```
Or from the terminal:
```bash
cargo add jit-lang ir-lang
```
You build the IR with `ir-lang`'s `Builder` and hand the result to jit-lang, so both crates appear in your dependencies.
<br>
## Quick Start
```rust
use jit_lang::compile;
use ir_lang::{Builder, BinOp, Type};
// Build `fn add(a: int, b: int) -> int { a + b }` with the IR builder.
let mut b = Builder::new("add", &[Type::Int, Type::Int], Type::Int);
let a = b.block_params(b.entry())[0];
let c = b.block_params(b.entry())[1];
let sum = b.bin(BinOp::Add, a, c);
b.ret(Some(sum));
// Compile it to native machine code.
let f = compile(&b.finish()).expect("add is well-formed");
assert_eq!(f.name(), "add");
// Reinterpret the entry point as a function pointer and call it.
// SAFETY: the signature is `fn(int, int) -> int`, and `f` outlives the call.
let add: extern "C" fn(i64, i64) -> i64 = unsafe { f.entry() };
assert_eq!(add(19, 23), 42);
```
<br>
<hr>
<br>
## How it works
A compile runs three stages:
1. **Validate.** The function is checked with [`Function::validate`](https://docs.rs/ir-lang), so only well-formed SSA is ever lowered. A malformed function is rejected with [`JitError::InvalidIr`](./docs/API.md#jiterror), not miscompiled.
2. **Translate and generate.** The IR is translated to Cranelift IR and Cranelift generates optimized machine code for the host, with its native instruction-set features enabled.
3. **Place.** The emitted bytes are copied into a guard-flanked memory region, which is then flipped from writable to read-execute — the write-xor-execute discipline. The functions compiled here are leaf functions with no outgoing calls, so the code is self-contained and needs no runtime relocation.
### Types and calling convention
The IR's four machine types map onto the host C ABI:
| `int` | 64-bit integer | `i64` |
| `float` | IEEE-754 double | `f64` |
| `bool` | byte holding `0` or `1` | `bool` / `u8` |
| `unit` (return) | no return value | `()` |
A `unit`-typed **parameter** has no machine representation and is refused with [`JitError::Unsupported`](./docs/API.md#jiterror). The compiled function uses the host's C calling convention, which is exactly what Rust's `extern "C"` denotes, so the two agree when you call it.
### A branch and a loop
```rust
use jit_lang::compile;
use ir_lang::{Builder, BinOp, Type};
// fn sum_to(n: int) -> int { let mut acc = 0; while n > 0 { acc += n; n -= 1 } acc }
let mut b = Builder::new("sum_to", &[Type::Int], Type::Int);
let n0 = b.block_params(b.entry())[0];
let header = b.create_block(&[Type::Int, Type::Int]); // (n, acc)
let body = b.create_block(&[]);
let exit = b.create_block(&[]);
let zero = b.iconst(0);
b.jump(header, &[n0, zero]);
b.switch_to(header);
let n = b.block_params(header)[0];
let acc = b.block_params(header)[1];
let z = b.iconst(0);
let more = b.bin(BinOp::Gt, n, z);
b.branch(more, body, &[], exit, &[]);
b.switch_to(body);
let acc2 = b.bin(BinOp::Add, acc, n);
let one = b.iconst(1);
let n2 = b.bin(BinOp::Sub, n, one);
b.jump(header, &[n2, acc2]);
b.switch_to(exit);
b.ret(Some(acc));
let f = compile(&b.finish()).expect("sum_to is well-formed");
// SAFETY: the signature is `fn(int) -> int` and `f` outlives every call.
let sum_to: extern "C" fn(i64) -> i64 = unsafe { f.entry() };
assert_eq!(sum_to(100), 5050);
```
<br>
<hr>
<br>
## API Overview
For a complete reference with examples, see [`docs/API.md`](./docs/API.md).
- [`Jit`](./docs/API.md#jit) — the engine; holds a code generator for the host and compiles many functions.
- [`Jit::new`](./docs/API.md#jitnew) / [`Jit::compile`](./docs/API.md#jitcompile) — build the engine, then compile a function to runnable code.
- [`compile`](./docs/API.md#compile) — the one-call shortcut: build an engine and compile a single function.
- [`Compiled`](./docs/API.md#compiled) — the result: native code in executable memory, with [`name`](./docs/API.md#compiled), [`params`](./docs/API.md#compiled), [`ret`](./docs/API.md#compiled), [`code_len`](./docs/API.md#compiled), [`as_ptr`](./docs/API.md#compiled), and the `unsafe` [`entry`](./docs/API.md#compiledentry).
- [`JitError`](./docs/API.md#jiterror) — the reason a compile failed: invalid IR, an unsupported feature or host, a code-generation failure, or executable memory being unavailable.
### Safety
Generating code and jumping into it cannot be checked by the compiler. The two obligations — the function-pointer type must match what was compiled, and the [`Compiled`](./docs/API.md#compiled) that owns the code must outlive every call — are concentrated in the single `unsafe` method [`Compiled::entry`](./docs/API.md#compiledentry). Building the IR, compiling it, and inspecting the result are all safe.
<br>
<hr>
<br>
## Performance
Performance is a hard constraint, not an afterthought (see [`REPS.md`](./REPS.md)). The translation to Cranelift IR is a single linear pass over the function; the heavy lifting — instruction selection, register allocation, optimization — is Cranelift's, run at its `speed` optimization level with the host's native CPU features enabled. A compiled function is plain native code: calling it is an indirect call, with no interpreter loop or dispatch in between.
Latest local Criterion means (`cargo bench --bench bench`, Linux x86_64 / WSL2, Rust stable, release build). `compile` covers validation, translation, code generation, and placing the code in executable memory:
| `compile` | two-way branch (`max`) | ~27 µs |
| `compile` | loop with a back-edge (`sum_to`) | ~34 µs |
| `compile` | straight-line, 16 `add` steps | ~178 µs |
| `compile` | straight-line, 256 `add` steps | ~4.1 ms |
| call | invoke a compiled function | ~1.4 ns |
A call into compiled code is a plain indirect call — about a nanosecond, the cost of the jump and return. Compile time is Cranelift's, and grows with function size; the large straight-line case is register allocation over a single big block. Numbers vary by CPU and environment; run the benches on your hardware for trends.
<br>
<hr>
<br>
## Configuration
jit-lang has no feature flags: the JIT is the crate, and it links the standard library and reaches the operating system for executable memory, so it is not `no_std`.
Its dependencies are the engine it is built on:
| [`ir-lang`](https://docs.rs/ir-lang) | the IR a function is built in — the input to every compile |
| [`cranelift-codegen`](https://docs.rs/cranelift-codegen) / [`cranelift-frontend`](https://docs.rs/cranelift-frontend) / [`cranelift-native`](https://docs.rs/cranelift-native) | native code generation for the host |
| [`pager-lang`](https://docs.rs/pager-lang) | executable, page-aligned memory with write-xor-execute control |
<br>
## Examples
Three runnable examples live in [`examples/`](./examples):
```bash
# Build `add`, compile it, and call it.
cargo run --example jit_and_call
# Compile and run a branch (abs) and a loop (sum_to).
cargo run --example control_flow
# Compile and run floating-point and boolean-returning functions.
cargo run --example floats
```
<br>
## Testing
```bash
# Unit, integration (workflow, properties, error paths), and doc tests
cargo test
# Property tests only
cargo test --test properties
# Lints and formatting
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
# Benchmarks (Criterion)
cargo bench --bench bench
```
The integration suite in [`tests/workflow.rs`](./tests/workflow.rs) compiles representative functions — `double`, `abs`, `max`, a countdown loop, float arithmetic, boolean logic, integer division, and a unit function — to native code and **runs** them, checking the results the CPU produced. The property tests in [`tests/properties.rs`](./tests/properties.rs) generate random functions, run the compiled code, and compare each result to an independent evaluation in Rust.
<br>
## Cross-Platform Support
Code generation targets the host through Cranelift, and executable memory is managed by pager-lang, so the supported targets are their intersection: **Linux**, **macOS**, and **Windows** on **x86-64** and **ARM64**. The CI matrix builds and tests on Linux, macOS, and Windows on stable and the 1.94 MSRV.
<br>
## Contributing
Engineering standards for this crate are the [Rust Efficiency & Performance Standards](./REPS.md); the current scope and plan are in [`dev/ROADMAP.md`](./dev/ROADMAP.md). Before a PR: `cargo fmt --all`, `cargo clippy --all-targets --all-features -- -D warnings`, and `cargo test --all-features` must be clean.
<br>
<div id="license">
<h2>License</h2>
<p>Licensed under either of</p>
<ul>
<li><b>Apache License, Version 2.0</b> — <a href="./LICENSE-APACHE">LICENSE-APACHE</a></li>
<li><b>MIT License</b> — <a href="./LICENSE-MIT">LICENSE-MIT</a></li>
</ul>
<p>at your option.</p>
</div>
<div align="center">
<h2></h2>
<sup>COPYRIGHT <small>©</small> 2026 <strong>James Gober <me@jamesgober.com>.</strong></sup>
</div>