-
Notifications
You must be signed in to change notification settings - Fork 538
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Xamarin.Android.Build.Tasks, monodroid] Optimize member remapping (#…
…7059) Fixes: #7020 Context: f6f11a5 Commit f6f11a5 introduced type & member remapping. The problem with its approach was that it used XML as the representation format, which needed to be parsed during process startup. This would contribute to app startup slowdowns if there were any remappings, but even if there weren't any remappings, the minimum App size increased by ~490KB, due to the added dependencies on `System.Private.Xml.dll` & more. This app size increase is not ideal. Remove most of the `.apk` size increases by moving the remapping info into `libxamarin-app.so`. If no remappings are used, then the size increase to `libxamarin-app.so` is negligible, and the `.apk` size is only 61KB larger than pre-f6f11a5a. ~~ API Changes ~~ `Android.Runtime.AndroidTypeManager` gains the following members: partial class AndroidTypeManager { struct JniRemappingReplacementMethod { public string target_type, target_name; public bool is_static; } static extern byte* _monodroid_lookup_replacement_type ( string jniSimpleReference ); static extern JniRemappingReplacementMethod* _monodroid_lookup_replacement_method_info ( string jniSourceType, string jniMethodName, string jniMethodSignature ); } `AndroidTypeManager._monodroid_lookup_replacement_type()` replaces the `JNIEnv.ReplacementTypes` dictionary from f6f11a5. `AndroidTypeManager._monodroid_lookup_replacement_method_info()` replaces the `JNIEnv.ReplacementMethods` dictionary from f6f11a5. Both `_monodroid_lookup_replacement_type()` and `_monodroid_lookup_replacement_method_info()` are P/Invokes into `libxamarin-app.so`. ~~ `libxamarin-app.so` Changes ~~ The contents of the `@(_AndroidRemapMembers)` item group are now stored within `libxamarin-app.so`, with the following structure: const uint32_t jni_remapping_replacement_method_index_entry_count; const JniRemappingIndexTypeEntry jni_remapping_method_replacement_index[]; const uint32_t jni_remapping_replacement_type_count; const JniRemappingTypeReplacementEntry jni_remapping_type_replacements[]; struct JniRemappingString { const uint32_t length; const char *str; }; struct JniRemappingReplacementMethod { const char *target_type, *target_name; const bool is_static; }; struct JniRemappingIndexMethodEntry { JniRemappingString name, signature; JniRemappingReplacementMethod replacement }; struct struct JniRemappingIndexTypeEntry { JniRemappingString name; uint32_t method_count; JniRemappingIndexMethodEntry *methods; }; struct JniRemappingTypeReplacementEntry { JniRemappingString name; const char *replacement; }; const char * _monodroid_lookup_replacement_type (const char *); const JniRemappingReplacementMethod* _monodroid_lookup_replacement_method_info (const char *jniSourceType, const char *jniMethodName, const char *jniMethodSignature); Referring to the `<replacements/>` XML from f6f11a5 in `@(_AndroidRemapMembers)`: * `//replace-type/@from` fills `JniRemappingTypeReplacementEntry::name`, `//replace-type/@to` fills `JniRemappingTypeReplacementEntry::replacement`, and `_monodroid_lookup_replacement_type()` performs a linear search over `jni_remapping_type_replacements`. * `//replace-method/@source-type` fills `JniRemappingIndexTypeEntry::name`, `//replace-method/@source-method-name` fills `JniRemappingIndexMethodEntry::name`, `//replace-method/@source-method-signature` fills `JniRemappingIndexMethodEntry::signature`, `//replace-method/@target-type` fills `JniRemappingReplacementMethod::target_type`, `//replace-method/@target-method-name` fills `JniRemappingReplacementMethod::target_name`, `//replace-method/@target-method-signature` and `//replace-method/@target-method-parameter-count` are *ignored*, `//replace-method/@target-method-instance-to-static` fills `JniRemappingReplacementMethod::is_static`, and `_monodroid_lookup_replacement_method_info()` performs a search over `jni_remapping_method_replacement_index` looking for entries with "matching" type names, method names, and method signatures, and once a match is found it returns a pointer to the `JniRemappingReplacementMethod` instance. Co-authored-by: Jonathan Peppers <jonathan.peppers@gmail.com>
- Loading branch information
1 parent
226d750
commit f99fc81
Showing
35 changed files
with
1,091 additions
and
316 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
160 changes: 160 additions & 0 deletions
160
src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
Oops, something went wrong.