Skip to content
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

[generator] generator --lang-features=emit-legacy-interface-invokers #1145

Merged
merged 16 commits into from
Oct 26, 2023

Conversation

jonpryor
Copy link
Member

Fixes: #910

Context: bc5bcf4
Context: #858

Consider the Java java.lang.Runnable interface:

package java.lang;
public interface Runnable {
    void run ();
}

This is bound as:

package Java.Lang;
public interface IRunnable : IJavaPeerable {
    void Run ();
}

with some slight differences depending on whether we're dealing with .NET Android (generator --codegen-target=xajavainterop1) or src/Java.Base (generator --codegen-target=javainterop1).

Now, assume a Java API + corresponding binding which returns a Runnable instance:

package example;
public class Whatever {
    public static Runnable createRunnable();
}

You can invoke IRunnable.Run() on the return value:

IRunnable r = Whatever.CreateRunnable();
r.Run();

but how does that work?

This works via an "interface Invoker", which is a class emitted by generator which implements the interface and invokes the interface methods through JNI:

internal partial class IRunnableInvoker : Java.Lang.Object, IRunnable {
    public void Run() => …
}

Once Upon A Time™, the interface invoker implementation mirrored that of classes: a static IntPtr field held the jmethodID value, which would be looked up on first-use and cached for subsequent invocations:

partial class IRunnableInvoker {
    static IntPtr id_run;
    public unsafe void Run() {
        if (id_run == IntPtr.Zero)
            id_run = JNIEnv.GetMethodID (class_ref, "run", "()V");
        JNIEnv.CallVoidMethod (Handle, id_run, …);
    }
}

This approach works until you have interface inheritance and methods which come from different interfaces:

package android.view;
public /* partial */ interface ViewManager {
    void addView(View view, ViewGroup.LayoutParams params);
}
public /* partial */ interface WindowManager extends ViewManager {
    void removeViewImmediate(View view);
}

This would be bound as:

namespace Android.Views;
public partial interface IViewManager : IJavaPeerable {
    void AddView (View view, ViewGroup.LayoutParams @params);
}
public partial IWindowManager : IViewManager {
    void RemoveViewImmediate (View view);
}
internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
    static IntPtr id_addView;
    public void AddView(View view, ViewGroup.LayoutParams @params)
    {
        if (id_addView == IntPtr.Zero)
            id_run = JNIEnv.GetMethodID (class_ref, "addView", "…");
        JNIEnv.CallVoidMethod (Handle, id_addView, …);
    }
}

Unfortunately, invoking IViewManager.AddView() through an IWindowManagerInvoker would crash!

D/dalvikvm( 6645): GetMethodID: method not found: Landroid/view/WindowManager;.addView:(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V
I/MonoDroid( 6645): UNHANDLED EXCEPTION: Java.Lang.NoSuchMethodError: Exception of type 'Java.Lang.NoSuchMethodError' was thrown.
I/MonoDroid( 6645): at Android.Runtime.JNIEnv.GetMethodID (intptr,string,string)
I/MonoDroid( 6645): at Android.Views.IWindowManagerInvoker.AddView (Android.Views.View,Android.Views.ViewGroup/LayoutParams)
I/MonoDroid( 6645): at Mono.Samples.Hello.HelloActivity.OnCreate (Android.OS.Bundle)
I/MonoDroid( 6645): at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr)
I/MonoDroid( 6645): at (wrapper dynamic-method) object.ecadbe0b-9124-445e-a498-f351075f6c89 (intptr,intptr,intptr)

Interfaces are not classes, and this is one of the places that this is most apparent. Because of this crash, we had to use instance jmethodID caches:

internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
    IntPtr id_addView;
    public void AddView(View view, ViewGroup.LayoutParams @params)
    {
        if (id_addView == IntPtr.Zero)
            id_run = JNIEnv.GetMethodID (class_ref, "addView", "…");
        JNIEnv.CallVoidMethod (Handle, id_addView, …);
    }
}

Pro: no more crash!

