Statically Compiling Ruby with LLVM

40
Statically Compiling Ruby with LLVM Laurent Sansonetti HipByte

description

How RubyMotion makes use of LLVM to statically compile Ruby into machine code. Talk given at the LLVM devroom at FOSDEM 2014.

Transcript of Statically Compiling Ruby with LLVM

Page 1: Statically Compiling Ruby with LLVM

Statically Compiling Ruby with LLVM

Laurent Sansonetti HipByte

Page 2: Statically Compiling Ruby with LLVM

About me• Laurent Sansonetti

• Programming Language Nerd • Founder of HipByte • From Belgium (yay!)

Page 3: Statically Compiling Ruby with LLVM

MacRuby

Page 4: Statically Compiling Ruby with LLVM

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

Page 5: Statically Compiling Ruby with LLVM

RubyMotion

Page 6: Statically Compiling Ruby with LLVM

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

Page 7: Statically Compiling Ruby with LLVM

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

Page 8: Statically Compiling Ruby with LLVM

Ruby

Page 9: Statically Compiling Ruby with LLVM

Ruby• Created in 1995 by Yukihiro Matsumoto (Matz)

• “human-oriented language” • Dynamically typed • Object oriented • Blocks • Exceptions • Garbage collection • …

Page 10: Statically Compiling Ruby with LLVM

hello.rbclass Hello def initialize(something) @something = something end def say puts “Hello “ + @something end end !Hello.new(‘world’).say

Page 11: Statically Compiling Ruby with LLVM

CRuby

Compilation

AST

Bytecode

Ruby Code

Runtime

Page 12: Statically Compiling Ruby with LLVM

Let’s use LLVM!

Page 13: Statically Compiling Ruby with LLVM

RubyMotion

Compilation

AST

LLVM IR

Assembly

Ruby Code

Runtime

Page 14: Statically Compiling Ruby with LLVM

RubyMotion compiler• About 12k C++ LOC • Targets LLVM 2.4 • Supports the entire Ruby language “specifications”

Page 15: Statically Compiling Ruby with LLVM

File functionsclass Hello def initialize(something) … end def say … end end !class Ohai < Hello def say … end end !Hello.new(‘world’).say

File Scope

Hello Ctor

Ohai Ctor

Hello Scope

Ohai Scope

File Code

1

2

53

4

Page 16: Statically Compiling Ruby with LLVM

Method functions

class Hello … def say puts “Hello “ + @something end end

Method IMP

ObjC Stub ObjC Runtime

Ruby Runtime

Page 17: Statically Compiling Ruby with LLVM

Methodsdef hello(x, y, z) … end

Page 18: Statically Compiling Ruby with LLVM

Methods

define internal i32 @"rb_scope__hello:__"(i32 %self, i8* %sel, i32 %a, i32 %b, i32 %c) { MainBlock: … }

Page 19: Statically Compiling Ruby with LLVM

Conditionalsdef hello(something) if something true else false end end

Page 20: Statically Compiling Ruby with LLVM

Conditionalsdefine 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: ; preds = %MainBlock, %MainBlock br label %merge !merge: ; preds = %MainBlock, %else %iftmp = phi i32 [ 0, %else ], [ 2, %MainBlock ] ret i32 %iftmp }

Page 21: Statically Compiling Ruby with LLVM

Local variables• Allocated on the stack

• Benefits from the mem2reg pass

Page 22: Statically Compiling Ruby with LLVM

Block variables• Allocated initially on the stack • Re-allocated in heap memory in case the block

leaves the scope of the method

Page 23: Statically Compiling Ruby with LLVM

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

Page 24: Statically Compiling Ruby with LLVM

Instance variablesdef initialize(foo) @foo = foo end

Page 25: Statically Compiling Ruby with LLVM

Instance variablesdefine 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 }

Page 26: Statically Compiling Ruby with LLVM

Instance variablesPRIMITIVE 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; }

Page 27: Statically Compiling Ruby with LLVM

After passes

Page 28: Statically Compiling Ruby with LLVM

Instance variablesdefine 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 }

Page 29: Statically Compiling Ruby with LLVM

Arithmeticdef answer 21 + 21 end

Page 30: Statically Compiling Ruby with LLVM

Arithmeticdefine 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 }

Page 31: Statically Compiling Ruby with LLVM

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 }

Page 32: Statically Compiling Ruby with LLVM

After passes

Page 33: Statically Compiling Ruby with LLVM

Arithmetic

define internal i32 @rb_scope__answer__(i32 %self, i8* %sel) { MainBlock: br label %entry_point !entry_point: … ret i32 169 }

Page 34: Statically Compiling Ruby with LLVM

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()

Page 35: Statically Compiling Ruby with LLVM

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.

Page 36: Statically Compiling Ruby with LLVM

REPL• Allows to interpret expressions at runtime

• Only for development (simulator) • App process loads the compiler • Uses JIT execution engine

Page 37: Statically Compiling Ruby with LLVM

Demo

Page 38: Statically Compiling Ruby with LLVM

LLVM lessonsPluses • 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

Page 39: Statically Compiling Ruby with LLVM

LLVM is awesome!

Page 40: Statically Compiling Ruby with LLVM

Thank [email protected]

Twitter: @lrz