statically compiling ruby with llvm

40
Statically Compiling Ruby with LLVM Laurent Sansonetti HipByte

Upload: laurent-sansonetti

Post on 15-May-2015

4.147 views

Category:

Technology


2 download

DESCRIPTION

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

TRANSCRIPT

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