Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0ae638d
[Mono.Android] call new Java "GC Bridge" APIs
jonathanpeppers May 8, 2025
f4a2148
What if everything was managed-only?
jonathanpeppers May 8, 2025
aa0a4ae
Now uses: https://github.com/dotnet/java-interop/pull/1334
jonathanpeppers May 12, 2025
0bc80b2
Revert "What if everything was managed-only?"
jonathanpeppers May 19, 2025
8f9c0fc
Setup `g_bpFinishCallback`
jonathanpeppers May 19, 2025
fda9d43
Update src/native/clr/host/internal-pinvokes.cc
jonathanpeppers May 20, 2025
eba552f
Update src/native/clr/host/internal-pinvokes.cc
jonathanpeppers May 20, 2025
4313f89
dotnet/runtime builds from BrzVlad:runtime:feature-clr-gcbridge
jonathanpeppers May 20, 2025
f4f8dcf
Add native portion of the GC interface
grendello May 21, 2025
4d8bd88
Don't need this anymore
grendello May 21, 2025
fccda54
Enable git lfs on AzDO
jonathanpeppers May 21, 2025
5b2e13e
Enable `$(system.debug)`
jonathanpeppers May 21, 2025
461d127
Revert "Enable `$(system.debug)`"
jonathanpeppers May 21, 2025
6cac797
Revert "Enable git lfs on AzDO"
jonathanpeppers May 21, 2025
0654d76
Update azure-pipelines.yaml
jonathanpeppers May 21, 2025
a0f2561
Working on yaml/git lfs
jonathanpeppers May 21, 2025
0221203
git lfs working directory
jonathanpeppers May 21, 2025
c990184
workingDirectory: ${{ parameters.xaSourcePath }}
jonathanpeppers May 21, 2025
f42f328
`git lfs install`
jonathanpeppers May 21, 2025
0ebf234
[native\mono] use `JniObjectReferenceControlBlock` structure
jonathanpeppers May 21, 2025
ba60ff7
Update to APIs from BrzVlad's branch
jonathanpeppers May 21, 2025
02c2562
Some updates to the recent native code changes
grendello May 21, 2025
664be1a
android-x64 runtime pack
jonathanpeppers May 21, 2025
96e1b9c
Remove comment that no longer applies
grendello May 22, 2025
75cfd4d
git lfs for test lanes
jonathanpeppers May 22, 2025
d51469c
Copy `packages` folder
jonathanpeppers May 22, 2025
1c25938
On-device tests use `custom-runtime.targets`
jonathanpeppers May 22, 2025
5023378
Update pinvoke-tables.include
jonathanpeppers May 22, 2025
1fba234
Let's see...
grendello May 22, 2025
b5b05f2
Bump to updated version of JI
grendello May 23, 2025
7e56838
Don't use HEAD of the JI PR, it doesn't build
grendello May 23, 2025
afe529f
Merge branch 'main' into dev/peppers/gcbridge
jonathanpeppers Jun 3, 2025
b677236
Update pinvoke-tables.include
jonathanpeppers Jun 3, 2025
a7c26b5
Merge branch 'main' into dev/peppers/gcbridge
jonathanpeppers Jun 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ Makefile eol=lf
*.wixproj eol=crlf
*.wxs eol=crlf
*.rtf eol=crlf

