Skip to content
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
4c1143b
Track type cast targets
jkoritzinsky May 27, 2025
ce10c21
Scan for casts as part of the MethodBodyScanner instead
jkoritzinsky May 27, 2025
5495324
After feedback, first draft implementing external type map (compiler …
jkoritzinsky Jun 3, 2025
bb84f34
Add first pass of external type map implementation for NAOT runtime
jkoritzinsky Jun 3, 2025
650c033
Force the target type in the external type map entry to be constructa…
jkoritzinsky Jun 3, 2025
1e9b608
First pass implementing the proxy type map
jkoritzinsky Jun 4, 2025
a619e5d
Fix compiler asserts
jkoritzinsky Jun 4, 2025
2a5531e
Don't root type map custom attributes
jkoritzinsky Jun 4, 2025
c7f3638
Provide a mechanism to specify NativeAOT-only test apps
jkoritzinsky Jun 4, 2025
847760b
Fix format writing so it actually works
jkoritzinsky Jun 4, 2025
6c825d8
Cache type map attribute type lookup
jkoritzinsky Jun 5, 2025
64af9d3
Add additional node in the IL Scanner when we see a cast that may be …
jkoritzinsky Jun 5, 2025
c3bf1ed
Enable the TypeMapApp test for NativeAOT and adjust as necessary to m…
jkoritzinsky Jun 5, 2025
2ed2d97
Split type map manager type so we don't need to pass null
jkoritzinsky Jun 5, 2025
4334a63
Match attributes based on name. Don't care about the assembly
jkoritzinsky Jun 5, 2025
654731b
Hook up the type map manager to the trimming test driver
jkoritzinsky Jun 5, 2025
89a776f
Add TrimmingTest and add one more test case in the NativeAOT test app
jkoritzinsky Jun 5, 2025
99cb5c9
Add warning validation
jkoritzinsky Jun 5, 2025
b73848d
Fix node name
jkoritzinsky Jun 5, 2025
0f083a5
Don't materialize a string for every entry as we search the hashtable
jkoritzinsky Jun 5, 2025
a48496a
Remove the "type map entry" nodes and rename the "associated" type ma…
jkoritzinsky Jun 6, 2025
bcf98ef
Collapse MetadataBasedTypeMapManager and TypeMapManager
jkoritzinsky Jun 6, 2025
462334c
We know all the type map nodes up front in the AnalysisBasedTypeMapMa…
jkoritzinsky Jun 6, 2025
e4ff1d9
Add an interface for each set of nodes to collapse the number of list…
jkoritzinsky Jun 6, 2025
1881042
Refactor the concept of "optimized out observations we must not forge…
jkoritzinsky Jun 6, 2025
3a9dbc9
Move attribute check outside of UsageBasedMetadataManager
jkoritzinsky Jun 6, 2025
098f57f
Put TypeMapStates into its own file as a top-level type
jkoritzinsky Jun 6, 2025
b3026cb
Fix comparison to match pattern
jkoritzinsky Jun 6, 2025
de0ed0b
Fix cast typo
jkoritzinsky Jun 7, 2025
2f40f9c
Make diagnostic names for invalid type map nodes unique
jkoritzinsky Jun 7, 2025
0445c4c
Just use reference equality and the default hash code for the hashtab…
jkoritzinsky Jun 9, 2025
3eae3d1
Add some more test cases around interfaces and use nameof instead of …
jkoritzinsky Jun 9, 2025
46cd22b
Add support in the illink analyzer test host for no-body classes and …
jkoritzinsky Jun 9, 2025
1659b1e
Don't try to remember optimizations. Just root whatever we scanned du…
jkoritzinsky Jun 10, 2025
0b037e8
Add comment on TrimTarget
jkoritzinsky Jun 10, 2025
aa1ada7
Fix failures in TypeMapApp test
jkoritzinsky Jun 11, 2025
da59332
Seal types
jkoritzinsky Jun 12, 2025
c37d585
Put stubs on the generated asssembly
jkoritzinsky Jun 12, 2025
44c0635
Don't instantiate Dictionary<TKey, TValue> on the "no map" path.
jkoritzinsky Jun 12, 2025
680e8bc
Persist maps for all requests and throw an exception when no map is a…
jkoritzinsky Jun 12, 2025
a31d9dc
Fix indentation and revert change in NativeFormatWriter
jkoritzinsky Jun 13, 2025
74c8a5a
Add more interesting test cases.
jkoritzinsky Jun 16, 2025
364edc7
Merge branch 'main' of https://github.com/dotnet/runtime into typemap…
jkoritzinsky Jun 17, 2025
1571bf5
Add a more in-depth spec at the IL level describing the type map feature
jkoritzinsky Jun 18, 2025
41a894b
PR feedback
jkoritzinsky Jun 18, 2025
e45a026
Cleanup based on primary constructor guidance
jkoritzinsky Jun 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion eng/testing/linker/trimmingTests.targets
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<_SkippedAppSourceFiles Include="@(TestConsoleAppSourceFiles)" Condition="$([System.String]::Copy('%(TestConsoleAppSourceFiles.SkipOnTestRuntimes)').Contains('$(OutputRID)'))" />

<_SkippedAppSourceFiles Include="@(TestConsoleAppSourceFiles)" Condition="'$(RunNativeAotTestApps)' == 'true' and '%(TestConsoleAppSourceFiles.NativeAotIncompatible)' == 'true'" />
<_SkippedAppSourceFiles Include="@(TestConsoleAppSourceFiles)" Condition="'$(RunNativeAotTestApps)' != 'true' and '%(TestConsoleAppSourceFiles.NativeAotOnly)' == 'true'" />

<_AppSourceFiles Include="@(TestConsoleAppSourceFiles)" Exclude="@(_SkippedAppSourceFiles)" />

Expand Down Expand Up @@ -165,7 +166,7 @@
</Exec>

<Error Condition="'$(ExecutionExitCode)' != '100'" Text="Error: [Failed Test]: %(TestConsoleApps.ProjectCompileItems). The Command %(TestConsoleApps.TestCommand) return a non-success exit code $(ExecutionExitCode)." ContinueOnError="ErrorAndContinue" />

<!-- Remove test projects dir to save disk space if the test was successful. Ignore failures as this is best effort.
Don't use Removedir as ContinueOnError on it doesn't work when using warnaserror/TreatWarningsAsErrors. -->
<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -825,4 +825,8 @@
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String)</Target>
</Suppression>
</Suppressions>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Internal.NativeFormat.TypeHashingAlgorithms</Target>
</Suppression>
</Suppressions>
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@
<Compile Include="System\Runtime\CompilerServices\StaticClassConstructionContext.cs" />
<Compile Include="System\Runtime\ExceptionIDs.cs" />
<Compile Include="System\Runtime\GCSettings.NativeAot.cs" />
<Compile Include="System\Runtime\InteropServices\TypeMapLazyDictionary.NativeAot.cs" />
<Compile Include="System\Runtime\TypeLoaderExports.cs" />
<Compile Include="System\Runtime\ThunkPool.cs" />
<Compile Include="System\Runtime\InteropServices\ComEventsHelper.NativeAot.cs" Condition="'$(FeatureCominterop)' == 'true'" />
Expand Down Expand Up @@ -322,6 +323,9 @@
<Compile Include="$(CompilerCommonPath)\TypeSystem\Common\Utilities\LockFreeReaderHashtableOfPointers.cs">
<Link>Utilities\LockFreeReaderHashtableOfPointers.cs</Link>
</Compile>
<Compile Include="$(CompilerCommonPath)\TypeSystem\Common\TypeHashingAlgorithms.cs">
<Link>Utilities\TypeHashingAlgorithms.cs</Link>
</Compile>
<Compile Include="$(AotCommonPath)\System\Collections\Generic\LowLevelList.cs">
<Link>System\Collections\Generic\LowLevelList.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;

