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

Marshal methods part 4 of 5 (runtime) #7285

Merged
merged 60 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
35f4e24
Implement marshal methods LLVM IR executable code generator.
grendello Mar 29, 2022
e34f88e
[marshal methods] Runtime fixes and missing features
grendello Mar 29, 2022
eb15bd2
Update
grendello Jul 18, 2022
a80e4a1
TODOs and CWLs
grendello Jul 18, 2022
49751f7
Update JI
grendello Jul 26, 2022
803b3e3
Merge branch 'main' into mm-codegen
grendello Aug 1, 2022
e6101c1
Merge branch 'mm-codegen' into mm-runtime
grendello Aug 1, 2022
657aff0
[WIP] Generate full native symbol names when necessary
grendello Aug 1, 2022
e8d3026
Handle enums properly (except when there are arrays of them)
grendello Aug 2, 2022
4d7ec30
Merge branch 'main' into mm-codegen
grendello Aug 3, 2022
39068d3
Merge branch 'mm-codegen' into mm-runtime
grendello Aug 3, 2022
82ece43
Generate correct native names and process overloads properly
grendello Aug 3, 2022
acd5bc8
When a class is first seen, make sure to set class index properly
grendello Aug 3, 2022
9a476d8
Merge branch 'main' into mm-codegen
grendello Aug 3, 2022
8d2503f
Merge branch 'mm-codegen' into mm-runtime
grendello Aug 3, 2022
f913d24
Merge branch 'main' into mm-codegen
grendello Aug 5, 2022
52c99db
Merge branch 'mm-codegen' into mm-runtime
grendello Aug 5, 2022
e5358f4
Trying to get the MAUI sample app running
grendello Aug 5, 2022
2f15f67
Merge branch 'main' into mm-codegen
grendello Aug 5, 2022
21c066f
Merge branch 'mm-codegen' into mm-runtime
grendello Aug 5, 2022
3c8901a
MAUI app ran with marshal methods for the first time
grendello Aug 8, 2022
7dc40fc
Check array dimensions when classifying methods
grendello Aug 8, 2022
2b2ce55
Merge branch 'main' into mm-codegen
grendello Aug 8, 2022
9c38c68
Merge branch 'mm-codegen' into mm-runtime
grendello Aug 8, 2022
85f2cfa
Merge branch 'main' into mm-codegen
grendello Aug 10, 2022
024b5f5
Merge branch 'mm-codegen' into mm-runtime
grendello Aug 10, 2022
4d92570
We need to generate wrappers for methods with non-blittable params
grendello Aug 10, 2022
20a2f00
Blittable wrapper generator progress
grendello Aug 11, 2022
2d3c043
Non-blittable marshal method wrappers implemented
grendello Aug 12, 2022
071e9d3
Merge branch 'main' into mm-codegen
grendello Aug 17, 2022
710242e
Merge branch 'mm-codegen' into mm-runtime
grendello Aug 17, 2022
af91895
Fix non-blittable wrapper method generation
grendello Aug 17, 2022
673836c
Merge branch 'main' into mm-codegen
grendello Aug 17, 2022
ca2f92d
Merge branch 'mm-codegen' into mm-runtime
grendello Aug 17, 2022
10e63e2
Merge branch 'main' into mm-codegen
grendello Aug 17, 2022
238417e
Merge branch 'mm-codegen' into mm-runtime
grendello Aug 17, 2022
72c4b60
Disable some debug CWLs
grendello Aug 18, 2022
eb94adc
Merge branch 'mm-codegen' into mm-runtime
grendello Aug 18, 2022
a8471cd
Add some debugging info to function pointer lookups
grendello Aug 18, 2022
cfaabf2
Escape double quotes in function attribute value
grendello Aug 18, 2022
8ca61bb
Merge branch 'main' into mm-codegen
grendello Aug 18, 2022
a473f27
Merge branch 'mm-codegen' into mm-runtime
grendello Aug 18, 2022
d99b7e3
Fix and optimize debugging info for class and method names
grendello Aug 19, 2022
cfa1b30
Reorganize code, move error handling to the end of function
grendello Aug 19, 2022
9c9612d
Merge branch 'main' into mm-runtime
grendello Aug 19, 2022
334619b
Disable marshal methods for PR
grendello Aug 19, 2022
c9332a8
Move some code behind the ENABLE_MARSHAL_METHODS flag
grendello Aug 19, 2022
e518a4e
Remove workarounds for methods with non-blittable types
grendello Aug 22, 2022
6ca1245
Update apkdesc files
grendello Aug 22, 2022
63bf193
Fix a failing test
grendello Aug 22, 2022
c516792
Merge branch 'main' into mm-runtime
grendello Aug 23, 2022
c8a2d9f
Implement some requested changes
grendello Aug 23, 2022
0aa3e95
Merge branch 'main' into mm-runtime
grendello Aug 24, 2022
49895b8
Merge branch 'main' into mm-runtime
grendello Aug 24, 2022
92fa671
Remove JI workaround
grendello Aug 24, 2022
6b116bf
Merge branch 'main' into mm-runtime
grendello Aug 26, 2022
e1e59f0
Merge branch 'main' into mm-runtime
grendello Aug 31, 2022
df04d47
Merge branch 'main' into mm-runtime
grendello Sep 2, 2022
d3d9007
Merge branch 'main' into mm-runtime
grendello Sep 6, 2022
b459e4a
Merge branch 'main' into mm-runtime
grendello Sep 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<linker>
<assembly fullname="System.Runtime.InteropServices">
<type fullname="System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute" />
</assembly>
</linker>
91 changes: 85 additions & 6 deletions src/Mono.Android/Android.Runtime/AndroidRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,28 @@ static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments argu
public override void RegisterNativeMembers (JniType nativeClass, Type type, string? methods) =>
RegisterNativeMembers (nativeClass, type, methods.AsSpan ());

