From 5b30fbccab1f0d568f6ec4131196e5dca357428d Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Wed, 26 Apr 2023 17:22:39 +0300 Subject: [PATCH 01/11] [mono][interp] Preserve klass information when generating MINT_CKNULL This information will be useful to later devirtualize the call --- src/mono/mono/mini/interp/transform.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index 21dd10037c40a7..f1dc9d23b42053 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -489,14 +489,20 @@ push_var (TransformData *td, int var_index) } while (0) static void -set_simple_type_and_local (TransformData *td, StackInfo *sp, int type) +set_type_and_local (TransformData *td, StackInfo *sp, int type, MonoClass *klass) { - SET_SIMPLE_TYPE (sp, type); + SET_TYPE (sp, type, klass); create_interp_stack_local (td, sp, MINT_STACK_SLOT_SIZE); if (!td->optimized) td->locals [sp->local].stack_offset = sp->offset; } +static void +set_simple_type_and_local (TransformData *td, StackInfo *sp, int type) +{ + set_type_and_local (td, sp, type, NULL); +} + static void push_type (TransformData *td, int type, MonoClass *k) { @@ -3538,7 +3544,7 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target StackInfo *sp = td->sp - 1 - csignature->param_count; interp_add_ins (td, MINT_CKNULL); interp_ins_set_sreg (td->last_ins, sp->local); - set_simple_type_and_local (td, sp, sp->type); + set_type_and_local (td, sp, sp->type, sp->klass); interp_ins_set_dreg (td->last_ins, sp->local); } From 6a5012282bafc84d7eec9b8c4d56112de0ce1f25 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Wed, 26 Apr 2023 17:28:59 +0300 Subject: [PATCH 02/11] [mono][interp] Add more specific type information for EqualityComparer.Default --- src/mono/mono/mini/interp/transform.c | 50 ++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index f1dc9d23b42053..a0f8d5cfbd32b8 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -3297,6 +3297,50 @@ interp_realign_simd_params (TransformData *td, StackInfo *sp_params, int num_arg } } +static GENERATE_GET_CLASS_WITH_CACHE (iequatable, "System", "IEquatable`1") +static GENERATE_GET_CLASS_WITH_CACHE (geqcomparer, "System.Collections.Generic", "GenericEqualityComparer`1"); + +// Provide more specific type information about the return value of a special +// call, so that we can devirtualize future calls on this object. +// +// Equivalent to jit's handle_call_res_devirt +static void +interp_handle_call_res_devirt (TransformData *td, MonoMethod *cmethod) +{ + if (m_class_get_image (cmethod->klass) == mono_defaults.corlib && + !strcmp (m_class_get_name (cmethod->klass), "EqualityComparer`1") && + !strcmp (cmethod->name, "get_Default")) { + + MonoType *param_type = mono_class_get_generic_class (cmethod->klass)->context.class_inst->type_argv [0]; + MonoClass *inst; + MonoGenericContext ctx; + ERROR_DECL (error); + + memset (&ctx, 0, sizeof (ctx)); + + MonoType *args [ ] = { param_type }; + ctx.class_inst = mono_metadata_get_generic_inst (1, args); + + inst = mono_class_inflate_generic_class_checked (mono_class_get_iequatable_class (), &ctx, error); + mono_error_assert_ok (error); + + if (mono_class_is_assignable_from_internal (inst, mono_class_from_mono_type_internal (param_type)) && param_type->type != MONO_TYPE_U1 && param_type->type != MONO_TYPE_STRING) { + MonoClass *gcomparer_inst; + + memset (&ctx, 0, sizeof (ctx)); + + args [0] = param_type; + ctx.class_inst = mono_metadata_get_generic_inst (1, args); + + MonoClass *gcomparer = mono_class_get_geqcomparer_class (); + g_assert (gcomparer); + gcomparer_inst = mono_class_inflate_generic_class_checked (gcomparer, &ctx, error); + if (is_ok (error)) + td->sp [-1].klass = gcomparer_inst; + } + } +} + /* Return FALSE if error, including inline failure */ static gboolean interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target_method, MonoGenericContext *generic_context, MonoClass *constrained_class, gboolean readonly, MonoError *error, gboolean check_visibility, gboolean save_last_error, gboolean tailcall) @@ -3555,7 +3599,7 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target if (interp_inline_method (td, target_method, mheader, error)) { td->ip += 5; - return TRUE; + goto done; } } @@ -3809,6 +3853,10 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target g_assert (call_offset == -1); } + +done: + if (csignature->ret->type != MONO_TYPE_VOID && target_method) + interp_handle_call_res_devirt (td, target_method); return TRUE; } From 791268adca255244f77a080656fd3032e176a70d Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Wed, 26 Apr 2023 18:09:41 +0300 Subject: [PATCH 03/11] [mono][interp] Devirtualize calls if we can find a final implementation Remove asserts from mono_class_get_virtual_method so we can check a virtual method on any klass, returning NULL if no implementation is found. --- src/mono/mono/metadata/object.c | 10 +++--- src/mono/mono/mini/interp/transform.c | 44 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/mono/mono/metadata/object.c b/src/mono/mono/metadata/object.c index c8fb3f79135784..3cfa1526413a9e 100644 --- a/src/mono/mono/metadata/object.c +++ b/src/mono/mono/metadata/object.c @@ -2516,13 +2516,11 @@ mono_class_get_virtual_method (MonoClass *klass, MonoMethod *method, MonoError * } else { res = vtable [method->slot]; } - } + } - { - if (method->is_inflated) { - /* Have to inflate the result */ - res = mono_class_inflate_generic_method_checked (res, &((MonoMethodInflated*)method)->context, error); - } + if (method->is_inflated) { + /* Have to inflate the result */ + res = mono_class_inflate_generic_method_checked (res, &((MonoMethodInflated*)method)->context, error); } return res; diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index a0f8d5cfbd32b8..eae4715c723aba 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -3341,6 +3341,37 @@ interp_handle_call_res_devirt (TransformData *td, MonoMethod *cmethod) } } +static MonoMethod* +interp_try_devirt (MonoClass *this_klass, MonoMethod *target_method) +{ + ERROR_DECL(error); + // No relevant information about the type + if (!this_klass || this_klass == mono_defaults.object_class) + return NULL; + + if (mono_class_is_interface (this_klass)) + return NULL; + + // Make sure first it is valid to lookup method in the vtable + gboolean assignable; + mono_class_is_assignable_from_checked (target_method->klass, this_klass, &assignable, error); + if (!is_ok (error) || !assignable) + return NULL; + + MonoMethod *new_target_method = mono_class_get_virtual_method (this_klass, target_method, error); + if (!is_ok (error) || !new_target_method) + return NULL; + + // TODO We would need to emit unboxing in order to devirtualize call to valuetype method + if (m_class_is_valuetype (new_target_method->klass)) + return NULL; + + if ((new_target_method->flags & METHOD_ATTRIBUTE_FINAL) || m_class_is_sealed (this_klass)) + return new_target_method; + + return NULL; +} + /* Return FALSE if error, including inline failure */ static gboolean interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target_method, MonoGenericContext *generic_context, MonoClass *constrained_class, gboolean readonly, MonoError *error, gboolean check_visibility, gboolean save_last_error, gboolean tailcall) @@ -3578,6 +3609,19 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target } } + // Attempt to devirtualize the call + if (is_virtual) { + MonoClass *this_klass = (td->sp - 1 - csignature->param_count)->klass; + MonoMethod *new_target_method = interp_try_devirt (this_klass, target_method); + + if (new_target_method) { + if (td->verbose_level) + g_print ("DEVIRTUALIZE %s.%s to %s.%s\n", m_class_get_name (target_method->klass), target_method->name, m_class_get_name (new_target_method->klass), new_target_method->name); + target_method = new_target_method; + is_virtual = FALSE; + } + } + if (op == -1) target_method = interp_transform_internal_calls (method, target_method, csignature, is_virtual); From d8f06dba0571ec474f0b579a2dc3afba43e5ec37 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Wed, 26 Apr 2023 18:20:25 +0300 Subject: [PATCH 04/11] [mono][interp] Optimize out null check on ldloca --- src/mono/mono/mini/interp/transform.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index eae4715c723aba..b3fc6d1cfcb1fe 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -9914,6 +9914,13 @@ interp_cprop (TransformData *td) sregs [0] = local; needs_retry = TRUE; } + } else if (opcode == MINT_CKNULL) { + InterpInst *def = local_defs [sregs [0]].ins; + if (def && def->opcode == MINT_LDLOCA_S) { + // CKNULL on LDLOCA is a NOP + ins->opcode = MINT_MOV_P; + needs_retry = TRUE; + } } ins_index++; From 05018aa6551145da685e22b714373053b1072b01 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Wed, 26 Apr 2023 18:59:00 +0300 Subject: [PATCH 05/11] [mono][interp] Constant fold unop conditionals applied to non null values --- src/mono/mono/mini/interp/transform.c | 35 +++++++++++++++++++-------- src/mono/mono/mini/interp/transform.h | 1 + 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index b3fc6d1cfcb1fe..312a189860794e 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -9122,7 +9122,7 @@ interp_fold_unop (TransformData *td, LocalValue *local_defs, InterpInst *ins) return ins; } -#define INTERP_FOLD_UNOP_BR(_opcode,_local_type,_cond) \ +#define INTERP_FOLD_UNOP_BR(_opcode,_cond) \ case _opcode: \ if (_cond) { \ ins->opcode = MINT_BR; \ @@ -9144,18 +9144,30 @@ interp_fold_unop_cond_br (TransformData *td, InterpBasicBlock *cbb, LocalValue * int sreg = ins->sregs [0]; LocalValue *val = &local_defs [sreg]; - if (val->type != LOCAL_VALUE_I4 && val->type != LOCAL_VALUE_I8) + if (val->type != LOCAL_VALUE_I4 && val->type != LOCAL_VALUE_I8 && val->type != LOCAL_VALUE_NON_NULL) return ins; - // Top of the stack is a constant - switch (ins->opcode) { - INTERP_FOLD_UNOP_BR (MINT_BRFALSE_I4, LOCAL_VALUE_I4, val->i == 0); - INTERP_FOLD_UNOP_BR (MINT_BRFALSE_I8, LOCAL_VALUE_I8, val->l == 0); - INTERP_FOLD_UNOP_BR (MINT_BRTRUE_I4, LOCAL_VALUE_I4, val->i != 0); - INTERP_FOLD_UNOP_BR (MINT_BRTRUE_I8, LOCAL_VALUE_I8, val->l != 0); + if (val->type == LOCAL_VALUE_NON_NULL) { + switch (ins->opcode) { + INTERP_FOLD_UNOP_BR (MINT_BRFALSE_I4, FALSE); + INTERP_FOLD_UNOP_BR (MINT_BRFALSE_I8, FALSE); + INTERP_FOLD_UNOP_BR (MINT_BRTRUE_I4, TRUE); + INTERP_FOLD_UNOP_BR (MINT_BRTRUE_I8, TRUE); - default: - return ins; + default: + return ins; + } + } else { + // Top of the stack is a constant + switch (ins->opcode) { + INTERP_FOLD_UNOP_BR (MINT_BRFALSE_I4, val->i == 0); + INTERP_FOLD_UNOP_BR (MINT_BRFALSE_I8, val->l == 0); + INTERP_FOLD_UNOP_BR (MINT_BRTRUE_I4, val->i != 0); + INTERP_FOLD_UNOP_BR (MINT_BRTRUE_I8, val->l != 0); + + default: + return ins; + } } if (td->verbose_level) { @@ -9921,6 +9933,9 @@ interp_cprop (TransformData *td) ins->opcode = MINT_MOV_P; needs_retry = TRUE; } + } else if (opcode == MINT_BOX) { + // TODO Add more relevant opcodes + local_defs [dreg].type = LOCAL_VALUE_NON_NULL; } ins_index++; diff --git a/src/mono/mono/mini/interp/transform.h b/src/mono/mono/mini/interp/transform.h index ee13fe9d40d5f6..fcc51c25f9c44b 100644 --- a/src/mono/mono/mini/interp/transform.h +++ b/src/mono/mono/mini/interp/transform.h @@ -50,6 +50,7 @@ typedef struct #define LOCAL_VALUE_LOCAL 1 #define LOCAL_VALUE_I4 2 #define LOCAL_VALUE_I8 3 +#define LOCAL_VALUE_NON_NULL 4 // LocalValue contains data to construct an InterpInst that is equivalent with the contents // of the stack slot / local / argument. From b36bcfa0c63e9c28cc647f87445082522e333864 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Wed, 26 Apr 2023 19:04:47 +0300 Subject: [PATCH 06/11] [mono][interp] Optimize out unused MINT_BOX --- src/mono/mono/mini/interp/mintops.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/mono/mini/interp/mintops.h b/src/mono/mono/mini/interp/mintops.h index 2849cec1778ff2..d22ab84bd68af6 100644 --- a/src/mono/mono/mini/interp/mintops.h +++ b/src/mono/mono/mini/interp/mintops.h @@ -232,7 +232,7 @@ typedef enum { #define MINT_IS_SIMD_CREATE(op) ((op) >= MINT_SIMD_V128_I1_CREATE && (op) <= MINT_SIMD_V128_I8_CREATE) // TODO Add more -#define MINT_NO_SIDE_EFFECTS(op) (MINT_IS_MOV (op) || MINT_IS_LDC_I4 (op) || MINT_IS_LDC_I8 (op) || op == MINT_LDPTR) +#define MINT_NO_SIDE_EFFECTS(op) (MINT_IS_MOV (op) || MINT_IS_LDC_I4 (op) || MINT_IS_LDC_I8 (op) || op == MINT_LDPTR || op == MINT_BOX) #define MINT_CALL_ARGS 2 #define MINT_CALL_ARGS_SREG -2 From 0488a5061494356d3427c44cc4640358c3344c11 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Thu, 27 Apr 2023 11:07:19 +0300 Subject: [PATCH 07/11] [mono] Remove duplicated code between interp and jit --- .../Generic/EqualityComparer.Mono.cs | 2 +- src/mono/mono/mini/interp/transform.c | 51 ++---------- src/mono/mono/mini/method-to-ir.c | 82 +++---------------- src/mono/mono/mini/mini.c | 45 ++++++++++ src/mono/mono/mini/mini.h | 2 + 5 files changed, 66 insertions(+), 116 deletions(-) diff --git a/src/mono/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.Mono.cs index 2d574506e39d51..603c1603290678 100644 --- a/src/mono/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Collections/Generic/EqualityComparer.Mono.cs @@ -32,7 +32,7 @@ private static EqualityComparer CreateComparer() ///////////////////////////////////////////////// // KEEP THIS IN SYNC WITH THE DEVIRT CODE - // IN METHOD-TO-IR.C + // IN mini_handle_call_res_devirt ///////////////////////////////////////////////// if (t == typeof(byte)) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index 312a189860794e..0b2886677dda3a 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -3297,50 +3297,6 @@ interp_realign_simd_params (TransformData *td, StackInfo *sp_params, int num_arg } } -static GENERATE_GET_CLASS_WITH_CACHE (iequatable, "System", "IEquatable`1") -static GENERATE_GET_CLASS_WITH_CACHE (geqcomparer, "System.Collections.Generic", "GenericEqualityComparer`1"); - -// Provide more specific type information about the return value of a special -// call, so that we can devirtualize future calls on this object. -// -// Equivalent to jit's handle_call_res_devirt -static void -interp_handle_call_res_devirt (TransformData *td, MonoMethod *cmethod) -{ - if (m_class_get_image (cmethod->klass) == mono_defaults.corlib && - !strcmp (m_class_get_name (cmethod->klass), "EqualityComparer`1") && - !strcmp (cmethod->name, "get_Default")) { - - MonoType *param_type = mono_class_get_generic_class (cmethod->klass)->context.class_inst->type_argv [0]; - MonoClass *inst; - MonoGenericContext ctx; - ERROR_DECL (error); - - memset (&ctx, 0, sizeof (ctx)); - - MonoType *args [ ] = { param_type }; - ctx.class_inst = mono_metadata_get_generic_inst (1, args); - - inst = mono_class_inflate_generic_class_checked (mono_class_get_iequatable_class (), &ctx, error); - mono_error_assert_ok (error); - - if (mono_class_is_assignable_from_internal (inst, mono_class_from_mono_type_internal (param_type)) && param_type->type != MONO_TYPE_U1 && param_type->type != MONO_TYPE_STRING) { - MonoClass *gcomparer_inst; - - memset (&ctx, 0, sizeof (ctx)); - - args [0] = param_type; - ctx.class_inst = mono_metadata_get_generic_inst (1, args); - - MonoClass *gcomparer = mono_class_get_geqcomparer_class (); - g_assert (gcomparer); - gcomparer_inst = mono_class_inflate_generic_class_checked (gcomparer, &ctx, error); - if (is_ok (error)) - td->sp [-1].klass = gcomparer_inst; - } - } -} - static MonoMethod* interp_try_devirt (MonoClass *this_klass, MonoMethod *target_method) { @@ -3899,8 +3855,11 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target done: - if (csignature->ret->type != MONO_TYPE_VOID && target_method) - interp_handle_call_res_devirt (td, target_method); + if (csignature->ret->type != MONO_TYPE_VOID && target_method) { + MonoClass *ret_klass = mini_handle_call_res_devirt (target_method); + if (ret_klass) + td->sp [-1].klass = ret_klass; + } return TRUE; } diff --git a/src/mono/mono/mini/method-to-ir.c b/src/mono/mono/mini/method-to-ir.c index 41cb7cdd6599db..32e2bf0c453e92 100644 --- a/src/mono/mono/mini/method-to-ir.c +++ b/src/mono/mono/mini/method-to-ir.c @@ -185,12 +185,6 @@ static int inline_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSigna static MonoInst* convert_value (MonoCompile *cfg, MonoType *type, MonoInst *ins); -/* helper methods signatures */ - -/* type loading helpers */ -static GENERATE_GET_CLASS_WITH_CACHE (iequatable, "System", "IEquatable`1") -static GENERATE_GET_CLASS_WITH_CACHE (geqcomparer, "System.Collections.Generic", "GenericEqualityComparer`1"); - /* * Instruction metadata */ @@ -5451,72 +5445,22 @@ emit_optimized_ldloca_ir (MonoCompile *cfg, guchar *ip, guchar *end, int local) static MonoInst* handle_call_res_devirt (MonoCompile *cfg, MonoMethod *cmethod, MonoInst *call_res) { - /* - * Devirt EqualityComparer.Default.Equals () calls for some types. - * The corefx code excepts these calls to be devirtualized. - * This depends on the implementation of EqualityComparer.Default, which is - * in mcs/class/referencesource/mscorlib/system/collections/generic/equalitycomparer.cs - */ - if (m_class_get_image (cmethod->klass) == mono_defaults.corlib && - !strcmp (m_class_get_name (cmethod->klass), "EqualityComparer`1") && - !strcmp (cmethod->name, "get_Default")) { - MonoType *param_type = mono_class_get_generic_class (cmethod->klass)->context.class_inst->type_argv [0]; - MonoClass *inst; - MonoGenericContext ctx; - ERROR_DECL (error); - - memset (&ctx, 0, sizeof (ctx)); - - MonoType *args [ ] = { param_type }; - ctx.class_inst = mono_metadata_get_generic_inst (1, args); - - inst = mono_class_inflate_generic_class_checked (mono_class_get_iequatable_class (), &ctx, error); - mono_error_assert_ok (error); + MonoClass *ret_klass = mini_handle_call_res_devirt (cmethod); - /* EqualityComparer.Default returns specific types depending on T */ - // FIXME: Add more - // 1. Implements IEquatable - // 2. Nullable - /* - * Can't use this for string/byte as it might use a different comparer: - * - * // Specialize type byte for performance reasons - * if (t == typeof(byte)) { - * return (EqualityComparer)(object)(new ByteEqualityComparer()); - * } - * #if MOBILE - * // Breaks .net serialization compatibility - * if (t == typeof (string)) - * return (EqualityComparer)(object)new InternalStringComparer (); - * #endif - */ - if (mono_class_is_assignable_from_internal (inst, mono_class_from_mono_type_internal (param_type)) && param_type->type != MONO_TYPE_U1 && param_type->type != MONO_TYPE_STRING) { - MonoInst *typed_objref; - MonoClass *gcomparer_inst; - - memset (&ctx, 0, sizeof (ctx)); - - args [0] = param_type; - ctx.class_inst = mono_metadata_get_generic_inst (1, args); - - MonoClass *gcomparer = mono_class_get_geqcomparer_class (); - g_assert (gcomparer); - gcomparer_inst = mono_class_inflate_generic_class_checked (gcomparer, &ctx, error); - if (is_ok (error)) { - MONO_INST_NEW (cfg, typed_objref, OP_TYPED_OBJREF); - typed_objref->type = STACK_OBJ; - typed_objref->dreg = alloc_ireg_ref (cfg); - typed_objref->sreg1 = call_res->dreg; - typed_objref->klass = gcomparer_inst; - MONO_ADD_INS (cfg->cbb, typed_objref); + if (ret_klass) { + MonoInst *typed_objref; + MONO_INST_NEW (cfg, typed_objref, OP_TYPED_OBJREF); + typed_objref->type = STACK_OBJ; + typed_objref->dreg = alloc_ireg_ref (cfg); + typed_objref->sreg1 = call_res->dreg; + typed_objref->klass = ret_klass; + MONO_ADD_INS (cfg->cbb, typed_objref); - call_res = typed_objref; + call_res = typed_objref; - /* Force decompose */ - cfg->flags |= MONO_CFG_NEEDS_DECOMPOSE; - cfg->cbb->needs_decompose = TRUE; - } - } + /* Force decompose */ + cfg->flags |= MONO_CFG_NEEDS_DECOMPOSE; + cfg->cbb->needs_decompose = TRUE; } return call_res; diff --git a/src/mono/mono/mini/mini.c b/src/mono/mono/mini/mini.c index a04e82bed45273..97eea8e486940a 100644 --- a/src/mono/mono/mini/mini.c +++ b/src/mono/mono/mini/mini.c @@ -4275,6 +4275,51 @@ mini_get_underlying_type (MonoType *type) return mini_type_get_underlying_type (type); } +static GENERATE_GET_CLASS_WITH_CACHE (iequatable, "System", "IEquatable`1") +static GENERATE_GET_CLASS_WITH_CACHE (geqcomparer, "System.Collections.Generic", "GenericEqualityComparer`1"); + +// Provide more specific type information about the return value of a special +// call, so that we can devirtualize future calls on this object. +MonoClass* +mini_handle_call_res_devirt (MonoMethod *cmethod) +{ + if (m_class_get_image (cmethod->klass) == mono_defaults.corlib && + !strcmp (m_class_get_name (cmethod->klass), "EqualityComparer`1") && + !strcmp (cmethod->name, "get_Default")) { + MonoType *param_type = mono_class_get_generic_class (cmethod->klass)->context.class_inst->type_argv [0]; + MonoClass *inst; + MonoGenericContext ctx; + ERROR_DECL (error); + + memset (&ctx, 0, sizeof (ctx)); + + MonoType *args [ ] = { param_type }; + ctx.class_inst = mono_metadata_get_generic_inst (1, args); + + inst = mono_class_inflate_generic_class_checked (mono_class_get_iequatable_class (), &ctx, error); + mono_error_assert_ok (error); + + // EqualityComparer.Default returns specific types depending on T + // FIXME: Special case more types: byte, string, nullable, enum ? + if (mono_class_is_assignable_from_internal (inst, mono_class_from_mono_type_internal (param_type)) && param_type->type != MONO_TYPE_U1 && param_type->type != MONO_TYPE_STRING) { + MonoClass *gcomparer_inst; + + memset (&ctx, 0, sizeof (ctx)); + + args [0] = param_type; + ctx.class_inst = mono_metadata_get_generic_inst (1, args); + + MonoClass *gcomparer = mono_class_get_geqcomparer_class (); + g_assert (gcomparer); + gcomparer_inst = mono_class_inflate_generic_class_checked (gcomparer, &ctx, error); + if (is_ok (error)) + return gcomparer_inst; + } + } + + return NULL; +} + void mini_jit_init (void) { diff --git a/src/mono/mono/mini/mini.h b/src/mono/mono/mini/mini.h index 1fffa53b9cd231..9c5f7e9e84ce6e 100644 --- a/src/mono/mono/mini/mini.h +++ b/src/mono/mono/mini/mini.h @@ -2778,6 +2778,8 @@ typedef enum { SHARE_MODE_GSHAREDVT = 0x1, } GetSharedMethodFlags; +MonoClass* mini_handle_call_res_devirt (MonoMethod *cmethod); + MonoType* mini_get_underlying_type (MonoType *type); MonoType* mini_type_get_underlying_type (MonoType *type); MonoClass* mini_get_class (MonoMethod *method, guint32 token, MonoGenericContext *context); From 0a4224a0e2f4b13a41e30dd041173554b63468d9 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Fri, 28 Apr 2023 13:25:42 +0300 Subject: [PATCH 08/11] [mono][metadata] Allow method to be called for abstract classes It will just return null if a concrete implementation wasn't found. --- src/mono/mono/metadata/object.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/mono/metadata/object.c b/src/mono/mono/metadata/object.c index 3cfa1526413a9e..fab53233699b6a 100644 --- a/src/mono/mono/metadata/object.c +++ b/src/mono/mono/metadata/object.c @@ -2517,8 +2517,8 @@ mono_class_get_virtual_method (MonoClass *klass, MonoMethod *method, MonoError * res = vtable [method->slot]; } } - - if (method->is_inflated) { + // res can be null if klass is abstract and doesn't implement method + if (res && method->is_inflated) { /* Have to inflate the result */ res = mono_class_inflate_generic_method_checked (res, &((MonoMethodInflated*)method)->context, error); } From 3b7d61da0a8c22abb5a8c0ab9c4f3a46d6cbb952 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Thu, 4 May 2023 13:48:06 +0300 Subject: [PATCH 09/11] [mono][interp] Don't populate wrong type information into the stack state --- src/mono/mono/mini/interp/transform.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index 0b2886677dda3a..c66b177dc60422 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -785,8 +785,16 @@ init_bb_stack_state (TransformData *td, InterpBasicBlock *bb) { // FIXME If already initialized, then we need to generate mov to the registers in the state. // Check if already initialized - if (bb->stack_height >= 0) + if (bb->stack_height >= 0) { + // Discard type information if we have type conflicts for stack contents + for (int i = 0; i < bb->stack_height; i++) { + if (bb->stack_state [i].klass != td->stack [i].klass) { + bb->stack_state [i].klass = NULL; + td->stack [i].klass = NULL; + } + } return; + } bb->stack_height = GPTRDIFF_TO_INT (td->sp - td->stack); if (bb->stack_height > 0) { From e3cb92f9c796b61b6dc890ef61f6cebe7df22f4f Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Mon, 8 May 2023 12:51:19 +0300 Subject: [PATCH 10/11] [mono][interp] Don't devirtualize final methods They can still be overriden --- src/mono/mono/mini/interp/transform.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index c66b177dc60422..b552d720ba591f 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -3330,7 +3330,8 @@ interp_try_devirt (MonoClass *this_klass, MonoMethod *target_method) if (m_class_is_valuetype (new_target_method->klass)) return NULL; - if ((new_target_method->flags & METHOD_ATTRIBUTE_FINAL) || m_class_is_sealed (this_klass)) + // final methods can still be overriden with explicit overrides + if (m_class_is_sealed (this_klass)) return new_target_method; return NULL; From b3f16766967e4b378f6fbdaff2c47d4b31994a7b Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Thu, 11 May 2023 16:16:34 +0300 Subject: [PATCH 11/11] [mono][interp] Ensure class is initialized before getting aot method --- src/mono/mono/mini/interp/transform.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index b552d720ba591f..da8c564332bfc6 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -1225,6 +1225,7 @@ mono_interp_jit_call_supported (MonoMethod *method, MonoMethodSignature *sig) if (mono_aot_only && m_class_get_image (method->klass)->aot_module && !(method->iflags & METHOD_IMPL_ATTRIBUTE_SYNCHRONIZED)) { ERROR_DECL (error); + mono_class_init_internal (method->klass); gpointer addr = mono_aot_get_method (method, error); if (addr && is_ok (error)) { MonoAotMethodFlags flags = mono_aot_get_method_flags (addr);