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

Mam remapping opt #7059

Merged
merged 2 commits into from
Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
125 changes: 33 additions & 92 deletions src/Mono.Android/Android.Runtime/AndroidRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,13 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference value)
}

class AndroidTypeManager : JniRuntime.JniTypeManager {
struct JniRemappingReplacementMethod
{
public string target_type;
public string target_name;
public bool is_static;
};

bool jniAddNativeMethodRegistrationAttributePresent;

public AndroidTypeManager (bool jniAddNativeMethodRegistrationAttributePresent)
Expand Down Expand Up @@ -317,123 +324,57 @@ protected override IEnumerable<string> GetSimpleReferences (Type type)
};
}

[DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr _monodroid_lookup_replacement_type (string jniSimpleReference);

protected override string? GetReplacementTypeCore (string jniSimpleReference)
{
if (JNIEnv.ReplacementTypes == null) {
if (!JNIEnv.jniRemappingInUse) {
return null;
}
if (JNIEnv.ReplacementTypes.TryGetValue (jniSimpleReference, out var v)) {
return v;

IntPtr ret = _monodroid_lookup_replacement_type (jniSimpleReference);
if (ret == IntPtr.Zero) {
return null;
}
return null;

return Marshal.PtrToStringAnsi (ret);
}

[DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr _monodroid_lookup_replacement_method_info (string jniSourceType, string jniMethodName, string jniMethodSignature);

protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature)
{
if (JNIEnv.ReplacementMethods == null) {
if (!JNIEnv.jniRemappingInUse) {
return null;
}
#if !STRUCTURED
if (!JNIEnv.ReplacementMethods.TryGetValue (CreateReplacementMethodsKey (jniSourceType, jniMethodName, jniMethodSignature), out var r) &&
!JNIEnv.ReplacementMethods.TryGetValue (CreateReplacementMethodsKey (jniSourceType, jniMethodName, GetMethodSignatureWithoutReturnType ()), out r) &&
!JNIEnv.ReplacementMethods.TryGetValue (CreateReplacementMethodsKey (jniSourceType, jniMethodName, null), out r)) {
return null;
}
ReadOnlySpan<char> replacementInfo = r;

var targetType = GetNextString (ref replacementInfo);
var targetName = GetNextString (ref replacementInfo);
var targetSig = GetNextString (ref replacementInfo);
var paramCountStr = GetNextString (ref replacementInfo);
var isStaticStr = GetNextString (ref replacementInfo);

int? paramCount = null;
if (!paramCountStr.IsEmpty) {
if (!int.TryParse (paramCountStr, 0, System.Globalization.CultureInfo.InvariantCulture, out var count)) {
return null;
}
paramCount = count;
IntPtr retInfo = _monodroid_lookup_replacement_method_info (jniSourceType, jniMethodName, jniMethodSignature);
if (retInfo == IntPtr.Zero) {
return null;
}

bool isStatic = false;
if (isStaticStr.Equals ("true", StringComparison.Ordinal)) {
isStatic = true;
}
var method = new JniRemappingReplacementMethod ();
method = Marshal.PtrToStructure<JniRemappingReplacementMethod>(retInfo);

if (targetSig.IsEmpty && isStatic) {
paramCount = paramCount ?? JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature);
paramCount++;
int? paramCount = null;
if (method.is_static) {
paramCount = JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature) + 1;
jniMethodSignature = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length);
}

return new JniRuntime.ReplacementMethodInfo {
SourceJniType = jniSourceType,
SourceJniMethodName = jniMethodName,
SourceJniMethodSignature = jniMethodSignature,
TargetJniType = targetType.IsEmpty ? jniSourceType : new string (targetType),
TargetJniMethodName = targetName.IsEmpty ? jniMethodName : new string (targetName),
TargetJniMethodSignature = targetSig.IsEmpty ? jniMethodSignature : new string (targetSig),
TargetJniType = method.target_type,
TargetJniMethodName = method.target_name,
TargetJniMethodSignature = jniMethodSignature,
TargetJniMethodParameterCount = paramCount,
TargetJniMethodInstanceToStatic = isStatic,
TargetJniMethodInstanceToStatic = method.is_static,
};
#else
if (!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, jniMethodSignature), out var r) &&
!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, GetMethodSignatureWithoutReturnType ()), out r) &&
!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, null), out r)) {
return null;
}
var targetSig = r.TargetSignature;
var paramCount = r.ParamCount;
if (targetSig == null && r.TurnStatic) {
targetSig = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length);
paramCount = paramCount ?? JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature);
paramCount++;
}
return new JniRuntime.ReplacementMethodInfo {
SourceJniType = jniSourceType,
SourceJniMethodName = jniMethodName,
SourceJniMethodSignature = jniMethodSignature,
TargetJniType = r.TargetType ?? jniSourceType,
TargetJniMethodName = r.TargetName ?? jniMethodName,
TargetJniMethodSignature = targetSig ?? jniMethodSignature,
TargetJniMethodParameterCount = paramCount,
TargetJniMethodInstanceToStatic = r.TurnStatic,
};
#endif // !STRUCTURED

