Skip to content

[NativeAOT] Hash relocations in method body folding keys - #135943

Merged
agocke merged 1 commit into
dotnet:mainfrom
andrew-kulikov:ilc-fold-hash-relocs
Oct 18, 2026
Merged

agocke merged 1 commit into
dotnet:mainfrom
andrew-kulikov:ilc-fold-hash-relocs

Conversation

@andrew-kulikov

@andrew-kulikov andrew-kulikov commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #135942

Problem

  • MethodInternKey hashes code bytes, GC info, frame infos and EH bytes, but not relocations, so bodies that differ only in their relocation targets always share a hash code.
  • With optimized code this is very common: each async method has its own struct state machine, so every async method gets its own byte-identical copy of AsyncStateMachineBox<TResult, TSM>.MoveNext, AsyncMethodBuilderCore.Start<TSM>, etc.
  • All of those land in one HashSet bucket, and each insertion runs AreSame against every earlier copy, so deduplication is O(n²) per method family, per pass.

Change

Relocations are mixed into the hash, consistently with MethodInternComparer.AreSame:

  • Reloc type and offset: compared exactly by AreSame.
  • MethodReadOnlyDataNode targets: their data bytes. AreSame compares them by content, and the hash covers a subset of what is compared.
  • Other targets: the identity of the target after mapping it through the previous pass's remapping.
    • AreSame treats t1 and t2 as equal when remapping[t1] == t2 (or vice versa).
    • Remapping values are never keys: a body only becomes a key when it matched a body already in the set, and bodies in the set are never keys. So both sides hash id(t2).

Why the output does not change:

  • The set of pairs Equals accepts is unchanged; only the bucket distribution changes.
  • The iteration order is still the sorted GetCompiledMethodBodies().
  • ISymbolNodes already hash by identity.

Results

Measured with the same change on v10.0.13 (osx-arm64 host, M1 Pro):

before after
iOS app, ~12,000 async methods, ILC wall time ~1200 s 247-252 s
same, --methodbodyfolding:none 252 s
sample with 8000 async Task<int> methods, dotnet publish 271 s 16 s
sample, 4000 methods 47 s 9 s
  • The object files are byte-identical to the unpatched compiler in all cases: the iOS app's 1.19 GB .o (two runs) and the sample's .o.
  • __managedcode keeps the folding savings (69.3 MB vs 80.0 MB without folding).

Repro: https://github.com/andrew-kulikov/runtime-samples/tree/master/nativeaot-async-folding

MethodInternKey only hashed code, GC info, frame infos and EH bytes, so
bodies that only differ in relocation targets (e.g. AsyncStateMachineBox
MoveNext instantiated over thousands of struct state machines) all got
the same hash, making method body deduplication quadratic. Hash
relocation type/offset and the target consistently with
MethodInternComparer.AreSame.
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Oct 16, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/crossgen-contrib
See info in area-owners.md if you want to be subscribed.

@andrew-kulikov

Copy link
Copy Markdown
Contributor Author

@andrew-kulikov please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@dotnet-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@dotnet-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@dotnet-policy-service agree company="Microsoft"

Contributor License Agreement

@dotnet-policy-service agree

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib
See info in area-owners.md if you want to be subscribed.

@agocke agocke left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

I also noticed that in large apps we repeatedly run passes even though most of the benefit is in the first pass, but after your change this doesn't seem to matter too much.

@agocke

agocke commented Oct 18, 2026

Copy link
Copy Markdown
Member

/ba-g all failures known

@agocke
agocke merged commit 4211829 into dotnet:main Oct 18, 2026
120 of 122 checks passed
@agocke

agocke commented Oct 18, 2026

Copy link
Copy Markdown
Member

/backport to release/11.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/11.0 (link to workflow run)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-NativeAOT-coreclr community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[NativeAOT] Method body folding takes quadratic time with many async methods (struct state machines)

3 participants