From 4b84e8beb1505fb31b2321727d0f7ee9969f7aca Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 16 Jul 2025 16:08:49 -0700 Subject: [PATCH 1/4] Implement GC bridge in NativeAOT --- .../System.Private.CoreLib.csproj | 1 + .../Java/JavaMarshal.CoreCLR.cs} | 26 --- src/coreclr/nativeaot/CMakeLists.txt | 7 + src/coreclr/nativeaot/Runtime/CMakeLists.txt | 2 + .../nativeaot/Runtime/HandleTableHelpers.cpp | 22 +++ src/coreclr/nativeaot/Runtime/gcenv.ee.cpp | 7 + .../nativeaot/Runtime/interoplibinterface.h | 12 ++ .../Runtime/interoplibinterface_java.cpp | 157 ++++++++++++++++++ .../src/System.Private.CoreLib.csproj | 1 + .../InteropServices/GCHandle.NativeAot.cs | 9 +- .../Java/JavaMarshal.NativeAot.cs | 66 ++++++++ .../src/System/Runtime/RuntimeImports.cs | 20 +++ src/coreclr/vm/interoplibinterface_java.cpp | 8 +- .../System.Private.CoreLib.Shared.projitems | 2 +- .../Java/JavaMarshal.Unsupported.cs | 35 ++++ src/tests/Interop/GCBridge/BridgeTest.csproj | 2 - 16 files changed, 343 insertions(+), 34 deletions(-) rename src/{libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.cs => coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.CoreCLR.cs} (81%) create mode 100644 src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp create mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.NativeAot.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.Unsupported.cs diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 2fb4c3e7ffa6bb..c12d6c854ac0c0 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -223,6 +223,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.CoreCLR.cs similarity index 81% rename from src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.cs rename to src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.CoreCLR.cs index 42f3b023a4803c..357e3575812e5c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.CoreCLR.cs @@ -12,39 +12,24 @@ public static partial class JavaMarshal { public static unsafe void Initialize(delegate* unmanaged markCrossReferences) { -#if NATIVEAOT - throw new NotImplementedException(); -#elif FEATURE_JAVAMARSHAL ArgumentNullException.ThrowIfNull(markCrossReferences); if (!InitializeInternal((IntPtr)markCrossReferences)) { throw new InvalidOperationException(SR.InvalidOperation_ReinitializeJavaMarshal); } -#else - throw new PlatformNotSupportedException(); -#endif } public static unsafe GCHandle CreateReferenceTrackingHandle(object obj, void* context) { -#if NATIVEAOT - throw new NotImplementedException(); -#elif FEATURE_JAVAMARSHAL ArgumentNullException.ThrowIfNull(obj); IntPtr handle = CreateReferenceTrackingHandleInternal(ObjectHandleOnStack.Create(ref obj), context); return GCHandle.FromIntPtr(handle); -#else - throw new PlatformNotSupportedException(); -#endif } public static unsafe void* GetContext(GCHandle obj) { -#if NATIVEAOT - throw new NotImplementedException(); -#elif FEATURE_JAVAMARSHAL IntPtr handle = GCHandle.ToIntPtr(obj); if (handle == IntPtr.Zero || !GetContextInternal(handle, out void* context)) @@ -53,18 +38,12 @@ public static unsafe GCHandle CreateReferenceTrackingHandle(object obj, void* co } return context; -#else - throw new PlatformNotSupportedException(); -#endif } public static unsafe void FinishCrossReferenceProcessing( MarkCrossReferencesArgs* crossReferences, ReadOnlySpan unreachableObjectHandles) { -#if NATIVEAOT - throw new NotImplementedException(); -#elif FEATURE_JAVAMARSHAL fixed (GCHandle* pHandles = unreachableObjectHandles) { FinishCrossReferenceProcessing( @@ -72,12 +51,8 @@ public static unsafe void FinishCrossReferenceProcessing( (nuint)unreachableObjectHandles.Length, pHandles); } -#else - throw new PlatformNotSupportedException(); -#endif } -#if FEATURE_JAVAMARSHAL && !NATIVEAOT [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "JavaMarshal_Initialize")] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool InitializeInternal(IntPtr callback); @@ -92,6 +67,5 @@ public static unsafe void FinishCrossReferenceProcessing( [SuppressGCTransition] [return: MarshalAs(UnmanagedType.Bool)] private static unsafe partial bool GetContextInternal(IntPtr handle, out void* context); -#endif } } diff --git a/src/coreclr/nativeaot/CMakeLists.txt b/src/coreclr/nativeaot/CMakeLists.txt index e1c43480500970..3aaa04c774c20f 100644 --- a/src/coreclr/nativeaot/CMakeLists.txt +++ b/src/coreclr/nativeaot/CMakeLists.txt @@ -30,7 +30,14 @@ endif (CLR_CMAKE_HOST_UNIX) if(CLR_CMAKE_TARGET_ANDROID) add_definitions(-DFEATURE_EMULATED_TLS) + set(FEATURE_JAVAMARSHAL 1) endif() +if(NOT DEFINED FEATURE_JAVAMARSHAL) + set(FEATURE_JAVAMARSHAL $,1,0>) +endif() + +add_compile_definitions($<${FEATURE_JAVAMARSHAL}:FEATURE_JAVAMARSHAL>) + add_subdirectory(Bootstrap) add_subdirectory(Runtime) diff --git a/src/coreclr/nativeaot/Runtime/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/CMakeLists.txt index c6a19f9b9bf023..61ed486a7989c8 100644 --- a/src/coreclr/nativeaot/Runtime/CMakeLists.txt +++ b/src/coreclr/nativeaot/Runtime/CMakeLists.txt @@ -20,6 +20,7 @@ set(COMMON_RUNTIME_SOURCES gcenv.ee.cpp GcStressControl.cpp HandleTableHelpers.cpp + interoplibinterface_java.cpp MathHelpers.cpp MiscHelpers.cpp TypeManager.cpp @@ -38,6 +39,7 @@ set(COMMON_RUNTIME_SOURCES yieldprocessornormalized.cpp ${GC_DIR}/gceventstatus.cpp + ${GC_DIR}/gcbridge.cpp ${GC_DIR}/gcload.cpp ${GC_DIR}/gcconfig.cpp ${GC_DIR}/gchandletable.cpp diff --git a/src/coreclr/nativeaot/Runtime/HandleTableHelpers.cpp b/src/coreclr/nativeaot/Runtime/HandleTableHelpers.cpp index 91750c078e4539..7cf154d29c0cb3 100644 --- a/src/coreclr/nativeaot/Runtime/HandleTableHelpers.cpp +++ b/src/coreclr/nativeaot/Runtime/HandleTableHelpers.cpp @@ -6,6 +6,7 @@ #include "objecthandle.h" #include "RestrictedCallouts.h" #include "gchandleutilities.h" +#include "interoplibinterface.h" FCIMPL2(OBJECTHANDLE, RhpHandleAlloc, Object *pObject, int type) @@ -64,6 +65,27 @@ FCIMPL2(void, RhUnregisterRefCountedHandleCallback, void * pCallout, MethodTable } FCIMPLEND +FCIMPL2(OBJECTHANDLE, RhpHandleAllocCrossReference, Object *pPrimary, void *pContext) +{ + return GCHandleUtilities::GetGCHandleManager()->GetGlobalHandleStore()->CreateHandleWithExtraInfo(pPrimary, HNDTYPE_CROSSREFERENCE, pContext); +} +FCIMPLEND + +FCIMPL2(FC_BOOL_RET, RhHandleTryGetCrossReferenceContext, OBJECTHANDLE handle, void **pContext) +{ + *pContext = nullptr; + + IGCHandleManager* gcHandleManager = GCHandleUtilities::GetGCHandleManager(); + if (gcHandleManager->HandleFetchType(handle) != HNDTYPE_CROSSREFERENCE) + { + FC_RETURN_BOOL(false); + } + + *pContext = gcHandleManager->GetExtraInfoFromHandle(handle); + FC_RETURN_BOOL(true); +} +FCIMPLEND + // This structure mirrors the managed type System.Runtime.InteropServices.ComWrappers.ManagedObjectWrapper. struct ManagedObjectWrapper { diff --git a/src/coreclr/nativeaot/Runtime/gcenv.ee.cpp b/src/coreclr/nativeaot/Runtime/gcenv.ee.cpp index 9285e676354434..e2c31db91e328b 100644 --- a/src/coreclr/nativeaot/Runtime/gcenv.ee.cpp +++ b/src/coreclr/nativeaot/Runtime/gcenv.ee.cpp @@ -809,4 +809,11 @@ void GCToEEInterface::FreeStringConfigValue(const char* value) delete[] value; } +void GCToEEInterface::TriggerClientBridgeProcessing(MarkCrossReferencesArgs* args) +{ +#ifdef FEATURE_JAVAMARSHAL + JavaMarshalNative::TriggerClientBridgeProcessing(args); +#endif +} + #endif // !DACCESS_COMPILE diff --git a/src/coreclr/nativeaot/Runtime/interoplibinterface.h b/src/coreclr/nativeaot/Runtime/interoplibinterface.h index fce04b81f90249..62aaa616bd1808 100644 --- a/src/coreclr/nativeaot/Runtime/interoplibinterface.h +++ b/src/coreclr/nativeaot/Runtime/interoplibinterface.h @@ -22,3 +22,15 @@ class ObjCMarshalNative }; #endif // FEATURE_OBJCMARSHAL + +#ifdef FEATURE_JAVAMARSHAL + +struct MarkCrossReferencesArgs; + +class JavaMarshalNative +{ +public: + static void TriggerClientBridgeProcessing( + MarkCrossReferencesArgs* args); +}; +#endif diff --git a/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp b/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp new file mode 100644 index 00000000000000..7a496b62611dfe --- /dev/null +++ b/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifdef FEATURE_JAVAMARSHAL + +// Runtime headers +#include "common.h" +#include "gcenv.h" +#include "gcenv.ee.h" +#include "gcheaputilities.h" +#include "gchandleutilities.h" +#include "thread.h" +#include "threadstore.h" +#include "threadstore.inl" +#include "event.h" + +#include "interoplibinterface.h" + +using CrossreferenceHandleCallback = void(__stdcall *)(MarkCrossReferencesArgs*); + +namespace +{ + volatile CrossreferenceHandleCallback g_MarkCrossReferences = NULL; + + Volatile g_GCBridgeActive = false; + CLREventStatic g_bridgeFinished; + + void ReleaseGCBridgeArgumentsWorker( + MarkCrossReferencesArgs* args) + { + // Memory was allocated for the collections by the GC. + // See callers of GCToEEInterface::TriggerGCBridge(). + + // Free memory in each of the SCCs + for (size_t i = 0; i < args->ComponentCount; i++) + { + delete[] args->Components[i].Contexts; + } + delete[] args->Components; + delete[] args->CrossReferences; + delete args; + } +} + +void JavaMarshalNative::TriggerClientBridgeProcessing( + _In_ MarkCrossReferencesArgs* args) +{ + _ASSERTE(GCHeapUtilities::IsGCInProgress()); + + if (g_GCBridgeActive) + { + // Release the memory allocated since the GCBridge + // is already running and we're not passing them to it. + ReleaseGCBridgeArgumentsWorker(args); + return; + } + + bool gcBridgeTriggered = false; + // Not initialized + if (g_MarkCrossReferences != NULL) + { + g_MarkCrossReferences(args); + gcBridgeTriggered = true; + } + + if (!gcBridgeTriggered) + { + // Release the memory allocated since the GCBridge + // wasn't trigger for some reason. + ReleaseGCBridgeArgumentsWorker(args); + return; + } + + // This runs during GC while the world is stopped, no synchronisation required + g_bridgeFinished.Reset(); + + // Mark the GCBridge as active. + g_GCBridgeActive = true; +} + +extern "C" BOOL QCALLTYPE JavaMarshal_Initialize( + void* markCrossReferences) +{ + _ASSERTE(markCrossReferences != NULL); + + BOOL success = FALSE; + + + // Switch to Cooperative mode since we are setting callbacks that + // will be used during a GC and we want to ensure a GC isn't occurring + // while they are being set. + { + bool wasCooperative = false; + Thread* pThisThread = ThreadStore::GetCurrentThreadIfAvailable(); + pThisThread->DeferTransitionFrame(); + pThisThread->DisablePreemptiveMode(); + + if (PalInterlockedCompareExchangePointer((void* volatile*)&g_MarkCrossReferences, (void*)markCrossReferences, NULL) == NULL) + { + success = g_bridgeFinished.CreateManualEventNoThrow(false); + } + + pThisThread->EnablePreemptiveMode(); + } + + return success; +} + +extern "C" void QCALLTYPE JavaMarshal_FinishCrossReferenceProcessing( + MarkCrossReferencesArgs *crossReferences, + size_t length, + void* unreachableObjectHandles) +{ + _ASSERTE(crossReferences->ComponentCount >= 0); + + _ASSERTE(g_GCBridgeActive); + + // Mark the GCBridge as inactive. + // This must be synchronized with the GC so switch to cooperative mode. + { + bool wasCooperative = false; + Thread* pThisThread = ThreadStore::GetCurrentThreadIfAvailable(); + pThisThread->DeferTransitionFrame(); + pThisThread->DisablePreemptiveMode(); + + GCHeapUtilities::GetGCHeap()->NullBridgeObjectsWeakRefs(length, unreachableObjectHandles); + + IGCHandleManager* pHandleManager = GCHandleUtilities::GetGCHandleManager(); + for (size_t i = 0; i < length; i++) + pHandleManager->DestroyHandleOfUnknownType(((OBJECTHANDLE*)unreachableObjectHandles)[i]); + + g_GCBridgeActive = false; + g_bridgeFinished.Set(); + + pThisThread->EnablePreemptiveMode(); + } + + ReleaseGCBridgeArgumentsWorker(crossReferences); +} + +FCIMPL0(FC_BOOL_RET, RhIsGCBridgeActive) +{ + FC_RETURN_BOOL(g_GCBridgeActive); +} +FCIMPLEND + +extern "C" void QCALLTYPE JavaMarshal_WaitForGCBridgeFinish() +{ + // We will transition to pre-emptive mode to wait for the bridge to finish. + Thread* pThisThread = ThreadStore::GetCurrentThreadIfAvailable(); + while (g_GCBridgeActive) + { + g_bridgeFinished.Wait(INFINITE, false, false); + } +} + +#endif // FEATURE_JAVAMARSHAL diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index f8f6e6374592c0..2c96332adf7d00 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -159,6 +159,7 @@ + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.NativeAot.cs index 7b66abd31635f0..195ab67ba4adb6 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.NativeAot.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.InteropServices.Java; + namespace System.Runtime.InteropServices { public partial struct GCHandle @@ -14,9 +16,14 @@ public partial struct GCHandle internal static void InternalSet(IntPtr handle, object? value) => RuntimeImports.RhHandleSet(handle, value); #if FEATURE_JAVAMARSHAL - // FIXME implement waiting for bridge processing internal static object? InternalGetBridgeWait(IntPtr handle) { + if (RuntimeImports.RhIsGCBridgeActive()) + { +#pragma warning disable CA1416 // Validate platform compatibility + JavaMarshal.WaitForGCBridgeFinish(); +#pragma warning restore CA1416 // Validate platform compatibility + } return InternalGet(handle); } #endif diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.NativeAot.cs new file mode 100644 index 00000000000000..950dfdf39e0a09 --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.NativeAot.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; + +namespace System.Runtime.InteropServices.Java +{ + [CLSCompliant(false)] + [SupportedOSPlatform("android")] + public static partial class JavaMarshal + { + public static unsafe void Initialize(delegate* unmanaged markCrossReferences) + { + ArgumentNullException.ThrowIfNull(markCrossReferences); + + if (!InitializeInternal((IntPtr)markCrossReferences)) + { + throw new InvalidOperationException(SR.InvalidOperation_ReinitializeJavaMarshal); + } + } + + public static unsafe GCHandle CreateReferenceTrackingHandle(object obj, void* context) + { + ArgumentNullException.ThrowIfNull(obj); + return GCHandle.FromIntPtr(RuntimeImports.RhHandleAllocCrossReference(obj, (IntPtr)context)); + } + + public static unsafe void* GetContext(GCHandle obj) + { + IntPtr handle = GCHandle.ToIntPtr(obj); + if (handle == IntPtr.Zero + || !RuntimeImports.RhHandleTryGetCrossReferenceContext(handle, out nint context)) + { + throw new InvalidOperationException(SR.InvalidOperation_IncorrectGCHandleType); + } + return (void*)context; + } + + public static unsafe void FinishCrossReferenceProcessing( + MarkCrossReferencesArgs* crossReferences, + ReadOnlySpan unreachableObjectHandles) + { + fixed (GCHandle* handlesPtr = unreachableObjectHandles) + { + FinishCrossReferenceProcessingBridge( + crossReferences, + (nuint)unreachableObjectHandles.Length, + handlesPtr); + } + } + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "JavaMarshal_Initialize")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static unsafe partial bool InitializeInternal(IntPtr markCrossReferences); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "JavaMarshal_FinishCrossReferenceProcessing")] + internal static unsafe partial void FinishCrossReferenceProcessingBridge( + MarkCrossReferencesArgs* crossReferences, + nuint numHandles, + GCHandle* unreachableObjectHandles); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "JavaMarshal_WaitForGCBridgeFinish")] + internal static partial void WaitForGCBridgeFinish(); + } +} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index e1886df7006f31..02a93cc1e4156e 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -294,6 +294,26 @@ internal static IntPtr RhHandleAllocDependent(object primary, object secondary) return h; } + [MethodImpl(MethodImplOptions.InternalCall)] + [RuntimeImport(RuntimeLibrary, "RhpHandleAllocCrossReference")] + internal static extern IntPtr RhpHandleAllocCrossReference(object value, IntPtr context); + + internal static IntPtr RhHandleAllocCrossReference(object value, IntPtr context) + { + IntPtr h = RhpHandleAllocCrossReference(value, context); + if (h == IntPtr.Zero) + throw new OutOfMemoryException(); + return h; + } + + [MethodImpl(MethodImplOptions.InternalCall)] + [RuntimeImport(RuntimeLibrary, "RhHandleTryGetCrossReferenceContext")] + internal static extern bool RhHandleTryGetCrossReferenceContext(IntPtr handle, out IntPtr context); + + [MethodImpl(MethodImplOptions.InternalCall)] + [RuntimeImport(RuntimeLibrary, "RhIsGCBridgeActive")] + internal static extern bool RhIsGCBridgeActive(); + // Free handle. [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhHandleFree")] diff --git a/src/coreclr/vm/interoplibinterface_java.cpp b/src/coreclr/vm/interoplibinterface_java.cpp index 0a30dce519f471..e4f1f07f11997b 100644 --- a/src/coreclr/vm/interoplibinterface_java.cpp +++ b/src/coreclr/vm/interoplibinterface_java.cpp @@ -36,11 +36,11 @@ namespace // Free memory in each of the SCCs for (size_t i = 0; i < args->ComponentCount; i++) { - free(args->Components[i].Contexts); + delete[] args->Components[i].Contexts; } - free(args->Components); - free(args->CrossReferences); - free(args); + delete[] args->Components; + delete[] args->CrossReferences; + delete args; } } diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 150b1d6a8e11cd..6a0b7d05b7ab6f 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -996,7 +996,7 @@ - + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.Unsupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.Unsupported.cs new file mode 100644 index 00000000000000..3914d81c2e6ab7 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.Unsupported.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; + +namespace System.Runtime.InteropServices.Java +{ + [CLSCompliant(false)] + [SupportedOSPlatform("android")] + public static partial class JavaMarshal + { + public static unsafe void Initialize(delegate* unmanaged markCrossReferences) + { + throw new PlatformNotSupportedException(); + } + + public static unsafe GCHandle CreateReferenceTrackingHandle(object obj, void* context) + { + throw new PlatformNotSupportedException(); + } + + public static unsafe void* GetContext(GCHandle obj) + { + throw new PlatformNotSupportedException(); + } + + public static unsafe void FinishCrossReferenceProcessing( + MarkCrossReferencesArgs* crossReferences, + ReadOnlySpan unreachableObjectHandles) + { + throw new PlatformNotSupportedException(); + } + } +} diff --git a/src/tests/Interop/GCBridge/BridgeTest.csproj b/src/tests/Interop/GCBridge/BridgeTest.csproj index 1425b1f51b2c64..965f8371061bb7 100644 --- a/src/tests/Interop/GCBridge/BridgeTest.csproj +++ b/src/tests/Interop/GCBridge/BridgeTest.csproj @@ -4,8 +4,6 @@ true true true - - true true From af2605dcfa1cbdf0eae625f6e1bc1518516fa03e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 17 Jul 2025 11:31:39 -0700 Subject: [PATCH 2/4] Move fetching value from gchandle to by synchronized with GC bridge --- .../nativeaot/Runtime/interoplibinterface.h | 2 +- .../Runtime/interoplibinterface_java.cpp | 49 +++++++++++++------ .../InteropServices/GCHandle.NativeAot.cs | 26 ++++++---- .../Java/JavaMarshal.NativeAot.cs | 3 -- 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/interoplibinterface.h b/src/coreclr/nativeaot/Runtime/interoplibinterface.h index 62aaa616bd1808..c89281d152d8cc 100644 --- a/src/coreclr/nativeaot/Runtime/interoplibinterface.h +++ b/src/coreclr/nativeaot/Runtime/interoplibinterface.h @@ -33,4 +33,4 @@ class JavaMarshalNative static void TriggerClientBridgeProcessing( MarkCrossReferencesArgs* args); }; -#endif +#endif // FEATURE_JAVAMARSHAL diff --git a/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp b/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp index 7a496b62611dfe..1e9d1645e7344c 100644 --- a/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp +++ b/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp @@ -89,20 +89,17 @@ extern "C" BOOL QCALLTYPE JavaMarshal_Initialize( // Switch to Cooperative mode since we are setting callbacks that // will be used during a GC and we want to ensure a GC isn't occurring // while they are being set. - { - bool wasCooperative = false; - Thread* pThisThread = ThreadStore::GetCurrentThreadIfAvailable(); - pThisThread->DeferTransitionFrame(); - pThisThread->DisablePreemptiveMode(); - - if (PalInterlockedCompareExchangePointer((void* volatile*)&g_MarkCrossReferences, (void*)markCrossReferences, NULL) == NULL) - { - success = g_bridgeFinished.CreateManualEventNoThrow(false); - } + Thread* pThisThread = ThreadStore::GetCurrentThreadIfAvailable(); + pThisThread->DeferTransitionFrame(); + pThisThread->DisablePreemptiveMode(); - pThisThread->EnablePreemptiveMode(); + if (PalInterlockedCompareExchangePointer((void* volatile*)&g_MarkCrossReferences, (void*)markCrossReferences, NULL) == NULL) + { + success = g_bridgeFinished.CreateManualEventNoThrow(false); } + pThisThread->EnablePreemptiveMode(); + return success; } @@ -118,7 +115,6 @@ extern "C" void QCALLTYPE JavaMarshal_FinishCrossReferenceProcessing( // Mark the GCBridge as inactive. // This must be synchronized with the GC so switch to cooperative mode. { - bool wasCooperative = false; Thread* pThisThread = ThreadStore::GetCurrentThreadIfAvailable(); pThisThread->DeferTransitionFrame(); pThisThread->DisablePreemptiveMode(); @@ -138,20 +134,41 @@ extern "C" void QCALLTYPE JavaMarshal_FinishCrossReferenceProcessing( ReleaseGCBridgeArgumentsWorker(crossReferences); } -FCIMPL0(FC_BOOL_RET, RhIsGCBridgeActive) +FCIMPL2(FC_BOOL_RET, GCHandle_InternalTryGetBridgeWait, OBJECTHANDLE handle, OBJECTREF* pObjResult) { - FC_RETURN_BOOL(g_GCBridgeActive); + if (g_GCBridgeActive) + { + FC_RETURN_BOOL(false); + } + + *pObjResult = ObjectFromHandle(handle); + FC_RETURN_BOOL(true); } FCIMPLEND -extern "C" void QCALLTYPE JavaMarshal_WaitForGCBridgeFinish() +extern "C" void QCALLTYPE GCHandle_InternalGetBridgeWait(OBJECTHANDLE handle, OBJECTREF* pObj) { - // We will transition to pre-emptive mode to wait for the bridge to finish. + // Transition to cooperative mode to ensure that the GC is not in progress Thread* pThisThread = ThreadStore::GetCurrentThreadIfAvailable(); + pThisThread->DeferTransitionFrame(); + pThisThread->DisablePreemptiveMode(); + while (g_GCBridgeActive) { + // This wait will transition to pre-emptive mode to wait for the bridge to finish. g_bridgeFinished.Wait(INFINITE, false, false); } + + // If we reach here, then the bridge has finished processing and we can be sure that + // it isn't currently active. + + // No GC can happen between the wait and obtaining of the reference, so the + // bridge processing status can't change, guaranteeing the nulling of weak refs + // took place in the bridge processing finish stage. + *pObj = ObjectFromHandle(handle); + + // Re-enable preemptive mode before we exit the QCall to ensure we're in the right GC state. + pThisThread->EnablePreemptiveMode(); } #endif // FEATURE_JAVAMARSHAL diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.NativeAot.cs index 195ab67ba4adb6..ae947f7605dd9a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.NativeAot.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.InteropServices.Java; +using System.Runtime.CompilerServices; namespace System.Runtime.InteropServices { @@ -16,16 +16,24 @@ public partial struct GCHandle internal static void InternalSet(IntPtr handle, object? value) => RuntimeImports.RhHandleSet(handle, value); #if FEATURE_JAVAMARSHAL - internal static object? InternalGetBridgeWait(IntPtr handle) + internal static unsafe object? InternalGetBridgeWait(IntPtr handle) { - if (RuntimeImports.RhIsGCBridgeActive()) - { -#pragma warning disable CA1416 // Validate platform compatibility - JavaMarshal.WaitForGCBridgeFinish(); -#pragma warning restore CA1416 // Validate platform compatibility - } - return InternalGet(handle); + object? target = null; + + if (InternalTryGetBridgeWait(handle, ref target)) + return target; + + InternalGetBridgeWait(handle, &target); + + return target; } #endif + + [MethodImpl(MethodImplOptions.InternalCall)] + [RuntimeImport(RuntimeImports.RuntimeLibrary, "GCHandle_InternalTryGetBridgeWait")] + private static extern bool InternalTryGetBridgeWait(IntPtr handle, ref object? result); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCHandle_InternalGetBridgeWait")] + private static unsafe partial void InternalGetBridgeWait(IntPtr handle, object?* result); } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.NativeAot.cs index 950dfdf39e0a09..c253114be4602d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.NativeAot.cs @@ -59,8 +59,5 @@ internal static unsafe partial void FinishCrossReferenceProcessingBridge( MarkCrossReferencesArgs* crossReferences, nuint numHandles, GCHandle* unreachableObjectHandles); - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "JavaMarshal_WaitForGCBridgeFinish")] - internal static partial void WaitForGCBridgeFinish(); } } From fd77d07b4094990537c9e544599d322b3821a67a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 17 Jul 2025 14:02:16 -0700 Subject: [PATCH 3/4] Don't treat pointer types as COM in the linker --- .../linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tools/illink/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs b/src/tools/illink/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs index a4c77237e317fc..244de7b0a0c9a1 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs @@ -218,6 +218,12 @@ static bool IsComInterop(IMarshalInfoProvider marshalInfoProvider, TypeReference if (nativeType == NativeType.None) { + if (parameterType.IsPointer) + { + // Pointer types are passed without marshalling + return false; + } + // Resolve will look at the element type var parameterTypeDef = context.TryResolve(parameterType); From 8379bc7acdc7bb7c6b6d8f98e3f6f85bf3091aa5 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 22 Jul 2025 16:25:28 -0700 Subject: [PATCH 4/4] PR feedback --- .../Runtime/interoplibinterface_java.cpp | 23 +++++++++---------- .../InteropServices/GCHandle.NativeAot.cs | 2 +- .../src/System/Runtime/RuntimeImports.cs | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp b/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp index 1e9d1645e7344c..78a65ca93ecd6a 100644 --- a/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp +++ b/src/coreclr/nativeaot/Runtime/interoplibinterface_java.cpp @@ -28,6 +28,8 @@ namespace void ReleaseGCBridgeArgumentsWorker( MarkCrossReferencesArgs* args) { + _ASSERTE(args != NULL); + // Memory was allocated for the collections by the GC. // See callers of GCToEEInterface::TriggerGCBridge(). @@ -55,22 +57,17 @@ void JavaMarshalNative::TriggerClientBridgeProcessing( return; } - bool gcBridgeTriggered = false; // Not initialized - if (g_MarkCrossReferences != NULL) - { - g_MarkCrossReferences(args); - gcBridgeTriggered = true; - } - - if (!gcBridgeTriggered) + if (g_MarkCrossReferences == NULL) { - // Release the memory allocated since the GCBridge - // wasn't trigger for some reason. + // Release the memory allocated since we + // don't have a GC bridge callback. ReleaseGCBridgeArgumentsWorker(args); return; } + g_MarkCrossReferences(args); + // This runs during GC while the world is stopped, no synchronisation required g_bridgeFinished.Reset(); @@ -85,7 +82,6 @@ extern "C" BOOL QCALLTYPE JavaMarshal_Initialize( BOOL success = FALSE; - // Switch to Cooperative mode since we are setting callbacks that // will be used during a GC and we want to ensure a GC isn't occurring // while they are being set. @@ -122,8 +118,9 @@ extern "C" void QCALLTYPE JavaMarshal_FinishCrossReferenceProcessing( GCHeapUtilities::GetGCHeap()->NullBridgeObjectsWeakRefs(length, unreachableObjectHandles); IGCHandleManager* pHandleManager = GCHandleUtilities::GetGCHandleManager(); + OBJECTHANDLE* handles = (OBJECTHANDLE*)unreachableObjectHandles; for (size_t i = 0; i < length; i++) - pHandleManager->DestroyHandleOfUnknownType(((OBJECTHANDLE*)unreachableObjectHandles)[i]); + pHandleManager->DestroyHandleOfUnknownType(handles[i]); g_GCBridgeActive = false; g_bridgeFinished.Set(); @@ -148,6 +145,8 @@ FCIMPLEND extern "C" void QCALLTYPE GCHandle_InternalGetBridgeWait(OBJECTHANDLE handle, OBJECTREF* pObj) { + _ASSERTE(pObj != NULL); + // Transition to cooperative mode to ensure that the GC is not in progress Thread* pThisThread = ThreadStore::GetCurrentThreadIfAvailable(); pThisThread->DeferTransitionFrame(); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.NativeAot.cs index ae947f7605dd9a..551d50a98b2bbe 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.NativeAot.cs @@ -27,7 +27,6 @@ public partial struct GCHandle return target; } -#endif [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeImports.RuntimeLibrary, "GCHandle_InternalTryGetBridgeWait")] @@ -35,5 +34,6 @@ public partial struct GCHandle [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCHandle_InternalGetBridgeWait")] private static unsafe partial void InternalGetBridgeWait(IntPtr handle, object?* result); +#endif } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index 02a93cc1e4156e..298dc148009042 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -296,7 +296,7 @@ internal static IntPtr RhHandleAllocDependent(object primary, object secondary) [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhpHandleAllocCrossReference")] - internal static extern IntPtr RhpHandleAllocCrossReference(object value, IntPtr context); + private static extern IntPtr RhpHandleAllocCrossReference(object value, IntPtr context); internal static IntPtr RhHandleAllocCrossReference(object value, IntPtr context) {