using Internal.NativeFormat;
using Internal.Reflection.Core.Execution;
using Internal.Runtime;
using Internal.Runtime.Augments;
using Internal.Runtime.TypeLoader;

namespace System.Runtime.InteropServices
{
internal static class TypeMapLazyDictionary
{
public static IReadOnlyDictionary<string, Type> CreateExternalTypeMap(RuntimeType typeMapGroup)
{
RuntimeTypeHandle typeMapGroupHandle = typeMapGroup.TypeHandle;
foreach (TypeManagerHandle module in RuntimeAugments.GetLoadedModules())
{
if (!TryGetNativeReaderForBlob(module, ReflectionMapBlob.ExternalTypeMap, out NativeReader externalTypeMapReader))
{
continue;
}
NativeParser externalTypeMapParser = new NativeParser(externalTypeMapReader, 0);
NativeHashtable externalTypeMapTable = new NativeHashtable(externalTypeMapParser);

ExternalReferencesTable externalReferences = default;
externalReferences.InitializeCommonFixupsTable(module);

var lookup = externalTypeMapTable.Lookup(typeMapGroupHandle.GetHashCode());
NativeParser entryParser;
while (!(entryParser = lookup.GetNext()).IsNull)
{
RuntimeTypeHandle foundTypeMapGroup = externalReferences.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned());
if (!foundTypeMapGroup.Equals(typeMapGroupHandle))
{
continue;
}
bool isValid = entryParser.GetUnsigned() == 1;
if (!isValid)
{
unsafe
{
delegate*<void> exceptionStub = (delegate*<void>)externalReferences.GetFunctionPointerFromIndex(entryParser.GetUnsigned());
exceptionStub();
Debug.Fail("Expected exception stub to throw an exception.");
return null; // Should never reach here, as the exception stub should throw an exception.
}
}

return new ExternalTypeMapDictionary(new NativeHashtable(entryParser), externalReferences);
}
}

throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(typeMapGroup);
}

public static IReadOnlyDictionary<Type, Type> CreateProxyTypeMap(RuntimeType typeMapGroup)
{
RuntimeTypeHandle typeMapGroupHandle = typeMapGroup.TypeHandle;
foreach (TypeManagerHandle module in RuntimeAugments.GetLoadedModules())
{
if (!TryGetNativeReaderForBlob(module, ReflectionMapBlob.ProxyTypeMap, out NativeReader externalTypeMapReader))
{
continue;
}
NativeParser externalTypeMapParser = new NativeParser(externalTypeMapReader, 0);
NativeHashtable externalTypeMapTable = new NativeHashtable(externalTypeMapParser);

ExternalReferencesTable externalReferences = default;
externalReferences.InitializeCommonFixupsTable(module);

var lookup = externalTypeMapTable.Lookup(typeMapGroupHandle.GetHashCode());
NativeParser entryParser;
while (!(entryParser = lookup.GetNext()).IsNull)
{
RuntimeTypeHandle foundTypeMapGroup = externalReferences.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned());
if (!foundTypeMapGroup.Equals(typeMapGroupHandle))
{
continue;
}
bool isValid = entryParser.GetUnsigned() == 1;
if (!isValid)
{
unsafe
{
delegate*<void> exceptionStub = (delegate*<void>)externalReferences.GetFunctionPointerFromIndex(entryParser.GetUnsigned());
exceptionStub();
Debug.Fail("Expected exception stub to throw an exception.");
return null; // Should never reach here, as the exception stub should throw an exception.
}
}

return new AssociatedTypeMapDictionary(new NativeHashtable(entryParser), externalReferences);
}
}

throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(typeMapGroup);
}

