Skip to content

Commit

Permalink
[Mono] Initial metadata update support (#20629)
Browse files Browse the repository at this point in the history
First part of support for metadata updates.

Contributes to dotnet/runtime#44806

The feature is off by default.  To enable, build with `/p:MonoMetadataUpdate=true` (e.g. `./build.sh --os browser /p:MonoMetadataUpdate=true`).  There are samples in `src/mono/netcore/sample/mbr` (see the README - the samples aren't completely standalone and need some external tooling to build) for console (only tested on Mac and Linux) and wasm.

There's a demo at https://lambdageek.dev/dl0/

* Initial metadata-update prototype

Co-Authored-By: Bernhard Urban-Forster <lewurm@gmail.com>

* Add metadata-update.{c,h} to CMakeLists.txt

* Add icall to corelib

* Add console and browser metadata update samples

   Both samples depend on the roslynildiff tool which should be specified with a RoslynILDiffFullPath property in the .csproj files for the projects.

* Add README for mbr samples

* [build] Add initial runtime support for MonoMetadataUpdate property

   In the runtime defines cmake ENABLE_METADATA_UPDATE option and sets a preprocessor flag.

   In System.Private.CoreLib, defines FEATURE_METADATA_UPDATE and uses it to throw a NotSupportedException from LoadMetadataUpdate

* [runtime] ifdef out metadata updates if not enabled

   Also move execution engine initialization into the main update function and use a MonoError to signal failures (such as if interp inlining is not turned off) instead of asserting at startup.

* [wasm] set log mask to metadata-update

* [mbr] Add InjectUpdate fn to sample

* [metadata-update] don't merge heaps

* Don't make entrypoint public yet

* Add LoadMetadataUpdate to linker descriptor

* [wasm] add default Makefile variable value

* fix mono/mono CI

   don't try to run enc tests yet since they depend on roslynildiff

* remove mono/mono/tests/enc

   Will add as runtime tests in a future PR

* [metadata-update] Add per-thread exposed generation

   A thread has to voluntarily roll up to the latest published generation in order to see updates.

   - Roll up to the latest published generation when attaching a thread
   - The updater thread sees the allocated unpublished generation

* [mbr] Fixup console sample

   Use a single changing testfile

* [metadata-update] delete unused method

* [mbr] Use 2 threads in console sample

* [metadata-update] Respect exposed generation in MethdDef RVA lookups

* [interp] Expose latest metadata update before transforming methods

* [mbr] Update samples after rebase

   Use the WasmApp.targets

* [metadata-update] Don't fail after the first unsupported edit

   Log all the unsupported edits, then cancel the update

* [metadata_update] Keep track of logical table sizes for deltas

   Keep track of inserted/modified rows for each table in each delta. This will help to use a simpler algorithm to locate effective table rows by keeping track of the logical number of rows in the appended tables

* [metadata-update] Use a GList for MonoImage:delta_image

   We're going to need to walk backwards from the latest published delta

* [metadata-update] add effective table lookup debug output

* Address review feedback

* [interp] Save top interp frame at MINT_SAFEPOINT to ThreadContext

   Give metadata updates a peek at the interp frames since the LMF so that it can copy the InterpMethods that are currently executing

   This only works with hybrid and full coop suspend.  Preemptive suspend will need another mechanism.

* [mbr] Extend console sample

   Add a busy thread to demonstrate that interp frames since the last managed frame are visible to the metadata update mechanism and the active method bodies are copied before being invalidated.

* [interp] Check mono_polling_required at safepoint

Co-authored-by: Bernhard Urban-Forster <lewurm@gmail.com>
Co-authored-by: lambdageek <lambdageek@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 11, 2021
1 parent efc3b8a commit 58f650c
Show file tree
Hide file tree
Showing 24 changed files with 1,589 additions and 25 deletions.
13 changes: 13 additions & 0 deletions mono/cil/tables.def
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,16 @@ TABLEDEF(MONO_TABLE_GENERICPARAM, "GenericParam") /* 0x2a */
TABLEDEF(MONO_TABLE_METHODSPEC, "MethodSpec")
TABLEDEF(MONO_TABLE_GENERICPARAMCONSTRAINT, "GenericParamConstraint")

TABLEDEF(MONO_TABLE_UNUSED8, "Unused8")
TABLEDEF(MONO_TABLE_UNUSED9, "Unused9")
TABLEDEF(MONO_TABLE_UNUSED10, "Unused10")

/* Portable PDB tables */
TABLEDEF(MONO_TABLE_DOCUMENT, "Document")
TABLEDEF(MONO_TABLE_METHODBODY, "Methodbody")
TABLEDEF(MONO_TABLE_LOCALSCOPE, "LocalScope")
TABLEDEF(MONO_TABLE_LOCALVARIABLE, "LocalVariable")
TABLEDEF(MONO_TABLE_LOCALCONSTANT, "LocalConstant")
TABLEDEF(MONO_TABLE_IMPORTSCOPE, "ImportScope")
TABLEDEF(MONO_TABLE_STATEMACHINEMETHOD, "StateMachineMethod")
TABLEDEF(MONO_TABLE_CUSTOMDEBUGINFORMATION, "CustomDebugInformation")
2 changes: 2 additions & 0 deletions mono/metadata/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ common_sources = \
metadata.c \
metadata-verify.c \
metadata-internals.h \
metadata-update.h \
metadata-update.c \
method-builder.h \
method-builder-internals.h \
method-builder.c \
Expand Down
2 changes: 1 addition & 1 deletion mono/metadata/class.c
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ mono_class_from_typeref_checked (MonoImage *image, guint32 type_token, MonoError
break;
}

if (idx > image->tables [MONO_TABLE_ASSEMBLYREF].rows) {
if (mono_metadata_table_bounds_check (image, MONO_TABLE_ASSEMBLYREF, idx)) {
mono_error_set_bad_image (error, image, "Image with invalid assemblyref token %08x.", idx);
return NULL;
}
Expand Down
7 changes: 6 additions & 1 deletion mono/metadata/domain.c
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,12 @@ mono_cleanup (void)
void
mono_close_exe_image (void)
{
if (exe_image)
gboolean do_close = exe_image != NULL;
#ifdef ENABLE_METADATA_UPDATE
/* FIXME: shutdown hack. We mess something up and try to double-close/free it. */
do_close = do_close && !exe_image->delta_image;
#endif
if (do_close)
mono_image_close (exe_image);
}

Expand Down
4 changes: 4 additions & 0 deletions mono/metadata/icall-decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,4 +308,8 @@ ICALL_EXPORT void ves_icall_System_Threading_LowLevelLifoSemaphore_ReleaseIn
ICALL_EXPORT void ves_icall_System_Runtime_Intrinsics_X86_X86Base___cpuidex (int abcd[4], int function_id, int subfunction_id);
#endif

#if defined(ENABLE_NETCORE) && defined(ENABLE_METADATA_UPDATE)
ICALL_EXPORT void ves_icall_Mono_Runtime_LoadMetadataUpdate (MonoAssembly *assm, gconstpointer dmeta_bytes, int32_t dmeta_len, gconstpointer dil_bytes, int32_t dil_len);
#endif

#endif // __MONO_METADATA_ICALL_DECL_H__
5 changes: 5 additions & 0 deletions mono/metadata/icall-def-netcore.h
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,11 @@ HANDLES_REUSE_WRAPPER(MPROP_3, "get_metadata_token", ves_icall_reflection_get_to
HANDLES(MPROP_4, "get_property_info", ves_icall_RuntimePropertyInfo_get_property_info, void, 3, (MonoReflectionProperty, MonoPropertyInfo_ref, PInfo))
HANDLES(MPROP_5, "internal_from_handle_type", ves_icall_System_Reflection_RuntimePropertyInfo_internal_from_handle_type, MonoReflectionProperty, 2, (MonoProperty_ptr, MonoType_ptr))

#ifdef ENABLE_METADATA_UPDATE
ICALL_TYPE(RUNF, "System.Runtime.CompilerServices.RuntimeFeature", RUNF_1)
NOHANDLES(ICALL(RUNF_1, "LoadMetadataUpdate_internal", ves_icall_Mono_Runtime_LoadMetadataUpdate))
#endif

ICALL_TYPE(RUNH, "System.Runtime.CompilerServices.RuntimeHelpers", RUNH_1)
HANDLES(RUNH_1, "GetObjectValue", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_GetObjectValue, MonoObject, 1, (MonoObject))
HANDLES(RUNH_2, "GetUninitializedObjectInternal", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_GetUninitializedObjectInternal, MonoObject, 1, (MonoType_ptr))
Expand Down
18 changes: 18 additions & 0 deletions mono/metadata/icall.c
Original file line number Diff line number Diff line change
Expand Up @@ -6777,6 +6777,24 @@ ves_icall_Mono_Runtime_DumpStateTotal (guint64 *portable_hash, guint64 *unportab
return result;
}

#if defined (ENABLE_NETCORE) && defined (ENABLE_METADATA_UPDATE)
void
ves_icall_Mono_Runtime_LoadMetadataUpdate (MonoAssembly *assm,
gconstpointer dmeta_bytes, int32_t dmeta_len,
gconstpointer dil_bytes, int32_t dil_len)
{
ERROR_DECL (error);
g_assert (assm);
g_assert (dmeta_len >= 0);
MonoImage *image_base = assm->image;
g_assert (image_base);

MonoDomain *domain = mono_domain_get ();
mono_image_load_enc_delta (domain, image_base, dmeta_bytes, dmeta_len, dil_bytes, dil_len, error);
mono_error_set_pending_exception (error);
}
#endif

MonoBoolean
ves_icall_System_Reflection_AssemblyName_ParseAssemblyName (const char *name, MonoAssemblyName *aname, MonoBoolean *is_version_defined_arg, MonoBoolean *is_token_defined_arg)
{
Expand Down
80 changes: 77 additions & 3 deletions mono/metadata/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "tabledefs.h"
#include "tokentype.h"
#include "metadata-internals.h"
#include "metadata-update.h"
#include "profiler-private.h"
#include "loader.h"
#include "marshal.h"
Expand All @@ -45,6 +46,7 @@
#include <mono/metadata/verify.h>
#include <mono/metadata/image-internals.h>
#include <mono/metadata/loaded-images-internals.h>
#include <mono/metadata/metadata-update.h>
#include <mono/metadata/w32process-internals.h>
#include <mono/metadata/debug-internals.h>
#include <mono/metadata/mono-private-unstable.h>
Expand Down Expand Up @@ -598,7 +600,7 @@ load_metadata_ptrs (MonoImage *image, MonoCLIImageInfo *iinfo)
}

/*
* Load representation of logical metadata tables, from the "#~" stream
* Load representation of logical metadata tables, from the "#~" or "#-" stream
*/
static gboolean
load_tables (MonoImage *image)
Expand All @@ -613,6 +615,15 @@ load_tables (MonoImage *image)
image->idx_string_wide = ((heap_sizes & 0x01) == 1);
image->idx_guid_wide = ((heap_sizes & 0x02) == 2);
image->idx_blob_wide = ((heap_sizes & 0x04) == 4);

#ifdef ENABLE_METADATA_UPDATE
if (G_UNLIKELY (image->minimal_delta)) {
/* sanity check */
g_assert (image->idx_string_wide);
g_assert (image->idx_guid_wide);
g_assert (image->idx_blob_wide);
}
#endif

valid_mask = read64 (heap_tables + 8);
rows = (const guint32 *) (heap_tables + 24);
Expand Down Expand Up @@ -1462,6 +1473,26 @@ mono_is_problematic_image (MonoImage *image)
return FALSE;
}

#ifdef ENABLE_METADATA_UPDATE
static void
dump_encmap (MonoImage *image)
{
MonoTableInfo *encmap = &image->tables [MONO_TABLE_ENCMAP];
if (!encmap || !encmap->rows)
return;

if (mono_trace_is_traced (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE)) {
mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "ENCMAP for %s", image->filename);
for (int i = 0; i < encmap->rows; ++i) {
guint32 cols [MONO_ENCMAP_SIZE];
mono_metadata_decode_row (encmap, i, cols, MONO_ENCMAP_SIZE);
int token = cols [MONO_ENCMAP_TOKEN];
mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "\t0x%08x: 0x%08x table: %s", i+1, token, mono_meta_table_name (mono_metadata_token_table (token)));
}
}
}
#endif

static MonoImage *
do_mono_image_load (MonoImage *image, MonoImageOpenStatus *status,
gboolean care_about_cli, gboolean care_about_pecoff)
Expand Down Expand Up @@ -1526,6 +1557,10 @@ do_mono_image_load (MonoImage *image, MonoImageOpenStatus *status,
if (image->loader == &pe_loader && !image->metadata_only && !mono_verifier_verify_table_data (image, error))
goto invalid_image;

#ifdef ENABLE_METADATA_UPDATE
dump_encmap (image);
#endif

mono_image_load_names (image);

mono_image_load_time_date_stamp (image);
Expand Down Expand Up @@ -2513,6 +2548,20 @@ mono_image_close_except_pools_all (MonoImage**images, int image_count)
}
}

