Implement fast stelemref.
authorRodrigo Kumpera <kumpera@gmail.com>
Fri, 8 Oct 2010 19:32:08 +0000 (16:32 -0300)
committerRodrigo Kumpera <kumpera@gmail.com>
Sat, 9 Oct 2010 01:17:04 +0000 (22:17 -0300)
* class.c (mono_class_setup_vtable_general): Inject
the array stelemref method on vectors of reference types.

* class.c (mono_class_init): Properly handle the fact
that arrays have different vtable sizes.

* marshal.c (mono_marshal_get_virtual_stelemref): New
function that returns a better tailored stelemref wrapper
for a given array type.

* method-to-ir.c (mono_method_to_ir): Emit a virtual
call to the new faster stelemref wrapper.

This schema uses 4 wrappers instead of one and puts
it into the vtable of vectors of reference types.

This change improves a synthetic benchmark of an
equal mix of stores as compiling corlib by 25%.

It improves pystones by 3-4%.

mono/metadata/class.c
mono/metadata/marshal.c
mono/metadata/marshal.h
mono/mini/method-to-ir.c

index d4f0a8ecb67117a10531008a15e2d0d1eff9fe8f..a6435c3fddcce5bbb4e64d7fa0f66817efb19b7e 100644 (file)
@@ -3752,6 +3752,13 @@ verify_class_overrides (MonoClass *class, MonoMethod **overrides, int onum)
        }
        return TRUE;
 }
+
+static gboolean
+mono_class_need_stelemref_method (MonoClass *class)
+{
+       return class->rank == 1 && MONO_TYPE_IS_REFERENCE (&class->element_class->byval_arg);
+}
+
 /*
  * LOCKING: this is supposed to be called with the loader lock held.
  */
