Cranelift のコンパイラ基盤を使ったハローワールドメモ。
Cranelift is a low-level retargetable code generator. It translates a target-independent intermediate representation into executable machine code.
Wasmtime の JIT 基盤として使われているプロダクトで、Rust から Crainelift IR を構築して各 CPU マシン語コードにコンパイルして実行することができる。オリジナルのコンパイラ言語を作る時にも活用できそうです。

逆ポーランド記法をパースして JIT で計算させる
Cargo.toml
[package] |
name = "cranelift-example" |
version = "0.1.0" |
edition = "2021" |
[dependencies] |
cranelift = "0.117.0" |
cranelift-codegen = "0.117.0" |
cranelift-frontend = "0.117.0" |
cranelift-jit = "0.117.0" |
cranelift-module = "0.117.0" |
libc = "0.2" |
bin/rpn.rs
use cranelift::codegen::ir::{types, AbiParam, Function, InstBuilder, Value}; |
use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext}; |
use cranelift_codegen::ir::UserFuncName; |
use cranelift_jit::{JITBuilder, JITModule}; |
use cranelift_module::Module; |
use std::collections::VecDeque; |
fn main() { |
// RPN expression input |
let rpn_expression = "5 1 2 + 4 * + 3 -" ; |
// Create a JIT builder and module. |
let jit_builder = JITBuilder:: new (cranelift_module::default_libcall_names()).expect( "Failed to create JITBuilder" ); |
let mut module = JITModule:: new (jit_builder); |
// Create the main function signature. |
let mut ctx = module.make_context(); |
let mut sig = module.make_signature(); |
sig.returns.push(AbiParam:: new (types::I32)); |
let main_func = module.declare_function( "main" , cranelift_module::Linkage::Export, &sig).unwrap(); |
ctx.func = Function::with_name_signature(UserFuncName::user(0, 1), sig); |
let mut builder_ctx = FunctionBuilderContext:: new (); |
let mut builder = FunctionBuilder:: new (&mut ctx.func, &mut builder_ctx); |
// Create the entry block. |
let entry_block = builder.create_block(); |
builder.switch_to_block(entry_block); |
builder.seal_block(entry_block); |
// Stack to hold values |
let mut stack: Vec<Value> = Vec:: new (); |
// Parse and evaluate the RPN expression |
let tokens: VecDeque<&str> = rpn_expression.split_whitespace().collect(); |
for token in tokens { |
match token { |
"+" => { |
let b = stack.pop().expect( "Stack underflow" ); |
let a = stack.pop().expect( "Stack underflow" ); |
let result = builder.ins().iadd(a, b); |
stack.push(result); |
} |
"-" => { |
let b = stack.pop().expect( "Stack underflow" ); |
let a = stack.pop().expect( "Stack underflow" ); |
let result = builder.ins().isub(a, b); |
stack.push(result); |
} |
"*" => { |
let b = stack.pop().expect( "Stack underflow" ); |
let a = stack.pop().expect( "Stack underflow" ); |
let result = builder.ins().imul(a, b); |
stack.push(result); |
} |
"/" => { |
let b = stack.pop().expect( "Stack underflow" ); |
let a = stack.pop().expect( "Stack underflow" ); |
let result = builder.ins().sdiv(a, b); |
stack.push(result); |
} |
_ => { |
let value: i32 = token.parse().expect( "Invalid token" ); |
let value = builder.ins().iconst(types::I32, value as i64); |
stack.push(value); |
} |
} |
} |
// The final result should be the only value left on the stack |
let result = stack.pop().expect( "No result on stack" ); |
builder.ins().return_(&[result]); |
// Finalize the function. |
builder.finalize(); |
println!( "rpn_expression: {}" , rpn_expression); |
println!( "Compiled function: " ); |
println!( "{}" , ctx.func.display()); |
// Compile and run the function. |
module.define_function(main_func, &mut ctx).unwrap(); |
module.clear_context(&mut ctx); |
module.finalize_definitions().expect( "Failed to finalize definitions" ); |
let code_ptr = module.get_finalized_function(main_func); |
let code_fn = unsafe { std::mem::transmute::<_, fn() -> i32>(code_ptr) }; |
let result = code_fn(); |
println!( "Result: {}" , result); // Expected output: Result: 14 |
} |
生成される Cranelift IR と実行ログ:
$ cargo run --bin rpn |
Compiling cranelift-example v0.1.0 (/Users/hk2a/devel/rust/cranelift-example) |
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.20s |
Running `target/debug/rpn` |
rpn_expression: 5 1 2 + 4 * + 3 - |
Compiled function : |
function u0:1() -> i32 system_v { |
block0: |
v0 = iconst.i32 5 |
v1 = iconst.i32 1 |
v2 = iconst.i32 2 |
v3 = iadd v1, v2 ; v1 = 1, v2 = 2 |
v4 = iconst.i32 4 |
v5 = imul v3, v4 ; v4 = 4 |
v6 = iadd v0, v5 ; v0 = 5 |
v7 = iconst.i32 3 |
v8 = isub v6, v7 ; v7 = 3 |
return v8 |
} |
Result: 14 |
逆ポーランド記法をパースして JIT で計算させる(スタックスロット)
bin/rpn.rs
use cranelift::codegen::ir::{types, AbiParam, Function, InstBuilder, StackSlot, StackSlotData, StackSlotKind, Value}; |
use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext}; |
use cranelift_codegen::ir::UserFuncName; |
use cranelift_jit::{JITBuilder, JITModule}; |
use cranelift_module::Module; |
use std::collections::VecDeque; |
fn main() { |
// RPN expression input |
let rpn_expression = "5 1 2 + 4 * + 3 -" ; |
// Create a JIT builder and module. |
let jit_builder = JITBuilder:: new (cranelift_module::default_libcall_names()).expect( "Failed to create JITBuilder" ); |
let mut module = JITModule:: new (jit_builder); |
// Create the main function signature. |
let mut ctx = module.make_context(); |
let mut sig = module.make_signature(); |
sig.returns.push(AbiParam:: new (types::I32)); |
let main_func = module.declare_function( "main" , cranelift_module::Linkage::Export, &sig).unwrap(); |
ctx.func = Function::with_name_signature(UserFuncName::user(0, 1), sig); |
let mut builder_ctx = FunctionBuilderContext:: new (); |
let mut builder = FunctionBuilder:: new (&mut ctx.func, &mut builder_ctx); |
// Create the entry block. |
let entry_block = builder.create_block(); |
builder.switch_to_block(entry_block); |
builder.seal_block(entry_block); |
// Stack slot to hold values |
let stack_slot = builder.create_sized_stack_slot(StackSlotData:: new (StackSlotKind::ExplicitSlot, 16, 0)); |
let mut offset = 0; |
// Parse and evaluate the RPN expression |
let tokens: VecDeque<&str> = rpn_expression.split_whitespace().collect(); |
for token in tokens { |
match token { |
"+" => { |
let b = pop(&mut builder, stack_slot, &mut offset); |
let a = pop(&mut builder, stack_slot, &mut offset); |
let result = builder.ins().iadd(a, b); |
push(&mut builder, stack_slot, &mut offset, result); |
} |
"-" => { |
let b = pop(&mut builder, stack_slot, &mut offset); |
let a = pop(&mut builder, stack_slot, &mut offset); |
let result = builder.ins().isub(a, b); |
push(&mut builder, stack_slot, &mut offset, result); |
} |
"*" => { |
let b = pop(&mut builder, stack_slot, &mut offset); |
let a = pop(&mut builder, stack_slot, &mut offset); |
let result = builder.ins().imul(a, b); |
push(&mut builder, stack_slot, &mut offset, result); |
} |
"/" => { |
let b = pop(&mut builder, stack_slot, &mut offset); |
let a = pop(&mut builder, stack_slot, &mut offset); |
let result = builder.ins().sdiv(a, b); |
push(&mut builder, stack_slot, &mut offset, result); |
} |
_ => { |
let value: i32 = token.parse().expect( "Invalid token" ); |
let value = builder.ins().iconst(types::I32, value as i64); |
push(&mut builder, stack_slot, &mut offset, value); |
} |
} |
} |
// The final result should be the only value left on the stack |
let result = pop(&mut builder, stack_slot, &mut offset); |
builder.ins().return_(&[result]); |
// Finalize the function. |
builder.finalize(); |
println!( "rpn_expression: {}" , rpn_expression); |
println!( "Compiled function: " ); |
println!( "{}" , ctx.func.display()); |
// Compile and run the function. |
module.define_function(main_func, &mut ctx).unwrap(); |
module.clear_context(&mut ctx); |
module.finalize_definitions().expect( "Failed to finalize definitions" ); |
let code_ptr = module.get_finalized_function(main_func); |
let code_fn = unsafe { std::mem::transmute::<_, fn() -> i32>(code_ptr) }; |
let result = code_fn(); |
println!( "Result: {}" , result); // Expected output: Result: 14 |
} |
// Helper functions for stack operations |
fn push(builder: &mut FunctionBuilder, stack_slot: StackSlot, offset: &mut i32, value: Value) { |
builder.ins().stack_store(value, stack_slot, *offset); |
*offset += 4; |
} |
fn pop(builder: &mut FunctionBuilder, stack_slot: StackSlot, offset: &mut i32) -> Value { |
*offset -= 4; |
builder.ins().stack_load(types::I32, stack_slot, *offset) |
} |
生成される Cranelift IR と実行ログ:
$ cargo run --bin rpn |
Compiling cranelift-example v0.1.0 (/Users/hk2a/devel/rust/cranelift-example) |
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.74s |
Running `target/debug/rpn` |
rpn_expression: 5 1 2 + 4 * + 3 - |
Compiled function: |
function u0:1() -> i32 system_v { |
ss0 = explicit_slot 16 |
block0: |
v0 = iconst.i32 5 |
stack_store v0, ss0 ; v0 = 5 |
v1 = iconst.i32 1 |
stack_store v1, ss0+4 ; v1 = 1 |
v2 = iconst.i32 2 |
stack_store v2, ss0+8 ; v2 = 2 |
v3 = stack_load.i32 ss0+8 |
v4 = stack_load.i32 ss0+4 |
v5 = iadd v4, v3 |
stack_store v5, ss0+4 |
v6 = iconst.i32 4 |
stack_store v6, ss0+8 ; v6 = 4 |
v7 = stack_load.i32 ss0+8 |
v8 = stack_load.i32 ss0+4 |
v9 = imul v8, v7 |
stack_store v9, ss0+4 |
v10 = stack_load.i32 ss0+4 |
v11 = stack_load.i32 ss0 |
v12 = iadd v11, v10 |
stack_store v12, ss0 |
v13 = iconst.i32 3 |
stack_store v13, ss0+4 ; v13 = 3 |
v14 = stack_load.i32 ss0+4 |
v15 = stack_load.i32 ss0 |
v16 = isub v15, v14 |
stack_store v16, ss0 |
v17 = stack_load.i32 ss0 |
return v17 |
} |
Result: 14 |
システムコールする例
libc の printf をコールして Hello, World を出力。
bin/hello.rs
use cranelift::codegen::ir::{types, AbiParam, Function, InstBuilder}; |
use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext}; |
use cranelift_codegen::ir::UserFuncName; |
use cranelift_jit::{JITBuilder, JITModule}; |
use std::ffi::CString; |
use cranelift_module::{Module, Linkage}; |
use libc; |
fn main() { |
// Create a JIT builder and module. |
let mut jit_builder = JITBuilder:: new (cranelift_module::default_libcall_names()).expect( "Failed to create JITBuilder" ); |
jit_builder.symbol( "printf" , printf as * const u8); |
let mut module = JITModule:: new (jit_builder); |
// Create a function signature for `printf`. |
let mut ctx = module.make_context(); |
let mut sig = module.make_signature(); |
let pointer_type = module.target_config().pointer_type(); |
sig.params.push(AbiParam:: new (pointer_type)); // フォーマット文字列の引数 |
sig.params.push(AbiParam:: new (pointer_type)); // 可変引数のためのダミー引数 |
sig.returns.push(AbiParam:: new (types::I32)); |
let printf = module.declare_function( "printf" , Linkage::Import, &sig).unwrap(); |
// Create the main function signature. |
let mut sig = module.make_signature(); |
sig.returns.push(AbiParam:: new (types::I32)); |
let main_func = module.declare_function( "main" , Linkage::Export, &sig).unwrap(); |
ctx.func = Function::with_name_signature(UserFuncName::user(0, 1), sig); |
let mut builder_ctx = FunctionBuilderContext:: new (); |
let mut builder = FunctionBuilder:: new (&mut ctx.func, &mut builder_ctx); |
// Create the entry block. |
let entry_block = builder.create_block(); |
builder.append_block_params_for_function_params(entry_block); |
builder.switch_to_block(entry_block); |
builder.seal_block(entry_block); |
// Create the string data. |
let hello_world = CString:: new ( "Hello, World!\n" ).unwrap(); |
let hello_world_ptr = hello_world.as_ptr() as i64; |
// Call the `printf` function. |
let printf_func = module.declare_func_in_func( printf , builder.func); |
let format_str = builder.ins().iconst(types::I64, hello_world_ptr); |
let zero = builder.ins().iconst(types::I64, 0); |
let call = builder.ins().call(printf_func, &[format_str, zero]); |
// Return the result of the call. |
let result = builder.inst_results(call)[0]; |
builder.ins().return_(&[result]); |
builder.finalize(); |
println!( "{}" , ctx.func.display()); |
// Compile and run the function. |
module.define_function(main_func, &mut ctx).unwrap(); |
module.clear_context(&mut ctx); |
module.finalize_definitions().expect( "Failed to finalize definitions" ); |
let code_ptr = module.get_finalized_function(main_func); |
let code_fn = unsafe { std::mem::transmute::<_, fn() -> i32>(code_ptr) }; |
code_fn(); |
} |
// Dummy printf function to link with. |
extern "C" fn printf (fmt: * const i8, _dummy: i64) -> i32 { |
unsafe { |
libc:: printf (fmt) |
} |
} |
生成される Cranelift IR と実行ログ:
$ cargo run --bin hello |
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s |
Running `target/debug/hello` |
function u0:1() -> i32 system_v { |
sig0 = (i64, i64) -> i32 system_v |
fn0 = u0:0 sig0 |
block0: |
v0 = iconst.i64 0x6000_0258_0180 |
v1 = iconst.i64 0 |
v2 = call fn0(v0, v1) ; v0 = 0x6000_0258_0180, v1 = 0 |
return v2 |
} |
Hello, World! |