Implement getivar

This commit is contained in:
Takashi Kokubun 2023-02-07 14:42:58 -08:00
parent a026bcedc8
commit 5a1cee1d96
8 changed files with 230 additions and 12 deletions

View file

@ -1,5 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
module RubyVM::MJIT module RubyVM::MJIT
# 32-bit memory access
class DwordPtr < Data.define(:reg, :disp); end
# https://www.intel.com/content/dam/develop/public/us/en/documents/325383-sdm-vol-2abcd.pdf # https://www.intel.com/content/dam/develop/public/us/en/documents/325383-sdm-vol-2abcd.pdf
# Mostly an x86_64 assembler, but this also has some stuff that is useful for any architecture. # Mostly an x86_64 assembler, but this also has some stuff that is useful for any architecture.
class Assembler class Assembler
@ -123,8 +126,28 @@ module RubyVM::MJIT
def cmp(left, right) def cmp(left, right)
case [left, right] case [left, right]
# CMP r/m64 r64 (Mod 01: [reg]+disp8) # CMP r/m32, imm32 (Mod 01: [reg]+disp8)
in [[Symbol => left_reg, Integer => left_disp], Symbol => right_reg] in [DwordPtr[reg: left_reg, disp: left_disp], Integer => right_imm] if imm8?(left_disp) && imm32?(right_imm)
# 81 /7 id
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
opcode: 0x81,
mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg],
disp: left_disp,
imm: imm32(right_imm),
)
# CMP r/m64, imm8 (Mod 11: reg)
in [Symbol => left_reg, Integer => right_imm] if r64?(left_reg) && imm8?(right_imm)
# REX.W + 83 /7 ib
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x83,
mod_rm: ModRM[mod: Mod11, reg: 7, rm: left_reg],
imm: imm8(right_imm),
)
# CMP r/m64, r64 (Mod 01: [reg]+disp8)
in [[Symbol => left_reg, Integer => left_disp], Symbol => right_reg] if r64?(right_reg)
# REX.W + 39 /r # REX.W + 39 /r
# MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r) # MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r)
insn( insn(
@ -453,6 +476,16 @@ module RubyVM::MJIT
disp: left_disp, disp: left_disp,
imm: imm32(right_imm), imm: imm32(right_imm),
) )
# TEST r/m64, imm32 (Mod 11: reg)
in [Symbol => left_reg, Integer => right_imm] if r64?(left_reg) && imm32?(right_imm)
# REX.W + F7 /0 id
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0xf7,
mod_rm: ModRM[mod: Mod11, reg: 0, rm: left_reg],
imm: imm32(right_imm),
)
# TEST r/m32, r32 (Mod 11: reg) # TEST r/m32, r32 (Mod 11: reg)
in [Symbol => left_reg, Symbol => right_reg] if r32?(left_reg) && r32?(right_reg) in [Symbol => left_reg, Symbol => right_reg] if r32?(left_reg) && r32?(right_reg)
# 85 /r # 85 /r

View file

@ -55,7 +55,14 @@ module RubyVM::MJIT
define_singleton_method(:size) { size } define_singleton_method(:size) { size }
# Return the offset to a field # Return the offset to a field
define_singleton_method(:offsetof) { |field| members.fetch(field).last / 8 } define_singleton_method(:offsetof) do |field, *fields|
member, offset = members.fetch(field)
offset /= 8
unless fields.empty?
offset += member.offsetof(*fields)
end
offset
end
# Return member names # Return member names
define_singleton_method(:members) { members.keys } define_singleton_method(:members) { members.keys }
@ -127,6 +134,15 @@ module RubyVM::MJIT
# Return the size of this type # Return the size of this type
define_singleton_method(:sizeof) { sizeof } define_singleton_method(:sizeof) { sizeof }
# Part of Struct's offsetof implementation
define_singleton_method(:offsetof) do |*fields|
if fields.size == 1
0
else
raise NotImplementedError
end
end
define_method(:initialize) do |addr| define_method(:initialize) do |addr|
super(addr, sizeof, members) super(addr, sizeof, members)
end end

View file

