summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/ruby_vm/rjit/assembler.rb4
-rw-r--r--lib/ruby_vm/rjit/insn_compiler.rb148
-rw-r--r--rjit_c.h5
-rw-r--r--rjit_c.rb7
-rwxr-xr-xtool/rjit/bindgen.rb2
5 files changed, 157 insertions, 9 deletions
diff --git a/lib/ruby_vm/rjit/assembler.rb b/lib/ruby_vm/rjit/assembler.rb
index 35f01392c9..73fff946de 100644
--- a/lib/ruby_vm/rjit/assembler.rb
+++ b/lib/ruby_vm/rjit/assembler.rb
@@ -449,6 +449,10 @@ module RubyVM::RJIT
def jne(dst)
case dst
+ # JNE rel8
+ in Label => dst_label
+ # 75 cb
+ insn(opcode: 0x75, imm: dst_label)
# JNE rel32
in Integer => dst_addr
# 0F 85 cd
diff --git a/lib/ruby_vm/rjit/insn_compiler.rb b/lib/ruby_vm/rjit/insn_compiler.rb
index 070049ca93..636cd75fc7 100644
--- a/lib/ruby_vm/rjit/insn_compiler.rb
+++ b/lib/ruby_vm/rjit/insn_compiler.rb
@@ -3315,6 +3315,31 @@ module RubyVM::RJIT
asm.jne(side_exit)
end
+ # clobbers object_reg
+ def guard_object_is_not_ruby2_keyword_hash(asm, object_reg, flags_reg, side_exit)
+ asm.comment('guard object is not ruby2 keyword hash')
+
+ not_ruby2_keyword = asm.new_label('not_ruby2_keyword')
+ asm.test(object_reg, C::RUBY_IMMEDIATE_MASK)
+ asm.jnz(not_ruby2_keyword)
+
+ asm.cmp(object_reg, Qfalse)
+ asm.je(not_ruby2_keyword)
+
+ asm.mov(flags_reg, [object_reg, C.RBasic.offsetof(:flags)])
+ type_reg = object_reg
+ asm.mov(type_reg, flags_reg)
+ asm.and(type_reg, C::RUBY_T_MASK)
+
+ asm.cmp(type_reg, C::RUBY_T_HASH)
+ asm.jne(not_ruby2_keyword)
+
+ asm.test(flags_reg, C::RHASH_PASS_AS_KEYWORDS)
+ asm.jnz(side_exit)
+
+ asm.write_label(not_ruby2_keyword)
+ end
+
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
@@ -4028,7 +4053,7 @@ module RubyVM::RJIT
end
end
- # vm_call_iseq_setup
+ # vm_call_iseq_setup (ISEQ only)
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
@@ -4051,7 +4076,26 @@ module RubyVM::RJIT
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_iseq_setup_normal(jit, ctx, asm, cme, flags, argc, iseq, block_handler, opt_pc, send_shift:, frame_type:, prev_ep: nil)
- # We will not have side exits from here. Adjust the stack.
+ # Push splat args, which was skipped in jit_caller_setup_arg.
+ if flags & C::VM_CALL_ARGS_SPLAT != 0
+ if iseq.body.param.opt_num != 0
+ asm.incr_counter(:send_args_splat_opt_num) # not supported yet
+ return CantCompile
+ end
+
+ array_length = jit.peek_at_stack(flags & C::VM_CALL_ARGS_BLOCKARG != 0 ? 1 : 0)&.length || 0 # blockarg is not popped yet
+ if iseq.body.param.lead_num != array_length + argc - 1
+ asm.incr_counter(:send_args_splat_arity_error)
+ return CantCompile
+ end
+
+ # We are going to assume that the splat fills all the remaining arguments.
+ # In the generated code we test if this is true and if not side exit.
+ argc = argc - 1 + array_length
+ jit_caller_setup_arg_splat(jit, ctx, asm, array_length)
+ end
+
+ # We will not have side exits from here. Adjust the stack, which was skipped in jit_call_opt_send.
if flags & C::VM_CALL_OPT_SEND != 0
jit_call_opt_send_shift_stack(ctx, asm, argc, send_shift:)
end
@@ -4316,8 +4360,7 @@ module RubyVM::RJIT
asm.incr_counter(:send_optimized_send_send)
return CantCompile
end
- # Ideally, we want to shift the stack here, but it's not safe until you reach the point
- # where you never exit. `send_shift` signals to lazily shift the stack by this amount.
+ # Lazily handle stack shift in jit_call_iseq_setup_normal
send_shift += 1
kw_splat = flags & C::VM_CALL_KW_SPLAT != 0
@@ -4426,6 +4469,7 @@ module RubyVM::RJIT
EndBlock
end
+ # vm_call_opt_send (lazy part)
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_opt_send_shift_stack(ctx, asm, argc, send_shift:)
@@ -4613,14 +4657,14 @@ module RubyVM::RJIT
asm.mov([EC, C.rb_execution_context_t.offsetof(:cfp)], cfp_reg)
end
- # vm_callee_setup_arg: Set up args and return opt_pc (or CantCompile)
+ # vm_callee_setup_arg (ISEQ only): Set up args and return opt_pc (or CantCompile)
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_callee_setup_arg(jit, ctx, asm, flags, argc, iseq)
if flags & C::VM_CALL_KW_SPLAT == 0
if C.rb_simple_iseq_p(iseq)
- if jit_caller_setup_arg(jit, ctx, asm, flags) == CantCompile
+ if jit_caller_setup_arg(jit, ctx, asm, flags, splat: true) == CantCompile
return CantCompile
end
if jit_caller_remove_empty_kw_splat(jit, ctx, asm, flags) == CantCompile
@@ -4806,13 +4850,18 @@ module RubyVM::RJIT
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
- def jit_caller_setup_arg(jit, ctx, asm, flags)
+ def jit_caller_setup_arg(jit, ctx, asm, flags, splat: false)
if flags & C::VM_CALL_ARGS_SPLAT != 0 && flags & C::VM_CALL_KW_SPLAT != 0
asm.incr_counter(:send_args_splat_kw_splat)
return CantCompile
elsif flags & C::VM_CALL_ARGS_SPLAT != 0
- asm.incr_counter(:send_args_splat)
- return CantCompile
+ if splat
+ # Lazily handle splat in jit_call_iseq_setup_normal
+ else
+ # splat is not supported in this path
+ asm.incr_counter(:send_args_splat)
+ return CantCompile
+ end
elsif flags & C::VM_CALL_KW_SPLAT != 0
asm.incr_counter(:send_args_kw_splat)
return CantCompile
@@ -4822,6 +4871,73 @@ module RubyVM::RJIT
end
end
+ # vm_caller_setup_arg_splat (+ CALLER_SETUP_ARG):
+ # Pushes arguments from an array to the stack that are passed with a splat (i.e. *args).
+ # It optimistically compiles to a static size that is the exact number of arguments needed for the function.
+ # @param jit [RubyVM::RJIT::JITState]
+ # @param ctx [RubyVM::RJIT::Context]
+ # @param asm [RubyVM::RJIT::Assembler]
+ def jit_caller_setup_arg_splat(jit, ctx, asm, required_args)
+ side_exit = side_exit(jit, ctx)
+
+ asm.comment('push_splat_args')
+
+ array_opnd = ctx.stack_opnd(0)
+ array_reg = :rax
+ asm.mov(array_reg, array_opnd)
+
+ guard_object_is_heap(asm, array_reg, counted_exit(side_exit, :send_args_splat_not_array))
+ guard_object_is_array(asm, array_reg, :rcx, counted_exit(side_exit, :send_args_splat_not_array))
+
+ array_len_opnd = :rcx
+ jit_array_len(asm, array_reg, array_len_opnd)
+
+ asm.comment('Side exit if length is not equal to remaining args')
+ asm.cmp(array_len_opnd, required_args)
+ asm.jne(counted_exit(side_exit, :send_args_splat_length_not_equal))
+
+ asm.comment('Check last argument is not ruby2keyword hash')
+
+ ary_opnd = :rcx
+ jit_array_ptr(asm, array_reg, ary_opnd) # clobbers array_reg
+
+ last_array_value = :rax
+ asm.mov(last_array_value, [ary_opnd, (required_args - 1) * C.VALUE.size])
+
+ ruby2_exit = counted_exit(side_exit, :send_args_splat_ruby2_hash);
+ guard_object_is_not_ruby2_keyword_hash(asm, last_array_value, :rcx, ruby2_exit) # clobbers :rax
+
+ asm.comment('Push arguments from array')
+ array_opnd = ctx.stack_pop(1)
+
+ if required_args > 0
+ # Load the address of the embedded array
+ # (struct RArray *)(obj)->as.ary
+ array_reg = :rax
+ asm.mov(array_reg, array_opnd)
+
+ # Conditionally load the address of the heap array
+ # (struct RArray *)(obj)->as.heap.ptr
+ flags_opnd = [array_reg, C.RBasic.offsetof(:flags)]
+ asm.test(flags_opnd, C::RARRAY_EMBED_FLAG)
+ heap_ptr_opnd = [array_reg, C.RArray.offsetof(:as, :heap, :ptr)]
+ # Load the address of the embedded array
+ # (struct RArray *)(obj)->as.ary
+ asm.lea(:rcx, [array_reg, C.RArray.offsetof(:as, :ary)])
+ asm.mov(:rax, heap_ptr_opnd)
+ asm.cmovnz(:rax, :rcx)
+ ary_opnd = :rax
+
+ (0...required_args).each do |i|
+ top = ctx.stack_push
+ asm.mov(:rcx, [ary_opnd, i * C.VALUE.size])
+ asm.mov(top, :rcx)
+ end
+
+ asm.comment('end push_each')
+ end
+ end
+
# CALLER_REMOVE_EMPTY_KW_SPLAT: Return CantCompile if not supported
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
@@ -4853,6 +4969,20 @@ module RubyVM::RJIT
asm.cmovz(len_reg, [array_reg, C.RArray.offsetof(:as, :heap, :len)])
end
+ # Generate RARRAY_CONST_PTR_TRANSIENT (part of RARRAY_AREF)
+ def jit_array_ptr(asm, array_reg, ary_opnd)
+ asm.comment('get array pointer for embedded or heap')
+
+ flags_opnd = [array_reg, C.RBasic.offsetof(:flags)]
+ asm.test(flags_opnd, C::RARRAY_EMBED_FLAG)
+ heap_ptr_opnd = [array_reg, C.RArray.offsetof(:as, :heap, :ptr)]
+ # Load the address of the embedded array
+ # (struct RArray *)(obj)->as.ary
+ asm.lea(array_reg, [array_reg, C.RArray.offsetof(:as, :ary)])
+ asm.mov(ary_opnd, heap_ptr_opnd)
+ asm.cmovnz(ary_opnd, array_reg)
+ end
+
def assert_equal(left, right)
if left != right
raise "'#{left.inspect}' was not '#{right.inspect}'"
diff --git a/rjit_c.h b/rjit_c.h
index 4039227480..619a6e05dc 100644
--- a/rjit_c.h
+++ b/rjit_c.h
@@ -22,6 +22,11 @@ RJIT_RUNTIME_COUNTERS(
send_args_splat_kw_splat,
send_args_splat,
+ send_args_splat_not_array,
+ send_args_splat_length_not_equal,
+ send_args_splat_opt_num,
+ send_args_splat_arity_error,
+ send_args_splat_ruby2_hash,
send_kw_splat,
send_kwarg,
send_klass_megamorphic,
diff --git a/rjit_c.rb b/rjit_c.rb
index 96b21f2b7d..271e614de1 100644
--- a/rjit_c.rb
+++ b/rjit_c.rb
@@ -386,6 +386,7 @@ module RubyVM::RJIT # :nodoc: all
C::RARRAY_EMBED_FLAG = Primitive.cexpr! %q{ SIZET2NUM(RARRAY_EMBED_FLAG) }
C::RARRAY_EMBED_LEN_MASK = Primitive.cexpr! %q{ SIZET2NUM(RARRAY_EMBED_LEN_MASK) }
C::RARRAY_EMBED_LEN_SHIFT = Primitive.cexpr! %q{ SIZET2NUM(RARRAY_EMBED_LEN_SHIFT) }
+ C::RHASH_PASS_AS_KEYWORDS = Primitive.cexpr! %q{ SIZET2NUM(RHASH_PASS_AS_KEYWORDS) }
C::RMODULE_IS_REFINEMENT = Primitive.cexpr! %q{ SIZET2NUM(RMODULE_IS_REFINEMENT) }
C::ROBJECT_EMBED = Primitive.cexpr! %q{ SIZET2NUM(ROBJECT_EMBED) }
C::RSTRUCT_EMBED_LEN_MASK = Primitive.cexpr! %q{ SIZET2NUM(RSTRUCT_EMBED_LEN_MASK) }
@@ -403,6 +404,7 @@ module RubyVM::RJIT # :nodoc: all
C::RUBY_SYMBOL_FLAG = Primitive.cexpr! %q{ SIZET2NUM(RUBY_SYMBOL_FLAG) }
C::RUBY_T_ARRAY = Primitive.cexpr! %q{ SIZET2NUM(RUBY_T_ARRAY) }
C::RUBY_T_CLASS = Primitive.cexpr! %q{ SIZET2NUM(RUBY_T_CLASS) }
+ C::RUBY_T_HASH = Primitive.cexpr! %q{ SIZET2NUM(RUBY_T_HASH) }
C::RUBY_T_ICLASS = Primitive.cexpr! %q{ SIZET2NUM(RUBY_T_ICLASS) }
C::RUBY_T_MASK = Primitive.cexpr! %q{ SIZET2NUM(RUBY_T_MASK) }
C::RUBY_T_MODULE = Primitive.cexpr! %q{ SIZET2NUM(RUBY_T_MODULE) }
@@ -1274,6 +1276,11 @@ module RubyVM::RJIT # :nodoc: all
rjit_insns_count: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), rjit_insns_count)")],
send_args_splat_kw_splat: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_args_splat_kw_splat)")],
send_args_splat: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_args_splat)")],
+ send_args_splat_not_array: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_args_splat_not_array)")],
+ send_args_splat_length_not_equal: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_args_splat_length_not_equal)")],
+ send_args_splat_opt_num: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_args_splat_opt_num)")],
+ send_args_splat_arity_error: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_args_splat_arity_error)")],
+ send_args_splat_ruby2_hash: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_args_splat_ruby2_hash)")],
send_kw_splat: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_kw_splat)")],
send_kwarg: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_kwarg)")],
send_klass_megamorphic: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_klass_megamorphic)")],
diff --git a/tool/rjit/bindgen.rb b/tool/rjit/bindgen.rb
index 66d45af361..e9994feaa2 100755
--- a/tool/rjit/bindgen.rb
+++ b/tool/rjit/bindgen.rb
@@ -428,6 +428,7 @@ generator = BindingGenerator.new(
RUBY_T_ARRAY
RUBY_T_CLASS
RUBY_T_ICLASS
+ RUBY_T_HASH
RUBY_T_MASK
RUBY_T_MODULE
RUBY_T_STRING
@@ -480,6 +481,7 @@ generator = BindingGenerator.new(
VM_SPECIAL_OBJECT_VMCORE
RUBY_ENCODING_MASK
RUBY_FL_FREEZE
+ RHASH_PASS_AS_KEYWORDS
],
},
values: {