Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8f2907e
Add multiple module support
abelstuker Nov 28, 2025
9c715bf
Add multiple module support for aruino, esp and zephyr
abelstuker Nov 28, 2025
527cee8
Update tests
abelstuker Nov 28, 2025
64c74ba
Fix bugs
abelstuker Nov 28, 2025
cfe7656
Fix unit test problems
abelstuker Nov 28, 2025
0d5942f
Clang format
abelstuker Nov 28, 2025
3010c4f
Fix shared memory and table deallocation
abelstuker Nov 28, 2025
6f25da1
Fix compiler maybe-uninitialized error
abelstuker Dec 4, 2025
1cfc083
Fix incorrect module use in interpretation after module switching
abelstuker Dec 8, 2025
77159da
Fix clang format
abelstuker Dec 8, 2025
477a681
Module switching on imported table indirect call
abelstuker Dec 8, 2025
b1392cb
Clang format
abelstuker Dec 8, 2025
7d17e08
Fix restore caller's module context on function return
abelstuker Dec 9, 2025
54bd0d6
Add linked modules flag for CLI
abelstuker Dec 9, 2025
63f8b6c
Fix may be uninitialized warning
abelstuker Dec 9, 2025
6baff06
fix: formatting and duplicate primitive installation after merge
abelstuker Apr 6, 2026
326f843
fix: remove primitive counting
abelstuker Apr 6, 2026
ae48193
fix: platform-specific macros correctly using execution context
abelstuker Apr 6, 2026
57c60b1
tests: add multiple module example tests
abelstuker Apr 6, 2026
00ca6a4
Add support for importing globals from other modules
abelstuker Apr 7, 2026
ecf0ef8
Simplify module linking in the CLI
abelstuker Apr 7, 2026
9c24dce
Clang format
tolauwae May 9, 2026
f12d58f
Fix Zephyr compile error
tolauwae May 10, 2026
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
53 changes: 43 additions & 10 deletions platforms/Arduino/Arduino.ino.template
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@
#include "Arduino.h"
#include "bin/upload.h"

unsigned int wasm_len = upload_wasm_len;
unsigned char* wasm = upload_wasm;
struct ModuleInfo {
unsigned char* wasm;
unsigned int wasm_len;
const char* name;
};

ModuleInfo modules[] = {
{upload_wasm, upload_wasm_len, "main"},
};
const size_t module_count = sizeof(modules) / sizeof(modules[0]);

WARDuino* wac = WARDuino::instance();
std::vector<Module*> loaded_modules;
Module* m;

#define UART_PIN 3
Expand Down Expand Up @@ -52,20 +61,44 @@ void setup(void) {
Serial.println(ESP.getFreePsram());
}

void loop() {
m = wac->load_module(wasm, wasm_len, {});
void loop() {

for (size_t i = 0; i < module_count; i++) {
Serial.print("Loading module: ");
Serial.println(modules[i].name);

Module* mod = wac->load_module(
modules[i].wasm,
modules[i].wasm_len,
modules[i].name
);

if (mod) {
loaded_modules.push_back(mod);
m = mod;
} else {
Serial.print(" ✗ Failed to load ");
Serial.println(modules[i].name);
}
}

printf("LOADED \n\n");
{{PAUSED}}
xTaskCreate(startDebuggerStd, "Debug Thread", 5000, NULL, 1, NULL);

disableCore0WDT();
printf("START\n\n");
Serial.println("START\n");

Serial.println("\nFree heap:");
Serial.println(ESP.getFreeHeap());

wac->run_module(m);
printf("END\n\n");
wac->unload_module(m);
}
if (m) {
wac->run_module(m);
}

Serial.println("END\n");

for (auto mod : loaded_modules) {
wac->unload_module(mod);
}
loaded_modules.clear();
}
187 changes: 132 additions & 55 deletions platforms/CLI-Emulator/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,15 @@ void print_help() {
print_version();
fprintf(stdout, "\n");
fprintf(stdout, "Usage:\n");
fprintf(stdout, " wdcli <file> [options]\n");
fprintf(stdout, " wdcli <main-module> [options]\n");
fprintf(stdout, "\n");
fprintf(stdout, "Arguments:\n");
fprintf(stdout, " <main-module> Main WebAssembly module to execute\n");
fprintf(stdout, "\n");
fprintf(stdout, "Options:\n");
fprintf(stdout,
" --link <file> Link additional module(s) (multiple files can "
"be specified)\n");
fprintf(stdout,
" --loop Let the runtime loop infinitely on exceptions "
"(default: false)\n");
Expand Down Expand Up @@ -71,9 +78,21 @@ void print_help() {
"(default: interpreter)\n");
fprintf(stdout, " --invoke Invoke a function from the module\n");
fprintf(stdout, " --version Get version information\n");
fprintf(stdout, " --help Show this help message\n");
}

