-
Notifications
You must be signed in to change notification settings - Fork 5k
GC Bridge for Android scenarios #114184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
GC Bridge for Android scenarios #114184
Conversation
Prior to check-in, update all places with "!TODO-JAVA!".
Implement scanning of the new GC Handle and calling a new VM callout to process the resulting data.
Implement the VM side of the GC Handle processing and callout to mark cross references.
Note regarding the
|
1 similar comment
Note regarding the
|
Tagging subscribers to this area: @dotnet/interop-contrib |
@@ -161,6 +161,11 @@ if(FEATURE_OBJCMARSHAL) | |||
add_compile_definitions(FEATURE_OBJCMARSHAL) | |||
endif() | |||
|
|||
if(FEATURE_JAVAMARSHAL) | |||
add_compile_definitions(FEATURE_JAVAMARSHAL) | |||
add_compile_definitions(FEATURE_GCBRIDGE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need two different FEATURE_... ifdefs for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point there aren't any. While I was implementing it the logic was clearly distinct though. There is the general concept of the GC Bridge and also the specific Java API and infrastructure that exposes that data. It was natural to separate the two given how we've implemented other interop scenarios involving the GC. It is entirely reasonable to collapse them, but making them distinct now is much easier than retroactively splitting them in the future.
@@ -14,7 +14,7 @@ | |||
#endif | |||
|
|||
#define INITIAL_HANDLE_TABLE_ARRAY_SIZE 10 | |||
#define HANDLE_MAX_INTERNAL_TYPES 12 | |||
#define HANDLE_MAX_INTERNAL_TYPES 16 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there perf consequences from bumping this limit? Should we start recycling deprecated HDNTYPE values (e.g. HNDTYPE_ASYNCPINNED)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just need to be careful that recycling wouldnt cause backcompat issues for standalone gc.
GCScan::GcScanWithBridge(GCHeap::Promote, | ||
condemned_gen_number, max_generation, | ||
&sc); | ||
drain_mark_queue(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wonder whether drain_mark_queue
is required after each scan or can it be combined with the one above ?
condemned_gen_number, max_generation, | ||
&sc); | ||
drain_mark_queue(); | ||
// fire_mark_event (ETW::???, current_promoted_bytes, last_promoted_bytes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assume this will be a TODO to enable later?
} | ||
CONTRACTL_END; | ||
|
||
#ifdef FEATURE_GCBRIDGE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldnt the full function be under a #ifdef
?
I don't think changes on the GC side need to be reviewed at this point. I know they will be different but we will address those when we actually start implementation. |
Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it. |
Context: dotnet/runtime#114184 Context: https://github.com/jonathanpeppers/BridgeSandbox So far, I: * Built dotnet/runtime, I built this branch: https://github.com/jonathanpeppers/runtime/tree/gcbridge_impl * Put the relevant arm64 packs in `packages/` folder and configured a `NuGet.config` to use them. * Setup a `UpdateRuntimePacks` target in the `Mono.Android.csproj` to use the 10.0.0-dev packs. * Make use of the new `JavaMarshal.CreateReferenceTrackingHandle()` API * This is WIP there are still more APIs.
Context: dotnet/runtime#114184 Context: https://github.com/jonathanpeppers/BridgeSandbox Context: dotnet/runtime@main...BrzVlad:runtime:feature-clr-gcbridge So far, I: * Built dotnet/runtime, I built this branch: https://github.com/jonathanpeppers/runtime/tree/gcbridge_impl * Put the relevant arm64 packs in `packages/` folder and configured a `NuGet.config` to use them. * Setup `build-tools\scripts\custom-runtime.targets` to use the 10.0.0-dev dotnet/runtime packs. * In `ManagedValueManager.cs`... * Call a new native method on startup: var mark_cross_references_ftn = RuntimeNativeMethods.clr_initialize_gc_bridge (&BridgeProcessingFinished); * Pass the returned function pointer to: JavaMarshal.Initialize (mark_cross_references_ftn); * In `AddPeer(IJavaPeerable value)` call `JavaMarshal.CreateReferenceTrackingHandle()` * In `RemovePeer(IJavaPeerable value)` call: static unsafe void FreeHandle (GCHandle handle) { IntPtr context = JavaMarshal.GetContext (handle); NativeMemory.Free((void*)context); }
Context: dotnet/runtime#114184 Context: https://github.com/jonathanpeppers/BridgeSandbox Context: dotnet/runtime@main...BrzVlad:runtime:feature-clr-gcbridge So far, I: * Built dotnet/runtime, I built this branch: https://github.com/jonathanpeppers/runtime/tree/gcbridge_impl * Put the relevant arm64 packs in `packages/` folder and configured a `NuGet.config` to use them. * Setup `build-tools\scripts\custom-runtime.targets` to use the 10.0.0-dev dotnet/runtime packs. * In `ManagedValueManager.cs`... * Call a new native method on startup: var mark_cross_references_ftn = RuntimeNativeMethods.clr_initialize_gc_bridge (&BridgeProcessingFinished); * Pass the returned function pointer to: JavaMarshal.Initialize (mark_cross_references_ftn); * In `AddPeer(IJavaPeerable value)` call `JavaMarshal.CreateReferenceTrackingHandle()` * In `RemovePeer(IJavaPeerable value)` call: static unsafe void FreeHandle (GCHandle handle) { IntPtr context = JavaMarshal.GetContext (handle); NativeMemory.Free((void*)context); }
Context: dotnet/runtime#114184 Context: https://github.com/jonathanpeppers/BridgeSandbox Context: dotnet/runtime@main...BrzVlad:runtime:feature-clr-gcbridge So far, I: * Built dotnet/runtime, I built this branch: https://github.com/jonathanpeppers/runtime/tree/gcbridge_impl * Put the relevant arm64 packs in `packages/` folder and configured a `NuGet.config` to use them. * Setup `build-tools\scripts\custom-runtime.targets` to use the 10.0.0-dev dotnet/runtime packs. * In `ManagedValueManager.cs`... * Call a new native method on startup: var mark_cross_references_ftn = RuntimeNativeMethods.clr_initialize_gc_bridge (&BridgeProcessingFinished); * Pass the returned function pointer to: JavaMarshal.Initialize (mark_cross_references_ftn); * In `AddPeer(IJavaPeerable value)` call `JavaMarshal.CreateReferenceTrackingHandle()` * In `RemovePeer(IJavaPeerable value)` call: static unsafe void FreeHandle (GCHandle handle) { IntPtr context = JavaMarshal.GetContext (handle); NativeMemory.Free((void*)context); }
Context: dotnet/runtime#114184 Context: dotnet/android#10125 Context: dotnet/android#10125 (comment) Part of adding a GC bridge to CoreCLR are the new APIs: namespace System.Runtime.InteropServices.Java; public struct ComponentCrossReference { public nint SourceGroupIndex, DestinationGroupIndex; } public unsafe struct StronglyConnectedComponent { public nint Count; public IntPtr* Context; } public static partial class JavaMarshal { public static unsafe void Initialize( delegate* unmanaged< System.IntPtr, // sccsLen StronglyConnectedComponent*, // sccs System.IntPtr, // ccrsLen ComponentCrossReference*, // ccrs void> markCrossReferences); public static GCHandle CreateReferenceTrackingHandle(object obj, IntPtr context); public static IntPtr GetContext(GCHandle obj); } Of note is the "data flow" of `context`: * `JavaMarshal.CreateReferenceTrackingHandle()` has a "`context`" parameter. * The `context` parameter to `JavaMarshal.CreateReferenceTrackingHandle()` is the return value of `JavaMarshal.GetContext()` * The `context` parameter to `JavaMarshal.CreateReferenceTrackingHandle()` is stored within `StronglyConnectedComponent.Context`. * The `markCrossReferences` parameter of `JavaMarshal.Initialize()` is called by the GC bridge and given a native array of `StronglyConnectedComponent` instances, which contains `Context`. The short of it is that the proposed GC bridge doesn't contain direct access to the `IJavaPeerable` instances in play. Instead, it has access to "context" which must contain the JNI Object Reference information that the `markCrossReferences` callback needs access to. Furthermore, the `context` pointer value *cannot change*, i.e. it needs to be a native pointer value a'la **malloc**(3), ***not*** a value which can be moved by the GC. (The *contents* can change; the pointer value cannot.)) While we're still prototyping this, what we currently believe we need is the JNI object reference, JNI object reference type, and (maybe?) the JNI Weak Global Reference value and "refs added" values. Update `IJavaPeerable` to add a `JniObjectReferenceControlBlock` property which can be used as the `context` parameter: partial interface IJavaPeerable { IntPtr JniObjectReferenceControlBlock => 0; } This supports usage of: IJavaPeerable value = … GCHandle handle = JavaMarshal.CreateReferenceTrackingHandle( value, value.JniObjectReferenceControlBlock );
API design and implementation questions: Why constrain to Android? It would be very very very useful (to me) if we could use this on Desktop environments (dotnet/java-interop unit tests!). Why My alternate proposal is to instead put some of these members into a namespace System.Runtime.InteropServices.GCBridge;
public struct ComponentCrossReference {
public nint SourceGroupIndex, DestinationGroupIndex;
}
public unsafe struct StronglyConnectedComponent {
public nint Count;
public IntPtr* Context;
} If we can relax the "only Android" requirement, it then makes sense (to me) to add some of these members to namespace System.Runtime.InteropServices;
partial struct GCHandle {
public static GCHandle AllocReferenceTracking(object? value, nint context);
public nint Context {get;}
} though it would also make sense to "hide" this corner case by keeping these members out of That leaves the I would thus propose: namespace System.Runtime.InteropServices.GCBridge;
public struct MarkCrossReferences {
public System.IntPtr componentsLen;
public StronglyConnectedComponent* components;
public System.IntPtr crossReferencesLen;
public ComponentCrossReference* crossReferences;
}
partial static class GCBridgeMarshal {
// May only be invoked once; throws on 2nd+ invocation
public static unsafe void InitializeOnce(
delegate* unmanaged<MarkCrossReferences*, void> markCrossReferences
);
// If we don't want to add members to GCHandle…
public static GCHandle CreateReferenceTrackingHandle(object obj, IntPtr context);
public static IntPtr GetContext(GCHandle obj);
} |
Agree in principle. This was simply narrowing where we support java interop. It isn't planned to be something we would recommend for users, but I do agree that having it testable on desktop is a big win and something we should be open to.
Following the ObjectiveC and Swift interop stories. The
Yep, these APIs should be narrowly used.
I don't see the need for a version API here. Happy to discuss further, but this in't an area where I think we will be doing much innovation and if we do it is likely to impact more than just the struct.
It can only be called once. It will throw if it is called again. This was also modeled after the ObjectiveC APIs.
I like the updated proposal. I don't think |
My two cents: I like the Java in the name. This is a 3rd variant of what can be called "GC bridge" that we are introducing APIs for. Two existing GC bridges are COM/WinRT and ObjectiveC. The design objective for all of them has been backward compatibility for MAUI workloads. I do not see any of them as a general-purpose solution.
For example, the runtime side of the ObjectiveC APIs does not have any ObjectiveC calls either and it can be used by any system that does lifetime management like ObjectiveC in theory. |
This goal was revised shortly after that blog post was published. (We are not blogging about plans anymore since the plans change and blogs like that just created confusion.) |
This PR contains the VM side of the work and the introduction of a new GC Handle type.
The GC work still needs to be completed. This is currently just a place to share work.