File: Contracts\StackWalk\GC\GCRefMapDecoder.cs
Web Access
Project: src\src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Contracts\Microsoft.Diagnostics.DataContractReader.Contracts.csproj (Microsoft.Diagnostics.DataContractReader.Contracts)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;

/// <summary>
/// Token values from CORCOMPILE_GCREFMAP_TOKENS (corcompile.h).
/// These indicate the type of GC reference at each transition block slot.
/// </summary>
internal enum GCRefMapToken
{
    Skip = 0,
    Ref = 1,
    Interior = 2,
    MethodParam = 3,
    TypeParam = 4,
    VASigCookie = 5,
}

/// <summary>
/// Managed port of the native GCRefMapDecoder (gcrefmap.h).
///
/// A GCRefMap is a compact bitstream that describes which transition block slots
/// contain GC references for a given call site (e.g., in ReadyToRun stubs).
/// It is used by ExternalMethodFrame and StubDispatchFrame to report GC roots
/// without needing the full MethodDesc/signature decoding path.
///
/// Encoding: each slot is encoded as a variable-length integer using 3 bits per
/// token (see <see cref="GCRefMapToken"/>), with a high-bit continuation flag.
/// A "skip" token advances the slot position without reporting. The stream ends
/// when all slots have been consumed (indicated by a zero byte after the last token).
///
/// The native implementation lives in coreclr/inc/gcrefmap.h (GCRefMapDecoder class).
/// </summary>
internal ref struct GCRefMapDecoder
{
    private readonly Target _target;
    private TargetPointer _currentByte;
    private int _pendingByte;
    private int _pos;

    public GCRefMapDecoder(Target target, TargetPointer blob)
    {
        _target = target;
        _currentByte = blob;
        _pendingByte = 0x80; // Forces first byte read
        _pos = 0;
    }

    public readonly bool AtEnd => _pendingByte == 0;

    public readonly int CurrentPos => _pos;

    private int GetBit()
    {
        int x = _pendingByte;
        if ((x & 0x80) != 0)
        {
            x = _target.Read<byte>(_currentByte);
            _currentByte = new TargetPointer(_currentByte.Value + 1);
            x |= (x & 0x80) << 7;
        }
        _pendingByte = x >> 1;
        return x & 1;
    }

    private int GetTwoBit()
    {
        int result = GetBit();
        result |= GetBit() << 1;
        return result;
    }

    private int GetInt()
    {
        int result = 0;
        int bit = 0;
        do
        {
            result |= GetBit() << (bit++);
            result |= GetBit() << (bit++);
            result |= GetBit() << (bit++);
        }
        while (GetBit() != 0);
        return result;
    }

    /// <summary>
    /// x86 only: Read the stack pop count from the stream.
    /// </summary>
    public uint ReadStackPop()
    {
        int x = GetTwoBit();
        if (x == 3)
            x = GetInt() + 3;
        return (uint)x;
    }

    /// <summary>
    /// Read the next GC reference token from the stream.
    /// Advances CurrentPos as appropriate.
    /// </summary>
    public GCRefMapToken ReadToken()
    {
        int val = GetTwoBit();
        if (val == 3)
        {
            int ext = GetInt();
            if ((ext & 1) == 0)
            {
                _pos += (ext >> 1) + 4;
                return GCRefMapToken.Skip;
            }
            else
            {
                _pos++;
                return (GCRefMapToken)((ext >> 1) + 3);
            }
        }
        _pos++;
        return (GCRefMapToken)val;
    }
}