diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp
index e66abbbf9b9ca2..73221a60143bf6 100644
--- a/src/coreclr/jit/lower.cpp
+++ b/src/coreclr/jit/lower.cpp
@@ -3239,11 +3239,11 @@ void Lowering::LowerFastTailCall(GenTreeCall* call)
if (!putargs.Empty())
{
GenTree* firstPutargStk = putargs.Bottom(0);
- GenTree* firstPutargStkOp = firstPutargStk->gtGetOp1();
+ GenTree* firstPutargStkOp = FirstOperand(firstPutargStk);
for (int i = 1; i < putargs.Height(); i++)
{
firstPutargStk = LIR::FirstNode(firstPutargStk, putargs.Bottom(i));
- firstPutargStkOp = LIR::FirstNode(firstPutargStkOp, putargs.Bottom(i)->gtGetOp1());
+ firstPutargStkOp = LIR::FirstNode(firstPutargStkOp, FirstOperand(putargs.Bottom(i)));
}
// Since this is a fast tailcall each PUTARG_STK will place the argument in the
// _incoming_ arg space area. This will effectively overwrite our already existing
@@ -3279,6 +3279,10 @@ void Lowering::LowerFastTailCall(GenTreeCall* call)
continue;
}
+ JITDUMP(
+ "PUTARG_STK [%06u] overwrites [%06u..%06u); parameter V%03u lives in [%06u..%06u); may need defensive copies\n",
+ Compiler::dspTreeID(put), overwrittenStart, overwrittenEnd, callerArgLclNum, argStart, argEnd);
+
// Codegen cannot handle a partially overlapping copy. For
// example, if we have
// bar(S16 stack, S32 stack2)
@@ -3287,10 +3291,11 @@ void Lowering::LowerFastTailCall(GenTreeCall* call)
// ahead. It is possible that this PUTARG_STK is the only use,
// in which case we will need to introduce a temp, so look for
// uses starting from it. Note that we assume that in-place
- // copies are OK.
+ // copies are ok provided the source is a scalar value.
GenTree* lookForUsesFrom = put->gtNext;
- if (overwrittenStart != argStart)
+ if ((overwrittenStart != argStart) || put->gtGetOp1()->OperIsFieldList())
{
+ JITDUMP("Non-atomic copy may be self-interfering. Expanding search...\n");
lookForUsesFrom = firstPutargStkOp;
}
@@ -3354,7 +3359,43 @@ void Lowering::LowerFastTailCall(GenTreeCall* call)
unreached();
#endif
}
+
+//------------------------------------------------------------------------
+// FirstOperand:
+// Find the earliest operand of a node.
+//
+// Arguments:
+// node - The node
//
+// Returns:
+// The earliest evaluated operand.
+//
+GenTree* Lowering::FirstOperand(GenTree* node)
+{
+ struct Helper
+ {
+ GenTree* Result = nullptr;
+
+ void Visit(GenTree* node)
+ {
+ node->VisitOperands([=](GenTree* op) {
+ Result = Result == nullptr ? op : LIR::FirstNode(Result, op);
+
+ if (op->isContained())
+ {
+ Visit(op);
+ }
+
+ return GenTree::VisitResult::Continue;
+ });
+ }
+ };
+
+ Helper helper;
+ helper.Visit(node);
+ return helper.Result;
+}
+
//------------------------------------------------------------------------
// RehomeArgForFastTailCall: Introduce temps for args that may be overwritten
// during fast tailcall sequence.
diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h
index 8f5d18e16f9eff..a09f858a2a5fd0 100644
--- a/src/coreclr/jit/lower.h
+++ b/src/coreclr/jit/lower.h
@@ -202,6 +202,7 @@ class Lowering final : public Phase
GenTree* LowerNonvirtPinvokeCall(GenTreeCall* call);
GenTree* LowerTailCallViaJitHelper(GenTreeCall* callNode, GenTree* callTarget);
void LowerFastTailCall(GenTreeCall* callNode);
+ GenTree* FirstOperand(GenTree* node);
void RehomeArgForFastTailCall(unsigned int lclNum,
GenTree* insertTempBefore,
GenTree* lookForUsesStart,
diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_122138/Runtime_122138.cs b/src/tests/JIT/Regression/JitBlue/Runtime_122138/Runtime_122138.cs
new file mode 100644
index 00000000000000..ae896d12e8e64b
--- /dev/null
+++ b/src/tests/JIT/Regression/JitBlue/Runtime_122138/Runtime_122138.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.CompilerServices;
+using Xunit;
+
+public class Runtime_122138
+{
+ [MethodImpl(MethodImplOptions.NoOptimization)]
+ [Fact]
+ public static void TestEntryPoint()
+ {
+ var test = new Runtime_122138();
+ test.Method1(0, 0, 0, 999, 999);
+ }
+
+ private void Method1(int a, int b, int c, int value1, int? value2)
+ {
+ Method2(1, 2, 3, value1, value2);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void Method2(long a, int b, int c, int? value1, int? value2)
+ {
+ Assert.Equal(value1, value2);
+ }
+}
\ No newline at end of file
diff --git a/src/tests/JIT/Regression/Regression_ro_1.csproj b/src/tests/JIT/Regression/Regression_ro_1.csproj
index 7181197dd10a42..123bfaa9c14033 100644
--- a/src/tests/JIT/Regression/Regression_ro_1.csproj
+++ b/src/tests/JIT/Regression/Regression_ro_1.csproj
@@ -75,6 +75,7 @@
+