@ -17,7 +17,7 @@ module RubyVM::MJIT
asm.incr_counter(:mjit_insns_count) asm.incr_counter(:mjit_insns_count)
asm.comment("Insn: #{insn.name}") asm.comment("Insn: #{insn.name}")
# 11/101 # 13/101
case insn.name case insn.name
# nop # nop
# getlocal # getlocal
@ -27,7 +27,7 @@ module RubyVM::MJIT
# getblockparamproxy # getblockparamproxy
# getspecial # getspecial
# setspecial # setspecial
# getinstancevariable when :getinstancevariable then getinstancevariable(jit, ctx, asm)
# setinstancevariable # setinstancevariable
# getclassvariable # getclassvariable
# setclassvariable # setclassvariable
@ -137,7 +137,22 @@ module RubyVM::MJIT
# getblockparamproxy # getblockparamproxy
# getspecial # getspecial
# setspecial # setspecial
# getinstancevariable
# @param jit [RubyVM::MJIT::JITState]
# @param ctx [RubyVM::MJIT::Context]
# @param asm [RubyVM::MJIT::Assembler]
def getinstancevariable(jit, ctx, asm)
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
id = jit.operand(0)
comptime_obj = jit.peek_at_self
jit_getivar(jit, ctx, asm, comptime_obj, id)
end
# setinstancevariable # setinstancevariable
# getclassvariable # getclassvariable
# setclassvariable # setclassvariable
@ -242,7 +257,7 @@ module RubyVM::MJIT
def leave(jit, ctx, asm) def leave(jit, ctx, asm)
assert_equal(ctx.stack_size, 1) assert_equal(ctx.stack_size, 1)
compile_check_ints(jit, ctx, asm) jit_check_ints(jit, ctx, asm)
asm.comment('pop stack frame') asm.comment('pop stack frame')
asm.lea(:rax, [CFP, C.rb_control_frame_t.size]) asm.lea(:rax, [CFP, C.rb_control_frame_t.size])
@ -520,16 +535,81 @@ module RubyVM::MJIT
# Helpers # Helpers
# #
# @param asm [RubyVM::MJIT::Assembler]
def guard_object_is_heap(asm, object_opnd, side_exit)
asm.comment('guard object is heap')
# Test that the object is not an immediate
asm.test(object_opnd, C.RUBY_IMMEDIATE_MASK)
asm.jnz(side_exit)
# Test that the object is not false
asm.cmp(object_opnd, Qfalse)
asm.je(side_exit)
end
# rb_vm_check_ints
# @param jit [RubyVM::MJIT::JITState] # @param jit [RubyVM::MJIT::JITState]
# @param ctx [RubyVM::MJIT::Context] # @param ctx [RubyVM::MJIT::Context]
# @param asm [RubyVM::MJIT::Assembler] # @param asm [RubyVM::MJIT::Assembler]
def compile_check_ints(jit, ctx, asm) def jit_check_ints(jit, ctx, asm)
asm.comment('RUBY_VM_CHECK_INTS(ec)') asm.comment('RUBY_VM_CHECK_INTS(ec)')
asm.mov(:eax, [EC, C.rb_execution_context_t.offsetof(:interrupt_flag)]) asm.mov(:eax, [EC, C.rb_execution_context_t.offsetof(:interrupt_flag)])
asm.test(:eax, :eax) asm.test(:eax, :eax)
asm.jnz(side_exit(jit, ctx)) asm.jnz(side_exit(jit, ctx))
end end
# vm_getivar
# @param jit [RubyVM::MJIT::JITState]
# @param ctx [RubyVM::MJIT::Context]
# @param asm [RubyVM::MJIT::Assembler]
def jit_getivar(jit, ctx, asm, comptime_obj, ivar_id)
side_exit = side_exit(jit, ctx)
# Guard not special const
if C.SPECIAL_CONST_P(comptime_obj)
asm.incr_counter(:getivar_special_const)
return CantCompile
end
asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:self)])
guard_object_is_heap(asm, :rax, side_exit) # TODO: counted side exit
case C.BUILTIN_TYPE(comptime_obj)
when C.T_OBJECT
# This is the only supported case for now
else
asm.incr_counter(:getivar_not_t_object)
return CantCompile
end
shape_id = C.rb_shape_get_shape_id(comptime_obj)
if shape_id == C.OBJ_TOO_COMPLEX_SHAPE_ID
asm.incr_counter(:getivar_too_complex)
return CantCompile
end
asm.comment('guard shape')
asm.cmp(DwordPtr[:rax, C.rb_shape_id_offset], shape_id)
asm.jne(side_exit) # TODO: counted side exit
index = C.rb_shape_get_iv_index(shape_id, ivar_id)
if index
if C.FL_TEST_RAW(comptime_obj, C.ROBJECT_EMBED)
asm.mov(:rax, [:rax, C.RObject.offsetof(:as, :ary) + (index * C.VALUE.size)])
val_opnd = :rax
else
asm.incr_counter(:getivar_too_complex)
return CantCompile
end
else
val_opnd = Qnil
end
stack_opnd = ctx.stack_push
asm.mov(stack_opnd, val_opnd)
KeepCompiling
end
# vm_call_method (vm_sendish -> vm_call_general -> vm_call_method) # vm_call_method (vm_sendish -> vm_call_general -> vm_call_method)
# @param jit [RubyVM::MJIT::JITState] # @param jit [RubyVM::MJIT::JITState]
# @param ctx [RubyVM::MJIT::Context] # @param ctx [RubyVM::MJIT::Context]
@ -792,6 +872,7 @@ module RubyVM::MJIT
def jit_caller_remove_empty_kw_splat(jit, ctx, asm, flags) def jit_caller_remove_empty_kw_splat(jit, ctx, asm, flags)
if (flags & C.VM_CALL_KW_SPLAT) > 0 if (flags & C.VM_CALL_KW_SPLAT) > 0
# We don't support removing the last Hash argument # We don't support removing the last Hash argument
asm.incr_counter(:send_kw_splat)
return CantCompile return CantCompile
end end
end end

