From db15d6eb568efb7ca9eef4855fb9f9bff53c89f5 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 25 May 2022 22:57:22 +0200 Subject: [PATCH 1/2] JNI remapping native code generator Co-authored-by: Jonathan Peppers --- .../Android.Runtime/AndroidRuntime.cs | 125 ++---- src/Mono.Android/Android.Runtime/JNIEnv.cs | 22 +- src/Mono.Android/Mono.Android.csproj | 1 - .../Tasks/GenerateJniRemappingNativeCode.cs | 160 ++++++++ .../Tasks/GeneratePackageManagerJava.cs | 3 + .../Tasks/PrepareAbiItems.cs | 5 + .../PackagingTest.cs | 11 - .../Utilities/EnvironmentHelper.cs | 23 +- .../Utilities/ApplicationConfig.cs | 2 + ...pplicationConfigNativeAssemblyGenerator.cs | 4 + .../JniRemappingAssemblyGenerator.cs | 365 ++++++++++++++++++ .../LlvmIrGenerator/IStructureInfo.cs | 6 + .../LlvmIrGenerator/LlvmIrGenerator.cs | 76 +++- .../LlvmIrGenerator/StructureInfo.cs | 32 +- .../LlvmIrGenerator/StructureMemberInfo.cs | 12 +- .../LlvmIrGenerator/TypeUtilities.cs | 16 +- .../Xamarin.Android.Common.targets | 60 ++- .../java/mono/android/MonoPackageManager.java | 31 -- .../java/mono/android/Runtime.java | 2 - src/monodroid/CMakeLists.txt | 1 + src/monodroid/jni/application_dso_stub.cc | 80 ++++ src/monodroid/jni/generate-pinvoke-tables.cc | 2 + src/monodroid/jni/jni-remapping.cc | 98 +++++ src/monodroid/jni/jni-remapping.hh | 21 + src/monodroid/jni/mono_android_Runtime.h | 4 +- src/monodroid/jni/monodroid-glue-designer.cc | 2 +- src/monodroid/jni/monodroid-glue-internal.hh | 12 +- src/monodroid/jni/monodroid-glue.cc | 29 +- src/monodroid/jni/pinvoke-override-api.cc | 13 + src/monodroid/jni/pinvoke-tables.include | 6 +- src/monodroid/jni/xamarin-app.hh | 40 ++ .../remap-mam-json-to-xml}/MamXmlParser.cs | 0 .../remap-mam-json-to-xml.csproj | 2 +- 33 files changed, 1037 insertions(+), 229 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs create mode 100644 src/monodroid/jni/jni-remapping.cc create mode 100644 src/monodroid/jni/jni-remapping.hh rename {src/Mono.Android/Android.Runtime => tools/remap-mam-json-to-xml}/MamXmlParser.cs (100%) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 1530b967188..aaa8823deed 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -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) @@ -317,52 +324,43 @@ protected override IEnumerable 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 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(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); } @@ -370,70 +368,13 @@ protected override IEnumerable GetSimpleReferences (Type type) 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 GetNextString (ref ReadOnlySpan 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 (); diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index db8da3d0f37..36c2e476340 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -15,11 +15,6 @@ using Java.Interop.Tools.TypeNameMappings; using System.Diagnostics.CodeAnalysis; -#if NET -using ReplacementTypesDict = System.Collections.Generic.Dictionary; -using ReplacementMethodsDict = System.Collections.Generic.Dictionary; -#endif // NET - namespace Android.Runtime { #pragma warning disable 0649 struct JnienvInitializeArgs { @@ -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 @@ -55,6 +49,7 @@ public static partial class JNIEnv { static int androidSdkVersion; static bool AllocObjectSupported; + internal static bool jniRemappingInUse; static IntPtr grefIGCUserPeer_class; @@ -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; @@ -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; @@ -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 diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 0722c156bf3..cda6e0a5f5f 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -253,7 +253,6 @@ - diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs new file mode 100644 index 00000000000..2c22150f41e --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs @@ -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 (); + var methodReplacements = new List (); + + 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 ``"); + } 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 typeReplacements, List 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; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 57f3805d4ee..5b67610cb6e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -410,6 +410,7 @@ void AddEnvironment () bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath); var appConfState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ApplicationConfigTaskState.RegisterTaskObjectKey, RegisteredTaskObjectLifetime.Build); + var jniRemappingNativeCodeInfo = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (GenerateJniRemappingNativeCode.JniRemappingNativeCodeInfoKey, RegisteredTaskObjectLifetime.Build); var appConfigAsmGen = new ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) { IsBundledApp = IsBundledApplication, UsesMonoAOT = usesMonoAOT, @@ -436,6 +437,8 @@ void AddEnvironment () AndroidRuntimeJNIEnvToken = android_runtime_jnienv_class_token, JNIEnvInitializeToken = jnienv_initialize_method_token, JNIEnvRegisterJniNativesToken = jnienv_registerjninatives_method_token, + JniRemappingReplacementTypeCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementTypeCount, + JniRemappingReplacementMethodIndexEntryCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementMethodIndexEntryCount, }; appConfigAsmGen.Init (); #if ENABLE_MARSHAL_METHODS diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs index 9845aaa1ef4..62ccbf3bfbc 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs @@ -14,9 +14,12 @@ public class PrepareAbiItems : AndroidTask const string TypeMapBase = "typemaps"; const string EnvBase = "environment"; const string CompressedAssembliesBase = "compressed_assemblies"; + const string JniRemappingBase = "jni_remap"; + #if ENABLE_MARSHAL_METHODS const string MarshalMethodsBase = "marshal_methods"; #endif + public override string TaskPrefix => "PAI"; [Required] @@ -52,6 +55,8 @@ public override bool RunTask () baseName = EnvBase; } else if (String.Compare ("compressed", Mode, StringComparison.OrdinalIgnoreCase) == 0) { baseName = CompressedAssembliesBase; + } else if (String.Compare ("jniremap", Mode, StringComparison.OrdinalIgnoreCase) == 0) { + baseName = JniRemappingBase; #if ENABLE_MARSHAL_METHODS } else if (String.Compare ("marshal_methods", Mode, StringComparison.OrdinalIgnoreCase) == 0) { baseName = MarshalMethodsBase; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs index 308617c3f4e..a797e2baa77 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs @@ -88,21 +88,10 @@ public void CheckIncludedAssemblies ([Values (false, true)] bool usesAssemblySto "Java.Interop.dll", "Mono.Android.dll", "rc.bin", - "System.Collections.dll", - "System.Collections.Concurrent.dll", - "System.Collections.NonGeneric.dll", "System.Console.dll", - "System.IO.Compression.dll", - "System.Net.Http.dll", - "System.Net.Primitives.dll", - "System.Net.Requests.dll", "System.Private.CoreLib.dll", "System.Runtime.dll", "System.Linq.dll", - "System.Private.Uri.dll", - "System.Private.Xml.dll", - "System.Security.Cryptography.dll", - "System.Text.RegularExpressions.dll", "UnnamedProject.dll", } : new [] { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index 41cb435ba2f..32a2a3637fa 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -60,10 +60,13 @@ public sealed class ApplicationConfig public uint android_runtime_jnienv_class_token; public uint jnienv_initialize_method_token; public uint jnienv_registerjninatives_method_token; + public uint jni_remapping_replacement_type_count; + public uint jni_remapping_replacement_method_index_entry_count; public uint mono_components_mask; - public string android_package_name; - }; - const uint ApplicationConfigFieldCount = 23; + public string android_package_name = String.Empty; + } + + const uint ApplicationConfigFieldCount = 25; const string ApplicationConfigSymbolName = "application_config"; const string AppEnvironmentVariablesSymbolName = "app_environment_variables"; @@ -307,12 +310,22 @@ static ApplicationConfig ReadApplicationConfig (EnvironmentFile envFile) ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("jnienv_registerjninatives_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 21: // mono_components_mask: uint32_t / .word | .long + case 21: // jni_remapping_replacement_type_count: uint32_t / .word | .long + Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); + ret.jni_remapping_replacement_type_count = ConvertFieldToUInt32 ("jni_remapping_replacement_type_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); + break; + + case 22: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long + Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); + ret.jni_remapping_replacement_method_index_entry_count = ConvertFieldToUInt32 ("jni_remapping_replacement_method_index_entry_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); + break; + + case 23: // mono_components_mask: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 22: // android_package_name: string / [pointer type] + case 24: // android_package_name: string / [pointer type] Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); pointers.Add (field [1].Trim ()); break; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs index e9bbe2ddae2..369bc794b50 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs @@ -45,6 +45,8 @@ sealed class ApplicationConfig public uint android_runtime_jnienv_class_token; public uint jnienv_initialize_method_token; public uint jnienv_registerjninatives_method_token; + public uint jni_remapping_replacement_type_count; + public uint jni_remapping_replacement_method_index_entry_count; public uint mono_components_mask; public string android_package_name = String.Empty; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index a8f24dd85a5..979d705c162 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -168,6 +168,8 @@ sealed class XamarinAndroidBundledAssembly public int AndroidRuntimeJNIEnvToken { get; set; } public int JNIEnvInitializeToken { get; set; } public int JNIEnvRegisterJniNativesToken { get; set; } + public int JniRemappingReplacementTypeCount { get; set; } + public int JniRemappingReplacementMethodIndexEntryCount { get; set; } public MonoComponent MonoComponents { get; set; } public PackageNamingPolicy PackageNamingPolicy { get; set; } public List NativeLibraries { get; set; } @@ -210,6 +212,8 @@ public override void Init () android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken, jnienv_initialize_method_token = (uint)JNIEnvInitializeToken, jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken, + jni_remapping_replacement_type_count = (uint)JniRemappingReplacementTypeCount, + jni_remapping_replacement_method_index_entry_count = (uint)JniRemappingReplacementMethodIndexEntryCount, mono_components_mask = (uint)MonoComponents, android_package_name = AndroidPackageName, }; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs new file mode 100644 index 00000000000..486fd2f4c7e --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JniRemappingAssemblyGenerator.cs @@ -0,0 +1,365 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Xamarin.Android.Tasks.LLVMIR; + +namespace Xamarin.Android.Tasks +{ + sealed class JniRemappingTypeReplacement + { + public string From { get; } + public string To { get; } + + public JniRemappingTypeReplacement (string from, string to) + { + From = from; + To = to; + } + } + + sealed class JniRemappingMethodReplacement + { + public string SourceType { get; } + public string SourceMethod { get; } + public string SourceMethodSignature { get; } + + public string TargetType { get; } + public string TargetMethod { get; } + + public bool TargetIsStatic { get; } + + public JniRemappingMethodReplacement (string sourceType, string sourceMethod, string sourceMethodSignature, + string targetType, string targetMethod, bool targetIsStatic) + { + SourceType = sourceType; + SourceMethod = sourceMethod; + SourceMethodSignature = sourceMethodSignature; + + TargetType = targetType; + TargetMethod = targetMethod; + TargetIsStatic = targetIsStatic; + } + } + + class JniRemappingAssemblyGenerator : LlvmIrComposer + { + sealed class JniRemappingTypeReplacementEntryContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var entry = EnsureType(data); + + if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { + return $"name: {entry.name.str}"; + } + + if (String.Compare ("replacement", fieldName, StringComparison.Ordinal) == 0) { + return $"replacement: {entry.replacement}"; + } + + return String.Empty; + } + } + + sealed class JniRemappingIndexTypeEntryContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var entry = EnsureType (data); + + if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { + return $"name: {entry.name.str}"; + } + + return String.Empty; + } + + public override string GetPointedToSymbolName (object data, string fieldName) + { + var entry = EnsureType (data); + + if (String.Compare ("methods", fieldName, StringComparison.Ordinal) == 0) { + return entry.MethodsArraySymbolName; + } + + return base.GetPointedToSymbolName (data, fieldName); + } + + public override ulong GetBufferSize (object data, string fieldName) + { + var entry = EnsureType (data); + if (String.Compare ("methods", fieldName, StringComparison.Ordinal) == 0) { + return (ulong)entry.TypeMethods.Count; + } + + return 0; + } + } + + sealed class JniRemappingIndexMethodEntryContextDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var entry = EnsureType (data); + + if (String.Compare ("name", fieldName, StringComparison.Ordinal) == 0) { + return $"name: {entry.name.str}"; + } + + if (String.Compare ("replacement", fieldName, StringComparison.Ordinal) == 0) { + return $"replacement: {entry.replacement.target_type}.{entry.replacement.target_name}"; + } + + if (String.Compare ("signature", fieldName, StringComparison.Ordinal) == 0) { + if (entry.signature.length == 0) { + return String.Empty; + } + + return $"signature: {entry.signature.str}"; + } + + return String.Empty; + } + } + + sealed class JniRemappingString + { + public uint length; + public string str; + }; + + sealed class JniRemappingReplacementMethod + { + public string target_type; + public string target_name; + public bool is_static; + }; + + [NativeAssemblerStructContextDataProvider (typeof(JniRemappingIndexMethodEntryContextDataProvider))] + sealed class JniRemappingIndexMethodEntry + { + [NativeAssembler (UsesDataProvider = true)] + public JniRemappingString name; + + [NativeAssembler (UsesDataProvider = true)] + public JniRemappingString signature; + + [NativeAssembler (UsesDataProvider = true)] + public JniRemappingReplacementMethod replacement; + }; + + [NativeAssemblerStructContextDataProvider (typeof(JniRemappingIndexTypeEntryContextDataProvider))] + sealed class JniRemappingIndexTypeEntry + { + [NativeAssembler (UsesDataProvider = true)] + public JniRemappingString name; + public uint method_count; + + [NativeAssembler (UsesDataProvider = true), NativePointer (PointsToSymbol = "")] + public JniRemappingIndexMethodEntry methods; + + [NativeAssembler (Ignore = true)] + public string MethodsArraySymbolName; + + [NativeAssembler (Ignore = true)] + public List> TypeMethods; + }; + + [NativeAssemblerStructContextDataProvider (typeof(JniRemappingTypeReplacementEntryContextDataProvider))] + sealed class JniRemappingTypeReplacementEntry + { + [NativeAssembler (UsesDataProvider = true)] + public JniRemappingString name; + + [NativeAssembler (UsesDataProvider = true)] + public string replacement; + }; + + List typeReplacementsInput; + List methodReplacementsInput; + + StructureInfo jniRemappingStringStructureInfo; + StructureInfo jniRemappingReplacementMethodStructureInfo; + StructureInfo jniRemappingIndexMethodEntryStructureInfo; + StructureInfo jniRemappingIndexTypeEntryStructureInfo; + StructureInfo jniRemappingTypeReplacementEntryStructureInfo; + + List> typeReplacements; + List> methodIndexTypes; + + public int ReplacementMethodIndexEntryCount { get; private set; } = 0; + + public JniRemappingAssemblyGenerator () + {} + + public JniRemappingAssemblyGenerator (List typeReplacements, List methodReplacements) + { + this.typeReplacementsInput = typeReplacements ?? throw new ArgumentNullException (nameof (typeReplacements)); + this.methodReplacementsInput = methodReplacements ?? throw new ArgumentNullException (nameof (methodReplacements)); + } + + public override void Init () + { + if (typeReplacementsInput == null) { + return; + } + + typeReplacements = new List> (); + foreach (JniRemappingTypeReplacement mtr in typeReplacementsInput) { + var entry = new JniRemappingTypeReplacementEntry { + name = MakeJniRemappingString (mtr.From), + replacement = mtr.To, + }; + + typeReplacements.Add (new StructureInstance (entry)); + } + typeReplacements.Sort ((StructureInstance l, StructureInstance r) => l.Obj.name.str.CompareTo (r.Obj.name.str)); + + methodIndexTypes = new List> (); + var types = new Dictionary> (StringComparer.Ordinal); + + foreach (JniRemappingMethodReplacement mmr in methodReplacementsInput) { + if (!types.TryGetValue (mmr.SourceType, out StructureInstance typeEntry)) { + var entry = new JniRemappingIndexTypeEntry { + name = MakeJniRemappingString (mmr.SourceType), + MethodsArraySymbolName = MakeMethodsArrayName (mmr.SourceType), + TypeMethods = new List> (), + }; + + typeEntry = new StructureInstance (entry); + methodIndexTypes.Add (typeEntry); + types.Add (mmr.SourceType, typeEntry); + } + + var method = new JniRemappingIndexMethodEntry { + name = MakeJniRemappingString (mmr.SourceMethod), + signature = MakeJniRemappingString (mmr.SourceMethodSignature), + replacement = new JniRemappingReplacementMethod { + target_type = mmr.TargetType, + target_name = mmr.TargetMethod, + is_static = mmr.TargetIsStatic, + }, + }; + + typeEntry.Obj.TypeMethods.Add (new StructureInstance (method)); + } + + foreach (var kvp in types) { + kvp.Value.Obj.method_count = (uint)kvp.Value.Obj.TypeMethods.Count; + kvp.Value.Obj.TypeMethods.Sort ((StructureInstance l, StructureInstance r) => l.Obj.name.str.CompareTo (r.Obj.name.str)); + } + + methodIndexTypes.Sort ((StructureInstance l, StructureInstance r) => l.Obj.name.str.CompareTo (r.Obj.name.str)); + ReplacementMethodIndexEntryCount = methodIndexTypes.Count; + + string MakeMethodsArrayName (string typeName) + { + return $"mm_{typeName.Replace ('/', '_')}"; + } + + JniRemappingString MakeJniRemappingString (string str) + { + return new JniRemappingString { + length = GetLength (str), + str = str, + }; + } + } + + uint GetLength (string str) + { + if (String.IsNullOrEmpty (str)) { + return 0; + } + + return (uint)Encoding.UTF8.GetBytes (str).Length; + } + + protected override void MapStructures (LlvmIrGenerator generator) + { + jniRemappingStringStructureInfo = generator.MapStructure (); + jniRemappingReplacementMethodStructureInfo = generator.MapStructure (); + jniRemappingIndexMethodEntryStructureInfo = generator.MapStructure (); + jniRemappingIndexTypeEntryStructureInfo = generator.MapStructure (); + jniRemappingTypeReplacementEntryStructureInfo = generator.MapStructure (); + } + + void WriteNestedStructure (LlvmIrGenerator generator, LlvmIrGenerator.StructureBodyWriterOptions bodyWriterOptions, Type structureType, object fieldInstance) + { + if (fieldInstance == null) { + return; + } + + if (structureType == typeof (JniRemappingString)) { + generator.WriteNestedStructure (jniRemappingStringStructureInfo, new StructureInstance ((JniRemappingString)fieldInstance), bodyWriterOptions); + return; + } + + if (structureType == typeof (JniRemappingReplacementMethod)) { + generator.WriteNestedStructure (jniRemappingReplacementMethodStructureInfo, new StructureInstance ((JniRemappingReplacementMethod)fieldInstance), bodyWriterOptions); + return; + } + + if (structureType == typeof (JniRemappingIndexTypeEntry)) { + generator.WriteNestedStructure (jniRemappingIndexTypeEntryStructureInfo, new StructureInstance ((JniRemappingIndexTypeEntry)fieldInstance), bodyWriterOptions); + } + + if (structureType == typeof (JniRemappingIndexMethodEntry)) { + generator.WriteNestedStructure (jniRemappingIndexMethodEntryStructureInfo, new StructureInstance ((JniRemappingIndexMethodEntry)fieldInstance), bodyWriterOptions); + } + + throw new InvalidOperationException ($"Unsupported nested structure type {structureType}"); + } + + protected override void Write (LlvmIrGenerator generator) + { + generator.WriteEOL (); + generator.WriteEOL ("JNI remapping data"); + + if (typeReplacements == null) { + generator.WriteStructureArray ( + jniRemappingTypeReplacementEntryStructureInfo, + 0, + LlvmIrVariableOptions.GlobalConstant, + "jni_remapping_type_replacements" + ); + + generator.WriteStructureArray ( + jniRemappingIndexTypeEntryStructureInfo, + 0, + LlvmIrVariableOptions.GlobalConstant, + "jni_remapping_method_replacement_index" + ); + + return; + } + + generator.WriteStructureArray ( + jniRemappingTypeReplacementEntryStructureInfo, + typeReplacements, + LlvmIrVariableOptions.GlobalConstant, + "jni_remapping_type_replacements", + nestedStructureWriter: WriteNestedStructure + ); + + foreach (StructureInstance entry in methodIndexTypes) { + generator.WriteStructureArray ( + jniRemappingIndexMethodEntryStructureInfo, + entry.Obj.TypeMethods, + LlvmIrVariableOptions.LocalConstant, + entry.Obj.MethodsArraySymbolName, + nestedStructureWriter: WriteNestedStructure + ); + } + + generator.WriteStructureArray ( + jniRemappingIndexTypeEntryStructureInfo, + methodIndexTypes, + LlvmIrVariableOptions.GlobalConstant, + "jni_remapping_method_replacement_index", + nestedStructureWriter: WriteNestedStructure + ); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs index 1bead28a005..b083e4e907c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs @@ -1,7 +1,13 @@ +using System; + namespace Xamarin.Android.Tasks.LLVMIR { interface IStructureInfo { + Type Type { get; } + ulong Size { get; } + int MaxFieldAlignment { get; } + void RenderDeclaration (LlvmIrGenerator generator); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index c639fb1742d..88dd83860f5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -12,7 +12,7 @@ namespace Xamarin.Android.Tasks.LLVMIR /// abstract class LlvmIrGenerator { - ref struct StructureBodyWriterOptions + internal sealed class StructureBodyWriterOptions { public readonly bool WriteFieldComment; public readonly string FieldIndent; @@ -319,6 +319,26 @@ public StructureInfo MapStructure () return ret; } + internal IStructureInfo GetStructureInfo (Type type) + { + IStructureInfo? ret = null; + + foreach (IStructureInfo si in structures) { + if (si.Type != type) { + continue; + } + + ret = si; + break; + } + + if (ret == null) { + throw new InvalidOperationException ($"Unmapped structure {type}"); + } + + return ret; + } + TextWriter EnsureOutput (TextWriter? output) { return output ?? Output; @@ -460,7 +480,9 @@ public void WriteStructureArray (StructureInfo info, ulong count, string? /// Writes an array of managed type , with data optionally specified in (if it's null, the array /// will be zero-initialized). specifies the symbol attributes (visibility, writeability etc) /// - public void WriteStructureArray (StructureInfo info, IList>? instances, LlvmIrVariableOptions options, string? symbolName = null, bool writeFieldComment = true, string? initialComment = null) + public void WriteStructureArray (StructureInfo info, IList>? instances, LlvmIrVariableOptions options, + string? symbolName = null, bool writeFieldComment = true, string? initialComment = null, + Action? nestedStructureWriter = null) { var arrayOutput = new StringWriter (); bool named = WriteStructureArrayStart (info, instances, options, symbolName, initialComment, arrayOutput); @@ -482,7 +504,7 @@ public void WriteStructureArray (StructureInfo info, IList instance = instances[i]; arrayOutput.WriteLine ($"{Indent}; {i}"); - WriteStructureBody (info, instance, bodyWriterOptions); + WriteStructureBody (info, instance, bodyWriterOptions, nestedStructureWriter); if (i < count - 1) { arrayOutput.Write (", "); } @@ -606,19 +628,26 @@ void MaybeWriteStructureStringsAndBuffers (StructureInfo info, StructureMe } } - void WriteStructureField (StructureInfo info, StructureInstance instance, StructureMemberInfo smi, int fieldIndex, StructureBodyWriterOptions options, TextWriter output, object? valueOverride = null, ulong? expectedArraySize = null) + void WriteStructureField (StructureInfo info, StructureInstance instance, StructureMemberInfo smi, int fieldIndex, + StructureBodyWriterOptions options, TextWriter output, object? valueOverride = null, ulong? expectedArraySize = null, + Action? nestedStructureWriter = null) { - output.Write (options.FieldIndent); - object? value = null; - if (smi.IsNativePointer) { + + if (smi.IsIRStruct ()) { + if (nestedStructureWriter == null) { + throw new InvalidOperationException ($"Nested structure found in type {typeof(T)}, field {smi.Info.Name} but no nested structure writer provided"); + } + nestedStructureWriter (this, options, smi.MemberType, valueOverride ?? GetTypedMemberValue (info, smi, instance, smi.MemberType)); + } else if (smi.IsNativePointer) { + output.Write (options.FieldIndent); WritePointer (info, smi, instance, output); } else if (smi.IsNativeArray) { if (!smi.IsInlineArray) { throw new InvalidOperationException ($"Out of line arrays aren't supported at this time (structure '{info.Name}', field '{smi.Info.Name}')"); } - output.Write ($"{smi.IRType} "); + output.Write ($"{options.FieldIndent}{smi.IRType} "); value = valueOverride ?? GetTypedMemberValue (info, smi, instance, smi.MemberType); if (smi.MemberType == typeof(byte[])) { @@ -628,13 +657,14 @@ void WriteStructureField (StructureInfo info, StructureInstance instanc } } else { value = valueOverride; + output.Write (options.FieldIndent); WritePrimitiveField (info, smi, instance, output); } FinishStructureField (info, smi, instance, options, fieldIndex, value, output); } - void WriteStructureBody (StructureInfo info, StructureInstance? instance, StructureBodyWriterOptions options) + void WriteStructureBody (StructureInfo info, StructureInstance? instance, StructureBodyWriterOptions options, Action? nestedStructureWriter = null) { TextWriter structureOutput = EnsureOutput (options.StructureOutput); structureOutput.Write ($"{options.StructIndent}%struct.{info.Name} "); @@ -645,7 +675,7 @@ void WriteStructureBody (StructureInfo info, StructureInstance? instanc StructureMemberInfo smi = info.Members[i]; MaybeWriteStructureStringsAndBuffers (info, smi, instance, options); - WriteStructureField (info, instance, smi, i, options, structureOutput); + WriteStructureField (info, instance, smi, i, options, structureOutput, nestedStructureWriter: nestedStructureWriter); } structureOutput.Write ($"{options.StructIndent}}}"); @@ -737,7 +767,7 @@ void WritePointer (StructureInfo info, StructureMemberInfo smi, Structu } } - throw new InvalidOperationException ($"Non-null pointers to objects of managed type '{smi.Info.MemberType}' (IR type '{smi.IRType}') currently not supported (value: {value})"); + throw new InvalidOperationException ($"While processing field '{smi.Info.Name}' of type '{info.Name}': non-null pointers to objects of managed type '{smi.MemberType}' (IR type '{smi.IRType}') currently not supported (value: {value})"); } void WriteNullPointer (StructureMemberInfo smi, TextWriter output) @@ -913,6 +943,25 @@ void FinishStructureWrite (StructureInfo info, StructureBodyWriterOptions WriteBufferToOutput (bodyWriterOptions.StructureOutput); } + public void WriteStructure (StructureInfo info, StructureInstance? instance, StructureBodyWriterOptions bodyWriterOptions, LlvmIrVariableOptions options, string? symbolName = null, bool writeFieldComment = true) + { + WriteStructureBody (info, instance, bodyWriterOptions); + FinishStructureWrite (info, bodyWriterOptions); + } + + public void WriteNestedStructure (StructureInfo info, StructureInstance instance, StructureBodyWriterOptions bodyWriterOptions) + { + var options = new StructureBodyWriterOptions ( + bodyWriterOptions.WriteFieldComment, + bodyWriterOptions.FieldIndent + Indent, + bodyWriterOptions.FieldIndent, // structure indent should start at the original struct's field column + bodyWriterOptions.StructureOutput, + bodyWriterOptions.StringsOutput, + bodyWriterOptions.BuffersOutput + ); + WriteStructureBody (info, instance, options); + } + /// /// Write a structure represented by managed type , with optional data passed in (if null, the structure /// is zero-initialized). specifies the symbol attributes (visibility, writeability etc) @@ -920,10 +969,7 @@ void FinishStructureWrite (StructureInfo info, StructureBodyWriterOptions public void WriteStructure (StructureInfo info, StructureInstance? instance, LlvmIrVariableOptions options, string? symbolName = null, bool writeFieldComment = true) { StructureBodyWriterOptions bodyWriterOptions = InitStructureWrite (info, options, symbolName, writeFieldComment); - - WriteStructureBody (info, instance, bodyWriterOptions); - - FinishStructureWrite (info, bodyWriterOptions); + WriteStructure (info, instance, bodyWriterOptions, options, symbolName, writeFieldComment); } /// diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs index a8ddbfdbac1..02aefd3070d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs @@ -8,6 +8,9 @@ namespace Xamarin.Android.Tasks.LLVMIR // TODO: add cache for members and data provider info sealed class StructureInfo : IStructureInfo { + Type type; + + public Type Type => type; public string Name { get; } = String.Empty; public ulong Size { get; } public List> Members { get; } = new List> (); @@ -20,10 +23,10 @@ sealed class StructureInfo : IStructureInfo public StructureInfo (LlvmIrGenerator generator) { - Type t = typeof(T); - Name = t.GetShortName (); - Size = GatherMembers (t, generator); - DataProvider = t.GetDataProvider (); + type = typeof(T); + Name = type.GetShortName (); + Size = GatherMembers (type, generator); + DataProvider = type.GetDataProvider (); } public void RenderDeclaration (LlvmIrGenerator generator) @@ -80,7 +83,7 @@ public ulong GetBufferSizeFromProvider (StructureMemberInfo smi, StructureIns return DataProvider.GetBufferSize (instance.Obj, smi.Info.Name); } - ulong GatherMembers (Type type, LlvmIrGenerator generator) + ulong GatherMembers (Type type, LlvmIrGenerator generator, bool storeMembers = true) { ulong size = 0; foreach (MemberInfo mi in type.GetMembers ()) { @@ -89,10 +92,13 @@ ulong GatherMembers (Type type, LlvmIrGenerator generator) } var info = new StructureMemberInfo (mi, generator); - Members.Add (info); - size += info.Size; - if ((int)info.Size > MaxFieldAlignment) { - MaxFieldAlignment = (int)info.Size; + if (storeMembers) { + Members.Add (info); + size += info.Size; + + if ((int)info.Alignment > MaxFieldAlignment) { + MaxFieldAlignment = (int)info.Alignment; + } } if (!HasStrings && info.MemberType == typeof (string)) { @@ -102,6 +108,14 @@ ulong GatherMembers (Type type, LlvmIrGenerator generator) if (!HasPreAllocatedBuffers && info.Info.IsNativePointerToPreallocatedBuffer (out ulong _)) { HasPreAllocatedBuffers = true; } + + // If we encounter an embedded struct (as opposed to a pointer), we need to descend and check if that struct contains any strings or buffers, but we + // do NOT want to store any members while doing that, as the struct should have been mapped by the composer previously. + // The presence of strings/buffers is important at the generation time as it is used to decide whether we need separate stream writers for them and + // if the owning structure does **not** have any of those, the generated code would be invalid + if (info.IsIRStruct ()) { + GatherMembers (info.MemberType, generator, storeMembers: false); + } } return size; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs index 57a2f65018d..c09d46910fe 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs @@ -11,10 +11,10 @@ sealed class StructureMemberInfo /// /// Size of a variable with this IR type. May differ from because the field - /// can be a pointer to type + /// can be a pointer to type or a struct /// public ulong Size { get; } - + public ulong Alignment { get; } public ulong ArrayElements { get; } /// @@ -57,6 +57,7 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) ArrayElements = 0; IsInlineArray = false; NeedsPadding = false; + Alignment = 0; if (IsNativePointer) { size = (ulong)generator.PointerSize; @@ -75,6 +76,10 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) IRType = $"[{arrayElements} x {IRType}]"; ArrayElements = (ulong)arrayElements; + } else if (this.IsIRStruct ()) { + IStructureInfo si = generator.GetStructureInfo (MemberType); + size = si.Size; + Alignment = (ulong)si.MaxFieldAlignment; } if (MemberType.IsArray && !IsInlineArray) { @@ -82,6 +87,9 @@ public StructureMemberInfo (MemberInfo mi, LlvmIrGenerator generator) } Size = size; + if (Alignment == 0) { + Alignment = size; + } } public object? GetValue (T instance) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs index 247554d32fb..def40e6c472 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs @@ -40,8 +40,22 @@ public static bool IsStructure (this Type type) return type.IsValueType && !type.IsEnum && !type.IsPrimitive && + !type.IsArray && type != typeof (decimal) && - type != typeof (DateTime); + type != typeof (DateTime) && + type != typeof (object); + } + + public static bool IsIRStruct (this StructureMemberInfo smi) + { + Type type = smi.MemberType; + + // type.IsStructure() handles checks for primitive types, enums etc + return + type != typeof(string) && + !smi.Info.IsInlineArray () && + !smi.Info.IsNativePointer () && + (type.IsStructure () || type.IsClass); } public static NativeAssemblerStructContextDataProvider? GetDataProvider (this Type t) diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index d58d740ee13..e32a7f80078 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -90,7 +90,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. - +