Skip to content

Commit b1eeb67

Browse files
authored
[release/9.0-staging] [mono][interp] Fix various leaks, primarily around dynamic methods (#120600)
* [mono][interp] Fix various leaks, primarily around dynamic methods (#119176) * [mono][interp] Fix leaking of compilation data For dynamic methods, imethod_alloc0 allocates from the dynamic method's mempool which is freed when the method is collected. We were previously allocating only from the global memory manager. * [mono][interp] Stop leaking data items Previously we were registering imethod locations from these data items in order for them to be patched once a method is tiered up. Because of this registering, the data item had to always be around. We now free the data item for dynamic methods and also we deregister the patch locations when freeing such a method. * [mono][interp] Free headers allocated for inlined methods We add them to a list for later freeing. This uses the same pattern as jit. * [mono][interp] Skip interp free for methods not yet compiled * [mono][interp] Fix attempt to free tiering data when tiering is disabled (#119294) * [mono] Actually free dynamic methods, even if we have profiler attached (#119749) * [mono] Actually free dynamic methods, even if we have profiler attached Ever since the early days of mono, freeing dynamic methods was disabled if a profiler was attached. The reason for this was probably that the profiler might store `MonoMethod` instances in its own data, leading to problems if we free those instances. Looking at the profilers nowadays it is not clear where patterns like this would happen. Profilers that do store methods (like aot, coverage), don't process wrapper methods, so we should be safe since dynamic methods have the MONO_WRAPPER_DYNAMIC_METHOD wrapper type. The problem is that, nowadays, we can always have a profiler attached even if no actual profiling happens. macios for example always calls mono_profiler_install. I belive actual callbacks can be added later as necessary. * Disable freeing dynamic methods only when eventpipe or debugger are enabled * [mono][interp] Fix race when registering patch point data items (#119990) The code of a compiled method can use data items that contain InterpMethod* pointers. Because at the moment when a method is compiled these referenced interp methods weren't yet tiered up, we will register these data item locations to the tiering backend so that, when they do get tiered up, the locations get updated with the new InterpMethod* reference. At this moment of compilation time, we could race with other compilers of the same InterpMethod so we could end up registering redundant data_items (only the data_items for the method winning the race will actually end up being used by the executing interpreter code). Normally this is harmless, we just patch some data that never gets used. The problems start when we free interpreter methods. When the interp method is freed, we need to clear the patch_sites_table of any locations pointing into this method's data items. We do a search starting from the data items of this method. Because we have no way to reference the wrongly registered data items, these entries will stay alive in the table. As the memory gets reused, these entries will point into other runtime code, and when the get patched, it will result in corruption of memory. We fix this issue by registering the patch sites only for the method winning the compilation race (the one that first sets the transformed flag). * [mono] Enable dynfree of methods only if env var is passed If the MONO_ENABLE_DYNMETHOD_FREE is not passed, the behavior is not changed at all in order to mitigate potential risk. The old behavior was that if we have a profiler installed we don't free dynamic methods, which was always the case on maui.
1 parent fde32a0 commit b1eeb67

File tree

7 files changed

+99
-14
lines changed

7 files changed

+99
-14
lines changed

src/mono/mono/metadata/loader.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <mono/metadata/loader-internals.h>
3737
#include <mono/metadata/class-init.h>
3838
#include <mono/metadata/class-internals.h>
39+
#include <mono/metadata/components.h>
3940
#include <mono/metadata/debug-helpers.h>
4041
#include <mono/metadata/reflection.h>
4142
#include <mono/metadata/profiler-private.h>
@@ -78,6 +79,8 @@ static gint32 memberref_sig_cache_size;
7879
static gint32 methods_size;
7980
static gint32 signatures_size;
8081

82+
static gboolean mono_enable_dynfree = FALSE;
83+
8184
void
8285
mono_loader_init (void)
8386
{
@@ -103,6 +106,11 @@ mono_loader_init (void)
103106
mono_counters_register ("MonoMethodSignature size",
104107
MONO_COUNTER_METADATA | MONO_COUNTER_INT, &signatures_size);
105108

109+
char *env_opt = g_getenv ("MONO_ENABLE_DYNMETHOD_FREE");
110+
if (env_opt && env_opt [0] == '1')
111+
mono_enable_dynfree = TRUE;
112+
g_free (env_opt);
113+
106114
inited = TRUE;
107115
}
108116
}
@@ -1371,8 +1379,14 @@ mono_free_method (MonoMethod *method)
13711379

13721380
MONO_PROFILER_RAISE (method_free, (method));
13731381

1374-
/* FIXME: This hack will go away when the profiler will support freeing methods */
1375-
if (G_UNLIKELY (mono_profiler_installed ()))
1382+
if (G_UNLIKELY (mono_profiler_installed () && !mono_enable_dynfree))
1383+
return;
1384+
1385+
// EventPipe might require information about methods to be stored throughout
1386+
// entire app execution, so stack traces can be resolved at a later time.
1387+
// Same for debugger, we are being overly conservative
1388+
if (mono_component_event_pipe ()->component.available () ||
1389+
mono_component_debugger ()->component.available ())
13761390
return;
13771391

13781392
if (method->signature) {

src/mono/mono/mini/interp/interp-internals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ struct InterpMethod {
148148
/* locals_size is equal to the offset of the param_area */
149149
guint32 locals_size;
150150
guint32 alloca_size;
151+
int n_data_items;
151152
int num_clauses; // clauses
152153
int transformed; // boolean
153154
unsigned int param_count;

src/mono/mono/mini/interp/interp.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3565,12 +3565,17 @@ interp_free_method (MonoMethod *method)
35653565

35663566
jit_mm_lock (jit_mm);
35673567

3568-
#if HOST_BROWSER
35693568
InterpMethod *imethod = (InterpMethod*)mono_internal_hash_table_lookup (&jit_mm->interp_code_hash, method);
3570-
mono_jiterp_free_method_data (method, imethod);
3569+
if (imethod) {
3570+
#if HOST_BROWSER
3571+
mono_jiterp_free_method_data (method, imethod);
35713572
#endif
35723573

3573-
mono_internal_hash_table_remove (&jit_mm->interp_code_hash, method);
3574+
mono_interp_clear_data_items_patch_sites (imethod->data_items, imethod->n_data_items);
3575+
3576+
mono_internal_hash_table_remove (&jit_mm->interp_code_hash, method);
3577+
}
3578+
35743579
jit_mm_unlock (jit_mm);
35753580

35763581
if (dmethod->mp) {

src/mono/mono/mini/interp/tiering.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,58 @@ register_imethod_data_item (gpointer data, gpointer user_data)
128128
}
129129
}
130130

131+
void
132+
mono_interp_clear_data_items_patch_sites (gpointer *data_items, int n_data_items)
133+
{
134+
if (!enable_tiering)
135+
return;
136+
// data_items is part of the memory of a dynamic method that is being freed.
137+
// slots within this memory can be registered as patch sites for other imethods
138+
// We conservatively assume each slot could be an imethod slot, then look it up
139+
// in imethod to patch_sites hashtable. If we find it in the hashtable, we remove
140+
// the slot from the patch site list.
141+
mono_os_mutex_lock (&tiering_mutex);
142+
143+
for (int i = 0; i < n_data_items; i++) {
144+
GSList *sites;
145+
gpointer *slot = data_items + i;
146+
gpointer imethod_candidate = *slot;
147+
148+
if (dn_simdhash_ptr_ptr_try_get_value (patch_sites_table, imethod_candidate, (void **)&sites)) {
149+
GSList *prev = NULL;
150+
151+
// Remove slot from sites list
152+
if (sites->data == slot) {
153+
// If the slot is found in the first element we will also need to update the hash table since
154+
// the list head changes
155+
if (!sites->next) {
156+
g_slist_free_1 (sites);
157+
dn_simdhash_ptr_ptr_try_remove (patch_sites_table, imethod_candidate);
158+
} else {
159+
prev = sites;
160+
sites = sites->next;
161+
g_slist_free_1 (prev);
162+
dn_simdhash_ptr_ptr_try_replace_value (patch_sites_table, imethod_candidate, sites);
163+
}
164+
} else {
165+
prev = sites;
166+
sites = sites->next;
167+
while (sites != NULL) {
168+
if (sites->data == slot) {
169+
prev->next = sites->next;
170+
g_slist_free_1 (sites);
171+
// duplicates not allowed
172+
break;
173+
}
174+
prev = sites;
175+
sites = sites->next;
176+
}
177+
}
178+
}
179+
}
180+
mono_os_mutex_unlock (&tiering_mutex);
181+
}
182+
131183
void
132184
mono_interp_register_imethod_data_items (gpointer *data_items, GSList *indexes)
133185
{

src/mono/mono/mini/interp/tiering.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ mono_interp_tiering_enabled (void);
1414
void
1515
mono_interp_register_imethod_data_items (gpointer *data_items, GSList *indexes);
1616

17+
void
18+
mono_interp_clear_data_items_patch_sites (gpointer *data_items, int n_data_items);
19+
1720
void
1821
mono_interp_register_imethod_patch_site (gpointer *imethod_ptr);
1922

src/mono/mono/mini/interp/transform.c

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3176,6 +3176,8 @@ interp_inline_newobj (TransformData *td, MonoMethod *target_method, MonoMethodSi
31763176
if (!interp_inline_method (td, target_method, mheader, error))
31773177
goto fail;
31783178

3179+
td->headers_to_free = g_slist_prepend_mempool (td->mempool, td->headers_to_free, mheader);
3180+
31793181
push_var (td, dreg);
31803182
return TRUE;
31813183
fail:
@@ -3760,6 +3762,7 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
37603762
return_val_if_nok (error, FALSE);
37613763

37623764
if (interp_inline_method (td, target_method, mheader, error)) {
3765+
td->headers_to_free = g_slist_prepend_mempool (td->mempool, td->headers_to_free, mheader);
37633766
td->ip += 5;
37643767
goto done;
37653768
}
@@ -4510,7 +4513,7 @@ interp_method_compute_offsets (TransformData *td, InterpMethod *imethod, MonoMet
45104513
// 64 vars * 72 bytes = 4608 bytes. Many methods need less than this
45114514
int target_vars_capacity = num_locals + 64;
45124515

4513-
imethod->local_offsets = (guint32*)g_malloc (num_il_locals * sizeof(guint32));
4516+
imethod->local_offsets = (guint32*)imethod_alloc0 (td, num_il_locals * sizeof(guint32));
45144517
td->vars = (InterpVar*)g_malloc0 (target_vars_capacity * sizeof (InterpVar));
45154518
td->vars_size = num_locals;
45164519
td->vars_capacity = target_vars_capacity;
@@ -4608,7 +4611,7 @@ interp_method_compute_offsets (TransformData *td, InterpMethod *imethod, MonoMet
46084611
}
46094612
#endif
46104613

4611-
imethod->clause_data_offsets = (guint32*)g_malloc (header->num_clauses * sizeof (guint32));
4614+
imethod->clause_data_offsets = (guint32*)imethod_alloc0 (td, header->num_clauses * sizeof (guint32));
46124615
td->clause_vars = (int*)mono_mempool_alloc (td->mempool, sizeof (int) * header->num_clauses);
46134616
for (guint i = 0; i < header->num_clauses; i++) {
46144617
int var = interp_create_var (td, mono_get_object_type ());
@@ -9424,7 +9427,7 @@ get_native_offset (TransformData *td, int il_offset)
94249427
}
94259428
}
94269429

9427-
static void
9430+
static GSList*
94289431
generate (MonoMethod *method, MonoMethodHeader *header, InterpMethod *rtm, MonoGenericContext *generic_context, MonoError *error)
94299432
{
94309433
TransformData transform_data;
@@ -9609,12 +9612,10 @@ generate (MonoMethod *method, MonoMethodHeader *header, InterpMethod *rtm, MonoG
96099612
rtm->alloca_size = td->total_locals_size + td->max_stack_size;
96109613
g_assert ((rtm->alloca_size % MINT_STACK_ALIGNMENT) == 0);
96119614
rtm->locals_size = td->param_area_offset;
9612-
// FIXME: Can't allocate this using imethod_alloc0 as its registered with mono_interp_register_imethod_data_items ()
9613-
//rtm->data_items = (gpointer*)imethod_alloc0 (td, td->n_data_items * sizeof (td->data_items [0]));
9614-
rtm->data_items = (gpointer*)mono_mem_manager_alloc0 (td->mem_manager, td->n_data_items * sizeof (td->data_items [0]));
9615+
rtm->data_items = (gpointer*)imethod_alloc0 (td, td->n_data_items * sizeof (td->data_items [0]));
96159616
memcpy (rtm->data_items, td->data_items, td->n_data_items * sizeof (td->data_items [0]));
9617+
rtm->n_data_items = td->n_data_items;
96169618

9617-
mono_interp_register_imethod_data_items (rtm->data_items, td->imethod_items);
96189619
rtm->patchpoint_data = td->patchpoint_data;
96199620

96209621
if (td->ref_slots) {
@@ -9688,14 +9689,17 @@ generate (MonoMethod *method, MonoMethodHeader *header, InterpMethod *rtm, MonoG
96889689
g_ptr_array_free (td->seq_points, TRUE);
96899690
if (td->line_numbers)
96909691
g_array_free (td->line_numbers, TRUE);
9691-
g_slist_free (td->imethod_items);
9692+
for (GSList *l = td->headers_to_free; l; l = l->next)
9693+
mono_metadata_free_mh ((MonoMethodHeader *)l->data);
96929694
mono_mempool_destroy (td->mempool);
96939695
mono_interp_pgo_generate_end ();
96949696
if (td->retry_compilation) {
96959697
retry_compilation = TRUE;
96969698
retry_with_inlining = td->retry_with_inlining;
96979699
goto retry;
96989700
}
9701+
9702+
return td->imethod_items;
96999703
}
97009704

97019705
gboolean
@@ -9841,7 +9845,8 @@ mono_interp_transform_method (InterpMethod *imethod, ThreadContext *context, Mon
98419845
memcpy (&tmp_imethod, imethod, sizeof (InterpMethod));
98429846
imethod = &tmp_imethod;
98439847

9844-
MONO_TIME_TRACK (mono_interp_stats.transform_time, generate (method, header, imethod, generic_context, error));
9848+
GSList *imethod_data_items;
9849+
MONO_TIME_TRACK (mono_interp_stats.transform_time, imethod_data_items = generate (method, header, imethod, generic_context, error));
98459850

98469851
mono_metadata_free_mh (header);
98479852

@@ -9862,6 +9867,8 @@ mono_interp_transform_method (InterpMethod *imethod, ThreadContext *context, Mon
98629867
mono_interp_stats.methods_transformed++;
98639868
mono_atomic_fetch_add_i32 (&mono_jit_stats.methods_with_interp, 1);
98649869

9870+
mono_interp_register_imethod_data_items (imethod->data_items, imethod_data_items);
9871+
98659872
// FIXME Publishing of seq points seems to be racy with tiereing. We can have both tiered and untiered method
98669873
// running at the same time. We could therefore get the optimized imethod seq points for the unoptimized method.
98679874
gpointer seq_points = NULL;
@@ -9871,6 +9878,8 @@ mono_interp_transform_method (InterpMethod *imethod, ThreadContext *context, Mon
98719878
}
98729879
jit_mm_unlock (jit_mm);
98739880

9881+
g_slist_free (imethod_data_items);
9882+
98749883
if (mono_stats_method_desc && mono_method_desc_full_match (mono_stats_method_desc, imethod->method)) {
98759884
g_printf ("Printing runtime stats at method: %s\n", mono_method_get_full_name (imethod->method));
98769885
mono_runtime_print_stats ();

src/mono/mono/mini/interp/transform.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ typedef struct
314314
// FIXME: ptr_u32
315315
dn_simdhash_ptr_ptr_t *data_hash;
316316
GSList *imethod_items;
317+
GSList *headers_to_free;
317318
#ifdef ENABLE_EXPERIMENT_TIERED
318319
// FIXME: ptr_u32
319320
dn_simdhash_ptr_ptr_t *patchsite_hash;

0 commit comments

Comments
 (0)