Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ set(TOOLS_DIR "${MAIN_DIR}/tools")

option(SITL "SITL build for host system" OFF)

set(TOOLCHAIN_OPTIONS none arm-none-eabi host)
set(TOOLCHAIN_OPTIONS none arm-none-eabi host wasm)
if (SITL)
if (CMAKE_HOST_APPLE)
set(MACOSX TRUE)
Expand Down
3 changes: 3 additions & 0 deletions cmake/settings.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ function(enable_settings exe name)
if(host STREQUAL TOOLCHAIN)
set(USE_HOST_GCC "-g")
endif()
if("wasm" STREQUAL TOOLCHAIN)
set(args_SETTINGS_CXX "em++")
endif()
set(output ${dir}/${SETTINGS_GENERATED_H} ${dir}/${SETTINGS_GENERATED_C})
add_custom_command(
OUTPUT ${output}
Expand Down
119 changes: 94 additions & 25 deletions cmake/sitl.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,45 @@ main_sources(SITL_COMMON_SRC_EXCLUDES
)

main_sources(SITL_SRC
config/config_streamer_file.c
drivers/serial_tcp.c
drivers/serial_tcp.h
target/SITL/sim/realFlight.c
target/SITL/sim/realFlight.h
target/SITL/sim/simHelper.c
target/SITL/sim/simHelper.h
target/SITL/sim/simple_soap_client.c
target/SITL/sim/simple_soap_client.h
target/SITL/sim/xplane.c
target/SITL/sim/xplane.h
drivers/serial_websocket.c
drivers/serial_websocket.h
)

# Only include TCP server and simulator code for non-WASM builds
if(NOT ${TOOLCHAIN} STREQUAL "wasm")
# File-based config storage for native SITL
main_sources(SITL_SRC
config/config_streamer_file.c
)
main_sources(SITL_SRC
drivers/serial_tcp.c
drivers/serial_tcp.h
target/SITL/sim/realFlight.c
target/SITL/sim/realFlight.h
target/SITL/sim/simHelper.c
target/SITL/sim/simHelper.h
target/SITL/sim/simple_soap_client.c
target/SITL/sim/simple_soap_client.h
target/SITL/sim/xplane.c
target/SITL/sim/xplane.h
)
else()
# WASM-specific: Manual PG registry (linker script not supported)
# RAM-based config storage (no file I/O in browser)
main_sources(SITL_SRC
config/config_streamer_ram.c
target/SITL/wasm_pg_registry.c
target/SITL/wasm_pg_runtime.c
target/SITL/wasm_pg_runtime.h
target/SITL/wasm_stubs.c
target/SITL/wasm_msp_bridge.c
target/SITL/wasm_eeprom_bridge.c
target/SITL/wasm_eeprom_bridge.h
target/SITL/serial_wasm.c
target/SITL/serial_wasm.h
)
endif()


if(CMAKE_HOST_APPLE)
set(MACOSX ON)
Expand All @@ -38,14 +64,14 @@ if(${CYGWIN})
set(SITL_LINK_OPTIONS ${SITL_LINK_OPTIONS} "-static-libgcc")
endif()

set(SITL_LINK_LIBRARIS
set(SITL_LINK_LIBRARIES
-lpthread
-lm
-lc
)

if(NOT MACOSX)
set(SITL_LINK_LIBRARIS ${SITL_LINK_LIBRARIS} -lrt)
set(SITL_LINK_LIBRARIES ${SITL_LINK_LIBRARIES} -lrt)
endif()

set(SITL_COMPILE_OPTIONS
Expand All @@ -64,9 +90,10 @@ if(NOT MACOSX)
-Wno-error=maybe-uninitialized
-fsingle-precision-constant
)
if (CMAKE_COMPILER_IS_GNUCC AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 12.0)
set(SITL_LINK_OPTIONS ${SITL_LINK_OPTIONS} "-Wl,--no-warn-rwx-segments")
endif()
# Temporarily disabled - ld version may not support this flag
# if (CMAKE_COMPILER_IS_GNUCC AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 12.0)
# set(SITL_LINK_OPTIONS ${SITL_LINK_OPTIONS} "-Wl,--no-warn-rwx-segments")
# endif()
else()
set(SITL_COMPILE_OPTIONS ${SITL_COMPILE_OPTIONS}
)
Expand All @@ -76,12 +103,51 @@ set(SITL_DEFINITIONS
SITL_BUILD
)

# WebAssembly-specific settings
if(${TOOLCHAIN} STREQUAL "wasm")
# Disable simulator for WASM builds
list(APPEND SITL_DEFINITIONS SKIP_SIMULATOR=1)
# Use RAM-based config storage (no file I/O in browser)
list(APPEND SITL_DEFINITIONS CONFIG_IN_RAM)

# Emscripten-specific compile options
set(SITL_COMPILE_OPTIONS ${SITL_COMPILE_OPTIONS}
# Phase 5 MVP: Disable pthreads
# -pthread
-funsigned-char
-g # Debug symbols for browser DevTools
)

# Emscripten linker options
set(SITL_LINK_OPTIONS
# Phase 5 MVP: Disable pthreads to avoid COOP/COEP header requirements
# -pthread
# -sUSE_PTHREADS=1
# -sPTHREAD_POOL_SIZE=8
-sALLOW_MEMORY_GROWTH=1
# ASYNCIFY allows WASM to unwind the call stack when exiting from EM_ASM callbacks.
# Without this, emscripten_force_exit() called from within EM_ASM (in systemReset)
# would freeze the JS event loop, preventing the reload IPC message from being processed.
-sASYNCIFY=1
-sWEBSOCKET_URL="ws://localhost:5771"
-sFORCE_FILESYSTEM=1
-sEXPORTED_FUNCTIONS=_main,_serialWriteByte,_serialReadByte,_serialAvailable,_serialGetRxDroppedBytes,_serialGetTxDroppedBytes,_wasmGetEepromPtr,_wasmGetEepromSize,_wasmReloadConfig,_wasmIsEepromValid,_malloc,_free
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Add the missing MSP bridge functions (_wasm_msp_process_command, _wasm_msp_get_api_version, _wasm_msp_get_fc_variant) to the EXPORTED_FUNCTIONS list in cmake/sitl.cmake to ensure they are available to the JavaScript test harness. [possible issue, importance: 9]

Suggested change
-sEXPORTED_FUNCTIONS=_main,_serialWriteByte,_serialReadByte,_serialAvailable,_serialGetRxDroppedBytes,_serialGetTxDroppedBytes,_wasmGetEepromPtr,_wasmGetEepromSize,_wasmReloadConfig,_wasmIsEepromValid,_malloc,_free
-sEXPORTED_FUNCTIONS=_main,_serialWriteByte,_serialReadByte,_serialAvailable,_serialGetRxDroppedBytes,_serialGetTxDroppedBytes,_wasmGetEepromPtr,_wasmGetEepromSize,_wasmReloadConfig,_wasmIsEepromValid,_wasm_msp_process_command,_wasm_msp_get_api_version,_wasm_msp_get_fc_variant,_malloc,_free

-sEXPORTED_RUNTIME_METHODS=ccall,cwrap,UTF8ToString,stringToUTF8,lengthBytesUTF8,getValue,setValue,HEAPU8,callMain
-gsource-map # Generate .wasm.map for browser debugging
-lidbfs.js
)

# Override libraries for WASM (no system libs needed)
set(SITL_LINK_LIBRARIES "")
endif()

function (target_sitl name)
if(CMAKE_VERSION VERSION_GREATER 3.22)
set(CMAKE_C_STANDARD 17)
endif()

if(NOT host STREQUAL TOOLCHAIN)
# Accept both host and wasm toolchains for SITL builds
if(NOT ${TOOLCHAIN} STREQUAL "host" AND NOT ${TOOLCHAIN} STREQUAL "wasm")
return()
endif()

Expand Down Expand Up @@ -123,16 +189,19 @@ function (target_sitl name)

target_compile_options(${exe_target} PRIVATE ${SITL_COMPILE_OPTIONS})

target_link_libraries(${exe_target} PRIVATE ${SITL_LINK_LIBRARIS})
target_link_libraries(${exe_target} PRIVATE ${SITL_LINK_LIBRARIES})
target_link_options(${exe_target} PRIVATE ${SITL_LINK_OPTIONS})

set(script_path ${MAIN_SRC_DIR}/target/link/sitl.ld)
if(NOT EXISTS ${script_path})
message(FATAL_ERROR "linker script ${script_path} doesn't exist")
endif()
set_target_properties(${exe_target} PROPERTIES LINK_DEPENDS ${script_path})
if(NOT MACOSX)
target_link_options(${exe_target} PRIVATE -T${script_path})
# Only use linker script for non-WASM builds
if(NOT ${TOOLCHAIN} STREQUAL "wasm")
set(script_path ${MAIN_SRC_DIR}/target/link/sitl.ld)
if(NOT EXISTS ${script_path})
message(FATAL_ERROR "linker script ${script_path} doesn't exist")
endif()
set_target_properties(${exe_target} PROPERTIES LINK_DEPENDS ${script_path})
if(NOT MACOSX)
target_link_options(${exe_target} PRIVATE -T${script_path})
endif()
endif()

if(${CYGWIN})
Expand Down
4 changes: 4 additions & 0 deletions cmake/wasm-checks.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# WASM toolchain checks
# Emscripten provides its own compiler checks, so this file is minimal

# No additional checks needed for WASM/Emscripten builds
46 changes: 46 additions & 0 deletions cmake/wasm.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Emscripten/WebAssembly toolchain for SITL
# This toolchain allows INAV SITL to compile to WebAssembly for browser-based simulation

# Set build type if not specified
if(NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_CONFIGURATION_TYPES Debug Release RelWithDebInfo)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "")
set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()

# Find Emscripten
if(NOT DEFINED ENV{EMSDK})
if(EXISTS "$ENV{HOME}/emsdk/emsdk_env.sh")
message(STATUS "EMSDK not set, trying to use ~/emsdk")
set(ENV{EMSDK} "$ENV{HOME}/emsdk")
else()
message(FATAL_ERROR "EMSDK environment variable not set. Please run: source ~/emsdk/emsdk_env.sh")
endif()
endif()

# Set Emscripten compilers
set(CMAKE_SYSTEM_NAME Emscripten)
set(CMAKE_SYSTEM_VERSION 1)

set(CMAKE_C_COMPILER "emcc" CACHE INTERNAL "c compiler")
set(CMAKE_CXX_COMPILER "em++" CACHE INTERNAL "c++ compiler")
set(CMAKE_AR "emar" CACHE INTERNAL "ar")
set(CMAKE_RANLIB "emranlib" CACHE INTERNAL "ranlib")

# Build type flags
set(debug_options "-O0 -g")
set(release_options "-O2 -DNDEBUG")
set(relwithdebinfo_options "-g ${release_options}")

set(CMAKE_C_FLAGS_DEBUG ${debug_options} CACHE INTERNAL "c compiler flags debug")
set(CMAKE_CXX_FLAGS_DEBUG ${debug_options} CACHE INTERNAL "c++ compiler flags debug")

set(CMAKE_C_FLAGS_RELEASE ${release_options} CACHE INTERNAL "c compiler flags release")
set(CMAKE_CXX_FLAGS_RELEASE ${release_options} CACHE INTERNAL "cxx compiler flags release")

set(CMAKE_C_FLAGS_RELWITHDEBINFO ${relwithdebinfo_options} CACHE INTERNAL "c compiler flags relwithdebinfo")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO ${relwithdebinfo_options} CACHE INTERNAL "cxx compiler flags relwithdebinfo")

