jit-assembler
A multi-architecture JIT assembler library for runtime code generation that works on any host architecture.
Features
- Multi-architecture support: Generate machine code for different target architectures
- Host-independent: Runs on any host architecture (x86_64, ARM64, etc.) to generate target code
- No-std compatible: Works in both
std
andno_std
environments - Type-safe: Leverages Rust's type system for safe instruction generation
- Dual API: Both macro-based DSL and builder pattern for different use cases
- IDE-friendly: Full autocomplete and type checking support
- JIT execution: Direct execution of assembled code as functions (std-only)
- Register usage tracking: Analyze register usage patterns for optimization and ABI compliance (
register-tracking
feature)
Supported Architectures
- RISC-V 64-bit (
riscv
feature, enabled by default) - AArch64 (
aarch64
feature, enabled by default) - Basic arithmetic and logical operations - x86-64 (
x86_64
feature) - Coming soon
Usage
Add this to your Cargo.toml
:
[]
= "0.1"
Basic Usage
use ;
use riscv64_asm;
// Macro style (concise and assembly-like)
let instructions = riscv64_asm! ;
// Method chaining style (recommended for programmatic use)
let mut builder = new;
let instructions2 = builder
.csrrw // CSR read-write using aliases
.addi // Add immediate with aliases
.add // Register add using aliases
.beq // Branch if equal
.jal // Jump and link
.ret // Return instruction
.instructions;
// Traditional style
let mut builder3 = new;
builder3.csrrw;
builder3.addi;
builder3.ret;
let instructions3 = builder3.instructions;
// Convert instructions to bytes easily
let bytes = instructions.to_bytes; // All instructions as one byte vector
let size = instructions.total_size; // Total size in bytes
let count = instructions.len; // Number of instructions
// Iterate over instructions
for in instructions.iter.enumerate
// Or access by index
let first_instr = instructions;
No-std Usage
For no_std
environments, disable the default features:
[]
= { = "0.1", = false, = ["riscv"] }
# Or for AArch64 only:
# jit-assembler = { version = "0.1", default-features = false, features = ["aarch64"] }
# Or for both architectures without std:
# jit-assembler = { version = "0.1", default-features = false, features = ["riscv", "aarch64"] }
Architecture Support
RISC-V
The RISC-V backend supports:
- Base integer instruction set (RV64I):
- Arithmetic:
add
,sub
,addi
,xor
,or
,and
,slt
,sltu
- Immediate arithmetic:
andi
,ori
,xori
,slti
,sltiu
- Shifts:
sll
,srl
,sra
,slli
,srli
,srai
- Upper immediates:
lui
,auipc
- Arithmetic:
- M extension (Integer Multiplication and Division):
- Multiply:
mul
,mulh
,mulhsu
,mulhu
- Divide:
div
,divu
,rem
,remu
- Multiply:
- Memory operations:
- Loads (signed):
ld
,lw
,lh
,lb
- Loads (unsigned):
lbu
,lhu
,lwu
- Stores:
sd
,sw
,sh
,sb
- Loads (signed):
- Control flow:
jal
,jalr
,beq
,bne
,blt
,bge
,bltu
,bgeu
- CSR instructions:
csrrw
,csrrs
,csrrc
,csrrwi
,csrrsi
,csrrci
- CSR pseudo-instructions:
csrr
(read),csrw
(write),csrs
(set),csrc
(clear),csrwi
,csrsi
,csrci
- Privileged instructions:
sret
,mret
,ecall
,ebreak
,wfi
- Pseudo-instructions:
ret
,li
- Register usage tracking: Full tracking support for all instruction types (
register-tracking
feature)
AArch64
The AArch64 backend supports:
- Basic arithmetic operations:
- Register operations:
add
,sub
,mul
,udiv
,sdiv
- Immediate operations:
addi
,subi
- Multiply-subtract operations:
msub
(multiply-subtract for implementing remainder)
- Register operations:
- Logical operations:
- Register operations:
and
,or
,xor
(EOR) - Move operations:
mov
- Register operations:
- Control flow:
- Return:
ret
,ret_reg
- Return:
- Extended operations:
- Immediate moves:
mov_imm
(for larger constants) - Shift operations:
shl
(left shift using multiply)
- Immediate moves:
- Register conventions: Following AAPCS64 (ARM ABI)
- Argument/return registers: X0-X7
- Caller-saved temporaries: X8-X18
- Callee-saved registers: X19-X28
- Special registers: X29 (FP), X30 (LR), X31 (SP/XZR)
- Register usage tracking: Full tracking support (
register-tracking
feature) - JIT compilation: Direct function compilation and execution
Future Architectures
Support for additional architectures is planned:
- x86-64: Intel/AMD 64-bit instruction set
Examples
JIT Compiler Integration
use ;
use riscv64_asm;
// Simple function generator with macro
// Builder pattern for complex logic
AArch64 Usage
use ;
use InstructionBuilder;
use aarch64_asm;
// Macro style (concise and assembly-like)
// Builder pattern style
// More complex AArch64 example with immediate values (macro style)
// More complex AArch64 example with immediate values (builder style)
JIT Execution (std-only)
Create and execute functions directly at runtime:
use ;
use InstructionBuilder;
// Create a JIT function that adds two numbers
let add_func = unsafe .expect;
// Call the JIT function naturally - just like a regular function!
let result = add_func.call;
assert_eq!;
// Create a function that returns a constant
let constant_func = unsafe .expect;
let result = constant_func.call;
assert_eq!;
Note: JIT execution requires the target architecture to match the host architecture. RISC-V code will only execute correctly on RISC-V systems.
Features:
- Type-safe function signatures
- Automatic memory management with
jit-allocator2
- Natural function call syntax:
func.call()
,func.call(arg)
,func.call(arg1, arg2)
, etc. - just like regular functions! - Cross-platform executable memory allocation
Register Usage Tracking
The register-tracking
feature enables comprehensive analysis of register usage patterns in your JIT-compiled code, helping with optimization and ABI compliance.
Enable Register Tracking
Add the feature to your Cargo.toml
:
[]
= { = "0.1", = ["register-tracking"] }
Usage Example
use ;
use InstructionBuilder;
let mut builder = new;
// Build a function that uses various registers
builder
.add // T0 written, T1+T2 read
.addi // T3 written, SP read
.mul // A0 written, A1+A2 read
.ld // S0 written, T0 read
.sd; // SP+S1 read
// Analyze register usage
let usage = builder.register_usage;
println!;
println!;
println!;
println!;
// ABI compliance analysis
println!;
println!;
println!;
// Detailed breakdown
let = usage.count_by_abi_class;
println!;
Key Features
- Separate tracking: Distinguishes between written (def) and read (use) registers
- ABI classification: Automatically categorizes registers as caller-saved, callee-saved, or special-purpose
- Stack frame analysis: Determines if function prologue/epilogue is needed based on callee-saved register usage
- Comprehensive coverage: Tracks all RISC-V instruction types (R, I, S, B, U, J, CSR)
- No-std compatible: Uses
hashbrown
for no-std environments
Register ABI Classification (RISC-V)
- Caller-saved: T0-T6, A0-A7, RA - Can be freely used without preservation
- Callee-saved: S0-S11, SP - Must be saved/restored if modified
- Special: X0 (zero), GP, TP - Require careful handling
This information is invaluable for:
- Register allocation: Choose optimal registers for variables
- ABI compliance: Ensure proper calling convention adherence
- Performance optimization: Minimize unnecessary register saves/restores
- Code analysis: Understand register pressure and usage patterns
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License. See the LICENSE file for details.