View file

@ -26,5 +26,9 @@ module RubyVM::MJIT
value = (cfp.sp + offset).* value = (cfp.sp + offset).*
C.to_ruby(value) C.to_ruby(value)
end end
def peek_at_self
C.to_ruby(cfp.self)
end
end end
end end

View file

@ -35,6 +35,7 @@ module RubyVM::MJIT
$stderr.puts("***MJIT: Printing MJIT statistics on exit***") $stderr.puts("***MJIT: Printing MJIT statistics on exit***")
print_counters(stats, prefix: 'send_', prompt: 'method call exit reasons') print_counters(stats, prefix: 'send_', prompt: 'method call exit reasons')
print_counters(stats, prefix: 'getivar_', prompt: 'getinstancevariable exit reasons')
$stderr.puts "compiled_block_count: #{format('%10d', stats[:compiled_block_count])}" $stderr.puts "compiled_block_count: #{format('%10d', stats[:compiled_block_count])}"
$stderr.puts "side_exit_count: #{format('%10d', stats[:side_exit_count])}" $stderr.puts "side_exit_count: #{format('%10d', stats[:side_exit_count])}"

View file

@ -119,6 +119,11 @@ MJIT_RUNTIME_COUNTERS(
send_kwarg, send_kwarg,
send_tailcall, send_tailcall,
getivar_not_embedded,
getivar_not_t_object,
getivar_special_const,
getivar_too_complex,
compiled_block_count compiled_block_count
) )
#undef MJIT_RUNTIME_COUNTERS #undef MJIT_RUNTIME_COUNTERS

View file

