[mono][aot] Optimize constrained calls made from gsharedvt methods. (#79339)
authorZoltan Varga <vargaz@gmail.com>
Wed, 8 Feb 2023 04:01:29 +0000 (23:01 -0500)
committerGitHub <noreply@github.com>
Wed, 8 Feb 2023 04:01:29 +0000 (23:01 -0500)
The calls are of the form:
.constrained T_GSHAREDVT
callvirt <method>

Whenever T_GSHAREDVT is a reference or value type is only known at runtime.

Previously these were handled by passing the arguments to a JIT icall which
computed the target method and did a runtime invoke.

Added 2 optimizations:
* Precompute the data which depends only on the type and the method,
  store it in an rgctx slot and pass it to the JIT icall.
* Add a fastpath for simpler cases which makes an indirect call
  from generated code.

src/mono/mono/metadata/icall-signatures.h
src/mono/mono/metadata/jit-icall-reg.h
src/mono/mono/mini/jit-icalls.c
src/mono/mono/mini/jit-icalls.h
src/mono/mono/mini/method-to-ir.c
src/mono/mono/mini/mini-generic-sharing.c
src/mono/mono/mini/mini-runtime.c
src/mono/mono/mini/mini.h

index 080d472..16b35ef 100644 (file)
@@ -275,6 +275,7 @@ ICALL_SIG (6, (ptr, ptr, ptr, ptr, ptr, ptr))       \
 ICALL_SIG (7, (int32, ptr, ptr, ptr, ptr, ptr, int32)) \
 ICALL_SIG (7, (void, ptr, ptr, ptr, ptr, ptr, ptr))    \
 ICALL_SIG (7, (ptr, ptr, ptr, ptr, ptr, ptr, ptr))     \
+ICALL_SIG (7, (object, ptr, ptr, ptr, ptr, ptr, ptr)) \
 ICALL_SIG (8, (void, ptr, ptr, int32, ptr, ptrref, ptr, ptrref))       \
 
 // ICALL_SIG_NAME: mono_icall_sig pasted with its parameters with underscores between each.
index 809a804..a3aac59 100644 (file)
@@ -338,6 +338,7 @@ MONO_JIT_ICALL (ves_icall_string_alloc) \
 MONO_JIT_ICALL (ves_icall_string_new_wrapper) \
 MONO_JIT_ICALL (ves_icall_thread_finish_async_abort) \
 MONO_JIT_ICALL (mono_marshal_lookup_pinvoke) \
+MONO_JIT_ICALL (mono_gsharedvt_constrained_call_fast) \
        \
 MONO_JIT_ICALL (count) \
 
index b68c4e4..93802f4 100644 (file)
@@ -1348,6 +1348,29 @@ mono_get_native_calli_wrapper (MonoImage *image, MonoMethodSignature *sig, gpoin
        return compiled_ptr;
 }
 
+gpointer
+mono_gsharedvt_constrained_call_fast (gpointer mp, MonoGsharedvtConstrainedCallInfo *info, gpointer *out_receiver)
+{
+       switch (info->call_type) {
+       case MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_VTYPE:
+               /* Calling a vtype method with a vtype receiver */
+               *out_receiver = mp;
+               return info->code;
+       case MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_REF:
+               /* Calling a ref method with a ref receiver */
+               *out_receiver = *(gpointer*)mp;
+               return info->code;
+       case MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_BOX: {
+               ERROR_DECL (error);
+               *out_receiver = mono_value_box_checked (info->klass, mp, error);
+               mono_error_assert_ok (error);
+               return info->code;
+       }
+       default:
+               return NULL;
+       }
+}
+
 static MonoMethod*
 constrained_gsharedvt_call_setup (gpointer mp, MonoMethod *cmethod, MonoClass *klass, gpointer *this_arg, MonoError *error)
 {
@@ -1427,7 +1450,8 @@ constrained_gsharedvt_call_setup (gpointer mp, MonoMethod *cmethod, MonoClass *k
  * MP is NULL if CMETHOD is a static virtual method.
  */
 MonoObject*
-mono_gsharedvt_constrained_call (gpointer mp, MonoMethod *cmethod, MonoClass *klass, guint8 *deref_args, gpointer *args)
+mono_gsharedvt_constrained_call (gpointer mp, MonoMethod *cmethod, MonoClass *klass,
+                                                                MonoGsharedvtConstrainedCallInfo *info, guint8 *deref_args, gpointer *args)
 {
        ERROR_DECL (error);
        MonoObject *o;
@@ -1435,26 +1459,40 @@ mono_gsharedvt_constrained_call (gpointer mp, MonoMethod *cmethod, MonoClass *kl
        gpointer this_arg;
        gpointer new_args [16];
 
-       /* Object.GetType () is an intrinsic under netcore */
-       if (!mono_class_is_ginst (cmethod->klass) && !cmethod->is_inflated && !strcmp (cmethod->name, "GetType")) {
-               MonoVTable *vt;
+       switch (info->call_type) {
+       case MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_VTYPE:
+               /* Calling a vtype method with a vtype receiver */
+               this_arg = mp;
+               m = info->method;
+               break;
+       case MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_REF:
+               /* Calling a ref method with a ref receiver */
+               this_arg = *(gpointer*)mp;
+               m = info->method;
+               break;
+       default:
+               /* Object.GetType () is an intrinsic under netcore */
+               if (!mono_class_is_ginst (cmethod->klass) && !cmethod->is_inflated && !strcmp (cmethod->name, "GetType")) {
+                       MonoVTable *vt;
+
+                       vt = mono_class_vtable_checked (klass, error);
+                       if (!is_ok (error)) {
+                               mono_error_set_pending_exception (error);
+                               return NULL;
+                       }
+                       return vt->type;
+               }
 
-               vt = mono_class_vtable_checked (klass, error);
+               m = constrained_gsharedvt_call_setup (mp, cmethod, klass, &this_arg, error);
                if (!is_ok (error)) {
                        mono_error_set_pending_exception (error);
                        return NULL;
                }
-               return vt->type;
-       }
-
-       m = constrained_gsharedvt_call_setup (mp, cmethod, klass, &this_arg, error);
-       if (!is_ok (error)) {
-               mono_error_set_pending_exception (error);
-               return NULL;
+               if (!m)
+                       return NULL;
+               break;
        }
 
-       if (!m)
-               return NULL;
        if (deref_args) {
                /* Have to deref gsharedvt ref arguments since the runtime invoke expects it */
                MonoMethodSignature *fsig = mono_method_signature_internal (m);
index fb38ae0..afd1093 100644 (file)
@@ -205,7 +205,10 @@ ICALL_EXPORT
 void
 ves_icall_mono_delegate_ctor_interp (MonoObject *this_obj, MonoObject *target, gpointer addr);
 
-ICALL_EXPORT MonoObject* mono_gsharedvt_constrained_call (gpointer mp, MonoMethod *cmethod, MonoClass *klass, guint8 *deref_args, gpointer *args);
+ICALL_EXPORT gpointer mono_gsharedvt_constrained_call_fast (gpointer mp, MonoGsharedvtConstrainedCallInfo *info, gpointer *out_receiver);
+
+ICALL_EXPORT MonoObject* mono_gsharedvt_constrained_call (gpointer mp, MonoMethod *cmethod, MonoClass *klass,
+                                                                                                                 MonoGsharedvtConstrainedCallInfo *info, guint8 *deref_args, gpointer *args);
 
 ICALL_EXPORT void mono_gsharedvt_value_copy (gpointer dest, gpointer src, MonoClass *klass);
 
index d7cb653..5e55944 100644 (file)
@@ -3787,11 +3787,32 @@ handle_constrained_gsharedvt_call (MonoCompile *cfg, MonoMethod *cmethod, MonoMe
        MonoInst *ins = NULL;
        gboolean emit_widen = *ref_emit_widen;
        gboolean supported;
+       MonoJumpInfoVirtMethod *info;
+       MonoJumpInfoRgctxEntry *entry;
+       MonoInst *call_info_ins;
+       int context_used;
+       MonoBasicBlock *end_bb = NULL, *slowpath_bb = NULL;
+       MonoInst *calls [2];
+       MonoInst *args [7];
+       MonoInst *orig_receiver = sp [0];
 
        /*
-        * Constrained calls need to behave differently at runtime dependending on whenever the receiver is instantiated as ref type or as a vtype.
-        * This is hard to do with the current call code, since we would have to emit a branch and two different calls. So instead, we
-        * pack the arguments into an array, and do the rest of the work in an icall.
+        * The calls are of the form:
+        * .constrained T_GSHAREDVT
+        * callvirt <method>
+        *
+        * There are 3 basic cases:
+        * - T is a vtype and the called method is a vtype method (ie. on T).
+        *   In this case a normal call is made.
+        * - T is a vtype, and the called method is a method on a reference type
+        *   (i.e. a method on Object/Valuetype/Enum)
+        *   In this case the receiver needs to be boxed.
+        * - T is a reference type.
+        *   In this case, it needs to be dereferenced (since its type is T&), and
+        *   a virtual call is made based on its runtime type.
+        *
+        * This is implemented by precomputing some data into an rgctx slot, then
+        * passing that data to jit icalls.
         */
        supported = ((cmethod->klass == mono_defaults.object_class) || mono_class_is_interface (cmethod->klass) || (!m_class_is_valuetype (cmethod->klass) && m_class_get_image (cmethod->klass) != mono_defaults.corlib));
        if (supported)
@@ -3807,101 +3828,161 @@ handle_constrained_gsharedvt_call (MonoCompile *cfg, MonoMethod *cmethod, MonoMe
                        }
                }
        }
-       if (supported) {
-               MonoInst *args [5];
+       if (!supported)
+               GSHAREDVT_FAILURE (CEE_CALLVIRT);
 
-               /*
-                * This case handles calls to
-                * - object:ToString()/Equals()/GetHashCode(),
-                * - System.IComparable<T>:CompareTo()
-                * - System.IEquatable<T>:Equals ()
-                * plus some simple interface calls enough to support AsyncTaskMethodBuilder.
-                */
+       /* rgctx entry containing precomputed data */
+       context_used = mono_method_check_context_used (cmethod) | mono_class_check_context_used (constrained_class);
+
+       info = (MonoJumpInfoVirtMethod *)mono_mempool_alloc0 (cfg->mempool, sizeof (MonoJumpInfoVirtMethod));
+       info->klass = constrained_class;
+       info->method = cmethod;
+
+       entry = mono_patch_info_rgctx_entry_new (cfg->mempool, cfg->method, context_used_is_mrgctx (cfg, context_used), MONO_PATCH_INFO_VIRT_METHOD, info, MONO_RGCTX_INFO_GSHAREDVT_CONSTRAINED_CALL_INFO);
+       call_info_ins = emit_rgctx_fetch (cfg, context_used, entry);
+
+       /*
+        * Fastpath: call mono_gsharedvt_constrained_call_fast, which returns
+        * both the boxed/unboxed etc. receiver and the address to call, then
+        * do an indirect call.
+        */
+       calls [0] = NULL;
+       // FIXME: Add more cases
+       if (fsig->hasthis && (fsig->ret->type == MONO_TYPE_VOID || MONO_TYPE_IS_PRIMITIVE (fsig->ret) || MONO_TYPE_IS_REFERENCE (fsig->ret)) && !mini_is_gsharedvt_signature (fsig)) {
+               /* Call mono_gsharedvt_constrained_call_fast (receiver, info, &new_receiver) */
+               args [0] = sp [0];
+               args [1] = call_info_ins;
+               int receiver_vreg = alloc_preg (cfg);
+               MONO_EMIT_NEW_PCONST (cfg, receiver_vreg, NULL);
+               EMIT_NEW_VARLOADA_VREG (cfg, args [2], receiver_vreg, mono_get_int_type ());
+
+               /* This returns the address/ftndesc to call */
+               MonoInst *code_ins = mono_emit_jit_icall (cfg, mono_gsharedvt_constrained_call_fast, args);
+
+               NEW_BBLOCK (cfg, end_bb);
+               NEW_BBLOCK (cfg, slowpath_bb);
+
+               /* If NULL, go to slowpath */
+               MONO_EMIT_NEW_BIALU_IMM (cfg, OP_COMPARE_IMM, -1, code_ins->dreg, 0);
+               MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_PBEQ, slowpath_bb);
+
+               /* Change the receiver to the new receiver returned by mono_gsharedvt_constrained_call_fast () */
+               int tmp_reg = alloc_preg (cfg);
+               EMIT_NEW_UNALU (cfg, ins, OP_MOVE, tmp_reg, receiver_vreg);
+               sp [0] = ins;
 
-               if (fsig->hasthis)
-                       args [0] = sp [0];
+               if (cfg->llvm_only)
+                       calls [0] = mini_emit_llvmonly_calli (cfg, fsig, sp, code_ins);
                else
-                       EMIT_NEW_PCONST (cfg, args [0], NULL);
-               args [1] = emit_get_rgctx_method (cfg, mono_method_check_context_used (cmethod), cmethod, MONO_RGCTX_INFO_METHOD);
-               args [2] = mini_emit_get_rgctx_klass (cfg, mono_class_check_context_used (constrained_class), constrained_class, MONO_RGCTX_INFO_KLASS);
-
-               /* !fsig->hasthis is for the wrapper for the Object.GetType () icall or static virtual methods */
-               if ((fsig->hasthis || m_method_is_static (cmethod)) && fsig->param_count) {
-                       /* Call mono_gsharedvt_constrained_call (gpointer mp, MonoMethod *cmethod, MonoClass *klass, gboolean *deref_args, gpointer *args) */
-                       gboolean has_gsharedvt = FALSE;
-                       for (int i = 0; i < fsig->param_count; ++i) {
-                               if (mini_is_gsharedvt_type (fsig->params [i]))
-                                       has_gsharedvt = TRUE;
-                       }
-                       /* Pass an array of bools which signal whenever the corresponding argument is a gsharedvt ref type */
-                       if (has_gsharedvt) {
-                               MONO_INST_NEW (cfg, ins, OP_LOCALLOC_IMM);
-                               ins->dreg = alloc_preg (cfg);
-                               ins->inst_imm = fsig->param_count;
-                               MONO_ADD_INS (cfg->cbb, ins);
-                               args [3] = ins;
-                       } else {
-                               EMIT_NEW_PCONST (cfg, args [3], 0);
-                       }
-                       /* Pass the arguments using a localloc-ed array using the format expected by runtime_invoke () */
+                       calls [0] = mini_emit_calli (cfg, fsig, sp, code_ins, NULL, NULL);
+
+               MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_BR, end_bb);
+
+               MONO_START_BB (cfg, slowpath_bb);
+       }
+
+       /*
+        * Slowpath: store the arguments to an array on the stack, then call
+        * mono_gsharedvt_constrained_call () which computes the target method and calls it using
+        * runtime invoke.
+        */
+       if (fsig->hasthis)
+               args [0] = orig_receiver;
+       else
+               EMIT_NEW_PCONST (cfg, args [0], NULL);
+       args [1] = emit_get_rgctx_method (cfg, mono_method_check_context_used (cmethod), cmethod, MONO_RGCTX_INFO_METHOD);
+       args [2] = mini_emit_get_rgctx_klass (cfg, mono_class_check_context_used (constrained_class), constrained_class, MONO_RGCTX_INFO_KLASS);
+       args [3] = call_info_ins;
+
+       MonoInst *is_gsharedvt_ins = NULL, *args_ins = NULL;
+
+       /* !fsig->hasthis is for the wrapper for the Object.GetType () icall or static virtual methods */
+       if ((fsig->hasthis || m_method_is_static (cmethod)) && fsig->param_count) {
+               /* Call mono_gsharedvt_constrained_call () */
+               gboolean has_gsharedvt = FALSE;
+               for (int i = 0; i < fsig->param_count; ++i) {
+                       if (mini_is_gsharedvt_type (fsig->params [i]))
+                               has_gsharedvt = TRUE;
+               }
+
+               /* Pass an array of bools which signal whenever the corresponding argument is a gsharedvt ref type */
+               if (has_gsharedvt) {
                        MONO_INST_NEW (cfg, ins, OP_LOCALLOC_IMM);
                        ins->dreg = alloc_preg (cfg);
-                       ins->inst_imm = fsig->param_count * sizeof (target_mgreg_t);
+                       ins->inst_imm = fsig->param_count;
                        MONO_ADD_INS (cfg->cbb, ins);
-                       args [4] = ins;
-
-                       for (int i = 0; i < fsig->param_count; ++i) {
-                               int addr_reg;
-
-                               if (mini_is_gsharedvt_type (fsig->params [i])) {
-                                       MonoInst *is_deref;
-                                       int deref_arg_reg;
-                                       ins = mini_emit_get_gsharedvt_info_klass (cfg, mono_class_from_mono_type_internal (fsig->params [i]), MONO_RGCTX_INFO_CLASS_BOX_TYPE);
-                                       deref_arg_reg = alloc_preg (cfg);
-                                       /* deref_arg = BOX_TYPE != MONO_GSHAREDVT_BOX_TYPE_VTYPE */
-                                       EMIT_NEW_BIALU_IMM (cfg, is_deref, OP_ISUB_IMM, deref_arg_reg, ins->dreg, 1);
-                                       MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI1_MEMBASE_REG, args [3]->dreg, i, is_deref->dreg);
-                               } else if (has_gsharedvt) {
-                                       MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI1_MEMBASE_IMM, args [3]->dreg, i, 0);
-                               }
-
-                               MonoInst *arg = sp [i + fsig->hasthis];
+                       is_gsharedvt_ins = ins;
+               } else {
+                       EMIT_NEW_PCONST (cfg, is_gsharedvt_ins, 0);
+               }
+               /* Pass the arguments using a localloc-ed array using the format expected by runtime_invoke () */
+               MONO_INST_NEW (cfg, ins, OP_LOCALLOC_IMM);
+               ins->dreg = alloc_preg (cfg);
+               ins->inst_imm = fsig->param_count * sizeof (target_mgreg_t);
+               MONO_ADD_INS (cfg->cbb, ins);
+               args_ins = ins;
 
-                               if (mini_is_gsharedvt_type (fsig->params [i]) || MONO_TYPE_IS_PRIMITIVE (fsig->params [i]) || MONO_TYPE_ISSTRUCT (fsig->params [i])) {
-                                       EMIT_NEW_VARLOADA_VREG (cfg, ins, arg->dreg, fsig->params [i]);
-                                       addr_reg = ins->dreg;
-                                       EMIT_NEW_STORE_MEMBASE (cfg, ins, OP_STORE_MEMBASE_REG, args [4]->dreg, i * sizeof (target_mgreg_t), addr_reg);
-                               } else {
-                                       EMIT_NEW_STORE_MEMBASE (cfg, ins, OP_STORE_MEMBASE_REG, args [4]->dreg, i * sizeof (target_mgreg_t), arg->dreg);
-                               }
+               for (int i = 0; i < fsig->param_count; ++i) {
+                       int addr_reg;
+
+                       if (mini_is_gsharedvt_type (fsig->params [i])) {
+                               MonoInst *is_deref;
+                               int deref_arg_reg;
+                               ins = mini_emit_get_gsharedvt_info_klass (cfg, mono_class_from_mono_type_internal (fsig->params [i]), MONO_RGCTX_INFO_CLASS_BOX_TYPE);
+                               deref_arg_reg = alloc_preg (cfg);
+                               /* deref_arg = BOX_TYPE != MONO_GSHAREDVT_BOX_TYPE_VTYPE */
+                               EMIT_NEW_BIALU_IMM (cfg, is_deref, OP_ISUB_IMM, deref_arg_reg, ins->dreg, 1);
+                               MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STOREI1_MEMBASE_REG, is_gsharedvt_ins->dreg, i, is_deref->dreg);
+                       } else if (has_gsharedvt) {
+                               MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STOREI1_MEMBASE_IMM, is_gsharedvt_ins->dreg, i, 0);
+                       }
+
+                       MonoInst *arg = sp [i + fsig->hasthis];
+                       if (mini_is_gsharedvt_type (fsig->params [i]) || MONO_TYPE_IS_PRIMITIVE (fsig->params [i]) || MONO_TYPE_ISSTRUCT (fsig->params [i])) {
+                               EMIT_NEW_VARLOADA_VREG (cfg, ins, arg->dreg, fsig->params [i]);
+                               addr_reg = ins->dreg;
+                               EMIT_NEW_STORE_MEMBASE (cfg, ins, OP_STORE_MEMBASE_REG, args_ins->dreg, i * sizeof (target_mgreg_t), addr_reg);
+                       } else {
+                               EMIT_NEW_STORE_MEMBASE (cfg, ins, OP_STORE_MEMBASE_REG, args_ins->dreg, i * sizeof (target_mgreg_t), arg->dreg);
                        }
-               } else {
-                       EMIT_NEW_ICONST (cfg, args [3], 0);
-                       EMIT_NEW_ICONST (cfg, args [4], 0);
-               }
-               ins = mono_emit_jit_icall (cfg, mono_gsharedvt_constrained_call, args);
-               emit_widen = FALSE;
-
-               if (mini_is_gsharedvt_type (fsig->ret)) {
-                       ins = handle_unbox_gsharedvt (cfg, mono_class_from_mono_type_internal (fsig->ret), ins);
-               } else if (MONO_TYPE_IS_PRIMITIVE (fsig->ret) || MONO_TYPE_ISSTRUCT (fsig->ret) || m_class_is_enumtype (mono_class_from_mono_type_internal (fsig->ret))) {
-                       MonoInst *add;
-
-                       /* Unbox */
-                       NEW_BIALU_IMM (cfg, add, OP_ADD_IMM, alloc_dreg (cfg, STACK_MP), ins->dreg, MONO_ABI_SIZEOF (MonoObject));
-                       MONO_ADD_INS (cfg->cbb, add);
-                       /* Load value */
-                       NEW_LOAD_MEMBASE_TYPE (cfg, ins, fsig->ret, add->dreg, 0);
-                       MONO_ADD_INS (cfg->cbb, ins);
-                       /* ins represents the call result */
                }
        } else {
-               GSHAREDVT_FAILURE (CEE_CALLVIRT);
+               EMIT_NEW_ICONST (cfg, is_gsharedvt_ins, 0);
+               EMIT_NEW_ICONST (cfg, args_ins, 0);
        }
 
+       args [4] = is_gsharedvt_ins;
+       args [5] = args_ins;
+
+       ins = mono_emit_jit_icall (cfg, mono_gsharedvt_constrained_call, args);
+       emit_widen = FALSE;
+
+       /* Unbox the return value */
+       if (mini_is_gsharedvt_type (fsig->ret)) {
+               ins = handle_unbox_gsharedvt (cfg, mono_class_from_mono_type_internal (fsig->ret), ins);
+       } else if (MONO_TYPE_IS_PRIMITIVE (fsig->ret) || MONO_TYPE_ISSTRUCT (fsig->ret) || m_class_is_enumtype (mono_class_from_mono_type_internal (fsig->ret))) {
+               MonoInst *add;
+
+               /* Unbox */
+               NEW_BIALU_IMM (cfg, add, OP_ADD_IMM, alloc_dreg (cfg, STACK_MP), ins->dreg, MONO_ABI_SIZEOF (MonoObject));
+               MONO_ADD_INS (cfg->cbb, add);
+               /* Load value */
+               NEW_LOAD_MEMBASE_TYPE (cfg, ins, fsig->ret, add->dreg, 0);
+               MONO_ADD_INS (cfg->cbb, ins);
+       }
+       calls [1] = ins;
+
+       /* Merge fastpath/slowpath */
+       if (slowpath_bb) {
+               MONO_EMIT_NEW_BRANCH_BLOCK (cfg, OP_BR, end_bb);
+               MONO_START_BB (cfg, end_bb);
+       }
+       if (calls [0] && fsig->ret->type != MONO_TYPE_VOID)
+               calls [0]->dreg = calls [1]->dreg;
+
        *ref_emit_widen = emit_widen;
 
-       return ins;
+       return calls [1];
 
  exception_exit:
        return NULL;
index 84a3ee1..6b81872 100644 (file)
@@ -703,7 +703,8 @@ inflate_info (MonoMemoryManager *mem_manager, MonoRuntimeGenericContextInfoTempl
        }
        case MONO_RGCTX_INFO_VIRT_METHOD:
        case MONO_RGCTX_INFO_VIRT_METHOD_CODE:
-       case MONO_RGCTX_INFO_VIRT_METHOD_BOX_TYPE: {
+       case MONO_RGCTX_INFO_VIRT_METHOD_BOX_TYPE:
+       case MONO_RGCTX_INFO_GSHAREDVT_CONSTRAINED_CALL_INFO: {
                MonoJumpInfoVirtMethod *info = (MonoJumpInfoVirtMethod *)data;
                MonoJumpInfoVirtMethod *res;
                MonoType *t;
@@ -2288,7 +2289,6 @@ instantiate_info (MonoMemoryManager *mem_manager, MonoRuntimeGenericContextInfoT
                g_assert (m_class_get_vtable (info->klass));
                method = m_class_get_vtable (info->klass) [ioffset + slot];
 
-
                if (info->method->is_inflated) {
                        MonoGenericContext *method_ctx = mono_method_get_context (info->method);
                        if (method_ctx->method_inst != NULL) {
@@ -2627,6 +2627,73 @@ instantiate_info (MonoMemoryManager *mem_manager, MonoRuntimeGenericContextInfoT
                g_assert (trampoline);
                return trampoline;
        }
+       case MONO_RGCTX_INFO_GSHAREDVT_CONSTRAINED_CALL_INFO: {
+               MonoJumpInfoVirtMethod *info = (MonoJumpInfoVirtMethod *)data;
+               MonoMethod *cmethod = info->method;
+               MonoMethod *m = NULL;
+               int vt_slot, iface_offset;
+               int call_type = MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_OTHER;
+               gpointer addr = NULL;
+
+               klass = info->klass;
+
+               if (mono_class_is_interface (klass) || (!m_class_is_valuetype (klass) && !m_class_is_sealed (klass))) {
+                       /*
+                        * The method that needs to be invoke depends on the actual class of the receiver, so it can only be
+                        * resolved at call time.
+                        */
+               } else if (!mono_method_signature_internal (cmethod)->pinvoke && m_method_is_virtual (cmethod)) {
+                       /* Lookup the virtual method */
+                       mono_class_setup_vtable (klass);
+                       g_assert (m_class_get_vtable (klass));
+                       vt_slot = mono_method_get_vtable_slot (cmethod);
+                       if (mono_class_is_interface (cmethod->klass)) {
+                               iface_offset = mono_class_interface_offset (klass, cmethod->klass);
+                               g_assert (iface_offset != -1);
+                               vt_slot += iface_offset;
+                       }
+                       m = m_class_get_vtable (klass) [vt_slot];
+                       if (cmethod->is_inflated) {
+                               m = mono_class_inflate_generic_method_full_checked (m, NULL, mono_method_get_context (cmethod), error);
+                               return_val_if_nok (error, NULL);
+                       }
+
+                       if (m_class_is_valuetype (klass) && (m->klass == mono_defaults.object_class || m->klass == m_class_get_parent (mono_defaults.enum_class) || m->klass == mono_defaults.enum_class)) {
+                               /* Calling a non-vtype method with a vtype receiver, has to box. */
+                               call_type = MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_BOX;
+                       } else if (m_class_is_valuetype (klass)) {
+                               /* Calling a vtype method with a vtype receiver */
+                               call_type = MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_VTYPE;
+                       } else {
+                               /* The class is sealed because of the check above, so we can resolve the method here */
+                               call_type = MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_REF;
+                       }
+               }
+
+               if (call_type != MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_OTHER) {
+                       if (mono_llvm_only) {
+                               gpointer arg = NULL;
+                               addr = mini_llvmonly_load_method (m, FALSE, FALSE, &arg, error);
+
+                               /* Returns an ftndesc */
+                               addr = mini_llvmonly_create_ftndesc (m, addr, arg);
+                       } else {
+                               addr = mono_compile_method_checked (m, error);
+                               return_val_if_nok (error, NULL);
+
+                               addr = mini_add_method_trampoline (m, addr, mono_method_needs_static_rgctx_invoke (m, FALSE), FALSE);
+                       }
+               }
+
+               // FIXME:
+               MonoGsharedvtConstrainedCallInfo *res = g_new0 (MonoGsharedvtConstrainedCallInfo, 1);
+               res->call_type = call_type;
+               res->klass = klass;
+               res->method = m;
+               res->code = addr;
+
+               return res;
+       }
        default:
                g_assert_not_reached ();
        }
@@ -2887,6 +2954,7 @@ mini_rgctx_info_type_to_patch_info_type (MonoRgctxInfoType info_type)
        case MONO_RGCTX_INFO_VIRT_METHOD:
        case MONO_RGCTX_INFO_VIRT_METHOD_CODE:
        case MONO_RGCTX_INFO_VIRT_METHOD_BOX_TYPE:
+       case MONO_RGCTX_INFO_GSHAREDVT_CONSTRAINED_CALL_INFO:
                return MONO_PATCH_INFO_VIRT_METHOD;
        case MONO_RGCTX_INFO_METHOD_GSHAREDVT_INFO:
                return MONO_PATCH_INFO_GSHAREDVT_METHOD;
index 1e5c5ed..3376a0c 100644 (file)
@@ -4972,7 +4972,8 @@ register_icalls (void)
        register_icall (mono_array_new_n_icall, mono_icall_sig_object_ptr_int_ptr, FALSE);
        register_icall (mono_get_native_calli_wrapper, mono_icall_sig_ptr_ptr_ptr_ptr, FALSE);
        register_icall (mono_resume_unwind, mono_icall_sig_void_ptr, TRUE);
-       register_icall (mono_gsharedvt_constrained_call, mono_icall_sig_object_ptr_ptr_ptr_ptr_ptr, FALSE);
+       register_icall (mono_gsharedvt_constrained_call, mono_icall_sig_object_ptr_ptr_ptr_ptr_ptr_ptr, FALSE);
+       register_icall (mono_gsharedvt_constrained_call_fast, mono_icall_sig_ptr_ptr_ptr_ptr, FALSE);
        register_icall (mono_gsharedvt_value_copy, mono_icall_sig_void_ptr_ptr_ptr, TRUE);
 
        //WARNING We do runtime selection here but the string *MUST* be to a fallback function that has same signature and behavior
index d4de135..245e334 100644 (file)
@@ -1008,6 +1008,21 @@ enum {
        MONO_GSHAREDVT_BOX_TYPE_NULLABLE = 3
 };
 
+/*
+ * Types of constrained calls from gsharedvt code
+ */
+enum {
+       /* Cannot be 0 since this is stored in rgctx slots, and 0 means an uninitialized rgctx slot */
+       /* Calling a vtype method with a vtype receiver */
+       MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_VTYPE = 1,
+       /* Calling a ref method with a ref receiver */
+       MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_REF = 2,
+       /* Calling a non-vtype method with a vtype receiver, has to box */
+       MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_BOX = 3,
+       /* Everything else */
+       MONO_GSHAREDVT_CONSTRAINT_CALL_TYPE_OTHER = 4
+};
+
 typedef enum {
        MONO_RGCTX_INFO_STATIC_DATA                  = 0,
        MONO_RGCTX_INFO_KLASS                        = 1,
@@ -1070,7 +1085,9 @@ typedef enum {
        /* The llvmonly interp entry for a method */
        MONO_RGCTX_INFO_LLVMONLY_INTERP_ENTRY         = 36,
        /* Same as VIRT_METHOD_CODE, but resolve MonoMethod* instead of code */
-       MONO_RGCTX_INFO_VIRT_METHOD                   = 37
+       MONO_RGCTX_INFO_VIRT_METHOD                   = 37,
+       /* Resolves to a MonoGsharedvtConstrainedCallInfo */
+       MONO_RGCTX_INFO_GSHAREDVT_CONSTRAINED_CALL_INFO = 38,
 } MonoRgctxInfoType;
 
 /* How an rgctx is passed to a method */
@@ -1135,6 +1152,14 @@ typedef struct {
        gpointer entries [MONO_ZERO_LEN_ARRAY];
 } MonoGSharedVtMethodRuntimeInfo;
 
+/* Precomputed information about constrained calls from gsharedvt methods */
+typedef struct {
+       int call_type;
+       MonoClass *klass;
+       MonoMethod *method;
+       gpointer code;
+} MonoGsharedvtConstrainedCallInfo;
+
 typedef struct
 {
        MonoClass *klass;