llvmrubyでRubyを拡張する
yarv2llvmはコンパイルするプログラムを文字列で与えています。これは、Rubyでバイトコード列を得るAPIがRubyVM::InstructionSequence.compileとcompile_fileしかなくどちらもRubyのプログラムを文字列で与えるためです。コンパイルするプログラムを文字列で与えるという仕様は、かなり煩雑です。Emacsでエディットしてもインデントも手動になるし、色も付きません。既存のRubyのメソッドのバイトコード列が得られれば、普通にメソッドを定義しておいて、jit_compile(:foo)とかやってコンパイルさせるって感じのスマートなインターフェースが実現できます。
既存のメソッドのバイトコード列を得るには拡張ライブラリを使う必要がありました。拡張ライブラリを使うと、インストールスクリプトを書くのが面倒になるので避けていました。ところが、今日llvmrubyを使うと、Rubyだけで実現できるんじゃないかなと思い試してみたら、何とかできました。
バイトコード列を得るプログラムを作っていてllvmrubyの可能性を思い知りました。llvmはC以上のことができるので手間さえ掛ければRubyをすべてllvmrubyで置き換えることが可能です。そこまで行かなくてもRubyに関するあらゆる拡張がRuby自身でできることになります。ちょうどLispマシンの中ではすべてのことがLispでできるということと似ているなと思いました。ただ、Lispマシン場合に比べてプログラミングがあまりにも煩雑ですが。
もっとllvmrubyのプログラミングが楽になるようなライブラリの整備が必要だなとも思いました。
# # dynamic_iseq.rb - Get iseq of method dynamically with LLVM # require 'llvm' module VMLib class DynamicInstSeq include LLVM include RubyInternals NODE = Type.struct([Type::Int32Ty, P_CHAR, VALUE, VALUE, VALUE]) P_NODE = Type.pointer(NODE) def initialize @module = LLVM::Module.new('dynamic_iseq') ExecutionEngine.get(@module) # Using Ruby API ftype = Type.function(P_NODE, [VALUE]) mbody = @module.external_function('rb_method_body', ftype) # entry point ftype = Type.function(VALUE, [VALUE]) @iseq_of = @module.get_or_insert_function('iseq_of', ftype) # method body b = @iseq_of.create_block.builder mt = @iseq_of.arguments[0] node = b.call(mbody, mt) body0 = b.struct_gep(node, 3) body1 = b.bit_cast(body0, Type.pointer(P_NODE)) body = b.load(body1) b.return(body) end def iseq_of(met) ExecutionEngine.run_function(@iseq_of, met) end end end if __FILE__ == $0 then def fact(x) if x == 1 then 1 else fact(x - 1) * x end end a = VMLib::DynamicInstSeq.new p a.iseq_of(method(:fact)).to_a end