From f014deb7613cd932a1f84de6b431e8147e838e56 Mon Sep 17 00:00:00 2001 From: lambdageek Date: Sat, 9 Jan 2021 14:42:33 +0000 Subject: [PATCH] [Mono] Initial metadata update support First part of support for metadata updates. Contributes to https://github.com/dotnet/runtime/issues/44806 The feature is off by default. To enable, build with `/p:MonoMetadataUpdate=true` (e.g. `./build.sh --os browser /p:MonoMetadataUpdate=true`). There are samples in `src/mono/netcore/sample/mbr` (see the README - the samples aren't completely standalone and need some external tooling to build) for console (only tested on Mac and Linux) and wasm. There's a demo at https://lambdageek.dev/dl0/ --- mono/cil/tables.def | 13 + mono/metadata/Makefile.am | 2 + mono/metadata/class.c | 2 +- mono/metadata/domain.c | 7 +- mono/metadata/icall-decl.h | 4 + mono/metadata/icall-def-netcore.h | 5 + mono/metadata/icall.c | 18 + mono/metadata/image.c | 80 ++- mono/metadata/loader.c | 50 +- mono/metadata/metadata-internals.h | 62 ++ mono/metadata/metadata-update.c | 974 ++++++++++++++++++++++++++++ mono/metadata/metadata-update.h | 50 ++ mono/metadata/metadata.c | 190 +++++- mono/metadata/metadata.h | 2 + mono/metadata/object-internals.h | 4 + mono/metadata/threads.c | 5 + mono/mini/ee.h | 1 + mono/mini/interp-stubs.c | 5 + mono/mini/interp/interp-internals.h | 5 + mono/mini/interp/interp.c | 103 ++- mono/mini/interp/transform.c | 5 + mono/mini/mini-runtime.c | 23 + mono/utils/mono-logger-internals.h | 2 +- mono/utils/mono-logger.c | 2 +- 24 files changed, 1589 insertions(+), 25 deletions(-) create mode 100644 mono/metadata/metadata-update.c create mode 100644 mono/metadata/metadata-update.h diff --git a/mono/cil/tables.def b/mono/cil/tables.def index d28447a1ada4..1a2888fb0558 100644 --- a/mono/cil/tables.def +++ b/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/mono/metadata/Makefile.am b/mono/metadata/Makefile.am index 26c0b992aeed..45392bbed42a 100644 --- a/mono/metadata/Makefile.am +++ b/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/mono/metadata/class.c b/mono/metadata/class.c index 02406e46e76a..e52274df2f00 100644 --- a/mono/metadata/class.c +++ b/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/mono/metadata/domain.c b/mono/metadata/domain.c index 073f86d77751..4af4b21702d6 100644 --- a/mono/metadata/domain.c +++ b/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/mono/metadata/icall-decl.h b/mono/metadata/icall-decl.h index 909621cf44dc..16b5278bb42e 100644 --- a/mono/metadata/icall-decl.h +++ b/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/mono/metadata/icall-def-netcore.h b/mono/metadata/icall-def-netcore.h index 0a6e7c6debd3..2939685c312b 100644 --- a/mono/metadata/icall-def-netcore.h +++ b/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/mono/metadata/icall.c b/mono/metadata/icall.c index 1a5e4e57a34d..c9293be7aade 100644 --- a/mono/metadata/icall.c +++ b/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/mono/metadata/image.c b/mono/metadata/image.c index fe5ec7d9eb10..757324eabd44 100644 --- a/mono/metadata/image.c +++ b/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 #include #include +#include #include #include #include @@ -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/mono/metadata/loader.c b/mono/metadata/loader.c index 1d5405e14bf4..6d45ea9a751e 100644 --- a/mono/metadata/loader.c +++ b/mono/metadata/loader.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -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/mono/metadata/metadata-internals.h b/mono/metadata/metadata-internals.h index 329f839f7fc3..ce785758b979 100644 --- a/mono/metadata/metadata-internals.h +++ b/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/mono/metadata/metadata-update.c b/mono/metadata/metadata-update.c new file mode 100644 index 000000000000..e6cb5f47a0c6 --- /dev/null +++ b/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 +#include "mono/utils/mono-compiler.h" + +#ifdef ENABLE_METADATA_UPDATE + +#include +#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 = ""; + if (!nspace) + nspace = ""; + + 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/mono/metadata/metadata-update.h b/mono/metadata/metadata-update.h new file mode 100644 index 000000000000..035d322ed145 --- /dev/null +++ b/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/mono/metadata/metadata.c b/mono/metadata/metadata.c index 73a6978c988e..bf5d119541bf 100644 --- a/mono/metadata/metadata.c +++ b/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/mono/metadata/metadata.h b/mono/metadata/metadata.h index c44931d7328d..7870adbdf88b 100644 --- a/mono/metadata/metadata.h +++ b/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/mono/metadata/object-internals.h b/mono/metadata/object-internals.h index db47b0825da7..af9514d2ccb7 100644 --- a/mono/metadata/object-internals.h +++ b/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/mono/metadata/threads.c b/mono/metadata/threads.c index 4bb057982394..755cfb0f07f5 100644 --- a/mono/metadata/threads.c +++ b/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/mono/mini/ee.h b/mono/mini/ee.h index a74b444e95fd..110e9be69473 100644 --- a/mono/mini/ee.h +++ b/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/mono/mini/interp-stubs.c b/mono/mini/interp-stubs.c index 0c06044553cf..1179bd1dd3e1 100644 --- a/mono/mini/interp-stubs.c +++ b/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/mono/mini/interp/interp-internals.h b/mono/mini/interp/interp-internals.h index 6e240756a2ec..5498da132faa 100644 --- a/mono/mini/interp/interp-internals.h +++ b/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/mono/mini/interp/interp.c b/mono/mini/interp/interp.c index f4075f2a13b5..2c7a77e3e05c 100644 --- a/mono/mini/interp/interp.c +++ b/mono/mini/interp/interp.c @@ -25,7 +25,9 @@ #include #include #include +#include #include +#include #include #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/mono/mini/interp/transform.c b/mono/mini/interp/transform.c index 1994437f0da0..3bb523930478 100644 --- a/mono/mini/interp/transform.c +++ b/mono/mini/interp/transform.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -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/mono/mini/mini-runtime.c b/mono/mini/mini-runtime.c index 2910d1a1659f..21f21ee15f48 100644 --- a/mono/mini/mini-runtime.c +++ b/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/mono/utils/mono-logger-internals.h b/mono/utils/mono-logger-internals.h index ad8555fa527c..9a2406adc050 100644 --- a/mono/utils/mono-logger-internals.h +++ b/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/mono/utils/mono-logger.c b/mono/utils/mono-logger.c index 8d2d2f8ce13c..6f5cae1e6dd1 100644 --- a/mono/utils/mono-logger.c +++ b/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;