[runtime] Implement lazy interface implementation for arrays.
authorRodrigo Kumpera <kumpera@gmail.com>
Mon, 21 Nov 2016 23:22:08 +0000 (15:22 -0800)
committerRodrigo Kumpera <kumpera@gmail.com>
Mon, 28 Nov 2016 23:10:15 +0000 (15:10 -0800)
We have the following set of problems:

Arrays implement interfaces that are invariant but behave like covariant.
Those are IList`1, ICollection`1, IEnumerable`1

Arrays of primitive types are type equivalent based on their underlying type.
This means int[], uint[] and IntEnum[] are all interchangeable. And so is casting among their interfaces.
Meaning this is valid: `(IList<IntEnum>)(object)new int[1]`.

Finally, the way we implement IEnumerator`1 for arrays forces it to behave just like the other 3 interfaces.

Implementation:

Introduce MonoClass::is_array_special_interface, set for all the above interfaces. This allow fast checking of the interfaces in question.

mono_class_is_assignable_from now includes the array casting rules.

Change mono_class_interface_offset_with_variance to take arrays into account when looking for a candidate iface.

The JIT implements casting of those interfaces using the cast with cache wrapper.

Finally, to handle the weird primitive rules, set the cast_class of sbyte, ushort, uint and ulong to their same-size buddies.
This makes it easier for the casting code to resolve this problem.

Problems:

Casting performance is terrible, anywhere from 60% to 1000% slower on a micro-benchmark.
Can be improved by extending our casting wrapper to handle those interfaces (probably would have to give up inlining as they would be huge)

The code in domain.c is icky, but not sure where to put it otherwise.

IEnumerator could be solved in Array.cs by not forcing it to be magic as well. Meaning the folowing

```
object array = new int[1];
var a = array as IList<int>;
var b = array as IList<uint>;

a.GetEnumerator().GetType () != b.GetEnumerator().GetType ();
```

The last line is false today but it's harmless to change it to avoid the casting penalty for such central type.

mono/metadata/class-internals.h
mono/metadata/class.c
mono/metadata/domain.c
mono/metadata/object.c
mono/mini/method-to-ir.c

index edba79cd320f048e8c61a2949d8a889212e65f84..45d96dc24d681a9622d828b13da5995ab931138e 100644 (file)
@@ -317,6 +317,7 @@ struct _MonoClass {
        guint has_finalize_inited    : 1; /* has_finalize is initialized */
        guint fields_inited : 1; /* setup_fields () has finished */
        guint has_failure : 1; /* See mono_class_get_exception_data () for a MonoErrorBoxed with the details */
+       guint is_array_special_interface : 1;
 
        MonoClass  *parent;
        MonoClass  *nested_in;
@@ -1101,6 +1102,8 @@ typedef struct {
        MonoClass *argumenthandle_class;
        MonoClass *monitor_class;
        MonoClass *generic_ilist_class;
+       MonoClass *generic_icollection_class;
+       MonoClass *generic_ienumerable_class;
        MonoClass *generic_nullable_class;
        MonoClass *handleref_class;
        MonoClass *attribute_class;
index 85f9a1319c4f235c304636fbd90862a0d0ace85a..a332083ca1a6250ae7dded897b07f945c0395d5b 100644 (file)
@@ -48,6 +48,8 @@
 #include <mono/utils/bsearch.h>
 #include <mono/utils/checked-build.h>
 
+#define ENABLE_LAZY_ARRAY_IFACES TRUE
+
 MonoStats mono_stats;
 
 gboolean mono_print_vtable = FALSE;
@@ -2985,6 +2987,18 @@ mono_class_interface_offset_with_variance (MonoClass *klass, MonoClass *itf, gbo
        if (i >= 0)
                return i;
        
+       if (itf->is_array_special_interface && klass->rank < 2) {
+               MonoClass *gtd = mono_class_get_generic_type_definition (itf);
+
+               for (i = 0; i < klass->interface_offsets_count; i++) {
+                       // printf ("\t%s\n", mono_type_get_full_name (klass->interfaces_packed [i]));
+                       if (mono_class_get_generic_type_definition (klass->interfaces_packed [i]) == gtd) {
+                               *non_exact_match = TRUE;
+                               return klass->interface_offsets_packed [i];
+                       }
+               }
+       }
+
        if (!mono_class_has_variant_generic_params (itf))
                return -1;
 
@@ -3139,7 +3153,7 @@ get_implicit_generic_array_interfaces (MonoClass *klass, int *num, int *is_enume
        gboolean internal_enumerator;
        gboolean eclass_is_valuetype;
 
-       if (!mono_defaults.generic_ilist_class) {
+       if (!mono_defaults.generic_ilist_class || ENABLE_LAZY_ARRAY_IFACES) {
                *num = 0;
                return NULL;
        }
@@ -6084,12 +6098,14 @@ mono_generic_class_get_class (MonoGenericClass *gclass)
        klass->enumtype = gklass->enumtype;
        klass->valuetype = gklass->valuetype;
 
+
        if (gklass->image->assembly_name && !strcmp (gklass->image->assembly_name, "System.Numerics.Vectors") && !strcmp (gklass->name_space, "System.Numerics") && !strcmp (gklass->name, "Vector`1")) {
                g_assert (gclass->context.class_inst);
                g_assert (gclass->context.class_inst->type_argc > 0);
                if (mono_type_is_primitive (gclass->context.class_inst->type_argv [0]))
                        klass->simd_type = 1;
        }
+       klass->is_array_special_interface = gklass->is_array_special_interface;
 
        klass->cast_class = klass->element_class = klass;
 
@@ -8437,6 +8453,23 @@ mono_class_is_assignable_from (MonoClass *klass, MonoClass *oklass)
                if (MONO_CLASS_IMPLEMENTS_INTERFACE (oklass, klass->interface_id))
                        return TRUE;
 
+               if (klass->is_array_special_interface && oklass->rank == 1) {
+                       //XXX we could offset this by having the cast target computed at JIT time
+                       //XXX we could go even further and emit a wrapper that would do the extra type check
+                       MonoClass *iface_klass = mono_class_from_mono_type (mono_class_get_generic_class (klass)->context.class_inst->type_argv [0]);
+                       MonoClass *obj_klass = oklass->cast_class; //This gets us the cast class of element type of the array
+
+                       // If the target we're trying to cast to is a valuetype, we must account of weird valuetype equivalences such as IntEnum <> int or uint <> int
+                       // We can't apply it for ref types as this would go wrong with arrays - IList<byte[]> would have byte tested
+                       if (iface_klass->valuetype)
+                               iface_klass = iface_klass->cast_class;
+
+                       //array covariant casts only operates on scalar to scalar
+                       //This is so int[] can't be casted to IComparable<int>[]
+                       if (!(obj_klass->valuetype && !iface_klass->valuetype) && mono_class_is_assignable_from (iface_klass, obj_klass))
+                               return TRUE;
+               }
+
                if (mono_class_has_variant_generic_params (klass)) {
                        int i;
                        mono_class_setup_interfaces (oklass, &error);
index 020b0dd00c4406bc0d482714b2af17e65836b0d6..e51ce5ffbcc9bc11fcdca4ef7deed1a1caec6a4e 100644 (file)
@@ -673,6 +673,13 @@ mono_init_internal (const char *filename, const char *exe_filename, const char *
        mono_defaults.uint64_class = mono_class_load_from_name (
                 mono_defaults.corlib, "System", "UInt64");
 
+       //XXX find a better place, which kinda doesn't exists as those types depend on each other.
+       mono_defaults.uint32_class->cast_class = mono_defaults.int32_class;
+       mono_defaults.uint16_class->cast_class = mono_defaults.int16_class;
+       mono_defaults.sbyte_class->cast_class = mono_defaults.byte_class;
+       mono_defaults.uint64_class->cast_class = mono_defaults.int64_class;
+
+
        mono_defaults.single_class = mono_class_load_from_name (
                 mono_defaults.corlib, "System", "Single");
 
@@ -801,11 +808,28 @@ mono_init_internal (const char *filename, const char *exe_filename, const char *
        mono_class_init (mono_defaults.array_class);
        mono_defaults.generic_nullable_class = mono_class_load_from_name (
                mono_defaults.corlib, "System", "Nullable`1");
-       mono_defaults.generic_ilist_class = mono_class_load_from_name (
-               mono_defaults.corlib, "System.Collections.Generic", "IList`1");
        mono_defaults.generic_ireadonlylist_class = mono_class_load_from_name (
                mono_defaults.corlib, "System.Collections.Generic", "IReadOnlyList`1");
 
+       //IList, ICollection, IEnumerable
+       mono_defaults.generic_ilist_class = mono_class_load_from_name (
+               mono_defaults.corlib, "System.Collections.Generic", "IList`1");
+       mono_defaults.generic_icollection_class = mono_class_load_from_name (
+               mono_defaults.corlib, "System.Collections.Generic", "ICollection`1");
+       mono_defaults.generic_ienumerable_class = mono_class_load_from_name (
+               mono_defaults.corlib, "System.Collections.Generic", "IEnumerable`1");
+
+       //XXX This is a hack, there's probably a better place to do this. Probably in mono_class_create_from_typedef
+       mono_defaults.generic_ilist_class->is_array_special_interface = 1;
+       mono_defaults.generic_icollection_class->is_array_special_interface = 1;
+       mono_defaults.generic_ienumerable_class->is_array_special_interface = 1;
+
+       //FIXME IEnumerator needs to be special because GetEnumerator uses magic under the hood
+       MonoClass *tmp = mono_class_load_from_name (
+               mono_defaults.corlib, "System.Collections.Generic", "IEnumerator`1");
+       tmp->is_array_special_interface = 1;
+
+
        mono_defaults.threadpool_wait_callback_class = mono_class_load_from_name (
                mono_defaults.corlib, "System.Threading", "_ThreadPoolWaitCallback");
 
index c0a600ef960f43d4e4e189fa67a62c96aa490f6d..b906e831e4b81d51cec2ec484127cfe5aa45ad6d 100644 (file)
@@ -75,6 +75,7 @@ static GENERATE_GET_CLASS_WITH_CACHE (activation_services, System.Runtime.Remoti
 #define ldstr_unlock() mono_os_mutex_unlock (&ldstr_section)
 static mono_mutex_t ldstr_section;
 
+
 /**
  * mono_runtime_object_init:
  * @this_obj: the object to initialize
@@ -6476,6 +6477,15 @@ mono_object_isinst_mbyref_checked (MonoObject *obj, MonoClass *klass, MonoError
        MONO_REQ_GC_UNSAFE_MODE;
 
        MonoVTable *vt;
+       static gboolean inited = FALSE;
+       static int special_array_iface_tests, special_array_iface_tests_fails;
+       if (!inited) {
+               inited = TRUE;
+               mono_counters_register ("Special array interfaces type tests",
+                               MONO_COUNTER_METADATA | MONO_COUNTER_INT, &special_array_iface_tests);
+               mono_counters_register ("Special array interface type tests fails",
+                               MONO_COUNTER_METADATA | MONO_COUNTER_INT, &special_array_iface_tests_fails);
+       }
 
        mono_error_init (error);
 
@@ -6489,8 +6499,17 @@ mono_object_isinst_mbyref_checked (MonoObject *obj, MonoClass *klass, MonoError
                        return obj;
                }
 
+               /* casting an array one of the invariant interfaces that must act as such */
+               if (klass->is_array_special_interface) {
+                       ++special_array_iface_tests;
+                       if (mono_class_is_assignable_from (klass, vt->klass))
+                               return obj;
+                       else
+                               ++special_array_iface_tests_fails;
+               }
+
                /*If the above check fails we are in the slow path of possibly raising an exception. So it's ok to it this way.*/
-               if (mono_class_has_variant_generic_params (klass) && mono_class_is_assignable_from (klass, obj->vtable->klass))
+               else if (mono_class_has_variant_generic_params (klass) && mono_class_is_assignable_from (klass, obj->vtable->klass))
                        return obj;
        } else {
                MonoClass *oklass = vt->klass;
index 81f1e9678541914d572b67b4808ebce6f598975a..df104c2e4fc45583860c41de89cf0d3c794c5fda 100644 (file)
@@ -15152,7 +15152,7 @@ mono_decompose_typecheck (MonoCompile *cfg, MonoBasicBlock *bb, MonoInst *ins)
        NEW_BBLOCK (cfg, first_bb);
        cfg->cbb = first_bb;
 
-       if (!context_used && mini_class_has_reference_variant_generic_argument (cfg, klass, context_used)) {
+       if (!context_used && (mini_class_has_reference_variant_generic_argument (cfg, klass, context_used) || klass->is_array_special_interface)) {
                if (is_isinst)
                        ret = emit_isinst_with_cache_nonshared (cfg, source, klass);
                else