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