From 35f4e240b11cff5d6cc9777378bee5d3f1d03d64 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 29 Mar 2022 22:01:22 +0200 Subject: [PATCH 01/79] Implement marshal methods LLVM IR executable code generator. The point of this commit is only to generate code and make sure it's valid as far as compiling and linking are concerned. The code has not been tested at run time as not all the infrastructure on the Xamarin.Android side is implemented yet. This is on purpose, to keep PRs smaller. The majority of this PR introduces various classes, enums and structures related to code generation. Support for various LLVM IR instructions is limited only to those we actually use and only to elements of those constructions that we use. As such, it's not a general purpose code generator which allows us to make some assumptions and take some shortcuts (without compromising correctness and validity of the generated code) Portions of the PR (the native type handling system) are to be treated as proof-of-concept as they are not as optimized (design wise) as they should be. The reason for this limitation is that it requires modifying the previous LLVM IR data generation code and it would contribute to this PR's already substantial size. The next PR in the series will take care of that rewrite as well as it will focus on implementing the runtime side of marshal methods, making it possible to actually run applications which use marshal methods. What this PR implements is the following: * LLVM IR * function and instruction attributes * function parameter (declaration/definition) and argument (runtime) handling * function variable (including parameters) handling, including unnamed local variables * support for native function signatures and pointers to functions * The ret, store, load, icmp, br, call and phi instructions * Marshal method generator * managed to JNI signature and symbol name translations --- .../Tasks/GenerateJavaStubs.cs | 2 + .../LlvmIrGenerator/Arm32LlvmIrGenerator.cs | 14 + .../LlvmIrGenerator/Arm64LlvmIrGenerator.cs | 14 + .../LlvmIrGenerator/FunctionAttributes.cs | 793 ++++++++++++++++++ .../LlvmIrGenerator/IStructureInfo.cs | 2 + .../LlvmFunctionAttributeSet.cs | 50 ++ .../LlvmIrGenerator/LlvmIrCallMarkers.cs | 10 + .../LlvmIrGenerator/LlvmIrComposer.cs | 4 + .../LlvmIrGenerator/LlvmIrFunction.cs | 189 +++++ .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 544 ++++++++++++ .../LlvmIrGenerator/LlvmIrGenerator.cs | 93 +- .../LlvmIrGenerator/LlvmIrIcmpCond.cs | 16 + .../LlvmIrGenerator/LlvmIrVariable.cs | 34 + .../LlvmIrGenerator/LlvmIrVariableOptions.cs | 9 + .../LlvmIrVariableReference.cs | 50 ++ .../LlvmNativeFunctionSignature.cs | 34 + .../LlvmIrGenerator/NativeClassAttribute.cs | 9 + .../LlvmIrGenerator/StructureInfo.cs | 4 +- .../LlvmIrGenerator/TypeUtilities.cs | 6 + .../LlvmIrGenerator/X64LlvmIrGenerator.cs | 15 + .../LlvmIrGenerator/X86LlvmIrGenerator.cs | 16 + .../MarshalMethodsNativeAssemblyGenerator.cs | 545 +++++++++++- src/monodroid/jni/application_dso_stub.cc | 2 +- src/monodroid/jni/mono-image-loader.hh | 2 +- src/monodroid/jni/monodroid-glue-internal.hh | 2 +- .../jni/xamarin-android-app-context.cc | 8 +- src/monodroid/jni/xamarin-app-marshaling.cc | 51 +- src/monodroid/jni/xamarin-app.hh | 4 +- 28 files changed, 2468 insertions(+), 54 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrCallMarkers.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrIcmpCond.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeClassAttribute.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 52317659f12..e46955630bf 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -237,6 +237,7 @@ void Run (DirectoryAssemblyResolver res) string managedKey = type.FullName.Replace ('/', '.'); string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.'); + Console.WriteLine ($"##G2: {type.FullName} -> {javaKey}"); acw_map.Write (type.GetPartialAssemblyQualifiedName (cache)); acw_map.Write (';'); acw_map.Write (javaKey); @@ -384,6 +385,7 @@ bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCac bool ok = true; foreach (var t in javaTypes) { + Console.WriteLine ($"##G0: JCW for {t.FullName}"); if (t.IsInterface) { // Interfaces are in typemap but they shouldn't have JCW generated for them continue; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs index 63cf752d6a6..5f60214ea33 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm32LlvmIrGenerator.cs @@ -16,6 +16,12 @@ class Arm32LlvmIrGenerator : LlvmIrGenerator public override int PointerSize => 4; protected override string Triple => "armv7-unknown-linux-android"; // NDK appends API level, we don't need that + static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { + new FramePointerFunctionAttribute ("all"), + new TargetCpuFunctionAttribute ("generic"), + new TargetFeaturesFunctionAttribute ("+armv7-a,+d32,+dsp,+fp64,+neon,+thumb-mode,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"), + }; + public Arm32LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) : base (arch, output, fileName) {} @@ -25,5 +31,13 @@ protected override void AddModuleFlagsMetadata (List flagsFi base.AddModuleFlagsMetadata (flagsFields); flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "min_enum_size", 4)); } + + protected override void InitFunctionAttributes () + { + base.InitFunctionAttributes (); + + FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); + FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs index 7e3a4ce43e4..68ca5fd19e8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/Arm64LlvmIrGenerator.cs @@ -16,6 +16,12 @@ class Arm64LlvmIrGenerator : LlvmIrGenerator public override int PointerSize => 8; protected override string Triple => "aarch64-unknown-linux-android"; // NDK appends API level, we don't need that + static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { + new FramePointerFunctionAttribute ("non-leaf"), + new TargetCpuFunctionAttribute ("generic"), + new TargetFeaturesFunctionAttribute ("+neon,+outline-atomics"), + }; + public Arm64LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) : base (arch, output, fileName) {} @@ -29,5 +35,13 @@ protected override void AddModuleFlagsMetadata (List flagsFi flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-all", 0)); flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "sign-return-address-with-bkey", 0)); } + + protected override void InitFunctionAttributes () + { + base.InitFunctionAttributes (); + + FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); + FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs new file mode 100644 index 00000000000..f71e1aa0401 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs @@ -0,0 +1,793 @@ +using System; +using System.Text; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + // Not all attributes are currently used throughout the code, but we define them call for potential future use. + // Documentation can be found here: https://llvm.org/docs/LangRef.html#function-attributes + abstract class LLVMFunctionAttribute + { + public string Name { get; } + public bool Quoted { get; } + public bool SupportsParams { get; } + public bool ParamsAreOptional { get; } + public bool HasValueAsignment { get; } + + protected LLVMFunctionAttribute (string name, bool quoted, bool supportsParams, bool optionalParams, bool hasValueAssignment) + { + Name = EnsureNonEmptyParameter (nameof (name), name); + + if (supportsParams && hasValueAssignment) { + throw new InvalidOperationException ($"Function attribute '{name}' cannot have both parameters and an assigned value"); + } + + ParamsAreOptional = optionalParams; + SupportsParams = supportsParams; + HasValueAsignment = hasValueAssignment; + Quoted = quoted; + } + + public string Render () + { + var sb = new StringBuilder (); + + if (Quoted) { + sb.Append ('"'); + } + + sb.Append (Name); + + if (Quoted) { + sb.Append ('"'); + } + + if (SupportsParams) { + if (!ParamsAreOptional || HasOptionalParams ()) { + sb.Append ('('); + RenderParams (sb); + sb.Append (')'); + } + } else if (HasValueAsignment) { + sb.Append ('='); + if (Quoted) { + sb.Append ('"'); + } + + RenderAssignedValue (sb); + + if (Quoted) { + sb.Append ('"'); + } + + } + + return sb.ToString (); + } + + protected virtual void RenderParams (StringBuilder sb) + {} + + protected virtual void RenderAssignedValue (StringBuilder sb) + {} + + protected virtual bool HasOptionalParams () + { + return false; + } + + protected string EnsureNonEmptyParameter (string name, string value) + { + if (String.IsNullOrEmpty (name)) { + throw new ArgumentException ("must not be null or empty", name); + } + + return value; + } + } + + abstract class LLVMFlagFunctionAttribute : LLVMFunctionAttribute + { + protected LLVMFlagFunctionAttribute (string name, bool quoted = false) + : base (name, quoted, supportsParams: false, optionalParams: false, hasValueAssignment: false) + {} + } + + class AlignstackFunctionAttribute : LLVMFunctionAttribute + { + uint alignment; + + public AlignstackFunctionAttribute (uint powerOfTwoAlignment) + : base ("alignstack", quoted: false, supportsParams: true, optionalParams: false, hasValueAssignment: false) + { + if ((powerOfTwoAlignment % 2) != 0) { + throw new ArgumentException ("must be power of two", nameof (powerOfTwoAlignment)); + } + + alignment = powerOfTwoAlignment; + } + + protected override void RenderParams (StringBuilder sb) + { + sb.Append (alignment); + } + } + + class AllocFamilyFunctionAttribute : LLVMFunctionAttribute + { + string family; + + public AllocFamilyFunctionAttribute (string familyName) + : base ("alloc-family", quoted: true, supportsParams: false, optionalParams: false, hasValueAssignment: true) + { + family = EnsureNonEmptyParameter (nameof (familyName), familyName); + } + + protected override void RenderAssignedValue (StringBuilder sb) + { + sb.Append (family); + } + } + + class AllockindFunctionAttribute : LLVMFunctionAttribute + { + string kind; + + public AllockindFunctionAttribute (string allocKind) + : base ("allockind", quoted: false, supportsParams: true, optionalParams: false, hasValueAssignment: false) + { + kind = EnsureNonEmptyParameter (nameof (allocKind), allocKind); + } + + protected override void RenderParams (StringBuilder sb) + { + sb.Append ('"'); + sb.Append (kind); + sb.Append ('"'); + } + } + + class AllocsizeFunctionAttribute : LLVMFunctionAttribute + { + uint elementSize; + uint? numberOfElements; + + public AllocsizeFunctionAttribute (uint elementSize, uint? numberOfElements = null) + : base ("allocsize", quoted: false, supportsParams: true, optionalParams: false, hasValueAssignment: false) + { + this.elementSize = elementSize; + this.numberOfElements = numberOfElements; + } + + protected override void RenderParams (StringBuilder sb) + { + sb.Append (elementSize); + if (!numberOfElements.HasValue) { + return; + } + + sb.Append (", "); + sb.Append (numberOfElements.Value); + } + } + + class AlwaysinlineFunctionAttribute : LLVMFlagFunctionAttribute + { + public AlwaysinlineFunctionAttribute () + : base ("alwaysinline") + {} + } + + class ArgmemonlyFunctionAttribute : LLVMFlagFunctionAttribute + { + public ArgmemonlyFunctionAttribute () + : base ("argmemonly") + {} + } + + class BuiltinFunctionAttribute : LLVMFlagFunctionAttribute + { + public BuiltinFunctionAttribute () + : base ("builtin") + {} + } + + class ColdFunctionAttribute : LLVMFlagFunctionAttribute + { + public ColdFunctionAttribute () + : base ("cold") + {} + } + + class ConvergentFunctionAttribute : LLVMFlagFunctionAttribute + { + public ConvergentFunctionAttribute () + : base ("convergent") + {} + } + + class DisableSanitizerInstrumentationFunctionAttribute : LLVMFlagFunctionAttribute + { + public DisableSanitizerInstrumentationFunctionAttribute () + : base ("disable_sanitizer_instrumentation") + {} + } + + class DontcallErrorFunctionAttribute : LLVMFlagFunctionAttribute + { + public DontcallErrorFunctionAttribute () + : base ("dontcall-error", quoted: true) + {} + } + + class DontcallWarnFunctionAttribute : LLVMFlagFunctionAttribute + { + public DontcallWarnFunctionAttribute () + : base ("dontcall-warn", quoted: true) + {} + } + + class FramePointerFunctionAttribute : LLVMFunctionAttribute + { + string fpMode; + + public FramePointerFunctionAttribute (string fpMode = "none") + : base ("frame-pointer", quoted: true, supportsParams: false, optionalParams: false, hasValueAssignment: true) + { + switch (fpMode) { + case "none": + case "non-leaf": + case "all": + this.fpMode = fpMode; + break; + + default: + throw new ArgumentException ($"unsupported mode value '{fpMode}'", nameof (fpMode)); + } + } + + protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (fpMode); + } + + class HotFunctionAttribute : LLVMFlagFunctionAttribute + { + public HotFunctionAttribute () + : base ("hot") + {} + } + + class InaccessiblememonlyFunctionAttribute : LLVMFlagFunctionAttribute + { + public InaccessiblememonlyFunctionAttribute () + : base ("inaccessiblememonly") + {} + } + + class InaccessiblememOrArgmemonlyFunctionAttribute : LLVMFlagFunctionAttribute + { + public InaccessiblememOrArgmemonlyFunctionAttribute () + : base ("inaccessiblemem_or_argmemonly") + {} + } + + class InlinehintFunctionAttribute : LLVMFlagFunctionAttribute + { + public InlinehintFunctionAttribute () + : base ("inlinehint") + {} + } + + class JumptableFunctionAttribute : LLVMFlagFunctionAttribute + { + public JumptableFunctionAttribute () + : base ("jumptable") + {} + } + + class MinsizeFunctionAttribute : LLVMFlagFunctionAttribute + { + public MinsizeFunctionAttribute () + : base ("minsize") + {} + } + + class NakedFunctionAttribute : LLVMFlagFunctionAttribute + { + public NakedFunctionAttribute () + : base ("naked") + {} + } + + class NoInlineLineTablesFunctionAttribute : LLVMFlagFunctionAttribute + { + public NoInlineLineTablesFunctionAttribute () + : base ("no-inline-line-tables", quoted: true) + {} + } + + class NoJumpTablesFunctionAttribute : LLVMFlagFunctionAttribute + { + public NoJumpTablesFunctionAttribute () + : base ("no-jump-tables") + {} + } + + class NobuiltinFunctionAttribute : LLVMFlagFunctionAttribute + { + public NobuiltinFunctionAttribute () + : base ("nobuiltin") + {} + } + + class NoduplicateFunctionAttribute : LLVMFlagFunctionAttribute + { + public NoduplicateFunctionAttribute () + : base ("noduplicate") + {} + } + + class NofreeFunctionAttribute : LLVMFlagFunctionAttribute + { + public NofreeFunctionAttribute () + : base ("nofree") + {} + } + + class NoimplicitfloatFunctionAttribute : LLVMFlagFunctionAttribute + { + public NoimplicitfloatFunctionAttribute () + : base ("noimplicitfloat") + {} + } + + class NoinlineFunctionAttribute : LLVMFlagFunctionAttribute + { + public NoinlineFunctionAttribute () + : base ("noinline") + {} + } + + class NomergeFunctionAttribute : LLVMFlagFunctionAttribute + { + public NomergeFunctionAttribute () + : base ("nomerge") + {} + } + + class NonlazybindFunctionAttribute : LLVMFlagFunctionAttribute + { + public NonlazybindFunctionAttribute () + : base ("nonlazybind") + {} + } + + class NoprofileFunctionAttribute : LLVMFlagFunctionAttribute + { + public NoprofileFunctionAttribute () + : base ("noprofile") + {} + } + + class NoredzoneFunctionAttribute : LLVMFlagFunctionAttribute + { + public NoredzoneFunctionAttribute () + : base ("noredzone") + {} + } + + class IndirectTlsSegRefsFunctionAttribute : LLVMFlagFunctionAttribute + { + public IndirectTlsSegRefsFunctionAttribute () + : base ("indirect-tls-seg-refs") + {} + } + + class NoreturnFunctionAttribute : LLVMFlagFunctionAttribute + { + public NoreturnFunctionAttribute () + : base ("noreturn") + {} + } + + class NorecurseFunctionAttribute : LLVMFlagFunctionAttribute + { + public NorecurseFunctionAttribute () + : base ("norecurse") + {} + } + + class WillreturnFunctionAttribute : LLVMFlagFunctionAttribute + { + public WillreturnFunctionAttribute () + : base ("willreturn") + {} + } + + class NosyncFunctionAttribute : LLVMFlagFunctionAttribute + { + public NosyncFunctionAttribute () + : base ("nosync") + {} + } + + class NounwindFunctionAttribute : LLVMFlagFunctionAttribute + { + public NounwindFunctionAttribute () + : base ("nounwind") + {} + } + + class NosanitizeBoundsFunctionAttribute : LLVMFlagFunctionAttribute + { + public NosanitizeBoundsFunctionAttribute () + : base ("nosanitize_bounds") + {} + } + + class NosanitizeCoverageFunctionAttribute : LLVMFlagFunctionAttribute + { + public NosanitizeCoverageFunctionAttribute () + : base ("nosanitize_coverage") + {} + } + + class NullPointerIsValidFunctionAttribute : LLVMFlagFunctionAttribute + { + public NullPointerIsValidFunctionAttribute () + : base ("null_pointer_is_valid") + {} + } + + class OptforfuzzingFunctionAttribute : LLVMFlagFunctionAttribute + { + public OptforfuzzingFunctionAttribute () + : base ("optforfuzzing") + {} + } + + class OptnoneFunctionAttribute : LLVMFlagFunctionAttribute + { + public OptnoneFunctionAttribute () + : base ("optnone") + {} + } + + class OptsizeFunctionAttribute : LLVMFlagFunctionAttribute + { + public OptsizeFunctionAttribute () + : base ("optsize") + {} + } + + class PatchableFunctionFunctionAttribute : LLVMFlagFunctionAttribute + { + public PatchableFunctionFunctionAttribute () + : base ("patchable-function", quoted: true) + {} + } + + class ProbeStackFunctionAttribute : LLVMFlagFunctionAttribute + { + public ProbeStackFunctionAttribute () + : base ("probe-stack") + {} + } + + class ReadnoneFunctionAttribute : LLVMFlagFunctionAttribute + { + public ReadnoneFunctionAttribute () + : base ("readnone") + {} + } + + class ReadonlyFunctionAttribute : LLVMFlagFunctionAttribute + { + public ReadonlyFunctionAttribute () + : base ("readonly") + {} + } + + class StackProbeSizeFunctionAttribute : LLVMFlagFunctionAttribute + { + public StackProbeSizeFunctionAttribute () + : base ("stack-probe-size", quoted: true) + {} + } + + class NoStackArgProbeFunctionAttribute : LLVMFlagFunctionAttribute + { + public NoStackArgProbeFunctionAttribute () + : base ("no-stack-arg-probe") + {} + } + + class WriteonlyFunctionAttribute : LLVMFlagFunctionAttribute + { + public WriteonlyFunctionAttribute () + : base ("writeonly") + {} + } + + class ReturnsTwiceFunctionAttribute : LLVMFlagFunctionAttribute + { + public ReturnsTwiceFunctionAttribute () + : base ("returns_twice") + {} + } + + class SafestackFunctionAttribute : LLVMFlagFunctionAttribute + { + public SafestackFunctionAttribute () + : base ("safestack") + {} + } + + class SanitizeAddressFunctionAttribute : LLVMFlagFunctionAttribute + { + public SanitizeAddressFunctionAttribute () + : base ("sanitize_address") + {} + } + + class SanitizeMemoryFunctionAttribute : LLVMFlagFunctionAttribute + { + public SanitizeMemoryFunctionAttribute () + : base ("sanitize_memory") + {} + } + + class SanitizeThreadFunctionAttribute : LLVMFlagFunctionAttribute + { + public SanitizeThreadFunctionAttribute () + : base ("sanitize_thread") + {} + } + + class SanitizeHwaddressFunctionAttribute : LLVMFlagFunctionAttribute + { + public SanitizeHwaddressFunctionAttribute () + : base ("sanitize_hwaddress") + {} + } + + class SanitizeMemtagFunctionAttribute : LLVMFlagFunctionAttribute + { + public SanitizeMemtagFunctionAttribute () + : base ("sanitize_memtag") + {} + } + + class SpeculativeLoadHardeningFunctionAttribute : LLVMFlagFunctionAttribute + { + public SpeculativeLoadHardeningFunctionAttribute () + : base ("speculative_load_hardening") + {} + } + + class SpeculatableFunctionAttribute : LLVMFlagFunctionAttribute + { + public SpeculatableFunctionAttribute () + : base ("speculatable") + {} + } + + class SspFunctionAttribute : LLVMFlagFunctionAttribute + { + public SspFunctionAttribute () + : base ("ssp") + {} + } + + class SspstrongFunctionAttribute : LLVMFlagFunctionAttribute + { + public SspstrongFunctionAttribute () + : base ("sspstrong") + {} + } + + class SspreqFunctionAttribute : LLVMFlagFunctionAttribute + { + public SspreqFunctionAttribute () + : base ("sspreq") + {} + } + + class StrictfpFunctionAttribute : LLVMFlagFunctionAttribute + { + public StrictfpFunctionAttribute () + : base ("strictfp") + {} + } + + class DenormalFpMathFunctionAttribute : LLVMFlagFunctionAttribute + { + public DenormalFpMathFunctionAttribute () + : base ("denormal-fp-math", quoted: true) + {} + } + + class DenormalFpMathF32FunctionAttribute : LLVMFlagFunctionAttribute + { + public DenormalFpMathF32FunctionAttribute () + : base ("denormal-fp-math-f32", quoted: true) + {} + } + + class ThunkFunctionAttribute : LLVMFlagFunctionAttribute + { + public ThunkFunctionAttribute () + : base ("thunk", quoted: true) + {} + } + + class TlsLoadHoistFunctionAttribute : LLVMFlagFunctionAttribute + { + public TlsLoadHoistFunctionAttribute () + : base ("tls-load-hoist") + {} + } + + class UwtableFunctionAttribute : LLVMFunctionAttribute + { + bool? isSync; + + public UwtableFunctionAttribute (bool? sync = null) + : base ("uwtable", quoted: false, supportsParams: true, optionalParams: true, hasValueAssignment: false) + { + isSync = sync; + } + + protected override bool HasOptionalParams () => isSync.HasValue; + + protected override void RenderParams (StringBuilder sb) + { + if (!isSync.HasValue) { + throw new InvalidOperationException ("Unable to render parameters, none given"); + } + + sb.Append (isSync.Value ? "sync" : "async"); + } + } + + class NocfCheckFunctionAttribute : LLVMFlagFunctionAttribute + { + public NocfCheckFunctionAttribute () + : base ("nocf_check") + {} + } + + class ShadowcallstackFunctionAttribute : LLVMFlagFunctionAttribute + { + public ShadowcallstackFunctionAttribute () + : base ("shadowcallstack") + {} + } + + class MustprogressFunctionAttribute : LLVMFlagFunctionAttribute + { + public MustprogressFunctionAttribute () + : base ("mustprogress") + {} + } + + class WarnStackSizeFunctionAttribute : LLVMFunctionAttribute + { + uint threshold; + + public WarnStackSizeFunctionAttribute (uint threshold) + : base ("warn-stack-size", quoted: true, supportsParams: false, optionalParams: false, hasValueAssignment: true) + { + this.threshold = threshold; + } + + protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (threshold); + } + + class VscaleRangeFunctionAttribute : LLVMFunctionAttribute + { + uint min; + uint? max; + + public VscaleRangeFunctionAttribute (uint min, uint? max = null) + : base ("vscale_range", quoted: false, supportsParams: true, optionalParams: false, hasValueAssignment: false) + { + this.min = min; + this.max = max; + } + + protected override void RenderParams (StringBuilder sb) + { + sb.Append (min); + if (!max.HasValue) { + return; + } + + sb.Append (", "); + sb.Append (max.Value); + } + } + + class MinLegalVectorWidthFunctionAttribute : LLVMFunctionAttribute + { + uint size; + + public MinLegalVectorWidthFunctionAttribute (uint size) + : base ("min-legal-vector-width", quoted: true, supportsParams: false, optionalParams: false, hasValueAssignment: true) + { + this.size = size; + } + + protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (size); + } + + class StackProtectorBufferSizeFunctionAttribute : LLVMFunctionAttribute + { + uint size; + + public StackProtectorBufferSizeFunctionAttribute (uint size) + : base ("stack-protector-buffer-size", quoted: true, supportsParams: false, optionalParams: false, hasValueAssignment: true) + { + this.size = size; + } + + protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (size); + } + + class TargetCpuFunctionAttribute : LLVMFunctionAttribute + { + string cpu; + + public TargetCpuFunctionAttribute (string cpu) + : base ("target-cpu", quoted: true, supportsParams: false, optionalParams: false, hasValueAssignment: true) + { + this.cpu = EnsureNonEmptyParameter (nameof (cpu), cpu); + } + + protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (cpu); + } + + class TuneCpuFunctionAttribute : LLVMFunctionAttribute + { + string cpu; + + public TuneCpuFunctionAttribute (string cpu) + : base ("tune-cpu", quoted: true, supportsParams: false, optionalParams: false, hasValueAssignment: true) + { + this.cpu = EnsureNonEmptyParameter (nameof (cpu), cpu); + } + + protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (cpu); + } + + class TargetFeaturesFunctionAttribute : LLVMFunctionAttribute + { + string features; + + public TargetFeaturesFunctionAttribute (string features) + : base ("target-features", quoted: true, supportsParams: false, optionalParams: false, hasValueAssignment: true) + { + this.features = EnsureNonEmptyParameter (nameof (features), features); + } + + protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (features); + } + + class NoTrappingMathFunctionAttribute : LLVMFunctionAttribute + { + bool yesno; + + public NoTrappingMathFunctionAttribute (bool yesno) + : base ("no-trapping-math", quoted: true, supportsParams: false, optionalParams: false, hasValueAssignment: true) + { + this.yesno = yesno; + } + + protected override void RenderAssignedValue (StringBuilder sb) => sb.Append (yesno.ToString ().ToLowerInvariant ()); + } + + class StackrealignFunctionAttribute : LLVMFlagFunctionAttribute + { + public StackrealignFunctionAttribute () + : base ("stackrealign", quoted: true) + {} + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs index b083e4e907c..afd17fefda5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/IStructureInfo.cs @@ -7,6 +7,8 @@ interface IStructureInfo Type Type { get; } ulong Size { get; } int MaxFieldAlignment { get; } + string Name { get; } + string NativeTypeDesignator { get; } void RenderDeclaration (LlvmIrGenerator generator); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs new file mode 100644 index 00000000000..4ba4ed9be75 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmFunctionAttributeSet.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + class LlvmFunctionAttributeSet : IEnumerable + { + HashSet attributes; + + public LlvmFunctionAttributeSet () + { + attributes = new HashSet (); + } + + public void Add (LLVMFunctionAttribute attr) + { + if (attr == null) { + throw new ArgumentNullException (nameof (attr)); + } + + // TODO: implement uniqueness checks + attributes.Add (attr); + } + + public void Add (LlvmFunctionAttributeSet sourceSet) + { + if (sourceSet == null) { + throw new ArgumentNullException (nameof (sourceSet)); + } + + foreach (LLVMFunctionAttribute attr in sourceSet) { + Add (attr); + } + } + + public string Render () + { + List list = attributes.ToList (); + list.Sort ((LLVMFunctionAttribute a, LLVMFunctionAttribute b) => a.Name.CompareTo (b.Name)); + + return String.Join (" ", list.Select (a => a.Render ())); + } + + public IEnumerator GetEnumerator () => attributes.GetEnumerator (); + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrCallMarkers.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrCallMarkers.cs new file mode 100644 index 00000000000..48acbcd5d64 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrCallMarkers.cs @@ -0,0 +1,10 @@ +namespace Xamarin.Android.Tasks.LLVMIR +{ + enum LlvmIrCallMarker + { + None, + Tail, + MustTail, + NoTail, + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs index 5347a1912cf..734ece97e18 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs @@ -22,6 +22,7 @@ public void Write (AndroidTargetArch arch, StreamWriter output, string fileName) { LlvmIrGenerator generator = LlvmIrGenerator.Create (arch, output, fileName); + InitGenerator (generator); MapStructures (generator); generator.WriteFileTop (); generator.WriteStructureDeclarations (); @@ -50,6 +51,9 @@ protected ulong HashName (string name, bool is64Bit) return (ulong)XXH32.DigestOf (nameBytes, 0, nameBytes.Length); } + protected virtual void InitGenerator (LlvmIrGenerator generator) + {} + /// /// Initialize the composer. It needs to allocate and populate all the structures that /// are used by the composer, before they can be mapped by the generator. The code here diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs new file mode 100644 index 00000000000..219491437d5 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunction.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Reflection; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + class LlvmIrFunctionLocalVariable : LlvmIrVariable + { + public LlvmIrFunctionLocalVariable (Type type, string? name = null, bool isNativePointer = false) + : base (type, name, signature: null, isNativePointer: isNativePointer) + {} + + public LlvmIrFunctionLocalVariable (LlvmNativeFunctionSignature nativeFunction, string? name = null, bool isNativePointer = false) + : base (typeof(LlvmNativeFunctionSignature), name, nativeFunction, isNativePointer: isNativePointer) + { + if (nativeFunction == null) { + throw new ArgumentNullException(nameof (nativeFunction)); + } + } + + public LlvmIrFunctionLocalVariable (LlvmIrVariable variable, string? name = null, bool isNativePointer = false) + : base (variable, name, isNativePointer) + {} + } + + class LlvmIrFunctionParameter : LlvmIrFunctionLocalVariable + { + public bool IsCplusPlusReference { get; } + + public LlvmIrFunctionParameter (Type type, string? name = null, bool isNativePointer = false, bool isCplusPlusReference = false) + : base (type, name, isNativePointer) + { + IsCplusPlusReference = isCplusPlusReference; + } + + public LlvmIrFunctionParameter (LlvmNativeFunctionSignature nativeFunction, string? name = null, bool isNativePointer = false, bool isCplusPlusReference = false) + : base (nativeFunction, name, isNativePointer) + { + IsCplusPlusReference = isCplusPlusReference; + } + } + + class LlvmIrFunctionArgument + { + public object Value { get; } + public Type Type { get; } + + public LlvmIrFunctionArgument (Type type, object? value = null) + { + Type = type ?? throw new ArgumentNullException (nameof (type)); + + if (value != null && value.GetType () != type) { + throw new ArgumentException ($"value type '{value.GetType ()}' does not match the argument type '{type}'"); + } + + Value = value; + } + + public LlvmIrFunctionArgument (LlvmIrFunctionLocalVariable variable) + { + Type = typeof(LlvmIrFunctionLocalVariable); + Value = variable; + } + } + + /// + /// Describes a native function to be emitted and keeps code emitting state between calls to various generator + /// methods. + /// + class LlvmIrFunction + { + const string Indent1 = LlvmIrGenerator.Indent; + const string Indent2 = LlvmIrGenerator.Indent + LlvmIrGenerator.Indent; + + // Function signature + public string Name { get; } + public Type ReturnType { get; } + public int AttributeSetID { get; } + public IList? Parameters { get; } + public string ImplicitFuncTopLabel { get; } + public IList? ParameterVariables { get; } + + // Function writing state + public string Indent { get; private set; } = LlvmIrGenerator.Indent; + + // Used for unnamed function parameters as well as unnamed local variables + uint localSlot = 0; + uint indentLevel = 1; + + public LlvmIrFunction (string name, Type returnType, int attributeSetID, List? parameters = null) + { + if (String.IsNullOrEmpty (name)) { + throw new ArgumentException ("must not be null or empty", nameof (name)); + } + Name = name; + ReturnType = returnType ?? throw new ArgumentNullException (nameof (returnType)); + AttributeSetID = attributeSetID; + Parameters = parameters?.Select (p => EnsureParameterName (p))?.ToList ()?.AsReadOnly (); + ParameterVariables = Parameters?.Select (p => new LlvmIrFunctionLocalVariable (p.Type, p.Name))?.ToList ()?.AsReadOnly (); + + // Unnamed local variables need to start from the value which equals [number_of_unnamed_parameters] + 1, + // since there's an implicit label created for the top of the function whose name is `[number_of_unnamed_parameters]` + ImplicitFuncTopLabel = localSlot.ToString (CultureInfo.InvariantCulture); + localSlot++; + + LlvmIrFunctionParameter EnsureParameterName (LlvmIrFunctionParameter parameter) + { + if (parameter == null) { + throw new InvalidOperationException ("null parameters aren't allowed"); + } + + if (!String.IsNullOrEmpty (parameter.Name)) { + return parameter; + } + + string name = GetNextSlotName (); + if (parameter.NativeFunction != null) { + return new LlvmIrFunctionParameter (parameter.NativeFunction, name, parameter.IsNativePointer, parameter.IsCplusPlusReference); + } + return new LlvmIrFunctionParameter (parameter.Type, name, parameter.IsNativePointer, parameter.IsCplusPlusReference); + } + } + + public LlvmIrFunctionLocalVariable MakeLocalVariable (Type type, string? name = null) + { + if (String.IsNullOrEmpty (name)) { + name = GetNextSlotName (); + } + + return new LlvmIrFunctionLocalVariable (type, name); + } + + public LlvmIrFunctionLocalVariable MakeLocalVariable (LlvmIrVariable variable, string? name = null) + { + if (String.IsNullOrEmpty (name)) { + name = GetNextSlotName (); + } + + return new LlvmIrFunctionLocalVariable (variable, name); + } + + public void IncreaseIndent () + { + indentLevel++; + Indent = MakeIndent (indentLevel); + } + + public void DecreaseIndent () + { + if (indentLevel == 0) { + return; + } + + indentLevel--; + Indent = MakeIndent (indentLevel); + } + + string MakeIndent (uint level) + { + switch (level) { + case 0: + return String.Empty; + + case 1: + return Indent1; + + case 2: + return Indent2; + + default: + var sb = new StringBuilder (); + for (uint i = 0; i < level; i++) { + sb.Append (LlvmIrGenerator.Indent); + } + return sb.ToString (); + } + } + + string GetNextSlotName () + { + string name = $"{localSlot.ToString (CultureInfo.InvariantCulture)}"; + localSlot++; + return name; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs new file mode 100644 index 00000000000..d5537296a7e --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -0,0 +1,544 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + abstract partial class LlvmIrGenerator + { + // In code generated by clang, function attributes are determined based on the compiler optimization, + // security arguments, architecture specific flags and so on. For our needs we will have but a + // handful of such sets, based on what clang generates for our native runtime. As such, there is nothing + // "smart" about how we select the attributes, they must match the compiler output for XA runtime, that's all. + // + // Sets are initialized here with the options common to all architectures, the rest is added in the architecture + // specific derived classes. + // + public const int FunctionAttributesXamarinAppInit = 0; + public const int FunctionAttributesJniMethods = 1; + public const int FunctionAttributesCall = 2; + + protected readonly Dictionary FunctionAttributes = new Dictionary (); + + bool codeOutputInitialized = false; + + /// + /// Writes the function definition up to the opening curly brace + /// + public void WriteFunctionStart (LlvmIrFunction function) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + LlvmFunctionAttributeSet? attributes = null; + if (function.AttributeSetID >= 0 && !FunctionAttributes.TryGetValue (function.AttributeSetID, out attributes)) { + throw new InvalidOperationException ($"Function '{function.Name}' refers to attribute set that does not exist (ID: {function.AttributeSetID})"); + } + + Output.WriteLine (); + if (attributes != null) { + WriteCommentLine ($"Function attributes: {attributes.Render ()}"); + } + + Output.Write ($"define {GetKnownIRType (function.ReturnType)} @{function.Name} ("); + WriteFunctionParameters (function.Parameters, writeNames: true); + Output.Write(") local_unnamed_addr "); + if (attributes != null) { + Output.Write ($"#{function.AttributeSetID}"); + } + Output.WriteLine (); + Output.WriteLine ("{"); + } + + void CodeRenderType (LlvmIrVariable variable, StringBuilder? builder = null) + { + if (variable.NativeFunction != null) { + if (builder == null) { + WriteFunctionSignature (variable.NativeFunction); + } else { + builder.Append (RenderFunctionSignature (variable.NativeFunction)); + } + return; + } + + string extraPointer = variable.IsNativePointer ? "*" : String.Empty; + string irType = $"{GetKnownIRType (variable.Type)}{extraPointer}"; + if (builder == null) { + Output.Write (irType); + } else { + builder.Append (irType); + } + } + + void WriteFunctionParameters (IList? parameters, bool writeNames) + { + string rendered = RenderFunctionParameters (parameters, writeNames); + if (String.IsNullOrEmpty (rendered)) { + return; + } + + Output.Write (rendered); + } + + public string RenderFunctionParameters (IList? parameters, bool writeNames) + { + if (parameters == null || parameters.Count == 0) { + return String.Empty; + } + + var sb = new StringBuilder (); + bool first = true; + foreach (LlvmIrFunctionParameter p in parameters) { + if (!first) { + sb.Append (", "); + } else { + first = false; + } + + CodeRenderType (p, sb); + + if (writeNames) { + sb.Append ($" %{p.Name}"); + } + } + + return sb.ToString (); + } + + public void WriteFunctionSignature (LlvmNativeFunctionSignature sig, bool isPointer = true) + { + Output.Write (RenderFunctionSignature (sig, isPointer)); + } + + public string RenderFunctionSignature (LlvmNativeFunctionSignature sig, bool isPointer = true) + { + if (sig == null) { + throw new ArgumentNullException (nameof (sig)); + } + + var sb = new StringBuilder (); + sb.Append (GetKnownIRType (sig.ReturnType)); + sb.Append (" ("); + sb.Append (RenderFunctionParameters (sig.Parameters, writeNames: false)); + sb.Append (")"); + if (isPointer) { + sb.Append ('*'); + } + + return sb.ToString (); + } + + /// + /// Writes the epilogue of a function, including the return statement if the function return + /// type is void. + /// + public void WriteFunctionEnd (LlvmIrFunction function) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + if (function.ReturnType == typeof (void)) { + EmitReturnInstruction (function); + } + + Output.WriteLine ("}"); + } + + /// + /// Emits the ret statement using as the returned value. If + /// is null, void is used as the return value. + /// + public void EmitReturnInstruction (LlvmIrFunction function, LlvmIrFunctionLocalVariable? retVar = null) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + string ret = retVar != null ? $"{GetKnownIRType(retVar.Type)} %{retVar.Name}" : "void"; + Output.WriteLine ($"{function.Indent}ret {ret}"); + } + + /// + /// Emits the store instruction (https://llvm.org/docs/LangRef.html#store-instruction), which stores data from a local + /// variable into either local or global destination. If types of and + /// differ, is bitcast to the type of . It is responsibility of the + /// caller to make sure the two types are compatible and/or convertible to each other. + /// + public void EmitStoreInstruction (LlvmIrFunction function, LlvmIrFunctionLocalVariable source, LlvmIrVariableReference destination) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + // TODO: implement bitcast, if necessary + Output.Write ($"{function.Indent}store "); + CodeRenderType (source); + Output.Write ($" %{source.Name}, "); + CodeRenderType (destination); + Output.WriteLine ($"* {destination.Reference}, align {GetTypeSize (destination.Type)}"); + } + + /// + /// Emits the load instruction (https://llvm.org/docs/LangRef.html#load-instruction) + /// + public LlvmIrFunctionLocalVariable EmitLoadInstruction (LlvmIrFunction function, LlvmIrVariableReference source, string? resultVariableName = null) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + var sb = new StringBuilder (); + CodeRenderType (source, sb); + + string variableType = sb.ToString (); + LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (source, resultVariableName); + Output.WriteLine ($"{function.Indent}%{result.Name} = load {variableType}, {variableType}* @{source.Name}, align {PointerSize}"); + + return result; + } + + /// + /// Emits the icmp comparison instruction (https://llvm.org/docs/LangRef.html#icmp-instruction) + /// + public LlvmIrFunctionLocalVariable EmitIcmpInstruction (LlvmIrFunction function, LlvmIrIcmpCond cond, LlvmIrVariableReference variable, string expectedValue, string? resultVariableName = null) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + string condOp; + switch (cond) { + case LlvmIrIcmpCond.Equal: // equal + condOp = "eq"; + break; + + case LlvmIrIcmpCond.NotEqual: // not equal + condOp = "ne"; + break; + + case LlvmIrIcmpCond.UnsignedGreaterThan: // unsigned greater than + condOp = "ugt"; + break; + + case LlvmIrIcmpCond.UnsignedGreaterOrEqual: // unsigned greater or equal + condOp = "uge"; + break; + + case LlvmIrIcmpCond.UnsignedLessThan: // unsigned less than + condOp = "ult"; + break; + + case LlvmIrIcmpCond.UnsignedLessOrEqual: // unsigned less or equal + condOp = "ule"; + break; + + case LlvmIrIcmpCond.SignedGreaterThan: // signed greater than, + condOp = "sgt"; + break; + + case LlvmIrIcmpCond.SignedGreaterOrEqual: // signed greater or equal + condOp = "sge"; + break; + + case LlvmIrIcmpCond.SignedLessThan: // signed less than + condOp = "slt"; + break; + + case LlvmIrIcmpCond.SignedLessOrEqual: // signed less or equal + condOp = "sle"; + break; + + default: + throw new InvalidOperationException ($"Unsupported `icmp` conditional '{cond}'"); + } + + var sb = new StringBuilder (); + CodeRenderType (variable, sb); + + string variableType = sb.ToString (); + LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (variable.Type, resultVariableName); + + Output.WriteLine ($"{function.Indent}%{result.Name} = icmp {condOp} {variableType} {variable.Reference}, {expectedValue}"); + + return result; + } + + public void EmitBrInstruction (LlvmIrFunction function, LlvmIrVariableReference condVariable, string labelTrue, string labelFalse) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + Output.WriteLine ($"{function.Indent}br i1 {condVariable.Reference}, label %{labelTrue}, label %{labelFalse}"); + } + + public void EmitBrInstruction (LlvmIrFunction function, string label) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + Output.WriteLine ($"{function.Indent}br label %{label}"); + } + + public void EmitLabel (LlvmIrFunction function, string labelName) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + Output.WriteLine ($"{labelName}:"); + } + + public LlvmIrFunctionLocalVariable? EmitCall (LlvmIrFunction function, LlvmIrVariableReference targetRef, List? arguments = null, + string? resultVariableName = null, LlvmIrCallMarker marker = LlvmIrCallMarker.Tail, int AttributeSetID = FunctionAttributesCall) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + if (targetRef == null) { + throw new ArgumentNullException (nameof (targetRef)); + } + + LlvmNativeFunctionSignature targetSignature = targetRef.NativeFunction; + if (targetSignature == null) { + throw new ArgumentException ("must be reference to native function", nameof (targetRef)); + } + + if (targetSignature.Parameters.Count > 0) { + if (arguments == null) { + throw new ArgumentNullException (nameof (arguments)); + } + + if (targetSignature.Parameters.Count != arguments.Count) { + throw new ArgumentException ($"number of passed parameters ({arguments.Count}) does not match number of parameters in function signature ({targetSignature.Parameters.Count})", nameof (arguments)); + } + } + + bool returnsValue = targetSignature.ReturnType != typeof(void); + LlvmIrFunctionLocalVariable? result = null; + + Output.Write (function.Indent); + if (returnsValue) { + result = function.MakeLocalVariable (targetSignature.ReturnType, resultVariableName); + Output.Write ($"%{result.Name} = "); + } + + switch (marker) { + case LlvmIrCallMarker.Tail: + Output.Write ("tail "); + break; + + case LlvmIrCallMarker.MustTail: + Output.Write ("musttail "); + break; + + case LlvmIrCallMarker.NoTail: + Output.Write ("notail "); + break; + + case LlvmIrCallMarker.None: + break; + + default: + throw new InvalidOperationException ($"Unsupported call marker '{marker}'"); + } + + Output.Write ($"call {GetKnownIRType (targetSignature.ReturnType)} {targetRef.Reference} ("); + + if (targetSignature.Parameters.Count > 0) { + for (int i = 0; i < targetSignature.Parameters.Count; i++) { + LlvmIrFunctionParameter parameter = targetSignature.Parameters[i]; + LlvmIrFunctionArgument argument = arguments[i]; + + AssertValidType (i, parameter, argument); + + if (i > 0) { + Output.Write (", "); + } + + string extra = parameter.IsNativePointer ? "*" : String.Empty; + string paramType = $"{GetKnownIRType (parameter.Type)}{extra}"; + Output.Write ($"{paramType} "); + + if (argument.Value is LlvmIrFunctionLocalVariable variable) { + Output.Write ($"%{variable.Name}"); + } else if (parameter.Type.IsNativePointer () || parameter.IsNativePointer) { + if (parameter.IsCplusPlusReference) { + Output.Write ("nonnull "); + } + + Output.Write ($"align {PointerSize} dereferenceable({PointerSize}) "); + + if (argument.Value is LlvmIrVariableReference variableRef) { + bool needBitcast = parameter.Type != argument.Type; + + if (needBitcast) { + Output.Write ("bitcast ("); + CodeRenderType (variableRef); + Output.Write ("* "); + } + + Output.Write (variableRef.Reference); + + if (needBitcast) { + Output.Write ($" to {paramType})"); + } + } else { + throw new InvalidOperationException ($"Unexpected pointer type in argument {i}, '{argument.Type}'"); + } + } else { + Output.Write (argument.Value.ToString ()); + } + } + } + + Output.Write (")"); + + if (AttributeSetID >= 0) { + if (!FunctionAttributes.ContainsKey (AttributeSetID)) { + throw new InvalidOperationException ($"Unknown attribute set ID {AttributeSetID}"); + } + Output.Write ($" #{AttributeSetID}"); + } + Output.WriteLine (); + + return result; + + static void AssertValidType (int index, LlvmIrFunctionParameter parameter, LlvmIrFunctionArgument argument) + { + if (argument.Type == typeof(LlvmIrFunctionLocalVariable) || argument.Type == typeof(LlvmIrVariableReference)) { + return; + } + + if (parameter.Type != typeof(IntPtr)) { + if (argument.Type != parameter.Type) { + ThrowException (); + } + return; + } + + if (argument.Type.IsNativePointer ()) { + return; + } + + if (typeof(LlvmIrVariable).IsAssignableFrom (argument.Type) && + argument.Value is LlvmIrVariable variable && + (variable.IsNativePointer || variable.NativeFunction != null)) { + return; + } + + ThrowException (); + + void ThrowException () + { + throw new InvalidOperationException ($"Argument {index} type '{argument.Type}' does not match the expected function parameter type '{parameter.Type}'"); + } + } + } + + /// + /// Emits the phi instruction (https://llvm.org/docs/LangRef.html#phi-instruction) for a function pointer type + /// + public LlvmIrFunctionLocalVariable EmitPhiInstruction (LlvmIrFunction function, LlvmIrVariableReference target, List<(LlvmIrVariableReference variableRef, string label)> pairs, string? resultVariableName = null) + { + if (function == null) { + throw new ArgumentNullException (nameof (function)); + } + + LlvmIrFunctionLocalVariable result = function.MakeLocalVariable (target, resultVariableName); + Output.Write ($"{function.Indent}%{result.Name} = phi "); + CodeRenderType (target); + + bool first = true; + foreach ((LlvmIrVariableReference variableRef, string label) in pairs) { + if (first) { + first = false; + Output.Write (' '); + } else { + Output.Write (", "); + } + + Output.Write ($"[{variableRef.Reference}, %{label}]"); + } + Output.WriteLine (); + + return result; + } + + public void InitCodeOutput () + { + if (codeOutputInitialized) { + return; + } + + InitFunctionAttributes (); + InitCodeMetadata (); + codeOutputInitialized = true; + } + + protected virtual void InitCodeMetadata () + { + MetadataManager.Add ("llvm.linker.options"); + } + + protected virtual void InitFunctionAttributes () + { + FunctionAttributes[FunctionAttributesXamarinAppInit] = new LlvmFunctionAttributeSet { + new MinLegalVectorWidthFunctionAttribute (0), + new MustprogressFunctionAttribute (), + new NofreeFunctionAttribute (), + new NorecurseFunctionAttribute (), + new NosyncFunctionAttribute (), + new NoTrappingMathFunctionAttribute (true), + new NounwindFunctionAttribute (), + new SspstrongFunctionAttribute (), + new StackProtectorBufferSizeFunctionAttribute (8), + new UwtableFunctionAttribute (), + new WillreturnFunctionAttribute (), + new WriteonlyFunctionAttribute (), + }; + + FunctionAttributes[FunctionAttributesJniMethods] = new LlvmFunctionAttributeSet { + new MinLegalVectorWidthFunctionAttribute (0), + new MustprogressFunctionAttribute (), + new NoTrappingMathFunctionAttribute (true), + new NounwindFunctionAttribute (), + new SspstrongFunctionAttribute (), + new StackProtectorBufferSizeFunctionAttribute (8), + new UwtableFunctionAttribute (), + }; + + FunctionAttributes[FunctionAttributesCall] = new LlvmFunctionAttributeSet { + new NounwindFunctionAttribute (), + }; + } + + void WriteAttributeSets () + { + if (!codeOutputInitialized) { + return; + } + + WriteSet (FunctionAttributesXamarinAppInit, Output); + WriteSet (FunctionAttributesJniMethods, Output); + WriteSet (FunctionAttributesCall, Output); + + Output.WriteLine (); + + void WriteSet (int id, TextWriter output) + { + output.Write ($"attributes #{id} = {{ "); + foreach (LLVMFunctionAttribute attr in FunctionAttributes[id]) { + output.Write (attr.Render ()); + output.Write (' '); + } + output.WriteLine ("}"); + } + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index 88dd83860f5..48f5db5964d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -10,7 +10,7 @@ namespace Xamarin.Android.Tasks.LLVMIR /// /// Base class for all classes which implement architecture-specific code generators. /// - abstract class LlvmIrGenerator + abstract partial class LlvmIrGenerator { internal sealed class StructureBodyWriterOptions { @@ -78,6 +78,7 @@ public StringSymbolInfo (string symbolName, ulong size) { typeof (double), "double" }, { typeof (string), "i8*" }, { typeof (IntPtr), "i8*" }, + { typeof (void), "void" }, }; // https://llvm.org/docs/LangRef.html#single-value-types @@ -150,6 +151,9 @@ public StringSymbolInfo (string symbolName, ulong size) List structures = new List (); Dictionary stringSymbolCache = new Dictionary (StringComparer.Ordinal); + LlvmIrMetadataItem llvmModuleFlags; + + public const string Indent = "\t"; protected abstract string DataLayout { get; } public abstract int PointerSize { get; } @@ -159,7 +163,6 @@ public StringSymbolInfo (string symbolName, ulong size) public TextWriter Output { get; } public AndroidTargetArch TargetArch { get; } - protected string Indent => "\t"; protected LlvmIrMetadataManager MetadataManager { get; } protected LlvmIrGenerator (AndroidTargetArch arch, TextWriter output, string fileName) @@ -231,15 +234,22 @@ public string MapManagedTypeToIR (Type type, out ulong size) { Type actualType = GetActualType (type); string irType = EnsureIrType (actualType); - if (!typeSizes.TryGetValue (actualType, out size)) { - if (actualType == typeof (string) || actualType == typeof (IntPtr)) { + size = GetTypeSize (actualType); + + return irType; + } + + ulong GetTypeSize (Type actualType) + { + if (!typeSizes.TryGetValue (actualType, out ulong size)) { + if (actualType == typeof (string) || actualType == typeof (IntPtr) || actualType == typeof (LlvmNativeFunctionSignature)) { size = (ulong)PointerSize; } else { - throw new InvalidOperationException ($"Unsupported managed type {type}"); + throw new InvalidOperationException ($"Unsupported managed type {actualType}"); } } - return irType; + return size; } /// @@ -278,26 +288,73 @@ public static string MapManagedTypeToNative (Type type) return type.GetShortName (); } + public string GetIRType (out ulong size, T? value = default) + { + if (typeof(T) == typeof(LlvmNativeFunctionSignature)) { + if (value == null) { + throw new ArgumentNullException (nameof (value)); + } + + size = (ulong)PointerSize; + return RenderFunctionSignature ((LlvmNativeFunctionSignature)(object)value); + } + + return MapManagedTypeToIR (out size); + } + + public string GetKnownIRType (Type type) + { + if (type == null) { + throw new ArgumentNullException (nameof (type)); + } + + if (type.IsNativeClass ()) { + IStructureInfo si = GetStructureInfo (type); + return $"%{si.NativeTypeDesignator}.{si.Name}"; + } + + return MapManagedTypeToIR (type); + } + + public string GetValue (T value) + { + if (typeof(T) == typeof(LlvmNativeFunctionSignature)) { + if (value == null) { + throw new ArgumentNullException (nameof (value)); + } + + var v = (LlvmNativeFunctionSignature)(object)value; + return v.FieldValue?.ToString () ?? v.ToString (); + } + + return value?.ToString () ?? String.Empty; + } + /// /// Initialize the generator. It involves adding required LLVM IR module metadata (such as data model specification, /// code generation flags etc) /// - public virtual void Init () + protected virtual void Init () { - LlvmIrMetadataItem flags = MetadataManager.Add ("llvm.module.flags"); + llvmModuleFlags = MetadataManager.Add ("llvm.module.flags"); LlvmIrMetadataItem ident = MetadataManager.Add ("llvm.ident"); var flagsFields = new List (); AddModuleFlagsMetadata (flagsFields); foreach (LlvmIrMetadataItem item in flagsFields) { - flags.AddReferenceField (item.Name); + llvmModuleFlags.AddReferenceField (item.Name); } LlvmIrMetadataItem identValue = MetadataManager.AddNumbered ($"Xamarin.Android {XABuildConfig.XamarinAndroidBranch} @ {XABuildConfig.XamarinAndroidCommitHash}"); ident.AddReferenceField (identValue.Name); } + protected void AddLlvmModuleFlag (LlvmIrMetadataItem flag) + { + llvmModuleFlags.AddReferenceField (flag.Name); + } + /// /// Since LLVM IR is strongly typed, it requires each structure to be properly declared before it is /// used throughout the code. This method uses reflection to scan the managed type @@ -463,7 +520,7 @@ public void WriteStructureArray (StructureInfo info, ulong count, LlvmIrVa { bool named = WriteStructureArrayStart (info, null, options, symbolName, initialComment); string pointerAsterisk = isArrayOfPointers ? "*" : String.Empty; - Output.Write ($"[{count} x %struct.{info.Name}{pointerAsterisk}] zeroinitializer"); + Output.Write ($"[{count} x %{info.NativeTypeDesignator}.{info.Name}{pointerAsterisk}] zeroinitializer"); WriteStructureArrayEnd (info, symbolName, (ulong)count, named, skipFinalComment: true, isArrayOfPointers: isArrayOfPointers); } @@ -488,7 +545,7 @@ public void WriteStructureArray (StructureInfo info, IList (info, instances, options, symbolName, initialComment, arrayOutput); int count = instances != null ? instances.Count : 0; - arrayOutput.Write ($"[{count} x %struct.{info.Name}] "); + arrayOutput.Write ($"[{count} x %{info.NativeTypeDesignator}.{info.Name}] "); if (instances != null) { var bodyWriterOptions = new StructureBodyWriterOptions ( writeFieldComment: true, @@ -667,7 +724,7 @@ void WriteStructureField (StructureInfo info, StructureInstance instanc void WriteStructureBody (StructureInfo info, StructureInstance? instance, StructureBodyWriterOptions options, Action? nestedStructureWriter = null) { TextWriter structureOutput = EnsureOutput (options.StructureOutput); - structureOutput.Write ($"{options.StructIndent}%struct.{info.Name} "); + structureOutput.Write ($"{options.StructIndent}%{info.NativeTypeDesignator}.{info.Name} "); if (instance != null) { structureOutput.WriteLine ("{"); @@ -843,7 +900,7 @@ public void WritePackedStructureArray (StructureInfo info, IList (string symbolName, T value, LlvmIrVariableOptions } WriteEOL (); - string irType = MapManagedTypeToIR (out ulong size); + string irType = GetIRType (out ulong size, value); WriteGlobalSymbolStart (symbolName, options); - Output.WriteLine ($"{irType} {value}, align {size}"); + Output.WriteLine ($"{irType} {GetValue (value)}, align {size}"); } /// @@ -1238,6 +1295,8 @@ public virtual void WriteFileEnd () { Output.WriteLine (); + WriteAttributeSets (); + foreach (LlvmIrMetadataItem metadata in MetadataManager.Items) { Output.WriteLine (metadata.Render ()); } @@ -1255,10 +1314,10 @@ public void WriteStructureDeclarations () } } - public void WriteStructureDeclarationStart (string name, bool forOpaqueType = false) + public void WriteStructureDeclarationStart (string typeDesignator, string name, bool forOpaqueType = false) { WriteEOL (); - Output.Write ($"%struct.{name} = type "); + Output.Write ($"%{typeDesignator}.{name} = type "); if (forOpaqueType) { Output.WriteLine ("opaque"); } else { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrIcmpCond.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrIcmpCond.cs new file mode 100644 index 00000000000..68310783d42 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrIcmpCond.cs @@ -0,0 +1,16 @@ +namespace Xamarin.Android.Tasks.LLVMIR +{ + enum LlvmIrIcmpCond + { + Equal, + NotEqual, + UnsignedGreaterThan, + UnsignedGreaterOrEqual, + UnsignedLessThan, + UnsignedLessOrEqual, + SignedGreaterThan, + SignedGreaterOrEqual, + SignedLessThan, + SignedLessOrEqual, + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs new file mode 100644 index 00000000000..0abda63bfdd --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariable.cs @@ -0,0 +1,34 @@ +using System; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + /// + /// Base class for all the variable (local and global) as well as function parameter classes. + /// + abstract class LlvmIrVariable + { + public LlvmNativeFunctionSignature? NativeFunction { get; } + public string? Name { get; } + public Type Type { get; } + + // Used when we need a pointer to pointer (etc) or when the type itself is not a pointer but we need one + // in a given context (e.g. function parameters) + public bool IsNativePointer { get; } + + protected LlvmIrVariable (Type type, string name, LlvmNativeFunctionSignature? signature, bool isNativePointer) + { + Type = type ?? throw new ArgumentNullException (nameof (type)); + Name = name; + NativeFunction = signature; + IsNativePointer = isNativePointer; + } + + protected LlvmIrVariable (LlvmIrVariable variable, string name, bool isNativePointer) + { + Type = variable?.Type ?? throw new ArgumentNullException (nameof (variable)); + Name = name; + NativeFunction = variable.NativeFunction; + IsNativePointer = isNativePointer; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs index c4abbc2a5a6..0c0b3c121b4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableOptions.cs @@ -41,6 +41,15 @@ class LlvmIrVariableOptions Writability = LlvmIrWritability.Writable, }; + /// + /// Options for a local, writable, insignificant address symbol + /// + public static readonly LlvmIrVariableOptions LocalWritableInsignificantAddr = new LlvmIrVariableOptions { + Linkage = LlvmIrLinkage.Internal, + Writability = LlvmIrWritability.Writable, + AddressSignificance = LlvmIrAddressSignificance.Unnamed, + }; + /// /// Options for a local, read-only, string which will end up in a strings ELF section /// diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs new file mode 100644 index 00000000000..628014eeb2a --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrVariableReference.cs @@ -0,0 +1,50 @@ +using System; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + /// + /// References either a local or global variable. + /// + class LlvmIrVariableReference : LlvmIrVariable + { + public string Reference { get; } + + public LlvmIrVariableReference (Type type, string name, bool isGlobal, bool isNativePointer = false) + : base (type, name, signature: null, isNativePointer: isNativePointer) + { + if (String.IsNullOrEmpty (name)) { + throw new ArgumentException ("must not be null or empty", nameof (name)); + } + Reference = MakeReference (isGlobal, name); + } + + public LlvmIrVariableReference (LlvmNativeFunctionSignature signature, string name, bool isGlobal, bool isNativePointer = false) + : base (typeof(LlvmNativeFunctionSignature), name, signature, isNativePointer) + { + if (signature == null) { + throw new ArgumentNullException (nameof (signature)); + } + + if (String.IsNullOrEmpty (name)) { + throw new ArgumentException ("must not be null or empty", nameof (name)); + } + + Reference = MakeReference (isGlobal, name); + } + + public LlvmIrVariableReference (LlvmIrVariable variable, bool isGlobal, bool isNativePointer = false) + : base (variable, variable?.Name, isNativePointer) + { + if (String.IsNullOrEmpty (variable?.Name)) { + throw new ArgumentException ("variable name must not be null or empty", nameof (variable)); + } + + Reference = MakeReference (isGlobal, variable?.Name); + } + + string MakeReference (bool isGlobal, string name) + { + return $"{(isGlobal ? '@' : '%')}{Name}"; + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs new file mode 100644 index 00000000000..01471c8199a --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmNativeFunctionSignature.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Android.Tasks.LLVMIR +{ + /// + /// Contains signature/description of a native function. All the types used for parameters or return value must + /// be mappable to LLVM IR types. This class can be used to describe pointers to functions which have no corresponding + /// managed method (e.g. `xamarin_app_init` used by marshal methods). Additionally, an optional default value can be + /// specified, to be used whenever a variable of this type is emitted (e.g. + class LlvmNativeFunctionSignature + { + public Type ReturnType { get; } + public IList? Parameters { get; } + public object? FieldValue { get; set; } + + public LlvmNativeFunctionSignature (Type returnType, List? parameters = null) + { + ReturnType = returnType ?? throw new ArgumentNullException (nameof (returnType)); + Parameters = parameters?.Select (p => EnsureValidParameter (p))?.ToList ()?.AsReadOnly (); + + LlvmIrFunctionParameter EnsureValidParameter (LlvmIrFunctionParameter parameter) + { + if (parameter == null) { + throw new InvalidOperationException ("null parameters aren't allowed"); + } + + return parameter; + } + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeClassAttribute.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeClassAttribute.cs new file mode 100644 index 00000000000..698f4e74434 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/NativeClassAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Xamarin.Android.Tasks +{ + [AttributeUsage (AttributeTargets.Class, Inherited = true)] + class NativeClassAttribute : Attribute + { + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs index 02aefd3070d..5cc1dfa6589 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs @@ -20,6 +20,7 @@ sealed class StructureInfo : IStructureInfo public bool HasPreAllocatedBuffers { get; private set; } public bool IsOpaque => Members.Count == 0; + public string NativeTypeDesignator { get; } public StructureInfo (LlvmIrGenerator generator) { @@ -27,12 +28,13 @@ public StructureInfo (LlvmIrGenerator generator) Name = type.GetShortName (); Size = GatherMembers (type, generator); DataProvider = type.GetDataProvider (); + NativeTypeDesignator = type.IsNativeClass () ? "class" : "struct"; } public void RenderDeclaration (LlvmIrGenerator generator) { TextWriter output = generator.Output; - generator.WriteStructureDeclarationStart (Name, forOpaqueType: IsOpaque); + generator.WriteStructureDeclarationStart (NativeTypeDesignator, Name, forOpaqueType: IsOpaque); if (IsOpaque) { return; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs index def40e6c472..fd41c1790e8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs @@ -67,5 +67,11 @@ public static bool IsIRStruct (this StructureMemberInfo smi) return Activator.CreateInstance (attr.Type) as NativeAssemblerStructContextDataProvider; } + + public static bool IsNativeClass (this Type t) + { + var attr = t.GetCustomAttribute (); + return attr != null; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs index 6d841fcfd59..81aa5dfdb9a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X64LlvmIrGenerator.cs @@ -16,6 +16,13 @@ class X64LlvmIrGenerator : LlvmIrGenerator public override int PointerSize => 8; protected override string Triple => "x86_64-unknown-linux-android"; // NDK appends API level, we don't need that + static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { + new FramePointerFunctionAttribute ("none"), + new TargetCpuFunctionAttribute ("x86-64"), + new TargetFeaturesFunctionAttribute ("+cx16,+cx8,+fxsr,+mmx,+popcnt,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87"), + new TuneCpuFunctionAttribute ("generic"), + }; + public X64LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) : base (arch, output, fileName) {} @@ -33,5 +40,13 @@ protected override int GetAggregateAlignment (int maxFieldAlignment, ulong dataS return maxFieldAlignment; } + + protected override void InitFunctionAttributes () + { + base.InitFunctionAttributes (); + + FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); + FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs index 89483550cc5..d779bf25bb2 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/X86LlvmIrGenerator.cs @@ -16,6 +16,14 @@ class X86LlvmIrGenerator : LlvmIrGenerator public override int PointerSize => 4; protected override string Triple => "i686-unknown-linux-android"; // NDK appends API level, we don't need that + static readonly LlvmFunctionAttributeSet commonAttributes = new LlvmFunctionAttributeSet { + new FramePointerFunctionAttribute ("none"), + new TargetCpuFunctionAttribute ("i686"), + new TargetFeaturesFunctionAttribute ("+cx8,+mmx,+sse,+sse2,+sse3,+ssse3,+x87"), + new TuneCpuFunctionAttribute ("generic"), + new StackrealignFunctionAttribute (), + }; + public X86LlvmIrGenerator (AndroidTargetArch arch, StreamWriter output, string fileName) : base (arch, output, fileName) {} @@ -25,5 +33,13 @@ protected override void AddModuleFlagsMetadata (List flagsFi base.AddModuleFlagsMetadata (flagsFields); flagsFields.Add (MetadataManager.AddNumbered (LlvmIrModuleMergeBehavior.Error, "NumRegisterParameters", 0)); } + + protected override void InitFunctionAttributes () + { + base.InitFunctionAttributes (); + + FunctionAttributes[FunctionAttributesXamarinAppInit].Add (commonAttributes); + FunctionAttributes[FunctionAttributesJniMethods].Add (commonAttributes); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index e18ab55f20b..7ff24962a15 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -18,22 +18,137 @@ namespace Xamarin.Android.Tasks { class MarshalMethodsNativeAssemblyGenerator : LlvmIrComposer { - sealed class MarshalMethodInfo + // This is here only to generate strongly-typed IR + internal sealed class MonoClass + {} + + [NativeClass] + sealed class _JNIEnv + {} + + // TODO: figure out why opaque classes like these have one byte field in clang's output + [NativeClass] + class _jobject { - public MarshalMethodEntry Method { get; } - public string NativeSymbolName { get; } + public byte b; } - // This is here only to generate strongly-typed IR - internal sealed class MonoClass + sealed class _jclass : _jobject + {} + + sealed class _jstring : _jobject + {} + + sealed class _jthrowable : _jobject + {} + + class _jarray : _jobject + {} + + sealed class _jobjectArray : _jarray + {} + + sealed class _jbooleanArray : _jarray + {} + + sealed class _jbyteArray : _jarray {} - struct MarshalMethodsManagedClass + sealed class _jcharArray : _jarray + {} + + sealed class _jshortArray : _jarray + {} + + sealed class _jintArray : _jarray + {} + + sealed class _jlongArray : _jarray + {} + + sealed class _jfloatArray : _jarray + {} + + sealed class _jdoubleArray : _jarray + {} + + sealed class MarshalMethodInfo + { + public MarshalMethodEntry Method { get; } + public string NativeSymbolName { get; } + public List Parameters { get; } + public Type ReturnType { get; } + public uint ClassCacheIndex { get; } + + // This one isn't known until the generation time, which happens after we instantiate the class + // in Init and it may be different between architectures/ABIs, hence it needs to be settable from + // the outside. + public uint AssemblyCacheIndex { get; set; } + + public MarshalMethodInfo (MarshalMethodEntry method, Type returnType, string nativeSymbolName, int classCacheIndex) + { + Method = method ?? throw new ArgumentNullException (nameof (method)); + ReturnType = returnType ?? throw new ArgumentNullException (nameof (returnType)); + if (String.IsNullOrEmpty (nativeSymbolName)) { + throw new ArgumentException ("must not be null or empty", nameof (nativeSymbolName)); + } + NativeSymbolName = nativeSymbolName; + Parameters = new List { + new LlvmIrFunctionParameter (typeof (_JNIEnv), "env", isNativePointer: true), // JNIEnv *env + new LlvmIrFunctionParameter (typeof (_jclass), "klass", isNativePointer: true), // jclass klass + }; + ClassCacheIndex = (uint)classCacheIndex; + } + } + + sealed class MarshalMethodsManagedClassDataProvider : NativeAssemblerStructContextDataProvider + { + public override string GetComment (object data, string fieldName) + { + var klass = EnsureType (data); + + if (String.Compare ("token", fieldName, StringComparison.Ordinal) == 0) { + return $"token 0x{klass.token:x}; class name: {klass.ClassName}"; + } + + return String.Empty; + } + } + + [NativeAssemblerStructContextDataProvider (typeof(MarshalMethodsManagedClassDataProvider))] + sealed class MarshalMethodsManagedClass { - uint token; + [NativeAssembler (UsesDataProvider = true)] + public uint token; [NativePointer (IsNull = true)] - MonoClass klass; + public MonoClass klass; + + [NativeAssembler (Ignore = true)] + public string ClassName; + }; + + static readonly Dictionary jniSimpleTypeMap = new Dictionary { + { 'Z', typeof(bool) }, + { 'B', typeof(byte) }, + { 'C', typeof(char) }, + { 'S', typeof(short) }, + { 'I', typeof(int) }, + { 'J', typeof(long) }, + { 'F', typeof(float) }, + { 'D', typeof(double) }, + }; + + static readonly Dictionary jniArrayTypeMap = new Dictionary { + { 'Z', typeof(_jbooleanArray) }, + { 'B', typeof(_jbyteArray) }, + { 'C', typeof(_jcharArray) }, + { 'S', typeof(_jshortArray) }, + { 'I', typeof(_jintArray) }, + { 'J', typeof(_jlongArray) }, + { 'F', typeof(_jfloatArray) }, + { 'D', typeof(_jdoubleArray) }, + { 'L', typeof(_jobjectArray) }, }; public ICollection UniqueAssemblyNames { get; set; } @@ -41,25 +156,428 @@ struct MarshalMethodsManagedClass public IDictionary> MarshalMethods { get; set; } StructureInfo monoImage; + StructureInfo marshalMethodsClass; StructureInfo monoClass; + StructureInfo<_JNIEnv> _jniEnvSI; + StructureInfo<_jobject> _jobjectSI; + StructureInfo<_jclass> _jclassSI; + StructureInfo<_jstring> _jstringSI; + StructureInfo<_jthrowable> _jthrowableSI; + StructureInfo<_jarray> _jarraySI; + StructureInfo<_jobjectArray> _jobjectArraySI; + StructureInfo<_jbooleanArray> _jbooleanArraySI; + StructureInfo<_jbyteArray> _jbyteArraySI; + StructureInfo<_jcharArray> _jcharArraySI; + StructureInfo<_jshortArray> _jshortArraySI; + StructureInfo<_jintArray> _jintArraySI; + StructureInfo<_jlongArray> _jlongArraySI; + StructureInfo<_jfloatArray> _jfloatArraySI; + StructureInfo<_jdoubleArray> _jdoubleArraySI; + + List methods; + List> classes = new List> (); public override void Init () { Console.WriteLine ($"Marshal methods count: {MarshalMethods?.Count ?? 0}"); + if (MarshalMethods == null || MarshalMethods.Count == 0) { + return; + } + + var seenClasses = new Dictionary (StringComparer.Ordinal); + methods = new List (); + foreach (IList entryList in MarshalMethods.Values) { + bool useFullNativeSignature = entryList.Count > 1; + foreach (MarshalMethodEntry entry in entryList) { + ProcessAndAddMethod (entry, useFullNativeSignature, seenClasses); + } + } + } + + void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, Dictionary seenClasses) + { + Console.WriteLine ("marshal method:"); + Console.WriteLine ($" top type: {entry.DeclaringType.FullName}"); + Console.WriteLine ($" registered method: [{entry.RegisteredMethod.DeclaringType.FullName}] {entry.RegisteredMethod.FullName}"); + Console.WriteLine ($" implemented method: [{entry.ImplementedMethod.DeclaringType.FullName}] {entry.ImplementedMethod.FullName}"); + Console.WriteLine ($" native callback: {entry.NativeCallback.FullName}"); + Console.WriteLine ($" connector: {entry.Connector.FullName}"); + Console.WriteLine ($" JNI name: {entry.JniMethodName}"); + Console.WriteLine ($" JNI signature: {entry.JniMethodSignature}"); + + var sb = new StringBuilder ("Java_"); + sb.Append (MangleForJni (entry.JniTypeName)); + sb.Append ('_'); + sb.Append (MangleForJni ($"n_{entry.JniMethodName}")); + + if (useFullNativeSignature) { + string signature = entry.JniMethodSignature; + if (signature.Length < 2) { + ThrowInvalidSignature (signature, "must be at least two characters long"); + } + + if (signature[0] != '(') { + ThrowInvalidSignature (signature, "must start with '('"); + } + + int sigEndIdx = signature.LastIndexOf (')'); + if (sigEndIdx < 1) { // the first position where ')' can appear is 1, for a method without parameters + ThrowInvalidSignature (signature, "missing closing parenthesis"); + } + + string sigParams = signature.Substring (1, sigEndIdx - 1); + if (sigParams.Length > 0) { + sb.Append ("__"); + sb.Append (MangleForJni (sigParams)); + } + } + + string klass = $"{entry.NativeCallback.DeclaringType.FullName}, {entry.NativeCallback.Module.Assembly.FullName}"; + if (!seenClasses.TryGetValue (klass, out int classIndex)) { + seenClasses.Add (klass, classes.Count); + + var mc = new MarshalMethodsManagedClass { + token = entry.NativeCallback.DeclaringType.MetadataToken.ToUInt32 (), + ClassName = klass, + }; + + classes.Add (new StructureInstance (mc)); + } + + (Type returnType, List? parameters) = ParseJniSignature (entry.JniMethodSignature, entry.ImplementedMethod); + var method = new MarshalMethodInfo (entry, returnType, nativeSymbolName: sb.ToString (), classIndex); + if (parameters != null && parameters.Count > 0) { + method.Parameters.AddRange (parameters); + } + + Console.WriteLine ($" Generated native symbol: {method.NativeSymbolName}"); + Console.WriteLine ($" Parsed return type: {returnType}"); + if (method.Parameters.Count > 0) { + Console.WriteLine (" Parsed parameters:"); + foreach (LlvmIrFunctionParameter p in method.Parameters) { + Console.WriteLine ($" {p.Type} {p.Name}"); + } + } + Console.WriteLine (); + + methods.Add (method); + + void ThrowInvalidSignature (string signature, string reason) + { + throw new InvalidOperationException ($"Invalid JNI signature '{signature}': {reason}"); + } + } + + string MangleForJni (string name) + { + Console.WriteLine ($" mangling '{name}'"); + var sb = new StringBuilder (name); + + sb.Replace ("_", "_1"); + sb.Replace ('/', '_'); + sb.Replace (";", "_2"); + sb.Replace ("[", "_3"); + // TODO: process unicode chars + + return sb.ToString (); + } + + (Type returnType, List? functionParams) ParseJniSignature (string signature, Mono.Cecil.MethodDefinition implementedMethod) + { + Type returnType = null; + List? parameters = null; + bool paramsDone = false; + int idx = 0; + while (!paramsDone && idx < signature.Length) { + char jniType = signature[idx]; + Type? managedType = JniTypeToManaged (jniType); + + if (managedType != null) { + AddParameter (managedType); + continue; + } + + if (jniType == '(') { + idx++; + continue; + } + + if (jniType == ')') { + paramsDone = true; + continue; + } + + throw new InvalidOperationException ($"Unsupported JNI type '{jniType}' at position {idx} of signature '{signature}'"); + } + + if (!paramsDone || idx >= signature.Length || signature[idx] != ')') { + throw new InvalidOperationException ($"Missing closing arguments parenthesis: '{signature}'"); + } + + idx++; + if (signature[idx] == 'V') { + returnType = typeof(void); + } else { + returnType = JniTypeToManaged (signature[idx]); + } + + return (returnType, parameters); + + Type? JniTypeToManaged (char jniType) + { + if (jniSimpleTypeMap.TryGetValue (jniType, out Type managedType)) { + idx++; + return managedType; + } + + if (jniType == 'L') { + return JavaClassToManaged (justSkip: false); + } + + if (jniType == '[') { + idx++; + jniType = signature[idx]; + if (jniArrayTypeMap.TryGetValue (jniType, out managedType)) { + JavaClassToManaged (justSkip: true); + return managedType; + } + + throw new InvalidOperationException ($"Unsupported JNI array type '{jniType}' at index {idx} of signature '{signature}'"); + } + + return null; + } + + Type? JavaClassToManaged (bool justSkip) + { + idx++; + StringBuilder sb = null; + if (!justSkip) { + sb = new StringBuilder (); + } + + while (idx < signature.Length) { + if (signature[idx] == ')') { + throw new InvalidOperationException ($"Syntax error: unterminated class type (missing ';' before closing parenthesis) in signature '{signature}'"); + } + + if (signature[idx] == ';') { + idx++; + break; + } + + sb?.Append (signature[idx++]); + } + + if (justSkip) { + return null; + } + + string typeName = sb.ToString (); + if (String.Compare (typeName, "java/lang/Class", StringComparison.Ordinal) == 0) { + return typeof(_jclass); + } + + if (String.Compare (typeName, "java/lang/String", StringComparison.Ordinal) == 0) { + return typeof(_jstring); + } + + if (String.Compare (typeName, "java/lang/Throwable", StringComparison.Ordinal) == 0) { + return typeof(_jthrowable); + } + + return typeof(_jobject); + } + + void AddParameter (Type type) + { + if (parameters == null) { + parameters = new List (); + } + + if (implementedMethod.Parameters.Count <= parameters.Count) { + throw new InvalidOperationException ($"Method {implementedMethod.FullName} managed signature doesn't match its JNI signature '{signature}' (not enough parameters)"); + } + + // Every parameter which isn't a primitive type becomes a pointer + parameters.Add (new LlvmIrFunctionParameter (type, implementedMethod.Parameters[parameters.Count].Name, isNativePointer: type.IsNativeClass ())); + } + } + + protected override void InitGenerator (LlvmIrGenerator generator) + { + generator.InitCodeOutput (); } protected override void MapStructures (LlvmIrGenerator generator) { monoImage = generator.MapStructure (); monoClass = generator.MapStructure (); + marshalMethodsClass = generator.MapStructure (); + _jniEnvSI = generator.MapStructure<_JNIEnv> (); + _jobjectSI = generator.MapStructure<_jobject> (); + _jclassSI = generator.MapStructure<_jclass> (); + _jstringSI = generator.MapStructure<_jstring> (); + _jthrowableSI = generator.MapStructure<_jthrowable> (); + _jarraySI = generator.MapStructure<_jarray> (); + _jobjectArraySI = generator.MapStructure<_jobjectArray> (); + _jbooleanArraySI = generator.MapStructure<_jbooleanArray> (); + _jbyteArraySI = generator.MapStructure<_jbyteArray> (); + _jcharArraySI = generator.MapStructure<_jcharArray> (); + _jshortArraySI = generator.MapStructure<_jshortArray> (); + _jintArraySI = generator.MapStructure<_jintArray> (); + _jlongArraySI = generator.MapStructure<_jlongArray> (); + _jfloatArraySI = generator.MapStructure<_jfloatArray> (); + _jdoubleArraySI = generator.MapStructure<_jdoubleArray> (); } protected override void Write (LlvmIrGenerator generator) { - WriteAssemblyImageCache (generator); + Dictionary asmNameToIndex = WriteAssemblyImageCache (generator); + WriteClassCache (generator); + LlvmIrVariableReference get_function_pointer_ref = WriteXamarinAppInitFunction (generator); + WriteNativeMethods (generator, asmNameToIndex, get_function_pointer_ref); + } + + void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asmNameToIndex, LlvmIrVariableReference get_function_pointer_ref) + { + if (methods == null || methods.Count == 0) { + return; + } + + foreach (MarshalMethodInfo mmi in methods) { + string asmName = mmi.Method.NativeCallback.DeclaringType.Module.Assembly.Name.Name; + if (!asmNameToIndex.TryGetValue (asmName, out uint asmIndex)) { + throw new InvalidOperationException ($"Unable to translate assembly name '{asmName}' to its index"); + } + mmi.AssemblyCacheIndex = asmIndex; + + WriteMarshalMethod (generator, mmi, get_function_pointer_ref); + } } - void WriteAssemblyImageCache (LlvmIrGenerator generator) + void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrVariableReference get_function_pointer_ref) + { + var backingFieldSignature = new LlvmNativeFunctionSignature ( + returnType: method.ReturnType, + parameters: method.Parameters + ) { + FieldValue = "null", + }; + + string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{method.AssemblyCacheIndex}_{method.ClassCacheIndex}_{method.Method.NativeCallback.MetadataToken.ToUInt32():x}"; + var backingFieldRef = new LlvmIrVariableReference (backingFieldSignature, backingFieldName, isGlobal: true); + + generator.WriteVariable (backingFieldName, backingFieldSignature, LlvmIrVariableOptions.LocalWritableInsignificantAddr); + + var func = new LlvmIrFunction ( + name: method.NativeSymbolName, + returnType: method.ReturnType, + attributeSetID: LlvmIrGenerator.FunctionAttributesJniMethods, + parameters: method.Parameters + ); + + generator.WriteFunctionStart (func); + + LlvmIrFunctionLocalVariable callbackVariable1 = generator.EmitLoadInstruction (func, backingFieldRef, "cb1"); + var callbackVariable1Ref = new LlvmIrVariableReference (callbackVariable1, isGlobal: false); + + LlvmIrFunctionLocalVariable isNullVariable = generator.EmitIcmpInstruction (func, LlvmIrIcmpCond.Equal, callbackVariable1Ref, expectedValue: "null", resultVariableName: "isNull"); + var isNullVariableRef = new LlvmIrVariableReference (isNullVariable, isGlobal: false); + + const string loadCallbackLabel = "loadCallback"; + const string callbackLoadedLabel = "callbackLoaded"; + + generator.EmitBrInstruction (func, isNullVariableRef, loadCallbackLabel, callbackLoadedLabel); + + generator.WriteEOL (); + generator.EmitLabel (func, loadCallbackLabel); + LlvmIrFunctionLocalVariable getFunctionPointerVariable = generator.EmitLoadInstruction (func, get_function_pointer_ref, "get_func_ptr"); + var getFunctionPtrRef = new LlvmIrVariableReference (getFunctionPointerVariable, isGlobal: false); + + generator.EmitCall ( + func, + getFunctionPtrRef, + new List { + new LlvmIrFunctionArgument (typeof(uint), method.AssemblyCacheIndex), + new LlvmIrFunctionArgument (typeof(uint), method.ClassCacheIndex), + new LlvmIrFunctionArgument (typeof(uint), method.Method.NativeCallback.MetadataToken.ToUInt32 ()), + new LlvmIrFunctionArgument (typeof(LlvmIrVariableReference), backingFieldRef), + } + ); + + LlvmIrFunctionLocalVariable callbackVariable2 = generator.EmitLoadInstruction (func, backingFieldRef, "cb2"); + var callbackVariable2Ref = new LlvmIrVariableReference (callbackVariable2, isGlobal: false); + + generator.EmitBrInstruction (func, callbackLoadedLabel); + + generator.WriteEOL (); + generator.EmitLabel (func, callbackLoadedLabel); + + LlvmIrFunctionLocalVariable fnVariable = generator.EmitPhiInstruction ( + func, + backingFieldRef, + new List<(LlvmIrVariableReference variableRef, string label)> { + (callbackVariable1Ref, func.ImplicitFuncTopLabel), + (callbackVariable2Ref, loadCallbackLabel), + }, + resultVariableName: "fn" + ); + var fnVariableRef = new LlvmIrVariableReference (fnVariable, isGlobal: false); + + LlvmIrFunctionLocalVariable? result = generator.EmitCall ( + func, + fnVariableRef, + func.ParameterVariables.Select (pv => new LlvmIrFunctionArgument (pv)).ToList () + ); + + if (result != null) { + generator.EmitReturnInstruction (func, result); + } + + generator.WriteFunctionEnd (func); + } + + LlvmIrVariableReference WriteXamarinAppInitFunction (LlvmIrGenerator generator) + { + var get_function_pointer_sig = new LlvmNativeFunctionSignature ( + returnType: typeof(void), + parameters: new List { + new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), + new LlvmIrFunctionParameter (typeof(uint), "class_index"), + new LlvmIrFunctionParameter (typeof(uint), "method_token"), + new LlvmIrFunctionParameter (typeof(IntPtr), "target_ptr", isNativePointer: true, isCplusPlusReference: true) + } + ) { + FieldValue = "null", + }; + + const string GetFunctionPointerFieldName = "get_function_pointer"; + generator.WriteVariable (GetFunctionPointerFieldName, get_function_pointer_sig, LlvmIrVariableOptions.LocalWritableInsignificantAddr); + + var fnParameter = new LlvmIrFunctionParameter (get_function_pointer_sig, "fn"); + var func = new LlvmIrFunction ( + name: "xamarin_app_init", + returnType: typeof (void), + attributeSetID: LlvmIrGenerator.FunctionAttributesXamarinAppInit, + parameters: new List { + fnParameter, + } + ); + + generator.WriteFunctionStart (func); + generator.EmitStoreInstruction (func, fnParameter, new LlvmIrVariableReference (get_function_pointer_sig, GetFunctionPointerFieldName, isGlobal: true)); + generator.WriteFunctionEnd (func); + + return new LlvmIrVariableReference (get_function_pointer_sig, GetFunctionPointerFieldName, isGlobal: true); + } + + void WriteClassCache (LlvmIrGenerator generator) + { + generator.WriteStructureArray (marshalMethodsClass, classes, LlvmIrVariableOptions.GlobalWritable, "marshal_methods_class_cache"); + } + + Dictionary WriteAssemblyImageCache (LlvmIrGenerator generator) { if (UniqueAssemblyNames == null) { throw new InvalidOperationException ("Internal error: unique assembly names not provided"); @@ -72,12 +590,15 @@ void WriteAssemblyImageCache (LlvmIrGenerator generator) bool is64Bit = generator.Is64Bit; generator.WriteStructureArray (monoImage, (ulong)NumberOfAssembliesInApk, "assembly_image_cache", isArrayOfPointers: true); + var asmNameToIndex = new Dictionary (StringComparer.Ordinal); if (is64Bit) { WriteHashes (); } else { WriteHashes (); } + return asmNameToIndex; + void WriteHashes () where T: struct { var hashes = new Dictionary (); @@ -109,7 +630,9 @@ void WriteHashes () where T: struct var indices = new List (); for (int i = 0; i < keys.Count; i++) { - indices.Add (hashes[keys[i]].index); + (string name, uint idx) = hashes[keys[i]]; + indices.Add (idx); + asmNameToIndex.Add (name, idx); } generator.WriteArray ( indices, diff --git a/src/monodroid/jni/application_dso_stub.cc b/src/monodroid/jni/application_dso_stub.cc index 75dee87ce40..42196ff6113 100644 --- a/src/monodroid/jni/application_dso_stub.cc +++ b/src/monodroid/jni/application_dso_stub.cc @@ -186,7 +186,7 @@ MarshalMethodsManagedClass marshal_methods_class_cache[] = { }, }; -void xamarin_app_init ([[maybe_unused]] get_function_pointer_fn fn) +void xamarin_app_init ([[maybe_unused]] get_function_pointer_fn fn) noexcept { // Dummy } diff --git a/src/monodroid/jni/mono-image-loader.hh b/src/monodroid/jni/mono-image-loader.hh index b844f5c3acb..db374151764 100644 --- a/src/monodroid/jni/mono-image-loader.hh +++ b/src/monodroid/jni/mono-image-loader.hh @@ -107,7 +107,7 @@ namespace xamarin::android::internal { force_inline static ssize_t find_index (hash_t hash) noexcept { ssize_t idx = Search::binary_search (hash, assembly_image_cache_hashes, number_of_cache_index_entries); - return idx >= 0 ? assembly_image_cache_indices[idx] : -1; + return idx >= 0 ? static_cast(assembly_image_cache_indices[idx]) : -1; } #endif // def USE_CACHE diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index e61a59435e5..8b0ef579bee 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -350,7 +350,7 @@ namespace xamarin::android::internal static void monodroid_debugger_unhandled_exception (MonoException *ex); #if defined (RELEASE) && defined (ANDROID) - static void* get_function_pointer (uint32_t mono_image_index, uint32_t class_token, uint32_t method_token) noexcept; + static void get_function_pointer (uint32_t mono_image_index, uint32_t class_token, uint32_t method_token, void*& target_ptr) noexcept; #endif // def RELEASE && def ANDROID #endif // def NET diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index e904e03e806..9beefa17d86 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -5,12 +5,12 @@ using namespace xamarin::android::internal; -void* MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t class_token, uint32_t method_token) noexcept +void MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void *&target_ptr) noexcept { MonoImage *image = MonoImageLoader::get_from_index (mono_image_index); // TODO: implement MonoClassLoader with caching. Best to use indexes instead of keying on tokens. - MonoClass *method_klass = mono_class_get (image, class_token); + MonoClass *method_klass = mono_class_get (image, class_index); MonoMethod *method = mono_get_method (image, method_token, method_klass); MonoError error; @@ -19,10 +19,10 @@ void* MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_ // TODO: make the error message friendlier somehow (class, method and assembly names) log_fatal (LOG_DEFAULT, "Failed to obtain function pointer to method with token 0x%x; class token: 0x%x; assembly index: %u", - method_token, class_token, mono_images_cleanup + method_token, class_index, mono_images_cleanup ); abort (); } - return ret; + target_ptr = ret; } diff --git a/src/monodroid/jni/xamarin-app-marshaling.cc b/src/monodroid/jni/xamarin-app-marshaling.cc index 39e706e089d..58378b6c5fe 100644 --- a/src/monodroid/jni/xamarin-app-marshaling.cc +++ b/src/monodroid/jni/xamarin-app-marshaling.cc @@ -11,7 +11,7 @@ static get_function_pointer_fn get_function_pointer; -void xamarin_app_init (get_function_pointer_fn fn) +void xamarin_app_init (get_function_pointer_fn fn) noexcept { get_function_pointer = fn; } @@ -19,19 +19,21 @@ void xamarin_app_init (get_function_pointer_fn fn) using android_app_activity_on_create_bundle_fn = void (*) (JNIEnv *env, jclass klass, jobject savedInstanceState); static android_app_activity_on_create_bundle_fn android_app_activity_on_create_bundle = nullptr; -JNIEXPORT void -JNICALL Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2 (JNIEnv *env, jclass klass, jobject savedInstanceState) +extern "C" JNIEXPORT void +JNICALL Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2 (JNIEnv *env, jclass klass, jobject savedInstanceState) noexcept { // log_info (LOG_DEFAULT, "%s (%p, %p, %p)", __PRETTY_FUNCTION__, env, klass, savedInstanceState); if (android_app_activity_on_create_bundle == nullptr) { - void *fn = get_function_pointer ( - 16 /* Mono.Android.dll index */, - 0x020000AF /* Android.App.Activity token */, - 0x0600055B /* n_OnCreate_Landroid_os_Bundle_ */ - ); + // void *fn = get_function_pointer ( + // 16 /* Mono.Android.dll index */, + // 0 /* Android.App.Activity index */, + // 0x0600055B /* n_OnCreate_Landroid_os_Bundle_ */ + // ); + + // android_app_activity_on_create_bundle = reinterpret_cast(fn); - android_app_activity_on_create_bundle = reinterpret_cast(fn); + get_function_pointer (16, 0, 0x0600055B, reinterpret_cast(android_app_activity_on_create_bundle)); } android_app_activity_on_create_bundle (env, klass, savedInstanceState); @@ -40,20 +42,37 @@ JNICALL Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2 (JNIEnv using android_app_activity_on_create_view_fn = jobject (*) (JNIEnv *env, jclass klass, jobject view, jstring name, jobject context, jobject attrs); static android_app_activity_on_create_view_fn android_app_activity_on_create_view = nullptr; -JNIEXPORT jobject -JNICALL Java_helloandroid_MainActivity_n_1onCreateView__Landroid_view_View_2Ljava_lang_String_2Landroid_content_Context_2Landroid_util_AttributeSet_2 (JNIEnv *env, jclass klass, jobject view, jstring name, jobject context, jobject attrs) +extern "C" JNIEXPORT jobject +JNICALL Java_helloandroid_MainActivity_n_1onCreateView__Landroid_view_View_2Ljava_lang_String_2Landroid_content_Context_2Landroid_util_AttributeSet_2 (JNIEnv *env, jclass klass, jobject view, jstring name, jobject context, jobject attrs) noexcept { // log_info (LOG_DEFAULT, "%s (%p, %p, %p, %p, %p, %p)", __PRETTY_FUNCTION__, env, klass, view, name, context, attrs); if (android_app_activity_on_create_view == nullptr) { - void *fn = get_function_pointer ( + get_function_pointer ( 16 /* Mono.Android.dll index */, - 0x020000AF /* Android.App.Activity token */, - 0x06000564 /* n_OnCreateView_Landroid_view_View_Ljava_lang_String_Landroid_content_Context_Landroid_util_AttributeSet_ */ + 0 /* Android.App.Activity index */, + 0x06000564 /* n_OnCreateView_Landroid_view_View_Ljava_lang_String_Landroid_content_Context_Landroid_util_AttributeSet_ */, + reinterpret_cast(android_app_activity_on_create_view) ); - - android_app_activity_on_create_view = reinterpret_cast(fn); } return android_app_activity_on_create_view (env, klass, view, name, context, attrs); } + +using onDoSomething_fn = jbyte (*) (JNIEnv *env, jclass klass, jint anInt, jlong aLong, jfloat aFloat, jboolean aBool, jchar aChar, jshort aShort, jdouble aDouble); +static onDoSomething_fn onDoSomething = nullptr; + +extern "C" JNIEXPORT jbyte +JNICALL Java_helloandroid_MainActivity_n_1onDoSomething (JNIEnv *env, jclass klass, jint anInt, jlong aLong, jfloat aFloat, jboolean aBool, jchar aChar, jshort aShort, jdouble aDouble) noexcept +{ + if (onDoSomething == nullptr) { + get_function_pointer ( + 16 /* Mono.Android.dll index */, + 0 /* Android.App.Activity index */, + 0x06000564 /* n_OnCreateView_Landroid_view_View_Ljava_lang_String_Landroid_content_Context_Landroid_util_AttributeSet_ */, + reinterpret_cast(onDoSomething) + ); + } + + return onDoSomething (env, klass, anInt, aLong, aFloat, aBool, aChar, aShort, aDouble); +} diff --git a/src/monodroid/jni/xamarin-app.hh b/src/monodroid/jni/xamarin-app.hh index b95f5a11030..bd1076a2ae0 100644 --- a/src/monodroid/jni/xamarin-app.hh +++ b/src/monodroid/jni/xamarin-app.hh @@ -330,9 +330,9 @@ MONO_API MONO_API_EXPORT const xamarin::android::hash_t assembly_image_cache_has MONO_API MONO_API_EXPORT uint32_t marshal_methods_number_of_classes; MONO_API MONO_API_EXPORT MarshalMethodsManagedClass marshal_methods_class_cache[]; -using get_function_pointer_fn = void*(*)(uint32_t mono_image_index, uint32_t class_token, uint32_t method_token); +using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr); -MONO_API_EXPORT void xamarin_app_init (get_function_pointer_fn fn); +MONO_API MONO_API_EXPORT void xamarin_app_init (get_function_pointer_fn fn) noexcept; #endif // def RELEASE && def ANDROID && def NET #endif // __XAMARIN_ANDROID_TYPEMAP_H From e34f88e2f2bd256945dc12641106a11d30e6b387 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 29 Mar 2022 22:01:22 +0200 Subject: [PATCH 02/79] [marshal methods] Runtime fixes and missing features Implement missing runtime support so that applications are able to actually run. Also fix issues with marshal method overloads as well as add code to handle the case where two unrelated methods (from different types) end up using the same JNI symbol name: * `Java_crc64e1fb321c08285b90_CellAdapter_n_1onActionItemClicked * `System.Boolean Android.Views.ActionMode/ICallback::OnActionItemClicked(Android.Views.ActionMode,Android.Views.IMenuItem)` * `System.Boolean AndroidX.AppCompat.View.ActionMode/ICallback::OnActionItemClicked(AndroidX.AppCompat.View.ActionMode,Android.Views.IMenuItem)` --- Directory.Build.props | 2 +- .../System.Runtime.InteropServices.xml | 7 ++ .../MarshalMethodsAssemblyRewriter.cs | 22 +++++- .../Utilities/MarshalMethodsClassifier.cs | 16 +++-- .../MarshalMethodsNativeAssemblyGenerator.cs | 72 +++++++++++++++---- src/monodroid/CMakeLists.txt | 2 +- src/monodroid/jni/monodroid-glue-internal.hh | 5 +- src/monodroid/jni/monodroid-glue.cc | 9 ++- .../jni/xamarin-android-app-context.cc | 45 ++++++++++-- 9 files changed, 145 insertions(+), 35 deletions(-) create mode 100644 src/Microsoft.Android.Sdk.ILLink/PreserveLists/System.Runtime.InteropServices.xml diff --git a/Directory.Build.props b/Directory.Build.props index 11c32ae8d20..9b63061cc6e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -26,7 +26,7 @@ - <_EnableMarshalMethods>NoThanks + <_EnableMarshalMethods>YesPlease diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/System.Runtime.InteropServices.xml b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/System.Runtime.InteropServices.xml new file mode 100644 index 00000000000..284c4ec42f3 --- /dev/null +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/System.Runtime.InteropServices.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 24e841e5252..0fae90d4886 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -38,9 +38,16 @@ public void Rewrite (DirectoryAssemblyResolver resolver) foreach (IList methodList in methods.Values) { foreach (MarshalMethodEntry method in methodList) { Console.WriteLine ($"\t{method.NativeCallback.FullName} (token: 0x{method.NativeCallback.MetadataToken.RID:x})"); + Console.WriteLine ($"\t NativeCallback == '{method.NativeCallback}'; Connector == '{method.Connector}'"); + Console.WriteLine ($"\t method.NativeCallback.CustomAttributes == {ToStringOrNull (method.NativeCallback?.CustomAttributes)}"); + Console.WriteLine ($"\t method.Connector.DeclaringType == {ToStringOrNull (method.Connector?.DeclaringType)}"); + Console.WriteLine ($"\t method.Connector.DeclaringType.Methods == {ToStringOrNull (method.Connector.DeclaringType?.Methods)}"); + Console.WriteLine ($"\t method.CallbackField == {ToStringOrNull (method.CallbackField)}"); + Console.WriteLine ($"\t method.CallbackField?.DeclaringType == {ToStringOrNull (method.CallbackField?.DeclaringType)}"); + Console.WriteLine ($"\t method.CallbackField?.DeclaringType.Fields == {ToStringOrNull (method.CallbackField?.DeclaringType?.Fields)}"); method.NativeCallback.CustomAttributes.Add (unmanagedCallersOnlyAttributes [method.NativeCallback.Module.Assembly]); - method.Connector.DeclaringType.Methods.Remove (method.Connector); - method.CallbackField?.DeclaringType.Fields.Remove (method.CallbackField); + method.Connector?.DeclaringType?.Methods?.Remove (method.Connector); + method.CallbackField?.DeclaringType?.Fields?.Remove (method.CallbackField); } } @@ -95,10 +102,19 @@ void MoveFile (string source, string target) Files.CopyIfChanged (source, target); try { File.Delete (source); - } catch (Exception ex) { + } catch (Exception) { log.LogWarning ($"Unable to delete source file '{source}' when moving it to '{target}'"); } } + + string ToStringOrNull (object? o) + { + if (o == null) { + return "'null'"; + } + + return o.ToString (); + } } ICollection GetAssemblyPaths (AssemblyDefinition asm) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index a6801fa6d13..64803fab04c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Java.Interop.Tools.Cecil; using Java.Interop.Tools.JavaCallableWrappers; @@ -370,18 +371,19 @@ FieldDefinition FindField (TypeDefinition type, string fieldName, bool lookForIn void StoreMethod (string connectorName, MethodDefinition registeredMethod, MarshalMethodEntry entry) { string typeName = registeredMethod.DeclaringType.FullName.Replace ('/', '+'); - string key = $"{typeName}, {registeredMethod.DeclaringType.GetPartialAssemblyName (tdCache)}\t{connectorName}"; - - // Several classes can override the same method, we need to generate the marshal method only once - if (marshalMethods.ContainsKey (key)) { - return; - } + string key = $"{typeName}, {registeredMethod.DeclaringType.GetPartialAssemblyName (tdCache)}\t{registeredMethod.Name}"; + // Several classes can override the same method, we need to generate the marshal method only once, at the same time + // keeping track of overloads if (!marshalMethods.TryGetValue (key, out IList list) || list == null) { list = new List (); marshalMethods.Add (key, list); } - list.Add (entry); + + string registeredName = registeredMethod.FullName; + if (list.Count == 0 || !list.Any (me => String.Compare (registeredName, me.RegisteredMethod.FullName, StringComparison.Ordinal) == 0)) { + list.Add (entry); + } } void StoreAssembly (AssemblyDefinition asm) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 7ff24962a15..dde9086ed9a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -186,15 +186,31 @@ public override void Init () var seenClasses = new Dictionary (StringComparer.Ordinal); methods = new List (); + + // It's possible that several otherwise different methods (from different classes, but with the same + // names and signatures) will actually share the same **short** native symbol name. In this case we must + // ensure that they all use long symbol names. This has to be done as a post-processing step, after we + // have already iterated over the entire method collection. + var overloadedNativeSymbolNames = new Dictionary> (StringComparer.Ordinal); foreach (IList entryList in MarshalMethods.Values) { bool useFullNativeSignature = entryList.Count > 1; foreach (MarshalMethodEntry entry in entryList) { - ProcessAndAddMethod (entry, useFullNativeSignature, seenClasses); + ProcessAndAddMethod (entry, useFullNativeSignature, seenClasses, overloadedNativeSymbolNames); + } + } + + // WIP: 🡇 + foreach (List mmiList in overloadedNativeSymbolNames.Values) { + if (mmiList.Count <= 1) { + continue; + } + + foreach (MarshalMethodInfo overloadedMethod in mmiList) { } } } - void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, Dictionary seenClasses) + void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, Dictionary seenClasses, Dictionary> overloadedNativeSymbolNames) { Console.WriteLine ("marshal method:"); Console.WriteLine ($" top type: {entry.DeclaringType.FullName}"); @@ -211,6 +227,7 @@ void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, sb.Append (MangleForJni ($"n_{entry.JniMethodName}")); if (useFullNativeSignature) { + Console.WriteLine (" Using FULL signature"); string signature = entry.JniMethodSignature; if (signature.Length < 2) { ThrowInvalidSignature (signature, "must be at least two characters long"); @@ -233,6 +250,7 @@ void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, } string klass = $"{entry.NativeCallback.DeclaringType.FullName}, {entry.NativeCallback.Module.Assembly.FullName}"; + Console.WriteLine ($" klass == {klass}"); if (!seenClasses.TryGetValue (klass, out int classIndex)) { seenClasses.Add (klass, classes.Count); @@ -244,7 +262,10 @@ void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, classes.Add (new StructureInstance (mc)); } + Console.WriteLine (" about to parse JNI sig"); (Type returnType, List? parameters) = ParseJniSignature (entry.JniMethodSignature, entry.ImplementedMethod); + Console.WriteLine (" parsed!"); + var method = new MarshalMethodInfo (entry, returnType, nativeSymbolName: sb.ToString (), classIndex); if (parameters != null && parameters.Count > 0) { method.Parameters.AddRange (parameters); @@ -260,6 +281,12 @@ void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, } Console.WriteLine (); + if (!overloadedNativeSymbolNames.TryGetValue (method.NativeSymbolName, out List overloadedMethods)) { + overloadedMethods = new List (); + overloadedNativeSymbolNames.Add (method.NativeSymbolName, overloadedMethods); + } + overloadedMethods.Add (method); + methods.Add (method); void ThrowInvalidSignature (string signature, string reason) @@ -290,12 +317,6 @@ string MangleForJni (string name) int idx = 0; while (!paramsDone && idx < signature.Length) { char jniType = signature[idx]; - Type? managedType = JniTypeToManaged (jniType); - - if (managedType != null) { - AddParameter (managedType); - continue; - } if (jniType == '(') { idx++; @@ -307,6 +328,12 @@ string MangleForJni (string name) continue; } + Type? managedType = JniTypeToManaged (jniType); + if (managedType != null) { + AddParameter (managedType); + continue; + } + throw new InvalidOperationException ($"Unsupported JNI type '{jniType}' at position {idx} of signature '{signature}'"); } @@ -325,8 +352,10 @@ string MangleForJni (string name) Type? JniTypeToManaged (char jniType) { + Console.WriteLine ($" turning JNI type '{jniType}' into managed type"); if (jniSimpleTypeMap.TryGetValue (jniType, out Type managedType)) { idx++; + Console.WriteLine ($" will return {managedType}"); return managedType; } @@ -335,16 +364,24 @@ string MangleForJni (string name) } if (jniType == '[') { + Console.WriteLine (" an array"); idx++; jniType = signature[idx]; if (jniArrayTypeMap.TryGetValue (jniType, out managedType)) { - JavaClassToManaged (justSkip: true); + if (jniType == 'L') { + Console.WriteLine (" skipping"); + JavaClassToManaged (justSkip: true); + } else { + idx++; + } + Console.WriteLine ($" will return {managedType}"); return managedType; } throw new InvalidOperationException ($"Unsupported JNI array type '{jniType}' at index {idx} of signature '{signature}'"); } + Console.WriteLine (" returning NULL managed type"); return null; } @@ -366,7 +403,8 @@ string MangleForJni (string name) break; } - sb?.Append (signature[idx++]); + sb?.Append (signature[idx]); + idx++; } if (justSkip) { @@ -445,18 +483,18 @@ void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asm return; } + var usedBackingFields = new HashSet (StringComparer.Ordinal); foreach (MarshalMethodInfo mmi in methods) { string asmName = mmi.Method.NativeCallback.DeclaringType.Module.Assembly.Name.Name; if (!asmNameToIndex.TryGetValue (asmName, out uint asmIndex)) { throw new InvalidOperationException ($"Unable to translate assembly name '{asmName}' to its index"); } mmi.AssemblyCacheIndex = asmIndex; - - WriteMarshalMethod (generator, mmi, get_function_pointer_ref); + WriteMarshalMethod (generator, mmi, get_function_pointer_ref, usedBackingFields); } } - void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrVariableReference get_function_pointer_ref) + void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, LlvmIrVariableReference get_function_pointer_ref, HashSet usedBackingFields) { var backingFieldSignature = new LlvmNativeFunctionSignature ( returnType: method.ReturnType, @@ -468,7 +506,10 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{method.AssemblyCacheIndex}_{method.ClassCacheIndex}_{method.Method.NativeCallback.MetadataToken.ToUInt32():x}"; var backingFieldRef = new LlvmIrVariableReference (backingFieldSignature, backingFieldName, isGlobal: true); - generator.WriteVariable (backingFieldName, backingFieldSignature, LlvmIrVariableOptions.LocalWritableInsignificantAddr); + if (!usedBackingFields.Contains (backingFieldName)) { + generator.WriteVariable (backingFieldName, backingFieldSignature, LlvmIrVariableOptions.LocalWritableInsignificantAddr); + usedBackingFields.Add (backingFieldName); + } var func = new LlvmIrFunction ( name: method.NativeSymbolName, @@ -574,6 +615,9 @@ LlvmIrVariableReference WriteXamarinAppInitFunction (LlvmIrGenerator generator) void WriteClassCache (LlvmIrGenerator generator) { + uint marshal_methods_number_of_classes = (uint)classes.Count; + + generator.WriteVariable (nameof (marshal_methods_number_of_classes), marshal_methods_number_of_classes); generator.WriteStructureArray (marshalMethodsClass, classes, LlvmIrVariableOptions.GlobalWritable, "marshal_methods_class_cache"); } diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index 41577f4f55a..d0b9bd9b72b 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -50,7 +50,7 @@ option(STRIP_DEBUG "Strip debugging information when linking" ${STRIP_DEBUG_DEFA option(DISABLE_DEBUG "Disable the built-in debugging code" OFF) option(USE_CCACHE "Use ccache, if found, to speed up recompilation" ${CCACHE_OPTION_DEFAULT}) -set(ENABLE_MARSHAL_METHODS False) +set(ENABLE_MARSHAL_METHODS True) if((MINGW OR NOT WIN32) AND USE_CCACHE) if(CMAKE_CXX_COMPILER MATCHES "/ccache/") diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index 8b0ef579bee..dda7b74e09f 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -350,7 +350,10 @@ namespace xamarin::android::internal static void monodroid_debugger_unhandled_exception (MonoException *ex); #if defined (RELEASE) && defined (ANDROID) - static void get_function_pointer (uint32_t mono_image_index, uint32_t class_token, uint32_t method_token, void*& target_ptr) noexcept; + template + 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; #endif // def RELEASE && def ANDROID #endif // def NET diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 655dac4d223..883f4ac8a27 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -874,9 +874,9 @@ MonodroidRuntime::mono_runtime_init ([[maybe_unused]] dynamic_local_string +force_inline void +MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void *&target_ptr) noexcept { + // We don't check for valid return values from image loader, class and method lookup because if any + // of them fails to find the requested entity, they will return `null`. In consequence, we can pass + // these pointers without checking all the way to `mono_method_get_unmanaged_callers_only_ftnptr`, after + // which call we check for errors. This saves some time (not much, but definitely more than zero) MonoImage *image = MonoImageLoader::get_from_index (mono_image_index); - // TODO: implement MonoClassLoader with caching. Best to use indexes instead of keying on tokens. - MonoClass *method_klass = mono_class_get (image, class_index); - MonoMethod *method = mono_get_method (image, method_token, method_klass); + if (XA_UNLIKELY (class_index >= marshal_methods_number_of_classes)) { + log_fatal (LOG_DEFAULT, + "Internal error: invalid index for class cache (expected at most %u, got %u)", + marshal_methods_number_of_classes - 1, + class_index + ); + abort (); + } + + MarshalMethodsManagedClass &klass = marshal_methods_class_cache[class_index]; + if (klass.klass == nullptr) { + klass.klass = mono_class_get (image, klass.token); + } + + MonoMethod *method = mono_get_method (image, method_token, klass.klass); MonoError error; void *ret = mono_method_get_unmanaged_callers_only_ftnptr (method, &error); @@ -24,5 +42,22 @@ void MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t abort (); } - target_ptr = ret; + if constexpr (NeedsLocking) { + // TODO: use atomic write + target_ptr = ret; + } else { + target_ptr = ret; + } +} + +void +MonodroidRuntime::get_function_pointer_at_startup (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void *&target_ptr) noexcept +{ + get_function_pointer (mono_image_index, class_index, method_token, target_ptr); +} + +void +MonodroidRuntime::get_function_pointer_at_runtime (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void *&target_ptr) noexcept +{ + get_function_pointer (mono_image_index, class_index, method_token, target_ptr); } From eb15bd2426db7a78a80a0d8d9f3b21eaae07c81b Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 18 Jul 2022 22:30:11 +0200 Subject: [PATCH 03/79] Update --- external/Java.Interop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/Java.Interop b/external/Java.Interop index 032f1e7160c..fadbb82c3b8 160000 --- a/external/Java.Interop +++ b/external/Java.Interop @@ -1 +1 @@ -Subproject commit 032f1e7160c23a78e5b37208542f763e29992067 +Subproject commit fadbb82c3b8ab7979c19e9f139bdf2589e47549e From a80e4a12d2aff35bbbf3bf1b4b5310c1f73ee960 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 18 Jul 2022 23:07:46 +0200 Subject: [PATCH 04/79] TODOs and CWLs --- .../MarshalMethodsAssemblyRewriter.cs | 4 ++- .../Utilities/MarshalMethodsClassifier.cs | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 0fae90d4886..8c6c5e8e06f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -38,7 +38,9 @@ public void Rewrite (DirectoryAssemblyResolver resolver) foreach (IList methodList in methods.Values) { foreach (MarshalMethodEntry method in methodList) { Console.WriteLine ($"\t{method.NativeCallback.FullName} (token: 0x{method.NativeCallback.MetadataToken.RID:x})"); - Console.WriteLine ($"\t NativeCallback == '{method.NativeCallback}'; Connector == '{method.Connector}'"); + Console.WriteLine ($"\t Top type == '{method.DeclaringType}'"); + Console.WriteLine ($"\t NativeCallback == '{method.NativeCallback}'"); + Console.WriteLine ($"\t Connector == '{method.Connector}'"); Console.WriteLine ($"\t method.NativeCallback.CustomAttributes == {ToStringOrNull (method.NativeCallback?.CustomAttributes)}"); Console.WriteLine ($"\t method.Connector.DeclaringType == {ToStringOrNull (method.Connector?.DeclaringType)}"); Console.WriteLine ($"\t method.Connector.DeclaringType.Methods == {ToStringOrNull (method.Connector.DeclaringType?.Methods)}"); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 64803fab04c..f7e4c7c7336 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -233,6 +233,9 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD return false; } + // TODO: if we can't native callback and/or delegate field using `callbackNameCore`, fall back to `jniName` (which is the first argument to the `[Register]` + // attribute). Or simply use `jniName` at once - needs testing. + string callbackNameCore = connectorName.Substring (HandlerNameStart.Length, connectorName.Length - HandlerNameStart.Length - HandlerNameEnd.Length); string nativeCallbackName = $"n_{callbackNameCore}"; string delegateFieldName = $"cb_{Char.ToLowerInvariant (callbackNameCore[0])}{callbackNameCore.Substring (1)}"; @@ -268,8 +271,40 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD } } + // TODO: check where DeclaringType is lost between here and rewriter, for: + // + // Classifying: + // method: Java.Lang.Object Microsoft.Maui.Controls.Platform.Compatibility.ShellSearchViewAdapter::GetItem(System.Int32) + // registered method: Java.Lang.Object Android.Widget.BaseAdapter::GetItem(System.Int32)) + // Attr: Android.Runtime.RegisterAttribute (parameter count: 3) + // Top type: Microsoft.Maui.Controls.Platform.Compatibility.ShellSearchViewAdapter + // Managed type: Android.Widget.BaseAdapter, Mono.Android + // connector: GetGetItem_IHandler (from spec: 'GetGetItem_IHandler') + // connector name: GetGetItem_IHandler + // native callback name: n_GetItem_I + // delegate field name: cb_getItem_I + // ##G1: Microsoft.Maui.Controls.Platform.Compatibility.ShellSearchViewAdapter -> crc640ec207abc449b2ca/ShellSearchViewAdapter + // ##G1: top type: Microsoft.Maui.Controls.Platform.Compatibility.ShellSearchViewAdapter -> crc640ec207abc449b2ca/ShellSearchViewAdapter + // ##G1: connectorMethod: System.Delegate Android.Widget.BaseAdapter::GetGetItem_IHandler() + // ##G1: delegateField: System.Delegate Android.Widget.BaseAdapter::cb_getItem_I + // + // And in the rewriter: + // + // System.IntPtr Android.Widget.BaseAdapter::n_GetItem_I(System.IntPtr,System.IntPtr,System.Int32) (token: 0x5fe3) + // Top type == 'Microsoft.Maui.Controls.Platform.Compatibility.ShellSearchViewAdapter' + // NativeCallback == 'System.IntPtr Android.Widget.BaseAdapter::n_GetItem_I(System.IntPtr,System.IntPtr,System.Int32)' + // Connector == 'System.Delegate GetGetItem_IHandler()' + // method.NativeCallback.CustomAttributes == Mono.Collections.Generic.Collection`1[Mono.Cecil.CustomAttribute] + // method.Connector.DeclaringType == 'null' + // method.Connector.DeclaringType.Methods == 'null' + // method.CallbackField == System.Delegate cb_getItem_I + // method.CallbackField?.DeclaringType == 'null' + // method.CallbackField?.DeclaringType.Fields == 'null' + Console.WriteLine ($"##G1: {implementedMethod.DeclaringType.FullName} -> {JavaNativeTypeManager.ToJniName (implementedMethod.DeclaringType, tdCache)}"); Console.WriteLine ($"##G1: top type: {topType.FullName} -> {JavaNativeTypeManager.ToJniName (topType, tdCache)}"); + Console.WriteLine ($"##G1: connectorMethod: {connectorMethod?.FullName}"); + Console.WriteLine ($"##G1: delegateField: {delegateField?.FullName}"); StoreMethod ( connectorName, From 49751f7f0cfe2559e710a71f1b99e75d6fe1b4a5 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 26 Jul 2022 10:14:53 +0200 Subject: [PATCH 05/79] Update JI --- external/Java.Interop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/Java.Interop b/external/Java.Interop index fadbb82c3b8..032f1e7160c 160000 --- a/external/Java.Interop +++ b/external/Java.Interop @@ -1 +1 @@ -Subproject commit fadbb82c3b8ab7979c19e9f139bdf2589e47549e +Subproject commit 032f1e7160c23a78e5b37208542f763e29992067 From 657aff0005797636986bc5e9a82a312108bef6c5 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 1 Aug 2022 18:33:43 +0200 Subject: [PATCH 06/79] [WIP] Generate full native symbol names when necessary --- .../MarshalMethodsNativeAssemblyGenerator.cs | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index dde9086ed9a..f35d4924410 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -26,7 +26,7 @@ internal sealed class MonoClass sealed class _JNIEnv {} - // TODO: figure out why opaque classes like these have one byte field in clang's output + // Empty class must have at least one member so that the class address can be obtained [NativeClass] class _jobject { @@ -75,7 +75,7 @@ sealed class _jdoubleArray : _jarray sealed class MarshalMethodInfo { public MarshalMethodEntry Method { get; } - public string NativeSymbolName { get; } + public string NativeSymbolName { get; set; } public List Parameters { get; } public Type ReturnType { get; } public uint ClassCacheIndex { get; } @@ -188,7 +188,7 @@ public override void Init () methods = new List (); // It's possible that several otherwise different methods (from different classes, but with the same - // names and signatures) will actually share the same **short** native symbol name. In this case we must + // names and similar signatures) will actually share the same **short** native symbol name. In this case we must // ensure that they all use long symbol names. This has to be done as a post-processing step, after we // have already iterated over the entire method collection. var overloadedNativeSymbolNames = new Dictionary> (StringComparer.Ordinal); @@ -199,28 +199,22 @@ public override void Init () } } - // WIP: 🡇 foreach (List mmiList in overloadedNativeSymbolNames.Values) { if (mmiList.Count <= 1) { continue; } + Console.WriteLine ($"Overloaded MM: {mmiList[0].NativeSymbolName}"); foreach (MarshalMethodInfo overloadedMethod in mmiList) { + Console.WriteLine ($" implemented in: {overloadedMethod.Method.DeclaringType.FullName} ({overloadedMethod.Method.RegisteredMethod.FullName})"); + overloadedMethod.NativeSymbolName = MakeNativeSymbolName (overloadedMethod.Method, useFullNativeSignature: true); + Console.WriteLine ($" new native symbol name: {overloadedMethod.NativeSymbolName}"); } } } - void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, Dictionary seenClasses, Dictionary> overloadedNativeSymbolNames) + string MakeNativeSymbolName (MarshalMethodEntry entry, bool useFullNativeSignature) { - Console.WriteLine ("marshal method:"); - Console.WriteLine ($" top type: {entry.DeclaringType.FullName}"); - Console.WriteLine ($" registered method: [{entry.RegisteredMethod.DeclaringType.FullName}] {entry.RegisteredMethod.FullName}"); - Console.WriteLine ($" implemented method: [{entry.ImplementedMethod.DeclaringType.FullName}] {entry.ImplementedMethod.FullName}"); - Console.WriteLine ($" native callback: {entry.NativeCallback.FullName}"); - Console.WriteLine ($" connector: {entry.Connector.FullName}"); - Console.WriteLine ($" JNI name: {entry.JniMethodName}"); - Console.WriteLine ($" JNI signature: {entry.JniMethodSignature}"); - var sb = new StringBuilder ("Java_"); sb.Append (MangleForJni (entry.JniTypeName)); sb.Append ('_'); @@ -249,6 +243,26 @@ void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, } } + return sb.ToString (); + + void ThrowInvalidSignature (string signature, string reason) + { + throw new InvalidOperationException ($"Invalid JNI signature '{signature}': {reason}"); + } + } + + void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, Dictionary seenClasses, Dictionary> overloadedNativeSymbolNames) + { + Console.WriteLine ("marshal method:"); + Console.WriteLine ($" top type: {entry.DeclaringType.FullName}"); + Console.WriteLine ($" registered method: [{entry.RegisteredMethod.DeclaringType.FullName}] {entry.RegisteredMethod.FullName}"); + Console.WriteLine ($" implemented method: [{entry.ImplementedMethod.DeclaringType.FullName}] {entry.ImplementedMethod.FullName}"); + Console.WriteLine ($" native callback: {entry.NativeCallback.FullName}"); + Console.WriteLine ($" connector: {entry.Connector.FullName}"); + Console.WriteLine ($" JNI name: {entry.JniMethodName}"); + Console.WriteLine ($" JNI signature: {entry.JniMethodSignature}"); + + string nativeSymbolName = MakeNativeSymbolName (entry, useFullNativeSignature); string klass = $"{entry.NativeCallback.DeclaringType.FullName}, {entry.NativeCallback.Module.Assembly.FullName}"; Console.WriteLine ($" klass == {klass}"); if (!seenClasses.TryGetValue (klass, out int classIndex)) { @@ -266,7 +280,7 @@ void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, (Type returnType, List? parameters) = ParseJniSignature (entry.JniMethodSignature, entry.ImplementedMethod); Console.WriteLine (" parsed!"); - var method = new MarshalMethodInfo (entry, returnType, nativeSymbolName: sb.ToString (), classIndex); + var method = new MarshalMethodInfo (entry, returnType, nativeSymbolName: nativeSymbolName, classIndex); if (parameters != null && parameters.Count > 0) { method.Parameters.AddRange (parameters); } @@ -288,11 +302,6 @@ void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, overloadedMethods.Add (method); methods.Add (method); - - void ThrowInvalidSignature (string signature, string reason) - { - throw new InvalidOperationException ($"Invalid JNI signature '{signature}': {reason}"); - } } string MangleForJni (string name) From e8d3026244e4c559f2dbd6cf516f780003242a96 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 2 Aug 2022 21:29:03 +0200 Subject: [PATCH 07/79] Handle enums properly (except when there are arrays of them) --- .../xaprepare/xaprepare/xaprepare.csproj | 2 +- .../Tasks/GenerateJavaStubs.cs | 7 ++- .../Utilities/MarshalMethodsClassifier.cs | 60 ++++++++++++++----- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/build-tools/xaprepare/xaprepare/xaprepare.csproj b/build-tools/xaprepare/xaprepare/xaprepare.csproj index c8382018794..6a75be1ba23 100644 --- a/build-tools/xaprepare/xaprepare/xaprepare.csproj +++ b/build-tools/xaprepare/xaprepare/xaprepare.csproj @@ -52,7 +52,7 @@ - + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 18e4527c373..b8028d701e3 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -347,6 +347,11 @@ void Run (DirectoryAssemblyResolver res) regCallsWriter.WriteLine ("\t\t// Application and Instrumentation ACWs must be registered first."); foreach (var type in javaTypes) { if (JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache)) { +#if ENABLE_MARSHAL_METHODS + if (!classifier.FoundDynamicallyRegisteredMethods (type)) { + continue; + } +#endif string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.'); regCallsWriter.WriteLine ("\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);", type.GetAssemblyQualifiedName (cache), javaKey); @@ -402,7 +407,7 @@ bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCac jti.Generate (writer); #if ENABLE_MARSHAL_METHODS if (!Debug) { - if (classifier.FoundDynamicallyRegisteredMethods) { + if (classifier.FoundDynamicallyRegisteredMethods (t)) { Log.LogWarning ($"Type '{t.GetAssemblyQualifiedName ()}' will register some of its Java override methods dynamically. This may adversely affect runtime performance. See preceding warnings for names of dynamically registered methods."); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index f7e4c7c7336..43e52e48042 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -106,21 +106,38 @@ sealed class NativeCallbackSignature : IMethodSignatureMatcher public NativeCallbackSignature (MethodDefinition target, TaskLoggingHelper log) { this.log = log; - returnType = MapType (target.ReturnType.FullName); + returnType = MapType (target.ReturnType); paramTypes = new List { "System.IntPtr", // jnienv "System.IntPtr", // native__this }; foreach (ParameterDefinition pd in target.Parameters) { - paramTypes.Add (MapType (pd.ParameterType.FullName)); + paramTypes.Add (MapType (pd.ParameterType)); } } - string MapType (string type) + string MapType (TypeReference typeRef) { - if (verbatimTypes.Contains (type)) { - return type; + string? typeName = null; + if (!typeRef.IsGenericParameter && !typeRef.IsArray) { + TypeDefinition typeDef = typeRef.Resolve (); + if (typeDef == null) { + throw new InvalidOperationException ($"Unable to resolve type '{typeRef.FullName}'"); + } + + if (typeDef.IsEnum) { + // TODO: get the underlying type + return "System.Int32"; + } + } + + if (String.IsNullOrEmpty (typeName)) { + typeName = typeRef.FullName; + } + + if (verbatimTypes.Contains (typeName)) { + return typeName; } return "System.IntPtr"; @@ -129,19 +146,27 @@ string MapType (string type) public bool Matches (MethodDefinition method) { if (method.Parameters.Count != paramTypes.Count || !method.IsStatic) { - log.LogDebugMessage ($"Method '{method.FullName}' doesn't match native callback signature (invalid parameter count or not static)"); + log.LogWarning ($"Method '{method.FullName}' doesn't match native callback signature (invalid parameter count or not static)"); return false; } if (String.Compare (returnType, method.ReturnType.FullName, StringComparison.Ordinal) != 0) { - log.LogDebugMessage ($"Method '{method.FullName}' doesn't match native callback signature (invalid return type)"); + log.LogWarning ($"Method '{method.FullName}' doesn't match native callback signature (invalid return type)"); return false; } for (int i = 0; i < method.Parameters.Count; i++) { ParameterDefinition pd = method.Parameters[i]; - if (String.Compare (pd.ParameterType.FullName, paramTypes[i], StringComparison.Ordinal) != 0) { - log.LogDebugMessage ($"Method '{method.FullName}' doesn't match native callback signature, expected parameter type '{paramTypes[i]}' at position {i}, found '{pd.ParameterType.FullName}'"); + string parameterTypeName; + + if (pd.ParameterType.IsArray) { + parameterTypeName = $"{pd.ParameterType.FullName}[]"; + } else { + parameterTypeName = pd.ParameterType.FullName; + } + + if (String.Compare (parameterTypeName, paramTypes[i], StringComparison.Ordinal) != 0) { + log.LogWarning ($"Method '{method.FullName}' doesn't match native callback signature, expected parameter type '{paramTypes[i]}' at position {i}, found '{parameterTypeName}'"); return false; } } @@ -155,11 +180,10 @@ public bool Matches (MethodDefinition method) Dictionary> marshalMethods; HashSet assemblies; TaskLoggingHelper log; - bool haveDynamicMethods; + HashSet typesWithDynamicallyRegisteredMethods; public IDictionary> MarshalMethods => marshalMethods; public ICollection Assemblies => assemblies; - public bool FoundDynamicallyRegisteredMethods => haveDynamicMethods; #endif public MarshalMethodsClassifier (TypeDefinitionCache tdCache, DirectoryAssemblyResolver res, TaskLoggingHelper log) @@ -170,6 +194,7 @@ public MarshalMethodsClassifier (TypeDefinitionCache tdCache, DirectoryAssemblyR resolver = res ?? throw new ArgumentNullException (nameof (tdCache)); marshalMethods = new Dictionary> (StringComparer.Ordinal); assemblies = new HashSet (); + typesWithDynamicallyRegisteredMethods = new HashSet (); #endif } @@ -192,12 +217,17 @@ public override bool ShouldBeDynamicallyRegistered (TypeDefinition topType, Meth return false; } - haveDynamicMethods = true; + typesWithDynamicallyRegisteredMethods.Add (topType); #endif // def ENABLE_MARSHAL_METHODS return true; } #if ENABLE_MARSHAL_METHODS + public bool FoundDynamicallyRegisteredMethods (TypeDefinition type) + { + return typesWithDynamicallyRegisteredMethods.Contains (type); + } + bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute registerAttribute) { Console.WriteLine ($"Classifying:\n\tmethod: {implementedMethod.FullName}\n\tregistered method: {registeredMethod.FullName})\n\tAttr: {registerAttribute.AttributeType.FullName} (parameter count: {registerAttribute.ConstructorArguments.Count})"); @@ -257,7 +287,7 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD var ncbs = new NativeCallbackSignature (registeredMethod, log); MethodDefinition nativeCallbackMethod = FindMethod (connectorDeclaringType, nativeCallbackName, ncbs); if (nativeCallbackMethod == null) { - log.LogWarning ($"\tUnable to find native callback method matching the '{registeredMethod.FullName}' signature"); + log.LogWarning ($"\tUnable to find native callback method '{nativeCallbackName}' in type '{connectorDeclaringType.FullName}', matching the '{registeredMethod.FullName}' signature (jniName: '{jniName}')"); return false; } @@ -415,8 +445,8 @@ void StoreMethod (string connectorName, MethodDefinition registeredMethod, Marsh marshalMethods.Add (key, list); } - string registeredName = registeredMethod.FullName; - if (list.Count == 0 || !list.Any (me => String.Compare (registeredName, me.RegisteredMethod.FullName, StringComparison.Ordinal) == 0)) { + string registeredName = entry.ImplementedMethod.FullName; + if (list.Count == 0 || !list.Any (me => String.Compare (registeredName, me.ImplementedMethod.FullName, StringComparison.Ordinal) == 0)) { list.Add (entry); } } From 82ece43bc5d40aa824fdcfe65af17f9308b7af3d Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 3 Aug 2022 17:22:43 +0200 Subject: [PATCH 08/79] Generate correct native names and process overloads properly Also, deal with duplicate native symbol names --- .../Utilities/MarshalMethodsClassifier.cs | 2 +- .../MarshalMethodsNativeAssemblyGenerator.cs | 32 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 43e52e48042..b4addc2ae60 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -445,7 +445,7 @@ void StoreMethod (string connectorName, MethodDefinition registeredMethod, Marsh marshalMethods.Add (key, list); } - string registeredName = entry.ImplementedMethod.FullName; + string registeredName = $"{entry.DeclaringType.FullName}::{entry.ImplementedMethod.Name}"; if (list.Count == 0 || !list.Any (me => String.Compare (registeredName, me.ImplementedMethod.FullName, StringComparison.Ordinal) == 0)) { list.Add (entry); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index f35d4924410..ba03134fd1f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -185,7 +185,7 @@ public override void Init () } var seenClasses = new Dictionary (StringComparer.Ordinal); - methods = new List (); + var allMethods = new List (); // It's possible that several otherwise different methods (from different classes, but with the same // names and similar signatures) will actually share the same **short** native symbol name. In this case we must @@ -195,7 +195,7 @@ public override void Init () foreach (IList entryList in MarshalMethods.Values) { bool useFullNativeSignature = entryList.Count > 1; foreach (MarshalMethodEntry entry in entryList) { - ProcessAndAddMethod (entry, useFullNativeSignature, seenClasses, overloadedNativeSymbolNames); + ProcessAndAddMethod (allMethods, entry, useFullNativeSignature, seenClasses, overloadedNativeSymbolNames); } } @@ -211,6 +211,30 @@ public override void Init () Console.WriteLine ($" new native symbol name: {overloadedMethod.NativeSymbolName}"); } } + + // In some cases it's possible that a single type implements two different interfaces which have methods with the same native signature: + // + // Microsoft.Maui.Controls.Handlers.TabbedPageManager/Listeners + // System.Void AndroidX.ViewPager.Widget.ViewPager/IOnPageChangeListener::OnPageSelected(System.Int32) + // System.Void AndroidX.ViewPager2.Widget.ViewPager2/OnPageChangeCallback::OnPageSelected(System.Int32) + // + // Both of the above methods will have the same native implementation and symbol name. e.g. (Java type name being `crc649ff77a65592e7d55/TabbedPageManager_Listeners`): + // Java_crc649ff77a65592e7d55_TabbedPageManager_1Listeners_n_1onPageSelected__I + // + // We need to de-duplicate the entries or the generated native code will fail to build. + var seenNativeSymbols = new HashSet (StringComparer.Ordinal); + methods = new List (); + + foreach (MarshalMethodInfo method in allMethods) { + if (seenNativeSymbols.Contains (method.NativeSymbolName)) { + // TODO: log properly + Console.WriteLine ($"Removed MM duplicate '{method.NativeSymbolName}' (implemented: {method.Method.ImplementedMethod.FullName}; registered: {method.Method.RegisteredMethod.FullName}"); + continue; + } + + seenNativeSymbols.Add (method.NativeSymbolName); + methods.Add (method); + } } string MakeNativeSymbolName (MarshalMethodEntry entry, bool useFullNativeSignature) @@ -251,7 +275,7 @@ void ThrowInvalidSignature (string signature, string reason) } } - void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, Dictionary seenClasses, Dictionary> overloadedNativeSymbolNames) + void ProcessAndAddMethod (List allMethods, MarshalMethodEntry entry, bool useFullNativeSignature, Dictionary seenClasses, Dictionary> overloadedNativeSymbolNames) { Console.WriteLine ("marshal method:"); Console.WriteLine ($" top type: {entry.DeclaringType.FullName}"); @@ -301,7 +325,7 @@ void ProcessAndAddMethod (MarshalMethodEntry entry, bool useFullNativeSignature, } overloadedMethods.Add (method); - methods.Add (method); + allMethods.Add (method); } string MangleForJni (string name) From acd5bc809e0d6188b06c871b0a0c35b3b0389c67 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 3 Aug 2022 18:05:48 +0200 Subject: [PATCH 09/79] When a class is first seen, make sure to set class index properly --- .../Utilities/MarshalMethodsNativeAssemblyGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index ba03134fd1f..dc0418ecb52 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -290,7 +290,8 @@ void ProcessAndAddMethod (List allMethods, MarshalMethodEntry string klass = $"{entry.NativeCallback.DeclaringType.FullName}, {entry.NativeCallback.Module.Assembly.FullName}"; Console.WriteLine ($" klass == {klass}"); if (!seenClasses.TryGetValue (klass, out int classIndex)) { - seenClasses.Add (klass, classes.Count); + classIndex = classes.Count; + seenClasses.Add (klass, classIndex); var mc = new MarshalMethodsManagedClass { token = entry.NativeCallback.DeclaringType.MetadataToken.ToUInt32 (), From e5358f48a1116ebb9431d9bb3093aba1912fd671 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 5 Aug 2022 23:09:23 +0200 Subject: [PATCH 10/79] Trying to get the MAUI sample app running Hit a snag with one of the callbacks using non-blittable arguments (`bool` in this case), but it seems to be the last (famous last words) obstacle preventing the app from starting. A handful of hacks are needed ATM, too. --- .../Android.Runtime/AndroidRuntime.cs | 55 +++++++++++++++++-- src/Mono.Android/Mono.Android.csproj | 4 ++ .../MarshalMethodsAssemblyRewriter.cs | 10 +++- .../MarshalMethodsNativeAssemblyGenerator.cs | 6 +- .../jni/xamarin-android-app-context.cc | 29 +++++++--- 5 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 74b08e63670..dd43b35cf00 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -475,6 +475,17 @@ public override void RegisterNativeMembers (JniType nativeClass, Type type, stri public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan methods) { + Logger.Log (LogLevel.Info, "monodroid-mm", $"RegisterNativeMembers ('{nativeClass.Name}', '{type.FullName}', '{methods.ToString ()}')"); + Logger.Log (LogLevel.Info, "monodroid-mm", "RegisterNativeMembers called from:"); + var st = new StackTrace (true); + foreach (string l in st.ToString ().Split ("\n")) { + Logger.Log (LogLevel.Info, "monodroid", l); + } +#if ENABLE_MARSHAL_METHODS + if (methods.Length == 0) { + return; + } +#endif try { if (FastRegisterNativeMembers (nativeClass, type, methods)) return; @@ -497,6 +508,9 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< MethodInfo []? typeMethods = null; ReadOnlySpan methodsSpan = methods; +#if ENABLE_MARSHAL_METHODS + bool needToRegisterNatives = false; +#endif while (!methodsSpan.IsEmpty) { int newLineIndex = methodsSpan.IndexOf ('\n'); @@ -508,7 +522,7 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< out ReadOnlySpan callbackString, out ReadOnlySpan callbackDeclaringTypeString); - Delegate callback; + Delegate? callback = null; if (callbackString.SequenceEqual ("__export__")) { var mname = name.Slice (2); MethodInfo? minfo = null; @@ -522,6 +536,9 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< if (minfo == null) throw new InvalidOperationException (String.Format ("Specified managed method '{0}' was not found. Signature: {1}", mname.ToString (), signature.ToString ())); callback = CreateDynamicCallback (minfo); +#if ENABLE_MARSHAL_METHODS + needToRegisterNatives = true; +#endif } else { Type callbackDeclaringType = type; if (!callbackDeclaringTypeString.IsEmpty) { @@ -530,16 +547,44 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< while (callbackDeclaringType.ContainsGenericParameters) { callbackDeclaringType = callbackDeclaringType.BaseType!; } - GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler), - callbackDeclaringType, callbackString.ToString ()); - callback = connector (); +#if ENABLE_MARSHAL_METHODS + // TODO: this is temporary hack, it needs a full fledged registration mechanism for methods like these (that is, ones which + // aren't registered with [Register] but are baked into Mono.Android's managed and Java code) + bool createCallback = false; + + if (String.Compare ("Java.Interop.TypeManager+JavaTypeManager", callbackDeclaringType.FullName, StringComparison.Ordinal) == 0) { + if (String.Compare ("GetActivateHandler", callbackString.ToString (), StringComparison.Ordinal) == 0) { + createCallback = true; + } + } + + if (createCallback) { + Logger.Log (LogLevel.Info, "monodroid-mm", $" creating delegate for: '{callbackString.ToString()}' in type {callbackDeclaringType.FullName}"); +#endif + GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler), + callbackDeclaringType, callbackString.ToString ()); + callback = connector (); +#if ENABLE_MARSHAL_METHODS + } else { + Logger.Log (LogLevel.Warn, "monodroid-mm", $" would try to create delegate for: '{callbackString.ToString()}' in type {callbackDeclaringType.FullName}"); + } +#endif + } + + if (callback != null) { +#if ENABLE_MARSHAL_METHODS + needToRegisterNatives = true; +#endif + natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback); } - natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback); } methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default; } +#if ENABLE_MARSHAL_METHODS + if (needToRegisterNatives) +#endif JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, natives.Length); } catch (Exception e) { JniEnvironment.Runtime.RaisePendingException (e); diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 47c4842f26f..0572ca73865 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -49,6 +49,10 @@ $([System.IO.Path]::GetFullPath ('$(OutputPath)$(AssemblyName).dll')) + + $(DefineConstants);ENABLE_MARSHAL_METHODS + + $(OutputPath)..\v1.0\mscorlib.dll diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 8c6c5e8e06f..b88f8118212 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -34,10 +34,16 @@ public void Rewrite (DirectoryAssemblyResolver resolver) unmanagedCallersOnlyAttributes.Add (asm, CreateImportedUnmanagedCallersOnlyAttribute (asm, unmanagedCallersOnlyAttributeCtor)); } + var processedMethods = new HashSet (StringComparer.Ordinal); Console.WriteLine ("Adding the [UnmanagedCallersOnly] attribute to native callback methods and removing unneeded fields+methods"); foreach (IList methodList in methods.Values) { foreach (MarshalMethodEntry method in methodList) { - Console.WriteLine ($"\t{method.NativeCallback.FullName} (token: 0x{method.NativeCallback.MetadataToken.RID:x})"); + string fullNativeCallbackName = method.NativeCallback.FullName; + if (processedMethods.Contains (fullNativeCallbackName)) { + continue; + } + + Console.WriteLine ($"\t{fullNativeCallbackName} (token: 0x{method.NativeCallback.MetadataToken.RID:x})"); Console.WriteLine ($"\t Top type == '{method.DeclaringType}'"); Console.WriteLine ($"\t NativeCallback == '{method.NativeCallback}'"); Console.WriteLine ($"\t Connector == '{method.Connector}'"); @@ -50,6 +56,8 @@ public void Rewrite (DirectoryAssemblyResolver resolver) method.NativeCallback.CustomAttributes.Add (unmanagedCallersOnlyAttributes [method.NativeCallback.Module.Assembly]); method.Connector?.DeclaringType?.Methods?.Remove (method.Connector); method.CallbackField?.DeclaringType?.Fields?.Remove (method.CallbackField); + + processedMethods.Add (fullNativeCallbackName); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index dc0418ecb52..c32aadc25d3 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -278,10 +278,10 @@ void ThrowInvalidSignature (string signature, string reason) void ProcessAndAddMethod (List allMethods, MarshalMethodEntry entry, bool useFullNativeSignature, Dictionary seenClasses, Dictionary> overloadedNativeSymbolNames) { Console.WriteLine ("marshal method:"); - Console.WriteLine ($" top type: {entry.DeclaringType.FullName}"); + Console.WriteLine ($" top type: {entry.DeclaringType.FullName} (token: 0x{entry.DeclaringType.MetadataToken.ToUInt32 ():x})"); Console.WriteLine ($" registered method: [{entry.RegisteredMethod.DeclaringType.FullName}] {entry.RegisteredMethod.FullName}"); Console.WriteLine ($" implemented method: [{entry.ImplementedMethod.DeclaringType.FullName}] {entry.ImplementedMethod.FullName}"); - Console.WriteLine ($" native callback: {entry.NativeCallback.FullName}"); + Console.WriteLine ($" native callback: {entry.NativeCallback.FullName} (token: 0x{entry.NativeCallback.MetadataToken.ToUInt32 ():x})"); Console.WriteLine ($" connector: {entry.Connector.FullName}"); Console.WriteLine ($" JNI name: {entry.JniMethodName}"); Console.WriteLine ($" JNI signature: {entry.JniMethodSignature}"); @@ -338,6 +338,8 @@ string MangleForJni (string name) sb.Replace ('/', '_'); sb.Replace (";", "_2"); sb.Replace ("[", "_3"); + sb.Replace ("$", "_00024"); + // TODO: process unicode chars return sb.ToString (); diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index 0c463e736f1..59ad19c03cf 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -9,12 +9,7 @@ template force_inline void MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void *&target_ptr) noexcept { - // We don't check for valid return values from image loader, class and method lookup because if any - // of them fails to find the requested entity, they will return `null`. In consequence, we can pass - // these pointers without checking all the way to `mono_method_get_unmanaged_callers_only_ftnptr`, after - // which call we check for errors. This saves some time (not much, but definitely more than zero) - MonoImage *image = MonoImageLoader::get_from_index (mono_image_index); - + log_warn (LOG_DEFAULT, __PRETTY_FUNCTION__); if (XA_UNLIKELY (class_index >= marshal_methods_number_of_classes)) { log_fatal (LOG_DEFAULT, "Internal error: invalid index for class cache (expected at most %u, got %u)", @@ -24,21 +19,39 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas abort (); } + // We don't check for valid return values from image loader, class and method lookup because if any + // of them fails to find the requested entity, they will return `null`. In consequence, we can pass + // these pointers without checking all the way to `mono_method_get_unmanaged_callers_only_ftnptr`, after + // which call we check for errors. This saves some time (not much, but definitely more than zero) + MonoImage *image = MonoImageLoader::get_from_index (mono_image_index); + log_warn (LOG_DEFAULT, " image == %p", image); + MarshalMethodsManagedClass &klass = marshal_methods_class_cache[class_index]; if (klass.klass == nullptr) { + log_warn (LOG_DEFAULT, " class not found yet, getting"); klass.klass = mono_class_get (image, klass.token); + log_warn (LOG_DEFAULT, " class == %p", klass.klass); } MonoMethod *method = mono_get_method (image, method_token, klass.klass); + log_warn (LOG_DEFAULT, " method == %p", method); MonoError error; void *ret = mono_method_get_unmanaged_callers_only_ftnptr (method, &error); + log_warn (LOG_DEFAULT, " ret == %p", ret); + if (ret == nullptr || error.error_code != MONO_ERROR_NONE) { // TODO: make the error message friendlier somehow (class, method and assembly names) log_fatal (LOG_DEFAULT, - "Failed to obtain function pointer to method with token 0x%x; class token: 0x%x; assembly index: %u", - method_token, class_index, mono_images_cleanup + "Failed to obtain function pointer to method with token 0x%x; class index: %u; assembly index: %u", + method_token, class_index, mono_image_index ); + + const char *msg = mono_error_get_message (&error); + if (msg != nullptr) { + log_fatal (LOG_DEFAULT, msg); + } + abort (); } From 3c8901aab2f1dd2e87f40bed03d7d73c469402b8 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 8 Aug 2022 17:58:35 +0200 Subject: [PATCH 11/79] MAUI app ran with marshal methods for the first time Unfortunately, 133 out of 182 methods are still registered dynamically, the reason being that they either return `bool` or take it as one of their parameters. `bool` isn't a blittable type and thus such methods cannot be used with `[UnregisteredCallersOnly]`. To move farther, we need to modify the generator to stop generating native callbacks (just them) with `bool`. --- .../Android.Runtime/AndroidRuntime.cs | 66 +++++++++++++-- .../Tasks/GenerateJavaStubs.cs | 4 + .../Utilities/MarshalMethodsClassifier.cs | 83 +++++++++++++++++++ 3 files changed, 145 insertions(+), 8 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index dd43b35cf00..e10b9f1ef75 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -473,16 +473,33 @@ static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments argu public override void RegisterNativeMembers (JniType nativeClass, Type type, string? methods) => RegisterNativeMembers (nativeClass, type, methods.AsSpan ()); +#if ENABLE_MARSHAL_METHODS + // Temporary hack, see comments in RegisterNativeMembers below + static readonly Dictionary> dynamicRegistrationMethods = new Dictionary> (StringComparer.Ordinal) { + {"Android.Views.View+IOnLayoutChangeListenerImplementor", new List { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, + {"Android.Views.View+IOnLayoutChangeListenerInvoker", new List { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, + {"Java.Interop.TypeManager+JavaTypeManager", new List { "GetActivateHandler" }}, + {"Microsoft.Maui.Controls.Platform.Compatibility.ShellPageContainer", new List { "GetOnLayout_ZIIIIHandler" }}, + {"Microsoft.Maui.Platform.ContentViewGroup", new List { "GetOnLayout_ZIIIIHandler" }}, + {"Microsoft.Maui.Platform.LayoutViewGroup", new List { "GetOnLayout_ZIIIIHandler" }}, + {"Microsoft.Maui.Platform.MauiMaterialButton", new List { "GetOnLayout_ZIIIIHandler" }}, + {"Microsoft.Maui.Platform.MauiScrollView", new List { "GetOnLayout_ZIIIIHandler" }}, + {"Microsoft.Maui.Platform.MauiTextView", new List { "GetOnLayout_ZIIIIHandler" }}, + {"Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutRenderer", new List { "GetDrawChild_Landroid_graphics_Canvas_Landroid_view_View_JHandler" }}, + }; +#endif + public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan methods) { - Logger.Log (LogLevel.Info, "monodroid-mm", $"RegisterNativeMembers ('{nativeClass.Name}', '{type.FullName}', '{methods.ToString ()}')"); + Logger.Log (LogLevel.Info, "monodroid-mm", $"RegisterNativeMembers ('{nativeClass?.Name}', '{type?.FullName}', '{methods.ToString ()}')"); Logger.Log (LogLevel.Info, "monodroid-mm", "RegisterNativeMembers called from:"); var st = new StackTrace (true); foreach (string l in st.ToString ().Split ("\n")) { - Logger.Log (LogLevel.Info, "monodroid", l); + Logger.Log (LogLevel.Info, "monodroid-mm", l); } #if ENABLE_MARSHAL_METHODS - if (methods.Length == 0) { + if (methods.IsEmpty) { + Logger.Log (LogLevel.Info, "monodroid-mm", "No methods to register, returning"); return; } #endif @@ -550,11 +567,25 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< #if ENABLE_MARSHAL_METHODS // TODO: this is temporary hack, it needs a full fledged registration mechanism for methods like these (that is, ones which // aren't registered with [Register] but are baked into Mono.Android's managed and Java code) + // + // It is also a temporary hack to register methods taking a `bool` parameter (as `bool` is not blittable, it cannot be used + // with `[UnmanagedCallersOnly]`) bool createCallback = false; + string declaringTypeName = callbackDeclaringType.FullName; + string callbackName = callbackString.ToString (); + + foreach (var kvp in dynamicRegistrationMethods) { + string dynamicTypeName = kvp.Key; - if (String.Compare ("Java.Interop.TypeManager+JavaTypeManager", callbackDeclaringType.FullName, StringComparison.Ordinal) == 0) { - if (String.Compare ("GetActivateHandler", callbackString.ToString (), StringComparison.Ordinal) == 0) { - createCallback = true; + foreach (string dynamicCallbackMethodName in kvp.Value) { + if (ShouldRegisterDynamically (declaringTypeName, callbackName, dynamicTypeName, dynamicCallbackMethodName)) { + createCallback = true; + break; + } + } + + if (createCallback) { + break; } } @@ -583,12 +614,31 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< } #if ENABLE_MARSHAL_METHODS - if (needToRegisterNatives) + if (needToRegisterNatives) { + // We need to reallocate as JniEnvironment.Types.RegisterNatives uses a `foreach` loop and will NREX otherwise (since we aren't filling all + // the slots in the original array potentially) + var newNatives = new JniNativeMethodRegistration[nativesIndex]; + Array.Copy (natives, newNatives, nativesIndex); + natives = newNatives; +#endif + JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, nativesIndex); +#if ENABLE_MARSHAL_METHODS + } #endif - JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, natives.Length); } catch (Exception e) { JniEnvironment.Runtime.RaisePendingException (e); } + +#if ENABLE_MARSHAL_METHODS + bool ShouldRegisterDynamically (string callbackTypeName, string callbackString, string typeName, string callbackName) + { + if (String.Compare (typeName, callbackTypeName, StringComparison.Ordinal) != 0) { + return false; + } + + return String.Compare (callbackName, callbackString, StringComparison.Ordinal) == 0; + } +#endif } static int CountMethods (ReadOnlySpan methodsSpan) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index b8028d701e3..c239cd2f431 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -216,6 +216,10 @@ void Run (DirectoryAssemblyResolver res) return; #if ENABLE_MARSHAL_METHODS + if (classifier.RejectedMethodCount > 0) { + Log.LogWarning ($"Number of methods in the project that will be registered dynamically: {classifier.RejectedMethodCount}"); + } + if (!Debug) { var rewriter = new MarshalMethodsAssemblyRewriter (classifier.MarshalMethods, classifier.Assemblies, marshalMethodsAssemblyPaths, Log); rewriter.Rewrite (res); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index b4addc2ae60..1ec511779c2 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -175,15 +175,33 @@ public bool Matches (MethodDefinition method) } } + // From: https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types + static readonly HashSet blittablePrimitiveTypes = new HashSet (StringComparer.Ordinal) { + "System.Byte", + "System.SByte", + "System.Int16", + "System.UInt16", + "System.Int32", + "System.UInt32", + "System.Int64", + "System.UInt64", + "System.IntPtr", + "System.UIntPtr", + "System.Single", + "System.Double", + }; + TypeDefinitionCache tdCache; DirectoryAssemblyResolver resolver; Dictionary> marshalMethods; HashSet assemblies; TaskLoggingHelper log; HashSet typesWithDynamicallyRegisteredMethods; + ulong rejectedMethodCount; public IDictionary> MarshalMethods => marshalMethods; public ICollection Assemblies => assemblies; + public ulong RejectedMethodCount => rejectedMethodCount; #endif public MarshalMethodsClassifier (TypeDefinitionCache tdCache, DirectoryAssemblyResolver res, TaskLoggingHelper log) @@ -246,6 +264,7 @@ bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registere } log.LogWarning ($"Method '{registeredMethod.FullName}' will be registered dynamically"); + rejectedMethodCount++; return true; } @@ -291,6 +310,10 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD return false; } + if (!EnsureIsValidUnmanagedCallersOnlyTarget (nativeCallbackMethod)) { + return false; + } + // In the standard handler "pattern", the native callback backing field is private, static and thus in the same type // as the native callback. FieldDefinition delegateField = FindField (nativeCallbackMethod.DeclaringType, delegateFieldName); @@ -360,6 +383,66 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD return true; } + bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method) + { + // Requirements: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0#remarks + if (!method.IsStatic) { + return LogReasonWhyAndReturn ($"is not static"); + } + + if (method.HasGenericParameters) { + return LogReasonWhyAndReturn ($"has generic parameters"); + } + + TypeReference type; + if (String.Compare ("System.Void", method.ReturnType.FullName, StringComparison.Ordinal) != 0) { + type = GetRealType (method.ReturnType); + if (!IsBlittable (type)) { + return LogReasonWhyAndReturn ($"has a non-blittable return type '{type.FullName}'"); + } + } + + // TODO: check if this also applies to base types, or just the declaring type + if (method.DeclaringType.HasGenericParameters) { + return LogReasonWhyAndReturn ($"is declared in a type with generic parameters"); + } + + if (!method.HasParameters) { + return true; + } + + foreach (ParameterDefinition pdef in method.Parameters) { + type = GetRealType (pdef.ParameterType); + + if (!IsBlittable (type)) { + return LogReasonWhyAndReturn ($"has a parameter of non-blittable type '{type.FullName}'"); + } + } + + return true; + + bool IsBlittable (TypeReference type) + { + return blittablePrimitiveTypes.Contains (type.FullName); + } + + TypeReference GetRealType (TypeReference type) + { + if (type.IsArray) { + // TODO: check array dimension + return type.GetElementType (); + } + + return type; + } + + bool LogReasonWhyAndReturn (string why) + { + log.LogWarning ($"Method '{method.FullName}' {why}. It cannot be used with the `[UnmanagedCallersOnly]` attribute"); + return false; + } + } + TypeDefinition FindType (AssemblyDefinition asm, string typeName) { foreach (ModuleDefinition md in asm.Modules) { From 7dc40fc2b41f701b9d8843133135e44f5d2da189 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 8 Aug 2022 18:17:10 +0200 Subject: [PATCH 12/79] Check array dimensions when classifying methods --- .../Utilities/MarshalMethodsClassifier.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 1ec511779c2..30537fe4496 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -397,12 +397,11 @@ bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method) TypeReference type; if (String.Compare ("System.Void", method.ReturnType.FullName, StringComparison.Ordinal) != 0) { type = GetRealType (method.ReturnType); - if (!IsBlittable (type)) { + if (!IsAcceptable (type)) { return LogReasonWhyAndReturn ($"has a non-blittable return type '{type.FullName}'"); } } - // TODO: check if this also applies to base types, or just the declaring type if (method.DeclaringType.HasGenericParameters) { return LogReasonWhyAndReturn ($"is declared in a type with generic parameters"); } @@ -414,22 +413,28 @@ bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method) foreach (ParameterDefinition pdef in method.Parameters) { type = GetRealType (pdef.ParameterType); - if (!IsBlittable (type)) { + if (!IsAcceptable (type)) { return LogReasonWhyAndReturn ($"has a parameter of non-blittable type '{type.FullName}'"); } } return true; - bool IsBlittable (TypeReference type) + bool IsAcceptable (TypeReference type) { + if (type.IsArray) { + var array = new ArrayType (type); + if (array.Rank > 1) { + return false; + } + } + return blittablePrimitiveTypes.Contains (type.FullName); } TypeReference GetRealType (TypeReference type) { if (type.IsArray) { - // TODO: check array dimension return type.GetElementType (); } From 4d925702b312fa6fc6ccbc32ccb4bdacdf5b607d Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 11 Aug 2022 00:11:04 +0200 Subject: [PATCH 13/79] We need to generate wrappers for methods with non-blittable params Beginnings of the generator --- .../MarshalMethodsAssemblyRewriter.cs | 93 ++++++++++++++++++- .../Utilities/MarshalMethodsClassifier.cs | 76 ++++++++------- .../Utilities/MarshalMethodsHelpers.cs | 35 +++++++ 3 files changed, 167 insertions(+), 37 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsHelpers.cs diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index b88f8118212..6c725983bdd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -7,6 +7,7 @@ using Microsoft.Android.Build.Tasks; using Microsoft.Build.Utilities; using Mono.Cecil; +using Mono.Cecil.Cil; using Xamarin.Android.Tools; namespace Xamarin.Android.Tasks @@ -34,6 +35,9 @@ public void Rewrite (DirectoryAssemblyResolver resolver) unmanagedCallersOnlyAttributes.Add (asm, CreateImportedUnmanagedCallersOnlyAttribute (asm, unmanagedCallersOnlyAttributeCtor)); } + Console.WriteLine (); + Console.WriteLine ("Modifying assemblies"); + var processedMethods = new HashSet (StringComparer.Ordinal); Console.WriteLine ("Adding the [UnmanagedCallersOnly] attribute to native callback methods and removing unneeded fields+methods"); foreach (IList methodList in methods.Values) { @@ -53,7 +57,13 @@ public void Rewrite (DirectoryAssemblyResolver resolver) Console.WriteLine ($"\t method.CallbackField == {ToStringOrNull (method.CallbackField)}"); Console.WriteLine ($"\t method.CallbackField?.DeclaringType == {ToStringOrNull (method.CallbackField?.DeclaringType)}"); Console.WriteLine ($"\t method.CallbackField?.DeclaringType.Fields == {ToStringOrNull (method.CallbackField?.DeclaringType?.Fields)}"); - method.NativeCallback.CustomAttributes.Add (unmanagedCallersOnlyAttributes [method.NativeCallback.Module.Assembly]); + + if (method.NeedsBlittableWorkaround) { + GenerateBlittableWrapper (method, unmanagedCallersOnlyAttributes); + } else { + method.NativeCallback.CustomAttributes.Add (unmanagedCallersOnlyAttributes [method.NativeCallback.Module.Assembly]); + } + method.Connector?.DeclaringType?.Methods?.Remove (method.Connector); method.CallbackField?.DeclaringType?.Fields?.Remove (method.CallbackField); @@ -127,6 +137,87 @@ string ToStringOrNull (object? o) } } + void GenerateBlittableWrapper (MarshalMethodEntry method, Dictionary unmanagedCallersOnlyAttributes) + { + Console.WriteLine ($"\t Generating blittable wrapper for: {method.NativeCallback.FullName}"); + MethodDefinition callback = method.NativeCallback; + string wrapperName = $"{callback.Name}_mm_wrapper"; + TypeReference retType = MapToBlittableTypeIfNecessary (callback.ReturnType); + var wrapperMethod = new MethodDefinition (wrapperName, callback.Attributes, retType); + callback.DeclaringType.Methods.Add (wrapperMethod); + + wrapperMethod.CustomAttributes.Add (unmanagedCallersOnlyAttributes [callback.Module.Assembly]); + int nparam = 0; + foreach (ParameterDefinition pdef in callback.Parameters) { + TypeReference newType = MapToBlittableTypeIfNecessary (pdef.ParameterType); + wrapperMethod.Parameters.Add (new ParameterDefinition (pdef.Name, pdef.Attributes, newType)); + + OpCode ldargOp; + bool paramRef = false; + switch (nparam++) { + case 0: + ldargOp = OpCodes.Ldarg_0; + break; + + case 1: + ldargOp = OpCodes.Ldarg_1; + break; + + case 2: + ldargOp = OpCodes.Ldarg_2; + break; + + case 3: + ldargOp = OpCodes.Ldarg_3; + break; + + default: + ldargOp = OpCodes.Ldarg_S; + paramRef = true; + break; + } + + Instruction ldarg; + + if (!paramRef) { + ldarg = Instruction.Create (ldargOp); + } else { + ldarg = Instruction.Create (ldargOp, pdef); + } + + // TODO: handle blittable type conversion here + + wrapperMethod.Body.Instructions.Add (ldarg); + } + + wrapperMethod.Body.Instructions.Add (Instruction.Create (OpCodes.Ret)); + Console.WriteLine ($"\t New method: {wrapperMethod.FullName}"); + } + + TypeReference MapToBlittableTypeIfNecessary (TypeReference type) + { + if (type.IsBlittable () || String.Compare ("System.Void", type.FullName, StringComparison.Ordinal) == 0) { + return type; + } + + if (String.Compare ("System.Boolean", type.FullName, StringComparison.Ordinal) == 0) { + // Maps to Java JNI's jboolean which is an unsigned 8-bit type + return ReturnValid (typeof(byte)); + } + + throw new NotSupportedException ($"Cannot map unsupported blittable type '{type.FullName}'"); + + TypeReference ReturnValid (Type typeToLookUp) + { + TypeReference? mappedType = type.Module.Assembly.MainModule.ImportReference (typeToLookUp); + if (mappedType == null) { + throw new InvalidOperationException ($"Unable to obtain reference to type '{typeToLookUp.FullName}'"); + } + + return mappedType; + } + } + ICollection GetAssemblyPaths (AssemblyDefinition asm) { if (!assemblyPaths.TryGetValue (asm.Name.Name, out HashSet paths)) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 30537fe4496..06942668b09 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -14,19 +14,27 @@ namespace Xamarin.Android.Tasks #if ENABLE_MARSHAL_METHODS public sealed class MarshalMethodEntry { - public TypeDefinition DeclaringType { get; } - public MethodDefinition NativeCallback { get; } - public MethodDefinition Connector { get; } - public MethodDefinition RegisteredMethod { get; } - public MethodDefinition ImplementedMethod { get; } - public FieldDefinition CallbackField { get; } - public string JniTypeName { get; } - public string JniMethodName { get; } - public string JniMethodSignature { get; } + public TypeDefinition DeclaringType { get; } + public MethodDefinition NativeCallback { get; } + + /// + /// Used only when is true. This wrapper is generated by + /// when rewriting assemblies, for methods which have either + /// a non-blittable return type or a parameter of a non-blittable type. + /// + public MethodDefinition? NativeCallbackWrapper { get; } + public MethodDefinition Connector { get; } + public MethodDefinition RegisteredMethod { get; } + public MethodDefinition ImplementedMethod { get; } + public FieldDefinition CallbackField { get; } + public string JniTypeName { get; } + public string JniMethodName { get; } + public string JniMethodSignature { get; } + public bool NeedsBlittableWorkaround { get; } public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition nativeCallback, MethodDefinition connector, MethodDefinition registeredMethod, MethodDefinition implementedMethod, FieldDefinition callbackField, string jniTypeName, - string jniName, string jniSignature) + string jniName, string jniSignature, bool needsBlittableWorkaround) { DeclaringType = declaringType ?? throw new ArgumentNullException (nameof (declaringType)); NativeCallback = nativeCallback ?? throw new ArgumentNullException (nameof (nativeCallback)); @@ -37,6 +45,7 @@ public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition native JniTypeName = EnsureNonEmpty (jniTypeName, nameof (jniTypeName)); JniMethodName = EnsureNonEmpty (jniName, nameof (jniName)); JniMethodSignature = EnsureNonEmpty (jniSignature, nameof (jniSignature)); + NeedsBlittableWorkaround = needsBlittableWorkaround; } string EnsureNonEmpty (string s, string argName) @@ -175,22 +184,6 @@ public bool Matches (MethodDefinition method) } } - // From: https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types - static readonly HashSet blittablePrimitiveTypes = new HashSet (StringComparer.Ordinal) { - "System.Byte", - "System.SByte", - "System.Int16", - "System.UInt16", - "System.Int32", - "System.UInt32", - "System.Int64", - "System.UInt64", - "System.IntPtr", - "System.UIntPtr", - "System.Single", - "System.Double", - }; - TypeDefinitionCache tdCache; DirectoryAssemblyResolver resolver; Dictionary> marshalMethods; @@ -310,7 +303,7 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD return false; } - if (!EnsureIsValidUnmanagedCallersOnlyTarget (nativeCallbackMethod)) { + if (!EnsureIsValidUnmanagedCallersOnlyTarget (nativeCallbackMethod, out bool needsBlittableWorkaround)) { return false; } @@ -371,7 +364,9 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD delegateField, JavaNativeTypeManager.ToJniName (topType, tdCache), jniName, - jniSignature) + jniSignature, + needsBlittableWorkaround + ) ); StoreAssembly (connectorMethod.Module.Assembly); @@ -383,27 +378,30 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD return true; } - bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method) + bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method, out bool needsBlittableWorkaround) { + needsBlittableWorkaround = false; + // Requirements: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0#remarks if (!method.IsStatic) { - return LogReasonWhyAndReturn ($"is not static"); + return LogReasonWhyAndReturnFailure ($"is not static"); } if (method.HasGenericParameters) { - return LogReasonWhyAndReturn ($"has generic parameters"); + return LogReasonWhyAndReturnFailure ($"has generic parameters"); } TypeReference type; if (String.Compare ("System.Void", method.ReturnType.FullName, StringComparison.Ordinal) != 0) { type = GetRealType (method.ReturnType); if (!IsAcceptable (type)) { - return LogReasonWhyAndReturn ($"has a non-blittable return type '{type.FullName}'"); + needsBlittableWorkaround = true; + WarnWhy ($"has a non-blittable return type '{type.FullName}'"); } } if (method.DeclaringType.HasGenericParameters) { - return LogReasonWhyAndReturn ($"is declared in a type with generic parameters"); + return LogReasonWhyAndReturnFailure ($"is declared in a type with generic parameters"); } if (!method.HasParameters) { @@ -414,7 +412,8 @@ bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method) type = GetRealType (pdef.ParameterType); if (!IsAcceptable (type)) { - return LogReasonWhyAndReturn ($"has a parameter of non-blittable type '{type.FullName}'"); + needsBlittableWorkaround = true; + WarnWhy ($"has a parameter ({pdef.Name}) of non-blittable type '{type.FullName}'"); } } @@ -429,7 +428,7 @@ bool IsAcceptable (TypeReference type) } } - return blittablePrimitiveTypes.Contains (type.FullName); + return type.IsBlittable (); } TypeReference GetRealType (TypeReference type) @@ -441,11 +440,16 @@ TypeReference GetRealType (TypeReference type) return type; } - bool LogReasonWhyAndReturn (string why) + bool LogReasonWhyAndReturnFailure (string why) { log.LogWarning ($"Method '{method.FullName}' {why}. It cannot be used with the `[UnmanagedCallersOnly]` attribute"); return false; } + + void WarnWhy (string why) + { + log.LogWarning ($"Method '{method.FullName}' {why}. A workaround is required, this may make the application slower"); + } } TypeDefinition FindType (AssemblyDefinition asm, string typeName) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsHelpers.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsHelpers.cs new file mode 100644 index 00000000000..84b5e488253 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsHelpers.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; + +using Mono.Cecil; + +namespace Xamarin.Android.Tasks +{ + static class MarshalMethodsHelpers + { + // From: https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types + static readonly HashSet blittableTypes = new HashSet (StringComparer.Ordinal) { + "System.Byte", + "System.SByte", + "System.Int16", + "System.UInt16", + "System.Int32", + "System.UInt32", + "System.Int64", + "System.UInt64", + "System.IntPtr", + "System.UIntPtr", + "System.Single", + "System.Double", + }; + + public static bool IsBlittable (this TypeReference type) + { + if (type == null) { + throw new ArgumentNullException (nameof (type)); + } + + return blittableTypes.Contains (type.FullName); + } + } +} From 20a2f001ded18bb365c064f40ea854a651656725 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 11 Aug 2022 12:09:49 +0200 Subject: [PATCH 14/79] Blittable wrapper generator progress --- .../Tasks/GenerateJavaStubs.cs | 1 + .../MarshalMethodsAssemblyRewriter.cs | 55 ++++++++++++++++--- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index c239cd2f431..7edce7b1b9d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -221,6 +221,7 @@ void Run (DirectoryAssemblyResolver res) } if (!Debug) { + // TODO: we must rewrite assemblies for all SupportedAbis var rewriter = new MarshalMethodsAssemblyRewriter (classifier.MarshalMethods, classifier.Assemblies, marshalMethodsAssemblyPaths, Log); rewriter.Rewrite (res); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 6c725983bdd..66225da4321 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -142,14 +142,24 @@ void GenerateBlittableWrapper (MarshalMethodEntry method, Dictionary Date: Fri, 12 Aug 2022 11:50:05 +0200 Subject: [PATCH 15/79] Non-blittable marshal method wrappers implemented The app still crashes for some reason (appears something is not decorated with the `[UnmanagedCallersOnly]` attribute) but the IL code generation is complete and the app doesn't report any native/runtime linking errors. TBC next week --- .../Tasks/GenerateJavaStubs.cs | 9 ++- .../MarshalMethodsAssemblyRewriter.cs | 57 ++++++++++++------- .../Utilities/MarshalMethodsClassifier.cs | 20 ++++++- .../MarshalMethodsNativeAssemblyGenerator.cs | 16 ++++-- 4 files changed, 74 insertions(+), 28 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 7edce7b1b9d..11af4516237 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -220,8 +220,14 @@ void Run (DirectoryAssemblyResolver res) Log.LogWarning ($"Number of methods in the project that will be registered dynamically: {classifier.RejectedMethodCount}"); } + if (classifier.WrappedMethodCount > 0) { + Log.LogWarning ($"Number of methods in the project that need marshal method wrappers: {classifier.WrappedMethodCount}"); + } + if (!Debug) { - // TODO: we must rewrite assemblies for all SupportedAbis + // TODO: we must rewrite assemblies for all SupportedAbis. Alternatively, we need to copy the ones that are identical + // Cecil does **not** guarantee that the same assembly modified twice in the same will yield the same result - tokens may differ, so can + // MVID. var rewriter = new MarshalMethodsAssemblyRewriter (classifier.MarshalMethods, classifier.Assemblies, marshalMethodsAssemblyPaths, Log); rewriter.Rewrite (res); } @@ -376,6 +382,7 @@ void StoreMarshalAssemblyPath (string name, ITaskItem asm) return; } + // TODO: we need to keep paths to ALL the assemblies, we need to rewrite them for all RIDs eventually. Right now we rewrite them just for one RID if (!marshalMethodsAssemblyPaths.TryGetValue (name, out HashSet assemblyPaths)) { assemblyPaths = new HashSet (); marshalMethodsAssemblyPaths.Add (name, assemblyPaths); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 66225da4321..6ed21b3afc3 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -149,13 +149,6 @@ void GenerateBlittableWrapper (MarshalMethodEntry method, Dictionary assemblies; TaskLoggingHelper log; HashSet typesWithDynamicallyRegisteredMethods; - ulong rejectedMethodCount; + ulong rejectedMethodCount = 0; + ulong wrappedMethodCount = 0; public IDictionary> MarshalMethods => marshalMethods; public ICollection Assemblies => assemblies; public ulong RejectedMethodCount => rejectedMethodCount; + public ulong WrappedMethodCount => wrappedMethodCount; #endif public MarshalMethodsClassifier (TypeDefinitionCache tdCache, DirectoryAssemblyResolver res, TaskLoggingHelper log) @@ -392,11 +394,13 @@ bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method, out bool } TypeReference type; + bool needsWrapper = false; if (String.Compare ("System.Void", method.ReturnType.FullName, StringComparison.Ordinal) != 0) { type = GetRealType (method.ReturnType); if (!IsAcceptable (type)) { needsBlittableWorkaround = true; WarnWhy ($"has a non-blittable return type '{type.FullName}'"); + needsWrapper = true; } } @@ -405,7 +409,7 @@ bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method, out bool } if (!method.HasParameters) { - return true; + return UpdateWrappedCountAndReturn (true); } foreach (ParameterDefinition pdef in method.Parameters) { @@ -414,10 +418,20 @@ bool EnsureIsValidUnmanagedCallersOnlyTarget (MethodDefinition method, out bool if (!IsAcceptable (type)) { needsBlittableWorkaround = true; WarnWhy ($"has a parameter ({pdef.Name}) of non-blittable type '{type.FullName}'"); + needsWrapper = true; } } - return true; + return UpdateWrappedCountAndReturn (true); + + bool UpdateWrappedCountAndReturn (bool retval) + { + if (needsWrapper) { + wrappedMethodCount++; + } + + return retval; + } bool IsAcceptable (TypeReference type) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index c32aadc25d3..8b5b2eec4c7 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -14,6 +14,8 @@ using Xamarin.Android.Tasks.LLVMIR; +using CecilMethodDefinition = global::Mono.Cecil.MethodDefinition; + namespace Xamarin.Android.Tasks { class MarshalMethodsNativeAssemblyGenerator : LlvmIrComposer @@ -282,19 +284,21 @@ void ProcessAndAddMethod (List allMethods, MarshalMethodEntry Console.WriteLine ($" registered method: [{entry.RegisteredMethod.DeclaringType.FullName}] {entry.RegisteredMethod.FullName}"); Console.WriteLine ($" implemented method: [{entry.ImplementedMethod.DeclaringType.FullName}] {entry.ImplementedMethod.FullName}"); Console.WriteLine ($" native callback: {entry.NativeCallback.FullName} (token: 0x{entry.NativeCallback.MetadataToken.ToUInt32 ():x})"); + Console.WriteLine ($" native callback wrapper: {entry.NativeCallbackWrapper}"); Console.WriteLine ($" connector: {entry.Connector.FullName}"); Console.WriteLine ($" JNI name: {entry.JniMethodName}"); Console.WriteLine ($" JNI signature: {entry.JniMethodSignature}"); + CecilMethodDefinition nativeCallback = entry.NativeCallbackWrapper ?? entry.NativeCallback; string nativeSymbolName = MakeNativeSymbolName (entry, useFullNativeSignature); - string klass = $"{entry.NativeCallback.DeclaringType.FullName}, {entry.NativeCallback.Module.Assembly.FullName}"; + string klass = $"{nativeCallback.DeclaringType.FullName}, {nativeCallback.Module.Assembly.FullName}"; Console.WriteLine ($" klass == {klass}"); if (!seenClasses.TryGetValue (klass, out int classIndex)) { classIndex = classes.Count; seenClasses.Add (klass, classIndex); var mc = new MarshalMethodsManagedClass { - token = entry.NativeCallback.DeclaringType.MetadataToken.ToUInt32 (), + token = nativeCallback.DeclaringType.MetadataToken.ToUInt32 (), ClassName = klass, }; @@ -521,7 +525,8 @@ void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asm var usedBackingFields = new HashSet (StringComparer.Ordinal); foreach (MarshalMethodInfo mmi in methods) { - string asmName = mmi.Method.NativeCallback.DeclaringType.Module.Assembly.Name.Name; + CecilMethodDefinition nativeCallback = mmi.Method.NativeCallbackWrapper ?? mmi.Method.NativeCallback; + string asmName = nativeCallback.DeclaringType.Module.Assembly.Name.Name; if (!asmNameToIndex.TryGetValue (asmName, out uint asmIndex)) { throw new InvalidOperationException ($"Unable to translate assembly name '{asmName}' to its index"); } @@ -539,7 +544,8 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll FieldValue = "null", }; - string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{method.AssemblyCacheIndex}_{method.ClassCacheIndex}_{method.Method.NativeCallback.MetadataToken.ToUInt32():x}"; + CecilMethodDefinition nativeCallback = method.Method.NativeCallbackWrapper ?? method.Method.NativeCallback; + string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{method.AssemblyCacheIndex}_{method.ClassCacheIndex}_{nativeCallback.MetadataToken.ToUInt32():x}"; var backingFieldRef = new LlvmIrVariableReference (backingFieldSignature, backingFieldName, isGlobal: true); if (!usedBackingFields.Contains (backingFieldName)) { @@ -578,7 +584,7 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll new List { new LlvmIrFunctionArgument (typeof(uint), method.AssemblyCacheIndex), new LlvmIrFunctionArgument (typeof(uint), method.ClassCacheIndex), - new LlvmIrFunctionArgument (typeof(uint), method.Method.NativeCallback.MetadataToken.ToUInt32 ()), + new LlvmIrFunctionArgument (typeof(uint), nativeCallback.MetadataToken.ToUInt32 ()), new LlvmIrFunctionArgument (typeof(LlvmIrVariableReference), backingFieldRef), } ); From af9189562f4cf8da79b650b581cf2d37044e3f3e Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 17 Aug 2022 19:54:04 +0200 Subject: [PATCH 16/79] Fix non-blittable wrapper method generation MAUI hello world works now --- .../Tasks/GenerateJavaStubs.cs | 20 +++++++++++-------- .../MarshalMethodsAssemblyRewriter.cs | 14 +++++++------ .../Utilities/MarshalMethodsClassifier.cs | 14 +++++++++---- .../MarshalMethodsNativeAssemblyGenerator.cs | 6 +++--- .../jni/xamarin-android-app-context.cc | 2 +- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 11af4516237..ba994494bd4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -216,14 +216,6 @@ void Run (DirectoryAssemblyResolver res) return; #if ENABLE_MARSHAL_METHODS - if (classifier.RejectedMethodCount > 0) { - Log.LogWarning ($"Number of methods in the project that will be registered dynamically: {classifier.RejectedMethodCount}"); - } - - if (classifier.WrappedMethodCount > 0) { - Log.LogWarning ($"Number of methods in the project that need marshal method wrappers: {classifier.WrappedMethodCount}"); - } - if (!Debug) { // TODO: we must rewrite assemblies for all SupportedAbis. Alternatively, we need to copy the ones that are identical // Cecil does **not** guarantee that the same assembly modified twice in the same will yield the same result - tokens may differ, so can @@ -376,6 +368,18 @@ void Run (DirectoryAssemblyResolver res) template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ())); #if ENABLE_MARSHAL_METHODS + if (!Debug) { + Log.LogDebugMessage ($"Number of generated marshal methods: {classifier.MarshalMethods.Count}"); + + if (classifier.RejectedMethodCount > 0) { + Log.LogWarning ($"Number of methods in the project that will be registered dynamically: {classifier.RejectedMethodCount}"); + } + + if (classifier.WrappedMethodCount > 0) { + Log.LogWarning ($"Number of methods in the project that need marshal method wrappers: {classifier.WrappedMethodCount}"); + } + } + void StoreMarshalAssemblyPath (string name, ITaskItem asm) { if (Debug) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 6ed21b3afc3..799789e8225 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -38,12 +38,13 @@ public void Rewrite (DirectoryAssemblyResolver resolver) Console.WriteLine (); Console.WriteLine ("Modifying assemblies"); - var processedMethods = new HashSet (StringComparer.Ordinal); + var processedMethods = new Dictionary (StringComparer.Ordinal); Console.WriteLine ("Adding the [UnmanagedCallersOnly] attribute to native callback methods and removing unneeded fields+methods"); foreach (IList methodList in methods.Values) { foreach (MarshalMethodEntry method in methodList) { string fullNativeCallbackName = method.NativeCallback.FullName; - if (processedMethods.Contains (fullNativeCallbackName)) { + if (processedMethods.TryGetValue (fullNativeCallbackName, out MethodDefinition nativeCallbackWrapper)) { + method.NativeCallbackWrapper = nativeCallbackWrapper; continue; } @@ -51,7 +52,7 @@ public void Rewrite (DirectoryAssemblyResolver resolver) Console.WriteLine ($"\t Top type == '{method.DeclaringType}'"); Console.WriteLine ($"\t NativeCallback == '{method.NativeCallback}'"); Console.WriteLine ($"\t Connector == '{method.Connector}'"); - Console.WriteLine ($"\t method.NativeCallback.CustomAttributes == {ToStringOrNull (method.NativeCallback?.CustomAttributes)}"); + Console.WriteLine ($"\t method.NativeCallback.CustomAttributes == {ToStringOrNull (method.NativeCallback.CustomAttributes)}"); Console.WriteLine ($"\t method.Connector.DeclaringType == {ToStringOrNull (method.Connector?.DeclaringType)}"); Console.WriteLine ($"\t method.Connector.DeclaringType.Methods == {ToStringOrNull (method.Connector.DeclaringType?.Methods)}"); Console.WriteLine ($"\t method.CallbackField == {ToStringOrNull (method.CallbackField)}"); @@ -59,7 +60,7 @@ public void Rewrite (DirectoryAssemblyResolver resolver) Console.WriteLine ($"\t method.CallbackField?.DeclaringType.Fields == {ToStringOrNull (method.CallbackField?.DeclaringType?.Fields)}"); if (method.NeedsBlittableWorkaround) { - GenerateBlittableWrapper (method, unmanagedCallersOnlyAttributes); + method.NativeCallbackWrapper = GenerateBlittableWrapper (method, unmanagedCallersOnlyAttributes); } else { method.NativeCallback.CustomAttributes.Add (unmanagedCallersOnlyAttributes [method.NativeCallback.Module.Assembly]); } @@ -67,7 +68,7 @@ public void Rewrite (DirectoryAssemblyResolver resolver) method.Connector?.DeclaringType?.Methods?.Remove (method.Connector); method.CallbackField?.DeclaringType?.Fields?.Remove (method.CallbackField); - processedMethods.Add (fullNativeCallbackName); + processedMethods.Add (fullNativeCallbackName, method.NativeCallback); } } @@ -137,7 +138,7 @@ string ToStringOrNull (object? o) } } - void GenerateBlittableWrapper (MarshalMethodEntry method, Dictionary unmanagedCallersOnlyAttributes) + MethodDefinition GenerateBlittableWrapper (MarshalMethodEntry method, Dictionary unmanagedCallersOnlyAttributes) { Console.WriteLine ($"\t Generating blittable wrapper for: {method.NativeCallback.FullName}"); MethodDefinition callback = method.NativeCallback; @@ -203,6 +204,7 @@ void GenerateBlittableWrapper (MarshalMethodEntry method, Dictionary + /// The "real" native callback, used if it doesn't contain any non-blittable types in its parameters + /// or return type. + /// + MethodDefinition nativeCallbackReal; /// /// Used only when is true. This wrapper is generated by /// when rewriting assemblies, for methods which have either /// a non-blittable return type or a parameter of a non-blittable type. /// - public MethodDefinition? NativeCallbackWrapper { get; } + public MethodDefinition? NativeCallbackWrapper { get; set; } + public TypeDefinition DeclaringType { get; } public MethodDefinition Connector { get; } public MethodDefinition RegisteredMethod { get; } public MethodDefinition ImplementedMethod { get; } @@ -32,12 +36,14 @@ public sealed class MarshalMethodEntry public string JniMethodSignature { get; } public bool NeedsBlittableWorkaround { get; } + public MethodDefinition NativeCallback => NativeCallbackWrapper ?? nativeCallbackReal; + public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition nativeCallback, MethodDefinition connector, MethodDefinition registeredMethod, MethodDefinition implementedMethod, FieldDefinition callbackField, string jniTypeName, string jniName, string jniSignature, bool needsBlittableWorkaround) { DeclaringType = declaringType ?? throw new ArgumentNullException (nameof (declaringType)); - NativeCallback = nativeCallback ?? throw new ArgumentNullException (nameof (nativeCallback)); + nativeCallbackReal = nativeCallback ?? throw new ArgumentNullException (nameof (nativeCallback)); Connector = connector ?? throw new ArgumentNullException (nameof (connector)); RegisteredMethod = registeredMethod ?? throw new ArgumentNullException (nameof (registeredMethod)); ImplementedMethod = implementedMethod ?? throw new ArgumentNullException (nameof (implementedMethod)); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 8b5b2eec4c7..23c30d9fa3f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -289,7 +289,7 @@ void ProcessAndAddMethod (List allMethods, MarshalMethodEntry Console.WriteLine ($" JNI name: {entry.JniMethodName}"); Console.WriteLine ($" JNI signature: {entry.JniMethodSignature}"); - CecilMethodDefinition nativeCallback = entry.NativeCallbackWrapper ?? entry.NativeCallback; + CecilMethodDefinition nativeCallback = entry.NativeCallback; string nativeSymbolName = MakeNativeSymbolName (entry, useFullNativeSignature); string klass = $"{nativeCallback.DeclaringType.FullName}, {nativeCallback.Module.Assembly.FullName}"; Console.WriteLine ($" klass == {klass}"); @@ -525,7 +525,7 @@ void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asm var usedBackingFields = new HashSet (StringComparer.Ordinal); foreach (MarshalMethodInfo mmi in methods) { - CecilMethodDefinition nativeCallback = mmi.Method.NativeCallbackWrapper ?? mmi.Method.NativeCallback; + CecilMethodDefinition nativeCallback = mmi.Method.NativeCallback; string asmName = nativeCallback.DeclaringType.Module.Assembly.Name.Name; if (!asmNameToIndex.TryGetValue (asmName, out uint asmIndex)) { throw new InvalidOperationException ($"Unable to translate assembly name '{asmName}' to its index"); @@ -544,7 +544,7 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll FieldValue = "null", }; - CecilMethodDefinition nativeCallback = method.Method.NativeCallbackWrapper ?? method.Method.NativeCallback; + CecilMethodDefinition nativeCallback = method.Method.NativeCallback; string backingFieldName = $"native_cb_{method.Method.JniMethodName}_{method.AssemblyCacheIndex}_{method.ClassCacheIndex}_{nativeCallback.MetadataToken.ToUInt32():x}"; var backingFieldRef = new LlvmIrVariableReference (backingFieldSignature, backingFieldName, isGlobal: true); diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index 59ad19c03cf..da4944a4568 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -34,7 +34,7 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas } MonoMethod *method = mono_get_method (image, method_token, klass.klass); - log_warn (LOG_DEFAULT, " method == %p", method); + log_warn (LOG_DEFAULT, " method == %p (%s.%s:%s)", method, mono_class_get_namespace (klass.klass), mono_class_get_name (klass.klass), mono_method_get_name (method)); MonoError error; void *ret = mono_method_get_unmanaged_callers_only_ftnptr (method, &error); From 72c4b6073701078888f0fc1be01eff71da664bbe Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 18 Aug 2022 13:58:18 +0200 Subject: [PATCH 17/79] Disable some debug CWLs --- src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 18e4527c373..14a17a1c295 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -237,7 +237,9 @@ void Run (DirectoryAssemblyResolver res) string managedKey = type.FullName.Replace ('/', '.'); string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.'); +#if ENABLE_MARSHAL_METHODS Console.WriteLine ($"##G2: {type.FullName} -> {javaKey}"); +#endif acw_map.Write (type.GetPartialAssemblyQualifiedName (cache)); acw_map.Write (';'); acw_map.Write (javaKey); @@ -385,7 +387,9 @@ bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCac bool ok = true; foreach (var t in javaTypes) { +#if ENABLE_MARSHAL_METHODS Console.WriteLine ($"##G0: JCW for {t.FullName}"); +#endif if (t.IsInterface) { // Interfaces are in typemap but they shouldn't have JCW generated for them continue; From a8471cd767bca81d0b012616d9ec2180f68d6789 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 18 Aug 2022 20:32:55 +0200 Subject: [PATCH 18/79] Add some debugging info to function pointer lookups We can now report names of the mathods and classes that we failed to look up. --- .../Tasks/GeneratePackageManagerJava.cs | 11 +- .../LlvmIrGenerator/LlvmIrGenerator.cs | 4 +- .../MarshalMethodsNativeAssemblyGenerator.cs | 138 ++++++++++++++---- src/monodroid/jni/application_dso_stub.cc | 17 +++ src/monodroid/jni/monodroid-glue-internal.hh | 3 + .../jni/xamarin-android-app-context.cc | 51 ++++++- src/monodroid/jni/xamarin-app.hh | 18 +++ 7 files changed, 202 insertions(+), 40 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 16105940dd2..e06e300a462 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -444,11 +444,12 @@ void AddEnvironment () #if ENABLE_MARSHAL_METHODS var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (GenerateJavaStubs.MarshalMethodsRegisterTaskKey, RegisteredTaskObjectLifetime.Build); - var marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator { - NumberOfAssembliesInApk = assemblyCount, - UniqueAssemblyNames = uniqueAssemblyNames, - MarshalMethods = marshalMethodsState?.MarshalMethods, - }; + var marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator ( + assemblyCount, + uniqueAssemblyNames, + marshalMethodsState?.MarshalMethods, + Log + ); marshalMethodsAsmGen.Init (); #endif foreach (string abi in SupportedAbis) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs index 48f5db5964d..eafeb39e173 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs @@ -588,10 +588,10 @@ public void WriteStructureArray (StructureInfo info, IList (info, instances, LlvmIrVariableOptions.Default, symbolName, writeFieldComment, initialComment); } - public void WriteArray (IList values, string symbolName) + public void WriteArray (IList values, string symbolName, string? initialComment = null) { WriteEOL (); - WriteEOL (symbolName); + WriteEOL (initialComment ?? symbolName); ulong arrayStringCounter = 0; var strings = new List (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 23c30d9fa3f..2fc433a83f5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -9,6 +9,7 @@ using Java.Interop.Tools.TypeNameMappings; using Java.Interop.Tools.JavaCallableWrappers; +using Microsoft.Android.Build.Tasks; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -130,6 +131,12 @@ sealed class MarshalMethodsManagedClass public string ClassName; }; + struct MarshalMethodName + { + public uint id; + public string name; + } + static readonly Dictionary jniSimpleTypeMap = new Dictionary { { 'Z', typeof(bool) }, { 'B', typeof(byte) }, @@ -153,12 +160,14 @@ sealed class MarshalMethodsManagedClass { 'L', typeof(_jobjectArray) }, }; - public ICollection UniqueAssemblyNames { get; set; } - public int NumberOfAssembliesInApk { get; set; } - public IDictionary> MarshalMethods { get; set; } + ICollection uniqueAssemblyNames; + int numberOfAssembliesInApk; + IDictionary> marshalMethods; + TaskLoggingHelper logger; StructureInfo monoImage; StructureInfo marshalMethodsClass; + StructureInfo marshalMethodName; StructureInfo monoClass; StructureInfo<_JNIEnv> _jniEnvSI; StructureInfo<_jobject> _jobjectSI; @@ -179,10 +188,22 @@ sealed class MarshalMethodsManagedClass List methods; List> classes = new List> (); + public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, IDictionary> marshalMethods, TaskLoggingHelper logger) + { + this.numberOfAssembliesInApk = numberOfAssembliesInApk; + this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames)); + this.marshalMethods = marshalMethods; + this.logger = logger ?? throw new ArgumentNullException (nameof (logger)); + + if (uniqueAssemblyNames.Count != numberOfAssembliesInApk) { + throw new InvalidOperationException ("Internal error: number of assemblies in the apk doesn't match the number of unique assembly names"); + } + } + public override void Init () { - Console.WriteLine ($"Marshal methods count: {MarshalMethods?.Count ?? 0}"); - if (MarshalMethods == null || MarshalMethods.Count == 0) { + Console.WriteLine ($"Marshal methods count: {marshalMethods?.Count ?? 0}"); + if (marshalMethods == null || marshalMethods.Count == 0) { return; } @@ -194,7 +215,7 @@ public override void Init () // ensure that they all use long symbol names. This has to be done as a post-processing step, after we // have already iterated over the entire method collection. var overloadedNativeSymbolNames = new Dictionary> (StringComparer.Ordinal); - foreach (IList entryList in MarshalMethods.Values) { + foreach (IList entryList in marshalMethods.Values) { bool useFullNativeSignature = entryList.Count > 1; foreach (MarshalMethodEntry entry in entryList) { ProcessAndAddMethod (allMethods, entry, useFullNativeSignature, seenClasses, overloadedNativeSymbolNames); @@ -229,8 +250,7 @@ public override void Init () foreach (MarshalMethodInfo method in allMethods) { if (seenNativeSymbols.Contains (method.NativeSymbolName)) { - // TODO: log properly - Console.WriteLine ($"Removed MM duplicate '{method.NativeSymbolName}' (implemented: {method.Method.ImplementedMethod.FullName}; registered: {method.Method.RegisteredMethod.FullName}"); + logger.LogDebugMessage ($"Removed MM duplicate '{method.NativeSymbolName}' (implemented: {method.Method.ImplementedMethod.FullName}; registered: {method.Method.RegisteredMethod.FullName}"); continue; } @@ -336,15 +356,40 @@ void ProcessAndAddMethod (List allMethods, MarshalMethodEntry string MangleForJni (string name) { Console.WriteLine ($" mangling '{name}'"); - var sb = new StringBuilder (name); + var sb = new StringBuilder (); + + foreach (char ch in name) { + switch (ch) { + case '_': + sb.Append ("_1"); + break; - sb.Replace ("_", "_1"); - sb.Replace ('/', '_'); - sb.Replace (";", "_2"); - sb.Replace ("[", "_3"); - sb.Replace ("$", "_00024"); + case '/': + sb.Append ('_'); + break; + + case ';': + sb.Append ("_2"); + break; - // TODO: process unicode chars + case '[': + sb.Append ("_3"); + break; + + case '$': + sb.Append ("_00024"); + break; + + default: + if ((int)ch > 127) { + sb.Append ("_0"); + sb.Append (((int)ch).ToString ("x04")); + } else { + sb.Append (ch); + } + break; + } + } return sb.ToString (); } @@ -492,6 +537,7 @@ protected override void MapStructures (LlvmIrGenerator generator) monoImage = generator.MapStructure (); monoClass = generator.MapStructure (); marshalMethodsClass = generator.MapStructure (); + marshalMethodName = generator.MapStructure (); _jniEnvSI = generator.MapStructure<_JNIEnv> (); _jobjectSI = generator.MapStructure<_jobject> (); _jclassSI = generator.MapStructure<_jclass> (); @@ -511,10 +557,50 @@ protected override void MapStructures (LlvmIrGenerator generator) protected override void Write (LlvmIrGenerator generator) { - Dictionary asmNameToIndex = WriteAssemblyImageCache (generator); + WriteAssemblyImageCache (generator, out Dictionary asmNameToIndex); WriteClassCache (generator); LlvmIrVariableReference get_function_pointer_ref = WriteXamarinAppInitFunction (generator); WriteNativeMethods (generator, asmNameToIndex, get_function_pointer_ref); + + var mm_class_names = new List (); + foreach (StructureInstance klass in classes) { + mm_class_names.Add (klass.Obj.ClassName); + } + generator.WriteArray (mm_class_names, "mm_class_names", "Names of classes in which marshal methods reside"); + + var uniqueMethods = new Dictionary (StringComparer.Ordinal); + foreach (MarshalMethodInfo mmi in methods) { + if (uniqueMethods.ContainsKey (mmi.Method.NativeCallback.FullName)) { + continue; + } + uniqueMethods.Add (mmi.Method.NativeCallback.FullName, mmi); + } + + MarshalMethodName name; + var mm_method_names = new List> (); + foreach (var kvp in uniqueMethods) { + MarshalMethodInfo mmi = kvp.Value; + string asmName = Path.GetFileName (mmi.Method.NativeCallback.Module.Assembly.MainModule.FileName); + if (!asmNameToIndex.TryGetValue (asmName, out uint idx)) { + throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to cache array index"); + } + + name = new MarshalMethodName { + // Tokens are unique per assembly + id = mmi.Method.NativeCallback.MetadataToken.ToUInt32 () | (idx << 32), + name = mmi.Method.NativeCallback.Name, + }; + mm_method_names.Add (new StructureInstance (name)); + } + + // Must terminate with an "invalid" entry + name = new MarshalMethodName { + id = 0, + name = String.Empty, + }; + mm_method_names.Add (new StructureInstance (name)); + + generator.WriteStructureArray (marshalMethodName, mm_method_names, LlvmIrVariableOptions.GlobalConstant, "mm_method_names"); } void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asmNameToIndex, LlvmIrVariableReference get_function_pointer_ref) @@ -663,33 +749,25 @@ void WriteClassCache (LlvmIrGenerator generator) generator.WriteStructureArray (marshalMethodsClass, classes, LlvmIrVariableOptions.GlobalWritable, "marshal_methods_class_cache"); } - Dictionary WriteAssemblyImageCache (LlvmIrGenerator generator) + void WriteAssemblyImageCache (LlvmIrGenerator generator, out Dictionary asmNameToIndex) { - if (UniqueAssemblyNames == null) { - throw new InvalidOperationException ("Internal error: unique assembly names not provided"); - } - - if (UniqueAssemblyNames.Count != NumberOfAssembliesInApk) { - throw new InvalidOperationException ("Internal error: number of assemblies in the apk doesn't match the number of unique assembly names"); - } - bool is64Bit = generator.Is64Bit; - generator.WriteStructureArray (monoImage, (ulong)NumberOfAssembliesInApk, "assembly_image_cache", isArrayOfPointers: true); + generator.WriteStructureArray (monoImage, (ulong)numberOfAssembliesInApk, "assembly_image_cache", isArrayOfPointers: true); - var asmNameToIndex = new Dictionary (StringComparer.Ordinal); + var asmNameToIndexData = new Dictionary (StringComparer.Ordinal); if (is64Bit) { WriteHashes (); } else { WriteHashes (); } - return asmNameToIndex; + asmNameToIndex = asmNameToIndexData; void WriteHashes () where T: struct { var hashes = new Dictionary (); uint index = 0; - foreach (string name in UniqueAssemblyNames) { + foreach (string name in uniqueAssemblyNames) { string clippedName = Path.GetFileNameWithoutExtension (name); ulong hashFull = HashName (name, is64Bit); ulong hashClipped = HashName (clippedName, is64Bit); @@ -718,7 +796,7 @@ void WriteHashes () where T: struct for (int i = 0; i < keys.Count; i++) { (string name, uint idx) = hashes[keys[i]]; indices.Add (idx); - asmNameToIndex.Add (name, idx); + asmNameToIndexData.Add (name, idx); } generator.WriteArray ( indices, diff --git a/src/monodroid/jni/application_dso_stub.cc b/src/monodroid/jni/application_dso_stub.cc index 42196ff6113..5cbc623260a 100644 --- a/src/monodroid/jni/application_dso_stub.cc +++ b/src/monodroid/jni/application_dso_stub.cc @@ -186,6 +186,23 @@ MarshalMethodsManagedClass marshal_methods_class_cache[] = { }, }; +const char* const mm_class_names[2] = { + "one", + "two", +}; + +const MarshalMethodName mm_method_names[] = { + { + .id = 1, + .name = "one", + }, + + { + .id = 2, + .name = "two", + }, +}; + void xamarin_app_init ([[maybe_unused]] get_function_pointer_fn fn) noexcept { // Dummy diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index dda7b74e09f..2572ac89824 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -350,6 +350,9 @@ namespace xamarin::android::internal static void monodroid_debugger_unhandled_exception (MonoException *ex); #if defined (RELEASE) && defined (ANDROID) + static const char* get_method_name (uint32_t mono_image_index, uint32_t method_token) noexcept; + static const char* get_class_name (uint32_t class_index) noexcept; + template 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; diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index da4944a4568..518f1671f9b 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -5,11 +5,45 @@ using namespace xamarin::android::internal; +static constexpr char Unknown[] = "Unknown"; + +const char* +MonodroidRuntime::get_method_name (uint32_t mono_image_index, uint32_t method_token) noexcept +{ + uint64_t id = (static_cast(mono_image_index) << 32) | method_token; + + size_t i = 0; + while (mm_method_names[i].id != 0) { + if (mm_method_names[i].id == id) { + return mm_method_names[i].name; + } + } + + return Unknown; +} + +const char* +MonodroidRuntime::get_class_name (uint32_t class_index) noexcept +{ + if (class_index >= marshal_methods_number_of_classes) { + return Unknown; + } + + return mm_class_names[class_index]; +} + template force_inline void MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void *&target_ptr) noexcept { log_warn (LOG_DEFAULT, __PRETTY_FUNCTION__); + log_debug ( + LOG_ASSEMBLY, + "MM: Trying to look up pointer to method '%s' in class '%s'", + get_method_name (mono_image_index, method_token), + get_class_name (class_index) + ); + if (XA_UNLIKELY (class_index >= marshal_methods_number_of_classes)) { log_fatal (LOG_DEFAULT, "Internal error: invalid index for class cache (expected at most %u, got %u)", @@ -41,11 +75,22 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas log_warn (LOG_DEFAULT, " ret == %p", ret); if (ret == nullptr || error.error_code != MONO_ERROR_NONE) { - // TODO: make the error message friendlier somehow (class, method and assembly names) log_fatal (LOG_DEFAULT, - "Failed to obtain function pointer to method with token 0x%x; class index: %u; assembly index: %u", - method_token, class_index, mono_image_index + "Failed to obtain function pointer to method '%s' in class '%s'", + get_method_name (mono_image_index, method_token), + get_class_name (class_index) + ); + log_fatal (LOG_DEFAULT, + "Looked for image index %u, class index %u, method token 0x%x", + mono_image_index, + class_index, + method_token ); + if (image == nullptr) { + log_fatal (LOG_DEFAULT, "Failed to load MonoImage for the assembly"); + } else if (method == nullptr) { + log_fatal (LOG_DEFAULT, "Failed to load class from the assembly"); + } const char *msg = mono_error_get_message (&error); if (msg != nullptr) { diff --git a/src/monodroid/jni/xamarin-app.hh b/src/monodroid/jni/xamarin-app.hh index bd1076a2ae0..23ef37cba33 100644 --- a/src/monodroid/jni/xamarin-app.hh +++ b/src/monodroid/jni/xamarin-app.hh @@ -330,6 +330,24 @@ MONO_API MONO_API_EXPORT const xamarin::android::hash_t assembly_image_cache_has MONO_API MONO_API_EXPORT uint32_t marshal_methods_number_of_classes; MONO_API MONO_API_EXPORT MarshalMethodsManagedClass marshal_methods_class_cache[]; +// +// These tables store names of classes and managed callback methods used in the generated marshal methods +// code. They are used just for error reporting. +// +// Class names are found at the same indexes as their corresponding entries in the `marshal_methods_class_cache` array +// above. Method names are stored as token:name pairs and the array must end with an "invalid" terminator entry (token +// == 0; name == nullptr) +// +struct MarshalMethodName +{ + // combination of assembly index (high 32 bits) and method token (low 32 bits) + const uint64_t id; + const char *name; +}; + +MONO_API MONO_API_EXPORT const char* const mm_class_names[]; +MONO_API MONO_API_EXPORT const MarshalMethodName mm_method_names[]; + using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr); MONO_API MONO_API_EXPORT void xamarin_app_init (get_function_pointer_fn fn) noexcept; From cfaabf2638afb1510f02c8d86850a7369c480f6c Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 18 Aug 2022 22:44:50 +0200 Subject: [PATCH 19/79] Escape double quotes in function attribute value --- .../Utilities/LlvmIrGenerator/FunctionAttributes.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs index f71e1aa0401..84167d9084d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs @@ -53,7 +53,12 @@ public string Render () sb.Append ('"'); } - RenderAssignedValue (sb); + var value = new StringBuilder (); + RenderAssignedValue (value); + + // LLVM IR escapes characters as \xx where xx is hexadecimal ASCII code + value.Replace ("\"", "\\22"); + sb.Append (value); if (Quoted) { sb.Append ('"'); From d99b7e3ef11e2bb559fe7c9ad89527fc27e139d8 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 19 Aug 2022 14:53:59 +0200 Subject: [PATCH 20/79] Fix and optimize debugging info for class and method names --- .../MarshalMethodsNativeAssemblyGenerator.cs | 66 +++++++++++++++---- src/monodroid/jni/monodroid-glue-internal.hh | 2 +- .../jni/xamarin-android-app-context.cc | 49 +++++++------- 3 files changed, 81 insertions(+), 36 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 2fc433a83f5..040ec4853ff 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -16,6 +16,7 @@ using Xamarin.Android.Tasks.LLVMIR; using CecilMethodDefinition = global::Mono.Cecil.MethodDefinition; +using CecilParameterDefinition = global::Mono.Cecil.ParameterDefinition; namespace Xamarin.Android.Tasks { @@ -131,9 +132,25 @@ sealed class MarshalMethodsManagedClass public string ClassName; }; - struct MarshalMethodName + sealed class MarshalMethodNameDataProvider : NativeAssemblerStructContextDataProvider { - public uint id; + public override string GetComment (object data, string fieldName) + { + var methodName = EnsureType (data); + + if (String.Compare ("id", fieldName, StringComparison.Ordinal) == 0) { + return $"id 0x{methodName.id:x}; name: {methodName.name}"; + } + + return String.Empty; + } + } + + [NativeAssemblerStructContextDataProvider (typeof(MarshalMethodNameDataProvider))] + sealed class MarshalMethodName + { + [NativeAssembler (UsesDataProvider = true)] + public ulong id; public string name; } @@ -568,27 +585,32 @@ protected override void Write (LlvmIrGenerator generator) } generator.WriteArray (mm_class_names, "mm_class_names", "Names of classes in which marshal methods reside"); - var uniqueMethods = new Dictionary (StringComparer.Ordinal); + var uniqueMethods = new Dictionary (); foreach (MarshalMethodInfo mmi in methods) { - if (uniqueMethods.ContainsKey (mmi.Method.NativeCallback.FullName)) { + string asmName = Path.GetFileName (mmi.Method.NativeCallback.Module.Assembly.MainModule.FileName); + if (!asmNameToIndex.TryGetValue (asmName, out uint idx)) { + throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to cache array index"); + } + + ulong id = ((ulong)idx << 32) | (ulong)mmi.Method.NativeCallback.MetadataToken.ToUInt32 (); + if (uniqueMethods.ContainsKey (id)) { continue; } - uniqueMethods.Add (mmi.Method.NativeCallback.FullName, mmi); + uniqueMethods.Add (id, mmi); } MarshalMethodName name; + var methodName = new StringBuilder (); var mm_method_names = new List> (); foreach (var kvp in uniqueMethods) { + ulong id = kvp.Key; MarshalMethodInfo mmi = kvp.Value; - string asmName = Path.GetFileName (mmi.Method.NativeCallback.Module.Assembly.MainModule.FileName); - if (!asmNameToIndex.TryGetValue (asmName, out uint idx)) { - throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to cache array index"); - } + RenderMethodNameWithParams (mmi.Method.NativeCallback, methodName); name = new MarshalMethodName { // Tokens are unique per assembly - id = mmi.Method.NativeCallback.MetadataToken.ToUInt32 () | (idx << 32), - name = mmi.Method.NativeCallback.Name, + id = id, + name = methodName.ToString (), }; mm_method_names.Add (new StructureInstance (name)); } @@ -601,6 +623,28 @@ protected override void Write (LlvmIrGenerator generator) mm_method_names.Add (new StructureInstance (name)); generator.WriteStructureArray (marshalMethodName, mm_method_names, LlvmIrVariableOptions.GlobalConstant, "mm_method_names"); + + void RenderMethodNameWithParams (CecilMethodDefinition md, StringBuilder buffer) + { + buffer.Clear (); + buffer.Append (md.Name); + buffer.Append ('('); + + if (md.HasParameters) { + bool first = true; + foreach (CecilParameterDefinition pd in md.Parameters) { + if (!first) { + buffer.Append (','); + } else { + first = false; + } + + buffer.Append (pd.ParameterType.Name); + } + } + + buffer.Append (')'); + } } void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asmNameToIndex, LlvmIrVariableReference get_function_pointer_ref) diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index 2572ac89824..c90a69c57fe 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -354,7 +354,7 @@ namespace xamarin::android::internal static const char* get_class_name (uint32_t class_index) noexcept; template - 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 (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; #endif // def RELEASE && def ANDROID diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index 518f1671f9b..259e43562fe 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -12,11 +12,13 @@ MonodroidRuntime::get_method_name (uint32_t mono_image_index, uint32_t method_to { uint64_t id = (static_cast(mono_image_index) << 32) | method_token; + log_debug (LOG_ASSEMBLY, "Looking for name of method with id 0x%llx, in mono image at index %u", id, mono_image_index); size_t i = 0; while (mm_method_names[i].id != 0) { if (mm_method_names[i].id == id) { return mm_method_names[i].name; } + i++; } return Unknown; @@ -34,14 +36,14 @@ MonodroidRuntime::get_class_name (uint32_t class_index) noexcept template force_inline void -MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void *&target_ptr) noexcept +MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept { log_warn (LOG_DEFAULT, __PRETTY_FUNCTION__); log_debug ( LOG_ASSEMBLY, - "MM: Trying to look up pointer to method '%s' in class '%s'", - get_method_name (mono_image_index, method_token), - get_class_name (class_index) + "MM: Trying to look up pointer to method '%s' (token 0x%x) in class '%s' (index %u)", + get_method_name (mono_image_index, method_token), method_token, + get_class_name (class_index), class_index ); if (XA_UNLIKELY (class_index >= marshal_methods_number_of_classes)) { @@ -58,34 +60,31 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas // these pointers without checking all the way to `mono_method_get_unmanaged_callers_only_ftnptr`, after // which call we check for errors. This saves some time (not much, but definitely more than zero) MonoImage *image = MonoImageLoader::get_from_index (mono_image_index); - log_warn (LOG_DEFAULT, " image == %p", image); - MarshalMethodsManagedClass &klass = marshal_methods_class_cache[class_index]; if (klass.klass == nullptr) { - log_warn (LOG_DEFAULT, " class not found yet, getting"); klass.klass = mono_class_get (image, klass.token); - log_warn (LOG_DEFAULT, " class == %p", klass.klass); } MonoMethod *method = mono_get_method (image, method_token, klass.klass); - log_warn (LOG_DEFAULT, " method == %p (%s.%s:%s)", method, mono_class_get_namespace (klass.klass), mono_class_get_name (klass.klass), mono_method_get_name (method)); - MonoError error; void *ret = mono_method_get_unmanaged_callers_only_ftnptr (method, &error); - log_warn (LOG_DEFAULT, " ret == %p", ret); if (ret == nullptr || error.error_code != MONO_ERROR_NONE) { - log_fatal (LOG_DEFAULT, - "Failed to obtain function pointer to method '%s' in class '%s'", - get_method_name (mono_image_index, method_token), - get_class_name (class_index) + log_fatal ( + LOG_DEFAULT, + "Failed to obtain function pointer to method '%s' in class '%s'", + get_method_name (mono_image_index, method_token), + get_class_name (class_index) ); - log_fatal (LOG_DEFAULT, - "Looked for image index %u, class index %u, method token 0x%x", - mono_image_index, - class_index, - method_token + + log_fatal ( + LOG_DEFAULT, + "Looked for image index %u, class index %u, method token 0x%x", + mono_image_index, + class_index, + method_token ); + if (image == nullptr) { log_fatal (LOG_DEFAULT, "Failed to load MonoImage for the assembly"); } else if (method == nullptr) { @@ -100,22 +99,24 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas abort (); } + log_debug (LOG_ASSEMBLY, "Loaded pointer to method %s", mono_method_get_name (method)); + if constexpr (NeedsLocking) { - // TODO: use atomic write - target_ptr = ret; + __atomic_store_n (&target_ptr, ret, __ATOMIC_RELEASE); + //target_ptr = ret; } else { target_ptr = ret; } } void -MonodroidRuntime::get_function_pointer_at_startup (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void *&target_ptr) noexcept +MonodroidRuntime::get_function_pointer_at_startup (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept { get_function_pointer (mono_image_index, class_index, method_token, target_ptr); } void -MonodroidRuntime::get_function_pointer_at_runtime (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void *&target_ptr) noexcept +MonodroidRuntime::get_function_pointer_at_runtime (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept { get_function_pointer (mono_image_index, class_index, method_token, target_ptr); } From cfa1b306d592474afb1f559d136a1004b374a917 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 19 Aug 2022 14:59:49 +0200 Subject: [PATCH 21/79] Reorganize code, move error handling to the end of function --- .../jni/xamarin-android-app-context.cc | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index 259e43562fe..58eaa69ab82 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -69,44 +69,46 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas MonoError error; void *ret = mono_method_get_unmanaged_callers_only_ftnptr (method, &error); - if (ret == nullptr || error.error_code != MONO_ERROR_NONE) { - log_fatal ( - LOG_DEFAULT, - "Failed to obtain function pointer to method '%s' in class '%s'", - get_method_name (mono_image_index, method_token), - get_class_name (class_index) - ); + if (XA_LIKELY (ret != nullptr)) { + if constexpr (NeedsLocking) { + __atomic_store_n (&target_ptr, ret, __ATOMIC_RELEASE); + } else { + target_ptr = ret; + } - log_fatal ( - LOG_DEFAULT, - "Looked for image index %u, class index %u, method token 0x%x", - mono_image_index, - class_index, - method_token - ); + log_debug (LOG_ASSEMBLY, "Loaded pointer to method %s", mono_method_get_name (method)); + return; + } - if (image == nullptr) { - log_fatal (LOG_DEFAULT, "Failed to load MonoImage for the assembly"); - } else if (method == nullptr) { - log_fatal (LOG_DEFAULT, "Failed to load class from the assembly"); - } + log_fatal ( + LOG_DEFAULT, + "Failed to obtain function pointer to method '%s' in class '%s'", + get_method_name (mono_image_index, method_token), + get_class_name (class_index) + ); + + log_fatal ( + LOG_DEFAULT, + "Looked for image index %u, class index %u, method token 0x%x", + mono_image_index, + class_index, + method_token + ); + + if (image == nullptr) { + log_fatal (LOG_DEFAULT, "Failed to load MonoImage for the assembly"); + } else if (method == nullptr) { + log_fatal (LOG_DEFAULT, "Failed to load class from the assembly"); + } + if (error.error_code != MONO_ERROR_NONE) { const char *msg = mono_error_get_message (&error); if (msg != nullptr) { log_fatal (LOG_DEFAULT, msg); } - - abort (); } - log_debug (LOG_ASSEMBLY, "Loaded pointer to method %s", mono_method_get_name (method)); - - if constexpr (NeedsLocking) { - __atomic_store_n (&target_ptr, ret, __ATOMIC_RELEASE); - //target_ptr = ret; - } else { - target_ptr = ret; - } + abort (); } void From 334619bc22fc8cfa468823cebff263a7ada2168a Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 19 Aug 2022 20:13:58 +0200 Subject: [PATCH 22/79] Disable marshal methods for PR --- Directory.Build.props | 2 +- src/monodroid/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 6e22304f9c6..3c0f998eab8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -26,7 +26,7 @@ - <_EnableMarshalMethods>YesPlease + <_EnableMarshalMethods>NoThanks diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index d0b9bd9b72b..41577f4f55a 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -50,7 +50,7 @@ option(STRIP_DEBUG "Strip debugging information when linking" ${STRIP_DEBUG_DEFA option(DISABLE_DEBUG "Disable the built-in debugging code" OFF) option(USE_CCACHE "Use ccache, if found, to speed up recompilation" ${CCACHE_OPTION_DEFAULT}) -set(ENABLE_MARSHAL_METHODS True) +set(ENABLE_MARSHAL_METHODS False) if((MINGW OR NOT WIN32) AND USE_CCACHE) if(CMAKE_CXX_COMPILER MATCHES "/ccache/") From c9332a8127e61e9b3656e438bebf42f94c2ed358 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 19 Aug 2022 20:21:24 +0200 Subject: [PATCH 23/79] Move some code behind the ENABLE_MARSHAL_METHODS flag --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index e10b9f1ef75..88f6d4d2994 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -491,13 +491,14 @@ public override void RegisterNativeMembers (JniType nativeClass, Type type, stri public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan methods) { +#if ENABLE_MARSHAL_METHODS Logger.Log (LogLevel.Info, "monodroid-mm", $"RegisterNativeMembers ('{nativeClass?.Name}', '{type?.FullName}', '{methods.ToString ()}')"); Logger.Log (LogLevel.Info, "monodroid-mm", "RegisterNativeMembers called from:"); var st = new StackTrace (true); foreach (string l in st.ToString ().Split ("\n")) { Logger.Log (LogLevel.Info, "monodroid-mm", l); } -#if ENABLE_MARSHAL_METHODS + if (methods.IsEmpty) { Logger.Log (LogLevel.Info, "monodroid-mm", "No methods to register, returning"); return; From e518a4ee26aa754532e70007df33d7114d70c448 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 22 Aug 2022 11:24:11 +0200 Subject: [PATCH 24/79] Remove workarounds for methods with non-blittable types These are now handled by the wrapper generator --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 88f6d4d2994..f2dec6af7be 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -479,13 +479,6 @@ public override void RegisterNativeMembers (JniType nativeClass, Type type, stri {"Android.Views.View+IOnLayoutChangeListenerImplementor", new List { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, {"Android.Views.View+IOnLayoutChangeListenerInvoker", new List { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, {"Java.Interop.TypeManager+JavaTypeManager", new List { "GetActivateHandler" }}, - {"Microsoft.Maui.Controls.Platform.Compatibility.ShellPageContainer", new List { "GetOnLayout_ZIIIIHandler" }}, - {"Microsoft.Maui.Platform.ContentViewGroup", new List { "GetOnLayout_ZIIIIHandler" }}, - {"Microsoft.Maui.Platform.LayoutViewGroup", new List { "GetOnLayout_ZIIIIHandler" }}, - {"Microsoft.Maui.Platform.MauiMaterialButton", new List { "GetOnLayout_ZIIIIHandler" }}, - {"Microsoft.Maui.Platform.MauiScrollView", new List { "GetOnLayout_ZIIIIHandler" }}, - {"Microsoft.Maui.Platform.MauiTextView", new List { "GetOnLayout_ZIIIIHandler" }}, - {"Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutRenderer", new List { "GetDrawChild_Landroid_graphics_Canvas_Landroid_view_View_JHandler" }}, }; #endif @@ -568,9 +561,6 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< #if ENABLE_MARSHAL_METHODS // TODO: this is temporary hack, it needs a full fledged registration mechanism for methods like these (that is, ones which // aren't registered with [Register] but are baked into Mono.Android's managed and Java code) - // - // It is also a temporary hack to register methods taking a `bool` parameter (as `bool` is not blittable, it cannot be used - // with `[UnmanagedCallersOnly]`) bool createCallback = false; string declaringTypeName = callbackDeclaringType.FullName; string callbackName = callbackString.ToString (); From 905e78464a87f147e443e796128e331d90a4a643 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 22 Aug 2022 12:45:29 +0200 Subject: [PATCH 25/79] Build marshal methods support unconditionally --- Directory.Build.props | 4 - .../Android.Runtime/AndroidRuntime.cs | 32 +------- src/Mono.Android/Mono.Android.csproj | 4 - .../Tasks/GenerateJavaStubs.cs | 31 ++------ .../Tasks/GeneratePackageManagerJava.cs | 13 +--- .../Tasks/PrepareAbiItems.cs | 5 -- .../MarshalMethodsAssemblyRewriter.cs | 3 - .../Utilities/MarshalMethodsClassifier.cs | 11 --- .../MarshalMethodsNativeAssemblyGenerator.cs | 7 -- .../Utilities/MarshalMethodsState.cs | 2 - .../Xamarin.Android.Build.Tasks.csproj | 4 - src/monodroid/CMakeLists.txt | 37 +-------- src/monodroid/jni/mono-image-loader.hh | 5 +- src/monodroid/jni/monodroid-glue.cc | 16 +--- src/monodroid/jni/xamarin-app-marshaling.cc | 78 ------------------- 15 files changed, 19 insertions(+), 233 deletions(-) delete mode 100644 src/monodroid/jni/xamarin-app-marshaling.cc diff --git a/Directory.Build.props b/Directory.Build.props index 3c0f998eab8..4fd65ee28eb 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -25,10 +25,6 @@ true - - <_EnableMarshalMethods>NoThanks - - 13.0.99 diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index f2dec6af7be..288c820a33d 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -475,10 +475,10 @@ public override void RegisterNativeMembers (JniType nativeClass, Type type, stri #if ENABLE_MARSHAL_METHODS // Temporary hack, see comments in RegisterNativeMembers below - static readonly Dictionary> dynamicRegistrationMethods = new Dictionary> (StringComparer.Ordinal) { - {"Android.Views.View+IOnLayoutChangeListenerImplementor", new List { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, - {"Android.Views.View+IOnLayoutChangeListenerInvoker", new List { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, - {"Java.Interop.TypeManager+JavaTypeManager", new List { "GetActivateHandler" }}, + static readonly Dictionary dynamicRegistrationMethods = new Dictionary (StringComparer.Ordinal) { + {"Android.Views.View+IOnLayoutChangeListenerImplementor", new string[] { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, + {"Android.Views.View+IOnLayoutChangeListenerInvoker", new string[] { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, + {"Java.Interop.TypeManager+JavaTypeManager", new string[] { "GetActivateHandler" }}, }; #endif @@ -488,9 +488,7 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< Logger.Log (LogLevel.Info, "monodroid-mm", $"RegisterNativeMembers ('{nativeClass?.Name}', '{type?.FullName}', '{methods.ToString ()}')"); Logger.Log (LogLevel.Info, "monodroid-mm", "RegisterNativeMembers called from:"); var st = new StackTrace (true); - foreach (string l in st.ToString ().Split ("\n")) { - Logger.Log (LogLevel.Info, "monodroid-mm", l); - } + Logger.Log (LogLevel.Info, "monodroid-mm", st.ToString ()); if (methods.IsEmpty) { Logger.Log (LogLevel.Info, "monodroid-mm", "No methods to register, returning"); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 799789e8225..8af1d3878ea 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -123,8 +123,9 @@ void MoveFile (string source, string target) Files.CopyIfChanged (source, target); try { File.Delete (source); - } catch (Exception) { + } catch (Exception ex) { log.LogWarning ($"Unable to delete source file '{source}' when moving it to '{target}'"); + log.LogDebugMessage (ex.ToString ()); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 00aab45617e..478c52389d0 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -283,7 +283,7 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD return false; } - // TODO: if we can't native callback and/or delegate field using `callbackNameCore`, fall back to `jniName` (which is the first argument to the `[Register]` + // TODO: if we can't find native callback and/or delegate field using `callbackNameCore`, fall back to `jniName` (which is the first argument to the `[Register]` // attribute). Or simply use `jniName` at once - needs testing. string callbackNameCore = connectorName.Substring (HandlerNameStart.Length, connectorName.Length - HandlerNameStart.Length - HandlerNameEnd.Length); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 040ec4853ff..e34a3395f44 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -231,6 +231,29 @@ public override void Init () // names and similar signatures) will actually share the same **short** native symbol name. In this case we must // ensure that they all use long symbol names. This has to be done as a post-processing step, after we // have already iterated over the entire method collection. + // + // A handful of examples from the Hello World MAUI app: + // + // Overloaded MM: Java_crc64e1fb321c08285b90_CellAdapter_n_1onActionItemClicked + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean Android.Views.ActionMode/ICallback::OnActionItemClicked(Android.Views.ActionMode,Android.Views.IMenuItem)) + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean AndroidX.AppCompat.View.ActionMode/ICallback::OnActionItemClicked(AndroidX.AppCompat.View.ActionMode,Android.Views.IMenuItem)) + // new native symbol name: Java_crc64e1fb321c08285b90_CellAdapter_n_1onActionItemClicked__Landroidx_appcompat_view_ActionMode_2Landroid_view_MenuItem_2 + // + // Overloaded MM: Java_crc64e1fb321c08285b90_CellAdapter_n_1onCreateActionMode + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean Android.Views.ActionMode/ICallback::OnCreateActionMode(Android.Views.ActionMode,Android.Views.IMenu)) + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean AndroidX.AppCompat.View.ActionMode/ICallback::OnCreateActionMode(AndroidX.AppCompat.View.ActionMode,Android.Views.IMenu)) + // new native symbol name: Java_crc64e1fb321c08285b90_CellAdapter_n_1onCreateActionMode__Landroidx_appcompat_view_ActionMode_2Landroid_view_Menu_2 + // + // Overloaded MM: Java_crc64e1fb321c08285b90_CellAdapter_n_1onDestroyActionMode + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Void Android.Views.ActionMode/ICallback::OnDestroyActionMode(Android.Views.ActionMode)) + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Void AndroidX.AppCompat.View.ActionMode/ICallback::OnDestroyActionMode(AndroidX.AppCompat.View.ActionMode)) + // new native symbol name: Java_crc64e1fb321c08285b90_CellAdapter_n_1onDestroyActionMode__Landroidx_appcompat_view_ActionMode_2 + // + // Overloaded MM: Java_crc64e1fb321c08285b90_CellAdapter_n_1onPrepareActionMode + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean Android.Views.ActionMode/ICallback::OnPrepareActionMode(Android.Views.ActionMode,Android.Views.IMenu)) + // implemented in: Microsoft.Maui.Controls.Handlers.Compatibility.CellAdapter (System.Boolean AndroidX.AppCompat.View.ActionMode/ICallback::OnPrepareActionMode(AndroidX.AppCompat.View.ActionMode,Android.Views.IMenu)) + // new native symbol name: Java_crc64e1fb321c08285b90_CellAdapter_n_1onPrepareActionMode__Landroidx_appcompat_view_ActionMode_2Landroid_view_Menu_2 + // var overloadedNativeSymbolNames = new Dictionary> (StringComparer.Ordinal); foreach (IList entryList in marshalMethods.Values) { bool useFullNativeSignature = entryList.Count > 1; From b60c767a5d658f415cbd7e4f28efbf8f0962e79a Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 23 Aug 2022 22:47:24 +0200 Subject: [PATCH 30/79] Don't package mono.android.jar when marshal methods are enabled --- src/Mono.Android/Mono.Android.targets | 3 -- .../Android/Xamarin.Android.Aapt2.targets | 2 + .../Tasks/GenerateJavaStubs.cs | 38 ++++++++++++------- .../Utilities/MarshalMethodsClassifier.cs | 21 ++++++++-- .../Xamarin.Android.Common.targets | 16 ++++++-- .../java/mono/android/TypeManager.java | 0 .../java/mono/android/app/IntentService.java | 0 7 files changed, 56 insertions(+), 24 deletions(-) rename src/{Mono.Android => java-runtime}/java/mono/android/TypeManager.java (100%) rename src/{Mono.Android => java-runtime}/java/mono/android/app/IntentService.java (100%) diff --git a/src/Mono.Android/Mono.Android.targets b/src/Mono.Android/Mono.Android.targets index 5a772b4e719..2ce7e3b4c10 100644 --- a/src/Mono.Android/Mono.Android.targets +++ b/src/Mono.Android/Mono.Android.targets @@ -198,9 +198,6 @@ 1.8 1.6 - - - True <_ProtobufFormat Condition=" '$(_ProtobufFormat)' == '' ">False <_Aapt2ProguardRules Condition=" '$(AndroidLinkTool)' != '' ">$(IntermediateOutputPath)aapt_rules.txt + <_OutputFileDir>$([System.IO.Path]::GetDirectoryName ('$(_PackagedResources)')) + > marshalMethodsAssemblyPaths = null; - if (!Debug) { + if (useMarshalMethods) { marshalMethodsAssemblyPaths = new Dictionary> (StringComparer.Ordinal); } @@ -164,7 +167,7 @@ void Run (DirectoryAssemblyResolver res) } res.Load (assembly.ItemSpec); - if (!Debug) { + if (useMarshalMethods) { StoreMarshalAssemblyPath (Path.GetFileNameWithoutExtension (assembly.ItemSpec), assembly); } } @@ -193,7 +196,10 @@ void Run (DirectoryAssemblyResolver res) var javaTypes = new List (); foreach (TypeDefinition td in allJavaTypes) { - if (!userAssemblies.ContainsKey (td.Module.Assembly.Name.Name) || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (td, cache)) { + // Whem marshal methods are in use we do not want to skip non-user assemblies (such as Mono.Android) - we need to generate JCWs for them during + // application build, unlike in Debug configuration or when marshal methods are disabled, in which case we use JCWs generated during Xamarin.Android + // build and stored in a jar file. + if ((!useMarshalMethods && !userAssemblies.ContainsKey (td.Module.Assembly.Name.Name)) || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (td, cache)) { continue; } @@ -201,16 +207,16 @@ void Run (DirectoryAssemblyResolver res) } MarshalMethodsClassifier classifier = null; - if (!Debug) { + if (useMarshalMethods) { classifier = new MarshalMethodsClassifier (cache, res, Log); } // Step 2 - Generate Java stub code - var success = CreateJavaSources (javaTypes, cache, classifier); + var success = CreateJavaSources (javaTypes, cache, classifier, useMarshalMethods); if (!success) return; - if (!Debug) { + if (useMarshalMethods) { // TODO: we must rewrite assemblies for all SupportedAbis. Alternatively, we need to copy the ones that are identical // Cecil does **not** guarantee that the same assembly modified twice in the same will yield the same result - tokens may differ, so can // MVID. @@ -343,7 +349,7 @@ void Run (DirectoryAssemblyResolver res) regCallsWriter.WriteLine ("\t\t// Application and Instrumentation ACWs must be registered first."); foreach (var type in javaTypes) { if (JavaNativeTypeManager.IsApplication (type, cache) || JavaNativeTypeManager.IsInstrumentation (type, cache)) { - if (!classifier.FoundDynamicallyRegisteredMethods (type)) { + if (classifier != null && !classifier.FoundDynamicallyRegisteredMethods (type)) { continue; } @@ -359,7 +365,7 @@ void Run (DirectoryAssemblyResolver res) SaveResource (applicationTemplateFile, applicationTemplateFile, real_app_dir, template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ())); - if (!Debug) { + if (useMarshalMethods) { Log.LogDebugMessage ($"Number of generated marshal methods: {classifier.MarshalMethods.Count}"); if (classifier.RejectedMethodCount > 0) { @@ -373,7 +379,7 @@ void Run (DirectoryAssemblyResolver res) void StoreMarshalAssemblyPath (string name, ITaskItem asm) { - if (Debug) { + if (!useMarshalMethods) { return; } @@ -387,8 +393,12 @@ void StoreMarshalAssemblyPath (string name, ITaskItem asm) } } - bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCache cache, MarshalMethodsClassifier classifier) + bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCache cache, MarshalMethodsClassifier classifier, bool useMarshalMethods) { + if (useMarshalMethods && classifier == null) { + throw new ArgumentNullException (nameof (classifier)); + } + string outputPath = Path.Combine (OutputDirectory, "src"); string monoInit = GetMonoInitSource (AndroidSdkPlatform); bool hasExportReference = ResolvedAssemblies.Any (assembly => Path.GetFileName (assembly.ItemSpec) == "Mono.Android.Export.dll"); @@ -410,7 +420,7 @@ bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCac }; jti.Generate (writer); - if (!Debug) { + if (useMarshalMethods) { if (classifier.FoundDynamicallyRegisteredMethods (t)) { Log.LogWarning ($"Type '{t.GetAssemblyQualifiedName ()}' will register some of its Java override methods dynamically. This may adversely affect runtime performance. See preceding warnings for names of dynamically registered methods."); } @@ -449,7 +459,7 @@ bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCac } } - if (!Debug) { + if (useMarshalMethods) { BuildEngine4.RegisterTaskObjectAssemblyLocal (MarshalMethodsRegisterTaskKey, new MarshalMethodsState (classifier.MarshalMethods), RegisteredTaskObjectLifetime.Build); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 6fa5f8031be..1e2d07d4cf9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -10,7 +10,7 @@ namespace Xamarin.Android.Tasks { - public sealed class MarshalMethodEntry + sealed class MarshalMethodEntry { /// /// The "real" native callback, used if it doesn't contain any non-blittable types in its parameters @@ -138,8 +138,7 @@ string MapType (TypeReference typeRef) } if (typeDef.IsEnum) { - // TODO: get the underlying type - return "System.Int32"; + return GetEnumUnderlyingType (typeDef).FullName; } } @@ -154,6 +153,19 @@ string MapType (TypeReference typeRef) return "System.IntPtr"; } + static TypeReference GetEnumUnderlyingType (TypeDefinition td) + { + var fields = td.Fields; + + for (int i = 0; i < fields.Count; i++) { + var field = fields [i]; + if (!field.IsStatic) + return field.FieldType; + } + + throw new InvalidOperationException ($"Unable to determine underlying type of the '{td.FullName}' enum"); + } + public bool Matches (MethodDefinition method) { if (method.Parameters.Count != paramTypes.Count || !method.IsStatic) { @@ -258,6 +270,9 @@ bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registere // TODO: Probably should check if all the methods and fields are private and static - only then it is safe(ish) to remove them bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodDefinition registeredMethod, MethodDefinition implementedMethod, string jniName, string jniSignature) { + Console.WriteLine ($" topType: {topType.FullName}"); + Console.WriteLine ($" registered method type: {registeredMethod.DeclaringType.FullName}"); + Console.WriteLine ($" implemented method type: {implementedMethod.DeclaringType.FullName}"); const string HandlerNameStart = "Get"; const string HandlerNameEnd = "Handler"; diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 686fb70c67d..29ad12888f4 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -331,6 +331,10 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. false true True + True + False + <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' ">False + <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' != 'True' ">$(AndroidEnableMarshalMethods) @@ -1337,7 +1341,7 @@ because xbuild doesn't support framework reference assemblies. <_AndroidJarAndDexDirectory Condition=" '$(UsingAndroidNETSdk)' != 'True' ">$(TargetFrameworkDirectory) <_AndroidJarAndDexDirectory Condition=" '$(UsingAndroidNETSdk)' == 'True' ">$(_XATargetFrameworkDirectories) - + @@ -1360,17 +1364,20 @@ because xbuild doesn't support framework reference assemblies. /> - + - + @@ -1490,7 +1497,8 @@ because xbuild doesn't support framework reference assemblies. SupportedAbis="@(_BuildTargetAbis)" SupportedOSPlatformVersion="$(SupportedOSPlatformVersion)" SkipJniAddNativeMethodRegistrationAttributeScan="$(_SkipJniAddNativeMethodRegistrationAttributeScan)" - CheckedBuild="$(_AndroidCheckedBuild)"> + CheckedBuild="$(_AndroidCheckedBuild)" + EnableMarshalMethods="$(_AndroidUseMarshalMethods)"> diff --git a/src/Mono.Android/java/mono/android/TypeManager.java b/src/java-runtime/java/mono/android/TypeManager.java similarity index 100% rename from src/Mono.Android/java/mono/android/TypeManager.java rename to src/java-runtime/java/mono/android/TypeManager.java diff --git a/src/Mono.Android/java/mono/android/app/IntentService.java b/src/java-runtime/java/mono/android/app/IntentService.java similarity index 100% rename from src/Mono.Android/java/mono/android/app/IntentService.java rename to src/java-runtime/java/mono/android/app/IntentService.java From 92fa671d51bcf465e43b6913de09338b2ccebb6d Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 24 Aug 2022 18:14:00 +0200 Subject: [PATCH 31/79] Remove JI workaround --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 288c820a33d..3bfffb9001b 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -604,11 +604,6 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< #if ENABLE_MARSHAL_METHODS if (needToRegisterNatives) { - // We need to reallocate as JniEnvironment.Types.RegisterNatives uses a `foreach` loop and will NREX otherwise (since we aren't filling all - // the slots in the original array potentially) - var newNatives = new JniNativeMethodRegistration[nativesIndex]; - Array.Copy (natives, newNatives, nativesIndex); - natives = newNatives; #endif JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, nativesIndex); #if ENABLE_MARSHAL_METHODS From 09678bd7f12f62f5c3a9dd1847f97c3420e94f48 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 24 Aug 2022 20:26:47 +0200 Subject: [PATCH 32/79] Don't register dynamically methods for which we generate JCWs Fix Unicode character encoding when generating native symbol names Add missing JI Java types to `java_runtime.jar` --- .../scripts/JavaCallableWrappers.targets | 1 - .../Android.Runtime/AndroidRuntime.cs | 4 +--- .../MarshalMethodsNativeAssemblyGenerator.cs | 19 ++++++++----------- src/java-runtime/java-runtime.csproj | 1 + 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/build-tools/scripts/JavaCallableWrappers.targets b/build-tools/scripts/JavaCallableWrappers.targets index 21d9bcd231c..bd0e0bbf117 100644 --- a/build-tools/scripts/JavaCallableWrappers.targets +++ b/build-tools/scripts/JavaCallableWrappers.targets @@ -23,7 +23,6 @@ /> <_JavaSources Include="$(IntermediateOutputPath)jcw\src\**\*.java" /> - <_JavaSources Include="$(JavaInteropSourceDirectory)\src\Java.Interop\java\com\xamarin\**\*.java" /> dynamicRegistrationMethods = new Dictionary (StringComparer.Ordinal) { - {"Android.Views.View+IOnLayoutChangeListenerImplementor", new string[] { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, - {"Android.Views.View+IOnLayoutChangeListenerInvoker", new string[] { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, {"Java.Interop.TypeManager+JavaTypeManager", new string[] { "GetActivateHandler" }}, }; public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan methods) { if (methods.IsEmpty) { - Logger.Log (LogLevel.Info, "monodroid-mm", "No methods to register, returning"); + Logger.Log (LogLevel.Debug, "monodroid-mm", "No methods to register, returning"); return; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 3ba3f77f5a5..c2d0351149a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -366,14 +366,15 @@ string MangleForJni (string name) foreach (char ch in name) { switch (ch) { - case '_': - sb.Append ("_1"); - break; - case '/': + case '.': sb.Append ('_'); break; + case '_': + sb.Append ("_1"); + break; + case ';': sb.Append ("_2"); break; @@ -382,16 +383,12 @@ string MangleForJni (string name) sb.Append ("_3"); break; - case '$': - sb.Append ("_00024"); - break; - default: - if ((int)ch > 127) { + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) { + sb.Append (ch); + } else { sb.Append ("_0"); sb.Append (((int)ch).ToString ("x04")); - } else { - sb.Append (ch); } break; } diff --git a/src/java-runtime/java-runtime.csproj b/src/java-runtime/java-runtime.csproj index 175397c9410..60a41f213a2 100644 --- a/src/java-runtime/java-runtime.csproj +++ b/src/java-runtime/java-runtime.csproj @@ -19,6 +19,7 @@ + From d60fc08765a56f3ccf911d5e91783163b0c581e9 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 25 Aug 2022 23:22:41 +0200 Subject: [PATCH 33/79] [WIP] Some cleanup + special cases BROKEN! Will crash with NREX when building an app! --- src/Mono.Android/Java.Interop/TypeManager.cs | 7 ++ src/Mono.Android/Mono.Android.targets | 3 + .../java/mono/android/TypeManager.java | 2 + .../Tasks/CreateTypeManagerJava.cs | 79 +++++++++++++++++++ .../Tasks/GenerateJavaStubs.cs | 2 + .../MarshalMethodsAssemblyRewriter.cs | 16 +++- .../Utilities/MarshalMethodsClassifier.cs | 73 ++++++++++++++--- .../Xamarin.Android.Build.Tasks.csproj | 3 + .../Xamarin.Android.Common.targets | 7 ++ src/monodroid/jni/embedded-assemblies.cc | 1 - src/monodroid/jni/mono-image-loader.hh | 8 +- .../jni/xamarin-android-app-context.cc | 3 +- 12 files changed, 182 insertions(+), 22 deletions(-) rename src/{java-runtime => Mono.Android}/java/mono/android/TypeManager.java (76%) create mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/CreateTypeManagerJava.cs diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 0c32fb53b84..34f3c7911ac 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -418,6 +418,13 @@ static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPt TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); } +#if NETCOREAPP + [UnmanagedCallersOnly] + static void n_Activate_mm (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) + { + TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); + } +#endif internal static Delegate GetActivateHandler () { return TypeManager.GetActivateHandler (); diff --git a/src/Mono.Android/Mono.Android.targets b/src/Mono.Android/Mono.Android.targets index af8031d31bd..3c0068d7580 100644 --- a/src/Mono.Android/Mono.Android.targets +++ b/src/Mono.Android/Mono.Android.targets @@ -199,6 +199,9 @@ 1.8 1.6 + + + "CTMJ"; + + [Required] + public string ResourceName { get; set; } + + [Required] + public string OutputFilePath { get; set; } + + static readonly Assembly ExecutingAssembly = Assembly.GetExecutingAssembly (); + + public override bool RunTask () + { + string? content = ReadResource (ResourceName); + + if (String.IsNullOrEmpty (content)) { + return false; + } + + var result = new StringBuilder (); + bool ignoring = false; + foreach (string line in content.Split ('\n')) { + if (!ignoring) { + if (ignoring = line.StartsWith ("//#MARSHAL_METHODS:START", StringComparison.Ordinal)) { + continue; + } + result.AppendLine (line); + } else if (line.StartsWith ("//#MARSHAL_METHODS:END", StringComparison.Ordinal)) { + ignoring = false; + } + } + + if (result.Length == 0) { + Log.LogDebugMessage ("TypeManager.java not generated, empty resource data"); + return false; + } + + using (var ms = new MemoryStream ()) { + using (var sw = new StreamWriter (ms)) { + sw.Write (result.ToString ()); + sw.Flush (); + + if (Files.CopyIfStreamChanged (ms, OutputFilePath)) { + Log.LogDebugMessage ($"Wrote resource {OutputFilePath}."); + } else { + Log.LogDebugMessage ($"Resource {OutputFilePath} is unchanged. Skipping."); + } + } + } + + return !Log.HasLoggedErrors; + } + + string? ReadResource (string resourceName) + { + using (var from = ExecutingAssembly.GetManifestResourceStream (resourceName)) { + if (from == null) { + Log.LogCodedError ("XA0116", Properties.Resources.XA0116, resourceName); + return null; + } + + using (var sr = new StreamReader (from)) { + return sr.ReadToEnd (); + } + } + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 23346fedb0b..ce54840ff61 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -366,6 +366,8 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ())); if (useMarshalMethods) { + classifier.AddSpecialCaseMethods (); + Log.LogDebugMessage ($"Number of generated marshal methods: {classifier.MarshalMethods.Count}"); if (classifier.RejectedMethodCount > 0) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 4a0743ac60c..9e2d2b8b682 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -53,13 +53,21 @@ public void Rewrite (DirectoryAssemblyResolver resolver) } if (method.Connector != null) { - log.LogDebugMessage ($"Removing connector method {method.Connector.FullName}"); - method.Connector.DeclaringType?.Methods?.Remove (method.Connector); + if (method.Connector.IsStatic && method.Connector.IsPrivate) { + log.LogDebugMessage ($"Removing connector method {method.Connector.FullName}"); + method.Connector.DeclaringType?.Methods?.Remove (method.Connector); + } else { + log.LogWarning ($"NOT removing connector method {method.Connector.FullName} because it's either not static or not private"); + } } if (method.CallbackField != null) { - log.LogDebugMessage ($"Removing callback delegate backing field {method.CallbackField.FullName}"); - method.CallbackField.DeclaringType?.Fields?.Remove (method.CallbackField); + if (method.CallbackField.IsStatic && method.CallbackField.IsPrivate) { + log.LogDebugMessage ($"Removing callback delegate backing field {method.CallbackField.FullName}"); + method.CallbackField.DeclaringType?.Fields?.Remove (method.CallbackField); + } else { + log.LogWarning ($"NOT removing callback field {method.CallbackField.FullName} because it's either not static or not private"); + } } processedMethods.Add (fullNativeCallbackName, method.NativeCallback); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 1e2d07d4cf9..4af61793cad 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -25,16 +25,17 @@ sealed class MarshalMethodEntry /// public MethodDefinition? NativeCallbackWrapper { get; set; } public TypeDefinition DeclaringType { get; } - public MethodDefinition Connector { get; } - public MethodDefinition RegisteredMethod { get; } - public MethodDefinition ImplementedMethod { get; } - public FieldDefinition CallbackField { get; } + public MethodDefinition? Connector { get; } + public MethodDefinition? RegisteredMethod { get; } + public MethodDefinition? ImplementedMethod { get; } + public FieldDefinition? CallbackField { get; } public string JniTypeName { get; } public string JniMethodName { get; } public string JniMethodSignature { get; } public bool NeedsBlittableWorkaround { get; } public MethodDefinition NativeCallback => NativeCallbackWrapper ?? nativeCallbackReal; + public bool IsSpecial { get; } public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition nativeCallback, MethodDefinition connector, MethodDefinition registeredMethod, MethodDefinition implementedMethod, FieldDefinition callbackField, string jniTypeName, @@ -50,6 +51,14 @@ public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition native JniMethodName = EnsureNonEmpty (jniName, nameof (jniName)); JniMethodSignature = EnsureNonEmpty (jniSignature, nameof (jniSignature)); NeedsBlittableWorkaround = needsBlittableWorkaround; + IsSpecial = false; + } + + public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition nativeCallback) + { + DeclaringType = declaringType ?? throw new ArgumentNullException (nameof (declaringType)); + nativeCallbackReal = nativeCallback ?? throw new ArgumentNullException (nameof (nativeCallback)); + IsSpecial = true; } string EnsureNonEmpty (string s, string argName) @@ -249,6 +258,55 @@ public bool FoundDynamicallyRegisteredMethods (TypeDefinition type) return typesWithDynamicallyRegisteredMethods.Contains (type); } + /// + /// Adds MarshalMethodEntry for each method that won't be returned by the JavaInterop type scanner, mostly + /// used for hand-written methods (e.g. Java.Interop.TypeManager+JavaTypeManager::n_Activate) + /// + public void AddSpecialCaseMethods () + { + AssemblyDefinition monoAndroid = resolver.Resolve ("Mono.Android"); + TypeDefinition? typeManager = monoAndroid?.MainModule.FindType ("Java.Interop.TypeManager"); + TypeDefinition? javaTypeManager = typeManager?.GetNestedType ("JavaTypeManager"); + + if (javaTypeManager == null) { + throw new InvalidOperationException ("Internal error: unable to find the Java.Interop.TypeManager+JavaTypeManager type in the Mono.Android assembly"); + } + + MethodDefinition? nActivate = null; + foreach (MethodDefinition method in javaTypeManager.Methods) { + Console.WriteLine ($" considering method: {method.Name}"); + if (String.Compare ("n_Activate_mm", method.Name, StringComparison.Ordinal) != 0) { + continue; + } + + if (!method.IsStatic) { + log.LogWarning ($"Method '{method.FullName}' is not static"); + continue; + } + + if (!method.IsPrivate) { + log.LogWarning ($"Method '{method.FullName}' is not private"); + continue; + } + + if (!method.GetCustomAttributes ("System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute").Any (cattr => cattr != null)) { + log.LogWarning ($"Method '{method.FullName}' isn't decorated with the UnmanagedCallersOnly attribute"); + continue; + } + + nActivate = method; + break; + } + + if (nActivate == null) { + throw new InvalidOperationException ("Internal error: unable to find the 'n_Activate_mm' method in the 'Java.Interop.TypeManager+JavaTypeManager, Mono.Android' type"); + } + + // TODO: obtain and pass to the constructor the JNI type name, method name and signature + var entry = new MarshalMethodEntry (javaTypeManager, nActivate); + marshalMethods.Add (".:!SpEcIaL:Java.Interop.TypeManager+JavaTypeManager::n_Activate_mm", new List { entry }); + } + bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute registerAttribute) { if (registerAttribute.ConstructorArguments.Count != 3) { @@ -267,12 +325,8 @@ bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registere return true; } - // TODO: Probably should check if all the methods and fields are private and static - only then it is safe(ish) to remove them bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodDefinition registeredMethod, MethodDefinition implementedMethod, string jniName, string jniSignature) { - Console.WriteLine ($" topType: {topType.FullName}"); - Console.WriteLine ($" registered method type: {registeredMethod.DeclaringType.FullName}"); - Console.WriteLine ($" implemented method type: {implementedMethod.DeclaringType.FullName}"); const string HandlerNameStart = "Get"; const string HandlerNameEnd = "Handler"; @@ -356,7 +410,6 @@ bool IsStandardHandler (TypeDefinition topType, ConnectorInfo connector, MethodD // method.CallbackField?.DeclaringType.Fields == 'null' StoreMethod ( - connectorName, registeredMethod, new MarshalMethodEntry ( topType, @@ -540,7 +593,7 @@ FieldDefinition FindField (TypeDefinition type, string fieldName, bool lookForIn return FindField (tdCache.Resolve (type.BaseType), fieldName, lookForInherited); } - void StoreMethod (string connectorName, MethodDefinition registeredMethod, MarshalMethodEntry entry) + void StoreMethod (MethodDefinition registeredMethod, MarshalMethodEntry entry) { string typeName = registeredMethod.DeclaringType.FullName.Replace ('/', '+'); string key = $"{typeName}, {registeredMethod.DeclaringType.GetPartialAssemblyName (tdCache)}\t{registeredMethod.Name}"; diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index d28e3e45db0..ae4a7e2eb2e 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -404,6 +404,9 @@ ApplicationRegistration.java + + JavaInteropTypeManager.java + diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 29ad12888f4..8ac34e83a46 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -41,6 +41,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. + @@ -1362,6 +1363,11 @@ because xbuild doesn't support framework reference assemblies. ResourceName="MonoRuntimeProvider.Bundled.20.java" OutputPath="$(_AndroidIntermediateJavaSourceDirectory)mono\MonoRuntimeProvider.java" /> + + diff --git a/src/monodroid/jni/embedded-assemblies.cc b/src/monodroid/jni/embedded-assemblies.cc index 0c4e0bf6986..de453cd2990 100644 --- a/src/monodroid/jni/embedded-assemblies.cc +++ b/src/monodroid/jni/embedded-assemblies.cc @@ -359,7 +359,6 @@ EmbeddedAssemblies::assembly_store_open_from_bundles (dynamic_local_string const& name, MonoAssemblyLoadContextGCHandle alc_gchandle, hash_t name_hash, uint8_t *assembly_data, uint32_t assembly_data_size) noexcept { - log_info (LOG_DEFAULT, "Loading assembly %s; hash 0x%zx", name.get (), name_hash); + log_debug (LOG_ASSEMBLY, "Mono image loader: loading assembly %s; hash 0x%zx", name.get (), name_hash); MonoImageOpenStatus status; MonoImage *image = mono_image_open_from_data_alc ( alc_gchandle, @@ -115,19 +115,17 @@ namespace xamarin::android::internal { force_inline static MonoImage* stash_and_return (MonoImage *image, MonoImageOpenStatus status, [[maybe_unused]] hash_t hash) noexcept { if (image == nullptr || status != MonoImageOpenStatus::MONO_IMAGE_OK) { - log_warn (LOG_ASSEMBLY, "Failed to open assembly image for '%s'. %s", mono_image_strerror (status)); + log_warn (LOG_ASSEMBLY, "Failed to open assembly image. %s", mono_image_strerror (status)); return nullptr; } #if defined (USE_CACHE) ssize_t index = find_index (hash); - log_info (LOG_DEFAULT, "Index matching the hash == %zd", index); if (index < 0) { - // TODO: Warn? + log_warn (LOG_ASSEMBLY, "Failed to look up image index for hash 0x%zx", hash); return image; } - log_info (LOG_DEFAULT, "Stashing"); // We don't need to worry about locking here. Even if we're overwriting an entry just set by another // thread, the image pointer is going to be the same (at least currently, it will change when we have // support for unloadable Assembly Load Contexts) and the actual write operation to the destination is diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index 58eaa69ab82..e5bdb073ae8 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -38,7 +38,6 @@ template force_inline void MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept { - log_warn (LOG_DEFAULT, __PRETTY_FUNCTION__); log_debug ( LOG_ASSEMBLY, "MM: Trying to look up pointer to method '%s' (token 0x%x) in class '%s' (index %u)", @@ -76,7 +75,7 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas target_ptr = ret; } - log_debug (LOG_ASSEMBLY, "Loaded pointer to method %s", mono_method_get_name (method)); + log_debug (LOG_ASSEMBLY, "Loaded pointer to method %s (%p)", mono_method_get_name (method), ret); return; } From aea0b0f1d4db2c1186cc86e913ce485aefef61af Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 26 Aug 2022 14:13:18 +0200 Subject: [PATCH 34/79] TypeManager special case now works --- .../Utilities/MarshalMethodsClassifier.cs | 130 ++++++++++++++---- .../MarshalMethodsNativeAssemblyGenerator.cs | 3 +- 2 files changed, 109 insertions(+), 24 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 4af61793cad..836267938b6 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -5,6 +5,8 @@ using Java.Interop.Tools.Cecil; using Java.Interop.Tools.JavaCallableWrappers; using Java.Interop.Tools.TypeNameMappings; + +using Microsoft.Android.Build.Tasks; using Microsoft.Build.Utilities; using Mono.Cecil; @@ -54,10 +56,13 @@ public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition native IsSpecial = false; } - public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition nativeCallback) + public MarshalMethodEntry (TypeDefinition declaringType, MethodDefinition nativeCallback, string jniTypeName, string jniName, string jniSignature) { DeclaringType = declaringType ?? throw new ArgumentNullException (nameof (declaringType)); nativeCallbackReal = nativeCallback ?? throw new ArgumentNullException (nameof (nativeCallback)); + JniTypeName = EnsureNonEmpty (jniTypeName, nameof (jniTypeName)); + JniMethodName = EnsureNonEmpty (jniName, nameof (jniName)); + JniMethodSignature = EnsureNonEmpty (jniSignature, nameof (jniSignature)); IsSpecial = true; } @@ -258,53 +263,132 @@ public bool FoundDynamicallyRegisteredMethods (TypeDefinition type) return typesWithDynamicallyRegisteredMethods.Contains (type); } - /// - /// Adds MarshalMethodEntry for each method that won't be returned by the JavaInterop type scanner, mostly - /// used for hand-written methods (e.g. Java.Interop.TypeManager+JavaTypeManager::n_Activate) - /// - public void AddSpecialCaseMethods () + void AddTypeManagerSpecialCaseMethods () { + const string FullTypeName = "Java.Interop.TypeManager+JavaTypeManager, Mono.Android"; + AssemblyDefinition monoAndroid = resolver.Resolve ("Mono.Android"); TypeDefinition? typeManager = monoAndroid?.MainModule.FindType ("Java.Interop.TypeManager"); TypeDefinition? javaTypeManager = typeManager?.GetNestedType ("JavaTypeManager"); if (javaTypeManager == null) { - throw new InvalidOperationException ("Internal error: unable to find the Java.Interop.TypeManager+JavaTypeManager type in the Mono.Android assembly"); + throw new InvalidOperationException ($"Internal error: unable to find the {FullTypeName} type in the Mono.Android assembly"); } + MethodDefinition? nActivate_mm = null; MethodDefinition? nActivate = null; + foreach (MethodDefinition method in javaTypeManager.Methods) { - Console.WriteLine ($" considering method: {method.Name}"); - if (String.Compare ("n_Activate_mm", method.Name, StringComparison.Ordinal) != 0) { - continue; + if (nActivate_mm == null && IsMatchingMethod (method, "n_Activate_mm")) { + if (method.GetCustomAttributes ("System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute").Any (cattr => cattr != null)) { + nActivate_mm = method; + } else { + log.LogWarning ($"Method '{method.FullName}' isn't decorated with the UnmanagedCallersOnly attribute"); + continue; + } } - if (!method.IsStatic) { - log.LogWarning ($"Method '{method.FullName}' is not static"); - continue; + if (nActivate == null && IsMatchingMethod (method, "n_Activate")) { + nActivate = method; } - if (!method.IsPrivate) { - log.LogWarning ($"Method '{method.FullName}' is not private"); + if (nActivate_mm != null && nActivate != null) { + break; + } + } + + if (nActivate_mm == null) { + ThrowMissingMethod ("nActivate_mm"); + } + + if (nActivate == null) { + ThrowMissingMethod ("nActivate"); + } + + string? jniTypeName = null; + foreach (CustomAttribute cattr in javaTypeManager.GetCustomAttributes ("Android.Runtime.RegisterAttribute")) { + if (cattr.ConstructorArguments.Count != 1) { + log.LogDebugMessage ($"[Register] attribute on type '{FullTypeName}' is expected to have 1 constructor argument, found {cattr.ConstructorArguments.Count}"); continue; } - if (!method.GetCustomAttributes ("System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute").Any (cattr => cattr != null)) { - log.LogWarning ($"Method '{method.FullName}' isn't decorated with the UnmanagedCallersOnly attribute"); + jniTypeName = (string)cattr.ConstructorArguments[0].Value; + if (!String.IsNullOrEmpty (jniTypeName)) { + break; + } + } + + string? jniMethodName = null; + string? jniSignature = null; + foreach (CustomAttribute cattr in nActivate.GetCustomAttributes ("Android.Runtime.RegisterAttribute")) { + if (cattr.ConstructorArguments.Count != 3) { + log.LogDebugMessage ($"[Register] attribute on method '{nActivate.FullName}' is expected to have 3 constructor arguments, found {cattr.ConstructorArguments.Count}"); continue; } - nActivate = method; - break; + jniMethodName = (string)cattr.ConstructorArguments[0].Value; + jniSignature = (string)cattr.ConstructorArguments[1].Value; + + if (!String.IsNullOrEmpty (jniMethodName) && !String.IsNullOrEmpty (jniSignature)) { + break; + } } - if (nActivate == null) { - throw new InvalidOperationException ("Internal error: unable to find the 'n_Activate_mm' method in the 'Java.Interop.TypeManager+JavaTypeManager, Mono.Android' type"); + bool missingInfo = false; + if (String.IsNullOrEmpty (jniTypeName)) { + missingInfo = true; + log.LogDebugMessage ($"Failed to obtain Java type name from the [Register] attribute on type '{FullTypeName}'"); } - // TODO: obtain and pass to the constructor the JNI type name, method name and signature - var entry = new MarshalMethodEntry (javaTypeManager, nActivate); + if (String.IsNullOrEmpty (jniMethodName)) { + missingInfo = true; + log.LogDebugMessage ($"Failed to obtain Java method name from the [Register] attribute on method '{nActivate.FullName}'"); + } + + if (String.IsNullOrEmpty (jniSignature)) { + missingInfo = true; + log.LogDebugMessage ($"Failed to obtain Java method signature from the [Register] attribute on method '{nActivate.FullName}'"); + } + + if (missingInfo) { + throw new InvalidOperationException ($"Missing information while constructing marshal method for the '{nActivate_mm.FullName}' method"); + } + + var entry = new MarshalMethodEntry (javaTypeManager, nActivate_mm, jniTypeName, jniMethodName, jniSignature); marshalMethods.Add (".:!SpEcIaL:Java.Interop.TypeManager+JavaTypeManager::n_Activate_mm", new List { entry }); + + void ThrowMissingMethod (string name) + { + throw new InvalidOperationException ($"Internal error: unable to find the '{name}' method in the '{FullTypeName}' type"); + } + + bool IsMatchingMethod (MethodDefinition method, string name) + { + if (String.Compare (name, method.Name, StringComparison.Ordinal) != 0) { + return false; + } + + if (!method.IsStatic) { + log.LogWarning ($"Method '{method.FullName}' is not static"); + return false; + } + + if (!method.IsPrivate) { + log.LogWarning ($"Method '{method.FullName}' is not private"); + return false; + } + + return true; + } + } + + /// + /// Adds MarshalMethodEntry for each method that won't be returned by the JavaInterop type scanner, mostly + /// used for hand-written methods (e.g. Java.Interop.TypeManager+JavaTypeManager::n_Activate) + /// + public void AddSpecialCaseMethods () + { + AddTypeManagerSpecialCaseMethods (); } bool IsDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute registerAttribute) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index c2d0351149a..bf1084b2078 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -344,7 +344,8 @@ void ProcessAndAddMethod (List allMethods, MarshalMethodEntry classes.Add (new StructureInstance (mc)); } - (Type returnType, List? parameters) = ParseJniSignature (entry.JniMethodSignature, entry.ImplementedMethod); + // Methods with `IsSpecial == true` are "synthetic" methods - they contain only the callback reference + (Type returnType, List? parameters) = ParseJniSignature (entry.JniMethodSignature, entry.IsSpecial ? entry.NativeCallback : entry.ImplementedMethod); var method = new MarshalMethodInfo (entry, returnType, nativeSymbolName: nativeSymbolName, classIndex); if (parameters != null && parameters.Count > 0) { From f17ad9f0bb7782bce1afabfefac5c248aa11f18b Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 5 Sep 2022 21:52:41 +0200 Subject: [PATCH 35/79] Fix Debug mode and Release w/o marshal methods --- .../Android.Runtime/AndroidRuntime.cs | 31 ++++---- src/Mono.Android/Android.Runtime/JNIEnv.cs | 5 ++ .../Tasks/GeneratePackageManagerJava.cs | 27 +++++-- .../Utilities/ApplicationConfig.cs | 1 + ...pplicationConfigNativeAssemblyGenerator.cs | 2 + .../MarshalMethodsNativeAssemblyGenerator.cs | 77 ++++++++++++------- .../Xamarin.Android.Common.targets | 2 +- src/monodroid/jni/monodroid-glue-internal.hh | 1 + src/monodroid/jni/monodroid-glue.cc | 1 + src/monodroid/jni/xamarin-app.hh | 1 + 10 files changed, 99 insertions(+), 49 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index d538ffb3264..5c91e754968 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -546,23 +546,28 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< // TODO: this is temporary hack, it needs a full fledged registration mechanism for methods like these (that is, ones which // aren't registered with [Register] but are baked into Mono.Android's managed and Java code) - bool createCallback = false; - string declaringTypeName = callbackDeclaringType.FullName; - string callbackName = callbackString.ToString (); - - foreach (var kvp in dynamicRegistrationMethods) { - string dynamicTypeName = kvp.Key; + bool createCallback; + if (JNIEnv.MarshalMethodsEnabled) { + string declaringTypeName = callbackDeclaringType.FullName; + string callbackName = callbackString.ToString (); + + createCallback = false; + foreach (var kvp in dynamicRegistrationMethods) { + string dynamicTypeName = kvp.Key; + + foreach (string dynamicCallbackMethodName in kvp.Value) { + if (ShouldRegisterDynamically (declaringTypeName, callbackName, dynamicTypeName, dynamicCallbackMethodName)) { + createCallback = true; + break; + } + } - foreach (string dynamicCallbackMethodName in kvp.Value) { - if (ShouldRegisterDynamically (declaringTypeName, callbackName, dynamicTypeName, dynamicCallbackMethodName)) { - createCallback = true; + if (createCallback) { break; } } - - if (createCallback) { - break; - } + } else { + createCallback = true; } if (createCallback) { diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index d9ef52d1255..2f031f459aa 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -34,6 +34,7 @@ struct JnienvInitializeArgs { public byte ioExceptionType; public int jniAddNativeMethodRegistrationAttributePresent; public bool jniRemappingInUse; + public bool marshalMethodsEnabled; } #pragma warning restore 0649 @@ -57,6 +58,7 @@ public static partial class JNIEnv { internal static bool IsRunningOnDesktop; internal static bool LogAssemblyCategory; + internal static bool MarshalMethodsEnabled; static AndroidRuntime? androidRuntime; static BoundExceptionType BoundExceptionType; @@ -156,6 +158,9 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) gref_gc_threshold = args->grefGcThreshold; jniRemappingInUse = args->jniRemappingInUse; +#if NETCOREAPP + MarshalMethodsEnabled = args->marshalMethodsEnabled; +#endif java_vm = args->javaVm; version = args->version; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 349eac81d03..cd9361051fc 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -68,6 +68,9 @@ public class GeneratePackageManagerJava : AndroidTask [Required] public bool InstantRunEnabled { get; set; } + [Required] + public bool EnableMarshalMethods { get; set; } + public string RuntimeConfigBinFilePath { get; set; } public string BoundExceptionType { get; set; } @@ -298,11 +301,12 @@ void AddEnvironment () }; int assemblyCount = 0; + bool enableMarshalMethods = EnableMarshalMethods; HashSet archAssemblyNames = null; - var uniqueAssemblyNames = new HashSet (StringComparer.OrdinalIgnoreCase); + HashSet uniqueAssemblyNames = enableMarshalMethods ? new HashSet (StringComparer.OrdinalIgnoreCase) : null; Action updateAssemblyCount = (ITaskItem assembly) => { string assemblyName = Path.GetFileName (assembly.ItemSpec); - if (!uniqueAssemblyNames.Contains (assemblyName)) { + if (enableMarshalMethods && !uniqueAssemblyNames.Contains (assemblyName)) { uniqueAssemblyNames.Add (assemblyName); } @@ -436,16 +440,23 @@ void AddEnvironment () JNIEnvRegisterJniNativesToken = jnienv_registerjninatives_method_token, JniRemappingReplacementTypeCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementTypeCount, JniRemappingReplacementMethodIndexEntryCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementMethodIndexEntryCount, + MarshalMethodsEnabled = EnableMarshalMethods, }; appConfigAsmGen.Init (); var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (GenerateJavaStubs.MarshalMethodsRegisterTaskKey, RegisteredTaskObjectLifetime.Build); - var marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator ( - assemblyCount, - uniqueAssemblyNames, - marshalMethodsState?.MarshalMethods, - Log - ); + MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGen; + + if (enableMarshalMethods) { + marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator ( + assemblyCount, + uniqueAssemblyNames, + marshalMethodsState?.MarshalMethods, + Log + ); + } else { + marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (); + } marshalMethodsAsmGen.Init (); foreach (string abi in SupportedAbis) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs index 369bc794b50..e5a1405b127 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs @@ -34,6 +34,7 @@ sealed class ApplicationConfig public bool jni_add_native_method_registration_attribute_present; public bool have_runtime_config_blob; public bool have_assemblies_blob; + public bool marshal_methods_enabled; public byte bound_stream_io_exception_type; public uint package_naming_policy; public uint environment_variable_count; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index 979d705c162..16bc2daa34a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -173,6 +173,7 @@ sealed class XamarinAndroidBundledAssembly public MonoComponent MonoComponents { get; set; } public PackageNamingPolicy PackageNamingPolicy { get; set; } public List NativeLibraries { get; set; } + public bool MarshalMethodsEnabled { get; set; } public ApplicationConfigNativeAssemblyGenerator (IDictionary environmentVariables, IDictionary systemProperties, TaskLoggingHelper log) { @@ -201,6 +202,7 @@ public override void Init () jni_add_native_method_registration_attribute_present = JniAddNativeMethodRegistrationAttributePresent, have_runtime_config_blob = HaveRuntimeConfigBlob, have_assemblies_blob = HaveAssemblyStore, + marshal_methods_enabled = MarshalMethodsEnabled, bound_stream_io_exception_type = (byte)BoundExceptionType, package_naming_policy = (uint)PackageNamingPolicy, environment_variable_count = (uint)(environmentVariables == null ? 0 : environmentVariables.Count * 2), diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index bf1084b2078..20c2c599d50 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -199,6 +199,19 @@ sealed class MarshalMethodName List methods; List> classes = new List> (); + readonly bool generateEmptyCode; + + /// + /// Constructor to be used ONLY when marshal methods are DISABLED + /// + public MarshalMethodsNativeAssemblyGenerator () + { + generateEmptyCode = true; + } + + /// + /// Constructor to be used ONLY when marshal methods are ENABLED + /// public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, IDictionary> marshalMethods, TaskLoggingHelper logger) { this.numberOfAssembliesInApk = numberOfAssembliesInApk; @@ -209,11 +222,13 @@ public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, IColl if (uniqueAssemblyNames.Count != numberOfAssembliesInApk) { throw new InvalidOperationException ("Internal error: number of assemblies in the apk doesn't match the number of unique assembly names"); } + + generateEmptyCode = false; } public override void Init () { - if (marshalMethods == null || marshalMethods.Count == 0) { + if (generateEmptyCode || marshalMethods == null || marshalMethods.Count == 0) { return; } @@ -568,17 +583,19 @@ protected override void Write (LlvmIrGenerator generator) generator.WriteArray (mm_class_names, "mm_class_names", "Names of classes in which marshal methods reside"); var uniqueMethods = new Dictionary (); - foreach (MarshalMethodInfo mmi in methods) { - string asmName = Path.GetFileName (mmi.Method.NativeCallback.Module.Assembly.MainModule.FileName); - if (!asmNameToIndex.TryGetValue (asmName, out uint idx)) { - throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to cache array index"); - } + if (!generateEmptyCode && methods != null) { + foreach (MarshalMethodInfo mmi in methods) { + string asmName = Path.GetFileName (mmi.Method.NativeCallback.Module.Assembly.MainModule.FileName); + if (!asmNameToIndex.TryGetValue (asmName, out uint idx)) { + throw new InvalidOperationException ($"Internal error: failed to match assembly name '{asmName}' to cache array index"); + } - ulong id = ((ulong)idx << 32) | (ulong)mmi.Method.NativeCallback.MetadataToken.ToUInt32 (); - if (uniqueMethods.ContainsKey (id)) { - continue; + ulong id = ((ulong)idx << 32) | (ulong)mmi.Method.NativeCallback.MetadataToken.ToUInt32 (); + if (uniqueMethods.ContainsKey (id)) { + continue; + } + uniqueMethods.Add (id, mmi); } - uniqueMethods.Add (id, mmi); } MarshalMethodName name; @@ -631,7 +648,7 @@ void RenderMethodNameWithParams (CecilMethodDefinition md, StringBuilder buffer) void WriteNativeMethods (LlvmIrGenerator generator, Dictionary asmNameToIndex, LlvmIrVariableReference get_function_pointer_ref) { - if (methods == null || methods.Count == 0) { + if (generateEmptyCode || methods == null || methods.Count == 0) { return; } @@ -793,22 +810,28 @@ void WriteHashes () where T: struct { var hashes = new Dictionary (); uint index = 0; - foreach (string name in uniqueAssemblyNames) { - string clippedName = Path.GetFileNameWithoutExtension (name); - ulong hashFull = HashName (name, is64Bit); - ulong hashClipped = HashName (clippedName, is64Bit); - - // - // If the number of name forms changes, xamarin-app.hh MUST be updated to set value of the - // `number_of_assembly_name_forms_in_image_cache` constant to the number of forms. - // - hashes.Add ((T)Convert.ChangeType (hashFull, typeof(T)), (name, index)); - hashes.Add ((T)Convert.ChangeType (hashClipped, typeof(T)), (clippedName, index)); - - index++; - } - List keys = hashes.Keys.ToList (); - keys.Sort (); + List keys; + + if (!generateEmptyCode) { + foreach (string name in uniqueAssemblyNames) { + string clippedName = Path.GetFileNameWithoutExtension (name); + ulong hashFull = HashName (name, is64Bit); + ulong hashClipped = HashName (clippedName, is64Bit); + + // + // If the number of name forms changes, xamarin-app.hh MUST be updated to set value of the + // `number_of_assembly_name_forms_in_image_cache` constant to the number of forms. + // + hashes.Add ((T)Convert.ChangeType (hashFull, typeof(T)), (name, index)); + hashes.Add ((T)Convert.ChangeType (hashClipped, typeof(T)), (clippedName, index)); + + index++; + } + keys = hashes.Keys.ToList (); + keys.Sort (); + } else { + keys = new List (); + } generator.WriteCommentLine ("Each entry maps hash of an assembly name to an index into the `assembly_image_cache` array"); generator.WriteArray ( diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 8ac34e83a46..d4b8fc27947 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1596,7 +1596,6 @@ because xbuild doesn't support framework reference assemblies. diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index c90a69c57fe..598f51372be 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -132,6 +132,7 @@ namespace xamarin::android::internal uint8_t boundExceptionType; int jniAddNativeMethodRegistrationAttributePresent; bool jniRemappingInUse; + bool marshalMethodsEnabled; }; #if defined (NET) diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index ae269578631..3464f0725a3 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1090,6 +1090,7 @@ MonodroidRuntime::init_android_runtime ( init.boundExceptionType = application_config.bound_exception_type; 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; // GC threshold is 90% of the max GREF count init.grefGcThreshold = static_cast(androidSystem.get_gref_gc_threshold ()); diff --git a/src/monodroid/jni/xamarin-app.hh b/src/monodroid/jni/xamarin-app.hh index 23ef37cba33..7cd8556a933 100644 --- a/src/monodroid/jni/xamarin-app.hh +++ b/src/monodroid/jni/xamarin-app.hh @@ -210,6 +210,7 @@ struct ApplicationConfig bool jni_add_native_method_registration_attribute_present; bool have_runtime_config_blob; bool have_assembly_store; + bool marshal_methods_enabled; uint8_t bound_exception_type; uint32_t package_naming_policy; uint32_t environment_variable_count; From 7a00e0e80078670022f09d39afdf104180d367e3 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 6 Sep 2022 19:56:13 +0200 Subject: [PATCH 36/79] Make AOT work (with trimming) The test app still fails to work if trimming is disabled with AOT enabled. --- .../targets/Microsoft.Android.Sdk.Aot.targets | 13 +++++++ ...oft.Android.Sdk.AssemblyResolution.targets | 7 +++- .../Utilities/EnvironmentHelper.cs | 38 +++++++++++-------- .../MarshalMethodsAssemblyRewriter.cs | 2 + .../Xamarin.Android.Common.targets | 12 ++++++ 5 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets index cac685b8837..6a55fc7b5ac 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets @@ -53,6 +53,19 @@ They run in a context of an inner build with a single $(RuntimeIdentifier). + + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets index 188ddf12e42..3ccabedc09d 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets @@ -38,8 +38,13 @@ _ResolveAssemblies MSBuild target. + + + <_RunAotMaybe Condition=" '$(_AndroidUseMarshalMethods)' != 'True' ">_AndroidAot + + 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 0f6e7fd910d..c3c30ebae39 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 @@ -49,6 +49,7 @@ public sealed class ApplicationConfig public bool jni_add_native_method_registration_attribute_present; public bool have_runtime_config_blob; public bool have_assemblies_blob; + public bool marshal_methods_enabled; public byte bound_stream_io_exception_type; public uint package_naming_policy; public uint environment_variable_count; @@ -66,7 +67,7 @@ public sealed class ApplicationConfig public string android_package_name = String.Empty; } - const uint ApplicationConfigFieldCount = 25; + const uint ApplicationConfigFieldCount = 26; const string ApplicationConfigSymbolName = "application_config"; const string AppEnvironmentVariablesSymbolName = "app_environment_variables"; @@ -255,77 +256,82 @@ static ApplicationConfig ReadApplicationConfig (EnvironmentFile envFile) ret.have_assemblies_blob = ConvertFieldToBool ("have_assemblies_blob", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 10: // bound_stream_io_exception_type: byte / .byte + case 10: // marshal_methods_enabled: bool / .byte + AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); + ret.marshal_methods_enabled = ConvertFieldToBool ("marshal_methods_enabled", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); + break; + + case 11: // bound_stream_io_exception_type: byte / .byte AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber); ret.bound_stream_io_exception_type = ConvertFieldToByte ("bound_stream_io_exception_type", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 11: // package_naming_policy: uint32_t / .word | .long + case 12: // package_naming_policy: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.package_naming_policy = ConvertFieldToUInt32 ("package_naming_policy", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 12: // environment_variable_count: uint32_t / .word | .long + case 13: // environment_variable_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.environment_variable_count = ConvertFieldToUInt32 ("environment_variable_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 13: // system_property_count: uint32_t / .word | .long + case 14: // system_property_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.system_property_count = ConvertFieldToUInt32 ("system_property_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 14: // number_of_assemblies_in_apk: uint32_t / .word | .long + case 15: // number_of_assemblies_in_apk: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_assemblies_in_apk = ConvertFieldToUInt32 ("number_of_assemblies_in_apk", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 15: // bundled_assembly_name_width: uint32_t / .word | .long + case 16: // bundled_assembly_name_width: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.bundled_assembly_name_width = ConvertFieldToUInt32 ("bundled_assembly_name_width", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 16: // number_of_assembly_store_files: uint32_t / .word | .long + case 17: // number_of_assembly_store_files: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_assembly_store_files = ConvertFieldToUInt32 ("number_of_assembly_store_files", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 17: // number_of_dso_cache_entries: uint32_t / .word | .long + case 18: // number_of_dso_cache_entries: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("number_of_dso_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 18: // android_runtime_jnienv_class_token: uint32_t / .word | .long + case 19: // android_runtime_jnienv_class_token: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("android_runtime_jnienv_class_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 19: // jnienv_initialize_method_token: uint32_t / .word | .long + case 20: // jnienv_initialize_method_token: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("jnienv_initialize_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 20: // jnienv_registerjninatives_method_token: uint32_t / .word | .long + case 21: // jnienv_registerjninatives_method_token: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("jnienv_registerjninatives_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 21: // jni_remapping_replacement_type_count: uint32_t / .word | .long + case 22: // jni_remapping_replacement_type_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.jni_remapping_replacement_type_count = ConvertFieldToUInt32 ("jni_remapping_replacement_type_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 22: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long + case 23: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.jni_remapping_replacement_method_index_entry_count = ConvertFieldToUInt32 ("jni_remapping_replacement_method_index_entry_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 23: // mono_components_mask: uint32_t / .word | .long + case 24: // mono_components_mask: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 24: // android_package_name: string / [pointer type] + case 25: // android_package_name: string / [pointer type] Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); pointers.Add (field [1].Trim ()); break; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 9e2d2b8b682..28870cf2846 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -82,6 +82,7 @@ public void Rewrite (DirectoryAssemblyResolver resolver) }; string output = $"{path}.new"; + log.LogDebugMessage ($"Writing new version of assembly: {output}"); asm.Write (output, writerParams); newAssemblyPaths.Add (output); } @@ -91,6 +92,7 @@ public void Rewrite (DirectoryAssemblyResolver resolver) // versions around. foreach (string path in newAssemblyPaths) { string target = Path.Combine (Path.GetDirectoryName (path), Path.GetFileNameWithoutExtension (path)); + log.LogDebugMessage ($"Copying rewritten assembly: {path} -> {target}"); MoveFile (path, target); string source = Path.ChangeExtension (path, ".pdb"); diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index d4b8fc27947..99375fa895c 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1658,9 +1658,21 @@ because xbuild doesn't support framework reference assemblies. /> + + + + + + <_GeneratePackageManagerJavaDependsOn> _GenerateJavaStubs; + _RunAotForAllRIDs; _ManifestMerger; _ConvertCustomView; $(_AfterConvertCustomView); From 5dccadbbb1d4d89e71580f0e6261f66abdb3d1be Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 7 Sep 2022 12:51:15 +0200 Subject: [PATCH 37/79] Time to test things --- .../Xamarin.Android.Build.Tests/BuildTest2.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs index 0f19a5a6926..f561c36c590 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs @@ -24,6 +24,66 @@ namespace Xamarin.Android.Build.Tests [Parallelizable (ParallelScope.Children)] public partial class BuildTest2 : BaseTest { + static object [] MarshalMethodsDefaultStatusSource = new object [] { + new object[] { + /* isClassic */ false, + /* isRelease */ true, + /* marshalMethodsEnabled */ true, + }, + new object[] { + /* isClassic */ false, + /* isRelease */ false, + /* marshalMethodsEnabled */ false, + }, + new object[] { + /* isClassic */ true, + /* isRelease */ false, + /* marshalMethodsEnabled */ false, + }, + new object[] { + /* isClassic */ true, + /* isRelease */ true, + /* marshalMethodsEnabled */ false, + }, + }; + + [Test] + [TestCaseSource (nameof (MarshalMethodsDefaultStatusSource))] + public void MarshalMethodsDefaultEnabledStatus (bool isClassic, bool isRelease, bool marshalMethodsEnabled) + { + if (isClassic) { + if (Builder.UseDotNet) { + Assert.Ignore ("Ignored in .NET6+"); + return; + } + } else if (!Builder.UseDotNet) { + Assert.Ignore ("Ignored in classic"); + return; + } + + var abis = new [] { "armeabi-v7a", "x86" }; + var proj = new XamarinAndroidApplicationProject { + IsRelease = isRelease + }; + + proj.SetAndroidSupportedAbis (abis); + + using (var b = CreateApkBuilder ()) { + Assert.IsTrue (b.Build (proj), "Build should have succeeded."); + Assert.IsTrue ( + StringAssertEx.ContainsText (b.LastBuildOutput, $"_AndroidUseMarshalMethods = {marshalMethodsEnabled}"), + $"The '_AndroidUseMarshalMethods' MSBuild property should have had the value of '{marshalMethodsEnabled}'" + ); + + string objPath = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); + List envFiles = EnvironmentHelper.GatherEnvironmentFiles (objPath, String.Join (";", abis), true); + EnvironmentHelper.ApplicationConfig app_config = EnvironmentHelper.ReadApplicationConfig (envFiles); + + Assert.That (app_config, Is.Not.Null, "application_config must be present in the environment files"); + Assert.AreEqual (app_config.marshal_methods_enabled, marshalMethodsEnabled, $"Marshal methods enabled status should be '{marshalMethodsEnabled}', but it was '{app_config.marshal_methods_enabled}'"); + } + } + [Test] public void CompressedWithoutLinker () { From cd832c77ee792e1de970e5ac13c96d71b9205383 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 7 Sep 2022 17:23:51 +0200 Subject: [PATCH 38/79] Turn a couple of warnings into debug messages for now Right now, the MCW generator doesn't know how to output methods without non-blittable types, so these warnings would be shown for each application. Avoid the noise and turn them back into warnings once the generator is updated. --- src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs | 3 ++- .../Utilities/MarshalMethodsClassifier.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index ce54840ff61..b6460470396 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -375,7 +375,8 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) } if (classifier.WrappedMethodCount > 0) { - Log.LogWarning ($"Number of methods in the project that need marshal method wrappers: {classifier.WrappedMethodCount}"); + // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers + Log.LogDebugMessage ($"Number of methods in the project that need marshal method wrappers: {classifier.WrappedMethodCount}"); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 836267938b6..270bad1c02c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -600,7 +600,8 @@ bool LogReasonWhyAndReturnFailure (string why) void WarnWhy (string why) { - log.LogWarning ($"Method '{method.FullName}' {why}. A workaround is required, this may make the application slower"); + // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers + log.LogDebugMessage ($"Method '{method.FullName}' {why}. A workaround is required, this may make the application slower"); } } From a9995b06c418c79566a6fac17b3ffdc04bc22919 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 7 Sep 2022 17:33:55 +0200 Subject: [PATCH 39/79] Update apkdesc files --- .../BuildReleaseArm64SimpleDotNet.apkdesc | 16 ++++----- .../BuildReleaseArm64XFormsDotNet.apkdesc | 34 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index b5d822f4fd6..bd5221081f9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -5,10 +5,10 @@ "Size": 3032 }, "assemblies/Java.Interop.dll": { - "Size": 59092 + "Size": 59189 }, "assemblies/Mono.Android.dll": { - "Size": 88614 + "Size": 88731 }, "assemblies/rc.bin": { "Size": 1182 @@ -32,7 +32,7 @@ "Size": 3496 }, "assemblies/System.Runtime.InteropServices.dll": { - "Size": 3738 + "Size": 3764 }, "assemblies/System.Threading.dll": { "Size": 5482 @@ -47,16 +47,16 @@ "Size": 62995 }, "assemblies/UnnamedProject.dll": { - "Size": 3560 + "Size": 3559 }, "classes.dex": { - "Size": 360720 + "Size": 18612 }, "lib/arm64-v8a/libmono-component-marshal-ilgen.so": { "Size": 96832 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 425416 + "Size": 432408 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3052824 @@ -71,7 +71,7 @@ "Size": 148696 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 10512 + "Size": 16896 }, "META-INF/BNDLTOOL.RSA": { "Size": 1213 @@ -107,5 +107,5 @@ "Size": 1904 } }, - "PackageSize": 2820744 + "PackageSize": 2755208 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index 0add4dbcdea..848f91fe425 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -5,13 +5,13 @@ "Size": 3568 }, "assemblies/FormsViewGroup.dll": { - "Size": 7114 + "Size": 7131 }, "assemblies/Java.Interop.dll": { - "Size": 66665 + "Size": 66767 }, "assemblies/Mono.Android.dll": { - "Size": 442077 + "Size": 437664 }, "assemblies/mscorlib.dll": { "Size": 3859 @@ -107,7 +107,7 @@ "Size": 3645 }, "assemblies/System.Runtime.InteropServices.dll": { - "Size": 3738 + "Size": 3764 }, "assemblies/System.Runtime.Serialization.dll": { "Size": 1949 @@ -149,10 +149,10 @@ "Size": 5872 }, "assemblies/Xamarin.AndroidX.AppCompat.AppCompatResources.dll": { - "Size": 5889 + "Size": 5908 }, "assemblies/Xamarin.AndroidX.AppCompat.dll": { - "Size": 112363 + "Size": 112407 }, "assemblies/Xamarin.AndroidX.CardView.dll": { "Size": 6596 @@ -161,13 +161,13 @@ "Size": 16407 }, "assemblies/Xamarin.AndroidX.Core.dll": { - "Size": 96515 + "Size": 96466 }, "assemblies/Xamarin.AndroidX.DrawerLayout.dll": { - "Size": 14065 + "Size": 13924 }, "assemblies/Xamarin.AndroidX.Fragment.dll": { - "Size": 39713 + "Size": 39483 }, "assemblies/Xamarin.AndroidX.Legacy.Support.Core.UI.dll": { "Size": 5924 @@ -185,16 +185,16 @@ "Size": 12476 }, "assemblies/Xamarin.AndroidX.RecyclerView.dll": { - "Size": 84474 + "Size": 83675 }, "assemblies/Xamarin.AndroidX.SavedState.dll": { "Size": 4869 }, "assemblies/Xamarin.AndroidX.SwipeRefreshLayout.dll": { - "Size": 10180 + "Size": 10116 }, "assemblies/Xamarin.AndroidX.ViewPager.dll": { - "Size": 17786 + "Size": 17484 }, "assemblies/Xamarin.Forms.Core.dll": { "Size": 528450 @@ -209,16 +209,16 @@ "Size": 60774 }, "assemblies/Xamarin.Google.Android.Material.dll": { - "Size": 39911 + "Size": 39785 }, "classes.dex": { - "Size": 3473196 + "Size": 3090764 }, "lib/arm64-v8a/libmono-component-marshal-ilgen.so": { "Size": 96832 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 425416 + "Size": 432408 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3052824 @@ -233,7 +233,7 @@ "Size": 148696 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 99936 + "Size": 322304 }, "META-INF/android.support.design_material.version": { "Size": 12 @@ -1988,5 +1988,5 @@ "Size": 341228 } }, - "PackageSize": 8107074 + "PackageSize": 8066114 } \ No newline at end of file From 86d9fcac683060baf9317db9f77494a1f811b1b9 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 7 Sep 2022 23:20:44 +0200 Subject: [PATCH 40/79] Try to avoid locked files on Windows --- .../Tasks/GenerateJavaStubs.cs | 8 +++++++- .../Utilities/MarshalMethodsClassifier.cs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index b6460470396..a00dc9c5b34 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -96,10 +96,16 @@ public class GenerateJavaStubs : AndroidTask public override bool RunTask () { + var readerParams = new ReaderParameters { + ReadingMode = ReadingMode.Immediate, + ReadWrite = true, + InMemory = true, + }; + try { // We're going to do 3 steps here instead of separate tasks so // we can share the list of JLO TypeDefinitions between them - using (var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true)) { + using (var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true, loadReaderParameters: readerParams)) { Run (res, useMarshalMethods: !Debug && EnableMarshalMethods); } } catch (XamarinAndroidException e) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 270bad1c02c..b9e3dc14963 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -388,6 +388,7 @@ bool IsMatchingMethod (MethodDefinition method, string name) /// public void AddSpecialCaseMethods () { + // TODO: make sure it works AddTypeManagerSpecialCaseMethods (); } From 65471a8786f5b227ea6acccf1b6fe9e8292bc5c9 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 8 Sep 2022 11:35:04 +0200 Subject: [PATCH 41/79] Try to unbreak build of tests Hopefully these settings also fix the locked assembly problem on Windows --- src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index a00dc9c5b34..a310ae4a45d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -97,7 +97,7 @@ public class GenerateJavaStubs : AndroidTask public override bool RunTask () { var readerParams = new ReaderParameters { - ReadingMode = ReadingMode.Immediate, +// ReadingMode = ReadingMode.Immediate, ReadWrite = true, InMemory = true, }; From 7f87a37528cdd13e6afd274d6d94c689af14b765 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 8 Sep 2022 15:51:50 +0200 Subject: [PATCH 42/79] Don't require the `EnableMarshalMethods` parameter Both tasks are used for design builds which don't need to use marshal methods. --- src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs | 3 --- .../Tasks/GeneratePackageManagerJava.cs | 2 -- 2 files changed, 5 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index a310ae4a45d..f93cf1a7801 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -51,9 +51,7 @@ public class GenerateJavaStubs : AndroidTask [Required] public bool GenerateNativeAssembly { get; set; } - [Required] public bool EnableMarshalMethods { get; set; } - public string ManifestTemplate { get; set; } public string[] MergedManifestDocuments { get; set; } @@ -97,7 +95,6 @@ public class GenerateJavaStubs : AndroidTask public override bool RunTask () { var readerParams = new ReaderParameters { -// ReadingMode = ReadingMode.Immediate, ReadWrite = true, InMemory = true, }; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index cd9361051fc..a741be343c1 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -68,9 +68,7 @@ public class GeneratePackageManagerJava : AndroidTask [Required] public bool InstantRunEnabled { get; set; } - [Required] public bool EnableMarshalMethods { get; set; } - public string RuntimeConfigBinFilePath { get; set; } public string BoundExceptionType { get; set; } From 115c6faff0fdcc4eaef75309256bf5ac4a771006 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 8 Sep 2022 17:09:28 +0200 Subject: [PATCH 43/79] Remove the check, it's incorrect There are duplicate assembly names in the apk (per-arch assemblies), so the number of unique names will rarely be the same as the number of unique names (unless only one architecture is built) --- .../Utilities/MarshalMethodsNativeAssemblyGenerator.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 20c2c599d50..98763a00f0f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -219,10 +219,6 @@ public MarshalMethodsNativeAssemblyGenerator (int numberOfAssembliesInApk, IColl this.marshalMethods = marshalMethods; this.logger = logger ?? throw new ArgumentNullException (nameof (logger)); - if (uniqueAssemblyNames.Count != numberOfAssembliesInApk) { - throw new InvalidOperationException ("Internal error: number of assemblies in the apk doesn't match the number of unique assembly names"); - } - generateEmptyCode = false; } From 222163ed8c2a2109f8e84c7bf9760c77bd7befc0 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 8 Sep 2022 17:23:21 +0200 Subject: [PATCH 44/79] Avoid warning about duplicate files to add to apk --- .../targets/Microsoft.Android.Sdk.Aot.targets | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets index 6a55fc7b5ac..1575e0349a5 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets @@ -48,6 +48,12 @@ They run in a context of an inner build with a single $(RuntimeIdentifier). + <_AotResolvedFileToPublish + Include="@(_AotCompiledAssemblies)" + Condition=" '$(_AndroidUseMarshalMethods)' == 'True' " ArchiveFileName="libaot-$([System.IO.Path]::GetFileNameWithoutExtension('%(_AotCompiledAssemblies.Identity)')).so" /> @@ -55,12 +61,12 @@ They run in a context of an inner build with a single $(RuntimeIdentifier). + Returns="@(_AotResolvedFileToPublish)"> - - + <_AotResolvedFileToPublish + Condition=" '%(_AotResolvedFileToPublish.RuntimeIdentifier)' == '' " + Update="@(_AotResolvedFileToPublish)" RuntimeIdentifier="$(RuntimeIdentifier)" /> From e635b27dda8a1d3764b4fcb57ca31380ccf3b1e5 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 8 Sep 2022 21:59:03 +0200 Subject: [PATCH 45/79] Update apkdesc files --- .../BuildReleaseArm64SimpleLegacy.apkdesc | 16 +++++++-------- .../BuildReleaseArm64XFormsLegacy.apkdesc | 20 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc index ac323f1cebb..e814dd13ded 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc @@ -5,10 +5,10 @@ "Size": 2604 }, "assemblies/Java.Interop.dll": { - "Size": 68921 + "Size": 69028 }, "assemblies/Mono.Android.dll": { - "Size": 259883 + "Size": 264788 }, "assemblies/mscorlib.dll": { "Size": 769017 @@ -17,13 +17,13 @@ "Size": 28199 }, "assemblies/System.dll": { - "Size": 9179 + "Size": 9180 }, "assemblies/UnnamedProject.dll": { - "Size": 2877 + "Size": 2881 }, "classes.dex": { - "Size": 362940 + "Size": 370480 }, "lib/arm64-v8a/libmono-btls-shared.so": { "Size": 1613872 @@ -32,7 +32,7 @@ "Size": 750976 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 332128 + "Size": 332880 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 4051864 @@ -41,7 +41,7 @@ "Size": 66184 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 20240 + "Size": 21112 }, "META-INF/ANDROIDD.RSA": { "Size": 1213 @@ -74,5 +74,5 @@ "Size": 1724 } }, - "PackageSize": 4028116 + "PackageSize": 4036308 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc index 4498b397cca..aea0929a8d2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc @@ -8,10 +8,10 @@ "Size": 7215 }, "assemblies/Java.Interop.dll": { - "Size": 69945 + "Size": 70056 }, "assemblies/Mono.Android.dll": { - "Size": 570605 + "Size": 571897 }, "assemblies/Mono.Security.dll": { "Size": 68433 @@ -32,7 +32,7 @@ "Size": 110693 }, "assemblies/System.Numerics.dll": { - "Size": 15682 + "Size": 15683 }, "assemblies/System.Runtime.Serialization.dll": { "Size": 186660 @@ -44,7 +44,7 @@ "Size": 395656 }, "assemblies/UnnamedProject.dll": { - "Size": 116896 + "Size": 116897 }, "assemblies/Xamarin.AndroidX.Activity.dll": { "Size": 7697 @@ -65,7 +65,7 @@ "Size": 131930 }, "assemblies/Xamarin.AndroidX.DrawerLayout.dll": { - "Size": 15426 + "Size": 15425 }, "assemblies/Xamarin.AndroidX.Fragment.dll": { "Size": 43135 @@ -95,7 +95,7 @@ "Size": 11272 }, "assemblies/Xamarin.AndroidX.ViewPager.dll": { - "Size": 19423 + "Size": 19424 }, "assemblies/Xamarin.Forms.Core.dll": { "Size": 524736 @@ -113,7 +113,7 @@ "Size": 43497 }, "classes.dex": { - "Size": 3475144 + "Size": 3482568 }, "lib/arm64-v8a/libmono-btls-shared.so": { "Size": 1613872 @@ -122,7 +122,7 @@ "Size": 750976 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 332128 + "Size": 332880 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 4051864 @@ -131,7 +131,7 @@ "Size": 66184 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 105288 + "Size": 106160 }, "META-INF/android.support.design_material.version": { "Size": 12 @@ -1883,5 +1883,5 @@ "Size": 341040 } }, - "PackageSize": 9545886 + "PackageSize": 9549982 } \ No newline at end of file From 221ad8f40e37bd1bc909608a83f4877e5bbd6eda Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 8 Sep 2022 22:37:52 +0200 Subject: [PATCH 46/79] Let's see if it breaks any tests (it works locally) --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 2 +- .../Utilities/MarshalMethodsClassifier.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 5c91e754968..f6185db09d8 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -475,7 +475,7 @@ public override void RegisterNativeMembers (JniType nativeClass, Type type, stri // Temporary hack, see comments in RegisterNativeMembers below static readonly Dictionary dynamicRegistrationMethods = new Dictionary (StringComparer.Ordinal) { - {"Java.Interop.TypeManager+JavaTypeManager", new string[] { "GetActivateHandler" }}, + // leaving it empty for now, until we're sure it's no longer needed (there are some test failures which may still require it to fix them) }; public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan methods) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index b9e3dc14963..270bad1c02c 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -388,7 +388,6 @@ bool IsMatchingMethod (MethodDefinition method, string name) /// public void AddSpecialCaseMethods () { - // TODO: make sure it works AddTypeManagerSpecialCaseMethods (); } From f9bca3c2c07255cc051de509c260f7ec71e52f1a Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 9 Sep 2022 10:54:43 +0200 Subject: [PATCH 47/79] Update apkdesc files --- .../BuildReleaseArm64SimpleDotNet.apkdesc | 12 ++++---- .../BuildReleaseArm64XFormsDotNet.apkdesc | 30 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index 880a58bb615..f64e7342a80 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -8,7 +8,7 @@ "Size": 59188 }, "assemblies/Mono.Android.dll": { - "Size": 88549 + "Size": 88673 }, "assemblies/rc.bin": { "Size": 1182 @@ -47,13 +47,13 @@ "Size": 62998 }, "assemblies/UnnamedProject.dll": { - "Size": 3560 + "Size": 3559 }, "classes.dex": { - "Size": 360720 + "Size": 18612 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 425416 + "Size": 432408 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3073392 @@ -68,7 +68,7 @@ "Size": 148696 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 10216 + "Size": 16640 }, "META-INF/BNDLTOOL.RSA": { "Size": 1213 @@ -104,5 +104,5 @@ "Size": 1904 } }, - "PackageSize": 2791978 + "PackageSize": 2726442 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index 692414a6c1e..95b72bc72cd 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -5,13 +5,13 @@ "Size": 3568 }, "assemblies/FormsViewGroup.dll": { - "Size": 7114 + "Size": 7131 }, "assemblies/Java.Interop.dll": { "Size": 66767 }, "assemblies/Mono.Android.dll": { - "Size": 442556 + "Size": 437613 }, "assemblies/mscorlib.dll": { "Size": 3859 @@ -149,10 +149,10 @@ "Size": 5872 }, "assemblies/Xamarin.AndroidX.AppCompat.AppCompatResources.dll": { - "Size": 5889 + "Size": 5908 }, "assemblies/Xamarin.AndroidX.AppCompat.dll": { - "Size": 112363 + "Size": 112407 }, "assemblies/Xamarin.AndroidX.CardView.dll": { "Size": 6596 @@ -161,13 +161,13 @@ "Size": 16407 }, "assemblies/Xamarin.AndroidX.Core.dll": { - "Size": 96515 + "Size": 96466 }, "assemblies/Xamarin.AndroidX.DrawerLayout.dll": { - "Size": 14065 + "Size": 13924 }, "assemblies/Xamarin.AndroidX.Fragment.dll": { - "Size": 39713 + "Size": 39483 }, "assemblies/Xamarin.AndroidX.Legacy.Support.Core.UI.dll": { "Size": 5924 @@ -185,16 +185,16 @@ "Size": 12476 }, "assemblies/Xamarin.AndroidX.RecyclerView.dll": { - "Size": 84474 + "Size": 83675 }, "assemblies/Xamarin.AndroidX.SavedState.dll": { "Size": 4869 }, "assemblies/Xamarin.AndroidX.SwipeRefreshLayout.dll": { - "Size": 10180 + "Size": 10116 }, "assemblies/Xamarin.AndroidX.ViewPager.dll": { - "Size": 17786 + "Size": 17484 }, "assemblies/Xamarin.Forms.Core.dll": { "Size": 528450 @@ -209,13 +209,13 @@ "Size": 60774 }, "assemblies/Xamarin.Google.Android.Material.dll": { - "Size": 39911 + "Size": 39785 }, "classes.dex": { - "Size": 3473196 + "Size": 3090268 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 425416 + "Size": 432408 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3073392 @@ -230,7 +230,7 @@ "Size": 148696 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 99640 + "Size": 321936 }, "META-INF/android.support.design_material.version": { "Size": 12 @@ -1985,5 +1985,5 @@ "Size": 341228 } }, - "PackageSize": 8078308 + "PackageSize": 8037348 } \ No newline at end of file From 977a0600c99511479aa2cc5874d45af86d8adc54 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 9 Sep 2022 23:00:29 +0200 Subject: [PATCH 48/79] Hunting for reasons of the crashes 09-09 18:49:14.764 32043 32043 D monodroid: Calling into managed runtime init 09-09 18:49:14.764 32043 32043 D Mono : AOT NOT FOUND: (wrapper native-to-managed) Android.Runtime.JNIEnv:Initialize (Android.Runtime.JnienvInitializeArgs*). 09-09 18:49:14.764 32043 32043 F monodroid: Failed to get pointer to Initialize. Mono error: (null) assembly:System.Private.CoreLib.dll type:TypeInitializationException member:(null) 09-09 18:49:14.764 32043 32043 F monodroid: /home/grendel/vc/xamarin/xamarin-android-worktrees/mm-cleanup-and-enable/src/monodroid/jni/monodroid-glue.cc:1212 (init_android_runtime): Failed to obtain unmanaged-callers-only pointer to the Android.Runtime.JNIEnv.Initialize met hod. (null) assembly:System.Private.CoreLib.dll type:TypeInitializationException member:(null) 09-09 18:49:14.761 32043 32043 D Mono : AOT NOT FOUND: (wrapper native-to-managed) Android.Runtime.JNIEnv:RegisterJniNatives (intptr,int,intptr,intptr,int). 09-09 18:49:14.761 32043 32043 D Mono : Running class .cctor for Android.Runtime.JNIEnv from 'Mono.Android.dll' 09-09 18:49:14.762 32043 32043 D Mono : AOT NOT FOUND: (wrapper runtime-invoke) :runtime_invoke_bool_object_intptr_intptr_int_intptr (object,intptr,intptr,intptr). 09-09 18:49:14.762 32043 32043 D Mono : AOT: NOT FOUND: System.NullReferenceException:.ctor (). 09-09 18:49:14.762 32043 32043 D Mono : AOT NOT FOUND: (wrapper runtime-invoke) object:runtime_invoke_void__this__ (object,intptr,intptr,intptr). 09-09 18:49:14.762 32043 32043 D Mono : AOT: NOT FOUND: System.TypeInitializationException:.ctor (string,System.Exception). 09-09 18:49:14.762 32043 32043 D Mono : AOT NOT FOUND: (wrapper runtime-invoke) :runtime_invoke_void__this___object_object (object,intptr,intptr,intptr). 09-09 18:49:14.762 32060 32060 D CompatibilityChangeReporter: Compat change id reported: 171979766; UID 10134; state: ENABLED 09-09 18:49:14.762 32043 32043 D Mono : AOT: NOT FOUND: System.SR:Format (string,object). 09-09 18:49:14.762 32043 32043 D Mono : AOT NOT FOUND: (wrapper stelemref) object:virt_stelemref_object (intptr,object). 09-09 18:49:14.762 32043 32043 D Mono : AOT: NOT FOUND: string:Join (string,object[]). 09-09 18:49:14.762 32043 32043 D Mono : AOT: NOT FOUND: string:JoinCore (System.ReadOnlySpan`1,object[]). 09-09 18:49:14.763 32043 32043 D Mono : AOT: FOUND method string:ToString () [0x71b231a770 - 0x71b231a790 0x71b2291972] 09-09 18:49:14.763 32043 32043 D Mono : AOT: FOUND method System.Text.ValueStringBuilder:.ctor (System.Span`1) [0x71b2355d50 - 0x71b2355da0 0x71b22930b3] 09-09 18:49:14.763 32043 32043 D Mono : AOT: NOT FOUND: System.Text.ValueStringBuilder:AppendSlow (string). 09-09 18:49:14.763 32043 32043 D Mono : AOT: NOT FOUND: System.Text.ValueStringBuilder:Append (System.ReadOnlySpan`1). 09-09 18:49:14.763 32043 32043 D Mono : AOT: NOT FOUND: System.Text.ValueStringBuilder:ToString (). 09-09 18:49:14.764 32043 32043 D Mono : AOT: FOUND method System.Span`1:ToString () [0x71b23bff10 - 0x71b23c04e0 0x71b22964f8] 09-09 18:49:14.764 32043 32043 D Mono : AOT NOT FOUND: (wrapper managed-to-managed) string:.ctor (System.ReadOnlySpan`1). 09-09 18:49:14.764 32043 32043 D Mono : AOT: FOUND method string:Ctor (System.ReadOnlySpan`1) [0x71b231a460 - 0x71b231a510 0x71b2291957] 09-09 18:49:14.764 32043 32043 D Mono : AOT: NOT FOUND: System.TypeInitializationException:.ctor (string,string,System.Exception). 09-09 18:49:14.764 32043 32043 D Mono : AOT: NOT FOUND: System.SystemException:.ctor (string,System.Exception). 09-09 18:49:14.764 32043 32043 D Mono : AOT: NOT FOUND: System.Exception:.ctor (string,System.Exception). --- src/Mono.Android/Android.Runtime/JNIEnv.cs | 2 +- src/monodroid/jni/monodroid-glue.cc | 28 ++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 2f031f459aa..a4823fe78c8 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -538,7 +538,7 @@ public static IntPtr FindClass (System.Type type) } } - static readonly int nameBufferLength = 1024; + const int nameBufferLength = 1024; [ThreadStatic] static char[]? nameBuffer; static unsafe IntPtr BinaryName (string classname) diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 3464f0725a3..efdf6363577 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1133,8 +1133,11 @@ MonodroidRuntime::init_android_runtime ( #endif // def NET method = mono_class_get_method_from_name (runtime, "Initialize", 1); } else { + log_warn (LOG_DEFAULT, "grendel: #1"); runtime = mono_class_get (image, application_config.android_runtime_jnienv_class_token); + log_warn (LOG_DEFAULT, "grendel: #2"); method = mono_get_method (image, application_config.jnienv_initialize_method_token, runtime); + log_warn (LOG_DEFAULT, "grendel: #3"); } abort_unless (runtime != nullptr, "INTERNAL ERROR: unable to find the Android.Runtime.JNIEnv class!"); @@ -1159,6 +1162,7 @@ MonodroidRuntime::init_android_runtime ( ); } + MonoError error; /* If running on desktop, we may be swapping in a new Mono.Android image when calling this * so always make sure we have the freshest handle to the method. */ @@ -1166,14 +1170,17 @@ MonodroidRuntime::init_android_runtime ( if constexpr (is_running_on_desktop) { registerType = mono_class_get_method_from_name (runtime, "RegisterJniNatives", 5); } else { + log_warn (LOG_DEFAULT, "grendel: #4"); registerType = mono_get_method (image, application_config.jnienv_registerjninatives_method_token, runtime); + log_warn (LOG_DEFAULT, "grendel: #5"); #if defined (NET) && defined (ANDROID) - MonoError error; + log_warn (LOG_DEFAULT, "grendel: #6"); jnienv_register_jni_natives = reinterpret_cast(mono_method_get_unmanaged_callers_only_ftnptr (registerType, &error)); + log_warn (LOG_DEFAULT, "grendel: #7"); #endif // def NET && def ANDROID } } - abort_unless (registerType != nullptr, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnv.RegisterJniNatives!"); + abort_unless (registerType != nullptr, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnv.RegisterJniNatives! %s", mono_error_get_message (&error)); MonoClass *android_runtime_jnienv = runtime; MonoClassField *bridge_processing_field = mono_class_get_field_from_name (runtime, const_cast ("BridgeProcessing")); @@ -1189,7 +1196,9 @@ MonodroidRuntime::init_android_runtime ( init.grefLoader = env->NewGlobalRef (loader); init.grefIGCUserPeer = utils.get_class_from_runtime_field (env, runtimeClass, "mono_android_IGCUserPeer", true); + log_warn (LOG_DEFAULT, "grendel: #8"); osBridge.initialize_on_runtime_init (env, runtimeClass); + log_warn (LOG_DEFAULT, "grendel: #9"); log_debug (LOG_DEFAULT, "Calling into managed runtime init"); @@ -1199,10 +1208,21 @@ MonodroidRuntime::init_android_runtime ( } #if defined (NET) && defined (ANDROID) - MonoError error; + log_warn (LOG_DEFAULT, "grendel: #10"); auto initialize = reinterpret_cast (mono_method_get_unmanaged_callers_only_ftnptr (method, &error)); - abort_unless (initialize != nullptr, "Failed to obtain unmanaged-callers-only pointer to the Android.Runtime.JNIEnv.Initialize method"); + log_warn (LOG_DEFAULT, "grendel: #11"); + if (initialize == nullptr) { + log_fatal (LOG_DEFAULT, "Failed to get pointer to Initialize. Mono error: %s", mono_error_get_message (&error)); + } + + abort_unless ( + initialize != nullptr, + "Failed to obtain unmanaged-callers-only pointer to the Android.Runtime.JNIEnv.Initialize method. %s", + mono_error_get_message (&error) + ); + log_warn (LOG_DEFAULT, "grendel: #12"); initialize (&init); + log_warn (LOG_DEFAULT, "grendel: #13"); #else // def NET && def ANDROID void *args [] = { &init, From 312d84e75bcc27c1a4bf1d12fd20a6dda0651dc5 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 12 Sep 2022 23:39:28 +0200 Subject: [PATCH 49/79] Refactor the JNIEnv class Put the `RegisterJniNatives` and `Initialize` methods, plus some static fields they use, in a separate class. This is an attempt to prevent crashes when JIT-ing these methods at startup, in some unit tests. --- .../PreserveLists/Mono.Android.xml | 5 +- src/Mono.Android/Android.App/SyncContext.cs | 2 +- .../Android.Runtime/AndroidRuntime.cs | 24 +- src/Mono.Android/Android.Runtime/JNIEnv.cs | 276 ++++++++---------- .../Android.Runtime/JNIEnvInit.cs | 159 ++++++++++ .../Android.Runtime/JNINativeWrapper.cs | 5 +- .../Android.Runtime/JNINativeWrapper.g.cs | 38 +-- .../Android.Runtime/JNINativeWrapper.g.tt | 2 +- src/Mono.Android/Java.Interop/Runtime.cs | 2 +- src/Mono.Android/Java.Interop/TypeManager.cs | 12 +- src/Mono.Android/Java.Lang/Object.cs | 10 +- src/Mono.Android/Java.Lang/Throwable.cs | 7 +- src/Mono.Android/Mono.Android.csproj | 1 + .../Linker/PreserveLists/Mono.Android.xml | 1 + .../Tasks/GeneratePackageManagerJava.cs | 4 +- .../MarshalMethodsAssemblyRewriter.cs | 11 +- src/monodroid/jni/monodroid-glue.cc | 8 +- src/monodroid/jni/shared-constants.hh | 2 +- 18 files changed, 352 insertions(+), 217 deletions(-) create mode 100644 src/Mono.Android/Android.Runtime/JNIEnvInit.cs diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml index 368771253da..7dc40bd6a40 100644 --- a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml @@ -7,10 +7,13 @@ - + + + + diff --git a/src/Mono.Android/Android.App/SyncContext.cs b/src/Mono.Android/Android.App/SyncContext.cs index ac0fe30093a..6c3daa83c7d 100644 --- a/src/Mono.Android/Android.App/SyncContext.cs +++ b/src/Mono.Android/Android.App/SyncContext.cs @@ -18,7 +18,7 @@ static bool EnsureLooper ([NotNullWhen (true)]Looper? looper, SendOrPostCallback { if (looper == null) { var message = $"No Android message loop is available. Skipping invocation of `{d.Method.DeclaringType?.FullName}.{d.Method.Name}`!"; - if (JNIEnv.IsRunningOnDesktop) + if (JNIEnvInit.IsRunningOnDesktop) message += " Using `await` when running on the Android Designer is not currently supported. Please use the `View.IsInEditMode` property."; Logger.Log (LogLevel.Error, "monodroid-synccontext", message); return false; diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index f6185db09d8..d78727fded9 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -54,7 +54,7 @@ public override string GetCurrentManagedThreadStackTrace (int skipFrames, bool f { if (!reference.IsValid) return null; - var peeked = JNIEnv.AndroidValueManager?.PeekPeer (reference); + var peeked = JNIEnvInit.AndroidValueManager?.PeekPeer (reference); var peekedExc = peeked as Exception; if (peekedExc == null) { var throwable = Java.Lang.Object.GetObject (reference.Handle, JniHandleOwnership.DoNotTransfer); @@ -62,7 +62,7 @@ public override string GetCurrentManagedThreadStackTrace (int skipFrames, bool f return throwable; } JniObjectReference.Dispose (ref reference, options); - var unwrapped = JNIEnv.AndroidValueManager?.UnboxException (peeked!); + var unwrapped = JNIEnvInit.AndroidValueManager?.UnboxException (peeked!); if (unwrapped != null) { return unwrapped; } @@ -182,7 +182,7 @@ public override JniObjectReference CreateGlobalReference (JniObjectReference val var tid = log ? Thread.CurrentThread.ManagedThreadId : 0; var from = log ? new StringBuilder (new StackTrace (true).ToString ()) : null; int gc = JNIEnv._monodroid_gref_log_new (value.Handle, ctype, r.Handle, ntype, tname, tid, from, 1); - if (gc >= JNIEnv.gref_gc_threshold) { + if (gc >= JNIEnvInit.gref_gc_threshold) { Logger.Log (LogLevel.Info, "monodroid-gc", gc + " outstanding GREFs. Performing a full GC!"); System.GC.Collect (); } @@ -276,7 +276,7 @@ protected override IEnumerable GetTypesForSimpleReference (string jniSimpl #endif // NET j; } - if (JNIEnv.IsRunningOnDesktop) { + if (JNIEnvInit.IsRunningOnDesktop) { return JavaNativeTypeManager.ToJniName (type); } return null; @@ -288,7 +288,7 @@ protected override IEnumerable GetSimpleReferences (Type type) #if NET j = GetReplacementTypeCore (j) ?? j; #endif // NET - if (JNIEnv.IsRunningOnDesktop) { + if (JNIEnvInit.IsRunningOnDesktop) { string? d = JavaNativeTypeManager.ToJniName (type); if (j != null && d != null) { return new[]{j, d}; @@ -328,7 +328,7 @@ protected override IEnumerable GetSimpleReferences (Type type) protected override string? GetReplacementTypeCore (string jniSimpleReference) { - if (!JNIEnv.jniRemappingInUse) { + if (!JNIEnvInit.jniRemappingInUse) { return null; } @@ -345,7 +345,7 @@ protected override IEnumerable GetSimpleReferences (Type type) protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature) { - if (!JNIEnv.jniRemappingInUse) { + if (!JNIEnvInit.jniRemappingInUse) { return null; } @@ -547,7 +547,7 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< // TODO: this is temporary hack, it needs a full fledged registration mechanism for methods like these (that is, ones which // aren't registered with [Register] but are baked into Mono.Android's managed and Java code) bool createCallback; - if (JNIEnv.MarshalMethodsEnabled) { + if (JNIEnvInit.MarshalMethodsEnabled) { string declaringTypeName = callbackDeclaringType.FullName; string callbackName = callbackString.ToString (); @@ -665,7 +665,7 @@ public override void AddPeer (IJavaPeerable value) throw new ArgumentException ("Must have a valid JNI object reference!", nameof (value)); var reference = value.PeerReference; - var hash = JNIEnv.IdentityHash! (reference.Handle); + var hash = JNIEnvInit.IdentityHash! (reference.Handle); AddPeer (value, reference, hash); } @@ -728,7 +728,7 @@ internal void AddPeer (IJavaPeerable value, IntPtr handle, JniHandleOwnership tr if (handleField == IntPtr.Zero) throw new InvalidOperationException ("Unable to allocate Global Reference for object '" + value.ToString () + "'!"); - IntPtr hash = JNIEnv.IdentityHash! (handleField); + IntPtr hash = JNIEnvInit.IdentityHash! (handleField); value.SetJniIdentityHashCode ((int) hash); if ((transfer & JniHandleOwnership.DoNotRegister) == 0) { AddPeer (value, new JniObjectReference (handleField, JniObjectReferenceType.Global), hash); @@ -786,7 +786,7 @@ public override void RemovePeer (IJavaPeerable value) // Likely an idempotent DIspose(); ignore. return; } - var hash = JNIEnv.IdentityHash! (reference.Handle); + var hash = JNIEnvInit.IdentityHash! (reference.Handle); RemovePeer (value, hash); } @@ -820,7 +820,7 @@ internal void RemovePeer (IJavaPeerable value, IntPtr hash) if (!reference.IsValid) return null; - var hash = JNIEnv.IdentityHash! (reference.Handle); + var hash = JNIEnvInit.IdentityHash! (reference.Handle); lock (instances) { if (instances.TryGetValue (hash, out var targets)) { for (int i = targets.Count - 1; i >= 0; i--) { diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index a4823fe78c8..bc8e55e1a1b 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -14,59 +14,35 @@ using System.Diagnostics.CodeAnalysis; namespace Android.Runtime { -#pragma warning disable 0649 - struct JnienvInitializeArgs { - public IntPtr javaVm; - public IntPtr env; - public IntPtr grefLoader; - public IntPtr Loader_loadClass; - public IntPtr grefClass; - public IntPtr Class_forName; - public uint logCategories; - public int version; - public int androidSdkVersion; - public int localRefsAreIndirect; - public int grefGcThreshold; - public IntPtr grefIGCUserPeer; - public int isRunningOnDesktop; - public byte brokenExceptionTransitions; - public int packageNamingPolicy; - public byte ioExceptionType; - public int jniAddNativeMethodRegistrationAttributePresent; - public bool jniRemappingInUse; - public bool marshalMethodsEnabled; - } -#pragma warning restore 0649 - public static partial class JNIEnv { - static IntPtr java_class_loader; - static IntPtr java_vm; - static IntPtr load_class_id; - static IntPtr gref_class; - static JniMethodInfo? mid_Class_forName; - static int version; - static int androidSdkVersion; + //static IntPtr java_class_loader; + //static IntPtr java_vm; + //static IntPtr load_class_id; + //static IntPtr gref_class; + //static JniMethodInfo? mid_Class_forName; + //static int version; + //static int androidSdkVersion; - static bool AllocObjectSupported; - internal static bool jniRemappingInUse; + //static bool AllocObjectSupported; + //internal static bool jniRemappingInUse; - static IntPtr grefIGCUserPeer_class; + //static IntPtr grefIGCUserPeer_class; - internal static int gref_gc_threshold; + //internal static int gref_gc_threshold; - internal static bool PropagateExceptions; + //internal static bool PropagateExceptions; - internal static bool IsRunningOnDesktop; - internal static bool LogAssemblyCategory; - internal static bool MarshalMethodsEnabled; + //internal static bool IsRunningOnDesktop; + //internal static bool LogAssemblyCategory; + //internal static bool MarshalMethodsEnabled; - static AndroidRuntime? androidRuntime; - static BoundExceptionType BoundExceptionType; + //static AndroidRuntime? androidRuntime; + //static BoundExceptionType BoundExceptionType; [ThreadStatic] static byte[]? mvid_bytes; - internal static AndroidValueManager? AndroidValueManager; + //internal static AndroidValueManager? AndroidValueManager; [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] internal extern static void monodroid_log (LogLevel level, LogCategories category, string message); @@ -82,7 +58,7 @@ public static partial class JNIEnv { public static IntPtr Handle { get { - return JniEnvironment.EnvironmentPointer; + return JNIEnvInit.Handle; } } @@ -96,7 +72,7 @@ internal static bool IsGCUserPeer (IntPtr value) if (value == IntPtr.Zero) return false; - return IsInstanceOf (value, grefIGCUserPeer_class); + return IsInstanceOf (value, JNIEnvInit.grefIGCUserPeer_class); } internal static bool ShouldWrapJavaException (Java.Lang.Throwable? t, [CallerMemberName] string? caller = null) @@ -108,7 +84,7 @@ internal static bool ShouldWrapJavaException (Java.Lang.Throwable? t, [CallerMem return false; } - bool wrap = BoundExceptionType == BoundExceptionType.System; + bool wrap = JNIEnvInit.BoundExceptionType == BoundExceptionType.System; if (!wrap) { monodroid_log (LogLevel.Warn, LogCategories.Default, @@ -121,96 +97,96 @@ internal static bool ShouldWrapJavaException (Java.Lang.Throwable? t, [CallerMem [DllImport ("libc")] static extern int gettid (); -#if NETCOREAPP - [UnmanagedCallersOnly] -#endif - static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len) - { - string typeName = new string ((char*) typeName_ptr, 0, typeName_len); - var type = Type.GetType (typeName); - if (type == null) { - monodroid_log (LogLevel.Error, - LogCategories.Default, - $"Could not load type '{typeName}'. Skipping JNI registration of type '{Java.Interop.TypeManager.GetClassName (jniClass)}'."); - return; - } - - var className = Java.Interop.TypeManager.GetClassName (jniClass); - Java.Interop.TypeManager.RegisterType (className, type); - - JniType? jniType = null; - JniType.GetCachedJniType (ref jniType, className); - - ReadOnlySpan methods = new ReadOnlySpan ((void*) methods_ptr, methods_len); - ((AndroidTypeManager)androidRuntime!.TypeManager).RegisterNativeMembers (jniType, type, methods); - } - -#if NETCOREAPP - [UnmanagedCallersOnly] -#endif - internal static unsafe void Initialize (JnienvInitializeArgs* args) - { - IntPtr total_timing_sequence = IntPtr.Zero; - IntPtr partial_timing_sequence = IntPtr.Zero; - - LogAssemblyCategory = (args->logCategories & (uint)LogCategories.Assembly) != 0; - - gref_gc_threshold = args->grefGcThreshold; - - jniRemappingInUse = args->jniRemappingInUse; -#if NETCOREAPP - MarshalMethodsEnabled = args->marshalMethodsEnabled; -#endif - java_vm = args->javaVm; - - version = args->version; - - androidSdkVersion = args->androidSdkVersion; - - java_class_loader = args->grefLoader; - load_class_id = args->Loader_loadClass; - gref_class = args->grefClass; - mid_Class_forName = new JniMethodInfo (args->Class_forName, isStatic: true); - - if (args->localRefsAreIndirect == 1) - IdentityHash = v => _monodroid_get_identity_hash_code (Handle, v); - else - IdentityHash = v => v; - -#if MONOANDROID1_0 - Mono.SystemDependencyProvider.Initialize (); -#endif - - BoundExceptionType = (BoundExceptionType)args->ioExceptionType; - androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0); - AndroidValueManager = (AndroidValueManager) androidRuntime.ValueManager; - - AllocObjectSupported = androidSdkVersion > 10; - IsRunningOnDesktop = args->isRunningOnDesktop == 1; - - grefIGCUserPeer_class = args->grefIGCUserPeer; - - PropagateExceptions = args->brokenExceptionTransitions == 0; - - JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args->packageNamingPolicy; - if (IsRunningOnDesktop) { - var packageNamingPolicy = Environment.GetEnvironmentVariable ("__XA_PACKAGE_NAMING_POLICY__"); - if (Enum.TryParse (packageNamingPolicy, out PackageNamingPolicy pnp)) { - JavaNativeTypeManager.PackageNamingPolicy = pnp; - } - } - -#if !MONOANDROID1_0 - SetSynchronizationContext (); -#endif - } - -#if !MONOANDROID1_0 - // NOTE: prevents Android.App.Application static ctor from running - [MethodImpl (MethodImplOptions.NoInlining)] - static void SetSynchronizationContext () => - SynchronizationContext.SetSynchronizationContext (Android.App.Application.SynchronizationContext); -#endif +// #if NETCOREAPP +// [UnmanagedCallersOnly] +// #endif +// static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len) +// { + // string typeName = new string ((char*) typeName_ptr, 0, typeName_len); + // var type = Type.GetType (typeName); + // if (type == null) { + // monodroid_log (LogLevel.Error, + // LogCategories.Default, + // $"Could not load type '{typeName}'. Skipping JNI registration of type '{Java.Interop.TypeManager.GetClassName (jniClass)}'."); + // return; + // } + + // var className = Java.Interop.TypeManager.GetClassName (jniClass); + // Java.Interop.TypeManager.RegisterType (className, type); + + // JniType? jniType = null; + // JniType.GetCachedJniType (ref jniType, className); + + // ReadOnlySpan methods = new ReadOnlySpan ((void*) methods_ptr, methods_len); + // ((AndroidTypeManager)androidRuntime!.TypeManager).RegisterNativeMembers (jniType, type, methods); +// } + +// #if NETCOREAPP +// [UnmanagedCallersOnly] +// #endif +// internal static unsafe void Initialize (JnienvInitializeArgs* args) +// { +// IntPtr total_timing_sequence = IntPtr.Zero; +// IntPtr partial_timing_sequence = IntPtr.Zero; + +// LogAssemblyCategory = (args->logCategories & (uint)LogCategories.Assembly) != 0; + +// gref_gc_threshold = args->grefGcThreshold; + +// jniRemappingInUse = args->jniRemappingInUse; +// #if NETCOREAPP +// MarshalMethodsEnabled = args->marshalMethodsEnabled; +// #endif +// java_vm = args->javaVm; + +// version = args->version; + +// androidSdkVersion = args->androidSdkVersion; + +// java_class_loader = args->grefLoader; +// load_class_id = args->Loader_loadClass; +// gref_class = args->grefClass; +// mid_Class_forName = new JniMethodInfo (args->Class_forName, isStatic: true); + +// if (args->localRefsAreIndirect == 1) +// IdentityHash = v => _monodroid_get_identity_hash_code (Handle, v); +// else +// IdentityHash = v => v; + +// #if MONOANDROID1_0 +// Mono.SystemDependencyProvider.Initialize (); +// #endif + +// BoundExceptionType = (BoundExceptionType)args->ioExceptionType; +// androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0); +// AndroidValueManager = (AndroidValueManager) androidRuntime.ValueManager; + +// AllocObjectSupported = androidSdkVersion > 10; +// IsRunningOnDesktop = args->isRunningOnDesktop == 1; + +// grefIGCUserPeer_class = args->grefIGCUserPeer; + +// PropagateExceptions = args->brokenExceptionTransitions == 0; + +// JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args->packageNamingPolicy; +// if (IsRunningOnDesktop) { +// var packageNamingPolicy = Environment.GetEnvironmentVariable ("__XA_PACKAGE_NAMING_POLICY__"); +// if (Enum.TryParse (packageNamingPolicy, out PackageNamingPolicy pnp)) { +// JavaNativeTypeManager.PackageNamingPolicy = pnp; +// } +// } + +// #if !MONOANDROID1_0 +// SetSynchronizationContext (); +// #endif +// } + +// #if !MONOANDROID1_0 +// // NOTE: prevents Android.App.Application static ctor from running +// [MethodImpl (MethodImplOptions.NoInlining)] +// static void SetSynchronizationContext () => +// SynchronizationContext.SetSynchronizationContext (Android.App.Application.SynchronizationContext); +// #endif internal static void Exit () { @@ -298,7 +274,7 @@ static void Initialize () internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPtr, IntPtr javaExceptionPtr) { - if (!PropagateExceptions) + if (!JNIEnvInit.PropagateExceptions) return; try { @@ -336,21 +312,17 @@ internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPt [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] extern static void _monodroid_gc_wait_for_bridge_processing (); -#pragma warning disable CS0649 // Field is never assigned to. This field is assigned from monodroid-glue.cc. - static volatile bool BridgeProcessing; // = false -#pragma warning restore CS0649 // Field is never assigned to. - public static void WaitForBridgeProcessing () { - if (!BridgeProcessing) + if (!JNIEnvInit.BridgeProcessing) return; _monodroid_gc_wait_for_bridge_processing (); } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - extern static IntPtr _monodroid_get_identity_hash_code (IntPtr env, IntPtr value); + // [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + // extern static IntPtr _monodroid_get_identity_hash_code (IntPtr env, IntPtr value); - internal static Func? IdentityHash; + //internal static Func? IdentityHash; public static IntPtr AllocObject (string jniClassName) { @@ -376,7 +348,7 @@ public static IntPtr AllocObject (Type type) public static unsafe IntPtr StartCreateInstance (IntPtr jclass, IntPtr constructorId, JValue* constructorParameters) { - if (AllocObjectSupported) { + if (JNIEnvInit.AllocObjectSupported) { return AllocObject (jclass); } return NewObject (jclass, constructorId, constructorParameters); @@ -390,7 +362,7 @@ public static unsafe IntPtr StartCreateInstance (IntPtr jclass, IntPtr construct public static unsafe void FinishCreateInstance (IntPtr instance, IntPtr jclass, IntPtr constructorId, JValue* constructorParameters) { - if (!AllocObjectSupported) + if (!JNIEnvInit.AllocObjectSupported) return; CallNonvirtualVoidMethod (instance, jclass, constructorId, constructorParameters); } @@ -403,7 +375,7 @@ public static unsafe void FinishCreateInstance (IntPtr instance, IntPtr jclass, public static unsafe IntPtr StartCreateInstance (Type type, string jniCtorSignature, JValue* constructorParameters) { - if (AllocObjectSupported) { + if (JNIEnvInit.AllocObjectSupported) { return AllocObject (type); } return CreateInstance (type, jniCtorSignature, constructorParameters); @@ -417,7 +389,7 @@ public static unsafe IntPtr StartCreateInstance (Type type, string jniCtorSignat public static unsafe IntPtr StartCreateInstance (string jniClassName, string jniCtorSignature, JValue* constructorParameters) { - if (AllocObjectSupported) + if (JNIEnvInit.AllocObjectSupported) return AllocObject (jniClassName); return CreateInstance (jniClassName, jniCtorSignature, constructorParameters); } @@ -430,7 +402,7 @@ public static unsafe IntPtr StartCreateInstance (string jniClassName, string jni public static unsafe void FinishCreateInstance (IntPtr instance, string jniCtorSignature, JValue* constructorParameters) { - if (!AllocObjectSupported) + if (!JNIEnvInit.AllocObjectSupported) return; InvokeConstructor (instance, jniCtorSignature, constructorParameters); } @@ -577,9 +549,9 @@ public unsafe static IntPtr FindClass (string classname) JniArgumentValue* parameters = stackalloc JniArgumentValue [3] { new JniArgumentValue (native_str), new JniArgumentValue (true), - new JniArgumentValue (java_class_loader), + new JniArgumentValue (JNIEnvInit.java_class_loader), }; - local_ref = JniEnvironment.StaticMethods.CallStaticObjectMethod (Java.Lang.Class.Members.JniPeerType.PeerReference, mid_Class_forName!, parameters); + local_ref = JniEnvironment.StaticMethods.CallStaticObjectMethod (Java.Lang.Class.Members.JniPeerType.PeerReference, JNIEnvInit.mid_Class_forName!, parameters); } finally { DeleteLocalRef (native_str); } @@ -757,7 +729,7 @@ internal static void LogTypemapTrace (StackTrace st) } if (ret == IntPtr.Zero) { - if (LogAssemblyCategory) { + if (JNIEnvInit.LogAssemblyCategory) { monodroid_log (LogLevel.Warn, LogCategories.Default, $"typemap: failed to map managed type to Java type: {type.AssemblyQualifiedName} (Module ID: {type.Module.ModuleVersionId}; Type token: {type.MetadataToken})"); LogTypemapTrace (new StackTrace (true)); } diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs new file mode 100644 index 00000000000..f0db449a557 --- /dev/null +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -0,0 +1,159 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +using Java.Interop; +using Java.Interop.Tools.TypeNameMappings; + +namespace Android.Runtime +{ + static class JNIEnvInit + { +#pragma warning disable 0649 + internal struct JnienvInitializeArgs { + public IntPtr javaVm; + public IntPtr env; + public IntPtr grefLoader; + public IntPtr Loader_loadClass; + public IntPtr grefClass; + public IntPtr Class_forName; + public uint logCategories; + public int version; + public int androidSdkVersion; + public int localRefsAreIndirect; + public int grefGcThreshold; + public IntPtr grefIGCUserPeer; + public int isRunningOnDesktop; + public byte brokenExceptionTransitions; + public int packageNamingPolicy; + public byte ioExceptionType; + public int jniAddNativeMethodRegistrationAttributePresent; + public bool jniRemappingInUse; + public bool marshalMethodsEnabled; + } +#pragma warning restore 0649 + + [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + extern static IntPtr _monodroid_get_identity_hash_code (IntPtr env, IntPtr value); + + internal static AndroidValueManager? AndroidValueManager; + internal static bool AllocObjectSupported; + internal static bool IsRunningOnDesktop; + internal static bool jniRemappingInUse; + internal static bool LogAssemblyCategory; + internal static bool MarshalMethodsEnabled; + internal static bool PropagateExceptions; + internal static BoundExceptionType BoundExceptionType; + internal static Func? IdentityHash; + internal static int gref_gc_threshold; + internal static IntPtr grefIGCUserPeer_class; + internal static IntPtr java_class_loader; + internal static JniMethodInfo? mid_Class_forName; + + static int androidSdkVersion; // TODO: doesn't need to be a field + static int version; // TODO: not needed? + static IntPtr gref_class; // TODO: not needed? + static IntPtr java_vm; // TODO: not needed? + static IntPtr load_class_id; // TODO: not needed? + + static AndroidRuntime? androidRuntime; + +#pragma warning disable CS0649 // Field is never assigned to. This field is assigned from monodroid-glue.cc. + internal static volatile bool BridgeProcessing; // = false +#pragma warning restore CS0649 // Field is never assigned to. + + internal static IntPtr Handle => JniEnvironment.EnvironmentPointer; + +#if NETCOREAPP + [UnmanagedCallersOnly] +#endif + static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len) + { + string typeName = new string ((char*) typeName_ptr, 0, typeName_len); + var type = Type.GetType (typeName); + if (type == null) { + JNIEnv.monodroid_log (LogLevel.Error, + LogCategories.Default, + $"Could not load type '{typeName}'. Skipping JNI registration of type '{Java.Interop.TypeManager.GetClassName (jniClass)}'."); + return; + } + + var className = Java.Interop.TypeManager.GetClassName (jniClass); + Java.Interop.TypeManager.RegisterType (className, type); + + JniType? jniType = null; + JniType.GetCachedJniType (ref jniType, className); + + ReadOnlySpan methods = new ReadOnlySpan ((void*) methods_ptr, methods_len); + ((AndroidTypeManager)androidRuntime!.TypeManager).RegisterNativeMembers (jniType, type, methods); + } + +#if NETCOREAPP + [UnmanagedCallersOnly] +#endif + internal static unsafe void Initialize (JnienvInitializeArgs* args) + { + IntPtr total_timing_sequence = IntPtr.Zero; + IntPtr partial_timing_sequence = IntPtr.Zero; + + LogAssemblyCategory = (args->logCategories & (uint)LogCategories.Assembly) != 0; + + gref_gc_threshold = args->grefGcThreshold; + + jniRemappingInUse = args->jniRemappingInUse; +#if NETCOREAPP + MarshalMethodsEnabled = args->marshalMethodsEnabled; +#endif + java_vm = args->javaVm; + + version = args->version; + + androidSdkVersion = args->androidSdkVersion; + + java_class_loader = args->grefLoader; + load_class_id = args->Loader_loadClass; + gref_class = args->grefClass; + mid_Class_forName = new JniMethodInfo (args->Class_forName, isStatic: true); + + if (args->localRefsAreIndirect == 1) + IdentityHash = v => _monodroid_get_identity_hash_code (Handle, v); + else + IdentityHash = v => v; + +#if MONOANDROID1_0 + Mono.SystemDependencyProvider.Initialize (); +#endif + + BoundExceptionType = (BoundExceptionType)args->ioExceptionType; + androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0); + AndroidValueManager = (AndroidValueManager) androidRuntime.ValueManager; + + AllocObjectSupported = androidSdkVersion > 10; + IsRunningOnDesktop = args->isRunningOnDesktop == 1; + + grefIGCUserPeer_class = args->grefIGCUserPeer; + + PropagateExceptions = args->brokenExceptionTransitions == 0; + + JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args->packageNamingPolicy; + if (IsRunningOnDesktop) { + var packageNamingPolicy = Environment.GetEnvironmentVariable ("__XA_PACKAGE_NAMING_POLICY__"); + if (Enum.TryParse (packageNamingPolicy, out PackageNamingPolicy pnp)) { + JavaNativeTypeManager.PackageNamingPolicy = pnp; + } + } + +#if !MONOANDROID1_0 + SetSynchronizationContext (); +#endif + } + +#if !MONOANDROID1_0 + // NOTE: prevents Android.App.Application static ctor from running + [MethodImpl (MethodImplOptions.NoInlining)] + static void SetSynchronizationContext () => + SynchronizationContext.SetSynchronizationContext (Android.App.Application.SynchronizationContext); +#endif + } +} diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs index 1d09ccf9772..60b1a96a081 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs @@ -41,7 +41,7 @@ public static Delegate CreateDelegate (Delegate dlg) if (result != null) return result; - if (JNIEnv.LogAssemblyCategory) { + if (JNIEnvInit.LogAssemblyCategory) { JNIEnv.monodroid_log (LogLevel.Debug, LogCategories.Assembly, $"Falling back to System.Reflection.Emit for delegate type '{delegateType}': {dlg.Method}"); } @@ -72,7 +72,7 @@ public static Delegate CreateDelegate (Delegate dlg) ig.Emit (OpCodes.Leave, label); - bool filter = Debugger.IsAttached || !JNIEnv.PropagateExceptions; + bool filter = Debugger.IsAttached || !JNIEnvInit.PropagateExceptions; if (filter && JNIEnv.mono_unhandled_exception_method != null) { ig.BeginExceptFilterBlock (); @@ -101,4 +101,3 @@ public static Delegate CreateDelegate (Delegate dlg) } } - diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs index 301a488008a..0c16450e50c 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs @@ -8,7 +8,7 @@ public static partial class JNINativeWrapper { static bool _unhandled_exception (Exception e) { - if (Debugger.IsAttached || !JNIEnv.PropagateExceptions) { + if (Debugger.IsAttached || !JNIEnvInit.PropagateExceptions) { JNIEnv.mono_unhandled_exception?.Invoke (e); return false; } @@ -22,7 +22,7 @@ internal static void Wrap_JniMarshal_PP_V (this _JniMarshal_PP_V callback, IntPt callback (jnienv, klazz); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -55,7 +55,7 @@ internal static void Wrap_JniMarshal_PPI_V (this _JniMarshal_PPI_V callback, Int callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -121,7 +121,7 @@ internal static void Wrap_JniMarshal_PPL_V (this _JniMarshal_PPL_V callback, Int callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -154,7 +154,7 @@ internal static void Wrap_JniMarshal_PPII_V (this _JniMarshal_PPII_V callback, I callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -165,7 +165,7 @@ internal static void Wrap_JniMarshal_PPLI_V (this _JniMarshal_PPLI_V callback, I callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -176,7 +176,7 @@ internal static void Wrap_JniMarshal_PPLZ_V (this _JniMarshal_PPLZ_V callback, I callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -187,7 +187,7 @@ internal static void Wrap_JniMarshal_PPLL_V (this _JniMarshal_PPLL_V callback, I callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -198,7 +198,7 @@ internal static void Wrap_JniMarshal_PPLF_V (this _JniMarshal_PPLF_V callback, I callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -253,7 +253,7 @@ internal static void Wrap_JniMarshal_PPIIL_V (this _JniMarshal_PPIIL_V callback, callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -286,7 +286,7 @@ internal static void Wrap_JniMarshal_PPLII_V (this _JniMarshal_PPLII_V callback, callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -297,7 +297,7 @@ internal static void Wrap_JniMarshal_PPIII_V (this _JniMarshal_PPIII_V callback, callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -319,7 +319,7 @@ internal static void Wrap_JniMarshal_PPILL_V (this _JniMarshal_PPILL_V callback, callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -341,7 +341,7 @@ internal static void Wrap_JniMarshal_PPLLL_V (this _JniMarshal_PPLLL_V callback, callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -385,7 +385,7 @@ internal static void Wrap_JniMarshal_PPIIII_V (this _JniMarshal_PPIIII_V callbac callback (jnienv, klazz, p0, p1, p2, p3); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -396,7 +396,7 @@ internal static void Wrap_JniMarshal_PPLLLL_V (this _JniMarshal_PPLLLL_V callbac callback (jnienv, klazz, p0, p1, p2, p3); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -418,7 +418,7 @@ internal static void Wrap_JniMarshal_PPLIIII_V (this _JniMarshal_PPLIIII_V callb callback (jnienv, klazz, p0, p1, p2, p3, p4); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -429,7 +429,7 @@ internal static void Wrap_JniMarshal_PPZIIII_V (this _JniMarshal_PPZIIII_V callb callback (jnienv, klazz, p0, p1, p2, p3, p4); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } @@ -440,7 +440,7 @@ internal static void Wrap_JniMarshal_PPLIIIIIIII_V (this _JniMarshal_PPLIIIIIIII callback (jnienv, klazz, p0, p1, p2, p3, p4, p5, p6, p7, p8); } catch (Exception e) when (_unhandled_exception (e)) { AndroidEnvironment.UnhandledException (e); - + } } diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt index 94a5da0272e..eccac6c18a0 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt @@ -250,7 +250,7 @@ namespace Android.Runtime { static bool _unhandled_exception (Exception e) { - if (Debugger.IsAttached || !JNIEnv.PropagateExceptions) { + if (Debugger.IsAttached || !JNIEnvInit.PropagateExceptions) { JNIEnv.mono_unhandled_exception?.Invoke (e); return false; } diff --git a/src/Mono.Android/Java.Interop/Runtime.cs b/src/Mono.Android/Java.Interop/Runtime.cs index 4ebf234200e..bcc3ec1c899 100644 --- a/src/Mono.Android/Java.Interop/Runtime.cs +++ b/src/Mono.Android/Java.Interop/Runtime.cs @@ -11,7 +11,7 @@ public static class Runtime { [Obsolete ("Please use Java.Interop.JniEnvironment.Runtime.ValueManager.GetSurfacedPeers()")] public static List GetSurfacedObjects () { - var peers = JNIEnv.AndroidValueManager!.GetSurfacedPeers (); + var peers = JNIEnvInit.AndroidValueManager!.GetSurfacedPeers (); var r = new List (peers.Count); foreach (var p in peers) { if (p.SurfacedPeer.TryGetTarget (out var target)) diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 34f3c7911ac..f723a22becc 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -144,7 +144,7 @@ static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPt string.Format ("warning: Skipping managed constructor invocation for handle 0x{0} (key_handle 0x{1}). " + "Please use JNIEnv.StartCreateInstance() + JNIEnv.FinishCreateInstance() instead of " + "JNIEnv.NewObject() and/or JNIEnv.CreateInstance().", - jobject.ToString ("x"), JNIEnv.IdentityHash! (jobject).ToString ("x"))); + jobject.ToString ("x"), JNIEnvInit.IdentityHash! (jobject).ToString ("x"))); } return; } @@ -183,7 +183,7 @@ internal static void Activate (IntPtr jobject, ConstructorInfo cinfo, object? [] cinfo.Invoke (newobj, parms); } catch (Exception e) { var m = string.Format ("Could not activate JNI Handle 0x{0} (key_handle 0x{1}) of Java type '{2}' as managed type '{3}'.", - jobject.ToString ("x"), JNIEnv.IdentityHash! (jobject).ToString ("x"), JNIEnv.GetClassNameFromInstance (jobject), cinfo.DeclaringType.FullName); + jobject.ToString ("x"), JNIEnvInit.IdentityHash! (jobject).ToString ("x"), JNIEnv.GetClassNameFromInstance (jobject), cinfo.DeclaringType.FullName); Logger.Log (LogLevel.Warn, "monodroid", m); Logger.Log (LogLevel.Warn, "monodroid", CreateJavaLocationException ().ToString ()); @@ -224,9 +224,9 @@ static Exception CreateJavaLocationException () if (type != null) return type; - if (!JNIEnv.IsRunningOnDesktop) { + if (!JNIEnvInit.IsRunningOnDesktop) { // Miss message is logged in the native runtime - if (JNIEnv.LogAssemblyCategory) + if (JNIEnvInit.LogAssemblyCategory) JNIEnv.LogTypemapTrace (new System.Diagnostics.StackTrace (true)); return null; } @@ -314,7 +314,7 @@ internal static IJavaPeerable CreateInstance (IntPtr handle, JniHandleOwnership result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable); } } catch (MissingMethodException e) { - var key_handle = JNIEnv.IdentityHash! (handle); + var key_handle = JNIEnvInit.IdentityHash! (handle); JNIEnv.DeleteRef (handle, transfer); throw new NotSupportedException ( string.Format ("Unable to activate instance of type {0} from native handle 0x{1} (key_handle 0x{2}).", @@ -359,7 +359,7 @@ public static void RegisterType (string java_class, Type t) if (String.Compare (jniFromType, java_class, StringComparison.OrdinalIgnoreCase) != 0) { TypeManagerMapDictionaries.ManagedToJni.Add (t, java_class); } - } else if (!JNIEnv.IsRunningOnDesktop || t != typeof (Java.Interop.TypeManager)) { + } else if (!JNIEnvInit.IsRunningOnDesktop || t != typeof (Java.Interop.TypeManager)) { // skip the registration and output a warning Logger.Log (LogLevel.Warn, "monodroid", string.Format ("Type Registration Skipped for {0} to {1} ", java_class, t.ToString())); } diff --git a/src/Mono.Android/Java.Lang/Object.cs b/src/Mono.Android/Java.Lang/Object.cs index 76618d7026e..848529fd8d9 100644 --- a/src/Mono.Android/Java.Lang/Object.cs +++ b/src/Mono.Android/Java.Lang/Object.cs @@ -170,7 +170,7 @@ void IJavaPeerable.DisposeUnlessReferenced () [EditorBrowsable (EditorBrowsableState.Never)] public void UnregisterFromRuntime () { - JNIEnv.AndroidValueManager?.RemovePeer (this, key_handle); + JNIEnvInit.AndroidValueManager?.RemovePeer (this, key_handle); } void IJavaPeerable.Disposed () @@ -207,7 +207,7 @@ void IJavaPeerable.SetPeerReference (JniObjectReference reference) public void Dispose () { - JNIEnv.AndroidValueManager?.DisposePeer (this); + JNIEnvInit.AndroidValueManager?.DisposePeer (this); } protected virtual void Dispose (bool disposing) @@ -224,7 +224,7 @@ internal static void Dispose (IJavaPeerable instance, ref IntPtr handle, IntPtr string.Format ("Disposing handle 0x{0}\n", handle.ToString ("x"))); } - JNIEnv.AndroidValueManager?.RemovePeer (instance, key_handle); + JNIEnvInit.AndroidValueManager?.RemovePeer (instance, key_handle); switch (handle_type) { case JObjectRefType.Global: @@ -248,13 +248,13 @@ internal static void Dispose (IJavaPeerable instance, ref IntPtr handle, IntPtr [EditorBrowsable (EditorBrowsableState.Never)] protected void SetHandle (IntPtr value, JniHandleOwnership transfer) { - JNIEnv.AndroidValueManager?.AddPeer (this, value, transfer, out handle); + JNIEnvInit.AndroidValueManager?.AddPeer (this, value, transfer, out handle); handle_type = JObjectRefType.Global; } internal static IJavaPeerable? PeekObject (IntPtr handle, Type? requiredType = null) { - var peeked = JNIEnv.AndroidValueManager?.PeekPeer (new JniObjectReference (handle)); + var peeked = JNIEnvInit.AndroidValueManager?.PeekPeer (new JniObjectReference (handle)); if (peeked == null) return null; if (requiredType != null && !requiredType.IsAssignableFrom (peeked.GetType ())) diff --git a/src/Mono.Android/Java.Lang/Throwable.cs b/src/Mono.Android/Java.Lang/Throwable.cs index b483c557c76..5dc63ef7952 100644 --- a/src/Mono.Android/Java.Lang/Throwable.cs +++ b/src/Mono.Android/Java.Lang/Throwable.cs @@ -241,7 +241,7 @@ public unsafe Java.Lang.Class? Class { [EditorBrowsable (EditorBrowsableState.Never)] protected void SetHandle (IntPtr value, JniHandleOwnership transfer) { - JNIEnv.AndroidValueManager?.AddPeer (this, value, transfer, out handle); + JNIEnvInit.AndroidValueManager?.AddPeer (this, value, transfer, out handle); handle_type = JObjectRefType.Global; } @@ -296,7 +296,7 @@ void IJavaPeerable.DisposeUnlessReferenced () public void UnregisterFromRuntime () { - JNIEnv.AndroidValueManager?.RemovePeer (this, key_handle); + JNIEnvInit.AndroidValueManager?.RemovePeer (this, key_handle); } void IJavaPeerable.Disposed () @@ -332,7 +332,7 @@ void IJavaPeerable.SetPeerReference (JniObjectReference reference) public void Dispose () { - JNIEnv.AndroidValueManager?.DisposePeer (this); + JNIEnvInit.AndroidValueManager?.DisposePeer (this); } protected virtual void Dispose (bool disposing) @@ -340,4 +340,3 @@ protected virtual void Dispose (bool disposing) } } } - diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 97e25263da6..215bc0e475c 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -249,6 +249,7 @@ + diff --git a/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml b/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml index 3c4d22fbfe0..15d6be826f9 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml +++ b/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml @@ -13,6 +13,7 @@ + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index a741be343c1..145ddbd3fc1 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -509,7 +509,7 @@ void GetRequiredTokens (string assemblyFilePath, out int android_runtime_jnienv_ } if (android_runtime_jnienv_class_token == -1 || jnienv_initialize_method_token == -1 || jnienv_registerjninatives_method_token == -1) { - throw new InvalidOperationException ($"Unable to find the required Android.Runtime.JNIEnv method tokens"); + throw new InvalidOperationException ($"Unable to find the required Android.Runtime.JNIEnvInit method tokens"); } } @@ -560,7 +560,7 @@ bool TypeMatches (TypeDefinition td) } string name = reader.GetString (td.Name); - if (String.Compare (name, "JNIEnv", StringComparison.Ordinal) != 0) { + if (String.Compare (name, "JNIEnvInit", StringComparison.Ordinal) != 0) { return false; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 28870cf2846..477d805f7fd 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -44,13 +44,14 @@ public void Rewrite (DirectoryAssemblyResolver resolver) continue; } - if (method.NeedsBlittableWorkaround) { - log.LogDebugMessage ($"Generating non-blittable type wrapper for callback method {method.NativeCallback.FullName}"); - method.NativeCallbackWrapper = GenerateBlittableWrapper (method, unmanagedCallersOnlyAttributes); - } else { + // TODO: verify again whether the generated code is valid + // if (method.NeedsBlittableWorkaround) { + // log.LogDebugMessage ($"Generating non-blittable type wrapper for callback method {method.NativeCallback.FullName}"); + // method.NativeCallbackWrapper = GenerateBlittableWrapper (method, unmanagedCallersOnlyAttributes); + // } else { log.LogDebugMessage ($"Adding the 'UnmanagedCallersOnly' attribute to callback method {method.NativeCallback.FullName}"); method.NativeCallback.CustomAttributes.Add (unmanagedCallersOnlyAttributes [method.NativeCallback.Module.Assembly]); - } + //} if (method.Connector != null) { if (method.Connector.IsStatic && method.Connector.IsPrivate) { diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index efdf6363577..8c28b1c71c3 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1180,12 +1180,12 @@ MonodroidRuntime::init_android_runtime ( #endif // def NET && def ANDROID } } - abort_unless (registerType != nullptr, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnv.RegisterJniNatives! %s", mono_error_get_message (&error)); + abort_unless (registerType != nullptr, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnvInit.RegisterJniNatives! %s", mono_error_get_message (&error)); MonoClass *android_runtime_jnienv = runtime; MonoClassField *bridge_processing_field = mono_class_get_field_from_name (runtime, const_cast ("BridgeProcessing")); - if (android_runtime_jnienv ==nullptr || bridge_processing_field == nullptr) { - log_fatal (LOG_DEFAULT, "INTERNAL_ERROR: Unable to find Android.Runtime.JNIEnv.BridgeProcessing"); + if (android_runtime_jnienv == nullptr || bridge_processing_field == nullptr) { + log_fatal (LOG_DEFAULT, "INTERNAL_ERROR: Unable to find Android.Runtime.JNIEnvInit.BridgeProcessing"); exit (FATAL_EXIT_CANNOT_FIND_JNIENV); } @@ -1217,7 +1217,7 @@ MonodroidRuntime::init_android_runtime ( abort_unless ( initialize != nullptr, - "Failed to obtain unmanaged-callers-only pointer to the Android.Runtime.JNIEnv.Initialize method. %s", + "Failed to obtain unmanaged-callers-only pointer to the Android.Runtime.JNIEnvInit.Initialize method. %s", mono_error_get_message (&error) ); log_warn (LOG_DEFAULT, "grendel: #12"); diff --git a/src/monodroid/jni/shared-constants.hh b/src/monodroid/jni/shared-constants.hh index 0f581b1cc04..f2c1aa881c2 100644 --- a/src/monodroid/jni/shared-constants.hh +++ b/src/monodroid/jni/shared-constants.hh @@ -21,7 +21,7 @@ namespace xamarin::android::internal static constexpr char JAVA_INTEROP_ASSEMBLY_NAME[] = "Java.Interop"; static constexpr char ANDROID_RUNTIME_NS_NAME[] = "Android.Runtime"; - static constexpr char JNIENV_CLASS_NAME[] = "JNIEnv"; + static constexpr char JNIENV_CLASS_NAME[] = "JNIEnvInit"; static constexpr char ANDROID_ENVIRONMENT_CLASS_NAME[] = "AndroidEnvironment"; static constexpr char DLL_EXTENSION[] = ".dll"; From c20ae31451473cbef4a043f8fbd22f2eb9138c60 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 13 Sep 2022 12:55:09 +0200 Subject: [PATCH 50/79] Generate assembly image cache array even if marshal methods disabled --- .../Tasks/GeneratePackageManagerJava.cs | 6 +-- .../MarshalMethodsAssemblyRewriter.cs | 10 ++--- .../MarshalMethodsNativeAssemblyGenerator.cs | 41 +++++++++---------- src/monodroid/CMakeLists.txt | 1 - src/monodroid/jni/mono-image-loader.cc | 5 --- src/monodroid/jni/mono-image-loader.hh | 4 +- 6 files changed, 28 insertions(+), 39 deletions(-) delete mode 100644 src/monodroid/jni/mono-image-loader.cc diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 145ddbd3fc1..ea88c2c4906 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -301,10 +301,10 @@ void AddEnvironment () int assemblyCount = 0; bool enableMarshalMethods = EnableMarshalMethods; HashSet archAssemblyNames = null; - HashSet uniqueAssemblyNames = enableMarshalMethods ? new HashSet (StringComparer.OrdinalIgnoreCase) : null; + HashSet uniqueAssemblyNames = new HashSet (StringComparer.OrdinalIgnoreCase); Action updateAssemblyCount = (ITaskItem assembly) => { string assemblyName = Path.GetFileName (assembly.ItemSpec); - if (enableMarshalMethods && !uniqueAssemblyNames.Contains (assemblyName)) { + if (!uniqueAssemblyNames.Contains (assemblyName)) { uniqueAssemblyNames.Add (assemblyName); } @@ -453,7 +453,7 @@ void AddEnvironment () Log ); } else { - marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (); + marshalMethodsAsmGen = new MarshalMethodsNativeAssemblyGenerator (uniqueAssemblyNames); } marshalMethodsAsmGen.Init (); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 477d805f7fd..f57cecc94be 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -45,13 +45,13 @@ public void Rewrite (DirectoryAssemblyResolver resolver) } // TODO: verify again whether the generated code is valid - // if (method.NeedsBlittableWorkaround) { - // log.LogDebugMessage ($"Generating non-blittable type wrapper for callback method {method.NativeCallback.FullName}"); - // method.NativeCallbackWrapper = GenerateBlittableWrapper (method, unmanagedCallersOnlyAttributes); - // } else { + if (method.NeedsBlittableWorkaround) { + log.LogDebugMessage ($"Generating non-blittable type wrapper for callback method {method.NativeCallback.FullName}"); + method.NativeCallbackWrapper = GenerateBlittableWrapper (method, unmanagedCallersOnlyAttributes); + } else { log.LogDebugMessage ($"Adding the 'UnmanagedCallersOnly' attribute to callback method {method.NativeCallback.FullName}"); method.NativeCallback.CustomAttributes.Add (unmanagedCallersOnlyAttributes [method.NativeCallback.Module.Assembly]); - //} + } if (method.Connector != null) { if (method.Connector.IsStatic && method.Connector.IsPrivate) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 98763a00f0f..8f46484e04b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -204,8 +204,9 @@ sealed class MarshalMethodName /// /// Constructor to be used ONLY when marshal methods are DISABLED /// - public MarshalMethodsNativeAssemblyGenerator () + public MarshalMethodsNativeAssemblyGenerator (ICollection uniqueAssemblyNames) { + this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames)); generateEmptyCode = true; } @@ -788,6 +789,7 @@ void WriteClassCache (LlvmIrGenerator generator) generator.WriteStructureArray (marshalMethodsClass, classes, LlvmIrVariableOptions.GlobalWritable, "marshal_methods_class_cache"); } + // TODO: this should probably be moved to a separate writer, since not only marshal methods use the cache void WriteAssemblyImageCache (LlvmIrGenerator generator, out Dictionary asmNameToIndex) { bool is64Bit = generator.Is64Bit; @@ -806,28 +808,23 @@ void WriteHashes () where T: struct { var hashes = new Dictionary (); uint index = 0; - List keys; - - if (!generateEmptyCode) { - foreach (string name in uniqueAssemblyNames) { - string clippedName = Path.GetFileNameWithoutExtension (name); - ulong hashFull = HashName (name, is64Bit); - ulong hashClipped = HashName (clippedName, is64Bit); - - // - // If the number of name forms changes, xamarin-app.hh MUST be updated to set value of the - // `number_of_assembly_name_forms_in_image_cache` constant to the number of forms. - // - hashes.Add ((T)Convert.ChangeType (hashFull, typeof(T)), (name, index)); - hashes.Add ((T)Convert.ChangeType (hashClipped, typeof(T)), (clippedName, index)); - - index++; - } - keys = hashes.Keys.ToList (); - keys.Sort (); - } else { - keys = new List (); + + foreach (string name in uniqueAssemblyNames) { + string clippedName = Path.GetFileNameWithoutExtension (name); + ulong hashFull = HashName (name, is64Bit); + ulong hashClipped = HashName (clippedName, is64Bit); + + // + // If the number of name forms changes, xamarin-app.hh MUST be updated to set value of the + // `number_of_assembly_name_forms_in_image_cache` constant to the number of forms. + // + hashes.Add ((T)Convert.ChangeType (hashFull, typeof(T)), (name, index)); + hashes.Add ((T)Convert.ChangeType (hashClipped, typeof(T)), (clippedName, index)); + + index++; } + List keys = hashes.Keys.ToList (); + keys.Sort (); generator.WriteCommentLine ("Each entry maps hash of an assembly name to an index into the `assembly_image_cache` array"); generator.WriteArray ( diff --git a/src/monodroid/CMakeLists.txt b/src/monodroid/CMakeLists.txt index f845a51fdbf..7f45848792b 100644 --- a/src/monodroid/CMakeLists.txt +++ b/src/monodroid/CMakeLists.txt @@ -489,7 +489,6 @@ set(XAMARIN_MONODROID_SOURCES ${SOURCES_DIR}/logger.cc ${SOURCES_DIR}/jni-remapping.cc ${SOURCES_DIR}/monodroid-glue.cc - ${SOURCES_DIR}/mono-image-loader.cc ${SOURCES_DIR}/osbridge.cc ${SOURCES_DIR}/shared-constants.cc ${SOURCES_DIR}/timezones.cc diff --git a/src/monodroid/jni/mono-image-loader.cc b/src/monodroid/jni/mono-image-loader.cc deleted file mode 100644 index ea2cc39a822..00000000000 --- a/src/monodroid/jni/mono-image-loader.cc +++ /dev/null @@ -1,5 +0,0 @@ -#include "mono-image-loader.hh" - -#if defined (USE_CACHE) -size_t xamarin::android::internal::MonoImageLoader::number_of_cache_index_entries = application_config.number_of_assemblies_in_apk * number_of_assembly_name_forms_in_image_cache; -#endif // def USE_CACHE diff --git a/src/monodroid/jni/mono-image-loader.hh b/src/monodroid/jni/mono-image-loader.hh index 3d2efe873a5..87bcbc2ed3a 100644 --- a/src/monodroid/jni/mono-image-loader.hh +++ b/src/monodroid/jni/mono-image-loader.hh @@ -55,7 +55,6 @@ namespace xamarin::android::internal { #if defined (NET) force_inline static MonoImage* load (dynamic_local_string const& name, MonoAssemblyLoadContextGCHandle alc_gchandle, hash_t name_hash, uint8_t *assembly_data, uint32_t assembly_data_size) noexcept { - log_debug (LOG_ASSEMBLY, "Mono image loader: loading assembly %s; hash 0x%zx", name.get (), name_hash); MonoImageOpenStatus status; MonoImage *image = mono_image_open_from_data_alc ( alc_gchandle, @@ -71,7 +70,6 @@ namespace xamarin::android::internal { force_inline static MonoImage* load (dynamic_local_string const& name, MonoAssemblyLoadContextGCHandle alc_gchandle, uint8_t *assembly_data, uint32_t assembly_data_size) noexcept { - return load (name, alc_gchandle, xxhash::hash (name.get (), name.length ()), assembly_data, assembly_data_size); } #endif // def NET @@ -136,7 +134,7 @@ namespace xamarin::android::internal { } #if defined (USE_CACHE) - static size_t number_of_cache_index_entries; + static inline size_t number_of_cache_index_entries = application_config.number_of_assemblies_in_apk * number_of_assembly_name_forms_in_image_cache;; #endif // def USE_CACHE }; } From 8c9ceca58029d1dec7a735eb4bc56318828d3aca Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 13 Sep 2022 23:54:30 +0200 Subject: [PATCH 51/79] Most problems solved, the crashes make sense now Copy all the rewritten assemblies to other RIDs so that AOT can generate valid .so files for them. The crash happening now is because once the assemblies are rewritten, Cecil no longer (no idea why) re-reads the new tokens. TBC tomorrow. --- .../PreserveLists/Mono.Android.xml | 7 +- .../Android.Runtime/AndroidRuntime.cs | 8 +-- src/Mono.Android/Android.Runtime/JNIEnv.cs | 12 ++-- .../Android.Runtime/JNIEnvInit.cs | 43 ++++-------- src/Mono.Android/Java.Interop/TypeManager.cs | 6 +- .../Tasks/GenerateJavaStubs.cs | 49 +++++++++++--- .../MarshalMethodsAssemblyRewriter.cs | 66 +++++++++++++++---- .../Xamarin.Android.Common.targets | 5 +- src/monodroid/jni/monodroid-glue.cc | 17 +---- 9 files changed, 130 insertions(+), 83 deletions(-) diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml index 7dc40bd6a40..eaa50318b75 100644 --- a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml @@ -8,12 +8,11 @@ + + + - - - - diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index d78727fded9..c128e994e05 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -665,7 +665,7 @@ public override void AddPeer (IJavaPeerable value) throw new ArgumentException ("Must have a valid JNI object reference!", nameof (value)); var reference = value.PeerReference; - var hash = JNIEnvInit.IdentityHash! (reference.Handle); + var hash = JNIEnv.IdentityHash (reference.Handle); AddPeer (value, reference, hash); } @@ -728,7 +728,7 @@ internal void AddPeer (IJavaPeerable value, IntPtr handle, JniHandleOwnership tr if (handleField == IntPtr.Zero) throw new InvalidOperationException ("Unable to allocate Global Reference for object '" + value.ToString () + "'!"); - IntPtr hash = JNIEnvInit.IdentityHash! (handleField); + IntPtr hash = JNIEnv.IdentityHash (handleField); value.SetJniIdentityHashCode ((int) hash); if ((transfer & JniHandleOwnership.DoNotRegister) == 0) { AddPeer (value, new JniObjectReference (handleField, JniObjectReferenceType.Global), hash); @@ -786,7 +786,7 @@ public override void RemovePeer (IJavaPeerable value) // Likely an idempotent DIspose(); ignore. return; } - var hash = JNIEnvInit.IdentityHash! (reference.Handle); + var hash = JNIEnv.IdentityHash (reference.Handle); RemovePeer (value, hash); } @@ -820,7 +820,7 @@ internal void RemovePeer (IJavaPeerable value, IntPtr hash) if (!reference.IsValid) return null; - var hash = JNIEnvInit.IdentityHash! (reference.Handle); + var hash = JNIEnv.IdentityHash (reference.Handle); lock (instances) { if (instances.TryGetValue (hash, out var targets)) { for (int i = targets.Count - 1; i >= 0; i--) { diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index bc8e55e1a1b..17d9d3d46bf 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -56,10 +56,14 @@ public static partial class JNIEnv { [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] internal extern static void monodroid_free (IntPtr ptr); - public static IntPtr Handle { - get { - return JNIEnvInit.Handle; - } + [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + extern static IntPtr _monodroid_get_identity_hash_code (IntPtr env, IntPtr value); + + public static IntPtr Handle => JniEnvironment.EnvironmentPointer; + + internal static IntPtr IdentityHash (IntPtr v) + { + return JNIEnvInit.LocalRefsAreIndirect ? _monodroid_get_identity_hash_code (Handle, v) : v; } public static void CheckHandle (IntPtr jnienv) diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index f0db449a557..80847654e02 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -15,11 +15,11 @@ internal struct JnienvInitializeArgs { public IntPtr javaVm; public IntPtr env; public IntPtr grefLoader; - public IntPtr Loader_loadClass; - public IntPtr grefClass; + public IntPtr Loader_loadClass; // TODO: remove, not needed anymore + public IntPtr grefClass; // TODO: remove, not needed anymore public IntPtr Class_forName; public uint logCategories; - public int version; + public int version; // TODO: remove, not needed anymore public int androidSdkVersion; public int localRefsAreIndirect; public int grefGcThreshold; @@ -35,36 +35,28 @@ internal struct JnienvInitializeArgs { #pragma warning restore 0649 [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - extern static IntPtr _monodroid_get_identity_hash_code (IntPtr env, IntPtr value); + extern static void monodroid_log (LogLevel level, LogCategories category, string message); internal static AndroidValueManager? AndroidValueManager; internal static bool AllocObjectSupported; internal static bool IsRunningOnDesktop; internal static bool jniRemappingInUse; + internal static bool LocalRefsAreIndirect; internal static bool LogAssemblyCategory; internal static bool MarshalMethodsEnabled; internal static bool PropagateExceptions; internal static BoundExceptionType BoundExceptionType; - internal static Func? IdentityHash; internal static int gref_gc_threshold; internal static IntPtr grefIGCUserPeer_class; internal static IntPtr java_class_loader; internal static JniMethodInfo? mid_Class_forName; - static int androidSdkVersion; // TODO: doesn't need to be a field - static int version; // TODO: not needed? - static IntPtr gref_class; // TODO: not needed? - static IntPtr java_vm; // TODO: not needed? - static IntPtr load_class_id; // TODO: not needed? - static AndroidRuntime? androidRuntime; #pragma warning disable CS0649 // Field is never assigned to. This field is assigned from monodroid-glue.cc. internal static volatile bool BridgeProcessing; // = false #pragma warning restore CS0649 // Field is never assigned to. - internal static IntPtr Handle => JniEnvironment.EnvironmentPointer; - #if NETCOREAPP [UnmanagedCallersOnly] #endif @@ -73,9 +65,9 @@ static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, In string typeName = new string ((char*) typeName_ptr, 0, typeName_len); var type = Type.GetType (typeName); if (type == null) { - JNIEnv.monodroid_log (LogLevel.Error, - LogCategories.Default, - $"Could not load type '{typeName}'. Skipping JNI registration of type '{Java.Interop.TypeManager.GetClassName (jniClass)}'."); + monodroid_log (LogLevel.Error, + LogCategories.Default, + $"Could not load type '{typeName}'. Skipping JNI registration of type '{Java.Interop.TypeManager.GetClassName (jniClass)}'."); return; } @@ -105,31 +97,22 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) #if NETCOREAPP MarshalMethodsEnabled = args->marshalMethodsEnabled; #endif - java_vm = args->javaVm; - - version = args->version; - - androidSdkVersion = args->androidSdkVersion; - java_class_loader = args->grefLoader; - load_class_id = args->Loader_loadClass; - gref_class = args->grefClass; + mid_Class_forName = new JniMethodInfo (args->Class_forName, isStatic: true); - if (args->localRefsAreIndirect == 1) - IdentityHash = v => _monodroid_get_identity_hash_code (Handle, v); - else - IdentityHash = v => v; + LocalRefsAreIndirect = args->localRefsAreIndirect == 1; #if MONOANDROID1_0 Mono.SystemDependencyProvider.Initialize (); #endif + bool androidNewerThan10 = args->androidSdkVersion > 10; BoundExceptionType = (BoundExceptionType)args->ioExceptionType; - androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0); + androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidNewerThan10, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0); AndroidValueManager = (AndroidValueManager) androidRuntime.ValueManager; - AllocObjectSupported = androidSdkVersion > 10; + AllocObjectSupported = androidNewerThan10; IsRunningOnDesktop = args->isRunningOnDesktop == 1; grefIGCUserPeer_class = args->grefIGCUserPeer; diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index f723a22becc..afe6b211c05 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -144,7 +144,7 @@ static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPt string.Format ("warning: Skipping managed constructor invocation for handle 0x{0} (key_handle 0x{1}). " + "Please use JNIEnv.StartCreateInstance() + JNIEnv.FinishCreateInstance() instead of " + "JNIEnv.NewObject() and/or JNIEnv.CreateInstance().", - jobject.ToString ("x"), JNIEnvInit.IdentityHash! (jobject).ToString ("x"))); + jobject.ToString ("x"), JNIEnv.IdentityHash (jobject).ToString ("x"))); } return; } @@ -183,7 +183,7 @@ internal static void Activate (IntPtr jobject, ConstructorInfo cinfo, object? [] cinfo.Invoke (newobj, parms); } catch (Exception e) { var m = string.Format ("Could not activate JNI Handle 0x{0} (key_handle 0x{1}) of Java type '{2}' as managed type '{3}'.", - jobject.ToString ("x"), JNIEnvInit.IdentityHash! (jobject).ToString ("x"), JNIEnv.GetClassNameFromInstance (jobject), cinfo.DeclaringType.FullName); + jobject.ToString ("x"), JNIEnv.IdentityHash (jobject).ToString ("x"), JNIEnv.GetClassNameFromInstance (jobject), cinfo.DeclaringType.FullName); Logger.Log (LogLevel.Warn, "monodroid", m); Logger.Log (LogLevel.Warn, "monodroid", CreateJavaLocationException ().ToString ()); @@ -314,7 +314,7 @@ internal static IJavaPeerable CreateInstance (IntPtr handle, JniHandleOwnership result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable); } } catch (MissingMethodException e) { - var key_handle = JNIEnvInit.IdentityHash! (handle); + var key_handle = JNIEnv.IdentityHash (handle); JNIEnv.DeleteRef (handle, transfer); throw new NotSupportedException ( string.Format ("Unable to activate instance of type {0} from native handle 0x{1} (key_handle 0x{2}).", diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index f93cf1a7801..8d288141c23 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -51,6 +51,7 @@ public class GenerateJavaStubs : AndroidTask [Required] public bool GenerateNativeAssembly { get; set; } + public bool LinkingEnabled { get; set; } public bool EnableMarshalMethods { get; set; } public string ManifestTemplate { get; set; } public string[] MergedManifestDocuments { get; set; } @@ -94,15 +95,15 @@ public class GenerateJavaStubs : AndroidTask public override bool RunTask () { - var readerParams = new ReaderParameters { - ReadWrite = true, - InMemory = true, - }; + // var readerParams = new ReaderParameters { + // ReadWrite = true, + // InMemory = true, + // }; try { // We're going to do 3 steps here instead of separate tasks so // we can share the list of JLO TypeDefinitions between them - using (var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true, loadReaderParameters: readerParams)) { + using (var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true/*, loadReaderParameters: readerParams*/)) { Run (res, useMarshalMethods: !Debug && EnableMarshalMethods); } } catch (XamarinAndroidException e) { @@ -220,11 +221,41 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) return; if (useMarshalMethods) { - // TODO: we must rewrite assemblies for all SupportedAbis. Alternatively, we need to copy the ones that are identical - // Cecil does **not** guarantee that the same assembly modified twice in the same will yield the same result - tokens may differ, so can - // MVID. + var targetPaths = new List (); + + if (!LinkingEnabled) { + targetPaths.Add (Path.GetDirectoryName (ResolvedAssemblies[0].ItemSpec)); + } else { + foreach (string abi in SupportedAbis) { + string rid; + + switch (abi) { + case "arm64-v8a": + rid = "android-arm64"; + break; + + case "armeabi-v7a": + rid = "android-arm"; + break; + + case "x86": + rid = "android-x86"; + break; + + case "x86_64": + rid = "android-x64"; + break; + + default: + throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); + } + + targetPaths.Add (Path.Combine (OutputDirectory, rid)); + } + } + var rewriter = new MarshalMethodsAssemblyRewriter (classifier.MarshalMethods, classifier.Assemblies, marshalMethodsAssemblyPaths, Log); - rewriter.Rewrite (res); + rewriter.Rewrite (res, targetPaths); } // Step 3 - Generate type maps diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index f57cecc94be..391dcf6f1cb 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -25,8 +25,20 @@ public MarshalMethodsAssemblyRewriter (IDictionary targetAssemblyPaths) { + if (resolver == null) { + throw new ArgumentNullException (nameof (resolver)); + } + + if (targetAssemblyPaths == null) { + throw new ArgumentNullException (nameof (targetAssemblyPaths)); + } + + if (targetAssemblyPaths.Count == 0) { + throw new ArgumentException ("must contain at least one target path", nameof (targetAssemblyPaths)); + } + MethodDefinition unmanagedCallersOnlyAttributeCtor = GetUnmanagedCallersOnlyAttributeConstructor (resolver); var unmanagedCallersOnlyAttributes = new Dictionary (); foreach (AssemblyDefinition asm in uniqueAssemblies) { @@ -44,7 +56,6 @@ public void Rewrite (DirectoryAssemblyResolver resolver) continue; } - // TODO: verify again whether the generated code is valid if (method.NeedsBlittableWorkaround) { log.LogDebugMessage ($"Generating non-blittable type wrapper for callback method {method.NativeCallback.FullName}"); method.NativeCallbackWrapper = GenerateBlittableWrapper (method, unmanagedCallersOnlyAttributes); @@ -82,8 +93,13 @@ public void Rewrite (DirectoryAssemblyResolver resolver) WriteSymbols = (File.Exists (path + ".mdb") || File.Exists (Path.ChangeExtension (path, ".pdb"))), }; + File.Copy (path, $"{path}.old"); string output = $"{path}.new"; log.LogDebugMessage ($"Writing new version of assembly: {output}"); + + // TODO: this should be used eventually, but it requires that all the types are reloaded from the assemblies before typemaps are generated + // since Cecil doesn't update the MVID in the already loaded types + //asm.MainModule.Mvid = Guid.NewGuid (); asm.Write (output, writerParams); newAssemblyPaths.Add (output); } @@ -92,31 +108,55 @@ public void Rewrite (DirectoryAssemblyResolver resolver) // Replace old versions of the assemblies only after we've finished rewriting without issues, otherwise leave the new // versions around. foreach (string path in newAssemblyPaths) { - string target = Path.Combine (Path.GetDirectoryName (path), Path.GetFileNameWithoutExtension (path)); - log.LogDebugMessage ($"Copying rewritten assembly: {path} -> {target}"); - MoveFile (path, target); + string? pdb = null; + string? mdb = null; string source = Path.ChangeExtension (path, ".pdb"); if (File.Exists (source)) { - target = Path.ChangeExtension (Path.Combine (Path.GetDirectoryName (source), Path.GetFileNameWithoutExtension (source)), ".pdb"); - - MoveFile (source, target); + pdb = source; } source = $"{path}.mdb"; if (File.Exists (source)) { - target = Path.ChangeExtension (path, ".mdb"); - MoveFile (source, target); + mdb = source; } + + foreach (string targetPath in targetAssemblyPaths) { + string target = Path.Combine (targetPath, Path.GetFileNameWithoutExtension (path)); + CopyFile (path, target); + + if (!String.IsNullOrEmpty (pdb)) { + target = Path.ChangeExtension (Path.Combine (targetPath, Path.GetFileNameWithoutExtension (source)), ".pdb"); + CopyFile (source, target); + } + + if (!String.IsNullOrEmpty (mdb)) { + target = Path.Combine (targetPath, Path.ChangeExtension (Path.GetFileName (path), ".mdb")); + CopyFile (mdb, target); + } + } + + RemoveFile (path); + RemoveFile (pdb); + RemoveFile (mdb); } - void MoveFile (string source, string target) + void CopyFile (string source, string target) { + log.LogDebugMessage ($"Copying rewritten assembly: {source} -> {target}"); Files.CopyIfChanged (source, target); + } + + void RemoveFile (string? path) + { + if (String.IsNullOrEmpty (path) || !File.Exists (path)) { + return; + } + try { - File.Delete (source); + File.Delete (path); } catch (Exception ex) { - log.LogWarning ($"Unable to delete source file '{source}' when moving it to '{target}'"); + log.LogWarning ($"Unable to delete source file '{path}'"); log.LogDebugMessage (ex.ToString ()); } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 3d1b65fa9a6..65902cec1ae 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1473,6 +1473,8 @@ because xbuild doesn't support framework reference assemblies. <_ManifestOutput Condition=" '$(AndroidManifestMerger)' == 'legacy' ">$(IntermediateOutputPath)android\AndroidManifest.xml <_ManifestOutput Condition=" '$(AndroidManifestMerger)' != 'legacy' ">$(IntermediateOutputPath)AndroidManifest.xml + <_LinkingEnabled Condition=" '$(AndroidLinkMode)' != 'None'">True + <_LinkingEnabled Condition=" '$(AndroidLinkMode)' == 'None'">False <_MergedManifestDocuments Condition=" '$(AndroidManifestMerger)' == 'legacy' " Include="@(ExtractedManifestDocuments)" /> @@ -1509,7 +1511,8 @@ because xbuild doesn't support framework reference assemblies. SupportedOSPlatformVersion="$(SupportedOSPlatformVersion)" SkipJniAddNativeMethodRegistrationAttributeScan="$(_SkipJniAddNativeMethodRegistrationAttributeScan)" CheckedBuild="$(_AndroidCheckedBuild)" - EnableMarshalMethods="$(_AndroidUseMarshalMethods)"> + EnableMarshalMethods="$(_AndroidUseMarshalMethods)" + LinkingEnabled="$(_LinkingEnabled)"> diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 8c28b1c71c3..04e2222f4a2 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1133,15 +1133,12 @@ MonodroidRuntime::init_android_runtime ( #endif // def NET method = mono_class_get_method_from_name (runtime, "Initialize", 1); } else { - log_warn (LOG_DEFAULT, "grendel: #1"); runtime = mono_class_get (image, application_config.android_runtime_jnienv_class_token); - log_warn (LOG_DEFAULT, "grendel: #2"); method = mono_get_method (image, application_config.jnienv_initialize_method_token, runtime); - log_warn (LOG_DEFAULT, "grendel: #3"); } - abort_unless (runtime != nullptr, "INTERNAL ERROR: unable to find the Android.Runtime.JNIEnv class!"); - abort_unless (method != nullptr, "INTERNAL ERROR: Unable to find the Android.Runtime.JNIEnv.Initialize method!"); + abort_unless (runtime != nullptr, "INTERNAL ERROR: unable to find the Android.Runtime.JNIEnvInit class!"); + abort_unless (method != nullptr, "INTERNAL ERROR: Unable to find the Android.Runtime.JNIEnvInit.Initialize method!"); MonoAssembly *ji_assm; #if defined (NET) @@ -1170,13 +1167,9 @@ MonodroidRuntime::init_android_runtime ( if constexpr (is_running_on_desktop) { registerType = mono_class_get_method_from_name (runtime, "RegisterJniNatives", 5); } else { - log_warn (LOG_DEFAULT, "grendel: #4"); registerType = mono_get_method (image, application_config.jnienv_registerjninatives_method_token, runtime); - log_warn (LOG_DEFAULT, "grendel: #5"); #if defined (NET) && defined (ANDROID) - log_warn (LOG_DEFAULT, "grendel: #6"); jnienv_register_jni_natives = reinterpret_cast(mono_method_get_unmanaged_callers_only_ftnptr (registerType, &error)); - log_warn (LOG_DEFAULT, "grendel: #7"); #endif // def NET && def ANDROID } } @@ -1196,9 +1189,7 @@ MonodroidRuntime::init_android_runtime ( init.grefLoader = env->NewGlobalRef (loader); init.grefIGCUserPeer = utils.get_class_from_runtime_field (env, runtimeClass, "mono_android_IGCUserPeer", true); - log_warn (LOG_DEFAULT, "grendel: #8"); osBridge.initialize_on_runtime_init (env, runtimeClass); - log_warn (LOG_DEFAULT, "grendel: #9"); log_debug (LOG_DEFAULT, "Calling into managed runtime init"); @@ -1208,9 +1199,7 @@ MonodroidRuntime::init_android_runtime ( } #if defined (NET) && defined (ANDROID) - log_warn (LOG_DEFAULT, "grendel: #10"); auto initialize = reinterpret_cast (mono_method_get_unmanaged_callers_only_ftnptr (method, &error)); - log_warn (LOG_DEFAULT, "grendel: #11"); if (initialize == nullptr) { log_fatal (LOG_DEFAULT, "Failed to get pointer to Initialize. Mono error: %s", mono_error_get_message (&error)); } @@ -1220,9 +1209,7 @@ MonodroidRuntime::init_android_runtime ( "Failed to obtain unmanaged-callers-only pointer to the Android.Runtime.JNIEnvInit.Initialize method. %s", mono_error_get_message (&error) ); - log_warn (LOG_DEFAULT, "grendel: #12"); initialize (&init); - log_warn (LOG_DEFAULT, "grendel: #13"); #else // def NET && def ANDROID void *args [] = { &init, From 2b33563f521028818b149de396422c6a85892087 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 13 Sep 2022 23:56:58 +0200 Subject: [PATCH 52/79] Put the assembly reader settings back in --- .../Tasks/GenerateJavaStubs.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 8d288141c23..3008e1e239e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -95,15 +95,15 @@ public class GenerateJavaStubs : AndroidTask public override bool RunTask () { - // var readerParams = new ReaderParameters { - // ReadWrite = true, - // InMemory = true, - // }; + var readerParams = new ReaderParameters { + ReadWrite = true, + InMemory = true, + }; try { // We're going to do 3 steps here instead of separate tasks so // we can share the list of JLO TypeDefinitions between them - using (var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true/*, loadReaderParameters: readerParams*/)) { + using (var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true, loadReaderParameters: readerParams)) { Run (res, useMarshalMethods: !Debug && EnableMarshalMethods); } } catch (XamarinAndroidException e) { From 73277aa3fb53dd669b1258a489e590fe04dc8388 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 14 Sep 2022 17:36:35 +0200 Subject: [PATCH 53/79] Fix copying of rewritten assemblies --- .../Tasks/GenerateJavaStubs.cs | 32 ++++++++++++------- .../MarshalMethodsAssemblyRewriter.cs | 1 - .../Xamarin.Android.Common.targets | 3 +- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 3008e1e239e..cd83314ff65 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -48,6 +48,9 @@ public class GenerateJavaStubs : AndroidTask [Required] public string TypemapOutputDirectory { get; set; } + [Required] + public string IntermediateOutputDirectory { get; set; } + [Required] public bool GenerateNativeAssembly { get; set; } @@ -95,15 +98,10 @@ public class GenerateJavaStubs : AndroidTask public override bool RunTask () { - var readerParams = new ReaderParameters { - ReadWrite = true, - InMemory = true, - }; - try { // We're going to do 3 steps here instead of separate tasks so // we can share the list of JLO TypeDefinitions between them - using (var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true, loadReaderParameters: readerParams)) { + using (DirectoryAssemblyResolver res = MakeResolver ()) { Run (res, useMarshalMethods: !Debug && EnableMarshalMethods); } } catch (XamarinAndroidException e) { @@ -122,16 +120,28 @@ public override bool RunTask () return !Log.HasLoggedErrors; } - void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) + DirectoryAssemblyResolver MakeResolver () { - PackageNamingPolicy pnp; - JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse (PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseCrc64; + var readerParams = new ReaderParameters { + ReadWrite = true, + InMemory = true, + }; + var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true, loadReaderParameters: readerParams); foreach (var dir in FrameworkDirectories) { - if (Directory.Exists (dir.ItemSpec)) + if (Directory.Exists (dir.ItemSpec)) { res.SearchDirectories.Add (dir.ItemSpec); + } } + return res; + } + + void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) + { + PackageNamingPolicy pnp; + JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse (PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseCrc64; + Dictionary> marshalMethodsAssemblyPaths = null; if (useMarshalMethods) { marshalMethodsAssemblyPaths = new Dictionary> (StringComparer.Ordinal); @@ -250,7 +260,7 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); } - targetPaths.Add (Path.Combine (OutputDirectory, rid)); + targetPaths.Add (Path.Combine (IntermediateOutputDirectory, rid, "linked")); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 391dcf6f1cb..b7e23e286ad 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -93,7 +93,6 @@ public void Rewrite (DirectoryAssemblyResolver resolver, List targetAsse WriteSymbols = (File.Exists (path + ".mdb") || File.Exists (Path.ChangeExtension (path, ".pdb"))), }; - File.Copy (path, $"{path}.old"); string output = $"{path}.new"; log.LogDebugMessage ($"Writing new version of assembly: {output}"); diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index b3f6b73d125..87f7642830b 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1517,7 +1517,8 @@ because xbuild doesn't support framework reference assemblies. SkipJniAddNativeMethodRegistrationAttributeScan="$(_SkipJniAddNativeMethodRegistrationAttributeScan)" CheckedBuild="$(_AndroidCheckedBuild)" EnableMarshalMethods="$(_AndroidUseMarshalMethods)" - LinkingEnabled="$(_LinkingEnabled)"> + LinkingEnabled="$(_LinkingEnabled)" + IntermediateOutputDirectory="$(IntermediateOutputPath)"> From 5b14a7033a4b6456b4c16a7a92e22189e674168c Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 14 Sep 2022 19:59:03 +0200 Subject: [PATCH 54/79] Fix a typo and relax requirements on GenerateJavaStubs.IntermediateOutputDirectory --- .../Tasks/GenerateJavaStubs.cs | 8 +++++--- .../Utilities/MarshalMethodsAssemblyRewriter.cs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index cd83314ff65..7510a917e96 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -48,12 +48,10 @@ public class GenerateJavaStubs : AndroidTask [Required] public string TypemapOutputDirectory { get; set; } - [Required] - public string IntermediateOutputDirectory { get; set; } - [Required] public bool GenerateNativeAssembly { get; set; } + public string IntermediateOutputDirectory { get; set; } public bool LinkingEnabled { get; set; } public bool EnableMarshalMethods { get; set; } public string ManifestTemplate { get; set; } @@ -236,6 +234,10 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) if (!LinkingEnabled) { targetPaths.Add (Path.GetDirectoryName (ResolvedAssemblies[0].ItemSpec)); } else { + if (String.IsNullOrEmpty (IntermediateOutputDirectory)) { + throw new InvalidOperationException ($"Internal error: marshal methods require the `IntermediateOutputDirectory` property of the `GenerateJavaStubs` task to have a value"); + } + foreach (string abi in SupportedAbis) { string rid; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index b7e23e286ad..af9aa9a475d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -126,7 +126,7 @@ public void Rewrite (DirectoryAssemblyResolver resolver, List targetAsse if (!String.IsNullOrEmpty (pdb)) { target = Path.ChangeExtension (Path.Combine (targetPath, Path.GetFileNameWithoutExtension (source)), ".pdb"); - CopyFile (source, target); + CopyFile (pdb, target); } if (!String.IsNullOrEmpty (mdb)) { From 9077b6a4eb8fcb79e4eb1d5bd3a1dbe02e0fd90e Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 14 Sep 2022 23:04:51 +0200 Subject: [PATCH 55/79] Fix unhandled exception propagation --- src/monodroid/jni/monodroid-glue.cc | 2 +- src/monodroid/jni/osbridge.cc | 2 +- src/monodroid/jni/shared-constants.hh | 3 ++- .../Xamarin.Android.RuntimeTests/MainActivity.cs | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 04e2222f4a2..5f3116bd510 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1127,7 +1127,7 @@ MonodroidRuntime::init_android_runtime ( if constexpr (is_running_on_desktop) { #if defined (NET) - runtime = mono_class_from_name (image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME); + runtime = mono_class_from_name (image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME); #else runtime = utils.monodroid_get_class_from_image (domain, image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME); #endif // def NET diff --git a/src/monodroid/jni/osbridge.cc b/src/monodroid/jni/osbridge.cc index d05b0a61947..be021905d6c 100644 --- a/src/monodroid/jni/osbridge.cc +++ b/src/monodroid/jni/osbridge.cc @@ -1143,7 +1143,7 @@ OSBridge::add_monodroid_domain (MonoDomain *domain) * use GC API to allocate memory and thus can't be called from within the GC callback as it causes a deadlock * (the routine allocating the memory waits for the GC round to complete first) */ - MonoClass *jnienv = utils.monodroid_get_class_from_name (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME);; + MonoClass *jnienv = utils.monodroid_get_class_from_name (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME);; node->domain = domain; node->bridge_processing_field = mono_class_get_field_from_name (jnienv, const_cast ("BridgeProcessing")); node->jnienv_vtable = mono_class_vtable (domain, jnienv); diff --git a/src/monodroid/jni/shared-constants.hh b/src/monodroid/jni/shared-constants.hh index f2c1aa881c2..7d0e662995f 100644 --- a/src/monodroid/jni/shared-constants.hh +++ b/src/monodroid/jni/shared-constants.hh @@ -21,7 +21,8 @@ namespace xamarin::android::internal static constexpr char JAVA_INTEROP_ASSEMBLY_NAME[] = "Java.Interop"; static constexpr char ANDROID_RUNTIME_NS_NAME[] = "Android.Runtime"; - static constexpr char JNIENV_CLASS_NAME[] = "JNIEnvInit"; + static constexpr char JNIENVINIT_CLASS_NAME[] = "JNIEnvInit"; + static constexpr char JNIENV_CLASS_NAME[] = "JNIEnv"; static constexpr char ANDROID_ENVIRONMENT_CLASS_NAME[] = "AndroidEnvironment"; static constexpr char DLL_EXTENSION[] = ".dll"; diff --git a/tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/MainActivity.cs b/tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/MainActivity.cs index 9bfe8087a5d..06bdfccaf49 100644 --- a/tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/MainActivity.cs +++ b/tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/MainActivity.cs @@ -7,7 +7,7 @@ namespace Xamarin.Android.RuntimeTests { - [Activity (Label = "runtime", MainLauncher = true, + [Activity (Label = "Mono.Android Tests", MainLauncher = true, Name="xamarin.android.runtimetests.MainActivity")] public partial class MainActivity : TestSuiteActivity { From 32347b79ad5d6328186861334063ae6e04b21940 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 14 Sep 2022 23:36:42 +0200 Subject: [PATCH 56/79] Fix builds of apps with linking and just a single RID --- .../Tasks/GenerateJavaStubs.cs | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 7510a917e96..2a4f76e2110 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -238,31 +238,37 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) throw new InvalidOperationException ($"Internal error: marshal methods require the `IntermediateOutputDirectory` property of the `GenerateJavaStubs` task to have a value"); } - foreach (string abi in SupportedAbis) { - string rid; + string baseDir; - switch (abi) { - case "arm64-v8a": - rid = "android-arm64"; - break; + if (SupportedAbis.Length == 1) { + targetPaths.Add (Path.Combine (IntermediateOutputDirectory, "linked")); + } else { + foreach (string abi in SupportedAbis) { + string rid; - case "armeabi-v7a": - rid = "android-arm"; - break; + switch (abi) { + case "arm64-v8a": + rid = "android-arm64"; + break; - case "x86": - rid = "android-x86"; - break; + case "armeabi-v7a": + rid = "android-arm"; + break; - case "x86_64": - rid = "android-x64"; - break; + case "x86": + rid = "android-x86"; + break; - default: - throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); - } + case "x86_64": + rid = "android-x64"; + break; + + default: + throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); + } - targetPaths.Add (Path.Combine (IntermediateOutputDirectory, rid, "linked")); + targetPaths.Add (Path.Combine (IntermediateOutputDirectory, rid, "linked")); + } } } From ddedcc32173e531c27a35657d5a26dd4a9459f0b Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 15 Sep 2022 23:15:29 +0200 Subject: [PATCH 57/79] [WIP] Investigating a Mono.Android crash 9-15 22:56:28.431 15070 15090 W monodroid: jclass java_interop_jnienv_find_class(JNIEnv *, jthrowable *, const char *) looking for 'android/content/Intent$ShortcutIconResource' --------- beginning of crash 09-15 22:56:28.432 15070 15090 F libc : Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xbb60ca20 in tid 15090 (Instrumentation), pid 15070 (droid.NET_Tests) 09-15 22:56:28.757 15103 15103 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 09-15 22:56:28.757 15103 15103 F DEBUG : Build fingerprint: 'google/raven/raven:13/TP1A.220624.021/8877034:user/release-keys' 09-15 22:56:28.757 15103 15103 F DEBUG : Revision: 'MP1.0' 09-15 22:56:28.757 15103 15103 F DEBUG : ABI: 'arm64' 09-15 22:56:28.757 15103 15103 F DEBUG : Timestamp: 2022-09-15 22:56:28.545823727+0200 09-15 22:56:28.757 15103 15103 F DEBUG : Process uptime: 3s 09-15 22:56:28.757 15103 15103 F DEBUG : Cmdline: Mono.Android.NET_Tests 09-15 22:56:28.757 15103 15103 F DEBUG : pid: 15070, tid: 15090, name: Instrumentation >>> Mono.Android.NET_Tests <<< 09-15 22:56:28.757 15103 15103 F DEBUG : uid: 10638 09-15 22:56:28.757 15103 15103 F DEBUG : tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE) 09-15 22:56:28.757 15103 15103 F DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x00000000bb60ca20 09-15 22:56:28.757 15103 15103 F DEBUG : x0 b400006e8ea08350 x1 0000000000000001 x2 0000000000000001 x3 0000000000000010 09-15 22:56:28.757 15103 15103 F DEBUG : x4 0000006dbaea7233 x5 0000006deea682be x6 0000000000000020 x7 0000000000000020 09-15 22:56:28.757 15103 15103 F DEBUG : x8 0000000000000008 x9 00000000bb60c9e0 x10 0000006deea682a7 x11 0000006deea682a7 09-15 22:56:28.757 15103 15103 F DEBUG : x12 3d2120746e696f70 x13 7274706c6c756e20 x14 0000006d3d90b998 x15 000000a74ab3008e 09-15 22:56:28.757 15103 15103 F DEBUG : x16 0000006dbb60f6b8 x17 0000007074e7b9ac x18 0000006d394cc000 x19 0000006d3d90b6d8 09-15 22:56:28.757 15103 15103 F DEBUG : x20 b400006e8ea08350 x21 0000000000000001 x22 0000006d3d90e000 x23 0000006d3d90b6d8 09-15 22:56:28.757 15103 15103 F DEBUG : x24 0000006d3d90b830 x25 0000006d3d90bb80 x26 0000006e7ea14cf0 x27 b400006e8ea08350 09-15 22:56:28.757 15103 15103 F DEBUG : x28 0000000000000000 x29 0000006d3d90b5d0 09-15 22:56:28.757 15103 15103 F DEBUG : lr 0000006dbb16f3dc sp 0000006d3d90b5b0 pc 0000006dbb171d44 pst 0000000080001000 09-15 22:56:28.757 15103 15103 F DEBUG : backtrace: 09-15 22:56:28.757 15103 15103 F DEBUG : #00 pc 0000000000371d44 /apex/com.android.art/lib64/libart.so (art::ArtMethod::PrettyMethod(bool)+76) (BuildId: 56e704c544e6c624201be2ab4933e853) 09-15 22:56:28.757 15103 15103 F DEBUG : #01 pc 000000000036f3d8 /apex/com.android.art/lib64/libart.so (void art::StackVisitor::WalkStack<(art::StackVisitor::CountTransitions)0>(bool)+5464) (BuildId: 56e704c544e6c624201be2ab4933e853) 09-15 22:56:28.757 15103 15103 F DEBUG : #02 pc 00000000005a0370 /apex/com.android.art/lib64/libart.so (art::JNI::FindClass(_JNIEnv*, char const*)+480) (BuildId: 56e704c544e6c624201be2ab4933e853) 09-15 22:56:28.757 15103 15103 F DEBUG : #03 pc 00000000005c8948 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::CheckJNI::FindClass(_JNIEnv*, char const*) (.__uniq.99033978352804627313491551960229047428.llvm.5591279935177935698)+228) (BuildId: 56e704c544e6c624201be2ab4933e853) 09-15 22:56:28.757 15103 15103 F DEBUG : #04 pc 000000000004de28 /data/app/~~aJedheWQLPfk1ulUOfKVyg==/Mono.Android.NET_Tests-XvAL5W7BvZDwkEbYfmLTIQ==/lib/arm64/libmonodroid.so (java_interop_jnienv_find_class+84) (BuildId: a92c56b31adcb233a4674b5eb523c0aaa67a811d) 09-15 22:56:28.757 15103 15103 F DEBUG : #05 pc 000000000000b220 --- src/Mono.Android/Android.Runtime/JNIEnv.cs | 8 +++++++- src/monodroid/jni/java_interop_api.c | 6 ++++++ src/monodroid/jni/xamarin-android-app-context.cc | 10 +++++++++- .../Java.Interop-Tests/Java.Interop-Tests.NET.csproj | 1 + .../Runtime-Microsoft.Android.Sdk/AndroidManifest.xml | 2 +- .../Mono.Android.NET-Tests.csproj | 1 + 6 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 17d9d3d46bf..f7d85fe96fb 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -420,15 +420,21 @@ public static unsafe void FinishCreateInstance (IntPtr instance, string jniCtorS public static unsafe void InvokeConstructor (IntPtr instance, string jniCtorSignature, JValue* constructorParameters) { IntPtr lrefClass = GetObjectClass (instance); + monodroid_log (LogLevel.Warn, LogCategories.Default, $"InvokeConstructor: lrefClass == {lrefClass}"); try { IntPtr ctor = JNIEnv.GetMethodID (lrefClass, "", jniCtorSignature); if (ctor == IntPtr.Zero) throw new ArgumentException (string.Format ("Could not find constructor JNI signature '{0}' on type '{1}'.", jniCtorSignature, Java.Interop.TypeManager.GetClassName (lrefClass))); CallNonvirtualVoidMethod (instance, lrefClass, ctor, constructorParameters); + } catch (Exception ex) { + monodroid_log (LogLevel.Warn, LogCategories.Default, $"Exception in InvokeConstructor: {ex.GetType()}"); +// throw; } finally { - DeleteLocalRef (lrefClass); + //DeleteLocalRef (lrefClass); } + +// DeleteLocalRef (lrefClass); } public static unsafe void InvokeConstructor (IntPtr instance, string jniCtorSignature, params JValue[] constructorParameters) diff --git a/src/monodroid/jni/java_interop_api.c b/src/monodroid/jni/java_interop_api.c index c4602190919..3d91778b1af 100644 --- a/src/monodroid/jni/java_interop_api.c +++ b/src/monodroid/jni/java_interop_api.c @@ -5,6 +5,9 @@ */ #include "java_interop_api.h" +#ifdef ANDROID +#include +#endif JI_API jint java_interop_jnienv_get_version (JNIEnv *env) @@ -25,6 +28,9 @@ java_interop_jnienv_define_class (JNIEnv *env, jthrowable *_thrown, const char* JI_API jclass java_interop_jnienv_find_class (JNIEnv *env, jthrowable *_thrown, const char* classname) { +#if defined (ANDROID) && defined (NET) + __android_log_print (ANDROID_LOG_WARN, "monodroid", "%s looking for '%s'", __PRETTY_FUNCTION__, classname); +#endif *_thrown = 0; jclass _r_ = (*env)->FindClass (env, classname); *_thrown = (*env)->ExceptionOccurred (env); diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index e5bdb073ae8..fe11f8e541f 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -1,4 +1,5 @@ #include +#include #include "monodroid-glue-internal.hh" #include "mono-image-loader.hh" @@ -45,6 +46,7 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas get_class_name (class_index), class_index ); + log_warn (LOG_DEFAULT, " mono_image_index == %u; class_index == %u; method_token == 0x%x", mono_image_index, class_index, method_token); if (XA_UNLIKELY (class_index >= marshal_methods_number_of_classes)) { log_fatal (LOG_DEFAULT, "Internal error: invalid index for class cache (expected at most %u, got %u)", @@ -54,6 +56,9 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas abort (); } + // We need to do that, as Mono APIs cannot be invoked from threads that aren't attached to the runtime. + mono_thread_attach (mono_get_root_domain()); + // We don't check for valid return values from image loader, class and method lookup because if any // of them fails to find the requested entity, they will return `null`. In consequence, we can pass // these pointers without checking all the way to `mono_method_get_unmanaged_callers_only_ftnptr`, after @@ -66,7 +71,10 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas MonoMethod *method = mono_get_method (image, method_token, klass.klass); MonoError error; + log_warn (LOG_DEFAULT, " method == %p (mono_image_index == %u; class_index == %u; method_token == 0x%x)", method, mono_image_index, class_index, method_token); + log_warn (LOG_DEFAULT, " pointer to method %s == %p, trying to get funcptr for it (mono_image_index == %u; class_index == %u; method_token == 0x%x)", mono_method_full_name (method, true), method, mono_image_index, class_index, method_token); void *ret = mono_method_get_unmanaged_callers_only_ftnptr (method, &error); + log_warn (LOG_DEFAULT, " obtained pointer == %p (mono_image_index == %u; class_index == %u; method_token == 0x%x)", ret, mono_image_index, class_index, method_token); if (XA_LIKELY (ret != nullptr)) { if constexpr (NeedsLocking) { @@ -75,7 +83,7 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas target_ptr = ret; } - log_debug (LOG_ASSEMBLY, "Loaded pointer to method %s (%p)", mono_method_get_name (method), ret); + log_debug (LOG_ASSEMBLY, "Loaded pointer to method %s (%p) (mono_image_index == %u; class_index == %u; method_token == 0x%x)", mono_method_full_name (method, true), ret, mono_image_index, class_index, method_token); return; } diff --git a/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj b/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj index 07e28916acc..f9a8feab9a3 100644 --- a/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj +++ b/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj @@ -24,6 +24,7 @@ + diff --git a/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/AndroidManifest.xml b/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/AndroidManifest.xml index aae3d489b4b..4a0f610906d 100644 --- a/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/AndroidManifest.xml +++ b/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/AndroidManifest.xml @@ -1,6 +1,6 @@  - + diff --git a/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/Mono.Android.NET-Tests.csproj b/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/Mono.Android.NET-Tests.csproj index 4543d58f976..238a81550b7 100644 --- a/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/Mono.Android.NET-Tests.csproj +++ b/tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/Mono.Android.NET-Tests.csproj @@ -41,6 +41,7 @@ + From 2aa7cfeddda0b147dc79998b659b8f2d57cbff3c Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 16 Sep 2022 22:25:05 +0200 Subject: [PATCH 58/79] Mono.Android.NET_Tests crash investigations continued Cause of the crash is know - we need to generate a `try/catch` wrapper around each native call. This will have to wait till the next week. In the meantime, a handful of tweaks and fixes which came up while investigating. --- src/Mono.Android/Android.Runtime/JNIEnv.cs | 8 +-- src/Mono.Android/Java.Interop/TypeManager.cs | 10 ++-- .../Tasks/GenerateJavaStubs.cs | 53 ++++++++----------- .../LlvmIrGenerator/LlvmIrGenerator.Code.cs | 8 ++- .../MarshalMethodsAssemblyRewriter.cs | 7 +-- .../MarshalMethodsNativeAssemblyGenerator.cs | 4 +- src/monodroid/jni/java_interop_api.c | 6 --- src/monodroid/jni/monodroid-glue-internal.hh | 6 +-- .../jni/xamarin-android-app-context.cc | 12 ++--- src/monodroid/jni/xamarin-app.hh | 3 +- 10 files changed, 55 insertions(+), 62 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index f7d85fe96fb..17d9d3d46bf 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -420,21 +420,15 @@ public static unsafe void FinishCreateInstance (IntPtr instance, string jniCtorS public static unsafe void InvokeConstructor (IntPtr instance, string jniCtorSignature, JValue* constructorParameters) { IntPtr lrefClass = GetObjectClass (instance); - monodroid_log (LogLevel.Warn, LogCategories.Default, $"InvokeConstructor: lrefClass == {lrefClass}"); try { IntPtr ctor = JNIEnv.GetMethodID (lrefClass, "", jniCtorSignature); if (ctor == IntPtr.Zero) throw new ArgumentException (string.Format ("Could not find constructor JNI signature '{0}' on type '{1}'.", jniCtorSignature, Java.Interop.TypeManager.GetClassName (lrefClass))); CallNonvirtualVoidMethod (instance, lrefClass, ctor, constructorParameters); - } catch (Exception ex) { - monodroid_log (LogLevel.Warn, LogCategories.Default, $"Exception in InvokeConstructor: {ex.GetType()}"); -// throw; } finally { - //DeleteLocalRef (lrefClass); + DeleteLocalRef (lrefClass); } - -// DeleteLocalRef (lrefClass); } public static unsafe void InvokeConstructor (IntPtr instance, string jniCtorSignature, params JValue[] constructorParameters) diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index afe6b211c05..f362e2241b0 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -379,7 +379,7 @@ public static void RegisterPackage (string package, Converter look { LazyInitPackageLookup (); - lock (packageLookup) { + lock (packageLookup!) { if (!packageLookup.TryGetValue (package, out var lookups)) packageLookup.Add (package, lookups = new List> ()); lookups.Add (lookup); @@ -397,7 +397,7 @@ public static void RegisterPackages (string[] packages, Converter if (packages.Length != lookups.Length) throw new ArgumentException ("`packages` and `lookups` arrays must have same number of elements."); - lock (packageLookup) { + lock (packageLookup!) { for (int i = 0; i < packages.Length; ++i) { string package = packages [i]; var lookup = lookups [i]; @@ -422,7 +422,11 @@ static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPt [UnmanagedCallersOnly] static void n_Activate_mm (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) { - TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); + try { + TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); + } catch (Exception ex) { + AndroidEnvironment.UnhandledException (ex); + } } #endif internal static Delegate GetActivateHandler () diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 2a4f76e2110..d1e8f4818cc 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -238,37 +238,8 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) throw new InvalidOperationException ($"Internal error: marshal methods require the `IntermediateOutputDirectory` property of the `GenerateJavaStubs` task to have a value"); } - string baseDir; - - if (SupportedAbis.Length == 1) { - targetPaths.Add (Path.Combine (IntermediateOutputDirectory, "linked")); - } else { - foreach (string abi in SupportedAbis) { - string rid; - - switch (abi) { - case "arm64-v8a": - rid = "android-arm64"; - break; - - case "armeabi-v7a": - rid = "android-arm"; - break; - - case "x86": - rid = "android-x86"; - break; - - case "x86_64": - rid = "android-x64"; - break; - - default: - throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); - } - - targetPaths.Add (Path.Combine (IntermediateOutputDirectory, rid, "linked")); - } + foreach (string abi in SupportedAbis) { + targetPaths.Add (Path.Combine (IntermediateOutputDirectory, AbiToRid (abi), "linked")); } } @@ -446,6 +417,26 @@ void StoreMarshalAssemblyPath (string name, ITaskItem asm) assemblyPaths.Add (asm.ItemSpec); } + + string AbiToRid (string abi) + { + switch (abi) { + case "arm64-v8a": + return "android-arm64"; + + case "armeabi-v7a": + return "android-arm"; + + case "x86": + return "android-x86"; + + case "x86_64": + return "android-x64"; + + default: + throw new InvalidOperationException ($"Internal error: unsupported ABI '{abi}'"); + } + } } bool CreateJavaSources (IEnumerable javaTypes, TypeDefinitionCache cache, MarshalMethodsClassifier classifier, bool useMarshalMethods) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs index d5537296a7e..85b9662c524 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.Code.cs @@ -26,7 +26,7 @@ abstract partial class LlvmIrGenerator /// /// Writes the function definition up to the opening curly brace /// - public void WriteFunctionStart (LlvmIrFunction function) + public void WriteFunctionStart (LlvmIrFunction function, string? comment = null) { if (function == null) { throw new ArgumentNullException (nameof (function)); @@ -38,6 +38,12 @@ public void WriteFunctionStart (LlvmIrFunction function) } Output.WriteLine (); + if (!String.IsNullOrEmpty (comment)) { + foreach (string line in comment.Split ('\n')) { + WriteCommentLine (line); + } + } + if (attributes != null) { WriteCommentLine ($"Function attributes: {attributes.Render ()}"); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index af9aa9a475d..c60819eab77 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -93,6 +93,7 @@ public void Rewrite (DirectoryAssemblyResolver resolver, List targetAsse WriteSymbols = (File.Exists (path + ".mdb") || File.Exists (Path.ChangeExtension (path, ".pdb"))), }; + string output = $"{path}.new"; log.LogDebugMessage ($"Writing new version of assembly: {output}"); @@ -110,7 +111,7 @@ public void Rewrite (DirectoryAssemblyResolver resolver, List targetAsse string? pdb = null; string? mdb = null; - string source = Path.ChangeExtension (path, ".pdb"); + string source = Path.ChangeExtension (Path.Combine (Path.GetDirectoryName (path), Path.GetFileNameWithoutExtension (path)), ".pdb"); if (File.Exists (source)) { pdb = source; } @@ -125,7 +126,7 @@ public void Rewrite (DirectoryAssemblyResolver resolver, List targetAsse CopyFile (path, target); if (!String.IsNullOrEmpty (pdb)) { - target = Path.ChangeExtension (Path.Combine (targetPath, Path.GetFileNameWithoutExtension (source)), ".pdb"); + target = Path.ChangeExtension (Path.Combine (targetPath, Path.GetFileNameWithoutExtension (pdb)), ".pdb"); CopyFile (pdb, target); } @@ -143,7 +144,7 @@ public void Rewrite (DirectoryAssemblyResolver resolver, List targetAsse void CopyFile (string source, string target) { log.LogDebugMessage ($"Copying rewritten assembly: {source} -> {target}"); - Files.CopyIfChanged (source, target); + File.Copy (source, target, true); } void RemoveFile (string? path) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 8f46484e04b..2dc932b3951 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -686,7 +686,7 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll parameters: method.Parameters ); - generator.WriteFunctionStart (func); + generator.WriteFunctionStart (func, $"Method: {nativeCallback.FullName}\nAssembly: {nativeCallback.Module.Assembly.Name}"); LlvmIrFunctionLocalVariable callbackVariable1 = generator.EmitLoadInstruction (func, backingFieldRef, "cb1"); var callbackVariable1Ref = new LlvmIrVariableReference (callbackVariable1, isGlobal: false); @@ -708,6 +708,7 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll func, getFunctionPtrRef, new List { + new LlvmIrFunctionArgument (func.ParameterVariables[0]), new LlvmIrFunctionArgument (typeof(uint), method.AssemblyCacheIndex), new LlvmIrFunctionArgument (typeof(uint), method.ClassCacheIndex), new LlvmIrFunctionArgument (typeof(uint), nativeCallback.MetadataToken.ToUInt32 ()), @@ -752,6 +753,7 @@ LlvmIrVariableReference WriteXamarinAppInitFunction (LlvmIrGenerator generator) var get_function_pointer_sig = new LlvmNativeFunctionSignature ( returnType: typeof(void), parameters: new List { + new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true), new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), new LlvmIrFunctionParameter (typeof(uint), "class_index"), new LlvmIrFunctionParameter (typeof(uint), "method_token"), diff --git a/src/monodroid/jni/java_interop_api.c b/src/monodroid/jni/java_interop_api.c index 3d91778b1af..c4602190919 100644 --- a/src/monodroid/jni/java_interop_api.c +++ b/src/monodroid/jni/java_interop_api.c @@ -5,9 +5,6 @@ */ #include "java_interop_api.h" -#ifdef ANDROID -#include -#endif JI_API jint java_interop_jnienv_get_version (JNIEnv *env) @@ -28,9 +25,6 @@ java_interop_jnienv_define_class (JNIEnv *env, jthrowable *_thrown, const char* JI_API jclass java_interop_jnienv_find_class (JNIEnv *env, jthrowable *_thrown, const char* classname) { -#if defined (ANDROID) && defined (NET) - __android_log_print (ANDROID_LOG_WARN, "monodroid", "%s looking for '%s'", __PRETTY_FUNCTION__, classname); -#endif *_thrown = 0; jclass _r_ = (*env)->FindClass (env, classname); *_thrown = (*env)->ExceptionOccurred (env); diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index 598f51372be..ec4aa559c0e 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -355,9 +355,9 @@ namespace xamarin::android::internal static const char* get_class_name (uint32_t class_index) noexcept; template - 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 (JNIEnv *env, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept; + static void get_function_pointer_at_startup (JNIEnv *env, uint32_t mono_image_index, uint32_t class_token, uint32_t method_token, void*& target_ptr) noexcept; + static void get_function_pointer_at_runtime (JNIEnv *env, uint32_t mono_image_index, uint32_t class_token, uint32_t method_token, void*& target_ptr) noexcept; #endif // def RELEASE && def ANDROID #endif // def NET diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index fe11f8e541f..d45ccf00bfd 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -37,7 +37,7 @@ MonodroidRuntime::get_class_name (uint32_t class_index) noexcept template force_inline void -MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept +MonodroidRuntime::get_function_pointer (JNIEnv *env, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept { log_debug ( LOG_ASSEMBLY, @@ -57,7 +57,7 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas } // We need to do that, as Mono APIs cannot be invoked from threads that aren't attached to the runtime. - mono_thread_attach (mono_get_root_domain()); + mono_thread_attach (mono_get_root_domain ()); // We don't check for valid return values from image loader, class and method lookup because if any // of them fails to find the requested entity, they will return `null`. In consequence, we can pass @@ -119,13 +119,13 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas } void -MonodroidRuntime::get_function_pointer_at_startup (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept +MonodroidRuntime::get_function_pointer_at_startup (JNIEnv *env, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept { - get_function_pointer (mono_image_index, class_index, method_token, target_ptr); + get_function_pointer (env, mono_image_index, class_index, method_token, target_ptr); } void -MonodroidRuntime::get_function_pointer_at_runtime (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept +MonodroidRuntime::get_function_pointer_at_runtime (JNIEnv *env, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept { - get_function_pointer (mono_image_index, class_index, method_token, target_ptr); + get_function_pointer (env, mono_image_index, class_index, method_token, target_ptr); } diff --git a/src/monodroid/jni/xamarin-app.hh b/src/monodroid/jni/xamarin-app.hh index 7cd8556a933..cabd14d7338 100644 --- a/src/monodroid/jni/xamarin-app.hh +++ b/src/monodroid/jni/xamarin-app.hh @@ -4,6 +4,7 @@ #include +#include #include #include "monodroid.h" @@ -349,7 +350,7 @@ struct MarshalMethodName MONO_API MONO_API_EXPORT const char* const mm_class_names[]; MONO_API MONO_API_EXPORT const MarshalMethodName mm_method_names[]; -using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr); +using get_function_pointer_fn = void(*)(JNIEnv *env, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr); MONO_API MONO_API_EXPORT void xamarin_app_init (get_function_pointer_fn fn) noexcept; #endif // def RELEASE && def ANDROID && def NET From 92fb339141e3b471e30a98572b20ee13896ab813 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 16 Sep 2022 22:27:25 +0200 Subject: [PATCH 59/79] TODO --- src/Mono.Android/Java.Interop/TypeManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index f362e2241b0..4ef9b26ca4f 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -422,6 +422,7 @@ static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPt [UnmanagedCallersOnly] static void n_Activate_mm (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) { + // TODO: need a full wrapper code here, a'la JNINativeWrapper.CreateDelegate try { TypeManager.n_Activate (jnienv, jclass, typename_ptr, signature_ptr, jobject, parameters_ptr); } catch (Exception ex) { From 170b5d1db68d8b69a7c6e46eb395065bf9accc8f Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 19 Sep 2022 23:52:41 +0200 Subject: [PATCH 60/79] Factor out bits of code to a separate assembly This is step one towards generating correct exception handling code in marshal methods. --- Xamarin.Android.sln | 9 + .../AndroidEnvironmentInternal.cs | 18 ++ .../Mono.Android.Runtime.csproj | 110 +++++++++ .../Properties/AssemblyInfo.cs.in | 22 ++ .../Android.Runtime/AndroidEnvironment.cs | 22 +- .../Android.Runtime/AndroidRuntime.cs | 51 ++-- .../Android.Runtime/BoundExceptionType.cs | 2 +- src/Mono.Android/Android.Runtime/JNIEnv.cs | 219 ++---------------- .../Android.Runtime/JNIEnvInit.cs | 28 ++- .../Android.Runtime/JNINativeWrapper.cs | 6 +- .../Android.Runtime/JNINativeWrapper.g.cs | 2 +- .../Android.Runtime/JNINativeWrapper.g.tt | 2 +- .../Android.Runtime/LogCategories.cs | 23 ++ src/Mono.Android/Android.Runtime/LogLevel.cs | 18 ++ src/Mono.Android/Android.Runtime/Logger.cs | 33 +-- .../Android.Runtime/RuntimeConstants.cs | 9 + .../Android.Runtime/RuntimeNativeMethods.cs | 89 +++++++ .../Android.Runtime/TimingLogger.cs | 6 +- src/Mono.Android/Java.Interop/Runtime.cs | 10 +- src/Mono.Android/Java.Interop/TypeManager.cs | 7 +- src/Mono.Android/Java.Lang/Object.cs | 2 +- src/Mono.Android/Mono.Android.csproj | 8 + src/monodroid/jni/monodroid-glue.cc | 8 +- src/monodroid/jni/osbridge.cc | 12 +- src/monodroid/jni/shared-constants.hh | 3 + 25 files changed, 405 insertions(+), 314 deletions(-) create mode 100644 src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs create mode 100644 src/Mono.Android.Runtime/Mono.Android.Runtime.csproj create mode 100644 src/Mono.Android.Runtime/Properties/AssemblyInfo.cs.in create mode 100644 src/Mono.Android/Android.Runtime/LogCategories.cs create mode 100644 src/Mono.Android/Android.Runtime/LogLevel.cs create mode 100644 src/Mono.Android/Android.Runtime/RuntimeConstants.cs create mode 100644 src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln index d0f508ac92e..baba28b1734 100644 --- a/Xamarin.Android.sln +++ b/Xamarin.Android.sln @@ -92,6 +92,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Android", "src\Mono.An EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Android.Export", "src\Mono.Android.Export\Mono.Android.Export.csproj", "{B8105878-D423-4159-A3E7-028298281EC6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Android.Runtime", "src\Mono.Android.Runtime\Mono.Android.Runtime.csproj", "{43564FB3-0F79-4FF4-A2B0-B1637072FF01}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Android.Build.BaseTasks", "external\xamarin-android-tools\src\Microsoft.Android.Build.BaseTasks\Microsoft.Android.Build.BaseTasks.csproj", "{3DE17662-DCD6-4F49-AF06-D39AACC8649A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Tools.AndroidSdk", "external\xamarin-android-tools\src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj", "{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}" @@ -296,6 +298,12 @@ Global {B8105878-D423-4159-A3E7-028298281EC6}.Debug|AnyCPU.Build.0 = Debug|Any CPU {B8105878-D423-4159-A3E7-028298281EC6}.Release|AnyCPU.ActiveCfg = Release|Any CPU {B8105878-D423-4159-A3E7-028298281EC6}.Release|AnyCPU.Build.0 = Release|Any CPU + + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Debug|AnyCPU.Build.0 = Debug|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Release|AnyCPU.ActiveCfg = Release|Any CPU + {43564FB3-0F79-4FF4-A2B0-B1637072FF01}.Release|AnyCPU.Build.0 = Release|Any CPU + {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Debug|AnyCPU.Build.0 = Debug|Any CPU {3DE17662-DCD6-4F49-AF06-D39AACC8649A}.Release|AnyCPU.ActiveCfg = Release|Any CPU @@ -456,6 +464,7 @@ Global {73DF9E10-E933-4222-B8E1-F4536FFF9FAD} = {864062D3-A415-4A6F-9324-5820237BA058} {66CF299A-CE95-4131-BCD8-DB66E30C4BF7} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {B8105878-D423-4159-A3E7-028298281EC6} = {04E3E11E-B47D-4599-8AFC-50515A95E715} + {43564FB3-0F79-4FF4-A2B0-B1637072FF01} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {3DE17662-DCD6-4F49-AF06-D39AACC8649A} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {1E5501E8-49C1-4659-838D-CC9720C5208F} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483} diff --git a/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs b/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs new file mode 100644 index 00000000000..198e6c92423 --- /dev/null +++ b/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs @@ -0,0 +1,18 @@ +using System; + +namespace Android.Runtime +{ + internal static class AndroidEnvironmentInternal + { + internal static Action? UnhandledExceptionHandler; + + internal static void UnhandledException (Exception e) + { + if (UnhandledExceptionHandler == null) { + return; + } + + UnhandledExceptionHandler (e); + } + } +} diff --git a/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj b/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj new file mode 100644 index 00000000000..f32de972aab --- /dev/null +++ b/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj @@ -0,0 +1,110 @@ + + + + + + + + $(DotNetTargetFramework) + true + false + ..\..\product.snk + false + true + false + false + 10 + true + enable + true + true + + + $(NoWarn);CS0169;CS0414;CS0649 + + $(DefineConstants);INSIDE_MONO_ANDROID_RUNTIME;JAVA_INTEROP + + + + $(_MonoAndroidNETDefaultOutDir) + + + + + + + + + + + + + $(BuildDependsOn); + _CopyToPackDirs; + + + + + + + + + + + + + + + + + + + + <_PackageVersion>$(ProductVersion) + <_PackageVersionBuild>$(XAVersionCommitCount) + + + <_PackageVersion>$(AndroidPackVersion) + <_PackageVersionBuild>$(PackVersionCommitCount) + + + + + + + + + + + + + + diff --git a/src/Mono.Android.Runtime/Properties/AssemblyInfo.cs.in b/src/Mono.Android.Runtime/Properties/AssemblyInfo.cs.in new file mode 100644 index 00000000000..53edcd5fd39 --- /dev/null +++ b/src/Mono.Android.Runtime/Properties/AssemblyInfo.cs.in @@ -0,0 +1,22 @@ +// +// AssemblyInfo.cs.in +// +// Authors: +// Jonathan Pryor (jonp@xamarin.com) +// +// Copyright 2014 Xamarin, Inc. +// Copyright 2016 Microsoft Corporation. + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; + +[assembly: AssemblyInformationalVersion ("@PACKAGE_VERSION@.@PACKAGE_VERSION_BUILD@; git-rev-head:@PACKAGE_HEAD_REV@; git-branch:@PACKAGE_HEAD_BRANCH@")] +[assembly: AssemblyTitle ("Mono.Android.Runtime.dll")] +[assembly: AssemblyProduct ("Xamarin.Android")] +[assembly: AssemblyCompany ("Microsoft Corporation")] +[assembly: AssemblyMetadata ("IsTrimmable", "False")] +[assembly: TargetPlatform("Android@API_LEVEL@.0")] +[assembly: SupportedOSPlatform("Android@MIN_API_LEVEL@.0")] + +[assembly: InternalsVisibleTo("Mono.Android, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")] diff --git a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs index ea5368a2f51..dda2dea521e 100644 --- a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs +++ b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs @@ -264,17 +264,14 @@ static void GetDisplayDPI (out float x_dpi, out float y_dpi) // static string GetDefaultTimeZone () { - IntPtr id = _monodroid_timezone_get_default_id (); + IntPtr id = RuntimeNativeMethods._monodroid_timezone_get_default_id (); try { return Marshal.PtrToStringAnsi (id)!; } finally { - JNIEnv.monodroid_free (id); + RuntimeNativeMethods.monodroid_free (id); } } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr _monodroid_timezone_get_default_id (); - // This is invoked by // mscorlib.dll!System.AndroidPlatform.GetDefaultSyncContext() // DO NOT REMOVE @@ -293,35 +290,26 @@ static string GetDefaultTimeZone () // These are invoked by // System.dll!System.AndroidPlatform.getifaddrs // DO NOT REMOVE - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern int _monodroid_getifaddrs (out IntPtr ifap); - static int GetInterfaceAddresses (out IntPtr ifap) { - return _monodroid_getifaddrs (out ifap); + return RuntimeNativeMethods._monodroid_getifaddrs (out ifap); } // These are invoked by // System.dll!System.AndroidPlatform.freeifaddrs // DO NOT REMOVE - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern void _monodroid_freeifaddrs (IntPtr ifap); - static void FreeInterfaceAddresses (IntPtr ifap) { - _monodroid_freeifaddrs (ifap); + RuntimeNativeMethods._monodroid_freeifaddrs (ifap); } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern void _monodroid_detect_cpu_and_architecture (ref ushort built_for_cpu, ref ushort running_on_cpu, ref byte is64bit); - static void DetectCPUAndArchitecture (out ushort builtForCPU, out ushort runningOnCPU, out bool is64bit) { ushort built_for_cpu = 0; ushort running_on_cpu = 0; byte _is64bit = 0; - _monodroid_detect_cpu_and_architecture (ref built_for_cpu, ref running_on_cpu, ref _is64bit); + RuntimeNativeMethods._monodroid_detect_cpu_and_architecture (ref built_for_cpu, ref running_on_cpu, ref _is64bit); builtForCPU = built_for_cpu; runningOnCPU = running_on_cpu; is64bit = _is64bit != 0; diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index c128e994e05..ba63b37bfca 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -17,7 +17,7 @@ namespace Android.Runtime { class AndroidRuntime : JniRuntime { - public const string InternalDllName = "xa-internal-api"; + public const string InternalDllName = RuntimeConstants.InternalDllName; internal AndroidRuntime (IntPtr jnienv, IntPtr vm, @@ -32,6 +32,11 @@ internal AndroidRuntime (IntPtr jnienv, classLoader_loadClass, jniAddNativeMethodRegistrationAttributePresent)) { +#if NETCOREAPP + // This is not ideal, but we need to set this while the runtime is initializing but we can't do it directly from the `JNIEnvInit.Initialize` method, since + // it lives in an assembly that does not reference Mono.Android. So we do it here, because this class is instantiated by JNIEnvInit.Initialize. + AndroidEnvironmentInternal.UnhandledExceptionHandler = AndroidEnvironment.UnhandledException; +#endif } public override void FailFast (string? message) @@ -102,20 +107,12 @@ public AndroidRuntimeOptions (IntPtr jnienv, } class AndroidObjectReferenceManager : JniRuntime.JniObjectReferenceManager { - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern int _monodroid_gref_get (); - public override int GlobalReferenceCount { - get {return _monodroid_gref_get ();} + get {return RuntimeNativeMethods._monodroid_gref_get ();} } - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern int _monodroid_weak_gref_get (); - public override int WeakGlobalReferenceCount { - get {return _monodroid_weak_gref_get ();} + get {return RuntimeNativeMethods._monodroid_weak_gref_get ();} } public override JniObjectReference CreateLocalReference (JniObjectReference value, ref int localReferenceCount) @@ -126,7 +123,7 @@ public override JniObjectReference CreateLocalReference (JniObjectReference valu var tname = Thread.CurrentThread.Name; var tid = Thread.CurrentThread.ManagedThreadId;; var from = new StringBuilder (new StackTrace (true).ToString ()); - JNIEnv._monodroid_lref_log_new (localReferenceCount, r.Handle, (byte) 'L', tname, tid, from, 1); + RuntimeNativeMethods._monodroid_lref_log_new (localReferenceCount, r.Handle, (byte) 'L', tname, tid, from, 1); } return r; @@ -138,7 +135,7 @@ public override void DeleteLocalReference (ref JniObjectReference value, ref int var tname = Thread.CurrentThread.Name; var tid = Thread.CurrentThread.ManagedThreadId;; var from = new StringBuilder (new StackTrace (true).ToString ()); - JNIEnv._monodroid_lref_log_delete (localReferenceCount-1, value.Handle, (byte) 'L', tname, tid, from, 1); + RuntimeNativeMethods._monodroid_lref_log_delete (localReferenceCount-1, value.Handle, (byte) 'L', tname, tid, from, 1); } base.DeleteLocalReference (ref value, ref localReferenceCount); } @@ -150,7 +147,7 @@ public override void CreatedLocalReference (JniObjectReference value, ref int lo var tname = Thread.CurrentThread.Name; var tid = Thread.CurrentThread.ManagedThreadId;; var from = new StringBuilder (new StackTrace (true).ToString ()); - JNIEnv._monodroid_lref_log_new (localReferenceCount, value.Handle, (byte) 'L', tname, tid, from, 1); + RuntimeNativeMethods._monodroid_lref_log_new (localReferenceCount, value.Handle, (byte) 'L', tname, tid, from, 1); } } @@ -161,14 +158,14 @@ public override IntPtr ReleaseLocalReference (ref JniObjectReference value, ref var tname = Thread.CurrentThread.Name; var tid = Thread.CurrentThread.ManagedThreadId;; var from = new StringBuilder (new StackTrace (true).ToString ()); - JNIEnv._monodroid_lref_log_delete (localReferenceCount-1, value.Handle, (byte) 'L', tname, tid, from, 1); + RuntimeNativeMethods._monodroid_lref_log_delete (localReferenceCount-1, value.Handle, (byte) 'L', tname, tid, from, 1); } return r; } public override void WriteGlobalReferenceLine (string format, params object?[] args) { - JNIEnv._monodroid_gref_log (string.Format (format, args)); + RuntimeNativeMethods._monodroid_gref_log (string.Format (format, args)); } public override JniObjectReference CreateGlobalReference (JniObjectReference value) @@ -181,7 +178,7 @@ public override JniObjectReference CreateGlobalReference (JniObjectReference val var tname = log ? Thread.CurrentThread.Name : null; var tid = log ? Thread.CurrentThread.ManagedThreadId : 0; var from = log ? new StringBuilder (new StackTrace (true).ToString ()) : null; - int gc = JNIEnv._monodroid_gref_log_new (value.Handle, ctype, r.Handle, ntype, tname, tid, from, 1); + int gc = RuntimeNativeMethods._monodroid_gref_log_new (value.Handle, ctype, r.Handle, ntype, tname, tid, from, 1); if (gc >= JNIEnvInit.gref_gc_threshold) { Logger.Log (LogLevel.Info, "monodroid-gc", gc + " outstanding GREFs. Performing a full GC!"); System.GC.Collect (); @@ -208,7 +205,7 @@ public override void DeleteGlobalReference (ref JniObjectReference value) var tname = log ? Thread.CurrentThread.Name : null; var tid = log ? Thread.CurrentThread.ManagedThreadId : 0; var from = log ? new StringBuilder (new StackTrace (true).ToString ()) : null; - JNIEnv._monodroid_gref_log_delete (value.Handle, ctype, tname, tid, from, 1); + RuntimeNativeMethods._monodroid_gref_log_delete (value.Handle, ctype, tname, tid, from, 1); base.DeleteGlobalReference (ref value); } @@ -223,7 +220,7 @@ public override JniObjectReference CreateWeakGlobalReference (JniObjectReference var tname = log ? Thread.CurrentThread.Name : null; var tid = log ? Thread.CurrentThread.ManagedThreadId : 0; var from = log ? new StringBuilder (new StackTrace (true).ToString ()) : null; - JNIEnv._monodroid_weak_gref_new (value.Handle, ctype, r.Handle, ntype, tname, tid, from, 1); + RuntimeNativeMethods._monodroid_weak_gref_new (value.Handle, ctype, r.Handle, ntype, tname, tid, from, 1); return r; } @@ -235,7 +232,7 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference value) var tname = log ? Thread.CurrentThread.Name : null; var tid = log ? Thread.CurrentThread.ManagedThreadId : 0; var from = log ? new StringBuilder (new StackTrace (true).ToString ()) : null; - JNIEnv._monodroid_weak_gref_delete (value.Handle, ctype, tname, tid, from, 1); + RuntimeNativeMethods._monodroid_weak_gref_delete (value.Handle, ctype, tname, tid, from, 1); base.DeleteWeakGlobalReference (ref value); } @@ -323,16 +320,13 @@ protected override IEnumerable GetSimpleReferences (Type type) }; } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr _monodroid_lookup_replacement_type (string jniSimpleReference); - protected override string? GetReplacementTypeCore (string jniSimpleReference) { if (!JNIEnvInit.jniRemappingInUse) { return null; } - IntPtr ret = _monodroid_lookup_replacement_type (jniSimpleReference); + IntPtr ret = RuntimeNativeMethods._monodroid_lookup_replacement_type (jniSimpleReference); if (ret == IntPtr.Zero) { return null; } @@ -340,16 +334,13 @@ protected override IEnumerable GetSimpleReferences (Type type) return Marshal.PtrToStringAnsi (ret); } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr _monodroid_lookup_replacement_method_info (string jniSourceType, string jniMethodName, string jniMethodSignature); - protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature) { if (!JNIEnvInit.jniRemappingInUse) { return null; } - IntPtr retInfo = _monodroid_lookup_replacement_method_info (jniSourceType, jniMethodName, jniMethodSignature); + IntPtr retInfo = RuntimeNativeMethods._monodroid_lookup_replacement_method_info (jniSourceType, jniMethodName, jniMethodSignature); if (retInfo == IntPtr.Zero) { return null; } @@ -735,7 +726,7 @@ internal void AddPeer (IJavaPeerable value, IntPtr handle, JniHandleOwnership tr } if (Logger.LogGlobalRef) { - JNIEnv._monodroid_gref_log ("handle 0x" + handleField.ToString ("x") + + RuntimeNativeMethods._monodroid_gref_log ("handle 0x" + handleField.ToString ("x") + "; key_handle 0x" + hash.ToString ("x") + ": Java Type: `" + JNIEnv.GetClassNameFromInstance (handleField) + "`; " + "MCW type: `" + value.GetType ().FullName + "`\n"); @@ -873,7 +864,7 @@ public override void FinalizePeer (IJavaPeerable value) throw new ArgumentNullException (nameof (value)); if (Logger.LogGlobalRef) { - JNIEnv._monodroid_gref_log ($"Finalizing handle {value.PeerReference}\n"); + RuntimeNativeMethods._monodroid_gref_log ($"Finalizing handle {value.PeerReference}\n"); } // FIXME: need hash cleanup mechanism. diff --git a/src/Mono.Android/Android.Runtime/BoundExceptionType.cs b/src/Mono.Android/Android.Runtime/BoundExceptionType.cs index 442f05ea7a1..f760ef89530 100644 --- a/src/Mono.Android/Android.Runtime/BoundExceptionType.cs +++ b/src/Mono.Android/Android.Runtime/BoundExceptionType.cs @@ -1,7 +1,7 @@ namespace Android.Runtime { // Keep the enum values in sync with those in src/monodroid/jni/monodroid-glue-internal.hh - enum BoundExceptionType : byte + internal enum BoundExceptionType : byte { System = 0x00, Java = 0x01, diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 17d9d3d46bf..05091ce49c3 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -15,55 +15,14 @@ namespace Android.Runtime { public static partial class JNIEnv { - //static IntPtr java_class_loader; - //static IntPtr java_vm; - //static IntPtr load_class_id; - //static IntPtr gref_class; - //static JniMethodInfo? mid_Class_forName; - //static int version; - //static int androidSdkVersion; - - //static bool AllocObjectSupported; - //internal static bool jniRemappingInUse; - - //static IntPtr grefIGCUserPeer_class; - - //internal static int gref_gc_threshold; - - //internal static bool PropagateExceptions; - - //internal static bool IsRunningOnDesktop; - //internal static bool LogAssemblyCategory; - //internal static bool MarshalMethodsEnabled; - - //static AndroidRuntime? androidRuntime; - //static BoundExceptionType BoundExceptionType; - [ThreadStatic] static byte[]? mvid_bytes; - //internal static AndroidValueManager? AndroidValueManager; - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal extern static void monodroid_log (LogLevel level, LogCategories category, string message); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal extern static IntPtr monodroid_timing_start (string? message); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal extern static void monodroid_timing_stop (IntPtr sequence, string? message); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal extern static void monodroid_free (IntPtr ptr); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - extern static IntPtr _monodroid_get_identity_hash_code (IntPtr env, IntPtr value); - public static IntPtr Handle => JniEnvironment.EnvironmentPointer; internal static IntPtr IdentityHash (IntPtr v) { - return JNIEnvInit.LocalRefsAreIndirect ? _monodroid_get_identity_hash_code (Handle, v) : v; + return JNIEnvInit.LocalRefsAreIndirect ? RuntimeNativeMethods._monodroid_get_identity_hash_code (Handle, v) : v; } public static void CheckHandle (IntPtr jnienv) @@ -82,7 +41,7 @@ internal static bool IsGCUserPeer (IntPtr value) internal static bool ShouldWrapJavaException (Java.Lang.Throwable? t, [CallerMemberName] string? caller = null) { if (t == null) { - monodroid_log (LogLevel.Warn, + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $"ShouldWrapJavaException was not passed a valid `Java.Lang.Throwable` instance. Called from method `{caller}`"); return false; @@ -90,9 +49,11 @@ internal static bool ShouldWrapJavaException (Java.Lang.Throwable? t, [CallerMem bool wrap = JNIEnvInit.BoundExceptionType == BoundExceptionType.System; if (!wrap) { - monodroid_log (LogLevel.Warn, - LogCategories.Default, - $"Not wrapping exception of type {t.GetType().FullName} from method `{caller}`. This will change in a future release."); + RuntimeNativeMethods.monodroid_log ( + LogLevel.Warn, + LogCategories.Default, + $"Not wrapping exception of type {t.GetType().FullName} from method `{caller}`. This will change in a future release." + ); } return wrap; @@ -101,97 +62,6 @@ internal static bool ShouldWrapJavaException (Java.Lang.Throwable? t, [CallerMem [DllImport ("libc")] static extern int gettid (); -// #if NETCOREAPP -// [UnmanagedCallersOnly] -// #endif -// static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len) -// { - // string typeName = new string ((char*) typeName_ptr, 0, typeName_len); - // var type = Type.GetType (typeName); - // if (type == null) { - // monodroid_log (LogLevel.Error, - // LogCategories.Default, - // $"Could not load type '{typeName}'. Skipping JNI registration of type '{Java.Interop.TypeManager.GetClassName (jniClass)}'."); - // return; - // } - - // var className = Java.Interop.TypeManager.GetClassName (jniClass); - // Java.Interop.TypeManager.RegisterType (className, type); - - // JniType? jniType = null; - // JniType.GetCachedJniType (ref jniType, className); - - // ReadOnlySpan methods = new ReadOnlySpan ((void*) methods_ptr, methods_len); - // ((AndroidTypeManager)androidRuntime!.TypeManager).RegisterNativeMembers (jniType, type, methods); -// } - -// #if NETCOREAPP -// [UnmanagedCallersOnly] -// #endif -// internal static unsafe void Initialize (JnienvInitializeArgs* args) -// { -// IntPtr total_timing_sequence = IntPtr.Zero; -// IntPtr partial_timing_sequence = IntPtr.Zero; - -// LogAssemblyCategory = (args->logCategories & (uint)LogCategories.Assembly) != 0; - -// gref_gc_threshold = args->grefGcThreshold; - -// jniRemappingInUse = args->jniRemappingInUse; -// #if NETCOREAPP -// MarshalMethodsEnabled = args->marshalMethodsEnabled; -// #endif -// java_vm = args->javaVm; - -// version = args->version; - -// androidSdkVersion = args->androidSdkVersion; - -// java_class_loader = args->grefLoader; -// load_class_id = args->Loader_loadClass; -// gref_class = args->grefClass; -// mid_Class_forName = new JniMethodInfo (args->Class_forName, isStatic: true); - -// if (args->localRefsAreIndirect == 1) -// IdentityHash = v => _monodroid_get_identity_hash_code (Handle, v); -// else -// IdentityHash = v => v; - -// #if MONOANDROID1_0 -// Mono.SystemDependencyProvider.Initialize (); -// #endif - -// BoundExceptionType = (BoundExceptionType)args->ioExceptionType; -// androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0); -// AndroidValueManager = (AndroidValueManager) androidRuntime.ValueManager; - -// AllocObjectSupported = androidSdkVersion > 10; -// IsRunningOnDesktop = args->isRunningOnDesktop == 1; - -// grefIGCUserPeer_class = args->grefIGCUserPeer; - -// PropagateExceptions = args->brokenExceptionTransitions == 0; - -// JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args->packageNamingPolicy; -// if (IsRunningOnDesktop) { -// var packageNamingPolicy = Environment.GetEnvironmentVariable ("__XA_PACKAGE_NAMING_POLICY__"); -// if (Enum.TryParse (packageNamingPolicy, out PackageNamingPolicy pnp)) { -// JavaNativeTypeManager.PackageNamingPolicy = pnp; -// } -// } - -// #if !MONOANDROID1_0 -// SetSynchronizationContext (); -// #endif -// } - -// #if !MONOANDROID1_0 -// // NOTE: prevents Android.App.Application static ctor from running -// [MethodImpl (MethodImplOptions.NoInlining)] -// static void SetSynchronizationContext () => -// SynchronizationContext.SetSynchronizationContext (Android.App.Application.SynchronizationContext); -// #endif - internal static void Exit () { /* Manually dispose surfaced objects and close the current JniEnvironment to @@ -204,7 +74,7 @@ internal static void Exit () obj.Dispose (); continue; } catch (Exception e) { - monodroid_log (LogLevel.Warn, LogCategories.Default, $"Couldn't dispose object: {e}"); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $"Couldn't dispose object: {e}"); } /* If calling Dispose failed, the assumption is that user-code in * the Dispose(bool) overload is to blame for it. In that case we @@ -232,29 +102,13 @@ static void ManualJavaObjectDispose (Java.Lang.Object obj) GC.SuppressFinalize (obj); } -#if NETCOREAPP - internal static Action mono_unhandled_exception = monodroid_debugger_unhandled_exception; -#else // NETCOREAPP - internal static Action mono_unhandled_exception = null!; -#endif // NETCOREAPP - internal static MethodInfo? mono_unhandled_exception_method = null; - #if !NETCOREAPP static Action AppDomain_DoUnhandledException = null!; #endif // ndef NETCOREAPP static void Initialize () { - if (mono_unhandled_exception == null) { - mono_unhandled_exception_method = typeof (System.Diagnostics.Debugger) - .GetMethod ("Mono_UnhandledException", BindingFlags.NonPublic | BindingFlags.Static); - if (mono_unhandled_exception_method != null) - mono_unhandled_exception = (Action) Delegate.CreateDelegate (typeof(Action), mono_unhandled_exception_method); - } - if (mono_unhandled_exception_method == null && mono_unhandled_exception != null) { - mono_unhandled_exception_method = mono_unhandled_exception.Method; - } - + JNIEnvInit.InitializeUnhandledExceptionMethod (); #if !NETCOREAPP if (AppDomain_DoUnhandledException == null) { var ad_due = typeof (AppDomain) @@ -271,11 +125,6 @@ static void Initialize () #endif // ndef NETCOREAPP } -#if NETCOREAPP - [MethodImplAttribute(MethodImplOptions.InternalCall)] - extern static void monodroid_unhandled_exception (Exception javaException); -#endif // def NETCOREAPP - internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPtr, IntPtr javaExceptionPtr) { if (!JNIEnvInit.PropagateExceptions) @@ -290,7 +139,7 @@ internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPt var javaException = JavaObject.GetObject (env, javaExceptionPtr, JniHandleOwnership.DoNotTransfer)!; if (Debugger.IsAttached) { - mono_unhandled_exception?.Invoke (javaException); + JNIEnvInit.mono_unhandled_exception?.Invoke (javaException); } try { @@ -306,28 +155,20 @@ internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPt //AppDomain.CurrentDomain.DoUnhandledException (args); AppDomain_DoUnhandledException?.Invoke (AppDomain.CurrentDomain, args); #else // ndef NETCOREAPP - monodroid_unhandled_exception (innerException ?? javaException); + RuntimeNativeMethods.monodroid_unhandled_exception (innerException ?? javaException); #endif // def NETCOREAPP } catch (Exception e) { Logger.Log (LogLevel.Error, "monodroid", "Exception thrown while raising AppDomain.UnhandledException event: " + e.ToString ()); } } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - extern static void _monodroid_gc_wait_for_bridge_processing (); - public static void WaitForBridgeProcessing () { if (!JNIEnvInit.BridgeProcessing) return; - _monodroid_gc_wait_for_bridge_processing (); + RuntimeNativeMethods._monodroid_gc_wait_for_bridge_processing (); } - // [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - // extern static IntPtr _monodroid_get_identity_hash_code (IntPtr env, IntPtr value); - - //internal static Func? IdentityHash; - public static IntPtr AllocObject (string jniClassName) { IntPtr jniClass = JNIEnv.FindClass (jniClassName); @@ -494,7 +335,7 @@ public static IntPtr FindClass (System.Type type) } catch (Java.Lang.Throwable e) { if (!((e is Java.Lang.NoClassDefFoundError) || (e is Java.Lang.ClassNotFoundException))) throw; - monodroid_log (LogLevel.Warn, LogCategories.Default, $"JNIEnv.FindClass(Type) caught unexpected exception: {e}"); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $"JNIEnv.FindClass(Type) caught unexpected exception: {e}"); var jni = Java.Interop.TypeManager.GetJniTypeName (type); if (jni != null) { e.Dispose (); @@ -615,27 +456,6 @@ internal static void DeleteRef (IntPtr handle, JniHandleOwnership transfer) } } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern int _monodroid_gref_log (string message); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern int _monodroid_gref_log_new (IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern void _monodroid_gref_log_delete (IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern void _monodroid_weak_gref_new (IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern void _monodroid_weak_gref_delete (IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern int _monodroid_lref_log_new (int lrefc, IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder from, int from_writable); - - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - internal static extern void _monodroid_lref_log_delete (int lrefc, IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder from, int from_writable); - public static IntPtr NewGlobalRef (IntPtr jobject) { var r = new JniObjectReference (jobject); @@ -696,20 +516,15 @@ public static string GetClassNameFromInstance (IntPtr jobject) [MethodImplAttribute(MethodImplOptions.InternalCall)] static extern unsafe IntPtr monodroid_typemap_managed_to_java (Type type, byte* mvid); -#if NETCOREAPP - [MethodImplAttribute(MethodImplOptions.InternalCall)] - static extern unsafe void monodroid_debugger_unhandled_exception (Exception e); -#endif // NETCOREAPP - internal static void LogTypemapTrace (StackTrace st) { string? trace = st.ToString ()?.Trim (); if (String.IsNullOrEmpty (trace)) return; - monodroid_log (LogLevel.Warn, LogCategories.Assembly, "typemap: called from"); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Assembly, "typemap: called from"); foreach (string line in trace!.Split ('\n')) { - monodroid_log (LogLevel.Warn, LogCategories.Assembly, line); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Assembly, line); } } @@ -721,7 +536,7 @@ internal static void LogTypemapTrace (StackTrace st) var mvid = new Span(mvid_bytes); byte[]? mvid_data = null; if (!type.Module.ModuleVersionId.TryWriteBytes (mvid)) { - monodroid_log (LogLevel.Warn, LogCategories.Default, $"Failed to obtain module MVID using the fast method, falling back to the slow one"); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $"Failed to obtain module MVID using the fast method, falling back to the slow one"); mvid_data = type.Module.ModuleVersionId.ToByteArray (); } else { mvid_data = mvid_bytes; @@ -734,7 +549,7 @@ internal static void LogTypemapTrace (StackTrace st) if (ret == IntPtr.Zero) { if (JNIEnvInit.LogAssemblyCategory) { - monodroid_log (LogLevel.Warn, LogCategories.Default, $"typemap: failed to map managed type to Java type: {type.AssemblyQualifiedName} (Module ID: {type.Module.ModuleVersionId}; Type token: {type.MetadataToken})"); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $"typemap: failed to map managed type to Java type: {type.AssemblyQualifiedName} (Module ID: {type.Module.ModuleVersionId}; Type token: {type.MetadataToken})"); LogTypemapTrace (new StackTrace (true)); } diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 80847654e02..4e83cb37479 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; @@ -8,7 +9,7 @@ namespace Android.Runtime { - static class JNIEnvInit + static internal class JNIEnvInit { #pragma warning disable 0649 internal struct JnienvInitializeArgs { @@ -34,9 +35,6 @@ internal struct JnienvInitializeArgs { } #pragma warning restore 0649 - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - extern static void monodroid_log (LogLevel level, LogCategories category, string message); - internal static AndroidValueManager? AndroidValueManager; internal static bool AllocObjectSupported; internal static bool IsRunningOnDesktop; @@ -53,6 +51,13 @@ internal struct JnienvInitializeArgs { static AndroidRuntime? androidRuntime; + internal static MethodInfo? mono_unhandled_exception_method = null; +#if NETCOREAPP + internal static Action mono_unhandled_exception = RuntimeNativeMethods.monodroid_debugger_unhandled_exception; +#else // NETCOREAPP + internal static Action mono_unhandled_exception = null!; +#endif // NETCOREAPP + #pragma warning disable CS0649 // Field is never assigned to. This field is assigned from monodroid-glue.cc. internal static volatile bool BridgeProcessing; // = false #pragma warning restore CS0649 // Field is never assigned to. @@ -65,7 +70,7 @@ static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, In string typeName = new string ((char*) typeName_ptr, 0, typeName_len); var type = Type.GetType (typeName); if (type == null) { - monodroid_log (LogLevel.Error, + RuntimeNativeMethods.monodroid_log (LogLevel.Error, LogCategories.Default, $"Could not load type '{typeName}'. Skipping JNI registration of type '{Java.Interop.TypeManager.GetClassName (jniClass)}'."); return; @@ -132,6 +137,19 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) #endif } + internal static void InitializeUnhandledExceptionMethod () + { + if (mono_unhandled_exception == null) { + JNIEnvInit.mono_unhandled_exception_method = typeof (System.Diagnostics.Debugger) + .GetMethod ("Mono_UnhandledException", BindingFlags.NonPublic | BindingFlags.Static); + if (JNIEnvInit.mono_unhandled_exception_method != null) + mono_unhandled_exception = (Action) Delegate.CreateDelegate (typeof(Action), JNIEnvInit.mono_unhandled_exception_method); + } + if (JNIEnvInit.mono_unhandled_exception_method == null && mono_unhandled_exception != null) { + JNIEnvInit.mono_unhandled_exception_method = mono_unhandled_exception.Method; + } + } + #if !MONOANDROID1_0 // NOTE: prevents Android.App.Application static ctor from running [MethodImpl (MethodImplOptions.NoInlining)] diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs index 60b1a96a081..866b26ad856 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs @@ -42,7 +42,7 @@ public static Delegate CreateDelegate (Delegate dlg) return result; if (JNIEnvInit.LogAssemblyCategory) { - JNIEnv.monodroid_log (LogLevel.Debug, LogCategories.Assembly, $"Falling back to System.Reflection.Emit for delegate type '{delegateType}': {dlg.Method}"); + RuntimeNativeMethods.monodroid_log (LogLevel.Debug, LogCategories.Assembly, $"Falling back to System.Reflection.Emit for delegate type '{delegateType}': {dlg.Method}"); } var ret_type = dlg.Method.ReturnType; @@ -73,10 +73,10 @@ public static Delegate CreateDelegate (Delegate dlg) ig.Emit (OpCodes.Leave, label); bool filter = Debugger.IsAttached || !JNIEnvInit.PropagateExceptions; - if (filter && JNIEnv.mono_unhandled_exception_method != null) { + if (filter && JNIEnvInit.mono_unhandled_exception_method != null) { ig.BeginExceptFilterBlock (); - ig.Emit (OpCodes.Call, JNIEnv.mono_unhandled_exception_method); + ig.Emit (OpCodes.Call, JNIEnvInit.mono_unhandled_exception_method); ig.Emit (OpCodes.Ldc_I4_1); ig.BeginCatchBlock (null!); } else { diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs index 0c16450e50c..e9d6f75adf4 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs @@ -9,7 +9,7 @@ public static partial class JNINativeWrapper static bool _unhandled_exception (Exception e) { if (Debugger.IsAttached || !JNIEnvInit.PropagateExceptions) { - JNIEnv.mono_unhandled_exception?.Invoke (e); + JNIEnvInit.mono_unhandled_exception?.Invoke (e); return false; } return true; diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt index eccac6c18a0..47e0a5353c2 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt @@ -251,7 +251,7 @@ namespace Android.Runtime static bool _unhandled_exception (Exception e) { if (Debugger.IsAttached || !JNIEnvInit.PropagateExceptions) { - JNIEnv.mono_unhandled_exception?.Invoke (e); + JNIEnvInit.mono_unhandled_exception?.Invoke (e); return false; } return true; diff --git a/src/Mono.Android/Android.Runtime/LogCategories.cs b/src/Mono.Android/Android.Runtime/LogCategories.cs new file mode 100644 index 00000000000..5eb6020cfad --- /dev/null +++ b/src/Mono.Android/Android.Runtime/LogCategories.cs @@ -0,0 +1,23 @@ +#if !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME +using System; + +namespace Android.Runtime +{ + // Keep in sync with the LogCategories enum in + // monodroid/libmonodroid/logger.{c,h} + [Flags] + internal enum LogCategories { + None = 0, + Default = 1 << 0, + Assembly = 1 << 1, + Debugger = 1 << 2, + GC = 1 << 3, + GlobalRef = 1 << 4, + LocalRef = 1 << 5, + Timing = 1 << 6, + Bundle = 1 << 7, + Net = 1 << 8, + Netlink = 1 << 9, + } +} +#endif // !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME diff --git a/src/Mono.Android/Android.Runtime/LogLevel.cs b/src/Mono.Android/Android.Runtime/LogLevel.cs new file mode 100644 index 00000000000..347f2037523 --- /dev/null +++ b/src/Mono.Android/Android.Runtime/LogLevel.cs @@ -0,0 +1,18 @@ +#if !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME +namespace Android.Runtime +{ + // Keep in sync with the LogLevel enum in + // monodroid/libmonodroid/logger.{c,h} + internal enum LogLevel { + Unknown = 0x00, + Default = 0x01, + Verbose = 0x02, + Debug = 0x03, + Info = 0x04, + Warn = 0x05, + Error = 0x06, + Fatal = 0x07, + Silent = 0x08 + } +} +#endif // !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME diff --git a/src/Mono.Android/Android.Runtime/Logger.cs b/src/Mono.Android/Android.Runtime/Logger.cs index 35e0651a835..f4665ab3a63 100644 --- a/src/Mono.Android/Android.Runtime/Logger.cs +++ b/src/Mono.Android/Android.Runtime/Logger.cs @@ -60,7 +60,7 @@ public static void Log (LogLevel level, string appname, string? log) { } } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] extern static uint monodroid_get_log_categories (); static Logger () @@ -68,35 +68,4 @@ static Logger () Categories = (LogCategories) monodroid_get_log_categories (); } } - - // Keep in sync with the LogLevel enum in - // monodroid/libmonodroid/logger.{c,h} - internal enum LogLevel { - Unknown = 0x00, - Default = 0x01, - Verbose = 0x02, - Debug = 0x03, - Info = 0x04, - Warn = 0x05, - Error = 0x06, - Fatal = 0x07, - Silent = 0x08 - } - - // Keep in sync with the LogCategories enum in - // monodroid/libmonodroid/logger.{c,h} - [Flags] - internal enum LogCategories { - None = 0, - Default = 1 << 0, - Assembly = 1 << 1, - Debugger = 1 << 2, - GC = 1 << 3, - GlobalRef = 1 << 4, - LocalRef = 1 << 5, - Timing = 1 << 6, - Bundle = 1 << 7, - Net = 1 << 8, - Netlink = 1 << 9, - } } diff --git a/src/Mono.Android/Android.Runtime/RuntimeConstants.cs b/src/Mono.Android/Android.Runtime/RuntimeConstants.cs new file mode 100644 index 00000000000..254fb8c2170 --- /dev/null +++ b/src/Mono.Android/Android.Runtime/RuntimeConstants.cs @@ -0,0 +1,9 @@ +#if !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME +namespace Android.Runtime +{ + internal static class RuntimeConstants + { + public const string InternalDllName = "xa-internal-api"; + } +} +#endif // !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME diff --git a/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs b/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs new file mode 100644 index 00000000000..6b2c558572e --- /dev/null +++ b/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs @@ -0,0 +1,89 @@ +#if !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Android.Runtime +{ + internal static class RuntimeNativeMethods + { + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static void monodroid_log (LogLevel level, LogCategories category, string message); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static IntPtr monodroid_timing_start (string? message); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static void monodroid_timing_stop (IntPtr sequence, string? message); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static void monodroid_free (IntPtr ptr); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static IntPtr _monodroid_get_identity_hash_code (IntPtr env, IntPtr value); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_gref_get (); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_weak_gref_get (); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _monodroid_lookup_replacement_type (string jniSimpleReference); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _monodroid_lookup_replacement_method_info (string jniSourceType, string jniMethodName, string jniMethodSignature); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr _monodroid_timezone_get_default_id (); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_getifaddrs (out IntPtr ifap); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void _monodroid_freeifaddrs (IntPtr ifap); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void _monodroid_detect_cpu_and_architecture (ref ushort built_for_cpu, ref ushort running_on_cpu, ref byte is64bit); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal extern static void _monodroid_gc_wait_for_bridge_processing (); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_gref_log (string message); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_gref_log_new (IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void _monodroid_gref_log_delete (IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void _monodroid_weak_gref_new (IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void _monodroid_weak_gref_delete (IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder? from, int from_writable); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_lref_log_new (int lrefc, IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder from, int from_writable); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void _monodroid_lref_log_delete (int lrefc, IntPtr handle, byte type, string? threadName, int threadId, [In] StringBuilder from, int from_writable); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr monodroid_TypeManager_get_java_class_name (IntPtr klass); + + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern int _monodroid_max_gref_get (); + +#if NETCOREAPP + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void monodroid_unhandled_exception (Exception javaException); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern unsafe void monodroid_debugger_unhandled_exception (Exception e); +#endif + } +} +#endif // !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME diff --git a/src/Mono.Android/Android.Runtime/TimingLogger.cs b/src/Mono.Android/Android.Runtime/TimingLogger.cs index 9bbbf538c0a..e34ec0d701e 100644 --- a/src/Mono.Android/Android.Runtime/TimingLogger.cs +++ b/src/Mono.Android/Android.Runtime/TimingLogger.cs @@ -45,7 +45,7 @@ public void Start (string? startMessage = null) if (sequence != IntPtr.Zero) return; - sequence = JNIEnv.monodroid_timing_start (startMessage ?? initStartMessage); + sequence = RuntimeNativeMethods.monodroid_timing_start (startMessage ?? initStartMessage); } /// @@ -65,7 +65,7 @@ public void Stop (string stopMessage) if (sequence == IntPtr.Zero) return; - JNIEnv.monodroid_timing_stop (sequence, stopMessage); + RuntimeNativeMethods.monodroid_timing_stop (sequence, stopMessage); sequence = IntPtr.Zero; } @@ -86,7 +86,7 @@ protected virtual void Dispose (bool disposing) { if (!disposed) { if (sequence != IntPtr.Zero) { - JNIEnv.monodroid_timing_stop (sequence, null); + RuntimeNativeMethods.monodroid_timing_stop (sequence, null); sequence = IntPtr.Zero; } diff --git a/src/Mono.Android/Java.Interop/Runtime.cs b/src/Mono.Android/Java.Interop/Runtime.cs index bcc3ec1c899..ceb14ca9d87 100644 --- a/src/Mono.Android/Java.Interop/Runtime.cs +++ b/src/Mono.Android/Java.Interop/Runtime.cs @@ -20,18 +20,12 @@ public static List GetSurfacedObjects () return r; } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern int _monodroid_max_gref_get (); - public static int MaxGlobalReferenceCount { - get {return _monodroid_max_gref_get ();} + get {return RuntimeNativeMethods._monodroid_max_gref_get ();} } - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - static extern int _monodroid_gref_get (); - public static int GlobalReferenceCount { - get {return _monodroid_gref_get ();} + get {return RuntimeNativeMethods._monodroid_gref_get ();} } public static int LocalReferenceCount { diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 4ef9b26ca4f..e70d00bc424 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -38,14 +38,11 @@ public static Dictionary ManagedToJni { } public static partial class TypeManager { - [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - extern static IntPtr monodroid_TypeManager_get_java_class_name (IntPtr klass); - internal static string GetClassName (IntPtr class_ptr) { - IntPtr ptr = monodroid_TypeManager_get_java_class_name (class_ptr); + IntPtr ptr = RuntimeNativeMethods.monodroid_TypeManager_get_java_class_name (class_ptr); string ret = Marshal.PtrToStringAnsi (ptr)!; - JNIEnv.monodroid_free (ptr); + RuntimeNativeMethods.monodroid_free (ptr); return ret; } diff --git a/src/Mono.Android/Java.Lang/Object.cs b/src/Mono.Android/Java.Lang/Object.cs index 848529fd8d9..e968f0369ff 100644 --- a/src/Mono.Android/Java.Lang/Object.cs +++ b/src/Mono.Android/Java.Lang/Object.cs @@ -220,7 +220,7 @@ internal static void Dispose (IJavaPeerable instance, ref IntPtr handle, IntPtr return; if (Logger.LogGlobalRef) { - JNIEnv._monodroid_gref_log ( + RuntimeNativeMethods._monodroid_gref_log ( string.Format ("Disposing handle 0x{0}\n", handle.ToString ("x"))); } diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 215bc0e475c..607c8a2ac9b 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -260,6 +260,8 @@ + + @@ -267,7 +269,9 @@ + + @@ -388,6 +392,10 @@ + + + + diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 5f3116bd510..8cb008403ba 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1073,8 +1073,8 @@ MonodroidRuntime::init_android_runtime ( #endif // def RELEASE && def ANDROID #if defined (NET) - mono_add_internal_call ("Android.Runtime.JNIEnv::monodroid_debugger_unhandled_exception", reinterpret_cast (monodroid_debugger_unhandled_exception)); - mono_add_internal_call ("Android.Runtime.JNIEnv::monodroid_unhandled_exception", reinterpret_cast(monodroid_unhandled_exception)); + mono_add_internal_call ("Android.Runtime.RuntimeNativeMethods::monodroid_debugger_unhandled_exception", reinterpret_cast (monodroid_debugger_unhandled_exception)); + mono_add_internal_call ("Android.Runtime.RuntimeNativeMethods::monodroid_unhandled_exception", reinterpret_cast(monodroid_unhandled_exception)); #endif // def NET struct JnienvInitializeArgs init = {}; @@ -1103,7 +1103,7 @@ MonodroidRuntime::init_android_runtime ( MonoAssembly *assm; #if defined (NET) - assm = utils.monodroid_load_assembly (default_alc, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME); + assm = utils.monodroid_load_assembly (default_alc, SharedConstants::MONO_ANDROID_RUNTIME_ASSEMBLY_NAME); #else // def NET assm = utils.monodroid_load_assembly (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME); #endif // ndef NET @@ -1129,7 +1129,7 @@ MonodroidRuntime::init_android_runtime ( #if defined (NET) runtime = mono_class_from_name (image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME); #else - runtime = utils.monodroid_get_class_from_image (domain, image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME); + runtime = utils.monodroid_get_class_from_image (domain, image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME); #endif // def NET method = mono_class_get_method_from_name (runtime, "Initialize", 1); } else { diff --git a/src/monodroid/jni/osbridge.cc b/src/monodroid/jni/osbridge.cc index be021905d6c..a5cb7222f01 100644 --- a/src/monodroid/jni/osbridge.cc +++ b/src/monodroid/jni/osbridge.cc @@ -1143,7 +1143,17 @@ OSBridge::add_monodroid_domain (MonoDomain *domain) * use GC API to allocate memory and thus can't be called from within the GC callback as it causes a deadlock * (the routine allocating the memory waits for the GC round to complete first) */ - MonoClass *jnienv = utils.monodroid_get_class_from_name (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME);; + MonoClass *jnienv = utils.monodroid_get_class_from_name ( + domain, +#if defined (NET) + SharedConstants::MONO_ANDROID_RUNTIME_ASSEMBLY_NAME, +#else + SharedConstants::MONO_ANDROID_ASSEMBLY_NAME, +#endif + SharedConstants::ANDROID_RUNTIME_NS_NAME, + SharedConstants::JNIENVINIT_CLASS_NAME + ); + node->domain = domain; node->bridge_processing_field = mono_class_get_field_from_name (jnienv, const_cast ("BridgeProcessing")); node->jnienv_vtable = mono_class_vtable (domain, jnienv); diff --git a/src/monodroid/jni/shared-constants.hh b/src/monodroid/jni/shared-constants.hh index 7d0e662995f..5f8800db902 100644 --- a/src/monodroid/jni/shared-constants.hh +++ b/src/monodroid/jni/shared-constants.hh @@ -17,6 +17,9 @@ namespace xamarin::android::internal class SharedConstants { public: +#if defined (NET) + static constexpr char MONO_ANDROID_RUNTIME_ASSEMBLY_NAME[] = "Mono.Android.Runtime"; +#endif static constexpr char MONO_ANDROID_ASSEMBLY_NAME[] = "Mono.Android"; static constexpr char JAVA_INTEROP_ASSEMBLY_NAME[] = "Java.Interop"; From 504ced93890a0e156874e3ff8e965e4e88e3e452 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 20 Sep 2022 23:09:21 +0200 Subject: [PATCH 61/79] [WIP] Exception wrapper generator in assembly rewriter --- .../create-packs/Microsoft.Android.Ref.proj | 1 + .../Microsoft.Android.Runtime.proj | 1 + .../PreserveLists/Mono.Android.Runtime.xml | 7 + .../PreserveLists/Mono.Android.xml | 2 - .../Mono.Android.Runtime.csproj | 1 + .../Properties/AssemblyInfo.cs.in | 1 - .../Android.Runtime/AndroidRuntimeInternal.cs | 39 +++ src/Mono.Android/Android.Runtime/JNIEnv.cs | 8 +- .../Android.Runtime/JNIEnvInit.cs | 24 -- .../Android.Runtime/JNINativeWrapper.cs | 8 +- .../Android.Runtime/JNINativeWrapper.g.cs | 80 +++--- .../Android.Runtime/JNINativeWrapper.g.tt | 6 +- .../Android.Runtime/RuntimeNativeMethods.cs | 1 - src/Mono.Android/Mono.Android.csproj | 1 + .../Tasks/GenerateJavaStubs.cs | 9 +- .../Tasks/GeneratePackageManagerJava.cs | 72 ++--- .../Utilities/EnvironmentFilesParser.cs | 82 ++++++ .../MarshalMethodsAssemblyRewriter.cs | 256 +++++++++++++++--- .../Utilities/ParsedEnvironment.cs | 6 + .../Xamarin.Android.Common.targets | 6 +- 20 files changed, 432 insertions(+), 179 deletions(-) create mode 100644 src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml create mode 100644 src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/ParsedEnvironment.cs diff --git a/build-tools/create-packs/Microsoft.Android.Ref.proj b/build-tools/create-packs/Microsoft.Android.Ref.proj index a4d71c2decb..39d0b2e6c11 100644 --- a/build-tools/create-packs/Microsoft.Android.Ref.proj +++ b/build-tools/create-packs/Microsoft.Android.Ref.proj @@ -33,6 +33,7 @@ by projects that use the Microsoft.Android framework in .NET 6+. <_AndroidRefPackAssemblies Include="$(JavaInteropSourceDirectory)\bin\$(Configuration)-net7.0\ref\Java.Interop.dll" /> <_AndroidRefPackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)ref\Mono.Android.dll" /> + <_AndroidRefPackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)ref\Mono.Android.Runtime.dll" /> <_AndroidRefPackAssemblies Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\ref\Mono.Android.Export.dll" /> diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index aef96f9dfc8..8982eb6f911 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -35,6 +35,7 @@ projects that use the Microsoft.Android framework in .NET 6+. <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)Java.Interop.dll" /> <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)Mono.Android.dll" /> + <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETDefaultOutDir)Mono.Android.Runtime.dll" /> <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\Mono.Android.Export.dll" /> <_AndroidRuntimePackAssets Include="$(MicrosoftAndroidSdkOutDir)lib\$(AndroidRID)\libmono-android.debug.so" /> diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml new file mode 100644 index 00000000000..db9a48d4778 --- /dev/null +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml index eaa50318b75..1f27591ef8f 100644 --- a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml @@ -18,8 +18,6 @@ - - diff --git a/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj b/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj index f32de972aab..495567700f3 100644 --- a/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj +++ b/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj @@ -46,6 +46,7 @@ + diff --git a/src/Mono.Android.Runtime/Properties/AssemblyInfo.cs.in b/src/Mono.Android.Runtime/Properties/AssemblyInfo.cs.in index 53edcd5fd39..6c392e8130f 100644 --- a/src/Mono.Android.Runtime/Properties/AssemblyInfo.cs.in +++ b/src/Mono.Android.Runtime/Properties/AssemblyInfo.cs.in @@ -15,7 +15,6 @@ using System.Runtime.Versioning; [assembly: AssemblyTitle ("Mono.Android.Runtime.dll")] [assembly: AssemblyProduct ("Xamarin.Android")] [assembly: AssemblyCompany ("Microsoft Corporation")] -[assembly: AssemblyMetadata ("IsTrimmable", "False")] [assembly: TargetPlatform("Android@API_LEVEL@.0")] [assembly: SupportedOSPlatform("Android@MIN_API_LEVEL@.0")] diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs new file mode 100644 index 00000000000..106b29db0c7 --- /dev/null +++ b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs @@ -0,0 +1,39 @@ +using System; +using System.Reflection; + +namespace Android.Runtime +{ + static internal class AndroidRuntimeInternal + { + internal static MethodInfo? mono_unhandled_exception_method = null; +#if NETCOREAPP + internal static Action mono_unhandled_exception = RuntimeNativeMethods.monodroid_debugger_unhandled_exception; +#else + internal static Action? mono_unhandled_exception = null; +#endif + +#pragma warning disable CS0649 // Field is never assigned to. This field is assigned from monodroid-glue.cc. + internal static volatile bool BridgeProcessing; // = false +#pragma warning restore CS0649 // Field is never assigned to. + + internal static void InitializeUnhandledExceptionMethod () + { + if (mono_unhandled_exception == null) { + mono_unhandled_exception_method = typeof (System.Diagnostics.Debugger) + .GetMethod ("Mono_UnhandledException", BindingFlags.NonPublic | BindingFlags.Static); + if (mono_unhandled_exception_method != null) + mono_unhandled_exception = (Action) Delegate.CreateDelegate (typeof(Action), mono_unhandled_exception_method); + } + if (mono_unhandled_exception_method == null && mono_unhandled_exception != null) { + mono_unhandled_exception_method = mono_unhandled_exception.Method; + } + } + + internal static void WaitForBridgeProcessing () + { + if (!BridgeProcessing) + return; + RuntimeNativeMethods._monodroid_gc_wait_for_bridge_processing (); + } + } +} diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 05091ce49c3..46c8d75313c 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -108,7 +108,7 @@ static void ManualJavaObjectDispose (Java.Lang.Object obj) static void Initialize () { - JNIEnvInit.InitializeUnhandledExceptionMethod (); + AndroidRuntimeInternal.InitializeUnhandledExceptionMethod (); #if !NETCOREAPP if (AppDomain_DoUnhandledException == null) { var ad_due = typeof (AppDomain) @@ -139,7 +139,7 @@ internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPt var javaException = JavaObject.GetObject (env, javaExceptionPtr, JniHandleOwnership.DoNotTransfer)!; if (Debugger.IsAttached) { - JNIEnvInit.mono_unhandled_exception?.Invoke (javaException); + AndroidRuntimeInternal.mono_unhandled_exception?.Invoke (javaException); } try { @@ -164,9 +164,7 @@ internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPt public static void WaitForBridgeProcessing () { - if (!JNIEnvInit.BridgeProcessing) - return; - RuntimeNativeMethods._monodroid_gc_wait_for_bridge_processing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); } public static IntPtr AllocObject (string jniClassName) diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 4e83cb37479..ac7bf299469 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -51,17 +51,6 @@ internal struct JnienvInitializeArgs { static AndroidRuntime? androidRuntime; - internal static MethodInfo? mono_unhandled_exception_method = null; -#if NETCOREAPP - internal static Action mono_unhandled_exception = RuntimeNativeMethods.monodroid_debugger_unhandled_exception; -#else // NETCOREAPP - internal static Action mono_unhandled_exception = null!; -#endif // NETCOREAPP - -#pragma warning disable CS0649 // Field is never assigned to. This field is assigned from monodroid-glue.cc. - internal static volatile bool BridgeProcessing; // = false -#pragma warning restore CS0649 // Field is never assigned to. - #if NETCOREAPP [UnmanagedCallersOnly] #endif @@ -137,19 +126,6 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) #endif } - internal static void InitializeUnhandledExceptionMethod () - { - if (mono_unhandled_exception == null) { - JNIEnvInit.mono_unhandled_exception_method = typeof (System.Diagnostics.Debugger) - .GetMethod ("Mono_UnhandledException", BindingFlags.NonPublic | BindingFlags.Static); - if (JNIEnvInit.mono_unhandled_exception_method != null) - mono_unhandled_exception = (Action) Delegate.CreateDelegate (typeof(Action), JNIEnvInit.mono_unhandled_exception_method); - } - if (JNIEnvInit.mono_unhandled_exception_method == null && mono_unhandled_exception != null) { - JNIEnvInit.mono_unhandled_exception_method = mono_unhandled_exception.Method; - } - } - #if !MONOANDROID1_0 // NOTE: prevents Android.App.Application static ctor from running [MethodImpl (MethodImplOptions.NoInlining)] diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs index 866b26ad856..5a1b668042c 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs @@ -20,9 +20,9 @@ static void get_runtime_types () if (exception_handler_method == null) AndroidEnvironment.FailFast ("Cannot find AndroidEnvironment.UnhandledException"); - wait_for_bridge_processing_method = typeof (JNIEnv).GetMethod ("WaitForBridgeProcessing", BindingFlags.Public | BindingFlags.Static); + wait_for_bridge_processing_method = typeof (AndroidRuntimeInternal).GetMethod ("WaitForBridgeProcessing", BindingFlags.NonPublic | BindingFlags.Static); if (wait_for_bridge_processing_method == null) - AndroidEnvironment.FailFast ("Cannot find JNIEnv.WaitForBridgeProcessing"); + AndroidEnvironment.FailFast ("Cannot find AndroidRuntimeInternal.WaitForBridgeProcessing"); } public static Delegate CreateDelegate (Delegate dlg) @@ -73,10 +73,10 @@ public static Delegate CreateDelegate (Delegate dlg) ig.Emit (OpCodes.Leave, label); bool filter = Debugger.IsAttached || !JNIEnvInit.PropagateExceptions; - if (filter && JNIEnvInit.mono_unhandled_exception_method != null) { + if (filter && AndroidRuntimeInternal.mono_unhandled_exception_method != null) { ig.BeginExceptFilterBlock (); - ig.Emit (OpCodes.Call, JNIEnvInit.mono_unhandled_exception_method); + ig.Emit (OpCodes.Call, AndroidRuntimeInternal.mono_unhandled_exception_method); ig.Emit (OpCodes.Ldc_I4_1); ig.BeginCatchBlock (null!); } else { diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs index e9d6f75adf4..738b2e07bc1 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs @@ -9,7 +9,7 @@ public static partial class JNINativeWrapper static bool _unhandled_exception (Exception e) { if (Debugger.IsAttached || !JNIEnvInit.PropagateExceptions) { - JNIEnvInit.mono_unhandled_exception?.Invoke (e); + AndroidRuntimeInternal.mono_unhandled_exception?.Invoke (e); return false; } return true; @@ -17,7 +17,7 @@ static bool _unhandled_exception (Exception e) internal static void Wrap_JniMarshal_PP_V (this _JniMarshal_PP_V callback, IntPtr jnienv, IntPtr klazz) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz); } catch (Exception e) when (_unhandled_exception (e)) { @@ -28,7 +28,7 @@ internal static void Wrap_JniMarshal_PP_V (this _JniMarshal_PP_V callback, IntPt internal static int Wrap_JniMarshal_PP_I (this _JniMarshal_PP_I callback, IntPtr jnienv, IntPtr klazz) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz); } catch (Exception e) when (_unhandled_exception (e)) { @@ -39,7 +39,7 @@ internal static int Wrap_JniMarshal_PP_I (this _JniMarshal_PP_I callback, IntPtr internal static bool Wrap_JniMarshal_PP_Z (this _JniMarshal_PP_Z callback, IntPtr jnienv, IntPtr klazz) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz); } catch (Exception e) when (_unhandled_exception (e)) { @@ -50,7 +50,7 @@ internal static bool Wrap_JniMarshal_PP_Z (this _JniMarshal_PP_Z callback, IntPt internal static void Wrap_JniMarshal_PPI_V (this _JniMarshal_PPI_V callback, IntPtr jnienv, IntPtr klazz, int p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -61,7 +61,7 @@ internal static void Wrap_JniMarshal_PPI_V (this _JniMarshal_PPI_V callback, Int internal static IntPtr Wrap_JniMarshal_PPI_L (this _JniMarshal_PPI_L callback, IntPtr jnienv, IntPtr klazz, int p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -72,7 +72,7 @@ internal static IntPtr Wrap_JniMarshal_PPI_L (this _JniMarshal_PPI_L callback, I internal static int Wrap_JniMarshal_PPI_I (this _JniMarshal_PPI_I callback, IntPtr jnienv, IntPtr klazz, int p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -83,7 +83,7 @@ internal static int Wrap_JniMarshal_PPI_I (this _JniMarshal_PPI_I callback, IntP internal static long Wrap_JniMarshal_PPI_J (this _JniMarshal_PPI_J callback, IntPtr jnienv, IntPtr klazz, int p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -94,7 +94,7 @@ internal static long Wrap_JniMarshal_PPI_J (this _JniMarshal_PPI_J callback, Int internal static int Wrap_JniMarshal_PPL_I (this _JniMarshal_PPL_I callback, IntPtr jnienv, IntPtr klazz, IntPtr p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -105,7 +105,7 @@ internal static int Wrap_JniMarshal_PPL_I (this _JniMarshal_PPL_I callback, IntP internal static IntPtr Wrap_JniMarshal_PPL_L (this _JniMarshal_PPL_L callback, IntPtr jnienv, IntPtr klazz, IntPtr p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -116,7 +116,7 @@ internal static IntPtr Wrap_JniMarshal_PPL_L (this _JniMarshal_PPL_L callback, I internal static void Wrap_JniMarshal_PPL_V (this _JniMarshal_PPL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -127,7 +127,7 @@ internal static void Wrap_JniMarshal_PPL_V (this _JniMarshal_PPL_V callback, Int internal static bool Wrap_JniMarshal_PPL_Z (this _JniMarshal_PPL_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -138,7 +138,7 @@ internal static bool Wrap_JniMarshal_PPL_Z (this _JniMarshal_PPL_Z callback, Int internal static bool Wrap_JniMarshal_PPJ_Z (this _JniMarshal_PPJ_Z callback, IntPtr jnienv, IntPtr klazz, long p0) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0); } catch (Exception e) when (_unhandled_exception (e)) { @@ -149,7 +149,7 @@ internal static bool Wrap_JniMarshal_PPJ_Z (this _JniMarshal_PPJ_Z callback, Int internal static void Wrap_JniMarshal_PPII_V (this _JniMarshal_PPII_V callback, IntPtr jnienv, IntPtr klazz, int p0, int p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -160,7 +160,7 @@ internal static void Wrap_JniMarshal_PPII_V (this _JniMarshal_PPII_V callback, I internal static void Wrap_JniMarshal_PPLI_V (this _JniMarshal_PPLI_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -171,7 +171,7 @@ internal static void Wrap_JniMarshal_PPLI_V (this _JniMarshal_PPLI_V callback, I internal static void Wrap_JniMarshal_PPLZ_V (this _JniMarshal_PPLZ_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, bool p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -182,7 +182,7 @@ internal static void Wrap_JniMarshal_PPLZ_V (this _JniMarshal_PPLZ_V callback, I internal static void Wrap_JniMarshal_PPLL_V (this _JniMarshal_PPLL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -193,7 +193,7 @@ internal static void Wrap_JniMarshal_PPLL_V (this _JniMarshal_PPLL_V callback, I internal static void Wrap_JniMarshal_PPLF_V (this _JniMarshal_PPLF_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, float p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -204,7 +204,7 @@ internal static void Wrap_JniMarshal_PPLF_V (this _JniMarshal_PPLF_V callback, I internal static IntPtr Wrap_JniMarshal_PPLI_L (this _JniMarshal_PPLI_L callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -215,7 +215,7 @@ internal static IntPtr Wrap_JniMarshal_PPLI_L (this _JniMarshal_PPLI_L callback, internal static IntPtr Wrap_JniMarshal_PPLL_L (this _JniMarshal_PPLL_L callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -226,7 +226,7 @@ internal static IntPtr Wrap_JniMarshal_PPLL_L (this _JniMarshal_PPLL_L callback, internal static bool Wrap_JniMarshal_PPLL_Z (this _JniMarshal_PPLL_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -237,7 +237,7 @@ internal static bool Wrap_JniMarshal_PPLL_Z (this _JniMarshal_PPLL_Z callback, I internal static bool Wrap_JniMarshal_PPIL_Z (this _JniMarshal_PPIL_Z callback, IntPtr jnienv, IntPtr klazz, int p0, IntPtr p1) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1); } catch (Exception e) when (_unhandled_exception (e)) { @@ -248,7 +248,7 @@ internal static bool Wrap_JniMarshal_PPIL_Z (this _JniMarshal_PPIL_Z callback, I internal static void Wrap_JniMarshal_PPIIL_V (this _JniMarshal_PPIIL_V callback, IntPtr jnienv, IntPtr klazz, int p0, int p1, IntPtr p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -259,7 +259,7 @@ internal static void Wrap_JniMarshal_PPIIL_V (this _JniMarshal_PPIIL_V callback, internal static int Wrap_JniMarshal_PPLII_I (this _JniMarshal_PPLII_I callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1, int p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -270,7 +270,7 @@ internal static int Wrap_JniMarshal_PPLII_I (this _JniMarshal_PPLII_I callback, internal static bool Wrap_JniMarshal_PPLII_Z (this _JniMarshal_PPLII_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1, int p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -281,7 +281,7 @@ internal static bool Wrap_JniMarshal_PPLII_Z (this _JniMarshal_PPLII_Z callback, internal static void Wrap_JniMarshal_PPLII_V (this _JniMarshal_PPLII_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1, int p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -292,7 +292,7 @@ internal static void Wrap_JniMarshal_PPLII_V (this _JniMarshal_PPLII_V callback, internal static void Wrap_JniMarshal_PPIII_V (this _JniMarshal_PPIII_V callback, IntPtr jnienv, IntPtr klazz, int p0, int p1, int p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -303,7 +303,7 @@ internal static void Wrap_JniMarshal_PPIII_V (this _JniMarshal_PPIII_V callback, internal static bool Wrap_JniMarshal_PPLLJ_Z (this _JniMarshal_PPLLJ_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, long p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -314,7 +314,7 @@ internal static bool Wrap_JniMarshal_PPLLJ_Z (this _JniMarshal_PPLLJ_Z callback, internal static void Wrap_JniMarshal_PPILL_V (this _JniMarshal_PPILL_V callback, IntPtr jnienv, IntPtr klazz, int p0, IntPtr p1, IntPtr p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -325,7 +325,7 @@ internal static void Wrap_JniMarshal_PPILL_V (this _JniMarshal_PPILL_V callback, internal static bool Wrap_JniMarshal_PPLIL_Z (this _JniMarshal_PPLIL_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1, IntPtr p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -336,7 +336,7 @@ internal static bool Wrap_JniMarshal_PPLIL_Z (this _JniMarshal_PPLIL_Z callback, internal static void Wrap_JniMarshal_PPLLL_V (this _JniMarshal_PPLLL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, IntPtr p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -347,7 +347,7 @@ internal static void Wrap_JniMarshal_PPLLL_V (this _JniMarshal_PPLLL_V callback, internal static IntPtr Wrap_JniMarshal_PPLLL_L (this _JniMarshal_PPLLL_L callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, IntPtr p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -358,7 +358,7 @@ internal static IntPtr Wrap_JniMarshal_PPLLL_L (this _JniMarshal_PPLLL_L callbac internal static bool Wrap_JniMarshal_PPLLL_Z (this _JniMarshal_PPLLL_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, IntPtr p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -369,7 +369,7 @@ internal static bool Wrap_JniMarshal_PPLLL_Z (this _JniMarshal_PPLLL_Z callback, internal static IntPtr Wrap_JniMarshal_PPIZI_L (this _JniMarshal_PPIZI_L callback, IntPtr jnienv, IntPtr klazz, int p0, bool p1, int p2) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2); } catch (Exception e) when (_unhandled_exception (e)) { @@ -380,7 +380,7 @@ internal static IntPtr Wrap_JniMarshal_PPIZI_L (this _JniMarshal_PPIZI_L callbac internal static void Wrap_JniMarshal_PPIIII_V (this _JniMarshal_PPIIII_V callback, IntPtr jnienv, IntPtr klazz, int p0, int p1, int p2, int p3) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2, p3); } catch (Exception e) when (_unhandled_exception (e)) { @@ -391,7 +391,7 @@ internal static void Wrap_JniMarshal_PPIIII_V (this _JniMarshal_PPIIII_V callbac internal static void Wrap_JniMarshal_PPLLLL_V (this _JniMarshal_PPLLLL_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1, IntPtr p2, IntPtr p3) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2, p3); } catch (Exception e) when (_unhandled_exception (e)) { @@ -402,7 +402,7 @@ internal static void Wrap_JniMarshal_PPLLLL_V (this _JniMarshal_PPLLLL_V callbac internal static bool Wrap_JniMarshal_PPLZZL_Z (this _JniMarshal_PPLZZL_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, bool p1, bool p2, IntPtr p3) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { return callback (jnienv, klazz, p0, p1, p2, p3); } catch (Exception e) when (_unhandled_exception (e)) { @@ -413,7 +413,7 @@ internal static bool Wrap_JniMarshal_PPLZZL_Z (this _JniMarshal_PPLZZL_Z callbac internal static void Wrap_JniMarshal_PPLIIII_V (this _JniMarshal_PPLIIII_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1, int p2, int p3, int p4) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2, p3, p4); } catch (Exception e) when (_unhandled_exception (e)) { @@ -424,7 +424,7 @@ internal static void Wrap_JniMarshal_PPLIIII_V (this _JniMarshal_PPLIIII_V callb internal static void Wrap_JniMarshal_PPZIIII_V (this _JniMarshal_PPZIIII_V callback, IntPtr jnienv, IntPtr klazz, bool p0, int p1, int p2, int p3, int p4) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2, p3, p4); } catch (Exception e) when (_unhandled_exception (e)) { @@ -435,7 +435,7 @@ internal static void Wrap_JniMarshal_PPZIIII_V (this _JniMarshal_PPZIIII_V callb internal static void Wrap_JniMarshal_PPLIIIIIIII_V (this _JniMarshal_PPLIIIIIIII_V callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { callback (jnienv, klazz, p0, p1, p2, p3, p4, p5, p6, p7, p8); } catch (Exception e) when (_unhandled_exception (e)) { diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt index 47e0a5353c2..b3bb339b6e7 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt @@ -251,7 +251,11 @@ namespace Android.Runtime static bool _unhandled_exception (Exception e) { if (Debugger.IsAttached || !JNIEnvInit.PropagateExceptions) { +#if NETCOREAPP + AndroidRuntimeInternal.mono_unhandled_exception?.Invoke (e); +#else JNIEnvInit.mono_unhandled_exception?.Invoke (e); +#endif return false; } return true; @@ -262,7 +266,7 @@ foreach (var info in delegateTypes) { #> internal static <#= info.Return #> Wrap<#= info.Type #> (this <#= info.Type #> callback, <#= info.Signature #>) { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); try { <#= info.Return != "void" ? "return " : "" #>callback (<#= info.Invoke #>); } catch (Exception e) when (_unhandled_exception (e)) { diff --git a/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs b/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs index 6b2c558572e..3487514a6a7 100644 --- a/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs +++ b/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs @@ -76,7 +76,6 @@ internal static class RuntimeNativeMethods [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] internal static extern int _monodroid_max_gref_get (); - #if NETCOREAPP [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern void monodroid_unhandled_exception (Exception javaException); diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 607c8a2ac9b..1fdbe977281 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -229,6 +229,7 @@ + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index d1e8f4818cc..dfca1b07a31 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -89,6 +89,9 @@ public class GenerateJavaStubs : AndroidTask public string SupportedOSPlatformVersion { get; set; } + [Required] + public ITaskItem[] Environments { get; set; } + [Output] public string [] GeneratedBinaryTypeMaps { get; set; } @@ -229,6 +232,10 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) return; if (useMarshalMethods) { + // We need to parse the environment files supplied by the user to see if they want to use broken exception transitions. This information is needed + // in order to properly generate wrapper methods in the marshal methods assembly rewriter. + // We don't care about those generated by us, since they won't contain the `XA_BROKEN_EXCEPTION_TRANSITIONS` variable we look for. + var environmentParser = new EnvironmentFilesParser (); var targetPaths = new List (); if (!LinkingEnabled) { @@ -244,7 +251,7 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) } var rewriter = new MarshalMethodsAssemblyRewriter (classifier.MarshalMethods, classifier.Assemblies, marshalMethodsAssemblyPaths, Log); - rewriter.Rewrite (res, targetPaths); + rewriter.Rewrite (res, targetPaths, environmentParser.AreBrokenExceptionTransitionsEnabled (Environments)); } // Step 3 - Generate type maps diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index ea88c2c4906..e8638b29761 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -176,8 +176,6 @@ static internal AndroidTargetArch GetAndroidTargetArchForAbi (string abi) void AddEnvironment () { bool usesMonoAOT = false; - bool usesAssemblyPreload = EnablePreloadAssembliesDefault; - bool brokenExceptionTransitions = false; var environmentVariables = new Dictionary (StringComparer.Ordinal); var systemProperties = new Dictionary (StringComparer.Ordinal); @@ -190,84 +188,48 @@ void AddEnvironment () usesMonoAOT = true; } - bool haveLogLevel = false; - bool haveMonoDebug = false; - bool havebuildId = false; - bool haveHttpMessageHandler = false; - bool haveTlsProvider = false; - bool haveMonoGCParams = false; - SequencePointsMode sequencePointsMode; if (!Aot.TryGetSequencePointsMode (AndroidSequencePointsMode, out sequencePointsMode)) sequencePointsMode = SequencePointsMode.None; - foreach (ITaskItem env in Environments ?? Array.Empty ()) { - foreach (string line in File.ReadLines (env.ItemSpec)) { - var lineToWrite = line; - if (lineToWrite.StartsWith ("MONO_LOG_LEVEL=", StringComparison.Ordinal)) - haveLogLevel = true; - if (lineToWrite.StartsWith ("MONO_GC_PARAMS=", StringComparison.Ordinal)) { - haveMonoGCParams = true; - if (lineToWrite.IndexOf ("bridge-implementation=old", StringComparison.Ordinal) >= 0) { - Log.LogCodedWarning ("XA2000", Properties.Resources.XA2000_gcParams_bridgeImpl); - } - } - if (lineToWrite.StartsWith ("XAMARIN_BUILD_ID=", StringComparison.Ordinal)) - havebuildId = true; - if (lineToWrite.StartsWith ("MONO_DEBUG=", StringComparison.Ordinal)) { - haveMonoDebug = true; - if (sequencePointsMode != SequencePointsMode.None && !lineToWrite.Contains ("gen-compact-seq-points")) - lineToWrite = line + ",gen-compact-seq-points"; - } - if (lineToWrite.StartsWith ("XA_HTTP_CLIENT_HANDLER_TYPE=", StringComparison.Ordinal)) - haveHttpMessageHandler = true; - - if (!UsingAndroidNETSdk && lineToWrite.StartsWith ("XA_TLS_PROVIDER=", StringComparison.Ordinal)) - haveTlsProvider = true; - - if (lineToWrite.StartsWith ("mono.enable_assembly_preload=", StringComparison.Ordinal)) { - int idx = lineToWrite.IndexOf ('='); - uint val; - if (idx < lineToWrite.Length - 1 && UInt32.TryParse (lineToWrite.Substring (idx + 1), out val)) { - usesAssemblyPreload = idx == 1; - } - continue; - } - if (lineToWrite.StartsWith ("XA_BROKEN_EXCEPTION_TRANSITIONS=", StringComparison.Ordinal)) { - brokenExceptionTransitions = true; - continue; - } + // Even though environment files were potentially parsed in GenerateJavaStubs, we need to do it here again because we might have additional environment + // files (generated by us) which weren't present by the time GeneratJavaStubs ran. + var environmentParser = new EnvironmentFilesParser { + BrokenExceptionTransitions = false, + UsesAssemblyPreload = EnablePreloadAssembliesDefault, + }; + environmentParser.Parse (Environments, sequencePointsMode, UsingAndroidNETSdk, Log); - AddEnvironmentVariableLine (lineToWrite); - } + foreach (string line in environmentParser.EnvironmentVariableLines) { + AddEnvironmentVariableLine (line); } - if (_Debug && !haveLogLevel) { + if (_Debug && !environmentParser.HaveLogLevel) { AddEnvironmentVariable (defaultLogLevel[0], defaultLogLevel[1]); } - if (sequencePointsMode != SequencePointsMode.None && !haveMonoDebug) { + if (sequencePointsMode != SequencePointsMode.None && !environmentParser.HaveMonoDebug) { AddEnvironmentVariable (defaultMonoDebug[0], defaultMonoDebug[1]); } - if (!havebuildId) + if (!environmentParser.HavebuildId) AddEnvironmentVariable ("XAMARIN_BUILD_ID", BuildId); - if (!haveHttpMessageHandler) { + if (!environmentParser.HaveHttpMessageHandler) { if (HttpClientHandlerType == null) AddEnvironmentVariable (defaultHttpMessageHandler[0], defaultHttpMessageHandler[1]); else AddEnvironmentVariable ("XA_HTTP_CLIENT_HANDLER_TYPE", HttpClientHandlerType.Trim ()); } - if (!UsingAndroidNETSdk && !haveTlsProvider) { + if (!UsingAndroidNETSdk && !environmentParser.HaveTlsProvider) { if (TlsProvider == null) AddEnvironmentVariable (defaultTlsProvider[0], defaultTlsProvider[1]); else AddEnvironmentVariable ("XA_TLS_PROVIDER", TlsProvider.Trim ()); } - if (!haveMonoGCParams) { + if (!environmentParser.HaveMonoGCParams) { if (EnableSGenConcurrent) AddEnvironmentVariable ("MONO_GC_PARAMS", "major=marksweep-conc"); else @@ -414,11 +376,11 @@ void AddEnvironment () IsBundledApp = IsBundledApplication, UsesMonoAOT = usesMonoAOT, UsesMonoLLVM = EnableLLVM, - UsesAssemblyPreload = usesAssemblyPreload, + UsesAssemblyPreload = environmentParser.UsesAssemblyPreload, MonoAOTMode = aotMode.ToString ().ToLowerInvariant (), AotEnableLazyLoad = AndroidAotEnableLazyLoad, AndroidPackageName = AndroidPackageName, - BrokenExceptionTransitions = brokenExceptionTransitions, + BrokenExceptionTransitions = environmentParser.BrokenExceptionTransitions, PackageNamingPolicy = pnp, BoundExceptionType = boundExceptionType, InstantRunEnabled = InstantRunEnabled, diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs new file mode 100644 index 00000000000..c663d3eaa8d --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Xamarin.Android.Tasks +{ + class EnvironmentFilesParser + { + public bool BrokenExceptionTransitions { get; set; } + public bool HavebuildId { get; private set; } + public bool HaveHttpMessageHandler { get; private set; } + public bool HaveLogLevel { get; private set; } + public bool HaveMonoDebug { get; private set; } + public bool HaveMonoGCParams { get; private set; } + public bool HaveTlsProvider { get; private set; } + public bool UsesAssemblyPreload { get; set; } + public List EnvironmentVariableLines { get; } = new List (); + + public bool AreBrokenExceptionTransitionsEnabled (ITaskItem[] environments) + { + foreach (ITaskItem env in environments ?? Array.Empty ()) { + foreach (string line in File.ReadLines (env.ItemSpec)) { + if (IsBrokenExceptionTransitionsLine (line.Trim ())) { + return true; + } + } + } + + return false; + } + + public void Parse (ITaskItem[] environments, SequencePointsMode sequencePointsMode, bool usingAndroidNETSdk, TaskLoggingHelper log) + { + foreach (ITaskItem env in environments ?? Array.Empty ()) { + foreach (string line in File.ReadLines (env.ItemSpec)) { + var lineToWrite = line.Trim (); + if (lineToWrite.StartsWith ("MONO_LOG_LEVEL=", StringComparison.Ordinal)) + HaveLogLevel = true; + if (lineToWrite.StartsWith ("MONO_GC_PARAMS=", StringComparison.Ordinal)) { + HaveMonoGCParams = true; + if (lineToWrite.IndexOf ("bridge-implementation=old", StringComparison.Ordinal) >= 0) { + log.LogCodedWarning ("XA2000", Properties.Resources.XA2000_gcParams_bridgeImpl); + } + } + if (lineToWrite.StartsWith ("XAMARIN_BUILD_ID=", StringComparison.Ordinal)) + HavebuildId = true; + if (lineToWrite.StartsWith ("MONO_DEBUG=", StringComparison.Ordinal)) { + HaveMonoDebug = true; + if (sequencePointsMode != SequencePointsMode.None && !lineToWrite.Contains ("gen-compact-seq-points")) + lineToWrite = line + ",gen-compact-seq-points"; + } + if (lineToWrite.StartsWith ("XA_HTTP_CLIENT_HANDLER_TYPE=", StringComparison.Ordinal)) + HaveHttpMessageHandler = true; + + if (!usingAndroidNETSdk && lineToWrite.StartsWith ("XA_TLS_PROVIDER=", StringComparison.Ordinal)) + HaveTlsProvider = true; + + if (lineToWrite.StartsWith ("mono.enable_assembly_preload=", StringComparison.Ordinal)) { + int idx = lineToWrite.IndexOf ('='); + uint val; + if (idx < lineToWrite.Length - 1 && UInt32.TryParse (lineToWrite.Substring (idx + 1), out val)) { + UsesAssemblyPreload = idx == 1; + } + continue; + } + if (IsBrokenExceptionTransitionsLine (lineToWrite)) { + BrokenExceptionTransitions = true; + continue; + } + + EnvironmentVariableLines.Add (lineToWrite); + } + } + } + + bool IsBrokenExceptionTransitionsLine (string lineToWrite) => lineToWrite.StartsWith ("XA_BROKEN_EXCEPTION_TRANSITIONS=", StringComparison.Ordinal); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index c60819eab77..1fecc4e8b75 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -12,6 +12,15 @@ namespace Xamarin.Android.Tasks { class MarshalMethodsAssemblyRewriter { + sealed class AssemblyImports + { + public CustomAttribute UnmanagedCallersOnlyAttribute; + public FieldReference MonoUnhandledExceptionMethod; + public MethodReference UnhandledExceptionMethod; + public MethodReference WaitForBridgeProcessingMethod; + public TypeReference SystemException; + } + IDictionary> methods; ICollection uniqueAssemblies; IDictionary > assemblyPaths; @@ -25,7 +34,7 @@ public MarshalMethodsAssemblyRewriter (IDictionary targetAssemblyPaths) + public void Rewrite (DirectoryAssemblyResolver resolver, List targetAssemblyPaths, bool brokenExceptionTransitions) { if (resolver == null) { throw new ArgumentNullException (nameof (resolver)); @@ -39,10 +48,33 @@ public void Rewrite (DirectoryAssemblyResolver resolver, List targetAsse throw new ArgumentException ("must contain at least one target path", nameof (targetAssemblyPaths)); } + AssemblyDefinition? monoAndroidRuntime = resolver.Resolve ("Mono.Android.Runtime"); + if (monoAndroidRuntime == null) { + throw new InvalidOperationException ($"Internal error: unable to load the Mono.Android.Runtime assembly"); + } + + TypeDefinition runtime = FindType (monoAndroidRuntime, "Android.Runtime.AndroidRuntimeInternal", required: true)!; + MethodDefinition waitForBridgeProcessingMethod = FindMethod (runtime, "WaitForBridgeProcessing", required: true)!; + FieldDefinition monoUnhandledExceptionMethod = FindField (runtime, "mono_unhandled_exception_method", required: true)!; + + TypeDefinition androidEnvironment = FindType (monoAndroidRuntime, "Android.Runtime.AndroidEnvironmentInternal", required: true)!; + MethodDefinition unhandledExceptionMethod = FindMethod (androidEnvironment, "UnhandledException", required: true)!; + + AssemblyDefinition corlib = resolver.Resolve ("System.Private.CoreLib"); + TypeDefinition systemException = FindType (corlib, "System.Exception", required: true); + MethodDefinition unmanagedCallersOnlyAttributeCtor = GetUnmanagedCallersOnlyAttributeConstructor (resolver); - var unmanagedCallersOnlyAttributes = new Dictionary (); + var assemblyImports = new Dictionary (); foreach (AssemblyDefinition asm in uniqueAssemblies) { - unmanagedCallersOnlyAttributes.Add (asm, CreateImportedUnmanagedCallersOnlyAttribute (asm, unmanagedCallersOnlyAttributeCtor)); + var imports = new AssemblyImports { + MonoUnhandledExceptionMethod = asm.MainModule.ImportReference (monoUnhandledExceptionMethod), + SystemException = asm.MainModule.ImportReference (systemException), + UnhandledExceptionMethod = asm.MainModule.ImportReference (unhandledExceptionMethod), + UnmanagedCallersOnlyAttribute = CreateImportedUnmanagedCallersOnlyAttribute (asm, unmanagedCallersOnlyAttributeCtor), + WaitForBridgeProcessingMethod = asm.MainModule.ImportReference (waitForBridgeProcessingMethod), + }; + + assemblyImports.Add (asm, imports); } log.LogDebugMessage ("Rewriting assemblies for marshal methods support"); @@ -58,10 +90,11 @@ public void Rewrite (DirectoryAssemblyResolver resolver, List targetAsse if (method.NeedsBlittableWorkaround) { log.LogDebugMessage ($"Generating non-blittable type wrapper for callback method {method.NativeCallback.FullName}"); - method.NativeCallbackWrapper = GenerateBlittableWrapper (method, unmanagedCallersOnlyAttributes); + method.NativeCallbackWrapper = GenerateBlittableWrapper (method, assemblyImports, brokenExceptionTransitions); } else { log.LogDebugMessage ($"Adding the 'UnmanagedCallersOnly' attribute to callback method {method.NativeCallback.FullName}"); - method.NativeCallback.CustomAttributes.Add (unmanagedCallersOnlyAttributes [method.NativeCallback.Module.Assembly]); + //method.NativeCallback.CustomAttributes.Add (assemblyImports [method.NativeCallback.Module.Assembly].UnmanagedCallersOnlyAttribute); + method.NativeCallbackWrapper = GenerateExceptionWrapper (method, assemblyImports, brokenExceptionTransitions); } if (method.Connector != null) { @@ -162,57 +195,114 @@ void RemoveFile (string? path) } } - MethodDefinition GenerateBlittableWrapper (MarshalMethodEntry method, Dictionary unmanagedCallersOnlyAttributes) + string GetWrapperName (MethodDefinition method) => $"{method.Name}_mm_wrapper"; + + MethodDefinition GenerateExceptionWrapper (MarshalMethodEntry method, Dictionary assemblyImports, bool brokenExceptionTransitions) { MethodDefinition callback = method.NativeCallback; - string wrapperName = $"{callback.Name}_mm_wrapper"; - TypeReference retType = MapToBlittableTypeIfNecessary (callback.ReturnType, out bool returnTypeMapped); - bool hasReturnValue = String.Compare ("System.Void", retType.FullName, StringComparison.Ordinal) != 0; - var wrapperMethod = new MethodDefinition (wrapperName, callback.Attributes, retType); + AssemblyImports imports = assemblyImports [callback.Module.Assembly]; + string wrapperName = GetWrapperName (callback); + bool hasReturnValue = String.Compare ("System.Void", callback.ReturnType.FullName, StringComparison.Ordinal) != 0; + var wrapperMethod = new MethodDefinition (wrapperName, callback.Attributes, callback.ReturnType); + callback.DeclaringType.Methods.Add (wrapperMethod); - wrapperMethod.CustomAttributes.Add (unmanagedCallersOnlyAttributes [callback.Module.Assembly]); + wrapperMethod.CustomAttributes.Add (imports.UnmanagedCallersOnlyAttribute); MethodBody body = wrapperMethod.Body; - int nparam = 0; + VariableDefinition? retval = null; + if (hasReturnValue) { + body.InitLocals = true; + retval = new VariableDefinition (callback.ReturnType); + body.Variables.Add (retval); + } + var exceptionHandler = new ExceptionHandler (ExceptionHandlerType.Catch) { + CatchType = imports.SystemException, + }; + + body.ExceptionHandlers.Add (exceptionHandler); + + uint nparam = 0; foreach (ParameterDefinition pdef in callback.Parameters) { TypeReference newType = MapToBlittableTypeIfNecessary (pdef.ParameterType, out _); wrapperMethod.Parameters.Add (new ParameterDefinition (pdef.Name, pdef.Attributes, newType)); + body.Instructions.Add (GetLoadArgInstruction (nparam++, pdef)); + } - OpCode ldargOp; - bool paramRef = false; - switch (nparam++) { - case 0: - ldargOp = OpCodes.Ldarg_0; - break; - - case 1: - ldargOp = OpCodes.Ldarg_1; - break; - - case 2: - ldargOp = OpCodes.Ldarg_2; - break; - - case 3: - ldargOp = OpCodes.Ldarg_3; - break; - - default: - ldargOp = OpCodes.Ldarg_S; - paramRef = true; - break; - } + exceptionHandler.TryStart = body.Instructions[0]; + body.Instructions.Add (Instruction.Create (OpCodes.Call, callback)); + + if (hasReturnValue) { + body.Instructions.Add (Instruction.Create (OpCodes.Stloc, retval)); + } + + Instruction ret = Instruction.Create (OpCodes.Ret); + Instruction? retValLoadInst = null; + Instruction leaveTarget; - Instruction ldarg; + if (hasReturnValue) { + retValLoadInst = Instruction.Create (OpCodes.Ldloc, retval); + leaveTarget = retValLoadInst; + } else { + leaveTarget = ret; + } + + var leaveTryInst = Instruction.Create (OpCodes.Leave_S, leaveTarget); + body.Instructions.Add (leaveTryInst); + exceptionHandler.TryEnd = leaveTryInst; + + var exceptionVar = new VariableDefinition (imports.SystemException); + body.Variables.Add (exceptionVar); + + var catchStartInst = Instruction.Create (OpCodes.Stloc, exceptionVar); + exceptionHandler.HandlerStart = catchStartInst; + + body.Instructions.Add (catchStartInst); + body.Instructions.Add (Instruction.Create (OpCodes.Ldarg_0)); + body.Instructions.Add (Instruction.Create (OpCodes.Ldloc, exceptionVar)); + + if (brokenExceptionTransitions) { + // TODO: add nullcheck + body.Instructions.Add (Instruction.Create (OpCodes.Call, imports.MonoUnhandledExceptionMethod)); + body.Instructions.Add (Instruction.Create (OpCodes.Throw)); + } else { + body.Instructions.Add (Instruction.Create (OpCodes.Call, imports.UnhandledExceptionMethod)); - if (!paramRef) { - ldarg = Instruction.Create (ldargOp); - } else { - ldarg = Instruction.Create (ldargOp, pdef); + if (hasReturnValue) { + // TODO: add equivalent to `return default` } + } + + var leaveCatchInst = Instruction.Create (OpCodes.Leave_S, leaveTarget); + exceptionHandler.HandlerEnd = leaveCatchInst; + body.Instructions.Add (leaveCatchInst); + + if (hasReturnValue) { + body.Instructions.Add (retValLoadInst); + } + body.Instructions.Add (ret); + + return wrapperMethod; + } + + MethodDefinition GenerateBlittableWrapper (MarshalMethodEntry method, Dictionary assemblyImports, bool brokenExceptionTransitions) + { + MethodDefinition callback = method.NativeCallback; + AssemblyImports imports = assemblyImports [callback.Module.Assembly]; + string wrapperName = GetWrapperName (callback); + TypeReference retType = MapToBlittableTypeIfNecessary (callback.ReturnType, out bool returnTypeMapped); + bool hasReturnValue = String.Compare ("System.Void", retType.FullName, StringComparison.Ordinal) != 0; + var wrapperMethod = new MethodDefinition (wrapperName, callback.Attributes, retType); + callback.DeclaringType.Methods.Add (wrapperMethod); + wrapperMethod.CustomAttributes.Add (imports.UnmanagedCallersOnlyAttribute); + + MethodBody body = wrapperMethod.Body; + uint nparam = 0; - body.Instructions.Add (ldarg); + foreach (ParameterDefinition pdef in callback.Parameters) { + TypeReference newType = MapToBlittableTypeIfNecessary (pdef.ParameterType, out _); + wrapperMethod.Parameters.Add (new ParameterDefinition (pdef.Name, pdef.Attributes, newType)); + body.Instructions.Add (GetLoadArgInstruction (nparam++, pdef)); if (!pdef.ParameterType.IsBlittable ()) { GenerateNonBlittableConversion (pdef.ParameterType, newType); @@ -276,6 +366,41 @@ void ThrowUnsupportedType (TypeReference type) } } + Instruction GetLoadArgInstruction (uint nparam, ParameterDefinition pdef) + { + OpCode ldargOp; + bool paramRef = false; + + switch (nparam++) { + case 0: + ldargOp = OpCodes.Ldarg_0; + break; + + case 1: + ldargOp = OpCodes.Ldarg_1; + break; + + case 2: + ldargOp = OpCodes.Ldarg_2; + break; + + case 3: + ldargOp = OpCodes.Ldarg_3; + break; + + default: + ldargOp = OpCodes.Ldarg_S; + paramRef = true; + break; + } + + if (!paramRef) { + return Instruction.Create (ldargOp); + } + + return Instruction.Create (ldargOp, pdef); + } + TypeReference MapToBlittableTypeIfNecessary (TypeReference type, out bool typeMapped) { if (type.IsBlittable () || String.Compare ("System.Void", type.FullName, StringComparison.Ordinal) == 0) { @@ -349,5 +474,52 @@ CustomAttribute CreateImportedUnmanagedCallersOnlyAttribute (AssemblyDefinition { return new CustomAttribute (targetAssembly.MainModule.ImportReference (unmanagedCallersOnlyAtributeCtor)); } + + MethodDefinition? FindMethod (TypeDefinition type, string methodName, bool required) + { + log.LogDebugMessage ($"Looking for method '{methodName}' in type {type}"); + foreach (MethodDefinition method in type.Methods) { + log.LogDebugMessage ($" method: {method.Name}"); + if (String.Compare (methodName, method.Name, StringComparison.Ordinal) == 0) { + log.LogDebugMessage (" match!"); + return method; + } + } + + if (required) { + throw new InvalidOperationException ($"Internal error: required method '{methodName}' in type {type} not found"); + } + + return null; + } + + FieldDefinition? FindField (TypeDefinition type, string fieldName, bool required) + { + foreach (FieldDefinition field in type.Fields) { + if (String.Compare (fieldName, field.Name, StringComparison.Ordinal) == 0) { + return field; + } + } + + if (required) { + throw new InvalidOperationException ($"Internal error: required field '{fieldName}' in type {type} not found"); + } + + return null; + } + + TypeDefinition? FindType (AssemblyDefinition asm, string typeName, bool required) + { + TypeDefinition? type = asm.MainModule.FindType (typeName); + if (type != null) { + return type; + } + + if (required) { + throw new InvalidOperationException ($"Internal error: required type '{typeName}' in assembly {asm} not found"); + } + + return null; + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ParsedEnvironment.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ParsedEnvironment.cs new file mode 100644 index 00000000000..f353bb952ab --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ParsedEnvironment.cs @@ -0,0 +1,6 @@ +namespace Xamarin.Android.Tasks +{ + class ParsedEnvironment + { + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 87f7642830b..4d32b377071 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1472,7 +1472,7 @@ because xbuild doesn't support framework reference assemblies. @@ -1518,7 +1518,8 @@ because xbuild doesn't support framework reference assemblies. CheckedBuild="$(_AndroidCheckedBuild)" EnableMarshalMethods="$(_AndroidUseMarshalMethods)" LinkingEnabled="$(_LinkingEnabled)" - IntermediateOutputDirectory="$(IntermediateOutputPath)"> + IntermediateOutputDirectory="$(IntermediateOutputPath)" + Environments="@(AndroidEnvironment);@(LibraryEnvironments)"> @@ -1689,7 +1690,6 @@ because xbuild doesn't support framework reference assemblies. _ManifestMerger; _ConvertCustomView; $(_AfterConvertCustomView); - _GenerateEnvironmentFiles; _AddStaticResources; $(_AfterAddStaticResources); _PrepareAssemblies; From 983ad70d2f1a989b2298d2f6bfd81960fbf3bef6 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 21 Sep 2022 20:22:17 +0200 Subject: [PATCH 62/79] Exception wrapper appears to work now --- .../Android.Runtime/AndroidRuntime.cs | 2 +- .../Android.Runtime/AndroidRuntimeInternal.cs | 4 +- .../MarshalMethodsAssemblyRewriter.cs | 188 ++++++++++-------- .../Utilities/ParsedEnvironment.cs | 6 - src/monodroid/jni/monodroid-glue.cc | 53 +++-- src/monodroid/jni/osbridge.cc | 8 +- src/monodroid/jni/shared-constants.hh | 1 + 7 files changed, 148 insertions(+), 114 deletions(-) delete mode 100644 src/Xamarin.Android.Build.Tasks/Utilities/ParsedEnvironment.cs diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index ba63b37bfca..67c780c3bf4 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -635,7 +635,7 @@ class AndroidValueManager : JniRuntime.JniValueManager { public override void WaitForGCBridgeProcessing () { - JNIEnv.WaitForBridgeProcessing (); + AndroidRuntimeInternal.WaitForBridgeProcessing (); } public override IJavaPeerable? CreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions options, Type? targetType) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs index 106b29db0c7..51834a19e37 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs @@ -3,7 +3,7 @@ namespace Android.Runtime { - static internal class AndroidRuntimeInternal + public static class AndroidRuntimeInternal { internal static MethodInfo? mono_unhandled_exception_method = null; #if NETCOREAPP @@ -29,7 +29,7 @@ internal static void InitializeUnhandledExceptionMethod () } } - internal static void WaitForBridgeProcessing () + public static void WaitForBridgeProcessing () { if (!BridgeProcessing) return; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index 1fecc4e8b75..ff0bd1f5ff9 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -14,11 +14,11 @@ class MarshalMethodsAssemblyRewriter { sealed class AssemblyImports { - public CustomAttribute UnmanagedCallersOnlyAttribute; - public FieldReference MonoUnhandledExceptionMethod; + public MethodReference MonoUnhandledExceptionMethod; + public TypeReference SystemException; public MethodReference UnhandledExceptionMethod; + public CustomAttribute UnmanagedCallersOnlyAttribute; public MethodReference WaitForBridgeProcessingMethod; - public TypeReference SystemException; } IDictionary> methods; @@ -55,11 +55,13 @@ public void Rewrite (DirectoryAssemblyResolver resolver, List targetAsse TypeDefinition runtime = FindType (monoAndroidRuntime, "Android.Runtime.AndroidRuntimeInternal", required: true)!; MethodDefinition waitForBridgeProcessingMethod = FindMethod (runtime, "WaitForBridgeProcessing", required: true)!; - FieldDefinition monoUnhandledExceptionMethod = FindField (runtime, "mono_unhandled_exception_method", required: true)!; TypeDefinition androidEnvironment = FindType (monoAndroidRuntime, "Android.Runtime.AndroidEnvironmentInternal", required: true)!; MethodDefinition unhandledExceptionMethod = FindMethod (androidEnvironment, "UnhandledException", required: true)!; + TypeDefinition runtimeNativeMethods = FindType (monoAndroidRuntime, "Android.Runtime.RuntimeNativeMethods", required: true); + MethodDefinition monoUnhandledExceptionMethod = FindMethod (runtimeNativeMethods, "monodroid_debugger_unhandled_exception", required: true); + AssemblyDefinition corlib = resolver.Resolve ("System.Private.CoreLib"); TypeDefinition systemException = FindType (corlib, "System.Exception", required: true); @@ -67,9 +69,9 @@ public void Rewrite (DirectoryAssemblyResolver resolver, List targetAsse var assemblyImports = new Dictionary (); foreach (AssemblyDefinition asm in uniqueAssemblies) { var imports = new AssemblyImports { - MonoUnhandledExceptionMethod = asm.MainModule.ImportReference (monoUnhandledExceptionMethod), - SystemException = asm.MainModule.ImportReference (systemException), - UnhandledExceptionMethod = asm.MainModule.ImportReference (unhandledExceptionMethod), + MonoUnhandledExceptionMethod = asm.MainModule.ImportReference (monoUnhandledExceptionMethod), + SystemException = asm.MainModule.ImportReference (systemException), + UnhandledExceptionMethod = asm.MainModule.ImportReference (unhandledExceptionMethod), UnmanagedCallersOnlyAttribute = CreateImportedUnmanagedCallersOnlyAttribute (asm, unmanagedCallersOnlyAttributeCtor), WaitForBridgeProcessingMethod = asm.MainModule.ImportReference (waitForBridgeProcessingMethod), }; @@ -88,15 +90,7 @@ public void Rewrite (DirectoryAssemblyResolver resolver, List targetAsse continue; } - if (method.NeedsBlittableWorkaround) { - log.LogDebugMessage ($"Generating non-blittable type wrapper for callback method {method.NativeCallback.FullName}"); - method.NativeCallbackWrapper = GenerateBlittableWrapper (method, assemblyImports, brokenExceptionTransitions); - } else { - log.LogDebugMessage ($"Adding the 'UnmanagedCallersOnly' attribute to callback method {method.NativeCallback.FullName}"); - //method.NativeCallback.CustomAttributes.Add (assemblyImports [method.NativeCallback.Module.Assembly].UnmanagedCallersOnlyAttribute); - method.NativeCallbackWrapper = GenerateExceptionWrapper (method, assemblyImports, brokenExceptionTransitions); - } - + method.NativeCallbackWrapper = GenerateWrapper (method, assemblyImports, brokenExceptionTransitions); if (method.Connector != null) { if (method.Connector.IsStatic && method.Connector.IsPrivate) { log.LogDebugMessage ($"Removing connector method {method.Connector.FullName}"); @@ -195,44 +189,62 @@ void RemoveFile (string? path) } } - string GetWrapperName (MethodDefinition method) => $"{method.Name}_mm_wrapper"; - - MethodDefinition GenerateExceptionWrapper (MarshalMethodEntry method, Dictionary assemblyImports, bool brokenExceptionTransitions) + MethodDefinition GenerateWrapper (MarshalMethodEntry method, Dictionary assemblyImports, bool brokenExceptionTransitions) { MethodDefinition callback = method.NativeCallback; AssemblyImports imports = assemblyImports [callback.Module.Assembly]; - string wrapperName = GetWrapperName (callback); + string wrapperName = $"{callback.Name}_mm_wrapper"; + TypeReference retType = MapToBlittableTypeIfNecessary (callback.ReturnType, out bool returnTypeMapped); bool hasReturnValue = String.Compare ("System.Void", callback.ReturnType.FullName, StringComparison.Ordinal) != 0; - var wrapperMethod = new MethodDefinition (wrapperName, callback.Attributes, callback.ReturnType); + var wrapperMethod = new MethodDefinition (wrapperName, callback.Attributes, retType); callback.DeclaringType.Methods.Add (wrapperMethod); wrapperMethod.CustomAttributes.Add (imports.UnmanagedCallersOnlyAttribute); MethodBody body = wrapperMethod.Body; + body.InitLocals = true; + VariableDefinition? retval = null; if (hasReturnValue) { - body.InitLocals = true; - retval = new VariableDefinition (callback.ReturnType); + retval = new VariableDefinition (retType); body.Variables.Add (retval); } + body.Instructions.Add (Instruction.Create (OpCodes.Call, imports.WaitForBridgeProcessingMethod)); var exceptionHandler = new ExceptionHandler (ExceptionHandlerType.Catch) { CatchType = imports.SystemException, }; body.ExceptionHandlers.Add (exceptionHandler); + Instruction? firstTryInstruction = null; + Instruction? inst = null; uint nparam = 0; foreach (ParameterDefinition pdef in callback.Parameters) { TypeReference newType = MapToBlittableTypeIfNecessary (pdef.ParameterType, out _); wrapperMethod.Parameters.Add (new ParameterDefinition (pdef.Name, pdef.Attributes, newType)); - body.Instructions.Add (GetLoadArgInstruction (nparam++, pdef)); + + inst = GetLoadArgInstruction (nparam++, pdef); + if (firstTryInstruction == null) { + firstTryInstruction = inst; + } + + body.Instructions.Add (inst); + + if (!pdef.ParameterType.IsBlittable ()) { + GenerateNonBlittableConversion (pdef.ParameterType, newType); + } } - exceptionHandler.TryStart = body.Instructions[0]; - body.Instructions.Add (Instruction.Create (OpCodes.Call, callback)); + inst = Instruction.Create (OpCodes.Call, callback); + if (firstTryInstruction == null) { + firstTryInstruction = inst; + } + body.Instructions.Add (inst); - if (hasReturnValue) { + exceptionHandler.TryStart = firstTryInstruction; + + if (hasReturnValue && !returnTypeMapped) { body.Instructions.Add (Instruction.Create (OpCodes.Stloc, retval)); } @@ -241,15 +253,18 @@ MethodDefinition GenerateExceptionWrapper (MarshalMethodEntry method, Dictionary Instruction leaveTarget; if (hasReturnValue) { + if (returnTypeMapped) { + GenerateRetValCast (callback.ReturnType, retType); + body.Instructions.Add (Instruction.Create (OpCodes.Stloc, retval)); + } + retValLoadInst = Instruction.Create (OpCodes.Ldloc, retval); leaveTarget = retValLoadInst; } else { leaveTarget = ret; } - var leaveTryInst = Instruction.Create (OpCodes.Leave_S, leaveTarget); - body.Instructions.Add (leaveTryInst); - exceptionHandler.TryEnd = leaveTryInst; + body.Instructions.Add (Instruction.Create (OpCodes.Leave_S, leaveTarget)); var exceptionVar = new VariableDefinition (imports.SystemException); body.Variables.Add (exceptionVar); @@ -257,66 +272,36 @@ MethodDefinition GenerateExceptionWrapper (MarshalMethodEntry method, Dictionary var catchStartInst = Instruction.Create (OpCodes.Stloc, exceptionVar); exceptionHandler.HandlerStart = catchStartInst; + // TryEnd must point to the next instruction after the try block + exceptionHandler.TryEnd = catchStartInst; + body.Instructions.Add (catchStartInst); body.Instructions.Add (Instruction.Create (OpCodes.Ldarg_0)); body.Instructions.Add (Instruction.Create (OpCodes.Ldloc, exceptionVar)); if (brokenExceptionTransitions) { - // TODO: add nullcheck body.Instructions.Add (Instruction.Create (OpCodes.Call, imports.MonoUnhandledExceptionMethod)); body.Instructions.Add (Instruction.Create (OpCodes.Throw)); } else { body.Instructions.Add (Instruction.Create (OpCodes.Call, imports.UnhandledExceptionMethod)); if (hasReturnValue) { - // TODO: add equivalent to `return default` + AddSetDefaultValueInstructions (body, retType, retval); } } - var leaveCatchInst = Instruction.Create (OpCodes.Leave_S, leaveTarget); - exceptionHandler.HandlerEnd = leaveCatchInst; - body.Instructions.Add (leaveCatchInst); + body.Instructions.Add (Instruction.Create (OpCodes.Leave_S, leaveTarget)); + // HandlerEnd must point to the next instruction after the catch block if (hasReturnValue) { body.Instructions.Add (retValLoadInst); + exceptionHandler.HandlerEnd = retValLoadInst; + } else { + exceptionHandler.HandlerEnd = ret; } body.Instructions.Add (ret); return wrapperMethod; - } - - MethodDefinition GenerateBlittableWrapper (MarshalMethodEntry method, Dictionary assemblyImports, bool brokenExceptionTransitions) - { - MethodDefinition callback = method.NativeCallback; - AssemblyImports imports = assemblyImports [callback.Module.Assembly]; - string wrapperName = GetWrapperName (callback); - TypeReference retType = MapToBlittableTypeIfNecessary (callback.ReturnType, out bool returnTypeMapped); - bool hasReturnValue = String.Compare ("System.Void", retType.FullName, StringComparison.Ordinal) != 0; - var wrapperMethod = new MethodDefinition (wrapperName, callback.Attributes, retType); - callback.DeclaringType.Methods.Add (wrapperMethod); - wrapperMethod.CustomAttributes.Add (imports.UnmanagedCallersOnlyAttribute); - - MethodBody body = wrapperMethod.Body; - uint nparam = 0; - - foreach (ParameterDefinition pdef in callback.Parameters) { - TypeReference newType = MapToBlittableTypeIfNecessary (pdef.ParameterType, out _); - wrapperMethod.Parameters.Add (new ParameterDefinition (pdef.Name, pdef.Attributes, newType)); - body.Instructions.Add (GetLoadArgInstruction (nparam++, pdef)); - - if (!pdef.ParameterType.IsBlittable ()) { - GenerateNonBlittableConversion (pdef.ParameterType, newType); - } - } - - body.Instructions.Add (Instruction.Create (OpCodes.Call, callback)); - - if (hasReturnValue && returnTypeMapped) { - GenerateRetValCast (callback.ReturnType, retType); - } - - body.Instructions.Add (Instruction.Create (OpCodes.Ret)); - return wrapperMethod; void GenerateNonBlittableConversion (TypeReference sourceType, TypeReference targetType) { @@ -366,6 +351,56 @@ void ThrowUnsupportedType (TypeReference type) } } + void AddSetDefaultValueInstructions (MethodBody body, TypeReference type, VariableDefinition retval) + { + bool supported = false; + + switch (type.FullName) { + case "System.Boolean": + case "System.Byte": + case "System.Int16": + case "System.Int32": + case "System.SByte": + case "System.UInt16": + case "System.UInt32": + supported = true; + body.Instructions.Add (Instruction.Create (OpCodes.Ldc_I4_0)); + break; + + case "System.Int64": + case "System.UInt64": + supported = true; + body.Instructions.Add (Instruction.Create (OpCodes.Ldc_I4_0)); + body.Instructions.Add (Instruction.Create (OpCodes.Conv_I8)); + break; + + case "System.IntPtr": + case "System.UIntPtr": + supported = true; + body.Instructions.Add (Instruction.Create (OpCodes.Ldc_I4_0)); + body.Instructions.Add (Instruction.Create (OpCodes.Conv_I)); + break; + + case "System.Single": + supported = true; + body.Instructions.Add (Instruction.Create (OpCodes.Ldc_R4, 0.0F)); + break; + + case "System.Double": + supported = true; + body.Instructions.Add (Instruction.Create (OpCodes.Ldc_R8, 0.0)); + break; + } + + if (supported) { + body.Instructions.Add (Instruction.Create (OpCodes.Stloc, retval)); + return; + } + + throw new InvalidOperationException ($"Unsupported type: '{type.FullName}'"); + } + + Instruction GetLoadArgInstruction (uint nparam, ParameterDefinition pdef) { OpCode ldargOp; @@ -493,21 +528,6 @@ CustomAttribute CreateImportedUnmanagedCallersOnlyAttribute (AssemblyDefinition return null; } - FieldDefinition? FindField (TypeDefinition type, string fieldName, bool required) - { - foreach (FieldDefinition field in type.Fields) { - if (String.Compare (fieldName, field.Name, StringComparison.Ordinal) == 0) { - return field; - } - } - - if (required) { - throw new InvalidOperationException ($"Internal error: required field '{fieldName}' in type {type} not found"); - } - - return null; - } - TypeDefinition? FindType (AssemblyDefinition asm, string typeName, bool required) { TypeDefinition? type = asm.MainModule.FindType (typeName); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ParsedEnvironment.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ParsedEnvironment.cs deleted file mode 100644 index f353bb952ab..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ParsedEnvironment.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Xamarin.Android.Tasks -{ - class ParsedEnvironment - { - } -} diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 8cb008403ba..d5e28115ca4 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -841,6 +841,10 @@ MonodroidRuntime::mono_runtime_init ([[maybe_unused]] dynamic_local_stringGetMethodID (init.grefClass, "getName", "()Ljava/lang/String;"); init.Class_forName = env->GetStaticMethodID (init.grefClass, "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"); - MonoAssembly *assm; + log_warn (LOG_DEFAULT, " loc #2"); + MonoAssembly *runtime_assembly; + MonoAssembly *mono_android_assembly; + #if defined (NET) - assm = utils.monodroid_load_assembly (default_alc, SharedConstants::MONO_ANDROID_RUNTIME_ASSEMBLY_NAME); + mono_android_assembly = utils.monodroid_load_assembly (default_alc, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME); + runtime_assembly = utils.monodroid_load_assembly (default_alc, SharedConstants::MONO_ANDROID_RUNTIME_ASSEMBLY_NAME); #else // def NET - assm = utils.monodroid_load_assembly (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME); + mono_android_assembly = utils.monodroid_load_assembly (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME); + runtime_assembly = mono_android_assembly; #endif // ndef NET - MonoImage *image = mono_assembly_get_image (assm); + MonoImage *runtime_assembly_image = mono_assembly_get_image (runtime_assembly); + MonoImage *mono_android_assembly_image; +#if defined (NET) + mono_android_assembly_image = mono_assembly_get_image (mono_android_assembly); +#else + mono_android_assembly_image = runtime_assembly_image; +#endif + log_warn (LOG_DEFAULT, " loc #3"); uint32_t i = 0; for ( ; i < OSBridge::NUM_XA_GC_BRIDGE_TYPES; ++i) { + log_warn (LOG_DEFAULT, " loc #4"); lookup_bridge_info ( #if !defined (NET) domain, #endif // ndef NET - image, + mono_android_assembly_image, &osBridge.get_java_gc_bridge_type (i), &osBridge.get_java_gc_bridge_info (i) ); + log_warn (LOG_DEFAULT, " loc #5"); } + log_warn (LOG_DEFAULT, " loc #6"); + MonoClass *runtime; MonoMethod *method; if constexpr (is_running_on_desktop) { #if defined (NET) - runtime = mono_class_from_name (image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME); + runtime = mono_class_from_name (mono_android_assembly_image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME); #else - runtime = utils.monodroid_get_class_from_image (domain, image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME); + runtime = utils.monodroid_get_class_from_image (domain, mono_android_assembly_image, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME); #endif // def NET method = mono_class_get_method_from_name (runtime, "Initialize", 1); } else { - runtime = mono_class_get (image, application_config.android_runtime_jnienv_class_token); - method = mono_get_method (image, application_config.jnienv_initialize_method_token, runtime); + runtime = mono_class_get (mono_android_assembly_image, application_config.android_runtime_jnienv_class_token); + method = mono_get_method (mono_android_assembly_image, application_config.jnienv_initialize_method_token, runtime); } abort_unless (runtime != nullptr, "INTERNAL ERROR: unable to find the Android.Runtime.JNIEnvInit class!"); @@ -1163,25 +1185,22 @@ MonodroidRuntime::init_android_runtime ( /* If running on desktop, we may be swapping in a new Mono.Android image when calling this * so always make sure we have the freshest handle to the method. */ + log_warn (LOG_DEFAULT, " loc #7"); if (registerType == nullptr || is_running_on_desktop) { if constexpr (is_running_on_desktop) { registerType = mono_class_get_method_from_name (runtime, "RegisterJniNatives", 5); } else { - registerType = mono_get_method (image, application_config.jnienv_registerjninatives_method_token, runtime); + log_warn (LOG_DEFAULT, " loc #8"); + registerType = mono_get_method (mono_android_assembly_image, application_config.jnienv_registerjninatives_method_token, runtime); #if defined (NET) && defined (ANDROID) jnienv_register_jni_natives = reinterpret_cast(mono_method_get_unmanaged_callers_only_ftnptr (registerType, &error)); #endif // def NET && def ANDROID + log_warn (LOG_DEFAULT, " loc #9"); } } + log_warn (LOG_DEFAULT, " loc #10"); abort_unless (registerType != nullptr, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnvInit.RegisterJniNatives! %s", mono_error_get_message (&error)); - MonoClass *android_runtime_jnienv = runtime; - MonoClassField *bridge_processing_field = mono_class_get_field_from_name (runtime, const_cast ("BridgeProcessing")); - if (android_runtime_jnienv == nullptr || bridge_processing_field == nullptr) { - log_fatal (LOG_DEFAULT, "INTERNAL_ERROR: Unable to find Android.Runtime.JNIEnvInit.BridgeProcessing"); - exit (FATAL_EXIT_CANNOT_FIND_JNIENV); - } - jclass lrefLoaderClass = env->GetObjectClass (loader); init.Loader_loadClass = env->GetMethodID (lrefLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); env->DeleteLocalRef (lrefLoaderClass); diff --git a/src/monodroid/jni/osbridge.cc b/src/monodroid/jni/osbridge.cc index a5cb7222f01..067e9019da4 100644 --- a/src/monodroid/jni/osbridge.cc +++ b/src/monodroid/jni/osbridge.cc @@ -1143,7 +1143,7 @@ OSBridge::add_monodroid_domain (MonoDomain *domain) * use GC API to allocate memory and thus can't be called from within the GC callback as it causes a deadlock * (the routine allocating the memory waits for the GC round to complete first) */ - MonoClass *jnienv = utils.monodroid_get_class_from_name ( + MonoClass *runtime = utils.monodroid_get_class_from_name ( domain, #if defined (NET) SharedConstants::MONO_ANDROID_RUNTIME_ASSEMBLY_NAME, @@ -1151,12 +1151,12 @@ OSBridge::add_monodroid_domain (MonoDomain *domain) SharedConstants::MONO_ANDROID_ASSEMBLY_NAME, #endif SharedConstants::ANDROID_RUNTIME_NS_NAME, - SharedConstants::JNIENVINIT_CLASS_NAME + SharedConstants::ANDROID_RUNTIME_INTERNAL_CLASS_NAME ); node->domain = domain; - node->bridge_processing_field = mono_class_get_field_from_name (jnienv, const_cast ("BridgeProcessing")); - node->jnienv_vtable = mono_class_vtable (domain, jnienv); + node->bridge_processing_field = mono_class_get_field_from_name (runtime, const_cast ("BridgeProcessing")); + node->jnienv_vtable = mono_class_vtable (domain, runtime); node->next = domains_list; domains_list = node; diff --git a/src/monodroid/jni/shared-constants.hh b/src/monodroid/jni/shared-constants.hh index 5f8800db902..a88dbcdf569 100644 --- a/src/monodroid/jni/shared-constants.hh +++ b/src/monodroid/jni/shared-constants.hh @@ -27,6 +27,7 @@ namespace xamarin::android::internal static constexpr char JNIENVINIT_CLASS_NAME[] = "JNIEnvInit"; static constexpr char JNIENV_CLASS_NAME[] = "JNIEnv"; static constexpr char ANDROID_ENVIRONMENT_CLASS_NAME[] = "AndroidEnvironment"; + static constexpr char ANDROID_RUNTIME_INTERNAL_CLASS_NAME[] = "AndroidRuntimeInternal"; static constexpr char DLL_EXTENSION[] = ".dll"; From 7d201e3e67ca5df47da3187135d712e439a4d167 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 21 Sep 2022 23:09:32 +0200 Subject: [PATCH 63/79] Fix a handful of CI test failures --- .../PreserveLists/Mono.Android.Runtime.xml | 7 +++++++ .../Tasks/GenerateJavaStubs.cs | 9 ++++++--- .../Utilities/MarshalMethodsAssemblyRewriter.cs | 11 ++++++++--- src/monodroid/jni/monodroid-glue.cc | 11 ----------- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml index db9a48d4778..4372dd8e259 100644 --- a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml @@ -3,5 +3,12 @@ + + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index dfca1b07a31..230aa449e7d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -89,7 +89,6 @@ public class GenerateJavaStubs : AndroidTask public string SupportedOSPlatformVersion { get; set; } - [Required] public ITaskItem[] Environments { get; set; } [Output] @@ -245,8 +244,12 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) throw new InvalidOperationException ($"Internal error: marshal methods require the `IntermediateOutputDirectory` property of the `GenerateJavaStubs` task to have a value"); } - foreach (string abi in SupportedAbis) { - targetPaths.Add (Path.Combine (IntermediateOutputDirectory, AbiToRid (abi), "linked")); + if (SupportedAbis.Length == 1) { + targetPaths.Add (Path.Combine (IntermediateOutputDirectory, "linked")); + } else { + foreach (string abi in SupportedAbis) { + targetPaths.Add (Path.Combine (IntermediateOutputDirectory, AbiToRid (abi), "linked")); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs index ff0bd1f5ff9..acd69f2d7ea 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs @@ -34,6 +34,7 @@ public MarshalMethodsAssemblyRewriter (IDictionary targetAssemblyPaths, bool brokenExceptionTransitions) { if (resolver == null) { @@ -530,9 +531,13 @@ CustomAttribute CreateImportedUnmanagedCallersOnlyAttribute (AssemblyDefinition TypeDefinition? FindType (AssemblyDefinition asm, string typeName, bool required) { - TypeDefinition? type = asm.MainModule.FindType (typeName); - if (type != null) { - return type; + log.LogDebugMessage ($"Looking for type '{typeName}' in assembly '{asm}'"); + foreach (TypeDefinition t in asm.MainModule.Types) { + log.LogDebugMessage ($" checking {t.FullName}"); + if (String.Compare (typeName, t.FullName, StringComparison.Ordinal) == 0) { + log.LogDebugMessage ($" match!"); + return t; + } } if (required) { diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index d5e28115ca4..0ad9753339e 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1103,11 +1103,9 @@ MonodroidRuntime::init_android_runtime ( log_warn (LOG_GC, "GREF GC Threshold: %i", init.grefGcThreshold); init.grefClass = utils.get_class_from_runtime_field (env, runtimeClass, "java_lang_Class", true); - log_warn (LOG_DEFAULT, " loc #1"); Class_getName = env->GetMethodID (init.grefClass, "getName", "()Ljava/lang/String;"); init.Class_forName = env->GetStaticMethodID (init.grefClass, "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"); - log_warn (LOG_DEFAULT, " loc #2"); MonoAssembly *runtime_assembly; MonoAssembly *mono_android_assembly; @@ -1126,11 +1124,9 @@ MonodroidRuntime::init_android_runtime ( #else mono_android_assembly_image = runtime_assembly_image; #endif - log_warn (LOG_DEFAULT, " loc #3"); uint32_t i = 0; for ( ; i < OSBridge::NUM_XA_GC_BRIDGE_TYPES; ++i) { - log_warn (LOG_DEFAULT, " loc #4"); lookup_bridge_info ( #if !defined (NET) domain, @@ -1139,11 +1135,8 @@ MonodroidRuntime::init_android_runtime ( &osBridge.get_java_gc_bridge_type (i), &osBridge.get_java_gc_bridge_info (i) ); - log_warn (LOG_DEFAULT, " loc #5"); } - log_warn (LOG_DEFAULT, " loc #6"); - MonoClass *runtime; MonoMethod *method; @@ -1185,20 +1178,16 @@ MonodroidRuntime::init_android_runtime ( /* If running on desktop, we may be swapping in a new Mono.Android image when calling this * so always make sure we have the freshest handle to the method. */ - log_warn (LOG_DEFAULT, " loc #7"); if (registerType == nullptr || is_running_on_desktop) { if constexpr (is_running_on_desktop) { registerType = mono_class_get_method_from_name (runtime, "RegisterJniNatives", 5); } else { - log_warn (LOG_DEFAULT, " loc #8"); registerType = mono_get_method (mono_android_assembly_image, application_config.jnienv_registerjninatives_method_token, runtime); #if defined (NET) && defined (ANDROID) jnienv_register_jni_natives = reinterpret_cast(mono_method_get_unmanaged_callers_only_ftnptr (registerType, &error)); #endif // def NET && def ANDROID - log_warn (LOG_DEFAULT, " loc #9"); } } - log_warn (LOG_DEFAULT, " loc #10"); abort_unless (registerType != nullptr, "INTERNAL ERROR: Unable to find Android.Runtime.JNIEnvInit.RegisterJniNatives! %s", mono_error_get_message (&error)); jclass lrefLoaderClass = env->GetObjectClass (loader); From 35d49d42a9e9e5c1349c98a66b3346458432a732 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 21 Sep 2022 23:26:00 +0200 Subject: [PATCH 64/79] Remove unnecessary argument --- .../Utilities/MarshalMethodsNativeAssemblyGenerator.cs | 2 -- src/monodroid/jni/monodroid-glue-internal.hh | 6 +++--- src/monodroid/jni/xamarin-android-app-context.cc | 10 +++++----- src/monodroid/jni/xamarin-app.hh | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 2dc932b3951..d83208521e5 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -708,7 +708,6 @@ void WriteMarshalMethod (LlvmIrGenerator generator, MarshalMethodInfo method, Ll func, getFunctionPtrRef, new List { - new LlvmIrFunctionArgument (func.ParameterVariables[0]), new LlvmIrFunctionArgument (typeof(uint), method.AssemblyCacheIndex), new LlvmIrFunctionArgument (typeof(uint), method.ClassCacheIndex), new LlvmIrFunctionArgument (typeof(uint), nativeCallback.MetadataToken.ToUInt32 ()), @@ -753,7 +752,6 @@ LlvmIrVariableReference WriteXamarinAppInitFunction (LlvmIrGenerator generator) var get_function_pointer_sig = new LlvmNativeFunctionSignature ( returnType: typeof(void), parameters: new List { - new LlvmIrFunctionParameter (typeof(_JNIEnv), "env", isNativePointer: true), new LlvmIrFunctionParameter (typeof(uint), "mono_image_index"), new LlvmIrFunctionParameter (typeof(uint), "class_index"), new LlvmIrFunctionParameter (typeof(uint), "method_token"), diff --git a/src/monodroid/jni/monodroid-glue-internal.hh b/src/monodroid/jni/monodroid-glue-internal.hh index ec4aa559c0e..598f51372be 100644 --- a/src/monodroid/jni/monodroid-glue-internal.hh +++ b/src/monodroid/jni/monodroid-glue-internal.hh @@ -355,9 +355,9 @@ namespace xamarin::android::internal static const char* get_class_name (uint32_t class_index) noexcept; template - static void get_function_pointer (JNIEnv *env, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept; - static void get_function_pointer_at_startup (JNIEnv *env, uint32_t mono_image_index, uint32_t class_token, uint32_t method_token, void*& target_ptr) noexcept; - static void get_function_pointer_at_runtime (JNIEnv *env, uint32_t mono_image_index, uint32_t class_token, uint32_t method_token, void*& target_ptr) noexcept; + 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; #endif // def RELEASE && def ANDROID #endif // def NET diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index d45ccf00bfd..d6312d97f2b 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -37,7 +37,7 @@ MonodroidRuntime::get_class_name (uint32_t class_index) noexcept template force_inline void -MonodroidRuntime::get_function_pointer (JNIEnv *env, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept +MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept { log_debug ( LOG_ASSEMBLY, @@ -119,13 +119,13 @@ MonodroidRuntime::get_function_pointer (JNIEnv *env, uint32_t mono_image_index, } void -MonodroidRuntime::get_function_pointer_at_startup (JNIEnv *env, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept +MonodroidRuntime::get_function_pointer_at_startup (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept { - get_function_pointer (env, mono_image_index, class_index, method_token, target_ptr); + get_function_pointer (mono_image_index, class_index, method_token, target_ptr); } void -MonodroidRuntime::get_function_pointer_at_runtime (JNIEnv *env, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept +MonodroidRuntime::get_function_pointer_at_runtime (uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr) noexcept { - get_function_pointer (env, mono_image_index, class_index, method_token, target_ptr); + get_function_pointer (mono_image_index, class_index, method_token, target_ptr); } diff --git a/src/monodroid/jni/xamarin-app.hh b/src/monodroid/jni/xamarin-app.hh index cabd14d7338..0843a17d270 100644 --- a/src/monodroid/jni/xamarin-app.hh +++ b/src/monodroid/jni/xamarin-app.hh @@ -350,7 +350,7 @@ struct MarshalMethodName MONO_API MONO_API_EXPORT const char* const mm_class_names[]; MONO_API MONO_API_EXPORT const MarshalMethodName mm_method_names[]; -using get_function_pointer_fn = void(*)(JNIEnv *env, uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr); +using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr); MONO_API MONO_API_EXPORT void xamarin_app_init (get_function_pointer_fn fn) noexcept; #endif // def RELEASE && def ANDROID && def NET From 1ebb5d80cce759ddd367b7076d8952d028f15868 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 26 Sep 2022 15:38:20 +0200 Subject: [PATCH 65/79] Fix a couple more causes for test failures --- src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs | 2 ++ src/Mono.Android/Android.Runtime/JNINativeWrapper.cs | 2 +- src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs | 4 +++- .../Xamarin.Android.Common.targets | 3 +++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs index 51834a19e37..37df6ef353a 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs @@ -1,3 +1,4 @@ +#if !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME using System; using System.Reflection; @@ -37,3 +38,4 @@ public static void WaitForBridgeProcessing () } } } +#endif // !NETCOREAPP || INSIDE_MONO_ANDROID_RUNTIME diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs index 5a1b668042c..d7b27e855d8 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs @@ -20,7 +20,7 @@ static void get_runtime_types () if (exception_handler_method == null) AndroidEnvironment.FailFast ("Cannot find AndroidEnvironment.UnhandledException"); - wait_for_bridge_processing_method = typeof (AndroidRuntimeInternal).GetMethod ("WaitForBridgeProcessing", BindingFlags.NonPublic | BindingFlags.Static); + wait_for_bridge_processing_method = typeof (AndroidRuntimeInternal).GetMethod ("WaitForBridgeProcessing", BindingFlags.Public | BindingFlags.Static); if (wait_for_bridge_processing_method == null) AndroidEnvironment.FailFast ("Cannot find AndroidRuntimeInternal.WaitForBridgeProcessing"); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 230aa449e7d..4053a0f9b1e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -53,6 +53,7 @@ public class GenerateJavaStubs : AndroidTask public string IntermediateOutputDirectory { get; set; } public bool LinkingEnabled { get; set; } + public bool HaveMultipleRIDs { get; set; } public bool EnableMarshalMethods { get; set; } public string ManifestTemplate { get; set; } public string[] MergedManifestDocuments { get; set; } @@ -244,7 +245,8 @@ void Run (DirectoryAssemblyResolver res, bool useMarshalMethods) throw new InvalidOperationException ($"Internal error: marshal methods require the `IntermediateOutputDirectory` property of the `GenerateJavaStubs` task to have a value"); } - if (SupportedAbis.Length == 1) { + // If the property is set then, even if we have just one RID, the linked assemblies path will include the RID + if (!HaveMultipleRIDs && SupportedAbis.Length == 1) { targetPaths.Add (Path.Combine (IntermediateOutputDirectory, "linked")); } else { foreach (string abi in SupportedAbis) { diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 4d32b377071..a96d370c614 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1480,6 +1480,8 @@ because xbuild doesn't support framework reference assemblies. <_ManifestOutput Condition=" '$(AndroidManifestMerger)' != 'legacy' ">$(IntermediateOutputPath)AndroidManifest.xml <_LinkingEnabled Condition=" '$(AndroidLinkMode)' != 'None'">True <_LinkingEnabled Condition=" '$(AndroidLinkMode)' == 'None'">False + <_HaveMultipleRIDs Condition=" '$(RuntimeIdentifiers)' != '' ">True + <_HaveMultipleRIDs Condition=" '$(RuntimeIdentifiers)' == '' ">False <_MergedManifestDocuments Condition=" '$(AndroidManifestMerger)' == 'legacy' " Include="@(ExtractedManifestDocuments)" /> @@ -1518,6 +1520,7 @@ because xbuild doesn't support framework reference assemblies. CheckedBuild="$(_AndroidCheckedBuild)" EnableMarshalMethods="$(_AndroidUseMarshalMethods)" LinkingEnabled="$(_LinkingEnabled)" + HaveMultipleRIDs="$(_HaveMultipleRIDs)" IntermediateOutputDirectory="$(IntermediateOutputPath)" Environments="@(AndroidEnvironment);@(LibraryEnvironments)"> From 62a5138699ff16c9c521110e053300e827002703 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 26 Sep 2022 19:00:22 +0200 Subject: [PATCH 66/79] More tests fixed --- .../Android.Runtime/AndroidRuntime.cs | 10 +---- .../PackagingTest.cs | 1 + .../BuildReleaseArm64SimpleDotNet.apkdesc | 21 ++++++---- .../BuildReleaseArm64XFormsDotNet.apkdesc | 41 ++++++++++--------- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 67c780c3bf4..a886e362067 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -471,19 +471,13 @@ public override void RegisterNativeMembers (JniType nativeClass, Type type, stri public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan methods) { - if (methods.IsEmpty) { - Logger.Log (LogLevel.Debug, "monodroid-mm", "No methods to register, returning"); - return; - } - try { - if (FastRegisterNativeMembers (nativeClass, type, methods)) - return; - if (methods.IsEmpty) { if (jniAddNativeMethodRegistrationAttributePresent) base.RegisterNativeMembers (nativeClass, type, methods.ToString ()); return; + } else if (FastRegisterNativeMembers (nativeClass, type, methods)) { + return; } int methodCount = CountMethods (methods); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs index e78f6267066..d7d53020f59 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs @@ -87,6 +87,7 @@ public void CheckIncludedAssemblies ([Values (false, true)] bool usesAssemblySto new [] { "Java.Interop.dll", "Mono.Android.dll", + "Mono.Android.Runtime.dll", "rc.bin", "System.Console.dll", "System.Private.CoreLib.dll", diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index f64e7342a80..33cfb16c606 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -5,10 +5,13 @@ "Size": 3032 }, "assemblies/Java.Interop.dll": { - "Size": 59188 + "Size": 59187 }, "assemblies/Mono.Android.dll": { - "Size": 88673 + "Size": 88390 + }, + "assemblies/Mono.Android.Runtime.dll": { + "Size": 5776 }, "assemblies/rc.bin": { "Size": 1182 @@ -26,10 +29,10 @@ "Size": 3388 }, "assemblies/System.Private.CoreLib.dll": { - "Size": 533485 + "Size": 533617 }, "assemblies/System.Runtime.dll": { - "Size": 3498 + "Size": 3655 }, "assemblies/System.Runtime.InteropServices.dll": { "Size": 3770 @@ -53,7 +56,7 @@ "Size": 18612 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 432408 + "Size": 433600 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3073392 @@ -68,16 +71,16 @@ "Size": 148696 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 16640 + "Size": 16896 }, "META-INF/BNDLTOOL.RSA": { "Size": 1213 }, "META-INF/BNDLTOOL.SF": { - "Size": 3339 + "Size": 3446 }, "META-INF/MANIFEST.MF": { - "Size": 3212 + "Size": 3319 }, "res/drawable-hdpi-v4/icon.png": { "Size": 4762 @@ -104,5 +107,5 @@ "Size": 1904 } }, - "PackageSize": 2726442 + "PackageSize": 2730619 } \ No newline at end of file diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc index 95b72bc72cd..db99dbbb8a5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc @@ -5,13 +5,16 @@ "Size": 3568 }, "assemblies/FormsViewGroup.dll": { - "Size": 7131 + "Size": 7314 }, "assemblies/Java.Interop.dll": { - "Size": 66767 + "Size": 66760 }, "assemblies/Mono.Android.dll": { - "Size": 437613 + "Size": 444587 + }, + "assemblies/Mono.Android.Runtime.dll": { + "Size": 5776 }, "assemblies/mscorlib.dll": { "Size": 3859 @@ -104,7 +107,7 @@ "Size": 16635 }, "assemblies/System.Runtime.dll": { - "Size": 3647 + "Size": 3808 }, "assemblies/System.Runtime.InteropServices.dll": { "Size": 3770 @@ -143,16 +146,16 @@ "Size": 1860 }, "assemblies/UnnamedProject.dll": { - "Size": 117252 + "Size": 117251 }, "assemblies/Xamarin.AndroidX.Activity.dll": { "Size": 5872 }, "assemblies/Xamarin.AndroidX.AppCompat.AppCompatResources.dll": { - "Size": 5908 + "Size": 6118 }, "assemblies/Xamarin.AndroidX.AppCompat.dll": { - "Size": 112407 + "Size": 112654 }, "assemblies/Xamarin.AndroidX.CardView.dll": { "Size": 6596 @@ -161,13 +164,13 @@ "Size": 16407 }, "assemblies/Xamarin.AndroidX.Core.dll": { - "Size": 96466 + "Size": 96736 }, "assemblies/Xamarin.AndroidX.DrawerLayout.dll": { - "Size": 13924 + "Size": 14237 }, "assemblies/Xamarin.AndroidX.Fragment.dll": { - "Size": 39483 + "Size": 39952 }, "assemblies/Xamarin.AndroidX.Legacy.Support.Core.UI.dll": { "Size": 5924 @@ -185,16 +188,16 @@ "Size": 12476 }, "assemblies/Xamarin.AndroidX.RecyclerView.dll": { - "Size": 83675 + "Size": 84803 }, "assemblies/Xamarin.AndroidX.SavedState.dll": { "Size": 4869 }, "assemblies/Xamarin.AndroidX.SwipeRefreshLayout.dll": { - "Size": 10116 + "Size": 10391 }, "assemblies/Xamarin.AndroidX.ViewPager.dll": { - "Size": 17484 + "Size": 18044 }, "assemblies/Xamarin.Forms.Core.dll": { "Size": 528450 @@ -209,13 +212,13 @@ "Size": 60774 }, "assemblies/Xamarin.Google.Android.Material.dll": { - "Size": 39785 + "Size": 40159 }, "classes.dex": { "Size": 3090268 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 432408 + "Size": 433600 }, "lib/arm64-v8a/libmonosgen-2.0.so": { "Size": 3073392 @@ -230,7 +233,7 @@ "Size": 148696 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 321936 + "Size": 324464 }, "META-INF/android.support.design_material.version": { "Size": 12 @@ -344,13 +347,13 @@ "Size": 1213 }, "META-INF/BNDLTOOL.SF": { - "Size": 79628 + "Size": 79735 }, "META-INF/com.google.android.material_material.version": { "Size": 10 }, "META-INF/MANIFEST.MF": { - "Size": 79501 + "Size": 79608 }, "META-INF/proguard/androidx-annotations.pro": { "Size": 339 @@ -1985,5 +1988,5 @@ "Size": 341228 } }, - "PackageSize": 8037348 + "PackageSize": 8057909 } \ No newline at end of file From fe2ae06ff2b27f74a1fcbbd15fc8de1f3edfbfc3 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 27 Sep 2022 12:27:37 +0200 Subject: [PATCH 67/79] Fix tests which use callbacks with Android.Graphics.Color the type must be mapped to `System.Int32` --- .../Utilities/MarshalMethodsClassifier.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 270bad1c02c..3a7d4712b3f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -164,6 +164,11 @@ string MapType (TypeReference typeRef) return typeName; } + // Android.Graphics.Color is mapped to/from a native `int` + if (String.Compare (typeName, "Android.Graphics.Color", StringComparison.Ordinal) == 0) { + return "System.Int32"; + } + return "System.IntPtr"; } @@ -188,7 +193,7 @@ public bool Matches (MethodDefinition method) } if (String.Compare (returnType, method.ReturnType.FullName, StringComparison.Ordinal) != 0) { - log.LogWarning ($"Method '{method.FullName}' doesn't match native callback signature (invalid return type)"); + log.LogWarning ($"Method '{method.FullName}' doesn't match native callback signature (invalid return type: expected '{returnType}', found '{method.ReturnType.FullName}')"); return false; } From e1609d9a3d9b1f332fa327817b5290a79142da0b Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 27 Sep 2022 15:02:59 +0200 Subject: [PATCH 68/79] Let's see if the failing tests work with these changes --- build-tools/scripts/TestApks.targets | 2 +- .../Android.Runtime/AndroidRuntime.cs | 63 +++++++++++-------- .../Android.Runtime/JNIEnvInit.cs | 4 ++ .../Utilities/MarshalMethodsClassifier.cs | 6 +- 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/build-tools/scripts/TestApks.targets b/build-tools/scripts/TestApks.targets index 523abfb5a39..37ca9805f5a 100644 --- a/build-tools/scripts/TestApks.targets +++ b/build-tools/scripts/TestApks.targets @@ -103,7 +103,7 @@ WriteOutputAsMessage="True" /> methods) { + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, "AndroidTypeManager.RegisterNativeMembers called"); try { if (methods.IsEmpty) { if (jniAddNativeMethodRegistrationAttributePresent) base.RegisterNativeMembers (nativeClass, type, methods.ToString ()); return; } else if (FastRegisterNativeMembers (nativeClass, type, methods)) { + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, " FastRegisterNativeMembers succeeded, returning"); return; } int methodCount = CountMethods (methods); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" methodCount: {methodCount}"); if (methodCount < 1) { - if (jniAddNativeMethodRegistrationAttributePresent) + if (jniAddNativeMethodRegistrationAttributePresent) { + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" calling base.RegisterNativeMembers"); base.RegisterNativeMembers (nativeClass, type, methods.ToString ()); + } + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" no methods to register??"); return; } @@ -521,6 +527,7 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< callback = CreateDynamicCallback (minfo); needToRegisterNatives = true; } else { + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" normal registration for '{name.ToString ()}'"); Type callbackDeclaringType = type; if (!callbackDeclaringTypeString.IsEmpty) { callbackDeclaringType = Type.GetType (callbackDeclaringTypeString.ToString (), throwOnError: true)!; @@ -531,36 +538,38 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< // TODO: this is temporary hack, it needs a full fledged registration mechanism for methods like these (that is, ones which // aren't registered with [Register] but are baked into Mono.Android's managed and Java code) - bool createCallback; - if (JNIEnvInit.MarshalMethodsEnabled) { - string declaringTypeName = callbackDeclaringType.FullName; - string callbackName = callbackString.ToString (); - - createCallback = false; - foreach (var kvp in dynamicRegistrationMethods) { - string dynamicTypeName = kvp.Key; - - foreach (string dynamicCallbackMethodName in kvp.Value) { - if (ShouldRegisterDynamically (declaringTypeName, callbackName, dynamicTypeName, dynamicCallbackMethodName)) { - createCallback = true; - break; - } - } - - if (createCallback) { - break; - } - } - } else { - createCallback = true; - } - - if (createCallback) { + // bool createCallback; + // if (JNIEnvInit.MarshalMethodsEnabled) { + // RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" JNI marshal methods enabled, checking if we need to create the callback"); + // string declaringTypeName = callbackDeclaringType.FullName; + // string callbackName = callbackString.ToString (); + + // createCallback = false; + // foreach (var kvp in dynamicRegistrationMethods) { + // string dynamicTypeName = kvp.Key; + + // foreach (string dynamicCallbackMethodName in kvp.Value) { + // if (ShouldRegisterDynamically (declaringTypeName, callbackName, dynamicTypeName, dynamicCallbackMethodName)) { + // createCallback = true; + // break; + // } + // } + + // if (createCallback) { + // break; + // } + // } + // RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" createCallback after loop: {createCallback}"); + // } else { + // createCallback = true; + // } + + // if (createCallback) { Logger.Log (LogLevel.Info, "monodroid-mm", $" creating delegate for: '{callbackString.ToString()}' in type {callbackDeclaringType.FullName}"); GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler), callbackDeclaringType, callbackString.ToString ()); callback = connector (); - } + //} } if (callback != null) { diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index ac7bf299469..b761f8a53ac 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -56,7 +56,9 @@ internal struct JnienvInitializeArgs { #endif static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len) { + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, "JNIEnvInit.RegisterJniNatives called"); string typeName = new string ((char*) typeName_ptr, 0, typeName_len); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" typeName: '{typeName}'; methods_len: {methods_len}"); var type = Type.GetType (typeName); if (type == null) { RuntimeNativeMethods.monodroid_log (LogLevel.Error, @@ -67,9 +69,11 @@ static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, In var className = Java.Interop.TypeManager.GetClassName (jniClass); Java.Interop.TypeManager.RegisterType (className, type); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" className: '{className}'"); JniType? jniType = null; JniType.GetCachedJniType (ref jniType, className); + RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" jniType: '{jniType.Name}'"); ReadOnlySpan methods = new ReadOnlySpan ((void*) methods_ptr, methods_len); ((AndroidTypeManager)androidRuntime!.TypeManager).RegisterNativeMembers (jniType, type, methods); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 3a7d4712b3f..a5e8b785e50 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -165,9 +165,9 @@ string MapType (TypeReference typeRef) } // Android.Graphics.Color is mapped to/from a native `int` - if (String.Compare (typeName, "Android.Graphics.Color", StringComparison.Ordinal) == 0) { - return "System.Int32"; - } + // if (String.Compare (typeName, "Android.Graphics.Color", StringComparison.Ordinal) == 0) { + // return "System.Int32"; + // } return "System.IntPtr"; } From 98655ea91b74475a9c4a364f20839ebaa45c911c Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 27 Sep 2022 15:34:18 +0200 Subject: [PATCH 69/79] Oops, forgot to put that back in --- .../Utilities/MarshalMethodsClassifier.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index a5e8b785e50..3a7d4712b3f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -165,9 +165,9 @@ string MapType (TypeReference typeRef) } // Android.Graphics.Color is mapped to/from a native `int` - // if (String.Compare (typeName, "Android.Graphics.Color", StringComparison.Ordinal) == 0) { - // return "System.Int32"; - // } + if (String.Compare (typeName, "Android.Graphics.Color", StringComparison.Ordinal) == 0) { + return "System.Int32"; + } return "System.IntPtr"; } From 5d138d023d45b265054a65a58313139b27732818 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 27 Sep 2022 19:07:23 +0200 Subject: [PATCH 70/79] Copy PR 7406 to test if it works for marshal methods --- Documentation/guides/messages/xa1030.md | 37 ++++++++++++++ .../Android.Runtime/AndroidRuntime.cs | 48 ++----------------- .../Android.Runtime/JNIEnvInit.cs | 4 -- ...soft.Android.Sdk.DefaultProperties.targets | 1 + .../Properties/Resources.resx | 4 ++ .../Xamarin.Android.Build.Tests/XASdkTests.cs | 15 +++++- .../Xamarin.Android.Common.targets | 4 ++ .../Tests/XASdkDeployTests.cs | 16 +++---- .../Mono.Android.NET-Tests.csproj | 1 + 9 files changed, 69 insertions(+), 61 deletions(-) create mode 100644 Documentation/guides/messages/xa1030.md diff --git a/Documentation/guides/messages/xa1030.md b/Documentation/guides/messages/xa1030.md new file mode 100644 index 00000000000..c84217c25b6 --- /dev/null +++ b/Documentation/guides/messages/xa1030.md @@ -0,0 +1,37 @@ +--- +title: Xamarin.Android error XA1030 +description: XA1030 error code +ms.date: 09/26/2022 +--- +# Xamarin.Android error XA1030 + +## Example messages + +``` +The 'RunAOTCompilation' MSBuild property is only supported when trimming is enabled. Edit the project file in a text editor to set 'PublishTrimmed' to 'true' for this build configuration. +``` + +## Solution + +Instead of using: + +```xml + + true + + false + None + +``` + +Use the default value for `$(PublishTrimmed)` and `$(AndroidLinkMode)` +instead: + +```xml + + true + +``` + +Additionally, as mentioned by [`XA0119`](xa0119.md), you should not +use `$(RunAOTCompilation)` in `Debug` configurations. diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 28596ea7cea..fa98adf6337 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -464,32 +464,22 @@ static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments argu public override void RegisterNativeMembers (JniType nativeClass, Type type, string? methods) => RegisterNativeMembers (nativeClass, type, methods.AsSpan ()); - // Temporary hack, see comments in RegisterNativeMembers below - static readonly Dictionary dynamicRegistrationMethods = new Dictionary (StringComparer.Ordinal) { - // leaving it empty for now, until we're sure it's no longer needed (there are some test failures which may still require it to fix them) - }; - public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan methods) { - RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, "AndroidTypeManager.RegisterNativeMembers called"); try { if (methods.IsEmpty) { if (jniAddNativeMethodRegistrationAttributePresent) base.RegisterNativeMembers (nativeClass, type, methods.ToString ()); return; } else if (FastRegisterNativeMembers (nativeClass, type, methods)) { - RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, " FastRegisterNativeMembers succeeded, returning"); return; } int methodCount = CountMethods (methods); - RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" methodCount: {methodCount}"); if (methodCount < 1) { if (jniAddNativeMethodRegistrationAttributePresent) { - RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" calling base.RegisterNativeMembers"); base.RegisterNativeMembers (nativeClass, type, methods.ToString ()); } - RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" no methods to register??"); return; } @@ -527,7 +517,6 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< callback = CreateDynamicCallback (minfo); needToRegisterNatives = true; } else { - RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" normal registration for '{name.ToString ()}'"); Type callbackDeclaringType = type; if (!callbackDeclaringTypeString.IsEmpty) { callbackDeclaringType = Type.GetType (callbackDeclaringTypeString.ToString (), throwOnError: true)!; @@ -536,40 +525,9 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< callbackDeclaringType = callbackDeclaringType.BaseType!; } - // TODO: this is temporary hack, it needs a full fledged registration mechanism for methods like these (that is, ones which - // aren't registered with [Register] but are baked into Mono.Android's managed and Java code) - // bool createCallback; - // if (JNIEnvInit.MarshalMethodsEnabled) { - // RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" JNI marshal methods enabled, checking if we need to create the callback"); - // string declaringTypeName = callbackDeclaringType.FullName; - // string callbackName = callbackString.ToString (); - - // createCallback = false; - // foreach (var kvp in dynamicRegistrationMethods) { - // string dynamicTypeName = kvp.Key; - - // foreach (string dynamicCallbackMethodName in kvp.Value) { - // if (ShouldRegisterDynamically (declaringTypeName, callbackName, dynamicTypeName, dynamicCallbackMethodName)) { - // createCallback = true; - // break; - // } - // } - - // if (createCallback) { - // break; - // } - // } - // RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" createCallback after loop: {createCallback}"); - // } else { - // createCallback = true; - // } - - // if (createCallback) { - Logger.Log (LogLevel.Info, "monodroid-mm", $" creating delegate for: '{callbackString.ToString()}' in type {callbackDeclaringType.FullName}"); - GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler), - callbackDeclaringType, callbackString.ToString ()); - callback = connector (); - //} + GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler), + callbackDeclaringType, callbackString.ToString ()); + callback = connector (); } if (callback != null) { diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index b761f8a53ac..ac7bf299469 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -56,9 +56,7 @@ internal struct JnienvInitializeArgs { #endif static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len) { - RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, "JNIEnvInit.RegisterJniNatives called"); string typeName = new string ((char*) typeName_ptr, 0, typeName_len); - RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" typeName: '{typeName}'; methods_len: {methods_len}"); var type = Type.GetType (typeName); if (type == null) { RuntimeNativeMethods.monodroid_log (LogLevel.Error, @@ -69,11 +67,9 @@ static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, In var className = Java.Interop.TypeManager.GetClassName (jniClass); Java.Interop.TypeManager.RegisterType (className, type); - RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" className: '{className}'"); JniType? jniType = null; JniType.GetCachedJniType (ref jniType, className); - RuntimeNativeMethods.monodroid_log (LogLevel.Warn, LogCategories.Default, $" jniType: '{jniType.Name}'"); ReadOnlySpan methods = new ReadOnlySpan ((void*) methods_ptr, methods_len); ((AndroidTypeManager)androidRuntime!.TypeManager).RegisterNativeMembers (jniType, type, methods); diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets index a1692029bbf..2d83fb1ef7d 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets @@ -79,6 +79,7 @@ true false <_AndroidXA1029 Condition=" '$(AotAssemblies)' != '' ">true + <_AndroidXA1030 Condition=" '$(RunAOTCompilation)' == 'true' and '$(PublishTrimmed)' == 'false' ">true $(RunAOTCompilation) true diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx index 509fe13fdfc..55e9040f419 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx @@ -466,6 +466,10 @@ In this message, the term "binding" means a piece of generated code that makes i The 'AotAssemblies' MSBuild property is deprecated. Edit the project file in a text editor to remove this property, and use the 'RunAOTCompilation' MSBuild property instead. The following are literal names and should not be translated: 'AotAssemblies', 'RunAOTCompilation' + + The 'RunAOTCompilation' MSBuild property is only supported when trimming is enabled. Edit the project file in a text editor to set 'PublishTrimmed' to 'true' for this build configuration. + The following are literal names and should not be translated: 'RunAOTCompilation', 'PublishTrimmed' + Use of AppDomain.CreateDomain() detected in assembly: {0}. .NET 6 and higher will only support a single AppDomain, so this API will no longer be available in Xamarin.Android once .NET 6 is released. The following are literal names and should not be translated: AppDomain.CreateDomain(), AppDomain diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs index 6d9dd27d5c7..bb009c736de 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs @@ -1103,6 +1103,7 @@ public void BenchmarkDotNet () /* useInterpreter */ true, /* publishTrimmed */ true, /* aot */ true, + /* expected */ true, }, // Debug + AOT new object [] { @@ -1110,6 +1111,7 @@ public void BenchmarkDotNet () /* useInterpreter */ false, /* publishTrimmed */ true, /* aot */ true, + /* expected */ true, }, // Debug + PublishTrimmed new object [] { @@ -1117,12 +1119,21 @@ public void BenchmarkDotNet () /* useInterpreter */ false, /* publishTrimmed */ true, /* aot */ false, + /* expected */ true, + }, + // AOT + PublishTrimmed=false + new object [] { + /* isRelease */ true, + /* useInterpreter */ false, + /* publishTrimmed */ false, + /* aot */ true, + /* expected */ false, }, }; [Test] [TestCaseSource (nameof (SettingCombinationsSource))] - public void SettingCombinations (bool isRelease, bool useInterpreter, bool publishTrimmed, bool aot) + public void SettingCombinations (bool isRelease, bool useInterpreter, bool publishTrimmed, bool aot, bool expected) { var proj = new XASdkProject { IsRelease = isRelease, @@ -1131,7 +1142,7 @@ public void SettingCombinations (bool isRelease, bool useInterpreter, bool publi proj.SetProperty ("PublishTrimmed", publishTrimmed.ToString ()); proj.SetProperty ("RunAOTCompilation", aot.ToString ()); var builder = CreateDotNetBuilder (proj); - Assert.IsTrue (builder.Build (), $"{proj.ProjectName} should succeed"); + Assert.AreEqual (expected, builder.Build (), $"{proj.ProjectName} should {(expected ? "succeed" : "fail")}"); } DotNetCLI CreateDotNetBuilder (string relativeProjectDir = null) diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index a96d370c614..dc0f72bf029 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -521,6 +521,10 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. ResourceName="XA1029" Condition=" $(_AndroidXA1029) == 'true' " /> + $(DotNetAndroidTargetFramework) + 21 Xamarin.Android.RuntimeTests Exe true From 71248514964d6d2ececa468faf2a4f864fdc0e11 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 28 Sep 2022 22:00:51 +0200 Subject: [PATCH 71/79] Disable marshal methods for Designer tests The tests break probably because the dynamic registration doesn't work when marshal methods are used and we don't generate the necessary code for desktop builds (libxamarin-app.{dll,dylib}) --- build-tools/automation/azure-pipelines.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/build-tools/automation/azure-pipelines.yaml b/build-tools/automation/azure-pipelines.yaml index 7a1fbe38c16..0ff5eda74ec 100644 --- a/build-tools/automation/azure-pipelines.yaml +++ b/build-tools/automation/azure-pipelines.yaml @@ -1203,6 +1203,7 @@ stages: clean: all variables: EnableRegressionTest: true + AndroidEnableMarshalMethods: false steps: - checkout: uitools clean: true From 9be7e6bd48ab0cb288f051d03d771fe87f88f3cf Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 28 Sep 2022 22:31:30 +0200 Subject: [PATCH 72/79] Add docs --- .../guides/internals/JavaJNI_Interop.md | 750 ++++++++++++++++++ 1 file changed, 750 insertions(+) create mode 100644 Documentation/guides/internals/JavaJNI_Interop.md diff --git a/Documentation/guides/internals/JavaJNI_Interop.md b/Documentation/guides/internals/JavaJNI_Interop.md new file mode 100644 index 00000000000..cb35e9795e0 --- /dev/null +++ b/Documentation/guides/internals/JavaJNI_Interop.md @@ -0,0 +1,750 @@ + +- [Introduction](#introduction) +- [Java <-> Managed interoperability overview](#java-managed-interoperability-overview) + - [Java Callable Wrappers (JCW)](#java-callable-wrappers-jcw) +- [Registration](#registration) + - [Dynamic registration](#dynamic-registration) + - [Dynamic Java Callable Wrappers registration code](#dynamic-java-callable-wrappers-registration-code) + - [Dynamic Registration call sequence](#dynamic-registration-call-sequence) + - [Marshal methods](#marshal-methods) + - [Marshal Methods Java Callable Wrappers registration code](#marshal-methods-java-callable-wrappers-registration-code) + - [Marshal methods C# source code](#marshal-methods-c-source-code) + - [JNI requirements](#jni-requirements) + - [LLVM IR code generation](#llvm-ir-code-generation) + - [Assembly rewriting](#assembly-rewriting) + - [Wrappers for methods with non-blittable types](#wrappers-for-methods-with-non-blittable-types) + - [UnmanagedCallersOnly attribute](#unmanagedcallersonly-attribute) + - [Marshal Methods Registration call sequence](#marshal-methods-registration-call-sequence) + + +# Introduction + +At the core of `Xamarin.Android` is its ability to interoperate with +the Java/Kotlin APIs implemented in the Android system. To make it +work, it is necessary to "bridge" the two separate worlds of Java VM +(`ART` in the Android OS) and the Managed VM (`MonoVM`). Application +developers expect to be able to call native Android APIs and receive +calls (or react to events) from the Android side using code written in +one of the .NET managed languages. To make it work, `Xamarin.Android` +employs a number of techniques, both at build and at run time, which +are described in the sections below. + +This guide is meant to explain the technical implementation in a way +that is sufficient to understand the system without having to read the +actual source code. + +# Java <-> Managed interoperability overview + +Java VM and Managed VM are two entirely separate entities which +co-exist in the same process/application. Despite sharing the same +process resources, they don't "naturally" communicate with each other. +There is no direct way to call Java/Kotlin from .NET a'la the +`p/invoke` mechanism which allows calling native code APIs. Nor there +exists a way for Java/Kotlin code to invoke managed methods. To make +it possible, `Xamarin.Android` takes advantage of the Java's [JNI](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html) +(`Java Native Interface`), a mechanism that allows native code +(.NET managed code being "native" in this context) to register +implementations of Java methods, written outside the Java VM and in +languages other than Java/Kotlin (for instance in `C`, `C++` or +`Rust`). + +Such methods need to be appropriately declared in the Java code, for +instance: + +```java +class MainActivity + extends androidx.appcompat.app.AppCompatActivity +{ + public void onCreate (android.os.Bundle p0) + { + n_onCreate (p0); + } + + private native void n_onCreate (android.os.Bundle p0); +} +``` + +Each native method is declared using the `native` keyword, and +whenever it is invoked from other Java code, the Java VM will use the +JNI to invoke the target method. + +Native methods can be registered either dynamically (by calling the +[`RegisterNatives`](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives) +JNI function) or "statically", by providing a native shared library +which exports a symbol with appropriate name which points to the +native function implementing the Java method. + +Both ways of registration are described in detail in the following +sections. + +## Java Callable Wrappers (JCW) + +`Xamarin.Android` wraps the entire Android API by generating +appropriate C# code which mirrors the Java/Kotlin code (classes, +interfaces, methods, properties etc). Each generated class that +corresponds to a Java/Kotlin type, is derived from the +`Java.Lang.Object` class (implemented in the `Mono.Android` assembly), +which marks it as a "Java interoperable type", meaning that it can +implement or override virtual Java methods. To make registration and +invoking of such methods possible, it is necessary to generate a Java +class which mirrors the Managed one and provides an entry point to +the Java <-> Managed transition. The Java classes are generated +during application (as well as `Xamarin.Android`) build and we call +them **Java Callable Wrappers** (or **JCW** for short). For instance, +the following managed class: + +```csharp +public class MainActivity : AppCompatActivity +{ + public override Android.Views.View? OnCreateView (Android.Views.View? parent, string name, Android.Content.Context context, Android.Util.IAttributeSet attrs) + { + return base.OnCreateView (parent, name, context, attrs); + } + + protected override void OnCreate (Bundle savedInstanceState) + { + base.OnCreate(savedInstanceState); + DoSomething (savedInstanceState); + } + + void DoSomething (Bundle bundle) + { + // do something with the bundle + } +} +``` + +overrides two Java virtual methods found in the `AppCompatActivity` +type: `OnCreateView` and `OnCreate`. The `DoSomething` method does +not correspond to any method found in the base Java type, and thus it +won't be included in the JCW. + +The Java Callable Wrapper generated for the above class would look as +follows (a few generated methods not relevant to the discussion have +been omitted for brevity): + +```java +public class MainActivity + extends androidx.appcompat.app.AppCompatActivity +{ + public android.view.View onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3) + { + return n_onCreateView (p0, p1, p2, p3); + } + private native android.view.View n_onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3); + + public void onCreate (android.os.Bundle p0) + { + n_onCreate (p0); + } + private native void n_onCreate (android.os.Bundle p0); +} +``` + +Understanding the connection between Managed methods and their Java +counterparts is required in order to understand the registration +mechanisms described in sections found later in this document. The +[Dynamic registration](#dynamic-registration) section will expand on +this example in order to explain the details of how the Managed type +and its methods are registered with the Java VM. + +# Registration + +Both mechanisms of method registration rely on generation of [Java +Callable Wrappers](#java-callable-wrappers-jcw), with [Dynamic +registration](#dynamic-registration) requiring more code to be +generated so that the registration can be performed at the runtime. + +JCW are generated only for types that derive from +the `Java.Lang.Object` type. Finding such types is the task of the +Java.Interop's [`JavaTypeScanner`](../../external/Java.Interop/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaTypeScanner.cs), +which uses `Mono.Cecil` to read all the assemblies referenced by the +application and its libraries. The returned list of assemblies is +then used by a variety of tasks, JCW being only one +of them. + +After all types are found, +[`JavaCallableWrapperGenerator`](../../external/Java.Interop/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs) +is invoked in order to analyze each method in each type, looking for +those which override a virtual Java method and, thus, need to be +included in the wrapper class code. The generator optionally (if +[marshal methods](#marshal-methods) are enabled) passes each method to +an implementation of the +[`Java.Interop.Tools.JavaCallableWrappers.JavaCallableMethodClassifier`](../../external/Java.Interop/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs) +abstract class (which is +[`MarshalMethodsClassifier`](../../src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs) +in our case), to check whether the given method can be registered +statically. + +`JavaCallableWrapperGenerator` looks for methods decorated with the +`[Register]` attribute, which most frequently is created by invoking +its constructor with three parameters: + + 1. Java method name + 2. JNI method signature + 3. "Connector" method name + +The "connector" is a static method which creates a delegate that +subsequently allows calling of the native callback method: + +```csharp +public class MainActivity : AppCompatActivity +{ + // Connector backing field + static Delegate? cb_onCreate_Landroid_os_Bundle_; + + // Connector method + static Delegate GetOnCreate_Landroid_os_Bundle_Handler () + { + if (cb_onCreate_Landroid_os_Bundle_ == null) + cb_onCreate_Landroid_os_Bundle_ = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_V) n_OnCreate_Landroid_os_Bundle_); + return cb_onCreate_Landroid_os_Bundle_; + } + + // Native callback + static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; + var savedInstanceState = global::Java.Lang.Object.GetObject (native_savedInstanceState, JniHandleOwnership.DoNotTransfer); + __this.OnCreate (savedInstanceState); + } + + // Target method + [Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")] + protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState) + { + const string __id = "onCreate.(Landroid/os/Bundle;)V"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue ((savedInstanceState == null) ? IntPtr.Zero : ((global::Java.Lang.Object) savedInstanceState).Handle); + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); + } finally { + global::System.GC.KeepAlive (savedInstanceState); + } + } +} +``` + +The above code is actually generated in the `Android.App.Activity` +class while Xamarin.Android is built, from which our example +`MainActivity` eventually derives. + +What happens with the above code depends on the registration mechanism +and is described in the sections below. + +## Dynamic registration + +This registration mechanism has been used by `Xamarin.Android` since +the beginning and it will remain in use for the foreseeable future +when the application is built in the `Debug` configuration or when +[Marshal Methods](#marshal-methods) are turned off. + +### Dynamic Java Callable Wrappers registration code + +Building on the C# example shown in the [Java Callable +Wrappers](#java-callable-wrappers-jcw) section, the following Java +code is generated (only the parts relevant to registration are shown): + +```java +public class MainActivity + extends androidx.appcompat.app.AppCompatActivity +{ +/** @hide */ + public static final String __md_methods; + static { + __md_methods = + "n_onCreateView:(Landroid/view/View;Ljava/lang/String;Landroid/content/Context;Landroid/util/AttributeSet;)Landroid/view/View;:GetOnCreateView_Landroid_view_View_Ljava_lang_String_Landroid_content_Context_Landroid_util_AttributeSet_Handler\n" + + "n_onCreate:(Landroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n" + + ""; + mono.android.Runtime.register ("HelloAndroid.MainActivity, HelloAndroid", MainActivity.class, __md_methods); + } + + public android.view.View onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3) + { + return n_onCreateView (p0, p1, p2, p3); + } + + private native android.view.View n_onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3); + + public void onCreate (android.os.Bundle p0) + { + n_onCreate (p0); + } + + private native void n_onCreate (android.os.Bundle p0); +} +``` + +Code fragment which takes part in registration is the class's static +constructor. For each method registered for the type (that is, +implemented or overridden in the managed code), the JCW generator +outputs a single string which contains full information about the type +and method to register. Each such registration string is terminated +with the newline character and the entire sequence ends with an empty +string. Together, all the lines are concatenated and placed in the +`__md_methods` static variable. The `mono.android.Runtime.register` +method (see below for more details) is then invoked to register all +the methods. + +### Dynamic Registration call sequence + +All the "native" methods declared in the generated Java type are +registered when the type is constructed or accessed for the first +time. This is when the Java VM invokes the type's static constructor, +kicking off a sequence of calls that eventually ends with all the type +methods registered with JNI: + + 1. `mono.android.Runtime.register` is itself a native method, + declared in the + [`Runtime`](../../src/java-runtime/java/mono/android/Runtime.java) + class of Xamarin.Android's Java runtime code, and implemented in + the native Xamarin.Android + [runtime](../../src/monodroid/jni/monodroid-glue.cc) (the + `MonodroidRuntime::Java_mono_android_Runtime_register` method). + Purpose of this method is to prepare a call into the + Xamarin.Android managed runtime code, the + [`Android.Runtime.JNIEnv::RegisterJniNatives`](../../src/Mono.Android/Android.Runtime/JNIEnv.cs) + method. + 2. `Android.Runtime.JNIEnv::RegisterJniNatives` is passed name of + the managed type for which to register Java methods and uses .NET + reflection to load that type, followed by a call to cache the + type (via `RegisterType` method in the + [`TypeManager`](../../src/Mono.Android/Java.Interop/TypeManager.cs) + class) to end with a call to the + `Android.Runtime.AndroidTypeManager::RegisterNativeMembers` + method. + 3. `Android.Runtime.AndroidTypeManager::RegisterNativeMembers` + eventually calls the + `Java.Interop.JniEnvironment.Types::RegisterNatives` method which + first generates a delegate to the native callback method, using + `System.Reflection.Emit` (via the + [`Android.Runtime.JNINativeWrapper::CreateDelegate`](../../src/Mono.Android/Android.Runtime/JNINativeWrapper.cs) + method) and, eventually, invokes Java JNI's `RegisterNatives` + function, finally registering the native methods for a managed + type. + +The `System.Reflection.Emit` sequence mentioned in 3. above is among +the most costly operations, repeated for each registered method. + +Some more information about Java type registration can be found +[here](https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration). + +## Marshal methods + +The goal of marshal methods is to completely bypass the [dynamic +registration sequence](#dynamic-registration-call-sequence), replacing +it with native code generated and compiled during application build, +thus saving on the startup time of the application. + +Marshal methods registration mechanism takes advantage of the JNI +ability to look up implementations of `native` Java methods in actual +native (shared) libraries. Such symbols must have names that follow a +set of rules, so that JNI is able to properly locate them (details are +explained in the [JNI Requirements](#jni-requirements) section below). + +To achieve that, the marshal methods mechanism uses a number of +classes which [generate native](#llvm-ir-code-generation) code and +[modify assemblies](#assembly-rewriting) that contain the registered +methods. + +Current implementation of the marshal methods classifier recognizes +the "standard" method registration pattern, using the example of the +`OnCreate` method shown in [Registration](#registration) above. + +The standard pattern consists of: + + * the "connector" method, `GetOnCreate_Landroid_os_Bundle_Handler` + above + * the delegate backing field, `cb_onCreate_Landroid_os_Bundle_` + above + * the native callback method, `n_OnCreate_Landroid_os_Bundle_` above + * and the virtual target method which dispatches the call to the + actual object, `OnCreate` above. + +Whenever the classifier's `ShouldBeDynamicallyRegistered` method is +called, it is passed not only the method's declaring type, but also +the `Register` attribute instance which it then uses to check whether +the method being registered conforms to the "standard" registration +pattern shown above. The connector, native callback methods as well +as the backing field must be private and static in order for the +registered method to be considered as a candidate for static +registration. + +Registered methods which don't follow the "standard" pattern will be +registered dynamically. + +### Marshal Methods Java Callable Wrappers registration code + +Building on the C# example show in the [Java Callable +Wrappers](#java-callable-wrappers-jcw) section, the following Java +code is generated (only the parts relevant to registration are shown): + +```java +public class MainActivity + extends androidx.appcompat.app.AppCompatActivity +{ + public android.view.View onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3) + { + return n_onCreateView (p0, p1, p2, p3); + } + + private native android.view.View n_onCreateView (android.view.View p0, java.lang.String p1, android.content.Context p2, android.util.AttributeSet p3); + + public void onCreate (android.os.Bundle p0) + { + n_onCreate (p0); + } + + private native void n_onCreate (android.os.Bundle p0); +} +``` + +Note that, compared to the code generated for the [dynamic +registration](#dynamic-java-callable-wrappers-registration-code) +mechanism, there is no static constructor while the rest +of the code remains exactly the same. + +### Marshal methods C# source code + +The marshal methods sections below will all refer to this code +fragment: + + +```csharp +public class MainActivity : AppCompatActivity +{ + // Connector backing field + static Delegate? cb_onCreate_Landroid_os_Bundle_; + + // Connector method + static Delegate GetOnCreate_Landroid_os_Bundle_Handler () + { + if (cb_onCreate_Landroid_os_Bundle_ == null) + cb_onCreate_Landroid_os_Bundle_ = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_V) n_OnCreate_Landroid_os_Bundle_); + return cb_onCreate_Landroid_os_Bundle_; + } + + // Native callback + static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; + var savedInstanceState = global::Java.Lang.Object.GetObject (native_savedInstanceState, JniHandleOwnership.DoNotTransfer); + __this.OnCreate (savedInstanceState); + } + + // Target method + [Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")] + protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState) + { + const string __id = "onCreate.(Landroid/os/Bundle;)V"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue ((savedInstanceState == null) ? IntPtr.Zero : ((global::Java.Lang.Object) savedInstanceState).Handle); + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); + } finally { + global::System.GC.KeepAlive (savedInstanceState); + } + } +} +``` + +### JNI requirements + +JNI +[specifies](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#resolving_native_method_names) +a number of rules which govern how the native symbol name is +constructed, so that a mapping of object-oriented Java code (with its +package names/namespaces, class names and overloadable methods) into the essentially +"flat" procedural "namespace" of the lowest common denominator C code. + +The precise rules are outlined in the URL above, and their short +version is as follows: + + * Each symbol starts with the `Java_` prefix + * Next follows the mangled (see below) **fully qualified class + name** + * Next the `_` character serves as a separator before + * A mangled **method** name, which is optionally followed by + * Double underscore `__` and the mangled method argument signature + +"Mangling" is a way of encoding certain characters that are not +directly representable both in the source code and in the native +symbol name. The JNI specification allows for direct use of ASCII +letters (capital and lowercase) and digits, while all the other +characters are either represented by placeholders or encoded as 16-bit +hexadecimal Unicode character code (table copied from the JNI +specification for easier reference): + +| Escape sequence | Denotes | +|-----------------|------------------------------------------| +| _0XXXX | a Unicode character XXXX, all lower case | +| _1 | The `_` character | +| _2 | The `;` character in signatures | +| _3 | The `[` character in signatures | +| _ | The `.` or `/` characters | + +Generation of JNI symbol names is performed by the +[`MarshalMethodsNativeAssemblyGenerator`](../../src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs) +class while generating the native function source code. + +JNI supports two forms of the native symbol name, as signalled in the +bullet list above - a short and a long one. The former is looked up +first by the Java VM, followed by the latter. The latter needs to be +used only for overloaded methods, which is what our generator does. + +### LLVM IR code generation + +[`MarshalMethodsNativeAssemblyGenerator`](../../src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs) +uses the LLVM IR generator infrastructure to output both data and +executable code for all the marshal methods wrappers. It is not +necessary to understand the generated code unless one needs to modify +it, so this document only shows the equivalent C++ code which can +serve as a guide to understanding how the marshal method runtime +invocation works: + +```C++ +using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr); + +static get_function_pointer_fn get_function_pointer; + +void xamarin_app_init (get_function_pointer_fn fn) noexcept +{ + get_function_pointer = fn; +} + +using android_app_activity_on_create_bundle_fn = void (*) (JNIEnv *env, jclass klass, jobject savedInstanceState); +static android_app_activity_on_create_bundle_fn android_app_activity_on_create_bundle = nullptr; + +extern "C" JNIEXPORT void +JNICALL Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2 (JNIEnv *env, jclass klass, jobject savedInstanceState) noexcept +{ + if (android_app_activity_on_create_bundle == nullptr) { + get_function_pointer ( + 16, // mono image index + 0, // class index + 0x0600055B, // method token + reinterpret_cast(android_app_activity_on_create_bundle) // target pointer + ); + } + + android_app_activity_on_create_bundle (env, klass, savedInstanceState); +} +``` + +The `xamarin_app_init` function is output only once and is called by +the `Xamarin.Android` runtime twice during application startup - once +to pass `get_function_pointer_fn` which does **not** use any locks (as +we know that until a certain point during startup we are in a single +lock, so no data access races can happen) and the other time just +before handing control over to the MonoVM, to pass pointer to +`get_function_pointer_fn` which **does** employ locking (since during +runtime it may very well happen that our generated Java native +functions will be called from different threads simultaneously). + +The `Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2` +function is a template which is repeated for each Java native +function, with each function having its own set of arguments and its +own callback backing field (`android_app_activity_on_create_bundle` +here). + +The `get_function_pointer` function takes as parameters indexes into a +couple of tables, one for `MonoImage*` pointers and the other for +`MonoClass*` pointers - both of which are generated by the +`MarshalMethodsNativeAssemblyGenerator` class at application build +time and allow for very fast lookup during run time. Target methods +are retrieved by their token value, within the specified `MonoImage*` +(essentially a pointer to managed assembly image in memory) and class. + +The method identified in such manner, **must** be decorated in the +managed code with the +[`[UnmanagedCallersOnly]`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0) +attribute (see [below](#unmanagedcallersonly-attribute) for more +details) so that it can be invoked directly, as if it was a native +method itself, with minimal managed marshaling overhead. + +### Assembly rewriting + +Please refer to the [C# code fragment](#marshal-methods-c-source-code) +above in order to understand this section. + +Managed assemblies (including `Mono.Android.dll`) which contain Java +types need to be usable in two contexts: with the "traditional" +dynamic registration and with marshal methods. Both of these +mechanisms, however, have different requirements. We cannot assume +that any assembly (either from Xamarin.Android or a third party nuget) +will have "marshal methods friendly" code and thus we need to make +sure that the code meets our requirements. + +We do it by reading each relevant assembly and modifying it by +altering the definition of the native callbacks and removing the +code that's no longer used by marshal methods. This task is performed +by the +[`MarshalMethodsAssemblyRewriter`](../../src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs) +invoked during application build after all the assemblies are linked +but **before** type maps are generated (as rewriting **will** alter +the method and potentially type tokens) + +The exact modifications we apply are: + + * Removal of the **connector backing field** + * Removal of the **connector method** + * Addition of the `[UnmanagedCallersOnly]` attribute to the **native + callback** method + * Optionally, generation of a [non-blittable types + wrapper](#wrappers-for-methods-with-non-blittable-types) for the + **native callback** method. + +All the modifications are performed with `Mono.Cecil`. + +After modifications, the assembly contains equivalent of the following +C# code for each marshal method: + +```csharp +public class MainActivity : AppCompatActivity +{ + // Native callback + [UnmanagedCallersOnly] + static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) + { + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; + var savedInstanceState = global::Java.Lang.Object.GetObject (native_savedInstanceState, JniHandleOwnership.DoNotTransfer); + __this.OnCreate (savedInstanceState); + } + + // Target method + [Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")] + protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState) + { + const string __id = "onCreate.(Landroid/os/Bundle;)V"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue ((savedInstanceState == null) ? IntPtr.Zero : ((global::Java.Lang.Object) savedInstanceState).Handle); + _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); + } finally { + global::System.GC.KeepAlive (savedInstanceState); + } + } +} +``` + +#### Wrappers for methods with non-blittable types + +The +[`[UnmanagedCallersOnly]`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0) +attribute requires that all the argument types as well as the method +return type are +[blittable](https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types). + +Among these types is one that's commonly used by the managed classes +implementing Java methods: `bool`. Currently this is the **only** +non-blittable type we've encountered in bindings, so at this point it +is the only one supported by the assembly rewriter. + +Whenever we encounter a method with a non-blittable type, we must +generate a wrapper for it, so that we can decorate it with the +`[UnmanagedCallersOnly]` attribute. This is easier and less error +prone than modifying the native callback method's IL stream to +implement the necessary conversion. + +An example of such method is +`Android.Views.View.IOnTouchListener::OnTouch`: + +```csharp +static bool n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_ (IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e) +{ + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; + var v = global::Java.Lang.Object.GetObject (native_v, JniHandleOwnership.DoNotTransfer); + var e = global::Java.Lang.Object.GetObject (native_e, JniHandleOwnership.DoNotTransfer); + bool __ret = __this.OnTouch (v, e); + return __ret; +} +``` + +As it returns a `bool` value, it needs a wrapper to cast the return +value properly. Each wrapper method retains the native callback method +name, but appends the `_mm_wrapper` suffix to it: + +```csharp + +[UnmanagedCallersOnly] +static byte n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent__mm_wrapper (IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e) +{ + return n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_(jnienv, native__this, native_v, native_e) ? 1 : 0; +} + +static bool n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_ (IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e) +{ + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; + var v = global::Java.Lang.Object.GetObject (native_v, JniHandleOwnership.DoNotTransfer); + var e = global::Java.Lang.Object.GetObject (native_e, JniHandleOwnership.DoNotTransfer); + bool __ret = __this.OnTouch (v, e); + return __ret; +} +``` + +The wrapper's return statement uses the ternary operator to "cast" the +boolean value to `1` (for `true`) or `0` (for `false`) because the +value of `bool` across the managed runtime can take a range of values: + + * `0` for `false` + * `-1` or `1` for `true` + * `!= 0` for true + +Since the `bool` type in C# can be 1, 2 or 4 bytes long, we need to +cast it to some type of a known and static size. The managed type +`byte` was chosen as it corresponds to the Java/JNI `jboolean` type, +defined as an unsigned 8-bit type. + +Whenever an **argument** value needs to be converted between `byte` and +`bool`, we generate code that is equivalent of the `argument != 0` +comparison, for instance for the +`Android.Views.View.IOnFocusChangeListener::OnFocusChange` method: + +```csharp +[UnmanagedCallersOnly] +static void n_OnFocusChange_Landroid_view_View_Z (IntPtr jnienv, IntPtr native__this, IntPtr native_v, byte hasFocus) +{ + n_OnFocusChange_Landroid_view_View_Z (jnienv, native__this, native_v, hasFocus != 0); +} + +static void n_OnFocusChange_Landroid_view_View_Z (IntPtr jnienv, IntPtr native__this, IntPtr native_v, bool hasFocus) +{ + var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; + var v = global::Java.Lang.Object.GetObject (native_v, JniHandleOwnership.DoNotTransfer); + __this.OnFocusChange (v, hasFocus); +} +``` + +#### UnmanagedCallersOnly attribute + +Each marshal methods native callback method is decorated with the +[`[UnmanagedCallersOnly]`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0) +attribute, in order for us to be able to invoke the callback directly +from native code with minimal overhead compared to traditional +managed-from-native method calls (`mono_runtime_invoke`) + +### Marshal Methods Registration call sequence + +The sequence described in the [dynamic +registration sequence](#dynamic-registration-call-sequence) section +above is completely removed for the marshal methods. What remains +common for both dynamic and marshal methods registration, is the +resolution of the native function target done by the Java VM runtime. +In both cases the method declared in a Java class as `native` is +looked up by the Java VM when first JIT-ing the code. The difference +lies in the way this lookup is performed. + +Dynamic registration uses the +[`RegisterNatives`](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives) +JNI function at the runtime, which stores a pointer to the registered +method inside the structure which describes a Java class in the Java +VM. + +Marshal methods, however, don't register anything with the JNI, +instead they rely on the symbol lookup mechanism of the Java VM. +Whenever a call to `native` Java method is JIT-ed and it is not +registered previously using the `RegisterNatives` JNI function, Java +VM will proceed to look for symbols in the process runtime image (e.g. +using `dlopen` + `dlsym` calls on Unix) and, having found a matching +symbol, use pointer to it as the target of the `native` Java method +call. From edcf09241b9d1e5ab88bc1ae1688914ca9a66b9f Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 29 Sep 2022 10:52:51 +0200 Subject: [PATCH 73/79] See if we can get crash logs from designer tests --- Configuration.props | 2 +- build-tools/automation/yaml-templates/run-designer-tests.yml | 5 ++++- src/Mono.Android/Android.Runtime/JNIEnvInit.cs | 2 +- .../Linker/PreserveLists/Mono.Android.xml | 2 +- src/monodroid/jni/monodroid-glue.cc | 1 - src/monodroid/jni/xamarin-android-app-context.cc | 4 ---- 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Configuration.props b/Configuration.props index 0aa03683792..a38424b5179 100644 --- a/Configuration.props +++ b/Configuration.props @@ -47,7 +47,7 @@ portable True latest - 32.0.465 + 32.0.476 Windows diff --git a/build-tools/automation/yaml-templates/run-designer-tests.yml b/build-tools/automation/yaml-templates/run-designer-tests.yml index 796c181577f..f6a63f5adc6 100644 --- a/build-tools/automation/yaml-templates/run-designer-tests.yml +++ b/build-tools/automation/yaml-templates/run-designer-tests.yml @@ -62,7 +62,10 @@ steps: displayName: 'Copy binlogs' inputs: sourceFolder: ${{ parameters.designerSourcePath }}/Xamarin.Designer.Android - contents: '**/*.binlog' + contents: | + **/*.binlog + **/hs*.log + **/hs*.mdmp targetFolder: $(Build.ArtifactStagingDirectory)/designer-binlogs overWrite: true flattenFolders: true diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index ac7bf299469..9311757806e 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -16,7 +16,7 @@ internal struct JnienvInitializeArgs { public IntPtr javaVm; public IntPtr env; public IntPtr grefLoader; - public IntPtr Loader_loadClass; // TODO: remove, not needed anymore + public IntPtr Loader_loadClass; public IntPtr grefClass; // TODO: remove, not needed anymore public IntPtr Class_forName; public uint logCategories; diff --git a/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml b/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml index 15d6be826f9..dbcd130d347 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml +++ b/src/Xamarin.Android.Build.Tasks/Linker/PreserveLists/Mono.Android.xml @@ -13,7 +13,7 @@ - + diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 8051cc6ee13..0694973ef72 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1060,7 +1060,6 @@ MonodroidRuntime::init_android_runtime ( #endif // ndef NET JNIEnv *env, jclass runtimeClass, jobject loader) { - log_warn (LOG_DEFAULT, __PRETTY_FUNCTION__); constexpr char icall_typemap_java_to_managed[] = "Java.Interop.TypeManager::monodroid_typemap_java_to_managed"; constexpr char icall_typemap_managed_to_java[] = "Android.Runtime.JNIEnv::monodroid_typemap_managed_to_java"; diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index d6312d97f2b..e54ae912b06 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -46,7 +46,6 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas get_class_name (class_index), class_index ); - log_warn (LOG_DEFAULT, " mono_image_index == %u; class_index == %u; method_token == 0x%x", mono_image_index, class_index, method_token); if (XA_UNLIKELY (class_index >= marshal_methods_number_of_classes)) { log_fatal (LOG_DEFAULT, "Internal error: invalid index for class cache (expected at most %u, got %u)", @@ -71,10 +70,7 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas MonoMethod *method = mono_get_method (image, method_token, klass.klass); MonoError error; - log_warn (LOG_DEFAULT, " method == %p (mono_image_index == %u; class_index == %u; method_token == 0x%x)", method, mono_image_index, class_index, method_token); - log_warn (LOG_DEFAULT, " pointer to method %s == %p, trying to get funcptr for it (mono_image_index == %u; class_index == %u; method_token == 0x%x)", mono_method_full_name (method, true), method, mono_image_index, class_index, method_token); void *ret = mono_method_get_unmanaged_callers_only_ftnptr (method, &error); - log_warn (LOG_DEFAULT, " obtained pointer == %p (mono_image_index == %u; class_index == %u; method_token == 0x%x)", ret, mono_image_index, class_index, method_token); if (XA_LIKELY (ret != nullptr)) { if constexpr (NeedsLocking) { From 48c33280f34338d5f6f5d7b268ed1c521671dc1d Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 29 Sep 2022 13:34:36 +0200 Subject: [PATCH 74/79] Let's see if this fixes the designer failures --- src/monodroid/jni/monodroid-glue.cc | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 0694973ef72..a4a223515c8 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -2504,18 +2504,25 @@ Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang, force_inline void MonodroidRuntime::Java_mono_android_Runtime_register (JNIEnv *env, jstring managedType, jclass nativeClass, jstring methods) { + log_info (LOG_DEFAULT, __PRETTY_FUNCTION__); size_t total_time_index; if (XA_UNLIKELY (FastTiming::enabled ())) { total_time_index = internal_timing->start_event (TimingEventKind::RuntimeRegister); } + log_info (LOG_DEFAULT, " loc #1"); jsize managedType_len = env->GetStringLength (managedType); + log_info (LOG_DEFAULT, " loc #2"); const jchar *managedType_ptr = env->GetStringChars (managedType, nullptr); + log_info (LOG_DEFAULT, " loc #3"); int methods_len = env->GetStringLength (methods); + log_info (LOG_DEFAULT, " loc #4"); const jchar *methods_ptr = env->GetStringChars (methods, nullptr); + log_info (LOG_DEFAULT, " loc #5"); #if !defined (NET) || !defined (ANDROID) + log_info (LOG_DEFAULT, " loc #6"); void *args[] = { &managedType_ptr, &managedType_len, @@ -2523,24 +2530,37 @@ MonodroidRuntime::Java_mono_android_Runtime_register (JNIEnv *env, jstring manag &methods_ptr, &methods_len, }; + log_info (LOG_DEFAULT, " loc #7"); MonoMethod *register_jni_natives = registerType; + log_info (LOG_DEFAULT, " loc #8 (register_jni_natives == %p)", register_jni_natives); #endif // ndef NET || ndef ANDROID #if !defined (NET) + log_info (LOG_DEFAULT, " loc #8"); MonoDomain *domain = utils.get_current_domain (/* attach_thread_if_needed */ false); + log_info (LOG_DEFAULT, " loc #9"); mono_jit_thread_attach (domain); + log_info (LOG_DEFAULT, " loc #10"); // Refresh current domain as it might have been modified by the above call domain = mono_domain_get (); + log_info (LOG_DEFAULT, " loc #11"); if constexpr (is_running_on_desktop) { - MonoClass *runtime = utils.monodroid_get_class_from_name (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENV_CLASS_NAME); + log_info (LOG_DEFAULT, " loc #12"); + MonoClass *runtime = utils.monodroid_get_class_from_name (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME, SharedConstants::ANDROID_RUNTIME_NS_NAME, SharedConstants::JNIENVINIT_CLASS_NAME); + log_info (LOG_DEFAULT, " loc #13 (runtime == %p)", runtime); register_jni_natives = mono_class_get_method_from_name (runtime, "RegisterJniNatives", 5); + log_info (LOG_DEFAULT, " loc #14 (register_jni_natives == %p)", register_jni_natives); } + log_info (LOG_DEFAULT, " loc #15"); utils.monodroid_runtime_invoke (domain, register_jni_natives, nullptr, args, nullptr); + log_info (LOG_DEFAULT, " loc #16"); #else // ndef NET #if !defined (ANDROID) + log_info (LOG_DEFAULT, " loc #17"); mono_runtime_invoke (register_jni_natives, nullptr, args, nullptr); + log_info (LOG_DEFAULT, " loc #18"); #else jnienv_register_jni_natives (managedType_ptr, managedType_len, nativeClass, methods_ptr, methods_len); #endif // ndef ANDROID @@ -2577,6 +2597,7 @@ JNICALL Java_mono_android_Runtime_dumpTimingData ([[maybe_unused]] JNIEnv *env, JNIEXPORT void JNICALL Java_mono_android_Runtime_register (JNIEnv *env, [[maybe_unused]] jclass klass, jstring managedType, jclass nativeClass, jstring methods) { + log_info (LOG_DEFAULT, __PRETTY_FUNCTION__); monodroidRuntime.Java_mono_android_Runtime_register (env, managedType, nativeClass, methods); } From 5f433b16af668a1bd6505dd1716f2904418e00cc Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 29 Sep 2022 18:14:21 +0200 Subject: [PATCH 75/79] Remove debug logging --- src/monodroid/jni/monodroid-glue.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index bbaf91396da..09a297dcde0 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -2504,7 +2504,6 @@ Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang, force_inline void MonodroidRuntime::Java_mono_android_Runtime_register (JNIEnv *env, jstring managedType, jclass nativeClass, jstring methods) { - log_info (LOG_DEFAULT, __PRETTY_FUNCTION__); size_t total_time_index; if (XA_UNLIKELY (FastTiming::enabled ())) { @@ -2578,7 +2577,6 @@ JNICALL Java_mono_android_Runtime_dumpTimingData ([[maybe_unused]] JNIEnv *env, JNIEXPORT void JNICALL Java_mono_android_Runtime_register (JNIEnv *env, [[maybe_unused]] jclass klass, jstring managedType, jclass nativeClass, jstring methods) { - log_info (LOG_DEFAULT, __PRETTY_FUNCTION__); monodroidRuntime.Java_mono_android_Runtime_register (env, managedType, nativeClass, methods); } From 8b1d3f96d4216dded069725f30865e0337d7dc3f Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 30 Sep 2022 23:40:38 +0200 Subject: [PATCH 76/79] Address reviews --- .../guides/internals/JavaJNI_Interop.md | 24 ++++----- build-tools/automation/azure-pipelines.yaml | 1 - .../PreserveLists/Mono.Android.Runtime.xml | 12 +---- .../Mono.Android.Runtime.csproj | 51 +----------------- .../Mono.Android.Runtime.targets | 52 +++++++++++++++++++ 5 files changed, 66 insertions(+), 74 deletions(-) create mode 100644 src/Mono.Android.Runtime/Mono.Android.Runtime.targets diff --git a/Documentation/guides/internals/JavaJNI_Interop.md b/Documentation/guides/internals/JavaJNI_Interop.md index cb35e9795e0..008fcfaabe1 100644 --- a/Documentation/guides/internals/JavaJNI_Interop.md +++ b/Documentation/guides/internals/JavaJNI_Interop.md @@ -19,13 +19,13 @@ # Introduction -At the core of `Xamarin.Android` is its ability to interoperate with +At the core of `.NET Android` is its ability to interoperate with the Java/Kotlin APIs implemented in the Android system. To make it work, it is necessary to "bridge" the two separate worlds of Java VM (`ART` in the Android OS) and the Managed VM (`MonoVM`). Application developers expect to be able to call native Android APIs and receive calls (or react to events) from the Android side using code written in -one of the .NET managed languages. To make it work, `Xamarin.Android` +one of the .NET managed languages. To make it work, `.NET Android` employs a number of techniques, both at build and at run time, which are described in the sections below. @@ -41,7 +41,7 @@ process resources, they don't "naturally" communicate with each other. There is no direct way to call Java/Kotlin from .NET a'la the `p/invoke` mechanism which allows calling native code APIs. Nor there exists a way for Java/Kotlin code to invoke managed methods. To make -it possible, `Xamarin.Android` takes advantage of the Java's [JNI](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html) +it possible, `.NET Android` takes advantage of the Java's [JNI](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html) (`Java Native Interface`), a mechanism that allows native code (.NET managed code being "native" in this context) to register implementations of Java methods, written outside the Java VM and in @@ -79,7 +79,7 @@ sections. ## Java Callable Wrappers (JCW) -`Xamarin.Android` wraps the entire Android API by generating +`.NET Android` wraps the entire Android API by generating appropriate C# code which mirrors the Java/Kotlin code (classes, interfaces, methods, properties etc). Each generated class that corresponds to a Java/Kotlin type, is derived from the @@ -89,7 +89,7 @@ implement or override virtual Java methods. To make registration and invoking of such methods possible, it is necessary to generate a Java class which mirrors the Managed one and provides an entry point to the Java <-> Managed transition. The Java classes are generated -during application (as well as `Xamarin.Android`) build and we call +during application (as well as `.NET Android`) build and we call them **Java Callable Wrappers** (or **JCW** for short). For instance, the following managed class: @@ -226,7 +226,7 @@ public class MainActivity : AppCompatActivity ``` The above code is actually generated in the `Android.App.Activity` -class while Xamarin.Android is built, from which our example +class while .NET Android is built, from which our example `MainActivity` eventually derives. What happens with the above code depends on the registration mechanism @@ -234,7 +234,7 @@ and is described in the sections below. ## Dynamic registration -This registration mechanism has been used by `Xamarin.Android` since +This registration mechanism has been used by `.NET Android` since the beginning and it will remain in use for the foreseeable future when the application is built in the `Debug` configuration or when [Marshal Methods](#marshal-methods) are turned off. @@ -297,12 +297,12 @@ methods registered with JNI: 1. `mono.android.Runtime.register` is itself a native method, declared in the [`Runtime`](../../src/java-runtime/java/mono/android/Runtime.java) - class of Xamarin.Android's Java runtime code, and implemented in - the native Xamarin.Android + class of .NET Android's Java runtime code, and implemented in + the native .NET Android [runtime](../../src/monodroid/jni/monodroid-glue.cc) (the `MonodroidRuntime::Java_mono_android_Runtime_register` method). Purpose of this method is to prepare a call into the - Xamarin.Android managed runtime code, the + .NET Android managed runtime code, the [`Android.Runtime.JNIEnv::RegisterJniNatives`](../../src/Mono.Android/Android.Runtime/JNIEnv.cs) method. 2. `Android.Runtime.JNIEnv::RegisterJniNatives` is passed name of @@ -532,7 +532,7 @@ JNICALL Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2 (JNIEnv ``` The `xamarin_app_init` function is output only once and is called by -the `Xamarin.Android` runtime twice during application startup - once +the `.NET Android` runtime twice during application startup - once to pass `get_function_pointer_fn` which does **not** use any locks (as we know that until a certain point during startup we are in a single lock, so no data access races can happen) and the other time just @@ -571,7 +571,7 @@ Managed assemblies (including `Mono.Android.dll`) which contain Java types need to be usable in two contexts: with the "traditional" dynamic registration and with marshal methods. Both of these mechanisms, however, have different requirements. We cannot assume -that any assembly (either from Xamarin.Android or a third party nuget) +that any assembly (either from .NET Android or a third party nuget) will have "marshal methods friendly" code and thus we need to make sure that the code meets our requirements. diff --git a/build-tools/automation/azure-pipelines.yaml b/build-tools/automation/azure-pipelines.yaml index 0ff5eda74ec..7a1fbe38c16 100644 --- a/build-tools/automation/azure-pipelines.yaml +++ b/build-tools/automation/azure-pipelines.yaml @@ -1203,7 +1203,6 @@ stages: clean: all variables: EnableRegressionTest: true - AndroidEnableMarshalMethods: false steps: - checkout: uitools clean: true diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml index 4372dd8e259..330f218233a 100644 --- a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.Runtime.xml @@ -1,14 +1,4 @@ - - - - - - - - - - - + diff --git a/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj b/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj index 495567700f3..62e90145971 100644 --- a/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj +++ b/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj @@ -58,54 +58,5 @@ - - - - <_PackageVersion>$(ProductVersion) - <_PackageVersionBuild>$(XAVersionCommitCount) - - - <_PackageVersion>$(AndroidPackVersion) - <_PackageVersionBuild>$(PackVersionCommitCount) - - - - - - - - - - - - - + diff --git a/src/Mono.Android.Runtime/Mono.Android.Runtime.targets b/src/Mono.Android.Runtime/Mono.Android.Runtime.targets new file mode 100644 index 00000000000..0b00eed9b47 --- /dev/null +++ b/src/Mono.Android.Runtime/Mono.Android.Runtime.targets @@ -0,0 +1,52 @@ + + + + + + <_PackageVersion>$(ProductVersion) + <_PackageVersionBuild>$(XAVersionCommitCount) + + + <_PackageVersion>$(AndroidPackVersion) + <_PackageVersionBuild>$(PackVersionCommitCount) + + + + + + + + + + + + + From d26d9e151d31adbde2fb0501c5c539fdaa042085 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 5 Oct 2022 18:43:22 +0200 Subject: [PATCH 77/79] Get rid of dead code It was a remnant from the attempt to move the `JNIEnvInit` class to a separate assembly. --- src/monodroid/jni/monodroid-glue.cc | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 09a297dcde0..590a4ec55cc 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1105,26 +1105,16 @@ MonodroidRuntime::init_android_runtime ( Class_getName = env->GetMethodID (init.grefClass, "getName", "()Ljava/lang/String;"); init.Class_forName = env->GetStaticMethodID (init.grefClass, "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"); - MonoAssembly *runtime_assembly; MonoAssembly *mono_android_assembly; #if defined (NET) mono_android_assembly = utils.monodroid_load_assembly (default_alc, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME); - runtime_assembly = utils.monodroid_load_assembly (default_alc, SharedConstants::MONO_ANDROID_RUNTIME_ASSEMBLY_NAME); #else // def NET mono_android_assembly = utils.monodroid_load_assembly (domain, SharedConstants::MONO_ANDROID_ASSEMBLY_NAME); - runtime_assembly = mono_android_assembly; #endif // ndef NET - MonoImage *runtime_assembly_image = mono_assembly_get_image (runtime_assembly); - MonoImage *mono_android_assembly_image; + MonoImage *mono_android_assembly_image = mono_assembly_get_image (mono_android_assembly); -#if defined (NET) - mono_android_assembly_image = mono_assembly_get_image (mono_android_assembly); -#else - mono_android_assembly_image = runtime_assembly_image; -#endif uint32_t i = 0; - for ( ; i < OSBridge::NUM_XA_GC_BRIDGE_TYPES; ++i) { lookup_bridge_info ( #if !defined (NET) From d5016fee5a5a898b923fb15eb0119217ef109857 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 10 Oct 2022 22:39:11 +0200 Subject: [PATCH 78/79] Update documentation --- .../guides/building-apps/build-properties.md | 9 ++++ .../guides/internals/JavaJNI_Interop.md | 42 +++++++++++++------ 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/Documentation/guides/building-apps/build-properties.md b/Documentation/guides/building-apps/build-properties.md index 34c6fc8355a..513a3b2f68d 100644 --- a/Documentation/guides/building-apps/build-properties.md +++ b/Documentation/guides/building-apps/build-properties.md @@ -424,6 +424,15 @@ not wish to run those checks. Added in Xamarin.Android 9.4. +## AndroidEnableMarshalMethods + +A bool property, not available in the classic Xamarin.Android +releases. + +Enable or disable generation of [marshal +methods](../../internals/JavaJNI_Interop.md). Defaults to `True` for +`Release` builds and to `False` for `Debug` builds. + ## AndroidEnableMultiDex A boolean property that diff --git a/Documentation/guides/internals/JavaJNI_Interop.md b/Documentation/guides/internals/JavaJNI_Interop.md index 008fcfaabe1..88532c3ccff 100644 --- a/Documentation/guides/internals/JavaJNI_Interop.md +++ b/Documentation/guides/internals/JavaJNI_Interop.md @@ -588,11 +588,13 @@ The exact modifications we apply are: * Removal of the **connector backing field** * Removal of the **connector method** - * Addition of the `[UnmanagedCallersOnly]` attribute to the **native - callback** method - * Optionally, generation of a [non-blittable types - wrapper](#wrappers-for-methods-with-non-blittable-types) for the - **native callback** method. + * Generation of a **native callback wrapper** method, which catches + and propagates unhandled exceptions thrown by the native callback + or the target method. This method is decorated with the + `[UnmanagedCallersOnly]` attribute and called directly from the + native code. + * Optionally, generate code in the **native callback wrapper** to handle + [non-blittable types](#wrappers-for-methods-with-non-blittable-types). All the modifications are performed with `Mono.Cecil`. @@ -603,7 +605,6 @@ C# code for each marshal method: public class MainActivity : AppCompatActivity { // Native callback - [UnmanagedCallersOnly] static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) { var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; @@ -611,6 +612,17 @@ public class MainActivity : AppCompatActivity __this.OnCreate (savedInstanceState); } + // Native callback exception wrapper + [UnmanagedCallersOnly] + static void n_OnCreate_Landroid_os_Bundle__mm_wrapper (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState) + { + try { + n_OnCreate_Landroid_os_Bundle_ (jnienv, native__this, native_savedInstanceState) + } catch (Exception ex) { + Android.Runtime.AndroidEnvironmentInternal.UnhandledException (ex); + } + } + // Target method [Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")] protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState) @@ -665,13 +677,6 @@ value properly. Each wrapper method retains the native callback method name, but appends the `_mm_wrapper` suffix to it: ```csharp - -[UnmanagedCallersOnly] -static byte n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent__mm_wrapper (IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e) -{ - return n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_(jnienv, native__this, native_v, native_e) ? 1 : 0; -} - static bool n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_ (IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e) { var __this = global::Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; @@ -680,6 +685,17 @@ static bool n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_ (IntPtr jnie bool __ret = __this.OnTouch (v, e); return __ret; } + +[UnmanagedCallersOnly] +static byte n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent__mm_wrapper (IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e) +{ + try { + return n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_(jnienv, native__this, native_v, native_e) ? 1 : 0; + } catch (Exception ex) { + Android.Runtime.AndroidEnvironmentInternal.UnhandledException (ex); + return default; + } +} ``` The wrapper's return statement uses the ternary operator to "cast" the From f8db2d207b4e900e83d6c6933447112fa037d4bc Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 10 Oct 2022 22:55:20 +0200 Subject: [PATCH 79/79] Fix indentation and trailing whitespace --- .../guides/internals/JavaJNI_Interop.md | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Documentation/guides/internals/JavaJNI_Interop.md b/Documentation/guides/internals/JavaJNI_Interop.md index 88532c3ccff..234f69fd9ac 100644 --- a/Documentation/guides/internals/JavaJNI_Interop.md +++ b/Documentation/guides/internals/JavaJNI_Interop.md @@ -32,7 +32,7 @@ are described in the sections below. This guide is meant to explain the technical implementation in a way that is sufficient to understand the system without having to read the actual source code. - + # Java <-> Managed interoperability overview Java VM and Managed VM are two entirely separate entities which @@ -104,9 +104,9 @@ public class MainActivity : AppCompatActivity protected override void OnCreate (Bundle savedInstanceState) { base.OnCreate(savedInstanceState); - DoSomething (savedInstanceState); + DoSomething (savedInstanceState); } - + void DoSomething (Bundle bundle) { // do something with the bundle @@ -161,7 +161,7 @@ Java.Interop's [`JavaTypeScanner`](../../external/Java.Interop/src/Java.Interop. which uses `Mono.Cecil` to read all the assemblies referenced by the application and its libraries. The returned list of assemblies is then used by a variety of tasks, JCW being only one -of them. +of them. After all types are found, [`JavaCallableWrapperGenerator`](../../external/Java.Interop/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs) @@ -183,7 +183,7 @@ its constructor with three parameters: 1. Java method name 2. JNI method signature 3. "Connector" method name - + The "connector" is a static method which creates a delegate that subsequently allows calling of the native callback method: @@ -509,7 +509,7 @@ static get_function_pointer_fn get_function_pointer; void xamarin_app_init (get_function_pointer_fn fn) noexcept { - get_function_pointer = fn; + get_function_pointer = fn; } using android_app_activity_on_create_bundle_fn = void (*) (JNIEnv *env, jclass klass, jobject savedInstanceState); @@ -520,11 +520,11 @@ JNICALL Java_helloandroid_MainActivity_n_1onCreate__Landroid_os_Bundle_2 (JNIEnv { if (android_app_activity_on_create_bundle == nullptr) { get_function_pointer ( - 16, // mono image index - 0, // class index - 0x0600055B, // method token - reinterpret_cast(android_app_activity_on_create_bundle) // target pointer - ); + 16, // mono image index + 0, // class index + 0x0600055B, // method token + reinterpret_cast(android_app_activity_on_create_bundle) // target pointer + ); } android_app_activity_on_create_bundle (env, klass, savedInstanceState); @@ -618,11 +618,11 @@ public class MainActivity : AppCompatActivity { try { n_OnCreate_Landroid_os_Bundle_ (jnienv, native__this, native_savedInstanceState) - } catch (Exception ex) { - Android.Runtime.AndroidEnvironmentInternal.UnhandledException (ex); - } + } catch (Exception ex) { + Android.Runtime.AndroidEnvironmentInternal.UnhandledException (ex); + } } - + // Target method [Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")] protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState) @@ -692,7 +692,7 @@ static byte n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent__mm_wrapper ( try { return n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_(jnienv, native__this, native_v, native_e) ? 1 : 0; } catch (Exception ex) { - Android.Runtime.AndroidEnvironmentInternal.UnhandledException (ex); + Android.Runtime.AndroidEnvironmentInternal.UnhandledException (ex); return default; } } @@ -754,7 +754,7 @@ Dynamic registration uses the [`RegisterNatives`](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives) JNI function at the runtime, which stores a pointer to the registered method inside the structure which describes a Java class in the Java -VM. +VM. Marshal methods, however, don't register anything with the JNI, instead they rely on the symbol lookup mechanism of the Java VM.