File: src\runtime\src\coreclr\nativeaot\Common\src\Internal\Runtime\CompilerHelpers\StartupCodeHelpers.cs
Web Access
Project: src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\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;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

using Debug = Internal.Runtime.CompilerHelpers.StartupDebug;

namespace Internal.Runtime.CompilerHelpers
{
    internal static partial class StartupCodeHelpers
    {
        /// <summary>
        /// Table of logical modules. Only the first s_moduleCount elements of the array are in use.
        /// </summary>
        private static TypeManagerHandle[] s_modules;

        /// <summary>
        /// Number of valid elements in the logical module table.
        /// </summary>
        private static int s_moduleCount;

        /// <summary>
        /// GC handle of an array with s_moduleCount elements, each representing and array of GC static bases of the types in the module.
        /// </summary>
        private static IntPtr s_moduleGCStaticsSpines;

        [UnmanagedCallersOnly(EntryPoint = "InitializeModules")]
        internal static unsafe void InitializeModules(IntPtr osModule, IntPtr* pModuleHeaders, int count, IntPtr* pClasslibFunctions, int nClasslibFunctions)
        {
            RuntimeImports.RhpRegisterOsModule(osModule);
            TypeManagerHandle[] modules = CreateTypeManagers(osModule, pModuleHeaders, count, pClasslibFunctions, nClasslibFunctions);
            object[] gcStaticBaseSpines = new object[count];

            for (int i = 0; i < modules.Length; i++)
            {
                InitializeGlobalTablesForModule(modules[i], i, gcStaticBaseSpines);
            }

            s_moduleGCStaticsSpines = RuntimeImports.RhHandleAlloc(gcStaticBaseSpines, GCHandleType.Normal);

            // We are now at a stage where we can use GC statics - publish the list of modules
            // so that the eager constructors can access it.
            s_modules = modules;
            s_moduleCount = modules.Length;

            // These two loops look funny but it's important to initialize the global tables before running
            // the first class constructor to prevent them calling into another uninitialized module
            for (int i = 0; i < modules.Length; i++)
            {
                RunInitializers(modules[i], ReadyToRunSectionType.EagerCctor);
            }
        }

        /// <summary>
        /// Return the number of registered logical modules; optionally copy them into an array.
        /// </summary>
        /// <param name="outputModules">Array to copy logical modules to, null = only return logical module count</param>
        internal static int GetLoadedModules(TypeManagerHandle[] outputModules)
        {
            if (outputModules != null)
            {
                int copyLimit = (s_moduleCount < outputModules.Length ? s_moduleCount : outputModules.Length);
                for (int copyIndex = 0; copyIndex < copyLimit; copyIndex++)
                {
                    outputModules[copyIndex] = s_modules[copyIndex];
                }
            }
            return s_moduleCount;
        }

        private static unsafe TypeManagerHandle[] CreateTypeManagers(IntPtr osModule, IntPtr* pModuleHeaders, int count, IntPtr* pClasslibFunctions, int nClasslibFunctions)
        {
            // Count the number of modules so we can allocate an array to hold the TypeManager objects.
            // At this stage of startup, complex collection classes will not work.
            int moduleCount = 0;
            for (int i = 0; i < count; i++)
            {
                // The null pointers are sentinel values and padding inserted as side-effect of
                // the section merging. (The global static constructors section used by C++ has
                // them too.)
                if (pModuleHeaders[i] != IntPtr.Zero)
                    moduleCount++;
            }

            // We cannot use the new keyword just yet, so stackalloc the array first
            TypeManagerHandle* pHandles = stackalloc TypeManagerHandle[moduleCount];
            int moduleIndex = 0;
            for (int i = 0; i < count; i++)
            {
                if (pModuleHeaders[i] != IntPtr.Zero)
                {
                    TypeManagerHandle handle = RuntimeImports.RhpCreateTypeManager(osModule, pModuleHeaders[i], pClasslibFunctions, nClasslibFunctions);

                    // Rehydrate any dehydrated data structures
                    IntPtr dehydratedDataSection = RuntimeImports.RhGetModuleSection(
                        handle, ReadyToRunSectionType.DehydratedData, out _);
                    if (dehydratedDataSection != IntPtr.Zero)
                    {
                        RehydrateData(dehydratedDataSection);
                    }

                    pHandles[moduleIndex++] = handle;
                }
            }

            // Any potentially dehydrated MethodTables got rehydrated, we can safely use `new` now.
            TypeManagerHandle[] modules = new TypeManagerHandle[moduleCount];
            for (int i = 0; i < moduleCount; i++)
                modules[i] = pHandles[i];

            return modules;
        }

