How to make a simple virtual machine

How to make a simple virtual machine

Teaches important virtual machine topics like interpretation, optimization and JIT-compilation. We'll use the Brainf**k language and build simple VMs in Python and, finally, a machine code JIT-compiler in C++ using GNU Lightning.

Link to code:
https://github.com/cslarsen/brainfuck-jit

Link to MeetUp:
http://www.meetup.com/Stavanger-Software-Developers-Meetup/events/224303407/

Full Abstract:
Virtual machines are everywhere! While they power most modern programming languages, they also show up in unexpected places like: Network packet filters, regular expression engines and all your big name games.

A virtual machine is a piece of software that executes really simple instructions, just like a CPU. Steve Jobs said it best: "It takes these really simple-minded instructions – 'Go fetch a number, add it to this one, put the result there' – but if you execute them at a rate of, say, 1 million per second, the results appear to be magic."

In this talk we'll uncover some of their mystery by building a dead simple machine, from scratch, using Python. You'll get a glimpse of what happens under the hood of VMs like the JVM and CLR, and you'll learn enough so that you can go home and build your own VM!

Near the end of the talk we'll also briefly touch on subjects like bytecode optimization, JIT-compilation and discuss usage applications.

Images of the 6502 CPU were taken from http://www.visual6502.org

62ec120256167ee34435f007becc2c13?s=128

Christian Stigen Larsen

August 18, 2015
Tweet

