From 585727c555305db872d7ad21d40edf30035c5a68 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 16 Apr 2026 16:17:33 -0400
Subject: [PATCH 01/27] [NativeAOT] Add cDAC data descriptor infrastructure
Add the native cDAC data descriptor for NativeAOT, enabling diagnostic
tools (cDAC reader, SOS) to inspect NativeAOT runtime state through the
same contract-based mechanism used by CoreCLR.
Includes:
- datadescriptor.inc with Thread, ThreadStore, MethodTable, ExInfo,
EEAllocContext, GCAllocContext, RuntimeInstance types and globals
- CMake integration using shared clrdatadescriptors.cmake infrastructure
- GC sub-descriptor for workstation and server GC
- Contract declarations: Thread (1001), Exception (1), RuntimeTypeSystem (1001)
- Symbol export via --export-dynamic-symbol in build targets
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Microsoft.NETCore.Native.targets | 2 +
src/coreclr/nativeaot/Runtime/CMakeLists.txt | 1 +
.../nativeaot/Runtime/Full/CMakeLists.txt | 4 +-
.../nativeaot/Runtime/RuntimeInstance.h | 6 +
.../Runtime/datadescriptor/CMakeLists.txt | 48 ++++++
.../Runtime/datadescriptor/datadescriptor.h | 27 ++++
.../Runtime/datadescriptor/datadescriptor.inc | 140 ++++++++++++++++++
.../nativeaot/Runtime/inc/MethodTable.h | 23 +++
src/coreclr/nativeaot/Runtime/threadstore.h | 7 +
9 files changed, 256 insertions(+), 2 deletions(-)
create mode 100644 src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
create mode 100644 src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
create mode 100644 src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
index 2c1e91afc8ba16..b3ce3dc5f56cf1 100644
--- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
+++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
@@ -236,6 +236,8 @@ The .NET Foundation licenses this file to you under the MIT license.
+
+
diff --git a/src/coreclr/nativeaot/Runtime/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/CMakeLists.txt
index 9f3a80c702358e..b4f63dc5faca0d 100644
--- a/src/coreclr/nativeaot/Runtime/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/CMakeLists.txt
@@ -338,6 +338,7 @@ convert_to_absolute_path(VXSORT_SOURCES ${VXSORT_SOURCES})
convert_to_absolute_path(DUMMY_VXSORT_SOURCES ${DUMMY_VXSORT_SOURCES})
if(NOT CLR_CMAKE_TARGET_ARCH_WASM)
+ add_subdirectory(datadescriptor)
add_subdirectory(Full)
else()
add_subdirectory(Portable)
diff --git a/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
index 74cdeca700a1ae..56fc4a3d7d6e0e 100644
--- a/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
@@ -24,11 +24,11 @@ endif (CLR_CMAKE_TARGET_WIN32)
add_library(Runtime.WorkstationGC STATIC ${COMMON_RUNTIME_SOURCES} ${FULL_RUNTIME_SOURCES} ${RUNTIME_ARCH_ASM_OBJECTS})
add_dependencies(Runtime.WorkstationGC aot_eventing_headers)
-target_link_libraries(Runtime.WorkstationGC PRIVATE aotminipal)
+target_link_libraries(Runtime.WorkstationGC PRIVATE aotminipal nativeaot_cdac_contract_descriptor nativeaot_gc_wks_descriptor)
add_library(Runtime.ServerGC STATIC ${COMMON_RUNTIME_SOURCES} ${FULL_RUNTIME_SOURCES} ${SERVER_GC_SOURCES} ${RUNTIME_ARCH_ASM_OBJECTS})
add_dependencies(Runtime.ServerGC aot_eventing_headers)
-target_link_libraries(Runtime.ServerGC PRIVATE aotminipal)
+target_link_libraries(Runtime.ServerGC PRIVATE aotminipal nativeaot_cdac_contract_descriptor nativeaot_gc_svr_descriptor)
add_library(standalonegc-disabled STATIC ${STANDALONEGC_DISABLED_SOURCES})
add_dependencies(standalonegc-disabled aot_eventing_headers)
diff --git a/src/coreclr/nativeaot/Runtime/RuntimeInstance.h b/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
index 349bf1dbc7b578..2292f6b502e28f 100644
--- a/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
+++ b/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
@@ -12,6 +12,7 @@ enum GenericVarianceType : uint8_t;
#include "forward_declarations.h"
#include "ICodeManager.h"
+#include "cdacdata.h"
extern "C" void PopulateDebugHeaders();
@@ -20,6 +21,7 @@ class RuntimeInstance
friend class AsmOffsets;
friend class Thread;
friend void PopulateDebugHeaders();
+ friend struct ::cdac_data;
PTR_ThreadStore m_pThreadStore;
HANDLE m_hPalInstance; // this is the HANDLE passed into DllMain
@@ -110,6 +112,10 @@ class RuntimeInstance
};
typedef DPTR(RuntimeInstance) PTR_RuntimeInstance;
+template<> struct cdac_data
+{
+ static constexpr size_t ThreadStore = offsetof(RuntimeInstance, m_pThreadStore);
+};
PTR_RuntimeInstance GetRuntimeInstance();
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
new file mode 100644
index 00000000000000..feabf0b62d2c2a
--- /dev/null
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
@@ -0,0 +1,48 @@
+set(CMAKE_INCLUDE_CURRENT_DIR OFF)
+
+# cDAC contract descriptor for NativeAOT
+#
+# This uses the shared datadescriptor infrastructure from
+# src/coreclr/debug/datadescriptor-shared/ to generate a
+# DotNetRuntimeContractDescriptor symbol in the NativeAOT runtime.
+#
+# Include directories and compile definitions are inherited from the parent
+# Runtime/CMakeLists.txt directory scope. The interface targets below only
+# need to add the datadescriptor-specific include path.
+
+include(${CLR_DIR}/clrdatadescriptors.cmake)
+
+add_library(nativeaot_descriptor_interface INTERFACE)
+target_include_directories(nativeaot_descriptor_interface INTERFACE
+ ${CMAKE_CURRENT_SOURCE_DIR})
+add_dependencies(nativeaot_descriptor_interface Runtime.WorkstationGC)
+generate_data_descriptors(
+ LIBRARY_NAME nativeaot_cdac_contract_descriptor
+ CONTRACT_NAME "DotNetRuntimeContractDescriptor"
+ INTERFACE_TARGET nativeaot_descriptor_interface
+ EXPORT_VISIBLE)
+
+# GC contract descriptors (workstation + server).
+# The GC has its own data descriptor exposed as a sub-descriptor via gc_descriptor in GcDacVars.
+set(GC_DESCRIPTOR_DIR "${CLR_DIR}/gc/datadescriptor")
+
+add_library(nativeaot_gc_wks_descriptor_interface INTERFACE)
+target_include_directories(nativeaot_gc_wks_descriptor_interface INTERFACE
+ ${GC_DESCRIPTOR_DIR}
+ ${GC_DIR})
+add_dependencies(nativeaot_gc_wks_descriptor_interface Runtime.WorkstationGC)
+generate_data_descriptors(
+ LIBRARY_NAME nativeaot_gc_wks_descriptor
+ CONTRACT_NAME "GCContractDescriptorWKS"
+ INTERFACE_TARGET nativeaot_gc_wks_descriptor_interface)
+
+add_library(nativeaot_gc_svr_descriptor_interface INTERFACE)
+target_include_directories(nativeaot_gc_svr_descriptor_interface INTERFACE
+ ${GC_DESCRIPTOR_DIR}
+ ${GC_DIR})
+add_dependencies(nativeaot_gc_svr_descriptor_interface Runtime.WorkstationGC)
+target_compile_definitions(nativeaot_gc_svr_descriptor_interface INTERFACE -DSERVER_GC)
+generate_data_descriptors(
+ LIBRARY_NAME nativeaot_gc_svr_descriptor
+ CONTRACT_NAME "GCContractDescriptorSVR"
+ INTERFACE_TARGET nativeaot_gc_svr_descriptor_interface)
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
new file mode 100644
index 00000000000000..31f9f945e7f2cb
--- /dev/null
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
@@ -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.
+
+// This header provides the includes needed for datadescriptor.inc to use
+// offsetof() on NativeAOT runtime data structures.
+//
+// Note: Some NativeAOT types have private members that offsetof() cannot access
+// from this compilation unit. For those types, we use known offset constants
+// validated at build time by AsmOffsetsVerify.cpp and DebugHeader.cpp static_asserts.
+
+#include "common.h"
+#include "gcenv.h"
+#include "gcheaputilities.h"
+#include "gcinterface.dac.h"
+#include "rhassert.h"
+#include "TargetPtrs.h"
+#include "PalLimitedContext.h"
+#include "Pal.h"
+#include "holder.h"
+#include "RuntimeInstance.h"
+#include "regdisplay.h"
+#include "StackFrameIterator.h"
+#include "thread.h"
+#include "threadstore.h"
+
+#include
+#include
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
new file mode 100644
index 00000000000000..fa71924c6120f0
--- /dev/null
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
@@ -0,0 +1,140 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+//
+// No include guards. This file is included multiple times.
+//
+// NativeAOT data descriptor declarations for the cDAC contract system.
+// This file defines the types, fields, and globals that diagnostic tools
+// need to inspect NativeAOT runtime state.
+//
+// When modifying this file (adding/removing types, fields, or globals), you must also:
+// 1. Update the corresponding contract doc in docs/design/datacontracts/.md
+// 2. Update the managed data class in src/native/managed/cdac/.../Data/.cs
+// 3. Update the contract implementation in src/native/managed/cdac/.../Contracts/.cs
+// 4. Update the mock descriptors and tests in src/native/managed/cdac/tests/.
+
+CDAC_BASELINE("empty")
+CDAC_TYPES_BEGIN()
+
+// ========================
+// Thread and ThreadStore
+// ========================
+
+CDAC_TYPE_BEGIN(Thread)
+CDAC_TYPE_INDETERMINATE(Thread)
+CDAC_TYPE_FIELD(Thread, T_UINT64, OSId, offsetof(RuntimeThreadLocals, m_threadId))
+CDAC_TYPE_FIELD(Thread, T_UINT32, State, offsetof(RuntimeThreadLocals, m_ThreadStateFlags))
+CDAC_TYPE_FIELD(Thread, T_POINTER, LinkNext, offsetof(RuntimeThreadLocals, m_pNext))
+CDAC_TYPE_FIELD(Thread, T_POINTER, ExceptionTracker, offsetof(RuntimeThreadLocals, m_pExInfoStackHead))
+CDAC_TYPE_FIELD(Thread, T_POINTER, CachedStackBase, offsetof(RuntimeThreadLocals, m_pStackHigh))
+CDAC_TYPE_FIELD(Thread, T_POINTER, CachedStackLimit, offsetof(RuntimeThreadLocals, m_pStackLow))
+CDAC_TYPE_FIELD(Thread, TYPE(RuntimeThreadLocals), RuntimeThreadLocals, offsetof(RuntimeThreadLocals, m_eeAllocContext))
+CDAC_TYPE_FIELD(Thread, T_POINTER, TransitionFrame, offsetof(RuntimeThreadLocals, m_pTransitionFrame))
+CDAC_TYPE_END(Thread)
+
+CDAC_TYPE_BEGIN(ThreadStore)
+CDAC_TYPE_INDETERMINATE(ThreadStore)
+CDAC_TYPE_FIELD(ThreadStore, T_POINTER, FirstThreadLink, cdac_data::FirstThreadLink)
+CDAC_TYPE_END(ThreadStore)
+
+CDAC_TYPE_BEGIN(RuntimeThreadLocals)
+CDAC_TYPE_INDETERMINATE(RuntimeThreadLocals)
+CDAC_TYPE_FIELD(RuntimeThreadLocals, TYPE(EEAllocContext), AllocContext, offsetof(RuntimeThreadLocals, m_eeAllocContext))
+CDAC_TYPE_END(RuntimeThreadLocals)
+
+// ========================
+// Allocation Context
+// ========================
+
+CDAC_TYPE_BEGIN(EEAllocContext)
+CDAC_TYPE_INDETERMINATE(EEAllocContext)
+CDAC_TYPE_FIELD(EEAllocContext, TYPE(GCAllocContext), GCAllocationContext, offsetof(ee_alloc_context, m_rgbAllocContextBuffer))
+CDAC_TYPE_END(EEAllocContext)
+
+CDAC_TYPE_BEGIN(GCAllocContext)
+CDAC_TYPE_INDETERMINATE(GCAllocContext)
+CDAC_TYPE_FIELD(GCAllocContext, T_POINTER, Pointer, offsetof(gc_alloc_context, alloc_ptr))
+CDAC_TYPE_FIELD(GCAllocContext, T_POINTER, Limit, offsetof(gc_alloc_context, alloc_limit))
+CDAC_TYPE_FIELD(GCAllocContext, T_INT64, AllocBytes, offsetof(gc_alloc_context, alloc_bytes))
+CDAC_TYPE_FIELD(GCAllocContext, T_INT64, AllocBytesLoh, offsetof(gc_alloc_context, alloc_bytes_uoh))
+CDAC_TYPE_END(GCAllocContext)
+
+// ========================
+// MethodTable (EEType)
+// ========================
+
+CDAC_TYPE_BEGIN(MethodTable)
+CDAC_TYPE_INDETERMINATE(MethodTable)
+CDAC_TYPE_FIELD(MethodTable, T_UINT32, Flags, cdac_data::Flags)
+CDAC_TYPE_FIELD(MethodTable, T_UINT32, BaseSize, cdac_data::BaseSize)
+CDAC_TYPE_FIELD(MethodTable, T_POINTER, RelatedType, cdac_data::RelatedType)
+CDAC_TYPE_FIELD(MethodTable, T_UINT16, NumVtableSlots, cdac_data::NumVtableSlots)
+CDAC_TYPE_FIELD(MethodTable, T_UINT16, NumInterfaces, cdac_data::NumInterfaces)
+CDAC_TYPE_FIELD(MethodTable, T_UINT32, HashCode, cdac_data::HashCode)
+CDAC_TYPE_FIELD(MethodTable, T_POINTER, VTable, cdac_data::VTable)
+CDAC_TYPE_END(MethodTable)
+
+// ========================
+// Exception Info
+// ========================
+
+CDAC_TYPE_BEGIN(ExInfo)
+CDAC_TYPE_INDETERMINATE(ExInfo)
+CDAC_TYPE_FIELD(ExInfo, T_POINTER, PreviousNestedInfo, offsetof(ExInfo, m_pPrevExInfo))
+CDAC_TYPE_FIELD(ExInfo, T_POINTER, ThrownObject, offsetof(ExInfo, m_exception))
+CDAC_TYPE_END(ExInfo)
+
+// ========================
+// RuntimeInstance
+// ========================
+
+CDAC_TYPE_BEGIN(RuntimeInstance)
+CDAC_TYPE_INDETERMINATE(RuntimeInstance)
+CDAC_TYPE_FIELD(RuntimeInstance, T_POINTER, ThreadStore, cdac_data::ThreadStore)
+CDAC_TYPE_END(RuntimeInstance)
+
+CDAC_TYPES_END()
+
+// ========================
+// Globals
+// ========================
+
+CDAC_GLOBALS_BEGIN()
+
+CDAC_GLOBAL_POINTER(RuntimeInstance, &g_pTheRuntimeInstance)
+
+CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &g_pFreeObjectEEType)
+
+CDAC_GLOBAL_POINTER(GCLowestAddress, &g_lowest_address)
+CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address)
+
+// NativeAOT MethodTable flag constants (accessed via cdac_data friend)
+CDAC_GLOBAL(MethodTableEETypeKindMask, uint32, cdac_data::EETypeKindMask)
+CDAC_GLOBAL(MethodTableHasComponentSizeFlag, uint32, cdac_data::HasComponentSizeFlag)
+CDAC_GLOBAL(MethodTableHasFinalizerFlag, uint32, cdac_data::HasFinalizerFlag)
+CDAC_GLOBAL(MethodTableHasPointersFlag, uint32, cdac_data::HasPointersFlag)
+CDAC_GLOBAL(MethodTableIsGenericFlag, uint32, cdac_data::IsGenericFlag)
+CDAC_GLOBAL(MethodTableElementTypeMask, uint32, cdac_data::ElementTypeMask)
+CDAC_GLOBAL(MethodTableElementTypeShift, uint32, cdac_data::ElementTypeShift)
+
+// Thread state flag constants
+CDAC_GLOBAL(ThreadStateFlagAttached, uint32, 0x00000001)
+CDAC_GLOBAL(ThreadStateFlagDetached, uint32, 0x00000002)
+
+// Object contract globals
+#ifdef TARGET_64BIT
+CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 7)
+#else
+CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 3)
+#endif
+
+// Contracts: declare which contracts this runtime supports
+CDAC_GLOBAL_CONTRACT(Thread, 1001)
+CDAC_GLOBAL_CONTRACT(Exception, 1)
+CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, 1001)
+
+// 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))
+
+CDAC_GLOBALS_END()
diff --git a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h
index 89ff445f6174d9..77c2ceec275a4b 100644
--- a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h
+++ b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h
@@ -12,6 +12,9 @@ class MethodTable;
class TypeManager;
struct TypeManagerHandle;
+// cdac_data template for exposing private members to the cDAC data descriptor.
+#include "cdacdata.h"
+
//-------------------------------------------------------------------------------------------------
// The subset of TypeFlags that NativeAOT knows about at runtime
// This should match the TypeFlags enum in the managed type system.
@@ -86,6 +89,7 @@ class MethodTable
{
friend class AsmOffsets;
friend void PopulateDebugHeaders();
+ friend struct ::cdac_data;
private:
struct RelatedTypeUnion
@@ -346,4 +350,23 @@ class MethodTable
UInt32_BOOL SanityCheck() { return Validate(); }
};
+template<> struct cdac_data
+{
+ static constexpr size_t Flags = offsetof(MethodTable, m_uFlags);
+ static constexpr size_t BaseSize = offsetof(MethodTable, m_uBaseSize);
+ static constexpr size_t RelatedType = offsetof(MethodTable, m_RelatedType);
+ static constexpr size_t NumVtableSlots = offsetof(MethodTable, m_usNumVtableSlots);
+ static constexpr size_t NumInterfaces = offsetof(MethodTable, m_usNumInterfaces);
+ static constexpr size_t HashCode = offsetof(MethodTable, m_uHashCode);
+ static constexpr size_t VTable = offsetof(MethodTable, m_VTable);
+
+ static constexpr uint32_t EETypeKindMask = MethodTable::EETypeKindMask;
+ static constexpr uint32_t HasComponentSizeFlag = MethodTable::HasComponentSizeFlag;
+ static constexpr uint32_t HasFinalizerFlag = MethodTable::HasFinalizerFlag;
+ static constexpr uint32_t HasPointersFlag = MethodTable::HasPointersFlag;
+ static constexpr uint32_t IsGenericFlag = MethodTable::IsGenericFlag;
+ static constexpr uint32_t ElementTypeMask = MethodTable::ElementTypeMask;
+ static constexpr uint32_t ElementTypeShift = MethodTable::ElementTypeShift;
+};
+
#pragma warning(pop)
diff --git a/src/coreclr/nativeaot/Runtime/threadstore.h b/src/coreclr/nativeaot/Runtime/threadstore.h
index d2347f9a631ffa..f87730e08fc574 100644
--- a/src/coreclr/nativeaot/Runtime/threadstore.h
+++ b/src/coreclr/nativeaot/Runtime/threadstore.h
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
#include "Crst.h"
+#include "cdacdata.h"
class Thread;
class CLREventStatic;
@@ -20,6 +21,7 @@ extern "C" void PopulateDebugHeaders();
class ThreadStore
{
friend void PopulateDebugHeaders();
+ friend struct ::cdac_data;
SList m_ThreadList;
PTR_RuntimeInstance m_pRuntimeInstance;
@@ -68,6 +70,11 @@ class ThreadStore
};
typedef DPTR(ThreadStore) PTR_ThreadStore;
+template<> struct cdac_data
+{
+ static constexpr size_t FirstThreadLink = offsetof(ThreadStore, m_ThreadList);
+};
+
ThreadStore * GetThreadStore();
#define FOREACH_THREAD(p_thread_name) \
From 1af663bdceb2d07f0ce57d3697e4776d812ea159 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 16 Apr 2026 16:18:12 -0400
Subject: [PATCH 02/27] [NativeAOT] Add managed type sub-descriptor for cDAC
Add ILC infrastructure to emit managed type field offsets as a cDAC
sub-descriptor, enabling diagnostic tools to inspect managed type
instances without runtime metadata.
Types opt-in via [CdacType] and [CdacField] attributes in CoreLib.
ILC discovers annotated types at compile time, computes field offsets,
and emits a ContractDescriptor (DotNetManagedContractDescriptor) that
the native runtime references as a sub-descriptor.
Includes:
- CdacTypeAttribute / CdacFieldAttribute in NativeAOT CoreLib
- ManagedDataDescriptorNode: emits ContractDescriptor with JSON layout
- ManagedDataDescriptorProvider: attribute-based type discovery with
validation (rejects generics, duplicates, empty names)
- Thread.NativeAot.cs annotated with ManagedThread descriptor fields
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Runtime/datadescriptor/datadescriptor.h | 7 +
.../Runtime/datadescriptor/datadescriptor.inc | 6 +
.../src/System.Private.CoreLib.csproj | 2 +
.../CompilerServices/CdacFieldAttribute.cs | 27 +++
.../CompilerServices/CdacTypeAttribute.cs | 28 +++
.../src/System/Threading/Thread.NativeAot.cs | 4 +
.../ManagedDataDescriptorNode.cs | 228 ++++++++++++++++++
.../Compiler/ManagedDataDescriptorProvider.cs | 102 ++++++++
.../ILCompiler.Compiler.csproj | 2 +
src/coreclr/tools/aot/ILCompiler/Program.cs | 2 +
10 files changed, 408 insertions(+)
create mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacFieldAttribute.cs
create mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs
create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
index 31f9f945e7f2cb..21f9bfd08fc80f 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
@@ -25,3 +25,10 @@
#include
#include
+
+// 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;
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
index fa71924c6120f0..7f477cebe2debd 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
@@ -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))
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj
index d120f10da497ac..bd0999730238a7 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj
@@ -195,6 +195,8 @@
+
+
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacFieldAttribute.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacFieldAttribute.cs
new file mode 100644
index 00000000000000..bb1e3e033227c5
--- /dev/null
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacFieldAttribute.cs
@@ -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
+{
+ ///
+ /// When applied to an instance field of a type annotated with ,
+ /// indicates that ILC should include this field in the managed cDAC data descriptor.
+ ///
+ [AttributeUsage(AttributeTargets.Field, Inherited = false)]
+ internal sealed class CdacFieldAttribute : Attribute
+ {
+ public CdacFieldAttribute()
+ {
+ }
+
+ public CdacFieldAttribute(string name)
+ {
+ Name = name;
+ }
+
+ ///
+ /// The cDAC descriptor field name. If not specified, the field's declared name is used.
+ ///
+ public string? Name { get; }
+ }
+}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs
new file mode 100644
index 00000000000000..f8a73e89c7eb9a
--- /dev/null
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs
@@ -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
+{
+ ///
+ /// 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).
+ ///
+ ///
+ /// Fields to include must be individually annotated with .
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
+ internal sealed class CdacTypeAttribute : Attribute
+ {
+ public CdacTypeAttribute(string name)
+ {
+ Name = name;
+ }
+
+ ///
+ /// The cDAC descriptor type name (e.g., "ManagedThread").
+ ///
+ public string Name { get; }
+ }
+}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs
index 3f7aa4ffc429df..ea5d063f73bc3e 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs
@@ -13,6 +13,7 @@
namespace System.Threading
{
+ [CdacType("ManagedThread")]
public sealed partial class Thread
{
// Extra bits used in _threadState
@@ -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;
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
new file mode 100644
index 00000000000000..38ec3d580eede8
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -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
+{
+ ///
+ /// 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.
+ ///
+ ///
+ /// The emitted structure matches the ContractDescriptor format:
+ ///
+ /// 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
+ /// };
+ ///
+ /// The JSON descriptor follows the cDAC contract descriptor schema:
+ ///
+ /// { "version": 0, "types": { "TypeName": [size, { "Field": offset }] }, "globals": {} }
+ ///
+ ///
+ public class ManagedDataDescriptorNode : ObjectNode, ISymbolDefinitionNode
+ {
+ public const string SymbolName = "DotNetManagedContractDescriptor";
+
+ private readonly List _typeDescriptors = new List();
+
+ 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);
+
+ ///
+ /// Register a managed type to be included in the descriptor.
+ ///
+ /// The cDAC type name (e.g., "ManagedIdDispenser")
+ /// The resolved managed type from ILC's type system
+ /// Optional field name remapping: cDAC field name → managed field name.
+ /// If null, all instance fields are included with their original names.
+ public void AddType(string descriptorTypeName, MetadataType type, Dictionary 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(), Array.Empty(), 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();
+ }
+
+ ///
+ /// Build the JSON and create the blob node. Must be called before the node
+ /// is added to the dependency graph.
+ ///
+ 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);
+ }
+
+ ///
+ /// The blob node containing the JSON data. Add this as a separate compilation root.
+ ///
+ 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 FieldMappings;
+
+ public ManagedTypeDescriptor(string descriptorName, MetadataType type, Dictionary fieldMappings)
+ {
+ DescriptorName = descriptorName;
+ Type = type;
+ FieldMappings = fieldMappings;
+ }
+ }
+ }
+}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs
new file mode 100644
index 00000000000000..215dde5cab4dfc
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs
@@ -0,0 +1,102 @@
+// 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.Reflection.Metadata;
+
+using ILCompiler.DependencyAnalysis;
+
+using Internal.TypeSystem;
+using Internal.TypeSystem.Ecma;
+
+namespace ILCompiler
+{
+ ///
+ /// Compilation root provider that emits managed type layout descriptors for the cDAC.
+ /// Types annotated with [CdacType] will have their [CdacField]-annotated field offsets
+ /// computed at compile time and embedded in the final binary as a ContractDescriptor
+ /// that the cDAC reader merges as a sub-descriptor.
+ ///
+ ///
+ /// The descriptor is emitted as a symbol named "DotNetManagedContractDescriptor"
+ /// that the NativeAOT runtime's C++ code references via extern and stores as a
+ /// sub-descriptor pointer in the main contract descriptor.
+ ///
+ public class ManagedDataDescriptorProvider : ICompilationRootProvider
+ {
+ private const string CdacTypeAttributeNamespace = "System.Runtime.CompilerServices";
+ private const string CdacTypeAttributeName = "CdacTypeAttribute";
+ private const string CdacFieldAttributeName = "CdacFieldAttribute";
+
+ private readonly CompilerTypeSystemContext _context;
+
+ public ManagedDataDescriptorProvider(CompilerTypeSystemContext context)
+ {
+ _context = context;
+ }
+
+ void ICompilationRootProvider.AddCompilationRoots(IRootingServiceProvider rootProvider)
+ {
+ var descriptorNode = new ManagedDataDescriptorNode();
+
+ DiscoverAnnotatedTypes(descriptorNode);
+ descriptorNode.FinalizeDescriptor();
+
+ rootProvider.AddCompilationRoot(descriptorNode, "Managed type descriptors for cDAC");
+ rootProvider.AddCompilationRoot(descriptorNode.JsonBlobNode, "Managed descriptor JSON data");
+ }
+
+ private void DiscoverAnnotatedTypes(ManagedDataDescriptorNode descriptorNode)
+ {
+ if (_context.SystemModule is not EcmaModule systemModule)
+ return;
+
+ var seenDescriptorNames = new HashSet();
+ MetadataReader reader = systemModule.MetadataReader;
+
+ foreach (TypeDefinitionHandle typeDefHandle in reader.TypeDefinitions)
+ {
+ EcmaType ecmaType = (EcmaType)systemModule.GetType(typeDefHandle);
+ var typeAttr = ecmaType.GetDecodedCustomAttribute(CdacTypeAttributeNamespace, CdacTypeAttributeName);
+ if (typeAttr is null)
+ continue;
+
+ string descriptorTypeName = (string)typeAttr.Value.FixedArguments[0].Value;
+
+ if (string.IsNullOrEmpty(descriptorTypeName))
+ throw new InvalidOperationException($"[CdacType] on '{ecmaType}' has a null or empty descriptor name.");
+
+ if (ecmaType.HasInstantiation)
+ throw new InvalidOperationException($"[CdacType] is not supported on generic type '{ecmaType}'.");
+
+ if (!seenDescriptorNames.Add(descriptorTypeName))
+ throw new InvalidOperationException($"Duplicate [CdacType] descriptor name '{descriptorTypeName}' on '{ecmaType}'.");
+
+ var fieldMappings = new Dictionary();
+ foreach (FieldDesc field in ecmaType.GetFields())
+ {
+ if (field.IsStatic || field is not EcmaField ecmaField)
+ continue;
+
+ var fieldAttr = ecmaField.GetDecodedCustomAttribute(CdacTypeAttributeNamespace, CdacFieldAttributeName);
+ if (fieldAttr is null)
+ continue;
+
+ string cdacFieldName = fieldAttr.Value.FixedArguments.Length > 0
+ && fieldAttr.Value.FixedArguments[0].Value is string name
+ ? name
+ : field.GetName();
+
+ if (!fieldMappings.TryAdd(cdacFieldName, field.GetName()))
+ throw new InvalidOperationException($"Duplicate [CdacField] name '{cdacFieldName}' on type '{ecmaType}'.");
+ }
+
+ if (fieldMappings.Count > 0)
+ {
+ descriptorNode.AddType(descriptorTypeName, (MetadataType)ecmaType, fieldMappings);
+ }
+ }
+ }
+ }
+}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
index a0e5f77c7c533b..791eb295455999 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
@@ -596,6 +596,7 @@
+
@@ -689,6 +690,7 @@
+
diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs
index 9786ffc489b473..e59a518e4a8965 100644
--- a/src/coreclr/tools/aot/ILCompiler/Program.cs
+++ b/src/coreclr/tools/aot/ILCompiler/Program.cs
@@ -263,6 +263,7 @@ public int Run()
compilationRoots.Add(new RuntimeConfigurationRootProvider(settingsBlobName, runtimeOptions));
compilationRoots.Add(new RuntimeConfigurationRootProvider(knobsBlobName, runtimeKnobs));
compilationRoots.Add(new ExpectedIsaFeaturesRootProvider(instructionSetSupport));
+ compilationRoots.Add(new ManagedDataDescriptorProvider(typeSystemContext));
if (SplitExeInitialization)
{
compilationRoots.Add(new MainMethodRootProvider(entrypointModule, CreateInitializerList(typeSystemContext), generateLibraryAndModuleInitializers: false));
@@ -274,6 +275,7 @@ public int Run()
compilationRoots.Add(new RuntimeConfigurationRootProvider(settingsBlobName, runtimeOptions));
compilationRoots.Add(new RuntimeConfigurationRootProvider(knobsBlobName, runtimeKnobs));
compilationRoots.Add(new ExpectedIsaFeaturesRootProvider(instructionSetSupport));
+ compilationRoots.Add(new ManagedDataDescriptorProvider(typeSystemContext));
if (SplitExeInitialization)
{
compilationRoots.Add(new NativeLibraryInitializerRootProvider(typeSystemContext.GeneratedAssembly, CreateInitializerList(typeSystemContext)));
From 5c322ca4e0efddf10a60355345df2364a9e6fa74 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 16 Apr 2026 17:29:31 -0400
Subject: [PATCH 03/27] Address PR review feedback
- Move ThreadStore from RuntimeInstance to direct global pointer (jkotas)
- Update doc comment to clarify SystemModule-only scope (copilot-bot)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp | 2 ++
src/coreclr/nativeaot/Runtime/RuntimeInstance.h | 7 -------
.../nativeaot/Runtime/datadescriptor/CMakeLists.txt | 3 ---
.../nativeaot/Runtime/datadescriptor/datadescriptor.h | 2 ++
.../Runtime/datadescriptor/datadescriptor.inc | 11 +----------
.../Compiler/ManagedDataDescriptorProvider.cs | 7 ++++---
6 files changed, 9 insertions(+), 23 deletions(-)
diff --git a/src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp b/src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp
index 74a930a0e9bdc8..3c6981b97fd2ac 100644
--- a/src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp
+++ b/src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp
@@ -161,6 +161,7 @@ PTR_uint8_t RuntimeInstance::GetTargetOfUnboxingAndInstantiatingStub(PTR_VOID Co
}
GPTR_IMPL_INIT(RuntimeInstance, g_pTheRuntimeInstance, NULL);
+GPTR_IMPL_INIT(ThreadStore, g_pThreadStore, NULL);
// WARNING: This method is called by suspension while one thread is interrupted
// in a random location, possibly holding random locks.
@@ -319,6 +320,7 @@ bool RuntimeInstance::Initialize(HANDLE hPalInstance)
ASSERT_MSG(g_pTheRuntimeInstance == NULL, "multi-instances are not supported");
g_pTheRuntimeInstance = pRuntimeInstance;
+ g_pThreadStore = pThreadStore;
return true;
}
diff --git a/src/coreclr/nativeaot/Runtime/RuntimeInstance.h b/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
index 2292f6b502e28f..a7ceb5e6b0ae54 100644
--- a/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
+++ b/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
@@ -12,7 +12,6 @@ enum GenericVarianceType : uint8_t;
#include "forward_declarations.h"
#include "ICodeManager.h"
-#include "cdacdata.h"
extern "C" void PopulateDebugHeaders();
@@ -21,7 +20,6 @@ class RuntimeInstance
friend class AsmOffsets;
friend class Thread;
friend void PopulateDebugHeaders();
- friend struct ::cdac_data;
PTR_ThreadStore m_pThreadStore;
HANDLE m_hPalInstance; // this is the HANDLE passed into DllMain
@@ -112,11 +110,6 @@ class RuntimeInstance
};
typedef DPTR(RuntimeInstance) PTR_RuntimeInstance;
-template<> struct cdac_data
-{
- static constexpr size_t ThreadStore = offsetof(RuntimeInstance, m_pThreadStore);
-};
-
PTR_RuntimeInstance GetRuntimeInstance();
#endif // __RuntimeInstance_h__
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
index feabf0b62d2c2a..4d082efc5659cd 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
@@ -15,7 +15,6 @@ include(${CLR_DIR}/clrdatadescriptors.cmake)
add_library(nativeaot_descriptor_interface INTERFACE)
target_include_directories(nativeaot_descriptor_interface INTERFACE
${CMAKE_CURRENT_SOURCE_DIR})
-add_dependencies(nativeaot_descriptor_interface Runtime.WorkstationGC)
generate_data_descriptors(
LIBRARY_NAME nativeaot_cdac_contract_descriptor
CONTRACT_NAME "DotNetRuntimeContractDescriptor"
@@ -30,7 +29,6 @@ add_library(nativeaot_gc_wks_descriptor_interface INTERFACE)
target_include_directories(nativeaot_gc_wks_descriptor_interface INTERFACE
${GC_DESCRIPTOR_DIR}
${GC_DIR})
-add_dependencies(nativeaot_gc_wks_descriptor_interface Runtime.WorkstationGC)
generate_data_descriptors(
LIBRARY_NAME nativeaot_gc_wks_descriptor
CONTRACT_NAME "GCContractDescriptorWKS"
@@ -40,7 +38,6 @@ add_library(nativeaot_gc_svr_descriptor_interface INTERFACE)
target_include_directories(nativeaot_gc_svr_descriptor_interface INTERFACE
${GC_DESCRIPTOR_DIR}
${GC_DIR})
-add_dependencies(nativeaot_gc_svr_descriptor_interface Runtime.WorkstationGC)
target_compile_definitions(nativeaot_gc_svr_descriptor_interface INTERFACE -DSERVER_GC)
generate_data_descriptors(
LIBRARY_NAME nativeaot_gc_svr_descriptor
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
index 21f9bfd08fc80f..32751b98a67b06 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
@@ -26,6 +26,8 @@
#include
#include
+GPTR_DECL(ThreadStore, g_pThreadStore);
+
// 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.
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
index 7f477cebe2debd..e49733b56b114d 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
@@ -84,15 +84,6 @@ CDAC_TYPE_FIELD(ExInfo, T_POINTER, PreviousNestedInfo, offsetof(ExInfo, m_pPrevE
CDAC_TYPE_FIELD(ExInfo, T_POINTER, ThrownObject, offsetof(ExInfo, m_exception))
CDAC_TYPE_END(ExInfo)
-// ========================
-// RuntimeInstance
-// ========================
-
-CDAC_TYPE_BEGIN(RuntimeInstance)
-CDAC_TYPE_INDETERMINATE(RuntimeInstance)
-CDAC_TYPE_FIELD(RuntimeInstance, T_POINTER, ThreadStore, cdac_data::ThreadStore)
-CDAC_TYPE_END(RuntimeInstance)
-
CDAC_TYPES_END()
// ========================
@@ -101,7 +92,7 @@ CDAC_TYPES_END()
CDAC_GLOBALS_BEGIN()
-CDAC_GLOBAL_POINTER(RuntimeInstance, &g_pTheRuntimeInstance)
+CDAC_GLOBAL_POINTER(ThreadStore, &g_pThreadStore)
CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &g_pFreeObjectEEType)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs
index 215dde5cab4dfc..107b7f95963835 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs
@@ -14,9 +14,10 @@ namespace ILCompiler
{
///
/// Compilation root provider that emits managed type layout descriptors for the cDAC.
- /// Types annotated with [CdacType] will have their [CdacField]-annotated field offsets
- /// computed at compile time and embedded in the final binary as a ContractDescriptor
- /// that the cDAC reader merges as a sub-descriptor.
+ /// Types in the system module (System.Private.CoreLib) annotated with [CdacType] will
+ /// have their [CdacField]-annotated field offsets computed at compile time and embedded
+ /// in the final binary as a ContractDescriptor that the cDAC reader merges as a
+ /// sub-descriptor.
///
///
/// The descriptor is emitted as a symbol named "DotNetManagedContractDescriptor"
From 56493a533be32e6bf521481ed6dca7fed0c2a68f Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Fri, 17 Apr 2026 12:41:36 -0400
Subject: [PATCH 04/27] Enable cdac-build-tool for NativeAOT builds
Add ClrNativeAotSubset to the HasCdacBuildTool condition in runtime.proj
so the cdac-build-tool processes datadescriptor.inc for NativeAOT instead
of linking a stub contract descriptor. Add missing GPTR_DECL for
g_pFreeObjectEEType in datadescriptor.h.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h | 1 +
src/coreclr/runtime.proj | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
index 32751b98a67b06..fc29972fb0b545 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
@@ -27,6 +27,7 @@
#include
GPTR_DECL(ThreadStore, g_pThreadStore);
+GPTR_DECL(MethodTable, g_pFreeObjectEEType);
// ILC emits a ContractDescriptor named "DotNetManagedContractDescriptor" with
// managed type layouts. We take its address so datadescriptor.inc can reference
diff --git a/src/coreclr/runtime.proj b/src/coreclr/runtime.proj
index 5d085be67b1061..20dd74882a1c2d 100644
--- a/src/coreclr/runtime.proj
+++ b/src/coreclr/runtime.proj
@@ -3,7 +3,7 @@
<_BuildNativeTargetOS>$(TargetOS)
<_BuildNativeTargetOS Condition="'$(TargetsLinuxBionic)' == 'true'">linux-bionic
- true
+ true
GetPgoDataPackagePath
AcquireEmscriptenSdk;$(BuildRuntimeDependsOnTargets);GenerateEmccExports
From 1101d7da639d2f37ca3e11acb578867fc1307435 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Fri, 17 Apr 2026 15:14:57 -0400
Subject: [PATCH 05/27] Address round 2 review feedback
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Refactor ManagedDataDescriptorNode to use MetadataManager.GetTypesWithEETypes()
for type discovery (Michal), ensuring only types with MethodTables are included
- Remove name overriding from CdacTypeAttribute and CdacFieldAttribute (Jan),
types and fields are published under their actual managed names
- Remove StartHelper field annotation (Jan) — not used in diagnostics
- Fix Linux build break: add SKIP_TRACING_DEFINITIONS to datadescriptor
CMake interface to avoid clretwallmain.h dependency
- Embed JSON inline in descriptor node instead of separate BlobNode
- Restore RuntimeInstance.h whitespace
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../nativeaot/Runtime/RuntimeInstance.h | 1 +
.../Runtime/datadescriptor/CMakeLists.txt | 1 +
.../CompilerServices/CdacFieldAttribute.cs | 14 +-
.../CompilerServices/CdacTypeAttribute.cs | 10 +-
.../src/System/Threading/Thread.NativeAot.cs | 7 +-
.../ManagedDataDescriptorNode.cs | 152 +++++-------------
.../Compiler/ManagedDataDescriptorProvider.cs | 88 +---------
src/coreclr/tools/aot/ILCompiler/Program.cs | 4 +-
8 files changed, 51 insertions(+), 226 deletions(-)
diff --git a/src/coreclr/nativeaot/Runtime/RuntimeInstance.h b/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
index a7ceb5e6b0ae54..349bf1dbc7b578 100644
--- a/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
+++ b/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
@@ -110,6 +110,7 @@ class RuntimeInstance
};
typedef DPTR(RuntimeInstance) PTR_RuntimeInstance;
+
PTR_RuntimeInstance GetRuntimeInstance();
#endif // __RuntimeInstance_h__
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
index 4d082efc5659cd..45640e40ba9023 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
@@ -15,6 +15,7 @@ include(${CLR_DIR}/clrdatadescriptors.cmake)
add_library(nativeaot_descriptor_interface INTERFACE)
target_include_directories(nativeaot_descriptor_interface INTERFACE
${CMAKE_CURRENT_SOURCE_DIR})
+target_compile_definitions(nativeaot_descriptor_interface INTERFACE SKIP_TRACING_DEFINITIONS)
generate_data_descriptors(
LIBRARY_NAME nativeaot_cdac_contract_descriptor
CONTRACT_NAME "DotNetRuntimeContractDescriptor"
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacFieldAttribute.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacFieldAttribute.cs
index bb1e3e033227c5..d23a1e58d6e229 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacFieldAttribute.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacFieldAttribute.cs
@@ -6,22 +6,10 @@ namespace System.Runtime.CompilerServices
///
/// When applied to an instance field of a type annotated with ,
/// indicates that ILC should include this field in the managed cDAC data descriptor.
+ /// The field's actual declared name is used in the descriptor.
///
[AttributeUsage(AttributeTargets.Field, Inherited = false)]
internal sealed class CdacFieldAttribute : Attribute
{
- public CdacFieldAttribute()
- {
- }
-
- public CdacFieldAttribute(string name)
- {
- Name = name;
- }
-
- ///
- /// The cDAC descriptor field name. If not specified, the field's declared name is used.
- ///
- public string? Name { get; }
}
}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs
index f8a73e89c7eb9a..ae5c48ceae2efe 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs
@@ -11,18 +11,10 @@ namespace System.Runtime.CompilerServices
///
///
/// Fields to include must be individually annotated with .
+ /// The type and field names used in the descriptor match the actual managed names.
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
internal sealed class CdacTypeAttribute : Attribute
{
- public CdacTypeAttribute(string name)
- {
- Name = name;
- }
-
- ///
- /// The cDAC descriptor type name (e.g., "ManagedThread").
- ///
- public string Name { get; }
}
}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs
index ea5d063f73bc3e..d7ed876309391c 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs
@@ -13,7 +13,7 @@
namespace System.Threading
{
- [CdacType("ManagedThread")]
+ [CdacType]
public sealed partial class Thread
{
// Extra bits used in _threadState
@@ -30,11 +30,10 @@ public sealed partial class Thread
private volatile int _threadState = (int)ThreadState.Unstarted;
private ThreadPriority _priority;
- [CdacField("ManagedThreadId")]
+ [CdacField]
private ManagedThreadId _managedThreadId;
- [CdacField("Name")]
+ [CdacField]
private string? _name;
- [CdacField("StartHelper")]
private StartHelper? _startHelper;
private Exception? _startException;
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
index 38ec3d580eede8..a84fc7b0732d6a 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -2,11 +2,11 @@
// 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;
+using Internal.TypeSystem.Ecma;
namespace ILCompiler.DependencyAnalysis
{
@@ -15,33 +15,17 @@ namespace ILCompiler.DependencyAnalysis
/// 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.
+ /// Types are discovered by scanning MetadataManager.GetTypesWithEETypes() for types
+ /// annotated with [CdacType], ensuring only types that actually have a MethodTable
+ /// in the binary are included.
///
- ///
- /// The emitted structure matches the ContractDescriptor format:
- ///
- /// 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
- /// };
- ///
- /// The JSON descriptor follows the cDAC contract descriptor schema:
- ///
- /// { "version": 0, "types": { "TypeName": [size, { "Field": offset }] }, "globals": {} }
- ///
- ///
public class ManagedDataDescriptorNode : ObjectNode, ISymbolDefinitionNode
{
- public const string SymbolName = "DotNetManagedContractDescriptor";
+ private const string CdacTypeAttributeNamespace = "System.Runtime.CompilerServices";
+ private const string CdacTypeAttributeName = "CdacTypeAttribute";
+ private const string CdacFieldAttributeName = "CdacFieldAttribute";
- private readonly List _typeDescriptors = new List();
+ public const string SymbolName = "DotNetManagedContractDescriptor";
public override ObjectNodeSection GetSection(NodeFactory factory) =>
factory.Target.IsWindows ? ObjectNodeSection.ReadOnlyDataSection : ObjectNodeSection.DataSection;
@@ -58,23 +42,17 @@ public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);
- ///
- /// Register a managed type to be included in the descriptor.
- ///
- /// The cDAC type name (e.g., "ManagedIdDispenser")
- /// The resolved managed type from ILC's type system
- /// Optional field name remapping: cDAC field name → managed field name.
- /// If null, all instance fields are included with their original names.
- public void AddType(string descriptorTypeName, MetadataType type, Dictionary 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(), Array.Empty(), 1, new ISymbolDefinitionNode[] { this });
+ string json = BuildJsonDescriptor(factory);
+ byte[] jsonBytes = Encoding.UTF8.GetBytes(json);
+
+ // Header layout: magic(8) + flags(4) + desc_size(4) + desc_ptr(ptr) + count(4) + pad(4) + data_ptr(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);
@@ -87,10 +65,10 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
builder.EmitUInt(flags);
// uint32_t descriptor_size
- builder.EmitInt(_jsonBytesLength);
+ builder.EmitInt(jsonBytes.Length);
- // char* descriptor — pointer to JSON blob (separate compilation root)
- builder.EmitPointerReloc(_jsonBlobNode);
+ // char* descriptor — points to inline JSON after the header
+ builder.EmitPointerReloc(this, headerSize);
// uint32_t pointer_data_count = 0
builder.EmitInt(0);
@@ -101,107 +79,69 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
// void** pointer_data = null
builder.EmitZeroPointer();
- return builder.ToObjectData();
- }
+ // Emit JSON bytes inline, null-terminated
+ builder.EmitBytes(jsonBytes);
+ builder.EmitByte(0);
- ///
- /// Build the JSON and create the blob node. Must be called before the node
- /// is added to the dependency graph.
- ///
- 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);
+ return builder.ToObjectData();
}
- ///
- /// The blob node containing the JSON data. Add this as a separate compilation root.
- ///
- public BlobNode JsonBlobNode => _jsonBlobNode;
-
- private BlobNode _jsonBlobNode;
- private int _jsonBytesLength;
-
- private string BuildJsonDescriptor()
+ private static string BuildJsonDescriptor(NodeFactory factory)
{
var sb = new StringBuilder();
sb.Append("{\"version\":0,\"types\":{");
bool firstType = true;
- foreach (var desc in _typeDescriptors)
+ foreach (TypeDesc type in factory.MetadataManager.GetTypesWithEETypes())
{
+ if (type is not EcmaType ecmaType)
+ continue;
+
+ if (!ecmaType.HasCustomAttribute(CdacTypeAttributeNamespace, CdacTypeAttributeName))
+ continue;
+
if (!firstType)
sb.Append(',');
firstType = false;
- EmitTypeJson(sb, desc);
+ EmitTypeJson(sb, ecmaType);
}
sb.Append("},\"globals\":{}}");
return sb.ToString();
}
- private static void EmitTypeJson(StringBuilder sb, ManagedTypeDescriptor desc)
+ private static void EmitTypeJson(StringBuilder sb, EcmaType type)
{
- MetadataType type = desc.Type;
-
- // Use 0 (indeterminate) for reference types — their "size" from cDAC perspective
- // is not meaningful since they're GC-managed objects.
+ // Use 0 (indeterminate) for reference types
int typeSize = type.IsValueType ? type.InstanceFieldSize.AsInt : 0;
- // JSON format: "TypeName": [size, { "Field1": offset, "Field2": offset }]
- sb.Append('"').Append(desc.DescriptorName).Append("\":[");
+ sb.Append('"').Append(type.GetName()).Append("\":[");
sb.Append(typeSize);
sb.Append(",{");
bool firstField = true;
foreach (FieldDesc field in type.GetFields())
{
- if (field.IsStatic)
+ if (field.IsStatic || field is not EcmaField ecmaField)
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 (!ecmaField.HasCustomAttribute(CdacTypeAttributeNamespace, CdacFieldAttributeName))
+ continue;
if (!firstField)
sb.Append(',');
firstField = false;
- sb.Append('"').Append(cdacFieldName).Append("\":");
+ sb.Append('"').Append(field.GetName()).Append("\":");
sb.Append(field.Offset.AsInt);
}
sb.Append("}]");
}
+ protected internal override int Phase => (int)ObjectNodePhase.Ordered;
+
#if !SUPPORT_JIT
public override int ClassCode => 0x4d444e01;
@@ -210,19 +150,5 @@ 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 FieldMappings;
-
- public ManagedTypeDescriptor(string descriptorName, MetadataType type, Dictionary fieldMappings)
- {
- DescriptorName = descriptorName;
- Type = type;
- FieldMappings = fieldMappings;
- }
- }
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs
index 107b7f95963835..1b8c8de9737aa0 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs
@@ -1,103 +1,21 @@
// 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.Reflection.Metadata;
-
using ILCompiler.DependencyAnalysis;
-using Internal.TypeSystem;
-using Internal.TypeSystem.Ecma;
-
namespace ILCompiler
{
///
- /// Compilation root provider that emits managed type layout descriptors for the cDAC.
- /// Types in the system module (System.Private.CoreLib) annotated with [CdacType] will
- /// have their [CdacField]-annotated field offsets computed at compile time and embedded
- /// in the final binary as a ContractDescriptor that the cDAC reader merges as a
- /// sub-descriptor.
+ /// Compilation root provider that adds the managed cDAC data descriptor node.
+ /// The node discovers [CdacType]-annotated types from MetadataManager.GetTypesWithEETypes()
+ /// during object data emission, ensuring only types with MethodTables are included.
///
- ///
- /// The descriptor is emitted as a symbol named "DotNetManagedContractDescriptor"
- /// that the NativeAOT runtime's C++ code references via extern and stores as a
- /// sub-descriptor pointer in the main contract descriptor.
- ///
public class ManagedDataDescriptorProvider : ICompilationRootProvider
{
- private const string CdacTypeAttributeNamespace = "System.Runtime.CompilerServices";
- private const string CdacTypeAttributeName = "CdacTypeAttribute";
- private const string CdacFieldAttributeName = "CdacFieldAttribute";
-
- private readonly CompilerTypeSystemContext _context;
-
- public ManagedDataDescriptorProvider(CompilerTypeSystemContext context)
- {
- _context = context;
- }
-
void ICompilationRootProvider.AddCompilationRoots(IRootingServiceProvider rootProvider)
{
var descriptorNode = new ManagedDataDescriptorNode();
-
- DiscoverAnnotatedTypes(descriptorNode);
- descriptorNode.FinalizeDescriptor();
-
rootProvider.AddCompilationRoot(descriptorNode, "Managed type descriptors for cDAC");
- rootProvider.AddCompilationRoot(descriptorNode.JsonBlobNode, "Managed descriptor JSON data");
- }
-
- private void DiscoverAnnotatedTypes(ManagedDataDescriptorNode descriptorNode)
- {
- if (_context.SystemModule is not EcmaModule systemModule)
- return;
-
- var seenDescriptorNames = new HashSet();
- MetadataReader reader = systemModule.MetadataReader;
-
- foreach (TypeDefinitionHandle typeDefHandle in reader.TypeDefinitions)
- {
- EcmaType ecmaType = (EcmaType)systemModule.GetType(typeDefHandle);
- var typeAttr = ecmaType.GetDecodedCustomAttribute(CdacTypeAttributeNamespace, CdacTypeAttributeName);
- if (typeAttr is null)
- continue;
-
- string descriptorTypeName = (string)typeAttr.Value.FixedArguments[0].Value;
-
- if (string.IsNullOrEmpty(descriptorTypeName))
- throw new InvalidOperationException($"[CdacType] on '{ecmaType}' has a null or empty descriptor name.");
-
- if (ecmaType.HasInstantiation)
- throw new InvalidOperationException($"[CdacType] is not supported on generic type '{ecmaType}'.");
-
- if (!seenDescriptorNames.Add(descriptorTypeName))
- throw new InvalidOperationException($"Duplicate [CdacType] descriptor name '{descriptorTypeName}' on '{ecmaType}'.");
-
- var fieldMappings = new Dictionary();
- foreach (FieldDesc field in ecmaType.GetFields())
- {
- if (field.IsStatic || field is not EcmaField ecmaField)
- continue;
-
- var fieldAttr = ecmaField.GetDecodedCustomAttribute(CdacTypeAttributeNamespace, CdacFieldAttributeName);
- if (fieldAttr is null)
- continue;
-
- string cdacFieldName = fieldAttr.Value.FixedArguments.Length > 0
- && fieldAttr.Value.FixedArguments[0].Value is string name
- ? name
- : field.GetName();
-
- if (!fieldMappings.TryAdd(cdacFieldName, field.GetName()))
- throw new InvalidOperationException($"Duplicate [CdacField] name '{cdacFieldName}' on type '{ecmaType}'.");
- }
-
- if (fieldMappings.Count > 0)
- {
- descriptorNode.AddType(descriptorTypeName, (MetadataType)ecmaType, fieldMappings);
- }
- }
}
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs
index e59a518e4a8965..5bb46ef0fae168 100644
--- a/src/coreclr/tools/aot/ILCompiler/Program.cs
+++ b/src/coreclr/tools/aot/ILCompiler/Program.cs
@@ -263,7 +263,7 @@ public int Run()
compilationRoots.Add(new RuntimeConfigurationRootProvider(settingsBlobName, runtimeOptions));
compilationRoots.Add(new RuntimeConfigurationRootProvider(knobsBlobName, runtimeKnobs));
compilationRoots.Add(new ExpectedIsaFeaturesRootProvider(instructionSetSupport));
- compilationRoots.Add(new ManagedDataDescriptorProvider(typeSystemContext));
+ compilationRoots.Add(new ManagedDataDescriptorProvider());
if (SplitExeInitialization)
{
compilationRoots.Add(new MainMethodRootProvider(entrypointModule, CreateInitializerList(typeSystemContext), generateLibraryAndModuleInitializers: false));
@@ -275,7 +275,7 @@ public int Run()
compilationRoots.Add(new RuntimeConfigurationRootProvider(settingsBlobName, runtimeOptions));
compilationRoots.Add(new RuntimeConfigurationRootProvider(knobsBlobName, runtimeKnobs));
compilationRoots.Add(new ExpectedIsaFeaturesRootProvider(instructionSetSupport));
- compilationRoots.Add(new ManagedDataDescriptorProvider(typeSystemContext));
+ compilationRoots.Add(new ManagedDataDescriptorProvider());
if (SplitExeInitialization)
{
compilationRoots.Add(new NativeLibraryInitializerRootProvider(typeSystemContext.GeneratedAssembly, CreateInitializerList(typeSystemContext)));
From 0405d35f5b4a24a31005b48bf24ead5a6e256b62 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Fri, 17 Apr 2026 15:44:37 -0400
Subject: [PATCH 06/27] Move ThreadStore pointer to static member matching
CoreCLR
Move g_pThreadStore global to ThreadStore::s_pThreadStore static member,
matching how CoreCLR exposes its ThreadStore pointer for the cDAC.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp | 3 +--
src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h | 1 -
.../nativeaot/Runtime/datadescriptor/datadescriptor.inc | 2 +-
src/coreclr/nativeaot/Runtime/threadstore.cpp | 2 ++
src/coreclr/nativeaot/Runtime/threadstore.h | 2 ++
5 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp b/src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp
index 3c6981b97fd2ac..12e8b60f2939dd 100644
--- a/src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp
+++ b/src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp
@@ -161,7 +161,6 @@ PTR_uint8_t RuntimeInstance::GetTargetOfUnboxingAndInstantiatingStub(PTR_VOID Co
}
GPTR_IMPL_INIT(RuntimeInstance, g_pTheRuntimeInstance, NULL);
-GPTR_IMPL_INIT(ThreadStore, g_pThreadStore, NULL);
// WARNING: This method is called by suspension while one thread is interrupted
// in a random location, possibly holding random locks.
@@ -320,7 +319,7 @@ bool RuntimeInstance::Initialize(HANDLE hPalInstance)
ASSERT_MSG(g_pTheRuntimeInstance == NULL, "multi-instances are not supported");
g_pTheRuntimeInstance = pRuntimeInstance;
- g_pThreadStore = pThreadStore;
+ ThreadStore::s_pThreadStore = pThreadStore;
return true;
}
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
index fc29972fb0b545..9e2a23cbb4fac2 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
@@ -26,7 +26,6 @@
#include
#include
-GPTR_DECL(ThreadStore, g_pThreadStore);
GPTR_DECL(MethodTable, g_pFreeObjectEEType);
// ILC emits a ContractDescriptor named "DotNetManagedContractDescriptor" with
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
index e49733b56b114d..ed0a70b183609b 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
@@ -92,7 +92,7 @@ CDAC_TYPES_END()
CDAC_GLOBALS_BEGIN()
-CDAC_GLOBAL_POINTER(ThreadStore, &g_pThreadStore)
+CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore)
CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &g_pFreeObjectEEType)
diff --git a/src/coreclr/nativeaot/Runtime/threadstore.cpp b/src/coreclr/nativeaot/Runtime/threadstore.cpp
index 0439f0c76058ab..c87207a562ba62 100644
--- a/src/coreclr/nativeaot/Runtime/threadstore.cpp
+++ b/src/coreclr/nativeaot/Runtime/threadstore.cpp
@@ -30,6 +30,8 @@ volatile uint32_t RhpTrapThreads = (uint32_t)TrapThreadsFlags::None;
GVAL_IMPL_INIT(PTR_Thread, RhpSuspendingThread, 0);
+SPTR_IMPL(ThreadStore, ThreadStore, s_pThreadStore);
+
ThreadStore * GetThreadStore()
{
return GetRuntimeInstance()->GetThreadStore();
diff --git a/src/coreclr/nativeaot/Runtime/threadstore.h b/src/coreclr/nativeaot/Runtime/threadstore.h
index f87730e08fc574..5f855dc5b18abd 100644
--- a/src/coreclr/nativeaot/Runtime/threadstore.h
+++ b/src/coreclr/nativeaot/Runtime/threadstore.h
@@ -20,6 +20,7 @@ extern "C" void PopulateDebugHeaders();
class ThreadStore
{
+ friend class RuntimeInstance;
friend void PopulateDebugHeaders();
friend struct ::cdac_data;
@@ -31,6 +32,7 @@ class ThreadStore
ThreadStore();
public:
+ SPTR_DECL(ThreadStore, s_pThreadStore);
void LockThreadStore();
void UnlockThreadStore();
From 1706da7bc931876f22c1cfe29801a749cf814ad9 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Fri, 17 Apr 2026 15:56:27 -0400
Subject: [PATCH 07/27] Move datadescriptor after eventpipe in CMake ordering
Move add_subdirectory(datadescriptor) after add_subdirectory(eventpipe)
so that the generated clretwallmain.h header exists before the
datadescriptor intermediary compiles. This removes the need for the
SKIP_TRACING_DEFINITIONS workaround.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/coreclr/nativeaot/Runtime/CMakeLists.txt | 5 ++++-
src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt | 1 -
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/coreclr/nativeaot/Runtime/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/CMakeLists.txt
index b4f63dc5faca0d..4c32d72c9c19fa 100644
--- a/src/coreclr/nativeaot/Runtime/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/CMakeLists.txt
@@ -338,7 +338,6 @@ convert_to_absolute_path(VXSORT_SOURCES ${VXSORT_SOURCES})
convert_to_absolute_path(DUMMY_VXSORT_SOURCES ${DUMMY_VXSORT_SOURCES})
if(NOT CLR_CMAKE_TARGET_ARCH_WASM)
- add_subdirectory(datadescriptor)
add_subdirectory(Full)
else()
add_subdirectory(Portable)
@@ -347,3 +346,7 @@ endif()
if(FEATURE_PERFTRACING)
add_subdirectory(eventpipe)
endif()
+
+if(NOT CLR_CMAKE_TARGET_ARCH_WASM)
+ add_subdirectory(datadescriptor)
+endif()
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
index 45640e40ba9023..4d082efc5659cd 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
@@ -15,7 +15,6 @@ include(${CLR_DIR}/clrdatadescriptors.cmake)
add_library(nativeaot_descriptor_interface INTERFACE)
target_include_directories(nativeaot_descriptor_interface INTERFACE
${CMAKE_CURRENT_SOURCE_DIR})
-target_compile_definitions(nativeaot_descriptor_interface INTERFACE SKIP_TRACING_DEFINITIONS)
generate_data_descriptors(
LIBRARY_NAME nativeaot_cdac_contract_descriptor
CONTRACT_NAME "DotNetRuntimeContractDescriptor"
From 3368b21efa2f90f9ed575d7807cbc77611d978dc Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Fri, 17 Apr 2026 17:25:06 -0400
Subject: [PATCH 08/27] Use nativeaot_runtime_includes for datadescriptor build
Create an interface library in Runtime/CMakeLists.txt that captures
the directory's INCLUDE_DIRECTORIES property, then link it into the
datadescriptor interface targets. This ensures the datadescriptor
intermediary compilation uses exactly the same include paths as the
regular NativeAOT Runtime build, without hardcoding paths.
Add a cdacdata.h redirect header in Runtime/inc/ that forwards to
the shared definition in src/coreclr/vm/cdacdata.h.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/coreclr/nativeaot/Runtime/CMakeLists.txt | 6 ++++++
.../Runtime/datadescriptor/CMakeLists.txt | 18 +++++++++++-------
src/coreclr/nativeaot/Runtime/inc/cdacdata.h | 5 +++++
3 files changed, 22 insertions(+), 7 deletions(-)
create mode 100644 src/coreclr/nativeaot/Runtime/inc/cdacdata.h
diff --git a/src/coreclr/nativeaot/Runtime/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/CMakeLists.txt
index 4c32d72c9c19fa..d01bca65de79c5 100644
--- a/src/coreclr/nativeaot/Runtime/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/CMakeLists.txt
@@ -348,5 +348,11 @@ if(FEATURE_PERFTRACING)
endif()
if(NOT CLR_CMAKE_TARGET_ARCH_WASM)
+ # Create an interface library that captures the Runtime's include directories
+ # for use by the datadescriptor intermediary compilation.
+ add_library(nativeaot_runtime_includes INTERFACE)
+ get_property(_runtime_includes DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
+ target_include_directories(nativeaot_runtime_includes INTERFACE ${_runtime_includes})
+
add_subdirectory(datadescriptor)
endif()
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
index 4d082efc5659cd..344dc601110f3d 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
@@ -6,15 +6,17 @@ set(CMAKE_INCLUDE_CURRENT_DIR OFF)
# src/coreclr/debug/datadescriptor-shared/ to generate a
# DotNetRuntimeContractDescriptor symbol in the NativeAOT runtime.
#
-# Include directories and compile definitions are inherited from the parent
-# Runtime/CMakeLists.txt directory scope. The interface targets below only
-# need to add the datadescriptor-specific include path.
+# The nativeaot_runtime_includes interface library (created in the parent
+# Runtime/CMakeLists.txt) provides the same include directories used by
+# the regular NativeAOT Runtime build.
include(${CLR_DIR}/clrdatadescriptors.cmake)
add_library(nativeaot_descriptor_interface INTERFACE)
target_include_directories(nativeaot_descriptor_interface INTERFACE
${CMAKE_CURRENT_SOURCE_DIR})
+target_link_libraries(nativeaot_descriptor_interface INTERFACE nativeaot_runtime_includes)
+add_dependencies(nativeaot_descriptor_interface aot_eventing_headers)
generate_data_descriptors(
LIBRARY_NAME nativeaot_cdac_contract_descriptor
CONTRACT_NAME "DotNetRuntimeContractDescriptor"
@@ -27,8 +29,9 @@ set(GC_DESCRIPTOR_DIR "${CLR_DIR}/gc/datadescriptor")
add_library(nativeaot_gc_wks_descriptor_interface INTERFACE)
target_include_directories(nativeaot_gc_wks_descriptor_interface INTERFACE
- ${GC_DESCRIPTOR_DIR}
- ${GC_DIR})
+ ${GC_DESCRIPTOR_DIR})
+target_link_libraries(nativeaot_gc_wks_descriptor_interface INTERFACE nativeaot_runtime_includes)
+add_dependencies(nativeaot_gc_wks_descriptor_interface aot_eventing_headers)
generate_data_descriptors(
LIBRARY_NAME nativeaot_gc_wks_descriptor
CONTRACT_NAME "GCContractDescriptorWKS"
@@ -36,8 +39,9 @@ generate_data_descriptors(
add_library(nativeaot_gc_svr_descriptor_interface INTERFACE)
target_include_directories(nativeaot_gc_svr_descriptor_interface INTERFACE
- ${GC_DESCRIPTOR_DIR}
- ${GC_DIR})
+ ${GC_DESCRIPTOR_DIR})
+target_link_libraries(nativeaot_gc_svr_descriptor_interface INTERFACE nativeaot_runtime_includes)
+add_dependencies(nativeaot_gc_svr_descriptor_interface aot_eventing_headers)
target_compile_definitions(nativeaot_gc_svr_descriptor_interface INTERFACE -DSERVER_GC)
generate_data_descriptors(
LIBRARY_NAME nativeaot_gc_svr_descriptor
diff --git a/src/coreclr/nativeaot/Runtime/inc/cdacdata.h b/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
new file mode 100644
index 00000000000000..511fc4e481dabb
--- /dev/null
+++ b/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
@@ -0,0 +1,5 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// Redirect to the shared cdac_data template definition.
+#include "../../vm/cdacdata.h"
From b6ae1b0f8946c0790187a165715b954617ab313a Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Sat, 18 Apr 2026 23:17:07 -0400
Subject: [PATCH 09/27] Fix cdacdata.h redirect path
Correct the relative include from ../../vm/ to ../../../vm/ since
cdacdata.h is at Runtime/inc/ (3 levels below src/coreclr/).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/coreclr/nativeaot/Runtime/inc/cdacdata.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/coreclr/nativeaot/Runtime/inc/cdacdata.h b/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
index 511fc4e481dabb..dbaecdb015f7e7 100644
--- a/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
+++ b/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
@@ -2,4 +2,4 @@
// The .NET Foundation licenses this file to you under the MIT license.
// Redirect to the shared cdac_data template definition.
-#include "../../vm/cdacdata.h"
+#include "../../../vm/cdacdata.h"
From 67615c477b57bbac41fc8e1786b6831235c08f0f Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Sun, 19 Apr 2026 08:52:54 -0400
Subject: [PATCH 10/27] Copy cdacdata.h template into NativeAOT Runtime/inc
Replace the relative-path redirect with a direct copy of the
cdac_data template definition. The redirect path ../../../vm/
failed to resolve in CI cross-compilation builds. Since the template
is small and stable (identical copies exist in gc/env/ and vm/),
a local copy in Runtime/inc/ avoids cross-directory include issues.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/coreclr/nativeaot/Runtime/inc/cdacdata.h | 31 ++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
diff --git a/src/coreclr/nativeaot/Runtime/inc/cdacdata.h b/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
index dbaecdb015f7e7..86dcf4f475d774 100644
--- a/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
+++ b/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
@@ -1,5 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// Redirect to the shared cdac_data template definition.
-#include "../../../vm/cdacdata.h"
+// This is a copy of src/coreclr/gc/env/cdacdata.h for use by NativeAOT headers.
+
+#ifndef CDACDATA_H__
+#define CDACDATA_H__
+
+// This struct enables exposing information that is private to a class to the cDAC. For example,
+// if class C has private information that must be provided, declare cdac_data as a friend of C
+// where D is the specialization of cdac_data that will expose the information. Then provide a
+// specialization cdac_data with constexpr members exposing the information.
+//
+// Note: in the common case, type D will be type C.
+//
+// For example, if the offset of field F in class C is required:
+//
+// class C {
+// private:
+// int F;
+// friend struct ::cdac_data;
+// };
+// template<> struct cdac_data {
+// static constexpr size_t F_Offset = offsetof(C, F);
+// };
+//
+template
+struct cdac_data
+{
+};
+
+#endif// CDACDATA_H__
From 5a93416810eded1dd90812295feb99272b597807 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Sun, 19 Apr 2026 14:19:59 -0400
Subject: [PATCH 11/27] Use Utf8JsonWriter for managed descriptor JSON
Replace manual StringBuilder JSON construction with System.Text.Json
Utf8JsonWriter, producing the correct compact format expected by the
cDAC reader's ContractDescriptorParser:
- Types as objects (not arrays)
- Sized types use '!' size sigil; reference types omit it (indeterminate)
- Fields as 'name': offset (number only)
- Proper JSON escaping for all names
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../ManagedDataDescriptorNode.cs | 70 ++++++++++---------
1 file changed, 37 insertions(+), 33 deletions(-)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
index a84fc7b0732d6a..59efcde02fa990 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -2,7 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
-using System.Text;
+using System.IO;
+using System.Text.Json;
using Internal.Text;
using Internal.TypeSystem;
@@ -47,8 +48,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
if (relocsOnly)
return new ObjectData(Array.Empty(), Array.Empty(), 1, new ISymbolDefinitionNode[] { this });
- string json = BuildJsonDescriptor(factory);
- byte[] jsonBytes = Encoding.UTF8.GetBytes(json);
+ byte[] jsonBytes = BuildJsonDescriptor(factory);
// Header layout: magic(8) + flags(4) + desc_size(4) + desc_ptr(ptr) + count(4) + pad(4) + data_ptr(ptr)
int headerSize = 8 + 4 + 4 + factory.Target.PointerSize + 4 + 4 + factory.Target.PointerSize;
@@ -86,41 +86,50 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
return builder.ToObjectData();
}
- private static string BuildJsonDescriptor(NodeFactory factory)
+ ///
+ /// 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.
+ ///
+ private static byte[] BuildJsonDescriptor(NodeFactory factory)
{
- var sb = new StringBuilder();
- sb.Append("{\"version\":0,\"types\":{");
-
- bool firstType = true;
- foreach (TypeDesc type in factory.MetadataManager.GetTypesWithEETypes())
+ using var stream = new MemoryStream();
+ using (var writer = new Utf8JsonWriter(stream))
{
- if (type is not EcmaType ecmaType)
- continue;
+ writer.WriteStartObject();
+ writer.WriteNumber("version", 0);
- if (!ecmaType.HasCustomAttribute(CdacTypeAttributeNamespace, CdacTypeAttributeName))
- continue;
+ writer.WriteStartObject("types");
+ foreach (TypeDesc type in factory.MetadataManager.GetTypesWithEETypes())
+ {
+ if (type is not EcmaType ecmaType)
+ continue;
+
+ if (!ecmaType.HasCustomAttribute(CdacTypeAttributeNamespace, CdacTypeAttributeName))
+ continue;
- if (!firstType)
- sb.Append(',');
- firstType = false;
+ WriteType(writer, ecmaType);
+ }
+ writer.WriteEndObject();
- EmitTypeJson(sb, ecmaType);
+ writer.WriteStartObject("globals");
+ writer.WriteEndObject();
+
+ writer.WriteEndObject();
}
- sb.Append("},\"globals\":{}}");
- return sb.ToString();
+ return stream.ToArray();
}
- private static void EmitTypeJson(StringBuilder sb, EcmaType type)
+ private static void WriteType(Utf8JsonWriter writer, EcmaType type)
{
- // Use 0 (indeterminate) for reference types
- int typeSize = type.IsValueType ? type.InstanceFieldSize.AsInt : 0;
+ writer.WriteStartObject(type.GetName());
- sb.Append('"').Append(type.GetName()).Append("\":[");
- sb.Append(typeSize);
- sb.Append(",{");
+ if (type.IsValueType)
+ {
+ writer.WriteNumber("!", type.InstanceFieldSize.AsInt);
+ }
- bool firstField = true;
foreach (FieldDesc field in type.GetFields())
{
if (field.IsStatic || field is not EcmaField ecmaField)
@@ -129,15 +138,10 @@ private static void EmitTypeJson(StringBuilder sb, EcmaType type)
if (!ecmaField.HasCustomAttribute(CdacTypeAttributeNamespace, CdacFieldAttributeName))
continue;
- if (!firstField)
- sb.Append(',');
- firstField = false;
-
- sb.Append('"').Append(field.GetName()).Append("\":");
- sb.Append(field.Offset.AsInt);
+ writer.WriteNumber(field.GetName(), field.Offset.AsInt);
}
- sb.Append("}]");
+ writer.WriteEndObject();
}
protected internal override int Phase => (int)ObjectNodePhase.Ordered;
From 465bd7b7d6a50f35545b0feb265f5f51b0bbbe48 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Mon, 20 Apr 2026 14:10:22 -0400
Subject: [PATCH 12/27] Address PR review feedback: attribute naming,
cdacdata.h, comment cleanup
- CdacTypeAttribute: Add optional Name property to override descriptor key,
addressing copilot bot concern about type name collisions
- ManagedDataDescriptorNode: Read Name from CdacTypeAttribute via
GetDecodedCustomAttribute, fall back to simple type name
- cdacdata.h: Reference primary copy from gc/env/ instead of duplicating
- MethodTable.h: Remove unnecessary explanatory comment
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../nativeaot/Runtime/inc/MethodTable.h | 1 -
src/coreclr/nativeaot/Runtime/inc/cdacdata.h | 31 ++-----------------
.../CompilerServices/CdacTypeAttribute.cs | 8 ++++-
.../ManagedDataDescriptorNode.cs | 23 +++++++++++++-
4 files changed, 31 insertions(+), 32 deletions(-)
diff --git a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h
index 77c2ceec275a4b..885c613f308307 100644
--- a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h
+++ b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h
@@ -12,7 +12,6 @@ class MethodTable;
class TypeManager;
struct TypeManagerHandle;
-// cdac_data template for exposing private members to the cDAC data descriptor.
#include "cdacdata.h"
//-------------------------------------------------------------------------------------------------
diff --git a/src/coreclr/nativeaot/Runtime/inc/cdacdata.h b/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
index 86dcf4f475d774..1feedebc73e9d3 100644
--- a/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
+++ b/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
@@ -1,32 +1,5 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// This is a copy of src/coreclr/gc/env/cdacdata.h for use by NativeAOT headers.
-
-#ifndef CDACDATA_H__
-#define CDACDATA_H__
-
-// This struct enables exposing information that is private to a class to the cDAC. For example,
-// if class C has private information that must be provided, declare cdac_data as a friend of C
-// where D is the specialization of cdac_data that will expose the information. Then provide a
-// specialization cdac_data with constexpr members exposing the information.
-//
-// Note: in the common case, type D will be type C.
-//
-// For example, if the offset of field F in class C is required:
-//
-// class C {
-// private:
-// int F;
-// friend struct ::cdac_data;
-// };
-// template<> struct cdac_data {
-// static constexpr size_t F_Offset = offsetof(C, F);
-// };
-//
-template
-struct cdac_data
-{
-};
-
-#endif// CDACDATA_H__
+// Re-export the primary cdac_data template from the GC environment.
+#include "../../gc/env/cdacdata.h"
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs
index ae5c48ceae2efe..a5a1b798268e25 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs
@@ -11,10 +11,16 @@ namespace System.Runtime.CompilerServices
///
///
/// Fields to include must be individually annotated with .
- /// The type and field names used in the descriptor match the actual managed names.
+ /// By default, the type's simple name is used in the descriptor. Set
+ /// to override the descriptor key (e.g., to use a fully-qualified name or avoid collisions).
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
internal sealed class CdacTypeAttribute : Attribute
{
+ ///
+ /// Optional override for the type name used in the cDAC descriptor JSON.
+ /// When null, the type's simple metadata name is used.
+ ///
+ public string? Name { get; set; }
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
index 59efcde02fa990..a066701bc19bb7 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -123,7 +123,9 @@ private static byte[] BuildJsonDescriptor(NodeFactory factory)
private static void WriteType(Utf8JsonWriter writer, EcmaType type)
{
- writer.WriteStartObject(type.GetName());
+ // Use the Name property from CdacTypeAttribute if set, otherwise use the simple type name
+ string descriptorName = GetCdacTypeName(type);
+ writer.WriteStartObject(descriptorName);
if (type.IsValueType)
{
@@ -144,6 +146,25 @@ private static void WriteType(Utf8JsonWriter writer, EcmaType type)
writer.WriteEndObject();
}
+ ///
+ /// Returns the descriptor name for a type annotated with [CdacType].
+ /// Uses the Name property if set, otherwise falls back to the simple type name.
+ ///
+ private static string GetCdacTypeName(EcmaType type)
+ {
+ var decoded = type.GetDecodedCustomAttribute(CdacTypeAttributeNamespace, CdacTypeAttributeName);
+ if (decoded.HasValue)
+ {
+ foreach (var named in decoded.Value.NamedArguments)
+ {
+ if (named.Name == "Name" && named.Value is string nameOverride && !string.IsNullOrEmpty(nameOverride))
+ return nameOverride;
+ }
+ }
+
+ return type.GetName();
+ }
+
protected internal override int Phase => (int)ObjectNodePhase.Ordered;
#if !SUPPORT_JIT
From 3e60d677b778300c6c41b8efe942d3d4cdb079df Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Mon, 20 Apr 2026 17:29:07 -0400
Subject: [PATCH 13/27] Remove unnecessary SUPPORT_JIT guard from
ManagedDataDescriptorNode
This node is NativeAOT-only and will never be compiled with SUPPORT_JIT.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
index a066701bc19bb7..8d609f2a529d18 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -167,13 +167,11 @@ private static string GetCdacTypeName(EcmaType type)
protected internal override int Phase => (int)ObjectNodePhase.Ordered;
-#if !SUPPORT_JIT
public override int ClassCode => 0x4d444e01;
public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
{
return 0; // Singleton
}
-#endif
}
}
From b03ad0c58d2242a11d6438600302d82711ae08a1 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Tue, 21 Apr 2026 15:07:45 -0400
Subject: [PATCH 14/27] Merge CdacType/CdacField into single
DataContractAttribute
Per Jan's feedback:
- Merge CdacTypeAttribute and CdacFieldAttribute into a single
DataContractAttribute usable on both types and fields
- Move to System.Diagnostics namespace (avoid CDAC abbreviation)
- Remove Name property (use actual type/field names only)
- Remove GetCdacTypeName helper (no name override logic needed)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../src/System.Private.CoreLib.csproj | 3 +-
.../Diagnostics/DataContractAttribute.cs | 16 +++++++++
.../CompilerServices/CdacFieldAttribute.cs | 15 --------
.../CompilerServices/CdacTypeAttribute.cs | 26 --------------
.../src/System/Threading/Thread.NativeAot.cs | 6 ++--
.../ManagedDataDescriptorNode.cs | 35 ++++++++-----------
6 files changed, 34 insertions(+), 67 deletions(-)
create mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/DataContractAttribute.cs
delete mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacFieldAttribute.cs
delete mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj
index bd0999730238a7..0f808ede316353 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj
@@ -195,8 +195,7 @@
-
-
+
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/DataContractAttribute.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/DataContractAttribute.cs
new file mode 100644
index 00000000000000..4d1cafeff9a3ce
--- /dev/null
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/DataContractAttribute.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Diagnostics
+{
+ ///
+ /// When applied to a type, indicates that ILC should include its field layout in the
+ /// managed cDAC data descriptor so diagnostic tools can inspect instances without
+ /// runtime metadata. When applied to a field of such a type, indicates ILC should
+ /// include that field's offset in the descriptor.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field, Inherited = false)]
+ internal sealed class DataContractAttribute : Attribute
+ {
+ }
+}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacFieldAttribute.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacFieldAttribute.cs
deleted file mode 100644
index d23a1e58d6e229..00000000000000
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacFieldAttribute.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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
-{
- ///
- /// When applied to an instance field of a type annotated with ,
- /// indicates that ILC should include this field in the managed cDAC data descriptor.
- /// The field's actual declared name is used in the descriptor.
- ///
- [AttributeUsage(AttributeTargets.Field, Inherited = false)]
- internal sealed class CdacFieldAttribute : Attribute
- {
- }
-}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs
deleted file mode 100644
index a5a1b798268e25..00000000000000
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/CdacTypeAttribute.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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
-{
- ///
- /// 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).
- ///
- ///
- /// Fields to include must be individually annotated with .
- /// By default, the type's simple name is used in the descriptor. Set
- /// to override the descriptor key (e.g., to use a fully-qualified name or avoid collisions).
- ///
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
- internal sealed class CdacTypeAttribute : Attribute
- {
- ///
- /// Optional override for the type name used in the cDAC descriptor JSON.
- /// When null, the type's simple metadata name is used.
- ///
- public string? Name { get; set; }
- }
-}
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs
index d7ed876309391c..5bd5dcdc717cc9 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs
@@ -13,7 +13,7 @@
namespace System.Threading
{
- [CdacType]
+ [DataContract]
public sealed partial class Thread
{
// Extra bits used in _threadState
@@ -30,9 +30,9 @@ public sealed partial class Thread
private volatile int _threadState = (int)ThreadState.Unstarted;
private ThreadPriority _priority;
- [CdacField]
+ [DataContract]
private ManagedThreadId _managedThreadId;
- [CdacField]
+ [DataContract]
private string? _name;
private StartHelper? _startHelper;
private Exception? _startException;
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
index 8d609f2a529d18..e4d8545540b3d5 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -22,9 +22,8 @@ namespace ILCompiler.DependencyAnalysis
///
public class ManagedDataDescriptorNode : ObjectNode, ISymbolDefinitionNode
{
- private const string CdacTypeAttributeNamespace = "System.Runtime.CompilerServices";
- private const string CdacTypeAttributeName = "CdacTypeAttribute";
- private const string CdacFieldAttributeName = "CdacFieldAttribute";
+ private const string DataContractAttributeNamespace = "System.Diagnostics";
+ private const string DataContractAttributeName = "DataContractAttribute";
public const string SymbolName = "DotNetManagedContractDescriptor";
@@ -105,7 +104,7 @@ private static byte[] BuildJsonDescriptor(NodeFactory factory)
if (type is not EcmaType ecmaType)
continue;
- if (!ecmaType.HasCustomAttribute(CdacTypeAttributeNamespace, CdacTypeAttributeName))
+ if (!ecmaType.HasCustomAttribute(DataContractAttributeNamespace, DataContractAttributeName))
continue;
WriteType(writer, ecmaType);
@@ -123,9 +122,7 @@ private static byte[] BuildJsonDescriptor(NodeFactory factory)
private static void WriteType(Utf8JsonWriter writer, EcmaType type)
{
- // Use the Name property from CdacTypeAttribute if set, otherwise use the simple type name
- string descriptorName = GetCdacTypeName(type);
- writer.WriteStartObject(descriptorName);
+ writer.WriteStartObject(GetMangledTypeName(type));
if (type.IsValueType)
{
@@ -137,7 +134,7 @@ private static void WriteType(Utf8JsonWriter writer, EcmaType type)
if (field.IsStatic || field is not EcmaField ecmaField)
continue;
- if (!ecmaField.HasCustomAttribute(CdacTypeAttributeNamespace, CdacFieldAttributeName))
+ if (!ecmaField.HasCustomAttribute(DataContractAttributeNamespace, DataContractAttributeName))
continue;
writer.WriteNumber(field.GetName(), field.Offset.AsInt);
@@ -147,22 +144,18 @@ private static void WriteType(Utf8JsonWriter writer, EcmaType type)
}
///
- /// Returns the descriptor name for a type annotated with [CdacType].
- /// Uses the Name property if set, otherwise falls back to the simple type name.
+ /// Returns a mangled fully-qualified type name matching the NativeAOT symbol convention
+ /// (e.g., System.Threading.Thread → System_Threading_Thread).
///
- private static string GetCdacTypeName(EcmaType type)
+ private static string GetMangledTypeName(MetadataType type)
{
- var decoded = type.GetDecodedCustomAttribute(CdacTypeAttributeNamespace, CdacTypeAttributeName);
- if (decoded.HasValue)
- {
- foreach (var named in decoded.Value.NamedArguments)
- {
- if (named.Name == "Name" && named.Value is string nameOverride && !string.IsNullOrEmpty(nameOverride))
- return nameOverride;
- }
- }
+ string ns = type.GetNamespace();
+ string name = type.GetName();
+
+ if (string.IsNullOrEmpty(ns))
+ return name;
- return type.GetName();
+ return $"{ns}.{name}".Replace('.', '_');
}
protected internal override int Phase => (int)ObjectNodePhase.Ordered;
From 4dcc843cf4a607c77ee370856a26d25783a89ab3 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 22 Apr 2026 14:55:16 -0400
Subject: [PATCH 15/27] Update doc comments to reference DataContractAttribute
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs | 2 +-
.../Compiler/ManagedDataDescriptorProvider.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
index e4d8545540b3d5..104c0e6e15e101 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -17,7 +17,7 @@ namespace ILCompiler.DependencyAnalysis
/// so it can emit field offsets that would otherwise require runtime metadata resolution.
///
/// Types are discovered by scanning MetadataManager.GetTypesWithEETypes() for types
- /// annotated with [CdacType], ensuring only types that actually have a MethodTable
+ /// annotated with [DataContract], ensuring only types that actually have a MethodTable
/// in the binary are included.
///
public class ManagedDataDescriptorNode : ObjectNode, ISymbolDefinitionNode
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs
index 1b8c8de9737aa0..734b9d1ad7faf8 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs
@@ -7,7 +7,7 @@ namespace ILCompiler
{
///
/// Compilation root provider that adds the managed cDAC data descriptor node.
- /// The node discovers [CdacType]-annotated types from MetadataManager.GetTypesWithEETypes()
+ /// The node discovers [DataContract]-annotated types from MetadataManager.GetTypesWithEETypes()
/// during object data emission, ensuring only types with MethodTables are included.
///
public class ManagedDataDescriptorProvider : ICompilationRootProvider
From 417592b6bb6501bde13c4aa088b9d7d81cebaade Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 22 Apr 2026 15:34:31 -0400
Subject: [PATCH 16/27] Add stresslog data descriptor entries for NativeAOT
Add StressLog, ThreadStressLog, StressLogChunk, StressMsgHeader, and
StressMsg types to the NativeAOT data descriptor. Add StressLog globals
(without module table per jkoritzinsky's feedback) and StressLog contract
version 2, matching CoreCLR.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Runtime/datadescriptor/datadescriptor.h | 1 +
.../Runtime/datadescriptor/datadescriptor.inc | 55 +++++++++++++++++++
src/coreclr/nativeaot/Runtime/inc/stressLog.h | 14 +++++
3 files changed, 70 insertions(+)
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
index 9e2a23cbb4fac2..a38daea487becf 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
@@ -27,6 +27,7 @@
#include
GPTR_DECL(MethodTable, g_pFreeObjectEEType);
+GPTR_DECL(StressLog, g_pStressLog);
// ILC emits a ContractDescriptor named "DotNetManagedContractDescriptor" with
// managed type layouts. We take its address so datadescriptor.inc can reference
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
index ed0a70b183609b..df6ae21a93edb2 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
@@ -84,6 +84,52 @@ CDAC_TYPE_FIELD(ExInfo, T_POINTER, PreviousNestedInfo, offsetof(ExInfo, m_pPrevE
CDAC_TYPE_FIELD(ExInfo, T_POINTER, ThrownObject, offsetof(ExInfo, m_exception))
CDAC_TYPE_END(ExInfo)
+// ========================
+// StressLog
+// ========================
+
+CDAC_TYPE_BEGIN(StressLog)
+CDAC_TYPE_SIZE(sizeof(StressLog))
+CDAC_TYPE_FIELD(StressLog, T_UINT32, LoggedFacilities, offsetof(StressLog, facilitiesToLog))
+CDAC_TYPE_FIELD(StressLog, T_UINT32, Level, offsetof(StressLog, levelToLog))
+CDAC_TYPE_FIELD(StressLog, T_UINT32, MaxSizePerThread, offsetof(StressLog, MaxSizePerThread))
+CDAC_TYPE_FIELD(StressLog, T_UINT32, MaxSizeTotal, offsetof(StressLog, MaxSizeTotal))
+CDAC_TYPE_FIELD(StressLog, T_INT32, TotalChunks, offsetof(StressLog, totalChunk))
+CDAC_TYPE_FIELD(StressLog, T_POINTER, Logs, offsetof(StressLog, logs))
+CDAC_TYPE_FIELD(StressLog, T_UINT64, TickFrequency, offsetof(StressLog, tickFrequency))
+CDAC_TYPE_FIELD(StressLog, T_UINT64, StartTimestamp, offsetof(StressLog, startTimeStamp))
+CDAC_TYPE_END(StressLog)
+
+CDAC_TYPE_BEGIN(ThreadStressLog)
+CDAC_TYPE_INDETERMINATE(ThreadStressLog)
+CDAC_TYPE_FIELD(ThreadStressLog, T_POINTER, Next, cdac_data::Next)
+CDAC_TYPE_FIELD(ThreadStressLog, T_UINT64, ThreadId, cdac_data::ThreadId)
+CDAC_TYPE_FIELD(ThreadStressLog, T_UINT8, WriteHasWrapped, cdac_data::WriteHasWrapped)
+CDAC_TYPE_FIELD(ThreadStressLog, T_POINTER, CurrentPtr, cdac_data::CurrentPtr)
+CDAC_TYPE_FIELD(ThreadStressLog, T_POINTER, ChunkListHead, cdac_data::ChunkListHead)
+CDAC_TYPE_FIELD(ThreadStressLog, T_POINTER, ChunkListTail, cdac_data::ChunkListTail)
+CDAC_TYPE_FIELD(ThreadStressLog, T_POINTER, CurrentWriteChunk, cdac_data::CurrentWriteChunk)
+CDAC_TYPE_END(ThreadStressLog)
+
+CDAC_TYPE_BEGIN(StressLogChunk)
+CDAC_TYPE_SIZE(sizeof(StressLogChunk))
+CDAC_TYPE_FIELD(StressLogChunk, T_POINTER, Prev, offsetof(StressLogChunk, prev))
+CDAC_TYPE_FIELD(StressLogChunk, T_POINTER, Next, offsetof(StressLogChunk, next))
+CDAC_TYPE_FIELD(StressLogChunk, T_ARRAY(T_UINT8), Buf, offsetof(StressLogChunk, buf))
+CDAC_TYPE_FIELD(StressLogChunk, T_UINT32, Sig1, offsetof(StressLogChunk, dwSig1))
+CDAC_TYPE_FIELD(StressLogChunk, T_UINT32, Sig2, offsetof(StressLogChunk, dwSig2))
+CDAC_TYPE_END(StressLogChunk)
+
+CDAC_TYPE_BEGIN(StressMsgHeader)
+CDAC_TYPE_SIZE(sizeof(StressMsg))
+CDAC_TYPE_END(StressMsgHeader)
+
+CDAC_TYPE_BEGIN(StressMsg)
+CDAC_TYPE_INDETERMINATE(StressMsg)
+CDAC_TYPE_FIELD(StressMsg, TYPE(StressMsgHeader), Header, 0)
+CDAC_TYPE_FIELD(StressMsg, T_POINTER, Args, offsetof(StressMsg, args))
+CDAC_TYPE_END(StressMsg)
+
CDAC_TYPES_END()
// ========================
@@ -119,10 +165,19 @@ CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 7)
CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 3)
#endif
+// StressLog globals (no module table in NativeAOT)
+CDAC_GLOBAL(StressLogEnabled, uint8, 1)
+CDAC_GLOBAL_POINTER(StressLog, &g_pStressLog)
+CDAC_GLOBAL(StressLogHasModuleTable, uint8, 0)
+CDAC_GLOBAL(StressLogChunkSize, uint32, STRESSLOG_CHUNK_SIZE)
+CDAC_GLOBAL(StressLogValidChunkSig, uint32, 0xCFCFCFCF)
+CDAC_GLOBAL(StressLogMaxMessageSize, uint64, (uint64_t)StressMsg::maxMsgSize)
+
// Contracts: declare which contracts this runtime supports
CDAC_GLOBAL_CONTRACT(Thread, 1001)
CDAC_GLOBAL_CONTRACT(Exception, 1)
CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, 1001)
+CDAC_GLOBAL_CONTRACT(StressLog, 2)
// 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
diff --git a/src/coreclr/nativeaot/Runtime/inc/stressLog.h b/src/coreclr/nativeaot/Runtime/inc/stressLog.h
index 17bf5fbfee1b20..282792d70fff33 100644
--- a/src/coreclr/nativeaot/Runtime/inc/stressLog.h
+++ b/src/coreclr/nativeaot/Runtime/inc/stressLog.h
@@ -47,6 +47,8 @@
#if defined(STRESS_LOG)
+#include "cdacdata.h"
+
//
// Logging levels and facilities
//
@@ -532,6 +534,7 @@ class ThreadStressLog {
PTR_Thread pThread; // thread associated with these stress logs
StressMsg * origCurPtr; // this holds the original curPtr before we start the dump
+ template friend struct ::cdac_data;
friend void PopulateDebugHeaders();
friend class StressLog;
@@ -793,4 +796,15 @@ inline StressMsg* ThreadStressLog::AdvWritePastBoundary(int cArgs) {
#endif // !STRESS_LOG || DACCESS_COMPILE
#endif // !__GCENV_BASE_INCLUDED__
+template<> struct cdac_data
+{
+ static constexpr size_t Next = offsetof(ThreadStressLog, next);
+ static constexpr size_t ThreadId = offsetof(ThreadStressLog, threadId);
+ static constexpr size_t WriteHasWrapped = offsetof(ThreadStressLog, writeHasWrapped);
+ static constexpr size_t CurrentPtr = offsetof(ThreadStressLog, curPtr);
+ static constexpr size_t ChunkListHead = offsetof(ThreadStressLog, chunkListHead);
+ static constexpr size_t ChunkListTail = offsetof(ThreadStressLog, chunkListTail);
+ static constexpr size_t CurrentWriteChunk = offsetof(ThreadStressLog, curWriteChunk);
+};
+
#endif // StressLog_h
From 85107c01a3c28eac04f72dbf9b5d788f69844b0c Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 22 Apr 2026 15:44:47 -0400
Subject: [PATCH 17/27] Address code review findings
- Use T_UINT32/T_UINT8/T_UINT64 macros for CDAC_GLOBAL types instead
of bare uint32/uint8/uint64, matching CoreCLR convention and enabling
debug-build type validation
- Combine Thread + RuntimeThreadLocals: put EEAllocContext directly on
Thread since Thread inherits from RuntimeThreadLocals at offset 0,
removing the unnecessary RuntimeThreadLocals intermediate type
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Runtime/datadescriptor/datadescriptor.inc | 48 +++++++++----------
src/coreclr/nativeaot/Runtime/inc/cdacdata.h | 31 +++++++++++-
2 files changed, 51 insertions(+), 28 deletions(-)
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
index df6ae21a93edb2..2ccb517aef4b24 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
@@ -28,7 +28,8 @@ CDAC_TYPE_FIELD(Thread, T_POINTER, LinkNext, offsetof(RuntimeThreadLocals, m_pNe
CDAC_TYPE_FIELD(Thread, T_POINTER, ExceptionTracker, offsetof(RuntimeThreadLocals, m_pExInfoStackHead))
CDAC_TYPE_FIELD(Thread, T_POINTER, CachedStackBase, offsetof(RuntimeThreadLocals, m_pStackHigh))
CDAC_TYPE_FIELD(Thread, T_POINTER, CachedStackLimit, offsetof(RuntimeThreadLocals, m_pStackLow))
-CDAC_TYPE_FIELD(Thread, TYPE(RuntimeThreadLocals), RuntimeThreadLocals, offsetof(RuntimeThreadLocals, m_eeAllocContext))
+// Thread inherits from RuntimeThreadLocals at offset 0, so EEAllocContext is directly on Thread
+CDAC_TYPE_FIELD(Thread, TYPE(EEAllocContext), AllocContext, offsetof(RuntimeThreadLocals, m_eeAllocContext))
CDAC_TYPE_FIELD(Thread, T_POINTER, TransitionFrame, offsetof(RuntimeThreadLocals, m_pTransitionFrame))
CDAC_TYPE_END(Thread)
@@ -37,11 +38,6 @@ CDAC_TYPE_INDETERMINATE(ThreadStore)
CDAC_TYPE_FIELD(ThreadStore, T_POINTER, FirstThreadLink, cdac_data::FirstThreadLink)
CDAC_TYPE_END(ThreadStore)
-CDAC_TYPE_BEGIN(RuntimeThreadLocals)
-CDAC_TYPE_INDETERMINATE(RuntimeThreadLocals)
-CDAC_TYPE_FIELD(RuntimeThreadLocals, TYPE(EEAllocContext), AllocContext, offsetof(RuntimeThreadLocals, m_eeAllocContext))
-CDAC_TYPE_END(RuntimeThreadLocals)
-
// ========================
// Allocation Context
// ========================
@@ -146,38 +142,38 @@ CDAC_GLOBAL_POINTER(GCLowestAddress, &g_lowest_address)
CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address)
// NativeAOT MethodTable flag constants (accessed via cdac_data friend)
-CDAC_GLOBAL(MethodTableEETypeKindMask, uint32, cdac_data::EETypeKindMask)
-CDAC_GLOBAL(MethodTableHasComponentSizeFlag, uint32, cdac_data::HasComponentSizeFlag)
-CDAC_GLOBAL(MethodTableHasFinalizerFlag, uint32, cdac_data::HasFinalizerFlag)
-CDAC_GLOBAL(MethodTableHasPointersFlag, uint32, cdac_data::HasPointersFlag)
-CDAC_GLOBAL(MethodTableIsGenericFlag, uint32, cdac_data::IsGenericFlag)
-CDAC_GLOBAL(MethodTableElementTypeMask, uint32, cdac_data::ElementTypeMask)
-CDAC_GLOBAL(MethodTableElementTypeShift, uint32, cdac_data::ElementTypeShift)
+CDAC_GLOBAL(MethodTableEETypeKindMask, T_UINT32, cdac_data::EETypeKindMask)
+CDAC_GLOBAL(MethodTableHasComponentSizeFlag, T_UINT32, cdac_data::HasComponentSizeFlag)
+CDAC_GLOBAL(MethodTableHasFinalizerFlag, T_UINT32, cdac_data::HasFinalizerFlag)
+CDAC_GLOBAL(MethodTableHasPointersFlag, T_UINT32, cdac_data::HasPointersFlag)
+CDAC_GLOBAL(MethodTableIsGenericFlag, T_UINT32, cdac_data::IsGenericFlag)
+CDAC_GLOBAL(MethodTableElementTypeMask, T_UINT32, cdac_data::ElementTypeMask)
+CDAC_GLOBAL(MethodTableElementTypeShift, T_UINT32, cdac_data::ElementTypeShift)
// Thread state flag constants
-CDAC_GLOBAL(ThreadStateFlagAttached, uint32, 0x00000001)
-CDAC_GLOBAL(ThreadStateFlagDetached, uint32, 0x00000002)
+CDAC_GLOBAL(ThreadStateFlagAttached, T_UINT32, 0x00000001)
+CDAC_GLOBAL(ThreadStateFlagDetached, T_UINT32, 0x00000002)
// Object contract globals
#ifdef TARGET_64BIT
-CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 7)
+CDAC_GLOBAL(ObjectToMethodTableUnmask, T_UINT8, 7)
#else
-CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 3)
+CDAC_GLOBAL(ObjectToMethodTableUnmask, T_UINT8, 3)
#endif
// StressLog globals (no module table in NativeAOT)
-CDAC_GLOBAL(StressLogEnabled, uint8, 1)
+CDAC_GLOBAL(StressLogEnabled, T_UINT8, 1)
CDAC_GLOBAL_POINTER(StressLog, &g_pStressLog)
-CDAC_GLOBAL(StressLogHasModuleTable, uint8, 0)
-CDAC_GLOBAL(StressLogChunkSize, uint32, STRESSLOG_CHUNK_SIZE)
-CDAC_GLOBAL(StressLogValidChunkSig, uint32, 0xCFCFCFCF)
-CDAC_GLOBAL(StressLogMaxMessageSize, uint64, (uint64_t)StressMsg::maxMsgSize)
+CDAC_GLOBAL(StressLogHasModuleTable, T_UINT8, 0)
+CDAC_GLOBAL(StressLogChunkSize, T_UINT32, STRESSLOG_CHUNK_SIZE)
+CDAC_GLOBAL(StressLogValidChunkSig, T_UINT32, 0xCFCFCFCF)
+CDAC_GLOBAL(StressLogMaxMessageSize, T_UINT64, (uint64_t)StressMsg::maxMsgSize)
// Contracts: declare which contracts this runtime supports
-CDAC_GLOBAL_CONTRACT(Thread, 1001)
-CDAC_GLOBAL_CONTRACT(Exception, 1)
-CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, 1001)
-CDAC_GLOBAL_CONTRACT(StressLog, 2)
+CDAC_GLOBAL_CONTRACT(Thread, n1)
+CDAC_GLOBAL_CONTRACT(Exception, c1)
+CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, n1)
+CDAC_GLOBAL_CONTRACT(StressLog, c2)
// 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
diff --git a/src/coreclr/nativeaot/Runtime/inc/cdacdata.h b/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
index 1feedebc73e9d3..86dcf4f475d774 100644
--- a/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
+++ b/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
@@ -1,5 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// Re-export the primary cdac_data template from the GC environment.
-#include "../../gc/env/cdacdata.h"
+// This is a copy of src/coreclr/gc/env/cdacdata.h for use by NativeAOT headers.
+
+#ifndef CDACDATA_H__
+#define CDACDATA_H__
+
+// This struct enables exposing information that is private to a class to the cDAC. For example,
+// if class C has private information that must be provided, declare cdac_data as a friend of C
+// where D is the specialization of cdac_data that will expose the information. Then provide a
+// specialization cdac_data with constexpr members exposing the information.
+//
+// Note: in the common case, type D will be type C.
+//
+// For example, if the offset of field F in class C is required:
+//
+// class C {
+// private:
+// int F;
+// friend struct ::cdac_data;
+// };
+// template<> struct cdac_data {
+// static constexpr size_t F_Offset = offsetof(C, F);
+// };
+//
+template
+struct cdac_data
+{
+};
+
+#endif// CDACDATA_H__
From 4897be660169b7be8c50785dea73810619d9a0f4 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Wed, 22 Apr 2026 17:20:32 -0400
Subject: [PATCH 18/27] Enable GC sub-descriptor for NativeAOT
Three fixes to populate the GC sub-descriptor:
1. Guard HEAP_ANALYZE fields in gc/datadescriptor with #ifdef HEAP_ANALYZE
(NativeAOT disables HEAP_ANALYZE, causing link errors for the
internal_root_array/heap_analyze_success fields)
2. Add -DGC_DESCRIPTOR to NativeAOT Runtime CMake so gc.cpp assigns
gcDacVars->gc_descriptor during GC initialization
3. Set GC_INTERFACE version numbers before GC_Initialize (matching
CoreCLR pattern) so PopulateDacVars enables the v6+ code path
that assigns gc_descriptor
4. Link both WKS and SVR GC descriptors into Runtime.ServerGC since
gc.cpp declares extern symbols for both when FEATURE_SVR_GC is defined
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/coreclr/gc/datadescriptor/datadescriptor.h | 2 ++
src/coreclr/gc/datadescriptor/datadescriptor.inc | 4 ++++
src/coreclr/nativeaot/Runtime/CMakeLists.txt | 1 +
src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt | 2 +-
src/coreclr/nativeaot/Runtime/gcheaputilities.cpp | 3 +++
5 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.h b/src/coreclr/gc/datadescriptor/datadescriptor.h
index d9589fedb0e056..d60a73e8e24a92 100644
--- a/src/coreclr/gc/datadescriptor/datadescriptor.h
+++ b/src/coreclr/gc/datadescriptor/datadescriptor.h
@@ -67,9 +67,11 @@ struct cdac_data
#endif // !USE_REGIONS
/* For use in GCHeapAnalyzeData APIs */
+#ifdef HEAP_ANALYZE
GC_HEAP_FIELD(InternalRootArray, internal_root_array)
GC_HEAP_FIELD(InternalRootArrayIndex, internal_root_array_index)
GC_HEAP_FIELD(HeapAnalyzeSuccess, heap_analyze_success)
+#endif // HEAP_ANALYZE
/* For use in GCInterestingInfo APIs */
GC_HEAP_FIELD(InterestingData, interesting_data_per_heap)
diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.inc b/src/coreclr/gc/datadescriptor/datadescriptor.inc
index 49243687f13f4b..2937aa7627eb8e 100644
--- a/src/coreclr/gc/datadescriptor/datadescriptor.inc
+++ b/src/coreclr/gc/datadescriptor/datadescriptor.inc
@@ -27,9 +27,11 @@ CDAC_TYPE_FIELD(GCHeap, T_POINTER, SavedSweepEphemeralSeg, cdac_data::SavedSweepEphemeralStart)
#endif // !USE_REGIONS
CDAC_TYPE_FIELD(GCHeap, TYPE(OomHistory), OomData, cdac_data::OomData)
+#ifdef HEAP_ANALYZE
CDAC_TYPE_FIELD(GCHeap, T_POINTER, InternalRootArray, cdac_data::InternalRootArray)
CDAC_TYPE_FIELD(GCHeap, T_NUINT, InternalRootArrayIndex, cdac_data::InternalRootArrayIndex)
CDAC_TYPE_FIELD(GCHeap, T_INT32, HeapAnalyzeSuccess, cdac_data::HeapAnalyzeSuccess)
+#endif // HEAP_ANALYZE
CDAC_TYPE_FIELD(GCHeap, T_POINTER, InterestingData, cdac_data::InterestingData)
CDAC_TYPE_FIELD(GCHeap, T_POINTER, CompactReasons, cdac_data::CompactReasons)
CDAC_TYPE_FIELD(GCHeap, T_POINTER, ExpandMechanisms, cdac_data::ExpandMechanisms)
@@ -166,9 +168,11 @@ CDAC_GLOBAL_POINTER(GCHeapSavedSweepEphemeralSeg, cdac_data::SavedSweepEphemeralStart)
#endif // !USE_REGIONS
CDAC_GLOBAL_POINTER(GCHeapOomData, cdac_data::OomData)
+#ifdef HEAP_ANALYZE
CDAC_GLOBAL_POINTER(GCHeapInternalRootArray, cdac_data::InternalRootArray)
CDAC_GLOBAL_POINTER(GCHeapInternalRootArrayIndex, cdac_data::InternalRootArrayIndex)
CDAC_GLOBAL_POINTER(GCHeapHeapAnalyzeSuccess, cdac_data::HeapAnalyzeSuccess)
+#endif // HEAP_ANALYZE
CDAC_GLOBAL_POINTER(GCHeapInterestingData, cdac_data::InterestingData)
CDAC_GLOBAL_POINTER(GCHeapCompactReasons, cdac_data::CompactReasons)
CDAC_GLOBAL_POINTER(GCHeapExpandMechanisms, cdac_data::ExpandMechanisms)
diff --git a/src/coreclr/nativeaot/Runtime/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/CMakeLists.txt
index d01bca65de79c5..f430090d5292a4 100644
--- a/src/coreclr/nativeaot/Runtime/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/CMakeLists.txt
@@ -290,6 +290,7 @@ if(CLR_CMAKE_TARGET_ARCH_I386 OR CLR_CMAKE_TARGET_ARCH_ARM)
add_definitions(-DINTERFACE_DISPATCH_CACHE_HAS_CELL_BACKPOINTER)
endif()
add_definitions(-D_LIB)
+add_definitions(-DGC_DESCRIPTOR)
# there is a problem with undefined symbols when this is set
# add_definitions(-DSTRESS_HEAP)
diff --git a/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
index 56fc4a3d7d6e0e..1cd73bfd07823d 100644
--- a/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
@@ -28,7 +28,7 @@ target_link_libraries(Runtime.WorkstationGC PRIVATE aotminipal nativeaot_cdac_co
add_library(Runtime.ServerGC STATIC ${COMMON_RUNTIME_SOURCES} ${FULL_RUNTIME_SOURCES} ${SERVER_GC_SOURCES} ${RUNTIME_ARCH_ASM_OBJECTS})
add_dependencies(Runtime.ServerGC aot_eventing_headers)
-target_link_libraries(Runtime.ServerGC PRIVATE aotminipal nativeaot_cdac_contract_descriptor nativeaot_gc_svr_descriptor)
+target_link_libraries(Runtime.ServerGC PRIVATE aotminipal nativeaot_cdac_contract_descriptor nativeaot_gc_wks_descriptor nativeaot_gc_svr_descriptor)
add_library(standalonegc-disabled STATIC ${STANDALONEGC_DISABLED_SOURCES})
add_dependencies(standalonegc-disabled aot_eventing_headers)
diff --git a/src/coreclr/nativeaot/Runtime/gcheaputilities.cpp b/src/coreclr/nativeaot/Runtime/gcheaputilities.cpp
index 2678b12c1aea0b..edafc599814e51 100644
--- a/src/coreclr/nativeaot/Runtime/gcheaputilities.cpp
+++ b/src/coreclr/nativeaot/Runtime/gcheaputilities.cpp
@@ -7,6 +7,7 @@
#include "gchandleutilities.h"
#include "gceventstatus.h"
+#include "gcinterface.h"
// This is the global GC heap, maintained by the VM.
GPTR_IMPL(IGCHeap, g_pGCHeap);
@@ -71,6 +72,8 @@ HRESULT GCHeapUtilities::InitializeDefaultGC()
IGCHeap* heap;
IGCHandleManager* manager;
+ g_gc_dac_vars.major_version_number = GC_INTERFACE_MAJOR_VERSION;
+ g_gc_dac_vars.minor_version_number = GC_INTERFACE_MINOR_VERSION;
HRESULT initResult = GC_Initialize(nullptr, &heap, &manager, &g_gc_dac_vars);
if (initResult == S_OK)
{
From a4db3d72a6178384c0c71ac156af7af0bbfc0412 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Fri, 24 Apr 2026 13:52:16 -0400
Subject: [PATCH 19/27] Fix stressLog guard, WASM export, and comment
- Guard cdac_data with #if defined(STRESS_LOG) to
prevent compile errors when NO_STRESS_LOG is defined (iOS/Android)
- Exclude browser/WASM from DotNetRuntimeContractDescriptor export
since the descriptor is not built for WASM targets
- Fix header layout comment to match actual field names
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets | 2 +-
src/coreclr/nativeaot/Runtime/inc/stressLog.h | 2 ++
.../Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs | 2 +-
3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
index b3ce3dc5f56cf1..950cc3d538a236 100644
--- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
+++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
@@ -237,7 +237,7 @@ The .NET Foundation licenses this file to you under the MIT license.
-
+
diff --git a/src/coreclr/nativeaot/Runtime/inc/stressLog.h b/src/coreclr/nativeaot/Runtime/inc/stressLog.h
index 282792d70fff33..888edc33961057 100644
--- a/src/coreclr/nativeaot/Runtime/inc/stressLog.h
+++ b/src/coreclr/nativeaot/Runtime/inc/stressLog.h
@@ -796,6 +796,7 @@ inline StressMsg* ThreadStressLog::AdvWritePastBoundary(int cArgs) {
#endif // !STRESS_LOG || DACCESS_COMPILE
#endif // !__GCENV_BASE_INCLUDED__
+#if defined(STRESS_LOG)
template<> struct cdac_data
{
static constexpr size_t Next = offsetof(ThreadStressLog, next);
@@ -806,5 +807,6 @@ template<> struct cdac_data
static constexpr size_t ChunkListTail = offsetof(ThreadStressLog, chunkListTail);
static constexpr size_t CurrentWriteChunk = offsetof(ThreadStressLog, curWriteChunk);
};
+#endif // STRESS_LOG
#endif // StressLog_h
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
index 104c0e6e15e101..7118dfabd8a2f6 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -49,7 +49,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
byte[] jsonBytes = BuildJsonDescriptor(factory);
- // Header layout: magic(8) + flags(4) + desc_size(4) + desc_ptr(ptr) + count(4) + pad(4) + data_ptr(ptr)
+ // 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);
From 669702194f956163b72a9cd3b461e90dffb2202c Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Fri, 24 Apr 2026 14:19:53 -0400
Subject: [PATCH 20/27] Guard GC_DESCRIPTOR define for non-WASM builds
WASM doesn't build the datadescriptor subdirectory, so defining
GC_DESCRIPTOR unconditionally would cause undefined symbol errors
for GCContractDescriptorWKS/SVR on browser/WASM builds.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/coreclr/nativeaot/Runtime/CMakeLists.txt | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/coreclr/nativeaot/Runtime/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/CMakeLists.txt
index f430090d5292a4..058e4e20adde42 100644
--- a/src/coreclr/nativeaot/Runtime/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/CMakeLists.txt
@@ -290,7 +290,9 @@ if(CLR_CMAKE_TARGET_ARCH_I386 OR CLR_CMAKE_TARGET_ARCH_ARM)
add_definitions(-DINTERFACE_DISPATCH_CACHE_HAS_CELL_BACKPOINTER)
endif()
add_definitions(-D_LIB)
-add_definitions(-DGC_DESCRIPTOR)
+if(NOT CLR_CMAKE_TARGET_ARCH_WASM)
+ add_definitions(-DGC_DESCRIPTOR)
+endif()
# there is a problem with undefined symbols when this is set
# add_definitions(-DSTRESS_HEAP)
From 459c33bec725cc814a49be1669f2d93cde79fe4a Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Fri, 24 Apr 2026 14:39:44 -0400
Subject: [PATCH 21/27] Remove CompareToImpl override from
ManagedDataDescriptorNode
Singleton nodes should not override CompareToImpl to return 0.
The base class SortableDependencyNode throws NotImplementedException
for duplicate instances, which is the correct pattern for singletons
(matching ArrayMapNode and other singleton nodes in ILC).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs | 5 -----
1 file changed, 5 deletions(-)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
index 7118dfabd8a2f6..dd763ade0ae531 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -161,10 +161,5 @@ private static string GetMangledTypeName(MetadataType type)
protected internal override int Phase => (int)ObjectNodePhase.Ordered;
public override int ClassCode => 0x4d444e01;
-
- public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
- {
- return 0; // Singleton
- }
}
}
From 0d09c17685c433dcdfb54730fe614b6973d70e09 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Mon, 27 Apr 2026 17:06:31 -0400
Subject: [PATCH 22/27] Use ILC NameMangler for type names and EmitUInt for
uint32_t fields
- Use factory.NameMangler.GetMangledTypeName() for type name mangling
instead of custom GetMangledTypeName method
- Use EmitUInt for descriptor_size, pointer_data_count, and pad0 fields
to match the uint32_t types in contract-descriptor.h
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../ManagedDataDescriptorNode.cs | 27 +++++--------------
1 file changed, 6 insertions(+), 21 deletions(-)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
index dd763ade0ae531..baec670c47e248 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -64,16 +64,16 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
builder.EmitUInt(flags);
// uint32_t descriptor_size
- builder.EmitInt(jsonBytes.Length);
+ 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.EmitInt(0);
+ builder.EmitUInt(0);
// uint32_t pad0
- builder.EmitInt(0);
+ builder.EmitUInt(0);
// void** pointer_data = null
builder.EmitZeroPointer();
@@ -107,7 +107,7 @@ private static byte[] BuildJsonDescriptor(NodeFactory factory)
if (!ecmaType.HasCustomAttribute(DataContractAttributeNamespace, DataContractAttributeName))
continue;
- WriteType(writer, ecmaType);
+ WriteType(writer, ecmaType, factory.NameMangler);
}
writer.WriteEndObject();
@@ -120,9 +120,9 @@ private static byte[] BuildJsonDescriptor(NodeFactory factory)
return stream.ToArray();
}
- private static void WriteType(Utf8JsonWriter writer, EcmaType type)
+ private static void WriteType(Utf8JsonWriter writer, EcmaType type, NameMangler nameMangler)
{
- writer.WriteStartObject(GetMangledTypeName(type));
+ writer.WriteStartObject(nameMangler.GetMangledTypeName(type).ToString());
if (type.IsValueType)
{
@@ -143,21 +143,6 @@ private static void WriteType(Utf8JsonWriter writer, EcmaType type)
writer.WriteEndObject();
}
- ///
- /// Returns a mangled fully-qualified type name matching the NativeAOT symbol convention
- /// (e.g., System.Threading.Thread → System_Threading_Thread).
- ///
- private static string GetMangledTypeName(MetadataType type)
- {
- string ns = type.GetNamespace();
- string name = type.GetName();
-
- if (string.IsNullOrEmpty(ns))
- return name;
-
- return $"{ns}.{name}".Replace('.', '_');
- }
-
protected internal override int Phase => (int)ObjectNodePhase.Ordered;
public override int ClassCode => 0x4d444e01;
From 4542b38c3f4975bae83c2113492c56ebaf7d774a Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Tue, 28 Apr 2026 13:19:38 -0400
Subject: [PATCH 23/27] comments
---
src/coreclr/nativeaot/Runtime/DebugHeader.cpp | 7 +++--
.../nativeaot/Runtime/RuntimeInstance.cpp | 10 +++----
.../nativeaot/Runtime/RuntimeInstance.h | 1 -
src/coreclr/nativeaot/Runtime/inc/cdacdata.h | 30 +------------------
.../Diagnostics/DataContractAttribute.cs | 2 +-
.../ManagedDataDescriptorNode.cs | 21 +++++++++++--
6 files changed, 29 insertions(+), 42 deletions(-)
diff --git a/src/coreclr/nativeaot/Runtime/DebugHeader.cpp b/src/coreclr/nativeaot/Runtime/DebugHeader.cpp
index 9c2a06892c629f..1accfdf5d5ae6a 100644
--- a/src/coreclr/nativeaot/Runtime/DebugHeader.cpp
+++ b/src/coreclr/nativeaot/Runtime/DebugHeader.cpp
@@ -80,8 +80,9 @@ struct DotNetRuntimeDebugHeader
// v1-v4 were never doc'ed but history is source control if you need it
// v5 - Thread now has an m_eeAllocContext field and the previous m_rgbAllocContextBuffer
// field is nested inside of it.
+ // v6 - Removed RuntimeInstance.m_pThreadStore field, added g_pThreadStore global.
//
- const uint16_t MajorVersion = 5;
+ const uint16_t MajorVersion = 6;
// This counter can be incremented to indicate back-compatible changes
// This field must be encoded little endian, regardless of the typical endianness of
@@ -256,13 +257,15 @@ extern "C" void PopulateDebugHeaders()
MAKE_DEBUG_FIELD_ENTRY(StressMsg, args);
MAKE_SIZE_ENTRY(RuntimeInstance);
- MAKE_DEBUG_FIELD_ENTRY(RuntimeInstance, m_pThreadStore);
MAKE_GLOBAL_ENTRY(g_CrashInfoBuffer);
RuntimeInstance *g_pTheRuntimeInstance = GetRuntimeInstance();
MAKE_GLOBAL_ENTRY(g_pTheRuntimeInstance);
+ ThreadStore *g_pThreadStore = ThreadStore::s_pThreadStore;
+ MAKE_GLOBAL_ENTRY(g_pThreadStore);
+
MAKE_GLOBAL_ENTRY(g_gcDacGlobals);
MAKE_GLOBAL_ENTRY(g_pFreeObjectEEType);
diff --git a/src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp b/src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp
index 12e8b60f2939dd..141e53aa4af2e3 100644
--- a/src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp
+++ b/src/coreclr/nativeaot/Runtime/RuntimeInstance.cpp
@@ -39,7 +39,7 @@ uint8_t g_CrashInfoBuffer[MAX_CRASHINFOBUFFER_SIZE] = { 0 };
ThreadStore * RuntimeInstance::GetThreadStore()
{
- return m_pThreadStore;
+ return ThreadStore::s_pThreadStore;
}
FCIMPL1(uint8_t *, RhGetCrashInfoBuffer, int32_t* pcbMaxSize)
@@ -179,7 +179,6 @@ RuntimeInstance::OsModuleList* RuntimeInstance::GetOsModuleList()
#ifndef DACCESS_COMPILE
RuntimeInstance::RuntimeInstance() :
- m_pThreadStore(NULL),
m_CodeManager(NULL),
m_conservativeStackReportingEnabled(false),
m_pUnboxingStubsRegion(NULL)
@@ -188,10 +187,10 @@ RuntimeInstance::RuntimeInstance() :
RuntimeInstance::~RuntimeInstance()
{
- if (NULL != m_pThreadStore)
+ if (NULL != ThreadStore::s_pThreadStore)
{
- delete m_pThreadStore;
- m_pThreadStore = NULL;
+ delete ThreadStore::s_pThreadStore;
+ ThreadStore::s_pThreadStore = NULL;
}
}
@@ -314,7 +313,6 @@ bool RuntimeInstance::Initialize(HANDLE hPalInstance)
pThreadStore.SuppressRelease();
pRuntimeInstance.SuppressRelease();
- pRuntimeInstance->m_pThreadStore = pThreadStore;
pRuntimeInstance->m_hPalInstance = hPalInstance;
ASSERT_MSG(g_pTheRuntimeInstance == NULL, "multi-instances are not supported");
diff --git a/src/coreclr/nativeaot/Runtime/RuntimeInstance.h b/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
index 349bf1dbc7b578..e50ddac18dfd40 100644
--- a/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
+++ b/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
@@ -21,7 +21,6 @@ class RuntimeInstance
friend class Thread;
friend void PopulateDebugHeaders();
- PTR_ThreadStore m_pThreadStore;
HANDLE m_hPalInstance; // this is the HANDLE passed into DllMain
public:
diff --git a/src/coreclr/nativeaot/Runtime/inc/cdacdata.h b/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
index 86dcf4f475d774..ffbf4431b381c7 100644
--- a/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
+++ b/src/coreclr/nativeaot/Runtime/inc/cdacdata.h
@@ -1,32 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// This is a copy of src/coreclr/gc/env/cdacdata.h for use by NativeAOT headers.
-
-#ifndef CDACDATA_H__
-#define CDACDATA_H__
-
-// This struct enables exposing information that is private to a class to the cDAC. For example,
-// if class C has private information that must be provided, declare cdac_data as a friend of C
-// where D is the specialization of cdac_data that will expose the information. Then provide a
-// specialization cdac_data with constexpr members exposing the information.
-//
-// Note: in the common case, type D will be type C.
-//
-// For example, if the offset of field F in class C is required:
-//
-// class C {
-// private:
-// int F;
-// friend struct ::cdac_data;
-// };
-// template<> struct cdac_data {
-// static constexpr size_t F_Offset = offsetof(C, F);
-// };
-//
-template
-struct cdac_data
-{
-};
-
-#endif// CDACDATA_H__
+#include "../../../inc/cdacdata.h"
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/DataContractAttribute.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/DataContractAttribute.cs
index 4d1cafeff9a3ce..062a9d311c5972 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/DataContractAttribute.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/DataContractAttribute.cs
@@ -6,7 +6,7 @@ namespace System.Diagnostics
///
/// When applied to a type, indicates that ILC should include its field layout in the
/// managed cDAC data descriptor so diagnostic tools can inspect instances without
- /// runtime metadata. When applied to a field of such a type, indicates ILC should
+ /// runtime metadata or symbols. When applied to a field of such a type, indicates ILC should
/// include that field's offset in the descriptor.
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field, Inherited = false)]
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
index baec670c47e248..1431a1ebba772d 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -107,7 +107,7 @@ private static byte[] BuildJsonDescriptor(NodeFactory factory)
if (!ecmaType.HasCustomAttribute(DataContractAttributeNamespace, DataContractAttributeName))
continue;
- WriteType(writer, ecmaType, factory.NameMangler);
+ WriteType(writer, ecmaType);
}
writer.WriteEndObject();
@@ -120,9 +120,9 @@ private static byte[] BuildJsonDescriptor(NodeFactory factory)
return stream.ToArray();
}
- private static void WriteType(Utf8JsonWriter writer, EcmaType type, NameMangler nameMangler)
+ private static void WriteType(Utf8JsonWriter writer, EcmaType type)
{
- writer.WriteStartObject(nameMangler.GetMangledTypeName(type).ToString());
+ writer.WriteStartObject(GetFullTypeName(type));
if (type.IsValueType)
{
@@ -143,6 +143,21 @@ private static void WriteType(Utf8JsonWriter writer, EcmaType type, NameMangler
writer.WriteEndObject();
}
+ ///
+ /// Returns a fully-qualified type name for cDAC descriptors
+ /// (e.g., "System.Threading.Thread").
+ ///
+ private static string GetFullTypeName(MetadataType type)
+ {
+ string ns = type.GetNamespace();
+ string name = type.GetName();
+
+ if (string.IsNullOrEmpty(ns))
+ return name;
+
+ return $"{ns}.{name}";
+ }
+
protected internal override int Phase => (int)ObjectNodePhase.Ordered;
public override int ClassCode => 0x4d444e01;
From 3411dae2ee124e6d75c926908c98219046575e42 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Tue, 28 Apr 2026 13:29:53 -0400
Subject: [PATCH 24/27] Remove RuntimeInstance from DebugHeader
RuntimeInstance size entry and g_pTheRuntimeInstance global are no
longer needed now that ThreadStore is accessed via g_pThreadStore.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
src/coreclr/nativeaot/Runtime/DebugHeader.cpp | 5 -----
1 file changed, 5 deletions(-)
diff --git a/src/coreclr/nativeaot/Runtime/DebugHeader.cpp b/src/coreclr/nativeaot/Runtime/DebugHeader.cpp
index 1accfdf5d5ae6a..0f86c1e2383792 100644
--- a/src/coreclr/nativeaot/Runtime/DebugHeader.cpp
+++ b/src/coreclr/nativeaot/Runtime/DebugHeader.cpp
@@ -256,13 +256,8 @@ extern "C" void PopulateDebugHeaders()
MAKE_SIZE_ENTRY(StressMsg);
MAKE_DEBUG_FIELD_ENTRY(StressMsg, args);
- MAKE_SIZE_ENTRY(RuntimeInstance);
-
MAKE_GLOBAL_ENTRY(g_CrashInfoBuffer);
- RuntimeInstance *g_pTheRuntimeInstance = GetRuntimeInstance();
- MAKE_GLOBAL_ENTRY(g_pTheRuntimeInstance);
-
ThreadStore *g_pThreadStore = ThreadStore::s_pThreadStore;
MAKE_GLOBAL_ENTRY(g_pThreadStore);
From f7fcfdee55b95ce5b00481200e506fa6ca4290ac Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Tue, 28 Apr 2026 14:12:59 -0400
Subject: [PATCH 25/27] Support nested types in managed descriptor type names
Walk ContainingType chain to produce fully-qualified names
with + separator (e.g., System.Foo.Outer+Inner).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
index 1431a1ebba772d..6d1d957375ae7e 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -145,10 +145,13 @@ private static void WriteType(Utf8JsonWriter writer, EcmaType type)
///
/// Returns a fully-qualified type name for cDAC descriptors
- /// (e.g., "System.Threading.Thread").
+ /// (e.g., "System.Threading.Thread" or "System.Foo.Outer+Inner").
///
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();
From 4b95fc715474fec236a75a2fd48bc7c82171a1c0 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Tue, 28 Apr 2026 14:35:20 -0400
Subject: [PATCH 26/27] Remove stray comment from datadescriptor.h
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../nativeaot/Runtime/datadescriptor/datadescriptor.h | 7 -------
1 file changed, 7 deletions(-)
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
index a38daea487becf..4b5b4e1824f413 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
@@ -1,13 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// This header provides the includes needed for datadescriptor.inc to use
-// offsetof() on NativeAOT runtime data structures.
-//
-// Note: Some NativeAOT types have private members that offsetof() cannot access
-// from this compilation unit. For those types, we use known offset constants
-// validated at build time by AsmOffsetsVerify.cpp and DebugHeader.cpp static_asserts.
-
#include "common.h"
#include "gcenv.h"
#include "gcheaputilities.h"
From 73d5e9a1a5dfd69cb97393abc454baaece5465b4 Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Tue, 28 Apr 2026 15:18:19 -0400
Subject: [PATCH 27/27] Remove MethodTable flag constants and add
baseline/contracts to managed descriptor
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Remove MethodTable flag constant globals from datadescriptor.inc
and cdac_data in MethodTable.h — these are already
defined as part of the contract in MethodTableFlags_1.cs
- Add baseline and contracts properties to managed sub-descriptor
JSON for self-describing format consistency
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../nativeaot/Runtime/datadescriptor/datadescriptor.inc | 9 ---------
src/coreclr/nativeaot/Runtime/inc/MethodTable.h | 8 --------
.../DependencyAnalysis/ManagedDataDescriptorNode.cs | 4 ++++
3 files changed, 4 insertions(+), 17 deletions(-)
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
index 2ccb517aef4b24..a3aaf5376bfddf 100644
--- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
@@ -141,15 +141,6 @@ CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &g_pFreeObjectEEType)
CDAC_GLOBAL_POINTER(GCLowestAddress, &g_lowest_address)
CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address)
-// NativeAOT MethodTable flag constants (accessed via cdac_data friend)
-CDAC_GLOBAL(MethodTableEETypeKindMask, T_UINT32, cdac_data::EETypeKindMask)
-CDAC_GLOBAL(MethodTableHasComponentSizeFlag, T_UINT32, cdac_data::HasComponentSizeFlag)
-CDAC_GLOBAL(MethodTableHasFinalizerFlag, T_UINT32, cdac_data::HasFinalizerFlag)
-CDAC_GLOBAL(MethodTableHasPointersFlag, T_UINT32, cdac_data::HasPointersFlag)
-CDAC_GLOBAL(MethodTableIsGenericFlag, T_UINT32, cdac_data::IsGenericFlag)
-CDAC_GLOBAL(MethodTableElementTypeMask, T_UINT32, cdac_data::ElementTypeMask)
-CDAC_GLOBAL(MethodTableElementTypeShift, T_UINT32, cdac_data::ElementTypeShift)
-
// Thread state flag constants
CDAC_GLOBAL(ThreadStateFlagAttached, T_UINT32, 0x00000001)
CDAC_GLOBAL(ThreadStateFlagDetached, T_UINT32, 0x00000002)
diff --git a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h
index 885c613f308307..8ff6c556404cf2 100644
--- a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h
+++ b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h
@@ -358,14 +358,6 @@ template<> struct cdac_data
static constexpr size_t NumInterfaces = offsetof(MethodTable, m_usNumInterfaces);
static constexpr size_t HashCode = offsetof(MethodTable, m_uHashCode);
static constexpr size_t VTable = offsetof(MethodTable, m_VTable);
-
- static constexpr uint32_t EETypeKindMask = MethodTable::EETypeKindMask;
- static constexpr uint32_t HasComponentSizeFlag = MethodTable::HasComponentSizeFlag;
- static constexpr uint32_t HasFinalizerFlag = MethodTable::HasFinalizerFlag;
- static constexpr uint32_t HasPointersFlag = MethodTable::HasPointersFlag;
- static constexpr uint32_t IsGenericFlag = MethodTable::IsGenericFlag;
- static constexpr uint32_t ElementTypeMask = MethodTable::ElementTypeMask;
- static constexpr uint32_t ElementTypeShift = MethodTable::ElementTypeShift;
};
#pragma warning(pop)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
index 6d1d957375ae7e..7041768e63d325 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs
@@ -97,6 +97,7 @@ private static byte[] BuildJsonDescriptor(NodeFactory factory)
{
writer.WriteStartObject();
writer.WriteNumber("version", 0);
+ writer.WriteString("baseline", "empty");
writer.WriteStartObject("types");
foreach (TypeDesc type in factory.MetadataManager.GetTypesWithEETypes())
@@ -114,6 +115,9 @@ private static byte[] BuildJsonDescriptor(NodeFactory factory)
writer.WriteStartObject("globals");
writer.WriteEndObject();
+ writer.WriteStartObject("contracts");
+ writer.WriteEndObject();
+
writer.WriteEndObject();
}