Skip to content

Commit 629ad50

Browse files
[mono] Implement public hot reload API (#48380)
* [mono] Implement public hot reload API Also add ApplyUpdateSdb that takes byte[] arguments for use with mono/debugger-libs as its awkward to call a ROS<byte> method from the debugger. Contributes to #45689 * Update src/mono/System.Private.CoreLib/src/System/Reflection/Metadata/AssemblyExtensions.cs Co-authored-by: Marek Safar <[email protected]> * Don't allow changes to System.Private.CoreLib * formatting * more formatting * Add a TODO to remove ApplyUpdateSdb * update DeltaHelper * don't use ApplyUpdateSdb in DeltaHelper * throw InvalidOpertionException for corlib changes with hot reload disabled * Run the test on mono on all platoforms; CoreCLR windows only * Use resource string * Address review feedback Co-authored-by: Marek Safar <[email protected]>
1 parent 2e2eccc commit 629ad50

File tree

9 files changed

+162
-96
lines changed

9 files changed

+162
-96
lines changed

src/libraries/System.Private.CoreLib/src/Resources/Strings.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3679,4 +3679,7 @@
36793679
<data name="InvalidOperation_EditFailed" xml:space="preserve">
36803680
<value>The assembly update failed.</value>
36813681
</data>
3682-
</root>
3682+
<data name="NotSupported_MethodBodyReplacement" xml:space="preserve">
3683+
<value>Method body replacement not supported in this runtime.</value>
3684+
</data>
3685+
</root>

src/libraries/System.Runtime.Loader/tests/AssemblyExtensionsTest.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ class NonRuntimeAssembly : Assembly
1212
}
1313

