Search Unity

Bug Invalid IL Code Exception with Ref Parameters and Unity's BlobBuilderArray<T>

Discussion in 'Entity Component System' started by herkip, Jun 5, 2023.

  1. herkip

    herkip

    Joined:
    Dec 21, 2013
    Posts:
    30
    I've come across a peculiar issue related to the usage of reference (ref) parameters in combination with Unity's BlobBuilderArray<T>, particularly when used with other parameters. I am using 2022.3 LTS.

    To illustrate, consider the following simplified delegate and function:

    Delegate:

    Code (CSharp):
    1. public delegate ref BaseNode NodeHandler2(int index, ref BaseNode baseNode, INode node,
    2.             ref BlobBuilder builder, ref BlobBuilderArray<BlobPtr<BaseNode>> children);
    3.  
    Function:

    Code (CSharp):
    1. public static ref BaseNode Allocate2(int index, ref BaseNode baseNode, INode node,
    2. ref BlobBuilder builder, ref BlobBuilderArray<BlobPtr<BaseNode>> children)
    3. {
    4. return ref baseNode;
    5. }
    6.  
    Function Call:

    Code (CSharp):
    1. var baseNode = new BaseNode();
    2. NodeHandler2 handler2 = Properties.Allocate2;
    3. handler2(index, ref baseNode, node, ref builder, ref children);
    4.  
    The issue seems to occur when I use the ref BlobBuilderArray<BlobPtr<BaseNode>> children and int index parameters simultaneously. When I do this, I encounter an System.InvalidProgramException: Invalid IL code error. However, the error doesn't occur when I only use either the ref BlobBuilderArray<BlobPtr<BaseNode>> children or the int index parameter, which suggests that the error is not due to any individual parameter but instead due to their combined use.

    Stack:
    Code (CSharp):
    1. System.InvalidProgramException : Invalid IL code in (wrapper delegate-invoke) <Module>:invoke_BaseNode&_int_BaseNode&_INode_BlobBuilder&_BlobBuilderArray`1<BlobPtr`1<BaseNode>>&
    2. (int,
    3. Company.Code.Modding.AST.Unmanaged.BaseNode&,
    4. Language.Ast.INode,
    5. Unity.Entities.BlobBuilder&,
    6. Unity.Entities.BlobBuilderArray`1<Unity.Entities.BlobPtr`1<Company.Code.Modding.AST.Unmanaged.BaseNode>>&
    7. ): IL_008b: stloc.s   5
    Does anybody have any idea about this.
    Thank you.
     
    Last edited: Jun 5, 2023
  2. herkip

    herkip

    Joined:
    Dec 21, 2013
    Posts:
    30
    Update:

    Upon further investigation, I have identified a potential pattern that might be causing the issue. It appears that when a delegate in Unity has a return type and more than four input parameters, a System.InvalidProgramException with the message "Invalid IL code" is thrown.

    For example, consider this delegate signature:

    Code (CSharp):
    1. public delegate ref BaseNode NodeHandler2(
    2.     int index,
    3.     ref BaseNode baseNode,
    4.     INode node,
    5.     ref BlobBuilder builder,
    6.     ref BlobBuilderArray<BlobPtr<BaseNode>> children
    7. );
    8.  

    The exception occurs when this delegate is used to point to a function matching its signature, and that function is invoked.

    Interestingly, when the return type of the delegate is changed to void, or when the number of input parameters is reduced to four or less, the exception does not occur. This suggests a potential limitation or bug in Unity's handling of delegates with a return type and more than four input parameters.


    Code (CSharp):
    1. public delegate void NodeHandler2(
    2.     int index,
    3.     ref BaseNode baseNode,
    4.     INode node,
    5.     ref BlobBuilder builder
    6. );
    7.  
    This observation could be helpful to anyone else experiencing a similar issue. Any insights or suggestions from the community would be much appreciated.
     
  3. herkip

    herkip

    Joined:
    Dec 21, 2013
    Posts:
    30
    After extensive debugging and discussions, I have found a workaround to the issue with delegate parameters count and return type in Unity, which I posted about earlier. The workaround essentially involves using unsafe code and wrapping our ref parameters inside an unmanaged struct.

    Here is the updated code:

    Code (CSharp):
    1. public unsafe struct Payload
    2. {
    3.     public INode Node;
    4.     public RefWrapper<BlobBuilder> Builder;
    5.     public RefWrapper<BlobPtr<BaseNode>> Child;
    6.     public RefWrapper<IdMapping> IdMappingNew;
    7. }
    8.  
    9. public unsafe struct RefWrapper<T> where T : unmanaged
    10. {
    11.     public T* Value;
    12.  
    13.     public RefWrapper(ref T value)
    14.     {
    15.         fixed (T* p = &value)
    16.         {
    17.             Value = p;
    18.         }
    19.     }
    20.  
    21.     public ref T Get()
    22.     {
    23.         return ref *Value;
    24.     }
    25. }
    26.  
    27. private unsafe ref BaseNode All(INode node, BlobBuilder builder,
    28. ref BlobBuilderArray<BlobPtr<BaseNode>> children, int index,
    29. ref IdMapping idMappingNew)
    30. {
    31.     var payload = new Payload()
    32.     {
    33.         Node = node,
    34.         IdMappingNew = new RefWrapper<IdMapping>(ref idMappingNew),
    35.         Builder = new RefWrapper<BlobBuilder>(ref builder),
    36.         Child = new RefWrapper<BlobPtr<BaseNode>>(ref children[index])
    37.     };
    38.  
    39.     NodeHandler2 handler2 = Properties.Allocate2;
    40.     ref var retValue = ref handler2(ref _allocate, ref payload);
    41. }
    42.  
    43. public static ref BaseNode Allocate2(ref Allocate allocate, ref NodeManager.Payload payLoad)
    44. {
    45.     return ref allocate.Execute<Unmanaged.Properties>(ref payLoad);
    46. }
    47.  
    48. public delegate ref BaseNode NodeHandler2(ref Allocate allocate, ref Payload payload);
    49.  
    Then, when using the Payload:
    Code (CSharp):
    1. ref var asRef = ref UnsafeUtility.AsRef<BlobPtr<BaseNode>>(payload.Child.Value);
    2. ref var ptr = ref UnsafeUtility.As<BlobPtr<BaseNode>, BlobPtr<T>>(ref asRef);
    3. ref var node = ref payload.Builder.Value->Allocate(ref ptr);
    4.  

    Please note that using pointers in C# may lead to unsafe code. Be sure that the original object you are taking a reference to outlives the RefWrapper. Also, be aware of Unity's own memory management when dealing with BlobPtr and similar structures.

    I hope this can be helpful to anyone encountering a similar problem.