G
YouTube TikTok Games
Skip to content

NativeAOT: add IlcStripNamesAssembly to strip type names from reflection metadata - #137574

Draft
Sergio0694 wants to merge 11 commits into
dotnet:mainfrom
Sergio0694:user/sergiopedri/ilc-trim-type-names
Draft

Sergio0694 wants to merge 11 commits into
dotnet:mainfrom
Sergio0694:user/sergiopedri/ilc-trim-type-names

Conversation

@Sergio0694

@Sergio0694 Sergio0694 commented Nov 15, 2026 •

Copy link
Copy Markdown
Contributor

Adds an opt-in ILC option to strip the names of the types of selected assemblies from the reflection metadata in the final binary.

Set <IlcStripNamesAssembly Include="MyAssembly" /> (passed to ILC as --stripnames:MyAssembly). Nothing changes unless the item is set. Names stay in the map, mstat and symbol files, so symbolicated stack traces keep full fidelity. This is meant for assemblies with large amounts of generated code, such as the CsWinRT 3.0 interop assembly, where ~70% of the metadata is name strings that are never used at runtime.

Size impact

Microsoft Store (CsWinRT 3.0), WinStore.App.dll, retail, OptimizationPreference=Size, with WinRT.Interop opted in:

Toolchain Before After Saved Reflection metadata
release/11.0 108,023,808 B 103,500,288 B 4.5 MB (-4.2%) 10.7 MB -> 6.0 MB
release/11.0 + #137541 84,836,352 B 82,701,312 B 2.1 MB (-2.5%) 5.2 MB -> 3.0 MB

The numbers were measured on a release/11.0 based build of an earlier revision of this change that also stripped member names. In the final Store metadata, the only members emitted for the stripped types are attribute constructors (which keep their names), so the saving comes entirely from type names and is not affected. This branch is rebased on main, where it builds and passes the tests below, but the Store was not re-measured on main.

Semantics for opted-in assemblies

  • Only type names are stripped. The compiler emits metadata for a member only if it is a reflection target, so member names are kept and lookups by member name keep working.
  • Type.Name, Namespace, FullName, AssemblyQualifiedName and ToString() throw NotSupportedException for these types (and for arrays, byrefs and constructed generics that involve them). The message names the assembly and IlcStripNamesAssembly. This fails fast instead of returning empty or made up names that would be hard to root cause.
  • By-name type lookups (Type.GetType, GetNestedType, GetInterface, ...) never match. Type identity, Activator, Invoke, MakeGeneric* and dispatch are unaffected.
  • Stack trace data is not emitted for methods of these types, so frames show as offsets that can be symbolicated.
  • Multi-file compilation and the system module are rejected. Full details and limitations are in src/coreclr/nativeaot/docs/optimizing.md.

Implementation

  • The metadata transform writes empty type names (types go into the root namespace). TypeDefinition and TypeReference writer records get a deterministic Discriminator so records that only differ by name are not pooled together.
  • Runtime lookups that hashed the type name (the type map reverse lookup and the scope type table) use metadata handles for stripped types.
  • NativeFormatRuntimeNamedTypeInfo.Name and Namespace throw. A few internal paths that enumerate types without needing the name (GetInterface, nested type member lookup, delegate diagnostics, crash reporting of an unhandled exception of such a type) were adjusted so they don't throw or lose the crash report. In debug builds, the debug name of such a type is the address of its MethodTable.

Testing

  • New SmokeTests/StrippedNames test: names throw, members keep names, overrides, lookups, identity, GVMs, generics, attributes, stack traces, and a check that type names are absent from the image. It passes on this branch.
  • Earlier runs on this branch: nativeaot smoke tests 28/29 (the failure was an assertion in the new test, since fixed), ILCompiler.TypeSystem.Tests 356/356. These were not re-run in full after the last changes.
  • The Store build was smoke tested in Windows Sandbox (home, search, product pages, navigation, settings) without crashes. That was an earlier revision on release/11.0 where names returned empty strings instead of throwing, so it does not cover the throwing behavior.

Related to #137567.

Note

This PR description and implementation were drafted with GitHub Copilot.

Sergio0694 and others added 8 commits November 14, 2026 17:23
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the names of all generic methods and of methods of attribute types,
reject multi-file compilation, and extend tests and docs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Apply StackTraceHidden before skipping stripped reflection methods, ignore
stripped interfaces in GetInterface, make the method name check defensive,
and clarify the docs limitations. Extend the smoke test.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
GetMemberWithSameMetadataDefinitionAs filtered candidates by name, which
can never match a stripped member. Also don't report diagnostic info for
delegates that target a method with a stripped name.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
11 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 dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Nov 15, 2026
@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.

@Sergio0694
Sergio0694 marked this pull request as draft November 15, 2026 02:45
@Sergio0694

Copy link
Copy Markdown
Contributor Author

