Skip to content

Commit f2de14d

Browse files
authored
[mono][jit] Devirtualize ldarg(s)+callvirt where possible. (dotnet#75748)
* [mono][jit] Devirtualizing ldarg(s)+callvirt where possible. dotnet#33015 * [mono][jit] Resolved compiler warnings. dotnet#33015 * [mono][jit] Fixed runtime crash when optimizing ldarg(a)+callvirt and vtable is unavailable. dotnet#33015 * [mono][jit] Checking mono_class_get_virtual_method assumptions now also fails when klass is abstract. * [mono][jit] Cleaned up optimization of ldarg(a)+callvirt. GetHashCode is devirtualized in callvirt handler now. * [mono][jit] ldarg(a)+callvirt optimization code style now conforms to guidelines. * [mono][jit] Simplified conditions on ldarg(a)+callvirt optimization. Fixed code style issues. * [mono][jit] Indentation fix. Vtable is now set up in check_get_virtual_method_assumptions.
1 parent 4850c88 commit f2de14d

File tree

1 file changed

+111
-5
lines changed

1 file changed

+111
-5
lines changed

src/mono/mono/mini/method-to-ir.c

+111-5
Original file line numberDiff line numberDiff line change
@@ -5667,6 +5667,109 @@ is_addressable_valuetype_load (MonoCompile* cfg, guint8* ip, MonoType* ldtype)
56675667
return is_load_instruction && is_in_previous_bb && is_struct;
56685668
}
56695669