private static unsafe bool TryGetNativeReaderForBlob(TypeManagerHandle module, ReflectionMapBlob blob, out NativeReader reader)
{
byte* pBlob;
uint cbBlob;

if (RuntimeImports.RhFindBlob(module, (uint)blob, &pBlob, &cbBlob))
{
reader = new NativeReader(pBlob, cbBlob);
return true;
}

reader = default;
return false;
}

private abstract class TypeMapDictionaryBase<TKey> : IReadOnlyDictionary<TKey, Type>
{
public abstract Type this[TKey key] { get; }
public abstract bool TryGetValue(TKey key, [MaybeNullWhen(false)] out Type value);
// Not supported to avoid exposing TypeMap entries in a manner that
// would violate invariants the Trimmer is attempting to enforce.
public IEnumerable<TKey> Keys => throw new NotSupportedException();
public IEnumerable<Type> Values => throw new NotSupportedException();
public int Count => throw new NotSupportedException();
public bool ContainsKey(TKey key) => throw new NotSupportedException();
public IEnumerator<KeyValuePair<TKey, Type>> GetEnumerator() => throw new NotSupportedException();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

private sealed class ExternalTypeMapDictionary(NativeHashtable table, ExternalReferencesTable externalReferences) : TypeMapDictionaryBase<string>
{
public override Type this[string key]
{
get
{
if (!TryGetValue(key, out Type? value))
{
ThrowHelper.ThrowKeyNotFoundException(key);
}
return value;
}
}

public override bool TryGetValue(string key, [MaybeNullWhen(false)] out Type value)
{
var lookup = table.Lookup(TypeHashingAlgorithms.ComputeNameHashCode(key));
NativeParser entryParser;
while (!(entryParser = lookup.GetNext()).IsNull)
{
if (entryParser.StringEquals(key))
{
entryParser.SkipString();
RuntimeTypeHandle typeHandle = externalReferences.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned());
value = Type.GetTypeFromHandle(typeHandle)!;
return true;
}
}
value = null;
return false;
}
}

