--- /dev/null
+/*
+ * generic-sharing.c: Support functions for generic sharing.
+ *
+ * Author:
+ * Mark Probst (mark.probst@gmail.com)
+ *
+ * (C) 2007 Novell, Inc.
+ */
+
+#include <config.h>
+
+#include <mono/metadata/class.h>
+
+#include "mini.h"
+
+static gboolean
+generic_inst_uses_type (MonoGenericInst *inst, MonoType *type)
+{
+ int i;
+
+ if (!inst)
+ return FALSE;
+
+ for (i = 0; i < inst->type_argc; ++i)
+ if (mono_metadata_type_equal (type, inst->type_argv [i]))
+ return TRUE;
+ return FALSE;
+}
+
+static int context_check_context_used (MonoGenericContext *context, MonoGenericContext *shared_context);
+
+static int
+type_check_context_used (MonoType *type, MonoGenericContext *context, gboolean recursive)
+{
+ int context_used = 0;
+
+ if (generic_inst_uses_type (context->class_inst, type))
+ context_used |= MONO_GENERIC_CONTEXT_USED_CLASS;
+ if (generic_inst_uses_type (context->method_inst, type))
+ context_used |= MONO_GENERIC_CONTEXT_USED_METHOD;
+
+ if (recursive) {
+ int t = mono_type_get_type (type);
+
+ if (t == MONO_TYPE_CLASS)
+ context_used |= mono_class_check_context_used (mono_type_get_class (type), context);
+ else if (t == MONO_TYPE_GENERICINST) {
+ MonoGenericClass *gclass = type->data.generic_class;
+
+ context_used |= context_check_context_used (&gclass->context, context);
+ context_used |= mono_class_check_context_used (gclass->container_class, context);
+ }
+ }
+
+ return context_used;
+}
+
+static int
+inst_check_context_used (MonoGenericInst *inst, MonoGenericContext *context)
+{
+ int context_used = 0;
+ int i;
+
+ if (!inst)
+ return 0;
+
+ for (i = 0; i < inst->type_argc; ++i)
+ context_used |= type_check_context_used (inst->type_argv [i], context, TRUE);
+
+ return context_used;
+}
+
+static int
+context_check_context_used (MonoGenericContext *context, MonoGenericContext *shared_context)
+{
+ int context_used = 0;
+
+ context_used |= inst_check_context_used (context->class_inst, shared_context);
+ context_used |= inst_check_context_used (context->method_inst, shared_context);
+
+ return context_used;
+}
+
+int
+mono_method_check_context_used (MonoMethod *method, MonoGenericContext *context)
+{
+ MonoGenericContext *method_context = mono_method_get_context (method);
+
+ if (!method_context)
+ return 0;
+
+ return context_check_context_used (method_context, context);
+}
+
+int
+mono_class_check_context_used (MonoClass *class, MonoGenericContext *context)
+{
+ int context_used = 0;
+
+ context_used |= type_check_context_used (&class->this_arg, context, FALSE);
+ context_used |= type_check_context_used (&class->byval_arg, context, FALSE);
+
+ if (class->generic_class)
+ context_used |= context_check_context_used (&class->generic_class->context, context);
+
+ return context_used;
+}
+
+static gboolean
+generic_inst_is_sharable (MonoGenericInst *inst)
+{
+ int i;
+
+ for (i = 0; i < inst->type_argc; ++i) {
+ int type = mono_type_get_type (inst->type_argv [i]);
+
+ if (type != MONO_TYPE_CLASS && type != MONO_TYPE_STRING && type != MONO_TYPE_OBJECT &&
+ type != MONO_TYPE_SZARRAY && type != MONO_TYPE_ARRAY)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+generic_context_is_sharable (MonoGenericContext *context)
+{
+ g_assert (context->class_inst || context->method_inst);
+
+ if (context->class_inst && !generic_inst_is_sharable (context->class_inst))
+ return FALSE;
+
+ if (context->method_inst && !generic_inst_is_sharable (context->method_inst))
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+mono_method_is_generic_impl (MonoMethod *method)
+{
+ return method->klass->generic_class != NULL && method->is_inflated;
+}
+
+gboolean
+mono_method_is_generic_sharable_impl (MonoMethod *method)
+{
+ if (!mono_method_is_generic_impl (method))
+ return FALSE;
+
+ if (method->is_inflated) {
+ MonoMethodInflated *inflated = (MonoMethodInflated*)method;
+ MonoGenericContext *context = &inflated->context;
+
+ if (!generic_context_is_sharable (context))
+ return FALSE;
+
+ g_assert (inflated->declaring);
+
+ if (inflated->declaring->generic_container) {
+ g_assert (inflated->declaring->generic_container->type_params);
+
+ if (inflated->declaring->generic_container->type_params->constraints)
+ return FALSE;
+ }
+ }
+
+ if (method->klass->generic_class) {
+ if (!generic_context_is_sharable (&method->klass->generic_class->context))
+ return FALSE;
+
+ g_assert (method->klass->generic_class->container_class &&
+ method->klass->generic_class->container_class->generic_container &&
+ method->klass->generic_class->container_class->generic_container->type_params);
+
+ if (method->klass->generic_class->container_class->generic_container->type_params->constraints)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static MonoGenericInst*
+share_generic_inst (MonoCompile *cfg, MonoGenericInst *inst)
+{
+ MonoType **type_argv;
+ int i;
+
+ if (!inst)
+ return NULL;
+
+ type_argv = mono_mempool_alloc0 (cfg->mempool, sizeof (MonoType*) * inst->type_argc);
+
+ for (i = 0; i < inst->type_argc; ++i)
+ type_argv [i] = &mono_defaults.object_class->byval_arg;
+
+ return mono_metadata_get_generic_inst (inst->type_argc, type_argv);
+}
+
+MonoGenericContext*
+mono_make_shared_context (MonoCompile *cfg, MonoGenericContext *context)
+{
+ MonoGenericContext *shared = mono_mempool_alloc0 (cfg->mempool, sizeof (MonoGenericContext));
+
+ shared->class_inst = share_generic_inst (cfg, context->class_inst);
+ shared->method_inst = share_generic_inst (cfg, context->method_inst);
+
+ return shared;
+}
g_free (field_fname); \
goto exception_exit; \
} while (0)
+#define GENERIC_SHARING_FAILURE do { \
+ if (cfg->generic_shared) { \
+ /* g_print ("sharing failed for method %s.%s in %s:%d\n", method->klass->name, method->name, __FILE__, __LINE__); */ \
+ cfg->exception_type = MONO_EXCEPTION_GENERIC_SHARING_FAILED; \
+ goto exception_exit; \
+ } \
+ } while (0)
/*
* this is used to determine when some branch optimizations are possible: we exclude FP compares
static int mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_bblock, MonoBasicBlock *end_bblock,
int locals_offset, MonoInst *return_var, GList *dont_inline, MonoInst **inline_args,
- guint inline_offset, gboolean is_virtual_call);
+ guint inline_offset, gboolean is_virtual_call, MonoGenericContext *shared_context);
/* helper methods signature */
static MonoMethodSignature *helper_sig_class_init_trampoline = NULL;
static int
inline_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, MonoBasicBlock *bblock, MonoInst **sp,
- guchar *ip, guint real_offset, GList *dont_inline, MonoBasicBlock **last_b, gboolean inline_allways)
+ guchar *ip, guint real_offset, GList *dont_inline, MonoBasicBlock **last_b, gboolean inline_allways, MonoGenericContext *shared_context)
{
MonoInst *ins, *rvar = NULL;
MonoMethodHeader *cheader;
prev_cil_offset_to_bb_len = cfg->cil_offset_to_bb_len;
prev_cil_start = cfg->cil_start;
- costs = mono_method_to_ir (cfg, cmethod, sbblock, ebblock, new_locals_offset, rvar, dont_inline, sp, real_offset, *ip == CEE_CALLVIRT);
+ costs = mono_method_to_ir (cfg, cmethod, sbblock, ebblock, new_locals_offset, rvar, dont_inline, sp, real_offset, *ip == CEE_CALLVIRT, shared_context);
cfg->inlined_method = prev_inlined_method;
cfg->cil_offset_to_bb = prev_cil_offset_to_bb;
static int
mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_bblock, MonoBasicBlock *end_bblock,
int locals_offset, MonoInst *return_var, GList *dont_inline, MonoInst **inline_args,
- guint inline_offset, gboolean is_virtual_call)
+ guint inline_offset, gboolean is_virtual_call, MonoGenericContext *shared_context)
{
MonoInst *zero_int32, *zero_int64, *zero_ptr, *zero_obj, *zero_r8;
MonoInst *ins, **sp, **stack_start;
end = ip + header->code_size;
mono_jit_stats.cil_code_size += header->code_size;
- if (sig->is_inflated)
+ if (cfg->generic_shared) {
+ g_assert (shared_context);
+ generic_context = shared_context;
+ } else if (sig->is_inflated)
generic_context = mono_method_get_context (method);
else if (generic_container)
generic_context = &generic_container->context;
if (!cmethod)
goto load_error;
+ if (cfg->generic_shared && mono_method_check_context_used (cmethod, generic_context))
+ GENERIC_SHARING_FAILURE;
+
if (mono_security_get_mode () == MONO_SECURITY_MODE_CAS) {
if (check_linkdemand (cfg, method, cmethod, bblock, ip))
INLINE_FAILURE;
if (cmethod && cmethod->klass->generic_container)
UNVERIFIED;
+ if (cfg->generic_shared && cmethod && mono_method_check_context_used (cmethod, generic_context))
+ GENERIC_SHARING_FAILURE;
+
CHECK_STACK (n);
//g_assert (!virtual || fsig->hasthis);
allways = TRUE;
}
- if ((costs = inline_method (cfg, cmethod, fsig, bblock, sp, ip, real_offset, dont_inline, &ebblock, allways))) {
+ if ((costs = inline_method (cfg, cmethod, fsig, bblock, sp, ip, real_offset, dont_inline, &ebblock, allways, shared_context))) {
ip += 5;
real_offset += 5;
MonoInst *iargs [2];
MonoMethodSignature *fsig;
int temp;
-
+
CHECK_OPSIZE (5);
token = read32 (ip + 1);
cmethod = mini_get_method (method, token, NULL, generic_context);
if (!mono_class_init (cmethod->klass))
goto load_error;
+ if (cfg->generic_shared && mono_method_check_context_used (cmethod, generic_context))
+ GENERIC_SHARING_FAILURE;
+
if (mono_security_get_mode () == MONO_SECURITY_MODE_CAS) {
if (check_linkdemand (cfg, method, cmethod, bblock, ip))
INLINE_FAILURE;
!g_list_find (dont_inline, cmethod)) {
int costs;
MonoBasicBlock *ebblock;
- if ((costs = inline_method (cfg, cmethod, fsig, bblock, sp, ip, real_offset, dont_inline, &ebblock, FALSE))) {
+ if ((costs = inline_method (cfg, cmethod, fsig, bblock, sp, ip, real_offset, dont_inline, &ebblock, FALSE, shared_context))) {
ip += 5;
real_offset += 5;
if (sp [0]->type != STACK_OBJ)
UNVERIFIED;
+ if (cfg->generic_shared && mono_class_check_context_used (klass, generic_context))
+ GENERIC_SHARING_FAILURE;
+
/* Needed by the code generated in inssel.brg */
mono_get_got_var (cfg);
iargs [0] = sp [0];
costs = inline_method (cfg, mono_isinst, mono_method_signature (mono_isinst), bblock,
- iargs, ip, real_offset, dont_inline, &ebblock, TRUE);
+ iargs, ip, real_offset, dont_inline, &ebblock, TRUE, shared_context);
g_assert (costs > 0);
klass = mini_get_class (method, token, generic_context);
CHECK_TYPELOAD (klass);
+ if (cfg->generic_shared && mono_class_check_context_used (klass, generic_context))
+ GENERIC_SHARING_FAILURE;
+
if (MONO_TYPE_IS_REFERENCE (&klass->byval_arg)) {
/* CASTCLASS */
if (klass->marshalbyref || klass->flags & TYPE_ATTRIBUTE_INTERFACE) {
iargs [0] = sp [0];
costs = inline_method (cfg, mono_castclass, mono_method_signature (mono_castclass), bblock,
- iargs, ip, real_offset, dont_inline, &ebblock, TRUE);
+ iargs, ip, real_offset, dont_inline, &ebblock, TRUE, shared_context);
g_assert (costs > 0);
klass = mini_get_class (method, token, generic_context);
CHECK_TYPELOAD (klass);
+ if (cfg->generic_shared && mono_class_check_context_used (klass, generic_context))
+ GENERIC_SHARING_FAILURE;
+
if (mono_class_is_nullable (klass)) {
int v = handle_unbox_nullable (cfg, bblock, *sp, ip, klass);
NEW_TEMPLOAD (cfg, *sp, v);
if (sp [0]->type != STACK_OBJ)
UNVERIFIED;
+ if (cfg->generic_shared && mono_class_check_context_used (klass, generic_context))
+ GENERIC_SHARING_FAILURE;
+
/* Needed by the code generated in inssel.brg */
mono_get_got_var (cfg);
iargs [0] = sp [0];
costs = inline_method (cfg, mono_castclass, mono_method_signature (mono_castclass), bblock,
- iargs, ip, real_offset, dont_inline, &ebblock, TRUE);
+ iargs, ip, real_offset, dont_inline, &ebblock, TRUE, shared_context);
g_assert (costs > 0);
if (cfg->opt & MONO_OPT_INLINE) {
costs = inline_method (cfg, stfld_wrapper, mono_method_signature (stfld_wrapper), bblock,
- iargs, ip, real_offset, dont_inline, &ebblock, TRUE);
+ iargs, ip, real_offset, dont_inline, &ebblock, TRUE, shared_context);
g_assert (costs > 0);
ip += 5;
NEW_ICONST (cfg, iargs [3], klass->valuetype ? field->offset - sizeof (MonoObject) : field->offset);
if ((cfg->opt & MONO_OPT_INLINE) && !MONO_TYPE_ISSTRUCT (mono_method_signature (wrapper)->ret)) {
costs = inline_method (cfg, wrapper, mono_method_signature (wrapper), bblock,
- iargs, ip, real_offset, dont_inline, &ebblock, TRUE);
+ iargs, ip, real_offset, dont_inline, &ebblock, TRUE, shared_context);
g_assert (costs > 0);
ip += 5;
if (!dont_verify && !cfg->skip_visibility && !mono_method_can_access_field (method, field))
FIELD_ACCESS_FAILURE;
+ if (cfg->generic_shared && mono_class_check_context_used (klass, generic_context))
+ GENERIC_SHARING_FAILURE;
+
g_assert (!(field->type->attrs & FIELD_ATTRIBUTE_LITERAL));
if ((*ip) == CEE_STSFLD)
break;
case CEE_BOX: {
MonoInst *val;
+
CHECK_STACK (1);
--sp;
val = *sp;
klass = mini_get_class (method, token, generic_context);
CHECK_TYPELOAD (klass);
+ if (cfg->generic_shared && mono_class_check_context_used (klass, generic_context))
+ GENERIC_SHARING_FAILURE;
+
if (MONO_TYPE_IS_REFERENCE (&klass->byval_arg)) {
*sp++ = val;
ip += 5;
inline_costs += 1;
break;
}
- case CEE_NEWARR:
+ case CEE_NEWARR: {
+ int context_used;
+
CHECK_STACK (1);
MONO_INST_NEW (cfg, ins, *ip);
ins->cil_code = ip;
klass = mini_get_class (method, token, generic_context);
CHECK_TYPELOAD (klass);
+
+ if (cfg->generic_shared)
+ context_used = mono_class_check_context_used (klass, generic_context);
+ else
+ context_used = 0;
+
+ if (context_used)
+ GENERIC_SHARING_FAILURE;
+
ins->inst_newa_class = klass;
ins->inst_newa_len = *sp;
ins->type = STACK_OBJ;
}
inline_costs += 1;
break;
+ }
case CEE_LDLEN:
CHECK_STACK (1);
--sp;
mono_class_init (klass);
ins->cil_code = ip;
+ if (cfg->generic_shared && mono_class_check_context_used (klass, generic_context))
+ GENERIC_SHARING_FAILURE;
+
loc = mono_compile_create_var (cfg, &mono_defaults.typed_reference_class->byval_arg, OP_LOCAL);
NEW_TEMPLOADA (cfg, ins->inst_right, loc->inst_c0);
}
else {
handle = mono_ldtoken (image, n, &handle_class, generic_context);
+ if (cfg->generic_shared &&
+ mono_class_check_context_used (handle_class, generic_context))
+ GENERIC_SHARING_FAILURE;
}
if (!handle)
goto load_error;
int temp;
MonoInst *res, *store, *addr, *vtvar, *iargs [3];
+ GENERIC_SHARING_FAILURE;
+
vtvar = mono_compile_create_var (cfg, &handle_class->byval_arg, OP_LOCAL);
NEW_IMAGECONST (cfg, iargs [0], image);
goto load_error;
mono_class_init (cmethod->klass);
+ if (cfg->generic_shared && mono_method_check_context_used (cmethod, generic_context))
+ GENERIC_SHARING_FAILURE;
+
cil_method = cmethod;
if (!dont_verify && !cfg->skip_visibility && !mono_method_can_access_method (method, cmethod))
METHOD_ACCESS_FAILURE;
goto load_error;
mono_class_init (cmethod->klass);
+ if (cfg->generic_shared && mono_method_check_context_used (cmethod, generic_context))
+ GENERIC_SHARING_FAILURE;
+
if (mono_security_get_mode () == MONO_SECURITY_MODE_CAS) {
if (check_linkdemand (cfg, method, cmethod, bblock, ip))
INLINE_FAILURE;
token = read32 (ip + 2);
klass = mini_get_class (method, token, generic_context);
CHECK_TYPELOAD (klass);
+
+ if (cfg->generic_shared && mono_class_check_context_used (klass, generic_context))
+ GENERIC_SHARING_FAILURE;
+
if (MONO_TYPE_IS_REFERENCE (&klass->byval_arg)) {
MonoInst *store, *load;
NEW_PCONST (cfg, load, NULL);
break;
}
case CEE_SIZEOF:
+ GENERIC_SHARING_FAILURE;
+
CHECK_STACK_OVF (1);
CHECK_OPSIZE (6);
token = read32 (ip + 2);
MonoJitInfo *jinfo;
int dfn = 0, i, code_size_ratio;
gboolean deadce_has_run = FALSE;
+ gboolean try_generic_shared = (opts & MONO_OPT_GSHARED) && mono_method_is_generic_sharable_impl (method);
+ MonoGenericContext *shared_context;
mono_jit_stats.methods_compiled++;
if (mono_profiler_get_events () & MONO_PROFILE_JIT_COMPILATION)
mono_profiler_method_jit (method);
+ if (opts & MONO_OPT_GSHARED) {
+ if (try_generic_shared)
+ mono_stats.generics_sharable_methods++;
+ else if (mono_method_is_generic_impl (method))
+ mono_stats.generics_unsharable_methods++;
+ }
+
+ restart_compile:
cfg = g_new0 (MonoCompile, 1);
cfg->method = method;
cfg->mempool = mono_mempool_new ();
cfg->verbose_level = mini_verbose;
cfg->compile_aot = compile_aot;
cfg->skip_visibility = method->skip_visibility;
+ cfg->generic_shared = try_generic_shared;
cfg->token_info_hash = g_hash_table_new (NULL, NULL);
if (!header) {
cfg->exception_type = MONO_EXCEPTION_INVALID_PROGRAM;
*/
mono_compile_create_vars (cfg);
- if ((i = mono_method_to_ir (cfg, method, NULL, NULL, cfg->locals_start, NULL, NULL, NULL, 0, FALSE)) < 0) {
+ if (try_generic_shared)
+ shared_context = mono_make_shared_context (cfg, mono_method_get_context (method));
+ else
+ shared_context = NULL;
+
+ if ((i = mono_method_to_ir (cfg, method, NULL, NULL, cfg->locals_start, NULL, NULL, NULL, 0, FALSE, shared_context)) < 0) {
+ if (try_generic_shared && cfg->exception_type == MONO_EXCEPTION_GENERIC_SHARING_FAILED) {
+ mono_destroy_compile (cfg);
+ try_generic_shared = FALSE;
+ goto restart_compile;
+ }
+ g_assert (cfg->exception_type != MONO_EXCEPTION_GENERIC_SHARING_FAILED);
+
if (cfg->prof_options & MONO_PROFILE_JIT_COMPILATION)
mono_profiler_method_end_jit (method, NULL, MONO_PROFILE_FAILED);
/* cfg contains the details of the failure, so let the caller cleanup */
jinfo->used_regs = cfg->used_int_regs;
jinfo->domain_neutral = (cfg->opt & MONO_OPT_SHARED) != 0;
jinfo->cas_inited = FALSE; /* initialization delayed at the first stalk walk using this method */
+ jinfo->generic_shared = cfg->generic_shared;
if (header->num_clauses) {
int i;
return cfg;
}
+static MonoMethod*
+method_get_declaring_generic_method (MonoMethod *method)
+{
+ MonoMethodInflated *inflated;
+
+ g_assert (method->is_inflated);
+
+ inflated = (MonoMethodInflated*)method;
+
+ return inflated->declaring;
+}
+
+static MonoJitInfo*
+lookup_generic_method (MonoDomain *domain, MonoMethod *method)
+{
+ MonoMethod *open_method;
+
+ if (!mono_method_is_generic_sharable_impl (method))
+ return NULL;
+
+ open_method = method_get_declaring_generic_method (method);
+
+ return mono_domain_lookup_shared_generic (domain, open_method);
+}
+
+static MonoJitInfo*
+lookup_method (MonoDomain *domain, MonoMethod *method)
+{
+ MonoJitInfo *ji = mono_internal_hash_table_lookup (&domain->jit_code_hash, method);
+
+ if (ji != NULL)
+ return ji;
+
+ return lookup_generic_method (domain, method);
+}
+
static gpointer
mono_jit_compile_method_inner (MonoMethod *method, MonoDomain *target_domain, int opt)
{
/* Check if some other thread already did the job. In this case, we can
discard the code this thread generated. */
- if ((info = mono_internal_hash_table_lookup (&target_domain->jit_code_hash, method))) {
+ if ((info = lookup_method (target_domain, method))) {
/* We can't use a domain specific method in another domain */
if ((target_domain == mono_domain_get ()) || info->domain_neutral) {
code = info->code_start;
if (code == NULL) {
mono_internal_hash_table_insert (&target_domain->jit_code_hash, method, cfg->jit_info);
code = cfg->native_code;
+
+ if (cfg->generic_shared && mono_method_is_generic_sharable_impl (method)) {
+ /* g_print ("inserting method %s.%s.%s\n", method->klass->name_space, method->klass->name, method->name); */
+ mono_domain_register_shared_generic (target_domain,
+ method_get_declaring_generic_method (method), cfg->jit_info);
+ mono_stats.generics_shared_methods++;
+ }
}
mono_destroy_compile (cfg);
mono_domain_lock (target_domain);
- if ((info = mono_internal_hash_table_lookup (&target_domain->jit_code_hash, method))) {
+ if ((info = lookup_method (target_domain, method))) {
/* We can't use a domain specific method in another domain */
if (! ((domain != target_domain) && !info->domain_neutral)) {
mono_domain_unlock (target_domain);
mono_domain_lock (target_domain);
- if ((info = mono_internal_hash_table_lookup (&target_domain->jit_code_hash, method))) {
+ if ((info = lookup_method (target_domain, method))) {
/* We can't use a domain specific method in another domain */
if (! ((domain != target_domain) && !info->domain_neutral)) {
mono_domain_unlock (target_domain);
g_print ("Generics metadata size: %ld\n", mono_stats.generics_metadata_size);
g_print ("Generics virtual invokes: %ld\n", mono_jit_stats.generic_virtual_invocations);
+ g_print ("Sharable generic methods: %ld\n", mono_stats.generics_sharable_methods);
+ g_print ("Unsharable generic methods: %ld\n", mono_stats.generics_unsharable_methods);
+ g_print ("Shared generic methods: %ld\n", mono_stats.generics_shared_methods);
+
g_print ("Dynamic code allocs: %ld\n", mono_stats.dynamic_code_alloc_count);
g_print ("Dynamic code bytes: %ld\n", mono_stats.dynamic_code_bytes_count);
g_print ("Dynamic code frees: %ld\n", mono_stats.dynamic_code_frees_count);