summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-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
5 files changed, 143 insertions, 8 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])}"