#if ENABLE_MARSHAL_METHODS
// Temporary hack, see comments in RegisterNativeMembers below
static readonly Dictionary<string, string[]> dynamicRegistrationMethods = new Dictionary<string, string[]> (StringComparer.Ordinal) {
{"Android.Views.View+IOnLayoutChangeListenerImplementor", new string[] { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }},
{"Android.Views.View+IOnLayoutChangeListenerInvoker", new string[] { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }},
{"Java.Interop.TypeManager+JavaTypeManager", new string[] { "GetActivateHandler" }},
};
#endif

public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<char> methods)
{
#if ENABLE_MARSHAL_METHODS
Logger.Log (LogLevel.Info, "monodroid-mm", $"RegisterNativeMembers ('{nativeClass?.Name}', '{type?.FullName}', '{methods.ToString ()}')");
Logger.Log (LogLevel.Info, "monodroid-mm", "RegisterNativeMembers called from:");
var st = new StackTrace (true);
Logger.Log (LogLevel.Info, "monodroid-mm", st.ToString ());

if (methods.IsEmpty) {
Logger.Log (LogLevel.Info, "monodroid-mm", "No methods to register, returning");
return;
}
#endif
try {
if (FastRegisterNativeMembers (nativeClass, type, methods))
return;
Expand All @@ -497,6 +517,9 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<
MethodInfo []? typeMethods = null;

ReadOnlySpan<char> methodsSpan = methods;
#if ENABLE_MARSHAL_METHODS
bool needToRegisterNatives = false;
#endif
while (!methodsSpan.IsEmpty) {
int newLineIndex = methodsSpan.IndexOf ('\n');

Expand All @@ -508,7 +531,7 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<
out ReadOnlySpan<char> callbackString,
out ReadOnlySpan<char> callbackDeclaringTypeString);

Delegate callback;
Delegate? callback = null;
if (callbackString.SequenceEqual ("__export__")) {
var mname = name.Slice (2);
MethodInfo? minfo = null;
Expand All @@ -522,6 +545,9 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<
if (minfo == null)
throw new InvalidOperationException (String.Format ("Specified managed method '{0}' was not found. Signature: {1}", mname.ToString (), signature.ToString ()));
callback = CreateDynamicCallback (minfo);
#if ENABLE_MARSHAL_METHODS
needToRegisterNatives = true;
#endif
} else {
Type callbackDeclaringType = type;
if (!callbackDeclaringTypeString.IsEmpty) {
Expand All @@ -530,20 +556,73 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<
while (callbackDeclaringType.ContainsGenericParameters) {
callbackDeclaringType = callbackDeclaringType.BaseType!;
}
GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler),
callbackDeclaringType, callbackString.ToString ());
callback = connector ();
#if ENABLE_MARSHAL_METHODS
// TODO: this is temporary hack, it needs a full fledged registration mechanism for methods like these (that is, ones which
// aren't registered with [Register] but are baked into Mono.Android's managed and Java code)
bool createCallback = false;
string declaringTypeName = callbackDeclaringType.FullName;
string callbackName = callbackString.ToString ();

foreach (var kvp in dynamicRegistrationMethods) {
string dynamicTypeName = kvp.Key;

foreach (string dynamicCallbackMethodName in kvp.Value) {
if (ShouldRegisterDynamically (declaringTypeName, callbackName, dynamicTypeName, dynamicCallbackMethodName)) {
createCallback = true;
break;
}
}

if (createCallback) {
Copy link
Member

Choose a reason for hiding this comment

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

This raises a "meta-question": the implication here is that whether we register via JNIEnv::RegisterNatives() or via C Java_… function is done on a per-method basis.

Is that actually supported on Android?

Rephrased; given the Java type:

package example;

public class C {
    public static native void m_viaRegister();
    public static native void m_viaFunction();
}

and then at runtime we have a Java_example_C_m_1viaFunction() function in a loaded .so, and we also call JNIEnv:RegisterNatives() but only for m_viaRegister()

Does that work? The JNI docs are not clear on how these two mechanisms interact.

For that matter, if both native mechanisms are provided, which mechanism actually invoked at runtime? Which has priority?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Answering that would require a deeper dive into Android JNI sources I suppose...

Copy link
Member

Choose a reason for hiding this comment

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

I don't think this requires reading Android source. An on-device test would answer this as well. (Though that answer could change over time, and thus wouldn't be a "final" answer, but it could still be informative.)

break;
}
}

if (createCallback) {
Logger.Log (LogLevel.Info, "monodroid-mm", $" creating delegate for: '{callbackString.ToString()}' in type {callbackDeclaringType.FullName}");
#endif
GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler),
callbackDeclaringType, callbackString.ToString ());
callback = connector ();
#if ENABLE_MARSHAL_METHODS
} else {
Logger.Log (LogLevel.Warn, "monodroid-mm", $" would try to create delegate for: '{callbackString.ToString()}' in type {callbackDeclaringType.FullName}");
}
#endif
}

