File: Contracts\ExecutionManager\Helpers\RangeSectionMap.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;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers;


/// <summary>
/// Algorithm for lookups in a multi-level map that covers a large address space
/// </summary>
/// <remarks><![CDATA[
/// The range section map logically partitions the entire 32-bit or 64-bit addressable space into chunks.
/// The map is implemented with multiple levels, where the bits of an address are used as indices into an array of
/// pointers.  The upper levels of the map point to the next level down. At the lowest level of the map, the pointers
/// point to the first range section fragment containing addresses in the chunk.
///
/// On 32-bit targets a 2 level map is used
///
/// | 31-24 | 23-16 | 15-0 |
/// |:----:|:----:|:----:|
/// | L2 | L1 | chunk |
///
/// That is, level 2 in the map has 256 entries pointing to level 1 maps (or null if there's nothing allocated), each
/// level 1 map has 256 entries covering a 64 KiB chunk and pointing to a linked list of range section fragments that
/// fall within that 64 KiB chunk.
///
/// On 64-bit targets, we take advantage of the fact that most architectures don't support a full 64-bit addressable
/// space: arm64 supports 52 bits of addressable memory and x86-64 supports 57 bits.  The runtime ignores the top
/// bits 63-57 and uses 5 levels of mapping
///
/// | 63-57 | 56-49 | 48-41 | 40-33 | 32-25 | 24-17 | 16-0 |
/// |:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:----:|
/// | unused | L5 | L4 | L3 | L2 | L1 | chunk |
///
/// That is, level 5 has 256 entires pointing to level 4 maps (or nothing if there's no code allocated in that
/// address range), level 4 entires point to level 3 maps and so on.  Each level 1 map has 256 entries covering
/// a 128 KiB chunk and pointing to a linked list of range section fragments that fall within that 128 KiB chunk.
/// ]]></remarks>
internal readonly struct RangeSectionMap
{
    private int MapLevels { get; }
    private int BitsPerLevel { get; } = 8;
    private int MaxSetBit { get; }
    private int EntriesPerMapLevel { get; } = 256;

    /// <summary>
    /// The entries in the non-leaf levels of the map are pointers to the next level of the map together
    /// with a collectible flag on the lowest bit.
    /// </summary>
    internal struct InteriorMapValue
    {
        public readonly TargetPointer RawValue;

        public TargetPointer Address => RawValue & ~1ul;

        public bool IsNull => Address == TargetPointer.Null;

        public InteriorMapValue(TargetPointer value)
        {
            RawValue = value;
        }
    }

    /// <summary>
    /// Points at a slot in the RangeSectionMap that corresponds to an address range that includes a particular address.
    /// </summary>
    /// <remarks>
    /// Represented as a pointer to the current map level and an index into that map level.
    /// </remarks>
    internal readonly struct Cursor
    {
        public TargetPointer LevelMap { get; }
        public int Level { get; }
        public int Index { get; }

        public Cursor(TargetPointer levelMap, int level, int index)
        {
            LevelMap = levelMap;
            Level = level;
            Index = index;
        }

        /// <summary>
        /// Gets the address of the slot in the current level's map that the cursor points to
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        public TargetPointer GetSlot(Target target) => LevelMap + (ulong)(Index * target.PointerSize);

        // the values at the non-leaf levels are pointers with the last bit stolen for the collectible flag
        public InteriorMapValue LoadValue(Target target) => new InteriorMapValue(target.ReadPointer(GetSlot(target)));

        public bool IsLeaf => Level == 1;

    }

    internal Cursor GetTopCursor(TargetPointer topMap, TargetCodePointer jittedCodeAddress)
    {
        int index = GetIndexForLevel(jittedCodeAddress, MapLevels);
        return new Cursor(topMap, MapLevels, index);
    }

    internal Cursor GetNextCursor(Target target, Cursor cursor, TargetCodePointer jittedCodeAddress)
    {
        int nextLevel = cursor.Level - 1;
        int nextIndex = GetIndexForLevel(jittedCodeAddress, nextLevel);
        TargetPointer nextMap = cursor.LoadValue(target).Address;
        return new Cursor(nextMap, nextLevel, nextIndex);
    }

    private RangeSectionMap(int mapLevels, int maxSetBit)
    {
        MapLevels = mapLevels;
        MaxSetBit = maxSetBit;
    }
    public static RangeSectionMap Create(Target target)
    {
        if (target.PointerSize == 4)
        {
            return new(mapLevels: 2, maxSetBit: 31); // 0 indexed
        }
        else if (target.PointerSize == 8)
        {
            return new(mapLevels: 5, maxSetBit: 56); // 0 indexed
        }
        else
        {
            throw new InvalidOperationException("Invalid pointer size");
        }
    }

    // note: level is 1-indexed
    internal int GetIndexForLevel(TargetCodePointer address, int level)
    {
        ulong addressAsInt = address.Value;
        ulong addressBitsUsedInMap = addressAsInt >> (MaxSetBit + 1 - (MapLevels * BitsPerLevel));
        ulong addressBitsShifted = addressBitsUsedInMap >> ((level - 1) * BitsPerLevel);
        ulong addressBitsUsedInLevel = (ulong)(EntriesPerMapLevel - 1) & addressBitsShifted;
        return checked((int)addressBitsUsedInLevel);
    }

    /// <summary>
    /// Find the location of the fragment that contains the given jitted code address.
    /// </summary>
    /// <param name="target"></param>
    /// <param name="topRangeSectionMap"></param>
    /// <param name="jittedCodeAddress"></param>
    /// <returns>TargetPointer.Null if the map doesn't contain a fragment that could cover this address range</returns>
    public TargetPointer /*PTR_RangeSectionFragment*/ FindFragment(Target target, Data.RangeSectionMap topRangeSectionMap, TargetCodePointer jittedCodeAddress)
    {
        return FindFragmentInternal(target, topRangeSectionMap.TopLevelData, jittedCodeAddress)?.LoadValue(target).Address ?? TargetPointer.Null;
    }

    internal Cursor? FindFragmentInternal(Target target, TargetPointer topMap, TargetCodePointer jittedCodeAddress)
    {
        Cursor cursor = GetTopCursor(topMap, jittedCodeAddress);
        while (!cursor.IsLeaf)
        {
            InteriorMapValue nextLevel = cursor.LoadValue(target);
            if (nextLevel.IsNull)
                return null;
            cursor = GetNextCursor(target, cursor, jittedCodeAddress);
        }
        return cursor;
    }
}