diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index 514b61cc1b91ab..4592ec4e194736 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -232,6 +232,10 @@ Include="$(LibrariesNativeArtifactsPath)src\es6\*.js" NativeSubDirectory="src\es6" IsNative="true" /> + + @@ -252,6 +253,7 @@ + diff --git a/src/mono/browser/browser.proj b/src/mono/browser/browser.proj index 759fa68ae54746..dac794923f61af 100644 --- a/src/mono/browser/browser.proj +++ b/src/mono/browser/browser.proj @@ -445,9 +445,14 @@ DestinationFolder="$(MonoObjDir)" SkipUnchangedFiles="true" /> + + + + diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets index 3b5683433fe862..458d8609cacdf8 100644 --- a/src/mono/browser/build/BrowserWasmApp.targets +++ b/src/mono/browser/build/BrowserWasmApp.targets @@ -83,6 +83,7 @@ + @@ -335,6 +336,8 @@ Dependencies="$(_WasmPInvokeHPath);$(_WasmPInvokeTablePath)" /> <_WasmRuntimePackSrcFile Include="$(_WasmRuntimePackSrcDir)driver.c" Dependencies="" /> + <_WasmRuntimePackSrcFile Include="$(_WasmRuntimePackSrcDir)heapshot.c" + Dependencies="" /> <_WasmRuntimePackSrcFile Include="$(_WasmRuntimePackSrcDir)runtime.c" Dependencies="@(_RuntimeCDependencies)" /> <_WasmRuntimePackSrcFile Include="$(_WasmRuntimePackSrcDir)corebindings.c" /> diff --git a/src/mono/browser/runtime/CMakeLists.txt b/src/mono/browser/runtime/CMakeLists.txt index 453fa40457b6da..dc56449c34bcee 100644 --- a/src/mono/browser/runtime/CMakeLists.txt +++ b/src/mono/browser/runtime/CMakeLists.txt @@ -6,7 +6,7 @@ option(DISABLE_THREADS "defined if the build does NOT support multithreading" ON option(ENABLE_JS_INTEROP_BY_VALUE "defined when JS interop without pointers to managed objects" OFF) set(CMAKE_EXECUTABLE_SUFFIX ".js") -add_executable(dotnet.native runtime.c corebindings.c driver.c pinvoke.c) +add_executable(dotnet.native runtime.c corebindings.c driver.c heapshot.c pinvoke.c) target_include_directories(dotnet.native PUBLIC ${MONO_INCLUDES} ${MONO_OBJ_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR}/include/wasm) target_compile_options(dotnet.native PUBLIC @${NATIVE_BIN_DIR}/src/emcc-default.rsp @${NATIVE_BIN_DIR}/src/emcc-compile.rsp -DGEN_PINVOKE=1 ${CONFIGURATION_COMPILE_OPTIONS}) diff --git a/src/mono/browser/runtime/cwraps.ts b/src/mono/browser/runtime/cwraps.ts index 2819e0c44b7f13..b0de239684e786 100644 --- a/src/mono/browser/runtime/cwraps.ts +++ b/src/mono/browser/runtime/cwraps.ts @@ -85,6 +85,7 @@ const fn_signatures: SigLine[] = [ [true, "mono_wasm_get_f32_unaligned", "number", ["number"]], [true, "mono_wasm_get_f64_unaligned", "number", ["number"]], [true, "mono_wasm_read_as_bool_or_null_unsafe", "number", ["number"]], + [true, "mono_wasm_perform_heapshot", "void", ["number"]], // jiterpreter [true, "mono_jiterp_trace_bailout", "void", ["number"]], @@ -211,6 +212,8 @@ export interface t_Cwraps { mono_wasm_get_f64_unaligned(source: VoidPtr): number; mono_wasm_read_as_bool_or_null_unsafe(obj: MonoObject): number; + mono_wasm_perform_heapshot(full: number): void; + mono_jiterp_trace_bailout(reason: number): void; mono_jiterp_get_trace_bailout_count(reason: number): number; mono_jiterp_value_copy(destination: VoidPtr, source: VoidPtr, klass: MonoClass): void; diff --git a/src/mono/browser/runtime/driver.c b/src/mono/browser/runtime/driver.c index 1b3663e4f3f3ca..00056a3b43a1dc 100644 --- a/src/mono/browser/runtime/driver.c +++ b/src/mono/browser/runtime/driver.c @@ -180,6 +180,9 @@ cleanup_runtime_config (MonovmRuntimeConfigArguments *args, void *user_data) free (user_data); } +void +mono_wasm_heapshot_initialize (void); + EMSCRIPTEN_KEEPALIVE void mono_wasm_load_runtime (int debug_level) { @@ -225,6 +228,8 @@ mono_wasm_load_runtime (int debug_level) root_domain = mono_wasm_load_runtime_common (debug_level, wasm_trace_logger, interp_opts); bindings_initialize_internals(); + + mono_wasm_heapshot_initialize(); } EMSCRIPTEN_KEEPALIVE void @@ -558,4 +563,4 @@ mono_wasm_read_as_bool_or_null_unsafe (PVOLATILE(MonoObject) obj) { end: MONO_EXIT_GC_UNSAFE; return result; -} \ No newline at end of file +} diff --git a/src/mono/browser/runtime/exports-binding.ts b/src/mono/browser/runtime/exports-binding.ts index 6ae7a08b95b0b8..d62af17b9435c2 100644 --- a/src/mono/browser/runtime/exports-binding.ts +++ b/src/mono/browser/runtime/exports-binding.ts @@ -17,6 +17,11 @@ import { mono_wasm_diagnostic_server_on_runtime_server_init, mono_wasm_event_pip import { mono_wasm_diagnostic_server_stream_signal_work_available } from "./diagnostics/server_pthread/stream-queue"; import { mono_log_warn, mono_wasm_console_clear, mono_wasm_trace_logger } from "./logging"; import { mono_wasm_profiler_leave, mono_wasm_profiler_enter } from "./profiler"; +import { + mono_wasm_heapshot_start, mono_wasm_heapshot_end, mono_wasm_heapshot_assembly, + mono_wasm_heapshot_class, mono_wasm_heapshot_object, mono_wasm_heapshot_gchandle, + mono_wasm_heapshot_roots, mono_wasm_heapshot_stats, mono_wasm_heapshot_counter +} from "./heapshot"; import { mono_wasm_browser_entropy } from "./crypto"; import { mono_wasm_cancel_promise } from "./cancelable-promise"; @@ -85,6 +90,17 @@ export const mono_wasm_imports = [ mono_wasm_set_entrypoint_breakpoint, mono_wasm_event_pipe_early_startup_callback, + // heapshot.c + mono_wasm_heapshot_start, + mono_wasm_heapshot_assembly, + mono_wasm_heapshot_class, + mono_wasm_heapshot_object, + mono_wasm_heapshot_gchandle, + mono_wasm_heapshot_roots, + mono_wasm_heapshot_stats, + mono_wasm_heapshot_counter, + mono_wasm_heapshot_end, + // src/native/minipal/random.c mono_wasm_browser_entropy, diff --git a/src/mono/browser/runtime/gc-handles.ts b/src/mono/browser/runtime/gc-handles.ts index 30e3f30271451b..4560ce81eb4ecf 100644 --- a/src/mono/browser/runtime/gc-handles.ts +++ b/src/mono/browser/runtime/gc-handles.ts @@ -205,6 +205,15 @@ export function assertNoProxies (): void { let force_dispose_proxies_in_progress = false; +export function enumerateProxies (csObject: Function, jsObject: Function): void { + for (const tup of _js_owned_object_table) + csObject(tup[0], tup[1] ? tup[1].deref() : null); + for (let js_handle = 0; js_handle < _cs_owned_objects_by_js_handle.length; js_handle++) + jsObject(js_handle, _cs_owned_objects_by_js_handle[js_handle]); + for (let jsv_handle = 0; jsv_handle < _cs_owned_objects_by_jsv_handle.length; jsv_handle++) + jsObject(jsv_handle, _cs_owned_objects_by_jsv_handle[jsv_handle]); +} + // when we arrive here from UninstallWebWorkerInterop, the C# will unregister the handles too. // when called from elsewhere, C# side could be unbalanced!! export function forceDisposeProxies (disposeMethods: boolean, verbose: boolean): void { diff --git a/src/mono/browser/runtime/heapshot.c b/src/mono/browser/runtime/heapshot.c new file mode 100644 index 00000000000000..fe439ebe6c7d6b --- /dev/null +++ b/src/mono/browser/runtime/heapshot.c @@ -0,0 +1,337 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// FIXME: unavailable in emscripten +// #include + +#include + +#include +#include +#include +#include +#include + +#include "wasm-config.h" +#include "runtime.h" + +#include "gc-common.h" + +static void * parachute = NULL; + +void +mono_wasm_heapshot_initialize (void); + +int +mono_assembly_update_heapshot_scratch_byte (MonoAssembly *assembly, char new_value); + +int +mono_class_update_heapshot_scratch_byte (MonoClass *klass, char new_value); + +typedef enum { + HANDLE_TYPE_MIN = 0, + HANDLE_WEAK = HANDLE_TYPE_MIN, + HANDLE_WEAK_TRACK, + HANDLE_NORMAL, + HANDLE_PINNED, + HANDLE_WEAK_FIELDS, + HANDLE_TYPE_MAX +} GCHandleType; + +enum { + ROOT_TYPE_NORMAL = 0, /* "normal" roots */ + ROOT_TYPE_PINNED = 1, /* roots without a GC descriptor */ + ROOT_TYPE_WBARRIER = 2, /* roots with a write barrier */ + ROOT_TYPE_NUM +}; + +struct _MonoProfiler { +}; + +static char next_heapshot_scratch_byte = 1; +static MonoProfiler heapshot_profiler; +static MonoProfilerHandle heapshot_profiler_handle = NULL; + +extern void +mono_wasm_heapshot_start (int full); +extern void +mono_wasm_heapshot_assembly ( + MonoAssembly *assembly, const char *name +); +extern void +mono_wasm_heapshot_class ( + MonoClass *klass, MonoClass *element_klass, MonoClass *nesting_klass, MonoAssembly *assembly, + const char *namespace, const char *name, int rank, + int kind, int gparam_count, MonoClass **gparams +); +extern void +mono_wasm_heapshot_object ( + MonoObject *obj, MonoClass *klass, uint32_t size, uint32_t ref_count, MonoObject **refs +); +extern void +mono_wasm_heapshot_gchandle (MonoObject *obj, int handle_type); +extern void +mono_wasm_heapshot_roots ( + const char *kind, int count, + const uint8_t *const *addresses, + MonoObject *const *objects +); +extern void +mono_wasm_heapshot_stats ( + int in_use_pages, int free_pages, int external_pages, int largest_free_chunk, + int sgen_los_size, int sgen_heap_capacity +); +extern void +mono_wasm_heapshot_counter ( + const char *name, double value +); +extern void +mono_wasm_heapshot_end (int full); + +void +mwpm_compute_stats (uint32_t *in_use_pages, uint32_t *free_pages, uint32_t *external_pages, uint32_t *largest_free_chunk); + +typedef void * (*SgenGCHandleIterateCallback) (void *hidden, GCHandleType handle_type, int max_generation, void *user); + +size_t +sgen_gc_get_total_heap_allocation (void); + +void +sgen_gchandle_iterate (GCHandleType handle_type, int max_generation, SgenGCHandleIterateCallback callback, void *user); + +void * +sgen_try_reveal_pointer (void *hidden, GCHandleType handle_type); + +typedef void (*SgenRootIterateCallback) (void *start, void *end, MonoGCRootSource source, int root_type, const char *msg, void *user); + +void +sgen_registered_root_iterate (SgenRootIterateCallback callback, void *user_data, int root_type); + +#define MALLINFO_FIELD_TYPE size_t + +struct mallinfo { + MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */ + MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */ + MALLINFO_FIELD_TYPE smblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */ + MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */ + MALLINFO_FIELD_TYPE fsmblks; /* always 0 */ + MALLINFO_FIELD_TYPE uordblks; /* total allocated space */ + MALLINFO_FIELD_TYPE fordblks; /* total free space */ + MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */ +}; + +struct mallinfo +mallinfo (void); + +extern uint32_t sgen_los_memory_usage, sgen_los_memory_usage_total; + +void +mono_wasm_heapshot_initialize (void) { + parachute = malloc (1024 * 64 * 10); +} + +static void +mono_wasm_on_gc_assembly (MonoAssembly *assembly) { + MonoAssemblyName *aname = mono_assembly_get_name (assembly); + mono_wasm_heapshot_assembly (assembly, mono_assembly_name_get_name (aname)); +} + +static void +mono_wasm_on_gc_class (MonoClass *klass) { + MonoImage *image = mono_class_get_image (klass); + MonoAssembly *assem = mono_image_get_assembly (image); + if (mono_assembly_update_heapshot_scratch_byte (assem, next_heapshot_scratch_byte)) + mono_wasm_on_gc_assembly (assem); + + MonoClass *info_klass = klass; + char namespace_buffer[1024]; + // If we're looking at an array, examine the element type instead and specify the rank + int rank = mono_class_get_rank (klass); + if (rank > 0) { + info_klass = mono_class_get_element_class (klass); + if (mono_class_update_heapshot_scratch_byte (info_klass, next_heapshot_scratch_byte)) + mono_wasm_on_gc_class (info_klass); + } + + MonoClass *gparams[64]; + int gparam_count = mono_class_get_generic_params (info_klass, gparams, sizeof(gparams) / sizeof(gparams[0])); + for (int i = 0; i < gparam_count; i++) { + if (mono_class_update_heapshot_scratch_byte (gparams[i], next_heapshot_scratch_byte)) + mono_wasm_on_gc_class (gparams[i]); + } + + MonoClass *nesting_klass = mono_class_get_nesting_type (info_klass); + if (nesting_klass && mono_class_update_heapshot_scratch_byte (nesting_klass, next_heapshot_scratch_byte)) + mono_wasm_on_gc_class (nesting_klass); + + mono_wasm_heapshot_class ( + klass, mono_class_get_element_class (klass), nesting_klass, + assem, mono_class_get_namespace (info_klass), mono_class_get_name (info_klass), rank, + mono_class_get_kind (klass), gparam_count, gparam_count ? gparams : NULL + ); +} + +// NOTE: for objects (like arrays) containing more than 128 refs, this will get invoked multiple times +static int +mono_wasm_on_gc_object ( + MonoObject *obj, + MonoClass *klass, + uintptr_t size, + uintptr_t num, + MonoObject **refs, + uintptr_t *offsets, + void *data +) { + if (mono_class_update_heapshot_scratch_byte (klass, next_heapshot_scratch_byte)) + mono_wasm_on_gc_class (klass); + mono_wasm_heapshot_object (obj, klass, (uint32_t)size, (uint32_t)num, refs); + return 0; +} + +static void * +mono_wasm_each_gchandle (void *hidden, GCHandleType handle_type, int max_generation, void *user) +{ + MonoObject *obj = sgen_try_reveal_pointer (hidden, handle_type); + if (obj) + mono_wasm_heapshot_gchandle (obj, handle_type); + return hidden; +} + +static void +mono_wasm_generate_heapshot_stats () { + uint32_t in_use_pages, free_pages, external_pages, largest_free_chunk; + mwpm_compute_stats (&in_use_pages, &free_pages, &external_pages, &largest_free_chunk); + mono_wasm_heapshot_stats ( + in_use_pages, free_pages, external_pages, largest_free_chunk, (int)sgen_los_memory_usage, (int)sgen_gc_get_total_heap_allocation () + ); + struct mallinfo minfo = mallinfo (); +#define mifield_inner(a, b) a.b +#define mifield(name) mono_wasm_heapshot_counter("dlmalloc/" #name, mifield_inner(minfo, name)) + mifield(arena); + mifield(ordblks); + mifield(smblks); + mifield(hblks); + mifield(hblkhd); + mifield(usmblks); + mifield(fsmblks); + mifield(uordblks); + mifield(fordblks); + mifield(keepcost); +} + +static void +mono_wasm_on_gc_event ( + MonoProfiler *prof, + MonoProfilerGCEvent gc_event, + uint32_t generation, + mono_bool serial +) { + if (gc_event != MONO_GC_EVENT_PRE_START_WORLD) + return; + + mono_wasm_generate_heapshot_stats(); + mono_gc_walk_heap (0, mono_wasm_on_gc_object, NULL); + for (int ht = HANDLE_TYPE_MIN; ht < HANDLE_TYPE_MAX; ht++) + sgen_gchandle_iterate ((GCHandleType)ht, 2, mono_wasm_each_gchandle, prof); +} + +static void +mono_wasm_on_gc_roots ( + MonoProfiler *prof, + uint64_t count, + const uint8_t *const *addresses, + MonoObject *const *objects, + const char *kind +) { + mono_wasm_heapshot_roots ( + kind, count, addresses, objects + ); +} + +struct _MonoCounter { + MonoCounter *next; + const char *name; + void *addr; + int type; + size_t size; +}; + +static int +mono_wasm_on_counter ( + MonoCounter *counter, gpointer user_data +) { + double value = 0; + switch (counter->type & MONO_COUNTER_TYPE_MASK) { + case MONO_COUNTER_INT: /* 32 bit int */ + value = *(int32_t *)counter->addr; + break; + case MONO_COUNTER_UINT: /* 32 bit uint */ + value = *(uint32_t *)counter->addr; + break; + case MONO_COUNTER_WORD: /* pointer-sized int */ + value = *(ssize_t *)counter->addr; + break; + case MONO_COUNTER_LONG: /* 64 bit int */ + value = *(int64_t *)counter->addr; + break; + case MONO_COUNTER_ULONG: /* 64 bit uint */ + value = *(uint64_t *)counter->addr; + break; + case MONO_COUNTER_DOUBLE: + value = *(double *)counter->addr; + break; + default: + return 1; + } + mono_wasm_heapshot_counter (counter->name, value); + return 1; +} + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_perform_heapshot (int full) { + if (parachute) { + free(parachute); + parachute = NULL; + } + if (!heapshot_profiler_handle) { + memset (&heapshot_profiler, 0, sizeof(MonoProfiler)); + heapshot_profiler_handle = mono_profiler_create (&heapshot_profiler); + } + // intentional wraparound + next_heapshot_scratch_byte = (char)((int)next_heapshot_scratch_byte + 1); + mono_wasm_heapshot_start (full); + mono_profiler_set_gc_event_callback (heapshot_profiler_handle, mono_wasm_on_gc_event); + mono_profiler_set_gc_roots_callback (heapshot_profiler_handle, mono_wasm_on_gc_roots); + if (full) + mono_gc_collect (mono_gc_max_generation ()); + else + mono_wasm_generate_heapshot_stats (); + mono_counters_foreach (mono_wasm_on_counter, NULL); + mono_profiler_set_gc_event_callback (heapshot_profiler_handle, NULL); + mono_profiler_set_gc_roots_callback (heapshot_profiler_handle, NULL); + mono_wasm_heapshot_end (full); +} diff --git a/src/mono/browser/runtime/heapshot/diagnostics-panel.html b/src/mono/browser/runtime/heapshot/diagnostics-panel.html new file mode 100644 index 00000000000000..0416b396e30854 --- /dev/null +++ b/src/mono/browser/runtime/heapshot/diagnostics-panel.html @@ -0,0 +1,204 @@ + + + + .NET Runtime WebAssembly Diagnostics + + + + +
+
+ + + +
+        
+ + diff --git a/src/mono/browser/runtime/heapshot/index.ts b/src/mono/browser/runtime/heapshot/index.ts new file mode 100644 index 00000000000000..e1e2263be11f8f --- /dev/null +++ b/src/mono/browser/runtime/heapshot/index.ts @@ -0,0 +1,328 @@ +/* eslint-disable no-console */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import ProductVersion from "consts:productVersion"; +import cwraps from "../cwraps"; +import { loaderHelpers } from "../globals"; +import { mono_log_info } from "../logging"; +import { getU32 } from "../memory"; +import { utf8ToString } from "../strings"; +import { BlobBuilder } from "../jiterpreter-support"; +import { enumerateProxies } from "../gc-handles"; +import { JiterpCounter, JiterpCounterNames } from "../jiterpreter-enums"; +import { getCounter } from "../jiterpreter-support"; +import type { VoidPtr, ManagedPointer, CharPtr } from "../types/emscripten"; + +const packetBuilderCapacity = 65536; +const packetFlushThreshold = 32768; +// FIXME: It would be ideal if we didn't have to actually retain the whole string +const stringTable = new Map(); +const incompletePackets = new Map(); +const formatVersion = 1; + +const nameFromKind = ["unknown", "def", "gtd", "ginst", "gparam", "array", "pointer"]; +const handleTypes = ["weak", "weak_track", "normal", "pinned", "weak_fields"]; + +let totalObjects = 0, totalRefs = 0, totalClasses = 0, totalAssemblies = 0, totalRoots = 0; +let mostRecentObjectPointer : ManagedPointer = 0; +let heapshotStartedWhen = performance.now(); + +const tabId = (Math.random() * 100000).toFixed(0); +const globalChannel : BroadcastChannel | null = globalThis["BroadcastChannel"] ? new BroadcastChannel(".NET Runtime Diagnostics") : null, + tabChannel : BroadcastChannel | null = globalThis["BroadcastChannel"] ? new BroadcastChannel(`.NET Runtime Diagnostics|${tabId}`) : null; +if (globalChannel) + globalChannel.addEventListener("message", channel_message); +if (tabChannel) + tabChannel.addEventListener("message", tabChannel_message); + +function channel_message (evt: MessageEvent) { + const data = evt.data; + if ((typeof (data) !== "object") || (typeof (data.sender) !== "string") || (typeof (data.cmd) !== "string")) + return; + + console.log("channel_message", data.cmd); + switch (data.cmd) { + case "whosThere": { + let title = "unknown"; + if (globalThis["document"] && globalThis["document"]["title"]) + title = globalThis.document.title; + globalChannel!.postMessage({ cmd: "iAmHere", sender: tabId, version: ProductVersion, running: loaderHelpers.is_runtime_running(), title }); + break; + } + } +} + +function tabChannel_message (evt: MessageEvent) { + const data = evt.data; + if ((typeof (data) !== "object") || (typeof (data.sender) !== "string") || (typeof (data.cmd) !== "string")) + return; + + console.log("tabChannel_message", data.cmd); + switch (data.cmd) { + case "queryStats": + cwraps.mono_wasm_perform_heapshot(0); + break; + case "takeSnapshot": + cwraps.mono_wasm_perform_heapshot(1); + break; + } +} + +export function mono_wasm_heapshot_assembly (assembly: VoidPtr, pName: CharPtr) { + const builder = getBuilder("ASSM"); + totalAssemblies += 1; + builder.appendU32(assembly); + // No point in using the string table for this + builder.appendName(utf8ToString(pName)); +} + +export function mono_wasm_heapshot_class (klass: VoidPtr, elementKlass: VoidPtr, nestingKlass: VoidPtr, assembly: VoidPtr, pNamespace: CharPtr, pName: CharPtr, rank: number, kind: number, numGps: number, pGp: VoidPtr): void { + const builder = getBuilder("TYPE"); + const kindName = nameFromKind[kind] || "unknown"; + totalClasses += 1; + builder.appendU32(klass); + builder.appendU32(elementKlass); + builder.appendU32(nestingKlass); + builder.appendU32(assembly); + builder.appendULeb(rank); + builder.appendULeb(getStringTableIndex(kindName)); + builder.appendULeb(utf8ToStringTableIndex(pNamespace)); + // We use the string table for names because each generic instance will have the same name + builder.appendULeb(utf8ToStringTableIndex(pName)); + builder.appendULeb(numGps); + for (let i = 0; i < numGps; i++) { + const gp = getU32(pGp + (i * 4)); + builder.appendU32(gp); + } +} + +// NOTE: for objects (like arrays) containing more than 128 refs, this will get invoked multiple times +export function mono_wasm_heapshot_object (pObj: ManagedPointer, klass: VoidPtr, size: number, numRefs: number, pRefs: VoidPtr): void { + // The object header and its refs are stored in separate streams + if (pObj !== mostRecentObjectPointer) { + // If we flush the object header chunk we want to flush the current refs chunk so that we don't + // have an object's refs span across multiple chunks unless it's unavoidable + if (autoFlush("OBJH")) + flush("REFS"); + + totalObjects += 1; + const objBuilder = getBuilder("OBJH"); + objBuilder.appendU32(pObj); + objBuilder.appendU32(klass); + objBuilder.appendULeb(size); + mostRecentObjectPointer = pObj; + } + + totalRefs += numRefs; + if (numRefs < 1) + return; + + const refBuilder = getBuilder("REFS"); + refBuilder.appendU32(pObj); + refBuilder.appendULeb(numRefs); + for (let i = 0; i < numRefs; i++) { + const pRef = getU32(pRefs + (i * 4)); + refBuilder.appendU32(pRef); + } +} + +export function mono_wasm_heapshot_gchandle (pObj: ManagedPointer, handleType: number): void { + const handleTypeName = handleTypes[handleType] || "unknown"; + const builder = getBuilder("GCHL"); + builder.appendULeb(getStringTableIndex(handleTypeName)); + builder.appendU32(pObj); +} + +function getStringTableIndex (text: string) { + if (text.length === 0) + return 0; + + let index = stringTable.get(text); + if (!index) { + index = (stringTable.size + 1); + stringTable.set(text, index); + const builder = getBuilder("STBL"); + builder.appendULeb(index); + builder.appendName(text); + } + + return index >>> 0; +} + +function utf8ToStringTableIndex (pText: CharPtr) { + if (!pText) + return 0; + + const text = utf8ToString(pText); + return getStringTableIndex(text); +} + +export function mono_wasm_heapshot_roots (kind: CharPtr, count: number, pAddresses: VoidPtr, pObjects: VoidPtr): void { + const builder = getBuilder("ROOT"); + builder.appendULeb(utf8ToStringTableIndex(kind)); + builder.appendULeb(count); + totalRoots += count; + for (let i = 0; i < count; i++) { + const addr = getU32(pAddresses + (i * 4)); + const obj = getU32(pObjects + (i * 4)); + builder.appendU32(addr); + builder.appendU32(obj); + } +} + +export function mono_wasm_heapshot_start (full: number): void { + stringTable.clear(); + for (const kvp of incompletePackets) + kvp[1].clear(); + totalObjects = totalRefs = totalClasses = totalAssemblies = totalRoots = 0; + mostRecentObjectPointer = 0; + + heapshotStartedWhen = performance.now(); + if (tabChannel) + tabChannel.postMessage({ cmd: "heapshotStart", version: formatVersion, full }); + if (full) + heapshotLog("heapshot starting"); +} + +function pagesToMegabytes (pages: number) { + return (pages * 65536 / (1024 * 1024)).toFixed(1); +} + +function bytesToMegabytes (bytes: number) { + return (bytes / (1024 * 1024)).toFixed(1); +} + +function getBuilder (chunkId: string) { + let result = incompletePackets.get(chunkId); + if (!result) { + result = new BlobBuilder(packetBuilderCapacity); + incompletePackets.set(chunkId, result); + } else + autoFlush(chunkId); + return result; +} + +function autoFlush (chunkId: string) { + const builder = incompletePackets.get(chunkId); + if (builder && (builder.size >= packetFlushThreshold)) { + heapshotPacket(chunkId, builder.getArrayView(false)); + builder.clear(); + return true; + } + return false; +} + +function flush (chunkId?: string) { + if (chunkId) { + const builder = incompletePackets.get(chunkId); + if (builder && (builder.size > 0)) { + heapshotPacket(chunkId, builder.getArrayView(false)); + builder.clear(); + } + return; + } + + for (const kvp of incompletePackets) { + if (kvp[1].size < 1) + continue; + + flush(kvp[0]); + } +} + +function heapshotLog (text: string) { + mono_log_info(text); + if (tabChannel) + tabChannel.postMessage({ cmd: "heapshotText", text }); +} + +function heapshotPacket (chunkId: string, chunk: Uint8Array) { + mono_log_info(`heapshot packet ${chunkId} ${chunk.length}b`); + if (!tabChannel) + return; + + // HACK: Workaround for bug in Chromium structured clone algorithm that copies the whole arraybuffer + if (chunk.buffer.byteLength !== (chunk.BYTES_PER_ELEMENT * chunk.length)) + chunk = chunk.slice(); + + tabChannel.postMessage({ cmd: "heapshotPacket", chunkId: chunkId, chunk: chunk }); +} + +function heapshotCounter (name: string, value: number) { + const builder = getBuilder("CNTR"); + // Using the stringtable here would just make the format harder to decode. + builder.appendName(name); + // We use F64 because some counters (like jiterpreter counters) are not int32's + builder.appendF64(value); +} + +export function mono_wasm_heapshot_counter (pName: CharPtr, value: number): void { + heapshotCounter(`mono/${utf8ToString(pName)}`, value); +} + +export function mono_wasm_heapshot_stats ( + pagesInUse: number, pagesFree: number, pagesUnknown: number, + largestFreeChunk: number, largeObjectHeapSize: number, sgenHeapCapacity: number +): void { + const totalPages = pagesInUse + pagesFree; + const line1 = `mwpm: ${pagesToMegabytes(totalPages)}MB allocated; ${(pagesInUse / totalPages * 100.0).toFixed(1)}% in use; largest free block: ${pagesToMegabytes(largestFreeChunk)}MB; unknown pages: ${pagesToMegabytes(pagesUnknown)}MB`; + const line2 = `sgen: heap capacity ${bytesToMegabytes(sgenHeapCapacity)}MB; LOS size ${bytesToMegabytes(largeObjectHeapSize)}MB`; + heapshotLog(line1); + heapshotLog(line2); + heapshotCounter("mwpm/pages-in-use", pagesInUse); + heapshotCounter("mwpm/pages-free", pagesFree); + heapshotCounter("mwpm/pages-unknown", pagesUnknown); + heapshotCounter("mwpm/largest-free-chunk", largestFreeChunk); + heapshotCounter("sgen/heap-capacity", sgenHeapCapacity); + heapshotCounter("sgen/los-size", largeObjectHeapSize); +} + +function recordObject (chunkId: string, handle: number, obj: any) { + const builder = getBuilder(chunkId); + builder.appendU32(handle); + builder.appendULeb(getStringTableIndex(typeof (obj))); + // We can't use obj.toString since for certain types it's defined to generate an absolute truckload of text + let name = obj && obj.constructor && obj.constructor.name + ? obj.constructor.name + : (obj && obj[Symbol.toStringTag] + ? obj[Symbol.toStringTag] + : "unknown" + ); + if ((typeof (obj) === "function") && obj.name) + name = `function ${obj.name}`; + builder.appendULeb(getStringTableIndex(name)); +} + +function mono_wasm_heapshot_cs_object (handle: number, proxy: any) { + recordObject("CSOB", handle, proxy); +} + +function mono_wasm_heapshot_js_object (handle: number, obj: any) { + recordObject("JSOB", handle, obj); +} + +export function mono_wasm_heapshot_end (full: number): void { + heapshotCounter("snapshot/version", formatVersion); + heapshotCounter("snapshot/num-strings", stringTable.size); + heapshotCounter("snapshot/num-objects", totalObjects); + heapshotCounter("snapshot/num-refs", totalRefs); + heapshotCounter("snapshot/num-roots", totalRoots); + heapshotCounter("snapshot/num-classes", totalClasses); + heapshotCounter("snapshot/num-assemblies", totalAssemblies); + try { + for (let i = 0; i < JiterpCounterNames.length; i++) + heapshotCounter(`jiterpreter/${JiterpCounterNames[i]}`, getCounter(i)); + + if (full) + enumerateProxies(mono_wasm_heapshot_cs_object, mono_wasm_heapshot_js_object); + } finally { + flush(); + const elapsedMs = performance.now() - heapshotStartedWhen; + if (full) + heapshotLog(`heapshot finished after ${elapsedMs.toFixed(1)}msec`); + if (tabChannel) + tabChannel.postMessage({ cmd: "heapshotEnd", full }); + } +} diff --git a/src/mono/browser/runtime/jiterpreter-enums.ts b/src/mono/browser/runtime/jiterpreter-enums.ts index 07a3f815b83127..0ae595d5120129 100644 --- a/src/mono/browser/runtime/jiterpreter-enums.ts +++ b/src/mono/browser/runtime/jiterpreter-enums.ts @@ -23,6 +23,22 @@ export const enum JiterpCounter { ElapsedCompilationMs, } +export const JiterpCounterNames = [ + "TraceCandidates", + "TracesCompiled", + "EntryWrappersCompiled", + "JitCallsCompiled", + "DirectJitCallsCompiled", + "Failures", + "BytesGenerated", + "NullChecksEliminated", + "NullChecksFused", + "BackBranchesEmitted", + "BackBranchesNotEmitted", + "ElapsedGenerationMs", + "ElapsedCompilationMs", +]; + // keep in sync with jiterpreter.c, see mono_jiterp_get_member_offset export const enum JiterpMember { VtableInitialized = 0, diff --git a/src/mono/browser/runtime/jiterpreter-support.ts b/src/mono/browser/runtime/jiterpreter-support.ts index e89b829c9df326..820c2644d065be 100644 --- a/src/mono/browser/runtime/jiterpreter-support.ts +++ b/src/mono/browser/runtime/jiterpreter-support.ts @@ -941,9 +941,10 @@ export class BlobBuilder { encoder?: TextEncoder; textBuf = new Uint8Array(1024); - constructor () { - this.capacity = 16 * 1024; + constructor (capacity?: number) { + this.capacity = capacity || 16 * 1024; this.buffer = Module._malloc(this.capacity); + mono_assert(this.buffer, () => `Failed to allocate ${this.capacity} byte(s) for BlobBuilder`); localHeapViewU8().fill(0, this.buffer, this.buffer + this.capacity); this.size = 0; this.clear(); diff --git a/src/mono/browser/runtime/rollup.config.js b/src/mono/browser/runtime/rollup.config.js index 6f718c373d404e..ca51405682be1c 100644 --- a/src/mono/browser/runtime/rollup.config.js +++ b/src/mono/browser/runtime/rollup.config.js @@ -13,6 +13,12 @@ import fast_glob from "fast-glob"; import gitCommitInfo from "git-commit-info"; import MagicString from "magic-string"; +setTimeout(() => { + // eslint-disable-next-line no-console + console.log("Forcibly exiting process to work around hang in rollup"); + process.exit(); +}, 35000); + const configuration = process.env.Configuration; const isDebug = configuration !== "Release"; const isContinuousIntegrationBuild = process.env.ContinuousIntegrationBuild === "true" ? true : false; diff --git a/src/mono/mono/eventpipe/ep-rt-mono-profiler-provider.c b/src/mono/mono/eventpipe/ep-rt-mono-profiler-provider.c index 12d730b8455255..fdd5a189f57e5b 100644 --- a/src/mono/mono/eventpipe/ep-rt-mono-profiler-provider.c +++ b/src/mono/mono/eventpipe/ep-rt-mono-profiler-provider.c @@ -1005,7 +1005,8 @@ buffer_gc_event_roots_callback ( MonoProfiler *prof, uint64_t count, const mono_byte *const * addresses, - MonoObject *const * objects) + MonoObject *const * objects, + const char *kind) { EP_ASSERT (gc_in_progress ()); @@ -3111,7 +3112,7 @@ ep_rt_mono_profiler_provider_fini (void) _gc_heap_dump_in_progress = 0; _gc_heap_dump_trigger_count = 0; _gc_state = 0; - + // We were cleaning up resources (mutexes, tls data, etc) here but it races with // other threads on shutdown. Skipping cleanup to prevent failures. If unloading // and not leaking these threads becomes a priority we will have to reimplement diff --git a/src/mono/mono/eventpipe/ep-rt-mono-runtime-provider.c b/src/mono/mono/eventpipe/ep-rt-mono-runtime-provider.c index 379b7bde8a1cae..4a09865c986371 100644 --- a/src/mono/mono/eventpipe/ep-rt-mono-runtime-provider.c +++ b/src/mono/mono/eventpipe/ep-rt-mono-runtime-provider.c @@ -4068,7 +4068,8 @@ buffer_gc_event_roots_callback ( MonoProfiler *prof, uint64_t count, const mono_byte *const * addresses, - MonoObject *const * objects) + MonoObject *const * objects, + const char *kind) { GCHeapDumpContext *context = gc_heap_dump_context_get (); if (!context) diff --git a/src/mono/mono/metadata/assembly.c b/src/mono/mono/metadata/assembly.c index 49eca9772f481e..ee6927c8501a34 100644 --- a/src/mono/mono/metadata/assembly.c +++ b/src/mono/mono/metadata/assembly.c @@ -3264,3 +3264,25 @@ mono_assembly_get_count (void) { return loaded_assembly_count; } + +#ifdef HOST_BROWSER +int +mono_assembly_update_heapshot_scratch_byte (MonoAssembly *assembly, guint8 new_value); + +// returns true if the byte was changed +int +mono_assembly_update_heapshot_scratch_byte (MonoAssembly *assembly, guint8 new_value) { + g_assert (assembly); + // early-out; we know that the scratch byte will only ever increment or wrap around, + // so if we see the value 7 we know it can only become 8 from this point and then 9, + // it won't ever wrap back around to 7 or 6 without us seeing an intermediate value + // so we don't have to worry about ABA here, and we don't need to worry about races + if (assembly->heapshot_scratch_byte == new_value) + return 0; + // if we're actually changing the scratch byte, do so atomically to ensure that only + // one thread believes it changed the byte and does the resulting work + // we know that two separate heapshots can't occur in parallel, so it's not possible + // for this operation to trample a 9 and replace it with an 8 + return mono_atomic_xchg_u8 (&assembly->heapshot_scratch_byte, new_value) != new_value; +} +#endif diff --git a/src/mono/mono/metadata/class-accessors.c b/src/mono/mono/metadata/class-accessors.c index b18b16b985df49..240fd72e9f1fed 100644 --- a/src/mono/mono/metadata/class-accessors.c +++ b/src/mono/mono/metadata/class-accessors.c @@ -571,7 +571,7 @@ mono_class_set_failure (MonoClass *klass, MonoErrorBoxed *boxed_error) /** * mono_class_set_deferred_failure: * \param klass class in which the failure was detected - + * This method marks the class with a deferred failure, indicating that a failure was detected but it will be processed during AOT runtime.. * Note that only the first failure is kept. * @@ -684,6 +684,31 @@ mono_class_has_metadata_update_info (MonoClass *klass) } } +int +mono_class_get_kind (MonoClass *klass) +{ + return m_class_get_class_kind (klass); +} + +int +mono_class_get_generic_params (MonoClass *klass, MonoClass **result, int result_capacity) +{ + if (m_class_get_class_kind (klass) != MONO_CLASS_GINST) + return 0; + + MonoGenericClass *gclass = mono_class_get_generic_class (klass); + MonoGenericInst *inst = gclass->context.class_inst; + for (int i = 0; i < (int)inst->type_argc; i++) { + MonoType *t = inst->type_argv [i]; + if (i < result_capacity) { + if (t) + result[i] = mono_class_from_mono_type_internal (t); + else + result[i] = NULL; + } + } + return MIN((int)inst->type_argc, result_capacity); +} #ifdef MONO_CLASS_DEF_PRIVATE #define MONO_CLASS_GETTER(funcname, rettype, optref, argtype, fieldname) rettype funcname (argtype *klass) { return optref klass-> fieldname ; } diff --git a/src/mono/mono/metadata/class-init.c b/src/mono/mono/metadata/class-init.c index 91f40403346167..f43c4af8df0971 100644 --- a/src/mono/mono/metadata/class-init.c +++ b/src/mono/mono/metadata/class-init.c @@ -4360,3 +4360,25 @@ mono_classes_init (void) mono_counters_register ("MonoClass size", MONO_COUNTER_METADATA | MONO_COUNTER_INT, &classes_size); } + +#ifdef HOST_BROWSER +int +mono_class_update_heapshot_scratch_byte (MonoClass *klass, guint8 new_value); + +// returns true if the byte was changed +int +mono_class_update_heapshot_scratch_byte (MonoClass *klass, guint8 new_value) { + g_assert (klass); + // early-out; we know that the scratch byte will only ever increment or wrap around, + // so if we see the value 7 we know it can only become 8 from this point and then 9, + // it won't ever wrap back around to 7 or 6 without us seeing an intermediate value + // so we don't have to worry about ABA here, and we don't need to worry about races + if (klass->heapshot_scratch_byte == new_value) + return 0; + // if we're actually changing the scratch byte, do so atomically to ensure that only + // one thread believes it changed the byte and does the resulting work + // we know that two separate heapshots can't occur in parallel, so it's not possible + // for this operation to trample a 9 and replace it with an 8 + return mono_atomic_xchg_u8 (&klass->heapshot_scratch_byte, new_value) != new_value; +} +#endif diff --git a/src/mono/mono/metadata/class-private-definition.h b/src/mono/mono/metadata/class-private-definition.h index 829ac16d12903e..95aa72ce8f87f2 100644 --- a/src/mono/mono/metadata/class-private-definition.h +++ b/src/mono/mono/metadata/class-private-definition.h @@ -29,8 +29,6 @@ struct _MonoClass { int instance_size; /* object instance size */ - guint inited : 1; - /* A class contains static and non static data. Static data can be * of the same type as the class itselfs, but it does not influence * the instance size of the class. To avoid cyclic calls to @@ -41,6 +39,8 @@ struct _MonoClass { */ /* ALL BITFIELDS SHOULD BE WRITTEN WHILE HOLDING THE LOADER LOCK */ + /* first bit */ + guint inited : 1; guint size_inited : 1; guint valuetype : 1; /* derives from System.ValueType */ guint enumtype : 1; /* derives from System.Enum */ @@ -48,28 +48,23 @@ struct _MonoClass { guint unicode : 1; /* class uses unicode char when marshalled */ guint wastypebuilder : 1; /* class was created at runtime from a TypeBuilder */ guint is_array_special_interface : 1; /* gtd or ginst of once of the magic interfaces that arrays implement */ - guint is_byreflike : 1; /* class is a valuetype and has System.Runtime.CompilerServices.IsByRefLikeAttribute */ - guint is_inlinearray : 1; /* class is a valuetype and has System.Runtime.CompilerServices.InlineArrayAttribute */ - - /* next byte */ - guint8 min_align; - /* next byte */ + guint is_byreflike : 1; /* class is a valuetype and has System.Runtime.CompilerServices.IsByRefLikeAttribute */ + guint is_inlinearray : 1; /* class is a valuetype and has System.Runtime.CompilerServices.InlineArrayAttribute */ guint packing_size : 4; guint ghcimpl : 1; /* class has its own GetHashCode impl */ guint has_finalize : 1; /* class has its own Finalize impl */ - guint delegate : 1; /* class is a Delegate */ /* next byte */ + guint delegate : 1; /* class is a Delegate */ guint gc_descr_inited : 1; /* gc_descr is initialized */ guint has_cctor : 1; /* class has a cctor */ guint has_references : 1; /* it has GC-tracked references in the instance */ guint has_ref_fields : 1; /* it has byref fields */ guint has_static_refs : 1; /* it has static fields that are GC-tracked */ guint no_special_static_fields : 1; /* has no thread/context static fields */ - guint nested_classes_inited : 1; /* Whenever nested_class is initialized */ + /* next byte */ guint interfaces_inited : 1; /* interfaces is initialized */ - /* next byte*/ guint simd_type : 1; /* class is a simd intrinsic type */ guint has_finalize_inited : 1; /* has_finalize is initialized */ guint fields_inited : 1; /* setup_fields () has finished */ @@ -77,11 +72,17 @@ struct _MonoClass { guint has_weak_fields : 1; /* class has weak reference fields */ guint has_dim_conflicts : 1; /* Class has conflicting default interface methods */ guint any_field_has_auto_layout : 1; /* a field in this type's layout uses auto-layout */ + /* next byte */ guint has_deferred_failure : 1; - /* next byte*/ guint is_exception_class : 1; /* is System.Exception or derived from it */ guint variant_search_table_inited : 1; + /* next byte */ + guint8 min_align; +#ifdef HOST_BROWSER + guint8 heapshot_scratch_byte; +#endif + MonoClass *parent; MonoClass *nested_in; diff --git a/src/mono/mono/metadata/metadata-internals.h b/src/mono/mono/metadata/metadata-internals.h index 54d85997d7af4a..e5a0c0af4e0873 100644 --- a/src/mono/mono/metadata/metadata-internals.h +++ b/src/mono/mono/metadata/metadata-internals.h @@ -212,6 +212,9 @@ struct _MonoAssembly { guint8 jit_optimizer_disabled_inited; guint8 runtime_marshalling_enabled; guint8 runtime_marshalling_enabled_inited; +#ifdef HOST_BROWSER + guint8 heapshot_scratch_byte; +#endif }; typedef struct { diff --git a/src/mono/mono/metadata/sgen-mono.c b/src/mono/mono/metadata/sgen-mono.c index a650acb6ed7253..647332266f920b 100644 --- a/src/mono/mono/metadata/sgen-mono.c +++ b/src/mono/mono/metadata/sgen-mono.c @@ -1259,6 +1259,7 @@ typedef struct { int count; /* must be the first field */ void *addresses [GC_ROOT_NUM]; void *objects [GC_ROOT_NUM]; + const char *kind; } GCRootReport; static void @@ -1266,7 +1267,7 @@ notify_gc_roots (GCRootReport *report) { if (!report->count) return; - MONO_PROFILER_RAISE (gc_roots, (report->count, (const mono_byte *const *)report->addresses, (MonoObject *const *) report->objects)); + MONO_PROFILER_RAISE (gc_roots, (report->count, (const mono_byte *const *)report->addresses, (MonoObject *const *) report->objects, report->kind)); report->count = 0; } @@ -1555,6 +1556,7 @@ static void report_stack_roots (void) { GCRootReport report = {0}; + report.kind = "stack"; FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) { void *aligned_stack_start; @@ -1610,6 +1612,7 @@ report_finalizer_roots_from_queue (SgenPointerQueue *queue, void* queue_address) GCRootReport report; size_t i; + report.kind = "finalizer"; report.count = 0; for (i = 0; i < queue->next_slot; ++i) { void *obj = queue->data [i]; @@ -1621,13 +1624,18 @@ report_finalizer_roots_from_queue (SgenPointerQueue *queue, void* queue_address) } static void -report_registered_roots_by_type (int root_type) +report_registered_roots_by_type (int root_type, const char *kind) { GCRootReport report = { 0 }; + report.kind = kind; void **start_root; RootRecord *root; report.count = 0; SGEN_HASH_TABLE_FOREACH (&sgen_roots_hash [root_type], void **, start_root, RootRecord *, root) { + const char *local_kind = root->msg ? root->msg : kind; + if (local_kind != report.kind) + notify_gc_roots(&report); + report.kind = local_kind; SGEN_LOG (6, "Profiler root scan %p-%p (desc: %p)", start_root, root->end_root, (void*)(intptr_t)root->root_desc); if (root_type == ROOT_TYPE_PINNED) report_pinning_roots (&report, start_root, (void**)root->end_root); @@ -1640,8 +1648,9 @@ report_registered_roots_by_type (int root_type) static void report_registered_roots (void) { - for (int i = 0; i < ROOT_TYPE_NUM; ++i) - report_registered_roots_by_type (i); + report_registered_roots_by_type (ROOT_TYPE_NORMAL, "normal"); + report_registered_roots_by_type (ROOT_TYPE_PINNED, "pinned"); + report_registered_roots_by_type (ROOT_TYPE_WBARRIER, "write barrier"); } static void @@ -1651,6 +1660,7 @@ report_ephemeron_roots (void) Ephemeron *cur, *array_end; GCObject *tombstone; GCRootReport report = { 0 }; + report.kind = "ephemeron"; for (current = ephemeron_list; current; current = current->next) { MonoArray *array = current->array; @@ -1686,6 +1696,7 @@ static void report_toggleref_roots (void) { GCRootReport report = { 0 }; + report.kind = "toggleref"; sgen_foreach_toggleref_root (report_toggleref_root, &report); notify_gc_roots (&report); } @@ -2260,8 +2271,6 @@ static size_t worker_heap_size; void sgen_client_total_allocated_heap_changed (size_t allocated_heap) { - mono_runtime_resource_check_limit (MONO_RESOURCE_GC_HEAP, allocated_heap); - /* * This function can be called from SGen's worker threads. We want to try * and avoid exposing those threads to the profiler API, so save the heap diff --git a/src/mono/mono/profiler/log.c b/src/mono/mono/profiler/log.c index abe5f3f479f578..da4cfe94674ea9 100644 --- a/src/mono/mono/profiler/log.c +++ b/src/mono/mono/profiler/log.c @@ -1254,7 +1254,7 @@ gc_reference (MonoObject *obj, MonoClass *klass, uintptr_t size, uintptr_t num, } static void -gc_roots (MonoProfiler *prof, uint64_t num, const mono_byte *const *addresses, MonoObject *const *objects) +gc_roots (MonoProfiler *prof, uint64_t num, const mono_byte *const *addresses, MonoObject *const *objects, const char *kind) { ENTER_LOG (&heap_roots_ctr, logbuffer, EVENT_SIZE /* event */ + diff --git a/src/mono/mono/sgen/sgen-gc.c b/src/mono/mono/sgen/sgen-gc.c index 705b8168ed36f3..ed10e8f30e9d2a 100644 --- a/src/mono/mono/sgen/sgen-gc.c +++ b/src/mono/mono/sgen/sgen-gc.c @@ -4076,10 +4076,10 @@ sgen_check_canary_for_object (gpointer addr) #define MEM_PRESSURE_COUNT 4 -#define MAX_MEMORYPRESSURE_RATIO 10 +#define MAX_MEMORYPRESSURE_RATIO 10 guint64 memory_pressure_gc_count = 0; guint64 memory_pressure_iteration = 0; -guint64 memory_pressure_adds[MEM_PRESSURE_COUNT] = {0, 0, 0, 0}; +guint64 memory_pressure_adds[MEM_PRESSURE_COUNT] = {0, 0, 0, 0}; guint64 memory_pressure_removes[MEM_PRESSURE_COUNT] = {0, 0, 0, 0}; // history of memory pressure removals const unsigned min_memorypressure_budget = 4 * 1024 * 1024; // 4 MB @@ -4118,7 +4118,7 @@ static gboolean pressure_check_heuristic (guint64 new_mem_value) for (gint i = 0; i < MEM_PRESSURE_COUNT; i++) { add += memory_pressure_adds[i]; rem += memory_pressure_removes[i]; - } + } add -= memory_pressure_adds[p]; rem -= memory_pressure_removes[p]; @@ -4150,7 +4150,7 @@ static gboolean pressure_check_heuristic (guint64 new_mem_value) } if (new_mem_value >= budget) { // last check - if we would exceed 20% of GC "duty cycle", do not trigger GC at this time - if ((size_t)(mono_time_since_last_stw() + time_last) > (time_last * 5)) { + if ((size_t)(mono_time_since_last_stw() + time_last) > (time_last * 5)) { return TRUE; } } @@ -4174,4 +4174,13 @@ void sgen_add_memory_pressure (guint64 bytes_allocated) check_pressure_counts (); } } + +void sgen_registered_root_iterate (SgenRootIterateCallback callback, gpointer user_data, int root_type) +{ + void **start_root; + RootRecord *root; + SGEN_HASH_TABLE_FOREACH (&sgen_roots_hash [root_type], void **, start_root, RootRecord *, root) { + callback (start_root, (void**)root->end_root, root->source, root_type, root->msg, user_data); + } SGEN_HASH_TABLE_FOREACH_END; +} #endif /* HAVE_SGEN_GC */ diff --git a/src/mono/mono/sgen/sgen-gc.h b/src/mono/mono/sgen/sgen-gc.h index 3e4d2086beab5b..86ac8eef893ef1 100644 --- a/src/mono/mono/sgen/sgen-gc.h +++ b/src/mono/mono/sgen/sgen-gc.h @@ -408,6 +408,10 @@ int sgen_register_root (char *start, size_t size, SgenDescriptor descr, int root void sgen_deregister_root (char* addr) MONO_PERMIT (need (sgen_lock_gc)); +typedef void (*SgenRootIterateCallback) (gpointer start, gpointer end, MonoGCRootSource source, int root_type, const char *msg, gpointer user); + +void sgen_registered_root_iterate (SgenRootIterateCallback callback, gpointer user_data, int root_type); + typedef void (*IterateObjectCallbackFunc) (GCObject*, size_t, void*); typedef gboolean (*IterateObjectResultCallbackFunc) (GCObject*, size_t, void*); @@ -888,6 +892,8 @@ void sgen_gc_unlock (void) MONO_PERMIT (use (sgen_gc_locked), revoke (sgen_gc_lo void sgen_queue_finalization_entry (GCObject *obj); const char* sgen_generation_name (int generation); +gpointer sgen_try_reveal_pointer (gpointer hidden, GCHandleType handle_type); + void sgen_finalize_in_range (int generation, ScanCopyContext ctx) MONO_PERMIT (need (sgen_gc_locked)); void sgen_null_link_in_range (int generation, ScanCopyContext ctx, gboolean track) diff --git a/src/mono/mono/sgen/sgen-gchandles.c b/src/mono/mono/sgen/sgen-gchandles.c index f869775cb4dcb6..5f368472ff1d43 100644 --- a/src/mono/mono/sgen/sgen-gchandles.c +++ b/src/mono/mono/sgen/sgen-gchandles.c @@ -471,6 +471,15 @@ scan_for_weak (gpointer hidden, GCHandleType handle_type, int max_generation, gp return MONO_GC_HANDLE_OBJECT_POINTER (obj, is_weak); } +gpointer +sgen_try_reveal_pointer (gpointer hidden, GCHandleType handle_type) +{ + const gboolean is_weak = GC_HANDLE_TYPE_IS_WEAK (handle_type); + if (!MONO_GC_HANDLE_VALID (hidden)) + return NULL; + return MONO_GC_REVEAL_POINTER (hidden, is_weak); +} + /* LOCKING: requires that the GC lock is held */ void sgen_null_link_in_range (int generation, ScanCopyContext ctx, gboolean track) diff --git a/src/mono/mono/utils/mono-codeman.c b/src/mono/mono/utils/mono-codeman.c index 26f8715c61dd0b..c49e7f6ea82b3f 100644 --- a/src/mono/mono/utils/mono-codeman.c +++ b/src/mono/mono/utils/mono-codeman.c @@ -582,7 +582,6 @@ new_codechunk (MonoCodeManager *cman, int size) MONO_PROFILER_RAISE (jit_chunk_created, ((mono_byte *) chunk->data, chunk->size)); code_memory_used += chunk_size; - mono_runtime_resource_check_limit (MONO_RESOURCE_JIT_CODE, code_memory_used); /*printf ("code chunk at: %p\n", ptr);*/ return chunk; } diff --git a/src/mono/mono/utils/mono-counters.c b/src/mono/mono/utils/mono-counters.c index 1f126a1c427091..4ed5ab91a90c74 100644 --- a/src/mono/mono/utils/mono-counters.c +++ b/src/mono/mono/utils/mono-counters.c @@ -9,10 +9,12 @@ #include #include "config.h" -#include +#include +#include "mono/utils/mono-counters.h" -// NOTE: this was turned into a no-op in dotnet/runtime, check git history for the -// original implementation in case we want to bring this back in the future +#ifdef HAVE_UNISTD_H +#include +#endif struct _MonoCounter { MonoCounter *next; @@ -22,100 +24,353 @@ struct _MonoCounter { size_t size; }; +static MonoCounter *counters = NULL; +static mono_mutex_t counters_mutex; + +static volatile gboolean initialized = FALSE; + +static int valid_mask = 0; +static int set_mask = 0; + +static GSList *register_callbacks = NULL; + +/** + * mono_counter_get_variance: + * \param counter counter to get the variance + * + * Variance specifies how the counter value is expected to behave between any two samplings. + * + * \returns the monotonicity of the counter. + */ int mono_counter_get_variance (MonoCounter *counter) { - return 0; + return counter->type & MONO_COUNTER_VARIANCE_MASK; } +/** + * mono_counter_get_unit: + * \param counter counter to get the unit + * + * The unit gives a high level view of the unit that the counter is measuring. + * + * \returns the unit of the counter. + */ int mono_counter_get_unit (MonoCounter *counter) { - return 0; + return counter->type & MONO_COUNTER_UNIT_MASK; } +/** + * mono_counter_get_section: + * \param counter counter to get the section + * Sections are the unit of organization between all counters. + * \returns the section of the counter. + */ + int mono_counter_get_section (MonoCounter *counter) { - return 0; + return counter->type & MONO_COUNTER_SECTION_MASK; } +/** + * mono_counter_get_type: + * \param counter counter to get the type + * \returns the type used to store the value of the counter. + */ int mono_counter_get_type (MonoCounter *counter) { - return 0; + return counter->type & MONO_COUNTER_TYPE_MASK; } +/** + * mono_counter_get_name: + * \param counter counter to get the name + * \returns the counter name. The string should not be freed. + */ + const char* -mono_counter_get_name (MonoCounter *name) +mono_counter_get_name (MonoCounter *counter) { - return NULL; + return counter->name; } +/** + * mono_counter_get_size: + * \param counter counter to get the max size of the counter + * Use the returned size to create the buffer used with \c mono_counters_sample + * \returns the max size of the counter data. + */ size_t mono_counter_get_size (MonoCounter *counter) { - return 0; + return counter->size; } +/** + * mono_counters_enable: + * \param sectionmask a mask listing the sections that will be displayed + * This is used to track which counters will be displayed. + */ void mono_counters_enable (int section_mask) { + valid_mask = section_mask & MONO_COUNTER_SECTION_MASK; } void mono_counters_init (void) { + if (initialized) + return; + + mono_os_mutex_init (&counters_mutex); + + initialized = TRUE; } +static void +register_internal (const char *name, int type, void *addr, int size) +{ + MonoCounter *counter; + GSList *register_callback; + + g_assert (size >= 0); + if ((type & MONO_COUNTER_VARIANCE_MASK) == 0) + type |= MONO_COUNTER_MONOTONIC; + + mono_os_mutex_lock (&counters_mutex); + + for (counter = counters; counter; counter = counter->next) { + if (counter->addr == addr) { + g_warning ("you are registering the same counter address twice: %s at %p", name, addr); + mono_os_mutex_unlock (&counters_mutex); + return; + } + } + + counter = (MonoCounter *) g_malloc (sizeof (MonoCounter)); + if (!counter) { + mono_os_mutex_unlock (&counters_mutex); + return; + } + counter->name = g_strdup (name); + counter->type = type; + counter->addr = addr; + counter->next = NULL; + counter->size = size; + + set_mask |= type; + + /* Append */ + if (counters) { + MonoCounter *item = counters; + while (item->next) + item = item->next; + item->next = counter; + } else { + counters = counter; + } + + for (register_callback = register_callbacks; register_callback; register_callback = register_callback->next) + ((MonoCounterRegisterCallback)register_callback->data) (counter); + + mono_os_mutex_unlock (&counters_mutex); +} + +/** + * mono_counters_register: + * \param name The name for this counters. + * \param type One of the possible \c MONO_COUNTER types, or \c MONO_COUNTER_CALLBACK for a function pointer. + * \param addr The address to register. + * + * Register \p addr as the address of a counter of type type. + * Note that \p name must be a valid string at all times until + * \c mono_counters_dump() is called. + * + * This function should not be used with counter types that require an explicit size such as string + * as the counter size will be set to zero making them effectively useless. + * + * It may be a function pointer if \c MONO_COUNTER_CALLBACK is specified: + * the function should return the value and take no arguments. + */ void mono_counters_register (const char* name, int type, void *addr) { + int size; + switch (type & MONO_COUNTER_TYPE_MASK) { + case MONO_COUNTER_INT: + size = sizeof (int); + break; + case MONO_COUNTER_UINT: + size = sizeof (guint); + break; + case MONO_COUNTER_LONG: + case MONO_COUNTER_TIME_INTERVAL: + size = sizeof (gint64); + break; + case MONO_COUNTER_ULONG: + size = sizeof (guint64); + break; + case MONO_COUNTER_WORD: + size = sizeof (gssize); + break; + case MONO_COUNTER_DOUBLE: + size = sizeof (double); + break; + case MONO_COUNTER_STRING: + size = 0; + break; + default: + g_assert_not_reached (); + } + + if (!initialized) + g_debug ("counters not enabled"); + else + register_internal (name, type, addr, size); } +/** + * mono_counters_register_with_size: + * \param name The name for this counters. + * \param type One of the possible MONO_COUNTER types, or MONO_COUNTER_CALLBACK for a function pointer. + * \param addr The address to register. + * \param size Max size of the counter data. + * + * Register \p addr as the address of a counter of type \p type. + * Note that \p name must be a valid string at all times until + * \c mono_counters_dump() is called. + * + * It may be a function pointer if \c MONO_COUNTER_CALLBACK is specified: + * the function should return the value and take no arguments. + * + * The value of \p size is ignored for types with fixed size such as int and long. + * + * Use \p size for types that can have dynamic size such as string. + * + * If \p size is negative, it's silently converted to zero. + */ void mono_counters_register_with_size (const char *name, int type, void *addr, int size) { + if (!initialized) + g_debug ("counters not enabled"); + else + register_internal (name, type, addr, size); } +/** + * mono_counters_on_register + * \param callback function to callback when a counter is registered + * Add a callback that is going to be called when a counter is registered + */ void mono_counters_on_register (MonoCounterRegisterCallback callback) { + if (!initialized) { + g_debug ("counters not enabled"); + return; + } + + mono_os_mutex_lock (&counters_mutex); + register_callbacks = g_slist_append (register_callbacks, (gpointer) callback); + mono_os_mutex_unlock (&counters_mutex); } +typedef int (*IntFunc) (void); +typedef guint (*UIntFunc) (void); +typedef gint64 (*LongFunc) (void); +typedef guint64 (*ULongFunc) (void); +typedef gssize (*PtrFunc) (void); +typedef double (*DoubleFunc) (void); +typedef char* (*StrFunc) (void); + +/** + * mono_counters_foreach: + * \param cb The callback that will be called for each counter. + * \param user_data Value passed as second argument of the callback. + * Iterate over all counters and call \p cb for each one of them. Stop iterating if + * the callback returns FALSE. + */ void mono_counters_foreach (CountersEnumCallback cb, gpointer user_data) { + MonoCounter *counter; + + if (!initialized) { + g_debug ("counters not enabled"); + return; + } + + mono_os_mutex_lock (&counters_mutex); + + for (counter = counters; counter; counter = counter->next) { + if (!cb (counter, user_data)) { + mono_os_mutex_unlock (&counters_mutex); + return; + } + } + + mono_os_mutex_unlock (&counters_mutex); } -int -mono_counters_sample (MonoCounter *counter, void *buffer, int buffer_size) +#define COPY_COUNTER(type,functype) do { \ + size = sizeof (type); \ + if (buffer_size < size) \ + size = -1; \ + else \ + *(type*)buffer = cb ? ((functype)counter->addr) () : *(type*)counter->addr; \ + } while (0); + +static void +mono_counters_dump_section (int section, int variance, FILE *outfile) { - return 0; } +/** + * mono_counters_dump: + * \param section_mask The sections to dump counters for + * \param outfile a FILE to dump the results to; NULL will default to g_print + * Displays the counts of all the enabled counters registered. + * To filter by variance, you can OR one or more variance with the specific section you want. + * Use \c MONO_COUNTER_SECTION_MASK to dump all categories of a specific variance. + */ void mono_counters_dump (int section_mask, FILE *outfile) { } +#undef FPRINTF_OR_G_PRINT + +/** + * mono_counters_cleanup: + * + * Perform any needed cleanup at process exit. + */ void mono_counters_cleanup (void) { -} + MonoCounter *counter; -void -mono_runtime_resource_check_limit (int resource_type, uintptr_t value) -{ -} + if (!initialized) + return; -int -mono_runtime_resource_limit (int resource_type, uintptr_t soft_limit, uintptr_t hard_limit) -{ - return 1; -} + mono_os_mutex_lock (&counters_mutex); -void -mono_runtime_resource_set_callback (MonoResourceCallback callback) -{ + counter = counters; + counters = NULL; + while (counter) { + MonoCounter *tmp = counter; + counter = counter->next; + g_free ((void*)tmp->name); + g_free (tmp); + } + + mono_os_mutex_unlock (&counters_mutex); } + + diff --git a/src/mono/mono/utils/mono-wasm-pagemgr.c b/src/mono/mono/utils/mono-wasm-pagemgr.c index d3a25a0ec4b851..e026c293cecd3c 100644 --- a/src/mono/mono/utils/mono-wasm-pagemgr.c +++ b/src/mono/mono/utils/mono-wasm-pagemgr.c @@ -187,38 +187,49 @@ transition_page_states (page_action action, uint32_t first_page, uint32_t page_c cleanup_preceding_pages (first_page); } -static void -print_stats () { -#if defined(MWPM_LOGGING) || defined(MWPM_STATS) - uint32_t in_use = 0, free = 0, unallocated = 0, - max_run = 0, current_run = 0; +void +mwpm_compute_stats (uint32_t *in_use_pages, uint32_t *free_pages, uint32_t *external_pages, uint32_t *largest_free_chunk) { + g_assert (in_use_pages && free_pages && external_pages && largest_free_chunk); + *in_use_pages = 0; + *free_pages = 0; + *external_pages = 0; + *largest_free_chunk = 0; + uint32_t current_run = 0; for (uint32_t i = first_controlled_page_index; i <= last_controlled_page_index; i++) { switch (page_table[i] & MWPM_STATE_MASK) { case MWPM_ALLOCATED: - in_use++; + (*in_use_pages)++; current_run = 0; break; case MWPM_FREE_DIRTY: case MWPM_FREE_ZEROED: - free++; + (*free_pages)++; current_run++; - if (current_run > max_run) - max_run = current_run; + if (current_run > *largest_free_chunk) + *largest_free_chunk = current_run; break; default: - unallocated++; + (*external_pages)++; current_run = 0; break; } } +} - uint32_t total = in_use + free; // + unallocated; +static void +print_stats () { +#if defined(MWPM_LOGGING) || defined(MWPM_STATS) + uint32_t in_use = 0, free_pages = 0, unallocated = 0, + max_run = 0; + mwpm_compute_stats (&in_use, &free_pages, &unallocated, &max_run); + + uint32_t total = in_use + free_pages; // + unallocated; g_print ( "sbrk(0)==%u. %u pages in use (%f%%), %u pages free, %u pages unknown. largest possible allocation: %u pages\n", - (uint32_t)sbrk(0), in_use, in_use * 100.0 / total, free, unallocated, max_run + (uint32_t)sbrk(0), in_use, in_use * 100.0 / total, free_pages, unallocated, max_run ); #endif } @@ -235,6 +246,12 @@ acquire_new_pages_initialized (uint32_t page_count) { if (bytes >= UINT32_MAX) return NULL; + if (first_controlled_page_index == UINT32_MAX) { + // HACK: Ensure we never allocate the zero page. It's important for it to be reserved. + if (sbrk (0) == 0) + sbrk (MWPM_PAGE_SIZE); + } + // We know that on WASM, sbrk grows the heap as necessary in order to return, // a region of N zeroed bytes, which isn't necessarily aligned or page-sized uint8_t *allocation = sbrk ((uint32_t)bytes); diff --git a/src/mono/mono/utils/mono-wasm-pagemgr.h b/src/mono/mono/utils/mono-wasm-pagemgr.h index 1720ffaf0967bf..f830e21058908b 100644 --- a/src/mono/mono/utils/mono-wasm-pagemgr.h +++ b/src/mono/mono/utils/mono-wasm-pagemgr.h @@ -84,4 +84,9 @@ mwpm_alloc_range (size_t size, uint8_t zeroed); void mwpm_free_range (void *base, size_t size); +// Computes stats on the state of pages managed by mwpm. Is not thread safe. +// External pages are pages of memory allocated by something other than mwpm (i.e. emscripten malloc/mmap). +void +mwpm_compute_stats (uint32_t *in_use_pages, uint32_t *free_pages, uint32_t *external_pages, uint32_t *largest_free_chunk); + #endif diff --git a/src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj b/src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj index 679ca812e0f420..1232168c36bb0c 100644 --- a/src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj +++ b/src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj @@ -14,6 +14,7 @@ + diff --git a/src/native/public/mono/metadata/details/class-functions.h b/src/native/public/mono/metadata/details/class-functions.h index 3d71d168d41e37..56eca59c2e83d4 100644 --- a/src/native/public/mono/metadata/details/class-functions.h +++ b/src/native/public/mono/metadata/details/class-functions.h @@ -131,6 +131,10 @@ MONO_API_FUNCTION(MONO_RT_EXTERNAL_ONLY mono_bool, mono_class_is_delegate, (Mono MONO_API_FUNCTION(MONO_RT_EXTERNAL_ONLY mono_bool, mono_class_implements_interface, (MonoClass* klass, MonoClass* iface)) +MONO_API_FUNCTION(int, mono_class_get_kind, (MonoClass *klass)) + +MONO_API_FUNCTION(int, mono_class_get_generic_params, (MonoClass *klass, MonoClass **result, int result_capacity)) + /* MonoClassField accessors */ MONO_API_FUNCTION(const char*, mono_field_get_name, (MonoClassField *field)) diff --git a/src/native/public/mono/metadata/profiler-events.h b/src/native/public/mono/metadata/profiler-events.h index 20300bff65f1c2..7de70559f42993 100644 --- a/src/native/public/mono/metadata/profiler-events.h +++ b/src/native/public/mono/metadata/profiler-events.h @@ -93,7 +93,7 @@ MONO_PROFILER_EVENT_1(gc_finalizing_object, GCFinalizingObject, MonoObject *, ob MONO_PROFILER_EVENT_1(gc_finalized_object, GCFinalizedObject, MonoObject *, object) MONO_PROFILER_EVENT_5(gc_root_register, RootRegister, const mono_byte *, start, uintptr_t, size, MonoGCRootSource, source, const void *, key, const char *, name) MONO_PROFILER_EVENT_1(gc_root_unregister, RootUnregister, const mono_byte *, start) -MONO_PROFILER_EVENT_3(gc_roots, GCRoots, uint64_t, count, const mono_byte *const *, addresses, MonoObject *const *, objects) +MONO_PROFILER_EVENT_4(gc_roots, GCRoots, uint64_t, count, const mono_byte *const *, addresses, MonoObject *const *, objects, const char *, kind) MONO_PROFILER_EVENT_1(monitor_contention, MonitorContention, MonoObject *, object) MONO_PROFILER_EVENT_1(monitor_failed, MonitorFailed, MonoObject *, object) diff --git a/src/native/public/mono/utils/details/mono-counters-functions.h b/src/native/public/mono/utils/details/mono-counters-functions.h index 73f8860494cb72..1e8080291db94c 100644 --- a/src/native/public/mono/utils/details/mono-counters-functions.h +++ b/src/native/public/mono/utils/details/mono-counters-functions.h @@ -30,15 +30,9 @@ MONO_API_FUNCTION(void, mono_counters_cleanup, (void)) MONO_API_FUNCTION(void, mono_counters_foreach, (CountersEnumCallback cb, void *user_data)) -MONO_API_FUNCTION(int, mono_counters_sample, (MonoCounter *counter, void *buffer, int buffer_size)) - MONO_API_FUNCTION(const char*, mono_counter_get_name, (MonoCounter *name)) MONO_API_FUNCTION(int, mono_counter_get_type, (MonoCounter *counter)) MONO_API_FUNCTION(int, mono_counter_get_section, (MonoCounter *counter)) MONO_API_FUNCTION(int, mono_counter_get_unit, (MonoCounter *counter)) MONO_API_FUNCTION(int, mono_counter_get_variance, (MonoCounter *counter)) MONO_API_FUNCTION(size_t, mono_counter_get_size, (MonoCounter *counter)) - -MONO_API_FUNCTION(int, mono_runtime_resource_limit, (int resource_type, uintptr_t soft_limit, uintptr_t hard_limit)) -MONO_API_FUNCTION(void, mono_runtime_resource_set_callback, (MonoResourceCallback callback)) -MONO_API_FUNCTION(void, mono_runtime_resource_check_limit, (int resource_type, uintptr_t value)) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs index b7559ddce65084..b1c2e44626c9d0 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -89,8 +89,10 @@ static void AddDictionary(StringBuilder sb, Dictionary? res) string resourceExtension = Path.GetExtension(resourceName); if (resourceName.StartsWith("dotnet.native.worker", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".mjs", StringComparison.OrdinalIgnoreCase)) return bootConfig.resources.jsModuleWorker ??= new(); - if (resourceName.StartsWith("dotnet.globalization", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) + else if (resourceName.StartsWith("dotnet.globalization", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) return bootConfig.resources.jsModuleGlobalization ??= new(); + else if (resourceName.StartsWith("diagnostics-panel", StringComparison.OrdinalIgnoreCase)) + return null; else if (resourceName.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) return bootConfig.resources.jsModuleNative ??= new(); else if (resourceName.StartsWith("dotnet.runtime", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase))