Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,10 @@

#include <stdint.h>
#include <stddef.h>

// ILC emits a ContractDescriptor named "DotNetManagedContractDescriptor" with
// managed type layouts. We take its address so datadescriptor.inc can reference
// it as a sub-descriptor via CDAC_GLOBAL_SUB_DESCRIPTOR.
struct ContractDescriptor;
extern "C" ContractDescriptor DotNetManagedContractDescriptor;
static const void* g_pManagedContractDescriptor = &DotNetManagedContractDescriptor;
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ CDAC_GLOBAL_CONTRACT(Thread, 1001)
CDAC_GLOBAL_CONTRACT(Exception, 1)
CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, 1001)

// Managed type sub-descriptor: ILC emits a ContractDescriptor with managed type layouts
// that the cDAC reader merges as a sub-descriptor. This provides field offsets for managed
// types (e.g., ConditionalWeakTable internals, IdDispenser) that are not exposed through
// native C++ data descriptors.
CDAC_GLOBAL_SUB_DESCRIPTOR(ManagedTypes, &g_pManagedContractDescriptor)

// GC sub-descriptor: the GC populates gc_descriptor during GC_Initialize.
// It is important for subdescriptor pointers to be the last pointers.
CDAC_GLOBAL_SUB_DESCRIPTOR(GC, &(g_gc_dac_vars.gc_descriptor))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@
<Compile Include="System\RuntimeType.NativeAot.cs" />
<Compile Include="System\Runtime\ControlledExecution.NativeAot.cs" />
<Compile Include="System\Runtime\DependentHandle.cs" />
<Compile Include="System\Runtime\CompilerServices\CdacFieldAttribute.cs" />
<Compile Include="System\Runtime\CompilerServices\CdacTypeAttribute.cs" />
<Compile Include="System\Runtime\CompilerServices\EagerStaticClassConstructionAttribute.cs" />
<Compile Include="System\Runtime\CompilerServices\RuntimeFeature.NativeAot.cs" />
<Compile Include="System\Runtime\CompilerServices\StaticClassConstructionContext.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.CompilerServices
{
/// <summary>
/// When applied to an instance field of a type annotated with <see cref="CdacTypeAttribute"/>,
/// indicates that ILC should include this field in the managed cDAC data descriptor.
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
internal sealed class CdacFieldAttribute : Attribute
{
public CdacFieldAttribute()
{
}

public CdacFieldAttribute(string name)
{
Name = name;
}

/// <summary>
/// The cDAC descriptor field name. If not specified, the field's declared name is used.
/// </summary>
public string? Name { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.CompilerServices
{
/// <summary>
/// When applied to a type, indicates that ILC should include its field layout in the
/// managed cDAC data descriptor. The cDAC reader merges this information as a
/// sub-descriptor so diagnostic tools can inspect managed type instances without
/// runtime metadata (critical for NativeAOT where metadata may be stripped).
/// </summary>
/// <remarks>
/// Fields to include must be individually annotated with <see cref="CdacFieldAttribute"/>.
/// </remarks>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
internal sealed class CdacTypeAttribute : Attribute
{
public CdacTypeAttribute(string name)
{
Name = name;
}

/// <summary>
/// The cDAC descriptor type name (e.g., "ManagedThread").
/// </summary>
public string Name { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace System.Threading
{
[CdacType("ManagedThread")]
public sealed partial class Thread
{
// Extra bits used in _threadState
Expand All @@ -29,8 +30,11 @@ public sealed partial class Thread

private volatile int _threadState = (int)ThreadState.Unstarted;
private ThreadPriority _priority;
[CdacField("ManagedThreadId")]
private ManagedThreadId _managedThreadId;
[CdacField("Name")]
private string? _name;
[CdacField("StartHelper")]
private StartHelper? _startHelper;
private Exception? _startException;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
// 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 System.Text;

using Internal.Text;
using Internal.TypeSystem;

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.
///
/// The NativeAOT runtime C++ code declares an extern pointer to this symbol and references
/// it via CDAC_GLOBAL_SUB_DESCRIPTOR in datadescriptor.inc, enabling the cDAC reader to
/// merge managed type information into its unified type map.
/// </summary>
/// <remarks>
/// The emitted structure matches the ContractDescriptor format:
/// <code>
/// struct ContractDescriptor {
/// uint64_t magic; // 0x0043414443434e44 "DNCCDAC\0"
/// uint32_t flags; // Platform flags
/// uint32_t descriptor_size; // JSON blob size
/// char* descriptor; // Pointer to JSON string
/// uint32_t pointer_data_count;
/// uint32_t pad0;
/// void** pointer_data; // Pointer to auxiliary data array
/// };
/// </code>
/// The JSON descriptor follows the cDAC contract descriptor schema:
/// <code>
/// { "version": 0, "types": { "TypeName": [size, { "Field": offset }] }, "globals": {} }
/// </code>
/// </remarks>
public class ManagedDataDescriptorNode : ObjectNode, ISymbolDefinitionNode
{
public const string SymbolName = "DotNetManagedContractDescriptor";

private readonly List<ManagedTypeDescriptor> _typeDescriptors = new List<ManagedTypeDescriptor>();

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);

/// <summary>
/// Register a managed type to be included in the descriptor.
/// </summary>
/// <param name="descriptorTypeName">The cDAC type name (e.g., "ManagedIdDispenser")</param>
/// <param name="type">The resolved managed type from ILC's type system</param>
/// <param name="fieldMappings">Optional field name remapping: cDAC field name → managed field name.
/// If null, all instance fields are included with their original names.</param>
public void AddType(string descriptorTypeName, MetadataType type, Dictionary<string, string> fieldMappings = null)
{
_typeDescriptors.Add(new ManagedTypeDescriptor(descriptorTypeName, type, fieldMappings));
}

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

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.EmitInt(_jsonBytesLength);

// char* descriptor — pointer to JSON blob (separate compilation root)
builder.EmitPointerReloc(_jsonBlobNode);

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

// uint32_t pad0
builder.EmitInt(0);

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

return builder.ToObjectData();
}

/// <summary>
/// Build the JSON and create the blob node. Must be called before the node
/// is added to the dependency graph.
/// </summary>
public void FinalizeDescriptor()
{
string jsonDescriptor = BuildJsonDescriptor();
byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonDescriptor);
_jsonBytesLength = jsonBytes.Length;

byte[] nullTerminated = new byte[jsonBytes.Length + 1];
Array.Copy(jsonBytes, nullTerminated, jsonBytes.Length);
_jsonBlobNode = new BlobNode(
new Utf8String("__ManagedContractDescriptorJsonBlob"),
ObjectNodeSection.ReadOnlyDataSection,
nullTerminated,
alignment: 1);
}

/// <summary>
/// The blob node containing the JSON data. Add this as a separate compilation root.
/// </summary>
public BlobNode JsonBlobNode => _jsonBlobNode;

private BlobNode _jsonBlobNode;
private int _jsonBytesLength;

private string BuildJsonDescriptor()
{
var sb = new StringBuilder();
sb.Append("{\"version\":0,\"types\":{");

bool firstType = true;
foreach (var desc in _typeDescriptors)
{
if (!firstType)
sb.Append(',');
firstType = false;

EmitTypeJson(sb, desc);
}

sb.Append("},\"globals\":{}}");
return sb.ToString();
}

private static void EmitTypeJson(StringBuilder sb, ManagedTypeDescriptor desc)
{
MetadataType type = desc.Type;

// Use 0 (indeterminate) for reference types — their "size" from cDAC perspective
// is not meaningful since they're GC-managed objects.
int typeSize = type.IsValueType ? type.InstanceFieldSize.AsInt : 0;

// JSON format: "TypeName": [size, { "Field1": offset, "Field2": offset }]
sb.Append('"').Append(desc.DescriptorName).Append("\":[");
sb.Append(typeSize);
sb.Append(",{");

bool firstField = true;
foreach (FieldDesc field in type.GetFields())
{
if (field.IsStatic)
continue;

string fieldName = field.GetName();
string cdacFieldName;
if (desc.FieldMappings is not null)
{
// Check if any cDAC name maps to this managed field name
cdacFieldName = null;
foreach (var kvp in desc.FieldMappings)
{
if (kvp.Value == fieldName)
{
cdacFieldName = kvp.Key;
break;
}
}
if (cdacFieldName is null)
continue;
}
else
{
cdacFieldName = fieldName;
}

if (!firstField)
sb.Append(',');
firstField = false;

sb.Append('"').Append(cdacFieldName).Append("\":");
sb.Append(field.Offset.AsInt);
}

sb.Append("}]");
}

#if !SUPPORT_JIT
public override int ClassCode => 0x4d444e01;

public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
{
return 0; // Singleton
}
#endif

private readonly struct ManagedTypeDescriptor
{
public readonly string DescriptorName;
public readonly MetadataType Type;
public readonly Dictionary<string, string> FieldMappings;

public ManagedTypeDescriptor(string descriptorName, MetadataType type, Dictionary<string, string> fieldMappings)
{
DescriptorName = descriptorName;
Type = type;
FieldMappings = fieldMappings;
}
}
}
}
Loading
Loading