private sealed class AssociatedTypeMapDictionary(NativeHashtable table, ExternalReferencesTable externalReferences) : TypeMapDictionaryBase<Type>
{
public override Type this[Type key]
{
get
{
if (!TryGetValue(key, out Type? value))
{
ThrowHelper.ThrowKeyNotFoundException(key);
}
return value;
}
}

public override bool TryGetValue(Type key, [MaybeNullWhen(false)] out Type value)
{
RuntimeTypeHandle handle = key.TypeHandle;
if (handle.IsNull)
{
value = null;
return false;
}

var lookup = table.Lookup(handle.GetHashCode());
NativeParser entryParser;
while (!(entryParser = lookup.GetNext()).IsNull)
{
RuntimeTypeHandle foundHandle = externalReferences.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned());
if (foundHandle.Equals(handle))
{
RuntimeTypeHandle targetHandle = externalReferences.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned());
value = Type.GetTypeFromHandle(targetHandle)!;
return true;
}
}
value = null;
return false;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,6 @@
<Compile Include="Internal\Runtime\TypeLoader\EETypeCreator.cs" />
<Compile Include="Internal\Reflection\Execution\AssemblyBinderImplementation.cs" />
<Compile Include="$(CompilerCommonPath)\Internal\Metadata\NativeFormat\MetadataTypeHashingAlgorithms.cs" />
<Compile Include="$(CompilerCommonPath)\TypeSystem\Common\TypeHashingAlgorithms.cs" />
<Compile Include="$(AotCommonPath)\Internal\Runtime\TypeLoader\ExternalReferencesTable.cs" />
<Compile Include="Internal\Runtime\TypeLoader\ExternalReferencesTable.NativeFormatModuleInfo.cs" />
<Compile Include="Internal\Runtime\TypeLoader\GenericDictionary.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ protected enum ObjectNodeOrder
StaticsInfoHashtableNode,
ReflectionVirtualInvokeMapNode,
ArrayOfEmbeddedPointersNode,
ExternalTypeMapObjectNode,
ProxyTypeMapObjectNode,
ExternalReferencesTableNode,
StackTraceEmbeddedMetadataNode,
StackTraceMethodMappingNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public void SkipString()
{
_offset = _reader.SkipString(_offset);
}

