Skip to content

Commit a077b7e

Browse files
committed
RJIT: Support rest args
1 parent 87dc06e commit a077b7e

File tree

5 files changed

+150
-3
lines changed

5 files changed

+150
-3
lines changed

lib/ruby_vm/rjit/compiler.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module RubyVM::RJIT
2727
CFP = :r15
2828
SP = :rbx
2929

30-
# Scratch registers: rax, rcx
30+
# Scratch registers: rax, rcx, rdx
3131

3232
# Mark objects in this Array during GC
3333
GC_REFS = []

lib/ruby_vm/rjit/insn_compiler.rb

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4287,8 +4287,89 @@ def jit_call_iseq(jit, ctx, asm, cme, calling, iseq, frame_type: nil, prev_ep: n
42874287
end
42884288

42894289
if iseq_has_rest
4290-
asm.incr_counter(:send_iseq_has_rest)
4291-
return CantCompile
4290+
# We are going to allocate so setting pc and sp.
4291+
jit_save_pc(jit, asm) # clobbers rax
4292+
jit_save_sp(ctx, asm)
4293+
4294+
if flags & C::VM_CALL_ARGS_SPLAT != 0
4295+
non_rest_arg_count = argc - 1
4296+
# We start by dupping the array because someone else might have
4297+
# a reference to it.
4298+
array = ctx.stack_pop(1)
4299+
asm.mov(C_ARGS[0], array)
4300+
asm.call(C.rb_ary_dup)
4301+
array = C_RET
4302+
if non_rest_arg_count > required_num
4303+
# If we have more arguments than required, we need to prepend
4304+
# the items from the stack onto the array.
4305+
diff = (non_rest_arg_count - required_num)
4306+
4307+
# diff is >0 so no need to worry about null pointer
4308+
asm.comment('load pointer to array elements')
4309+
offset_magnitude = C.VALUE.size * diff
4310+
values_opnd = ctx.sp_opnd(-offset_magnitude)
4311+
values_ptr = :rcx
4312+
asm.lea(values_ptr, values_opnd)
4313+
4314+
asm.comment('prepend stack values to rest array')
4315+
asm.mov(C_ARGS[0], diff)
4316+
asm.mov(C_ARGS[1], values_ptr)
4317+
asm.mov(C_ARGS[2], array)
4318+
asm.call(C.rb_yjit_rb_ary_unshift_m)
4319+
ctx.stack_pop(diff)
4320+
4321+
stack_ret = ctx.stack_push
4322+
asm.mov(stack_ret, C_RET)
4323+
# We now should have the required arguments
4324+
# and an array of all the rest arguments
4325+
argc = required_num + 1
4326+
elsif non_rest_arg_count < required_num
4327+
# If we have fewer arguments than required, we need to take some
4328+
# from the array and move them to the stack.
4329+
diff = (required_num - non_rest_arg_count)
4330+
# This moves the arguments onto the stack. But it doesn't modify the array.
4331+
move_rest_args_to_stack(array, diff, jit, ctx, asm)
4332+
4333+
# We will now slice the array to give us a new array of the correct size
4334+
asm.mov(C_ARGS[0], array)
4335+
asm.mov(C_ARGS[1], diff)
4336+
asm.call(C.rjit_rb_ary_subseq_length)
4337+
stack_ret = ctx.stack_push
4338+
asm.mov(stack_ret, C_RET)
4339+
4340+
# We now should have the required arguments
4341+
# and an array of all the rest arguments
4342+
argc = required_num + 1
4343+
else
4344+
# The arguments are equal so we can just push to the stack
4345+
assert_equal(non_rest_arg_count, required_num)
4346+
stack_ret = ctx.stack_push
4347+
asm.mov(stack_ret, array)
4348+
end
4349+
else
4350+
assert_equal(true, argc >= required_num)
4351+
n = (argc - required_num)
4352+
argc = required_num + 1
4353+
# If n is 0, then elts is never going to be read, so we can just pass null
4354+
if n == 0
4355+
values_ptr = 0
4356+
else
4357+
asm.comment('load pointer to array elements')
4358+
offset_magnitude = C.VALUE.size * n
4359+
values_opnd = ctx.sp_opnd(-offset_magnitude)
4360+
values_ptr = :rcx
4361+
asm.lea(values_ptr, values_opnd)
4362+
end
4363+
4364+
asm.mov(C_ARGS[0], EC)
4365+
asm.mov(C_ARGS[1], n)
4366+
asm.mov(C_ARGS[2], values_ptr)
4367+
asm.call(C.rb_ec_ary_new_from_values)
4368+
4369+
ctx.stack_pop(n)
4370+
stack_ret = ctx.stack_push
4371+
asm.mov(stack_ret, C_RET)
4372+
end
42924373
end
42934374

42944375
if doing_kw_call
@@ -5017,6 +5098,49 @@ def jit_caller_setup_arg(jit, ctx, asm, flags, splat: false)
50175098
end
50185099
end
50195100

5101+
# Pushes arguments from an array to the stack. Differs from push splat because
5102+
# the array can have items left over.
5103+
# @param jit [RubyVM::RJIT::JITState]
5104+
# @param ctx [RubyVM::RJIT::Context]
5105+
# @param asm [RubyVM::RJIT::Assembler]
5106+
def move_rest_args_to_stack(array, num_args, jit, ctx, asm)
5107+
side_exit = side_exit(jit, ctx)
5108+
5109+
asm.comment('move_rest_args_to_stack')
5110+
5111+
# array is :rax
5112+
array_len_opnd = :rcx
5113+
jit_array_len(asm, array, array_len_opnd)
5114+
5115+
asm.comment('Side exit if length is less than required')
5116+
asm.cmp(array_len_opnd, num_args)
5117+
asm.jl(counted_exit(side_exit, :send_iseq_has_rest_and_splat_not_equal))
5118+
5119+
asm.comment('Push arguments from array')
5120+
5121+
# Load the address of the embedded array
5122+
# (struct RArray *)(obj)->as.ary
5123+
array_reg = array
5124+
5125+
# Conditionally load the address of the heap array
5126+
# (struct RArray *)(obj)->as.heap.ptr
5127+
flags_opnd = [array_reg, C.RBasic.offsetof(:flags)]
5128+
asm.test(flags_opnd, C::RARRAY_EMBED_FLAG)
5129+
heap_ptr_opnd = [array_reg, C.RArray.offsetof(:as, :heap, :ptr)]
5130+
# Load the address of the embedded array
5131+
# (struct RArray *)(obj)->as.ary
5132+
ary_opnd = :rdx # NOTE: array :rax is used after move_rest_args_to_stack too
5133+
asm.lea(:rcx, [array_reg, C.RArray.offsetof(:as, :ary)])
5134+
asm.mov(ary_opnd, heap_ptr_opnd)
5135+
asm.cmovnz(ary_opnd, :rcx)
5136+
5137+
num_args.times do |i|
5138+
top = ctx.stack_push
5139+
asm.mov(:rcx, [ary_opnd, i * C.VALUE.size])
5140+
asm.mov(top, :rcx)
5141+
end
5142+
end
5143+
50205144
# vm_caller_setup_arg_splat (+ CALLER_SETUP_ARG):
50215145
# Pushes arguments from an array to the stack that are passed with a splat (i.e. *args).
50225146
# It optimistically compiles to a static size that is the exact number of arguments needed for the function.

rjit_c.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,13 @@ rjit_str_simple_append(VALUE str1, VALUE str2)
176176
return rb_str_cat(str1, RSTRING_PTR(str2), RSTRING_LEN(str2));
177177
}
178178

