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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions docs/tools/illink/data-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,52 @@ ILLink uses several data formats to control or influence the trimming process. T
## Output Data Formats

- [Dependencies Trace](#dependencies-trace-format)
- [Type Maps](#type-map-output-format)

## Type Map Output Format

ILLink and NativeAOT ILC accept `--output-typemaps <file>` to write a UTF-8 XML
sidecar describing their retained type maps. This is an opt-in intermediate build
artifact, independent of dependency tracing. It does not root any additional
types or maps.
In SDK builds, setting `TypeMapXmlFile` to a non-empty path enables generation
and selects the output path. If it is unset or empty, no artifact is written.
SDK consumers can choose an intermediate path so the sidecar is not published
or bundled with the application.

```xml
<typemaps>
<group type="Example.JavaUniverse,Example.Interop">
<external>
<entry key="example/MainActivity" value="Example.MainActivity,Example.App" />
</external>
<proxy>
<entry key="Example.ManagedPeer,Example.App" value="Example.JavaProxy,Example.Interop" />
</proxy>
</group>
<group type="Example.OtherUniverse,Example.Interop">
<external />
</group>
</typemaps>
```

Each `group` identifies a universe by its type. Its optional `external` and
`proxy` sections contain the retained mappings of the corresponding kind.
An absent section means that map kind was not requested/retained. An empty
section represents a retained map with no reported entries.

Every `entry` has the same two required attributes, `key` and `value`:

| Map kind | `key` | `value` |
| --- | --- | --- |
| `external` | The original external string, such as a Java/JNI name. | The mapped managed type name. |
| `proxy` | The managed source type name. | The mapped proxy type name. |

Managed type names (`group.type`, every `entry.value`, and proxy `entry.key`)
use reflection type-name syntax with simple assembly qualification.

External keys are preserved verbatim, without normalization.
Consumers should only use artifacts from successful builds.

# Format Details

Expand Down
21 changes: 21 additions & 0 deletions docs/tools/illink/illink-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,27 @@ Example:

`illink -d ../../libs -a Program.dll`

### Writing retained type maps

`--output-typemaps <file>` writes an XML sidecar containing the retained external
(string-to-type) and proxy (type-to-type) maps, grouped by universe. No report is
generated by default.

Both tools write to the path supplied on the command line.
For SDK builds, setting `TypeMapXmlFile` to a non-empty path enables output and
specifies the path passed to the tool. Leaving it unset or empty disables
output. SDK consumers should choose an intermediate path under `obj`; the XML
is not copied to the publish directory or included in a single-file bundle.

The sidecar is an expected output of the trimming/compilation step. Deleting it
causes that step to run again. An unchanged build skips the producer, while an
actual producer invocation replaces the sidecar even when its contents match.
Consumers generating R8 rules should write those rules only when their contents
change, so unrelated managed-code changes do not force R8 work just because the
sidecar was rewritten.

See the [type map output format](data-formats.md#type-map-output-format).

### Excluding framework features

One of the ways to reduce core assembly sizes is by removing framework capabilities. This
Expand Down
7 changes: 7 additions & 0 deletions eng/testing/linker/SupportFiles/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,11 @@
<!-- Overriding these targets as these projects won't need to binplace -->
<Target Name="PublishTestAsSelfContained" />

<!-- Lets a test inspect the type maps written by ILC or ILLink when it sets TypeMapXmlFile. -->
<Target Name="CopyTypeMapsForTesting"
AfterTargets="Publish"
Condition="'$(TypeMapXmlFile)' != ''">
<Copy SourceFiles="$(TypeMapXmlFile)" DestinationFiles="$(PublishDir)typemaps.xml" />
</Target>

</Project>
32 changes: 30 additions & 2 deletions eng/testing/linker/trimmingTests.targets
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
<TestConsoleApps Include="@(_AppSourceFiles->'%(ProjectFile)')">
<ProjectCompileItems>%(FullPath)</ProjectCompileItems>
</TestConsoleApps>
<TestConsoleApps AdditionalProperties="MSBuildEnableWorkloadResolver=$(MSBuildEnableWorkloadResolver)" Condition="'$(MSBuildEnableWorkloadResolver)' != ''" />
<TestConsoleApps AdditionalProperties="%(TestConsoleApps.AdditionalProperties);MSBuildEnableWorkloadResolver=$(MSBuildEnableWorkloadResolver)" Condition="'$(MSBuildEnableWorkloadResolver)' != ''" />
</ItemGroup>
</Target>

Expand All @@ -70,6 +70,16 @@
<_additionalProjectReferencesString>@(_additionalProjectReference, '%0a')</_additionalProjectReferencesString>
</PropertyGroup>

<ItemGroup>
<_testProjectReference Remove="@(_testProjectReference)" />
<_testProjectReference Include="%(TestConsoleApps.AdditionalProjectReferences)" />
</ItemGroup>

<PropertyGroup>
<_additionalProjectReferencesString>$(_additionalProjectReferencesString)
@(_testProjectReference->'&lt;ProjectReference Include=&quot;%(FullPath)&quot; SkipUseReferenceAssembly=&quot;true&quot; /&gt;', '%0a')</_additionalProjectReferencesString>
</PropertyGroup>

<ItemGroup>
<_additionalProjectSourceFiles Include="%(TestConsoleApps.AdditionalSourceFiles)" />
</ItemGroup>
Expand Down Expand Up @@ -143,9 +153,27 @@
DependsOnTargets="GenerateProjects"
Returns="@(TestConsoleApps)" />

<Target Name="PublishTrimmedProjects"
<Target Name="BuildTestProjectReferences"
DependsOnTargets="GenerateProjects">

<ItemGroup>
<_testProjectToBuild Include="%(TestConsoleApps.AdditionalProjectReferences)" />
</ItemGroup>

<MSBuild Projects="@(_testProjectToBuild)"
Targets="Restore"
Condition="'@(_testProjectToBuild)' != '' and '$(SkipTrimmingProjectsRestore)' != 'true'"
Properties="Configuration=$(Configuration)" />

<MSBuild Projects="@(_testProjectToBuild)"
Targets="Build"
Condition="'@(_testProjectToBuild)' != ''"
Properties="Configuration=$(Configuration);BuildProjectReferences=false" />
</Target>

<Target Name="PublishTrimmedProjects"
DependsOnTargets="GenerateProjects;BuildTestProjectReferences">

<MSBuild Projects="@(TestConsoleApps)"
Targets="Restore"
Condition="'$(SkipTrimmingProjectsRestore)' != 'true'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<ExportsFile Condition="$(ExportsFile) == '' and '$(BuildingFrameworkLibrary)' != 'true'">$(NativeIntermediateOutputPath)$(TargetName)$(ExportsFileExt)</ExportsFile>

<IlcCompileOutput>$(NativeObject)</IlcCompileOutput>
<_IlcTypeMapXmlFile Condition="'$(TypeMapXmlFile)' != '' and '$(BuildingFrameworkLibrary)' != 'true'">$(TypeMapXmlFile)</_IlcTypeMapXmlFile>

<LinkNativeDependsOn>IlcCompile</LinkNativeDependsOn>

Expand Down Expand Up @@ -270,6 +271,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<IlcArg Condition="$(IlcDwarfVersion) == '5'" Include="--gdwarf-5" />
<IlcArg Condition="$(IlcGenerateMapFile) == 'true'" Include="--map:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).map.xml" />
<IlcArg Condition="$(IlcGenerateMstatFile) == 'true'" Include="--mstat:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).mstat" />
<IlcArg Condition="'$(_IlcTypeMapXmlFile)' != ''" Include="--output-typemaps:$(_IlcTypeMapXmlFile)" />
<IlcArg Condition="$(IlcGenerateDgmlFile) == 'true'" Include="--dgmllog:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).codegen.dgml.xml" />
<IlcArg Condition="$(IlcGenerateDgmlFile) == 'true'" Include="--scandgmllog:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).scan.dgml.xml" />
<IlcArg Condition="$(IlcMultiModule) != 'true' and $(NativeDebugSymbols) == 'true' and $(EnableSourceLink) == 'true' and $(_targetOS) == 'win'" Include="--sourcelink:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).sourcelink" />
Expand Down Expand Up @@ -346,7 +348,7 @@ The .NET Foundation licenses this file to you under the MIT license.

<Target Name="IlcCompile"
Inputs="@(IlcCompileInput);@(IlcReference);@(IlcSatelliteAssembly);@(RdXmlFile);$(IlcOrderFile);%(ManagedBinary.IlcRspFile)"
Outputs="%(ManagedBinary.IlcOutputFile)"
Outputs="%(ManagedBinary.IlcOutputFile);$(_IlcTypeMapXmlFile)"
DependsOnTargets="WriteIlcRspFileForCompilation;$(IlcCompileDependsOn)">
<Message Text="Generating native code" Importance="high" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public ExternalTypeMapObjectNode(TypeMapManager manager, INativeFormatTypeRefere
_externalReferences = externalReferences;
}

#if !READYTORUN
internal IEnumerable<IExternalTypeMapNode> GetTypeMaps() => _manager.GetExternalTypeMaps();
#endif

public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
{
// This node does not trigger generation of other nodes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal interface IExternalTypeMapNode : IDependencyNode, ISortableNode
Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider externalReferences);
#if !READYTORUN
IExternalTypeMapNode ToAnalysisBasedNode(NodeFactory factory);
IEnumerable<KeyValuePair<string, TypeDesc>> GetEntries(NodeFactory factory);
#endif
}
}
1 change: 1 addition & 0 deletions src/coreclr/tools/Common/Compiler/IProxyTypeMapNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal interface IProxyTypeMapNode : IDependencyNode, ISortableNode
Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider externalReferences);
#if !READYTORUN
IProxyTypeMapNode ToAnalysisBasedNode(NodeFactory factory);
IEnumerable<KeyValuePair<TypeDesc, TypeDesc>> GetEntries(NodeFactory factory);
#endif
}
}
4 changes: 4 additions & 0 deletions src/coreclr/tools/Common/Compiler/ProxyTypeMapObjectNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ public ProxyTypeMapObjectNode(TypeMapManager manager, INativeFormatTypeReference
_externalReferences = externalReferences;
}