1414
[Fact]
15-
[ActiveIssue("https://github.com/dotnet/runtime/issues/45689", platforms: TestPlatforms.AnyUnix, runtimes: TestRuntimes.Mono)]
16-
[PlatformSpecific(TestPlatforms.Windows)]
17-
[SkipOnMono("Not yet implemented on Mono")]
15+
[ActiveIssue("https://github.com/dotnet/runtime/issues/45689", platforms: ~TestPlatforms.Windows, runtimes: TestRuntimes.CoreCLR)]
1816
public static void ApplyUpdateInvalidParameters()
1917
{
2018
// Dummy delta arrays

src/mono/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.xml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -548,11 +548,6 @@
548548
<method signature="System.Void .ctor(System.Object)" />
549549
</type>
550550

551-
<!-- Used by metadata update -->
552-
<type fullname="System.Runtime.CompilerServices.RuntimeFeature">
553-
<method name="LoadMetadataUpdate" />
554-
</type>
555-
556551
<!-- domain.c: mono_defaults.marshal_class -->
557552
<type fullname="System.Runtime.InteropServices.Marshal" preserve="fields" >
558553
<!-- marshal.c (mono_marshal_get_struct_to_ptr) -->
@@ -637,5 +632,12 @@
637632
<!-- marshal-ilgen.c:emit_invoke_call -->
638633
<method signature="System.Void .ctor()" />
639634
</type>
635+
636+
<!-- Used by metadata update -->
637+
<!-- TODO: Remove this and add a debugger-libs command to invoke updates, see https://github.com/dotnet/runtime/issues/48458 -->
638+
<type fullname="System.Reflection.Metadata.AssemblyExtensions">
639+
<method name="ApplyUpdateSdb" />
640+
</type>
641+
640642
</assembly>
641643
</linker>
Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,68 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Runtime.CompilerServices;
5+
46
namespace System.Reflection.Metadata
57
{
68
public static class AssemblyExtensions
79
{
810
[CLSCompliant(false)]
911
public static unsafe bool TryGetRawMetadata(this Assembly assembly, out byte* blob, out int length) => throw new NotImplementedException();
1012

11-
public static void ApplyUpdate(Assembly assembly, ReadOnlySpan<byte> metadataDelta, ReadOnlySpan<byte> ilDelta, ReadOnlySpan<byte> pdbDelta) => throw new NotImplementedException();
13+
/// <summary>
14+
/// Updates the specified assembly using the provided metadata, IL and PDB deltas.
15+
/// </summary>
16+
/// <remarks>
17+
/// Currently executing methods will continue to use the existing IL. New executions of modified methods will
18+
/// use the new IL. Different runtimes may have different limitations on what kinds of changes are supported,
19+
/// and runtimes make no guarantees as to the state of the assembly and process if the delta includes
20+
/// unsupported changes.
21+
/// </remarks>
22+
/// <param name="assembly">The assembly to update.</param>
23+
/// <param name="metadataDelta">The metadata changes to be applied.</param>
24+
/// <param name="ilDelta">The IL changes to be applied.</param>
25+
/// <param name="pdbDelta">The PDB changes to be applied.</param>
26+
/// <exception cref="ArgumentNullException">The assembly argument is null.</exception>
27+
/// <exception cref="NotSupportedException">The update could not be applied.</exception>
28+
public static void ApplyUpdate(Assembly assembly, ReadOnlySpan<byte> metadataDelta, ReadOnlySpan<byte> ilDelta, ReadOnlySpan<byte> pdbDelta)
29+
{
30+
if (assembly is not RuntimeAssembly runtimeAssembly)
31+
{
32+
if (assembly is null) throw new ArgumentNullException(nameof(assembly));
33+
throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
34+
}
35+
36+
// System.Private.CoreLib is not editable
37+
if (runtimeAssembly == typeof(AssemblyExtensions).Assembly)
38+
throw new InvalidOperationException (SR.InvalidOperation_AssemblyNotEditable);
39+
40+
#if !FEATURE_METADATA_UPDATE
41+
throw new NotSupportedException (SR.NotSupported_MethodBodyReplacement);
42+
#else
43+
unsafe
44+
{
45+
IntPtr monoAssembly = runtimeAssembly.GetUnderlyingNativeHandle ();
46+
fixed (byte* metadataDeltaPtr = metadataDelta, ilDeltaPtr = ilDelta, pdbDeltaPtr = pdbDelta)
47+
{
48+
ApplyUpdate_internal(monoAssembly, metadataDeltaPtr, metadataDelta.Length, ilDeltaPtr, ilDelta.Length, pdbDeltaPtr, pdbDelta.Length);
49+
}
50+
}
51+
#endif
52+
}
53+
54+
internal static void ApplyUpdateSdb(Assembly assembly, byte[] metadataDelta, byte[] ilDelta, byte[]? pdbDelta)
55+
{
56+
ReadOnlySpan<byte> md = metadataDelta;
57+
ReadOnlySpan<byte> il = ilDelta;
58+
ReadOnlySpan<byte> dpdb = pdbDelta == null ? default : pdbDelta;
59+
ApplyUpdate (assembly, md, il, dpdb);
60+
}
61+
62+
#if FEATURE_METADATA_UPDATE
63+
[MethodImpl (MethodImplOptions.InternalCall)]
64+
private static unsafe extern void ApplyUpdate_internal (IntPtr base_assm, byte* dmeta_bytes, int dmeta_length, byte *dil_bytes, int dil_length, byte *dpdb_bytes, int dpdb_length);
65+
#endif
66+
1267
}
1368
}

src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.Mono.cs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,5 @@ public static bool IsDynamicCodeCompiled
1717
[Intrinsic] // the JIT/AOT compiler will change this flag to false for FullAOT scenarios, otherwise true
1818
get => IsDynamicCodeCompiled;
1919
}
20-
21-
#if !FEATURE_METADATA_UPDATE
22-
internal static void LoadMetadataUpdate (Assembly assm, byte[] dmeta_data, byte[] dil_data) {
23-
throw new NotSupportedException ("Method body replacement not supported in this runtime");
24-
}
25-
#else
26-
[MethodImplAttribute (MethodImplOptions.InternalCall)]
27-
private static unsafe extern void LoadMetadataUpdate_internal (IntPtr base_assm, byte* dmeta_bytes, int dmeta_length, byte *dil_bytes, int dil_length);
28-
29-
internal static void LoadMetadataUpdate (Assembly assm, byte[] dmeta_data, byte[] dil_data) {
30-
unsafe {
31-
fixed (byte* dmeta_bytes = dmeta_data)
32-
fixed (byte* dil_bytes = dil_data) {
33-
IntPtr mono_assembly = ((RuntimeAssembly)assm).GetUnderlyingNativeHandle ();
34-
LoadMetadataUpdate_internal (mono_assembly, dmeta_bytes, dmeta_data.Length, dil_bytes, dil_data.Length);
35-
}
36-
}
37-
}
38-
#endif
3920
}
4021
}

src/mono/mono/metadata/icall-decl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ ICALL_EXPORT void ves_icall_System_Runtime_Intrinsics_X86_X86Base___cpuidex (int
239239
#endif
240240

241241
#ifdef ENABLE_METADATA_UPDATE
242-
ICALL_EXPORT void ves_icall_Mono_Runtime_LoadMetadataUpdate (MonoAssembly *assm, gconstpointer dmeta_bytes, int32_t dmeta_len, gconstpointer dil_bytes, int32_t dil_len);
242+
ICALL_EXPORT void ves_icall_AssemblyExtensions_ApplyUpdate (MonoAssembly *assm, gconstpointer dmeta_bytes, int32_t dmeta_len, gconstpointer dil_bytes, int32_t dil_len, gconstpointer dpdb_bytes, int32_t dpdb_len);
243243
#endif
244244

245245
#endif // __MONO_METADATA_ICALL_DECL_H__

src/mono/mono/metadata/icall-def-netcore.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,11 @@ HANDLES(FILEDI_1, "get_marshal_info", ves_icall_System_Reflection_FieldInfo_get_
239239

240240
HANDLES(FILEDI_2, "internal_from_handle_type", ves_icall_System_Reflection_FieldInfo_internal_from_handle_type, MonoReflectionField, 2, (MonoClassField_ref, MonoType_ref))
241241

242+
#ifdef ENABLE_METADATA_UPDATE
243+
ICALL_TYPE(ASSMEXT, "System.Reflection.Metadata.AssemblyExtensions", ASSMEXT_1)
244+
NOHANDLES(ICALL(ASSMEXT_1, "ApplyUpdate_internal", ves_icall_AssemblyExtensions_ApplyUpdate))
245+
#endif
246+
242247
ICALL_TYPE(MBASE, "System.Reflection.MethodBase", MBASE_1)
243248
HANDLES(MBASE_1, "GetCurrentMethod", ves_icall_GetCurrentMethod, MonoReflectionMethod, 0, ())
244249

@@ -324,11 +329,6 @@ HANDLES_REUSE_WRAPPER(MPROP_3, "get_metadata_token", ves_icall_reflection_get_to
324329
HANDLES(MPROP_4, "get_property_info", ves_icall_RuntimePropertyInfo_get_property_info, void, 3, (MonoReflectionProperty, MonoPropertyInfo_ref, PInfo))
325330
HANDLES(MPROP_5, "internal_from_handle_type", ves_icall_System_Reflection_RuntimePropertyInfo_internal_from_handle_type, MonoReflectionProperty, 2, (MonoProperty_ptr, MonoType_ptr))
326331

327-
#ifdef ENABLE_METADATA_UPDATE
328-
ICALL_TYPE(RUNF, "System.Runtime.CompilerServices.RuntimeFeature", RUNF_1)
329-
NOHANDLES(ICALL(RUNF_1, "LoadMetadataUpdate_internal", ves_icall_Mono_Runtime_LoadMetadataUpdate))
330-
#endif
331-
332332
ICALL_TYPE(RUNH, "System.Runtime.CompilerServices.RuntimeHelpers", RUNH_1)
333333
HANDLES(RUNH_1, "GetObjectValue", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_GetObjectValue, MonoObject, 1, (MonoObject))
334334
HANDLES(RUNH_2, "GetUninitializedObjectInternal", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_GetUninitializedObjectInternal, MonoObject, 1, (MonoType_ptr))

src/mono/mono/metadata/icall.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5842,15 +5842,17 @@ ves_icall_Mono_Runtime_DumpStateTotal (guint64 *portable_hash, guint64 *unportab
58425842

58435843
#ifdef ENABLE_METADATA_UPDATE
58445844
void
5845-
ves_icall_Mono_Runtime_LoadMetadataUpdate (MonoAssembly *assm,
5845+
ves_icall_AssemblyExtensions_ApplyUpdate (MonoAssembly *assm,
58465846
gconstpointer dmeta_bytes, int32_t dmeta_len,
5847-
gconstpointer dil_bytes, int32_t dil_len)
5847+
gconstpointer dil_bytes, int32_t dil_len,
5848+
gconstpointer dpdb_bytes, int32_t dpdb_len)
58485849
{
58495850
ERROR_DECL (error);
58505851
g_assert (assm);
58515852
g_assert (dmeta_len >= 0);
58525853
MonoImage *image_base = assm->image;
58535854
g_assert (image_base);
5855+
// TODO: use dpdb_bytes
58545856

58555857
MonoDomain *domain = mono_domain_get ();
58565858
mono_image_load_enc_delta (domain, image_base, dmeta_bytes, dmeta_len, dil_bytes, dil_len, error);
Lines changed: 84 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,104 @@
11
using System;
22
using System.Reflection;
3+
using System.Reflection.Emit;
34
using System.Runtime.CompilerServices;
45
using System.Runtime.Loader;
56
using System.Collections.Generic;
67

78
namespace MonoDelta {
8-
public class DeltaHelper {
9-
const string name = "System.Runtime.CompilerServices.RuntimeFeature";
9+
public class DeltaHelper {
10+
private static Action<Assembly, byte[], byte[], byte[]> _updateMethod;
1011

11-
private static MethodBase _updateMethod;
12+
private static Action<Assembly, byte[], byte[], byte[]> UpdateMethod => _updateMethod ?? InitUpdateMethod();
1213

13-
private static MethodBase UpdateMethod => _updateMethod ?? InitUpdateMethod();
14+
private static Action<Assembly, byte[], byte[], byte[]> InitUpdateMethod ()
15+
{
16+
var monoType = typeof(System.Reflection.Metadata.AssemblyExtensions);
17+
const string methodName = "ApplyUpdate";
18+
var mi = monoType.GetMethod (methodName, BindingFlags.Public | BindingFlags.Static);
19+
if (mi == null)
20+
throw new Exception ($"Couldn't get {methodName} from {monoType.FullName}");
21+
_updateMethod = MakeUpdateMethod (mi); //Delegate.CreateDelegate (typeof(Action<Assembly, byte[], byte[], byte[]>), mi) as Action<Assembly, byte[], byte[], byte[]>;
22+
return _updateMethod;
23+
}
1424

15-
private static MethodBase InitUpdateMethod ()
16-
{
17-
var monoType = Type.GetType (name, throwOnError: true);
18-
if (monoType == null)
19-
throw new Exception ($"Couldn't get the type {name}");
20-
_updateMethod = monoType.GetMethod ("LoadMetadataUpdate", BindingFlags.NonPublic | BindingFlags.Static);
21-
if (_updateMethod == null)
22-
throw new Exception ($"Couldn't get LoadMetadataUpdate from {name}");
23-
return _updateMethod;
24-
}
25+
private static Action<Assembly, byte[], byte[], byte[]> MakeUpdateMethod (MethodInfo applyUpdate)
26+
{
27+
// Make
28+
// void ApplyUpdateArray (Assembly a, byte[] dmeta, byte[] dil, byte[] dpdb)
29+
// {
30+
// ApplyUpdate (a, (ReadOnlySpan<byte>)dmeta, (ReadOnlySpan<byte>)dil, (ReadOnlySpan<byte>)dpdb);
31+
// }
32+
var dm = new DynamicMethod ("CallApplyUpdate", typeof(void), new Type[] { typeof(Assembly), typeof(byte[]), typeof(byte[]), typeof(byte[])}, typeof (DeltaHelper).Module);
33+
var ilg = dm.GetILGenerator ();
34+
var conv = typeof(ReadOnlySpan<byte>).GetMethod("op_Implicit", new Type[] {typeof(byte[])});
2535

26-
private static void LoadMetadataUpdate (Assembly assm, byte[] dmeta_data, byte[] dil_data)
27-
{
28-
UpdateMethod.Invoke (null, new object [] { assm, dmeta_data, dil_data});
29-
}
36+
ilg.Emit (OpCodes.Ldarg_0);
37+
ilg.Emit (OpCodes.Ldarg_1);
38+
ilg.Emit (OpCodes.Call, conv);
39+
ilg.Emit (OpCodes.Ldarg_2);
40+
ilg.Emit (OpCodes.Call, conv);
41+
ilg.Emit (OpCodes.Ldarg_3);
42+
ilg.Emit (OpCodes.Call, conv);
43+
ilg.Emit (OpCodes.Call, applyUpdate);
44+
ilg.Emit (OpCodes.Ret);
3045

31-
DeltaHelper () { }
46+
return dm.CreateDelegate(typeof(Action<Assembly, byte[], byte[], byte[]>)) as Action<Assembly, byte[], byte[], byte[]>;
47+
}
3248

33-
public static DeltaHelper Make ()
34-
{
35-
return new DeltaHelper ();
36-
}
49+
private static void LoadMetadataUpdate (Assembly assm, byte[] dmeta_data, byte[] dil_data, byte[] dpdb_data)
50+
{
51+
UpdateMethod (assm, dmeta_data, dil_data, dpdb_data);
52+
}
3753

38-
public static void InjectUpdate (string assemblyName, string dmeta_base64, string dil_base64) {
39-
var an = new AssemblyName (assemblyName);
40-
Assembly assm = null;
41-
/* TODO: non-default ALCs */
42-
foreach (var candidate in AssemblyLoadContext.Default.Assemblies) {
43-
if (candidate.GetName().Name == an.Name) {
44-
assm = candidate;
45-
break;
46-
}
47-
}
48-
if (assm == null)
49-
throw new ArgumentException ("assemblyName");
50-
var dmeta_data = Convert.FromBase64String (dmeta_base64);
51-
var dil_data = Convert.FromBase64String (dil_base64);
52-
LoadMetadataUpdate (assm, dmeta_data, dil_data);
53-
}
54+
DeltaHelper () { }
5455

55-
private Dictionary<Assembly, int> assembly_count = new Dictionary<Assembly, int> ();
56+
public static DeltaHelper Make ()
57+
{
58+
return new DeltaHelper ();
59+
}
5660

57-
public void Update (Assembly assm) {
58-
int count;
59-
if (!assembly_count.TryGetValue (assm, out count))
60-
count = 1;
61-
else
62-
count++;
63-
assembly_count [assm] = count;
61+
public static void InjectUpdate (string assemblyName, string dmeta_base64, string dil_base64) {
62+
var an = new AssemblyName (assemblyName);
63+
Assembly assm = null;
64+
/* TODO: non-default ALCs */
65+
foreach (var candidate in AssemblyLoadContext.Default.Assemblies) {
66+
if (candidate.GetName().Name == an.Name) {
67+
assm = candidate;
68+
break;
69+
}
70+
}
71+
if (assm == null)
72+
throw new ArgumentException ("assemblyName");
73+
var dmeta_data = Convert.FromBase64String (dmeta_base64);
74+
var dil_data = Convert.FromBase64String (dil_base64);
75+
byte[] dpdb_data = null;
76+
LoadMetadataUpdate (assm, dmeta_data, dil_data, dpdb_data);
77+
}
6478

65-
/* FIXME WASM: Location is empty on wasm. Make up a name based on Name */
66-
string basename = assm.Location;
67-
if (basename == "")
68-
basename = assm.GetName().Name + ".dll";
69-
Console.WriteLine ($"Apply Delta Update for {basename}, revision {count}");
79+
private Dictionary<Assembly, int> assembly_count = new Dictionary<Assembly, int> ();
7080

71-
string dmeta_name = $"{basename}.{count}.dmeta";
72-
string dil_name = $"{basename}.{count}.dil";
73-
byte[] dmeta_data = System.IO.File.ReadAllBytes (dmeta_name);
74-
byte[] dil_data = System.IO.File.ReadAllBytes (dil_name);
81+
public void Update (Assembly assm) {
82+
int count;
83+
if (!assembly_count.TryGetValue (assm, out count))
84+
count = 1;
85+
else
86+
count++;
87+
assembly_count [assm] = count;
7588

76-
LoadMetadataUpdate (assm, dmeta_data, dil_data);
77-
}
78-
}
89+
/* FIXME WASM: Location is empty on wasm. Make up a name based on Name */
90+
string basename = assm.Location;
91+
if (basename == "")
92+
basename = assm.GetName().Name + ".dll";
93+
Console.WriteLine ($"Apply Delta Update for {basename}, revision {count}");
94+
95+
string dmeta_name = $"{basename}.{count}.dmeta";
96+
string dil_name = $"{basename}.{count}.dil";
97+
byte[] dmeta_data = System.IO.File.ReadAllBytes (dmeta_name);
98+
byte[] dil_data = System.IO.File.ReadAllBytes (dil_name);
99+
byte[] dpdb_data = null; // TODO also use the dpdb data
100+
101+
LoadMetadataUpdate (assm, dmeta_data, dil_data, dpdb_data);
102+
}
103+
}
79104
}

0 commit comments

Comments
 (0)