diff --git a/src/wasm_stubs.c b/src/wasm_stubs.c index 94c121be96..830121d23e 100644 --- a/src/wasm_stubs.c +++ b/src/wasm_stubs.c @@ -1,5 +1,8 @@ #include +#include +#include #include +#include #include #include #include @@ -9,6 +12,23 @@ #include #include +#ifndef POLLIN +# define POLLIN 0x001 +#endif +#ifndef POLLOUT +# define POLLOUT 0x004 +#endif +#ifndef POLLERR +# define POLLERR 0x008 +#endif +#ifndef POLLHUP +# define POLLHUP 0x010 +#endif + +void uv__io_cb(uv_loop_t *loop, uv__io_t *w, unsigned events); + +// System info stubs + uint64_t uv_get_free_memory(void) { return 8 * 1024 * 1024; @@ -26,7 +46,6 @@ uint64_t uv_get_constrained_memory(void) return uv_get_total_memory(); } -// Report an idle virtual system void uv_loadavg(double avg[3]) { avg[0] = 0.0; avg[1] = 0.0; avg[2] = 0.0; @@ -46,7 +65,7 @@ int uv_resident_set_memory(size_t *rss) if (!rss) { return UV_EINVAL; } - *rss = uv_get_total_memory() / 2; + *rss = uv_get_total_memory() / 2; // fake value return 0; } @@ -67,13 +86,8 @@ int uv_exepath(char *buffer, size_t *size) return 0; } -int uv__io_fork(uv_loop_t *loop) -{ - (void)loop; - return 0; -} - -int uv_cpu_info(uv_cpu_info_t * *cpu_infos, int *count) +// Browsers have no native CPU information. So return a single virtual CPU +int uv_cpu_info(uv_cpu_info_t **cpu_infos, int *count) { if (!cpu_infos || !count) { return UV_EINVAL; @@ -92,7 +106,8 @@ int uv_cpu_info(uv_cpu_info_t * *cpu_infos, int *count) return 0; } -int uv_interface_addresses(uv_interface_address_t * *addresses, int *count) +// Browsers do not expose host network interfaces. Report only loopback. +int uv_interface_addresses(uv_interface_address_t **addresses, int *count) { if (!addresses || !count) { return UV_EINVAL; @@ -115,25 +130,138 @@ int uv_interface_addresses(uv_interface_address_t * *addresses, int *count) return 0; } -void uv__platform_invalidate_fd(uv_loop_t *loop, int fd) +#define UV_BROWSER_MAX_FD 256 + +typedef struct { + _Atomic uint32_t generation; // woken on any fd state change + _Atomic int32_t readable[UV_BROWSER_MAX_FD]; + _Atomic int32_t writable[UV_BROWSER_MAX_FD]; +} uv_browser_shared_state_t; + +static uv_browser_shared_state_t g_shared_state; + +EMSCRIPTEN_KEEPALIVE +void *uv_browser_get_shared_state_ptr(void) { - (void)loop; (void)fd; -} -int uv__io_check_fd(uv_loop_t *loop, void *w) -{ - (void)loop; (void)w; return 0; + return (void *)&g_shared_state; +} + +EMSCRIPTEN_KEEPALIVE +void uv_browser_set_readable(int fd, int32_t nbytes) +{ + if (fd < 0 || fd >= UV_BROWSER_MAX_FD) { + return; + } + atomic_store(&g_shared_state.readable[fd], nbytes); + atomic_fetch_add(&g_shared_state.generation, 1); + emscripten_futex_wake(&g_shared_state.generation, 1); +} + +EMSCRIPTEN_KEEPALIVE +void uv_browser_set_writable(int fd, int32_t nbytes) +{ + if (fd < 0 || fd >= UV_BROWSER_MAX_FD) { + return; + } + atomic_store(&g_shared_state.writable[fd], nbytes); + atomic_fetch_add(&g_shared_state.generation, 1); + emscripten_futex_wake(&g_shared_state.generation, 1); +} + +static int uv_browser_scan_ready(uv_loop_t *loop, unsigned *out_idx, int *out_revents) +{ + unsigned i; + uv__io_t *w; + int revents; + + for (i = 0; i < loop->nwatchers && i < UV_BROWSER_MAX_FD; i++) { + w = loop->watchers[i]; + if (w == NULL || w->pevents == 0) { + continue; + } + + revents = 0; + if ((w->pevents & POLLIN) && atomic_load(&g_shared_state.readable[i]) > 0) { + revents |= POLLIN; + } + if ((w->pevents & POLLOUT) && atomic_load(&g_shared_state.writable[i]) > 0) { + revents |= POLLOUT; + } + + if (revents) { + *out_idx = i; + *out_revents = revents; + return 1; + } + } + return 0; } -// Replaces the kernel poll() call void uv__io_poll(uv_loop_t *loop, int timeout) { - if (timeout > 0) { - emscripten_sleep(timeout); - uv_update_time(loop); - emscripten_sleep(10); - uv_update_time(loop); - } else if (timeout == 0) { - emscripten_sleep(0); + double deadline; + double remaining; + uint32_t g0; + unsigned idx; + int revents; + int have_signal; + uv__io_t *w; + + deadline = (timeout < 0) ? -1.0 : (emscripten_get_now() + (double)timeout); + have_signal = 0; + + while (true) { + /* Any state change that lands between this load and the scan below is still caught by the scan + itself. Any change that lands after the scan will bump generation past g0, so the futex_wait call + falls through immediately instead of sleeping through it. This closes the narrow window where a notify + could otherwise land between "nothing is ready" and "waiting". */ + + g0 = atomic_load(&g_shared_state.generation); + + if (uv_browser_scan_ready(loop, &idx, &revents)) { + have_signal = 1; + break; + } + + if (deadline >= 0.0) { + remaining = deadline - emscripten_get_now(); + if (remaining <= 0.0) { + break; + } + } else { + remaining = 60000.0; + } + + int rc = emscripten_futex_wait(&g_shared_state.generation, g0, remaining); + } + + uv_update_time(loop); + + if (!have_signal) { + return; + } + + for (idx = 0; idx < loop->nwatchers && idx < UV_BROWSER_MAX_FD; idx++) { + w = loop->watchers[idx]; + if (w == NULL || w->pevents == 0) { + continue; + } + + revents = 0; + if ((w->pevents & POLLIN) && atomic_load(&g_shared_state.readable[idx]) > 0) { + revents |= POLLIN; + } + if ((w->pevents & POLLOUT) && atomic_load(&g_shared_state.writable[idx]) > 0) { + revents |= POLLOUT; + } + + revents &= w->pevents | POLLERR | POLLHUP; + + if (revents == 0) { + continue; + } + + uv__io_cb(loop, w, revents); } } @@ -151,7 +279,29 @@ void uv__platform_loop_delete(uv_loop_t *loop) (void)loop; } -// Fakes thread names when nvim tracks or debugs its internal processes +void uv__platform_invalidate_fd(uv_loop_t *loop, int fd) +{ + if (fd < 0 || fd >= UV_BROWSER_MAX_FD) { + return; + } + atomic_store(&g_shared_state.readable[fd], 0); + atomic_store(&g_shared_state.writable[fd], 0); +} + +int uv__io_check_fd(uv_loop_t *loop, int fd) +{ + (void)loop; (void)fd; + return 0; +} + +int uv__io_fork(uv_loop_t *loop) +{ + (void)loop; + return 0; +} + +// pthread shims + int pthread_getname_np(pthread_t thread, char *name, size_t len) { (void)thread; @@ -165,17 +315,12 @@ int pthread_getname_np(pthread_t thread, char *name, size_t len) int pthread_setname_np(pthread_t thread, const char *name) { - (void)thread; (void)name; return 0; + (void)thread; (void)name; + return 0; } + int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param *param) { - (void)thread; (void)policy; (void)param; return 0; -} -int sched_get_priority_max(int policy) -{ - (void)policy; return 1; -} -int sched_get_priority_min(int policy) -{ - (void)policy; return 1; + (void)thread; (void)policy; (void)param; + return 0; }