Initial implementation of send

This commit is contained in:
Takashi Kokubun 2023-01-07 21:24:30 -08:00
Родитель d09c723975
Коммит fa0b9c1c97
8 изменённых файлов: 354 добавлений и 67 удалений

Просмотреть файл

@ -136,6 +136,10 @@ module RubyVM::MJIT
in Integer => dst_addr
# E9 cd
insn(opcode: 0xe9, imm: rel32(dst_addr))
# JMP r/m64 (Mod 01: [reg]+disp8)
in [Symbol => dst_reg, Integer => dst_disp] if imm8?(dst_disp)
# FF /4
insn(opcode: 0xff, mod_rm: ModRM[mod: Mod01, reg: 4, rm: dst_reg], disp: dst_disp)
# JMP r/m64 (Mod 11: reg)
in Symbol => dst_reg
# FF /4
@ -145,6 +149,17 @@ module RubyVM::MJIT
end
end
def jne(dst)
case dst
# JNE rel32
in Integer => dst_addr
# 0F 85 cd
insn(opcode: [0x0f, 0x85], imm: rel32(dst_addr))
else
raise NotImplementedError, "jne: not-implemented operands: #{dst.inspect}"
end
end
def jnz(dst)
case dst
# JNZ rel32
@ -182,6 +197,23 @@ module RubyVM::MJIT
end
end
def lea(dst, src)
case [dst, src]
# LEA r64,m (Mod 01: [reg]+disp8)
in [Symbol => dst_reg, [Symbol => src_reg, Integer => src_disp]] if r64?(dst_reg) && r64?(src_reg) && imm8?(src_disp)
# REX.W + 8D /r
# RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: 0x8d,
mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
disp: src_disp,
)
else
raise NotImplementedError, "lea: not-implemented operands: #{dst.inspect}, #{src.inspect}"
end
end
def mov(dst, src)
case dst
in Symbol => dst_reg

Просмотреть файл

@ -1,8 +1,9 @@
class RubyVM::MJIT::BlockStub < Struct.new(
:iseq, # @param [RubyVM::MJIT::CPointer::Struct_rb_iseq_struct] Stub target ISEQ
:ctx, # @param [RubyVM::MJIT::Context] Stub target context
:pc, # @param [Integer] Stub target pc
:start_addr, # @param [Integer] Stub source start address to be re-generated
:end_addr, # @param [Integer] Stub source end address to be re-generated
:iseq, # @param [RubyVM::MJIT::CPointer::Struct_rb_iseq_struct] Stub target ISEQ
:ctx, # @param [RubyVM::MJIT::Context] Stub target context
:pc, # @param [Integer] Stub target pc
:start_addr, # @param [Integer] Stub source start address to be re-generated
:end_addr, # @param [Integer] Stub source end address to be re-generated
:change_block, # @param [Proc] Recompile the source address with a new block address
)
end

Просмотреть файл

@ -44,6 +44,11 @@ module RubyVM::MJIT
@ocb = CodeBlock.new(mem_block: mem_block + mem_size / 2, mem_size: mem_size / 2, outlined: true)
@exit_compiler = ExitCompiler.new
@insn_compiler = InsnCompiler.new(@ocb, @exit_compiler)
@leave_exit = Assembler.new.then do |asm|
@exit_compiler.compile_leave_exit(asm)
@ocb.write(asm)
end
end
# Compile an ISEQ from its entry point.
@ -86,10 +91,10 @@ module RubyVM::MJIT
new_addr = @cb.write(new_asm)
@cb.with_write_addr(block_stub.start_addr) do
asm = Assembler.new
asm.comment('regenerate block stub')
asm.jmp(new_addr)
block_stub.change_block.call(asm, new_addr)
@cb.write(asm)
end
new_addr
end
end
@ -181,6 +186,10 @@ module RubyVM::MJIT
# Load sp to a dedicated register
asm.mov(SP, [CFP, C.rb_control_frame_t.offsetof(:sp)]) # rbx = cfp->sp
# Setup cfp->jit_return
asm.mov(:rax, @leave_exit)
asm.mov([CFP, C.rb_control_frame_t.offsetof(:jit_return)], :rax)
end
# @param asm [RubyVM::MJIT::Assembler]

