diff options
-rw-r--r-- | yjit.c | 7 | ||||
-rw-r--r-- | yjit/bindgen/src/main.rs | 1 | ||||
-rw-r--r-- | yjit/src/codegen.rs | 80 | ||||
-rw-r--r-- | yjit/src/cruby_bindings.inc.rs | 1 |
4 files changed, 66 insertions, 23 deletions
@@ -845,6 +845,13 @@ VALUE rb_yjit_rb_ary_unshift_m(int argc, VALUE *argv, VALUE ary); VALUE +rb_yjit_rb_ary_subseq_length(VALUE ary, long beg) +{ + long len = RARRAY_LEN(ary); + return rb_ary_subseq(ary, beg, len); +} + +VALUE rb_yarv_fix_mod_fix(VALUE recv, VALUE obj) { return rb_fix_mod_fix(recv, obj); diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 71f7725f6b..031573621f 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -137,6 +137,7 @@ fn main() { .allowlist_function("rb_ary_clear") .allowlist_function("rb_ary_dup") .allowlist_function("rb_yjit_rb_ary_unshift_m") + .allowlist_function("rb_yjit_rb_ary_subseq_length") // From internal/array.h .allowlist_function("rb_ec_ary_new_from_values") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 60e4847fe0..3daeb3de1d 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -5095,6 +5095,43 @@ fn get_array_ptr(asm: &mut Assembler, array_reg: Opnd) -> Opnd { asm.csel_nz(ary_opnd, heap_ptr_opnd) } +/// Pushes arguments from an array to the stack. Differs from push splat because +/// the array can have items left over. +fn move_rest_args_to_stack(array: Opnd, num_args: u32, ctx: &mut Context, asm: &mut Assembler, ocb: &mut OutlinedCb, side_exit: Target) { + asm.comment("move_rest_args_to_stack"); + + let array_len_opnd = get_array_len(asm, array); + + asm.comment("Side exit if length doesn't not equal remaining args"); + asm.cmp(array_len_opnd, num_args.into()); + asm.jbe(counted_exit!(ocb, side_exit, send_splatarray_length_not_equal)); + + asm.comment("Push arguments from array"); + + // Load the address of the embedded array + // (struct RArray *)(obj)->as.ary + let array_reg = asm.load(array); + + // Conditionally load the address of the heap array + // (struct RArray *)(obj)->as.heap.ptr + let flags_opnd = Opnd::mem(VALUE_BITS, array_reg, RUBY_OFFSET_RBASIC_FLAGS); + asm.test(flags_opnd, Opnd::UImm(RARRAY_EMBED_FLAG as u64)); + let heap_ptr_opnd = Opnd::mem( + (8 * size_of::<usize>()) as u8, + array_reg, + RUBY_OFFSET_RARRAY_AS_HEAP_PTR, + ); + // Load the address of the embedded array + // (struct RArray *)(obj)->as.ary + let ary_opnd = asm.lea(Opnd::mem(VALUE_BITS, array_reg, RUBY_OFFSET_RARRAY_AS_ARY)); + let ary_opnd = asm.csel_nz(ary_opnd, heap_ptr_opnd); + + for i in 0..num_args { + let top = ctx.stack_push(Type::Unknown); + asm.mov(top, Opnd::mem(64, ary_opnd, i as i32 * SIZEOF_VALUE_I32)); + } +} + /// 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. @@ -5355,23 +5392,6 @@ fn gen_send_iseq( let mut start_pc_offset = 0; let required_num = unsafe { get_iseq_body_param_lead_num(iseq) }; - // If we have a rest and a splat, we can take a shortcut if the - // number of non-splat arguments is equal to the number of required - // arguments. - // For example: - // def foo(a, b, *rest) - // foo(1, 2, *[3, 4]) - // In this case, we can just dup the splat array as the rest array. - // No need to move things around between the array and stack. - - let non_rest_arg_count = argc - 1; - if iseq_has_rest && flags & VM_CALL_ARGS_SPLAT != 0 && non_rest_arg_count != required_num { - if non_rest_arg_count < required_num { - gen_counter_incr!(asm, send_iseq_has_rest_and_splat_fewer); - return CantCompile; - } - } - // This struct represents the metadata about the caller-specified // keyword arguments. let kw_arg = unsafe { vm_ci_kwarg(ci) }; @@ -5830,9 +5850,9 @@ fn gen_send_iseq( gen_save_sp(asm, ctx); if flags & VM_CALL_ARGS_SPLAT != 0 { - // We guarded above that if there is a splat and rest - // the number of arguments lines up. - // So we are just going to dupe the array and push it onto the stack. + let non_rest_arg_count = argc - 1; + // We start by dupping the array because someone else might have + // a reference to it. let array = ctx.stack_pop(1); let array = asm.ccall( rb_ary_dup as *const u8, @@ -5856,14 +5876,28 @@ fn gen_send_iseq( ); ctx.stack_pop(diff as usize); + let stack_ret = ctx.stack_push(Type::TArray); + asm.mov(stack_ret, array); // We now should have the required arguments // and an array of all the rest arguments argc = required_num + 1; + } else if non_rest_arg_count < required_num { + // If we have fewer arguments than required, we need to take some + // from the array and move them to the stack. + let diff = (required_num - non_rest_arg_count) as u32; + // This moves the arguments onto the stack. But it doesn't modify the array. + move_rest_args_to_stack(array, diff, ctx, asm, ocb, side_exit); + + // We will now slice the array to give us a new array of the correct size + let ret = asm.ccall(rb_yjit_rb_ary_subseq_length as *const u8, vec![array, Opnd::UImm(diff as u64)]); let stack_ret = ctx.stack_push(Type::TArray); - asm.mov(stack_ret, array); + asm.mov(stack_ret, ret); + + // We now should have the required arguments + // and an array of all the rest arguments + argc = required_num + 1; } else { - // We exit on less than right now, this is only handling - // the case where they are equal + // The arguments are equal so we can just push to the stack assert!(non_rest_arg_count == required_num); let stack_ret = ctx.stack_push(Type::TArray); asm.mov(stack_ret, array); diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 0b847211de..18c519f325 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -1297,6 +1297,7 @@ extern "C" { argv: *mut VALUE, ary: VALUE, ) -> VALUE; + pub fn rb_yjit_rb_ary_subseq_length(ary: VALUE, beg: ::std::os::raw::c_long) -> VALUE; pub fn rb_yarv_fix_mod_fix(recv: VALUE, obj: VALUE) -> VALUE; pub fn rb_yjit_dump_iseq_loc(iseq: *const rb_iseq_t, insn_idx: u32); pub fn rb_FL_TEST(obj: VALUE, flags: VALUE) -> VALUE; |