From 903ba37ce70d2840983774e1d6fb55f8002561e2 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Fri, 19 Aug 2022 19:30:18 +0200 Subject: [PATCH] [Xamarin.Android.Build.Tasks, monodroid] LLVM-IR Generator (#7163) Context: e1af9587bb98d4c249bbc392ceccc2b53ffff155 Commit e1af9587 mentioned a TODO: > * Finish the LLVM code generator so that LLVM Marshal Methods are > emitted into `libxamarin-app.so`. Implement LLVM Marshal Methods LLVM-IR executable code generator: * LLVM-IR Generation Infrastructure: * 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 * LLVM-IR Marshal Method generator * managed to JNI signature and symbol name translations When `ENABLE_MARSHAL_METHODS` is defined, LLVM-IR code is generated, compiled, and linked. Generated code is minimally tested for runtime behavior, as not all the infrastructure on the Xamarin.Android side is implemented yet. This is on purpose, to keep commits smaller. 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. The native type handling system portion of this commit is to be treated as proof-of-concept: is is not as optimized (design wise) as it 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 commits' already substantial size. ~~ TODO ~~ Update/rewrite infrastructure to focus on implementing the runtime side of marshal methods, making it possible to actually run applications which use marshal methods. Finish prototyping the infrastructure so that a MAUI app can be run with LLVm Marshal Methods enabled. --- .../Tasks/GenerateJavaStubs.cs | 6 + .../LlvmIrGenerator/Arm32LlvmIrGenerator.cs | 14 + .../LlvmIrGenerator/Arm64LlvmIrGenerator.cs | 14 + .../LlvmIrGenerator/FunctionAttributes.cs | 798 ++++++++++++++++++ .../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, 2477 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 6906f31e0a0..14a17a1c295 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -237,6 +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); @@ -384,6 +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; 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..84167d9084d --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/FunctionAttributes.cs @@ -0,0 +1,798 @@ +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 ('"'); + } + + 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 ('"'); + } + + } + + 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