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];