Skip to content

Precalculate pinterface guids for known generic instantiations#1588

Open
DefaultRyan wants to merge 17 commits into
masterfrom
user/defaultryan/pinterface_precalc
Open

Precalculate pinterface guids for known generic instantiations#1588
DefaultRyan wants to merge 17 commits into
masterfrom
user/defaultryan/pinterface_precalc

Conversation

@DefaultRyan
Copy link
Copy Markdown
Member

@DefaultRyan DefaultRyan commented May 27, 2026

C++/WinRT provides very powerful constexpr magic to allow users to calculate names and guids of parameterized types without requiring them to declare these instantiations in MIDL.

The catch is that the guid work is expensive because it involves, among other things, a SHA-1 hash computation. This happens at compile-time, so there is no runtime impact, but it does impact build times. The impact is particularly noticeable when many such pinterface specializations are needed, leading to build-time workarounds like https://github.com/microsoft/microsoft-ui-xaml/pull/209/changes

With this change, we resursively walk types in the namespace, looking for generic type instantiations in implmented/base interfaces, as well as method params and return types. We precalculate the guid of these generic types from inside cppwinrt.exe and emit them in the impl/.0.h namespace headers.

This can't possibly capture every possible generic instantiation, but at least we can streamline the types we find in the winmd. I also stamp out IReference pinterface specializations for the "standard" types.

For example, Windows.Foundation.Collections.IPropertySet will now trigger explicit specializations of winrt::impl::pinterface_guid for the following WFC types:

  • IMap<hstring, IInspectable> (interface impl of IPropertySet)
  • IObservableMap<hstring, IInspectable> (interface impl of IPropertySet)
  • IIterable<IKeyValuePair<hstring, IInspectable>> (interface impl of IPropertySet)
  • IKeyValuePair<hstring, IInspectable> (part of the above IIterable instantiation, but also referenced in other methods)
  • IMapView<hstring, IInspectable> (from IMap::GetView())
  • IIterator<IKeyValuePair<hstring, IInspectable>> (from IIterable::First())
  • MapChangedEventHandler<hstring, IInspectable> (from IObservableMap::MapChanged)
  • IMapChangedEventArgs<hstring> (from MapChangedEventHandler)

These type combinations may be encountered in multiple different namespaces, and unlike vanilla inline variables and functions, C++ compilers don't like it when you provide multiple explicit template specializations of the same type, even when those specializations are identical, so I'm guarding them in preprocessor guards, like so:

#ifndef WINRT_IMPL_PINTERFACE_GUID_winrt__Windows__Foundation__Collections__IKeyValuePair_hstring__hstring_
#define WINRT_IMPL_PINTERFACE_GUID_winrt__Windows__Foundation__Collections__IKeyValuePair_hstring__hstring_
    template <> struct pinterface_guid<winrt::Windows::Foundation::Collections::IKeyValuePair<hstring, hstring>>
    {
        static constexpr bool precomputed = true;
        static constexpr guid value{ 0x60310303,0x49C5,0x52E6,{ 0xAB,0xC6,0xA9,0xB3,0x6E,0xCC,0xC7,0x16 } }; // 60310303-49C5-52E6-ABC6-A9B36ECCC716
    };
#endif // WINRT_IMPL_PINTERFACE_GUID_winrt__Windows__Foundation__Collections__IKeyValuePair_hstring__hstring_

Benchmark results

I created a benchmark file that gets guid_of<T>() on 60 or so generics that get precomputed pinterface guids (IRerefence<T> of built-in types, aforementioned pinterfaces from IPropertyValue, etc), and built it 10 times with these changes, as well as with the current master baseline. I also timed how long cppwinrt.exe took, to measure the runtime impact of precalculating these guids.

Metric BEFORE (master) AFTER (this PR) Delta
cppwinrt.exe warm avg 0.796s 0.882s +0.086s (+11%)
cl.exe warm avg 5.166s 3.806s -1.360s (-26%)
total warm avg 5.962s 4.688s -1.274s (-21%)

I did some profiling, and somewhat surprisingly, the actual guid calculation is barely a blip in the increased cppwinrt.exe. The additional time is dominated by the additional metadata walk, specifically, getting signature blobs to obtain generic type instantiations, and params/returns for method signatures, because those tend to return a temporary std::vector of stuff instead of the lighter-weight table traversals. There's some opportunity to optimize the winmd reader here, along with making type resolution (aka find_required) faster than the current std::map approach.

@DefaultRyan DefaultRyan marked this pull request as ready for review May 28, 2026 20:49
@DefaultRyan DefaultRyan requested a review from Copilot May 28, 2026 20:49
@DefaultRyan DefaultRyan requested review from Scottj1s, dunhor and jonwis May 28, 2026 20:50
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR precomputes parameterized interface GUIDs to reduce expensive compile-time SHA-1 work in generated C++/WinRT projections and module builds.

Changes:

  • Adds shared WinMD signature/GUID computation utilities and reuses them in natvis.
  • Adds metadata walking and code emission for generated pinterface_guid<T> specializations.
  • Adds hand-written standard IReference<T> / IReferenceArray<T> specializations and tests.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