@ -94,6 +94,39 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! 'RBOOL(rb_simple_iseq_p((rb_iseq_t *)NUM2SIZET(_iseq_addr)))' Primitive.cexpr! 'RBOOL(rb_simple_iseq_p((rb_iseq_t *)NUM2SIZET(_iseq_addr)))'
end end
def SPECIAL_CONST_P(obj)
_value = to_value(obj)
Primitive.cexpr! 'RBOOL(SPECIAL_CONST_P((VALUE)NUM2SIZET(_value)))'
end
def BUILTIN_TYPE(obj)
_value = to_value(obj)
Primitive.cexpr! 'INT2NUM(BUILTIN_TYPE((VALUE)NUM2SIZET(_value)))'
end
def rb_shape_get_shape_id(obj)
_value = to_value(obj)
Primitive.cexpr! 'UINT2NUM((unsigned int)rb_shape_get_shape_id((VALUE)NUM2SIZET(_value)))'
end
def rb_shape_id_offset
Primitive.cexpr! 'INT2NUM(rb_shape_id_offset())'
end
def rb_shape_get_iv_index(shape_id, ivar_id)
Primitive.cstmt! %{
rb_shape_t *shape = rb_shape_get_shape_by_id((shape_id_t)NUM2SIZET(shape_id));
attr_index_t index;
bool found = rb_shape_get_iv_index(shape, (ID)NUM2SIZET(ivar_id), &index);
return found ? UINT2NUM(index) : Qnil;
}
end
def FL_TEST_RAW(obj, flags)
_value = to_value(obj)
Primitive.cexpr! 'RBOOL(FL_TEST_RAW((VALUE)NUM2SIZET(_value), (VALUE)NUM2SIZET(flags)))'
end
#======================================================================================== #========================================================================================
# #
# Old stuff # Old stuff
@ -278,6 +311,10 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! %q{ UINT2NUM(METHOD_VISI_PUBLIC) } Primitive.cexpr! %q{ UINT2NUM(METHOD_VISI_PUBLIC) }
end end
def C.ROBJECT_EMBED
Primitive.cexpr! %q{ UINT2NUM(ROBJECT_EMBED) }
end
def C.RUBY_EVENT_CLASS def C.RUBY_EVENT_CLASS
Primitive.cexpr! %q{ UINT2NUM(RUBY_EVENT_CLASS) } Primitive.cexpr! %q{ UINT2NUM(RUBY_EVENT_CLASS) }
end end
@ -310,6 +347,10 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! %q{ UINT2NUM(SHAPE_ROOT) } Primitive.cexpr! %q{ UINT2NUM(SHAPE_ROOT) }
end end
def C.T_OBJECT
Primitive.cexpr! %q{ UINT2NUM(T_OBJECT) }
end
def C.VM_BLOCK_HANDLER_NONE def C.VM_BLOCK_HANDLER_NONE
Primitive.cexpr! %q{ UINT2NUM(VM_BLOCK_HANDLER_NONE) } Primitive.cexpr! %q{ UINT2NUM(VM_BLOCK_HANDLER_NONE) }
end end
@ -366,10 +407,18 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! %q{ ULONG2NUM(INVALID_SHAPE_ID) } Primitive.cexpr! %q{ ULONG2NUM(INVALID_SHAPE_ID) }
end end
def C.OBJ_TOO_COMPLEX_SHAPE_ID
Primitive.cexpr! %q{ ULONG2NUM(OBJ_TOO_COMPLEX_SHAPE_ID) }
end
def C.RUBY_FIXNUM_FLAG def C.RUBY_FIXNUM_FLAG
Primitive.cexpr! %q{ ULONG2NUM(RUBY_FIXNUM_FLAG) } Primitive.cexpr! %q{ ULONG2NUM(RUBY_FIXNUM_FLAG) }
end end
def C.RUBY_IMMEDIATE_MASK
Primitive.cexpr! %q{ ULONG2NUM(RUBY_IMMEDIATE_MASK) }
end
def C.SHAPE_MASK def C.SHAPE_MASK
Primitive.cexpr! %q{ ULONG2NUM(SHAPE_MASK) } Primitive.cexpr! %q{ ULONG2NUM(SHAPE_MASK) }
end end
@ -414,6 +463,22 @@ module RubyVM::MJIT # :nodoc: all
@RB_BUILTIN ||= self.rb_builtin_function @RB_BUILTIN ||= self.rb_builtin_function
end end
def C.RObject
@RObject ||= CType::Struct.new(
"RObject", Primitive.cexpr!("SIZEOF(struct RObject)"),
basic: [self.RBasic, Primitive.cexpr!("OFFSETOF((*((struct RObject *)NULL)), basic)")],
as: [CType::Union.new(
"", Primitive.cexpr!("SIZEOF(((struct RObject *)NULL)->as)"),
heap: CType::Struct.new(
"", Primitive.cexpr!("SIZEOF(((struct RObject *)NULL)->as.heap)"),
ivptr: [CType::Pointer.new { self.VALUE }, Primitive.cexpr!("OFFSETOF(((struct RObject *)NULL)->as.heap, ivptr)")],
iv_index_tbl: [CType::Pointer.new { self.rb_id_table }, Primitive.cexpr!("OFFSETOF(((struct RObject *)NULL)->as.heap, iv_index_tbl)")],
),
ary: CType::Pointer.new { self.VALUE },
), Primitive.cexpr!("OFFSETOF((*((struct RObject *)NULL)), as)")],
)
end
def C.attr_index_t def C.attr_index_t
@attr_index_t ||= CType::Immediate.parse("uint32_t") @attr_index_t ||= CType::Immediate.parse("uint32_t")
end end
@ -799,6 +864,10 @@ module RubyVM::MJIT # :nodoc: all
send_args_splat: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_runtime_counters *)NULL)), send_args_splat)")], send_args_splat: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_runtime_counters *)NULL)), send_args_splat)")],
send_kwarg: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_runtime_counters *)NULL)), send_kwarg)")], send_kwarg: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_runtime_counters *)NULL)), send_kwarg)")],
send_tailcall: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_runtime_counters *)NULL)), send_tailcall)")], send_tailcall: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_runtime_counters *)NULL)), send_tailcall)")],
getivar_not_embedded: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_runtime_counters *)NULL)), getivar_not_embedded)")],
getivar_not_t_object: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_runtime_counters *)NULL)), getivar_not_t_object)")],
getivar_special_const: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_runtime_counters *)NULL)), getivar_special_const)")],
getivar_too_complex: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_runtime_counters *)NULL)), getivar_too_complex)")],
compiled_block_count: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_runtime_counters *)NULL)), compiled_block_count)")], compiled_block_count: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_mjit_runtime_counters *)NULL)), compiled_block_count)")],
) )
end end
@ -848,6 +917,14 @@ module RubyVM::MJIT # :nodoc: all
@shape_id_t ||= CType::Immediate.find(Primitive.cexpr!("SIZEOF(shape_id_t)"), Primitive.cexpr!("SIGNED_TYPE_P(shape_id_t)")) @shape_id_t ||= CType::Immediate.find(Primitive.cexpr!("SIZEOF(shape_id_t)"), Primitive.cexpr!("SIGNED_TYPE_P(shape_id_t)"))
end end
def C.RBasic
CType::Stub.new(:RBasic)
end
def C.rb_id_table
CType::Stub.new(:rb_id_table)
end
def C._Bool def C._Bool
CType::Bool.new CType::Bool.new
end end
@ -892,10 +969,6 @@ module RubyVM::MJIT # :nodoc: all
CType::Stub.new(:rb_fiber_t) CType::Stub.new(:rb_fiber_t)
end end
def C.rb_id_table
CType::Stub.new(:rb_id_table)
end
def C.rb_ensure_list_t def C.rb_ensure_list_t
CType::Stub.new(:rb_ensure_list_t) CType::Stub.new(:rb_ensure_list_t)
end end

