From 46c0166b34143e868e6361a9d545314a8bbddbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Sowi=C5=84ski?= Date: Sat, 10 Aug 2024 04:49:16 +0200 Subject: [PATCH] [RISC-V][LoongArch64] Pass FP struct fields at arbitrary offsets in ArgIterator and CallDescrWorker (#105800) * Add calls by reflection to tests * Adjust CallDescrWorker to support passing and returning structs according to floating-point calling convention fully * Adjust ArgIterator so C# and C++ versions match * Merge RISC-V and LoongArch ArgIterator implementations because our ABIs are nearly the same * Update LoongArch CallDescWorker assembly * Update ContainsPointers method name * Update tests by reflection * Missing asmconstants.h update for loongarch * Remove legacy StructFloatFieldInfoFlags * LoongArch typos Co-authored-by: Qiao Pengcheng --------- Co-authored-by: Qiao Pengcheng Co-authored-by: Jan Kotas --- .../Runtime/RiscVLoongArch64FpStruct.cs | 42 -- .../ReadyToRun/ArgIterator.cs | 273 ++---------- .../ReadyToRun/TransitionBlock.cs | 13 +- src/coreclr/vm/argdestination.h | 102 ++--- src/coreclr/vm/callhelpers.cpp | 51 ++- src/coreclr/vm/callhelpers.h | 7 + src/coreclr/vm/callingconvention.h | 208 +++------ src/coreclr/vm/loongarch64/asmconstants.h | 31 +- .../loongarch64/calldescrworkerloongarch64.S | 123 +----- src/coreclr/vm/methodtable.h | 40 -- src/coreclr/vm/reflectioninvocation.cpp | 19 +- src/coreclr/vm/riscv64/asmconstants.h | 31 +- .../vm/riscv64/calldescrworkerriscv64.S | 123 +----- .../JIT/Directed/StructABI/EmptyStructs.cs | 414 ++++++++++++++++++ 14 files changed, 658 insertions(+), 819 deletions(-) diff --git a/src/coreclr/tools/Common/Internal/Runtime/RiscVLoongArch64FpStruct.cs b/src/coreclr/tools/Common/Internal/Runtime/RiscVLoongArch64FpStruct.cs index ba76758241bf5..9652bd7efebc5 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/RiscVLoongArch64FpStruct.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/RiscVLoongArch64FpStruct.cs @@ -9,37 +9,6 @@ namespace Internal.JitInterface { - // StructFloatFieldInfoFlags: used on LoongArch64 and RISC-V architecture as a legacy representation of - // FpStructInRegistersInfo, returned by FpStructInRegistersInfo.ToOldFlags() - // - // `STRUCT_NO_FLOAT_FIELD` means structs are not passed using the float register(s). - // - // Otherwise, and only for structs with no more than two fields and a total struct size no larger - // than two pointers: - // - // The lowest four bits denote the floating-point info: - // bit 0: `1` means there is only one float or double field within the struct. - // bit 1: `1` means only the first field is floating-point type. - // bit 2: `1` means only the second field is floating-point type. - // bit 3: `1` means the two fields are both floating-point type. - // The bits[5:4] denoting whether the field size is 8-bytes: - // bit 4: `1` means the first field's size is 8. - // bit 5: `1` means the second field's size is 8. - // - // Note that bit 0 and 3 cannot both be set. - [Flags] - public enum StructFloatFieldInfoFlags - { - STRUCT_NO_FLOAT_FIELD = 0x0, - STRUCT_FLOAT_FIELD_ONLY_ONE = 0x1, - STRUCT_FLOAT_FIELD_ONLY_TWO = 0x8, - STRUCT_FLOAT_FIELD_FIRST = 0x2, - STRUCT_FLOAT_FIELD_SECOND = 0x4, - STRUCT_FIRST_FIELD_SIZE_IS8 = 0x10, - STRUCT_SECOND_FIELD_SIZE_IS8 = 0x20, - }; - - // Bitfields for FpStructInRegistersInfo.flags [Flags] public enum FpStruct @@ -79,17 +48,6 @@ public struct FpStructInRegistersInfo public uint Size1st() { return 1u << (int)SizeShift1st(); } public uint Size2nd() { return 1u << (int)SizeShift2nd(); } - - 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); - } } internal static class RiscVLoongArch64FpStruct 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 cd945116e3e9c..f2dc1331b2fa3 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 @@ -831,17 +831,10 @@ public int GetNextOffset() break; case TargetArchitecture.LoongArch64: - _loongarch64IdxGenReg = numRegistersUsed; - _loongarch64OfsStack = 0; - - _loongarch64IdxFPReg = 0; - break; - case TargetArchitecture.RiscV64: - _riscv64IdxGenReg = numRegistersUsed; - _riscv64OfsStack = 0; - - _riscv64IdxFPReg = 0; + _rvLa64IdxGenReg = numRegistersUsed; + _rvLa64OfsStack = 0; + _rvLa64IdxFPReg = 0; break; default: throw new NotImplementedException(); @@ -1329,136 +1322,10 @@ public int GetNextOffset() } case TargetArchitecture.LoongArch64: - { - int cFPRegs = 0; - FpStructInRegistersInfo info = new FpStructInRegistersInfo{}; - _hasArgLocDescForStructInRegs = false; - - switch (argType) - { - case CorElementType.ELEMENT_TYPE_R4: - case CorElementType.ELEMENT_TYPE_R8: - // Floating point argument - cFPRegs = 1; - break; - - case CorElementType.ELEMENT_TYPE_VALUETYPE: - { - // Composite greater than 16 bytes should be passed by reference - if (argSize > _transitionBlock.EnregisteredParamTypeMaxSize) - { - argSize = _transitionBlock.PointerSize; - } - else - { - info = RiscVLoongArch64FpStruct.GetFpStructInRegistersInfo( - _argTypeHandle.GetRuntimeTypeHandle(), TargetArchitecture.LoongArch64); - if (info.flags != FpStruct.UseIntCallConv) - { - 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 ((info.flags & (FpStruct.FloatInt | FpStruct.IntFloat)) != 0) - { - Debug.Assert(cFPRegs == 1); - if ((_loongarch64IdxFPReg < _transitionBlock.NumArgumentRegisters) && (_loongarch64IdxGenReg < _transitionBlock.NumArgumentRegisters)) - { - _argLocDescForStructInRegs = new ArgLocDesc(); - _argLocDescForStructInRegs.m_idxFloatReg = _loongarch64IdxFPReg; - _argLocDescForStructInRegs.m_cFloatReg = 1; - - _argLocDescForStructInRegs.m_idxGenReg = _loongarch64IdxGenReg; - _argLocDescForStructInRegs.m_cGenReg = 1; - - _hasArgLocDescForStructInRegs = true; - _argLocDescForStructInRegs.m_structFields = info; - - int argOfsInner = 0; - if ((info.flags & FpStruct.IntFloat) != 0) - { - argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _loongarch64IdxGenReg * _transitionBlock.PointerSize; - } - else - { - argOfsInner = _transitionBlock.OffsetOfFloatArgumentRegisters + _loongarch64IdxFPReg * _transitionBlock.FloatRegisterSize; - } - - _loongarch64IdxFPReg++; - _loongarch64IdxGenReg++; - return argOfsInner; - } - } - else if (cFPRegs + _loongarch64IdxFPReg <= _transitionBlock.NumArgumentRegisters) - { - // Each floating point register in the argument area is 8 bytes. - int argOfsInner = _transitionBlock.OffsetOfFloatArgumentRegisters + _loongarch64IdxFPReg * _transitionBlock.FloatRegisterSize; - const FpStruct twoFloats = FpStruct.BothFloat - | (FpStruct)(2 << (int)FpStruct.PosSizeShift1st) - | (FpStruct)(2 << (int)FpStruct.PosSizeShift2nd); - if (info.flags == twoFloats) - { - // struct with two single-float fields. - _argLocDescForStructInRegs = new ArgLocDesc(); - _argLocDescForStructInRegs.m_idxFloatReg = _loongarch64IdxFPReg; - _argLocDescForStructInRegs.m_cFloatReg = 2; - Debug.Assert(cFPRegs == 2); - Debug.Assert(argSize == 8); - - _hasArgLocDescForStructInRegs = true; - _argLocDescForStructInRegs.m_structFields = info; - } - _loongarch64IdxFPReg += cFPRegs; - return argOfsInner; - } - else - { - _loongarch64IdxFPReg = 8; - } - } - - { - Debug.Assert((cbArg % _transitionBlock.PointerSize) == 0); - - int regSlots = ALIGN_UP(cbArg, _transitionBlock.PointerSize) / _transitionBlock.PointerSize; - // Only R4-R11 are valid argument registers. - if (_loongarch64IdxGenReg + regSlots <= 8) - { - // The entirety of the arg fits in the register slots. - int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _loongarch64IdxGenReg * 8; - _loongarch64IdxGenReg += regSlots; - return argOfsInner; - } - else if (_loongarch64IdxGenReg < 8) - { - int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _loongarch64IdxGenReg * 8; - _loongarch64IdxGenReg = 8; - _loongarch64OfsStack += 8; - return argOfsInner; - } - } - - argOfs = _transitionBlock.OffsetOfArgs + _loongarch64OfsStack; - _loongarch64OfsStack += cbArg; - return argOfs; - } - case TargetArchitecture.RiscV64: { if (IsVarArg) - throw new NotImplementedException("Varargs on RISC-V not supported yet"); + throw new NotImplementedException("Varargs on RISC-V and LoongArch not supported yet"); int cFPRegs = 0; FpStructInRegistersInfo info = new FpStructInRegistersInfo{}; @@ -1501,79 +1368,82 @@ public int GetNextOffset() if (cFPRegs > 0 && !IsVarArg) { + // If there's enough free registers, pass according to hardware floating-point calling convention + if ((info.flags & (FpStruct.FloatInt | FpStruct.IntFloat)) != 0) { Debug.Assert(cFPRegs == 1); + Debug.Assert((info.flags & (FpStruct.OnlyOne | FpStruct.BothFloat)) == 0); - if ((1 + _riscv64IdxFPReg <= _transitionBlock.NumArgumentRegisters) && (1 + _riscv64IdxGenReg <= _transitionBlock.NumArgumentRegisters)) + if ((1 + _rvLa64IdxFPReg <= _transitionBlock.NumArgumentRegisters) && (1 + _rvLa64IdxGenReg <= _transitionBlock.NumArgumentRegisters)) { _argLocDescForStructInRegs = new ArgLocDesc(); - _argLocDescForStructInRegs.m_idxFloatReg = _riscv64IdxFPReg; + _argLocDescForStructInRegs.m_idxFloatReg = _rvLa64IdxFPReg; _argLocDescForStructInRegs.m_cFloatReg = 1; _argLocDescForStructInRegs.m_structFields = info; - _argLocDescForStructInRegs.m_idxGenReg = _riscv64IdxGenReg; + _argLocDescForStructInRegs.m_idxGenReg = _rvLa64IdxGenReg; _argLocDescForStructInRegs.m_cGenReg = 1; _hasArgLocDescForStructInRegs = true; int argOfsInner = ((info.flags & FpStruct.IntFloat) != 0) - ? _transitionBlock.OffsetOfArgumentRegisters + _riscv64IdxGenReg * _transitionBlock.PointerSize - : _transitionBlock.OffsetOfFloatArgumentRegisters + _riscv64IdxFPReg * _transitionBlock.FloatRegisterSize; + ? _transitionBlock.OffsetOfArgumentRegisters + _rvLa64IdxGenReg * _transitionBlock.PointerSize + : _transitionBlock.OffsetOfFloatArgumentRegisters + _rvLa64IdxFPReg * _transitionBlock.FloatRegisterSize; - _riscv64IdxFPReg++; - _riscv64IdxGenReg++; + _rvLa64IdxFPReg++; + _rvLa64IdxGenReg++; return argOfsInner; } } - else if (cFPRegs + _riscv64IdxFPReg <= _transitionBlock.NumArgumentRegisters) + else if (cFPRegs + _rvLa64IdxFPReg <= _transitionBlock.NumArgumentRegisters) { - // Each floating point register in the argument area is 8 bytes. - int argOfsInner = _transitionBlock.OffsetOfFloatArgumentRegisters + _riscv64IdxFPReg * _transitionBlock.FloatRegisterSize; - const FpStruct twoFloats = FpStruct.BothFloat - | (FpStruct)(2 << (int)FpStruct.PosSizeShift1st) - | (FpStruct)(2 << (int)FpStruct.PosSizeShift2nd); - if (info.flags == twoFloats) + int argOfsInner = _transitionBlock.OffsetOfFloatArgumentRegisters + _rvLa64IdxFPReg * _transitionBlock.FloatRegisterSize; + if (info.flags != FpStruct.UseIntCallConv) { - // struct with two single-float fields. + Debug.Assert((info.flags & (FpStruct.OnlyOne | FpStruct.BothFloat)) != 0); _argLocDescForStructInRegs = new ArgLocDesc(); - _argLocDescForStructInRegs.m_idxFloatReg = _riscv64IdxFPReg; - _argLocDescForStructInRegs.m_cFloatReg = 2; - Debug.Assert(cFPRegs == 2); - Debug.Assert(argSize == 8); - _hasArgLocDescForStructInRegs = true; + _argLocDescForStructInRegs.m_idxFloatReg = _rvLa64IdxFPReg; + _argLocDescForStructInRegs.m_cFloatReg = cFPRegs; _argLocDescForStructInRegs.m_structFields = info; } - _riscv64IdxFPReg += cFPRegs; + _rvLa64IdxFPReg += cFPRegs; return argOfsInner; } } { + // Pass according to integer calling convention Debug.Assert((cbArg % _transitionBlock.PointerSize) == 0); int regSlots = ALIGN_UP(cbArg, _transitionBlock.PointerSize) / _transitionBlock.PointerSize; - // Only a0-a7 are valid argument registers. - if (_riscv64IdxGenReg + regSlots <= 8) + if (_rvLa64IdxGenReg + regSlots <= _transitionBlock.NumArgumentRegisters) { // The entirety of the arg fits in the register slots. - int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _riscv64IdxGenReg * 8; - _riscv64IdxGenReg += regSlots; + int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _rvLa64IdxGenReg * _transitionBlock.PointerSize; + _rvLa64IdxGenReg += regSlots; return argOfsInner; } - else if (_riscv64IdxGenReg < 8) + else if (_rvLa64IdxGenReg < _transitionBlock.NumArgumentRegisters) { - int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _riscv64IdxGenReg * 8; - _riscv64IdxGenReg = 8; - _riscv64OfsStack += 8; + // Split argument + Debug.Assert(regSlots == 2); + int lastReg = _transitionBlock.NumArgumentRegisters - 1; + Debug.Assert(_rvLa64IdxGenReg == lastReg, "pass head in last register"); + Debug.Assert(_rvLa64OfsStack == 0, "pass tail in first stack slot"); + + int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + lastReg * _transitionBlock.PointerSize; + _rvLa64IdxGenReg = _transitionBlock.NumArgumentRegisters; + _rvLa64OfsStack = _transitionBlock.PointerSize; return argOfsInner; } } - argOfs = _transitionBlock.OffsetOfArgs + _riscv64OfsStack; - _riscv64OfsStack += cbArg; + // Pass argument entirely on stack + argOfs = _transitionBlock.OffsetOfArgs + _rvLa64OfsStack; + _rvLa64OfsStack += cbArg; return argOfs; } @@ -1860,55 +1730,6 @@ private void ForceSigWalk() } case TargetArchitecture.LoongArch64: - { - if (_hasArgLocDescForStructInRegs) - { - return _argLocDescForStructInRegs; - } - - // LIMITED_METHOD_CONTRACT; - - ArgLocDesc pLoc = new ArgLocDesc(); - - if (_transitionBlock.IsFloatArgumentRegisterOffset(argOffset)) - { - int floatRegOfsInBytes = argOffset - _transitionBlock.OffsetOfFloatArgumentRegisters; - Debug.Assert((floatRegOfsInBytes % _transitionBlock.FloatRegisterSize) == 0); - pLoc.m_idxFloatReg = floatRegOfsInBytes / _transitionBlock.FloatRegisterSize; - pLoc.m_cFloatReg = 1; - - return pLoc; - } - - int byteArgSize = GetArgSize(); - - // Composites greater than 16bytes are passed by reference - TypeHandle dummy; - if (GetArgType(out dummy) == CorElementType.ELEMENT_TYPE_VALUETYPE && GetArgSize() > _transitionBlock.EnregisteredParamTypeMaxSize) - { - byteArgSize = _transitionBlock.PointerSize; - } - - if (!_transitionBlock.IsStackArgumentOffset(argOffset)) - { - pLoc.m_idxGenReg = _transitionBlock.GetArgumentIndexFromOffset(argOffset); - if ((pLoc.m_idxGenReg == 7) && (byteArgSize > _transitionBlock.PointerSize)) - { - pLoc.m_cGenReg = 1; - pLoc.m_byteStackIndex = 0; - pLoc.m_byteStackSize = 8; - } - else - pLoc.m_cGenReg = (short)(ALIGN_UP(byteArgSize, _transitionBlock.PointerSize) / _transitionBlock.PointerSize); - } - else - { - pLoc.m_byteStackIndex = _transitionBlock.GetStackArgumentByteIndexFromOffset(argOffset); - pLoc.m_byteStackSize = _transitionBlock.StackElemSize(byteArgSize, IsValueType(), IsFloatHfa()); - } - return pLoc; - } - case TargetArchitecture.RiscV64: { if (_hasArgLocDescForStructInRegs) @@ -2044,13 +1865,10 @@ private void ForceSigWalk() private int _arm64OfsStack; // Offset of next stack location to be assigned a value private int _arm64IdxFPReg; // Next FP register to be assigned a value - private int _loongarch64IdxGenReg; // Next general register to be assigned a value - private int _loongarch64OfsStack; // Offset of next stack location to be assigned a value - private int _loongarch64IdxFPReg; // Next FP register to be assigned a value - - private int _riscv64IdxGenReg; // Next general register to be assigned a value - private int _riscv64OfsStack; // Offset of next stack location to be assigned a value - private int _riscv64IdxFPReg; // Next FP register to be assigned a value + // RISC-V64 and LoongArch64 + private int _rvLa64IdxGenReg; // Next general register to be assigned a value + private int _rvLa64OfsStack; // Offset of next stack location to be assigned a value + private int _rvLa64IdxFPReg; // Next FP register to be assigned a value // These are enum flags in CallingConventions.h, but that's really ugly in C#, so I've changed them to bools. private bool _ITERATION_STARTED; // Started iterating over arguments @@ -2059,6 +1877,11 @@ private void ForceSigWalk() private bool _RETURN_HAS_RET_BUFFER; // Cached value of HasRetBuffArg private uint _fpReturnSize; + // Offsets of fields returned according to RISC-V/LoongArch 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, @@ -2088,7 +1911,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 621168ecd66eb..940f1f96b8e4e 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 @@ -303,10 +303,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) { @@ -397,8 +399,13 @@ public void ComputeReturnValueTreatment(CorElementType type, TypeHandle thRetTyp if (size <= EnregisteredReturnTypeIntegerMaxSize) { if (IsLoongArch64 || IsRiscV64) - fpReturnSize = (uint)RiscVLoongArch64FpStruct.GetFpStructInRegistersInfo( - thRetType.GetRuntimeTypeHandle(), Architecture).flags; + { + FpStructInRegistersInfo info = RiscVLoongArch64FpStruct.GetFpStructInRegistersInfo( + thRetType.GetRuntimeTypeHandle(), Architecture); + fpReturnSize = (uint)info.flags; + returnedFpFieldOffset1st = info.offset1st; + returnedFpFieldOffset2nd = info.offset2nd; + } break; } diff --git a/src/coreclr/vm/argdestination.h b/src/coreclr/vm/argdestination.h index eef0a531e0a8c..081d96cecdfc2 100644 --- a/src/coreclr/vm/argdestination.h +++ b/src/coreclr/vm/argdestination.h @@ -106,76 +106,54 @@ class ArgDestination #endif // TARGET_RISCV64 _ASSERTE(IsStructPassedInRegs()); + _ASSERTE(destOffset == 0); _ASSERTE(fieldBytes <= 16); - int argOfs = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_argLocDescForStructInRegs->m_idxFloatReg * 8; - - static const FpStruct::Flags twoFloats = FpStruct::Flags(FpStruct::BothFloat - | (2 << FpStruct::PosSizeShift1st) - | (2 << FpStruct::PosSizeShift2nd)); - if (m_argLocDescForStructInRegs->m_structFields.flags == twoFloats) - { // 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.flags & FpStruct::FloatInt) != 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.flags & FpStruct::IntFloat) == 0);//the second field is integer. + 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.SizeShift1st() == 3) - { - *(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. - } + int floatRegOffset = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + + m_argLocDescForStructInRegs->m_idxFloatReg * FLOAT_REGISTER_SIZE; + INT64* floatReg = (INT64*)((char*)m_base + floatRegOffset); + static_assert(sizeof(*floatReg) == FLOAT_REGISTER_SIZE, ""); - argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_argLocDescForStructInRegs->m_idxGenReg * 8; - if (m_argLocDescForStructInRegs->m_structFields.SizeShift1st() == 3 || - m_argLocDescForStructInRegs->m_structFields.SizeShift2nd() == 3) - { - *(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 & (OnlyOne | BothFloat | FloatInt)) // copy first floating field + { + void* field = (char*)src + info.offset1st; + *floatReg++ = (info.SizeShift1st() == 3) ? *(INT64*)field : NanBox | *(INT32*)field; } - else if ((m_argLocDescForStructInRegs->m_structFields.flags & FpStruct::IntFloat) != 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.flags & FpStruct::FloatInt) == 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.SizeShift1st() < 3 && - m_argLocDescForStructInRegs->m_structFields.SizeShift2nd() < 3) - { - // 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) - { - // NOTE: here ignoring the first size. - argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_argLocDescForStructInRegs->m_idxGenReg * 8; - *(UINT64*)((char*)m_base + argOfs) = *(UINT64*)src; - } + 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 (info.flags & (FloatInt | IntFloat)) // copy integer field { - _ASSERTE(!"---------UNReachable-------LoongArch64/RISC-V64!!!"); + int intRegOffset = TransitionBlock::GetOffsetOfArgumentRegisters() + + m_argLocDescForStructInRegs->m_idxGenReg * TARGET_POINTER_SIZE; + void* intReg = (char*)m_base + intRegOffset; + + // Unlike passing primitives on RISC-V, the integer field of a struct passed by hardware floating-point + // calling convention is not type-extended to full register length. Trash the upper bits so the callee + // accidentally assuming it is extended consistently gets a bad value. + RISCV64_ONLY(INDEBUG(*(INT64*)intReg = 0xDadAddedC0ffee00l;)) + + uint32_t offset = (info.flags & IntFloat) ? info.offset1st : info.offset2nd; + void* field = (char*)src + offset; + unsigned sizeShift = (info.flags & IntFloat) ? info.SizeShift1st() : info.SizeShift2nd(); + switch (sizeShift) + { + case 0: *(INT8* )intReg = *(INT8* )field; break; + case 1: *(INT16*)intReg = *(INT16*)field; break; + case 2: *(INT32*)intReg = *(INT32*)field; break; + case 3: *(INT64*)intReg = *(INT64*)field; break; + default: _ASSERTE(false); + } } } diff --git a/src/coreclr/vm/callhelpers.cpp b/src/coreclr/vm/callhelpers.cpp index 277073dae856c..3906d396c87f5 100644 --- a/src/coreclr/vm/callhelpers.cpp +++ b/src/coreclr/vm/callhelpers.cpp @@ -158,6 +158,37 @@ void DispatchCallDebuggerWrapper( PAL_ENDTRY } +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) +void CopyReturnedFpStructFromRegisters(void* dest, UINT64 returnRegs[2], FpStructInRegistersInfo info, + bool handleGcRefs) +{ + _ASSERTE(info.flags != FpStruct::UseIntCallConv); + + auto copyReg = [handleGcRefs, dest, returnRegs](uint32_t destOffset, unsigned regIndex, bool isInt, unsigned sizeShift) + { + const UINT64* srcField = &returnRegs[regIndex]; + void* destField = (char*)dest + destOffset; + int size = 1 << sizeShift; + + static const int ptrShift = 3; + static_assert((1 << ptrShift) == TARGET_POINTER_SIZE, ""); + bool maybeRef = handleGcRefs && isInt && sizeShift == ptrShift && (destOffset & ((1 << ptrShift) - 1)) == 0; + + if (maybeRef) + memmoveGCRefs(destField, srcField, size); + else + memcpyNoGCRefs(destField, srcField, size); + }; + + // returnRegs contain [ fa0, fa1/a0 ]; FpStruct::IntFloat is the only case where the field order is swapped + bool swap = info.flags & FpStruct::IntFloat; + + copyReg(info.offset1st, (swap ? 1 : 0), (info.flags & FpStruct::IntFloat), info.SizeShift1st()); + if ((info.flags & FpStruct::OnlyOne) == 0) + copyReg(info.offset2nd, (swap ? 0 : 1), (info.flags & FpStruct::FloatInt), info.SizeShift2nd()); +} +#endif // TARGET_RISCV64 || TARGET_LOONGARCH64 + // Helper for VM->managed calls with simple signatures. void * DispatchCallSimple( SIZE_T *pSrc, @@ -539,13 +570,7 @@ void MethodDescCallSite::CallTargetWorker(const ARG_SLOT *pArguments, ARG_SLOT * #ifdef CALLDESCR_REGTYPEMAP callDescrData.dwRegTypeMap = dwRegTypeMap; #endif -#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) - // Temporary conversion to old flags, CallDescrWorker needs to be overhauled anyway - // to work with arbitrary field offsets and sizes, and support struct size > 16 on RISC-V. - callDescrData.fpReturnSize = FpStructInRegistersInfo{FpStruct::Flags(fpReturnSize)}.ToOldFlags(); -#else callDescrData.fpReturnSize = fpReturnSize; -#endif callDescrData.pTarget = m_pCallTarget; #ifdef FEATURE_INTERPRETER @@ -560,15 +585,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); +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + if (callDescrData.fpReturnSize != FpStruct::UseIntCallConv) + { + FpStructInRegistersInfo info = m_argIt.GetReturnFpStructInRegistersInfo(); + CopyReturnedFpStructFromRegisters(pReturnValue, callDescrData.returnValue, info, false); + } + else +#endif // defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + { + 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 f6dd105263b2f..b39586d82c341 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) || defined(TARGET_LOONGARCH64) +// Copy structs returned according to floating-point calling convention from 'returnRegs' containing struct fields +// (each returned in one register) as they are filled by CallDescrWorkerInternal, to the final destination in memory +// 'dest' respecting the struct's layout described in 'info'. +void CopyReturnedFpStructFromRegisters(void* dest, UINT64 returnRegs[2], FpStructInRegistersInfo info, bool handleGcRefs); +#endif // defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + bool IsCerRootMethod(MethodDesc *pMD); class MethodDescCallSite diff --git a/src/coreclr/vm/callingconvention.h b/src/coreclr/vm/callingconvention.h index e2bf88af122f8..9991e0fc6b63e 100644 --- a/src/coreclr/vm/callingconvention.h +++ b/src/coreclr/vm/callingconvention.h @@ -374,6 +374,10 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE { WRAPPER_NO_CONTRACT; m_dwFlags = 0; +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + 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) || defined(TARGET_LOONGARCH64) + 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 // defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + #ifdef TARGET_X86 //========================================================================= // Indicates whether an argument is to be put in a register using the @@ -909,6 +928,11 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE protected: DWORD m_dwFlags; // Cached flags int m_nSizeOfArgStack; // Cached value of SizeOfArgStack +#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + // Cached offsets of struct fields returned according to hardware floating-point calling convention + // (FpStruct::Flags are packed in m_dwFlags) + unsigned m_returnedFpFieldOffsets[ENREGISTERED_RETURNTYPE_MAXSIZE / sizeof(ARG_SLOT)]; +#endif DWORD m_argNum; @@ -1202,11 +1226,7 @@ int ArgIteratorTemplate::GetNextOffset() m_ofsStack = 0; m_idxFPReg = 0; -#elif defined(TARGET_LOONGARCH64) - m_idxGenReg = numRegistersUsed; - m_ofsStack = 0; - m_idxFPReg = 0; -#elif defined(TARGET_RISCV64) +#elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) m_idxGenReg = numRegistersUsed; m_ofsStack = 0; m_idxFPReg = 0; @@ -1667,14 +1687,13 @@ int ArgIteratorTemplate::GetNextOffset() int argOfs = TransitionBlock::GetOffsetOfArgs() + m_ofsStack; m_ofsStack += cbArg; return argOfs; -#elif defined(TARGET_LOONGARCH64) - +#elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) + assert(!this->IsVarArg()); // Varargs on RISC-V and LoongArch not supported yet int cFPRegs = 0; FpStructInRegistersInfo info = {}; switch (argType) { - case ELEMENT_TYPE_R4: case ELEMENT_TYPE_R8: // Floating point argument @@ -1713,126 +1732,8 @@ int ArgIteratorTemplate::GetNextOffset() if (cFPRegs > 0 && !this->IsVarArg()) { - if (info.flags & (FpStruct::FloatInt | FpStruct::IntFloat)) - { - assert(cFPRegs == 1); - assert((info.flags & (FpStruct::OnlyOne | FpStruct::BothFloat)) == 0); - - if ((1 + m_idxFPReg <= NUM_ARGUMENT_REGISTERS) && (1 + m_idxGenReg <= NUM_ARGUMENT_REGISTERS)) - { - int argOfs = 0; - m_argLocDescForStructInRegs.Init(); - m_argLocDescForStructInRegs.m_idxFloatReg = m_idxFPReg; - m_argLocDescForStructInRegs.m_cFloatReg = 1; - m_argLocDescForStructInRegs.m_idxGenReg = m_idxGenReg; - m_argLocDescForStructInRegs.m_cGenReg = 1; - m_argLocDescForStructInRegs.m_structFields = info; + // If there's enough free registers, pass according to hardware floating-point calling convention - if (info.flags & FpStruct::IntFloat) - { - argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * 8; - } - else - { - argOfs = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_idxFPReg * 8; - } - - m_idxFPReg += 1; - m_idxGenReg += 1; - - m_hasArgLocDescForStructInRegs = true; - - return argOfs; - } - } - else if (cFPRegs + m_idxFPReg <= NUM_ARGUMENT_REGISTERS) - { - int argOfs = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_idxFPReg * 8; - static const FpStruct::Flags twoFloats = FpStruct::Flags(FpStruct::BothFloat - | (2 << FpStruct::PosSizeShift1st) - | (2 << FpStruct::PosSizeShift2nd)); - if (info.flags == twoFloats) // struct with two float-fields. - { - 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 = info; - } - m_idxFPReg += cFPRegs; - return argOfs; - } - } - - { - 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 argOfs = TransitionBlock::GetOffsetOfArgs() + m_ofsStack; - m_ofsStack += ALIGN_UP(cbArg, TARGET_POINTER_SIZE); - - return argOfs; -#elif defined(TARGET_RISCV64) - assert(!this->IsVarArg()); // Varargs on RISC-V not supported yet - int cFPRegs = 0; - FpStructInRegistersInfo info = {}; - - switch (argType) - { - case ELEMENT_TYPE_R4: - case ELEMENT_TYPE_R8: - // 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) - { - argSize = sizeof(TADDR); - } - else - { - info = MethodTable::GetFpStructInRegistersInfo(thValueType); - if (info.flags != FpStruct::UseIntCallConv) - { - cFPRegs = (info.flags & FpStruct::BothFloat) ? 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 (info.flags & (FpStruct::FloatInt | FpStruct::IntFloat)) { assert(cFPRegs == 1); @@ -1843,36 +1744,33 @@ int ArgIteratorTemplate::GetNextOffset() m_argLocDescForStructInRegs.Init(); m_argLocDescForStructInRegs.m_idxFloatReg = m_idxFPReg; m_argLocDescForStructInRegs.m_cFloatReg = 1; - int argOfs = (info.flags & FpStruct::IntFloat) - ? TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * 8 - : TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_idxFPReg * 8; - m_idxFPReg += 1; m_argLocDescForStructInRegs.m_structFields = info; m_argLocDescForStructInRegs.m_idxGenReg = m_idxGenReg; m_argLocDescForStructInRegs.m_cGenReg = 1; - m_idxGenReg += 1; m_hasArgLocDescForStructInRegs = true; + int argOfs = (info.flags & FpStruct::IntFloat) + ? TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * TARGET_POINTER_SIZE + : TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_idxFPReg * FLOAT_REGISTER_SIZE; + + m_idxFPReg++; + m_idxGenReg++; return argOfs; } } else if (cFPRegs + m_idxFPReg <= NUM_ARGUMENT_REGISTERS) { - int argOfs = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_idxFPReg * 8; - static const FpStruct::Flags twoFloats = FpStruct::Flags(FpStruct::BothFloat - | (2 << FpStruct::PosSizeShift1st) - | (2 << FpStruct::PosSizeShift2nd)); - if (info.flags == twoFloats) // struct with two float-fields. + int argOfs = TransitionBlock::GetOffsetOfFloatArgumentRegisters() + m_idxFPReg * FLOAT_REGISTER_SIZE; + if (info.flags != FpStruct::UseIntCallConv) { + assert(info.flags & (FpStruct::OnlyOne | FpStruct::BothFloat)); 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_cFloatReg = cFPRegs; m_argLocDescForStructInRegs.m_structFields = info; } m_idxFPReg += cFPRegs; @@ -1881,23 +1779,33 @@ int ArgIteratorTemplate::GetNextOffset() } { + // Pass according to integer calling convention + assert((cbArg % TARGET_POINTER_SIZE) == 0); + 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; + // The entirety of the arg fits in the register slots. + int argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * TARGET_POINTER_SIZE; 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); + // Split argument + assert(regSlots == 2); + static const int lastReg = NUM_ARGUMENT_REGISTERS - 1; + assert(m_idxGenReg == lastReg); // pass head in last register + assert(m_ofsStack == 0); // pass tail in first stack slot + + int argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + lastReg * TARGET_POINTER_SIZE; m_idxGenReg = NUM_ARGUMENT_REGISTERS; + m_ofsStack = TARGET_POINTER_SIZE; return argOfs; } } + // Pass argument entirely on stack int argOfs = TransitionBlock::GetOffsetOfArgs() + m_ofsStack; m_ofsStack += ALIGN_UP(cbArg, TARGET_POINTER_SIZE); @@ -2017,20 +1925,14 @@ void ArgIteratorTemplate::ComputeReturnFlags() } #endif // defined(TARGET_X86) || defined(TARGET_AMD64) -#if defined(TARGET_LOONGARCH64) - if (size <= ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE) - { - assert(!thValueType.IsTypeDesc()); - FpStructInRegistersInfo info = MethodTable::GetFpStructInRegistersInfo(thValueType); - flags |= info.flags << RETURN_FP_SIZE_SHIFT; - break; - } -#elif defined(TARGET_RISCV64) +#if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) if (size <= ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE) { assert(!thValueType.IsTypeDesc()); FpStructInRegistersInfo info = MethodTable::GetFpStructInRegistersInfo(thValueType); flags |= info.flags << RETURN_FP_SIZE_SHIFT; + m_returnedFpFieldOffsets[0] = info.offset1st; + m_returnedFpFieldOffsets[1] = info.offset2nd; break; } #else diff --git a/src/coreclr/vm/loongarch64/asmconstants.h b/src/coreclr/vm/loongarch64/asmconstants.h index 9754443c20be5..c4fbdc443e9c4 100644 --- a/src/coreclr/vm/loongarch64/asmconstants.h +++ b/src/coreclr/vm/loongarch64/asmconstants.h @@ -71,35 +71,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/loongarch64/calldescrworkerloongarch64.S b/src/coreclr/vm/loongarch64/calldescrworkerloongarch64.S index 4c7069c40eff0..17458bfaef07d 100644 --- a/src/coreclr/vm/loongarch64/calldescrworkerloongarch64.S +++ b/src/coreclr/vm/loongarch64/calldescrworkerloongarch64.S @@ -76,127 +76,28 @@ LOCAL_LABEL(CallDescrWorkerInternalReturnAddress): ld.w $a3, $s0, CallDescrData__fpReturnSize - // 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) + // Struct returned according to hardware floating-point calling convention. + // Just save the returned registers ($fa0, $fa1/$a0) and let CopyReturnedFpStructFromRegisters worry about placing + // the fields as they were originally laid out in memory. - ori $t4, $zero, CallDescrData__flagOneDouble - beq $t4, $a3, LOCAL_LABEL(DoubleReturn) + fst.d $f0, $s0, CallDescrData__returnValue // $fa0 is always occupied; we have at least one floating field - ori $t4, $zero, CallDescrData__flagFloatInt - beq $t4, $a3, LOCAL_LABEL(FloatIntReturn) + andi $a3, $a3, FpStruct__BothFloat + bne $a3, $zero, LOCAL_LABEL(SecondFieldFloatReturn) - ori $t4, $zero, CallDescrData__flagDoubleInt - beq $t4, $a3, LOCAL_LABEL(DoubleIntReturn) - - 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) - - //b LOCAL_LABEL(NotCorrectReturn) -LOCAL_LABEL(NotCorrectReturn): - st.w $ra, $zero, 0 - EMIT_BREAKPOINT // Unreachable - -LOCAL_LABEL(FloatReturn): - fst.s $f0, $s0, CallDescrData__returnValue - b LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(DoubleReturn): - fst.d $f0, $s0, CallDescrData__returnValue - b LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(FloatIntReturn): - fst.s $f0, $s0, CallDescrData__returnValue - st.w $a0, $s0, CallDescrData__returnValue + 4 - b LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(DoubleIntReturn): - fst.d $f0, $s0, CallDescrData__returnValue - st.w $a0, $s0, CallDescrData__returnValue + 8 - b LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(FloatLongReturn): - fst.s $f0, $s0, CallDescrData__returnValue - st.d $a0, $s0, CallDescrData__returnValue + 8 - b LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(DoubleLongReturn): - fst.d $f0, $s0, CallDescrData__returnValue - st.d $a0, $s0, CallDescrData__returnValue + 8 - b LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(IntFloatReturn): - st.w $a0, $s0, CallDescrData__returnValue - fst.s $f0, $s0, CallDescrData__returnValue + 4 - b LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(LongFloatReturn): - st.d $a0, $s0, CallDescrData__returnValue - fst.s $f0, $s0, CallDescrData__returnValue + 8 - b LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(IntDoubleReturn): - st.w $a0, $s0, CallDescrData__returnValue - fst.d $f0, $s0, CallDescrData__returnValue + 8 - b LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(LongDoubleReturn): - st.d $a0, $s0, CallDescrData__returnValue - fst.d $f0, $s0, CallDescrData__returnValue + 8 - b LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(FloatFloatReturn): - fst.s $f0, $s0, CallDescrData__returnValue - fst.s $f1, $s0, CallDescrData__returnValue + 4 - b LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(DoubleFloatReturn): - fst.d $f0, $s0, CallDescrData__returnValue - fst.s $f1, $s0, CallDescrData__returnValue + 8 - b LOCAL_LABEL(ReturnDone) - -LOCAL_LABEL(FloatDoubleReturn): - fst.s $f0, $s0, CallDescrData__returnValue - fst.d $f1, $s0, CallDescrData__returnValue + 8 + // The second returned register is integer (FpStruct::FloatInt | FpStruct::IntFloat) + // Note: it will also go in here for FpStruct::OnlyOne but storing a register of trash doesn't hurt + st.d $a0, $s0, CallDescrData__returnValue + 8 b LOCAL_LABEL(ReturnDone) -LOCAL_LABEL(DoubleDoubleReturn): - fst.d $f0, $s0, CallDescrData__returnValue - fst.d $f1, $s0, CallDescrData__returnValue + 8 +LOCAL_LABEL(SecondFieldFloatReturn): + fst.d $fa1, $s0, CallDescrData__returnValue + 8 b LOCAL_LABEL(ReturnDone) LOCAL_LABEL(IntReturn): - // Save return value into retbuf for int + // Save struct returned according to integer calling convention st.d $a0, $s0, CallDescrData__returnValue st.d $a1, $s0, CallDescrData__returnValue + 8 diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 2f0593e2eac4b..751ec9d0855dd 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -802,35 +802,6 @@ typedef DPTR(SystemVStructRegisterPassingHelper) SystemVStructRegisterPassingHel #endif // UNIX_AMD64_ABI_ITF #if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) -// StructFloatFieldInfoFlags: used on LoongArch64 and RISC-V architecture as a legacy representation of -// FpStructInRegistersInfo, returned by FpStructInRegistersInfo::ToOldFlags() -// -// `STRUCT_NO_FLOAT_FIELD` means structs are not passed using the float register(s). -// -// Otherwise, and only for structs with no more than two fields and a total struct size no larger -// than two pointers: -// -// The lowest four bits denote the floating-point info: -// bit 0: `1` means there is only one float or double field within the struct. -// bit 1: `1` means only the first field is floating-point type. -// bit 2: `1` means only the second field is floating-point type. -// bit 3: `1` means the two fields are both floating-point type. -// The bits[5:4] denoting whether the field size is 8-bytes: -// bit 4: `1` means the first field's size is 8. -// bit 5: `1` means the second field's size is 8. -// -// Note that bit 0 and 3 cannot both be set. -enum StructFloatFieldInfoFlags -{ - STRUCT_NO_FLOAT_FIELD = 0x0, - STRUCT_FLOAT_FIELD_ONLY_ONE = 0x1, - STRUCT_FLOAT_FIELD_ONLY_TWO = 0x8, - STRUCT_FLOAT_FIELD_FIRST = 0x2, - STRUCT_FLOAT_FIELD_SECOND = 0x4, - STRUCT_FIRST_FIELD_SIZE_IS8 = 0x10, - STRUCT_SECOND_FIELD_SIZE_IS8 = 0x20, -}; - // Bitfields for FpStructInRegistersInfo::flags namespace FpStruct { @@ -883,17 +854,6 @@ struct FpStructInRegistersInfo 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)); - } }; #endif // defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 165507c7ef9a3..37dd3e6635a24 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -497,13 +497,7 @@ FCIMPL4(Object*, RuntimeMethodHandle::InvokeMethod, #ifdef CALLDESCR_REGTYPEMAP callDescrData.dwRegTypeMap = 0; #endif -#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) - // Temporary conversion to old flags, CallDescrWorker needs to be overhauled anyway - // to work with arbitrary field offsets and sizes, and support struct size > 16 on RISC-V. - callDescrData.fpReturnSize = FpStructInRegistersInfo{FpStruct::Flags(argit.GetFPReturnSize())}.ToOldFlags(); -#else callDescrData.fpReturnSize = argit.GetFPReturnSize(); -#endif // This is duplicated logic from MethodDesc::GetCallTarget PCODE pTarget; @@ -709,7 +703,18 @@ 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) || defined(TARGET_LOONGARCH64) + if (callDescrData.fpReturnSize != FpStruct::UseIntCallConv) + { + FpStructInRegistersInfo info = argit.GetReturnFpStructInRegistersInfo(); + bool hasPointers = gc.retVal->GetMethodTable()->ContainsGCPointers(); + CopyReturnedFpStructFromRegisters(gc.retVal->GetData(), callDescrData.returnValue, info, hasPointers); + } + else +#endif // defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) + { + 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 0ba094537ad3e..a2fb19faef15d 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 2139787bee149..54725758b41b2 100644 --- a/src/coreclr/vm/riscv64/calldescrworkerriscv64.S +++ b/src/coreclr/vm/riscv64/calldescrworkerriscv64.S @@ -74,131 +74,32 @@ 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) + // Struct returned according to hardware floating-point calling convention. + // Just save the returned registers (fa0, fa1/a0) and let CopyReturnedFpStructFromRegisters worry about placing + // the fields as they were originally laid out in memory. - ori t4, zero, CallDescrData__flagOneDouble - beq t4, a3, LOCAL_LABEL(DoubleReturn) + fsd fa0, CallDescrData__returnValue(s1) // fa0 is always occupied; we have at least one floating field - ori t4, zero, CallDescrData__flagFloatInt - beq t4, a3, LOCAL_LABEL(FloatIntReturn) + andi a3, a3, FpStruct__BothFloat + bne a3, zero, LOCAL_LABEL(SecondFieldFloatReturn) - ori t4, zero, CallDescrData__flagDoubleInt - beq t4, a3, LOCAL_LABEL(DoubleIntReturn) - - 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 register is integer (FpStruct::FloatInt | FpStruct::IntFloat) + // Note: it will also go in here for FpStruct::OnlyOne but storing a register of trash doesn't hurt + 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/tests/JIT/Directed/StructABI/EmptyStructs.cs b/src/tests/JIT/Directed/StructABI/EmptyStructs.cs index 89d2e27ef3e62..0db15f5a7bff8 100644 --- a/src/tests/JIT/Directed/StructABI/EmptyStructs.cs +++ b/src/tests/JIT/Directed/StructABI/EmptyStructs.cs @@ -49,6 +49,23 @@ public static void Test_Empty_Sanity() Assert.Equal(0, native); Assert.Equal(0, managed); } + + [Fact] + [ActiveIssue(RiscVClangEmptyStructsIgnored, typeof(Program), nameof(IsRiscV64))] + [ActiveIssue(Arm32ClangEmptyStructsIgnored, typeof(Program), nameof(IsArm32))] + [ActiveIssue(SystemVPassNoClassEightbytes, typeof(Program), nameof(IsSystemV))] + [ActiveIssue(ProblemsWithEmptyStructPassing, typeof(Program), nameof(IsArm64))] + public static void Test_Empty_ByReflection_Sanity() + { + Empty empty = new Empty{}; + int native = (int)typeof(Program).GetMethod("Echo_Empty_Sanity").Invoke( + null, new object[] {-2, 3f, empty, -3, 2f}); + int managed = (int)typeof(Program).GetMethod("Echo_Empty_Sanity_Managed").Invoke( + null, new object[] {-2, 3f, empty, -3, 2f}); + + Assert.Equal(0, native); + Assert.Equal(0, managed); + } #endregion #region IntEmpty_SystemVTests @@ -87,6 +104,19 @@ public static void Test_IntEmpty_SysV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact] + public static void Test_IntEmpty_ByReflection_SysV() + { + var expected = (IntEmpty)typeof(IntEmpty).GetMethod("Get").Invoke(null, new object[] {}); + var native = (IntEmpty)typeof(Program).GetMethod("Echo_IntEmpty_SysV").Invoke( + null, new object[] {0, 0f, expected, 1, -1f}); + var managed = (IntEmpty)typeof(Program).GetMethod("Echo_IntEmpty_SysV_Managed").Invoke( + null, new object[] {0, 0f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion #region IntEmptyPair_SystemVTests @@ -125,6 +155,19 @@ public static void Test_IntEmptyPair_SysV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact] + public static void Test_IntEmptyPair_ByReflection_SysV() + { + var expected = IntEmptyPair.Get(); + var native = (IntEmptyPair)typeof(Program).GetMethod("Echo_IntEmptyPair_SysV").Invoke( + null, new object[] {0, 0f, expected, 1, -1f}); + var managed = (IntEmptyPair)typeof(Program).GetMethod("Echo_IntEmptyPair_SysV_Managed").Invoke( + null, new object[] {0, 0f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion #region EmptyFloatIntInt_SystemVTests @@ -167,6 +210,19 @@ public static void Test_EmptyFloatIntInt_SysV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact] + public static void Test_EmptyFloatIntInt_ByReflection_SysV() + { + var expected = EmptyFloatIntInt.Get(); + var native = (EmptyFloatIntInt)typeof(Program).GetMethod("Echo_EmptyFloatIntInt_SysV").Invoke( + null, new object[] {0, 0f, expected, 1, -1f}); + var managed = (EmptyFloatIntInt)typeof(Program).GetMethod("Echo_EmptyFloatIntInt_SysV_Managed").Invoke( + null, new object[] {0, 0f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion #region FloatFloatEmptyFloat_SystemVTests @@ -209,6 +265,19 @@ public static void Test_FloatFloatEmptyFloat_SysV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact] + public static void Test_FloatFloatEmptyFloat_ByReflection_SysV() + { + var expected = FloatFloatEmptyFloat.Get(); + var native = (FloatFloatEmptyFloat)typeof(Program).GetMethod("Echo_FloatFloatEmptyFloat_SysV").Invoke( + null, new object[] {0, 0f, expected, 1, -1f}); + var managed = (FloatFloatEmptyFloat)typeof(Program).GetMethod("Echo_FloatFloatEmptyFloat_SysV_Managed").Invoke( + null, new object[] {0, 0f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion public struct Eight @@ -255,6 +324,19 @@ public static void Test_Empty8Float_RiscV() Assert.Equal(expected, managed); } + [Fact, ActiveIssue(SystemVPassNoClassEightbytes, typeof(Program), nameof(IsSystemV))] + public static void Test_Empty8Float_ByReflection_RiscV() + { + var expected = Empty8Float.Get(); + var native = (Empty8Float)typeof(Program).GetMethod("Echo_Empty8Float_RiscV").Invoke( + null, new object[] {0, 0f, expected, -1, 1f}); + var managed = (Empty8Float)typeof(Program).GetMethod("Echo_Empty8Float_RiscV_Managed").Invoke( + null, new object[] {0, 0f, expected, -1, 1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern Empty8Float Echo_Empty8Float_InIntegerRegs_RiscV( int a0, @@ -284,6 +366,19 @@ public static void Test_Empty8Float_InIntegerRegs_RiscV() Assert.Equal(expected, managed); } + [Fact, ActiveIssue(SystemVPassNoClassEightbytes, typeof(Program), nameof(IsSystemV))] + public static void Test_Empty8Float_InIntegerRegs_ByReflection_RiscV() + { + var expected = Empty8Float.Get(); + var native = (Empty8Float)typeof(Program).GetMethod("Echo_Empty8Float_InIntegerRegs_RiscV").Invoke( + null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + var managed = (Empty8Float)typeof(Program).GetMethod("Echo_Empty8Float_InIntegerRegs_RiscV_Managed").Invoke( + null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern Empty8Float Echo_Empty8Float_Split_RiscV( int a0, int a1, int a2, int a3, int a4, int a5, int a6, @@ -313,6 +408,19 @@ public static void Test_Empty8Float_Split_RiscV() Assert.Equal(expected, managed); } + [Fact, ActiveIssue(SystemVPassNoClassEightbytes, typeof(Program), nameof(IsSystemV))] + public static void Test_Empty8Float_Split_ByReflection_RiscV() + { + var expected = Empty8Float.Get(); + var native = (Empty8Float)typeof(Program).GetMethod("Echo_Empty8Float_Split_RiscV").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + var managed = (Empty8Float)typeof(Program).GetMethod("Echo_Empty8Float_Split_RiscV_Managed").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern Empty8Float Echo_Empty8Float_OnStack_RiscV( int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, @@ -341,6 +449,19 @@ public static void Test_Empty8Float_OnStack_RiscV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact, ActiveIssue(SystemVPassNoClassEightbytes, typeof(Program), nameof(IsSystemV))] + public static void Test_Empty8Float_OnStack_ByReflection_RiscV() + { + var expected = Empty8Float.Get(); + var native = (Empty8Float)typeof(Program).GetMethod("Echo_Empty8Float_OnStack_RiscV").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + var managed = (Empty8Float)typeof(Program).GetMethod("Echo_Empty8Float_OnStack_RiscV_Managed").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion #region FloatEmpty8Float_RiscVTests @@ -383,6 +504,19 @@ public static void Test_FloatEmpty8Float_RiscV() Assert.Equal(expected, managed); } + [Fact] + public static void Test_FloatEmpty8Float_ByReflection_RiscV() + { + var expected = FloatEmpty8Float.Get(); + var native = (FloatEmpty8Float)typeof(Program).GetMethod("Echo_FloatEmpty8Float_RiscV").Invoke( + null, new object[] {0, 0f, expected, -1, 1f}); + var managed = (FloatEmpty8Float)typeof(Program).GetMethod("Echo_FloatEmpty8Float_RiscV_Managed").Invoke( + null, new object[] {0, 0f, expected, -1, 1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern FloatEmpty8Float Echo_FloatEmpty8Float_InIntegerRegs_RiscV( int a0, @@ -412,6 +546,19 @@ public static void Test_FloatEmpty8Float_InIntegerRegs_RiscV() Assert.Equal(expected, managed); } + [Fact] + public static void Test_FloatEmpty8Float_InIntegerRegs_ByReflection_RiscV() + { + var expected = FloatEmpty8Float.Get(); + var native = (FloatEmpty8Float)typeof(Program).GetMethod("Echo_FloatEmpty8Float_InIntegerRegs_RiscV").Invoke( + null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + var managed = (FloatEmpty8Float)typeof(Program).GetMethod("Echo_FloatEmpty8Float_InIntegerRegs_RiscV_Managed").Invoke( + null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern FloatEmpty8Float Echo_FloatEmpty8Float_Split_RiscV( int a0, int a1, int a2, int a3, int a4, int a5, int a6, @@ -441,6 +588,19 @@ public static void Test_FloatEmpty8Float_Split_RiscV() Assert.Equal(expected, managed); } + [Fact] + public static void Test_FloatEmpty8Float_Split_ByReflection_RiscV() + { + var expected = FloatEmpty8Float.Get(); + var native = (FloatEmpty8Float)typeof(Program).GetMethod("Echo_FloatEmpty8Float_Split_RiscV").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, -2, 2f}); + var managed = (FloatEmpty8Float)typeof(Program).GetMethod("Echo_FloatEmpty8Float_Split_RiscV_Managed").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, -2, 2f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern FloatEmpty8Float Echo_FloatEmpty8Float_OnStack_RiscV( int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, @@ -469,6 +629,19 @@ public static void Test_FloatEmpty8Float_OnStack_RiscV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact] + public static void Test_FloatEmpty8Float_OnStack_ByReflection_RiscV() + { + var expected = FloatEmpty8Float.Get(); + var native = (FloatEmpty8Float)typeof(Program).GetMethod("Echo_FloatEmpty8Float_OnStack_RiscV").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 3, -3f}); + var managed = (FloatEmpty8Float)typeof(Program).GetMethod("Echo_FloatEmpty8Float_OnStack_RiscV_Managed").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 3, -3f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion #region FloatEmptyShort_RiscVTests @@ -511,6 +684,19 @@ public static void Test_FloatEmptyShort_RiscV() Assert.Equal(expected, managed); } + [Fact] + public static void Test_FloatEmptyShort_ByReflection_RiscV() + { + var expected = FloatEmptyShort.Get(); + var native = (FloatEmptyShort)typeof(Program).GetMethod("Echo_FloatEmptyShort_RiscV").Invoke( + null, new object[] {0, 0f, expected, -1, 1f}); + var managed = (FloatEmptyShort)typeof(Program).GetMethod("Echo_FloatEmptyShort_RiscV_Managed").Invoke( + null, new object[] {0, 0f, expected, -1, 1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern FloatEmptyShort Echo_FloatEmptyShort_InIntegerRegs_RiscV( int a0, @@ -540,6 +726,19 @@ public static void Test_FloatEmptyShort_InIntegerRegs_RiscV() Assert.Equal(expected, managed); } + [Fact] + public static void Test_FloatEmptyShort_InIntegerRegs_ByReflection_RiscV() + { + var expected = FloatEmptyShort.Get(); + var native = (FloatEmptyShort)typeof(Program).GetMethod("Echo_FloatEmptyShort_InIntegerRegs_RiscV").Invoke( + null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + var managed = (FloatEmptyShort)typeof(Program).GetMethod("Echo_FloatEmptyShort_InIntegerRegs_RiscV_Managed").Invoke( + null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern FloatEmptyShort Echo_FloatEmptyShort_OnStack_RiscV( int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, @@ -568,6 +767,19 @@ public static void Test_FloatEmptyShort_OnStack_RiscV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact] + public static void Test_FloatEmptyShort_OnStack_ByReflection_RiscV() + { + var expected = FloatEmptyShort.Get(); + var native = (FloatEmptyShort)typeof(Program).GetMethod("Echo_FloatEmptyShort_OnStack_RiscV").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 2, -2f}); + var managed = (FloatEmptyShort)typeof(Program).GetMethod("Echo_FloatEmptyShort_OnStack_RiscV_Managed").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 2, -2f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion #region EmptyFloatEmpty5Sbyte_RiscVTests @@ -610,6 +822,19 @@ public static void Test_EmptyFloatEmpty5Sbyte_RiscV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact] + public static void Test_EmptyFloatEmpty5Sbyte_ByReflection_RiscV() + { + var expected = EmptyFloatEmpty5Sbyte.Get(); + var native = (EmptyFloatEmpty5Sbyte)typeof(Program).GetMethod("Echo_EmptyFloatEmpty5Sbyte_RiscV").Invoke( + null, new object[] {0, 0f, expected, -1, 1f}); + var managed = (EmptyFloatEmpty5Sbyte)typeof(Program).GetMethod("Echo_EmptyFloatEmpty5Sbyte_RiscV_Managed").Invoke( + null, new object[] {0, 0f, expected, -1, 1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion #region EmptyFloatEmpty5Byte_RiscVTests @@ -653,6 +878,19 @@ public static void Test_EmptyFloatEmpty5Byte_RiscV() Assert.Equal(expected, managed); } + [Fact] + public static void Test_EmptyFloatEmpty5Byte_ByReflection_RiscV() + { + var expected = EmptyFloatEmpty5Byte.Get(); + var native = (EmptyFloatEmpty5Byte)typeof(Program).GetMethod("Echo_EmptyFloatEmpty5Byte_RiscV").Invoke( + null, new object[] {0, 0f, expected, -1, 1f}); + var managed = (EmptyFloatEmpty5Byte)typeof(Program).GetMethod("Echo_EmptyFloatEmpty5Byte_RiscV_Managed").Invoke( + null, new object[] {0, 0f, expected, -1, 1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern EmptyFloatEmpty5Byte Echo_EmptyFloatEmpty5Byte_InIntegerRegs_RiscV( int a0, @@ -682,6 +920,19 @@ public static void Test_EmptyFloatEmpty5Byte_InIntegerRegs_RiscV() Assert.Equal(expected, managed); } + [Fact] + public static void Test_EmptyFloatEmpty5Byte_InIntegerRegs_ByReflection_RiscV() + { + var expected = EmptyFloatEmpty5Byte.Get(); + var native = (EmptyFloatEmpty5Byte)typeof(Program).GetMethod("Echo_EmptyFloatEmpty5Byte_InIntegerRegs_RiscV").Invoke( + null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + var managed = (EmptyFloatEmpty5Byte)typeof(Program).GetMethod("Echo_EmptyFloatEmpty5Byte_InIntegerRegs_RiscV_Managed").Invoke( + null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern EmptyFloatEmpty5Byte Echo_EmptyFloatEmpty5Byte_Split_RiscV( int a0, int a1, int a2, int a3, int a4, int a5, int a6, @@ -711,6 +962,19 @@ public static void Test_EmptyFloatEmpty5Byte_Split_RiscV() Assert.Equal(expected, managed); } + [Fact] + public static void Test_EmptyFloatEmpty5Byte_Split_ByReflection_RiscV() + { + var expected = EmptyFloatEmpty5Byte.Get(); + var native = (EmptyFloatEmpty5Byte)typeof(Program).GetMethod("Echo_EmptyFloatEmpty5Byte_Split_RiscV").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + var managed = (EmptyFloatEmpty5Byte)typeof(Program).GetMethod("Echo_EmptyFloatEmpty5Byte_Split_RiscV_Managed").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern EmptyFloatEmpty5Byte Echo_EmptyFloatEmpty5Byte_OnStack_RiscV( int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, @@ -739,6 +1003,19 @@ public static void Test_EmptyFloatEmpty5Byte_OnStack_RiscV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact] + public static void Test_EmptyFloatEmpty5Byte_OnStack_ByReflection_RiscV() + { + var expected = EmptyFloatEmpty5Byte.Get(); + var native = (EmptyFloatEmpty5Byte)typeof(Program).GetMethod("Echo_EmptyFloatEmpty5Byte_OnStack_RiscV").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + var managed = (EmptyFloatEmpty5Byte)typeof(Program).GetMethod("Echo_EmptyFloatEmpty5Byte_OnStack_RiscV_Managed").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion #region NestedEmpty_RiscVTests @@ -790,6 +1067,19 @@ public static void Test_DoubleFloatNestedEmpty_RiscV() Assert.Equal(expected, managed); } + [Fact] + public static void Test_DoubleFloatNestedEmpty_ByReflection_RiscV() + { + var expected = DoubleFloatNestedEmpty.Get(); + var native = (DoubleFloatNestedEmpty)typeof(Program).GetMethod("Echo_DoubleFloatNestedEmpty_RiscV").Invoke( + null, new object[] {0, 0f, expected, 1, -1f}); + var managed = (DoubleFloatNestedEmpty)typeof(Program).GetMethod("Echo_DoubleFloatNestedEmpty_RiscV_Managed").Invoke( + null, new object[] {0, 0f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern DoubleFloatNestedEmpty Echo_DoubleFloatNestedEmpty_InIntegerRegs_RiscV( int a0, @@ -818,6 +1108,19 @@ public static void Test_DoubleFloatNestedEmpty_InIntegerRegs_RiscV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact] + public static void Test_DoubleFloatNestedEmpty_InIntegerRegs_ByReflection_RiscV() + { + var expected = DoubleFloatNestedEmpty.Get(); + var native = (DoubleFloatNestedEmpty)typeof(Program).GetMethod("Echo_DoubleFloatNestedEmpty_InIntegerRegs_RiscV").Invoke( + null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, expected, 1, -1f}); + var managed = (DoubleFloatNestedEmpty)typeof(Program).GetMethod("Echo_DoubleFloatNestedEmpty_InIntegerRegs_RiscV_Managed").Invoke( + null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion #region EmptyUshortAndDouble_RiscVTests @@ -864,6 +1167,19 @@ public static void Test_EmptyUshortAndDouble_RiscV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact] + public static void Test_EmptyUshortAndDouble_ByReflection_RiscV() + { + var expected = EmptyUshortAndDouble.Get(); + var native = (EmptyUshortAndDouble)typeof(Program).GetMethod("Echo_EmptyUshortAndDouble_RiscV").Invoke( + null, new object[] {0, 0f, expected, -1, 1f}); + var managed = (EmptyUshortAndDouble)typeof(Program).GetMethod("Echo_EmptyUshortAndDouble_RiscV_Managed").Invoke( + null, new object[] {0, 0f, expected, -1, 1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion #region PackedEmptyFloatLong_RiscVTests @@ -908,6 +1224,20 @@ public static void Test_PackedEmptyFloatLong_RiscV() Assert.Equal(expected, managed); } + [Fact] + [ActiveIssue(ProblemsWithEmptyStructPassing, typeof(Program), nameof(IsArm64Or32))] + public static void Test_PackedEmptyFloatLong_ByReflection_RiscV() + { + var expected = PackedEmptyFloatLong.Get(); + var native = (PackedEmptyFloatLong)typeof(Program).GetMethod("Echo_PackedEmptyFloatLong_RiscV").Invoke( + null, new object[] {0, 0f, expected, 1, -1f}); + var managed = (PackedEmptyFloatLong)typeof(Program).GetMethod("Echo_PackedEmptyFloatLong_RiscV_Managed").Invoke( + null, new object[] {0, 0f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern PackedEmptyFloatLong Echo_PackedEmptyFloatLong_InIntegerRegs_RiscV( int a0, @@ -938,6 +1268,20 @@ public static void Test_PackedEmptyFloatLong_InIntegerRegs_RiscV() Assert.Equal(expected, managed); } + [Fact] + [ActiveIssue(ProblemsWithEmptyStructPassing, typeof(Program), nameof(IsArm64Or32))] + public static void Test_PackedEmptyFloatLong_InIntegerRegs_ByReflection_RiscV() + { + var expected = PackedEmptyFloatLong.Get(); + var native = (PackedEmptyFloatLong)typeof(Program).GetMethod("Echo_PackedEmptyFloatLong_InIntegerRegs_RiscV").Invoke( + null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + var managed = (PackedEmptyFloatLong)typeof(Program).GetMethod("Echo_PackedEmptyFloatLong_InIntegerRegs_RiscV_Managed").Invoke( + null, new object[] {0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern PackedEmptyFloatLong Echo_PackedEmptyFloatLong_Split_RiscV( int a0, int a1, int a2, int a3, int a4, int a5, int a6, @@ -968,6 +1312,20 @@ public static void Test_PackedEmptyFloatLong_Split_RiscV() Assert.Equal(expected, managed); } + [Fact] + [ActiveIssue(ProblemsWithEmptyStructPassing, typeof(Program), nameof(IsArm64Or32))] + public static void Test_PackedEmptyFloatLong_Split_ByReflection_RiscV() + { + var expected = PackedEmptyFloatLong.Get(); + var native = (PackedEmptyFloatLong)typeof(Program).GetMethod("Echo_PackedEmptyFloatLong_Split_RiscV").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + var managed = (PackedEmptyFloatLong)typeof(Program).GetMethod("Echo_PackedEmptyFloatLong_Split_RiscV_Managed").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern PackedEmptyFloatLong Echo_PackedEmptyFloatLong_OnStack_RiscV( int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, @@ -997,6 +1355,20 @@ public static void Test_PackedEmptyFloatLong_OnStack_RiscV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact] + [ActiveIssue(ProblemsWithEmptyStructPassing, typeof(Program), nameof(IsArm64Or32))] + public static void Test_PackedEmptyFloatLong_OnStack_ByReflection_RiscV() + { + var expected = PackedEmptyFloatLong.Get(); + var native = (PackedEmptyFloatLong)typeof(Program).GetMethod("Echo_PackedEmptyFloatLong_OnStack_RiscV").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + var managed = (PackedEmptyFloatLong)typeof(Program).GetMethod("Echo_PackedEmptyFloatLong_OnStack_RiscV_Managed").Invoke( + null, new object[] {0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f}); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion @@ -1042,6 +1414,20 @@ public static void Test_PackedFloatEmptyByte_RiscV() Assert.Equal(expected, managed); } + [Fact] + [ActiveIssue(ProblemsWithEmptyStructPassing, typeof(Program), nameof(IsArm64Or32))] + public static void Test_PackedFloatEmptyByte_ByReflection_RiscV() + { + var expected = PackedFloatEmptyByte.Get(); + var native = (PackedFloatEmptyByte)typeof(Program).GetMethod("Echo_PackedFloatEmptyByte_RiscV").Invoke( + null, new object[] {0, 0f, expected, 1, -1f }); + var managed = (PackedFloatEmptyByte)typeof(Program).GetMethod("Echo_PackedFloatEmptyByte_RiscV_Managed").Invoke( + null, new object[] {0, 0f, expected, 1, -1f }); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern PackedFloatEmptyByte Echo_PackedFloatEmptyByte_InIntegerRegs_RiscV( int a0, @@ -1072,6 +1458,20 @@ public static void Test_PackedFloatEmptyByte_InIntegerRegs_RiscV() Assert.Equal(expected, managed); } + [Fact] + [ActiveIssue(ProblemsWithEmptyStructPassing, typeof(Program), nameof(IsArm64Or32))] + public static void Test_PackedFloatEmptyByte_InIntegerRegs_ByReflection_RiscV() + { + var expected = PackedFloatEmptyByte.Get(); + var native = (PackedFloatEmptyByte)typeof(Program).GetMethod("Echo_PackedFloatEmptyByte_InIntegerRegs_RiscV").Invoke( + null, new object[] { 0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f }); + var managed = (PackedFloatEmptyByte)typeof(Program).GetMethod("Echo_PackedFloatEmptyByte_InIntegerRegs_RiscV_Managed").Invoke( + null, new object[] { 0, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f }); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } + [DllImport("EmptyStructsLib")] public static extern PackedFloatEmptyByte Echo_PackedFloatEmptyByte_OnStack_RiscV( int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, @@ -1101,5 +1501,19 @@ public static void Test_PackedFloatEmptyByte_OnStack_RiscV() Assert.Equal(expected, native); Assert.Equal(expected, managed); } + + [Fact] + [ActiveIssue(ProblemsWithEmptyStructPassing, typeof(Program), nameof(IsArm64Or32))] + public static void Test_PackedFloatEmptyByte_OnStack_ByReflection_RiscV() + { + var expected = PackedFloatEmptyByte.Get(); + var native = (PackedFloatEmptyByte)typeof(Program).GetMethod("Echo_PackedFloatEmptyByte_OnStack_RiscV").Invoke( + null, new object[] { 0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f }); + var managed = (PackedFloatEmptyByte)typeof(Program).GetMethod("Echo_PackedFloatEmptyByte_OnStack_RiscV_Managed").Invoke( + null, new object[] { 0, 1, 2, 3, 4, 5, 6, 7, 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, expected, 1, -1f }); + + Assert.Equal(expected, native); + Assert.Equal(expected, managed); + } #endregion } \ No newline at end of file