Transcript

  1. How to make a simple virtual machine Christian Stigen Larsen

    — Roxar Software Solutions Stavanger Software Developers, Aug. 2015
  2. while  True:      op  =  next_instruction()      

         if  op  ==  foo:          do_foo()      elif  op  ==  bar:          do_bar()      elif          …    
  3. while  True:      op  =  next_instruction()      

         if  op  ==  foo:          do_foo()      elif  op  ==  bar:          do_bar()      elif          …    
  4. while  True:      op  =  next_instruction()      

         if  op  ==  foo:          do_foo()      elif  op  ==  bar:          do_bar()      elif          …    
  5. while  True:      op  =  next_instruction()      

         if  op  ==  foo:          do_foo()      elif  op  ==  bar:          do_bar()      elif          …    
  6. while  True:      op  =  next_instruction()      

         if  op  ==  foo:          do_foo()      elif  op  ==  bar:          do_bar()      elif          …    
  7. while  True:      op  =  next_instruction()      

         if  op  ==  foo:          do_foo()      elif  op  ==  bar:          do_bar()      elif          …    
  8. Brainf**k

  9. > Move right < Move left + Increment - Decrement

    . Print , Read [ Start loop ] End loop Instructions
  10. ++++++++[>++++[>++>+++>+++>+<<<<-­‐]   >+>+>-­‐>>+[<]<-­‐]>>.>-­‐-­‐-­‐.+++++++..++   +.>>.<-­‐.<.+++.-­‐-­‐-­‐-­‐-­‐-­‐.-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐.>>+.   >++. «Hello  World!»

  11. 0 4 0 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 … … + > - - …
  12. 0 4 0 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 … … + > - - …
  13. 1 4 0 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 … … + > - - …
  14. 1 4 0 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 … … + > - - …
  15. 1 4 0 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 … … + > - - …
  16. 1 4 0 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 … … + > - - …
  17. 1 3 0 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 … … + > - - …
  18. 1 3 0 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 … … + > - - …
  19. 1 2 0 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 … … + > - - …
  20. 1 2 0 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 … … + > - - …
  21. +++++++++++++++++++++++++   +++++++++++++++++++++++++   ++++++++++++++++++++++.>+   +++++++++++++++++++++++++   +++++++++++++++++++++++++  

    ++++++++++++++++++.>+++++   +++++++++++++++++++++++++   +++++++++++++++++++++++++   +++++++++++++++++++++..>+   +++++++++.
  22. +++++++++++++++++++++++++   +++++++++++++++++++++++++   ++++++++++++++++++++++.>+   +++++++++++++++++++++++++   +++++++++++++++++++++++++  

    ++++++++++++++++++.>+++++   +++++++++++++++++++++++++   +++++++++++++++++++++++++   +++++++++++++++++++++..>+   +++++++++. HELL
  23. +++++++++++++++++++++++++   +++++++++++++++++++++++++   ++++++++++++++++++++++.>+   +++++++++++++++++++++++++   +++++++++++++++++++++++++  

    ++++++++++++++++++.>+++++   +++++++++++++++++++++++++   +++++++++++++++++++++++++   +++++++++++++++++++++..>+   +++++++++. HELL
  24. +++++++++++++++++++++++++   +++++++++++++++++++++++++   ++++++++++++++++++++++.>+   +++++++++++++++++++++++++   +++++++++++++++++++++++++  

    ++++++++++++++++++.>+++++   +++++++++++++++++++++++++   +++++++++++++++++++++++++   +++++++++++++++++++++..>+   +++++++++. HELL
  25. +++++++++++++++++++++++++   +++++++++++++++++++++++++   ++++++++++++++++++++++.>+   +++++++++++++++++++++++++   +++++++++++++++++++++++++  

    ++++++++++++++++++.>+++++   +++++++++++++++++++++++++   +++++++++++++++++++++++++   +++++++++++++++++++++..>+   +++++++++. HELL
  26. Loops 2 1 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 0 … … [ . - ] …
  27. Loops 2 1 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 0 … … [ . - ] …
  28. Loops 2 1 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 0 … … [ . - ] …
  29. Loops 1 1 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 0 … … [ . - ] …
  30. Loops 1 1 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 0 … … [ . - ] …
  31. Loops 1 1 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 0 … … [ . - ] …
  32. Loops 1 1 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 0 … … [ . - ] …
  33. Loops 1 1 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 0 … … [ . - ] …
  34. Loops 0 1 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 0 … … [ . - ] …
  35. Loops 0 1 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 0 … … [ . - ] …
  36. Loops 0 1 0 0 0 0 0 0 0

    0 0 0 0 0 0 0 0 0 0 … … [ . - ] …
  37. Interpreter class  Machine:      def  __init__(…):      

       self.memory  =  [0]*memsize          self.mptr  =  0            self.code  =  …          self.cptr  =  0          self.stack  =  deque()
  38. class  Machine:      def  __init__(…):        

     self.memory  =  [0]*memsize          self.mptr  =  0            self.code  =  …          self.cptr  =  0          self.stack  =  deque()
  39. class  Machine:      def  __init__(…):        

     self.memory  =  [0]*memsize          self.mptr  =  0            self.code  =  …          self.cptr  =  0          self.stack  =  deque()
  40. class  Machine:      def  __init__(…):        

     self.memory  =  [0]*memsize          self.mptr  =  0            self.code  =  …          self.cptr  =  0          self.stack  =  deque()
  41. def  run(self):      while  True:        

     instruction  =  self.next()          self.dispatch(instruction)
  42. def  run(self):      while  True:        

     instruction  =  self.next()          self.dispatch(instruction)
  43. def  run(self):      while  True:        

     instruction  =  self.next()          self.dispatch(instruction)
  44. def  run(self):      while  True:        

     instruction  =  self.next()          self.dispatch(instruction)
  45. def  next(self):          instruction  =  self.code[self.cptr]  

           self.cptr  +=  1          return  instruction
  46. def  next(self):          instruction  =  self.code[self.cptr]  

           self.cptr  +=  1          return  instruction
  47. def  next(self):          instruction  =  self.code[self.cptr]  

           self.cptr  +=  1          return  instruction
  48. def  dispatch(self,  i):      if  i  ==  «>»:  

           self.mptr  +=  1      elif  i  ==  «<»:          self.mptr  -­‐=  1      elif  i  ==  «+»:          self.memory[self.mptr]  +=  1      elif  i  ==  «-­‐»:          self.memory[self.mptr]  -­‐=  1      elif  i  ==  «.»:          value  =  self.memory[self.mptr]          sys.stdout.write(value)      elif  i  ==  «,»:          value  =  sys.stdint.read(1)          self.memory[self.mptr]  =  value  
  49. def  dispatch(self,  i):      if  i  ==  «>»:  

           self.mptr  +=  1      elif  i  ==  «<»:          self.mptr  -­‐=  1      elif  i  ==  «+»:          self.memory[self.mptr]  +=  1      elif  i  ==  «-­‐»:          self.memory[self.mptr]  -­‐=  1      elif  i  ==  «.»:          value  =  self.memory[self.mptr]          sys.stdout.write(value)      elif  i  ==  «,»:          value  =  sys.stdint.read(1)          self.memory[self.mptr]  =  value  
  50. def  dispatch(self,  i):      if  i  ==  «>»:  

           self.mptr  +=  1      elif  i  ==  «<»:          self.mptr  -­‐=  1      elif  i  ==  «+»:          self.memory[self.mptr]  +=  1      elif  i  ==  «-­‐»:          self.memory[self.mptr]  -­‐=  1      elif  i  ==  «.»:          value  =  self.memory[self.mptr]          sys.stdout.write(value)      elif  i  ==  «,»:          value  =  sys.stdint.read(1)          self.memory[self.mptr]  =  value  
  51. def  dispatch(self,  i):      if  i  ==  «>»:  

           self.mptr  +=  1      elif  i  ==  «<»:          self.mptr  -­‐=  1      elif  i  ==  «+»:          self.memory[self.mptr]  +=  1      elif  i  ==  «-­‐»:          self.memory[self.mptr]  -­‐=  1      elif  i  ==  «.»:          value  =  self.memory[self.mptr]          sys.stdout.write(value)      elif  i  ==  «,»:          value  =  sys.stdint.read(1)          self.memory[self.mptr]  =  value  
  52. def  dispatch(self,  i):      if  i  ==  «>»:  

           self.mptr  +=  1      elif  i  ==  «<»:          self.mptr  -­‐=  1      elif  i  ==  «+»:          self.memory[self.mptr]  +=  1      elif  i  ==  «-­‐»:          self.memory[self.mptr]  -­‐=  1      elif  i  ==  «.»:          value  =  self.memory[self.mptr]          sys.stdout.write(value)      elif  i  ==  «,»:          value  =  sys.stdint.read(1)          self.memory[self.mptr]  =  value  
  53. def  dispatch(self,  i):      if  i  ==  «>»:  

           self.mptr  +=  1      elif  i  ==  «<»:          self.mptr  -­‐=  1      elif  i  ==  «+»:          self.memory[self.mptr]  +=  1      elif  i  ==  «-­‐»:          self.memory[self.mptr]  -­‐=  1      elif  i  ==  «.»:          value  =  self.memory[self.mptr]          sys.stdout.write(value)      elif  i  ==  «,»:          value  =  sys.stdint.read(1)          self.memory[self.mptr]  =  value  
  54. def  dispatch(self,  i):      if  i  ==  «>»:  

           self.mptr  +=  1      elif  i  ==  «<»:          self.mptr  -­‐=  1      elif  i  ==  «+»:          self.memory[self.mptr]  +=  1      elif  i  ==  «-­‐»:          self.memory[self.mptr]  -­‐=  1      elif  i  ==  «.»:          value  =  self.memory[self.mptr]          sys.stdout.write(value)      elif  i  ==  «,»:          value  =  sys.stdint.read(1)          self.memory[self.mptr]  =  value  
  55. def  dispatch(self,  i):      if  i  ==  «>»:  

           self.mptr  +=  1      elif  i  ==  «<»:          self.mptr  -­‐=  1      elif  i  ==  «+»:          self.memory[self.mptr]  +=  1      elif  i  ==  «-­‐»:          self.memory[self.mptr]  -­‐=  1      elif  i  ==  «.»:          value  =  self.memory[self.mptr]          sys.stdout.write(value)      elif  i  ==  «,»:          value  =  sys.stdint.read(1)          self.memory[self.mptr]  =  value  
  56. elif  i  ==  «[»:      if  self.memory[self.mptr]  !=  0:

             self.stack.append(self.cptr-­‐1)      elif:          self.skip_block()
  57. elif  i  ==  «[»:      if  self.memory[self.mptr]  !=  0:

             self.stack.append(self.cptr-­‐1)      elif:          self.skip_block()
  58. elif  i  ==  «[»:      if  self.memory[self.mptr]  !=  0:

             self.stack.append(self.cptr-­‐1)      elif:          self.skip_block()
  59. elif  i  ==  «[»:      if  self.memory[self.mptr]  !=  0:

             self.stack.append(self.cptr-­‐1)      elif:          self.skip_block()
  60. elif  i  ==  «[»:      if  self.memory[self.mptr]  !=  0:

             self.stack.append(self.cptr-­‐1)      elif:          self.skip_block() [  …  [  …  ]  …  ]
  61. elif  i  ==  «[»:      if  self.memory[self.mptr]  !=  0:

             self.stack.append(self.cptr-­‐1)      elif:          count  =  1          while  count  >  0:              i  =  self.next()              if  i  ==  «[»:                  count  +=  1              elif  i  ==  «]»:                  count  -­‐=  1
  62. elif  i  ==  «[»:      if  self.memory[self.mptr]  !=  0:

             self.stack.append(self.cptr-­‐1)      elif:          self.skip_block()
  63. elif  i  ==  «[»:      if  self.memory[self.mptr]  !=  0:

             self.stack.append(self.cptr-­‐1)      elif:          self.skip_block()   elif  i  ==  «]»:      return_addr  =  self.stack.pop()      if  self.memory[self.mptr]  !=  0:          self.cptr  =  return_addr
  64. elif  i  ==  «[»:      if  self.memory[self.mptr]  !=  0:

             self.stack.append(self.cptr-­‐1)      elif:          self.skip_block()   elif  i  ==  «]»:      return_addr  =  self.stack.pop()      if  self.memory[self.mptr]  !=  0:          self.cptr  =  return_addr
  65. elif  i  ==  «[»:      if  self.memory[self.mptr]  !=  0:

             self.stack.append(self.cptr-­‐1)      elif:          self.skip_block()   elif  i  ==  «]»:      return_addr  =  self.stack.pop()      if  self.memory[self.mptr]  !=  0:          self.cptr  =  return_addr
  66. elif  i  ==  «[»:      if  self.memory[self.mptr]  !=  0:

             self.stack.append(self.cptr-­‐1)      elif:          self.skip_block()   elif  i  ==  «]»:      return_addr  =  self.stack.pop()      if  self.memory[self.mptr]  !=  0:          self.cptr  =  return_addr
  67. elif  i  ==  «[»:      if  self.memory[self.mptr]  !=  0:

             self.stack.append(self.cptr-­‐1)      elif:          self.skip_block()   elif  i  ==  «]»:      return_addr  =  self.stack.pop()      if  self.memory[self.mptr]  !=  0:          self.cptr  =  return_addr
  68. Demo

  69. Stack vs Register Machines

  70. Register1 Register 2 … Program Counter Frame Stack Register Machine

    Memory I/O
  71. Register1 Register 2 … Program Counter Frame Stack Register Machine

    Memory I/O self.memory[0]  (kinda) self.memory self.cptr self.stack sys.stdin,  sys.stdout self.memory[1]  (kinda)
  72. None
  73. None
  74. Register1 Register 2 … Program Counter Frame Stack Register Machine

    Memory I/O self.memory[0]  (kinda) self.memory self.cptr self.stack sys.stdin,  sys.stdout self.memory[1]  (kinda)
  75. Register1 Register 2 … Program Counter Frame Stack Register Machine

    Memory I/O
  76. Register1 Register 2 … Program Counter Frame Stack Register Machine

    Memory I/O Stack Machine Data Stack Program Counter Return Stack Memory I/O
  77. Some Stack Machines • JVM • CLR • Forth •

    CPython • Lua • …
  78. 1 Data Stack 1  2  +  3  *

  79. 2 1 Data Stack 1  2  +  3  *

  80. 2 1 Data Stack 1  2  +  3  *

  81. 3 Data Stack 1  2  +  3  *

  82. 3 3 Data Stack 1  2  +  3  *

  83. 9 Data Stack 1  2  +  3  *

  84. 9 Data Stack 1  2  +  3  * (1+2)*3

  85. Can we make the interpreter faster?

  86. JIT to Python Bytecode

  87. LOAD_CONST  «Hello  there»   PRINT_ITEM   PRINT_NEWLINE   LOAD_CONST  None

      RETURN_VALUE Python Bytecode
  88. LOAD_CONST  «Hello  there»   PRINT_ITEM   PRINT_NEWLINE   LOAD_CONST  None

      RETURN_VALUE Python Bytecode
  89. LOAD_CONST  «Hello  there»   PRINT_ITEM   PRINT_NEWLINE   LOAD_CONST  None

      RETURN_VALUE Python Bytecode
  90. LOAD_CONST  «Hello  there»   PRINT_ITEM   PRINT_NEWLINE   LOAD_CONST  None

      RETURN_VALUE Python Bytecode
  91. Brainf**k to bytecode def  plus():      LOAD_FAST  «ptr»  

       LOAD_CONST  1      INPLACE_ADD      STORE_FAST  «ptr»
  92. Loops label  «start-­‐of-­‐loop»   LOAD_FAST  «memory»        memory[ptr]

      LOAD_FAST  «ptr»   BINARY_SUBSCR   LOAD_CONST  0                    memory[ptr]  ==  0   COMPARE_OP  «==»   POP_JUMP_IF_TRUE  «end-­‐of-­‐loop»   if  memory[ptr]  ==  0:  goto  «end-­‐of-­‐loop»
  93. Loops label  «start-­‐of-­‐loop»   LOAD_FAST  «memory»        memory[ptr]

      LOAD_FAST  «ptr»   BINARY_SUBSCR   LOAD_CONST  0                    memory[ptr]  ==  0   COMPARE_OP  «==»   POP_JUMP_IF_TRUE  «end-­‐of-­‐loop»   if  memory[ptr]  ==  0:  goto  «end-­‐of-­‐loop»
  94. Optimizations >>>> ptr  +=  4 <<<< ptr  -­‐=  4 ++++

    memory[ptr]  +=  4 -­‐  -­‐  -­‐  -­‐ memory[ptr]  -­‐=  4
  95. Demo

  96. Can we make it faster?

  97. JIT to Machine Code

  98. Register V0 Points to memory cell Register V1 For arithmetic

    movi  V0,  <address  of  first  cell> GNU Lightning
  99. < subi  V0,  1 > addi  V0,  1 + ldr

     V1,  V0   addi  V1,  1   str  V0,  V1 -­‐ ldr  V1,  V0   subi  V1,  1   str  V0,  V1
  100. < subi  V0,  1 > addi  V0,  1 + ldr

     V1,  V0   addi  V1,  1   str  V0,  V1 -­‐ ldr  V1,  V0   subi  V1,  1   str  V0,  V1
  101. < subi  V0,  1 > addi  V0,  1 + ldr

     V1,  V0   addi  V1,  1   str  V0,  V1 -­‐ ldr  V1,  V0   subi  V1,  1   str  V0,  V1
  102. < subi  V0,  1 > addi  V0,  1 + ldr

     V1,  V0   addi  V1,  1   str  V0,  V1 -­‐ ldr  V1,  V0   subi  V1,  1   str  V0,  V1
  103. [ loop_start:      ldr  V1,  V0      beqi

     V1,  0,  loop_end ] loop_start:      ldr  V1,  V0      bnei  V1,  0,  loop_start
  104. [ loop_start:      ldr  V1,  V0      beqi

     V1,  0,  loop_end ]    jmp  loop_start
  105. ++++++++++   >+++[<.>-­‐]

  106. ++++++++++   >+++[<.>-­‐] mov        (%rbx),%r13   add

           $0xa,%r13   mov        %r13,(%rbx)   add        $0x8,%rbx   mov        (%rbx),%r13   add        $0x3,%r13   mov        %r13,(%rbx)  
  107. ++++++++++   >+++[<.>-­‐] mov        (%rbx),%r13   add

           $0xa,%r13   mov        %r13,(%rbx)   add        $0x8,%rbx   mov        (%rbx),%r13   add        $0x3,%r13   mov        %r13,(%rbx)  
  108. ++++++++++   >+++[<.>-­‐] mov        (%rbx),%r13   add

           $0xa,%r13   mov        %r13,(%rbx)   add        $0x8,%rbx   mov        (%rbx),%r13   add        $0x3,%r13   mov        %r13,(%rbx)  
  109. ++++++++++   >+++[<.>-­‐] mov        (%rbx),%r13   add

           $0xa,%r13   mov        %r13,(%rbx)   add        $0x8,%rbx   mov        (%rbx),%r13   add        $0x3,%r13   mov        %r13,(%rbx)  
  110. ++++++++++   >+++[<.>-­‐] 0x10006f03b:    mov        (%rbx),%r13

      0x10006f03e:   test      %r13,%r13   0x10006f041:   je          0x10006f090   …   0x10006f048:   sub        $0x8,%rbx   …   0x10006f084:   mov        (%rbx),%r13   0x10006f087:   test      %r13,%r13   0x10006f08a:   jne        0x10006f048   0x10006f090:   …
  111. ++++++++++   >+++[<.>-­‐] 0x10006f03b:    mov        (%rbx),%r13

      0x10006f03e:   test      %r13,%r13   0x10006f041:   je          0x10006f090   …   0x10006f048:   sub        $0x8,%rbx   …   0x10006f084:   mov        (%rbx),%r13   0x10006f087:   test      %r13,%r13   0x10006f08a:   jne        0x10006f048   0x10006f090:   …
  112. ++++++++++   >+++[<.>-­‐] 0x10006f03b:    mov        (%rbx),%r13

      0x10006f03e:   test      %r13,%r13   0x10006f041:   je          0x10006f090   …   0x10006f048:   sub        $0x8,%rbx   …   0x10006f084:   mov        (%rbx),%r13   0x10006f087:   test      %r13,%r13   0x10006f08a:   jne        0x10006f048   0x10006f090:   …
  113. ++++++++++   >+++[<.>-­‐] 0x10006f03b:    mov        (%rbx),%r13

      0x10006f03e:   test      %r13,%r13   0x10006f041:   je          0x10006f090   …   0x10006f048:   sub        $0x8,%rbx   …   0x10006f084:   mov        (%rbx),%r13   0x10006f087:   test      %r13,%r13   0x10006f08a:   jne        0x10006f048   0x10006f090:   …
  114. Demo

  115. Where do we go from here?

  116. Applications

  117. Applications Berkeley Packet Filter

  118. Applications ScummVM

  119. Applications Console Emulators

  120. Applications Presentation Apps

  121. Applications Embedded Programming

  122. Applications Genetic Programming

  123. Applications Fast Networking

  124. Turtles all the way down

  125. Turtles all the way down Java Source JVM Machine Code

    Microcode
  126. The end, at last!

  127. None