-
Notifications
You must be signed in to change notification settings - Fork 121
Addressing issues with generics on AOT #1377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…rnal enumerators which we can't reference in source generator.
…ll covariant interfaces.
| { | ||
| } | ||
| internal sealed class {{vtableAttribute.ClassName}}WinRTTypeDetails : global::WinRT.IWinRTExposedTypeDetails |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: right now we have this WinRTExposedType, which links to a separate generated type with the details, implementing the interface, which is constructed at runtime using reflection. Could we not do the same as ComWrappers is doing instead, that is, make WinRTExposedType non-sealed, and then have the generator create a derived type directly implementing the interface, and use that to annotate the projection types? This way CsWinRT can simply look that up and cast directly to the interface type, no need to use Activator.CreateInstance and reflection to get an instance to invoke methods on. Basically it'd just generate this:
[FooWinRTExposedType]
partial class Foo
{
}
internal sealed class FooWinRTExposedType : WinRTExposedTypeAttribute
{
public override ComWrappers.ComInterfaceEntry[] GetExposedInterfaces() => ...;
}Then CsWinRT can just do type.GetCustomAttribute<WinRTExposedTypeAttribute>(true) and use that directly. Should also help startup a tiny bit since there's no more Activator.CreateInstance being called. And this also works downlevel just fine, as we don't even need any attribute generics.
Additionally, this means we don't even need the interface, we just override the abstract GetExposedInterfaces() method on the base WinRTExposedType, which is also slightly faster to invoke than an interface dispatch (same for casting to the attribute type rather than to the interface type). And also in general, it's just one less type we need.
What do you think? 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was one of my initial ideas during the refactor. This might still be preferrable to do, will need to do some comparisons. The main reason I switched to what I have was I was concerned about generating much more code for each struct, especially blittable ones, where we only generate the struct today and not any marshaling classes. With the current approach, I was able to use a generic type that I passed to share the implementation for structs. But after this PR is in, I am willing to try out the other approach again and see how much more it really adds to code size and if it is minimal. Also I was thinking this approach may make it easier in the future to move to generic attributes when we can, but with that said, not sure which is better perf.
Sergio0694
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaving a few more notes in case it helps 🙂
Tried building this branch locally but I'm still getting about 490 warnings from WinRT.Runtime, most of them being incorrect or missing trim/AOT annotations (related to #1347). Is this expected, and would this just be fixed in a separate PR?
All this work is really great to see though, love CsWinRT getting some improvements! 🎉
I plan on going through all the remaining warnings and looking at if they are in fallback code paths or are not needed anymore or wrong annotations. I wanted to first get the changes in that would address all known AOT and trimming compat issues. |
| hasWinrtExposedClassAttribute = true; | ||
| entries.AddRange(lookupTableEntries); | ||
| } | ||
| else if (RuntimeFeature.IsDynamicCodeCompiled) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible for this path to be-AOT compatible somehow? This change can cause some IIDs to be missing entirely, causing CreateCCWForObjectForABI to fail. One affected API is StorageFile.GetFileFromPathAsync.
Here's a screenshot of AsyncOperationCompletedHandler<StorageFile> in CoreCLR (left) versus NativeAOT (right) (provided by @hez2010):

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code path is meant to be the fallback. It looks like the first guid is the one that is missing with the other extra ones being duplicates. For this scenario, the WinRT AOT source generator should have generated the necessary vtable entries as part of the lookup table. Do you see any warnings for WinRTAotSourceGenerator failing to run? If not, are you able to check if there is a generated file called WinRTGlobalVtableLookup.g.cs and inside of it, do you see a reference to the mentioned type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those missing IIDs are delegates, such as AsyncOperationCompletedHandler<T>.
It can cause any async operations to fail (not only StorageFile.GetFileFronPathAsync)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a test case to test this scenario and I discovered a different issue, but not this specific one you are running into. I think I know why you are seeing this. Today, you typically add a CsWinRT package reference to generate a projection for a set of WinMD files or for authoring scenarios. With these changes, you also need to add a CsWinRT package reference to make your library or app which consumes CsWinRT projected APIs AOT compatible. This is because we have a new source generator that we are using to generate some code in order to be AOT compatible specifically in the generic type scenarios. In the future we may look for over ways to have the source generator run without needing a CsWinRT package reference such as when using the net windows10 TFM.
Can you confirm whether your app or library where this AsyncOperationCompletedHandler is being added has a CsWinRT package reference. If not, can you add one and see if that helps. I assume you are testing this by manually building my changes, so you might not have a CsWinRT package built. If so, you might be able to directly add a reference to the source generator as we do in our functional tests here. Let me know if that addresses it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for confirming. OpenStreamForReadAsync should be coming from here. I will need to enlighten the source generator to handle this scenario. I will also look into the other scenario you mentioned which it doesn't handle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can generate code for async operations at the invocation instead of the await expression (an async call is not necessarily to be awaited directly):
i.e. adding the below code under if (invocationSymbol is IMethodSymbol methodSymbol && GeneratorHelper.IsWinRTType(methodSymbol.ContainingSymbol)) in GetVtableAttributesToAddOnLookupTable:
if (GeneratorHelper.IsWinRTType(methodSymbol.ReturnType))
{
var completedProperty = methodSymbol.ReturnType.GetMembers("Completed").FirstOrDefault() as IPropertySymbol;
if (completedProperty != null && completedProperty.Type.MetadataName.Contains("Async") && completedProperty.Type.MetadataName.Contains("`"))
{
vtableAttributes.Add(GetVtableAttributeToAdd(completedProperty.Type, GeneratorHelper.IsWinRTType, context.SemanticModel.Compilation.Assembly, false));
}
}This can handle all async invocations and it works fine with the below code:
var file = await StorageFile.GetFileFromPathAsync(...);
using var stream = await file.OpenAsync(FileAccessMode.Read).AsTask().ConfigureAwait(continueOnCapturedContext: false);
using var sw = new StreamReader(stream.AsStreamForRead());
Console.WriteLine(sw.ReadToEnd());There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created a PR for you to resolve the above issue: #1383
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partially unrelated, but I'm very confused by this line in the generated code above:
IID = global::WinRT.GuidGenerator.GetIID(typeof(global::Windows.Foundation.AsyncOperationCompletedHandler<global::Windows.Storage.StorageFile>).GetHelperType())Given this is all generated code, can't the generator precompute that IID into some static field as usual (like all new AOT-friendly projections are also doing in this PR) and just use that here, rather than using GuidGenerator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left this for now as I have plans to try to remove the delegates from the lookup table if possible and move it into the projection itself similar to the rcw instantiations.
| public string DefaultInterface; | ||
| public string StaticInterface; | ||
| public bool IsSynthesizedInterface; | ||
| public bool IsComponentType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
Hit another exception "Write lock may not be acquired with read lock held." |
I did also run into this during my validation, working on a fix. |
…read is doing a read on it.
| public static global::System.Guid IID { get; } = new Guid(new global::System.ReadOnlySpan<byte>(new byte[] { 0x35, 0, 0, 0, 0, 0, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46 })); | ||
| #else | ||
| public static global::System.Guid IID { get; } = new(0x00000035, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46); | ||
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did something similar in InterfaceIIDs. Do we want to remove those?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly, will handle cleanup in separate PR.

This PR addresses a couple of different AOT compat issues related to generics:
WinRTExposedTypeattribute take an array of interfaces but instead take a type which then returns the list of interfaces to put on the vftbl. This allows for the ability to do initialization of generic interfaces such as initializing the code generated version of the vftbl for the specific generic instantiation before returning the interface array.WindowsRuntimeTypeAttributeto take the source generated guid signature for structs so that in the scenarios where we can't precompute the IID, we can still compute the IID without using reflection to iterate through all the struct fields.WinRTExposedTypeattribute for vftbl generation.WinRT.Runtime.CsWinRTAotOptimizerEnabledproperty which is by default on. It is mainly there if after we end discovering some scenarios where our source generator behaves incorrectly. This will allow those folks to unblock themselves by disabling the optimizer especially if they aren't yet concerned about publishing for AOT or trimming.