Просмотреть файл

@ -24,6 +24,17 @@ module RubyVM::MJIT
asm.ret
end
# @param ocb [CodeBlock]
def compile_leave_exit(asm)
# Restore callee-saved registers
asm.pop(SP)
asm.pop(EC)
asm.pop(CFP)
# :rax is written by #leave
asm.ret
end
# @param jit [RubyVM::MJIT::JITState]
# @param ctx [RubyVM::MJIT::Context]
# @param asm [RubyVM::MJIT::Assembler]
@ -44,11 +55,10 @@ module RubyVM::MJIT
asm.ret
end
# @param jit [RubyVM::MJIT::JITState]
# @param ctx [RubyVM::MJIT::Context]
# @param asm [RubyVM::MJIT::Assembler]
# @param block_stub [RubyVM::MJIT::BlockStub]
def compile_block_stub(jit, ctx, asm, block_stub)
def compile_block_stub(ctx, asm, block_stub)
# Call rb_mjit_block_stub_hit
asm.comment("block stub hit: #{block_stub.iseq.body.location.label}@#{C.rb_iseq_path(block_stub.iseq)}:#{iseq_lineno(block_stub.iseq, block_stub.pc)}")
asm.mov(:rdi, to_value(block_stub))
@ -98,7 +108,7 @@ module RubyVM::MJIT
# @param asm [RubyVM::MJIT::Assembler]
def save_pc_and_sp(jit, ctx, asm)
# Update pc (TODO: manage PC offset?)
asm.comment("save pc #{'and sp' if ctx.sp_offset != 0}")
asm.comment("save PC#{' and SP' if ctx.sp_offset != 0} to CFP")
asm.mov(:rax, jit.pc) # rax = jit.pc
asm.mov([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax) # cfp->pc = rax

Просмотреть файл

@ -6,7 +6,7 @@ module RubyVM::MJIT
@ocb = ocb
@exit_compiler = exit_compiler
@invariants = Invariants.new(ocb, exit_compiler)
freeze
# freeze # workaround a binding.irb issue. TODO: resurrect this
end
# @param jit [RubyVM::MJIT::JITState]
@ -17,7 +17,7 @@ module RubyVM::MJIT
asm.incr_counter(:mjit_insns_count)
asm.comment("Insn: #{insn.name}")
# 10/101
# 11/101
case insn.name
# nop
# getlocal
@ -70,7 +70,7 @@ module RubyVM::MJIT
# definemethod
# definesmethod
# send
# opt_send_without_block
when :opt_send_without_block then opt_send_without_block(jit, ctx, asm)
# objtostring
# opt_str_freeze
# opt_nil_p
@ -217,7 +217,16 @@ module RubyVM::MJIT
# definemethod
# definesmethod
# send
# opt_send_without_block
# @param jit [RubyVM::MJIT::JITState]
# @param ctx [RubyVM::MJIT::Context]
# @param asm [RubyVM::MJIT::Assembler]
# @param cd `RubyVM::MJIT::CPointer::Struct_rb_call_data`
def opt_send_without_block(jit, ctx, asm)
cd = C.rb_call_data.new(jit.operand(0))
compile_send_general(jit, ctx, asm, cd)
end
# objtostring
# opt_str_freeze
# opt_nil_p
@ -233,24 +242,23 @@ module RubyVM::MJIT
def leave(jit, ctx, asm)
assert_eq!(ctx.stack_size, 1)
asm.comment('RUBY_VM_CHECK_INTS(ec)')
asm.mov(:eax, [EC, C.rb_execution_context_t.offsetof(:interrupt_flag)])
asm.test(:eax, :eax)
asm.jnz(side_exit(jit, ctx))
compile_check_ints(jit, ctx, asm)
asm.comment('pop stack frame')
asm.add(CFP, C.rb_control_frame_t.size) # cfp = cfp + 1
asm.mov([EC, C.rb_execution_context_t.offsetof(:cfp)], CFP) # ec->cfp = cfp
asm.lea(:rax, [CFP, C.rb_control_frame_t.size])
asm.mov(CFP, :rax)
asm.mov([EC, C.rb_execution_context_t.offsetof(:cfp)], :rax)
# Return a value
# Return a value (for compile_leave_exit)
asm.mov(:rax, [SP])
# Restore callee-saved registers
asm.pop(SP)
asm.pop(EC)
asm.pop(CFP)
# Set caller's SP and push a value to its stack (for JIT)
asm.mov(SP, [CFP, C.rb_control_frame_t.offsetof(:sp)])
asm.mov([SP], :rax)
# Jump to cfp->jit_return
asm.jmp([CFP, -C.rb_control_frame_t.size + C.rb_control_frame_t.offsetof(:jit_return)])
asm.ret
EndBlock
end
@ -471,6 +479,161 @@ module RubyVM::MJIT
# Helpers
#
# @param jit [RubyVM::MJIT::JITState]
# @param ctx [RubyVM::MJIT::Context]
# @param asm [RubyVM::MJIT::Assembler]
def compile_check_ints(jit, ctx, asm)
asm.comment('RUBY_VM_CHECK_INTS(ec)')
asm.mov(:eax, [EC, C.rb_execution_context_t.offsetof(:interrupt_flag)])
asm.test(:eax, :eax)
asm.jnz(side_exit(jit, ctx))
end
# @param jit [RubyVM::MJIT::JITState]
# @param ctx [RubyVM::MJIT::Context]
# @param asm [RubyVM::MJIT::Assembler]
# @param cd `RubyVM::MJIT::CPointer::Struct_rb_call_data`
def compile_send_general(jit, ctx, asm, cd)
ci = cd.ci
argc = C.vm_ci_argc(ci)
mid = C.vm_ci_mid(ci)
flags = C.vm_ci_flag(ci)
if flags & C.VM_CALL_KW_SPLAT != 0
return CantCompile
end
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
raise 'sp_offset != stack_size' if ctx.sp_offset != ctx.stack_size # TODO: handle this
recv_depth = argc + ((flags & C.VM_CALL_ARGS_BLOCKARG == 0) ? 0 : 1)
recv_index = ctx.stack_size - 1 - recv_depth
comptime_recv = jit.peek_at_stack(recv_depth)
comptime_recv_klass = C.rb_class_of(comptime_recv)
# Guard known class
if comptime_recv_klass.singleton_class?
asm.comment('guard known object with singleton class')
asm.mov(:rax, C.to_value(comptime_recv))
asm.cmp([SP, C.VALUE.size * recv_index], :rax)
asm.jne(side_exit(jit, ctx))
else
return CantCompile
end
# Do method lookup
cme = C.rb_callable_method_entry(comptime_recv_klass, mid)
if cme.nil?
return CantCompile
end
case C.METHOD_ENTRY_VISI(cme)
when C.METHOD_VISI_PUBLIC
# You can always call public methods
when C.METHOD_VISI_PRIVATE
if flags & C.VM_CALL_FCALL == 0
# VM_CALL_FCALL: Callsites without a receiver of an explicit `self` receiver
return CantCompile
end
when C.METHOD_VISI_PROTECTED
return CantCompile # TODO: support this
else
raise 'cmes should always have a visibility'
end
# TODO: assume_method_lookup_stable
if flags & C.VM_CALL_ARGS_SPLAT != 0 && cme.def.type != C.VM_METHOD_TYPE_ISEQ
return CantCompile
end
case cme.def.type
when C.VM_METHOD_TYPE_ISEQ
iseq = def_iseq_ptr(cme.def)
frame_type = C.VM_FRAME_MAGIC_METHOD | C.VM_ENV_FLAG_LOCAL
compile_send_iseq(jit, ctx, asm, iseq, ci, frame_type, cme, flags, argc)
else
return CantCompile
end
end
def compile_send_iseq(jit, ctx, asm, iseq, ci, frame_type, cme, flags, argc)
# TODO: check a bunch of CantCompile cases
compile_check_ints(jit, ctx, asm)
# TODO: stack overflow check
# TODO: more flag checks
# Pop arguments and a receiver for the current caller frame
raise 'sp_offset != stack_size' if ctx.sp_offset != ctx.stack_size # TODO: handle this
sp_index = ctx.stack_size - argc - 1 # arguments and receiver
asm.comment('save SP to caller CFP')
asm.lea(:rax, [SP, sp_index])
asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], :rax)
# TODO: do something about ctx.sp_index
asm.comment('save PC to CFP')
next_pc = jit.pc + jit.insn.len * C.VALUE.size
asm.mov(:rax, next_pc)
asm.mov([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax) # cfp->pc = rax
# TODO: push cme, specval, frame type
# TODO: push callee control frame
asm.comment('switch to new CFP')
asm.lea(:rax, [CFP, -C.rb_control_frame_t.size])
asm.mov(CFP, :rax);
asm.mov([EC, C.rb_execution_context_t.offsetof(:cfp)], :rax)
asm.comment('save SP to callee CFP')
num_locals = 0 # TODO
sp_offset = C.VALUE.size * (3 + num_locals + ctx.stack_size)
asm.add(SP, sp_offset)
asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP)
asm.comment('save ISEQ to callee CFP')
asm.mov(:rax, iseq.to_i)
asm.mov([CFP, C.rb_control_frame_t.offsetof(:iseq)], :rax)
asm.comment('save EP to callee CFP')
asm.lea(:rax, [SP, -C.VALUE.size])
asm.mov([CFP, C.rb_control_frame_t.offsetof(:ep)], :rax)
asm.comment('set frame type')
asm.mov([SP, C.VALUE.size * -1], C.VM_FRAME_MAGIC_METHOD | C.VM_ENV_FLAG_LOCAL)
asm.comment('set specval')
asm.mov([SP, C.VALUE.size * -2], C.VM_BLOCK_HANDLER_NONE)
# Stub the return destination from the callee
# TODO: set up return ctx correctly
jit_return_stub = BlockStub.new(iseq: jit.iseq, pc: next_pc, ctx: ctx.dup)
jit_return = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_block_stub(ctx, ocb_asm, jit_return_stub)
@ocb.write(ocb_asm)
end
jit_return_stub.change_block = proc do |jump_asm, new_addr|
jump_asm.comment('update cfp->jit_return')
jump_asm.stub(jit_return_stub) do
jump_asm.mov(:rax, new_addr)
jump_asm.mov([CFP, C.rb_control_frame_t.offsetof(:jit_return)], :rax)
end
end
jit_return_stub.change_block.call(asm, jit_return)
callee_ctx = Context.new
compile_block_stub(iseq, iseq.body.iseq_encoded.to_i, callee_ctx, asm)
EndBlock
end
def assert_eq!(left, right)
if left != right
raise "'#{left.inspect}' was not '#{right.inspect}'"
@ -487,21 +650,24 @@ module RubyVM::MJIT
# @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,
ctx: ctx.dup,
pc: jit.pc,
)
compile_block_stub(jit.iseq, jit.pc, ctx, asm, comment: 'defer_compilation: block stub')
end
def compile_block_stub(iseq, pc, ctx, asm, comment: 'block stub')
block_stub = BlockStub.new(iseq:, pc:, ctx: ctx.dup)
stub_hit = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_block_stub(jit, ctx, ocb_asm, block_stub)
@exit_compiler.compile_block_stub(ctx, ocb_asm, block_stub)
@ocb.write(ocb_asm)
end
asm.comment('defer_compilation: block stub')
asm.stub(block_stub) do
asm.jmp(stub_hit)
block_stub.change_block = proc do |jump_asm, new_addr|
jump_asm.comment(comment)
jump_asm.stub(block_stub) do
jump_asm.jmp(new_addr)
end
end
block_stub.change_block.call(asm, stub_hit)
end
# @param jit [RubyVM::MJIT::JITState]
@ -514,5 +680,9 @@ module RubyVM::MJIT
@exit_compiler.compile_side_exit(jit, ctx, asm)
jit.side_exits[jit.pc] = @ocb.write(asm)
end
def def_iseq_ptr(cme_def)
C.rb_iseq_check(cme_def.body.iseq.iseqptr)
end
end
end

