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.