summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakashi Kokubun <[email protected]>2023-02-07 14:42:58 -0800
committerTakashi Kokubun <[email protected]>2023-03-05 22:11:20 -0800
commit5a1cee1d965301b05e9d2d85b0ee39ef3c6757f7 (patch)
tree452e673ff17de60fa4b2c35613c39e386cd760e5
parenta026bcedc87a2f502befbd7476a997a9fe030203 (diff)
Implement getivar
-rw-r--r--lib/ruby_vm/mjit/assembler.rb37
-rw-r--r--lib/ruby_vm/mjit/c_pointer.rb18
-rw-r--r--lib/ruby_vm/mjit/insn_compiler.rb91
-rw-r--r--lib/ruby_vm/mjit/jit_state.rb4
-rw-r--r--lib/ruby_vm/mjit/stats.rb1
-rw-r--r--mjit_c.h5
-rw-r--r--mjit_c.rb81
-rwxr-xr-xtool/mjit/bindgen.rb5
8 files changed, 230 insertions, 12 deletions
diff --git a/lib/ruby_vm/mjit/assembler.rb b/lib/ruby_vm/mjit/assembler.rb
index f2f5bd77c4..f2cd90a789 100644
--- a/lib/ruby_vm/mjit/assembler.rb
+++ b/lib/ruby_vm/mjit/assembler.rb
@@ -1,5 +1,8 @@
# frozen_string_literal: true
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
# Mostly an x86_64 assembler, but this also has some stuff that is useful for any architecture.
class Assembler
@@ -123,8 +126,28 @@ module RubyVM::MJIT
def cmp(left, right)
case [left, right]
- # CMP r/m64 r64 (Mod 01: [reg]+disp8)
- in [[Symbol => left_reg, Integer => left_disp], Symbol => right_reg]
+ # CMP r/m32, imm32 (Mod 01: [reg]+disp8)
+ 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
# MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r)
insn(
@@ -453,6 +476,16 @@ module RubyVM::MJIT
disp: left_disp,
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)
in [Symbol => left_reg, Symbol => right_reg] if r32?(left_reg) && r32?(right_reg)
# 85 /r
diff --git a/lib/ruby_vm/mjit/c_pointer.rb b/lib/ruby_vm/mjit/c_pointer.rb
index 03742dd53a..c91f6f646b 100644
--- a/lib/ruby_vm/mjit/c_pointer.rb
+++ b/lib/ruby_vm/mjit/c_pointer.rb
@@ -55,7 +55,14 @@ module RubyVM::MJIT
define_singleton_method(:size) { size }
# 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
define_singleton_method(:members) { members.keys }
@@ -127,6 +134,15 @@ module RubyVM::MJIT
# Return the size of this type
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|
super(addr, sizeof, members)
end
diff --git a/lib/ruby_vm/mjit/insn_compiler.rb b/lib/ruby_vm/mjit/insn_compiler.rb
index 0ea0020f19..5852c28deb 100644
--- a/lib/ruby_vm/mjit/insn_compiler.rb
+++ b/lib/ruby_vm/mjit/insn_compiler.rb
@@ -17,7 +17,7 @@ module RubyVM::MJIT
asm.incr_counter(:mjit_insns_count)
asm.comment("Insn: #{insn.name}")
- # 11/101
+ # 13/101
case insn.name
# nop
# getlocal
@@ -27,7 +27,7 @@ module RubyVM::MJIT
# getblockparamproxy
# getspecial
# setspecial
- # getinstancevariable
+ when :getinstancevariable then getinstancevariable(jit, ctx, asm)
# setinstancevariable
# getclassvariable
# setclassvariable
@@ -137,7 +137,22 @@ module RubyVM::MJIT
# getblockparamproxy
# getspecial
# 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
# getclassvariable
# setclassvariable
@@ -242,7 +257,7 @@ module RubyVM::MJIT
def leave(jit, ctx, asm)
assert_equal(ctx.stack_size, 1)
- compile_check_ints(jit, ctx, asm)
+ jit_check_ints(jit, ctx, asm)
asm.comment('pop stack frame')
asm.lea(:rax, [CFP, C.rb_control_frame_t.size])
@@ -520,16 +535,81 @@ module RubyVM::MJIT
# 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 ctx [RubyVM::MJIT::Context]
# @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.mov(:eax, [EC, C.rb_execution_context_t.offsetof(:interrupt_flag)])
asm.test(:eax, :eax)
asm.jnz(side_exit(jit, ctx))
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)
# @param jit [RubyVM::MJIT::JITState]
# @param ctx [RubyVM::MJIT::Context]
@@ -792,6 +872,7 @@ module RubyVM::MJIT
def jit_caller_remove_empty_kw_splat(jit, ctx, asm, flags)
if (flags & C.VM_CALL_KW_SPLAT) > 0
# We don't support removing the last Hash argument
+ asm.incr_counter(:send_kw_splat)
return CantCompile
end
end
diff --git a/lib/ruby_vm/mjit/jit_state.rb b/lib/ruby_vm/mjit/jit_state.rb
index 1f888a02ae..cf1ec2bbd1 100644
--- a/lib/ruby_vm/mjit/jit_state.rb
+++ b/lib/ruby_vm/mjit/jit_state.rb
@@ -26,5 +26,9 @@ module RubyVM::MJIT
value = (cfp.sp + offset).*
C.to_ruby(value)
end
+
+ def peek_at_self
+ C.to_ruby(cfp.self)
+ end
end
end
diff --git a/lib/ruby_vm/mjit/stats.rb b/lib/ruby_vm/mjit/stats.rb
index bc5a30738e..9a73d2bbdf 100644
--- a/lib/ruby_vm/mjit/stats.rb
+++ b/lib/ruby_vm/mjit/stats.rb
@@ -35,6 +35,7 @@ module RubyVM::MJIT
$stderr.puts("***MJIT: Printing MJIT statistics on exit***")
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 "side_exit_count: #{format('%10d', stats[:side_exit_count])}"
diff --git a/mjit_c.h b/mjit_c.h
index 5c08616dcd..d84b0f92cc 100644
--- a/mjit_c.h
+++ b/mjit_c.h
@@ -119,6 +119,11 @@ MJIT_RUNTIME_COUNTERS(
send_kwarg,
send_tailcall,
+ getivar_not_embedded,
+ getivar_not_t_object,
+ getivar_special_const,
+ getivar_too_complex,
+
compiled_block_count
)
#undef MJIT_RUNTIME_COUNTERS
diff --git a/mjit_c.rb b/mjit_c.rb
index f487c6ddca..980f3ee36c 100644
--- a/mjit_c.rb
+++ b/mjit_c.rb
@@ -94,6 +94,39 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! 'RBOOL(rb_simple_iseq_p((rb_iseq_t *)NUM2SIZET(_iseq_addr)))'
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
@@ -278,6 +311,10 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! %q{ UINT2NUM(METHOD_VISI_PUBLIC) }
end
+ def C.ROBJECT_EMBED
+ Primitive.cexpr! %q{ UINT2NUM(ROBJECT_EMBED) }
+ end
+
def C.RUBY_EVENT_CLASS
Primitive.cexpr! %q{ UINT2NUM(RUBY_EVENT_CLASS) }
end
@@ -310,6 +347,10 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! %q{ UINT2NUM(SHAPE_ROOT) }
end
+ def C.T_OBJECT
+ Primitive.cexpr! %q{ UINT2NUM(T_OBJECT) }
+ end
+
def C.VM_BLOCK_HANDLER_NONE
Primitive.cexpr! %q{ UINT2NUM(VM_BLOCK_HANDLER_NONE) }
end
@@ -366,10 +407,18 @@ module RubyVM::MJIT # :nodoc: all
Primitive.cexpr! %q{ ULONG2NUM(INVALID_SHAPE_ID) }
end
+ def C.OBJ_TOO_COMPLEX_SHAPE_ID
+ Primitive.cexpr! %q{ ULONG2NUM(OBJ_TOO_COMPLEX_SHAPE_ID) }
+ end
+
def C.RUBY_FIXNUM_FLAG
Primitive.cexpr! %q{ ULONG2NUM(RUBY_FIXNUM_FLAG) }
end
+ def C.RUBY_IMMEDIATE_MASK
+ Primitive.cexpr! %q{ ULONG2NUM(RUBY_IMMEDIATE_MASK) }
+ end
+
def C.SHAPE_MASK
Primitive.cexpr! %q{ ULONG2NUM(SHAPE_MASK) }
end
@@ -414,6 +463,22 @@ module RubyVM::MJIT # :nodoc: all
@RB_BUILTIN ||= self.rb_builtin_function
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
@attr_index_t ||= CType::Immediate.parse("uint32_t")
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_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)")],
+ 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)")],
)
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)"))
end
+ def C.RBasic
+ CType::Stub.new(:RBasic)
+ end
+
+ def C.rb_id_table
+ CType::Stub.new(:rb_id_table)
+ end
+
def C._Bool
CType::Bool.new
end
@@ -892,10 +969,6 @@ module RubyVM::MJIT # :nodoc: all
CType::Stub.new(:rb_fiber_t)
end
- def C.rb_id_table
- CType::Stub.new(:rb_id_table)
- end
-
def C.rb_ensure_list_t
CType::Stub.new(:rb_ensure_list_t)
end
diff --git a/tool/mjit/bindgen.rb b/tool/mjit/bindgen.rb
index 891cb7d297..32c2417bef 100755
--- a/tool/mjit/bindgen.rb
+++ b/tool/mjit/bindgen.rb
@@ -354,6 +354,7 @@ generator = BindingGenerator.new(
METHOD_VISI_PRIVATE
METHOD_VISI_PROTECTED
METHOD_VISI_PUBLIC
+ ROBJECT_EMBED
RUBY_EVENT_CLASS
SHAPE_CAPACITY_CHANGE
SHAPE_FLAG_SHIFT
@@ -362,6 +363,7 @@ generator = BindingGenerator.new(
SHAPE_INITIAL_CAPACITY
SHAPE_IVAR
SHAPE_ROOT
+ T_OBJECT
VM_BLOCK_HANDLER_NONE
VM_CALL_ARGS_BLOCKARG
VM_CALL_ARGS_SPLAT
@@ -378,7 +380,9 @@ generator = BindingGenerator.new(
],
ULONG: %w[
INVALID_SHAPE_ID
+ OBJ_TOO_COMPLEX_SHAPE_ID
RUBY_FIXNUM_FLAG
+ RUBY_IMMEDIATE_MASK
SHAPE_MASK
],
PTR: %w[
@@ -395,6 +399,7 @@ generator = BindingGenerator.new(
IC
IVC
RB_BUILTIN
+ RObject
attr_index_t
compile_branch
compile_status