From c6e1ce24afaed240738d2ab2313464d5f8ed5f9b Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 16 Jul 2026 13:33:30 +0200 Subject: [PATCH 1/3] Remove the "managed" typemap implementation Now that NativeAOT defaults to the trimmable typemap (#12121), the reflection-based "managed" typemap is unused. Remove all traces of it: * Runtime (src/Mono.Android): delete ManagedTypeManager and ManagedTypeMapping, remove the RuntimeFeature.ManagedTypeMap feature switch, and drop the managed branches in JNIEnvInit.CreateTypeManager, JNIEnv.ArrayCreateInstance, and JavaConvert. * NativeAOT host: JreRuntime.CreateDefaultTypeManager always returns TrimmableTypeMapTypeManager. * Generator / assembly rewriting: delete the ILLink TypeMappingStep and its registration in Microsoft.Android.Sdk.TypeMap.LlvmIr.targets. * MSBuild/SDK: remove _AndroidUseManagedTypeMap and the ManagedTypeMap RuntimeHostConfigurationOption, drop "managed" from the _AndroidTypeMapImplementation validation, and remove the now-dead non-trimmable NativeAOT scaffolding in Microsoft.Android.Sdk.NativeAOT.targets. * Tests/docs: remove "managed" test cases and update documentation to list only llvm-ir and trimmable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 40c65ba4-c9e2-442f-bc68-0bd274f0bf8c --- .github/copilot-instructions.md | 4 +- .../Java.Interop/JreRuntime.cs | 10 +- .../TypeMappingStep.cs | 422 ------------------ src/Mono.Android/Android.Runtime/JNIEnv.cs | 11 - .../Android.Runtime/JNIEnvInit.cs | 8 - src/Mono.Android/Java.Interop/JavaConvert.cs | 9 - .../ManagedTypeManager.cs | 188 -------- .../ManagedTypeMapping.cs | 106 ----- .../RuntimeFeature.cs | 5 - src/Mono.Android/Mono.Android.csproj | 2 - .../Microsoft.Android.Sdk.NativeAOT.targets | 54 +-- ...icrosoft.Android.Sdk.RuntimeConfig.targets | 5 - ...crosoft.Android.Sdk.TypeMap.LlvmIr.targets | 6 - .../Tasks/GenerateTypeMappings.cs | 4 +- .../Xamarin.Android.Build.Tests/BuildTest2.cs | 127 ------ .../Xamarin.Android.Common.targets | 4 +- .../Tests/InstallAndRunTests.cs | 3 - 17 files changed, 9 insertions(+), 959 deletions(-) delete mode 100644 src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs delete mode 100644 src/Mono.Android/Microsoft.Android.Runtime/ManagedTypeManager.cs delete mode 100644 src/Mono.Android/Microsoft.Android.Runtime/ManagedTypeMapping.cs diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 14000ed8eb8..17f39f2bb10 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -219,13 +219,13 @@ When diagnosing runtime, build, or test failures, follow these practices. They e make prepare && make all CONFIGURATION=Release ./dotnet-local.sh build -t:Install -c Release \ tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \ - -p:_AndroidTypeMapImplementation= \ + -p:_AndroidTypeMapImplementation= \ -p:UseMonoRuntime= ( cd tests/Mono.Android-Tests/Mono.Android-Tests ../../../dotnet-local.sh test Mono.Android.NET-Tests.csproj --no-build -c Release \ --report-trx --results-directory ../../../bin/TestRelease/TestResults \ - -p:_AndroidTypeMapImplementation= \ + -p:_AndroidTypeMapImplementation= \ -p:UseMonoRuntime= ) ``` diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs index 04163ac243f..b10ffc1bca9 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs @@ -77,15 +77,7 @@ internal protected JreRuntime (NativeAotRuntimeOptions builder) static JniRuntime.JniTypeManager CreateDefaultTypeManager () { - if (RuntimeFeature.TrimmableTypeMap) { - return new TrimmableTypeMapTypeManager (); - } - - return CreateManagedTypeManager (); - - [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Managed type manager is preserved by the MarkJavaObjects trimmer step.")] - [UnconditionalSuppressMessage ("Trimming", "IL3050", Justification = "This type manager won't be used in Native AOT builds in the future.")] - static JniRuntime.JniTypeManager CreateManagedTypeManager () => new ManagedTypeManager (); + return new TrimmableTypeMapTypeManager (); } [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "CoreCLR value manager is preserved by the MarkJavaObjects trimmer step.")] diff --git a/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs b/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs deleted file mode 100644 index 0f775f4d87c..00000000000 --- a/src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs +++ /dev/null @@ -1,422 +0,0 @@ -using System; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using Java.Interop.Tools.Cecil; -using Java.Interop.Tools.TypeNameMappings; -using Mono.Cecil; -using Mono.Cecil.Cil; -using Mono.Linker; -using Mono.Linker.Steps; - -namespace Microsoft.Android.Sdk.ILLink; - -/// -/// MVP "typemap" implementation for NativeAOT -/// -public class TypeMappingStep : BaseStep -{ - const string AssemblyName = "Mono.Android"; - const string TypeName = "Microsoft.Android.Runtime.ManagedTypeMapping"; - const string SystemIOHashingAssemblyPathCustomData = "SystemIOHashingAssemblyPath"; - readonly IDictionary> TypeMappings = new Dictionary> (StringComparer.Ordinal); - AssemblyDefinition? MonoAndroidAssembly; - - delegate ulong HashMethod (ReadOnlySpan data, long seed = 0); - HashMethod? _hashMethod; - - protected override void Process () - { - _hashMethod = LoadHashMethod (); - } - - protected override void ProcessAssembly (AssemblyDefinition assembly) - { - if (assembly.Name.Name == AssemblyName) { - MonoAndroidAssembly = assembly; - } - if (Annotations?.GetAction (assembly) == AssemblyAction.Delete) - return; - - foreach (var type in assembly.MainModule.Types) { - ProcessType (assembly, type); - } - } - - protected override void EndProcess () - { - Context.LogMessage ($"Writing {TypeMappings.Count} typemap entries"); - - if (MonoAndroidAssembly is null) { - throw new InvalidOperationException ($"Unable to find {AssemblyName} assembly"); - } - - var module = MonoAndroidAssembly.MainModule; - var type = module.GetType (TypeName); - if (type is null) { - throw new InvalidOperationException ($"Unable to find {TypeName} type"); - } - - var typeMappingRecords = TypeMappings - .Select (kvp => - new TypeMapRecord { - JniName = kvp.Key, - Types = kvp.Value.ToArray (), - Context = Context, - }) - .ToArray (); - - // Java -> .NET mapping - { - var orderedJavaToDotnetMapping = typeMappingRecords.OrderBy (record => Hash (record.JniName)) - .ToArray (); - var jniNames = orderedJavaToDotnetMapping.Select (record => record.JniName) - .ToArray (); - var jniNameHashes = jniNames.Select (Hash) - .ToArray (); - var types = orderedJavaToDotnetMapping.Select (record => record.SelectTypeDefinition ()) - .ToArray (); - - Context.LogMessage ("JNI -> .NET mappings"); - Context.LogMessage ($"Generated field {type.FullName}.s_get_JniNameHashes_data contains {jniNameHashes.Length} hashes:"); - for (int i = 0; i < jniNameHashes.Length; ++i ) { - var java = jniNames [i]; - var hash = jniNameHashes [i]; - Context.LogMessage ($"\t0x{hash.ToString ("x", System.Globalization.CultureInfo.InvariantCulture), -16} // {i,4}: {java}"); - } - Context.LogMessage ($"Generated method {type.FullName}.GetTypeByJniNameHashIndex contains {types.Length} mappings:"); - var maxAqtnLength = types.Max (t => TypeDefinitionRocks.GetAssemblyQualifiedName (t, Context).Length)+2; - for (int i = 0; i < types.Length; ++i ) { - var aqtn = TypeDefinitionRocks.GetAssemblyQualifiedName (types [i], Context); - var java = jniNames [i]; - var hash = jniNameHashes [i]; - Context.LogMessage ( - string.Format (System.Globalization.CultureInfo.InvariantCulture, - "\tindex {0,4} => Type.GetType({1,-" + maxAqtnLength + "}), // `{2}` hash=0x{3:x16}", i, $"\"{aqtn}\"", java, hash)); - } - Context.LogMessage ($"Generated method {type.FullName}.GetJniNameByJniNameHashIndex contains {jniNames.Length} mappings:"); - var maxJavaLength = jniNames.Max (s => s.Length)+2; - for (int i = 0; i < jniNames.Length; ++i ) { - var java = jniNames [i]; - var aqtn = TypeDefinitionRocks.GetAssemblyQualifiedName (types [i], Context); - var hash = jniNameHashes [i]; - Context.LogMessage ( - string.Format (System.Globalization.CultureInfo.InvariantCulture, - "\tindex {0,4} => {1,-" + maxJavaLength + "}, // `{2}` hash=0x{3:x16}", i, $"\"{java}\"", aqtn, hash)); - } - - GenerateHashes (jniNameHashes, methodName: "get_JniNameHashes"); - GenerateGetTypeByJniNameHashIndex (types); - GenerateStringSwitchMethod (type, "GetJniNameByJniNameHashIndex", jniNames); - } - - // .NET -> Java mapping - { - var orderedManagedToJavaMapping = typeMappingRecords - .SelectMany (record => record.Flatten ()) - .OrderBy (record => Hash (record.TypeName)) - .ToArray (); - - var typeNames = orderedManagedToJavaMapping - .Select (record => record.TypeName) - .ToArray (); - var typeNameHashes = typeNames.Select (Hash) - .ToArray (); - var jniNames = orderedManagedToJavaMapping.Select (record => record.JniName) - .ToArray (); - - Context.LogMessage (".NET -> JNI mappings"); - Context.LogMessage ($"Generated field {type.FullName}.s_get_TypeNameHashes_data contains {typeNameHashes.Length} hashes:"); - for (int i = 0; i < typeNameHashes.Length; ++i ) { - var aqtn = typeNames [i]; - var hash = typeNameHashes [i]; - Context.LogMessage ($"\t0x{hash.ToString ("x", System.Globalization.CultureInfo.InvariantCulture), -16} // {i,4}: {aqtn}"); - } - var maxJavaLength = jniNames.Max (s => s.Length) + 2; - Context.LogMessage ($"Generated method {type.FullName}.GetJniNameByTypeNameHashIndex contains {jniNames.Length} mappings:"); - for (int i = 0; i < jniNames.Length; ++i ) { - var java = jniNames [i]; - var aqtn = typeNames [i]; - var hash = typeNameHashes [i]; - Context.LogMessage ( - string.Format (System.Globalization.CultureInfo.InvariantCulture, - "\tindex {0,4} => {1,-" + maxJavaLength + "}, // `{2}` hash=0x{3:x16}", i, $"\"{java}\"", aqtn, hash)); - } - Context.LogMessage ($"Generated method {type.FullName}.GetTypeNameByTypeNameHashIndex contains {typeNames.Length} mappings:"); - var maxAqtnLength = typeNames.Max (s => s.Length) + 2; - for (int i = 0; i < typeNames.Length; ++i ) { - var java = jniNames [i]; - var aqtn = typeNames [i]; - var hash = typeNameHashes [i]; - Context.LogMessage ( - string.Format (System.Globalization.CultureInfo.InvariantCulture, - "\tindex {0,4} => {1,-" + maxAqtnLength + "}, // `{2}` hash=0x{3:x16}", i, $"\"{aqtn}\"", java, hash)); - } - - GenerateHashes (typeNameHashes, methodName: "get_TypeNameHashes"); - GenerateStringSwitchMethod (type, "GetJniNameByTypeNameHashIndex", jniNames); - GenerateStringSwitchMethod (type, "GetTypeNameByTypeNameHashIndex", typeNames); - } - - void GenerateGetTypeByJniNameHashIndex (IEnumerable types) - { - var method = type.Methods.FirstOrDefault (m => m.Name == "GetTypeByJniNameHashIndex"); - if (method is null) { - throw new InvalidOperationException ($"Unable to find {TypeName}.GetTypeByJniNameHashIndex() method"); - } - - var getTypeFromHandle = module.ImportReference (typeof (Type).GetMethod ("GetTypeFromHandle")); - if (getTypeFromHandle is null) { - throw new InvalidOperationException ($"Unable to find Type.GetTypeFromHandle() method"); - } - - // Clear IL in method body - method.Body.Instructions.Clear (); - - var il = method.Body.GetILProcessor (); - - var targets = new List (); - foreach (var target in types) { - targets.Add (il.Create (OpCodes.Ldtoken, module.ImportReference (target))); - } - - il.Emit (OpCodes.Ldarg_0); - il.Emit (OpCodes.Switch, targets.ToArray ()); - - var defaultTarget = il.Create (OpCodes.Ldnull); - il.Emit (OpCodes.Br, defaultTarget); - - foreach (var target in targets) { - il.Append (target); - il.Emit (OpCodes.Call, getTypeFromHandle); - il.Emit (OpCodes.Ret); - } - - il.Append (defaultTarget); - il.Emit (OpCodes.Ret); - } - - void GenerateStringSwitchMethod (TypeDefinition type, string methodName, string[] values) - { - var method = type.Methods.FirstOrDefault (m => m.Name == methodName); - if (method is null) { - throw new InvalidOperationException ($"Unable to find {type.FullName}.{methodName} method"); - } - - // Clear IL in method body - method.Body.Instructions.Clear (); - - var il = method.Body.GetILProcessor (); - - var targets = new List (); - foreach (var value in values) { - targets.Add (il.Create (OpCodes.Ldstr, value)); - } - - il.Emit (OpCodes.Ldarg_0); - il.Emit (OpCodes.Switch, targets.ToArray ()); - - var defaultTarget = il.Create (OpCodes.Ldnull); - il.Emit (OpCodes.Br, defaultTarget); - - foreach (var target in targets) { - il.Append (target); - il.Emit (OpCodes.Ret); - } - - il.Append (defaultTarget); - il.Emit (OpCodes.Ret); - } - - void GenerateHashes (ulong[] hashes, string methodName) - { - // Sanity check: hashes must be unique and sorted - if (hashes.Length > 0) { - ulong previous = hashes[0]; - for (int i = 1; i < hashes.Length; ++i) { - if (hashes[i] == previous) { - throw new InvalidOperationException ($"Duplicate hashes"); - } else if (hashes[i] < previous) { - throw new InvalidOperationException ($"Hashes are not in ascending order"); - } - - previous = hashes[i]; - } - } - - GenerateReadOnlySpanGetter (type, methodName, hashes, sizeof (ulong), BitConverter.GetBytes); - } - - void GenerateReadOnlySpanGetter (TypeDefinition type, string name, T[] data, int sizeOfT, Func getBytes) - where T : struct - { - // Create static array struct for `byte[#data * sizeof(T)]` - var arrayType = GetArrayType (type, data.Length * sizeOfT); - - // Create static field to store the raw bytes - var bytesField = new FieldDefinition ($"s_{name}_data", FieldAttributes.Private | FieldAttributes.Static | FieldAttributes.InitOnly, arrayType); - bytesField.InitialValue = data.SelectMany (getBytes).ToArray (); - if (!bytesField.Attributes.HasFlag (FieldAttributes.HasFieldRVA)) { - throw new InvalidOperationException ($"Field {bytesField.Name} does not have RVA"); - } - - type.Fields.Add (bytesField); - - // Generate the Hashes getter - var getHashes = type.Methods.FirstOrDefault (f => f.Name == name) ?? throw new InvalidOperationException ($"Unable to find {TypeName}.{name} field"); - - getHashes.Body.Instructions.Clear (); - var il = getHashes.Body.GetILProcessor (); - - il.Emit (OpCodes.Ldsflda, bytesField); - il.Emit (OpCodes.Ldc_I4, data.Length); - il.Emit (OpCodes.Newobj, module.ImportReference (typeof (ReadOnlySpan).GetConstructor (new[] { typeof(void*), typeof(int) }))); - - il.Emit (OpCodes.Ret); - } - - TypeDefinition GetArrayType (TypeDefinition type, int size) - { - var hashesArrayName = $"HashesArray_{size}"; - var arrayType = type.NestedTypes.FirstOrDefault (td => td.Name == hashesArrayName); - if (arrayType is null) { - arrayType = new TypeDefinition ( - "", - hashesArrayName, - TypeAttributes.NestedPrivate | TypeAttributes.ExplicitLayout, - module.ImportReference (typeof (ValueType))) - { - PackingSize = 1, - ClassSize = size, - }; - - type.NestedTypes.Add (arrayType); - } - - return arrayType; - } - } - - class TypeMapRecord - { - public required string JniName { get; init; } - public required TypeDefinition[] Types { get; init; } - public required LinkContext Context { get; init; } - - public string TypeName - { - get - { - // We need to drop the version, culture, and public key information from the AQN. - var type = SelectTypeDefinition (); - var assemblyQualifiedName = TypeDefinitionRocks.GetAssemblyQualifiedName (type, Context); - var commaIndex = assemblyQualifiedName.IndexOf(','); - var secondCommaIndex = assemblyQualifiedName.IndexOf(',', startIndex: commaIndex + 1); - return secondCommaIndex < 0 - ? assemblyQualifiedName - : assemblyQualifiedName.Substring (0, secondCommaIndex); - } - } - - public TypeDefinition SelectTypeDefinition () - { - if (Types.Length == 1) - return Types[0]; - - var best = Types[0]; - foreach (var type in Types) { - if (type == best) - continue; - // Types in Mono.Android assembly should be first in the list - if (best.Module.Assembly.Name.Name != "Mono.Android" && - type.Module.Assembly.Name.Name == "Mono.Android") { - best = type; - continue; - } - // We found the `Invoker` type *before* the declared type - // Fix things up so the abstract type is first, and the `Invoker` is considered a duplicate. - if ((type.IsAbstract || type.IsInterface) && - !best.IsAbstract && - !best.IsInterface && - type.IsAssignableFrom (best, Context)) { - best = type; - continue; - } - - // we found a generic subclass of a non-generic type - if (type.IsGenericInstance && - !best.IsGenericInstance && - type.IsAssignableFrom (best, Context)) { - best = type; - continue; - } - } - foreach (var type in Types) { - if (type == best) - continue; - Context.LogMessage ($"Duplicate typemap entry for {JniName} => {type.FullName}"); - } - return best; - } - - public IEnumerable Flatten () => - Types.Select (type => - new TypeMapRecord { - JniName = JniName, - Types = new[] { type }, - Context = Context, - }); - } - - void ProcessType (AssemblyDefinition assembly, TypeDefinition type) - { - if (type.HasJavaPeer (Context)) { - var javaName = JavaNativeTypeManager.ToJniName (type, Context); - if (!TypeMappings.TryGetValue (javaName, out var list)) { - TypeMappings.Add (javaName, list = new List ()); - } - list.Add (type); - } - - if (!type.HasNestedTypes) - return; - - foreach (TypeDefinition nested in type.NestedTypes) - ProcessType (assembly, nested); - } - - ulong Hash (string value) - { - ReadOnlySpan bytes = MemoryMarshal.AsBytes(value.AsSpan ()); - ulong hash = _hashMethod!(bytes); - - if (!BitConverter.IsLittleEndian) { - hash = BinaryPrimitives.ReverseEndianness (hash); - } - - return hash; - } - - HashMethod LoadHashMethod () - { - if (!Context.TryGetCustomData (SystemIOHashingAssemblyPathCustomData, out var assemblyPath)) { - throw new InvalidOperationException ($"The {nameof (TypeMappingStep)} step requires setting the '{SystemIOHashingAssemblyPathCustomData}' custom data"); - } else if (!System.IO.File.Exists (assemblyPath)) { - throw new InvalidOperationException ($"The '{SystemIOHashingAssemblyPathCustomData}' custom data must point to a valid assembly path ('{assemblyPath}' does not exist)"); - } - - System.Reflection.MethodInfo? hashToUInt64MethodInfo; - try { - hashToUInt64MethodInfo = System.Reflection.Assembly.LoadFile (assemblyPath).GetType ("System.IO.Hashing.XxHash3")?.GetMethod ("HashToUInt64"); - } catch (Exception ex) { - throw new InvalidOperationException ($"The '{SystemIOHashingAssemblyPathCustomData}' custom data must point to a valid assembly path ('{assemblyPath}' could not be loaded)", ex); - } - - if (hashToUInt64MethodInfo is null) { - throw new InvalidOperationException ($"Unable to find System.IO.Hashing.XxHash3.HashToUInt64 method, {nameof(TypeMappingStep)} cannot proceed"); - } - - return (HashMethod)Delegate.CreateDelegate (typeof (HashMethod), hashToUInt64MethodInfo, throwOnBindFailure: true); - } -} diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 39041b470d8..234913e3d63 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -35,17 +35,6 @@ static Array ArrayCreateInstance (Type elementType, int length) return SafeArrayFactory.CreateInstance (elementType, rank: 1, length); } - if (RuntimeFeature.ManagedTypeMap) { - return ArrayCreateInstanceWithSuppression (elementType, length); - - [UnconditionalSuppressMessage ("Trimming", "IL3050:RequiresDynamicCode", - Justification = "Temporarily suppressed for the \"ManagedTypeMap\".")] - Array ArrayCreateInstanceWithSuppression (Type elementType, int length) - { - return Array.CreateInstance (elementType, length); - } - } - throw new NotSupportedException ($"It is not possible to create an array with element type '{elementType}'."); } diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index a30ba3626ba..ff3ade9854c 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -174,16 +174,8 @@ internal static JniRuntime.JniTypeManager CreateTypeManager (JnienvInitializeArg return new TrimmableTypeMapTypeManager (); } - if (RuntimeFeature.IsNativeAotRuntime || RuntimeFeature.ManagedTypeMap) { - return CreateManagedTypeManager (); - } - return CreateAndroidTypeManager (args); - [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Managed type manager is preserved by the MarkJavaObjects trimmer step.")] - [UnconditionalSuppressMessage ("Trimming", "IL3050", Justification = "This type manager won't be used in Native AOT builds in the future.")] - static JniRuntime.JniTypeManager CreateManagedTypeManager () => new ManagedTypeManager (); - [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "This type manager won't be used in Native AOT builds.")] [UnconditionalSuppressMessage ("Trimming", "IL3050", Justification = "This type manager won't be used in Native AOT builds.")] static JniRuntime.JniTypeManager CreateAndroidTypeManager (JnienvInitializeArgs args) => new AndroidTypeManager (args.jniAddNativeMethodRegistrationAttributePresent != 0); diff --git a/src/Mono.Android/Java.Interop/JavaConvert.cs b/src/Mono.Android/Java.Interop/JavaConvert.cs index c02a02e00fe..51d16713029 100644 --- a/src/Mono.Android/Java.Interop/JavaConvert.cs +++ b/src/Mono.Android/Java.Interop/JavaConvert.cs @@ -82,15 +82,6 @@ static class JavaConvert { var factoryConverter = TryMakeGenericCollectionTypeFactory (target); if (factoryConverter != null) return factoryConverter; - } else if (RuntimeFeature.ManagedTypeMap) { - var factoryConverter = TryMakeGenericCollectionTypeFactoryWithSuppression (target); - if (factoryConverter != null) - return factoryConverter; - - [UnconditionalSuppressMessage ("AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "Temporary suppression for ManagedTypeMap")] - static Func? TryMakeGenericCollectionTypeFactoryWithSuppression (Type target) - => TryMakeGenericCollectionTypeFactory (target); } else { throw new NotSupportedException ($"Cannot convert Java collection elements to closed generic array element type '{target}' because the runtime does not support dynamic code generation."); } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ManagedTypeManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedTypeManager.cs deleted file mode 100644 index b05dbdc379a..00000000000 --- a/src/Mono.Android/Microsoft.Android.Runtime/ManagedTypeManager.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using Java.Interop; -using Java.Interop.Tools.TypeNameMappings; - -namespace Microsoft.Android.Runtime; - -[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Temporary suppression for Java.Interop reflection manager base.")] -[UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Temporary suppression for Java.Interop reflection manager base.")] -class ManagedTypeManager : JniRuntime.ReflectionJniTypeManager { - - const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; - internal const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; - internal const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes; - internal const DynamicallyAccessedMemberTypes MethodsConstructors = MethodsAndPrivateNested | Constructors; - - public ManagedTypeManager () - { - } - - [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "'Invoker' types are preserved by the MarkJavaObjects trimmer step.")] - [UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = "'Invoker' types are preserved by the MarkJavaObjects trimmer step.")] - [UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "Generic 'Invoker' types are preserved by the MarkJavaObjects trimmer step.")] - [UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Generic 'Invoker' types may not be available in AOT scenarios.")] - protected override Type? GetInvokerTypeCore (Type type) - { - const string suffix = "Invoker"; - - Type[] arguments = type.GetGenericArguments (); - if (arguments.Length == 0) - return type.Assembly.GetType (type + suffix) ?? base.GetInvokerTypeCore (type); - Type definition = type.GetGenericTypeDefinition (); - int bt = definition.FullName!.IndexOf ("`", StringComparison.Ordinal); - if (bt == -1) - throw new NotSupportedException ("Generic type doesn't follow generic type naming convention! " + type.FullName); - Type? suffixDefinition = definition.Assembly.GetType ( - definition.FullName.Substring (0, bt) + suffix + definition.FullName.Substring (bt)); - if (suffixDefinition == null) - return base.GetInvokerTypeCore (type); - return suffixDefinition.MakeGenericType (arguments); - } - - // NOTE: suppressions below also in `src/Mono.Android/Android.Runtime/AndroidRuntime.cs` - [UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Type.GetType() can never statically know the string value parsed from parameter 'methods'.")] - [UnconditionalSuppressMessage ("Trimming", "IL2067", Justification = "Delegate.CreateDelegate() can never statically know the string value parsed from parameter 'methods'.")] - [UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "Delegate.CreateDelegate() can never statically know the string value parsed from parameter 'methods'.")] - [UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "JniNativeMethodRegistration[] registration path will be migrated to the blittable RegisterNatives overload in a future change.")] - public override void RegisterNativeMembers ( - JniType nativeClass, - Type type, - ReadOnlySpan methods) - { - if (methods.IsEmpty) { - base.RegisterNativeMembers (nativeClass, type, methods); - return; - } - - int methodCount = CountMethods (methods); - if (methodCount < 1) { - base.RegisterNativeMembers (nativeClass, type, methods); - return; - } - - JniNativeMethodRegistration [] natives = new JniNativeMethodRegistration [methodCount]; - int nativesIndex = 0; - - ReadOnlySpan methodsSpan = methods; - bool needToRegisterNatives = false; - - while (!methodsSpan.IsEmpty) { - int newLineIndex = methodsSpan.IndexOf ('\n'); - - ReadOnlySpan methodLine = methodsSpan.Slice (0, newLineIndex != -1 ? newLineIndex : methodsSpan.Length); - if (!methodLine.IsEmpty) { - SplitMethodLine (methodLine, - out ReadOnlySpan name, - out ReadOnlySpan signature, - out ReadOnlySpan callbackString, - out ReadOnlySpan callbackDeclaringTypeString); - - Delegate? callback = null; - if (callbackString.SequenceEqual ("__export__")) { - throw new InvalidOperationException (FormattableString.Invariant ($"Methods such as {callbackString.ToString ()} are not implemented!")); - } else { - Type callbackDeclaringType = type; - if (!callbackDeclaringTypeString.IsEmpty) { - callbackDeclaringType = Type.GetType (callbackDeclaringTypeString.ToString (), throwOnError: true)!; - } - while (callbackDeclaringType.ContainsGenericParameters) { - callbackDeclaringType = callbackDeclaringType.BaseType!; - } - - GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler), - callbackDeclaringType, callbackString.ToString ()); - callback = connector (); - } - - if (callback != null) { - needToRegisterNatives = true; - natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback); - } - } - - methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default; - } - - if (needToRegisterNatives) { - JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, nativesIndex); - } - } - - - protected override IEnumerable GetTypesForSimpleReference (string jniSimpleReference) - { - // Base class contains built-in mappings (e.g. java/lang/String → System.String) - // which must take priority over ManagedTypeMapping (which would return Java.Lang.String). - foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference)) { - yield return t; - } - if (ManagedTypeMapping.TryGetType (jniSimpleReference, out var target)) { - yield return target; - } - } - - [UnconditionalSuppressMessage ("Trimming", "IL2068", Justification = "Temporary suppression until ManagedTypeMapping type entries carry DAM annotations.")] - protected override Type? GetTypeForSimpleReference (string jniSimpleReference) - { - var type = base.GetTypeForSimpleReference (jniSimpleReference); - if (type != null) { - return type; - } - - return ManagedTypeMapping.TryGetType (jniSimpleReference, out var target) ? target : null; - } - - protected override IEnumerable GetSimpleReferences (Type type) - { - foreach (var r in base.GetSimpleReferences (type)) { - yield return r; - } - - if (ManagedTypeMapping.TryGetJniName (type, out var jniName)) { - yield return jniName; - } - } - - protected override IReadOnlyList? GetStaticMethodFallbackTypesCore (string jniSimpleReference) - { - return JniRemappingLookup.GetStaticMethodFallbackTypes (jniSimpleReference, useReplacementTypes: false); - } - - static int CountMethods (ReadOnlySpan methodsSpan) - { - int count = 0; - while (!methodsSpan.IsEmpty) { - count++; - - int newLineIndex = methodsSpan.IndexOf ('\n'); - methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default; - } - return count; - } - - static void SplitMethodLine ( - ReadOnlySpan methodLine, - out ReadOnlySpan name, - out ReadOnlySpan signature, - out ReadOnlySpan callback, - out ReadOnlySpan callbackDeclaringType) - { - int colonIndex = methodLine.IndexOf (':'); - name = methodLine.Slice (0, colonIndex); - methodLine = methodLine.Slice (colonIndex + 1); - - colonIndex = methodLine.IndexOf (':'); - signature = methodLine.Slice (0, colonIndex); - methodLine = methodLine.Slice (colonIndex + 1); - - colonIndex = methodLine.IndexOf (':'); - callback = methodLine.Slice (0, colonIndex != -1 ? colonIndex : methodLine.Length); - - callbackDeclaringType = colonIndex != -1 ? methodLine.Slice (colonIndex + 1) : default; - } - - delegate Delegate GetCallbackHandler (); -} diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ManagedTypeMapping.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedTypeMapping.cs deleted file mode 100644 index 3cc7d790342..00000000000 --- a/src/Mono.Android/Microsoft.Android.Runtime/ManagedTypeMapping.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Buffers.Binary; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO.Hashing; -using System.Runtime.InteropServices; -using System.Text; -using Android.Runtime; - -namespace Microsoft.Android.Runtime; - -internal static class ManagedTypeMapping -{ - internal static bool TryGetType (string jniName, [NotNullWhen (true)] out Type? type) - { - type = null; - - // the hashes array is sorted and all the hashes are unique - ulong jniNameHash = Hash (jniName); - int jniNameHashIndex = MemoryExtensions.BinarySearch (JniNameHashes, jniNameHash); - if (jniNameHashIndex < 0) { - return false; - } - - // we need to make sure if this is the right match or if it is a hash collision - if (jniName != GetJniNameByJniNameHashIndex (jniNameHashIndex)) { - return false; - } - - type = GetTypeByJniNameHashIndex (jniNameHashIndex); - if (type is null) { - throw new InvalidOperationException ($"Type for {jniName} (hash: {jniNameHash}, index: {jniNameHashIndex}) not found."); - } - - return true; - } - - internal static bool TryGetJniName (Type type, [NotNullWhen (true)] out string? jniName) - { - jniName = null; - - string? assemblyQualifiedName = type.AssemblyQualifiedName; - if (assemblyQualifiedName is null) { - jniName = null; - return false; - } - - ReadOnlySpan typeName = GetSimplifiedAssemblyQualifiedTypeName (assemblyQualifiedName); - - // the hashes array is sorted and all the hashes are unique - ulong typeNameHash = Hash (typeName); - int typeNameHashIndex = MemoryExtensions.BinarySearch (TypeNameHashes, typeNameHash); - if (typeNameHashIndex < 0) { - return false; - } - - // we need to make sure if this is the match or if it is a hash collision - if (!typeName.SequenceEqual (GetTypeNameByTypeNameHashIndex (typeNameHashIndex))) { - return false; - } - - jniName = GetJniNameByTypeNameHashIndex (typeNameHashIndex); - if (jniName is null) { - throw new InvalidOperationException ($"JNI name for {typeName} (hash: {typeNameHash}, index: {typeNameHashIndex}) not found."); - } - - return true; - } - - private static ulong Hash (ReadOnlySpan value) - { - ReadOnlySpan bytes = MemoryMarshal.AsBytes (value); - ulong hash = XxHash3.HashToUInt64 (bytes); - - // The bytes in the hashes array are stored as little endian. If the target platform is big endian, - // we need to reverse the endianness of the hash. - if (!BitConverter.IsLittleEndian) { - hash = BinaryPrimitives.ReverseEndianness (hash); - } - - return hash; - } - - // This method keeps only the full type name and the simple assembly name. - // It drops the version, culture, and public key information. - // - // For example: "System.Int32, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e" - // becomes: "System.Int32, System.Private.CoreLib" - private static ReadOnlySpan GetSimplifiedAssemblyQualifiedTypeName(string assemblyQualifiedName) - { - var commaIndex = assemblyQualifiedName.IndexOf(','); - var secondCommaIndex = assemblyQualifiedName.IndexOf(',', startIndex: commaIndex + 1); - return secondCommaIndex < 0 - ? assemblyQualifiedName - : assemblyQualifiedName.AsSpan(0, secondCommaIndex); - } - - // Replaced by src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs - private static ReadOnlySpan TypeNameHashes => throw new NotImplementedException (); - private static Type? GetTypeByJniNameHashIndex (int jniNameHashIndex) => throw new NotImplementedException (); - private static string? GetJniNameByJniNameHashIndex (int jniNameHashIndex) => throw new NotImplementedException (); - - private static ReadOnlySpan JniNameHashes => throw new NotImplementedException (); - private static string? GetJniNameByTypeNameHashIndex (int typeNameHashIndex) => throw new NotImplementedException (); - private static string? GetTypeNameByTypeNameHashIndex (int typeNameHashIndex) => throw new NotImplementedException (); -} diff --git a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs index 5d20f9e5ac4..f15a79ff6f4 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs @@ -5,7 +5,6 @@ namespace Microsoft.Android.Runtime; static class RuntimeFeature { - const bool ManagedTypeMapEnabledByDefault = false; const bool IsMonoRuntimeEnabledByDefault = true; const bool IsCoreClrRuntimeEnabledByDefault = false; const bool IsNativeAotRuntimeEnabledByDefault = false; @@ -17,10 +16,6 @@ static class RuntimeFeature const string FeatureSwitchPrefix = "Microsoft.Android.Runtime.RuntimeFeature."; const string StartupHookProviderSwitch = "System.StartupHookProvider.IsSupported"; - [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (ManagedTypeMap)}")] - internal static bool ManagedTypeMap { get; } = - AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (ManagedTypeMap)}", out bool isEnabled) ? isEnabled : ManagedTypeMapEnabledByDefault; - [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (IsMonoRuntime)}")] internal static bool IsMonoRuntime { get; } = AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (IsMonoRuntime)}", out bool isEnabled) ? isEnabled : IsMonoRuntimeEnabledByDefault; diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 25aee38cdf3..2470133942e 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -365,8 +365,6 @@ - - diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets index 96835c928c8..020e0bd928a 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets @@ -71,21 +71,8 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. /> - - - - _AndroidBeforeIlcCompile; - SetupOSSpecificProps; - PrepareForILLink; - ILLink; - ComputeIlcCompileInputs; - _AndroidComputeIlcCompileInputs; - $(IlcCompileDependsOn) - - <_AndroidBeforeIlcCompileDependsOn>_PrepareLinking - - + _AndroidBeforeIlcCompile; SetupOSSpecificProps; @@ -156,29 +143,11 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. false - - - - true - - <_OriginalSuppressTrimAnalysisWarnings>$(SuppressTrimAnalysisWarnings) - true - - <_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --notrimwarn - - - - - $(_OriginalSuppressTrimAnalysisWarnings) - @@ -193,24 +162,6 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. managed assemblies remain — the outer build needs them for _GenerateJavaStubs and typemap generation. --> <_IlcManagedInputAssemblies Remove="@(_IlcManagedInputAssemblies)" /> - - - - - - - - - <_AndroidILLinkAssemblies Include="@(ManagedAssemblyToLink->'$(IntermediateLinkDir)%(Filename)%(Extension)')" Condition="Exists('$(IntermediateLinkDir)%(Filename)%(Extension)')" /> - - - - @@ -441,8 +392,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. in the inner per-RID build. --> - <_AndroidRunNativeCompileDependsOn Condition=" '$(_AndroidTypeMapImplementation)' != 'trimmable' ">_PreTrimmingFixLegacyDesignerUpdateItems;NativeCompile - <_AndroidRunNativeCompileDependsOn Condition=" '$(_AndroidTypeMapImplementation)' == 'trimmable' ">NativeCompile + <_AndroidRunNativeCompileDependsOn>NativeCompile <_BinaryRuntimeConfigPath>$(IntermediateOutputPath)$(ProjectRuntimeConfigFileName).bin - <_AndroidUseManagedTypeMap Condition=" '$(_AndroidTypeMapImplementation)' == 'managed' ">true <_AndroidEnableObjectReferenceLogging Condition=" '$(_AndroidEnableObjectReferenceLogging)' == '' And '$(PublishTrimmed)' == 'true' ">false @@ -54,10 +53,6 @@ See: https://github.com/dotnet/runtime/blob/b13715b6984889a709ba29ea8a1961db469f Value="$(AndroidAvoidEmitForPerformance)" Trim="true" /> - - <_TrimmerCustomSteps - Condition=" '$(_AndroidTypeMapImplementation)' == 'managed' " - Include="$(_AndroidLinkerCustomStepAssembly)" - AfterStep="CleanStep" - Type="Microsoft.Android.Sdk.ILLink.TypeMappingStep" - /> ();"); - - using var b = CreateApkBuilder (); - Assert.IsTrue (b.Build (proj), "Build should have succeeded."); - b.Output.AssertTargetIsNotSkipped ("_PrepareLinking"); - - string [] mono_classes = [ - "Lmono/MonoRuntimeProvider;", - ]; - string[] mono_files = [ - "lib/arm64-v8a/libmonosgen-2.0.so", - "lib/x86_64/libmonosgen-2.0.so", - ]; - string [] nativeaot_files = [ - $"lib/arm64-v8a/lib{proj.ProjectName}.so", - $"lib/x86_64/lib{proj.ProjectName}.so", - ]; - - var intermediate = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); - var output = Path.Combine (Root, b.ProjectDirectory, proj.OutputPath); - - var linkedMonoAndroidAssembly = Path.Combine (intermediate, "android-arm64", "linked", "Mono.Android.dll"); - FileAssert.Exists (linkedMonoAndroidAssembly); - var javaClassNames = new List (); - var types = new List (); - - using (var assembly = AssemblyDefinition.ReadAssembly (linkedMonoAndroidAssembly)) { - var typeName = "Android.App.Activity"; - var methodName = "GetOnCreate_Landroid_os_Bundle_Handler"; - var type = assembly.MainModule.GetType (typeName); - Assert.IsNotNull (type, $"{linkedMonoAndroidAssembly} should contain {typeName}"); - var method = type.Methods.FirstOrDefault (m => m.Name == methodName); - Assert.IsNotNull (method, $"{linkedMonoAndroidAssembly} should contain {typeName}.{methodName}"); - - type = assembly.MainModule.Types.FirstOrDefault (t => t.Name == "ManagedTypeMapping"); - Assert.IsNotNull (type, $"{linkedMonoAndroidAssembly} should contain ManagedTypeMapping"); - method = type.Methods.FirstOrDefault (m => m.Name == "GetJniNameByTypeNameHashIndex"); - Assert.IsNotNull (method, $"{type.Name} should contain GetJniNameByTypeNameHashIndex"); - - foreach (var i in method.Body.Instructions) { - if (i.OpCode != Mono.Cecil.Cil.OpCodes.Ldstr) - continue; - if (i.Operand is not string javaName) - continue; - if (i.Next.OpCode != Mono.Cecil.Cil.OpCodes.Ret) - continue; - javaClassNames.Add (javaName); - } - - method = type.Methods.FirstOrDefault (m => m.Name == "GetTypeByJniNameHashIndex"); - Assert.IsNotNull (method, $"{type.Name} should contain GetTypeByJniNameHashIndex"); - - foreach (var i in method.Body.Instructions) { - if (i.OpCode != Mono.Cecil.Cil.OpCodes.Ldtoken) - continue; - if (i.Operand is not TypeReference typeReference) - continue; - if (i.Next?.OpCode != Mono.Cecil.Cil.OpCodes.Call) - continue; - if (i.Next.Next?.OpCode != Mono.Cecil.Cil.OpCodes.Ret) - continue; - types.Add (typeReference); - } - - // Basic types - AssertTypeMap ("java/lang/Object", "Java.Lang.Object"); - AssertTypeMap ("java/lang/String", "Java.Lang.String"); - AssertTypeMap ("[Ljava/lang/Object;", "Java.Interop.JavaArray`1"); - AssertTypeMap ("java/util/ArrayList", "Android.Runtime.JavaList"); - AssertTypeMap ("android/app/Activity", "Android.App.Activity"); - AssertTypeMap ("android/widget/Button", "Android.Widget.Button"); - Assert.IsFalse (StringAssertEx.ContainsText (b.LastBuildOutput, - "Duplicate typemap entry for java/util/ArrayList => Android.Runtime.JavaList`1"), - "Should get log message about duplicate Android.Runtime.JavaList`1!"); - - // Special *Invoker case - AssertTypeMap ("android/view/View$OnClickListener", "Android.Views.View/IOnClickListener"); - Assert.IsFalse (StringAssertEx.ContainsText (b.LastBuildOutput, - "Duplicate typemap entry for android/view/View$OnClickListener => Android.Views.View/IOnClickListenerInvoker"), - "Should get log message about duplicate IOnClickListenerInvoker!"); - } - - // Verify that Java stubs for Mono.Android.dll were generated, instead of using mono.android.jar/dex - var onLayoutChangeListenerImplementor = Path.Combine (intermediate, "android", "src", "mono", "android", "view", "View_OnClickListenerImplementor.java"); - FileAssert.Exists (onLayoutChangeListenerImplementor); - - var dexFile = Path.Combine (intermediate, "android", "bin", "classes.dex"); - FileAssert.Exists (dexFile); - foreach (var className in mono_classes) { - Assert.IsFalse (DexUtils.ContainsClassWithMethod (className, "", "()V", dexFile, AndroidSdkPath), $"`{dexFile}` should *not* include `{className}`!"); - } - - var apkFile = Path.Combine (output, $"{proj.PackageName}-Signed.apk"); - FileAssert.Exists (apkFile); - using var zip = ZipHelper.OpenZip (apkFile); - foreach (var mono_file in mono_files) { - Assert.IsFalse (zip.ContainsEntry (mono_file, caseSensitive: true), $"APK must *not* contain `{mono_file}`."); - } - foreach (var nativeaot_file in nativeaot_files) { - Assert.IsTrue (zip.ContainsEntry (nativeaot_file, caseSensitive: true), $"APK must contain `{nativeaot_file}`."); - } - - void AssertTypeMap(string javaName, string managedName) - { - var javaNameIndex = javaClassNames.FindIndex (name => name == javaName); - var typeIndex = types.FindIndex (td => td.ToString() == managedName); - - if (javaNameIndex < 0) { - Assert.Fail ($"TypeMapping should contain \"{javaName}\"!"); - } else if (typeIndex < 0) { - Assert.Fail ($"TypeMapping should contain \"{managedName}\"!"); - } - } - } - [Test] public void BuildBasicApplicationThenMoveIt ([Values] bool isRelease, [Values (AndroidRuntime.CoreCLR, AndroidRuntime.NativeAOT)] AndroidRuntime runtime) { diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 8e0b5004150..6a982714484 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -3000,8 +3000,8 @@ because xbuild doesn't support framework reference assemblies. Condition=" '$(DesignTimeBuild)' != 'true' "/> - + Get_DotNetRun_Data () foreach (AndroidRuntime runtime in new[] { AndroidRuntime.CoreCLR, AndroidRuntime.NativeAOT }) { AddTestData (true, "llvm-ir", runtime); AddTestData (false, "llvm-ir", runtime); - AddTestData (true, "managed", runtime); - // NOTE: TypeMappingStep is not yet setup for Debug mode - //AddTestData (false, "managed", runtime); } AddTestData (true, "trimmable", AndroidRuntime.CoreCLR); From a647cf3da4e52b45bed2c111c4085e3924b7c50e Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 16 Jul 2026 15:32:17 +0200 Subject: [PATCH 2/3] Remove the managed marshal methods lookup feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The managed marshal methods lookup generated a managed IL table (indexed by assembly/class/method) so native code could call back into managed to resolve marshal-method function pointers. It was only ever used by CoreCLR/NativeAOT; MonoVM always used the native `get_function_pointer` path, and marshal methods default off for non-MonoVM. Remove the feature entirely — marshal methods are now MonoVM-only. * MSBuild codegen: delete ManagedMarshalMethodsLookupInfo and ManagedMarshalMethodsLookupGenerator; drop the managed-lookup branch in MarshalMethodsNativeAssemblyGenerator (keep the MonoVM native path), the index population in MarshalMethodCecilAdapter (+ the AssemblyIndex/ ClassIndex/MethodIndex properties), the rewriter/generator/task plumbing, and the EnableManagedMarshalMethodsLookup task parameters. * Native ABI: remove managed_marshal_methods_lookup_enabled from ApplicationConfig (mono + clr), the DSO stubs, and managedMarshalMethodsLookupEnabled from JnienvInitializeArgs; drop the CLR host invariant and the now-dead get_function_pointer_placeholder; MonoVM always wires up the native get_function_pointer callbacks. * Runtime: delete ManagedMarshalMethodsLookupTable and the managed xamarin_app_init p/invoke; remove the struct field + init branch. * Targets/tests: remove _AndroidUseManagedMarshalMethodsLookup, update EnvironmentHelper struct mirrors/field counts, and delete the device test that force-enabled the feature. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 40c65ba4-c9e2-442f-bc68-0bd274f0bf8c --- .../Android.Runtime/JNIEnvInit.cs | 11 - .../ManagedMarshalMethodsLookupTable.cs | 33 --- src/Mono.Android/Mono.Android.csproj | 1 - ...crosoft.Android.Sdk.TypeMap.LlvmIr.targets | 1 - .../GenerateNativeApplicationConfigSources.cs | 3 - .../GenerateNativeMarshalMethodSources.cs | 12 +- .../Tasks/RewriteMarshalMethods.cs | 32 +-- .../Utilities/EnvironmentHelper.cs | 18 +- .../Utilities/ApplicationConfig.cs | 1 - .../Utilities/ApplicationConfigCLR.cs | 1 - ...pplicationConfigNativeAssemblyGenerator.cs | 2 - ...icationConfigNativeAssemblyGeneratorCLR.cs | 2 - .../ManagedMarshalMethodsLookupGenerator.cs | 235 ------------------ .../ManagedMarshalMethodsLookupInfo.cs | 189 -------------- .../Utilities/MarshalMethodCecilAdapter.cs | 17 +- .../MarshalMethodsAssemblyRewriter.cs | 15 +- .../MarshalMethodsNativeAssemblyGenerator.cs | 22 +- ...alMethodsNativeAssemblyGeneratorCoreCLR.cs | 4 +- ...halMethodsNativeAssemblyGeneratorMonoVM.cs | 4 +- .../Utilities/NativeCodeGenState.cs | 1 - .../Xamarin.Android.Common.targets | 4 - src/native/clr/host/host.cc | 3 - src/native/clr/include/xamarin-app.hh | 1 - .../xamarin-app-stub/application_dso_stub.cc | 1 - .../common/include/managed-interface.hh | 1 - .../mono/monodroid/monodroid-glue-internal.hh | 1 - src/native/mono/monodroid/monodroid-glue.cc | 12 +- .../monodroid/xamarin-android-app-context.cc | 9 - .../xamarin-app-stub/application_dso_stub.cc | 1 - .../mono/xamarin-app-stub/xamarin-app.hh | 1 - .../Tests/InstallAndRunTests.cs | 32 --- 31 files changed, 21 insertions(+), 649 deletions(-) delete mode 100644 src/Mono.Android/Java.Interop/ManagedMarshalMethodsLookupTable.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/ManagedMarshalMethodsLookupGenerator.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/ManagedMarshalMethodsLookupInfo.cs diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index ff3ade9854c..88c2de947e7 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading; using Java.Interop; @@ -34,7 +33,6 @@ internal struct JnienvInitializeArgs { public bool jniRemappingInUse; public bool marshalMethodsEnabled; public IntPtr grefGCUserPeerable; - public bool managedMarshalMethodsLookupEnabled; public IntPtr propagateUncaughtExceptionFn; public IntPtr registerJniNativesFn; } @@ -145,11 +143,6 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) JniRuntime.SetCurrent (androidRuntime); RegisterTrimmableTypeMapNativeMethodsIfNeeded (); - if (args->managedMarshalMethodsLookupEnabled) { - delegate* unmanaged getFunctionPointer = &ManagedMarshalMethodsLookupTable.GetFunctionPointer; - xamarin_app_init (args->env, getFunctionPointer); - } - args->propagateUncaughtExceptionFn = (IntPtr)(delegate* unmanaged)&PropagateUncaughtException; if (!RuntimeFeature.TrimmableTypeMap) { @@ -163,10 +156,6 @@ IntPtr GetRegisterJniNativesFnPtr () => (IntPtr)(delegate* unmanaged)&RegisterJniNatives; } - [LibraryImport (RuntimeConstants.InternalDllName)] - [UnmanagedCallConv (CallConvs = new[] { typeof (CallConvCdecl) })] - private static unsafe partial void xamarin_app_init (IntPtr env, delegate* unmanaged get_function_pointer); - [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "The AndroidTypeManager branch is only reached when RuntimeFeature.TrimmableTypeMap is false; the linker substitutes the feature switch and trims this branch in trimmable apps.")] internal static JniRuntime.JniTypeManager CreateTypeManager (JnienvInitializeArgs args) { diff --git a/src/Mono.Android/Java.Interop/ManagedMarshalMethodsLookupTable.cs b/src/Mono.Android/Java.Interop/ManagedMarshalMethodsLookupTable.cs deleted file mode 100644 index cd15fb3e337..00000000000 --- a/src/Mono.Android/Java.Interop/ManagedMarshalMethodsLookupTable.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -using Android.Runtime; - -namespace Java.Interop; - -internal class ManagedMarshalMethodsLookupTable -{ - [UnmanagedCallersOnly] - internal static unsafe void GetFunctionPointer (int assemblyIndex, int classIndex, int methodIndex, IntPtr* target) - { - try { - IntPtr result = GetFunctionPointer (assemblyIndex, classIndex, methodIndex); - if (result == IntPtr.Zero || result == (IntPtr)(-1)) { - throw new InvalidOperationException ($"Failed to get function pointer for ({assemblyIndex}, {classIndex}, {methodIndex})"); - } - - *target = result; - } catch (Exception ex) { - AndroidEnvironment.UnhandledException (ex); - AndroidEnvironment.FailFast ("GetFunctionPointer failed: should not be reached"); - } - } - - static IntPtr GetFunctionPointer (int assemblyIndex, int classIndex, int methodIndex) - { - // ManagedMarshalMethodsLookupGenerator generates the body of this method is generated at app build time - throw new NotImplementedException (); - } -} diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 2470133942e..3be9a245b6f 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -322,7 +322,6 @@ - diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets index c496014062a..52ef1811dbd 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets @@ -85,7 +85,6 @@ diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs index dd2788f30a1..c64f9790503 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs @@ -58,7 +58,6 @@ public class GenerateNativeApplicationConfigSources : AndroidTask public string AndroidRuntime { get; set; } = ""; public bool EnableMarshalMethods { get; set; } - public bool EnableManagedMarshalMethodsLookup { get; set; } public string? RuntimeConfigBinFilePath { get; set; } public string ProjectRuntimeConfigFilePath { get; set; } = String.Empty; public string? BoundExceptionType { get; set; } @@ -283,7 +282,6 @@ static bool ShouldSkipAssembly (ITaskItem assembly) JniRemappingReplacementTypeCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementTypeCount, JniRemappingReplacementMethodIndexEntryCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementMethodIndexEntryCount, MarshalMethodsEnabled = EnableMarshalMethods, - ManagedMarshalMethodsLookupEnabled = EnableManagedMarshalMethodsLookup, IgnoreSplitConfigs = ShouldIgnoreSplitConfigs (), HaveAssemblyStore = UseAssemblyStore, }; @@ -313,7 +311,6 @@ static bool ShouldSkipAssembly (ITaskItem assembly) JniRemappingReplacementTypeCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementTypeCount, JniRemappingReplacementMethodIndexEntryCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementMethodIndexEntryCount, MarshalMethodsEnabled = EnableMarshalMethods, - ManagedMarshalMethodsLookupEnabled = EnableManagedMarshalMethodsLookup, IgnoreSplitConfigs = ShouldIgnoreSplitConfigs (), }; } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeMarshalMethodSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeMarshalMethodSources.cs index a0134611a69..32552171cef 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeMarshalMethodSources.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeMarshalMethodSources.cs @@ -41,12 +41,6 @@ public class GenerateNativeMarshalMethodSources : AndroidTask /// public override string TaskPrefix => "GNM"; - /// - /// Gets or sets whether to generate managed marshal methods lookup tables. - /// When enabled, creates runtime data structures for efficient marshal method resolution. - /// - public bool EnableManagedMarshalMethodsLookup { get; set; } - /// /// Gets or sets whether marshal methods generation is enabled. /// When false, generates empty placeholder files to maintain build consistency. @@ -241,8 +235,7 @@ MarshalMethodsNativeAssemblyGenerator MakeMonoGenerator () Log, assemblyCount, uniqueAssemblyNames, - EnsureCodeGenState (nativeCodeGenStates, targetArch), - EnableManagedMarshalMethodsLookup + EnsureCodeGenState (nativeCodeGenStates, targetArch) ); } @@ -266,8 +259,7 @@ MarshalMethodsNativeAssemblyGenerator MakeCoreCLRGenerator () return new MarshalMethodsNativeAssemblyGeneratorCoreCLR ( Log, uniqueAssemblyNames, - EnsureCodeGenState (nativeCodeGenStates, targetArch), - EnableManagedMarshalMethodsLookup + EnsureCodeGenState (nativeCodeGenStates, targetArch) ); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs b/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs index 029e1140dbb..d390b1cc988 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs @@ -19,8 +19,7 @@ namespace Xamarin.Android.Tasks; /// 1. Retrieves marshal method classifications from the build pipeline state /// 2. Parses environment files to determine exception transition behavior /// 3. Rewrites assemblies to replace dynamic registration with static marshal methods -/// 4. Optionally builds managed lookup tables for runtime marshal method resolution -/// 5. Reports statistics on marshal method generation and any fallback to dynamic registration +/// 4. Reports statistics on marshal method generation and any fallback to dynamic registration /// /// The rewriting process creates native callback wrappers for methods that have non-blittable /// parameters or return types, ensuring compatibility with the [UnmanagedCallersOnly] attribute @@ -33,13 +32,6 @@ public class RewriteMarshalMethods : AndroidTask /// public override string TaskPrefix => "RMM"; - /// - /// Gets or sets whether to enable managed marshal methods lookup tables. - /// When enabled, generates runtime lookup structures that allow dynamic resolution - /// of marshal methods without string comparisons, improving runtime performance. - /// - public bool EnableManagedMarshalMethodsLookup { get; set; } - /// /// Gets or sets the environment files to parse for configuration settings. /// These files may contain settings like XA_BROKEN_EXCEPTION_TRANSITIONS that @@ -69,12 +61,8 @@ public class RewriteMarshalMethods : AndroidTask /// 3. For each target architecture: /// - Rewrite assemblies to use marshal methods /// - Add special case methods (e.g., TypeManager methods) - /// - Optionally build managed lookup tables /// 4. Report statistics on marshal method generation /// 5. Log warnings for methods that must fall back to dynamic registration - /// - /// The task handles the ordering dependency between special case methods and managed - /// lookup tables - special cases must be added first so they appear in the lookup tables. /// public override bool RunTask () { @@ -104,19 +92,8 @@ public override bool RunTask () return false; } - // Handle the ordering dependency between special case methods and managed lookup tables - if (!EnableManagedMarshalMethodsLookup) { - // Standard path: rewrite first, then add special cases - RewriteMethods (state, brokenExceptionTransitionsEnabled); - state.Classifier.AddSpecialCaseMethods (); - } else { - // Managed lookup path: add special cases first so they appear in lookup tables - // We need to run `AddSpecialCaseMethods` before `RewriteMarshalMethods` so that we can see the special case - // methods (such as TypeManager.n_Activate_mm) when generating the managed lookup tables. - state.Classifier.AddSpecialCaseMethods (); - state.ManagedMarshalMethodsLookupInfo = new ManagedMarshalMethodsLookupInfo (Log); - RewriteMethods (state, brokenExceptionTransitionsEnabled); - } + RewriteMethods (state, brokenExceptionTransitionsEnabled); + state.Classifier.AddSpecialCaseMethods (); // Report statistics on marshal method generation Log.LogDebugMessage ($"[{state.TargetArch}] Number of generated marshal methods: {state.Classifier.MarshalMethods.Count}"); @@ -152,7 +129,6 @@ public override bool RunTask () /// - Adding [UnmanagedCallersOnly] attributes to native callbacks /// - Generating wrapper methods for non-blittable types /// - Modifying assembly references and imports - /// - Building managed lookup table entries /// void RewriteMethods (NativeCodeGenState state, bool brokenExceptionTransitionsEnabled) { @@ -160,7 +136,7 @@ void RewriteMethods (NativeCodeGenState state, bool brokenExceptionTransitionsEn return; } - var rewriter = new MarshalMethodsAssemblyRewriter (Log, state.TargetArch, state.Classifier, state.Resolver, state.ManagedMarshalMethodsLookupInfo); + var rewriter = new MarshalMethodsAssemblyRewriter (Log, state.TargetArch, state.Classifier, state.Resolver); rewriter.Rewrite (brokenExceptionTransitionsEnabled); } } 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 d8c2ab96b50..35d0327017f 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,11 +60,10 @@ public sealed class ApplicationConfig_CoreCLR : IApplicationConfig public uint jni_remapping_replacement_type_count; public uint jni_remapping_replacement_method_index_entry_count; public string android_package_name = String.Empty; - public bool managed_marshal_methods_lookup_enabled; public bool have_assembly_store; } - const uint ApplicationConfigFieldCount_CoreCLR = 20; + const uint ApplicationConfigFieldCount_CoreCLR = 19; // This must be identical to the ApplicationConfig structure in src/native/mono/xamarin-app-stub/xamarin-app.hh public sealed class ApplicationConfig_MonoVM : IApplicationConfig @@ -95,7 +94,6 @@ public sealed class ApplicationConfig_MonoVM : IApplicationConfig public uint jni_remapping_replacement_method_index_entry_count; public uint mono_components_mask; public string android_package_name = String.Empty; - public bool managed_marshal_methods_lookup_enabled; } // This is shared between MonoVM and CoreCLR hosts, not used by NativeAOT @@ -129,7 +127,7 @@ public sealed class JniPreloads public string SourceFile; } - const uint ApplicationConfigFieldCount_MonoVM = 27; + const uint ApplicationConfigFieldCount_MonoVM = 26; const string ApplicationConfigSymbolName = "application_config"; const string AppEnvironmentVariablesSymbolName = "app_environment_variables"; @@ -398,12 +396,7 @@ static IApplicationConfig ReadApplicationConfig_CoreCLR (EnvironmentFile envFile pointers.Add (field [1].Trim ()); break; - case 18: // managed_marshal_methods_lookup_enabled: bool / .byte - AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); - ret.managed_marshal_methods_lookup_enabled = ConvertFieldToBool ("managed_marshal_methods_lookup_enabled", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); - break; - - case 19: // have_assembly_store: bool / .byte + case 18: // have_assembly_store: bool / .byte AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); ret.have_assembly_store = ConvertFieldToBool ("have_assembly_store", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; @@ -575,11 +568,6 @@ static IApplicationConfig ReadApplicationConfig_MonoVM (EnvironmentFile envFile) Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); pointers.Add (field [1].Trim ()); break; - - case 26: // managed_marshal_methods_lookup_enabled: bool / .byte - AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); - ret.managed_marshal_methods_lookup_enabled = ConvertFieldToBool ("managed_marshal_methods_lookup_enabled", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); - break; } fieldCount++; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs index 6b6cc92b545..b1eb9306ffb 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs @@ -59,6 +59,5 @@ sealed class ApplicationConfig [NativeAssembler (NumberFormat = LLVMIR.LlvmIrVariableNumberFormat.Hexadecimal)] public uint mono_components_mask; public string android_package_name = String.Empty; - public bool managed_marshal_methods_lookup_enabled; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigCLR.cs index 832c37299d7..2ea9f06e60a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigCLR.cs @@ -49,6 +49,5 @@ sealed class ApplicationConfigCLR public uint jni_remapping_replacement_type_count; public uint jni_remapping_replacement_method_index_entry_count; public string android_package_name = String.Empty; - public bool managed_marshal_methods_lookup_enabled; public bool have_assembly_store; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index 8916b2ab96a..983af9bdf44 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -205,7 +205,6 @@ sealed class DsoCacheState public ICollection? NativeLibrariesNoJniPreload { get; set; } public ICollection? NativeLibrariesAlwaysJniPreload { get; set; } public bool MarshalMethodsEnabled { get; set; } - public bool ManagedMarshalMethodsLookupEnabled { get; set; } public bool IgnoreSplitConfigs { get; set; } public ApplicationConfigNativeAssemblyGenerator (IDictionary environmentVariables, IDictionary systemProperties, TaskLoggingHelper log) @@ -250,7 +249,6 @@ protected override void Construct (LlvmIrModule module) have_runtime_config_blob = HaveRuntimeConfigBlob, have_assemblies_blob = HaveAssemblyStore, marshal_methods_enabled = MarshalMethodsEnabled, - managed_marshal_methods_lookup_enabled = ManagedMarshalMethodsLookupEnabled, ignore_split_configs = IgnoreSplitConfigs, bound_stream_io_exception_type = (byte)BoundExceptionType, package_naming_policy = (uint)PackageNamingPolicy, diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs index 4f977d2d71f..d29e5834145 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs @@ -195,7 +195,6 @@ sealed class DsoCacheState public ICollection? NativeLibrariesNoJniPreload { get; set; } public ICollection? NativeLibrariesAlwaysJniPreload { get; set; } public bool MarshalMethodsEnabled { get; set; } - public bool ManagedMarshalMethodsLookupEnabled { get; set; } public bool IgnoreSplitConfigs { get; set; } public bool HaveAssemblyStore { get; set; } @@ -267,7 +266,6 @@ protected override void Construct (LlvmIrModule module) uses_assembly_preload = UsesAssemblyPreload, jni_add_native_method_registration_attribute_present = JniAddNativeMethodRegistrationAttributePresent, marshal_methods_enabled = MarshalMethodsEnabled, - managed_marshal_methods_lookup_enabled = ManagedMarshalMethodsLookupEnabled, ignore_split_configs = IgnoreSplitConfigs, number_of_runtime_properties = (uint)(runtimeProperties == null ? 0 : runtimeProperties.Count), package_naming_policy = (uint)PackageNamingPolicy, diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManagedMarshalMethodsLookupGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManagedMarshalMethodsLookupGenerator.cs deleted file mode 100644 index 1c1709567af..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ManagedMarshalMethodsLookupGenerator.cs +++ /dev/null @@ -1,235 +0,0 @@ -#nullable disable - -using System; -using System.Collections.Generic; - -using Microsoft.Android.Build.Tasks; -using Microsoft.Build.Utilities; -using Mono.Cecil; -using Mono.Cecil.Cil; -using Xamarin.Android.Tools; - -namespace Xamarin.Android.Tasks; - -class ManagedMarshalMethodsLookupGenerator -{ - readonly ManagedMarshalMethodsLookupInfo managedMarshalMethodsLookupInfo; - readonly TaskLoggingHelper log; - readonly AndroidTargetArch targetArch; - readonly TypeDefinition functionPointerLookup; - - readonly Dictionary> typeReferenceCache = new (); - - public ManagedMarshalMethodsLookupGenerator ( - TaskLoggingHelper log, - AndroidTargetArch targetArch, - ManagedMarshalMethodsLookupInfo managedMarshalMethodsLookupInfo, - TypeDefinition functionPointerLookup) - { - this.log = log; - this.targetArch = targetArch; - this.managedMarshalMethodsLookupInfo = managedMarshalMethodsLookupInfo; - this.functionPointerLookup = functionPointerLookup; - } - - public void Generate (IEnumerable> marshalMethods) - { - foreach (IList methodList in marshalMethods) { - foreach (MarshalMethodEntry method in methodList) { - managedMarshalMethodsLookupInfo.AddNativeCallbackWrapper (method.NativeCallbackWrapper); - } - } - - foreach (var assemblyInfo in managedMarshalMethodsLookupInfo.AssemblyLookup.Values) { - foreach (var classInfo in assemblyInfo.ClassLookup.Values) { - classInfo.GetFunctionPointerMethod = GenerateGetFunctionPointer (classInfo); - } - - assemblyInfo.GetFunctionPointerMethod = GenerateGetFunctionPointerPerAssembly (assemblyInfo); - } - - GenerateGlobalGetFunctionPointerMethod (); - } - - MethodDefinition GenerateGetFunctionPointer (ManagedMarshalMethodsLookupInfo.ClassLookupInfo classLookup) - { - var declaringType = classLookup.DeclaringType; - log.LogDebugMessage ($"Generating `GetFunctionPointer` for {declaringType.FullName} ({classLookup.MethodLookup.Count} UCO methods)"); - - var intType = ImportReference (declaringType.Module, typeof (int)); - var intPtrType = ImportReference (declaringType.Module, typeof (System.IntPtr)); - - // an "unspeakable" method name is used to avoid conflicts with user-defined methods - var getFunctionPointerMethod = classLookup.GetFunctionPointerMethod = new MethodDefinition ("GetFunctionPointer", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, intPtrType); - - getFunctionPointerMethod.DeclaringType = declaringType; - declaringType.Methods.Add (getFunctionPointerMethod); - - var methodIndexParameter = new ParameterDefinition ("methodIndex", ParameterAttributes.None, intType); - getFunctionPointerMethod.Parameters.Add (methodIndexParameter); - - // we're setting the index here as we are creating the actual switch targets array to guarantee the indexing will be in sync - var targets = new Instruction [classLookup.MethodLookup.Count]; - uint methodIndex = 0; - foreach (var methodLookup in classLookup.MethodLookup.Values) { - methodLookup.Index = methodIndex++; - targets [methodLookup.Index] = Instruction.Create (OpCodes.Ldftn, methodLookup.NativeCallbackWrapper); - } - - var il = getFunctionPointerMethod.Body.GetILProcessor (); - il.Emit (OpCodes.Ldarg, methodIndexParameter); - il.Emit (OpCodes.Switch, targets); - - // The default target - il.Emit (OpCodes.Ldc_I4_M1); - il.Emit (OpCodes.Conv_I); - il.Emit (OpCodes.Ret); - - for (var k = 0; k < targets.Length; k++) { - il.Append (targets [k]); - il.Emit (OpCodes.Ret); - } - - // in the case of private/private protected/protected nested types, we need to generate proxy method(s) in the parent type(s) - // so that we can call the actual GetFunctionPointer method from our assembly-level GetFunctionPointer method - while (declaringType.IsNested && (declaringType.IsNestedPrivate || declaringType.IsNestedFamily || declaringType.IsNestedFamilyAndAssembly)) { - var parentType = declaringType.DeclaringType; - - // an "unspeakable" method name is used to avoid conflicts with user-defined methods - var parentProxyMethod = new MethodDefinition ($"GetFunctionPointer_{declaringType.Name}", MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig, intPtrType); - parentProxyMethod.DeclaringType = parentType; - parentType.Methods.Add (parentProxyMethod); - - var parentMethodIndexParameter = new ParameterDefinition ("methodIndex", ParameterAttributes.None, intType); - parentProxyMethod.Parameters.Add (parentMethodIndexParameter); - - var proxyIl = parentProxyMethod.Body.GetILProcessor (); - proxyIl.Emit (OpCodes.Ldarg, parentMethodIndexParameter); - proxyIl.Emit (OpCodes.Call, getFunctionPointerMethod); - proxyIl.Emit (OpCodes.Ret); - - declaringType = parentType; - getFunctionPointerMethod = parentProxyMethod; - } - - return getFunctionPointerMethod; - } - - MethodDefinition GenerateGetFunctionPointerPerAssembly (ManagedMarshalMethodsLookupInfo.AssemblyLookupInfo assemblyInfo) - { - var module = assemblyInfo.Assembly.MainModule; - - var intType = ImportReference (module, typeof (int)); - var intPtrType = ImportReference (module, typeof (System.IntPtr)); - var objectType = ImportReference (module, typeof (object)); - - var lookupType = new TypeDefinition ("Java.Interop", "__ManagedMarshalMethodsLookupTable__", TypeAttributes.Public | TypeAttributes.Sealed, objectType); - module.Types.Add (lookupType); - - var getFunctionPointerMethod = new MethodDefinition ($"GetFunctionPointer", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, intPtrType); - getFunctionPointerMethod.DeclaringType = lookupType; - lookupType.Methods.Add (getFunctionPointerMethod); - - var classIndexParameter = new ParameterDefinition ("classIndex", ParameterAttributes.None, intType); - getFunctionPointerMethod.Parameters.Add (classIndexParameter); - - var methodIndexParameter = new ParameterDefinition ("methodIndex", ParameterAttributes.None, intType); - getFunctionPointerMethod.Parameters.Add (methodIndexParameter); - - // we're setting the index here as we are creating the actual switch targets array to guarantee the indexing will be in sync - var targets = new Instruction [assemblyInfo.ClassLookup.Count]; - uint classIndex = 0; - foreach (var classInfo in assemblyInfo.ClassLookup.Values) { - classInfo.Index = classIndex++; - targets [classInfo.Index] = Instruction.Create (OpCodes.Call, module.ImportReference (classInfo.GetFunctionPointerMethod)); - } - - var il = getFunctionPointerMethod.Body.GetILProcessor (); - il.Emit (OpCodes.Ldarg, methodIndexParameter); - - il.Emit (OpCodes.Ldarg, classIndexParameter); - il.Emit (OpCodes.Switch, targets); - - // "Default target" - il.Emit (OpCodes.Pop); // methodIndex - il.Emit (OpCodes.Ldc_I4_M1); - il.Emit (OpCodes.Conv_I); - il.Emit (OpCodes.Ret); - - for (int i = 0; i < targets.Length; i++) { - il.Append (targets [i]); // call - il.Emit (OpCodes.Ret); - } - - return getFunctionPointerMethod; - } - - void GenerateGlobalGetFunctionPointerMethod () - { - var module = functionPointerLookup.Module; - - var intType = ImportReference (module, typeof (int)); - var intPtrType = ImportReference (module, typeof (System.IntPtr)); - - var getFunctionPointerMethod = FindMethod (functionPointerLookup, "GetFunctionPointer", parametersCount: 3, required: true); - getFunctionPointerMethod.Body.Instructions.Clear (); - - // we're setting the index here as we are creating the actual switch targets array to guarantee the indexing will be in sync - var targets = new Instruction [managedMarshalMethodsLookupInfo.AssemblyLookup.Count]; - uint assemblyIndex = 0; - foreach (var assemblyInfo in managedMarshalMethodsLookupInfo.AssemblyLookup.Values) { - assemblyInfo.Index = assemblyIndex++; - targets [assemblyInfo.Index] = Instruction.Create (OpCodes.Call, module.ImportReference (assemblyInfo.GetFunctionPointerMethod)); - } - - var il = getFunctionPointerMethod.Body.GetILProcessor (); - il.Emit (OpCodes.Ldarg_1); // classIndex - il.Emit (OpCodes.Ldarg_2); // methodIndex - - il.Emit (OpCodes.Ldarg_0); // assemblyIndex - il.Emit (OpCodes.Switch, targets); - - // The default target - il.Emit (OpCodes.Pop); // methodIndex - il.Emit (OpCodes.Pop); // classIndex - il.Emit (OpCodes.Ldc_I4_M1); - il.Emit (OpCodes.Conv_I); - il.Emit (OpCodes.Ret); - - for (int i = 0; i < targets.Length; i++) { - il.Append (targets [i]); // call - il.Emit (OpCodes.Ret); - } - } - - TypeReference ImportReference (ModuleDefinition module, Type type) - { - if (!typeReferenceCache.TryGetValue (module, out var cache)) { - typeReferenceCache [module] = cache = new (); - } - - if (!cache.TryGetValue (type, out var typeReference)) { - cache [type] = typeReference = module.ImportReference (type); - } - - return typeReference; - } - - MethodDefinition? FindMethod (TypeDefinition type, string methodName, int parametersCount, bool required) - { - log.LogDebugMessage ($"[{targetArch}] Looking for method '{methodName}' with {parametersCount} params in type {type}"); - foreach (MethodDefinition method in type.Methods) { - log.LogDebugMessage ($"[{targetArch}] method: {method.Name}"); - if (method.Parameters.Count == parametersCount && MonoAndroidHelper.StringEquals (methodName, method.Name)) { - log.LogDebugMessage ($"[{targetArch}] match!"); - return method; - } - } - - if (required) { - throw new InvalidOperationException ($"[{targetArch}] Internal error: required method '{methodName}' in type {type} not found"); - } - - return null; - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManagedMarshalMethodsLookupInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManagedMarshalMethodsLookupInfo.cs deleted file mode 100644 index 1394c12f620..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ManagedMarshalMethodsLookupInfo.cs +++ /dev/null @@ -1,189 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; - -using Java.Interop.Tools.Cecil; -using Mono.Cecil; -using Xamarin.Android.Tools; -using Microsoft.Android.Build.Tasks; -using Microsoft.Build.Utilities; - -namespace Xamarin.Android.Tasks; - -/// -/// Manages lookup information for managed marshal methods that can be resolved at runtime. -/// This class builds hierarchical indexes (Assembly -> Class -> Method) to efficiently -/// locate native callback wrappers and their associated metadata. Used when -/// EnableManagedMarshalMethodsLookup is enabled to support dynamic marshal method resolution. -/// -class ManagedMarshalMethodsLookupInfo (TaskLoggingHelper log) -{ - readonly TaskLoggingHelper _log = log; - - /// - /// Gets the top-level lookup dictionary organized by assembly name. - /// Each assembly contains classes, which in turn contain methods with their lookup information. - /// - public Dictionary AssemblyLookup { get; } = new (StringComparer.Ordinal); - - /// - /// Retrieves the hierarchical indexes (assembly, class, method) for a given native callback wrapper method. - /// These indexes can be used at runtime to efficiently locate the method without string comparisons. - /// - /// The native callback wrapper method to get indexes for. - /// A tuple containing the assembly index, class index, and method index. - /// - /// Thrown when the assembly, class, or method is not found in the lookup indexes, or when - /// the indexes have invalid values. - /// - public (uint AssemblyIndex, uint ClassIndex, uint MethodIndex) GetIndex (MethodDefinition nativeCallbackWrapper) - { - var (assemblyName, className, methodName) = GetNames (nativeCallbackWrapper); - - if (!AssemblyLookup.TryGetValue (assemblyName, out var assemblyInfo)) { - throw new InvalidOperationException ($"Assembly '{assemblyName}' not found in the lookup indexes."); - } - - if (!assemblyInfo.ClassLookup.TryGetValue (className, out var classInfo)) { - throw new InvalidOperationException ($"Class '{className}' not found in the lookup indexes."); - } - - if (!classInfo.MethodLookup.TryGetValue (methodName, out var methodLookup)) { - throw new InvalidOperationException ($"Method '{methodName}' not found in the lookup indexes."); - } - - if (assemblyInfo.Index < 0 || classInfo.Index < 0 || methodLookup.Index < 0) { - throw new InvalidOperationException ($"Invalid index values for {assemblyName}/{className}/{methodName}: {assemblyInfo.Index}, {classInfo.Index}, {methodLookup.Index}"); - } - - return (assemblyInfo.Index, classInfo.Index, methodLookup.Index); - } - - /// - /// Adds a native callback wrapper method to the lookup hierarchy. - /// Creates the necessary assembly, class, and method entries if they don't exist. - /// This method is called during the marshal method generation process to build the lookup tables. - /// - /// The native callback wrapper method to add. - public void AddNativeCallbackWrapper (MethodDefinition nativeCallbackWrapper) - { - var (assemblyName, className, methodName) = GetNames (nativeCallbackWrapper); - - // Get or create assembly info - if (!AssemblyLookup.TryGetValue (assemblyName, out var assemblyInfo)) { - AssemblyLookup [assemblyName] = assemblyInfo = new AssemblyLookupInfo (); - } - - // Get or create class info - if (!assemblyInfo.ClassLookup.TryGetValue (className, out var classInfo)) { - assemblyInfo.ClassLookup [className] = classInfo = new ClassLookupInfo (); - } - - // Get or create method info - if (!classInfo.MethodLookup.TryGetValue (methodName, out var methodInfo)) { - classInfo.MethodLookup [methodName] = methodInfo = new MethodLookupInfo (); - } else { - // Method already exists - this shouldn't normally happen - _log.LogDebugMessage ($"Method '{assemblyName}'/'{className}'/'{methodName}' already has an associated UnmanagedCallersOnly method."); - return; - } - - // Populate the lookup info with the actual Cecil objects - assemblyInfo.Assembly = nativeCallbackWrapper.DeclaringType.Module.Assembly; - classInfo.DeclaringType = nativeCallbackWrapper.DeclaringType; - methodInfo.NativeCallbackWrapper = nativeCallbackWrapper; - } - - /// - /// Extracts the assembly name, class name, and method name from a method definition. - /// These names are used as keys in the hierarchical lookup structure. - /// - /// The method definition to extract names from. - /// A tuple containing the assembly name, class name, and method name. - private static (string, string, string) GetNames (MethodDefinition nativeCallback) - { - var type = nativeCallback.DeclaringType; - var assemblyName = type.Module.Assembly.Name.Name; - var className = type.FullName; - var methodName = nativeCallback.Name; - - return (assemblyName, className, methodName); - } - - /// - /// Contains lookup information for an assembly, including its assigned index - /// and the classes it contains. - /// - internal sealed class AssemblyLookupInfo - { - /// - /// Gets or sets the assigned index for this assembly in the lookup tables. - /// Initialized to uint.MaxValue to detect unassigned indexes. - /// - public uint Index { get; set; } = uint.MaxValue; - - /// - /// Gets or sets the assembly definition associated with this lookup info. - /// - public AssemblyDefinition Assembly { get; set; } = null!; - - /// - /// Gets or sets the method used to get function pointers for methods in this assembly. - /// This may be null if the assembly doesn't have such a method. - /// - public MethodDefinition? GetFunctionPointerMethod { get; set; } - - /// - /// Gets the lookup dictionary for classes within this assembly. - /// - public Dictionary ClassLookup { get; } = new (StringComparer.Ordinal); - } - - /// - /// Contains lookup information for a class, including its assigned index - /// and the methods it contains. - /// - internal sealed class ClassLookupInfo - { - /// - /// Gets or sets the assigned index for this class in the lookup tables. - /// Initialized to uint.MaxValue to detect unassigned indexes. - /// - public uint Index { get; set; } = uint.MaxValue; - - /// - /// Gets or sets the type definition associated with this lookup info. - /// - public TypeDefinition DeclaringType { get; set; } = null!; - - /// - /// Gets or sets the method used to get function pointers for methods in this class. - /// This may be null if the class doesn't have such a method. - /// - public MethodDefinition? GetFunctionPointerMethod { get; set; } - - /// - /// Gets the lookup dictionary for methods within this class. - /// - public Dictionary MethodLookup { get; } = new (StringComparer.Ordinal); - } - - /// - /// Contains lookup information for a method, including its assigned index - /// and the native callback wrapper method definition. - /// - internal sealed class MethodLookupInfo - { - /// - /// Gets or sets the assigned index for this method in the lookup tables. - /// Initialized to uint.MaxValue to detect unassigned indexes. - /// - public uint Index { get; set; } = uint.MaxValue; - - /// - /// Gets or sets the native callback wrapper method definition. - /// - public MethodDefinition NativeCallbackWrapper { get; set; } = null!; - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs index 7c6119f5347..3f92fec0c0e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs @@ -61,17 +61,16 @@ static NativeCodeGenStateObject CreateNativeCodeGenState (AndroidTargetArch arch var methods = new List (group.Value.Count); foreach (var method in group.Value) { - var entry = CreateEntry (method, state.ManagedMarshalMethodsLookupInfo); + var entry = CreateEntry (method); methods.Add (entry); } obj.MarshalMethods.Add (group.Key, methods); } - return obj; } - static MarshalMethodEntryObject CreateEntry (MarshalMethodEntry entry, ManagedMarshalMethodsLookupInfo? info) + static MarshalMethodEntryObject CreateEntry (MarshalMethodEntry entry) { var obj = new MarshalMethodEntryObject ( declaringType: CreateDeclaringType (entry.DeclaringType), @@ -84,14 +83,6 @@ static MarshalMethodEntryObject CreateEntry (MarshalMethodEntry entry, ManagedMa registeredMethod: CreateMethodBase (entry.RegisteredMethod) ); - if (info is not null) { - (uint assemblyIndex, uint classIndex, uint methodIndex) = info.GetIndex (entry.NativeCallback); - - obj.NativeCallback.AssemblyIndex = assemblyIndex; - obj.NativeCallback.ClassIndex = classIndex; - obj.NativeCallback.MethodIndex = methodIndex; - } - return obj; } @@ -245,10 +236,6 @@ class MarshalMethodEntryMethodObject : MarshalMethodEntryMethodBaseObject public uint MetadataToken { get; } public List Parameters { get; } - public uint? AssemblyIndex { get; set; } - public uint? ClassIndex { get; set; } - public uint? MethodIndex { get; set; } - public bool HasParameters => Parameters.Count > 0; public MarshalMethodEntryMethodObject (string name, string fullName, MarshalMethodEntryTypeObject declaringType, uint metadataToken, List parameters) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 807632241ba..95c32a89bac 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -28,15 +28,13 @@ sealed class AssemblyImports readonly MarshalMethodsCollection classifier; readonly XAAssemblyResolver resolver; readonly AndroidTargetArch targetArch; - readonly ManagedMarshalMethodsLookupInfo? managedMarshalMethodsLookupInfo; - public MarshalMethodsAssemblyRewriter (TaskLoggingHelper log, AndroidTargetArch targetArch, MarshalMethodsCollection classifier, XAAssemblyResolver resolver, ManagedMarshalMethodsLookupInfo? managedMarshalMethodsLookupInfo) + public MarshalMethodsAssemblyRewriter (TaskLoggingHelper log, AndroidTargetArch targetArch, MarshalMethodsCollection classifier, XAAssemblyResolver resolver) { this.log = log ?? throw new ArgumentNullException (nameof (log)); this.targetArch = targetArch; this.classifier = classifier ?? throw new ArgumentNullException (nameof (classifier));; this.resolver = resolver ?? throw new ArgumentNullException (nameof (resolver));; - this.managedMarshalMethodsLookupInfo = managedMarshalMethodsLookupInfo; } // TODO: do away with broken exception transitions, there's no point in supporting them @@ -130,17 +128,6 @@ public void Rewrite (bool brokenExceptionTransitions) } } - if (managedMarshalMethodsLookupInfo is not null) { - // TODO the code should probably go to different assemblies than Mono.Android (to avoid recursive dependencies) - var rootAssembly = resolver.Resolve ("Mono.Android") ?? throw new InvalidOperationException ($"[{targetArch}] Internal error: unable to load the Mono.Android assembly"); - var managedMarshalMethodsLookupTableType = FindType (rootAssembly, "Java.Interop.ManagedMarshalMethodsLookupTable", required: true); - if (managedMarshalMethodsLookupTableType == null) - throw new ArgumentNullException (nameof (managedMarshalMethodsLookupTableType)); - - var managedMarshalMethodLookupGenerator = new ManagedMarshalMethodsLookupGenerator (log, targetArch, managedMarshalMethodsLookupInfo, managedMarshalMethodsLookupTableType); - managedMarshalMethodLookupGenerator.Generate (classifier.MarshalMethods.Values); - } - foreach (AssemblyDefinition asm in classifier.AssembliesWithMarshalMethods) { string? path = asm.MainModule.FileName; if (String.IsNullOrEmpty (path)) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 950f89530bc..65f70601c62 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -210,7 +210,6 @@ public MarshalMethodAssemblyIndexValuePlaceholder (MarshalMethodInfo mmi, Assemb readonly LlvmIrCallMarker defaultCallMarker; #pragma warning restore CS0414 readonly bool generateEmptyCode; - readonly bool managedMarshalMethodsLookupEnabled; readonly AndroidTargetArch targetArch; readonly NativeCodeGenStateObject? codeGenState; @@ -232,12 +231,11 @@ protected MarshalMethodsNativeAssemblyGenerator (TaskLoggingHelper log, AndroidT /// /// Constructor to be used ONLY when marshal methods are ENABLED /// - protected MarshalMethodsNativeAssemblyGenerator (TaskLoggingHelper log, ICollection uniqueAssemblyNames, NativeCodeGenStateObject codeGenState, bool managedMarshalMethodsLookupEnabled) + protected MarshalMethodsNativeAssemblyGenerator (TaskLoggingHelper log, ICollection uniqueAssemblyNames, NativeCodeGenStateObject codeGenState) : base (log) { this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames)); this.codeGenState = codeGenState ?? throw new ArgumentNullException (nameof (codeGenState)); - this.managedMarshalMethodsLookupEnabled = managedMarshalMethodsLookupEnabled; generateEmptyCode = false; defaultCallMarker = LlvmIrCallMarker.Tail; @@ -686,14 +684,8 @@ void WriteBody (LlvmIrFunctionBody body) LlvmIrLocalVariable getFuncPtrResult = func.CreateLocalVariable (typeof(IntPtr), "get_func_ptr"); body.Load (writeState.GetFunctionPtrVariable, getFuncPtrResult, tbaa: module.TbaaAnyPointer); - List getFunctionPointerArguments; - if (managedMarshalMethodsLookupEnabled) { - (uint assemblyIndex, uint classIndex, uint methodIndex) = GetManagedMarshalMethodsLookupIndexes (nativeCallback); - getFunctionPointerArguments = new List { assemblyIndex, classIndex, methodIndex, backingField }; - } else { - var placeholder = new MarshalMethodAssemblyIndexValuePlaceholder (method, writeState.AssemblyCacheState); - getFunctionPointerArguments = new List { placeholder, method.ClassCacheIndex, nativeCallback.MetadataToken, backingField }; - } + var placeholder = new MarshalMethodAssemblyIndexValuePlaceholder (method, writeState.AssemblyCacheState); + var getFunctionPointerArguments = new List { placeholder, method.ClassCacheIndex, nativeCallback.MetadataToken, backingField }; LlvmIrInstructions.Call call = body.Call (writeState.GetFunctionPtrFunction, arguments: getFunctionPointerArguments, funcPointer: getFuncPtrResult); @@ -722,14 +714,6 @@ void WriteBody (LlvmIrFunctionBody body) body.Ret (nativeFunc.Signature.ReturnType, result); } - (uint assemblyIndex, uint classIndex, uint methodIndex) GetManagedMarshalMethodsLookupIndexes (MarshalMethodEntryMethodObject nativeCallback) - { - var assemblyIndex = nativeCallback.AssemblyIndex ?? throw new InvalidOperationException ("ManagedMarshalMethodsLookupInfo missing"); - var classIndex = nativeCallback.ClassIndex ?? throw new InvalidOperationException ("ManagedMarshalMethodsLookupInfo missing"); - var methodIndex = nativeCallback.MethodIndex ?? throw new InvalidOperationException ("ManagedMarshalMethodsLookupInfo missing"); - - return (assemblyIndex, classIndex, methodIndex); - } } LlvmIrFunctionAttributeSet MakeMarshalMethodAttributeSet (LlvmIrModule module) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorCoreCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorCoreCLR.cs index e57a943afe9..53db30c3aed 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorCoreCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorCoreCLR.cs @@ -14,7 +14,7 @@ public MarshalMethodsNativeAssemblyGeneratorCoreCLR (TaskLoggingHelper log, Andr : base (log, targetArch, uniqueAssemblyNames) {} - public MarshalMethodsNativeAssemblyGeneratorCoreCLR (TaskLoggingHelper log, ICollection uniqueAssemblyNames, NativeCodeGenStateObject codeGenState, bool managedMarshalMethodsLookupEnabled) - : base (log, uniqueAssemblyNames, codeGenState, managedMarshalMethodsLookupEnabled) + public MarshalMethodsNativeAssemblyGeneratorCoreCLR (TaskLoggingHelper log, ICollection uniqueAssemblyNames, NativeCodeGenStateObject codeGenState) + : base (log, uniqueAssemblyNames, codeGenState) {} } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorMonoVM.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorMonoVM.cs index 8409dd6bc61..61f73b56a53 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorMonoVM.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorMonoVM.cs @@ -51,8 +51,8 @@ public MarshalMethodsNativeAssemblyGeneratorMonoVM (TaskLoggingHelper log, Andro this.numberOfAssembliesInApk = numberOfAssembliesInApk; } - public MarshalMethodsNativeAssemblyGeneratorMonoVM (TaskLoggingHelper log, int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, NativeCodeGenStateObject codeGenState, bool managedMarshalMethodsLookupEnabled) - : base (log, uniqueAssemblyNames, codeGenState, managedMarshalMethodsLookupEnabled) + public MarshalMethodsNativeAssemblyGeneratorMonoVM (TaskLoggingHelper log, int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, NativeCodeGenStateObject codeGenState) + : base (log, uniqueAssemblyNames, codeGenState) { this.numberOfAssembliesInApk = numberOfAssembliesInApk; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs index 9bd8618dec1..35a52e9b695 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs @@ -43,7 +43,6 @@ class NativeCodeGenState public TypeDefinitionCache TypeCache { get; } public bool JniAddNativeMethodRegistrationAttributePresent { get; set; } - public ManagedMarshalMethodsLookupInfo? ManagedMarshalMethodsLookupInfo { get; set; } public NativeCodeGenState (AndroidTargetArch arch, TypeDefinitionCache tdCache, XAAssemblyResolver resolver, List allJavaTypes, List javaTypesForJCW, MarshalMethodsCollection? classifier) { diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 6a982714484..98d20ffecbf 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -355,8 +355,6 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' ">False <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' != 'True' ">$(AndroidEnableMarshalMethods) - <_AndroidUseManagedMarshalMethodsLookup Condition=" '$(_AndroidUseManagedMarshalMethodsLookup)' == '' and '$(_AndroidUseMarshalMethods)' == 'True' and '$(_AndroidRuntime)' != 'MonoVM' ">True - <_AndroidUseManagedMarshalMethodsLookup Condition=" '$(_AndroidUseManagedMarshalMethodsLookup)' == '' ">False @@ -1810,7 +1808,6 @@ because xbuild doesn't support framework reference assemblies. RuntimeConfigBinFilePath="$(_BinaryRuntimeConfigPath)" UseAssemblyStore="$(_AndroidUseAssemblyStore)" EnableMarshalMethods="$(_AndroidUseMarshalMethods)" - EnableManagedMarshalMethodsLookup="$(_AndroidUseManagedMarshalMethodsLookup)" CustomBundleConfigFile="$(AndroidBundleConfigurationFile)" TargetsCLR="$(_AndroidUseCLR)" AndroidRuntime="$(_AndroidRuntime)" @@ -1821,7 +1818,6 @@ because xbuild doesn't support framework reference assemblies. 0 || application_config.jni_remapping_replacement_method_index_entry_count > 0; init.marshalMethodsEnabled = application_config.marshal_methods_enabled; - init.managedMarshalMethodsLookupEnabled = application_config.managed_marshal_methods_lookup_enabled; - abort_unless (!init.marshalMethodsEnabled || init.managedMarshalMethodsLookupEnabled, - "Managed marshal methods lookup must be enabled if marshal methods are enabled"); // GC threshold is 90% of the max GREF count init.grefGcThreshold = static_cast(AndroidSystem::get_gref_gc_threshold ()); diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index fd0ea6a12fd..81f5bd91532 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -230,7 +230,6 @@ struct ApplicationConfig uint32_t jni_remapping_replacement_type_count; uint32_t jni_remapping_replacement_method_index_entry_count; const char *android_package_name; - bool managed_marshal_methods_lookup_enabled; bool have_assembly_store; }; diff --git a/src/native/clr/xamarin-app-stub/application_dso_stub.cc b/src/native/clr/xamarin-app-stub/application_dso_stub.cc index 3d2f2eae958..c8f5ebd00da 100644 --- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc @@ -67,7 +67,6 @@ const ApplicationConfig application_config = { .jni_remapping_replacement_type_count = 2, .jni_remapping_replacement_method_index_entry_count = 2, .android_package_name = android_package_name, - .managed_marshal_methods_lookup_enabled = false, .have_assembly_store = false, }; diff --git a/src/native/common/include/managed-interface.hh b/src/native/common/include/managed-interface.hh index ccf6c8b4f6b..ca29461dc05 100644 --- a/src/native/common/include/managed-interface.hh +++ b/src/native/common/include/managed-interface.hh @@ -35,7 +35,6 @@ namespace xamarin::android { bool jniRemappingInUse; bool marshalMethodsEnabled; jobject grefGCUserPeerable; - bool managedMarshalMethodsLookupEnabled; jnienv_propagate_uncaught_exception_fn propagateUncaughtExceptionFn; jnienv_register_jni_natives_fn registerJniNativesFn; }; diff --git a/src/native/mono/monodroid/monodroid-glue-internal.hh b/src/native/mono/monodroid/monodroid-glue-internal.hh index e1d1f446d07..ce15f833774 100644 --- a/src/native/mono/monodroid/monodroid-glue-internal.hh +++ b/src/native/mono/monodroid/monodroid-glue-internal.hh @@ -195,7 +195,6 @@ namespace xamarin::android::internal static void get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept; static void get_function_pointer_at_startup (uint32_t mono_image_index, uint32_t class_token, uint32_t method_token, void*& target_ptr) noexcept; static void get_function_pointer_at_runtime (uint32_t mono_image_index, uint32_t class_token, uint32_t method_token, void*& target_ptr) noexcept; - static void get_function_pointer_placeholder (uint32_t mono_image_index, uint32_t class_token, uint32_t method_token, void*& target_ptr) noexcept; #endif // def RELEASE #if defined (DEBUG) diff --git a/src/native/mono/monodroid/monodroid-glue.cc b/src/native/mono/monodroid/monodroid-glue.cc index 99c81025e37..00ff460bb7b 100644 --- a/src/native/mono/monodroid/monodroid-glue.cc +++ b/src/native/mono/monodroid/monodroid-glue.cc @@ -705,13 +705,7 @@ MonodroidRuntime::mono_runtime_init ([[maybe_unused]] JNIEnv *env, [[maybe_unuse #if defined (RELEASE) if (application_config.marshal_methods_enabled) { - if (!application_config.managed_marshal_methods_lookup_enabled) { - xamarin_app_init (env, get_function_pointer_at_startup); - } else { - // xamarin_app_init will be called with the actual get_function_pointer method - // via p/invoke from JNIEnvInit.Initialize - xamarin_app_init (env, get_function_pointer_placeholder); - } + xamarin_app_init (env, get_function_pointer_at_startup); } #endif // def RELEASE && def ANDROID && def NET } @@ -833,7 +827,6 @@ MonodroidRuntime::init_android_runtime (JNIEnv *env, jclass runtimeClass, jobjec init.jniAddNativeMethodRegistrationAttributePresent = application_config.jni_add_native_method_registration_attribute_present ? 1 : 0; init.jniRemappingInUse = application_config.jni_remapping_replacement_type_count > 0 || application_config.jni_remapping_replacement_method_index_entry_count > 0; init.marshalMethodsEnabled = application_config.marshal_methods_enabled; - init.managedMarshalMethodsLookupEnabled = application_config.managed_marshal_methods_lookup_enabled; java_System_identityHashCode = env->GetStaticMethodID (java_System, "identityHashCode", "(Ljava/lang/Object;)I"); @@ -1568,8 +1561,7 @@ MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass kl } #if defined (RELEASE) - // when managed marshal methods lookups are enabled, xamarin_app_init is called via p/invoke from JNIEnvInit.Initialize - if (application_config.marshal_methods_enabled && !application_config.managed_marshal_methods_lookup_enabled) { + if (application_config.marshal_methods_enabled) { xamarin_app_init (env, get_function_pointer_at_runtime); } #endif // def RELEASE && def ANDROID && def NET diff --git a/src/native/mono/monodroid/xamarin-android-app-context.cc b/src/native/mono/monodroid/xamarin-android-app-context.cc index 33e3a023f37..9ef193ddeb7 100644 --- a/src/native/mono/monodroid/xamarin-android-app-context.cc +++ b/src/native/mono/monodroid/xamarin-android-app-context.cc @@ -132,12 +132,3 @@ MonodroidRuntime::get_function_pointer_at_runtime (uint32_t mono_image_index, ui { get_function_pointer (mono_image_index, class_index, method_token, target_ptr); } - -void -MonodroidRuntime::get_function_pointer_placeholder (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept -{ - target_ptr = nullptr; - Helpers::abort_application ( - std::format ("The callback for get_function_pointer has not been initialized yet. Failed to resolve ({}, {}, {})."sv, mono_image_index, class_index, method_token) - ); -} diff --git a/src/native/mono/xamarin-app-stub/application_dso_stub.cc b/src/native/mono/xamarin-app-stub/application_dso_stub.cc index 8d01b2ff57c..6ed48fac62c 100644 --- a/src/native/mono/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/mono/xamarin-app-stub/application_dso_stub.cc @@ -67,7 +67,6 @@ const ApplicationConfig application_config = { .jni_remapping_replacement_method_index_entry_count = 2, .mono_components_mask = MonoComponent::None, .android_package_name = android_package_name, - .managed_marshal_methods_lookup_enabled = false, }; const char* const mono_aot_mode_name = "normal"; diff --git a/src/native/mono/xamarin-app-stub/xamarin-app.hh b/src/native/mono/xamarin-app-stub/xamarin-app.hh index 2a21c1e827b..d2dacdd9263 100644 --- a/src/native/mono/xamarin-app-stub/xamarin-app.hh +++ b/src/native/mono/xamarin-app-stub/xamarin-app.hh @@ -254,7 +254,6 @@ struct ApplicationConfig uint32_t jni_remapping_replacement_method_index_entry_count; MonoComponent mono_components_mask; const char *android_package_name; - bool managed_marshal_methods_lookup_enabled; }; struct DSOApkEntry diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index d062deeabec..df8b64cee47 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -2462,38 +2462,6 @@ public void NativeAOTSample () } } - [Test] - public void AppStartsWithManagedMarshalMethodsLookupEnabled ([Values (AndroidRuntime.CoreCLR, AndroidRuntime.NativeAOT)] AndroidRuntime runtime) - { - const bool isRelease = true; - if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { - return; - } - - // TODO: Segfaults on Mono currently - if (runtime == AndroidRuntime.MonoVM) { - Assert.Ignore ("MonoVM segfaults in this test."); - } - - var proj = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { - IsRelease = isRelease, - }; - proj.SetRuntime (runtime); - proj.SetProperty ("AndroidUseMarshalMethods", "true"); - proj.SetProperty ("_AndroidUseManagedMarshalMethodsLookup", "true"); - - using var builder = CreateApkBuilder (); - builder.Save (proj); - - var dotnet = new DotNetCLI (Path.Combine (Root, builder.ProjectDirectory, proj.ProjectFilePath)); - Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed"); - Assert.IsTrue (dotnet.Run (), "`dotnet run --no-build` should succeed"); - - bool didLaunch = WaitForActivityToStart (proj.PackageName, "MainActivity", - Path.Combine (Root, builder.ProjectDirectory, "logcat.log"), ActivityStartTimeoutInSeconds); - Assert.IsTrue (didLaunch, "Activity should have started."); - } - [Test] public void StartAndroidActivityRespectsAndroidDeviceUserId () { From 84541adb744b1312abc139aef26c8b95d2f2b24d Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 09:08:43 +0200 Subject: [PATCH 3/3] Restore JNIEnvInit interop import Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 676a2066-2e01-4963-86bc-942e444a22da --- src/Mono.Android/Android.Runtime/JNIEnvInit.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 88c2de947e7..9e6c6fa599d 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using Java.Interop;