diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 89c61ebeac690e..4142cd85cc4802 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -356,6 +356,117 @@ enum StructFloatFieldInfoFlags STRUCT_HAS_8BYTES_FIELDS_MASK = (STRUCT_FIRST_FIELD_SIZE_IS8 | STRUCT_SECOND_FIELD_SIZE_IS8), }; +// Bitfields for FpStructInRegistersInfo::flags +namespace FpStruct +{ + enum class IntKind + { + Signed, + Unsigned, + GcRef, + GcByRef, + }; + + enum Flags + { + // Positions of flags and bitfields + PosOnlyOne = 0, + PosBothFloat = 1, + PosFloatInt = 2, + PosIntFloat = 3, + PosSizeShift1st = 4, // 2 bits + PosSizeShift2nd = 6, // 2 bits + PosIntFieldKind = 8, // 2 bits + + UseIntCallConv = 0, // struct is passed according to integer calling convention + + // The flags and bitfields + OnlyOne = 1 << PosOnlyOne, // has only one field, which is floating-point + BothFloat = 1 << PosBothFloat, // has two fields, both are floating-point + FloatInt = 1 << PosFloatInt, // has two fields, 1st is floating and 2nd is integer + IntFloat = 1 << PosIntFloat, // has two fields, 2nd is floating and 1st is integer + SizeShift1stMask = 0b11 << PosSizeShift1st, // log2(size) of 1st field + SizeShift2ndMask = 0b11 << PosSizeShift2nd, // log2(size) of 2nd field + IntFieldKindMask = 0b11 << PosIntFieldKind, // the kind of the integer field (FpStruct::IntKind) + // Note: flags OnlyOne, BothFloat, FloatInt, and IntFloat are mutually exclusive + }; +} + +// On RISC-V and LoongArch a struct with up to two non-empty fields, at least one of them floating-point, +// can be passed in registers according to hardware FP calling convention. FpStructInRegistersInfo represents +// passing information for such parameters. +struct FpStructInRegistersInfo +{ + FpStruct::Flags flags; + uint32_t offset1st; + uint32_t offset2nd; + + unsigned SizeShift1st() const { return (flags >> FpStruct::PosSizeShift1st) & 0b11; } + unsigned SizeShift2nd() const { return (flags >> FpStruct::PosSizeShift2nd) & 0b11; } + + unsigned Size1st() const { return 1u << SizeShift1st(); } + unsigned Size2nd() const { return 1u << SizeShift2nd(); } + + FpStruct::IntKind IntFieldKind() const + { + return (FpStruct::IntKind)((flags >> FpStruct::PosIntFieldKind) & 0b11); + } + + const char* IntFieldKindName() const + { + static const char* intKindNames[] = { "Signed", "Unsigned", "GcRef", "GcByRef" }; + return (flags & (FpStruct::FloatInt | FpStruct::IntFloat)) + ? intKindNames[(int)IntFieldKind()] + : "None"; + } + + const char* FlagName() const + { + switch (flags & (FpStruct::OnlyOne | FpStruct::BothFloat | FpStruct::FloatInt | FpStruct::IntFloat)) + { + case FpStruct::OnlyOne: return "OnlyOne"; + case FpStruct::BothFloat: return "BothFloat"; + case FpStruct::FloatInt: return "FloatInt"; + case FpStruct::IntFloat: return "IntFloat"; + default: return "?"; + } + } + + StructFloatFieldInfoFlags ToOldFlags() const + { + return StructFloatFieldInfoFlags( + ((flags & FpStruct::OnlyOne) ? STRUCT_FLOAT_FIELD_ONLY_ONE : 0) | + ((flags & FpStruct::BothFloat) ? STRUCT_FLOAT_FIELD_ONLY_TWO : 0) | + ((flags & FpStruct::FloatInt) ? STRUCT_FLOAT_FIELD_FIRST : 0) | + ((flags & FpStruct::IntFloat) ? STRUCT_FLOAT_FIELD_SECOND : 0) | + ((SizeShift1st() == 3) ? STRUCT_FIRST_FIELD_SIZE_IS8 : 0) | + ((SizeShift2nd() == 3) ? STRUCT_SECOND_FIELD_SIZE_IS8 : 0)); + } + + static FpStructInRegistersInfo FromOldFlags(StructFloatFieldInfoFlags flags) + { + unsigned sizeShift1st = (flags & STRUCT_FIRST_FIELD_SIZE_IS8) ? 3 : 2; + unsigned sizeShift2nd = (flags & STRUCT_SECOND_FIELD_SIZE_IS8) ? 3 : 2; + bool hasTwo = !(flags & STRUCT_FLOAT_FIELD_ONLY_ONE); + return { + FpStruct::Flags( + ((flags & STRUCT_FLOAT_FIELD_ONLY_ONE) ? FpStruct::OnlyOne : 0) | + ((flags & STRUCT_FLOAT_FIELD_ONLY_TWO) ? FpStruct::BothFloat : 0) | + ((flags & STRUCT_FLOAT_FIELD_FIRST) ? FpStruct::FloatInt : 0) | + ((flags & STRUCT_FLOAT_FIELD_SECOND) ? FpStruct::IntFloat : 0) | + (sizeShift1st << FpStruct::PosSizeShift1st) | + (hasTwo ? (sizeShift2nd << FpStruct::PosSizeShift2nd) : 0) + // No GC ref info in old flags + ), + // Lacking actual field offsets, assume fields are naturally aligned without empty fields or padding + 0, + hasTwo ? (1u << (sizeShift1st > sizeShift2nd ? sizeShift1st : sizeShift2nd)) : 0, + }; + } +}; + +static_assert(sizeof(FpStructInRegistersInfo) == 3 * sizeof(uint32_t), ""); + #include "corinfoinstructionset.h" // CorInfoHelpFunc defines the set of helpers (accessed via the ICorDynamicInfo::getHelperFtn()) @@ -3075,7 +3186,7 @@ class ICorStaticInfo virtual void getSwiftLowering(CORINFO_CLASS_HANDLE structHnd, CORINFO_SWIFT_LOWERING* pLowering) = 0; virtual uint32_t getLoongArch64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE cls) = 0; - virtual uint32_t getRISCV64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE cls) = 0; + virtual FpStructInRegistersInfo getRiscV64PassFpStructInRegistersInfo(CORINFO_CLASS_HANDLE cls) = 0; }; /***************************************************************************** diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index 38f9dac0e2e4d1..3f3e1a750c38c8 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -520,7 +520,7 @@ void getSwiftLowering( uint32_t getLoongArch64PassStructInRegisterFlags( CORINFO_CLASS_HANDLE structHnd) override; -uint32_t getRISCV64PassStructInRegisterFlags( +FpStructInRegistersInfo getRiscV64PassFpStructInRegistersInfo( CORINFO_CLASS_HANDLE structHnd) override; uint32_t getThreadTLSIndex( diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 76a1bbc466d790..9830b3a1818782 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -43,11 +43,11 @@ typedef const GUID *LPCGUID; #define GUID_DEFINED #endif // !GUID_DEFINED -constexpr GUID JITEEVersionIdentifier = { /* e428e66d-5e0e-4320-ad8a-fa5a50f6da07 */ - 0xe428e66d, - 0x5e0e, - 0x4320, - {0xad, 0x8a, 0xfa, 0x5a, 0x50, 0xf6, 0xda, 0x07} +constexpr GUID JITEEVersionIdentifier = { /* 1907f831-d9c8-4b42-a549-0cdce990d1c6 */ + 0x1907f831, + 0xd9c8, + 0x4b42, + {0xa5, 0x49, 0x0c, 0xdc, 0xe9, 0x90, 0xd1, 0xc6} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/coreclr/jit/ICorJitInfo_names_generated.h b/src/coreclr/jit/ICorJitInfo_names_generated.h index a2bfe1de9c80cc..f027a85c22ba35 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -130,7 +130,7 @@ DEF_CLR_API(getMethodHash) DEF_CLR_API(getSystemVAmd64PassStructInRegisterDescriptor) DEF_CLR_API(getSwiftLowering) DEF_CLR_API(getLoongArch64PassStructInRegisterFlags) -DEF_CLR_API(getRISCV64PassStructInRegisterFlags) +DEF_CLR_API(getRiscV64PassFpStructInRegistersInfo) DEF_CLR_API(getThreadTLSIndex) DEF_CLR_API(getAddrOfCaptureThreadGlobal) DEF_CLR_API(getHelperFtn) diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index ff54b778f2a2cd..19e075c7813378 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -1238,12 +1238,12 @@ uint32_t WrapICorJitInfo::getLoongArch64PassStructInRegisterFlags( return temp; } -uint32_t WrapICorJitInfo::getRISCV64PassStructInRegisterFlags( +FpStructInRegistersInfo WrapICorJitInfo::getRiscV64PassFpStructInRegistersInfo( CORINFO_CLASS_HANDLE structHnd) { - API_ENTER(getRISCV64PassStructInRegisterFlags); - uint32_t temp = wrapHnd->getRISCV64PassStructInRegisterFlags(structHnd); - API_LEAVE(getRISCV64PassStructInRegisterFlags); + API_ENTER(getRiscV64PassFpStructInRegistersInfo); + FpStructInRegistersInfo temp = wrapHnd->getRiscV64PassFpStructInRegistersInfo(structHnd); + API_LEAVE(getRiscV64PassFpStructInRegistersInfo); return temp; } diff --git a/src/coreclr/jit/buildstring.cpp b/src/coreclr/jit/buildstring.cpp index 3f0222ad2649ac..f51e262c517ef8 100644 --- a/src/coreclr/jit/buildstring.cpp +++ b/src/coreclr/jit/buildstring.cpp @@ -1,17 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#define STRINGIFY(L) #L -#define MAKESTRING(M, L) M(L) -#define STRINGIZE(X) MAKESTRING(STRINGIFY, X) - #if defined(__clang__) #define BUILD_COMPILER \ - "Clang " STRINGIZE(__clang_major__) "." STRINGIZE(__clang_minor__) "." STRINGIZE(__clang_patchlevel__) + "Clang " STRINGIFY(__clang_major__) "." STRINGIFY(__clang_minor__) "." STRINGIFY(__clang_patchlevel__) #elif defined(_MSC_VER) -#define BUILD_COMPILER "MSVC " STRINGIZE(_MSC_FULL_VER) +#define BUILD_COMPILER "MSVC " STRINGIFY(_MSC_FULL_VER) #elif defined(__GNUC__) -#define BUILD_COMPILER "GCC " STRINGIZE(__GNUC__) "." STRINGIZE(__GNUC_MINOR__) "." STRINGIZE(__GNUC_PATCHLEVEL__) +#define BUILD_COMPILER "GCC " STRINGIFY(__GNUC__) "." STRINGIFY(__GNUC_MINOR__) "." STRINGIFY(__GNUC_PATCHLEVEL__) #else #define BUILD_COMPILER "Unknown" #endif @@ -26,6 +22,8 @@ #define TARGET_ARCH_STRING "arm64" #elif defined(TARGET_LOONGARCH64) #define TARGET_ARCH_STRING "loongarch64" +#elif defined(TARGET_RISCV64) +#define TARGET_ARCH_STRING "riscv64" #else #define TARGET_ARCH_STRING "Unknown" #endif diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 240a7d97276421..f997747b989c08 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -3170,6 +3170,13 @@ var_types CodeGen::genParamStackType(LclVarDsc* dsc, const ABIPassingSegment& se // can always use the full register size here. This allows us to // use stp more often. return TYP_I_IMPL; +#elif defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + // On RISC-V/LoongArch structs passed according to floating-point calling convention are enregistered one + // field per register regardless of the layout of the fields in memory, so the small int load/store + // instructions must not be upsized to 4 bytes, otherwise for example: + // * struct { struct{} e1,e2,e3; byte b; float f; } -- 4-byte store for 'b' would trash 'f' + // * struct { float f; struct{} e1,e2,e3; byte b; } -- 4-byte store for 'b' would trash adjacent stack slot + return seg.GetRegisterType(); #else return genActualType(seg.GetRegisterType()); #endif @@ -7360,16 +7367,17 @@ void CodeGen::genStructReturn(GenTree* treeNode) #if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) // On LoongArch64, for a struct like "{ int, double }", "retTypeDesc" will be "{ TYP_INT, TYP_DOUBLE }", // i. e. not include the padding for the first field, and so the general loop below won't work. - var_types type = retTypeDesc.GetReturnRegType(0); - regNumber toReg = retTypeDesc.GetABIReturnReg(0, compiler->info.compCallConv); - GetEmitter()->emitIns_R_S(ins_Load(type), emitTypeSize(type), toReg, lclNode->GetLclNum(), 0); + var_types type = retTypeDesc.GetReturnRegType(0); + regNumber toReg = retTypeDesc.GetABIReturnReg(0, compiler->info.compCallConv); + unsigned offset = retTypeDesc.GetReturnFieldOffset(0); + GetEmitter()->emitIns_R_S(ins_Load(type), emitTypeSize(type), toReg, lclNode->GetLclNum(), offset); if (regCount > 1) { assert(regCount == 2); - int offset = genTypeSize(type); - type = retTypeDesc.GetReturnRegType(1); - offset = (int)((unsigned int)offset < genTypeSize(type) ? genTypeSize(type) : offset); - toReg = retTypeDesc.GetABIReturnReg(1, compiler->info.compCallConv); + assert(offset + genTypeSize(type) <= retTypeDesc.GetReturnFieldOffset(1)); + type = retTypeDesc.GetReturnRegType(1); + toReg = retTypeDesc.GetABIReturnReg(1, compiler->info.compCallConv); + offset = retTypeDesc.GetReturnFieldOffset(1); GetEmitter()->emitIns_R_S(ins_Load(type), emitTypeSize(type), toReg, lclNode->GetLclNum(), offset); } #else // !TARGET_LOONGARCH64 && !TARGET_RISCV64 @@ -7644,6 +7652,11 @@ void CodeGen::genMultiRegStoreToLocal(GenTreeLclVar* lclNode) assert(regCount == varDsc->lvFieldCnt); } +#if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) + // genMultiRegStoreToLocal is only used for calls on RISC-V and LoongArch + const ReturnTypeDesc* returnTypeDesc = actualOp1->AsCall()->GetReturnTypeDesc(); +#endif + #ifdef SWIFT_SUPPORT const uint32_t* offsets = nullptr; if (actualOp1->IsCall() && (actualOp1->AsCall()->GetUnmanagedCallConv() == CorInfoCallConvExtension::Swift)) @@ -7694,8 +7707,8 @@ void CodeGen::genMultiRegStoreToLocal(GenTreeLclVar* lclNode) else { #if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - // should consider the padding field within a struct. - offset = (offset % genTypeSize(srcType)) ? AlignUp(offset, genTypeSize(srcType)) : offset; + // Should consider the padding, empty struct fields, etc within a struct. + offset = returnTypeDesc->GetReturnFieldOffset(i); #endif #ifdef SWIFT_SUPPORT if (offsets != nullptr) diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 9b035278d66d53..576debc9e841c9 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -743,7 +743,7 @@ var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, // Otherwise we pass this struct by value on the stack // setup wbPassType and useType indicate that this is passed by value according to the X86/ARM32 ABI - // On LOONGARCH64 struct that is 1-16 bytes is passed by value in one/two register(s) + // On LOONGARCH64 and RISCV64 struct that is 1-16 bytes is returned by value in one/two register(s) howToPassStruct = SPK_ByValue; useType = TYP_STRUCT; @@ -757,7 +757,6 @@ var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, else // (structSize > MAX_PASS_MULTIREG_BYTES) { // We have a (large) struct that can't be replaced with a "primitive" type - // and can't be passed in multiple registers #if defined(TARGET_X86) || defined(TARGET_ARM) || defined(UNIX_AMD64_ABI) @@ -767,11 +766,23 @@ var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, useType = TYP_STRUCT; #elif defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - - // Otherwise we pass this struct by reference to a copy - // setup wbPassType and useType indicate that this is passed using one register (by reference to a copy) - howToPassStruct = SPK_ByReference; - useType = TYP_UNKNOWN; +#ifdef TARGET_RISCV64 + // Struct larger than 16 can still be passed in registers according to FP call conv if it has empty fields + // or more padding + FpStructInRegistersInfo info = GetPassFpStructInRegistersInfo(clsHnd); + if (info.flags != FpStruct::UseIntCallConv) + { + howToPassStruct = SPK_ByValue; + useType = TYP_STRUCT; + } + else +#endif + { + // Otherwise we pass this struct by reference to a copy + // setup wbPassType and useType indicate that this is passed using one register (by reference to a copy) + howToPassStruct = SPK_ByReference; + useType = TYP_UNKNOWN; + } #else // TARGET_XXX @@ -945,20 +956,17 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, } #elif defined(TARGET_RISCV64) - if (structSize <= (TARGET_POINTER_SIZE * 2)) + FpStructInRegistersInfo info = GetPassFpStructInRegistersInfo(clsHnd); + if ((info.flags & FpStruct::OnlyOne) != 0) { - uint32_t floatFieldFlags = info.compCompHnd->getRISCV64PassStructInRegisterFlags(clsHnd); - - if ((floatFieldFlags & STRUCT_FLOAT_FIELD_ONLY_ONE) != 0) - { - howToReturnStruct = SPK_PrimitiveType; - useType = (structSize > 4) ? TYP_DOUBLE : TYP_FLOAT; - } - else if (floatFieldFlags & (STRUCT_HAS_FLOAT_FIELDS_MASK ^ STRUCT_FLOAT_FIELD_ONLY_ONE)) - { - howToReturnStruct = SPK_ByValue; - useType = TYP_STRUCT; - } + howToReturnStruct = SPK_PrimitiveType; + useType = (info.SizeShift1st() == 3) ? TYP_DOUBLE : TYP_FLOAT; + } + else if (info.flags != FpStruct::UseIntCallConv) + { + assert((info.flags & (FpStruct::BothFloat | FpStruct::FloatInt | FpStruct::IntFloat)) != 0); + howToReturnStruct = SPK_ByValue; + useType = TYP_STRUCT; } #endif @@ -1105,7 +1113,8 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, #elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - // On LOONGARCH64/RISCV64 struct that is 1-16 bytes is returned by value in one/two register(s) + // On LOONGARCH64 (always) and RISCV64 (integer calling convention) struct that is 1-16 bytes is + // returned by value in one/two register(s) howToReturnStruct = SPK_ByValue; useType = TYP_STRUCT; @@ -8300,6 +8309,105 @@ void Compiler::GetStructTypeOffset( GetStructTypeOffset(structDesc, type0, type1, offset0, offset1); } +#elif defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) +//------------------------------------------------------------------------ +// GetPassFpStructInRegistersInfo: Gets the information on passing of a struct according to hardware floating-point +// calling convention. +// +// Arguments: +// structHandle - type handle +// +// Return value: +// The passing info +FpStructInRegistersInfo Compiler::GetPassFpStructInRegistersInfo(CORINFO_CLASS_HANDLE structHandle) +{ +#ifdef TARGET_RISCV64 +#define getInfoFunc getRiscV64PassFpStructInRegistersInfo +#else +#define getInfoFunc getLoongArchPassFpStructInRegistersInfo +#endif + + FpStructInRegistersInfo ret = info.compCompHnd->getInfoFunc(structHandle); +#ifdef DEBUG + if (VERBOSE) + { + logf("**** " STRINGIFY(getInfoFunc) "(0x%x (%s, %u bytes)) =>\n", dspPtr(structHandle), + eeGetClassName(structHandle), info.compCompHnd->getClassSize(structHandle)); +#undef getInfoFunc + if (ret.flags == FpStruct::UseIntCallConv) + { + logf(" pass by integer calling convention\n"); + } + else + { + bool hasOne = ((ret.flags & FpStruct::OnlyOne) != 0); + long size2nd = hasOne ? -1l : ret.Size2nd(); + long offset2nd = hasOne ? -1l : ret.offset2nd; + logf(" may be passed by floating-point calling convention:\n" + " flags=%#03x; %s, field sizes={%u, %li}, field offsets={%u, %li}, IntFieldKind=%s\n", + ret.flags, ret.FlagName(), ret.Size1st(), size2nd, ret.offset1st, offset2nd, ret.IntFieldKindName()); + } + } +#endif // DEBUG + return ret; +} + +//------------------------------------------------------------------------ +// GetTypesFromFpStructInRegistersInfo: Gets the field types of a struct passed in registers according to hardware +// floating-point calling convention. +// +// Arguments: +// info - the structure contaning information about how the struct is passed +// type1st - out param; type of the first field +// type2nd - out param; type of the second field (written to iff the struct has two fields) +// +// Return value: +// None +// +// Notes: +// If the struct should be passed according to integer calling convention, none of the output pararmeters are +// written to. +// +void Compiler::GetTypesFromFpStructInRegistersInfo(FpStructInRegistersInfo info, var_types* type1st, var_types* type2nd) +{ + if ((info.flags & (FpStruct::BothFloat | FpStruct::FloatInt | FpStruct::OnlyOne)) != 0) + *type1st = (info.SizeShift1st() == 3) ? TYP_DOUBLE : TYP_FLOAT; + + if ((info.flags & (FpStruct::BothFloat | FpStruct::IntFloat)) != 0) + *type2nd = (info.SizeShift2nd() == 3) ? TYP_DOUBLE : TYP_FLOAT; + + if ((info.flags & (FpStruct::FloatInt | FpStruct::IntFloat)) != 0) + { + bool isInt1st = ((info.flags & FpStruct::IntFloat) != 0); + FpStruct::IntKind kind = info.IntFieldKind(); + var_types* intType = isInt1st ? type1st : type2nd; + if (kind < FpStruct::IntKind::GcRef) + { + assert(kind == FpStruct::IntKind::Signed || kind == FpStruct::IntKind::Unsigned); + struct IntType + { + // Ignore signedness because Compiler::fgDebugCheckTypes doesn't allow unsigned types + static constexpr var_types Get(unsigned sizeShift) + { + return (var_types)(TYP_BYTE + (sizeShift * 2)); + } + }; + static_assert(IntType::Get(0) == TYP_BYTE, ""); + static_assert(IntType::Get(1) == TYP_SHORT, ""); + static_assert(IntType::Get(2) == TYP_INT, ""); + static_assert(IntType::Get(3) == TYP_LONG, ""); + + unsigned sizeShift = isInt1st ? info.SizeShift1st() : info.SizeShift2nd(); + *intType = IntType::Get(sizeShift); + } + else + { + assert(kind == FpStruct::IntKind::GcRef || kind == FpStruct::IntKind::GcByRef); + *intType = (kind == FpStruct::IntKind::GcRef) ? TYP_REF : TYP_BYREF; + } + } +} + #endif // defined(UNIX_AMD64_ABI) /*****************************************************************************/ diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 6136c97d5d47b8..05fb9b6e566776 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -554,17 +554,9 @@ class LclVarDsc unsigned char lvIsLastUseCopyOmissionCandidate : 1; #endif // FEATURE_IMPLICIT_BYREFS -#if defined(TARGET_LOONGARCH64) - unsigned char lvIs4Field1 : 1; // Set if the 1st field is int or float within struct for LA-ABI64. - unsigned char lvIs4Field2 : 1; // Set if the 2nd field is int or float within struct for LA-ABI64. - unsigned char lvIsSplit : 1; // Set if the argument is splited. -#endif // defined(TARGET_LOONGARCH64) - -#if defined(TARGET_RISCV64) - unsigned char lvIs4Field1 : 1; // Set if the 1st field is int or float within struct for RISCV64. - unsigned char lvIs4Field2 : 1; // Set if the 2nd field is int or float within struct for RISCV64. - unsigned char lvIsSplit : 1; // Set if the argument is splited. -#endif // defined(TARGET_RISCV64) +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + unsigned char lvIsSplit : 1; // Set if the argument is split across last integer register and stack. +#endif // defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) unsigned char lvSingleDef : 1; // variable has a single def. Used to identify ref type locals that can get type // updates @@ -8489,9 +8481,10 @@ class Compiler { return eeRunWithSPMIErrorTrap( [](Functor* pf) { - (*pf)(); + (*pf)(); }, &f); + } bool eeRunWithSPMIErrorTrapImp(void (*function)(void*), void* param); @@ -11377,6 +11370,12 @@ class Compiler void GetStructTypeOffset( CORINFO_CLASS_HANDLE typeHnd, var_types* type0, var_types* type1, uint8_t* offset0, uint8_t* offset1); +#elif defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + FpStructInRegistersInfo GetPassFpStructInRegistersInfo(CORINFO_CLASS_HANDLE structHandle); + + static void GetTypesFromFpStructInRegistersInfo(FpStructInRegistersInfo info, + var_types* type1st, + var_types* type2nd); #endif // defined(UNIX_AMD64_ABI) void fgMorphMultiregStructArgs(GenTreeCall* call); diff --git a/src/coreclr/jit/ee_il_dll.cpp b/src/coreclr/jit/ee_il_dll.cpp index 2f86c8b5274536..f8831f39af3c97 100644 --- a/src/coreclr/jit/ee_il_dll.cpp +++ b/src/coreclr/jit/ee_il_dll.cpp @@ -425,11 +425,19 @@ unsigned Compiler::eeGetArgSize(CorInfoType corInfoType, CORINFO_CLASS_HANDLE ty } } #elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - // Any structs that are larger than MAX_PASS_MULTIREG_BYTES are always passed by reference + // Any structs that are larger than MAX_PASS_MULTIREG_BYTES are always passed by reference... if (structSize > MAX_PASS_MULTIREG_BYTES) { - // This struct is passed by reference using a single 'slot' - return TARGET_POINTER_SIZE; +#ifdef TARGET_RISCV64 + // ... unless on RISC-V they are still eligible for passing according to hardware floating-point calling + // convention, e.g. struct {long; struct{}; double; }. + FpStructInRegistersInfo fpInfo = GetPassFpStructInRegistersInfo(typeHnd); + if (fpInfo.flags == FpStruct::UseIntCallConv) +#endif + { + // This struct is passed by reference using a single 'slot' + return TARGET_POINTER_SIZE; + } } // otherwise will we pass this struct by value in multiple registers #elif !defined(TARGET_ARM) @@ -1375,7 +1383,7 @@ void Compiler::eeGetSystemVAmd64PassStructInRegisterDescriptor( printf(" eightByte #%d -- classification: ", i); dumpSystemVClassificationType(structPassInRegDescPtr->eightByteClassifications[i]); printf(", byteSize: %d, byteOffset: %d\n", structPassInRegDescPtr->eightByteSizes[i], - structPassInRegDescPtr->eightByteOffsets[i]); + structPassInRegDescPtr->eightByteOffsetn[i]); } } } diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 3f2ace8415866c..1cba1343d2e655 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -28623,6 +28623,15 @@ void ReturnTypeDesc::InitializeStructReturnType(Compiler* comp, assert(returnType != TYP_UNKNOWN); assert(returnType != TYP_STRUCT); m_regType[0] = returnType; + +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + FpStructInRegistersInfo fpInfo = comp->GetPassFpStructInRegistersInfo(retClsHnd); + if (fpInfo.flags != FpStruct::UseIntCallConv) + { + assert((fpInfo.flags & FpStruct::OnlyOne) != 0); + m_fieldOffset[0] = fpInfo.offset1st; + } +#endif // defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) break; } @@ -28688,45 +28697,29 @@ void ReturnTypeDesc::InitializeStructReturnType(Compiler* comp, } #elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - assert((structSize >= TARGET_POINTER_SIZE) && (structSize <= (2 * TARGET_POINTER_SIZE))); - -#ifdef TARGET_LOONGARCH64 - uint32_t floatFieldFlags = comp->info.compCompHnd->getLoongArch64PassStructInRegisterFlags(retClsHnd); -#else - uint32_t floatFieldFlags = comp->info.compCompHnd->getRISCV64PassStructInRegisterFlags(retClsHnd); -#endif - BYTE gcPtrs[2] = {TYPE_GC_NONE, TYPE_GC_NONE}; - comp->info.compCompHnd->getClassGClayout(retClsHnd, &gcPtrs[0]); - - if (floatFieldFlags & STRUCT_FLOAT_FIELD_ONLY_TWO) - { - comp->compFloatingPointUsed = true; - assert((structSize > 8) == ((floatFieldFlags & STRUCT_HAS_8BYTES_FIELDS_MASK) > 0)); - m_regType[0] = (floatFieldFlags & STRUCT_FIRST_FIELD_SIZE_IS8) ? TYP_DOUBLE : TYP_FLOAT; - m_regType[1] = (floatFieldFlags & STRUCT_SECOND_FIELD_SIZE_IS8) ? TYP_DOUBLE : TYP_FLOAT; - } - else if (floatFieldFlags & STRUCT_FLOAT_FIELD_FIRST) + assert(structSize >= TARGET_POINTER_SIZE); + LOONGARCH64_ONLY(assert(structSize <= (2 * TARGET_POINTER_SIZE));) + FpStructInRegistersInfo fpInfo = comp->GetPassFpStructInRegistersInfo(retClsHnd); + if (fpInfo.flags != FpStruct::UseIntCallConv) { comp->compFloatingPointUsed = true; - assert((structSize > 8) == ((floatFieldFlags & STRUCT_HAS_8BYTES_FIELDS_MASK) > 0)); - m_regType[0] = (floatFieldFlags & STRUCT_FIRST_FIELD_SIZE_IS8) ? TYP_DOUBLE : TYP_FLOAT; - m_regType[1] = - (floatFieldFlags & STRUCT_SECOND_FIELD_SIZE_IS8) ? comp->getJitGCType(gcPtrs[1]) : TYP_INT; - } - else if (floatFieldFlags & STRUCT_FLOAT_FIELD_SECOND) - { - comp->compFloatingPointUsed = true; - assert((structSize > 8) == ((floatFieldFlags & STRUCT_HAS_8BYTES_FIELDS_MASK) > 0)); - m_regType[0] = - (floatFieldFlags & STRUCT_FIRST_FIELD_SIZE_IS8) ? comp->getJitGCType(gcPtrs[0]) : TYP_INT; - m_regType[1] = (floatFieldFlags & STRUCT_SECOND_FIELD_SIZE_IS8) ? TYP_DOUBLE : TYP_FLOAT; + + assert((fpInfo.flags & FpStruct::OnlyOne) == 0); + m_fieldOffset[0] = fpInfo.offset1st; + m_fieldOffset[1] = fpInfo.offset2nd; + Compiler::GetTypesFromFpStructInRegistersInfo(fpInfo, &m_regType[0], &m_regType[1]); } else { + assert(structSize <= (2 * TARGET_POINTER_SIZE)); + BYTE gcPtrs[2] = {TYPE_GC_NONE, TYPE_GC_NONE}; + comp->info.compCompHnd->getClassGClayout(retClsHnd, &gcPtrs[0]); for (unsigned i = 0; i < 2; ++i) { m_regType[i] = comp->getJitGCType(gcPtrs[i]); } + m_fieldOffset[0] = 0; + m_fieldOffset[1] = TARGET_POINTER_SIZE; } #elif defined(TARGET_X86) diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 4092f40e7d2a69..72d2b0db10f8be 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -4261,6 +4261,13 @@ struct ReturnTypeDesc private: var_types m_regType[MAX_RET_REG_COUNT]; +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + // Structs according to hardware floating-point calling convention are passed as two logical fields, each in + // separate register, disregarding struct layout such as packing, custom alignment, padding with empty structs, etc. + // We need size (can be derived from m_regType) & offset of each field for memory load/stores + unsigned m_fieldOffset[MAX_RET_REG_COUNT]; +#endif + #ifdef DEBUG bool m_inited; #endif @@ -4292,6 +4299,9 @@ struct ReturnTypeDesc for (unsigned i = 0; i < MAX_RET_REG_COUNT; ++i) { m_regType[i] = TYP_UNKNOWN; +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + m_fieldOffset[i] = 0; +#endif } #ifdef DEBUG m_inited = false; @@ -4337,8 +4347,11 @@ struct ReturnTypeDesc for (unsigned i = regCount + 1; i < MAX_RET_REG_COUNT; ++i) { assert(m_regType[i] == TYP_UNKNOWN); - } +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + assert(m_fieldOffset[i] == 0); #endif + } +#endif // DEBUG return regCount; } @@ -4387,6 +4400,25 @@ struct ReturnTypeDesc return result; } + unsigned GetSingleReturnFieldOffset() const + { + assert(!IsMultiRegRetType()); + assert(m_regType[0] != TYP_UNKNOWN); +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + return m_fieldOffset[0]; +#else + return 0; +#endif + } + +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + unsigned GetReturnFieldOffset(unsigned index) const + { + assert(m_regType[index] != TYP_UNKNOWN); + return m_fieldOffset[index]; + } +#endif + // Get i'th ABI return register regNumber GetABIReturnReg(unsigned idx, CorInfoCallConvExtension callConv) const; @@ -4496,6 +4528,7 @@ struct CallArgABIInformation , ByteSize(0) #if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) , StructFloatFieldType() + , StructFloatFieldOffset() #endif , ArgType(TYP_UNDEF) , PassedByRef(false) @@ -4530,10 +4563,11 @@ struct CallArgABIInformation SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR StructDesc; #endif // UNIX_AMD64_ABI #if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - // For LoongArch64's ABI, the struct which has float field(s) and no more than two fields + // For LoongArch64's and RISC-V 64's ABI, the struct which has float field(s) and no more than two fields // may be passed by float register(s). // e.g `struct {int a; float b;}` passed by an integer register and a float register. var_types StructFloatFieldType[2]; + unsigned StructFloatFieldOffset[2]; #endif // The type used to pass this argument. This is generally the original // argument type, but when a struct is passed as a scalar type, this is diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 84d4de9046e6b7..57260631c705a4 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -898,79 +898,59 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un } else #elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - uint32_t floatFlags = STRUCT_NO_FLOAT_FIELD; + FpStructInRegistersInfo fpInfo = {}; + var_types argRegTypeInStruct1 = TYP_UNKNOWN; var_types argRegTypeInStruct2 = TYP_UNKNOWN; - if ((strip(corInfoType) == CORINFO_TYPE_VALUECLASS) && (argSize <= MAX_PASS_MULTIREG_BYTES)) + if ((strip(corInfoType) == CORINFO_TYPE_VALUECLASS) LOONGARCH64_ONLY(&&(argSize <= MAX_PASS_MULTIREG_BYTES))) { -#if defined(TARGET_LOONGARCH64) - floatFlags = info.compCompHnd->getLoongArch64PassStructInRegisterFlags(typeHnd); -#else - floatFlags = info.compCompHnd->getRISCV64PassStructInRegisterFlags(typeHnd); -#endif + fpInfo = GetPassFpStructInRegistersInfo(typeHnd); } - if ((floatFlags & STRUCT_HAS_FLOAT_FIELDS_MASK) != 0) + if (fpInfo.flags != FpStruct::UseIntCallConv) { assert(varTypeIsStruct(argType)); - int floatNum = 0; - if ((floatFlags & STRUCT_FLOAT_FIELD_ONLY_ONE) != 0) - { - assert(argSize <= 8); - assert(varDsc->lvExactSize() <= argSize); - - floatNum = 1; - canPassArgInRegisters = varDscInfo->canEnreg(TYP_DOUBLE, 1); - - argRegTypeInStruct1 = (varDsc->lvExactSize() == 8) ? TYP_DOUBLE : TYP_FLOAT; - } - else if ((floatFlags & STRUCT_FLOAT_FIELD_ONLY_TWO) != 0) - { - floatNum = 2; - canPassArgInRegisters = varDscInfo->canEnreg(TYP_DOUBLE, 2); - - argRegTypeInStruct1 = (floatFlags & STRUCT_FIRST_FIELD_SIZE_IS8) ? TYP_DOUBLE : TYP_FLOAT; - argRegTypeInStruct2 = (floatFlags & STRUCT_SECOND_FIELD_SIZE_IS8) ? TYP_DOUBLE : TYP_FLOAT; - } - else if ((floatFlags & STRUCT_FLOAT_FIELD_FIRST) != 0) - { - floatNum = 1; - canPassArgInRegisters = varDscInfo->canEnreg(TYP_DOUBLE, 1); - canPassArgInRegisters = canPassArgInRegisters && varDscInfo->canEnreg(TYP_I_IMPL, 1); - - argRegTypeInStruct1 = (floatFlags & STRUCT_FIRST_FIELD_SIZE_IS8) ? TYP_DOUBLE : TYP_FLOAT; - argRegTypeInStruct2 = (floatFlags & STRUCT_SECOND_FIELD_SIZE_IS8) ? TYP_LONG : TYP_INT; - } - else if ((floatFlags & STRUCT_FLOAT_FIELD_SECOND) != 0) - { - floatNum = 1; - canPassArgInRegisters = varDscInfo->canEnreg(TYP_DOUBLE, 1); - canPassArgInRegisters = canPassArgInRegisters && varDscInfo->canEnreg(TYP_I_IMPL, 1); + assert(((fpInfo.flags & FpStruct::OnlyOne) != 0) || varDsc->lvExactSize() <= argSize); + cSlotsToEnregister = ((fpInfo.flags & FpStruct::OnlyOne) != 0) ? 1 : 2; - argRegTypeInStruct1 = (floatFlags & STRUCT_FIRST_FIELD_SIZE_IS8) ? TYP_LONG : TYP_INT; - argRegTypeInStruct2 = (floatFlags & STRUCT_SECOND_FIELD_SIZE_IS8) ? TYP_DOUBLE : TYP_FLOAT; - } + int floatNum = ((fpInfo.flags & FpStruct::BothFloat) != 0) ? 2 : 1; + canPassArgInRegisters = varDscInfo->canEnreg(TYP_DOUBLE, floatNum); + if (canPassArgInRegisters && ((fpInfo.flags & (FpStruct::FloatInt | FpStruct::IntFloat)) != 0)) + canPassArgInRegisters = varDscInfo->canEnreg(TYP_I_IMPL, 1); - assert((floatNum == 1) || (floatNum == 2)); + Compiler::GetTypesFromFpStructInRegistersInfo(fpInfo, &argRegTypeInStruct1, &argRegTypeInStruct2); if (!canPassArgInRegisters) { - // On LoongArch64, if there aren't any remaining floating-point registers to pass the argument, - // integer registers (if any) are used instead. - canPassArgInRegisters = varDscInfo->canEnreg(argType, cSlotsToEnregister); - argRegTypeInStruct1 = TYP_UNKNOWN; argRegTypeInStruct2 = TYP_UNKNOWN; - if (cSlotsToEnregister == 2) +#ifdef TARGET_RISCV64 + if (argSize > MAX_PASS_MULTIREG_BYTES) + { + // According to integer calling convention, structs > 16 bytes are passed by implicit reference. + cSlots = 1; + cSlotsToEnregister = 1; + varDsc->lvIsImplicitByRef = 1; // lvaSetStruct doesn't know about register availability so set here + canPassArgInRegisters = varDscInfo->canEnreg(TYP_I_IMPL, 1); + } + else +#endif // TARGET_RISCV64 { - if (!canPassArgInRegisters && varDscInfo->canEnreg(TYP_I_IMPL, 1)) + // On LoongArch64 and RISC-V64, if there aren't any remaining floating-point registers to pass the + // argument, integer registers (if any) are used instead. + cSlotsToEnregister = cSlots; + canPassArgInRegisters = varDscInfo->canEnreg(argType, cSlotsToEnregister); + if (cSlots == 2) { - // Here a struct-arg which needs two registers but only one integer register available, - // it has to be split. - argRegTypeInStruct1 = TYP_I_IMPL; - canPassArgInRegisters = true; + if (!canPassArgInRegisters && varDscInfo->canEnreg(TYP_I_IMPL, 1)) + { + // Here a struct-arg which needs two registers but only one integer register available, + // it has to be split. + argRegTypeInStruct1 = TYP_I_IMPL; + canPassArgInRegisters = true; + } } } } @@ -1091,15 +1071,13 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un { varDsc->SetArgReg( genMapRegArgNumToRegNum(firstAllocatedRegArgNum, argRegTypeInStruct1, info.compCallConv)); - varDsc->lvIs4Field1 = (genTypeSize(argRegTypeInStruct1) == 4) ? 1 : 0; if (argRegTypeInStruct2 != TYP_UNKNOWN) { secondAllocatedRegArgNum = varDscInfo->allocRegArg(argRegTypeInStruct2, 1); varDsc->SetOtherArgReg( genMapRegArgNumToRegNum(secondAllocatedRegArgNum, argRegTypeInStruct2, info.compCallConv)); - varDsc->lvIs4Field2 = (genTypeSize(argRegTypeInStruct2) == 4) ? 1 : 0; } - else if (cSlots > 1) + else if (cSlotsToEnregister > 1) { // Here a struct-arg which needs two registers but only one integer register available, // it has to be split. But we reserved extra 8-bytes for the whole struct. @@ -1280,7 +1258,7 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un } printf("\n"); } -#endif // DEBUG +#endif // DEBUG } // end if (canPassArgInRegisters) else { @@ -1675,9 +1653,7 @@ void Compiler::lvaInitVarDsc(LclVarDsc* varDsc, varDsc->lvIsImplicitByRef = 0; #endif // FEATURE_IMPLICIT_BYREFS #if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - varDsc->lvIs4Field1 = 0; - varDsc->lvIs4Field2 = 0; - varDsc->lvIsSplit = 0; + varDsc->lvIsSplit = 0; #endif // TARGET_LOONGARCH64 || TARGET_RISCV64 // Set the lvType (before this point it is TYP_UNDEF). @@ -1971,7 +1947,6 @@ void Compiler::lvaClassifyParameterABI() // Old info does not take 4 shadow slots on win-x64 into account. oldStackSize += 32; #endif - assert(lvaParameterStackSize == roundUp(oldStackSize, TARGET_POINTER_SIZE)); } #endif @@ -3421,7 +3396,6 @@ void Compiler::lvaSetStruct(unsigned varNum, ClassLayout* layout, bool unsafeVal structPassingKind howToReturnStruct; getArgTypeForStruct(layout->GetClassHandle(), &howToReturnStruct, info.compIsVarArgs, varDsc->lvExactSize()); - if (howToReturnStruct == SPK_ByReference) { JITDUMP("Marking V%02i as a byref parameter\n", varNum); diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index a966813f047542..bf69825b27787e 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -4694,12 +4694,14 @@ void Lowering::LowerStoreLocCommon(GenTreeLclVarCommon* lclStore) // x86 uses it only for long return type, not for structs. assert(slotCount == 1); assert(lclRegType != TYP_UNDEF); -#else // !TARGET_XARCH || UNIX_AMD64_ABI +#else // !TARGET_XARCH || UNIX_AMD64_ABI if (!comp->IsHfa(layout->GetClassHandle())) { if (slotCount > 1) { +#if !defined(TARGET_RISCV64) && !defined(TARGET_LOONGARCH64) assert(call->HasMultiRegRetVal()); +#endif } else { @@ -5004,6 +5006,7 @@ void Lowering::LowerRetSingleRegStructLclVar(GenTreeUnOp* ret) // Otherwise we don't mind that we leave the upper bits undefined. lclVar->ChangeType(ret->TypeGet()); } + lclVar->AsLclFld()->SetLclOffs(comp->compRetTypeDesc.GetSingleReturnFieldOffset()); } else { @@ -5146,7 +5149,6 @@ void Lowering::LowerStoreSingleRegCallStruct(GenTreeBlk* store) const ClassLayout* layout = store->GetLayout(); var_types regType = layout->GetRegisterType(); - if (regType != TYP_UNDEF) { #if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) @@ -5192,8 +5194,8 @@ GenTreeLclVar* Lowering::SpillStructCallResult(GenTreeCall* call) const comp->lvaSetVarDoNotEnregister(spillNum DEBUGARG(DoNotEnregisterReason::LocalField)); CORINFO_CLASS_HANDLE retClsHnd = call->gtRetClsHnd; comp->lvaSetStruct(spillNum, retClsHnd, false); - GenTreeLclFld* spill = comp->gtNewStoreLclFldNode(spillNum, call->TypeGet(), 0, call); - + unsigned offset = call->GetReturnTypeDesc()->GetSingleReturnFieldOffset(); + GenTreeLclFld* spill = comp->gtNewStoreLclFldNode(spillNum, call->TypeGet(), offset, call); BlockRange().InsertAfter(call, spill); ContainCheckStoreLoc(spill); GenTreeLclVar* loadCallResult = comp->gtNewLclvNode(spillNum, TYP_STRUCT)->AsLclVar(); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 1d2c876779bd4b..f76358a13cd413 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -2126,10 +2126,10 @@ void CallArgs::AddFinalArgsAndDetermineABIInfo(Compiler* comp, GenTreeCall* call var_types structBaseType = comp->getArgTypeForStruct(argSigClass, &howToPassStruct, IsVarArgs(), argLayout->GetSize()); #if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + arg.AbiInfo.PassedByRef = false; if (arg.NewAbiInfo.HasAnyFloatingRegisterSegment()) { // Struct passed according to hardware floating-point calling convention - assert(arg.NewAbiInfo.NumSegments <= 2); assert(!arg.NewAbiInfo.HasAnyStackSegment()); if (arg.NewAbiInfo.NumSegments == 2) { @@ -2148,8 +2148,17 @@ void CallArgs::AddFinalArgsAndDetermineABIInfo(Compiler* comp, GenTreeCall* call for (unsigned i = 0; i < arg.NewAbiInfo.NumSegments; ++i) { - arg.AbiInfo.StructFloatFieldType[i] = arg.NewAbiInfo.Segment(i).GetRegisterType(); + arg.AbiInfo.StructFloatFieldType[i] = arg.NewAbiInfo.Segment(i).GetRegisterType(); + arg.AbiInfo.StructFloatFieldOffset[i] = arg.NewAbiInfo.Segment(i).Offset; } + assert(howToPassStruct == Compiler::SPK_ByValue || howToPassStruct == Compiler::SPK_PrimitiveType); + } + else if (argLayout->GetSize() > MAX_PASS_MULTIREG_BYTES) + { + // Struct passed according to integer calling convention by reference + assert(arg.NewAbiInfo.NumSegments == 1); + assert(arg.NewAbiInfo.Segment(0).Size == TARGET_POINTER_SIZE); + howToPassStruct = Compiler::SPK_ByReference; } #endif // defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) arg.AbiInfo.PassedByRef = howToPassStruct == Compiler::SPK_ByReference; @@ -2587,7 +2596,13 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call) // We have a struct argument that fits into a register, and it is either a power of 2, // or a local. // Change our argument, as needed, into a value of the appropriate type. - assert((structBaseType != TYP_STRUCT) && (genTypeSize(structBaseType) >= originalSize)); + assert(structBaseType != TYP_STRUCT); + + // On RISC-V / LoongArch the passing size may be smaller than the original size if we pass a struct + // according to hardware FP calling convention and it has empty fields +#if !defined(TARGET_LOONGARCH64) && !defined(TARGET_RISCV64) + assert(genTypeSize(structBaseType) >= originalSize); +#endif if (argObj->OperIsLoad()) { @@ -2649,6 +2664,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call) if (argObj->OperIs(GT_LCL_VAR)) { argObj->SetOper(GT_LCL_FLD); +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + argObj->AsLclFld()->SetLclOffs(arg.AbiInfo.StructFloatFieldOffset[0]); +#endif } lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::SwizzleArg)); } @@ -2920,7 +2938,10 @@ GenTree* Compiler::fgMorphMultiregStructArg(CallArg* arg) } else { +#ifndef TARGET_RISCV64 assert(structSize <= MAX_ARG_REG_COUNT * TARGET_POINTER_SIZE); +#endif + assert(arg->AbiInfo.NumRegs <= MAX_ARG_REG_COUNT); auto getSlotType = [layout](unsigned inx) { return (layout != nullptr) ? layout->GetGCPtrType(inx) : TYP_I_IMPL; @@ -2941,11 +2962,11 @@ GenTree* Compiler::fgMorphMultiregStructArg(CallArg* arg) } else #elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - if ((arg->AbiInfo.StructFloatFieldType[inx] != TYP_UNDEF) && - !varTypeIsGC(getSlotType(offset / TARGET_POINTER_SIZE))) + if (arg->AbiInfo.StructFloatFieldType[inx] != TYP_UNDEF) { - elems[inx].Type = arg->AbiInfo.StructFloatFieldType[inx]; - offset += (structSize > TARGET_POINTER_SIZE) ? 8 : 4; + var_types slotType = getSlotType(arg->AbiInfo.StructFloatFieldOffset[inx] / TARGET_POINTER_SIZE); + elems[inx].Offset = arg->AbiInfo.StructFloatFieldOffset[inx]; + elems[inx].Type = varTypeIsGC(slotType) ? slotType : arg->AbiInfo.StructFloatFieldType[inx]; } else #endif // TARGET_LOONGARCH64 || TARGET_RISCV64 @@ -3002,9 +3023,9 @@ GenTree* Compiler::fgMorphMultiregStructArg(CallArg* arg) } #if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - // For LoongArch64's ABI, the struct {long a; float b;} may be passed - // by integer and float registers and it needs to include the padding here. - assert(roundUp(structSize, TARGET_POINTER_SIZE) == roundUp(loadExtent, TARGET_POINTER_SIZE)); + // For LoongArch64's and RISC-V ABI, struct { long a; float b; } or struct { int a; float b; struct {/*empty*/}; } + // may be passed by integer and float registers and it needs to include the padding here. + assert(roundUp(structSize, TARGET_POINTER_SIZE) >= roundUp(loadExtent, TARGET_POINTER_SIZE)); #else if (argNode->IsLocal()) { diff --git a/src/coreclr/jit/scopeinfo.cpp b/src/coreclr/jit/scopeinfo.cpp index d2d9a65d01be3a..263956c22167e5 100644 --- a/src/coreclr/jit/scopeinfo.cpp +++ b/src/coreclr/jit/scopeinfo.cpp @@ -1750,9 +1750,11 @@ void CodeGen::psiBegProlog() var_types regType; if (varTypeIsStruct(lclVarDsc)) { +#ifdef TARGET_LOONGARCH // Must be <= 16 bytes or else it wouldn't be passed in registers, // which can be bigger (and is handled above). noway_assert(EA_SIZE_IN_BYTES(lclVarDsc->lvSize()) <= 16); +#endif // TARGET_LOONGARCH if (emitter::isFloatReg(lclVarDsc->GetArgReg())) { regType = TYP_DOUBLE; diff --git a/src/coreclr/jit/targetriscv64.cpp b/src/coreclr/jit/targetriscv64.cpp index 6adb2b7b91ea1b..567fc141b02e9a 100644 --- a/src/coreclr/jit/targetriscv64.cpp +++ b/src/coreclr/jit/targetriscv64.cpp @@ -58,33 +58,29 @@ ABIPassingInformation RiscV64Classifier::Classify(Compiler* comp, ClassLayout* structLayout, WellKnownArg /*wellKnownParam*/) { - StructFloatFieldInfoFlags flags = STRUCT_NO_FLOAT_FIELD; - unsigned intFields = 0, floatFields = 0; - unsigned passedSize; + FpStructInRegistersInfo info = {}; + unsigned intFields = 0, floatFields = 0; + unsigned passedSize; + using namespace FpStruct; if (varTypeIsStruct(type)) { passedSize = structLayout->GetSize(); - if (passedSize > MAX_PASS_MULTIREG_BYTES) - { - passedSize = TARGET_POINTER_SIZE; // pass by reference - } - else if (!structLayout->IsBlockLayout()) + if (!structLayout->IsBlockLayout()) { - flags = (StructFloatFieldInfoFlags)comp->info.compCompHnd->getRISCV64PassStructInRegisterFlags( - structLayout->GetClassHandle()); + info = comp->GetPassFpStructInRegistersInfo(structLayout->GetClassHandle()); - if ((flags & STRUCT_FLOAT_FIELD_ONLY_ONE) != 0) + if ((info.flags & OnlyOne) != 0) { floatFields = 1; } - else if ((flags & STRUCT_FLOAT_FIELD_ONLY_TWO) != 0) + else if ((info.flags & BothFloat) != 0) { floatFields = 2; } - else if (flags != STRUCT_NO_FLOAT_FIELD) + else if (info.flags != UseIntCallConv) { - assert((flags & (STRUCT_FLOAT_FIELD_FIRST | STRUCT_FLOAT_FIELD_SECOND)) != 0); + assert((info.flags & (FloatInt | IntFloat)) != 0); floatFields = 1; intFields = 1; } @@ -104,34 +100,38 @@ ABIPassingInformation RiscV64Classifier::Classify(Compiler* comp, // Hardware floating-point calling convention if ((floatFields == 1) && (intFields == 0)) { - if (flags == STRUCT_NO_FLOAT_FIELD) + unsigned offset = 0; + if (info.flags == UseIntCallConv) + { assert(varTypeIsFloating(type)); // standalone floating-point real + } else - assert((flags & STRUCT_FLOAT_FIELD_ONLY_ONE) != 0); // struct containing just one FP real + { + assert((info.flags & OnlyOne) != 0); // struct containing just one FP real + passedSize = info.Size1st(); + offset = info.offset1st; + } - return ABIPassingInformation::FromSegment(comp, ABIPassingSegment::InRegister(m_floatRegs.Dequeue(), 0, + return ABIPassingInformation::FromSegment(comp, ABIPassingSegment::InRegister(m_floatRegs.Dequeue(), offset, passedSize)); } else { assert(varTypeIsStruct(type)); assert((floatFields + intFields) == 2); - assert(flags != STRUCT_NO_FLOAT_FIELD); - assert((flags & STRUCT_FLOAT_FIELD_ONLY_ONE) == 0); - - unsigned firstSize = ((flags & STRUCT_FIRST_FIELD_SIZE_IS8) != 0) ? 8 : 4; - unsigned secondSize = ((flags & STRUCT_SECOND_FIELD_SIZE_IS8) != 0) ? 8 : 4; - unsigned offset = max(firstSize, secondSize); // TODO: cover empty fields and custom offsets / alignments + assert(info.flags != UseIntCallConv); + assert((info.flags & OnlyOne) == 0); - bool isFirstFloat = (flags & (STRUCT_FLOAT_FIELD_ONLY_TWO | STRUCT_FLOAT_FIELD_FIRST)) != 0; - bool isSecondFloat = (flags & (STRUCT_FLOAT_FIELD_ONLY_TWO | STRUCT_FLOAT_FIELD_SECOND)) != 0; + bool isFirstFloat = (info.flags & (BothFloat | FloatInt)) != 0; + bool isSecondFloat = (info.flags & (BothFloat | IntFloat)) != 0; assert(isFirstFloat || isSecondFloat); regNumber firstReg = (isFirstFloat ? m_floatRegs : m_intRegs).Dequeue(); regNumber secondReg = (isSecondFloat ? m_floatRegs : m_intRegs).Dequeue(); - return ABIPassingInformation::FromSegments(comp, ABIPassingSegment::InRegister(firstReg, 0, firstSize), - ABIPassingSegment::InRegister(secondReg, offset, secondSize)); + ABIPassingSegment seg1st = ABIPassingSegment::InRegister(firstReg, info.offset1st, info.Size1st()); + ABIPassingSegment seg2nd = ABIPassingSegment::InRegister(secondReg, info.offset2nd, info.Size2nd()); + return ABIPassingInformation::FromSegments(comp, seg1st, seg2nd); } } else @@ -146,6 +146,9 @@ ABIPassingInformation RiscV64Classifier::Classify(Compiler* comp, return seg; }; + if (passedSize > MAX_PASS_MULTIREG_BYTES) + passedSize = TARGET_POINTER_SIZE; // pass by implicit reference + if (m_intRegs.Count() > 0) { if (passedSize <= TARGET_POINTER_SIZE) diff --git a/src/coreclr/jit/utils.h b/src/coreclr/jit/utils.h index f665a4e813eff6..1a28ba7b8dee57 100644 --- a/src/coreclr/jit/utils.h +++ b/src/coreclr/jit/utils.h @@ -1174,4 +1174,7 @@ bool CastFromFloatOverflows(float fromValue, var_types toType); bool CastFromDoubleOverflows(double fromValue, var_types toType); } // namespace CheckedOps +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) + #endif // _UTILS_H_ diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index a695d07be6fcf1..6c31276783f732 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -3453,10 +3453,10 @@ private uint getLoongArch64PassStructInRegisterFlags(CORINFO_CLASS_STRUCT_* cls) return LoongArch64PassStructInRegister.GetLoongArch64PassStructInRegisterFlags(typeDesc); } - private uint getRISCV64PassStructInRegisterFlags(CORINFO_CLASS_STRUCT_* cls) + private FpStructInRegistersInfo getRiscV64PassFpStructInRegistersInfo(CORINFO_CLASS_STRUCT_* cls) { TypeDesc typeDesc = HandleToObject(cls); - return RISCV64PassStructInRegister.GetRISCV64PassStructInRegisterFlags(typeDesc); + return RISCV64PassStructInRegister.GetRiscV64PassFpStructInRegistersInfo(typeDesc); } private uint getThreadTLSIndex(ref void* ppIndirection) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index 950ce7f4a12232..71cd542838a59c 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -1874,12 +1874,12 @@ private static uint _getLoongArch64PassStructInRegisterFlags(IntPtr thisHandle, } [UnmanagedCallersOnly] - private static uint _getRISCV64PassStructInRegisterFlags(IntPtr thisHandle, IntPtr* ppException, CORINFO_CLASS_STRUCT_* structHnd) + private static FpStructInRegistersInfo _getRiscV64PassFpStructInRegistersInfo(IntPtr thisHandle, IntPtr* ppException, CORINFO_CLASS_STRUCT_* structHnd) { var _this = GetThis(thisHandle); try { - return _this.getRISCV64PassStructInRegisterFlags(structHnd); + return _this.getRiscV64PassFpStructInRegistersInfo(structHnd); } catch (Exception ex) { @@ -2737,7 +2737,7 @@ private static IntPtr GetUnmanagedCallbacks() callbacks[123] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; callbacks[124] = (delegate* unmanaged)&_getSwiftLowering; callbacks[125] = (delegate* unmanaged)&_getLoongArch64PassStructInRegisterFlags; - callbacks[126] = (delegate* unmanaged)&_getRISCV64PassStructInRegisterFlags; + callbacks[126] = (delegate* unmanaged)&_getRiscV64PassFpStructInRegistersInfo; callbacks[127] = (delegate* unmanaged)&_getThreadTLSIndex; callbacks[128] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; callbacks[129] = (delegate* unmanaged)&_getHelperFtn; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index b7062fc7660354..58c987db0a4174 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1262,6 +1262,74 @@ public enum StructFloatFieldInfoFlags STRUCT_HAS_8BYTES_FIELDS_MASK = (STRUCT_FIRST_FIELD_SIZE_IS8 | STRUCT_SECOND_FIELD_SIZE_IS8), }; + + public enum FpStruct_IntKind + { + Signed, + Unsigned, + GcRef, + GcByRef, + } + + // Bitfields for FpStructInRegistersInfo.flags + [Flags] + public enum FpStruct + { + // Positions of flags and bitfields + PosOnlyOne = 0, + PosBothFloat = 1, + PosFloatInt = 2, + PosIntFloat = 3, + PosSizeShift1st = 4, // 2 bits + PosSizeShift2nd = 6, // 2 bits + PosIntFieldKind = 8, // 2 bits + + UseIntCallConv = 0, // struct is passed according to integer calling convention + + // The flags and bitfields + OnlyOne = 1 << PosOnlyOne, // has only one field, which is floating-point + BothFloat = 1 << PosBothFloat, // has two fields, both are floating-point + FloatInt = 1 << PosFloatInt, // has two fields, 1st is floating and 2nd is integer + IntFloat = 1 << PosIntFloat, // has two fields, 2nd is floating and 1st is integer + SizeShift1stMask = 0b11 << PosSizeShift1st, // log2(size) of 1st field + SizeShift2ndMask = 0b11 << PosSizeShift2nd, // log2(size) of 2nd field + IntFieldKindMask = 0b11 << PosIntFieldKind, // the kind of the integer field (FpStruct::IntKind) + // Note: flags OnlyOne, BothFloat, FloatInt, and IntFloat are mutually exclusive + } + + // On RISC-V and LoongArch a struct with up to two non-empty fields, at least one of them floating-point, + // can be passed in registers according to hardware FP calling convention. FpStructInRegistersInfo represents + // passing information for such parameters. + public struct FpStructInRegistersInfo + { + public FpStruct flags; + public uint offset1st; + public uint offset2nd; + + public uint SizeShift1st() { return (uint)((int)flags >> (int)FpStruct.PosSizeShift1st) & 0b11; } + + public uint SizeShift2nd() { return (uint)((int)flags >> (int)FpStruct.PosSizeShift2nd) & 0b11; } + + public uint Size1st() { return 1u << (int)SizeShift1st(); } + public uint Size2nd() { return 1u << (int)SizeShift2nd(); } + + public FpStruct_IntKind IntFieldKind() + { + return (FpStruct_IntKind)(((int)flags >> (int)FpStruct.PosIntFieldKind) & 0b11); + } + + public StructFloatFieldInfoFlags ToOldFlags() + { + return + ((flags & FpStruct.OnlyOne) != 0 ? StructFloatFieldInfoFlags.STRUCT_FLOAT_FIELD_ONLY_ONE : 0) | + ((flags & FpStruct.BothFloat) != 0 ? StructFloatFieldInfoFlags.STRUCT_FLOAT_FIELD_ONLY_TWO : 0) | + ((flags & FpStruct.FloatInt) != 0 ? StructFloatFieldInfoFlags.STRUCT_FLOAT_FIELD_FIRST : 0) | + ((flags & FpStruct.IntFloat) != 0 ? StructFloatFieldInfoFlags.STRUCT_FLOAT_FIELD_SECOND : 0) | + ((SizeShift1st() == 3) ? StructFloatFieldInfoFlags.STRUCT_FIRST_FIELD_SIZE_IS8 : 0) | + ((SizeShift2nd() == 3) ? StructFloatFieldInfoFlags.STRUCT_SECOND_FIELD_SIZE_IS8 : 0); + } + } + // DEBUGGER DATA public enum MappingTypes { diff --git a/src/coreclr/tools/Common/JitInterface/RISCV64PassStructInRegister.cs b/src/coreclr/tools/Common/JitInterface/RISCV64PassStructInRegister.cs index fae694f8768fc9..8aa2664f47e525 100644 --- a/src/coreclr/tools/Common/JitInterface/RISCV64PassStructInRegister.cs +++ b/src/coreclr/tools/Common/JitInterface/RISCV64PassStructInRegister.cs @@ -5,17 +5,16 @@ using System.Diagnostics; using ILCompiler; using Internal.TypeSystem; +using static Internal.JitInterface.FpStruct; using static Internal.JitInterface.StructFloatFieldInfoFlags; 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 types, ref int typeIndex) + private static bool HandleInlineArrayOld(int elementTypeIndex, int nElements, Span types, ref int typeIndex) { int nFlattenedFieldsPerElement = typeIndex - elementTypeIndex; if (nFlattenedFieldsPerElement == 0) @@ -38,7 +37,7 @@ private static bool HandleInlineArray(int elementTypeIndex, int nElements, Span< return true; } - private static bool FlattenFieldTypes(TypeDesc td, Span types, ref int typeIndex) + private static bool FlattenFieldTypesOld(TypeDesc td, Span types, ref int typeIndex) { IEnumerable fields = td.GetFields(); int nFields = 0; @@ -59,7 +58,7 @@ private static bool FlattenFieldTypes(TypeDesc td, Span 0, "InlineArray length must be > 0"); + return false; // struct containing an array of empty structs is passed by integer calling convention + } + + if (!HandleInlineArrayOld(elementTypeIndex, nElements, types, ref typeIndex)) return false; } return true; } - public static uint GetRISCV64PassStructInRegisterFlags(TypeDesc td) + private static uint GetRISCV64PassStructInRegisterFlags(TypeDesc td) { - if (td.GetElementSize().AsInt > ENREGISTERED_PARAMTYPE_MAXSIZE) - return (uint)STRUCT_NO_FLOAT_FIELD; - Span types = stackalloc StructFloatFieldInfoFlags[] { STRUCT_NO_FLOAT_FIELD, STRUCT_NO_FLOAT_FIELD }; int nFields = 0; - if (!FlattenFieldTypes(td, types, ref nFields) || nFields == 0) + if (!FlattenFieldTypesOld(td, types, ref nFields) || nFields == 0) return (uint)STRUCT_NO_FLOAT_FIELD; Debug.Assert(nFields == 1 || nFields == 2); @@ -123,5 +127,203 @@ public static uint GetRISCV64PassStructInRegisterFlags(TypeDesc td) } return (uint)flags; } + + + private static void SetFpStructInRegistersInfoField(ref FpStructInRegistersInfo info, int index, + bool isFloating, FpStruct_IntKind intKind, uint size, uint offset) + { + Debug.Assert(index < 2); + if (isFloating) + { + Debug.Assert(size == sizeof(float) || size == sizeof(double)); + Debug.Assert(intKind == FpStruct_IntKind.Signed); + Debug.Assert((int)FpStruct_IntKind.Signed == 0, + "IntKind for floating fields should not clobber IntKind for int fields"); + } + + Debug.Assert(size >= 1 && size <= 8); + Debug.Assert((size & (size - 1)) == 0, "size needs to be a power of 2"); + const int sizeShiftLUT = (0 << (1*2)) | (1 << (2*2)) | (2 << (4*2)) | (3 << (8*2)); + int sizeShift = (sizeShiftLUT >> ((int)size * 2)) & 0b11; + + // Use FloatInt and IntFloat as marker flags for 1st and 2nd field respectively being floating. + // Fix to real flags (with OnlyOne and BothFloat) after flattening is complete. + Debug.Assert((int)PosIntFloat == (int)PosFloatInt + 1, "FloatInt and IntFloat need to be adjacent"); + Debug.Assert((int)PosSizeShift2nd == (int)PosSizeShift1st + 2, "SizeShift1st and 2nd need to be adjacent"); + int floatFlag = Convert.ToInt32(isFloating) << ((int)PosFloatInt + index); + int sizeShiftMask = sizeShift << ((int)PosSizeShift1st + 2 * index); + + info.flags |= (FpStruct)(floatFlag | sizeShiftMask | ((int)intKind << (int)PosIntFieldKind)); + (index == 0 ? ref info.offset1st : ref info.offset2nd) = offset; + } + + private static bool HandleInlineArray(int elementTypeIndex, int nElements, ref FpStructInRegistersInfo info, ref int typeIndex) + { + int nFlattenedFieldsPerElement = typeIndex - elementTypeIndex; + if (nFlattenedFieldsPerElement == 0) + { + Debug.Assert(nElements == 1, "HasImpliedRepeatedFields must have returned a false positive"); + return true; // ignoring empty struct + } + + Debug.Assert(nFlattenedFieldsPerElement == 1 || nFlattenedFieldsPerElement == 2); + + if (nElements > 2) + return false; // array has too many elements + + if (nElements == 2) + { + if (typeIndex + nFlattenedFieldsPerElement > 2) + return false; // array has too many fields per element + + Debug.Assert(elementTypeIndex == 0); + Debug.Assert(typeIndex == 1); + + // duplicate the array element info + const int typeSize = (int)PosIntFloat - (int)PosFloatInt; + info.flags |= (FpStruct)((int)info.flags << typeSize); + info.offset2nd = info.offset1st + info.Size1st(); + } + return true; + } + + private static bool FlattenFields(TypeDesc td, uint offset, ref FpStructInRegistersInfo info, ref int typeIndex) + { + IEnumerable fields = td.GetFields(); + int nFields = 0; + int elementTypeIndex = typeIndex; + FieldDesc prevField = null; + foreach (FieldDesc field in fields) + { + if (field.IsStatic) + continue; + nFields++; + + if (prevField != null && prevField.Offset.AsInt + prevField.FieldType.GetElementSize().AsInt > field.Offset.AsInt) + return false; // fields overlap, treat as union + + prevField = field; + + TypeFlags category = field.FieldType.Category; + if (category == TypeFlags.ValueType) + { + TypeDesc nested = field.FieldType; + if (!FlattenFields(nested, offset + (uint)field.Offset.AsInt, ref info, ref typeIndex)) + return false; + } + else if (field.FieldType.GetElementSize().AsInt <= TARGET_POINTER_SIZE) + { + if (typeIndex >= 2) + return false; // too many fields + + bool isFloating = category is TypeFlags.Single or TypeFlags.Double; + bool isSignedInt = category is + TypeFlags.SByte or + TypeFlags.Int16 or + TypeFlags.Int32 or + TypeFlags.Int64 or + TypeFlags.IntPtr; + bool isGcRef = category is + TypeFlags.Class or + TypeFlags.Interface or + TypeFlags.Array or + TypeFlags.SzArray; + + FpStruct_IntKind intKind = + isGcRef ? FpStruct_IntKind.GcRef : + (category is TypeFlags.ByRef) ? FpStruct_IntKind.GcByRef : + (isSignedInt || isFloating) ? FpStruct_IntKind.Signed : FpStruct_IntKind.Unsigned; + + SetFpStructInRegistersInfoField(ref info, typeIndex++, + isFloating, intKind, (uint)field.FieldType.GetElementSize().AsInt, offset + (uint)field.Offset.AsInt); + } + else + { + return false; // field is too big + } + } + + if ((td as MetadataType).HasImpliedRepeatedFields()) + { + Debug.Assert(nFields == 1); + int nElements = td.GetElementSize().AsInt / prevField.FieldType.GetElementSize().AsInt; + + // Only InlineArrays can have an element type of an empty struct, fixed-size buffers take only primitives + if ((typeIndex - elementTypeIndex) == 0 && (td as MetadataType).IsInlineArray) + { + Debug.Assert(nElements > 0, "InlineArray length must be > 0"); + return false; // struct containing an array of empty structs is passed by integer calling convention + } + + if (!HandleInlineArray(elementTypeIndex, nElements, ref info, ref typeIndex)) + return false; + } + return true; + } + + private static bool IsAligned(uint val, uint alignment) => 0 == (val & (alignment - 1)); + + private static FpStructInRegistersInfo GetRiscV64PassFpStructInRegistersInfoImpl(TypeDesc td) + { + FpStructInRegistersInfo info = new FpStructInRegistersInfo{}; + int nFields = 0; + if (!FlattenFields(td, 0, ref info, ref nFields)) + return new FpStructInRegistersInfo{}; + + if ((info.flags & (FloatInt | IntFloat)) == 0) + return new FpStructInRegistersInfo{}; // struct has no floating fields + + Debug.Assert(nFields == 1 || nFields == 2); + + if ((info.flags & (FloatInt | IntFloat)) == (FloatInt | IntFloat)) + { + Debug.Assert(nFields == 2); + info.flags ^= (FloatInt | IntFloat | BothFloat); // replace (FloatInt | IntFloat) with BothFloat + } + else if (nFields == 1) + { + Debug.Assert((info.flags & FloatInt) != 0); + Debug.Assert((info.flags & (IntFloat | SizeShift2ndMask)) == 0); + Debug.Assert(info.offset2nd == 0); + info.flags ^= (FloatInt | OnlyOne); // replace FloatInt with OnlyOne + } + Debug.Assert(nFields == ((info.flags & OnlyOne) != 0 ? 1 : 2)); + FpStruct floatFlags = info.flags & (OnlyOne | BothFloat | FloatInt | IntFloat); + Debug.Assert(floatFlags != 0); + Debug.Assert(((uint)floatFlags & ((uint)floatFlags - 1)) == 0, + "there can be only one of (OnlyOne | BothFloat | FloatInt | IntFloat)"); + if (nFields == 2) + { + uint end1st = info.offset1st + info.Size1st(); + uint end2nd = info.offset2nd + info.Size2nd(); + Debug.Assert(end1st <= info.offset2nd || end2nd <= info.offset1st, "fields must not overlap"); + } + Debug.Assert(info.offset1st + info.Size1st() <= td.GetElementSize().AsInt); + Debug.Assert(info.offset2nd + info.Size2nd() <= td.GetElementSize().AsInt); + if (info.IntFieldKind() != FpStruct_IntKind.Signed) + { + Debug.Assert((info.flags & (FloatInt | IntFloat)) != 0); + if (info.IntFieldKind() >= FpStruct_IntKind.GcRef) + { + Debug.Assert((info.flags & IntFloat) != 0 + ? ((info.SizeShift1st() == 3) && IsAligned(info.offset1st, TARGET_POINTER_SIZE)) + : ((info.SizeShift2nd() == 3) && IsAligned(info.offset2nd, TARGET_POINTER_SIZE))); + } + } + if ((info.flags & (OnlyOne | BothFloat)) != 0) + Debug.Assert(info.IntFieldKind() == FpStruct_IntKind.Signed); + + return info; + } + + public static FpStructInRegistersInfo GetRiscV64PassFpStructInRegistersInfo(TypeDesc td) + { + FpStructInRegistersInfo info = GetRiscV64PassFpStructInRegistersInfoImpl(td); + uint flags = GetRISCV64PassStructInRegisterFlags(td); + + Debug.Assert(flags == (uint)info.ToOldFlags()); + + return info; + } } } diff --git a/src/coreclr/tools/Common/JitInterface/SystemVStructClassificator.cs b/src/coreclr/tools/Common/JitInterface/SystemVStructClassificator.cs index bfb88cf877537e..f8a2235096c5f2 100644 --- a/src/coreclr/tools/Common/JitInterface/SystemVStructClassificator.cs +++ b/src/coreclr/tools/Common/JitInterface/SystemVStructClassificator.cs @@ -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; @@ -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 @@ -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; @@ -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 { @@ -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) { diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index 9fc314fe56a370..e0d13ac19a8efc 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -100,6 +100,7 @@ CORINFO_HELPER_DESC*,ref CORINFO_HELPER_DESC const CORINFO_HELPER_DESC*,ref CORINFO_HELPER_DESC int*,ref int unsigned int*,ref uint +FpStructInRegistersInfo,FpStructInRegistersInfo CORINFO_JUST_MY_CODE_HANDLE**,ref CORINFO_JUST_MY_CODE_HANDLE_* @@ -289,7 +290,7 @@ FUNCTIONS bool getSystemVAmd64PassStructInRegisterDescriptor(CORINFO_CLASS_HANDLE structHnd, SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR* structPassInRegDescPtr); void getSwiftLowering(CORINFO_CLASS_HANDLE structHnd, CORINFO_SWIFT_LOWERING* pLowering); uint32_t getLoongArch64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE structHnd); - uint32_t getRISCV64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE structHnd); + FpStructInRegistersInfo getRiscV64PassFpStructInRegistersInfo(CORINFO_CLASS_HANDLE structHnd); uint32_t getThreadTLSIndex(void **ppIndirection); int32_t * getAddrOfCaptureThreadGlobal(void **ppIndirection); void* getHelperFtn (CorInfoHelpFunc ftnNum, void **ppIndirection); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs index 16736aef488535..13e63729163089 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs @@ -12,6 +12,7 @@ using Internal.NativeFormat; using Internal.TypeSystem; using Internal.CorConstants; +using static Internal.JitInterface.StructFloatFieldInfoFlags; namespace ILCompiler.DependencyAnalysis.ReadyToRun @@ -234,6 +235,8 @@ internal struct ArgLocDesc public int m_byteStackSize; // Stack size in bytes public uint m_floatFlags; // struct with two-fields can be passed by registers. + public FpStructInRegistersInfo m_structFields; // RISC-V - Struct field info when using floating-point register(s) + // Initialize to represent a non-placed argument (no register or stack slots referenced). public void Init() { @@ -244,6 +247,7 @@ public void Init() m_byteStackIndex = -1; m_byteStackSize = 0; m_floatFlags = 0; + m_structFields = new FpStructInRegistersInfo(); m_fRequires64BitAlignment = false; } @@ -592,6 +596,19 @@ public uint GetFPReturnSize() return _fpReturnSize; } + public FpStructInRegistersInfo GetReturnFpStructInRegistersInfo() + { + // WRAPPER_NO_CONTRACT; + Debug.Assert(_transitionBlock.Architecture == TargetArchitecture.RiscV64); + if (!_RETURN_FLAGS_COMPUTED) + ComputeReturnFlags(); + return new FpStructInRegistersInfo { + flags = (FpStruct)_fpReturnSize, + offset1st = _returnedFpFieldOffset1st, + offset2nd = _returnedFpFieldOffset2nd + }; + } + public bool IsArgPassedByRef() { // LIMITED_METHOD_CONTRACT; @@ -628,7 +645,10 @@ public bool IsArgPassedByRef() if (_argType == CorElementType.ELEMENT_TYPE_VALUETYPE) { Debug.Assert(!_argTypeHandle.IsNull()); - return ((_argSize > _transitionBlock.EnregisteredParamTypeMaxSize) || _transitionBlock.IsArgPassedByRef(_argTypeHandle)); + // On RISC-V structs larger than 16 bytes can still be passed in registers according to FP call conv if it + // has empty fields or more padding, so also check for _hasArgLocDescForStructInRegs. + return (_argSize > _transitionBlock.EnregisteredParamTypeMaxSize) + && !_hasArgLocDescForStructInRegs; } return false; default: @@ -1458,58 +1478,43 @@ public int GetNextOffset() case TargetArchitecture.RiscV64: { + if (IsVarArg) + throw new NotImplementedException("Varargs on RISC-V not supported yet"); + int cFPRegs = 0; - uint floatFieldFlags = (uint)StructFloatFieldInfoFlags.STRUCT_NO_FLOAT_FIELD; + FpStructInRegistersInfo info = new FpStructInRegistersInfo{}; _hasArgLocDescForStructInRegs = false; switch (argType) { case CorElementType.ELEMENT_TYPE_R4: - // 32-bit floating point argument. - cFPRegs = 1; - break; - case CorElementType.ELEMENT_TYPE_R8: - // 64-bit floating point argument. + // Floating point argument cFPRegs = 1; break; case CorElementType.ELEMENT_TYPE_VALUETYPE: + TypeDesc td = _argTypeHandle.GetRuntimeTypeHandle(); + info = RISCV64PassStructInRegister.GetRiscV64PassFpStructInRegistersInfo(td); + if (info.flags != FpStruct.UseIntCallConv) { - // Composite greater than 16 bytes should be passed by reference - if (argSize > _transitionBlock.EnregisteredParamTypeMaxSize) - { - argSize = _transitionBlock.PointerSize; - } - else - { - floatFieldFlags = RISCV64PassStructInRegister.GetRISCV64PassStructInRegisterFlags(_argTypeHandle.GetRuntimeTypeHandle()); - if ((floatFieldFlags & (uint)StructFloatFieldInfoFlags.STRUCT_FLOAT_FIELD_ONLY_TWO) != 0) - { - cFPRegs = 2; - } - else if ((floatFieldFlags & (uint)StructFloatFieldInfoFlags.STRUCT_HAS_FLOAT_FIELDS_MASK) != 0) - { - cFPRegs = 1; - } - } - - break; + // Struct may be passed according to hardware floating-point calling convention + cFPRegs = ((info.flags & FpStruct.BothFloat) != 0) ? 2 : 1; } + break; default: break; } - bool isValueType = (argType == CorElementType.ELEMENT_TYPE_VALUETYPE); - int cbArg = _transitionBlock.StackElemSize(argSize, isValueType, false); - - if (cFPRegs > 0 && !IsVarArg) + if (cFPRegs > 0) { - if (isValueType && ((floatFieldFlags & (uint)StructFloatFieldInfoFlags.STRUCT_HAS_ONE_FLOAT_MASK) != 0)) + // Pass according to hardware floating-point calling convention iff the argument can be fully enregistered + if ((info.flags & (FpStruct.FloatInt | FpStruct.IntFloat)) != 0) { Debug.Assert(cFPRegs == 1); - if ((_riscv64IdxFPReg < 8) && (_riscv64IdxGenReg < 8)) + + if ((1 + _riscv64IdxFPReg <= _transitionBlock.NumArgumentRegisters) && (1 + _riscv64IdxGenReg <= _transitionBlock.NumArgumentRegisters)) { _argLocDescForStructInRegs = new ArgLocDesc(); _argLocDescForStructInRegs.m_idxFloatReg = _riscv64IdxFPReg; @@ -1518,68 +1523,68 @@ public int GetNextOffset() _argLocDescForStructInRegs.m_idxGenReg = _riscv64IdxGenReg; _argLocDescForStructInRegs.m_cGenReg = 1; + _argLocDescForStructInRegs.m_structFields = info; _hasArgLocDescForStructInRegs = true; - _argLocDescForStructInRegs.m_floatFlags = floatFieldFlags; - int argOfsInner = - ((floatFieldFlags & (uint)StructFloatFieldInfoFlags.STRUCT_FLOAT_FIELD_SECOND) != 0) - ? _transitionBlock.OffsetOfArgumentRegisters + _riscv64IdxGenReg * 8 - : _transitionBlock.OffsetOfFloatArgumentRegisters + _riscv64IdxFPReg * 8; + int regOffset = ((info.flags & FpStruct.IntFloat) != 0) + ? _transitionBlock.OffsetOfArgumentRegisters + _riscv64IdxGenReg * _transitionBlock.PointerSize + : _transitionBlock.OffsetOfFloatArgumentRegisters + _riscv64IdxFPReg * _transitionBlock.FloatRegisterSize; _riscv64IdxFPReg++; _riscv64IdxGenReg++; - return argOfsInner; + return regOffset; } } - else if (cFPRegs + _riscv64IdxFPReg <= 8) + else if (cFPRegs + _riscv64IdxFPReg <= _transitionBlock.NumArgumentRegisters) { - // Each floating point register in the argument area is 8 bytes. - int argOfsInner = _transitionBlock.OffsetOfFloatArgumentRegisters + _riscv64IdxFPReg * 8; - if (floatFieldFlags == (uint)StructFloatFieldInfoFlags.STRUCT_FLOAT_FIELD_ONLY_TWO) + int regOffset = _transitionBlock.OffsetOfFloatArgumentRegisters + _riscv64IdxFPReg * _transitionBlock.FloatRegisterSize; + if ((info.flags & (FpStruct.BothFloat | FpStruct.OnlyOne)) != 0) { - // struct with two single-float fields. _argLocDescForStructInRegs = new ArgLocDesc(); _argLocDescForStructInRegs.m_idxFloatReg = _riscv64IdxFPReg; _argLocDescForStructInRegs.m_cFloatReg = 2; - Debug.Assert(cFPRegs == 2); - Debug.Assert(argSize == 8); + _argLocDescForStructInRegs.m_structFields = info; _hasArgLocDescForStructInRegs = true; - _argLocDescForStructInRegs.m_floatFlags = (uint)StructFloatFieldInfoFlags.STRUCT_FLOAT_FIELD_ONLY_TWO; } _riscv64IdxFPReg += cFPRegs; - return argOfsInner; - } - else - { - _riscv64IdxFPReg = 8; + return regOffset; } } - { - Debug.Assert((cbArg % _transitionBlock.PointerSize) == 0); + // Pass according to integer calling convention - int regSlots = ALIGN_UP(cbArg, _transitionBlock.PointerSize) / _transitionBlock.PointerSize; - // Only a0-a7 are valid argument registers. - if (_riscv64IdxGenReg + regSlots <= 8) - { - // The entirety of the arg fits in the register slots. - int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _riscv64IdxGenReg * 8; - _riscv64IdxGenReg += regSlots; - return argOfsInner; - } - else if (_riscv64IdxGenReg < 8) - { - int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _riscv64IdxGenReg * 8; - _riscv64IdxGenReg = 8; - _riscv64OfsStack += 8; - return argOfsInner; - } + if (argSize > _transitionBlock.EnregisteredParamTypeMaxSize) + argSize = _transitionBlock.PointerSize; // pass by implicit reference + + bool isValueType = (argType == CorElementType.ELEMENT_TYPE_VALUETYPE); + int cbArg = _transitionBlock.StackElemSize(argSize, isValueType, false); + Debug.Assert((cbArg % _transitionBlock.PointerSize) == 0); + + int regSlots = ALIGN_UP(cbArg, _transitionBlock.PointerSize) / _transitionBlock.PointerSize; + Debug.Assert(regSlots <= 2); + if (_riscv64IdxGenReg + regSlots <= _transitionBlock.NumArgumentRegisters) // pass in register(s) + { + int regOffset = _transitionBlock.OffsetOfArgumentRegisters + _riscv64IdxGenReg * _transitionBlock.PointerSize; + _riscv64IdxGenReg += regSlots; + return regOffset; + } + else if (_riscv64IdxGenReg < _transitionBlock.NumArgumentRegisters) // pass split; head in register, tail on stack + { + Debug.Assert(regSlots == 2); + Debug.Assert(_riscv64IdxGenReg + 1 == _transitionBlock.NumArgumentRegisters, "last argument register should be free"); + Debug.Assert((_riscv64IdxGenReg + regSlots - _transitionBlock.NumArgumentRegisters) == 1, "one stack slot needed"); + Debug.Assert(_riscv64OfsStack == 0, "tail of a split argument should be the first slot on the stack"); + + int regOffset = _transitionBlock.OffsetOfArgumentRegisters + _riscv64IdxGenReg * _transitionBlock.PointerSize; + _riscv64OfsStack = 8; + _riscv64IdxGenReg = _transitionBlock.NumArgumentRegisters; + return regOffset; } - argOfs = _transitionBlock.OffsetOfArgs + _riscv64OfsStack; - _riscv64OfsStack += cbArg; - return argOfs; + int stackOffset = _transitionBlock.OffsetOfArgs + _riscv64OfsStack; // pass entirely on stack + _riscv64OfsStack += ALIGN_UP(cbArg, _transitionBlock.PointerSize); + return stackOffset; } default: @@ -2062,8 +2067,14 @@ private void ForceSigWalk() private bool _SIZE_OF_ARG_STACK_COMPUTED; private bool _RETURN_FLAGS_COMPUTED; private bool _RETURN_HAS_RET_BUFFER; // Cached value of HasRetBuffArg + private uint _fpReturnSize; + // Offsets of fields returned according to RISC-V hardware floating-point calling convention + // (FpStruct flags are in _fpReturnSize) + private uint _returnedFpFieldOffset1st; + private uint _returnedFpFieldOffset2nd; + /* ITERATION_STARTED = 0x0001, SIZE_OF_ARG_STACK_COMPUTED = 0x0002, RETURN_FLAGS_COMPUTED = 0x0004, @@ -2093,7 +2104,7 @@ private void ComputeReturnFlags() if (!_RETURN_HAS_RET_BUFFER) { - _transitionBlock.ComputeReturnValueTreatment(type, thRetType, IsVarArg, out _RETURN_HAS_RET_BUFFER, out _fpReturnSize); + _transitionBlock.ComputeReturnValueTreatment(type, thRetType, IsVarArg, out _RETURN_HAS_RET_BUFFER, out _fpReturnSize, out _returnedFpFieldOffset1st, out _returnedFpFieldOffset2nd); } _RETURN_FLAGS_COMPUTED = true; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TransitionBlock.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TransitionBlock.cs index 7f2b1fd25ee4fe..cfa49e81f0e64e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TransitionBlock.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TransitionBlock.cs @@ -11,6 +11,7 @@ using Internal.TypeSystem; using Internal.CorConstants; using Internal.JitInterface; +using static Internal.JitInterface.StructFloatFieldInfoFlags; namespace ILCompiler.DependencyAnalysis.ReadyToRun { @@ -303,10 +304,12 @@ public virtual bool IsVarArgPassedByRef(int size) return size > EnregisteredParamTypeMaxSize; } - public void ComputeReturnValueTreatment(CorElementType type, TypeHandle thRetType, bool isVarArgMethod, out bool usesRetBuffer, out uint fpReturnSize) + public void ComputeReturnValueTreatment(CorElementType type, TypeHandle thRetType, bool isVarArgMethod, out bool usesRetBuffer, out uint fpReturnSize, out uint returnedFpFieldOffset1st, out uint returnedFpFieldOffset2nd) { usesRetBuffer = false; fpReturnSize = 0; + returnedFpFieldOffset1st = 0; + returnedFpFieldOffset2nd = 0; switch (type) { @@ -385,15 +388,24 @@ public void ComputeReturnValueTreatment(CorElementType type, TypeHandle thRetTyp break; } } - - if (size <= EnregisteredReturnTypeIntegerMaxSize) + else if (IsLoongArch64) { - if (IsLoongArch64) + if (size <= EnregisteredReturnTypeIntegerMaxSize) + { fpReturnSize = LoongArch64PassStructInRegister.GetLoongArch64PassStructInRegisterFlags(thRetType.GetRuntimeTypeHandle()) & 0xff; - else if (IsRiscV64) - fpReturnSize = RISCV64PassStructInRegister.GetRISCV64PassStructInRegisterFlags(thRetType.GetRuntimeTypeHandle()) & 0xff; - break; + break; + } + } + else if (IsRiscV64) + { + TypeDesc td = thRetType.GetRuntimeTypeHandle(); + FpStructInRegistersInfo info = RISCV64PassStructInRegister.GetRiscV64PassFpStructInRegistersInfo(td); + fpReturnSize = (uint)info.flags; + returnedFpFieldOffset1st = info.offset1st; + returnedFpFieldOffset2nd = info.offset2nd; + if (info.flags != FpStruct.UseIntCallConv || size <= EnregisteredReturnTypeIntegerMaxSize) + break; } } @@ -714,9 +726,20 @@ public override bool IsArgPassedByRef(TypeHandle th) { Debug.Assert(!th.IsNull()); Debug.Assert(th.IsValueType()); + if (th.GetSize() <= EnregisteredParamTypeMaxSize) + return false; - // Composites greater than 16 bytes are passed by reference - return th.GetSize() > EnregisteredParamTypeMaxSize; + // Structs larger than 16 bytes can still be passed in registers according to FP call conv if it has empty + // fields or more padding, so make sure it's passed according to integer call conv which bounds structs + // passed by value to 16 bytes. + // + // Note: if it's larger than 16 bytes and elegible for passing according to FP call conv, it still does not + // mean it will not be passed by reference. We need to know if there's enough free registers, otherwise + // it will fall back to passing according to integer calling convention (by implicit reference). + // (see ArgIterator.IsArgPassedByRef()) + TypeDesc td = th.GetRuntimeTypeHandle(); + FpStructInRegistersInfo info = RISCV64PassStructInRegister.GetRiscV64PassFpStructInRegistersInfo(td); + return (info.flags == FpStruct.UseIntCallConv); } public sealed override int GetRetBuffArgOffset(bool hasThis) => OffsetOfFirstGCRefMapSlot + (hasThis ? 8 : 0); diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index 75722274d9e9b6..c61ec8e0c0cfb5 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -137,7 +137,7 @@ struct JitInterfaceCallbacks bool (* getSystemVAmd64PassStructInRegisterDescriptor)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE structHnd, SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR* structPassInRegDescPtr); void (* getSwiftLowering)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE structHnd, CORINFO_SWIFT_LOWERING* pLowering); uint32_t (* getLoongArch64PassStructInRegisterFlags)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE structHnd); - uint32_t (* getRISCV64PassStructInRegisterFlags)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE structHnd); + FpStructInRegistersInfo (* getRiscV64PassFpStructInRegistersInfo)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE structHnd); uint32_t (* getThreadTLSIndex)(void * thisHandle, CorInfoExceptionClass** ppException, void** ppIndirection); int32_t* (* getAddrOfCaptureThreadGlobal)(void * thisHandle, CorInfoExceptionClass** ppException, void** ppIndirection); void* (* getHelperFtn)(void * thisHandle, CorInfoExceptionClass** ppException, CorInfoHelpFunc ftnNum, void** ppIndirection); @@ -1416,11 +1416,11 @@ class JitInterfaceWrapper : public ICorJitInfo return temp; } - virtual uint32_t getRISCV64PassStructInRegisterFlags( + virtual FpStructInRegistersInfo getRiscV64PassFpStructInRegistersInfo( CORINFO_CLASS_HANDLE structHnd) { CorInfoExceptionClass* pException = nullptr; - uint32_t temp = _callbacks->getRISCV64PassStructInRegisterFlags(_thisHandle, &pException, structHnd); + FpStructInRegistersInfo temp = _callbacks->getRiscV64PassFpStructInRegistersInfo(_thisHandle, &pException, structHnd); if (pException != nullptr) throw pException; return temp; } diff --git a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h index fd85fdd97e892f..b180f48ffcb0a6 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h @@ -128,7 +128,7 @@ LWM(GetStringConfigValue, DWORD, DWORD) LWM(GetSystemVAmd64PassStructInRegisterDescriptor, DWORDLONG, Agnostic_GetSystemVAmd64PassStructInRegisterDescriptor) LWM(GetSwiftLowering, DWORDLONG, Agnostic_GetSwiftLowering) LWM(GetLoongArch64PassStructInRegisterFlags, DWORDLONG, DWORD) -LWM(GetRISCV64PassStructInRegisterFlags, DWORDLONG, DWORD) +LWM(GetRiscV64PassFpStructInRegistersInfo, DWORDLONG, FpStructInRegistersInfo) LWM(GetTailCallHelpers, Agnostic_GetTailCallHelpers, Agnostic_CORINFO_TAILCALL_HELPERS) LWM(UpdateEntryPointForTailCall, Agnostic_CORINFO_CONST_LOOKUP, Agnostic_CORINFO_CONST_LOOKUP) LWM(GetThreadTLSIndex, DWORD, DLD) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index 0e0fa438e727a0..d0e8ac4d35d522 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -6360,28 +6360,31 @@ DWORD MethodContext::repGetLoongArch64PassStructInRegisterFlags(CORINFO_CLASS_HA return value; } -void MethodContext::recGetRISCV64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE structHnd, DWORD value) +void MethodContext::recGetRiscV64PassFpStructInRegistersInfo(CORINFO_CLASS_HANDLE structHnd, FpStructInRegistersInfo value) { - if (GetRISCV64PassStructInRegisterFlags == nullptr) - GetRISCV64PassStructInRegisterFlags = new LightWeightMap(); + if (GetRiscV64PassFpStructInRegistersInfo == nullptr) + GetRiscV64PassFpStructInRegistersInfo = new LightWeightMap(); DWORDLONG key = CastHandle(structHnd); - GetRISCV64PassStructInRegisterFlags->Add(key, value); - DEBUG_REC(dmpGetRISCV64PassStructInRegisterFlags(key, value)); + GetRiscV64PassFpStructInRegistersInfo->Add(key, value); + DEBUG_REC(dmpGetRiscV64PassFpStructInRegistersInfo(key, value)); } -void MethodContext::dmpGetRISCV64PassStructInRegisterFlags(DWORDLONG key, DWORD value) +void MethodContext::dmpGetRiscV64PassFpStructInRegistersInfo(DWORDLONG key, FpStructInRegistersInfo value) { - printf("GetRISCV64PassStructInRegisterFlags key %016" PRIX64 " value-%08X", key, value); + printf("GetRiscV64PassFpStructInRegistersInfo key %016" PRIX64 " value-%#03x-" + "{%s, sizes={%u, %u}, offsets={%u, %u}, IntFieldKind=%s}\n", + key, value.flags, + value.FlagName(), value.Size1st(), value.Size2nd(), value.offset1st, value.offset2nd, value.IntFieldKindName()); } -DWORD MethodContext::repGetRISCV64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE structHnd) +FpStructInRegistersInfo MethodContext::repGetRiscV64PassFpStructInRegistersInfo(CORINFO_CLASS_HANDLE structHnd) { DWORDLONG key = CastHandle(structHnd); - DWORD value = LookupByKeyOrMissNoMessage(GetRISCV64PassStructInRegisterFlags, key); - DEBUG_REP(dmpGetRISCV64PassStructInRegisterFlags(key, value)); + FpStructInRegistersInfo value = LookupByKeyOrMissNoMessage(GetRiscV64PassFpStructInRegistersInfo, key); + DEBUG_REP(dmpGetRiscV64PassFpStructInRegistersInfo(key, value)); return value; } diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h index c970dd9a7b226c..25f64095adeb45 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -787,6 +787,10 @@ class MethodContext void dmpGetRISCV64PassStructInRegisterFlags(DWORDLONG key, DWORD value); DWORD repGetRISCV64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE structHnd); + void recGetRiscV64PassFpStructInRegistersInfo(CORINFO_CLASS_HANDLE structHnd, FpStructInRegistersInfo value); + void dmpGetRiscV64PassFpStructInRegistersInfo(DWORDLONG key, FpStructInRegistersInfo value); + FpStructInRegistersInfo repGetRiscV64PassFpStructInRegistersInfo(CORINFO_CLASS_HANDLE structHnd); + void recGetRelocTypeHint(void* target, WORD result); void dmpGetRelocTypeHint(DWORDLONG key, DWORD value); WORD repGetRelocTypeHint(void* target); @@ -1170,7 +1174,7 @@ enum mcPackets Packet_GetThreadLocalFieldInfo = 207, Packet_GetThreadLocalStaticBlocksInfo = 208, Packet_GetThreadLocalStaticInfo_NativeAOT = 209, - Packet_GetRISCV64PassStructInRegisterFlags = 210, + // Packet_GetRISCV64PassStructInRegisterFlags = 210, Packet_GetObjectContent = 211, Packet_GetTypeLayout = 212, Packet_HaveSameMethodDefinition = 213, @@ -1181,6 +1185,7 @@ enum mcPackets Packet_GetClassStaticDynamicInfo = 218, Packet_GetClassThreadStaticDynamicInfo = 219, Packet_IsGenericType = 220, + Packet_GetRiscV64PassFpStructInRegistersInfo = 221, }; void SetDebugDumpVariables(); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index 84ad616c28eb9c..9cc74d8946a940 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -1418,11 +1418,11 @@ uint32_t interceptor_ICJI::getLoongArch64PassStructInRegisterFlags(CORINFO_CLASS return temp; } -uint32_t interceptor_ICJI::getRISCV64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE structHnd) +FpStructInRegistersInfo interceptor_ICJI::getRiscV64PassFpStructInRegistersInfo(CORINFO_CLASS_HANDLE structHnd) { - mc->cr->AddCall("getRISCV64PassStructInRegisterFlags"); - uint32_t temp = original_ICorJitInfo->getRISCV64PassStructInRegisterFlags(structHnd); - mc->recGetRISCV64PassStructInRegisterFlags(structHnd, temp); + mc->cr->AddCall("getRiscV64PassFpStructInRegistersInfo"); + FpStructInRegistersInfo temp = original_ICorJitInfo->getRiscV64PassFpStructInRegistersInfo(structHnd); + mc->recGetRiscV64PassFpStructInRegistersInfo(structHnd, temp); return temp; } diff --git a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp index 2d6faf2c657283..400b8a4cdbc8d7 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -1015,11 +1015,11 @@ uint32_t interceptor_ICJI::getLoongArch64PassStructInRegisterFlags( return original_ICorJitInfo->getLoongArch64PassStructInRegisterFlags(structHnd); } -uint32_t interceptor_ICJI::getRISCV64PassStructInRegisterFlags( +FpStructInRegistersInfo interceptor_ICJI::getRiscV64PassFpStructInRegistersInfo( CORINFO_CLASS_HANDLE structHnd) { - mcs->AddCall("getRISCV64PassStructInRegisterFlags"); - return original_ICorJitInfo->getRISCV64PassStructInRegisterFlags(structHnd); + mcs->AddCall("getRiscV64PassFpStructInRegistersInfo"); + return original_ICorJitInfo->getRiscV64PassFpStructInRegistersInfo(structHnd); } uint32_t interceptor_ICJI::getThreadTLSIndex( diff --git a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp index 210485e727c9bf..b9678e5170e09b 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -889,10 +889,10 @@ uint32_t interceptor_ICJI::getLoongArch64PassStructInRegisterFlags( return original_ICorJitInfo->getLoongArch64PassStructInRegisterFlags(structHnd); } -uint32_t interceptor_ICJI::getRISCV64PassStructInRegisterFlags( +FpStructInRegistersInfo interceptor_ICJI::getRiscV64PassFpStructInRegistersInfo( CORINFO_CLASS_HANDLE structHnd) { - return original_ICorJitInfo->getRISCV64PassStructInRegisterFlags(structHnd); + return original_ICorJitInfo->getRiscV64PassFpStructInRegistersInfo(structHnd); } uint32_t interceptor_ICJI::getThreadTLSIndex( diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index 9e29f43e03ec38..ecd7630d293c43 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1257,10 +1257,10 @@ uint32_t MyICJI::getLoongArch64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE st return jitInstance->mc->repGetLoongArch64PassStructInRegisterFlags(structHnd); } -uint32_t MyICJI::getRISCV64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE structHnd) +FpStructInRegistersInfo MyICJI::getRiscV64PassFpStructInRegistersInfo(CORINFO_CLASS_HANDLE structHnd) { - jitInstance->mc->cr->AddCall("getRISCV64PassStructInRegisterFlags"); - return jitInstance->mc->repGetRISCV64PassStructInRegisterFlags(structHnd); + jitInstance->mc->cr->AddCall("getRiscV64PassFpStructInRegistersInfo"); + return jitInstance->mc->repGetRiscV64PassFpStructInRegistersInfo(structHnd); } // Stuff on ICorDynamicInfo diff --git a/src/coreclr/vm/argdestination.h b/src/coreclr/vm/argdestination.h index 57df0326648167..4aeca6eb4c9870 100644 --- a/src/coreclr/vm/argdestination.h +++ b/src/coreclr/vm/argdestination.h @@ -106,72 +106,53 @@ class ArgDestination #endif // TARGET_RISCV64 _ASSERTE(IsStructPassedInRegs()); - _ASSERTE(fieldBytes <= 16); + _ASSERTE(destOffset == 0); + LOONGARCH64_ONLY(_ASSERTE(fieldBytes <= 16);) - int argOfs = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_argLocDescForStructInRegs->m_idxFloatReg * 8; + using namespace FpStruct; + FpStructInRegistersInfo info = m_argLocDescForStructInRegs->m_structFields; + _ASSERTE(m_argLocDescForStructInRegs->m_cFloatReg == ((info.flags & BothFloat) ? 2 : 1)); + _ASSERTE(m_argLocDescForStructInRegs->m_cGenReg == ((info.flags & (FloatInt | IntFloat)) ? 1 : 0)); + _ASSERTE(info.offset2nd + info.Size2nd() <= fieldBytes); - if (m_argLocDescForStructInRegs->m_structFields == STRUCT_FLOAT_FIELD_ONLY_TWO) - { // struct with two floats. - _ASSERTE(m_argLocDescForStructInRegs->m_cFloatReg == 2); - _ASSERTE(m_argLocDescForStructInRegs->m_cGenReg == 0); - *(INT64*)((char*)m_base + argOfs) = NanBox | *(INT32*)src; - *(INT64*)((char*)m_base + argOfs + 8) = NanBox | *((INT32*)src + 1); - } - else if ((m_argLocDescForStructInRegs->m_structFields & STRUCT_FLOAT_FIELD_FIRST) != 0) - { // the first field is float or double. - _ASSERTE(m_argLocDescForStructInRegs->m_cFloatReg == 1); - _ASSERTE(m_argLocDescForStructInRegs->m_cGenReg == 1); - _ASSERTE((m_argLocDescForStructInRegs->m_structFields & STRUCT_FLOAT_FIELD_SECOND) == 0);//the second field is integer. + int floatRegOffset = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + + m_argLocDescForStructInRegs->m_idxFloatReg * FLOAT_REGISTER_SIZE; + INT64* floatReg = (INT64*)((char*)m_base + floatRegOffset); - if ((m_argLocDescForStructInRegs->m_structFields & STRUCT_FIRST_FIELD_SIZE_IS8) == 0) - { - *(INT64*)((char*)m_base + argOfs) = NanBox | *(INT32*)src; // the first field is float - } - else - { - *(UINT64*)((char*)m_base + argOfs) = *(UINT64*)src; // the first field is double. - } + if (info.flags & (OnlyOne | BothFloat | FloatInt)) // copy first floating field + { + void* field = (char*)src + info.offset1st; + *floatReg++ = (info.SizeShift1st() == 3) ? *(INT64*)field : NanBox | *(INT32*)field; + } - argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_argLocDescForStructInRegs->m_idxGenReg * 8; - if ((m_argLocDescForStructInRegs->m_structFields & STRUCT_HAS_8BYTES_FIELDS_MASK) != 0) - { - *(UINT64*)((char*)m_base + argOfs) = *((UINT64*)src + 1); - } - else - { - *(INT64*)((char*)m_base + argOfs) = *((INT32*)src + 1); // the second field is int32. - } + if (info.flags & (BothFloat | IntFloat)) // copy second floating field + { + void* field = (char*)src + info.offset2nd; + *floatReg = (info.SizeShift2nd() == 3) ? *(INT64*)field : NanBox | *(INT32*)field; } - else if ((m_argLocDescForStructInRegs->m_structFields & STRUCT_FLOAT_FIELD_SECOND) != 0) - { // the second field is float or double. - _ASSERTE(m_argLocDescForStructInRegs->m_cFloatReg == 1); - _ASSERTE(m_argLocDescForStructInRegs->m_cGenReg == 1); - _ASSERTE((m_argLocDescForStructInRegs->m_structFields & STRUCT_FLOAT_FIELD_FIRST) == 0);//the first field is integer. - - // destOffset - nonzero when copying values into Nullable, it is the offset of the T value inside of the Nullable. - // here the first field maybe Nullable. - if ((m_argLocDescForStructInRegs->m_structFields & STRUCT_HAS_8BYTES_FIELDS_MASK) == 0) - { - // the second field is float. - *(INT64*)((char*)m_base + argOfs) = NanBox | (destOffset == 0 ? *((INT32*)src + 1) : *(INT32*)src); - } - else - { - // the second field is double. - *(UINT64*)((char*)m_base + argOfs) = destOffset == 0 ? *((UINT64*)src + 1) : *(UINT64*)src; - } - if (0 == destOffset) + if (info.flags & (FloatInt | IntFloat)) // copy integer field + { + int intRegOffset = TransitionBlock::GetOffsetOfArgumentRegisters() + + m_argLocDescForStructInRegs->m_idxGenReg * TARGET_POINTER_SIZE; + INT64* intReg = (INT64*)((char*)m_base + intRegOffset); + + void* field = (char*)src + ((info.flags & IntFloat) ? info.offset1st : info.offset2nd); + unsigned sizeShift = (info.flags & IntFloat) ? info.SizeShift1st() : info.SizeShift2nd(); + bool isSigned = (info.IntFieldKind() == FpStruct::IntKind::Signed); + switch ((sizeShift << 1) + isSigned) { - // NOTE: here ignoring the first size. - argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_argLocDescForStructInRegs->m_idxGenReg * 8; - *(UINT64*)((char*)m_base + argOfs) = *(UINT64*)src; + case (0 << 1) + 1: *intReg = *(INT8* )field; break; + case (1 << 1) + 1: *intReg = *(INT16*)field; break; + case (2 << 1) + 1: *intReg = *(INT32*)field; break; + case (3 << 1) + 1: *intReg = *(INT64*)field; break; + case (0 << 1) + 0: *intReg = *(UINT8* )field; break; + case (1 << 1) + 0: *intReg = *(UINT16*)field; break; + case (2 << 1) + 0: *intReg = *(UINT32*)field; break; + case (3 << 1) + 0: *intReg = *(UINT64*)field; break; + default: _ASSERTE(false); } } - else - { - _ASSERTE(!"---------UNReachable-------LoongArch64/RISC-V64!!!"); - } } #endif // !DACCESS_COMPILE diff --git a/src/coreclr/vm/callhelpers.cpp b/src/coreclr/vm/callhelpers.cpp index 3125c21ea3d13d..c4d38aa79c4364 100644 --- a/src/coreclr/vm/callhelpers.cpp +++ b/src/coreclr/vm/callhelpers.cpp @@ -158,6 +158,35 @@ void DispatchCallDebuggerWrapper( PAL_ENDTRY } +#if defined(TARGET_RISCV64) +void CopyReturnedFpStructFromRegisters(void* dest, UINT64 returnRegs[2], FpStructInRegistersInfo info, + bool handleGcRefs) +{ + _ASSERTE(info.flags != FpStruct::UseIntCallConv); + + // IntFloat is the only case where returnRegs[0] (fa0) represents the second field, not the first. + UINT64* returnField1st = &returnRegs[(info.flags & FpStruct::IntFloat) ? 1 : 0]; + UINT64* returnField2nd = &returnRegs[(info.flags & FpStruct::IntFloat) ? 0 : 1]; + + memcpyNoGCRefs((char*)dest + info.offset1st, returnField1st, info.Size1st()); + + if ((info.flags & FpStruct::OnlyOne) == 0) + { + char* field2ndDest = (char*)dest + info.offset2nd; + if (handleGcRefs && info.IntFieldKind() == FpStruct::IntKind::GcRef) + { + _ASSERTE(info.flags & (FpStruct::FloatInt | FpStruct::IntFloat)); + _ASSERTE(info.Size2nd() == TARGET_POINTER_SIZE); + memmoveGCRefs(field2ndDest, returnField2nd, TARGET_POINTER_SIZE); + } + else + { + memcpyNoGCRefs(field2ndDest, returnField2nd, info.Size2nd()); + } + } +} +#endif // TARGET_RISCV64 + // Helper for VM->managed calls with simple signatures. void * DispatchCallSimple( SIZE_T *pSrc, @@ -549,15 +578,27 @@ void MethodDescCallSite::CallTargetWorker(const ARG_SLOT *pArguments, ARG_SLOT * CallDescrWorkerWithHandler(&callDescrData); } +#ifdef FEATURE_HFA if (pvRetBuff != NULL) { memcpyNoGCRefs(pvRetBuff, &callDescrData.returnValue, sizeof(callDescrData.returnValue)); } +#endif // FEATURE_HFA if (pReturnValue != NULL) { - _ASSERTE((DWORD)cbReturnValue <= sizeof(callDescrData.returnValue)); - memcpyNoGCRefs(pReturnValue, &callDescrData.returnValue, cbReturnValue); + NOT_RISCV64(_ASSERTE((DWORD)cbReturnValue <= sizeof(callDescrData.returnValue));) +#if defined(TARGET_RISCV64) + if (callDescrData.fpReturnSize != FpStruct::UseIntCallConv) + { + FpStructInRegistersInfo info = m_argIt.GetReturnFpStructInRegistersInfo(); + CopyReturnedFpStructFromRegisters(pReturnValue, callDescrData.returnValue, info, false); + } + else +#endif // TARGET_RISCV64 + { + memcpyNoGCRefs(pReturnValue, &callDescrData.returnValue, cbReturnValue); + } #if !defined(HOST_64BIT) && BIGENDIAN { diff --git a/src/coreclr/vm/callhelpers.h b/src/coreclr/vm/callhelpers.h index f6dd105263b2f2..36e85e12c3f95e 100644 --- a/src/coreclr/vm/callhelpers.h +++ b/src/coreclr/vm/callhelpers.h @@ -73,6 +73,13 @@ void * DispatchCallSimple( PCODE pTargetAddress, DWORD dwDispatchCallSimpleFlags); +#if defined(TARGET_RISCV64) +// On RISC-V structs returned according to floating-point calling convention may be larger than 16 bytes. +// This routine copies such structs from 'returnRegs' containing fields (each in one register) as they are filled +// by CallDescrWorkerInternal, to the final destination 'dest' respecting the struct's layout described in 'info'. +void CopyReturnedFpStructFromRegisters(void* dest, UINT64 returnRegs[2], FpStructInRegistersInfo info, bool handleGcRefs); +#endif // TARGET_RISCV64 + bool IsCerRootMethod(MethodDesc *pMD); class MethodDescCallSite diff --git a/src/coreclr/vm/callingconvention.h b/src/coreclr/vm/callingconvention.h index 59a3e0eb6c8691..4a3c28104ff33d 100644 --- a/src/coreclr/vm/callingconvention.h +++ b/src/coreclr/vm/callingconvention.h @@ -42,7 +42,7 @@ struct ArgLocDesc int m_byteStackIndex; // Stack offset in bytes (or -1) int m_byteStackSize; // Stack size in bytes #if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - int m_structFields; // Struct field info when using Float-register except two-doubles case. + FpStructInRegistersInfo m_structFields; // Struct field info when using floating-point register(s) #endif #if defined(UNIX_AMD64_ABI) @@ -97,7 +97,7 @@ struct ArgLocDesc m_hfaFieldSize = 0; #endif // defined(TARGET_ARM64) #if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - m_structFields = STRUCT_NO_FLOAT_FIELD; + m_structFields = {}; #endif #if defined(UNIX_AMD64_ABI) m_eeClass = NULL; @@ -374,6 +374,10 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE { WRAPPER_NO_CONTRACT; m_dwFlags = 0; +#if defined(TARGET_RISCV64) + m_returnedFpFieldOffsets[0] = 0; + m_returnedFpFieldOffsets[1] = 0; +#endif } UINT SizeOfArgStack() @@ -436,6 +440,21 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE return m_dwFlags >> RETURN_FP_SIZE_SHIFT; } +#if defined(TARGET_RISCV64) + FpStructInRegistersInfo GetReturnFpStructInRegistersInfo() + { + WRAPPER_NO_CONTRACT; + if (!(m_dwFlags & RETURN_FLAGS_COMPUTED)) + ComputeReturnFlags(); + + return { + FpStruct::Flags(m_dwFlags >> RETURN_FP_SIZE_SHIFT), + m_returnedFpFieldOffsets[0], + m_returnedFpFieldOffsets[1], + }; + } +#endif // TARGET_RISCV64 + #ifdef TARGET_X86 //========================================================================= // Indicates whether an argument is to be put in a register using the @@ -534,7 +553,22 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE // Composites greater than 16 bytes are passed by reference return (size > ENREGISTERED_PARAMTYPE_MAXSIZE); #elif defined(TARGET_RISCV64) - return (size > ENREGISTERED_PARAMTYPE_MAXSIZE); + if (th.GetSignatureCorElementType() == ELEMENT_TYPE_VALUETYPE) + { + if (size <= ENREGISTERED_PARAMTYPE_MAXSIZE) + return FALSE; + + // Structs larger than 16 bytes can still be passed in registers according to FP call conv if it has empty + // fields or more padding, so make sure it's passed according to integer call conv which bounds structs + // passed by value to 16 bytes. + // + // Note: if it's larger than 16 bytes and elegible for passing according to FP call conv, it still does not + // mean it will not be passed by reference. We need to know if there's enough free registers, otherwise + // it will fall back to passing according to integer calling convention (by implicit reference). + // (see the non-static overload of IsArgPassedByRef()) + return MethodTable::GetRiscV64PassFpStructInRegistersInfo(th).flags == FpStruct::UseIntCallConv; + } + return FALSE; #else PORTABILITY_ASSERT("ArgIteratorTemplate::IsArgPassedByRef"); return FALSE; @@ -593,7 +627,10 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE if (m_argType == ELEMENT_TYPE_VALUETYPE) { _ASSERTE(!m_argTypeHandle.IsNull()); - return (m_argSize > ENREGISTERED_PARAMTYPE_MAXSIZE); + // On RISC-V structs larger than 16 bytes can still be passed in registers according to FP call conv if it + // has empty fields or more padding, so also check for m_hasArgLocDescForStructInRegs. + return (m_argSize > ENREGISTERED_PARAMTYPE_MAXSIZE) + RISCV64_ONLY(&& !m_hasArgLocDescForStructInRegs); } return FALSE; #else @@ -909,6 +946,11 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE protected: DWORD m_dwFlags; // Cached flags int m_nSizeOfArgStack; // Cached value of SizeOfArgStack +#if defined(TARGET_RISCV64) + // Offsets of fields returned according to hardware floating-point calling convention + // (FpStruct::Flags are embedded in m_dwFlags) + unsigned m_returnedFpFieldOffsets[2]; +#endif DWORD m_argNum; @@ -1790,121 +1832,108 @@ int ArgIteratorTemplate::GetNextOffset() return argOfs; #elif defined(TARGET_RISCV64) - + assert(!this->IsVarArg()); // Varargs on RISC-V not supported yet int cFPRegs = 0; - int flags = 0; + FpStructInRegistersInfo info = {}; switch (argType) { - case ELEMENT_TYPE_R4: - // 32-bit floating point argument. - cFPRegs = 1; - break; - case ELEMENT_TYPE_R8: - // 64-bit floating point argument. + // Floating point argument cFPRegs = 1; break; case ELEMENT_TYPE_VALUETYPE: - { - // Handle struct which containing floats or doubles that can be passed - // in FP registers if possible. - - // Composite greater than 16bytes should be passed by reference - if (argSize > ENREGISTERED_PARAMTYPE_MAXSIZE) + info = MethodTable::GetRiscV64PassFpStructInRegistersInfo(thValueType); + if (info.flags != FpStruct::UseIntCallConv) { - argSize = sizeof(TADDR); + cFPRegs = (info.flags & FpStruct::BothFloat) ? 2 : 1; } - else - { - flags = MethodTable::GetRiscV64PassStructInRegisterFlags(thValueType); - if (flags & STRUCT_HAS_FLOAT_FIELDS_MASK) - { - cFPRegs = (flags & STRUCT_FLOAT_FIELD_ONLY_TWO) ? 2 : 1; - } - } - break; - } default: break; } - const bool isValueType = (argType == ELEMENT_TYPE_VALUETYPE); - const bool isFloatHfa = thValueType.IsFloatHfa(); - const int cbArg = StackElemSize(argSize, isValueType, isFloatHfa); - - if (cFPRegs > 0 && !this->IsVarArg()) + if (cFPRegs > 0) { - if (flags & (STRUCT_FLOAT_FIELD_FIRST | STRUCT_FLOAT_FIELD_SECOND)) + // Pass according to hardware floating-point calling convention iff the argument can be fully enregistered + if (info.flags & (FpStruct::FloatInt | FpStruct::IntFloat)) { assert(cFPRegs == 1); - assert((STRUCT_FLOAT_FIELD_FIRST == (flags & STRUCT_HAS_FLOAT_FIELDS_MASK)) || (STRUCT_FLOAT_FIELD_SECOND == (flags & STRUCT_HAS_FLOAT_FIELDS_MASK))); - if ((1 + m_idxFPReg <= NUM_ARGUMENT_REGISTERS) && (m_idxGenReg + 1 <= NUM_ARGUMENT_REGISTERS)) + if ((1 + m_idxFPReg <= NUM_ARGUMENT_REGISTERS) && (1 + m_idxGenReg <= NUM_ARGUMENT_REGISTERS)) { m_argLocDescForStructInRegs.Init(); m_argLocDescForStructInRegs.m_idxFloatReg = m_idxFPReg; m_argLocDescForStructInRegs.m_cFloatReg = 1; - int argOfs = (flags & STRUCT_FLOAT_FIELD_SECOND) - ? TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * 8 - : TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_idxFPReg * 8; - m_idxFPReg += 1; - - m_argLocDescForStructInRegs.m_structFields = flags; m_argLocDescForStructInRegs.m_idxGenReg = m_idxGenReg; m_argLocDescForStructInRegs.m_cGenReg = 1; - m_idxGenReg += 1; + m_argLocDescForStructInRegs.m_structFields = info; m_hasArgLocDescForStructInRegs = true; - return argOfs; + int regOffset = (info.flags & FpStruct::IntFloat) + ? TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * TARGET_POINTER_SIZE + : TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_idxFPReg * FLOAT_REGISTER_SIZE; + m_idxFPReg++; + m_idxGenReg++; + return regOffset; } } else if (cFPRegs + m_idxFPReg <= NUM_ARGUMENT_REGISTERS) { - int argOfs = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_idxFPReg * 8; - if (flags == STRUCT_FLOAT_FIELD_ONLY_TWO) // struct with two float-fields. + int regOffset = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_idxFPReg * FLOAT_REGISTER_SIZE; + + if (info.flags & (FpStruct::BothFloat | FpStruct::OnlyOne)) { m_argLocDescForStructInRegs.Init(); - m_hasArgLocDescForStructInRegs = true; m_argLocDescForStructInRegs.m_idxFloatReg = m_idxFPReg; - assert(cFPRegs == 2); - m_argLocDescForStructInRegs.m_cFloatReg = 2; - assert(argSize == 8); - m_argLocDescForStructInRegs.m_structFields = STRUCT_FLOAT_FIELD_ONLY_TWO; + m_argLocDescForStructInRegs.m_cFloatReg = cFPRegs; + + m_argLocDescForStructInRegs.m_structFields = info; + m_hasArgLocDescForStructInRegs = true; } m_idxFPReg += cFPRegs; - return argOfs; + return regOffset; } } + // Pass according to integer calling convention + + if (argSize > ENREGISTERED_PARAMTYPE_MAXSIZE) + argSize = sizeof(TADDR); // pass by implicit reference + + bool isValueType = (argType == ELEMENT_TYPE_VALUETYPE); + int cbArg = StackElemSize(argSize, isValueType, false); + assert((cbArg % TARGET_POINTER_SIZE) == 0); + + int regSlots = ALIGN_UP(cbArg, TARGET_POINTER_SIZE) / TARGET_POINTER_SIZE; + assert(regSlots <= 2); + if (m_idxGenReg + regSlots <= NUM_ARGUMENT_REGISTERS) // pass in register(s) { - const int regSlots = ALIGN_UP(cbArg, TARGET_POINTER_SIZE) / TARGET_POINTER_SIZE; - if (m_idxGenReg + regSlots <= NUM_ARGUMENT_REGISTERS) - { - int argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * 8; - m_idxGenReg += regSlots; - return argOfs; - } - else if (m_idxGenReg < NUM_ARGUMENT_REGISTERS) - { - int argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * 8; - m_ofsStack += (m_idxGenReg + regSlots - NUM_ARGUMENT_REGISTERS)*8; - assert(m_ofsStack == 8); - m_idxGenReg = NUM_ARGUMENT_REGISTERS; - return argOfs; - } + int regOffset = TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * TARGET_POINTER_SIZE; + m_idxGenReg += regSlots; + return regOffset; } + else if (m_idxGenReg < NUM_ARGUMENT_REGISTERS) // pass split; head in register, tail on stack + { + assert(regSlots == 2); + assert(m_idxGenReg + 1 == NUM_ARGUMENT_REGISTERS); // last argument register should be free + assert((m_idxGenReg + regSlots - NUM_ARGUMENT_REGISTERS) == 1); // one stack slot needed + assert(m_ofsStack == 0); // tail of a split argument should be the first slot on the stack - int argOfs = TransitionBlock::GetOffsetOfArgs() + m_ofsStack; - m_ofsStack += ALIGN_UP(cbArg, TARGET_POINTER_SIZE); + int regOffset = TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * TARGET_POINTER_SIZE; + m_ofsStack = 8; + m_idxGenReg = NUM_ARGUMENT_REGISTERS; + return regOffset; + } - return argOfs; + int stackOffset = TransitionBlock::GetOffsetOfArgs() + m_ofsStack; // pass entirely on stack + m_ofsStack += ALIGN_UP(cbArg, TARGET_POINTER_SIZE); + return stackOffset; #else PORTABILITY_ASSERT("ArgIteratorTemplate::GetNextOffset"); return TransitionBlock::InvalidOffset; @@ -2028,12 +2057,14 @@ void ArgIteratorTemplate::ComputeReturnFlags() break; } #elif defined(TARGET_RISCV64) - if (size <= ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE) - { - assert(!thValueType.IsTypeDesc()); - flags = (MethodTable::GetRiscV64PassStructInRegisterFlags(thValueType) & 0xff) << RETURN_FP_SIZE_SHIFT; + assert(!thValueType.IsTypeDesc()); + FpStructInRegistersInfo info = MethodTable::GetRiscV64PassFpStructInRegistersInfo(thValueType); + flags |= info.flags << RETURN_FP_SIZE_SHIFT; + m_returnedFpFieldOffsets[0] = info.offset1st; + m_returnedFpFieldOffsets[1] = info.offset2nd; + + if (info.flags != FpStruct::UseIntCallConv || size <= ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE) break; - } #else if (size <= ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE) break; diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index 51d29277a383cd..ed5ddb8b544c4b 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -172,7 +172,7 @@ class ShuffleIterator if (m_currentFloatRegIndex < m_argLocDesc->m_cFloatReg) { #if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) - if ((m_argLocDesc->m_structFields & STRUCT_FLOAT_FIELD_SECOND) && (m_currentGenRegIndex < m_argLocDesc->m_cGenReg)) + if ((m_argLocDesc->m_structFields.flags & FpStruct::IntFloat) && (m_currentGenRegIndex < m_argLocDesc->m_cGenReg)) { // the first field is integer so just skip this. } diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 54ce75255eeaf6..388241b404a7ce 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -9486,7 +9486,7 @@ uint32_t CEEInfo::getLoongArch64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE c return size; } -uint32_t CEEInfo::getRISCV64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE cls) +FpStructInRegistersInfo CEEInfo::getRiscV64PassFpStructInRegistersInfo(CORINFO_CLASS_HANDLE cls) { CONTRACTL { NOTHROW; @@ -9496,15 +9496,15 @@ uint32_t CEEInfo::getRISCV64PassStructInRegisterFlags(CORINFO_CLASS_HANDLE cls) JIT_TO_EE_TRANSITION_LEAF(); - uint32_t size = STRUCT_NO_FLOAT_FIELD; + FpStructInRegistersInfo info = {}; #if defined(TARGET_RISCV64) - size = (uint32_t)MethodTable::GetRiscV64PassStructInRegisterFlags(TypeHandle(cls)); + info = MethodTable::GetRiscV64PassFpStructInRegistersInfo(TypeHandle(cls)); #endif // TARGET_RISCV64 EE_TO_JIT_TRANSITION_LEAF(); - return size; + return info; } /*********************************************************************/ diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index a3e9641bc73e1a..07b24bc1022173 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -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 now broken because of this. 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 } @@ -2716,11 +2733,14 @@ void MethodTable::AssignClassifiedEightByteTypes(SystemVStructRegisterPassingHe #endif // defined(UNIX_AMD64_ABI_ITF) #if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) -static bool HandleInlineArray(int elementTypeIndex, int nElements, StructFloatFieldInfoFlags types[2], int& typeIndex) +static bool HandleInlineArrayOld(int elementTypeIndex, int nElements, StructFloatFieldInfoFlags types[2], int& typeIndex) { int nFlattenedFieldsPerElement = typeIndex - elementTypeIndex; if (nFlattenedFieldsPerElement == 0) - return true; + { + assert(nElements == 1); // HasImpliedRepeatedFields must have returned a false positive + return true; // ignoring empty struct + } assert(nFlattenedFieldsPerElement == 1 || nFlattenedFieldsPerElement == 2); @@ -2739,7 +2759,7 @@ static bool HandleInlineArray(int elementTypeIndex, int nElements, StructFloatFi return true; } -static bool FlattenFieldTypes(TypeHandle th, StructFloatFieldInfoFlags types[2], int& typeIndex) +static bool FlattenFieldTypesOld(TypeHandle th, StructFloatFieldInfoFlags types[2], int& typeIndex) { bool isManaged = !th.IsTypeDesc(); MethodTable* pMT = isManaged ? th.AsMethodTable() : th.AsNativeValueType(); @@ -2760,7 +2780,7 @@ static bool FlattenFieldTypes(TypeHandle th, StructFloatFieldInfoFlags types[2], if (type == ELEMENT_TYPE_VALUETYPE) { MethodTable* nested = fields[i].GetApproxFieldTypeHandleThrowing().GetMethodTable(); - if (!FlattenFieldTypes(TypeHandle(nested), types, typeIndex)) + if (!FlattenFieldTypesOld(TypeHandle(nested), types, typeIndex)) return false; } else if (fields[i].GetSize() <= TARGET_POINTER_SIZE) @@ -2783,7 +2803,15 @@ static bool FlattenFieldTypes(TypeHandle th, StructFloatFieldInfoFlags types[2], { assert(nFields == 1); int nElements = pMT->GetNumInstanceFieldBytes() / fields[0].GetSize(); - if (!HandleInlineArray(elementTypeIndex, nElements, types, typeIndex)) + + // Only InlineArrays can have an element type of an empty struct, fixed-size buffers take only primitives + if ((typeIndex - elementTypeIndex) == 0 && pMT->GetClass()->IsInlineArray()) + { + assert(nElements > 0); + return false; + } + + if (!HandleInlineArrayOld(elementTypeIndex, nElements, types, typeIndex)) return false; } } @@ -2801,12 +2829,12 @@ static bool FlattenFieldTypes(TypeHandle th, StructFloatFieldInfoFlags types[2], int elementTypeIndex = typeIndex; MethodTable* nested = fields[i].GetNestedNativeMethodTable(); - if (!FlattenFieldTypes(TypeHandle(nested), types, typeIndex)) + if (!FlattenFieldTypesOld(TypeHandle(nested), types, typeIndex)) return false; // In native layout fixed arrays are marked as NESTED just like structs int nElements = fields[i].GetNumElements(); - if (!HandleInlineArray(elementTypeIndex, nElements, types, typeIndex)) + if (!HandleInlineArrayOld(elementTypeIndex, nElements, types, typeIndex)) return false; } else if (fields[i].NativeSize() <= TARGET_POINTER_SIZE) @@ -2837,7 +2865,7 @@ int MethodTable::GetLoongArch64PassStructInRegisterFlags(TypeHandle th) StructFloatFieldInfoFlags types[2] = {STRUCT_NO_FLOAT_FIELD, STRUCT_NO_FLOAT_FIELD}; int nFields = 0; - if (!FlattenFieldTypes(th, types, nFields) || nFields == 0) + if (!FlattenFieldTypesOld(th, types, nFields) || nFields == 0) return STRUCT_NO_FLOAT_FIELD; assert(nFields == 1 || nFields == 2); @@ -2867,14 +2895,11 @@ int MethodTable::GetLoongArch64PassStructInRegisterFlags(TypeHandle th) #endif #if defined(TARGET_RISCV64) -int MethodTable::GetRiscV64PassStructInRegisterFlags(TypeHandle th) +static int 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) + if (!FlattenFieldTypesOld(th, types, nFields) || nFields == 0) return STRUCT_NO_FLOAT_FIELD; assert(nFields == 1 || nFields == 2); @@ -2903,6 +2928,311 @@ int MethodTable::GetRiscV64PassStructInRegisterFlags(TypeHandle th) } #endif +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) +static void SetFpStructInRegistersInfoField(FpStructInRegistersInfo& info, int index, + bool isFloating, FpStruct::IntKind intKind, unsigned size, uint32_t offset) +{ + assert(index < 2); + if (isFloating) + { + assert(size == sizeof(float) || size == sizeof(double)); + assert(intKind == FpStruct::IntKind::Signed); + static_assert((int)FpStruct::IntKind::Signed == 0, + "IntKind for floating fields should not clobber IntKind for int fields"); + } + + assert(size >= 1 && size <= 8); + assert((size & (size - 1)) == 0); // size needs to be a power of 2 + static const int sizeShiftLUT = (0 << (1*2)) | (1 << (2*2)) | (2 << (4*2)) | (3 << (8*2)); + int sizeShift = (sizeShiftLUT >> (size * 2)) & 0b11; + + using namespace FpStruct; + // Use FloatInt and IntFloat as marker flags for 1st and 2nd field respectively being floating. + // Fix to real flags (with OnlyOne and BothFloat) after flattening is complete. + static_assert(PosIntFloat == PosFloatInt + 1, "FloatInt and IntFloat need to be adjacent"); + static_assert(PosSizeShift2nd == PosSizeShift1st + 2, "SizeShift1st and 2nd need to be adjacent"); + int floatFlag = isFloating << (PosFloatInt + index); + int sizeShiftMask = sizeShift << (PosSizeShift1st + 2 * index); + + info.flags = FpStruct::Flags(info.flags | floatFlag | sizeShiftMask | ((int)intKind << PosIntFieldKind)); + (index == 0 ? info.offset1st : info.offset2nd) = offset; +} + +static bool HandleInlineArray(int elementTypeIndex, int nElements, FpStructInRegistersInfo& info, int& typeIndex + DEBUG_ARG(int nestingLevel)) +{ + int nFlattenedFieldsPerElement = typeIndex - elementTypeIndex; + if (nFlattenedFieldsPerElement == 0) + { + assert(nElements == 1); // HasImpliedRepeatedFields must have returned a false positive + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s * ignoring empty struct\n", + nestingLevel * 4, "")); + return true; + } + + assert(nFlattenedFieldsPerElement == 1 || nFlattenedFieldsPerElement == 2); + + if (nElements > 2) + { + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s * array has too many elements: %i\n", + nestingLevel * 4, "", nElements)); + return false; + } + + if (nElements == 2) + { + if (typeIndex + nFlattenedFieldsPerElement > 2) + { + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s * array has too many fields per element: %i, fields already found: %i\n", + nestingLevel * 4, "", nFlattenedFieldsPerElement, typeIndex)); + return false; + } + + assert(elementTypeIndex == 0); + assert(typeIndex == 1); + + // duplicate the array element info + static const int typeSize = FpStruct::PosIntFloat - FpStruct::PosFloatInt; + info.flags = FpStruct::Flags(info.flags | (info.flags << typeSize)); + info.offset2nd = info.offset1st + info.Size1st(); + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s * duplicated array element type\n", + nestingLevel * 4, "")); + } + return true; +} + +static bool FlattenFields(TypeHandle th, uint32_t offset, FpStructInRegistersInfo& info, int& typeIndex + DEBUG_ARG(int nestingLevel)) +{ + bool isManaged = !th.IsTypeDesc(); + MethodTable* pMT = isManaged ? th.AsMethodTable() : th.AsNativeValueType(); + int nFields = isManaged ? pMT->GetNumIntroducedInstanceFields() : pMT->GetNativeLayoutInfo()->GetNumFields(); + + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s flattening %s (%s, %i fields)\n", + nestingLevel * 4, "", pMT->GetDebugClassName(), (isManaged ? "managed" : "native"), nFields)); + + // TODO: templatize isManaged and use if constexpr for differences when we migrate to C++17 + // because the logic for both branches is nearly the same. + if (isManaged) + { + FieldDesc* fields = pMT->GetApproxFieldDescListRaw(); + int elementTypeIndex = typeIndex; + for (int i = 0; i < nFields; ++i) + { + if (i > 0 && fields[i-1].GetOffset() + fields[i-1].GetSize() > fields[i].GetOffset()) + { + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s " + " * fields %s [%i..%i) and %s [%i..%i) overlap, treat as union\n", + nestingLevel * 4, "", + fields[i-1].GetDebugName(), fields[i-1].GetOffset(), fields[i-1].GetOffset() + fields[i-1].GetSize(), + fields[i].GetDebugName(), fields[i].GetOffset(), fields[i].GetOffset() + fields[i].GetSize())); + return false; + } + + CorElementType type = fields[i].GetFieldType(); + if (type == ELEMENT_TYPE_VALUETYPE) + { + MethodTable* nested = fields[i].GetApproxFieldTypeHandleThrowing().GetMethodTable(); + if (!FlattenFields(TypeHandle(nested), offset + fields[i].GetOffset(), info, typeIndex DEBUG_ARG(nestingLevel + 1))) + return false; + } + else if (fields[i].GetSize() <= TARGET_POINTER_SIZE) + { + if (typeIndex >= 2) + { + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s * too many fields\n", + nestingLevel * 4, "")); + return false; + } + + bool isFloating = CorTypeInfo::IsFloat_NoThrow(type); + bool isSignedInt = ( + type == ELEMENT_TYPE_I1 || + type == ELEMENT_TYPE_I2 || + type == ELEMENT_TYPE_I4 || + type == ELEMENT_TYPE_I8 || + type == ELEMENT_TYPE_I); + CorInfoGCType gcType = CorTypeInfo::GetGCType_NoThrow(type); + + FpStruct::IntKind intKind = + (gcType == TYPE_GC_REF) ? FpStruct::IntKind::GcRef : + (gcType == TYPE_GC_BYREF) ? FpStruct::IntKind::GcByRef : + (isSignedInt || isFloating) ? FpStruct::IntKind::Signed : FpStruct::IntKind::Unsigned; + + SetFpStructInRegistersInfoField(info, typeIndex++, + isFloating, intKind, CorTypeInfo::Size_NoThrow(type), offset + fields[i].GetOffset()); + + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s * found field %s [%i..%i), type: %s\n", + nestingLevel * 4, "", fields[i].GetDebugName(), + fields[i].GetOffset(), fields[i].GetOffset() + fields[i].GetSize(), CorTypeInfo::GetName(type))); + } + else + { + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s " + " * field %s, type: %s, is too big (%i bytes)\n", + nestingLevel * 4, "", fields[i].GetDebugName(), + CorTypeInfo::GetName(type), fields[i].GetSize())); + return false; + } + } + + if (HasImpliedRepeatedFields(pMT)) // inline array or fixed buffer + { + assert(nFields == 1); + int nElements = pMT->GetNumInstanceFieldBytes() / fields[0].GetSize(); + + // Only InlineArrays can have an element type of an empty struct, fixed-size buffers take only primitives + if ((typeIndex - elementTypeIndex) == 0 && pMT->GetClass()->IsInlineArray()) + { + assert(nElements > 0); // InlineArray length must be > 0 + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s " + " * struct %s containing a %i-element array of empty structs %s is passed by integer calling convention\n", + nestingLevel * 4, "", pMT->GetDebugClassName(), nElements, fields[0].GetDebugName())); + return false; + } + + if (!HandleInlineArray(elementTypeIndex, nElements, info, typeIndex DEBUG_ARG(nestingLevel + 1))) + return false; + } + } + else // native layout + { + const NativeFieldDescriptor* fields = pMT->GetNativeLayoutInfo()->GetNativeFieldDescriptors(); + for (int i = 0; i < nFields; ++i) + { + if (i > 0 && fields[i-1].GetExternalOffset() + fields[i-1].NativeSize() > fields[i].GetExternalOffset()) + { + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s " + " * fields %s [%i..%i) and %s [%i..%i) overlap, treat as union\n", + nestingLevel * 4, "", + fields[i-1].GetFieldDesc()->GetDebugName(), fields[i-1].GetExternalOffset(), fields[i-1].GetExternalOffset() + fields[i-1].NativeSize(), + fields[i].GetFieldDesc()->GetDebugName(), fields[i].GetExternalOffset(), fields[i].GetExternalOffset() + fields[i].NativeSize())); + return false; + } + + static const char* categoryNames[] = {"FLOAT", "NESTED", "INTEGER", "ILLEGAL"}; + NativeFieldCategory category = fields[i].GetCategory(); + if (category == NativeFieldCategory::NESTED) + { + int elementTypeIndex = typeIndex; + + MethodTable* nested = fields[i].GetNestedNativeMethodTable(); + if (!FlattenFields(TypeHandle(nested), offset + fields[i].GetExternalOffset(), info, typeIndex DEBUG_ARG(nestingLevel + 1))) + return false; + + // In native layout fixed arrays are marked as NESTED just like structs + int nElements = fields[i].GetNumElements(); + if (!HandleInlineArray(elementTypeIndex, nElements, info, typeIndex DEBUG_ARG(nestingLevel + 1))) + return false; + } + else if (fields[i].NativeSize() <= TARGET_POINTER_SIZE) + { + if (typeIndex >= 2) + { + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s * too many fields\n", + nestingLevel * 4, "")); + return false; + } + + SetFpStructInRegistersInfoField(info, typeIndex++, + (category == NativeFieldCategory::FLOAT), + FpStruct::IntKind::Signed, // NativeFieldDescriptor doesn't save signedness, TODO: should it? + fields[i].NativeSize(), + offset + fields[i].GetExternalOffset()); + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s * found field %s [%i..%i), type: %s\n", + nestingLevel * 4, "", fields[i].GetFieldDesc()->GetDebugName(), + fields[i].GetExternalOffset(), fields[i].GetExternalOffset() + fields[i].NativeSize(), categoryNames[(int)category])); + } + else + { + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo:%*s " + " * field %s, type: %s, is too big (%i bytes)\n", + nestingLevel * 4, "", fields[i].GetFieldDesc()->GetDebugName(), + categoryNames[(int)category], fields[i].NativeSize())); + return false; + } + } + } + return true; +} +#endif + +#if defined(TARGET_RISCV64) + +static FpStructInRegistersInfo GetRiscV64PassFpStructInRegistersInfoImpl(TypeHandle th) +{ + FpStructInRegistersInfo info = {}; + int nFields = 0; + if (!FlattenFields(th, 0, info, nFields DEBUG_ARG(0))) + return FpStructInRegistersInfo{}; + + using namespace FpStruct; + if ((info.flags & (FloatInt | IntFloat)) == 0) + { + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo: struct %s (%u bytes) has no floating fields\n", + (!th.IsTypeDesc() ? th.AsMethodTable() : th.AsNativeValueType())->GetDebugClassName(), th.GetSize())); + return FpStructInRegistersInfo{}; + } + + assert(nFields == 1 || nFields == 2); + + if ((info.flags & (FloatInt | IntFloat)) == (FloatInt | IntFloat)) + { + assert(nFields == 2); + info.flags = FpStruct::Flags(info.flags ^ (FloatInt | IntFloat | BothFloat)); // replace (FloatInt | IntFloat) with BothFloat + } + else if (nFields == 1) + { + assert((info.flags & FloatInt) != 0); + assert((info.flags & (IntFloat | SizeShift2ndMask)) == 0); + assert(info.offset2nd == 0); + info.flags = FpStruct::Flags(info.flags ^ (FloatInt | OnlyOne)); // replace FloatInt with OnlyOne + } + assert(nFields == ((info.flags & OnlyOne) != 0 ? 1 : 2)); + int floatFlags = info.flags & (OnlyOne | BothFloat | FloatInt | IntFloat); + assert(floatFlags != 0); + assert((floatFlags & (floatFlags - 1)) == 0); // there can be only one of (OnlyOne | BothFloat | FloatInt | IntFloat) + if (nFields == 2) + { + unsigned end1st = info.offset1st + info.Size1st(); + unsigned end2nd = info.offset2nd + info.Size2nd(); + assert(end1st <= info.offset2nd || end2nd <= info.offset1st); // fields must not overlap + } + assert(info.offset1st + info.Size1st() <= th.GetSize()); + assert(info.offset2nd + info.Size2nd() <= th.GetSize()); + if (info.IntFieldKind() != FpStruct::IntKind::Signed) + { + assert(info.flags & (FloatInt | IntFloat)); + if (info.IntFieldKind() >= FpStruct::IntKind::GcRef) + { + assert((info.flags & IntFloat) != 0 + ? ((info.SizeShift1st() == 3) && IS_ALIGNED(info.offset1st, TARGET_POINTER_SIZE)) + : ((info.SizeShift2nd() == 3) && IS_ALIGNED(info.offset2nd, TARGET_POINTER_SIZE))); + } + } + if (info.flags & (OnlyOne | BothFloat)) + assert(info.IntFieldKind() == FpStruct::IntKind::Signed); + + LOG((LF_JIT, LL_EVERYTHING, "FpStructInRegistersInfo: " + "struct %s (%u bytes) can be passed with floating-point calling convention, flags=%#03x; " + "%s, sizes={%u, %u}, offsets={%u, %u}, IntFieldKindMask=%s\n", + (!th.IsTypeDesc() ? th.AsMethodTable() : th.AsNativeValueType())->GetDebugClassName(), th.GetSize(), info.flags, + info.FlagName(), info.Size1st(), info.Size2nd(), info.offset1st, info.offset2nd, info.IntFieldKindName() + )); + return info; +} + +FpStructInRegistersInfo MethodTable::GetRiscV64PassFpStructInRegistersInfo(TypeHandle th) +{ + FpStructInRegistersInfo info = GetRiscV64PassFpStructInRegistersInfoImpl(th); + int flags = GetRiscV64PassStructInRegisterFlags(th); + + assert(flags == info.ToOldFlags()); + + return info; +} +#endif // TARGET_RISCV64 + #if !defined(DACCESS_COMPILE) namespace { diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index be663a43067ff6..44d2ce7c9c37a7 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -1014,7 +1014,7 @@ class MethodTable #if defined(TARGET_LOONGARCH64) static int GetLoongArch64PassStructInRegisterFlags(TypeHandle th); #elif defined(TARGET_RISCV64) - static int GetRiscV64PassStructInRegisterFlags(TypeHandle th); + static FpStructInRegistersInfo GetRiscV64PassFpStructInRegistersInfo(TypeHandle th); #endif #if defined(UNIX_AMD64_ABI_ITF) diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index a98dafd62a558b..a08f65aa0cde94 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -632,7 +632,6 @@ FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, UINT structSize = argit.GetArgSize(); - bool needsStackCopy = false; ArgDestination argDest(pTransitionBlock, ofs, argit.GetArgLocDescForStructInRegs()); #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE @@ -704,7 +703,17 @@ FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, // we have allocated for this purpose. else if (!fHasRetBuffArg) { - CopyValueClass(gc.retVal->GetData(), &callDescrData.returnValue, gc.retVal->GetMethodTable()); +#if defined(TARGET_RISCV64) + if (callDescrData.fpReturnSize != FpStruct::UseIntCallConv) + { + FpStructInRegistersInfo info = argit.GetReturnFpStructInRegistersInfo(); + CopyReturnedFpStructFromRegisters(gc.retVal->GetData(), callDescrData.returnValue, info, true); + } + else +#endif // TARGET_RISCV64 + { + CopyValueClass(gc.retVal->GetData(), &callDescrData.returnValue, gc.retVal->GetMethodTable()); + } } // From here on out, it is OK to have GCs since the return object (which may have had // GC pointers has been put into a GC object and thus protected. diff --git a/src/coreclr/vm/riscv64/asmconstants.h b/src/coreclr/vm/riscv64/asmconstants.h index 0ba094537ad3e4..a2fb19faef15d8 100644 --- a/src/coreclr/vm/riscv64/asmconstants.h +++ b/src/coreclr/vm/riscv64/asmconstants.h @@ -66,35 +66,8 @@ ASMCONSTANTS_C_ASSERT(CallDescrData__fpReturnSize == offsetof(CallDescrD ASMCONSTANTS_C_ASSERT(CallDescrData__pTarget == offsetof(CallDescrData, pTarget)) ASMCONSTANTS_C_ASSERT(CallDescrData__returnValue == offsetof(CallDescrData, returnValue)) -#define CallDescrData__flagOneFloat 0x1 -#define CallDescrData__flagOneDouble 0x11 -#define CallDescrData__flagFloatInt 0x2 -#define CallDescrData__flagFloatLong 0x22 -#define CallDescrData__flagDoubleInt 0x12 -#define CallDescrData__flagDoubleLong 0x32 -#define CallDescrData__flagIntFloat 0x4 -#define CallDescrData__flagIntDouble 0x24 -#define CallDescrData__flagLongFloat 0x14 -#define CallDescrData__flagLongDouble 0x34 -#define CallDescrData__flagFloatFloat 0x8 -#define CallDescrData__flagFloatDouble 0x28 -#define CallDescrData__flagDoubleFloat 0x18 -#define CallDescrData__flagDoubleDouble 0x38 - -ASMCONSTANTS_C_ASSERT(CallDescrData__flagOneFloat == (int)STRUCT_FLOAT_FIELD_ONLY_ONE) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagOneDouble == (int)(STRUCT_FLOAT_FIELD_ONLY_ONE | STRUCT_FIRST_FIELD_SIZE_IS8)) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagFloatInt == (int)STRUCT_FLOAT_FIELD_FIRST) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagFloatLong == (int)(STRUCT_FLOAT_FIELD_FIRST | STRUCT_SECOND_FIELD_SIZE_IS8)) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagDoubleInt == (int)(STRUCT_FLOAT_FIELD_FIRST | STRUCT_FIRST_FIELD_SIZE_IS8)) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagDoubleLong == (int)(CallDescrData__flagDoubleInt | STRUCT_SECOND_FIELD_SIZE_IS8)) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagIntFloat == (int)STRUCT_FLOAT_FIELD_SECOND) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagIntDouble == (int)(STRUCT_FLOAT_FIELD_SECOND | STRUCT_SECOND_FIELD_SIZE_IS8)) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagLongFloat == (int)(STRUCT_FLOAT_FIELD_SECOND | STRUCT_FIRST_FIELD_SIZE_IS8)) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagLongDouble == (int)(CallDescrData__flagLongFloat | STRUCT_SECOND_FIELD_SIZE_IS8)) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagFloatFloat == (int)STRUCT_FLOAT_FIELD_ONLY_TWO) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagFloatDouble == (int)(STRUCT_FLOAT_FIELD_ONLY_TWO | STRUCT_SECOND_FIELD_SIZE_IS8)) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagDoubleFloat == (int)(STRUCT_FLOAT_FIELD_ONLY_TWO | STRUCT_FIRST_FIELD_SIZE_IS8)) -ASMCONSTANTS_C_ASSERT(CallDescrData__flagDoubleDouble == (int)(CallDescrData__flagDoubleFloat | STRUCT_SECOND_FIELD_SIZE_IS8)) +#define FpStruct__BothFloat 0b10 +ASMCONSTANTS_C_ASSERT(FpStruct__BothFloat == (int)FpStruct::BothFloat) #define CORINFO_NullReferenceException_ASM 0 ASMCONSTANTS_C_ASSERT( CORINFO_NullReferenceException_ASM diff --git a/src/coreclr/vm/riscv64/calldescrworkerriscv64.S b/src/coreclr/vm/riscv64/calldescrworkerriscv64.S index 2139787bee1490..607c523d66e18a 100644 --- a/src/coreclr/vm/riscv64/calldescrworkerriscv64.S +++ b/src/coreclr/vm/riscv64/calldescrworkerriscv64.S @@ -74,131 +74,34 @@ LOCAL_LABEL(CallDescrWorkerInternalReturnAddress): lw a3, CallDescrData__fpReturnSize(s1) - // Int return case beq a3, zero, LOCAL_LABEL(IntReturn) - // Struct with Float/Double field return case. - ori t4, zero, CallDescrData__flagOneFloat - beq t4, a3, LOCAL_LABEL(FloatReturn) + // Save struct returned according to hardware floating-point calling convention - ori t4, zero, CallDescrData__flagOneDouble - beq t4, a3, LOCAL_LABEL(DoubleReturn) + // Note: due to padding such structs may be larger than sizeof(CallDescrData::returnValue) (16 bytes) + // so just save the returned registers and let the routine copying the struct to the final memory location + // (CopyReturnedFpStructFromRegisters) worry about placing the fields as they were originally laid out. - ori t4, zero, CallDescrData__flagFloatInt - beq t4, a3, LOCAL_LABEL(FloatIntReturn) + fsd fa0, CallDescrData__returnValue(s1) // we have at least one floating field in fa0 - ori t4, zero, CallDescrData__flagDoubleInt - beq t4, a3, LOCAL_LABEL(DoubleIntReturn) + andi a3, a3, FpStruct__BothFloat + bne a3, zero, LOCAL_LABEL(SecondFieldFloatReturn) - ori t4, zero, CallDescrData__flagFloatLong - beq t4, a3, LOCAL_LABEL(FloatLongReturn) - - ori t4, zero, CallDescrData__flagDoubleLong - beq t4, a3, LOCAL_LABEL(DoubleLongReturn) - - ori t4, zero, CallDescrData__flagIntFloat - beq t4, a3, LOCAL_LABEL(IntFloatReturn) - - ori t4, zero, CallDescrData__flagLongFloat - beq t4, a3, LOCAL_LABEL(LongFloatReturn) - - ori t4, zero, CallDescrData__flagIntDouble - beq t4, a3, LOCAL_LABEL(IntDoubleReturn) - - ori t4, zero, CallDescrData__flagLongDouble - beq t4, a3, LOCAL_LABEL(LongDoubleReturn) - - ori t4, zero, CallDescrData__flagFloatFloat - beq t4, a3, LOCAL_LABEL(FloatFloatReturn) - - ori t4, zero, CallDescrData__flagDoubleFloat - beq t4, a3, LOCAL_LABEL(DoubleFloatReturn) - - ori t4, zero, CallDescrData__flagFloatDouble - beq t4, a3, LOCAL_LABEL(FloatDoubleReturn) - - ori t4, zero, CallDescrData__flagDoubleDouble - beq t4, a3, LOCAL_LABEL(DoubleDoubleReturn) - -LOCAL_LABEL(NotCorrectReturn): - sw ra, 0(zero) - EMIT_BREAKPOINT // Unreachable - -LOCAL_LABEL(FloatReturn): - fsw fa0, CallDescrData__returnValue(s1) - j LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(DoubleReturn): - fsd fa0, CallDescrData__returnValue(s1) - j LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(FloatIntReturn): - fsw fa0, CallDescrData__returnValue(s1) - sw a0, (CallDescrData__returnValue + 4)(s1) - j LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(DoubleIntReturn): - fsd fa0, CallDescrData__returnValue(s1) - sw a0, (CallDescrData__returnValue + 8)(s1) - j LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(FloatLongReturn): - fsw fa0, CallDescrData__returnValue(s1) - sd a0, (CallDescrData__returnValue + 8)(s1) + // The second returned field is integer (FpStruct::Float1st | FpStruct::Float2nd) + // Note: will go in here also for FpStruct::OnlyOne but storing a register of trash is probably faster than branching + sd a0, (CallDescrData__returnValue + 8)(s1) j LOCAL_LABEL(ReturnDone) -LOCAL_LABEL(DoubleLongReturn): - fsd fa0, CallDescrData__returnValue(s1) - sd a0, (CallDescrData__returnValue + 8)(s1) - j LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(IntFloatReturn): - sw a0, CallDescrData__returnValue(s1) - fsw fa0, (CallDescrData__returnValue + 4)(s1) - j LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(LongFloatReturn): - sd a0, CallDescrData__returnValue(s1) - fsw fa0, (CallDescrData__returnValue + 8)(s1) - j LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(IntDoubleReturn): - sw a0, CallDescrData__returnValue(s1) - fsd fa0, (CallDescrData__returnValue + 8)(s1) - j LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(LongDoubleReturn): - sd a0, CallDescrData__returnValue(s1) - fsd fa0, (CallDescrData__returnValue + 8)(s1) - j LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(FloatFloatReturn): - fsw fa0, CallDescrData__returnValue(s1) - fsw fa1, (CallDescrData__returnValue + 4)(s1) - j LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(DoubleFloatReturn): - fsd fa0, CallDescrData__returnValue(s1) - fsw fa1, (CallDescrData__returnValue + 8)(s1) - j LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(FloatDoubleReturn): - fsw fa0, CallDescrData__returnValue(s1) - fsd fa1, (CallDescrData__returnValue + 8)(s1) - j LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(DoubleDoubleReturn): - fsd fa0, CallDescrData__returnValue(s1) - fsd fa1, (CallDescrData__returnValue + 8)(s1) +LOCAL_LABEL(SecondFieldFloatReturn): + fsd fa1, (CallDescrData__returnValue + 8)(s1) j LOCAL_LABEL(ReturnDone) LOCAL_LABEL(IntReturn): - // Save return value into retbuf for int + // Save struct returned according to integer calling convention sd a0, CallDescrData__returnValue(s1) sd a1, (CallDescrData__returnValue + 8)(s1) LOCAL_LABEL(ReturnDone): - EPILOG_STACK_RESTORE EPILOG_RESTORE_REG s1, 16 EPILOG_RESTORE_REG_PAIR_INDEXED fp, ra, 0x20 diff --git a/src/coreclr/vm/riscv64/profiler.cpp b/src/coreclr/vm/riscv64/profiler.cpp index fc8eff48456388..785344d0437689 100644 --- a/src/coreclr/vm/riscv64/profiler.cpp +++ b/src/coreclr/vm/riscv64/profiler.cpp @@ -111,13 +111,14 @@ LPVOID ProfileArgIterator::CopyStructFromRegisters(const ArgLocDesc* sir) _ASSERTE(m_handle); PROFILE_PLATFORM_SPECIFIC_DATA* pData = reinterpret_cast(m_handle); + StructFloatFieldInfoFlags flags = sir->m_structFields.ToOldFlags(); struct { bool isFloat, is8; } fields[] = { - { (bool) (sir->m_structFields & (STRUCT_FLOAT_FIELD_FIRST | STRUCT_FLOAT_FIELD_ONLY_TWO | STRUCT_FLOAT_FIELD_ONLY_ONE)), - (bool) (sir->m_structFields & STRUCT_FIRST_FIELD_SIZE_IS8) }, - { (bool) (sir->m_structFields & (STRUCT_FLOAT_FIELD_SECOND | STRUCT_FLOAT_FIELD_ONLY_TWO)), - (bool) (sir->m_structFields & STRUCT_SECOND_FIELD_SIZE_IS8) }, + { (bool) (flags & (STRUCT_FLOAT_FIELD_FIRST | STRUCT_FLOAT_FIELD_ONLY_TWO | STRUCT_FLOAT_FIELD_ONLY_ONE)), + (bool) (flags & STRUCT_FIRST_FIELD_SIZE_IS8) }, + { (bool) (flags & (STRUCT_FLOAT_FIELD_SECOND | STRUCT_FLOAT_FIELD_ONLY_TWO)), + (bool) (flags & STRUCT_SECOND_FIELD_SIZE_IS8) }, }; - int fieldCount = (sir->m_structFields & STRUCT_FLOAT_FIELD_ONLY_ONE) ? 1 : 2; + int fieldCount = (flags & STRUCT_FLOAT_FIELD_ONLY_ONE) ? 1 : 2; UINT64 bufferPosBegin = m_bufferPos; const double *fRegBegin = &pData->floatArgumentRegisters.f[sir->m_idxFloatReg], *fReg = fRegBegin; const double *fRegEnd = &pData->floatArgumentRegisters.f[0] + NUM_FLOAT_ARGUMENT_REGISTERS; @@ -185,7 +186,7 @@ LPVOID ProfileArgIterator::GetNextArgAddr() // If both fields are in registers of same kind (either float or general) and both are 8 bytes, no need to copy. // We can get away with returning a ptr to argumentRegisters since the struct would have the same layout. if ((sir->m_cFloatReg ^ sir->m_cGenReg) != 2 || - (sir->m_structFields & STRUCT_HAS_8BYTES_FIELDS_MASK) != STRUCT_HAS_8BYTES_FIELDS_MASK) + (sir->m_structFields.ToOldFlags() & STRUCT_HAS_8BYTES_FIELDS_MASK) != STRUCT_HAS_8BYTES_FIELDS_MASK) { return CopyStructFromRegisters(sir); } @@ -317,7 +318,7 @@ LPVOID ProfileArgIterator::GetReturnBufferAddr(void) sir.m_cGenReg = -1; sir.m_byteStackIndex = 0; sir.m_byteStackSize = -1; - sir.m_structFields = fpReturnSize; + sir.m_structFields = FpStructInRegistersInfo::FromOldFlags((StructFloatFieldInfoFlags)fpReturnSize); return CopyStructFromRegisters(&sir); } diff --git a/src/tests/JIT/Directed/StructABI/CMakeLists.txt b/src/tests/JIT/Directed/StructABI/CMakeLists.txt index 1c7c8684995a59..1b2006ac2ef18b 100644 --- a/src/tests/JIT/Directed/StructABI/CMakeLists.txt +++ b/src/tests/JIT/Directed/StructABI/CMakeLists.txt @@ -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) diff --git a/src/tests/JIT/Directed/StructABI/StructABI.cpp b/src/tests/JIT/Directed/StructABI/StructABI.cpp new file mode 100644 index 00000000000000..755ba1e9b59192 --- /dev/null +++ b/src/tests/JIT/Directed/StructABI/StructABI.cpp @@ -0,0 +1,259 @@ +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include +#include + +#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 Empty8Float +{ + Empty e0, e1, e2, e3, e4, e5, e6, e7; + float FieldF; +}; + +struct EmptyFloatEmpty5Byte +{ + Empty e; + float FieldF; + Empty e0, e1, e2, e3, e4; + int8_t FieldB; +}; + +struct EmptyFloatEmpty5UByte +{ + Empty e; + float FieldF; + Empty e0, e1, e2, e3, e4; + uint8_t FieldB; +}; + +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 +struct Eight +{ + T e1, e2, e3, e4, e5, e6, e7, e8; +}; + +struct FloatEmpty32kInt +{ + float FieldF; + Eight>>>> 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 Empty8Float EchoEmpty8FloatRiscV(int a0, float fa0, Empty8Float fa1) +{ + return fa1; +} + +DLLEXPORT Empty8Float EchoEmpty8FloatInIntegerRegsRiscV( + int a0, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, Empty8Float a1_a2) +{ + return a1_a2; +} + +DLLEXPORT Empty8Float EchoEmpty8FloatSplitRiscV( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, Empty8Float a7_stack0) +{ + return a7_stack0; +} + +DLLEXPORT Empty8Float EchoEmpty8FloatOnStackRiscV( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, Empty8Float stack0_stack1) +{ + return stack0_stack1; +} + +DLLEXPORT EmptyFloatEmpty5Byte EchoEmptyFloatEmpty5ByteRiscV(int a0, float fa0, EmptyFloatEmpty5Byte fa1_a1) +{ + return fa1_a1; +} + +DLLEXPORT EmptyFloatEmpty5UByte EchoEmptyFloatEmpty5UByteRiscV(int a0, float fa0, EmptyFloatEmpty5UByte fa1_a1) +{ + return fa1_a1; +} + +DLLEXPORT EmptyFloatEmpty5Byte EchoEmptyFloatEmpty5ByteInIntegerRegsRiscV( + int a0, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, EmptyFloatEmpty5Byte a1_a2) +{ + return a1_a2; +} + +DLLEXPORT EmptyFloatEmpty5Byte EchoEmptyFloatEmpty5ByteSplitRiscV( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, EmptyFloatEmpty5Byte a7_stack0) +{ + return a7_stack0; +} + +DLLEXPORT EmptyFloatEmpty5Byte EchoEmptyFloatEmpty5ByteOnStackRiscV( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, EmptyFloatEmpty5Byte stack0_stack1) +{ + return stack0_stack1; +} + +DLLEXPORT LongEmptyDouble EchoLongEmptyDoubleRiscV(int a0, float fa0, LongEmptyDouble a1_fa1) +{ + return a1_fa1; +} + +DLLEXPORT LongEmptyDouble EchoLongEmptyDoubleByImplicitRefRiscV( + int a0, float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, LongEmptyDouble a1) +{ + return a1; +} + +DLLEXPORT NestedEmptyFloatDouble EchoNestedEmptyFloatDoubleRiscV(int a0, float fa0, NestedEmptyFloatDouble fa1_fa2) +{ + return fa1_fa2; +} + +DLLEXPORT NestedEmptyFloatDouble EchoNestedEmptyFloatDoubleInIntegerRegsRiscV( + int a0, float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, NestedEmptyFloatDouble a1_a2) +{ + return a1_a2; +} + +DLLEXPORT EmptyIntAndFloat EchoEmptyIntAndFloatRiscV(int a0, float fa0, EmptyIntAndFloat a1_fa1) +{ + return a1_fa1; +} + +DLLEXPORT LongEmptyAndFloat EchoLongEmptyAndFloatRiscV(int a0, float fa0, LongEmptyAndFloat a1_fa1) +{ + return a1_fa1; +} + +DLLEXPORT ArrayOfEmptiesFloatDouble EchoArrayOfEmptiesFloatDoubleRiscV(int a0, float fa0, ArrayOfEmptiesFloatDouble a1_a2) +{ + return a1_a2; +} + +DLLEXPORT FloatEmpty32kInt EchoFloatEmpty32kIntRiscV(int a0, float fa0, FloatEmpty32kInt fa1_a1) +{ + return fa1_a1; +} + +DLLEXPORT PackedEmptyFloatLong EchoPackedEmptyFloatLongRiscV(int a0, float fa0, PackedEmptyFloatLong fa1_a1) +{ + return fa1_a1; +} + +DLLEXPORT PackedEmptyFloatLong EchoPackedEmptyFloatLongInIntegerRegsRiscV( + int a0, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, PackedEmptyFloatLong a1_a2) +{ + return a1_a2; +} + +DLLEXPORT PackedEmptyFloatLong EchoPackedEmptyFloatLongSplitRiscV( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, PackedEmptyFloatLong a7_stack0) +{ + return a7_stack0; +} + +DLLEXPORT PackedEmptyFloatLong EchoPackedEmptyFloatLongOnStackRiscV( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, PackedEmptyFloatLong stack0_stack1) +{ + return stack0_stack1; +} + +DLLEXPORT ExplicitFloatLong EchoExplicitFloatLongRiscV(int a0, float fa0, ExplicitFloatLong fa1_a1) +{ + return fa1_a1; +} + +} // extern "C" diff --git a/src/tests/JIT/Directed/StructABI/StructABI.cs b/src/tests/JIT/Directed/StructABI/StructABI.cs index 2f74d49e75853f..033800d4384308 100644 --- a/src/tests/JIT/Directed/StructABI/StructABI.cs +++ b/src/tests/JIT/Directed/StructABI/StructABI.cs @@ -668,6 +668,228 @@ public bool Equals(Nested9 other) } } +public struct Empty +{ +} + +public struct Empty8Float +{ + public Empty e0, e1, e2, e3, e4, e5, e6, e7; + public float FieldF; + + public static Empty8Float Get() + { + return new Empty8Float { FieldF = 3.14159f }; + } + + public bool Equals(Empty8Float other) + { + return FieldF.Equals(other.FieldF); + } +} + +public struct EmptyFloatEmpty5Byte +{ + public Empty e; + public float FieldF; + public Empty e0, e1, e2, e3, e4; + public sbyte FieldB; + + public static EmptyFloatEmpty5Byte Get() + { + return new EmptyFloatEmpty5Byte { FieldF = 3.14159f, FieldB = -123 }; + } + + public bool Equals(EmptyFloatEmpty5Byte other) + { + return FieldF.Equals(other.FieldF) && FieldB == other.FieldB; + } +} + +public struct EmptyFloatEmpty5UByte +{ + public Empty e; + public float FieldF; + public Empty e0, e1, e2, e3, e4; + public byte FieldB; + + public static EmptyFloatEmpty5UByte Get() + { + return new EmptyFloatEmpty5UByte { FieldF = 3.14159f, FieldB = 234 }; + } + + public bool Equals(EmptyFloatEmpty5UByte other) + { + return FieldF.Equals(other.FieldF) && FieldB == other.FieldB; + } +} + +public struct LongEmptyDouble +{ + public long FieldL; + public Empty FieldE; + public double FieldD; + + public static LongEmptyDouble Get() + { + return new LongEmptyDouble { FieldL = 0xcafef00d, FieldD = 3.14159d }; + } + + public bool Equals(LongEmptyDouble other) + { + return FieldL == other.FieldL && FieldD.Equals(other.FieldD); + } +} + +public struct NestedEmpty +{ + public struct InnerEmpty + { + public Empty e; + } + public InnerEmpty e; +} + +public struct NestedEmptyFloatDouble +{ + public NestedEmpty FieldNE; + public float FieldF; + public double FieldD; + + public static NestedEmptyFloatDouble Get() + { + return new NestedEmptyFloatDouble { FieldF = 3.14159f, FieldD = 3.14159d }; + } + + public bool Equals(NestedEmptyFloatDouble other) + { + return FieldF.Equals(other.FieldF) && FieldD.Equals(other.FieldD); + } +} + +public struct EmptyIntAndFloat +{ + public struct EmptyInt + { + public Empty FieldE; + public int FieldI; + } + public EmptyInt FieldEI; + public float FieldF; + + public static EmptyIntAndFloat Get() + { + return new EmptyIntAndFloat { FieldEI = new EmptyInt { FieldI = 0xcafe }, FieldF = 3.14159f }; + } + + public bool Equals(EmptyIntAndFloat other) + { + return FieldEI.FieldI == other.FieldEI.FieldI && FieldF.Equals(other.FieldF); + } +} + +public struct LongEmptyAndFloat +{ + public struct LongEmpty + { + public long FieldL; + public Empty FieldE; + } + public LongEmpty FieldLE; + public float FieldF; + + public static LongEmptyAndFloat Get() + { + return new LongEmptyAndFloat { FieldLE = new LongEmpty { FieldL = 0xcafef00d }, FieldF = 3.14159f }; + } + + public bool Equals(LongEmptyAndFloat other) + { + return FieldLE.FieldL == other.FieldLE.FieldL && FieldF.Equals(other.FieldF); + } +} + +[InlineArray(1)] +public struct ArrayOfEmpties +{ + public Empty e; +}; + +public struct ArrayOfEmptiesFloatDouble +{ + public ArrayOfEmpties FieldAoE; + public float FieldF; + public double FieldD; + + public static ArrayOfEmptiesFloatDouble Get() + { + return new ArrayOfEmptiesFloatDouble { FieldF = 3.14159f, FieldD = 3.14159 }; + } + + public bool Equals(ArrayOfEmptiesFloatDouble other) + { + return FieldF.Equals(other.FieldF) && FieldD.Equals(other.FieldD); + } +} + +public struct Eight +{ + public T e1, e2, e3, e4, e5, e6, e7, e8; +} + +public struct FloatEmpty32kInt +{ + public float FieldF; + public Eight>>>> FieldEmpty32k; + public int FieldI; + + public static FloatEmpty32kInt Get() + { + return new FloatEmpty32kInt { FieldF = 3.14159f, FieldI = 0xcafe }; + } + + public bool Equals(FloatEmpty32kInt other) + { + return FieldF.Equals(other.FieldF) && FieldI == other.FieldI; + } +} + +[StructLayout(LayoutKind.Sequential, Pack=1)] +public struct PackedEmptyFloatLong +{ + public Empty FieldE; + public float FieldF; + public long FieldL; + + public static PackedEmptyFloatLong Get() + { + return new PackedEmptyFloatLong { FieldF = 3.14159f, FieldL = 0xcafef00d }; + } + + public bool Equals(PackedEmptyFloatLong other) + { + return FieldF.Equals(other.FieldF) && FieldL == other.FieldL; + } +} + +[StructLayout(LayoutKind.Explicit, Pack=1)] +public struct ExplicitFloatLong +{ + [FieldOffset(1)] public float FieldF; + [FieldOffset(5)] public long FieldL; + + public static ExplicitFloatLong Get() + { + return new ExplicitFloatLong { FieldF = 3.14159f, FieldL = 0xcafef00d }; + } + + public bool Equals(ExplicitFloatLong other) + { + return FieldF.Equals(other.FieldF) && FieldL == other.FieldL; + } +} + + public static partial class StructABI { [DllImport("StructABILib")] @@ -799,6 +1021,92 @@ public static partial class StructABI [DllImport("StructABILib")] static extern DoubleAndByte EnoughRegistersSysV4(double a, double b, double c, double d, double e, double f, double g, DoubleAndByte value); + [DllImport("StructABILib")] + public static extern Empty8Float EchoEmpty8FloatRiscV(int a0, float fa0, Empty8Float fa1); + + [DllImport("StructABILib")] + public static extern Empty8Float EchoEmpty8FloatInIntegerRegsRiscV( + int a0, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, Empty8Float a1_a2); + + [DllImport("StructABILib")] + public static extern Empty8Float EchoEmpty8FloatSplitRiscV( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, Empty8Float a7_stack0); + + [DllImport("StructABILib")] + public static extern Empty8Float EchoEmpty8FloatOnStackRiscV( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, Empty8Float stack0_stack1); + + [DllImport("StructABILib")] + public static extern EmptyFloatEmpty5Byte EchoEmptyFloatEmpty5ByteRiscV(int a0, float fa0, EmptyFloatEmpty5Byte fa1_a1); + + [DllImport("StructABILib")] + public static extern EmptyFloatEmpty5UByte EchoEmptyFloatEmpty5UByteRiscV(int a0, float fa0, EmptyFloatEmpty5UByte fa1_a1); + + [DllImport("StructABILib")] + public static extern EmptyFloatEmpty5Byte EchoEmptyFloatEmpty5ByteInIntegerRegsRiscV( + int a0, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, EmptyFloatEmpty5Byte a1_a2); + + [DllImport("StructABILib")] + public static extern EmptyFloatEmpty5Byte EchoEmptyFloatEmpty5ByteSplitRiscV( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, EmptyFloatEmpty5Byte a7_stack0); + + [DllImport("StructABILib")] + public static extern EmptyFloatEmpty5Byte EchoEmptyFloatEmpty5ByteOnStackRiscV( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, EmptyFloatEmpty5Byte stack0_stack1); + + [DllImport("StructABILib")] + public static extern LongEmptyDouble EchoLongEmptyDoubleRiscV(int a0, float fa0, LongEmptyDouble a1_fa1); + + [DllImport("StructABILib")] + public static extern LongEmptyDouble EchoLongEmptyDoubleByImplicitRefRiscV( + int a0, float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, LongEmptyDouble a1); + + [DllImport("StructABILib")] + public static extern NestedEmptyFloatDouble EchoNestedEmptyFloatDoubleRiscV(int a0, float fa0, NestedEmptyFloatDouble fa1_fa2); + + [DllImport("StructABILib")] + public static extern NestedEmptyFloatDouble EchoNestedEmptyFloatDoubleInIntegerRegsRiscV( + int a0, float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, NestedEmptyFloatDouble a1_a2); + + [DllImport("StructABILib")] + public static extern EmptyIntAndFloat EchoEmptyIntAndFloatRiscV(int a0, float fa0, EmptyIntAndFloat a1_fa1); + + [DllImport("StructABILib")] + public static extern LongEmptyAndFloat EchoLongEmptyAndFloatRiscV(int a0, float fa0, LongEmptyAndFloat a1_fa1); + + [DllImport("StructABILib")] + public static extern ArrayOfEmptiesFloatDouble EchoArrayOfEmptiesFloatDoubleRiscV(int a0, float fa0, ArrayOfEmptiesFloatDouble a1_a2); + + [DllImport("StructABILib")] + public static extern FloatEmpty32kInt EchoFloatEmpty32kIntRiscV(int a0, float fa0, FloatEmpty32kInt fa1_a1); + + [DllImport("StructABILib")] + public static extern PackedEmptyFloatLong EchoPackedEmptyFloatLongRiscV(int a0, float fa0, PackedEmptyFloatLong fa1_a1); + + [DllImport("StructABILib")] + public static extern PackedEmptyFloatLong EchoPackedEmptyFloatLongInIntegerRegsRiscV( + int a0, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, PackedEmptyFloatLong a1_a2); + + [DllImport("StructABILib")] + public static extern PackedEmptyFloatLong EchoPackedEmptyFloatLongSplitRiscV( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, PackedEmptyFloatLong a7_stack0); + + [DllImport("StructABILib")] + public static extern PackedEmptyFloatLong EchoPackedEmptyFloatLongOnStackRiscV( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, PackedEmptyFloatLong stack0_stack1); + + [DllImport("StructABILib")] + public static extern ExplicitFloatLong EchoExplicitFloatLongRiscV(int a0, float fa0, ExplicitFloatLong fa1_a1); + //////////////////////////////////////////////////////////////////////////// // Managed echo tests. //////////////////////////////////////////////////////////////////////////// @@ -1062,6 +1370,158 @@ static DoubleAndByte EnoughRegistersSysV4Managed(double a, double b, double c, d return value; } + [MethodImpl(MethodImplOptions.NoInlining)] + public static Empty8Float EchoEmpty8FloatRiscVManaged(int a0, float fa0, Empty8Float fa1) + { + return fa1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Empty8Float EchoEmpty8FloatInIntegerRegsRiscVManaged( + int a0, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, Empty8Float a1_a2) + { + return a1_a2; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Empty8Float EchoEmpty8FloatSplitRiscVManaged( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, Empty8Float a7_stack0) + { + return a7_stack0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Empty8Float EchoEmpty8FloatOnStackRiscVManaged( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, Empty8Float stack0_stack1) + { + return stack0_stack1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static EmptyFloatEmpty5Byte EchoEmptyFloatEmpty5ByteRiscVManaged(int a0, float fa0, EmptyFloatEmpty5Byte fa1_a1) + { + return fa1_a1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static EmptyFloatEmpty5UByte EchoEmptyFloatEmpty5UByteRiscVManaged(int a0, float fa0, EmptyFloatEmpty5UByte fa1_a1) + { + return fa1_a1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static EmptyFloatEmpty5Byte EchoEmptyFloatEmpty5ByteInIntegerRegsRiscVManaged( + int a0, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, EmptyFloatEmpty5Byte a1_a2) + { + return a1_a2; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static EmptyFloatEmpty5Byte EchoEmptyFloatEmpty5ByteSplitRiscVManaged( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, EmptyFloatEmpty5Byte a7_stack0) + { + return a7_stack0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static EmptyFloatEmpty5Byte EchoEmptyFloatEmpty5ByteOnStackRiscVManaged( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, EmptyFloatEmpty5Byte stack0_stack1) + { + return stack0_stack1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static LongEmptyDouble EchoLongEmptyDoubleRiscVManaged(int a0, float fa0, LongEmptyDouble a1_fa1) + { + return a1_fa1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static LongEmptyDouble EchoLongEmptyDoubleByImplicitRefRiscVManaged( + int a0, float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, LongEmptyDouble a1) + { + return a1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static NestedEmptyFloatDouble EchoNestedEmptyFloatDoubleRiscVManaged(int a0, float fa0, NestedEmptyFloatDouble fa1_fa2) + { + return fa1_fa2; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static NestedEmptyFloatDouble EchoNestedEmptyFloatDoubleInIntegerRegsRiscVManaged( + int a0, float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, NestedEmptyFloatDouble a1_a2) + { + return a1_a2; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static EmptyIntAndFloat EchoEmptyIntAndFloatRiscVManaged(int a0, float fa0, EmptyIntAndFloat a1_fa1) + { + return a1_fa1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static LongEmptyAndFloat EchoLongEmptyAndFloatRiscVManaged(int a0, float fa0, LongEmptyAndFloat a1_fa1) + { + return a1_fa1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static ArrayOfEmptiesFloatDouble EchoArrayOfEmptiesFloatDoubleRiscVManaged(int a0, float fa0, ArrayOfEmptiesFloatDouble a1_a2) + { + return a1_a2; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static FloatEmpty32kInt EchoFloatEmpty32kIntRiscVManaged(int a0, float fa0, FloatEmpty32kInt fa1_a1) + { + return fa1_a1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static PackedEmptyFloatLong EchoPackedEmptyFloatLongRiscVManaged(int a0, float fa0, PackedEmptyFloatLong fa1_a1) + { + return fa1_a1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static PackedEmptyFloatLong EchoPackedEmptyFloatLongInIntegerRegsRiscVManaged( + int a0, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, PackedEmptyFloatLong a1_a2) + { + return a1_a2; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static PackedEmptyFloatLong EchoPackedEmptyFloatLongSplitRiscVManaged( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, PackedEmptyFloatLong a7_stack0) + { + return a7_stack0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static PackedEmptyFloatLong EchoPackedEmptyFloatLongOnStackRiscVManaged( + int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, + float fa0, float fa1, float fa2, float fa3, float fa4, float fa5, float fa6, float fa7, PackedEmptyFloatLong stack0_stack1) + { + return stack0_stack1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static ExplicitFloatLong EchoExplicitFloatLongRiscVManaged(int a0, float fa0, ExplicitFloatLong fa1_a1) + { + return fa1_a1; + } + //////////////////////////////////////////////////////////////////////////// // Wrapper methods // @@ -2154,6 +2614,1035 @@ static bool EnoughRegistersSysV4Wrapper() return ok; } + static bool EchoEmpty8FloatRiscVWrapper() + { + bool ok = true; + Empty8Float expected = Empty8Float.Get(); + Empty8Float native = EchoEmpty8FloatRiscV(0, 0f, expected); + Empty8Float managed = EchoEmpty8FloatRiscVManaged(0, 0f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoEmpty8FloatRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoEmpty8FloatRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmpty8FloatRiscVReflectionWrapper() + { + bool ok = true; + var expected = Empty8Float.Get(); + var native = (Empty8Float)typeof(StructABI).GetMethod("EchoEmpty8FloatRiscV") + .Invoke(null, new object[] {0, 0f, expected}); + var managed = (Empty8Float)typeof(StructABI).GetMethod("EchoEmpty8FloatRiscVManaged") + .Invoke(null, new object[] {0, 0f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoEmpty8FloatRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoEmpty8FloatRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmpty8FloatInIntegerRegsRiscVWrapper() + { + bool ok = true; + Empty8Float expected = Empty8Float.Get(); + Empty8Float native = EchoEmpty8FloatInIntegerRegsRiscV( + 0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + Empty8Float managed = EchoEmpty8FloatInIntegerRegsRiscVManaged( + 0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoEmpty8FloatInIntegerRegsRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoEmpty8FloatInIntegerRegsRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmpty8FloatInIntegerRegsRiscVReflectionWrapper() + { + bool ok = true; + var expected = Empty8Float.Get(); + var native = (Empty8Float)typeof(StructABI).GetMethod("EchoEmpty8FloatInIntegerRegsRiscV") + .Invoke(null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + var managed = (Empty8Float)typeof(StructABI).GetMethod("EchoEmpty8FloatInIntegerRegsRiscVManaged") + .Invoke(null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoEmpty8FloatInIntegerRegsRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoEmpty8FloatInIntegerRegsRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmpty8FloatSplitRiscVWrapper() + { + bool ok = true; + Empty8Float expected = Empty8Float.Get(); + Empty8Float native = EchoEmpty8FloatSplitRiscV( + 0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + Empty8Float managed = EchoEmpty8FloatSplitRiscVManaged( + 0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoEmpty8FloatSplitRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoEmpty8FloatSplitRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmpty8FloatSplitRiscVReflectionWrapper() + { + bool ok = true; + var expected = Empty8Float.Get(); + var native = (Empty8Float)typeof(StructABI).GetMethod("EchoEmpty8FloatSplitRiscV") + .Invoke(null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + var managed = (Empty8Float)typeof(StructABI).GetMethod("EchoEmpty8FloatSplitRiscVManaged") + .Invoke(null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoEmpty8FloatSplitRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoEmpty8FloatSplitRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmpty8FloatOnStackRiscVWrapper() + { + bool ok = true; + Empty8Float expected = Empty8Float.Get(); + Empty8Float native = EchoEmpty8FloatOnStackRiscV( + 0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + Empty8Float managed = EchoEmpty8FloatOnStackRiscVManaged( + 0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoEmpty8FloatOnStackRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoEmpty8FloatOnStackRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmpty8FloatOnStackRiscVReflectionWrapper() + { + bool ok = true; + var expected = Empty8Float.Get(); + var native = (Empty8Float)typeof(StructABI).GetMethod("EchoEmpty8FloatOnStackRiscV") + .Invoke(null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + var managed = (Empty8Float)typeof(StructABI).GetMethod("EchoEmpty8FloatOnStackRiscVManaged") + .Invoke(null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoEmpty8FloatOnStackRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoEmpty8FloatOnStackRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmptyFloatEmpty5ByteRiscVWrapper() + { + bool ok = true; + EmptyFloatEmpty5Byte expected = EmptyFloatEmpty5Byte.Get(); + EmptyFloatEmpty5Byte native = EchoEmptyFloatEmpty5ByteRiscV(0, 0f, expected); + EmptyFloatEmpty5Byte managed = EchoEmptyFloatEmpty5ByteRiscVManaged(0, 0f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoEmptyFloatEmpty5ByteRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoEmptyFloatEmpty5ByteRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmptyFloatEmpty5ByteRiscVReflectionWrapper() + { + bool ok = true; + var expected = EmptyFloatEmpty5Byte.Get(); + var native = (EmptyFloatEmpty5Byte)typeof(StructABI).GetMethod("EchoEmptyFloatEmpty5ByteRiscV") + .Invoke(null, new object[] {0, 0f, expected}); + var managed = (EmptyFloatEmpty5Byte)typeof(StructABI).GetMethod("EchoEmptyFloatEmpty5ByteRiscVManaged") + .Invoke(null, new object[] {0, 0f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoEmptyFloatEmpty5ByteRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoEmptyFloatEmpty5ByteRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmptyFloatEmpty5UByteRiscVWrapper() + { + bool ok = true; + EmptyFloatEmpty5UByte expected = EmptyFloatEmpty5UByte.Get(); + EmptyFloatEmpty5UByte native = EchoEmptyFloatEmpty5UByteRiscV(0, 0f, expected); + EmptyFloatEmpty5UByte managed = EchoEmptyFloatEmpty5UByteRiscVManaged(0, 0f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoEmptyFloatEmpty5UByteRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoEmptyFloatEmpty5UByteRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmptyFloatEmpty5UByteRiscVReflectionWrapper() + { + bool ok = true; + var expected = EmptyFloatEmpty5UByte.Get(); + var native = (EmptyFloatEmpty5UByte)typeof(StructABI).GetMethod("EchoEmptyFloatEmpty5UByteRiscV") + .Invoke(null, new object[] {0, 0f, expected}); + var managed = (EmptyFloatEmpty5UByte)typeof(StructABI).GetMethod("EchoEmptyFloatEmpty5UByteRiscVManaged") + .Invoke(null, new object[] {0, 0f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoEmptyFloatEmpty5UByteRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoEmptyFloatEmpty5UByteRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmptyFloatEmpty5ByteInIntegerRegsRiscVWrapper() + { + bool ok = true; + EmptyFloatEmpty5Byte expected = EmptyFloatEmpty5Byte.Get(); + EmptyFloatEmpty5Byte native = EchoEmptyFloatEmpty5ByteInIntegerRegsRiscV( + 0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + EmptyFloatEmpty5Byte managed = EchoEmptyFloatEmpty5ByteInIntegerRegsRiscVManaged( + 0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoEmptyFloatEmpty5ByteInIntegerRegsRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoEmptyFloatEmpty5ByteInIntegerRegsRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmptyFloatEmpty5ByteInIntegerRegsRiscVReflectionWrapper() + { + bool ok = true; + var expected = EmptyFloatEmpty5Byte.Get(); + var native = (EmptyFloatEmpty5Byte)typeof(StructABI).GetMethod("EchoEmptyFloatEmpty5ByteInIntegerRegsRiscV") + .Invoke(null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + var managed = (EmptyFloatEmpty5Byte)typeof(StructABI).GetMethod("EchoEmptyFloatEmpty5ByteInIntegerRegsRiscVManaged") + .Invoke(null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoEmptyFloatEmpty5ByteInIntegerRegsRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoEmptyFloatEmpty5ByteInIntegerRegsRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmptyFloatEmpty5ByteSplitRiscVWrapper() + { + bool ok = true; + EmptyFloatEmpty5Byte expected = EmptyFloatEmpty5Byte.Get(); + EmptyFloatEmpty5Byte native = EchoEmptyFloatEmpty5ByteSplitRiscV( + 0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + EmptyFloatEmpty5Byte managed = EchoEmptyFloatEmpty5ByteSplitRiscVManaged( + 0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoEmptyFloatEmpty5ByteSplitRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoEmptyFloatEmpty5ByteSplitRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmptyFloatEmpty5ByteSplitRiscVReflectionWrapper() + { + bool ok = true; + var expected = EmptyFloatEmpty5Byte.Get(); + var native = (EmptyFloatEmpty5Byte)typeof(StructABI).GetMethod("EchoEmptyFloatEmpty5ByteSplitRiscV") + .Invoke(null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + var managed = (EmptyFloatEmpty5Byte)typeof(StructABI).GetMethod("EchoEmptyFloatEmpty5ByteSplitRiscVManaged") + .Invoke(null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoEmptyFloatEmpty5ByteSplitRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoEmptyFloatEmpty5ByteSplitRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmptyFloatEmpty5ByteOnStackRiscVWrapper() + { + bool ok = true; + EmptyFloatEmpty5Byte expected = EmptyFloatEmpty5Byte.Get(); + EmptyFloatEmpty5Byte native = EchoEmptyFloatEmpty5ByteOnStackRiscV( + 0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + EmptyFloatEmpty5Byte managed = EchoEmptyFloatEmpty5ByteOnStackRiscVManaged( + 0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoEmptyFloatEmpty5ByteOnStackRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoEmptyFloatEmpty5ByteOnStackRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmptyFloatEmpty5ByteOnStackRiscVReflectionWrapper() + { + bool ok = true; + var expected = EmptyFloatEmpty5Byte.Get(); + var native = (EmptyFloatEmpty5Byte)typeof(StructABI).GetMethod("EchoEmptyFloatEmpty5ByteOnStackRiscV") + .Invoke(null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + var managed = (EmptyFloatEmpty5Byte)typeof(StructABI).GetMethod("EchoEmptyFloatEmpty5ByteOnStackRiscVManaged") + .Invoke(null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoEmptyFloatEmpty5ByteOnStackRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoEmptyFloatEmpty5ByteOnStackRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoLongEmptyDoubleRiscVWrapper() + { + bool ok = true; + LongEmptyDouble expected = LongEmptyDouble.Get(); + LongEmptyDouble native = EchoLongEmptyDoubleRiscV(0, 0f, expected); + LongEmptyDouble managed = EchoLongEmptyDoubleRiscVManaged(0, 0f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoLongEmptyDoubleRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoLongEmptyDoubleRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoLongEmptyDoubleRiscVReflectionWrapper() + { + bool ok = true; + var expected = LongEmptyDouble.Get(); + var native = (LongEmptyDouble)typeof(StructABI).GetMethod("EchoLongEmptyDoubleRiscV") + .Invoke(null, new object[] {0, 0f, expected}); + var managed = (LongEmptyDouble)typeof(StructABI).GetMethod("EchoLongEmptyDoubleRiscVManaged") + .Invoke(null, new object[] {0, 0f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoLongEmptyDoubleRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoLongEmptyDoubleRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoLongEmptyDoubleByImplicitRefRiscVWrapper() + { + bool ok = true; + LongEmptyDouble expected = LongEmptyDouble.Get(); + LongEmptyDouble native = EchoLongEmptyDoubleByImplicitRefRiscV(0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + LongEmptyDouble managed = EchoLongEmptyDoubleByImplicitRefRiscVManaged(0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoLongEmptyDoubleByImplicitRefRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoLongEmptyDoubleByImplicitRefRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoLongEmptyDoubleByImplicitRefRiscVReflectionWrapper() + { + bool ok = true; + var expected = LongEmptyDouble.Get(); + var native = (LongEmptyDouble)typeof(StructABI).GetMethod("EchoLongEmptyDoubleByImplicitRefRiscV") + .Invoke(null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + var managed = (LongEmptyDouble)typeof(StructABI).GetMethod("EchoLongEmptyDoubleByImplicitRefRiscVManaged") + .Invoke(null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoLongEmptyDoubleByImplicitRefRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoLongEmptyDoubleByImplicitRefRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoNestedEmptyFloatDoubleRiscVWrapper() + { + bool ok = true; + NestedEmptyFloatDouble expected = NestedEmptyFloatDouble.Get(); + NestedEmptyFloatDouble native = EchoNestedEmptyFloatDoubleRiscV(0, 0f, expected); + NestedEmptyFloatDouble managed = EchoNestedEmptyFloatDoubleRiscVManaged(0, 0f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoNestedEmptyFloatDoubleRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoNestedEmptyFloatDoubleRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoNestedEmptyFloatDoubleRiscVReflectionWrapper() + { + bool ok = true; + var expected = NestedEmptyFloatDouble.Get(); + var native = (NestedEmptyFloatDouble)typeof(StructABI).GetMethod("EchoNestedEmptyFloatDoubleRiscV") + .Invoke(null, new object[] {0, 0f, expected}); + var managed = (NestedEmptyFloatDouble)typeof(StructABI).GetMethod("EchoNestedEmptyFloatDoubleRiscVManaged") + .Invoke(null, new object[] {0, 0f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoNestedEmptyFloatDoubleRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoNestedEmptyFloatDoubleRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoNestedEmptyFloatDoubleInIntegerRegsRiscVWrapper() + { + bool ok = true; + NestedEmptyFloatDouble expected = NestedEmptyFloatDouble.Get(); + NestedEmptyFloatDouble native = EchoNestedEmptyFloatDoubleInIntegerRegsRiscV(0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, expected); + NestedEmptyFloatDouble managed = EchoNestedEmptyFloatDoubleInIntegerRegsRiscVManaged(0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoNestedEmptyFloatDoubleInIntegerRegsRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoNestedEmptyFloatDoubleInIntegerRegsRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoNestedEmptyFloatDoubleInIntegerRegsRiscVReflectionWrapper() + { + bool ok = true; + var expected = NestedEmptyFloatDouble.Get(); + var native = (NestedEmptyFloatDouble)typeof(StructABI).GetMethod("EchoNestedEmptyFloatDoubleInIntegerRegsRiscV") + .Invoke(null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, expected}); + var managed = (NestedEmptyFloatDouble)typeof(StructABI).GetMethod("EchoNestedEmptyFloatDoubleInIntegerRegsRiscVManaged") + .Invoke(null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoNestedEmptyFloatDoubleInIntegerRegsRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoNestedEmptyFloatDoubleInIntegerRegsRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmptyIntAndFloatRiscVWrapper() + { + bool ok = true; + EmptyIntAndFloat expected = EmptyIntAndFloat.Get(); + EmptyIntAndFloat native = EchoEmptyIntAndFloatRiscV(0, 0f, expected); + EmptyIntAndFloat managed = EchoEmptyIntAndFloatRiscVManaged(0, 0f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoEmptyIntAndFloatRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoEmptyIntAndFloatRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoEmptyIntAndFloatRiscVReflectionWrapper() + { + bool ok = true; + var expected = EmptyIntAndFloat.Get(); + var native = (EmptyIntAndFloat)typeof(StructABI).GetMethod("EchoEmptyIntAndFloatRiscV") + .Invoke(null, new object[] {0, 0f, expected}); + var managed = (EmptyIntAndFloat)typeof(StructABI).GetMethod("EchoEmptyIntAndFloatRiscVManaged") + .Invoke(null, new object[] {0, 0f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoEmptyIntAndFloatRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoEmptyIntAndFloatRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoLongEmptyAndFloatRiscVWrapper() + { + bool ok = true; + LongEmptyAndFloat expected = LongEmptyAndFloat.Get(); + LongEmptyAndFloat native = EchoLongEmptyAndFloatRiscV(0, 0f, expected); + LongEmptyAndFloat managed = EchoLongEmptyAndFloatRiscVManaged(0, 0f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoLongEmptyAndFloatRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoLongEmptyAndFloatRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoLongEmptyAndFloatRiscVReflectionWrapper() + { + bool ok = true; + var expected = LongEmptyAndFloat.Get(); + var native = (LongEmptyAndFloat)typeof(StructABI).GetMethod("EchoLongEmptyAndFloatRiscV") + .Invoke(null, new object[] {0, 0f, expected}); + var managed = (LongEmptyAndFloat)typeof(StructABI).GetMethod("EchoLongEmptyAndFloatRiscVManaged") + .Invoke(null, new object[] {0, 0f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoLongEmptyAndFloatRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoLongEmptyAndFloatRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoArrayOfEmptiesFloatDoubleRiscVWrapper() + { + bool ok = true; + ArrayOfEmptiesFloatDouble expected = ArrayOfEmptiesFloatDouble.Get(); + ArrayOfEmptiesFloatDouble native = EchoArrayOfEmptiesFloatDoubleRiscV(0, 0f, expected); + ArrayOfEmptiesFloatDouble managed = EchoArrayOfEmptiesFloatDoubleRiscVManaged(0, 0f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoArrayOfEmptiesFloatDouble failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoArrayOfEmptiesFloatDouble failed"); + ok = false; + } + + return ok; + } + + static bool EchoArrayOfEmptiesFloatDoubleRiscVReflectionWrapper() + { + bool ok = true; + var expected = ArrayOfEmptiesFloatDouble.Get(); + var native = (ArrayOfEmptiesFloatDouble)typeof(StructABI).GetMethod("EchoArrayOfEmptiesFloatDoubleRiscV") + .Invoke(null, new object[] {0, 0f, expected}); + var managed = (ArrayOfEmptiesFloatDouble)typeof(StructABI).GetMethod("EchoArrayOfEmptiesFloatDoubleRiscVManaged") + .Invoke(null, new object[] {0, 0f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoArrayOfEmptiesFloatDoubleRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoArrayOfEmptiesFloatDoubleRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoFloatEmpty32kIntRiscVWrapper() + { + bool ok = true; + FloatEmpty32kInt expected = FloatEmpty32kInt.Get(); + FloatEmpty32kInt native = EchoFloatEmpty32kIntRiscV(0, 0f, expected); + FloatEmpty32kInt managed = EchoFloatEmpty32kIntRiscVManaged(0, 0f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoFloatEmpty32kInt failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoFloatEmpty32kInt failed"); + ok = false; + } + + return ok; + } + + static bool EchoFloatEmpty32kIntRiscVReflectionWrapper() + { + bool ok = true; + var expected = FloatEmpty32kInt.Get(); + var native = (FloatEmpty32kInt)typeof(StructABI).GetMethod("EchoFloatEmpty32kIntRiscV") + .Invoke(null, new object[] {0, 0f, expected}); + var managed = (FloatEmpty32kInt)typeof(StructABI).GetMethod("EchoFloatEmpty32kIntRiscVManaged") + .Invoke(null, new object[] {0, 0f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoFloatEmpty32kIntRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoFloatEmpty32kIntRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoPackedEmptyFloatLongRiscVWrapper() + { + bool ok = true; + PackedEmptyFloatLong expected = PackedEmptyFloatLong.Get(); + PackedEmptyFloatLong native = EchoPackedEmptyFloatLongRiscV(0, 0f, expected); + PackedEmptyFloatLong managed = EchoPackedEmptyFloatLongRiscVManaged(0, 0f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoPackedEmptyFloatLong failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoPackedEmptyFloatLong failed"); + ok = false; + } + + return ok; + } + + static bool EchoPackedEmptyFloatLongRiscVReflectionWrapper() + { + bool ok = true; + var expected = PackedEmptyFloatLong.Get(); + var native = (PackedEmptyFloatLong)typeof(StructABI).GetMethod("EchoPackedEmptyFloatLongRiscV") + .Invoke(null, new object[] {0, 0f, expected}); + var managed = (PackedEmptyFloatLong)typeof(StructABI).GetMethod("EchoPackedEmptyFloatLongRiscVManaged") + .Invoke(null, new object[] {0, 0f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoPackedEmptyFloatLongRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoPackedEmptyFloatLongRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoPackedEmptyFloatLongInIntegerRegsRiscVWrapper() + { + bool ok = true; + PackedEmptyFloatLong expected = PackedEmptyFloatLong.Get(); + PackedEmptyFloatLong native = EchoPackedEmptyFloatLongInIntegerRegsRiscV( + 0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + PackedEmptyFloatLong managed = EchoPackedEmptyFloatLongInIntegerRegsRiscVManaged( + 0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoPackedEmptyFloatLongInIntegerRegsRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoPackedEmptyFloatLongInIntegerRegsRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoPackedEmptyFloatLongInIntegerRegsRiscVReflectionWrapper() + { + bool ok = true; + var expected = PackedEmptyFloatLong.Get(); + var native = (PackedEmptyFloatLong)typeof(StructABI).GetMethod("EchoPackedEmptyFloatLongInIntegerRegsRiscV") + .Invoke(null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + var managed = (PackedEmptyFloatLong)typeof(StructABI).GetMethod("EchoPackedEmptyFloatLongInIntegerRegsRiscVManaged") + .Invoke(null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoPackedEmptyFloatLongInIntegerRegsRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoPackedEmptyFloatLongInIntegerRegsRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoPackedEmptyFloatLongSplitRiscVWrapper() + { + bool ok = true; + PackedEmptyFloatLong expected = PackedEmptyFloatLong.Get(); + PackedEmptyFloatLong native = EchoPackedEmptyFloatLongSplitRiscV( + 0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + PackedEmptyFloatLong managed = EchoPackedEmptyFloatLongSplitRiscVManaged( + 0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoPackedEmptyFloatLongSplitRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoPackedEmptyFloatLongSplitRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoPackedEmptyFloatLongSplitRiscVReflectionWrapper() + { + bool ok = true; + var expected = PackedEmptyFloatLong.Get(); + var native = (PackedEmptyFloatLong)typeof(StructABI).GetMethod("EchoPackedEmptyFloatLongSplitRiscV") + .Invoke(null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + var managed = (PackedEmptyFloatLong)typeof(StructABI).GetMethod("EchoPackedEmptyFloatLongSplitRiscVManaged") + .Invoke(null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoPackedEmptyFloatLongSplitRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoPackedEmptyFloatLongSplitRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoPackedEmptyFloatLongOnStackRiscVWrapper() + { + bool ok = true; + PackedEmptyFloatLong expected = PackedEmptyFloatLong.Get(); + PackedEmptyFloatLong native = EchoPackedEmptyFloatLongOnStackRiscV( + 0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + PackedEmptyFloatLong managed = EchoPackedEmptyFloatLongOnStackRiscVManaged( + 0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoPackedEmptyFloatLongOnStackRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoPackedEmptyFloatLongOnStackRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoPackedEmptyFloatLongOnStackRiscVReflectionWrapper() + { + bool ok = true; + var expected = PackedEmptyFloatLong.Get(); + var native = (PackedEmptyFloatLong)typeof(StructABI).GetMethod("EchoPackedEmptyFloatLongOnStackRiscV") + .Invoke(null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + var managed = (PackedEmptyFloatLong)typeof(StructABI).GetMethod("EchoPackedEmptyFloatLongOnStackRiscVManaged") + .Invoke(null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoPackedEmptyFloatLongOnStackRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoPackedEmptyFloatLongOnStackRiscV failed"); + ok = false; + } + + return ok; + } + + static bool EchoExplicitFloatLongRiscVWrapper() + { + bool ok = true; + ExplicitFloatLong expected = ExplicitFloatLong.Get(); + ExplicitFloatLong native = EchoExplicitFloatLongRiscV(0, 0f, expected); + ExplicitFloatLong managed = EchoExplicitFloatLongRiscVManaged(0, 0f, expected); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call for EchoExplicitFloatLong failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call for EchoExplicitFloatLong failed"); + ok = false; + } + + return ok; + } + + static bool EchoExplicitFloatLongRiscVReflectionWrapper() + { + bool ok = true; + var expected = ExplicitFloatLong.Get(); + var native = (ExplicitFloatLong)typeof(StructABI).GetMethod("EchoExplicitFloatLongRiscV") + .Invoke(null, new object[] {0, 0f, expected}); + var managed = (ExplicitFloatLong)typeof(StructABI).GetMethod("EchoExplicitFloatLongRiscVManaged") + .Invoke(null, new object[] {0, 0f, expected}); + + if (!expected.Equals(native)) + { + Console.WriteLine("Native call by reflection for EchoExplicitFloatLongRiscV failed"); + ok = false; + } + + if (!expected.Equals(managed)) + { + Console.WriteLine("Managed call by reflection for EchoExplicitFloatLongRiscV failed"); + ok = false; + } + + return ok; + } + [Fact] public static int TestEntryPoint() { @@ -2202,6 +3691,48 @@ public static int TestEntryPoint() if (!EnoughRegistersSysV2Wrapper()) ok = false; if (!EnoughRegistersSysV3Wrapper()) ok = false; if (!EnoughRegistersSysV4Wrapper()) ok = false; + if (!EchoEmpty8FloatRiscVWrapper()) ok = false; + if (!EchoEmpty8FloatRiscVReflectionWrapper()) ok = false; + if (!EchoEmpty8FloatInIntegerRegsRiscVWrapper()) ok = false; + if (!EchoEmpty8FloatInIntegerRegsRiscVReflectionWrapper()) ok = false; + if (!EchoEmpty8FloatSplitRiscVWrapper()) ok = false; + if (!EchoEmpty8FloatSplitRiscVReflectionWrapper()) ok = false; + if (!EchoEmpty8FloatOnStackRiscVWrapper()) ok = false; + if (!EchoEmpty8FloatOnStackRiscVReflectionWrapper()) ok = false; + if (!EchoEmptyFloatEmpty5ByteRiscVWrapper()) ok = false; + if (!EchoEmptyFloatEmpty5ByteRiscVReflectionWrapper()) ok = false; + if (!EchoEmptyFloatEmpty5UByteRiscVWrapper()) ok = false; + if (!EchoEmptyFloatEmpty5UByteRiscVReflectionWrapper()) ok = false; + if (!EchoEmptyFloatEmpty5ByteInIntegerRegsRiscVWrapper()) ok = false; + if (!EchoEmptyFloatEmpty5ByteInIntegerRegsRiscVReflectionWrapper()) ok = false; + if (!EchoEmptyFloatEmpty5ByteSplitRiscVWrapper()) ok = false; + if (!EchoEmptyFloatEmpty5ByteSplitRiscVReflectionWrapper()) ok = false; + if (!EchoEmptyFloatEmpty5ByteOnStackRiscVWrapper()) ok = false; + if (!EchoEmptyFloatEmpty5ByteOnStackRiscVReflectionWrapper()) ok = false; + if (!EchoLongEmptyDoubleRiscVWrapper()) ok = false; + if (!EchoLongEmptyDoubleRiscVReflectionWrapper()) ok = false; + if (!EchoLongEmptyDoubleByImplicitRefRiscVWrapper()) ok = false; + if (!EchoLongEmptyDoubleByImplicitRefRiscVReflectionWrapper()) ok = false; + if (!EchoNestedEmptyFloatDoubleInIntegerRegsRiscVWrapper()) ok = false; + if (!EchoNestedEmptyFloatDoubleInIntegerRegsRiscVReflectionWrapper()) ok = false; + if (!EchoEmptyIntAndFloatRiscVWrapper()) ok = false; + if (!EchoEmptyIntAndFloatRiscVReflectionWrapper()) ok = false; + if (!EchoLongEmptyAndFloatRiscVWrapper()) ok = false; + if (!EchoLongEmptyAndFloatRiscVReflectionWrapper()) ok = false; + if (!EchoArrayOfEmptiesFloatDoubleRiscVWrapper()) ok = false; + if (!EchoArrayOfEmptiesFloatDoubleRiscVReflectionWrapper()) ok = false; + if (!EchoFloatEmpty32kIntRiscVWrapper()) ok = false; + if (!EchoFloatEmpty32kIntRiscVReflectionWrapper()) ok = false; + if (!EchoPackedEmptyFloatLongRiscVWrapper()) ok = false; + if (!EchoPackedEmptyFloatLongRiscVReflectionWrapper()) ok = false; + if (!EchoPackedEmptyFloatLongInIntegerRegsRiscVWrapper()) ok = false; + if (!EchoPackedEmptyFloatLongInIntegerRegsRiscVReflectionWrapper()) ok = false; + if (!EchoPackedEmptyFloatLongSplitRiscVWrapper()) ok = false; + if (!EchoPackedEmptyFloatLongSplitRiscVReflectionWrapper()) ok = false; + if (!EchoPackedEmptyFloatLongOnStackRiscVWrapper()) ok = false; + if (!EchoPackedEmptyFloatLongOnStackRiscVReflectionWrapper()) ok = false; + if (!EchoExplicitFloatLongRiscVWrapper()) ok = false; + if (!EchoExplicitFloatLongRiscVReflectionWrapper()) ok = false; return ok ? 100 : -1; }