[llvmonly] Optimize virtual calls by storing a function descriptor in vtable slots...
authorZoltan Varga <vargaz@gmail.com>
Sun, 3 Jan 2016 05:49:38 +0000 (00:49 -0500)
committerZoltan Varga <vargaz@gmail.com>
Sun, 3 Jan 2016 05:49:38 +0000 (00:49 -0500)
mono/mini/aot-compiler.c
mono/mini/jit-icalls.c
mono/mini/jit-icalls.h
mono/mini/method-to-ir.c
mono/mini/mini-llvm.c
mono/mini/mini-runtime.c
mono/mini/mini.h

index f70fbb6b27bdb32cfc3e4e5ece093dcb8d9fe04b..c073ec22cb5edac40f9d8b406fd90a67b88ca4a8 100644 (file)
@@ -9621,6 +9621,7 @@ static const char *preinited_jit_icalls[] = {
        "mono_aot_init_gshared_method_rgctx",
        "mono_llvm_throw_corlib_exception",
        "mono_resolve_vcall",
+       "mono_init_vtable_slot",
        "mono_helper_ldstr_mscorlib"
 };
 
index e7850d725a1bb851f5033ba8ac61060c3ebdc2b9..7f451da92b0010b9d6d2d14b9242965d25d5cd50 100644 (file)
@@ -1421,7 +1421,6 @@ resolve_vcall (MonoObject *this_obj, int slot, MonoMethod *imt_method, gpointer
 {
        MonoVTable *vt;
        MonoMethod *m, *generic_virtual = NULL;
-       gpointer *vtable_slot;
        gpointer addr, compiled_method;
        gboolean need_unbox_tramp = FALSE;
 
@@ -1433,18 +1432,12 @@ resolve_vcall (MonoObject *this_obj, int slot, MonoMethod *imt_method, gpointer
 
        vt = this_obj->vtable;
 
-       vtable_slot = &(vt->vtable [slot]);
-
        /* Same as in common_call_trampoline () */
 
        /* Avoid loading metadata or creating a generic vtable if possible */
        addr = mono_aot_get_method_from_vt_slot (mono_domain_get (), vt, slot);
-       if (addr && !vt->klass->valuetype) {
-               if (mono_domain_owns_vtable_slot (mono_domain_get (), vtable_slot))
-                       *vtable_slot = addr;
-
+       if (addr && !vt->klass->valuetype)
                return mono_create_ftnptr (mono_domain_get (), addr);
-       }
 
        m = mono_class_get_vtable_entry (vt->klass, slot);
 
@@ -1488,8 +1481,6 @@ resolve_vcall (MonoObject *this_obj, int slot, MonoMethod *imt_method, gpointer
 
        // FIXME: Unify this with mono_resolve_iface_call
 
-       *vtable_slot = addr;
-
        if (gsharedvt) {
                /*
                 * The callee uses the gsharedvt calling convention, have to add an out wrapper.
@@ -1519,6 +1510,29 @@ mono_resolve_vcall_gsharedvt (MonoObject *this_obj, int slot, MonoMethod *imt_me
        return resolve_vcall (this_obj, slot, imt_method, out_rgctx_arg, TRUE);
 }
 
+/*
+ * mono_init_vtable_slot:
+ *
+ *   Initialize slot SLOT of the vtable of THIS_OBJ.
+ * Return the contents of the vtable slot.
+ */
+gpointer
+mono_init_vtable_slot (MonoObject *this_obj, int slot)
+{
+       gpointer arg;
+       gpointer addr = resolve_vcall (this_obj, slot, NULL, &arg, FALSE);
+       gpointer *ftnptr;
+
+       ftnptr = mono_domain_alloc0 (this_obj->vtable->domain, 2 * sizeof (gpointer));
+       ftnptr [0] = addr;
+       ftnptr [1] = arg;
+       mono_memory_barrier ();
+
+       this_obj->vtable->vtable [slot] = ftnptr;
+
+       return ftnptr;
+}
+
 /*
  * mono_init_delegate:
  *
index bd7bd4ec7f917a2ff58be3cc2041b7e6c216b84d..ea460826e53f8bc419db4aaf9879efdcb7d5b0dd 100644 (file)
@@ -203,6 +203,8 @@ gpointer mono_resolve_iface_call_gsharedvt (MonoObject *this_obj, int imt_slot,
 
 gpointer mono_resolve_vcall_gsharedvt (MonoObject *this_obj, int imt_slot, MonoMethod *imt_method, gpointer *out_arg);
 
+gpointer mono_init_vtable_slot (MonoObject *this_obj, int slot);
+
 void mono_init_delegate (MonoDelegate *del, MonoObject *target, MonoMethod *method);
 
 void mono_init_delegate_virtual (MonoDelegate *del, MonoObject *target, MonoMethod *method);
index 4e73c57850bab1d385ac3cd5dd1b2c5973a49252..0620ac7c075265ad30e5aac5f4f57e1d406b2e4e 100644 (file)
@@ -7652,6 +7652,113 @@ emit_optimized_ldloca_ir (MonoCompile *cfg, unsigned char *ip, unsigned char *en
        return NULL;
 }
 
+static MonoInst*
+emit_llvmonly_virtual_call (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, int context_used, MonoInst **sp, MonoInst *imt_arg)
+{
+       MonoInst *icall_args [16];
+       MonoInst *call_target, *ins;
+       int arg_reg;
+       gboolean is_iface = cmethod->klass->flags & TYPE_ATTRIBUTE_INTERFACE;
+       guint32 slot;
+
+       MONO_EMIT_NULL_CHECK (cfg, sp [0]->dreg);
+
+       if (is_iface)
+               slot = mono_method_get_imt_slot (cmethod);
+       else
+               slot = mono_method_get_vtable_index (cmethod);
+
+       if (!fsig->generic_param_count && !is_iface && !imt_arg && !(cfg->gsharedvt && mini_is_gsharedvt_variable_signature (fsig))) {
+               /*
+                * The simplest case, a normal virtual call.
+                */
+               int this_reg = sp [0]->dreg;
+               int vtable_reg = alloc_preg (cfg);
+               int slot_reg = alloc_preg (cfg);
+               int addr_reg = alloc_preg (cfg);
+               int arg_reg = alloc_preg (cfg);
+               int offset;
+               MonoBasicBlock *non_null_bb;
+
+               MONO_EMIT_NEW_LOAD_MEMBASE (cfg, vtable_reg, this_reg, MONO_STRUCT_OFFSET (MonoObject, vtable));
+               offset = MONO_STRUCT_OFFSET (MonoVTable, vtable) + (slot * SIZEOF_VOID_P);
+
+               /* Load the vtable slot, which contains a function descriptor. */
+               MONO_EMIT_NEW_LOAD_MEMBASE (cfg, slot_reg, vtable_reg, offset);
+
+               NEW_BBLOCK (cfg, non_null_bb);
+
+               MONO_EMIT_NEW_BIALU_IMM (cfg, OP_COMPARE_IMM, -1, slot_reg, 0);
+               cfg->cbb->last_ins->flags |= MONO_INST_LIKELY;
+               MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_PBNE_UN, non_null_bb);
+
+               /* Slow path */
+               // FIXME: Make the wrapper use the preserveall cconv
+               // FIXME: Use one icall per slot for small slot numbers ?
+               icall_args [0] = sp [0];
+               EMIT_NEW_ICONST (cfg, icall_args [1], slot);
+               /* Make the icall return the vtable slot value to save some code space */
+               ins = mono_emit_jit_icall (cfg, mono_init_vtable_slot, icall_args);
+               ins->dreg = slot_reg;
+               MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_BR, non_null_bb);
+
+               /* Fastpath */
+               MONO_START_BB (cfg, non_null_bb);
+               /* Load the address + arg from the vtable slot */
+               EMIT_NEW_LOAD_MEMBASE (cfg, call_target, OP_LOAD_MEMBASE, addr_reg, slot_reg, 0);
+               MONO_EMIT_NEW_LOAD_MEMBASE (cfg, arg_reg, slot_reg, SIZEOF_VOID_P);
+
+               return emit_extra_arg_calli (cfg, fsig, sp, arg_reg, call_target);
+       }
+
+       // FIXME: Optimize this
+
+       icall_args [0] = sp [0];
+       EMIT_NEW_ICONST (cfg, icall_args [1], slot);
+
+       if (fsig->generic_param_count) {
+               /* virtual generic call */
+               g_assert (!imt_arg);
+               /* Same as the virtual generic case above */
+               imt_arg = emit_get_rgctx_method (cfg, context_used,
+                                                                                cmethod, MONO_RGCTX_INFO_METHOD);
+               icall_args [2] = imt_arg;
+       } else if (imt_arg) {
+               icall_args [2] = imt_arg;
+       } else {
+               EMIT_NEW_AOTCONST (cfg, ins, MONO_PATCH_INFO_METHODCONST, cmethod);
+               icall_args [2] = ins;
+       }
+
+       // FIXME: For generic virtual calls, avoid computing the rgctx twice
+
+       arg_reg = alloc_preg (cfg);
+       MONO_EMIT_NEW_PCONST (cfg, arg_reg, NULL);
+       EMIT_NEW_VARLOADA_VREG (cfg, icall_args [3], arg_reg, &mono_defaults.int_class->byval_arg);
+
+       if (cfg->gsharedvt && mini_is_gsharedvt_variable_signature (fsig)) {
+               /*
+                * We handle virtual calls made from gsharedvt methods here instead
+                * of the gsharedvt block above.
+                */
+               if (is_iface)
+                       call_target = mono_emit_jit_icall (cfg, mono_resolve_iface_call_gsharedvt, icall_args);
+               else
+                       call_target = mono_emit_jit_icall (cfg, mono_resolve_vcall_gsharedvt, icall_args);
+       } else {
+               if (is_iface)
+                       call_target = mono_emit_jit_icall (cfg, mono_resolve_iface_call, icall_args);
+               else
+                       call_target = mono_emit_jit_icall (cfg, mono_resolve_vcall, icall_args);
+       }
+
+       /*
+        * Pass the extra argument even if the callee doesn't receive it, most
+        * calling conventions allow this.
+        */
+       return emit_extra_arg_calli (cfg, fsig, sp, arg_reg, call_target);
+}
+
 static gboolean
 is_exception_class (MonoClass *klass)
 {
@@ -9733,66 +9840,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                         * Virtual calls in llvm-only mode.
                         */
                        if (cfg->llvm_only && virtual_ && cmethod && (cmethod->flags & METHOD_ATTRIBUTE_VIRTUAL)) {
-                               MonoInst *icall_args [16];
-                               MonoInst *call_target;
-                               int arg_reg;
-                               gboolean is_iface = cmethod->klass->flags & TYPE_ATTRIBUTE_INTERFACE;
-
-                               MONO_EMIT_NULL_CHECK (cfg, sp [0]->dreg);
-
-                               // FIXME: Optimize this
-
-                               guint32 slot;
-
-                               if (is_iface)
-                                       slot = mono_method_get_imt_slot (cmethod);
-                               else
-                                       slot = mono_method_get_vtable_index (cmethod);
-
-                               icall_args [0] = sp [0];
-                               EMIT_NEW_ICONST (cfg, icall_args [1], slot);
-
-                               if (fsig->generic_param_count) {
-                                       /* virtual generic call */
-                                       g_assert (!imt_arg);
-                                       /* Same as the virtual generic case above */
-                                       imt_arg = emit_get_rgctx_method (cfg, context_used,
-                                                                                                        cmethod, MONO_RGCTX_INFO_METHOD);
-                                       icall_args [2] = imt_arg;
-                               } else if (imt_arg) {
-                                       icall_args [2] = imt_arg;
-                               } else {
-                                       EMIT_NEW_AOTCONST (cfg, ins, MONO_PATCH_INFO_METHODCONST, cmethod);
-                                       icall_args [2] = ins;
-                               }
-
-                               // FIXME: For generic virtual calls, avoid computing the rgctx twice
-
-                               arg_reg = alloc_preg (cfg);
-                               MONO_EMIT_NEW_PCONST (cfg, arg_reg, NULL);
-                               EMIT_NEW_VARLOADA_VREG (cfg, icall_args [3], arg_reg, &mono_defaults.int_class->byval_arg);
-
-                               if (cfg->gsharedvt && mini_is_gsharedvt_variable_signature (fsig)) {
-                                       /*
-                                        * We handle virtual calls made from gsharedvt methods here instead
-                                        * of the gsharedvt block above.
-                                        */
-                                       if (is_iface)
-                                               call_target = mono_emit_jit_icall (cfg, mono_resolve_iface_call_gsharedvt, icall_args);
-                                       else
-                                               call_target = mono_emit_jit_icall (cfg, mono_resolve_vcall_gsharedvt, icall_args);
-                               } else {
-                                       if (is_iface)
-                                               call_target = mono_emit_jit_icall (cfg, mono_resolve_iface_call, icall_args);
-                                       else
-                                               call_target = mono_emit_jit_icall (cfg, mono_resolve_vcall, icall_args);
-                               }
-
-                               /*
-                                * Pass the extra argument even if the callee doesn't receive it, most calling
-                                * calling conventions allow this.
-                                */
-                               ins = emit_extra_arg_calli (cfg, fsig, sp, arg_reg, call_target);
+                               ins = emit_llvmonly_virtual_call (cfg, cmethod, fsig, context_used, sp, imt_arg);
                                goto call_end;
                        }
 
index bbc8218a8a0ffa317b304e4c6a4c25ce433bafdd..5d3081deeeb130181b4ff91b927ec59655bc1c83 100644 (file)
@@ -3014,7 +3014,7 @@ process_call (EmitContext *ctx, MonoBasicBlock *bb, LLVMBuilderRef *builder_ref,
        gboolean vretaddr;
        LLVMTypeRef llvm_sig;
        gpointer target;
-       gboolean is_virtual, calli;
+       gboolean is_virtual, calli, preserveall;
        LLVMBuilderRef builder = *builder_ref;
 
        if (call->signature->call_convention != MONO_CALL_DEFAULT)
@@ -3034,6 +3034,8 @@ process_call (EmitContext *ctx, MonoBasicBlock *bb, LLVMBuilderRef *builder_ref,
 
        is_virtual = (ins->opcode == OP_VOIDCALL_MEMBASE || ins->opcode == OP_CALL_MEMBASE || ins->opcode == OP_VCALL_MEMBASE || ins->opcode == OP_LCALL_MEMBASE || ins->opcode == OP_FCALL_MEMBASE || ins->opcode == OP_RCALL_MEMBASE);
        calli = !call->fptr_is_patch && (ins->opcode == OP_VOIDCALL_REG || ins->opcode == OP_CALL_REG || ins->opcode == OP_VCALL_REG || ins->opcode == OP_LCALL_REG || ins->opcode == OP_FCALL_REG || ins->opcode == OP_RCALL_REG);
+       /* Unused */
+       preserveall = FALSE;
 
        /* FIXME: Avoid creating duplicate methods */
 
@@ -3294,6 +3296,8 @@ process_call (EmitContext *ctx, MonoBasicBlock *bb, LLVMBuilderRef *builder_ref,
        g_assert (!(call->rgctx_arg_reg && call->imt_arg_reg));
        if (!sig->pinvoke && !cfg->llvm_only)
                LLVMSetInstructionCallConv (lcall, LLVMMono1CallConv);
+       if (preserveall)
+               mono_llvm_set_call_preserveall_cc (lcall);
 
        if (cinfo->ret.storage == LLVMArgVtypeByRef)
                LLVMAddInstrAttribute (lcall, 1 + cinfo->vret_arg_pindex, LLVMStructRetAttribute);
@@ -4170,7 +4174,8 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb)
                case OP_LCOMPARE_IMM:
                case OP_COMPARE_IMM: {
                        CompRelation rel;
-                       LLVMValueRef cmp;
+                       LLVMValueRef cmp, args [16];
+                       gboolean likely = (ins->flags & MONO_INST_LIKELY) != 0;
 
                        if (ins->next->opcode == OP_NOP)
                                break;
@@ -4232,6 +4237,12 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb)
                        } else
                                cmp = LLVMBuildICmp (builder, cond_to_llvm_cond [rel], lhs, rhs, "");
 
+                       if (likely) {
+                               args [0] = cmp;
+                               args [1] = LLVMConstInt (LLVMInt1Type (), 1, FALSE);
+                               cmp = LLVMBuildCall (ctx->builder, LLVMGetNamedFunction (ctx->lmodule, "llvm.expect.i1"), args, 2, "");
+                       }
+
                        if (MONO_IS_COND_BRANCH_OP (ins->next)) {
                                if (ins->next->inst_true_bb == ins->next->inst_false_bb) {
                                        /*
@@ -7143,6 +7154,7 @@ add_intrinsics (LLVMModuleRef module)
        }
 
        AddFunc2 (module, "llvm.expect.i8", LLVMInt8Type (), LLVMInt8Type (), LLVMInt8Type ());
+       AddFunc2 (module, "llvm.expect.i1", LLVMInt1Type (), LLVMInt1Type (), LLVMInt1Type ());
 
        /* EH intrinsics */
        {
index b78a31d35c4b9fccf08478bb0e44171ff6c85bac..0871055e10a9c88146d151171157dcd19b7e691f 100644 (file)
@@ -2719,7 +2719,7 @@ mini_get_vtable_trampoline (int slot_index)
                        /* The vtable/imt construction code in object.c depends on this being non-NULL */
                        return no_imt_trampoline;
                else
-                       return no_vcall_trampoline;
+                       return NULL;
        }
 
        g_assert (slot_index >= - MONO_IMT_SIZE);
@@ -3585,6 +3585,8 @@ register_icalls (void)
        register_icall_no_wrapper (mono_resolve_vcall, "mono_resolve_vcall", "ptr object int ptr ptr");
        register_icall_no_wrapper (mono_resolve_iface_call_gsharedvt, "mono_resolve_iface_call_gsharedvt", "ptr object int ptr ptr");
        register_icall_no_wrapper (mono_resolve_vcall_gsharedvt, "mono_resolve_vcall_gsharedvt", "ptr object int ptr ptr");
+       /* This needs a wrapper so it can have a preserveall cconv */
+       register_icall (mono_init_vtable_slot, "mono_init_vtable_slot", "ptr object int", FALSE);
        register_icall (mono_init_delegate, "mono_init_delegate", "void object object ptr", TRUE);
        register_icall (mono_init_delegate_virtual, "mono_init_delegate_virtual", "void object object ptr", TRUE);
        register_icall (mono_get_assembly_object, "mono_get_assembly_object", "object ptr", TRUE);
index a5e335eb0a98ee2575f17ab550d495da906e4c25..98992e8b8c57b6da405e8dd8abfe2a0f98aab641 100644 (file)
@@ -983,7 +983,9 @@ enum {
         * Set on instructions during code emission which make calls, i.e. OP_CALL, OP_THROW.
         * backend.pc_offset will be set to the pc offset at the end of the native call instructions.
         */
-       MONO_INST_GC_CALLSITE = 128
+       MONO_INST_GC_CALLSITE = 128,
+       /* On comparisons, mark the branch following the condition as likely to be taken */
+       MONO_INST_LIKELY = 128,
 };
 
 #define inst_c0 data.op[0].const_val