dotnet/runtime

Mono Method Body Replacement

lambdageek opened this issue · 7 comments

Contributes to dotnet/xamarin#13 and dotnet/aspnetcore#5456

Overview

The goal of Mono method body replacement is to improve the inner dev loop experience for customers using Xamarin to create mobile apps and those using Blazor to create WebAssembly projects.

Method body replacement is a subset of a more general hot reload experience with a restricted subset of allowed edits. Non-supported ("rude") edits will generally be rejected by the tooling outside the runtime, however the runtime
itself should be transactional in that a change is either committed by the runtime in total or else it is rejected and the runtime state is unchanged.

The changes are delivered to the runtime using the EnC "dmeta" and "DIL" (and "DPDB") delta files. If the changes are accepted, then future invocations of methods affected by the delta will use the new method bodies. Currently executing versions of the methods will use the previous versions.

Multiple versions of changes can be applied in succession. Once a change is applied, it cannot be unapplied (but of course a new change can be used to undo the previous change). For collectible ALCs unloading the assembly will unload all its changes.

Method body replacement will only be supported with the Mono interpreter with inlining turned off. The DOTNET_MODIFIABLE_ASSEMBLIES environment variable must be set to the (case-insensitive) value debug. The assemblies that are eligible for modifications must be compiled with the /debug option.

Method body replacement will not interfere with debugging.

Metadata deltas will be injected either using the debugger or using a managed API. If the debugger is not involved, the runtime will pause the executing user threads at critical points during the update.

End to end scenarios

  • CLI (dotnet-watch)
    • Blazor WebAssembly
    • MAUI postponed to .net7
  • Visual Studio "Apply Changes" during debugging

Tasks

