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): public unsafe struct Scorers<T> where T : unmanaged, IContext<T> { private Scorer<T>* scorer; internal Scorers(Scorer<T>* scorer) { this.scorer = scorer; } public short ScorerType => this.scorer->ScorerType; [MethodImpl(MethodImplOptions.AggressiveInlining)] public float Score<TS>(ref T context) where TS : unmanaged, IScorer<T> { return ((TS*)this.scorer)->Score(ref context); // Infinite loops in burst, fine in mono return (*(TS*)this.scorer).Score(ref context); // Infinite loops in burst, fine in mono return UnsafeUtility.AsRef<TS>(this.scorer).Score(ref context); // Works fine in burst and mono } } Which is called from here Code (CSharp): float IContext<AIContext>.CustomScorers(Scorers<AIContext> scorer, ref AIContext context) { return scorer.ScorerType switch { 1 => scorer.Score<HasEntityInMemory>(ref context), _ => 0, }; } 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): float IContext<AIContext>.CustomScorers(Scorer<AIContext>* scorer, ref AIContext context) { return scorer->ScorerType switch { 1 => ((HasEntityInMemory*)scorer)->Score(ref context), _ => 0, }; } 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): return ((TS*)this.scorer)->Score(ref context); // Infinite loops in burst, fine in mono return (*(TS*)this.scorer).Score(ref context); // Infinite loops in burst, fine in mono 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.
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.)
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>.