Optimize generated activation factories for authored components#2471
Draft
Sergio0694 wants to merge 5 commits into
Draft
Optimize generated activation factories for authored components#2471Sergio0694 wants to merge 5 commits into
Sergio0694 wants to merge 5 commits into
Conversation
… writer Expose the authored component's compiled implementation assembly path(s) to the projection writer via ProjectionWriterOptions.ComponentImplementationAssemblyPaths (carried through Settings.ComponentImplementationAssemblies). The component .winmd does not carry implementation details such as the static fields backing XAML dependency properties, so the writer will consult these managed assemblies to make per-type activation-factory decisions. The projection generator host collects, in component mode, the paths of reference assemblies marked [WindowsRuntimeComponentAssembly] and forwards them through. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The generated '<Type>ServerActivationFactory' classes always emitted a static constructor calling RuntimeHelpers.RunClassConstructor(typeof(Type).TypeHandle). That is only required when the authored type registers XAML dependency properties (declared as static DependencyProperty fields), so that they are registered before the type is first activated. For every other authored type it is pure overhead. The factory now emits the static constructor only when the authored type, or an authored base type, declares a static field of type DependencyProperty (Windows.UI.Xaml.DependencyProperty for UWP XAML, otherwise Microsoft.UI.Xaml.DependencyProperty). This is determined by scanning the managed component implementation assemblies (the .winmd does not carry these fields) via the new ComponentImplementationMetadata helper; types not found there conservatively keep the constructor. The conditional emission goes through a WriteStaticConstructor callback so the factory body remains a single interpolated template either way. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The version-agnostic SignatureComparer used as the IEqualityComparer for AsmResolver entity collections lived only in the interop generator. The projection writer now needs it too (for its component activatable-type sets), so promote it to the shared WinRT.Generator.Core library as a single SignatureComparerExtensions.IgnoreVersion and have both tools consume it: - Add SignatureComparerExtensions (IgnoreVersion) to WinRT.Generator.Core and make Core's internals visible to WinRT.Projection.Writer. - Reference Core from the projection writer and drop the writer's local copy. - Remove the interop generator's duplicate IgnoreVersion (keeping its tuple comparers) and resolve it from Core instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ComponentImplementationMetadata.Load previously computed, for every type in the component's managed assemblies, whether it declared a dependency property. That work is only needed for the handful of activatable types actually projected, so make it lazy: - ComponentImplementationMetadata is now a thin resolver that just indexes the managed assembly types by full name and resolves them on demand. - A new ComponentStaticConstructorAnalyzer computes whether a type (or an authored base type) registers a dependency property, resolving and walking base types only when queried and memoizing the result per visited type so shared hierarchies are traversed once. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WriteFactoryClass took an 'emitStaticConstructor' bool that the caller pre-computed into a separate set during discovery. Since the method already has the type and the emit context, let the context carry the component's loaded implementation assemblies and have the factory query them directly: - ProjectionEmitContext now exposes the shared ComponentStaticConstructorAnalyzer, created once on the ProjectionGenerator and reused by every emit context (so its memoization is shared). The analyzer's cache is now a ConcurrentDictionary since it is queried from the parallel emission work items (AsmResolver reads are thread-safe). - WriteFactoryClass drops the bool parameter and computes the requirement from the context. - DiscoverComponentActivatableTypes no longer builds the requiring-static-constructor set, and ProjectionGeneratorRunState no longer carries it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Optimize the
*ServerActivationFactoryclasses generated for authored Windows Runtime components: the static constructor that forces the authored type's class constructor to run before activation is now emitted only when the type actually needs it (i.e. when it, or an authored base type, registers XAML dependency properties). For all other authored types it is pure overhead and is now omitted.Motivation
Every generated activation factory used to emit a static constructor calling
RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle). That is only required when the authored type registers XAML dependency properties (declared asstatic DependencyPropertyfields), so they are registered before the type is first activated. For every other type the static constructor is unnecessary work at activation time, so making it pay-for-play removes that overhead.Whether a type registers dependency properties cannot be answered from the
.winmdmetadata (it does not carry static fields), so the change also threads the component's compiled implementation assemblies into the projection writer and adds the infrastructure to answer that question on demand while emitting each factory. Along the way it consolidates a small shared helper (SignatureComparer.IgnoreVersion) into the shared generator core so both the projection writer and the interop generator use a single copy.Changes
The PR is organised into focused commits:
ProjectionWriterOptions.ComponentImplementationAssemblyPaths(carried throughSettings) exposes the authored component's compiled.dll(s) to the writer, and the projection generator host collects, in component mode, the reference assemblies marked[WindowsRuntimeComponentAssembly].ComponentFactory.WriteFactoryClassemits the static constructor only when required, via a callback so the factory body stays a single interpolated template; addsDependencyPropertyand theMicrosoft.UI.Xaml/Windows.UI.Xamlnamespaces to the well-known name references.SignatureComparer.IgnoreVersionto the shared generator core — promotes the version-agnostic comparer toWinRT.Generator.Coreand migrates both the projection writer and the interop generator to consume the single shared copy, removing the interop generator's duplicate.ComponentImplementationMetadatais a thin resolver that indexes the managed assemblies' types by name, and a newComponentStaticConstructorAnalyzercomputes the requirement on demand, walking base types lazily and memoizing per visited type so shared hierarchies are traversed once.ProjectionEmitContextcarries the shared analyzer, soWriteFactoryClassderives the answer from the type and context instead of taking a pre-computed flag; the analyzer's cache is concurrent since it is queried from the parallel emission work items.Backward compatible: when the implementation assemblies are not available (e.g. reference-projection generation), the analysis conservatively keeps the static constructor, matching the previous always-emit behavior.