diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index e2b6a11b143..6945d6ad40f 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -629,7 +629,9 @@ class AndroidValueManager : JniRuntime.JniValueManager { public override void WaitForGCBridgeProcessing () { - AndroidRuntimeInternal.WaitForBridgeProcessing (); + if (!AndroidRuntimeInternal.BridgeProcessing) + return; + RuntimeNativeMethods._monodroid_gc_wait_for_bridge_processing (); } public override IJavaPeerable? CreatePeer ( diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs index 31d80445485..c8d3452af40 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs @@ -64,9 +64,7 @@ static void MonoUnhandledException (Exception ex) public static void WaitForBridgeProcessing () { - if (!BridgeProcessing) - return; - RuntimeNativeMethods._monodroid_gc_wait_for_bridge_processing (); + Java.Interop.JniEnvironment.Runtime.ValueManager.WaitForGCBridgeProcessing (); } } } diff --git a/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs b/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs index e740dfdb4c9..e4c68c98f7a 100644 --- a/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs +++ b/src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs @@ -2,6 +2,7 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Java; using System.Text; namespace Android.Runtime @@ -18,7 +19,7 @@ enum TraceKind : uint All = Java | Managed | Native | Signals, } - internal static class RuntimeNativeMethods + internal unsafe static class RuntimeNativeMethods { [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] internal extern static void monodroid_log (LogLevel level, LogCategories category, string message); @@ -92,6 +93,11 @@ internal static class RuntimeNativeMethods [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] internal static extern bool clr_typemap_java_to_managed (string java_type_name, out IntPtr managed_assembly_name, out uint managed_type_token_id); + [DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern delegate* unmanaged clr_initialize_gc_bridge ( + delegate* unmanaged bridge_processing_started_callback, + delegate* unmanaged bridge_processing_finished_callback); + [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern void monodroid_unhandled_exception (Exception javaException); diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs index 5d517c381c1..e84a518971b 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs @@ -1,5 +1,6 @@ // Originally from: https://github.com/dotnet/java-interop/blob/9b1d8781e8e322849d05efac32119c913b21c192/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; @@ -10,6 +11,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Java; using System.Threading; using Android.Runtime; using Java.Interop; @@ -20,52 +22,81 @@ class ManagedValueManager : JniRuntime.JniValueManager { const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; - Dictionary>? RegisteredInstances = new Dictionary>(); + readonly Dictionary> RegisteredInstances = new (); + readonly ConcurrentQueue CollectedContexts = new (); + + bool disposed; + + static readonly SemaphoreSlim bridgeProcessingSemaphore = new (1, 1); static Lazy s_instance = new (() => new ManagedValueManager ()); public static ManagedValueManager GetOrCreateInstance () => s_instance.Value; - ManagedValueManager () + unsafe ManagedValueManager () { + // There can only be one instance of ManagedValueManager because we can call JavaMarshal.Initialize only once. + var mark_cross_references_ftn = RuntimeNativeMethods.clr_initialize_gc_bridge (&BridgeProcessingStarted, &BridgeProcessingFinished); + JavaMarshal.Initialize (mark_cross_references_ftn); } - public override void WaitForGCBridgeProcessing () + protected override void Dispose (bool disposing) { + disposed = true; + base.Dispose (disposing); } - public override void CollectPeers () + void ThrowIfDisposed () { - if (RegisteredInstances == null) + if (disposed) throw new ObjectDisposedException (nameof (ManagedValueManager)); + } - var peers = new List (); + public override void WaitForGCBridgeProcessing () + { + bridgeProcessingSemaphore.Wait (); + bridgeProcessingSemaphore.Release (); + } - lock (RegisteredInstances) { - foreach (var ps in RegisteredInstances.Values) { - foreach (var p in ps) { - peers.Add (p); - } + public unsafe override void CollectPeers () + { + ThrowIfDisposed (); + + while (CollectedContexts.TryDequeue (out IntPtr contextPtr)) { + Debug.Assert (contextPtr != IntPtr.Zero, "CollectedContexts should not contain null pointers."); + HandleContext* context = (HandleContext*)contextPtr; + + lock (RegisteredInstances) { + Remove (context); } - RegisteredInstances.Clear (); + + HandleContext.Free (ref context); } - List? exceptions = null; - foreach (var peer in peers) { - try { - peer.Dispose (); + + void Remove (HandleContext* context) + { + int key = context->PeerIdentityHashCode; + if (!RegisteredInstances.TryGetValue (key, out List? peers)) + return; + + for (int i = peers.Count - 1; i >= 0; i--) { + var peer = peers [i]; + if (peer.BelongsToContext (context)) { + peers.RemoveAt (i); + } } - catch (Exception e) { - exceptions = exceptions ?? new List (); - exceptions.Add (e); + + if (peers.Count == 0) { + RegisteredInstances.Remove (key); } } - if (exceptions != null) - throw new AggregateException ("Exceptions while collecting peers.", exceptions); } public override void AddPeer (IJavaPeerable value) { - if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (ManagedValueManager)); + ThrowIfDisposed (); + + // Remove any collected contexts before adding a new peer. + CollectPeers (); var r = value.PeerReference; if (!r.IsValid) @@ -77,35 +108,31 @@ public override void AddPeer (IJavaPeerable value) } int key = value.JniIdentityHashCode; lock (RegisteredInstances) { - List? peers; + List? peers; if (!RegisteredInstances.TryGetValue (key, out peers)) { - peers = new List () { - value, - }; + peers = [new ReferenceTrackingHandle (value)]; RegisteredInstances.Add (key, peers); return; } for (int i = peers.Count - 1; i >= 0; i--) { - var p = peers [i]; - if (!JniEnvironment.Types.IsSameObject (p.PeerReference, value.PeerReference)) + ReferenceTrackingHandle peer = peers [i]; + if (peer.Target is not IJavaPeerable target) + continue; + if (!JniEnvironment.Types.IsSameObject (target.PeerReference, value.PeerReference)) continue; - if (Replaceable (p)) { - peers [i] = value; + if (target.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable)) { + peer.Dispose (); + peers [i] = new ReferenceTrackingHandle (value); } else { - WarnNotReplacing (key, value, p); + WarnNotReplacing (key, value, target); } + GC.KeepAlive (target); return; } - peers.Add (value); - } - } - static bool Replaceable (IJavaPeerable peer) - { - if (peer == null) - return true; - return peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable); + peers.Add (new ReferenceTrackingHandle (value)); + } } void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepValue) @@ -126,8 +153,7 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal public override IJavaPeerable? PeekPeer (JniObjectReference reference) { - if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (ManagedValueManager)); + ThrowIfDisposed (); if (!reference.IsValid) return null; @@ -135,15 +161,17 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal int key = GetJniIdentityHashCode (reference); lock (RegisteredInstances) { - List? peers; - if (!RegisteredInstances.TryGetValue (key, out peers)) + if (!RegisteredInstances.TryGetValue (key, out List? peers)) return null; for (int i = peers.Count - 1; i >= 0; i--) { - var p = peers [i]; - if (JniEnvironment.Types.IsSameObject (reference, p.PeerReference)) - return p; + if (peers [i].Target is IJavaPeerable peer + && JniEnvironment.Types.IsSameObject (reference, peer.PeerReference)) + { + return peer; + } } + if (peers.Count == 0) RegisteredInstances.Remove (key); } @@ -152,23 +180,27 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal public override void RemovePeer (IJavaPeerable value) { - if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (ManagedValueManager)); + ThrowIfDisposed (); + + // Remove any collected contexts before modifying RegisteredInstances + CollectPeers (); if (value == null) throw new ArgumentNullException (nameof (value)); - int key = value.JniIdentityHashCode; lock (RegisteredInstances) { - List? peers; - if (!RegisteredInstances.TryGetValue (key, out peers)) + int key = value.JniIdentityHashCode; + if (!RegisteredInstances.TryGetValue (key, out List? peers)) return; for (int i = peers.Count - 1; i >= 0; i--) { - var p = peers [i]; - if (object.ReferenceEquals (value, p)) { + ReferenceTrackingHandle peer = peers [i]; + IJavaPeerable target = peer.Target; + if (ReferenceEquals (value, target)) { peers.RemoveAt (i); + peer.Dispose (); } + GC.KeepAlive (target); } if (peers.Count == 0) RegisteredInstances.Remove (key); @@ -247,21 +279,220 @@ Type GetDeclaringType (ConstructorInfo cinfo) => public override List GetSurfacedPeers () { - if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (ManagedValueManager)); + ThrowIfDisposed (); + + // Remove any collected contexts before iterating over all the registered instances + CollectPeers (); lock (RegisteredInstances) { var peers = new List (RegisteredInstances.Count); - foreach (var e in RegisteredInstances) { - foreach (var p in e.Value) { - peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference (p))); + foreach (var (identityHashCode, referenceTrackingHandles) in RegisteredInstances) { + foreach (var peer in referenceTrackingHandles) { + if (peer.Target is IJavaPeerable target) { + peers.Add (new JniSurfacedPeerInfo (identityHashCode, new WeakReference (target))); + } } } return peers; } } - const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + unsafe struct ReferenceTrackingHandle : IDisposable + { + WeakReference _weakReference; + HandleContext* _context; + + public bool BelongsToContext (HandleContext* context) + => _context == context; + + public ReferenceTrackingHandle (IJavaPeerable peer) + { + _context = HandleContext.Alloc (peer); + _weakReference = new WeakReference (peer); + } + + public IJavaPeerable? Target + => _weakReference.TryGetTarget (out var target) ? target : null; + + public void Dispose () + { + if (_context == null) + return; + + IJavaPeerable? target = Target; + + GCHandle handle = HandleContext.GetAssociatedGCHandle (_context); + HandleContext.Free (ref _context); + _weakReference.SetTarget (null); + if (handle.IsAllocated) { + handle.Free (); + } + + // Make sure the target is not collected before we finish disposing + GC.KeepAlive (target); + } + } + + [StructLayout (LayoutKind.Sequential)] + unsafe struct HandleContext + { + static readonly nuint Size = (nuint)Marshal.SizeOf (); + static readonly Dictionary referenceTrackingHandles = new (); + + int identityHashCode; + IntPtr controlBlock; + + public int PeerIdentityHashCode => identityHashCode; + public bool IsCollected + { + get + { + if (controlBlock == IntPtr.Zero) + throw new InvalidOperationException ("HandleContext control block is not initialized."); + + return ((JniObjectReferenceControlBlock*) controlBlock)->handle == IntPtr.Zero; + } + } + + // This is an internal mirror of the Java.Interop.JniObjectReferenceControlBlock + private struct JniObjectReferenceControlBlock + { + public IntPtr handle; + public int handle_type; + public IntPtr weak_handle; + public int refs_added; + } + + public static GCHandle GetAssociatedGCHandle (HandleContext* context) + { + lock (referenceTrackingHandles) { + if (!referenceTrackingHandles.TryGetValue ((IntPtr) context, out GCHandle handle)) { + throw new InvalidOperationException ("Unknown reference tracking handle."); + } + + return handle; + } + } + + public static unsafe void EnsureAllContextsAreOurs (MarkCrossReferencesArgs* mcr) + { + lock (referenceTrackingHandles) { + for (nuint i = 0; i < mcr->ComponentCount; i++) { + StronglyConnectedComponent component = mcr->Components [i]; + EnsureAllContextsInComponentAreOurs (component); + } + } + + static void EnsureAllContextsInComponentAreOurs (StronglyConnectedComponent component) + { + for (nuint i = 0; i < component.Count; i++) { + EnsureContextIsOurs ((IntPtr)component.Contexts [i]); + } + } + + static void EnsureContextIsOurs (IntPtr context) + { + if (!referenceTrackingHandles.ContainsKey (context)) { + throw new InvalidOperationException ("Unknown reference tracking handle."); + } + } + } + + public static HandleContext* Alloc (IJavaPeerable peer) + { + var context = (HandleContext*) NativeMemory.AllocZeroed (1, Size); + if (context == null) { + throw new OutOfMemoryException ("Failed to allocate memory for HandleContext."); + } + + context->identityHashCode = peer.JniIdentityHashCode; + context->controlBlock = peer.JniObjectReferenceControlBlock; + + GCHandle handle = JavaMarshal.CreateReferenceTrackingHandle (peer, context); + lock (referenceTrackingHandles) { + referenceTrackingHandles [(IntPtr) context] = handle; + } + + return context; + } + + public static void Free (ref HandleContext* context) + { + if (context == null) { + return; + } + + lock (referenceTrackingHandles) { + referenceTrackingHandles.Remove ((IntPtr)context); + } + + NativeMemory.Free (context); + context = null; + } + } + + [UnmanagedCallersOnly] + static unsafe void BridgeProcessingStarted (MarkCrossReferencesArgs* mcr) + { + if (mcr == null) { + throw new ArgumentNullException (nameof (mcr), "MarkCrossReferencesArgs should never be null."); + } + + HandleContext.EnsureAllContextsAreOurs (mcr); + bridgeProcessingSemaphore.Wait (); + } + + [UnmanagedCallersOnly] + static unsafe void BridgeProcessingFinished (MarkCrossReferencesArgs* mcr) + { + if (mcr == null) { + throw new ArgumentNullException (nameof (mcr), "MarkCrossReferencesArgs should never be null."); + } + + ReadOnlySpan handlesToFree = ProcessCollectedContexts (mcr); + JavaMarshal.FinishCrossReferenceProcessing (mcr, handlesToFree); + + bridgeProcessingSemaphore.Release (); + } + + static unsafe ReadOnlySpan ProcessCollectedContexts (MarkCrossReferencesArgs* mcr) + { + List handlesToFree = []; + ManagedValueManager instance = GetOrCreateInstance (); + + for (int i = 0; (nuint)i < mcr->ComponentCount; i++) { + StronglyConnectedComponent component = mcr->Components [i]; + for (int j = 0; (nuint)j < component.Count; j++) { + ProcessContext ((HandleContext*)component.Contexts [j]); + } + } + + void ProcessContext (HandleContext* context) + { + if (context == null) { + throw new ArgumentNullException (nameof (context), "HandleContext should never be null."); + } + + // Ignore contexts which were not collected + if (!context->IsCollected) { + return; + } + + GCHandle handle = HandleContext.GetAssociatedGCHandle (context); + + // Note: modifying the RegisteredInstances dictionary while processing the collected contexts + // is tricky and can lead to deadlocks, so we remember which contexts were collected and we will free + // them later outside of the bridge processing loop. + instance.CollectedContexts.Enqueue ((IntPtr)context); + + // important: we must not free the handle before passing it to JavaMarshal.FinishCrossReferenceProcessing + handlesToFree.Add (handle); + } + + return CollectionsMarshal.AsSpan (handlesToFree); + } + + const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) }; @@ -285,11 +516,11 @@ protected override bool TryConstructPeer ( return base.TryConstructPeer (self, ref reference, options, type); } - protected override bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (true)]out object? result) + protected override bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (true)] out object? result) { var proxy = value as JavaProxyThrowable; if (proxy != null) { - result = proxy.InnerException; + result = proxy.InnerException; return true; } return base.TryUnboxPeerObject (value, out result); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc index 0d09c5250f4..59aacae52d3 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc @@ -5,67 +5,67 @@ "Size": 3036 }, "classes.dex": { - "Size": 22484 + "Size": 22488 }, "lib/arm64-v8a/lib__Microsoft.Android.Resource.Designer.dll.so": { "Size": 18288 }, "lib/arm64-v8a/lib_Java.Interop.dll.so": { - "Size": 86688 + "Size": 88472 }, "lib/arm64-v8a/lib_Mono.Android.dll.so": { - "Size": 117712 + "Size": 124040 }, "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": { - "Size": 22384 + "Size": 23424 }, "lib/arm64-v8a/lib_System.Console.dll.so": { - "Size": 24392 + "Size": 24408 }, "lib/arm64-v8a/lib_System.Linq.dll.so": { - "Size": 25336 + "Size": 25488 }, "lib/arm64-v8a/lib_System.Private.CoreLib.dll.so": { - "Size": 628216 + "Size": 692528 }, "lib/arm64-v8a/lib_System.Runtime.dll.so": { - "Size": 20056 + "Size": 20104 }, "lib/arm64-v8a/lib_System.Runtime.InteropServices.dll.so": { - "Size": 21480 + "Size": 21528 }, "lib/arm64-v8a/lib_UnnamedProject.dll.so": { "Size": 20024 }, "lib/arm64-v8a/libarc.bin.so": { - "Size": 18872 + "Size": 18984 }, "lib/arm64-v8a/libmono-component-marshal-ilgen.so": { - "Size": 36440 + "Size": 36616 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 1524752 + "Size": 1508312 }, "lib/arm64-v8a/libmonosgen-2.0.so": { - "Size": 3101112 + "Size": 3119744 }, "lib/arm64-v8a/libSystem.Globalization.Native.so": { - "Size": 71976 + "Size": 71952 }, "lib/arm64-v8a/libSystem.IO.Compression.Native.so": { - "Size": 758896 + "Size": 759304 }, "lib/arm64-v8a/libSystem.Native.so": { - "Size": 103520 + "Size": 104216 }, "lib/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so": { - "Size": 165000 + "Size": 165240 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 19536 + "Size": 19800 }, "META-INF/BNDLTOOL.RSA": { - "Size": 1221 + "Size": 1223 }, "META-INF/BNDLTOOL.SF": { "Size": 3266 @@ -98,5 +98,5 @@ "Size": 1904 } }, - "PackageSize": 3078677 + "PackageSize": 3148309 } \ No newline at end of file diff --git a/src/native/clr/host/CMakeLists.txt b/src/native/clr/host/CMakeLists.txt index d2338f4f072..b8d1785fe39 100644 --- a/src/native/clr/host/CMakeLists.txt +++ b/src/native/clr/host/CMakeLists.txt @@ -29,6 +29,8 @@ set(XAMARIN_NET_ANDROID_STATIC_LIB "${XAMARIN_NET_ANDROID_LIB}-static") set(XAMARIN_MONODROID_SOURCES assembly-store.cc + bridge-processing.cc + gc-bridge.cc host.cc host-jni.cc host-util.cc diff --git a/src/native/clr/host/bridge-processing.cc b/src/native/clr/host/bridge-processing.cc new file mode 100644 index 00000000000..a72e75d902a --- /dev/null +++ b/src/native/clr/host/bridge-processing.cc @@ -0,0 +1,428 @@ +#include +#include +#include +#include +#include + +using namespace xamarin::android; + +void BridgeProcessing::initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept +{ + abort_if_invalid_pointer_argument (env, "env"); + abort_if_invalid_pointer_argument (runtimeClass, "runtimeClass"); + + GCUserPeer_class = RuntimeUtil::get_class_from_runtime_field (env, runtimeClass, "mono_android_GCUserPeer", true); + GCUserPeer_ctor = env->GetMethodID (GCUserPeer_class, "", "()V"); + + abort_unless (GCUserPeer_class != nullptr && GCUserPeer_ctor != nullptr, "Failed to load mono.android.GCUserPeer!"); +} + +BridgeProcessing::BridgeProcessing (MarkCrossReferencesArgs *args) noexcept + : env{ OSBridge::ensure_jnienv () }, + cross_refs{ args } +{ + if (args == nullptr) [[unlikely]] { + Helpers::abort_application (LOG_GC, "Cross references argument is a NULL pointer"sv); + } + + if (args->ComponentCount > 0 && args->Components == nullptr) [[unlikely]] { + Helpers::abort_application (LOG_GC, "Components member of the cross references arguments structure is NULL"sv); + } + + if (args->CrossReferenceCount > 0 && args->CrossReferences == nullptr) [[unlikely]] { + Helpers::abort_application (LOG_GC, "CrossReferences member of the cross references arguments structure is NULL"sv); + } +} + +void BridgeProcessing::process () noexcept +{ + prepare_for_java_collection (); + GCBridge::trigger_java_gc (env); + cleanup_after_java_collection (); + log_gc_summary (); +} + +void BridgeProcessing::prepare_for_java_collection () noexcept +{ + // Before looking at xrefs, scan the SCCs. During collection, an SCC has to behave like a + // single object. If the number of objects in the SCC is anything other than 1, the SCC + // must be doctored to mimic that one-object nature. + for (size_t i = 0; i < cross_refs->ComponentCount; i++) { + const StronglyConnectedComponent &scc = cross_refs->Components [i]; + prepare_scc_for_java_collection (i, scc); + } + + // Add the cross scc refs + for (size_t i = 0; i < cross_refs->CrossReferenceCount; i++) { + const ComponentCrossReference &xref = cross_refs->CrossReferences [i]; + add_cross_reference (xref.SourceGroupIndex, xref.DestinationGroupIndex); + } + + // With cross references processed, the temporary peer list can be released + for (const auto& [scc, temporary_peer] : temporary_peers) { + env->DeleteLocalRef (temporary_peer); + } + + // Switch global to weak references + for (size_t i = 0; i < cross_refs->ComponentCount; i++) { + const StronglyConnectedComponent &scc = cross_refs->Components [i]; + for (size_t j = 0; j < scc.Count; j++) { + const HandleContext *context = scc.Contexts [j]; + abort_unless (context != nullptr, "Context must not be null"); + + take_weak_global_ref (*context); + } + } +} + +void BridgeProcessing::prepare_scc_for_java_collection (size_t scc_index, const StronglyConnectedComponent &scc) noexcept +{ + // Count == 0 case: Some SCCs might have no IGCUserPeers associated with them, so we must create one + if (scc.Count == 0) { + temporary_peers [scc_index] = env->NewObject (GCUserPeer_class, GCUserPeer_ctor); + return; + } + + // Count == 1 case: The SCC contains a single object, there is no need to do anything special. + if (scc.Count == 1) { + return; + } + + // Count > 1 case: The SCC contains many objects which must be collected as one. + // Solution: Make all objects within the SCC directly or indirectly reference each other + add_circular_references (scc); +} + +CrossReferenceTarget BridgeProcessing::select_cross_reference_target (size_t scc_index) noexcept +{ + const StronglyConnectedComponent &scc = cross_refs->Components [scc_index]; + + if (scc.Count == 0) { + const auto temporary_peer = temporary_peers.find (scc_index); + abort_unless (temporary_peer != temporary_peers.end(), "Temporary peer must be found in the map"); + return { .is_temporary_peer = true, .temporary_peer = temporary_peer->second }; + } + + abort_unless (scc.Contexts [0] != nullptr, "SCC must have at least one context"); + return { .is_temporary_peer = false, .context = scc.Contexts [0] }; +} + +// caller must ensure that scc.Count > 1 +void BridgeProcessing::add_circular_references (const StronglyConnectedComponent &scc) noexcept +{ + auto get_control_block = [&scc](size_t index) -> JniObjectReferenceControlBlock& { + abort_unless (scc.Contexts [index] != nullptr, "Context in SCC must not be null"); + JniObjectReferenceControlBlock *control_block = scc.Contexts [index]->control_block; + abort_unless (control_block != nullptr, "Control block in SCC must not be null"); + return *control_block; + }; + + size_t prev_index = scc.Count - 1; + for (size_t next_index = 0; next_index < scc.Count; next_index++) { + JniObjectReferenceControlBlock &prev = get_control_block (prev_index); + const JniObjectReferenceControlBlock &next = get_control_block (next_index); + + bool reference_added = add_reference (prev.handle, next.handle); + + abort_unless (reference_added, [this, &prev, &next] { + jclass prev_java_class = env->GetObjectClass (prev.handle); + const char *prev_class_name = Host::get_java_class_name_for_TypeManager (prev_java_class); + + jclass next_java_class = env->GetObjectClass (next.handle); + const char *next_class_name = Host::get_java_class_name_for_TypeManager (next_java_class); + + return detail::_format_message ( + "Failed to add reference between objects in a strongly connected component: %s -> %s.", + prev_class_name, + next_class_name); + }); + + prev.refs_added = 1; + prev_index = next_index; + } +} + +void BridgeProcessing::add_cross_reference (size_t source_index, size_t dest_index) noexcept +{ + CrossReferenceTarget from = select_cross_reference_target (source_index); + CrossReferenceTarget to = select_cross_reference_target (dest_index); + + if (add_reference (from.get_handle(), to.get_handle())) { + from.mark_refs_added_if_needed (); + } +} + +bool BridgeProcessing::add_reference (jobject from, jobject to) noexcept +{ + abort_if_invalid_pointer_argument (from, "from"); + abort_if_invalid_pointer_argument (to, "to"); + + jclass java_class = env->GetObjectClass (from); + jmethodID add_method_id = env->GetMethodID (java_class, "monodroidAddReference", "(Ljava/lang/Object;)V"); + + if (add_method_id == nullptr) [[unlikely]] { + // TODO: is this a fatal error? + env->ExceptionClear (); + log_missing_add_references_method (java_class); + env->DeleteLocalRef (java_class); + return false; + } + + env->DeleteLocalRef (java_class); + env->CallVoidMethod (from, add_method_id, to); + return true; +} + +void BridgeProcessing::clear_references_if_needed (const HandleContext &context) noexcept +{ + if (context.is_collected ()) { + return; + } + + JniObjectReferenceControlBlock *control_block = context.control_block; + + abort_unless (control_block != nullptr, "Control block must not be null"); + abort_unless (control_block->handle != nullptr, "Control block handle must not be null"); + abort_unless (control_block->handle_type == JNIGlobalRefType, "Control block handle type must be global reference"); + + if (control_block->refs_added == 0) { + return; + } + + clear_references (control_block->handle); + control_block->refs_added = 0; +} + +void BridgeProcessing::clear_references (jobject handle) noexcept +{ + abort_if_invalid_pointer_argument (handle, "handle"); + + jclass java_class = env->GetObjectClass (handle); + jmethodID clear_method_id = env->GetMethodID (java_class, "monodroidClearReferences", "()V"); + + if (clear_method_id == nullptr) [[unlikely]] { + env->ExceptionClear (); + log_missing_clear_references_method (java_class); + env->DeleteLocalRef (java_class); + return; + } + + env->DeleteLocalRef (java_class); + env->CallVoidMethod (handle, clear_method_id); +} + +void BridgeProcessing::take_global_ref (HandleContext &context) noexcept +{ + abort_unless (context.control_block != nullptr, "Control block must not be null"); + abort_unless (context.control_block->handle_type == JNIWeakGlobalRefType, "Expected weak global reference type for handle"); + + jobject weak = context.control_block->handle; + jobject handle = env->NewGlobalRef (weak); + log_weak_to_gref (weak, handle); + + if (handle == nullptr) { + log_weak_ref_collected (weak); + } + + context.control_block->handle = handle; // by doing this, the weak reference won't be deleted AGAIN in managed code + context.control_block->handle_type = JNIGlobalRefType; + + log_weak_ref_delete (weak); + env->DeleteWeakGlobalRef (weak); +} + +void BridgeProcessing::take_weak_global_ref (const HandleContext &context) noexcept +{ + abort_unless (context.control_block != nullptr, "Control block must not be null"); + abort_unless (context.control_block->handle_type == JNIGlobalRefType, "Expected global reference type for handle"); + + jobject handle = context.control_block->handle; + log_take_weak_global_ref (handle); + + jobject weak = env->NewWeakGlobalRef (handle); + log_weak_gref_new (handle, weak); + + context.control_block->handle = weak; + context.control_block->handle_type = JNIWeakGlobalRefType; + + log_gref_delete (handle); + env->DeleteGlobalRef (handle); +} + +void BridgeProcessing::cleanup_after_java_collection () noexcept +{ + for (size_t i = 0; i < cross_refs->ComponentCount; i++) { + const StronglyConnectedComponent &scc = cross_refs->Components [i]; + + // try to switch back to global refs to analyze what stayed alive + for (size_t j = 0; j < scc.Count; j++) { + HandleContext *context = scc.Contexts [j]; + abort_unless (context != nullptr, "Context must not be null"); + + take_global_ref (*context); + clear_references_if_needed (*context); + } + + abort_unless_all_collected_or_all_alive (scc); + } +} + +void BridgeProcessing::abort_unless_all_collected_or_all_alive (const StronglyConnectedComponent &scc) noexcept +{ + if (scc.Count == 0) { + return; + } + + abort_unless (scc.Contexts [0] != nullptr, "Context must not be null"); + bool is_collected = scc.Contexts [0]->is_collected (); + + for (size_t j = 1; j < scc.Count; j++) { + const HandleContext *context = scc.Contexts [j]; + abort_unless (context != nullptr, "Context must not be null"); + abort_unless (context->is_collected () == is_collected, "Cannot have a mix of collected and alive contexts in the SCC"); + } +} + +jobject CrossReferenceTarget::get_handle () const noexcept +{ + if (is_temporary_peer) { + return temporary_peer; + } + + abort_unless (context != nullptr, "Context must not be null"); + abort_unless (context->control_block != nullptr, "Control block must not be null"); + return context->control_block->handle; +} + +void CrossReferenceTarget::mark_refs_added_if_needed () noexcept +{ + if (is_temporary_peer) { + return; + } + + abort_unless (context != nullptr, "Context must not be null"); + abort_unless (context->control_block != nullptr, "Control block must not be null"); + context->control_block->refs_added = 1; +} + +[[gnu::always_inline]] +void BridgeProcessing::log_missing_add_references_method ([[maybe_unused]] jclass java_class) noexcept +{ + log_error (LOG_DEFAULT, "Failed to find monodroidAddReferences method"); +#if DEBUG + abort_if_invalid_pointer_argument (java_class, "java_class"); + if (!Logger::gc_spew_enabled ()) [[likely]] { + return; + } + + char *class_name = Host::get_java_class_name_for_TypeManager (java_class); + log_error (LOG_GC, "Missing monodroidAddReferences method for object of class {}", optional_string (class_name)); + free (class_name); +#endif +} + +[[gnu::always_inline]] +void BridgeProcessing::log_missing_clear_references_method ([[maybe_unused]] jclass java_class) noexcept +{ + log_error (LOG_DEFAULT, "Failed to find monodroidClearReferences method"); +#if DEBUG + abort_if_invalid_pointer_argument (java_class, "java_class"); + if (!Logger::gc_spew_enabled ()) [[likely]] { + return; + } + + char *class_name = Host::get_java_class_name_for_TypeManager (java_class); + log_error (LOG_GC, "Missing monodroidClearReferences method for object of class {}", optional_string (class_name)); + free (class_name); +#endif +} + +[[gnu::always_inline]] +void BridgeProcessing::log_weak_to_gref (jobject weak, jobject handle) noexcept +{ + if (handle != nullptr) { + OSBridge::_monodroid_gref_log_new (weak, OSBridge::get_object_ref_type (env, weak), + handle, OSBridge::get_object_ref_type (env, handle), + "finalizer", gettid (), + " at [[clr-gc:take_global_ref]]", 0); + } + + if (!Logger::gref_log ()) [[likely]] { + return; + } + + OSBridge::_monodroid_gref_log ( + std::format ("take_global_ref wref={:#x} -> handle={:#x}\n"sv, + reinterpret_cast (weak), + reinterpret_cast (handle)).data ()); +} + +[[gnu::always_inline]] +void BridgeProcessing::log_weak_ref_collected (jobject weak) noexcept +{ + if (Logger::gc_spew_enabled ()) [[likely]] { + return; + } + + OSBridge::_monodroid_gref_log ( + std::format ("handle {:#x}/W; was collected by a Java GC"sv, reinterpret_cast (weak)).data ()); +} + +[[gnu::always_inline]] +void BridgeProcessing::log_take_weak_global_ref (jobject handle) noexcept +{ + if (!Logger::gref_log ()) [[likely]] { + return; + } + + OSBridge::_monodroid_gref_log (std::format ("take_weak_global_ref handle={:#x}\n"sv, reinterpret_cast (handle)).data ()); +} + +[[gnu::always_inline]] +void BridgeProcessing::log_weak_gref_new (jobject handle, jobject weak) noexcept +{ + OSBridge::_monodroid_weak_gref_new (handle, OSBridge::get_object_ref_type (env, handle), + weak, OSBridge::get_object_ref_type (env, weak), + "finalizer", gettid (), " at [[clr-gc:take_weak_global_ref]]", 0); +} + +[[gnu::always_inline]] +void BridgeProcessing::log_gref_delete (jobject handle) noexcept +{ + OSBridge::_monodroid_gref_log_delete (handle, OSBridge::get_object_ref_type (env, handle), + "finalizer", gettid (), " at [[clr-gc:take_weak_global_ref]]", 0); +} + +[[gnu::always_inline]] +void BridgeProcessing::log_weak_ref_delete (jobject weak) noexcept +{ + OSBridge::_monodroid_weak_gref_delete (weak, OSBridge::get_object_ref_type (env, weak), + "finalizer", gettid (), " at [[clr-gc:take_global_ref]]", 0); +} + +[[gnu::always_inline]] +void BridgeProcessing::log_gc_summary () noexcept +{ + if (!Logger::gc_spew_enabled ()) [[likely]] { + return; + } + + size_t total = 0; + size_t alive = 0; + + for (size_t i = 0; i < cross_refs->ComponentCount; i++) { + const StronglyConnectedComponent &scc = cross_refs->Components [i]; + + for (size_t j = 0; j < scc.Count; j++) { + HandleContext *context = scc.Contexts [j]; + abort_unless (context != nullptr, "Context must not be null"); + + total = Helpers::add_with_overflow_check (total, 1); + if (!context->is_collected ()) { + alive = Helpers::add_with_overflow_check (alive, 1); + } + } + } + + log_info (LOG_GC, "GC cleanup summary: {} objects tested - resurrecting {}.", total, alive); +} diff --git a/src/native/clr/host/gc-bridge.cc b/src/native/clr/host/gc-bridge.cc new file mode 100644 index 00000000000..6bf3a387e96 --- /dev/null +++ b/src/native/clr/host/gc-bridge.cc @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include + +using namespace xamarin::android; + +void GCBridge::initialize_on_onload (JNIEnv *env) noexcept +{ + abort_if_invalid_pointer_argument (env, "env"); + + jclass Runtime_class = env->FindClass ("java/lang/Runtime"); + abort_unless (Runtime_class != nullptr, "Failed to look up java/lang/Runtime class."); + + jmethodID Runtime_getRuntime = env->GetStaticMethodID (Runtime_class, "getRuntime", "()Ljava/lang/Runtime;"); + abort_unless (Runtime_getRuntime != nullptr, "Failed to look up the Runtime.getRuntime() method."); + + Runtime_gc = env->GetMethodID (Runtime_class, "gc", "()V"); + abort_unless (Runtime_gc != nullptr, "Failed to look up the Runtime.gc() method."); + + Runtime_instance = OSBridge::lref_to_gref (env, env->CallStaticObjectMethod (Runtime_class, Runtime_getRuntime)); + abort_unless (Runtime_instance != nullptr, "Failed to obtain Runtime instance."); + + env->DeleteLocalRef (Runtime_class); +} + +void GCBridge::initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept +{ + abort_if_invalid_pointer_argument (env, "env"); + abort_if_invalid_pointer_argument (runtimeClass, "runtimeClass"); + + BridgeProcessing::initialize_on_runtime_init (env, runtimeClass); +} + +void GCBridge::trigger_java_gc (JNIEnv *env) noexcept +{ + abort_if_invalid_pointer_argument (env, "env"); + + env->CallVoidMethod (Runtime_instance, Runtime_gc); + if (!env->ExceptionCheck ()) [[likely]] { + return; + } + + env->ExceptionDescribe (); + env->ExceptionClear (); + log_error (LOG_DEFAULT, "Java GC failed"); +} + +void GCBridge::mark_cross_references (MarkCrossReferencesArgs *args) noexcept +{ + abort_if_invalid_pointer_argument (args, "args"); + abort_unless (args->Components != nullptr || args->ComponentCount == 0, "Components must not be null if ComponentCount is greater than 0"); + abort_unless (args->CrossReferences != nullptr || args->CrossReferenceCount == 0, "CrossReferences must not be null if CrossReferenceCount is greater than 0"); + log_mark_cross_references_args_if_enabled (args); + + shared_args.store (args); + shared_args_semaphore.release (); +} + +void GCBridge::bridge_processing () noexcept +{ + abort_unless (bridge_processing_started_callback != nullptr, "GC bridge processing started callback is not set"); + abort_unless (bridge_processing_finished_callback != nullptr, "GC bridge processing finished callback is not set"); + + while (true) { + // wait until mark cross references args are set by the GC callback + shared_args_semaphore.acquire (); + MarkCrossReferencesArgs *args = shared_args.load (); + + bridge_processing_started_callback (args); + + BridgeProcessing bridge_processing {args}; + bridge_processing.process (); + + bridge_processing_finished_callback (args); + } +} + +[[gnu::always_inline]] +void GCBridge::log_mark_cross_references_args_if_enabled (MarkCrossReferencesArgs *args) noexcept +{ + if (!Logger::gc_spew_enabled ()) [[likely]] { + return; + } + + log_info (LOG_GC, "cross references callback invoked with {} sccs and {} xrefs.", args->ComponentCount, args->CrossReferenceCount); + + JNIEnv *env = OSBridge::ensure_jnienv (); + + for (size_t i = 0; i < args->ComponentCount; ++i) { + const StronglyConnectedComponent &scc = args->Components [i]; + log_info (LOG_GC, "group {} with {} objects", i, scc.Count); + for (size_t j = 0; j < scc.Count; ++j) { + log_handle_context (env, scc.Contexts [j]); + } + } + + if (!Util::should_log (LOG_GC)) { + return; + } + + for (size_t i = 0; i < args->CrossReferenceCount; ++i) { + size_t source_index = args->CrossReferences [i].SourceGroupIndex; + size_t dest_index = args->CrossReferences [i].DestinationGroupIndex; + log_info_nocheck_fmt (LOG_GC, "xref [{}] {} -> {}", i, source_index, dest_index); + } +} + +[[gnu::always_inline]] +void GCBridge::log_handle_context (JNIEnv *env, HandleContext *ctx) noexcept +{ + abort_unless (ctx != nullptr, "Context must not be null"); + abort_unless (ctx->control_block != nullptr, "Control block must not be null"); + + jobject handle = ctx->control_block->handle; + jclass java_class = env->GetObjectClass (handle); + if (java_class != nullptr) { + char *class_name = Host::get_java_class_name_for_TypeManager (java_class); + log_info (LOG_GC, "gref {:#x} [{}]", reinterpret_cast (handle), class_name); + free (class_name); + } else { + log_info (LOG_GC, "gref {:#x} [unknown class]", reinterpret_cast (handle)); + } +} diff --git a/src/native/clr/host/generate-pinvoke-tables.cc b/src/native/clr/host/generate-pinvoke-tables.cc index 8f31936cdd7..0b354cae033 100644 --- a/src/native/clr/host/generate-pinvoke-tables.cc +++ b/src/native/clr/host/generate-pinvoke-tables.cc @@ -72,6 +72,7 @@ const std::vector internal_pinvoke_names = { "monodroid_TypeManager_get_java_class_name", "clr_typemap_managed_to_java", "clr_typemap_java_to_managed", + "clr_initialize_gc_bridge", "_monodroid_weak_gref_delete", "_monodroid_weak_gref_get", "_monodroid_weak_gref_new", diff --git a/src/native/clr/host/host.cc b/src/native/clr/host/host.cc index 388ba333f5a..ea8ccbc5215 100644 --- a/src/native/clr/host/host.cc +++ b/src/native/clr/host/host.cc @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -451,9 +452,8 @@ void Host::Java_mono_android_Runtime_initInternal ( log_info (LOG_GC, "GREF GC Threshold: {}"sv, init.grefGcThreshold); - // TODO: GC bridge to initialize here - OSBridge::initialize_on_runtime_init (env, runtimeClass); + GCBridge::initialize_on_runtime_init (env, runtimeClass); if (FastTiming::enabled ()) [[unlikely]] { internal_timing.start_event (TimingEventKind::NativeToManagedTransition); @@ -572,6 +572,7 @@ auto Host::Java_JNI_OnLoad (JavaVM *vm, [[maybe_unused]] void *reserved) noexcep JNIEnv *env = nullptr; vm->GetEnv ((void**)&env, JNI_VERSION_1_6); OSBridge::initialize_on_onload (vm, env); + GCBridge::initialize_on_onload (env); AndroidSystem::init_max_gref_count (); return JNI_VERSION_1_6; diff --git a/src/native/clr/host/internal-pinvokes.cc b/src/native/clr/host/internal-pinvokes.cc index a490a8b069e..5260a6bac6a 100644 --- a/src/native/clr/host/internal-pinvokes.cc +++ b/src/native/clr/host/internal-pinvokes.cc @@ -1,3 +1,4 @@ +#include #include #include #include @@ -25,22 +26,7 @@ int _monodroid_gref_log_new (jobject curHandle, char curType, jobject newHandle, void _monodroid_gref_log_delete (jobject handle, char type, const char *threadName, int threadId, const char *from, int from_writable) noexcept { - // NOTE: disabled until GC bridge is in - // It currently causes a crash in Mono.Android_Tests: - // - // FATAL EXCEPTION: Instr: xamarin.android.runtimetests.NUnitInstrumentation - // Process: Mono.Android.NET_Tests, PID: 16194 - // android.runtime.JavaProxyThrowable: [System.ObjectDisposedException]: Cannot access disposed object with JniIdentityHashCode=166175665. - // Object name: 'Xamarin.Android.RuntimeTests.NUnitInstrumentation'. - // at Java.Interop.JniPeerMembers.AssertSelf + 0x2f(Unknown Source) - // at Java.Interop.JniPeerMembers+JniInstanceMethods.InvokeVirtualVoidMethod + 0x1(Unknown Source) - // at Android.App.Instrumentation.Finish + 0x46(Unknown Source) - // at Xamarin.Android.UnitTests.TestInstrumentation`1.OnStart + 0x6d(Unknown Source) - // at Android.App.Instrumentation.n_OnStart + 0x1f(Unknown Source) - // at crc643df67da7b13bb6b1.TestInstrumentation_1.n_onStart(Native Method) - // at crc643df67da7b13bb6b1.TestInstrumentation_1.onStart(TestInstrumentation_1.java:32) - // at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2606) - // OSBridge::_monodroid_gref_log_delete (handle, type, threadName, threadId, from, from_writable); + OSBridge::_monodroid_gref_log_delete (handle, type, threadName, threadId, from, from_writable); } const char* clr_typemap_managed_to_java (const char *typeName, const uint8_t *mvid) noexcept @@ -53,6 +39,13 @@ bool clr_typemap_java_to_managed (const char *java_type_name, char const** assem return TypeMapper::java_to_managed (java_type_name, assembly_name, managed_type_token_id); } +BridgeProcessingFtn clr_initialize_gc_bridge ( + BridgeProcessingStartedFtn bridge_processing_started_callback, + BridgeProcessingFinishedFtn bridge_processing_finished_callback) noexcept +{ + return GCBridge::initialize_callback (bridge_processing_started_callback, bridge_processing_finished_callback); +} + void monodroid_log (LogLevel level, LogCategories category, const char *message) noexcept { switch (level) { @@ -175,7 +168,8 @@ void _monodroid_lref_log_delete (int lrefc, jobject handle, char type, const cha void _monodroid_gc_wait_for_bridge_processing () { - // mono_gc_wait_for_bridge_processing (); - replace with the new GC bridge call, when we have it + // TODO do we need this method? + Helpers::abort_application (LOG_DEFAULT, "The method _monodroid_gc_wait_for_bridge_processing is not implemented. This is a stub and should not be called."sv); } void _monodroid_detect_cpu_and_architecture (uint16_t *built_for_cpu, uint16_t *running_on_cpu, unsigned char *is64bit) diff --git a/src/native/clr/host/os-bridge.cc b/src/native/clr/host/os-bridge.cc index 249f33432c1..904ad36c523 100644 --- a/src/native/clr/host/os-bridge.cc +++ b/src/native/clr/host/os-bridge.cc @@ -49,6 +49,21 @@ auto OSBridge::lref_to_gref (JNIEnv *env, jobject lref) noexcept -> jobject return g; } +auto OSBridge::get_object_ref_type (JNIEnv *env, void *handle) noexcept -> char +{ + jobjectRefType value; + if (handle == nullptr) + return 'I'; + value = env->GetObjectRefType (reinterpret_cast (handle)); + switch (value) { + case JNIInvalidRefType: return 'I'; + case JNILocalRefType: return 'L'; + case JNIGlobalRefType: return 'G'; + case JNIWeakGlobalRefType: return 'W'; + default: return '*'; + } +} + auto OSBridge::_monodroid_gref_inc () noexcept -> int { return __sync_add_and_fetch (&gc_gref_count, 1); diff --git a/src/native/clr/host/pinvoke-tables.include b/src/native/clr/host/pinvoke-tables.include index 000e4ce8afb..65afe1a26d5 100644 --- a/src/native/clr/host/pinvoke-tables.include +++ b/src/native/clr/host/pinvoke-tables.include @@ -11,7 +11,7 @@ namespace { #if INTPTR_MAX == INT64_MAX //64-bit internal p/invoke table - std::array internal_pinvokes {{ + std::array internal_pinvokes {{ {0xa50ce5de13bf8b5, "_monodroid_timezone_get_default_id", reinterpret_cast(&_monodroid_timezone_get_default_id)}, {0x3ade4348ac8ce0fa, "_monodroid_freeifaddrs", reinterpret_cast(&_monodroid_freeifaddrs)}, {0x3b2467e7eadd4a6a, "_monodroid_lref_log_new", reinterpret_cast(&_monodroid_lref_log_new)}, @@ -21,6 +21,7 @@ namespace { {0x5f0b4e426eff086b, "_monodroid_detect_cpu_and_architecture", reinterpret_cast(&_monodroid_detect_cpu_and_architecture)}, {0x9099a4b95e3c3a89, "_monodroid_lref_log_delete", reinterpret_cast(&_monodroid_lref_log_delete)}, {0x9187e6bc6294cacf, "clr_typemap_managed_to_java", reinterpret_cast(&clr_typemap_managed_to_java)}, + {0x920bf58357fb56f3, "clr_initialize_gc_bridge", reinterpret_cast(&clr_initialize_gc_bridge)}, {0x9a946dfe9916a942, "clr_typemap_java_to_managed", reinterpret_cast(&clr_typemap_java_to_managed)}, {0xa6ec846592d99536, "_monodroid_weak_gref_delete", reinterpret_cast(&_monodroid_weak_gref_delete)}, {0xa7f58f3ee428cc6b, "_monodroid_gref_log_delete", reinterpret_cast(&_monodroid_gref_log_delete)}, @@ -545,7 +546,7 @@ constexpr hash_t system_security_cryptography_native_android_library_hash = 0x18 constexpr hash_t system_globalization_native_library_hash = 0x28b5c8fca080abd5; #else //32-bit internal p/invoke table - std::array internal_pinvokes {{ + std::array internal_pinvokes {{ {0xb7a486a, "monodroid_TypeManager_get_java_class_name", reinterpret_cast(&monodroid_TypeManager_get_java_class_name)}, {0x2aea7c33, "_monodroid_max_gref_get", reinterpret_cast(&_monodroid_max_gref_get)}, {0x3227d81a, "monodroid_timing_start", reinterpret_cast(&monodroid_timing_start)}, @@ -558,6 +559,7 @@ constexpr hash_t system_globalization_native_library_hash = 0x28b5c8fca080abd5; {0x9a734f16, "_monodroid_weak_gref_get", reinterpret_cast(&_monodroid_weak_gref_get)}, {0x9c5b24a8, "_monodroid_weak_gref_new", reinterpret_cast(&_monodroid_weak_gref_new)}, {0xa04e5d1c, "monodroid_free", reinterpret_cast(&monodroid_free)}, + {0xa3c1e548, "clr_initialize_gc_bridge", reinterpret_cast(&clr_initialize_gc_bridge)}, {0xad511c82, "_monodroid_timezone_get_default_id", reinterpret_cast(&_monodroid_timezone_get_default_id)}, {0xb02468aa, "_monodroid_gref_get", reinterpret_cast(&_monodroid_gref_get)}, {0xb6431f9a, "clr_typemap_java_to_managed", reinterpret_cast(&clr_typemap_java_to_managed)}, @@ -1079,6 +1081,6 @@ constexpr hash_t system_security_cryptography_native_android_library_hash = 0x93 constexpr hash_t system_globalization_native_library_hash = 0xa66f1e5a; #endif -constexpr size_t internal_pinvokes_count = 26; +constexpr size_t internal_pinvokes_count = 27; constexpr size_t dotnet_pinvokes_count = 491; } // end of anonymous namespace diff --git a/src/native/clr/include/host/bridge-processing.hh b/src/native/clr/include/host/bridge-processing.hh new file mode 100644 index 00000000000..0fdb56d931e --- /dev/null +++ b/src/native/clr/include/host/bridge-processing.hh @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +#include +#include +#include + +struct CrossReferenceTarget +{ + bool is_temporary_peer; + union + { + jobject temporary_peer; + HandleContext* context; + }; + + jobject get_handle () const noexcept; + void mark_refs_added_if_needed () noexcept; +}; + +class BridgeProcessing +{ +public: + BridgeProcessing (MarkCrossReferencesArgs *args) noexcept; + static void initialize_on_runtime_init (JNIEnv *jniEnv, jclass runtimeClass) noexcept; + void process () noexcept; +private: + JNIEnv* env; + MarkCrossReferencesArgs *cross_refs; + std::unordered_map temporary_peers; + + static inline jclass GCUserPeer_class = nullptr; + static inline jmethodID GCUserPeer_ctor = nullptr; + + void prepare_for_java_collection () noexcept; + void prepare_scc_for_java_collection (size_t scc_index, const StronglyConnectedComponent &scc) noexcept; + void take_weak_global_ref (const HandleContext &context) noexcept; + + void add_circular_references (const StronglyConnectedComponent &scc) noexcept; + void add_cross_reference (size_t source_index, size_t dest_index) noexcept; + CrossReferenceTarget select_cross_reference_target (size_t scc_index) noexcept; + bool add_reference (jobject from, jobject to) noexcept; + + void cleanup_after_java_collection () noexcept; + void cleanup_scc_for_java_collection (const StronglyConnectedComponent &scc) noexcept; + void abort_unless_all_collected_or_all_alive (const StronglyConnectedComponent &scc) noexcept; + void take_global_ref (HandleContext &context) noexcept; + + void clear_references_if_needed (const HandleContext &context) noexcept; + void clear_references (jobject handle) noexcept; + + void log_missing_add_references_method (jclass java_class) noexcept; + void log_missing_clear_references_method (jclass java_class) noexcept; + void log_weak_to_gref (jobject weak, jobject handle) noexcept; + void log_weak_ref_collected (jobject weak) noexcept; + void log_take_weak_global_ref (jobject handle) noexcept; + void log_weak_gref_new (jobject handle, jobject weak) noexcept; + void log_gref_delete (jobject handle) noexcept; + void log_weak_ref_delete (jobject weak) noexcept; + void log_gc_summary () noexcept; +}; diff --git a/src/native/clr/include/host/gc-bridge.hh b/src/native/clr/include/host/gc-bridge.hh new file mode 100644 index 00000000000..80c0ac68132 --- /dev/null +++ b/src/native/clr/include/host/gc-bridge.hh @@ -0,0 +1,101 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +struct JniObjectReferenceControlBlock +{ + jobject handle; + int handle_type; + jobject weak_handle; + int refs_added; +}; + +struct HandleContext +{ + int32_t identity_hash_code; + JniObjectReferenceControlBlock *control_block; + + bool is_collected () const noexcept + { + abort_unless (control_block != nullptr, "Control block must not be null"); + return control_block->handle == nullptr; + } +}; + +struct StronglyConnectedComponent +{ + size_t Count; + HandleContext **Contexts; +}; + +struct ComponentCrossReference +{ + size_t SourceGroupIndex; + size_t DestinationGroupIndex; +}; + +struct MarkCrossReferencesArgs +{ + size_t ComponentCount; + StronglyConnectedComponent *Components; + size_t CrossReferenceCount; + ComponentCrossReference *CrossReferences; +}; + +using BridgeProcessingStartedFtn = void (*)(MarkCrossReferencesArgs*); +using BridgeProcessingFinishedFtn = void (*)(MarkCrossReferencesArgs*); +using BridgeProcessingFtn = void (*)(MarkCrossReferencesArgs*); + +namespace xamarin::android { + class GCBridge + { + public: + static void initialize_on_onload (JNIEnv *env) noexcept; + static void initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept; + + static BridgeProcessingFtn initialize_callback ( + BridgeProcessingStartedFtn bridge_processing_started, + BridgeProcessingFinishedFtn bridge_processing_finished) noexcept + { + abort_if_invalid_pointer_argument (bridge_processing_started, "bridge_processing_started"); + abort_if_invalid_pointer_argument (bridge_processing_finished, "bridge_processing_finished"); + abort_unless (GCBridge::bridge_processing_started_callback == nullptr, "GC bridge processing started callback is already set"); + abort_unless (GCBridge::bridge_processing_finished_callback == nullptr, "GC bridge processing finished callback is already set"); + + GCBridge::bridge_processing_started_callback = bridge_processing_started; + GCBridge::bridge_processing_finished_callback = bridge_processing_finished; + + bridge_processing_thread = std::thread { GCBridge::bridge_processing }; + bridge_processing_thread.detach (); + + return mark_cross_references; + } + + static void trigger_java_gc (JNIEnv *env) noexcept; + + private: + static inline std::thread bridge_processing_thread {}; + + static inline std::binary_semaphore shared_args_semaphore{0}; + static inline std::atomic shared_args; + + static inline jobject Runtime_instance = nullptr; + static inline jmethodID Runtime_gc = nullptr; + + static inline BridgeProcessingStartedFtn bridge_processing_started_callback = nullptr; + static inline BridgeProcessingFinishedFtn bridge_processing_finished_callback = nullptr; + + static void bridge_processing () noexcept; + static void mark_cross_references (MarkCrossReferencesArgs *args) noexcept; + + static void log_mark_cross_references_args_if_enabled (MarkCrossReferencesArgs *args) noexcept; + static void log_handle_context (JNIEnv *env, HandleContext *ctx) noexcept; + }; +} diff --git a/src/native/clr/include/host/os-bridge.hh b/src/native/clr/include/host/os-bridge.hh index be3291f4866..b9da49b07f9 100644 --- a/src/native/clr/include/host/os-bridge.hh +++ b/src/native/clr/include/host/os-bridge.hh @@ -13,6 +13,7 @@ namespace xamarin::android { static void initialize_on_onload (JavaVM *vm, JNIEnv *env) noexcept; static void initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept; static auto lref_to_gref (JNIEnv *env, jobject lref) noexcept -> jobject; + static auto get_object_ref_type (JNIEnv *env, void *handle) noexcept -> char; static auto get_gc_gref_count () noexcept -> int { @@ -38,8 +39,11 @@ namespace xamarin::android { JNIEnv *env = nullptr; jvm->GetEnv ((void**)&env, JNI_VERSION_1_6); if (env == nullptr) { - // TODO: attach to the runtime thread here - jvm->GetEnv ((void**)&env, JNI_VERSION_1_6); + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_6; + args.name = nullptr; + args.group = nullptr; + jvm->AttachCurrentThread (&env, &args); abort_unless (env != nullptr, "Unable to get a valid pointer to JNIEnv"); } diff --git a/src/native/clr/include/runtime-base/internal-pinvokes.hh b/src/native/clr/include/runtime-base/internal-pinvokes.hh index a3c69c6c6e7..0bc3f2d841f 100644 --- a/src/native/clr/include/runtime-base/internal-pinvokes.hh +++ b/src/native/clr/include/runtime-base/internal-pinvokes.hh @@ -3,6 +3,7 @@ #include #include +#include #include #include "logger.hh" #include @@ -14,6 +15,9 @@ extern "C" { void _monodroid_gref_log_delete (jobject handle, char type, const char *threadName, int threadId, const char *from, int from_writable) noexcept; const char* clr_typemap_managed_to_java (const char *typeName, const uint8_t *mvid) noexcept; bool clr_typemap_java_to_managed (const char *java_type_name, char const** assembly_name, uint32_t *managed_type_token_id) noexcept; + BridgeProcessingFtn clr_initialize_gc_bridge ( + BridgeProcessingStartedFtn bridge_processing_started_callback, + BridgeProcessingFinishedFtn mark_cross_references_callback) noexcept; void monodroid_log (xamarin::android::LogLevel level, LogCategories category, const char *message) noexcept; char* monodroid_TypeManager_get_java_class_name (jclass klass) noexcept; void monodroid_free (void *ptr) noexcept;