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))