if (callback != null) {
#if ENABLE_MARSHAL_METHODS
needToRegisterNatives = true;
#endif
natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback);
}
natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback);
}

methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default;
}

JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, natives.Length);
#if ENABLE_MARSHAL_METHODS
if (needToRegisterNatives) {
#endif
JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, nativesIndex);
#if ENABLE_MARSHAL_METHODS
}
#endif
} catch (Exception e) {
JniEnvironment.Runtime.RaisePendingException (e);
}

#if ENABLE_MARSHAL_METHODS
bool ShouldRegisterDynamically (string callbackTypeName, string callbackString, string typeName, string callbackName)
{
if (String.Compare (typeName, callbackTypeName, StringComparison.Ordinal) != 0) {
return false;
}

return String.Compare (callbackName, callbackString, StringComparison.Ordinal) == 0;
}
#endif
}

static int CountMethods (ReadOnlySpan<char> methodsSpan)
Expand Down
4 changes: 4 additions & 0 deletions src/Mono.Android/Mono.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
<JavaCallableWrapperAbsAssembly>$([System.IO.Path]::GetFullPath ('$(OutputPath)$(AssemblyName).dll'))</JavaCallableWrapperAbsAssembly>
</PropertyGroup>

<PropertyGroup Condition=" '$(_EnableMarshalMethods)' == 'YesPlease' ">
<DefineConstants>$(DefineConstants);ENABLE_MARSHAL_METHODS</DefineConstants>
</PropertyGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'monoandroid10' ">
<Reference Include="mscorlib">
<HintPath>$(OutputPath)..\v1.0\mscorlib.dll</HintPath>
Expand Down
23 changes: 22 additions & 1 deletion src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ void Run (DirectoryAssemblyResolver res)

