File: parent\parent\Common\Internal\Runtime\EETypeBuilderHelpers.cs
Web Access
Project: ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// 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.Diagnostics;

using Internal.TypeSystem;

namespace Internal.Runtime
{
    internal static class EETypeBuilderHelpers
    {
        private static EETypeElementType ComputeEETypeElementType(TypeDesc type)
        {
            // Enums are represented as their underlying type
            type = type.UnderlyingType;

            if (type.IsWellKnownType(WellKnownType.Array))
            {
                // SystemArray is a special EETypeElementType that doesn't exist in TypeFlags
                return EETypeElementType.SystemArray;
            }
            else
            {
                // The rest of TypeFlags should be directly castable to EETypeElementType.
                // Spot check the enums match.
                Debug.Assert((int)TypeFlags.Void == (int)EETypeElementType.Void);
                Debug.Assert((int)TypeFlags.IntPtr == (int)EETypeElementType.IntPtr);
                Debug.Assert((int)TypeFlags.Single == (int)EETypeElementType.Single);
                Debug.Assert((int)TypeFlags.UInt32 == (int)EETypeElementType.UInt32);
                Debug.Assert((int)TypeFlags.Pointer == (int)EETypeElementType.Pointer);
                Debug.Assert((int)TypeFlags.Array == (int)EETypeElementType.Array);

                EETypeElementType elementType = (EETypeElementType)type.Category;

                // Would be surprising to get these here though.
                Debug.Assert(elementType != EETypeElementType.SystemArray);
                Debug.Assert(elementType <= EETypeElementType.FunctionPointer);

                return elementType;

            }
        }

        public static uint ComputeFlags(TypeDesc type)
        {
            uint flags;
            if (type.IsParameterizedType)
                flags = (uint)EETypeKind.ParameterizedEEType;
            else if (type.IsFunctionPointer)
                flags = (uint)EETypeKind.FunctionPointerEEType;
            else
                flags = (uint)EETypeKind.CanonicalEEType;

            // 5 bits near the top of flags are used to convey enum underlying type, primitive type, or mark the type as being System.Array
            EETypeElementType elementType = ComputeEETypeElementType(type);
            flags |= ((uint)elementType << (byte)EETypeFlags.ElementTypeShift);

            if (type.IsArray || type.IsString)
            {
                flags |= (uint)EETypeFlags.HasComponentSizeFlag;
            }

            if (type.HasVariance)
            {
                flags |= (uint)EETypeFlags.GenericVarianceFlag;
            }

            if (type.IsGenericDefinition)
            {
                flags |= (uint)EETypeKind.GenericTypeDefEEType;

                // Generic type definition EETypes don't set the other flags.
                return flags;
            }

            if (type.HasFinalizer)
            {
                flags |= (uint)EETypeFlags.HasFinalizerFlag;
            }

            if (type.IsDefType
                && !type.IsCanonicalSubtype(CanonicalFormKind.Universal)
                && ((DefType)type).ContainsGCPointers)
            {
                flags |= (uint)EETypeFlags.HasPointersFlag;
            }
            else if (type.IsArray && !type.IsCanonicalSubtype(CanonicalFormKind.Universal))
            {
                var arrayElementType = ((ArrayType)type).ElementType;
                if ((arrayElementType.IsValueType && ((DefType)arrayElementType).ContainsGCPointers) || arrayElementType.IsGCPointer)
                {
                    flags |= (uint)EETypeFlags.HasPointersFlag;
                }
            }

            if (type.HasInstantiation)
            {
                flags |= (uint)EETypeFlags.IsGenericFlag;
            }

            return flags;
        }

        public static int GetMinimumObjectSize(TypeSystemContext typeSystemContext)
            => typeSystemContext.Target.PointerSize * 3;

        public static int ComputeBaseSize(TypeDesc type)
        {
            int pointerSize = type.Context.Target.PointerSize;
            int objectSize;

            if (type.IsInterface)
            {
                // Interfaces don't live on the GC heap. Don't bother computing a number.
                // Zero compresses better than any useless number we would come up with.
                return 0;
            }
            else if (type.IsDefType)
            {
                LayoutInt instanceByteCount = ((DefType)type).InstanceByteCount;

                if (instanceByteCount.IsIndeterminate)
                {
                    // Some value must be put in, but the specific value doesn't matter as it
                    // isn't used for specific instantiations, and the universal canon MethodTable
                    // is never associated with an allocated object.
                    objectSize = pointerSize;
                }
                else
                {
                    objectSize = pointerSize +
                        ((DefType)type).InstanceByteCount.AsInt; // +pointerSize for SyncBlock
                }

                if (type.IsValueType)
                    objectSize += pointerSize; // + EETypePtr field inherited from System.Object
            }
            else if (type.IsArray)
            {
                objectSize = 3 * pointerSize; // SyncBlock + EETypePtr + Length
                if (type.IsMdArray)
                    objectSize +=
                        2 * sizeof(int) * ((ArrayType)type).Rank;
            }
            else if (type.IsPointer)
            {
                // These never get boxed and don't have a base size. Use a sentinel value recognized by the runtime.
                return ParameterizedTypeShapeConstants.Pointer;
            }
            else if (type.IsByRef)
            {
                // These never get boxed and don't have a base size. Use a sentinel value recognized by the runtime.
                return ParameterizedTypeShapeConstants.ByRef;
            }
            else if (type.IsFunctionPointer)
            {
                // These never get boxed and don't have a base size. We store the 'unmanaged' flag and number of parameters.
                MethodSignature sig = ((FunctionPointerType)type).Signature;
                return (sig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) switch
                {
                    0 => sig.Length,
                    _ => sig.Length | unchecked((int)FunctionPointerFlags.IsUnmanaged),
                };
            }
            else
                throw new NotImplementedException();

            objectSize = AlignmentHelper.AlignUp(objectSize, pointerSize);
            objectSize = Math.Max(GetMinimumObjectSize(type.Context), objectSize);

            if (type.IsString)
            {
                // If this is a string, throw away objectSize we computed so far. Strings are special.
                // SyncBlock + EETypePtr + length + firstChar
                objectSize = 2 * pointerSize +
                    sizeof(int) +
                    StringComponentSize.Value;
            }

            return objectSize;
        }

