File: src\System\Runtime\CompilerServices\CastHelpers.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
 
namespace System.Runtime.CompilerServices
{
    [StackTraceHidden]
    [DebuggerStepThrough]
    internal static unsafe partial class CastHelpers
    {
        // In coreclr the table is allocated and written to on the native side.
        internal static int[]? s_table;
 
        [LibraryImport(RuntimeHelpers.QCall)]
        internal static partial void ThrowInvalidCastException(void* fromTypeHnd, void* toTypeHnd);
 
        [DoesNotReturn]
        internal static void ThrowInvalidCastException(object fromType, void* toTypeHnd)
        {
            ThrowInvalidCastException(RuntimeHelpers.GetMethodTable(fromType), toTypeHnd);
            throw null!; // Provide hint to the inliner that this method does not return
        }
 
        [LibraryImport(RuntimeHelpers.QCall)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static partial bool IsInstanceOf_NoCacheLookup(void *toTypeHnd, [MarshalAs(UnmanagedType.Bool)] bool throwCastException, ObjectHandleOnStack obj);
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static object? IsInstanceOfAny_NoCacheLookup(void* toTypeHnd, object obj)
        {
            if (IsInstanceOf_NoCacheLookup(toTypeHnd, false, ObjectHandleOnStack.Create(ref obj)))
            {
                return obj;
            }
            return null;
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static object ChkCastAny_NoCacheLookup(void* toTypeHnd, object obj)
        {
            IsInstanceOf_NoCacheLookup(toTypeHnd, true, ObjectHandleOnStack.Create(ref obj));
            return obj;
        }
 
        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern void WriteBarrier(ref object? dst, object? obj);
 
        // IsInstanceOf test used for unusual cases (naked type parameters, variant generic types)
        // Unlike the IsInstanceOfInterface and IsInstanceOfClass functions,
        // this test must deal with all kinds of type tests
        [DebuggerHidden]
        internal static object? IsInstanceOfAny(void* toTypeHnd, object? obj)
        {
            if (obj != null)
            {
                void* mt = RuntimeHelpers.GetMethodTable(obj);
                if (mt != toTypeHnd)
                {
                    CastResult result = CastCache.TryGet(s_table!, (nuint)mt, (nuint)toTypeHnd);
                    if (result == CastResult.CanCast)
                    {
                        // do nothing
                    }
                    else if (result == CastResult.CannotCast)
                    {
                        obj = null;
                    }
                    else
                    {
                        goto slowPath;
                    }
                }
            }
 
            return obj;
 
        slowPath:
            // fall through to the slow helper
            return IsInstanceOfAny_NoCacheLookup(toTypeHnd, obj);
        }
 
        [DebuggerHidden]
        private static object? IsInstanceOfInterface(void* toTypeHnd, object? obj)
        {
            const int unrollSize = 4;
 
            if (obj != null)
            {
                MethodTable* mt = RuntimeHelpers.GetMethodTable(obj);
                nint interfaceCount = mt->InterfaceCount;
                if (interfaceCount != 0)
                {
                    MethodTable** interfaceMap = mt->InterfaceMap;
                    if (interfaceCount < unrollSize)
                    {
                        // If not enough for unrolled, jmp straight to small loop
                        // as we already know there is one or more interfaces so don't need to check again.
                        goto few;
                    }
 
                    do
                    {
                        if (interfaceMap[0] == toTypeHnd ||
                            interfaceMap[1] == toTypeHnd ||
                            interfaceMap[2] == toTypeHnd ||
                            interfaceMap[3] == toTypeHnd)
                        {
                            goto done;
                        }
 
                        interfaceMap += unrollSize;
                        interfaceCount -= unrollSize;
                    } while (interfaceCount >= unrollSize);
 
                    if (interfaceCount == 0)
                    {
                        // If none remaining, skip the short loop
                        goto extra;
                    }
 
                few:
                    do
                    {
                        if (interfaceMap[0] == toTypeHnd)
                        {
                            goto done;
                        }
 
                        // Assign next offset
                        interfaceMap++;
                        interfaceCount--;
                    } while (interfaceCount > 0);
                }
 
            extra:
                if (mt->NonTrivialInterfaceCast)
                {
                    goto slowPath;
                }
 
                obj = null;
            }
 
        done:
            return obj;
 
        slowPath:
            return IsInstance_Helper(toTypeHnd, obj);
        }
 
        [DebuggerHidden]
        private static object? IsInstanceOfClass(void* toTypeHnd, object? obj)
        {
            if (obj == null || RuntimeHelpers.GetMethodTable(obj) == toTypeHnd)
                return obj;
 
            MethodTable* mt = RuntimeHelpers.GetMethodTable(obj)->ParentMethodTable;
            for (; ; )
            {
                if (mt == toTypeHnd)
                    goto done;
 
                if (mt == null)
                    break;
 
                mt = mt->ParentMethodTable;
                if (mt == toTypeHnd)
                    goto done;
 
                if (mt == null)
                    break;
 
                mt = mt->ParentMethodTable;
                if (mt == toTypeHnd)
                    goto done;
 
                if (mt == null)
                    break;
 
                mt = mt->ParentMethodTable;
                if (mt == toTypeHnd)
                    goto done;
 
                if (mt == null)
                    break;
 
                mt = mt->ParentMethodTable;
            }
 
#if FEATURE_TYPEEQUIVALENCE
            // this helper is not supposed to be used with type-equivalent "to" type.
            Debug.Assert(!((MethodTable*)toTypeHnd)->HasTypeEquivalence);
#endif // FEATURE_TYPEEQUIVALENCE
 
            obj = null;
 
        done:
            return obj;
        }
 
        [DebuggerHidden]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static object? IsInstance_Helper(void* toTypeHnd, object obj)
        {
            CastResult result = CastCache.TryGet(s_table!, (nuint)RuntimeHelpers.GetMethodTable(obj), (nuint)toTypeHnd);
            if (result == CastResult.CanCast)
            {
                return obj;
            }
            else if (result == CastResult.CannotCast)
            {
                return null;
            }
 
            // fall through to the slow helper
            return IsInstanceOfAny_NoCacheLookup(toTypeHnd, obj);
        }
 
        // ChkCast test used for unusual cases (naked type parameters, variant generic types)
        // Unlike the ChkCastInterface and ChkCastClass functions,
        // this test must deal with all kinds of type tests
        [DebuggerHidden]
        internal static object? ChkCastAny(void* toTypeHnd, object? obj)
        {
            CastResult result;
 
            if (obj != null)
            {
                void* mt = RuntimeHelpers.GetMethodTable(obj);
                if (mt != toTypeHnd)
                {
                    result = CastCache.TryGet(s_table!, (nuint)mt, (nuint)toTypeHnd);
                    if (result != CastResult.CanCast)
                    {
                        goto slowPath;
                    }
                }
            }
 
            return obj;
 
        slowPath:
            // fall through to the slow helper
            object objRet = ChkCastAny_NoCacheLookup(toTypeHnd, obj);
            // Make sure that the fast helper have not lied
            Debug.Assert(result != CastResult.CannotCast);
            return objRet;
        }
 
        [DebuggerHidden]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static object? ChkCast_Helper(void* toTypeHnd, object obj)
        {
            CastResult result = CastCache.TryGet(s_table!, (nuint)RuntimeHelpers.GetMethodTable(obj), (nuint)toTypeHnd);
            if (result == CastResult.CanCast)
            {
                return obj;
            }
 
            // fall through to the slow helper
            return ChkCastAny_NoCacheLookup(toTypeHnd, obj);
        }
 
        [DebuggerHidden]
        private static object? ChkCastInterface(void* toTypeHnd, object? obj)
        {
            const int unrollSize = 4;
 
            if (obj != null)
            {
                MethodTable* mt = RuntimeHelpers.GetMethodTable(obj);
                nint interfaceCount = mt->InterfaceCount;
                if (interfaceCount == 0)
                {
                    goto slowPath;
                }
 
                MethodTable** interfaceMap = mt->InterfaceMap;
                if (interfaceCount < unrollSize)
                {
                    // If not enough for unrolled, jmp straight to small loop
                    // as we already know there is one or more interfaces so don't need to check again.
                    goto few;
                }
 
                do
                {
                    if (interfaceMap[0] == toTypeHnd ||
                        interfaceMap[1] == toTypeHnd ||
                        interfaceMap[2] == toTypeHnd ||
                        interfaceMap[3] == toTypeHnd)
                    {
                        goto done;
                    }
 
                    // Assign next offset
                    interfaceMap += unrollSize;
                    interfaceCount -= unrollSize;
                } while (interfaceCount >= unrollSize);
 
                if (interfaceCount == 0)
                {
                    // If none remaining, skip the short loop
                    goto slowPath;
                }
 
            few:
                do
                {
                    if (interfaceMap[0] == toTypeHnd)
                    {
                        goto done;
                    }
 
                    // Assign next offset
                    interfaceMap++;
                    interfaceCount--;
                } while (interfaceCount > 0);
 
                goto slowPath;
            }
 
        done:
            return obj;
 
        slowPath:
            return ChkCast_Helper(toTypeHnd, obj);
        }
 
        [DebuggerHidden]
        private static object? ChkCastClass(void* toTypeHnd, object? obj)
        {
            if (obj == null || RuntimeHelpers.GetMethodTable(obj) == toTypeHnd)
            {
                return obj;
            }
 
            return ChkCastClassSpecial(toTypeHnd, obj);
        }
 
        // Optimized helper for classes. Assumes that the trivial cases
        // has been taken care of by the inlined check
        [DebuggerHidden]
        private static object? ChkCastClassSpecial(void* toTypeHnd, object obj)
        {
            MethodTable* mt = RuntimeHelpers.GetMethodTable(obj);
            Debug.Assert(mt != toTypeHnd, "The check for the trivial cases should be inlined by the JIT");
 
            for (; ; )
            {
                mt = mt->ParentMethodTable;
                if (mt == toTypeHnd)
                    goto done;
 
                if (mt == null)
                    break;
 
                mt = mt->ParentMethodTable;
                if (mt == toTypeHnd)
                    goto done;
 
                if (mt == null)
                    break;
 
                mt = mt->ParentMethodTable;
                if (mt == toTypeHnd)
                    goto done;
 
                if (mt == null)
                    break;
 
                mt = mt->ParentMethodTable;
                if (mt == toTypeHnd)
                    goto done;
 
                if (mt == null)
                    break;
            }
 
            goto slowPath;
 
        done:
            return obj;
 
        slowPath:
            return ChkCast_Helper(toTypeHnd, obj);
        }
 
        [DebuggerHidden]
        private static ref byte Unbox(MethodTable* toTypeHnd, object obj)
        {
            // This will throw NullReferenceException if obj is null.
            if (RuntimeHelpers.GetMethodTable(obj) == toTypeHnd)
                return ref obj.GetRawData();
 
            return ref Unbox_Helper(toTypeHnd, obj);
        }
 
        [DebuggerHidden]
        private static void ThrowIndexOutOfRangeException()
        {
            throw new IndexOutOfRangeException();
        }
 
        [DebuggerHidden]
        private static void ThrowArrayMismatchException()
        {
            throw new ArrayTypeMismatchException();
        }
 
        [DebuggerHidden]
        private static ref object? LdelemaRef(object?[] array, nint index, void* type)
        {
            // This will throw NullReferenceException if array is null.
            if ((nuint)index >= (uint)array.Length)
                ThrowIndexOutOfRangeException();
 
            Debug.Assert(index >= 0);
            ref object? element = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), index);
            void* elementType = RuntimeHelpers.GetMethodTable(array)->ElementType;
 
            if (elementType != type)
                ThrowArrayMismatchException();
 
            return ref element;
        }
 
        [DebuggerHidden]
        private static void StelemRef(object?[] array, nint index, object? obj)
        {
            // This will throw NullReferenceException if array is null.
            if ((nuint)index >= (uint)array.Length)
                ThrowIndexOutOfRangeException();
 
            Debug.Assert(index >= 0);
            ref object? element = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), index);
            void* elementType = RuntimeHelpers.GetMethodTable(array)->ElementType;
 
            if (obj == null)
                goto assigningNull;
 
            if (elementType != RuntimeHelpers.GetMethodTable(obj))
                goto notExactMatch;
 
            doWrite:
                WriteBarrier(ref element, obj);
                return;
 
            assigningNull:
                element = null;
                return;
 
            notExactMatch:
                if (array.GetType() == typeof(object[]))
                    goto doWrite;
 
            StelemRef_Helper(ref element, elementType, obj);
        }
 
        [DebuggerHidden]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void StelemRef_Helper(ref object? element, void* elementType, object obj)
        {
            CastResult result = CastCache.TryGet(s_table!, (nuint)RuntimeHelpers.GetMethodTable(obj), (nuint)elementType);
            if (result == CastResult.CanCast)
            {
                WriteBarrier(ref element, obj);
                return;
            }
 
            StelemRef_Helper_NoCacheLookup(ref element, elementType, obj);
        }
 
        [DebuggerHidden]
        private static void StelemRef_Helper_NoCacheLookup(ref object? element, void* elementType, object obj)
        {
            Debug.Assert(obj != null);
 
            object? obj2 = IsInstanceOfAny_NoCacheLookup(elementType, obj);
            if (obj2 == null)
            {
                ThrowArrayMismatchException();
            }
 
            WriteBarrier(ref element, obj2);
        }
 
        [DebuggerHidden]
        private static unsafe void ArrayTypeCheck(object obj, Array array)
        {
            Debug.Assert(obj != null);
 
            void* elementType = RuntimeHelpers.GetMethodTable(array)->ElementType;
            Debug.Assert(elementType != RuntimeHelpers.GetMethodTable(obj)); // Should be handled by caller
 
            CastResult result = CastCache.TryGet(s_table!, (nuint)RuntimeHelpers.GetMethodTable(obj), (nuint)elementType);
            if (result == CastResult.CanCast)
            {
                return;
            }
 
            ArrayTypeCheck_Helper(obj, elementType);
        }
 
        [DebuggerHidden]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static unsafe void ArrayTypeCheck_Helper(object obj, void* elementType)
        {
            Debug.Assert(obj != null);
 
            if (IsInstanceOfAny_NoCacheLookup(elementType, obj) == null)
            {
                ThrowArrayMismatchException();
            }
        }
 
        // Helpers for Unboxing
