-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[x64][SysV] Classify empty structs for passing like padding #103799
Changes from all commits
aba87b6
131a3c5
cb22839
db406a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2114,11 +2114,14 @@ bool MethodTable::ClassifyEightBytesWithManagedLayout(SystemVStructRegisterPassi | |
|
||
DWORD numIntroducedFields = GetNumIntroducedInstanceFields(); | ||
|
||
// It appears the VM gives a struct with no fields of size 1. | ||
// Don't pass in register such structure. | ||
if (numIntroducedFields == 0) | ||
{ | ||
return false; | ||
helperPtr->largestFieldOffset = startOffsetOfStruct; | ||
LOG((LF_JIT, LL_EVERYTHING, "%*s**** Classify empty struct %s (%p) like padding, startOffset %d, total struct size %d\n", | ||
nestingLevel * 5, "", this->GetDebugClassName(), this, startOffsetOfStruct, helperPtr->structSize)); | ||
|
||
AssignClassifiedEightByteTypes(helperPtr, nestingLevel); | ||
return true; | ||
} | ||
|
||
// The SIMD Intrinsic types are meant to be handled specially and should not be passed as struct registers | ||
|
@@ -2334,7 +2337,12 @@ bool MethodTable::ClassifyEightBytesWithNativeLayout(SystemVStructRegisterPassin | |
// No fields. | ||
if (numIntroducedFields == 0) | ||
{ | ||
return false; | ||
helperPtr->largestFieldOffset = startOffsetOfStruct; | ||
LOG((LF_JIT, LL_EVERYTHING, "%*s**** Classify empty struct %s (%p) like padding, startOffset %d, total struct size %d\n", | ||
nestingLevel * 5, "", this->GetDebugClassName(), this, startOffsetOfStruct, helperPtr->structSize)); | ||
|
||
AssignClassifiedEightByteTypes(helperPtr, nestingLevel); | ||
return true; | ||
} | ||
|
||
bool hasImpliedRepeatedFields = HasImpliedRepeatedFields(this); | ||
|
@@ -2586,8 +2594,12 @@ void MethodTable::AssignClassifiedEightByteTypes(SystemVStructRegisterPassingHe | |
// Calculate the eightbytes and their types. | ||
|
||
int lastFieldOrdinal = sortedFieldOrder[largestFieldOffset]; | ||
unsigned int offsetAfterLastFieldByte = largestFieldOffset + helperPtr->fieldSizes[lastFieldOrdinal]; | ||
SystemVClassificationType lastFieldClassification = helperPtr->fieldClassifications[lastFieldOrdinal]; | ||
unsigned int lastFieldSize = (lastFieldOrdinal >= 0) ? helperPtr->fieldSizes[lastFieldOrdinal] : 0; | ||
unsigned int offsetAfterLastFieldByte = largestFieldOffset + lastFieldSize; | ||
_ASSERTE(offsetAfterLastFieldByte <= helperPtr->structSize); | ||
SystemVClassificationType lastFieldClassification = (lastFieldOrdinal >= 0) | ||
? helperPtr->fieldClassifications[lastFieldOrdinal] | ||
: SystemVClassificationTypeNoClass; | ||
|
||
unsigned int usedEightBytes = 0; | ||
unsigned int accumulatedSizeForEightBytes = 0; | ||
|
@@ -2614,6 +2626,8 @@ void MethodTable::AssignClassifiedEightByteTypes(SystemVStructRegisterPassingHe | |
// the SysV ABI spec. | ||
fieldSize = 1; | ||
fieldClassificationType = offset < offsetAfterLastFieldByte ? SystemVClassificationTypeNoClass : lastFieldClassification; | ||
if (offset % SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES == 0) // new eightbyte | ||
foundFieldInEightByte = false; | ||
} | ||
else | ||
{ | ||
|
@@ -2666,13 +2680,16 @@ void MethodTable::AssignClassifiedEightByteTypes(SystemVStructRegisterPassingHe | |
} | ||
} | ||
|
||
if ((offset + 1) % SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES == 0) // If we just finished checking the last byte of an eightbyte | ||
// If we just finished checking the last byte of an eightbyte or the entire struct | ||
if ((offset + 1) % SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES == 0 || (offset + 1) == helperPtr->structSize) | ||
{ | ||
if (!foundFieldInEightByte) | ||
{ | ||
// If we didn't find a field in an eight-byte (i.e. there are no explicit offsets that start a field in this eightbyte) | ||
// If we didn't find a field in an eightbyte (i.e. there are no explicit offsets that start a field in this eightbyte) | ||
// then the classification of this eightbyte might be NoClass. We can't hand a classification of NoClass to the JIT | ||
// so set the class to Integer (as though the struct has a char[8] padding) if the class is NoClass. | ||
// | ||
// TODO: Fix JIT, NoClass eightbytes are valid and passing them is broken because of this. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you plan to fix this TODO in this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. It would require going through the JIT like I did in #101796 for RISC-V and fix in many places. Right now my priority is on RISC-V but when I'm done with #101796 I can revisit it. By then @jakobbotsch's ongoing effort to centralize ABI passing infos will probably be more advanced, which will also facilitate fixing x64. |
||
if (helperPtr->eightByteClassifications[offset / SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES] == SystemVClassificationTypeNoClass) | ||
{ | ||
helperPtr->eightByteClassifications[offset / SYSTEMV_EIGHT_BYTE_SIZE_IN_BYTES] = SystemVClassificationTypeInteger; | ||
|
@@ -2705,9 +2722,9 @@ void MethodTable::AssignClassifiedEightByteTypes(SystemVStructRegisterPassingHe | |
LOG((LF_JIT, LL_EVERYTHING, " **** Number EightBytes: %d\n", helperPtr->eightByteCount)); | ||
for (unsigned i = 0; i < helperPtr->eightByteCount; i++) | ||
{ | ||
_ASSERTE(helperPtr->eightByteClassifications[i] != SystemVClassificationTypeNoClass); | ||
LOG((LF_JIT, LL_EVERYTHING, " **** eightByte %d -- classType: %s, eightByteOffset: %d, eightByteSize: %d\n", | ||
i, GetSystemVClassificationTypeName(helperPtr->eightByteClassifications[i]), helperPtr->eightByteOffsets[i], helperPtr->eightByteSizes[i])); | ||
_ASSERTE(helperPtr->eightByteClassifications[i] != SystemVClassificationTypeNoClass); | ||
} | ||
#endif // _DEBUG | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
#include <stdint.h> | ||
#include <stddef.h> | ||
|
||
#ifdef _MSC_VER | ||
#define DLLEXPORT __declspec(dllexport) | ||
#else | ||
#define DLLEXPORT __attribute__((visibility("default"))) | ||
#endif // _MSC_VER | ||
|
||
struct Empty | ||
{ | ||
}; | ||
static_assert(sizeof(Empty) == 1, "Empty struct must be sized like in .NET"); | ||
|
||
|
||
struct IntEmpty | ||
{ | ||
int32_t Int0; | ||
Empty Empty0; | ||
}; | ||
|
||
extern "C" DLLEXPORT IntEmpty EchoIntEmptySysV(int i0, IntEmpty val) | ||
{ | ||
return val; | ||
} | ||
|
||
|
||
struct IntEmptyPair | ||
{ | ||
IntEmpty IntEmpty0; | ||
IntEmpty IntEmpty1; | ||
}; | ||
|
||
extern "C" DLLEXPORT IntEmptyPair EchoIntEmptyPairSysV(int i0, IntEmptyPair val) | ||
{ | ||
return val; | ||
} | ||
|
||
|
||
struct EmptyFloatIntInt | ||
{ | ||
Empty Empty0; | ||
float Float0; | ||
int32_t Int0; | ||
int32_t Int1; | ||
}; | ||
|
||
extern "C" DLLEXPORT EmptyFloatIntInt EchoEmptyFloatIntIntSysV(int i0, float f0, EmptyFloatIntInt val) | ||
{ | ||
return val; | ||
} | ||
|
||
|
||
struct FloatFloatEmptyFloat | ||
{ | ||
float Float0; | ||
float Float1; | ||
Empty Empty0; | ||
float Float2; | ||
}; | ||
|
||
extern "C" DLLEXPORT FloatFloatEmptyFloat EchoFloatFloatEmptyFloatSysV(float f0, FloatFloatEmptyFloat val) | ||
{ | ||
return val; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
// 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.InteropServices; | ||
using System.Runtime.CompilerServices; | ||
using Xunit; | ||
|
||
public static class Program | ||
{ | ||
public struct Empty | ||
{ | ||
} | ||
|
||
|
||
public struct IntEmpty | ||
{ | ||
public int Int0; | ||
public Empty Empty0; | ||
|
||
public static IntEmpty Get() | ||
=> new IntEmpty { Int0 = 0xBabc1a }; | ||
|
||
public bool Equals(IntEmpty other) | ||
=> Int0 == other.Int0; | ||
|
||
public override string ToString() | ||
=> $"{{Int0:{Int0:x}}}"; | ||
} | ||
|
||
[DllImport("EmptyStructsLib")] | ||
public static extern IntEmpty EchoIntEmptySysV(int i0, IntEmpty val); | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
public static IntEmpty EchoIntEmptySysVManaged(int i0, IntEmpty val) => val; | ||
|
||
[Fact] | ||
public static void TestIntEmptySysV() | ||
{ | ||
IntEmpty expected = IntEmpty.Get(); | ||
IntEmpty native = EchoIntEmptySysV(0, expected); | ||
IntEmpty managed = EchoIntEmptySysVManaged(0, expected); | ||
|
||
Assert.Equal(expected, native); | ||
Assert.Equal(expected, managed); | ||
} | ||
|
||
|
||
public struct IntEmptyPair | ||
{ | ||
public IntEmpty IntEmpty0; | ||
public IntEmpty IntEmpty1; | ||
|
||
public static IntEmptyPair Get() | ||
=> new IntEmptyPair { IntEmpty0 = IntEmpty.Get(), IntEmpty1 = IntEmpty.Get() }; | ||
|
||
public bool Equals(IntEmptyPair other) | ||
=> IntEmpty0.Equals(other.IntEmpty0) && IntEmpty1.Equals(other.IntEmpty1); | ||
|
||
public override string ToString() | ||
=> $"{{IntEmpty0:{IntEmpty0}, IntEmpty1:{IntEmpty1}}}"; | ||
} | ||
|
||
[DllImport("EmptyStructsLib")] | ||
public static extern IntEmptyPair EchoIntEmptyPairSysV(int i0, IntEmptyPair val); | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
public static IntEmptyPair EchoIntEmptyPairSysVManaged(int i0, IntEmptyPair val) => val; | ||
|
||
[Fact] | ||
public static void TestIntEmptyPairSysV() | ||
{ | ||
IntEmptyPair expected = IntEmptyPair.Get(); | ||
IntEmptyPair native = EchoIntEmptyPairSysV(0, expected); | ||
IntEmptyPair managed = EchoIntEmptyPairSysVManaged(0, expected); | ||
|
||
Assert.Equal(expected, native); | ||
Assert.Equal(expected, managed); | ||
} | ||
|
||
|
||
public struct EmptyFloatIntInt | ||
{ | ||
public Empty Empty0; | ||
public float Float0; | ||
public int Int0; | ||
public int Int1; | ||
|
||
public static EmptyFloatIntInt Get() | ||
=> new EmptyFloatIntInt { Float0 = 2.71828f, Int0 = 0xBabc1a, Int1 = 0xC10c1a }; | ||
|
||
public bool Equals(EmptyFloatIntInt other) | ||
=> Float0 == other.Float0 && Int0 == other.Int0 && Int1 == other.Int1; | ||
|
||
public override string ToString() | ||
=> $"{{Float0:{Float0}, Int0:{Int0:x}, Int1:{Int1:x}}}"; | ||
} | ||
|
||
[DllImport("EmptyStructsLib")] | ||
public static extern EmptyFloatIntInt EchoEmptyFloatIntIntSysV(int i0, float f0, EmptyFloatIntInt val); | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
public static EmptyFloatIntInt EchoEmptyFloatIntIntSysVManaged(int i0, float f0, EmptyFloatIntInt val) => val; | ||
|
||
[Fact] | ||
public static void TestEmptyFloatIntIntSysV() | ||
{ | ||
EmptyFloatIntInt expected = EmptyFloatIntInt.Get(); | ||
EmptyFloatIntInt native = EchoEmptyFloatIntIntSysV(0, 0f, expected); | ||
EmptyFloatIntInt managed = EchoEmptyFloatIntIntSysVManaged(0, 0f, expected); | ||
|
||
Assert.Equal(expected, native); | ||
Assert.Equal(expected, managed); | ||
} | ||
|
||
|
||
public struct FloatFloatEmptyFloat | ||
{ | ||
public float Float0; | ||
public float Float1; | ||
public Empty Empty0; | ||
public float Float2; | ||
|
||
public static FloatFloatEmptyFloat Get() | ||
=> new FloatFloatEmptyFloat { Float0 = 2.71828f, Float1 = 3.14159f, Float2 = 1.61803f }; | ||
|
||
public bool Equals(FloatFloatEmptyFloat other) | ||
=> Float0 == other.Float0 && Float1 == other.Float1 && Float2 == other.Float2; | ||
|
||
public override string ToString() | ||
=> $"{{Float0:{Float0}, Float1:{Float1}, Float2:{Float2}}}"; | ||
} | ||
|
||
[DllImport("EmptyStructsLib")] | ||
public static extern FloatFloatEmptyFloat EchoFloatFloatEmptyFloatSysV(float f0, FloatFloatEmptyFloat val); | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
public static FloatFloatEmptyFloat EchoFloatFloatEmptyFloatSysVManaged(float f0, FloatFloatEmptyFloat val) => val; | ||
|
||
[Fact] | ||
public static void TestFloatFloatEmptyFloatSysV() | ||
{ | ||
FloatFloatEmptyFloat expected = FloatFloatEmptyFloat.Get(); | ||
FloatFloatEmptyFloat native = EchoFloatFloatEmptyFloatSysV(0f, expected); | ||
FloatFloatEmptyFloat managed = EchoFloatFloatEmptyFloatSysVManaged(0f, expected); | ||
|
||
Assert.Equal(expected, native); | ||
Assert.Equal(expected, managed); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does the difference between NoClass and something like
char[8]
end up being? For significant padding (structs with explicit layout) it seems like we have to consider them to be the same or things will end up odd/wrong.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
char[8] is an INTEGER eightbyte which gets assigned an integer register for passing; a NO_CLASS eightbyte doesn't get any register. Passing on the stack is the same, though (NO_CLASS padding does take up space).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opened #104098 as I'll need an URL for ActiveIssue in tests in future PRs.
Meanwhile this smaller fix is ready for review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this fix handle the significant padding cases correctly (i.e. as before this change)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should, the NO_CLASS -> INTEGER eightbyte reclassification is still there. And I didn't see any CLR tests (prio1) go off on Checked build, assuming there are tests for significant padding by now.