Con: every different instance of IWindowManagerInvoker needs to separately lookup whatever methods are required. There is some caching, so repeated calls to AddView() on the same instance will hit the cache, but if you obtain a different IWindowManager instance, jmethodID values will need to be looked up again.

This was "fine", until #858 enters the picture: interface invokers were full of Android-isms --
Android.Runtime.JNIEnv.GetMethodID()! -- and thus not APIs that @jonpryor wished to expose within desktop Java.Base bindings.

Enter generator --lang-features=emit-legacy-interface-invokers: when not specified, interface invokers will now use JniPeerMembers for method lookup and invocation, allowing jmethodID values to be cached across instances. In order to prevent the runtime crash, an interface may have multiple JniPeerMembers values, one per inherited interface, which is used to invoke methods from that interface.

IWindowManagerInvoker now becomes:

internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
    static readonly JniPeerMembers _members_ViewManager = …;
    static readonly JniPeerMembers _members_WindowManager = …;

    public void AddView(View view, ViewGroup.LayoutParams @params)
    {
        const string __id = "addView.…";
        _members_ViewManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …);
    }

    public void RemoveViewImmediate(View view)
    {
        const string __id = "removeViewImmediate.…";
        _members_WindowManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …);
    }
}

This has two advantages:

  1. More caching!
  2. Desktop Java.Base binding can now have interface invokers.

TODO: Performance? How much better is this?
Update tests/invocation-overhead.

Update tests/generator-Tests expected output.
Note: to keep this patch small, only JavaInterop1 output uses the new pattern. XAJavaInterop1 output is unchanged, even though it will use the new output by default.

Added CS0114 to $(NoWarn) in Java.Base.csproj to ignore warnings such as:

…/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.ICharSequence.cs(195,25): warning CS0114:
'ICharSequenceInvoker.ToString()' hides inherited member 'Object.ToString()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.

Ignoring CS0114 is also done in Mono.Android.dll as well, so this is not a new or unique requirement.

Update Java.Interop.dll so that
JniRuntime.JniValueManager.GetActivationConstructor() now knows about and looks for *Invoker types, then uses the activation constructor from the *Invoker type when the source type is an abstract class or interface.

Update tests/Java.Base-Tests to test for implicit *Invoker lookup and invocation support.

Fixes: #910

Context: bc5bcf4
Context: #858

Consider the Java `java.lang.Runnable` interface:

	package java.lang;
	public interface Runnable {
	    void run ();
	}

This is bound as:

	package Java.Lang;
	public interface IRunnable : IJavaPeerable {
	    void Run ();
	}

with some slight differences depending on whether we're dealing with
.NET Android (`generator --codegen-target=xajavainterop1`) or
`src/Java.Base` (`generator --codegen-target=javainterop1`).

Now, assume a Java API + corresponding binding which returns a
`Runnable` instance:

	package example;
	public class Whatever {
	    public static Runnable createRunnable();
	}

You can invoke `IRunnable.Run()` on the return value:

	IRunnable r = Whatever.CreateRunnable();
	r.Run();

but how does that work?

This works via an "interface Invoker", which is a class emitted by
`generator` which implements the interface and invokes the interface
methods through JNI:

	internal partial class IRunnableInvoker : Java.Lang.Object, IRunnable {
	    public void Run() => …
	}

Once Upon A Time™, the interface invoker implementation mirrored that
of classes: a static `IntPtr` field held the `jmethodID` value, which
would be looked up on first-use and cached for subsequent invocations:

	partial class IRunnableInvoker {
	    static IntPtr id_run;
	    public unsafe void Run() {
	        if (id_run == IntPtr.Zero)
	            id_run = JNIEnv.GetMethodID (class_ref, "run", "()V");
	        JNIEnv.CallVoidMethod (Handle, id_run, …);
	    }
	}

This approach works until you have interface inheritance and methods
which come from different interfaces:

	package android.view;
	public /* partial */ interface ViewManager {
	    void addView(View view, ViewGroup.LayoutParams params);
	}
	public /* partial */ interface WindowManager extends ViewManager {
	    void removeViewImmediate(View view);
	}

