JITアセンブラを作った
とはいってもx86の機械語を生成するのではなく、VMのバイトコードを生成するやつを作りました。x86のJITアセンブラは次の世代で作る予定です。
この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) ))