最小限VM

手元にWEB-DB Press vol.60の「言語設計の基礎知識」がある人はP.28を参照ください。ここにあるような「カウントダウンをするwhile」、原稿ではC言語で説明していますけど、本当は機械語とかにもふれたかったのですよ。紙面と時間の都合でできていないのですけど。
で、生の機械語はそれはそれで色々説明しないといけないから大変かな、と思ってwhileを実現するために最小限の仮想機械を作ってみました。

# -*- encoding: utf-8 -*-
"""
mini-VM 解説のための最小限のVM
最小限とは?最小限である必要があるか?
チューリング完全である必要があるのか?

jump pos : PCをposに変更
if_eq a1 v1 pos : mem[a1] == v1 ならjump pos
print a1 : mem[a1]をprint
set a1 v1 : mem[a1]をv1にする
sub v1 : mem[0]からv1を引く
"""

memory = [0] * 10
code = [
    ("set", 0, 10), # A = 10
    ("if_eq", 0, 5), # 1: if A == 0
    ("sub", 1), # A = A - 1
    ("print", 0), # print A
    ("jump", 1),
]

def eval(code):
    cur = 0
    while cur < len(code):
        line = code[cur]
        cur += 1
        op = line[0]
        if op == "set":
            memory[line[1]] = line[2]
        elif op == "print":
            print memory[line[1]]
        elif op == "if_eq":
            if memory[0] == line[1]:
                cur = line[2]
        elif op == "sub":
            memory[0] -= line[1]
        elif op == "jump":
            cur = line[1]

eval(code)

あとPythonバイトコードではこんな感じ。真偽値をスタックに詰んでるところがちょっと違うか。

>>> def foo():
...     x = 10
...     while x > 0:
...             print x
...             x -= 1
... 
>>> dis.dis(foo) # 必要ないところは削除
  2           0 LOAD_CONST               1 (10)
              3 STORE_FAST               0 (x)

  3           6 SETUP_LOOP              33 (to 42)
        >>    9 LOAD_FAST                0 (x)
             12 LOAD_CONST               2 (0)
             15 COMPARE_OP               4 (>)
             18 JUMP_IF_FALSE           19 (to 40)
             21 POP_TOP             

  4          22 LOAD_FAST                0 (x)
             25 PRINT_ITEM          
             26 PRINT_NEWLINE       

  5          27 LOAD_FAST                0 (x)
             30 LOAD_CONST               3 (1)
             33 INPLACE_SUBTRACT    
             34 STORE_FAST               0 (x)
             37 JUMP_ABSOLUTE            9
        >>   40 POP_TOP             
             41 POP_BLOCK           

で、これでif, while, forまでは作れるわけなのだけども、関数をつくろうと思うと今の「飛び先固定のジャンプ」ではできない。そこでcallとreturnを作ろう、と思ったのだけど、そもそもこのVMでプログラムカウンタがコードからいじれるメモリー領域上にないのが行けないのであって、こういう管理に必要な値自身もmemに入れるべきだったのかもなぁ。



Pythonで書いてディスアセンブルしたものを見せるんだったら別にネイティブでも良くない?と気づいたのでやってみた。

#include<stdio.h>
int main(){
  __asm__("#set x value");
  int x = 10;
  __asm__("#start loop");
  while(x > 0){
    __asm__("#print x");
    printf("%d\n", x);
    __asm__("#decrement x");
    x--;
    __asm__("#end of loop body");
  }
  __asm__("#end of loop");
}
        #set x value
        movl    $10, -4(%rbp)
        #start loop
        jmp     L2
L3:
        #print x
        movl    -4(%rbp), %esi
        leaq    LC0(%rip), %rdi
        movl    $0, %eax
        call    _printf
        #decrement x
        decl    -4(%rbp)
        #end of loop body
L2:
        cmpl    $0, -4(%rbp)
        jg      L3
        #end of loop

いきなり下に飛ぶのかー。ふむふむ。まあでもこれ説明をちゃんと書けば予備知識のない人でも読める気がする。