        public static ushort ComputeFlagsEx(TypeDesc type)
        {
            ushort flagsEx = 0;

            if (type is MetadataType mdType &&
                            mdType.Module == mdType.Context.SystemModule &&
                            (mdType.Name == "WeakReference"u8 || mdType.Name == "WeakReference`1"u8) &&
                            mdType.Namespace == "System"u8)
            {
                flagsEx |= (ushort)EETypeFlagsEx.HasEagerFinalizerFlag;
            }

            if (HasCriticalFinalizer(type))
            {
                flagsEx |= (ushort)EETypeFlagsEx.HasCriticalFinalizerFlag;
            }

            if (type.Context.Target.IsApplePlatform && IsTrackedReferenceWithFinalizer(type))
            {
                flagsEx |= (ushort)EETypeFlagsEx.IsTrackedReferenceWithFinalizerFlag;
            }

            if (type.IsIDynamicInterfaceCastable)
            {
                flagsEx |= (ushort)EETypeFlagsEx.IDynamicInterfaceCastableFlag;
            }

            if (type.IsByRefLike)
            {
                flagsEx |= (ushort)EETypeFlagsEx.IsByRefLikeFlag;
            }

            if (type.RequiresAlign8())
            {
                flagsEx |= (ushort)EETypeFlagsEx.RequiresAlign8Flag;
            }

            if (type.IsValueType && type != type.Context.UniversalCanonType)
            {
                int numInstanceFieldBytes = ((DefType)type).InstanceByteCountUnaligned.AsInt;

                // Value types should have at least 1 byte of size
                Debug.Assert(numInstanceFieldBytes >= 1);

                // The size of value types doesn't include the MethodTable pointer.  We need to add this so that
                // the number of instance field bytes consistently represents the boxed size.
                numInstanceFieldBytes += type.Context.Target.PointerSize;

                // For unboxing to work correctly and for supporting dynamic type loading for derived types we need
                // to record the actual size of the fields of a type without any padding for GC heap allocation (since
                // we can unbox into locals or arrays where this padding is not used, and because field layout for derived
                // types is effected by the unaligned base size). We don't want to store this information for all EETypes
                // since it's only relevant for value types, so it's added as an optional field. It's
                // also enough to simply store the size of the padding which cuts down our storage requirements.

                int valueTypeFieldPadding = (ComputeBaseSize(type) - type.Context.Target.PointerSize) - numInstanceFieldBytes;
                Debug.Assert(int.TrailingZeroCount((int)EETypeFlagsEx.ValueTypeFieldPaddingMask) == ValueTypeFieldPaddingConsts.Shift);
                Debug.Assert((valueTypeFieldPadding & ((int)EETypeFlagsEx.ValueTypeFieldPaddingMask >> ValueTypeFieldPaddingConsts.Shift)) == valueTypeFieldPadding);
                flagsEx |= (ushort)(valueTypeFieldPadding << ValueTypeFieldPaddingConsts.Shift);
            }

            if (type.IsNullable)
            {
                FieldDesc field = type.GetField("value"u8);

                int nullableValueOffset = field.Offset.AsInt;

                // In the definition of Nullable<T>, the first field should be the boolean representing "hasValue"
                Debug.Assert(nullableValueOffset > 0);

                // The field is offset due to alignment. This should be a power of two.
                Debug.Assert((nullableValueOffset & (nullableValueOffset - 1)) == 0);

                int log2nullableOffset = int.TrailingZeroCount(nullableValueOffset);

                Debug.Assert(int.TrailingZeroCount((int)EETypeFlagsEx.NullableValueOffsetMask) == NullableValueOffsetConsts.Shift);
                Debug.Assert((log2nullableOffset & ((int)EETypeFlagsEx.NullableValueOffsetMask >> NullableValueOffsetConsts.Shift)) == log2nullableOffset);
                flagsEx |= (ushort)(log2nullableOffset << NullableValueOffsetConsts.Shift);
            }

            return flagsEx;
        }

        private static bool HasCriticalFinalizer(TypeDesc type)
        {
            do
            {
                if (!type.HasFinalizer)
                    return false;

                if (type is MetadataType mdType &&
                            mdType.Module == mdType.Context.SystemModule &&
                            mdType.Name == "CriticalFinalizerObject"u8 &&
                            mdType.Namespace == "System.Runtime.ConstrainedExecution"u8)
                    return true;

                type = type.BaseType;
            }
            while (type != null);

            return false;
        }

        private static bool IsTrackedReferenceWithFinalizer(TypeDesc type)
        {
            do
            {
                if (!type.HasFinalizer)
                    return false;

                if (((MetadataType)type).HasCustomAttribute("System.Runtime.InteropServices.ObjectiveC", "ObjectiveCTrackedTypeAttribute"))
                    return true;

                type = type.BaseType;
            }
            while (type != null);

            return false;
        }
    }
}