179+
VALUE
180+
rjit_rb_ary_subseq_length(VALUE ary, long beg)
181+
{
182+
long len = RARRAY_LEN(ary);
183+
return rb_ary_subseq(ary, beg, len);
184+
}
185+
179186
// The code we generate in gen_send_cfunc() doesn't fire the c_return TracePoint event
180187
// like the interpreter. When tracing for c_return is enabled, we patch the code after
181188
// the C method return to call into this to fire the event.
@@ -507,6 +514,7 @@ extern VALUE rb_str_bytesize(VALUE str);
507514
extern const rb_callable_method_entry_t *rb_callable_method_entry_or_negative(VALUE klass, ID mid);
508515
extern VALUE rb_vm_yield_with_cfunc(rb_execution_context_t *ec, const struct rb_captured_block *captured, int argc, const VALUE *argv);
509516
extern VALUE rb_vm_set_ivar_id(VALUE obj, ID id, VALUE val);
517+
extern VALUE rb_yjit_rb_ary_unshift_m(int argc, VALUE *argv, VALUE ary);
510518

511519
#include "rjit_c.rbinc"
512520

rjit_c.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,10 @@ def C.rb_ary_clear
519519
Primitive.cexpr! %q{ SIZET2NUM((size_t)rb_ary_clear) }
520520
end
521521

522+
def C.rb_ary_dup
523+
Primitive.cexpr! %q{ SIZET2NUM((size_t)rb_ary_dup) }
524+
end
525+
522526
def C.rb_ary_entry_internal
523527
Primitive.cexpr! %q{ SIZET2NUM((size_t)rb_ary_entry_internal) }
524528
end
@@ -727,6 +731,10 @@ def C.rb_vm_yield_with_cfunc
727731
Primitive.cexpr! %q{ SIZET2NUM((size_t)rb_vm_yield_with_cfunc) }
728732
end
729733

734+
def C.rb_yjit_rb_ary_unshift_m
735+
Primitive.cexpr! %q{ SIZET2NUM((size_t)rb_yjit_rb_ary_unshift_m) }
736+
end
737+
730738
def C.rjit_full_cfunc_return
731739
Primitive.cexpr! %q{ SIZET2NUM((size_t)rjit_full_cfunc_return) }
732740
end
@@ -735,6 +743,10 @@ def C.rjit_optimized_call
735743
Primitive.cexpr! %q{ SIZET2NUM((size_t)rjit_optimized_call) }
736744
end
737745

746+
def C.rjit_rb_ary_subseq_length
747+
Primitive.cexpr! %q{ SIZET2NUM((size_t)rjit_rb_ary_subseq_length) }
748+
end
749+
738750
def C.rjit_record_exit_stack
739751
Primitive.cexpr! %q{ SIZET2NUM((size_t)rjit_record_exit_stack) }
740752
end

tool/rjit/bindgen.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,9 @@ def push_target(target)
563563
rb_str_dup
564564
rb_vm_yield_with_cfunc
565565
rb_vm_set_ivar_id
566+
rb_ary_dup
567+
rjit_rb_ary_subseq_length
568+
rb_yjit_rb_ary_unshift_m
566569
],
567570
types: %w[
568571
CALL_DATA

0 commit comments

Comments
 (0)