From 099f447a4e035197794f2e8ee6cac05bf2d1162b Mon Sep 17 00:00:00 2001 From: Jakub Gonet Date: Mon, 1 Dec 2025 14:24:15 +0100 Subject: [PATCH 1/5] wasm: adapt cmakelists Signed-off-by: Jakub Gonet --- CMakeLists.txt | 1 + src/platforms/emscripten/src/CMakeLists.txt | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 32484ee851..5231f057c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ option(AVM_RELEASE "Build an AtomVM release" OFF) option(AVM_CREATE_STACKTRACES "Create stacktraces" ON) option(AVM_BUILD_RUNTIME_ONLY "Only build the AtomVM runtime" OFF) option(COVERAGE "Build for code coverage" OFF) +option(AVM_USE_WASM_MJS "Use ES modules for Emscripten platform" OFF) option(AVM_PRINT_PROCESS_CRASH_DUMPS "Print crash reports when processes die with non-standard reasons" ON) # JIT & execution of precompiled code diff --git a/src/platforms/emscripten/src/CMakeLists.txt b/src/platforms/emscripten/src/CMakeLists.txt index 38da49320f..16f36c7c94 100644 --- a/src/platforms/emscripten/src/CMakeLists.txt +++ b/src/platforms/emscripten/src/CMakeLists.txt @@ -28,8 +28,12 @@ add_subdirectory(../../../libAtomVM libAtomVM) target_link_libraries(AtomVM PUBLIC libAtomVM) target_compile_options(libAtomVM PUBLIC -O3 -fno-exceptions -fno-rtti -pthread -sINLINING_LIMIT -sUSE_ZLIB=1) target_compile_definitions(libAtomVM PRIVATE WITH_ZLIB) -target_link_options(AtomVM PRIVATE -sEXPORTED_RUNTIME_METHODS=ccall -sUSE_ZLIB=1 -O3 -pthread -sFETCH -lwebsocket.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/atomvm.pre.js) - +if (AVM_USE_WASM_MJS) + target_link_options(AtomVM PRIVATE -sEXPORTED_RUNTIME_METHODS=ccall,cwrap,stringToNewUTF8 -sEMULATE_FUNCTION_POINTER_CASTS=1 -sEXPORTED_FUNCTIONS=_malloc,_cast,_call,_main -sEXPORT_ES6=1 -sUSE_ZLIB=1 -O3 -pthread -sFETCH -lwebsocket.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/atomvm.pre.js) + set(CMAKE_EXECUTABLE_SUFFIX ".mjs") +else() + target_link_options(AtomVM PRIVATE -sEXPORTED_RUNTIME_METHODS=ccall -sUSE_ZLIB=1 -O3 -pthread -sFETCH -lwebsocket.js --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/atomvm.pre.js) +endif() if (CMAKE_BUILD_TYPE STREQUAL "Debug") target_link_options(AtomVM PRIVATE -sASSERTIONS=2 -sSAFE_HEAP -sSTACK_OVERFLOW_CHECK) endif() From 9859c62936ef902d11de8c396f3c92c4c2955da3 Mon Sep 17 00:00:00 2001 From: Jakub Gonet Date: Sat, 20 Sep 2025 13:17:02 +0300 Subject: [PATCH 2/5] wasm: add JS scaffolding for tracked values Signed-off-by: Jakub Gonet --- src/platforms/emscripten/src/atomvm.pre.js | 60 ++++++++++++++++++++-- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/src/platforms/emscripten/src/atomvm.pre.js b/src/platforms/emscripten/src/atomvm.pre.js index 73ac9f647a..91f0f7484d 100644 --- a/src/platforms/emscripten/src/atomvm.pre.js +++ b/src/platforms/emscripten/src/atomvm.pre.js @@ -17,10 +17,60 @@ * * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later */ -Module['cast'] = function(name, message) { - ccall("cast", 'void', ['string', 'string'], [name, message]); +Module["cast"] = function (name, message) { + ccall("cast", "void", ["string", "string"], [name, message]); }; -Module['call'] = async function(name, message) { - const promiseId = ccall("call", 'integer', ['string', 'string'], [name, message]); - return promiseMap.get(promiseId).promise; +Module["call"] = async function (name, message) { + const promiseId = ccall( + "call", + "integer", + ["string", "string"], + [name, message], + ); + return promiseMap.get(promiseId).promise; }; + +// experimental API +Module["nextTrackedObjectKey"] = function () { + return ccall("next_tracked_object_key", "integer", [], []); +}; +Module["trackedObjectsMap"] = new Map(); +Module["onTrackedObjectDelete"] = (key) => { + Module["trackedObjectsMap"].delete(key); +}; +Module["onGetTrackedObjects"] = (keys) => { + const getTrackedObject = (key) => Module["trackedObjectsMap"].get(key); + return keys.map(getTrackedObject); +}; +Module["onRunTrackedJs"] = (scriptString, isDebug) => { + const trackValue = (value) => { + const key = Module["nextTrackedObjectKey"](); + Module["trackedObjectsMap"].set(key, value); + return key; + }; + + let result; + try { + const indirectEval = eval; + result = indirectEval(scriptString); + } catch (_e) { + return null; + } + isDebug && ensureValidResult(result); + return result?.map(trackValue) ?? []; +}; + +function ensureValidResult(result) { + const isIndex = (k) => typeof k === "number"; + + if (result === null) { + return; + } + if (Array.isArray(result) && keys.every(isIndex)) { + return; + } + + const message = + "Evaluated script returned invalid value. Expected number array or null"; + throw new Error(message); +} From cb6b8f04fe26d614f860e4b68373630b7f435d8e Mon Sep 17 00:00:00 2001 From: Jakub Gonet Date: Sat, 20 Sep 2025 14:00:24 +0300 Subject: [PATCH 3/5] wasm: add get_next_tracked_object_key It's a field in emscripten platform struct. It could be a callback entirely in JS but we use threads. Threads are emulated via webworkers which have their own contexts and variables aren't shared with each other. This means that we would lose uniqueness of the keys. This commit also changes output format from file loaded and ran at