Search Unity

Burst infinite loop

Discussion in 'Burst' started by tertle, Jun 8, 2021.

  1. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    I was trying to hide some unsafe code using a handle struct and have run into an infinite loop seemingly caused by burst compiling (1.5.4) as it runs fine when is burst is off or if I remove the handle struct and use the pointer directly.

    The code

    Code (CSharp):
    1. public unsafe struct Scorers<T>
    2.     where T : unmanaged, IContext<T>
    3. {
    4.     private Scorer<T>* scorer;
    5.  
    6.     internal Scorers(Scorer<T>* scorer)
    7.     {
    8.         this.scorer = scorer;
    9.     }
    10.  
    11.     public short ScorerType => this.scorer->ScorerType;
    12.  
    13.     [MethodImpl(MethodImplOptions.AggressiveInlining)]
    14.     public float Score<TS>(ref T context)
    15.         where TS : unmanaged, IScorer<T>
    16.     {
    17.         return ((TS*)this.scorer)->Score(ref context); // Infinite loops in burst, fine in mono
    18.         return (*(TS*)this.scorer).Score(ref context); // Infinite loops in burst, fine in mono
    19.         return UnsafeUtility.AsRef<TS>(this.scorer).Score(ref context); // Works fine in burst and mono
    20.     }
    21. }
    Which is called from here

    Code (CSharp):
    1. float IContext<AIContext>.CustomScorers(Scorers<AIContext> scorer, ref AIContext context)
    2. {
    3.     return scorer.ScorerType switch
    4.     {
    5.         1 => scorer.Score<HasEntityInMemory>(ref context),
    6.         _ => 0,
    7.     };
    8. }
    However if I replace it with the original code before I tried to hide the pointers with a handle, it works totally fine.

    Code (CSharp):
    1. float IContext<AIContext>.CustomScorers(Scorer<AIContext>* scorer, ref AIContext context)
    2. {
    3.     return scorer->ScorerType switch
    4.     {
    5.         1 => ((HasEntityInMemory*)scorer)->Score(ref context),
    6.         _ => 0,
    7.     };
    8. }
    So as mentioned in comments, UnsafeUtility.AsRef also seems to work fine inside the handle struct instead.

    Looking at the assembly for these 3 cases,
    Code (CSharp):
    1. return ((TS*)this.scorer)->Score(ref context); // Infinite loops in burst, fine in mono
    2. return (*(TS*)this.scorer).Score(ref context); // Infinite loops in burst, fine in mono
    3. return UnsafeUtility.AsRef<TS>(this.scorer).Score(ref context); // Works fine in burst and mono
    the first two cases generate odd assembly while the third one generates the assembly I'd expect similar to the original ((HasEntityInMemory*)scorer)->Score(ref context) implementation.

    I can provide burst generated code or the full standalone library privately if desired. Just in case it matters, the memory it points to is allocated using UnsafeUtility.Malloc with Allocator.Persistent.

    I seem to have a workaround using UnsafeUtility.AsRef so it's not a huge deal for me, just something I thought you might want to investigate.
     
  2. tim_jones

    tim_jones

    Unity Technologies

    Joined:
    May 2, 2019
    Posts:
    287
    Hi @tertle - thanks for posting this. Yes, I'd be interested in seeing the Burst-generated code please. (Feel free to DM it to me if you want to keep it private.)
     
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Sent you the burst generated code in a conversation, hope it helps.
     
  4. tim_jones

    tim_jones

    Unity Technologies

    Joined:
    May 2, 2019
    Posts:
    287
    Just to follow up here in case anyone else has this same issue - this is indeed a bug, which we'll fix. The bug is that this specific type of cast to the generic
    TS*
    type ends up being ignored, and instead it emits a call to
    Scorer<T>
    .
     
    tertle likes this.