#ifdef ENABLE_METADATA_UPDATE
static void
mono_image_close_except_pools_all_list (GList *images)
{
for (GList *ptr = images; ptr; ptr = ptr->next) {
MonoImage *image = (MonoImage *)ptr->data;
if (image) {
if (!mono_image_close_except_pools (image))
ptr->data = NULL;
}
}
}
#endif

/*
* Returns whether mono_image_close_finish() must be called as well.
* We must unload images in two steps because clearing the domain in
Expand Down Expand Up @@ -2552,6 +2601,10 @@ mono_image_close_except_pools (MonoImage *image)

mono_metadata_clean_for_image (image);

#ifdef ENABLE_METADATA_UPDATE
mono_metadata_update_cleanup_on_close (image);
#endif

/*
* The caches inside a MonoImage might refer to metadata which is stored in referenced
* assemblies, so we can't release these references in mono_assembly_close () since the
Expand Down Expand Up @@ -2679,6 +2732,11 @@ mono_image_close_except_pools (MonoImage *image)
mono_image_close_except_pools_all (image->modules, image->module_count);
g_free (image->modules_loaded);

#ifdef ENABLE_METADATA_UPDATE
if (image->delta_image)
mono_image_close_except_pools_all_list (image->delta_image);
#endif

mono_os_mutex_destroy (&image->szarray_cache_lock);
mono_os_mutex_destroy (&image->lock);

Expand All @@ -2705,6 +2763,20 @@ mono_image_close_all (MonoImage**images, int image_count)
g_free (images);
}

#ifdef ENABLE_METADATA_UPDATE
static void
mono_image_close_all_list (GList *images)
{
for (GList *ptr = images; ptr; ptr = ptr->next) {
MonoImage *image = (MonoImage *)ptr->data;
if (image)
mono_image_close_finish (image);
}

g_list_free (images);
}
#endif

void
mono_image_close_finish (MonoImage *image)
{
Expand All @@ -2723,6 +2795,10 @@ mono_image_close_finish (MonoImage *image)
mono_image_close_all (image->files, image->file_count);
mono_image_close_all (image->modules, image->module_count);

#ifdef ENABLE_METADATA_UPDATE
mono_image_close_all_list (image->delta_image);
#endif

#ifndef DISABLE_PERFCOUNTERS
/* FIXME: use an explicit subtraction method as soon as it's available */
mono_atomic_fetch_add_i32 (&mono_perfcounters->loader_bytes, -1 * mono_mempool_get_allocated (image->mempool));
Expand Down Expand Up @@ -3425,5 +3501,3 @@ mono_image_append_class_to_reflection_info_set (MonoClass *klass)
mono_image_unlock (image);
}



