-/*
- * handle.c: Handle to object in native code
+/**
+ * \file
+ * Handle to object in native code
*
* Authors:
* - Ludovic Henry <ludovic@xamarin.com>
#include <mono/utils/atomic.h>
#include <mono/utils/mono-lazy-init.h>
#include <mono/utils/mono-threads.h>
+#ifdef HAVE_BACKTRACE_SYMBOLS
+#include <execinfo.h>
+#endif
+
/* TODO (missing pieces)
Add counters for:
We could then generate neat type safe wrappers.
*/
+/*
+ * NOTE: Async suspend
+ *
+ * If we are running with cooperative GC, all the handle stack
+ * manipulation will complete before a GC thread scans the handle
+ * stack. If we are using async suspend, however, a thread may be
+ * trying to allocate a new handle, or unwind the handle stack when
+ * the GC stops the world.
+ *
+ * In particular, we need to ensure that if the mutator thread is
+ * suspended while manipulating the handle stack, the stack is in a
+ * good enough state to be scanned. In particular, the size of each
+ * chunk should be updated before an object is written into the
+ * handle, and chunks to be scanned (between bottom and top) should
+ * always be valid.
+ *
+ * Note that the handle stack is scanned PRECISELY (see
+ * sgen_client_scan_thread_data ()). That means there should not be
+ * stale objects scanned. So when we manipulate the size of a chunk,
+ * wemust ensure that the newly scannable slot is either null or
+ * points to a valid value.
+ */
+
+#if defined(HAVE_BOEHM_GC) || defined(HAVE_NULL_GC)
+static HandleStack*
+new_handle_stack (void)
+{
+ return (HandleStack *)mono_gc_alloc_fixed (sizeof (HandleStack), MONO_GC_DESCRIPTOR_NULL, MONO_ROOT_SOURCE_HANDLE, "Thread Handle Stack");
+}
+
+static void
+free_handle_stack (HandleStack *stack)
+{
+ mono_gc_free_fixed (stack);
+}
+
+static HandleChunk*
+new_handle_chunk (void)
+{
+#if defined(HAVE_BOEHM_GC)
+ return (HandleChunk *)GC_MALLOC (sizeof (HandleChunk));
+#elif defined(HAVE_NULL_GC)
+ return (HandleChunk *)g_malloc (sizeof (HandleChunk));
+#endif
+}
+
+static void
+free_handle_chunk (HandleChunk *chunk)
+{
+#if defined(HAVE_NULL_GC)
+ g_free (chunk);
+#endif
+}
+#else
+static HandleStack*
+new_handle_stack (void)
+{
+ return g_new (HandleStack, 1);
+}
+
+static void
+free_handle_stack (HandleStack *stack)
+{
+ g_free (stack);
+}
+
+static HandleChunk*
+new_handle_chunk (void)
+{
+ return g_new (HandleChunk, 1);
+}
+
+static void
+free_handle_chunk (HandleChunk *chunk)
+{
+ g_free (chunk);
+}
+#endif
+
const MonoObjectHandle mono_null_value_handle = NULL;
#define THIS_IS_AN_OK_NUMBER_OF_HANDLES 100
+static HandleChunkElem*
+chunk_element (HandleChunk *chunk, int idx)
+{
+ return &chunk->elems[idx];
+}
+
+static HandleChunkElem*
+handle_to_chunk_element (MonoObjectHandle o)
+{
+ return (HandleChunkElem*)o;
+}
+
+/* Given a HandleChunkElem* search through the current handle stack to find its chunk and offset. */
+static HandleChunk*
+chunk_element_to_chunk_idx (HandleStack *stack, HandleChunkElem *elem, int *out_idx)
+{
+ HandleChunk *top = stack->top;
+ HandleChunk *cur = stack->bottom;
+
+ *out_idx = 0;
+
+ while (cur != NULL) {
+ HandleChunkElem *front = &cur->elems [0];
+ HandleChunkElem *back = &cur->elems [cur->size];
+
+ if (front <= elem && elem < back) {
+ *out_idx = (int)(elem - front);
+ return cur;
+ }
+
+ if (cur == top)
+ break; /* didn't find it. */
+ cur = cur->next;
+ }
+ return NULL;
+}
+
+#ifdef MONO_HANDLE_TRACK_OWNER
+#ifdef HAVE_BACKTRACE_SYMBOLS
+#define SET_BACKTRACE(btaddrs) do { \
+ backtrace(btaddrs, 7); \
+ } while (0)
+#else
+#define SET_BACKTRACE(btaddrs) 0
+#endif
+#define SET_OWNER(chunk,idx) do { (chunk)->elems[(idx)].owner = owner; SET_BACKTRACE (&((chunk)->elems[(idx)].backtrace_ips[0])); } while (0)
+#else
+#define SET_OWNER(chunk,idx) do { } while (0)
+#endif
+
+#ifdef MONO_HANDLE_TRACK_SP
+#define SET_SP(handles,chunk,idx) do { (chunk)->elems[(idx)].alloc_sp = handles->stackmark_sp; } while (0)
+#else
+#define SET_SP(handles,chunk,idx) do { } while (0)
+#endif
+
+#ifdef MONO_HANDLE_TRACK_SP
+void
+mono_handle_chunk_leak_check (HandleStack *handles) {
+ if (handles->stackmark_sp) {
+ /* walk back from the top to the topmost non-empty chunk */
+ HandleChunk *c = handles->top;
+ while (c && c->size <= 0 && c != handles->bottom) {
+ c = c->prev;
+ }
+ if (c == NULL || c->size == 0)
+ return;
+ g_assert (c && c->size > 0);
+ HandleChunkElem *e = chunk_element (c, c->size - 1);
+ if (e->alloc_sp < handles->stackmark_sp) {
+ /* If we get here, the topmost object on the handle stack was
+ * allocated from a function that is deeper in the call stack than
+ * the most recent HANDLE_FUNCTION_ENTER. That means it was
+ * probably not wrapped in a HANDLE_FUNCTION_ENTER/_RETURN pair
+ * and will never be reclaimed. */
+ g_warning ("Handle %p (object = %p) (allocated from \"%s\") is leaking.\n", e, e->o,
+#ifdef MONO_HANDLE_TRACK_OWNER
+ e->owner
+#else
+ "<unknown owner>"
+#endif
+ );
+ }
+ }
+}
+#endif
+
/* Actual handles implementation */
MonoRawHandle
-mono_handle_new (MonoObject *object)
+#ifndef MONO_HANDLE_TRACK_OWNER
+mono_handle_new (MonoObject *obj)
+#else
+mono_handle_new (MonoObject *obj, const char *owner)
+#endif
{
MonoThreadInfo *info = mono_thread_info_current ();
HandleStack *handles = (HandleStack *)info->handle_stack;
HandleChunk *top = handles->top;
+#ifdef MONO_HANDLE_TRACK_SP
+ mono_handle_chunk_leak_check (handles);
+#endif
retry:
if (G_LIKELY (top->size < OBJECTS_PER_HANDLES_CHUNK)) {
- MonoObject **h = &top->objects [top->size++];
- *h = object;
- return h;
+ int idx = top->size;
+ gpointer* objslot = &top->elems [idx].o;
+ /* can be interrupted anywhere here, so:
+ * 1. make sure the new slot is null
+ * 2. make the new slot scannable (increment size)
+ * 3. put a valid object in there
+ *
+ * (have to do 1 then 3 so that if we're interrupted
+ * between 1 and 2, the object is still live)
+ */
+ *objslot = NULL;
+ mono_memory_write_barrier ();
+ top->size++;
+ mono_memory_write_barrier ();
+ *objslot = obj;
+ SET_OWNER (top,idx);
+ SET_SP (handles, top, idx);
+ return objslot;
}
if (G_LIKELY (top->next)) {
+ top->next->size = 0;
+ /* make sure size == 0 is visible to a GC thread before it sees the new top */
+ mono_memory_write_barrier ();
top = top->next;
- top->size = 0;
handles->top = top;
goto retry;
}
- HandleChunk *new_chunk = g_new (HandleChunk, 1);
+ HandleChunk *new_chunk = new_handle_chunk ();
new_chunk->size = 0;
new_chunk->prev = top;
new_chunk->next = NULL;
+ /* make sure size == 0 before new chunk is visible */
+ mono_memory_write_barrier ();
top->next = new_chunk;
handles->top = new_chunk;
goto retry;
}
+MonoRawHandle
+#ifndef MONO_HANDLE_TRACK_OWNER
+mono_handle_new_interior (gpointer rawptr)
+#else
+mono_handle_new_interior (gpointer rawptr, const char *owner)
+#endif
+{
+ MonoThreadInfo *info = mono_thread_info_current ();
+ HandleStack *handles = (HandleStack *)info->handle_stack;
+ HandleChunk *top = handles->interior;
+#ifdef MONO_HANDLE_TRACK_SP
+ mono_handle_chunk_leak_check (handles);
+#endif
+ g_assert (top);
+
+ /*
+ * Don't extend the chunk now, interior handles are
+ * only used for icall arguments, they shouldn't
+ * overflow.
+ */
+ g_assert (top->size < OBJECTS_PER_HANDLES_CHUNK);
+ int idx = top->size;
+ gpointer *objslot = &top->elems [idx].o;
+ *objslot = NULL;
+ mono_memory_write_barrier ();
+ top->size++;
+ mono_memory_write_barrier ();
+ *objslot = rawptr;
+ SET_OWNER (top,idx);
+ SET_SP (handles, top, idx);
+ return objslot;
+}
HandleStack*
mono_handle_stack_alloc (void)
{
- HandleStack *stack = g_new (HandleStack, 1);
- HandleChunk *chunk = g_new (HandleChunk, 1);
+ HandleStack *stack = new_handle_stack ();
+ HandleChunk *chunk = new_handle_chunk ();
+ HandleChunk *interior = new_handle_chunk ();
- stack->top = stack->bottom = chunk;
- chunk->size = 0;
chunk->prev = chunk->next = NULL;
+ chunk->size = 0;
+ interior->prev = interior->next = NULL;
+ interior->size = 0;
+ mono_memory_write_barrier ();
+ stack->top = stack->bottom = chunk;
+ stack->interior = interior;
+#ifdef MONO_HANDLE_TRACK_SP
+ stack->stackmark_sp = NULL;
+#endif
return stack;
}
if (!stack)
return;
HandleChunk *c = stack->bottom;
+ stack->top = stack->bottom = NULL;
+ mono_memory_write_barrier ();
while (c) {
HandleChunk *next = c->next;
- g_free (c);
+ free_handle_chunk (c);
c = next;
}
- g_free (c);
- g_free (stack);
+ free_handle_chunk (c);
+ free_handle_chunk (stack->interior);
+ free_handle_stack (stack);
}
void
-mono_handle_stack_scan (HandleStack *stack, GcScanFunc func, gpointer gc_data)
+mono_handle_stack_free_domain (HandleStack *stack, MonoDomain *domain)
{
+ /* Called by the GC while clearing out objects of the given domain from the heap. */
+ /* If there are no handles-related bugs, there is nothing to do: if a
+ * thread accessed objects from the domain it was aborted, so any
+ * threads left alive cannot have any handles that point into the
+ * unloading domain. However if there is a handle leak, the handle stack is not */
+ if (!stack)
+ return;
+ /* Root domain only unloaded when mono is shutting down, don't need to check anything */
+ if (domain == mono_get_root_domain () || mono_runtime_is_shutting_down ())
+ return;
HandleChunk *cur = stack->bottom;
HandleChunk *last = stack->top;
-
if (!cur)
return;
+ while (cur) {
+ for (int idx = 0; idx < cur->size; ++idx) {
+ HandleChunkElem *elem = &cur->elems[idx];
+ if (!elem->o)
+ continue;
+ g_assert (mono_object_domain (elem->o) != domain);
+ }
+ if (cur == last)
+ break;
+ cur = cur->next;
+ }
+ /* We don't examine the interior pointers here because the GC treats
+ * them conservatively and anyway we don't have enough information here to
+ * find the object's vtable.
+ */
+}
+static void
+check_handle_stack_monotonic (HandleStack *stack)
+{
+ /* check that every allocated handle in the current handle stack is at no higher in the native stack than its predecessors */
+#ifdef MONO_HANDLE_TRACK_SP
+ HandleChunk *cur = stack->bottom;
+ HandleChunk *last = stack->top;
+ if (!cur)
+ return;
+ HandleChunkElem *prev = NULL;
+ gboolean monotonic = TRUE;
while (cur) {
- int i;
- for (i = 0; i < cur->size; ++i)
- func ((gpointer*)&cur->objects [i], gc_data);
+ for (int i = 0;i < cur->size; ++i) {
+ HandleChunkElem *elem = chunk_element (cur, i);
+ if (prev && elem->alloc_sp < prev->alloc_sp) {
+ monotonic = FALSE;
+ g_warning ("Handle %p (object %p) (allocated from \"%s\") is was allocated deeper in the call stack than its successor (allocated from \"%s\").", prev, prev->o,
+#ifdef MONO_HANDLE_TRACK_OWNER
+ prev->owner,
+ elem->owner
+#else
+ "unknown owner",
+ "unknown owner"
+#endif
+ );
+
+ }
+ prev = elem;
+ }
if (cur == last)
break;
cur = cur->next;
}
+ g_assert (monotonic);
+#endif
+}
+
+void
+mono_handle_stack_scan (HandleStack *stack, GcScanFunc func, gpointer gc_data, gboolean precise)
+{
+ if (precise) /* run just once (per handle stack) per GC */
+ check_handle_stack_monotonic (stack);
+ /*
+ We're called twice - on the imprecise pass we call func to pin the
+ objects where the handle points to its interior. On the precise
+ pass, we scan all the objects where the handles point to the start of
+ the object.
+
+ Note that if we're running, we know the world is stopped.
+ */
+ if (precise) {
+ HandleChunk *cur = stack->bottom;
+ HandleChunk *last = stack->top;
+
+ while (cur) {
+ for (int i = 0; i < cur->size; ++i) {
+ HandleChunkElem* elem = chunk_element (cur, i);
+ gpointer* obj_slot = &elem->o;
+ if (*obj_slot != NULL)
+ func (obj_slot, gc_data);
+ }
+ if (cur == last)
+ break;
+ cur = cur->next;
+ }
+ } else {
+ HandleChunk *cur = stack->interior;
+
+ if (!cur)
+ return;
+ for (int i = 0; i < cur->size; ++i) {
+ HandleChunkElem* elem = chunk_element (cur, i);
+ gpointer* ptr_slot = &elem->o;
+ if (*ptr_slot != NULL)
+ func (ptr_slot, gc_data);
+ }
+ }
}
void
}
if (size > THIS_IS_AN_OK_NUMBER_OF_HANDLES)
- printf ("%s USED %d handles\n", func_name, size);
+ g_warning ("%s USED %d handles\n", func_name, size);
}
/*
MonoRawHandle
mono_stack_mark_pop_value (MonoThreadInfo *info, HandleStackMark *stackmark, MonoRawHandle value)
{
- g_error ("impl me");
+ MonoObject *obj = value ? *((MonoObject**)value) : NULL;
+ mono_stack_mark_pop (info, stackmark);
+#ifndef MONO_HANDLE_TRACK_OWNER
+ return mono_handle_new (obj);
+#else
+ return mono_handle_new (obj, "<mono_stack_mark_pop_value>");
+#endif
}
/* Temporary place for some of the handle enabled wrapper functions*/
MonoStringHandle
-mono_string_new_handle (MonoDomain *domain, const char *data)
+mono_string_new_handle (MonoDomain *domain, const char *data, MonoError *error)
{
- return MONO_HANDLE_NEW (MonoString, mono_string_new (domain, data));
+ return MONO_HANDLE_NEW (MonoString, mono_string_new_checked (domain, data, error));
}
MonoArrayHandle
return MONO_HANDLE_NEW (MonoArray, mono_array_new_checked (domain, eclass, n, error));
}
+MonoArrayHandle
+mono_array_new_full_handle (MonoDomain *domain, MonoClass *array_class, uintptr_t *lengths, intptr_t *lower_bounds, MonoError *error)
+{
+ return MONO_HANDLE_NEW (MonoArray, mono_array_new_full_checked (domain, array_class, lengths, lower_bounds, error));
+}
+
#ifdef ENABLE_CHECKED_BUILD
/* Checked build helpers */
void
}
#endif
+
+uintptr_t
+mono_array_handle_length (MonoArrayHandle arr)
+{
+ MONO_REQ_GC_UNSAFE_MODE;
+
+ return MONO_HANDLE_RAW (arr)->max_length;
+}
+
+uint32_t
+mono_gchandle_from_handle (MonoObjectHandle handle, mono_bool pinned)
+{
+ /* FIXME: chunk_element_to_chunk_idx does a linear search through the
+ * chunks and we only need it for the assert */
+ MonoThreadInfo *info = mono_thread_info_current ();
+ HandleStack *stack = (HandleStack*) info->handle_stack;
+ HandleChunkElem* elem = handle_to_chunk_element (handle);
+ int elem_idx = 0;
+ HandleChunk *chunk = chunk_element_to_chunk_idx (stack, elem, &elem_idx);
+ /* gchandles cannot deal with interior pointers */
+ g_assert (chunk != NULL);
+ return mono_gchandle_new (MONO_HANDLE_RAW (handle), pinned);
+}
+
+MonoObjectHandle
+mono_gchandle_get_target_handle (uint32_t gchandle)
+{
+ return MONO_HANDLE_NEW (MonoObject, mono_gchandle_get_target (gchandle));
+}
+
+gpointer
+mono_array_handle_pin_with_size (MonoArrayHandle handle, int size, uintptr_t idx, uint32_t *gchandle)
+{
+ g_assert (gchandle != NULL);
+ *gchandle = mono_gchandle_from_handle (MONO_HANDLE_CAST(MonoObject,handle), TRUE);
+ MonoArray *raw = MONO_HANDLE_RAW (handle);
+ return mono_array_addr_with_size (raw, size, idx);
+}
+
+gunichar2*
+mono_string_handle_pin_chars (MonoStringHandle handle, uint32_t *gchandle)
+{
+ g_assert (gchandle != NULL);
+ *gchandle = mono_gchandle_from_handle (MONO_HANDLE_CAST (MonoObject, handle), TRUE);
+ MonoString *raw = MONO_HANDLE_RAW (handle);
+ return mono_string_chars (raw);
+}
+
+gpointer
+mono_object_handle_pin_unbox (MonoObjectHandle obj, uint32_t *gchandle)
+{
+ g_assert (!MONO_HANDLE_IS_NULL (obj));
+ MonoClass *klass = mono_handle_class (obj);
+ g_assert (klass->valuetype);
+ *gchandle = mono_gchandle_from_handle (obj, TRUE);
+ return mono_object_unbox (MONO_HANDLE_RAW (obj));
+}
+
+void
+mono_array_handle_memcpy_refs (MonoArrayHandle dest, uintptr_t dest_idx, MonoArrayHandle src, uintptr_t src_idx, uintptr_t len)
+{
+ mono_array_memcpy_refs (MONO_HANDLE_RAW (dest), dest_idx, MONO_HANDLE_RAW (src), src_idx, len);
+}
+
+gboolean
+mono_handle_stack_is_empty (HandleStack *stack)
+{
+ return (stack->top == stack->bottom && stack->top->size == 0);
+}