File: Contracts\ExecutionManager\Helpers\NibbleMapConstantLookup.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.

using System.Numerics;
using System.Diagnostics;
using System;

using static Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers.NibbleMapHelpers;

namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers;

// CoreCLR nibblemap with O(1) lookup time.
//
// Implementation very similar to NibbleMapLinearLookup, but with the addition of writing relative pointers
// into the nibblemap whenever a code block completely covers a DWORD. This allows for O(1) lookup
// with the cost of O(n) write time.
//
// Pointers are encoded using the top 28 bits of the DWORD normally, the bottom 4 bits of the pointer
// are reduced to 2 bits due to 4 byte code offset and encoded in bits 28 .. 31 of the DWORD with values
// 9-12. This is used to differentiate nibble values and pointer DWORDs.
//
// To read the nibblemap, we check if the DWORD is a pointer. If so, then we know the value currentPC is
// part of a managed code block beginning at the mapBase + decoded pointer. If the DWORD is empty
// (no pointer or previous nibbles), then we only need to read the previous DWORD. If that DWORD is empty,
// then we must not be in a managed function. Otherwise the write algorithm would have written a relative
// pointer in the DWORD.
//
// Note, a currentPC pointing to bytes outside a function have undefined lookup behavior.
// In this implementation we may "extend" the lookup period of a function several hundred bytes
// if there is not another function following it.

internal sealed class NibbleMapConstantLookup : INibbleMap
{
    private readonly Target _target;

    private NibbleMapConstantLookup(Target target)
    {
        _target = target;
    }

    internal static bool IsPointer(MapUnit mapUnit)
    {
        return (mapUnit.Value & MapUnit.NibbleMask) > 8;
    }

    internal static TargetPointer DecodePointer(TargetPointer baseAddress, MapUnit mapUnit)
    {
        uint nibble = mapUnit.Value & MapUnit.NibbleMask;
        uint relativePointer = (mapUnit.Value & ~MapUnit.NibbleMask) + ((nibble - 9) << 2);
        return baseAddress + relativePointer;
    }

    internal static uint EncodePointer(uint relativeAddress)
    {
        uint nibble = ((relativeAddress & MapUnit.NibbleMask) >>> 2) + 9;
        return (relativeAddress & ~MapUnit.NibbleMask) + nibble;
    }

    internal TargetPointer FindMethodCode(TargetPointer mapBase, TargetPointer mapStart, TargetCodePointer currentPC)
    {
        TargetNUInt relativeAddress = new TargetNUInt(currentPC.Value - mapBase.Value);
        DecomposeAddress(relativeAddress, out MapKey mapIdx, out Nibble bucketByteIndex);

        MapUnit t = mapIdx.ReadMapUnit(_target, mapStart);

        // if pointer, return value
        if (IsPointer(t))
        {
            return DecodePointer(mapBase, t);
        }

        // shift the nibble we want to the least significant position
        t = t.FocusOnIndexedNibble(mapIdx);

        // if the nibble is non-zero, we have found the start of a method,
        // but we need to check that the start is before the current address, not after
        if (!t.Nibble.IsEmpty && t.Nibble.Value <= bucketByteIndex.Value)
        {
            return GetAbsoluteAddress(mapBase, mapIdx, t.Nibble);
        }

        // search backwards through the current map unit
        // we processed the lsb nibble, move to the next one
        t = t.ShiftNextNibble;

        // if there's any nibble set in the current unit, find it
        if (!t.IsEmpty)
        {
            mapIdx = mapIdx.Prev;
            while (t.Nibble.IsEmpty)
            {
                t = t.ShiftNextNibble;
                mapIdx = mapIdx.Prev;
            }
            return GetAbsoluteAddress(mapBase, mapIdx, t.Nibble);
        }

        // We finished the current map unit, we want to move to the previous one.
        // But if we were in the first map unit, we can stop
        if (mapIdx.InFirstMapUnit)
        {
            return TargetPointer.Null;
        }

        // We're now done with the current map unit.
        // Align the map index to the current map unit, then move back one nibble into the previous map unit
        mapIdx = mapIdx.AlignDownToMapUnit();
        mapIdx = mapIdx.Prev;

        // read the map unit containing mapIdx and skip over it if it is all zeros
        t = mapIdx.ReadMapUnit(_target, mapStart);

        // if t is empty, then currentPC can not be in a function
        if (t.IsEmpty)
        {
            return TargetPointer.Null;
        }

        // if t is not empty, it must contain a pointer or a nibble
        if (IsPointer(t))
        {
            return DecodePointer(mapBase, t);
        }

        // move to the correct nibble in the map unit
        while (!mapIdx.IsZero && t.Nibble.IsEmpty)
        {
            t = t.ShiftNextNibble;
            mapIdx = mapIdx.Prev;
        }

        return GetAbsoluteAddress(mapBase, mapIdx, t.Nibble);
    }

    public static INibbleMap Create(Target target)
    {
        return new NibbleMapConstantLookup(target);
    }

    public TargetPointer FindMethodCode(Data.CodeHeapListNode heapListNode, TargetCodePointer jittedCodeAddress)
    {
        if (jittedCodeAddress < heapListNode.StartAddress || jittedCodeAddress > heapListNode.EndAddress)
        {
            return TargetPointer.Null;
        }

        return FindMethodCode(heapListNode.MapBase, heapListNode.HeaderMap, jittedCodeAddress);
    }
}