diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props
index 7871e7d61a77da..bb0135898ad02c 100644
--- a/src/coreclr/clr.featuredefines.props
+++ b/src/coreclr/clr.featuredefines.props
@@ -22,8 +22,6 @@
         <!-- single thread -->
         <FeaturePortableTimer Condition="'$(WasmEnableThreads)' != 'true'">false</FeaturePortableTimer>
         <FeaturePortableThreadPool Condition="'$(WasmEnableThreads)' != 'true'">false</FeaturePortableThreadPool>
-        <!-- until we implement ST event pipe for CoreCLR -->
-        <FeaturePerfTracing>false</FeaturePerfTracing>
         <ProfilingSupportedBuild>false</ProfilingSupportedBuild>
     </PropertyGroup>
 
diff --git a/src/coreclr/clrdefinitions.cmake b/src/coreclr/clrdefinitions.cmake
index 0b37a72192cc33..b21c66f05adcba 100644
--- a/src/coreclr/clrdefinitions.cmake
+++ b/src/coreclr/clrdefinitions.cmake
@@ -79,9 +79,14 @@ add_definitions(-DFEATURE_DEFAULT_INTERFACES)
 if(FEATURE_EVENT_TRACE)
     add_compile_definitions(FEATURE_EVENT_TRACE)
     add_definitions(-DFEATURE_PERFTRACING)
-else(FEATURE_EVENT_TRACE)
+elseif(FEATURE_PERFTRACING)
+    add_definitions(-DFEATURE_PERFTRACING)
+    if(CLR_CROSS_COMPONENTS_BUILD)
+        add_custom_target(eventing_headers)
+    endif()
+else()
     add_custom_target(eventing_headers) # add a dummy target to avoid checking for FEATURE_EVENT_TRACE in multiple places
-endif(FEATURE_EVENT_TRACE)
+endif()
 if(FEATURE_GDBJIT)
     add_definitions(-DFEATURE_GDBJIT)
 endif()
diff --git a/src/coreclr/clrfeatures.cmake b/src/coreclr/clrfeatures.cmake
index 0cf973e1e8013c..d8f20db1d4c770 100644
--- a/src/coreclr/clrfeatures.cmake
+++ b/src/coreclr/clrfeatures.cmake
@@ -23,13 +23,19 @@ if(CLR_CMAKE_TARGET_TIZEN_LINUX)
   set(FEATURE_GDBJIT_LANGID_CS 1)
 endif()
 
+# FEATURE_EVENT_TRACE: Enables the full eventing infrastructure (generated FireEtw* functions,
+# EventPipe write calls, ETW on Windows). Set on all platforms except cross-component WASM builds.
 if(NOT DEFINED FEATURE_EVENT_TRACE)
-  if (NOT CLR_CMAKE_TARGET_BROWSER AND NOT (CLR_CROSS_COMPONENTS_BUILD AND CLR_CMAKE_TARGET_ARCH_WASM))
+  if (NOT (CLR_CROSS_COMPONENTS_BUILD AND CLR_CMAKE_TARGET_ARCH_WASM))
     # To actually disable FEATURE_EVENT_TRACE, also change clr.featuredefines.props
     set(FEATURE_EVENT_TRACE 1)
   endif()
 endif(NOT DEFINED FEATURE_EVENT_TRACE)
 
+# FEATURE_EVENTSOURCE_XPLAT: Enables the LTTng-based XplatEventLogger path (LTTNG_TRACE_CONTEXT,
+# XplatEventLogger, XplatEventLoggerController, and the generated FireEtXplat* functions).
+# Currently Linux-only (excluding Android). Used as the sole guard for all LTTng-related code;
+# platforms without this flag (Windows, macOS, Browser, WASI) use EventPipe-only eventing.
 if(NOT DEFINED FEATURE_EVENTSOURCE_XPLAT)
   if (CLR_CMAKE_TARGET_LINUX AND NOT CLR_CMAKE_TARGET_ANDROID AND NOT (CLR_CROSS_COMPONENTS_BUILD AND CLR_CMAKE_TARGET_ARCH_WASM))
     # To actually disable FEATURE_EVENTSOURCE_XPLAT, also change clr.featuredefines.props
@@ -37,9 +43,14 @@ if(NOT DEFINED FEATURE_EVENTSOURCE_XPLAT)
   endif()
 endif(NOT DEFINED FEATURE_EVENTSOURCE_XPLAT)
 
-if(NOT DEFINED FEATURE_PERFTRACING AND FEATURE_EVENT_TRACE)
-  set(FEATURE_PERFTRACING 1)
-endif(NOT DEFINED FEATURE_PERFTRACING AND FEATURE_EVENT_TRACE)
+# FEATURE_PERFTRACING: Enables the EventPipe diagnostics subsystem (event sessions, IPC protocol,
+# diagnostic server). Set whenever FEATURE_EVENT_TRACE is set, or when threadless perftracing is
+# explicitly requested via FEATURE_PERFTRACING_DISABLE_THREADS.
+if(NOT DEFINED FEATURE_PERFTRACING)
+  if(FEATURE_EVENT_TRACE OR FEATURE_PERFTRACING_DISABLE_THREADS)
+    set(FEATURE_PERFTRACING 1)
+  endif()
+endif(NOT DEFINED FEATURE_PERFTRACING)
 
 if(NOT DEFINED FEATURE_DBGIPC)
   if(CLR_CMAKE_TARGET_UNIX)
diff --git a/src/coreclr/inc/eventtracebase.h b/src/coreclr/inc/eventtracebase.h
index b81b8386bcc0dc..16cb14bf278228 100644
--- a/src/coreclr/inc/eventtracebase.h
+++ b/src/coreclr/inc/eventtracebase.h
@@ -121,6 +121,7 @@ enum EtwGCSettingFlags
 #define ETWFireEvent(EventName) FireEtw##EventName(GetClrInstanceId())
 
 #define ETW_TRACING_INITIALIZED(RegHandle) (TRUE)
+#if defined(FEATURE_EVENTSOURCE_XPLAT)
 #define ETW_EVENT_ENABLED(Context, EventDescriptor) (EventPipeHelper::IsEnabled(Context, EventDescriptor.Level, EventDescriptor.Keyword) || \
         (XplatEventLogger::IsKeywordEnabled(Context, EventDescriptor.Level, EventDescriptor.Keyword)))
 #define ETW_CATEGORY_ENABLED(Context, Level, Keyword) (EventPipeHelper::IsEnabled(Context, Level, Keyword) || \
@@ -128,6 +129,12 @@ enum EtwGCSettingFlags
 #define ETW_TRACING_ENABLED(Context, EventDescriptor) (EventEnabled##EventDescriptor())
 #define ETW_TRACING_CATEGORY_ENABLED(Context, Level, Keyword) (EventPipeHelper::IsEnabled(Context, Level, Keyword) || \
         (XplatEventLogger::IsKeywordEnabled(Context, Level, Keyword)))
+#else // FEATURE_EVENTSOURCE_XPLAT
+#define ETW_EVENT_ENABLED(Context, EventDescriptor) (EventPipeHelper::IsEnabled(Context, EventDescriptor.Level, EventDescriptor.Keyword))
+#define ETW_CATEGORY_ENABLED(Context, Level, Keyword) (EventPipeHelper::IsEnabled(Context, Level, Keyword))
+#define ETW_TRACING_ENABLED(Context, EventDescriptor) (EventEnabled##EventDescriptor())
+#define ETW_TRACING_CATEGORY_ENABLED(Context, Level, Keyword) (EventPipeHelper::IsEnabled(Context, Level, Keyword))
+#endif // FEATURE_EVENTSOURCE_XPLAT
 #define ETW_PROVIDER_ENABLED(ProviderSymbol) (TRUE)
 #else //defined(FEATURE_PERFTRACING)
 #define ETW_INLINE