        /// <summary>
        /// Each managed module linked into the final binary may have its own global tables for strings,
        /// statics, etc that need initializing. InitializeGlobalTables walks through the modules
        /// and offers each a chance to initialize its global tables.
        /// </summary>
        private static unsafe void InitializeGlobalTablesForModule(TypeManagerHandle typeManager, int moduleIndex, object[] gcStaticBaseSpines)
        {
            // Configure the module indirection cell with the newly created TypeManager. This allows EETypes to find
            // their interface dispatch map tables.
            int length;
            TypeManagerSlot* section = (TypeManagerSlot*)RuntimeImports.RhGetModuleSection(typeManager, ReadyToRunSectionType.TypeManagerIndirection, out length);
            section->TypeManager = typeManager;
            section->ModuleIndex = moduleIndex;

            // Initialize statics if any are present
            IntPtr staticsSection = RuntimeImports.RhGetModuleSection(typeManager, ReadyToRunSectionType.GCStaticRegion, out length);
            if (staticsSection != IntPtr.Zero)
            {
                Debug.Assert(length % (MethodTable.SupportsRelativePointers ? sizeof(int) : sizeof(nint)) == 0);

                object[] spine = InitializeStatics(staticsSection, length);

                // Call write barrier directly. Assigning object reference does a type check.
                Debug.Assert((uint)moduleIndex < (uint)gcStaticBaseSpines.Length);
                ref object rawSpineIndexData = ref Unsafe.As<byte, object>(ref Unsafe.As<RawArrayData>(gcStaticBaseSpines).Data);
                Unsafe.Add(ref rawSpineIndexData, moduleIndex) = spine;
            }

            // Initialize frozen object segment for the module with GC present
            IntPtr frozenObjectSection = RuntimeImports.RhGetModuleSection(typeManager, ReadyToRunSectionType.FrozenObjectRegion, out length);
            if (frozenObjectSection != IntPtr.Zero)
            {
                Debug.Assert(length % IntPtr.Size == 0);
                InitializeModuleFrozenObjectSegment(frozenObjectSection, length);
            }
        }

        private static unsafe void InitializeModuleFrozenObjectSegment(IntPtr segmentStart, int length)
        {
            if (RuntimeImports.RhRegisterFrozenSegment((void*)segmentStart, (nuint)length, (nuint)length, (nuint)length) == IntPtr.Zero)
            {
                // This should only happen if we ran out of memory.
                RuntimeExceptionHelpers.FailFast("Failed to register frozen object segment for the module.");
            }
        }

        internal static void RunModuleInitializers()
        {
            for (int i = 0; i < s_moduleCount; i++)
            {
                RunInitializers(s_modules[i], ReadyToRunSectionType.ModuleInitializerList);
            }
        }

        private static unsafe void RunInitializers(TypeManagerHandle typeManager, ReadyToRunSectionType section)
        {
            var pInitializers = (byte*)RuntimeImports.RhGetModuleSection(typeManager, section, out int length);
            Debug.Assert(length % (MethodTable.SupportsRelativePointers ? sizeof(int) : sizeof(nint)) == 0);

            for (byte* pCurrent = pInitializers;
                pCurrent < (pInitializers + length);
                pCurrent += MethodTable.SupportsRelativePointers ? sizeof(int) : sizeof(nint))
            {
                var initializer = MethodTable.SupportsRelativePointers ? (delegate*<void>)ReadRelPtr32(pCurrent) : *(delegate*<void>*)pCurrent;
                initializer();
            }

            static void* ReadRelPtr32(void* address)
                => (byte*)address + *(int*)address;
        }

        private static unsafe object[] InitializeStatics(IntPtr gcStaticRegionStart, int length)
        {
            byte* gcStaticRegionEnd = (byte*)gcStaticRegionStart + length;

            object[] spine = new object[length / (MethodTable.SupportsRelativePointers ? sizeof(int) : sizeof(nint))];

            ref object rawSpineData = ref Unsafe.As<byte, object>(ref Unsafe.As<RawArrayData>(spine).Data);

