diff --git a/build.zig b/build.zig
index 3e7f285214..8421dfbc85 100644
--- a/build.zig
+++ b/build.zig
@@ -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);
@@ -570,6 +568,8 @@ pub fn build(b: *std.Build) !void {
if (is_windows) "-DWIN32_LEAN_AND_MEAN" else "",
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 });
@@ -585,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") });
}
@@ -610,14 +616,29 @@ pub fn build(b: *std.Build) !void {
emcc.addArgs(&.{
b.fmt("--sysroot={s}", .{s}),
"-sALLOW_MEMORY_GROWTH=1",
- "-sEXPORT_ALL=1",
- "-sINITIAL_MEMORY=67108864",
+ "--profiling-funcs",
+ "-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",
"-sFORCE_FILESYSTEM=1",
- "-sERROR_ON_UNDEFINED_SYMBOLS=0",
+ "-sNODERAWFS=0",
+ "-sERROR_ON_UNDEFINED_SYMBOLS=1",
+ "-sEXIT_RUNTIME=0",
+ "-sINVOKE_RUN=0",
+ "-sMODULARIZE=1",
+ "-sEXPORT_NAME=createNvim",
"--no-entry",
- "-Wl,--export-all",
- "-Wl,--no-gc-sections",
+ "-fexceptions",
+ "-flto",
+ "-sSTACK_OVERFLOW_CHECK=2",
+ "-sASSERTIONS=1",
+ "-sASYNCIFY=1",
+ "-sASYNCIFY_STACK_SIZE=65536",
});
+
emcc.addArg("-o");
const nvim_js = emcc.addOutputFileArg("nvim.js");
nvim_exe_step.dependOn(&b.addInstallFileWithDir(nvim_js, .bin, "nvim.js").step);
diff --git a/build.zig.zon b/build.zig.zon
index ef78a5ec23..94ecabf92a 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -6,8 +6,8 @@
.dependencies = .{
.zlua = .{
- .url = "git+https://github.com/natecraddock/ziglua#1dcb9f2b7b466a1609cc11e746c2efc4f17685da",
- .hash = "zlua-0.1.0-hGRpC22ABQA4y12Uz7QaHuCkKrpaa_66xidApjtFuKFg",
+ .url = "git+https://github.com/natecraddock/ziglua#2f0f668a9a7e7d4ab5d90853958837bae5bc3ca2",
+ .hash = "zlua-0.1.0-hGRpC8aUBQD4jNxDkqeKXAk_5HInJKze1SWVYbYkLuxO",
},
.lpeg = .{
.url = "https://github.com/neovim/deps/raw/d495ee6f79e7962a53ad79670cb92488abe0b9b4/opt/lpeg-1.1.0.tar.gz",
@@ -30,8 +30,8 @@
.lazy = true,
},
.libuv = .{
- .url = "git+https://github.com/allyourcodebase/libuv.git#b465d9ae435fadb4c30c3abf1fd3177dc0c8a5c5",
- .hash = "libuv-1.52.0-htqqv0xqAAAccfnkBz9DgFM9I9FSzP05_44lKS63OrZr",
+ .url = "git+https://github.com/allyourcodebase/libuv.git#c1ccce24e1077ae7780857c89fb4b3aeb9080315",
+ .hash = "libuv-1.52.0-htqqvzNqAAAB6cHIx-wIffe_mqMW5GOKjHeDBJYHGwls",
.lazy = true,
},
.utf8proc = .{ .path = "./deps/utf8proc/", .lazy = true },
diff --git a/contrib/test_wasm.html b/contrib/test_wasm.html
new file mode 100644
index 0000000000..a33964166d
--- /dev/null
+++ b/contrib/test_wasm.html
@@ -0,0 +1,68 @@
+
+
+
+
+nvim.wasm test
+
+
+nvim.wasm load test
+
+
+
+
+
+
+
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 48f1657ec0..2e2d35c52e 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -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})
diff --git a/src/wasm_stubs.c b/src/wasm_stubs.c
new file mode 100644
index 0000000000..94c121be96
--- /dev/null
+++ b/src/wasm_stubs.c
@@ -0,0 +1,181 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+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;
+}
+
+int uv_uptime(double *uptime)
+{
+ if (!uptime) {
+ return UV_EINVAL;
+ }
+ *uptime = emscripten_get_now() / 1000.0;
+ return 0;
+}
+
+int uv_resident_set_memory(size_t *rss)
+{
+ if (!rss) {
+ return UV_EINVAL;
+ }
+ *rss = uv_get_total_memory() / 2;
+ return 0;
+}
+
+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;
+}
+
+int uv__io_fork(uv_loop_t *loop)
+{
+ (void)loop;
+ return 0;
+}
+
+int uv_cpu_info(uv_cpu_info_t * *cpu_infos, int *count)
+{
+ 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;
+}
+
+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)
+{
+ 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)
+{
+ (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;
+}