[前][次][番号順一覧][スレッド一覧]

ruby-changes:68901

From: Maxime <ko1@a...>
Date: Thu, 21 Oct 2021 08:13:05 +0900 (JST)
Subject: [ruby-changes:68901] 6a29131439 (master): Implement Ruby-to-Ruby calls in ujit (opt_send_without_block)

https://git.ruby-lang.org/ruby.git/commit/?id=6a29131439

From 6a29131439b88f696b54d4d732f5bffae9a56aac Mon Sep 17 00:00:00 2001
From: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@s...>
Date: Mon, 1 Feb 2021 16:57:38 -0500
Subject: Implement Ruby-to-Ruby calls in ujit (opt_send_without_block)

---
 ujit_codegen.c | 228 ++++++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 185 insertions(+), 43 deletions(-)

diff --git a/ujit_codegen.c b/ujit_codegen.c
index 3dd6f174ac..e5e7b90e59 100644
--- a/ujit_codegen.c
+++ b/ujit_codegen.c
@@ -854,50 +854,8 @@ gen_jump(jitstate_t* jit, ctx_t* ctx) https://github.com/ruby/ruby/blob/trunk/ujit_codegen.c#L854
 }
 
 static bool
-gen_opt_send_without_block(jitstate_t* jit, ctx_t* ctx)
+gen_opt_swb_cfunc(jitstate_t* jit, ctx_t* ctx, struct rb_call_data * cd, const rb_callable_method_entry_t *cme, int32_t argc)
 {
-    //fprintf(stderr, "gen_opt_send_without_block\n");
-
-    // Relevant definitions:
-    // rb_execution_context_t       : vm_core.h
-    // invoker, cfunc logic         : method.h, vm_method.c
-    // rb_callable_method_entry_t   : method.h
-    // vm_call_cfunc_with_frame     : vm_insnhelper.c
-    // rb_callcache                 : vm_callinfo.h
-
-    struct rb_call_data * cd = (struct rb_call_data *)jit_get_arg(jit, 0);
-    int32_t argc = (int32_t)vm_ci_argc(cd->ci);
-
-    // Don't JIT calls with keyword splat
-    if (vm_ci_flag(cd->ci) & VM_CALL_KW_SPLAT)
-    {
-        return false;
-    }
-
-    // Don't JIT calls that aren't simple
-    if (!(vm_ci_flag(cd->ci) & VM_CALL_ARGS_SIMPLE))
-    {
-        return false;
-    }
-
-    // Don't JIT if the inline cache is not set
-    if (!cd->cc || !cd->cc->klass) {
-        return false;
-    }
-
-    const rb_callable_method_entry_t *cme = vm_cc_cme(cd->cc);
-
-    // Don't JIT if the method entry is out of date
-    if (METHOD_ENTRY_INVALIDATED(cme)) {
-        return false;
-    }
-
-    // Don't JIT if this is not a C call
-    if (cme->def->type != VM_METHOD_TYPE_CFUNC)
-    {
-        return false;
-    }
-
     const rb_method_cfunc_t *cfunc = UNALIGNED_MEMBER_PTR(cme->def, body.cfunc);
 
     // Don't JIT if the argument count doesn't match
@@ -1106,6 +1064,190 @@ gen_opt_send_without_block(jitstate_t* jit, ctx_t* ctx) https://github.com/ruby/ruby/blob/trunk/ujit_codegen.c#L1064
     return true;
 }
 
