Skip to content

Commit 9e9daf4

Browse files
committed
Add JavaBridgedValueManager
Context: dotnet/runtime#115506 Context: dotnet/android#10125 We have an *API*, but not (yet) usable *implementation*. "Import" the `ManagedValueManager` from dotnet/android#10125, renaming to `JavaBridgedValueManager`, and add the proposed bridge API from dotnet/runtime#115506 to verify that it all compiles. It compiles! Next step: does it *work*? If I squint right, the proposed API looks very very similar to the existing MonoVM GC bridge API. Can I implement the proposed API in terms of MonoVM, and then have C# code perform the bridge code instead of native code, when using MonoVM? Let's find out!
1 parent e3062db commit 9e9daf4

File tree

1 file changed

+318
-0
lines changed

1 file changed

+318
-0
lines changed
Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Diagnostics;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Globalization;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Reflection;
10+
using System.Runtime.CompilerServices;
11+
using System.Runtime.InteropServices;
12+
using System.Runtime.InteropServices.Java;
13+
using System.Threading;
14+
15+
using Java.Interop;
16+
17+
namespace System.Runtime.InteropServices.Java {
18+
// https://github.com/dotnet/runtime/issues/115506
19+
20+
public struct ComponentCrossReference {
21+
public nint SourceGroupIndex;
22+
public nint DestinationGroupIndex;
23+
}
24+
25+
public unsafe struct StronglyConnectedComponent {
26+
public nint Count;
27+
public System.IntPtr* Context;
28+
}
29+
30+
public unsafe struct MarkCrossReferences {
31+
public nint ComponentsLen;
32+
public StronglyConnectedComponent* Components;
33+
public nint CrossReferencesLen;
34+
public ComponentCrossReference* CrossReferences;
35+
}
36+
37+
static class JavaMarshal {
38+
public static unsafe void Initialize(delegate* unmanaged<MarkCrossReferences*, void> markCrossReferences) =>
39+
throw null!;
40+
41+
public static GCHandle CreateReferenceTrackingHandle(object obj, IntPtr context) =>
42+
throw null!;
43+
44+
public static IntPtr GetContext(GCHandle obj) =>
45+
throw null!;
46+
47+
public static unsafe void ReleaseMarkCrossReferenceResources(MarkCrossReferences* crossReferences) =>
48+
throw null!;
49+
}
50+
}
51+
52+
namespace Java.Interop {
53+
54+
class JavaBridgedValueManager : JniRuntime.JniValueManager
55+
{
56+
Dictionary<int, List<GCHandle>>? RegisteredInstances = new ();
57+
58+
internal unsafe JavaBridgedValueManager ()
59+
{
60+
JavaMarshal.Initialize (&MarkCrossReferences);
61+
}
62+
63+
public override void WaitForGCBridgeProcessing ()
64+
{
65+
}
66+
67+
public override void CollectPeers ()
68+
{
69+
if (RegisteredInstances == null)
70+
throw new ObjectDisposedException (nameof (ManagedValueManager));
71+
72+
var peers = new List<GCHandle> ();
73+
74+
lock (RegisteredInstances) {
75+
foreach (var ps in RegisteredInstances.Values) {
76+
foreach (var p in ps) {
77+
peers.Add (p);
78+
}
79+
}
80+
RegisteredInstances.Clear ();
81+
}
82+
List<Exception>? exceptions = null;
83+
foreach (var peer in peers) {
84+
try {
85+
if (peer.Target is IDisposable disposable)
86+
disposable.Dispose ();
87+
}
88+
catch (Exception e) {
89+
exceptions = exceptions ?? new List<Exception> ();
90+
exceptions.Add (e);
91+
}
92+
}
93+
if (exceptions != null)
94+
throw new AggregateException ("Exceptions while collecting peers.", exceptions);
95+
}
96+
97+
public override void AddPeer (IJavaPeerable value)
98+
{
99+
if (RegisteredInstances == null)
100+
throw new ObjectDisposedException (nameof (ManagedValueManager));
101+
102+
var r = value.PeerReference;
103+
if (!r.IsValid)
104+
throw new ObjectDisposedException (value.GetType ().FullName);
105+
106+
if (r.Type != JniObjectReferenceType.Global) {
107+
value.SetPeerReference (r.NewGlobalRef ());
108+
JniObjectReference.Dispose (ref r, JniObjectReferenceOptions.CopyAndDispose);
109+
}
110+
int key = value.JniIdentityHashCode;
111+
lock (RegisteredInstances) {
112+
List<GCHandle>? peers;
113+
if (!RegisteredInstances.TryGetValue (key, out peers)) {
114+
peers = new List<GCHandle> () {
115+
CreateReferenceTrackingHandle (value)
116+
};
117+
RegisteredInstances.Add (key, peers);
118+
return;
119+
}
120+
121+
for (int i = peers.Count - 1; i >= 0; i--) {
122+
var p = peers [i];
123+
if (p.Target is not IJavaPeerable peer)
124+
continue;
125+
if (!JniEnvironment.Types.IsSameObject (peer.PeerReference, value.PeerReference))
126+
continue;
127+
if (Replaceable (p)) {
128+
peers [i] = CreateReferenceTrackingHandle (value);
129+
} else {
130+
WarnNotReplacing (key, value, peer);
131+
}
132+
return;
133+
}
134+
peers.Add (CreateReferenceTrackingHandle (value));
135+
}
136+
}
137+
138+
static bool Replaceable (GCHandle handle)
139+
{
140+
if (handle.Target is not IJavaPeerable peer)
141+
return true;
142+
return peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable);
143+
}
144+
145+
void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepValue)
146+
{
147+
Runtime.ObjectReferenceManager.WriteGlobalReferenceLine (
148+
"Warning: Not registering PeerReference={0} IdentityHashCode=0x{1} Instance={2} Instance.Type={3} Java.Type={4}; " +
149+
"keeping previously registered PeerReference={5} Instance={6} Instance.Type={7} Java.Type={8}.",
150+
ignoreValue.PeerReference.ToString (),
151+
key.ToString ("x", CultureInfo.InvariantCulture),
152+
RuntimeHelpers.GetHashCode (ignoreValue).ToString ("x", CultureInfo.InvariantCulture),
153+
ignoreValue.GetType ().FullName,
154+
JniEnvironment.Types.GetJniTypeNameFromInstance (ignoreValue.PeerReference),
155+
keepValue.PeerReference.ToString (),
156+
RuntimeHelpers.GetHashCode (keepValue).ToString ("x", CultureInfo.InvariantCulture),
157+
keepValue.GetType ().FullName,
158+
JniEnvironment.Types.GetJniTypeNameFromInstance (keepValue.PeerReference));
159+
}
160+
161+
public override IJavaPeerable? PeekPeer (JniObjectReference reference)
162+
{
163+
if (RegisteredInstances == null)
164+
throw new ObjectDisposedException (nameof (ManagedValueManager));
165+
166+
if (!reference.IsValid)
167+
return null;
168+
169+
int key = GetJniIdentityHashCode (reference);
170+
171+
lock (RegisteredInstances) {
172+
List<GCHandle>? peers;
173+
if (!RegisteredInstances.TryGetValue (key, out peers))
174+
return null;
175+
176+
for (int i = peers.Count - 1; i >= 0; i--) {
177+
var p = peers [i];
178+
if (p.Target is IJavaPeerable peer && JniEnvironment.Types.IsSameObject (reference, peer.PeerReference))
179+
return peer;
180+
}
181+
if (peers.Count == 0)
182+
RegisteredInstances.Remove (key);
183+
}
184+
return null;
185+
}
186+
187+
public override void RemovePeer (IJavaPeerable value)
188+
{
189+
if (RegisteredInstances == null)
190+
throw new ObjectDisposedException (nameof (ManagedValueManager));
191+
192+
if (value == null)
193+
throw new ArgumentNullException (nameof (value));
194+
195+
int key = value.JniIdentityHashCode;
196+
lock (RegisteredInstances) {
197+
List<GCHandle>? peers;
198+
if (!RegisteredInstances.TryGetValue (key, out peers))
199+
return;
200+
201+
for (int i = peers.Count - 1; i >= 0; i--) {
202+
var p = peers [i];
203+
if (object.ReferenceEquals (value, p.Target)) {
204+
peers.RemoveAt (i);
205+
FreeHandle (p);
206+
}
207+
}
208+
if (peers.Count == 0)
209+
RegisteredInstances.Remove (key);
210+
}
211+
}
212+
213+
public override void FinalizePeer (IJavaPeerable value)
214+
{
215+
var h = value.PeerReference;
216+
var o = Runtime.ObjectReferenceManager;
217+
// MUST NOT use SafeHandle.ReferenceType: local refs are tied to a JniEnvironment
218+
// and the JniEnvironment's corresponding thread; it's a thread-local value.
219+
// Accessing SafeHandle.ReferenceType won't kill anything (so far...), but
220+
// instead it always returns JniReferenceType.Invalid.
221+
if (!h.IsValid || h.Type == JniObjectReferenceType.Local) {
222+
if (o.LogGlobalReferenceMessages) {
223+
o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}",
224+
h.ToString (),
225+
value.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture),
226+
RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture),
227+
value.GetType ().ToString ());
228+
}
229+
RemovePeer (value);
230+
value.SetPeerReference (new JniObjectReference ());
231+
value.Finalized ();
232+
return;
233+
}
234+
235+
RemovePeer (value);
236+
if (o.LogGlobalReferenceMessages) {
237+
o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}",
238+
h.ToString (),
239+
value.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture),
240+
RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture),
241+
value.GetType ().ToString ());
242+
}
243+
value.SetPeerReference (new JniObjectReference ());
244+
JniObjectReference.Dispose (ref h);
245+
value.Finalized ();
246+
}
247+
248+
public override void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues)
249+
{
250+
try {
251+
ActivateViaReflection (reference, cinfo, argumentValues);
252+
} catch (Exception e) {
253+
var m = string.Format (
254+
CultureInfo.InvariantCulture,
255+
"Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.",
256+
reference,
257+
GetJniIdentityHashCode (reference).ToString ("x", CultureInfo.InvariantCulture),
258+
JniEnvironment.Types.GetJniTypeNameFromInstance (reference),
259+
cinfo.DeclaringType?.FullName);
260+
Debug.WriteLine (m);
261+
262+
throw new NotSupportedException (m, e);
263+
}
264+
}
265+
266+
void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues)
267+
{
268+
var declType = GetDeclaringType (cinfo);
269+
270+
#pragma warning disable IL2072
271+
var self = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (declType);
272+
#pragma warning restore IL2072
273+
self.SetPeerReference (reference);
274+
275+
cinfo.Invoke (self, argumentValues);
276+
277+
[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "🤷‍♂️")]
278+
[return: DynamicallyAccessedMembers (Constructors)]
279+
Type GetDeclaringType (ConstructorInfo cinfo) =>
280+
cinfo.DeclaringType ?? throw new NotSupportedException ("Do not know the type to create!");
281+
}
282+
283+
public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
284+
{
285+
if (RegisteredInstances == null)
286+
throw new ObjectDisposedException (nameof (ManagedValueManager));
287+
288+
lock (RegisteredInstances) {
289+
var peers = new List<JniSurfacedPeerInfo> (RegisteredInstances.Count);
290+
foreach (var e in RegisteredInstances) {
291+
foreach (var p in e.Value) {
292+
if (p.Target is not IJavaPeerable peer)
293+
continue;
294+
peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference<IJavaPeerable> (peer)));
295+
}
296+
}
297+
return peers;
298+
}
299+
}
300+
301+
static GCHandle CreateReferenceTrackingHandle (IJavaPeerable value) =>
302+
JavaMarshal.CreateReferenceTrackingHandle (value, value.JniObjectReferenceControlBlock);
303+
304+
static unsafe void FreeHandle (GCHandle handle)
305+
{
306+
IntPtr context = JavaMarshal.GetContext (handle);
307+
NativeMemory.Free ((void*) context);
308+
}
309+
310+
[UnmanagedCallersOnly]
311+
internal static unsafe void MarkCrossReferences (MarkCrossReferences* crossReferences)
312+
{
313+
// Java.Lang.JavaSystem.Gc ();
314+
315+
JavaMarshal.ReleaseMarkCrossReferenceResources (crossReferences);
316+
}
317+
}
318+
}

0 commit comments

Comments
 (0)