diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 8cb4432b6a..6d507e17d7 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -1210,6 +1210,18 @@ assert_equal 'foo123', %q{ make_str("foo", 123) } +# test invokebuiltin as used in struct assignment +assert_equal '123', %q{ + def foo(obj) + obj.foo = 123 + end + + struct = Struct.new(:foo) + obj = struct.new + foo(obj) + foo(obj) +} + # test invokebuiltin_delegate as used inside Dir.open assert_equal '.', %q{ def foo(path) diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb index 4672e83c9c..ed83bd681a 100644 --- a/test/ruby/test_yjit.rb +++ b/test/ruby/test_yjit.rb @@ -310,6 +310,19 @@ class TestYJIT < Test::Unit::TestCase RUBY end + def test_invokebuiltin + assert_compiles(<<~RUBY) + def foo(obj) + obj.foo = 123 + obj.bar = 123 + end + + Foo = Struct.new(:foo, :bar) + foo(Foo.new(123)) + foo(Foo.new(123)) + RUBY + end + def test_super_iseq assert_compiles(<<~'RUBY', insns: %i[invokesuper opt_plus opt_mult], result: 15) class A diff --git a/yjit_codegen.c b/yjit_codegen.c index 9f0029712d..c056b1c216 100644 --- a/yjit_codegen.c +++ b/yjit_codegen.c @@ -4136,6 +4136,39 @@ gen_getblockparamproxy(jitstate_t *jit, ctx_t *ctx) return YJIT_KEEP_COMPILING; } +static codegen_status_t +gen_invokebuiltin(jitstate_t *jit, ctx_t *ctx) +{ + const struct rb_builtin_function *bf = (struct rb_builtin_function *)jit_get_arg(jit, 0); + + if (bf->argc + 2 > NUM_C_ARG_REGS) { + return YJIT_CANT_COMPILE; + } + + // If the calls don't allocate, do they need up to date PC, SP? + jit_prepare_routine_call(jit, ctx, REG0); + + // Call the builtin func (ec, recv, arg1, arg2, ...) + mov(cb, C_ARG_REGS[0], REG_EC); + mov(cb, C_ARG_REGS[1], member_opnd(REG_CFP, rb_control_frame_t, self)); + + // Copy arguments from locals + for (int32_t i = 0; i < bf->argc; i++) { + x86opnd_t stack_opnd = ctx_stack_opnd(ctx, bf->argc - i - 1); + x86opnd_t c_arg_reg = C_ARG_REGS[2 + i]; + mov(cb, c_arg_reg, stack_opnd); + } + + call_ptr(cb, REG0, (void *)bf->func_ptr); + + // Push the return value + ctx_stack_pop(ctx, bf->argc); + x86opnd_t stack_ret = ctx_stack_push(ctx, TYPE_UNKNOWN); + mov(cb, stack_ret, RAX); + + return YJIT_KEEP_COMPILING; +} + // opt_invokebuiltin_delegate calls a builtin function, like // invokebuiltin does, but instead of taking arguments from the top of the // stack uses the argument locals (and self) from the current method. @@ -4145,7 +4178,7 @@ gen_opt_invokebuiltin_delegate(jitstate_t *jit, ctx_t *ctx) const struct rb_builtin_function *bf = (struct rb_builtin_function *)jit_get_arg(jit, 0); int32_t start_index = (int32_t)jit_get_arg(jit, 1); - if (bf->argc + 2 >= NUM_C_ARG_REGS) { + if (bf->argc + 2 > NUM_C_ARG_REGS) { return YJIT_CANT_COMPILE; } @@ -4387,6 +4420,7 @@ yjit_init_codegen(void) yjit_reg_op(BIN(opt_length), gen_opt_length); yjit_reg_op(BIN(opt_regexpmatch2), gen_opt_regexpmatch2); yjit_reg_op(BIN(opt_getinlinecache), gen_opt_getinlinecache); + yjit_reg_op(BIN(invokebuiltin), gen_invokebuiltin); yjit_reg_op(BIN(opt_invokebuiltin_delegate), gen_opt_invokebuiltin_delegate); yjit_reg_op(BIN(opt_invokebuiltin_delegate_leave), gen_opt_invokebuiltin_delegate); yjit_reg_op(BIN(opt_case_dispatch), gen_opt_case_dispatch);