When a non-inlined CastHelpers.StelemRef user call storing null is replaced by a plain STOREIND in morph, the block keeps a stale BBF_GC_SAFE_POINT flag. A call-free loop is then emitted partially interruptible and GC suspension hangs forever.
Minimal Repro
using System;
using System.Runtime.CompilerServices;
using System.Threading;
public class Program
{
static volatile bool s_started;
static int s_x;
[MethodImpl(MethodImplOptions.AggressiveInlining)] static void F6() { s_x++; }
[MethodImpl(MethodImplOptions.AggressiveInlining)] static void F5() { F6(); F6(); F6(); F6(); }
[MethodImpl(MethodImplOptions.AggressiveInlining)] static void F4() { F5(); F5(); F5(); F5(); }
[MethodImpl(MethodImplOptions.AggressiveInlining)] static void F3() { F4(); F4(); F4(); F4(); }
[MethodImpl(MethodImplOptions.AggressiveInlining)] static void F2() { F3(); F3(); F3(); F3(); }
static void F1() => F2(); // exhausts the inline budget, so StelemRef can't be inlined
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static void Test(object[] arr, int i)
{
F1();
s_started = true;
while (true)
{
object o = null;
arr[i] = o;
}
}
static void Main()
{
var t = new Thread(() => Test(new string[10], 1)) { IsBackground = true };
t.Start();
while (!s_started) { }
Thread.Sleep(100);
GC.Collect();
Console.WriteLine("Done");
}
}
Expected
Actual
The process hangs in GC.Collect(). Test is reported as ; partially interruptible while its loop contains no call.
Regression?
Yes. .NET 8, 9 and 10 print Done; .NET 11 RC2 and main hang.
Notes
fgMorphCall sets BBF_GC_SAFE_POINT while the call is still CT_USER_FUNC, then the "stelem of null" optimization replaces the call with a STOREIND. The flag is never cleared, so fgHasCycleWithoutGCSafePoint thinks the loop has a safe point.
Became reachable in .NET 11 when stelem.ref started being imported as a user call to CastHelpers.StelemRef.
When a non-inlined
CastHelpers.StelemRefuser call storingnullis replaced by a plainSTOREINDin morph, the block keeps a staleBBF_GC_SAFE_POINTflag. A call-free loop is then emitted partially interruptible and GC suspension hangs forever.Minimal Repro
Expected
Actual
The process hangs in
GC.Collect().Testis reported as; partially interruptiblewhile its loop contains no call.Regression?
Yes. .NET 8, 9 and 10 print
Done; .NET 11 RC2 and main hang.Notes
fgMorphCallsetsBBF_GC_SAFE_POINTwhile the call is stillCT_USER_FUNC, then the "stelem of null" optimization replaces the call with aSTOREIND. The flag is never cleared, sofgHasCycleWithoutGCSafePointthinks the loop has a safe point.Became reachable in .NET 11 when stelem.ref started being imported as a user call to
CastHelpers.StelemRef.