Skip to content

Commit

Permalink
Mono hot reload reorg and beginning (disabled) add method support (#6…
Browse files Browse the repository at this point in the history
…1853)

This PR does two things:
1. Reorganizes how we keep track of deltas, and how we do metadata lookups after updates
2. Adds an ifdefed-out initial draft of support for adding methods.  (It worked for a simple test but it's not ready for general use yet).

Details of the delta changes.

* Previously Mono used a fully immutable model of metadata changes.  Logically each delta brings in modifications to existing table rows and addition of new rows.  Previously mono never formed the fully mutated tables - instead, each lookup was essentially responsible for playing all the changes forward every time it needed to lookup a row.  This was fine when we primarily supported only row additions (because we could just skip past all the updates that hadn't added the row we wanted yet, and just look in a single delta).  But as we started to support more and more modifications (of custom attributes, of property getters & setters, of parameters, etc) we now had to look through the whole list of deltas to make sure we found the latest modification of every row.
* The new approach is for each `DeltaInfo` (representing a summary of a particular update to a single assembly) to contain a collection of `mutants` - the fully modified tables with all the newly added rows and all the new modifications.  So lookups are fast now - we just go to the latest generation that is visible to a particular thread and look at its mutants tables.  The downside is increased memory use.  So we allocate the mutants from memory pools owned by each `DeltaInfo`.  Right now we never dealloc the pools, but in the future we could.  We already iterate over all the threads during our stop-the-world phase - we can record the earliest generation still needed by every thread and delete the pools for all earlier copies.  In practice this means we only keep 3 sets of tables: the mmaped baseline tables, the newest mutants, and the mutants from the prior generation for any still-executing methods.
* Additionally instead of storing a list of delta images in the `BaselineInfo`, we now store a list of `DeltaInfo` structs which each `DeltaInfo` pointing to a delta image.  This means that we can avoid repeated hash table lookups to map from a delta image to its `DeltaInfo` every time the runtime queries us for a table row or for a heap element.

---

* checkpoint: allow adding methods to existing classes.

Doesn't do anything yet.

And needs more sanity checking (roslyn won't give us virtual or abstract
methods, but we should check).

* fixme in loader

* [metadata] Add mono_metadata_table_num_rows

Returns the number of rows in a metadata table, taking into account metadata
update deltas

* add table to ptr table helper

* Param attr lookups for deltas can have param_index == 0

The params are added with a subsequent enclog "add param" function.

* WIP: start adding support for parameter additions.

It "works" in that calling methods appears to work (ie direct token references
seem to do the right thing).

Primarily this is because the param table additions are not that interesting.
All the good stuff is in the method signature (which is just in the blob heap).

Presumably anything that actually needs parameter attributes, or anything that
uses reflection to look at the parameters, will break.

* WIP: add MethodDef -> TypeDef lookup

Allows calling non-public methods, which are now assigned the correct parent

* Add hot reload test for lambda capturing this

Lambdas that only capture `this` (and not local variables or arguments) compile
to a private instance method in the enclosing class.  So it is enough to
support EnC deltas that add methods.

* clarify comments about MONO_METHOD_PARAMLIST

* [hot_reload] Store debug info of updated methods

* [hot_reload] Allocate modifiable tables in DeltaInfo

This is the foundation for a new approach for metadata lookups.
Instead of using an immutable model (each lookup traverses every delta to find
the most up to date version of each table row), we are going to create a
complete updated table for each generation and only do the lookup in the latest
exposed generation directly.  (This is essentially the CoreCLR model).

This commit is just the first foundations: we allocate the tables and copy over
the previous generations' rows and zero out any rows that will be inserted.
Delta applications and lookups have not been updated yet.

As a slight optimization, tables that don't have modified or added rows are not
copied from the base image.  If a generation modifies or adds rows, from that
point forward, each subsequent generation will copy the table.

We could be a bit more thrifty with copying, but it will complicate lookups.
Also eventually we will try to deallocate the pools for generations that are
older than no thread needs anymore.

Metadata heaps are still looked up in each delta directly - heap combining does
not seem necessary yet.

* [mini] Allow MONO_VERBOSE_METHOD='*:*'

Implement method name wildcard matching for method descriptions

Globbing doesn't work because we don't have g_pattern_match_simple in eglib.
But a plain '*' wildcard does work.

* populate mutated table rows

leave suppressed columns unchanged

* [hot_reload] Switch lookups to the mutant tables

* cleanup: remove of effective_table calculation

The row index doesn't change in the new scheme, pass it by value

* cleanup: Remove relative_delta_index from component API

It's only used internally by the update logic

* cleanup: Pass DeltaInfo to relative_delta_index

don't compute it from the dmeta image

* cleanup: Store a list of DeltaInfo in the BaselineInfo

Instead of storing a list of delta images and then relying on delta_info_lookup
to find the DeltaInfo, just store them directly on the baseline info.

This changes the cleanup responsibilities a bit.  Now we destroy the DeltaInfo
when we are iterate through the delta infos when we close the baseline image,
instead of when we remove the delta_image_to_info hashtable entry.

* Turn off method addition support, for now

Just want to get the cleanups enabled for now

* Fix null ptr when checking for updated ppdb info
  • Loading branch information
lambdageek committed Nov 30, 2021
1 parent 4e9ea99 commit b5bb3c9
Show file tree
Hide file tree
Showing 13 changed files with 721 additions and 205 deletions.
@@ -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<string,string> fn = s => field;
return "123";
}

}
}
@@ -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<string,string> fn = s => NewMethod (s + field, 42);
return fn ("123");
}

