-
Notifications
You must be signed in to change notification settings - Fork 5k
ILLink : Fix instantiation tracking issue with icall/pinvoke parameters #113437
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
base: main
Are you sure you want to change the base?
ILLink : Fix instantiation tracking issue with icall/pinvoke parameters #113437
Conversation
Tagging subscribers to this area: @dotnet/illink |
Is it possible to hit this with anything but COM marshalling? IMHO the added test cases all result in COM marshalling that we generate warnings on because it's not trim safe and not marking type as instantiated is the least of the problems for COM scenarios. Or is this for some internal Unity interop scenarios? The default constructor case covers marshalling classes with StructLayout != Auto (and that one wouldn't be COM marshalling). |
...s/illink/test/Mono.Linker.Tests.Cases/Interop/InternalCalls/OutTypesAreMarkedInstantiated.cs
Outdated
Show resolved
Hide resolved
Along the lines of dotnet#113434. The existing `ProcessInteropMethod` has a bug where the return type and/or out parameter types will not be marked as instantiated. In this case, the bug happens when the return type or parameter types do not have a `.ctor()`. This is because `ProcessInteropMethod` will call `MarkDefaultConstructor` on the type which will cause the type to be marked instantiated if the `.ctor()` exists.
15890c0
to
7d8d8f0
Compare
For my own understanding - what does the default constructor have to do with non-auto layout marshalling? @jkotas @MichalStrehovsky For consistency, would it make sense to handle parameters of interop/intrinsic methods the same as parameters of any other method with respect to instantiation? That is: don't mark constructors or consider types instantiated just because they are parameters of an interop/intrinsic method (and instead rely on the interop scenario or runtime descriptors to root the required types/ctors)? |
I didn't open this PR because I encountered a problem in any particular. I opened it because during the scenario outlined in #113434 the reason CoreCLR held together was because
Is an icall which led to Which got me thinking, the foundation of the not instantiated optimizations is that the linker can accurately track when a given type is created. For methods that don't have a body, or method's whose managed body may never be the real code that is executed, the linker no longer has awareness as to what is going to happen. That can of course create all sorts of trimming problems, but why would we not attempt to keep things glue together and at least not apply the not instantiated optimizations? I appreciate it's not perfect, for example, maybe the type that is created is a derived type, but even then marking the base type as instantiated is at least closer to correct. I understand this doesn't fully fix all holes. It reduces risk while causing no more marking than a correctly annotated set of code would mark. That said, I can think of 1 exception. The exception I can think of would be when you have an interop method that you know will always return null. For example
And if the platform isn't I guess what I'm getting at is, the current discrepancy between how the linker handles icalls/pinvokes vs intrinsics feels odd. That's not to say that they have to be handled 100% the same, but it feels undesirable that the test that originally caught this
and fail when defined as
Wouldn't it be better if both held together or both resulted in problems without a |
The marshalling rules for built-in interop will instantiate a non-auto layout type when it is returned from the method. For example:
I do not think that the type even needs to have a default constructor in this case. What's our current take on handling built-in interop in the trimmer? Are we replicating built-in interop rules in the trimmer or did we give up on that? |
In any case, the special handling of built-in interop marshaling rules should be unnecessary for assemblies with |
The marshalling logic lives here. Basically whether the class has layout or not decides how it will be marshalled. If it's Auto layout, it's COM. If it's not Auto, it's marshalled field-by-field/as blittable: runtime/src/coreclr/vm/mlinfo.cpp Lines 1558 to 1578 in cc01298
We should be replicating them or generating trim warnings. We generate trim warnings for COM, there might be other problematic scenarios where we still don't generate a warning (AsAny?). There's an issue tracking this somewhere.
Oh, interesting, I was under the impression we call parameterless constructor. In that case this change looks good to me. It actually fixes a p/invoke bug (and we might be keeping the parameterless constructor unnecessarily for this scenario, but who knows what other interop scenario the parameterless ctor might be covering - illink doesn't really bother trying to distinguish these). We might want to update the test to use a class with layout just in case we want to tighten the classification in the future and then we'll be scratching our head why we are testing COM.
I think the problem with icalls is that they can make up different types (in this case it will actually be System.RuntimeType in any of our runtimes) and ILLink cannot predict all that - the runtime needs to declare what the icall will do using DynamicDependency/ILLink.xml. Pinvokes have better defined rules and there's no possible way a more derived type could come through (at least I hope, not an interop expert). |
FWIW, it is done here: runtime/src/coreclr/vm/ilmarshalers.cpp Line 2230 in cc01298
|
+1 Other changes to consider based on the discussion:
|
I'm going to leave this PR open for a bit. My plan is to
Question for @MichalStrehovsky when I get back to this PR. Do you want me to keep the |
If we wanted to go that route, we'd need to audit the marshalling rules and replicate some of them in illink. When I wrote "who knows what other interop scenario the parameterless ctor might be covering - illink doesn't really bother trying to distinguish these", I actually had a scenario in mind already - anything that derives from I'd be weary to touch this. We now set DisableRuntimeMarshallingAttribute pretty much everywhere in the repo so breaking runtime marshalling is unlikely to be found in our testing. Only third party code would run into it. |
Along the lines of #113434. The existing
ProcessInteropMethod
has a bug where the return type and/or out parameter types will not be marked as instantiated. In this case, the bug happens when the return type or parameter types do not have a.ctor()
. This is becauseProcessInteropMethod
will callMarkDefaultConstructor
on the type which will cause the type to be marked instantiated if the.ctor()
exists.