@@ -248,16 +255,11 @@ struct ProfilingScanContext;
 extern UINT32 g_nClrInstanceId;
 
 #define GetClrInstanceId()  (static_cast<UINT16>(g_nClrInstanceId))
-#if defined(HOST_UNIX) && (defined(FEATURE_EVENT_TRACE) || defined(FEATURE_EVENTSOURCE_XPLAT))
+#if defined(HOST_UNIX) && defined(FEATURE_EVENT_TRACE)
 #define KEYWORDZERO 0x0
-
-#define DEF_LTTNG_KEYWORD_ENABLED 1
-#ifdef FEATURE_EVENT_TRACE
 #include "clrproviders.h"
-#endif // FEATURE_EVENT_TRACE
 #include "clrconfig.h"
-
-#endif // defined(HOST_UNIX) && (defined(FEATURE_EVENT_TRACE) || defined(FEATURE_EVENTSOURCE_XPLAT))
+#endif // defined(HOST_UNIX) && defined(FEATURE_EVENT_TRACE)
 
 #if defined(FEATURE_PERFTRACING) || defined(FEATURE_EVENTSOURCE_XPLAT)
 
@@ -416,7 +418,7 @@ class XplatEventLoggerConfiguration
 };
 #endif // defined(FEATURE_PERFTRACING) || defined(FEATURE_EVENTSOURCE_XPLAT)
 
