RJIT: Initial support of splat

This commit is contained in:
Takashi Kokubun 2023-03-21 22:55:23 -07:00
Родитель 466aa8010f
Коммит 1dd65f7c55
5 изменённых файлов: 157 добавлений и 9 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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}'"

Просмотреть файл

@ -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,

Просмотреть файл

@ -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)")],

Просмотреть файл

@ -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: {