Skip to content

[mono] box related IL instruction sequence optimization for interpreter #99379

@fanyang-mono

Description

@fanyang-mono

Mono mini has a bunch of box related IL instruction sequence optimizations. See the following code

/*
* Look for:
*
* <push int/long ptr>
* <push int/long>
* box MyFlags
* constrained. MyFlags
* callvirt instance bool class [mscorlib] System.Enum::HasFlag (class [mscorlib] System.Enum)
*
* If we find this sequence and the operand types on box and constrained
* are equal, we can emit a specialized instruction sequence instead of
* the very slow HasFlag () call.
* This code sequence is generated by older mcs/csc, the newer one is handled in
* emit_inst_for_method ().
*/
guint32 constrained_token;
guint32 callvirt_token;
if ((cfg->opt & MONO_OPT_INTRINS) &&
// FIXME ip_in_bb as we go?
next_ip < end && ip_in_bb (cfg, cfg->cbb, next_ip) &&
(ip = il_read_constrained (next_ip, end, &constrained_token)) &&
ip_in_bb (cfg, cfg->cbb, ip) &&
(ip = il_read_callvirt (ip, end, &callvirt_token)) &&
ip_in_bb (cfg, cfg->cbb, ip) &&
m_class_is_enumtype (klass) &&
(enum_class = mini_get_class (method, constrained_token, generic_context)) &&
(has_flag = mini_get_method (cfg, method, callvirt_token, NULL, generic_context)) &&
has_flag->klass == mono_defaults.enum_class &&
!strcmp (has_flag->name, "HasFlag") &&
(has_flag_sig = mono_method_signature_internal (has_flag)) &&
has_flag_sig->hasthis &&
has_flag_sig->param_count == 1) {
CHECK_TYPELOAD (enum_class);
if (enum_class == klass) {
MonoInst *enum_this, *enum_flag;
next_ip = ip;
il_op = MONO_CEE_CALLVIRT;
--sp;
enum_this = sp [0];
enum_flag = sp [1];
*sp++ = mini_handle_enum_has_flag (cfg, klass, enum_this, -1, enum_flag);
break;
}
}
guint32 unbox_any_token;
/*
* Common in generic code:
* box T1, unbox.any T2.
*/
if ((cfg->opt & MONO_OPT_INTRINS) &&
next_ip < end && ip_in_bb (cfg, cfg->cbb, next_ip) &&
(ip = il_read_unbox_any (next_ip, end, &unbox_any_token))) {
MonoClass *unbox_klass = mini_get_class (method, unbox_any_token, generic_context);
CHECK_TYPELOAD (unbox_klass);
if (klass == unbox_klass) {
next_ip = ip;
*sp++ = val;
break;
}
}
// Optimize
//
// box
// call object::GetType()
//
guint32 gettype_token;
if ((ip = il_read_call(next_ip, end, &gettype_token)) && ip_in_bb (cfg, cfg->cbb, ip)) {
MonoMethod* gettype_method = mini_get_method (cfg, method, gettype_token, NULL, generic_context);
if (!strcmp (gettype_method->name, "GetType") && gettype_method->klass == mono_defaults.object_class) {
mono_class_init_internal(klass);
if (mono_class_get_checked (m_class_get_image (klass), m_class_get_type_token (klass), error) == klass) {
if (cfg->compile_aot) {
EMIT_NEW_TYPE_FROM_HANDLE_CONST (cfg, ins, m_class_get_image (klass), m_class_get_type_token (klass), generic_context);
} else {
MonoType *klass_type = m_class_get_byval_arg (klass);
MonoReflectionType* reflection_type = mono_type_get_object_checked (klass_type, cfg->error);
EMIT_NEW_PCONST (cfg, ins, reflection_type);
}
ins->type = STACK_OBJ;
ins->klass = mono_defaults.systemtype_class;
*sp++ = ins;
next_ip = ip;
break;
}
}
}
// Optimize
//
// box
// ldnull
// ceq (or cgt.un)
//
// to just
//
// ldc.i4.0 (or 1)
guchar* ldnull_ip;
if ((ldnull_ip = il_read_op (next_ip, end, CEE_LDNULL, MONO_CEE_LDNULL)) && ip_in_bb (cfg, cfg->cbb, ldnull_ip)) {
gboolean is_eq = FALSE, is_neq = FALSE;
if ((ip = il_read_op (ldnull_ip, end, CEE_PREFIX1, MONO_CEE_CEQ)))
is_eq = TRUE;
else if ((ip = il_read_op (ldnull_ip, end, CEE_PREFIX1, MONO_CEE_CGT_UN)))
is_neq = TRUE;
if ((is_eq || is_neq) && ip_in_bb (cfg, cfg->cbb, ip) &&
!mono_class_is_nullable (klass) && !mini_is_gsharedvt_klass (klass)) {
next_ip = ip;
il_op = (MonoOpcodeEnum) (is_eq ? CEE_LDC_I4_0 : CEE_LDC_I4_1);
EMIT_NEW_ICONST (cfg, ins, is_eq ? 0 : 1);
ins->type = STACK_I4;
*sp++ = ins;
break;
}
}
guint32 isinst_tk = 0;
if ((ip = il_read_op_and_token (next_ip, end, CEE_ISINST, MONO_CEE_ISINST, &isinst_tk)) &&
ip_in_bb (cfg, cfg->cbb, ip)) {
MonoClass *isinst_class = mini_get_class (method, isinst_tk, generic_context);
if (!mono_class_is_nullable (klass) && !mono_class_is_nullable (isinst_class) &&
!mini_is_gsharedvt_variable_klass (klass) && !mini_is_gsharedvt_variable_klass (isinst_class) &&
!mono_class_is_open_constructed_type (m_class_get_byval_arg (klass)) &&
!mono_class_is_open_constructed_type (m_class_get_byval_arg (isinst_class))) {
// Optimize
//
// box
// isinst [Type]
// brfalse/brtrue
//
// to
//
// ldc.i4.0 (or 1)
// brfalse/brtrue
//
guchar* br_ip = NULL;
if ((br_ip = il_read_brtrue (ip, end, &target)) || (br_ip = il_read_brtrue_s (ip, end, &target)) ||
(br_ip = il_read_brfalse (ip, end, &target)) || (br_ip = il_read_brfalse_s (ip, end, &target))) {
gboolean isinst = mono_class_is_assignable_from_internal (isinst_class, klass);
next_ip = ip;
il_op = (MonoOpcodeEnum) (isinst ? CEE_LDC_I4_1 : CEE_LDC_I4_0);
EMIT_NEW_ICONST (cfg, ins, isinst ? 1 : 0);
ins->type = STACK_I4;
*sp++ = ins;
break;
}
// Optimize
//
// box
// isinst [Type]
// ldnull
// ceq/cgt.un
//
// to
//
// ldc.i4.0 (or 1)
//
ldnull_ip = NULL;
if ((ldnull_ip = il_read_op (ip, end, CEE_LDNULL, MONO_CEE_LDNULL)) && ip_in_bb (cfg, cfg->cbb, ldnull_ip)) {
gboolean is_eq = FALSE, is_neq = FALSE;
if ((ip = il_read_op (ldnull_ip, end, CEE_PREFIX1, MONO_CEE_CEQ)))
is_eq = TRUE;
else if ((ip = il_read_op (ldnull_ip, end, CEE_PREFIX1, MONO_CEE_CGT_UN)))
is_neq = TRUE;
if ((is_eq || is_neq) && ip_in_bb (cfg, cfg->cbb, ip) &&
!mono_class_is_nullable (klass) && !mini_is_gsharedvt_klass (klass)) {
gboolean isinst = mono_class_is_assignable_from_internal (isinst_class, klass);
next_ip = ip;
if (is_eq)
isinst = !isinst;
il_op = (MonoOpcodeEnum) (isinst ? CEE_LDC_I4_1 : CEE_LDC_I4_0);
EMIT_NEW_ICONST (cfg, ins, isinst ? 1 : 0);
ins->type = STACK_I4;
*sp++ = ins;
break;
}
}
// Optimize
//
// box
// isinst [Type]
// unbox.any
//
// to
//
// nop
//
guchar* unbox_ip = NULL;
guint32 unbox_token = 0;
if ((unbox_ip = il_read_unbox_any (ip, end, &unbox_token)) && ip_in_bb (cfg, cfg->cbb, unbox_ip)) {
MonoClass *unbox_klass = mini_get_class (method, unbox_token, generic_context);
CHECK_TYPELOAD (unbox_klass);
if (!mono_class_is_nullable (unbox_klass) &&
!mini_is_gsharedvt_klass (unbox_klass) &&
klass == isinst_class &&
klass == unbox_klass)
{
*sp++ = val;
next_ip = unbox_ip;
break;
}
}
}
}
// ASSUME
// interface ISomeIface { void Method(); }
// struct SomeStruct : ISomeIface {...}
// OPTIMIZE
// box SomeStruct
// callvirt instance ISomeIface::Method()
// TO
// call SomeStruct::Method()
guint32 callvirt_proc_token;
if (!((cfg->compile_aot || cfg->compile_llvm) && !mono_class_is_def(klass)) && // we cannot devirtualize in AOT when using generics
next_ip < end &&
il_read_callvirt (next_ip, end, &callvirt_proc_token) &&
ip_in_bb (cfg, cfg->cbb, next_ip) ) {
MonoMethod* iface_method;
MonoMethodSignature* iface_method_sig;
if (val &&
val->flags != MONO_INST_FAULT && // not null
!mono_class_is_nullable (klass) &&
!mini_is_gsharedvt_klass (klass) &&
(iface_method = mini_get_method (cfg, method, callvirt_proc_token, NULL, generic_context)) &&
(iface_method_sig = mono_method_signature_internal (iface_method)) && // callee signture is healthy
iface_method_sig->hasthis &&
iface_method_sig->param_count == 0 && // the callee has no args (other than this)
!iface_method_sig->has_type_parameters &&
iface_method_sig->generic_param_count == 0) { // and no type params, apparently virtual generic methods require special handling
if (!m_class_is_inited (iface_method->klass)) {
if (!mono_class_init_internal (iface_method->klass))
TYPE_LOAD_ERROR (iface_method->klass);
}
ERROR_DECL (struct_method_error);
MonoMethod* struct_method = mono_class_get_virtual_method (klass, iface_method, struct_method_error);
if (is_ok (struct_method_error)) {
MonoMethodSignature* struct_method_sig = mono_method_signature_internal (struct_method);
if (!struct_method ||
!MONO_METHOD_IS_FINAL (struct_method) ||
!struct_method_sig ||
struct_method_sig->has_type_parameters ||
!mono_method_can_access_method (method, struct_method)) {
// do not optimize, let full callvirt deal with it
} else if (val->opcode == OP_TYPED_OBJREF) {
*sp++ = val;
cmethod_override = struct_method;
break;
} else {
MonoInst* srcvar = get_vreg_to_inst (cfg, val->dreg);
if (!srcvar)
srcvar = mono_compile_create_var_for_vreg (cfg, m_class_get_byval_arg (klass), OP_LOCAL, val->dreg);
EMIT_NEW_VARLOADA (cfg, ins, srcvar, m_class_get_byval_arg (klass));
*sp++= ins;
cmethod_override = struct_method;
break;
}
} else {
mono_error_cleanup (struct_method_error);
}
}
}

Interpreter needs to support these optimizations as well, in order to support the ref struct feature.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions