Skip to content

Commit

Permalink
[mono] refactor metadata update code (#85177)
Browse files Browse the repository at this point in the history
* WIP: remove mono_class_set_metadata_update_info generics blocker

* Allow GTDs to store MonoClassMetadataUpdateInfo

   allow generic type definitions to can contain added members

* [loader] Use iterator in find_method_in_class

   Instead of using a for-loop, use an interator that will also pick up added methods from hot reload.

   There's an issue here compared to the old code: the old code could cope with MonoClass:methods containing null pointers.  But the iterator approach signals that iteration is finished by returning NULL.

   Need to check whether the old code is reachable on modern .NET

* Get method add/update info for generic instance classes

* Cleanup the codepath of calling hot_reload_find_method_by_name

* Add reflection support for property of generic instance

* Fix ios build failure

* Add new capabilities

Fixes #82792
Fixes #82791

---------

Co-authored-by: Aleksey Kliger <alklig@microsoft.com>
  • Loading branch information
fanyang-mono and lambdageek authored Apr 24, 2023
1 parent 5eb4ff0 commit 018a9bd
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 26 deletions.
2 changes: 2 additions & 0 deletions src/mono/mono/component/hot_reload-internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ struct _MonoClassMetadataUpdateInfo {
GSList *added_events; /* a set of MonoClassMetadataUpdateEvent* values */

MonoClassRuntimeMetadataUpdateInfo runtime;

uint32_t generation; /* must be updated when a GTD gets added props, events or fields; must be updated when a GINST copies updated info from the parent */
};

/*
Expand Down
147 changes: 137 additions & 10 deletions src/mono/mono/component/hot_reload.c
Original file line number Diff line number Diff line change
Expand Up @@ -3318,6 +3318,8 @@ hot_reload_get_static_field_addr (MonoClassField *field)
static MonoMethod *
hot_reload_find_method_by_name (MonoClass *klass, const char *name, int param_count, int flags, MonoError *error)
{
g_assert (!mono_class_is_ginst (klass));

GSList *members = hot_reload_get_added_members (klass);
if (!members)
return NULL;
Expand Down Expand Up @@ -3366,13 +3368,19 @@ hot_reload_added_methods_iter (MonoClass *klass, gpointer *iter)
uint32_t idx = GPOINTER_TO_UINT (*iter);
g_assert (idx >= mono_class_get_method_count (klass));

GSList *members = hot_reload_get_added_members (klass);
GSList *members = NULL;
int class_kind = m_class_get_class_kind (klass);
if (class_kind == MONO_CLASS_GINST) {
MonoClass *gklass = mono_class_get_generic_class (klass)->container_class;
members = hot_reload_get_added_members (gklass);
} else {
members = hot_reload_get_added_members (klass);
}

if (!members)
return NULL;
// expect to only see class defs here. Rationale: adding methods to generic classes is not
// allowed (if a generation adds a new generic class, it won't be here - those methods will
// be in the normal iteration code, not here.
g_assert (m_class_get_class_kind (klass) == MONO_CLASS_DEF);
// We don't expect to see arrays or pointers here since they don't own MONO_TABLE_METHOD entries.
g_assert (class_kind == MONO_CLASS_DEF || class_kind == MONO_CLASS_GTD || class_kind == MONO_CLASS_GINST);

mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "Iterating added methods of 0x%08x idx = %u", m_class_get_type_token (klass), idx);

Expand All @@ -3386,9 +3394,18 @@ hot_reload_added_methods_iter (MonoClass *klass, gpointer *iter)
if (cur_count == idx) {
// found a method, advance iter and return the method.
*iter = GUINT_TO_POINTER (1+idx);
ERROR_DECL (local_error);
MonoMethod *method = mono_get_method_checked (m_class_get_image (klass), token, klass, NULL, local_error);
mono_error_cleanup (local_error);
MonoMethod *method = NULL;
if (class_kind == MONO_CLASS_GINST) {
MonoClass *gklass = mono_class_get_generic_class (klass)->container_class;
ERROR_DECL (local_error);
MonoMethod *gmethod = mono_get_method_checked (m_class_get_image (gklass), token, klass, NULL, local_error);
method = mono_class_inflate_generic_method_full_checked (gmethod, klass, mono_class_get_context (klass), local_error);
mono_error_cleanup (local_error);
} else {
ERROR_DECL (local_error);
method = mono_get_method_checked (m_class_get_image (klass), token, klass, NULL, local_error);
mono_error_cleanup (local_error);
}
return method;
}
cur_count++;
Expand Down Expand Up @@ -3477,7 +3494,7 @@ hot_reload_get_method_params (MonoImage *base_image, uint32_t methoddef_token, u
static const char *
hot_reload_get_capabilities (void)
{
return "Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes AddInstanceFieldToExistingType";
return "Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes AddInstanceFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod";
}

static GENERATE_GET_CLASS_WITH_CACHE_DECL (hot_reload_instance_field_table);
Expand Down Expand Up @@ -3515,10 +3532,120 @@ hot_reload_added_field_ldflda (MonoObject *instance, MonoType *field_type, uint3
return result;
}

static void recompute_ginst_update_info(MonoClass *ginst, MonoClass *gtd, MonoClassMetadataUpdateInfo *gtd_info);

static MonoClassMetadataUpdateInfo *
hot_reload_get_or_add_ginst_update_info(MonoClass *ginst)
{
g_assert (m_class_get_class_kind (ginst) == MONO_CLASS_GINST);
MonoClassMetadataUpdateInfo *info = mono_class_get_metadata_update_info (ginst);

gboolean needToRefresh = FALSE;

if (!info)
needToRefresh = TRUE;

MonoClass *gtd = mono_class_get_generic_type_definition (ginst);
MonoClassMetadataUpdateInfo *gtd_info = mono_class_get_metadata_update_info (gtd);

if (!gtd_info)
return NULL;

if (info && info->generation < gtd_info->generation)
needToRefresh = TRUE;

if (!needToRefresh)
return info;

mono_loader_lock ();
info = mono_class_get_metadata_update_info (ginst);
if (info && info->generation == gtd_info->generation) {
mono_loader_unlock ();
return info;
}

recompute_ginst_update_info (ginst, gtd, gtd_info);

info = mono_class_get_metadata_update_info (ginst);
mono_loader_unlock ();
return info;
}

#define mono_class_new0(klass,struct_type, n_structs) \
((struct_type *) mono_class_alloc0 ((klass), ((gsize) sizeof (struct_type)) * ((gsize) (n_structs))))

static void
recompute_ginst_update_info(MonoClass *ginst, MonoClass *gtd, MonoClassMetadataUpdateInfo *gtd_info)
{
// if ginst has a `MonoClassMetadataUpdateInfo`, use it to start with, otherwise, allocate a new one
MonoClassMetadataUpdateInfo *info = mono_class_get_or_add_metadata_update_info (ginst);

if (!info)
info = mono_class_new0 (ginst, MonoClassMetadataUpdateInfo, 1);

// replace info->added_props by a new list re-computed from gtd_info->added_props
info->added_props = NULL;
for (GSList *ptr = gtd_info->added_props; ptr; ptr = ptr->next) {
MonoClassMetadataUpdateProperty *gtd_added_prop = (MonoClassMetadataUpdateProperty *)ptr->data;
MonoClassMetadataUpdateProperty *added_prop = mono_class_new0 (ginst, MonoClassMetadataUpdateProperty, 1);

added_prop->prop = gtd_added_prop->prop;
added_prop->token = gtd_added_prop->token;

ERROR_DECL (error);
if (added_prop->prop.get)
added_prop->prop.get = mono_class_inflate_generic_method_full_checked (
added_prop->prop.get, ginst, mono_class_get_context (ginst), error);
if (added_prop->prop.set)
added_prop->prop.set = mono_class_inflate_generic_method_full_checked (
added_prop->prop.set, ginst, mono_class_get_context (ginst), error);
g_assert (is_ok (error)); /*FIXME proper error handling*/

added_prop->prop.parent = ginst;

info->added_props = g_slist_prepend_mem_manager (m_class_get_mem_manager (ginst), info->added_props, (gpointer)added_prop);
}

// replace info->added_events by a new list re-computed from gtd_info->added_events
info->added_events = NULL;
for (GSList *ptr = gtd_info->added_events; ptr; ptr = ptr->next) {
MonoClassMetadataUpdateEvent *gtd_added_event = (MonoClassMetadataUpdateEvent *)ptr->data;
MonoClassMetadataUpdateEvent *added_event = mono_class_new0 (ginst, MonoClassMetadataUpdateEvent, 1);

added_event->evt = gtd_added_event->evt;

ERROR_DECL (error);
if (added_event->evt.add)
added_event->evt.add = mono_class_inflate_generic_method_full_checked (
added_event->evt.add, ginst, mono_class_get_context (ginst), error);
if (added_event->evt.remove)
added_event->evt.remove = mono_class_inflate_generic_method_full_checked (
added_event->evt.remove, ginst, mono_class_get_context (ginst), error);
if (added_event->evt.raise)
added_event->evt.raise = mono_class_inflate_generic_method_full_checked (
added_event->evt.raise, ginst, mono_class_get_context (ginst), error);
g_assert (is_ok (error)); /*FIXME proper error handling*/

added_event->evt.parent = ginst;

info->added_events = g_slist_prepend_mem_manager (m_class_get_mem_manager (ginst), info->added_events, (gpointer)added_event);
}

