|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// Provides an abstraction over platform specific calling conventions (specifically, the calling convention
// utilized by the JIT on that platform). The caller enumerates each argument of a signature in turn, and is
// provided with information mapping that argument into registers and/or stack locations.
using System;
using System.Diagnostics;
using Internal.JitInterface;
using Internal.NativeFormat;
using Internal.TypeSystem;
using Internal.CorConstants;
using Internal;
using ILCompiler.DependencyAnalysis.Wasm;
namespace ILCompiler.DependencyAnalysis.ReadyToRun
{
public enum CORCOMPILE_GCREFMAP_TOKENS : byte
{
GCREFMAP_SKIP = 0,
GCREFMAP_REF = 1,
GCREFMAP_INTERIOR = 2,
GCREFMAP_METHOD_PARAM = 3,
GCREFMAP_TYPE_PARAM = 4,
GCREFMAP_VASIG_COOKIE = 5,
};
public enum CallingConventions
{
ManagedInstance,
ManagedStatic,
StdCall,
/*FastCall, CDecl */
}
internal struct TypeHandle
{
public TypeHandle(TypeDesc type)
{
_type = type;
_isByRef = _type.IsByRef;
if (_isByRef)
{
_type = ((ByRefType)_type).ParameterType;
}
}
private readonly TypeDesc _type;
private readonly bool _isByRef;
public bool Equals(TypeHandle other)
{
return _isByRef == other._isByRef && _type == other._type;
}
public override int GetHashCode() { return (int)_type.GetHashCode(); }
public bool IsNull() { return _type == null && !_isByRef; }
public bool IsValueType() { if (_isByRef) return false; return _type.IsValueType; }
public bool IsPointerType() { if (_isByRef) return false; return _type.IsPointer; }
public bool HasIndeterminateSize() { return IsValueType() && ((DefType)_type).InstanceFieldSize.IsIndeterminate; }
public int PointerSize => _type.Context.Target.PointerSize;
public int GetSize()
{
if (IsValueType())
return ((DefType)_type).InstanceFieldSize.AsInt;
else
return PointerSize;
}
public bool RequiresAlign8()
{
if (_type.Context.Target.Architecture != TargetArchitecture.ARM)
{
return false;
}
if (_isByRef)
{
return false;
}
return _type.RequiresAlign8();
}
public bool IsHomogeneousAggregate()
{
TargetArchitecture targetArch = _type.Context.Target.Architecture;
if ((targetArch != TargetArchitecture.ARM) && (targetArch != TargetArchitecture.ARM64))
{
return false;
}
if (_isByRef)
{
return false;
}
return _type is DefType defType && defType.IsHomogeneousAggregate;
}
public int GetHomogeneousAggregateElementSize()
{
Debug.Assert(IsHomogeneousAggregate());
switch (_type.Context.Target.Architecture)
{
case TargetArchitecture.ARM:
return RequiresAlign8() ? 8 : 4;
case TargetArchitecture.ARM64:
return ((DefType)_type).GetHomogeneousAggregateElementSize();
}
throw new InvalidOperationException();
}
public CorElementType GetCorElementType()
{
if (_isByRef)
{
return CorElementType.ELEMENT_TYPE_BYREF;
}
Internal.TypeSystem.TypeFlags category = _type.UnderlyingType.Category;
// We use the UnderlyingType to handle Enums properly
return category switch
{
Internal.TypeSystem.TypeFlags.Boolean => CorElementType.ELEMENT_TYPE_BOOLEAN,
Internal.TypeSystem.TypeFlags.Char => CorElementType.ELEMENT_TYPE_CHAR,
Internal.TypeSystem.TypeFlags.SByte => CorElementType.ELEMENT_TYPE_I1,
Internal.TypeSystem.TypeFlags.Byte => CorElementType.ELEMENT_TYPE_U1,
Internal.TypeSystem.TypeFlags.Int16 => CorElementType.ELEMENT_TYPE_I2,
Internal.TypeSystem.TypeFlags.UInt16 => CorElementType.ELEMENT_TYPE_U2,
Internal.TypeSystem.TypeFlags.Int32 => CorElementType.ELEMENT_TYPE_I4,
Internal.TypeSystem.TypeFlags.UInt32 => CorElementType.ELEMENT_TYPE_U4,
Internal.TypeSystem.TypeFlags.Int64 => CorElementType.ELEMENT_TYPE_I8,
Internal.TypeSystem.TypeFlags.UInt64 => CorElementType.ELEMENT_TYPE_U8,
Internal.TypeSystem.TypeFlags.IntPtr => CorElementType.ELEMENT_TYPE_I,
Internal.TypeSystem.TypeFlags.UIntPtr => CorElementType.ELEMENT_TYPE_U,
Internal.TypeSystem.TypeFlags.Single => CorElementType.ELEMENT_TYPE_R4,
Internal.TypeSystem.TypeFlags.Double => CorElementType.ELEMENT_TYPE_R8,
Internal.TypeSystem.TypeFlags.ValueType => CorElementType.ELEMENT_TYPE_VALUETYPE,
Internal.TypeSystem.TypeFlags.Nullable => CorElementType.ELEMENT_TYPE_VALUETYPE,
Internal.TypeSystem.TypeFlags.Void => CorElementType.ELEMENT_TYPE_VOID,
Internal.TypeSystem.TypeFlags.Pointer => CorElementType.ELEMENT_TYPE_PTR,
Internal.TypeSystem.TypeFlags.FunctionPointer => CorElementType.ELEMENT_TYPE_FNPTR,
_ => CorElementType.ELEMENT_TYPE_CLASS
};
}
private static int[] s_elemSizes = new int[]
{
0, //ELEMENT_TYPE_END 0x0
0, //ELEMENT_TYPE_VOID 0x1
1, //ELEMENT_TYPE_BOOLEAN 0x2
2, //ELEMENT_TYPE_CHAR 0x3
1, //ELEMENT_TYPE_I1 0x4
1, //ELEMENT_TYPE_U1 0x5
2, //ELEMENT_TYPE_I2 0x6
2, //ELEMENT_TYPE_U2 0x7
4, //ELEMENT_TYPE_I4 0x8
4, //ELEMENT_TYPE_U4 0x9
8, //ELEMENT_TYPE_I8 0xa
8, //ELEMENT_TYPE_U8 0xb
4, //ELEMENT_TYPE_R4 0xc
8, //ELEMENT_TYPE_R8 0xd
-2,//ELEMENT_TYPE_STRING 0xe
-2,//ELEMENT_TYPE_PTR 0xf
-2,//ELEMENT_TYPE_BYREF 0x10
-1,//ELEMENT_TYPE_VALUETYPE 0x11
-2,//ELEMENT_TYPE_CLASS 0x12
0, //ELEMENT_TYPE_VAR 0x13
-2,//ELEMENT_TYPE_ARRAY 0x14
0, //ELEMENT_TYPE_GENERICINST 0x15
0, //ELEMENT_TYPE_TYPEDBYREF 0x16
0, // UNUSED 0x17
-2,//ELEMENT_TYPE_I 0x18
-2,//ELEMENT_TYPE_U 0x19
0, // UNUSED 0x1a
-2,//ELEMENT_TYPE_FPTR 0x1b
-2,//ELEMENT_TYPE_OBJECT 0x1c
-2,//ELEMENT_TYPE_SZARRAY 0x1d
};
public static int GetElemSize(CorElementType t, TypeHandle thValueType)
{
if (((int)t) <= 0x1d)
{
int elemSize = s_elemSizes[(int)t];
if (elemSize == -1)
{
return (int)thValueType.GetSize();
}
if (elemSize == -2)
{
return thValueType.PointerSize;
}
return elemSize;
}
return 0;
}
public TypeDesc GetRuntimeTypeHandle() { return _type; }
}
// Describes how a single argument is laid out in registers and/or stack locations when given as an input to a
// managed method as part of a larger signature.
//
// Locations are split into floating point registers, general registers and stack offsets. Registers are
// obviously architecture dependent but are represented as a zero-based index into the usual sequence in which
// such registers are allocated for input on the platform in question. For instance:
// X86: 0 == ecx, 1 == edx
// ARM: 0 == r0, 1 == r1, 2 == r2 etc.
//
// Stack locations are represented as offsets from the stack pointer (at the point of the call). The offset is
// given as an index of a pointer sized slot. Similarly the size of data on the stack is given in slot-sized
// units. For instance, given an index of 2 and a size of 3:
// X86: argument starts at [ESP + 8] and is 12 bytes long
// AMD64: argument starts at [RSP + 16] and is 24 bytes long
//
// The structure is flexible enough to describe an argument that is split over several (consecutive) registers
// and possibly on to the stack as well.
internal struct ArgLocDesc
{
public int m_idxFloatReg; // First floating point register used (or -1)
public int m_cFloatReg; // Count of floating point registers used (or 0)
public int m_idxGenReg; // First general register used (or -1)
public short m_cGenReg; // Count of general registers used (or 0)
public bool m_fRequires64BitAlignment; // ARM - True if the argument should always be aligned (in registers or on the stack
public int m_byteStackIndex; // Stack offset in bytes (or -1)
public int m_byteStackSize; // Stack size in bytes
public uint m_floatFlags; // struct with two-fields can be passed by registers.
public FpStructInRegistersInfo m_structFields; // RISC-V and LoongArch - Struct field info when using floating-point register(s)
// Initialize to represent a non-placed argument (no register or stack slots referenced).
public void Init()
{
m_idxFloatReg = -1;
m_cFloatReg = 0;
m_idxGenReg = -1;
m_cGenReg = 0;
m_byteStackIndex = -1;
m_byteStackSize = 0;
m_floatFlags = 0;
m_structFields = new FpStructInRegistersInfo();
m_fRequires64BitAlignment = false;
}
};
// The ArgDestination class represents a destination location of an argument.
internal readonly struct ArgDestination
{
/// <summary>
/// Transition block context.
/// </summary>
private readonly TransitionBlock _transitionBlock;
// Offset of the argument relative to the base. On AMD64 on Unix, it can have a special
// value that represent a struct that contain both general purpose and floating point fields
// passed in registers.
private readonly int _offset;
// For structs passed in registers, this member points to an ArgLocDesc that contains
// details on the layout of the struct in general purpose and floating point registers.
private readonly ArgLocDesc? _argLocDescForStructInRegs;
// Construct the ArgDestination
public ArgDestination(TransitionBlock transitionBlock, int offset, ArgLocDesc? argLocDescForStructInRegs)
{
_transitionBlock = transitionBlock;
_offset = offset;
_argLocDescForStructInRegs = argLocDescForStructInRegs;
}
public void GcMark(CORCOMPILE_GCREFMAP_TOKENS[] frame, int delta, bool interior)
{
frame[_offset + delta] = interior ? CORCOMPILE_GCREFMAP_TOKENS.GCREFMAP_INTERIOR : CORCOMPILE_GCREFMAP_TOKENS.GCREFMAP_REF;
}
// Returns true if the ArgDestination represents a homogeneous aggregate struct
bool IsHomogeneousAggregate()
{
return _argLocDescForStructInRegs.HasValue;
}
// Unix AMD64 ABI: Returns true if the ArgDestination represents a struct passed in registers.
public bool IsStructPassedInRegs()
{
return _offset == TransitionBlock.StructInRegsOffset;
}
private int GetStructFloatRegDestinationAddress()
{
Debug.Assert(IsStructPassedInRegs());
return _transitionBlock.OffsetOfFloatArgumentRegisters + _argLocDescForStructInRegs.Value.m_idxFloatReg * 16;
}
// Get destination address for non-floating point fields of a struct passed in registers.
private int GetStructGenRegDestinationAddress()
{
Debug.Assert(IsStructPassedInRegs());
return _transitionBlock.OffsetOfArgumentRegisters + _argLocDescForStructInRegs.Value.m_idxGenReg * 8;
}
// 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
internal void ReportPointersFromStructInRegisters(TypeDesc type, int delta, CORCOMPILE_GCREFMAP_TOKENS[] frame)
{
Debug.Assert(IsStructPassedInRegs());
int genRegDest = GetStructGenRegDestinationAddress();
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR descriptor;
SystemVStructClassificator.GetSystemVAmd64PassStructInRegisterDescriptor(type, out descriptor);
for (int i = 0; i < descriptor.eightByteCount; i++)
{
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))
{
Debug.Assert(eightByteSize == 8);
Debug.Assert((genRegDest & 7) == 0);
CORCOMPILE_GCREFMAP_TOKENS token = (eightByteClassification == SystemVClassificationType.SystemVClassificationTypeIntegerByRef) ? CORCOMPILE_GCREFMAP_TOKENS.GCREFMAP_INTERIOR : CORCOMPILE_GCREFMAP_TOKENS.GCREFMAP_REF;
frame[delta + genRegDest] = token;
}
genRegDest += eightByteSize;
}
}
}
}
internal class ArgIteratorData
{
public ArgIteratorData(bool hasThis,
bool isVarArg,
TypeHandle[] parameterTypes,
TypeHandle returnType)
{
_hasThis = hasThis;
_isVarArg = isVarArg;
_parameterTypes = parameterTypes;
_returnType = returnType;
}
private bool _hasThis;
private bool _isVarArg;
private TypeHandle[] _parameterTypes;
private TypeHandle _returnType;
public override bool Equals(object obj)
{
if (this == obj)
return true;
ArgIteratorData other = obj as ArgIteratorData;
if (other == null)
return false;
if (_hasThis != other._hasThis || _isVarArg != other._isVarArg || !_returnType.Equals(other._returnType))
return false;
if (_parameterTypes == null)
return other._parameterTypes == null;
if (other._parameterTypes == null || _parameterTypes.Length != other._parameterTypes.Length)
return false;
for (int i = 0; i < _parameterTypes.Length; i++)
if (!_parameterTypes[i].Equals(other._parameterTypes[i]))
return false;
return true;
}
public override int GetHashCode()
{
return 37 + (_parameterTypes == null ?
_returnType.GetHashCode() :
VersionResilientHashCode.GenericInstanceHashCode(_returnType.GetHashCode(), _parameterTypes));
}
public bool HasThis() { return _hasThis; }
public bool IsVarArg() { return _isVarArg; }
public int NumFixedArgs() { return _parameterTypes != null ? _parameterTypes.Length : 0; }
// Argument iteration.
public CorElementType GetArgumentType(int argNum, out TypeHandle thArgType)
{
thArgType = _parameterTypes[argNum];
CorElementType returnValue = thArgType.GetCorElementType();
return returnValue;
}
public TypeHandle GetByRefArgumentType(int argNum)
{
return (argNum < _parameterTypes.Length && _parameterTypes[argNum].GetCorElementType() == CorElementType.ELEMENT_TYPE_BYREF) ?
_parameterTypes[argNum] :
default(TypeHandle);
}
public CorElementType GetReturnType(out TypeHandle thRetType)
{
thRetType = _returnType;
return thRetType.GetCorElementType();
}
}
//-----------------------------------------------------------------------
// ArgIterator is helper for dealing with calling conventions.
// It is tightly coupled with TransitionBlock. It uses offsets into
// TransitionBlock to represent argument locations for efficiency
// reasons. Alternatively, it can also return ArgLocDesc for less
// performance critical code.
//
// The ARGITERATOR_BASE argument of the template is provider of the parsed
// method signature. Typically, the arg iterator works on top of MetaSig.
// Reflection invoke uses alternative implementation to save signature parsing
// time because of it has the parsed signature available.
//-----------------------------------------------------------------------
//template<class ARGITERATOR_BASE>
internal struct ArgIterator
{
private readonly TypeSystemContext _context;
private readonly TransitionBlock _transitionBlock;
private bool _hasThis;
private bool _hasParamType;
private bool _hasAsyncContinuation;
private bool _extraFunctionPointerArg;
private ArgIteratorData _argData;
private bool[] _forcedByRefParams;
private bool _skipFirstArg;
private bool _extraObjectFirstArg;
private CallingConventions _interpreterCallingConvention;
private bool _hasArgLocDescForStructInRegs;
private ArgLocDesc _argLocDescForStructInRegs;
public bool HasThis => _hasThis;
public bool IsVarArg => _argData.IsVarArg();
public bool HasParamType => _hasParamType;
public bool HasAsyncContinuation => _hasAsyncContinuation;
public int NumFixedArgs => _argData.NumFixedArgs() + (_extraFunctionPointerArg ? 1 : 0) + (_extraObjectFirstArg ? 1 : 0);
// Argument iteration.
public CorElementType GetArgumentType(int argNum, out TypeHandle thArgType, out bool forceByRefReturn)
{
forceByRefReturn = false;
if (_extraObjectFirstArg && argNum == 0)
{
thArgType = new TypeHandle(_context.GetWellKnownType(WellKnownType.Object));
return CorElementType.ELEMENT_TYPE_CLASS;
}
argNum = _extraObjectFirstArg ? argNum - 1 : argNum;
Debug.Assert(argNum >= 0);
if (_forcedByRefParams != null && (argNum + 1) < _forcedByRefParams.Length)
forceByRefReturn = _forcedByRefParams[argNum + 1];
if (_extraFunctionPointerArg && argNum == _argData.NumFixedArgs())
{
thArgType = new TypeHandle(_context.GetWellKnownType(WellKnownType.IntPtr));
return CorElementType.ELEMENT_TYPE_I;
}
return _argData.GetArgumentType(argNum, out thArgType);
}
public CorElementType GetReturnType(out TypeHandle thRetType, out bool forceByRefReturn)
{
if (_forcedByRefParams != null && _forcedByRefParams.Length > 0)
forceByRefReturn = _forcedByRefParams[0];
else
forceByRefReturn = false;
return _argData.GetReturnType(out thRetType);
}
public void Reset()
{
_argType = default(CorElementType);
_argTypeHandle = default(TypeHandle);
_argSize = 0;
_argNum = 0;
_argForceByRef = false;
_ITERATION_STARTED = false;
}
//public:
//------------------------------------------------------------
// Constructor
//------------------------------------------------------------
public ArgIterator(
TypeSystemContext context,
ArgIteratorData argData,
CallingConventions callConv,
bool hasParamType,
bool hasAsyncContinuation,
bool extraFunctionPointerArg,
bool[] forcedByRefParams,
bool skipFirstArg,
bool extraObjectFirstArg)
{
this = default(ArgIterator);
_context = context;
_argData = argData;
_hasThis = callConv == CallingConventions.ManagedInstance;
_hasParamType = hasParamType;
_hasAsyncContinuation = hasAsyncContinuation;
_extraFunctionPointerArg = extraFunctionPointerArg;
_forcedByRefParams = forcedByRefParams;
_skipFirstArg = skipFirstArg;
_extraObjectFirstArg = extraObjectFirstArg;
_interpreterCallingConvention = callConv;
_transitionBlock = TransitionBlock.FromTarget(context.Target);
}
private uint SizeOfArgStack()
{
// WRAPPER_NO_CONTRACT;
if (!_SIZE_OF_ARG_STACK_COMPUTED)
ForceSigWalk();
Debug.Assert(_SIZE_OF_ARG_STACK_COMPUTED);
Debug.Assert((_nSizeOfArgStack % _transitionBlock.PointerSize) == 0);
return (uint)_nSizeOfArgStack;
}
// For use with ArgIterator. This function computes the amount of additional
// memory required above the TransitionBlock. The parameter offsets
// returned by ArgIterator::GetNextOffset are relative to a
// FramedMethodFrame, and may be in either of these regions.
public int SizeOfFrameArgumentArray()
{
// WRAPPER_NO_CONTRACT;
uint size = SizeOfArgStack();
if (_transitionBlock.IsX64 && !_transitionBlock.IsX64UnixABI)
{
// The argument registers are not included in the stack size on AMD64
size += (uint)_transitionBlock.SizeOfArgumentRegisters;
}
Debug.Assert((size % _transitionBlock.PointerSize) == 0);
return (int)size;
}
//------------------------------------------------------------------------
public uint CbStackPop()
{
if (_transitionBlock.IsX86)
{
// WRAPPER_NO_CONTRACT;
if (IsVarArg)
return 0;
else
return SizeOfArgStack();
}
else
{
throw new NotImplementedException();
}
}
// Is there a hidden parameter for the return parameter?
//
public bool HasRetBuffArg()
{
// WRAPPER_NO_CONTRACT;
if (!_RETURN_FLAGS_COMPUTED)
ComputeReturnFlags();
return _RETURN_HAS_RET_BUFFER;
}
public uint GetFPReturnSize()
{
// WRAPPER_NO_CONTRACT;
if (!_RETURN_FLAGS_COMPUTED)
ComputeReturnFlags();
return _fpReturnSize;
}
public bool IsArgPassedByRef()
{
// LIMITED_METHOD_CONTRACT;
if (IsArgForcedPassedByRef())
{
return true;
}
if (_argType == CorElementType.ELEMENT_TYPE_BYREF)
{
return true;
}
if (_transitionBlock.EnregisteredParamTypeMaxSize != 0)
{
switch (_transitionBlock.Architecture)
{
case TargetArchitecture.X64:
return _transitionBlock.IsArgPassedByRef(_argSize);
case TargetArchitecture.ARM64:
if (_argType == CorElementType.ELEMENT_TYPE_VALUETYPE)
{
Debug.Assert(!_argTypeHandle.IsNull());
return ((_argSize > _transitionBlock.EnregisteredParamTypeMaxSize) && (!_argTypeHandle.IsHomogeneousAggregate() || IsVarArg));
}
return false;
case TargetArchitecture.LoongArch64:
if (_argType == CorElementType.ELEMENT_TYPE_VALUETYPE)
{
Debug.Assert(!_argTypeHandle.IsNull());
return ((_argSize > _transitionBlock.EnregisteredParamTypeMaxSize) || _transitionBlock.IsArgPassedByRef(_argTypeHandle));
}
return false;
case TargetArchitecture.RiscV64:
if (_argType == CorElementType.ELEMENT_TYPE_VALUETYPE)
{
Debug.Assert(!_argTypeHandle.IsNull());
return ((_argSize > _transitionBlock.EnregisteredParamTypeMaxSize) || _transitionBlock.IsArgPassedByRef(_argTypeHandle));
}
return false;
default:
throw new NotImplementedException();
}
}
else
{
if (_transitionBlock.IsWasm32)
{
if (_argType == CorElementType.ELEMENT_TYPE_VALUETYPE)
{
return _transitionBlock.IsArgPassedByRef(_argTypeHandle);
}
}
return false;
}
}
private bool IsArgForcedPassedByRef()
{
// This should be true for valuetypes instantiated over T in a generic signature using universal shared generic calling convention
return _argForceByRef;
}
//------------------------------------------------------------
// Return the offsets of the special arguments
//------------------------------------------------------------
public int GetThisOffset()
{
return _transitionBlock.ThisOffset;
}
public int GetVASigCookieOffset()
{
// WRAPPER_NO_CONTRACT;
Debug.Assert(IsVarArg);
if (_transitionBlock.IsX86)
{
// x86 is special as always
return _transitionBlock.SizeOfTransitionBlock;
}
else
{
// VaSig cookie is after this and retbuf arguments by default.
int ret = _transitionBlock.OffsetOfArgumentRegisters;
int slotSize = _transitionBlock.StackElemSize(_transitionBlock.PointerSize);
if (HasThis)
{
ret += slotSize;
}
if (HasRetBuffArg() && _transitionBlock.IsRetBuffPassedAsFirstArg)
{
ret += slotSize;
}
return ret;
}
}
public int GetParamTypeArgOffset()
{
Debug.Assert(HasParamType);
if (_transitionBlock.IsX86)
{
// x86 is special as always
if (!_SIZE_OF_ARG_STACK_COMPUTED)
ForceSigWalk();
switch (_paramTypeLoc)
{
case ParamTypeLocation.Ecx:// PARAM_TYPE_REGISTER_ECX:
return _transitionBlock.OffsetOfArgumentRegisters + TransitionBlock.X86Constants.OffsetOfEcx;
case ParamTypeLocation.Edx:
return _transitionBlock.OffsetOfArgumentRegisters + TransitionBlock.X86Constants.OffsetOfEdx;
default:
break;
}
// The param type arg is last stack argument otherwise
return _transitionBlock.SizeOfTransitionBlock;
}
else
{
// The hidden arg is after this and retbuf arguments by default.
int ret = _transitionBlock.OffsetOfArgumentRegisters;
int slotSize = _transitionBlock.StackElemSize(_transitionBlock.PointerSize);
if (HasThis)
{
ret += slotSize;
}
if (HasRetBuffArg() && _transitionBlock.IsRetBuffPassedAsFirstArg)
{
ret += slotSize;
}
return ret;
}
}
public int GetAsyncContinuationArgOffset()
{
Debug.Assert(HasAsyncContinuation);
if (_transitionBlock.IsX86)
{
// x86 is special as always
if (!_SIZE_OF_ARG_STACK_COMPUTED)
ForceSigWalk();
switch (_asyncContinuationLoc)
{
case AsyncContinuationLocation.Ecx:
return _transitionBlock.OffsetOfArgumentRegisters + TransitionBlock.X86Constants.OffsetOfEcx;
case AsyncContinuationLocation.Edx:
return _transitionBlock.OffsetOfArgumentRegisters + TransitionBlock.X86Constants.OffsetOfEdx;
default:
break;
}
// If the async continuation is a stack arg, then it comes last unless
// there also is a param type arg on the stack, in which case it comes
// before it.
if (HasParamType && _paramTypeLoc == ParamTypeLocation.Stack)
{
return _transitionBlock.SizeOfTransitionBlock + _transitionBlock.PointerSize;
}
return _transitionBlock.SizeOfTransitionBlock;
}
else
{
// The hidden arg is after this, retbuf and param type arguments by default.
int ret = _transitionBlock.OffsetOfArgumentRegisters;
int slotSize = _transitionBlock.StackElemSize(_transitionBlock.PointerSize);
if (HasThis)
{
ret += slotSize;
}
if (HasRetBuffArg() && _transitionBlock.IsRetBuffPassedAsFirstArg)
{
ret += slotSize;
}
if (HasParamType)
{
ret += slotSize;
}
return ret;
}
}
//------------------------------------------------------------
// Each time this is called, this returns a byte offset of the next
// argument from the TransitionBlock* pointer. This offset can be positive *or* negative.
//
// Returns TransitionBlock::InvalidOffset once you've hit the end
// of the list.
//------------------------------------------------------------
public int GetNextOffset()
{
// WRAPPER_NO_CONTRACT;
// SUPPORTS_DAC;
if (!_ITERATION_STARTED)
{
int numRegistersUsed = 0;
if (HasThis)
numRegistersUsed++;
if (HasRetBuffArg() && _transitionBlock.IsRetBuffPassedAsFirstArg)
{
numRegistersUsed++;
}
Debug.Assert(!IsVarArg || !HasParamType);
// DESKTOP BEHAVIOR - This block is disabled for x86 as the param arg is the last argument on .NET Framework x86.
if (!_transitionBlock.IsX86)
{
if (HasParamType)
{
numRegistersUsed++;
}
if (HasAsyncContinuation)
{
numRegistersUsed++;
}
}
if (!_transitionBlock.IsX86 && IsVarArg)
{
numRegistersUsed++;
}
switch (_transitionBlock.Architecture)
{
case TargetArchitecture.X86:
if (IsVarArg)
{
numRegistersUsed = _transitionBlock.NumArgumentRegisters; // Nothing else gets passed in registers for varargs
}
_x86NumRegistersUsed = numRegistersUsed;
_x86OfsStack = (int)(_transitionBlock.OffsetOfArgs + SizeOfArgStack());
break;
case TargetArchitecture.X64:
if (_transitionBlock.IsX64UnixABI)
{
_x64UnixIdxGenReg = numRegistersUsed;
_x64UnixIdxStack = 0;
_x64UnixIdxFPReg = 0;
}
else
{
_x64WindowsCurOfs = _transitionBlock.OffsetOfArgs + numRegistersUsed * _transitionBlock.PointerSize;
}
break;
case TargetArchitecture.Wasm32:
_wasmOfsStack = numRegistersUsed * _transitionBlock.StackElemSize(_transitionBlock.PointerSize);
break;
case TargetArchitecture.ARM:
_armIdxGenReg = numRegistersUsed;
_armOfsStack = 0;
_armWFPRegs = 0;
break;
case TargetArchitecture.ARM64:
_arm64IdxGenReg = numRegistersUsed;
_arm64OfsStack = 0;
_arm64IdxFPReg = 0;
break;
case TargetArchitecture.LoongArch64:
case TargetArchitecture.RiscV64:
_rvLa64IdxGenReg = numRegistersUsed;
_rvLa64OfsStack = 0;
_rvLa64IdxFPReg = 0;
break;
default:
throw new NotImplementedException();
}
_argNum = (_skipFirstArg ? 1 : 0);
_ITERATION_STARTED = true;
}
if (_argNum >= NumFixedArgs)
return TransitionBlock.InvalidOffset;
CorElementType argType = GetArgumentType(_argNum, out _argTypeHandle, out _argForceByRef);
_argTypeHandleOfByRefParam = (argType == CorElementType.ELEMENT_TYPE_BYREF ? _argData.GetByRefArgumentType(_argNum) : default(TypeHandle));
_argNum++;
int argSize = TypeHandle.GetElemSize(argType, _argTypeHandle);
_argType = argType;
_argSize = argSize;
argType = _argForceByRef ? CorElementType.ELEMENT_TYPE_BYREF : argType;
argSize = _argForceByRef ? _transitionBlock.PointerSize : argSize;
int argOfs;
switch (_transitionBlock.Architecture)
{
case TargetArchitecture.X86:
if (_transitionBlock.IsArgumentInRegister(ref _x86NumRegistersUsed, argType, _argTypeHandle))
{
return _transitionBlock.OffsetOfArgumentRegisters + (_transitionBlock.NumArgumentRegisters - _x86NumRegistersUsed) * _transitionBlock.PointerSize;
}
_x86OfsStack -= _transitionBlock.StackElemSize(argSize);
argOfs = _x86OfsStack;
Debug.Assert(argOfs >= _transitionBlock.OffsetOfArgs);
return argOfs;
case TargetArchitecture.X64:
if (_transitionBlock.IsX64UnixABI)
{
int cbArg = _transitionBlock.StackElemSize(argSize);
_hasArgLocDescForStructInRegs = false;
_fX64UnixArgInRegisters = true;
int cFPRegs = 0;
int cGenRegs = 0;
switch (argType)
{
case CorElementType.ELEMENT_TYPE_R4:
// 32-bit floating point argument.
cFPRegs = 1;
break;
case CorElementType.ELEMENT_TYPE_R8:
// 64-bit floating point argument.
cFPRegs = 1;
break;
case CorElementType.ELEMENT_TYPE_VALUETYPE:
{
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR descriptor;
SystemVStructClassificator.GetSystemVAmd64PassStructInRegisterDescriptor(_argTypeHandle.GetRuntimeTypeHandle(), out descriptor);
if (descriptor.passedInRegisters)
{
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:
cGenRegs = cbArg / 8; // GP reg size
break;
}
if (cFPRegs > 0)
{
if (cFPRegs + _x64UnixIdxFPReg <= TransitionBlock.X64UnixTransitionBlock.NUM_FLOAT_ARGUMENT_REGISTERS)
{
int argOfsInner = _transitionBlock.OffsetOfFloatArgumentRegisters + _x64UnixIdxFPReg * 8;
_x64UnixIdxFPReg += cFPRegs;
return argOfsInner;
}
}
else if (cGenRegs > 0)
{
if (_x64UnixIdxGenReg + cGenRegs <= _transitionBlock.NumArgumentRegisters)
{
int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _x64UnixIdxGenReg * 8;
_x64UnixIdxGenReg += cGenRegs;
return argOfsInner;
}
}
_fX64UnixArgInRegisters = false;
argOfs = _transitionBlock.OffsetOfArgs + _x64UnixIdxStack * 8;
int cArgSlots = cbArg / _transitionBlock.PointerSize;
_x64UnixIdxStack += cArgSlots;
return argOfs;
}
else
{
int cFPRegs = 0;
switch (argType)
{
case CorElementType.ELEMENT_TYPE_R4:
// 32-bit floating point argument.
cFPRegs = 1;
break;
case CorElementType.ELEMENT_TYPE_R8:
// 64-bit floating point argument.
cFPRegs = 1;
break;
}
// Each argument takes exactly one slot on AMD64
argOfs = _x64WindowsCurOfs - _transitionBlock.OffsetOfArgs;
_x64WindowsCurOfs += _transitionBlock.PointerSize;
if ((cFPRegs == 0) || (argOfs >= _transitionBlock.SizeOfArgumentRegisters))
{
return argOfs + _transitionBlock.OffsetOfArgs;
}
else
{
int idxFpReg = argOfs / _transitionBlock.PointerSize;
return _transitionBlock.OffsetOfFloatArgumentRegisters + idxFpReg * TransitionBlock.SizeOfM128A;
}
}
case TargetArchitecture.Wasm32:
{
bool isValueType = (argType == CorElementType.ELEMENT_TYPE_VALUETYPE);
WasmValueType actualWasmAbiType = default(WasmValueType);
switch (argType)
{
case CorElementType.ELEMENT_TYPE_VALUETYPE:
actualWasmAbiType = WasmLowering.LowerType(_argTypeHandle.GetRuntimeTypeHandle());
break;
case CorElementType.ELEMENT_TYPE_I8:
case CorElementType.ELEMENT_TYPE_U8:
actualWasmAbiType = WasmValueType.I64;
break;
case CorElementType.ELEMENT_TYPE_R8:
actualWasmAbiType = WasmValueType.F64;
break;
case CorElementType.ELEMENT_TYPE_R4:
actualWasmAbiType = WasmValueType.F32;
break;
default:
actualWasmAbiType = WasmValueType.I32;
break;
}
int cbArg;
int align;
switch (actualWasmAbiType)
{
case WasmValueType.I64:
case WasmValueType.F64:
cbArg = 8;
align = 8;
break;
case WasmValueType.I32:
case WasmValueType.F32:
cbArg = 8;
align = 8;
break;
case WasmValueType.V128:
cbArg = 16;
align = 16;
break;
default:
throw new Exception(); // These are the only WasmValueTypes defined in our transition block handling
}
_wasmOfsStack = ALIGN_UP(_wasmOfsStack, align);
argOfs = _transitionBlock.OffsetOfArgs + _wasmOfsStack;
// Advance the stack pointer over the argument just placed.
_wasmOfsStack += cbArg;
return argOfs;
}
case TargetArchitecture.ARM:
{
// First look at the underlying type of the argument to determine some basic properties:
// 1) The size of the argument in bytes (rounded up to the stack slot size of 4 if necessary).
// 2) Whether the argument represents a floating point primitive (ELEMENT_TYPE_R4 or ELEMENT_TYPE_R8).
// 3) Whether the argument requires 64-bit alignment (anything that contains a Int64/UInt64).
bool fFloatingPoint = false;
bool fRequiresAlign64Bit = false;
switch (argType)
{
case CorElementType.ELEMENT_TYPE_I8:
case CorElementType.ELEMENT_TYPE_U8:
// 64-bit integers require 64-bit alignment on ARM.
fRequiresAlign64Bit = true;
break;
case CorElementType.ELEMENT_TYPE_R4:
// 32-bit floating point argument.
fFloatingPoint = true;
break;
case CorElementType.ELEMENT_TYPE_R8:
// 64-bit floating point argument.
fFloatingPoint = true;
fRequiresAlign64Bit = true;
break;
case CorElementType.ELEMENT_TYPE_VALUETYPE:
{
// Value type case: extract the alignment requirement, note that this has to handle
// the interop "native value types".
fRequiresAlign64Bit = _argTypeHandle.RequiresAlign8();
// Handle HFAs: packed structures of 1-4 floats or doubles that are passed in FP argument
// registers if possible.
if (_argTypeHandle.IsHomogeneousAggregate())
fFloatingPoint = true;
break;
}
default:
// The default is are 4-byte arguments (or promoted to 4 bytes), non-FP and don't require any
// 64-bit alignment.
break;
}
// Now attempt to place the argument into some combination of floating point or general registers and
// the stack.
// Save the alignment requirement
_armRequires64BitAlignment = fRequiresAlign64Bit;
int cbArg = _transitionBlock.StackElemSize(argSize);
Debug.Assert((cbArg % _transitionBlock.PointerSize) == 0);
// Ignore floating point argument placement in registers if we're dealing with a vararg function (the ABI
// specifies this so that vararg processing on the callee side is simplified).
if (fFloatingPoint && _transitionBlock.IsArmhfABI && !IsVarArg)
{
// Handle floating point (primitive) arguments.
// First determine whether we can place the argument in VFP registers. There are 16 32-bit
// and 8 64-bit argument registers that share the same register space (e.g. D0 overlaps S0 and
// S1). The ABI specifies that VFP values will be passed in the lowest sequence of registers that
// haven't been used yet and have the required alignment. So the sequence (float, double, float)
// would be mapped to (S0, D1, S1) or (S0, S2/S3, S1).
//
// We use a 16-bit bitmap to record which registers have been used so far.
//
// So we can use the same basic loop for each argument type (float, double or HFA struct) we set up
// the following input parameters based on the size and alignment requirements of the arguments:
// wAllocMask : bitmask of the number of 32-bit registers we need (1 for 1, 3 for 2, 7 for 3 etc.)
// cSteps : number of loop iterations it'll take to search the 16 registers
// cShift : how many bits to shift the allocation mask on each attempt
ushort wAllocMask = checked((ushort)((1 << (cbArg / 4)) - 1));
ushort cSteps = (ushort)(fRequiresAlign64Bit ? 9 - (cbArg / 8) : 17 - (cbArg / 4));
ushort cShift = fRequiresAlign64Bit ? (ushort)2 : (ushort)1;
// Look through the availability bitmask for a free register or register pair.
for (ushort i = 0; i < cSteps; i++)
{
if ((_armWFPRegs & wAllocMask) == 0)
{
// We found one, mark the register or registers as used.
_armWFPRegs |= wAllocMask;
// Indicate the registers used to the caller and return.
return _transitionBlock.OffsetOfFloatArgumentRegisters + (i * cShift * 4);
}
wAllocMask <<= cShift;
}
// The FP argument is going to live on the stack. Once this happens the ABI demands we mark all FP
// registers as unavailable.
_armWFPRegs = 0xffff;
// Doubles or HFAs containing doubles need the stack aligned appropriately.
if (fRequiresAlign64Bit)
_armOfsStack = ALIGN_UP(_armOfsStack, _transitionBlock.PointerSize * 2);
// Indicate the stack location of the argument to the caller.
int argOfsInner = _transitionBlock.OffsetOfArgs + _armOfsStack;
// Record the stack usage.
_armOfsStack += cbArg;
return argOfsInner;
}
//
// Handle the non-floating point case.
//
if (_armIdxGenReg < 4)
{
if (fRequiresAlign64Bit)
{
// The argument requires 64-bit alignment. Align either the next general argument register if
// we have any left. See step C.3 in the algorithm in the ABI spec.
_armIdxGenReg = ALIGN_UP(_armIdxGenReg, 2);
}
int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _armIdxGenReg * 4;
int cRemainingRegs = 4 - _armIdxGenReg;
if (cbArg <= cRemainingRegs * _transitionBlock.PointerSize)
{
// Mark the registers just allocated as used.
_armIdxGenReg += ALIGN_UP(cbArg, _transitionBlock.PointerSize) / _transitionBlock.PointerSize;
return argOfsInner;
}
// The ABI supports splitting a non-FP argument across registers and the stack. But this is
// disabled if the FP arguments already overflowed onto the stack (i.e. the stack index is not
// zero). The following code marks the general argument registers as exhausted if this condition
// holds. See steps C.5 in the algorithm in the ABI spec.
_armIdxGenReg = 4;
if (_armOfsStack == 0)
{
_armOfsStack += cbArg - cRemainingRegs * _transitionBlock.PointerSize;
return argOfsInner;
}
}
if (fRequiresAlign64Bit)
{
// The argument requires 64-bit alignment. If it is going to be passed on the stack, align
// the next stack slot. See step C.6 in the algorithm in the ABI spec.
_armOfsStack = ALIGN_UP(_armOfsStack, _transitionBlock.PointerSize * 2);
}
argOfs = _transitionBlock.OffsetOfArgs + _armOfsStack;
// Advance the stack pointer over the argument just placed.
_armOfsStack += cbArg;
return argOfs;
}
case TargetArchitecture.ARM64:
{
int cFPRegs = 0;
bool isFloatHFA = false;
switch (argType)
{
case CorElementType.ELEMENT_TYPE_R4:
// 32-bit floating point argument.
cFPRegs = 1;
break;
case CorElementType.ELEMENT_TYPE_R8:
// 64-bit floating point argument.
cFPRegs = 1;
break;
case CorElementType.ELEMENT_TYPE_VALUETYPE:
{
// Handle HAs: packed structures of 1-4 floats, doubles, or short vectors
// that are passed in FP argument registers if possible.
if (_argTypeHandle.IsHomogeneousAggregate())
{
_argLocDescForStructInRegs = new ArgLocDesc();
_argLocDescForStructInRegs.m_idxFloatReg = _arm64IdxFPReg;
int haElementSize = _argTypeHandle.GetHomogeneousAggregateElementSize();
if (haElementSize == 4)
{
isFloatHFA = true;
}
cFPRegs = argSize / haElementSize;
_argLocDescForStructInRegs.m_cFloatReg = cFPRegs;
// Check if we have enough registers available for the HA passing
if (cFPRegs + _arm64IdxFPReg <= 8)
{
_hasArgLocDescForStructInRegs = true;
}
}
else
{
// Composite greater than 16 bytes should be passed by reference
if (argSize > _transitionBlock.EnregisteredParamTypeMaxSize)
{
argSize = _transitionBlock.PointerSize;
}
}
break;
}
default:
break;
}
bool isValueType = (argType == CorElementType.ELEMENT_TYPE_VALUETYPE);
int cbArg = _transitionBlock.StackElemSize(argSize, isValueType, isFloatHFA);
if (cFPRegs > 0 && !IsVarArg)
{
if (cFPRegs + _arm64IdxFPReg <= 8)
{
// Each floating point register in the argument area is 16 bytes.
int argOfsInner = _transitionBlock.OffsetOfFloatArgumentRegisters + _arm64IdxFPReg * 16;
_arm64IdxFPReg += cFPRegs;
return argOfsInner;
}
else
{
_arm64IdxFPReg = 8;
}
}
else
{
Debug.Assert(_transitionBlock.IsAppleArm64ABI || (cbArg % _transitionBlock.PointerSize) == 0);
int regSlots = ALIGN_UP(cbArg, _transitionBlock.PointerSize) / _transitionBlock.PointerSize;
// Only x0-x7 are valid argument registers (x8 is always the return buffer)
if (_arm64IdxGenReg + regSlots <= 8)
{
// The entirety of the arg fits in the register slots.
int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _arm64IdxGenReg * 8;
_arm64IdxGenReg += regSlots;
return argOfsInner;
}
else if (_context.Target.IsWindows && IsVarArg && (_arm64IdxGenReg < 8))
{
// Address the Windows ARM64 varargs case where an arg is split between regs and stack.
// This can happen in the varargs case because the first 64 bytes of the stack are loaded
// into x0-x7, and any remaining stack arguments are placed normally.
int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _arm64IdxGenReg * 8;
// Increase m_ofsStack to account for the space used for the remainder of the arg after
// registers are filled.
_arm64OfsStack += cbArg + (_arm64IdxGenReg - 8) * _transitionBlock.PointerSize;
// We used up the remaining reg slots.
_arm64IdxGenReg = 8;
return argOfsInner;
}
else
{
// Don't use reg slots for this. It will be passed purely on the stack arg space.
_arm64IdxGenReg = 8;
}
}
if (_transitionBlock.IsAppleArm64ABI)
{
int alignment;
if (!isValueType)
{
Debug.Assert((cbArg & (cbArg - 1)) == 0);
alignment = cbArg;
}
else if (isFloatHFA)
{
alignment = 4;
}
else
{
alignment = 8;
}
_arm64OfsStack = ALIGN_UP(_arm64OfsStack, alignment);
}
argOfs = _transitionBlock.OffsetOfArgs + _arm64OfsStack;
_arm64OfsStack += cbArg;
return argOfs;
}
case TargetArchitecture.LoongArch64:
case TargetArchitecture.RiscV64:
{
if (IsVarArg)
throw new NotImplementedException("Varargs on RISC-V and LoongArch not supported yet");
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.RiscV64);
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 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 + _rvLa64IdxFPReg <= _transitionBlock.NumArgumentRegisters) && (1 + _rvLa64IdxGenReg <= _transitionBlock.NumArgumentRegisters))
{
_argLocDescForStructInRegs = new ArgLocDesc();
_argLocDescForStructInRegs.m_idxFloatReg = _rvLa64IdxFPReg;
_argLocDescForStructInRegs.m_cFloatReg = 1;
_argLocDescForStructInRegs.m_structFields = info;
_argLocDescForStructInRegs.m_idxGenReg = _rvLa64IdxGenReg;
_argLocDescForStructInRegs.m_cGenReg = 1;
_hasArgLocDescForStructInRegs = true;
int argOfsInner = ((info.flags & FpStruct.IntFloat) != 0)
? _transitionBlock.OffsetOfArgumentRegisters + _rvLa64IdxGenReg * _transitionBlock.PointerSize
: _transitionBlock.OffsetOfFloatArgumentRegisters + _rvLa64IdxFPReg * _transitionBlock.FloatRegisterSize;
_rvLa64IdxFPReg++;
_rvLa64IdxGenReg++;
return argOfsInner;
}
}
else if (cFPRegs + _rvLa64IdxFPReg <= _transitionBlock.NumArgumentRegisters)
{
int argOfsInner = _transitionBlock.OffsetOfFloatArgumentRegisters + _rvLa64IdxFPReg * _transitionBlock.FloatRegisterSize;
if (info.flags != FpStruct.UseIntCallConv)
{
Debug.Assert((info.flags & (FpStruct.OnlyOne | FpStruct.BothFloat)) != 0);
_argLocDescForStructInRegs = new ArgLocDesc();
_hasArgLocDescForStructInRegs = true;
_argLocDescForStructInRegs.m_idxFloatReg = _rvLa64IdxFPReg;
_argLocDescForStructInRegs.m_cFloatReg = cFPRegs;
_argLocDescForStructInRegs.m_structFields = info;
}
_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;
if (_rvLa64IdxGenReg + regSlots <= _transitionBlock.NumArgumentRegisters)
{
// The entirety of the arg fits in the register slots.
int argOfsInner = _transitionBlock.OffsetOfArgumentRegisters + _rvLa64IdxGenReg * _transitionBlock.PointerSize;
_rvLa64IdxGenReg += regSlots;
return argOfsInner;
}
else if (_rvLa64IdxGenReg < _transitionBlock.NumArgumentRegisters)
{
// 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;
}
}
// Pass argument entirely on stack
argOfs = _transitionBlock.OffsetOfArgs + _rvLa64OfsStack;
_rvLa64OfsStack += cbArg;
return argOfs;
}
default:
throw new NotImplementedException();
}
}
public CorElementType GetArgType(out TypeHandle pTypeHandle)
{
// LIMITED_METHOD_CONTRACT;
pTypeHandle = _argTypeHandle;
return _argType;
}
public CorElementType GetByRefArgType(out TypeHandle pByRefArgTypeHandle)
{
// LIMITED_METHOD_CONTRACT;
pByRefArgTypeHandle = _argTypeHandleOfByRefParam;
return _argType;
}
public int GetArgSize()
{
// LIMITED_METHOD_CONTRACT;
return _argSize;
}
public bool IsValueType()
{
return (_argType == CorElementType.ELEMENT_TYPE_VALUETYPE);
}
public bool IsFloatHfa()
{
if (IsValueType() && !IsVarArg && _argTypeHandle.IsHomogeneousAggregate())
{
int hfaElementSize = _argTypeHandle.GetHomogeneousAggregateElementSize();
return hfaElementSize == 4;
}
return false;
}
private void ForceSigWalk()
{
// This can be only used before the actual argument iteration started
Debug.Assert(!_ITERATION_STARTED);
int numRegistersUsed = 0;
int nSizeOfArgStack = 0;
if (_transitionBlock.IsX86)
{
//
// x86 is special as always
//
if (HasThis)
numRegistersUsed++;
if (HasRetBuffArg() && _transitionBlock.IsRetBuffPassedAsFirstArg)
{
numRegistersUsed++;
}
if (IsVarArg)
{
nSizeOfArgStack += _transitionBlock.PointerSize;
numRegistersUsed = _transitionBlock.NumArgumentRegisters; // Nothing else gets passed in registers for varargs
}
int nArgs = NumFixedArgs;
for (int i = (_skipFirstArg ? 1 : 0); i < nArgs; i++)
{
TypeHandle thArgType;
bool argForcedToBeByref;
CorElementType type = GetArgumentType(i, out thArgType, out argForcedToBeByref);
if (argForcedToBeByref)
type = CorElementType.ELEMENT_TYPE_BYREF;
if (!_transitionBlock.IsArgumentInRegister(ref numRegistersUsed, type, thArgType))
{
int structSize = TypeHandle.GetElemSize(type, thArgType);
nSizeOfArgStack += _transitionBlock.StackElemSize(structSize);
if (nSizeOfArgStack > TransitionBlock.MaxArgSize)
{
throw new NotSupportedException();
}
}
}
// On x86, async continuation comes before param type in allocation order
if (HasAsyncContinuation)
{
if (numRegistersUsed < _transitionBlock.NumArgumentRegisters)
{
numRegistersUsed++;
_asyncContinuationLoc = (numRegistersUsed == 1) ?
AsyncContinuationLocation.Ecx : AsyncContinuationLocation.Edx;
}
else
{
nSizeOfArgStack += _transitionBlock.PointerSize;
_asyncContinuationLoc = AsyncContinuationLocation.Stack;
}
}
if (HasParamType)
{
if (numRegistersUsed < _transitionBlock.NumArgumentRegisters)
{
numRegistersUsed++;
_paramTypeLoc = (numRegistersUsed == 1) ?
ParamTypeLocation.Ecx : ParamTypeLocation.Edx;
}
else
{
nSizeOfArgStack += _transitionBlock.PointerSize;
_paramTypeLoc = ParamTypeLocation.Stack;
}
}
}
else
{
int maxOffset = _transitionBlock.OffsetOfArgs;
int ofs;
while (TransitionBlock.InvalidOffset != (ofs = GetNextOffset()))
{
int stackElemSize;
if (_transitionBlock.IsX64)
{
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.PointerSize;
}
}
else
{
stackElemSize = _transitionBlock.StackElemSize(GetArgSize(), IsValueType(), IsFloatHfa());
if (IsArgPassedByRef())
stackElemSize = _transitionBlock.StackElemSize(_transitionBlock.PointerSize);
}
int endOfs = ofs + stackElemSize;
if (endOfs > maxOffset)
{
if (endOfs > TransitionBlock.MaxArgSize)
{
throw new NotSupportedException();
}
maxOffset = endOfs;
}
}
if (maxOffset == _transitionBlock.OffsetOfArgs && _transitionBlock.IsWasm32)
{
// Wasm puts all arguments on the stack, even the unnamed ones like the param registers, this pointer and async continuation. If we didn't see any named arguments, then we need to account for the unnamed ones here.
maxOffset = _transitionBlock.OffsetOfArgs + _wasmOfsStack;
}
// Clear the iterator started flag
_ITERATION_STARTED = false;
nSizeOfArgStack = maxOffset - _transitionBlock.OffsetOfArgs;
if (_transitionBlock.IsX64 && !_transitionBlock.IsX64UnixABI)
{
nSizeOfArgStack = (nSizeOfArgStack > (int)_transitionBlock.SizeOfArgumentRegisters) ?
(nSizeOfArgStack - _transitionBlock.SizeOfArgumentRegisters) : 0;
}
}
// arg stack size is rounded to the stack slot size on all platforms.
nSizeOfArgStack = ALIGN_UP(nSizeOfArgStack, _transitionBlock.StackElemSize(_transitionBlock.PointerSize));
// Cache the result
_nSizeOfArgStack = nSizeOfArgStack;
_SIZE_OF_ARG_STACK_COMPUTED = true;
Reset();
}
// Get layout information for the argument that the ArgIterator is currently visiting.
public ArgLocDesc? GetArgLoc(int argOffset)
{
switch (_transitionBlock.Architecture)
{
case TargetArchitecture.Wasm32:
{
ArgLocDesc pLoc = new ArgLocDesc();
int byteArgSize = GetArgSize();
if (IsArgPassedByRef())
byteArgSize = _transitionBlock.PointerSize;
pLoc.m_byteStackIndex = _transitionBlock.GetStackArgumentByteIndexFromOffset(argOffset);
pLoc.m_byteStackSize = _transitionBlock.StackElemSize(byteArgSize);
return pLoc;
}
case TargetArchitecture.ARM:
{
// LIMITED_METHOD_CONTRACT;
ArgLocDesc pLoc = new ArgLocDesc();
pLoc.m_fRequires64BitAlignment = _armRequires64BitAlignment;
int byteArgSize = GetArgSize();
if (_transitionBlock.IsFloatArgumentRegisterOffset(argOffset))
{
int floatRegOfsInBytes = argOffset - _transitionBlock.OffsetOfFloatArgumentRegisters;
Debug.Assert((floatRegOfsInBytes % _transitionBlock.FloatRegisterSize) == 0);
pLoc.m_idxFloatReg = floatRegOfsInBytes / _transitionBlock.FloatRegisterSize;
pLoc.m_cFloatReg = ALIGN_UP(byteArgSize, _transitionBlock.FloatRegisterSize) / _transitionBlock.FloatRegisterSize;
return pLoc;
}
if (!_transitionBlock.IsStackArgumentOffset(argOffset))
{
pLoc.m_idxGenReg = _transitionBlock.GetArgumentIndexFromOffset(argOffset);
if (byteArgSize <= (4 - pLoc.m_idxGenReg) * _transitionBlock.PointerSize)
{
pLoc.m_cGenReg = (short)(ALIGN_UP(byteArgSize, _transitionBlock.PointerSize) / _transitionBlock.PointerSize);
}
else
{
pLoc.m_cGenReg = (short)(4 - pLoc.m_idxGenReg);
pLoc.m_byteStackIndex = 0;
pLoc.m_byteStackSize = _transitionBlock.StackElemSize(byteArgSize) - pLoc.m_cGenReg * _transitionBlock.PointerSize;
}
}
else
{
pLoc.m_byteStackIndex = _transitionBlock.GetStackArgumentByteIndexFromOffset(argOffset);
pLoc.m_byteStackSize = _transitionBlock.StackElemSize(byteArgSize);
}
return pLoc;
}
case TargetArchitecture.ARM64:
{
// 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;
if (!_argTypeHandle.IsNull() && _argTypeHandle.IsHomogeneousAggregate())
{
int haElementSize = _argTypeHandle.GetHomogeneousAggregateElementSize();
pLoc.m_cFloatReg = GetArgSize() / haElementSize;
}
else
{
pLoc.m_cFloatReg = 1;
}
return pLoc;
}
int byteArgSize = GetArgSize();
// On ARM64 some composites are implicitly passed by reference.
if (IsArgPassedByRef())
{
byteArgSize = _transitionBlock.PointerSize;
}
if (!_transitionBlock.IsStackArgumentOffset(argOffset))
{
pLoc.m_idxGenReg = _transitionBlock.GetArgumentIndexFromOffset(argOffset);
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.LoongArch64:
case TargetArchitecture.RiscV64:
{
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.X64:
if (_transitionBlock.IsX64UnixABI)
{
if (_hasArgLocDescForStructInRegs)
{
return _argLocDescForStructInRegs;
}
if (argOffset == TransitionBlock.StructInRegsOffset)
{
// We always already have argLocDesc for structs passed in registers, we
// compute it in the GetNextOffset for those since it is always needed.
Debug.Assert(false);
return null;
}
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;
}
else if (!_transitionBlock.IsStackArgumentOffset(argOffset))
{
pLoc.m_idxGenReg = _transitionBlock.GetArgumentIndexFromOffset(argOffset);
pLoc.m_cGenReg = 1;
}
else
{
pLoc.m_byteStackIndex = _transitionBlock.GetStackArgumentByteIndexFromOffset(argOffset);
int argSizeInBytes;
if (IsArgPassedByRef())
argSizeInBytes = _transitionBlock.PointerSize;
else
argSizeInBytes = GetArgSize();
pLoc.m_byteStackSize = _transitionBlock.StackElemSize(argSizeInBytes);
}
return pLoc;
}
else
{
return null;
}
case TargetArchitecture.X86:
return null;
default:
throw new NotImplementedException();
}
}
private int _nSizeOfArgStack; // Cached value of SizeOfArgStack
private int _argNum;
// Cached information about last argument
private CorElementType _argType;
private int _argSize;
private TypeHandle _argTypeHandle;
private TypeHandle _argTypeHandleOfByRefParam;
private bool _argForceByRef;
private int _x86OfsStack; // Current position of the stack iterator
private int _x86NumRegistersUsed;
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
private int _armOfsStack; // Offset of next stack location to be assigned a value
private ushort _armWFPRegs; // Bitmask of available floating point argument registers (s0-s15/d0-d7)
private bool _armRequires64BitAlignment; // Cached info about the current arg
private int _wasmOfsStack; // Cached info about the current arg for Wasm32
private int _arm64IdxGenReg; // Next general register to be assigned a value
private int _arm64OfsStack; // Offset of next stack location to be assigned a value
private int _arm64IdxFPReg; // 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
private bool _SIZE_OF_ARG_STACK_COMPUTED;
private bool _RETURN_FLAGS_COMPUTED;
private bool _RETURN_HAS_RET_BUFFER; // Cached value of HasRetBuffArg
private uint _fpReturnSize;
// Offsets of fields returned according to RISC-V/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,
RETURN_HAS_RET_BUFFER = 0x0008, // Cached value of HasRetBuffArg
*/
private enum ParamTypeLocation
{
Stack,
Ecx,
Edx
}
private ParamTypeLocation _paramTypeLoc;
/* X86: PARAM_TYPE_REGISTER_MASK = 0x0030,
PARAM_TYPE_REGISTER_STACK = 0x0010,
PARAM_TYPE_REGISTER_ECX = 0x0020,
PARAM_TYPE_REGISTER_EDX = 0x0030,*/
private enum AsyncContinuationLocation
{
Stack,
Ecx,
Edx
}
private AsyncContinuationLocation _asyncContinuationLoc;
/* X86: ASYNC_CONTINUATION_REGISTER_MASK = 0x00C0,
ASYNC_CONTINUATION_REGISTER_STACK = 0x0040,
ASYNC_CONTINUATION_REGISTER_ECX = 0x0080,
ASYNC_CONTINUATION_REGISTER_EDX = 0x00C0,*/
// METHOD_INVOKE_NEEDS_ACTIVATION = 0x0040, // Flag used by ArgIteratorForMethodInvoke
// RETURN_FP_SIZE_SHIFT = 8, // The rest of the flags is cached value of GetFPReturnSize
private void ComputeReturnFlags()
{
TypeHandle thRetType;
CorElementType type = GetReturnType(out thRetType, out _RETURN_HAS_RET_BUFFER);
if (!_RETURN_HAS_RET_BUFFER)
{
_transitionBlock.ComputeReturnValueTreatment(type, thRetType, IsVarArg, out _RETURN_HAS_RET_BUFFER, out _fpReturnSize, out _returnedFpFieldOffset1st, out _returnedFpFieldOffset2nd);
}
_RETURN_FLAGS_COMPUTED = true;
}
public static int ALIGN_UP(int input, int align_to)
{
return (input + (align_to - 1)) & ~(align_to - 1);
}
};
}
|