Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

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

Discussion in 'Experimental Scripting Previews' started by pcysl5edgo, Oct 11, 2020.

  1. pcysl5edgo

    pcysl5edgo

    Joined:
    Jun 3, 2018
    Posts:
    65
    # Summary
    There is an important JIT time optimization of typeof(T). See: https://gist.github.com/ufcpp/1f6a47d2e2a90d7d378ce67139c19847

    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.

    I will talk about this code.
    Code (CSharp):
    1. using System;
    2. using System.Globalization;
    3. using TMPro;
    4. using UnityEngine;
    5.  
    6. using Unity.Mathematics;
    7.  
    8. public unsafe class ArrayEmbed : MonoBehaviour
    9. {
    10.     [SerializeField] private TMP_Text text = default;
    11.  
    12.     void Start()
    13.     {
    14.         text.text = C<int>.D().ToString();
    15.         text.text += C<uint>.D().ToString();
    16.         text.text += C<string>.D().ToString();
    17.         text.text += sizeof(Y).ToString();
    18.         text.text += E<decimal>.D().ToString();
    19.         text.text += E<byte>.D().ToString();
    20.         text.text += E<long>.D().ToString();
    21.     }
    22. }
    23.  
    24. public struct Y
    25. {
    26.     public int V;
    27. }
    28.  
    29. public static class C<T>
    30. {
    31.     public static int D()
    32.     {
    33.         if (typeof(T) == typeof(int)) return 1;
    34.         else if (typeof(T) == typeof(string)) return -1;
    35.         else return typeof(T).GetHashCode();
    36.     }
    37. }
    38.  
    39. public static unsafe class E<T> where T : unmanaged
    40. {
    41.     public static int D()
    42.     {
    43.         if (typeof(T) == typeof(int)) return sizeof(T);
    44.         else return sizeof(T);
    45.     }
    46. }

    The sample code is compiled and its IL representation is https://sharplab.io/#v2:EYLgxg9gTgp...QhFOjEgvF6ilSLlsAAvaohWbAtq7VJI1EQjGGJZzIA===.
    1. ldtoken `type-name or type-parameter`
    2. call `System.Type System.Type:: GetTypeFromHandle(System.RuntimeTypeHandle)`
    3. ldtoken `type-name or type-parameter`
    4. call `System.Type System.Type:: GetTypeFromHandle(System.RuntimeTypeHandle)`
    5. 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.

    Code (CSharp):
    1. // System.Int32 C`1<System.Int32>::D()
    2. IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR int32_t C_1_D_m23EAB822E50B3406618716DA00861C16A3E768D7_gshared (const RuntimeMethod* method)
    3. {
    4.     static bool s_Il2CppMethodInitialized;
    5.     if (!s_Il2CppMethodInitialized)
    6.     {
    7.         il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var);
    8.         il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&String_t_0_0_0_var);
    9.         il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Type_t_il2cpp_TypeInfo_var);
    10.         s_Il2CppMethodInitialized = true;
    11.     }
    12.     {
    13.         // if (typeof(T) == typeof(int)) return 1;
    14.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_0 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
    15.         IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
    16.         Type_t * L_1;
    17.         L_1 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_0, /*hidden argument*/NULL);
    18.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_2 = { reinterpret_cast<intptr_t> (Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var) };
    19.         Type_t * L_3;
    20.         L_3 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_2, /*hidden argument*/NULL);
    21.         bool L_4;
    22.         L_4 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_1, (Type_t *)L_3, /*hidden argument*/NULL);
    23.         if (!L_4)
    24.         {
    25.             goto IL_001d;
    26.         }
    27.     }
    28.     {
    29.         // if (typeof(T) == typeof(int)) return 1;
    30.         return 1;
    31.     }
    32.  
    33. IL_001d:
    34.     {
    35.         // else if (typeof(T) == typeof(string)) return -1;
    36.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_5 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
    37.         IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
    38.         Type_t * L_6;
    39.         L_6 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_5, /*hidden argument*/NULL);
    40.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_7 = { reinterpret_cast<intptr_t> (String_t_0_0_0_var) };
    41.         Type_t * L_8;
    42.         L_8 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_7, /*hidden argument*/NULL);
    43.         bool L_9;
    44.         L_9 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_6, (Type_t *)L_8, /*hidden argument*/NULL);
    45.         if (!L_9)
    46.         {
    47.             goto IL_003a;
    48.         }
    49.     }
    50.     {
    51.         // else if (typeof(T) == typeof(string)) return -1;
    52.         return (-1);
    53.     }
    54.  
    55. IL_003a:
    56.     {
    57.         // else return typeof(T).GetHashCode();
    58.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_10 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
    59.         IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
    60.         Type_t * L_11;
    61.         L_11 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_10, /*hidden argument*/NULL);
    62.         NullCheck((RuntimeObject *)L_11);
    63.         int32_t L_12;
    64.         L_12 = VirtFuncInvoker0< int32_t >::Invoke(2 /* System.Int32 System.Object::GetHashCode() */, (RuntimeObject *)L_11);
    65.         return L_12;
    66.     }
    67. }
    68.  
    69. // System.Int32 C`1<System.Object>::D()
    70. IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR int32_t C_1_D_m60360274BCBAAC8A294C353B2B0184D60E01B690_gshared (const RuntimeMethod* method)
    71. {
    72.     static bool s_Il2CppMethodInitialized;
    73.     if (!s_Il2CppMethodInitialized)
    74.     {
    75.         il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var);
    76.         il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&String_t_0_0_0_var);
    77.         il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Type_t_il2cpp_TypeInfo_var);
    78.         s_Il2CppMethodInitialized = true;
    79.     }
    80.     {
    81.         // if (typeof(T) == typeof(int)) return 1;
    82.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_0 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
    83.         IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
    84.         Type_t * L_1;
    85.         L_1 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_0, /*hidden argument*/NULL);
    86.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_2 = { reinterpret_cast<intptr_t> (Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var) };
    87.         Type_t * L_3;
    88.         L_3 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_2, /*hidden argument*/NULL);
    89.         bool L_4;
    90.         L_4 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_1, (Type_t *)L_3, /*hidden argument*/NULL);
    91.         if (!L_4)
    92.         {
    93.             goto IL_001d;
    94.         }
    95.     }
    96.     {
    97.         // if (typeof(T) == typeof(int)) return 1;
    98.         return 1;
    99.     }
    100.  
    101. IL_001d:
    102.     {
    103.         // else if (typeof(T) == typeof(string)) return -1;
    104.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_5 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
    105.         IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
    106.         Type_t * L_6;
    107.         L_6 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_5, /*hidden argument*/NULL);
    108.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_7 = { reinterpret_cast<intptr_t> (String_t_0_0_0_var) };
    109.         Type_t * L_8;
    110.         L_8 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_7, /*hidden argument*/NULL);
    111.         bool L_9;
    112.         L_9 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_6, (Type_t *)L_8, /*hidden argument*/NULL);
    113.         if (!L_9)
    114.         {
    115.             goto IL_003a;
    116.         }
    117.     }
    118.     {
    119.         // else if (typeof(T) == typeof(string)) return -1;
    120.         return (-1);
    121.     }
    122.  
    123. IL_003a:
    124.     {
    125.         // else return typeof(T).GetHashCode();
    126.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_10 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
    127.         IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
    128.         Type_t * L_11;
    129.         L_11 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_10, /*hidden argument*/NULL);
    130.         NullCheck((RuntimeObject *)L_11);
    131.         int32_t L_12;
    132.         L_12 = VirtFuncInvoker0< int32_t >::Invoke(2 /* System.Int32 System.Object::GetHashCode() */, (RuntimeObject *)L_11);
    133.         return L_12;
    134.     }
    135. }
    sizeof(T) is treated as my proposal.
    sizeof(T) is replaced with sizeof(uint8_t).

    Code (CSharp):
    1. // System.Int32 E`1<System.Byte>::D()
    2. IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR int32_t E_1_D_m634365F34758541C291A04E1051FDEB0544A1658_gshared (const RuntimeMethod* method)
    3. {
    4.     static bool s_Il2CppMethodInitialized;
    5.     if (!s_Il2CppMethodInitialized)
    6.     {
    7.         il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var);
    8.         il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Type_t_il2cpp_TypeInfo_var);
    9.         s_Il2CppMethodInitialized = true;
    10.     }
    11.     {
    12.         // if (typeof(T) == typeof(int)) return sizeof(T);
    13.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_0 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
    14.         IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
    15.         Type_t * L_1;
    16.         L_1 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_0, /*hidden argument*/NULL);
    17.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_2 = { reinterpret_cast<intptr_t> (Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var) };
    18.         Type_t * L_3;
    19.         L_3 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_2, /*hidden argument*/NULL);
    20.         bool L_4;
    21.         L_4 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_1, (Type_t *)L_3, /*hidden argument*/NULL);
    22.         // if (typeof(T) == typeof(int)) return sizeof(T);
    23.         uint32_t L_5 = sizeof(uint8_t);
    24.         return L_5;
    25.     }
    26. }
    27.  
    28. // System.Int32 E`1<System.Decimal>::D()
    29. IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR int32_t E_1_D_m78DC72B793035E6CC29AEC8E50901E774F5E6454_gshared (const RuntimeMethod* method)
    30. {
    31.     static bool s_Il2CppMethodInitialized;
    32.     if (!s_Il2CppMethodInitialized)
    33.     {
    34.         il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var);
    35.         il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Type_t_il2cpp_TypeInfo_var);
    36.         s_Il2CppMethodInitialized = true;
    37.     }
    38.     {
    39.         // if (typeof(T) == typeof(int)) return sizeof(T);
    40.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_0 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
    41.         IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
    42.         Type_t * L_1;
    43.         L_1 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_0, /*hidden argument*/NULL);
    44.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_2 = { reinterpret_cast<intptr_t> (Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var) };
    45.         Type_t * L_3;
    46.         L_3 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_2, /*hidden argument*/NULL);
    47.         bool L_4;
    48.         L_4 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_1, (Type_t *)L_3, /*hidden argument*/NULL);
    49.         // if (typeof(T) == typeof(int)) return sizeof(T);
    50.         uint32_t L_5 = sizeof(Decimal_t2978B229CA86D3B7BA66A0AEEE014E0DE4F940D7 );
    51.         return L_5;
    52.     }
    53. }
    54.  
    55. // System.Int32 E`1<System.Int64>::D()
    56. IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR int32_t E_1_D_m6598740E9AE16F8A622607D19DE92BDBCBF23F1F_gshared (const RuntimeMethod* method)
    57. {
    58.     static bool s_Il2CppMethodInitialized;
    59.     if (!s_Il2CppMethodInitialized)
    60.     {
    61.         il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var);
    62.         il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Type_t_il2cpp_TypeInfo_var);
    63.         s_Il2CppMethodInitialized = true;
    64.     }
    65.     {
    66.         // if (typeof(T) == typeof(int)) return sizeof(T);
    67.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_0 = { reinterpret_cast<intptr_t> (IL2CPP_RGCTX_TYPE(InitializedTypeInfo(method->klass)->rgctx_data, 0)) };
    68.         IL2CPP_RUNTIME_CLASS_INIT(Type_t_il2cpp_TypeInfo_var);
    69.         Type_t * L_1;
    70.         L_1 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_0, /*hidden argument*/NULL);
    71.         RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9  L_2 = { reinterpret_cast<intptr_t> (Int32_tFDE5F8CD43D10453F6A2E0C77FE48C6CC7009046_0_0_0_var) };
    72.         Type_t * L_3;
    73.         L_3 = Type_GetTypeFromHandle_m8BB57524FF7F9DB1803BC561D2B3A4DBACEB385E((RuntimeTypeHandle_tC33965ADA3E041E0C94AF05E5CB527B56482CEF9 )L_2, /*hidden argument*/NULL);
    74.         bool L_4;
    75.         L_4 = Type_op_Equality_mA438719A1FDF103C7BBBB08AEF564E7FAEEA0046((Type_t *)L_1, (Type_t *)L_3, /*hidden argument*/NULL);
    76.         // if (typeof(T) == typeof(int)) return sizeof(T);
    77.         uint32_t L_5 = sizeof(int64_t);
    78.         return L_5;
    79.     }
    80. }
    81.  
     
  2. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,775
    Thanks for the suggestion. I agree, this would be a great optimization. I've added it to our roadmap.
     
    JesOb, Peter77 and pcysl5edgo like this.
  3. pcysl5edgo

    pcysl5edgo

    Joined:
    Jun 3, 2018
    Posts:
    65
    Thank you!
     
    Peter77 likes this.