Generic sharing with value types

Let’s jump back now to the HelloWorld_DemonstrateGenericSharing_m4 function and look at the implementation for GenericType<DateTime>. The DateTime type is a value type, so GenericType<DateTime> is not shared. We can jump to the declaration of constructor for this type, GenericType_1__ctor_m10. There we see a #define, as in the other cases, but the #define maps to the GenericType_1__ctor_m10_gshared function, which is specific to the GenericType<DateTime> class, and is not used by any other class.

Thinking about generic sharing conceptually

The implementation of generic sharing can be difficult to understand and follow. The problem space itself is fraught with pathological cases (e.g. the curiously recurring template pattern). It can help to think about a few concepts:

  • Every method implementation on a generic type is shared
  • Some generic types only share method implementations with themselves (e.g. generic types with a value type generic parameter, GenericType above)
  • Generic types with a reference type generic parameter are fully shared - they always use the implementation with System.Object for all type parameters.
  • Generic types with two or more type parameters can be partially shared if at least one of those type parameters is a reference type.

The il2cpp.exe utility always generates the fully shared method implementations for any generic type. It generates other method implementations only when they are used.

Sharing of generic methods

Just as method implementations on generic types can be shared, so can method implementation for generic methods. In the original script code, notice that the UsesDifferentGenericParameter method uses a different type parameter than the GenericType class. When we looked at the shared method implementations for the GenericType class, we did not see the UsesDifferentGenericParameter method. If I search the generated code for “UsesDifferentGenericParameter” I see that the implementation of this method is in the GenericMethods0.cpp file:

                extern "C" Object_t * GenericType_1_UsesDifferentGenericParameter_TisObject_t_m15243_gshared (GenericType_1_t2159 * __this, Object_t * ___value, MethodInfo* method)
{
{
Object_t * L_0 = ___value;
return L_0;
}
}

            

Notice that this the fully shared version of the method implementation, accepting the type Object_t*. Although this method is in a generic type, the behavior would be the same for a generic method in a non-generic type as well. Effectively, il2cpp.exe attempts to always generate the least code possible for method implementations involving generic parameters.

Conclusion

Generic sharing has been one of the most important improvements to the IL2CPP scripting backend since its initial release. It allows the generated C++ code to be as small as possible, sharing method implementations where they do not differ in behavior. As we look to continue to decrease binary size, we will work to take advantage of more opportunities to share method implementations.

In the next post, we will explore how p/invoke wrappers are generated, and how types are marshaled from managed to native code. We will be able to see the cost of marshaling various types, and debug problems with marshaling code.