@@ -3771,6 +3778,7 @@ mono_class_setup_vtable_general (MonoClass *class, MonoMethod **overrides, int o
        int first_non_interface_slot;
 #endif
        GSList *virt_methods = NULL, *l;
+       int stelemref_slot = 0;
 
        if (class->vtable)
                return;
@@ -3811,6 +3819,13 @@ mono_class_setup_vtable_general (MonoClass *class, MonoMethod **overrides, int o
 
        max_vtsize += class->method.count;
 
+       /*Array have a slot for stelemref*/
+       if (mono_class_need_stelemref_method (class)) {
+               stelemref_slot = cur_slot;
+               ++max_vtsize;
+               ++cur_slot;
+       }
+
        vtable = alloca (sizeof (gpointer) * max_vtsize);
        memset (vtable, 0, sizeof (gpointer) * max_vtsize);
 
@@ -3897,6 +3912,17 @@ mono_class_setup_vtable_general (MonoClass *class, MonoMethod **overrides, int o
                }
        }
 
+       /*Array have a slot for stelemref*/
+       if (mono_class_need_stelemref_method (class)) {
+               MonoMethod *method = mono_marshal_get_virtual_stelemref (class);
+               if (!method->slot)
+                       method->slot = stelemref_slot;
+               else
+                       g_assert (method->slot == stelemref_slot);
+
+               vtable [stelemref_slot] = method;
+       }
+
        TRACE_INTERFACE_VTABLE (print_vtable_full (class, vtable, cur_slot, first_non_interface_slot, "AFTER INHERITING PARENT VTABLE", TRUE));
        /* override interface methods */
        for (i = 0; i < onum; i++) {
@@ -4621,14 +4647,19 @@ mono_class_init (MonoClass *class)
                class->ghcimpl = cached_info.ghcimpl;
                class->has_cctor = cached_info.has_cctor;
        } else if (class->rank == 1 && class->byval_arg.type == MONO_TYPE_SZARRAY) {
-               static int szarray_vtable_size = 0;
+               /* SZARRAY can have 2 vtable layouts, with and without the stelemref method.
+                * The first slot if for array with.
+                */
+               static int szarray_vtable_size[2] = { 0 };
+
+               int slot = MONO_TYPE_IS_REFERENCE (&class->element_class->byval_arg) ? 0 : 1;
 
                /* SZARRAY case */
-               if (!szarray_vtable_size) {
+               if (!szarray_vtable_size [slot]) {
                        mono_class_setup_vtable (class);
-                       szarray_vtable_size = class->vtable_size;
+                       szarray_vtable_size [slot] = class->vtable_size;
                } else {
-                       class->vtable_size = szarray_vtable_size;
+                       class->vtable_size = szarray_vtable_size[slot];
                }
        } else if (class->generic_class && !MONO_CLASS_IS_INTERFACE (class)) {
                MonoClass *gklass = class->generic_class->container_class;
@@ -4726,6 +4757,7 @@ mono_class_init (MonoClass *class)
        }
 
        if (class->parent) {
+               int first_iface_slot;
                /* This will compute class->parent->vtable_size for some classes */
                mono_class_init (class->parent);
                if (class->parent->exception_type) {
@@ -4744,7 +4776,10 @@ mono_class_init (MonoClass *class)
                        if (mono_loader_get_last_error ())
                                goto leave;
                }
-               setup_interface_offsets (class, class->parent->vtable_size);
+               first_iface_slot = class->parent->vtable_size;
+               if (mono_class_need_stelemref_method (class))
+                       ++first_iface_slot;
+               setup_interface_offsets (class, first_iface_slot);
        } else {
                setup_interface_offsets (class, 0);
        }
index 8917f227eab7086f64a1ae13cd4c3432a7d0ba80..09d1d09a1f32956a27cc13889e1412277b3b986b 100644 (file)
@@ -9562,6 +9562,361 @@ mono_marshal_get_unbox_wrapper (MonoMethod *method)
        return res;     
 }
 
+enum {
+       STELEMREF_OBJECT, /*no check at all*/
+       STELEMREF_SEALED_CLASS, /*check vtable->klass->element_type */
+       STELEMREF_CLASS, /*only the klass->parents check*/
+       STELEMREF_COMPLEX, /*arrays, interfaces, MBR or classes with variant generic args - go straight to icalls*/
+       STELEMREF_KIND_COUNT
+};
+
+static const char *strelemref_wrapper_name[] = {
+       "object", "sealed_class", "class", "complex"
+};
+
+static gboolean
+is_monomorphic_array (MonoClass *klass)
+{
+       MonoClass *element_class;
+       if (klass->rank != 1)
+               return FALSE;
+
+       element_class = klass->element_class;
+       return (element_class->flags & TYPE_ATTRIBUTE_SEALED) || element_class->valuetype;
+}
+
+static int
+get_virtual_stelemref_kind (MonoClass *element_class)
+{
+       if (element_class == mono_defaults.object_class)
+               return STELEMREF_OBJECT;
+       if (is_monomorphic_array (element_class))
+               return STELEMREF_SEALED_CLASS;
+       /*Arrays are sealed but are covariant on their element type, We can't use any of the fast paths.*/
+       if (MONO_CLASS_IS_INTERFACE (element_class) || element_class->marshalbyref || element_class->rank || mono_class_has_variant_generic_params (element_class))
+               return STELEMREF_COMPLEX;
+       if (element_class->flags & TYPE_ATTRIBUTE_SEALED)
+               return STELEMREF_SEALED_CLASS;
+       return STELEMREF_CLASS;
+}
+
+static void
+load_array_element_address (MonoMethodBuilder *mb)
+{
+       mono_mb_emit_ldarg (mb, 0);
+       mono_mb_emit_ldarg (mb, 1);
+       mono_mb_emit_op (mb, CEE_LDELEMA, mono_defaults.object_class);
+}
+
+static void
+load_array_class (MonoMethodBuilder *mb, int aklass)
+{
+       mono_mb_emit_ldarg (mb, 0);
+       mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoObject, vtable));
+       mono_mb_emit_byte (mb, CEE_LDIND_I);
+       mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoVTable, klass));
+       mono_mb_emit_byte (mb, CEE_LDIND_I);
+       mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoClass, element_class));
+       mono_mb_emit_byte (mb, CEE_LDIND_I);
+       mono_mb_emit_stloc (mb, aklass);
+}
+
+static void
+load_value_class (MonoMethodBuilder *mb, int vklass)
+{
+       mono_mb_emit_ldarg (mb, 2);
+       mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoObject, vtable));
+       mono_mb_emit_byte (mb, CEE_LDIND_I);
+       mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoVTable, klass));
+       mono_mb_emit_byte (mb, CEE_LDIND_I);
+       mono_mb_emit_stloc (mb, vklass);
+}
+
+#if 0
+static void
+record_vstore_2 (MonoObject *array, size_t index, MonoObject *value)
+{
+       char *name = mono_type_get_full_name (array->vtable->klass->element_class);
+       printf ("slow vstore of %s\n", name);
+       g_free (name);
+}
+#endif
+
+/*
+ * TODO:
+ *     - Separate simple interfaces from variant interfaces or mbr types. This way we can avoid the icall for them.
+ *     - Emit a (new) mono bytecode that produces OP_COND_EXC_NE_UN to raise ArrayTypeMismatch
+ *     - Maybe mve some MonoClass field into the vtable to reduce the number of loads
+ *     - Add a case for arrays of arrays.
+ */
+MonoMethod*
+mono_marshal_get_virtual_stelemref (MonoClass *array_class)
+{
+       static MonoMethod *cached_methods [STELEMREF_KIND_COUNT] = { NULL }; /*object iface sealed regular*/
+       static MonoMethodSignature *signature;
+       MonoMethodBuilder *mb;
+       MonoMethod *res;
+       int kind;
+
+       guint32 b1, b2, b3;
+       int aklass, vklass;
+       int array_slot_addr;
+
+       g_assert (array_class->rank == 1);
+       kind = get_virtual_stelemref_kind (array_class->element_class);
+
+       if (cached_methods [kind])
+               return cached_methods [kind];
+
+       mb = mono_mb_new_no_dup_name (mono_defaults.object_class, g_strdup_printf ("virt_stelemref_%s", strelemref_wrapper_name [kind]), MONO_WRAPPER_STELEMREF);
+
+       if (!signature) {
+               MonoMethodSignature *sig = mono_metadata_signature_alloc (mono_defaults.corlib, 2);
+
+               /* void this::stelemref (size_t idx, void* value) */
+               sig->ret = &mono_defaults.void_class->byval_arg;
+               sig->hasthis = TRUE;
+               sig->params [0] = &mono_defaults.int_class->byval_arg; /* this is a natural sized int */
+               sig->params [1] = &mono_defaults.object_class->byval_arg;
+               signature = sig;
+       }
+
+       /*For now simply call plain old stelemref*/
+       switch (kind) {
+       case STELEMREF_OBJECT:
+               /* ldelema (implicit bound check) */
+               load_array_element_address (mb);
+               /* do_store */
+               mono_mb_emit_ldarg (mb, 2);
+               mono_mb_emit_byte (mb, CEE_STIND_REF);
+               mono_mb_emit_byte (mb, CEE_RET);
+               break;
+
+       case STELEMREF_COMPLEX:
+               /*
+               <ldelema (bound check)>
+               if (!value)
+                       goto store;
+               if (!mono_object_isinst (value, aklass))
+                       goto do_exception;
+
+                do_store:
+                        *array_slot_addr = value;
+
+               do_exception:
+                       throw new ArrayTypeMismatchException ();
+               */
+
+               aklass = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
+               array_slot_addr = mono_mb_add_local (mb, &mono_defaults.object_class->this_arg);
+
+#if 0
+               {
+                       /*Use this to debug stores that are going thru the slow path*/
+                       MonoMethodSignature *csig;
+                       csig = mono_metadata_signature_alloc (mono_defaults.corlib, 3);
+                       csig->ret = &mono_defaults.void_class->byval_arg;
+                       csig->params [0] = &mono_defaults.object_class->byval_arg;
+                       csig->params [1] = &mono_defaults.int_class->byval_arg; /* this is a natural sized int */
+                       csig->params [2] = &mono_defaults.object_class->byval_arg;
+                       mono_mb_emit_ldarg (mb, 0);
+                       mono_mb_emit_ldarg (mb, 1);
+                       mono_mb_emit_ldarg (mb, 2);
+                       mono_mb_emit_native_call (mb, csig, record_vstore_2);
+               }
+#endif
+
+               /* ldelema (implicit bound check) */
+               load_array_element_address (mb);
+               mono_mb_emit_stloc (mb, array_slot_addr);
+
+               /* if (!value) goto do_store */
+               mono_mb_emit_ldarg (mb, 2);
+               b1 = mono_mb_emit_branch (mb, CEE_BRFALSE);
+
+               /* aklass = array->vtable->klass->element_class */
+               load_array_class (mb, aklass);
+
+               /*if (mono_object_isinst (value, aklass)) */
+               mono_mb_emit_ldarg (mb, 2);
+               mono_mb_emit_ldloc (mb, aklass);
+               mono_mb_emit_icall (mb, mono_object_isinst);
+               b2 = mono_mb_emit_branch (mb, CEE_BRFALSE);
+
+               /* do_store: */
+               mono_mb_patch_branch (mb, b1);
+               mono_mb_emit_ldloc (mb, array_slot_addr);
+               mono_mb_emit_ldarg (mb, 2);
+               mono_mb_emit_byte (mb, CEE_STIND_REF);
+               mono_mb_emit_byte (mb, CEE_RET);
+
+               /* do_exception: */
+               mono_mb_patch_branch (mb, b2);
+
+               mono_mb_emit_exception (mb, "ArrayTypeMismatchException", NULL);
+               break;
+
+       case STELEMREF_SEALED_CLASS:
+               /*
+               <ldelema (bound check)>
+               if (!value)
+                       goto store;
+
+               aklass = array->vtable->klass->element_class;
+               vklass = value->vtable->klass;
+
+               if (vklass != aklass)
+                       goto do_exception;
+
+               do_store:
+                        *array_slot_addr = value;
+
+               do_exception:
+                       throw new ArrayTypeMismatchException ();
+               */
+               aklass = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
+               vklass = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
+               array_slot_addr = mono_mb_add_local (mb, &mono_defaults.object_class->this_arg);
+
+
+               /* ldelema (implicit bound check) */
+               load_array_element_address (mb);
+               mono_mb_emit_stloc (mb, array_slot_addr);
+
+               /* if (!value) goto do_store */
+               mono_mb_emit_ldarg (mb, 2);
+               b1 = mono_mb_emit_branch (mb, CEE_BRFALSE);
+
+               /* aklass = array->vtable->klass->element_class */
+               load_array_class (mb, aklass);
+
+               /* vklass = value->vtable->klass */
+               load_value_class (mb, vklass);
+
+               /*if (vklass != aklass) goto do_exception; */
+               mono_mb_emit_ldloc (mb, aklass);
+               mono_mb_emit_ldloc (mb, vklass);
+               b2 = mono_mb_emit_branch (mb, CEE_BNE_UN);
+
+               /* do_store: */
+               mono_mb_patch_branch (mb, b1);
+               mono_mb_emit_ldloc (mb, array_slot_addr);
+               mono_mb_emit_ldarg (mb, 2);
+               mono_mb_emit_byte (mb, CEE_STIND_REF);
+               mono_mb_emit_byte (mb, CEE_RET);
+
+               /* do_exception: */
+               mono_mb_patch_branch (mb, b2);
+               mono_mb_emit_exception (mb, "ArrayTypeMismatchException", NULL);
+               break;
+
+       case STELEMREF_CLASS:
+               /*
+               the method:
+               <ldelema (bound check)>
+               if (!value)
+                       goto do_store;
+
+               aklass = array->vtable->klass->element_class;
+               vklass = value->vtable->klass;
+
+               if (vklass->idepth < aklass->idepth)
+                       goto do_exception;
+
+               if (vklass->supertypes [aklass->idepth - 1] != aklass)
+                       goto do_exception;
+
+               do_store:
+                       *array_slot_addr = value;
+                       return;
+
+               long:
+                       throw new ArrayTypeMismatchException ();
+               */
+               aklass = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
+               vklass = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
+               array_slot_addr = mono_mb_add_local (mb, &mono_defaults.object_class->this_arg);
+
+               /* ldelema (implicit bound check) */
+               load_array_element_address (mb);
+               mono_mb_emit_stloc (mb, array_slot_addr);
+
+               /* if (!value) goto do_store */
+               mono_mb_emit_ldarg (mb, 2);
+               b1 = mono_mb_emit_branch (mb, CEE_BRFALSE);
+
+               /* aklass = array->vtable->klass->element_class */
+               load_array_class (mb, aklass);
+
+               /* vklass = value->vtable->klass */
+               load_value_class (mb, vklass);
+
+               /*if (mono_object_isinst (value, aklass)) */
+               mono_mb_emit_ldarg (mb, 2);
+               mono_mb_emit_ldloc (mb, aklass);
+               mono_mb_emit_icall (mb, mono_object_isinst);
+               b2 = mono_mb_emit_branch (mb, CEE_BRFALSE);
+
+               /* if (vklass->idepth < aklass->idepth) goto failue */
+               mono_mb_emit_ldloc (mb, vklass);
+               mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoClass, idepth));
+               mono_mb_emit_byte (mb, CEE_LDIND_U2);
+
+               mono_mb_emit_ldloc (mb, aklass);
+               mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoClass, idepth));
+               mono_mb_emit_byte (mb, CEE_LDIND_U2);
+
+               b2 = mono_mb_emit_branch (mb, CEE_BLT_UN);
+
+               /* if (vklass->supertypes [aklass->idepth - 1] != aklass) goto failure */
+               mono_mb_emit_ldloc (mb, vklass);
+               mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoClass, supertypes));
+               mono_mb_emit_byte (mb, CEE_LDIND_I);
+
+               mono_mb_emit_ldloc (mb, aklass);
+               mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoClass, idepth));
+               mono_mb_emit_byte (mb, CEE_LDIND_U2);
+               mono_mb_emit_icon (mb, 1);
+               mono_mb_emit_byte (mb, CEE_SUB);
+               mono_mb_emit_icon (mb, sizeof (void*));
+               mono_mb_emit_byte (mb, CEE_MUL);
+               mono_mb_emit_byte (mb, CEE_ADD);
+               mono_mb_emit_byte (mb, CEE_LDIND_I);
+
+               mono_mb_emit_ldloc (mb, aklass);
+               b3 = mono_mb_emit_branch (mb, CEE_BNE_UN);
+
+               /* do_store: */
+               mono_mb_patch_branch (mb, b1);
+               mono_mb_emit_ldloc (mb, array_slot_addr);
+               mono_mb_emit_ldarg (mb, 2);
+               mono_mb_emit_byte (mb, CEE_STIND_REF);
+               mono_mb_emit_byte (mb, CEE_RET);
+
+               /* do_exception: */
+               mono_mb_patch_branch (mb, b2);
+               mono_mb_patch_branch (mb, b3);
+
+               mono_mb_emit_exception (mb, "ArrayTypeMismatchException", NULL);
+               break;
+
+       default:
+               mono_mb_emit_ldarg (mb, 0);
+               mono_mb_emit_ldarg (mb, 1);
+               mono_mb_emit_ldarg (mb, 2);
+               mono_mb_emit_managed_call (mb, mono_marshal_get_stelemref (), NULL);
+               mono_mb_emit_byte (mb, CEE_RET);
+               g_assert (0);
+       }
+
+       res = mono_mb_create_method (mb, signature, 4);
+       res->flags |= METHOD_ATTRIBUTE_VIRTUAL;
+       cached_methods [kind] = res;
+
+       mono_mb_free (mb);
+       return res;
+}
+
 MonoMethod*
 mono_marshal_get_stelemref ()
 {
index dd7f1175faf154c4c458c8a05f196b159cbd7fe5..7baa9035cac89c2a663d82a3032e5dea87911e03 100644 (file)
@@ -265,6 +265,9 @@ mono_marshal_get_proxy_cancast (MonoClass *klass) MONO_INTERNAL;
 MonoMethod *
 mono_marshal_get_stelemref (void) MONO_INTERNAL;
 
+MonoMethod*
+mono_marshal_get_virtual_stelemref (MonoClass *array_class) MONO_INTERNAL;
+
 MonoMethod*
 mono_marshal_get_array_address (int rank, int elem_size) MONO_INTERNAL;
 
index 1b81519d2260547baef2899ef175a2b193458f37..8a4ba0eb880d7a0006e53448b44fa5985bee451c 100644 (file)
@@ -8955,9 +8955,14 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                        /* storing a NULL doesn't need any of the complex checks in stelemref */
                        if (generic_class_is_reference_type (cfg, klass) &&
                                !(sp [2]->opcode == OP_PCONST && sp [2]->inst_p0 == NULL)) {
-                               MonoMethod* helper = mono_marshal_get_stelemref ();
+                               MonoClass *obj_array = mono_array_class_get_cached (mono_defaults.object_class, 1);
+                               MonoMethod *helper = mono_marshal_get_virtual_stelemref (obj_array);
                                MonoInst *iargs [3];
 
+                               if (!helper->slot)
+                                       mono_class_setup_vtable (obj_array);
+                               g_assert (helper->slot);
+
                                if (sp [0]->type != STACK_OBJ)
                                        UNVERIFIED;
                                if (sp [2]->type != STACK_OBJ)
@@ -8966,8 +8971,8 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                                iargs [2] = sp [2];
                                iargs [1] = sp [1];
                                iargs [0] = sp [0];
-                               
-                               mono_emit_method_call (cfg, helper, iargs, NULL);
+
+                               mono_emit_method_call (cfg, helper, iargs, sp [0]);
                        } else {
                                if (sp [1]->opcode == OP_ICONST) {
                                        int array_reg = sp [0]->dreg;