[Crossgen2] Deduplicate identical IL method bodies in R2R composite image#126047
[Crossgen2] Deduplicate identical IL method bodies in R2R composite image#126047kotlarmilos wants to merge 5 commits intodotnet:mainfrom
Conversation
…bodies in R2R composite images Add a --dedup-il-bodies flag to crossgen2 that enables content-based deduplication of identical IL method bodies when emitting R2R composite component assemblies. When enabled, crossgen2 uses a content-keyed dictionary per component factory. When a method's IL body bytes match an already-seen body, the existing CopiedMethodILNode is reused, causing multiple MethodDef RVA entries to point to the same IL body blob. Enable by default on Apple mobile platforms (ios, tvos, iossimulator, tvossimulator, maccatalyst) via MSBuild targets, matching the pattern used for --strip-inlining-info and --strip-debug-info. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
--dedup-il-bodies to deduplicate identical IL method bodies in R2R composite image
There was a problem hiding this comment.
Pull request overview
This PR adds an opt-in crossgen2 optimization (--dedup-il-bodies) to content-deduplicate identical IL method bodies when producing R2R composite component assemblies, and wires it through the CLI and Apple mobile MSBuild defaults to reduce output size.
Changes:
- Add
--dedup-il-bodiesCLI option and plumb it intoNodeFactoryOptimizationFlags. - Implement IL-body content deduplication in the ReadyToRun node factory using a content-keyed cache.
- Enable the flag by default for Apple mobile RIDs via
Microsoft.NET.CrossGen.targets.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tasks/Crossgen2Tasks/Microsoft.NET.CrossGen.targets | Enables --dedup-il-bodies by default for iOS/tvOS simulators and maccatalyst publish R2R. |
| src/coreclr/tools/aot/crossgen2/Properties/Resources.resx | Adds localized description string for the new CLI option. |
| src/coreclr/tools/aot/crossgen2/Program.cs | Wires the CLI option into NodeFactoryOptimizationFlags.DedupILBodies. |
| src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs | Declares and registers the new --dedup-il-bodies option. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs | Adds the dedup cache and gates dedup behavior behind DedupILBodies. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMethodILNode.cs | Adds helper to read raw method body bytes for content-based dedup keys. |
.../tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
Outdated
Show resolved
Hide resolved
.../tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
Outdated
Show resolved
Hide resolved
.../tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
Outdated
Show resolved
Hide resolved
.../tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
Outdated
Show resolved
Hide resolved
|
Can this be on by default? I do not think it needs to be an option. |
jkoritzinsky
left a comment
There was a problem hiding this comment.
Could we instead use/enhance ObjectDataInterner from ILC (and enable it in crossgen2) to do the deduplicating for us during emit?
Remove the DedupILBodies flag from NodeFactoryOptimizationFlags, the --dedup-il-bodies CLI option, the MSBuild PublishReadyToRunDedupILBodies properties, and the resource string. Deduplication of identical copied IL method bodies is now unconditional. Restructure CopiedMethodIL to use a factory function in the dedup dictionary to avoid creating unused per-method nodes when bodies deduplicate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run runtime-coreclr crossgen2,runtime-coreclr crossgen2-composite |
|
Azure Pipelines successfully started running 2 pipeline(s). |
My understanding of the ObjectDataInterner: it is designed for folding compiled native method bodies where it compares code + relocations + other info and runs in iterations because folding can enabling further folds. For the dedup none of that applies. Enabling it in crossgen2 would require more changes. I think it can be moved, I just want to check if that was the intention. |
.../tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
Outdated
Show resolved
Hide resolved
I think it would be overkill for what we need here. |
Remove the intermediate NodeCache<MethodDesc, CopiedMethodILNode> and use only the content-keyed ConcurrentDictionary for IL body dedup, as suggested in code review. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
.../tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
Outdated
Show resolved
Hide resolved
...tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMethodILNode.cs
Outdated
Show resolved
Hide resolved
.../tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
Show resolved
Hide resolved
|
Looks like this will have an interaction with #125647 that might push this to having to do this later. |
|
Yes, let's first land #125647. |
|
Should this not be done by Roslyn instead? Are there any drawbacks to deduplicating like this? |
This is de-duplicating across multiple assemblies in a composite R2R image. |
ReadBodyBytes now accounts for stripping: when a method would be stripped, it returns s_minimalILBody instead of original IL bytes. This makes the dedup key reflect the effective (post-strip) body, so all stripped methods share a single CopiedMethodILNode. GetData() reuses ReadBodyBytes to eliminate duplicated stripping logic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2af15a7 to
28021fa
Compare
.../tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
Show resolved
Hide resolved
.../tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
Show resolved
Hide resolved
...tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMethodILNode.cs
Show resolved
Hide resolved
--dedup-il-bodies to deduplicate identical IL method bodies in R2R composite image|
/azp run runtime-coreclr crossgen2,runtime-coreclr crossgen2-composite |
|
Azure Pipelines successfully started running 2 pipeline(s). |
Either this PR or #126112 is fine with me. I will let you figure out which one we want to go with. |
| byte[] bodyBytes = CopiedMethodILNode.ReadBodyBytes(method, OptimizationFlags); | ||
| return _copiedMethodIL.GetOrAdd(bodyBytes, _ => new CopiedMethodILNode(method)); |
There was a problem hiding this comment.
This may not work. If first time someone asks for CopiedMethodIL and optimizationFlags.CompiledMethodDefs is still null, we'll get a node for the real method body bytes. This node will become part of the dependency graph and will get written to the output no matter if there's still a reference to it at the writing phase (if the node is marked, it will get written to the output).
If this is not a concern, we should remove the question mark from optimizationFlags.CompiledMethodDefs?.Contains(method) so that this is a hard crash if anyone wants to use this before we do the analysis.
If it is a concern, there are ways to address this. Probably the easiest would be to delete the CopiedMethodILNode completely and instead emit the bodies as part of CopiedMetadataBlobNode. We don't need a separate symbol for each method body - the builder.EmitReloc(factory.CopiedMethodIL(method), RelocType.IMAGE_REL_BASED_ADDR32NB); can be builder.EmitReloc(this, RelocType.IMAGE_REL_BASED_ADDR32NB, someOffsetFromStart); (where someOffsetFromStart is _sourceModule.PEReader.GetMetadata().Length + cumulativeSizeOfMethodBodiesEmittedSoFar).
I feel similar about this. If we have a use case for deduplication beyond the method bodies, it might be worth it to take Jeremy's change. If we don't see where else we would use this, I'd not add more complexity to the ILC deduplication. |
|
Jeremy's change allows for more flexibility going forward. @jkoritzinsky How do you feel? Do you want to proceed with your PR instead? |
|
Closing this PR in favour of #126112 |
Description
This PR adds content-based deduplication of IL method bodies when emitting R2R composite component assemblies.
CopiedMethodILon the node factory uses aConcurrentDictionary<byte[], CopiedMethodILNode>keyed by effective body bytes. When a method's body matches an already-seen body, the existing node is reused, causing multiple MethodDef RVA entries to point to the same IL body blob.Deduplication operates on post-strip bytes:
ReadBodyBytesreturns the stripped stub for strippable methods, so all stripped non-generic methods collapse into a single shared node. This builds on top of the--strip-il-bodiesoption added in #125647.Impact (MAUI HelloWorld iOS arm64)