/* * sgen-mono.c: SGen features specific to Mono. * * Copyright (C) 2014 Xamarin Inc * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 2.0 as published by the Free Software Foundation; * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License 2.0 along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "config.h" #ifdef HAVE_SGEN_GC #include "metadata/sgen-gc.h" #include "metadata/sgen-protocol.h" #include "metadata/monitor.h" #include "metadata/sgen-layout-stats.h" #include "metadata/sgen-client.h" #include "metadata/marshal.h" #include "metadata/method-builder.h" #include "metadata/abi-details.h" #include "metadata/profiler-private.h" #include "utils/mono-memory-model.h" /* If set, check that there are no references to the domain left at domain unload */ gboolean sgen_mono_xdomain_checks = FALSE; static gboolean ptr_on_stack (void *ptr) { gpointer stack_start = &stack_start; SgenThreadInfo *info = mono_thread_info_current (); if (ptr >= stack_start && ptr < (gpointer)info->stack_end) return TRUE; return FALSE; } #ifdef SGEN_HEAVY_BINARY_PROTOCOL #undef HANDLE_PTR #define HANDLE_PTR(ptr,obj) do { \ gpointer o = *(gpointer*)(ptr); \ if ((o)) { \ gpointer d = ((char*)dest) + ((char*)(ptr) - (char*)(obj)); \ binary_protocol_wbarrier (d, o, (gpointer) SGEN_LOAD_VTABLE (o)); \ } \ } while (0) static void scan_object_for_binary_protocol_copy_wbarrier (gpointer dest, char *start, mword desc) { #define SCAN_OBJECT_NOVTABLE #include "sgen-scan-object.h" } #endif void mono_gc_wbarrier_value_copy (gpointer dest, gpointer src, int count, MonoClass *klass) { HEAVY_STAT (++stat_wbarrier_value_copy); g_assert (klass->valuetype); SGEN_LOG (8, "Adding value remset at %p, count %d, descr %p for class %s (%p)", dest, count, klass->gc_descr, klass->name, klass); if (sgen_ptr_in_nursery (dest) || ptr_on_stack (dest) || !SGEN_CLASS_HAS_REFERENCES (klass)) { size_t element_size = mono_class_value_size (klass, NULL); size_t size = count * element_size; mono_gc_memmove_atomic (dest, src, size); return; } #ifdef SGEN_HEAVY_BINARY_PROTOCOL if (binary_protocol_is_heavy_enabled ()) { size_t element_size = mono_class_value_size (klass, NULL); int i; for (i = 0; i < count; ++i) { scan_object_for_binary_protocol_copy_wbarrier ((char*)dest + i * element_size, (char*)src + i * element_size - sizeof (MonoObject), (mword) klass->gc_descr); } } #endif sgen_get_remset ()->wbarrier_value_copy (dest, src, count, mono_class_value_size (klass, NULL)); } /** * mono_gc_wbarrier_object_copy: * * Write barrier to call when obj is the result of a clone or copy of an object. */ void mono_gc_wbarrier_object_copy (MonoObject* obj, MonoObject *src) { int size; HEAVY_STAT (++stat_wbarrier_object_copy); if (sgen_ptr_in_nursery (obj) || ptr_on_stack (obj)) { size = mono_object_class (obj)->instance_size; mono_gc_memmove_aligned ((char*)obj + sizeof (MonoObject), (char*)src + sizeof (MonoObject), size - sizeof (MonoObject)); return; } #ifdef SGEN_HEAVY_BINARY_PROTOCOL if (binary_protocol_is_heavy_enabled ()) scan_object_for_binary_protocol_copy_wbarrier (obj, (char*)src, (mword) src->vtable->gc_descr); #endif sgen_get_remset ()->wbarrier_object_copy (obj, src); } #define ALIGN_TO(val,align) ((((guint64)val) + ((align) - 1)) & ~((align) - 1)) /* Vtable of the objects used to fill out nursery fragments before a collection */ static MonoVTable *array_fill_vtable; MonoVTable* sgen_client_get_array_fill_vtable (void) { if (!array_fill_vtable) { static MonoClass klass; static char _vtable[sizeof(MonoVTable)+8]; MonoVTable* vtable = (MonoVTable*) ALIGN_TO(_vtable, 8); gsize bmap; MonoDomain *domain = mono_get_root_domain (); g_assert (domain); klass.element_class = mono_defaults.byte_class; klass.rank = 1; klass.instance_size = sizeof (MonoArray); klass.sizes.element_size = 1; klass.name = "array_filler_type"; vtable->klass = &klass; bmap = 0; vtable->gc_descr = mono_gc_make_descr_for_array (TRUE, &bmap, 0, 1); vtable->rank = 1; array_fill_vtable = vtable; } return array_fill_vtable; } gboolean sgen_client_array_fill_range (char *start, size_t size) { MonoArray *o; if (size < sizeof (MonoArray)) { memset (start, 0, size); return FALSE; } o = (MonoArray*)start; o->obj.vtable = sgen_client_get_array_fill_vtable (); /* Mark this as not a real object */ o->obj.synchronisation = GINT_TO_POINTER (-1); o->bounds = NULL; o->max_length = (mono_array_size_t)(size - sizeof (MonoArray)); return TRUE; } static MonoGCFinalizerCallbacks fin_callbacks; guint mono_gc_get_vtable_bits (MonoClass *class) { guint res = 0; /* FIXME move this to the bridge code */ if (sgen_need_bridge_processing ()) { switch (sgen_bridge_class_kind (class)) { case GC_BRIDGE_TRANSPARENT_BRIDGE_CLASS: case GC_BRIDGE_OPAQUE_BRIDGE_CLASS: res = SGEN_GC_BIT_BRIDGE_OBJECT; break; case GC_BRIDGE_OPAQUE_CLASS: res = SGEN_GC_BIT_BRIDGE_OPAQUE_OBJECT; break; case GC_BRIDGE_TRANSPARENT_CLASS: break; } } if (fin_callbacks.is_class_finalization_aware) { if (fin_callbacks.is_class_finalization_aware (class)) res |= SGEN_GC_BIT_FINALIZER_AWARE; } return res; } static gboolean is_finalization_aware (MonoObject *obj) { MonoVTable *vt = ((MonoVTable*)SGEN_LOAD_VTABLE (obj)); return (vt->gc_bits & SGEN_GC_BIT_FINALIZER_AWARE) == SGEN_GC_BIT_FINALIZER_AWARE; } void sgen_client_object_queued_for_finalization (MonoObject *obj) { if (fin_callbacks.object_queued_for_finalization && is_finalization_aware (obj)) fin_callbacks.object_queued_for_finalization (obj); } void mono_gc_register_finalizer_callbacks (MonoGCFinalizerCallbacks *callbacks) { if (callbacks->version != MONO_GC_FINALIZER_EXTENSION_VERSION) g_error ("Invalid finalizer callback version. Expected %d but got %d\n", MONO_GC_FINALIZER_EXTENSION_VERSION, callbacks->version); fin_callbacks = *callbacks; } typedef struct _EphemeronLinkNode EphemeronLinkNode; struct _EphemeronLinkNode { EphemeronLinkNode *next; char *array; }; typedef struct { void *key; void *value; } Ephemeron; static EphemeronLinkNode *ephemeron_list; /* LOCKING: requires that the GC lock is held */ static void null_ephemerons_for_domain (MonoDomain *domain) { EphemeronLinkNode *current = ephemeron_list, *prev = NULL; while (current) { MonoObject *object = (MonoObject*)current->array; if (object) SGEN_ASSERT (0, object->vtable, "Can't have objects without vtables."); if (object && object->vtable->domain == domain) { EphemeronLinkNode *tmp = current; if (prev) prev->next = current->next; else ephemeron_list = current->next; current = current->next; sgen_free_internal (tmp, INTERNAL_MEM_EPHEMERON_LINK); } else { prev = current; current = current->next; } } } /* LOCKING: requires that the GC lock is held */ void sgen_client_clear_unreachable_ephemerons (ScanCopyContext ctx) { CopyOrMarkObjectFunc copy_func = ctx.ops->copy_or_mark_object; SgenGrayQueue *queue = ctx.queue; EphemeronLinkNode *current = ephemeron_list, *prev = NULL; MonoArray *array; Ephemeron *cur, *array_end; char *tombstone; while (current) { char *object = current->array; if (!sgen_is_object_alive_for_current_gen (object)) { EphemeronLinkNode *tmp = current; SGEN_LOG (5, "Dead Ephemeron array at %p", object); if (prev) prev->next = current->next; else ephemeron_list = current->next; current = current->next; sgen_free_internal (tmp, INTERNAL_MEM_EPHEMERON_LINK); continue; } copy_func ((void**)&object, queue); current->array = object; SGEN_LOG (5, "Clearing unreachable entries for ephemeron array at %p", object); array = (MonoArray*)object; cur = mono_array_addr (array, Ephemeron, 0); array_end = cur + mono_array_length_fast (array); tombstone = (char*)((MonoVTable*)SGEN_LOAD_VTABLE (object))->domain->ephemeron_tombstone; for (; cur < array_end; ++cur) { char *key = (char*)cur->key; if (!key || key == tombstone) continue; SGEN_LOG (5, "[%td] key %p (%s) value %p (%s)", cur - mono_array_addr (array, Ephemeron, 0), key, sgen_is_object_alive_for_current_gen (key) ? "reachable" : "unreachable", cur->value, cur->value && sgen_is_object_alive_for_current_gen (cur->value) ? "reachable" : "unreachable"); if (!sgen_is_object_alive_for_current_gen (key)) { cur->key = tombstone; cur->value = NULL; continue; } } prev = current; current = current->next; } } /* LOCKING: requires that the GC lock is held Limitations: We scan all ephemerons on every collection since the current design doesn't allow for a simple nursery/mature split. */ gboolean sgen_client_mark_ephemerons (ScanCopyContext ctx) { CopyOrMarkObjectFunc copy_func = ctx.ops->copy_or_mark_object; SgenGrayQueue *queue = ctx.queue; gboolean nothing_marked = TRUE; EphemeronLinkNode *current = ephemeron_list; MonoArray *array; Ephemeron *cur, *array_end; char *tombstone; for (current = ephemeron_list; current; current = current->next) { char *object = current->array; SGEN_LOG (5, "Ephemeron array at %p", object); /*It has to be alive*/ if (!sgen_is_object_alive_for_current_gen (object)) { SGEN_LOG (5, "\tnot reachable"); continue; } copy_func ((void**)&object, queue); array = (MonoArray*)object; cur = mono_array_addr (array, Ephemeron, 0); array_end = cur + mono_array_length_fast (array); tombstone = (char*)((MonoVTable*)SGEN_LOAD_VTABLE (object))->domain->ephemeron_tombstone; for (; cur < array_end; ++cur) { char *key = cur->key; if (!key || key == tombstone) continue; SGEN_LOG (5, "[%td] key %p (%s) value %p (%s)", cur - mono_array_addr (array, Ephemeron, 0), key, sgen_is_object_alive_for_current_gen (key) ? "reachable" : "unreachable", cur->value, cur->value && sgen_is_object_alive_for_current_gen (cur->value) ? "reachable" : "unreachable"); if (sgen_is_object_alive_for_current_gen (key)) { char *value = cur->value; copy_func ((void**)&cur->key, queue); if (value) { if (!sgen_is_object_alive_for_current_gen (value)) nothing_marked = FALSE; copy_func ((void**)&cur->value, queue); } } } } SGEN_LOG (5, "Ephemeron run finished. Is it done %d", nothing_marked); return nothing_marked; } gboolean mono_gc_ephemeron_array_add (MonoObject *obj) { EphemeronLinkNode *node; LOCK_GC; node = sgen_alloc_internal (INTERNAL_MEM_EPHEMERON_LINK); if (!node) { UNLOCK_GC; return FALSE; } node->array = (char*)obj; node->next = ephemeron_list; ephemeron_list = node; SGEN_LOG (5, "Registered ephemeron array %p", obj); UNLOCK_GC; return TRUE; } static gboolean need_remove_object_for_domain (char *start, MonoDomain *domain) { if (mono_object_domain (start) == domain) { SGEN_LOG (4, "Need to cleanup object %p", start); binary_protocol_cleanup (start, (gpointer)SGEN_LOAD_VTABLE (start), sgen_safe_object_get_size ((MonoObject*)start)); return TRUE; } return FALSE; } static void process_object_for_domain_clearing (char *start, MonoDomain *domain) { GCVTable *vt = (GCVTable*)SGEN_LOAD_VTABLE (start); if (vt->klass == mono_defaults.internal_thread_class) g_assert (mono_object_domain (start) == mono_get_root_domain ()); /* The object could be a proxy for an object in the domain we're deleting. */ #ifndef DISABLE_REMOTING if (mono_defaults.real_proxy_class->supertypes && mono_class_has_parent_fast (vt->klass, mono_defaults.real_proxy_class)) { MonoObject *server = ((MonoRealProxy*)start)->unwrapped_server; /* The server could already have been zeroed out, so we need to check for that, too. */ if (server && (!SGEN_LOAD_VTABLE (server) || mono_object_domain (server) == domain)) { SGEN_LOG (4, "Cleaning up remote pointer in %p to object %p", start, server); ((MonoRealProxy*)start)->unwrapped_server = NULL; } } #endif } static gboolean clear_domain_process_object (char *obj, MonoDomain *domain) { gboolean remove; process_object_for_domain_clearing (obj, domain); remove = need_remove_object_for_domain (obj, domain); if (remove && ((MonoObject*)obj)->synchronisation) { void **dislink = mono_monitor_get_object_monitor_weak_link ((MonoObject*)obj); if (dislink) sgen_register_disappearing_link (NULL, dislink, FALSE, TRUE); } return remove; } static void clear_domain_process_minor_object_callback (char *obj, size_t size, MonoDomain *domain) { if (clear_domain_process_object (obj, domain)) { CANARIFY_SIZE (size); memset (obj, 0, size); } } static void clear_domain_process_major_object_callback (char *obj, size_t size, MonoDomain *domain) { clear_domain_process_object (obj, domain); } static void clear_domain_free_major_non_pinned_object_callback (char *obj, size_t size, MonoDomain *domain) { if (need_remove_object_for_domain (obj, domain)) major_collector.free_non_pinned_object (obj, size); } static void clear_domain_free_major_pinned_object_callback (char *obj, size_t size, MonoDomain *domain) { if (need_remove_object_for_domain (obj, domain)) major_collector.free_pinned_object (obj, size); } /* * When appdomains are unloaded we can easily remove objects that have finalizers, * but all the others could still be present in random places on the heap. * We need a sweep to get rid of them even though it's going to be costly * with big heaps. * The reason we need to remove them is because we access the vtable and class * structures to know the object size and the reference bitmap: once the domain is * unloaded the point to random memory. */ void mono_gc_clear_domain (MonoDomain * domain) { LOSObject *bigobj, *prev; int i; LOCK_GC; binary_protocol_domain_unload_begin (domain); sgen_stop_world (0); if (sgen_concurrent_collection_in_progress ()) sgen_perform_collection (0, GENERATION_OLD, "clear domain", TRUE); SGEN_ASSERT (0, !sgen_concurrent_collection_in_progress (), "We just ordered a synchronous collection. Why are we collecting concurrently?"); major_collector.finish_sweeping (); sgen_process_fin_stage_entries (); sgen_process_dislink_stage_entries (); sgen_clear_nursery_fragments (); if (sgen_mono_xdomain_checks && domain != mono_get_root_domain ()) { sgen_scan_for_registered_roots_in_domain (domain, ROOT_TYPE_NORMAL); sgen_scan_for_registered_roots_in_domain (domain, ROOT_TYPE_WBARRIER); sgen_check_for_xdomain_refs (); } /*Ephemerons and dislinks must be processed before LOS since they might end up pointing to memory returned to the OS.*/ null_ephemerons_for_domain (domain); for (i = GENERATION_NURSERY; i < GENERATION_MAX; ++i) sgen_null_links_for_domain (domain, i); for (i = GENERATION_NURSERY; i < GENERATION_MAX; ++i) sgen_remove_finalizers_for_domain (domain, i); sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data, (IterateObjectCallbackFunc)clear_domain_process_minor_object_callback, domain, FALSE); /* We need two passes over major and large objects because freeing such objects might give their memory back to the OS (in the case of large objects) or obliterate its vtable (pinned objects with major-copying or pinned and non-pinned objects with major-mark&sweep), but we might need to dereference a pointer from an object to another object if the first object is a proxy. */ major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, (IterateObjectCallbackFunc)clear_domain_process_major_object_callback, domain); for (bigobj = los_object_list; bigobj; bigobj = bigobj->next) clear_domain_process_object (bigobj->data, domain); prev = NULL; for (bigobj = los_object_list; bigobj;) { if (need_remove_object_for_domain (bigobj->data, domain)) { LOSObject *to_free = bigobj; if (prev) prev->next = bigobj->next; else los_object_list = bigobj->next; bigobj = bigobj->next; SGEN_LOG (4, "Freeing large object %p", bigobj->data); sgen_los_free_object (to_free); continue; } prev = bigobj; bigobj = bigobj->next; } major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_NON_PINNED, (IterateObjectCallbackFunc)clear_domain_free_major_non_pinned_object_callback, domain); major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_PINNED, (IterateObjectCallbackFunc)clear_domain_free_major_pinned_object_callback, domain); if (domain == mono_get_root_domain ()) { sgen_pin_stats_print_class_stats (); sgen_object_layout_dump (stdout); } sgen_restart_world (0, NULL); binary_protocol_domain_unload_end (domain); binary_protocol_flush_buffers (FALSE); UNLOCK_GC; } static MonoMethod* alloc_method_cache [ATYPE_NUM]; static gboolean use_managed_allocator = TRUE; #ifdef MANAGED_ALLOCATION #define OPDEF(a,b,c,d,e,f,g,h,i,j) \ a = i, enum { #include "mono/cil/opcode.def" CEE_LAST }; #undef OPDEF #ifdef HAVE_KW_THREAD #define EMIT_TLS_ACCESS_NEXT_ADDR(mb) do { \ mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX); \ mono_mb_emit_byte ((mb), CEE_MONO_TLS); \ mono_mb_emit_i4 ((mb), TLS_KEY_SGEN_TLAB_NEXT_ADDR); \ } while (0) #define EMIT_TLS_ACCESS_TEMP_END(mb) do { \ mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX); \ mono_mb_emit_byte ((mb), CEE_MONO_TLS); \ mono_mb_emit_i4 ((mb), TLS_KEY_SGEN_TLAB_TEMP_END); \ } while (0) #else #if defined(__APPLE__) || defined (HOST_WIN32) #define EMIT_TLS_ACCESS_NEXT_ADDR(mb) do { \ mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX); \ mono_mb_emit_byte ((mb), CEE_MONO_TLS); \ mono_mb_emit_i4 ((mb), TLS_KEY_SGEN_THREAD_INFO); \ mono_mb_emit_icon ((mb), MONO_STRUCT_OFFSET (SgenThreadInfo, tlab_next_addr)); \ mono_mb_emit_byte ((mb), CEE_ADD); \ mono_mb_emit_byte ((mb), CEE_LDIND_I); \ } while (0) #define EMIT_TLS_ACCESS_TEMP_END(mb) do { \ mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX); \ mono_mb_emit_byte ((mb), CEE_MONO_TLS); \ mono_mb_emit_i4 ((mb), TLS_KEY_SGEN_THREAD_INFO); \ mono_mb_emit_icon ((mb), MONO_STRUCT_OFFSET (SgenThreadInfo, tlab_temp_end)); \ mono_mb_emit_byte ((mb), CEE_ADD); \ mono_mb_emit_byte ((mb), CEE_LDIND_I); \ } while (0) #else #define EMIT_TLS_ACCESS_NEXT_ADDR(mb) do { g_error ("sgen is not supported when using --with-tls=pthread.\n"); } while (0) #define EMIT_TLS_ACCESS_TEMP_END(mb) do { g_error ("sgen is not supported when using --with-tls=pthread.\n"); } while (0) #endif #endif /* FIXME: Do this in the JIT, where specialized allocation sequences can be created * for each class. This is currently not easy to do, as it is hard to generate basic * blocks + branches, but it is easy with the linear IL codebase. * * For this to work we'd need to solve the TLAB race, first. Now we * require the allocator to be in a few known methods to make sure * that they are executed atomically via the restart mechanism. */ static MonoMethod* create_allocator (int atype) { int p_var, size_var; guint32 slowpath_branch, max_size_branch; MonoMethodBuilder *mb; MonoMethod *res; MonoMethodSignature *csig; static gboolean registered = FALSE; int tlab_next_addr_var, new_next_var; int num_params, i; const char *name = NULL; AllocatorWrapperInfo *info; if (!registered) { mono_register_jit_icall (mono_gc_alloc_obj, "mono_gc_alloc_obj", mono_create_icall_signature ("object ptr int"), FALSE); mono_register_jit_icall (mono_gc_alloc_vector, "mono_gc_alloc_vector", mono_create_icall_signature ("object ptr int int"), FALSE); mono_register_jit_icall (mono_gc_alloc_string, "mono_gc_alloc_string", mono_create_icall_signature ("object ptr int int32"), FALSE); registered = TRUE; } if (atype == ATYPE_SMALL) { num_params = 2; name = "AllocSmall"; } else if (atype == ATYPE_NORMAL) { num_params = 1; name = "Alloc"; } else if (atype == ATYPE_VECTOR) { num_params = 2; name = "AllocVector"; } else if (atype == ATYPE_STRING) { num_params = 2; name = "AllocString"; } else { g_assert_not_reached (); } csig = mono_metadata_signature_alloc (mono_defaults.corlib, num_params); if (atype == ATYPE_STRING) { csig->ret = &mono_defaults.string_class->byval_arg; csig->params [0] = &mono_defaults.int_class->byval_arg; csig->params [1] = &mono_defaults.int32_class->byval_arg; } else { csig->ret = &mono_defaults.object_class->byval_arg; for (i = 0; i < num_params; ++i) csig->params [i] = &mono_defaults.int_class->byval_arg; } mb = mono_mb_new (mono_defaults.object_class, name, MONO_WRAPPER_ALLOC); #ifndef DISABLE_JIT size_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg); if (atype == ATYPE_SMALL) { /* size_var = size_arg */ mono_mb_emit_ldarg (mb, 1); mono_mb_emit_stloc (mb, size_var); } else if (atype == ATYPE_NORMAL) { /* size = vtable->klass->instance_size; */ mono_mb_emit_ldarg (mb, 0); mono_mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoVTable, klass)); mono_mb_emit_byte (mb, CEE_ADD); mono_mb_emit_byte (mb, CEE_LDIND_I); mono_mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoClass, instance_size)); mono_mb_emit_byte (mb, CEE_ADD); /* FIXME: assert instance_size stays a 4 byte integer */ mono_mb_emit_byte (mb, CEE_LDIND_U4); mono_mb_emit_byte (mb, CEE_CONV_I); mono_mb_emit_stloc (mb, size_var); } else if (atype == ATYPE_VECTOR) { MonoExceptionClause *clause; int pos, pos_leave, pos_error; MonoClass *oom_exc_class; MonoMethod *ctor; /* * n > MONO_ARRAY_MAX_INDEX => OutOfMemoryException * n < 0 => OverflowException * * We can do an unsigned comparison to catch both cases, then in the error * case compare signed to distinguish between them. */ mono_mb_emit_ldarg (mb, 1); mono_mb_emit_icon (mb, MONO_ARRAY_MAX_INDEX); mono_mb_emit_byte (mb, CEE_CONV_U); pos = mono_mb_emit_short_branch (mb, CEE_BLE_UN_S); mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); mono_mb_emit_byte (mb, CEE_MONO_NOT_TAKEN); mono_mb_emit_ldarg (mb, 1); mono_mb_emit_icon (mb, 0); pos_error = mono_mb_emit_short_branch (mb, CEE_BLT_S); mono_mb_emit_exception (mb, "OutOfMemoryException", NULL); mono_mb_patch_short_branch (mb, pos_error); mono_mb_emit_exception (mb, "OverflowException", NULL); mono_mb_patch_short_branch (mb, pos); clause = mono_image_alloc0 (mono_defaults.corlib, sizeof (MonoExceptionClause)); clause->try_offset = mono_mb_get_label (mb); /* vtable->klass->sizes.element_size */ mono_mb_emit_ldarg (mb, 0); mono_mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoVTable, klass)); mono_mb_emit_byte (mb, CEE_ADD); mono_mb_emit_byte (mb, CEE_LDIND_I); mono_mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoClass, sizes)); mono_mb_emit_byte (mb, CEE_ADD); mono_mb_emit_byte (mb, CEE_LDIND_U4); mono_mb_emit_byte (mb, CEE_CONV_I); /* * n */ mono_mb_emit_ldarg (mb, 1); mono_mb_emit_byte (mb, CEE_MUL_OVF_UN); /* + sizeof (MonoArray) */ mono_mb_emit_icon (mb, sizeof (MonoArray)); mono_mb_emit_byte (mb, CEE_ADD_OVF_UN); mono_mb_emit_stloc (mb, size_var); pos_leave = mono_mb_emit_branch (mb, CEE_LEAVE); /* catch */ clause->flags = MONO_EXCEPTION_CLAUSE_NONE; clause->try_len = mono_mb_get_pos (mb) - clause->try_offset; clause->data.catch_class = mono_class_from_name (mono_defaults.corlib, "System", "OverflowException"); g_assert (clause->data.catch_class); clause->handler_offset = mono_mb_get_label (mb); oom_exc_class = mono_class_from_name (mono_defaults.corlib, "System", "OutOfMemoryException"); g_assert (oom_exc_class); ctor = mono_class_get_method_from_name (oom_exc_class, ".ctor", 0); g_assert (ctor); mono_mb_emit_byte (mb, CEE_POP); mono_mb_emit_op (mb, CEE_NEWOBJ, ctor); mono_mb_emit_byte (mb, CEE_THROW); clause->handler_len = mono_mb_get_pos (mb) - clause->handler_offset; mono_mb_set_clauses (mb, 1, clause); mono_mb_patch_branch (mb, pos_leave); /* end catch */ } else if (atype == ATYPE_STRING) { int pos; /* * a string allocator method takes the args: (vtable, len) * * bytes = offsetof (MonoString, chars) + ((len + 1) * 2) * * condition: * * bytes <= INT32_MAX - (SGEN_ALLOC_ALIGN - 1) * * therefore: * * offsetof (MonoString, chars) + ((len + 1) * 2) <= INT32_MAX - (SGEN_ALLOC_ALIGN - 1) * len <= (INT32_MAX - (SGEN_ALLOC_ALIGN - 1) - offsetof (MonoString, chars)) / 2 - 1 */ mono_mb_emit_ldarg (mb, 1); mono_mb_emit_icon (mb, (INT32_MAX - (SGEN_ALLOC_ALIGN - 1) - MONO_STRUCT_OFFSET (MonoString, chars)) / 2 - 1); pos = mono_mb_emit_short_branch (mb, MONO_CEE_BLE_UN_S); mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); mono_mb_emit_byte (mb, CEE_MONO_NOT_TAKEN); mono_mb_emit_exception (mb, "OutOfMemoryException", NULL); mono_mb_patch_short_branch (mb, pos); mono_mb_emit_ldarg (mb, 1); mono_mb_emit_icon (mb, 1); mono_mb_emit_byte (mb, MONO_CEE_SHL); //WE manually fold the above + 2 here mono_mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoString, chars) + 2); mono_mb_emit_byte (mb, CEE_ADD); mono_mb_emit_stloc (mb, size_var); } else { g_assert_not_reached (); } if (atype != ATYPE_SMALL) { /* size += ALLOC_ALIGN - 1; */ mono_mb_emit_ldloc (mb, size_var); mono_mb_emit_icon (mb, SGEN_ALLOC_ALIGN - 1); mono_mb_emit_byte (mb, CEE_ADD); /* size &= ~(ALLOC_ALIGN - 1); */ mono_mb_emit_icon (mb, ~(SGEN_ALLOC_ALIGN - 1)); mono_mb_emit_byte (mb, CEE_AND); mono_mb_emit_stloc (mb, size_var); } /* if (size > MAX_SMALL_OBJ_SIZE) goto slowpath */ if (atype != ATYPE_SMALL) { mono_mb_emit_ldloc (mb, size_var); mono_mb_emit_icon (mb, SGEN_MAX_SMALL_OBJ_SIZE); max_size_branch = mono_mb_emit_short_branch (mb, MONO_CEE_BGT_UN_S); } /* * We need to modify tlab_next, but the JIT only supports reading, so we read * another tls var holding its address instead. */ /* tlab_next_addr (local) = tlab_next_addr (TLS var) */ tlab_next_addr_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg); EMIT_TLS_ACCESS_NEXT_ADDR (mb); mono_mb_emit_stloc (mb, tlab_next_addr_var); /* p = (void**)tlab_next; */ p_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg); mono_mb_emit_ldloc (mb, tlab_next_addr_var); mono_mb_emit_byte (mb, CEE_LDIND_I); mono_mb_emit_stloc (mb, p_var); /* new_next = (char*)p + size; */ new_next_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg); mono_mb_emit_ldloc (mb, p_var); mono_mb_emit_ldloc (mb, size_var); mono_mb_emit_byte (mb, CEE_CONV_I); mono_mb_emit_byte (mb, CEE_ADD); mono_mb_emit_stloc (mb, new_next_var); /* if (G_LIKELY (new_next < tlab_temp_end)) */ mono_mb_emit_ldloc (mb, new_next_var); EMIT_TLS_ACCESS_TEMP_END (mb); slowpath_branch = mono_mb_emit_short_branch (mb, MONO_CEE_BLT_UN_S); /* Slowpath */ if (atype != ATYPE_SMALL) mono_mb_patch_short_branch (mb, max_size_branch); mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); mono_mb_emit_byte (mb, CEE_MONO_NOT_TAKEN); /* FIXME: mono_gc_alloc_obj takes a 'size_t' as an argument, not an int32 */ mono_mb_emit_ldarg (mb, 0); mono_mb_emit_ldloc (mb, size_var); if (atype == ATYPE_NORMAL || atype == ATYPE_SMALL) { mono_mb_emit_icall (mb, mono_gc_alloc_obj); } else if (atype == ATYPE_VECTOR) { mono_mb_emit_ldarg (mb, 1); mono_mb_emit_icall (mb, mono_gc_alloc_vector); } else if (atype == ATYPE_STRING) { mono_mb_emit_ldarg (mb, 1); mono_mb_emit_icall (mb, mono_gc_alloc_string); } else { g_assert_not_reached (); } mono_mb_emit_byte (mb, CEE_RET); /* Fastpath */ mono_mb_patch_short_branch (mb, slowpath_branch); /* FIXME: Memory barrier */ /* tlab_next = new_next */ mono_mb_emit_ldloc (mb, tlab_next_addr_var); mono_mb_emit_ldloc (mb, new_next_var); mono_mb_emit_byte (mb, CEE_STIND_I); /*The tlab store must be visible before the the vtable store. This could be replaced with a DDS but doing it with IL would be tricky. */ mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); mono_mb_emit_byte (mb, CEE_MONO_MEMORY_BARRIER); mono_mb_emit_i4 (mb, MONO_MEMORY_BARRIER_REL); /* *p = vtable; */ mono_mb_emit_ldloc (mb, p_var); mono_mb_emit_ldarg (mb, 0); mono_mb_emit_byte (mb, CEE_STIND_I); if (atype == ATYPE_VECTOR) { /* arr->max_length = max_length; */ mono_mb_emit_ldloc (mb, p_var); mono_mb_emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoArray, max_length)); mono_mb_emit_ldarg (mb, 1); #ifdef MONO_BIG_ARRAYS mono_mb_emit_byte (mb, CEE_STIND_I); #else mono_mb_emit_byte (mb, CEE_STIND_I4); #endif } else if (atype == ATYPE_STRING) { /* need to set length and clear the last char */ /* s->length = len; */ mono_mb_emit_ldloc (mb, p_var); mono_mb_emit_icon (mb, MONO_STRUCT_OFFSET (MonoString, length)); mono_mb_emit_byte (mb, MONO_CEE_ADD); mono_mb_emit_ldarg (mb, 1); mono_mb_emit_byte (mb, MONO_CEE_STIND_I4); /* s->chars [len] = 0; */ mono_mb_emit_ldloc (mb, p_var); mono_mb_emit_ldloc (mb, size_var); mono_mb_emit_icon (mb, 2); mono_mb_emit_byte (mb, MONO_CEE_SUB); mono_mb_emit_byte (mb, MONO_CEE_ADD); mono_mb_emit_icon (mb, 0); mono_mb_emit_byte (mb, MONO_CEE_STIND_I2); } /* We must make sure both vtable and max_length are globaly visible before returning to managed land. */ mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); mono_mb_emit_byte (mb, CEE_MONO_MEMORY_BARRIER); mono_mb_emit_i4 (mb, MONO_MEMORY_BARRIER_REL); /* return p */ mono_mb_emit_ldloc (mb, p_var); mono_mb_emit_byte (mb, CEE_RET); #endif res = mono_mb_create_method (mb, csig, 8); mono_mb_free (mb); mono_method_get_header (res)->init_locals = FALSE; info = mono_image_alloc0 (mono_defaults.corlib, sizeof (AllocatorWrapperInfo)); info->gc_name = "sgen"; info->alloc_type = atype; mono_marshal_set_wrapper_info (res, info); return res; } #endif int mono_gc_get_aligned_size_for_allocator (int size) { int aligned_size = size; aligned_size += SGEN_ALLOC_ALIGN - 1; aligned_size &= ~(SGEN_ALLOC_ALIGN - 1); return aligned_size; } /* * Generate an allocator method implementing the fast path of mono_gc_alloc_obj (). * The signature of the called method is: * object allocate (MonoVTable *vtable) */ MonoMethod* mono_gc_get_managed_allocator (MonoClass *klass, gboolean for_box, gboolean known_instance_size) { #ifdef MANAGED_ALLOCATION if (collect_before_allocs) return NULL; if (!mono_runtime_has_tls_get ()) return NULL; if (klass->instance_size > tlab_size) return NULL; if (known_instance_size && ALIGN_TO (klass->instance_size, SGEN_ALLOC_ALIGN) >= SGEN_MAX_SMALL_OBJ_SIZE) return NULL; if (klass->has_finalize || mono_class_is_marshalbyref (klass) || (mono_profiler_get_events () & MONO_PROFILE_ALLOCATIONS)) return NULL; if (klass->rank) return NULL; if (klass->byval_arg.type == MONO_TYPE_STRING) return mono_gc_get_managed_allocator_by_type (ATYPE_STRING); /* Generic classes have dynamic field and can go above MAX_SMALL_OBJ_SIZE. */ if (known_instance_size) return mono_gc_get_managed_allocator_by_type (ATYPE_SMALL); else return mono_gc_get_managed_allocator_by_type (ATYPE_NORMAL); #else return NULL; #endif } MonoMethod* mono_gc_get_managed_array_allocator (MonoClass *klass) { #ifdef MANAGED_ALLOCATION if (klass->rank != 1) return NULL; if (!mono_runtime_has_tls_get ()) return NULL; if (mono_profiler_get_events () & MONO_PROFILE_ALLOCATIONS) return NULL; if (has_per_allocation_action) return NULL; g_assert (!mono_class_has_finalizer (klass) && !mono_class_is_marshalbyref (klass)); return mono_gc_get_managed_allocator_by_type (ATYPE_VECTOR); #else return NULL; #endif } void sgen_set_use_managed_allocator (gboolean flag) { use_managed_allocator = flag; } MonoMethod* mono_gc_get_managed_allocator_by_type (int atype) { #ifdef MANAGED_ALLOCATION MonoMethod *res; if (!use_managed_allocator) return NULL; if (!mono_runtime_has_tls_get ()) return NULL; res = alloc_method_cache [atype]; if (res) return res; res = create_allocator (atype); LOCK_GC; if (alloc_method_cache [atype]) { mono_free_method (res); res = alloc_method_cache [atype]; } else { mono_memory_barrier (); alloc_method_cache [atype] = res; } UNLOCK_GC; return res; #else return NULL; #endif } guint32 mono_gc_get_managed_allocator_types (void) { return ATYPE_NUM; } gboolean sgen_is_managed_allocator (MonoMethod *method) { int i; for (i = 0; i < ATYPE_NUM; ++i) if (method == alloc_method_cache [i]) return TRUE; return FALSE; } gboolean sgen_has_managed_allocator (void) { int i; for (i = 0; i < ATYPE_NUM; ++i) if (alloc_method_cache [i]) return TRUE; return FALSE; } const char* sgen_client_description_for_internal_mem_type (int type) { switch (type) { case INTERNAL_MEM_EPHEMERON_LINK: return "ephemeron-link"; default: return NULL; } } void sgen_client_pre_collection_checks (void) { if (sgen_mono_xdomain_checks) { sgen_clear_nursery_fragments (); sgen_check_for_xdomain_refs (); } } void sgen_client_init (void) { sgen_register_fixed_internal_mem_type (INTERNAL_MEM_EPHEMERON_LINK, sizeof (EphemeronLinkNode)); } gboolean sgen_client_handle_gc_debug (const char *opt) { if (!strcmp (opt, "xdomain-checks")) { sgen_mono_xdomain_checks = TRUE; } else { return FALSE; } return TRUE; } void sgen_client_print_gc_debug_usage (void) { fprintf (stderr, " xdomain-checks\n"); } #endif