From 1b788f4dc3e3a8829488e52c032ad6a70671e070 Mon Sep 17 00:00:00 2001 From: Zoltan Varga Date: Tue, 7 Feb 2023 23:01:29 -0500 Subject: [PATCH] [mono][aot] Optimize constrained calls made from gsharedvt methods. (#79339) The calls are of the form: .constrained T_GSHAREDVT callvirt 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 | 1 + src/mono/mono/metadata/jit-icall-reg.h | 1 + src/mono/mono/mini/jit-icalls.c | 66 ++++-- src/mono/mono/mini/jit-icalls.h | 5 +- src/mono/mono/mini/method-to-ir.c | 249 ++++++++++++++-------- src/mono/mono/mini/mini-generic-sharing.c | 72 ++++++- src/mono/mono/mini/mini-runtime.c | 3 +- src/mono/mono/mini/mini.h | 27 ++- 8 files changed, 321 insertions(+), 103 deletions(-) diff --git a/src/mono/mono/metadata/icall-signatures.h b/src/mono/mono/metadata/icall-signatures.h index 080d4724d8945..16b35efa530ce 100644 --- a/src/mono/mono/metadata/icall-signatures.h +++ b/src/mono/mono/metadata/icall-signatures.h @@ -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. diff --git a/src/mono/mono/metadata/jit-icall-reg.h b/src/mono/mono/metadata/jit-icall-reg.h index 809a80403b521..a3aac59f4d458 100644 --- a/src/mono/mono/metadata/jit-icall-reg.h +++ b/src/mono/mono/metadata/jit-icall-reg.h @@ -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) \ diff --git a/src/mono/mono/mini/jit-icalls.c b/src/mono/mono/mini/jit-icalls.c index b68c4e4a8cce9..93802f47b34e9 100644 --- a/src/mono/mono/mini/jit-icalls.c +++ b/src/mono/mono/mini/jit-icalls.c @@ -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); diff --git a/src/mono/mono/mini/jit-icalls.h b/src/mono/mono/mini/jit-icalls.h index fb38ae02830d6..afd10935c5dc4 100644 --- a/src/mono/mono/mini/jit-icalls.h +++ b/src/mono/mono/mini/jit-icalls.h @@ -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); diff --git a/src/mono/mono/mini/method-to-ir.c b/src/mono/mono/mini/method-to-ir.c index d7cb653f79d02..5e55944f5578c 100644 --- a/src/mono/mono/mini/method-to-ir.c +++ b/src/mono/mono/mini/method-to-ir.c @@ -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 + * + * 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:CompareTo() - * - System.IEquatable: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; diff --git a/src/mono/mono/mini/mini-generic-sharing.c b/src/mono/mono/mini/mini-generic-sharing.c index 84a3ee1b08d88..6b81872c4f307 100644 --- a/src/mono/mono/mini/mini-generic-sharing.c +++ b/src/mono/mono/mini/mini-generic-sharing.c @@ -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; diff --git a/src/mono/mono/mini/mini-runtime.c b/src/mono/mono/mini/mini-runtime.c index 1e5c5edf7dfcf..3376a0c8cbd47 100644 --- a/src/mono/mono/mini/mini-runtime.c +++ b/src/mono/mono/mini/mini-runtime.c @@ -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 diff --git a/src/mono/mono/mini/mini.h b/src/mono/mono/mini/mini.h index d4de1354ea2f1..245e334fe2e5a 100644 --- a/src/mono/mono/mini/mini.h +++ b/src/mono/mono/mini/mini.h @@ -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;