#if FEATURE_TYPEEQUIVALENCE
        [DebuggerHidden]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static bool AreTypesEquivalent(MethodTable* pMTa, MethodTable* pMTb)
        {
            if (pMTa == pMTb)
            {
                return true;
            }
 
            if (!pMTa->HasTypeEquivalence || !pMTb->HasTypeEquivalence)
            {
                return false;
            }
 
            return RuntimeHelpers.AreTypesEquivalent(pMTa, pMTb);
        }
#endif // FEATURE_TYPEEQUIVALENCE
 
        [DebuggerHidden]
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static bool IsNullableForType(MethodTable* typeMT, MethodTable* boxedMT)
        {
            if (!typeMT->IsNullable)
            {
                return false;
            }
 
            // Normally getting the first generic argument involves checking the PerInstInfo to get the count of generic dictionaries
            // in the hierarchy, and then doing a bit of math to find the right dictionary, but since we know this is nullable
            // we can do a simple double deference to do the same thing.
            Debug.Assert(typeMT->InstantiationArg0() == **typeMT->PerInstInfo);
            MethodTable *pMTNullableArg = **typeMT->PerInstInfo;
            if (pMTNullableArg == boxedMT)
            {
                return true;
            }
            else
            {
#if FEATURE_TYPEEQUIVALENCE
                return AreTypesEquivalent(pMTNullableArg, boxedMT);
#else
                return false;
#endif // FEATURE_TYPEEQUIVALENCE
            }
        }
 
        [DebuggerHidden]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void Unbox_Nullable_NotIsNullableForType(ref byte destPtr, MethodTable* typeMT, object obj)
        {
            // Also allow true nullables to be unboxed normally.
            // This should not happen normally, but can happen in debugger scenarios.
            if (typeMT != RuntimeHelpers.GetMethodTable(obj))
            {
                CastHelpers.ThrowInvalidCastException(obj, typeMT);
            }
            Buffer.BulkMoveWithWriteBarrier(ref destPtr, ref RuntimeHelpers.GetRawData(obj), typeMT->GetNullableNumInstanceFieldBytes());
        }
 
        [DebuggerHidden]
        internal static void Unbox_Nullable(ref byte destPtr, MethodTable* typeMT, object? obj)
        {
            if (obj == null)
            {
                if (!typeMT->ContainsGCPointers)
                {
                    SpanHelpers.ClearWithoutReferences(ref destPtr, typeMT->GetNullableNumInstanceFieldBytes());
                }
                else
                {
                    SpanHelpers.ClearWithReferences(ref Unsafe.As<byte, IntPtr>(ref destPtr), typeMT->GetNumInstanceFieldBytesIfContainsGCPointers() / (nuint)sizeof(IntPtr));
                }
            }
            else
            {
                if (!IsNullableForType(typeMT, RuntimeHelpers.GetMethodTable(obj)))
                {
                    Unbox_Nullable_NotIsNullableForType(ref destPtr, typeMT, obj);
                }
                else
                {
                    Unsafe.As<byte, bool>(ref destPtr) = true;
                    ref byte dst = ref Unsafe.Add(ref destPtr, typeMT->NullableValueAddrOffset);
                    uint valueSize = typeMT->NullableValueSize;
                    ref byte src = ref RuntimeHelpers.GetRawData(obj);
                    if (typeMT->ContainsGCPointers)
                        Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, valueSize);
                    else
                        SpanHelpers.Memmove(ref dst, ref src, valueSize);
                }
            }
        }
 
        [DebuggerHidden]
        internal static object? ReboxFromNullable(MethodTable* srcMT, object src)
        {
            Debug.Assert(srcMT->IsNullable);
 
            ref byte nullableData = ref src.GetRawData();
 
            // If 'hasValue' is false, return null.
            if (!Unsafe.As<byte, bool>(ref nullableData))
                return null;
 
            // Allocate a new instance of the T in Nullable<T>.
            MethodTable* dstMT = srcMT->InstantiationArg0();
            object dst = RuntimeTypeHandle.InternalAlloc(dstMT);
 
            // Copy data from the Nullable<T>.
            ref byte srcData = ref Unsafe.Add(ref nullableData, srcMT->NullableValueAddrOffset);
            ref byte dstData = ref RuntimeHelpers.GetRawData(dst);
            if (dstMT->ContainsGCPointers)
                Buffer.BulkMoveWithWriteBarrier(ref dstData, ref srcData, dstMT->GetNumInstanceFieldBytesIfContainsGCPointers());
            else
                SpanHelpers.Memmove(ref dstData, ref srcData, dstMT->GetNumInstanceFieldBytes());
 
            return dst;
        }
 
        [DebuggerHidden]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static ref byte Unbox_Helper(MethodTable* pMT1, object obj)
        {
            // must be a value type
            Debug.Assert(pMT1->IsValueType);
 
            MethodTable* pMT2 = RuntimeHelpers.GetMethodTable(obj);
            if ((!pMT1->IsPrimitive || !pMT2->IsPrimitive ||
                pMT1->GetPrimitiveCorElementType() != pMT2->GetPrimitiveCorElementType())
#if FEATURE_TYPEEQUIVALENCE
                && !AreTypesEquivalent(pMT1, pMT2)
#endif // FEATURE_TYPEEQUIVALENCE
                )
            {
                CastHelpers.ThrowInvalidCastException(obj, pMT1);
            }
 
            return ref RuntimeHelpers.GetRawData(obj);
        }
 
        [DebuggerHidden]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void Unbox_TypeTest_Helper(MethodTable *pMT1, MethodTable *pMT2)
        {
            if ((!pMT1->IsPrimitive || !pMT2->IsPrimitive ||
                pMT1->GetPrimitiveCorElementType() != pMT2->GetPrimitiveCorElementType())
#if FEATURE_TYPEEQUIVALENCE
                && !AreTypesEquivalent(pMT1, pMT2)
#endif // FEATURE_TYPEEQUIVALENCE
                )
            {
                CastHelpers.ThrowInvalidCastException(pMT1, pMT2);
            }
        }
 
        [DebuggerHidden]
        private static void Unbox_TypeTest(MethodTable *pMT1, MethodTable *pMT2)
        {
            if (pMT1 == pMT2)
                return;
            else
                Unbox_TypeTest_Helper(pMT1, pMT2);
        }
    }
}