Skip to content

Commit 71373ae

Browse files
authored
[RISC-V][LoongArch64] Pass structs containing empty struct arrays according to integer calling convention (#106266)
* Don't pass structs containing empty struct arrays according to hardware floating-point calling convention * Fix adding registers after the passed struct in native version of Echo_DoubleFloatNestedEmpty_InIntegerRegs_RiscV * Make sure xunit uses the Equals implementation provided by overriding Equals from object * Add comment to clarify why EmptyStructs test uses C++ instead of C
1 parent e96eaee commit 71373ae

File tree

4 files changed

+134
-30
lines changed

4 files changed

+134
-30
lines changed

src/coreclr/tools/Common/Internal/Runtime/RiscVLoongArch64FpStruct.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ public struct FpStructInRegistersInfo
4343
public uint offset2nd;
4444

4545
public uint SizeShift1st() { return (uint)((int)flags >> (int)FpStruct.PosSizeShift1st) & 0b11; }
46-
4746
public uint SizeShift2nd() { return (uint)((int)flags >> (int)FpStruct.PosSizeShift2nd) & 0b11; }
4847

4948
public uint Size1st() { return 1u << (int)SizeShift1st(); }
@@ -84,7 +83,7 @@ private static bool HandleInlineArray(int elementTypeIndex, int nElements, ref F
8483
int nFlattenedFieldsPerElement = typeIndex - elementTypeIndex;
8584
if (nFlattenedFieldsPerElement == 0)
8685
{
87-
Debug.Assert(nElements == 1, "HasImpliedRepeatedFields must have returned a false positive");
86+
Debug.Assert(nElements == 1, "HasImpliedRepeatedFields must have returned a false, it can't be an array");
8887
return true; // ignoring empty struct
8988
}
9089

@@ -158,6 +157,14 @@ private static bool FlattenFields(TypeDesc td, uint offset, ref FpStructInRegist
158157
{
159158
Debug.Assert(nFields == 1);
160159
int nElements = td.GetElementSize().AsInt / prevField.FieldType.GetElementSize().AsInt;
160+
161+
// Only InlineArrays can have element type of empty struct, fixed-size buffers take only primitives
162+
if ((typeIndex - elementTypeIndex) == 0 && (td as MetadataType).IsInlineArray)
163+
{
164+
Debug.Assert(nElements > 0, "InlineArray length must be > 0");
165+
return false; // struct containing an array of empty structs is passed by integer calling convention
166+
}
167+
161168
if (!HandleInlineArray(elementTypeIndex, nElements, ref info, ref typeIndex))
162169
return false;
163170
}

src/coreclr/vm/methodtable.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2710,7 +2710,7 @@ static bool HandleInlineArray(int elementTypeIndex, int nElements, FpStructInReg
27102710
int nFlattenedFieldsPerElement = typeIndex - elementTypeIndex;
27112711
if (nFlattenedFieldsPerElement == 0)
27122712
{
2713-
assert(nElements == 1); // HasImpliedRepeatedFields must have returned a false positive
2713+
assert(nElements == 1); // HasImpliedRepeatedFields must have returned a false positive, it can't be an array
27142714
LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s * ignoring empty struct\n",
27152715
nestingLevel * 4, ""));
27162716
return true;
@@ -2819,6 +2819,17 @@ static bool FlattenFields(TypeHandle th, uint32_t offset, FpStructInRegistersInf
28192819
{
28202820
assert(nFields == 1);
28212821
int nElements = pMT->GetNumInstanceFieldBytes() / fields[0].GetSize();
2822+
2823+
// Only InlineArrays can have element type of empty struct, fixed-size buffers take only primitives
2824+
if ((typeIndex - elementTypeIndex) == 0 && pMT->GetClass()->IsInlineArray())
2825+
{
2826+
assert(nElements > 0); // InlineArray length must be > 0
2827+
LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s "
2828+
" * struct %s containing a %i-element array of empty structs %s is passed by integer calling convention\n",
2829+
nestingLevel * 4, "", pMT->GetDebugClassName(), nElements, fields[0].GetDebugName()));
2830+
return false;
2831+
}
2832+
28222833
if (!HandleInlineArray(elementTypeIndex, nElements, info, typeIndex DEBUG_ARG(nestingLevel + 1)))
28232834
return false;
28242835
}

src/tests/JIT/Directed/StructABI/EmptyStructs.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,28 @@ extern "C" DLLEXPORT DoubleFloatNestedEmpty Echo_DoubleFloatNestedEmpty_InIntege
287287
float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6,
288288
DoubleFloatNestedEmpty a1_a2, int a3, float fa7)
289289
{
290-
return a1_a2;
291290
a1_a2.Float0 += (float)a3 + fa7;
291+
return a1_a2;
292+
}
293+
294+
295+
struct ArrayOfEmpties
296+
{
297+
Empty e[1];
298+
};
299+
300+
struct ArrayOfEmptiesFloatDouble
301+
{
302+
ArrayOfEmpties ArrayOfEmpties0;
303+
float Float0;
304+
double Double0;
305+
};
306+
307+
extern "C" DLLEXPORT ArrayOfEmptiesFloatDouble Echo_ArrayOfEmptiesFloatDouble_RiscV(int a0, float fa0,
308+
ArrayOfEmptiesFloatDouble a1_a2, int a3, float fa1)
309+
{
310+
a1_a2.Double0 += (double)a3 + fa1;
311+
return a1_a2;
292312
}
293313

294314

src/tests/JIT/Directed/StructABI/EmptyStructs.cs

Lines changed: 92 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
// Note: this test checks passing empty struct fields in .NET; confronting it against C++ on native compilers is just
5+
// a means to assert compliance to the platform calling convention. The native part is using C++ because it defines
6+
// empty structs as 1 byte like in .NET. Empty structs in C are undefined (it's a GCC extension to define them as 0
7+
// bytes) and .NET managed/unmanaged interop follows the C ABI, not C++, so signatures with empty struct fields should
8+
// not be used in any real-world interop calls.
9+
410
using System;
511
using System.Runtime.InteropServices;
612
using System.Runtime.CompilerServices;
@@ -77,8 +83,8 @@ public struct IntEmpty
7783
public static IntEmpty Get()
7884
=> new IntEmpty { Int0 = 0xBabc1a };
7985

80-
public bool Equals(IntEmpty other)
81-
=> Int0 == other.Int0;
86+
public override bool Equals(object other)
87+
=> other is IntEmpty o && Int0 == o.Int0;
8288

8389
public override string ToString()
8490
=> $"{{Int0:{Int0:x}}}";
@@ -128,8 +134,8 @@ public struct IntEmptyPair
128134
public static IntEmptyPair Get()
129135
=> new IntEmptyPair { IntEmpty0 = IntEmpty.Get(), IntEmpty1 = IntEmpty.Get() };
130136

131-
public bool Equals(IntEmptyPair other)
132-
=> IntEmpty0.Equals(other.IntEmpty0) && IntEmpty1.Equals(other.IntEmpty1);
137+
public override bool Equals(object other)
138+
=> other is IntEmptyPair o && IntEmpty0.Equals(o.IntEmpty0) && IntEmpty1.Equals(o.IntEmpty1);
133139

134140
public override string ToString()
135141
=> $"{{IntEmpty0:{IntEmpty0}, IntEmpty1:{IntEmpty1}}}";
@@ -181,8 +187,8 @@ public struct EmptyFloatIntInt
181187
public static EmptyFloatIntInt Get()
182188
=> new EmptyFloatIntInt { Float0 = 2.71828f, Int0 = 0xBabc1a, Int1 = 0xC10c1a };
183189

184-
public bool Equals(EmptyFloatIntInt other)
185-
=> Float0 == other.Float0 && Int0 == other.Int0 && Int1 == other.Int1;
190+
public override bool Equals(object other)
191+
=> other is EmptyFloatIntInt o && Float0 == o.Float0 && Int0 == o.Int0 && Int1 == o.Int1;
186192

187193
public override string ToString()
188194
=> $"{{Float0:{Float0}, Int0:{Int0:x}, Int1:{Int1:x}}}";
@@ -236,8 +242,8 @@ public struct FloatFloatEmptyFloat
236242
public static FloatFloatEmptyFloat Get()
237243
=> new FloatFloatEmptyFloat { Float0 = 2.71828f, Float1 = 3.14159f, Float2 = 1.61803f };
238244

239-
public bool Equals(FloatFloatEmptyFloat other)
240-
=> Float0 == other.Float0 && Float1 == other.Float1 && Float2 == other.Float2;
245+
public override bool Equals(object other)
246+
=> other is FloatFloatEmptyFloat o && Float0 == o.Float0 && Float1 == o.Float1 && Float2 == o.Float2;
241247

242248
public override string ToString()
243249
=> $"{{Float0:{Float0}, Float1:{Float1}, Float2:{Float2}}}";
@@ -294,8 +300,8 @@ public struct Empty8Float
294300
public static Empty8Float Get()
295301
=> new Empty8Float { Float0 = 2.71828f };
296302

297-
public bool Equals(Empty8Float other)
298-
=> Float0 == other.Float0;
303+
public override bool Equals(object other)
304+
=> other is Empty8Float o && Float0 == o.Float0;
299305

300306
public override string ToString()
301307
=> $"{{Float0:{Float0}}}";
@@ -474,8 +480,8 @@ public struct FloatEmpty8Float
474480
public static FloatEmpty8Float Get()
475481
=> new FloatEmpty8Float { Float0 = 2.71828f, Float1 = 3.14159f };
476482

477-
public bool Equals(FloatEmpty8Float other)
478-
=> Float0 == other.Float0 && Float1 == other.Float1;
483+
public override bool Equals(object other)
484+
=> other is FloatEmpty8Float o && Float0 == o.Float0 && Float1 == o.Float1;
479485

480486
public override string ToString()
481487
=> $"{{Float0:{Float0}, Float1:{Float1}}}";
@@ -654,8 +660,8 @@ public struct FloatEmptyShort
654660
public static FloatEmptyShort Get()
655661
=> new FloatEmptyShort { Float0 = 2.71828f, Short0 = 0x1dea };
656662

657-
public bool Equals(FloatEmptyShort other)
658-
=> Float0 == other.Float0 && Short0 == other.Short0;
663+
public override bool Equals(object other)
664+
=> other is FloatEmptyShort o && Float0 == o.Float0 && Short0 == o.Short0;
659665

660666
public override string ToString()
661667
=> $"{{Float0:{Float0}, Short0:{Short0}}}";
@@ -793,8 +799,8 @@ public struct EmptyFloatEmpty5Sbyte
793799
public static EmptyFloatEmpty5Sbyte Get()
794800
=> new EmptyFloatEmpty5Sbyte { Float0 = 2.71828f, Sbyte0 = -123 };
795801

796-
public bool Equals(EmptyFloatEmpty5Sbyte other)
797-
=> Float0 == other.Float0 && Sbyte0 == other.Sbyte0;
802+
public override bool Equals(object other)
803+
=> other is EmptyFloatEmpty5Sbyte o && Float0 == o.Float0 && Sbyte0 == o.Sbyte0;
798804

799805
public override string ToString()
800806
=> $"{{Float0:{Float0}, Sbyte0:{Sbyte0}}}";
@@ -848,8 +854,8 @@ public struct EmptyFloatEmpty5Byte
848854
public static EmptyFloatEmpty5Byte Get()
849855
=> new EmptyFloatEmpty5Byte { Float0 = 2.71828f, Byte0 = 123 };
850856

851-
public bool Equals(EmptyFloatEmpty5Byte other)
852-
=> Float0 == other.Float0 && Byte0 == other.Byte0;
857+
public override bool Equals(object other)
858+
=> other is EmptyFloatEmpty5Byte o && Float0 == o.Float0 && Byte0 == o.Byte0;
853859

854860
public override string ToString()
855861
=> $"{{Float0:{Float0}, Byte0:{Byte0}}}";
@@ -1037,8 +1043,8 @@ public struct DoubleFloatNestedEmpty
10371043
public static DoubleFloatNestedEmpty Get()
10381044
=> new DoubleFloatNestedEmpty { Double0 = 2.71828, Float0 = 3.14159f };
10391045

1040-
public bool Equals(DoubleFloatNestedEmpty other)
1041-
=> Double0 == other.Double0 && Float0 == other.Float0;
1046+
public override bool Equals(object other)
1047+
=> other is DoubleFloatNestedEmpty o && Double0 == o.Double0 && Float0 == o.Float0;
10421048

10431049
public override string ToString()
10441050
=> $"{{Double0:{Double0}, Float0:{Float0}}}";
@@ -1123,6 +1129,66 @@ public static void Test_DoubleFloatNestedEmpty_InIntegerRegs_ByReflection_RiscV(
11231129
}
11241130
#endregion
11251131

1132+
#region ArrayOfEmptiesFloatDouble_RiscVTests
1133+
[InlineArray(1)]
1134+
public struct ArrayOfEmpties
1135+
{
1136+
public Empty e;
1137+
}
1138+
1139+
public struct ArrayOfEmptiesFloatDouble
1140+
{
1141+
public ArrayOfEmpties ArrayOfEmpties0;
1142+
public float Float0;
1143+
public double Double0;
1144+
1145+
public static ArrayOfEmptiesFloatDouble Get()
1146+
=> new ArrayOfEmptiesFloatDouble { Float0 = 3.14159f, Double0 = 2.71828 };
1147+
1148+
public override bool Equals(object other)
1149+
=> other is ArrayOfEmptiesFloatDouble o && Float0 == o.Float0 && Double0 == o.Double0;
1150+
1151+
public override string ToString()
1152+
=> $"{{Float0:{Float0}, Double0:{Double0}}}";
1153+
}
1154+
1155+
[DllImport("EmptyStructsLib")]
1156+
public static extern ArrayOfEmptiesFloatDouble Echo_ArrayOfEmptiesFloatDouble_RiscV(int a0, float fa0,
1157+
ArrayOfEmptiesFloatDouble a1_a2, int a3, float fa1);
1158+
1159+
[MethodImpl(MethodImplOptions.NoInlining)]
1160+
public static ArrayOfEmptiesFloatDouble Echo_ArrayOfEmptiesFloatDouble_RiscV_Managed(int a0, float fa0,
1161+
ArrayOfEmptiesFloatDouble a1_a2, int a3, float fa1)
1162+
{
1163+
a1_a2.Double0 += (double)a3 + fa1;
1164+
return a1_a2;
1165+
}
1166+
1167+
[Fact]
1168+
public static void Test_ArrayOfEmptiesFloatDouble_RiscV()
1169+
{
1170+
ArrayOfEmptiesFloatDouble expected = ArrayOfEmptiesFloatDouble.Get();
1171+
ArrayOfEmptiesFloatDouble native = Echo_ArrayOfEmptiesFloatDouble_RiscV(0, 0f, expected, 1, -1f);
1172+
ArrayOfEmptiesFloatDouble managed = Echo_ArrayOfEmptiesFloatDouble_RiscV_Managed(0, 0f, expected, 1, -1f);
1173+
1174+
Assert.Equal(expected, native);
1175+
Assert.Equal(expected, managed);
1176+
}
1177+
1178+
[Fact]
1179+
public static void Test_ArrayOfEmptiesFloatDouble_ByReflection_RiscV()
1180+
{
1181+
var expected = ArrayOfEmptiesFloatDouble.Get();
1182+
var native = (ArrayOfEmptiesFloatDouble)typeof(Program).GetMethod("Echo_ArrayOfEmptiesFloatDouble_RiscV").Invoke(
1183+
null, new object[] {0, 0f, expected, 1, -1f});
1184+
var managed = (ArrayOfEmptiesFloatDouble)typeof(Program).GetMethod("Echo_ArrayOfEmptiesFloatDouble_RiscV_Managed").Invoke(
1185+
null, new object[] {0, 0f, expected, 1, -1f});
1186+
1187+
Assert.Equal(expected, native);
1188+
Assert.Equal(expected, managed);
1189+
}
1190+
#endregion
1191+
11261192
#region EmptyUshortAndDouble_RiscVTests
11271193
public struct EmptyUshortAndDouble
11281194
{
@@ -1138,8 +1204,8 @@ public struct EmptyUshort
11381204
EmptyUshort0 = new EmptyUshort { Ushort0 = 0xBaca }, Double0 = 2.71828
11391205
};
11401206

1141-
public bool Equals(EmptyUshortAndDouble other)
1142-
=> EmptyUshort0.Ushort0 == other.EmptyUshort0.Ushort0 && Double0 == other.Double0;
1207+
public override bool Equals(object other)
1208+
=> other is EmptyUshortAndDouble o && EmptyUshort0.Ushort0 == o.EmptyUshort0.Ushort0 && Double0 == o.Double0;
11431209

11441210
public override string ToString()
11451211
=> $"{{EmptyUshort0.Ushort0:{EmptyUshort0.Ushort0}, Double0:{Double0}}}";
@@ -1193,8 +1259,8 @@ public struct PackedEmptyFloatLong
11931259
public static PackedEmptyFloatLong Get()
11941260
=> new PackedEmptyFloatLong { Float0 = 2.71828f, Long0 = 0xDadAddedC0ffee };
11951261

1196-
public bool Equals(PackedEmptyFloatLong other)
1197-
=> Float0 == other.Float0 && Long0 == other.Long0;
1262+
public override bool Equals(object other)
1263+
=> other is PackedEmptyFloatLong o && Float0 == o.Float0 && Long0 == o.Long0;
11981264

11991265
public override string ToString()
12001266
=> $"{{Float0:{Float0}, Long0:{Long0}}}";
@@ -1383,8 +1449,8 @@ public struct PackedFloatEmptyByte
13831449
public static PackedFloatEmptyByte Get()
13841450
=> new PackedFloatEmptyByte { Float0 = 2.71828f, Byte0 = 0xba };
13851451

1386-
public bool Equals(PackedFloatEmptyByte other)
1387-
=> Float0 == other.Float0 && Byte0 == other.Byte0;
1452+
public override bool Equals(object other)
1453+
=> other is PackedFloatEmptyByte o && Float0 == o.Float0 && Byte0 == o.Byte0;
13881454

13891455
public override string ToString()
13901456
=> $"{{Float0:{Float0}, Byte0:{Byte0}}}";

0 commit comments

Comments
 (0)