cppwinrt/winmd_signature.h Adds shared signature building, GUID extraction, and SHA-1 GUID computation helpers.
cppwinrt/helpers.h Adds generic-instantiation discovery across interfaces, bases, and method signatures.
cppwinrt/code_writers.h Emits guarded pinterface_guid<T> specializations for discovered instantiations.
cppwinrt/file_writers.h Invokes generic specialization emission from namespace .0.h generation.
strings/base_identity.h Adds a precomputed marker to the primary pinterface_guid template.
strings/base_reference_produce.h Adds standard precomputed IReference / IReferenceArray GUID specializations.
natvis/type_resolver.cpp Refactors natvis GUID generation to use shared WinMD signature helpers.
test/test/pinterface_guid_precomputed.cpp Adds non-module coverage for precomputed GUID values and flags.
test/test/test.vcxproj Includes the new non-module test source.
test/test_cpp20_module/pinterface_guid_precomputed.cpp Adds module-boundary coverage for precomputed GUID values and flags.
test/test_cpp20_module/test_cpp20_module.vcxproj Includes the new C++20 module test source.

Comment thread strings/base_reference_produce.h Outdated
Comment thread strings/base_reference_produce.h Outdated
Comment thread cppwinrt/helpers.h
// The pinterface signature format is: pinterface({open-type-guid};{arg-signature})
// These signatures are part of the WinRT ABI specification and are immutable.
static void add_well_known_ireference_instantiations(
std::map<std::string, generic_inst_info>& instantiations)
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.

Aren't all these in the winmds as interface attributes already? midlrt should be precomputing even the "no specified [uuid()]" interfaces?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You're talking about the 2 guids under iref_guid and iref_arr_guid? Yes, I suppose they are. Since I was trying to unconditionally insert these instantiations even though they aren't mentioned, I guess I got a little greedy and hardcoded these interfaces so I wouldn't have to synthesize a GenericTypeInstSig and look up the various bits. But, yes, it's probably better to use the actual attributes from IReference in the winmd, and synthesize the GenericTypeInstSig so it shared the rest of the computation code.

But if you're talking about the pinterface guids for the various specializations, those aren't in the winmd. Yes, midlrt precomputes the guids for certain specializations and emits them in the header files, but that data does not go into the winmd.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@jonwis The IReference logic is now "less special". I'm using the same GenericTypeIntSig path as the other specializations we discover in the winmds.

}

// ---- SHA1 computation ----
struct sha1_context
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.

Is there a platform-standard crypto engine we should be using here instead of handrolling?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'll check it out. We're already handrolling in the constexpr compile-time code, so we've already broken the seal, but it's probably better to not have two separate implementations.

I know we have Windows.Security.Cryptography.Core.HashAlgorithmProvider. but I assume that won't work on non-Windows platforms, and I don't know if that API is available on all the Windows editions we need to run on. If this were app code, I'd be all over that, but I'm not too keen on converting stuff back and forth from a dynamically allocated IBuffer in this perf-sensitive code.

There is BCryptHashData, but has the same concerns around availability and cross-plat.

Outside of that, there aren't any truly standard implementations, but a fewseem to enjoy a degree of widespread use and seem to be somewhat trusted: OpenSSL, Crypto++, and Botan, with a heavy preference toward OpenSSL for the "official-ness". We aren't doing anything actually sensitive, just the SHA-1 hash (which will already raise some security warnings we'll need to deal with), but better safe than sorry if we're going to take on a third-party dependency.

Neither appear to have an official Nuget package (there are some that claim to have those packages, but with questionable provenance), so those are out. There are vcpkg bundles of each, though, so that could be an option, but cppwinrt has never had vcpkg support, so that's a whole other lift to add it to the build, CI, and official pipelines.

Sounds like something for "future us" to figure out...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think adding vcpkg hooks to the build is out of scope at the moment, but I want to take an IOU on that work, and use it to find an off-the-shelf SHA-1 implementation.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Comment thread test/test/pinterface_guid_precomputed.cpp Outdated
Comment thread test/test_cpp20_module/pinterface_guid_precomputed.cpp Outdated
DefaultRyan and others added 2 commits May 28, 2026 18:56
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@DefaultRyan DefaultRyan changed the title Precalculate pinterface names/guids for known generic instantiations Precalculate pinterface guids for known generic instantiations May 29, 2026
Copilot AI changed the title Precalculate pinterface guids for known generic instantiations Restore constructor-based TypeSig/GenericTypeInstSig path for well-known IReference instantiations May 30, 2026
@DefaultRyan
Copy link
Copy Markdown
Member Author

OMG, when I had the GitHub agent try to fix a build break, it completely nuked the PR title and description. Going to find the old one and change it back.

@sylveon
Copy link
Copy Markdown
Contributor

sylveon commented May 30, 2026

You can grab the previous revision from here:
image

@DefaultRyan DefaultRyan changed the title Restore constructor-based TypeSig/GenericTypeInstSig path for well-known IReference instantiations Precalculate pinterface guids for known generic instantiations May 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants