File: Compiler\ReadyToRunMetadataFieldLayoutAlgorithm.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.ReadyToRun\ILCompiler.ReadyToRun.csproj (ILCompiler.ReadyToRun)
// 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 System.Collections.Generic;
using System.Collections.Concurrent;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;

using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;
using Internal.TypeSystem.Interop;
using Internal.CorConstants;

namespace ILCompiler
{
    public static class ReadyToRunTypeExtensions
    {
        public static LayoutInt FieldBaseOffset(this MetadataType type)
        {
            return ((ReadyToRunCompilerContext)type.Context).CalculateFieldBaseOffset(type);
        }
    }

    internal class ReadyToRunMetadataFieldLayoutAlgorithm : MetadataFieldLayoutAlgorithm
    {
        /// <summary>
        /// Map from EcmaModule instances to field layouts within the individual modules.
        /// </summary>
        private ModuleFieldLayoutMap _moduleFieldLayoutMap;

        /// <summary>
        /// Compilation module group is used to identify which types extend beyond the current version bubble.
        /// </summary>
        private ReadyToRunCompilationModuleGroupBase _compilationGroup;

        public ReadyToRunMetadataFieldLayoutAlgorithm()
        {
            _moduleFieldLayoutMap = new ModuleFieldLayoutMap();
        }

        /// <summary>
        /// Set up compilation group needed for proper calculation of base class alignment in auto layout.
        /// </summary>
        /// <param name="compilationGroup"></param>
        public void SetCompilationGroup(ReadyToRunCompilationModuleGroupBase compilationGroup)
        {
            _compilationGroup = compilationGroup;
        }

        public override ComputedStaticFieldLayout ComputeStaticFieldLayout(DefType defType, StaticLayoutKind layoutKind)
        {
            ComputedStaticFieldLayout layout = new ComputedStaticFieldLayout();
            if (defType.GetTypeDefinition() is EcmaType ecmaType)
            {
                // ECMA types are the only ones that can have statics
                ModuleFieldLayout moduleFieldLayout = _moduleFieldLayoutMap.GetOrCreateValue(ecmaType.Module);
                layout.Offsets = _moduleFieldLayoutMap.GetOrAddDynamicLayout(defType, moduleFieldLayout);
            }
            return layout;
        }

        /// <summary>
        /// Map from modules to their static field layouts.
        /// </summary>
        private class ModuleFieldLayoutMap : LockFreeReaderHashtable<EcmaModule, ModuleFieldLayout>
        {
            protected override bool CompareKeyToValue(EcmaModule key, ModuleFieldLayout value)
            {
                return key == value.Module;
            }

            protected override bool CompareValueToValue(ModuleFieldLayout value1, ModuleFieldLayout value2)
            {
                return value1.Module == value2.Module;
            }

            protected override ModuleFieldLayout CreateValueFromKey(EcmaModule module)
            {
                return new ModuleFieldLayout(module);
            }

            private void GetElementTypeInfoGeneric(
                EcmaModule module,
                FieldDesc fieldDesc,
                EntityHandle valueTypeHandle,
                bool moduleLayout,
                out int alignment,
                out int size,
                out bool isGcPointerField,
                out bool isGcBoxedField)
            {
                alignment = 1;
                size = 0;
                isGcPointerField = false;
                isGcBoxedField = false;

                TypeDesc fieldType = fieldDesc.FieldType;

                if (fieldType.IsPrimitive || fieldType.IsFunctionPointer || fieldType.IsPointer)
                {
                    size = fieldType.GetElementSize().AsInt;
                    alignment = size;
                }
                else if (fieldType.IsByRef || fieldType.IsByRefLike)
                {
                    ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadGeneral, fieldDesc.OwningType);
                }
                else if (fieldType.IsValueType)
                {
                    if (IsTypeByRefLike(valueTypeHandle, module.MetadataReader))
                    {
                        ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadGeneral, fieldDesc.OwningType);
                    }
                    if (moduleLayout && fieldType.GetTypeDefinition() is EcmaType ecmaType && ecmaType.Module != module)
                    {
                        // Allocate pessimistic non-GC area for cross-module fields as that's what CoreCLR does
                        // <a href="https://github.com/dotnet/runtime/blob/17154bd7b8f21d6d8d6fca71b89d7dcb705ec32b/src/coreclr/vm/ceeload.cpp#L1006">here</a>
                        alignment = TargetDetails.MaximumPrimitiveSize;
                        size = TargetDetails.MaximumPrimitiveSize;
                        isGcBoxedField = true;
                    }
                    else if (fieldType.IsEnum)
                    {
                        size = fieldType.UnderlyingType.GetElementSize().AsInt;
                        alignment = size;
                        isGcBoxedField = false;
                    }
                    else
                    {
                        // All struct statics are boxed in CoreCLR
                        isGcBoxedField = true;
                    }
                }
                else
                {
                    isGcPointerField = true;
                }
            }

