diff --git a/eng/native.wasm.targets b/eng/native.wasm.targets
index 734349ba2f1d34..f8593ec4c87e1d 100644
--- a/eng/native.wasm.targets
+++ b/eng/native.wasm.targets
@@ -82,13 +82,13 @@
-
+
+
+
diff --git a/src/coreclr/CMakeLists.txt b/src/coreclr/CMakeLists.txt
index 7a713a828f5c01..768d8b535f0e36 100644
--- a/src/coreclr/CMakeLists.txt
+++ b/src/coreclr/CMakeLists.txt
@@ -39,10 +39,6 @@ if(CLR_CMAKE_TARGET_WASI)
add_compile_definitions(_WASI_EMULATED_PROCESS_CLOCKS)
add_compile_definitions(_WASI_EMULATED_MMAN)
add_compile_definitions(_WASI_EMULATED_GETPID)
- # EventPipe / diag-server PAL require sockets we have not validated
- # for the WASI bring-up; revisit once corerun.wasm runs.
- set(FEATURE_EVENT_TRACE 0)
- set(FEATURE_PERFTRACING 0)
include_directories(BEFORE SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/pal/src/include/pal/wasi)
# Cross-compile try_run() answers — wasm32 binaries cannot execute on
# the build host, so pre-seed the cache.
diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props
index 440379ea5e1317..49429013d2ddea 100644
--- a/src/coreclr/clr.featuredefines.props
+++ b/src/coreclr/clr.featuredefines.props
@@ -32,18 +32,6 @@
false
-
-
- false
-
-
true
true
diff --git a/src/coreclr/debug/debug-pal/CMakeLists.txt b/src/coreclr/debug/debug-pal/CMakeLists.txt
index 59b18e2c91ddb8..d7a233790e7316 100644
--- a/src/coreclr/debug/debug-pal/CMakeLists.txt
+++ b/src/coreclr/debug/debug-pal/CMakeLists.txt
@@ -27,12 +27,7 @@ if(CLR_CMAKE_HOST_WIN32)
endif(CLR_CMAKE_HOST_WIN32)
if(CLR_CMAKE_HOST_UNIX)
- if (NOT CLR_CMAKE_TARGET_WASI)
- # ds-ipc-pal-socket.c needs UNIX/TCP sockets; re-enable once
- # WASI PERFTRACING is wired. Tracked by https://github.com/dotnet/runtime/issues/130383
-
- set(DEBUG_PAL_REFEREENCE_DIAGNOSTICSERVER ON)
- endif()
+ set(DEBUG_PAL_REFEREENCE_DIAGNOSTICSERVER ON)
add_definitions(-DPAL_IMPLEMENTATION)
add_definitions(-D_POSIX_C_SOURCE=200809L)
diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.cpp b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.cpp
index 17859b17988bd4..aaa62cfe2b3995 100644
--- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.cpp
+++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.cpp
@@ -197,4 +197,63 @@ ep_rt_coreclr_session_stopping (void)
#endif // FEATURE_PGO && PERFTRACING_DISABLE_THREADS
}
+#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS)
+// WASI has no host event loop to schedule EventPipe jobs (session streaming, diagnostic server) on,
+// so they are kept on this list and run by the managed WasiEventLoop (WasiEventPipeJobs.cs).
+// A job returns non-zero when it is done; otherwise it stays queued and runs again on the next pump.
+// Single-threaded by construction, so no locking.
+struct WasiEventPipeJob
+{
+ size_t (*Callback)(void *data);
+ void *Data;
+ WasiEventPipeJob *Next;
+};
+
+static WasiEventPipeJob *s_wasiJobsHead = nullptr;
+static WasiEventPipeJob *s_wasiJobsTail = nullptr;
+
+static void WasiAppendJob(WasiEventPipeJob *job)
+{
+ job->Next = nullptr;
+ if (s_wasiJobsTail != nullptr)
+ s_wasiJobsTail->Next = job;
+ else
+ s_wasiJobsHead = job;
+ s_wasiJobsTail = job;
+}
+
+bool ep_rt_coreclr_wasi_queue_job (size_t (*cb)(void *data), void *data)
+{
+ _ASSERTE (cb != nullptr);
+ WasiEventPipeJob *job = new (nothrow) WasiEventPipeJob();
+ if (job == nullptr)
+ return false;
+
+ job->Callback = cb;
+ job->Data = data;
+ WasiAppendJob (job);
+ return true;
+}
+
+bool ep_rt_coreclr_wasi_run_jobs (void)
+{
+ // Detach the current list so jobs queued while running are picked up on the next pump.
+ WasiEventPipeJob *job = s_wasiJobsHead;
+ s_wasiJobsHead = nullptr;
+ s_wasiJobsTail = nullptr;
+
+ while (job != nullptr)
+ {
+ WasiEventPipeJob *next = job->Next;
+ if (job->Callback (job->Data) != 0)
+ delete job;
+ else
+ WasiAppendJob (job);
+ job = next;
+ }
+
+ return s_wasiJobsHead != nullptr;
+}
+#endif // TARGET_WASI && PERFTRACING_DISABLE_THREADS
+
#endif /* ENABLE_PERFTRACING */
diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
index c557950df39b68..1ba3a2134d023a 100644
--- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
+++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
@@ -314,12 +314,26 @@ ep_rt_init_finish (void)
STATIC_CONTRACT_NOTHROW;
}
+#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS)
+// WASI has no host event loop, so jobs are kept on a native list that the managed
+// WASI event loop drains (see ep-rt-coreclr.cpp).
+bool ep_rt_coreclr_wasi_queue_job (size_t (*cb)(void *data), void *data);
+
+// Runs the queued jobs once; returns true if any job is still queued.
+bool ep_rt_coreclr_wasi_run_jobs (void);
+#endif
+
static
inline
void
ep_rt_shutdown (void)
{
STATIC_CONTRACT_NOTHROW;
+#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS)
+ // The event loop no longer runs, so run the queued jobs once to let the now-disabled streaming
+ // sessions release their references; freeing a session ends its trace file.
+ ep_rt_coreclr_wasi_run_jobs ();
+#endif
}
static
@@ -1111,6 +1125,8 @@ ep_rt_queue_job (
if (!cb (params))
SystemJS_DiagnosticServerQueueJob (cb, params);
return true;
+#elif defined(TARGET_WASI)
+ return ep_rt_coreclr_wasi_queue_job ((size_t (*)(void *))job_func, params);
#else
EP_UNREACHABLE ("Not implemented on this platform");
return false;
diff --git a/src/coreclr/vm/eventpipeadapter.h b/src/coreclr/vm/eventpipeadapter.h
index 7be8fe82827d05..b3bbd4a0cb1569 100644
--- a/src/coreclr/vm/eventpipeadapter.h
+++ b/src/coreclr/vm/eventpipeadapter.h
@@ -285,6 +285,15 @@ class EventPipeAdapter final
return !ep_rt_wait_event_wait (ep_session_get_wait_event (session), (uint32_t)timeoutMs, false) ? true : false;
}
+#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS)
+ // Runs the queued EventPipe jobs; returns true if any remain queued.
+ static inline bool RunWasiJobs()
+ {
+ STATIC_CONTRACT_NOTHROW;
+ return ep_rt_coreclr_wasi_run_jobs ();
+ }
+#endif // TARGET_WASI && PERFTRACING_DISABLE_THREADS
+
static inline FILETIME GetSessionStartTime(EventPipeSession *session)
{
STATIC_CONTRACT_NOTHROW;
diff --git a/src/coreclr/vm/eventpipeinternal.cpp b/src/coreclr/vm/eventpipeinternal.cpp
index 5a2dc9933c800c..cdb0e2ecd35bc8 100644
--- a/src/coreclr/vm/eventpipeinternal.cpp
+++ b/src/coreclr/vm/eventpipeinternal.cpp
@@ -296,4 +296,21 @@ extern "C" BOOL QCALLTYPE EventPipeInternal_WaitForSessionSignal(UINT64 sessionI
return result;
}
+#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS)
+// Runs the queued EventPipe jobs and returns whether any are still queued, so the caller knows to run again.
+extern "C" CLR_BOOL QCALLTYPE EventPipeInternal_WasiRunJobs(QCallExceptionStatus* qcallError)
+{
+ QCALL_CONTRACT;
+
+ bool pending = false;
+
+ BEGIN_QCALL;
+
+ pending = EventPipeAdapter::RunWasiJobs();
+
+ END_QCALL;
+ return pending ? TRUE : FALSE;
+}
+#endif // TARGET_WASI && PERFTRACING_DISABLE_THREADS
+
#endif // FEATURE_PERFTRACING
diff --git a/src/coreclr/vm/eventpipeinternal.h b/src/coreclr/vm/eventpipeinternal.h
index 09cb9acedaca28..2854de3e2013ff 100644
--- a/src/coreclr/vm/eventpipeinternal.h
+++ b/src/coreclr/vm/eventpipeinternal.h
@@ -104,6 +104,10 @@ extern "C" BOOL QCALLTYPE EventPipeInternal_WaitForSessionSignal(
INT32 timeoutMs,
QCallExceptionStatus* qcallError);
+#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS)
+extern "C" CLR_BOOL QCALLTYPE EventPipeInternal_WasiRunJobs(QCallExceptionStatus* qcallError);
+#endif // TARGET_WASI && PERFTRACING_DISABLE_THREADS
+
#endif // FEATURE_PERFTRACING
#endif // __EVENTPIPEINTERNAL_H__
diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp
index b35b8e3ffd8053..757daa2d98ddd5 100644
--- a/src/coreclr/vm/qcallentrypoints.cpp
+++ b/src/coreclr/vm/qcallentrypoints.cpp
@@ -492,6 +492,9 @@ static const Entry s_QCall[] =
DllImportEntry(EventPipeInternal_GetNextEvent)
DllImportEntry(EventPipeInternal_SignalSession)
DllImportEntry(EventPipeInternal_WaitForSessionSignal)
+#if defined(TARGET_WASI) && defined(PERFTRACING_DISABLE_THREADS)
+ DllImportEntry(EventPipeInternal_WasiRunJobs)
+#endif // TARGET_WASI && PERFTRACING_DISABLE_THREADS
#endif // FEATURE_PERFTRACING
#if defined(TARGET_UNIX)
DllImportEntry(FreeEnvironmentStringsW)
diff --git a/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp b/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp
index c90fd710a16802..e08d4b23c9fdd8 100644
--- a/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp
+++ b/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp
@@ -316,6 +316,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_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, int32_t, int64_t, int32_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), ARG_I32(7));
+ }
+
static void CallFunc_I32_I32_I64_I64_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
int32_t (*fptr)(int32_t, int32_t, int64_t, int64_t, int32_t) = (int32_t (*)(int32_t, int32_t, int64_t, int64_t, int32_t))pcode;
@@ -372,6 +378,18 @@ namespace
*((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_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ int32_t (*fptr)(int64_t, int32_t, int32_t) = (int32_t (*)(int64_t, int32_t, int32_t))pcode;
+ *((int32_t*)pRet) = (*fptr)(ARG_I64(0), ARG_I32(1), ARG_I32(2));
+ }
+
static void CallFunc_I64_I32_I64_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
int32_t (*fptr)(int64_t, int32_t, int64_t, int32_t, int32_t) = (int32_t (*)(int64_t, int32_t, int64_t, int32_t, int32_t))pcode;
@@ -397,12 +415,24 @@ namespace
*((int64_t*)pRet) = (*fptr)(ARG_I32(0));
}
+ static void CallFunc_I32_I32_RetI64(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ int64_t (*fptr)(int32_t, int32_t) = (int64_t (*)(int32_t, int32_t))pcode;
+ *((int64_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1));
+ }
+
static void CallFunc_I32_I32_I32_RetI64(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
int64_t (*fptr)(int32_t, int32_t, int32_t) = (int64_t (*)(int32_t, int32_t, int32_t))pcode;
*((int64_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2));
}
+ static void CallFunc_I32_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, int32_t) = (int64_t (*)(int32_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), ARG_I32(5));
+ }
+
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;
@@ -516,6 +546,24 @@ namespace
(*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6), ARG_I32(7), ARG_I32(8), ARG_I32(9), ARG_I32(10));
}
+ static void CallFunc_F64_F64_F64_F64_F64_F64_F64_F64_F64_I32_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, int32_t) = (void (*)(double, double, double, double, double, double, double, double, double, int32_t, 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), ARG_I32(11));
+ }
+
+ static void CallFunc_F64_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ void (*fptr)(double, int32_t, int32_t) = (void (*)(double, int32_t, int32_t))pcode;
+ (*fptr)(ARG_F64(0), ARG_I32(1), ARG_I32(2));
+ }
+
+ static void CallFunc_F64_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ void (*fptr)(double, int32_t, int32_t, int32_t, int32_t) = (void (*)(double, int32_t, int32_t, int32_t, int32_t))pcode;
+ (*fptr)(ARG_F64(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4));
+ }
+
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;
@@ -560,6 +608,12 @@ namespace
(*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2), ARG_IND(3), ARG_I32(4), ARG_I32(5), ARG_I32(6));
}
+ static void CallFunc_I32_I32_F64_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ void (*fptr)(int32_t, int32_t, double, int32_t) = (void (*)(int32_t, int32_t, double, int32_t))pcode;
+ (*fptr)(ARG_I32(0), ARG_I32(1), ARG_F64(2), ARG_I32(3));
+ }
+
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;
@@ -602,6 +656,12 @@ namespace
(*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6));
}
+ static void CallFunc_I32_I32_I32_I32_I64_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ void (*fptr)(int32_t, int32_t, int32_t, int32_t, int64_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int64_t, int32_t))pcode;
+ (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I64(4), ARG_I32(5));
+ }
+
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;
@@ -691,6 +751,7 @@ const StringToPortableSigThunk g_portableCallHelperThunks[] = {
{ "Miiiil", (void*)&CallFunc_I32_I32_I32_I64_RetI32 },
{ "Miiiip", (void*)&CallFunc_I32_I32_I32_RetI32_PE },
{ "Miiil", (void*)&CallFunc_I32_I32_I64_RetI32 },
+ { "Miiiliiiii", (void*)&CallFunc_I32_I32_I64_I32_I32_I32_I32_I32_RetI32 },
{ "Miiilli", (void*)&CallFunc_I32_I32_I64_I64_I32_RetI32 },
{ "Miiip", (void*)&CallFunc_I32_I32_RetI32_PE },
{ "Miil", (void*)&CallFunc_I32_I64_RetI32 },
@@ -700,11 +761,15 @@ const StringToPortableSigThunk g_portableCallHelperThunks[] = {
{ "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 },
+ { "Milii", (void*)&CallFunc_I64_I32_I32_RetI32 },
{ "Mililii", (void*)&CallFunc_I64_I32_I64_I32_I32_RetI32 },
{ "Mip", (void*)&CallFunc_Void_RetI32_PE },
{ "Ml", (void*)&CallFunc_Void_RetI64 },
{ "Mli", (void*)&CallFunc_I32_RetI64 },
+ { "Mlii", (void*)&CallFunc_I32_I32_RetI64 },
{ "Mliii", (void*)&CallFunc_I32_I32_I32_RetI64 },
+ { "Mliiiiii", (void*)&CallFunc_I32_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 },
@@ -723,6 +788,9 @@ const StringToPortableSigThunk g_portableCallHelperThunks[] = {
{ "MvS8iiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_RetVoid },
{ "MvS8iiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_RetVoid },
{ "MvS8iiiiiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid },
+ { "Mvdddddddddiii", (void*)&CallFunc_F64_F64_F64_F64_F64_F64_F64_F64_F64_I32_I32_I32_RetVoid },
+ { "Mvdii", (void*)&CallFunc_F64_I32_I32_RetVoid },
+ { "Mvdiiii", (void*)&CallFunc_F64_I32_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 },
@@ -730,6 +798,7 @@ const StringToPortableSigThunk g_portableCallHelperThunks[] = {
{ "Mvii", (void*)&CallFunc_I32_I32_RetVoid },
{ "MviiS8S8ii", (void*)&CallFunc_I32_I32_S8_S8_I32_I32_RetVoid },
{ "MviiS8S8iii", (void*)&CallFunc_I32_I32_S8_S8_I32_I32_I32_RetVoid },
+ { "Mviidi", (void*)&CallFunc_I32_I32_F64_I32_RetVoid },
{ "Mviii", (void*)&CallFunc_I32_I32_I32_RetVoid },
{ "MviiiS8S8i", (void*)&CallFunc_I32_I32_I32_S8_S8_I32_RetVoid },
{ "MviiiS8S8ii", (void*)&CallFunc_I32_I32_I32_S8_S8_I32_I32_RetVoid },
@@ -737,6 +806,7 @@ const StringToPortableSigThunk g_portableCallHelperThunks[] = {
{ "Mviiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_RetVoid },
{ "Mviiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetVoid },
{ "Mviiiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_I32_RetVoid },
+ { "Mviiiili", (void*)&CallFunc_I32_I32_I32_I32_I64_I32_RetVoid },
{ "Mviiip", (void*)&CallFunc_I32_I32_I32_RetVoid_PE },
{ "Mviip", (void*)&CallFunc_I32_I32_RetVoid_PE },
{ "Mvili", (void*)&CallFunc_I32_I64_I32_RetVoid },
diff --git a/src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp b/src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp
index 4d94590f2f4073..39745a7a611c53 100644
--- a/src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp
+++ b/src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp
@@ -92,6 +92,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)
+{
+ // 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);
+ }
+
+ 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 };
+ 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)
{
@@ -1010,6 +1023,7 @@ const ReverseThunkMapEntry g_ReverseThunks[] =
{ 3962535319, "CallEntryPoint#5:System.Private.CoreLib:System:Environment", { &MD_System_Private_CoreLib_System_Environment_CallEntryPoint_I32_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Environment_CallEntryPoint_I32_I32_I32_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 } },
{ 3378852959, "ConfigCallback#5:System.Private.CoreLib:System:GC", { &MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid, (void*)&Call_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid } },
diff --git a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/ActivityTracking.cs b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/ActivityTracking.cs
index cb7390b021c58b..d467ed3a7e5180 100644
--- a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/ActivityTracking.cs
+++ b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/ActivityTracking.cs
@@ -53,7 +53,6 @@ public void IsSupported()
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/129088", typeof(PlatformDetection), nameof(PlatformDetection.IsAndroid), nameof(PlatformDetection.IsMonoRuntime))]
- [ActiveIssue("https://github.com/dotnet/runtime/issues/134963", TestPlatforms.Wasi)]
public void StartStopCreatesActivity()
{
using ActivityEventListener l = new ActivityEventListener();
@@ -70,7 +69,6 @@ public void StartStopCreatesActivity()
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/129088", typeof(PlatformDetection), nameof(PlatformDetection.IsAndroid), nameof(PlatformDetection.IsMonoRuntime))]
- [ActiveIssue("https://github.com/dotnet/runtime/issues/134963", TestPlatforms.Wasi)]
public async Task ActivityFlowsAsync()
{
using ActivityEventListener l = new ActivityEventListener();
@@ -120,7 +118,6 @@ private async Task YieldTwoActivitiesDeep(ActivityEventSource es)
// Compare with SetCurrentActivityIdAfterEventDoesNotFlowAsync below.
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/129088", typeof(PlatformDetection), nameof(PlatformDetection.IsAndroid), nameof(PlatformDetection.IsMonoRuntime))]
- [ActiveIssue("https://github.com/dotnet/runtime/issues/134963", TestPlatforms.Wasi)]
public async Task SetCurrentActivityIdBeforeEventFlowsAsync()
{
using ActivityEventListener l = new ActivityEventListener();
@@ -147,7 +144,6 @@ public async Task SetCurrentActivityIdBeforeEventFlowsAsync()
// Compare with SetCurrentActivityIdBeforeEventFlowsAsync above.
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/129088", typeof(PlatformDetection), nameof(PlatformDetection.IsAndroid), nameof(PlatformDetection.IsMonoRuntime))]
- [ActiveIssue("https://github.com/dotnet/runtime/issues/134963", TestPlatforms.Wasi)]
public async Task SetCurrentActivityIdAfterEventDoesNotFlowAsync()
{
using ActivityEventListener l = new ActivityEventListener();
diff --git a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestsEventSourceLifetime.cs b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestsEventSourceLifetime.cs
index 9e86e94b363c26..b4cd37b52a09d4 100644
--- a/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestsEventSourceLifetime.cs
+++ b/src/libraries/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestsEventSourceLifetime.cs
@@ -4,6 +4,7 @@
using System;
using System.Diagnostics.Tracing;
using System.Reflection;
+using System.Threading.Tasks;
using Xunit;
namespace BasicEventSourceTests
@@ -35,6 +36,65 @@ public void Test_EventSource_Lifetime()
TestUtilities.CheckNoEventSourcesRunning("Stop");
}
+ ///
+ /// Validates that disposing an EventListener subscribed to the runtime provider releases its native
+ /// EventPipe session. EventPipe has a fixed number of sessions, so leaking one per listener would
+ /// eventually stop new subscriptions from receiving events.
+ ///
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMonoRuntime))]
+ public async Task RuntimeProviderListener_RepeatedSubscriptions_DoNotLeakSessions()
+ {
+ const int Iterations = 80; // more than the 64 native session slots
+ const EventKeywords GCKeyword = (EventKeywords)0x1;
+
+ for (int i = 0; i < Iterations; i++)
+ {
+ using var listener = new RuntimeProviderListener(GCKeyword);
+ if (i % 2 == 0)
+ {
+ await Task.Delay(1);
+ }
+ }
+
+ await Task.Delay(500);
+
+ using var finalListener = new RuntimeProviderListener(GCKeyword);
+ for (int attempt = 0; attempt < 50 && !finalListener.ReceivedEvent; attempt++)
+ {
+ GC.Collect();
+ await Task.Delay(100);
+ }
+
+ Assert.True(finalListener.ReceivedEvent, "A new runtime-provider subscription received no events.");
+ }
+
+ private sealed class RuntimeProviderListener : EventListener
+ {
+ private readonly EventKeywords _keywords;
+
+ public volatile bool ReceivedEvent;
+
+ public RuntimeProviderListener(EventKeywords keywords)
+ {
+ _keywords = keywords;
+ // Sources created before the listener are delivered from the base constructor, before _keywords is set.
+ foreach (EventSource source in EventSource.GetSources())
+ {
+ OnEventSourceCreated(source);
+ }
+ }
+
+ protected override void OnEventSourceCreated(EventSource source)
+ {
+ if (source.Name == "Microsoft-Windows-DotNETRuntime" && _keywords != 0)
+ {
+ EnableEvents(source, EventLevel.Informational, _keywords);
+ }
+ }
+
+ protected override void OnEventWritten(EventWrittenEventArgs eventData) => ReceivedEvent = true;
+ }
+
private void ExerciseEventSource(WeakReference wrProvider, WeakReference wrEventSource)
{
using (var es = new LifetimeTestEventSource())
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index 30dd3fad791e65..2a77d3f4771ef6 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -3005,6 +3005,9 @@
+
+
+
diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.Wasm.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.Wasm.cs
index b9d9acb6db8b51..2222fd983f2784 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.Wasm.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.Wasm.cs
@@ -17,16 +17,19 @@ private void StartDispatchTask(ulong sessionID, DateTime syncTimeUtc, long syncT
Debug.Assert(Monitor.IsEntered(m_dispatchControlLock));
Debug.Assert(sessionID != 0);
- m_dispatchTaskCancellationSource = new CancellationTokenSource();
+ CancellationTokenSource cancellationSource = new CancellationTokenSource();
+ m_dispatchTaskCancellationSource = cancellationSource;
+ CancellationToken token = cancellationSource.Token;
Task? previousDispatchTask = m_dispatchTask;
if (previousDispatchTask != null)
{
- m_dispatchTask = previousDispatchTask.ContinueWith(_ => DispatchEventsToEventListeners(sessionID, syncTimeUtc, syncTimeQPC, timeQPCFrequency, m_dispatchTaskCancellationSource.Token),
- m_dispatchTaskCancellationSource.Token, TaskContinuationOptions.None, TaskScheduler.Default);
+ // The continuation must always run, even if already cancelled, because it owns disabling the session.
+ m_dispatchTask = previousDispatchTask.ContinueWith(_ => DispatchEventsToEventListeners(sessionID, syncTimeUtc, syncTimeQPC, timeQPCFrequency, token),
+ CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default).Unwrap();
}
else
{
- m_dispatchTask = DispatchEventsToEventListeners(sessionID, syncTimeUtc, syncTimeQPC, timeQPCFrequency, m_dispatchTaskCancellationSource.Token);
+ m_dispatchTask = DispatchEventsToEventListeners(sessionID, syncTimeUtc, syncTimeQPC, timeQPCFrequency, token);
}
}
@@ -34,17 +37,25 @@ private async Task DispatchEventsToEventListeners(ulong sessionID, DateTime sync
{
Debug.Assert(sessionID != 0);
- while (!token.IsCancellationRequested)
+ try
{
- DispatchEventsToEventListenersOnce(sessionID, syncTimeUtc, syncTimeQPC, timeQPCFrequency, token);
- await Task.Delay(DispatcherDelayMs, token).ConfigureAwait(false);
+ while (!token.IsCancellationRequested)
+ {
+ DispatchEventsToEventListenersOnce(sessionID, syncTimeUtc, syncTimeQPC, timeQPCFrequency, token);
+ await Task.Delay(DispatcherDelayMs, token).ConfigureAwait(false);
+ }
}
+ catch (OperationCanceledException) when (token.IsCancellationRequested)
+ {
+ }
+ finally
+ {
+ // Wait for SignalSession() to be called before we call disable, otherwise
+ // the SignalSession() call could be on a disabled session.
+ await Task.Yield();
- // Wait for SignalSession() to be called before we call disable, otherwise
- // the SignalSession() call could be on a disabled session.
- await Task.Yield();
-
- EventPipeInternal.Disable(sessionID);
+ EventPipeInternal.Disable(sessionID);
+ }
}
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs
index 0dba1ec0041502..91620ed239b385 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventLoop.cs
@@ -56,6 +56,10 @@ internal static T PollWasiEventLoopUntilResolved(Task mainTask)
s_mainTask = mainTask;
while (!mainTask.IsCompleted)
{
+#if FEATURE_PERFTRACING && CORECLR && !FEATURE_MULTITHREADING
+ // Before Dispatch, which can block in wasi:io/poll, so the pump's timer is registered first.
+ WasiEventPipeJobs.Pump();
+#endif
ThreadPoolWorkQueue.Dispatch();
WasiFinalizerScheduler.DrainIfPending();
}
@@ -78,6 +82,10 @@ internal static void PollWasiEventLoopUntilResolvedVoid(Task mainTask)
s_mainTask = mainTask;
while (!mainTask.IsCompleted)
{
+#if FEATURE_PERFTRACING && CORECLR && !FEATURE_MULTITHREADING
+ // Before Dispatch, which can block in wasi:io/poll, so the pump's timer is registered first.
+ WasiEventPipeJobs.Pump();
+#endif
ThreadPoolWorkQueue.Dispatch();
WasiFinalizerScheduler.DrainIfPending();
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventPipeJobs.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventPipeJobs.cs
new file mode 100644
index 00000000000000..fbc5fe31444815
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiEventPipeJobs.cs
@@ -0,0 +1,52 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+
+namespace System.Threading
+{
+ // Runs the native EventPipe jobs (session streaming, diagnostic server) that browser schedules with
+ // setTimeout. WASI has no host event loop, so the native side keeps the job queue and WasiEventLoop
+ // calls Pump on each iteration. Jobs only run while the event loop is being pumped.
+ internal static partial class WasiEventPipeJobs
+ {
+ // Matches the browser re-schedule interval for unfinished jobs.
+ private const int PumpIntervalMs = 100;
+
+ private static bool s_pumpRunning;
+
+ // Runs the queued jobs and returns true if any remain queued.
+ [ErrorHandler(typeof(QCallExceptionStatusMarshaller), ErrorLocation.HiddenLastParameter)]
+ [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "EventPipeInternal_WasiRunJobs")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static partial bool RunJobs();
+
+ internal static void Pump()
+ {
+ if (s_pumpRunning)
+ {
+ return;
+ }
+
+ s_pumpRunning = true;
+ _ = PumpAsync();
+ }
+
+ private static async Task PumpAsync()
+ {
+ try
+ {
+ while (RunJobs())
+ {
+ await Task.Delay(PumpIntervalMs).ConfigureAwait(false);
+ }
+ }
+ finally
+ {
+ s_pumpRunning = false;
+ }
+ }
+ }
+}
diff --git a/src/native/eventpipe/ep-session.c b/src/native/eventpipe/ep-session.c
index 09c6a12b41f104..72420de83d934a 100644
--- a/src/native/eventpipe/ep-session.c
+++ b/src/native/eventpipe/ep-session.c
@@ -153,6 +153,8 @@ EP_RT_DEFINE_THREAD_FUNC (streaming_thread)
static size_t streaming_loop_tick(EventPipeSession *const session) {
bool events_written = false;
bool ok;
+ if (!ep_session_has_started (session))
+ ep_rt_volatile_store_uint32_t (&session->started, 1);
if (!ep_session_get_streaming_enabled (session)){
ep_session_dec_ref (session);
return 1; // done
@@ -161,8 +163,8 @@ static size_t streaming_loop_tick(EventPipeSession *const session) {
ok = ep_session_write_all_buffers_to_file (session, &events_written);
EP_GCX_PREEMP_EXIT
if (!ok) {
+ // Keep the job so the next tick sees streaming disabled and releases its reference.
ep_disable ((EventPipeSessionID)session);
- return 1; // done
}
return 0; // continue
}
@@ -189,8 +191,10 @@ session_create_streaming_thread (EventPipeSession *session)
EP_UNREACHABLE ("Unable to create streaming thread.");
#else
ep_session_inc_ref (session);
- ep_rt_volatile_store_uint32_t (&session->started, 1);
- ep_rt_queue_job ((void *)streaming_loop_tick, (void *)session);
+ if (!ep_rt_queue_job ((void *)streaming_loop_tick, (void *)session)) {
+ // The session's own reference keeps it alive, so this only drops the job's reference.
+ ep_session_dec_ref (session);
+ }
#endif
}
@@ -400,10 +404,11 @@ ep_session_dec_ref (EventPipeSession *session)
{
ep_return_void_if_nok (session != NULL);
- EP_ASSERT (!ep_session_get_streaming_enabled (session));
-
- if (ep_rt_atomic_dec_uint32_t (&session->ref_count) == 0)
+ if (ep_rt_atomic_dec_uint32_t (&session->ref_count) == 0) {
+ // Streaming must be stopped before the last reference goes away.
+ EP_ASSERT (!ep_session_get_streaming_enabled (session));
session_free (session);
+ }
}
static