2 * sgen-gchandles.c: SGen GC handles.
4 * Copyright (C) 2015 Xamarin Inc
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License 2.0 as published by the Free Software Foundation;
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License 2.0 along with this library; if not, write to the Free
17 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 #include "mono/sgen/sgen-gc.h"
24 #include "mono/sgen/sgen-client.h"
25 #include "mono/sgen/sgen-array-list.h"
26 #include "mono/utils/mono-membar.h"
28 #ifdef HEAVY_STATISTICS
29 static volatile guint32 stat_gc_handles_allocated = 0;
30 static volatile guint32 stat_gc_handles_max_allocated = 0;
34 * A table of GC handle data, implementing a simple lock-free bitmap allocator.
36 * Each entry in a bucket is a pointer with two tag bits: if
37 * 'GC_HANDLE_OCCUPIED' returns true for a slot, then the slot is occupied; if
38 * so, then 'GC_HANDLE_VALID' gives whether the entry refers to a valid (1) or
39 * NULL (0) object reference. If the reference is valid, then the pointer is an
40 * object pointer. If the reference is NULL, and 'GC_HANDLE_TYPE_IS_WEAK' is
41 * true for 'type', then the pointer is a metadata pointer--this allows us to
42 * retrieve the domain ID of an expired weak reference in Mono.
46 SgenArrayList entries_array;
51 protocol_gchandle_update (int handle_type, gpointer link, gpointer old_value, gpointer new_value)
53 gboolean old = MONO_GC_HANDLE_IS_OBJECT_POINTER (old_value);
54 gboolean new_ = MONO_GC_HANDLE_IS_OBJECT_POINTER (new_value);
55 gboolean track = handle_type == HANDLE_WEAK_TRACK;
57 if (!MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type))
61 binary_protocol_dislink_add (link, MONO_GC_REVEAL_POINTER (new_value, TRUE), track);
62 else if (old && !new_)
63 binary_protocol_dislink_remove (link, track);
64 else if (old && new_ && old_value != new_value)
65 binary_protocol_dislink_update (link, MONO_GC_REVEAL_POINTER (new_value, TRUE), track);
68 /* Returns the new value in the slot, or NULL if the CAS failed. */
69 static inline gpointer
70 try_set_slot (volatile gpointer *slot, GCObject *obj, gpointer old, GCHandleType type)
74 new_ = MONO_GC_HANDLE_OBJECT_POINTER (obj, GC_HANDLE_TYPE_IS_WEAK (type));
76 new_ = MONO_GC_HANDLE_METADATA_POINTER (sgen_client_default_metadata (), GC_HANDLE_TYPE_IS_WEAK (type));
77 SGEN_ASSERT (0, new_, "Why is the occupied bit not set?");
78 if (InterlockedCompareExchangePointer (slot, new_, old) == old) {
79 protocol_gchandle_update (type, (gpointer)slot, old, new_);
85 static inline gboolean
86 is_slot_set (volatile gpointer *slot)
88 gpointer entry = *slot;
89 if (MONO_GC_HANDLE_OCCUPIED (entry))
94 /* Try to claim a slot by setting its occupied bit. */
95 static inline gboolean
96 try_occupy_slot (volatile gpointer *slot, gpointer obj, int data)
98 if (is_slot_set (slot))
100 return try_set_slot (slot, (GCObject *)obj, NULL, (GCHandleType)data) != NULL;
104 bucket_alloc_callback (gpointer *bucket, guint32 new_bucket_size, gboolean alloc)
107 sgen_register_root ((char *)bucket, new_bucket_size, SGEN_DESCRIPTOR_NULL, ROOT_TYPE_PINNED, MONO_ROOT_SOURCE_GC_HANDLE, "pinned gc handles");
109 sgen_deregister_root ((char *)bucket);
112 static HandleData gc_handles [] = {
113 { SGEN_ARRAY_LIST_INIT (NULL, is_slot_set, try_occupy_slot, -1), (HANDLE_WEAK) },
114 { SGEN_ARRAY_LIST_INIT (NULL, is_slot_set, try_occupy_slot, -1), (HANDLE_WEAK_TRACK) },
115 { SGEN_ARRAY_LIST_INIT (NULL, is_slot_set, try_occupy_slot, -1), (HANDLE_NORMAL) },
116 { SGEN_ARRAY_LIST_INIT (bucket_alloc_callback, is_slot_set, try_occupy_slot, -1), (HANDLE_PINNED) }
120 gc_handles_for_type (GCHandleType type)
122 return type < HANDLE_TYPE_MAX ? &gc_handles [type] : NULL;
125 /* This assumes that the world is stopped. */
127 sgen_mark_normal_gc_handles (void *addr, SgenUserMarkFunc mark_func, void *gc_data)
129 HandleData *handles = gc_handles_for_type (HANDLE_NORMAL);
130 SgenArrayList *array = &handles->entries_array;
131 volatile gpointer *slot;
132 gpointer hidden, revealed;
134 SGEN_ARRAY_LIST_FOREACH_SLOT (array, slot) {
136 revealed = MONO_GC_REVEAL_POINTER (hidden, FALSE);
137 if (!MONO_GC_HANDLE_IS_OBJECT_POINTER (hidden))
139 mark_func ((MonoObject **)&revealed, gc_data);
141 *slot = MONO_GC_HANDLE_OBJECT_POINTER (revealed, FALSE);
142 } SGEN_ARRAY_LIST_END_FOREACH_SLOT;
147 alloc_handle (HandleData *handles, GCObject *obj, gboolean track)
150 SgenArrayList *array = &handles->entries_array;
153 * If a GC happens shortly after a new bucket is allocated, the entire
154 * bucket could be scanned even though it's mostly empty. To avoid this,
155 * we track the maximum index seen so far, so that we can skip the empty
158 * Note that we update `next_slot` before we even try occupying the
159 * slot. If we did it the other way around and a GC happened in
160 * between, the GC wouldn't know that the slot was occupied. This is
161 * not a huge deal since `obj` is on the stack and thus pinned anyway,
162 * but hopefully some day it won't be anymore.
164 index = sgen_array_list_add (array, obj, handles->type, TRUE);
165 #ifdef HEAVY_STATISTICS
166 InterlockedIncrement ((volatile gint32 *)&stat_gc_handles_allocated);
167 if (stat_gc_handles_allocated > stat_gc_handles_max_allocated)
168 stat_gc_handles_max_allocated = stat_gc_handles_allocated;
170 /* Ensure that a GC handle cannot be given to another thread without the slot having been set. */
171 mono_memory_write_barrier ();
172 res = MONO_GC_HANDLE (index, handles->type);
173 sgen_client_gchandle_created (handles->type, obj, res);
178 object_older_than (GCObject *object, int generation)
180 return generation == GENERATION_NURSERY && !sgen_ptr_in_nursery (object);
184 * Maps a function over all GC handles.
185 * This assumes that the world is stopped!
188 sgen_gchandle_iterate (GCHandleType handle_type, int max_generation, SgenGCHandleIterateCallback callback, gpointer user)
190 HandleData *handle_data = gc_handles_for_type (handle_type);
191 SgenArrayList *array = &handle_data->entries_array;
192 gpointer hidden, result, occupied;
193 volatile gpointer *slot;
195 /* If a new bucket has been allocated, but the capacity has not yet been
196 * increased, nothing can yet have been allocated in the bucket because the
197 * world is stopped, so we shouldn't miss any handles during iteration.
199 SGEN_ARRAY_LIST_FOREACH_SLOT (array, slot) {
201 occupied = (gpointer) MONO_GC_HANDLE_OCCUPIED (hidden);
202 g_assert (hidden ? !!occupied : !occupied);
205 result = callback (hidden, handle_type, max_generation, user);
207 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (result), "Why did the callback return an unoccupied entry?");
209 HEAVY_STAT (InterlockedDecrement ((volatile gint32 *)&stat_gc_handles_allocated));
210 protocol_gchandle_update (handle_type, (gpointer)slot, hidden, result);
212 } SGEN_ARRAY_LIST_END_FOREACH_SLOT;
217 * @obj: managed object to get a handle for
218 * @pinned: whether the object should be pinned
220 * This returns a handle that wraps the object, this is used to keep a
221 * reference to a managed object from the unmanaged world and preventing the
222 * object from being disposed.
224 * If @pinned is false the address of the object can not be obtained, if it is
225 * true the address of the object can be obtained. This will also pin the
226 * object so it will not be possible by a moving garbage collector to move the
229 * Returns: a handle that can be used to access the object from
233 mono_gchandle_new (GCObject *obj, gboolean pinned)
235 return alloc_handle (gc_handles_for_type (pinned ? HANDLE_PINNED : HANDLE_NORMAL), obj, FALSE);
239 * mono_gchandle_new_weakref:
240 * @obj: managed object to get a handle for
241 * @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.
243 * This returns a weak handle that wraps the object, this is used to
244 * keep a reference to a managed object from the unmanaged world.
245 * Unlike the mono_gchandle_new the object can be reclaimed by the
246 * garbage collector. In this case the value of the GCHandle will be
249 * If @track_resurrection is TRUE the object will be tracked through
250 * finalization and if the object is resurrected during the execution
251 * of the finalizer, then the returned weakref will continue to hold
252 * a reference to the object. If @track_resurrection is FALSE, then
253 * the weak reference's target will become NULL as soon as the object
254 * is passed on to the finalizer.
256 * Returns: a handle that can be used to access the object from
260 mono_gchandle_new_weakref (GCObject *obj, gboolean track_resurrection)
262 return alloc_handle (gc_handles_for_type (track_resurrection ? HANDLE_WEAK_TRACK : HANDLE_WEAK), obj, track_resurrection);
266 link_get (volatile gpointer *link_addr, gboolean is_weak)
268 void *volatile *link_addr_volatile;
272 link_addr_volatile = link_addr;
273 ptr = (void*)*link_addr_volatile;
275 * At this point we have a hidden pointer. If the GC runs
276 * here, it will not recognize the hidden pointer as a
277 * reference, and if the object behind it is not referenced
278 * elsewhere, it will be freed. Once the world is restarted
279 * we reveal the pointer, giving us a pointer to a freed
280 * object. To make sure we don't return it, we load the
281 * hidden pointer again. If it's still the same, we can be
282 * sure the object reference is valid.
284 if (ptr && MONO_GC_HANDLE_IS_OBJECT_POINTER (ptr))
285 obj = (GCObject *)MONO_GC_REVEAL_POINTER (ptr, is_weak);
291 * If a GC happens here, obj needs to be on the stack or in a
292 * register, so we need to prevent this from being reordered
295 sgen_dummy_use (obj);
296 mono_memory_barrier ();
299 sgen_client_ensure_weak_gchandles_accessible ();
301 if ((void*)*link_addr_volatile != ptr)
308 * mono_gchandle_get_target:
309 * @gchandle: a GCHandle's handle.
311 * The handle was previously created by calling `mono_gchandle_new` or
312 * `mono_gchandle_new_weakref`.
314 * Returns a pointer to the `MonoObject*` represented by the handle or
315 * NULL for a collected object if using a weakref handle.
318 mono_gchandle_get_target (guint32 gchandle)
320 guint index = MONO_GC_HANDLE_SLOT (gchandle);
321 GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
322 HandleData *handles = gc_handles_for_type (type);
323 /* Invalid handles are possible; accessing one should produce NULL. (#34276) */
326 return link_get (sgen_array_list_get_slot (&handles->entries_array, index), MONO_GC_HANDLE_TYPE_IS_WEAK (type));
330 sgen_gchandle_set_target (guint32 gchandle, GCObject *obj)
332 guint32 index = MONO_GC_HANDLE_SLOT (gchandle);
333 GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
334 HandleData *handles = gc_handles_for_type (type);
335 volatile gpointer *slot;
341 slot = sgen_array_list_get_slot (&handles->entries_array, index);
345 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (entry), "Why are we setting the target on an unoccupied slot?");
346 } while (!try_set_slot (slot, obj, entry, (GCHandleType)handles->type));
350 mono_gchandle_slot_metadata (volatile gpointer *slot, gboolean is_weak)
356 if (!MONO_GC_HANDLE_OCCUPIED (entry))
358 if (MONO_GC_HANDLE_IS_OBJECT_POINTER (entry)) {
359 GCObject *obj = (GCObject *)MONO_GC_REVEAL_POINTER (entry, is_weak);
360 /* See note [dummy use]. */
361 sgen_dummy_use (obj);
363 * FIXME: The compiler could technically not carry a reference to obj around
364 * at this point and recompute it later, in which case we would still use
369 return sgen_client_metadata_for_object (obj);
371 metadata = MONO_GC_REVEAL_POINTER (entry, is_weak);
372 /* See note [dummy use]. */
373 sgen_dummy_use (metadata);
380 sgen_gchandle_get_metadata (guint32 gchandle)
382 guint32 index = MONO_GC_HANDLE_SLOT (gchandle);
383 GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
384 HandleData *handles = gc_handles_for_type (type);
385 volatile gpointer *slot;
389 if (index >= handles->entries_array.capacity)
392 slot = sgen_array_list_get_slot (&handles->entries_array, index);
394 return mono_gchandle_slot_metadata (slot, MONO_GC_HANDLE_TYPE_IS_WEAK (type));
398 * mono_gchandle_free:
399 * @gchandle: a GCHandle's handle.
401 * Frees the @gchandle handle. If there are no outstanding
402 * references, the garbage collector can reclaim the memory of the
406 mono_gchandle_free (guint32 gchandle)
408 guint32 index = MONO_GC_HANDLE_SLOT (gchandle);
409 GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
410 HandleData *handles = gc_handles_for_type (type);
411 volatile gpointer *slot;
416 slot = sgen_array_list_get_slot (&handles->entries_array, index);
419 if (index < handles->entries_array.capacity && MONO_GC_HANDLE_OCCUPIED (entry)) {
421 protocol_gchandle_update (handles->type, (gpointer)slot, entry, NULL);
422 HEAVY_STAT (InterlockedDecrement ((volatile gint32 *)&stat_gc_handles_allocated));
424 /* print a warning? */
426 sgen_client_gchandle_destroyed (handles->type, gchandle);
430 * Returns whether to remove the link from its hash.
433 null_link_if_necessary (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
435 const gboolean is_weak = GC_HANDLE_TYPE_IS_WEAK (handle_type);
436 ScanCopyContext *ctx = (ScanCopyContext *)user;
440 if (!MONO_GC_HANDLE_VALID (hidden))
443 obj = (GCObject *)MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
444 SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
446 if (object_older_than (obj, max_generation))
449 if (major_collector.is_object_live (obj))
452 /* Clear link if object is ready for finalization. This check may be redundant wrt is_object_live(). */
453 if (sgen_gc_is_object_ready_for_finalization (obj))
454 return MONO_GC_HANDLE_METADATA_POINTER (sgen_client_metadata_for_object (obj), is_weak);
457 ctx->ops->copy_or_mark_object (©, ctx->queue);
458 SGEN_ASSERT (0, copy, "Why couldn't we copy the object?");
459 /* Update link if object was moved. */
460 return MONO_GC_HANDLE_OBJECT_POINTER (copy, is_weak);
463 /* LOCKING: requires that the GC lock is held */
465 sgen_null_link_in_range (int generation, ScanCopyContext ctx, gboolean track)
467 sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if_necessary, &ctx);
471 SgenObjectPredicateFunc predicate;
473 } WeakLinkAlivePredicateClosure;
476 null_link_if (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
478 WeakLinkAlivePredicateClosure *closure = (WeakLinkAlivePredicateClosure *)user;
481 if (!MONO_GC_HANDLE_VALID (hidden))
484 obj = (GCObject *)MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
485 SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
487 if (object_older_than (obj, max_generation))
490 if (closure->predicate (obj, closure->data))
496 /* LOCKING: requires that the GC lock is held */
498 sgen_null_links_if (SgenObjectPredicateFunc predicate, void *data, int generation, gboolean track)
500 WeakLinkAlivePredicateClosure closure = { predicate, data };
501 sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if, &closure);
505 sgen_init_gchandles (void)
507 #ifdef HEAVY_STATISTICS
508 mono_counters_register ("GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_UINT, (void *)&stat_gc_handles_allocated);
509 mono_counters_register ("max GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_UINT, (void *)&stat_gc_handles_max_allocated);