# Mark as configured for Emscripten
set(CMAKE_CROSSCOMPILING_EMULATOR "\${CMAKE_CURRENT_BINARY_DIR}/node_modules/.bin/node" CACHE FILEPATH "Node.js for running WebAssembly")
10 changes: 10 additions & 0 deletions src/main/config/config_streamer_ram.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
#include "platform.h"
#include "drivers/system.h"
#include "config/config_streamer.h"
#include "common/utils.h"

#ifdef __EMSCRIPTEN__
#include "target/SITL/wasm_eeprom_bridge.h"
#endif

#if defined(CONFIG_IN_RAM)

Expand All @@ -32,6 +37,11 @@ void config_streamer_impl_unlock(void)
void config_streamer_impl_lock(void)
{
streamerLocked = true;

#ifdef __EMSCRIPTEN__
// Notify JavaScript that EEPROM was saved so it can persist to IndexedDB
wasmNotifyEepromSaved();
#endif
}

int config_streamer_impl_write_word(config_streamer_t *c, config_streamer_buffer_align_type_t *buffer)
Expand Down
12 changes: 12 additions & 0 deletions src/main/config/parameter_group.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,25 @@ static void pgResetInstance(const pgRegistry_t *reg, uint8_t *base)
const uint16_t regSize = pgSize(reg);