Просмотреть файл

@ -20,9 +20,9 @@ module RubyVM::MJIT
pc == cfp.pc.to_i
end
def peek_at_stack(offset_from_top)
def peek_at_stack(depth_from_top)
raise 'not at current insn' unless at_current_insn?
offset = -(1 + offset_from_top)
offset = -(1 + depth_from_top)
value = (cfp.sp + offset).*
C.to_ruby(value)
end

104
mjit_c.rb
Просмотреть файл

@ -81,6 +81,21 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! 'UINT2NUM(rb_iseq_line_no((const rb_iseq_t *)NUM2SIZET(_iseq_addr), NUM2SIZET(pos)))'
end
def rb_class_of(obj)
Primitive.cexpr! 'rb_class_of(obj)'
end
def rb_callable_method_entry(klass, mid)
cme_addr = Primitive.cexpr! 'SIZET2NUM((size_t)rb_callable_method_entry(klass, NUM2UINT(mid)))'
return nil if cme_addr == 0
rb_callable_method_entry_struct.new(cme_addr)
end
def METHOD_ENTRY_VISI(cme)
_cme_addr = cme.to_i
Primitive.cexpr! 'UINT2NUM(METHOD_ENTRY_VISI((const rb_callable_method_entry_t *)NUM2SIZET(_cme_addr)))'
end
#========================================================================================
#
# Old stuff
@ -142,6 +157,11 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! 'UINT2NUM(vm_ci_flag((CALL_INFO)NUM2PTR(_ci_addr)))'
end
def vm_ci_mid(ci)
_ci_addr = ci.to_i
Primitive.cexpr! 'UINT2NUM(vm_ci_mid((CALL_INFO)NUM2PTR(_ci_addr)))'
end
def rb_splat_or_kwargs_p(ci)
_ci_addr = ci.to_i
Primitive.cstmt! %{
@ -233,30 +253,6 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! %q{ INT2NUM(NOT_COMPILED_STACK_SIZE) }
end
def C.VM_CALL_KW_SPLAT
Primitive.cexpr! %q{ INT2NUM(VM_CALL_KW_SPLAT) }
end
def C.VM_CALL_KW_SPLAT_bit
Primitive.cexpr! %q{ INT2NUM(VM_CALL_KW_SPLAT_bit) }
end
def C.VM_CALL_TAILCALL
Primitive.cexpr! %q{ INT2NUM(VM_CALL_TAILCALL) }
end
def C.VM_CALL_TAILCALL_bit
Primitive.cexpr! %q{ INT2NUM(VM_CALL_TAILCALL_bit) }
end
def C.VM_METHOD_TYPE_CFUNC
Primitive.cexpr! %q{ INT2NUM(VM_METHOD_TYPE_CFUNC) }
end
def C.VM_METHOD_TYPE_ISEQ
Primitive.cexpr! %q{ INT2NUM(VM_METHOD_TYPE_ISEQ) }
end
def C.BOP_LT
Primitive.cexpr! %q{ UINT2NUM(BOP_LT) }
end
@ -269,6 +265,18 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! %q{ UINT2NUM(INTEGER_REDEFINED_OP_FLAG) }
end
def C.METHOD_VISI_PRIVATE
Primitive.cexpr! %q{ UINT2NUM(METHOD_VISI_PRIVATE) }
end
def C.METHOD_VISI_PROTECTED
Primitive.cexpr! %q{ UINT2NUM(METHOD_VISI_PROTECTED) }
end
def C.METHOD_VISI_PUBLIC
Primitive.cexpr! %q{ UINT2NUM(METHOD_VISI_PUBLIC) }
end
def C.RUBY_EVENT_CLASS
Primitive.cexpr! %q{ UINT2NUM(RUBY_EVENT_CLASS) }
end
@ -301,6 +309,54 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! %q{ UINT2NUM(SHAPE_ROOT) }
end
def C.VM_BLOCK_HANDLER_NONE
Primitive.cexpr! %q{ UINT2NUM(VM_BLOCK_HANDLER_NONE) }
end
def C.VM_CALL_ARGS_BLOCKARG
Primitive.cexpr! %q{ UINT2NUM(VM_CALL_ARGS_BLOCKARG) }
end
def C.VM_CALL_ARGS_SPLAT
Primitive.cexpr! %q{ UINT2NUM(VM_CALL_ARGS_SPLAT) }
end
def C.VM_CALL_FCALL
Primitive.cexpr! %q{ UINT2NUM(VM_CALL_FCALL) }
end
def C.VM_CALL_KW_SPLAT
Primitive.cexpr! %q{ UINT2NUM(VM_CALL_KW_SPLAT) }
end
def C.VM_CALL_KW_SPLAT_bit
Primitive.cexpr! %q{ UINT2NUM(VM_CALL_KW_SPLAT_bit) }
end
def C.VM_CALL_TAILCALL
Primitive.cexpr! %q{ UINT2NUM(VM_CALL_TAILCALL) }
end
def C.VM_CALL_TAILCALL_bit
Primitive.cexpr! %q{ UINT2NUM(VM_CALL_TAILCALL_bit) }
end
def C.VM_ENV_FLAG_LOCAL
Primitive.cexpr! %q{ UINT2NUM(VM_ENV_FLAG_LOCAL) }
end
def C.VM_FRAME_MAGIC_METHOD
Primitive.cexpr! %q{ UINT2NUM(VM_FRAME_MAGIC_METHOD) }
end
def C.VM_METHOD_TYPE_CFUNC
Primitive.cexpr! %q{ UINT2NUM(VM_METHOD_TYPE_CFUNC) }
end
def C.VM_METHOD_TYPE_ISEQ
Primitive.cexpr! %q{ UINT2NUM(VM_METHOD_TYPE_ISEQ) }
end
def C.INVALID_SHAPE_ID
Primitive.cexpr! %q{ ULONG2NUM(INVALID_SHAPE_ID) }
end

Просмотреть файл

@ -345,17 +345,14 @@ generator = BindingGenerator.new(
values: {
INT: %w[
NOT_COMPILED_STACK_SIZE
VM_CALL_KW_SPLAT
VM_CALL_KW_SPLAT_bit
VM_CALL_TAILCALL
VM_CALL_TAILCALL_bit
VM_METHOD_TYPE_CFUNC
VM_METHOD_TYPE_ISEQ
],
UINT: %w[
BOP_LT
BOP_MINUS
INTEGER_REDEFINED_OP_FLAG
METHOD_VISI_PRIVATE
METHOD_VISI_PROTECTED
METHOD_VISI_PUBLIC
RUBY_EVENT_CLASS
SHAPE_CAPACITY_CHANGE
SHAPE_FLAG_SHIFT
@ -364,6 +361,18 @@ generator = BindingGenerator.new(
SHAPE_INITIAL_CAPACITY
SHAPE_IVAR
SHAPE_ROOT
VM_BLOCK_HANDLER_NONE
VM_CALL_ARGS_BLOCKARG
VM_CALL_ARGS_SPLAT
VM_CALL_FCALL
VM_CALL_KW_SPLAT
VM_CALL_KW_SPLAT_bit
VM_CALL_TAILCALL
VM_CALL_TAILCALL_bit
VM_ENV_FLAG_LOCAL
VM_FRAME_MAGIC_METHOD
VM_METHOD_TYPE_CFUNC
VM_METHOD_TYPE_ISEQ
],
ULONG: %w[
INVALID_SHAPE_ID