Rubyによる正規表現コンパイラ(その2)

ようやくできた! 苦しかった・・・。
昨日のruby66版の後ろにくっつけるか別ファイルにしてrequireするように書き換えてください。

次はベンチマークを行います。

llvmrubyではまったところ

  1. 関数の引数で渡した文字列はVALUE型なのでそのままでは、扱えません。rb_string_value_ptrを呼んでchar *へのポインタの中身を得る必要があります。ただし、rb_string_value_ptrの引数はVALUE *です。そのため、いったんallocで領域を確保して、そこに引数で渡った値を入れて、allocした領域をrb_string_value_ptrの引数にする必要があります。
  2. ブロックは互いに独立しています。アセンブラのラベルみたいな感覚でブロックがつながっていると思っているとassertエラーを食らいます。br命令で明示的にブロックにジャンプするようにする必要がある場合があります。
  3. 型が合わないとassertエラーが起きます。型チェックはかなり厳格です。Rubyのプログラムの気分でコーディングしていると泣きます。

追記
Rubyの仕様に合せて、マッチに失敗したときはnilを返すように変更しました。マッチが成功したときはRubyの仕様と異なります。

require 'llvm'

class MatcherLLVM
  include LLVM
  include RubyHelpers

  def initialize
    @module = LLVM::Module.new('regexp')
    ExecutionEngine.get(@module)
    @func = @module.get_or_insert_function("regmatch", 
                                            Type.function(VALUE, [VALUE]))
    eb = @func.create_block
    @main_block = eb.builder

    @rb_string_value_ptr = @module.external_function('rb_string_value_ptr', 
                                                     Type.function(P_CHAR, [P_VALUE]))
    @strlen = @module.external_function('strlen', Type.function(INT, [P_CHAR]))
  end

  def match(str)
    ExecutionEngine.run_function(@func, str)
  end

    
  def compile(stm)
    rstr = @func.arguments[0]
    blocks = {}

    rstrp = @main_block.alloca(VALUE, 4)
    @main_block.store(rstr, rstrp)
    str = @main_block.call(@rb_string_value_ptr, rstrp)
    len = @main_block.call(@strlen, str)

    idxp = @main_block.alloca(INT, 4)
    @main_block.store(-1.llvm(INT), idxp)

    starray = stm.state
    starray.each_with_index do |elest, stn|
      blocks[stn] = @func.create_block
    end
    @main_block.br(blocks[0])

    starray.each_with_index do |elest, stn|
      @main_block.set_insert_point(blocks[stn])

      if stn == starray.size - 1 then
        idx = @main_block.load(idxp)
        @main_block.return(2.llvm) # true
      else
        idx = @main_block.load(idxp)
        idx = @main_block.add(idx, 1.llvm(INT))
        @main_block.store(idx, idxp)
        thenb = @func.create_block
        elseb = @func.create_block

        cmp = @main_block.icmp_sge(idx, len)
        @main_block.cond_br(cmp, thenb, elseb)
        @main_block.set_insert_point(thenb)
        @main_block.return(4.llvm)  # nil
        @main_block.set_insert_point(elseb)

        defstat = elest[256]
        elest.each do |key, val|
          if val != defstat then
            idx = @main_block.load(idxp)
            chp = @main_block.gep(str, idx)
            ch = @main_block.load(chp)
            cmp = @main_block.icmp_eq(ch, key.llvm(CHAR))

            elseb = @func.create_block

            @main_block.cond_br(cmp, blocks[val], elseb)
            @main_block.set_insert_point(elseb)
          end
        end

        if defstat then
          @main_block.br(blocks[defstat])
        else
          @main_block.return(4.llvm) # nil
        end
      end
    end
    @func
  end
end

stm = reexp_comp(".*cc\\*dd*a")

mllvm = MatcherLLVM.new
mllvm.compile(stm)
p mllvm.match("fffcccddddafff")
p mllvm.match("nnncccaaaa")
p mllvm.match("kkkdcc*dddannnn")