File: Compiler\DependencyAnalysis\ManagedDataDescriptorNode.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.Diagnostics;
using System.IO;
using System.Text.Json;

using Internal.Text;
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;

namespace ILCompiler.DependencyAnalysis
{
    /// <summary>
    /// Emits a ContractDescriptor for managed type layouts that the cDAC reader
    /// can consume as a sub-descriptor. ILC knows managed type layouts at compile time,
    /// so it can emit field offsets that would otherwise require runtime metadata resolution.
    ///
    /// Types are discovered by scanning MetadataManager.GetTypesWithEETypes() for types
    /// annotated with [DataContract], ensuring only types that actually have a MethodTable
    /// in the binary are included.
    /// </summary>
    public class ManagedDataDescriptorNode : ObjectNode, ISymbolDefinitionNode
    {
        private const string DataContractAttributeNamespace = "System.Diagnostics";
        private const string DataContractAttributeName = "DataContractAttribute";

        public const string SymbolName = "DotNetManagedContractDescriptor";

        public override ObjectNodeSection GetSection(NodeFactory factory) =>
            factory.Target.IsWindows ? ObjectNodeSection.ReadOnlyDataSection : ObjectNodeSection.DataSection;

        public override bool StaticDependenciesAreComputed => true;
        public override bool IsShareable => false;

        public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            sb.Append(nameMangler.NodeMangler.ExternVariable(new Utf8String(SymbolName)));
        }

        public int Offset => 0;

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

        public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
        {
            if (relocsOnly)
                return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });

            byte[] jsonBytes = BuildJsonDescriptor(factory);

            // Header layout: magic(8) + flags(4) + desc_size(4) + desc_ptr(ptr) + pointer_data_count(4) + pad(4) + pointer_data(ptr)
            int headerSize = 8 + 4 + 4 + factory.Target.PointerSize + 4 + 4 + factory.Target.PointerSize;

            ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly);
            builder.RequireInitialPointerAlignment();
            builder.AddSymbol(this);

            // uint64_t magic
            builder.EmitLong(0x0043414443434e44L); // "DNCCDAC\0"

            // uint32_t flags (bit 0 must be set; bit 1 indicates 32-bit pointers)
            uint flags = (uint)(0x01 | (factory.Target.PointerSize == 4 ? 0x02 : 0x00));
            builder.EmitUInt(flags);

            // uint32_t descriptor_size
            builder.EmitUInt((uint)jsonBytes.Length);

            // char* descriptor — points to inline JSON after the header
            builder.EmitPointerReloc(this, headerSize);

            // uint32_t pointer_data_count = 0
            builder.EmitUInt(0);

            // uint32_t pad0
            builder.EmitUInt(0);

            // void** pointer_data = null
            builder.EmitZeroPointer();

            // Emit JSON bytes inline, null-terminated
            Debug.Assert(builder.CountBytes == headerSize);
            builder.EmitBytes(jsonBytes);
            builder.EmitByte(0);

            return builder.ToObjectData();
        }

        /// <summary>
        /// Build the JSON descriptor using the compact format expected by the cDAC reader's
        /// ContractDescriptorParser. Types are objects with an optional "!" size sigil and
        /// field-name properties mapped to their offsets.
        /// </summary>
        private static byte[] BuildJsonDescriptor(NodeFactory factory)
        {
            using var stream = new MemoryStream();
            using (var writer = new Utf8JsonWriter(stream))
            {
                writer.WriteStartObject();
                writer.WriteNumber("version", 0);
                writer.WriteString("baseline", "empty");

                writer.WriteStartObject("types");
                foreach (TypeDesc type in factory.MetadataManager.GetTypesWithEETypes())
                {
                    if (type is not EcmaType ecmaType)
                        continue;

                    if (!ecmaType.HasCustomAttribute(DataContractAttributeNamespace, DataContractAttributeName))
                        continue;

                    WriteType(writer, ecmaType);
                }
                writer.WriteEndObject();

                writer.WriteStartObject("globals");
                writer.WriteEndObject();

                writer.WriteStartObject("contracts");
                writer.WriteEndObject();

                writer.WriteEndObject();
            }

            return stream.ToArray();
        }

        private static void WriteType(Utf8JsonWriter writer, EcmaType type)
        {
            writer.WriteStartObject(GetFullTypeName(type));

            if (type.IsValueType)
            {
                writer.WriteNumber("!", type.InstanceFieldSize.AsInt);
            }

            foreach (FieldDesc field in type.GetFields())
            {
                if (field.IsStatic || field is not EcmaField ecmaField)
                    continue;

                if (!ecmaField.HasCustomAttribute(DataContractAttributeNamespace, DataContractAttributeName))
                    continue;

                writer.WriteNumber(field.GetName(), field.Offset.AsInt);
            }

            writer.WriteEndObject();
        }

        /// <summary>
        /// Returns a fully-qualified type name for cDAC descriptors
        /// (e.g., "System.Threading.Thread" or "System.Foo.Outer+Inner").
        /// </summary>
        private static string GetFullTypeName(MetadataType type)
        {
            if (type.ContainingType is not null)
                return $"{GetFullTypeName(type.ContainingType)}+{type.GetName()}";

            string ns = type.GetNamespace();
            string name = type.GetName();

            if (string.IsNullOrEmpty(ns))
                return name;

            return $"{ns}.{name}";
        }

        public override int ClassCode => 0x4d444e01;
    }
}