diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp
index ef0578b2cbff50..b0c76482db9b88 100644
--- a/src/coreclr/jit/lower.cpp
+++ b/src/coreclr/jit/lower.cpp
@@ -2984,9 +2984,8 @@ void Lowering::LowerRet(GenTreeUnOp* ret)
// There are two kinds of retyping:
// - A simple bitcast can be inserted when:
// - We're returning a floating type as an integral type or vice-versa, or
- // - We're returning a struct as a primitive type and using the old form of retyping.
- // - If we're returning a struct as a primitive type and *not* using old retying, we change the type of
- // 'retval' in 'LowerRetStructLclVar()'
+ // - If we're returning a struct as a primitive type, we change the type of
+ // 'retval' in 'LowerRetStructLclVar()'
bool needBitcast =
(ret->TypeGet() != TYP_VOID) && (varTypeUsesFloatReg(ret) != varTypeUsesFloatReg(ret->gtGetOp1()));
bool doPrimitiveBitcast = false;
@@ -3408,6 +3407,7 @@ void Lowering::LowerRetSingleRegStructLclVar(GenTreeUnOp* ret)
unsigned lclNum = lclVar->GetLclNum();
LclVarDsc* varDsc = comp->lvaGetDesc(lclNum);
+ bool replacedInLowering = false;
if (varDsc->CanBeReplacedWithItsField(comp))
{
// We can replace the struct with its only field and keep the field on a register.
@@ -3420,8 +3420,9 @@ void Lowering::LowerRetSingleRegStructLclVar(GenTreeUnOp* ret)
"[%06u]\n",
lclNum, fieldLclNum, comp->dspTreeID(ret));
lclVar->ChangeType(fieldDsc->lvType);
- lclNum = fieldLclNum;
- varDsc = comp->lvaGetDesc(lclNum);
+ lclNum = fieldLclNum;
+ varDsc = comp->lvaGetDesc(lclNum);
+ replacedInLowering = true;
}
else if (varDsc->lvPromoted)
{
@@ -3432,20 +3433,52 @@ void Lowering::LowerRetSingleRegStructLclVar(GenTreeUnOp* ret)
if (varDsc->lvDoNotEnregister)
{
+ assert(!replacedInLowering);
lclVar->ChangeOper(GT_LCL_FLD);
lclVar->AsLclFld()->SetLclOffs(0);
- lclVar->ChangeType(ret->TypeGet());
+
+ // We are returning as a primitive type and the lcl is of struct type.
+ assert(comp->info.compRetNativeType != TYP_STRUCT);
+ assert((genTypeSize(comp->info.compRetNativeType) == genTypeSize(ret)) ||
+ (varTypeIsIntegral(ret) && varTypeIsIntegral(comp->info.compRetNativeType) &&
+ (genTypeSize(comp->info.compRetNativeType) <= genTypeSize(ret))));
+ // If the actual return type requires normalization, then make sure we
+ // do so by using the correct small type for the GT_LCL_FLD. It would
+ // be conservative to check just compRetNativeType for this since small
+ // structs are normalized to primitive types when they are returned in
+ // registers, so we would normalize for them as well.
+ if (varTypeIsSmall(comp->info.compRetType))
+ {
+ assert(genTypeSize(comp->info.compRetNativeType) == genTypeSize(comp->info.compRetType));
+ lclVar->ChangeType(comp->info.compRetType);
+ }
+ else
+ {
+ // Otherwise we don't mind that we leave the upper bits undefined.
+ lclVar->ChangeType(ret->TypeGet());
+ }
}
else
{
const var_types lclVarType = varDsc->GetRegisterType(lclVar);
assert(lclVarType != TYP_UNDEF);
+
+ if (varDsc->lvNormalizeOnLoad() && replacedInLowering)
+ {
+ // For a normalize-on-load var that we replaced late we need to insert a cast
+ // as morph would typically be responsible for this.
+ GenTreeCast* cast = comp->gtNewCastNode(TYP_INT, lclVar, false, lclVarType);
+ ret->gtOp1 = cast;
+ BlockRange().InsertBefore(ret, cast);
+ ContainCheckCast(cast);
+ }
+
const var_types actualType = genActualType(lclVarType);
lclVar->ChangeType(actualType);
if (varTypeUsesFloatReg(ret) != varTypeUsesFloatReg(lclVarType))
{
- GenTree* bitcast = comp->gtNewBitCastNode(ret->TypeGet(), lclVar);
+ GenTree* bitcast = comp->gtNewBitCastNode(ret->TypeGet(), ret->gtOp1);
ret->gtOp1 = bitcast;
BlockRange().InsertBefore(ret, bitcast);
ContainCheckBitCast(bitcast);
diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_58373/Runtime_58373_1.cs b/src/tests/JIT/Regression/JitBlue/Runtime_58373/Runtime_58373_1.cs
new file mode 100644
index 00000000000000..76a46072e556d2
--- /dev/null
+++ b/src/tests/JIT/Regression/JitBlue/Runtime_58373/Runtime_58373_1.cs
@@ -0,0 +1,29 @@
+// 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;
+
+public unsafe class Runtime_58373
+{
+ public static int Main()
+ {
+ short halfValue = HalfToInt16Bits(MakeHalf());
+ int x = halfValue;
+ short val2 = HalfToInt16Bits(*(Half*)&x);
+
+ return halfValue == val2 ? 100 : -1;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static Half MakeHalf()
+ {
+ return (Half)(-1.0f);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static short HalfToInt16Bits(Half h)
+ {
+ return *(short*)&h;
+ }
+}
\ No newline at end of file
diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_58373/Runtime_58373_1.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_58373/Runtime_58373_1.csproj
new file mode 100644
index 00000000000000..1a1d3eadae5ce4
--- /dev/null
+++ b/src/tests/JIT/Regression/JitBlue/Runtime_58373/Runtime_58373_1.csproj
@@ -0,0 +1,13 @@
+
+
+ Exe
+
+
+ None
+ True
+ True
+
+
+
+
+
\ No newline at end of file
diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_58373/Runtime_58373_2.cs b/src/tests/JIT/Regression/JitBlue/Runtime_58373/Runtime_58373_2.cs
new file mode 100644
index 00000000000000..c8d1286c1ba7b2
--- /dev/null
+++ b/src/tests/JIT/Regression/JitBlue/Runtime_58373/Runtime_58373_2.cs
@@ -0,0 +1,47 @@
+// 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;
+
+public unsafe class Runtime_58373
+{
+ public static int Main()
+ {
+ // Use up a lot of registers
+ int a = GetVal();
+ int b = GetVal();
+ int c = GetVal();
+ int d = GetVal();
+ int e = GetVal();
+ int f = GetVal();
+ int g = GetVal();
+ int h = GetVal();
+ int i = GetVal();
+
+ short val1 = HalfToInt16Bits(MakeHalf());
+ Half half = MakeHalf();
+ MakeHalf(); // This will spill lower 16 bits of 'half' to memory
+ short val2 = HalfToInt16Bits(half); // This will pass 32 bits as arg with upper 16 bits undefined
+
+ return val1 == val2 ? 100 + a + b + c + d + e + f + g + h + i : -1;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static int GetVal()
+ {
+ return 0;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static Half MakeHalf()
+ {
+ return default;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static short HalfToInt16Bits(Half h)
+ {
+ return *(short*)&h;
+ }
+}
\ No newline at end of file
diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_58373/Runtime_58373_2.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_58373/Runtime_58373_2.csproj
new file mode 100644
index 00000000000000..1a1d3eadae5ce4
--- /dev/null
+++ b/src/tests/JIT/Regression/JitBlue/Runtime_58373/Runtime_58373_2.csproj
@@ -0,0 +1,13 @@
+
+
+ Exe
+
+
+ None
+ True
+ True
+
+
+
+
+
\ No newline at end of file