// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using ILCompiler.Reflection.ReadyToRun;
namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers;
internal class GcInfoDecoder<TTraits> : IGCInfoDecoder where TTraits : IGCInfoTraits
{
private enum DecodePoints
{
CodeLength,
ReturnKind,
VarArg,
PrologLength,
GSCookie,
PSPSym,
GenericInstContext,
EditAndContinue,
ReversePInvoke,
InterruptibleRanges,
SlotTable,
Complete,
}
[Flags]
internal enum GcInfoHeaderFlags : uint
{
GC_INFO_IS_VARARG = 0x1,
GC_INFO_HAS_SECURITY_OBJECT = 0x2,
GC_INFO_HAS_GS_COOKIE = 0x4,
GC_INFO_HAS_PSP_SYM = 0x8,
GC_INFO_HAS_GENERICS_INST_CONTEXT_MASK = 0x30,
GC_INFO_HAS_GENERICS_INST_CONTEXT_NONE = 0x00,
GC_INFO_HAS_GENERICS_INST_CONTEXT_MT = 0x10,
GC_INFO_HAS_GENERICS_INST_CONTEXT_MD = 0x20,
GC_INFO_HAS_GENERICS_INST_CONTEXT_THIS = 0x30,
GC_INFO_HAS_STACK_BASE_REGISTER = 0x40,
GC_INFO_WANTS_REPORT_ONLY_LEAF = 0x80, // GC_INFO_HAS_TAILCALLS = 0x80, for ARM and ARM64
GC_INFO_HAS_EDIT_AND_CONTINUE_INFO = 0x100,
GC_INFO_REVERSE_PINVOKE_FRAME = 0x200,
GC_INFO_FLAGS_BIT_SIZE_VERSION_1 = 9,
GC_INFO_FLAGS_BIT_SIZE = 10,
};
[Flags]
internal enum GcSlotFlags : uint
{
GC_SLOT_BASE = 0x0,
GC_SLOT_INTERIOR = 0x1,
GC_SLOT_PINNED = 0x2,
GC_SLOT_UNTRACKED = 0x4,
}
[Flags]
internal enum GcStackSlotBase : uint
{
GC_CALLER_SP_REL = 0x0,
GC_SP_REL = 0x1,
GC_FRAMEREG_REL = 0x2,
GC_SPBASE_FIRST = GC_CALLER_SP_REL,
GC_SPBASE_LAST = GC_FRAMEREG_REL,
}
public readonly record struct GcSlotDesc
{
/* Register Slot */
public readonly uint RegisterNumber;
/* Stack Slot */
public readonly int SpOffset;
public readonly GcStackSlotBase Base;
/* Shared fields */
public readonly GcSlotFlags Flags;
public readonly bool IsRegister;
private GcSlotDesc(uint registerNumber, int spOffset, GcStackSlotBase slotBase, GcSlotFlags flags, bool isRegister = false)
{
RegisterNumber = registerNumber;
SpOffset = spOffset;
Base = slotBase;
Flags = flags;
IsRegister = isRegister;
}
public static GcSlotDesc CreateRegisterSlot(uint registerNumber, GcSlotFlags flags)
=> new GcSlotDesc(registerNumber, 0, 0, flags, isRegister: true);
public static GcSlotDesc CreateStackSlot(int spOffset, GcStackSlotBase slotBase, GcSlotFlags flags)
=> new GcSlotDesc(0, spOffset, slotBase, flags, isRegister: false);
}
private readonly Target _target;
private readonly TargetPointer _pGcInfo;
private readonly uint _gcVersion;
private readonly NativeReader _reader;
private readonly RuntimeInfoArchitecture _arch;
private readonly bool PartiallyInterruptibleGCSupported = true;
/* Decode State */
private int _bitOffset;
private IEnumerator<DecodePoints> _decodePoints;
private List<DecodePoints> _completedDecodePoints = [];
/* Header Fields */
private bool _slimHeader;
private GcInfoHeaderFlags _headerFlags;
private uint _stackBaseRegister;
private uint _codeLength;
private uint _validRangeStart;
private uint _validRangeEnd;
private int _gsCookieStackSlot;
private int _pspSymStackSlot;
private int _genericsInstContextStackSlot;
private uint _sizeOfEnCPreservedArea;
private uint _sizeOfEnCFixedStackFrame;
private int _reversePInvokeFrameStackSlot;
private uint _fixedStackParameterScratchArea;
/* Fields */
private uint _numSafePoints;
private uint _numInterruptibleRanges;
private List<InterruptibleRange> _interruptibleRanges = [];
private List<uint> _safePoints = [];
/* Slot Table Fields */
private uint _numRegisters;
private uint _numUntrackedSlots;
private uint _numSlots;
private List<GcSlotDesc> _slots = [];
private int _liveStateBitOffset;
public GcInfoDecoder(Target target, TargetPointer gcInfoAddress, uint gcVersion)
{
_target = target;
_pGcInfo = gcInfoAddress;
_gcVersion = gcVersion;
TargetStream targetStream = new TargetStream(_target, _pGcInfo, /*arbitrary*/ 10000);
_reader = new NativeReader(targetStream, _target.IsLittleEndian);
_arch = target.Contracts.RuntimeInfo.GetTargetArchitecture();
_decodePoints = Decode().GetEnumerator();
}
#region Decoding Methods
private IEnumerable<DecodePoints> Decode()
{
IEnumerable<DecodePoints> headerDecodePoints = DecodeHeader();
foreach (DecodePoints dp in headerDecodePoints)
yield return dp;
IEnumerable<DecodePoints> bodyDecodePoints = DecodeBody();
foreach (DecodePoints dp in bodyDecodePoints)
yield return dp;
yield return DecodePoints.Complete;
}
private IEnumerable<DecodePoints> DecodeBody()
{
IEnumerable<DecodePoints> safePoints = DecodeSafePoints();
foreach (DecodePoints dp in safePoints)
yield return dp;
IEnumerable<DecodePoints> interruptibleRanges = DecodeInterruptibleRanges();
foreach (DecodePoints dp in interruptibleRanges)
yield return dp;
yield return DecodePoints.InterruptibleRanges;
IEnumerable<DecodePoints> slotTable = DecodeSlotTable();
foreach (DecodePoints dp in slotTable)
yield return dp;
// Save the bit offset for EnumerateLiveSlots — the live state data follows immediately
_liveStateBitOffset = _bitOffset;
yield return DecodePoints.SlotTable;
}
private IEnumerable<DecodePoints> DecodeSlotTable()
{
if (_reader.ReadBits(1, ref _bitOffset) != 0)
{
_numRegisters = _reader.DecodeVarLengthUnsigned(TTraits.NUM_REGISTERS_ENCBASE, ref _bitOffset);
}
uint numStackSlots = 0;
if (_reader.ReadBits(1, ref _bitOffset) != 0)
{
numStackSlots = _reader.DecodeVarLengthUnsigned(TTraits.NUM_STACK_SLOTS_ENCBASE, ref _bitOffset);
_numUntrackedSlots = _reader.DecodeVarLengthUnsigned(TTraits.NUM_UNTRACKED_SLOTS_ENCBASE, ref _bitOffset);
}
_numSlots = _numRegisters + numStackSlots + _numUntrackedSlots;
_slots = new List<GcSlotDesc>((int)_numSlots);
// Decode register slots
if (_numRegisters > 0)
{
uint regNum = _reader.DecodeVarLengthUnsigned(TTraits.REGISTER_ENCBASE, ref _bitOffset);
GcSlotFlags flags = (GcSlotFlags)_reader.ReadBits(2, ref _bitOffset);
_slots.Add(GcSlotDesc.CreateRegisterSlot(regNum, flags));
for (int i = 1; i < _numRegisters; i++)
{
if (flags != 0)
{
regNum = _reader.DecodeVarLengthUnsigned(TTraits.REGISTER_ENCBASE, ref _bitOffset);
flags = (GcSlotFlags)_reader.ReadBits(2, ref _bitOffset);
}
else
{
regNum += _reader.DecodeVarLengthUnsigned(TTraits.REGISTER_DELTA_ENCBASE, ref _bitOffset) + 1;
}
_slots.Add(GcSlotDesc.CreateRegisterSlot(regNum, flags));
}
}
// Decode stack slots
if (numStackSlots > 0)
{
GcStackSlotBase spBase = (GcStackSlotBase)_reader.ReadBits(2, ref _bitOffset);
int normSpOffset = _reader.DecodeVarLengthSigned(TTraits.STACK_SLOT_ENCBASE, ref _bitOffset);
int spOffset = TTraits.DenormalizeStackSlot(normSpOffset);
GcSlotFlags flags = (GcSlotFlags)_reader.ReadBits(2, ref _bitOffset);
_slots.Add(GcSlotDesc.CreateStackSlot(spOffset, spBase, flags));
for (int i = 1; i < numStackSlots; i++)
{
spBase = (GcStackSlotBase)_reader.ReadBits(2, ref _bitOffset);
if (flags != 0)
{
// When previous flags were non-zero, the next slot uses a FULL offset (not delta)
normSpOffset = _reader.DecodeVarLengthSigned(TTraits.STACK_SLOT_ENCBASE, ref _bitOffset);
spOffset = TTraits.DenormalizeStackSlot(normSpOffset);
flags = (GcSlotFlags)_reader.ReadBits(2, ref _bitOffset);
}
else
{
int normSpOffsetDelta = (int)_reader.DecodeVarLengthUnsigned(TTraits.STACK_SLOT_DELTA_ENCBASE, ref _bitOffset);
normSpOffset += normSpOffsetDelta;
spOffset = TTraits.DenormalizeStackSlot(normSpOffset);
}
_slots.Add(GcSlotDesc.CreateStackSlot(spOffset, spBase, flags));
}
}
// Decode untracked slots
if (_numUntrackedSlots > 0)
{
GcStackSlotBase spBase = (GcStackSlotBase)_reader.ReadBits(2, ref _bitOffset);
int normSpOffset = _reader.DecodeVarLengthSigned(TTraits.STACK_SLOT_ENCBASE, ref _bitOffset);
int spOffset = TTraits.DenormalizeStackSlot(normSpOffset);
GcSlotFlags flags = (GcSlotFlags)_reader.ReadBits(2, ref _bitOffset);
_slots.Add(GcSlotDesc.CreateStackSlot(spOffset, spBase, flags));
for (int i = 1; i < _numUntrackedSlots; i++)
{
spBase = (GcStackSlotBase)_reader.ReadBits(2, ref _bitOffset);
if (flags != 0)
{
// When previous flags were non-zero, the next slot uses a FULL offset (not delta)
normSpOffset = _reader.DecodeVarLengthSigned(TTraits.STACK_SLOT_ENCBASE, ref _bitOffset);
spOffset = TTraits.DenormalizeStackSlot(normSpOffset);
flags = (GcSlotFlags)_reader.ReadBits(2, ref _bitOffset);
}
else
{
int normSpOffsetDelta = (int)_reader.DecodeVarLengthUnsigned(TTraits.STACK_SLOT_DELTA_ENCBASE, ref _bitOffset);
normSpOffset += normSpOffsetDelta;
spOffset = TTraits.DenormalizeStackSlot(normSpOffset);
}
_slots.Add(GcSlotDesc.CreateStackSlot(spOffset, spBase, flags));
}
}
yield break;
}
private IEnumerable<DecodePoints> DecodeInterruptibleRanges()
{
if (_numInterruptibleRanges == 0)
yield break;
uint prevEndOffset = 0;
uint lastInterruptibleRangeStopOffsetNormalized = 0;
_interruptibleRanges = new List<InterruptibleRange>((int)_numInterruptibleRanges);
for (uint i = 0; i < _numInterruptibleRanges; i++)
{
uint normStartDelta = _reader.DecodeVarLengthUnsigned(TTraits.INTERRUPTIBLE_RANGE_DELTA1_ENCBASE, ref _bitOffset);
uint normStopDelta = _reader.DecodeVarLengthUnsigned(TTraits.INTERRUPTIBLE_RANGE_DELTA2_ENCBASE, ref _bitOffset) + 1;
uint rangeStartOffsetNormalized = lastInterruptibleRangeStopOffsetNormalized + normStartDelta;
uint rangeStopOffsetNormalized = rangeStartOffsetNormalized + normStopDelta;
uint rangeStartOffset = TTraits.DenormalizeCodeOffset(rangeStartOffsetNormalized);
uint rangeStopOffset = TTraits.DenormalizeCodeOffset(rangeStopOffsetNormalized);
Debug.Assert(rangeStartOffset < rangeStopOffset);
Debug.Assert(rangeStartOffset >= prevEndOffset);
lastInterruptibleRangeStopOffsetNormalized = rangeStopOffsetNormalized;
prevEndOffset = rangeStopOffset;
_interruptibleRanges.Add(new(rangeStartOffset, rangeStopOffset));
}
yield break;
}
private IEnumerable<DecodePoints> DecodeSafePoints()
{
uint numBitsPerOffset = CeilOfLog2(TTraits.NormalizeCodeOffset(_codeLength));
_safePoints = new List<uint>((int)_numSafePoints);
for (uint i = 0; i < _numSafePoints; i++)
{
uint offset = TTraits.DenormalizeCodeOffset((uint)_reader.ReadBits((int)numBitsPerOffset, ref _bitOffset));
_safePoints.Add(offset);
}
yield break;
}
private IEnumerable<DecodePoints> DecodeHeader()
{
_slimHeader = _reader.ReadBits(1, ref _bitOffset) == 0;
if (!_slimHeader)
{
return DecodeFatHeader();
}
else
{
return DecodeSlimHeader();
}
}
private IEnumerable<DecodePoints> DecodeSlimHeader()
{
if (_reader.ReadBits(1, ref _bitOffset) != 0)
{
_headerFlags = GcInfoHeaderFlags.GC_INFO_HAS_STACK_BASE_REGISTER;
_stackBaseRegister = TTraits.DenormalizeStackBaseRegister(0);
}
else
{
_headerFlags = default;
_stackBaseRegister = TTraits.NO_STACK_BASE_REGISTER;
}
yield return DecodePoints.ReturnKind;
yield return DecodePoints.VarArg;
_codeLength = TTraits.DenormalizeCodeLength(_reader.DecodeVarLengthUnsigned(TTraits.CODE_LENGTH_ENCBASE, ref _bitOffset));
// predecoding the rest of slim header does not require any reading.
_validRangeStart = 0;
_validRangeEnd = 0;
_gsCookieStackSlot = TTraits.NO_GS_COOKIE;
_pspSymStackSlot = TTraits.NO_PSP_SYM;
_genericsInstContextStackSlot = TTraits.NO_GENERICS_INST_CONTEXT;
_sizeOfEnCPreservedArea = TTraits.NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA;
_sizeOfEnCFixedStackFrame = 0;
_reversePInvokeFrameStackSlot = TTraits.NO_REVERSE_PINVOKE_FRAME;
if (TTraits.HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA)
_fixedStackParameterScratchArea = 0;
yield return DecodePoints.CodeLength;
yield return DecodePoints.PrologLength;
yield return DecodePoints.GSCookie;
yield return DecodePoints.PSPSym;
yield return DecodePoints.GenericInstContext;
yield return DecodePoints.EditAndContinue;
yield return DecodePoints.ReversePInvoke;
if (PartiallyInterruptibleGCSupported)
{
_numSafePoints = _reader.DecodeVarLengthUnsigned(TTraits.NUM_SAFE_POINTS_ENCBASE, ref _bitOffset);
}
_numInterruptibleRanges = 0;
}
private IEnumerable<DecodePoints> DecodeFatHeader()
{
_headerFlags = (GcInfoHeaderFlags)_reader.ReadBits((int)GcInfoHeaderFlags.GC_INFO_FLAGS_BIT_SIZE, ref _bitOffset);
yield return DecodePoints.ReturnKind;
yield return DecodePoints.VarArg;
_codeLength = TTraits.DenormalizeCodeLength(_reader.DecodeVarLengthUnsigned(TTraits.CODE_LENGTH_ENCBASE, ref _bitOffset));
yield return DecodePoints.CodeLength;
if (_headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_HAS_GS_COOKIE))
{
// Note that normalization as a code offset can be different than
// normalization as code length
uint normCodeLength = TTraits.NormalizeCodeLength(_codeLength);
// Decode prolog/epilog information
uint normPrologSize = _reader.DecodeVarLengthUnsigned(TTraits.NORM_PROLOG_SIZE_ENCBASE, ref _bitOffset) + 1;
uint normEpilogSize = _reader.DecodeVarLengthUnsigned(TTraits.NORM_EPILOG_SIZE_ENCBASE, ref _bitOffset);
_validRangeStart = TTraits.DenormalizeCodeOffset(normPrologSize);
_validRangeEnd = TTraits.DenormalizeCodeOffset(normCodeLength - normEpilogSize);
Debug.Assert(_validRangeStart < _validRangeEnd);
}
else if ((_headerFlags & GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_MASK) != GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_NONE)
{
// Decode prolog information
uint normPrologSize = _reader.DecodeVarLengthUnsigned(TTraits.NORM_PROLOG_SIZE_ENCBASE, ref _bitOffset) + 1;
_validRangeStart = TTraits.DenormalizeCodeOffset(normPrologSize);
// satisfy asserts that assume m_GSCookieValidRangeStart != 0 ==> m_GSCookieValidRangeStart < m_GSCookieValidRangeEnd
_validRangeEnd = _validRangeStart + 1;
}
else
{
_validRangeStart = 0;
_validRangeEnd = 0;
}
yield return DecodePoints.PrologLength;
_gsCookieStackSlot = _headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_HAS_GS_COOKIE) ?
TTraits.DenormalizeStackSlot(_reader.DecodeVarLengthSigned(TTraits.GS_COOKIE_STACK_SLOT_ENCBASE, ref _bitOffset)) :
TTraits.NO_GS_COOKIE;
yield return DecodePoints.GSCookie;
_pspSymStackSlot = TTraits.NO_PSP_SYM;
yield return DecodePoints.PSPSym;
_genericsInstContextStackSlot = (_headerFlags & GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_MASK) != GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_NONE ?
TTraits.DenormalizeStackSlot(_reader.DecodeVarLengthSigned(TTraits.GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE, ref _bitOffset)) :
TTraits.NO_GENERICS_INST_CONTEXT;
yield return DecodePoints.GenericInstContext;
_stackBaseRegister = _headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_HAS_STACK_BASE_REGISTER) ?
TTraits.DenormalizeStackBaseRegister(_reader.DecodeVarLengthUnsigned(TTraits.STACK_BASE_REGISTER_ENCBASE, ref _bitOffset)) :
TTraits.NO_STACK_BASE_REGISTER;
if (_headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_HAS_EDIT_AND_CONTINUE_INFO))
{
_sizeOfEnCPreservedArea = _reader.DecodeVarLengthUnsigned(TTraits.SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE, ref _bitOffset);
// Arm64 has an additional field for EnC fixed stack frame size
// This is controlled by target architecture rather than on the traits because it impacts the interpreter
if (_arch == RuntimeInfoArchitecture.Arm64)
{
_sizeOfEnCFixedStackFrame = _reader.DecodeVarLengthUnsigned(TTraits.SIZE_OF_EDIT_AND_CONTINUE_FIXED_STACK_FRAME_ENCBASE, ref _bitOffset);
}
}
else
{
_sizeOfEnCPreservedArea = TTraits.NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA;
_sizeOfEnCFixedStackFrame = 0;
}
yield return DecodePoints.EditAndContinue;
_reversePInvokeFrameStackSlot = _headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_REVERSE_PINVOKE_FRAME) ?
TTraits.DenormalizeStackSlot(_reader.DecodeVarLengthSigned(TTraits.REVERSE_PINVOKE_FRAME_ENCBASE, ref _bitOffset)) :
TTraits.NO_REVERSE_PINVOKE_FRAME;
yield return DecodePoints.ReversePInvoke;
if (TTraits.HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA)
{
_fixedStackParameterScratchArea =
TTraits.DenormalizeSizeOfStackArea(_reader.DecodeVarLengthUnsigned(TTraits.SIZE_OF_STACK_AREA_ENCBASE, ref _bitOffset));
}
if (PartiallyInterruptibleGCSupported)
{
_numSafePoints = _reader.DecodeVarLengthUnsigned(TTraits.NUM_SAFE_POINTS_ENCBASE, ref _bitOffset);
}
_numInterruptibleRanges = _reader.DecodeVarLengthUnsigned(TTraits.NUM_INTERRUPTIBLE_RANGES_ENCBASE, ref _bitOffset);
}
private void EnsureDecodedTo(DecodePoints point)
{
while (!_completedDecodePoints.Contains(point))
{
if (!_decodePoints.MoveNext())
return; // nothing more to decode
_completedDecodePoints.Add(_decodePoints.Current);
}
}
#endregion
#region Access Methods
public GCInfoHeader GetHeader()
{
EnsureDecodedTo(DecodePoints.ReversePInvoke);
GcInfoHeaderFlags genericsInstContextFlags = _headerFlags & GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_MASK;
SpecialSlot? gsCookie = _gsCookieStackSlot != TTraits.NO_GS_COOKIE ? new SpecialSlot(_gsCookieStackSlot) : null;
SpecialSlot? pspSym = _pspSymStackSlot != TTraits.NO_PSP_SYM ? new SpecialSlot(_pspSymStackSlot) : null;
SpecialSlot? genericsInstContext = _genericsInstContextStackSlot != TTraits.NO_GENERICS_INST_CONTEXT ? new SpecialSlot(_genericsInstContextStackSlot) : null;
GenericsContextKind genericsContextKind = genericsInstContextFlags switch
{
GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_MD => GenericsContextKind.MethodDesc,
GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_MT => GenericsContextKind.MethodHandle,
GcInfoHeaderFlags.GC_INFO_HAS_GENERICS_INST_CONTEXT_THIS => GenericsContextKind.This,
_ => GenericsContextKind.None,
};
bool flag80Set = _headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_WANTS_REPORT_ONLY_LEAF);
bool wantsReportOnlyLeaf = _arch == RuntimeInfoArchitecture.X64 && _gcVersion < 4 ? flag80Set : true;
bool hasTailCalls = _arch is RuntimeInfoArchitecture.Arm or RuntimeInfoArchitecture.Arm64 or RuntimeInfoArchitecture.LoongArch64 or RuntimeInfoArchitecture.RiscV64
&& flag80Set;
return new GCInfoHeader(
Version: _gcVersion,
CodeSize: _codeLength,
PrologSize: _validRangeStart,
StackBaseRegister: _stackBaseRegister,
SizeOfStackParameterArea: _fixedStackParameterScratchArea,
IsVarArg: _headerFlags.HasFlag(GcInfoHeaderFlags.GC_INFO_IS_VARARG),
WantsReportOnlyLeaf: wantsReportOnlyLeaf,
HasTailCalls: hasTailCalls,
GSCookie: gsCookie,
GSCookieValidRangeStart: gsCookie.HasValue ? _validRangeStart : 0,
GSCookieValidRangeEnd: gsCookie.HasValue ? _validRangeEnd : 0,
PSPSym: pspSym,
GenericsInstContext: genericsInstContext,
GenericsInstContextKind: genericsContextKind);
}
public uint GetCodeLength()
{
EnsureDecodedTo(DecodePoints.CodeLength);
return _codeLength;
}
public bool TryGetGenericContextStorage(GenericContextLoc contextKind, uint instructionOffset, out GenericContextStorage storage)
{
if (TTraits.IsInterpreter && contextKind == GenericContextLoc.ThisPtr)
{
storage = new GenericContextStorage(GenericContextStorageKind.InterpreterArgumentRelative, registerNumber: 0, offset: 0);
return true;
}
EnsureDecodedTo(DecodePoints.ReversePInvoke);
if (_genericsInstContextStackSlot == TTraits.NO_GENERICS_INST_CONTEXT)
{
storage = default;
return false;
}
if (TTraits.IsInterpreter)
{
storage = new GenericContextStorage(
GenericContextStorageKind.InterpreterArgumentRelative,
registerNumber: 0,
_genericsInstContextStackSlot);
}
else if (_stackBaseRegister != TTraits.NO_STACK_BASE_REGISTER)
{
storage = new GenericContextStorage(
GenericContextStorageKind.RegisterRelative,
_stackBaseRegister,
_genericsInstContextStackSlot);
}
else
{
storage = new GenericContextStorage(
GenericContextStorageKind.StackPointerRelative,
registerNumber: 0,
_genericsInstContextStackSlot);
}
return true;
}
// The GcInfoDecoder format (all non-x86 architectures) has no ambient SP
public TargetPointer GetAmbientSP(uint codeOffset, TargetPointer fp, TargetPointer sp) => TargetPointer.Null;
public IReadOnlyList<InterruptibleRange> GetInterruptibleRanges()
{
EnsureDecodedTo(DecodePoints.InterruptibleRanges);
return _interruptibleRanges;
}
public bool IsGcSafe(uint instructionOffset)
{
// Mirrors native EECodeManager::IsGcSafe for the GcInfoDecoder path: the offset is
// GC-safe if it is either fully interruptible or a partially-interruptible safe point.
EnsureDecodedTo(DecodePoints.InterruptibleRanges);
// Interruptible: the offset lies within [start, stop) of any range.
// Mirrors native GcInfoDecoder::EnumerateInterruptibleRanges/SetIsInterruptibleCB.
foreach (InterruptibleRange range in _interruptibleRanges)
{
if (instructionOffset >= range.StartOffset && instructionOffset < range.EndOffset)
return true;
}
// Safe point: the offset matches an entry in the safe point table.
// Mirrors native GcInfoDecoder::IsSafePoint.
if (_numSafePoints == 0)
return false;
return FindSafePoint(instructionOffset) != _numSafePoints;
}
public IReadOnlyList<uint> GetSafePoints()
{
EnsureDecodedTo(DecodePoints.InterruptibleRanges);
return _safePoints;
}
public IReadOnlyList<GCSlotLifetime> GetSlotLifetimes()
{
EnsureDecodedTo(DecodePoints.SlotTable);
List<GCSlotLifetime> lifetimes = [];
uint numTracked = NumTrackedSlots;
// Untracked slots are always live for the entire method
for (uint slotIndex = numTracked; slotIndex < _numSlots; slotIndex++)
{
GcSlotDesc slot = _slots[(int)slotIndex];
uint gcFlags = (uint)slot.Flags & ((uint)GcSlotFlags.GC_SLOT_INTERIOR | (uint)GcSlotFlags.GC_SLOT_PINNED | (uint)GcSlotFlags.GC_SLOT_UNTRACKED);
lifetimes.Add(new GCSlotLifetime(slot.IsRegister, slot.RegisterNumber, slot.SpOffset, (uint)slot.Base, gcFlags, 0, _codeLength));
}
if (numTracked == 0)
return lifetimes;
// For methods with only safe points (not fully interruptible),
// decode tracked slot lifetimes from the per-safe-point bitmaps.
if (_numInterruptibleRanges == 0)
{
if (_numSafePoints == 0)
return lifetimes;
int safePointBitPos = _liveStateBitOffset;
// Read indirect live state table header
uint numBitsPerOffset = 0;
if (_reader.ReadBits(1, ref safePointBitPos) != 0)
{
numBitsPerOffset = (uint)_reader.DecodeVarLengthUnsigned(TTraits.POINTER_SIZE_ENCBASE, ref safePointBitPos) + 1;
}
// For each safe point, read the live slot bitmap.
// Safe points are independent snapshots. We emit lifetimes spanning
// from the first safe point where a slot is live to the last consecutive
// safe point where it remains live.
IReadOnlyList<uint> safePoints = GetSafePoints();
uint[] liveStart = new uint[numTracked];
bool[] prevLive = new bool[numTracked];
for (uint sp = 0; sp < _numSafePoints; sp++)
{
bool[] curLive = new bool[numTracked];
int spBitOffset;
if (numBitsPerOffset != 0)
{
// Indirect table: read offset pointer for this safe point
int offsetTablePos = safePointBitPos;
int spOffsetBit = offsetTablePos + (int)(sp * numBitsPerOffset);
uint liveStatesOffset = (uint)_reader.ReadBits((int)numBitsPerOffset, ref spOffsetBit);
int liveStatesStart = (int)(((uint)offsetTablePos + _numSafePoints * numBitsPerOffset + 7) & (~7u));
spBitOffset = (int)(liveStatesStart + liveStatesOffset);
if (_reader.ReadBits(1, ref spBitOffset) != 0)
{
// RLE encoded
bool fSkip = _reader.ReadBits(1, ref spBitOffset) == 0;
bool fReport = true;
uint readSlots = (uint)_reader.DecodeVarLengthUnsigned(
fSkip ? TTraits.LIVESTATE_RLE_SKIP_ENCBASE : TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref spBitOffset);
fSkip = !fSkip;
while (readSlots < numTracked)
{
uint cnt = (uint)_reader.DecodeVarLengthUnsigned(
fSkip ? TTraits.LIVESTATE_RLE_SKIP_ENCBASE : TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref spBitOffset) + 1;
if (fReport)
{
for (uint si = readSlots; si < readSlots + cnt; si++)
curLive[si] = true;
}
readSlots += cnt;
fSkip = !fSkip;
fReport = !fReport;
}
}
else
{
for (uint si = 0; si < numTracked; si++)
curLive[si] = _reader.ReadBits(1, ref spBitOffset) != 0;
}
}
else
{
// Direct bitmap: numTracked bits per safe point
spBitOffset = safePointBitPos + (int)(sp * numTracked);
for (uint si = 0; si < numTracked; si++)
curLive[si] = _reader.ReadBits(1, ref spBitOffset) != 0;
}
uint spOffset = safePoints[(int)sp];
for (uint si = 0; si < numTracked; si++)
{
if (curLive[si] && !prevLive[si])
{
// Slot became live at this safe point
liveStart[si] = spOffset;
}
else if (!curLive[si] && prevLive[si])
{
// Slot is dead -- emit lifetime ending after previous safe point
EmitSlotLifetime(si, liveStart[si], safePoints[(int)(sp - 1)] + 1, lifetimes);
}
}
Array.Copy(curLive, prevLive, numTracked);
}
// Close any slots still live at the last safe point
uint lastSpOffset = safePoints[(int)(_numSafePoints - 1)];
for (uint si = 0; si < numTracked; si++)
{
if (prevLive[si])
EmitSlotLifetime(si, liveStart[si], lastSpOffset + 1, lifetimes);
}
return lifetimes;
}
int bitOffset = _liveStateBitOffset;
// Skip indirect live state table header and safe point data
if (_numSafePoints > 0 && _reader.ReadBits(1, ref bitOffset) != 0)
{
_reader.DecodeVarLengthUnsigned(TTraits.POINTER_SIZE_ENCBASE, ref bitOffset);
}
bitOffset += (int)(_numSafePoints * numTracked);
// Compute total interruptible length
uint numInterruptibleLength = 0;
for (int i = 0; i < _interruptibleRanges.Count; i++)
{
uint normStart = TTraits.NormalizeCodeOffset(_interruptibleRanges[i].StartOffset);
uint normStop = TTraits.NormalizeCodeOffset(_interruptibleRanges[i].EndOffset);
numInterruptibleLength += normStop - normStart;
}
uint numChunks = (numInterruptibleLength + TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK - 1) / TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK;
uint numBitsPerPointer = (uint)_reader.DecodeVarLengthUnsigned(TTraits.POINTER_SIZE_ENCBASE, ref bitOffset);
if (numBitsPerPointer == 0)
return lifetimes;
int pointerTablePos = bitOffset;
int chunksStartPos = (int)(((uint)pointerTablePos + numChunks * numBitsPerPointer + 7) & (~7u));
// Track per-slot live state across chunks: slotIndex -> beginCodeOffset
Dictionary<uint, uint> activeSlots = [];
for (uint chunk = 0; chunk < numChunks; chunk++)
{
bitOffset = pointerTablePos + (int)(chunk * numBitsPerPointer);
uint chunkPointer = (uint)_reader.ReadBits((int)numBitsPerPointer, ref bitOffset);
if (chunkPointer == 0)
continue;
int chunkPos = (int)(chunksStartPos + chunkPointer - 1);
bitOffset = chunkPos;
// Read couldBeLive bitvector
List<uint> couldBeLiveSlots = ReadCouldBeLiveSlots(ref bitOffset, numTracked);
int finalStateBitOffset = bitOffset;
int transitionBitOffset = bitOffset + couldBeLiveSlots.Count;
uint chunkStartNormOffset = chunk * TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK;
for (int i = 0; i < couldBeLiveSlots.Count; i++)
{
uint slotIndex = couldBeLiveSlots[i];
uint finalState = (uint)_reader.ReadBits(1, ref finalStateBitOffset);
// Collect transitions within this chunk
List<uint> transitions = [];
while (_reader.ReadBits(1, ref transitionBitOffset) != 0)
{
uint transOffset = (uint)_reader.ReadBits(TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK_LOG2, ref transitionBitOffset);
transitions.Add(transOffset);
}
// Initial state = finalState XOR (transitions.Count % 2)
uint currentState = finalState ^ (uint)(transitions.Count % 2);
// Walk transitions and track lifetime boundaries
uint prevNormOffset = chunkStartNormOffset;
foreach (uint transOffset in transitions)
{
uint absNormOffset = chunkStartNormOffset + transOffset;
if (currentState != 0 && !activeSlots.ContainsKey(slotIndex))
{
// Slot is live and we haven't recorded its start yet
activeSlots[slotIndex] = DenormInterruptibleOffset(prevNormOffset);
}
else if (currentState == 0 && activeSlots.TryGetValue(slotIndex, out uint beginOffset))
{
// Slot just died. It was live up to prevNormOffset and is dead in
// [prevNormOffset, absNormOffset), so the death boundary is prevNormOffset.
EmitSlotLifetime(slotIndex, beginOffset, DenormInterruptibleOffset(prevNormOffset), lifetimes);
activeSlots.Remove(slotIndex);
}
currentState ^= 1;
prevNormOffset = absNormOffset;
}
// End of chunk state
if (currentState != 0 && !activeSlots.ContainsKey(slotIndex))
activeSlots[slotIndex] = DenormInterruptibleOffset(prevNormOffset);
else if (currentState == 0 && activeSlots.TryGetValue(slotIndex, out uint begin))
{
EmitSlotLifetime(slotIndex, begin, DenormInterruptibleOffset(prevNormOffset), lifetimes);
activeSlots.Remove(slotIndex);
}
}
}
// Close any remaining open lifetimes
foreach ((uint slotIndex, uint beginOffset) in activeSlots)
EmitSlotLifetime(slotIndex, beginOffset, _codeLength, lifetimes);
return lifetimes;
}
private List<uint> ReadCouldBeLiveSlots(ref int bitOffset, uint numTracked)
{
List<uint> slots = [];
if (_reader.ReadBits(1, ref bitOffset) != 0)
{
// RLE encoded
bool fSkip = _reader.ReadBits(1, ref bitOffset) == 0;
bool fReport = true;
uint readSlots = (uint)_reader.DecodeVarLengthUnsigned(
fSkip ? TTraits.LIVESTATE_RLE_SKIP_ENCBASE : TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset);
fSkip = !fSkip;
while (readSlots < numTracked)
{
uint cnt = (uint)_reader.DecodeVarLengthUnsigned(
fSkip ? TTraits.LIVESTATE_RLE_SKIP_ENCBASE : TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset) + 1;
if (fReport)
{
for (uint j = 0; j < cnt; j++)
slots.Add(readSlots + j);
}
readSlots += cnt;
fSkip = !fSkip;
fReport = !fReport;
}
}
else
{
for (uint i = 0; i < numTracked; i++)
{
if (_reader.ReadBits(1, ref bitOffset) != 0)
slots.Add(i);
}
}
return slots;
}
private uint DenormInterruptibleOffset(uint normOffset)
{
uint accumulated = 0;
for (int i = 0; i < _interruptibleRanges.Count; i++)
{
uint normStart = TTraits.NormalizeCodeOffset(_interruptibleRanges[i].StartOffset);
uint normStop = TTraits.NormalizeCodeOffset(_interruptibleRanges[i].EndOffset);
uint rangeLen = normStop - normStart;
if (normOffset < accumulated + rangeLen)
{
uint delta = normOffset - accumulated;
return TTraits.DenormalizeCodeOffset(normStart + delta);
}
accumulated += rangeLen;
}
return _codeLength;
}
private void EmitSlotLifetime(uint slotIndex, uint beginOffset, uint endOffset, List<GCSlotLifetime> lifetimes)
{
GcSlotDesc slot = _slots[(int)slotIndex];
uint gcFlags = (uint)slot.Flags & ((uint)GcSlotFlags.GC_SLOT_INTERIOR | (uint)GcSlotFlags.GC_SLOT_PINNED);
lifetimes.Add(new GCSlotLifetime(slot.IsRegister, slot.RegisterNumber, slot.SpOffset, (uint)slot.Base, gcFlags, beginOffset, endOffset));
}
public uint NumTrackedSlots => _numSlots - _numUntrackedSlots;
IReadOnlyList<LiveSlot> IGCInfoDecoder.EnumerateLiveSlots(
uint instructionOffset,
GcSlotEnumerationOptions options)
{
List<LiveSlot> result = [];
EnumerateLiveSlots(instructionOffset, options,
(uint slotIndex, GcSlotDesc slot, uint gcFlags) =>
{
result.Add(new LiveSlot(slot.IsRegister, slot.RegisterNumber, slot.SpOffset, (uint)slot.Base, gcFlags));
});
return result;
}
/// <summary>
/// Enumerates all GC slots that are live at the given instruction offset, invoking the callback for each.
/// This is the managed equivalent of the native GcInfoDecoder::EnumerateLiveSlots.
/// </summary>
private bool EnumerateLiveSlots(
uint instructionOffset,
GcSlotEnumerationOptions options,
Action<uint, GcSlotDesc, uint> reportSlot)
{
EnsureDecodedTo(DecodePoints.SlotTable);
bool executionAborted = options.IsExecutionAborted;
bool reportScratchSlots = options.IsActiveFrame;
bool reportFpBasedSlotsOnly = options.ReportFPBasedSlotsOnly;
// WantsReportOnlyLeaf is always true for non-legacy formats
if (options.IsParentOfFuncletStackFrame)
return true;
uint numTracked = NumTrackedSlots;
if (numTracked == 0)
return ReportUntrackedAndSucceed();
uint normBreakOffset = TTraits.NormalizeCodeOffset(instructionOffset);
// Find safe point index
uint safePointIndex = _numSafePoints;
if (_numSafePoints > 0)
{
safePointIndex = FindSafePoint(instructionOffset);
}
// Use a local bit offset starting from the saved live state position
// so we don't disturb the decoder's main _bitOffset.
int bitOffset = _liveStateBitOffset;
if (PartiallyInterruptibleGCSupported)
{
uint pseudoBreakOffset = 0;
uint numInterruptibleLength = 0;
if (safePointIndex < _numSafePoints && !executionAborted)
{
// We have a safe point match — skip interruptible range computation
}
else
{
// Compute pseudoBreakOffset from interruptible ranges
int countIntersections = 0;
for (int i = 0; i < _interruptibleRanges.Count; i++)
{
uint normStart = TTraits.NormalizeCodeOffset(_interruptibleRanges[i].StartOffset);
uint normStop = TTraits.NormalizeCodeOffset(_interruptibleRanges[i].EndOffset);
if (normBreakOffset >= normStart && normBreakOffset < normStop)
{
Debug.Assert(pseudoBreakOffset == 0);
countIntersections++;
pseudoBreakOffset = numInterruptibleLength + normBreakOffset - normStart;
}
numInterruptibleLength += normStop - normStart;
}
Debug.Assert(countIntersections <= 1);
if (countIntersections == 0 && executionAborted)
return true; // Native: goto ExitSuccess (skip all reporting including untracked)
}
// Read the indirect live state table header (if present)
uint numBitsPerOffset = 0;
if (_numSafePoints > 0 && _reader.ReadBits(1, ref bitOffset) != 0)
{
numBitsPerOffset = (uint)_reader.DecodeVarLengthUnsigned(TTraits.POINTER_SIZE_ENCBASE, ref bitOffset) + 1;
}
// ---- Try partially interruptible first ----
if (!executionAborted && safePointIndex != _numSafePoints)
{
if (numBitsPerOffset != 0)
{
int offsetTablePos = bitOffset;
bitOffset += (int)(safePointIndex * numBitsPerOffset);
uint liveStatesOffset = (uint)_reader.ReadBits((int)numBitsPerOffset, ref bitOffset);
int liveStatesStart = (int)(((uint)offsetTablePos + _numSafePoints * numBitsPerOffset + 7) & (~7u));
bitOffset = (int)(liveStatesStart + liveStatesOffset);
if (_reader.ReadBits(1, ref bitOffset) != 0)
{
// RLE encoded
bool fSkip = _reader.ReadBits(1, ref bitOffset) == 0;
bool fReport = true;
uint readSlots = (uint)_reader.DecodeVarLengthUnsigned(
fSkip ? TTraits.LIVESTATE_RLE_SKIP_ENCBASE : TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset);
fSkip = !fSkip;
while (readSlots < numTracked)
{
uint cnt = (uint)_reader.DecodeVarLengthUnsigned(
fSkip ? TTraits.LIVESTATE_RLE_SKIP_ENCBASE : TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset) + 1;
if (fReport)
{
for (uint slotIndex = readSlots; slotIndex < readSlots + cnt; slotIndex++)
ReportSlot(slotIndex, reportScratchSlots, reportFpBasedSlotsOnly, reportSlot);
}
readSlots += cnt;
fSkip = !fSkip;
fReport = !fReport;
}
Debug.Assert(readSlots == numTracked);
return ReportUntrackedAndSucceed();
}
// Normal 1-bit-per-slot encoding follows
}
else
{
bitOffset += (int)(safePointIndex * numTracked);
}
for (uint slotIndex = 0; slotIndex < numTracked; slotIndex++)
{
if (_reader.ReadBits(1, ref bitOffset) != 0)
ReportSlot(slotIndex, reportScratchSlots, reportFpBasedSlotsOnly, reportSlot);
}
return ReportUntrackedAndSucceed();
}
else
{
// Skip over safe point live state data.
// NOTE: The native code always skips numSafePoints * numTracked here,
// even when numBitsPerOffset != 0 (indirect table). This is technically
// wrong for the indirect case, but the encoder never produces both
// indirect safe points AND interruptible ranges, so it's unreachable.
// Match the native behavior for consistency.
bitOffset += (int)(_numSafePoints * numTracked);
if (_numInterruptibleRanges == 0)
return ReportUntrackedAndSucceed();
}
// ---- Fully-interruptible path ----
Debug.Assert(_numInterruptibleRanges > 0);
Debug.Assert(numInterruptibleLength > 0);
uint numChunks = (numInterruptibleLength + TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK - 1) / TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK;
uint breakChunk = pseudoBreakOffset / TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK;
Debug.Assert(breakChunk < numChunks);
uint numBitsPerPointer = (uint)_reader.DecodeVarLengthUnsigned(TTraits.POINTER_SIZE_ENCBASE, ref bitOffset);
if (numBitsPerPointer == 0)
return ReportUntrackedAndSucceed();
int pointerTablePos = bitOffset;
// Find the chunk pointer (walk backwards if current chunk has no data)
uint chunkPointer;
uint chunk = breakChunk;
for (; ; )
{
bitOffset = pointerTablePos + (int)(chunk * numBitsPerPointer);
chunkPointer = (uint)_reader.ReadBits((int)numBitsPerPointer, ref bitOffset);
if (chunkPointer != 0)
break;
if (chunk-- == 0)
return ReportUntrackedAndSucceed();
}
int chunksStartPos = (int)(((uint)pointerTablePos + numChunks * numBitsPerPointer + 7) & (~7u));
int chunkPos = (int)(chunksStartPos + chunkPointer - 1);
bitOffset = chunkPos;
// Read "couldBeLive" bitvector — first pass to count
int couldBeLiveBitOffset = bitOffset;
uint numCouldBeLiveSlots = 0;
if (_reader.ReadBits(1, ref bitOffset) != 0)
{
// RLE encoded
bool fSkipCBL = _reader.ReadBits(1, ref bitOffset) == 0;
bool fReportCBL = true;
uint readSlots = (uint)_reader.DecodeVarLengthUnsigned(
fSkipCBL ? TTraits.LIVESTATE_RLE_SKIP_ENCBASE : TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset);
fSkipCBL = !fSkipCBL;
while (readSlots < numTracked)
{
uint cnt = (uint)_reader.DecodeVarLengthUnsigned(
fSkipCBL ? TTraits.LIVESTATE_RLE_SKIP_ENCBASE : TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref bitOffset) + 1;
if (fReportCBL)
numCouldBeLiveSlots += cnt;
readSlots += cnt;
fSkipCBL = !fSkipCBL;
fReportCBL = !fReportCBL;
}
Debug.Assert(readSlots == numTracked);
}
else
{
for (uint i = 0; i < numTracked; i++)
{
if (_reader.ReadBits(1, ref bitOffset) != 0)
numCouldBeLiveSlots++;
}
}
Debug.Assert(numCouldBeLiveSlots > 0);
// "finalState" bits follow couldBeLive
int finalStateBitOffset = bitOffset;
// Transition data follows final state bits
int transitionBitOffset = bitOffset + (int)numCouldBeLiveSlots;
// Re-read couldBeLive to iterate slot indices (second pass)
int cblOffset = couldBeLiveBitOffset;
bool cblSimple = _reader.ReadBits(1, ref cblOffset) == 0;
bool cblSkipFirst = false;
uint cblCnt = 0;
uint slotIdx = 0;
if (!cblSimple)
{
cblSkipFirst = _reader.ReadBits(1, ref cblOffset) == 0;
slotIdx = unchecked((uint)-1);
}
for (uint i = 0; i < numCouldBeLiveSlots; i++)
{
if (cblSimple)
{
while (_reader.ReadBits(1, ref cblOffset) == 0)
slotIdx++;
}
else if (cblCnt > 0)
{
cblCnt--;
}
else if (cblSkipFirst)
{
uint tmp = (uint)_reader.DecodeVarLengthUnsigned(TTraits.LIVESTATE_RLE_SKIP_ENCBASE, ref cblOffset) + 1;
slotIdx += tmp;
cblCnt = (uint)_reader.DecodeVarLengthUnsigned(TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref cblOffset);
}
else
{
uint tmp = (uint)_reader.DecodeVarLengthUnsigned(TTraits.LIVESTATE_RLE_RUN_ENCBASE, ref cblOffset) + 1;
slotIdx += tmp;
cblCnt = (uint)_reader.DecodeVarLengthUnsigned(TTraits.LIVESTATE_RLE_SKIP_ENCBASE, ref cblOffset);
}
uint isLive = (uint)_reader.ReadBits(1, ref finalStateBitOffset);
if (chunk == breakChunk)
{
uint normBreakOffsetDelta = pseudoBreakOffset % TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK;
for (; ; )
{
if (_reader.ReadBits(1, ref transitionBitOffset) == 0)
break;
uint transitionOffset = (uint)_reader.ReadBits(TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK_LOG2, ref transitionBitOffset);
Debug.Assert(transitionOffset > 0 && transitionOffset < TTraits.NUM_NORM_CODE_OFFSETS_PER_CHUNK);
if (transitionOffset > normBreakOffsetDelta)
isLive ^= 1;
}
}
if (isLive != 0)
ReportSlot(slotIdx, reportScratchSlots, reportFpBasedSlotsOnly, reportSlot);
slotIdx++;
}
}
return ReportUntrackedAndSucceed();
bool ReportUntrackedAndSucceed()
{
if (_numUntrackedSlots > 0 && !options.IsParentOfFuncletStackFrame && !options.SuppressUntrackedSlots)
{
// Native passes reportScratchSlots=true for untracked slots (see native
// ReportUntrackedSlots: "Report everything (although there should *never*
// be any scratch slots that are untracked)"). In practice the JIT can
// produce untracked scratch register slots for interior pointers, so they
// must be reported regardless of whether this is a leaf frame.
for (uint slotIndex = numTracked; slotIndex < _numSlots; slotIndex++)
ReportSlot(slotIndex, reportScratchSlots: true, reportFpBasedSlotsOnly, reportSlot);
}
return true;
}
}
private void ReportSlot(uint slotIndex, bool reportScratchSlots, bool reportFpBasedSlotsOnly, Action<uint, GcSlotDesc, uint> reportSlot)
{
Debug.Assert(slotIndex < _slots.Count);
GcSlotDesc slot = _slots[(int)slotIndex];
uint gcFlags = (uint)slot.Flags & ((uint)GcSlotFlags.GC_SLOT_INTERIOR | (uint)GcSlotFlags.GC_SLOT_PINNED);
if (slot.IsRegister)
{
// Skip scratch registers for non-leaf frames
if (!reportScratchSlots && TTraits.IsScratchRegister(slot.RegisterNumber))
return;
// FP-based-only mode skips all register slots
if (reportFpBasedSlotsOnly)
return;
}
else
{
// FP-based-only mode: only report GC_FRAMEREG_REL slots
if (reportFpBasedSlotsOnly && slot.Base != GcStackSlotBase.GC_FRAMEREG_REL)
return;
}
reportSlot(slotIndex, slot, gcFlags);
}
private uint FindSafePoint(uint codeOffset)
{
EnsureDecodedTo(DecodePoints.InterruptibleRanges);
// TODO(stackref): The native FindSafePoint uses binary search (NarrowSafePointSearch)
// when numSafePoints > 32. This is a performance optimization only — no correctness impact.
// Linear scan through safe point offsets from the saved position
for (int i = 0; i < _safePoints.Count; i++)
{
if (_safePoints[i] == codeOffset)
return (uint)i;
if (_safePoints[i] > codeOffset)
break;
}
return _numSafePoints; // not found
}
#endregion
#region Helper Methods
private static uint CeilOfLog2(ulong value)
{
Debug.Assert(value > 0);
value = (value << 1) - 1;
return (uint)(63 - BitOperations.LeadingZeroCount(value));
}
#endregion
}