2 * sgen-gchandles.c: SGen GC handles.
4 * Copyright (C) 2015 Xamarin Inc
6 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
12 #include "mono/sgen/sgen-gc.h"
13 #include "mono/sgen/sgen-client.h"
14 #include "mono/sgen/sgen-array-list.h"
15 #include "mono/utils/mono-membar.h"
17 #ifdef HEAVY_STATISTICS
18 static volatile guint32 stat_gc_handles_allocated = 0;
19 static volatile guint32 stat_gc_handles_max_allocated = 0;
23 * A table of GC handle data, implementing a simple lock-free bitmap allocator.
25 * Each entry in a bucket is a pointer with two tag bits: if
26 * 'GC_HANDLE_OCCUPIED' returns true for a slot, then the slot is occupied; if
27 * so, then 'GC_HANDLE_VALID' gives whether the entry refers to a valid (1) or
28 * NULL (0) object reference. If the reference is valid, then the pointer is an
29 * object pointer. If the reference is NULL, and 'GC_HANDLE_TYPE_IS_WEAK' is
30 * true for 'type', then the pointer is a metadata pointer--this allows us to
31 * retrieve the domain ID of an expired weak reference in Mono.
35 SgenArrayList entries_array;
40 protocol_gchandle_update (int handle_type, gpointer link, gpointer old_value, gpointer new_value)
42 gboolean old = MONO_GC_HANDLE_IS_OBJECT_POINTER (old_value);
43 gboolean new_ = MONO_GC_HANDLE_IS_OBJECT_POINTER (new_value);
44 gboolean track = handle_type == HANDLE_WEAK_TRACK;
46 if (!MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type))
50 binary_protocol_dislink_add (link, MONO_GC_REVEAL_POINTER (new_value, TRUE), track);
51 else if (old && !new_)
52 binary_protocol_dislink_remove (link, track);
53 else if (old && new_ && old_value != new_value)
54 binary_protocol_dislink_update (link, MONO_GC_REVEAL_POINTER (new_value, TRUE), track);
57 /* Returns the new value in the slot, or NULL if the CAS failed. */
58 static inline gpointer
59 try_set_slot (volatile gpointer *slot, GCObject *obj, gpointer old, GCHandleType type)
63 new_ = MONO_GC_HANDLE_OBJECT_POINTER (obj, GC_HANDLE_TYPE_IS_WEAK (type));
65 new_ = MONO_GC_HANDLE_METADATA_POINTER (sgen_client_default_metadata (), GC_HANDLE_TYPE_IS_WEAK (type));
66 SGEN_ASSERT (0, new_, "Why is the occupied bit not set?");
67 if (InterlockedCompareExchangePointer (slot, new_, old) == old) {
68 protocol_gchandle_update (type, (gpointer)slot, old, new_);
74 static inline gboolean
75 is_slot_set (volatile gpointer *slot)
77 gpointer entry = *slot;
78 if (MONO_GC_HANDLE_OCCUPIED (entry))
83 /* Try to claim a slot by setting its occupied bit. */
84 static inline gboolean
85 try_occupy_slot (volatile gpointer *slot, gpointer obj, int data)
87 if (is_slot_set (slot))
89 return try_set_slot (slot, (GCObject *)obj, NULL, (GCHandleType)data) != NULL;
93 bucket_alloc_callback (gpointer *bucket, guint32 new_bucket_size, gboolean alloc)
96 sgen_register_root ((char *)bucket, new_bucket_size, SGEN_DESCRIPTOR_NULL, ROOT_TYPE_PINNED, MONO_ROOT_SOURCE_GC_HANDLE, "pinned gc handles");
98 sgen_deregister_root ((char *)bucket);
101 static HandleData gc_handles [] = {
102 { SGEN_ARRAY_LIST_INIT (NULL, is_slot_set, try_occupy_slot, -1), (HANDLE_WEAK) },
103 { SGEN_ARRAY_LIST_INIT (NULL, is_slot_set, try_occupy_slot, -1), (HANDLE_WEAK_TRACK) },
104 { SGEN_ARRAY_LIST_INIT (NULL, is_slot_set, try_occupy_slot, -1), (HANDLE_NORMAL) },
105 { SGEN_ARRAY_LIST_INIT (bucket_alloc_callback, is_slot_set, try_occupy_slot, -1), (HANDLE_PINNED) }
109 gc_handles_for_type (GCHandleType type)
111 return type < HANDLE_TYPE_MAX ? &gc_handles [type] : NULL;
114 /* This assumes that the world is stopped. */
116 sgen_mark_normal_gc_handles (void *addr, SgenUserMarkFunc mark_func, void *gc_data)
118 HandleData *handles = gc_handles_for_type (HANDLE_NORMAL);
119 SgenArrayList *array = &handles->entries_array;
120 volatile gpointer *slot;
121 gpointer hidden, revealed;
123 SGEN_ARRAY_LIST_FOREACH_SLOT (array, slot) {
125 revealed = MONO_GC_REVEAL_POINTER (hidden, FALSE);
126 if (!MONO_GC_HANDLE_IS_OBJECT_POINTER (hidden))
128 mark_func ((MonoObject **)&revealed, gc_data);
130 *slot = MONO_GC_HANDLE_OBJECT_POINTER (revealed, FALSE);
131 } SGEN_ARRAY_LIST_END_FOREACH_SLOT;
136 alloc_handle (HandleData *handles, GCObject *obj, gboolean track)
139 SgenArrayList *array = &handles->entries_array;
142 * If a GC happens shortly after a new bucket is allocated, the entire
143 * bucket could be scanned even though it's mostly empty. To avoid this,
144 * we track the maximum index seen so far, so that we can skip the empty
147 * Note that we update `next_slot` before we even try occupying the
148 * slot. If we did it the other way around and a GC happened in
149 * between, the GC wouldn't know that the slot was occupied. This is
150 * not a huge deal since `obj` is on the stack and thus pinned anyway,
151 * but hopefully some day it won't be anymore.
153 index = sgen_array_list_add (array, obj, handles->type, TRUE);
154 #ifdef HEAVY_STATISTICS
155 InterlockedIncrement ((volatile gint32 *)&stat_gc_handles_allocated);
156 if (stat_gc_handles_allocated > stat_gc_handles_max_allocated)
157 stat_gc_handles_max_allocated = stat_gc_handles_allocated;
159 /* Ensure that a GC handle cannot be given to another thread without the slot having been set. */
160 mono_memory_write_barrier ();
161 res = MONO_GC_HANDLE (index, handles->type);
162 sgen_client_gchandle_created (handles->type, obj, res);
167 object_older_than (GCObject *object, int generation)
169 return generation == GENERATION_NURSERY && !sgen_ptr_in_nursery (object);
173 * Maps a function over all GC handles.
174 * This assumes that the world is stopped!
177 sgen_gchandle_iterate (GCHandleType handle_type, int max_generation, SgenGCHandleIterateCallback callback, gpointer user)
179 HandleData *handle_data = gc_handles_for_type (handle_type);
180 SgenArrayList *array = &handle_data->entries_array;
181 gpointer hidden, result, occupied;
182 volatile gpointer *slot;
184 /* If a new bucket has been allocated, but the capacity has not yet been
185 * increased, nothing can yet have been allocated in the bucket because the
186 * world is stopped, so we shouldn't miss any handles during iteration.
188 SGEN_ARRAY_LIST_FOREACH_SLOT (array, slot) {
190 occupied = (gpointer) MONO_GC_HANDLE_OCCUPIED (hidden);
191 g_assert (hidden ? !!occupied : !occupied);
194 result = callback (hidden, handle_type, max_generation, user);
196 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (result), "Why did the callback return an unoccupied entry?");
198 HEAVY_STAT (InterlockedDecrement ((volatile gint32 *)&stat_gc_handles_allocated));
199 protocol_gchandle_update (handle_type, (gpointer)slot, hidden, result);
201 } SGEN_ARRAY_LIST_END_FOREACH_SLOT;
206 * @obj: managed object to get a handle for
207 * @pinned: whether the object should be pinned
209 * This returns a handle that wraps the object, this is used to keep a
210 * reference to a managed object from the unmanaged world and preventing the
211 * object from being disposed.
213 * If @pinned is false the address of the object can not be obtained, if it is
214 * true the address of the object can be obtained. This will also pin the
215 * object so it will not be possible by a moving garbage collector to move the
218 * Returns: a handle that can be used to access the object from
222 mono_gchandle_new (GCObject *obj, gboolean pinned)
224 return alloc_handle (gc_handles_for_type (pinned ? HANDLE_PINNED : HANDLE_NORMAL), obj, FALSE);
228 * mono_gchandle_new_weakref:
229 * @obj: managed object to get a handle for
230 * @track_resurrection: Determines how long to track the object, if this is set to TRUE, the object is tracked after finalization, if FALSE, the object is only tracked up until the point of finalization.
232 * This returns a weak handle that wraps the object, this is used to
233 * keep a reference to a managed object from the unmanaged world.
234 * Unlike the mono_gchandle_new the object can be reclaimed by the
235 * garbage collector. In this case the value of the GCHandle will be
238 * If @track_resurrection is TRUE the object will be tracked through
239 * finalization and if the object is resurrected during the execution
240 * of the finalizer, then the returned weakref will continue to hold
241 * a reference to the object. If @track_resurrection is FALSE, then
242 * the weak reference's target will become NULL as soon as the object
243 * is passed on to the finalizer.
245 * Returns: a handle that can be used to access the object from
249 mono_gchandle_new_weakref (GCObject *obj, gboolean track_resurrection)
251 return alloc_handle (gc_handles_for_type (track_resurrection ? HANDLE_WEAK_TRACK : HANDLE_WEAK), obj, track_resurrection);
255 link_get (volatile gpointer *link_addr, gboolean is_weak)
257 void *volatile *link_addr_volatile;
261 link_addr_volatile = link_addr;
262 ptr = (void*)*link_addr_volatile;
264 * At this point we have a hidden pointer. If the GC runs
265 * here, it will not recognize the hidden pointer as a
266 * reference, and if the object behind it is not referenced
267 * elsewhere, it will be freed. Once the world is restarted
268 * we reveal the pointer, giving us a pointer to a freed
269 * object. To make sure we don't return it, we load the
270 * hidden pointer again. If it's still the same, we can be
271 * sure the object reference is valid.
273 if (ptr && MONO_GC_HANDLE_IS_OBJECT_POINTER (ptr))
274 obj = (GCObject *)MONO_GC_REVEAL_POINTER (ptr, is_weak);
280 * If a GC happens here, obj needs to be on the stack or in a
281 * register, so we need to prevent this from being reordered
284 sgen_dummy_use (obj);
285 mono_memory_barrier ();
288 sgen_client_ensure_weak_gchandles_accessible ();
290 if ((void*)*link_addr_volatile != ptr)
297 * mono_gchandle_get_target:
298 * @gchandle: a GCHandle's handle.
300 * The handle was previously created by calling `mono_gchandle_new` or
301 * `mono_gchandle_new_weakref`.
303 * Returns a pointer to the `MonoObject*` represented by the handle or
304 * NULL for a collected object if using a weakref handle.
307 mono_gchandle_get_target (guint32 gchandle)
309 guint index = MONO_GC_HANDLE_SLOT (gchandle);
310 GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
311 HandleData *handles = gc_handles_for_type (type);
312 /* Invalid handles are possible; accessing one should produce NULL. (#34276) */
315 return link_get (sgen_array_list_get_slot (&handles->entries_array, index), MONO_GC_HANDLE_TYPE_IS_WEAK (type));
319 sgen_gchandle_set_target (guint32 gchandle, GCObject *obj)
321 guint32 index = MONO_GC_HANDLE_SLOT (gchandle);
322 GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
323 HandleData *handles = gc_handles_for_type (type);
324 volatile gpointer *slot;
330 slot = sgen_array_list_get_slot (&handles->entries_array, index);
334 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (entry), "Why are we setting the target on an unoccupied slot?");
335 } while (!try_set_slot (slot, obj, entry, (GCHandleType)handles->type));
339 mono_gchandle_slot_metadata (volatile gpointer *slot, gboolean is_weak)
345 if (!MONO_GC_HANDLE_OCCUPIED (entry))
347 if (MONO_GC_HANDLE_IS_OBJECT_POINTER (entry)) {
348 GCObject *obj = (GCObject *)MONO_GC_REVEAL_POINTER (entry, is_weak);
349 /* See note [dummy use]. */
350 sgen_dummy_use (obj);
352 * FIXME: The compiler could technically not carry a reference to obj around
353 * at this point and recompute it later, in which case we would still use
358 return sgen_client_metadata_for_object (obj);
360 metadata = MONO_GC_REVEAL_POINTER (entry, is_weak);
361 /* See note [dummy use]. */
362 sgen_dummy_use (metadata);
369 sgen_gchandle_get_metadata (guint32 gchandle)
371 guint32 index = MONO_GC_HANDLE_SLOT (gchandle);
372 GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
373 HandleData *handles = gc_handles_for_type (type);
374 volatile gpointer *slot;
378 if (index >= handles->entries_array.capacity)
381 slot = sgen_array_list_get_slot (&handles->entries_array, index);
383 return mono_gchandle_slot_metadata (slot, MONO_GC_HANDLE_TYPE_IS_WEAK (type));
387 * mono_gchandle_free:
388 * @gchandle: a GCHandle's handle.
390 * Frees the @gchandle handle. If there are no outstanding
391 * references, the garbage collector can reclaim the memory of the
395 mono_gchandle_free (guint32 gchandle)
397 guint32 index = MONO_GC_HANDLE_SLOT (gchandle);
398 GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
399 HandleData *handles = gc_handles_for_type (type);
400 volatile gpointer *slot;
405 slot = sgen_array_list_get_slot (&handles->entries_array, index);
408 if (index < handles->entries_array.capacity && MONO_GC_HANDLE_OCCUPIED (entry)) {
410 protocol_gchandle_update (handles->type, (gpointer)slot, entry, NULL);
411 HEAVY_STAT (InterlockedDecrement ((volatile gint32 *)&stat_gc_handles_allocated));
413 /* print a warning? */
415 sgen_client_gchandle_destroyed (handles->type, gchandle);
419 * Returns whether to remove the link from its hash.
422 null_link_if_necessary (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
424 const gboolean is_weak = GC_HANDLE_TYPE_IS_WEAK (handle_type);
425 ScanCopyContext *ctx = (ScanCopyContext *)user;
429 if (!MONO_GC_HANDLE_VALID (hidden))
432 obj = (GCObject *)MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
433 SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
435 if (object_older_than (obj, max_generation))
438 if (major_collector.is_object_live (obj))
441 /* Clear link if object is ready for finalization. This check may be redundant wrt is_object_live(). */
442 if (sgen_gc_is_object_ready_for_finalization (obj))
443 return MONO_GC_HANDLE_METADATA_POINTER (sgen_client_metadata_for_object (obj), is_weak);
446 ctx->ops->copy_or_mark_object (©, ctx->queue);
447 SGEN_ASSERT (0, copy, "Why couldn't we copy the object?");
448 /* Update link if object was moved. */
449 return MONO_GC_HANDLE_OBJECT_POINTER (copy, is_weak);
452 /* LOCKING: requires that the GC lock is held */
454 sgen_null_link_in_range (int generation, ScanCopyContext ctx, gboolean track)
456 sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if_necessary, &ctx);
460 SgenObjectPredicateFunc predicate;
462 } WeakLinkAlivePredicateClosure;
465 null_link_if (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
467 WeakLinkAlivePredicateClosure *closure = (WeakLinkAlivePredicateClosure *)user;
470 if (!MONO_GC_HANDLE_VALID (hidden))
473 obj = (GCObject *)MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
474 SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
476 if (object_older_than (obj, max_generation))
479 if (closure->predicate (obj, closure->data))
485 /* LOCKING: requires that the GC lock is held */
487 sgen_null_links_if (SgenObjectPredicateFunc predicate, void *data, int generation, gboolean track)
489 WeakLinkAlivePredicateClosure closure = { predicate, data };
490 sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if, &closure);
494 sgen_init_gchandles (void)
496 #ifdef HEAVY_STATISTICS
497 mono_counters_register ("GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_UINT, (void *)&stat_gc_handles_allocated);
498 mono_counters_register ("max GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_UINT, (void *)&stat_gc_handles_max_allocated);