| File: Contracts\ExecutionManager\Helpers\NibbleMapHelpers.cs | Web Access |
| Project: 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; namespace Microsoft.Diagnostics.DataContractReader.ExecutionManagerHelpers; internal static class NibbleMapHelpers { // we will partition the address space into buckets of this many bytes. // There is at most one code block header per bucket. // Normally we would then need 5 bits (Log2(BytesPerBucket))to find the exact start address, // but because code headers are aligned, we can store the offset in a 4-bit nibble instead and shift appropriately to compute // the effective address internal const ulong BytesPerBucket = 8 * MapUnit.SizeInBytes; // We load the map contents as 32-bit integers, which contains 8 4-bit nibbles. // The algorithm will focus on each nibble in a map unit before moving on to the previous map unit internal readonly struct MapUnit { public const int SizeInBytes = sizeof(uint); public const ulong SizeInNibbles = 2 * SizeInBytes; public readonly uint Value; public MapUnit(uint value) => Value = value; public override string ToString() => $"0x{Value:x}"; // Shift the next nibble into the least significant position. public MapUnit ShiftNextNibble => new MapUnit(Value >>> 4); public const uint NibbleMask = 0x0Fu; internal Nibble Nibble => new(Value & NibbleMask); public bool IsEmpty => Value == 0; // Assuming mapIdx is the index of a nibble within the current map unit, // shift the unit so that nibble is in the least significant position and return the result. public MapUnit FocusOnIndexedNibble(MapKey mapIdx) { uint shift = mapIdx.GetNibbleShift(); return new MapUnit(Value >>> (int)shift); } } // Each nibble is a 4-bit integer that gives an offset within a bucket. // We reserse 0 to mean that there is no method starting at any offset within a bucket internal readonly struct Nibble { public readonly uint Value; public Nibble(uint value) { Debug.Assert(value <= 0xF); Value = value; } public static Nibble Zero => new Nibble(0); public bool IsEmpty => Value == 0; public ulong TargetByteOffset { get { Debug.Assert(Value != 0); return (uint)(Value - 1) * MapUnit.SizeInBytes; } } } // The key to the map is the index of an individual nibble internal readonly struct MapKey { private readonly ulong _mapIdx; public MapKey(ulong mapIdx) => _mapIdx = mapIdx; public override string ToString() => $"0x{_mapIdx:x}"; // The offset of the address in the target space that this map index represents public ulong TargetByteOffset => _mapIdx * BytesPerBucket; // The index of the map unit that contains this map index public ulong ContainingMapUnitIndex => _mapIdx / MapUnit.SizeInNibbles; // The offset of the map unit that contains this map index public ulong ContainingMapUnitByteOffset => ContainingMapUnitIndex * MapUnit.SizeInBytes; // The map index is the index of a nibble within the map, this gives the index of that nibble within a map unit. public uint NibbleIndexInMapUnit => (uint)(_mapIdx & (MapUnit.SizeInNibbles - 1)); // go to the previous nibble public MapKey Prev { get { Debug.Assert(_mapIdx > 0); return new MapKey(_mapIdx - 1); } } // to to the previous map unit public MapKey PrevMapUnit => new MapKey(_mapIdx - MapUnit.SizeInNibbles); // Get a MapKey that is aligned to the first nibble in the map unit that contains this map index public MapKey AlignDownToMapUnit() =>new MapKey(_mapIdx & (~(MapUnit.SizeInNibbles - 1))); // If the map index is less than the size of a map unit, we are in the first MapUnit and // can stop searching public bool InFirstMapUnit => _mapIdx < MapUnit.SizeInNibbles; public bool IsZero => _mapIdx == 0; // given the index of a nibble in the map, compute how much we have to shift a MapUnit to put that // nibble in the least significant position. internal uint GetNibbleShift() { return 28 - (NibbleIndexInMapUnit * 4); // bit shift - 4 bits per nibble } internal MapUnit ReadMapUnit(Target target, TargetPointer mapStart) { // Given a logical index into the map, compute the address in memory where that map unit is located TargetPointer mapUnitAdderss = mapStart + ContainingMapUnitByteOffset; return new MapUnit(target.Read<uint>(mapUnitAdderss)); } } // for tests internal static uint ComputeNibbleShift(MapKey mapIdx) => mapIdx.GetNibbleShift(); internal static TargetPointer RoundTripAddress(TargetPointer mapBase, TargetPointer currentPC) { TargetNUInt relativeAddress = new TargetNUInt(currentPC.Value - mapBase.Value); DecomposeAddress(relativeAddress, out MapKey mapIdx, out Nibble bucketByteIndex); return GetAbsoluteAddress(mapBase, mapIdx, bucketByteIndex); } // Given a base address, a map index, and a nibble value, compute the absolute address in memory // that the index and nibble point to. internal static TargetPointer GetAbsoluteAddress(TargetPointer baseAddress, MapKey mapIdx, Nibble nibble) { return baseAddress + mapIdx.TargetByteOffset + nibble.TargetByteOffset; } // Given a relative address, decompose it into // the bucket index and an offset within the bucket. internal static void DecomposeAddress(TargetNUInt relative, out MapKey mapIdx, out Nibble bucketByteIndex) { mapIdx = new(relative.Value / BytesPerBucket); uint bucketByteOffset = (uint)(relative.Value & (BytesPerBucket - 1)); bucketByteIndex = new Nibble((bucketByteOffset / MapUnit.SizeInBytes) + 1); } }