Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Building fast interpreters in Rust

1,453 views

Published on

Presentation I gave at a Rust Austin meetup in November 2018 about exploring different approaches for interpreting custom DSLs in Rust with varying speed characteristics and associated safety issues.

Published in: Technology
  • Be the first to comment

Building fast interpreters in Rust

  1. 1. Arbitrary code execution in Rust or how to roll out custom DSL without getting hurt Ingvar Stepanyan @RReverser
  2. 2. Let's build a simple DSL
  3. 3. Let's build a simple DSL ...or use one that everyone knows and loves?
  4. 4. Let's build a simple DSL ...or use one that everyone knows and loves? z = x * x + y * y - 2 * x * y
  5. 5. AST representation pub enum BinOp { Add, Sub, Mul, Div, }
  6. 6. AST representation pub enum Variable { X, Y, }
  7. 7. AST representation pub enum Expr { Literal(f64), Variable(Variable), Binary { lhs: Box<Expr>, op: BinOp, rhs: Box<Expr>, }, }
  8. 8. Parsing
  9. 9. Parsing https://github.com/Geal/nom
  10. 10. Parsing named!(var<CompleteStr, Variable>, alt!( tag!("x") => { |_| Variable ::X } | tag!("y") => { |_| Variable ::Y } ));
  11. 11. Parsing named!(atom<CompleteStr, Expr>, alt!( double => { Expr ::Literal } | var => { Expr ::Variable } | delimited!(char!('('), add_op, char!(')')) ));
  12. 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. 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. 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
  15. 15. AST interpretation Binary {     lhs: Binary {         lhs: Literal(2.0),         op: Mul,         rhs: Variable(X),     },     op: Mul,     rhs: Variable(Y), } z = 2 * x * y
  16. 16. AST interpretation Binary {     lhs: Binary {         lhs: Literal(2.0),         op: Mul,         rhs: Variable(X),     },     op: Mul,     rhs: Variable(Y), } z = 2 * x * y
  17. 17. AST interpretation Binary {     lhs: Binary {         lhs: Literal(2.0),         op: Mul,         rhs: Variable(X),     },     op: Mul,     rhs: Variable(Y), } z = 2 * x * y
  18. 18. AST interpretation Binary {     lhs: Binary {         lhs: Literal(2.0),         op: Mul,         rhs: Variable(X),     },     op: Mul,     rhs: Variable(Y), } z = 2 * x * y
  19. 19. AST interpretation Binary {     lhs: Binary {         lhs: Literal(2.0),         op: Mul,         rhs: Variable(X),     },     op: Mul,     rhs: Variable(Y), } z = 2 * x * y
  20. 20. AST interpretation Binary {     lhs: Binary {         lhs: Literal(2.0),         op: Mul,         rhs: Variable(X),     },     op: Mul,     rhs: Variable(Y), } z = 2 * x * y
  21. 21. AST interpretation Binary {     lhs: Binary {         lhs: Literal(2.0),         op: Mul,         rhs: Variable(X),     },     op: Mul,     rhs: Variable(Y), } z = 2 * x * y
  22. 22. AST interpretation Binary {     lhs: Binary {         lhs: Literal(2.0),         op: Mul,         rhs: Variable(X),     },     op: Mul,     rhs: Variable(Y), } z = 2 * x * y
  23. 23. AST interpretation Binary {     lhs: Binary {         lhs: Literal(2.0),         op: Mul,         rhs: Variable(X),     },     op: Mul,     rhs: Variable(Y), } z = 2 * x * y
  24. 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) }
  25. 25. AST interpretation AST: 28.13 ns 0 500 1,000 1,500 2,000 0 10 20 30 40 50 60 70 80 90 100
  26. 26. Bytecode interpretation z = 2 * x * y
  27. 27. Bytecode interpretation z = 2 * x * y 2 x * y *
  28. 28. Bytecode interpretation z = 2 x * y * [ ]
  29. 29. Bytecode interpretation z = 2 x * y * [ ]
  30. 30. Bytecode interpretation z = 2 x * y * [ 2 ]
  31. 31. Bytecode interpretation z = 2 x * y * [ 2, x ]
  32. 32. Bytecode interpretation z = 2 x * y * [ 2 * x ]
  33. 33. Bytecode interpretation z = 2 x * y * [ 2 * x, y ]
  34. 34. Bytecode interpretation z = 2 x * y * [ (2 * x) * y ]
  35. 35. Bytecode interpretation enum BytecodeOp { Literal(f64), Variable(Variable), BinOp(BinOp), }
  36. 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. 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)); } } }
  38. 38. Bytecode interpretation AST: 28.13 ns Bytecode: 27.79 ns Bytecode compilation: 207.72 ns 0 500 1,000 1,500 2,000 0 10 20 30 40 50 60 70 80 90 100 AST Bytecode
  39. 39. Closures kind-of-JIT
  40. 40. Closures kind-of-JIT pub type CompiledExpr = Box<dyn Fn(&Context) -> f64>;
  41. 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. 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
  43. 43. JIT compilation
  44. 44. JIT compilation https://llvm.org/
  45. 45. JIT compilation: SSA form z = x * y + y * x
  46. 46. JIT compilation: SSA form z = x * y + y * x
  47. 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. 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. 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. 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. 51. LLVM from Rust let context = Context ::create(); let f64type = context.f64_type(); let builder = context.create_builder();
  52. 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. 53. LLVM from Rust let block = func.append_basic_block("entry"); builder.position_at_end(&block); let result = ...; builder.build_return(Some(&result));
  54. 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. 55. LLVM from Rust let engine = module.create_jit_execution_engine( OptimizationLevel ::None )?; unsafe { engine.get_function("jit_compiled")? }
  56. 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. 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
  58. 58. LLVM JIT AST: 28.13 ns LLVM: 1.79 ns
  59. 59. LLVM JIT AST: 28.13 ns LLVM: 1.79 ns LLVM compilation: 820.98 us
  60. 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. 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)
  62. 62. JIT compilation
  63. 63. JIT compilation https://github.com/CraneStation/cranelift:
  64. 64. 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:
  65. 65. 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.
  66. 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. 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. 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. 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. 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));
  71. 71. Cranelift let mut builder = FunctionBuilder ::new( &mut ctx.func, &mut func_builder_ctx );
  72. 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. 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. 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)) })
  75. 75. Cranelift JIT
  76. 76. Cranelift JIT AST: 28.131 ns Cranelift: 2.36 ns
  77. 77. Cranelift JIT AST: 28.131 ns Cranelift: 2.36 ns Cranelift compilation: 71.75 us
  78. 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. 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. 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

×