This would be bound as:

	namespace Android.Views;
	public partial interface IViewManager : IJavaPeerable {
	    void AddView (View view, ViewGroup.LayoutParams @params);
	}
	public partial IWindowManager : IViewManager {
	    void RemoveViewImmediate (View view);
	}
	internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
	    static IntPtr id_addView;
	    public void AddView(View view, ViewGroup.LayoutParams @params)
	    {
	        if (id_addView == IntPtr.Zero)
	            id_run = JNIEnv.GetMethodID (class_ref, "addView", "…");
	        JNIEnv.CallVoidMethod (Handle, id_addView, …);
	    }
	}

Unfortunately, *invoking* `IViewManager.AddView()` through an
`IWindowManagerInvoker` would crash!

	D/dalvikvm( 6645): GetMethodID: method not found: Landroid/view/WindowManager;.addView:(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V
	I/MonoDroid( 6645): UNHANDLED EXCEPTION: Java.Lang.NoSuchMethodError: Exception of type 'Java.Lang.NoSuchMethodError' was thrown.
	I/MonoDroid( 6645): at Android.Runtime.JNIEnv.GetMethodID (intptr,string,string)
	I/MonoDroid( 6645): at Android.Views.IWindowManagerInvoker.AddView (Android.Views.View,Android.Views.ViewGroup/LayoutParams)
	I/MonoDroid( 6645): at Mono.Samples.Hello.HelloActivity.OnCreate (Android.OS.Bundle)
	I/MonoDroid( 6645): at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr)
	I/MonoDroid( 6645): at (wrapper dynamic-method) object.ecadbe0b-9124-445e-a498-f351075f6c89 (intptr,intptr,intptr)

Interfaces are not classes, and this is one of the places that this
is most apparent.  Because of this crash, we had to use *instance*
`jmethodID` caches:

	internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
	    IntPtr id_addView;
	    public void AddView(View view, ViewGroup.LayoutParams @params)
	    {
	        if (id_addView == IntPtr.Zero)
	            id_run = JNIEnv.GetMethodID (class_ref, "addView", "…");
	        JNIEnv.CallVoidMethod (Handle, id_addView, …);
	    }
	}

Pro: no more crash!

Con: *every different instance* of `IWindowManagerInvoker` needs to
separately lookup whatever methods are required.  There is *some*
caching, so repeated calls to `AddView()` on the same instance will
hit the cache, but if you obtain a different `IWindowManager`
instance, `jmethodID` values will need to be looked up again.

This was "fine", until #858 enters the picture:
interface invokers were full of Android-isms --
`Android.Runtime.JNIEnv.GetMethodID()`! -- and thus ***not*** APIs
that @jonpryor wished to expose within desktop Java.Base bindings.

Enter `generator --lang-features=emit-legacy-interface-invokers`:
when *not* specified, interface invokers will now use
`JniPeerMembers` for method lookup and invocation, allowing
`jmethodID` values to be cached *across* instances.  In order to
prevent the runtime crash, an interface may have *multiple*
`JniPeerMembers` values, one per inherited interface, which is used
to invoke methods from that interface.

`IWindowManagerInvoker` now becomes:

	internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
	    static readonly JniPeerMembers _members_ViewManager = …;
	    static readonly JniPeerMembers _members_WindowManager = …;

	    public void AddView(View view, ViewGroup.LayoutParams @params)
	    {
	        const string __id = "addView.…";
	        _members_ViewManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …);
	    }

	    public void RemoveViewImmediate(View view)
	    {
	        const string __id = "removeViewImmediate.…";
	        _members_WindowManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …);
	    }
	}

This has two advantages:

 1. More caching!
 2. Desktop `Java.Base` binding can now have interface invokers.

TODO: Performance?  How much better is this?
Update tests/invocation-overhead.

Update `tests/generator-Tests` expected output.
Note: to keep this patch small, only JavaInterop1 output uses the
new pattern.  XAJavaInterop1 output is unchanged, *even though*
it will use the new output *by default*.