-#if defined(HOST_UNIX) && (defined(FEATURE_EVENT_TRACE) || defined(FEATURE_EVENTSOURCE_XPLAT))
+#if defined(FEATURE_EVENTSOURCE_XPLAT)
 
 class XplatEventLoggerController
 {
@@ -436,7 +438,7 @@ class XplatEventLoggerController
         {
             ActivateAllKeywordsOfAllProviders();
         }
-#ifdef FEATURE_EVENT_TRACE
+#if defined(FEATURE_EVENT_TRACE) && defined(HOST_UNIX)
         else
         {
             LTTNG_TRACE_CONTEXT *provider = GetProvider(providerName);
@@ -453,7 +455,7 @@ class XplatEventLoggerController
 
     static void ActivateAllKeywordsOfAllProviders()
     {
-#ifdef FEATURE_EVENT_TRACE
+#if defined(FEATURE_EVENT_TRACE) && defined(HOST_UNIX)
         for (LTTNG_TRACE_CONTEXT * const provider : ALL_LTTNG_PROVIDERS_CONTEXT)
         {
             provider->EnabledKeywordsBitmask = (ULONGLONG)(-1);
@@ -464,7 +466,7 @@ class XplatEventLoggerController
     }
 
 private:
-#ifdef FEATURE_EVENT_TRACE
+#if defined(FEATURE_EVENT_TRACE) && defined(HOST_UNIX)
     static LTTNG_TRACE_CONTEXT * const GetProvider(LPCWSTR providerName)
     {
         auto length = u16_strlen(providerName);
@@ -490,7 +492,7 @@ class XplatEventLogger
         return configEventLogging.val(CLRConfig::EXTERNAL_EnableEventLog);
     }
 
-#ifdef FEATURE_EVENT_TRACE
+#if defined(FEATURE_EVENT_TRACE) && defined(HOST_UNIX)
     inline static bool IsProviderEnabled(DOTNET_TRACE_CONTEXT providerCtx)
     {
         return providerCtx.LttngProvider->IsEnabled;
@@ -557,7 +559,7 @@ class XplatEventLogger
 };
 
 
-#endif  // defined(HOST_UNIX) && (defined(FEATURE_EVENT_TRACE) || defined(FEATURE_EVENTSOURCE_XPLAT))
+#endif  // defined(FEATURE_EVENTSOURCE_XPLAT)
 
 #if defined(FEATURE_EVENT_TRACE)
 
diff --git a/src/coreclr/interpreter/CMakeLists.txt b/src/coreclr/interpreter/CMakeLists.txt
index fb6dd25cc4c9c6..f4f88b9ed3260b 100644
--- a/src/coreclr/interpreter/CMakeLists.txt
+++ b/src/coreclr/interpreter/CMakeLists.txt
@@ -47,6 +47,10 @@ add_library_clr(clrinterpreter_objects OBJECT ${INTERPRETER_SOURCES})
 
 target_include_directories(clrinterpreter_objects PRIVATE ../jitshared)
 
+if (FEATURE_PERFTRACING_DISABLE_THREADS)
+  target_compile_definitions(clrinterpreter_objects PRIVATE PERFTRACING_DISABLE_THREADS)
+endif()
+
 if (NOT CMAKE_GENERATOR MATCHES "Visual Studio")
   set_target_properties(clrinterpreter_objects PROPERTIES EXCLUDE_FROM_ALL $<NOT:${FEATURE_INTERPRETER}>)
 endif()
diff --git a/src/coreclr/scripts/genEventPipe.py b/src/coreclr/scripts/genEventPipe.py
index 762fcffe94bdb4..6f801099df02e3 100644
--- a/src/coreclr/scripts/genEventPipe.py
+++ b/src/coreclr/scripts/genEventPipe.py
@@ -181,12 +181,20 @@ def generateClrEventPipeWriteEventsImpl(
         "void Init" +
         providerPrettyName +
         "(void)\n{\n")
+    if runtimeFlavor.coreclr:
+        callbackExpr = ("\n#ifdef FEATURE_EVENT_TRACE\n" +
+            "        " + eventPipeCallbackCastExpr + "(" + callbackName + ")\n" +
+            "#else\n" +
+            "        nullptr\n" +
+            "#endif\n    ")
+    else:
+        callbackExpr = eventPipeCallbackCastExpr + "(" + callbackName + ")"
     WriteEventImpl.append(
         "    EventPipeProvider" +
         providerPrettyName +
         " = " + createProviderFunc + "(" +
         providerPrettyName +
-        "Name, " + eventPipeCallbackCastExpr + "(" + callbackName + "));\n")
+        "Name, " + callbackExpr + ");\n")
     for eventNode in eventNodes:
         eventName = eventNode.getAttribute('symbol')
         templateName = eventNode.getAttribute('template')
diff --git a/src/coreclr/scripts/genEventing.py b/src/coreclr/scripts/genEventing.py
index 9aaa0df0e085b1..6d4006a4c49a28 100644
--- a/src/coreclr/scripts/genEventing.py
+++ b/src/coreclr/scripts/genEventing.py
@@ -439,12 +439,15 @@ def generateClrallEvents(eventNodes, allTemplates, target_cpp, runtimeFlavor, is
 
                 if runtimeFlavor.coreclr or write_xplatheader or runtimeFlavor.nativeaot:
                     if not is_host_windows:
-                        # native AOT does not support non-windows eventing other than via event pipe
+                        # native AOT and browser/wasi do not support non-windows eventing other than via event pipe
                         if not runtimeFlavor.nativeaot:
-                            clrallEvents.append(" || (XplatEventLogger" +
+                            clrallEvents.append("\n#if defined(FEATURE_EVENTSOURCE_XPLAT)\n")
+                            clrallEvents.append("    || (XplatEventLogger" +
                             ("::" if target_cpp else "_") +
                             "IsEventLoggingEnabled() && EventXplatEnabled" +
-                            eventName + "());}\n\n")
+                            eventName + "())\n")
+                            clrallEvents.append("#endif // FEATURE_EVENTSOURCE_XPLAT\n")
+                            clrallEvents.append(";}\n\n")
                         else:
                             clrallEvents.append(";}\n\n")
                     else:
@@ -534,8 +537,12 @@ def generateClrallEvents(eventNodes, allTemplates, target_cpp, runtimeFlavor, is
             fnbody.append("ActivityId,RelatedActivityId);\n")
 
             if runtimeFlavor.coreclr or write_xplatheader:
+                if not is_host_windows:
+                    fnbody.append("#if defined(FEATURE_EVENTSOURCE_XPLAT)\n")
                 fnbody.append(lindent)
                 fnbody.append("status &= FireEtXplat" + eventName + "(" + ''.join(line) + ");\n")
+                if not is_host_windows:
+                    fnbody.append("#endif // FEATURE_EVENTSOURCE_XPLAT\n")
 
             if runtimeFlavor.nativeaot:
                 if providerName == "Microsoft-Windows-DotNETRuntime" or providerName == "Microsoft-Windows-DotNETRuntimePrivate" or providerName == "Microsoft-Windows-DotNETRuntimeRundown":
@@ -868,7 +875,9 @@ def generatePlatformIndependentFiles(sClrEtwAllMan, incDir, etmDummyFile, extern
 typedef struct _DOTNET_TRACE_CONTEXT
 {
     EVENTPIPE_TRACE_CONTEXT EventPipeProvider;
+#if defined(FEATURE_EVENTSOURCE_XPLAT)
     PLTTNG_TRACE_CONTEXT LttngProvider;
+#endif
 } DOTNET_TRACE_CONTEXT, *PDOTNET_TRACE_CONTEXT;
 #endif // DOTNET_TRACE_CONTEXT_DEF
 """
@@ -894,7 +903,9 @@ def generatePlatformIndependentFiles(sClrEtwAllMan, incDir, etmDummyFile, extern
 """)
             if not is_host_windows and not runtimeFlavor.nativeaot:
                 Clrproviders.write(eventpipe_trace_context_typedef)  # define EVENTPIPE_TRACE_CONTEXT
+                Clrproviders.write("#if defined(FEATURE_EVENTSOURCE_XPLAT)\n")
                 Clrproviders.write(lttng_trace_context_typedef)  # define LTTNG_TRACE_CONTEXT
+                Clrproviders.write("#endif // FEATURE_EVENTSOURCE_XPLAT\n")
                 Clrproviders.write(dotnet_trace_context_typedef_unix + "\n")
 
             allProviders = []
@@ -910,7 +921,9 @@ def generatePlatformIndependentFiles(sClrEtwAllMan, incDir, etmDummyFile, extern
                     eventpipeProviderCtxName = providerSymbol + "_EVENTPIPE_Context"
                     Clrproviders.write('__attribute__((weak)) EVENTPIPE_TRACE_CONTEXT ' + eventpipeProviderCtxName + ' = { W("' + providerName + '"), 0, false, 0 };\n')
                     lttngProviderCtxName = providerSymbol + "_LTTNG_Context"
+                    Clrproviders.write('#if defined(FEATURE_EVENTSOURCE_XPLAT)\n')
                     Clrproviders.write('__attribute__((weak)) LTTNG_TRACE_CONTEXT ' + lttngProviderCtxName + ' = { W("' + providerName + '"), 0, false, 0 };\n')
+                    Clrproviders.write('#endif // FEATURE_EVENTSOURCE_XPLAT\n')
 
                 Clrproviders.write("// Keywords\n");
                 for keywordNode in providerNode.getElementsByTagName('keyword'):
@@ -934,10 +947,12 @@ def generatePlatformIndependentFiles(sClrEtwAllMan, incDir, etmDummyFile, extern
 
             # define and initialize runtime providers' DOTNET_TRACE_CONTEXT depending on the platform
             if not is_host_windows and not runtimeFlavor.nativeaot:
+                Clrproviders.write('#if defined(FEATURE_EVENTSOURCE_XPLAT)\n')
                 Clrproviders.write('#define NB_PROVIDERS ' + str(nbProviders) + '\n')
                 Clrproviders.write(('constexpr ' if target_cpp else 'static const ') + 'LTTNG_TRACE_CONTEXT * ALL_LTTNG_PROVIDERS_CONTEXT[NB_PROVIDERS] = { ')
                 Clrproviders.write(', '.join(allProviders))
                 Clrproviders.write(' };\n')
+                Clrproviders.write('#endif // FEATURE_EVENTSOURCE_XPLAT\n')
 
 
     clreventpipewriteevents = os.path.join(incDir, "clreventpipewriteevents.h")
diff --git a/src/coreclr/vm/eventing/CMakeLists.txt b/src/coreclr/vm/eventing/CMakeLists.txt
index 39fb58c2f46daf..f5faa23294c328 100644
--- a/src/coreclr/vm/eventing/CMakeLists.txt
+++ b/src/coreclr/vm/eventing/CMakeLists.txt
@@ -40,10 +40,13 @@ add_custom_command(
 set_source_files_properties(${EventingHeaders} PROPERTIES GENERATED TRUE)
 
 add_custom_target(eventing_headers DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/eventing_headers.timestamp)
-add_dependencies(eventing_headers eventprovider)
-
-add_subdirectory(eventpipe)
 
 if(CLR_CMAKE_HOST_WIN32)
   add_subdirectory(EtwProvider)
 endif()
+
+if (TARGET eventprovider)
+  add_dependencies(eventing_headers eventprovider)
+endif()
+
+add_subdirectory(eventpipe)
diff --git a/src/coreclr/vm/eventing/eventpipe/CMakeLists.txt b/src/coreclr/vm/eventing/eventpipe/CMakeLists.txt
index e6a4ba24e3bead..5822b478b2d63e 100644
--- a/src/coreclr/vm/eventing/eventpipe/CMakeLists.txt
+++ b/src/coreclr/vm/eventing/eventpipe/CMakeLists.txt
@@ -44,6 +44,7 @@ add_library_clr(eventpipe_gen_objs OBJECT ${GEN_EVENTPIPE_SOURCES})
 target_precompile_headers(eventpipe_gen_objs PRIVATE [["common.h"]])
 set_target_properties(eventpipe_gen_objs PROPERTIES LINKER_LANGUAGE CXX)
 add_dependencies(eventpipe_gen_objs eventing_headers)
+target_compile_definitions(eventpipe_gen_objs PRIVATE FEATURE_PERFTRACING)
 
 add_library_clr(eventpipe_objs OBJECT ${CORECLR_EVENTPIPE_SHIM_SOURCES} ${CORECLR_EVENTPIPE_SHIM_HEADERS})
 set_target_properties(eventpipe_objs PROPERTIES
@@ -53,6 +54,10 @@ set_target_properties(eventpipe_objs PROPERTIES
 )
 add_dependencies(eventpipe_objs eventing_headers)
 target_link_libraries(eventpipe_objs PRIVATE dn-diagnosticserver dn-eventpipe)
+target_compile_definitions(eventpipe_objs PRIVATE FEATURE_PERFTRACING)
+if (FEATURE_PERFTRACING_DISABLE_THREADS)
+  target_compile_definitions(eventpipe_objs PRIVATE PERFTRACING_DISABLE_THREADS)
+endif()
 
 # The CoreCLR EventPipe runtime is C++, but the EventPipe and DiagnosticServer sources are C.
 # Forcibly override them to build as C++.
diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
index ebb46b8eb5396c..bc331427218e0b 100644
--- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
+++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
@@ -434,11 +434,13 @@ ep_rt_provider_config_init (EventPipeProviderConfiguration *provider_config)
 {
 	STATIC_CONTRACT_NOTHROW;
 
+#ifdef FEATURE_EVENT_TRACE
 	if (!ep_rt_utf8_string_compare (ep_config_get_rundown_provider_name_utf8 (), ep_provider_config_get_provider_name (provider_config))) {
 		MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_DOTNET_Context.EventPipeProvider.Level = (UCHAR) ep_provider_config_get_logging_level (provider_config);
 		MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_DOTNET_Context.EventPipeProvider.EnabledKeywordsBitmask = ep_provider_config_get_keywords (provider_config);
 		MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_DOTNET_Context.EventPipeProvider.IsEnabled = true;
 	}
+#endif
 }
 
 // This function is auto-generated from /src/scripts/genEventPipe.py
@@ -469,9 +471,13 @@ ep_rt_providers_validate_all_disabled (void)
 {
 	STATIC_CONTRACT_NOTHROW;
 
+#ifdef FEATURE_EVENT_TRACE
 	return (!MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context.EventPipeProvider.IsEnabled &&
 		!MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_DOTNET_Context.EventPipeProvider.IsEnabled &&
 		!MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_DOTNET_Context.EventPipeProvider.IsEnabled);
+#else
+	return true;
+#endif
 }
 
 static
@@ -667,6 +673,8 @@ ep_rt_byte_array_free (uint8_t *ptr)
  * Event.
  */
 
+#ifndef PERFTRACING_DISABLE_THREADS
+
 static
 void
 ep_rt_wait_event_alloc (
@@ -765,6 +773,79 @@ ep_rt_wait_event_is_valid (ep_rt_wait_event_handle_t *wait_event)
 	return wait_event->event->IsValid ();
 }
 
+#else // PERFTRACING_DISABLE_THREADS
+
+// In single-threaded mode, wait events are no-ops. INVALID_HANDLE_VALUE is used as
+// the "allocated" sentinel (distinguishing allocated from freed/NULL).
+
+static
+inline
+void
+ep_rt_wait_event_alloc (
+	ep_rt_wait_event_handle_t *wait_event,
+	bool manual,
+	bool initial)
+{
+	STATIC_CONTRACT_NOTHROW;
+	EP_ASSERT (wait_event != NULL);
+	wait_event->event = (CLREventStatic *)INVALID_HANDLE_VALUE;
+}
+
+static
+inline
+void
+ep_rt_wait_event_free (ep_rt_wait_event_handle_t *wait_event)
+{
+	STATIC_CONTRACT_NOTHROW;
+	wait_event->event = NULL;
+}
+
+static
+inline
+bool
+ep_rt_wait_event_set (ep_rt_wait_event_handle_t *wait_event)
+{
+	STATIC_CONTRACT_NOTHROW;
+	return true;
+}
+
+static
+inline
+int32_t
+ep_rt_wait_event_wait (
+	ep_rt_wait_event_handle_t *wait_event,
+	uint32_t timeout,
+	bool alertable)
+{
+	STATIC_CONTRACT_NOTHROW;
+	EP_ASSERT (wait_event != NULL && wait_event->event == (CLREventStatic *)INVALID_HANDLE_VALUE);
+	return (int32_t)0;
+}
+
+static
+inline
+EventPipeWaitHandle
+ep_rt_wait_event_get_wait_handle (ep_rt_wait_event_handle_t *wait_event)
+{
+	STATIC_CONTRACT_NOTHROW;
+	EP_ASSERT (wait_event != NULL);
+	return (EventPipeWaitHandle)wait_event->event;
+}
+
+static
+inline
+bool
+ep_rt_wait_event_is_valid (ep_rt_wait_event_handle_t *wait_event)
+{
+	STATIC_CONTRACT_NOTHROW;
+	if (wait_event == NULL || wait_event->event == NULL || wait_event->event != (CLREventStatic *)INVALID_HANDLE_VALUE)
+		return false;
+	return true;
+}
+
+#endif // PERFTRACING_DISABLE_THREADS
+
+
 /*
  * Misc.
  */
@@ -851,6 +932,7 @@ typedef struct _rt_coreclr_thread_params_internal_t {
 #undef EP_RT_DEFINE_THREAD_FUNC
 #define EP_RT_DEFINE_THREAD_FUNC(name) static ep_rt_thread_start_func_return_t WINAPI name (LPVOID data)
 
+#ifndef PERFTRACING_DISABLE_THREADS
 EP_RT_DEFINE_THREAD_FUNC (ep_rt_thread_coreclr_start_func)
 {
 	STATIC_CONTRACT_NOTHROW;
@@ -934,9 +1016,48 @@ ep_rt_queue_job (
 	void *job_func,
 	void *params)
 {
-    EP_UNREACHABLE ("Not implemented in CoreCLR");
+	EP_UNREACHABLE ("Not implemented in multi-threaded");
+	return false;
+}
+
+#else // PERFTRACING_DISABLE_THREADS
+
+static
+inline
+bool
+ep_rt_thread_create (
+	void *thread_func,
+	void *params,
+	EventPipeThreadType thread_type,
+	void *id)
+{
+	EP_UNREACHABLE ("Not implemented in single-threaded");
+	return false;
+}
+
+#ifdef HOST_BROWSER
+#include "wasm/entrypoints.h"
+typedef size_t (*ep_rt_job_cb_t)(void *data);
+#endif
+
+static
+bool
+ep_rt_queue_job (
+	void *job_func,
+	void *params)
+{
+#ifdef HOST_BROWSER
+	// In single-threaded mode the job runs on the browser event loop
+	SystemJS_DiagnosticServerQueueJob ((ep_rt_job_cb_t)job_func, params);
+	return true;
+#else
+	EP_UNREACHABLE ("Not implemented on this platform");
+	return false;
+#endif
 }
 
+#endif // PERFTRACING_DISABLE_THREADS
+
 static
 inline
 void
@@ -950,6 +1071,7 @@ inline
 void
 ep_rt_thread_sleep (uint64_t ns)
 {
+#ifndef PERFTRACING_DISABLE_THREADS
 	STATIC_CONTRACT_NOTHROW;
 
 #ifdef TARGET_UNIX
@@ -958,6 +1080,7 @@ ep_rt_thread_sleep (uint64_t ns)
 	const uint32_t NUM_NANOSECONDS_IN_1_MS = 1000000;
 	ClrSleepEx (static_cast<DWORD>(ns / NUM_NANOSECONDS_IN_1_MS), FALSE);
 #endif //TARGET_UNIX
+#endif // PERFTRACING_DISABLE_THREADS
 }
 
 static
diff --git a/src/coreclr/vm/eventtrace.cpp b/src/coreclr/vm/eventtrace.cpp
index cd4cff43f3c88d..53edc1c17933cd 100644
--- a/src/coreclr/vm/eventtrace.cpp
+++ b/src/coreclr/vm/eventtrace.cpp
@@ -50,11 +50,16 @@ DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context = {
 DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_DOTNET_Context = { &MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_EVENTPIPE_Context };
 DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_DOTNET_Context = { &MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_Context, MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_EVENTPIPE_Context };
 DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_DOTNET_Context = { &MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_Context, MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_EVENTPIPE_Context };
-#else
+#elif defined(FEATURE_EVENTSOURCE_XPLAT)
 DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_EVENTPIPE_Context, &MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_LTTNG_Context };
 DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_EVENTPIPE_Context, &MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_LTTNG_Context };
 DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_EVENTPIPE_Context, &MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_LTTNG_Context };
 DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_EVENTPIPE_Context, &MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_LTTNG_Context };
+#else
+DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_EVENTPIPE_Context };
+DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_EVENTPIPE_Context };
+DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_EVENTPIPE_Context };
+DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_EVENTPIPE_Context };
 #endif // HOST_UNIX
 
 #ifdef FEATURE_NATIVEAOT
@@ -2277,9 +2282,9 @@ void InitializeEventTracing()
     // providers can do so now
     ETW::TypeSystemLog::PostRegistrationInit();
 
-#if defined(HOST_UNIX) && defined (FEATURE_PERFTRACING)
+#if defined(FEATURE_EVENTSOURCE_XPLAT)
     XplatEventLogger::InitializeLogger();
-#endif // HOST_UNIX && FEATURE_PERFTRACING
+#endif // FEATURE_EVENTSOURCE_XPLAT
 }
 
 // Plumbing to funnel event pipe callbacks and ETW callbacks together into a single common
@@ -5607,10 +5612,10 @@ bool EventPipeHelper::IsEnabled(DOTNET_TRACE_CONTEXT Context, UCHAR Level, ULONG
 }
 #endif // FEATURE_PERFTRACING
 
-#if defined(HOST_UNIX)  && defined(FEATURE_PERFTRACING)
+#if defined(FEATURE_EVENTSOURCE_XPLAT)
 // This is a wrapper method for LTTng. See https://github.com/dotnet/coreclr/pull/27273 for details.
 extern "C" bool XplatEventLoggerIsEnabled()
 {
     return XplatEventLogger::IsEventLoggingEnabled();
 }
-#endif // HOST_UNIX && FEATURE_PERFTRACING
+#endif // FEATURE_EVENTSOURCE_XPLAT
diff --git a/src/coreclr/vm/eventtrace_bulktype.cpp b/src/coreclr/vm/eventtrace_bulktype.cpp
index 3b148be7627a07..4eac600acfa2c4 100644
--- a/src/coreclr/vm/eventtrace_bulktype.cpp
+++ b/src/coreclr/vm/eventtrace_bulktype.cpp
@@ -420,9 +420,11 @@ void BulkStaticsLogger::FireBulkStaticsEvent()
     EventDataDescCreate(&eventData[3], m_buffer, m_used);
 
     ULONG result = EventWrite(Microsoft_Windows_DotNETRuntimeHandle, &GCBulkRootStaticVar, ARRAY_SIZE(eventData), eventData);
-#else
+#elif defined(FEATURE_EVENTSOURCE_XPLAT)
     ULONG result = FireEtXplatGCBulkRootStaticVar(m_count, appDomain, instance, m_used, m_buffer);
-#endif //!defined(HOST_UNIX)
+#else
+    ULONG result = ERROR_SUCCESS;
+#endif
     result |= EventPipeWriteEventGCBulkRootStaticVar(m_count, appDomain, instance, m_used, m_buffer);
 
     _ASSERTE(result == ERROR_SUCCESS);
diff --git a/src/coreclr/vm/eventtrace_gcheap.cpp b/src/coreclr/vm/eventtrace_gcheap.cpp
index d8b395941218f8..2c9c3df6e2f473 100644
--- a/src/coreclr/vm/eventtrace_gcheap.cpp
+++ b/src/coreclr/vm/eventtrace_gcheap.cpp
@@ -408,6 +408,24 @@ VOID ETW::GCLog::EndMovedReferences(size_t profilingContext, BOOL fAllowProfApiN
     delete pContext;
 }
 
+#if defined(TARGET_BROWSER)
+#include "wasm/entrypoints.h"
+
+static size_t ForceGCForDiagnosticsJob(void* data)
+{
+    CONTRACTL
+    {
+        NOTHROW;
+        GC_TRIGGERS;
+        MODE_ANY;
+    }
+    CONTRACTL_END;
+
+    ETW::GCLog::ForceGCForDiagnostics();
+    return 1; // done
+}
+#endif // TARGET_BROWSER
+
 // This implements the public runtime provider's GCHeapCollectKeyword.  It
 // performs a full, gen-2, blocking GC.
 VOID ETW::GCLog::ForceGC(LONGLONG l64ClientSequenceNumber)
@@ -427,7 +445,14 @@ VOID ETW::GCLog::ForceGC(LONGLONG l64ClientSequenceNumber)
 
     InterlockedExchange64(&s_l64LastClientSequenceNumber, l64ClientSequenceNumber);
 
+#if defined(TARGET_BROWSER)
+    // On single-threaded browser, we cannot call ForceGCForDiagnostics synchronously
+    // from within the provider callback (which runs during ep_enable_3 under the
+    // EventPipe lock). Defer the GC to the next event loop turn.
+    SystemJS_DiagnosticServerQueueJob(ForceGCForDiagnosticsJob, NULL);
+#else
     ForceGCForDiagnostics();
+#endif // TARGET_BROWSER
 }
 
 //---------------------------------------------------------------------------------------
diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp
index f1b400afbbac6c..d65fb6b022419d 100644
--- a/src/coreclr/vm/gcenv.ee.cpp
+++ b/src/coreclr/vm/gcenv.ee.cpp
@@ -878,7 +878,7 @@ void GCToEEInterface::DiagUpdateGenerationBounds()
 
 void GCToEEInterface::DiagGCEnd(size_t index, int gen, int reason, bool fConcurrent)
 {
-#ifdef GC_PROFILING
+#if defined(GC_PROFILING) || defined(PERFTRACING_DISABLE_THREADS)
     // We were only doing generation bounds and GC finish callback for non concurrent GCs so
     // I am keeping that behavior to not break profilers. But if BasicGC monitoring is enabled
     // we will do these for all GCs.
@@ -886,7 +886,9 @@ void GCToEEInterface::DiagGCEnd(size_t index, int gen, int reason, bool fConcurr
     {
         GCProfileWalkHeap(false);
     }
+#endif // defined(GC_PROFILING) || defined(PERFTRACING_DISABLE_THREADS)
 
+#ifdef GC_PROFILING
     if (CORProfilerTrackBasicGC() || (!fConcurrent && CORProfilerTrackGC()))
     {
         DiagUpdateGenerationBounds();
diff --git a/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp b/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp
index 849e33a9cf07b9..08980a9ddf67e2 100644
--- a/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp
+++ b/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp
@@ -298,6 +298,12 @@ namespace
         *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I64(2));
     }
 
+    static void CallFunc_I32_I32_I64_I32_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+    {
+        int32_t (*fptr)(int32_t, int32_t, int64_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int64_t, int32_t, int32_t, int32_t, int32_t))pcode;
+        *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I64(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6));
+    }
+
     NOINLINE static void CallFunc_I32_I32_RetI32_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet)
     {
         alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK;
@@ -342,6 +348,18 @@ namespace
         *((int32_t*)pRet) = (*fptr)(&framePointer, ARG_I32(0), pPortableEntryPoint);
     }
 