+static bool
+gen_opt_swb_iseq(jitstate_t* jit, ctx_t* ctx, struct rb_call_data * cd, const rb_callable_method_entry_t *cme, int32_t argc)
+{
+    const rb_iseq_t *iseq = def_iseq_ptr(cme->def);
+    const VALUE* start_pc = iseq->body->iseq_encoded;
+    int num_params = iseq->body->param.size;
+    int num_locals = iseq->body->local_table_size - num_params;
+
+    rb_gc_register_mark_object((VALUE)iseq); // FIXME: intentional LEAK!
+
+    if (num_params != argc) {
+        //fprintf(stderr, "param argc mismatch\n");
+        return false;
+    }
+
+    // Create a size-exit to fall back to the interpreter
+    uint8_t* side_exit = ujit_side_exit(jit, ctx);
+
+    // Check for interrupts
+    // RUBY_VM_CHECK_INTS(ec)
+    mov(cb, REG0_32, member_opnd(REG_EC, rb_execution_context_t, interrupt_mask));
+    not(cb, REG0_32);
+    test(cb, member_opnd(REG_EC, rb_execution_context_t, interrupt_flag), REG0_32);
+    jnz_ptr(cb, side_exit);
+
+    // Points to the receiver operand on the stack
+    x86opnd_t recv = ctx_stack_opnd(ctx, argc);
+    mov(cb, REG0, recv);
+
+    // Callee method ID
+    //ID mid = vm_ci_mid(cd->ci);
+    //printf("JITting call to Ruby function \"%s\", argc: %d\n", rb_id2name(mid), argc);
+    //print_str(cb, "");
+    //print_str(cb, "recv");
+    //print_ptr(cb, recv);
+
+    // Check that the receiver is a heap object
+    test(cb, REG0, imm_opnd(RUBY_IMMEDIATE_MASK));
+    jnz_ptr(cb, side_exit);
+    cmp(cb, REG0, imm_opnd(Qfalse));
+    je_ptr(cb, side_exit);
+    cmp(cb, REG0, imm_opnd(Qnil));
+    je_ptr(cb, side_exit);
+
+    // Pointer to the klass field of the receiver &(recv->klass)
+    x86opnd_t klass_opnd = mem_opnd(64, REG0, offsetof(struct RBasic, klass));
+
+    assume_method_lookup_stable(cd->cc, cme, jit->block);
+
+    // Bail if receiver class is different from compile-time call cache class
+    mov(cb, REG1, imm_opnd(cd->cc->klass));
+    cmp(cb, klass_opnd, REG1);
+    jne_ptr(cb, side_exit);
+
+    // Store incremented PC into current control frame in case callee raises.
+    mov(cb, REG0, const_ptr_opnd(jit->pc + insn_len(BIN(opt_send_without_block))));
+    mov(cb, mem_opnd(64, REG_CFP, offsetof(rb_control_frame_t, pc)), REG0);
+
+    // Store the updated SP on the CFP (pop arguments and self)
+    lea(cb, REG0, ctx_sp_opnd(ctx, sizeof(VALUE) * -(argc + 1)));
+    mov(cb, member_opnd(REG_CFP, rb_control_frame_t, sp), REG0);
+
+    // Stack overflow check
+    // #define CHECK_VM_STACK_OVERFLOW0(cfp, sp, margin)
+    // REG_CFP <= REG_SP + 4 * sizeof(VALUE) + sizeof(rb_control_frame_t)
+    lea(cb, REG0, ctx_sp_opnd(ctx, sizeof(VALUE) * 4 + sizeof(rb_control_frame_t)));
+    cmp(cb, REG_CFP, REG0);
+    jle_ptr(cb, side_exit);
+    
+    // Adjust the callee's stack pointer
+    lea(cb, REG0, ctx_sp_opnd(ctx, sizeof(VALUE) * (3 + num_locals)));
+
+    // Initialize local variables to Qnil
+    for (int i=0; i < num_locals; i++) {
+        mov(cb, mem_opnd(64, REG0, sizeof(VALUE) * (i - num_locals - 3)), imm_opnd(Qnil));
+    }
+
+    // Put compile time cme into REG1. It's assumed to be valid because we are notified when
+    // any cme we depend on become outdated. See rb_ujit_method_lookup_change().
+    mov(cb, REG1, const_ptr_opnd(cme));
+    // Write method entry at sp[-3]
+    // sp[-3] = me;
+    mov(cb, mem_opnd(64, REG0, 8 * -3), REG1);
+
+    // Write block handler at sp[-2]
+    // sp[-2] = block_handler;
+    mov(cb, mem_opnd(64, REG0, 8 * -2), imm_opnd(VM_BLOCK_HANDLER_NONE));
+
+    // Write env flags at sp[-1]
+    // sp[-1] = frame_type;
+    uint64_t frame_type = VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL;
+    mov(cb, mem_opnd(64, REG0, 8 * -1), imm_opnd(frame_type));
+
+    // Allocate a new CFP (ec->cfp--)
+    sub(
+        cb,
+        member_opnd(REG_EC, rb_execution_context_t, cfp),
+        imm_opnd(sizeof(rb_control_frame_t))
+    );
+
+    // Setup the new frame
+    // *cfp = (const struct rb_control_frame_struct) {
+    //    .pc         = 0,
+    //    .sp         = sp,
+    //    .iseq       = 0,
+    //    .self       = recv,
+    //    .ep         = sp - 1,
+    //    .block_code = 0,
+    //    .__bp__     = sp,
+    // };
+    mov(cb, REG1, member_opnd(REG_EC, rb_execution_context_t, cfp));
+    mov(cb, member_opnd(REG1, rb_control_frame_t, block_code), imm_opnd(0));
+    mov(cb, member_opnd(REG1, rb_control_frame_t, sp), REG0);
+    mov(cb, member_opnd(REG1, rb_control_frame_t, __bp__), REG0);
+    sub(cb, REG0, imm_opnd(sizeof(VALUE)));
+    mov(cb, member_opnd(REG1, rb_control_frame_t, ep), REG0);
+    mov(cb, REG0, recv);
+    mov(cb, member_opnd(REG1, rb_control_frame_t, self), REG0);
+    mov(cb, REG0, const_ptr_opnd(iseq));
+    mov(cb, member_opnd(REG1, rb_control_frame_t, iseq), REG0);
+    mov(cb, REG0, const_ptr_opnd(start_pc));
+    mov(cb, member_opnd(REG1, rb_control_frame_t, pc), REG0);
+
+    //print_str(cb, "calling Ruby func:");
+    //print_str(cb, rb_id2name(mid));
+
+    // Write the post call bytes, exit to the interpreter
+    cb_write_post_call_bytes(cb);
+
+    return true;
+}
+
+static bool
+gen_opt_send_without_block(jitstate_t* jit, ctx_t* ctx)
+{
+    // Relevant definitions:
+    // rb_execution_context_t       : vm_core.h
+    // invoker, cfunc logic         : method.h, vm_method.c
+    // rb_callable_method_entry_t   : method.h
+    // vm_call_cfunc_with_frame     : vm_insnhelper.c
+    // rb_callcache                 : vm_callinfo.h
+
+    struct rb_call_data * cd = (struct rb_call_data *)jit_get_arg(jit, 0);
+    int32_t argc = (int32_t)vm_ci_argc(cd->ci);
+
+    // Don't JIT calls with keyword splat
+    if (vm_ci_flag(cd->ci) & VM_CALL_KW_SPLAT)
+    {
+        return false;
+    }
+
+    // Don't JIT calls that aren't simple
+    if (!(vm_ci_flag(cd->ci) & VM_CALL_ARGS_SIMPLE))
+    {
+        return false;
+    }
+
+    // Don't JIT if the inline cache is not set
+    if (!cd->cc || !cd->cc->klass) {
+        return false;
+    }
+
+    const rb_callable_method_entry_t *cme = vm_cc_cme(cd->cc);
+
+    // Don't JIT if the method entry is out of date
+    if (METHOD_ENTRY_INVALIDATED(cme)) {
+        return false;
+    }
+
+    // If this is a C call
+    if (cme->def->type == VM_METHOD_TYPE_CFUNC)
+    {
+        return gen_opt_swb_cfunc(jit, ctx, cd, cme, argc);
+    }
+
+    // If this is a Ruby call
+    if (cme->def->type == VM_METHOD_TYPE_ISEQ)
+    {
+        return gen_opt_swb_iseq(jit, ctx, cd, cme, argc);
+    }
+
+    return false;
+}
+
 static bool
 gen_leave(jitstate_t* jit, ctx_t* ctx)
 {
-- 
cgit v1.2.1


--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]