IL2CPP: Proposal: Replace typeof(T) == typeof(struct-type) IL sequence with constant boolean value

# Summary
There is an important JIT time optimization of typeof(T). See: ジェネリックな typeof(T) == typeof(...) は JIT 時最適化で消えるかどうか · GitHub

Current IL2CPP does not optimize this topic.
The generated code needs some runtime function calls and it’s inefficient.

If this typeof(T) == typeof(int) → true/false optimization is performed, the size of the executing file will be reduced and performance will get better.

# More Specific

typeof(T) is a JIT-time constant as well as sizeof(T).
Current IL2CPP.exe emits C++ codes of the Generic Type/Methods.
Those C++ codes are shared among reference-types.
The struct-types have specialized C++ codes.

Sample C# Source Code

I will talk about this code.

using System;
using System.Globalization;
using TMPro;
using UnityEngine;

using Unity.Mathematics;

public unsafe class ArrayEmbed : MonoBehaviour
{
    [SerializeField] private TMP_Text text = default;
  
    void Start()
    {
        text.text = C<int>.D().ToString();
        text.text += C<uint>.D().ToString();
        text.text += C<string>.D().ToString();
        text.text += sizeof(Y).ToString();
        text.text += E<decimal>.D().ToString();
        text.text += E<byte>.D().ToString();
        text.text += E<long>.D().ToString();
    }
}

public struct Y
{
    public int V;
}

public static class C<T>
{
    public static int D()
    {
        if (typeof(T) == typeof(int)) return 1;
        else if (typeof(T) == typeof(string)) return -1;
        else return typeof(T).GetHashCode();
    }
}

public static unsafe class E<T> where T : unmanaged
{
    public static int D()
    {
        if (typeof(T) == typeof(int)) return sizeof(T);
        else return sizeof(T);
    }
}

The sample code is compiled and its IL representation is SharpLab.

  • ldtoken type-name or type-parameter

  • call System.Type System.Type:: GetTypeFromHandle(System.RuntimeTypeHandle)

  • ldtoken type-name or type-parameter

  • call System.Type System.Type:: GetTypeFromHandle(System.RuntimeTypeHandle)

  • call `bool System.Type:: op_Equality(System.Type, System.Type)

The above IL sequence is very important pattern.
2 type name is taken and 1 boolean value is put on the stack.
IL2CPP.exe knows those 2 input type names and can determine true or false.
For the .NET JIT, typeof(T) == typeof(int) is a constant.

typeof(T) == typeof(struct-type) is often written in the if-statement.
If it is replaced with constant boolean value, the C++ compiler will delete the never used branch from the execution file.

Current IL2CPP generated code

// System.Int32 C`1<System.Int32>::smile:()
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR int32_t C_1_D_m23EAB822E50B3406618716DA00861C16A3E768D7_gshared (const RuntimeMethod* method)
{
    static bool s_Il2CppMethodInitialized;
    if (!s_Il2CppMethodInitialized)
    {
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var);
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&String_t_0_0_0_var);
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Type_t_il2cpp_TypeInfo_var);
        s_Il2CppMethodInitialized = true;
    }
    {
        // if (typeof(T) == typeof(int)) return 1;
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_0 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
        IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
        Type_t * L_1;
        L_1 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_0, /*hidden argument*/NULL);
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_2 = { reinterpret_cast<intptr_t> (Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var) };
        Type_t * L_3;
        L_3 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_2, /*hidden argument*/NULL);
        bool L_4;
        L_4 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_1, (Type_t *)L_3, /*hidden argument*/NULL);
        if (!L_4)
        {
            goto IL_001d;
        }
    }
    {
        // if (typeof(T) == typeof(int)) return 1;
        return 1;
    }

IL_001d:
    {
        // else if (typeof(T) == typeof(string)) return -1;
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_5 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
        IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
        Type_t * L_6;
        L_6 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_5, /*hidden argument*/NULL);
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_7 = { reinterpret_cast<intptr_t> (String_t_0_0_0_var) };
        Type_t * L_8;
        L_8 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_7, /*hidden argument*/NULL);
        bool L_9;
        L_9 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_6, (Type_t *)L_8, /*hidden argument*/NULL);
        if (!L_9)
        {
            goto IL_003a;
        }
    }
    {
        // else if (typeof(T) == typeof(string)) return -1;
        return (-1);
    }

IL_003a:
    {
        // else return typeof(T).GetHashCode();
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_10 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
        IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
        Type_t * L_11;
        L_11 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_10, /*hidden argument*/NULL);
        NullCheck((RuntimeObject *)L_11);
        int32_t L_12;
        L_12 = VirtFuncInvoker0< int32_t >::Invoke(2 /* System.Int32 System.Object::GetHashCode() */, (RuntimeObject *)L_11);
        return L_12;
    }
}