#if ENABLE_MARSHAL_METHODS
if (!Debug) {
// TODO: we must rewrite assemblies for all SupportedAbis. Alternatively, we need to copy the ones that are identical
// Cecil does **not** guarantee that the same assembly modified twice in the same will yield the same result - tokens may differ, so can
// MVID.
var rewriter = new MarshalMethodsAssemblyRewriter (classifier.MarshalMethods, classifier.Assemblies, marshalMethodsAssemblyPaths, Log);
rewriter.Rewrite (res);
}
Expand Down Expand Up @@ -349,6 +352,11 @@ void Run (DirectoryAssemblyResolver res)
regCallsWriter.WriteLine ("\t\t// Application and Instrumentation ACWs must be registered first.");
foreach (var type in javaTypes) {
if (JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache)) {
#if ENABLE_MARSHAL_METHODS
if (!classifier.FoundDynamicallyRegisteredMethods (type)) {
continue;
}
#endif
string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.');
regCallsWriter.WriteLine ("\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);",
type.GetAssemblyQualifiedName (cache), javaKey);
Expand All @@ -362,12 +370,25 @@ void Run (DirectoryAssemblyResolver res)
template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ()));

#if ENABLE_MARSHAL_METHODS
if (!Debug) {
Log.LogDebugMessage ($"Number of generated marshal methods: {classifier.MarshalMethods.Count}");

if (classifier.RejectedMethodCount > 0) {
Log.LogWarning ($"Number of methods in the project that will be registered dynamically: {classifier.RejectedMethodCount}");
}

if (classifier.WrappedMethodCount > 0) {
Log.LogWarning ($"Number of methods in the project that need marshal method wrappers: {classifier.WrappedMethodCount}");
}
}

void StoreMarshalAssemblyPath (string name, ITaskItem asm)
{
if (Debug) {
return;
}

// TODO: we need to keep paths to ALL the assemblies, we need to rewrite them for all RIDs eventually. Right now we rewrite them just for one RID
if (!marshalMethodsAssemblyPaths.TryGetValue (name, out HashSet<string> assemblyPaths)) {
assemblyPaths = new HashSet<string> ();
marshalMethodsAssemblyPaths.Add (name, assemblyPaths);
Expand Down Expand Up @@ -406,7 +427,7 @@ bool CreateJavaSources (IEnumerable<TypeDefinition> javaTypes, TypeDefinitionCac
jti.Generate (writer);
#if ENABLE_MARSHAL_METHODS
if (!Debug) {
if (classifier.FoundDynamicallyRegisteredMethods) {
if (classifier.FoundDynamicallyRegisteredMethods (t)) {
Log.LogWarning ($"Type '{t.GetAssemblyQualifiedName ()}' will register some of its Java override methods dynamically. This may adversely affect runtime performance. See preceding warnings for names of dynamically registered methods.");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,11 +444,12 @@ void AddEnvironment ()
#if ENABLE_MARSHAL_METHODS
var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<MarshalMethodsState> (GenerateJavaStubs.MarshalMethodsRegisterTaskKey, RegisteredTaskObjectLifetime.Build);

var marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator {
NumberOfAssembliesInApk = assemblyCount,
UniqueAssemblyNames = uniqueAssemblyNames,
MarshalMethods = marshalMethodsState?.MarshalMethods,
};
var marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (
assemblyCount,
uniqueAssemblyNames,
marshalMethodsState?.MarshalMethods,
Log
);
marshalMethodsAsmGen.Init ();
#endif
foreach (string abi in SupportedAbis) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public void CheckIncludedAssemblies ([Values (false, true)] bool usesAssemblySto
"System.Console.dll",
"System.Private.CoreLib.dll",
"System.Runtime.dll",
"System.Runtime.InteropServices.dll",
"System.Linq.dll",
"UnnamedProject.dll",
//NOTE: appeared in .NET 7.0.100-rc.1.22423.7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -588,10 +588,10 @@ public void WriteStructureArray<T> (StructureInfo<T> info, IList<StructureInstan
WriteStructureArray<T> (info, instances, LlvmIrVariableOptions.Default, symbolName, writeFieldComment, initialComment);
}

public void WriteArray (IList<string> values, string symbolName)
public void WriteArray (IList<string> values, string symbolName, string? initialComment = null)
{
WriteEOL ();
WriteEOL (symbolName);
WriteEOL (initialComment ?? symbolName);

ulong arrayStringCounter = 0;
var strings = new List<StringSymbolInfo> ();
Expand Down
Loading