diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/FirstCallAfterUpdate.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/FirstCallAfterUpdate.cs new file mode 100644 index 00000000000000..47bbcd171ad3b4 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/FirstCallAfterUpdate.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class FirstCallAfterUpdate { + public FirstCallAfterUpdate() {} + public string Method1 (string s) { + return s + " STRING"; + } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/FirstCallAfterUpdate_v1.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/FirstCallAfterUpdate_v1.cs new file mode 100644 index 00000000000000..66d1d336e6baa3 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/FirstCallAfterUpdate_v1.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class FirstCallAfterUpdate { + public FirstCallAfterUpdate() {} + public string Method1(string s) { + return "NEW " + s; + } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/FirstCallAfterUpdate_v2.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/FirstCallAfterUpdate_v2.cs new file mode 100644 index 00000000000000..9ea687752b8026 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/FirstCallAfterUpdate_v2.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class FirstCallAfterUpdate { + public FirstCallAfterUpdate() {} + public string Method1(string s) { + return s + "EST STRING"; + } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate.csproj b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate.csproj new file mode 100644 index 00000000000000..1cd197f92264de --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate.csproj @@ -0,0 +1,12 @@ + + + System.Runtime.Loader.Tests + $(NetCoreAppCurrent) + true + deltascript.json + true + + + + + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/deltascript.json b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/deltascript.json new file mode 100644 index 00000000000000..64d533be6b7560 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate/deltascript.json @@ -0,0 +1,7 @@ +{ + "changes": [ + {"document": "FirstCallAfterUpdate.cs", "update": "FirstCallAfterUpdate_v1.cs"}, + {"document": "FirstCallAfterUpdate.cs", "update": "FirstCallAfterUpdate_v2.cs"}, + ] +} + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs index 794ace67148c94..3e240e65caabf8 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs @@ -94,6 +94,26 @@ void LambdaCapturesThis() }); } + [ConditionalFact(typeof(ApplyUpdateUtil), nameof (ApplyUpdateUtil.IsSupported))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/54617", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] + void FirstCallAfterUpdate() + { + /* Tests that updating a method that has not been called before works correctly and that + * the JIT/interpreter doesn't have to rely on cached baseline data. */ + ApplyUpdateUtil.TestCase(static () => + { + var assm = typeof (ApplyUpdate.Test.FirstCallAfterUpdate).Assembly; + + var o = new ApplyUpdate.Test.FirstCallAfterUpdate (); + + ApplyUpdateUtil.ApplyUpdate(assm); + ApplyUpdateUtil.ApplyUpdate(assm); + + string r = o.Method1("NEW"); + + Assert.Equal("NEWEST STRING", r); + }); + } [ConditionalFact(typeof(ApplyUpdateUtil), nameof (ApplyUpdateUtil.IsSupported))] [ActiveIssue("https://github.com/dotnet/runtime/issues/52993", TestRuntimes.Mono)] 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 f893446f21d0e2..8b946a20828d2e 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 @@ -38,6 +38,7 @@ + @@ -54,6 +55,7 @@ + diff --git a/src/mono/mono/component/hot_reload.c b/src/mono/mono/component/hot_reload.c index 80374aeef906bc..d43cca86b251a6 100644 --- a/src/mono/mono/component/hot_reload.c +++ b/src/mono/mono/component/hot_reload.c @@ -793,6 +793,13 @@ hot_reload_effective_table_slow (const MonoTableInfo **t, int *idx) 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; + GList *list = info->delta_image; MonoImage *dmeta; int ridx; @@ -1272,6 +1279,29 @@ apply_enclog_pass2 (MonoImage *image_base, BaselineInfo *base_info, uint32_t gen MonoTableInfo *table_enclog = &image_dmeta->tables [MONO_TABLE_ENCLOG]; int rows = table_info_get_rows (table_enclog); + /* NOTE: Suppressed colums + * + * Certain column values in some tables in the deltas are not meant to be applied over the + * previous generation. See CMiniMdRW::m_SuppressedDeltaColumns in CoreCLR. For example the + * MONO_METHOD_PARAMLIST column in MONO_TABLE_METHOD is always 0 in an update - for modified + * rows the previous value must be carried over. For added rows, it is supposed to be + * initialized to the end of the param table and updated with the "Param create" func code + * in subsequent EnCLog records. + * + * For mono's immutable model (where we don't change the baseline image data), we will need + * to mutate the delta image tables to incorporate the suppressed column values from the + * previous generation. + * + * For Baseline capabilities, the only suppressed column is MONO_METHOD_PARAMLIST - which we + * can ignore because we don't do anything with param updates and the only column we care + * about is MONO_METHOD_RVA which gets special case treatment with set_update_method(). + * + * But when we implement additional capabilities (for example UpdateParameters), we will + * need to start mutating the delta image tables to pick up the suppressed column values. + * Fortunately whether we get the delta from the debugger or from the runtime API, we always + * have it in writable memory (and not mmap-ed pages), so we can rewrite the table values. + */ + gboolean assemblyref_updated = FALSE; for (int i = 0; i < rows ; ++i) { guint32 cols [MONO_ENCLOG_SIZE];