build: add emscripten/wasm32 support - #462
Merged
Merged
Conversation
An unhandled exception leaves vm->exception set on the way out of uc_vm_execute_chunk(), and nothing clears it afterwards. The next uc_vm_execute() on that VM reaches the exception check before it retires a single instruction, unwinds immediately and returns ERROR_RUNTIME, having handed the exception handler the message from the previous run. Every call after that does the same, so one uncaught error retires the VM for the life of the process. uc_vm_call() is the other way into the interpreter and already clears the exception on entry for this reason; uc_vm_execute() now matches it. There was no way for an embedder to do this itself: uc_vm_clear_exception() is static and nothing equivalent is exported. The ucode binary executes one program and exits, so it never sees this. It is embedders that run several scripts against one long-lived VM that lose it, and the failure is quiet, because the reported error is a real one from earlier rather than anything the current script did. Signed-off-by: John Crispin <john@phrozen.org>
jow-
force-pushed
the
emscripten-support
branch
4 times, most recently
from
October 2, 2026 20:09
3d6139d to
baa1d11
Compare
Two small guards so the core compiles and links under emscripten: - internal/platform.h: treat __EMSCRIPTEN__ like __linux__ for the <endian.h>/<sys/sysmacros.h> include branch (both are provided by the emscripten sysroot) - module.h: mark the uc_module_entry() trampoline weak. It is defined in every module object via this header, so linking several modules into a single executable (static/wasm builds) previously failed with a multiple-definition error; the linker now keeps one copy. The dlopen() path is unaffected (the symbol stays exported). Signed-off-by: Jo-Philipp Wich <jo@mein.io>
uc_source_new_file() sets runpath to the filename, but the buffer constructor left it uninitialized, so module path resolution (uc_compiler_canonicalize_path) and include() saw garbage for buffer-based sources, and the free() of the "foreign" runpath in the source destructor was a heap corruption waiting to happen. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Build libucode.a and ucode-modules.a for wasm32: cmake -B build-wasm -DCMAKE_TOOLCHAIN_FILE=cmake/emscripten.cmake libucode is built as a static library (no dlopen under wasm) and the CLI, udbg, plugin modules, unit tests and examples are skipped. The default LIB_SEARCH_PATH is "./*.uc"; embedders extend REQUIRE_SEARCH_PATH at runtime. ucode-modules.a contains the modules selected via UCODE_WASM_MODULES (default: io math struct fs zlib resolv socket digest), each compiled with uc_module_init renamed to uc_module_init_<name> so several can be linked into one executable. The embedder calls the renamed inits itself (e.g. to preload the modules into the global modules dictionary) and lists the names in config->force_dynlink_list, so the compiler resolves them at compile time and the runtime require() cache hits the preloaded objects. The emscripten sysroot ships neither json-c nor libmd, so cmake/emscripten-deps.cmake fetches and builds both as external projects (only included for the wasm build; toolchain files cannot host this because they are re-included by every try_compile() probe): - json-c 0.18 via its own CMake build, with -Wno-macro-redefined (math_compat.h redefines NAN/INFINITY/isnan/isinf) and -DHAVE_SNPRINTF=1 (its snprintf check is gated on UNIX/MINGW/ CYGWIN, which the Generic system does not match) - libmd 1.0.4 via the cmake/deps/libmd wrapper, since upstream is autotools-only. The wrapper compiles the hash sources directly with a minimal config.h and replicates the autotools expansion of the helper.c template (one translation unit per algorithm) for the one-shot <ALGO>Data() functions. Per-module build details: zlib needs -sUSE_ZLIB=1 (emscripten's sysroot zlib), socket needs -Wno-sign-compare (emscripten's CMSG_NXTHDR macro compares size_t and ptrdiff_t). Server-side socket APIs (bind/listen/accept) are compiled in but not supported by emscripten; they fail at runtime. Also un-ignore cmake/*.cmake (the *.cmake pattern previously caught the new toolchain and dependency files). Signed-off-by: Jo-Philipp Wich <jo@mein.io>
jow-
force-pushed
the
emscripten-support
branch
from
October 2, 2026 20:14
baa1d11 to
4e65efb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds build support for compiling ucode to WebAssembly via emscripten, plus a few related fixes:
cmake -B build-wasm -DCMAKE_TOOLCHAIN_FILE=cmake/emscripten.cmakebuildslibucode.aanducode-modules.afor wasm32. The CLI, udbg, plugin modules, unit tests and examples are skipped; json-c and libmd are fetched and built as external projects since the emscripten sysroot lacks them. Modules are selected viaUCODE_WASM_MODULES(default: io math struct fs zlib resolv socket digest), each with itsuc_module_initrenamed touc_module_init_<name>so several can be linked into one executable.__EMSCRIPTEN__like__linux__ininternal/platform.h, mark theuc_module_entry()trampoline weak (multiple-definition error when linking several modules statically).runpathuninitialized, so module path resolution andinclude()saw garbage for buffer-based sources, and the destructor's free() of the "foreign" runpath was a heap corruption waiting to happen.vm->exceptionset, so the nextuc_vm_execute()on that VM unwound immediately and returned ERROR_RUNTIME forever, retiring the VM for the life of the process.uc_vm_call()already cleared it on entry;uc_vm_execute()now matches it.