From 5a1cee1d965301b05e9d2d85b0ee39ef3c6757f7 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 7 Feb 2023 14:42:58 -0800 Subject: [PATCH] Implement getivar --- lib/ruby_vm/mjit/assembler.rb | 37 ++++++++++++- lib/ruby_vm/mjit/c_pointer.rb | 18 +++++- lib/ruby_vm/mjit/insn_compiler.rb | 91 +++++++++++++++++++++++++++++-- lib/ruby_vm/mjit/jit_state.rb | 4 ++ lib/ruby_vm/mjit/stats.rb | 1 + mjit_c.h | 5 ++ mjit_c.rb | 81 +++++++++++++++++++++++++-- tool/mjit/bindgen.rb | 5 ++ 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