danthedaniel/BF-JIT
{ "createdAt": "2018-11-30T05:32:08Z", "defaultBranch": "master", "description": "BrainFuck just-in-time compiler", "fullName": "danthedaniel/BF-JIT", "homepage": "", "language": "Rust", "name": "BF-JIT", "pushedAt": "2025-09-24T03:36:39Z", "stargazersCount": 30, "topics": [ "brainfuck", "interpreter", "just-in-time" ], "updatedAt": "2025-11-11T01:41:07Z", "url": "https://github.com/danthedaniel/BF-JIT"}BF Just-in-Time Compiler
Section titled “BF Just-in-Time Compiler”A very over-engineered BrainFuck interpreter/optimizing JIT compiler written in rust. Done from first-principles without any research or examination of prior art*.
*Update:
The aarch64 implementation in src/runnable/jit/code_gen/aarch64.rs was written
almost entirely by Claude 4 Opus.
Support
Section titled “Support”- Linux x86-64 (GNU/musl)
- Linux aarch64 (GNU/musl)
- MacOS x86-64
- MacOS aarch64
Interpreter Only
Section titled “Interpreter Only”- Linux x86
If you are using VS Code or derivative editors, it’s recommended to install the Rust Analyzer extension and configure your editor to check all supported targets:
# First install all supported targets so "cargo check --target $TARGET" worksrustup target add x86_64-unknown-linux-gnurustup target add aarch64-unknown-linux-gnurustup target add i686-unknown-linux-gnurustup target add x86_64-apple-darwinrustup target add aarch64-apple-darwinAdd these keys to .vscode/settings.json:
{ "rust-analyzer.cargo.allTargets": true, "rust-analyzer.cargo.buildScripts.enable": true, "rust-analyzer.check.targets": [ "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu", "i686-unknown-linux-gnu", "x86_64-apple-darwin", "aarch64-apple-darwin", ],}Fucker
Usage: fucker [--int] <program> fucker (--ast) <program> fucker (-h | --help)
Options: -h --help Show this screen. --ast Display intermediate language. --int Use an interpreter instead of the JIT compiler.What is BrainFuck?
Section titled “What is BrainFuck?”BrainFuck is an esoteric programming language designed to be both turing complete and easy to compile. The environment provides the programmer with an a 30,000 cell array of unsigned bytes and a data pointer. There are only 8 single character commands:
+: Increment the current memory cell by 1 (with wrapping overflow)-: Decrement the current memory cell by 1 (with wrapping underflow)>: Shift the data pointer to the next memory cell<: Shift the data pointer to the previous memory cell.: Output the current memory cell as an ASCII character,: Read one ASCII character from stdin[: Jump to the matching]if the current memory cell is0]: Jump to the matching[if the current memory cell is not0
Implementation
Section titled “Implementation”Parser and AST
Section titled “Parser and AST”The compiler first parses BrainFuck source code into an Abstract Syntax Tree (AST) representation. This intermediate representation enables optimizations before execution:
#[derive(Debug, Clone, Eq, PartialEq)]pub enum AstNode { /// Add to the current memory cell. Incr(u8), /// Remove from the current memory cell. Decr(u8), /// Shift the data pointer to the right. Next(u16), /// Shift the data pointer to the left. Prev(u16), /// Display the current memory cell as an ASCII character. Print, /// Read one character from stdin. Read, /// Set a literal value in the current cell. Set(u8), /// Add the current cell to the cell n spaces away and set the current cell to 0. AddTo(i16), /// Subtract the current cell from the cell n spaces away and set the current cell to 0. SubFrom(i16), /// Multiply current cell by a factor and add to cell at offset, then set current to 0. MultiplyAddTo(i16, u8), /// Copy current cell to multiple offsets, then set current to 0. CopyTo(Vec<i16>), /// Loop over the contained instructions while the current memory cell is /// not zero. Loop(VecDeque<AstNode>),}Optimization Techniques
Section titled “Optimization Techniques”The compiler implements several optimization passes during AST construction:
1. Run-Length Encoding
Section titled “1. Run-Length Encoding”Sequential identical operations are combined into single instructions with counts:
++++becomesIncr(4)>>>>becomesNext(4)----becomesDecr(4)<<<<becomesPrev(4)
This optimization alone provides an approximately 3x speedup on typical BrainFuck programs.
2. Loop Pattern Recognition
Section titled “2. Loop Pattern Recognition”Common BrainFuck idioms are detected and replaced with optimized operations:
Zero loops: [-] or [+] → Set(0)
- Replaces loops that simply zero the current cell
Move loops: [-<+>] → AddTo(-1)
- Detects patterns that move the current cell’s value to another location
- Supports both addition (
AddTo) and subtraction (SubFrom) variants - Works with arbitrary offsets in either direction
3. Constant Folding
Section titled “3. Constant Folding”Operations on literal values are computed at compile time:
Set(5)followed byIncr(3)becomesSet(8)
Execution Backends
Section titled “Execution Backends”The compiler supports two execution modes:
Interpreter Backend
Section titled “Interpreter Backend”A traditional bytecode interpreter that executes the optimized AST directly. This provides:
- Guaranteed compatibility across all architectures
- Fallback when JIT compilation is unavailable
The interpreter uses a simple virtual machine with:
- 30,000+ cell memory array (dynamically expandable)
- Program counter and data pointer
- Stack-based loop handling with pre-computed jump offsets
JIT Compiler Backend
Section titled “JIT Compiler Backend”A Just-In-Time compiler that generates native machine code for maximum performance.
Supported Architectures:
- x86-64
- AArch64
JIT Compilation Strategy: The JIT uses a hybrid approach combining Ahead-of-Time (AOT) and Just-in-Time compilation:
- Small loops (< 22 instructions): Compiled immediately (AOT)
- Large loops: Deferred compilation using a promise system
- Hot code paths: Compiled on first execution, cached for subsequent runs
Code Generation:
- Direct assembly generation without external assemblers
- Register allocation optimized for BrainFuck’s memory model:
r10/x19: Data pointer registerr11/x20: JIT context pointerr12/x21: Virtual function table pointer
- Efficient calling conventions for I/O operations
- Proper stack frame management and callee-saved register preservation
Assembly Code Examples:
The JIT compiler generates native assembly for each BrainFuck operation:
Increment (++++ → Incr(4)):
; x86-64add BYTE PTR [r10], 4
; AArch64ldrb w8, [x19]add w8, w8, #4strb w8, [x19]Pointer movement (>>>> → Next(4)):
; x86-64add r10, 4
; AArch64add x19, x19, #4Cell zeroing ([-] → Set(0)):
; x86-64mov BYTE PTR [r10], 0
; AArch64mov w8, #0strb w8, [x19]Move operation ([-<+>] → AddTo(-1)):
; x86-64movzx eax, BYTE PTR [r10] ; Load current celladd BYTE PTR [r10-1], al ; Add to target cellmov BYTE PTR [r10], 0 ; Zero current cell
; AArch64ldrb w8, [x19] ; Load current cellldrb w9, [x19, #-1] ; Load target celladd w9, w9, w8 ; Add valuesstrb w9, [x19, #-1] ; Store to targetmov w8, #0 ; Zero current cellstrb w8, [x19]Memory Management:
- Executable memory pages allocated with proper permissions
- Automatic cleanup of compiled code fragments
- Promise-based deferred compilation for memory efficiency
Control Flow Handling
Section titled “Control Flow Handling”Loops are the most complex aspect of BrainFuck compilation:
- AOT loops: Small loops are compiled inline with conditional jumps
- JIT loops: Large loops use a callback mechanism:
- First execution triggers compilation via callback
- Compiled code is cached in a promise table
- Subsequent executions call the cached native code directly
Jump Resolution:
- Forward jumps (
[) use conditional branches that skip the loop body - Backward jumps (
]) use conditional branches that return to loop start - Jump distances are computed during compilation for optimal instruction selection
I/O System
Section titled “I/O System”Both backends use an I/O system supporting:
- Standard input/output (default)
- Custom readers/writers for testing
- Proper error handling for EOF conditions
- UTF-8/ASCII character handling
The JIT compiler implements I/O through a virtual function table, allowing:
- Efficient native code calls to Rust I/O functions
- Consistent behavior between interpreter and JIT modes
- Easy testing with mock I/O streams
Benchmarks
Section titled “Benchmarks”Ran on mandelbrot.bf.
Intel Core i5-3230M
Section titled “Intel Core i5-3230M”| Version | Runtime |
|---|---|
| Naive Interpreter | 56.824s |
| Optimized Interpreter | 19.055s |
| Optimized JIT | 1.06s |
Apple M3
Section titled “Apple M3”| Version | Runtime |
|---|---|
| Optimized Interpreter | 8.18s |
| Optimized JIT | 0.39s |