string GetMethodSignatureWithoutReturnType ()
{
int i = jniMethodSignature.IndexOf (')');
return jniMethodSignature.Substring (0, i+1);
}

string GetValue (string? value)
{
return value == null ? "null" : $"\"{value}\"";
}

ReadOnlySpan<char> GetNextString (ref ReadOnlySpan<char> info)
{
int index = info.IndexOf ('\t');
var r = info;
if (index >= 0) {
r = info.Slice (0, index);
info = info.Slice (index+1);
return r;
}
info = default;
return r;
}
}

static string CreateReplacementMethodsKey (string? sourceType, string? methodName, string? methodSignature) =>
new StringBuilder ()
.Append (sourceType)
.Append ('\t')
.Append (methodName)
.Append ('\t')
.Append (methodSignature)
.ToString ();
#endif // NET

delegate Delegate GetCallbackHandler ();
Expand Down
22 changes: 3 additions & 19 deletions src/Mono.Android/Android.Runtime/JNIEnv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@
using Java.Interop.Tools.TypeNameMappings;
using System.Diagnostics.CodeAnalysis;

#if NET
using ReplacementTypesDict = System.Collections.Generic.Dictionary<string, string>;
using ReplacementMethodsDict = System.Collections.Generic.Dictionary<string, string>;
#endif // NET

