summaryrefslogtreecommitdiff
path: root/lib/ruby_vm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ruby_vm')
-rw-r--r--lib/ruby_vm/mjit/assembler.rb69
-rw-r--r--lib/ruby_vm/mjit/block_stub.rb7
-rw-r--r--lib/ruby_vm/mjit/code_block.rb9
-rw-r--r--lib/ruby_vm/mjit/compiler.rb51
-rw-r--r--lib/ruby_vm/mjit/context.rb9
-rw-r--r--lib/ruby_vm/mjit/exit_compiler.rb61
-rw-r--r--lib/ruby_vm/mjit/insn_compiler.rb42
-rw-r--r--lib/ruby_vm/mjit/jit_state.rb8
8 files changed, 207 insertions, 49 deletions
diff --git a/lib/ruby_vm/mjit/assembler.rb b/lib/ruby_vm/mjit/assembler.rb
index bfe716e554..bd2da22d69 100644
--- a/lib/ruby_vm/mjit/assembler.rb
+++ b/lib/ruby_vm/mjit/assembler.rb
@@ -14,9 +14,7 @@ module RubyVM::MJIT
Rel32Pad = Object.new
# A set of ModR/M values encoded on #insn
- class ModRM < Data.define(:mod, :reg, :rm)
- def initialize(mod:, reg: nil, rm: nil) = super
- end
+ class ModRM < Data.define(:mod, :reg, :rm); end
Mod00 = 0b00 # Mod 00: [reg]
Mod01 = 0b01 # Mod 01: [reg]+disp8
Mod10 = 0b10 # Mod 10: [reg]+disp16
@@ -33,9 +31,11 @@ module RubyVM::MJIT
@labels = {}
@label_id = 0
@comments = Hash.new { |h, k| h[k] = [] }
+ @stubs = Hash.new { |h, k| h[k] = [] }
end
def assemble(addr)
+ set_stub_addrs(addr)
resolve_rel32(addr)
resolve_labels
@@ -67,7 +67,7 @@ module RubyVM::MJIT
insn(
prefix: REX_W,
opcode: 0x83,
- mod_rm: ModRM[mod: Mod11, rm: dst_reg],
+ mod_rm: ModRM[mod: Mod11, reg: 0, rm: dst_reg],
imm: imm8(src_imm),
)
# ADD r/m64, imm8 (Mod 00: [reg])
@@ -77,7 +77,7 @@ module RubyVM::MJIT
insn(
prefix: REX_W,
opcode: 0x83,
- mod_rm: ModRM[mod: Mod00, rm: dst_reg],
+ mod_rm: ModRM[mod: Mod00, reg: 0, rm: dst_reg],
imm: imm8(src_imm),
)
else
@@ -85,12 +85,34 @@ module RubyVM::MJIT
end
end
+ # @param addr [Integer]
+ def call(addr)
+ # CALL rel32
+ # E8 cd
+ insn(opcode: 0xe8, imm: rel32(addr))
+ end
+
+ def jmp(dst)
+ case dst
+ # JMP rel32
+ in Integer => dst_addr
+ # E9 cd
+ insn(opcode: 0xe9, imm: rel32(dst_addr))
+ # JMP r/m64 (Mod 11: reg)
+ in Symbol => dst_reg
+ # FF /4
+ insn(opcode: 0xff, mod_rm: ModRM[mod: Mod11, reg: 4, rm: dst_reg])
+ else
+ raise NotImplementedError, "jmp: not-implemented operands: #{dst.inspect}"
+ end
+ end
+
def jnz(dst)
case dst
# JNZ rel32
- in Integer => addr
+ in Integer => dst_addr
# 0F 85 cd
- insn(opcode: [0x0f, 0x85], imm: rel32(addr))
+ insn(opcode: [0x0f, 0x85], imm: rel32(dst_addr))
else
raise NotImplementedError, "jnz: not-implemented operands: #{dst.inspect}"
end
@@ -99,9 +121,9 @@ module RubyVM::MJIT
def jz(dst)
case dst
# JZ rel8
- in Label => label
+ in Label => dst_label
# 74 cb
- insn(opcode: 0x74, imm: label)
+ insn(opcode: 0x74, imm: dst_label)
else
raise NotImplementedError, "jz: not-implemented operands: #{dst.inspect}"
end
@@ -155,7 +177,7 @@ module RubyVM::MJIT
insn(
prefix: REX_W,
opcode: 0xc7,
- mod_rm: ModRM[mod: Mod11, rm: dst_reg],
+ mod_rm: ModRM[mod: Mod11, reg: 0, rm: dst_reg],
imm: imm32(src_imm),
)
# MOV r64, imm64
@@ -180,7 +202,7 @@ module RubyVM::MJIT
insn(
prefix: REX_W,
opcode: 0xc7,
- mod_rm: ModRM[mod: Mod00, rm: dst_reg],
+ mod_rm: ModRM[mod: Mod00, reg: 0, rm: dst_reg],
imm: imm32(src_imm),
)
# MOV r/m64, r64 (Mod 00: [reg])
@@ -207,7 +229,7 @@ module RubyVM::MJIT
insn(
prefix: REX_W,
opcode: 0xc7,
- mod_rm: ModRM[mod: Mod01, rm: dst_reg],
+ mod_rm: ModRM[mod: Mod01, reg: 0, rm: dst_reg],
disp: dst_disp,
imm: imm32(src_imm),
)
@@ -284,6 +306,10 @@ module RubyVM::MJIT
@comments[@bytes.size] << message
end
+ def stub(stub)
+ @stubs[@bytes.size] << stub
+ end
+
def new_label(name)
Label.new(id: @label_id += 1, name:)
end
@@ -314,8 +340,8 @@ module RubyVM::MJIT
opcode += reg_code(rd)
end
if mod_rm
- prefix |= REX_R if mod_rm.reg && extended_reg?(mod_rm.reg)
- prefix |= REX_B if mod_rm.rm && extended_reg?(mod_rm.rm)
+ prefix |= REX_R if mod_rm.reg.is_a?(Symbol) && extended_reg?(mod_rm.reg)
+ prefix |= REX_B if mod_rm.rm.is_a?(Symbol) && extended_reg?(mod_rm.rm)
end
# Encode insn
@@ -326,8 +352,8 @@ module RubyVM::MJIT
if mod_rm
mod_rm_byte = encode_mod_rm(
mod: mod_rm.mod,
- reg: mod_rm.reg ? reg_code(mod_rm.reg) : 0,
- rm: mod_rm.rm ? reg_code(mod_rm.rm) : 0,
+ reg: mod_rm.reg.is_a?(Symbol) ? reg_code(mod_rm.reg) : mod_rm.reg,
+ rm: mod_rm.rm.is_a?(Symbol) ? reg_code(mod_rm.rm) : mod_rm.rm,
)
@bytes.push(mod_rm_byte)
end
@@ -413,7 +439,7 @@ module RubyVM::MJIT
unless imm32?(imm)
raise ArgumentError, "unexpected imm32: #{imm}"
end
- imm_bytes(imm, 4)
+ [imm].pack('l').unpack('c*')
end
# io: 8 bytes
@@ -465,6 +491,15 @@ module RubyVM::MJIT
[Rel32.new(addr), Rel32Pad, Rel32Pad, Rel32Pad]
end
+ def set_stub_addrs(write_addr)
+ @bytes.each_with_index do |byte, index|
+ @stubs.fetch(index, []).each do |stub|
+ stub.addr = write_addr + index
+ stub.freeze
+ end
+ end
+ end
+
def resolve_rel32(write_addr)
@bytes.each_with_index do |byte, index|
if byte.is_a?(Rel32)
diff --git a/lib/ruby_vm/mjit/block_stub.rb b/lib/ruby_vm/mjit/block_stub.rb
new file mode 100644
index 0000000000..c6908d09ca
--- /dev/null
+++ b/lib/ruby_vm/mjit/block_stub.rb
@@ -0,0 +1,7 @@
+class RubyVM::MJIT::BlockStub < Struct.new(
+ :iseq, # @param [RubyVM::MJIT::CPointer::Struct_rb_iseq_struct] Jump target ISEQ
+ :pc, # @param [Integer] Jump target pc
+ :ctx, # @param [RubyVM::MJIT::Context] Jump target context
+ :addr, # @param [Integer] Jump source address to be re-generated
+)
+end
diff --git a/lib/ruby_vm/mjit/code_block.rb b/lib/ruby_vm/mjit/code_block.rb
index b4cc3d721c..ad91f53404 100644
--- a/lib/ruby_vm/mjit/code_block.rb
+++ b/lib/ruby_vm/mjit/code_block.rb
@@ -37,6 +37,15 @@ module RubyVM::MJIT
start_addr
end
+ def with_addr(addr)
+ old_write_pos = @write_pos
+ @write_pos = addr - @mem_block
+ @comments.delete(addr) # TODO: clean up old comments for all overwritten insns?
+ yield
+ ensure
+ @write_pos = old_write_pos
+ end
+
private
def write_addr
diff --git a/lib/ruby_vm/mjit/compiler.rb b/lib/ruby_vm/mjit/compiler.rb
index ac094c7007..8491ac68c6 100644
--- a/lib/ruby_vm/mjit/compiler.rb
+++ b/lib/ruby_vm/mjit/compiler.rb
@@ -1,4 +1,5 @@
require 'ruby_vm/mjit/assembler'
+require 'ruby_vm/mjit/block_stub'
require 'ruby_vm/mjit/code_block'
require 'ruby_vm/mjit/context'
require 'ruby_vm/mjit/exit_compiler'
@@ -38,31 +39,56 @@ module RubyVM::MJIT
@insn_compiler = InsnCompiler.new(@ocb)
end
- # @param iseq [RubyVM::MJIT::CPointer::Struct]
- def compile(iseq)
+ # Compile an ISEQ from its entry point.
+ # @param iseq `RubyVM::MJIT::CPointer::Struct_rb_iseq_t`
+ # @param cfp `RubyVM::MJIT::CPointer::Struct_rb_control_frame_t`
+ def compile(iseq, cfp)
# TODO: Support has_opt
return if iseq.body.param.flags.has_opt
asm = Assembler.new
- asm.comment("Block: #{iseq.body.location.label}@#{pathobj_path(iseq.body.location.pathobj)}:#{iseq.body.location.first_lineno}")
+ asm.comment("Block: #{iseq.body.location.label}@#{C.rb_iseq_path(iseq)}:#{iseq.body.location.first_lineno}")
compile_prologue(asm)
- compile_block(asm, iseq)
+ compile_block(asm, jit: JITState.new(iseq:, cfp:))
iseq.body.jit_func = @cb.write(asm)
rescue Exception => e
$stderr.puts e.full_message # TODO: check verbose
end
+ # Continue compilation from a stub.
+ # @param stub [RubyVM::MJIT::BlockStub]
+ # @param cfp `RubyVM::MJIT::CPointer::Struct_rb_control_frame_t`
+ # @return [Integer] The starting address of a compiled stub
+ def stub_hit(stub, cfp)
+ # Update cfp->pc for `jit.at_current_insn?`
+ cfp.pc = stub.pc
+
+ # Compile the jump target
+ new_addr = Assembler.new.then do |asm|
+ jit = JITState.new(iseq: stub.iseq, cfp:)
+ index = (stub.pc - stub.iseq.body.iseq_encoded.to_i) / C.VALUE.size
+ compile_block(asm, jit:, index:, ctx: stub.ctx)
+ @cb.write(asm)
+ end
+
+ # Re-generate the jump source
+ @cb.with_addr(stub.addr) do
+ asm = Assembler.new
+ asm.comment('regenerate block stub')
+ asm.jmp(new_addr)
+ @cb.write(asm)
+ end
+ new_addr
+ end
+
private
- # 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::Assembler]
def compile_prologue(asm)
- asm.comment("MJIT entry")
+ asm.comment('MJIT entry')
# Save callee-saved registers used by JITed code
asm.push(CFP)
@@ -78,11 +104,8 @@ module RubyVM::MJIT
end
# @param asm [RubyVM::MJIT::Assembler]
- def compile_block(asm, iseq)
- jit = JITState.new
- ctx = Context.new
-
- index = 0
+ def compile_block(asm, jit:, index: 0, ctx: Context.new)
+ iseq = jit.iseq
while index < iseq.body.iseq_size
insn = self.class.decode_insn(iseq.body.iseq_encoded[index])
jit.pc = (iseq.body.iseq_encoded + index).to_i
@@ -181,7 +204,7 @@ module RubyVM::MJIT
# opt_mod
# opt_eq
# opt_neq
- # opt_lt
+ when :opt_lt then @insn_compiler.opt_lt(jit, ctx, asm)
# opt_le
# opt_gt
# opt_ge
diff --git a/lib/ruby_vm/mjit/context.rb b/lib/ruby_vm/mjit/context.rb
index 2bc499cd4e..8433cc5f95 100644
--- a/lib/ruby_vm/mjit/context.rb
+++ b/lib/ruby_vm/mjit/context.rb
@@ -1,8 +1,15 @@
class RubyVM::MJIT::Context < Struct.new(
- :stack_size, # @param [Integer]
+ :stack_size, # @param [Integer] The number of values on the stack
+ :sp_offset, # @param [Integer] JIT sp offset relative to the interpreter's sp
)
def initialize(*)
super
self.stack_size ||= 0
+ self.sp_offset ||= 0
+ end
+
+ def stack_push(size)
+ self.stack_size += size
+ self.sp_offset += size
end
end
diff --git a/lib/ruby_vm/mjit/exit_compiler.rb b/lib/ruby_vm/mjit/exit_compiler.rb
index c9f2190931..bec2eab514 100644
--- a/lib/ruby_vm/mjit/exit_compiler.rb
+++ b/lib/ruby_vm/mjit/exit_compiler.rb
@@ -1,6 +1,9 @@
module RubyVM::MJIT
class ExitCompiler
- def initialize = freeze
+ def initialize
+ # TODO: Use GC offsets
+ @gc_refs = []
+ end
# @param jit [RubyVM::MJIT::JITState]
# @param ctx [RubyVM::MJIT::Context]
@@ -12,19 +15,12 @@ module RubyVM::MJIT
asm.mov(:rax, (C.mjit_insn_exits + insn.bin).to_i)
asm.add([:rax], 1) # TODO: lock
end
- asm.comment("exit to interpreter")
- # Update pc
- asm.mov(:rax, jit.pc) # rax = jit.pc
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax) # cfp->pc = rax
-
- # Update sp
- if ctx.stack_size > 0
- asm.add(SP, C.VALUE.size * ctx.stack_size) # rbx += stack_size
- asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP) # cfp->sp = rbx
- end
+ # Fix pc/sp offsets for the interpreter
+ save_pc_and_sp(jit, ctx, asm)
# Restore callee-saved registers
+ asm.comment('exit to interpreter')
asm.pop(SP)
asm.pop(EC)
asm.pop(CFP)
@@ -32,5 +28,48 @@ module RubyVM::MJIT
asm.mov(:rax, Qundef)
asm.ret
end
+
+ # @param jit [RubyVM::MJIT::JITState]
+ # @param asm [RubyVM::MJIT::Assembler]
+ # @param stub [RubyVM::MJIT::BlockStub]
+ def compile_jump_stub(jit, asm, stub)
+ case stub
+ when BlockStub
+ asm.comment("block stub hit: #{stub.iseq.body.location.label}@#{C.rb_iseq_path(stub.iseq)}:#{stub.iseq.body.location.first_lineno}")
+ else
+ raise "unexpected stub object: #{stub.inspect}"
+ end
+
+ # Call rb_mjit_stub_hit
+ asm.mov(:rdi, to_value(stub))
+ asm.call(C.rb_mjit_stub_hit)
+
+ # Jump to the address returned by rb_mjit_stub_hit
+ asm.jmp(:rax)
+ end
+
+ private
+
+ # @param jit [RubyVM::MJIT::JITState]
+ # @param ctx [RubyVM::MJIT::Context]
+ # @param asm [RubyVM::MJIT::Assembler]
+ def save_pc_and_sp(jit, ctx, asm)
+ # Update pc
+ asm.comment("save pc #{'and sp' if ctx.sp_offset != 0}")
+ asm.mov(:rax, jit.pc) # rax = jit.pc
+ asm.mov([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax) # cfp->pc = rax
+
+ # Update sp
+ if ctx.sp_offset != 0
+ asm.add(SP, C.VALUE.size * ctx.sp_offset) # sp += stack_size
+ asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP) # cfp->sp = sp
+ ctx.sp_offset = 0
+ end
+ end
+
+ def to_value(obj)
+ @gc_refs << obj
+ C.to_value(obj)
+ end
end
end
diff --git a/lib/ruby_vm/mjit/insn_compiler.rb b/lib/ruby_vm/mjit/insn_compiler.rb
index 96bab0a225..0d40cb6f50 100644
--- a/lib/ruby_vm/mjit/insn_compiler.rb
+++ b/lib/ruby_vm/mjit/insn_compiler.rb
@@ -1,7 +1,7 @@
module RubyVM::MJIT
# scratch regs: rax
#
- # 4/101
+ # 5/101
class InsnCompiler
# @param ocb [CodeBlock]
def initialize(ocb)
@@ -33,7 +33,7 @@ module RubyVM::MJIT
# @param asm [RubyVM::MJIT::Assembler]
def putnil(jit, ctx, asm)
asm.mov([SP, C.VALUE.size * ctx.stack_size], Qnil)
- ctx.stack_size += 1
+ ctx.stack_push(1)
KeepCompiling
end
@@ -55,7 +55,7 @@ module RubyVM::MJIT
asm.mov([SP, C.VALUE.size * ctx.stack_size], :rax)
end
- ctx.stack_size += 1
+ ctx.stack_push(1)
KeepCompiling
end
@@ -141,7 +141,18 @@ module RubyVM::MJIT
# opt_mod
# opt_eq
# opt_neq
- # opt_lt
+
+ # @param jit [RubyVM::MJIT::JITState]
+ # @param ctx [RubyVM::MJIT::Context]
+ # @param asm [RubyVM::MJIT::Assembler]
+ def opt_lt(jit, ctx, asm)
+ unless jit.at_current_insn?
+ defer_compilation(jit, ctx, asm)
+ return EndBlock
+ end
+ CantCompile
+ end
+
# opt_le
# opt_gt
# opt_ge
@@ -178,7 +189,7 @@ module RubyVM::MJIT
# Push it to the stack
asm.mov([SP, C.VALUE.size * ctx.stack_size], :rax)
- ctx.stack_size += 1
+ ctx.stack_push(1)
KeepCompiling
end
@@ -198,6 +209,27 @@ module RubyVM::MJIT
# @param jit [RubyVM::MJIT::JITState]
# @param ctx [RubyVM::MJIT::Context]
+ # @param asm [RubyVM::MJIT::Assembler]
+ def defer_compilation(jit, ctx, asm)
+ # Make a stub to compile the current insn
+ block_stub = BlockStub.new(
+ iseq: jit.iseq,
+ pc: jit.pc,
+ ctx: ctx.dup,
+ )
+
+ stub_hit = Assembler.new.then do |ocb_asm|
+ @exit_compiler.compile_jump_stub(jit, ocb_asm, block_stub)
+ @ocb.write(ocb_asm)
+ end
+
+ asm.comment('defer_compilation: block stub')
+ asm.stub(block_stub)
+ asm.jmp(stub_hit)
+ end
+
+ # @param jit [RubyVM::MJIT::JITState]
+ # @param ctx [RubyVM::MJIT::Context]
def compile_side_exit(jit, ctx)
asm = Assembler.new
@exit_compiler.compile_exit(jit, ctx, asm)
diff --git a/lib/ruby_vm/mjit/jit_state.rb b/lib/ruby_vm/mjit/jit_state.rb
index 0a35a516ca..1a3e78b3da 100644
--- a/lib/ruby_vm/mjit/jit_state.rb
+++ b/lib/ruby_vm/mjit/jit_state.rb
@@ -1,9 +1,15 @@
module RubyVM::MJIT
class JITState < Struct.new(
- :pc, # @param [Integer]
+ :iseq,
+ :pc, # @param [Integer] The JIT target PC
+ :cfp, # @param `RubyVM::MJIT::CPointer::Struct_rb_control_frame_t` The JIT source CFP (before MJIT is called)
)
def operand(index)
C.VALUE.new(pc)[index + 1]
end
+
+ def at_current_insn?
+ pc == cfp.pc.to_i
+ end
end
end