// System.Int32 C`1<System.Object>::smile:()
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR int32_t C_1_D_m60360274BCBAAC8A294C353B2B0184D60E01B690_gshared (const RuntimeMethod* method)
{
    static bool s_Il2CppMethodInitialized;
    if (!s_Il2CppMethodInitialized)
    {
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var);
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&String_t_0_0_0_var);
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Type_t_il2cpp_TypeInfo_var);
        s_Il2CppMethodInitialized = true;
    }
    {
        // if (typeof(T) == typeof(int)) return 1;
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_0 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
        IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
        Type_t * L_1;
        L_1 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_0, /*hidden argument*/NULL);
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_2 = { reinterpret_cast<intptr_t> (Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var) };
        Type_t * L_3;
        L_3 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_2, /*hidden argument*/NULL);
        bool L_4;
        L_4 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_1, (Type_t *)L_3, /*hidden argument*/NULL);
        if (!L_4)
        {
            goto IL_001d;
        }
    }
    {
        // if (typeof(T) == typeof(int)) return 1;
        return 1;
    }

IL_001d:
    {
        // else if (typeof(T) == typeof(string)) return -1;
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_5 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
        IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
        Type_t * L_6;
        L_6 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_5, /*hidden argument*/NULL);
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_7 = { reinterpret_cast<intptr_t> (String_t_0_0_0_var) };
        Type_t * L_8;
        L_8 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_7, /*hidden argument*/NULL);
        bool L_9;
        L_9 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_6, (Type_t *)L_8, /*hidden argument*/NULL);
        if (!L_9)
        {
            goto IL_003a;
        }
    }
    {
        // else if (typeof(T) == typeof(string)) return -1;
        return (-1);
    }

IL_003a:
    {
        // else return typeof(T).GetHashCode();
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_10 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
        IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
        Type_t * L_11;
        L_11 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_10, /*hidden argument*/NULL);
        NullCheck((RuntimeObject *)L_11);
        int32_t L_12;
        L_12 = VirtFuncInvoker0< int32_t >::Invoke(2 /* System.Int32 System.Object::GetHashCode() */, (RuntimeObject *)L_11);
        return L_12;
    }
}

sizeof(T) is treated as my proposal.
sizeof(T) is replaced with sizeof(uint8_t).

// System.Int32 E`1<System.Byte>::smile:()
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR int32_t E_1_D_m634365F34758541C291A04E1051FDEB0544A1658_gshared (const RuntimeMethod* method)
{
    static bool s_Il2CppMethodInitialized;
    if (!s_Il2CppMethodInitialized)
    {
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var);
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Type_t_il2cpp_TypeInfo_var);
        s_Il2CppMethodInitialized = true;
    }
    {
        // if (typeof(T) == typeof(int)) return sizeof(T);
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_0 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
        IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
        Type_t * L_1;
        L_1 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_0, /*hidden argument*/NULL);
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_2 = { reinterpret_cast<intptr_t> (Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var) };
        Type_t * L_3;
        L_3 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_2, /*hidden argument*/NULL);
        bool L_4;
        L_4 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_1, (Type_t *)L_3, /*hidden argument*/NULL);
        // if (typeof(T) == typeof(int)) return sizeof(T);
        uint32_t L_5 = sizeof(uint8_t);
        return L_5;
    }
}

// System.Int32 E`1<System.Decimal>::smile:()
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR int32_t E_1_D_m78DC72B793035E6CC29AEC8E50901E774F5E6454_gshared (const RuntimeMethod* method)
{
    static bool s_Il2CppMethodInitialized;
    if (!s_Il2CppMethodInitialized)
    {
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var);
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Type_t_il2cpp_TypeInfo_var);
        s_Il2CppMethodInitialized = true;
    }
    {
        // if (typeof(T) == typeof(int)) return sizeof(T);
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_0 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
        IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
        Type_t * L_1;
        L_1 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_0, /*hidden argument*/NULL);
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_2 = { reinterpret_cast<intptr_t> (Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var) };
        Type_t * L_3;
        L_3 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_2, /*hidden argument*/NULL);
        bool L_4;
        L_4 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_1, (Type_t *)L_3, /*hidden argument*/NULL);
        // if (typeof(T) == typeof(int)) return sizeof(T);
        uint32_t L_5 = sizeof(Decimal_t2978B229CA86D3B7BA66A0AEEE014E0DE4F940D7 );
        return L_5;
    }
}

// System.Int32 E`1<System.Int64>::smile:()
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR int32_t E_1_D_m6598740E9AE16F8A622607D19DE92BDBCBF23F1F_gshared (const RuntimeMethod* method)
{
    static bool s_Il2CppMethodInitialized;
    if (!s_Il2CppMethodInitialized)
    {
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var);
        il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Type_t_il2cpp_TypeInfo_var);
        s_Il2CppMethodInitialized = true;
    }
    {
        // if (typeof(T) == typeof(int)) return sizeof(T);
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_0 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
        IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
        Type_t * L_1;
        L_1 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_0, /*hidden argument*/NULL);
        RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_2 = { reinterpret_cast<intptr_t> (Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var) };
        Type_t * L_3;
        L_3 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_2, /*hidden argument*/NULL);
        bool L_4;
        L_4 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_1, (Type_t *)L_3, /*hidden argument*/NULL);
        // if (typeof(T) == typeof(int)) return sizeof(T);
        uint32_t L_5 = sizeof(int64_t);
        return L_5;
    }
}
4 Likes

Thanks for the suggestion. I agree, this would be a great optimization. I’ve added it to our roadmap.

3 Likes

Thank you!

1 Like