namespace Android.Runtime {
#pragma warning disable 0649
struct JnienvInitializeArgs {
Expand All @@ -40,8 +35,7 @@ struct JnienvInitializeArgs {
public int packageNamingPolicy;
public byte ioExceptionType;
public int jniAddNativeMethodRegistrationAttributePresent;
public IntPtr mappingXml;
public int mappingXmlLen;
public bool jniRemappingInUse;
}
#pragma warning restore 0649

Expand All @@ -55,6 +49,7 @@ public static partial class JNIEnv {
static int androidSdkVersion;

static bool AllocObjectSupported;
internal static bool jniRemappingInUse;

static IntPtr grefIGCUserPeer_class;

Expand All @@ -68,11 +63,6 @@ public static partial class JNIEnv {
static AndroidRuntime? androidRuntime;
static BoundExceptionType BoundExceptionType;

#if NET
internal static ReplacementTypesDict? ReplacementTypes;
internal static ReplacementMethodsDict? ReplacementMethods;
#endif // NET

[ThreadStatic]
static byte[]? mvid_bytes;

Expand Down Expand Up @@ -167,6 +157,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)

gref_gc_threshold = args->grefGcThreshold;

jniRemappingInUse = args->jniRemappingInUse;
java_vm = args->javaVm;

version = args->version;
Expand All @@ -178,13 +169,6 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
gref_class = args->grefClass;
mid_Class_forName = new JniMethodInfo (args->Class_forName, isStatic: true);

#if NET
if (args->mappingXml != IntPtr.Zero) {
var xml = Encoding.UTF8.GetString ((byte*) args->mappingXml, args->mappingXmlLen);
(ReplacementTypes, ReplacementMethods) = MamXmlParser.ParseStrings (xml);
}
#endif // NET

if (args->localRefsAreIndirect == 1)
IdentityHash = v => _monodroid_get_identity_hash_code (Handle, v);
else
Expand Down
1 change: 0 additions & 1 deletion src/Mono.Android/Mono.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,6 @@
<Compile Include="Android.Runtime\JObjectRefType.cs" />
<Compile Include="Android.Runtime\JValue.cs" />
<Compile Include="Android.Runtime\Logger.cs" />
<Compile Include="Android.Runtime\MamXmlParser.cs" />
<Compile Include="Android.Runtime\NamespaceMappingAttribute.cs" />
<Compile Include="Android.Runtime\OutputStreamAdapter.cs" />
<Compile Include="Android.Runtime\OutputStreamInvoker.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Xml;

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.Android.Build.Tasks;

namespace Xamarin.Android.Tasks
{
public class GenerateJniRemappingNativeCode : AndroidTask
{
internal const string JniRemappingNativeCodeInfoKey = ".:!JniRemappingNativeCodeInfo!:.";

internal sealed class JniRemappingNativeCodeInfo
{
public int ReplacementTypeCount { get; }
public int ReplacementMethodIndexEntryCount { get; }

public JniRemappingNativeCodeInfo (int replacementTypeCount, int replacementMethodIndexEntryCount)
{
ReplacementTypeCount = replacementTypeCount;
ReplacementMethodIndexEntryCount = replacementMethodIndexEntryCount;
}
}

public override string TaskPrefix => "GJRNC";

public ITaskItem RemappingXmlFilePath { get; set; }

[Required]
public string OutputDirectory { get; set; }

[Required]
public string [] SupportedAbis { get; set; }

public bool GenerateEmptyCode { get; set; }

public override bool RunTask ()
{
if (!GenerateEmptyCode) {
if (RemappingXmlFilePath == null) {
throw new InvalidOperationException ("RemappingXmlFilePath parameter is required");
}

Generate ();
} else {
GenerateEmpty ();
}

return !Log.HasLoggedErrors;
}

void GenerateEmpty ()
{
Generate (new JniRemappingAssemblyGenerator (), typeReplacementsCount: 0);
}

void Generate ()
{
var typeReplacements = new List<JniRemappingTypeReplacement> ();
var methodReplacements = new List<JniRemappingMethodReplacement> ();

var readerSettings = new XmlReaderSettings {
XmlResolver = null,
};

using (var reader = XmlReader.Create (File.OpenRead (RemappingXmlFilePath.ItemSpec), readerSettings)) {
if (reader.MoveToContent () != XmlNodeType.Element || reader.LocalName != "replacements") {
Log.LogError ($"Input file `{RemappingXmlFilePath.ItemSpec}` does not start with `<replacements/>`");
} else {
ReadXml (reader, typeReplacements, methodReplacements);
}
}

Generate (new JniRemappingAssemblyGenerator (typeReplacements, methodReplacements), typeReplacements.Count);
}

void Generate (JniRemappingAssemblyGenerator jniRemappingGenerator, int typeReplacementsCount)
{
jniRemappingGenerator.Init ();

foreach (string abi in SupportedAbis) {
string baseAsmFilePath = Path.Combine (OutputDirectory, $"jni_remap.{abi.ToLowerInvariant ()}");
string llFilePath = $"{baseAsmFilePath}.ll";

using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
jniRemappingGenerator.Write (GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llFilePath);
sw.Flush ();
Files.CopyIfStreamChanged (sw.BaseStream, llFilePath);
}
}

BuildEngine4.RegisterTaskObjectAssemblyLocal (
JniRemappingNativeCodeInfoKey,
new JniRemappingNativeCodeInfo (typeReplacementsCount, jniRemappingGenerator.ReplacementMethodIndexEntryCount),
RegisteredTaskObjectLifetime.Build
);
}

void ReadXml (XmlReader reader, List<JniRemappingTypeReplacement> typeReplacements, List<JniRemappingMethodReplacement> methodReplacements)
{
bool haveAllAttributes;

while (reader.Read ()) {
if (reader.NodeType != XmlNodeType.Element) {
continue;
}

haveAllAttributes = true;
if (String.Compare ("replace-type", reader.LocalName, StringComparison.Ordinal) == 0) {
haveAllAttributes &= GetRequiredAttribute ("from", out string from);
haveAllAttributes &= GetRequiredAttribute ("to", out string to);
if (!haveAllAttributes) {
continue;
}

typeReplacements.Add (new JniRemappingTypeReplacement (from, to));
} else if (String.Compare ("replace-method", reader.LocalName, StringComparison.Ordinal) == 0) {
haveAllAttributes &= GetRequiredAttribute ("source-type", out string sourceType);
haveAllAttributes &= GetRequiredAttribute ("source-method-name", out string sourceMethodName);
haveAllAttributes &= GetRequiredAttribute ("target-type", out string targetType);
haveAllAttributes &= GetRequiredAttribute ("target-method-name", out string targetMethodName);
haveAllAttributes &= GetRequiredAttribute ("target-method-instance-to-static", out string targetIsStatic);

if (!haveAllAttributes) {
continue;
}

if (!Boolean.TryParse (targetIsStatic, out bool isStatic)) {
Log.LogError ($"Attribute 'target-method-instance-to-static' in element '{reader.LocalName}' value '{targetIsStatic}' cannot be parsed as boolean; {RemappingXmlFilePath.ItemSpec} line {GetCurrentLineNumber ()}");
continue;
}

string sourceMethodSignature = reader.GetAttribute ("source-method-signature");
methodReplacements.Add (
new JniRemappingMethodReplacement (
sourceType, sourceMethodName, sourceMethodSignature,
targetType, targetMethodName, isStatic
)
);
}
}

bool GetRequiredAttribute (string attributeName, out string attributeValue)
{
attributeValue = reader.GetAttribute (attributeName);
if (!String.IsNullOrEmpty (attributeValue)) {
return true;
}

Log.LogError ($"Attribute '{attributeName}' missing from element '{reader.LocalName}'; {RemappingXmlFilePath.ItemSpec} line {GetCurrentLineNumber ()}");
return false;
}

int GetCurrentLineNumber () => ((IXmlLineInfo)reader).LineNumber;
}
}
}
Loading