memset(base, 0, regSize);
#ifdef __EMSCRIPTEN__
// WASM: Can't use linker section boundaries (__pg_resetdata_start/end are stubs)
// Heuristic: In WASM, function table indices are small integers (< 4096),
// while data pointers are actual memory addresses (>= 4096 in Emscripten's layout).
// Reset templates are data; reset functions are function pointers.
if (reg->reset.ptr && (uintptr_t)reg->reset.ptr >= 4096) {
// Likely a data template pointer - use it
memcpy(base, reg->reset.ptr, regSize);
}
// Skip function pointer calls - they cause "table index out of bounds" in WASM
#else
if (reg->reset.ptr >= (void*)__pg_resetdata_start && reg->reset.ptr < (void*)__pg_resetdata_end) {
// pointer points to resetdata section, to it is data template
memcpy(base, reg->reset.ptr, regSize);
} else if (reg->reset.fn) {
// reset function, call it
reg->reset.fn(base);
}
#endif
}

void pgReset(const pgRegistry_t* reg, int profileIndex)
Expand Down
11 changes: 10 additions & 1 deletion src/main/config/parameter_group.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,16 @@ static inline uint16_t pgIsProfile(const pgRegistry_t* reg) {return (reg->size &

#define PG_PACKED __attribute__((packed))

#ifdef __APPLE__
#ifdef __EMSCRIPTEN__
// WASM builds use manual registry (linker script sections not supported)
extern const pgRegistry_t* const __pg_registry_start;
extern const pgRegistry_t* const __pg_registry_end;
#define PG_REGISTER_ATTRIBUTES __attribute__ ((used, aligned(4)))

extern const uint8_t* const __pg_resetdata_start;
extern const uint8_t* const __pg_resetdata_end;
#define PG_RESETDATA_ATTRIBUTES __attribute__ ((used, aligned(2)))
#elif defined(__APPLE__)
extern const pgRegistry_t __pg_registry_start[] __asm("section$start$__DATA$__pg_registry");
extern const pgRegistry_t __pg_registry_end[] __asm("section$end$__DATA$__pg_registry");
#define PG_REGISTER_ATTRIBUTES __attribute__ ((section("__DATA,__pg_registry"), used, aligned(8)))
Expand Down
Loading
Loading