Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby VM Internals: TMI

Ruby VM Internals: TMI

RubyConf 2015 talk.

Aaron Patterson

November 15, 2015
Tweet

More Decks by Aaron Patterson

Other Decks in Technology

Transcript

  1. View Slide

  2. JavaScript

    View Slide

  3. Everything is broken.

    View Slide

  4. Ruby VM Internals
    The TMI Edition

    View Slide

  5. Aaron Patterson

    View Slide

  6. @tenderlove

    View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. Ruby Core Team
    Rails Core Team

    View Slide

  11. I am bad at saying "no"

    View Slide

  12. View Slide

  13. View Slide


  14. View Slide

  15. https://github.com/ManageIQ/
    manageiq

    View Slide

  16. RubyConf 5k

    View Slide

  17. TEXAS!!!

    View Slide

  18. View Slide

  19. Stupid Ruby VM Tricks

    View Slide

  20. Stupid Ruby VM Tricks

    View Slide

  21. Stupid Ruby VM Tricks

    View Slide

  22. Ruby’s Virtual Machine
    and its internals

    View Slide

  23. WARNING:
    THIS IS A TECH TALK

    View Slide

  24. &&&&&

    View Slide

  25. Ruby’s Virtual Machine
    and its internals

    View Slide

  26. Actually a talk about Failure.

    View Slide

  27. Building an AOT Compiler

    View Slide

  28. Rebrand Failure as:
    "Potential For Success"

    View Slide

  29. Time Breakdown of "Feeling
    Successful"
    1%
    9%
    11%
    11%
    31%
    37%
    Fail
    Fail
    Fail
    Fail
    Fail
    Success

    View Slide

  30. At least I learned something,
    right?

    View Slide

  31. NO

    View Slide

  32. View Slide

  33. ruby myprogram.rb

    View Slide

  34. Stuff that happens
    before the program runs
    Running the actual
    program
    eval

    View Slide

  35. Stuff that happens
    before the program runs
    Running the actual
    program
    Lexer
    Virtual Machine
    About 2 Slides Each
    Parser
    Compiler
    Most of Our Time

    View Slide

  36. Lexer

    View Slide

  37. DOT
    name: 'each'
    DO
    END
    array.each do; end name: 'array'
    LEXER

    View Slide

  38. Parser

    View Slide

  39. Method Call
    array each
    block
    DOT
    name: 'each'
    DO
    END
    name: 'array'
    PARSER
    AST
    (Abstract Syntax Tree)

    View Slide

  40. Hsing-Hui Hsu
    @SoManyHs
    3:05

    View Slide

  41. Ruby 1.8 and 1.9 Diverge

    View Slide

  42. Ruby 1.8: Interprets AST

    View Slide

  43. AST Interpreter
    Method Call
    array each
    block

    View Slide

  44. Ruby 1.9+: YARV (VM)

    View Slide

  45. put self
    send array
    send each
    Compile Phase
    Method Call
    array each
    block
    COMPILER

    View Slide

  46. What is a Virtual Machine?

    View Slide

  47. It’s like a Real Machine,
    but it’s Virtual.

    View Slide

  48. global _start
    section .text
    _start:
    ; write(1, message, 13)
    mov rax, 1 ; system call 1 is write
    mov rdi, 1 ; file handle 1 is stdout
    mov rsi, message ; address of string to
    output
    mov rdx, 13 ; number of bytes
    syscall ; invoke operating system to
    do the write
    ; exit(0)
    mov eax, 60 ; system call 60 is exit
    xor rdi, rdi ; exit code 0
    syscall ; invoke operating system to
    exit
    message:
    db "Hello, World", 10 ; note the newline at the
    end

    View Slide

  49. These are defined by the
    capabilities of the processor

    View Slide

  50. Virtual Machines
    Give You Freedom

    View Slide

  51. What instructions should I
    dream up today?

    View Slide

  52. Two Types of VM:
    Stack Based & Register Based

    View Slide

  53. Stack Based VMs

    View Slide

  54. Like a Calculator

    View Slide

  55. HP Calculators are RAD

    View Slide

  56. 9 * 18

    View Slide

  57. 9㾑 18㾑

    View Slide

  58. View Slide

  59. *

    View Slide

  60. View Slide

  61. "That’s so much work, why
    bother?"

    View Slide

  62. GO BACK TO YOUR
    TI-83 Plus

    View Slide

  63. Instructions
    9㾑
    18㾑
    *
    Stack
    1: 9
    2: 9
    1: 18
    1: 162

    View Slide

  64. HP 49g+
    9㾑
    18㾑
    *
    YARV
    putobject 9
    putobject 18
    opt_mult

    View Slide

  65. Virtual Machines
    Should Not Intimidate You

    View Slide

  66. C Code Should

    View Slide

  67. YARV Code
    putobject 9
    putobject 18
    opt_mult
    Program
    Just an Array Instruction
    Parameters

    View Slide

  68. So how do we get this
    "machine code"?

    View Slide

  69. Compiler

    View Slide

  70. Mult-Pass Compiler

    View Slide

  71. AST
    Linked List
    Optimizations
    Byte Code

    View Slide

  72. RubyVM::InstructionSequence

    View Slide

  73. RubyVM::InstructionSequence
    ins = RubyVM::InstructionSequence.new <def foo
    "hello "
    end
    puts foo + "world"
    eoruby
    puts ins.disasm

    View Slide

  74. RubyVM::InstructionSequence
    == disasm: #@>================================
    0000 trace 1 ( 1)
    0002 putspecialobject 1
    0004 putspecialobject 2
    0006 putobject :foo
    0008 putiseq foo
    0010 opt_send_without_block ,
    0013 pop
    0014 trace 1 ( 5)
    0016 putself
    0017 putself
    0018 opt_send_without_block ,
    0021 putstring "world"
    0023 opt_plus ,
    0026 opt_send_without_block ,
    0029 leave
    == disasm: #>=======================================
    0000 trace 8 ( 1)
    0002 trace 1 ( 2)
    0004 putstring "hello "
    0006 trace 16 ( 3)
    0008 leave ( 2)

    View Slide

  75. AST

    View Slide

  76. What is an AST?

    View Slide

  77. "Abstract Syntax Tree"

    View Slide

  78. Get the AST
    NODE*
    rb_compile_string(const char *f, VALUE s, int line)

    View Slide

  79. NODE is an AST Node
    Method Call
    array each
    block

    View Slide

  80. Linked List

    View Slide

  81. What is a Linked List (LL)?

    View Slide

  82. Invented in 1836

    View Slide

  83. Battle of the à la Mode

    View Slide

  84. Alexander Graham Link

    View Slide

  85. Item 1
    Item 2
    Item 3

    View Slide

  86. Linked List Generation
    VALUE
    rb_iseq_compile_node(rb_iseq_t *iseq, NODE *node)
    Our Node
    Final Product

    View Slide

  87. AST
    Linked List
    Optimizations
    Byte Code

    View Slide

  88. Which Branch To Follow?
    VALUE
    rb_iseq_compile_node(rb_iseq_t *iseq, NODE *node)
    {
    DECL_ANCHOR(ret);
    INIT_ANCHOR(ret);
    if (node == 0) {
    COMPILE(ret, "nil", node);
    iseq_set_local_table(iseq, 0);
    }
    else if (nd_type(node) == NODE_SCOPE) {

    View Slide

  89. Which Branch To Follow?
    #define nd_type(n) ((int) (((RNODE(n))->flags &
    NODE_TYPEMASK)>>NODE_TYPESHIFT))

    View Slide

  90. LETS DO SOMETHING
    DANGEROUS

    View Slide

  91. Reach Inside
    require 'fiddle'
    include Fiddle
    func = Function.new Handle::DEFAULT['rb_compile_string'],
    [TYPE_VOIDP, TYPE_VOIDP, TYPE_INT], TYPE_VOIDP
    node = func.call "", Fiddle.dlwrap("def foo; end"), 0
    p Fiddle.dlunwrap node

    View Slide

  92. MUWAHAHAHA
    $ ruby test2.rb
    test2.rb:9:in `p': method `inspect' called on unexpected
    T_NODE object (0x007facab8e6570 flags=0x1)
    (NotImplementedError)
    from test2.rb:9:in `'

    View Slide

  93. Which Branch To Follow?
    VALUE
    rb_iseq_compile_node(rb_iseq_t *iseq, NODE *node)
    {
    DECL_ANCHOR(ret);
    INIT_ANCHOR(ret);
    if (node == 0) {
    COMPILE(ret, "nil", node);
    iseq_set_local_table(iseq, 0);
    }
    else if (nd_type(node) == NODE_SCOPE) {

    View Slide

  94. Which Branch To Follow?
    enum node_type {
    NODE_SCOPE,

    View Slide

  95. THIS IS IT!!!
    default: {
    COMPILE(ret, "scoped node", node->nd_body);
    break;
    }

    View Slide

  96. iseq_compile_each

    View Slide

  97. iseq_compile_each
    /**
    compile each node
    self: InstructionSequence
    node: Ruby compiled node
    poped: This node will be poped
    */
    static int
    iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)

    View Slide

  98. > 2300 lines

    View Slide

  99. `true`
    case NODE_TRUE:{
    if (!poped) {
    ADD_INSN1(ret, line, putobject, Qtrue);
    }
    break;
    }
    AST Node Type
    Add New LL
    Item
    Instruction
    Name
    Instruction
    Arg

    View Slide

  100. `p true`
    $ ruby -e'puts RubyVM::InstructionSequence.new("p true").disasm'
    == disasm:
    #@>================================
    0000 trace 1
    ( 1)
    0002 putself
    0003 putobject true
    0005 opt_send_without_block ARGS_SIMPLE>,
    0008 leave
    `putobject`
    Instruction
    `true`
    Parameter

    View Slide

  101. If Statements
    then_label = NEW_LABEL(line);
    else_label = NEW_LABEL(line);
    end_label = NEW_LABEL(line);
    compile_branch_condition(iseq, cond_seq, node->nd_cond,
    then_label, else_label);
    COMPILE_(then_seq, "then", node->nd_body, poped);
    COMPILE_(else_seq, "else", node->nd_else, poped);
    ADD_SEQ(ret, cond_seq);
    ADD_LABEL(ret, then_label);
    ADD_SEQ(ret, then_seq);
    ADD_INSNL(ret, line, jump, end_label);
    ADD_LABEL(ret, else_label);
    ADD_SEQ(ret, else_seq);
    ADD_LABEL(ret, end_label);

    View Slide

  102. COMPILE recurses,
    ADD_* adds a new LL Item

    View Slide

  103. Node
    insn: putself
    Node
    insn: putobject
    arg: true
    Node
    insn: opt_send_without_block
    arg: "p"

    View Slide

  104. Why a Linked List?

    View Slide

  105. Optimizations

    View Slide

  106. rb_iseq_compile_node

    View Slide

  107. iseq_setup

    View Slide

  108. AST
    Linked List
    Optimizations
    Byte Code

    View Slide

  109. iseq_optimize
    Compile time optimizations

    View Slide

  110. Optimization Settings
    $ ruby -e'p RubyVM::InstructionSequence.compile_option'
    {:inline_const_cache=>true, :peephole_optimization=>true
    , :tailcall_optimization=>false, :specialized_instructio
    n=>true, :operands_unification=>true, :instructions_unif
    ication=>false, :stack_caching=>false, :trace_instructio
    n=>true, :frozen_string_literal=>false, :frozen_string_l
    iteral_debug=>false, :debug_level=>0}

    View Slide

  111. Optimization Settings
    $ ruby -e'p RubyVM::InstructionSequence.compile_option'
    {:inline_const_cache=>true, :peephole_optimization=>true
    , :tailcall_optimization=>false, :specialized_instructio
    n=>true, :operands_unification=>true, :instructions_unif
    ication=>false, :stack_caching=>false, :trace_instructio
    n=>true, :frozen_string_literal=>false, :frozen_string_l
    iteral_debug=>false, :debug_level=>0}

    View Slide

  112. Peephole Optimizations:
    Eliminating Dead Code

    View Slide

  113. Dead Code == Useless
    Instructions

    View Slide

  114. Remove Useless Instructions
    /*
    * useless jump elimination:
    * jump LABEL1
    * ...
    * LABEL1:
    * jump LABEL2
    *
    * => in this case, first jump instruction should jump to
    * LABEL2 directly
    */

    View Slide

  115. Specialized Instructions

    View Slide

  116. For All Your Special Occasions

    View Slide

  117. `foo.bar` vs `foo + bar`

    View Slide

  118. Regular Method Dispatch (foo.bar)
    $ ruby -e"puts RubyVM::InstructionSequence.new(\"foo.bar\", nil, nil,
    0, :specialized_instruction => false).disasm"
    == disasm: #@>================================
    0000 putself
    0001 send , , nil
    0005 send , , nil
    0009 leave
    send

    View Slide

  119. Regular Method Dispatch (foo + bar)
    $ ruby -e"puts RubyVM::InstructionSequence.new(\"foo + bar\", nil, nil,
    0, :specialized_instruction => false).disasm"
    == disasm: #@>================================
    0000 putself
    0001 send , , nil
    0005 putself
    0006 send , , nil
    0010 send , , nil
    0014 leave
    send

    View Slide

  120. Specialized Instructions
    $ ruby -e"puts RubyVM::InstructionSequence.new(\"foo + bar\", nil, nil,
    0, :specialized_instruction => true).disasm"
    == disasm: #@>================================
    0000 putself
    0001 opt_send_without_block ,
    0004 putself
    0005 opt_send_without_block ,
    0008 opt_plus ,
    0011 leave
    send

    View Slide

  121. Specialized Instructions
    do less work

    View Slide

  122. Reminder: We haven’t even run
    any Ruby code yet.

    View Slide

  123. Linked Lists:

    View Slide

  124. Byte Code

    View Slide

  125. iseq_setup

    View Slide

  126. iseq_set_sequence

    View Slide

  127. Byte Code:
    A list of integers*.
    *The integers are addresses

    View Slide

  128. This fact will come back to
    "byte" us.

    View Slide

  129. Raw Instructions
    /**
    ruby insn object list -> raw instruction sequence
    */
    static int
    iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *anchor)
    {
    LABEL *lobj;
    INSN *iobj;
    struct iseq_line_info_entry *line_info_table;
    unsigned int last_line = 0;
    LINK_ELEMENT *list;
    VALUE *generated_iseq;
    List of actual instructions
    and parameters

    View Slide

  130. Adds instruction to the list
    generated_iseq[code_index] = insn;

    case TS_VALUE: /* VALUE */
    {
    VALUE v = operands[j];
    generated_iseq[code_index + 1 + j] = v;
    /* to mark ruby object */
    iseq_add_mark_object(iseq, v);
    break;
    }
    Put the instruction in
    Add Parameter

    View Slide

  131. puts 'foo'
    $ ruby -e"puts RubyVM::InstructionSequence.new(\"puts 'foo'\").disasm"
    == disasm: #@>================================
    0000 trace 1
    ( 1)
    0002 putself
    0003 putstring "foo"
    0005 opt_send_without_block ,

    0008 leave
    insn
    "foo"

    View Slide

  132. We finally have
    our byte code!

    View Slide

  133. Virtual Machine

    View Slide

  134. insns.def

    View Slide

  135. Instruction Format
    @c: category
    @e: english description
    @j: japanese description
    instruction form:
    DEFINE_INSN
    instruction_name
    (instruction_operands, ..)
    (pop_values, ..)
    (return value)
    {
    .. // insn body
    }
    Byte Code
    Stack

    View Slide

  136. putstring Instruction
    /**
    @c put
    @e put string val. string will be copied.
    @j จࣈྻΛίϐʔͯ͠ελοΫʹϓογϡ͢Δɻ
    */
    DEFINE_INSN
    putstring
    (VALUE str)
    ()
    (VALUE val)
    {
    val = rb_str_resurrect(str);
    }

    View Slide

  137. VM Optimizations

    View Slide

  138. VM Optimization List
    $ ruby -e'p RubyVM::OPTS'
    ["direct threaded code", "operands unification", "inline method cache"]

    View Slide

  139. Check vm_core.h

    View Slide

  140. Native Execution

    View Slide

  141. Code

    View Slide

  142. Decode and Dispatch

    View Slide

  143. Code
    Loop
    Routine
    Routine

    View Slide

  144. Threaded Interpretation

    View Slide

  145. Code
    Loop
    Routine
    Routine

    View Slide

  146. Code
    Dispatch
    Routine
    Routine
    Dispatch

    View Slide

  147. 1. Our VM is Generated
    2. Eliminates the Loop

    View Slide

  148. Code
    Dispatch
    Routine
    Routine
    Dispatch

    View Slide

  149. We can do better!

    View Slide

  150. What if:
    The instruction
    was a function address?

    View Slide

  151. Direct Threaded Interpretation

    View Slide

  152. Code
    Dispatch
    Routine
    Routine
    Dispatch

    View Slide

  153. Failing at AOT Compiling

    View Slide

  154. Byte Code:
    A list of integers*.
    *The integers are addresses

    View Slide

  155. AHA! If it’s just a list
    of integers, we should be
    able to save that list to a
    file, then load it again.

    View Slide

  156. Past Aaron was not too bright.

    View Slide

  157. Adds instruction to the list
    generated_iseq[code_index] = insn;

    case TS_VALUE: /* VALUE */
    {
    VALUE v = operands[j];
    generated_iseq[code_index + 1 + j] = v;
    /* to mark ruby object */
    iseq_add_mark_object(iseq, v);
    break;
    }
    Add Parameter

    View Slide

  158. VALUE is a heap allocated
    Ruby object.

    View Slide

  159. The Pointer Always Changes

    View Slide

  160. The Object must be written to
    disk.

    View Slide

  161. END STUFF

    View Slide

  162. Practical Applications

    View Slide

  163. Tweak Optimizations

    View Slide

  164. Understand what your Ruby
    code is *really* doing.

    View Slide

  165. Interpolation vs String#+
    # "#{foo}#{bar}" vs foo + bar
    z = RubyVM::InstructionSequence.new <x = "\#{foo}\#{bar}"
    eoruby
    puts z.disasm
    z = RubyVM::InstructionSequence.new <x = foo + bar
    eoruby
    puts z.disasm

    View Slide

  166. Browse `iseq_compile_each`

    View Slide

  167. Which is faster?
    TABLE = {
    'foo' => 'bar'.freeze,
    'bar' => 'baz'.freeze
    }
    def table_lookup x
    TABLE[x]
    end
    def case_lookup x
    case x
    when 'foo' then 'bar'.freeze
    when 'bar' then 'baz'.freeze
    end
    end

    View Slide

  168. Now which is faster?
    TABLE = {
    'foo' => 'bar'.freeze,
    'bar' => 'baz'.freeze
    }
    def table_lookup x
    TABLE[x] || 'omg'.freeze
    end
    def case_lookup x
    case x
    when 'foo' then 'bar'.freeze
    when 'bar' then 'baz'.freeze
    when nil then 'omg'.freeze
    end
    end

    View Slide

  169. Why?

    View Slide

  170. REMEMBER THESE:

    View Slide

  171. insns.def
    iseq_compile_each
    rb_iseq_compile_node

    View Slide

  172. Where to learn more

    View Slide

  173. yarvarch.ja
    #title YARVΞʔΩςΫνϟ
    #set author ೔ຊ Ruby ͷձ ͩ͜͞͞͏͍ͪ
    - 2005-03-03(Thu) 00:31:12 +0900 ͍Ζ͍Ζͱॻ͖௚͠
    ----
    * ͜Ε͸ʁ
    [[YARV: Yet Another RubyVM|http://www.atdot.net/yarv]] ͷ ઃܭϝϞͰ͢ɻ
    YARV ͸ɺRuby ϓϩάϥϜͷͨΊͷ࣍ͷػೳΛఏڙ͠·͢ɻ
    - Compiler
    - VM Generator
    - VM (Virtual Machine)
    - Assembler
    - Dis-Assembler
    - (experimental) JIT Compiler
    - (experimental) AOT Compiler
    ݱࡏͷ YARV ͸ Ruby ΠϯλϓϦλͷ֦ுϥΠϒϥϦͱ࣮ͯ͠૷͍ͯ͠·͢ɻ͜

    View Slide

  174. yarvarch.en
    #title YARV: Yet another RubyVM - Software Architecture
    maybe writing.
    * YARV instruction set

    View Slide

  175. View Slide

  176. Thank You!!!

    View Slide