diff --git a/src/coreclr/nativeaot/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs b/src/coreclr/nativeaot/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs index 7f5372e706fbe5..b39210b9eff8d4 100644 --- a/src/coreclr/nativeaot/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs +++ b/src/coreclr/nativeaot/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -337,10 +338,25 @@ static void WriteRelPtr32(void* dest, void* value) } } + [DataContract] [StructLayout(LayoutKind.Sequential)] internal unsafe struct TypeManagerSlot { + [DataContract] public TypeManagerHandle TypeManager; + + [DataContract] public int ModuleIndex; } + + [DataContract] + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct TypeThreadStaticIndex + { + [DataContract] + public TypeManagerSlot* TypeManagerSlot; + + [DataContract] + public nint ClassIndex; + } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs index c8cea459512be1..04d585b26de163 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs @@ -338,7 +338,9 @@ public static unsafe object LoadPointerTypeFieldValueFromValueType(TypedReferenc public static unsafe object GetThreadStaticBase(IntPtr cookie) { - return ThreadStatics.GetThreadStaticBaseForType(*(TypeManagerSlot**)cookie, (int)*((IntPtr*)(cookie) + 1)); + TypeThreadStaticIndex* index = (TypeThreadStaticIndex*)cookie; + + return ThreadStatics.GetThreadStaticBaseForType(index->TypeManagerSlot, (int)index->ClassIndex); } public static int GetHighestStaticThreadStaticIndex(TypeManagerHandle typeManager) diff --git a/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj b/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj index e96a5deff9b401..10a0661a98c7f5 100644 --- a/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj +++ b/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj @@ -71,6 +71,9 @@ + + System\Diagnostics\DataContractAttribute.cs + Internal\NativeFormat\NativeFormatReader.Primitives.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs index 6295e9341f6bf3..3d39bf2e45e599 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Text; +using System.Text.Json; using ILCompiler.Dataflow; +using ILCompiler.DependencyAnalysis; using Internal.IL; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; @@ -80,7 +82,11 @@ public void TestDependencyGraphInvariants(EcmaMethod method) .UseILProvider(ilProvider); IILScanner scanner = builder.GetILScannerBuilder() - .UseCompilationRoots(new ICompilationRootProvider[] { new SingleMethodRootProvider(method) }) + .UseCompilationRoots(new ICompilationRootProvider[] + { + new SingleMethodRootProvider(method), + new ThreadStaticBaseRootProvider(method.OwningType) + }) .UseMetadataManager(metadataManager) .ToILScanner(); @@ -119,6 +125,13 @@ public void TestDependencyGraphInvariants(EcmaMethod method) Assert.DoesNotContain(methodToCheck.GetCanonMethodTarget(CanonicalFormKind.Specific), results.CompiledMethodBodies); } + foreach (var attr in method.GetDecodedCustomAttributes(assetsNamespace, "GeneratesDataDescriptorTypeAttribute")) + { + foundSomethingToCheck = true; + string typeName = (string)attr.FixedArguments[0].Value; + AssertDataDescriptorType(metadataManager, typeName, context.Target.PointerSize); + } + // // Make sure we checked something // @@ -133,5 +146,39 @@ private static MethodDesc GetMethodFromAttribute(CustomAttributeValue attr) return ((TypeDesc)attr.FixedArguments[0].Value).GetMethod(Encoding.UTF8.GetBytes((string)attr.FixedArguments[1].Value), null); } + + private static void AssertDataDescriptorType(UsageBasedMetadataManager metadataManager, string typeName, int pointerSize) + { + byte[] json = ManagedDataDescriptorNode.BuildJsonDescriptor(metadataManager); + using JsonDocument document = JsonDocument.Parse(json); + JsonElement jsonType = document.RootElement.GetProperty("types").GetProperty(typeName); + Assert.Equal(2 * pointerSize, jsonType.GetProperty("!").GetInt32()); + if (typeName == "Internal.Runtime.CompilerHelpers.TypeManagerSlot") + { + Assert.Equal(0, jsonType.GetProperty("TypeManager").GetInt32()); + Assert.Equal(pointerSize, jsonType.GetProperty("ModuleIndex").GetInt32()); + } + else + { + Assert.Equal("Internal.Runtime.CompilerHelpers.TypeThreadStaticIndex", typeName); + Assert.Equal(0, jsonType.GetProperty("TypeManagerSlot").GetInt32()); + Assert.Equal(pointerSize, jsonType.GetProperty("ClassIndex").GetInt32()); + } + } + + private sealed class ThreadStaticBaseRootProvider : ICompilationRootProvider + { + private readonly TypeDesc _type; + + public ThreadStaticBaseRootProvider(TypeDesc type) + { + _type = type; + } + + public void AddCompilationRoots(IRootingServiceProvider rootProvider) + { + rootProvider.RootThreadStaticBaseForType(_type, "Dependency graph test"); + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/DependencyGraph.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/DependencyGraph.cs index ef2fdab5b8b790..e8251f6e3fb018 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/DependencyGraph.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/DependencyGraph.cs @@ -64,6 +64,18 @@ public static void Entrypoint() new Derived().CallBaseGenericVirtualDirectly(); } } + + class ThreadStaticDataDescriptorTest + { + [ThreadStatic] + public static object Value = null; + + [GeneratesDataDescriptorType("Internal.Runtime.CompilerHelpers.TypeManagerSlot")] + [GeneratesDataDescriptorType("Internal.Runtime.CompilerHelpers.TypeThreadStaticIndex")] + public static void Entrypoint() + { + } + } } #region Custom attributes that define invariants to check @@ -92,5 +104,18 @@ public NoMethodBodyAttribute(Type owningType, string methodName) { } public Type[] GenericArguments; public Type[] Signature; } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class GeneratesDataDescriptorTypeAttribute : Attribute + { + public GeneratesDataDescriptorTypeAttribute(string name) { } + } #endregion } + +namespace System +{ + public sealed class ThreadStaticAttribute : Attribute + { + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/IDataDescriptorTypeProvider.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/IDataDescriptorTypeProvider.cs new file mode 100644 index 00000000000000..2b1ca2d2775fc6 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/IDataDescriptorTypeProvider.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + internal interface IDataDescriptorTypeProvider + { + MetadataType GetDataDescriptorType(CompilerTypeSystemContext context); + } +} 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 c8930fa8c6d339..7ce611339ca716 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ManagedDataDescriptorNode.cs @@ -17,9 +17,9 @@ 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. /// - /// Types are discovered by scanning MetadataManager.GetTypesWithEETypes() for types - /// annotated with [DataContract], ensuring only types that actually have a MethodTable - /// in the binary are included. + /// Runtime types are discovered by scanning MetadataManager.GetTypesWithEETypes() for + /// [DataContract]-annotated types. ILC object nodes can also register annotated managed + /// layout types for data that does not have a MethodTable. /// public class ManagedDataDescriptorNode : ObjectNode, ISymbolDefinitionNode { @@ -48,7 +48,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) if (relocsOnly) return new ObjectData(Array.Empty(), Array.Empty(), 1, new ISymbolDefinitionNode[] { this }); - byte[] jsonBytes = BuildJsonDescriptor(factory); + byte[] jsonBytes = BuildJsonDescriptor(factory.MetadataManager); // 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; @@ -92,7 +92,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) /// ContractDescriptorParser. Types are objects with an optional "!" size sigil and /// field-name properties mapped to their offsets. /// - private static byte[] BuildJsonDescriptor(NodeFactory factory) + internal static byte[] BuildJsonDescriptor(MetadataManager metadataManager) { using var stream = new MemoryStream(); using (var writer = new Utf8JsonWriter(stream)) @@ -102,7 +102,7 @@ private static byte[] BuildJsonDescriptor(NodeFactory factory) writer.WriteString("baseline", "empty"); writer.WriteStartObject("types"); - foreach (TypeDesc type in factory.MetadataManager.GetTypesWithEETypes()) + foreach (TypeDesc type in metadataManager.GetTypesWithEETypes()) { if (type is not EcmaType ecmaType) continue; @@ -112,6 +112,17 @@ private static byte[] BuildJsonDescriptor(NodeFactory factory) WriteType(writer, ecmaType); } + + foreach (MetadataType type in metadataManager.GetDataDescriptorTypes()) + { + if (type is not EcmaType ecmaType || + !ecmaType.HasCustomAttribute(DataContractAttributeNamespace, DataContractAttributeName)) + { + throw new InvalidOperationException($"Registered data descriptor type '{type}' is not annotated with [DataContract]."); + } + + WriteType(writer, ecmaType); + } writer.WriteEndObject(); writer.WriteStartObject("globals"); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 636bf28595a23f..e6ff2d0d48a078 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -1666,6 +1666,7 @@ public Utf8String GetSymbolAlternateName(ISymbolNode node, out bool isHidden) public virtual void AttachToDependencyGraph(DependencyAnalyzerBase graph) { ReadyToRunHeader = new ReadyToRunHeaderNode(); + MetadataManager.AttachToDependencyGraph(graph); graph.AddRoot(ReadyToRunHeader, "ReadyToRunHeader is always generated"); graph.AddRoot(new ModulesSectionNode(), "ModulesSection is always generated"); @@ -1694,7 +1695,6 @@ public virtual void AttachToDependencyGraph(DependencyAnalyzerBase InteropStubManager.AddToReadyToRunHeader(ReadyToRunHeader, this, commonFixupsTableNode); TypeMapManager.AddToReadyToRunHeader(ReadyToRunHeader, this, new ExternalReferencesTableIndex(commonFixupsTableNode, this)); MetadataManager.AddToReadyToRunHeader(ReadyToRunHeader, this, commonFixupsTableNode); - MetadataManager.AttachToDependencyGraph(graph); TypeMapManager.AttachToDependencyGraph(graph); ReadyToRunHeader.Add(MetadataManager.BlobIdToReadyToRunSection(ReflectionMapBlob.CommonFixupsTable), commonFixupsTableNode); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeManagerIndirectionNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeManagerIndirectionNode.cs index 37240280b95d8b..f7b56d6d0f7ca7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeManagerIndirectionNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeManagerIndirectionNode.cs @@ -2,10 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using Internal.Text; +using Internal.TypeSystem; namespace ILCompiler.DependencyAnalysis { - public class TypeManagerIndirectionNode : ObjectNode, ISymbolDefinitionNode + public class TypeManagerIndirectionNode : ObjectNode, ISymbolDefinitionNode, IDataDescriptorTypeProvider { public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { @@ -31,5 +32,8 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) } public override int ClassCode => -2028598574; + + MetadataType IDataDescriptorTypeProvider.GetDataDescriptorType(CompilerTypeSystemContext context) + => context.SystemModule.GetType("Internal.Runtime.CompilerHelpers"u8, "TypeManagerSlot"u8); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeThreadStaticIndexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeThreadStaticIndexNode.cs index 95853fe6cf35cf..aa155e41b3d5ba 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeThreadStaticIndexNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeThreadStaticIndexNode.cs @@ -10,7 +10,7 @@ namespace ILCompiler.DependencyAnalysis /// /// Represents a node containing information necessary at runtime to locate type's thread static base. /// - public class TypeThreadStaticIndexNode : DehydratableObjectNode, ISymbolDefinitionNode, ISortableSymbolNode + public class TypeThreadStaticIndexNode : DehydratableObjectNode, ISymbolDefinitionNode, ISortableSymbolNode, IDataDescriptorTypeProvider { private MetadataType _type; private ThreadStaticsNode _inlinedThreadStatics; @@ -96,6 +96,9 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo public override int ClassCode => -149601250; + MetadataType IDataDescriptorTypeProvider.GetDataDescriptorType(CompilerTypeSystemContext context) + => context.SystemModule.GetType("Internal.Runtime.CompilerHelpers"u8, "TypeThreadStaticIndex"u8); + public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) { return comparer.Compare(_type, ((TypeThreadStaticIndexNode)other)._type); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs index 734b9d1ad7faf8..da44aea5e51379 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ManagedDataDescriptorProvider.cs @@ -7,8 +7,8 @@ namespace ILCompiler { /// /// Compilation root provider that adds the managed cDAC data descriptor node. - /// The node discovers [DataContract]-annotated types from MetadataManager.GetTypesWithEETypes() - /// during object data emission, ensuring only types with MethodTables are included. + /// The node discovers [DataContract]-annotated runtime types and layouts registered by + /// ILC object nodes during object data emission. /// public class ManagedDataDescriptorProvider : ICompilationRootProvider { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs index 7de1176f3cbbe4..22f04180ccd9e4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs @@ -69,6 +69,7 @@ public abstract class MetadataManager : ICompilationRootProvider private readonly SortedSet _typesWithThreadStaticsGenerated = new SortedSet(CompilerComparer.Instance); private readonly SortedSet _typesWithEETypesGenerated = new SortedSet(TypeSystemComparer.Instance); private readonly SortedSet _typesWithConstructedEETypesGenerated = new SortedSet(TypeSystemComparer.Instance); + private readonly SortedSet _dataDescriptorTypes = new SortedSet(TypeSystemComparer.Instance); private readonly SortedSet _methodsGenerated = new SortedSet(TypeSystemComparer.Instance); private readonly SortedSet _reflectableMethods = new SortedSet(TypeSystemComparer.Instance); private readonly SortedSet _genericDictionariesGenerated = new SortedSet(CompilerComparer.Instance); @@ -250,6 +251,11 @@ public virtual void AddToReadyToRunHeader(ReadyToRunHeaderNode header, NodeFacto protected virtual void Graph_NewMarkedNode(DependencyNodeCore obj) { + if (obj is IDataDescriptorTypeProvider dataDescriptorTypeProvider) + { + _dataDescriptorTypes.Add(dataDescriptorTypeProvider.GetDataDescriptorType(_typeSystemContext)); + } + var eetypeNode = obj as EETypeNode; if (eetypeNode != null) { @@ -1156,6 +1162,11 @@ internal IEnumerable GetTypesWithEETypes() return _typesWithEETypesGenerated; } + internal IEnumerable GetDataDescriptorTypes() + { + return _dataDescriptorTypes; + } + internal IEnumerable GetTypesWithConstructedEETypes() { return _typesWithConstructedEETypesGenerated; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index e26214457ac8c3..53cda0682f2287 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -20,6 +20,10 @@ $(NoWarn);CA1859 + + + + @@ -641,6 +645,7 @@ +