private string NewMethod (string s, int i) {
return i.ToString() + s;
}

}
}
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>System.Runtime.Loader.Tests</RootNamespace>
<TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
<TestRuntime>true</TestRuntime>
<DeltaScript>deltascript.json</DeltaScript>
</PropertyGroup>
<ItemGroup>
<Compile Include="AddLambdaCapturingThis.cs" />
</ItemGroup>
</Project>
@@ -0,0 +1,6 @@
{
"changes": [
{"document": "AddLambdaCapturingThis.cs", "update": "AddLambdaCapturingThis_v1.cs"},
]
}

20 changes: 20 additions & 0 deletions src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs
Expand Up @@ -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
{
}
Expand Down
Expand Up @@ -43,6 +43,7 @@
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.LambdaBodyChange\System.Reflection.Metadata.ApplyUpdate.Test.LambdaBodyChange.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.LambdaCapturesThis\System.Reflection.Metadata.ApplyUpdate.Test.LambdaCapturesThis.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate\System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate.csproj" />
<ProjectReference Include="ApplyUpdate\System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis\System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis.csproj" />

</ItemGroup>
<ItemGroup Condition="'$(TargetOS)' == 'Browser'">
Expand All @@ -60,6 +61,7 @@
<TrimmerRootAssembly Condition="$([System.String]::Copy('%(ResolvedFileToPublish.FileName)%(ResolvedFileToPublish.Extension)').EndsWith('System.Reflection.Metadata.ApplyUpdate.Test.LambdaBodyChange.dll'))" Include="%(ResolvedFileToPublish.FullPath)" />
<TrimmerRootAssembly Condition="$([System.String]::Copy('%(ResolvedFileToPublish.FileName)%(ResolvedFileToPublish.Extension)').EndsWith('System.Reflection.Metadata.ApplyUpdate.Test.LambdaCapturesThis.dll'))" Include="%(ResolvedFileToPublish.FullPath)" />
<TrimmerRootAssembly Condition="$([System.String]::Copy('%(ResolvedFileToPublish.FileName)%(ResolvedFileToPublish.Extension)').EndsWith('System.Reflection.Metadata.ApplyUpdate.Test.FirstCallAfterUpdate.dll'))" Include="%(ResolvedFileToPublish.FullPath)" />
<TrimmerRootAssembly Condition="$([System.String]::Copy('%(ResolvedFileToPublish.FileName)%(ResolvedFileToPublish.Extension)').EndsWith('System.Reflection.Metadata.ApplyUpdate.Test.AddLambdaCapturingThis.dll'))" Include="%(ResolvedFileToPublish.FullPath)" />
</ItemGroup>
</Target>

Expand Down
45 changes: 33 additions & 12 deletions src/mono/mono/component/hot_reload-stub.c
Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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,
Expand 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
Expand Down Expand Up @@ -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 ();
}
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit b5bb3c9

Please sign in to comment.