5670+
/*
5671+
* check_get_virtual_method_assumptions:
5672+
*
5673+
* This shadows mono_class_get_virtual_method, but instead of actually resolving
5674+
* the virtual method, this only checks if mono_class_get_virtual_method would
5675+
* succeed. This is in place because that function fails catastrophically in some
5676+
* cases, bringing down the entire runtime. Returns TRUE if the function is safe
5677+
* to call, FALSE otherwise.
5678+
*/
5679+
static gboolean
5680+
check_get_virtual_method_assumptions (MonoClass* klass, MonoMethod* method)
5681+
{
5682+
if (m_class_is_abstract(klass))
5683+
return FALSE;
5684+
5685+
if (((method->flags & METHOD_ATTRIBUTE_FINAL) || !(method->flags & METHOD_ATTRIBUTE_VIRTUAL)))
5686+
return TRUE;
5687+
5688+
mono_class_setup_vtable (klass);
5689+
if (m_class_get_vtable (klass) == NULL)
5690+
return FALSE;
5691+
5692+
if (method->slot == -1) {
5693+
if (method->is_inflated) {
5694+
if (((MonoMethodInflated*)method)->declaring->slot == -1)
5695+
return FALSE;
5696+
} else {
5697+
return FALSE;
5698+
}
5699+
}
5700+
5701+
if (method->slot != -1 && mono_class_is_interface (method->klass)) {
5702+
gboolean variance_used = FALSE;
5703+
int iface_offset = mono_class_interface_offset_with_variance (klass, method->klass, &variance_used);
5704+
if (iface_offset <= 0)
5705+
return FALSE;
5706+
}
5707+
5708+
if (method->is_inflated)
5709+
return FALSE;
5710+
5711+
return TRUE;
5712+
}
5713+
5714+
/*
5715+
* try_prepare_objaddr_callvirt_optimization:
5716+
*
5717+
* Determine in a load+callvirt optimization can be performed and if so,
5718+
* resolve the callvirt target method, so that it can behave as call.
5719+
* Returns null, if the optimization cannot be performed.
5720+
*/
5721+
static MonoMethod*
5722+
try_prepare_objaddr_callvirt_optimization (MonoCompile *cfg, guchar *next_ip, guchar* end, MonoMethod *method, MonoGenericContext* generic_context, MonoClass *klass)
5723+
{
5724+
// TODO: relax the _is_def requirement?
5725+
if (cfg->compile_aot || cfg->compile_llvm || !klass || !mono_class_is_def (klass))
5726+
return NULL;
5727+
5728+
guchar* callvirt_ip;
5729+
guint32 callvirt_proc_token;
5730+
if (!(callvirt_ip = il_read_callvirt (next_ip, end, &callvirt_proc_token)) ||
5731+
!ip_in_bb (cfg, cfg->cbb, callvirt_ip))
5732+
return NULL;
5733+
5734+
MonoMethod* iface_method = mini_get_method (cfg, method, callvirt_proc_token, NULL, generic_context);
5735+
if (!iface_method ||
5736+
iface_method->is_generic ||
5737+
iface_method->dynamic || // Reflection.Emit-generated methods should have this flag
5738+
!strcmp (iface_method->name, "GetHashCode")) // the callvirt handler itself optimizes those
5739+
return NULL;
5740+
5741+
MonoMethodSignature* iface_method_sig;
5742+
if (!((iface_method_sig = mono_method_signature_internal (iface_method)) &&
5743+
iface_method_sig->hasthis &&
5744+
iface_method_sig->param_count == 0 &&
5745+
!iface_method_sig->has_type_parameters &&
5746+
iface_method_sig->generic_param_count == 0))
5747+
return NULL;
5748+
5749+
if (!check_get_virtual_method_assumptions (klass, iface_method))
5750+
return NULL;
5751+
5752+
ERROR_DECL (struct_method_error);
5753+
MonoMethod* struct_method = mono_class_get_virtual_method (klass, iface_method, struct_method_error);
5754+
5755+
if (is_ok (struct_method_error)) {
5756+
if (!struct_method || !MONO_METHOD_IS_FINAL (struct_method))
5757+
return NULL;
5758+
5759+
MonoMethodSignature* struct_method_sig = mono_method_signature_internal (struct_method);
5760+
if (!struct_method_sig ||
5761+
struct_method_sig->has_type_parameters ||
5762+
!mono_method_can_access_method (method, struct_method)) {
5763+
return NULL;
5764+
}
5765+
} else {
5766+
mono_error_cleanup (struct_method_error);
5767+
return NULL;
5768+
}
5769+
5770+
return struct_method;
5771+
}
5772+
56705773
/*
56715774
* handle_ctor_call:
56725775
*
@@ -7014,29 +7117,32 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
70147117
case MONO_CEE_LDARG_S:
70157118
case MONO_CEE_LDARG:
70167119
CHECK_ARG (n);
7017-
if (next_ip < end && is_addressable_valuetype_load (cfg, next_ip, cfg->arg_types[n])) {
7120+
if (next_ip < end && is_addressable_valuetype_load (cfg, next_ip, cfg->arg_types [n])) {
70187121
EMIT_NEW_ARGLOADA (cfg, ins, n);
70197122
} else {
70207123
EMIT_NEW_ARGLOAD (cfg, ins, n);
70217124
}
70227125
*sp++ = ins;
7126+
/*if (!m_method_is_icall (method)) */{
7127+
MonoMethod* callvirt_target = try_prepare_objaddr_callvirt_optimization (cfg, next_ip, end, method, generic_context, param_types [n]->data.klass);
7128+
if (callvirt_target)
7129+
cmethod_override = callvirt_target;
7130+
}
70237131
break;
7024-
70257132
case MONO_CEE_LDLOC_0:
70267133
case MONO_CEE_LDLOC_1:
70277134
case MONO_CEE_LDLOC_2:
70287135
case MONO_CEE_LDLOC_3:
70297136
case MONO_CEE_LDLOC_S:
70307137
case MONO_CEE_LDLOC:
70317138
CHECK_LOCAL (n);
7032-
if (next_ip < end && is_addressable_valuetype_load (cfg, next_ip, header->locals[n])) {
7139+
if (next_ip < end && is_addressable_valuetype_load (cfg, next_ip, header->locals [n])) {
70337140
EMIT_NEW_LOCLOADA (cfg, ins, n);
70347141
} else {
70357142
EMIT_NEW_LOCLOAD (cfg, ins, n);
70367143
}
70377144
*sp++ = ins;
70387145
break;
7039-
70407146
case MONO_CEE_STLOC_0:
70417147
case MONO_CEE_STLOC_1:
70427148
case MONO_CEE_STLOC_2:
@@ -7477,7 +7583,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
74777583

74787584
// The method to be called may have already been resolved when handling a previous opcode. In that
74797585
// case, we ignore the operand and act as CALL, instead of CALLVIRT.
7480-
// E.g. https://github.com/dotnet/runtime/issues/32166 (box+callvirt optimization)
7586+
// E.g. https://github.com/dotnet/runtime/issues/32166 (box+callvirt optimization)
74817587
if (cmethod_override) {
74827588
cmethod = cmethod_override;
74837589
cmethod_override = NULL;

0 commit comments

Comments
 (0)