VM: (bug) open static delegate corrupts arguments when the shuffle graph contains an FP/stack cycle (linux-x64)
Description
On linux-x64, an open static delegate whose Invoke -> target shuffle contains a cycle passing through floating-point registers produces a shuffle thunk that silently corrupts arguments. One integer argument register is clobbered and one FP argument is never moved, so the target method observes wrong values with no error.
Repro
using System;
using System.Runtime.CompilerServices;
public struct S { public double D; public long L; }
public delegate void Del(long a, long b, long c, long d, long e, S s,
double d1, double d2, double d3, double d4, double d5, double d6, double d7, double d8);
public static class Program
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target(long a, long b, long c, long d, long e, S s,
double d1, double d2, double d3, double d4, double d5, double d6, double d7, double d8)
{
Console.WriteLine($"longs: {a} {b} {c} {d} {e}");
Console.WriteLine($"struct: {s.D} {s.L}");
Console.WriteLine($"doubles: {d1} {d2} {d3} {d4} {d5} {d6} {d7} {d8}");
}
public static void Main()
{
Del f = Target;
f(1, 2, 3, 4, 5, new S { D = 100.5, L = 7 }, 11, 12, 13, 14, 15, 16, 17, 18);
}
}Expected
longs: 1 2 3 4 5
struct: 100.5 7
doubles: 11 12 13 14 15 16 17 18Actual
longs: 1 1 3 4 5
struct: 100.5 7
doubles: 12 12 13 14 15 16 17 18Argument b received a's value and d1 received d2's value.
Analysis
GenerateShuffleArrayPortable (src/coreclr/vm/comdelegate.cpp) breaks the cycle stack[0] -> xmm0 -> ... -> xmm7 -> stack[0] by routing one endpoint through ShuffleEntry::HELPERREG.
StubLinkerCPU::EmitShuffleThunk (src/coreclr/vm/i386/stublinkerx86.cpp) then unconditionally treats a register endpoint of a HELPERREG move as an integer argument register (c_argRegs[...]), ignoring ShuffleEntry::FPREGMASK. The non-helper reg-to-reg path a few lines below does honor FPREGMASK.
Regression?
No. Reproduces on .NET 10.0.7 and 11.0.0-preview.3.
Platform
linux-x64. windows-x64 is unaffected because its calling convention does not produce this cycle.
Source: dotnet/runtime