packages/* filter=lfs diff=lfs merge=lfs -text
25 changes: 19 additions & 6 deletions Documentation/workflow/DevelopmentTips.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,13 +450,26 @@ A second (better) way is to add this MSBuild target to your Android
`.csproj` file:

```xml
<Target Name="UpdateMonoRuntimePacks" BeforeTargets="ProcessFrameworkReferences">
<Target Name="UpdateRuntimeVersion" BeforeTargets="ProcessFrameworkReferences">
<PropertyGroup>
<!-- This could be a version I built myself -->
<_Version>10.0.0-dev</_Version>
</PropertyGroup>
<ItemGroup>
<KnownRuntimePack
Update="Microsoft.NETCore.App"
Condition=" '%(KnownRuntimePack.TargetFramework)' == 'net6.0' "
LatestRuntimeFrameworkVersion="6.0.0-preview.7.21364.3"
/>
<!-- For runtime packs only -->
<KnownRuntimePack
Update="Microsoft.NETCore.App"
Condition=" '%(KnownRuntimePack.TargetFramework)' == 'net10.0' "
LatestRuntimeFrameworkVersion="$(_Version)"
/>
<!-- For new .NET APIs -->
<KnownFrameworkReference
Update="Microsoft.NETCore.App"
Condition=" '%(KnownFrameworkReference.TargetFramework)' == 'net10.0' "
DefaultRuntimeFrameworkVersion="$(_Version)"
LatestRuntimeFrameworkVersion="$(_Version)"
TargetingPackVersion="$(_Version)"
/>
</ItemGroup>
</Target>
```
Expand Down
1 change: 1 addition & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<!-- End: Package sources from dotnet-android -->
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
<!-- ensure only the sources defined below are used -->
<add key="local-packages" value="packages" />
<add key="dotnet-public" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json" protocolVersion="3" />
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" protocolVersion="3" />
<!-- This is for packages needed by debugger-libs -->
Expand Down
21 changes: 21 additions & 0 deletions build-tools/scripts/custom-runtime.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project>
<!-- Use version in local packages folder -->
<Target Name="UpdateRuntimeVersion" BeforeTargets="ProcessFrameworkReferences">
<PropertyGroup>
<_Version>10.0.0-dev</_Version>
</PropertyGroup>
<ItemGroup>
<KnownFrameworkReference Update="Microsoft.NETCore.App"
Condition=" '%(KnownFrameworkReference.TargetFramework)' == 'net10.0' "
DefaultRuntimeFrameworkVersion="$(_Version)"
LatestRuntimeFrameworkVersion="$(_Version)"
TargetingPackVersion="$(_Version)"
/>
<KnownRuntimePack
Update="Microsoft.NETCore.App"
Condition=" '%(KnownRuntimePack.TargetFramework)' == 'net10.0' "
LatestRuntimeFrameworkVersion="$(_Version)"
/>
</ItemGroup>
</Target>
</Project>
3 changes: 3 additions & 0 deletions packages/Microsoft.NETCore.App.Ref.10.0.0-dev.nupkg
Git LFS file not shown
Git LFS file not shown
65 changes: 46 additions & 19 deletions src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,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;
Expand All @@ -20,10 +21,11 @@ class ManagedValueManager : JniRuntime.JniValueManager
{
const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

Dictionary<int, List<IJavaPeerable>>? RegisteredInstances = new Dictionary<int, List<IJavaPeerable>>();
Dictionary<int, List<GCHandle>>? RegisteredInstances = new ();

internal ManagedValueManager ()
internal unsafe ManagedValueManager ()
{
JavaMarshal.Initialize (&FinishBridgeProcessing);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonathanpeppers Note that the method that you register here will be called directly from the GC while the world is stopped. This means that you shouldn't register a managed method here. Instead there should be a native method that is not blocking (aka it dispatches the work of doing the java GC to a separate thread) and then returns so the C# GC can conclude. Once this separate thread finishes with the java gc, only then it can callback into managed so it does the FinishCrossReferenceProcessing (according to the new version of the api)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will revert f4a2148, I was already kind of skeptical that everything could be done in managed code.

/cc @grendello I think we need to be able to run these lines in our CoreCLR native code:

set_bridge_processing_field (domains_list, 1);
gc_prepare_for_java_collection (env, num_sccs, sccs, num_xrefs, xrefs);
java_gc (env);
gc_cleanup_after_java_collection (env, num_sccs, sccs);
set_bridge_processing_field (domains_list, 0);

Then at the end, it would call the UCO FinishBridgeProcessing() method.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BrzVlad how hard it is to register an ecall with CoreCLR? We can register a standard p/invoke method here, but ecall would be faster. I seem to recall it's not as straightforward in CoreCLR as it is in MonoVM, though?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By ecall do you mean mono's mono_add_internal_call ? It is not clear to me how would you plan to use it in this context ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, equivalent to Mono's icall. I assume there's a way to obtain its address from native code and use it to register in the above call.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm missing some context, but icalls are used for managed code to call into native runtime code, that is registered to the runtime. While in JavaMarshal.Initialize (..); you are registering a native callback to be called by the runtime during GC. This native code is called directly, there should be no overhead or transitions involved, so I don't understand how an icall would be relevant here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so it's me who misunderstood - I thought the callback would be called by the managed code at some point, not the native runtime. If it's a direct call from native runtime then the point is moot. Thanks for the clarification :) (I'm only now getting acquainted with this new GC bridge)

}

public override void WaitForGCBridgeProcessing ()
Expand All @@ -35,7 +37,7 @@ public override void CollectPeers ()
if (RegisteredInstances == null)
throw new ObjectDisposedException (nameof (ManagedValueManager));

var peers = new List<IJavaPeerable> ();
var peers = new List<GCHandle> ();

lock (RegisteredInstances) {
foreach (var ps in RegisteredInstances.Values) {
Expand All @@ -48,7 +50,8 @@ public override void CollectPeers ()
List<Exception>? exceptions = null;
foreach (var peer in peers) {
try {
peer.Dispose ();
if (peer.Target is IDisposable disposable)
disposable.Dispose ();
}
catch (Exception e) {
exceptions = exceptions ?? new List<Exception> ();
Expand All @@ -74,33 +77,35 @@ public override void AddPeer (IJavaPeerable value)
}
int key = value.JniIdentityHashCode;
lock (RegisteredInstances) {
List<IJavaPeerable>? peers;
List<GCHandle>? peers;
if (!RegisteredInstances.TryGetValue (key, out peers)) {
peers = new List<IJavaPeerable> () {
value,
peers = new List<GCHandle> () {
CreateReferenceTrackingHandle (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))
if (p.Target is not IJavaPeerable peer)
continue;
if (!JniEnvironment.Types.IsSameObject (peer.PeerReference, value.PeerReference))
continue;
if (Replaceable (p)) {
peers [i] = value;
peers [i] = CreateReferenceTrackingHandle (value);
} else {
WarnNotReplacing (key, value, p);
WarnNotReplacing (key, value, peer);
}
return;
}
peers.Add (value);
peers.Add (CreateReferenceTrackingHandle (value));
}
}

static bool Replaceable (IJavaPeerable peer)
static bool Replaceable (GCHandle handle)
{
if (peer == null)
if (handle.Target is not IJavaPeerable peer)
return true;
return peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable);
}
Expand Down Expand Up @@ -132,14 +137,14 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal
int key = GetJniIdentityHashCode (reference);

lock (RegisteredInstances) {
List<IJavaPeerable>? peers;
List<GCHandle>? peers;
if (!RegisteredInstances.TryGetValue (key, out 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 (p.Target is IJavaPeerable peer && JniEnvironment.Types.IsSameObject (reference, peer.PeerReference))
return peer;
}
if (peers.Count == 0)
RegisteredInstances.Remove (key);
Expand All @@ -157,14 +162,15 @@ public override void RemovePeer (IJavaPeerable value)

int key = value.JniIdentityHashCode;
lock (RegisteredInstances) {
List<IJavaPeerable>? peers;
List<GCHandle>? peers;
if (!RegisteredInstances.TryGetValue (key, out peers))
return;

for (int i = peers.Count - 1; i >= 0; i--) {
var p = peers [i];
if (object.ReferenceEquals (value, p)) {
if (object.ReferenceEquals (value, p.Target)) {
peers.RemoveAt (i);
FreeHandle (p);
}
}
if (peers.Count == 0)
Expand Down Expand Up @@ -251,13 +257,34 @@ public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
var peers = new List<JniSurfacedPeerInfo> (RegisteredInstances.Count);
foreach (var e in RegisteredInstances) {
foreach (var p in e.Value) {
peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference<IJavaPeerable> (p)));
if (p.Target is not IJavaPeerable peer)
continue;
peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference<IJavaPeerable> (peer)));
}
}
return peers;
}
}

static GCHandle CreateReferenceTrackingHandle (IJavaPeerable value) =>
JavaMarshal.CreateReferenceTrackingHandle (value, value.JniObjectReferenceControlBlock);

static unsafe void FreeHandle (GCHandle handle)
{
IntPtr context = JavaMarshal.GetContext (handle);
NativeMemory.Free ((void*) context);
}

[UnmanagedCallersOnly]
internal static unsafe void FinishBridgeProcessing (nint sccsLen, StronglyConnectedComponent* sccs, nint ccrsLen, ComponentCrossReference* ccrs)
{
Java.Lang.JavaSystem.Gc ();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the C# binding of java.lang.System.gc(): https://developer.android.com/reference/java/lang/System#gc()


JavaMarshal.ReleaseMarkCrossReferenceResources (
new Span<StronglyConnectedComponent> (sccs, (int) sccsLen),
new Span<ComponentCrossReference> (ccrs, (int) ccrsLen));
}

const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) };
Expand Down
1 change: 1 addition & 0 deletions src/Mono.Android/Mono.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
<Import Project="..\Xamarin.Android.NamingCustomAttributes\Xamarin.Android.NamingCustomAttributes.projitems" Label="Shared" Condition="Exists('..\Xamarin.Android.NamingCustomAttributes\Xamarin.Android.NamingCustomAttributes.projitems')" />
<Import Project="Mono.Android.targets" />
<Import Project="..\..\build-tools\scripts\JavaCallableWrappers.targets" />
<Import Project="..\..\build-tools\scripts\custom-runtime.targets" />
<Import Project="$(IntermediateOutputPath)mcw\Mono.Android.projitems" Condition="Exists('$(IntermediateOutputPath)mcw\Mono.Android.projitems')" />

<ItemGroup>
Expand Down
Loading