5 * Copyright (C) 2015 Xamarin Inc
7 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
13 #include "mono/sgen/sgen-gc.h"
14 #include "mono/sgen/sgen-client.h"
15 #include "mono/sgen/sgen-array-list.h"
16 #include "mono/utils/mono-membar.h"
18 #ifdef HEAVY_STATISTICS
19 static volatile guint32 stat_gc_handles_allocated = 0;
20 static volatile guint32 stat_gc_handles_max_allocated = 0;
24 * A table of GC handle data, implementing a simple lock-free bitmap allocator.
26 * Each entry in a bucket is a pointer with two tag bits: if
27 * 'GC_HANDLE_OCCUPIED' returns true for a slot, then the slot is occupied; if
28 * so, then 'GC_HANDLE_VALID' gives whether the entry refers to a valid (1) or
29 * NULL (0) object reference. If the reference is valid, then the pointer is an
30 * object pointer. If the reference is NULL, and 'GC_HANDLE_TYPE_IS_WEAK' is
31 * true for 'type', then the pointer is a metadata pointer--this allows us to
32 * retrieve the domain ID of an expired weak reference in Mono.
36 SgenArrayList entries_array;
41 protocol_gchandle_update (int handle_type, gpointer link, gpointer old_value, gpointer new_value)
43 gboolean old = MONO_GC_HANDLE_IS_OBJECT_POINTER (old_value);
44 gboolean new_ = MONO_GC_HANDLE_IS_OBJECT_POINTER (new_value);
45 gboolean track = handle_type == HANDLE_WEAK_TRACK;
47 if (!MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type))
51 binary_protocol_dislink_add (link, MONO_GC_REVEAL_POINTER (new_value, TRUE), track);
52 else if (old && !new_)
53 binary_protocol_dislink_remove (link, track);
54 else if (old && new_ && old_value != new_value)
55 binary_protocol_dislink_update (link, MONO_GC_REVEAL_POINTER (new_value, TRUE), track);
58 /* Returns the new value in the slot, or NULL if the CAS failed. */
59 static inline gpointer
60 try_set_slot (volatile gpointer *slot, GCObject *obj, gpointer old, GCHandleType type)
64 new_ = MONO_GC_HANDLE_OBJECT_POINTER (obj, GC_HANDLE_TYPE_IS_WEAK (type));
66 new_ = MONO_GC_HANDLE_METADATA_POINTER (sgen_client_default_metadata (), GC_HANDLE_TYPE_IS_WEAK (type));
67 SGEN_ASSERT (0, new_, "Why is the occupied bit not set?");
68 if (InterlockedCompareExchangePointer (slot, new_, old) == old) {
69 protocol_gchandle_update (type, (gpointer)slot, old, new_);
75 static inline gboolean
76 is_slot_set (volatile gpointer *slot)
78 gpointer entry = *slot;
79 if (MONO_GC_HANDLE_OCCUPIED (entry))
84 /* Try to claim a slot by setting its occupied bit. */
85 static inline gboolean
86 try_occupy_slot (volatile gpointer *slot, gpointer obj, int data)
88 if (is_slot_set (slot))
90 return try_set_slot (slot, (GCObject *)obj, NULL, (GCHandleType)data) != NULL;
94 bucket_alloc_callback (gpointer *bucket, guint32 new_bucket_size, gboolean alloc)
97 sgen_register_root ((char *)bucket, new_bucket_size, SGEN_DESCRIPTOR_NULL, ROOT_TYPE_PINNED, MONO_ROOT_SOURCE_GC_HANDLE, "pinned gc handles");
99 sgen_deregister_root ((char *)bucket);
102 static HandleData gc_handles [] = {
103 { SGEN_ARRAY_LIST_INIT (NULL, is_slot_set, try_occupy_slot, -1), (HANDLE_WEAK) },
104 { SGEN_ARRAY_LIST_INIT (NULL, is_slot_set, try_occupy_slot, -1), (HANDLE_WEAK_TRACK) },
105 { SGEN_ARRAY_LIST_INIT (NULL, is_slot_set, try_occupy_slot, -1), (HANDLE_NORMAL) },
106 { SGEN_ARRAY_LIST_INIT (bucket_alloc_callback, is_slot_set, try_occupy_slot, -1), (HANDLE_PINNED) }
110 gc_handles_for_type (GCHandleType type)
112 return type < HANDLE_TYPE_MAX ? &gc_handles [type] : NULL;
115 /* This assumes that the world is stopped. */
117 sgen_mark_normal_gc_handles (void *addr, SgenUserMarkFunc mark_func, void *gc_data)
119 HandleData *handles = gc_handles_for_type (HANDLE_NORMAL);
120 SgenArrayList *array = &handles->entries_array;
121 volatile gpointer *slot;
122 gpointer hidden, revealed;
124 SGEN_ARRAY_LIST_FOREACH_SLOT (array, slot) {
126 revealed = MONO_GC_REVEAL_POINTER (hidden, FALSE);
127 if (!MONO_GC_HANDLE_IS_OBJECT_POINTER (hidden))
129 mark_func ((MonoObject **)&revealed, gc_data);
131 *slot = MONO_GC_HANDLE_OBJECT_POINTER (revealed, FALSE);
132 } SGEN_ARRAY_LIST_END_FOREACH_SLOT;
137 alloc_handle (HandleData *handles, GCObject *obj, gboolean track)
140 SgenArrayList *array = &handles->entries_array;
143 * If a GC happens shortly after a new bucket is allocated, the entire
144 * bucket could be scanned even though it's mostly empty. To avoid this,
145 * we track the maximum index seen so far, so that we can skip the empty
148 * Note that we update `next_slot` before we even try occupying the
149 * slot. If we did it the other way around and a GC happened in
150 * between, the GC wouldn't know that the slot was occupied. This is
151 * not a huge deal since `obj` is on the stack and thus pinned anyway,
152 * but hopefully some day it won't be anymore.
154 index = sgen_array_list_add (array, obj, handles->type, TRUE);
155 #ifdef HEAVY_STATISTICS
156 InterlockedIncrement ((volatile gint32 *)&stat_gc_handles_allocated);
157 if (stat_gc_handles_allocated > stat_gc_handles_max_allocated)
158 stat_gc_handles_max_allocated = stat_gc_handles_allocated;
160 /* Ensure that a GC handle cannot be given to another thread without the slot having been set. */
161 mono_memory_write_barrier ();
162 res = MONO_GC_HANDLE (index, handles->type);
163 sgen_client_gchandle_created (handles->type, obj, res);
168 object_older_than (GCObject *object, int generation)
170 return generation == GENERATION_NURSERY && !sgen_ptr_in_nursery (object);
174 * Maps a function over all GC handles.
175 * This assumes that the world is stopped!
178 sgen_gchandle_iterate (GCHandleType handle_type, int max_generation, SgenGCHandleIterateCallback callback, gpointer user)
180 HandleData *handle_data = gc_handles_for_type (handle_type);
181 SgenArrayList *array = &handle_data->entries_array;
182 gpointer hidden, result, occupied;
183 volatile gpointer *slot;
185 /* If a new bucket has been allocated, but the capacity has not yet been
186 * increased, nothing can yet have been allocated in the bucket because the
187 * world is stopped, so we shouldn't miss any handles during iteration.
189 SGEN_ARRAY_LIST_FOREACH_SLOT (array, slot) {
191 occupied = (gpointer) MONO_GC_HANDLE_OCCUPIED (hidden);
192 g_assert (hidden ? !!occupied : !occupied);
195 result = callback (hidden, handle_type, max_generation, user);
197 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (result), "Why did the callback return an unoccupied entry?");
199 HEAVY_STAT (InterlockedDecrement ((volatile gint32 *)&stat_gc_handles_allocated));
200 protocol_gchandle_update (handle_type, (gpointer)slot, hidden, result);
202 } SGEN_ARRAY_LIST_END_FOREACH_SLOT;
207 * \param obj managed object to get a handle for
208 * \param 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 \p 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 unmanaged code.
221 mono_gchandle_new (GCObject *obj, gboolean pinned)
223 return alloc_handle (gc_handles_for_type (pinned ? HANDLE_PINNED : HANDLE_NORMAL), obj, FALSE);
227 * mono_gchandle_new_weakref:
228 * \param obj managed object to get a handle for
229 * \param 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.
231 * This returns a weak handle that wraps the object, this is used to
232 * keep a reference to a managed object from the unmanaged world.
233 * Unlike the \c mono_gchandle_new the object can be reclaimed by the
234 * garbage collector. In this case the value of the GCHandle will be
237 * If \p track_resurrection is TRUE the object will be tracked through
238 * finalization and if the object is resurrected during the execution
239 * of the finalizer, then the returned weakref will continue to hold
240 * a reference to the object. If \p track_resurrection is FALSE, then
241 * the weak reference's target will become NULL as soon as the object
242 * is passed on to the finalizer.
244 * \returns a handle that can be used to access the object from
248 mono_gchandle_new_weakref (GCObject *obj, gboolean track_resurrection)
250 return alloc_handle (gc_handles_for_type (track_resurrection ? HANDLE_WEAK_TRACK : HANDLE_WEAK), obj, track_resurrection);
254 link_get (volatile gpointer *link_addr, gboolean is_weak)
256 void *volatile *link_addr_volatile;
260 link_addr_volatile = link_addr;
261 ptr = (void*)*link_addr_volatile;
263 * At this point we have a hidden pointer. If the GC runs
264 * here, it will not recognize the hidden pointer as a
265 * reference, and if the object behind it is not referenced
266 * elsewhere, it will be freed. Once the world is restarted
267 * we reveal the pointer, giving us a pointer to a freed
268 * object. To make sure we don't return it, we load the
269 * hidden pointer again. If it's still the same, we can be
270 * sure the object reference is valid.
272 if (ptr && MONO_GC_HANDLE_IS_OBJECT_POINTER (ptr))
273 obj = (GCObject *)MONO_GC_REVEAL_POINTER (ptr, is_weak);
279 * If a GC happens here, obj needs to be on the stack or in a
280 * register, so we need to prevent this from being reordered
283 sgen_dummy_use (obj);
284 mono_memory_barrier ();
287 sgen_client_ensure_weak_gchandles_accessible ();
289 if ((void*)*link_addr_volatile != ptr)
296 * mono_gchandle_get_target:
297 * \param gchandle a GCHandle's handle.
299 * The handle was previously created by calling \c mono_gchandle_new or
300 * \c mono_gchandle_new_weakref.
302 * \returns a pointer to the \c MonoObject* represented by the handle or
303 * NULL for a collected object if using a weakref handle.
306 mono_gchandle_get_target (guint32 gchandle)
308 guint index = MONO_GC_HANDLE_SLOT (gchandle);
309 GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
310 HandleData *handles = gc_handles_for_type (type);
311 /* Invalid handles are possible; accessing one should produce NULL. (#34276) */
314 return link_get (sgen_array_list_get_slot (&handles->entries_array, index), MONO_GC_HANDLE_TYPE_IS_WEAK (type));
318 sgen_gchandle_set_target (guint32 gchandle, GCObject *obj)
320 guint32 index = MONO_GC_HANDLE_SLOT (gchandle);
321 GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
322 HandleData *handles = gc_handles_for_type (type);
323 volatile gpointer *slot;
329 slot = sgen_array_list_get_slot (&handles->entries_array, index);
333 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (entry), "Why are we setting the target on an unoccupied slot?");
334 } while (!try_set_slot (slot, obj, entry, (GCHandleType)handles->type));
338 mono_gchandle_slot_metadata (volatile gpointer *slot, gboolean is_weak)
344 if (!MONO_GC_HANDLE_OCCUPIED (entry))
346 if (MONO_GC_HANDLE_IS_OBJECT_POINTER (entry)) {
347 GCObject *obj = (GCObject *)MONO_GC_REVEAL_POINTER (entry, is_weak);
348 /* See note [dummy use]. */
349 sgen_dummy_use (obj);
351 * FIXME: The compiler could technically not carry a reference to obj around
352 * at this point and recompute it later, in which case we would still use
357 return sgen_client_metadata_for_object (obj);
359 metadata = MONO_GC_REVEAL_POINTER (entry, is_weak);
360 /* See note [dummy use]. */
361 sgen_dummy_use (metadata);
368 sgen_gchandle_get_metadata (guint32 gchandle)
370 guint32 index = MONO_GC_HANDLE_SLOT (gchandle);
371 GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
372 HandleData *handles = gc_handles_for_type (type);
373 volatile gpointer *slot;
377 if (index >= handles->entries_array.capacity)
380 slot = sgen_array_list_get_slot (&handles->entries_array, index);
382 return mono_gchandle_slot_metadata (slot, MONO_GC_HANDLE_TYPE_IS_WEAK (type));
386 * mono_gchandle_free:
387 * \param gchandle a GCHandle's handle.
389 * Frees the \p gchandle handle. If there are no outstanding
390 * references, the garbage collector can reclaim the memory of the
394 mono_gchandle_free (guint32 gchandle)
396 guint32 index = MONO_GC_HANDLE_SLOT (gchandle);
397 GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
398 HandleData *handles = gc_handles_for_type (type);
399 volatile gpointer *slot;
404 slot = sgen_array_list_get_slot (&handles->entries_array, index);
407 if (index < handles->entries_array.capacity && MONO_GC_HANDLE_OCCUPIED (entry)) {
409 protocol_gchandle_update (handles->type, (gpointer)slot, entry, NULL);
410 HEAVY_STAT (InterlockedDecrement ((volatile gint32 *)&stat_gc_handles_allocated));
412 /* print a warning? */
414 sgen_client_gchandle_destroyed (handles->type, gchandle);
418 * Returns whether to remove the link from its hash.
421 null_link_if_necessary (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
423 const gboolean is_weak = GC_HANDLE_TYPE_IS_WEAK (handle_type);
424 ScanCopyContext *ctx = (ScanCopyContext *)user;
428 if (!MONO_GC_HANDLE_VALID (hidden))
431 obj = (GCObject *)MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
432 SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
434 if (object_older_than (obj, max_generation))
437 if (major_collector.is_object_live (obj))
440 /* Clear link if object is ready for finalization. This check may be redundant wrt is_object_live(). */
441 if (sgen_gc_is_object_ready_for_finalization (obj))
442 return MONO_GC_HANDLE_METADATA_POINTER (sgen_client_metadata_for_object (obj), is_weak);
445 ctx->ops->copy_or_mark_object (©, ctx->queue);
446 SGEN_ASSERT (0, copy, "Why couldn't we copy the object?");
447 /* Update link if object was moved. */
448 return MONO_GC_HANDLE_OBJECT_POINTER (copy, is_weak);
451 /* LOCKING: requires that the GC lock is held */
453 sgen_null_link_in_range (int generation, ScanCopyContext ctx, gboolean track)
455 sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if_necessary, &ctx);
459 SgenObjectPredicateFunc predicate;
461 } WeakLinkAlivePredicateClosure;
464 null_link_if (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
466 WeakLinkAlivePredicateClosure *closure = (WeakLinkAlivePredicateClosure *)user;
469 if (!MONO_GC_HANDLE_VALID (hidden))
472 obj = (GCObject *)MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
473 SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
475 if (object_older_than (obj, max_generation))
478 if (closure->predicate (obj, closure->data))
479 return MONO_GC_HANDLE_METADATA_POINTER (sgen_client_default_metadata (), GC_HANDLE_TYPE_IS_WEAK (handle_type));
484 /* LOCKING: requires that the GC lock is held */
486 sgen_null_links_if (SgenObjectPredicateFunc predicate, void *data, int generation, gboolean track)
488 WeakLinkAlivePredicateClosure closure = { predicate, data };
489 sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if, &closure);
493 sgen_init_gchandles (void)
495 #ifdef HEAVY_STATISTICS
496 mono_counters_register ("GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_UINT, (void *)&stat_gc_handles_allocated);
497 mono_counters_register ("max GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_UINT, (void *)&stat_gc_handles_max_allocated);