#if !READYTORUN
internal IEnumerable<IProxyTypeMapNode> GetTypeMaps() => _manager.GetProxyTypeMaps();
#endif

public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
{
// This node does not trigger generation of other nodes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ internal sealed class AnalyzedExternalTypeMapNode(TypeDesc typeMapGroup, IReadOn
{
public TypeDesc TypeMapGroup => typeMapGroup;

public IEnumerable<KeyValuePair<string, TypeDesc>> GetEntries(NodeFactory factory) => entries;

public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider externalReferences)
{
VertexHashtable typeMapHashTable = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ namespace ILCompiler.DependencyAnalysis
internal sealed class AnalyzedProxyTypeMapNode(TypeDesc typeMapGroup, IReadOnlyDictionary<TypeDesc, TypeDesc> entries) : SortableDependencyNode, IProxyTypeMapNode
{
public TypeDesc TypeMapGroup => typeMapGroup;

public IEnumerable<KeyValuePair<TypeDesc, TypeDesc>> GetEntries(NodeFactory factory) => entries;

public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider externalReferences)
{
VertexHashtable typeMapHashTable = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer
private static IEETypeNode GetEffectiveTrimTargetType(NodeFactory factory, TypeDesc trimmingTargetType)
=> RuntimeConstructableTypeDependencies.GetEffectiveTrimTargetType(factory, trimmingTargetType, conditionConstructed: false);

public IEnumerable<KeyValuePair<string, TypeDesc>> GetEntries(NodeFactory factory)
{
foreach ((string name, IEETypeNode target) in GetMarkedEntries(factory))
yield return new KeyValuePair<string, TypeDesc>(name, target.Type);
}

public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider externalReferences)
{
VertexHashtable typeMapHashTable = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,7 @@ public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section se
}

public IExternalTypeMapNode ToAnalysisBasedNode(NodeFactory factory) => new InvalidExternalTypeMapNode(TypeMapGroup, ThrowingMethodStub);

public IEnumerable<KeyValuePair<string, TypeDesc>> GetEntries(NodeFactory factory) => [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,7 @@ public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section se
}

public IProxyTypeMapNode ToAnalysisBasedNode(NodeFactory factory) => new InvalidProxyTypeMapNode(TypeMapGroup, ThrowingMethodStub);

public IEnumerable<KeyValuePair<TypeDesc, TypeDesc>> GetEntries(NodeFactory factory) => [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDep
}
}

public IEnumerable<KeyValuePair<TypeDesc, TypeDesc>> GetEntries(NodeFactory factory)
{
foreach ((IEETypeNode key, IEETypeNode value) in GetMarkedEntries(factory))
yield return new KeyValuePair<TypeDesc, TypeDesc>(key.Type, value.Type);
}

public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider externalReferences)
{
VertexHashtable typeMapHashTable = new VertexHashtable();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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.IO;

using ILCompiler.DependencyAnalysis;
using Internal.TypeSystem;

using Map = ILLink.Shared.TypeMapXmlWriter.Map;

namespace ILCompiler;

public sealed class TypeMapObjectDumper(string fileName) : ObjectDumper
{
private readonly CustomAttributeTypeNameFormatter _typeNameFormatter = new CustomAttributeTypeNameFormatter();
private readonly SortedDictionary<string, (Map External, Map Proxy)> _groups = new(StringComparer.Ordinal);

internal override void Begin()
{
}

protected override void DumpObjectNode(NodeFactory factory, ObjectNode node, ObjectNode.ObjectData objectData)
{
if (node is ExternalTypeMapObjectNode externalMaps)
{
foreach (IExternalTypeMapNode mapNode in externalMaps.GetTypeMaps())
{
var map = new Map();
foreach (KeyValuePair<string, TypeDesc> entry in mapNode.GetEntries(factory))
map.Entries.Add((entry.Key, GetName(entry.Value)));

string name = GetName(mapNode.TypeMapGroup);
_groups.TryGetValue(name, out var maps);
maps.External = map;
_groups[name] = maps;
}
}

if (node is ProxyTypeMapObjectNode proxyMaps)
{
foreach (IProxyTypeMapNode mapNode in proxyMaps.GetTypeMaps())
{
var map = new Map();
foreach (KeyValuePair<TypeDesc, TypeDesc> entry in mapNode.GetEntries(factory))
map.Entries.Add((GetName(entry.Key), GetName(entry.Value)));

string name = GetName(mapNode.TypeMapGroup);
_groups.TryGetValue(name, out var maps);
maps.Proxy = map;
_groups[name] = maps;
}
}
}

internal override void End()
{
using FileStream output = new FileStream(fileName, FileMode.Create);
ILLink.Shared.TypeMapXmlWriter.WriteTypeMapsToStream(output, _groups);
}

private string GetName(TypeDesc type) => _typeNameFormatter.FormatName(type, options: true);
}
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,7 @@
<Compile Include="Compiler\ILAssemblyGeneratingMethodDebugInfoProvider.cs" />
<Compile Include="Compiler\StackTraceEmissionPolicy.cs" />
<Compile Include="Compiler\PreinitializationManager.cs" />
<Compile Include="Compiler\TypeMapObjectDumper.cs" />
<Compile Include="Compiler\TypePreinit.cs" />
<Compile Include="Compiler\UsageBasedInteropStubManager.cs" />
<Compile Include="Compiler\UsageBasedMetadataManager.cs" />
Expand Down
5 changes: 4 additions & 1 deletion src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ internal sealed class ILCompilerRootCommand : RootCommand
new("--disable-generated-code-heuristics") { Description = "Disable heuristics for detecting compiler-generated code" };
public Option<string> TypeMapEntryAssembly { get; } =
new("--typemap-entry-assembly") { Description = "Assembly name to use as entry point for TypeMap generation" };
public Option<string> TypeMapFileName { get; } =
new("--output-typemaps") { Description = "Write retained type maps to the specified XML file" };

public OptimizationMode OptimizationMode { get; private set; }
public ParseResult Result;
Expand Down Expand Up @@ -277,6 +279,7 @@ public ILCompilerRootCommand(string[] args) : base(".NET Native IL Compiler")
Options.Add(UnmanagedEntryPointsAssemblies);
Options.Add(DisableGeneratedCodeHeuristics);
Options.Add(TypeMapEntryAssembly);
Options.Add(TypeMapFileName);

this.SetAction(result =>
{
Expand Down Expand Up @@ -312,7 +315,7 @@ public ILCompilerRootCommand(string[] args) : base(".NET Native IL Compiler")
#pragma warning disable CA1861 // Avoid constant arrays as arguments. Only executed once during the execution of the program.
Helpers.MakeReproPackage(makeReproPath, result.GetValue(OutputFilePath), args, result,
inputOptions : new[] { "-r", "--reference", "-m", "--mibc", "--rdxml", "--directpinvokelist", "--descriptor", "--satellite", "--order" },
outputOptions : new[] { "-o", "--out", "--exportsfile", "--dgmllog", "--scandgmllog", "--mstat", "--sourcelink" });
outputOptions : new[] { "-o", "--out", "--exportsfile", "--dgmllog", "--scandgmllog", "--mstat", "--sourcelink", "--output-typemaps" });
#pragma warning restore CA1861 // Avoid constant arrays as arguments
}

Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/tools/aot/ILCompiler/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,10 @@ void RunScanner()
if (sourceLinkFileName != null)
dumpers.Add(new SourceLinkWriter(sourceLinkFileName));

string typeMapFileName = Get(_command.TypeMapFileName);
if (typeMapFileName != null)
dumpers.Add(new TypeMapObjectDumper(typeMapFileName));

// Write to a temporary file and rename on success to avoid leaving partial files on failure
string tempOutputFilePath = outputFilePath + ".tmp";
CompilationResults compilationResults = compilation.Compile(tempOutputFilePath, ObjectDumper.Compose(dumpers));
Expand Down
Loading
Loading