From b0a316f4dc5c22cee297f71860f76e2a8a240e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksey=20Kliger=20=28=CE=BBgeek=29?= <alklig@microsoft.com> Date: Mon, 11 Jan 2021 13:13:10 -0500 Subject: [PATCH] [Mono] Initial metadata update support (#45612) * 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> --- src/mono/cmake/config.h.in | 4 + src/mono/cmake/options.cmake | 1 + src/mono/mono.proj | 10 + src/mono/mono/cil/tables.def | 13 + src/mono/mono/metadata/CMakeLists.txt | 2 + src/mono/mono/metadata/Makefile.am | 2 + src/mono/mono/metadata/class.c | 2 +- src/mono/mono/metadata/domain.c | 7 +- src/mono/mono/metadata/icall-decl.h | 4 + src/mono/mono/metadata/icall-def-netcore.h | 5 + src/mono/mono/metadata/icall.c | 18 + src/mono/mono/metadata/image.c | 80 +- src/mono/mono/metadata/loader.c | 50 +- src/mono/mono/metadata/metadata-internals.h | 62 ++ src/mono/mono/metadata/metadata-update.c | 974 ++++++++++++++++++ src/mono/mono/metadata/metadata-update.h | 50 + src/mono/mono/metadata/metadata.c | 190 +++- src/mono/mono/metadata/metadata.h | 2 + src/mono/mono/metadata/object-internals.h | 4 + src/mono/mono/metadata/threads.c | 5 + src/mono/mono/mini/ee.h | 1 + src/mono/mono/mini/interp-stubs.c | 5 + src/mono/mono/mini/interp/interp-internals.h | 5 + src/mono/mono/mini/interp/interp.c | 103 +- src/mono/mono/mini/interp/transform.c | 5 + src/mono/mono/mini/mini-runtime.c | 23 + src/mono/mono/utils/mono-logger-internals.h | 2 +- src/mono/mono/utils/mono-logger.c | 2 +- .../System.Private.CoreLib.csproj | 5 + .../src/ILLink/ILLink.Descriptors.xml | 5 + .../CompilerServices/RuntimeFeature.Mono.cs | 20 + .../netcore/sample/mbr/DeltaHelper.targets | 96 ++ .../sample/mbr/DeltaHelper/DeltaHelper.cs | 79 ++ .../sample/mbr/DeltaHelper/DeltaHelper.csproj | 6 + src/mono/netcore/sample/mbr/README.md | 62 ++ src/mono/netcore/sample/mbr/browser/Makefile | 27 + .../netcore/sample/mbr/browser/Program.cs | 32 + .../netcore/sample/mbr/browser/Program_v1.cs | 32 + .../netcore/sample/mbr/browser/Program_v2.cs | 32 + .../sample/mbr/browser/WasmDelta.csproj | 65 ++ .../netcore/sample/mbr/browser/index.html | 39 + .../netcore/sample/mbr/browser/runtime.js | 15 + .../sample/mbr/console/ConsoleDelta.csproj | 39 + src/mono/netcore/sample/mbr/console/Makefile | 26 + .../netcore/sample/mbr/console/Program.cs | 117 +++ .../netcore/sample/mbr/console/TestClass.cs | 11 + .../sample/mbr/console/TestClass_v1.cs | 11 + src/mono/wasm/Makefile | 4 + src/mono/wasm/runtime/driver.c | 3 + src/mono/wasm/wasm.proj | 3 +- 50 files changed, 2334 insertions(+), 26 deletions(-) create mode 100644 src/mono/mono/metadata/metadata-update.c create mode 100644 src/mono/mono/metadata/metadata-update.h create mode 100644 src/mono/netcore/sample/mbr/DeltaHelper.targets create mode 100644 src/mono/netcore/sample/mbr/DeltaHelper/DeltaHelper.cs create mode 100644 src/mono/netcore/sample/mbr/DeltaHelper/DeltaHelper.csproj create mode 100644 src/mono/netcore/sample/mbr/README.md create mode 100644 src/mono/netcore/sample/mbr/browser/Makefile create mode 100644 src/mono/netcore/sample/mbr/browser/Program.cs create mode 100644 src/mono/netcore/sample/mbr/browser/Program_v1.cs create mode 100644 src/mono/netcore/sample/mbr/browser/Program_v2.cs create mode 100644 src/mono/netcore/sample/mbr/browser/WasmDelta.csproj create mode 100644 src/mono/netcore/sample/mbr/browser/index.html create mode 100644 src/mono/netcore/sample/mbr/browser/runtime.js create mode 100644 src/mono/netcore/sample/mbr/console/ConsoleDelta.csproj create mode 100644 src/mono/netcore/sample/mbr/console/Makefile create mode 100644 src/mono/netcore/sample/mbr/console/Program.cs create mode 100644 src/mono/netcore/sample/mbr/console/TestClass.cs create mode 100644 src/mono/netcore/sample/mbr/console/TestClass_v1.cs diff --git a/src/mono/cmake/config.h.in b/src/mono/cmake/config.h.in index bfbf8ddda4dd2..07df677f684bd 100644 --- a/src/mono/cmake/config.h.in +++ b/src/mono/cmake/config.h.in @@ -1771,8 +1771,12 @@ /* Enable runtime checks of mempool references between metadata images (must set env var MONO_CHECK_MODE=metadata) */ #cmakedefine ENABLE_CHECKED_BUILD_METADATA 1 +/* Enable runtime support for metadata updates */ +#cmakedefine ENABLE_METADATA_UPDATE 1 + #if defined(ENABLE_LLVM) && defined(HOST_WIN32) && defined(TARGET_WIN32) && (!defined(TARGET_AMD64) || !defined(_MSC_VER)) #error LLVM for host=Windows and target=Windows is only supported on x64 MSVC build. #endif #endif + diff --git a/src/mono/cmake/options.cmake b/src/mono/cmake/options.cmake index 5170d66b0bf46..35507870e876d 100644 --- a/src/mono/cmake/options.cmake +++ b/src/mono/cmake/options.cmake @@ -65,6 +65,7 @@ option (ENABLE_CHECKED_BUILD_PRIVATE_TYPES "Enable compile time checking that ge option (ENABLE_CHECKED_BUILD_GC "Enable runtime GC Safe / Unsafe mode assertion checks (must set env var MONO_CHECK_MODE=gc)") option (ENABLE_CHECKED_BUILD_THREAD "Enable runtime history of per-thread coop state transitions (must set env var MONO_CHECK_MODE=thread)") option (ENABLE_CHECKED_BUILD_METADATA "Enable runtime checks of mempool references between metadata images (must set env var MONO_CHECK_MODE=metadata)") +option (ENABLE_METADATA_UPDATE "Enable runtime support for metadata updates") set (GC_SUSPEND "default" CACHE STRING "GC suspend method (default, preemptive, coop, hybrid)") set (CHECKED_BUILD "" CACHE STRING "Set ENABLE_CHECKED_BUILD_ options at once. Comma-separated list of lowercase ENABLE_CHECKED_BUILD_ options ie. 'gc,threads,private_types' etc.") diff --git a/src/mono/mono.proj b/src/mono/mono.proj index 8bc7108838d69..c460c4515dafc 100644 --- a/src/mono/mono.proj +++ b/src/mono/mono.proj @@ -8,6 +8,7 @@ - MonoAOTEnableLLVM - enable LLVM for an AOT-only Mono - MonoAOTLLVMDir - [optional] the directory where LLVM is located, for an AOT-only Mono - MonoVerboseBuild - enable verbose build + - MonoMetadataUpdate - enable experimental method body replacement code --> <PropertyGroup> @@ -302,6 +303,12 @@ <_MonoCXXFLAGS Include="-Wl,--build-id=sha1" /> </ItemGroup> + <!-- Experimental features --> + <ItemGroup Condition="'$(MonoMetadataUpdate)' == 'true'"> + <!-- FIXME: Add sanity check that the interpreter is enabled --> + <_MonoCMakeArgs Include="-DENABLE_METADATA_UPDATE=1" /> + </ItemGroup> + <PropertyGroup> <_MonoCFLAGSOption>-DCMAKE_C_FLAGS="@(_MonoCPPFLAGS, ' ') @(_MonoCFLAGS, ' ')"</_MonoCFLAGSOption> <_MonoCXXFLAGSOption>-DCMAKE_CXX_FLAGS="@(_MonoCPPFLAGS, ' ') @(_MonoCXXFLAGS, ' ')"</_MonoCXXFLAGSOption> @@ -403,6 +410,9 @@ <MonoAOTCMakeArgs Condition="'$(MonoAotOffsetsFile)' != ''" Include="-DAOT_OFFSETS_FILE="$(MonoAotOffsetsFile)"" /> <MonoAOTCMakeArgs Condition="'$(MonoAOTEnableLLVM)' == 'true'" Include="-DLLVM_PREFIX=$(MonoAOTLLVMDir)" /> <MonoAOTCMakeArgs Include="$(_MonoAOTCXXFLAGSOption)" /> + <!-- Experimental features --> + <!-- FIXME: Add sanity check that the interpreter is enabled --> + <MonoAOTCMakeArgs Condition="'$(MonoMetadataUpdate)' == 'true'" Include="-DENABLE_METADATA_UPDATE=1" /> <!-- Select generator platform for VS generator --> <MonoAOTCMakeArgs Condition="'$(OS)' == 'Windows_NT' and '$(_MonoUseNinja)' != 'true' and '$(Platform)' == 'x64'" Include="-A x64" /> diff --git a/src/mono/mono/cil/tables.def b/src/mono/mono/cil/tables.def index d28447a1ada4f..1a2888fb05584 100644 --- a/src/mono/mono/cil/tables.def +++ b/src/mono/mono/cil/tables.def @@ -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") diff --git a/src/mono/mono/metadata/CMakeLists.txt b/src/mono/mono/metadata/CMakeLists.txt index be616aafddf21..232729b8014c2 100644 --- a/src/mono/mono/metadata/CMakeLists.txt +++ b/src/mono/mono/metadata/CMakeLists.txt @@ -128,6 +128,8 @@ set(metadata_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 diff --git a/src/mono/mono/metadata/Makefile.am b/src/mono/mono/metadata/Makefile.am index 26c0b992aeedf..45392bbed42a6 100644 --- a/src/mono/mono/metadata/Makefile.am +++ b/src/mono/mono/metadata/Makefile.am @@ -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 \ diff --git a/src/mono/mono/metadata/class.c b/src/mono/mono/metadata/class.c index 02406e46e76a4..e52274df2f009 100644 --- a/src/mono/mono/metadata/class.c +++ b/src/mono/mono/metadata/class.c @@ -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; } diff --git a/src/mono/mono/metadata/domain.c b/src/mono/mono/metadata/domain.c index 073f86d77751c..4af4b21702d69 100644 --- a/src/mono/mono/metadata/domain.c +++ b/src/mono/mono/metadata/domain.c @@ -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); } diff --git a/src/mono/mono/metadata/icall-decl.h b/src/mono/mono/metadata/icall-decl.h index 909621cf44dc6..16b5278bb42e7 100644 --- a/src/mono/mono/metadata/icall-decl.h +++ b/src/mono/mono/metadata/icall-decl.h @@ -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__ diff --git a/src/mono/mono/metadata/icall-def-netcore.h b/src/mono/mono/metadata/icall-def-netcore.h index 0a6e7c6debd3e..2939685c312bf 100644 --- a/src/mono/mono/metadata/icall-def-netcore.h +++ b/src/mono/mono/metadata/icall-def-netcore.h @@ -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)) diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c index 1a5e4e57a34db..c9293be7aade6 100644 --- a/src/mono/mono/metadata/icall.c +++ b/src/mono/mono/metadata/icall.c @@ -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) { diff --git a/src/mono/mono/metadata/image.c b/src/mono/mono/metadata/image.c index fe5ec7d9eb103..757324eabd441 100644 --- a/src/mono/mono/metadata/image.c +++ b/src/mono/mono/metadata/image.c @@ -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" @@ -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> @@ -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) @@ -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); @@ -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) @@ -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); @@ -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 @@ -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 @@ -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); @@ -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) { @@ -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)); @@ -3425,5 +3501,3 @@ mono_image_append_class_to_reflection_info_set (MonoClass *klass) mono_image_unlock (image); } - - diff --git a/src/mono/mono/metadata/loader.c b/src/mono/mono/metadata/loader.c index 1d5405e14bf45..6d45ea9a751ed 100644 --- a/src/mono/mono/metadata/loader.c +++ b/src/mono/mono/metadata/loader.c @@ -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> @@ -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, @@ -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; } @@ -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* @@ -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); @@ -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; diff --git a/src/mono/mono/metadata/metadata-internals.h b/src/mono/mono/metadata/metadata-internals.h index 329f839f7fc33..ce785758b9799 100644 --- a/src/mono/mono/metadata/metadata-internals.h +++ b/src/mono/mono/metadata/metadata-internals.h @@ -587,6 +587,23 @@ struct _MonoImage { /* Contains 1 based indexes */ GHashTable *weak_field_indexes; +#ifdef ENABLE_METADATA_UPDATE + /* List of MonoImages of deltas. Parent image owns 1 refcount ref of the delta image */ + GList *delta_image; + /* Tail of delta_image for fast appends */ + GList *delta_image_last; + + /* Metadata delta images only */ + uint32_t generation; /* global update ID that added this delta image */ + + /* Maps MethodDef token indices to something. In base images a boolean + * flag that there's an update for the method; in delta images a + * pointer into the RVA of the delta IL */ + GHashTable *method_table_update; + + +#endif + /* * No other runtime locks must be taken while holding this lock. * It's meant to be used only to mutate and query structures part of this image. @@ -887,6 +904,30 @@ mono_install_image_loader (const MonoImageLoader *loader); void mono_image_append_class_to_reflection_info_set (MonoClass *klass); +#ifndef ENABLE_METADATA_UPDATE +static inline void +mono_image_effective_table (const MonoTableInfo **t, int *idx) +{ +} +#else /* ENABLE_METADATA_UPDATE */ +void +mono_image_effective_table_slow (const MonoTableInfo **t, int *idx); + +static inline void +mono_image_effective_table (const MonoTableInfo **t, int *idx) +{ + if (G_LIKELY (*idx < (*t)->rows)) + return; + mono_image_effective_table_slow (t, idx); +} + +int +mono_image_relative_delta_index (MonoImage *image_dmeta, int token); + +void +mono_image_load_enc_delta (MonoDomain *domain, MonoImage *base_image, gconstpointer dmeta, uint32_t dmeta_len, gconstpointer dil, uint32_t dil_len, MonoError *error); +#endif /* ENABLE_METADATA_UPDATE */ + gpointer mono_image_set_alloc (MonoImageSet *set, guint size); @@ -943,6 +984,27 @@ mono_metadata_clean_generic_classes_for_image (MonoImage *image); MONO_API void mono_metadata_cleanup (void); +#ifndef ENABLE_METADATA_UPDATE +static inline gboolean +mono_metadata_table_bounds_check (MonoImage *image, int table_index, int token_index) +{ + /* token_index is 1-based. TRUE means the token is out of bounds */ + return token_index > image->tables [table_index].rows; +} +#else +gboolean +mono_metadata_table_bounds_check_slow (MonoImage *image, int table_index, int token_index); + +static inline gboolean +mono_metadata_table_bounds_check (MonoImage *image, int table_index, int token_index) +{ + /* returns true if given index is not in bounds with provided table/index pair */ + if (G_LIKELY (token_index <= image->tables [table_index].rows)) + return FALSE; + return mono_metadata_table_bounds_check_slow (image, table_index, token_index); +} +#endif + const char * mono_meta_table_name (int table); void mono_metadata_compute_table_bases (MonoImage *meta); diff --git a/src/mono/mono/metadata/metadata-update.c b/src/mono/mono/metadata/metadata-update.c new file mode 100644 index 0000000000000..e6cb5f47a0c60 --- /dev/null +++ b/src/mono/mono/metadata/metadata-update.c @@ -0,0 +1,974 @@ +/** + * \file + * Routines for publishing metadata updates + * + * Copyright 2020 Microsoft + * + * Licensed under the MIT license. See LICENSE file in the project root for full license information. + */ + +#include <config.h> +#include "mono/utils/mono-compiler.h" + +#ifdef ENABLE_METADATA_UPDATE + +#include <glib.h> +#include "mono/metadata/metadata-internals.h" +#include "mono/metadata/metadata-update.h" +#include "mono/metadata/object-internals.h" +#include "mono/metadata/tokentype.h" +#include "mono/utils/mono-coop-mutex.h" +#include "mono/utils/mono-error-internals.h" +#include "mono/utils/mono-lazy-init.h" +#include "mono/utils/mono-logger-internals.h" +#include "mono/utils/mono-path.h" + +/* TLS value is a uint32_t of the latest published generation that the thread can see */ +static MonoNativeTlsKey exposed_generation_id; + +#if 1 +#define UPDATE_DEBUG(stmt) do { stmt; } while (0) +#else +#define UPDATE_DEBUG(stmt) /*empty */ +#endif + +/* For each delta image, for each table: + * - the total logical number of rows for the previous generation + * - the number of modified rows in the current generation + * - the number of inserted rows in the current generation + * + * In each delta, the physical tables contain the rows that modify existing rows of a prior generation, + * followed by inserted rows. + * https://github.com/dotnet/runtime/blob/6072e4d3a7a2a1493f514cdf4be75a3d56580e84/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataAggregator.cs#L324 + * + * The total logical number of rows in a table for a particular generation is + * prev_gen_rows + inserted_rows. + */ +typedef struct _delta_row_count { + guint32 prev_gen_rows; + guint32 modified_rows; + guint32 inserted_rows; +} delta_row_count; + +typedef struct _DeltaInfo { + // for each table, the row in the EncMap table that has the first token for remapping it? + uint32_t enc_recs [MONO_TABLE_NUM]; + delta_row_count count [MONO_TABLE_NUM]; +} DeltaInfo; + + +static void +mono_metadata_update_ee_init (MonoError *error); + +/* Maps each MonoTableInfo* to the MonoImage that it belongs to. This is + * mapping the base image MonoTableInfos to the base MonoImage. We don't need + * this for deltas. + */ +static GHashTable *table_to_image, *delta_image_to_info; +/* Low-level lock to protects table_to_image and delta_image_to_info */ +/* FIXME: use concurrent hash tables so that readers don't have to lock. */ +static MonoCoopMutex table_to_image_mutex; + +static void +table_to_image_lock (void) +{ + mono_coop_mutex_lock (&table_to_image_mutex); +} + +static void +table_to_image_unlock (void) +{ + mono_coop_mutex_unlock (&table_to_image_mutex); +} + + +static void +delta_info_destroy (DeltaInfo *dinfo) +{ + g_free (dinfo); +} + +static DeltaInfo * +delta_info_lookup_locked (MonoImage *delta_image) +{ + return (DeltaInfo*)g_hash_table_lookup (delta_image_to_info, delta_image); +} + +static DeltaInfo * +delta_info_lookup (MonoImage *delta_image) +{ + DeltaInfo *result; + table_to_image_lock (); + result = delta_info_lookup_locked (delta_image); + table_to_image_unlock (); + return result; +} + +static void +table_to_image_init (void) +{ + mono_coop_mutex_init (&table_to_image_mutex); + table_to_image = g_hash_table_new (NULL, NULL); + delta_image_to_info = g_hash_table_new (NULL, NULL); +} + +static gboolean +remove_base_image (gpointer key, gpointer value, gpointer user_data) +{ + MonoImage *base_image = (MonoImage*)user_data; + MonoImage *value_image = (MonoImage*)value; + return (value_image == base_image); +} + +void +mono_metadata_update_cleanup_on_close (MonoImage *image) +{ + table_to_image_lock (); + /* remove all keys (delta images) that map to the given image (base image) */ + g_hash_table_foreach_remove (table_to_image, remove_base_image, (gpointer)image); + /* remove delta image info */ + DeltaInfo *delta_info = delta_info_lookup_locked (image); + if (delta_info) { + g_hash_table_remove (delta_image_to_info, image); + delta_info_destroy (delta_info); + } + table_to_image_unlock (); +} + +static void +table_to_image_add (MonoImage *base_image) +{ + /* If at least one table from this image is already here, they all are */ + if (g_hash_table_contains (table_to_image, &base_image->tables[MONO_TABLE_MODULE])) + return; + table_to_image_lock (); + if (g_hash_table_contains (table_to_image, &base_image->tables[MONO_TABLE_MODULE])) { + table_to_image_unlock (); + return; + } + for (int idx = 0; idx < MONO_TABLE_NUM; ++idx) { + MonoTableInfo *table = &base_image->tables[idx]; + g_hash_table_insert (table_to_image, table, base_image); + } + table_to_image_unlock (); +} + +MonoImage * +mono_table_info_get_base_image (const MonoTableInfo *t) +{ + MonoImage *image = (MonoImage *) g_hash_table_lookup (table_to_image, t); + return image; +} + +static MonoImage* +mono_image_open_dmeta_from_data (MonoImage *base_image, uint32_t generation, gconstpointer dmeta_bytes, uint32_t dmeta_length, MonoImageOpenStatus *status); + +static void +mono_image_append_delta (MonoImage *base, MonoImage *delta); + +static int +metadata_update_local_generation (MonoImage *base, MonoImage *delta); + +void +mono_metadata_update_init (void) +{ + table_to_image_init (); + mono_native_tls_alloc (&exposed_generation_id, NULL); +} + +void +mono_metadata_update_cleanup (void) +{ + mono_native_tls_free (exposed_generation_id); +} + +/* Inform the execution engine that updates are coming */ +static void +mono_metadata_update_ee_init (MonoError *error) +{ + static gboolean inited = FALSE; + + if (inited) + return; + if (mono_get_runtime_callbacks ()->metadata_update_init) + mono_get_runtime_callbacks ()->metadata_update_init (error); + inited = TRUE; +} + +static +void +mono_metadata_update_invoke_hook (MonoDomain *domain, MonoAssemblyLoadContext *alc, uint32_t generation) +{ + if (mono_get_runtime_callbacks ()->metadata_update_published) + mono_get_runtime_callbacks ()->metadata_update_published (domain, alc, generation); +} + +static uint32_t update_published, update_alloc_frontier; +static MonoCoopMutex publish_mutex; + +static void +publish_lock (void) +{ + mono_coop_mutex_lock (&publish_mutex); +} + +static void +publish_unlock (void) +{ + mono_coop_mutex_unlock (&publish_mutex); +} + +static mono_lazy_init_t metadata_update_lazy_init; + +static void +initialize (void) +{ + mono_coop_mutex_init (&publish_mutex); +} + +static void +thread_set_exposed_generation (uint32_t value) +{ + mono_native_tls_set_value (exposed_generation_id, GUINT_TO_POINTER((guint)value)); +} + +uint32_t +mono_metadata_update_prepare (MonoDomain *domain) { + mono_lazy_initialize (&metadata_update_lazy_init, initialize); + /* + * TODO: assert that the updater isn't depending on current metadata, else publishing might block. + */ + publish_lock (); + uint32_t alloc_gen = ++update_alloc_frontier; + /* Expose the alloc frontier to the updater thread */ + thread_set_exposed_generation (alloc_gen); + return alloc_gen; +} + +gboolean +mono_metadata_update_available (void) { + return update_published < update_alloc_frontier; +} + +/** + * mono_metadata_update_thread_expose_published: + * + * Allow the current thread to see the latest published deltas. + * + * Returns the current published generation that the thread will see. + */ +uint32_t +mono_metadata_update_thread_expose_published (void) +{ + mono_memory_read_barrier (); + uint32_t thread_current_gen = update_published; + thread_set_exposed_generation (thread_current_gen); + return thread_current_gen; +} + +/** + * mono_metadata_update_get_thread_generation: + * + * Return the published generation that the current thread is allowed to see. + * May be behind the latest published generation if the thread hasn't called + * \c mono_metadata_update_thread_expose_published in a while. + */ +uint32_t +mono_metadata_update_get_thread_generation (void) +{ + return (uint32_t)GPOINTER_TO_UINT(mono_native_tls_get_value(exposed_generation_id)); +} + +gboolean +mono_metadata_wait_for_update (uint32_t timeout_ms) +{ + /* TODO: give threads a way to voluntarily wait for an update to be published. */ + g_assert_not_reached (); +} + +void +mono_metadata_update_publish (MonoDomain *domain, MonoAssemblyLoadContext *alc, uint32_t generation) { + g_assert (update_published < generation && generation <= update_alloc_frontier); + /* TODO: wait for all threads that are using old metadata to update. */ + mono_metadata_update_invoke_hook (domain, alc, generation); + update_published = update_alloc_frontier; + mono_memory_write_barrier (); + publish_unlock (); +} + +void +mono_metadata_update_cancel (uint32_t generation) +{ + g_assert (update_alloc_frontier == generation); + g_assert (update_alloc_frontier > 0); + g_assert (update_alloc_frontier - 1 >= update_published); + --update_alloc_frontier; + /* Roll back exposed generation to the last published one */ + thread_set_exposed_generation (update_published); + publish_unlock (); +} + +/** + * LOCKING: Assumes the publish_lock is held + */ +void +mono_image_append_delta (MonoImage *base, MonoImage *delta) +{ + if (!base->delta_image) { + base->delta_image = base->delta_image_last = g_list_alloc (); + base->delta_image->data = (gpointer)delta; + return; + } + g_assert (((MonoImage*)base->delta_image_last->data)->generation < delta->generation); + base->delta_image_last = g_list_append (base->delta_image_last, delta); +} + +/** + * LOCKING: assumes the publish_lock is held + */ +MonoImage* +mono_image_open_dmeta_from_data (MonoImage *base_image, uint32_t generation, gconstpointer dmeta_bytes, uint32_t dmeta_length, MonoImageOpenStatus *status) +{ + MonoAssemblyLoadContext *alc = mono_image_get_alc (base_image); + MonoImage *dmeta_image = mono_image_open_from_data_internal (alc, (char*)dmeta_bytes, dmeta_length, TRUE, status, FALSE, TRUE, NULL, NULL); + + dmeta_image->generation = generation; + + /* base_image takes ownership of 1 refcount ref of dmeta_image */ + mono_image_append_delta (base_image, dmeta_image); + + return dmeta_image; +} + +static const char * +scope_to_string (uint32_t tok) +{ + const char *scope; + switch (tok & MONO_RESOLUTION_SCOPE_MASK) { + case MONO_RESOLUTION_SCOPE_MODULE: + scope = "."; + break; + case MONO_RESOLUTION_SCOPE_MODULEREF: + scope = "M"; + break; + case MONO_RESOLUTION_SCOPE_TYPEREF: + scope = "T"; + break; + case MONO_RESOLUTION_SCOPE_ASSEMBLYREF: + scope = "A"; + break; + default: + g_assert_not_reached (); + } + return scope; +} + +static void +dump_update_summary (MonoImage *image_base, MonoImage *image_dmeta) +{ + int rows; + + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "dmeta tables:"); + for (int idx = 0; idx < MONO_TABLE_NUM; ++idx) { + if (image_dmeta->tables [idx].base) + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "\t0x%02x \"%s\"", idx, mono_meta_table_name (idx)); + } + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "================================"); + + rows = mono_image_get_table_rows (image_base, MONO_TABLE_TYPEREF); + for (int i = 1; i <= rows; ++i) { + guint32 cols [MONO_TYPEREF_SIZE]; + mono_metadata_decode_row (&image_base->tables [MONO_TABLE_TYPEREF], i - 1, cols, MONO_TYPEREF_SIZE); + const char *scope = scope_to_string (cols [MONO_TYPEREF_SCOPE]); + const char *name = mono_metadata_string_heap (image_base, cols [MONO_TYPEREF_NAME]); + const char *nspace = mono_metadata_string_heap (image_base, cols [MONO_TYPEREF_NAMESPACE]); + + if (!name) + name = "<N/A>"; + if (!nspace) + nspace = "<N/A>"; + + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "base typeref i=%d (token=0x%08x) -> scope=%s, namespace=%s, name=%s", i, MONO_TOKEN_TYPE_REF | i, scope, nspace, name); + } + if (!image_dmeta->minimal_delta) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "--------------------------------"); + + rows = mono_image_get_table_rows (image_dmeta, MONO_TABLE_TYPEREF); + for (int i = 1; i <= rows; ++i) { + guint32 cols [MONO_TYPEREF_SIZE]; + mono_metadata_decode_row (&image_dmeta->tables [MONO_TABLE_TYPEREF], i - 1, cols, MONO_TYPEREF_SIZE); + const char *scope = scope_to_string (cols [MONO_TYPEREF_SCOPE]); + const char *name = mono_metadata_string_heap (image_base, cols [MONO_TYPEREF_NAME]); + const char *nspace = mono_metadata_string_heap (image_base, cols [MONO_TYPEREF_NAMESPACE]); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "dmeta typeref i=%d (token=0x%08x) -> scope=%s, nspace=%s, name=%s", i, MONO_TOKEN_TYPE_REF | i, scope, nspace, name); + } + } + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "================================"); + + rows = mono_image_get_table_rows (image_base, MONO_TABLE_METHOD); + for (int i = 1; i <= rows ; ++i) { + guint32 cols [MONO_METHOD_SIZE]; + mono_metadata_decode_row_raw (&image_base->tables [MONO_TABLE_METHOD], i - 1, cols, MONO_METHOD_SIZE); + const char *name = mono_metadata_string_heap (image_base, cols [MONO_METHOD_NAME]); + guint32 rva = cols [MONO_METHOD_RVA]; + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "base method i=%d (token=0x%08x), rva=%d/0x%04x, name=%s", i, MONO_TOKEN_METHOD_DEF | i, rva, rva, name); + } + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "--------------------------------"); + + rows = mono_image_get_table_rows (image_dmeta, MONO_TABLE_METHOD); + for (int i = 1; i <= rows ; ++i) { + guint32 cols [MONO_METHOD_SIZE]; + mono_metadata_decode_row_raw (&image_dmeta->tables [MONO_TABLE_METHOD], i - 1, cols, MONO_METHOD_SIZE); + const char *name = mono_metadata_string_heap (image_base, cols [MONO_METHOD_NAME]); + guint32 rva = cols [MONO_METHOD_RVA]; + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "dmeta method i=%d (token=0x%08x), rva=%d/0x%04x, name=%s", i, MONO_TOKEN_METHOD_DEF | i, rva, rva, name); + } + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "================================"); + + rows = mono_image_get_table_rows (image_base, MONO_TABLE_STANDALONESIG); + for (int i = 1; i <= rows; ++i) { + guint32 cols [MONO_STAND_ALONE_SIGNATURE_SIZE]; + mono_metadata_decode_row (&image_base->tables [MONO_TABLE_STANDALONESIG], i - 1, cols, MONO_STAND_ALONE_SIGNATURE_SIZE); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "base standalonesig i=%d (token=0x%08x) -> 0x%08x", i, MONO_TOKEN_SIGNATURE | i, cols [MONO_STAND_ALONE_SIGNATURE]); + } + + if (!image_dmeta->minimal_delta) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "--------------------------------"); + + rows = mono_image_get_table_rows (image_dmeta, MONO_TABLE_STANDALONESIG); + for (int i = 1; i <= rows; ++i) { + guint32 cols [MONO_STAND_ALONE_SIGNATURE_SIZE]; + mono_metadata_decode_row_raw (&image_dmeta->tables [MONO_TABLE_STANDALONESIG], i - 1, cols, MONO_STAND_ALONE_SIGNATURE_SIZE); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "dmeta standalonesig i=%d (token=0x%08x) -> 0x%08x", i, MONO_TOKEN_SIGNATURE | i, cols [MONO_STAND_ALONE_SIGNATURE]); + } + } + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "================================"); + +} + +void +mono_image_effective_table_slow (const MonoTableInfo **t, int *idx) +{ + if (G_LIKELY (*idx < (*t)->rows)) + return; + + /* FIXME: don't let any thread other than the updater thread see values from a delta image + * with a generation past update_published + */ + + MonoImage *base = mono_table_info_get_base_image (*t); + if (!base || !base->delta_image) + return; + + GList *list = base->delta_image; + MonoImage *dmeta; + int ridx; + MonoTableInfo *table; + + /* Invariant: `*t` must be a `MonoTableInfo` of the base image. */ + g_assert (base->tables < *t && *t < &base->tables [MONO_TABLE_LAST]); + + size_t s = ALIGN_TO (sizeof (MonoTableInfo), sizeof (gpointer)); + int tbl_index = ((intptr_t) *t - (intptr_t) base->tables) / s; + + /* FIXME: I don't understand how ReplaceMethodOften works - it always has a + * EnCMap entry 2: 0x06000002 (MethodDef) for every revision. Shouldn't the number of methodDef rows be going up? + + * Apparently not - because conceptually the EnC log is saying to overwrite the existing rows. + */ + + /* FIXME: so if the tables are conceptually mutated by each delta, we can't just stop at the + * first lookup that gets a relative index in the right range, can we? that will always be + * the oldest delta. + */ + + /* FIXME: the other problem is that the EnClog is a sequence of actions to MUTATE rows. So when looking up an existing row we have to be able to make it so that naive callers decoding that row see the updated data. + * + * That's the main thing that PAss1 should eb doing for us. + * + * I think we can't get away from mutating. The format is just too geared toward it. + * + * We should make the mutations atomic, though. (And I guess the heap extension is probably unavoidable) + * + * 1. Keep a table of inv + */ + + do { + g_assertf (list, "couldn't find idx=0x%08x in assembly=%s", *idx, dmeta && dmeta->name ? dmeta->name : "unknown image"); + dmeta = (MonoImage*)list->data; + list = list->next; + table = &dmeta->tables [tbl_index]; + ridx = mono_image_relative_delta_index (dmeta, mono_metadata_make_token (tbl_index, *idx + 1)) - 1; + } while (ridx < 0 || ridx >= table->rows); + + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "effective table for %s: 0x%08x -> 0x%08x (gen %d)", mono_meta_table_name (tbl_index), *idx, ridx, metadata_update_local_generation (base, dmeta)); + + *t = table; + *idx = ridx; +} + +/* + * The ENCMAP table contains the base of the relative offset. + * + * Example: + * Say you have a base image with a METHOD table having 5 entries. The minimal + * delta image adds another one, so it would be indexed with token + * `MONO_TOKEN_METHOD_DEF | 6`. However, the minimal delta image only has this + * single entry, and thus this would be an out-of-bounds access. That's where + * the ENCMAP table comes into play: It will have an entry + * `MONO_TOKEN_METHOD_DEF | 5`, so before accessing the new entry in the + * minimal delta image, it has to be substracted. Thus the new relative index + * is `1`, and no out-of-bounds acccess anymore. + * + * One can assume that ENCMAP is sorted (todo: verify this claim). + * + * BTW, `enc_recs` is just a pre-computed map to make the lookup for the + * relative index faster. + */ +int +mono_image_relative_delta_index (MonoImage *image_dmeta, int token) +{ + MonoTableInfo *encmap = &image_dmeta->tables [MONO_TABLE_ENCMAP]; + int table = mono_metadata_token_table (token); + int index = mono_metadata_token_index (token); + + /* this helper expects and returns as "index origin = 1" */ + g_assert (index > 0); + + if (!encmap->rows || !image_dmeta->minimal_delta) + return mono_metadata_token_index (token); + + DeltaInfo *delta_info = delta_info_lookup (image_dmeta); + g_assert (delta_info); + + int index_map = delta_info->enc_recs [table]; + guint32 cols[MONO_ENCMAP_SIZE]; + mono_metadata_decode_row (encmap, index_map - 1, cols, MONO_ENCMAP_SIZE); + int map_entry = cols [MONO_ENCMAP_TOKEN]; + + while (mono_metadata_token_table (map_entry) == table && mono_metadata_token_index (map_entry) < index && index_map < encmap->rows) { + mono_metadata_decode_row (encmap, ++index_map - 1, cols, MONO_ENCMAP_SIZE); + map_entry = cols [MONO_ENCMAP_TOKEN]; + } + + if (mono_metadata_token_table (map_entry) == table) { +#if 0 + g_assert (mono_metadata_token_index (map_entry) == index); +#endif + if (mono_metadata_token_index (map_entry) != index) + if (mono_trace_is_traced (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE)) + g_print ("warning: map_entry=0x%08x != index=0x%08x. is this a problem?\n", map_entry, index); + } + + int return_val = index_map - delta_info->enc_recs [table] + 1; + g_assert (return_val > 0); + return return_val; +} + +static DeltaInfo* +delta_info_init (MonoImage *image_dmeta, MonoImage *image_base) +{ + MonoTableInfo *encmap = &image_dmeta->tables [MONO_TABLE_ENCMAP]; + int table, prev_table = -1, idx; + + g_assert (!delta_info_lookup (image_dmeta)); + + if (!encmap->rows) + return NULL; + + DeltaInfo *delta_info = g_malloc0 (sizeof (DeltaInfo)); + + /*** Compute logical table sizes ***/ + if (image_base->delta_image == image_base->delta_image_last) { + /* this is the first update. */ + for (int i = 0; i < MONO_TABLE_NUM; ++i) { + delta_info->count[i].prev_gen_rows = image_base->tables[i].rows; + } + } else { + /* Current image_dmeta is image_base->delta_image_last->data, + * find its predecessor + */ + MonoImage *prev_delta = NULL; + g_assert (image_base->delta_image_last->prev != NULL); + prev_delta = (MonoImage*)image_base->delta_image_last->prev->data; + DeltaInfo *prev_gen_info = delta_info_lookup (prev_delta); + for (int i = 0; i < MONO_TABLE_NUM; ++i) { + delta_info->count[i].prev_gen_rows = prev_gen_info->count[i].prev_gen_rows + prev_gen_info->count[i].inserted_rows; + } + } + + + /* TODO: while going through the tables, update delta_info->count[tbl].{modified,inserted}_rows */ + + for (idx = 1; idx <= encmap->rows; ++idx) { + guint32 cols[MONO_ENCMAP_SIZE]; + mono_metadata_decode_row (encmap, idx - 1, cols, MONO_ENCMAP_SIZE); + uint32_t tok = cols [MONO_ENCMAP_TOKEN]; + table = mono_metadata_token_table (tok); + uint32_t rid = mono_metadata_token_index (tok); + g_assert (table >= 0 && table < MONO_TABLE_NUM); + g_assert (table != MONO_TABLE_ENCLOG); + g_assert (table != MONO_TABLE_ENCMAP); + g_assert (table >= prev_table); + /* FIXME: check bounds - is it < or <=. */ + if (rid < delta_info->count[table].prev_gen_rows) + delta_info->count[table].modified_rows++; + else + delta_info->count[table].inserted_rows++; + if (table == prev_table) + continue; + while (prev_table < table) { + prev_table++; + delta_info->enc_recs [prev_table] = idx; + } + } + g_assert (prev_table < MONO_TABLE_NUM - 1); + while (prev_table < MONO_TABLE_NUM - 1) { + prev_table++; + delta_info->enc_recs [prev_table] = idx; + } + + if (mono_trace_is_traced (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE)) { + for (int i = 0 ; i < MONO_TABLE_NUM; ++i) { + if (!image_dmeta->tables [i].base) + continue; + + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "enc_recs [%02x] / %s = 0x%02x\t(inserted: %03d, modified: %03d)", i, mono_meta_table_name (i), delta_info->enc_recs[i], delta_info->count[i].inserted_rows, delta_info->count[i].modified_rows); + } + } + + table_to_image_lock (); + g_hash_table_insert (delta_image_to_info, image_dmeta, delta_info); + table_to_image_unlock (); + + + return delta_info; +} + +static const char* +funccode_to_str (int func_code) +{ + switch (func_code) { + case 0: return "Func default"; + case 1: return "Method Create"; + case 2: return "Field Create"; + case 3: return "Param Create"; + case 4: return "Property Create"; + case 5: return "Event Create"; + default: g_assert_not_reached (); + } + return NULL; +} + +/* Run some sanity checks first. If we detect unsupported scenarios, this + * function will fail and the metadata update should be aborted. This should + * run before anything in the metadata world is updated. */ +static gboolean +apply_enclog_pass1 (MonoImage *image_base, MonoImage *image_dmeta, gconstpointer dil_data, uint32_t dil_length, MonoError *error) +{ + MonoTableInfo *table_enclog = &image_dmeta->tables [MONO_TABLE_ENCLOG]; + int rows = table_enclog->rows; + + gboolean unsupported_edits = FALSE; + + /* hack: make a pass over it, looking only for table method updates, in + * order to give more meaningful error messages first */ + + for (int i = 0; i < rows ; ++i) { + guint32 cols [MONO_ENCLOG_SIZE]; + mono_metadata_decode_row (table_enclog, i, cols, MONO_ENCLOG_SIZE); + + // FIXME: the top bit 0x8000000 of log_token is some kind of + // indicator see IsRecId in metamodelrw.cpp and + // MDInternalRW::EnumDeltaTokensInit which skips over those + // records when EditAndContinueModule::ApplyEditAndContinue is + // iterating. + int log_token = cols [MONO_ENCLOG_TOKEN]; + int func_code = cols [MONO_ENCLOG_FUNC_CODE]; + + int token_table = mono_metadata_token_table (log_token); + int token_index = mono_metadata_token_index (log_token); + + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "row[0x%02x]:0x%08x (%s idx=0x%02x) (base table has 0x%04x rows)\tfunc=0x%02x\n", i, log_token, mono_meta_table_name (token_table), token_index, image_base->tables [token_table].rows, func_code); + + + if (token_table != MONO_TABLE_METHOD) + continue; + + if (token_index > image_base->tables [token_table].rows) { + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "\tcannot add new method with token 0x%08x", log_token); + mono_error_set_type_load_name (error, NULL, image_base->name, "EnC: cannot add new method with token 0x%08x", log_token); + unsupported_edits = TRUE; + } + + g_assert (func_code == 0); /* anything else doesn't make sense here */ + } + + for (int i = 0; i < rows ; ++i) { + guint32 cols [MONO_ENCLOG_SIZE]; + mono_metadata_decode_row (table_enclog, i, cols, MONO_ENCLOG_SIZE); + + int log_token = cols [MONO_ENCLOG_TOKEN]; + int func_code = cols [MONO_ENCLOG_FUNC_CODE]; + + int token_table = mono_metadata_token_table (log_token); + int token_index = mono_metadata_token_index (log_token); + + if (token_table == MONO_TABLE_ASSEMBLYREF) { + /* okay, supported */ + } else if (token_table == MONO_TABLE_METHOD) { + /* handled above */ + } else { + if (token_index <= image_base->tables [token_table].rows) { + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "row[0x%02x]:0x%08x we do not support patching of existing table cols.", i, log_token); + mono_error_set_type_load_name (error, NULL, image_base->name, "EnC: we do not support patching of existing table cols. token=0x%08x", log_token); + unsupported_edits = TRUE; + continue; + } + } + + + /* + * So the way a non-default func_code works is that it's attached to the EnCLog + * record preceeding the new member defintion (so e.g. an addMethod code will be on + * the preceeding MONO_TABLE_TYPEDEF enc record that identifies the parent type). + */ + switch (func_code) { + case 0: /* default */ + break; + default: + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "row[0x%02x]:0x%08x FunCode %d (%s) not supported (token=0x%08x)", i, log_token, func_code, funccode_to_str (func_code), log_token); + mono_error_set_type_load_name (error, NULL, image_base->name, "EnC: FuncCode %d (%s) not supported (token=0x%08x)", func_code, funccode_to_str (func_code), log_token); + unsupported_edits = TRUE; + continue; + } + } + return !unsupported_edits; +} + +static void +set_update_method (MonoImage *image_base, uint32_t generation, MonoImage *image_dmeta, uint32_t token_index, const char* il_address) +{ + /* FIXME: this is a race if other threads are doing a lookup. */ + g_hash_table_insert (image_base->method_table_update, GUINT_TO_POINTER (token_index), GUINT_TO_POINTER (generation)); + g_hash_table_insert (image_dmeta->method_table_update, GUINT_TO_POINTER (token_index), (gpointer) il_address); +} + +/* do actuall enclog application */ +static gboolean +apply_enclog_pass2 (MonoImage *image_base, uint32_t generation, MonoImage *image_dmeta, gconstpointer dil_data, uint32_t dil_length, MonoError *error) +{ + MonoTableInfo *table_enclog = &image_dmeta->tables [MONO_TABLE_ENCLOG]; + int rows = table_enclog->rows; + + gboolean assemblyref_updated = FALSE; + for (int i = 0; i < rows ; ++i) { + guint32 cols [MONO_ENCLOG_SIZE]; + mono_metadata_decode_row (table_enclog, i, cols, MONO_ENCLOG_SIZE); + + int log_token = cols [MONO_ENCLOG_TOKEN]; + int func_code = cols [MONO_ENCLOG_FUNC_CODE]; + + int token_table = mono_metadata_token_table (log_token); + int token_index = mono_metadata_token_index (log_token); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "enclog i=%d: token=0x%08x (table=%s): %d", i, log_token, mono_meta_table_name (token_table), func_code); + + /* TODO: See CMiniMdRW::ApplyDelta for how to drive this. + */ + switch (func_code) { + case 0: /* default */ + break; + default: + g_error ("EnC: unsupported FuncCode, should be caught by pass1"); + break; + } + + if (token_table == MONO_TABLE_ASSEMBLYREF) { + g_assert (token_index > image_base->tables [token_table].rows); + + if (assemblyref_updated) + continue; + + assemblyref_updated = TRUE; + + /* FIXME: use DeltaInfo:prev_gen_rows instead of looping */ + /* TODO: do we know that there will never be modified rows in ASSEMBLYREF? */ + int old_rows = image_base->tables [MONO_TABLE_ASSEMBLYREF].rows; + for (GList *l = image_base->delta_image; l; l = l->next) { + MonoImage *delta_child = l->data; + old_rows += delta_child->tables [MONO_TABLE_ASSEMBLYREF].rows; + } + int new_rows = image_dmeta->tables [MONO_TABLE_ASSEMBLYREF].rows; + + old_rows -= new_rows; + g_assert (new_rows > 0); + g_assert (old_rows > 0); + + /* TODO: this can end bad with code around assembly.c:mono_assembly_load_reference */ + mono_image_lock (image_base); + MonoAssembly **old_array = image_base->references; + g_assert (image_base->nreferences == old_rows); + + image_base->references = g_new0 (MonoAssembly *, old_rows + new_rows + 1); + memcpy (image_base->references, old_array, sizeof (gpointer) * (old_rows + 1)); + image_base->nreferences = old_rows + new_rows; + mono_image_unlock (image_base); + + g_free (old_array); + } else if (token_table == MONO_TABLE_METHOD) { + if (token_index > image_base->tables [token_table].rows) { + g_error ("EnC: new method added, should be caught by pass1"); + } + + if (!image_base->method_table_update) + image_base->method_table_update = g_hash_table_new (g_direct_hash, g_direct_equal); + if (!image_dmeta->method_table_update) + image_dmeta->method_table_update = g_hash_table_new (g_direct_hash, g_direct_equal); + + int mapped_token = mono_image_relative_delta_index (image_dmeta, mono_metadata_make_token (token_table, token_index)); + int rva = mono_metadata_decode_row_col (&image_dmeta->tables [MONO_TABLE_METHOD], mapped_token - 1, MONO_METHOD_RVA); + if (rva < dil_length) { + char *il_address = ((char *) dil_data) + rva; + set_update_method (image_base, generation, image_dmeta, token_index, il_address); + } else { + /* rva points probably into image_base IL stream. can this ever happen? */ + g_print ("TODO: this case is still a bit WTF. token=0x%08x with rva=0x%04x\n", log_token, rva); + } + } else if (token_table == MONO_TABLE_TYPEDEF) { + /* TODO: throw? */ + /* TODO: happens changing the class (adding field or method). we ignore it, but dragons are here */ + + /* existing entries are supposed to be patched */ + g_assert (token_index <= image_base->tables [token_table].rows); + } else { + g_assert (token_index > image_base->tables [token_table].rows); + if (mono_trace_is_traced (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE)) + g_print ("todo: do something about this table index: 0x%02x\n", token_table); + } + } + return TRUE; +} + +/** + * + * LOCKING: Takes the publish_lock + */ +void +mono_image_load_enc_delta (MonoDomain *domain, MonoImage *image_base, gconstpointer dmeta_bytes, uint32_t dmeta_length, gconstpointer dil_bytes, uint32_t dil_length, MonoError *error) +{ + mono_metadata_update_ee_init (error); + if (!is_ok (error)) + return; + + const char *basename = image_base->filename; + /* FIXME: + * (1) do we need to memcpy dmeta_bytes ? (maybe) + * (2) do we need to memcpy dil_bytes ? (pretty sure, yes) + */ + + if (mono_trace_is_traced (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE)) { + g_print ("LOADING basename=%s delta update.\ndelta image=%p & dil=%p\n", basename, dmeta_bytes, dil_bytes); + /* TODO: add a non-async version of mono_dump_mem */ + mono_dump_mem (dmeta_bytes, dmeta_length); + mono_dump_mem (dil_bytes, dil_length); + } + + uint32_t generation = mono_metadata_update_prepare (domain); + + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "base image string size: 0x%08x", image_base->heap_strings.size); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "base image user string size: 0x%08x", image_base->heap_us.size); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "base image blob heap addr: %p", image_base->heap_blob.data); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "base image blob heap size: 0x%08x", image_base->heap_blob.size); + + MonoImageOpenStatus status; + MonoImage *image_dmeta = mono_image_open_dmeta_from_data (image_base, generation, dmeta_bytes, dmeta_length, &status); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "delta image string size: 0x%08x", image_dmeta->heap_strings.size); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "delta image user string size: 0x%08x", image_dmeta->heap_us.size); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "delta image blob heap addr: %p", image_dmeta->heap_blob.data); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "delta image blob heap size: 0x%08x", image_dmeta->heap_blob.size); + g_assert (image_dmeta); + g_assert (status == MONO_IMAGE_OK); + + if (image_dmeta->minimal_delta) { + guint32 idx = mono_metadata_decode_row_col (&image_dmeta->tables [MONO_TABLE_MODULE], 0, MONO_MODULE_NAME); + + const char *module_name = NULL; + module_name = mono_metadata_string_heap (image_base, idx); + + /* Set the module name now that we know the base String heap size */ + g_assert (!image_dmeta->module_name); + image_dmeta->module_name = module_name; + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "applied dmeta name: '%s'\n", module_name); + } + + MonoTableInfo *table_enclog = &image_dmeta->tables [MONO_TABLE_ENCLOG]; + MonoTableInfo *table_encmap = &image_dmeta->tables [MONO_TABLE_ENCMAP]; + + if (!table_enclog->rows && !table_encmap->rows) { + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "No enclog or encmap in delta image for base=%s, nothing to do", basename); + mono_metadata_update_cancel (generation); + return; + } + + if (!apply_enclog_pass1 (image_base, image_dmeta, dil_bytes, dil_length, error)) { + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "Error on sanity-checking delta image to base=%s, due to: %s", basename, mono_error_get_message (error)); + mono_metadata_update_cancel (generation); + return; + } + + /* if there are updates, start tracking the tables of the base image, if we weren't already. */ + if (table_enclog->rows) + table_to_image_add (image_base); + + delta_info_init (image_dmeta, image_base); + + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "base guid: %s", image_base->guid); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "dmeta guid: %s", image_dmeta->guid); + + if (mono_trace_is_traced (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE)) + dump_update_summary (image_base, image_dmeta); + + if (!apply_enclog_pass2 (image_base, generation, image_dmeta, dil_bytes, dil_length, error)) { + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, "Error applying delta image to base=%s, due to: %s", basename, mono_error_get_message (error)); + mono_metadata_update_cancel (generation); + return; + } + mono_error_assert_ok (error); + + MonoAssemblyLoadContext *alc = mono_image_get_alc (image_base); + mono_metadata_update_publish (domain, alc, generation); + + mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_METADATA_UPDATE, ">>> EnC delta for base=%s (generation %d) applied", basename, generation); +} + +/* + * Returns how many times the base image was updated upto and including the given delta. + */ +static int +metadata_update_local_generation (MonoImage *base, MonoImage *delta) +{ + if (delta == base) + return 0; + int index = g_list_index (base->delta_image, delta); + g_assert (index != -1); + return 1 + index; +} + +/* + * Returns how many times the given base image has been updated so far. + * + * NOTE: doesn't look at update_published or update_alloc_frontier, and therefore only usable by the + * update originator. + */ +static int +metadata_update_count_updates (MonoImage *base) +{ + if (!base->delta_image_last) + return 0; + else + return metadata_update_local_generation (base, (MonoImage*)base->delta_image_last->data); +} +#else /* ENABLE_METADATA_UPDATE */ +MONO_EMPTY_SOURCE_FILE (metadata_update); +#endif /* ENABLE_METADATA_UPDATE */ + diff --git a/src/mono/mono/metadata/metadata-update.h b/src/mono/mono/metadata/metadata-update.h new file mode 100644 index 0000000000000..035d322ed1454 --- /dev/null +++ b/src/mono/mono/metadata/metadata-update.h @@ -0,0 +1,50 @@ +/** + * \file + */ + +#ifndef __MONO_METADATA_UPDATE_H__ +#define __MONO_METADATA_UPDATE_H__ + +#include "mono/utils/mono-forward.h" +#include "mono/metadata/loader-internals.h" +#include "mono/metadata/metadata-internals.h" + +#ifdef ENABLE_METADATA_UPDATE + +void +mono_metadata_update_init (void); + +void +mono_metadata_update_cleanup (void); + +gboolean +mono_metadata_update_available (void); + +uint32_t +mono_metadata_update_thread_expose_published (void); + +uint32_t +mono_metadata_update_get_thread_generation (void); + +gboolean +mono_metadata_wait_for_update (uint32_t timeout_ms); + +uint32_t +mono_metadata_update_prepare (MonoDomain *domain); + +void +mono_metadata_update_publish (MonoDomain *domain, MonoAssemblyLoadContext *alc, uint32_t generation); + +void +mono_metadata_update_cancel (uint32_t generation); + +void +mono_metadata_update_cleanup_on_close (MonoImage *base_image); + +MonoImage * +mono_table_info_get_base_image (const MonoTableInfo *t); + + +#endif /* ENABLE_METADATA_UPDATE */ + +#endif /*__MONO_METADATA_UPDATE_H__*/ diff --git a/src/mono/mono/metadata/metadata.c b/src/mono/mono/metadata/metadata.c index 73a6978c988e9..bf5d119541bf2 100644 --- a/src/mono/mono/metadata/metadata.c +++ b/src/mono/mono/metadata/metadata.c @@ -24,6 +24,7 @@ #include "class-internals.h" #include "metadata-internals.h" #include "reflection-internals.h" +#include "metadata-update.h" #include "verify-internals.h" #include "class.h" #include "marshal.h" @@ -989,6 +990,35 @@ mono_metadata_compute_size (MonoImage *meta, int tableindex, guint32 *result_bit return size; } +#ifdef ENABLE_METADATA_UPDATE +/* returns true if given index is not in bounds with provided table/index pair */ +gboolean +mono_metadata_table_bounds_check_slow (MonoImage *image, int table_index, int token_index) +{ + if (G_LIKELY (token_index <= image->tables [table_index].rows)) + return FALSE; + + GList *list = image->delta_image; + MonoImage *dmeta; + MonoTableInfo *table; + int ridx; + + uint32_t exposed_gen = mono_metadata_update_get_thread_generation (); + do { + if (!list) + return TRUE; + dmeta = list->data; + if (dmeta->generation > exposed_gen) + return TRUE; + list = list->next; + table = &dmeta->tables [table_index]; + ridx = mono_image_relative_delta_index (dmeta, mono_metadata_make_token (table_index, token_index + 1)) - 1; + } while (ridx < 0 || ridx >= table->rows); + + return FALSE; +} +#endif + /** * mono_metadata_compute_table_bases: * \param meta metadata context to compute table values @@ -1045,6 +1075,62 @@ mono_metadata_locate_token (MonoImage *meta, guint32 token) return mono_metadata_locate (meta, token >> 24, token & 0xffffff); } + + +typedef MonoStreamHeader* (*MetadataHeapGetterFunc) (MonoImage*); + +#ifdef ENABLE_METADATA_UPDATE +static MonoStreamHeader * +get_string_heap (MonoImage *image) +{ + return &image->heap_strings; +} + +static MonoStreamHeader * +get_user_string_heap (MonoImage *image) +{ + return &image->heap_us; +} + +static MonoStreamHeader * +get_blob_heap (MonoImage *image) +{ + return &image->heap_blob; +} + +static gboolean +mono_delta_heap_lookup (MonoImage *base_image, MetadataHeapGetterFunc get_heap, guint32 orig_index, MonoImage **image_out, guint32 *index_out) +{ + g_assert (image_out); + g_assert (index_out); + MonoStreamHeader *heap = get_heap (base_image); + g_assert (orig_index >= heap->size && base_image->delta_image); + + *image_out = base_image; + *index_out = orig_index; + + guint32 prev_size = heap->size; + + uint32_t current_gen = mono_metadata_update_get_thread_generation (); + GList *cur; + for (cur = base_image->delta_image; cur; cur = cur->next) { + *image_out = (MonoImage*)cur->data; + heap = get_heap (*image_out); + + if ((*image_out)->generation > current_gen) + return FALSE; + + /* FIXME: for non-minimal deltas we should just look in the last published image. */ + if (G_LIKELY ((*image_out)->minimal_delta)) + *index_out -= prev_size; + if (*index_out < heap->size) + break; + prev_size = heap->size; + } + return (cur != NULL); +} +#endif + /** * mono_metadata_string_heap: * \param meta metadata context @@ -1054,7 +1140,18 @@ mono_metadata_locate_token (MonoImage *meta, guint32 token) const char * mono_metadata_string_heap (MonoImage *meta, guint32 index) { - g_assert (index < meta->heap_strings.size); +#ifdef ENABLE_METADATA_UPDATE + if (G_UNLIKELY (index >= meta->heap_strings.size && meta->delta_image)) { + MonoImage *dmeta; + guint32 dindex; + gboolean ok = mono_delta_heap_lookup (meta, &get_string_heap, index, &dmeta, &dindex); + g_assertf (ok, "Could not find token=0x%08x in string heap of assembly=%s and its delta images", index, meta && meta->name ? meta->name : "unknown image"); + meta = dmeta; + index = dindex; + } +#endif + + g_assertf (index < meta->heap_strings.size, " index = 0x%08x size = 0x%08x meta=%s ", index, meta->heap_strings.size, meta && meta->name ? meta->name : "unknown image" ); g_return_val_if_fail (index < meta->heap_strings.size, ""); return meta->heap_strings.data + index; } @@ -1080,7 +1177,24 @@ mono_metadata_string_heap_checked (MonoImage *meta, guint32 index, MonoError *er } return img->sheap.data + index; } - else if (G_UNLIKELY (!(index < meta->heap_strings.size))) { + +#ifdef ENABLE_METADATA_UPDATE + if (G_UNLIKELY (index >= meta->heap_strings.size && meta->delta_image)) { + MonoImage *dmeta; + guint32 dindex; + gboolean ok = mono_delta_heap_lookup (meta, &get_string_heap, index, &dmeta, &dindex); + if (G_UNLIKELY (!ok)) { + const char *image_name = meta && meta->name ? meta->name : "unknown image"; + mono_error_set_bad_image_by_name (error, image_name, "string heap index %ud out bounds %u: %s, also checked delta images", index, meta->heap_strings.size, image_name); + + return NULL; + } + meta = dmeta; + index = dindex; + } +#endif + + if (G_UNLIKELY (!(index < meta->heap_strings.size))) { const char *image_name = meta && meta->name ? meta->name : "unknown image"; mono_error_set_bad_image_by_name (error, image_name, "string heap index %ud out bounds %u: %s", index, meta->heap_strings.size, image_name); return NULL; @@ -1097,6 +1211,16 @@ mono_metadata_string_heap_checked (MonoImage *meta, guint32 index, MonoError *er const char * mono_metadata_user_string (MonoImage *meta, guint32 index) { +#ifdef ENABLE_METADATA_UPDATE + if (G_UNLIKELY (index >= meta->heap_us.size && meta->delta_image)) { + MonoImage *dmeta; + guint32 dindex; + gboolean ok = mono_delta_heap_lookup (meta, &get_user_string_heap, index, &dmeta, &dindex); + g_assertf (ok, "Could not find token=0x%08x in user string heap of assembly=%s and its delta images", index, meta && meta->name ? meta->name : "unknown image"); + meta = dmeta; + index = dindex; + } +#endif g_assert (index < meta->heap_us.size); g_return_val_if_fail (index < meta->heap_us.size, ""); return meta->heap_us.data + index; @@ -1116,6 +1240,16 @@ mono_metadata_blob_heap (MonoImage *meta, guint32 index) * assertion is hit, consider updating caller to use * mono_metadata_blob_heap_null_ok and handling a null return value. */ g_assert (!(index == 0 && meta->heap_blob.size == 0)); +#ifdef ENABLE_METADATA_UPDATE + if (G_UNLIKELY (index >= meta->heap_blob.size && meta->delta_image)) { + MonoImage *dmeta; + guint32 dindex; + gboolean ok = mono_delta_heap_lookup (meta, &get_blob_heap, index, &dmeta, &dindex); + g_assertf (ok, "Could not find token=0x%08x in blob heap of assembly=%s and its delta images", index, meta && meta->name ? meta->name : "unknown image"); + meta = dmeta; + index = dindex; + } +#endif g_assert (index < meta->heap_blob.size); return meta->heap_blob.data + index; } @@ -1161,6 +1295,20 @@ mono_metadata_blob_heap_checked (MonoImage *meta, guint32 index, MonoError *erro } if (G_UNLIKELY (index == 0 && meta->heap_blob.size == 0)) return NULL; +#ifdef ENABLE_METADATA_UPDATE + if (G_UNLIKELY (index >= meta->heap_blob.size && meta->delta_image)) { + MonoImage *dmeta; + guint32 dindex; + gboolean ok = mono_delta_heap_lookup (meta, &get_blob_heap, index, &dmeta, &dindex); + if (G_UNLIKELY(!ok)) { + const char *image_name = meta && meta->name ? meta->name : "unknown image"; + mono_error_set_bad_image_by_name (error, image_name, "Could not find token=0x%08x in blob heap of assembly=%s and its delta images", index, image_name); + return NULL; + } + meta = dmeta; + index = dindex; + } +#endif if (G_UNLIKELY (!(index < meta->heap_blob.size))) { const char *image_name = meta && meta->name ? meta->name : "unknown image"; mono_error_set_bad_image_by_name (error, image_name, "blob heap index %u out of bounds %u: %s", index, meta->heap_blob.size, image_name); @@ -1178,6 +1326,7 @@ mono_metadata_blob_heap_checked (MonoImage *meta, guint32 index, MonoError *erro const char * mono_metadata_guid_heap (MonoImage *meta, guint32 index) { + /* EnC TODO: lookup in MonoImage:delta_image_last. Unlike the other heaps, the GUID heaps are always full in every delta, even in minimal delta images. */ --index; index *= 16; /* adjust for guid size and 1-based index */ g_return_val_if_fail (index < meta->heap_guid.size, ""); @@ -1201,6 +1350,17 @@ dword_align (const unsigned char *ptr) */ void mono_metadata_decode_row (const MonoTableInfo *t, int idx, guint32 *res, int res_size) +{ + mono_image_effective_table (&t, &idx); + + mono_metadata_decode_row_raw (t, idx, res, res_size); +} + +/** + * same as mono_metadata_decode_row, but ignores potential delta images + */ +void +mono_metadata_decode_row_raw (const MonoTableInfo *t, int idx, guint32 *res, int res_size) { guint32 bitfield = t->size_bitfield; int i, count = mono_metadata_table_count (bitfield); @@ -1246,11 +1406,13 @@ mono_metadata_decode_row (const MonoTableInfo *t, int idx, guint32 *res, int res gboolean mono_metadata_decode_row_checked (const MonoImage *image, const MonoTableInfo *t, int idx, guint32 *res, int res_size, MonoError *error) { + const char *image_name = image && image->name ? image->name : "unknown image"; + + mono_image_effective_table (&t, &idx); + guint32 bitfield = t->size_bitfield; int i, count = mono_metadata_table_count (bitfield); - const char *image_name = image && image->name ? image->name : "unknown image"; - if (G_UNLIKELY (! (idx < t->rows && idx >= 0))) { mono_error_set_bad_image_by_name (error, image_name, "row index %d out of bounds: %d rows: %s", idx, t->rows, image_name); return FALSE; @@ -1320,10 +1482,13 @@ mono_metadata_decode_row_dynamic_checked (const MonoDynamicImage *image, const M guint32 mono_metadata_decode_row_col (const MonoTableInfo *t, int idx, guint col) { - guint32 bitfield = t->size_bitfield; int i; const char *data; int n; + + mono_image_effective_table (&t, &idx); + + guint32 bitfield = t->size_bitfield; g_assert (idx < t->rows); g_assert (col < mono_metadata_table_count (bitfield)); @@ -1836,6 +2001,10 @@ mono_metadata_init (void) mono_counters_register ("ImgSet Cache Hit", MONO_COUNTER_METADATA | MONO_COUNTER_INT, &img_set_cache_hit); mono_counters_register ("ImgSet Cache Miss", MONO_COUNTER_METADATA | MONO_COUNTER_INT, &img_set_cache_miss); mono_counters_register ("ImgSet Count", MONO_COUNTER_METADATA | MONO_COUNTER_INT, &img_set_count); + +#ifdef ENABLE_METADATA_UPDATE + mono_metadata_update_init (); +#endif } /** @@ -1847,6 +2016,9 @@ mono_metadata_init (void) void mono_metadata_cleanup (void) { +#ifdef ENABLE_METADATA_UPDATE + mono_metadata_update_cleanup (); +#endif g_hash_table_destroy (type_cache); type_cache = NULL; g_ptr_array_free (image_sets, TRUE); @@ -4503,12 +4675,12 @@ mono_metadata_parse_mh_full (MonoImage *m, MonoGenericContainer *container, cons } if (local_var_sig_tok) { - int idx = (local_var_sig_tok & 0xffffff)-1; - if (idx >= t->rows || idx < 0) { - mono_error_set_bad_image (error, m, "Invalid method header local vars signature token 0x%8x", idx); + int idx = mono_metadata_token_index (local_var_sig_tok) - 1; + if (mono_metadata_table_bounds_check (m, MONO_TABLE_STANDALONESIG, idx)) { + mono_error_set_bad_image (error, m, "Invalid method header local vars signature token 0x%08x", idx); goto fail; } - mono_metadata_decode_row (t, idx, cols, 1); + mono_metadata_decode_row (t, idx, cols, MONO_STAND_ALONE_SIGNATURE_SIZE); if (!mono_verifier_verify_standalone_signature (m, cols [MONO_STAND_ALONE_SIGNATURE], error)) goto fail; diff --git a/src/mono/mono/metadata/metadata.h b/src/mono/mono/metadata/metadata.h index c44931d7328d8..7870adbdf88b8 100644 --- a/src/mono/mono/metadata/metadata.h +++ b/src/mono/mono/metadata/metadata.h @@ -205,6 +205,8 @@ typedef struct { MONO_API void mono_metadata_init (void); +void mono_metadata_decode_row_raw (const MonoTableInfo *t, int idx, uint32_t *res, int res_size); + MONO_API void mono_metadata_decode_row (const MonoTableInfo *t, int idx, uint32_t *res, diff --git a/src/mono/mono/metadata/object-internals.h b/src/mono/mono/metadata/object-internals.h index db47b0825da74..af9514d2ccb71 100644 --- a/src/mono/mono/metadata/object-internals.h +++ b/src/mono/mono/metadata/object-internals.h @@ -843,6 +843,10 @@ typedef struct { GHashTable *(*get_weak_field_indexes) (MonoImage *image); void (*install_state_summarizer) (void); gboolean (*is_interpreter_enabled) (void); +#ifdef ENABLE_METADATA_UPDATE + void (*metadata_update_init) (MonoError *error); + void (*metadata_update_published) (MonoDomain *domain, MonoAssemblyLoadContext *alc, uint32_t generation); +#endif } MonoRuntimeCallbacks; typedef gboolean (*MonoInternalStackWalk) (MonoStackFrameInfo *frame, MonoContext *ctx, gpointer data); diff --git a/src/mono/mono/metadata/threads.c b/src/mono/mono/metadata/threads.c index 4bb057982394b..755cfb0f07f52 100644 --- a/src/mono/mono/metadata/threads.c +++ b/src/mono/mono/metadata/threads.c @@ -938,6 +938,11 @@ mono_thread_attach_internal (MonoThread *thread, gboolean force_attach, gboolean set_current_thread_for_domain (domain, internal, thread); +#ifdef MONO_METADATA_UPDATE + /* Roll up to the latest published metadata generation */ + mono_metadata_update_thread_expose_published (); +#endif + THREAD_DEBUG (g_message ("%s: Attached thread ID %" G_GSIZE_FORMAT " (handle %p)", __func__, internal->tid, internal->handle)); return TRUE; diff --git a/src/mono/mono/mini/ee.h b/src/mono/mono/mini/ee.h index a74b444e95fd8..110e9be694738 100644 --- a/src/mono/mono/mini/ee.h +++ b/src/mono/mono/mini/ee.h @@ -57,6 +57,7 @@ typedef gpointer MonoInterpFrameHandle; MONO_EE_CALLBACK (void, stop_single_stepping, (void)) \ MONO_EE_CALLBACK (void, free_context, (gpointer)) \ MONO_EE_CALLBACK (void, set_optimizations, (guint32)) \ + MONO_EE_CALLBACK (void, metadata_update_init, (MonoError *error)) \ MONO_EE_CALLBACK (void, invalidate_transformed, (MonoDomain *domain)) \ MONO_EE_CALLBACK (void, cleanup, (void)) \ MONO_EE_CALLBACK (void, mark_stack, (gpointer thread_info, GcScanFunc func, gpointer gc_data, gboolean precise)) \ diff --git a/src/mono/mono/mini/interp-stubs.c b/src/mono/mono/mini/interp-stubs.c index 0c06044553cf5..1179bd1dd3e14 100644 --- a/src/mono/mono/mini/interp-stubs.c +++ b/src/mono/mono/mini/interp-stubs.c @@ -79,6 +79,11 @@ stub_set_optimizations (guint32 i) { } +static void +stub_metadata_update_init (MonoError *error) +{ +} + static void stub_invalidate_transformed (MonoDomain *domain) { diff --git a/src/mono/mono/mini/interp/interp-internals.h b/src/mono/mono/mini/interp/interp-internals.h index 6e240756a2ec5..5498da132faa7 100644 --- a/src/mono/mono/mini/interp/interp-internals.h +++ b/src/mono/mono/mini/interp/interp-internals.h @@ -225,6 +225,11 @@ typedef struct { guchar *stack_pointer; /* Used for allocation of localloc regions */ FrameDataAllocator data_stack; + /* Used when a thread self-suspends at a safepoint in the interpreter, points to the + * currently executing frame. (If a thread self-suspends somewhere else in the runtime, this + * is NULL - the LMF will point to the InterpFrame before the thread exited the interpreter) + */ + InterpFrame *safepoint_frame; } ThreadContext; typedef struct { diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c index f4075f2a13b5e..2c7a77e3e05cd 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -25,7 +25,9 @@ #include <mono/utils/gc_wrapper.h> #include <mono/utils/mono-math.h> #include <mono/utils/mono-counters.h> +#include <mono/utils/mono-logger-internals.h> #include <mono/utils/mono-tls-inline.h> +#include <mono/utils/mono-threads.h> #include <mono/utils/mono-membar.h> #ifdef HAVE_ALLOCA_H @@ -418,6 +420,8 @@ interp_free_context (gpointer ctx) set_context (NULL); } + context->safepoint_frame = NULL; + mono_vfree (context->stack_start, INTERP_STACK_SIZE, MONO_MEM_ACCOUNT_INTERP_STACK); /* Prevent interp_mark_stack from trying to scan the data_stack, before freeing it */ context->stack_start = NULL; @@ -426,6 +430,19 @@ interp_free_context (gpointer ctx) g_free (context); } +static void +context_set_safepoint_frame (ThreadContext *context, InterpFrame *frame) +{ + g_assert (!context->has_resume_state); + context->safepoint_frame = frame; +} + +static void +context_clear_safepoint_frame (ThreadContext *context) +{ + context->safepoint_frame = NULL; +} + void mono_interp_error_cleanup (MonoError* error) { @@ -1995,6 +2012,7 @@ interp_entry (InterpEntryData *data) context->stack_pointer = (guchar*)sp; g_assert (!context->has_resume_state); + g_assert (!context->safepoint_frame); if (rmethod->needs_thread_attach) mono_threads_detach_coop (orig_domain, &attach_cookie); @@ -5149,8 +5167,12 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs MINT_IN_CASE(MINT_SAFEPOINT) /* Do synchronous checking of abort requests */ EXCEPTION_CHECKPOINT; - /* Poll safepoint */ - mono_threads_safepoint (); + if (G_UNLIKELY (mono_polling_required)) { + context_set_safepoint_frame (context, frame); + /* Poll safepoint */ + mono_threads_safepoint (); + context_clear_safepoint_frame (context); + } ++ip; MINT_IN_BREAK; MINT_IN_CASE(MINT_LDFLDA_UNSAFE) { @@ -7389,13 +7411,90 @@ invalidate_transform (gpointer imethod_) imethod->transformed = FALSE; } +static void +copy_imethod_for_frame (MonoDomain *domain, InterpFrame *frame) +{ + InterpMethod *copy = (InterpMethod *) mono_domain_alloc0 (domain, sizeof (InterpMethod)); + memcpy (copy, frame->imethod, sizeof (InterpMethod)); + copy->next_jit_code_hash = NULL; /* we don't want that in our copy */ + frame->imethod = copy; + /* Note: The copy will be around until the domain is unloading. Ideally we + * would reclaim its memory when the corresponding InterpFrame is popped. + */ +} + +static void +interp_metadata_update_init (MonoError *error) +{ + if ((mono_interp_opt & INTERP_OPT_INLINE) != 0) + mono_error_set_execution_engine (error, "Interpreter inlining must be turned off for metadata updates"); +} + +#ifdef ENABLE_METADATA_UPDATE +static void +metadata_update_backup_frames (MonoDomain *domain, MonoThreadInfo *info, InterpFrame *frame) +{ + while (frame) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "threadinfo=%p, copy imethod for method=%s", info, mono_method_full_name (frame->imethod->method, 1)); + copy_imethod_for_frame (domain, frame); + frame = frame->parent; + } +} + +static void +metadata_update_prepare_to_invalidate (MonoDomain *domain) +{ + /* (1) make a copy of imethod for every interpframe that is on the stack, + * so we do not invalidate currently running methods */ + + FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) { + if (!info || !info->jit_data) + continue; + + ThreadContext *context = (ThreadContext*)info->jit_data->interp_context; + + /* If the thread was in the interpreter and hit a safepoint + * opcode and suspended, backup the frames since the last lmf. + */ + if (context && context->safepoint_frame) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_METADATA_UPDATE, "threadinfo=%p, has safepoint frame %p", info, context->safepoint_frame); + metadata_update_backup_frames (domain, info, context->safepoint_frame); + } + + MonoLMF *lmf = info->jit_data->lmf; + while (lmf) { + if (((gsize) lmf->previous_lmf) & 2) { + MonoLMFExt *ext = (MonoLMFExt *) lmf; + if (ext->kind == MONO_LMFEXT_INTERP_EXIT || ext->kind == MONO_LMFEXT_INTERP_EXIT_WITH_CTX) { + InterpFrame *frame = ext->interp_exit_data; + metadata_update_backup_frames (domain, info, frame); + } + } + lmf = (MonoLMF *)(((gsize) lmf->previous_lmf) & ~3); + } + } FOREACH_THREAD_END + + /* (2) invalidate all the registered imethods */ +} +#endif + + static void interp_invalidate_transformed (MonoDomain *domain) { + gboolean need_stw_restart = FALSE; +#ifdef ENABLE_METADATA_UPDATE + need_stw_restart = TRUE; + mono_gc_stop_world (); + metadata_update_prepare_to_invalidate (domain); +#endif MonoJitDomainInfo *info = domain_jit_info (domain); mono_domain_jit_code_hash_lock (domain); mono_internal_hash_table_apply (&info->interp_code_hash, invalidate_transform); mono_domain_jit_code_hash_unlock (domain); + + if (need_stw_restart) + mono_gc_restart_world (); } static void diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index 1994437f0da09..3bb523930478a 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -14,6 +14,7 @@ #include <mono/metadata/debug-helpers.h> #include <mono/metadata/exception.h> #include <mono/metadata/exception-internals.h> +#include <mono/metadata/metadata-update.h> #include <mono/metadata/mono-endian.h> #include <mono/metadata/marshal.h> #include <mono/metadata/profiler-private.h> @@ -8500,6 +8501,10 @@ mono_interp_transform_method (InterpMethod *imethod, ThreadContext *context, Mon error_init (error); +#ifdef ENABLE_METADATA_UPDATE + mono_metadata_update_thread_expose_published (); +#endif + if (mono_class_is_open_constructed_type (m_class_get_byval_arg (method->klass))) { mono_error_set_invalid_operation (error, "%s", "Could not execute the method because the containing type is not fully instantiated."); return; diff --git a/src/mono/mono/mini/mini-runtime.c b/src/mono/mono/mini/mini-runtime.c index 2910d1a1659fc..21f21ee15f484 100644 --- a/src/mono/mono/mini/mini-runtime.c +++ b/src/mono/mono/mini/mini-runtime.c @@ -169,6 +169,11 @@ GSList *mono_interp_only_classes; static void register_icalls (void); static void runtime_cleanup (MonoDomain *domain, gpointer user_data); +#ifdef ENABLE_METADATA_UPDATE +static void mini_metadata_update_init (MonoError *error); +static void mini_invalidate_transformed_interp_methods (MonoDomain *domain, MonoAssemblyLoadContext *alc, uint32_t generation); +#endif + gboolean mono_running_on_valgrind (void) @@ -4433,6 +4438,10 @@ mini_init (const char *filename, const char *runtime_version) #ifndef DISABLE_CRASH_REPORTING callbacks.install_state_summarizer = mini_register_sigterm_handler; #endif +#ifdef ENABLE_METADATA_UPDATE + callbacks.metadata_update_init = mini_metadata_update_init; + callbacks.metadata_update_published = mini_invalidate_transformed_interp_methods; +#endif mono_install_callbacks (&callbacks); @@ -5326,3 +5335,17 @@ mono_runtime_install_custom_handlers_usage (void) "No handlers supported on current platform.\n"); } #endif /* HOST_WIN32 */ + +#ifdef ENABLE_METADATA_UPDATE +void +mini_metadata_update_init (MonoError *error) +{ + mini_get_interp_callbacks ()->metadata_update_init (error); +} + +void +mini_invalidate_transformed_interp_methods (MonoDomain *domain, MonoAssemblyLoadContext *alc G_GNUC_UNUSED, uint32_t generation G_GNUC_UNUSED) +{ + mini_get_interp_callbacks ()->invalidate_transformed (domain); +} +#endif diff --git a/src/mono/mono/utils/mono-logger-internals.h b/src/mono/mono/utils/mono-logger-internals.h index ad8555fa527cb..9a2406adc0501 100644 --- a/src/mono/mono/utils/mono-logger-internals.h +++ b/src/mono/mono/utils/mono-logger-internals.h @@ -164,6 +164,6 @@ void mono_log_write_recorder (const char *log_domain, GLogLevelFlags level, mono void mono_log_close_recorder (void); void mono_log_dump_recorder (void); -void mono_dump_mem (gpointer d, int len); +void mono_dump_mem (gconstpointer d, int len); #endif /* __MONO_LOGGER_INTERNAL_H__ */ diff --git a/src/mono/mono/utils/mono-logger.c b/src/mono/mono/utils/mono-logger.c index 8d2d2f8ce13ce..6f5cae1e6dd17 100644 --- a/src/mono/mono/utils/mono-logger.c +++ b/src/mono/mono/utils/mono-logger.c @@ -552,7 +552,7 @@ conv_ascii_char (gchar s) /* No memfree because only called during crash */ void -mono_dump_mem (gpointer d, int len) +mono_dump_mem (gconstpointer d, int len) { guint8 *data = (guint8 *) d; diff --git a/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj index 041411097ff0e..a48b961b142d2 100644 --- a/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -118,6 +118,11 @@ <DefineConstants Condition="'$(FeaturePerfTracing)' == 'true'">$(DefineConstants);FEATURE_PERFTRACING</DefineConstants> </PropertyGroup> + <!-- Experimental mono metadata update feature --> + <PropertyGroup> + <DefineConstants Condition="'$(MonoMetadataUpdate)' == 'true'">$(DefineConstants);FEATURE_METADATA_UPDATE</DefineConstants> + </PropertyGroup> + <!-- ILLinker settings --> <PropertyGroup> <ILLinkTrimAssembly>true</ILLinkTrimAssembly> diff --git a/src/mono/netcore/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.xml b/src/mono/netcore/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.xml index 39ab94fb1193b..a7123b5c756b1 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.xml +++ b/src/mono/netcore/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.xml @@ -551,6 +551,11 @@ <method signature="System.Void .ctor(System.Object)" /> </type> + <!-- Used by metadata update --> + <type fullname="System.Runtime.CompilerServices.RuntimeFeature"> + <method name="LoadMetadataUpdate" /> + </type> + <!-- domain.c: mono_defaults.marshal_class --> <type fullname="System.Runtime.InteropServices.Marshal" preserve="fields" > <!-- marshal.c (mono_marshal_get_struct_to_ptr) --> diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.Mono.cs index 958eb38f174a1..58c0efb6c90d0 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeFeature.Mono.cs @@ -1,5 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Reflection; namespace System.Runtime.CompilerServices { @@ -16,5 +17,24 @@ public static bool IsDynamicCodeCompiled [Intrinsic] // the JIT/AOT compiler will change this flag to false for FullAOT scenarios, otherwise true get => IsDynamicCodeCompiled; } + +#if !FEATURE_METADATA_UPDATE + internal static void LoadMetadataUpdate (Assembly assm, byte[] dmeta_data, byte[] dil_data) { + throw new NotSupportedException ("Method body replacement not supported in this runtime"); + } +#else + [MethodImplAttribute (MethodImplOptions.InternalCall)] + private static unsafe extern void LoadMetadataUpdate_internal (IntPtr base_assm, byte* dmeta_bytes, int dmeta_length, byte *dil_bytes, int dil_length); + + internal static void LoadMetadataUpdate (Assembly assm, byte[] dmeta_data, byte[] dil_data) { + unsafe { + fixed (byte* dmeta_bytes = dmeta_data) + fixed (byte* dil_bytes = dil_data) { + IntPtr mono_assembly = ((RuntimeAssembly)assm).GetUnderlyingNativeHandle (); + LoadMetadataUpdate_internal (mono_assembly, dmeta_bytes, dmeta_data.Length, dil_bytes, dil_data.Length); + } + } + } +#endif } } diff --git a/src/mono/netcore/sample/mbr/DeltaHelper.targets b/src/mono/netcore/sample/mbr/DeltaHelper.targets new file mode 100644 index 0000000000000..1d400bdcace5b --- /dev/null +++ b/src/mono/netcore/sample/mbr/DeltaHelper.targets @@ -0,0 +1,96 @@ +<Project> + <!-- Find all DeltaBase items and their Deltas metadata and create a single + DeltaFiles item that includes all of them. Also create a DeltaCount property is the number of Deltas + --> + <Target Name="GetDeltaNames"> + <ItemGroup> + <DeltaFiles Include="@(DeltaBase)"/> + <DeltaFiles Include="%(DeltaBase.Deltas)"/> + <_JustDeltas Include="%(DeltaBase.Deltas)"/> + </ItemGroup> + <PropertyGroup> + <DeltaCount>@(_JustDeltas->Count())</DeltaCount> + </PropertyGroup> + <Message Importance="Low" Text="in GetDeltaNames, @(DeltaFiles)"/> + </Target> + + <!-- Invoke roslynildiff to appl the DeltaFiles to the output assembly to + create the .dmeta, .dil and .dpdb files on the OutputPath --> + <!-- Have to be careful about AfterTargets. If we happen to go after a design-time target, we could get into an infinite loop. --> + <Target Name="CompileDiff" AfterTargets="Build" DependsOnTargets="GetDeltaNames"> + <Message Importance="High" Condition="'$(RoslynILDiffFullPath)' == ''" Text="WARNING: RoslynILDiffFullPath property is unset. Using default which may be wrong on your system" /> + <PropertyGroup> + <RoslynILDiffFullPath Condition="'$(RoslynILDiffFullPath)' == ''">/Users/alklig/work/roslynildiff/roslynildiff</RoslynILDiffFullPath> + <RoslynILDiffArgs>-msbuild:$(MSBuildProjectFullPath)</RoslynILDiffArgs> + <!-- HACK: have to pass config and RID so that this target works for a 'dotnet publish' run. + What other properties do I need to pass? Maybe roslynildiff should just expose an MSBuild task so we can pass everything --> + <RoslynILDiffArgs Condition="'$(Configuration)' != ''">$(RoslynILDiffArgs) -p:Configuration=$(Configuration)</RoslynILDiffArgs> + <RoslynILDiffArgs Condition="'$(RuntimeIdentifier)' != ''">$(RoslynILDiffArgs) -p:RuntimeIdentifier=$(RuntimeIdentifier)</RoslynILDiffArgs> + <RoslynILDiffArgs>$(RoslynILDiffArgs) @(DeltaFiles, ' ')</RoslynILDiffArgs> + </PropertyGroup> + <Exec Command="$(RoslynILDiffFullPath) $(RoslynILDiffArgs)"/> + </Target> + + <!-- Computes the names of the files that roslynildiff will produce, given the name of the base assembly and the + number of deltas --> + <UsingTask TaskName="ComputeDeltaFileOutputNames" TaskFactory="RoslynCodeTaskFactory" + AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" > + <ParameterGroup> + <BaseAssemblyName ParameterType="System.String" Required="true" /> + <DeltaCount ParameterType="System.Int32" Required="true" /> + <DeltaOutputs ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" /> + </ParameterGroup> + <Task> + <!-- <Reference Include=""/> --> + <Using Namespace="System"/> + <Using Namespace="System.IO"/> + <Code Type="Fragment" Language="cs"> +<![CDATA[ +// Display "Hello, world!" +//Log.LogWarning("Hello, world!"); +// Log.LogMessageFromText($"Parameter1: '{Parameter1}'", MessageImportance.High); +int count = DeltaCount; +if (count == 0) { + Log.LogError("Did not expect 0 deltas"); + Success = false; + return Success; +} +string baseAssemblyName = BaseAssemblyName; +ITaskItem[] result = new TaskItem[3*count]; +for (int i = 0; i < count; ++i) { + int rev = 1+i; + string dmeta = baseAssemblyName + $".{rev}.dmeta"; + string dil = baseAssemblyName + $".{rev}.dil"; + string dpdb = baseAssemblyName + $".{rev}.dpdb"; + result[3*i] = new TaskItem(dmeta); + result[3*i+1] = new TaskItem(dil); + result[3*i+2] = new TaskItem(dpdb); +} +DeltaOutputs = result; +]]> + </Code> + </Task> + </UsingTask> + + + <Target Name="ComputeDeltaFileOutputNames"> + <!-- FIXME: is AssemblyName the best property to get the output assembly? --> + <ComputeDeltaFileOutputNames BaseAssemblyName="$(OutputPath)\$(AssemblyName).dll" DeltaCount="$(DeltaCount)"> + <Output TaskParameter="DeltaOutputs" ItemName="_DeltaFileForPublish" /> + </ComputeDeltaFileOutputNames> + <Message Importance="High" Text="Task gave me @(_DeltaFileForPublish)" /> + </Target> + + <Target Name="AddDiffsToPublishList" + AfterTargets="ComputeResolvedFilesToPublishList" + DependsOnTargets="ComputeDeltaFileOutputNames" > + <ItemGroup> + <ResolvedFileToPublish Include="@(_DeltaFileForPublish)"> + <RelativePath>.\%(Filename)%(Extension)</RelativePath> + <CopyToPublishDirectory>always</CopyToPublishDirectory> + </ResolvedFileToPublish> + </ItemGroup> + <Message Importance="Low" Text="XXX ResolvedFileToPublish @(ResolvedFileToPublish)"/> + </Target> + +</Project> diff --git a/src/mono/netcore/sample/mbr/DeltaHelper/DeltaHelper.cs b/src/mono/netcore/sample/mbr/DeltaHelper/DeltaHelper.cs new file mode 100644 index 0000000000000..7188782df8b5e --- /dev/null +++ b/src/mono/netcore/sample/mbr/DeltaHelper/DeltaHelper.cs @@ -0,0 +1,79 @@ +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Loader; +using System.Collections.Generic; + +namespace MonoDelta { + public class DeltaHelper { + const string name = "System.Runtime.CompilerServices.RuntimeFeature"; + + private static MethodBase _updateMethod; + + private static MethodBase UpdateMethod => _updateMethod ?? InitUpdateMethod(); + + private static MethodBase InitUpdateMethod () + { + var monoType = Type.GetType (name, throwOnError: true); + if (monoType == null) + throw new Exception ($"Couldn't get the type {name}"); + _updateMethod = monoType.GetMethod ("LoadMetadataUpdate", BindingFlags.NonPublic | BindingFlags.Static); + if (_updateMethod == null) + throw new Exception ($"Couldn't get LoadMetadataUpdate from {name}"); + return _updateMethod; + } + + private static void LoadMetadataUpdate (Assembly assm, byte[] dmeta_data, byte[] dil_data) + { + UpdateMethod.Invoke (null, new object [] { assm, dmeta_data, dil_data}); + } + + DeltaHelper () { } + + public static DeltaHelper Make () + { + return new DeltaHelper (); + } + + public static void InjectUpdate (string assemblyName, string dmeta_base64, string dil_base64) { + var an = new AssemblyName (assemblyName); + Assembly assm = null; + /* TODO: non-default ALCs */ + foreach (var candidate in AssemblyLoadContext.Default.Assemblies) { + if (candidate.GetName().Name == an.Name) { + assm = candidate; + break; + } + } + if (assm == null) + throw new ArgumentException ("assemblyName"); + var dmeta_data = Convert.FromBase64String (dmeta_base64); + var dil_data = Convert.FromBase64String (dil_base64); + LoadMetadataUpdate (assm, dmeta_data, dil_data); + } + + private Dictionary<Assembly, int> assembly_count = new Dictionary<Assembly, int> (); + + public void Update (Assembly assm) { + int count; + if (!assembly_count.TryGetValue (assm, out count)) + count = 1; + else + count++; + assembly_count [assm] = count; + + /* FIXME WASM: Location is empty on wasm. Make up a name based on Name */ + string basename = assm.Location; + if (basename == "") + basename = assm.GetName().Name + ".dll"; + Console.WriteLine ($"Apply Delta Update for {basename}, revision {count}"); + + string dmeta_name = $"{basename}.{count}.dmeta"; + string dil_name = $"{basename}.{count}.dil"; + byte[] dmeta_data = System.IO.File.ReadAllBytes (dmeta_name); + byte[] dil_data = System.IO.File.ReadAllBytes (dil_name); + + LoadMetadataUpdate (assm, dmeta_data, dil_data); + } + } +} diff --git a/src/mono/netcore/sample/mbr/DeltaHelper/DeltaHelper.csproj b/src/mono/netcore/sample/mbr/DeltaHelper/DeltaHelper.csproj new file mode 100644 index 0000000000000..47bb08e4c66e9 --- /dev/null +++ b/src/mono/netcore/sample/mbr/DeltaHelper/DeltaHelper.csproj @@ -0,0 +1,6 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework> + <EnableTargetingPackDownload>false</EnableTargetingPackDownload> + </PropertyGroup> +</Project> diff --git a/src/mono/netcore/sample/mbr/README.md b/src/mono/netcore/sample/mbr/README.md new file mode 100644 index 0000000000000..c4f860327be7e --- /dev/null +++ b/src/mono/netcore/sample/mbr/README.md @@ -0,0 +1,62 @@ + +# Mono Method Body Replacement Samples + +## Prerequisites + +To run the tests you will need to build a runtime with the Mono metadata update +property set, and you will need the `roslynildiff` commandline tool. + +At this time there is only support for Mac and Linux. No Windows. + +## Building + +Build the runtime with the `/p:MonoMetadataUpdate=true` option. + +Both Debug and Release configurations should work, although mixing a Release +configuration with a Debug runtime configuration (or vice versa) is not +supported. + +For desktop: + +```console +build.sh -s Mono+Libs /p:MonoMetadataUpdate=true +``` + + +For Wasm: + +Make sure `EMSDK_PATH` is set (see [workflow](../../../../../docs/workflow/building/libraries/webassembly-instructions.md)) +```console +build.sh --os browser /p:MonoMetadataUpdate=true +``` + +## Running + +Edit [browser/WasmDelta.csproj](./browser/WasmDelta.csproj) or [console/ConsoleDelta.csproj](./console/ConsoleDelta.csproj) and set the `RoslynILDiffFullPath` property to the path to the `roslynildiff` shell script: + +```xml + <!-- Add this before the Import of DeltaHelper.targets --> + <PropertyGroup> + <RoslynILDiffFullPath>/home/user/tools/roslyn-ildiff/roslynildiff</RoslynILDiffFullPath> + </PropertyGroup> +``` + +(Make sure the configuration is the same as the runtime that you built) + +For console: + +``` +make MONO_CONFIG=Debug publish && make MONO_CONFIG=Debug run +``` + +The output from `run` should print an old string, apply an update and then print a new string + +For wasm: + +``` +make CONFIG=Debug && make CONFIG=Debug run +``` + +Then go to http://localhost:8000/ and click the button once or twice (the example has 2 updates prebuilt) + + diff --git a/src/mono/netcore/sample/mbr/browser/Makefile b/src/mono/netcore/sample/mbr/browser/Makefile new file mode 100644 index 0000000000000..e4c115d70a786 --- /dev/null +++ b/src/mono/netcore/sample/mbr/browser/Makefile @@ -0,0 +1,27 @@ +TOP=../../../../../.. +DOTNET=$(TOP)/dotnet.sh + +ifeq ($(V),) +DOTNET_Q_ARGS=--nologo -v:q -consoleloggerparameters:NoSummary +else +DOTNET_Q_ARGS=--nologo +endif + +CONFIG?=Release + +all: build + +build: + $(DOTNET) build $(DOTNET_Q_ARGS) /p:TargetArchitecture=wasm /p:TargetOS=Browser /p:Configuration=$(CONFIG) WasmDelta.csproj + +clean: + rm -rf bin + +run: + if ! $(DOTNET) tool list --global | grep dotnet-serve; then \ + echo "The tool dotnet-serve could not be found. Install with: $(DOTNET) tool install --global dotnet-serve"; \ + exit 1; \ + else \ + $(DOTNET) serve -d bin/$(CONFIG)/publish -p 8000; \ + fi + diff --git a/src/mono/netcore/sample/mbr/browser/Program.cs b/src/mono/netcore/sample/mbr/browser/Program.cs new file mode 100644 index 0000000000000..9174760b66f0b --- /dev/null +++ b/src/mono/netcore/sample/mbr/browser/Program.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using MonoDelta; + +namespace Sample +{ + public class Test + { + static DeltaHelper replacer = DeltaHelper.Make (); + + public static void Main(string[] args) + { + Console.WriteLine ("Hello, World!"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int TestMeaning() + { + return 42; + } + + public static void Update() + { + Assembly assm = typeof (Test).Assembly; + replacer.Update (assm); + } + } +} diff --git a/src/mono/netcore/sample/mbr/browser/Program_v1.cs b/src/mono/netcore/sample/mbr/browser/Program_v1.cs new file mode 100644 index 0000000000000..02974e4697bfc --- /dev/null +++ b/src/mono/netcore/sample/mbr/browser/Program_v1.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using MonoDelta; + +namespace Sample +{ + public class Test + { + static DeltaHelper replacer = DeltaHelper.Make (); + + public static void Main(string[] args) + { + Console.WriteLine ("Hello, World!"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int TestMeaning() + { + return 43; + } + + public static void Update() + { + Assembly assm = typeof (Test).Assembly; + replacer.Update (assm); + } + } +} diff --git a/src/mono/netcore/sample/mbr/browser/Program_v2.cs b/src/mono/netcore/sample/mbr/browser/Program_v2.cs new file mode 100644 index 0000000000000..98eec23781796 --- /dev/null +++ b/src/mono/netcore/sample/mbr/browser/Program_v2.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using MonoDelta; + +namespace Sample +{ + public class Test + { + static DeltaHelper replacer = DeltaHelper.Make (); + + public static void Main(string[] args) + { + Console.WriteLine ("Hello, World!"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int TestMeaning() + { + return 128; + } + + public static void Update() + { + Assembly assm = typeof (Test).Assembly; + replacer.Update (assm); + } + } +} diff --git a/src/mono/netcore/sample/mbr/browser/WasmDelta.csproj b/src/mono/netcore/sample/mbr/browser/WasmDelta.csproj new file mode 100644 index 0000000000000..7440308f883ed --- /dev/null +++ b/src/mono/netcore/sample/mbr/browser/WasmDelta.csproj @@ -0,0 +1,65 @@ +<Project Sdk="Microsoft.NET.Sdk" DefaultTargets="WasmBuildApp"> + <PropertyGroup> + <OutputType>Exe</OutputType> + <OutputPath>bin</OutputPath> + <EnableDefaultCompileItems>false</EnableDefaultCompileItems> + <TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework> + <TargetArchitecture>wasm</TargetArchitecture> + <TargetOS>Browser</TargetOS> + <MicrosoftNetCoreAppRuntimePackRidDir>$(ArtifactsBinDir)microsoft.netcore.app.runtime.browser-wasm\$(Configuration)\runtimes\browser-wasm\</MicrosoftNetCoreAppRuntimePackRidDir> + <BuildDir>$(MSBuildThisFileDirectory)obj\$(Configuration)\wasm</BuildDir> + <AppDir>$(MSBuildThisFileDirectory)bin\$(Configuration)\publish</AppDir> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\DeltaHelper\DeltaHelper.csproj" /> + </ItemGroup> + + + <Target Name="PrepareForWasmBuild" DependsOnTargets="RebuildWasmAppBuilder;Build" BeforeTargets="WasmBuildApp"> + <PropertyGroup> + <WasmAppDir>$(AppDir)</WasmAppDir> + <WasmMainAssemblyPath>bin\WasmDelta.dll</WasmMainAssemblyPath> + <WasmMainJSPath>runtime.js</WasmMainJSPath> + + <WasmResolveAssembliesBeforeBuild>true</WasmResolveAssembliesBeforeBuild> + </PropertyGroup> + <ItemGroup> + <WasmAssembliesToBundle Include="$(MicrosoftNetCoreAppRuntimePackRidDir)lib\$(NetCoreAppCurrent)\System.Private.Runtime.InteropServices.JavaScript.dll" /> + + <WasmAssemblySearchPaths Include="bin"/> + <WasmAssemblySearchPaths Include="$(MicrosoftNetCoreAppRuntimePackRidDir)native"/> + <WasmAssemblySearchPaths Include="$(MicrosoftNetCoreAppRuntimePackRidDir)lib\$(NetCoreAppCurrent)"/> + </ItemGroup> + </Target> + + <Target Name="PrepareDeltasForWasmApp" DependsOnTargets="Build;CompileDiff;ComputeDeltaFileOutputNames" BeforeTargets="WasmBuildApp"> + <ItemGroup> + <WasmFilesToIncludeInFileSystem Include="@(_DeltaFileForPublish)"> + <TargetPath>\%(_DeltaFileForPublish.Filename)%(_DeltaFileForPublish.Extension)</TargetPath> + </WasmFilesToIncludeInFileSystem> + </ItemGroup> + </Target> + + <ItemGroup> + <Compile Include="Program.cs" /> + <Content Include="index.html"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> + </ItemGroup> + + <ItemGroup> + <DeltaBase Include="Program.cs"> + <Deltas>Program_v1.cs;Program_v2.cs</Deltas> + </DeltaBase> + </ItemGroup> + + <Target Name="AfterWasmBuildApp" AfterTargets="WasmBuildApp"> + <Copy SourceFiles="bin\index.html" DestinationFolder="$(AppDir)" /> + </Target> + + <Import Project="$(MonoProjectRoot)\wasm\build\WasmApp.targets" /> + + <!-- Set RoslynILDiffFullPath property to the path of roslynildiff --> + <Import Project="..\DeltaHelper.targets" /> +</Project> diff --git a/src/mono/netcore/sample/mbr/browser/index.html b/src/mono/netcore/sample/mbr/browser/index.html new file mode 100644 index 0000000000000..472cf5f29ea05 --- /dev/null +++ b/src/mono/netcore/sample/mbr/browser/index.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<!-- Licensed to the .NET Foundation under one or more agreements. --> +<!-- The .NET Foundation licenses this file to you under the MIT license. --> +<html> + <head> + <title>TESTS</title> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + </head> + <body> + <h3 id="header">Wasm Browser Sample</h3> + Result from Sample.Test.TestMeaning: <span id="out"></span> + <div> + Click here (upto 2 times): <button id="update">Update</button> + </div> + <script type='text/javascript'> + var App = { + init: function () { + var outElement = document.getElementById("out"); + document.getElementById("update").addEventListener("click", function () { + BINDING.call_static_method("[WasmDelta] Sample.Test:Update", []); + console.log ("applied update"); + var ret = BINDING.call_static_method("[WasmDelta] Sample.Test:TestMeaning", []); + outElement.innerHTML = ret; + }) + var ret = BINDING.call_static_method("[WasmDelta] Sample.Test:TestMeaning", []); + outElement.innerHTML = ret; + console.log ("ready"); + }, + }; + </script> + <script type="text/javascript" src="mono-config.js"></script> + <script type="text/javascript" src="runtime.js"></script> + + <script defer src="dotnet.js"></script> + + + </body> +</html> diff --git a/src/mono/netcore/sample/mbr/browser/runtime.js b/src/mono/netcore/sample/mbr/browser/runtime.js new file mode 100644 index 0000000000000..7db3cb4fc70e7 --- /dev/null +++ b/src/mono/netcore/sample/mbr/browser/runtime.js @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +var Module = { + onRuntimeInitialized: function () { + config.loaded_cb = function () { + App.init (); + }; + config.fetch_file_cb = function (asset) { + return fetch (asset, { credentials: 'same-origin' }); + } + + MONO.mono_load_runtime_and_bcl_args (config); + }, +}; diff --git a/src/mono/netcore/sample/mbr/console/ConsoleDelta.csproj b/src/mono/netcore/sample/mbr/console/ConsoleDelta.csproj new file mode 100644 index 0000000000000..d807eff508bdc --- /dev/null +++ b/src/mono/netcore/sample/mbr/console/ConsoleDelta.csproj @@ -0,0 +1,39 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework> + <EnableTargetingPackDownload>false</EnableTargetingPackDownload> + <EnableDefaultCompileItems>false</EnableDefaultCompileItems> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\DeltaHelper\DeltaHelper.csproj" /> + </ItemGroup> + + <ItemGroup> + <Compile Include="Program.cs" /> + <Compile Include="TestClass.cs" /> + </ItemGroup> + + + <ItemGroup> + <DeltaBase Include="TestClass.cs"> + <Deltas>TestClass_v1.cs</Deltas> + </DeltaBase> + </ItemGroup> + + <!-- Redirect 'dotnet publish' to in-tree runtime pack --> + <Target Name="TrickRuntimePackLocation" AfterTargets="ProcessFrameworkReferences"> + <ItemGroup> + <RuntimePack> + <PackageDirectory>$(ArtifactsBinDir)microsoft.netcore.app.runtime.$(RuntimeIdentifier)\$(Configuration)</PackageDirectory> + </RuntimePack> + </ItemGroup> + <Message Text="Packaged ID: %(RuntimePack.PackageDirectory)" Importance="high" /> + </Target> + + + <!-- Set RoslynILDiffFullPath property to the path of roslynildiff --> + <Import Project="..\DeltaHelper.targets" /> + +</Project> diff --git a/src/mono/netcore/sample/mbr/console/Makefile b/src/mono/netcore/sample/mbr/console/Makefile new file mode 100644 index 0000000000000..359522a516392 --- /dev/null +++ b/src/mono/netcore/sample/mbr/console/Makefile @@ -0,0 +1,26 @@ +TOP=../../../../../../ +DOTNET:=$(TOP)./dotnet.sh +DOTNET_Q_ARGS=--nologo -v:q -consoleloggerparameters:NoSummary + +CONFIG ?=Release +MONO_ARCH=x64 + +OS := $(shell uname -s) +ifeq ($(OS),Darwin) + TARGET_OS=osx +else + TARGET_OS=linux +endif + +MONO_ENV_OPTIONS ?=--interp=-inline + +publish: + $(DOTNET) publish -c $(CONFIG) -r $(TARGET_OS)-$(MONO_ARCH) + +run: publish + COMPlus_DebugWriteToStdErr=1 \ + MONO_ENV_OPTIONS="$(MONO_ENV_OPTIONS)" \ + $(TOP)artifacts/bin/ConsoleDelta/$(MONO_ARCH)/$(CONFIG)/$(TARGET_OS)-$(MONO_ARCH)/publish/ConsoleDelta + +clean: + rm -rf $(TOP)artifacts/bin/HelloWorld/ diff --git a/src/mono/netcore/sample/mbr/console/Program.cs b/src/mono/netcore/sample/mbr/console/Program.cs new file mode 100644 index 0000000000000..01fba0d8e4ee1 --- /dev/null +++ b/src/mono/netcore/sample/mbr/console/Program.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using MonoDelta; + +namespace HelloWorld +{ + internal class Program + { + class State { + public readonly ManualResetEventSlim mreIn; + public readonly ManualResetEventSlim mreOut; + public readonly ManualResetEventSlim mreBusy; + public string res; + private volatile bool busyChanged; + + public State() { + mreIn = new ManualResetEventSlim (); + mreOut = new ManualResetEventSlim (); + mreBusy = new ManualResetEventSlim (); + res = ""; + busyChanged = false; + } + + + public bool BusyChanged {get => busyChanged ; set { busyChanged = value; mreBusy.Set ();} } + + public void WaitForBusy () { + mreBusy.Wait (); + mreBusy.Reset (); + } + + public string ConsumerStep () { + mreIn.Set (); + mreOut.Wait (); + mreOut.Reset (); + return res; + } + + public void ProducerStep (Func<string> step) { + mreIn.Wait (); + mreIn.Reset (); + res = step (); + mreOut.Set (); + } + } + + private static int Main(string[] args) + { + bool isMono = typeof(object).Assembly.GetType("Mono.RuntimeStructs") != null; + Console.WriteLine($"Hello World {(isMono ? "from Mono!" : "from CoreCLR!")}"); + Console.WriteLine(typeof(object).Assembly.FullName); + Console.WriteLine(System.Reflection.Assembly.GetEntryAssembly ()); + Console.WriteLine(System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription); + + Assembly assm = typeof (TestClass).Assembly; + var replacer = DeltaHelper.Make (); + + var st = new State (); + var t = new Thread (MutatorThread); + t.Start (st); + var t2 = new Thread (BusyThread) { IsBackground = true }; + t2.Start (st); + + string res = st.ConsumerStep (); + if (res != "OLD STRING") + return 1; + + replacer.Update (assm); + + res = st.ConsumerStep (); + if (res != "NEW STRING") + return 2; + + st.WaitForBusy (); + Console.WriteLine ("BusyChanged: {0}", st.BusyChanged); + + return 0; + } + + private static void MutatorThread (object o) + { + var st = (State)o; + static string Step () => TestClass.TargetMethod (); + st.ProducerStep (Step); + st.ProducerStep (Step); + } + + // This method is not affected by the update, but it calls the target + // method which is. Still we expect to see "BusyThread" and its + // callees show up in the trace output when it safepoints during an + // update. + private static void BusyThread (object o) + { + State st = (State)o; + string prev = TestClass.TargetMethod (); + while (true) { + Thread.Sleep (0); + for (int i = 0; i < 5000; ++i) { + if (i % 1000 == 0) { + string cur = TestClass.TargetMethod (); + if (cur != prev) { + st.BusyChanged = true; + prev = cur; + } + } + } + } + } + + } +} + diff --git a/src/mono/netcore/sample/mbr/console/TestClass.cs b/src/mono/netcore/sample/mbr/console/TestClass.cs new file mode 100644 index 0000000000000..c18db33b47257 --- /dev/null +++ b/src/mono/netcore/sample/mbr/console/TestClass.cs @@ -0,0 +1,11 @@ +using System; +using System.Runtime.CompilerServices; + +public class TestClass { + [MethodImpl(MethodImplOptions.NoInlining)] + public static string TargetMethod () { + string s = "OLD STRING"; + Console.WriteLine (s); + return s; + } +} diff --git a/src/mono/netcore/sample/mbr/console/TestClass_v1.cs b/src/mono/netcore/sample/mbr/console/TestClass_v1.cs new file mode 100644 index 0000000000000..a41793c3f7dcc --- /dev/null +++ b/src/mono/netcore/sample/mbr/console/TestClass_v1.cs @@ -0,0 +1,11 @@ +using System; +using System.Runtime.CompilerServices; + +public class TestClass { + [MethodImpl(MethodImplOptions.NoInlining)] + public static string TargetMethod () { + string s = "NEW STRING"; + Console.WriteLine (s); + return s; + } +} diff --git a/src/mono/wasm/Makefile b/src/mono/wasm/Makefile index a1f1752d1f1e1..f07aedab6d27f 100644 --- a/src/mono/wasm/Makefile +++ b/src/mono/wasm/Makefile @@ -21,6 +21,7 @@ ICU_LIBDIR?= SYSTEM_NATIVE_LIBDIR?=$(TOP)/src/libraries/Native/Unix/System.Native ENABLE_ES6?=false _MSBUILD_WASM_BUILD_ARGS=/p:TargetOS=Browser /p:TargetArchitecture=wasm /p:Configuration=$(CONFIG) +ENABLE_METADATA_UPDATE?=false all: build-native icu-files source-files header-files @@ -63,6 +64,9 @@ EMCC_FLAGS=--profiling-funcs -s ALLOW_MEMORY_GROWTH=1 -s NO_EXIT_RUNTIME=1 -s US ifneq ($(ENABLE_ES6),false) EMCC_FLAGS+=-s MODULARIZE=1 -s EXPORT_ES6=1 endif +ifeq ($(ENABLE_METADATA_UPDATE),true) + EMCC_FLAGS+=-DENABLE_METADATA_UPDATE=1 +endif EMCC_DEBUG_FLAGS =-g -Os -s ASSERTIONS=1 -DENABLE_NETCORE=1 -DDEBUG=1 EMCC_RELEASE_FLAGS=-Oz --llvm-opts 2 -DENABLE_NETCORE=1 diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index 1e66fcd913aa3..ab7895f0eb3be 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -466,6 +466,9 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_runtime (const char *unused, int debug_level) { const char *interp_opts = ""; +#ifdef ENABLE_METADATA_UPDATE + interp_opts = "-inline"; /* FIXME: EnC hack - make this configurable */ +#endif #ifdef DEBUG monoeg_g_setenv ("MONO_LOG_LEVEL", "debug", 0); diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 1993f1d252f08..092c10cbf17ec 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -13,6 +13,7 @@ <MicrosoftNetCoreAppRuntimePackNativeDir>$([MSBuild]::NormalizeDirectory('$(MicrosoftNetCoreAppRuntimePackRidDir)', 'native'))</MicrosoftNetCoreAppRuntimePackNativeDir> <WasmEnableES6 Condition="'$(WasmEnableES6)' == ''">false</WasmEnableES6> <FilterSystemTimeZones Condition="'$(FilterSystemTimeZones)' == ''">false</FilterSystemTimeZones> + <WasmEnableMetadataUpdate Condition="'$(MonoMetadataUpdate)' == 'true'">true</WasmEnableMetadataUpdate> </PropertyGroup> <Target Name="CheckEnv"> @@ -71,7 +72,7 @@ <_IcuLibdir>$(PkgMicrosoft_NETCore_Runtime_ICU_Transport)/runtimes/browser-wasm/native/lib</_IcuLibdir> </PropertyGroup> - <Exec Command="make -C $(MonoProjectRoot)wasm all SHELL=/bin/bash BINDIR=$(ArtifactsBinDir) MONO_BIN_DIR=$(MonoArtifactsPath) OBJDIR=$(ArtifactsObjDir) NATIVE_BIN_DIR=$(NativeBinDir) CONFIG=$(Configuration) PINVOKE_TABLE=$(WasmPInvokeTablePath) ICU_LIBDIR=$(_IcuLibdir) ENABLE_ES6=$(WasmEnableES6)" + <Exec Command="make -C $(MonoProjectRoot)wasm all SHELL=/bin/bash BINDIR=$(ArtifactsBinDir) MONO_BIN_DIR=$(MonoArtifactsPath) OBJDIR=$(ArtifactsObjDir) NATIVE_BIN_DIR=$(NativeBinDir) CONFIG=$(Configuration) PINVOKE_TABLE=$(WasmPInvokeTablePath) ICU_LIBDIR=$(_IcuLibdir) ENABLE_ES6=$(WasmEnableES6) ENABLE_METADATA_UPDATE=$(WasmEnableMetadataUpdate)" IgnoreStandardErrorWarningFormat="true" /> <ItemGroup>