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