Added [CS0114][0] to `$(NoWarn)` in `Java.Base.csproj` to ignore
warnings such as:

	…/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.ICharSequence.cs(195,25): warning CS0114:
	'ICharSequenceInvoker.ToString()' hides inherited member 'Object.ToString()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.

Ignoring CS0114 is also done in `Mono.Android.dll` as well, so this
is not a new or unique requirement.

Update `Java.Interop.dll` so that
`JniRuntime.JniValueManager.GetActivationConstructor()` now knows
about and looks for `*Invoker` types, then uses the activation
constructor from the `*Invoker` type when the source type is an
abstract `class` or `interface`.

Update `tests/Java.Base-Tests` to test for implicit `*Invoker` lookup
and invocation support.

[0]: https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0114
@jonpryor
Copy link
Member Author

TODO:

  • xamarin-android side test PR
  • Update tests/invocation-overhead and verify this is actually faster. (I mean, how can it not? but…)

How bad are instance `jmethodID`s?
(Or, how much better is using `JniPeerMembers`?)

Add a new `InterfaceInvokerTiming` test fixture to
`Java.Interop-PerformanceTests.dll`, which:

 1. "Reimplements" the "legacy" and "JniPeerMembers" Invoker
    strategies
 2. For each Invoker strategy:
     a. Invokes a Java method which returns a `java.lang.Runnable`
        instance
     b. Invokes `Runnable.run()` on the instance returned by (2.a)
        …100 times.
     c. Repeat (2.a) and (2.b) 100 times.

The result is that `JniPeerMembers` is *much* faster:

	% dotnet build tests/Java.Interop-PerformanceTests/*.csproj && \
		dotnet test --logger "console;verbosity=detailed"  bin/TestDebug-net7.0/Java.Interop-PerformanceTests.dll --filter "Name~InterfaceInvokerTiming"
	…
	 Passed InterfaceInvokers [1 s]
	 Standard Output Messages:
	## InterfaceInvokers Timing: instanceIds: 00:00:01.1095502
	## InterfaceInvokers Timing: peerMembers: 00:00:00.1400427

Using `JniPeerMembers` takes ~1/8th the time as using `jmethodID`s.

Oddly -- suggesting that something is wrong with my test, review
welcome! -- if I increase the (2.b) iteration count, the `peerMembers`
time is largely unchanged (~0.14s), while the `instanceIds` time
increases linearly.

*Something* is wrong there.  I'm not sure what.  (Or *nothing* is
wrong, and instance `jmethodID` are just *that* bad.)
jonpryor added a commit to jonpryor/xamarin-android that referenced this pull request Sep 15, 2023
@jonpryor
Copy link
Member Author

DO NOT MERGE until we're targeting .NET 9. and dotnet/android#8339 is good.

Trying to build in xamarin-android resulted in lots of errors:

    …/mcw/Android.Text.IInputType.cs(34,17): error CS0122: 'Object._members' is inaccessible due to its protection level

Oops.

Looks like at least *some* tests need to run w/ XAJavaInterop1 output!

Update `BaseGeneratorTest` to use `XAJavaInterop1` output for the
tests, update `generator` so that the tests *pass*, and update the
expected output accordingly.
jonpryor added a commit to jonpryor/xamarin-android that referenced this pull request Sep 19, 2023
Context: dotnet/android#8339

While testing on dotnet/android#8339, we hit this error
(among others, to be addressed later):

	src/Mono.Android/obj/Debug/net8.0/android-34/mcw/Android.Views.IWindowInsetsController.cs(304,41): error CS0103: The name 'behavior' does not exist in the current context

This was caused because of code such as:

	public partial interface IWindowInsetsController {
		public unsafe int SystemBarsBehavior {
			get {
				const string __id = "getSystemBarsBehavior.()I";
				try {
					var __rm = _members_IWindowInsetsController.InstanceMethods.InvokeAbstractInt32Method (__id, this, null);
					return __rm;
				} finally {
				}
			}
			set {
				const string __id = "setSystemBarsBehavior.(I)V";
				try {
					JniArgumentValue* __args = stackalloc JniArgumentValue [1];
					__args [0] = new JniArgumentValue (behavior);
					_members_IWindowInsetsController.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args);
				} finally {
				}
			}
		}
	}

This happened because when emitting the property setter, we need
to update the `set*` method's parameter name to be `value` so that
the normal property setter body is emitted properly.

Update `InterfaceInvokerProperty.cs` so that the parameter name
is set to `value`.

Update `tests/generator-Tests/Integration-Tests/Interfaces.cs`
so that we test interface generation for JavaInterop1.

Update `tests/generator-Tests/SupportFiles/*.cs` so that
`Interfaces.cs` test output can be compiled.

Update `tests/generator-Tests/expected.ji/TestInterface/TestInterface.xml`
so that some parameter names for `set*()` methods are *not* already
named `value`, to induce the original error.

Flush `tests/generator-Tests/expected.ji/TestInterface`.
A concern with *just* using the interface name is when the same
interface name exists in multiple packages, and you're implementing
*all* of them:

    package p1;
    public interface List {}

    package p2;
    public interface List {}

    package example;
    public interface MyList implements p1.List, p2.List {}

With this API, the interface invoker for `IMyListInvoker` would
try to emit `_members_List` *twice*, which is a good way to get
compilation errors.

To avoid this, we could *hash* the package name + type name, as we
do with Java Callable Wrappers, but if you see `_members_hash1` vs.
`_members_hash2`, it's difficult to review what's going on.

Instead, use the full Java-side name, "mangled" so that it can be
an identifier.  This results in:

    partial class IMyListInvoker : Java.Lang.Object, IMyList {
        static readonly JniPeerMembers _members_p1_List = …;
        static readonly JniPeerMembers _members_p2_List = …;
        static readonly JniPeerMembers _members_example_MyList = …
    }

This can result in long member names, but we should be well within
C# language limits (512 characters?), and it's readable!
Consider `java.util.Deque`:

	/* partial */ interface Collection<E> {
	    boolean add(E e);
	    void clear();
	}
	/* partial */ interface Queue<E> implements Collection<E> {
	    boolean add(E e);
	}
	/* partial */ interface Deque<E> implements Queue<E> {
	    boolean add(E e);
	}