50 changes: 43 additions & 7 deletions mono/metadata/loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <mono/metadata/tokentype.h>
#include <mono/metadata/tabledefs.h>
#include <mono/metadata/metadata-internals.h>
#include <mono/metadata/metadata-update.h>
#include <mono/metadata/loader.h>
#include <mono/metadata/loader-internals.h>
#include <mono/metadata/class-init.h>
Expand Down Expand Up @@ -864,7 +865,7 @@ method_from_memberref (MonoImage *image, guint32 idx, MonoGenericContext *typesp

error_init (error);

mono_metadata_decode_row (&tables [MONO_TABLE_MEMBERREF], idx-1, cols, 3);
mono_metadata_decode_row (&tables [MONO_TABLE_MEMBERREF], idx-1, cols, MONO_MEMBERREF_SIZE);
nindex = cols [MONO_MEMBERREF_CLASS] >> MONO_MEMBERREF_PARENT_BITS;
class_index = cols [MONO_MEMBERREF_CLASS] & MONO_MEMBERREF_PARENT_MASK;
/*g_print ("methodref: 0x%x 0x%x %s\n", class, nindex,
Expand Down Expand Up @@ -1073,7 +1074,7 @@ mono_get_method_from_token (MonoImage *image, guint32 token, MonoClass *klass,

if (used_context) *used_context = FALSE;

if (idx > image->tables [MONO_TABLE_METHOD].rows) {
if (mono_metadata_table_bounds_check (image, MONO_TABLE_METHOD, idx)) {
mono_error_set_bad_image (error, image, "Bad method token 0x%08x (out of bounds).", token);
return NULL;
}
Expand Down Expand Up @@ -2017,6 +2018,26 @@ mono_method_has_no_body (MonoMethod *method)
(method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL));
}

#ifdef ENABLE_METADATA_UPDATE
static gpointer
get_method_update_rva (MonoImage *image_base, uint32_t idx)
{
gpointer loc = NULL;
uint32_t cur = mono_metadata_update_get_thread_generation ();
GList *ptr = image_base->delta_image;
/* Go through all the updates that the current thread can see and see
* if they updated the method. Keep the latest visible update */
for (; ptr != NULL; ptr = ptr->next) {
MonoImage *image_delta = (MonoImage*) ptr->data;
if (image_delta->generation > cur)
break;
if (image_delta->method_table_update)
loc = g_hash_table_lookup (image_delta->method_table_update, GUINT_TO_POINTER (idx));
}
return loc;
}
#endif

