x86アセンブラDSLを作ってみるテスト

ずーっと前にStringでバイナリを作ってDLで呼び出すコードを書いたことあったけども、x86アセンブラの文法はわりとそのままRubyで解釈できるんじゃね?って思ってDSLを作ってみるテスト。Rubyも新しくなったからfiddleを使う。

require 'fiddle'

module X86ASM
  class Register32;end
  class EAX < Register32;end

  class Assembler
    attr_reader :buf

    def initialize
      @buf = ""
    end
  
    def eax
      EAX.new
    end
  
    def mov(dst, src)
      if Register32 === dst
        if Integer === src
          @buf += "\xb8".b + [src].pack("V").b
        end
      end
    end
  
    def ret
      @buf += "\xc3".b
    end
  end

  def self.asm(&b)
    tmp = Assembler.new
    tmp.instance_eval &b
    Fiddle::Function.new([tmp.buf].pack("P").unpack("l!")[0], [], Fiddle::TYPE_INT)
  end
end

hoge = X86ASM.asm do
  mov eax, 510
  ret
end
p hoge.call #=> 510

movやeaxはメソッドとして定義して、eaxはEAXクラスのインスタンスを返す。今はb8固定で生成してるけども、レジスタオブジェクトの種類により値を変えれば好きなレジスタに格納することができるだろうし、例えばmov eax, [ecx]とかなら第2引数が配列になるからそれで判定できるし、+とか付く場合はレジスタオブジェクトに+メソッドを定義するとかRefinementsでArrayの定義を上書きしてやるとかで結構それっぽくできるのではなかろうか。
難点はラベルで、LABEL:という構文はエラーになるから書けなくて、せいぜいlabel :LABELと書くぐらいが関の山か。