Repository navigation
NativeAOT: add IlcStripNamesAssembly to strip type names from reflection metadata - #137574
Sergio0694 wants to merge 11 commits into
Conversation
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: 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. |
|
Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib |
|
@MichalStrehovsky @jkotas the idea for this came from the fact that we were wasting several MBs just for 100% useless type names in 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 😄 |
Are users ever exposed to exceptions thrown from this code? In that case, these names are not useless, it's the stack trace data. |
|
@MichalStrehovsky right, this is why this would be an opt-in, and we'd automatically set this in CsWinRT only when you already have |
|
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 |
|
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>
|
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. |
|
That's fair, yeah I can do that. The idea is that you really shouldn't possibly be reaching one of these here anyway 🙂 |
…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>
|
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. |
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, withWinRT.Interopopted in: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
Type.Name,Namespace,FullName,AssemblyQualifiedNameandToString()throwNotSupportedExceptionfor these types (and for arrays, byrefs and constructed generics that involve them). The message names the assembly andIlcStripNamesAssembly. This fails fast instead of returning empty or made up names that would be hard to root cause.Type.GetType,GetNestedType,GetInterface, ...) never match. Type identity,Activator,Invoke,MakeGeneric*and dispatch are unaffected.src/coreclr/nativeaot/docs/optimizing.md.Implementation
TypeDefinitionandTypeReferencewriter records get a deterministicDiscriminatorso records that only differ by name are not pooled together.NativeFormatRuntimeNamedTypeInfo.NameandNamespacethrow. 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 itsMethodTable.Testing
SmokeTests/StrippedNamestest: 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.ILCompiler.TypeSystem.Tests356/356. These were not re-run in full after the last changes.Related to #137567.
Note
This PR description and implementation were drafted with GitHub Copilot.