File: Compiler\DependencyAnalysis\NonGCStaticsNode.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\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.Collections.Generic;

using Internal.Text;
using Internal.TypeSystem;

using Debug = System.Diagnostics.Debug;

namespace ILCompiler.DependencyAnalysis
{
    /// <summary>
    /// Represents a node with non-GC static data associated with a type, along
    /// with it's class constructor context. The non-GC static data region shall be prefixed
    /// with the class constructor context if the type has a class constructor that
    /// needs to be triggered before the type members can be accessed.
    /// </summary>
    public class NonGCStaticsNode : DehydratableObjectNode, ISymbolDefinitionNode, ISortableSymbolNode
    {
        private readonly MetadataType _type;
        private readonly PreinitializationManager _preinitializationManager;

        public NonGCStaticsNode(MetadataType type, PreinitializationManager preinitializationManager)
        {
            Debug.Assert(!type.IsCanonicalSubtype(CanonicalFormKind.Specific));
            Debug.Assert(!type.IsGenericDefinition);
            _type = type;
            _preinitializationManager = preinitializationManager;
        }

        protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);

        protected override ObjectNodeSection GetDehydratedSection(NodeFactory factory)
        {
            if (HasCCtorContext)
            {
                // Needs to be writable initialized section because we need info on how to run cctor and whether it ran.
                return ObjectNodeSection.DataSection;
            }
            else if (_preinitializationManager.IsPreinitialized(_type))
            {
                // Unix linkers don't like relocs to readonly data section
                if (!factory.Target.IsWindows)
                    return ObjectNodeSection.DataSection;

                ReadOnlyFieldPolicy readOnlyPolicy = _preinitializationManager.ReadOnlyFieldPolicy;

                bool allFieldsReadOnly = true;
                foreach (FieldDesc field in _type.GetFields())
                {
                    if (!IsNonGcStaticField(field))
                        continue;

                    allFieldsReadOnly = readOnlyPolicy.IsReadOnly(field);
                    if (!allFieldsReadOnly)
                        break;
                }

                // If all fields are read only, we can place this into a read only section
                if (allFieldsReadOnly)
                    return ObjectNodeSection.ReadOnlyDataSection;
                else
                    return ObjectNodeSection.DataSection;
            }
            else
            {
                // This is all zeros; place this to the BSS section
                return ObjectNodeSection.BssSection;
            }
        }

        public static Utf8String GetMangledName(TypeDesc type, NameMangler nameMangler)
        {
            return nameMangler.NodeMangler.NonGCStatics(type);
        }

