diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc
index 9b9d2d21f191eb..62714b3266a672 100644
--- a/src/coreclr/vm/datadescriptor/datadescriptor.inc
+++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc
@@ -986,6 +986,18 @@ CDAC_TYPE_FIELD(TransitionBlock, T_UINT32, FirstGCRefMapSlot, offsetof(Transitio
CDAC_TYPE_FIELD(TransitionBlock, T_UINT32, ArgumentRegistersOffset, offsetof(TransitionBlock, m_argumentRegisters))
CDAC_TYPE_FIELD(TransitionBlock, T_UINT32, FirstGCRefMapSlot, offsetof(TransitionBlock, m_argumentRegisters))
#endif
+// Negative offset to where float argument registers are saved (relative to TransitionBlock pointer).
+// This is -sizeof(FloatArgumentRegisters) (-padding) on platforms that have them, 0 otherwise.
+#ifdef CALLDESCR_FPARGREGS
+#ifdef TARGET_ARM
+// ARM has 8-byte alignment padding after FloatArgumentRegisters
+CDAC_TYPE_FIELD(TransitionBlock, T_INT32, OffsetOfFloatArgumentRegisters, -(int)(sizeof(FloatArgumentRegisters) + TARGET_POINTER_SIZE))
+#else
+CDAC_TYPE_FIELD(TransitionBlock, T_INT32, OffsetOfFloatArgumentRegisters, -(int)sizeof(FloatArgumentRegisters))
+#endif
+#else
+CDAC_TYPE_FIELD(TransitionBlock, T_INT32, OffsetOfFloatArgumentRegisters, 0)
+#endif
CDAC_TYPE_END(TransitionBlock)
#ifdef DEBUGGING_SUPPORTED
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/CallingConvention/ArgIterator.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/CallingConvention/ArgIterator.cs
new file mode 100644
index 00000000000000..cbb42062124f0a
--- /dev/null
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/CallingConvention/ArgIterator.cs
@@ -0,0 +1,797 @@
+// 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.
+// Ported from crossgen2's ArgIterator.cs.
+
+using System;
+using System.Diagnostics;
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.CallingConvention;
+
+///
+/// Enumerates method arguments and maps each to a register or stack offset
+/// within a TransitionBlock.
+/// Ported from crossgen2's ArgIterator.
+///
+internal struct ArgIterator
+{
+ private readonly CallingConventionInfo _ccInfo;
+ private readonly ArgIteratorData _argData;
+ private readonly bool _hasThis;
+ private readonly bool _hasParamType;
+ private readonly bool _hasAsyncContinuation;
+ private readonly bool _extraFunctionPointerArg;
+ private readonly bool[] _forcedByRefParams;
+ private readonly bool _skipFirstArg;
+ private readonly bool _extraObjectFirstArg;
+
+ // Iteration state
+ private bool _ITERATION_STARTED;
+ private bool _SIZE_OF_ARG_STACK_COMPUTED;
+ private bool _RETURN_FLAGS_COMPUTED;
+ private bool _RETURN_HAS_RET_BUFFER;
+
+ private CorElementType _argType;
+ private ArgTypeInfo _argTypeHandle;
+ private ArgTypeInfo _argTypeHandleOfByRefParam;
+ private int _argSize;
+ private int _argNum;
+ private bool _argForceByRef;
+ private int _nSizeOfArgStack;
+
+ // Per-architecture register allocation state
+ // x86
+ private int _x86NumRegistersUsed;
+ private int _x86OfsStack;
+
+ // x64 Windows
+ private int _x64WindowsCurOfs;
+
+ // x64 Unix
+ private int _x64UnixIdxGenReg;
+ private int _x64UnixIdxStack;
+ private int _x64UnixIdxFPReg;
+
+ // ARM32
+ private int _armIdxGenReg;
+ private int _armOfsStack;
+ private ushort _armWFPRegs;
+ private bool _armRequires64BitAlignment;
+
+ // ARM64
+ private int _arm64IdxGenReg;
+ private int _arm64OfsStack;
+ private int _arm64IdxFPReg;
+
+ // LoongArch64 / RISC-V64
+ private int _rvLa64IdxGenReg;
+ private int _rvLa64OfsStack;
+ private int _rvLa64IdxFPReg;
+
+ // Struct-in-registers tracking
+ private bool _hasArgLocDescForStructInRegs;
+#pragma warning disable CS0649 // Assigned in platform-specific paths (ARM64 HFA, Unix AMD64 struct-in-regs)
+ private ArgLocDesc _argLocDescForStructInRegs;
+#pragma warning restore CS0649
+
+ // x86 param type location
+ private enum ParamTypeLocation
+ {
+ Stack,
+ Ecx,
+ Edx,
+ }
+ private ParamTypeLocation _paramTypeLoc;
+
+ private enum AsyncContinuationLocation
+ {
+ Stack,
+ Ecx,
+ Edx,
+ }
+ private AsyncContinuationLocation _asyncContinuationLoc;
+
+ 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);
+
+ public ArgIterator(
+ CallingConventionInfo ccInfo,
+ ArgIteratorData argData,
+ bool hasParamType,
+ bool hasAsyncContinuation,
+ bool[] forcedByRefParams,
+ bool skipFirstArg = false,
+ bool extraObjectFirstArg = false,
+ bool extraFunctionPointerArg = false)
+ {
+ this = default;
+ _ccInfo = ccInfo;
+ _argData = argData;
+ _hasThis = argData.HasThis();
+ _hasParamType = hasParamType;
+ _hasAsyncContinuation = hasAsyncContinuation;
+ _extraFunctionPointerArg = extraFunctionPointerArg;
+ _forcedByRefParams = forcedByRefParams;
+ _skipFirstArg = skipFirstArg;
+ _extraObjectFirstArg = extraObjectFirstArg;
+ }
+
+ public CorElementType GetArgumentType(int argNum, out ArgTypeInfo thArgType, out bool forceByRefReturn)
+ {
+ forceByRefReturn = false;
+
+ if (_extraObjectFirstArg && argNum == 0)
+ {
+ thArgType = ArgTypeInfo.ForPrimitive(CorElementType.Class, _ccInfo.PointerSize);
+ return CorElementType.Class;
+ }
+
+ argNum = _extraObjectFirstArg ? argNum - 1 : argNum;
+
+ if (_forcedByRefParams is not null && (argNum + 1) < _forcedByRefParams.Length)
+ forceByRefReturn = _forcedByRefParams[argNum + 1];
+
+ if (_extraFunctionPointerArg && argNum == _argData.NumFixedArgs())
+ {
+ thArgType = ArgTypeInfo.ForPrimitive(CorElementType.I, _ccInfo.PointerSize);
+ return CorElementType.I;
+ }
+
+ return _argData.GetArgumentType(argNum, out thArgType);
+ }
+
+ public CorElementType GetReturnType(out ArgTypeInfo thRetType, out bool forceByRefReturn)
+ {
+ forceByRefReturn = _forcedByRefParams is not null && _forcedByRefParams.Length > 0 && _forcedByRefParams[0];
+ return _argData.GetReturnType(out thRetType);
+ }
+
+ public void Reset()
+ {
+ _argType = default;
+ _argTypeHandle = default;
+ _argSize = 0;
+ _argNum = 0;
+ _argForceByRef = false;
+ _ITERATION_STARTED = false;
+ }
+
+ private uint SizeOfArgStack()
+ {
+ if (!_SIZE_OF_ARG_STACK_COMPUTED)
+ ForceSigWalk();
+ Debug.Assert(_SIZE_OF_ARG_STACK_COMPUTED);
+ return (uint)_nSizeOfArgStack;
+ }
+
+ public int SizeOfFrameArgumentArray()
+ {
+ uint size = SizeOfArgStack();
+
+ if (_ccInfo.Architecture == RuntimeInfoArchitecture.X64 && !_ccInfo.IsX64UnixABI)
+ {
+ size += (uint)_ccInfo.SizeOfArgumentRegisters;
+ }
+
+ return (int)size;
+ }
+
+ public uint CbStackPop()
+ {
+ if (_ccInfo.Architecture == RuntimeInfoArchitecture.X86)
+ {
+ return IsVarArg ? 0 : SizeOfArgStack();
+ }
+ throw new NotImplementedException();
+ }
+
+ public bool HasRetBuffArg()
+ {
+ if (!_RETURN_FLAGS_COMPUTED)
+ ComputeReturnFlags();
+ return _RETURN_HAS_RET_BUFFER;
+ }
+
+ public int GetThisOffset() => _ccInfo.ThisOffset;
+
+ public int GetVASigCookieOffset()
+ {
+ Debug.Assert(IsVarArg);
+
+ if (_ccInfo.Architecture == RuntimeInfoArchitecture.X86)
+ {
+ return (int)_ccInfo.SizeOfTransitionBlock;
+ }
+
+ int ret = (int)_ccInfo.ArgumentRegistersOffset;
+ if (HasThis)
+ ret += _ccInfo.PointerSize;
+ if (HasRetBuffArg() && _ccInfo.IsRetBuffPassedAsFirstArg)
+ ret += _ccInfo.PointerSize;
+ return ret;
+ }
+
+ public int GetParamTypeArgOffset()
+ {
+ Debug.Assert(HasParamType);
+
+ if (_ccInfo.Architecture == RuntimeInfoArchitecture.X86)
+ {
+ if (!_SIZE_OF_ARG_STACK_COMPUTED)
+ ForceSigWalk();
+
+ return _paramTypeLoc switch
+ {
+ ParamTypeLocation.Ecx => (int)_ccInfo.ArgumentRegistersOffset + _ccInfo.PointerSize, // ECX offset
+ ParamTypeLocation.Edx => (int)_ccInfo.ArgumentRegistersOffset, // EDX offset
+ _ => (int)_ccInfo.SizeOfTransitionBlock,
+ };
+ }
+
+ int ret = (int)_ccInfo.ArgumentRegistersOffset;
+ if (HasThis) ret += _ccInfo.PointerSize;
+ if (HasRetBuffArg() && _ccInfo.IsRetBuffPassedAsFirstArg) ret += _ccInfo.PointerSize;
+ return ret;
+ }
+
+ public int GetAsyncContinuationArgOffset()
+ {
+ Debug.Assert(HasAsyncContinuation);
+
+ if (_ccInfo.Architecture == RuntimeInfoArchitecture.X86)
+ {
+ if (!_SIZE_OF_ARG_STACK_COMPUTED)
+ ForceSigWalk();
+
+ return _asyncContinuationLoc switch
+ {
+ AsyncContinuationLocation.Ecx => (int)_ccInfo.ArgumentRegistersOffset + _ccInfo.PointerSize,
+ AsyncContinuationLocation.Edx => (int)_ccInfo.ArgumentRegistersOffset,
+ _ => HasParamType && _paramTypeLoc == ParamTypeLocation.Stack
+ ? (int)_ccInfo.SizeOfTransitionBlock + _ccInfo.PointerSize
+ : (int)_ccInfo.SizeOfTransitionBlock,
+ };
+ }
+
+ int ret = (int)_ccInfo.ArgumentRegistersOffset;
+ if (HasThis) ret += _ccInfo.PointerSize;
+ if (HasRetBuffArg() && _ccInfo.IsRetBuffPassedAsFirstArg) ret += _ccInfo.PointerSize;
+ if (HasParamType) ret += _ccInfo.PointerSize;
+ return ret;
+ }
+
+ public bool IsArgPassedByRef()
+ {
+ if (_argForceByRef)
+ return true;
+ if (_argType == CorElementType.Byref)
+ return true;
+
+ if (_ccInfo.EnregisteredParamTypeMaxSize != 0)
+ {
+ return _ccInfo.Architecture switch
+ {
+ RuntimeInfoArchitecture.X64 => _ccInfo.IsArgPassedByRef(_argSize),
+ RuntimeInfoArchitecture.Arm64 => _argType == CorElementType.ValueType
+ && (_argSize > _ccInfo.EnregisteredParamTypeMaxSize) && (!_argTypeHandle.IsHomogeneousAggregate || IsVarArg),
+ RuntimeInfoArchitecture.LoongArch64 or RuntimeInfoArchitecture.RiscV64 => _argType == CorElementType.ValueType
+ && _argSize > _ccInfo.EnregisteredParamTypeMaxSize,
+ _ => false,
+ };
+ }
+ return false;
+ }
+
+ public ArgLocDesc? GetArgLoc(int _)
+ {
+ return _hasArgLocDescForStructInRegs ? _argLocDescForStructInRegs : null;
+ }
+
+ public int GetArgSize() => _argSize;
+
+ ///
+ /// Returns the next argument's offset in the transition block, or
+ /// when all arguments
+ /// have been enumerated.
+ ///
+ public int GetNextOffset()
+ {
+ if (!_ITERATION_STARTED)
+ {
+ int numRegistersUsed = 0;
+
+ if (HasThis) numRegistersUsed++;
+ if (HasRetBuffArg() && _ccInfo.IsRetBuffPassedAsFirstArg) numRegistersUsed++;
+
+ Debug.Assert(!IsVarArg || !HasParamType);
+
+ if (_ccInfo.Architecture != RuntimeInfoArchitecture.X86)
+ {
+ if (HasParamType) numRegistersUsed++;
+ if (HasAsyncContinuation) numRegistersUsed++;
+ }
+
+ if (_ccInfo.Architecture != RuntimeInfoArchitecture.X86 && IsVarArg)
+ numRegistersUsed++;
+
+ switch (_ccInfo.Architecture)
+ {
+ case RuntimeInfoArchitecture.X86:
+ if (IsVarArg) numRegistersUsed = _ccInfo.NumArgumentRegisters;
+ _x86NumRegistersUsed = numRegistersUsed;
+ _x86OfsStack = (int)(_ccInfo.OffsetOfArgs + SizeOfArgStack());
+ break;
+
+ case RuntimeInfoArchitecture.X64:
+ if (_ccInfo.IsX64UnixABI)
+ {
+ _x64UnixIdxGenReg = numRegistersUsed;
+ _x64UnixIdxStack = 0;
+ _x64UnixIdxFPReg = 0;
+ }
+ else
+ {
+ _x64WindowsCurOfs = (int)_ccInfo.OffsetOfArgs + numRegistersUsed * _ccInfo.PointerSize;
+ }
+ break;
+
+ case RuntimeInfoArchitecture.Arm:
+ _armIdxGenReg = numRegistersUsed;
+ _armOfsStack = 0;
+ _armWFPRegs = 0;
+ break;
+
+ case RuntimeInfoArchitecture.Arm64:
+ _arm64IdxGenReg = numRegistersUsed;
+ _arm64OfsStack = 0;
+ _arm64IdxFPReg = 0;
+ break;
+
+ case RuntimeInfoArchitecture.LoongArch64:
+ case RuntimeInfoArchitecture.RiscV64:
+ _rvLa64IdxGenReg = numRegistersUsed;
+ _rvLa64OfsStack = 0;
+ _rvLa64IdxFPReg = 0;
+ break;
+
+ default:
+ throw new NotSupportedException(_ccInfo.Architecture.ToString());
+ }
+
+ _argNum = _skipFirstArg ? 1 : 0;
+ _ITERATION_STARTED = true;
+ }
+
+ if (_argNum >= NumFixedArgs)
+ return CallingConventionInfo.InvalidOffset;
+
+ CorElementType argType = GetArgumentType(_argNum, out _argTypeHandle, out _argForceByRef);
+ _argTypeHandleOfByRefParam = argType == CorElementType.Byref ? _argData.GetByRefArgumentType(_argNum) : default;
+ _argNum++;
+
+ int argSize = ArgTypeInfo.GetElemSize(argType, _argTypeHandle, _ccInfo.PointerSize);
+ _argType = argType;
+ _argSize = argSize;
+
+ argType = _argForceByRef ? CorElementType.Byref : argType;
+ argSize = _argForceByRef ? _ccInfo.PointerSize : argSize;
+
+ _hasArgLocDescForStructInRegs = false;
+
+ switch (_ccInfo.Architecture)
+ {
+ case RuntimeInfoArchitecture.X64:
+ return GetNextOffsetX64(argType, argSize);
+
+ case RuntimeInfoArchitecture.Arm64:
+ return GetNextOffsetArm64(argType, argSize);
+
+ case RuntimeInfoArchitecture.X86:
+ return GetNextOffsetX86(argType, argSize);
+
+ case RuntimeInfoArchitecture.Arm:
+ return GetNextOffsetArm32(argType, argSize);
+
+ case RuntimeInfoArchitecture.LoongArch64:
+ case RuntimeInfoArchitecture.RiscV64:
+ return GetNextOffsetRiscVLoongArch(argType, argSize);
+
+ default:
+ throw new NotSupportedException(_ccInfo.Architecture.ToString());
+ }
+ }
+
+ // ---- Per-architecture GetNextOffset implementations ----
+ // These match crossgen2's ArgIterator.GetNextOffset() switch cases.
+
+ private int GetNextOffsetX64(CorElementType argType, int argSize)
+ {
+ if (_ccInfo.IsX64UnixABI)
+ {
+ // TODO: Full Unix AMD64 implementation with SystemV struct classification
+ // For now, simplified: all args go through GP regs then stack
+ int cbArg = _ccInfo.StackElemSize(argSize);
+ int cGenRegs = cbArg / 8;
+
+ if (argType is CorElementType.R4 or CorElementType.R8)
+ {
+ if (_x64UnixIdxFPReg < _ccInfo.NumFloatArgumentRegisters)
+ {
+ int argOfs = _ccInfo.OffsetOfFloatArgumentRegisters + _x64UnixIdxFPReg * _ccInfo.FloatRegisterSize;
+ _x64UnixIdxFPReg++;
+ return argOfs;
+ }
+ }
+ else if (cGenRegs > 0 && _x64UnixIdxGenReg + cGenRegs <= _ccInfo.NumArgumentRegisters)
+ {
+ int argOfs = (int)_ccInfo.ArgumentRegistersOffset + _x64UnixIdxGenReg * _ccInfo.PointerSize;
+ _x64UnixIdxGenReg += cGenRegs;
+ return argOfs;
+ }
+
+ int stackOfs = (int)_ccInfo.OffsetOfArgs + _x64UnixIdxStack * _ccInfo.PointerSize;
+ _x64UnixIdxStack += _ccInfo.StackElemSize(argSize) / _ccInfo.PointerSize;
+ return stackOfs;
+ }
+ else
+ {
+ // Windows x64: each arg takes exactly one slot
+ int cFPRegs = argType is CorElementType.R4 or CorElementType.R8 ? 1 : 0;
+
+ int argOfs = _x64WindowsCurOfs - (int)_ccInfo.OffsetOfArgs;
+ _x64WindowsCurOfs += _ccInfo.PointerSize;
+
+ if (cFPRegs == 0 || argOfs >= _ccInfo.SizeOfArgumentRegisters)
+ {
+ return argOfs + (int)_ccInfo.OffsetOfArgs;
+ }
+ else
+ {
+ int idxFpReg = argOfs / _ccInfo.PointerSize;
+ return _ccInfo.OffsetOfFloatArgumentRegisters + idxFpReg * 16; // SizeOfM128A
+ }
+ }
+ }
+
+ private int GetNextOffsetArm64(CorElementType argType, int argSize)
+ {
+ int cFPRegs = 0;
+ bool isFloatHFA = false;
+
+ switch (argType)
+ {
+ case CorElementType.R4:
+ case CorElementType.R8:
+ cFPRegs = 1;
+ break;
+
+ case CorElementType.ValueType:
+ if (_argTypeHandle.IsHomogeneousAggregate)
+ {
+ int haElementSize = _argTypeHandle.HomogeneousAggregateElementSize;
+ if (haElementSize == 4) isFloatHFA = true;
+ cFPRegs = argSize / haElementSize;
+ }
+ else if (argSize > _ccInfo.EnregisteredParamTypeMaxSize)
+ {
+ argSize = _ccInfo.PointerSize;
+ }
+ break;
+ }
+
+ bool isValueType = argType == CorElementType.ValueType;
+ int cbArg = _ccInfo.StackElemSize(argSize, isValueType, isFloatHFA);
+
+ if (cFPRegs > 0 && !IsVarArg)
+ {
+ if (cFPRegs + _arm64IdxFPReg <= 8)
+ {
+ int argOfs = _ccInfo.OffsetOfFloatArgumentRegisters + _arm64IdxFPReg * 16;
+ _arm64IdxFPReg += cFPRegs;
+ return argOfs;
+ }
+ else
+ {
+ _arm64IdxFPReg = 8;
+ }
+ }
+ else
+ {
+ int regSlots = CallingConventionInfo.AlignUp(cbArg, _ccInfo.PointerSize) / _ccInfo.PointerSize;
+ if (_arm64IdxGenReg + regSlots <= 8)
+ {
+ int argOfs = (int)_ccInfo.ArgumentRegistersOffset + _arm64IdxGenReg * 8;
+ _arm64IdxGenReg += regSlots;
+ return argOfs;
+ }
+ else
+ {
+ _arm64IdxGenReg = 8;
+ }
+ }
+
+ if (_ccInfo.IsAppleArm64ABI)
+ {
+ int alignment = isValueType ? (isFloatHFA ? 4 : 8) : cbArg;
+ _arm64OfsStack = CallingConventionInfo.AlignUp(_arm64OfsStack, alignment);
+ }
+
+ int result = (int)_ccInfo.OffsetOfArgs + _arm64OfsStack;
+ _arm64OfsStack += cbArg;
+ return result;
+ }
+
+ private int GetNextOffsetX86(CorElementType argType, int argSize)
+ {
+ if (_x86NumRegistersUsed < _ccInfo.NumArgumentRegisters
+ && argType is not CorElementType.ValueType
+ and not CorElementType.R4
+ and not CorElementType.R8
+ and not CorElementType.I8
+ and not CorElementType.U8)
+ {
+ _x86NumRegistersUsed++;
+ return (int)_ccInfo.ArgumentRegistersOffset +
+ (_ccInfo.NumArgumentRegisters - _x86NumRegistersUsed) * _ccInfo.PointerSize;
+ }
+
+ int cbArg = _ccInfo.StackElemSize(argSize);
+ _x86OfsStack -= cbArg;
+ return _x86OfsStack;
+ }
+
+ private int GetNextOffsetArm32(CorElementType argType, int argSize)
+ {
+ bool fFloatingPoint = false;
+ bool fRequiresAlign64Bit = false;
+
+ switch (argType)
+ {
+ case CorElementType.I8:
+ case CorElementType.U8:
+ fRequiresAlign64Bit = true;
+ break;
+ case CorElementType.R4:
+ fFloatingPoint = true;
+ break;
+ case CorElementType.R8:
+ fFloatingPoint = true;
+ fRequiresAlign64Bit = true;
+ break;
+ case CorElementType.ValueType:
+ fRequiresAlign64Bit = _argTypeHandle.RequiresAlign8;
+ if (_argTypeHandle.IsHomogeneousAggregate) fFloatingPoint = true;
+ break;
+ }
+
+ _armRequires64BitAlignment = fRequiresAlign64Bit;
+ int cbArg = _ccInfo.StackElemSize(argSize);
+
+ if (fFloatingPoint && _ccInfo.IsArmhfABI && !IsVarArg)
+ {
+ 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;
+
+ for (ushort i = 0; i < cSteps; i++)
+ {
+ if ((_armWFPRegs & wAllocMask) == 0)
+ {
+ _armWFPRegs |= wAllocMask;
+ return _ccInfo.OffsetOfFloatArgumentRegisters + (i * cShift * 4);
+ }
+ wAllocMask <<= cShift;
+ }
+
+ _armWFPRegs = 0xffff;
+
+ if (fRequiresAlign64Bit)
+ _armOfsStack = CallingConventionInfo.AlignUp(_armOfsStack, _ccInfo.PointerSize * 2);
+
+ int argOfs = (int)_ccInfo.OffsetOfArgs + _armOfsStack;
+ _armOfsStack += cbArg;
+ return argOfs;
+ }
+
+ if (_armIdxGenReg < 4)
+ {
+ if (fRequiresAlign64Bit)
+ _armIdxGenReg = CallingConventionInfo.AlignUp(_armIdxGenReg, 2);
+
+ int argOfs = (int)_ccInfo.ArgumentRegistersOffset + _armIdxGenReg * 4;
+ int cRemainingRegs = 4 - _armIdxGenReg;
+
+ if (cbArg <= cRemainingRegs * _ccInfo.PointerSize)
+ {
+ _armIdxGenReg += CallingConventionInfo.AlignUp(cbArg, _ccInfo.PointerSize) / _ccInfo.PointerSize;
+ return argOfs;
+ }
+
+ _armIdxGenReg = 4;
+
+ if (_armOfsStack == 0)
+ {
+ _armOfsStack += cbArg - cRemainingRegs * _ccInfo.PointerSize;
+ return argOfs;
+ }
+ }
+
+ if (fRequiresAlign64Bit)
+ _armOfsStack = CallingConventionInfo.AlignUp(_armOfsStack, _ccInfo.PointerSize * 2);
+
+ int result = (int)_ccInfo.OffsetOfArgs + _armOfsStack;
+ _armOfsStack += cbArg;
+ return result;
+ }
+
+ private int GetNextOffsetRiscVLoongArch(CorElementType argType, int argSize)
+ {
+ // Simplified: no FP struct detection, just use integer calling convention
+ int cFPRegs = argType is CorElementType.R4 or CorElementType.R8 ? 1 : 0;
+
+ if (argType == CorElementType.ValueType && argSize > _ccInfo.EnregisteredParamTypeMaxSize)
+ argSize = _ccInfo.PointerSize;
+
+ int cbArg = _ccInfo.StackElemSize(argSize);
+
+ if (cFPRegs > 0 && !IsVarArg && cFPRegs + _rvLa64IdxFPReg <= _ccInfo.NumFloatArgumentRegisters)
+ {
+ int argOfs = _ccInfo.OffsetOfFloatArgumentRegisters + _rvLa64IdxFPReg * _ccInfo.FloatRegisterSize;
+ _rvLa64IdxFPReg += cFPRegs;
+ return argOfs;
+ }
+
+ int regSlots = CallingConventionInfo.AlignUp(cbArg, _ccInfo.PointerSize) / _ccInfo.PointerSize;
+ if (_rvLa64IdxGenReg + regSlots <= _ccInfo.NumArgumentRegisters)
+ {
+ int argOfs = (int)_ccInfo.ArgumentRegistersOffset + _rvLa64IdxGenReg * _ccInfo.PointerSize;
+ _rvLa64IdxGenReg += regSlots;
+ return argOfs;
+ }
+ else
+ {
+ _rvLa64IdxGenReg = _ccInfo.NumArgumentRegisters;
+ }
+
+ int result = (int)_ccInfo.OffsetOfArgs + _rvLa64OfsStack;
+ _rvLa64OfsStack += cbArg;
+ return result;
+ }
+
+ // ---- Return type computation ----
+
+ private void ComputeReturnFlags()
+ {
+ _RETURN_FLAGS_COMPUTED = true;
+ CorElementType retType = GetReturnType(out ArgTypeInfo thRetType, out bool forceByRef);
+
+ if (forceByRef)
+ {
+ _RETURN_HAS_RET_BUFFER = true;
+ return;
+ }
+
+ switch (retType)
+ {
+ case CorElementType.TypedByRef:
+ _RETURN_HAS_RET_BUFFER = true;
+ break;
+
+ case CorElementType.ValueType:
+ if (thRetType.Size > _ccInfo.EnregisteredParamTypeMaxSize && _ccInfo.EnregisteredParamTypeMaxSize > 0)
+ {
+ _RETURN_HAS_RET_BUFFER = true;
+ }
+ else if (_ccInfo.Architecture is RuntimeInfoArchitecture.X86 or RuntimeInfoArchitecture.X64)
+ {
+ int size = thRetType.Size;
+ if ((size & (size - 1)) != 0) // not power of 2
+ _RETURN_HAS_RET_BUFFER = true;
+ }
+ break;
+ }
+ }
+
+ private void ForceSigWalk()
+ {
+ Debug.Assert(!_ITERATION_STARTED);
+
+ int numRegistersUsed = 0;
+ int nSizeOfArgStack = 0;
+
+ if (_ccInfo.Architecture == RuntimeInfoArchitecture.X86)
+ {
+ if (HasThis) numRegistersUsed++;
+ if (HasRetBuffArg() && _ccInfo.IsRetBuffPassedAsFirstArg) numRegistersUsed++;
+ if (IsVarArg)
+ {
+ nSizeOfArgStack += _ccInfo.PointerSize;
+ numRegistersUsed = _ccInfo.NumArgumentRegisters;
+ }
+
+ int nArgs = NumFixedArgs;
+ for (int i = _skipFirstArg ? 1 : 0; i < nArgs; i++)
+ {
+ CorElementType type = GetArgumentType(i, out ArgTypeInfo thArgType, out bool argForced);
+ if (argForced) type = CorElementType.Byref;
+
+ // Simplified: assume all non-trivial types go to stack
+ int structSize = ArgTypeInfo.GetElemSize(type, thArgType, _ccInfo.PointerSize);
+ nSizeOfArgStack += _ccInfo.StackElemSize(structSize);
+ }
+
+ if (HasAsyncContinuation)
+ {
+ if (numRegistersUsed < _ccInfo.NumArgumentRegisters)
+ {
+ numRegistersUsed++;
+ _asyncContinuationLoc = numRegistersUsed == 1 ? AsyncContinuationLocation.Ecx : AsyncContinuationLocation.Edx;
+ }
+ else
+ {
+ nSizeOfArgStack += _ccInfo.PointerSize;
+ _asyncContinuationLoc = AsyncContinuationLocation.Stack;
+ }
+ }
+
+ if (HasParamType)
+ {
+ if (numRegistersUsed < _ccInfo.NumArgumentRegisters)
+ {
+ numRegistersUsed++;
+ _paramTypeLoc = numRegistersUsed == 1 ? ParamTypeLocation.Ecx : ParamTypeLocation.Edx;
+ }
+ else
+ {
+ nSizeOfArgStack += _ccInfo.PointerSize;
+ _paramTypeLoc = ParamTypeLocation.Stack;
+ }
+ }
+ }
+ else
+ {
+ // Non-x86: iterate through GetNextOffset to compute stack size
+ int maxOffset = (int)_ccInfo.OffsetOfArgs;
+ int ofs;
+ while (CallingConventionInfo.InvalidOffset != (ofs = GetNextOffset()))
+ {
+ int stackElemSize;
+ if (_ccInfo.Architecture == RuntimeInfoArchitecture.X64)
+ {
+ stackElemSize = _ccInfo.IsX64UnixABI
+ ? _ccInfo.StackElemSize(GetArgSize())
+ : _ccInfo.PointerSize;
+ }
+ else
+ {
+ stackElemSize = _ccInfo.StackElemSize(GetArgSize());
+ }
+
+ int endOfs = ofs + stackElemSize;
+ if (IsArgumentRegisterOffset(ofs))
+ continue;
+ if (CallingConventionInfo.IsFloatArgumentRegisterOffset(ofs))
+ continue;
+ if (ofs == CallingConventionInfo.StructInRegsOffset)
+ continue;
+ if (endOfs > maxOffset)
+ maxOffset = endOfs;
+ }
+
+ nSizeOfArgStack = maxOffset - (int)_ccInfo.OffsetOfArgs;
+ Reset();
+ }
+
+ _nSizeOfArgStack = nSizeOfArgStack;
+ _SIZE_OF_ARG_STACK_COMPUTED = true;
+ }
+
+ private bool IsArgumentRegisterOffset(int offset)
+ {
+ return _ccInfo.IsArgumentRegisterOffset(offset);
+ }
+}
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/CallingConvention/ArgIteratorData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/CallingConvention/ArgIteratorData.cs
new file mode 100644
index 00000000000000..b48a86f5b2e0ee
--- /dev/null
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/CallingConvention/ArgIteratorData.cs
@@ -0,0 +1,82 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// Ported from crossgen2's ArgIterator.cs — data holder types.
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.CallingConvention;
+
+///
+/// Describes how a single argument is laid out in registers and/or stack locations.
+/// Ported from crossgen2's ArgLocDesc.
+///
+internal struct ArgLocDesc
+{
+ public int m_idxFloatReg;
+ public int m_cFloatReg;
+
+ public int m_idxGenReg;
+ public short m_cGenReg;
+
+ public bool m_fRequires64BitAlignment;
+
+ public int m_byteStackIndex;
+ public int m_byteStackSize;
+
+ public void Init()
+ {
+ m_idxFloatReg = -1;
+ m_cFloatReg = 0;
+ m_idxGenReg = -1;
+ m_cGenReg = 0;
+ m_byteStackIndex = -1;
+ m_byteStackSize = 0;
+ m_fRequires64BitAlignment = false;
+ }
+}
+
+///
+/// Holds parsed method signature data for .
+/// Ported from crossgen2's ArgIteratorData.
+///
+internal sealed class ArgIteratorData
+{
+ private readonly bool _hasThis;
+ private readonly bool _isVarArg;
+ private readonly ArgTypeInfo[] _parameterTypes;
+ private readonly ArgTypeInfo _returnType;
+
+ public ArgIteratorData(
+ bool hasThis,
+ bool isVarArg,
+ ArgTypeInfo[] parameterTypes,
+ ArgTypeInfo returnType)
+ {
+ _hasThis = hasThis;
+ _isVarArg = isVarArg;
+ _parameterTypes = parameterTypes;
+ _returnType = returnType;
+ }
+
+ public bool HasThis() => _hasThis;
+ public bool IsVarArg() => _isVarArg;
+ public int NumFixedArgs() => _parameterTypes.Length;
+
+ public CorElementType GetArgumentType(int argNum, out ArgTypeInfo thArgType)
+ {
+ thArgType = _parameterTypes[argNum];
+ return thArgType.CorElementType;
+ }
+
+ public ArgTypeInfo GetByRefArgumentType(int argNum)
+ {
+ if (argNum < _parameterTypes.Length && _parameterTypes[argNum].CorElementType == CorElementType.Byref)
+ return _parameterTypes[argNum];
+ return default;
+ }
+
+ public CorElementType GetReturnType(out ArgTypeInfo thRetType)
+ {
+ thRetType = _returnType;
+ return thRetType.CorElementType;
+ }
+}
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/CallingConvention/ArgTypeInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/CallingConvention/ArgTypeInfo.cs
new file mode 100644
index 00000000000000..bd3ca6ecf632d5
--- /dev/null
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/CallingConvention/ArgTypeInfo.cs
@@ -0,0 +1,142 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// Type information needed by ArgIterator for calling convention analysis.
+// Ported from crossgen2's TypeHandle struct in ArgIterator.cs.
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.CallingConvention;
+
+///
+/// Pre-computed type information needed by for
+/// calling convention analysis. This is a value type to avoid allocations
+/// during argument iteration.
+///
+///
+/// Mirrors crossgen2's TypeHandle struct in ArgIterator.cs, but uses
+/// data from the cDAC's rather than
+/// crossgen2's TypeDesc.
+///
+internal readonly struct ArgTypeInfo
+{
+ public CorElementType CorElementType { get; init; }
+ public int Size { get; init; }
+ public bool IsValueType { get; init; }
+ public bool RequiresAlign8 { get; init; }
+ public bool IsHomogeneousAggregate { get; init; }
+ public int HomogeneousAggregateElementSize { get; init; }
+
+ ///
+ /// The TypeHandle from the target runtime, used for value type field enumeration
+ /// and SystemV struct classification.
+ ///
+ public TypeHandle RuntimeTypeHandle { get; init; }
+
+ public bool IsNull => CorElementType == default && Size == 0;
+
+ ///
+ /// Gets the element size for a given CorElementType, matching crossgen2's
+ /// TypeHandle.GetElemSize. Returns the type's actual size for value
+ /// types, or pointer size for reference types.
+ ///
+ public static int GetElemSize(CorElementType t, ArgTypeInfo thValueType, int pointerSize)
+ {
+ if ((int)t <= 0x1d)
+ {
+ int elemSize = s_elemSizes[(int)t];
+ if (elemSize == -1)
+ return thValueType.Size;
+ if (elemSize == -2)
+ return pointerSize;
+ return elemSize;
+ }
+ return 0;
+ }
+
+ private static readonly int[] s_elemSizes =
+ [
+ 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
+ ];
+
+ ///
+ /// Creates an from a target TypeHandle using the
+ /// runtime type system contract.
+ ///
+ public static ArgTypeInfo FromTypeHandle(Target target, TypeHandle th)
+ {
+ IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem;
+ CorElementType corType = rts.GetSignatureCorElementType(th);
+
+ bool isValueType = corType is CorElementType.ValueType;
+ int size = isValueType
+ ? (int)rts.GetBaseSize(th) - 2 * target.PointerSize // InstanceFieldSize = BaseSize - ObjHeader - MethodTable ptr
+ : target.PointerSize;
+
+ bool requiresAlign8 = false;
+ bool isHfa = false;
+ int hfaElemSize = 0;
+
+ if (isValueType)
+ {
+ // TODO: Implement RequiresAlign8 via IRuntimeTypeSystem
+ // TODO: Implement IsHomogeneousAggregate via IRuntimeTypeSystem
+ }
+
+ return new ArgTypeInfo
+ {
+ CorElementType = corType,
+ Size = size,
+ IsValueType = isValueType,
+ RequiresAlign8 = requiresAlign8,
+ IsHomogeneousAggregate = isHfa,
+ HomogeneousAggregateElementSize = hfaElemSize,
+ RuntimeTypeHandle = th,
+ };
+ }
+
+ ///
+ /// Creates an for a primitive type that doesn't need
+ /// type handle resolution.
+ ///
+ public static ArgTypeInfo ForPrimitive(CorElementType corType, int pointerSize)
+ {
+ return new ArgTypeInfo
+ {
+ CorElementType = corType,
+ Size = GetElemSize(corType, default, pointerSize),
+ IsValueType = false,
+ RequiresAlign8 = false,
+ IsHomogeneousAggregate = false,
+ HomogeneousAggregateElementSize = 0,
+ RuntimeTypeHandle = default,
+ };
+ }
+}
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/CallingConvention/CallingConventionInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/CallingConvention/CallingConventionInfo.cs
new file mode 100644
index 00000000000000..ee89c064c24a27
--- /dev/null
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/CallingConvention/CallingConventionInfo.cs
@@ -0,0 +1,274 @@
+// 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 managed calling convention utilized by the JIT).
+// Ported from crossgen2's TransitionBlock.cs.
+
+using System;
+
+namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.CallingConvention;
+
+///
+/// Architecture-specific calling convention constants and methods for
+/// mapping method arguments to register and stack locations.
+///
+///
+/// Layout-dependent values (offsets, sizes) come from the data descriptor
+/// (). ABI-invariant values (register counts,
+/// alignment rules) are hardcoded per architecture since they are defined by
+/// the hardware/OS ABI and never change.
+///
+internal sealed class CallingConventionInfo
+{
+ // Layout values from the data descriptor
+ private readonly uint _sizeOfTransitionBlock;
+ private readonly uint _argumentRegistersOffset;
+ private readonly uint _firstGCRefMapSlot;
+ private readonly uint _offsetOfArgs;
+ private readonly int _offsetOfFloatArgumentRegisters;
+
+ // ABI invariants
+ public int PointerSize { get; }
+ public int NumArgumentRegisters { get; }
+ public int NumFloatArgumentRegisters { get; }
+ public int FloatRegisterSize { get; }
+ public int EnregisteredParamTypeMaxSize { get; }
+ public int StackSlotSize { get; }
+ public bool IsRetBuffPassedAsFirstArg { get; }
+ public bool IsX64UnixABI { get; }
+ public bool IsAppleArm64ABI { get; }
+ public bool IsArmhfABI { get; }
+
+ public RuntimeInfoArchitecture Architecture { get; }
+
+ // Convenience accessors
+ public uint SizeOfTransitionBlock => _sizeOfTransitionBlock;
+ public uint ArgumentRegistersOffset => _argumentRegistersOffset;
+ public uint FirstGCRefMapSlot => _firstGCRefMapSlot;
+ public uint OffsetOfArgs => _offsetOfArgs;
+ public int OffsetOfFloatArgumentRegisters => _offsetOfFloatArgumentRegisters;
+ public int SizeOfArgumentRegisters => NumArgumentRegisters * PointerSize;
+
+ public const int InvalidOffset = -1;
+ public const int StructInRegsOffset = -2;
+
+ ///
+ /// Creates a for the given target, reading
+ /// layout data from the data descriptor and filling in ABI constants from
+ /// the target's architecture and OS.
+ ///
+ public CallingConventionInfo(Target target)
+ {
+ IRuntimeInfo runtimeInfo = target.Contracts.RuntimeInfo;
+ Architecture = runtimeInfo.GetTargetArchitecture();
+ RuntimeInfoOperatingSystem os = runtimeInfo.GetTargetOperatingSystem();
+ PointerSize = target.PointerSize;
+
+ // Read layout values from the data descriptor
+ Target.TypeInfo tbType = target.GetTypeInfo(DataType.TransitionBlock);
+ _sizeOfTransitionBlock = (uint)tbType.Size!;
+ _argumentRegistersOffset = (uint)tbType.Fields["ArgumentRegistersOffset"].Offset;
+ _firstGCRefMapSlot = (uint)tbType.Fields["FirstGCRefMapSlot"].Offset;
+ _offsetOfArgs = (uint)tbType.Fields["OffsetOfArgs"].Offset;
+ _offsetOfFloatArgumentRegisters = tbType.Fields["OffsetOfFloatArgumentRegisters"].Offset;
+
+ // Fill in ABI invariants based on architecture
+ switch (Architecture)
+ {
+ case RuntimeInfoArchitecture.X86:
+ NumArgumentRegisters = 2; // ECX, EDX
+ NumFloatArgumentRegisters = 0;
+ FloatRegisterSize = 0;
+ EnregisteredParamTypeMaxSize = 0;
+ StackSlotSize = 4;
+ IsRetBuffPassedAsFirstArg = true;
+ break;
+
+ case RuntimeInfoArchitecture.X64:
+ if (os is RuntimeInfoOperatingSystem.Unix or RuntimeInfoOperatingSystem.Apple)
+ {
+ // Unix/Apple AMD64 ABI (SysV)
+ NumArgumentRegisters = 6; // RDI, RSI, RDX, RCX, R8, R9
+ NumFloatArgumentRegisters = 8; // XMM0-XMM7
+ FloatRegisterSize = 16; // M128A
+ EnregisteredParamTypeMaxSize = 16;
+ IsX64UnixABI = true;
+ }
+ else
+ {
+ // Windows AMD64 ABI
+ NumArgumentRegisters = 4; // RCX, RDX, R8, R9
+ NumFloatArgumentRegisters = 0; // Shared with GP regs on Windows
+ FloatRegisterSize = 16;
+ EnregisteredParamTypeMaxSize = 8;
+ }
+ StackSlotSize = 8;
+ IsRetBuffPassedAsFirstArg = true;
+ break;
+
+ case RuntimeInfoArchitecture.Arm:
+ NumArgumentRegisters = 4; // R0-R3
+ NumFloatArgumentRegisters = 16; // 16 single-precision slots (D0-D7 / S0-S15)
+ FloatRegisterSize = 4;
+ EnregisteredParamTypeMaxSize = 0;
+ StackSlotSize = 4;
+ IsRetBuffPassedAsFirstArg = true;
+ IsArmhfABI = true; // TODO: detect armel
+ break;
+
+ case RuntimeInfoArchitecture.Arm64:
+ NumArgumentRegisters = 8; // X0-X7
+ NumFloatArgumentRegisters = 8; // V0-V7
+ FloatRegisterSize = 16;
+ EnregisteredParamTypeMaxSize = 16;
+ StackSlotSize = 8;
+ IsRetBuffPassedAsFirstArg = false; // ARM64 uses X8 for retbuf
+ IsAppleArm64ABI = os == RuntimeInfoOperatingSystem.Apple;
+ break;
+
+ case RuntimeInfoArchitecture.LoongArch64:
+ NumArgumentRegisters = 8; // A0-A7
+ NumFloatArgumentRegisters = 8; // FA0-FA7
+ FloatRegisterSize = 8;
+ EnregisteredParamTypeMaxSize = 16;
+ StackSlotSize = 8;
+ IsRetBuffPassedAsFirstArg = true;
+ break;
+
+ case RuntimeInfoArchitecture.RiscV64:
+ NumArgumentRegisters = 8; // a0-a7
+ NumFloatArgumentRegisters = 8; // fa0-fa7
+ FloatRegisterSize = 8;
+ EnregisteredParamTypeMaxSize = 16;
+ StackSlotSize = 8;
+ IsRetBuffPassedAsFirstArg = true;
+ break;
+
+ default:
+ throw new NotSupportedException($"Architecture {Architecture} is not supported for calling convention analysis.");
+ }
+ }
+
+ // ---- Derived methods ----
+
+ ///
+ /// Returns the byte offset of the 'this' pointer in the transition block.
+ ///
+ public int ThisOffset
+ {
+ get
+ {
+ if (Architecture == RuntimeInfoArchitecture.X86)
+ {
+ // ECX offset within ArgumentRegisters: ECX is at offset PointerSize (after EDX at 0)
+ return (int)ArgumentRegistersOffset + PointerSize;
+ }
+ return (int)ArgumentRegistersOffset;
+ }
+ }
+
+ ///
+ /// Rounds up a parameter size to the stack slot size for the platform.
+ ///
+ public int StackElemSize(int parmSize, bool isValueType = false, bool isFloatHfa = false)
+ {
+ if (IsAppleArm64ABI)
+ {
+ if (!isValueType)
+ {
+ // Primitives use their natural size, no padding
+ return parmSize;
+ }
+ if (isFloatHfa)
+ {
+ // Float HFA: 4-byte alignment
+ return parmSize;
+ }
+ }
+ return AlignUp(parmSize, StackSlotSize);
+ }
+
+ ///
+ /// Maps a GCRefMap position index to the byte offset in the transition block.
+ ///
+ public int OffsetFromGCRefMapPos(int pos)
+ {
+ if (Architecture == RuntimeInfoArchitecture.X86)
+ {
+ if (pos < NumArgumentRegisters)
+ {
+ return (int)ArgumentRegistersOffset + SizeOfArgumentRegisters - (pos + 1) * PointerSize;
+ }
+ return (int)OffsetOfArgs + (pos - NumArgumentRegisters) * PointerSize;
+ }
+ return (int)FirstGCRefMapSlot + pos * PointerSize;
+ }
+
+ ///
+ /// Returns true if the argument at the given offset is a float register.
+ /// Float register offsets are negative.
+ ///
+ public static bool IsFloatArgumentRegisterOffset(int offset) => offset < 0;
+
+ ///
+ /// Returns true if the argument at the given offset is in a general-purpose register.
+ ///
+ public bool IsArgumentRegisterOffset(int offset)
+ {
+ return offset >= (int)ArgumentRegistersOffset
+ && offset < (int)ArgumentRegistersOffset + SizeOfArgumentRegisters;
+ }
+
+ ///
+ /// Returns true if the argument at the given offset is on the stack.
+ ///
+ public bool IsStackArgumentOffset(int offset)
+ {
+ return offset >= (int)ArgumentRegistersOffset + SizeOfArgumentRegisters;
+ }
+
+ ///
+ /// Checks if a value type of the given size should be passed by reference
+ /// (applies to x64 and ARM64).
+ ///
+ public bool IsArgPassedByRef(int size)
+ {
+ if (EnregisteredParamTypeMaxSize == 0)
+ return false;
+
+ if (Architecture == RuntimeInfoArchitecture.X64)
+ {
+ // On x64, also check power-of-2 rule
+ return size > EnregisteredParamTypeMaxSize || (size & (size - 1)) != 0;
+ }
+
+ return size > EnregisteredParamTypeMaxSize;
+ }
+
+ ///
+ /// Returns the byte offset of the return buffer argument.
+ ///
+ public int GetRetBuffArgOffset(bool hasThis)
+ {
+ if (Architecture == RuntimeInfoArchitecture.X86)
+ {
+ // x86: retbuf goes in EDX if hasThis (this in ECX), else ECX
+ return hasThis
+ ? (int)ArgumentRegistersOffset // EDX offset = 0
+ : (int)ArgumentRegistersOffset + PointerSize; // ECX offset
+ }
+ if (Architecture == RuntimeInfoArchitecture.Arm64)
+ {
+ // ARM64: retbuf is in X8, which is at FirstGCRefMapSlot
+ return (int)FirstGCRefMapSlot;
+ }
+ // Default: retbuf is after 'this' in the argument registers
+ return (int)ArgumentRegistersOffset + (hasThis ? PointerSize : 0);
+ }
+
+ internal static int AlignUp(int value, int alignment)
+ {
+ return (value + (alignment - 1)) & ~(alignment - 1);
+ }
+}
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs
index 0152698f6bc65c..88bfcaece90d1d 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs
@@ -4,6 +4,7 @@
using System;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
+using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.CallingConvention;
using Microsoft.Diagnostics.DataContractReader.Data;
namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;
@@ -50,11 +51,15 @@ internal enum FrameType
private readonly Target target;
private readonly TargetPointer terminator;
private TargetPointer currentFramePointer;
+ private CallingConventionInfo? _callingConventionInfo;
internal Data.Frame CurrentFrame => target.ProcessedData.GetOrAdd(currentFramePointer);
public TargetPointer CurrentFrameAddress => currentFramePointer;
+ private CallingConventionInfo GetCallingConventionInfo()
+ => _callingConventionInfo ??= new CallingConventionInfo(target);
+
public FrameIterator(Target target, ThreadData threadData)
{
this.target = target;
@@ -413,8 +418,8 @@ private void PromoteCallerStackUsingGCRefMap(
{
int pos = decoder.CurrentPos;
GCRefMapToken token = decoder.ReadToken();
- uint offset = OffsetFromGCRefMapPos(pos);
- TargetPointer slotAddress = new(transitionBlock.Value + offset);
+ int offset = GetCallingConventionInfo().OffsetFromGCRefMapPos(pos);
+ TargetPointer slotAddress = new(transitionBlock.Value + (ulong)offset);
switch (token)
{
@@ -618,7 +623,6 @@ private void PromoteCallerStack(
MethodDescHandle mdh = rts.GetMethodDescHandle(methodDescPtr);
bool hasThis = methodSig.Header.IsInstance;
- bool hasRetBuf = methodSig.ReturnType is GcTypeKind.Other;
bool requiresInstArg = false;
bool isAsync = false;
bool isValueTypeThis = false;
@@ -635,54 +639,73 @@ private void PromoteCallerStack(
{
}
- PromoteCallerStackHelper(transitionBlock, methodSig, hasThis, hasRetBuf,
+ PromoteCallerStackHelper(transitionBlock, methodSig, hasThis,
requiresInstArg, isAsync, isValueTypeThis, scanContext);
}
///
/// Core logic for promoting caller stack GC references.
- /// Matches native TransitionFrame::PromoteCallerStackHelper (frames.cpp:1560).
+ /// Uses to correctly map arguments to their
+ /// register/stack locations per the target's calling convention.
+ /// Matches native TransitionFrame::PromoteCallerStackHelper (frames.cpp:1546).
///
private void PromoteCallerStackHelper(
TargetPointer transitionBlock,
MethodSignature methodSig,
bool hasThis,
- bool hasRetBuf,
bool requiresInstArg,
bool isAsync,
bool isValueTypeThis,
GcScanContext scanContext)
{
- int numRegistersUsed = 0;
- if (hasThis)
- numRegistersUsed++;
- if (hasRetBuf)
- numRegistersUsed++;
- if (requiresInstArg)
- numRegistersUsed++;
- if (isAsync)
- numRegistersUsed++;
-
- bool isArm64 = IsTargetArm64();
- if (isArm64)
- numRegistersUsed++;
-
- if (hasThis)
+ CallingConventionInfo ccInfo;
+ try
+ {
+ ccInfo = GetCallingConventionInfo();
+ }
+ catch
{
- int thisPos = isArm64 ? 1 : 0;
- uint thisOffset = OffsetFromGCRefMapPos(thisPos);
- TargetPointer thisAddr = new(transitionBlock.Value + thisOffset);
+ return;
+ }
+
+ // Build ArgTypeInfo array from decoded signature
+ ArgTypeInfo[] paramTypes = new ArgTypeInfo[methodSig.ParameterTypes.Length];
+ for (int i = 0; i < paramTypes.Length; i++)
+ {
+ paramTypes[i] = GcTypeKindToArgTypeInfo(methodSig.ParameterTypes[i], ccInfo.PointerSize);
+ }
+ ArgTypeInfo returnTypeInfo = GcTypeKindToArgTypeInfo(methodSig.ReturnType, ccInfo.PointerSize);
+
+ ArgIteratorData argData = new(hasThis, methodSig.Header.CallingConvention is SignatureCallingConvention.VarArgs, paramTypes, returnTypeInfo);
+ CallingConvention.ArgIterator argit = new(ccInfo, argData, hasParamType: requiresInstArg, hasAsyncContinuation: isAsync, forcedByRefParams: System.Array.Empty());
+
+ // Promote 'this' for non-static methods
+ if (argit.HasThis)
+ {
+ int thisOffset = argit.GetThisOffset();
+ TargetPointer thisAddr = new(transitionBlock.Value + (ulong)thisOffset);
GcScanFlags thisFlags = isValueTypeThis ? GcScanFlags.GC_CALL_INTERIOR : GcScanFlags.None;
scanContext.GCReportCallback(thisAddr, thisFlags);
}
- // TODO(stackref): Promote async continuation pointer at its specific offset
+ // Promote async continuation
+ if (argit.HasAsyncContinuation)
+ {
+ int asyncOffset = argit.GetAsyncContinuationArgOffset();
+ TargetPointer asyncAddr = new(transitionBlock.Value + (ulong)asyncOffset);
+ scanContext.GCReportCallback(asyncAddr, GcScanFlags.None);
+ }
- int pos = numRegistersUsed;
- foreach (GcTypeKind kind in methodSig.ParameterTypes)
+ // Walk each argument using ArgIterator for correct offsets
+ int argIndex = 0;
+ int argOffset;
+ while ((argOffset = argit.GetNextOffset()) != CallingConventionInfo.InvalidOffset)
{
- uint offset = OffsetFromGCRefMapPos(pos);
- TargetPointer slotAddress = new(transitionBlock.Value + offset);
+ if (argIndex >= methodSig.ParameterTypes.Length)
+ break;
+
+ GcTypeKind kind = methodSig.ParameterTypes[argIndex];
+ TargetPointer slotAddress = new(transitionBlock.Value + (ulong)argOffset);
switch (kind)
{
@@ -693,15 +716,41 @@ private void PromoteCallerStackHelper(
scanContext.GCReportCallback(slotAddress, GcScanFlags.GC_CALL_INTERIOR);
break;
case GcTypeKind.Other:
- // TODO(stackref): Value type GCDesc scanning
+ // Value type: if passed by ref, report as interior pointer
+ if (argit.IsArgPassedByRef())
+ {
+ scanContext.GCReportCallback(slotAddress, GcScanFlags.GC_CALL_INTERIOR);
+ }
+ // TODO: For value types passed by value, enumerate fields for embedded GC refs
break;
case GcTypeKind.None:
break;
}
- pos++;
+ argIndex++;
}
}
+ ///
+ /// Converts a to a minimal for
+ /// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
+ ///
+ private static ArgTypeInfo GcTypeKindToArgTypeInfo(GcTypeKind kind, int pointerSize)
+ {
+ return kind switch
+ {
+ GcTypeKind.None => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
+ GcTypeKind.Ref => ArgTypeInfo.ForPrimitive(CorElementType.Class, pointerSize),
+ GcTypeKind.Interior => ArgTypeInfo.ForPrimitive(CorElementType.Byref, pointerSize),
+ GcTypeKind.Other => new ArgTypeInfo
+ {
+ CorElementType = CorElementType.ValueType,
+ Size = pointerSize, // Conservative: assume pointer-sized for now
+ IsValueType = true,
+ },
+ _ => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
+ };
+ }
+
private ReadOnlySpan GetMethodSignatureBytes(TargetPointer methodDescPtr, out MetadataReader? metadataReader)
{
metadataReader = null;
@@ -733,13 +782,6 @@ private ReadOnlySpan GetMethodSignatureBytes(TargetPointer methodDescPtr,
return blobReader.ReadBytes(blobReader.Length);
}
- private uint OffsetFromGCRefMapPos(int pos)
- {
- Target.TypeInfo tbType = target.GetTypeInfo(DataType.TransitionBlock);
- uint firstSlotOffset = (uint)tbType.Fields[nameof(Data.TransitionBlock.FirstGCRefMapSlot)].Offset;
- return firstSlotOffset + (uint)(pos * target.PointerSize);
- }
-
private bool IsTargetArm64()
{
return target.Contracts.RuntimeInfo.GetTargetArchitecture() is RuntimeInfoArchitecture.Arm64;
diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs
index 97443b11f43645..e727286cc7de06 100644
--- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs
+++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs
@@ -23,6 +23,7 @@ public TransitionBlock(Target target, TargetPointer address)
// in the data descriptor. They represent computed layout positions, not actual memory reads.
FirstGCRefMapSlot = (uint)type.Fields[nameof(FirstGCRefMapSlot)].Offset;
ArgumentRegistersOffset = (uint)type.Fields[nameof(ArgumentRegistersOffset)].Offset;
+ OffsetOfFloatArgumentRegisters = type.Fields[nameof(OffsetOfFloatArgumentRegisters)].Offset;
}
public TargetPointer ReturnAddress { get; }
@@ -42,4 +43,11 @@ public TransitionBlock(Target target, TargetPointer address)
/// Offset to the argument registers area, relative to the TransitionBlock pointer.
///
public uint ArgumentRegistersOffset { get; }
+
+ ///
+ /// Offset to the float argument registers area, relative to the TransitionBlock pointer.
+ /// Negative on most platforms (float regs are stored before the TransitionBlock).
+ /// Zero on platforms without float argument registers (x86, Windows x64).
+ ///
+ public int OffsetOfFloatArgumentRegisters { get; }
}