[browser] native - no sockets, no threads, no blocking#123962
[browser] native - no sockets, no threads, no blocking#123962pavelsavara wants to merge 26 commits intodotnet:mainfrom
Conversation
|
Tagging subscribers to this area: @karelz, @dotnet/ncl |
There was a problem hiding this comment.
Pull request overview
This pull request adds support for the browser (WASM) target by providing stub implementations for features not available in the browser environment, specifically: no sockets, no threads, and no blocking operations.
Changes:
- Added browser-specific stub implementations for networking, interface addresses, network statistics, and console APIs
- Updated error messages in WASM-specific files from "WASI" to "WASM" for clarity
- Modified CoreCLR PAL to prevent blocking operations and thread suspension in single-threaded WASM
- Updated build configuration to disable socket support for browser targets and include browser-specific source files
- Added perftracing configuration for browser builds
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/native/libs/System.Native/pal_signal_wasm.c | Updated error messages from "WASI" to "WASM" |
| src/native/libs/System.Native/pal_networkstatistics_wasm.c | New file with stub implementations returning "not supported" for network statistics APIs |
| src/native/libs/System.Native/pal_networking_browser.c | New file with comprehensive stub implementations for all networking APIs, returning ENOTSUP |
| src/native/libs/System.Native/pal_io.c | Added early return for pipe() on WASM targets with ENOTSUP |
| src/native/libs/System.Native/pal_interfaceaddresses_browser.c | New file with stub implementations for interface enumeration APIs |
| src/native/libs/System.Native/pal_dynamicload_wasm.c | Updated error messages from "WASI" to "WASM" |
| src/native/libs/System.Native/pal_console_wasm.c | New console implementation for WASM with some working features (stdin/isatty) and stubs for unsupported features |
| src/native/libs/System.Native/CMakeLists.txt | Updated build configuration to differentiate between browser and WASI targets, using browser-specific source files |
| src/native/eventpipe/ds-ipc-pal-websocket.c | Added string duplication helper functions for eventpipe |
| src/mono/mono/profiler/log.c | Changed sys/socket.h include guard from HOST_WIN32 to HAVE_SYS_SOCKET_H |
| src/mono/mono/profiler/helper.c | Changed sys/socket.h include guard from HOST_WIN32 to HAVE_SYS_SOCKET_H |
| src/mono/mono/profiler/CMakeLists.txt | Disabled AOT profiler for browser targets |
| src/mono/mono/mini/cfgdump.c | Added guard for sys/socket.h include |
| src/mono/browser/runtime/dotnet.d.ts | Formatting fix: removed space before parentheses in function signatures |
| src/coreclr/pal/src/thread/threadsusp.cpp | Added guard to prevent thread suspension in single-threaded WASM |
| src/coreclr/pal/src/thread/thread.cpp | Added guard to prevent CREATE_SUSPENDED flag in single-threaded WASM |
| src/coreclr/pal/src/synchmgr/wait.cpp | Added guards to prevent blocking waits and made Sleep return immediately in WASM |
| src/coreclr/pal/src/synchmgr/synchmanager.cpp | Added assertion to prevent waiting on non-signaled objects in WASM |
| src/coreclr/pal/src/debug/debug.cpp | Added WASM-specific implementation of PAL_ProbeMemory using emscripten heap size |
| eng/native/tryrun.browser.cmake | Disabled HAVE_SYS_SOCKET_H for browser targets |
| eng/native.wasm.targets | Added perftracing configuration flags for browser builds |
|
Whole this PR makes emscripten emulator smaller only by 20KB of JavaScript - from 154KB to 134KB. I hoped for more, I'm not sure I want to merge this... |
|
These are JS emulators of The rest of the JS support is OK. See https://gist.github.com/pavelsavara/f11dd1078548a16fc923de444f1c9d9b All the symbols in the wasm files are https://gist.github.com/pavelsavara/b7653066a83554168739558630d34847 Among them these caught my eye |
#117813 is the main offender. |
|
|
||
| #include <errno.h> | ||
| #include <string.h> | ||
| #include <stdlib.h> | ||
| #include <unistd.h> | ||
|
|
||
| // Error codes from pal_errno.h | ||
| #define Error_ENOTSUP 95 | ||
| #define Error_EFAULT 14 | ||
| #define Error_EAFNOSUPPORT 97 | ||
| #define Error_EINVAL 22 | ||
| #define Error_EBADF 9 | ||
| #define Error_SUCCESS 0 | ||
|
|
There was a problem hiding this comment.
The Error_* constants are hard-coded to raw Unix errno values (e.g., ENOTSUP=95, EFAULT=14). System.Native APIs are expected to return PAL Error enum values (see pal_error_common.h), which are different (e.g., Error_ENOTSUP is 0x1003D). Returning raw errno values here will be misinterpreted by managed code. Use the shared PAL Error constants (include pal_errno.h / pal_error_common.h) instead of redefining them.
| #include <errno.h> | |
| #include <string.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| // Error codes from pal_errno.h | |
| #define Error_ENOTSUP 95 | |
| #define Error_EFAULT 14 | |
| #define Error_EAFNOSUPPORT 97 | |
| #define Error_EINVAL 22 | |
| #define Error_EBADF 9 | |
| #define Error_SUCCESS 0 | |
| #include "pal_errno.h" | |
| #include <errno.h> | |
| #include <string.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> |
| if (createdSocket == NULL) | ||
| { | ||
| return Error_EFAULT; | ||
| } | ||
|
|
||
| // Allocate fake socket structure | ||
| FakeSocket* fs = (FakeSocket*)malloc(sizeof(FakeSocket)); | ||
| if (fs == NULL) | ||
| { | ||
| *createdSocket = -1; | ||
| return Error_EFAULT; | ||
| } | ||
|
|
||
| fs->addressFamily = addressFamily; | ||
| fs->socketType = socketType; | ||
| fs->protocolType = protocolType; | ||
| fs->isListening = 0; | ||
| fs->magic = FAKE_SOCKET_MAGIC; | ||
|
|
||
| *createdSocket = (intptr_t)fs; | ||
| return Error_SUCCESS; |
There was a problem hiding this comment.
SystemNative_Socket returns a malloc'ed pointer cast to intptr_t as the "socket" handle. In the rest of System.Native (and managed code), sockets are treated as OS file descriptors (see ToFileDescriptor casts/asserts). Returning a pointer here risks invalid close()/fcntl usage (pointer truncation) and makes resource lifetime undefined (e.g., only freed in SystemNative_Shutdown). Consider returning Error_ENOTSUP (and setting *createdSocket = -1) so callers fail predictably, or ensure the handle is a real fd compatible with the existing PAL contract.
| if (createdSocket == NULL) | |
| { | |
| return Error_EFAULT; | |
| } | |
| // Allocate fake socket structure | |
| FakeSocket* fs = (FakeSocket*)malloc(sizeof(FakeSocket)); | |
| if (fs == NULL) | |
| { | |
| *createdSocket = -1; | |
| return Error_EFAULT; | |
| } | |
| fs->addressFamily = addressFamily; | |
| fs->socketType = socketType; | |
| fs->protocolType = protocolType; | |
| fs->isListening = 0; | |
| fs->magic = FAKE_SOCKET_MAGIC; | |
| *createdSocket = (intptr_t)fs; | |
| return Error_SUCCESS; | |
| (void)addressFamily; | |
| (void)socketType; | |
| (void)protocolType; | |
| if (createdSocket == NULL) | |
| { | |
| return Error_EFAULT; | |
| } | |
| *createdSocket = -1; | |
| return Error_ENOTSUP; |
| int32_t SystemNative_SetSockOpt( | ||
| intptr_t socket, int32_t socketOptionLevel, int32_t socketOptionName, uint8_t* optionValue, int32_t optionLen) | ||
| { | ||
| FakeSocket* fs = GetFakeSocket(socket); | ||
| if (fs == NULL) | ||
| { | ||
| return Error_EBADF; | ||
| } | ||
|
|
||
| (void)socketOptionLevel; | ||
| (void)socketOptionName; | ||
| (void)optionValue; | ||
| (void)optionLen; | ||
| // Pretend setting options succeeds | ||
| return Error_SUCCESS; | ||
| } | ||
|
|
||
| int32_t SystemNative_SetRawSockOpt( | ||
| intptr_t socket, int32_t socketOptionLevel, int32_t socketOptionName, uint8_t* optionValue, int32_t optionLen) | ||
| { | ||
| FakeSocket* fs = GetFakeSocket(socket); | ||
| if (fs == NULL) | ||
| { | ||
| return Error_EBADF; | ||
| } | ||
|
|
||
| (void)socketOptionLevel; | ||
| (void)socketOptionName; | ||
| (void)optionValue; | ||
| (void)optionLen; | ||
| // Pretend setting options succeeds | ||
| return Error_SUCCESS; | ||
| } |
There was a problem hiding this comment.
SystemNative_SetSockOpt / SystemNative_SetRawSockOpt currently return success unconditionally for a fake socket. If socket options are not actually supported in the browser target, reporting success can cause managed code to continue under incorrect assumptions. Prefer returning Error_ENOTSUP (or only succeeding for an explicitly documented no-op subset) to keep behavior consistent with other unsupported networking APIs in this file.
| int32_t SystemNative_GetTcpGlobalStatistics(TcpGlobalStatistics* retStats) | ||
| { | ||
| if (retStats == NULL) | ||
| { | ||
| return -1; | ||
| } | ||
| memset(retStats, 0, sizeof(TcpGlobalStatistics)); | ||
| return -1; // Not supported | ||
| } | ||
|
|
||
| int32_t SystemNative_GetIPv4GlobalStatistics(IPv4GlobalStatistics* retStats) | ||
| { | ||
| if (retStats == NULL) | ||
| { | ||
| return -1; | ||
| } | ||
| memset(retStats, 0, sizeof(IPv4GlobalStatistics)); | ||
| return -1; // Not supported |
There was a problem hiding this comment.
These stubs return -1 for unsupported operations but don't set errno (ENOTSUP/EFAULT). The existing fallback implementations in pal_networkstatistics.c set errno=ENOTSUP before returning -1, and managed callers may rely on errno/GetLastPInvokeError for exception details. Set errno appropriately on all failure returns (including the NULL retStats case).
| return 0; | ||
| } | ||
|
|
||
| int32_t SystemNative_GetActiveTcpConnectionInfos(NativeTcpConnectionInformation* infos, int32_t* infoCount) | ||
| { | ||
| (void)infos; | ||
| if (infoCount != NULL) | ||
| { | ||
| *infoCount = 0; | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| int32_t SystemNative_GetEstimatedUdpListenerCount(void) | ||
| { | ||
| return 0; | ||
| } | ||
|
|
||
| int32_t SystemNative_GetActiveUdpListeners(IPEndPointInfo* infos, int32_t* infoCount) | ||
| { | ||
| (void)infos; | ||
| if (infoCount != NULL) | ||
| { | ||
| *infoCount = 0; | ||
| } | ||
| return 0; |
There was a problem hiding this comment.
The "estimated/active" count APIs return 0 (success) in this stub implementation. On other platforms, when network statistics aren't supported, these return -1 with errno=ENOTSUP (see pal_networkstatistics.c fallback). Returning 0 changes semantics from "not supported" to "supported with empty data", which can lead to incorrect behavior in managed code. Align these with the existing not-supported contract (return -1 and set errno=ENOTSUP).
| return 0; | |
| } | |
| int32_t SystemNative_GetActiveTcpConnectionInfos(NativeTcpConnectionInformation* infos, int32_t* infoCount) | |
| { | |
| (void)infos; | |
| if (infoCount != NULL) | |
| { | |
| *infoCount = 0; | |
| } | |
| return 0; | |
| } | |
| int32_t SystemNative_GetEstimatedUdpListenerCount(void) | |
| { | |
| return 0; | |
| } | |
| int32_t SystemNative_GetActiveUdpListeners(IPEndPointInfo* infos, int32_t* infoCount) | |
| { | |
| (void)infos; | |
| if (infoCount != NULL) | |
| { | |
| *infoCount = 0; | |
| } | |
| return 0; | |
| errno = ENOTSUP; | |
| return -1; | |
| } | |
| int32_t SystemNative_GetActiveTcpConnectionInfos(NativeTcpConnectionInformation* infos, int32_t* infoCount) | |
| { | |
| (void)infos; | |
| (void)infoCount; | |
| errno = ENOTSUP; | |
| return -1; | |
| } | |
| int32_t SystemNative_GetEstimatedUdpListenerCount(void) | |
| { | |
| errno = ENOTSUP; | |
| return -1; | |
| } | |
| int32_t SystemNative_GetActiveUdpListeners(IPEndPointInfo* infos, int32_t* infoCount) | |
| { | |
| (void)infos; | |
| (void)infoCount; | |
| errno = ENOTSUP; | |
| return -1; |
| int32_t SystemNative_GetNetworkInterfaces(int32_t* interfaceCount, NetworkInterfaceInfo** interfaces, int32_t* addressCount, IpAddressInfo** addressList) | ||
| { | ||
| if (interfaceCount == NULL || interfaces == NULL || addressCount == NULL || addressList == NULL) | ||
| { |
There was a problem hiding this comment.
When argument validation fails (returning -1), this stub doesn't set errno. Other System.Native APIs typically set errno (e.g., EFAULT) to enable managed code to surface a meaningful P/Invoke error. Set errno=EFAULT (or another appropriate value) before returning -1 for invalid pointer arguments.
| { | |
| { | |
| errno = EFAULT; |
# Conflicts: # src/native/corehost/corehost.proj # src/native/libs/build-native.proj
Fixes #122506