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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
require 'mjit/codegen'
require 'mjit/context'
require 'mjit/instruction'
require 'mjit/x86_assembler'
module RubyVM::MJIT
# Compilation status
KeepCompiling = :KeepCompiling
CantCompile = :CantCompile
EndBlock = :EndBlock
# Ruby constants
Qnil = Fiddle::Qnil
Qundef = Fiddle::Qundef
class Compiler
attr_accessor :write_pos
# @param mem_block [Integer] JIT buffer address
def initialize(mem_block)
@mem_block = mem_block
@write_pos = 0
@codegen = Codegen.new
end
# @param iseq [RubyVM::MJIT::CPointer::Struct]
def call(iseq)
return if iseq.body.location.label == '<main>'
asm = X86Assembler.new
compile_prologue(asm)
compile_block(asm, iseq)
iseq.body.jit_func = compile(asm)
rescue Exception => e
$stderr.puts e.full_message # TODO: check verbose
end
def write_addr
@mem_block + @write_pos
end
private
# @param asm [RubyVM::MJIT::X86Assembler]
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
#
# Callee-saved: rbx, rsp, rbp, r12, r13, r14, r15
# Caller-saved: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11
#
# @param asm [RubyVM::MJIT::X86Assembler]
def compile_prologue(asm)
# Save callee-saved registers used by JITed code
asm.push(:rbx)
# Load sp to a register
asm.mov(:rbx, [:rsi, C.rb_control_frame_t.offsetof(:sp)]) # rbx = cfp->sp
end
# @param asm [RubyVM::MJIT::X86Assembler]
def compile_block(asm, iseq)
ctx = Context.new
index = 0
while index < iseq.body.iseq_size
insn = decode_insn(iseq.body.iseq_encoded[index])
case compile_insn(ctx, asm, insn)
when EndBlock
break
when CantCompile
compile_exit(ctx, asm, (iseq.body.iseq_encoded + index).to_i)
break
end
index += insn.len
end
end
# @param ctx [RubyVM::MJIT::Context]
# @param asm [RubyVM::MJIT::X86Assembler]
def compile_insn(ctx, asm, insn)
case insn.name
when :putnil then @codegen.putnil(ctx, asm)
when :leave then @codegen.leave(ctx, asm)
else CantCompile
end
end
# @param ctx [RubyVM::MJIT::Context]
# @param asm [RubyVM::MJIT::X86Assembler]
def compile_exit(ctx, asm, exit_pc)
# update pc
asm.mov(:rax, exit_pc) # rax = exit_pc
asm.mov([:rsi, C.rb_control_frame_t.offsetof(:pc)], :rax) # cfp->pc = rax
# update sp
if ctx.stack_size > 0
asm.add(:rbx, C.VALUE.size * ctx.stack_size) # rbx += stack_size
asm.mov([:rsi, C.rb_control_frame_t.offsetof(:sp)], :rbx) # cfp->sp = rbx
end
# Restore callee-saved registers
asm.pop(:rbx)
asm.mov(:rax, Qundef)
asm.ret
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
puts
end
end
end
|