View file

@ -354,6 +354,7 @@ generator = BindingGenerator.new(
METHOD_VISI_PRIVATE METHOD_VISI_PRIVATE
METHOD_VISI_PROTECTED METHOD_VISI_PROTECTED
METHOD_VISI_PUBLIC METHOD_VISI_PUBLIC
ROBJECT_EMBED
RUBY_EVENT_CLASS RUBY_EVENT_CLASS
SHAPE_CAPACITY_CHANGE SHAPE_CAPACITY_CHANGE
SHAPE_FLAG_SHIFT SHAPE_FLAG_SHIFT
@ -362,6 +363,7 @@ generator = BindingGenerator.new(
SHAPE_INITIAL_CAPACITY SHAPE_INITIAL_CAPACITY
SHAPE_IVAR SHAPE_IVAR
SHAPE_ROOT SHAPE_ROOT
T_OBJECT
VM_BLOCK_HANDLER_NONE VM_BLOCK_HANDLER_NONE
VM_CALL_ARGS_BLOCKARG VM_CALL_ARGS_BLOCKARG
VM_CALL_ARGS_SPLAT VM_CALL_ARGS_SPLAT
@ -378,7 +380,9 @@ generator = BindingGenerator.new(
], ],
ULONG: %w[ ULONG: %w[
INVALID_SHAPE_ID INVALID_SHAPE_ID
OBJ_TOO_COMPLEX_SHAPE_ID
RUBY_FIXNUM_FLAG RUBY_FIXNUM_FLAG
RUBY_IMMEDIATE_MASK
SHAPE_MASK SHAPE_MASK
], ],
PTR: %w[ PTR: %w[
@ -395,6 +399,7 @@ generator = BindingGenerator.new(
IC IC
IVC IVC
RB_BUILTIN RB_BUILTIN
RObject
attr_index_t attr_index_t
compile_branch compile_branch
compile_status compile_status