        public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            sb.Append(nameMangler.NodeMangler.NonGCStatics(_type));
        }

        int ISymbolNode.Offset => 0;

        int ISymbolDefinitionNode.Offset
        {
            get
            {
                // Make sure the NonGCStatics symbol always points to the beginning of the data.
                if (HasCCtorContext)
                {
                    return GetClassConstructorContextStorageSize(_type.Context.Target, _type);
                }
                else
                {
                    return 0;
                }
            }
        }

        public static bool TypeHasCctorContext(PreinitializationManager preinitializationManager, MetadataType type)
        {
            // If the type has a lazy static constructor, we need the cctor context.
            if (preinitializationManager.HasLazyStaticConstructor(type))
                return true;

            // If the type has a canonical form and accessing the base from a canonical
            // context requires a cctor check, we need the cctor context.
            TypeDesc canonType = type.ConvertToCanonForm(CanonicalFormKind.Specific);
            if (canonType != type && preinitializationManager.HasLazyStaticConstructor(canonType))
                return true;

            // Otherwise no cctor context needed.
            return false;
        }

        public bool HasCCtorContext => TypeHasCctorContext(_preinitializationManager, _type);

        public bool HasLazyStaticConstructor => _preinitializationManager.HasLazyStaticConstructor(_type);

        public override bool IsShareable => EETypeNode.IsTypeNodeShareable(_type);

        public MetadataType Type => _type;

        public static int GetClassConstructorContextSize(TargetDetails target)
        {
            // TODO: Assert that StaticClassConstructionContext type has the expected size
            //       (need to make it a well known type?)
            return target.PointerSize;
        }

        private static int GetClassConstructorContextStorageSize(TargetDetails target, MetadataType type)
        {
            int alignmentRequired = Math.Max(type.NonGCStaticFieldAlignment.AsInt, GetClassConstructorContextAlignment(target));
            return AlignmentHelper.AlignUp(GetClassConstructorContextSize(type.Context.Target), alignmentRequired);
        }

        private static int GetClassConstructorContextAlignment(TargetDetails target)
        {
            // TODO: Assert that StaticClassConstructionContext type has the expected alignment
            //       (need to make it a well known type?)
            return target.PointerSize;
        }

        public override bool HasConditionalStaticDependencies => _type.ConvertToCanonForm(CanonicalFormKind.Specific) != _type;

        public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory)
        {
            // If we have a type loader template for this type, we need to keep track of the generated
            // bases in the type info hashtable. The type symbol node does such accounting.
            return new CombinedDependencyListEntry[]
            {
                new CombinedDependencyListEntry(factory.NecessaryTypeSymbol(_type),
                    factory.NativeLayout.TemplateTypeLayout(_type.ConvertToCanonForm(CanonicalFormKind.Specific)),
                    "Keeping track of template-constructable type static bases"),
            };
        }

        public override bool StaticDependenciesAreComputed => true;

        protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
        {
            DependencyList dependencyList = new DependencyList();

            if (factory.PreinitializationManager.HasEagerStaticConstructor(_type))
            {
                dependencyList.Add(factory.EagerCctorIndirection(_type.GetStaticConstructor()), "Eager .cctor");
            }

            ModuleUseBasedDependencyAlgorithm.AddDependenciesDueToModuleUse(ref dependencyList, factory, _type.Module);

            return dependencyList;
        }

        private static bool IsNonGcStaticField(FieldDesc field)
            => field.IsStatic && !field.HasRva && !field.IsLiteral && !field.IsThreadStatic && !field.HasGCStaticBase;

        protected override ObjectData GetDehydratableData(NodeFactory factory, bool relocsOnly)
        {
            ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly);

            // If the type has a class constructor, its non-GC statics section is prefixed
            // by System.Runtime.CompilerServices.StaticClassConstructionContext struct.
            if (HasCCtorContext)
            {
                int alignmentRequired = Math.Max(_type.NonGCStaticFieldAlignment.AsInt, GetClassConstructorContextAlignment(_type.Context.Target));
                int classConstructorContextStorageSize = GetClassConstructorContextStorageSize(factory.Target, _type);
                builder.RequireInitialAlignment(alignmentRequired);

                Debug.Assert(classConstructorContextStorageSize >= GetClassConstructorContextSize(_type.Context.Target));

                // Add padding before the context if alignment forces us to do so
                builder.EmitZeros(classConstructorContextStorageSize - GetClassConstructorContextSize(_type.Context.Target));

                // Emit the actual StaticClassConstructionContext

                // If we're emitting the cctor context, but the type is actually preinitialized, emit the
                // cctor context as already executed.
                if (!HasLazyStaticConstructor)
                {
                    // Pointer to the cctor: Zero means initialized
                    builder.EmitZeroPointer();
                }
                else
                {
                    // Emit pointer to the cctor
                    MethodDesc cctorMethod = _type.GetStaticConstructor();
                    builder.EmitPointerReloc(factory.ExactCallableAddress(cctorMethod));
                }
            }
            else
            {
                builder.RequireInitialAlignment(_type.NonGCStaticFieldAlignment.AsInt);
            }

            if (_preinitializationManager.IsPreinitialized(_type))
            {
                TypePreinit.PreinitializationInfo preinitInfo = _preinitializationManager.GetPreinitializationInfo(_type);
                int initialOffset = builder.CountBytes;
                foreach (FieldDesc field in _type.GetFields())
                {
                    if (!IsNonGcStaticField(field))
                        continue;

                    int padding = field.Offset.AsInt - builder.CountBytes + initialOffset;
                    Debug.Assert(padding >= 0);
                    builder.EmitZeros(padding);

                    TypePreinit.ISerializableValue val = preinitInfo.GetFieldValue(field);
                    int currentOffset = builder.CountBytes;
                    val.WriteFieldData(ref builder, factory);
                    Debug.Assert(builder.CountBytes - currentOffset == field.FieldType.GetElementSize().AsInt);
                }

                int pad = _type.NonGCStaticFieldSize.AsInt - builder.CountBytes + initialOffset;
                Debug.Assert(pad >= 0);
                builder.EmitZeros(pad);
            }
            else
            {
                builder.EmitZeros(_type.NonGCStaticFieldSize.AsInt);
            }

            builder.AddSymbol(this);

            return builder.ToObjectData();
        }

        public override int ClassCode => -1173104872;

        public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
        {
            return comparer.Compare(_type, ((NonGCStaticsNode)other)._type);
        }
    }
}