Statically Compiling Ruby with LLVM

6,151 views
5,645 views

Published on

How RubyMotion makes use of LLVM to statically compile Ruby into machine code.

Talk given at the LLVM devroom at FOSDEM 2014.

Published in: Technology, Education
0 Comments
10 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
6,151
On SlideShare
0
From Embeds
0
Number of Embeds
368
Actions
Shares
0
Downloads
44
Comments
0
Likes
10
Embeds 0
No embeds

No notes for slide

Statically Compiling Ruby with LLVM

  1. 1. Statically Compiling Ruby with LLVM Laurent Sansonetti HipByte
  2. 2. About me • Laurent Sansonetti • Programming Language Nerd • Founder of HipByte • From Belgium (yay!)
  3. 3. MacRuby
  4. 4. MacRuby • 2007: Project created (as a hobby) • Replacement for RubyCocoa • Fork of CRuby 1.9 • 2008: Had a beer with Chris Lattner • 2009: Replaced bytecode VM by LLVM JIT • 2011: Left Apple
  5. 5. RubyMotion
  6. 6. RubyMotion • Command-line toolchain for iOS / OS X dev • Implementation of Ruby dialect • Unified Ruby runtime with Objective-C • Static compiler for Ruby into Intel/ARM • Platform for wrappers/libraries ecosystem • Commercial product & sustainable business
  7. 7. RubyMotion • Command-line toolchain for iOS / OS X dev • Implementation of Ruby dialect • Unified Ruby runtime with Objective-C • Static compiler for Ruby into Intel/ARM • Platform for wrappers/libraries ecosystem • Commercial product & sustainable business
  8. 8. Ruby
  9. 9. Ruby • Created in 1995 by Yukihiro Matsumoto (Matz) • “human-oriented language” • Dynamically typed • Object oriented • Blocks • Exceptions • Garbage collection • …
  10. 10. hello.rb class Hello def initialize(something) @something = something end def say puts “Hello “ + @something end end ! Hello.new(‘world’).say
  11. 11. CRuby Compilation Ruby Code AST Bytecode Runtime
  12. 12. Let’s use LLVM!
  13. 13. RubyMotion Compilation Ruby Code AST LLVM IR Assembly Runtime
  14. 14. RubyMotion compiler • About 12k C++ LOC • Targets LLVM 2.4 • Supports the entire Ruby language “specifications”
  15. 15. File functions class Hello def initialize(something) … end def say … end end ! class Ohai < Hello def say … end end ! Hello.new(‘world’).say File Scope 1 5 3 Hello Ctor 2 Hello Scope Ohai Ctor 4 Ohai Scope File Code
  16. 16. Method functions class Hello … def say puts “Hello “ + @something end end Method IMP Ruby Runtime ObjC Stub ObjC Runtime
  17. 17. Methods def hello(x, y, z) … end
  18. 18. Methods define internal i32 @"rb_scope__hello:__"(i32 %self, i8* %sel, i32 %a, i32 %b, i32 %c) { MainBlock: … }
  19. 19. Conditionals def hello(something) if something true else false end end
  20. 20. Conditionals define internal i32 @"rb_scope__hello:__"(i32 %self, i8* %sel, i32 %something) { MainBlock: call void @llvm.dbg.declare(metadata !{i32 %self}, metadata !23), !dbg !54 call void @llvm.dbg.value(metadata !{i32 %something}, i64 0, metadata !24), !dbg !54 %0 = alloca i8 store volatile i8 0, i8* %0 switch i32 %something, label %merge [ i32 0, label %else i32 4, label %else ] ! else: br label %merge ! ; preds = %MainBlock, %MainBlock merge: ; preds = %MainBlock, %else %iftmp = phi i32 [ 0, %else ], [ 2, %MainBlock ] ret i32 %iftmp }
  21. 21. Local variables • Allocated on the stack • Benefits from the mem2reg pass
  22. 22. Block variables • Allocated initially on the stack • Re-allocated in heap memory in case the block leaves the scope of the method
  23. 23. kernel.bc • Runtime primitives • vm_fast_{plus,minus,…} (arithmetic ops) • vm_ivar_{get,set} (instance variables) • vm_dispatch (method dispatch) • … • Pre-compiled into LLVM bitcode • Loaded by the compiler • Provides the initial module
  24. 24. Instance variables def initialize(foo) @foo = foo end
  25. 25. Instance variables define internal i32 @"rb_scope__initialize:__"(i32 %self, i8* %sel, i32 %foo) { MainBlock: %0 = alloca i32* %1 = alloca i32 store i32* %1, i32** %0 store i32 %foo, i32* %1 %2 = alloca i8 store volatile i8 0, i8* %2 br label %entry_point ! entry_point: %3 = load i32** %0 %4 = load i32* %3 %5 = load i32* @3 %6 = load i8** @4 call void @vm_ivar_set(i32 %self, i32 %5, i32 %4, i8* %6) ret i32 %4 }
  26. 26. Instance variables PRIMITIVE void vm_ivar_set(VALUE obj, ID name, VALUE val, void *cache_p) { … klass = *(VALUE *)obj; if (klass == cache->klass) { if ((unsigned int)cache->slot < ROBJECT(obj)->num_slots) { rb_object_ivar_slot_t *slot; slot = &ROBJECT(obj)->slots[cache->slot]; if (slot->name == name) { … GC_WB_OBJ(&slot->value, val); return; … // slow path PRIMITIVE VALUE vm_gc_wb(VALUE *slot, VALUE val) { … *slot = val; return val; }
  27. 27. After passes
  28. 28. Instance variables define internal i32 @"rb_scope__initialize:__"(i32 %self, i8* %sel, i32 %foo) { MainBlock: br label %entry_point ! entry_point: … %39 = getelementptr inbounds %struct.rb_object_ivar_slot_t* %28, i32 %26, i32 1 store i32 %4, i32* %39 … ret i32 %4 }
  29. 29. Arithmetic def answer 21 + 21 end
  30. 30. Arithmetic define internal i32 @rb_scope__answer__(i32 %self, i8* %sel) { MainBlock: br label %entry_point ! entry_point: %0 = load i8** @8 %1 = load i8* @9 %2 = call i32 @vm_fast_plus(i32 85, i32 85, i8 %1) ret i32 %2 }
  31. 31. Arithmetic PRIMITIVE VALUE vm_fast_plus(VALUE left, VALUE right, unsigned char overridden) { if (overridden == 0 && NUMERIC_P(left) && NUMERIC_P(right)) { if (FIXNUM_P(left) && FIXNUM_P(right)) { const long res = FIX2LONG(left) + FIX2LONG(right); if (FIXABLE(res)) { return LONG2FIX(res); } } } … // slow path }
  32. 32. After passes
  33. 33. Arithmetic define internal i32 @rb_scope__answer__(i32 %self, i8* %sel) { MainBlock: br label %entry_point ! entry_point: … ret i32 169 }
  34. 34. Exceptions • Implemented as C++ exceptions • Zero-cost for “normal flow” • Handlers are compiled using IR intrinsics • • “catch all” landing pad clause Exception#raise triggers __cxa_raise()
  35. 35. DWARF • All instructions have proper debug location metadata • Method/block arguments and local variables are tagged as DW_TAG_{arg,auto}_variable • Build system generates a .dSYM bundle • Can be loaded by gdb/lldb, atos(1), profilers, etc.
  36. 36. REPL • Allows to interpret expressions at runtime • Only for development (simulator) • App process loads the compiler • Uses JIT execution engine
  37. 37. Demo
  38. 38. LLVM lessons Pluses • Great to write static compilers • Easy to target new platforms • Lots of great optimization passes Minuses • C++ API breakage • Huge code size • IR is not 100% portable • Proprietary backends • Not as great to use as a JIT
  39. 39. LLVM is awesome!
  40. 40. Thank you lrz@hipbyte.com Twitter: @lrz

×