Skip to content

Commit c7c6aa0

Browse files
authored
[mono][aot] Prefer specific instances instead of gshared for methods containing static virtual calls. (#76033)
These calls cannot be resolved at compile time in gshared methods, so they cannot be inlined etc. They are used in perf sensitive BCL code like SpanHelpers. To fix this, modify the AOT compiler so in addition to the gshared versions, it emits specific instances of these methods if possible. This only affects a small subset of gshared methods so it doesn't lead to a noticable code size increase. Fixes #75801.
1 parent 94c6fe6 commit c7c6aa0

File tree

3 files changed

+67
-6
lines changed

3 files changed

+67
-6
lines changed

src/mono/mono/mini/aot-compiler.c

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,10 @@ typedef struct MonoAotCompile {
396396
GList *profile_data;
397397
GHashTable *profile_methods;
398398
GHashTable *blob_hash;
399+
/* Maps MonoMethod*->GPtrArray* */
400+
GHashTable *gshared_instances;
401+
/* Hash of gshared methods where specific instances are preferred */
402+
GHashTable *prefer_instances;
399403
#ifdef EMIT_WIN32_UNWIND_INFO
400404
GList *unwind_info_section_cache;
401405
#endif
@@ -4302,8 +4306,23 @@ mono_dedup_cache_method (MonoAotCompile *acfg, MonoMethod *method)
43024306
g_hash_table_insert (acfg->dedup_stats, stats_name, GUINT_TO_POINTER (count));
43034307
}
43044308

4309+
static gboolean
4310+
is_open_method (MonoMethod *method)
4311+
{
4312+
MonoGenericContext *context;
4313+
4314+
if (!method->is_inflated)
4315+
return FALSE;
4316+
context = mono_method_get_context (method);
4317+
if (context->class_inst && context->class_inst->is_open)
4318+
return TRUE;
4319+
if (context->method_inst && context->method_inst->is_open)
4320+
return TRUE;
4321+
return FALSE;
4322+
}
4323+
43054324
static void
4306-
add_extra_method_with_depth (MonoAotCompile *acfg, MonoMethod *method, int depth)
4325+
add_extra_method_full (MonoAotCompile *acfg, MonoMethod *method, gboolean prefer_gshared, int depth)
43074326
{
43084327
ERROR_DECL (error);
43094328

@@ -4315,7 +4334,7 @@ add_extra_method_with_depth (MonoAotCompile *acfg, MonoMethod *method, int depth
43154334
return;
43164335
}
43174336

4318-
if (mono_method_is_generic_sharable_full (method, TRUE, TRUE, FALSE)) {
4337+
if (prefer_gshared && mono_method_is_generic_sharable_full (method, TRUE, TRUE, FALSE)) {
43194338
MonoMethod *orig = method;
43204339

43214340
method = mini_get_shared_method_full (method, SHARE_MODE_NONE, error);
@@ -4325,10 +4344,23 @@ add_extra_method_with_depth (MonoAotCompile *acfg, MonoMethod *method, int depth
43254344
return;
43264345
}
43274346

4347+
if (g_hash_table_lookup (acfg->prefer_instances, method))
4348+
/* Compile an instance as well */
4349+
add_extra_method_full (acfg, orig, FALSE, depth);
4350+
43284351
/* Add it to profile_methods so its not skipped later */
43294352
if (acfg->aot_opts.profile_only && g_hash_table_lookup (acfg->profile_methods, orig))
43304353
g_hash_table_insert (acfg->profile_methods, method, method);
4331-
} else if ((acfg->jit_opts & MONO_OPT_GSHAREDVT) && prefer_gsharedvt_method (acfg, method) && mono_method_is_generic_sharable_full (method, FALSE, FALSE, TRUE)) {
4354+
4355+
if (!is_open_method (orig) && !mono_method_is_generic_sharable_full (orig, TRUE, FALSE, FALSE)) {
4356+
GPtrArray *instances = g_hash_table_lookup (acfg->gshared_instances, method);
4357+
if (!instances) {
4358+
instances = g_ptr_array_new ();
4359+
g_hash_table_insert (acfg->gshared_instances, method, instances);
4360+
}
4361+
g_ptr_array_add (instances, orig);
4362+
}
4363+
} else if ((acfg->jit_opts & MONO_OPT_GSHAREDVT) && prefer_gshared && prefer_gsharedvt_method (acfg, method) && mono_method_is_generic_sharable_full (method, FALSE, FALSE, TRUE)) {
43324364
/* Use the gsharedvt version */
43334365
method = mini_get_shared_method_full (method, SHARE_MODE_GSHAREDVT, error);
43344366
mono_error_assert_ok (error);
@@ -4347,6 +4379,12 @@ add_extra_method_with_depth (MonoAotCompile *acfg, MonoMethod *method, int depth
43474379
add_method_full (acfg, method, TRUE, depth);
43484380
}
43494381

4382+
static void
4383+
add_extra_method_with_depth (MonoAotCompile *acfg, MonoMethod *method, int depth)
4384+
{
4385+
add_extra_method_full (acfg, method, TRUE, depth);
4386+
}
4387+
43504388
static void
43514389
add_extra_method (MonoAotCompile *acfg, MonoMethod *method)
43524390
{
@@ -9209,6 +9247,22 @@ compile_method (MonoAotCompile *acfg, MonoMethod *method)
92099247
printf ("%s ### %d\n", mono_method_get_full_name (method), cfg->code_size);
92109248
}
92119249

9250+
if (cfg->prefer_instances) {
9251+
/*
9252+
* Compile the original specific instances in addition to the gshared method
9253+
* for performance reasons, since gshared methods cannot implement some
9254+
* features like static virtual methods efficiently.
9255+
*/
9256+
g_hash_table_insert (acfg->prefer_instances, method, method);
9257+
GPtrArray *instances = g_hash_table_lookup (acfg->gshared_instances, method);
9258+
if (instances) {
9259+
for (guint i = 0; i < instances->len; ++i) {
9260+
MonoMethod *instance = (MonoMethod*)g_ptr_array_index (instances, i);
9261+
add_extra_method_full (acfg, instance, FALSE, 0);
9262+
}
9263+
}
9264+
}
9265+
92129266
/* Adds generic instances referenced by this method */
92139267
/*
92149268
* The depth is used to avoid infinite loops when generic virtual recursion is
@@ -13631,6 +13685,8 @@ acfg_create (MonoAssembly *ass, guint32 jit_opts)
1363113685
acfg->gsharedvt_in_signatures = g_hash_table_new ((GHashFunc)mono_signature_hash, (GEqualFunc)mono_metadata_signature_equal);
1363213686
acfg->gsharedvt_out_signatures = g_hash_table_new ((GHashFunc)mono_signature_hash, (GEqualFunc)mono_metadata_signature_equal);
1363313687
acfg->profile_methods = g_hash_table_new (NULL, NULL);
13688+
acfg->gshared_instances = g_hash_table_new (NULL, NULL);
13689+
acfg->prefer_instances = g_hash_table_new (NULL, NULL);
1363413690
mono_os_mutex_init_recursive (&acfg->mutex);
1363513691

1363613692
init_got_info (&acfg->got_info);

src/mono/mono/mini/method-to-ir.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7493,10 +7493,14 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
74937493
if (constrained_class) {
74947494
if (m_method_is_static (cil_method) && mini_class_check_context_used (cfg, constrained_class)) {
74957495
/* get_constrained_method () doesn't work on the gparams used by generic sharing */
7496-
// FIXME: Other configurations
7497-
//if (!cfg->gsharedvt)
7498-
// GENERIC_SHARING_FAILURE (CEE_CALL);
74997496
gshared_static_virtual = TRUE;
7497+
if (!cfg->gsharedvt)
7498+
/*
7499+
* We can't resolve these calls at compile time, and they are used in
7500+
* perf-sensitive code in the BCL, so ask the AOT compiler to try to use specific instances
7501+
* instead of this gshared method.
7502+
*/
7503+
cfg->prefer_instances = TRUE;
75007504
} else {
75017505
cmethod = get_constrained_method (cfg, image, token, cil_method, constrained_class, generic_context);
75027506
CHECK_CFG_ERROR;

src/mono/mono/mini/mini.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,7 @@ typedef struct {
14921492
guint after_method_to_ir : 1;
14931493
guint disable_inline_rgctx_fetch : 1;
14941494
guint deopt : 1;
1495+
guint prefer_instances : 1;
14951496
guint8 uses_simd_intrinsics;
14961497
int r4_stack_type;
14971498
gpointer debug_info;

0 commit comments

Comments
 (0)