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/utils/mono-membar.h"
27 #ifdef HEAVY_STATISTICS
28 static volatile guint64 stat_gc_handles_allocated = 0;
29 static volatile guint64 stat_gc_handles_max_allocated = 0;
32 #define BUCKETS (32 - MONO_GC_HANDLE_TYPE_SHIFT)
33 #define MIN_BUCKET_BITS (5)
34 #define MIN_BUCKET_SIZE (1 << MIN_BUCKET_BITS)
37 * A table of GC handle data, implementing a simple lock-free bitmap allocator.
39 * 'entries' is an array of pointers to buckets of increasing size. The first
40 * bucket has size 'MIN_BUCKET_SIZE', and each bucket is twice the size of the
43 * |-------|-- MIN_BUCKET_SIZE
45 * [1] -> xxxxxxxxxxxxxxxx
46 * [2] -> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
49 * The size of the spine, 'BUCKETS', is chosen so that the maximum number of
50 * entries is no less than the maximum index value of a GC handle.
52 * Each entry in a bucket is a pointer with two tag bits: if
53 * 'GC_HANDLE_OCCUPIED' returns true for a slot, then the slot is occupied; if
54 * so, then 'GC_HANDLE_VALID' gives whether the entry refers to a valid (1) or
55 * NULL (0) object reference. If the reference is valid, then the pointer is an
56 * object pointer. If the reference is NULL, and 'GC_HANDLE_TYPE_IS_WEAK' is
57 * true for 'type', then the pointer is a metadata pointer--this allows us to
58 * retrieve the domain ID of an expired weak reference in Mono.
60 * Finally, 'slot_hint' denotes the position of the last allocation, so that the
61 * whole array needn't be searched on every allocation.
65 volatile gpointer *volatile entries [BUCKETS];
66 volatile guint32 capacity;
67 volatile guint32 slot_hint;
68 volatile guint32 max_index;
73 bucket_size (guint index)
75 return 1 << (index + MIN_BUCKET_BITS);
78 /* Computes floor(log2(index + MIN_BUCKET_SIZE)) - 1, giving the index
79 * of the bucket containing a slot.
82 index_bucket (guint index)
85 return CHAR_BIT * sizeof (index) - __builtin_clz (index + MIN_BUCKET_SIZE) - 1 - MIN_BUCKET_BITS;
88 index += MIN_BUCKET_SIZE;
93 return count - 1 - MIN_BUCKET_BITS;
98 bucketize (guint index, guint *bucket, guint *offset)
100 *bucket = index_bucket (index);
101 *offset = index - bucket_size (*bucket) + MIN_BUCKET_SIZE;
105 protocol_gchandle_update (int handle_type, gpointer link, gpointer old_value, gpointer new_value)
107 gboolean old = MONO_GC_HANDLE_IS_OBJECT_POINTER (old_value);
108 gboolean new = MONO_GC_HANDLE_IS_OBJECT_POINTER (new_value);
109 gboolean track = handle_type == HANDLE_WEAK_TRACK;
111 if (!MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type))
115 binary_protocol_dislink_add (link, MONO_GC_REVEAL_POINTER (new_value, TRUE), track);
116 else if (old && !new)
117 binary_protocol_dislink_remove (link, track);
118 else if (old && new && old_value != new_value)
119 binary_protocol_dislink_update (link, MONO_GC_REVEAL_POINTER (new_value, TRUE), track);
122 /* Returns the new value in the slot, or NULL if the CAS failed. */
123 static inline gpointer
124 try_set_slot (volatile gpointer *slot, GCObject *obj, gpointer old, GCHandleType type)
128 new = MONO_GC_HANDLE_OBJECT_POINTER (obj, GC_HANDLE_TYPE_IS_WEAK (type));
130 new = MONO_GC_HANDLE_METADATA_POINTER (sgen_client_default_metadata (), GC_HANDLE_TYPE_IS_WEAK (type));
131 SGEN_ASSERT (0, new, "Why is the occupied bit not set?");
132 if (InterlockedCompareExchangePointer (slot, new, old) == old) {
133 protocol_gchandle_update (type, (gpointer)slot, old, new);
139 /* Try to claim a slot by setting its occupied bit. */
140 static inline gboolean
141 try_occupy_slot (HandleData *handles, guint bucket, guint offset, GCObject *obj, gboolean track)
143 volatile gpointer *link_addr = &(handles->entries [bucket] [offset]);
144 if (MONO_GC_HANDLE_OCCUPIED (*link_addr))
146 return try_set_slot (link_addr, obj, NULL, handles->type) != NULL;
149 #define EMPTY_HANDLE_DATA(t) \
151 .entries = { NULL }, \
158 /* weak and weak-track arrays will be allocated in malloc memory
160 static HandleData gc_handles [] = {
161 EMPTY_HANDLE_DATA (HANDLE_WEAK),
162 EMPTY_HANDLE_DATA (HANDLE_WEAK_TRACK),
163 EMPTY_HANDLE_DATA (HANDLE_NORMAL),
164 EMPTY_HANDLE_DATA (HANDLE_PINNED)
168 gc_handles_for_type (GCHandleType type)
170 g_assert (type < HANDLE_TYPE_MAX);
171 return &gc_handles [type];
174 /* This assumes that the world is stopped. */
176 sgen_mark_normal_gc_handles (void *addr, SgenUserMarkFunc mark_func, void *gc_data)
178 HandleData *handles = gc_handles_for_type (HANDLE_NORMAL);
179 size_t bucket, offset;
180 const guint max_bucket = index_bucket (handles->capacity);
182 const guint32 max_index = handles->max_index;
183 for (bucket = 0; bucket < max_bucket; ++bucket) {
184 volatile gpointer *entries = handles->entries [bucket];
185 for (offset = 0; offset < bucket_size (bucket); ++offset, ++index) {
186 volatile gpointer *entry;
187 gpointer hidden, revealed;
188 /* No need to iterate beyond the largest index ever allocated. */
189 if (index > max_index)
191 entry = &entries [offset];
193 revealed = MONO_GC_REVEAL_POINTER (hidden, FALSE);
194 if (!MONO_GC_HANDLE_IS_OBJECT_POINTER (hidden))
196 mark_func ((MonoObject **)&revealed, gc_data);
198 *entry = MONO_GC_HANDLE_OBJECT_POINTER (revealed, FALSE);
204 handle_data_find_unset (HandleData *handles, guint32 begin, guint32 end)
207 gint delta = begin < end ? +1 : -1;
208 for (index = begin; index < end; index += delta) {
209 guint bucket, offset;
210 volatile gpointer *entries;
211 bucketize (index, &bucket, &offset);
212 entries = handles->entries [bucket];
214 if (!MONO_GC_HANDLE_OCCUPIED (entries [offset]))
220 /* Adds a bucket if necessary and possible. */
222 handle_data_grow (HandleData *handles, guint32 old_capacity)
224 const guint new_bucket = index_bucket (old_capacity);
225 const guint32 growth = bucket_size (new_bucket);
226 const guint32 new_capacity = old_capacity + growth;
228 const size_t new_bucket_size = sizeof (**handles->entries) * growth;
229 if (handles->capacity >= new_capacity)
231 entries = g_malloc0 (new_bucket_size);
232 if (handles->type == HANDLE_PINNED)
233 sgen_register_root ((char *)entries, new_bucket_size, SGEN_DESCRIPTOR_NULL, ROOT_TYPE_PINNED, MONO_ROOT_SOURCE_GC_HANDLE, "pinned gc handles");
234 if (InterlockedCompareExchangePointer ((volatile gpointer *)&handles->entries [new_bucket], entries, NULL) == NULL) {
235 if (InterlockedCompareExchange ((volatile gint32 *)&handles->capacity, new_capacity, old_capacity) != old_capacity)
236 g_assert_not_reached ();
237 handles->slot_hint = old_capacity;
238 mono_memory_write_barrier ();
241 /* Someone beat us to the allocation. */
242 if (handles->type == HANDLE_PINNED)
243 sgen_deregister_root ((char *)entries);
248 alloc_handle (HandleData *handles, GCObject *obj, gboolean track)
252 guint bucket, offset;
256 if (!handles->capacity)
257 handle_data_grow (handles, 0);
259 capacity = handles->capacity;
260 slot_hint = handles->slot_hint;
261 index = handle_data_find_unset (handles, slot_hint, capacity);
263 index = handle_data_find_unset (handles, 0, slot_hint);
265 handle_data_grow (handles, capacity);
268 handles->slot_hint = index;
269 bucketize (index, &bucket, &offset);
270 if (!try_occupy_slot (handles, bucket, offset, obj, track))
272 /* If a GC happens shortly after a new bucket is allocated, the entire
273 * bucket could be scanned even though it's mostly empty. To avoid this, we
274 * track the maximum index seen so far, so that we can skip the empty slots.
277 max_index = handles->max_index;
278 if (index <= max_index)
280 } while (!InterlockedCompareExchange ((volatile gint32 *)&handles->max_index, index, max_index));
281 #ifdef HEAVY_STATISTICS
282 InterlockedIncrement64 ((volatile gint64 *)&stat_gc_handles_allocated);
283 if (stat_gc_handles_allocated > stat_gc_handles_max_allocated)
284 stat_gc_handles_max_allocated = stat_gc_handles_allocated;
286 /* Ensure that a GC handle cannot be given to another thread without the slot having been set. */
287 mono_memory_write_barrier ();
288 res = MONO_GC_HANDLE (index, handles->type);
289 sgen_client_gchandle_created (handles->type, obj, res);
294 object_older_than (GCObject *object, int generation)
296 return generation == GENERATION_NURSERY && !sgen_ptr_in_nursery (object);
300 * Maps a function over all GC handles.
301 * This assumes that the world is stopped!
304 sgen_gchandle_iterate (GCHandleType handle_type, int max_generation, gpointer callback(gpointer, GCHandleType, int, gpointer), gpointer user)
306 HandleData *handle_data = gc_handles_for_type (handle_type);
307 size_t bucket, offset;
308 guint max_bucket = index_bucket (handle_data->capacity);
310 guint32 max_index = handle_data->max_index;
311 /* If a new bucket has been allocated, but the capacity has not yet been
312 * increased, nothing can yet have been allocated in the bucket because the
313 * world is stopped, so we shouldn't miss any handles during iteration.
315 for (bucket = 0; bucket < max_bucket; ++bucket) {
316 volatile gpointer *entries = handle_data->entries [bucket];
317 for (offset = 0; offset < bucket_size (bucket); ++offset, ++index) {
320 /* Table must contain no garbage pointers. */
322 /* No need to iterate beyond the largest index ever allocated. */
323 if (index > max_index)
325 hidden = entries [offset];
326 occupied = MONO_GC_HANDLE_OCCUPIED (hidden);
327 g_assert (hidden ? occupied : !occupied);
330 result = callback (hidden, handle_type, max_generation, user);
332 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (result), "Why did the callback return an unoccupied entry?");
334 HEAVY_STAT (InterlockedDecrement64 ((volatile gint64 *)&stat_gc_handles_allocated));
335 protocol_gchandle_update (handle_type, (gpointer)&entries [offset], hidden, result);
336 entries [offset] = result;
343 * @obj: managed object to get a handle for
344 * @pinned: whether the object should be pinned
346 * This returns a handle that wraps the object, this is used to keep a
347 * reference to a managed object from the unmanaged world and preventing the
348 * object from being disposed.
350 * If @pinned is false the address of the object can not be obtained, if it is
351 * true the address of the object can be obtained. This will also pin the
352 * object so it will not be possible by a moving garbage collector to move the
355 * Returns: a handle that can be used to access the object from
359 mono_gchandle_new (GCObject *obj, gboolean pinned)
361 return alloc_handle (gc_handles_for_type (pinned ? HANDLE_PINNED : HANDLE_NORMAL), obj, FALSE);
365 * mono_gchandle_new_weakref:
366 * @obj: managed object to get a handle for
367 * @pinned: whether the object should be pinned
369 * This returns a weak handle that wraps the object, this is used to
370 * keep a reference to a managed object from the unmanaged world.
371 * Unlike the mono_gchandle_new the object can be reclaimed by the
372 * garbage collector. In this case the value of the GCHandle will be
375 * If @pinned is false the address of the object can not be obtained, if it is
376 * true the address of the object can be obtained. This will also pin the
377 * object so it will not be possible by a moving garbage collector to move the
380 * Returns: a handle that can be used to access the object from
384 mono_gchandle_new_weakref (GCObject *obj, gboolean track_resurrection)
386 return alloc_handle (gc_handles_for_type (track_resurrection ? HANDLE_WEAK_TRACK : HANDLE_WEAK), obj, track_resurrection);
390 link_get (volatile gpointer *link_addr, gboolean is_weak)
392 void *volatile *link_addr_volatile;
396 link_addr_volatile = link_addr;
397 ptr = (void*)*link_addr_volatile;
399 * At this point we have a hidden pointer. If the GC runs
400 * here, it will not recognize the hidden pointer as a
401 * reference, and if the object behind it is not referenced
402 * elsewhere, it will be freed. Once the world is restarted
403 * we reveal the pointer, giving us a pointer to a freed
404 * object. To make sure we don't return it, we load the
405 * hidden pointer again. If it's still the same, we can be
406 * sure the object reference is valid.
408 if (ptr && MONO_GC_HANDLE_IS_OBJECT_POINTER (ptr))
409 obj = (GCObject *)MONO_GC_REVEAL_POINTER (ptr, is_weak);
415 * If a GC happens here, obj needs to be on the stack or in a
416 * register, so we need to prevent this from being reordered
419 sgen_dummy_use (obj);
420 mono_memory_barrier ();
423 sgen_client_ensure_weak_gchandles_accessible ();
425 if ((void*)*link_addr_volatile != ptr)
432 * mono_gchandle_get_target:
433 * @gchandle: a GCHandle's handle.
435 * The handle was previously created by calling mono_gchandle_new or
436 * mono_gchandle_new_weakref.
438 * Returns a pointer to the MonoObject represented by the handle or
439 * NULL for a collected object if using a weakref handle.
442 mono_gchandle_get_target (guint32 gchandle)
444 guint index = MONO_GC_HANDLE_SLOT (gchandle);
445 guint type = MONO_GC_HANDLE_TYPE (gchandle);
446 HandleData *handles = gc_handles_for_type (type);
447 guint bucket, offset;
448 g_assert (index < handles->capacity);
449 bucketize (index, &bucket, &offset);
450 return link_get (&handles->entries [bucket] [offset], MONO_GC_HANDLE_TYPE_IS_WEAK (type));
454 sgen_gchandle_set_target (guint32 gchandle, GCObject *obj)
456 guint index = MONO_GC_HANDLE_SLOT (gchandle);
457 guint type = MONO_GC_HANDLE_TYPE (gchandle);
458 HandleData *handles = gc_handles_for_type (type);
459 guint bucket, offset;
462 g_assert (index < handles->capacity);
463 bucketize (index, &bucket, &offset);
466 slot = handles->entries [bucket] [offset];
467 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (slot), "Why are we setting the target on an unoccupied slot?");
468 } while (!try_set_slot (&handles->entries [bucket] [offset], obj, slot, handles->type));
472 mono_gchandle_slot_metadata (volatile gpointer *slot_addr, gboolean is_weak)
478 if (!MONO_GC_HANDLE_OCCUPIED (slot))
480 if (MONO_GC_HANDLE_IS_OBJECT_POINTER (slot)) {
481 GCObject *obj = MONO_GC_REVEAL_POINTER (slot, is_weak);
482 /* See note [dummy use]. */
483 sgen_dummy_use (obj);
485 * FIXME: The compiler could technically not carry a reference to obj around
486 * at this point and recompute it later, in which case we would still use
489 if (*slot_addr != slot)
491 return sgen_client_metadata_for_object (obj);
493 metadata = MONO_GC_REVEAL_POINTER (slot, is_weak);
494 /* See note [dummy use]. */
495 sgen_dummy_use (metadata);
496 if (*slot_addr != slot)
502 sgen_gchandle_get_metadata (guint32 gchandle)
504 guint index = MONO_GC_HANDLE_SLOT (gchandle);
505 guint type = MONO_GC_HANDLE_TYPE (gchandle);
506 HandleData *handles = gc_handles_for_type (type);
507 guint bucket, offset;
508 if (index >= handles->capacity)
510 bucketize (index, &bucket, &offset);
511 return mono_gchandle_slot_metadata (&handles->entries [bucket] [offset], MONO_GC_HANDLE_TYPE_IS_WEAK (type));
515 * mono_gchandle_free:
516 * @gchandle: a GCHandle's handle.
518 * Frees the @gchandle handle. If there are no outstanding
519 * references, the garbage collector can reclaim the memory of the
523 mono_gchandle_free (guint32 gchandle)
525 guint index = MONO_GC_HANDLE_SLOT (gchandle);
526 guint type = MONO_GC_HANDLE_TYPE (gchandle);
527 HandleData *handles = gc_handles_for_type (type);
528 guint bucket, offset;
530 bucketize (index, &bucket, &offset);
531 slot = handles->entries [bucket] [offset];
532 if (index < handles->capacity && MONO_GC_HANDLE_OCCUPIED (slot)) {
533 handles->entries [bucket] [offset] = NULL;
534 protocol_gchandle_update (handles->type, (gpointer)&handles->entries [bucket] [offset], slot, NULL);
535 HEAVY_STAT (InterlockedDecrement64 ((volatile gint64 *)&stat_gc_handles_allocated));
537 /* print a warning? */
539 sgen_client_gchandle_destroyed (handles->type, gchandle);
543 * Returns whether to remove the link from its hash.
546 null_link_if_necessary (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
548 const gboolean is_weak = GC_HANDLE_TYPE_IS_WEAK (handle_type);
549 ScanCopyContext *ctx = (ScanCopyContext *)user;
553 if (!MONO_GC_HANDLE_VALID (hidden))
556 obj = MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
557 SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
559 if (object_older_than (obj, max_generation))
562 if (major_collector.is_object_live (obj))
565 /* Clear link if object is ready for finalization. This check may be redundant wrt is_object_live(). */
566 if (sgen_gc_is_object_ready_for_finalization (obj))
567 return MONO_GC_HANDLE_METADATA_POINTER (sgen_client_metadata_for_object (obj), is_weak);
570 ctx->ops->copy_or_mark_object (©, ctx->queue);
571 SGEN_ASSERT (0, copy, "Why couldn't we copy the object?");
572 /* Update link if object was moved. */
573 return MONO_GC_HANDLE_OBJECT_POINTER (copy, is_weak);
576 /* LOCKING: requires that the GC lock is held */
578 sgen_null_link_in_range (int generation, ScanCopyContext ctx, gboolean track)
580 sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if_necessary, &ctx);
584 SgenObjectPredicateFunc predicate;
586 } WeakLinkAlivePredicateClosure;
589 null_link_if (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
591 /* Strictly speaking, function pointers are not guaranteed to have the same size as data pointers. */
592 WeakLinkAlivePredicateClosure *closure = (WeakLinkAlivePredicateClosure *)user;
595 if (!MONO_GC_HANDLE_VALID (hidden))
598 obj = MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
599 SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
601 if (object_older_than (obj, max_generation))
604 if (closure->predicate (obj, closure->data))
610 /* LOCKING: requires that the GC lock is held */
612 sgen_null_links_if (SgenObjectPredicateFunc predicate, void *data, int generation, gboolean track)
614 WeakLinkAlivePredicateClosure closure = { predicate, data };
615 sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if, &closure);
619 sgen_init_gchandles (void)
621 #ifdef HEAVY_STATISTICS
622 mono_counters_register ("GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_gc_handles_allocated);
623 mono_counters_register ("max GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_gc_handles_max_allocated);