Module *load(WARDuino wac, const char *file_name, Options opt) {
std::string getModuleName(const char *path) {
std::string p(path);
size_t lastSlash = p.find_last_of("/\\");
std::string filename =
(lastSlash == std::string::npos) ? p : p.substr(lastSlash + 1);
size_t lastDot = filename.find_last_of(".");
if (lastDot != std::string::npos) return filename.substr(0, lastDot);
return filename;
}

Module *load(WARDuino *wac, const char *file_name, const char *module_name,
Options opt) {
uint8_t *wasm;
unsigned int file_size;

Expand Down Expand Up @@ -104,8 +123,7 @@ Module *load(WARDuino wac, const char *file_name, Options opt) {
}
fclose(file);
file = nullptr;

return wac.load_module(wasm, file_size, opt);
return wac->load_module(wasm, file_size, module_name, opt);

error:
fclose(file);
Expand Down Expand Up @@ -261,45 +279,52 @@ StackValue parseParameter(const char *input, uint8_t value_type) {
int main(int argc, const char *argv[]) {
ARGV_SHIFT(); // Skip command name

// Configuration
bool return_exception = true;
bool no_debug = false;
bool no_socket = false;
const char *socket = "8192";
bool initiallyPaused = false;
const char *file_name = nullptr;
const char *main_module = nullptr;
std::vector<const char *> linked_modules;
const char *proxy = nullptr;
const char *baudrate = nullptr;
const char *mode = "interpreter";
bool dump_info = false;

const char *fname = nullptr;
std::vector<StackValue> arguments = std::vector<StackValue>();
const char *invoke_fname = nullptr;
std::vector<const char *> invoke_raw_args;

// Parse main module (first positional argument)
if (argc > 0 && argv[0][0] != '-') {
ARGV_GET(file_name);

dbg_info("=== LOAD MODULE INTO WARDUINO ===\n");
m = load(*wac, file_name,
{.disable_memory_bounds = false,
.mangle_table_index = false,
.dlsym_trim_underscore = false,
.return_exception = return_exception});
main_module = argv[0];
ARGV_SHIFT();
}

// Parse options
while (argc > 0) {
const char *arg = argv[0];
if (arg[0] != '-') {
break;
}

ARGV_SHIFT();
if (!strcmp("--version", arg)) {
print_version();
return 0;
} else if (!strcmp("--help", arg)) {
} else if (!strcmp("--help", arg) || !strcmp("-h", arg)) {
print_help();
return 0;
} else if (!strcmp("--link", arg)) {
bool found = false;
while (argc > 0 && argv[0][0] != '-') {
const char *link_file;
ARGV_GET(link_file);
linked_modules.push_back(link_file);
found = true;
}
if (!found) {
fprintf(stderr,
"wdcli: --link requires at least one file argument\n");
return 1;
}
} else if (!strcmp("--loop", arg)) {
return_exception = false;
} else if (!strcmp("--no-debug", arg)) {
Expand All @@ -311,47 +336,98 @@ int main(int argc, const char *argv[]) {
} else if (!strcmp("--paused", arg)) {
initiallyPaused = true;
} else if (!strcmp("--proxy", arg)) {
ARGV_GET(proxy); // /dev/ttyUSB0
ARGV_GET(proxy);
} else if (!strcmp("--baudrate", arg)) {
ARGV_GET(baudrate);
} else if (!strcmp("--mode", arg)) {
ARGV_GET(mode);
} else if (!strcmp("--invoke", arg)) {
ARGV_GET(fname);

// find function
int fidx = wac->get_export_fidx(m, fname);
if (fidx < 0) {
fprintf(stderr, "wdcli: no exported function with name '%s'\n",
fname);
return 1;
}

Block function = m->functions[fidx];

// consume all arguments for the function
for (uint32_t i = 0; i < function.type->param_count; ++i) {
const char *number = nullptr;
ARGV_GET(number);

if (number[0] == '-') {
FATAL("wdcli: wrong number of arguments for '%s'\n", fname);
}

arguments.push_back(
parseParameter(number, function.type->params[i]));
ARGV_GET(invoke_fname);
// Collect remaining args as potential parameters until next flag
while (argc > 0 && argv[0][0] != '-') {
const char *val;
ARGV_GET(val);
invoke_raw_args.push_back(val);
}
} else if (!strcmp("--dump-info", arg)) {
dump_info = true;
} else {
fprintf(stderr, "wdcli: unknown option '%s'\n", arg);
fprintf(stderr, "Try 'wdcli --help' for more information.\n");
return 1;
}
}

if (main_module == nullptr) {
fprintf(stderr, "wdcli: no main module specified\n");
fprintf(stderr, "Usage: wdcli <main-module> [options]\n");
fprintf(stderr, "Try 'wdcli --help' for more information.\n");
return 1;
}

dbg_info("=== LOAD MODULES INTO WARDUINO ===\n");
std::vector<Module *> loaded_modules;

for (const char *file_path : linked_modules) {
std::string modName = getModuleName(file_path);
Module *new_mod = load(wac, file_path, modName.c_str(),
{.disable_memory_bounds = false,
.mangle_table_index = false,
.dlsym_trim_underscore = false,
.return_exception = return_exception});

if (new_mod) {
new_mod->warduino = wac;
loaded_modules.push_back(new_mod);
dbg_info(" Loaded linked module: %s\n", modName.c_str());
} else {
fprintf(stderr, "wdcli: failed to load linked module '%s'\n",
file_path);
return 1;
}
}

if (argc != 0 || file_name == nullptr) {
print_help();
// Load main module last
std::string mainModName = getModuleName(main_module);
m = load(wac, main_module, mainModName.c_str(),
{.disable_memory_bounds = false,
.mangle_table_index = false,
.dlsym_trim_underscore = false,
.return_exception = return_exception});

if (m) {
m->warduino = wac;
loaded_modules.push_back(m);
dbg_info(" Loaded main module: %s\n", mainModName.c_str());
} else {
fprintf(stderr, "wdcli: failed to load main module '%s'\n",
main_module);
return 1;
}

m->warduino = wac;
std::vector<StackValue> parsed_args;
if (invoke_fname != nullptr) {
int fidx = wac->get_export_fidx(m, invoke_fname);
if (fidx < 0) {
fprintf(stderr, "wdcli: no exported function with name '%s'\n",
invoke_fname);
return 1;
}
Block function = m->functions[fidx];

if (invoke_raw_args.size() != function.type->param_count) {
FATAL(
"wdcli: wrong number of arguments for '%s' (expected %d, got "
"%zu)\n",
invoke_fname, function.type->param_count,
invoke_raw_args.size());
}

for (uint32_t i = 0; i < function.type->param_count; ++i) {
parsed_args.push_back(
parseParameter(invoke_raw_args[i], function.type->params[i]));
}
}

if (initiallyPaused) {
wac->debugger->pauseRuntime(m);
Expand Down Expand Up @@ -388,12 +464,11 @@ int main(int argc, const char *argv[]) {
json["primitive_fidx_mapping"] = fidx_mapping;

std::cout << json << std::endl;
wac->unload_module(m);
for (auto mod : loaded_modules) wac->unload_module(mod);
exit(0);
}

if (strcmp(mode, "proxy") == 0) {
// Run in proxy mode
wac->debugger->proxify();
} else if (proxy) {
// Connect to proxy device
Expand Down Expand Up @@ -443,19 +518,21 @@ int main(int argc, const char *argv[]) {
options.no_socket = no_socket;
options.socket = std::stoi(socket);
setupDebuggerCommunication(options);

communication = std::thread(startDebuggerCommunication);
}

// Run Wasm module
dbg_info("\n=== STARTED INTERPRETATION (main thread) ===\n");
if (fname != nullptr) {
uint32_t fidx = wac->get_export_fidx(m, fname);
wac->invoke(m, fidx, arguments.size(), &arguments[0]);
if (invoke_fname != nullptr) {
uint32_t fidx = wac->get_export_fidx(m, invoke_fname);
wac->invoke(m, fidx, parsed_args.size(), parsed_args.data());
} else {
wac->run_module(m);
}
wac->unload_module(m);

// Unload all
for (auto mod : loaded_modules) {
wac->unload_module(mod);
}
wac->debugger->stop();

if (!no_debug) {
Expand All @@ -464,4 +541,4 @@ int main(int argc, const char *argv[]) {
}

return 0;
}
}
Loading
Loading