-
Notifications
You must be signed in to change notification settings - Fork 64
[jnimarshalmethod-gen] Avoid creating duplicate marshal methods #385
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
[jnimarshalmethod-gen] Avoid creating duplicate marshal methods #385
Conversation
Fixes dotnet#384 Sort the methods before generating marshal methods. Sort them so that methods with name containing the interface name are at the end. During processing check whether we already have a marshal method with the same name. (note that the name contains parameter types)
|
I am also cooking a test for that. It would take a bit of effort though as I will need to use cecil to check the generated marshaling class. I would like to add the test in a separate PR to not block the XA's dotnet/android#2153 progress. |
tools/jnimarshalmethod-gen/App.cs
Outdated
| { | ||
| public int Compare (MethodInfo a, MethodInfo b) | ||
| { | ||
| bool aContainsDot = a.Name.IndexOf ('.') != -1; |
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 is not an adequate way to determine if a MethodInfo is for an explicit interface implementation, as mentioned elsewhere.
For example -- and you made me dig up F# just to do this?! ;-) -- consider this F# declaration which implements the System.IServiceProvider interface:
open System;
type MyServiceProvider() =
interface IServiceProvider with
member this.GetService(serviceType : Type): System.Object = nullCompile, then disassemble, and the method we see is:
// method line 2
.method private virtual hidebysig newslot
instance default object 'System-IServiceProvider-GetService' (class [mscorlib]System.Type serviceType) cil managed
{
// Method begins at RVA 0x205c
.override class [mscorlib]System.IServiceProvider::GetService
// Code size 2 (0x2)
.maxstack 8
IL_0000: ldnull
IL_0001: ret
} // end of method MyServiceProvider::System-IServiceProvider-GetService(Look mah, no .s! It instead uses dashes!)
Which means simply checking for . won't work with F# code.
Instead, we need to use Type.GetInterfaceMap(Type).
csharp example of use:
var t = typeof(List<int>);
var c = t.GetProperty("Count");
// `cm` implements ICollection<int>.Count. How do we actually tell?
var cm = c.GetGetMethod ();
System.Reflection.InterfaceMapping map = cm.DeclaringType.GetInterfaceMap(typeof(ICollection<int>));
map.TargetMethods.Contains(cm); // true!Alas, there doesn't appear to be anything as easy as Cecil's MethodDefinition.Overrides, which you used last time.
Also, calling Type.GetInterfaceMap() separately for every method will be terrible, perf-wise, so some form of machine would be needed.
That aside, simply checking for . is insufficient. It will break.
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.
As an alternative to using Type.GetInterfaceMapping(), I wonder if it would be equally effective to just check the return type, and prefer non-interface return types over interface return types?
| bool aContainsDot = a.Name.IndexOf ('.') != -1; | |
| int e = string.Compare (a.Name, b.Name); | |
| if (e == 0 && a.ReturnType.IsInterface != b.ReturnType.IsInterface) { | |
| if (!a.ReturnType.IsInterface) | |
| return -1; | |
| if (!b.ReturnType.IsInterface) | |
| return 1; | |
| throw new InvalidOperationException ("What condition did I miss?"); | |
| } | |
| return e; | |
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 is not an adequate way to determine if a MethodInfo is for an explicit interface implementation, as mentioned elsewhere.
I didn't like the string comparison either, but because I thought the method was not overriden in that class, I didn't find better solution and stopped thinking about it at that point. My bad. The class has the methods with override as I found later (it was partial class split into 2 source files).
Anyway. Thinking more about it, I guess the whole solution is actually wrong and we should have both marshal methods. Because we will be missing the marshal method, when someone calls the iface method from Java side.
I think we should just name the marshal methods differently and include the return type in the method name. So in this case we would have:
__<$>_jni_marshal_methods.n_append_Ljava_lang_CharSequence__Ljava_lang_Appendable
__<$>_jni_marshal_methods.n_append_Ljava_lang_CharSequence__Ljava_lang_CharBuffer
Note double '_' before the return type name. It would actually be enough to have it for the iface method I guess:
__<$>_jni_marshal_methods.n_append_Ljava_lang_CharSequence__Ljava_lang_Appendable
__<$>_jni_marshal_methods.n_append_Ljava_lang_CharSequence
Then I hope it should be reachable from Java side as the registration signature contains the return type. Does it look OK to you? If so then I should probably try it to confirm it works.
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.
Thinking even more about it, we might not need to change it here at all as the registration class doesn't go thru C# and on IL level it should be OK. We should probably just check signatures from the register attribute in the linker, when updating the registration class.
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 have tried to update linker to update the registration methods with duplicates. There were issues with cecil usage, so resolved back to not generate the "duplicate" methods in the generator.
When sorting methods so that interface implementation methods are
last, use `MethodDefinition:HasOverrides` to identify interface
implementations.
Introduce new cache to map reflection methods to cecil method
definitions. Example cache hit ratio:
~ 92% (1210/1318)
when generating marshal methods for
`Mono.Android.dll:Java.Nio.CharBuffer*` types.
| public static string GetCecilName (this Type type) | ||
| { | ||
| return type.FullName.Replace ('+', '/'); | ||
| return type.FullName?.Replace ('+', '/'); |
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.
My guess is this can now return null? If so do we know where this method is called? Do those calling points need to be updated to handle a null?
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.
The updated path with null goes only thru CompareTypes where it is used only for comparison.
Fixes #384
Sort the methods before generating marshal methods. Sort them so that
methods with name containing the interface name are at the end.
During processing check whether we already have a marshal method with
the same name. (note that the name contains parameter types)
When sorting methods so that interface implementation methods are
last, use
MethodDefinition:HasOverridesto identify interfaceimplementations.
Introduce new cache to map reflection methods to cecil method
definitions. Example cache hit ratio:
when generating marshal methods for
Mono.Android.dll:Java.Nio.CharBuffer*types.