Add an optimized implementation for interface calls.
authorZoltan Varga <vargaz@gmail.com>
Sun, 3 Jan 2016 10:43:07 +0000 (05:43 -0500)
committerZoltan Varga <vargaz@gmail.com>
Sun, 3 Jan 2016 10:45:22 +0000 (05:45 -0500)
This works by using the imt entries in the vtable to store 'imt thunks', which in llvm-only mode, are C functions
which receive an info structure and the imt method and return the real method which needs to be called. Thus
interface calls are compiled into two calls: one to the imt thunk, and one to the method address returned by
the thunk.

mono/mini/jit-icalls.c
mono/mini/jit-icalls.h
mono/mini/method-to-ir.c
mono/mini/mini-runtime.c
mono/mini/mini-trampolines.c

index 7f451da92b0010b9d6d2d14b9242965d25d5cd50..a5e07593622d01958894ccfb4b0fa467c8b581b3 100644 (file)
@@ -1377,8 +1377,6 @@ resolve_iface_call (MonoObject *this_obj, int imt_slot, MonoMethod *imt_method,
                                                                                                        target, addr);
        }
 
-       *vtable_slot = addr;
-
        return addr;
 }
 
@@ -1412,26 +1410,19 @@ is_generic_method_definition (MonoMethod *m)
 }
 
 /*
- * mono_resolve_vcall:
+ * resolve_vcall:
  *
- *   Return the executable code for calling this_obj->vtable [slot].
+ *   Return the executable code for calling vt->vtable [slot].
  */
 static gpointer
-resolve_vcall (MonoObject *this_obj, int slot, MonoMethod *imt_method, gpointer *out_arg, gboolean gsharedvt)
+resolve_vcall (MonoVTable *vt, int slot, MonoMethod *imt_method, gpointer *out_arg, gboolean gsharedvt)
 {
-       MonoVTable *vt;
        MonoMethod *m, *generic_virtual = NULL;
        gpointer addr, compiled_method;
        gboolean need_unbox_tramp = FALSE;
 
        // FIXME: Optimize this
 
-       if (!this_obj)
-               /* The caller will handle it */
-               return NULL;
-
-       vt = this_obj->vtable;
-
        /* Same as in common_call_trampoline () */
 
        /* Avoid loading metadata or creating a generic vtable if possible */
@@ -1501,38 +1492,49 @@ resolve_vcall (MonoObject *this_obj, int slot, MonoMethod *imt_method, gpointer
 gpointer
 mono_resolve_vcall (MonoObject *this_obj, int slot, MonoMethod *imt_method, gpointer *out_rgctx_arg)
 {
-       return resolve_vcall (this_obj, slot, imt_method, out_rgctx_arg, FALSE);
+       g_assert (this_obj);
+
+       return resolve_vcall (this_obj->vtable, slot, imt_method, out_rgctx_arg, FALSE);
 }
 
 gpointer
 mono_resolve_vcall_gsharedvt (MonoObject *this_obj, int slot, MonoMethod *imt_method, gpointer *out_rgctx_arg)
 {
-       return resolve_vcall (this_obj, slot, imt_method, out_rgctx_arg, TRUE);
+       g_assert (this_obj);
+
+       return resolve_vcall (this_obj->vtable, 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)
+mono_init_vtable_slot_vt (MonoVTable *vtable, int slot)
 {
-       gpointer arg;
-       gpointer addr = resolve_vcall (this_obj, slot, NULL, &arg, FALSE);
+       gpointer arg = NULL;
+       gpointer addr;
        gpointer *ftnptr;
 
-       ftnptr = mono_domain_alloc0 (this_obj->vtable->domain, 2 * sizeof (gpointer));
+       addr = resolve_vcall (vtable, slot, NULL, &arg, FALSE);
+       ftnptr = mono_domain_alloc0 (vtable->domain, 2 * sizeof (gpointer));
        ftnptr [0] = addr;
        ftnptr [1] = arg;
        mono_memory_barrier ();
 
-       this_obj->vtable->vtable [slot] = ftnptr;
+       vtable->vtable [slot] = ftnptr;
 
        return ftnptr;
 }
 
+/*
+ * 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)
+{
+       return mono_init_vtable_slot_vt (this_obj->vtable, slot);
+}
+
 /*
  * mono_init_delegate:
  *
index ea460826e53f8bc419db4aaf9879efdcb7d5b0dd..c9380cb403d33841f182f769066b159338b3ed86 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_vt (MonoVTable *vtable, int slot);
+
 gpointer mono_init_vtable_slot (MonoObject *this_obj, int slot);
 
 void mono_init_delegate (MonoDelegate *del, MonoObject *target, MonoMethod *method);
index 0620ac7c075265ad30e5aac5f4f57e1d406b2e4e..c99820c5bbf478e19a4d9a18d832049203edd923 100644 (file)
@@ -146,10 +146,13 @@ MONO_API MonoInst* mono_emit_native_call (MonoCompile *cfg, gconstpointer func,
 
 static int inline_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, MonoInst **sp,
                                                  guchar *ip, guint real_offset, gboolean inline_always);
+static MonoInst*
+emit_llvmonly_virtual_call (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, int context_used, MonoInst **sp, MonoInst *imt_arg);
 
 /* helper methods signatures */
 static MonoMethodSignature *helper_sig_domain_get;
 static MonoMethodSignature *helper_sig_rgctx_lazy_fetch_trampoline;
+static MonoMethodSignature *helper_sig_llvmonly_imt_thunk;
 
 /*
  * Instruction metadata
@@ -354,6 +357,7 @@ mono_create_helper_signatures (void)
 {
        helper_sig_domain_get = mono_create_icall_signature ("ptr");
        helper_sig_rgctx_lazy_fetch_trampoline = mono_create_icall_signature ("ptr ptr");
+       helper_sig_llvmonly_imt_thunk = mono_create_icall_signature ("ptr ptr ptr");
 }
 
 static MONO_NEVER_INLINE void
@@ -2760,26 +2764,8 @@ mono_emit_method_call_full (MonoCompile *cfg, MonoMethod *method, MonoMethodSign
        if (!sig)
                sig = mono_method_signature (method);
 
-       if (cfg->llvm_only && (method->klass->flags & TYPE_ATTRIBUTE_INTERFACE)) {
-               MonoInst *icall_args [16];
-               MonoInst *ins;
-
-               // FIXME: Optimize this
-
-               guint32 imt_slot = mono_method_get_imt_slot (method);
-
-               icall_args [0] = this_ins;
-               EMIT_NEW_ICONST (cfg, icall_args [1], imt_slot);
-               if (imt_arg) {
-                       icall_args [2] = imt_arg;
-               } else {
-                       EMIT_NEW_AOTCONST (cfg, ins, MONO_PATCH_INFO_METHODCONST, method);
-                       icall_args [2] = ins;
-               }
-               EMIT_NEW_PCONST (cfg, icall_args [3], NULL);
-
-               call_target = mono_emit_jit_icall (cfg, mono_resolve_iface_call, icall_args);
-       }
+       if (cfg->llvm_only && (method->klass->flags & TYPE_ATTRIBUTE_INTERFACE))
+               g_assert_not_reached ();
 
        if (rgctx_arg) {
                rgctx_reg = mono_alloc_preg (cfg);
@@ -2813,37 +2799,8 @@ mono_emit_method_call_full (MonoCompile *cfg, MonoMethod *method, MonoMethodSign
        }
 #endif
 
-       if (cfg->llvm_only && !call_target && virtual_ && (method->flags & METHOD_ATTRIBUTE_VIRTUAL)) {
-               // FIXME: Vcall optimizations below
-               MonoInst *icall_args [16];
-               MonoInst *ins;
-               int rgctx_reg;
-
-               if (sig->generic_param_count) {
-                       /*
-                        * Generic virtual call, pass the concrete method as the imt argument.
-                        */
-                       imt_arg = emit_get_rgctx_method (cfg, context_used,
-                                                                                        method, MONO_RGCTX_INFO_METHOD);
-               }
-
-               // FIXME: Optimize this
-
-               int slot = mono_method_get_vtable_index (method);
-
-               icall_args [0] = this_ins;
-               EMIT_NEW_ICONST (cfg, icall_args [1], slot);
-               if (imt_arg) {
-                       icall_args [2] = imt_arg;
-               } else {
-                       EMIT_NEW_PCONST (cfg, ins, NULL);
-                       icall_args [2] = ins;
-               }
-               rgctx_reg = alloc_preg (cfg);
-               MONO_EMIT_NEW_PCONST (cfg, rgctx_reg, NULL);
-               EMIT_NEW_VARLOADA_VREG (cfg, icall_args [3], rgctx_reg, &mono_defaults.int_class->byval_arg);
-               call_target = mono_emit_jit_icall (cfg, mono_resolve_vcall, icall_args);
-       }
+       if (cfg->llvm_only && !call_target && virtual_ && (method->flags & METHOD_ATTRIBUTE_VIRTUAL))
+               return emit_llvmonly_virtual_call (cfg, method, sig, 0, args, NULL);
 
        need_unbox_trampoline = method->klass == mono_defaults.object_class || (method->klass->flags & TYPE_ATTRIBUTE_INTERFACE);
 
@@ -7663,6 +7620,8 @@ emit_llvmonly_virtual_call (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSig
 
        MONO_EMIT_NULL_CHECK (cfg, sp [0]->dreg);
 
+       // FIXME: Pass a vtable to the icalls
+
        if (is_iface)
                slot = mono_method_get_imt_slot (cmethod);
        else
@@ -7711,6 +7670,44 @@ emit_llvmonly_virtual_call (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSig
                return emit_extra_arg_calli (cfg, fsig, sp, arg_reg, call_target);
        }
 
+       if (!fsig->generic_param_count && is_iface && !imt_arg && !(cfg->gsharedvt && mini_is_gsharedvt_variable_signature (fsig))) {
+               /*
+                * A simple interface call
+                *
+                * We make a call through an imt slot to obtain the function descriptor we need to call.
+                * The imt slot contains a function descriptor for a runtime function + arg.
+                * The slot is already initialized when the vtable is created so there is no need
+                * to check it here.
+                */
+               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;
+               MonoInst *thunk_addr_ins, *thunk_arg_ins, *ftndesc_ins;
+
+               MONO_EMIT_NEW_LOAD_MEMBASE (cfg, vtable_reg, this_reg, MONO_STRUCT_OFFSET (MonoObject, vtable));
+               offset = ((gint32)slot - MONO_IMT_SIZE) * SIZEOF_VOID_P;
+
+               /* Load the imt slot, which contains a function descriptor. */
+               MONO_EMIT_NEW_LOAD_MEMBASE (cfg, slot_reg, vtable_reg, offset);
+
+               /* Load the address + arg of the imt thunk from the imt slot */
+               EMIT_NEW_LOAD_MEMBASE (cfg, thunk_addr_ins, OP_LOAD_MEMBASE, addr_reg, slot_reg, 0);
+               EMIT_NEW_LOAD_MEMBASE (cfg, thunk_arg_ins, OP_LOAD_MEMBASE, arg_reg, slot_reg, SIZEOF_VOID_P);
+               /*
+                * IMT thunks in llvm-only mode are C functions which take an info argument
+                * plus the imt method and return the ftndesc to call.
+                */
+               icall_args [0] = thunk_arg_ins;
+               EMIT_NEW_AOTCONST (cfg, ins, MONO_PATCH_INFO_METHODCONST, cmethod);
+               icall_args [1] = ins;
+               ftndesc_ins = mono_emit_calli (cfg, helper_sig_llvmonly_imt_thunk, icall_args, thunk_addr_ins, NULL, NULL);
+
+               return emit_llvmonly_calli (cfg, fsig, sp, ftndesc_ins);
+       }
+
        // FIXME: Optimize this
 
        icall_args [0] = sp [0];
index 1c5d1a6c644f8d665538712550ebb0841eb5643b..a82179e296e4420f93c6bf399580456ba5913818 100644 (file)
@@ -1830,6 +1830,12 @@ mono_jit_map_is_enabled (void)
 
 #endif
 
+static void
+no_gsharedvt_in_wrapper (void)
+{
+       g_assert_not_reached ();
+}
+
 static gpointer
 mono_jit_compile_method_with_opt (MonoMethod *method, guint32 opt, MonoException **ex)
 {
@@ -1929,6 +1935,19 @@ mono_jit_compile_method_with_opt (MonoMethod *method, guint32 opt, MonoException
                code = mono_jit_compile_method_inner (method, target_domain, opt, ex);
 
        if (!code && mono_llvm_only) {
+               if (method->wrapper_type == MONO_WRAPPER_UNKNOWN) {
+                       WrapperInfo *info = mono_marshal_get_wrapper_info (method);
+
+                       if (info->subtype == WRAPPER_SUBTYPE_GSHAREDVT_IN_SIG) {
+                               /*
+                                * These wrappers are only created for signatures which are in the program, but
+                                * sometimes we load methods too eagerly and have to create them even if they
+                                * will never be called.
+                                */
+                               return no_gsharedvt_in_wrapper;
+                       }
+               }
+
                printf ("AOT method not found in llvmonly mode: %s\n", mono_method_full_name (method, 1));
                g_assert_not_reached ();
        }
@@ -2507,6 +2526,164 @@ mono_jit_runtime_invoke (MonoMethod *method, void *obj, void **params, MonoObjec
        }
 }
 
+typedef struct {
+       MonoVTable *vtable;
+       int slot;
+} IMTThunkInfo;
+
+typedef gpointer (*IMTThunkFunc) (gpointer *arg, MonoMethod *imt_method);
+
+/*
+ * mini_llvmonly_initial_imt_thunk:
+ *
+ *  This function is called the first time a call is made through an IMT thunk.
+ * It should have the same signature as the mono_llvmonly_imt_thunk_... functions.
+ */
+static gpointer
+mini_llvmonly_initial_imt_thunk (gpointer *arg, MonoMethod *imt_method)
+{
+       IMTThunkInfo *info = (IMTThunkInfo*)arg;
+       gpointer *imt;
+       gpointer *ftndesc;
+       IMTThunkFunc func;
+
+       mono_vtable_build_imt_slot (info->vtable, info->slot);
+
+       imt = (gpointer*)info->vtable;
+       imt -= MONO_IMT_SIZE;
+
+       /* Return what the real IMT thunk returns */
+       ftndesc = imt [info->slot];
+       func = ftndesc [0];
+       return func ((gpointer *)ftndesc [1], imt_method);
+}
+
+/* This is called indirectly through an imt slot. */
+static gpointer
+mono_llvmonly_imt_thunk (gpointer *arg, MonoMethod *imt_method)
+{
+       int i = 0;
+
+       /* arg points to an array created in mono_llvmonly_get_imt_thunk () */
+       while (arg [i] && arg [i] != imt_method)
+               i += 2;
+       g_assert (arg [i]);
+
+       return arg [i + 1];
+}
+
+/* Optimized versions of mono_llvmonly_imt_thunk () for different table sizes */
+static gpointer
+mono_llvmonly_imt_thunk_1 (gpointer *arg, MonoMethod *imt_method)
+{
+       //g_assert (arg [0] == imt_method);
+       return arg [1];
+}
+
+static gpointer
+mono_llvmonly_imt_thunk_2 (gpointer *arg, MonoMethod *imt_method)
+{
+       //g_assert (arg [0] == imt_method || arg [2] == imt_method);
+       if (arg [0] == imt_method)
+               return arg [1];
+       else
+               return arg [3];
+}
+
+static gpointer
+mono_llvmonly_imt_thunk_3 (gpointer *arg, MonoMethod *imt_method)
+{
+       //g_assert (arg [0] == imt_method || arg [2] == imt_method || arg [4] == imt_method);
+       if (arg [0] == imt_method)
+               return arg [1];
+       else if (arg [2] == imt_method)
+               return arg [3];
+       else
+               return arg [5];
+}
+
+static gpointer
+mono_llvmonly_get_imt_thunk (MonoVTable *vtable, MonoDomain *domain, MonoIMTCheckItem **imt_entries, int count, gpointer fail_tramp)
+{
+       gpointer *buf;
+       gpointer *res;
+       int i, index, real_count;
+
+       /*
+        * Create an array which is passed to the imt thunk functions.
+        * The array contains MonoMethod-function descriptor pairs, terminated by a NULL entry.
+        */
+
+       real_count = 0;
+       for (i = 0; i < count; ++i) {
+               MonoIMTCheckItem *item = imt_entries [i];
+
+               if (item->has_target_code)
+                       continue;
+
+               if (item->is_equals)
+                       real_count ++;
+       }
+
+       /*
+        * Initialize all vtable entries reachable from this imt slot, so the compiled
+        * code doesn't have to check it.
+        */
+       for (i = 0; i < count; ++i) {
+               MonoIMTCheckItem *item = imt_entries [i];
+               int vt_slot;
+
+               if (!item->is_equals || item->has_target_code)
+                       continue;
+               vt_slot = item->value.vtable_slot;
+               mono_init_vtable_slot_vt (vtable, vt_slot);
+       }
+
+       /* Save the entries into an array */
+       buf = (void **)mono_domain_alloc (domain, (real_count + 1) * 2 * sizeof (gpointer));
+       index = 0;
+       for (i = 0; i < count; ++i) {
+               MonoIMTCheckItem *item = imt_entries [i];
+
+               if (!item->is_equals || item->has_target_code)
+                       continue;
+
+               g_assert (item->key);
+               g_assert (!item->has_target_code);
+               g_assert (vtable->vtable [item->value.vtable_slot]);
+
+               buf [(index * 2)] = item->key;
+               buf [(index * 2) + 1] = vtable->vtable [item->value.vtable_slot];
+               index ++;
+       }
+       buf [(index * 2)] = NULL;
+       buf [(index * 2) + 1] = fail_tramp;
+
+       /*
+        * Return a function descriptor for a C function with 'buf' as its argument.
+        * It will by called by JITted code.
+        */
+       res = (void **)mono_domain_alloc (domain, 2 * sizeof (gpointer));
+       // FIXME: Add more special cases
+       switch (real_count) {
+       case 1:
+               res [0] = mono_llvmonly_imt_thunk_1;
+               break;
+       case 2:
+               res [0] = mono_llvmonly_imt_thunk_2;
+               break;
+       case 3:
+               res [0] = mono_llvmonly_imt_thunk_3;
+               break;
+       default:
+               res [0] = mono_llvmonly_imt_thunk;
+               break;
+       }
+       res [1] = buf;
+
+       return res;
+}
+
 MONO_SIG_HANDLER_FUNC (, mono_sigfpe_signal_handler)
 {
        MonoException *exc = NULL;
@@ -2714,12 +2891,20 @@ mini_get_vtable_trampoline (MonoVTable *vt, int slot_index)
        int index = slot_index + MONO_IMT_SIZE;
 
        if (mono_llvm_only) {
-               /* Not used */
-               if (slot_index < 0)
-                       /* The vtable/imt construction code in object.c depends on this being non-NULL */
-                       return no_imt_trampoline;
-               else
+               if (slot_index < 0) {
+                       /* Initialize the IMT thunks to a 'trampoline' so the generated code doesn't have to initialize it */
+                       // FIXME: Memory management
+                       gpointer *ftndesc = g_malloc (2 * sizeof (gpointer));
+                       IMTThunkInfo *info = g_new0 (IMTThunkInfo, 1);
+                       info->vtable = vt;
+                       info->slot = index;
+                       ftndesc [0] = mini_llvmonly_initial_imt_thunk;
+                       ftndesc [1] = info;
+                       mono_memory_barrier ();
+                       return ftndesc;
+               } else {
                        return NULL;
+               }
        }
 
        g_assert (slot_index >= - MONO_IMT_SIZE);
@@ -2758,6 +2943,9 @@ mini_get_imt_trampoline (MonoVTable *vt, int slot_index)
 static gboolean
 mini_imt_entry_inited (MonoVTable *vt, int imt_slot_index)
 {
+       if (mono_llvm_only)
+               return FALSE;
+
        gpointer *imt = (gpointer*)vt;
        imt -= MONO_IMT_SIZE;
 
@@ -3283,10 +3471,14 @@ mini_init (const char *filename, const char *runtime_version)
                mono_marshal_use_aot_wrappers (TRUE);
        }
 
-       if (mono_aot_only)
+       if (mono_llvm_only) {
+               mono_install_imt_thunk_builder (mono_llvmonly_get_imt_thunk);
+               mono_set_always_build_imt_thunks (TRUE);
+       } else if (mono_aot_only) {
                mono_install_imt_thunk_builder (mono_aot_get_imt_thunk);
-       else
+       } else {
                mono_install_imt_thunk_builder (mono_arch_build_imt_thunk);
+       }
 
        /*Init arch tls information only after the metadata side is inited to make sure we see dynamic appdomain tls keys*/
        mono_arch_finish_init ();
index 174d6f694ad1b5b92adf5ab4ecef810dc061b6c7..9d27890f16371b760fe915f75772741386068ea6 100644 (file)
@@ -182,7 +182,8 @@ mini_resolve_imt_method (MonoVTable *vt, gpointer *vtable_slot, MonoMethod *imt_
        /* We can only use the AOT compiled code if we don't require further processing */
        lookup_aot = !generic_virtual & !variant_iface;
 
-       mono_vtable_build_imt_slot (vt, mono_method_get_imt_slot (imt_method));
+       if (!mono_llvm_only)
+               mono_vtable_build_imt_slot (vt, mono_method_get_imt_slot (imt_method));
 
        if (imt_method->is_inflated && ((MonoMethodInflated*)imt_method)->context.method_inst) {
                MonoError error;