diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis/AddLambdaCapturingThis.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis/AddLambdaCapturingThis.cs new file mode 100644 index 0000000000000..186144e290e35 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis/AddLambdaCapturingThis.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; + + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class AddLambdaCapturingThis + { + public AddLambdaCapturingThis () { + field = "abcd"; + } + + public string GetField => field; + + private string field; + + public string TestMethod () { + // capture 'this' but no locals + Func fn = s => field; + return "123"; + } + + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis/AddLambdaCapturingThis_v1.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis/AddLambdaCapturingThis_v1.cs new file mode 100644 index 0000000000000..22dd869021dc3 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis/AddLambdaCapturingThis_v1.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; + + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class AddLambdaCapturingThis + { + public AddLambdaCapturingThis () { + field = "abcd"; + } + + public string GetField => field; + + private string field; + + public string TestMethod () { + // capture 'this' but no locals + Func fn = s => NewMethod (s + field, 42); + return fn ("123"); + } + + private string NewMethod (string s, int i) { + return i.ToString() + s; + } + + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis.csproj b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis.csproj new file mode 100644 index 0000000000000..88c636e683cf6 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis.csproj @@ -0,0 +1,11 @@ + + + System.Runtime.Loader.Tests + $(NetCoreAppCurrent) + true + deltascript.json + + + + + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis/deltascript.json b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis/deltascript.json new file mode 100644 index 0000000000000..1e618dede3646 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis/deltascript.json @@ -0,0 +1,6 @@ +{ + "changes": [ + {"document": "AddLambdaCapturingThis.cs", "update": "AddLambdaCapturingThis_v1.cs"}, + ] +} + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs index 3e240e65caabf..a582e3f4a7bb9 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs @@ -263,6 +263,26 @@ public void AsyncMethodChanges() }); } + [ActiveIssue ("https://github.com/dotnet/runtime/issues/50249", TestRuntimes.Mono)] + [ConditionalFact(typeof(ApplyUpdateUtil), nameof(ApplyUpdateUtil.IsSupported))] + public static void TestAddLambdaCapturingThis() + { + ApplyUpdateUtil.TestCase(static () => + { + var assm = typeof(System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis).Assembly; + + var x = new System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis(); + + Assert.Equal("123", x.TestMethod()); + + ApplyUpdateUtil.ApplyUpdate(assm); + + string result = x.TestMethod(); + Assert.Equal("42123abcd", result); + }); + } + + class NonRuntimeAssembly : Assembly { } diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj index 19e5d6bc5069b..2ecf08c6f3da5 100644 --- a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj @@ -43,6 +43,7 @@ + @@ -60,6 +61,7 @@ + diff --git a/src/mono/mono/component/hot_reload-stub.c b/src/mono/mono/component/hot_reload-stub.c index 35b8fe38a56e4..6064a6a78f875 100644 --- a/src/mono/mono/component/hot_reload-stub.c +++ b/src/mono/mono/component/hot_reload-stub.c @@ -39,10 +39,7 @@ static void hot_reload_stub_cleanup_on_close (MonoImage *image); static void -hot_reload_stub_effective_table_slow (const MonoTableInfo **t, int *idx); - -static int -hot_reload_stub_relative_delta_index (MonoImage *image_dmeta, int token); +hot_reload_stub_effective_table_slow (const MonoTableInfo **t, int idx); static void hot_reload_stub_close_except_pools_all (MonoImage *base_image); @@ -65,6 +62,15 @@ hot_reload_stub_get_updated_method_ppdb (MonoImage *base_image, uint32_t idx); static gboolean hot_reload_stub_has_modified_rows (const MonoTableInfo *table); +static int +hot_reload_stub_table_num_rows_slow (MonoImage *image, int table_index); + +static GArray* +hot_reload_stub_get_added_methods (MonoClass *klass); + +static uint32_t +hot_reload_stub_method_parent (MonoImage *image, uint32_t method_index); + static MonoComponentHotReload fn_table = { { MONO_COMPONENT_ITF_VERSION, &hot_reload_stub_available }, &hot_reload_stub_set_fastpath_data, @@ -74,7 +80,6 @@ static MonoComponentHotReload fn_table = { &hot_reload_stub_get_thread_generation, &hot_reload_stub_cleanup_on_close, &hot_reload_stub_effective_table_slow, - &hot_reload_stub_relative_delta_index, &hot_reload_stub_apply_changes, &hot_reload_stub_close_except_pools_all, &hot_reload_stub_close_all, @@ -83,6 +88,9 @@ static MonoComponentHotReload fn_table = { &hot_reload_stub_delta_heap_lookup, &hot_reload_stub_get_updated_method_ppdb, &hot_reload_stub_has_modified_rows, + &hot_reload_stub_table_num_rows_slow, + &hot_reload_stub_get_added_methods, + &hot_reload_stub_method_parent, }; static bool @@ -135,13 +143,7 @@ hot_reload_stub_cleanup_on_close (MonoImage *image) } void -hot_reload_stub_effective_table_slow (const MonoTableInfo **t, int *idx) -{ - g_assert_not_reached (); -} - -static int -hot_reload_stub_relative_delta_index (MonoImage *image_dmeta, int token) +hot_reload_stub_effective_table_slow (const MonoTableInfo **t, int idx) { g_assert_not_reached (); } @@ -192,6 +194,25 @@ hot_reload_stub_has_modified_rows (const MonoTableInfo *table) return FALSE; } +static int +hot_reload_stub_table_num_rows_slow (MonoImage *image, int table_index) +{ + g_assert_not_reached (); /* should always take the fast path */ +} + +static GArray* +hot_reload_stub_get_added_methods (MonoClass *klass) +{ + return NULL; +} + +static uint32_t +hot_reload_stub_method_parent (MonoImage *image, uint32_t method_index) +{ + return 0; +} + + MONO_COMPONENT_EXPORT_ENTRYPOINT MonoComponentHotReload * mono_component_hot_reload_init (void) diff --git a/src/mono/mono/component/hot_reload.c b/src/mono/mono/component/hot_reload.c index 83faf008067fa..a3caeb13ef3a1 100644 --- a/src/mono/mono/component/hot_reload.c +++ b/src/mono/mono/component/hot_reload.c @@ -28,6 +28,10 @@ #include +#undef ALLOW_METHOD_ADD + +typedef struct _DeltaInfo DeltaInfo; + static void hot_reload_init (void); @@ -53,13 +57,13 @@ static void hot_reload_cleanup_on_close (MonoImage *image); static void -hot_reload_effective_table_slow (const MonoTableInfo **t, int *idx); +hot_reload_effective_table_slow (const MonoTableInfo **t, int idx); static void hot_reload_apply_changes (int origin, MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, gconstpointer dpdb_bytes_orig, uint32_t dpdb_length, MonoError *error); static int -hot_reload_relative_delta_index (MonoImage *image_dmeta, int token); +hot_reload_relative_delta_index (MonoImage *image_dmeta, DeltaInfo *delta_info, int token); static void hot_reload_close_except_pools_all (MonoImage *base_image); @@ -82,6 +86,16 @@ hot_reload_get_updated_method_ppdb (MonoImage *base_image, uint32_t idx); static gboolean hot_reload_has_modified_rows (const MonoTableInfo *table); +static int +hot_reload_table_num_rows_slow (MonoImage *image, int table_index); + +static GArray* +hot_reload_get_added_methods (MonoClass *klass); + +static uint32_t +hot_reload_method_parent (MonoImage *base, uint32_t method_token); + + static MonoComponentHotReload fn_table = { { MONO_COMPONENT_ITF_VERSION, &hot_reload_available }, &hot_reload_set_fastpath_data, @@ -91,7 +105,6 @@ static MonoComponentHotReload fn_table = { &hot_reload_get_thread_generation, &hot_reload_cleanup_on_close, &hot_reload_effective_table_slow, - &hot_reload_relative_delta_index, &hot_reload_apply_changes, &hot_reload_close_except_pools_all, &hot_reload_close_all, @@ -100,6 +113,9 @@ static MonoComponentHotReload fn_table = { &hot_reload_delta_heap_lookup, &hot_reload_get_updated_method_ppdb, &hot_reload_has_modified_rows, + &hot_reload_table_num_rows_slow, + &hot_reload_get_added_methods, + &hot_reload_method_parent, }; MonoComponentHotReload * @@ -151,8 +167,9 @@ typedef struct _delta_row_count { } delta_row_count; /* Additional informaiton for MonoImages representing deltas */ -typedef struct _DeltaInfo { +struct _DeltaInfo { uint32_t generation; /* global update ID that added this delta image */ + MonoImage *delta_image; /* DeltaInfo doesn't own the image, the base MonoImage owns the reference */ /* Maps MethodDef token indices to a pointer into the RVA of the delta IL */ GHashTable *method_table_update; @@ -165,25 +182,36 @@ typedef struct _DeltaInfo { delta_row_count count [MONO_TABLE_NUM]; MonoPPDBFile *ppdb_file; -} DeltaInfo; + + MonoMemPool *pool; /* mutated tables are allocated here */ + + MonoTableInfo mutants[MONO_TABLE_NUM]; +}; /* Additional informaiton for baseline MonoImages */ typedef struct _BaselineInfo { - /* List of MonoImages of deltas. Parent image owns 1 refcount ref of the delta image */ - GList *delta_image; - /* Tail of delta_image for fast appends */ - GList *delta_image_last; + /* List of DeltaInfos of deltas*/ + GList *delta_info; + /* Tail of delta_info for fast appends */ + GList *delta_info_last; /* Maps MethodDef token indices to a boolean flag that there's an update for the method */ GHashTable *method_table_update; /* TRUE if any published update modified an existing row */ gboolean any_modified_rows [MONO_TABLE_NUM]; + + GHashTable *added_methods; /* maps each MonoClass to a GArray of added method tokens */ + GHashTable *method_parent; /* maps added methoddef tokens to typedef tokens */ } BaselineInfo; + #define DOTNET_MODIFIABLE_ASSEMBLIES "DOTNET_MODIFIABLE_ASSEMBLIES" +/* See Note: Suppressed Columns */ +static guint16 m_SuppressedDeltaColumns [MONO_TABLE_NUM]; + /** * mono_metadata_update_enable: * \param modifiable_assemblies_out: set to MonoModifiableAssemblies value @@ -206,7 +234,8 @@ hot_reload_update_enabled (int *modifiable_assemblies_out) char *val = g_getenv (DOTNET_MODIFIABLE_ASSEMBLIES); if (val && !g_strcasecmp (val, "debug")) { mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "Metadata update enabled for debuggable assemblies"); - modifiable = MONO_MODIFIABLE_ASSM_DEBUG; + modifiable + = MONO_MODIFIABLE_ASSM_DEBUG; } g_free (val); inited = TRUE; @@ -254,7 +283,7 @@ hot_reload_no_inline (MonoMethod *caller, MonoMethod *callee) * this for deltas. */ static GHashTable *table_to_image; -/* Maps each delta MonoImage to its DeltaInfo */ +/* Maps each delta MonoImage to its DeltaInfo. Doesn't own the DeltaInfo or the images */ static GHashTable *delta_image_to_info; /* Maps each baseline MonoImage to its BaselineInfo */ static GHashTable *baseline_image_to_info; @@ -324,7 +353,7 @@ baseline_info_lookup (MonoImage *base_image) } static DeltaInfo* -delta_info_init (MonoImage *image_dmeta, MonoImage *image_base, MonoPPDBFile *ppdb_file, BaselineInfo *base_info, uint32_t generation); +delta_info_init (MonoImage *image_dmeta, MonoImage *image_base, MonoPPDBFile *ppdb_file, BaselineInfo *base_info, uint32_t generation, DeltaInfo **prev_last_delta); static void free_ppdb_entry (gpointer key, gpointer val, gpointer user_data) @@ -342,6 +371,9 @@ delta_info_destroy (DeltaInfo *dinfo) g_hash_table_destroy (dinfo->method_ppdb_table_update); } mono_ppdb_close (dinfo->ppdb_file); + + if (dinfo->pool) + mono_mempool_destroy (dinfo->pool); g_free (dinfo); } @@ -357,6 +389,7 @@ delta_info_lookup (MonoImage *delta_image) DeltaInfo *result; table_to_image_lock (); result = delta_info_lookup_locked (delta_image); + g_assert (!result || result->delta_image == delta_image); table_to_image_unlock (); return result; } @@ -384,12 +417,6 @@ hot_reload_cleanup_on_close (MonoImage *image) table_to_image_lock (); /* remove all keys (delta images) that map to the given image (base image) */ g_hash_table_foreach_remove (table_to_image, remove_base_image, (gpointer)image); - /* remove delta image info */ - DeltaInfo *delta_info = delta_info_lookup_locked (image); - if (delta_info) { - g_hash_table_remove (delta_image_to_info, image); - delta_info_destroy (delta_info); - } table_to_image_unlock (); } @@ -399,11 +426,16 @@ hot_reload_close_except_pools_all (MonoImage *base_image) BaselineInfo *info = baseline_info_lookup (base_image); if (!info) return; - for (GList *ptr = info->delta_image; ptr; ptr = ptr->next) { - MonoImage *image = (MonoImage *)ptr->data; + for (GList *ptr = info->delta_info; ptr; ptr = ptr->next) { + DeltaInfo *info = (DeltaInfo *)ptr->data; + MonoImage *image = info->delta_image; if (image) { + table_to_image_lock (); + g_hash_table_remove (delta_image_to_info, image); + table_to_image_unlock (); + /* if for some reason the image has other references, break the link to this delta_info that is going away */ if (!mono_image_close_except_pools (image)) - ptr->data = NULL; + info->delta_image = NULL; } } } @@ -414,12 +446,18 @@ hot_reload_close_all (MonoImage *base_image) BaselineInfo *info = baseline_info_lookup (base_image); if (!info) return; - for (GList *ptr = info->delta_image; ptr; ptr = ptr->next) { - MonoImage *image = (MonoImage *)ptr->data; - if (image) + for (GList *ptr = info->delta_info; ptr; ptr = ptr->next) { + DeltaInfo *info = (DeltaInfo *)ptr->data; + if (!info) + continue; + MonoImage *image = info->delta_image; + if (image) { mono_image_close_finish (image); + } + delta_info_destroy (info); + ptr->data = NULL; } - g_list_free (info->delta_image); + g_list_free (info->delta_info); baseline_info_remove (base_image); baseline_info_destroy (info); } @@ -474,17 +512,24 @@ table_info_find_in_base (const MonoTableInfo *table, MonoImage **base_out, int * static MonoImage* image_open_dmeta_from_data (MonoImage *base_image, uint32_t generation, gconstpointer dmeta_bytes, uint32_t dmeta_length); -static void +static DeltaInfo* image_append_delta (MonoImage *base, BaselineInfo *base_info, MonoImage *delta, DeltaInfo *delta_info); -static int -metadata_update_local_generation (MonoImage *base, BaselineInfo *base_info, MonoImage *delta); +static void +add_method_to_baseline (BaselineInfo *base_info, DeltaInfo *delta_info, MonoClass *klass, uint32_t method_token, MonoDebugInformationEnc* pdb_address); + void hot_reload_init (void) { table_to_image_init (); mono_native_tls_alloc (&exposed_generation_id, NULL); + + /* See CMiniMdRW::ApplyDelta in metamodelenc.cpp in CoreCLR */ + m_SuppressedDeltaColumns[MONO_TABLE_EVENTMAP] = (1 << MONO_EVENT_MAP_EVENTLIST); + m_SuppressedDeltaColumns[MONO_TABLE_PROPERTYMAP] = (1 << MONO_PROPERTY_MAP_PROPERTY_LIST); + m_SuppressedDeltaColumns[MONO_TABLE_METHOD] = (1 << MONO_METHOD_PARAMLIST); + m_SuppressedDeltaColumns[MONO_TABLE_TYPEDEF] = (1 << MONO_TYPEDEF_FIELD_LIST)|(1<pool); + + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "Initializing mutant tables for image %p (generation %d)", base, delta->generation); + for (int i = 0; i < MONO_TABLE_NUM; ++i) + { + gboolean need_copy = FALSE; + /* if any generation modified any row of this table, make a copy for the current generation. */ + if (base_info->any_modified_rows [i]) + need_copy = TRUE; + delta_row_count *count = &delta->count [i]; + guint32 base_rows = table_info_get_rows (&base->tables [i]); + /* if some previous generation added rows, or we're adding rows, make a copy */ + if (base_rows != count->prev_gen_rows || count->inserted_rows) + need_copy = TRUE; + if (!need_copy) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, " Table 0x%02x unchanged (rows: base %d, prev %d, inserted %d), not copied", i, base_rows, count->prev_gen_rows, count->inserted_rows); + continue; + } else { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, " Table 0x%02x changed (rows: base %d, prev %d, inserted %d), IS copied", i, base_rows, count->prev_gen_rows, count->inserted_rows); + } + /* The invariant is that once we made a copy in any previous generation, we'll make + * a copy in this generation. So subsequent generations can copy either from the + * immediately preceeding generation or from the baseline if the preceeding + * generation didn't make a copy. */ + + guint32 rows = count->prev_gen_rows + count->inserted_rows; + + MonoTableInfo *tbl = &delta->mutants [i]; + tbl->row_size = base->tables[i].row_size; + tbl->size_bitfield = base->tables[i].size_bitfield; + tbl->rows_ = rows; + + tbl->base = mono_mempool_alloc (delta->pool, tbl->row_size * rows); + const MonoTableInfo *prev_table; + if (!prev_delta || prev_delta->mutants [i].base == NULL) + prev_table = &base->tables [i]; + else + prev_table = &prev_delta->mutants [i]; + + g_assert (prev_table != NULL); + g_assert (table_info_get_rows (prev_table) == count->prev_gen_rows); + + /* copy the old rows and zero out the new ones */ + memcpy ((char*)tbl->base, prev_table->base, count->prev_gen_rows * tbl->row_size); + memset (((char*)tbl->base) + count->prev_gen_rows * tbl->row_size, 0, count->inserted_rows * tbl->row_size); + } +} + + /** * LOCKING: Assumes the publish_lock is held + * Returns: The previous latest delta, or NULL if this is the first delta */ -void +DeltaInfo * image_append_delta (MonoImage *base, BaselineInfo *base_info, MonoImage *delta, DeltaInfo *delta_info) { - if (!base_info->delta_image) { - base_info->delta_image = base_info->delta_image_last = g_list_alloc (); - base_info->delta_image->data = (gpointer)delta; + if (!base_info->delta_info) { + base_info->delta_info = base_info->delta_info_last = g_list_alloc (); + base_info->delta_info->data = (gpointer)delta_info; mono_memory_write_barrier (); /* Have to set this here so that passes over the metadata in the updater thread start using the slow path */ base->has_updates = TRUE; - return; + return NULL; } - g_assert (delta_info_lookup(((MonoImage*)base_info->delta_image_last->data))->generation < delta_info->generation); + DeltaInfo *prev_last_delta = (DeltaInfo*)base_info->delta_info_last->data; + g_assert (prev_last_delta->generation < delta_info->generation); /* g_list_append returns the given list, not the newly appended */ - GList *l = g_list_append (base_info->delta_image_last, delta); + GList *l = g_list_append (base_info->delta_info_last, delta_info); g_assert (l != NULL && l->next != NULL && l->next->next == NULL); - base_info->delta_image_last = l->next; + base_info->delta_info_last = l->next; mono_memory_write_barrier (); /* Have to set this here so that passes over the metadata in the updater thread start using the slow path */ base->has_updates = TRUE; - + return prev_last_delta; } /** @@ -773,8 +877,33 @@ dump_update_summary (MonoImage *image_base, MonoImage *image_dmeta) } +static gboolean +effective_table_mutant (MonoImage *base, BaselineInfo *info, int tbl_index, const MonoTableInfo **t, int idx) +{ + GList *ptr =info->delta_info_last; + uint32_t exposed_gen = hot_reload_get_thread_generation (); + MonoImage *dmeta = NULL; + DeltaInfo *delta_info = NULL; + + /* walk backward from the latest image until we find one that matches the current thread's exposed generation */ + do { + delta_info = (DeltaInfo*)ptr->data; + dmeta = delta_info->delta_image; + if (delta_info->generation <= exposed_gen) + break; + ptr = ptr->prev; + } while (ptr); + if (!ptr) + return FALSE; + g_assert (dmeta != NULL); + g_assert (delta_info != NULL); + + *t = &delta_info->mutants [tbl_index]; + return TRUE; +} + void -hot_reload_effective_table_slow (const MonoTableInfo **t, int *idx) +hot_reload_effective_table_slow (const MonoTableInfo **t, int idx) { /* FIXME: don't let any thread other than the updater thread see values from a delta image * with a generation past update_published @@ -788,61 +917,9 @@ hot_reload_effective_table_slow (const MonoTableInfo **t, int *idx) if (!info) return; - gboolean any_modified = info->any_modified_rows[tbl_index]; - - if (G_LIKELY (*idx < table_info_get_rows (*t) && !any_modified)) - return; - - /* FIXME: when adding methods this won't work anymore. We will need to update the delta - * images' suppressed columns (see the Note in pass2 about - * CMiniMdRW::m_SuppressedDeltaColumns) with the baseline values. */ - /* The only column from the updates that matters the RVA, which is looked up elsewhere. */ - if (tbl_index == MONO_TABLE_METHOD) - return; + gboolean success = effective_table_mutant (base, info, tbl_index, t, idx); - GList *list = info->delta_image; - MonoImage *dmeta = NULL; - int ridx; - MonoTableInfo *table; - int g = 0; - - /* Candidate: the last delta that had updates for the requested row */ - MonoImage *cand_dmeta = NULL; - MonoTableInfo *cand_table = NULL; - int cand_ridx = -1; - int cand_g = 0; - - gboolean cont; - do { - g_assertf (list, "couldn't find idx=0x%08x in assembly=%s", *idx, dmeta && dmeta->name ? dmeta->name : "unknown image"); - dmeta = (MonoImage*)list->data; - list = list->next; - table = &dmeta->tables [tbl_index]; - int rel_row = hot_reload_relative_delta_index (dmeta, mono_metadata_make_token (tbl_index, *idx + 1)); - g_assert (rel_row == -1 || (rel_row > 0 && rel_row <= table_info_get_rows (table))); - g++; - if (rel_row != -1) { - cand_dmeta = dmeta; - cand_table = table; - cand_ridx = rel_row - 1; - cand_g = g; - } - ridx = rel_row - 1; - if (!any_modified) { - /* if the table only got additions, not modifications, don't continue after we find the first image that has the right number of rows */ - cont = ridx < 0 || ridx >= table_info_get_rows (table); - } else { - /* otherwise, keep going in case a later generation modified the row again */ - cont = list != NULL; - } - } while (cont); - - if (cand_ridx != -1) { - mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "effective table for %s: 0x%08x -> 0x%08x (rows = 0x%08x) (gen %d, g %d)", mono_meta_table_name (tbl_index), *idx, cand_ridx, table_info_get_rows (cand_table), metadata_update_local_generation (base, info, cand_dmeta), cand_g); - - *t = cand_table; - *idx = cand_ridx; - } + g_assert (success); } /* @@ -866,29 +943,25 @@ hot_reload_effective_table_slow (const MonoTableInfo **t, int *idx) * relative index faster. */ int -hot_reload_relative_delta_index (MonoImage *image_dmeta, int token) +hot_reload_relative_delta_index (MonoImage *image_dmeta, DeltaInfo *delta_info, int token) { MonoTableInfo *encmap = &image_dmeta->tables [MONO_TABLE_ENCMAP]; + int table = mono_metadata_token_table (token); int index = mono_metadata_token_index (token); - /* this helper expects and returns as "index origin = 1" */ - g_assert (index > 0); + int index_map = delta_info->enc_recs [table]; + int encmap_rows = table_info_get_rows (encmap); if (!table_info_get_rows (encmap) || !image_dmeta->minimal_delta) return mono_metadata_token_index (token); - DeltaInfo *delta_info = delta_info_lookup (image_dmeta); - g_assert (delta_info); - - int index_map = delta_info->enc_recs [table]; - int encmap_rows = table_info_get_rows (encmap); - /* if the table didn't have any updates in this generation and the * table index is bigger than the last table that got updates, * enc_recs will point past the last row */ if (index_map - 1 == encmap_rows) return -1; + guint32 cols[MONO_ENCMAP_SIZE]; mono_metadata_decode_row (encmap, index_map - 1, cols, MONO_ENCMAP_SIZE); int map_entry = cols [MONO_ENCMAP_TOKEN]; @@ -930,7 +1003,7 @@ hot_reload_relative_delta_index (MonoImage *image_dmeta, int token) /* LOCKING: assumes publish_lock is held */ static DeltaInfo* -delta_info_init (MonoImage *image_dmeta, MonoImage *image_base, MonoPPDBFile *ppdb_file, BaselineInfo *base_info, uint32_t generation) +delta_info_init (MonoImage *image_dmeta, MonoImage *image_base, MonoPPDBFile *ppdb_file, BaselineInfo *base_info, uint32_t generation, DeltaInfo **prev_delta_info) { MonoTableInfo *encmap = &image_dmeta->tables [MONO_TABLE_ENCMAP]; g_assert (!delta_info_lookup (image_dmeta)); @@ -942,13 +1015,19 @@ delta_info_init (MonoImage *image_dmeta, MonoImage *image_base, MonoPPDBFile *pp delta_info->generation = generation; delta_info->ppdb_file = ppdb_file; + delta_info->delta_image = image_dmeta; table_to_image_lock (); g_hash_table_insert (delta_image_to_info, image_dmeta, delta_info); table_to_image_unlock (); + delta_info->pool = mono_mempool_new (); + + + g_assert (prev_delta_info); + /* base_image takes ownership of 1 refcount ref of dmeta_image */ - image_append_delta (image_base, base_info, image_dmeta, delta_info); + *prev_delta_info = image_append_delta (image_base, base_info, image_dmeta, delta_info); return delta_info; } @@ -962,19 +1041,15 @@ delta_info_compute_table_records (MonoImage *image_dmeta, MonoImage *image_base, int table, prev_table = -1, idx; /*** Compute logical table sizes ***/ - if (base_info->delta_image == base_info->delta_image_last) { + if (base_info->delta_info == base_info->delta_info_last) { /* this is the first update. */ for (int i = 0; i < MONO_TABLE_NUM; ++i) { delta_info->count[i].prev_gen_rows = table_info_get_rows (&image_base->tables[i]); } } else { - /* Current image_dmeta is image_base->delta_image_last->data, - * find its predecessor - */ - MonoImage *prev_delta = NULL; - g_assert (base_info->delta_image_last->prev != NULL); - prev_delta = (MonoImage*)base_info->delta_image_last->prev->data; - DeltaInfo *prev_gen_info = delta_info_lookup (prev_delta); + g_assert (delta_info == (DeltaInfo*)base_info->delta_info_last->data); + g_assert (base_info->delta_info_last->prev != NULL); + DeltaInfo *prev_gen_info = (DeltaInfo*)base_info->delta_info_last->prev->data; for (int i = 0; i < MONO_TABLE_NUM; ++i) { delta_info->count[i].prev_gen_rows = prev_gen_info->count[i].prev_gen_rows + prev_gen_info->count[i].inserted_rows; } @@ -1025,6 +1100,15 @@ delta_info_compute_table_records (MonoImage *image_dmeta, MonoImage *image_base, return TRUE; } +enum MonoEnCFuncCode { + ENC_FUNC_DEFAULT = 0, + ENC_FUNC_ADD_METHOD = 1, + ENC_FUNC_ADD_FIELD = 2, + ENC_FUNC_ADD_PARAM = 3, + ENC_FUNC_ADD_PROPERTY = 4, + ENC_FUNC_ADD_EVENT = 5, +}; + static const char* funccode_to_str (int func_code) { @@ -1040,11 +1124,106 @@ funccode_to_str (int func_code) return NULL; } +/* + * Apply the row from the delta image given by log_token to the cur_delta mutated table. + * + */ +static void +delta_info_mutate_row (MonoImage *image_base, MonoImage *image_dmeta, DeltaInfo *cur_delta, guint32 log_token) +{ + int token_table = mono_metadata_token_table (log_token); + int token_index = mono_metadata_token_index (log_token); /* 1-based */ + + gboolean modified = token_index <= cur_delta->count [token_table].prev_gen_rows; + + int delta_index = hot_reload_relative_delta_index (image_dmeta, cur_delta, log_token); + + /* The complication here is that we want the mutant table to look like the table in + * image_base with respect to column widths, but the delta tables are generally coming in + * uncompressed (4-byte columns). So we have to copy one column at a time and adjust the + * widths as we go. + */ + + guint32 dst_bitfield = image_base->tables [token_table].size_bitfield; + guint32 src_bitfield = image_dmeta->tables [token_table].size_bitfield; + + const char *src_base = image_dmeta->tables [token_table].base + (delta_index - 1) * image_dmeta->tables [token_table].row_size; + char *dst_base = (char*)cur_delta->mutants [token_table].base + (token_index - 1) * image_base->tables [token_table].row_size; + + guint32 src_offset = 0, dst_offset = 0; + for (int col = 0; col < mono_metadata_table_count (dst_bitfield); ++col) { + guint32 dst_col_size = mono_metadata_table_size (dst_bitfield, col); + guint32 src_col_size = mono_metadata_table_size (src_bitfield, col); + if ((m_SuppressedDeltaColumns [token_table] & (1 << col)) == 0) { + const char *src = src_base + src_offset; + char *dst = dst_base + dst_offset; + + /* copy src to dst, via a temporary to adjust for size differences */ + /* FIXME: unaligned access, endianness */ + guint32 tmp; + + switch (src_col_size) { + case 1: + tmp = *(guint8*)src; + break; + case 2: + tmp = *(guint16*)src; + break; + case 4: + tmp = *(guint32*)src; + break; + default: + g_assert_not_reached (); + } + + /* FIXME: unaligned access, endianness */ + switch (dst_col_size) { + case 1: + *(guint8*)dst = (guint8)tmp; + break; + case 2: + *(guint16*)dst = (guint16)tmp; + break; + case 4: + *(guint32*)dst = tmp; + break; + default: + g_assert_not_reached (); + } + + } + src_offset += src_col_size; + dst_offset += dst_col_size; + } + g_assert (dst_offset == image_base->tables [token_table].row_size); + + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "mutate: table=0x%02x row=0x%04x delta row=0x%04x %s", token_table, token_index, delta_index, modified ? "Mod" : "Add"); +} + +static void +prepare_mutated_rows (const MonoTableInfo *table_enclog, MonoImage *image_base, MonoImage *image_dmeta, DeltaInfo *delta_info) +{ + int rows = table_info_get_rows (table_enclog); + /* Prepare the mutated metadata tables */ + for (int i = 0; i < rows ; ++i) { + guint32 cols [MONO_ENCLOG_SIZE]; + mono_metadata_decode_row (table_enclog, i, cols, MONO_ENCLOG_SIZE); + + int log_token = cols [MONO_ENCLOG_TOKEN]; + int func_code = cols [MONO_ENCLOG_FUNC_CODE]; + + if (func_code != ENC_FUNC_DEFAULT) + continue; + + delta_info_mutate_row (image_base, image_dmeta, delta_info, log_token); + } +} + /* Run some sanity checks first. If we detect unsupported scenarios, this * function will fail and the metadata update should be aborted. This should * run before anything in the metadata world is updated. */ static gboolean -apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer dil_data, uint32_t dil_length, MonoError *error) +apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, DeltaInfo *delta_info, gconstpointer dil_data, uint32_t dil_length, MonoError *error) { MonoTableInfo *table_enclog = &image_dmeta->tables [MONO_TABLE_ENCLOG]; int rows = table_info_get_rows (table_enclog); @@ -1069,18 +1248,29 @@ apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer int token_table = mono_metadata_token_table (log_token); int token_index = mono_metadata_token_index (log_token); - mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "row[0x%02x]:0x%08x (%s idx=0x%02x) (base table has 0x%04x rows)\tfunc=0x%02x\n", i, log_token, mono_meta_table_name (token_table), token_index, table_info_get_rows (&image_base->tables [token_table]), func_code); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "row[0x%02x]:0x%08x (%s idx=0x%02x) (base table has 0x%04x rows)\tfunc=0x%02x (\"%s\")\n", i, log_token, mono_meta_table_name (token_table), token_index, table_info_get_rows (&image_base->tables [token_table]), func_code, funccode_to_str (func_code)); if (token_table != MONO_TABLE_METHOD) continue; +#ifndef ALLOW_METHOD_ADD + if (token_index > table_info_get_rows (&image_base->tables [token_table])) { mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "\tcannot add new method with token 0x%08x", log_token); mono_error_set_type_load_name (error, NULL, image_base->name, "EnC: cannot add new method with token 0x%08x", log_token); unsupported_edits = TRUE; } +#endif + +#ifdef ALLOW_METHOD_ADD + /* adding a new parameter to a new method is ok */ + /* FIXME: total rows, not just the baseline rows */ + if (func_code == ENC_FUNC_ADD_PARAM && token_index > table_info_get_rows (&image_base->tables [token_table])) + continue; +#endif + g_assert (func_code == 0); /* anything else doesn't make sense here */ } @@ -1099,6 +1289,10 @@ apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer /* okay, supported */ break; case MONO_TABLE_METHOD: +#ifdef ALLOW_METHOD_ADD + if (func_code == ENC_FUNC_ADD_PARAM) + continue; /* ok, allowed */ +#endif /* handled above */ break; case MONO_TABLE_PROPERTY: { @@ -1116,7 +1310,7 @@ apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer if (token_index > table_info_get_rows (&image_base->tables [token_table])) { /* new rows are fine, as long as they point at existing methods */ guint32 sema_cols [MONO_METHOD_SEMA_SIZE]; - int mapped_token = hot_reload_relative_delta_index (image_dmeta, mono_metadata_make_token (token_table, token_index)); + int mapped_token = hot_reload_relative_delta_index (image_dmeta, delta_info, mono_metadata_make_token (token_table, token_index)); g_assert (mapped_token != -1); mono_metadata_decode_row (&image_dmeta->tables [MONO_TABLE_METHODSEMANTICS], mapped_token - 1, sema_cols, MONO_METHOD_SEMA_SIZE); @@ -1151,7 +1345,7 @@ apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer /* modifying existing rows is ok, as long as the parent and ctor are the same */ guint32 ca_upd_cols [MONO_CUSTOM_ATTR_SIZE]; guint32 ca_base_cols [MONO_CUSTOM_ATTR_SIZE]; - int mapped_token = hot_reload_relative_delta_index (image_dmeta, mono_metadata_make_token (token_table, token_index)); + int mapped_token = hot_reload_relative_delta_index (image_dmeta, delta_info, mono_metadata_make_token (token_table, token_index)); g_assert (mapped_token != -1); mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "row[0x%02x]:0x%08x CUSTOM_ATTR update. mapped index = 0x%08x\n", i, log_token, mapped_token); @@ -1186,7 +1380,7 @@ apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer /* We only allow modifications where the parameter name doesn't change. */ uint32_t base_param [MONO_PARAM_SIZE]; uint32_t upd_param [MONO_PARAM_SIZE]; - int mapped_token = hot_reload_relative_delta_index (image_dmeta, mono_metadata_make_token (token_table, token_index)); + int mapped_token = hot_reload_relative_delta_index (image_dmeta, delta_info, mono_metadata_make_token (token_table, token_index)); g_assert (mapped_token != -1); mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "row[0x%02x]:0x%08x PARAM update. mapped index = 0x%08x\n", i, log_token, mapped_token); @@ -1206,6 +1400,28 @@ apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer } else break; /* added a row. ok */ } + case MONO_TABLE_TYPEDEF: { +#ifdef ALLOW_METHOD_ADD + /* FIXME: wrong for cumulative updates - need to look at DeltaInfo:count.prev_gen_rows */ + gboolean new_class = token_index > table_info_get_rows (&image_base->tables [token_table]); + /* only allow adding methods to existing classes for now */ + if (!new_class && func_code == ENC_FUNC_ADD_METHOD) { + /* next record should be a MONO_TABLE_METHOD addition (func == default) */ + g_assert (i + 1 < rows); + guint32 next_cols [MONO_ENCLOG_SIZE]; + mono_metadata_decode_row (table_enclog, i + 1, next_cols, MONO_ENCLOG_SIZE); + g_assert (next_cols [MONO_ENCLOG_FUNC_CODE] == ENC_FUNC_DEFAULT); + int next_token = next_cols [MONO_ENCLOG_TOKEN]; + int next_table = mono_metadata_token_table (next_token); + int next_index = mono_metadata_token_index (next_token); + g_assert (next_table == MONO_TABLE_METHOD); + g_assert (next_index > table_info_get_rows (&image_base->tables [next_table])); + i++; /* skip the next record */ + continue; + } +#endif + /* fallthru */ + } default: /* FIXME: this bounds check is wrong for cumulative updates - need to look at the DeltaInfo:count.prev_gen_rows */ if (token_index <= table_info_get_rows (&image_base->tables [token_table])) { @@ -1223,7 +1439,7 @@ apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer * the preceeding MONO_TABLE_TYPEDEF enc record that identifies the parent type). */ switch (func_code) { - case 0: /* default */ + case ENC_FUNC_DEFAULT: /* default */ break; default: mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "row[0x%02x]:0x%08x FunCode %d (%s) not supported (token=0x%08x)", i, log_token, func_code, funccode_to_str (func_code), log_token); @@ -1235,6 +1451,12 @@ apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer return !unsupported_edits; } +static void +set_delta_method_debug_info (DeltaInfo *delta_info, uint32_t token_index, MonoDebugInformationEnc *pdb_address) +{ + g_hash_table_insert (delta_info->method_ppdb_table_update, GUINT_TO_POINTER (token_index), (gpointer) pdb_address); +} + static void set_update_method (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, MonoImage *image_dmeta, DeltaInfo *delta_info, uint32_t token_index, const char* il_address, MonoDebugInformationEnc* pdb_address) { @@ -1242,7 +1464,7 @@ set_update_method (MonoImage *image_base, BaselineInfo *base_info, uint32_t gene /* FIXME: this is a race if other threads are doing a lookup. */ g_hash_table_insert (base_info->method_table_update, GUINT_TO_POINTER (token_index), GUINT_TO_POINTER (generation)); g_hash_table_insert (delta_info->method_table_update, GUINT_TO_POINTER (token_index), (gpointer) il_address); - g_hash_table_insert (delta_info->method_ppdb_table_update, GUINT_TO_POINTER (token_index), (gpointer) pdb_address); + set_delta_method_debug_info (delta_info, token_index, pdb_address); } static MonoDebugInformationEnc * @@ -1272,6 +1494,26 @@ hot_reload_get_method_debug_information (MonoPPDBFile *ppdb_file, int idx) return NULL; } +static void G_GNUC_UNUSED +dump_assembly_ref_names (MonoImage *image) +{ + if (!mono_trace_is_traced (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE)) + return; + for (int i = 0; i < image->nreferences; ++i) { + ERROR_DECL(local_error); + MonoAssemblyName aname; + mono_assembly_get_assemblyref_checked (image, i, &aname, local_error); + + if (is_ok (local_error)) + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "Reference[%02d] = '%s'", i, aname.name); + else { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "Reference[%02d] error '%s'", i, mono_error_get_message (local_error)); + mono_error_cleanup (local_error); + } + } +} + + /* do actuall enclog application */ static gboolean apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t generation, MonoImage *image_dmeta, DeltaInfo *delta_info, gconstpointer dil_data, uint32_t dil_length, MonoError *error) @@ -1302,6 +1544,12 @@ apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t gen * have it in writable memory (and not mmap-ed pages), so we can rewrite the table values. */ + + MonoClass *add_method_klass = NULL; +#ifdef ALLOW_METHOD_ADD + uint32_t add_param_method_index = 0; +#endif + gboolean assemblyref_updated = FALSE; for (int i = 0; i < rows ; ++i) { guint32 cols [MONO_ENCLOG_SIZE]; @@ -1317,11 +1565,31 @@ apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t gen /* TODO: See CMiniMdRW::ApplyDelta for how to drive this. */ switch (func_code) { - case 0: /* default */ - break; - default: - g_error ("EnC: unsupported FuncCode, should be caught by pass1"); - break; + case ENC_FUNC_DEFAULT: /* default */ + break; +#ifdef ALLOW_METHOD_ADD + case ENC_FUNC_ADD_METHOD: { + g_assert (token_table == MONO_TABLE_TYPEDEF); + /* FIXME: this bounds check is wrong for cumulative updates - need to look at the DeltaInfo:count.prev_gen_rows */ + /* should've been caught by pass1 if we're adding a new method to a new class. */ + MonoClass *klass = mono_class_get_checked (image_base, log_token, error); + if (!is_ok (error)) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "Can't get class with token 0x%08x due to: %s", log_token, mono_error_get_message (error)); + return FALSE; + } + add_method_klass = klass; + break; + } + + case ENC_FUNC_ADD_PARAM: { + g_assert (token_table == MONO_TABLE_METHOD); + add_param_method_index = token_index; + break; + } +#endif + default: + g_error ("EnC: unsupported FuncCode, should be caught by pass1"); + break; } switch (token_table) { @@ -1336,8 +1604,8 @@ apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t gen /* FIXME: use DeltaInfo:prev_gen_rows instead of looping */ /* TODO: do we know that there will never be modified rows in ASSEMBLYREF? */ int old_rows = table_info_get_rows (&image_base->tables [MONO_TABLE_ASSEMBLYREF]); - for (GList *l = base_info->delta_image; l; l = l->next) { - MonoImage *delta_child = l->data; + for (GList *l = base_info->delta_info; l; l = l->next) { + MonoImage *delta_child = ((DeltaInfo*)l->data)->delta_image; old_rows += table_info_get_rows (&delta_child->tables [MONO_TABLE_ASSEMBLYREF]); } int new_rows = table_info_get_rows (&image_dmeta->tables [MONO_TABLE_ASSEMBLYREF]); @@ -1356,13 +1624,29 @@ apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t gen image_base->nreferences = old_rows + new_rows; mono_image_unlock (image_base); +#if 0 + dump_assembly_ref_names (image_base); +#endif + g_free (old_array); break; } case MONO_TABLE_METHOD: { +#ifdef ALLOW_METHOD_ADD + /* if adding a param, handle it with the next record */ + if (func_code == ENC_FUNC_ADD_PARAM) + break; + if (token_index > table_info_get_rows (&image_base->tables [token_table])) { - g_error ("EnC: new method added, should be caught by pass1"); + if (!add_method_klass) + g_error ("EnC: new method added but I don't know the class, should be caught by pass1"); + g_assert (add_method_klass); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "Adding new method 0x%08x to class %s.%s", token_index, m_class_get_name_space (add_method_klass), m_class_get_name (add_method_klass)); + MonoDebugInformationEnc *method_debug_information = hot_reload_get_method_debug_information (delta_info->ppdb_file, token_index); + add_method_to_baseline (base_info, delta_info, add_method_klass, token_index, method_debug_information); + add_method_klass = NULL; } +#endif if (!base_info->method_table_update) base_info->method_table_update = g_hash_table_new (g_direct_hash, g_direct_equal); @@ -1372,7 +1656,7 @@ apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t gen delta_info->method_ppdb_table_update = g_hash_table_new (g_direct_hash, g_direct_equal); - int mapped_token = hot_reload_relative_delta_index (image_dmeta, mono_metadata_make_token (token_table, token_index)); + int mapped_token = hot_reload_relative_delta_index (image_dmeta, delta_info, mono_metadata_make_token (token_table, token_index)); int rva = mono_metadata_decode_row_col (&image_dmeta->tables [MONO_TABLE_METHOD], mapped_token - 1, MONO_METHOD_RVA); if (rva < dil_length) { char *il_address = ((char *) dil_data) + rva; @@ -1382,6 +1666,7 @@ apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t gen /* rva points probably into image_base IL stream. can this ever happen? */ g_print ("TODO: this case is still a bit contrived. token=0x%08x with rva=0x%04x\n", log_token, rva); } + add_method_klass = NULL; break; } case MONO_TABLE_TYPEDEF: { @@ -1405,6 +1690,26 @@ apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t gen } case MONO_TABLE_PARAM: { /* ok, pass1 checked for disallowed modifications */ + /* ALLOW_METHOD_ADD: FIXME: here we would really like to update the method's paramlist column to point to the new params. */ + /* if there were multiple added methods, this comes in as several method + * additions, followed by the parameter additions. + * + * 10: 0x02000002 (TypeDef) 0x00000001 (AddMethod) + * 11: 0x06000006 (MethodDef) 0 + * 12: 0x02000002 (TypeDef) 0x00000001 (AddMethod) + * 13: 0x06000007 (MethodDef) 0 + * 14: 0x06000006 (MethodDef) 0x00000003 (AddParameter) + * 15: 0x08000003 (Param) 0 + * 16: 0x06000006 (MethodDef) 0x00000003 (AddParameter) + * 17: 0x08000004 (Param) 0 + * 18: 0x06000007 (MethodDef) 0x00000003 (AddParameter) + * 19: 0x08000005 (Param) 0 + * + * So by the time we see the param additions, the methods are already in. + * + * FIXME: we need a lookaside table (like method_parent) for every place + * that looks at MONO_METHOD_PARAMLIST + */ break; } default: { @@ -1468,6 +1773,7 @@ hot_reload_apply_changes (int origin, MonoImage *image_base, gconstpointer dmeta /* makes a copy of dil_bytes_orig */ gpointer dil_bytes = open_dil_data (image_base, dil_bytes_orig, dil_length); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "delta IL bytes copied to addr=%p", dil_bytes); MonoPPDBFile *ppdb_file = NULL; if (dpdb_length > 0) @@ -1482,7 +1788,8 @@ hot_reload_apply_changes (int origin, MonoImage *image_base, gconstpointer dmeta BaselineInfo *base_info = baseline_info_lookup_or_add (image_base); - DeltaInfo *delta_info = delta_info_init (image_dmeta, image_base, ppdb_file, base_info, generation); + DeltaInfo *prev_delta_info = NULL; + DeltaInfo *delta_info = delta_info_init (image_dmeta, image_base, ppdb_file, base_info, generation, &prev_delta_info); if (image_dmeta->minimal_delta) { @@ -1517,8 +1824,14 @@ hot_reload_apply_changes (int origin, MonoImage *image_base, gconstpointer dmeta return; } + delta_info_initialize_mutants (image_base, base_info, prev_delta_info, delta_info); + + prepare_mutated_rows (table_enclog, image_base, image_dmeta, delta_info); + + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "Populated mutated tables for delta image %p", image_dmeta); + - if (!apply_enclog_pass1 (image_base, image_dmeta, dil_bytes, dil_length, error)) { + if (!apply_enclog_pass1 (image_base, image_dmeta, delta_info, dil_bytes, dil_length, error)) { mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "Error on sanity-checking delta image to base=%s, due to: %s", basename, mono_error_get_message (error)); hot_reload_update_cancel (generation); return; @@ -1547,35 +1860,6 @@ hot_reload_apply_changes (int origin, MonoImage *image_base, gconstpointer dmeta mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, ">>> EnC delta for base=%s (generation %d) applied", basename, generation); } -/* - * Returns how many times the base image was updated upto and including the given delta. - */ -static int -metadata_update_local_generation (MonoImage *base, BaselineInfo *base_info, MonoImage *delta) -{ - if (delta == base) - return 0; - int index = g_list_index (base_info->delta_image, delta); - g_assert (index != -1); - return 1 + index; -} - -/* - * Returns how many times the given base image has been updated so far. - * - * NOTE: doesn't look at update_published or update_alloc_frontier, and therefore only usable by the - * update originator. - */ -static int -metadata_update_count_updates (MonoImage *base) -{ - BaselineInfo *base_info = baseline_info_lookup (base); - if (!base_info || !base_info->delta_image_last) - return 0; - else - return metadata_update_local_generation (base, base_info, (MonoImage*)base_info->delta_image_last->data); -} - static gpointer get_method_update_rva (MonoImage *image_base, BaselineInfo *base_info, uint32_t idx, gboolean is_pdb) { @@ -1585,9 +1869,8 @@ get_method_update_rva (MonoImage *image_base, BaselineInfo *base_info, uint32_t /* Go through all the updates that the current thread can see and see * if they updated the method. Keep the latest visible update */ - for (GList *ptr = base_info->delta_image; ptr != NULL; ptr = ptr->next) { - MonoImage *image_delta = (MonoImage*) ptr->data; - DeltaInfo *delta_info = delta_info_lookup (image_delta); + for (GList *ptr = base_info->delta_info; ptr != NULL; ptr = ptr->next) { + DeltaInfo *delta_info = (DeltaInfo*)ptr->data; g_assert (delta_info); if (delta_info->generation > cur) break; @@ -1624,6 +1907,10 @@ hot_reload_get_updated_method_ppdb (MonoImage *base_image, uint32_t idx) if (G_UNLIKELY (gen > 0)) { loc = get_method_update_rva (base_image, info, idx, TRUE); } + /* Check the method_parent table as a way of checking if the method was added by a later generation. If so, still look for its PPDB info in our update tables */ + if (G_UNLIKELY (loc == 0 && info->method_parent && GPOINTER_TO_UINT (g_hash_table_lookup (info->method_parent, GUINT_TO_POINTER (idx))) > 0)) { + loc = get_method_update_rva (base_image, info, idx, TRUE); + } } return loc; } @@ -1653,27 +1940,25 @@ hot_reload_table_bounds_check (MonoImage *base_image, int table_index, int token BaselineInfo *base_info = baseline_info_lookup (base_image); g_assert (base_info); - GList *list = base_info->delta_image; + GList *list = base_info->delta_info; MonoImage *dmeta; MonoTableInfo *table; /* result row, 0-based */ int ridx; - int original_token = mono_metadata_make_token (table_index, token_index); - uint32_t exposed_gen = hot_reload_get_thread_generation (); do { if (!list) return TRUE; - dmeta = list->data; - DeltaInfo *delta_info = delta_info_lookup (dmeta); + DeltaInfo *delta_info = (DeltaInfo*)list->data; g_assert (delta_info); + dmeta = delta_info->delta_image; if (delta_info->generation > exposed_gen) return TRUE; list = list->next; - table = &dmeta->tables [table_index]; - /* mono_image_relative_delta_index returns a 1-based index */ - ridx = hot_reload_relative_delta_index (dmeta, original_token) - 1; + + table = &delta_info->mutants [table_index]; + ridx = token_index - 1; } while (ridx < 0 || ridx >= table_info_get_rows (table)); return FALSE; @@ -1688,7 +1973,7 @@ hot_reload_delta_heap_lookup (MonoImage *base_image, MetadataHeapGetterFunc get_ g_assert (orig_index >= heap->size); BaselineInfo *base_info = baseline_info_lookup (base_image); g_assert (base_info); - g_assert (base_info->delta_image); + g_assert (base_info->delta_info); *image_out = base_image; *index_out = orig_index; @@ -1697,13 +1982,15 @@ hot_reload_delta_heap_lookup (MonoImage *base_image, MetadataHeapGetterFunc get_ uint32_t current_gen = hot_reload_get_thread_generation (); GList *cur; - for (cur = base_info->delta_image; cur; cur = cur->next) { - MonoImage *delta_image = (MonoImage*)cur->data; + for (cur = base_info->delta_info; cur; cur = cur->next) { + DeltaInfo *delta_info = (DeltaInfo*)cur->data; + g_assert (delta_info); + MonoImage *delta_image = delta_info->delta_image; + g_assert (delta_image); heap = get_heap (delta_image); *image_out = delta_image; - DeltaInfo *delta_info = delta_info_lookup (delta_image); if (delta_info->generation > current_gen) return FALSE; @@ -1730,3 +2017,74 @@ hot_reload_has_modified_rows (const MonoTableInfo *table) return info->any_modified_rows[tbl_index]; } +static int +hot_reload_table_num_rows_slow (MonoImage *base, int table_index) +{ + BaselineInfo *base_info = baseline_info_lookup (base); + if (!base_info) + return FALSE; + + uint32_t current_gen = hot_reload_get_thread_generation (); + + int rows = table_info_get_rows (&base->tables [table_index]); + GList *cur; + for (cur = base_info->delta_info; cur; cur = cur->next) { + DeltaInfo *delta_info = (DeltaInfo*)cur->data; + g_assert (delta_info); + if (delta_info->generation > current_gen) + break; + rows = delta_info->count [table_index].prev_gen_rows + delta_info->count [table_index].inserted_rows; + } + return rows; +} + +static void +add_method_to_baseline (BaselineInfo *base_info, DeltaInfo *delta_info, MonoClass *klass, uint32_t method_token, MonoDebugInformationEnc* pdb_address) +{ + if (!base_info->added_methods) { + base_info->added_methods = g_hash_table_new (g_direct_hash, g_direct_equal); + } + if (!base_info->method_parent) { + base_info->method_parent = g_hash_table_new (g_direct_hash, g_direct_equal); + } + /* FIXME: locking for readers/writers of the GArray */ + GArray *arr = g_hash_table_lookup (base_info->added_methods, klass); + if (!arr) { + arr = g_array_new (FALSE, FALSE, sizeof(uint32_t)); + g_hash_table_insert (base_info->added_methods, klass, arr); + } + g_array_append_val (arr, method_token); + g_hash_table_insert (base_info->method_parent, GUINT_TO_POINTER (method_token), GUINT_TO_POINTER (m_class_get_type_token (klass))); + + if (pdb_address) + set_delta_method_debug_info (delta_info, method_token, pdb_address); +} + +static GArray* +hot_reload_get_added_methods (MonoClass *klass) +{ + /* FIXME: locking for the GArray? */ + MonoImage *image = m_class_get_image (klass); + if (!image->has_updates) + return NULL; + BaselineInfo *base_info = baseline_info_lookup (image); + if (!base_info || base_info->added_methods == NULL) + return NULL; + + return g_hash_table_lookup (base_info->added_methods, klass); +} + +static uint32_t +hot_reload_method_parent (MonoImage *base_image, uint32_t method_token) +{ + if (!base_image->has_updates) + return 0; + BaselineInfo *base_info = baseline_info_lookup (base_image); + if (!base_info || base_info->method_parent == NULL) + return 0; + + uint32_t res = GPOINTER_TO_UINT (g_hash_table_lookup (base_info->method_parent, GUINT_TO_POINTER (method_token))); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "method_parent lookup: 0x%08x returned 0x%08x\n", method_token, res); + + return res; +} diff --git a/src/mono/mono/component/hot_reload.h b/src/mono/mono/component/hot_reload.h index 37dc1401bbd11..416ff56e70eab 100644 --- a/src/mono/mono/component/hot_reload.h +++ b/src/mono/mono/component/hot_reload.h @@ -21,8 +21,7 @@ typedef struct _MonoComponentHotReload { uint32_t (*thread_expose_published) (void); uint32_t (*get_thread_generation) (void); void (*cleanup_on_close) (MonoImage *image); - void (*effective_table_slow) (const MonoTableInfo **t, int *idx); - int (*relative_delta_index) (MonoImage *image_dmeta, int token); + void (*effective_table_slow) (const MonoTableInfo **t, int idx); void (*apply_changes) (int origin, MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, gconstpointer dpdb_bytes_orig, uint32_t dpdb_length, MonoError *error); void (*image_close_except_pools_all) (MonoImage *base_image); void (*image_close_all) (MonoImage *base_image); @@ -31,6 +30,9 @@ typedef struct _MonoComponentHotReload { gboolean (*delta_heap_lookup) (MonoImage *base_image, MetadataHeapGetterFunc get_heap, uint32_t orig_index, MonoImage **image_out, uint32_t *index_out); gpointer (*get_updated_method_ppdb) (MonoImage *base_image, uint32_t idx); gboolean (*has_modified_rows) (const MonoTableInfo *table); + gboolean (*table_num_rows_slow) (MonoImage *base_image, int table_index); + GArray* (*get_added_methods) (MonoClass *klass); + uint32_t (*method_parent) (MonoImage *base_image, uint32_t method_index); } MonoComponentHotReload; MONO_COMPONENT_EXPORT_ENTRYPOINT diff --git a/src/mono/mono/metadata/loader.c b/src/mono/mono/metadata/loader.c index fc64623fad652..a3e451d4517f4 100644 --- a/src/mono/mono/metadata/loader.c +++ b/src/mono/mono/metadata/loader.c @@ -427,6 +427,8 @@ find_method_in_class (MonoClass *klass, const char *name, const char *qname, con { int i; + /* FIXME: method refs from metadata-upate probably end up here */ + /* Search directly in the metadata to avoid calling setup_methods () */ error_init (error); @@ -1160,6 +1162,8 @@ mono_get_method_checked (MonoImage *image, guint32 token, MonoClass *klass, Mono MonoMethod *result = NULL; gboolean used_context = FALSE; + /* FIXME: method definition lookups for metadata-update probably end up here */ + /* We do everything inside the lock to prevent creation races */ error_init (error); diff --git a/src/mono/mono/metadata/metadata-internals.h b/src/mono/mono/metadata/metadata-internals.h index f750438ac7d33..4e5ddf40abe86 100644 --- a/src/mono/mono/metadata/metadata-internals.h +++ b/src/mono/mono/metadata/metadata-internals.h @@ -791,24 +791,21 @@ gboolean mono_metadata_has_updates_api (void); void -mono_image_effective_table_slow (const MonoTableInfo **t, int *idx); +mono_image_effective_table_slow (const MonoTableInfo **t, int idx); gboolean mono_metadata_update_has_modified_rows (const MonoTableInfo *t); static inline void -mono_image_effective_table (const MonoTableInfo **t, int *idx) +mono_image_effective_table (const MonoTableInfo **t, int idx) { if (G_UNLIKELY (mono_metadata_has_updates ())) { - if (G_UNLIKELY (*idx >= table_info_get_rows ((*t)) || mono_metadata_update_has_modified_rows (*t))) { + if (G_UNLIKELY (idx >= table_info_get_rows ((*t)) || mono_metadata_update_has_modified_rows (*t))) { mono_image_effective_table_slow (t, idx); } } } -int -mono_image_relative_delta_index (MonoImage *image_dmeta, int token); - enum MonoEnCDeltaOrigin { MONO_ENC_DELTA_API = 0, MONO_ENC_DELTA_DBG = 1, @@ -848,6 +845,18 @@ mono_metadata_clean_generic_classes_for_image (MonoImage *image); gboolean mono_metadata_table_bounds_check_slow (MonoImage *image, int table_index, int token_index); +int +mono_metadata_table_num_rows_slow (MonoImage *image, int table_index); + +static inline int +mono_metadata_table_num_rows (MonoImage *image, int table_index) +{ + if (G_LIKELY (!image->has_updates)) + return table_info_get_rows (&image->tables [table_index]); + else + return mono_metadata_table_num_rows_slow (image, table_index); +} + /* token_index is 1-based */ static inline gboolean mono_metadata_table_bounds_check (MonoImage *image, int table_index, int token_index) @@ -1223,4 +1232,18 @@ mono_type_get_array_type_internal (MonoType *type) return type->data.array; } +static inline int +mono_metadata_table_to_ptr_table (int table_num) +{ + switch (table_num) { + case MONO_TABLE_FIELD: return MONO_TABLE_FIELD_POINTER; + case MONO_TABLE_METHOD: return MONO_TABLE_METHOD_POINTER; + case MONO_TABLE_PARAM: return MONO_TABLE_PARAM_POINTER; + case MONO_TABLE_PROPERTY: return MONO_TABLE_PROPERTY_POINTER; + case MONO_TABLE_EVENT: return MONO_TABLE_EVENT_POINTER; + default: + g_assert_not_reached (); + } +} + #endif /* __MONO_METADATA_INTERNALS_H__ */ diff --git a/src/mono/mono/metadata/metadata-update.c b/src/mono/mono/metadata/metadata-update.c index 99a2d99a5d7c3..800ffb9f04727 100644 --- a/src/mono/mono/metadata/metadata-update.c +++ b/src/mono/mono/metadata/metadata-update.c @@ -61,17 +61,11 @@ mono_metadata_update_cleanup_on_close (MonoImage *base_image) } void -mono_image_effective_table_slow (const MonoTableInfo **t, int *idx) +mono_image_effective_table_slow (const MonoTableInfo **t, int idx) { mono_component_hot_reload ()->effective_table_slow (t, idx); } -int -mono_image_relative_delta_index (MonoImage *image_dmeta, int token) -{ - return mono_component_hot_reload ()->relative_delta_index (image_dmeta, token); -} - void mono_image_load_enc_delta (int origin, MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, gconstpointer dpdb, uint32_t dpdb_len, MonoError *error) { @@ -141,3 +135,15 @@ mono_metadata_has_updates_api (void) { return mono_metadata_has_updates (); } + +/** + * mono_metadata_table_num_rows: + * + * Returns the number of rows from the specified table that the current thread can see. + * If there's a EnC metadata update, this number may change. + */ +int +mono_metadata_table_num_rows_slow (MonoImage *base_image, int table_index) +{ + return mono_component_hot_reload()->table_num_rows_slow (base_image, table_index); +} diff --git a/src/mono/mono/metadata/metadata.c b/src/mono/mono/metadata/metadata.c index 580a0988650e3..5912a12a0c909 100644 --- a/src/mono/mono/metadata/metadata.c +++ b/src/mono/mono/metadata/metadata.c @@ -1303,7 +1303,7 @@ mono_metadata_decode_row (const MonoTableInfo *t, int idx, guint32 *res, int res void mono_metadata_decode_row_slow (const MonoTableInfo *t, int idx, guint32 *res, int res_size) { - mono_image_effective_table (&t, &idx); + mono_image_effective_table (&t, idx); mono_metadata_decode_row_raw (t, idx, res, res_size); } @@ -1359,7 +1359,7 @@ mono_metadata_decode_row_checked (const MonoImage *image, const MonoTableInfo *t { const char *image_name = image && image->name ? image->name : "unknown image"; - mono_image_effective_table (&t, &idx); + mono_image_effective_table (&t, idx); guint32 bitfield = t->size_bitfield; int i, count = mono_metadata_table_count (bitfield); @@ -1448,7 +1448,7 @@ mono_metadata_decode_row_col (const MonoTableInfo *t, int idx, guint col) guint32 mono_metadata_decode_row_col_slow (const MonoTableInfo *t, int idx, guint col) { - mono_image_effective_table (&t, &idx); + mono_image_effective_table (&t, idx); return mono_metadata_decode_row_col_raw (t, idx, col); } @@ -2311,8 +2311,12 @@ mono_metadata_get_param_attrs (MonoImage *m, int def, int param_count) guint lastp, i, param_index = mono_metadata_decode_row_col (methodt, def - 1, MONO_METHOD_PARAMLIST); int *pattrs = NULL; + /* hot reload deltas may specify 0 for the param table index */ + if (param_index == 0) + return NULL; + /* FIXME: metadata-update */ - int rows = table_info_get_rows (methodt); + int rows = mono_metadata_table_num_rows (m, MONO_TABLE_METHOD); if (def < rows) lastp = mono_metadata_decode_row_col (methodt, def, MONO_METHOD_PARAMLIST); else @@ -4829,7 +4833,12 @@ mono_metadata_typedef_from_method (MonoImage *meta, guint32 index) if (meta->uncompressed_metadata) loc.idx = search_ptr_table (meta, MONO_TABLE_METHOD_POINTER, loc.idx); - /* FIXME: metadata-update */ + /* if it's not in the base image, look in the hot reload table */ + gboolean added = (loc.idx > table_info_get_rows (&meta->tables [MONO_TABLE_METHOD])); + if (added) { + uint32_t res = mono_component_hot_reload ()->method_parent (meta, loc.idx); + return res; /* 0 if not found, otherwise 1-based */ + } if (!mono_binary_search (&loc, tdef->base, table_info_get_rows (tdef), tdef->row_size, typedef_locator)) return 0;