// finally, update the generation of the ginst info to the same one as the gtd
info->generation = gtd_info->generation;
// we're done info is now up to date
}

static MonoProperty *
hot_reload_added_properties_iter (MonoClass *klass, gpointer *iter)
{
MonoClassMetadataUpdateInfo *info = mono_class_get_metadata_update_info (klass);
MonoClassMetadataUpdateInfo *info;
if (mono_class_is_ginst (klass)) {
info = hot_reload_get_or_add_ginst_update_info (klass);
} else {
info = mono_class_get_metadata_update_info (klass);
}

if (!info)
return NULL;

Expand Down
14 changes: 7 additions & 7 deletions src/mono/mono/metadata/class-accessors.c
Original file line number Diff line number Diff line change
Expand Up @@ -620,11 +620,10 @@ MonoClassMetadataUpdateInfo*
mono_class_get_metadata_update_info (MonoClass *klass)
{
switch (m_class_get_class_kind (klass)) {
case MONO_CLASS_GTD:
return NULL;
case MONO_CLASS_DEF:
return (MonoClassMetadataUpdateInfo *)get_pointer_property (klass, PROP_METADATA_UPDATE_INFO);
case MONO_CLASS_GTD:
case MONO_CLASS_GINST:
return (MonoClassMetadataUpdateInfo *)get_pointer_property (klass, PROP_METADATA_UPDATE_INFO);
case MONO_CLASS_GPARAM:
case MONO_CLASS_ARRAY:
case MONO_CLASS_POINTER:
Expand All @@ -643,13 +642,13 @@ mono_class_set_metadata_update_info (MonoClass *klass, MonoClassMetadataUpdateIn
{
switch (m_class_get_class_kind (klass)) {
case MONO_CLASS_GTD:
g_assertf (0, "%s: EnC metadata update info on generic types is not supported", __func__);
break;
case MONO_CLASS_DEF:
case MONO_CLASS_GINST:
set_pointer_property (klass, PROP_METADATA_UPDATE_INFO, value);
return;
case MONO_CLASS_GINST:
case MONO_CLASS_GPARAM:
/* metadata-update: this shouldn't happen */
g_assert_not_reached();
case MONO_CLASS_POINTER:
case MONO_CLASS_GC_FILLER:
g_assert_not_reached ();
Expand All @@ -664,11 +663,12 @@ mono_class_has_metadata_update_info (MonoClass *klass)
{
switch (m_class_get_class_kind (klass)) {
case MONO_CLASS_GTD:
return FALSE;
case MONO_CLASS_DEF:
return get_pointer_property (klass, PROP_METADATA_UPDATE_INFO) != NULL;
case MONO_CLASS_GINST:
case MONO_CLASS_GPARAM:
/* metadata-update: this shouldn't happen */
g_assert_not_reached();
case MONO_CLASS_POINTER:
case MONO_CLASS_GC_FILLER:
return FALSE;
Expand Down
2 changes: 1 addition & 1 deletion src/mono/mono/metadata/class-internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -1142,7 +1142,7 @@ mono_method_can_access_field_full (MonoMethod *method, MonoClassField *field, Mo
gboolean
mono_class_can_access_class (MonoClass *access_class, MonoClass *target_class);

MonoClass *
MONO_COMPONENT_API MonoClass *
mono_class_get_generic_type_definition (MonoClass *klass);

gboolean
Expand Down
2 changes: 1 addition & 1 deletion src/mono/mono/metadata/class.c
Original file line number Diff line number Diff line change
Expand Up @@ -5913,7 +5913,7 @@ mono_class_get_method_from_name_checked (MonoClass *klass, const char *name,

mono_class_init_internal (klass);

if (mono_class_is_ginst (klass) && !m_class_get_methods (klass)) {
if (mono_class_is_ginst (klass) && (!m_class_get_methods (klass) || m_class_get_image (klass)->has_updates)) {
res = mono_class_get_method_from_name_checked (mono_class_get_generic_class (klass)->container_class, name, param_count, flags, error);

if (res)
Expand Down
38 changes: 31 additions & 7 deletions src/mono/mono/metadata/loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -486,9 +486,16 @@ find_method_in_class (MonoClass *klass, const char *name, const char *qname, con
return NULL;
}
int mcount = mono_class_get_method_count (klass);
MonoMethod **klass_methods = m_class_get_methods (klass);
for (i = 0; i < mcount; ++i) {
MonoMethod *m = klass_methods [i];
i = -1;
gpointer iter = NULL;
MonoMethod *m = NULL;
gboolean matched = FALSE;
/* FIXME: metadata-update iterating using
* mono_class_get_methods will break if `m` is NULL. Need to
* reconcile with the `if (!m)` "we must cope" comment below.
*/
while ((m = mono_class_get_methods (klass, &iter))) {
++i;
MonoMethodSignature *msig;

/* We must cope with failing to load some of the types. */
Expand All @@ -507,16 +514,33 @@ find_method_in_class (MonoClass *klass, const char *name, const char *qname, con
continue;

if (sig->call_convention == MONO_CALL_VARARG) {
if (mono_metadata_signature_vararg_match (sig, msig))
if (mono_metadata_signature_vararg_match (sig, msig)) {
matched = TRUE;
break;
}
} else {
if (mono_metadata_signature_equal (sig, msig))
if (mono_metadata_signature_equal (sig, msig)) {
matched = TRUE;
break;
}
}
}

if (i < mcount)
return mono_class_get_method_by_index (from_class, i);
if (matched) {
if (i < mcount)
return mono_class_get_method_by_index (from_class, i);
else if (m != NULL) {
// FIXME: metadata-update: hack
// it's from a metadata-update, probably
m = mono_class_inflate_generic_method_full_checked (
m, from_class, mono_class_get_context (from_class), error);
mono_error_assert_ok (error);
g_assert (m != NULL);
g_assert (m->klass == from_class);
g_assert (m->is_inflated);
return m;
}
}
return NULL;
}

Expand Down

0 comments on commit 018a9bd

Please sign in to comment.