yarv2llvmを書くことにしました
レンラダが不完全だけど動き出して気づいたのは、これ手でllvmに直していたら苦労するなってことです。しかも、レンラダのアルゴリズムを替えると苦労はみんな水の泡。
そこで、YARVの出力をllvmにするプログラムを作ろうと思い立ちました。Syoyoさんのpy2llvm(http://code.google.com/p/py2llvm/)の劣化コピーみたいな感じです。
方針はこんな感じです
- RubyのプログラムをRubyVM::InstructionSequence.compileを使ってコンパイルし、YARVのバイトコードを得る
- バイトコードを一部トレースしてllvmを得る。llvmを生成する処はllvmrubyを使用する
- 型推論はサボりまくり、プログラマには必要なだけ型指定を要求する
llvmrubyのruby_vm.rbと違い、YARVをスタックマシンとしてコンパイルするのではなく、トレースしてレジスタマシン(SSAマシン)としてコンパイルします。
とりあえず、こんなテストをしてみました。getlocal, setlocal, 四則演算をトラバースしてもとのRubyの式を再生するというものです。instruction.rbは長くなるので、githubに置きました(http://github.com/miura1729/yarv2llvm/tree/master/lib/instruction.rb)
require 'instruction' include VMLib is = RubyVM::InstructionSequence.compile(File.read(ARGV[0])).to_a iseq = InstSeqTree.new(nil, is) stack = [] iseq.traverse_code([nil, nil, nil]) do |code, info, header| code.lblock_list.each do |ln| p ln local = ["", :self] + code.header['locals'].reverse p local code.lblock[ln].each do |ins| nm = ins[0] p1 = ins[1] case nm when :getlocal stack.push local[p1] when :setlocal src = stack.pop p "#{local[p1]} = #{src}" when :opt_plus s1 = stack.pop s2 = stack.pop stack.push "#{s1} + #{s2}" when :opt_minus s1 = stack.pop s2 = stack.pop stack.push "#{s1} - #{s2}" when :opt_mult s1 = stack.pop s2 = stack.pop stack.push "#{s1} * #{s2}" when :opt_div s1 = stack.pop s2 = stack.pop stack.push "#{s1} / #{s2}" end end end end
こんなプログラムを食わせると、
class Foo def foo(sx, sy, sz) vx = sx vy = sy vz = sz vs = Math.sqrt(vx * vx + vy * vy + vz * vz) vx = vx / vs vy = vy / vs vz = vz / vs end end
こんな出力になります。
nil ["", :self] nil ["", :self] nil ["", :self, :vs, :vz, :vy, :vx, :sz, :sy, :sx] "vx = sx" "vy = sy" "vz = sz" :label_22 ["", :self, :vs, :vz, :vy, :vx, :sz, :sy, :sx] :label_29 ["", :self, :vs, :vz, :vy, :vx, :sz, :sy, :sx] "vs = vz * vz + vy * vy + vx * vx" "vx = vs / vx" "vy = vs / vy" "vz = vs / vz"