diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 722afadcd9a21..a6ddc2a8cb6e9 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -453,6 +453,11 @@ void Compiler::impAppendStmt(Statement* stmt, unsigned chkLevel, bool checkConsu } } + // In the case of GT_RET_EXPR any subsequent spills will appear in the wrong place -- after + // the call. We need to move them to before the call + // + Statement* lastStmt = impLastStmt; + if ((dstVarDsc != nullptr) && !dstVarDsc->IsAddressExposed() && !dstVarDsc->lvHasLdAddrOp) { impSpillLclRefs(lvaGetLclNum(dstVarDsc), chkLevel); @@ -480,6 +485,40 @@ void Compiler::impAppendStmt(Statement* stmt, unsigned chkLevel, bool checkConsu { impSpillSpecialSideEff(); } + + if ((lastStmt != impLastStmt) && expr->OperIs(GT_RET_EXPR)) + { + GenTree* const call = expr->AsRetExpr()->gtInlineCandidate; + JITDUMP("\nimpAppendStmt: after sinking a local struct store into inline candidate [%06u], we need to " + "reorder subsequent spills.\n", + dspTreeID(call)); + + // Move all newly appended statements to just before the call's statement. + // First, find the statement containing the call. + // + Statement* insertBeforeStmt = lastStmt; + + while (insertBeforeStmt->GetRootNode() != call) + { + assert(insertBeforeStmt != impStmtList); + insertBeforeStmt = insertBeforeStmt->GetPrevStmt(); + } + + Statement* movingStmt = lastStmt->GetNextStmt(); + + JITDUMP("Moving " FMT_STMT " through " FMT_STMT " before " FMT_STMT "\n", movingStmt->GetID(), + impLastStmt->GetID(), insertBeforeStmt->GetID()); + + // We move these backwards, so must keep moving the insert + // point to keep them in order. + // + while (impLastStmt != lastStmt) + { + Statement* movingStmt = impExtractLastStmt(); + impInsertStmtBefore(movingStmt, insertBeforeStmt); + insertBeforeStmt = movingStmt; + } + } } impAppendStmtCheck(stmt, chkLevel); diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_95349/Runtime_95349.cs b/src/tests/JIT/Regression/JitBlue/Runtime_95349/Runtime_95349.cs new file mode 100644 index 0000000000000..95cb943a4f6d0 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_95349/Runtime_95349.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Issues with stack spill ordering around some GDVs +// Compile with None + +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using Xunit; + +class P +{ + virtual public (double x, double y) XY() => (0, 0); +} + +class P1 : P +{ + override public (double x, double y) XY() => (1, 2); +} + +public class Runtime_95349 +{ + [MethodImpl(MethodImplOptions.NoInlining)] + static int Problem(P p, int n, (double x, double y) tuple) + { + int wn = 0; + for (int i = 0; i < n; i++) + { + (double x, double y) tupleTmp = tuple; + tuple = p.XY(); + (_, double f) = tupleTmp; + wn = Wn(wn, f, tuple.y); + } + + return wn; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Wn(int wn, double f, double y) + { + wn += (f == -1) ? 1 : 0; + return wn; + } + + [Fact] + public static void Test() + { + P p = new P1(); + int n = 100_000; + for (int i = 0; i < 100; i++) + { + _ = Problem(p, n, (-1, -1)); + Thread.Sleep(30); + } + + int r = Problem(p, n, (-1, -1)); + Console.WriteLine($"r = {r} (expected 1)"); + Assert.Equal(1, r); + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_95349/Runtime_95349.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_95349/Runtime_95349.csproj new file mode 100644 index 0000000000000..1bb887ea34b0f --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_95349/Runtime_95349.csproj @@ -0,0 +1,9 @@ + + + True + None + + + + + \ No newline at end of file