build(wasm): add smoke test

This commit is contained in:
rawan10101
2026-06-25 14:37:40 +03:00
parent 4932862d9a
commit 7cbea86f49
4 changed files with 221 additions and 92 deletions

View File

@@ -552,8 +552,6 @@ pub fn build(b: *std.Build) !void {
var filtered_sources = try std.ArrayList([]u8).initCapacity(b.allocator, nvim_sources.items.len);
for (nvim_sources.items) |s| {
// wasm_stubs.c defines libuv symbols & excludes it when real libuv is linked
if (!is_wasm and std.mem.eql(u8, s.name, "wasm_stubs.c")) continue;
try filtered_sources.append(b.allocator, b.fmt("src/nvim/{s}", .{s.name}));
}
const src_paths = try b.allocator.alloc([]u8, filtered_sources.items.len + unit_test_sources.items.len);
@@ -571,6 +569,7 @@ pub fn build(b: *std.Build) !void {
if (is_windows) "-DUTF8PROC_STATIC" else "",
if (use_unibilium) "-DHAVE_UNIBILIUM" else "",
if (is_wasm) "-flto" else "",
if (is_wasm) "-DMAKE_LIB" else "",
};
nvim_mod.addCSourceFiles(.{ .files = src_paths, .flags = &flags });
@@ -586,6 +585,12 @@ pub fn build(b: *std.Build) !void {
"src/cjson/strbuf.c",
}, .flags = &flags });
if (is_wasm) {
nvim_mod.addCSourceFiles(.{ .files = &.{
"src/wasm_stubs.c",
}, .flags = &flags });
}
if (is_windows) {
nvim_mod.addWin32ResourceFile(.{ .file = b.path("src/nvim/os/nvim.rc") });
}
@@ -612,8 +617,9 @@ pub fn build(b: *std.Build) !void {
b.fmt("--sysroot={s}", .{s}),
"-sALLOW_MEMORY_GROWTH=1",
"--profiling-funcs",
"-sEXPORTED_FUNCTIONS=_main,_malloc,_free",
"-sEXPORTED_RUNTIME_METHODS=stringToUTF8,lengthBytesUTF8,setValue,getValue,UTF8ToString,callMain,ENV,FS,HEAPU8",
"-sEXPORTED_FUNCTIONS=_nvim_main,_malloc,_free",
"-Wno-undefined",
"-sEXPORTED_RUNTIME_METHODS=stringToUTF8,lengthBytesUTF8,setValue,getValue,UTF8ToString,ENV,FS,HEAPU8",
"-sSTACK_SIZE=8388608",
"-sINITIAL_MEMORY=268435456",
"-sMAXIMUM_MEMORY=2147483648",
@@ -624,7 +630,6 @@ pub fn build(b: *std.Build) !void {
"-sINVOKE_RUN=0",
"-sMODULARIZE=1",
"-sEXPORT_NAME=createNvim",
"-Wl,--unresolved-symbols=ignore-all",
"--no-entry",
"-fexceptions",
"-flto",

68
contrib/test_wasm.html Normal file
View File

@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>nvim.wasm test</title>
</head>
<body style="font-family: monospace; white-space: pre-wrap">
<h3>nvim.wasm load test</h3>
<script src="../zig-out/bin/nvim.js"></script>
<script>
function log(msg) {
document.body.append(msg + "\n");
}
function makeArgv(Module, args) {
const ptrs = [];
for (const s of args) {
const len = Module.lengthBytesUTF8(s) + 1;
const p = Module._malloc(len);
Module.stringToUTF8(s, p, len);
ptrs.push(p);
}
const argv = Module._malloc((ptrs.length + 1) * 4);
for (let i = 0; i < ptrs.length; i++) {
Module.setValue(argv + i * 4, ptrs[i], '*');
}
Module.setValue(argv + ptrs.length * 4, 0, '*');
return { argc: ptrs.length, argv };
}
createNvim({
noInitialRun: true,
print: (t) => log("[stdout] " + t),
printErr: (t) => log("[stderr] " + t),
preRun: [(m) => {
m.ENV.TERM = "dumb";
m.ENV.HOME = "/home/user";
m.ENV.VIMRUNTIME = "/runtime";
}],
})
.then((m) => {
log("module loaded");
const args = makeArgv(m, [
"nvim",
"--version"
]);
const ret = m._nvim_main(args.argc, args.argv);
log("_nvim_main returned: " + ret);
})
.catch((e) => {
log("module init failed: " + e);
});
</script>
</body>
</html>

View File

@@ -443,6 +443,7 @@ list(APPEND UNCRUSTIFY_NVIM_SOURCES ${NVIM_SOURCES} ${NVIM_HEADERS})
list(APPEND UNCRUSTIFY_NVIM_SOURCES
${PROJECT_SOURCE_DIR}/src/tee/tee.c
${PROJECT_SOURCE_DIR}/src/xxd/xxd.c
${PROJECT_SOURCE_DIR}/src/wasm_stubs.c
)
foreach(sfile ${NVIM_SOURCES})

View File

@@ -1,126 +1,181 @@
#include <emscripten.h>
#include <pthread.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <time.h>
#include <uv.h>
#include <pthread.h>
#include <emscripten.h>
uint64_t uv_get_free_memory(void) { return 8 * 1024 * 1024; }
uint64_t uv_get_total_memory(void) { return 256 * 1024 * 1024; }
uint64_t uv_get_available_memory(void) { return uv_get_free_memory(); }
uint64_t uv_get_constrained_memory(void) { return uv_get_total_memory(); }
uint64_t uv_get_free_memory(void)
{
return 8 * 1024 * 1024;
}
uint64_t uv_get_total_memory(void)
{
return 256 * 1024 * 1024;
}
uint64_t uv_get_available_memory(void)
{
return uv_get_free_memory();
}
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;
avg[0] = 0.0; avg[1] = 0.0; avg[2] = 0.0;
}
int uv_uptime(double* uptime)
int uv_uptime(double *uptime)
{
if (!uptime) return UV_EINVAL;
*uptime = emscripten_get_now() / 1000.0;
return 0;
if (!uptime) {
return UV_EINVAL;
}
*uptime = emscripten_get_now() / 1000.0;
return 0;
}
int uv_resident_set_memory(size_t* rss)
int uv_resident_set_memory(size_t *rss)
{
if (!rss) return UV_EINVAL;
*rss = uv_get_total_memory() / 2;
return 0;
if (!rss) {
return UV_EINVAL;
}
*rss = uv_get_total_memory() / 2;
return 0;
}
int uv_exepath(char* buffer, size_t* size)
int uv_exepath(char *buffer, size_t *size)
{
if (!buffer || !size) return UV_EINVAL;
const char* exepath = "/nvim.wasm";
size_t len = strlen(exepath);
if (*size <= len) {
*size = len + 1;
return UV_ENOBUFS;
}
memcpy(buffer, exepath, len);
buffer[len] = '\0';
*size = len;
return 0;
if (!buffer || !size) {
return UV_EINVAL;
}
const char *exepath = "/nvim.wasm";
size_t len = strlen(exepath);
if (*size <= len) {
*size = len + 1;
return UV_ENOBUFS;
}
memcpy(buffer, exepath, len);
buffer[len] = '\0';
*size = len;
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)
int uv__io_fork(uv_loop_t *loop)
{
if (!cpu_infos || !count) return UV_EINVAL;
*cpu_infos = (uv_cpu_info_t*) malloc(sizeof(uv_cpu_info_t));
if (!*cpu_infos) return UV_ENOMEM;
uv_cpu_info_t* cpu = *cpu_infos;
cpu->model = "WebAssembly Virtual CPU";
cpu->speed = 0;
cpu->cpu_times.user = 0; cpu->cpu_times.nice = 0; cpu->cpu_times.sys = 0;
cpu->cpu_times.idle = 0; cpu->cpu_times.irq = 0;
*count = 1;
return 0;
(void)loop;
return 0;
}
int uv_interface_addresses(uv_interface_address_t** addresses, int* count)
int uv_cpu_info(uv_cpu_info_t * *cpu_infos, int *count)
{
if (!addresses || !count) return UV_EINVAL;
*addresses = (uv_interface_address_t*) malloc(sizeof(uv_interface_address_t));
if (!*addresses) return UV_ENOMEM;
if (!cpu_infos || !count) {
return UV_EINVAL;
}
*cpu_infos = (uv_cpu_info_t *)malloc(sizeof(uv_cpu_info_t));
if (!*cpu_infos) {
return UV_ENOMEM;
}
uv_interface_address_t* addr = *addresses;
memset(addr, 0, sizeof(uv_interface_address_t));
addr->name = strdup("lo");
if (!addr->name) { free(addr); return UV_ENOMEM; }
addr->is_internal = 1;
uv_ip4_addr("127.0.0.1", 0, (struct sockaddr_in*)&addr->address);
uv_ip4_addr("255.0.0.0", 0, (struct sockaddr_in*)&addr->netmask);
*count = 1;
return 0;
uv_cpu_info_t *cpu = *cpu_infos;
cpu->model = "WebAssembly Virtual CPU";
cpu->speed = 0;
cpu->cpu_times.user = 0; cpu->cpu_times.nice = 0; cpu->cpu_times.sys = 0;
cpu->cpu_times.idle = 0; cpu->cpu_times.irq = 0;
*count = 1;
return 0;
}
void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) { (void)loop; (void)fd; }
int uv__io_check_fd(uv_loop_t* loop, void* w) { (void)loop; (void)w; return 0; }
int uv_interface_addresses(uv_interface_address_t * *addresses, int *count)
{
if (!addresses || !count) {
return UV_EINVAL;
}
*addresses = (uv_interface_address_t *)malloc(sizeof(uv_interface_address_t));
if (!*addresses) {
return UV_ENOMEM;
}
uv_interface_address_t *addr = *addresses;
memset(addr, 0, sizeof(uv_interface_address_t));
addr->name = strdup("lo");
if (!addr->name) {
free(addr); return UV_ENOMEM;
}
addr->is_internal = 1;
uv_ip4_addr("127.0.0.1", 0, (struct sockaddr_in *)&addr->address);
uv_ip4_addr("255.0.0.0", 0, (struct sockaddr_in *)&addr->netmask);
*count = 1;
return 0;
}
void uv__platform_invalidate_fd(uv_loop_t *loop, int fd)
{
(void)loop; (void)fd;
}
int uv__io_check_fd(uv_loop_t *loop, void *w)
{
(void)loop; (void)w; 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);
}
}
int uv__platform_loop_init(uv_loop_t* loop)
void uv__io_poll(uv_loop_t *loop, int timeout)
{
if (!loop) return UV_EINVAL;
loop->backend_fd = -1;
return 0;
if (timeout > 0) {
emscripten_sleep(timeout);
uv_update_time(loop);
emscripten_sleep(10);
uv_update_time(loop);
} else if (timeout == 0) {
emscripten_sleep(0);
}
}
void uv__platform_loop_delete(uv_loop_t* loop) { (void)loop; }
int uv__platform_loop_init(uv_loop_t *loop)
{
if (!loop) {
return UV_EINVAL;
}
loop->backend_fd = -1;
return 0;
}
void uv__platform_loop_delete(uv_loop_t *loop)
{
(void)loop;
}
// Fakes thread names when nvim tracks or debugs its internal processes
int pthread_getname_np(pthread_t thread, char* name, size_t len)
int pthread_getname_np(pthread_t thread, char *name, size_t len)
{
(void)thread;
if (!name || len < 1) return EINVAL;
strncpy(name, "nvim-main", len - 1);
name[len - 1] = '\0';
return 0;
(void)thread;
if (!name || len < 1) {
return EINVAL;
}
strncpy(name, "nvim-main", len - 1);
name[len - 1] = '\0';
return 0;
}
int pthread_setname_np(pthread_t thread, const char* name) { (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; }
int pthread_setname_np(pthread_t thread, const char *name)
{
(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;
}