JITアセンブラを作った

とはいってもx86機械語を生成するのではなく、VMバイトコードを生成するやつを作りました。x86JITアセンブラは次の世代で作る予定です。
このJITアセンブラは現在実装中のインタプリタに内蔵するJITコンパイラで用います。
以下はフィボナッチを計算するコードを動的に生成する例。

(fun jit_fib (n) (
    (var c (make_jitassembler))

    (var ifelse (fresh_label c))
    (var fib    (fresh_label c))

    (set_label c fib)
    (put_arg0 c)
    (put_imm_i3 c)
    (put_if_ge c ifelse)
    (put_imm_i1 c)
    (put_ireturn c)
    (set_label c ifelse)
    (put_arg0 c)
    (put_imm_i1 c)
    (put_isub c)
    (put_call c fib 4)
    (put_arg0 c)
    (put_imm_i2 c)
    (put_isub c)
    (put_call c fib 4)
    (put_iadd c)
    (put_ireturn c)

    (return (int (jitcall (get_code c) (get n))))
    ))

現在のVMだとfib(36)の計算がCore 2 Duoのマシンで0.98秒程度だったのでかなり速いと思います。
後で多機能にしていく分もっと遅くなる筈ですが、ネイティブJITによる高速化の余地があるので最終的には0.5秒くらいを目指します。

実装についてですが、このJITアセンブラは大部分を自動生成していて100行くらいです。
インストラクション定義にオペランドの型も書いておいて↓

(var vm_instructions `(
    (nop       () @true ())
    (imm_i0    () @true ((vmpush %edx))) ; also used for 'nil'
   ...
    (call (addr byte)  @nil  (
        (asm "movl %ebx, %eax")
        (asm "addl $4, %eax")
        (vmpush %eax) ; store return point
        (vmpush %edi) ; store base pointer
    ...

こんな感じで関数を自動生成しています↓

(var put_func `(
    (byte   . put_byte)
    (short  . put_short)
    (ushort . put_short)
    (int    . put_int)
    (prim   . put_prim)
    (addr   . put_label)
    ))

(var code 0)
(foreach i vm_instructions (do
    (var args `(c))
    (var body `((put_byte c @code)))
    (var name (symbol2s (car i)))
    (var operands (cadr i))
    (var j 0)
    (foreach opd operands (do
        (var arg (tosym (++ `arg j)))
        (push body `(@(assoc opd put_func) c @arg))
        (push args arg)
        (incr j)
        ))
    (= body (reverse body))
    (= args (reverse args))
    (push code-base `(export fun @(tosym (++ `put_ name)) @args @body))
    (incr code)
    ))

あと、JITアセンブラは高速に動いてほしいという関係上、ラベルのアドレス計算はバックパッチによる実装を行います。