When implementing `IDequeInvoker`, we need to implement
`ICollection.Clear()`, which is only declared in `Collection`:

	partial class IDequeInvoker {
	    static readonly JniPeerMembers _members_java_util_Deque = new JniPeerMembers ("java/util/Deque", typeof (IDequeInvoker));
	    static readonly JniPeerMembers _members_java_util_Queue = new JniPeerMembers ("java/util/Queue", typeof (IDequeInvoker));

	    public unsafe void Clear ()
	    {
	        const string __id = "clear.()V";
	        try {
	            _members_java_util_Collection.InstanceMethods.InvokeAbstractVoidMethod (__id, this, null);
	        } finally {
	        }
	    }
	}

The problem? `_members_java_util_Collection` didn't exist, because
we only emitted `_members_*` fields for interfaces returned by
`GenBase.GetAllImplementedInterfaces()`, which only returns "toplevel"
implemented interfaces, but not the full graph of interfaces.
In this case, we got `_members_java_util_Queue`, but not
`_members_java_util_Collection`, which meant that
`IDequeInvoker.Clear()` was attempting to access a field which did
not exist.

Update `InterfaceInvokerClass` so that the complete set of implemented
interfaces is retrieved and emitted.
jonpryor added a commit to jonpryor/xamarin-android that referenced this pull request Oct 23, 2023
jonpryor added a commit to jonpryor/xamarin-android that referenced this pull request Oct 23, 2023
jonpryor added a commit to jonpryor/xamarin-android that referenced this pull request Oct 23, 2023
HashSet has indeterminate ordering, so rebuilds may change the field
order, which could make things weird.  Sort them.
@jonpryor
Copy link
Member Author

Performance Tests?

