From 21dc0db53b16f0c8ce99bde87363392950d9a4f3 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Thu, 5 Dec 2019 11:40:22 +0100 Subject: [PATCH] Fix crossgen2 ArgIterator for SystemV Amd64 Unix ABI (#499) * Fix crossgen2 ArgIterator for amd64 Unix ABI The ArgIterator was missing proper handling of the SystemV struct classification. There were also several minor issues. * Remove the SystemV struct classification cache Running without the cache showed no difference in the total build time of all the coreclr pri1 tests and all the runtime assemblies, so I am removing the cache. --- .../src/tools/Common/JitInterface/CorInfoImpl.cs | 5 +- .../JitInterface/SystemVStructClassificator.cs | 49 +++--- .../tools/ReadyToRun.SuperIlc/BuildFolderSet.cs | 2 + .../DependencyAnalysis/ReadyToRun/ArgIterator.cs | 180 +++++++++++---------- .../ReadyToRun/GCRefMapBuilder.cs | 4 +- .../ReadyToRun/TransitionBlock.cs | 7 +- 6 files changed, 128 insertions(+), 119 deletions(-) diff --git a/src/coreclr/src/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/src/tools/Common/JitInterface/CorInfoImpl.cs index bad1708..e2f7a53 100644 --- a/src/coreclr/src/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/src/tools/Common/JitInterface/CorInfoImpl.cs @@ -2524,13 +2524,12 @@ namespace Internal.JitInterface private byte* findNameOfToken(CORINFO_MODULE_STRUCT_* moduleHandle, mdToken token, byte* szFQName, UIntPtr FQNameCapacity) { throw new NotImplementedException("findNameOfToken"); } - SystemVStructClassificator _systemVStructClassificator = new SystemVStructClassificator(); - private bool getSystemVAmd64PassStructInRegisterDescriptor(CORINFO_CLASS_STRUCT_* structHnd, SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR* structPassInRegDescPtr) { TypeDesc typeDesc = HandleToObject(structHnd); - return _systemVStructClassificator.getSystemVAmd64PassStructInRegisterDescriptor(typeDesc, structPassInRegDescPtr); + SystemVStructClassificator.GetSystemVAmd64PassStructInRegisterDescriptor(typeDesc, out *structPassInRegDescPtr); + return true; } private uint getThreadTLSIndex(ref void* ppIndirection) diff --git a/src/coreclr/src/tools/Common/JitInterface/SystemVStructClassificator.cs b/src/coreclr/src/tools/Common/JitInterface/SystemVStructClassificator.cs index fe7f13d..3f437c5 100644 --- a/src/coreclr/src/tools/Common/JitInterface/SystemVStructClassificator.cs +++ b/src/coreclr/src/tools/Common/JitInterface/SystemVStructClassificator.cs @@ -12,10 +12,8 @@ namespace Internal.JitInterface using static SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR; using static SystemVClassificationType; - internal class SystemVStructClassificator + internal static class SystemVStructClassificator { - private Dictionary _classificationCache = new Dictionary(); - private struct SystemVStructRegisterPassingHelper { internal SystemVStructRegisterPassingHelper(int totalStructSize) @@ -93,44 +91,37 @@ namespace Internal.JitInterface } } - public unsafe bool getSystemVAmd64PassStructInRegisterDescriptor(TypeDesc typeDesc, SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR* structPassInRegDescPtr) + public static void GetSystemVAmd64PassStructInRegisterDescriptor(TypeDesc typeDesc, out SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structPassInRegDescPtr) { - structPassInRegDescPtr->passedInRegisters = false; + structPassInRegDescPtr = default; + structPassInRegDescPtr.passedInRegisters = false; int typeSize = typeDesc.GetElementSize().AsInt; if (typeDesc.IsValueType && (typeSize <= CLR_SYSTEMV_MAX_STRUCT_BYTES_TO_PASS_IN_REGISTERS)) { - Debug.Assert((TypeDef2SystemVClassification(typeDesc) == SystemVClassificationTypeStruct) || - (TypeDef2SystemVClassification(typeDesc) == SystemVClassificationTypeTypedReference)); - - if (_classificationCache.TryGetValue(typeDesc, out SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR descriptor)) + if ((TypeDef2SystemVClassification(typeDesc) != SystemVClassificationTypeStruct) && + (TypeDef2SystemVClassification(typeDesc) != SystemVClassificationTypeTypedReference)) { - *structPassInRegDescPtr = descriptor; + return; } - else + + SystemVStructRegisterPassingHelper helper = new SystemVStructRegisterPassingHelper(typeSize); + bool canPassInRegisters = ClassifyEightBytes(typeDesc, ref helper, 0); + if (canPassInRegisters) { - SystemVStructRegisterPassingHelper helper = new SystemVStructRegisterPassingHelper(typeSize); - bool canPassInRegisters = ClassifyEightBytes(typeDesc, ref helper, 0); - if (canPassInRegisters) - { - structPassInRegDescPtr->passedInRegisters = canPassInRegisters; - structPassInRegDescPtr->eightByteCount = (byte)helper.EightByteCount; - Debug.Assert(structPassInRegDescPtr->eightByteCount <= CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS); + structPassInRegDescPtr.passedInRegisters = canPassInRegisters; + structPassInRegDescPtr.eightByteCount = (byte)helper.EightByteCount; + Debug.Assert(structPassInRegDescPtr.eightByteCount <= CLR_SYSTEMV_MAX_EIGHTBYTES_COUNT_TO_PASS_IN_REGISTERS); - structPassInRegDescPtr->eightByteClassifications0 = helper.EightByteClassifications[0]; - structPassInRegDescPtr->eightByteSizes0 = (byte)helper.EightByteSizes[0]; - structPassInRegDescPtr->eightByteOffsets0 = (byte)helper.EightByteOffsets[0]; + structPassInRegDescPtr.eightByteClassifications0 = helper.EightByteClassifications[0]; + structPassInRegDescPtr.eightByteSizes0 = (byte)helper.EightByteSizes[0]; + structPassInRegDescPtr.eightByteOffsets0 = (byte)helper.EightByteOffsets[0]; - structPassInRegDescPtr->eightByteClassifications1 = helper.EightByteClassifications[1]; - structPassInRegDescPtr->eightByteSizes1 = (byte)helper.EightByteSizes[1]; - structPassInRegDescPtr->eightByteOffsets1 = (byte)helper.EightByteOffsets[1]; - } - - _classificationCache.Add(typeDesc, *structPassInRegDescPtr); + structPassInRegDescPtr.eightByteClassifications1 = helper.EightByteClassifications[1]; + structPassInRegDescPtr.eightByteSizes1 = (byte)helper.EightByteSizes[1]; + structPassInRegDescPtr.eightByteOffsets1 = (byte)helper.EightByteOffsets[1]; } } - - return true; } private static SystemVClassificationType TypeDef2SystemVClassification(TypeDesc typeDesc) diff --git a/src/coreclr/src/tools/ReadyToRun.SuperIlc/BuildFolderSet.cs b/src/coreclr/src/tools/ReadyToRun.SuperIlc/BuildFolderSet.cs index 51c5da6..2189465 100644 --- a/src/coreclr/src/tools/ReadyToRun.SuperIlc/BuildFolderSet.cs +++ b/src/coreclr/src/tools/ReadyToRun.SuperIlc/BuildFolderSet.cs @@ -41,6 +41,8 @@ namespace ReadyToRun.SuperIlc new FrameworkExclusion("Microsoft.CodeAnalysis.CSharp", "Ibc TypeToken 6200019a has type token which resolves to a nil token", crossgen2Only: true), new FrameworkExclusion("Microsoft.CodeAnalysis", "Ibc TypeToken 620001af unable to find external typedef", crossgen2Only: true), new FrameworkExclusion("Microsoft.CodeAnalysis.VisualBasic", "Ibc TypeToken 620002ce unable to find external typedef", crossgen2Only: true), + + new FrameworkExclusion("System.Runtime.Serialization.Formatters", "Assert in JIT on Linux", crossgen2Only: true) }; private readonly IEnumerable _buildFolders; diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs index d03ab7a..619df70 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ArgIterator.cs @@ -312,92 +312,41 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun return _transitionBlock.OffsetOfArgumentRegisters + _argLocDescForStructInRegs.Value.m_idxGenReg * 8; } - private SystemVClassificationType GetTypeClassification(TypeDesc type) - { - switch (type.Category) - { - case Internal.TypeSystem.TypeFlags.Void: - return SystemVClassificationType.SystemVClassificationTypeUnknown; - - case Internal.TypeSystem.TypeFlags.Boolean: - case Internal.TypeSystem.TypeFlags.Char: - case Internal.TypeSystem.TypeFlags.SByte: - case Internal.TypeSystem.TypeFlags.Byte: - case Internal.TypeSystem.TypeFlags.Int16: - case Internal.TypeSystem.TypeFlags.UInt16: - case Internal.TypeSystem.TypeFlags.Int32: - case Internal.TypeSystem.TypeFlags.UInt32: - case Internal.TypeSystem.TypeFlags.Int64: - case Internal.TypeSystem.TypeFlags.UInt64: - case Internal.TypeSystem.TypeFlags.IntPtr: - case Internal.TypeSystem.TypeFlags.UIntPtr: - case Internal.TypeSystem.TypeFlags.Enum: - case Internal.TypeSystem.TypeFlags.Pointer: - case Internal.TypeSystem.TypeFlags.FunctionPointer: - return SystemVClassificationType.SystemVClassificationTypeInteger; - - case Internal.TypeSystem.TypeFlags.Single: - case Internal.TypeSystem.TypeFlags.Double: - return SystemVClassificationType.SystemVClassificationTypeSSE; - - case Internal.TypeSystem.TypeFlags.ByRef: - return SystemVClassificationType.SystemVClassificationTypeIntegerByRef; - - case Internal.TypeSystem.TypeFlags.ValueType: - return SystemVClassificationType.SystemVClassificationTypeStruct; - - case Internal.TypeSystem.TypeFlags.Class: - case Internal.TypeSystem.TypeFlags.GenericParameter: - case Internal.TypeSystem.TypeFlags.Array: - case Internal.TypeSystem.TypeFlags.SzArray: - case Internal.TypeSystem.TypeFlags.Interface: - return SystemVClassificationType.SystemVClassificationTypeIntegerReference; - - default: - return SystemVClassificationType.SystemVClassificationTypeUnknown; - } - } - // Report managed object pointers in the struct in registers // Arguments: // fn - promotion function to apply to each managed object pointer // sc - scan context to pass to the promotion function // fieldBytes - size of the structure - void ReportPointersFromStructInRegisters(TypeDesc type, int delta, CORCOMPILE_GCREFMAP_TOKENS[] frame) + internal void ReportPointersFromStructInRegisters(TypeDesc type, int delta, CORCOMPILE_GCREFMAP_TOKENS[] frame) { // SPAN-TODO: GC reporting - https://github.com/dotnet/coreclr/issues/8517 Debug.Assert(IsStructPassedInRegs()); int genRegDest = GetStructGenRegDestinationAddress(); - foreach (FieldDesc field in type.GetFields()) + + SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR descriptor; + SystemVStructClassificator.GetSystemVAmd64PassStructInRegisterDescriptor(type, out descriptor); + + for (int i = 0; i < descriptor.eightByteCount; i++) { - if (field.IsStatic) - { - continue; - } - SystemVClassificationType eightByteClassification = GetTypeClassification(field.FieldType); + int eightByteSize = (i == 0) ? descriptor.eightByteSizes0 : descriptor.eightByteSizes1; + SystemVClassificationType eightByteClassification = (i == 0) ? descriptor.eightByteClassifications0 : descriptor.eightByteClassifications1; if (eightByteClassification != SystemVClassificationType.SystemVClassificationTypeSSE) { if ((eightByteClassification == SystemVClassificationType.SystemVClassificationTypeIntegerReference) || (eightByteClassification == SystemVClassificationType.SystemVClassificationTypeIntegerByRef)) { - int eightByteSize = field.FieldType.GetElementSize().AsInt; + Debug.Assert(eightByteSize == 8); Debug.Assert((genRegDest & 7) == 0); - CORCOMPILE_GCREFMAP_TOKENS token; - if (eightByteClassification == SystemVClassificationType.SystemVClassificationTypeIntegerByRef) - { - token = CORCOMPILE_GCREFMAP_TOKENS.GCREFMAP_INTERIOR; - } - else - { - token = CORCOMPILE_GCREFMAP_TOKENS.GCREFMAP_REF; - } - int eightByteIndex = (genRegDest + field.Offset.AsInt) >> 3; + CORCOMPILE_GCREFMAP_TOKENS token = (eightByteClassification == SystemVClassificationType.SystemVClassificationTypeIntegerByRef) ? CORCOMPILE_GCREFMAP_TOKENS.GCREFMAP_INTERIOR : CORCOMPILE_GCREFMAP_TOKENS.GCREFMAP_REF; + int eightByteIndex = (genRegDest >> 3) + i; frame[delta + eightByteIndex] = token; } + + genRegDest += eightByteSize; } } } @@ -506,6 +455,8 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun private bool _skipFirstArg; private bool _extraObjectFirstArg; private CallingConventions _interpreterCallingConvention; + private bool _hasArgLocDescForStructInRegs; + private ArgLocDesc _argLocDescForStructInRegs; public bool HasThis => _hasThis; public bool IsVarArg => _argData.IsVarArg(); @@ -949,7 +900,10 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun case TargetArchitecture.X64: if (_transitionBlock.IsX64UnixABI) { + _hasArgLocDescForStructInRegs = false; + _fX64UnixArgInRegisters = true; int cFPRegs = 0; + int cGenRegs = 0; switch (argType) { @@ -965,12 +919,55 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun break; case CorElementType.ELEMENT_TYPE_VALUETYPE: + { + SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR descriptor; + SystemVStructClassificator.GetSystemVAmd64PassStructInRegisterDescriptor(_argTypeHandle.GetRuntimeTypeHandle(), out descriptor); + + if (descriptor.passedInRegisters) { - // UNIXTODO: FEATURE_UNIX_AMD64_STRUCT_PASSING: Passing of structs, HFAs. For now, use the Windows convention. - argSize = _transitionBlock.PointerSize; - break; + cGenRegs = 0; + for (int i = 0; i < descriptor.eightByteCount; i++) + { + switch ((i == 0) ? descriptor.eightByteClassifications0 : descriptor.eightByteClassifications1) + { + case SystemVClassificationType.SystemVClassificationTypeInteger: + case SystemVClassificationType.SystemVClassificationTypeIntegerReference: + case SystemVClassificationType.SystemVClassificationTypeIntegerByRef: + cGenRegs++; + break; + case SystemVClassificationType.SystemVClassificationTypeSSE: + cFPRegs++; + break; + default: + Debug.Assert(false); + break; + } + } + + // Check if we have enough registers available for the struct passing + if ((cFPRegs + _x64UnixIdxFPReg <= TransitionBlock.X64UnixTransitionBlock.NUM_FLOAT_ARGUMENT_REGISTERS) && (cGenRegs + _x64UnixIdxGenReg) <= _transitionBlock.NumArgumentRegisters) + { + _argLocDescForStructInRegs = new ArgLocDesc(); + _argLocDescForStructInRegs.m_cGenReg = (short)cGenRegs; + _argLocDescForStructInRegs.m_cFloatReg = cFPRegs; + _argLocDescForStructInRegs.m_idxGenReg = _x64UnixIdxGenReg; + _argLocDescForStructInRegs.m_idxFloatReg = _x64UnixIdxFPReg; + + _hasArgLocDescForStructInRegs = true; + + _x64UnixIdxGenReg += cGenRegs; + _x64UnixIdxFPReg += cFPRegs; + + return TransitionBlock.StructInRegsOffset; + } } + // Set the register counts to indicate that this argument will not be passed in registers + cFPRegs = 0; + cGenRegs = 0; + break; + } + default: break; } @@ -980,7 +977,7 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun if (cFPRegs > 0) { - if (cFPRegs + _x64UnixIdxFPReg <= 8) + if (cFPRegs + _x64UnixIdxFPReg <= TransitionBlock.X64UnixTransitionBlock.NUM_FLOAT_ARGUMENT_REGISTERS) { int argOfsInner = _transitionBlock.OffsetOfFloatArgumentRegisters + _x64UnixIdxFPReg * 8; _x64UnixIdxFPReg += cFPRegs; @@ -989,7 +986,7 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun } else { - if (_x64UnixIdxGenReg + cArgSlots <= 6) + if (_x64UnixIdxGenReg + cArgSlots <= _transitionBlock.NumArgumentRegisters) { int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _x64UnixIdxGenReg * 8; _x64UnixIdxGenReg += cArgSlots; @@ -997,6 +994,8 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun } } + _fX64UnixArgInRegisters = false; + argOfs = _transitionBlock.OffsetOfArgs + _x64UnixIdxStack * 8; _x64UnixIdxStack += cArgSlots; return argOfs; @@ -1408,9 +1407,21 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun if (_transitionBlock.IsX64) { - // All stack arguments take just one stack slot on AMD64 because of arguments bigger - // than a stack slot are passed by reference. - stackElemSize = _transitionBlock.StackElemSize(); + if (_transitionBlock.IsX64UnixABI) + { + if (_fX64UnixArgInRegisters) + { + continue; + } + + stackElemSize = _transitionBlock.StackElemSize(GetArgSize()); + } + else + { + // All stack arguments take just one stack slot on AMD64 because of arguments bigger + // than a stack slot are passed by reference. + stackElemSize = _transitionBlock.StackElemSize(); + } } else { @@ -1546,7 +1557,10 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun case TargetArchitecture.X64: if (_transitionBlock.IsX64UnixABI) { - // LIMITED_METHOD_CONTRACT; + if (_hasArgLocDescForStructInRegs) + { + return _argLocDescForStructInRegs; + } if (argOffset == TransitionBlock.StructInRegsOffset) { @@ -1562,24 +1576,23 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun { // Dividing by 8 as size of each register in FloatArgumentRegisters is 8 bytes. pLoc.m_idxFloatReg = (argOffset - _transitionBlock.OffsetOfFloatArgumentRegisters) / 8; - - // UNIXTODO: Passing of structs, HFAs. For now, use the Windows convention. pLoc.m_cFloatReg = 1; - return pLoc; } - - // UNIXTODO: Passing of structs, HFAs. For now, use the Windows convention. - int cSlots = 1; - - if (!_transitionBlock.IsStackArgumentOffset(argOffset)) + else if (!_transitionBlock.IsStackArgumentOffset(argOffset)) { pLoc.m_idxGenReg = _transitionBlock.GetArgumentIndexFromOffset(argOffset); - pLoc.m_cGenReg = (short)cSlots; + pLoc.m_cGenReg = 1; } else { pLoc.m_idxStack = (argOffset - _transitionBlock.OffsetOfArgs) / 8; - pLoc.m_cStack = cSlots; + int argOnStackSize; + int stackElemSize = _transitionBlock.StackElemSize(); + if (IsArgPassedByRef()) + argOnStackSize = stackElemSize; + else + argOnStackSize = GetArgSize(); + pLoc.m_cStack = (argOnStackSize + stackElemSize - 1) / stackElemSize; } return pLoc; } @@ -1613,6 +1626,7 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun private int _x64UnixIdxGenReg; private int _x64UnixIdxStack; private int _x64UnixIdxFPReg; + private bool _fX64UnixArgInRegisters; private int _x64WindowsCurOfs; // Current position of the stack iterator private int _armIdxGenReg; // Next general register to be assigned a value diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapBuilder.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapBuilder.cs index da49be8..9c7ef19 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapBuilder.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapBuilder.cs @@ -269,8 +269,8 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun // ReportPointersFromValueTypeArg if (argDest.IsStructPassedInRegs()) { - // ReportPointersFromStructPassedInRegs - throw new NotImplementedException(); + argDest.ReportPointersFromStructInRegisters(type, delta, frame); + return; } // ReportPointersFromValueType if (type.IsByRefLike) diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TransitionBlock.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TransitionBlock.cs index ab5a633..dac2a93 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TransitionBlock.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TransitionBlock.cs @@ -361,7 +361,7 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun /// /// X64 properties common to Windows and Unix ABI. /// - private abstract class X64TransitionBlock : TransitionBlock + internal abstract class X64TransitionBlock : TransitionBlock { public override TargetArchitecture Architecture => TargetArchitecture.X64; public override int PointerSize => 8; @@ -398,10 +398,12 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun public override int EnregisteredReturnTypeIntegerMaxSize => 8; } - private sealed class X64UnixTransitionBlock : X64TransitionBlock + internal sealed class X64UnixTransitionBlock : X64TransitionBlock { public static readonly TransitionBlock Instance = new X64UnixTransitionBlock(); + public override bool IsX64UnixABI => true; + public const int NUM_FLOAT_ARGUMENT_REGISTERS = 8; // RDI, RSI, RDX, RCX, R8, R9 @@ -414,6 +416,7 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun public override int OffsetOfFloatArgumentRegisters => SizeOfM128A * NUM_FLOAT_ARGUMENT_REGISTERS; public override int EnregisteredParamTypeMaxSize => 16; public override int EnregisteredReturnTypeIntegerMaxSize => 16; + public override bool IsArgPassedByRef(TypeHandle th) => false; } private sealed class Arm32TransitionBlock : TransitionBlock -- 2.7.4