public bool StringEquals(string str)
{
return _reader.StringEquals(_offset, str);
}
}

internal partial class NativeReader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2041,15 +2041,5 @@ internal override void Save(NativeWriter writer)
}
}
}

public override bool Equals(object obj)
{
throw new NotImplementedException();
}

public override int GetHashCode()
{
throw new NotImplementedException();
}
}
}
4 changes: 4 additions & 0 deletions src/coreclr/tools/Common/Internal/Runtime/MetadataBlob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,9 @@ internal enum ReflectionMapBlob
StaticsInfoHashtable = 34,
GenericMethodsHashtable = 35,
ExactMethodInstantiationsHashtable = 36,

// Type map blobs:
ExternalTypeMap = 40,
ProxyTypeMap = 41,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
// Generic functions to compute the hashcode value of types
// ---------------------------------------------------------------------------

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;

namespace Internal.NativeFormat
{
#if SYSTEM_PRIVATE_CORELIB
[CLSCompliant(false)]
#endif
public static class TypeHashingAlgorithms
{
public struct HashCodeBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public partial class CompilationBuilder
protected SecurityMitigationOptions _mitigationOptions;
protected bool _dehydrate;
protected bool _useDwarf5;
protected TypeMapManager _typeMapManager = new UsageBasedTypeMapManager(TypeMapMetadata.Empty);

partial void InitializePartial()
{
Expand Down Expand Up @@ -129,6 +130,12 @@ public CompilationBuilder UseDwarf5(bool value)
return this;
}

public CompilationBuilder UseTypeMapManager(TypeMapManager typeMapManager)
{
_typeMapManager = typeMapManager;
return this;
}

protected PreinitializationManager GetPreinitializationManager()
{
if (_preinitializationManager == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,36 @@ private partial bool TryHandleIntrinsic (
_diagnosticContext.AddDiagnostic(DiagnosticId.AvoidAssemblyGetFilesInSingleFile, calledMethod.GetDisplayName());
break;

case IntrinsicId.TypeMapping_GetOrCreateExternalTypeMapping:
{
if (calledMethod.Method.Instantiation[0].ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true))
{
// We only support GetOrCreateExternalTypeMapping for a fully specified type.
_diagnosticContext.AddDiagnostic(DiagnosticId.TypeMapGroupTypeCannotBeStaticallyDetermined,
calledMethod.Method.Instantiation[0].GetDisplayName());
}
else
{
TypeDesc typeMapGroup = calledMethod.Method.Instantiation[0];
_reflectionMarker.Dependencies.Add(_reflectionMarker.Factory.ExternalTypeMapRequest(typeMapGroup), "TypeMapping.GetOrCreateExternalTypeMapping called on type");
}
break;
}
case IntrinsicId.TypeMapping_GetOrCreateProxyTypeMapping:
{
if (calledMethod.Method.Instantiation[0].ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true))
{
// We only support GetOrCreateProxyTypeMapping for a fully specified type.
_diagnosticContext.AddDiagnostic(DiagnosticId.TypeMapGroupTypeCannotBeStaticallyDetermined,
calledMethod.Method.Instantiation[0].GetDisplayName());
}
else
{
TypeDesc typeMapGroup = calledMethod.Method.Instantiation[0];
_reflectionMarker.Dependencies.Add(_reflectionMarker.Factory.ProxyTypeMapRequest(typeMapGroup), "TypeMapping.GetOrCreateProxyTypeMapping called on type");
}
break;
}
default:
return false;
}
Expand Down
Loading
Loading