Skip to content
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

[RISC-V][x64] WiP: Passing empty struct fields #101796

Closed
wants to merge 70 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
c2c2bba
First, add a bunch of failing tests
tomeksowi May 2, 2024
03d4b23
Reduce empty megabyte field to 32k as msvc caps size of arguments at 64k
tomeksowi May 6, 2024
4403c4b
Don't stop calculating flags if struct size > 16 bytes
tomeksowi May 6, 2024
bf81116
Classify empty structs on x64 like padding
tomeksowi May 8, 2024
60ad834
Quell C-linkage warnings just in case
tomeksowi May 8, 2024
9064784
Add Pack=1 to bypass a known problem with field alignment requirement…
tomeksowi May 9, 2024
002c4b2
Merge branch 'main' into empty-struct-passing
tomeksowi May 10, 2024
b02c913
Check `size > 16` only when passing parameters according to integer c…
tomeksowi May 10, 2024
0fde22d
Don't assume struct size > 16 means return by implicit ref to a retur…
tomeksowi May 13, 2024
f89a68d
Don't assume if struct size > 16, IsArgPassedByRef should return true…
tomeksowi May 13, 2024
f5e3930
Adjust ArgIterator.GetNextOffset() for crossgen2 to be the same as th…
tomeksowi May 14, 2024
0a5ce05
Adjust crossgen2 version of ComputeReturnFlags (ComputeReturnValueTre…
tomeksowi May 14, 2024
6a3118b
Adjust IsArgPassedByRef for crossgen2 with native version for VM. Als…
tomeksowi May 14, 2024
0a1a7ff
Don't assume struct (size > 16) means pass by reference in Compiler::…
tomeksowi May 14, 2024
9aa2d10
Add a test for a single-float struct padded with empty struct field
tomeksowi May 14, 2024
9d9d0e2
Relax checks so the new test cases JIT without false positive assertions
tomeksowi May 16, 2024
983d423
New structure to store information about passing structs according to…
tomeksowi May 16, 2024
8366470
Implement GetRiscV64PassFPStructInfo for now with checks against exis…
tomeksowi May 17, 2024
c3e8578
Rename to match similar functions on other architectures
tomeksowi May 20, 2024
c2dd449
Use bitfields to keep the most important flags for decision making pa…
tomeksowi May 20, 2024
0d81d68
Fix JIT for structs with single float padded with empty structs
tomeksowi May 20, 2024
ec8d404
Replace bitfields with manual flags to avoid potential portability pr…
tomeksowi May 21, 2024
98e1861
Update C# version of GetRiscV64PassFpStructInRegistersInfo
tomeksowi May 21, 2024
623a2a6
Replace getRISCV64PassStructInRegisterFlags with getRiscV64PassFpStru…
tomeksowi May 21, 2024
f550c22
Fix offset assignment in C# GetRiscV64PassFpStructInRegistersInfo
tomeksowi May 22, 2024
338e244
Use enregistered struct field offsets in JIT new ABI classifiers
tomeksowi May 22, 2024
894fccd
Merge branch 'main' into empty-struct-passing
tomeksowi May 22, 2024
4cdd228
Fix build: formatting and using static ordering
tomeksowi May 22, 2024
99ec678
Include GC info in FpStructInRegistersInfo like on System V. While it…
tomeksowi May 23, 2024
b5e1cdc
Nicer size shift calculation routine
tomeksowi May 23, 2024
95e787d
Fix build
tomeksowi May 24, 2024
48e7944
Fix Empty8Float test
tomeksowi May 28, 2024
7102f31
Add EmptyFloatEmpty5(U)Byte tests
tomeksowi May 28, 2024
e5d67cd
Increase field visibility to match other tests
tomeksowi May 29, 2024
e25552e
Fix EmptyFloatEmpty5(U)Byte and LongEmptyDouble tests by using correc…
tomeksowi May 31, 2024
28a88b3
Fix LongEmptyGDoubleByImplicitRef and the rest of tests in StructABI …
tomeksowi Jun 4, 2024
123eaaf
Make tests harder
tomeksowi Jun 4, 2024
1a85e0b
Improve logging for GetRiscV64PassFpStructInRegistersInfo, similar to…
tomeksowi Jun 5, 2024
6163536
Format fix
tomeksowi Jun 5, 2024
6dc499d
Fix test EchoArrayOfEmptiesFloatDouble by returning FpStruct{ UseIntC…
tomeksowi Jun 5, 2024
afd95cd
Fix false positive marking a struct split between register and stack
tomeksowi Jun 6, 2024
de83bc7
Add more tests for cases where structs eligible for hardware floating…
tomeksowi Jun 6, 2024
978e851
Fix test EchoEmptyFloatEmpty5ByteSplitRiscV
tomeksowi Jun 6, 2024
dab9167
Remove LclVarDsc::lvIs4Field{1,2} because they were write-only
tomeksowi Jun 6, 2024
2d24bee
Fix ArgIteratorTemplate::GetNextOffset() for struct{Arr[], float}
tomeksowi Jun 6, 2024
9eb6b00
Merge branch 'main' into getnextoffset-for-struct-ptr-float
tomeksowi Jun 7, 2024
d3e974c
Merge branch 'getnextoffset-for-struct-ptr-float' into empty-struct-p…
tomeksowi Jun 7, 2024
a0ccb2a
Merge branch 'main' into empty-struct-passing
tomeksowi Jun 7, 2024
f469ef5
Fix arm32 build by reverting to using cSlotsToEnregister in lclvars.
tomeksowi Jun 10, 2024
811f947
Add failing tests for hardware floating-point calling convention by r…
tomeksowi Jun 10, 2024
b6bef68
Merge branch 'main' into empty-struct-passing
tomeksowi Jun 10, 2024
ac2670b
Fix small struct passing to calls by reflection: CopyStructToRegister…
tomeksowi Jun 11, 2024
76533b6
Fix FP structs returned from calls by reflection
tomeksowi Jun 12, 2024
c7e8a11
Fix the rest of the reflection tests by properly determining whether …
tomeksowi Jun 12, 2024
16b549f
Update crossgen2 C# version of ArgIterator to changes on the native side
tomeksowi Jun 14, 2024
3389bcf
Fix copying structs returned by hardware floating-point calling conve…
tomeksowi Jun 14, 2024
d4f81be
Merge branch 'main' into empty-struct-passing
tomeksowi Jun 14, 2024
7551d35
Merge branch 'main' into empty-struct-passing
tomeksowi Jun 18, 2024
4d2e2cb
Add signedness to integer field FpStructPassInRegistersInfo
tomeksowi Jun 21, 2024
907ac95
Better flag names
tomeksowi Jun 21, 2024
892ec22
Improve explanation why RISC-V can't use genActualType in genParamSta…
tomeksowi Jun 21, 2024
e73450e
Take signedness into account in CopyStructToRegisters
tomeksowi Jun 21, 2024
717cd59
Remove IsSize(1st|2nd)8 because they weren't used much
tomeksowi Jun 21, 2024
64de193
Improve getter names in FpStructInRegistersInfo
tomeksowi Jun 21, 2024
d2d1841
Add comment to fix JIT to AssignClassifiedEightByteTypes
tomeksowi Jun 21, 2024
47f9d87
Add helpers for IntKind and flag names to FpStructInRegistersInfo
tomeksowi Jun 21, 2024
09c0b38
Merge branch 'main' into empty-struct-passing
tomeksowi Jun 21, 2024
8d6a102
Fix C# build: Enum values should be on separate lines
tomeksowi Jun 21, 2024
5ebd2af
Merge branch 'main' into empty-struct-passing
tomeksowi Jun 21, 2024
7036dc0
Make a logging wrapper Compiler::GetPassFpStructInRegistersInfo, simi…
tomeksowi Jun 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ namespace Internal.JitInterface
{
internal static class RISCV64PassStructInRegister
{
private const int
ENREGISTERED_PARAMTYPE_MAXSIZE = 16,
TARGET_POINTER_SIZE = 8;
private const int TARGET_POINTER_SIZE = 8;

private static bool HandleInlineArray(int elementTypeIndex, int nElements, Span<StructFloatFieldInfoFlags> types, ref int typeIndex)
{
Expand Down Expand Up @@ -90,9 +88,6 @@ private static bool FlattenFieldTypes(TypeDesc td, Span<StructFloatFieldInfoFlag

public static uint GetRISCV64PassStructInRegisterFlags(TypeDesc td)
{
if (td.GetElementSize().AsInt > ENREGISTERED_PARAMTYPE_MAXSIZE)
return (uint)STRUCT_NO_FLOAT_FIELD;

Span<StructFloatFieldInfoFlags> types = stackalloc StructFloatFieldInfoFlags[] {
STRUCT_NO_FLOAT_FIELD, STRUCT_NO_FLOAT_FIELD
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using ILCompiler;
using Internal.TypeSystem;
using System.Runtime.CompilerServices;
using static Internal.JitInterface.SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR;
using static Internal.JitInterface.SystemVClassificationType;

Expand Down Expand Up @@ -207,7 +208,10 @@ private static bool ClassifyEightBytes(TypeDesc typeDesc,

if (numIntroducedFields == 0)
{
return false;
// Classify empty struct like padding
helper.LargestFieldOffset = startOffsetOfStruct;
AssignClassifiedEightByteTypes(ref helper);
return true;
}

// The SIMD and Int128 Intrinsic types are meant to be handled specially and should not be passed as struct registers
Expand Down Expand Up @@ -375,8 +379,12 @@ private static void AssignClassifiedEightByteTypes(ref SystemVStructRegisterPass
// Calculate the eightbytes and their types.

int lastFieldOrdinal = sortedFieldOrder[largestFieldOffset];
int offsetAfterLastFieldByte = largestFieldOffset + helper.FieldSizes[lastFieldOrdinal];
SystemVClassificationType lastFieldClassification = helper.FieldClassifications[lastFieldOrdinal];
int lastFieldSize = (lastFieldOrdinal >= 0) ? helper.FieldSizes[lastFieldOrdinal] : 0;
int offsetAfterLastFieldByte = largestFieldOffset + lastFieldSize;
Debug.Assert(offsetAfterLastFieldByte <= helper.StructSize);
SystemVClassificationType lastFieldClassification = (lastFieldOrdinal >= 0)
? helper.FieldClassifications[lastFieldOrdinal]
: SystemVClassificationTypeNoClass;

int usedEightBytes = 0;
int accumulatedSizeForEightBytes = 0;
Expand All @@ -403,6 +411,8 @@ private static void AssignClassifiedEightByteTypes(ref SystemVStructRegisterPass
// 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
{
Expand Down Expand Up @@ -455,7 +465,8 @@ private static void AssignClassifiedEightByteTypes(ref SystemVStructRegisterPass
}
}

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) == helper.StructSize)
{
if (!foundFieldInEightByte)
{
Expand Down
34 changes: 23 additions & 11 deletions src/coreclr/vm/methodtable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2212,11 +2212,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
Expand Down Expand Up @@ -2432,7 +2435,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);
Expand Down Expand Up @@ -2684,8 +2692,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;
Expand All @@ -2712,6 +2724,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
{
Expand Down Expand Up @@ -2764,7 +2778,8 @@ 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)
{
Expand Down Expand Up @@ -2803,9 +2818,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
}
Expand Down Expand Up @@ -3567,9 +3582,6 @@ static bool FlattenFieldTypes(TypeHandle th, StructFloatFieldInfoFlags types[2],

int MethodTable::GetRiscV64PassStructInRegisterFlags(TypeHandle th)
{
if (th.GetSize() > ENREGISTERED_PARAMTYPE_MAXSIZE)
return STRUCT_NO_FLOAT_FIELD;

StructFloatFieldInfoFlags types[2] = {STRUCT_NO_FLOAT_FIELD, STRUCT_NO_FLOAT_FIELD};
int nFields = 0;
if (!FlattenFieldTypes(th, types, nFields) || nFields == 0)
Expand Down
5 changes: 3 additions & 2 deletions src/tests/JIT/Directed/StructABI/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ project (StructABILib)
include_directories(${INC_PLATFORM_DIR})

if(CLR_CMAKE_HOST_WIN32)
add_compile_options(/TC) # compile all files as C
set_source_files_properties(StructABI.c PROPERTIES COMPILE_OPTIONS /TC) # compile as C
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
set(CMAKE_CPP_FLAGS "${CMAKE_CPP_FLAGS} -fvisibility=hidden -Wno-return-type-c-linkage")
endif()

# add the executable
add_library (StructABILib SHARED StructABI.c)
add_library (StructABILib SHARED StructABI.c StructABI.cpp)

# add the install targets
install (TARGETS StructABILib DESTINATION bin)
159 changes: 159 additions & 0 deletions src/tests/JIT/Directed/StructABI/StructABI.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// 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 LongEmptyDouble
{
int64_t FieldL;
Empty FieldE;
double FieldD;
};

struct NestedEmpty
{
struct InnerEmpty
{
Empty e;
} e;
};
static_assert(sizeof(NestedEmpty) == 1, "Nested empty struct must be sized like in .NET");

struct NestedEmptyFloatDouble
{
NestedEmpty FieldNE;
float FieldF;
double FieldD;
};

struct EmptyIntAndFloat
{
struct EmptyInt
{
Empty FieldE;
int32_t FieldI;
};
EmptyInt FieldEI;
float FieldF;
};

struct LongEmptyAndFloat
{
struct LongEmpty
{
int64_t FieldL;
Empty FieldE;
};
LongEmpty FieldLE;
float FieldF;
};

struct ArrayOfEmpties
{
Empty e[1];
};

struct ArrayOfEmptiesFloatDouble
{
ArrayOfEmpties FieldAoE;
float FieldF;
double FieldD;
};

template<typename T>
struct Eight
{
T e1, e2, e3, e4, e5, e6, e7, e8;
};

struct FloatEmpty32kInt
{
float FieldF;
Eight<Eight<Eight<Eight<Eight<Empty>>>>> FieldEmpty32k;
int32_t FieldI;
};

#pragma pack(push, 1)
struct PackedEmptyFloatLong
{
Empty FieldE;
float FieldF;
int64_t FieldL;
};
#pragma pack(pop)

struct ExplicitFloatLong
{
PackedEmptyFloatLong s;
};
static_assert(offsetof(ExplicitFloatLong, s.FieldE) == 0, "");
static_assert(offsetof(ExplicitFloatLong, s.FieldF) == 1, "");
static_assert(offsetof(ExplicitFloatLong, s.FieldL) == 5, "");

extern "C"
{

DLLEXPORT LongEmptyDouble EchoLongEmptyDoubleRiscV(LongEmptyDouble a0_fa0)
{
return a0_fa0;
}

DLLEXPORT LongEmptyDouble EchoLongEmptyDoubleByImplicitRefRiscV(
float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, LongEmptyDouble byRef)
{
return byRef;
}

DLLEXPORT NestedEmptyFloatDouble EchoNestedEmptyFloatDoubleRiscV(NestedEmptyFloatDouble fa0_fa1)
{
return fa0_fa1;
}

DLLEXPORT NestedEmptyFloatDouble EchoNestedEmptyFloatDoubleInIntegerRegsRiscV(
float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, NestedEmptyFloatDouble a0_a1)
{
return a0_a1;
}

DLLEXPORT EmptyIntAndFloat EchoEmptyIntAndFloatRiscV(EmptyIntAndFloat a0_fa0)
{
return a0_fa0;
}

DLLEXPORT LongEmptyAndFloat EchoLongEmptyAndFloatRiscV(LongEmptyAndFloat a0_fa0)
{
return a0_fa0;
}

DLLEXPORT ArrayOfEmptiesFloatDouble EchoArrayOfEmptiesFloatDoubleInIntegerRegsRiscV(ArrayOfEmptiesFloatDouble a0_a1)
{
return a0_a1;
}

DLLEXPORT FloatEmpty32kInt EchoFloatEmpty32kIntRiscV(FloatEmpty32kInt fa0_a0)
{
return fa0_a0;
}

DLLEXPORT PackedEmptyFloatLong EchoPackedEmptyFloatLongRiscV(PackedEmptyFloatLong fa0_a0)
{
return fa0_a0;
}

DLLEXPORT ExplicitFloatLong EchoExplicitFloatLongRiscV(ExplicitFloatLong fa0_a0)
{
return fa0_a0;
}

} // extern "C"
Loading
Loading