            private void GetElementTypeInfo(
                EcmaModule module,
                FieldDesc fieldDesc,
                EntityHandle valueTypeHandle,
                CorElementType elementType,
                int pointerSize,
                bool moduleLayout,
                out int alignment,
                out int size,
                out bool isGcPointerField,
                out bool isGcBoxedField)
            {
                alignment = 1;
                size = 0;
                isGcPointerField = false;
                isGcBoxedField = false;

                switch (elementType)
                {
                    case CorElementType.ELEMENT_TYPE_I1:
                    case CorElementType.ELEMENT_TYPE_U1:
                    case CorElementType.ELEMENT_TYPE_BOOLEAN:
                        size = 1;
                        break;

                    case CorElementType.ELEMENT_TYPE_I2:
                    case CorElementType.ELEMENT_TYPE_U2:
                    case CorElementType.ELEMENT_TYPE_CHAR:
                        alignment = 2;
                        size = 2;
                        break;

                    case CorElementType.ELEMENT_TYPE_I4:
                    case CorElementType.ELEMENT_TYPE_U4:
                    case CorElementType.ELEMENT_TYPE_R4:
                        alignment = 4;
                        size = 4;
                        break;

                    case CorElementType.ELEMENT_TYPE_FNPTR:
                    case CorElementType.ELEMENT_TYPE_PTR:
                    case CorElementType.ELEMENT_TYPE_I:
                    case CorElementType.ELEMENT_TYPE_U:
                        alignment = pointerSize;
                        size = pointerSize;
                        break;

                    case CorElementType.ELEMENT_TYPE_I8:
                    case CorElementType.ELEMENT_TYPE_U8:
                    case CorElementType.ELEMENT_TYPE_R8:
                        alignment = 8;
                        size = 8;
                        break;

                    case CorElementType.ELEMENT_TYPE_VAR:
                    case CorElementType.ELEMENT_TYPE_MVAR:
                    case CorElementType.ELEMENT_TYPE_STRING:
                    case CorElementType.ELEMENT_TYPE_SZARRAY:
                    case CorElementType.ELEMENT_TYPE_ARRAY:
                    case CorElementType.ELEMENT_TYPE_CLASS:
                    case CorElementType.ELEMENT_TYPE_OBJECT:
                        isGcPointerField = true;
                        break;

                    case CorElementType.ELEMENT_TYPE_BYREF:
                        ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadGeneral, fieldDesc.OwningType);
                        break;

                    // Statics for valuetypes where the valuetype is defined in this module are handled here.
                    // Other valuetype statics utilize the pessimistic model below.
                    case CorElementType.ELEMENT_TYPE_VALUETYPE:
                        if (IsTypeByRefLike(valueTypeHandle, module.MetadataReader))
                        {
                            ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadGeneral, fieldDesc.OwningType);
                        }
                        if (moduleLayout && fieldDesc.FieldType.GetTypeDefinition() is EcmaType ecmaType && ecmaType.Module != module)
                        {
                            // Allocate pessimistic non-GC area for cross-module fields as that's what CoreCLR does
                            // <a href="https://github.com/dotnet/runtime/blob/17154bd7b8f21d6d8d6fca71b89d7dcb705ec32b/src/coreclr/vm/ceeload.cpp#L1006">here</a>
                            alignment = TargetDetails.MaximumPrimitiveSize;
                            size = TargetDetails.MaximumPrimitiveSize;
                            isGcBoxedField = true;
                        }
                        else if (fieldDesc.FieldType.IsEnum)
                        {
                            size = fieldDesc.FieldType.UnderlyingType.GetElementSize().AsInt;
                            alignment = size;
                        }
                        else
                        {
                            // All struct statics are boxed in CoreCLR
                            isGcBoxedField = true;
                        }
                        break;

                    default:
                        // Field has an unexpected type
                        throw new InvalidProgramException();
                }
            }

            public FieldAndOffset[] GetOrAddDynamicLayout(DefType defType, ModuleFieldLayout moduleFieldLayout)
            {
                FieldAndOffset[] fieldsForType;
                if (!moduleFieldLayout.TryGetDynamicLayout(defType, out fieldsForType))
                {
                    OffsetsForType offsetsForType = new OffsetsForType(
                        nonGcOffset: LayoutInt.Zero,
                        tlsNonGcOffset: new LayoutInt(2 * defType.Context.Target.PointerSize),
                        gcOffset: LayoutInt.Zero,
                        tlsGcOffset: new LayoutInt(2 * defType.Context.Target.PointerSize));

                    fieldsForType = moduleFieldLayout.GetOrAddDynamicLayout(
                        defType,
                        CalculateTypeLayout(defType, moduleFieldLayout.Module, offsetsForType));
                }

                return fieldsForType;
            }

            public FieldAndOffset[] CalculateTypeLayout(DefType defType, EcmaModule module, in OffsetsForType offsetsForType)
            {
                List<FieldAndOffset> fieldsForType = null;
                int pointerSize = module.Context.Target.PointerSize;

                // In accordance with CoreCLR runtime conventions,
                // index 0 corresponds to regular statics, index 1 to thread-local statics.
                int[][] nonGcStaticsCount = new int[StaticIndex.Count][]
                {
                    new int[TargetDetails.MaximumLog2PrimitiveSize + 1],
                    new int[TargetDetails.MaximumLog2PrimitiveSize + 1],
                };

                int[] gcPointerCount = new int[StaticIndex.Count];
                int[] gcBoxedCount = new int[StaticIndex.Count];

                foreach (FieldDesc field in defType.GetFields())
                {
                    FieldDefinition fieldDef = module.MetadataReader.GetFieldDefinition(((EcmaField)field.GetTypicalFieldDefinition()).Handle);
                    if ((fieldDef.Attributes & (FieldAttributes.Static | FieldAttributes.Literal)) == FieldAttributes.Static)
                    {
                        if ((fieldDef.Attributes & FieldAttributes.HasFieldRVA) != 0)
                            continue;

                        int index = (IsFieldThreadStatic(in fieldDef, module.MetadataReader) ? StaticIndex.ThreadLocal : StaticIndex.Regular);
                        int alignment;
                        int size;
                        bool isGcPointerField;
                        bool isGcBoxedField;

                        CorElementType corElementType;
                        EntityHandle valueTypeHandle;

                        GetFieldElementTypeAndValueTypeHandle(in fieldDef, module.MetadataReader, out corElementType, out valueTypeHandle);

                        if (defType.HasInstantiation)
                        {
                            GetElementTypeInfoGeneric(module, field, valueTypeHandle, moduleLayout: false,
                                out alignment, out size, out isGcPointerField, out isGcBoxedField);
                        }
                        else
                        {
                            GetElementTypeInfo(module, field, valueTypeHandle, corElementType, pointerSize, moduleLayout: false,
                                out alignment, out size, out isGcPointerField, out isGcBoxedField);
                        }
                        if (isGcPointerField)
                        {
                            gcPointerCount[index]++;
                        }
                        else if (isGcBoxedField)
                        {
                            gcBoxedCount[index]++;
                        }
                        else if (size != 0)
                        {
                            int log2Size = GetLog2Size(size);
                            nonGcStaticsCount[index][log2Size]++;
                        }
                    }
                }

                LayoutInt[] nonGcStaticFieldOffsets = new LayoutInt[StaticIndex.Count]
                {
                    offsetsForType.NonGcOffsets[StaticIndex.Regular],
                    offsetsForType.NonGcOffsets[StaticIndex.ThreadLocal],
                };

                LayoutInt[][] nonGcStatics = new LayoutInt[StaticIndex.Count][]
                {
                    new LayoutInt[TargetDetails.MaximumLog2PrimitiveSize + 1],
                    new LayoutInt[TargetDetails.MaximumLog2PrimitiveSize + 1],
                };

                for (int log2Size = TargetDetails.MaximumLog2PrimitiveSize; log2Size >= 0; log2Size--)
                {
                    for (int index = 0; index < StaticIndex.Count; index++)
                    {
                        LayoutInt offset = nonGcStaticFieldOffsets[index];
                        nonGcStatics[index][log2Size] = offset;
                        offset += new LayoutInt(nonGcStaticsCount[index][log2Size] << log2Size);
                        nonGcStaticFieldOffsets[index] = offset;
                    }
                }

                LayoutInt[] gcBoxedFieldOffsets = new LayoutInt[StaticIndex.Count]
                {
                    offsetsForType.GcOffsets[StaticIndex.Regular],
                    offsetsForType.GcOffsets[StaticIndex.ThreadLocal],
                };

                LayoutInt[] gcPointerFieldOffsets = new LayoutInt[StaticIndex.Count]
                {
                    offsetsForType.GcOffsets[StaticIndex.Regular] + new LayoutInt(gcBoxedCount[StaticIndex.Regular] * pointerSize),
                    offsetsForType.GcOffsets[StaticIndex.ThreadLocal] + new LayoutInt(gcBoxedCount[StaticIndex.ThreadLocal] * pointerSize)
                };

                foreach (FieldDesc field in defType.GetFields())
                {
                    FieldDefinitionHandle fieldDefHandle = ((EcmaField)field.GetTypicalFieldDefinition()).Handle;
                    FieldDefinition fieldDef = module.MetadataReader.GetFieldDefinition(fieldDefHandle);
                    if ((fieldDef.Attributes & (FieldAttributes.Static | FieldAttributes.Literal)) == FieldAttributes.Static)
                    {
                        int index = (IsFieldThreadStatic(in fieldDef, module.MetadataReader) ? StaticIndex.ThreadLocal : StaticIndex.Regular);
                        int alignment;
                        int size;
                        bool isGcPointerField;
                        bool isGcBoxedField;

                        CorElementType corElementType;
                        EntityHandle valueTypeHandle;

                        GetFieldElementTypeAndValueTypeHandle(in fieldDef, module.MetadataReader, out corElementType, out valueTypeHandle);

                        if (defType.HasInstantiation)
                        {
                            GetElementTypeInfoGeneric(module, field, valueTypeHandle, moduleLayout: false,
                                out alignment, out size, out isGcPointerField, out isGcBoxedField);
                        }
                        else
                        {
                            GetElementTypeInfo(module, field, valueTypeHandle, corElementType, pointerSize, moduleLayout: false,
                                out alignment, out size, out isGcPointerField, out isGcBoxedField);
                        }

                        LayoutInt offset = LayoutInt.Zero;

                        if ((fieldDef.Attributes & FieldAttributes.HasFieldRVA) != 0)
                        {
                            offset = new LayoutInt(fieldDef.GetRelativeVirtualAddress());
                        }
                        else if (isGcPointerField)
                        {
                            offset = gcPointerFieldOffsets[index];
                            gcPointerFieldOffsets[index] += new LayoutInt(pointerSize);
                        }
                        else if (isGcBoxedField)
                        {
                            offset = gcBoxedFieldOffsets[index];
                            gcBoxedFieldOffsets[index] += new LayoutInt(pointerSize);
                        }
                        else if (size != 0)
                        {
                            int log2Size = GetLog2Size(size);
                            offset = nonGcStatics[index][log2Size];
                            nonGcStatics[index][log2Size] += new LayoutInt(1 << log2Size);
                        }

                        if (fieldsForType == null)
                        {
                            fieldsForType = new List<FieldAndOffset>();
                        }
                        fieldsForType.Add(new FieldAndOffset(field, offset));
                    }
                }

                return fieldsForType == null ? null : fieldsForType.ToArray();
            }


            protected override int GetKeyHashCode(EcmaModule key)
            {
                return key.GetHashCode();
            }

            protected override int GetValueHashCode(ModuleFieldLayout value)
            {
                return value.Module.GetHashCode();
            }

            /// <summary>
            /// Try to locate the ThreadStatic custom attribute on the field (much like EcmaField.cs does in the method InitializeFieldFlags).
            /// </summary>
            /// <param name="fieldDef">Field definition</param>
            /// <param name="metadataReader">Metadata reader for the module</param>
            /// <returns>true when the field is marked with the ThreadStatic custom attribute</returns>
            private static bool IsFieldThreadStatic(in FieldDefinition fieldDef, MetadataReader metadataReader)
            {
                return !metadataReader.GetCustomAttributeHandle(fieldDef.GetCustomAttributes(), "System", "ThreadStaticAttribute").IsNil;
            }

            /// <summary>
            /// Try to locate the IsByRefLike attribute on the type (much like EcmaType does in ComputeTypeFlags).
            /// </summary>
            /// <param name="typeDefHandle">Handle to the field type to analyze</param>
            /// <param name="metadataReader">Metadata reader for the active module</param>
            /// <returns></returns>
            private static bool IsTypeByRefLike(EntityHandle typeDefHandle, MetadataReader metadataReader)
            {
                return typeDefHandle.Kind == HandleKind.TypeDefinition &&
                    !metadataReader.GetCustomAttributeHandle(
                        metadataReader.GetTypeDefinition((TypeDefinitionHandle)typeDefHandle).GetCustomAttributes(),
                        "System.Runtime.CompilerServices",
                        "IsByRefLikeAttribute").IsNil;
            }

            /// <summary>
            /// Partially decode field signature to obtain CorElementType and optionally the type handle for VALUETYPE fields.
            /// </summary>
            /// <param name="fieldDef">Metadata field definition</param>
            /// <param name="metadataReader">Metadata reader for the active module</param>
            /// <param name="corElementType">Output element type decoded from the signature</param>
            /// <param name="valueTypeHandle">Value type handle decoded from the signature</param>
            private static void GetFieldElementTypeAndValueTypeHandle(
                in FieldDefinition fieldDef,
                MetadataReader metadataReader,
                out CorElementType corElementType,
                out EntityHandle valueTypeHandle)
            {
                BlobReader signature = metadataReader.GetBlobReader(fieldDef.Signature);
                SignatureHeader signatureHeader = signature.ReadSignatureHeader();
                if (signatureHeader.Kind != SignatureKind.Field)
                {
                    throw new InvalidProgramException();
                }

                corElementType = ReadElementType(ref signature);
                valueTypeHandle = default(EntityHandle);
                if (corElementType == CorElementType.ELEMENT_TYPE_GENERICINST)
                {
                    corElementType = ReadElementType(ref signature);
                }

                if (corElementType == CorElementType.ELEMENT_TYPE_VALUETYPE)
                {
                    valueTypeHandle = signature.ReadTypeHandle();
                }
            }

            /// <summary>
            /// Extract element type from a field signature after skipping various modifiers.
            /// </summary>
            /// <param name="signature">Signature byte array</param>
            /// <param name="index">On input, index into the signature array. Gets modified to point after the element type on return.</param>
            /// <returns></returns>
            private static CorElementType ReadElementType(ref BlobReader signature)
            {
                // SigParser::PeekElemType
                byte signatureByte = signature.ReadByte();
                if (signatureByte < (byte)CorElementType.ELEMENT_TYPE_CMOD_REQD)
                {
                    // Fast path
                    return (CorElementType)signatureByte;
                }

                // SigParser::SkipCustomModifiers -> SkipAnyVASentinel
                if (signatureByte == (byte)CorElementType.ELEMENT_TYPE_SENTINEL)
                {
                    signatureByte = signature.ReadByte();
                }

                // SigParser::SkipCustomModifiers - modifier loop
                while (signatureByte == (byte)CorElementType.ELEMENT_TYPE_CMOD_REQD ||
                    signatureByte == (byte)CorElementType.ELEMENT_TYPE_CMOD_OPT)
                {
                    signature.ReadCompressedInteger();
                    signatureByte = signature.ReadByte();
                }
                return (CorElementType)signatureByte;
            }


            /// <summary>
            /// Return the integral value of dyadic logarithm of given size
            /// up to MaximumLog2PrimitiveSize.
            /// </summary>
            /// <param name="size">Size to calculate base 2 logarithm for</param>
            /// <returns></returns>
            private static int GetLog2Size(int size)
            {
                switch (size)
                {
                    case 0:
                    case 1:
                        return 0;
                    case 2:
                        return 1;
                    case 3:
                    case 4:
                        return 2;
                    default:
                        Debug.Assert(TargetDetails.MaximumLog2PrimitiveSize == 3);
                        return TargetDetails.MaximumLog2PrimitiveSize;
                }
            }
        }

        public static class StaticIndex
        {
            public const int Regular = 0;
            public const int ThreadLocal = 1;

            public const int Count = 2;
        }

        /// <summary>
        /// Starting offsets of static allocations for a given type.
        /// </summary>
        private struct OffsetsForType
        {
            /// <summary>
            /// Starting offset for non-GC statics in DomainLocalModule / ThreadLocalModule
            /// </summary>
            public readonly LayoutInt[] NonGcOffsets;

            /// <summary>
            /// Starting offset for GC statics in DomainLocalModule / ThreadLocalModule
            /// </summary>
            public readonly LayoutInt[] GcOffsets;

            public OffsetsForType(LayoutInt nonGcOffset, LayoutInt tlsNonGcOffset, LayoutInt gcOffset, LayoutInt tlsGcOffset)
            {
                NonGcOffsets = new LayoutInt[StaticIndex.Count] { nonGcOffset, tlsNonGcOffset };
                GcOffsets = new LayoutInt[StaticIndex.Count] { gcOffset, tlsGcOffset };
            }
        }

        /// <summary>
        /// Field layouts for a given EcmaModule.
        /// </summary>
        private class ModuleFieldLayout
        {
            public EcmaModule Module { get; }

            private ConcurrentDictionary<DefType, FieldAndOffset[]> _typeToFieldMap;

            public ModuleFieldLayout(
                EcmaModule module)
            {
                Module = module;

                _typeToFieldMap = new ConcurrentDictionary<DefType, FieldAndOffset[]>();
            }

            public bool TryGetDynamicLayout(DefType instantiatedType, out FieldAndOffset[] fieldMap)
            {
                return _typeToFieldMap.TryGetValue(instantiatedType, out fieldMap);
            }

            public FieldAndOffset[] GetOrAddDynamicLayout(DefType instantiatedType, FieldAndOffset[] fieldMap)
            {
                return _typeToFieldMap.GetOrAdd(instantiatedType, fieldMap);
            }
        }

        protected override ComputedInstanceFieldLayout ComputeInstanceFieldLayout(MetadataType type, int numInstanceFields)
        {
            ClassLayoutMetadata layoutMetadata = type.GetClassLayout();
            MetadataLayoutKind layoutKind = layoutMetadata.Kind;
            switch (layoutKind)
            {
                case MetadataLayoutKind.CStruct:
                    return ComputeCStructFieldLayout(type, numInstanceFields);
                case MetadataLayoutKind.CUnion:
                    return ComputeCUnionFieldLayout(type, numInstanceFields);
                case MetadataLayoutKind.Explicit:
                    // Works around https://github.com/dotnet/runtime/issues/102868
                    if (type is { IsValueType: false, BaseType.IsSequentialLayout: true })
                    {
                        ThrowHelper.ThrowTypeLoadException(type);
                    }

                    return ComputeExplicitFieldLayout(type, numInstanceFields, layoutMetadata);
                case MetadataLayoutKind.Sequential when !type.ContainsGCPointers:
                    // Works around https://github.com/dotnet/runtime/issues/102868
                    if (type is { IsValueType: false, BaseType.IsExplicitLayout: true })
                    {
                        ThrowHelper.ThrowTypeLoadException(type);
                    }

                    return ComputeSequentialFieldLayout(type, numInstanceFields, layoutMetadata);
                default:
                    return ComputeAutoFieldLayout(type, numInstanceFields, layoutMetadata);
            }
        }

        /// <summary>
        /// This method decides whether the type needs aligned base offset in order to have layout resilient to
        /// base class layout changes.
        /// </summary>
        protected override void AlignBaseOffsetIfNecessary(MetadataType type, ref LayoutInt baseOffset, bool requiresAlign8, bool requiresAlignedBase)
        {
            if (requiresAlignedBase || _compilationGroup.NeedsAlignmentBetweenBaseTypeAndDerived(baseType: (MetadataType)type.BaseType, derivedType: type))
            {
                bool use8Align = (requiresAlign8 || type.BaseType.RequiresAlign8()) && type.Context.Target.Architecture != TargetArchitecture.X86;
                LayoutInt alignment = new LayoutInt(use8Align ? 8 : type.Context.Target.PointerSize);
                baseOffset = LayoutInt.AlignUp(baseOffset, alignment, type.Context.Target);
            }
        }
    }
}