# JavaTiming_CreateRunnable_Invocations=100, Runnable_Run_Invocations=100
% dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop-PerformanceTests.dll --filter "Name~InterfaceInvokerTiming"
…
 ## InterfaceInvokers Timing: instanceIds: 00:00:01.2014814
 ## InterfaceInvokers Timing: peerMembers: 00:00:00.1477179

# JavaTiming_CreateRunnable_Invocations=100, Runnable_Run_Invocations=1000
% dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop-PerformanceTests.dll --filter "Name~InterfaceInvokerTiming"
 ## InterfaceInvokers Timing: instanceIds: 00:00:10.2499754
 ## InterfaceInvokers Timing: peerMembers: 00:00:00.1760339

…which makes me think there's something wrong with the current performance tests. Increasing Runnable_Run_Invocations tenfold increases instanceIds time tenfold, but barely touches peerMembers time. Like…how? What is wrong here?

v => {
opts.EmitLegacyInterfaceInvokers = v?.Contains ("emit-legacy-interface-invokers") == true;
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the intention with this flag? Will we use it internally? Is it an escape hatch if there are issues with this implementation? Are we going to publicly expose it for users?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, the intention is that if the new idea screws somebody up -- they can't compile, whatever -- then they'll have a way to disable it. As such, yes, we'd need to publicly expose the toggle via a .NET Android MSBuild property.

@@ -366,6 +366,23 @@ public void FixupMethodOverrides (CodeGenerationOptions opt)
set => support.FullName = value;
}

(object Package, object Name, string Id) javaFullNameIdCache;

public string JavaFullNameId {
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice to add a comment to this property explaining what the format is and what uses it. There are so many *Name properties that I would like to start documenting what each one is for.

Copy link
Member Author

Choose a reason for hiding this comment

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

doc-comment added.

jonpryor added a commit to jonpryor/xamarin-android that referenced this pull request Oct 25, 2023
@jonpryor jonpryor merged commit 1adb796 into main Oct 26, 2023
@jonpryor jonpryor deleted the jonp-iface-invokers-910 branch October 26, 2023 18:08
jonpryor added a commit to jonpryor/xamarin-android that referenced this pull request Oct 26, 2023
Fixes: dotnet/java-interop#910
Fixes: dotnet/java-interop#1139

Changes: dotnet/java-interop@3c83179...1adb796

  * dotnet/java-interop@1adb7964: [generator] `generator --lang-features=emit-legacy-interface-invokers` (dotnet/java-interop#1145)
  * dotnet/java-interop@6bd7ae48: [Xamarin.Android.Tools.Bytecode] Kotlin internal prop visibility (dotnet/java-interop#1151)

Updates `class-parse` to better support parsing the visibility of
Kotlin properties.

Updates `generator` to emit optimized interface Invokers.

Add support for a new `$(AndroidEmitLegacyInterfaceInvokers)` MSBuild
property; when `true`, causes `generator` to emit legacy interface
Invokers.  The default value is `false`.
jonpryor added a commit to dotnet/android that referenced this pull request Nov 7, 2023
Fixes: dotnet/java-interop#910
Fixes: dotnet/java-interop#1139

Changes: dotnet/java-interop@3c83179...38c8a82

  * dotnet/java-interop@38c8a827: [Xamarin.Android.Tools.Bytecode] Kotlin unsigned internal props (dotnet/java-interop#1156)
  * dotnet/java-interop@1adb7964: [generator] `generator --lang-features=emit-legacy-interface-invokers` (dotnet/java-interop#1145)
  * dotnet/java-interop@6bd7ae48: [Xamarin.Android.Tools.Bytecode] Kotlin internal prop visibility (dotnet/java-interop#1151)

Updates `class-parse` to better support parsing the visibility of
Kotlin properties.

Updates `generator` to emit optimized interface Invokers.
The previous interface invoker codegen strategy can be enabled by
setting `$(_AndroidEmitLegacyInterfaceInvokers)`=True.
@github-actions github-actions bot locked and limited conversation to collaborators Apr 12, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optimize Interface Invokers
2 participants