-
Notifications
You must be signed in to change notification settings - Fork 127
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
IL2091 errors for all APIs using an annotated type as parameter, is this by design? #2938
Comments
Maybe same as #2843 |
ILLink currently validates that all T's are annotated the same way. I agree that a lot of this validation doesn't translate to actual issues. @vitek-karas @sbomer @tlakollo looks like this is what we were discussing on email last week. I've ran into this annotating BenchmarkDotNet too: dotnet/BenchmarkDotNet#2046 |
This is a valid point - and as @MichalStrehovsky pointed out we've already ran into this recently. We're trying to figure out what is the smaller subset where linker will need to do the validation. Unfortunately right now, it requires the annotations to match everywhere. As for the fields question - the annotations follow the reflection's Currently the only annotation which preserves everything across base types is Basically it tries to hint you to not use private reflection 😉 , which is typically a good idea. |
Glad to hear this is being looked into! I'll just leave those annotations everywhere for now then 🙂 @vitek-karas just to clarify, in my scenario I'm only dealing with generic types constrained to |
Sorry - I didn't understand that "recursively" means recursing into the fields (I though recursing into base types). Linker has a rule where types with explicit layout (structs are by default explicit layout) always keep all their fields (so that we don't change the size and field layout for interop reasons). So if your struct only stores other value types, then this would work. But if there's a reference type somewhere, that will break. This will not be warning free - linker doesn't incorporate this rules when making warning decisions (not sure if it would help, but in any case it's low priority), but if you can guarantee that everything you walk over are explicit layout types, then it should work. In a way you would not need any annotation - since just the fact that the type is instantiated (which is always assumed for value types) will preserve all fields on it. I would keep the annotation - more as a documentation really - and probably add a comment that this relies on the types to be explicit layout, or maybe add a check that it's all values types (non-explicit layout value types should be rare). One other comment about the code - don't use pragma suppressions since they only work for analyzer. All pragmas are removed by the compiler and don't make it into IL, so the same warning should popup again from trimmer/NativeAOT. Instead use Currently there's no annotation which would make the trimmer preserve something "recursively into fields/members". We've been thinking about it for a while but there's no solution in sight (it usually leads into "keep almost everything", or "everybody needs slightly different things... how are we supposed to know what"). |
Ah, that's great to know, thank you for the additional info! I have
Ah, I didn't realize that, thank you! I've opened a PR with an hotfix for this in the MVVM Toolkit as well (CommunityToolkit/dotnet#359) and I'll do the same fix in ComputeSharp too now that I'm aware of this, thank you for the additional details here! 😄 I guess all my questions have been resolved, feel free to close this or keep it to track the more narrow annotations 👍 |
Actually you're right that this won't necessarily work for NativeAOT. I'm not even sure if NativeAOT will keep the fields, but it is probably unlikely to keep the metadata, meaning your code would probably not work. |
Wait, can you elaborate? Do you mean it would still not work even with the annotations I've added? |
For NativeAOT it probably would not work even with the annotations - the annotations are NOT recursive, so the first level would work, but technically there's no guarantee that the second level would be able to enumerate the fields (the fields would probably still be there in the data structure, but runtime would have no metadata about them -> not able to enumerate them). @MichalStrehovsky would know for sure. |
Yep, it won't work with NativeAOT. IL Linker needs to keep fields including their metadata because the program cannot access them without metadata. It's not the case for native code that accesses fields by doing Here's a quick test program: using System.Reflection;
using System.Runtime.InteropServices;
unsafe
{
Console.WriteLine($"sizeof S1: {sizeof(S1)}");
Console.WriteLine($"sizeof S2: {sizeof(S2)}");
Console.WriteLine($"sizeof S3: {sizeof(S3)}");
}
DumpFields(typeof(S1));
static void DumpFields(Type t, int pad = 0)
{
if (t.IsPrimitive) return;
Console.Write(new string(' ', pad));
Console.WriteLine(t.FullName);
foreach (var f in t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
Console.Write(new string(' ', pad + 1));
Console.WriteLine(f.Name);
DumpFields(f.FieldType, pad + 2);
}
}
struct S1
{
public S2 Field1;
public S3 Field2;
}
struct S2
{
public int Field;
}
[StructLayout(LayoutKind.Auto)]
struct S3
{
public int Field;
} With dotnet run:
With PublishTrimmed:
With PublishAot:
Notice PublishTrimmed also touched fields on the Auto layout type. NativeAOT doesn't currently do that because the cost is basically just padding. |
Mmh I'm confused, I tried this on my end too but while I do get warnings, the code still runs fine? 🤔 I get:
(I updated your code to add an This was with:
In a blank console app with just the code above, plus Anyway, it would seem NativeAot is preserving all metadata here by default? I assume one shouldn't rely on this though? My question is: if I add the trimming annotations, would this still be technically not safe? I would like to not have to go as far as adding |
Try with one if the rc1 builds. |
I've updated the test program with what I actually ended up using. NativeAOT will very aggressively trim non-reflected things. Fields have zero importance after native compilation. Code that accesses them just uses movs. illink needs to keep them if someone uses them (at least right now - it could also just access offsets if it knew the layout and ensured the layout stays the same with IL definition of the field removed). |
I see. And you can confirm that even with the annotation, only the top level would be preserved? If that was the case, I guess I could live with it but then I would really like to not have to repeat these everywhere. |
Yes, we don't have a way to express "keep fields recursively" (it runs into the "what does it mean to keep fields recursively" question that everyone has different answers for - e.g. serializers would want to recursively keep fields of |
I see. Alright I guess
Just out of curiosity, isn't that what |
That did a bunch of random heuristics. E.g. if a field was an ImmutableDictionary, it ensured the builder is kept. .NET Native relied on heuristics and then crossed its fingers that it's all that's necessary to make code work at runtime. One wouldn't know unless they run the app and thoroughly test. What we do now is we rely on statically analyzable code and generating warnings if it's not analyzable. The warnings tell if it will work. Not everything can be expressed that way. |
.NET Native has lot of code which understands XmlSerializer and DataContractSerializer and how to keep enough for these to work. It's basically a losing proposition trying to teach the tooling about every serializer out there. Also of note is that there are lot of libraries which are not called "serializer" but actually act like one, for example "DB -> OM mappers", some loggers, "config -> OM readers", "XAML -> OM readers", ... |
I see. Yeah I guess the fact .NET Native has so many heuristics can save you in some cases, but life now on NAOT with all the trimming annotations is indeed waaaay better, for sure. Enabling trimming in the Store was really painful and it took a ton of trial and error to try to spot whether anything had silently broken, because there's just no real way to know in advance 🥲 And then even after spotting that it's a whole other lot of work to fix that and then ensure it really fixed the issue. Things are really so much better on this front now, you folks did some incredible job. If only we had trimming annotations on UWP too... 😄 |
Thanks a lot for the kind words - really happy that it helps. I'm going to keep this issue to track the work to reduce the necessary annotations for generic parameters. |
I'm working on adding NativeAOT support to my swap chain Win32 sample for ComputeSharp (see here) and I'm having trouble understanding a lot of linker errors I'm getting. I've fixed them adding the necessary annotations, but I'm still quite confused as to why they're needed in the first place.
You can see the full code in this PR, but to summarize, I have this:
Now, I'm getting a whole bunch of linker errors like this:
But like... Why? I do need the annotation for the type, because I'm doing reflection in the constructor, sure. But if I have a generic method that receives an instance of this method, why does it also need the trimming annotation? The input type would have already be constructed there, so this method will never reach any code paths doing reflection. Is this just because the linker can't know the constructor is the only place where this will happen, and it assumes every API on this type might also reflect on
T
? If so, is there a more narrow annotation I can use so that only callsites constructing the type require the annotation?Right now otherwise I have to add a whole bunch of annotations everywhere that I'm not sure are actually needed 🤔
Side question: if I need to reflect on the fields of a type and all their fields, recursively, what annotation do I need? I'm using
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)]
but then I still get a linker error when recursive on the method on the type of one of the explored fields. Is there some annotation to imply I want to retain all fields of a type, recursively so I could use it here? Thank you! 😄The text was updated successfully, but these errors were encountered: