Search Unity

[BUG] Burst compiler bug when modifying and returning struct value

Discussion in 'Burst' started by craftx, Dec 15, 2018.

  1. craftx

    craftx

    Joined:
    Oct 2, 2018
    Posts:
    13
    Burst: 0.2.4-preview.39
    Entities: 0.0.12-preview.21
    Incremental Compiler: 0.0.42-preview.31
    Unity Version: 2018.2.10f1

    The following code does not produce correct result when burst is enabled:

    Code (CSharp):
    1.  
    2.     public struct Data : IComponentData
    3.     {
    4.         public MyVector Value1;
    5.         public MyVector Value2;
    6.     }
    7.  
    8.     public struct MyVector
    9.     {
    10.         public long X;
    11.         public long Y;
    12.         public long Z;
    13.     }
    14.  
    15.     public static MyVector UpdateMyVector(MyVector value)
    16.     {
    17.         value = new MyVector
    18.         {
    19.             X = value.X + 5678,
    20.         };
    21.         return value;
    22.     }
    23.  
    24.     public class TestBurstComponentSystem : JobComponentSystem
    25.     {
    26.         [BurstCompile]
    27.         private struct Job : IJobProcessComponentData<Data>
    28.         {
    29.             public void Execute(ref Data data)
    30.             {
    31.                 data.Value2 = UpdateMyVector(data.Value1);
    32.             }
    33.         }
    34.  
    35.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    36.         {
    37.             return new Job().Schedule(this, inputDeps);
    38.         }
    39.     }
    40.  
    The only part of interest is the UpdateMyVector method. It takes a MyVector struct as input and produces a MyVector struct as output. It's expected to return a new struct with modified X value while Y, Z being 0. When burst compiler is disabled, it works as expected. However, when burst compiler is enabled, the method returns the original input value as if the assignment doesn't exist.

    I have put up a simple scene to reproduce this bug here:
    https://github.com/xfxyjwf/burstbug

    Open "Assets/Scenes/SampleScene.unity" and click play. At the center of the screen you are expected to see the number 5678, but when burst compiler is enabled you will see 0 instead. By toggling on/off burst jobs, you can see different values on the screen.

    I have also observed that the bug only happens when the size of the struct is larger than 16 bytes. In the above example, if you remove the unused "Y", "Z" field from MyVector struct, burst compiler will start to produce correct results.

    More info from dnSpy and burst inspector:

    Regardless the size of the MyVector struct, the IL instructions for UpdateMyVector (shown in dnSpy) are always :
    Code (CSharp):
    1. 0    0000    nop
    2. 1    0001    ldloca.s    V_0 (0)
    3. 2    0003    initobj    TestBurstBug/MyVector
    4. 3    0009    ldloca.s    V_0 (0)
    5. 4    000B    ldarga.s    value (0)
    6. 5    000D    ldfld    int64 TestBurstBug/MyVector::X
    7. 6    0012    ldc.i4    0x162E
    8. 7    0017    conv.i8
    9. 8    0018    add
    10. 9    0019    stfld    int64 TestBurstBug/MyVector::X
    11. 10    001E    ldloc.0
    12. 11    001F    starg.s    value (0)
    13. 12    0021    ldarg.0
    14. 13    0022    stloc.1
    15. 14    0023    br    15 (0028) ldloc.1
    16. 15    0028    ldloc.1
    17. 16    0029    ret
    However, depending on the size of MyVector, the IL instructions show in burst compiler are different.

    When sizeof(MyVector) <= 16:
    Code (csharp):
    1. TestBurstBug/MyVector .TestBurstBug, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null::UpdateMyVector(.TestBurstBug+MyVector, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)(TestBurstBug/MyVector value)
    2. [0] TestBurstBug/MyVector var.0.
    3. [1] TestBurstBug/MyVector var.1.
    4. [2] TestBurstBug/MyVector var.value <-- From parameter `value` due to instruction `IL_000b: ldarga.s value`
    5.  
    6. --------------------------------------------
    7. BL.entry: Block
    8. --------------------------------------------
    9. IL_000b: ldarg value
    10. IL_000b: stloc value args(IL_000b) var.value
    11. IL_0000: br BL.0000
    12.  
    13. --------------------------------------------
    14. BL.0000: Block
    15. --------------------------------------------
    16. IL_0001: ldloca.s V_0 var.0.
    17. IL_0003: initobj TestBurstBug/MyVector args(IL_0001)
    18. IL_0009: ldloca.s V_0 var.0.
    19. IL_000b: ldloca value var.value
    20. IL_000d: ldfld System.Int64 TestBurstBug/MyVector::X args(IL_000b)
    21. IL_0012: ldc.i4 5678
    22. IL_0017: conv.i8 args(IL_0012)
    23. IL_0018: add args(IL_000d, IL_0017)
    24. IL_0019: stfld System.Int64 TestBurstBug/MyVector::X args(IL_0009, IL_0018)
    25. IL_001e: ldloc.0 var.0.
    26. IL_001f: stloc value args(IL_001e) var.value
    27. IL_0021: ldloc var.value
    28. IL_0022: stloc args(IL_0021) var.1.
    29. IL_0023: br BL.0028
    30.  
    31. --------------------------------------------
    32. BL.0028: Block
    33. --------------------------------------------
    34. IL_0028: ldloc.1 var.1.
    35. IL_0029: ret args(IL_0028)
    When sizeof(MyVector) >= 16:
    Code (csharp):
    1. void .TestBurstBug, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null::UpdateMyVector(.TestBurstBug+MyVector, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)(TestBurstBug/MyVector* $___struct_ret, TestBurstBug/MyVector* value)
    2. [0] TestBurstBug/MyVector var.0.
    3. [1] TestBurstBug/MyVector var.1.
    4. [2] TestBurstBug/MyVector var.value <-- From parameter `value` due to instruction `IL_000b: ldarga.s value`
    5.  
    6. --------------------------------------------
    7. BL.entry: Block
    8. --------------------------------------------
    9. IL_000b: ldarg value
    10. IL_000b: ldobj value args(IL_000b) <-- load of indirect argument
    11. IL_000b: stloc value args(IL_000b) var.value
    12. IL_0000: br BL.0000
    13.  
    14. --------------------------------------------
    15. BL.0000: Block
    16. --------------------------------------------
    17. IL_0001: ldloca.s V_0 var.0.
    18. IL_0003: initobj TestBurstBug/MyVector args(IL_0001)
    19. IL_0009: ldloca.s V_0 var.0.
    20. IL_000b: ldarg value <-- load of indirect argument address
    21. IL_000d: ldfld System.Int64 TestBurstBug/MyVector::X args(IL_000b)
    22. IL_0012: ldc.i4 5678
    23. IL_0017: conv.i8 args(IL_0012)
    24. IL_0018: add args(IL_000d, IL_0017)
    25. IL_0019: stfld System.Int64 TestBurstBug/MyVector::X args(IL_0009, IL_0018)
    26. IL_001e: ldloc.0 var.0.
    27. IL_001f: stloc value args(IL_001e) var.value
    28. IL_0021: ldarg.0
    29. IL_0021: ldobj args(IL_0021) <-- load of indirect argument
    30. IL_0022: stloc args(IL_0021) var.1.
    31. IL_0023: br BL.0028
    32.  
    33. --------------------------------------------
    34. BL.0028: Block
    35. --------------------------------------------
    36. IL_0028: ldloc.1 var.1.
    37. IL_0029: ldarg.0
    38. IL_0029: stobj args(IL_0029, IL_0028) <-- write result to caller allocated struct
    39. IL_0029: ret
    It seems to me the burst inspector is not showing the original IL instructions but instead a modified version. In both cases (sizeof(MyVector) <= 16 and sizeof(MyVector) > 16), the modified IL introduced a new local variable "var.value" to be used in place of the argument "value". The difference between the two lies in instruction IL_0021. The instructions above IL_0021 are writing the newly created MyVector to "var.value", but in one version, IL_0021 loads from "var.value", but in the other version, IL_0021 loads from arg.0. The one loading from arg.0 ends up producing incorrect LLVM IR code returning the original input value rather than the modified one. I think the bug lies in the code that transforms original IL to the modified version.
     
  2. sidespin

    sidespin

    Joined:
    Jun 22, 2018
    Posts:
    20
    This looks like a serious bug. Makes me wonder if any ECS app is in production
     
  3. deplinenoise

    deplinenoise

    Unity Technologies

    Joined:
    Dec 20, 2017
    Posts:
    33
    This issue has been fixed and the code behaves correctly with the latest Burst version.
     
    starikcetin and FROS7 like this.