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)
}
}
}
- 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;
}
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;
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) {
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 ();
}
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;