Statically Compiling Ruby with LLVM
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

Statically Compiling Ruby with LLVM

on

  • 4,241 views

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

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

Talk given at the LLVM devroom at FOSDEM 2014.

Statistics

Views

Total Views
4,241
Views on SlideShare
3,916
Embed Views
325

Actions

Likes
6
Downloads
21
Comments
0

2 Embeds 325

https://twitter.com 323
http://gazeta.yandex.ru 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Statically Compiling Ruby with LLVM Presentation Transcript

  • 1. Statically Compiling Ruby with LLVM Laurent Sansonetti HipByte
  • 2. About me • Laurent Sansonetti • Programming Language Nerd • Founder of HipByte • From Belgium (yay!)
  • 3. MacRuby
  • 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. RubyMotion
  • 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. 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. Ruby
  • 9. Ruby • Created in 1995 by Yukihiro Matsumoto (Matz) • “human-oriented language” • Dynamically typed • Object oriented • Blocks • Exceptions • Garbage collection • …
  • 10. hello.rb class Hello def initialize(something) @something = something end def say puts “Hello “ + @something end end ! Hello.new(‘world’).say
  • 11. CRuby Compilation Ruby Code AST Bytecode Runtime
  • 12. Let’s use LLVM!
  • 13. RubyMotion Compilation Ruby Code AST LLVM IR Assembly Runtime
  • 14. RubyMotion compiler • About 12k C++ LOC • Targets LLVM 2.4 • Supports the entire Ruby language “specifications”
  • 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. Method functions class Hello … def say puts “Hello “ + @something end end Method IMP Ruby Runtime ObjC Stub ObjC Runtime
  • 17. Methods def hello(x, y, z) … end
  • 18. Methods define internal i32 @"rb_scope__hello:__"(i32 %self, i8* %sel, i32 %a, i32 %b, i32 %c) { MainBlock: … }
  • 19. Conditionals def hello(something) if something true else false end end
  • 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. Local variables • Allocated on the stack • Benefits from the mem2reg pass
  • 22. Block variables • Allocated initially on the stack • Re-allocated in heap memory in case the block leaves the scope of the method
  • 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. Instance variables def initialize(foo) @foo = foo end
  • 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. 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. After passes
  • 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. Arithmetic def answer 21 + 21 end
  • 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. 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. After passes
  • 33. Arithmetic define internal i32 @rb_scope__answer__(i32 %self, i8* %sel) { MainBlock: br label %entry_point ! entry_point: … ret i32 169 }
  • 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. 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. REPL • Allows to interpret expressions at runtime • Only for development (simulator) • App process loads the compiler • Uses JIT execution engine
  • 37. Demo
  • 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. LLVM is awesome!
  • 40. Thank you lrz@hipbyte.com Twitter: @lrz