@MichalStrehovsky @jkotas the idea for this came from the fact that we were wasting several MBs just for 100% useless type names in WinRT.Interop.dll (our sidecar .dll in CsWinRT 3.0 with just interop stuff), which contains tens of thousands of types. I experimented first updating CsWinRT to try to emit shorter names (e.g. short hashes or something), but that was both suboptimal (still wasted a lot of space), and also made stacktraces completely useless there, since you wouldn't know what types were actually being used, even with symbols.

So this instead is adding an opt-in (that e.g. CsWinRT would do automatically for the user) to strip these names in this .dll (we might even consider moving more interop code to other assemblies we can strip). This way you both save as much size as possible (since we completely skip all names everywhere there), but also perfectly preserve stacktraces, since all original type names still end up in the .pdb, so you still get full fidelity stacktraces (e.g. in Watson).

Like the other issue, open to suggestions for the exact property name to expose, but this is the general idea 😄
Let me know what you think!

@MichalStrehovsky

Copy link
Copy Markdown
Member

useless type names in WinRT.Interop.dll

Are users ever exposed to exceptions thrown from this code? In that case, these names are not useless, it's the stack trace data.

@Sergio0694

Copy link
Copy Markdown
Contributor Author

@MichalStrehovsky right, this is why this would be an opt-in, and we'd automatically set this in CsWinRT only when you already have StackTraceSupport=false. So that means you'd already not have stacktrace information available at runtime anyway, and stripping type names in ILC means the .pdb still has everything, so you still get full stacktraces from there. Basically there's no point keeping over 2MB worth of type names there if you're already stating that you don't want debugging info available at runtime anyway (because you said StackTraceSupport=false in your project). What do you think?

@MichalStrehovsky

Copy link
Copy Markdown
Member

That would make more sense; I saw the changes in System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/StackTraceMetadata.cs‎ which were indicating this was supposed to coexist somehow.

Why are we going after names only? We already have the --reflectiondata:none mode and previously the reflection disabled mode. What is the purpose of stripping name, but keeping type flags/attributes and other stuff?

@Sergio0694

Copy link
Copy Markdown
Contributor Author

This is basically me digging into the MSTAT files for the Store on CsWinRT 3.0 and trying to figure out how we can reduce binary size, and running experiments on everything that seemed to account for a bunch of size that ideally wouldn't be there. What started this PR in particular is the fact that when looking into all the reflection blob in the binary, a ton of it (4.5 MB on plain .NET 11) was literally just type names for types in WinRT.Interop.dll, which are just never used (that .dll is just the one we emit in IL with all the interop goo for WinRT marshalling). The goal is to end up with something that saves size but doesn't actually break functionality, and that we could enable in production (e.g. the reflection-disabled mode is a non-starter given that CsWinRT does rely on a relatively large amount of attribute lookups in various kinds of places, and other AOT-safe minimal uses of reflection, we can't just drop all of that). I'm basically just trying to reduce the amount of "not actually needed cruft" as much as possible 😅

The compiler only emits metadata for members that are reflection targets, so their
names are kept and lookups by name keep working. In the Store, none of the members of
the stripped types were emitted other than attribute constructors, so this doesn't
change the savings, and it removes the need to special case overrides, generic methods,
attribute types and enum literals, and the method name and signature handling in the runtime.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@MichalStrehovsky

Copy link
Copy Markdown
Member

Based on the experience with reflection disabled mode, I don't really want to end up with something that will allow things to limp along long enough until it ends up with users filling bugs in the dotnet/runtime repo that are not easy to root cause immediately as "you did this to yourself, this is not our problem, closed, bye".

So accessing a name or namespace on such injured type needs to fail fast. I don't care it fails fast from object.ToString. i don't care it fails fast from InvalidCastException trying to find type name. It needs to end right there with a clear stack. Not return empty names. Not return fake names. This was a big support cost even when people had to dig this setting out from repo docs. CsWinRT or Windows team enabling this by default would bring back the support costs.

@Sergio0694

Copy link
Copy Markdown
Contributor Author

That's fair, yeah I can do that. The idea is that you really shouldn't possibly be reaching one of these here anyway 🙂

Sergio0694 and others added 2 commits November 14, 2026 23:16
…equested

Returning an empty name lets bugs go unnoticed until they surface somewhere else, so
Type.Name, Namespace, FullName, AssemblyQualifiedName and ToString (and everything that is
composed from them) throw NotSupportedException for these types. The message mentions the
assembly and IlcStripNamesAssembly. Lookups by name and the stack trace exclusion are unchanged.
Crash reporting of an unhandled exception of such a type falls back to the message and the stack trace.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…d name

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@Sergio0694 Sergio0694 changed the title NativeAOT: add IlcStripNamesAssembly to strip type and member names from reflection metadata NativeAOT: add IlcStripNamesAssembly to strip type names from reflection metadata Nov 15, 2026
@Sergio0694

Copy link
Copy Markdown
Contributor Author

I've updated the PR to only strip type names, and made trying to get the name of stripped types just throw. I kept a minimal name generation only for the debugger output, but runtime accesses still throw immediately like @MichalStrehovsky asked.

This branch has not been deployed

No deployments
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.

3 participants