summaryrefslogtreecommitdiff
path: root/lib/ruby_vm/mjit/compiler.rb
blob: 75314501c102d6cf3da45af9f9df3d39b9e059a7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
require 'mjit/insn_compiler'
require 'mjit/instruction'
require 'mjit/x86_assembler'

module RubyVM::MJIT
  # Compilation status
  KeepCompiling = :keep_compiling
  CantCompile = :cant_compile
  EndBlock = :end_block

  class Compiler
    # Ruby constants
    Qundef = Fiddle::Qundef

    attr_accessor :write_pos

    # @param mem_block [Integer] JIT buffer address
    def initialize(mem_block)
      @mem_block = mem_block
      @write_pos = 0
      @insn_compiler = InsnCompiler.new
    end

    # @param iseq [RubyVM::MJIT::CPointer::Struct]
    def call(iseq)
      return if iseq.body.location.label == '<main>'
      iseq.body.jit_func = compile_block(iseq)
    rescue Exception => e
      $stderr.puts e.full_message # TODO: check verbose
    end

    def write_addr
      @mem_block + @write_pos
    end

    private

    def compile(asm)
      start_addr = write_addr

      C.mjit_mark_writable
      @write_pos += asm.compile(start_addr)
      C.mjit_mark_executable

      end_addr = write_addr
      if C.mjit_opts.dump_disasm && start_addr < end_addr
        dump_disasm(start_addr, end_addr)
      end
      start_addr
    end

    # ec -> RDI, cfp -> RSI
    def compile_block(iseq)
      addr = write_addr
      asm = X86Assembler.new

      index = 0
      while index < iseq.body.iseq_size
        insn = decode_insn(iseq.body.iseq_encoded[index])
        status = compile_insn(asm, insn)
        if status == EndBlock
          break
        end
        index += insn.len
      end

      compile(asm)
    end

    def compile_insn(asm, insn)
      case insn.name
      when :putnil then @insn_compiler.putnil(asm)
      when :leave  then @insn_compiler.leave(asm)
      else raise NotImplementedError, "insn '#{insn.name}' is not supported yet"
      end
    end

    def decode_insn(encoded)
      INSNS.fetch(C.rb_vm_insn_decode(encoded))
    end

    def dump_disasm(from, to)
      C.dump_disasm(from, to).each do |address, mnemonic, op_str|
        puts "  0x#{"%p" % address}: #{mnemonic} #{op_str}"
      end
    end
  end
end