// FIXME Replace all internal callers of mono_method_get_header_checked with
// mono_method_get_header_internal; the difference is in error initialization.
MonoMethodHeader*
Expand All @@ -2025,7 +2046,7 @@ mono_method_get_header_internal (MonoMethod *method, MonoError *error)
int idx;
guint32 rva;
MonoImage* img;
gpointer loc;
gpointer loc = NULL;
MonoGenericContainer *container;

error_init (error);
Expand Down Expand Up @@ -2070,12 +2091,27 @@ mono_method_get_header_internal (MonoMethod *method, MonoError *error)
*/
g_assert (mono_metadata_token_table (method->token) == MONO_TABLE_METHOD);
idx = mono_metadata_token_index (method->token);
rva = mono_metadata_decode_row_col (&img->tables [MONO_TABLE_METHOD], idx - 1, MONO_METHOD_RVA);

if (!mono_verifier_verify_method_header (img, rva, error))
return NULL;
#ifdef ENABLE_METADATA_UPDATE
/* EnC case */
if (G_UNLIKELY (img->method_table_update)) {
/* pre-computed rva pointer into delta IL image */
uint32_t gen = GPOINTER_TO_UINT (g_hash_table_lookup (img->method_table_update, GUINT_TO_POINTER (idx)));
if (G_UNLIKELY (gen > 0)) {
loc = get_method_update_rva (img, idx);
}
}
#endif

if (!loc) {
rva = mono_metadata_decode_row_col (&img->tables [MONO_TABLE_METHOD], idx - 1, MONO_METHOD_RVA);

if (!mono_verifier_verify_method_header (img, rva, error))
return NULL;

loc = mono_image_rva_map (img, rva);
}

loc = mono_image_rva_map (img, rva);
if (!loc) {
mono_error_set_bad_image (error, img, "Method has zero rva");
return NULL;
Expand Down
Loading

0 comments on commit 58f650c

Please sign in to comment.