Building fast interpreters in Rust
- 3. Let's build a simple DSL
...or use one that everyone knows and loves?
- 4. Let's build a simple DSL
...or use one that everyone knows and loves?
z = x * x + y * y - 2 * x * y
- 12. Parsing
named!(mul_op<CompleteStr, Expr>, do_parse!(
init: atom >>
res: fold_many0!(
tuple!(
alt!(
char!('*') => { |_| BinOp ::Mul } |
char!('/') => { |_| BinOp ::Div }
),
atom
),
init,
|lhs, (op, rhs)| Expr ::Binary {
lhs: Box ::new(lhs),
op,
rhs: Box ::new(rhs),
}
) >>
(res)
));
- 13. Parsing
named!(add_op<CompleteStr, Expr>, do_parse!(
init: mul_op >>
res: fold_many0!(
tuple!(
alt!(
char!('+') => { |_| BinOp ::Add } |
char!('-') => { |_| BinOp ::Sub }
),
mul_op
),
init,
|lhs, (op, rhs)| Expr ::Binary {
lhs: Box ::new(lhs),
op,
rhs: Box ::new(rhs),
}
) >>
(res)
));
- 14. Parsing
• nom (combinators) - https://github.com/Geal/nom
• combine - https://github.com/Marwes/combine
• peg - https://crates.io/crates/peg
• lalrpop (LR / LALR) - https://github.com/lalrpop/lalrpop
• ...https://crates.io/keywords/parsing
- 24. AST interpretation
Expr ::Literal(lit) => *lit,
Expr ::Variable(var) => var.calc(ctx),
Expr ::Binary { lhs, op, rhs } => {
let lhs = lhs.calc(ctx);
let rhs = rhs.calc(ctx);
op.apply(lhs, rhs)
}
- 36. Bytecode interpretation
Expr ::Literal(lit) => bc.push(BytecodeOp ::Literal(*lit)),
Expr ::Variable(v) => bc.push(BytecodeOp ::Variable(*v)),
Expr ::Binary { lhs, op, rhs } => {
lhs.compile(bc);
rhs.compile(bc);
bc.push(BytecodeOp ::BinOp(*op));
}
- 37. Bytecode interpretation
for op in &self.ops {
match op {
BytecodeOp ::Literal(lit) => stack.push(*lit),
BytecodeOp ::Variable(var) => stack.push(var.calc(ctx)),
BytecodeOp ::BinOp(op) => {
let rhs = stack.pop().unwrap();
let lhs = stack.pop().unwrap();
stack.push(op.apply(lhs, rhs));
}
}
}
- 41. Closures kind-of-JIT
Expr ::Literal(lit) => Box ::new(move |_ctx| lit),
Expr ::Variable(var) => match var {
Variable ::X => Box ::new(move |ctx| ctx.x),
...
},
Expr ::Binary { lhs, op, rhs } => {
let lhs = lhs.compile();
let rhs = rhs.compile();
match op {
BinOp ::Add => Box ::new(move |ctx| lhs(ctx) + rhs(ctx)),
...,
}
}
- 42. Closures kind-of-JIT
AST: 28.13 ns
Bytecode: 27.79 ns | Closure: 20.62 ns
Bytecode compilation: 207.72 ns | Closure: 200.68 ns
0
500
1,000
1,500
2,000
0 10 20 30 40 50 60 70 80 90 100
AST Bytecode Closure
- 47. JIT compilation: SSA form
z = x * y + y * x
define double @z(double %x, double %y) unnamed_addr #0 {
%0 = fmul double %x, %y
%1 = fmul double %y, %x
%2 = fadd double %0, %1
ret double %2
}
- 48. JIT compilation: SSA form
z = x * y + y * x
define double @z(double %x, double %y) unnamed_addr #0 {
%0 = fmul double %x, %y
%1 = fmul double %y, %x
%2 = fadd double %0, %1
ret double %2
}
- 49. JIT compilation: SSA form
z = x * y + y * x
define double @z(double %x, double %y) unnamed_addr #0 {
%0 = fmul double %x, %y
%1 = fadd double %0, %0
ret double %1
}
- 50. LLVM from Rust
• Raw C API bindings:
https://github.com/tari/llvm-sys.rs
• llvm-rs (high-level wrappers, unmaintained):
https://github.com/TomBebbington/llvm-rs
• Inkwell (high-level wrappers, maintained):
https://github.com/TheDan64/inkwell
- 51. LLVM from Rust
let context = Context ::create();
let f64type = context.f64_type();
let builder = context.create_builder();
- 52. LLVM from Rust
let module = context.create_module("module");
let func = module.add_function(
"jit_compiled",
f64type.fn_type(&[
f64type.into(),
f64type.into()
], false),
None,
);
- 53. LLVM from Rust
let block = func.append_basic_block("entry");
builder.position_at_end(&block);
let result = ...;
builder.build_return(Some(&result));
- 54. LLVM from Rust
Expr ::Literal(lit) => f64type.const_float(*lit),
Expr ::Variable(var) => func.get_nth_param( ...).into_float_value(),
Expr ::Binary { lhs, op, rhs } => {
let lhs = lhs.compile( ...);
let rhs = rhs.compile( ...);
match op {
BinOp ::Add => builder.build_float_add(lhs, rhs, "add"),
...
}
}
- 55. LLVM from Rust
let engine = module.create_jit_execution_engine(
OptimizationLevel ::None
)?;
unsafe {
engine.get_function("jit_compiled")?
}
- 56. LLVM from Rust
EXC_BAD_ACCESS (code=1, address=0x6d)
0x0000000100324790 jit`llvm ::verifyModule(llvm ::Module const&,
llvm ::raw_ostream*, bool*) + 121
0x0000000100180266 jit`LLVMVerifyModule + 119
0x000000010000b030 jit`verify(self =<unavailable>) at module.rs:634
0x000000010000b11a jit`clone(self =<unavailable>) at module.rs:1323
0x000000010000ad06 jit`create_jit_execution_engine(self =<unavailable>,
opt_level =<unavailable>) at module.rs:502
- 57. LLVM from Rust
EXC_BAD_ACCESS (code=1, address=0x0)
0x000000010085c19e jit`llvm ::LLVMContext ::removeModule(llvm ::Module*) + 4
0x00000001008840f3 jit`llvm ::Module ::~Module() + 31
0x0000000100407123
jit`llvm ::MCJIT ::OwningModuleContainer ::freeModulePtrSet(llvm ::SmallPtrSet<l
lvm ::Module*, 4u>&) + 97
0x0000000100407044
jit`llvm ::MCJIT ::OwningModuleContainer ::~OwningModuleContainer() + 18
0x0000000100404452 jit`llvm ::MCJIT ::~MCJIT() + 214
0x0000000100404654 jit`llvm ::MCJIT ::~MCJIT() + 14
0x0000000100008090
jit`_$LT$inkwell ..execution_engine ..ExecEngineInner$u20$as$u20$core ..ops ..dr
op ..Drop$GT$ ::drop ::h86f518daac700ebf(self=0x00007ffeefbff310) at
execution_engine.rs:421
0x0000000100001a05
jit`core ::ptr ::drop_in_place ::h91efe68b20f1058b((null)=0x00007ffeefbff310)at
ptr.rs:59
0x0000000100001783
jit`core ::ptr ::drop_in_place ::h198dbc589dc5920e((null)=0x00007ffeefbff310)at
ptr.rs:59
- 60. LLVM JIT
AST: 28.13 ns
LLVM: 1.79 ns
1
10
100
1,000
10,000
100,000
1,000,000
10,000,000
100,000,000
1,000,000,000
1 10 100 1,000 10,000 100,000 1,000,000 10,000,000
AST LLVM
LLVM compilation: 820.98 us
- 61. LLVM JIT (optimised)
AST: 28.13 ns
LLVM: 1.79 ns / 1.50 ns
LLVM compilation: 820.98 us / 1.79 ms
1
10
100
1,000
10,000
100,000
1,000,000
10,000,000
100,000,000
1,000,000,000
1 10 100 1,000 10,000 100,000 1,000,000 10,000,000
AST LLVM LLVM (opt)
- 66. JIT compilation
https://github.com/CraneStation/cranelift:
Cranelift is designed to be a code generator for WebAssembly, but it is general enough to be
useful elsewhere too. The initial planned uses that affected its design are:
• WebAssembly compiler for the SpiderMonkey engine in Firefox.
• Backend for the IonMonkey JavaScript JIT compiler in Firefox.
- 67. JIT compilation
https://github.com/CraneStation/cranelift:
Cranelift is designed to be a code generator for WebAssembly, but it is general enough to be
useful elsewhere too. The initial planned uses that affected its design are:
• WebAssembly compiler for the SpiderMonkey engine in Firefox.
• Backend for the IonMonkey JavaScript JIT compiler in Firefox.
• Debug build backend for the Rust compiler.
- 68. JIT compilation
https://github.com/CraneStation/cranelift:
Cranelift is designed to be a code generator for WebAssembly, but it is general enough to be
useful elsewhere too. The initial planned uses that affected its design are:
• WebAssembly compiler for the SpiderMonkey engine in Firefox.
• Backend for the IonMonkey JavaScript JIT compiler in Firefox.
• Debug build backend for the Rust compiler.
...
Cranelift's APIs are not yet stable.
- 69. Cranelift
let builder = SimpleJITBuilder ::new();
let mut module: Module<SimpleJITBackend> = Module ::new(builder);
let mut ctx = module.make_context();
let mut func_builder_ctx = FunctionBuilderContext ::new();
- 70. Cranelift
let sig = &mut ctx.func.signature;
sig.params.push(AbiParam ::new(types ::F64)); // X
sig.params.push(AbiParam ::new(types ::F64)); // Y
sig.returns.push(AbiParam ::new(types ::F64));
- 72. Cranelift
let entry_ebb = builder.create_ebb();
builder.append_ebb_params_for_function_params(entry_ebb);
builder.switch_to_block(entry_ebb);
builder.seal_block(entry_ebb);
let result = ...;
builder.ins().return_(&[result]);
builder.finalize();
- 73. Cranelift
Expr ::Literal(lit) => {
let ins = builder.ins();
ins.f64const(Ieee64 ::with_float(*lit))
}
Expr ::Variable(var) => builder.ebb_params(ebb)[ ...],
Expr ::Binary { lhs, op, rhs } => {
let lhs = lhs.compile( ...);
let rhs = rhs.compile( ...);
let ins = builder.ins();
match op {
BinOp ::Add => ins.fadd(lhs, rhs),
...
}
}
- 74. Cranelift
let id = module.declare_function(
"jit_compiled",
Linkage ::Local,
&ctx.func.signature
)?;
module.define_function(id, &mut ctx)?;
module.clear_context(&mut ctx);
module.finalize_definitions();
Ok(unsafe {
::std ::mem ::transmute(module.get_finalized_function(id))
})
- 78. Cranelift JIT
AST: 28.131 ns
Cranelift: 2.36 ns
1
10
100
1,000
10,000
100,000
1,000,000
10,000,000
100,000,000
1,000,000,000
1 10 100 1,000 10,000 100,000 1,000,000 10,000,000
AST Cranelift
Cranelift compilation: 71.75 us
- 79. Cranelift JIT
AST: 28.131 ns
LLVM: 1.79 ns | Cranelift: 2.36 ns
1
10
100
1,000
10,000
100,000
1,000,000
10,000,000
100,000,000
1,000,000,000
1 10 100 1,000 10,000 100,000 1,000,000 10,000,000
AST LLVM Cranelift
LLVM compilation: 820.98 us | Cranelift: 71.75 us
- 80. Full comparison
1
10
100
1,000
10,000
100,000
1,000,000
10,000,000
100,000,000
1,000,000,000
1 10 100 1,000 10,000 100,000 1,000,000 10,000,000
AST LLVM Cranelift Closure