/* * obj is some object. If it's not in the major heap (i.e. if it's in * the nursery or LOS), return FALSE. Otherwise return whether it's * been marked or copied. */ static gboolean major_is_object_live (char *obj) { MSBlockInfo *block; int word, bit; #ifndef FIXED_HEAP mword objsize; #endif if (ptr_in_nursery (obj)) return FALSE; #ifdef FIXED_HEAP /* LOS */ if (!MS_PTR_IN_SMALL_MAJOR_HEAP (obj)) return FALSE; #else objsize = SGEN_ALIGN_UP (mono_sgen_safe_object_get_size ((MonoObject*)obj)); /* LOS */ if (objsize > SGEN_MAX_SMALL_OBJ_SIZE) return FALSE; #endif /* now we know it's in a major block */ block = MS_BLOCK_FOR_OBJ (obj); DEBUG (9, g_assert (!block->pinned)); MS_CALC_MARK_BIT (word, bit, obj); return MS_MARK_BIT (block, word, bit) ? TRUE : FALSE; }
/* LOCKING: requires that the GC lock is held */ void sgen_mark_bridge_object (MonoObject *obj) { SgenHashTable *hash_table = get_finalize_entry_hash_table (ptr_in_nursery (obj) ? GENERATION_NURSERY : GENERATION_OLD); sgen_hash_table_set_key (hash_table, obj, tagged_object_apply (obj, BRIDGE_OBJECT_MARKED)); }
static void major_copy_or_mark_object (void **ptr, SgenGrayQueue *queue) { void *obj = *ptr; MSBlockInfo *block; HEAVY_STAT (++stat_copy_object_called_major); DEBUG (9, g_assert (obj)); DEBUG (9, g_assert (current_collection_generation == GENERATION_OLD)); if (ptr_in_nursery (obj)) { int word, bit; char *forwarded; if ((forwarded = SGEN_OBJECT_IS_FORWARDED (obj))) { *ptr = forwarded; return; } if (SGEN_OBJECT_IS_PINNED (obj)) return; HEAVY_STAT (++stat_objects_copied_major); obj = copy_object_no_checks (obj, queue); *ptr = obj; /* * FIXME: See comment for copy_object_no_checks(). If * we have that, we can let the allocation function * give us the block info, too, and we won't have to * re-fetch it. */ block = MS_BLOCK_FOR_OBJ (obj); MS_CALC_MARK_BIT (word, bit, obj); DEBUG (9, g_assert (!MS_MARK_BIT (block, word, bit))); MS_SET_MARK_BIT (block, word, bit); } else { #ifdef FIXED_HEAP if (MS_PTR_IN_SMALL_MAJOR_HEAP (obj)) #else mword objsize; objsize = SGEN_ALIGN_UP (mono_sgen_safe_object_get_size ((MonoObject*)obj)); if (objsize <= SGEN_MAX_SMALL_OBJ_SIZE) #endif { block = MS_BLOCK_FOR_OBJ (obj); MS_MARK_OBJECT_AND_ENQUEUE (obj, block, queue); } else { if (SGEN_OBJECT_IS_PINNED (obj)) return; binary_protocol_pin (obj, (gpointer)SGEN_LOAD_VTABLE (obj), mono_sgen_safe_object_get_size ((MonoObject*)obj)); SGEN_PIN_OBJECT (obj); /* FIXME: only enqueue if object has references */ GRAY_OBJECT_ENQUEUE (queue, obj); } } }
/* LOCKING: requires that the GC lock is held */ static void process_fin_stage_entry (MonoObject *obj, void *user_data) { if (ptr_in_nursery (obj)) register_for_finalization (obj, user_data, GENERATION_NURSERY); else register_for_finalization (obj, user_data, GENERATION_OLD); }
/* LOCKING: requires that the GC lock is held */ void sgen_collect_bridge_objects (int generation, ScanCopyContext ctx) { CopyOrMarkObjectFunc copy_func = ctx.copy_func; GrayQueue *queue = ctx.queue; SgenHashTable *hash_table = get_finalize_entry_hash_table (generation); MonoObject *object; gpointer dummy; char *copy; if (no_finalize) return; SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) { int tag = tagged_object_get_tag (object); object = tagged_object_get_object (object); /* Bridge code told us to ignore this one */ if (tag == BRIDGE_OBJECT_MARKED) continue; /* Object is a bridge object and major heap says it's dead */ if (major_collector.is_object_live ((char*)object)) continue; /* Nursery says the object is dead. */ if (!sgen_gc_is_object_ready_for_finalization (object)) continue; if (!sgen_is_bridge_object (object)) continue; copy = (char*)object; copy_func ((void**)©, queue); sgen_bridge_register_finalized_object ((MonoObject*)copy); if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) { /* remove from the list */ SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE); /* insert it into the major hash */ sgen_hash_table_replace (&major_finalizable_hash, tagged_object_apply (copy, tag), NULL, NULL); SGEN_LOG (5, "Promoting finalization of object %p (%s) (was at %p) to major table", copy, sgen_safe_name (copy), object); continue; } else { /* update pointer */ SGEN_LOG (5, "Updating object for finalization: %p (%s) (was at %p)", copy, sgen_safe_name (copy), object); SGEN_HASH_TABLE_FOREACH_SET_KEY (tagged_object_apply (copy, tag)); } }
/* LOCKING: requires that the GC lock is held */ static void collect_bridge_objects (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, GrayQueue *queue) { SgenHashTable *hash_table = get_finalize_entry_hash_table (generation); MonoObject *object; gpointer dummy; char *copy; if (no_finalize) return; SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) { int tag = tagged_object_get_tag (object); object = tagged_object_get_object (object); /* Bridge code told us to ignore this one */ if (tag == BRIDGE_OBJECT_MARKED) continue; /* Object is a bridge object and major heap says it's dead */ if (!((char*)object >= start && (char*)object < end && !major_collector.is_object_live ((char*)object))) continue; /* Nursery says the object is dead. */ if (!object_is_fin_ready (object)) continue; if (!mono_sgen_is_bridge_object (object)) continue; copy = (char*)object; copy_func ((void**)©, queue); mono_sgen_bridge_register_finalized_object ((MonoObject*)copy); if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) { /* remove from the list */ SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE); /* insert it into the major hash */ mono_sgen_hash_table_replace (&major_finalizable_hash, tagged_object_apply (copy, tag), NULL); DEBUG (5, fprintf (gc_debug_file, "Promoting finalization of object %p (%s) (was at %p) to major table\n", copy, safe_name (copy), object)); continue; } else { /* update pointer */ DEBUG (5, fprintf (gc_debug_file, "Updating object for finalization: %p (%s) (was at %p)\n", copy, safe_name (copy), object)); SGEN_HASH_TABLE_FOREACH_SET_KEY (tagged_object_apply (copy, tag)); } } SGEN_HASH_TABLE_FOREACH_END;
/* LOCKING: requires that the GC lock is held */ static void process_dislink_stage_entry (MonoObject *obj, void *_link) { void **link = _link; add_or_remove_disappearing_link (NULL, link, GENERATION_NURSERY); add_or_remove_disappearing_link (NULL, link, GENERATION_OLD); if (obj) { if (ptr_in_nursery (obj)) add_or_remove_disappearing_link (obj, link, GENERATION_NURSERY); else add_or_remove_disappearing_link (obj, link, GENERATION_OLD); } }
static gboolean major_is_object_live (char *obj) { mword objsize; /* nursery */ if (ptr_in_nursery (obj)) return FALSE; objsize = SGEN_ALIGN_UP (sgen_safe_object_get_size ((MonoObject*)obj)); /* LOS */ if (objsize > SGEN_MAX_SMALL_OBJ_SIZE) return FALSE; /* pinned chunk */ if (obj_is_from_pinned_alloc (obj)) return FALSE; /* now we know it's in a major heap section */ return MAJOR_SECTION_FOR_OBJECT (obj)->is_to_space; }
static void major_copy_or_mark_object (void **obj_slot, SgenGrayQueue *queue) { char *forwarded; char *obj = *obj_slot; mword objsize; DEBUG (9, g_assert (current_collection_generation == GENERATION_OLD)); HEAVY_STAT (++stat_copy_object_called_major); DEBUG (9, fprintf (gc_debug_file, "Precise copy of %p from %p", obj, obj_slot)); /* * obj must belong to one of: * * 1. the nursery * 2. the LOS * 3. a pinned chunk * 4. a non-to-space section of the major heap * 5. a to-space section of the major heap * * In addition, objects in 1, 2 and 4 might also be pinned. * Objects in 1 and 4 might be forwarded. * * Before we can copy the object we must make sure that we are * allowed to, i.e. that the object not pinned, not already * forwarded, not in the nursery To Space and doesn't belong * to the LOS, a pinned chunk, or a to-space section. * * We are usually called for to-space objects (5) when we have * two remset entries for the same reference. The first entry * copies the object and updates the reference and the second * calls us with the updated reference that points into * to-space. There might also be other circumstances where we * get to-space objects. */ if ((forwarded = SGEN_OBJECT_IS_FORWARDED (obj))) { DEBUG (9, g_assert (((MonoVTable*)SGEN_LOAD_VTABLE(obj))->gc_descr)); DEBUG (9, fprintf (gc_debug_file, " (already forwarded to %p)\n", forwarded)); HEAVY_STAT (++stat_major_copy_object_failed_forwarded); *obj_slot = forwarded; return; } if (SGEN_OBJECT_IS_PINNED (obj)) { DEBUG (9, g_assert (((MonoVTable*)SGEN_LOAD_VTABLE(obj))->gc_descr)); DEBUG (9, fprintf (gc_debug_file, " (pinned, no change)\n")); HEAVY_STAT (++stat_major_copy_object_failed_pinned); return; } if (ptr_in_nursery (obj)) { /* A To Space object is already on its final destination for the current collection. */ if (sgen_nursery_is_to_space (obj)) return; goto copy; } /* * At this point we know obj is not pinned, not forwarded and * belongs to 2, 3, 4, or 5. * * LOS object (2) are simple, at least until we always follow * the rule: if objsize > SGEN_MAX_SMALL_OBJ_SIZE, pin the * object and return it. At the end of major collections, we * walk the los list and if the object is pinned, it is * marked, otherwise it can be freed. * * Pinned chunks (3) and major heap sections (4, 5) both * reside in blocks, which are always aligned, so once we've * eliminated LOS objects, we can just access the block and * see whether it's a pinned chunk or a major heap section. */ objsize = SGEN_ALIGN_UP (sgen_safe_object_get_size ((MonoObject*)obj)); if (G_UNLIKELY (objsize > SGEN_MAX_SMALL_OBJ_SIZE || obj_is_from_pinned_alloc (obj))) { if (SGEN_OBJECT_IS_PINNED (obj)) return; DEBUG (9, fprintf (gc_debug_file, " (marked LOS/Pinned %p (%s), size: %td)\n", obj, sgen_safe_name (obj), objsize)); binary_protocol_pin (obj, (gpointer)SGEN_LOAD_VTABLE (obj), sgen_safe_object_get_size ((MonoObject*)obj)); SGEN_PIN_OBJECT (obj); GRAY_OBJECT_ENQUEUE (queue, obj); HEAVY_STAT (++stat_major_copy_object_failed_large_pinned); return; } /* * Now we know the object is in a major heap section. All we * need to do is check whether it's already in to-space (5) or * not (4). */ if (MAJOR_OBJ_IS_IN_TO_SPACE (obj)) { DEBUG (9, g_assert (objsize <= SGEN_MAX_SMALL_OBJ_SIZE)); DEBUG (9, fprintf (gc_debug_file, " (already copied)\n")); HEAVY_STAT (++stat_major_copy_object_failed_to_space); return; } copy: HEAVY_STAT (++stat_objects_copied_major); *obj_slot = copy_object_no_checks (obj, queue); }
static void major_copy_or_mark_object (void **ptr, SgenGrayQueue *queue) { void *obj = *ptr; mword vtable_word = *(mword*)obj; MonoVTable *vt = (MonoVTable*)(vtable_word & ~SGEN_VTABLE_BITS_MASK); mword objsize; MSBlockInfo *block; HEAVY_STAT (++stat_copy_object_called_major); DEBUG (9, g_assert (obj)); DEBUG (9, g_assert (current_collection_generation == GENERATION_OLD)); if (ptr_in_nursery (obj)) { int word, bit; gboolean has_references; void *destination; if (vtable_word & SGEN_FORWARDED_BIT) { *ptr = (void*)vt; return; } if (vtable_word & SGEN_PINNED_BIT) return; HEAVY_STAT (++stat_objects_copied_major); objsize = SGEN_ALIGN_UP (mono_sgen_par_object_get_size (vt, (MonoObject*)obj)); has_references = SGEN_VTABLE_HAS_REFERENCES (vt); destination = major_alloc_object (objsize, has_references); if (SGEN_CAS_PTR (obj, (void*)((mword)destination | SGEN_FORWARDED_BIT), vt) == vt) { gboolean was_marked; par_copy_object_no_checks (destination, vt, obj, objsize, has_references ? queue : NULL); obj = destination; *ptr = obj; /* * FIXME: If we make major_alloc_object() give * us the block info, too, we won't have to * re-fetch it here. */ block = MS_BLOCK_FOR_OBJ (obj); MS_CALC_MARK_BIT (word, bit, obj); DEBUG (9, g_assert (!MS_MARK_BIT (block, word, bit))); MS_PAR_SET_MARK_BIT (was_marked, block, word, bit); } else { /* * FIXME: We have allocated destination, but * we cannot use it. Give it back to the * allocator. */ *(void**)destination = NULL; vtable_word = *(mword*)obj; g_assert (vtable_word & SGEN_FORWARDED_BIT); obj = (void*)(vtable_word & ~SGEN_VTABLE_BITS_MASK); *ptr = obj; } } else { #ifdef FIXED_HEAP if (MS_PTR_IN_SMALL_MAJOR_HEAP (obj)) #else objsize = SGEN_ALIGN_UP (mono_sgen_par_object_get_size (vt, (MonoObject*)obj)); if (objsize <= SGEN_MAX_SMALL_OBJ_SIZE) #endif { block = MS_BLOCK_FOR_OBJ (obj); MS_PAR_MARK_OBJECT_AND_ENQUEUE (obj, block, queue); } else { if (vtable_word & SGEN_PINNED_BIT) return; binary_protocol_pin (obj, vt, mono_sgen_safe_object_get_size ((MonoObject*)obj)); if (SGEN_CAS_PTR (obj, (void*)(vtable_word | SGEN_PINNED_BIT), (void*)vtable_word) == (void*)vtable_word) { if (SGEN_VTABLE_HAS_REFERENCES (vt)) GRAY_OBJECT_ENQUEUE (queue, obj); } else { g_assert (SGEN_OBJECT_IS_PINNED (obj)); } } } }
/* LOCKING: requires that the GC lock is held */ void sgen_collect_bridge_objects (int generation, ScanCopyContext ctx) { CopyOrMarkObjectFunc copy_func = ctx.ops->copy_or_mark_object; GrayQueue *queue = ctx.queue; SgenHashTable *hash_table = get_finalize_entry_hash_table (generation); GCObject *object; gpointer dummy G_GNUC_UNUSED; GCObject *copy; SgenPointerQueue moved_fin_objects; sgen_pointer_queue_init (&moved_fin_objects, INTERNAL_MEM_TEMPORARY); if (no_finalize) return; SGEN_HASH_TABLE_FOREACH (hash_table, GCObject *, object, gpointer, dummy) { int tag = tagged_object_get_tag (object); object = tagged_object_get_object (object); /* Bridge code told us to ignore this one */ if (tag == BRIDGE_OBJECT_MARKED) continue; /* Object is a bridge object and major heap says it's dead */ if (major_collector.is_object_live (object)) continue; /* Nursery says the object is dead. */ if (!sgen_gc_is_object_ready_for_finalization (object)) continue; if (!sgen_client_bridge_is_bridge_object (object)) continue; copy = object; copy_func (©, queue); sgen_client_bridge_register_finalized_object (copy); if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) { /* remove from the list */ SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE); /* insert it into the major hash */ sgen_hash_table_replace (&major_finalizable_hash, tagged_object_apply (copy, tag), NULL, NULL); SGEN_LOG (5, "Promoting finalization of object %p (%s) (was at %p) to major table", copy, sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (copy)), object); continue; } else if (copy != object) { /* update pointer */ SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE); /* register for reinsertion */ sgen_pointer_queue_add (&moved_fin_objects, tagged_object_apply (copy, tag)); SGEN_LOG (5, "Updating object for finalization: %p (%s) (was at %p)", copy, sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (copy)), object); continue; } } SGEN_HASH_TABLE_FOREACH_END;
/* LOCKING: requires that the GC lock is held */ static void finalize_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, GrayQueue *queue) { FinalizeEntryHashTable *hash_table = get_finalize_entry_hash_table (generation); FinalizeEntry *entry, *prev; int i; FinalizeEntry **finalizable_hash = hash_table->table; mword finalizable_hash_size = hash_table->size; if (no_finalize) return; for (i = 0; i < finalizable_hash_size; ++i) { prev = NULL; for (entry = finalizable_hash [i]; entry;) { if ((char*)entry->object >= start && (char*)entry->object < end && !major_collector.is_object_live (entry->object)) { gboolean is_fin_ready = object_is_fin_ready (entry->object); char *copy = entry->object; copy_func ((void**)©, queue); if (is_fin_ready) { char *from; FinalizeEntry *next; /* remove and put in fin_ready_list */ if (prev) prev->next = entry->next; else finalizable_hash [i] = entry->next; next = entry->next; num_ready_finalizers++; hash_table->num_registered--; queue_finalization_entry (entry); bridge_register_finalized_object ((MonoObject*)copy); /* Make it survive */ from = entry->object; entry->object = copy; DEBUG (5, fprintf (gc_debug_file, "Queueing object for finalization: %p (%s) (was at %p) (%d/%d)\n", entry->object, safe_name (entry->object), from, num_ready_finalizers, hash_table->num_registered)); entry = next; continue; } else { char *from = entry->object; if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) { FinalizeEntry *next = entry->next; unsigned int major_hash; /* remove from the list */ if (prev) prev->next = entry->next; else finalizable_hash [i] = entry->next; hash_table->num_registered--; entry->object = copy; /* insert it into the major hash */ rehash_fin_table_if_necessary (&major_finalizable_hash); major_hash = mono_object_hash ((MonoObject*) copy) % major_finalizable_hash.size; entry->next = major_finalizable_hash.table [major_hash]; major_finalizable_hash.table [major_hash] = entry; major_finalizable_hash.num_registered++; DEBUG (5, fprintf (gc_debug_file, "Promoting finalization of object %p (%s) (was at %p) to major table\n", copy, safe_name (copy), from)); entry = next; continue; } else { /* update pointer */ DEBUG (5, fprintf (gc_debug_file, "Updating object for finalization: %p (%s) (was at %p)\n", entry->object, safe_name (entry->object), from)); entry->object = copy; } } } prev = entry; entry = entry->next; } } }
/* LOCKING: requires that the GC lock is held */ static void null_link_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, gboolean before_finalization, GrayQueue *queue) { DisappearingLinkHashTable *hash = get_dislink_hash_table (generation); DisappearingLink **disappearing_link_hash = hash->table; int disappearing_link_hash_size = hash->size; DisappearingLink *entry, *prev; int i; if (!hash->num_links) return; for (i = 0; i < disappearing_link_hash_size; ++i) { prev = NULL; for (entry = disappearing_link_hash [i]; entry;) { char *object; gboolean track = DISLINK_TRACK (entry); /* * Tracked references are processed after * finalization handling whereas standard weak * references are processed before. If an * object is still not marked after finalization * handling it means that it either doesn't have * a finalizer or the finalizer has already run, * so we must null a tracking reference. */ if (track == before_finalization) { prev = entry; entry = entry->next; continue; } object = DISLINK_OBJECT (entry); if (object >= start && object < end && !major_collector.is_object_live (object)) { if (object_is_fin_ready (object)) { void **p = entry->link; DisappearingLink *old; *p = NULL; /* remove from list */ if (prev) prev->next = entry->next; else disappearing_link_hash [i] = entry->next; DEBUG (5, fprintf (gc_debug_file, "Dislink nullified at %p to GCed object %p\n", p, object)); old = entry->next; mono_sgen_free_internal (entry, INTERNAL_MEM_DISLINK); entry = old; hash->num_links--; continue; } else { char *copy = object; copy_func ((void**)©, queue); /* Update pointer if it's moved. If the object * has been moved out of the nursery, we need to * remove the link from the minor hash table to * the major one. * * FIXME: what if an object is moved earlier? */ if (hash == &minor_disappearing_link_hash && !ptr_in_nursery (copy)) { void **link = entry->link; DisappearingLink *old; /* remove from list */ if (prev) prev->next = entry->next; else disappearing_link_hash [i] = entry->next; old = entry->next; mono_sgen_free_internal (entry, INTERNAL_MEM_DISLINK); entry = old; hash->num_links--; g_assert (copy); *link = HIDE_POINTER (copy, track); add_or_remove_disappearing_link ((MonoObject*)copy, link, GENERATION_OLD); DEBUG (5, fprintf (gc_debug_file, "Upgraded dislink at %p to major because object %p moved to %p\n", link, object, copy)); continue; } else { *entry->link = HIDE_POINTER (copy, track); DEBUG (5, fprintf (gc_debug_file, "Updated dislink at %p to %p\n", entry->link, DISLINK_OBJECT (entry))); } } } prev = entry; entry = entry->next; } } }
static void major_copy_or_mark_object (void **ptr, SgenGrayQueue *queue) { void *obj = *ptr; MSBlockInfo *block; HEAVY_STAT (++stat_copy_object_called_major); DEBUG (9, g_assert (obj)); DEBUG (9, g_assert (current_collection_generation == GENERATION_OLD)); if (ptr_in_nursery (obj)) { int word, bit; char *forwarded, *old_obj; if ((forwarded = SGEN_OBJECT_IS_FORWARDED (obj))) { *ptr = forwarded; return; } if (SGEN_OBJECT_IS_PINNED (obj)) return; HEAVY_STAT (++stat_objects_copied_major); do_copy_object: old_obj = obj; obj = copy_object_no_checks (obj, queue); if (G_UNLIKELY (old_obj == obj)) { /*If we fail to evacuate an object we just stop doing it for a given block size as all other will surely fail too.*/ if (!ptr_in_nursery (obj)) { int size_index; block = MS_BLOCK_FOR_OBJ (obj); size_index = block->obj_size_index; evacuate_block_obj_sizes [size_index] = FALSE; MS_MARK_OBJECT_AND_ENQUEUE (obj, block, queue); } return; } *ptr = obj; /* * FIXME: See comment for copy_object_no_checks(). If * we have that, we can let the allocation function * give us the block info, too, and we won't have to * re-fetch it. */ block = MS_BLOCK_FOR_OBJ (obj); MS_CALC_MARK_BIT (word, bit, obj); DEBUG (9, g_assert (!MS_MARK_BIT (block, word, bit))); MS_SET_MARK_BIT (block, word, bit); } else { char *forwarded; #ifndef FIXED_HEAP mword objsize; #endif if ((forwarded = SGEN_OBJECT_IS_FORWARDED (obj))) { *ptr = forwarded; return; } #ifdef FIXED_HEAP if (MS_PTR_IN_SMALL_MAJOR_HEAP (obj)) #else objsize = SGEN_ALIGN_UP (mono_sgen_safe_object_get_size ((MonoObject*)obj)); if (objsize <= SGEN_MAX_SMALL_OBJ_SIZE) #endif { int size_index; block = MS_BLOCK_FOR_OBJ (obj); size_index = block->obj_size_index; if (!block->has_pinned && evacuate_block_obj_sizes [size_index]) { if (block->is_to_space) return; HEAVY_STAT (++stat_major_objects_evacuated); goto do_copy_object; } else { MS_MARK_OBJECT_AND_ENQUEUE (obj, block, queue); } } else { if (SGEN_OBJECT_IS_PINNED (obj)) return; binary_protocol_pin (obj, (gpointer)SGEN_LOAD_VTABLE (obj), mono_sgen_safe_object_get_size ((MonoObject*)obj)); SGEN_PIN_OBJECT (obj); /* FIXME: only enqueue if object has references */ GRAY_OBJECT_ENQUEUE (queue, obj); } } }