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.

5

Share

Download to read offline

Building fast interpreters in Rust

Download to read offline

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.

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

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
  • kungenw4301

    Nov. 17, 2020
  • esneko

    Jul. 26, 2019
  • rongfengliang

    Jun. 26, 2019
  • MarioFleischhacker

    Mar. 6, 2019
  • ssuserad616d

    Mar. 4, 2019

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.

Views

Total views

3,499

On Slideshare

0

From embeds

0

Number of embeds

15

Actions

Downloads

26

Shares

0

Comments

0

Likes

5

×