+    static void CallFunc_I64_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+    {
+        int32_t (*fptr)(int64_t) = (int32_t (*)(int64_t))pcode;
+        *((int32_t*)pRet) = (*fptr)(ARG_I64(0));
+    }
+
+    static void CallFunc_I64_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+    {
+        int32_t (*fptr)(int64_t, int32_t) = (int32_t (*)(int64_t, int32_t))pcode;
+        *((int32_t*)pRet) = (*fptr)(ARG_I64(0), ARG_I32(1));
+    }
+
     static void CallFunc_I64_I32_I64_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
     {
         int32_t (*fptr)(int64_t, int32_t, int64_t, int32_t) = (int32_t (*)(int64_t, int32_t, int64_t, int32_t))pcode;
@@ -373,6 +391,12 @@ namespace
         *((int64_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2));
     }
 
+    static void CallFunc_I32_I32_I32_I32_I32_RetI64(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+    {
+        int64_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (int64_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode;
+        *((int64_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4));
+    }
+
     static void CallFunc_I32_I32_I32_I64_RetI64(PCODE pcode, int8_t* pArgs, int8_t* pRet)
     {
         int64_t (*fptr)(int32_t, int32_t, int32_t, int64_t) = (int64_t (*)(int32_t, int32_t, int32_t, int64_t))pcode;
@@ -493,6 +517,24 @@ namespace
         (*fptr)(&framePointer, ARG_I32(0), pPortableEntryPoint);
     }
 
+    static void CallFunc_F64_F64_F64_F64_F64_F64_F64_F64_F64_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+    {
+        void (*fptr)(double, double, double, double, double, double, double, double, double, int32_t, int32_t) = (void (*)(double, double, double, double, double, double, double, double, double, int32_t, int32_t))pcode;
+        (*fptr)(ARG_F64(0), ARG_F64(1), ARG_F64(2), ARG_F64(3), ARG_F64(4), ARG_F64(5), ARG_F64(6), ARG_F64(7), ARG_F64(8), ARG_I32(9), ARG_I32(10));
+    }
+
+    static void CallFunc_F64_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+    {
+        void (*fptr)(double, int32_t) = (void (*)(double, int32_t))pcode;
+        (*fptr)(ARG_F64(0), ARG_I32(1));
+    }
+
+    static void CallFunc_F64_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+    {
+        void (*fptr)(double, int32_t, int32_t, int32_t) = (void (*)(double, int32_t, int32_t, int32_t))pcode;
+        (*fptr)(ARG_F64(0), ARG_I32(1), ARG_I32(2), ARG_I32(3));
+    }
+
     NOINLINE static void CallFunc_F64_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet)
     {
         alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK;
@@ -537,6 +579,12 @@ namespace
         (*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2), ARG_IND(3), ARG_I32(4), ARG_I32(5));
     }
 