            int currentBase = 0;
            for (byte* block = (byte*)gcStaticRegionStart;
                block < gcStaticRegionEnd;
                block += MethodTable.SupportsRelativePointers ? sizeof(int) : sizeof(nint))
            {
                // Gc Static regions can be shared by modules linked together during compilation. To ensure each
                // is initialized once, the static region pointer is stored with lowest bit set in the image.
                // The first time we initialize the static region its pointer is replaced with an object reference
                // whose lowest bit is no longer set.
                IntPtr* pBlock = MethodTable.SupportsRelativePointers ? (IntPtr*)ReadRelPtr32(block) : *(IntPtr**)block;
                nint blockAddr = MethodTable.SupportsRelativePointers ? (nint)ReadRelPtr32(pBlock) : *pBlock;
                if ((blockAddr & GCStaticRegionConstants.Uninitialized) == GCStaticRegionConstants.Uninitialized)
                {
                    object? obj = null;
                    RuntimeImports.RhAllocateNewObject(
                        new IntPtr(blockAddr & ~GCStaticRegionConstants.Mask),
                        (uint)GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP,
                        &obj);
                    if (obj == null)
                    {
                        RuntimeExceptionHelpers.FailFast("Failed allocating GC static bases");
                    }


                    if ((blockAddr & GCStaticRegionConstants.HasPreInitializedData) == GCStaticRegionConstants.HasPreInitializedData)
                    {
                        // The next pointer is preinitialized data blob that contains preinitialized static GC fields,
                        // which are pointer relocs to GC objects in frozen segment.
                        // It actually has all GC fields including non-preinitialized fields and we simply copy over the
                        // entire blob to this object, overwriting everything.
                        void* pPreInitDataAddr = MethodTable.SupportsRelativePointers ? ReadRelPtr32((int*)pBlock + 1) : (void*)*(pBlock + 1);
                        RuntimeImports.RhBulkMoveWithWriteBarrier(ref obj.GetRawData(), ref *(byte*)pPreInitDataAddr, obj.GetRawObjectDataSize());
                    }

                    // Call write barrier directly. Assigning object reference does a type check.
                    Debug.Assert(currentBase < spine.Length);
                    Unsafe.Add(ref rawSpineData, currentBase) = obj;

                    // Update the base pointer to point to the pinned object
                    *pBlock = *(IntPtr*)&obj;
                }

                currentBase++;
            }

            return spine;

            static void* ReadRelPtr32(void* address)
                => (byte*)address + *(int*)address;
        }

        private static unsafe void RehydrateData(IntPtr dehydratedData)
        {
            // Destination for the hydrated data is in the first 32-bit relative pointer
            byte* pDest = (byte*)ReadRelPtr32((void*)dehydratedData);

            // Next is length of the dehydrated data
            int length = *(int*)(dehydratedData + sizeof(int));

            // The dehydrated data follows
            byte* pCurrent = (byte*)dehydratedData + sizeof(int) * 2;
            byte* pEnd = (byte*)dehydratedData + length;

            // Fixup table immediately follows the command stream
            int* pFixups = (int*)pEnd;

            while (pCurrent < pEnd)
            {
                pCurrent = DehydratedDataCommand.Decode(pCurrent, out int command, out int payload);
                switch (command)
                {
                    case DehydratedDataCommand.Copy:
                        Debug.Assert(payload != 0);
                        if (payload < 4)
                        {
                            *pDest = *pCurrent;
                            if (payload > 1)
                                *(short*)(pDest + payload - 2) = *(short*)(pCurrent + payload - 2);
                        }
                        else if (payload < 8)
                        {
                            *(int*)pDest = *(int*)pCurrent;
                            *(int*)(pDest + payload - 4) = *(int*)(pCurrent + payload - 4);
                        }
                        else if (payload <= 16)
                        {
#if TARGET_64BIT
                            *(long*)pDest = *(long*)pCurrent;
                            *(long*)(pDest + payload - 8) = *(long*)(pCurrent + payload - 8);
#else
                            *(int*)pDest = *(int*)pCurrent;
                            *(int*)(pDest + 4) = *(int*)(pCurrent + 4);
                            *(int*)(pDest + payload - 8) = *(int*)(pCurrent + payload - 8);
                            *(int*)(pDest + payload - 4) = *(int*)(pCurrent + payload - 4);
#endif
                        }
                        else
                        {
                            // At the time of writing this, 90% of DehydratedDataCommand.Copy cases
                            // would fall into the above specialized cases. 10% fall back to memmove.
                            Unsafe.CopyBlock(pDest, pCurrent, (uint)payload);
                        }

                        pDest += payload;
                        pCurrent += payload;
                        break;
                    case DehydratedDataCommand.ZeroFill:
                        pDest += payload;
                        break;
                    case DehydratedDataCommand.PtrReloc:
                        *(void**)pDest = ReadRelPtr32(pFixups + payload);
                        pDest += sizeof(void*);
                        break;
                    case DehydratedDataCommand.RelPtr32Reloc:
                        WriteRelPtr32(pDest, ReadRelPtr32(pFixups + payload));
                        pDest += sizeof(int);
                        break;
                    case DehydratedDataCommand.InlinePtrReloc:
                        while (payload-- > 0)
                        {
                            *(void**)pDest = ReadRelPtr32(pCurrent);
                            pDest += sizeof(void*);
                            pCurrent += sizeof(int);
                        }
                        break;
                    case DehydratedDataCommand.InlineRelPtr32Reloc:
                        while (payload-- > 0)
                        {
                            WriteRelPtr32(pDest, ReadRelPtr32(pCurrent));
                            pDest += sizeof(int);
                            pCurrent += sizeof(int);
                        }
                        break;
                }
            }

            static void* ReadRelPtr32(void* address)
                => (byte*)address + *(int*)address;

            static void WriteRelPtr32(void* dest, void* value)
                => *(int*)dest = (int)((byte*)value - (byte*)dest);
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    internal unsafe struct TypeManagerSlot
    {
        public TypeManagerHandle TypeManager;
        public int ModuleIndex;
    }
}