diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 47744efb73..a52ed8027d 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -2766,3 +2766,23 @@ assert_equal 'ok', %q{ foo(s) rescue :ok foo(s) rescue :ok } + +# File.join is a cfunc accepting variable arguments as a Ruby array (argc = -2) +assert_equal 'foo/bar', %q{ + def foo + File.join("foo", "bar") + end + + foo + foo +} + +# File.join is a cfunc accepting variable arguments as a Ruby array (argc = -2) +assert_equal '', %q{ + def foo + File.join() + end + + foo + foo +} diff --git a/yjit.c b/yjit.c index e4fa84d510..39ade5f1e2 100644 --- a/yjit.c +++ b/yjit.c @@ -69,7 +69,6 @@ YJIT_DECLARE_COUNTERS( send_missing_method, send_bmethod, send_refined_method, - send_cfunc_ruby_array_varg, send_cfunc_argc_mismatch, send_cfunc_toomany_args, send_cfunc_tracing, diff --git a/yjit_codegen.c b/yjit_codegen.c index c9a4404550..c8de630747 100644 --- a/yjit_codegen.c +++ b/yjit_codegen.c @@ -3244,12 +3244,6 @@ gen_send_cfunc(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const { const rb_method_cfunc_t *cfunc = UNALIGNED_MEMBER_PTR(cme->def, body.cfunc); - // If the function expects a Ruby array of arguments - if (cfunc->argc < 0 && cfunc->argc != -1) { - GEN_COUNTER_INC(cb, send_cfunc_ruby_array_varg); - return YJIT_CANT_COMPILE; - } - // If the argument count doesn't match if (cfunc->argc >= 0 && cfunc->argc != argc) { GEN_COUNTER_INC(cb, send_cfunc_argc_mismatch); @@ -3402,6 +3396,28 @@ gen_send_cfunc(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const lea(cb, C_ARG_REGS[1], ctx_stack_opnd(ctx, argc - 1)); mov(cb, C_ARG_REGS[2], ctx_stack_opnd(ctx, argc)); } + // Variadic method with Ruby array + if (cfunc->argc == -2) { + // Create a Ruby array from the arguments. + // + // This follows similar behaviour to vm_call_cfunc_with_frame() and + // call_cfunc_m2(). We use rb_ec_ary_new_from_values() instead of + // rb_ary_new4() since we have REG_EC available. + // + // Before getting here we will have set the new CFP in the EC, and the + // stack at CFP's SP will contain the values we are inserting into the + // Array, so they will be properly marked if we hit a GC. + + // rb_ec_ary_new_from_values(rb_execution_context_t *ec, long n, const VLAUE *elts) + mov(cb, C_ARG_REGS[0], REG_EC); + mov(cb, C_ARG_REGS[1], imm_opnd(argc)); + lea(cb, C_ARG_REGS[2], ctx_stack_opnd(ctx, argc - 1)); + call_ptr(cb, REG0, (void *)rb_ec_ary_new_from_values); + + // rb_file_s_join(VALUE recv, VALUE args) + mov(cb, C_ARG_REGS[0], ctx_stack_opnd(ctx, argc)); + mov(cb, C_ARG_REGS[1], RAX); + } // Pop the C function arguments from the stack (in the caller) ctx_stack_pop(ctx, argc + 1);