+    static void CallFunc_I32_I32_F64_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+    {
+        void (*fptr)(int32_t, int32_t, double) = (void (*)(int32_t, int32_t, double))pcode;
+        (*fptr)(ARG_I32(0), ARG_I32(1), ARG_F64(2));
+    }
+
     static void CallFunc_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
     {
         void (*fptr)(int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t))pcode;
@@ -573,6 +621,12 @@ namespace
         (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5));
     }
 
+    static void CallFunc_I32_I32_I32_I32_I64_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+    {
+        void (*fptr)(int32_t, int32_t, int32_t, int32_t, int64_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int64_t))pcode;
+        (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I64(4));
+    }
+
     NOINLINE static void CallFunc_I32_I32_I32_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet)
     {
         alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK;
@@ -653,6 +707,7 @@ const StringToWasmSigThunk g_wasmThunks[] = {
     { "Miiiil", (void*)&CallFunc_I32_I32_I32_I64_RetI32 },
     { "Miiiip", (void*)&CallFunc_I32_I32_I32_RetI32_PE },
     { "Miiil", (void*)&CallFunc_I32_I32_I64_RetI32 },
+    { "Miiiliiii", (void*)&CallFunc_I32_I32_I64_I32_I32_I32_I32_RetI32 },
     { "Miiip", (void*)&CallFunc_I32_I32_RetI32_PE },
     { "Miil", (void*)&CallFunc_I32_I64_RetI32 },
     { "Miili", (void*)&CallFunc_I32_I64_I32_RetI32 },
@@ -660,11 +715,14 @@ const StringToWasmSigThunk g_wasmThunks[] = {
     { "Miill", (void*)&CallFunc_I32_I64_I64_RetI32 },
     { "Miilli", (void*)&CallFunc_I32_I64_I64_I32_RetI32 },
     { "Miip", (void*)&CallFunc_I32_RetI32_PE },
+    { "Mil", (void*)&CallFunc_I64_RetI32 },
+    { "Mili", (void*)&CallFunc_I64_I32_RetI32 },
     { "Milili", (void*)&CallFunc_I64_I32_I64_I32_RetI32 },
     { "Mip", (void*)&CallFunc_Void_RetI32_PE },
     { "Ml", (void*)&CallFunc_Void_RetI64 },
     { "Mli", (void*)&CallFunc_I32_RetI64 },
     { "Mliii", (void*)&CallFunc_I32_I32_I32_RetI64 },
+    { "Mliiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_RetI64 },
     { "Mliiil", (void*)&CallFunc_I32_I32_I32_I64_RetI64 },
     { "Mlili", (void*)&CallFunc_I32_I64_I32_RetI64 },
     { "Mlillp", (void*)&CallFunc_I32_I64_I64_RetI64_PE },
@@ -684,6 +742,9 @@ const StringToWasmSigThunk g_wasmThunks[] = {
     { "MvS8iiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_RetVoid },
     { "MvS8iiiiiiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid },
     { "MvTp", (void*)&CallFunc_This_RetVoid_PE },
+    { "Mvdddddddddii", (void*)&CallFunc_F64_F64_F64_F64_F64_F64_F64_F64_F64_I32_I32_RetVoid },
+    { "Mvdi", (void*)&CallFunc_F64_I32_RetVoid },
+    { "Mvdiii", (void*)&CallFunc_F64_I32_I32_I32_RetVoid },
     { "Mvdiip", (void*)&CallFunc_F64_I32_I32_RetVoid_PE },
     { "Mvfiip", (void*)&CallFunc_F32_I32_I32_RetVoid_PE },
     { "Mvi", (void*)&CallFunc_I32_RetVoid },
@@ -691,12 +752,14 @@ const StringToWasmSigThunk g_wasmThunks[] = {
     { "Mvii", (void*)&CallFunc_I32_I32_RetVoid },
     { "MviiS8S8i", (void*)&CallFunc_I32_I32_S8_S8_I32_RetVoid },
     { "MviiS8S8ii", (void*)&CallFunc_I32_I32_S8_S8_I32_I32_RetVoid },
+    { "Mviid", (void*)&CallFunc_I32_I32_F64_RetVoid },
     { "Mviii", (void*)&CallFunc_I32_I32_I32_RetVoid },
     { "MviiiS8S8", (void*)&CallFunc_I32_I32_I32_S8_S8_RetVoid },
     { "MviiiS8S8i", (void*)&CallFunc_I32_I32_I32_S8_S8_I32_RetVoid },
     { "Mviiii", (void*)&CallFunc_I32_I32_I32_I32_RetVoid },
     { "Mviiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_RetVoid },
     { "Mviiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetVoid },
+    { "Mviiiil", (void*)&CallFunc_I32_I32_I32_I32_I64_RetVoid },
     { "Mviiip", (void*)&CallFunc_I32_I32_I32_RetVoid_PE },
     { "Mviip", (void*)&CallFunc_I32_I32_RetVoid_PE },
     { "Mvip", (void*)&CallFunc_I32_RetVoid_PE },
diff --git a/src/coreclr/vm/wasm/callhelpers-reverse.cpp b/src/coreclr/vm/wasm/callhelpers-reverse.cpp
index 4c154ed1fe46e4..e690a467933715 100644
--- a/src/coreclr/vm/wasm/callhelpers-reverse.cpp
+++ b/src/coreclr/vm/wasm/callhelpers-reverse.cpp
@@ -162,6 +162,19 @@ static void Call_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeH
     ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid);
 }
 
+static MethodDesc* MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid = nullptr;
+static void Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid(void * arg0, int32_t arg1, uint32_t arg2, int64_t arg3, int64_t arg4, void * arg5, void * arg6)
+{
+    int64_t args[7] = { (int64_t)arg0, (int64_t)arg1, (int64_t)arg2, (int64_t)arg3, (int64_t)arg4, (int64_t)arg5, (int64_t)arg6 };
+
+    // Lazy lookup of MethodDesc for the function export scenario.
+    if (!MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid)
+    {
+        LookupUnmanagedCallersOnlyMethodByName("System.Diagnostics.Tracing.EventPipeEventProvider, System.Private.CoreLib", "Callback", &MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid);
+    }
+    ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid);
+}
+
 static MethodDesc* MD_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid = nullptr;
 static void Call_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid(void * arg0, void * arg1, void * arg2, void * arg3)
 {
@@ -1181,6 +1194,7 @@ const ReverseThunkMapEntry g_ReverseThunks[] =
     { 433365813, "CallJSExport#2:System.Runtime.InteropServices.JavaScript:System.Runtime.InteropServices.JavaScript:JavaScriptExports", { &MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CallJSExport_I32_I32_RetVoid, (void*)&Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CallJSExport_I32_I32_RetVoid } },
     { 1821934012, "CallStartupHook#2:System.Private.CoreLib:System:StartupHookProvider", { &MD_System_Private_CoreLib_System_StartupHookProvider_CallStartupHook_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StartupHookProvider_CallStartupHook_I32_I32_RetVoid } },
     { 2915047114, "CallToString#3:System.Private.CoreLib:System.Runtime.CompilerServices:RuntimeHelpers", { &MD_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid } },
+    { 4077371982, "Callback#7:System.Private.CoreLib:System.Diagnostics.Tracing:EventPipeEventProvider", { &MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid } },
     { 3358042195, "ClearManaged#4:System.Private.CoreLib:System.StubHelpers:MngdRefCustomMarshaler", { &MD_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid } },
     { 2311968855, "ClearNative#4:System.Private.CoreLib:System.StubHelpers:MngdRefCustomMarshaler", { &MD_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearNative_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearNative_I32_I32_I32_I32_RetVoid } },
     { 3113228365, "CompleteTask#1:System.Runtime.InteropServices.JavaScript:System.Runtime.InteropServices.JavaScript:JavaScriptExports", { &MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CompleteTask_I32_RetVoid, (void*)&Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CompleteTask_I32_RetVoid } },
diff --git a/src/coreclr/vm/wasm/entrypoints.h b/src/coreclr/vm/wasm/entrypoints.h
index d53de7e63df3d1..3f71598b8715d6 100644
--- a/src/coreclr/vm/wasm/entrypoints.h
+++ b/src/coreclr/vm/wasm/entrypoints.h
@@ -13,6 +13,7 @@ extern "C" void SystemJS_ResolveMainPromise(int exitCode);
 extern "C" void SystemJS_RejectMainPromise(const char16_t *message, int messageLength, const char16_t *stackTrace, int stackTraceLength);
 extern "C" void SystemJS_ScheduleTimer(int shortestDueTimeMs);
 extern "C" void SystemJS_ScheduleBackgroundJob();
+extern "C" void SystemJS_DiagnosticServerQueueJob(size_t (*cb)(void *data), void *data);
 
 #endif
 
diff --git a/src/coreclr/vm/wks/CMakeLists.txt b/src/coreclr/vm/wks/CMakeLists.txt
index 25399d9b1e751f..bb69aec244f907 100644
--- a/src/coreclr/vm/wks/CMakeLists.txt
+++ b/src/coreclr/vm/wks/CMakeLists.txt
@@ -67,6 +67,10 @@ target_compile_definitions(cee_wks_mergeable PUBLIC CORECLR_EMBEDDED)
 
 target_compile_definitions(cee_wks_core PRIVATE GC_DESCRIPTOR)
 
+if (FEATURE_PERFTRACING_DISABLE_THREADS)
+  target_compile_definitions(cee_wks_core PRIVATE PERFTRACING_DISABLE_THREADS)
+endif()
+
 if (CLR_CMAKE_HOST_WIN32)
     link_natvis_sources_for_target(cee_wks INTERFACE ../vm.natvis)
     link_natvis_sources_for_target(cee_wks_mergeable INTERFACE ../vm.natvis)
diff --git a/src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj b/src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj
index e5ce9d7789566a..1789b2304a360f 100644
--- a/src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj
+++ b/src/libraries/System.Diagnostics.Tracing/tests/System.Diagnostics.Tracing.Tests.csproj
@@ -7,12 +7,14 @@
     <TestDisableParallelization>true</TestDisableParallelization>
     <!-- https://github.com/dotnet/runtime/issues/126862 -->
     <EnableTrimAnalyzer>false</EnableTrimAnalyzer>
+    <TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
+    <NoWarn>$(NoWarn);WASM0066</NoWarn>
   </PropertyGroup>
-  <PropertyGroup Condition="'$(TargetOS)' == 'browser'">
+  <PropertyGroup Condition="'$(TargetPlatformIdentifier)' == 'browser'">
     <!-- this is necessary for libmono-component-diagnostics_tracing-static.a to be linked, which makes the EventPipeEventProvider.EventActivityIdControl to work in Mono -->
     <EnableDiagnostics>true</EnableDiagnostics>
   </PropertyGroup>
-  <ItemGroup Condition="'$(TargetsAppleMobile)' == 'true' or '$(TargetOS)' == 'android'">
+  <ItemGroup Condition="'$(TargetsAppleMobile)' == 'true' or '$(TargetPlatformIdentifier)' == 'android'">
     <RuntimeComponents Include="diagnostics_tracing" />
     <RuntimeComponents Include="marshal-ilgen" />
   </ItemGroup>
diff --git a/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets b/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets
index 40b486404316fd..068b1bfa856b98 100644
--- a/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets
+++ b/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets
@@ -91,6 +91,7 @@
            NativeLibrary items are converted to NativeFileReference items later in the
            flow (in _CoreCLRPrepareForNativeBuild) so we have to inspect both here. -->
       <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and (@(NativeFileReference->Count()) &gt; 0 or @(NativeLibrary->Count()) &gt; 0)">true</WasmBuildNative>
+      <WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(EnableDiagnostics)' == 'true'">true</WasmBuildNative>
     </PropertyGroup>
   </Target>
 
diff --git a/src/native/libs/Common/JavaScript/types/ems-ambient.ts b/src/native/libs/Common/JavaScript/types/ems-ambient.ts
index 0f394656c9c632..0b31a52dce3b04 100644
--- a/src/native/libs/Common/JavaScript/types/ems-ambient.ts
+++ b/src/native/libs/Common/JavaScript/types/ems-ambient.ts
@@ -29,7 +29,7 @@ export type EmsAmbientSymbolsType = EmscriptenModuleInternal & {
     _SystemJS_ExecuteBackgroundJobCallback: () => void;
     _SystemJS_ExecuteFinalizationCallback: () => void;
     _SystemJS_ExecuteDiagnosticServerCallback: () => void;
-    _SystemJS_ScheduleDiagnosticServer: () => void;
+    _SystemJS_ScheduleDiagnosticServer: (delayMs: number) => void;
     _BrowserHost_CreateHostContract: () => VoidPtr;
     _BrowserHost_InitializeDotnet: (propertiesCount: number, propertyKeys: CharPtrPtr, propertyValues: CharPtrPtr) => number;
     _BrowserHost_ExecuteAssembly: (mainAssemblyNamePtr: number, argsLength: number, argsPtr: number) => number;
diff --git a/src/native/libs/System.Native.Browser/diagnostic_server_jobs.c b/src/native/libs/System.Native.Browser/diagnostic_server_jobs.c
index 4bda75186dcb51..cbee13f3a5d83a 100644
--- a/src/native/libs/System.Native.Browser/diagnostic_server_jobs.c
+++ b/src/native/libs/System.Native.Browser/diagnostic_server_jobs.c
@@ -8,7 +8,7 @@
 
 #include "diagnostic_server_jobs.h"
 
-extern void SystemJS_ScheduleDiagnosticServer(void);
+extern void SystemJS_ScheduleDiagnosticServer(int delayMs);
 
 typedef struct DsJobNode {
     ds_job_cb cb;
@@ -32,7 +32,7 @@ SystemJS_DiagnosticServerQueueJob (ds_job_cb cb, void *data)
     node->next = jobs;
     jobs = node;
     if (wasEmpty) {
-        SystemJS_ScheduleDiagnosticServer ();
+        SystemJS_ScheduleDiagnosticServer (0);
     }
 }
 
@@ -56,6 +56,6 @@ SystemJS_ExecuteDiagnosticServerCallback (void)
     }
 
     if (jobs) {
-        SystemJS_ScheduleDiagnosticServer ();
+        SystemJS_ScheduleDiagnosticServer (100);
     }
 }
diff --git a/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-js.ts b/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-js.ts
index 22384640be5328..8e7a6410255125 100644
--- a/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-js.ts
+++ b/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-js.ts
@@ -87,7 +87,7 @@ class DiagnosticSession extends DiagnosticConnectionBase implements IDiagnosticC
 
     // this is message from the diagnostic server, which is dotnet VM in this browser
     send(message: Uint8Array): number {
-        dotnetNativeBrowserExports.SystemJS_ScheduleDiagnosticServer();
+        dotnetNativeBrowserExports.SystemJS_ScheduleDiagnosticServer(0);
         if (this.isAdvertMessage(message)) {
             // eslint-disable-next-line @typescript-eslint/no-this-alias
             serverSession = this;
@@ -119,7 +119,7 @@ class DiagnosticSession extends DiagnosticConnectionBase implements IDiagnosticC
     // this is message to the diagnostic server, which is dotnet VM in this browser
     respond(message: Uint8Array): void {
         this.messagesReceived.push(message);
-        dotnetNativeBrowserExports.SystemJS_ScheduleDiagnosticServer();
+        dotnetNativeBrowserExports.SystemJS_ScheduleDiagnosticServer(0);
     }
 
     close(): number {
diff --git a/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-ws.ts b/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-ws.ts
index 28b84362b966ee..f6c3c42b522008 100644
--- a/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-ws.ts
+++ b/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-ws.ts
@@ -41,7 +41,7 @@ class DiagnosticConnectionWS extends DiagnosticConnectionBase implements IDiagno
     }
 
     send(message: Uint8Array): number {
-        dotnetNativeBrowserExports.SystemJS_ScheduleDiagnosticServer();
+        dotnetNativeBrowserExports.SystemJS_ScheduleDiagnosticServer(0);
         // copy the message
         if (this.ws!.readyState == WebSocket.CLOSED || this.ws!.readyState == WebSocket.CLOSING) {
             return -1;
@@ -61,7 +61,7 @@ class DiagnosticConnectionWS extends DiagnosticConnectionBase implements IDiagno
     }
 
     close(): number {
-        dotnetNativeBrowserExports.SystemJS_ScheduleDiagnosticServer();
+        dotnetNativeBrowserExports.SystemJS_ScheduleDiagnosticServer(0);
         this.ws.close();
         return 0;
     }
diff --git a/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server.ts b/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server.ts
index 4a59c891e53381..5c6d3c5bbd1c94 100644
--- a/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server.ts
+++ b/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server.ts
@@ -8,6 +8,7 @@ import { createDiagConnectionWs } from "./diagnostic-server-ws";
 import { advertise } from "./client-commands";
 import { IDiagnosticConnection } from "./types";
 import { dotnetApi, Module } from "./cross-module";
+import { ENVIRONMENT_IS_WEB } from "./per-module";
 
 let socketHandles: Map<number, IDiagnosticConnection> = undefined as any;
 let nextSocketHandle = 1;
@@ -93,7 +94,7 @@ export function initializeDS() {
     const loaderConfig = dotnetApi.getConfig();
     const diagnosticPorts = "DOTNET_DiagnosticPorts";
     loaderConfig.environmentVariables ??= {};
-    if (loaderConfig.environmentVariables![diagnosticPorts] === undefined) {
+    if (ENVIRONMENT_IS_WEB && loaderConfig.environmentVariables![diagnosticPorts] === undefined) {
         loaderConfig.environmentVariables![diagnosticPorts] = "js://ready";
     }
     initializeJsClient();
diff --git a/src/native/libs/System.Native.Browser/native/scheduling.ts b/src/native/libs/System.Native.Browser/native/scheduling.ts
index 09708677dfaf64..45b71570f2b37a 100644
--- a/src/native/libs/System.Native.Browser/native/scheduling.ts
+++ b/src/native/libs/System.Native.Browser/native/scheduling.ts
@@ -81,17 +81,20 @@ export function SystemJS_ScheduleFinalization(): void {
     }
 }
 
-export function SystemJS_ScheduleDiagnosticServer(): void {
+export function SystemJS_ScheduleDiagnosticServer(delayMs: number): void {
     if (_ems_.ABORT || _ems_.DOTNET.isAborting) {
         // runtime is shutting down
         return;
     }
+    if (delayMs !== 0 && (_ems_.ENVIRONMENT_IS_SHELL || _ems_.DOTNET.lastScheduledDiagnosticServerId)) {
+        return;
+    }
     if (_ems_.DOTNET.lastScheduledDiagnosticServerId) {
         globalThis.clearTimeout(_ems_.DOTNET.lastScheduledDiagnosticServerId);
         _ems_.runtimeKeepalivePop();
         _ems_.DOTNET.lastScheduledDiagnosticServerId = undefined;
     }
-    _ems_.DOTNET.lastScheduledDiagnosticServerId = _ems_.safeSetTimeout(SystemJS_ScheduleDiagnosticServerTick, 0);
+    _ems_.DOTNET.lastScheduledDiagnosticServerId = _ems_.safeSetTimeout(SystemJS_ScheduleDiagnosticServerTick, delayMs);
 
     function SystemJS_ScheduleDiagnosticServerTick(): void {
         try {
