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

Ruby VM Internals: TMI

Ruby VM Internals: TMI

RubyConf 2015 talk.

F29327647a9cff5c69618bae420792ea?s=128

Aaron Patterson

November 15, 2015
Tweet

Transcript

  1. None
  2. JavaScript

  3. Everything is broken.

  4. Ruby VM Internals The TMI Edition

  5. Aaron Patterson

  6. @tenderlove

  7. None
  8. None
  9. None
  10. Ruby Core Team Rails Core Team

  11. I am bad at saying "no"

  12. None
  13. None
  14. https://github.com/ManageIQ/ manageiq

  15. RubyConf 5k

  16. TEXAS!!!

  17. None
  18. Stupid Ruby VM Tricks

  19. Stupid Ruby VM Tricks

  20. Stupid Ruby VM Tricks

  21. Ruby’s Virtual Machine and its internals

  22. WARNING: THIS IS A TECH TALK

  23. &&&&&

  24. Ruby’s Virtual Machine and its internals

  25. Actually a talk about Failure.

  26. Building an AOT Compiler

  27. Rebrand Failure as: "Potential For Success"

  28. Time Breakdown of "Feeling Successful" 1% 9% 11% 11% 31%

    37% Fail Fail Fail Fail Fail Success
  29. At least I learned something, right?

  30. NO

  31. None
  32. ruby myprogram.rb

  33. Stuff that happens before the program runs Running the actual

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

    program Lexer Virtual Machine About 2 Slides Each Parser Compiler Most of Our Time
  35. Lexer

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

    LEXER
  37. Parser

  38. Method Call array each block DOT name: 'each' DO END

    name: 'array' PARSER AST (Abstract Syntax Tree)
  39. Hsing-Hui Hsu @SoManyHs 3:05

  40. Ruby 1.8 and 1.9 Diverge

  41. Ruby 1.8: Interprets AST

  42. AST Interpreter Method Call array each block

  43. Ruby 1.9+: YARV (VM)

  44. put self send array send each Compile Phase Method Call

    array each block COMPILER
  45. What is a Virtual Machine?

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

  47. 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
  48. These are defined by the capabilities of the processor

  49. Virtual Machines Give You Freedom

  50. What instructions should I dream up today?

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

  52. Stack Based VMs

  53. Like a Calculator

  54. HP Calculators are RAD

  55. 9 * 18

  56. 9㾑 18㾑

  57. None
  58. *

  59. None
  60. "That’s so much work, why bother?"

  61. GO BACK TO YOUR TI-83 Plus

  62. Instructions 9㾑 18㾑 * Stack 1: 9 2: 9 1:

    18 1: 162
  63. HP 49g+ 9㾑 18㾑 * YARV putobject 9 putobject 18

    opt_mult
  64. Virtual Machines Should Not Intimidate You

  65. C Code Should

  66. YARV Code putobject 9 putobject 18 opt_mult Program Just an

    Array Instruction Parameters
  67. So how do we get this "machine code"?

  68. Compiler

  69. Mult-Pass Compiler

  70. AST Linked List Optimizations Byte Code

  71. RubyVM::InstructionSequence

  72. RubyVM::InstructionSequence ins = RubyVM::InstructionSequence.new <<-eoruby def foo "hello " end

    puts foo + "world" eoruby puts ins.disasm
  73. RubyVM::InstructionSequence == disasm: #<ISeq:<compiled>@<compiled>>================================ 0000 trace 1 ( 1) 0002

    putspecialobject 1 0004 putspecialobject 2 0006 putobject :foo 0008 putiseq foo 0010 opt_send_without_block <callinfo!mid:core#define_method, argc:3, ARGS_SIMPLE>, <callcache> 0013 pop 0014 trace 1 ( 5) 0016 putself 0017 putself 0018 opt_send_without_block <callinfo!mid:foo, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache> 0021 putstring "world" 0023 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache> 0026 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache> 0029 leave == disasm: #<ISeq:foo@<compiled>>======================================= 0000 trace 8 ( 1) 0002 trace 1 ( 2) 0004 putstring "hello " 0006 trace 16 ( 3) 0008 leave ( 2)
  74. AST

  75. What is an AST?

  76. "Abstract Syntax Tree"

  77. Get the AST NODE* rb_compile_string(const char *f, VALUE s, int

    line)
  78. NODE is an AST Node Method Call array each block

  79. Linked List

  80. What is a Linked List (LL)?

  81. Invented in 1836

  82. Battle of the à la Mode

  83. Alexander Graham Link

  84. Item 1 Item 2 Item 3

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

    Final Product
  86. AST Linked List Optimizations Byte Code

  87. 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) {
  88. Which Branch To Follow? #define nd_type(n) ((int) (((RNODE(n))->flags & NODE_TYPEMASK)>>NODE_TYPESHIFT))

  89. LETS DO SOMETHING DANGEROUS

  90. 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
  91. 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 `<main>'
  92. 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) {
  93. Which Branch To Follow? enum node_type { NODE_SCOPE,

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

    }
  95. iseq_compile_each

  96. 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)
  97. > 2300 lines

  98. `true` case NODE_TRUE:{ if (!poped) { ADD_INSN1(ret, line, putobject, Qtrue);

    } break; } AST Node Type Add New LL Item Instruction Name Instruction Arg
  99. `p true` $ ruby -e'puts RubyVM::InstructionSequence.new("p true").disasm' == disasm: #<ISeq:<compiled>@<compiled>>================================

    0000 trace 1 ( 1) 0002 putself 0003 putobject true 0005 opt_send_without_block <callinfo!mid:p, argc:1, FCALL| ARGS_SIMPLE>, <callcache> 0008 leave `putobject` Instruction `true` Parameter
  100. 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);
  101. COMPILE recurses, ADD_* adds a new LL Item

  102. Node insn: putself Node insn: putobject arg: true Node insn:

    opt_send_without_block arg: "p"
  103. Why a Linked List?

  104. Optimizations

  105. rb_iseq_compile_node

  106. iseq_setup

  107. AST Linked List Optimizations Byte Code

  108. iseq_optimize Compile time optimizations

  109. 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}
  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}
  111. Peephole Optimizations: Eliminating Dead Code

  112. Dead Code == Useless Instructions

  113. Remove Useless Instructions /* * useless jump elimination: * jump

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

  115. For All Your Special Occasions

  116. `foo.bar` vs `foo + bar`

  117. Regular Method Dispatch (foo.bar) $ ruby -e"puts RubyVM::InstructionSequence.new(\"foo.bar\", nil, nil,

    0, :specialized_instruction => false).disasm" == disasm: #<ISeq:<compiled>@<compiled>>================================ 0000 putself 0001 send <callinfo!mid:foo, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>, nil 0005 send <callinfo!mid:bar, argc:0, ARGS_SIMPLE>, <callcache>, nil 0009 leave send
  118. Regular Method Dispatch (foo + bar) $ ruby -e"puts RubyVM::InstructionSequence.new(\"foo

    + bar\", nil, nil, 0, :specialized_instruction => false).disasm" == disasm: #<ISeq:<compiled>@<compiled>>================================ 0000 putself 0001 send <callinfo!mid:foo, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>, nil 0005 putself 0006 send <callinfo!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>, nil 0010 send <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>, nil 0014 leave send
  119. Specialized Instructions $ ruby -e"puts RubyVM::InstructionSequence.new(\"foo + bar\", nil, nil,

    0, :specialized_instruction => true).disasm" == disasm: #<ISeq:<compiled>@<compiled>>================================ 0000 putself 0001 opt_send_without_block <callinfo!mid:foo, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache> 0004 putself 0005 opt_send_without_block <callinfo!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache> 0008 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache> 0011 leave send
  120. Specialized Instructions do less work

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

  122. Linked Lists:

  123. Byte Code

  124. iseq_setup

  125. iseq_set_sequence

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

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

  128. 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
  129. 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
  130. puts 'foo' $ ruby -e"puts RubyVM::InstructionSequence.new(\"puts 'foo'\").disasm" == disasm: #<ISeq:<compiled>@<compiled>>================================

    0000 trace 1 ( 1) 0002 putself 0003 putstring "foo" 0005 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache> 0008 leave insn "foo"
  131. We finally have our byte code!

  132. Virtual Machine

  133. insns.def

  134. 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
  135. 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); }
  136. VM Optimizations

  137. VM Optimization List $ ruby -e'p RubyVM::OPTS' ["direct threaded code",

    "operands unification", "inline method cache"]
  138. Check vm_core.h

  139. Native Execution

  140. Code

  141. Decode and Dispatch

  142. Code Loop Routine Routine

  143. Threaded Interpretation

  144. Code Loop Routine Routine

  145. Code Dispatch Routine Routine Dispatch

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

  147. Code Dispatch Routine Routine Dispatch

  148. We can do better!

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

  150. Direct Threaded Interpretation

  151. Code Dispatch Routine Routine Dispatch

  152. Failing at AOT Compiling

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

  154. AHA! If it’s just a list of integers, we should

    be able to save that list to a file, then load it again.
  155. Past Aaron was not too bright.

  156. 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
  157. VALUE is a heap allocated Ruby object.

  158. The Pointer Always Changes

  159. The Object must be written to disk.

  160. END STUFF

  161. Practical Applications

  162. Tweak Optimizations

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

  164. Interpolation vs String#+ # "#{foo}#{bar}" vs foo + bar z

    = RubyVM::InstructionSequence.new <<-eoruby x = "\#{foo}\#{bar}" eoruby puts z.disasm z = RubyVM::InstructionSequence.new <<-eoruby x = foo + bar eoruby puts z.disasm
  165. Browse `iseq_compile_each`

  166. 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
  167. 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
  168. Why?

  169. REMEMBER THESE:

  170. insns.def iseq_compile_each rb_iseq_compile_node

  171. Where to learn more

  172. 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 ΠϯλϓϦλͷ֦ுϥΠϒϥϦͱ࣮ͯ͠૷͍ͯ͠·͢ɻ͜
  173. yarvarch.en #title YARV: Yet another RubyVM - Software Architecture maybe

    writing. * YARV instruction set <%= d %>
  174. None
  175. Thank You!!!