File: Contracts\RuntimeTypeSystem_1.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;
using System.Reflection.Metadata.Ecma335;
using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers;
using Microsoft.Diagnostics.DataContractReader.Data;
using System.Reflection.Metadata;
using System.Collections.Immutable;
using System.Reflection;

namespace Microsoft.Diagnostics.DataContractReader.Contracts;

internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem
{
    private const int TYPE_MASK_OFFSET = 27; // offset of type in field desc flags2
    private readonly Target _target;
    private readonly TargetPointer _freeObjectMethodTablePointer;
    private readonly TargetPointer _continuationMethodTablePointer;
    private readonly ulong _methodDescAlignment;
    private readonly TypeValidation _typeValidation;
    private readonly MethodValidation _methodValidation;

    // TODO(cdac): we mutate this dictionary - copies of the RuntimeTypeSystem_1 struct share this instance.
    // If we need to invalidate our view of memory, we should clear this dictionary.
    private readonly Dictionary<TargetPointer, MethodTable> _methodTables = new();
    private readonly Dictionary<TargetPointer, MethodDesc> _methodDescs = new();
    private readonly Dictionary<TypeKey, TypeHandle> _typeHandles = new();
    private readonly Dictionary<TypeKeyByName, TypeHandle> _typeHandlesByName = new();

    public void Flush()
    {
        _methodTables.Clear();
        _methodDescs.Clear();
        _typeHandles.Clear();
        _typeHandlesByName.Clear();
    }

    internal struct MethodTable
    {
        internal MethodTableFlags_1 Flags { get; }
        internal ushort NumInterfaces { get; }
        internal ushort NumVirtuals { get; }
        internal TargetPointer ParentMethodTable { get; }
        internal TargetPointer Module { get; }
        internal TargetPointer EEClassOrCanonMT { get; }
        internal TargetPointer PerInstInfo { get; }
        internal TargetPointer AuxiliaryData { get; }
        internal MethodTable(Data.MethodTable data)
        {
            Flags = new MethodTableFlags_1
            {
                MTFlags = data.MTFlags,
                MTFlags2 = data.MTFlags2,
                BaseSize = data.BaseSize,
            };
            NumInterfaces = data.NumInterfaces;
            NumVirtuals = data.NumVirtuals;
            EEClassOrCanonMT = data.EEClassOrCanonMT;
            Module = data.Module;
            ParentMethodTable = data.ParentMethodTable;
            PerInstInfo = data.PerInstInfo;
            AuxiliaryData = data.AuxiliaryData;
        }

        // this MethodTable is a canonical MethodTable if its EEClassOrCanonMT is an EEClass
        internal bool IsCanonMT => MethodTableFlags_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == MethodTableFlags_1.EEClassOrCanonMTBits.EEClass;
    }

    private readonly struct TypeKey : IEquatable<TypeKey>
    {
        public TypeKey(TypeHandle typeHandle, CorElementType elementType, int rank, ImmutableArray<TypeHandle> typeArgs)
        {
            TypeHandle = typeHandle;
            ElementType = elementType;
            Rank = rank;
            TypeArgs = typeArgs;
        }
        public TypeHandle TypeHandle { get; }
        public CorElementType ElementType { get; }
        public int Rank { get; }
        public ImmutableArray<TypeHandle> TypeArgs { get; }

        public bool Equals(TypeKey other)
        {
            if (ElementType != other.ElementType || Rank != other.Rank || TypeArgs.Length != other.TypeArgs.Length)
                return false;
            for (int i = 0; i < TypeArgs.Length; i++)
            {
                if (!TypeArgs[i].Equals(other.TypeArgs[i]))
                    return false;
            }
            return true;
        }

        public override bool Equals(object? obj) => obj is TypeKey other && Equals(other);

        public override int GetHashCode()
        {
            int hash = HashCode.Combine(TypeHandle.GetHashCode(), (int)ElementType, Rank);
            foreach (TypeHandle th in TypeArgs)
            {
                hash = HashCode.Combine(hash, th.GetHashCode());
            }
            return hash;
        }
    }

    private readonly struct TypeKeyByName : IEquatable<TypeKeyByName>
    {
        public TypeKeyByName(string name, string namespaceName, TargetPointer module)
        {
            Name = name;
            Namespace = namespaceName;
            Module = module;
        }
        public string Name { get; }
        public string Namespace { get; }
        public TargetPointer Module { get; }
        public bool Equals(TypeKeyByName other) => Name == other.Name && Namespace == other.Namespace && Module == other.Module;
        public override bool Equals(object? obj) => obj is TypeKeyByName other && Equals(other);
        public override int GetHashCode() => HashCode.Combine(Name, Namespace, Module);
    }

    // Low order bits of TypeHandle address.
    // If the low bits contain a 2, then it is a TypeDesc
    [Flags]
    internal enum TypeHandleBits
    {
        MethodTable = 0,
        TypeDesc = 2,
        ValidMask = 2,
    }

    internal enum InstantiatedMethodDescFlags2 : ushort
    {
        KindMask = 0x07,
        GenericMethodDefinition = 0x01,
        UnsharedMethodInstantiation = 0x02,
        SharedMethodInstantiation = 0x03,
        WrapperStubWithInstantiations = 0x04,
    }

    [Flags]
    internal enum DynamicMethodDescExtendedFlags : uint
    {
        IsLCGMethod = 0x00004000,
        IsILStub = 0x00008000,
        ILStubTypeMask = 0x000007FF,
    }

    [Flags]
    internal enum AsyncMethodFlags : uint
    {
        None = 0,
        AsyncCall = 0x1,
        Thunk = 16,
    }

    [Flags]
    internal enum ILStubType : uint
    {
        StubPInvokeVarArg = 0x4,
        StubCLRToCOMInterop = 0x6,
    }

    // on MethodDescChunk.FlagsAndTokenRange
    [Flags]
    internal enum MethodDescChunkFlags : ushort
    {
        // Has this chunk had its methods been determined eligible for tiered compilation or not
        DeterminedIsEligibleForTieredCompilation = 0x4000,
        // Is this chunk associated with a LoaderModule directly? If this flag is set, then the LoaderModule pointer is placed at the end of the chunk.
        LoaderModuleAttachedToChunk = 0x8000,
    }

    internal enum MethodTableAuxiliaryFlags : uint
    {
        Initialized = 0x0001,
        IsInitError = 0x0100,
        IsNotFullyLoaded = 0x0040,
    }

    internal enum TypeDescFlags : uint
    {
        IsNotFullyLoaded = 0x00001000,
    }

    internal enum FieldDescFlags1 : uint
    {
        TokenMask = 0xffffff,
        IsStatic = 0x1000000,
        IsThreadStatic = 0x2000000,
        IsRVA = 0x4000000,
    }

    internal enum FieldDescFlags2 : uint
    {
        TypeMask = 0xf8000000,
        OffsetMask = 0x07ffffff,
    }

    internal struct MethodDesc
    {
        private readonly Data.MethodDesc _desc;
        private readonly Data.MethodDescChunk _chunk;
        private readonly Target _target;

        internal TargetPointer Address { get; init; }

        internal TargetPointer ChunkAddress { get; init; }

        internal MethodDesc(Target target, TargetPointer methodDescPointer, Data.MethodDesc desc, TargetPointer methodDescChunkAddress, Data.MethodDescChunk chunk)
        {
            _target = target;
            _desc = desc;
            _chunk = chunk;
            ChunkAddress = methodDescChunkAddress;
            Address = methodDescPointer;

            Token = ComputeToken(target, desc, chunk);
            Size = ComputeSize(target, desc);
        }

        public TargetPointer MethodTable => _chunk.MethodTable;
        public ushort Slot => _desc.Slot;
        public uint Token { get; }
        public uint Size { get; }

        private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.MethodDescChunk chunk)
        {
            int tokenRemainderBitCount = target.ReadGlobal<byte>(Constants.Globals.MethodDescTokenRemainderBitCount);
            int tokenRangeBitCount = EcmaMetadataUtils.RowIdBitCount - tokenRemainderBitCount;
            uint allRidBitsSet = EcmaMetadataUtils.RIDMask;
            uint tokenRemainderMask = allRidBitsSet >> tokenRangeBitCount;
            uint tokenRangeMask = allRidBitsSet >> tokenRemainderBitCount;

            uint tokenRemainder = (uint)(desc.Flags3AndTokenRemainder & tokenRemainderMask);
            uint tokenRange = ((uint)(chunk.FlagsAndTokenRange & tokenRangeMask)) << tokenRemainderBitCount;
            return EcmaMetadataUtils.CreateMethodDef(tokenRange | tokenRemainder);
        }

        private static uint ComputeSize(Target target, Data.MethodDesc desc)
        {
            // See s_ClassificationSizeTable in method.cpp in the runtime for how the size is determined based on the method classification and flags.
            uint baseSize;
            switch ((MethodClassification)(desc.Flags & (ushort)MethodDescFlags_1.MethodDescFlags.ClassificationMask))
            {
                case MethodClassification.IL:
                    baseSize = target.GetTypeInfo(DataType.MethodDesc).Size ?? throw new InvalidOperationException("MethodDesc type size must be known");
                    break;
                case MethodClassification.FCall:
                    baseSize = target.GetTypeInfo(DataType.FCallMethodDesc).Size ?? throw new InvalidOperationException("FCallMethodDesc type size must be known");
                    break;
                case MethodClassification.PInvoke:
                    baseSize = target.GetTypeInfo(DataType.PInvokeMethodDesc).Size ?? throw new InvalidOperationException("PInvokeMethodDesc type size must be known");
                    break;
                case MethodClassification.EEImpl:
                    baseSize = target.GetTypeInfo(DataType.EEImplMethodDesc).Size ?? throw new InvalidOperationException("EEImplMethodDesc type size must be known");
                    break;
                case MethodClassification.Array:
                    baseSize = target.GetTypeInfo(DataType.ArrayMethodDesc).Size ?? throw new InvalidOperationException("ArrayMethodDesc type size must be known");
                    break;
                case MethodClassification.Instantiated:
                    baseSize = target.GetTypeInfo(DataType.InstantiatedMethodDesc).Size ?? throw new InvalidOperationException("InstantiatedMethodDesc type size must be known");
                    break;
                case MethodClassification.ComInterop:
                    baseSize = target.GetTypeInfo(DataType.CLRToCOMCallMethodDesc).Size ?? throw new InvalidOperationException("CLRToCOMCallMethodDesc type size must be known");
                    break;
                case MethodClassification.Dynamic:
                    baseSize = target.GetTypeInfo(DataType.DynamicMethodDesc).Size ?? throw new InvalidOperationException("DynamicMethodDesc type size must be known");
                    break;
                default:
                    throw new InvalidOperationException("Invalid method classification");
            }

            MethodDescFlags_1.MethodDescFlags flags = (MethodDescFlags_1.MethodDescFlags)desc.Flags;
            if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot))
                baseSize += target.GetTypeInfo(DataType.NonVtableSlot).Size ?? throw new InvalidOperationException("NonVtableSlot type size must be known");

            if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasMethodImpl))
                baseSize += target.GetTypeInfo(DataType.MethodImpl).Size ?? throw new InvalidOperationException("MethodImpl type size must be known");

            if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot))
                baseSize += target.GetTypeInfo(DataType.NativeCodeSlot).Size ?? throw new InvalidOperationException("NativeCodeSlot type size must be known");

            if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData))
                baseSize += target.GetTypeInfo(DataType.AsyncMethodData).Size ?? throw new InvalidOperationException("AsyncMethodData type size must be known");

            return baseSize;
        }

        public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags_1.MethodDescFlags.ClassificationMask);

        private bool HasFlags(MethodDescFlags_1.MethodDescFlags flags) => (_desc.Flags & (ushort)flags) != 0;
        private bool HasFlags(MethodDescFlags_1.MethodDescFlags3 flags) => (_desc.Flags3AndTokenRemainder & (ushort)flags) != 0;
        internal bool HasFlags(MethodDescChunkFlags flags) => (_chunk.FlagsAndTokenRange & (ushort)flags) != 0;

        public bool IsEligibleForTieredCompilation => HasFlags(MethodDescFlags_1.MethodDescFlags3.IsEligibleForTieredCompilation);


        public bool IsUnboxingStub => HasFlags(MethodDescFlags_1.MethodDescFlags3.IsUnboxingStub);

        public TargetPointer CodeData => _desc.CodeData;

        public TargetPointer? GCCoverageInfo => _desc.GCCoverageInfo;

        public bool IsIL => Classification == MethodClassification.IL || Classification == MethodClassification.Instantiated;

        internal bool HasNonVtableSlot => MethodDescOptionalSlots.HasNonVtableSlot(_desc.Flags);
        internal bool HasNativeCodeSlot => MethodDescOptionalSlots.HasNativeCodeSlot(_desc.Flags);
        internal bool HasAsyncMethodData => MethodDescOptionalSlots.HasAsyncMethodData(_desc.Flags);

        internal bool HasStableEntryPoint => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasStableEntryPoint);
        internal bool HasPrecode => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasPrecode);
        internal bool IsStatic => HasFlags(MethodDescFlags_1.MethodDescFlags.Static);

        internal TargetPointer GetAddressOfNonVtableSlot() => MethodDescOptionalSlots.GetAddressOfNonVtableSlot(Address, Classification, _desc.Flags, _target);
        internal TargetPointer GetAddressOfNativeCodeSlot() => MethodDescOptionalSlots.GetAddressOfNativeCodeSlot(Address, Classification, _desc.Flags, _target);
        internal TargetPointer GetAddressOfAsyncMethodData() => MethodDescOptionalSlots.GetAddressOfAsyncMethodData(Address, Classification, _desc.Flags, _target);

        internal bool IsLoaderModuleAttachedToChunk => HasFlags(MethodDescChunkFlags.LoaderModuleAttachedToChunk);

        public ulong SizeOfChunk
        {
            get
            {
                ulong typeSize = _target.GetTypeInfo(DataType.MethodDescChunk).Size!.Value;
                ulong chunkSize = (ulong)(_chunk.Size + 1) * _target.ReadGlobal<ulong>(Constants.Globals.MethodDescAlignment);
                ulong extra = IsLoaderModuleAttachedToChunk ? (ulong)_target.PointerSize : 0;
                return typeSize + chunkSize + extra;
            }
        }

    }

    private enum OptimizationTier_1 : uint
    {
        OptimizationTier0,
        OptimizationTier1,
        OptimizationTier1OSR,
        OptimizationTierOptimized,
        OptimizationTier0Instrumented,
        OptimizationTier1Instrumented,
        OptimizationTierUnknown = 0xFFFFFFFF
    }

    private sealed class InstantiatedMethodDesc : IData<InstantiatedMethodDesc>
    {
        public static InstantiatedMethodDesc Create(Target target, TargetPointer address) => new InstantiatedMethodDesc(target, address);

        private readonly TargetPointer _address;
        private readonly Data.InstantiatedMethodDesc _desc;

        private InstantiatedMethodDesc(Target target, TargetPointer methodDescPointer)
        {
            _address = methodDescPointer;
            RuntimeTypeSystem_1 rts = (RuntimeTypeSystem_1)target.Contracts.RuntimeTypeSystem;
            _desc = target.ProcessedData.GetOrAdd<Data.InstantiatedMethodDesc>(methodDescPointer);

            int numGenericArgs = _desc.NumGenericArgs;
            TargetPointer perInstInfo = _desc.PerInstInfo;
            if ((perInstInfo == TargetPointer.Null) || (numGenericArgs == 0))
            {
                Instantiation = System.Array.Empty<TypeHandle>();
            }
            else
            {
                Instantiation = new TypeHandle[numGenericArgs];
                for (int i = 0; i < numGenericArgs; i++)
                {
                    Instantiation[i] = rts.GetTypeHandle(target.ReadPointer(perInstInfo + (ulong)target.PointerSize * (ulong)i));
                }
            }
        }

        private bool HasFlags(InstantiatedMethodDescFlags2 mask, InstantiatedMethodDescFlags2 flags) => (_desc.Flags2 & (ushort)mask) == (ushort)flags;
        internal bool IsWrapperStubWithInstantiations => HasFlags(InstantiatedMethodDescFlags2.KindMask, InstantiatedMethodDescFlags2.WrapperStubWithInstantiations);
        internal bool IsGenericMethodDefinition => HasFlags(InstantiatedMethodDescFlags2.KindMask, InstantiatedMethodDescFlags2.GenericMethodDefinition);
        internal bool HasPerInstInfo => _desc.PerInstInfo != TargetPointer.Null;
        internal bool HasMethodInstantiation => IsGenericMethodDefinition || HasPerInstInfo;
        public TypeHandle[] Instantiation { get; }
    }

    private sealed class DynamicMethodDesc : IData<DynamicMethodDesc>
    {
        public static DynamicMethodDesc Create(Target target, TargetPointer address) => new DynamicMethodDesc(target, address);

        private readonly TargetPointer _address;
        private readonly Data.DynamicMethodDesc _desc;
        private readonly Data.StoredSigMethodDesc _storedSigDesc;

        private DynamicMethodDesc(Target target, TargetPointer methodDescPointer)
        {
            _address = methodDescPointer;
            _desc = target.ProcessedData.GetOrAdd<Data.DynamicMethodDesc>(methodDescPointer);

            MethodName = _desc.MethodName != TargetPointer.Null
                ? target.ReadUtf8String(_desc.MethodName)
                : string.Empty;

            _storedSigDesc = target.ProcessedData.GetOrAdd<Data.StoredSigMethodDesc>(methodDescPointer);
        }

        public string MethodName { get; }
        public DynamicMethodDescExtendedFlags ExtendedFlags => (DynamicMethodDescExtendedFlags)_storedSigDesc.ExtendedFlags;

        public bool IsDynamicMethod => ExtendedFlags.HasFlag(DynamicMethodDescExtendedFlags.IsLCGMethod);
        public bool IsILStub => ExtendedFlags.HasFlag(DynamicMethodDescExtendedFlags.IsILStub);
        public ILStubType ILStubType => (ILStubType)(ExtendedFlags & DynamicMethodDescExtendedFlags.ILStubTypeMask);
        public bool IsCLRToCOMStub => ILStubType == ILStubType.StubCLRToCOMInterop;
        public bool IsPInvokeVarArgStub => ILStubType == ILStubType.StubPInvokeVarArg;
        public bool HasMDContextArg => IsCLRToCOMStub || IsPInvokeVarArgStub;
    }

    private sealed class StoredSigMethodDesc : IData<StoredSigMethodDesc>
    {
        public static StoredSigMethodDesc Create(Target target, TargetPointer address) => new StoredSigMethodDesc(target, address);

        public byte[] Signature { get; }
        private StoredSigMethodDesc(Target target, TargetPointer methodDescPointer)
        {
            Data.StoredSigMethodDesc storedSigMethodDesc = target.ProcessedData.GetOrAdd<Data.StoredSigMethodDesc>(methodDescPointer);
            Signature = new byte[storedSigMethodDesc.cSig];
            target.ReadBuffer(storedSigMethodDesc.Sig, Signature.AsSpan());
        }
    }

    internal RuntimeTypeSystem_1(Target target)
    {
        _target = target;
        _freeObjectMethodTablePointer = target.ReadPointer(
            target.ReadGlobalPointer(Constants.Globals.FreeObjectMethodTable));
        _continuationMethodTablePointer = target.ReadPointer(
            target.ReadGlobalPointer(Constants.Globals.ContinuationMethodTable));
        _methodDescAlignment = target.ReadGlobal<ulong>(Constants.Globals.MethodDescAlignment);
        _typeValidation = new TypeValidation(target, _continuationMethodTablePointer);
        _methodValidation = new MethodValidation(target, _methodDescAlignment);
        _methodValidation.SetMethodTableQueries(new NonValidatedMethodTableQueries(this));
    }

    internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer;
    internal TargetPointer ContinuationMethodTablePointer => _continuationMethodTablePointer;

    internal ulong MethodDescAlignment => _methodDescAlignment;

    public TypeHandle GetTypeHandle(TargetPointer typeHandlePointer)
    {
        TypeHandleBits addressLowBits = (TypeHandleBits)((ulong)typeHandlePointer & ((ulong)_target.PointerSize - 1));

        if ((addressLowBits != TypeHandleBits.MethodTable) && (addressLowBits != TypeHandleBits.TypeDesc))
        {
            throw new ArgumentException("Invalid type handle pointer", nameof(typeHandlePointer));
        }

        // if we already validated this address, return a handle
        if (_methodTables.ContainsKey(typeHandlePointer))
        {
            return new TypeHandle(typeHandlePointer);
        }

        // Check for a TypeDesc
        if (addressLowBits == TypeHandleBits.TypeDesc)
        {
            // This is a TypeDesc
            return new TypeHandle(typeHandlePointer);
        }

        TargetPointer methodTablePointer = typeHandlePointer;

        // Check if we cached the underlying data already
        if (_target.ProcessedData.TryGet(methodTablePointer, out Data.MethodTable? methodTableData))
        {
            // we already cached the data, we must have validated the address, create the representation struct for our use
            MethodTable trustedMethodTable = new MethodTable(methodTableData);
            _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable);
            return new TypeHandle(methodTablePointer);
        }

        // If it's the free object method table, we trust it to be valid
        if (methodTablePointer == FreeObjectMethodTablePointer)
        {
            Data.MethodTable freeObjectMethodTableData = _target.ProcessedData.GetOrAdd<Data.MethodTable>(methodTablePointer);
            MethodTable trustedMethodTable = new MethodTable(freeObjectMethodTableData);
            _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTable);
            return new TypeHandle(methodTablePointer);
        }

        // Otherwse, get ready to validate
        if (!_typeValidation.TryValidateMethodTablePointer(methodTablePointer))
        {
            throw new ArgumentException("Invalid method table pointer", nameof(typeHandlePointer));
        }
        // ok, we validated it, cache the data and add the MethodTable_1 struct to the dictionary
        Data.MethodTable trustedMethodTableData = _target.ProcessedData.GetOrAdd<Data.MethodTable>(methodTablePointer);
        MethodTable trustedMethodTableF = new MethodTable(trustedMethodTableData);
        _ = _methodTables.TryAdd(methodTablePointer, trustedMethodTableF);
        return new TypeHandle(methodTablePointer);
    }
    public TargetPointer GetModule(TypeHandle typeHandle)
    {
        if (typeHandle.IsMethodTable())
        {
            return _methodTables[typeHandle.Address].Module;
        }
        else if (typeHandle.IsTypeDesc())
        {
            if (HasTypeParam(typeHandle))
            {
                return GetModule(GetTypeParam(typeHandle));
            }
            else if (IsGenericVariable(typeHandle, out TargetPointer genericParamModule, out _))
            {
                return genericParamModule;
            }
            else
            {
                System.Diagnostics.Debug.Assert(IsFunctionPointer(typeHandle, out _, out _));
                return TargetPointer.Null;
            }
        }
        else
        {
            return TargetPointer.Null;
        }
    }
    public TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : GetClassData(typeHandle).MethodTable;
    public TargetPointer GetParentMethodTable(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : _methodTables[typeHandle.Address].ParentMethodTable;

    public uint GetBaseSize(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[typeHandle.Address].Flags.BaseSize;

    public uint GetComponentSize(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : _methodTables[typeHandle.Address].Flags.ComponentSize;

    private TargetPointer GetClassPointer(TypeHandle typeHandle)
    {
        MethodTable methodTable = _methodTables[typeHandle.Address];
        switch (MethodTableFlags_1.GetEEClassOrCanonMTBits(methodTable.EEClassOrCanonMT))
        {
            case MethodTableFlags_1.EEClassOrCanonMTBits.EEClass:
                return methodTable.EEClassOrCanonMT;
            case MethodTableFlags_1.EEClassOrCanonMTBits.CanonMT:
                TargetPointer canonMTPtr = MethodTableFlags_1.UntagEEClassOrCanonMT(methodTable.EEClassOrCanonMT);
                TypeHandle canonMTHandle = GetTypeHandle(canonMTPtr);
                MethodTable canonMT = _methodTables[canonMTHandle.Address];
                return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass
            default:
                throw new InvalidOperationException();
        }
    }

    // only called on validated method tables, so we don't need to re-validate the EEClass
    private Data.EEClass GetClassData(TypeHandle typeHandle)
    {
        TargetPointer clsPtr = GetClassPointer(typeHandle);
        return _target.ProcessedData.GetOrAdd<Data.EEClass>(clsPtr);
    }


    public bool IsFreeObjectMethodTable(TypeHandle typeHandle) => FreeObjectMethodTablePointer == typeHandle.Address;

    public bool IsString(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsString;
    public bool IsObjRef(TypeHandle typeHandle)
    {
        CorElementType elementType = GetSignatureCorElementType(typeHandle);
        // Keep this aligned with CorTypeInfo::IsObjRef semantics for signature element types.
        return elementType is CorElementType.Class or CorElementType.Array or CorElementType.SzArray;
    }
    public bool ContainsGCPointers(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.ContainsGCPointers;
    public bool RequiresAlign8(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.RequiresAlign8;
    public bool IsContinuation(TypeHandle typeHandle) => typeHandle.IsMethodTable()
        && _continuationMethodTablePointer != TargetPointer.Null
        && _methodTables[typeHandle.Address].ParentMethodTable == _continuationMethodTablePointer;

    IEnumerable<(uint Offset, uint Size)> IRuntimeTypeSystem.GetGCDescSeries(TypeHandle typeHandle, uint numComponents)
    {
        if (!typeHandle.IsMethodTable())
            yield break;

        if (!ContainsGCPointers(typeHandle))
            yield break;

        uint baseSize = GetBaseSize(typeHandle);
        uint componentSize = GetComponentSize(typeHandle);
        uint objectSize = baseSize + numComponents * componentSize;

        ulong mtAddress = typeHandle.Address;
        ulong pointerSize = (ulong)_target.PointerSize;

        // Sign-extend NumSeries from native pointer width.
        long numSeries = _target.PointerSize == sizeof(uint)
            ? (long)(int)_target.ReadPointer(mtAddress - pointerSize).Value
            : (long)_target.ReadPointer(mtAddress - pointerSize).Value;
        if (numSeries == 0)
            yield break;

        if (numSeries > 0)
        {
            // Regular series: iterate from highest (closest to MT) to lowest.
            for (ulong i = 0; i < (ulong)numSeries; i++)
            {
                ulong seriesBase = mtAddress - (3 + 2 * i) * pointerSize;
                ulong rawSeriesSize = _target.ReadPointer(seriesBase).Value;
                ulong seriesOffset = _target.ReadPointer(seriesBase + pointerSize).Value;
                yield return ((uint)seriesOffset, (uint)(rawSeriesSize + objectSize));
            }
        }
        else
        {
            long absNumSeries = -numSeries;
            ulong startOffset = _target.ReadPointer(mtAddress - 2 * pointerSize).Value;

            var seriesItems = new (uint Nptrs, uint Skip)[absNumSeries];
            for (long i = 0; i < absNumSeries; i++)
            {
                ulong itemAddress = mtAddress - (3 + (ulong)i) * pointerSize;

                // Read val_serie_item fields individually for endianness safety.
                uint nptrs, skip;
                if (_target.PointerSize == sizeof(uint))
                {
                    nptrs = _target.Read<ushort>(itemAddress);
                    skip = _target.Read<ushort>(itemAddress + sizeof(ushort));
                }
                else
                {
                    nptrs = _target.Read<uint>(itemAddress);
                    skip = _target.Read<uint>(itemAddress + sizeof(uint));
                }

                seriesItems[i] = (nptrs, skip);
            }

            ulong currentOffset = startOffset;
            for (int i = 0; i < numComponents; i++)
            {
                for (long j = 0; j < absNumSeries; j++)
                {
                    if (currentOffset > objectSize - pointerSize)
                        yield break;
                    uint runBytes = seriesItems[j].Nptrs * (uint)pointerSize;
                    yield return ((uint)currentOffset, runBytes);
                    currentOffset += runBytes + seriesItems[j].Skip;
                }
            }
        }
    }

    public bool IsDynamicStatics(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsDynamicStatics;
    public ushort GetNumInterfaces(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : _methodTables[typeHandle.Address].NumInterfaces;

    public uint GetTypeDefToken(TypeHandle typeHandle)
    {
        if (!typeHandle.IsMethodTable())
            return 0;
        MethodTable methodTable = _methodTables[typeHandle.Address];
        return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24));
    }
    public ushort GetNumVtableSlots(TypeHandle typeHandle)
    {
        if (!typeHandle.IsMethodTable())
            return 0;
        MethodTable methodTable = _methodTables[typeHandle.Address];
        ushort numNonVirtualSlots = methodTable.IsCanonMT ? GetClassData(typeHandle).NumNonVirtualSlots : (ushort)0;
        return checked((ushort)(methodTable.NumVirtuals + numNonVirtualSlots));
    }
    public ushort GetNumMethods(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : GetClassData(typeHandle).NumMethods;
    public uint GetTypeDefTypeAttributes(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (uint)0 : GetClassData(typeHandle).CorTypeAttr;
    public ushort GetNumInstanceFields(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : GetClassData(typeHandle).NumInstanceFields;
    public ushort GetNumStaticFields(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : GetClassData(typeHandle).NumStaticFields;
    public ushort GetNumThreadStaticFields(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? (ushort)0 : GetClassData(typeHandle).NumThreadStaticFields;
    public TargetPointer GetFieldDescList(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? TargetPointer.Null : GetClassData(typeHandle).FieldDescList;
    public bool IsTrackedReferenceWithFinalizer(TypeHandle typeHandle) => typeHandle.IsMethodTable() && _methodTables[typeHandle.Address].Flags.IsTrackedReferenceWithFinalizer;
    private TargetPointer GetDynamicStaticsInfo(TypeHandle typeHandle)
    {
        if (!typeHandle.IsMethodTable())
            return default;

        MethodTable methodTable = _methodTables[typeHandle.Address];
        if (!methodTable.Flags.IsDynamicStatics)
            return default;
        TargetPointer dynamicStaticsInfoSize = _target.GetTypeInfo(DataType.DynamicStaticsInfo).Size!.Value;
        TargetPointer dynamicStaticsInfoAddr = methodTable.AuxiliaryData - dynamicStaticsInfoSize;
        return dynamicStaticsInfoAddr;
    }

    private Data.ThreadStaticsInfo GetThreadStaticsInfo(TypeHandle typeHandle)
    {
        MethodTable methodTable = _methodTables[typeHandle.Address];
        TargetPointer threadStaticsInfoSize = _target.GetTypeInfo(DataType.ThreadStaticsInfo).Size!.Value;
        TargetPointer threadStaticsInfoAddr = methodTable.AuxiliaryData - threadStaticsInfoSize;
        Data.ThreadStaticsInfo threadStaticsInfo = _target.ProcessedData.GetOrAdd<Data.ThreadStaticsInfo>(threadStaticsInfoAddr);
        return threadStaticsInfo;
    }

    public TargetPointer GetGCThreadStaticsBasePointer(TypeHandle typeHandle, TargetPointer threadPtr)
    {
        if (!typeHandle.IsMethodTable())
            return TargetPointer.Null;
        TargetPointer tlsIndexPtr = GetThreadStaticsInfo(typeHandle).GCTlsIndex;
        Contracts.IThread threadContract = _target.Contracts.Thread;
        return threadContract.GetThreadLocalStaticBase(threadPtr, tlsIndexPtr);
    }

    public TargetPointer GetNonGCThreadStaticsBasePointer(TypeHandle typeHandle, TargetPointer threadPtr)
    {
        if (!typeHandle.IsMethodTable())
            return TargetPointer.Null;
        TargetPointer tlsIndexPtr = GetThreadStaticsInfo(typeHandle).NonGCTlsIndex;
        Contracts.IThread threadContract = _target.Contracts.Thread;
        return threadContract.GetThreadLocalStaticBase(threadPtr, tlsIndexPtr);
    }

    public TargetPointer GetGCStaticsBasePointer(TypeHandle typeHandle)
    {
        TargetPointer dynamicStaticsInfoAddr = GetDynamicStaticsInfo(typeHandle);
        if (dynamicStaticsInfoAddr == TargetPointer.Null)
            return TargetPointer.Null;
        Data.DynamicStaticsInfo dynamicStaticsInfo = _target.ProcessedData.GetOrAdd<Data.DynamicStaticsInfo>(dynamicStaticsInfoAddr);
        return dynamicStaticsInfo.GCStatics;
    }

    public TargetPointer GetNonGCStaticsBasePointer(TypeHandle typeHandle)
    {
        TargetPointer dynamicStaticsInfoAddr = GetDynamicStaticsInfo(typeHandle);
        if (dynamicStaticsInfoAddr == TargetPointer.Null)
            return TargetPointer.Null;
        Data.DynamicStaticsInfo dynamicStaticsInfo = _target.ProcessedData.GetOrAdd<Data.DynamicStaticsInfo>(dynamicStaticsInfoAddr);
        return dynamicStaticsInfo.NonGCStatics;
    }

    public ReadOnlySpan<TypeHandle> GetInstantiation(TypeHandle typeHandle)
    {
        if (!typeHandle.IsMethodTable())
            return default;

        MethodTable methodTable = _methodTables[typeHandle.Address];
        if (!methodTable.Flags.HasInstantiation)
            return default;

        return _target.ProcessedData.GetOrAdd<TypeInstantiation>(typeHandle.Address).TypeHandles;
    }

    public bool IsClassInited(TypeHandle typeHandle)
    {
        if (!typeHandle.IsMethodTable())
            return false;
        MethodTable methodTable = _methodTables[typeHandle.Address];
        MethodTableAuxiliaryData auxiliaryData = _target.ProcessedData.GetOrAdd<MethodTableAuxiliaryData>(methodTable.AuxiliaryData);
        return (auxiliaryData.Flags & (uint)MethodTableAuxiliaryFlags.Initialized) != 0;
    }

    public bool IsInitError(TypeHandle typeHandle)
    {
        if (!typeHandle.IsMethodTable())
            return false;
        MethodTable methodTable = _methodTables[typeHandle.Address];
        MethodTableAuxiliaryData auxiliaryData = _target.ProcessedData.GetOrAdd<MethodTableAuxiliaryData>(methodTable.AuxiliaryData);
        return (auxiliaryData.Flags & (uint)MethodTableAuxiliaryFlags.IsInitError) != 0;
    }

    private sealed class TypeInstantiation : IData<TypeInstantiation>
    {
        public static TypeInstantiation Create(Target target, TargetPointer address) => new TypeInstantiation(target, address);

        public TypeHandle[] TypeHandles { get; }
        private TypeInstantiation(Target target, TargetPointer typePointer)
        {
            RuntimeTypeSystem_1 rts = (RuntimeTypeSystem_1)target.Contracts.RuntimeTypeSystem;
            MethodTable methodTable = rts._methodTables[typePointer];
            Debug.Assert(methodTable.Flags.HasInstantiation);

            TargetPointer perInstInfo = methodTable.PerInstInfo;
            TargetPointer genericsDictInfoAddr = perInstInfo - (ulong)target.PointerSize;
            GenericsDictInfo genericsDictInfo = target.ProcessedData.GetOrAdd<GenericsDictInfo>(genericsDictInfoAddr);

            // Use the last dictionary. This corresponds to the specific type - any previous ones are for superclasses
            // See PerInstInfo in methodtable.h for details in coreclr
            TargetPointer dictionaryPointer = target.ReadPointer(perInstInfo + (ulong)target.PointerSize * (ulong)(genericsDictInfo.NumDicts - 1));

            int numberOfGenericArgs = genericsDictInfo.NumTypeArgs;
            TypeHandles = new TypeHandle[numberOfGenericArgs];
            for (int i = 0; i < numberOfGenericArgs; i++)
            {
                TypeHandles[i] = rts.GetTypeHandle(target.ReadPointer(dictionaryPointer + (ulong)target.PointerSize * (ulong)i));
            }
        }
    }

    public bool IsGenericTypeDefinition(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsGenericTypeDefinition;
    public bool ContainsGenericVariables(TypeHandle typeHandle)
    {
        if (typeHandle.IsTypeDesc())
        {
            CorElementType type = GetSignatureCorElementType(typeHandle);
            if (type == CorElementType.Var || type == CorElementType.MVar)
                return true;

            else if (HasTypeParam(typeHandle))
            {
                return ContainsGenericVariables(GetRootTypeParam(typeHandle));
            }

            else if (type == CorElementType.FnPtr)
            {
                _ = IsFunctionPointer(typeHandle, out ReadOnlySpan<TypeHandle> signatureTypeArgs, out _);
                foreach (TypeHandle sigTypeArg in signatureTypeArgs)
                {
                    if (ContainsGenericVariables(sigTypeArg))
                        return true;
                }
            }

            return false;
        }
        return _methodTables[typeHandle.Address].Flags.ContainsGenericVariables;
    }

    public bool IsCollectible(TypeHandle typeHandle) => !typeHandle.IsMethodTable() ? false : _methodTables[typeHandle.Address].Flags.IsCollectible;
    public bool HasTypeParam(TypeHandle typeHandle)
    {
        if (typeHandle.IsMethodTable())
        {
            MethodTable methodTable = _methodTables[typeHandle.Address];
            return methodTable.Flags.IsArray;
        }
        else if (typeHandle.IsTypeDesc())
        {
            var typeDesc = _target.ProcessedData.GetOrAdd<TypeDesc>(typeHandle.TypeDescAddress());
            CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF);
            switch (elemType)
            {
                case CorElementType.ValueType:
                case CorElementType.Byref:
                case CorElementType.Ptr:
                    return true;
            }
        }
        return false;
    }

    public CorElementType GetSignatureCorElementType(TypeHandle typeHandle)
    {
        if (typeHandle.IsMethodTable())
        {
            MethodTable methodTable = _methodTables[typeHandle.Address];

            switch (methodTable.Flags.GetFlag(MethodTableFlags_1.WFLAGS_HIGH.Category_Mask))
            {
                case MethodTableFlags_1.WFLAGS_HIGH.Category_Array:
                    return CorElementType.Array;
                case MethodTableFlags_1.WFLAGS_HIGH.Category_Array | MethodTableFlags_1.WFLAGS_HIGH.Category_IfArrayThenSzArray:
                    return CorElementType.SzArray;
                case MethodTableFlags_1.WFLAGS_HIGH.Category_ValueType:
                case MethodTableFlags_1.WFLAGS_HIGH.Category_Nullable:
                case MethodTableFlags_1.WFLAGS_HIGH.Category_PrimitiveValueType:
                    return CorElementType.ValueType;
                case MethodTableFlags_1.WFLAGS_HIGH.Category_TruePrimitive:
                    return (CorElementType)GetClassData(typeHandle).InternalCorElementType;
                default:
                    return CorElementType.Class;
            }
        }
        else if (typeHandle.IsTypeDesc())
        {
            var typeDesc = _target.ProcessedData.GetOrAdd<TypeDesc>(typeHandle.TypeDescAddress());
            return (CorElementType)(typeDesc.TypeAndFlags & 0xFF);
        }

        return default;
    }

    public CorElementType GetInternalCorElementType(TypeHandle typeHandle)
    {
        CorElementType sigType = GetSignatureCorElementType(typeHandle);
        if (sigType == CorElementType.ValueType && typeHandle.IsMethodTable())
        {
            CorElementType internalType = (CorElementType)GetClassData(typeHandle).InternalCorElementType;
            if (internalType != CorElementType.ValueType)
                return internalType;
        }

        return sigType;
    }

    public bool IsValueType(TypeHandle typeHandle)
    {
        if (typeHandle.IsMethodTable())
        {
            MethodTable methodTable = _methodTables[typeHandle.Address];
            return methodTable.Flags.IsValueType;
        }
        else if (typeHandle.IsTypeDesc())
        {
            var typeDesc = _target.ProcessedData.GetOrAdd<TypeDesc>(typeHandle.TypeDescAddress());
            return (CorElementType)(typeDesc.TypeAndFlags & 0xFF) == CorElementType.ValueType;
        }

        return false;
    }

    public bool IsEnum(TypeHandle typeHandle)
    {
        // Enums have Category_PrimitiveValueType in their MethodTable flags and their
        // InternalCorElementType is a primitive type (I1, U1, I2, U2, I4, U4, I8, U8),
        // not ValueType. Regular primitive value types (IntPtr/UIntPtr) have Category_TruePrimitive.
        if (!typeHandle.IsMethodTable())
            return false;

        CorElementType sigType = GetSignatureCorElementType(typeHandle);
        if (sigType != CorElementType.ValueType)
            return false;

        CorElementType internalType = (CorElementType)GetClassData(typeHandle).InternalCorElementType;
        return internalType != CorElementType.ValueType;
    }

    // return true if the TypeHandle represents an array, and set the rank to either 0 (if the type is not an array), or the rank number if it is.
    public bool IsArray(TypeHandle typeHandle, out uint rank)
    {
        if (typeHandle.IsMethodTable())
        {
            MethodTable methodTable = _methodTables[typeHandle.Address];

            switch (methodTable.Flags.GetFlag(MethodTableFlags_1.WFLAGS_HIGH.Category_Mask))
            {
                case MethodTableFlags_1.WFLAGS_HIGH.Category_Array:
                    // Multidim array: BaseSize = ArrayBaseSize + Rank * sizeof(uint) * 2
                    uint arrayBaseSize = _target.ReadGlobal<uint>(Constants.Globals.ArrayBaseSize);
                    uint boundsSize = methodTable.Flags.BaseSize - arrayBaseSize;
                    rank = boundsSize / (sizeof(uint) * 2);
                    return true;

                case MethodTableFlags_1.WFLAGS_HIGH.Category_Array | MethodTableFlags_1.WFLAGS_HIGH.Category_IfArrayThenSzArray:
                    rank = 1;
                    return true;
            }
        }

        rank = 0;
        return false;
    }

    public TypeHandle GetTypeParam(TypeHandle typeHandle)
    {
        if (typeHandle.IsMethodTable())
        {
            MethodTable methodTable = _methodTables[typeHandle.Address];
            if (!methodTable.Flags.IsArray)
                throw new ArgumentException(nameof(typeHandle));

            return GetTypeHandle(methodTable.PerInstInfo);
        }
        else if (typeHandle.IsTypeDesc())
        {
            var typeDesc = _target.ProcessedData.GetOrAdd<TypeDesc>(typeHandle.TypeDescAddress());
            CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF);
            switch (elemType)
            {
                case CorElementType.ValueType:
                case CorElementType.Byref:
                case CorElementType.Ptr:
                    ParamTypeDesc paramTypeDesc = _target.ProcessedData.GetOrAdd<ParamTypeDesc>(typeHandle.TypeDescAddress());
                    return GetTypeHandle(paramTypeDesc.TypeArg);
            }
        }
        throw new ArgumentException(nameof(typeHandle));
    }

    private TypeHandle GetRootTypeParam(TypeHandle typeHandle)
    {
        TypeHandle current = typeHandle;
        while (HasTypeParam(current))
        {
            current = GetTypeParam(current);
        }
        return current;
    }

    private bool GenericInstantiationMatch(TypeHandle genericType, TypeHandle potentialMatch, ImmutableArray<TypeHandle> typeArguments)
    {
        ReadOnlySpan<TypeHandle> instantiation = GetInstantiation(potentialMatch);
        if (instantiation.Length != typeArguments.Length)
            return false;

        if (GetTypeDefToken(genericType) != GetTypeDefToken(potentialMatch))
            return false;

        if (GetModule(genericType) != GetModule(potentialMatch))
            return false;

        for (int i = 0; i < instantiation.Length; i++)
        {
            if (!(instantiation[i].Address == typeArguments[i].Address))
                return false;
        }
        return true;
    }

    private bool ArrayPtrMatch(TypeHandle elementType, CorElementType corElementType, int rank, TypeHandle potentialMatch)
    {
        IsArray(potentialMatch, out uint typeHandleRank);
        return GetSignatureCorElementType(potentialMatch) == corElementType &&
                GetTypeParam(potentialMatch).Address == elementType.Address &&
                (corElementType == CorElementType.SzArray || corElementType == CorElementType.Byref ||
                corElementType == CorElementType.Ptr || (rank == typeHandleRank));

    }

    private bool IsLoaded(TypeHandle typeHandle)
    {
        if (typeHandle.Address == TargetPointer.Null)
            return false;
        if (typeHandle.IsTypeDesc())
        {
            Data.TypeDesc typeDesc = _target.ProcessedData.GetOrAdd<TypeDesc>(typeHandle.TypeDescAddress());
            return (typeDesc.TypeAndFlags & (uint)TypeDescFlags.IsNotFullyLoaded) == 0; // IsUnloaded
        }

        MethodTable methodTable = _methodTables[typeHandle.Address];
        Data.MethodTableAuxiliaryData auxData = _target.ProcessedData.GetOrAdd<Data.MethodTableAuxiliaryData>(methodTable.AuxiliaryData);
        return (auxData.Flags & (uint)MethodTableAuxiliaryFlags.IsNotFullyLoaded) == 0; // IsUnloaded
    }

    TypeHandle IRuntimeTypeSystem.GetConstructedType(TypeHandle typeHandle, CorElementType corElementType, int rank, ImmutableArray<TypeHandle> typeArguments)
    {
        if (typeHandle.Address == TargetPointer.Null)
            return new TypeHandle(TargetPointer.Null);
        if (_typeHandles.TryGetValue(new TypeKey(typeHandle, corElementType, rank, typeArguments), out TypeHandle existing))
            return existing;
        ILoader loaderContract = _target.Contracts.Loader;
        TargetPointer loaderModule = GetLoaderModule(typeHandle);
        ModuleHandle moduleHandle = loaderContract.GetModuleHandleFromModulePtr(loaderModule);
        TypeHandle potentialMatch;
        foreach (TargetPointer ptr in loaderContract.GetAvailableTypeParams(moduleHandle))
        {
            potentialMatch = GetTypeHandle(ptr);
            if (corElementType == CorElementType.GenericInst)
            {
                if (GenericInstantiationMatch(typeHandle, potentialMatch, typeArguments) && IsLoaded(potentialMatch))
                {
                    _ = _typeHandles.TryAdd(new TypeKey(typeHandle, corElementType, rank, typeArguments), potentialMatch);
                    return potentialMatch;
                }
            }
            else if (ArrayPtrMatch(typeHandle, corElementType, rank, potentialMatch) && IsLoaded(potentialMatch))
            {
                _ = _typeHandles.TryAdd(new TypeKey(typeHandle, corElementType, rank, typeArguments), potentialMatch);
                return potentialMatch;
            }
        }
        return new TypeHandle(TargetPointer.Null);
    }

    TypeHandle IRuntimeTypeSystem.GetPrimitiveType(CorElementType typeCode)
    {
        TargetPointer coreLib = _target.ReadGlobalPointer(Constants.Globals.CoreLib);
        CoreLibBinder coreLibData = _target.ProcessedData.GetOrAdd<CoreLibBinder>(coreLib);
        TargetPointer typeHandlePtr = _target.ReadPointer(coreLibData.Classes + (ulong)typeCode * (ulong)_target.PointerSize);
        return GetTypeHandle(typeHandlePtr);
    }

    private static bool ModuleMatch(AssemblyReference assemblyRef, AssemblyDefinition assemblyDef)
    {
        AssemblyName assemblyRefName = assemblyRef.GetAssemblyName();
        AssemblyName assemblyDefName = assemblyDef.GetAssemblyName();
        if ((assemblyRefName.Name != assemblyDefName.Name) ||
                (assemblyRefName.Version != assemblyDefName.Version) ||
                (assemblyRefName.CultureName != assemblyDefName.CultureName))
        {
            return false;
        }

        ReadOnlySpan<byte> refToken = assemblyRefName.GetPublicKeyToken();
        ReadOnlySpan<byte> defToken = assemblyDefName.GetPublicKeyToken();
        return refToken.SequenceEqual(defToken);
    }

    private MetadataReader? LookForHandle(AssemblyReference exportedAssemblyRef)
    {
        TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain);
        TargetPointer appDomain = _target.ReadPointer(appDomainPointer);
        foreach (ModuleHandle mdhandle in _target.Contracts.Loader.GetModuleHandles(appDomain, AssemblyIterationFlags.IncludeLoaded))
        {
            MetadataReader? md2 = _target.Contracts.EcmaMetadata.GetMetadata(mdhandle);
            if (md2 == null)
                continue;
            AssemblyDefinition assemblyDefinition = md2.GetAssemblyDefinition();
            if (ModuleMatch(exportedAssemblyRef, assemblyDefinition))
            {
                return md2;
            }
        }
        return null;
    }

    TypeHandle IRuntimeTypeSystem.GetTypeByNameAndModule(string name, string nameSpace, ModuleHandle moduleHandle)
    {
        ILoader loader = _target.Contracts.Loader;
        TargetPointer modulePtr = loader.GetModule(moduleHandle);
        if (_typeHandlesByName.TryGetValue(new TypeKeyByName(name, nameSpace, modulePtr), out TypeHandle existing))
            return existing;
        string[] parts = name.Split('+');
        string outerName = parts[0];
        MetadataReader? md = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle);
        TypeDefinitionHandle currentHandle = default;
        // create a hash set of MDs and if we come across the same one more than once in a loop then we return null typehandle
        HashSet<MetadataReader> seenMDs = new();
        // 1. find the outer type
        while (md != null && seenMDs.Add(md))
        {
            foreach (TypeDefinitionHandle typeDefHandle in md.TypeDefinitions)
            {
                TypeDefinition typedef = md.GetTypeDefinition(typeDefHandle);
                if (md.GetString(typedef.Name) == outerName && md.GetString(typedef.Namespace) == nameSpace)
                {
                    // found our outermost type, remember it
                    currentHandle = typeDefHandle;
                    break;
                }
            }

            if (currentHandle == default)
            {
                // look for forwarded types
                foreach (ExportedTypeHandle exportedTypeHandle in md.ExportedTypes)
                {
                    ExportedType exportedType = md.GetExportedType(exportedTypeHandle);
                    if (exportedType.Implementation.Kind != HandleKind.AssemblyReference || !exportedType.IsForwarder)
                        continue;
                    if (md.GetString(exportedType.Name) == outerName && md.GetString(exportedType.Namespace) == nameSpace)
                    {
                        // get the assembly ref for target
                        AssemblyReferenceHandle arefHandle = (AssemblyReferenceHandle)exportedType.Implementation;
                        AssemblyReference exportedAssemblyRef = md.GetAssemblyReference(arefHandle);
                        md = LookForHandle(exportedAssemblyRef);
                        break;
                    }
                }
            }
            else break; // if we found our typedef without forwarding break out of the while loop
        }

        if (currentHandle == default)
            return new TypeHandle(TargetPointer.Null);

        // 2. Walk down the nested types
        for (int i = 1; i < parts.Length; i++)
        {
            string nestedName = parts[i];
            bool found = false;
            foreach (TypeDefinitionHandle nestedHandle in md!.GetTypeDefinition(currentHandle).GetNestedTypes())
            {
                TypeDefinition nestedDef = md.GetTypeDefinition(nestedHandle);
                if (md.GetString(nestedDef.Name) == nestedName)
                {
                    currentHandle = nestedHandle;
                    found = true;
                    break;
                }
            }
            if (!found)
                return new TypeHandle(TargetPointer.Null);
        }

        // 3. We have the handle, look up the type handle
        int token = MetadataTokens.GetToken((EntityHandle)currentHandle);
        TargetPointer typeDefToMethodTable = loader.GetLookupTables(moduleHandle).TypeDefToMethodTable;
        TargetPointer typeHandlePtr = loader.GetModuleLookupMapElement(typeDefToMethodTable, (uint)token, out _);
        IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
        if (typeHandlePtr == TargetPointer.Null)
            return new TypeHandle(TargetPointer.Null);
        TypeHandle foundTypeHandle = rts.GetTypeHandle(typeHandlePtr);
        _ = _typeHandlesByName.TryAdd(new TypeKeyByName(name, nameSpace, modulePtr), foundTypeHandle);
        return foundTypeHandle;
    }

    public bool IsGenericVariable(TypeHandle typeHandle, out TargetPointer module, out uint token)
    {
        module = TargetPointer.Null;
        token = 0;

        if (!typeHandle.IsTypeDesc())
            return false;

        var typeDesc = _target.ProcessedData.GetOrAdd<TypeDesc>(typeHandle.TypeDescAddress());
        CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF);
        switch (elemType)
        {
            case CorElementType.MVar:
            case CorElementType.Var:
                TypeVarTypeDesc typeVarTypeDesc = _target.ProcessedData.GetOrAdd<TypeVarTypeDesc>(typeHandle.TypeDescAddress());
                module = typeVarTypeDesc.Module;
                token = typeVarTypeDesc.Token;
                return true;
        }
        return false;
    }

    public bool IsFunctionPointer(TypeHandle typeHandle, out ReadOnlySpan<TypeHandle> retAndArgTypes, out byte callConv)
    {
        retAndArgTypes = default;
        callConv = default;

        if (!typeHandle.IsTypeDesc())
            return false;

        var typeDesc = _target.ProcessedData.GetOrAdd<TypeDesc>(typeHandle.TypeDescAddress());
        CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF);
        if (elemType != CorElementType.FnPtr)
            return false;

        FnPtrTypeDesc fnPtrTypeDesc = _target.ProcessedData.GetOrAdd<FnPtrTypeDesc>(typeHandle.TypeDescAddress());
        retAndArgTypes = _target.ProcessedData.GetOrAdd<FunctionPointerRetAndArgs>(typeHandle.TypeDescAddress()).TypeHandles;
        callConv = (byte)fnPtrTypeDesc.CallConv;
        return true;
    }

    public bool IsPointer(TypeHandle typeHandle)
    {
        if (!typeHandle.IsTypeDesc())
            return false;

        var typeDesc = _target.ProcessedData.GetOrAdd<TypeDesc>(typeHandle.TypeDescAddress());
        CorElementType elemType = (CorElementType)(typeDesc.TypeAndFlags & 0xFF);
        return elemType == CorElementType.Ptr;
    }

    public TargetPointer GetLoaderModule(TypeHandle typeHandle)
    {
        if (typeHandle.IsTypeDesc())
        {
            // TypeDesc::GetLoaderModule
            if (HasTypeParam(typeHandle))
            {
                return GetLoaderModule(GetTypeParam(typeHandle));
            }
            else if (IsGenericVariable(typeHandle, out TargetPointer genericParamModule, out _))
            {
                return genericParamModule;
            }
            else
            {
                System.Diagnostics.Debug.Assert(IsFunctionPointer(typeHandle, out _, out _));
                FnPtrTypeDesc fnPtrTypeDesc = _target.ProcessedData.GetOrAdd<FnPtrTypeDesc>(typeHandle.TypeDescAddress());
                return fnPtrTypeDesc.LoaderModule;
            }
        }

        MethodTable mt = _methodTables[typeHandle.Address];
        Data.MethodTableAuxiliaryData mtAuxData = _target.ProcessedData.GetOrAdd<Data.MethodTableAuxiliaryData>(mt.AuxiliaryData);
        return mtAuxData.LoaderModule;
    }

    private sealed class FunctionPointerRetAndArgs : IData<FunctionPointerRetAndArgs>
    {
        public static FunctionPointerRetAndArgs Create(Target target, TargetPointer address) => new FunctionPointerRetAndArgs(target, address);

        public TypeHandle[] TypeHandles { get; }
        private FunctionPointerRetAndArgs(Target target, TargetPointer typePointer)
        {
            RuntimeTypeSystem_1 rts = (RuntimeTypeSystem_1)target.Contracts.RuntimeTypeSystem;
            FnPtrTypeDesc fnPtrTypeDesc = target.ProcessedData.GetOrAdd<FnPtrTypeDesc>(typePointer);

            TargetPointer retAndArgs = fnPtrTypeDesc.RetAndArgTypes;
            int numberOfRetAndArgTypes = checked((int)fnPtrTypeDesc.NumArgs + 1);

            TypeHandles = new TypeHandle[numberOfRetAndArgTypes];
            for (int i = 0; i < numberOfRetAndArgTypes; i++)
            {
                TypeHandles[i] = rts.GetTypeHandle(target.ReadPointer(retAndArgs + (ulong)target.PointerSize * (ulong)i));
            }
        }
    }

    public MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer)
        => GetMethodDescHandle(methodDescPointer, validate: true);

    private MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer, bool validate)
    {
        // if we already have a method desc at this address, return a handle
        if (_methodDescs.ContainsKey(methodDescPointer))
        {
            return new MethodDescHandle(methodDescPointer);
        }

        TargetPointer methodDescChunkPointer;
        if (validate)
        {
            if (!_methodValidation.ValidateMethodDescPointer(methodDescPointer, out methodDescChunkPointer))
            {
                throw new ArgumentException("Invalid method desc pointer", nameof(methodDescPointer));
            }
        }
        else
        {
            methodDescChunkPointer = _methodValidation.GetMethodDescChunkPointerThrowing(methodDescPointer, _target.ProcessedData.GetOrAdd<Data.MethodDesc>(methodDescPointer));
        }

        // ok, we validated it, cache the data and add the MethodDesc struct to the dictionary
        Data.MethodDescChunk validatedMethodDescChunkData = _target.ProcessedData.GetOrAdd<Data.MethodDescChunk>(methodDescChunkPointer);
        Data.MethodDesc validatedMethodDescData = _target.ProcessedData.GetOrAdd<Data.MethodDesc>(methodDescPointer);

        MethodDesc trustedMethodDescF = new MethodDesc(_target, methodDescPointer, validatedMethodDescData, methodDescChunkPointer, validatedMethodDescChunkData);
        _ = _methodDescs.TryAdd(methodDescPointer, trustedMethodDescF);
        return new MethodDescHandle(methodDescPointer);
    }

    public TargetPointer GetMethodTable(MethodDescHandle methodDescHandle) => _methodDescs[methodDescHandle.Address].MethodTable;

    private InstantiatedMethodDesc AsInstantiatedMethodDesc(MethodDesc methodDesc)
    {
        Debug.Assert(methodDesc.Classification == MethodClassification.Instantiated);
        return _target.ProcessedData.GetOrAdd<InstantiatedMethodDesc>(methodDesc.Address);
    }

    private DynamicMethodDesc AsDynamicMethodDesc(MethodDesc methodDesc)
    {
        Debug.Assert(methodDesc.Classification == MethodClassification.Dynamic);
        return _target.ProcessedData.GetOrAdd<DynamicMethodDesc>(methodDesc.Address);
    }

    private StoredSigMethodDesc AsStoredSigMethodDesc(MethodDesc methodDesc)
    {
        Debug.Assert(methodDesc.Classification == MethodClassification.Dynamic ||
                     methodDesc.Classification == MethodClassification.EEImpl ||
                     methodDesc.Classification == MethodClassification.Array);
        return _target.ProcessedData.GetOrAdd<StoredSigMethodDesc>(methodDesc.Address);
    }

    public bool IsGenericMethodDefinition(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

        if (methodDesc.Classification != MethodClassification.Instantiated)
            return false;
        return AsInstantiatedMethodDesc(methodDesc).IsGenericMethodDefinition;
    }

    public ReadOnlySpan<TypeHandle> GetGenericMethodInstantiation(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

        if (methodDesc.Classification != MethodClassification.Instantiated)
            return default;

        return AsInstantiatedMethodDesc(methodDesc).Instantiation;
    }

    private bool RequiresInstArg(MethodDesc methodDesc)
    {
        if (HasMethodInstantiation(methodDesc))
            return true;

        if (methodDesc.IsStatic)
            return true;

        MethodTable mt = _methodTables[methodDesc.MethodTable];
        if (mt.Flags.IsValueType)
            return true;

        if (mt.Flags.IsInterface && !IsAbstract(methodDesc))
            return true;

        return false;
    }

    public GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
        if (!IsSharedByGenericInstantiations(methodDesc))
            return GenericContextLoc.None;
        else if (RequiresInstArg(methodDesc))
            return GenericContextLoc.InstArg;
        else
            return GenericContextLoc.ThisPtr;
    }

    private bool IsSharedByGenericInstantiations(MethodDesc methodDesc)
    {
        // Check method-level sharing: InstantiatedMethodDesc with SharedMethodInstantiation
        if (methodDesc.Classification == MethodClassification.Instantiated)
        {
            InstantiatedMethodDesc imd = AsInstantiatedMethodDesc(methodDesc);
            if (imd.IsWrapperStubWithInstantiations)
                return false;

            // Check SharedMethodInstantiation flag
            Data.InstantiatedMethodDesc imdData = _target.ProcessedData.GetOrAdd<Data.InstantiatedMethodDesc>(methodDesc.Address);
            if ((imdData.Flags2 & (ushort)InstantiatedMethodDescFlags2.KindMask)
                == (ushort)InstantiatedMethodDescFlags2.SharedMethodInstantiation)
                return true;
        }

        // Check class-level sharing: canonical MethodTable with generic instantiation
        MethodTable mt = _methodTables[methodDesc.MethodTable];
        return mt.IsCanonMT && mt.Flags.HasInstantiation;
    }

    private bool IsAbstract(MethodDesc methodDesc)
    {
        if (methodDesc.Classification == MethodClassification.Array || methodDesc.Classification == MethodClassification.Dynamic)
            return false;
        uint token = methodDesc.Token;
        if (EcmaMetadataUtils.GetRowId(token) == 0)
            return false;

        TargetPointer modulePtr = _methodTables[methodDesc.MethodTable].Module;
        ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr);
        MetadataReader? mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle);
        if (mdReader is null)
            return false;

        MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)EcmaMetadataUtils.GetRowId(token));
        MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle);
        return (methodDef.Attributes & MethodAttributes.Abstract) != 0;
    }

    public bool IsAsyncMethod(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
        if (!methodDesc.HasAsyncMethodData)
            return false;

        // AsyncMethodData is the last optional slot, placed after NativeCodeSlot.
        // Read the AsyncMethodFlags (first field) and check for AsyncCall.
        TargetPointer asyncDataAddr = methodDesc.GetAddressOfAsyncMethodData();
        uint asyncFlags = _target.Read<uint>(asyncDataAddr);
        return (asyncFlags & (uint)AsyncMethodFlags.AsyncCall) != 0;
    }

    public uint GetMethodToken(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
        return methodDesc.Token;
    }

    public bool IsArrayMethod(MethodDescHandle methodDescHandle, out ArrayFunctionType functionType)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

        if (methodDesc.Classification != MethodClassification.Array)
        {
            functionType = default;
            return false;
        }

        // To get the array function index, subtract the number of virtuals from the method's slot
        // The array vtable looks like:
        //    System.Object vtable
        //    System.Array vtable
        //    type[] vtable
        //    Get
        //    Set
        //    Address
        //    .ctor        // possibly more
        // See ArrayMethodDesc for details in coreclr
        MethodTable methodTable = GetOrCreateMethodTable(methodDesc);
        int arrayMethodIndex = methodDesc.Slot - methodTable.NumVirtuals;
        functionType = arrayMethodIndex switch
        {
            0 => ArrayFunctionType.Get,
            1 => ArrayFunctionType.Set,
            2 => ArrayFunctionType.Address,
            >= 3 => ArrayFunctionType.Constructor,
            _ => throw new InvalidOperationException()
        };

        return true;
    }

    public bool IsNoMetadataMethod(MethodDescHandle methodDescHandle, out string methodName)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

        if (methodDesc.Classification != MethodClassification.Dynamic)
        {
            methodName = string.Empty;
            return false;
        }

        methodName = AsDynamicMethodDesc(methodDesc).MethodName;
        return true;
    }

    public bool IsStoredSigMethodDesc(MethodDescHandle methodDescHandle, out ReadOnlySpan<byte> signature)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

        switch (methodDesc.Classification)
        {
            case MethodClassification.Dynamic:
            case MethodClassification.EEImpl:
            case MethodClassification.Array:
                break; // These have stored sigs

            default:
                signature = default;
                return false;
        }

        signature = AsStoredSigMethodDesc(methodDesc).Signature;
        return true;
    }

    public bool IsDynamicMethod(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

        if (methodDesc.Classification != MethodClassification.Dynamic)
        {
            return false;
        }

        return AsDynamicMethodDesc(methodDesc).IsDynamicMethod;
    }

    public bool IsIL(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
        return methodDesc.IsIL;
    }

    public bool IsILStub(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
        if (methodDesc.Classification != MethodClassification.Dynamic)
        {
            return false;
        }

        return AsDynamicMethodDesc(methodDesc).IsILStub;
    }

    public bool HasMDContextArg(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

        if (methodDesc.Classification != MethodClassification.Dynamic)
        {
            return false;
        }

        return AsDynamicMethodDesc(methodDesc).HasMDContextArg;
    }

    private MethodTable GetOrCreateMethodTable(MethodDesc methodDesc)
    {
        // Ensures that the method table is valid, created, and cached
        _ = GetTypeHandle(methodDesc.MethodTable);
        return _methodTables[methodDesc.MethodTable];
    }

    private struct VtableIndirections
    {
        // See methodtable.inl  VTABLE_SLOTS_PER_CHUNK and the comment on it
        private const int NumPointersPerIndirection = 8;
        private const int NumPointersPerIndirectionLog2 = 3;
        private readonly Target _target;
        public readonly TargetPointer Address;
        public VtableIndirections(Target target, TargetPointer address)
        {
            _target = target;
            Address = address;
        }

        public TargetPointer GetAddressOfSlot(uint slotNum)
        {
            TargetPointer indirectionPointer = Address + (ulong)(slotNum >> NumPointersPerIndirectionLog2) * (ulong)_target.PointerSize;
            TargetPointer slotsStart = _target.ReadPointer(indirectionPointer);
            return slotsStart + (ulong)(slotNum & (NumPointersPerIndirection - 1)) * (ulong)_target.PointerSize;
        }
    }

    private VtableIndirections GetVTableIndirections(TargetPointer methodTableAddress)
    {
        var typeInfo = _target.GetTypeInfo(DataType.MethodTable);
        return new VtableIndirections(_target, methodTableAddress + typeInfo.Size!.Value);
    }

    private TargetPointer GetAddressOfSlot(TypeHandle typeHandle, uint slotNum)
    {
        if (!typeHandle.IsMethodTable())
            throw new InvalidOperationException($"nameof{typeHandle} is not a MethodTable");

        Debug.Assert(slotNum < GetNumVtableSlots(typeHandle), "Slot number is greater than the number of slots");

        // MethodTable::GetSlotPtrRaw
        MethodTable mt = _methodTables[typeHandle.Address];
        if (slotNum < mt.NumVirtuals)
        {
            // Virtual slots live in chunks pointed to by vtable indirections
            return GetVTableIndirections(typeHandle.Address).GetAddressOfSlot(slotNum);
        }
        else
        {
            Debug.Assert(mt.NumVirtuals < GetNumVtableSlots(typeHandle), "Method table does not have non-virtual slots");

            // Non-virtual slots < GetNumVtableSlots live before the MethodTableAuxiliaryData. The array grows backwards
            TargetPointer auxDataPtr = mt.AuxiliaryData;
            Data.MethodTableAuxiliaryData auxData = _target.ProcessedData.GetOrAdd<Data.MethodTableAuxiliaryData>(auxDataPtr);
            TargetPointer nonVirtualSlotsArray = auxDataPtr + (ulong)auxData.OffsetToNonVirtualSlots;
            return nonVirtualSlotsArray - ((1 + (slotNum - mt.NumVirtuals)) * (ulong)_target.PointerSize);
        }
    }

    private bool IsWrapperStub(MethodDesc md)
    {
        return md.IsUnboxingStub || IsInstantiatingStub(md);
    }

    private bool IsInstantiatingStub(MethodDesc md)
    {
        return md.Classification == MethodClassification.Instantiated && !md.IsUnboxingStub && AsInstantiatedMethodDesc(md).IsWrapperStubWithInstantiations;
    }

    private bool HasMethodInstantiation(MethodDesc md)
    {
        return md.Classification == MethodClassification.Instantiated && AsInstantiatedMethodDesc(md).HasMethodInstantiation;
    }

    private bool IsGenericMethodDefinition(MethodDesc md)
    {
        return md.Classification == MethodClassification.Instantiated && AsInstantiatedMethodDesc(md).IsGenericMethodDefinition;
    }

    private TargetPointer GetLoaderModule(MethodDesc md)
    {
        // MethodDesc::GetLoaderModule:
        // return GetMethodDescChunk()->GetLoaderModule();
        // MethodDescChunk::GetLoaderModule:
        if (md.IsLoaderModuleAttachedToChunk)
        {
            TargetPointer methodDescChunkPointer = md.ChunkAddress;
            TargetPointer endOfChunk = methodDescChunkPointer + md.SizeOfChunk;
            TargetPointer ppLoaderModule = endOfChunk - (ulong)_target.PointerSize;
            return _target.ReadPointer(ppLoaderModule);
        }
        else
        {
            TargetPointer mtAddr = GetMethodTable(new MethodDescHandle(md.Address));
            TypeHandle mt = GetTypeHandle(mtAddr);
            return GetLoaderModule(mt);
        }
    }

    bool IRuntimeTypeSystem.IsCollectibleMethod(MethodDescHandle methodDesc)
    {
        MethodDesc md = _methodDescs[methodDesc.Address];
        TargetPointer loaderModuleAddr = GetLoaderModule(md);
        ModuleHandle mod = _target.Contracts.Loader.GetModuleHandleFromModulePtr(loaderModuleAddr);
        return _target.Contracts.Loader.IsCollectible(mod);
    }

    bool IRuntimeTypeSystem.IsVersionable(MethodDescHandle methodDesc)
    {
        MethodDesc md = _methodDescs[methodDesc.Address];
        if (md.IsEligibleForTieredCompilation)
            return true;
        // MethodDesc::IsEligibleForReJIT
        if (_target.Contracts.ReJIT.IsEnabled())
        {
            if (!md.IsIL)
                return false;
            if (IsWrapperStub(md))
                return false;
            return _target.Contracts.CodeVersions.CodeVersionManagerSupportsMethod(methodDesc.Address);
        }
        return false;
    }

    TargetPointer IRuntimeTypeSystem.GetMethodDescVersioningState(MethodDescHandle methodDesc)
    {
        MethodDesc md = _methodDescs[methodDesc.Address];
        TargetPointer codeDataAddress = md.CodeData;
        if (codeDataAddress == TargetPointer.Null)
            return TargetPointer.Null;

        Data.MethodDescCodeData codeData = _target.ProcessedData.GetOrAdd<Data.MethodDescCodeData>(codeDataAddress);
        return codeData.VersioningState;
    }

    uint IRuntimeTypeSystem.GetMethodToken(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
        return methodDesc.Token;
    }

    ushort IRuntimeTypeSystem.GetSlotNumber(MethodDescHandle methodDesc)
    {
        MethodDesc md = _methodDescs[methodDesc.Address];
        return md.Slot;
    }
    bool IRuntimeTypeSystem.HasNativeCodeSlot(MethodDescHandle methodDesc)
    {
        MethodDesc md = _methodDescs[methodDesc.Address];
        return md.HasNativeCodeSlot;
    }

    // Based on MethodTable::IntroducedMethodIterator
    private IEnumerable<MethodDescHandle> GetIntroducedMethods(TypeHandle typeHandle)
    {
        Debug.Assert(typeHandle.IsMethodTable());

        EEClass eeClass = GetClassData(typeHandle);

        TargetPointer chunkAddr = eeClass.MethodDescChunk;
        while (chunkAddr != TargetPointer.Null)
        {
            MethodDescChunk chunk = _target.ProcessedData.GetOrAdd<MethodDescChunk>(chunkAddr);
            TargetPointer methodDescPtr = chunk.FirstMethodDesc;
            // chunk.Count is the number of MethodDescs in the chunk - 1
            for (int i = 0; i < chunk.Count + 1; i++)
            {
                // Validation of some MethodDescs fails in heap dumps due to missing memory.
                // Skipping validation should be okay as the pointers come from the target.
                MethodDescHandle methodDescHandle = GetMethodDescHandle(methodDescPtr, validate: false);
                MethodDesc md = _methodDescs[methodDescHandle.Address];
                methodDescPtr += md.Size;
                yield return methodDescHandle;
            }

            chunkAddr = chunk.Next;
        }
    }

    IEnumerable<TargetPointer> IRuntimeTypeSystem.GetIntroducedMethodDescs(TypeHandle typeHandle)
    {
        if (!typeHandle.IsMethodTable())
            yield break;

        TypeHandle canonMT = GetTypeHandle(GetCanonicalMethodTable(typeHandle));
        foreach (MethodDescHandle mdh in GetIntroducedMethods(canonMT))
        {
            yield return mdh.Address;
        }
    }

    // Uses GetMethodDescForVtableSlot if slot is less than the number of vtable slots
    // otherwise looks for the slot in the introduced methods
    TargetPointer IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, ushort slot)
    {
        if (!typeHandle.IsMethodTable())
            // TypeDesc do not contain any slots.
            return TargetPointer.Null;

        TypeHandle canonMT = GetTypeHandle(GetCanonicalMethodTable(typeHandle));
        if (slot < GetNumVtableSlots(canonMT))
        {
            return GetMethodDescForVtableSlot(canonMT, slot);
        }
        else
        {
            foreach (MethodDescHandle mdh in GetIntroducedMethods(canonMT))
            {
                MethodDesc md = _methodDescs[mdh.Address];
                if (md.Slot == slot)
                {
                    return mdh.Address;
                }
            }
            return TargetPointer.Null;
        }
    }

    private TargetPointer GetMethodDescForVtableSlot(TypeHandle typeHandle, ushort slot)
    {
        // based on MethodTable::GetMethodDescForSlot_NoThrow
        if (!typeHandle.IsMethodTable())
            // TypeDesc do not contain any slots.
            throw new ArgumentException(nameof(slot), "Slot number is greater than the number of slots");

        TargetPointer cannonMTPTr = GetCanonicalMethodTable(typeHandle);
        TypeHandle canonMT = GetTypeHandle(cannonMTPTr);
        if (slot >= GetNumVtableSlots(canonMT))
            throw new ArgumentException(nameof(slot), "Slot number is greater than the number of slots");

        TargetPointer slotPtr = GetAddressOfSlot(canonMT, slot);
        TargetCodePointer pCode = _target.ReadCodePointer(slotPtr);

        if (pCode == TargetCodePointer.Null)
        {
            TargetPointer lookupMTPtr = cannonMTPTr;
            while (lookupMTPtr != TargetPointer.Null)
            {
                // if pCode is null, we iterate through the method descs in the MT.
                TypeHandle lookupMT = GetTypeHandle(lookupMTPtr);
                foreach (MethodDescHandle mdh in GetIntroducedMethods(lookupMT))
                {
                    MethodDesc md = _methodDescs[mdh.Address];
                    if (md.Slot == slot)
                    {
                        return mdh.Address;
                    }
                }
                lookupMTPtr = GetParentMethodTable(lookupMT);
                if (lookupMTPtr != TargetPointer.Null)
                    lookupMTPtr = GetCanonicalMethodTable(GetTypeHandle(lookupMTPtr));
            }
            return TargetPointer.Null;
        }

        return GetMethodDescForEntrypoint(pCode);
    }

    private readonly TargetPointer GetMethodDescForEntrypoint(TargetCodePointer pCode)
    {
        // standard path, ask ExecutionManager for the MethodDesc
        IExecutionManager executionManager = _target.Contracts.ExecutionManager;
        if (executionManager.GetCodeBlockHandle(pCode) is CodeBlockHandle cbh)
        {
            TargetPointer methodDescPtr = executionManager.GetMethodDesc(cbh);
            return methodDescPtr;
        }

        // stub path, read address as a Precode and read MethodDesc from it
        {
            TargetPointer methodDescPtr = _target.Contracts.PrecodeStubs.GetMethodDescFromStubAddress(pCode);
            return methodDescPtr;
        }
    }

    TargetCodePointer IRuntimeTypeSystem.GetSlot(TypeHandle typeHandle, uint slot)
    {
        // based on MethodTable::GetSlot(uint slotNumber)

        if (slot < GetNumVtableSlots(typeHandle))
        {
            TargetPointer slotPtr = GetAddressOfSlot(typeHandle, slot);
            return _target.ReadCodePointer(slotPtr);
        }

        return TargetCodePointer.Null;
    }

    TargetPointer IRuntimeTypeSystem.GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc)
    {
        MethodDesc md = _methodDescs[methodDesc.Address];
        return md.GetAddressOfNativeCodeSlot();
    }

    TargetCodePointer IRuntimeTypeSystem.GetNativeCode(MethodDescHandle methodDescHandle)
    {
        MethodDesc md = _methodDescs[methodDescHandle.Address];
        // TODO(cdac): _ASSERTE(!IsDefaultInterfaceMethod() || HasNativeCodeSlot());
        if (md.HasNativeCodeSlot)
        {
            // When profiler is enabled, profiler may ask to rejit a code even though we
            // we have ngen code for this MethodDesc.  (See MethodDesc::DoPrestub).
            // This means that *ppCode is not stable. It can turn from non-zero to zero.
            TargetPointer ppCode = md.GetAddressOfNativeCodeSlot();
            TargetCodePointer pCode = _target.ReadCodePointer(ppCode);
            return CodePointerUtils.CodePointerFromAddress(pCode.AsTargetPointer, _target);
        }

        if (!md.HasStableEntryPoint || md.HasPrecode)
            return TargetCodePointer.Null;

        return GetStableEntryPoint(md);
    }

    TargetCodePointer IRuntimeTypeSystem.GetMethodEntryPointIfExists(MethodDescHandle methodDescHandle)
    {
        MethodDesc md = _methodDescs[methodDescHandle.Address];
        return GetMethodEntryPointIfExists(md);
    }

    private TargetCodePointer GetStableEntryPoint(MethodDesc md)
    {
        // TODO(cdac): _ASSERTE(!IsVersionableWithVtableSlotBackpatch());
        Debug.Assert(md.HasStableEntryPoint);

        return GetMethodEntryPointIfExists(md);
    }

    private TargetCodePointer GetMethodEntryPointIfExists(MethodDesc md)
    {
        if (md.HasNonVtableSlot)
        {
            TargetPointer pSlot = md.GetAddressOfNonVtableSlot();
            return _target.ReadCodePointer(pSlot);
        }

        TargetPointer methodTablePointer = md.MethodTable;
        TypeHandle typeHandle = GetTypeHandle(methodTablePointer);
        Debug.Assert(_methodTables[typeHandle.Address].IsCanonMT);
        TargetPointer addrOfSlot = GetAddressOfSlot(typeHandle, md.Slot);
        return _target.ReadCodePointer(addrOfSlot);
    }

    TargetPointer IRuntimeTypeSystem.GetGCStressCodeCopy(MethodDescHandle methodDesc)
    {
        MethodDesc md = _methodDescs[methodDesc.Address];
        if (md.GCCoverageInfo is TargetPointer gcCoverageInfoAddr && gcCoverageInfoAddr != TargetPointer.Null)
        {
            Target.TypeInfo gcCoverageInfoType = _target.GetTypeInfo(DataType.GCCoverageInfo);
            return gcCoverageInfoAddr + (ulong)gcCoverageInfoType.Fields["SavedCode"].Offset;
        }
        return TargetPointer.Null;
    }

    internal static OptimizationTier GetOptimizationTier(uint? optimizationTier)
    {
        return (OptimizationTier_1?)optimizationTier switch
        {
            OptimizationTier_1.OptimizationTier0 => OptimizationTier.OptimizationTier0,
            OptimizationTier_1.OptimizationTier1 => OptimizationTier.OptimizationTier1,
            OptimizationTier_1.OptimizationTier1OSR => OptimizationTier.OptimizationTier1OSR,
            OptimizationTier_1.OptimizationTierOptimized => OptimizationTier.OptimizationTierOptimized,
            OptimizationTier_1.OptimizationTier0Instrumented => OptimizationTier.OptimizationTier0Instrumented,
            OptimizationTier_1.OptimizationTier1Instrumented => OptimizationTier.OptimizationTier1Instrumented,
            _ => OptimizationTier.OptimizationTierUnknown,
        };
    }

    OptimizationTier IRuntimeTypeSystem.GetMethodDescOptimizationTier(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
        TargetPointer codeDataAddress = methodDesc.CodeData;
        if (codeDataAddress == TargetPointer.Null)
            return OptimizationTier.OptimizationTierUnknown;

        Data.MethodDescCodeData codeData = _target.ProcessedData.GetOrAdd<Data.MethodDescCodeData>(codeDataAddress);
        return GetOptimizationTier(codeData.OptimizationTier);
    }

    bool IRuntimeTypeSystem.IsEligibleForTieredCompilation(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
        return methodDesc.IsEligibleForTieredCompilation;
    }

    public bool IsAsyncThunkMethod(MethodDescHandle methodDescHandle)
    {
        MethodDesc md = _methodDescs[methodDescHandle.Address];
        if (!md.HasAsyncMethodData)
        {
            return false;
        }

        Data.AsyncMethodData asyncData = _target.ProcessedData.GetOrAdd<Data.AsyncMethodData>(md.GetAddressOfAsyncMethodData());
        return ((AsyncMethodFlags)asyncData.Flags).HasFlag(AsyncMethodFlags.Thunk);
    }

    public bool IsWrapperStub(MethodDescHandle methodDescHandle)
    {
        MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
        return IsWrapperStub(methodDesc);
    }

    private sealed class NonValidatedMethodTableQueries : MethodValidation.IMethodTableQueries
    {
        private readonly RuntimeTypeSystem_1 _rts;
        public NonValidatedMethodTableQueries(RuntimeTypeSystem_1 rts)
        {
            _rts = rts;
        }

        public bool SlotIsVtableSlot(TargetPointer methodTablePointer, uint slot)
        {
            return _rts.SlotIsVtableSlot(methodTablePointer, slot);
        }

        public TargetPointer GetAddressOfMethodTableSlot(TargetPointer methodTablePointer, uint slot)
        {
            return _rts.GetAddressOfMethodTableSlot(methodTablePointer, slot);
        }
    }

    // for the benefit of MethodValidation
    private TargetPointer GetAddressOfMethodTableSlot(TargetPointer methodTablePointer, uint slot)
    {
        TypeHandle typeHandle = GetTypeHandle(methodTablePointer);
        Debug.Assert(_methodTables[typeHandle.Address].IsCanonMT);
        TargetPointer addrOfSlot = GetAddressOfSlot(typeHandle, slot);
        return addrOfSlot;
    }

    private bool SlotIsVtableSlot(TargetPointer methodTablePointer, uint slot)
    {
        TypeHandle typeHandle = GetTypeHandle(methodTablePointer);
        return slot < GetNumVtableSlots(typeHandle);
    }
    TargetPointer IRuntimeTypeSystem.GetMTOfEnclosingClass(TargetPointer fieldDescPointer)
    {
        Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd<Data.FieldDesc>(fieldDescPointer);
        return fieldDesc.MTOfEnclosingClass;
    }

    uint IRuntimeTypeSystem.GetFieldDescMemberDef(TargetPointer fieldDescPointer)
    {
        Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd<Data.FieldDesc>(fieldDescPointer);
        return EcmaMetadataUtils.CreateFieldDef(fieldDesc.DWord1 & (uint)FieldDescFlags1.TokenMask);
    }

    bool IRuntimeTypeSystem.IsFieldDescThreadStatic(TargetPointer fieldDescPointer)
    {
        Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd<Data.FieldDesc>(fieldDescPointer);
        return (fieldDesc.DWord1 & (uint)FieldDescFlags1.IsThreadStatic) != 0;
    }

    private bool IsFieldDescRVA(TargetPointer fieldDescPointer)
    {
        Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd<Data.FieldDesc>(fieldDescPointer);
        return (fieldDesc.DWord1 & (uint)FieldDescFlags1.IsRVA) != 0;
    }

    bool IRuntimeTypeSystem.IsFieldDescStatic(TargetPointer fieldDescPointer)
    {
        Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd<Data.FieldDesc>(fieldDescPointer);
        return (fieldDesc.DWord1 & (uint)FieldDescFlags1.IsStatic) != 0;
    }

    CorElementType IRuntimeTypeSystem.GetFieldDescType(TargetPointer fieldDescPointer)
    {
        Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd<Data.FieldDesc>(fieldDescPointer);
        // 27 is the number of bits that the type is shifted left. if you change the enum, please change this too.
        return (CorElementType)((fieldDesc.DWord2 & (uint)FieldDescFlags2.TypeMask) >> TYPE_MASK_OFFSET);
    }

    uint IRuntimeTypeSystem.GetFieldDescOffset(TargetPointer fieldDescPointer, FieldDefinition fieldDef)
    {
        Data.FieldDesc fieldDesc = _target.ProcessedData.GetOrAdd<Data.FieldDesc>(fieldDescPointer);
        if (fieldDesc.DWord2 == _target.ReadGlobal<uint>(Constants.Globals.FieldOffsetBigRVA))
        {
            return (uint)fieldDef.GetRelativeVirtualAddress();
        }
        return fieldDesc.DWord2 & (uint)FieldDescFlags2.OffsetMask;
    }

    TargetPointer IRuntimeTypeSystem.GetFieldDescByName(TypeHandle typeHandle, string fieldName)
    {
        if (!typeHandle.IsMethodTable())
            return TargetPointer.Null;

        TargetPointer modulePtr = GetModule(typeHandle);
        if (modulePtr == TargetPointer.Null)
            return TargetPointer.Null;

        uint typeDefToken = GetTypeDefToken(typeHandle);
        if (typeDefToken == 0)
            return TargetPointer.Null;

        EntityHandle entityHandle = MetadataTokens.EntityHandle((int)typeDefToken);
        if (entityHandle.Kind != HandleKind.TypeDefinition)
            return TargetPointer.Null;

        TypeDefinitionHandle typeDefHandle = (TypeDefinitionHandle)entityHandle;

        ILoader loader = _target.Contracts.Loader;
        ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr);
        MetadataReader? md = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle);
        if (md is null)
            return TargetPointer.Null;

        TargetPointer fieldDefToDescMap = loader.GetLookupTables(moduleHandle).FieldDefToDesc;
        foreach (FieldDefinitionHandle fieldDefHandle in md.GetTypeDefinition(typeDefHandle).GetFields())
        {
            FieldDefinition fieldDef = md.GetFieldDefinition(fieldDefHandle);
            if (md.GetString(fieldDef.Name) == fieldName)
            {
                uint fieldDefToken = (uint)MetadataTokens.GetToken(fieldDefHandle);
                TargetPointer fieldDescPtr = loader.GetModuleLookupMapElement(fieldDefToDescMap, fieldDefToken, out _);
                return fieldDescPtr;
            }
        }

        return TargetPointer.Null;
    }

    private TargetPointer GetStaticAddressHandle(TargetPointer @base, uint offset, bool isRVA, TargetPointer fieldDescPointer, ModuleHandle moduleHandle)
    {
        if (isRVA)
        {
            ILoader loader = _target.Contracts.Loader;
            if (offset == _target.ReadGlobal<uint>(Constants.Globals.FieldOffsetDynamicRVA))
            {
                return loader.GetDynamicIL(moduleHandle, ((IRuntimeTypeSystem)this).GetFieldDescMemberDef(fieldDescPointer));
            }
            TargetPointer peAssembly = loader.GetPEAssembly(moduleHandle);
            return loader.GetFieldAddressFromRva(peAssembly, (int)offset);
        }
        return new TargetPointer(@base + offset);
    }

    private TargetPointer GetFieldDescStaticOrThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer? thread = null)
    {
        TargetPointer enclosingMT = ((IRuntimeTypeSystem)this).GetMTOfEnclosingClass(fieldDescPointer);
        TypeHandle ctx = GetTypeHandle(enclosingMT);
        TargetPointer modulePtr = GetModule(ctx);
        ILoader loader = _target.Contracts.Loader;
        ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePtr);
        CorElementType type = ((IRuntimeTypeSystem)this).GetFieldDescType(fieldDescPointer);
        TargetPointer @base;
        if (type == CorElementType.Class || type == CorElementType.ValueType)
        {
            if (thread.HasValue)
            {
                @base = GetGCThreadStaticsBasePointer(ctx, thread.Value);
            }
            else
            {
                @base = GetGCStaticsBasePointer(ctx);
            }
        }
        else
        {
            if (thread.HasValue)
            {
                @base = GetNonGCThreadStaticsBasePointer(ctx, thread.Value);
            }
            else
            {
                @base = GetNonGCStaticsBasePointer(ctx);
            }
        }

        if (@base == TargetPointer.Null)
            return TargetPointer.Null;

        MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!;
        uint token = ((IRuntimeTypeSystem)this).GetFieldDescMemberDef(fieldDescPointer);
        FieldDefinitionHandle fieldHandle = (FieldDefinitionHandle)MetadataTokens.Handle((int)token);
        FieldDefinition fieldDef = mdReader.GetFieldDefinition(fieldHandle);

        uint offset = ((IRuntimeTypeSystem)this).GetFieldDescOffset(fieldDescPointer, fieldDef);
        bool isRVA = IsFieldDescRVA(fieldDescPointer);
        TargetPointer handleAddr = GetStaticAddressHandle(@base, offset, isRVA, fieldDescPointer, moduleHandle);
        if (type == CorElementType.ValueType && !isRVA)
        {
            TargetPointer objRef = _target.ReadPointer(handleAddr);
            Data.Object obj = _target.ProcessedData.GetOrAdd<Data.Object>(objRef);
            return obj.Data;
        }
        return handleAddr;
    }

    TargetPointer IRuntimeTypeSystem.GetFieldDescStaticAddress(TargetPointer fieldDescPointer) => GetFieldDescStaticOrThreadStaticAddress(fieldDescPointer);

    TargetPointer IRuntimeTypeSystem.GetFieldDescThreadStaticAddress(TargetPointer fieldDescPointer, TargetPointer thread) => GetFieldDescStaticOrThreadStaticAddress(fieldDescPointer, thread);

    void IRuntimeTypeSystem.GetCoreLibFieldDescAndDef(string @namespace, string typeName, string fieldName, out TargetPointer fieldDescAddr, out FieldDefinition fieldDef)
    {
        ILoader loader = _target.Contracts.Loader;
        TargetPointer systemAssembly = loader.GetSystemAssembly();
        ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(systemAssembly);
        IRuntimeTypeSystem rts = (IRuntimeTypeSystem)this;
        TypeHandle th = rts.GetTypeByNameAndModule(typeName, @namespace, moduleHandle);
        fieldDescAddr = rts.GetFieldDescByName(th, fieldName);
        uint token = rts.GetFieldDescMemberDef(fieldDescAddr);
        FieldDefinitionHandle fieldHandle = (FieldDefinitionHandle)MetadataTokens.Handle((int)token);
        MetadataReader mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle)!;
        fieldDef = mdReader.GetFieldDefinition(fieldHandle);
    }
}