From 35f4e240b11cff5d6cc9777378bee5d3f1d03d64 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 29 Mar 2022 22:01:22 +0200 Subject: [PATCH 01/28] 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/28] [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/28] 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/28] 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/28] 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/28] [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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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 6ca12454777e7b0bdd806ffbac5af153ffffbf44 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 22 Aug 2022 13:15:41 +0200 Subject: [PATCH 25/28] Update apkdesc files --- .../Base/BuildReleaseArm64SimpleDotNet.apkdesc | 17 ++++++++++------- .../Base/BuildReleaseArm64XFormsDotNet.apkdesc | 17 ++++++++++------- 2 files changed, 20 insertions(+), 14 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 068915f853b..24472a6e41e 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": 58665 }, "assemblies/Mono.Android.dll": { - "Size": 87971 + "Size": 87905 }, "assemblies/rc.bin": { "Size": 1182 @@ -25,8 +25,11 @@ "assemblies/System.Runtime.dll": { "Size": 2375 }, + "assemblies/System.Runtime.InteropServices.dll": { + "Size": 3722 + }, "assemblies/UnnamedProject.dll": { - "Size": 3560 + "Size": 3554 }, "classes.dex": { "Size": 360744 @@ -50,19 +53,19 @@ "Size": 148696 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 9784 + "Size": 9848 }, "META-INF/BNDLTOOL.RSA": { "Size": 1213 }, "META-INF/BNDLTOOL.SF": { - "Size": 2690 + "Size": 2807 }, "META-INF/MANIFEST.MF": { - "Size": 2563 + "Size": 2680 }, "res/drawable-hdpi-v4/icon.png": { - "Size": 4762 + "Size": 4791 }, "res/drawable-mdpi-v4/icon.png": { "Size": 2200 @@ -86,5 +89,5 @@ "Size": 1904 } }, - "PackageSize": 2664509 + "PackageSize": 2668696 } \ 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 8804f9c369c..08a5d9dddcf 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 @@ -11,7 +11,7 @@ "Size": 66533 }, "assemblies/Mono.Android.dll": { - "Size": 441958 + "Size": 441885 }, "assemblies/mscorlib.dll": { "Size": 3857 @@ -100,6 +100,9 @@ "assemblies/System.Runtime.dll": { "Size": 2578 }, + "assemblies/System.Runtime.InteropServices.dll": { + "Size": 3722 + }, "assemblies/System.Runtime.Serialization.dll": { "Size": 1948 }, @@ -122,7 +125,7 @@ "Size": 1857 }, "assemblies/UnnamedProject.dll": { - "Size": 117251 + "Size": 117246 }, "assemblies/Xamarin.AndroidX.Activity.dll": { "Size": 5872 @@ -212,7 +215,7 @@ "Size": 148696 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 99208 + "Size": 99272 }, "META-INF/android.support.design_material.version": { "Size": 12 @@ -326,13 +329,13 @@ "Size": 1213 }, "META-INF/BNDLTOOL.SF": { - "Size": 78979 + "Size": 79096 }, "META-INF/com.google.android.material_material.version": { "Size": 10 }, "META-INF/MANIFEST.MF": { - "Size": 78852 + "Size": 78969 }, "META-INF/proguard/androidx-annotations.pro": { "Size": 339 @@ -779,7 +782,7 @@ "Size": 470 }, "res/drawable-hdpi-v4/icon.png": { - "Size": 4762 + "Size": 4791 }, "res/drawable-hdpi-v4/notification_bg_low_normal.9.png": { "Size": 212 @@ -1967,5 +1970,5 @@ "Size": 341228 } }, - "PackageSize": 7995895 + "PackageSize": 8000082 } \ No newline at end of file From 63bf19339b55022d066666e51b64e4f38274d5a5 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 22 Aug 2022 15:49:38 +0200 Subject: [PATCH 26/28] Fix a failing test --- .../Tests/Xamarin.Android.Build.Tests/PackagingTest.cs | 1 + 1 file changed, 1 insertion(+) 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 e9a50539a9b..e81e2486c8b 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 @@ -91,6 +91,7 @@ public void CheckIncludedAssemblies ([Values (false, true)] bool usesAssemblySto "System.Console.dll", "System.Private.CoreLib.dll", "System.Runtime.dll", + "System.Runtime.InteropServices.dll", "System.Linq.dll", "UnnamedProject.dll", } : From c8a2d9f46ccad88f213ea473ef4287d9bc089fb7 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 23 Aug 2022 11:18:35 +0200 Subject: [PATCH 27/28] Implement some requested changes --- .../System.Runtime.InteropServices.xml | 1 - .../Android.Runtime/AndroidRuntime.cs | 12 ++++------ .../MarshalMethodsAssemblyRewriter.cs | 3 ++- .../Utilities/MarshalMethodsClassifier.cs | 2 +- .../MarshalMethodsNativeAssemblyGenerator.cs | 23 +++++++++++++++++++ 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/System.Runtime.InteropServices.xml b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/System.Runtime.InteropServices.xml index 284c4ec42f3..653e1295da5 100644 --- a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/System.Runtime.InteropServices.xml +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/System.Runtime.InteropServices.xml @@ -1,6 +1,5 @@ - 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 92fa671d51bcf465e43b6913de09338b2ccebb6d Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 24 Aug 2022 18:14:00 +0200 Subject: [PATCH 28/28] 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