Priority 0

  • DONE PR #45612 Put the metadata update work-in-progress under a compile-time feature flag and merge to dotnet/runtime master.

  • Fix potential race when one thread is doing a metadata update and another is transforming some method. May need some kind of rwlock. (Fixed as part of #45612 - we now use an explicit call to expose the update to a thread (at entry to the interpreter transformer)

  • Support preemptive suspend, or else PR #46873 use hybrid suspend by default, and communicate to downstream projects that MBR requires hybrid/coop suspend (mostly affects XM and XI - single-threaded wasm is ok)

  • Write up a list of edits that we expect to support and coordinate with Roslyn team to implement a delta generator that rejects unsupported edits. Summary ecma335 gist

  • Measure perf impact of metadata update infrastructure on the interpreter. (That is, if there are no updates coming, what is the overhead?) #46842 (comment)

  • set compile time feature flag to on by default; put functionality under a runtime feature flag

    • Enable on Android, and Browser #47785
    • Enable on iOS, tvOS and MacCatalyst #50458 - samples #50740
    • Enable on Desktop - #54124
    • Investigate workload dev/production packs - turn off MBR in production We're using the mono runtime components infrastructure for this
  • Debugger

    • Apply metadata updates using debugger-libs (#48458).
    • Notify debugger client about updated sequence points, breakpoints, etc (depends on runtime dpdb consumption) #55220
      • Add support for consuming "dpdb" debugger file deltas
      • When applying updates through the debugger, ensure debugger engine and debugger are in sync after the delta. #55220
      • If there is a breakpoint in an old version of a method we need to send back info identifying the method version, not just a token so that the debugger frontend can display a view of the old code. This may require debugger-libs and soft debugger protocol work We invalidate old breakpoints and set them in the updated method body.
    • Apply hot reload changes through WebAssembly debugger #55220
  • Propose and implement required runtime APIs (Coordinate with #45629 so that ideally Mono and CoreCLR implement the same API.)

    • (P0) Issue #45689 Public (or at least well-known) managed API for applying updates. Mono PR #48380
    • (P0) Issue #47274 Startup runtime with hot reload enabled if DOTNET_MODIFIABLE_ASSEMBLIES=debug is set. PR #49507
  • Properly detect cases we don't support, backout metadata updates and raise a single well-defined managed exception that leaves the runtime in a functional state. No need to roll back, update failures are fatal and don't need to be rolled back.

  • set up testing infrastructure and run CI

    • publish external testing artifact that our CI will depend on ⟶ https://github.com/dotnet/hotreload-utils
    • Public hotreload-utils to DARC and consume in dotnet/runtime
    • msbuild task for working with pre-canned deltas
    • Add testing infrastructure to System.Runtime.Loader.Tests testsuite #51144
    • Enable testing on WebAssembly
    • Enable testing on Android, iOS net7
    • Enable testing on Desktop mono
    • convert samples (src/mono/netcore/samples/mbr) into functional tests
  • Managed API to return hot reload supported edits #50111

Priority 1

  • Investigate MonoImage refcount mistake - fix the shutdown hack (exe_image) (The shutdown code is dead on netcore).

  • Support more edits

    • modify bodies of property getters and setters #51011

      If GetValueToDisplay is defined as a property instead of a method, then changes to its code can no longer be applied (error: "EnC: we do not support patching of existing table cols.").

    • #56574
  • (P1) #49361 Managed events that fire when a delta is applied

  • Use MetadataUpdateHandlerAttribute to clear Mono's reflection cache #50978 net7

  • #55228 PR: #55698

  • Hot Reload Manager support may need #47462 - add support for host startup hooks to mono MAUI and Blazor WebAssembly will inject the hot reload manager using their own mechanisms.

  • Feature flag API to enable IL trimming of hot reload support code #51159

  • Block applying changes to assemblies that have AOT images loaded. There's no supported hybrid interp/AOT scenarios.

Future (.net7 +)

Moved to #57365

Bugs

  • #49227 -> PR #49328
    • This was previously reported as:

      Most changes to a protected override void BuildRenderTree (Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder); method on a component class ends up producing a kind of change that the runtime can't apply and crashes with errors like "BadImageFormatException: Image with invalid assemblyref token 0000000f".

  • PR #49795
    • This was previously reported as:

      Changing logic in basic ways usually does not work fine. For example, changing return "my string"; to var other = "test"; return "my string" + other; leads to a crash with error "TypeLoadException: Could not resolve type with token 0100004c from typeref".

    • and as Blazor Wasm rendering incorrect components (ie multiple <Counter/> components would render as another previously added component)
  • #50190 - editing type 'B' (referenced from A) then editing 'A' causes changes to 'B' to be reverted - PR #50248 (also backported to 6.0 preview3 branch - #50254)
  • #52344 - A GC after applying an update can collect the updated method bodies (!!). This manifests in a variety of ways but is usually a BadImageFormatException thrown from the caller of an updated method.
  • #55097

Tagging subscribers to this area: @CoffeeFlux
See info in area-owners.md if you want to be subscribed.

Issue Details
Description:

Method Body Replacement

Contributes to dotnet/xamarin#13 and dotnet/aspnetcore#5456

Overview

The goal of Mono method body replacement is to improve the inner dev loop experience for customers using Xamarin to create mobile apps and those using Blazor to create WebAssembly projects.

Method body replacement is a subset of a more general hot reload experience with a restricted subset of allowed edits. Non-supported ("rude") edits will generally be rejected by the tooling outside the runtime, however the runtime
itself should be transactional in that a change is either committed by the runtime in total or else it is rejected and the runtime state is unchanged.

The changes are delivered to the runtime using the EnC "dmeta" and "DIL" (and "DPDB") delta files. If the changes are accepted, then future invocations of methods affected by the delta will use the new method bodies. Currently executing versions of the methods will use the previous versions.

Method body replacement will only be supported with the Mono interpreter with inlining turned off.

Method body replacement will not interfere with debugging.

Metadata deltas will be injected either using the debugger or using another API. If the debugger is not involved, the runtime will pause the executing user threads at critical points during the update.

Tasks

Priority 0

  • Put the metadata update work-in-progress under a compile-time feature flag and merge to dotnet/runtime master
  • Investigate what happens with debugger attached
    • Add support for consuming "dpdb" debugger file deltas
    • What should happen if there is a breakpoint in an old version of a method - migrate breakpoint to new version or keep both? How should a breakpoint hit in a previous version of a method be reported to the debugger frontend?
  • Properly detect cases we don't support, backout metadata updates and raise a managed exception
  • Investiage MonoImage refcount mistake - fix the shutdown hack (exe_image)
  • set compile time feature flag to on by default (in debug builds?), put under runtime feature flag
  • set up testing infrastructure and run CI
    • publish external testing artifact that our CI will depend on
    • msbuild task for working with pre-canned deltas
  • Publish wasm, android and ios Debug runtime packs with MBR support

Priority 1

  • Support mixed AOT/interp hybrid mode
  • Support adding nested classes, lambdas and async methods
  • Propose and implement an API event that can be used to notify frameworks that a change was applied.
  • Implement additional

Future

  • Type system additions (static fields, static methods, virtual method overrides, etc)

Out of scope

  • Type system modifications
  • Active statements / On-stack replacement
  • Object migration
  • JIT support
Author: lambdageek
Assignees: -
Labels:

Team Epic, area-VM-meta-mono, hard problem, tracking

Milestone: -

why not support for jit?

hope you can support it in jit mode

@srxqds The reason we're initially focusing on the interpreter is both for practical implementation reasons and due to the use-case that we're targeting. The use-case is Blazor WebAssembly and Xamarin mobile developers who are iterating on their code. This functionality is intended to be an improvement to the developer experience, not something that we expect developers to ship in production to end-users. For this use-case, it made sense to focus on the interpreter - because that's all that is available for WebAssembly, and it can also be supported on mobile.

The practical engineering reason is because this is going to involve a lot of changes at a foundational level in Mono. There are basically three places where we will need to make changes: in the low-level code that deals with .NET IL metadata; in the type system representation (MonoClass etc); and in the compilation stage and the execution engine. The advantage of doing interpreter first is that it has a relatively isolated compilation (inter/transform.c) and there is a single place where the code to be executed is looked up. So we can make a small change that we are pretty sure won't lead to a ton of bugs.

So we can focus on making changes to the metadata and the type system without also having to deal with the compilation and execution engine at the same time.

Once the metadata and typesystem changes are stable, it might make sense to look at the JIT, but right now it would just be a distraction.

@srxqds The reason we're initially focusing on the interpreter is both for practical implementation reasons and due to the use-case that we're targeting. The use-case is Blazor WebAssembly and Xamarin mobile developers who are iterating on their code. This functionality is intended to be an improvement to the developer experience, not something that we expect developers to ship in production to end-users. For this use-case, it made sense to focus on the interpreter - because that's all that is available for WebAssembly, and it can also be supported on mobile.

The practical engineering reason is because this is going to involve a lot of changes at a foundational level in Mono. There are basically three places where we will need to make changes: in the low-level code that deals with .NET IL metadata; in the type system representation (MonoClass etc); and in the compilation stage and the execution engine. The advantage of doing interpreter first is that it has a relatively isolated compilation (inter/transform.c) and there is a single place where the code to be executed is looked up. So we can make a small change that we are pretty sure won't lead to a ton of bugs.

So we can focus on making changes to the metadata and the type system without also having to deal with the compilation and execution engine at the same time.

Once the metadata and typesystem changes are stable, it might make sense to look at the JIT, but right now it would just be a distraction.

ok, thank you for reply.

hi, when can you begin to support add static fields and methods to existing classes work?

Pushed the future work to a separate tracking issue. This one is done. #57365