Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a41a68b
Add multiple module support
abelstuker Nov 28, 2025
3ba59d3
Add multiple module support for aruino, esp and zephyr
abelstuker Nov 28, 2025
9975bba
Update tests
abelstuker Nov 28, 2025
63f3d6b
Fix resolved not used error
abelstuker Nov 28, 2025
cff02d5
Fix bugs
abelstuker Nov 28, 2025
18301e7
Fix bugs
abelstuker Nov 28, 2025
2357ddc
Fix unit test problems
abelstuker Nov 28, 2025
ea3f9bd
Clang format
abelstuker Nov 28, 2025
df16894
Fix shared memory and table deallocation
abelstuker Nov 28, 2025
dbbd663
Merge branch 'main' into feat/multiple-modules
abelstuker Dec 4, 2025
0779b0f
Fix compiler maybe-uninitialized error
abelstuker Dec 4, 2025
3423f50
Fix incorrect merge conflict resolution
abelstuker Dec 4, 2025
c2c3bca
Cleanup module struct runtime state
abelstuker Dec 4, 2025
9101250
Fix incorrect module use in interpretation after module switching
abelstuker Dec 8, 2025
ca3132a
Fix clang format
abelstuker Dec 8, 2025
19e1bb5
Module switching on imported table indirect call
abelstuker Dec 8, 2025
88c49a0
Clang format
abelstuker Dec 8, 2025
3c66673
Fix restore caller's module context on function return
abelstuker Dec 9, 2025
4505964
Add linked modules flag for CLI
abelstuker Dec 9, 2025
57692a2
Fix may be uninitialized warning
abelstuker Dec 9, 2025
723d50c
Merge remote-tracking branch 'origin' into feat/multiple-modules
abelstuker Apr 6, 2026
90e510c
fix: formatting and duplicate primitive installation after merge
abelstuker Apr 6, 2026
a797699
fix: remove primitive counting
abelstuker Apr 6, 2026
62841aa
fix: platform-specific macros correctly using execution context
abelstuker Apr 6, 2026
7699ddf
fix: leftover code from merge
abelstuker Apr 6, 2026
c644569
fix: more leftover code from merge
abelstuker Apr 6, 2026
1bee63b
tests: add multiple module example tests
abelstuker Apr 6, 2026
fd2f7b1
Add support for importing globals from other modules
abelstuker Apr 7, 2026
57c44c9
Simplify module linking in the CLI
abelstuker Apr 7, 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