From 8df566f7f18b64efeab35ac35e18474eddf92a53 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Tue, 11 Nov 2025 13:26:17 +0100 Subject: [PATCH 1/8] feat: shared mutable globals --- src/Debug/debugger.cpp | 16 +++---- src/Interpreter/instructions.cpp | 5 +- src/Primitives/arduino.cpp | 39 +++++++++++++++- src/Primitives/emulated.cpp | 39 +++++++++++++++- src/Primitives/idf.cpp | 40 +++++++++++++++- src/Primitives/primitives.h | 2 + src/Primitives/zephyr.cpp | 39 +++++++++++++++- src/WARDuino.h | 2 + src/WARDuino/CallbackHandler.cpp | 5 +- src/WARDuino/WARDuino.cpp | 79 +++++++++++++++++--------------- src/WARDuino/internals.h | 10 +++- 11 files changed, 222 insertions(+), 54 deletions(-) diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index bafa69570..d0f5b0913 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -844,7 +844,7 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray, this->channel->write("%s\"globals\":[", addComma ? "," : ""); addComma = true; for (uint32_t j = 0; j < m->global_count; j++) { - auto v = m->globals + j; + auto v = (*(m->globals + j))->value; printValue(v, j, j == (m->global_count - 1)); } this->channel->write("]"); // closing globals @@ -1063,15 +1063,15 @@ void Debugger::freeState(Module *m, uint8_t *interruptData) { debug("globals freeing state and then allocating\n"); if (m->global_count > 0) free(m->globals); if (amount > 0) - m->globals = static_cast( - acalloc(amount, sizeof(StackValue), "globals")); + m->globals = static_cast( + acalloc(amount, sizeof(Global *), "globals")); } else { debug("globals setting existing state to zero\n"); for (uint32_t i = 0; i < m->global_count; i++) { debug("decreasing global_count\n"); - StackValue *sv = &m->globals[i]; - sv->value_type = 0; - sv->value.uint32 = 0; + Global *glob = m->globals[i]; + glob->value->value_type = 0; + glob->value->value.uint32 = 0; } } m->global_count = 0; @@ -1221,7 +1221,7 @@ bool Debugger::saveState(Module *m, uint8_t *interruptData) { if (type_index >= sizeof(valtypes)) { FATAL("received unknown type %" PRIu8 "\n", type_index); } - StackValue *sv = &m->globals[m->global_count++]; + StackValue *sv = m->globals[m->global_count++]->value; size_t qb = type_index == 0 || type_index == 2 ? 4 : 8; debug("receiving type %" PRIu8 " and %d bytes \n", type_index, @@ -1539,7 +1539,7 @@ bool Debugger::handleUpdateGlobalValue(const Module *m, uint8_t *data) const { if (index >= m->global_count) return false; this->channel->write("Global %u being changed\n", index); - StackValue *v = &m->globals[index]; + StackValue *v = m->globals[index]->value; constexpr bool decodeType = false; deserialiseStackValue(data, decodeType, v); this->channel->write("Global %u changed to %u\n", index, v->value.uint32); diff --git a/src/Interpreter/instructions.cpp b/src/Interpreter/instructions.cpp index fd666702e..c24967cfc 100644 --- a/src/Interpreter/instructions.cpp +++ b/src/Interpreter/instructions.cpp @@ -459,9 +459,9 @@ bool i_instr_tee_local(Module *m) { bool i_instr_get_global(Module *m) { int32_t arg = read_LEB_32(&m->pc_ptr); #if TRACE - debug(" - arg: 0x%x, got %s\n", arg, value_repr(&m->globals[arg])); + debug(" - arg: 0x%x, got %s\n", arg, value_repr(m->globals[arg])); #endif - m->stack[++m->sp] = m->globals[arg]; + m->stack[++m->sp] = *m->globals[arg]->value; return true; } @@ -471,6 +471,7 @@ bool i_instr_get_global(Module *m) { bool i_instr_set_global(Module *m) { uint32_t arg = read_LEB_32(&m->pc_ptr); m->globals[arg] = m->stack[m->sp--]; + *m->globals[arg]->value = m->stack[m->sp--]; #if TRACE debug(" - arg: 0x%x, got %s\n", arg, value_repr(&m->stack[m->sp + 1])); #endif diff --git a/src/Primitives/arduino.cpp b/src/Primitives/arduino.cpp index f66ed4d86..8e41775e0 100644 --- a/src/Primitives/arduino.cpp +++ b/src/Primitives/arduino.cpp @@ -136,8 +136,12 @@ int resolve_isr(int pin) { #define ALL_PRIMITIVES (NUM_PRIMITIVES + NUM_PRIMITIVES_ARDUINO) -// Global index for installing primitives +#define NUM_GLOBALS 1 +#define ALL_GLOBALS NUM_GLOBALS + +// Indices for installing primitives and globals int prim_index = 0; +int global_index = 0; /* Private macros to install a primitive @@ -196,8 +200,28 @@ int prim_index = 0; #define arg8 get_arg(m, 8) #define arg9 get_arg(m, 9) +#define def_glob(name, type, mut, init_value) \ + StackValue name##_sv { .value_type = type, init_value };\ + Global name = { \ + .mutability = mut, \ + .import_field = #name, \ + .value = &name##_sv \ + }; \ + +#define install_global(global_name) \ + { \ + dbg_info("installing global: %s\n", #global_name); \ + if (global_index < ALL_GLOBALS) { \ + globals[global_index++] = (global_name); \ + } else { \ + FATAL("global_index out of bounds"); \ + } \ + } \ + // The primitive table PrimitiveEntry primitives[ALL_PRIMITIVES]; +// The globals table +Global globals[ALL_GLOBALS]; // uint32_t param_arr_len0[0] = {}; @@ -1076,6 +1100,19 @@ bool resolve_external_memory(char *symbol, Memory **val) { return false; } +bool resolve_external_global(char *symbol, Global **val) { + debug("Resolve external global for %s \n", symbol); + + for (auto &global : globals) { + if (!strcmp(symbol, global.import_field)) { + *val = &global; + return true; + } + } + FATAL("Could not find global %s \n", symbol); + // return false; // unreachable +} + //------------------------------------------------------ // Restore external state when restoring a snapshot //------------------------------------------------------ diff --git a/src/Primitives/emulated.cpp b/src/Primitives/emulated.cpp index cfcd171b9..b16363f7a 100644 --- a/src/Primitives/emulated.cpp +++ b/src/Primitives/emulated.cpp @@ -32,8 +32,12 @@ #define ALL_PRIMITIVES (NUM_PRIMITIVES + NUM_PRIMITIVES_ARDUINO) -// Global index for installing primitives +#define NUM_GLOBALS 0 +#define ALL_GLOBALS NUM_GLOBALS + +// Indices for installing primitives and globals int prim_index = 0; +int global_index = 0; double sensor_emu = 0; @@ -94,8 +98,28 @@ double sensor_emu = 0; #define arg8 get_arg(m, 8) #define arg9 get_arg(m, 9) +#define def_glob(name, type, mut, init_value) \ + StackValue name##_sv { .value_type = type, init_value };\ + Global name = { \ + .mutability = mut, \ + .import_field = #name, \ + .value = &name##_sv \ + }; \ + +#define install_global(global_name) \ + { \ + dbg_info("installing global: %s\n", #global_name); \ + if (global_index < ALL_GLOBALS) { \ + globals[global_index++] = (global_name); \ + } else { \ + FATAL("global_index out of bounds"); \ + } \ + } \ + // The primitive table PrimitiveEntry primitives[ALL_PRIMITIVES]; +// The globals table +Global globals[ALL_GLOBALS]; // uint32_t param_arr_len0[0] = {}; @@ -728,6 +752,19 @@ bool resolve_external_memory(char *symbol, Memory **val) { // return false; // unreachable } +bool resolve_external_global(char *symbol, Global **val) { + debug("Resolve external global for %s \n", symbol); + + for (auto &global : globals) { + if (!strcmp(symbol, global.import_field)) { + *val = &global; + return true; + } + } + FATAL("Could not find global %s \n", symbol); + // return false; // unreachable +} + //------------------------------------------------------ // Restore external state when restoring a snapshot //------------------------------------------------------ diff --git a/src/Primitives/idf.cpp b/src/Primitives/idf.cpp index bcba2739a..72b6afd0d 100644 --- a/src/Primitives/idf.cpp +++ b/src/Primitives/idf.cpp @@ -32,8 +32,13 @@ #define ALL_PRIMITIVES (NUM_PRIMITIVES + NUM_PRIMITIVES_ARDUINO) -// Global index for installing primitives +#define NUM_GLOBALS 0 +#define ALL_GLOBALS NUM_GLOBALS + +// Indices for installing primitives and globals int prim_index = 0; +int global_index = 0; + double sensor_emu = 0; @@ -94,8 +99,28 @@ double sensor_emu = 0; #define arg8 get_arg(m, 8) #define arg9 get_arg(m, 9) +#define def_glob(name, type, mut, init_value) \ + StackValue name##_sv { .value_type = type, init_value };\ + Global name = { \ + .mutability = mut, \ + .import_field = #name, \ + .value = &name##_sv \ + }; \ + +#define install_global(global_name) \ + { \ + dbg_info("installing global: %s\n", #global_name); \ + if (global_index < ALL_GLOBALS) { \ + globals[global_index++] = (global_name); \ + } else { \ + FATAL("global_index out of bounds"); \ + } \ + } \ + // The primitive table PrimitiveEntry primitives[ALL_PRIMITIVES]; +// The globals table +Global globals[ALL_GLOBALS]; // uint32_t param_arr_len0[0] = {}; @@ -295,6 +320,19 @@ bool resolve_external_memory(char *symbol, Memory **val) { return false; } +bool resolve_external_global(char *symbol, Global **val) { + debug("Resolve external global for %s \n", symbol); + + for (auto &global : globals) { + if (!strcmp(symbol, global.import_field)) { + *val = &global; + return true; + } + } + FATAL("Could not find global %s \n", symbol); + // return false; // unreachable +} + //------------------------------------------------------ // Restore external state when restoring a snapshot //------------------------------------------------------ diff --git a/src/Primitives/primitives.h b/src/Primitives/primitives.h index 0a9509e90..11138798a 100644 --- a/src/Primitives/primitives.h +++ b/src/Primitives/primitives.h @@ -25,6 +25,8 @@ bool resolve_primitive(char *symbol, Primitive *val); */ bool resolve_external_memory(char *symbol, Memory **val); +bool resolve_external_global(char *symbol, Global **val); + void install_primitives(); std::vector get_io_state(Module *m); diff --git a/src/Primitives/zephyr.cpp b/src/Primitives/zephyr.cpp index 7e80619e0..27e1366d0 100644 --- a/src/Primitives/zephyr.cpp +++ b/src/Primitives/zephyr.cpp @@ -39,8 +39,12 @@ #endif #define ALL_PRIMITIVES OBB_PRIMITIVES + 7 -// Global index for installing primitives +#define NUM_GLOBALS 0 +#define ALL_GLOBALS NUM_GLOBALS + +// Indices for installing primitives and globals int prim_index = 0; +int global_index = 0; double sensor_emu = 0; @@ -101,8 +105,28 @@ double sensor_emu = 0; #define arg8 get_arg(m, 8) #define arg9 get_arg(m, 9) +#define def_glob(name, type, mut, init_value) \ + StackValue name##_sv { .value_type = type, init_value };\ + Global name = { \ + .mutability = mut, \ + .import_field = #name, \ + .value = &name##_sv \ + }; \ + +#define install_global(global_name) \ + { \ + dbg_info("installing global: %s\n", #global_name); \ + if (global_index < ALL_GLOBALS) { \ + globals[global_index++] = (global_name); \ + } else { \ + FATAL("global_index out of bounds"); \ + } \ + } \ + // The primitive table PrimitiveEntry primitives[ALL_PRIMITIVES]; +// The globals table +Global globals[ALL_GLOBALS]; // uint32_t param_arr_len0[0] = {}; @@ -627,6 +651,19 @@ bool resolve_external_memory(char *symbol, Memory **val) { return false; } +bool resolve_external_global(char *symbol, Global **val) { + debug("Resolve external global for %s \n", symbol); + + for (auto &global : globals) { + if (!strcmp(symbol, global.import_field)) { + *val = &global; + return true; + } + } + FATAL("Could not find global %s \n", symbol); + // return false; // unreachable +} + //------------------------------------------------------ // Restore external state when restoring a snapshot //------------------------------------------------------ diff --git a/src/WARDuino.h b/src/WARDuino.h index 2069eda63..57e18a25d 100644 --- a/src/WARDuino.h +++ b/src/WARDuino.h @@ -112,6 +112,8 @@ class WARDuino { uint32_t get_export_fidx(Module *m, const char *name); + uint32_t get_export_global_idx(Module *m, const char *name); + void handleInterrupt(size_t len, uint8_t *buff) const; void instantiate_module(Module *m, uint8_t *bytes, uint32_t byte_count); diff --git a/src/WARDuino/CallbackHandler.cpp b/src/WARDuino/CallbackHandler.cpp index 28e0bc3b2..506484de5 100644 --- a/src/WARDuino/CallbackHandler.cpp +++ b/src/WARDuino/CallbackHandler.cpp @@ -193,8 +193,9 @@ Callback::Callback(Module *m, std::string id, uint32_t tidx) { } void Callback::resolve_event(const Event &e) { - dbg_trace("Callback(%s, %i): resolving Event(%s, \"%s\")\n", topic.c_str(), - table_index, e.topic.c_str(), e.payload); + dbg_trace("Callback(%s, %u): resolving Event(%s, \"%s\")\n", + topic.c_str(), static_cast(table_index), + e.topic.c_str(), e.payload.c_str()); // Copy topic and payload to linear memory uint32_t start = 10000; // TODO use reserved area in linear memory diff --git a/src/WARDuino/WARDuino.cpp b/src/WARDuino/WARDuino.cpp index ae41c1fee..5cffc2942 100644 --- a/src/WARDuino/WARDuino.cpp +++ b/src/WARDuino/WARDuino.cpp @@ -36,8 +36,7 @@ bool resolvesym(char *filename, char *symbol, uint8_t external_kind, void **val, } case 0x03: // Global { - *err = (char *)"Unsupported type of import: global (0x03)"; - return false; + return resolve_external_global(symbol, (Global **)val); } default: *err = (char *)"Unsupported type of import"; @@ -295,6 +294,20 @@ uint32_t WARDuino::get_export_fidx(Module *m, const char *name) { return static_cast(-1); } +uint32_t WARDuino::get_export_global_idx(Module *m, const char *name) { + // Find name global index + for (uint32_t g = 0; g < m->global_count; g++) { + char *gname = m->globals[g]->export_name; + if (!gname) { + continue; + } + if (strncmp(name, gname, 1024) == 0) { + return g; + } + } + return static_cast(-1); +} + void WARDuino::instantiate_module(Module *m, uint8_t *bytes, uint32_t byte_count) { uint32_t word; @@ -539,33 +552,22 @@ void WARDuino::instantiate_module(Module *m, uint8_t *bytes, } case 0x03: // Global { + auto *gval = (Global *)val; + ASSERT(gval->mutability == mutability, + "Imported global mutability mismatch:\n"); + ASSERT(gval->value->value_type == content_type, + "Imported global type mismatch\n"); m->global_count += 1; - m->globals = (StackValue *)arecalloc( + m->globals = (Global **)arecalloc( m->globals, m->global_count - 1, - m->global_count, sizeof(StackValue), "globals"); - StackValue *glob = &m->globals[m->global_count - 1]; - glob->value_type = content_type; - - switch ( - content_type) { // NOLINT(hicpp-multiway-paths-covered) - case I32: - memcpy(&glob->value.uint32, val, 4); - break; - case I64: - memcpy(&glob->value.uint64, val, 8); - break; - case F32: - memcpy(&glob->value.f32, val, 4); - break; - case F64: - memcpy(&glob->value.f64, val, 8); - break; - } + m->global_count, sizeof(Global *), "globals"); + Global **glob = &m->globals[m->global_count - 1]; + *glob = gval; debug( " setting global %d (content_type %d) to " "%p: %s\n", m->global_count - 1, content_type, val, - value_repr(glob)); + value_repr(*glob)); break; } default: @@ -634,20 +636,21 @@ void WARDuino::instantiate_module(Module *m, uint8_t *bytes, for (uint32_t g = 0; g < global_count; g++) { // Same allocation Import of global above uint8_t type = read_LEB(&pos, 7); - // TODO: use mutability uint8_t mutability = read_LEB(&pos, 1); (void)mutability; uint32_t gidx = m->global_count; m->global_count += 1; - m->globals = (StackValue *)arecalloc( - m->globals, gidx, m->global_count, sizeof(StackValue), + m->globals = (Global **)arecalloc( + m->globals, gidx, m->global_count, sizeof(Global *), "globals"); - m->globals[gidx].value_type = type; + m->globals[gidx] = (Global *)acalloc(1, sizeof(Global), "globals"); + m->globals[gidx]->value = (StackValue *)acalloc(1, sizeof(StackValue), "globals"); // Run the init_expr to get global value run_init_expr(m, type, &pos); - m->globals[gidx] = m->stack[m->sp--]; + *(m->globals[gidx]->value) = m->stack[m->sp--]; + m->globals[gidx]->mutability = mutability; } pos = start_pos + section_len; break; @@ -658,18 +661,20 @@ void WARDuino::instantiate_module(Module *m, uint8_t *bytes, uint32_t export_count = read_LEB_32(&pos); for (uint32_t e = 0; e < export_count; e++) { char *name = read_string(&pos, nullptr); - uint32_t kind = *(pos++); // read and move pos uint32_t index = read_LEB_32(&pos); - if (kind != 0x00) { - dbg_warn( - " ignoring non-function export '%s'" - " kind 0x%x index 0x%x\n", - name, kind, index); - continue; + switch (kind) { + case 0x00: + m->functions[index].export_name = name; + break; + case 0x03: + m->globals[index]->export_name = name; + break; + default: + dbg_warn(" ignoring non-function export '%s'" + " kind 0x%x index 0x%x\n", + name, kind, index); } - m->functions[index].export_name = name; - debug(" export: %s (0x%x)\n", name, index); } break; } diff --git a/src/WARDuino/internals.h b/src/WARDuino/internals.h index d0f6b2638..d2f9a81a2 100644 --- a/src/WARDuino/internals.h +++ b/src/WARDuino/internals.h @@ -82,6 +82,14 @@ typedef struct Memory { uint8_t *bytes = nullptr; // memory area } Memory; +typedef struct Global { + char *export_name; // export name of the global + char *import_module; // import module name + char *import_field; // import field name + bool mutability; // 0: immutable, 1: mutable + StackValue *value; // current value +} Global; + typedef struct Options { // when true: host memory addresses will be outside allocated memory area // so do not do bounds checking @@ -118,7 +126,7 @@ typedef struct Module { Table table; Memory memory; uint32_t global_count = 0; // number of globals - StackValue *globals = nullptr; // globals + Global **globals = nullptr; // globals // Runtime state uint8_t *pc_ptr = nullptr; // program counter int sp = -1; // operand stack pointer From 7877ba960990765e03cb6f5688837e523b8981a2 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Tue, 11 Nov 2025 14:02:04 +0100 Subject: [PATCH 2/8] chore: clang format --- src/Primitives/arduino.cpp | 31 ++++++++++++++----------------- src/Primitives/emulated.cpp | 31 ++++++++++++++----------------- src/Primitives/idf.cpp | 32 ++++++++++++++------------------ src/Primitives/zephyr.cpp | 31 ++++++++++++++----------------- src/WARDuino/CallbackHandler.cpp | 6 +++--- src/WARDuino/WARDuino.cpp | 19 +++++++++++-------- src/WARDuino/internals.h | 14 +++++++------- 7 files changed, 77 insertions(+), 87 deletions(-) diff --git a/src/Primitives/arduino.cpp b/src/Primitives/arduino.cpp index 8e41775e0..1a52d3dde 100644 --- a/src/Primitives/arduino.cpp +++ b/src/Primitives/arduino.cpp @@ -200,23 +200,20 @@ int global_index = 0; #define arg8 get_arg(m, 8) #define arg9 get_arg(m, 9) -#define def_glob(name, type, mut, init_value) \ - StackValue name##_sv { .value_type = type, init_value };\ - Global name = { \ - .mutability = mut, \ - .import_field = #name, \ - .value = &name##_sv \ - }; \ - -#define install_global(global_name) \ - { \ - dbg_info("installing global: %s\n", #global_name); \ - if (global_index < ALL_GLOBALS) { \ - globals[global_index++] = (global_name); \ - } else { \ - FATAL("global_index out of bounds"); \ - } \ - } \ +#define def_glob(name, type, mut, init_value) \ + StackValue name##_sv{.value_type = type, init_value}; \ + Global name = { \ + .mutability = mut, .import_field = #name, .value = &name##_sv}; + +#define install_global(global_name) \ + { \ + dbg_info("installing global: %s\n", #global_name); \ + if (global_index < ALL_GLOBALS) { \ + globals[global_index++] = (global_name); \ + } else { \ + FATAL("global_index out of bounds"); \ + } \ + } // The primitive table PrimitiveEntry primitives[ALL_PRIMITIVES]; diff --git a/src/Primitives/emulated.cpp b/src/Primitives/emulated.cpp index b16363f7a..fac101e15 100644 --- a/src/Primitives/emulated.cpp +++ b/src/Primitives/emulated.cpp @@ -98,23 +98,20 @@ double sensor_emu = 0; #define arg8 get_arg(m, 8) #define arg9 get_arg(m, 9) -#define def_glob(name, type, mut, init_value) \ - StackValue name##_sv { .value_type = type, init_value };\ - Global name = { \ - .mutability = mut, \ - .import_field = #name, \ - .value = &name##_sv \ - }; \ - -#define install_global(global_name) \ - { \ - dbg_info("installing global: %s\n", #global_name); \ - if (global_index < ALL_GLOBALS) { \ - globals[global_index++] = (global_name); \ - } else { \ - FATAL("global_index out of bounds"); \ - } \ - } \ +#define def_glob(name, type, mut, init_value) \ + StackValue name##_sv{.value_type = type, init_value}; \ + Global name = { \ + .mutability = mut, .import_field = #name, .value = &name##_sv}; + +#define install_global(global_name) \ + { \ + dbg_info("installing global: %s\n", #global_name); \ + if (global_index < ALL_GLOBALS) { \ + globals[global_index++] = (global_name); \ + } else { \ + FATAL("global_index out of bounds"); \ + } \ + } // The primitive table PrimitiveEntry primitives[ALL_PRIMITIVES]; diff --git a/src/Primitives/idf.cpp b/src/Primitives/idf.cpp index 72b6afd0d..77d24c6d1 100644 --- a/src/Primitives/idf.cpp +++ b/src/Primitives/idf.cpp @@ -39,7 +39,6 @@ int prim_index = 0; int global_index = 0; - double sensor_emu = 0; /* @@ -99,23 +98,20 @@ double sensor_emu = 0; #define arg8 get_arg(m, 8) #define arg9 get_arg(m, 9) -#define def_glob(name, type, mut, init_value) \ - StackValue name##_sv { .value_type = type, init_value };\ - Global name = { \ - .mutability = mut, \ - .import_field = #name, \ - .value = &name##_sv \ - }; \ - -#define install_global(global_name) \ - { \ - dbg_info("installing global: %s\n", #global_name); \ - if (global_index < ALL_GLOBALS) { \ - globals[global_index++] = (global_name); \ - } else { \ - FATAL("global_index out of bounds"); \ - } \ - } \ +#define def_glob(name, type, mut, init_value) \ + StackValue name##_sv{.value_type = type, init_value}; \ + Global name = { \ + .mutability = mut, .import_field = #name, .value = &name##_sv}; + +#define install_global(global_name) \ + { \ + dbg_info("installing global: %s\n", #global_name); \ + if (global_index < ALL_GLOBALS) { \ + globals[global_index++] = (global_name); \ + } else { \ + FATAL("global_index out of bounds"); \ + } \ + } // The primitive table PrimitiveEntry primitives[ALL_PRIMITIVES]; diff --git a/src/Primitives/zephyr.cpp b/src/Primitives/zephyr.cpp index 27e1366d0..daf5ecdac 100644 --- a/src/Primitives/zephyr.cpp +++ b/src/Primitives/zephyr.cpp @@ -105,23 +105,20 @@ double sensor_emu = 0; #define arg8 get_arg(m, 8) #define arg9 get_arg(m, 9) -#define def_glob(name, type, mut, init_value) \ - StackValue name##_sv { .value_type = type, init_value };\ - Global name = { \ - .mutability = mut, \ - .import_field = #name, \ - .value = &name##_sv \ - }; \ - -#define install_global(global_name) \ - { \ - dbg_info("installing global: %s\n", #global_name); \ - if (global_index < ALL_GLOBALS) { \ - globals[global_index++] = (global_name); \ - } else { \ - FATAL("global_index out of bounds"); \ - } \ - } \ +#define def_glob(name, type, mut, init_value) \ + StackValue name##_sv{.value_type = type, init_value}; \ + Global name = { \ + .mutability = mut, .import_field = #name, .value = &name##_sv}; + +#define install_global(global_name) \ + { \ + dbg_info("installing global: %s\n", #global_name); \ + if (global_index < ALL_GLOBALS) { \ + globals[global_index++] = (global_name); \ + } else { \ + FATAL("global_index out of bounds"); \ + } \ + } // The primitive table PrimitiveEntry primitives[ALL_PRIMITIVES]; diff --git a/src/WARDuino/CallbackHandler.cpp b/src/WARDuino/CallbackHandler.cpp index 506484de5..934008e66 100644 --- a/src/WARDuino/CallbackHandler.cpp +++ b/src/WARDuino/CallbackHandler.cpp @@ -193,9 +193,9 @@ Callback::Callback(Module *m, std::string id, uint32_t tidx) { } void Callback::resolve_event(const Event &e) { - dbg_trace("Callback(%s, %u): resolving Event(%s, \"%s\")\n", - topic.c_str(), static_cast(table_index), - e.topic.c_str(), e.payload.c_str()); + dbg_trace("Callback(%s, %u): resolving Event(%s, \"%s\")\n", topic.c_str(), + static_cast(table_index), e.topic.c_str(), + e.payload.c_str()); // Copy topic and payload to linear memory uint32_t start = 10000; // TODO use reserved area in linear memory diff --git a/src/WARDuino/WARDuino.cpp b/src/WARDuino/WARDuino.cpp index 5cffc2942..5d4ddc391 100644 --- a/src/WARDuino/WARDuino.cpp +++ b/src/WARDuino/WARDuino.cpp @@ -640,11 +640,13 @@ void WARDuino::instantiate_module(Module *m, uint8_t *bytes, (void)mutability; uint32_t gidx = m->global_count; m->global_count += 1; - m->globals = (Global **)arecalloc( - m->globals, gidx, m->global_count, sizeof(Global *), - "globals"); - m->globals[gidx] = (Global *)acalloc(1, sizeof(Global), "globals"); - m->globals[gidx]->value = (StackValue *)acalloc(1, sizeof(StackValue), "globals"); + m->globals = + (Global **)arecalloc(m->globals, gidx, m->global_count, + sizeof(Global *), "globals"); + m->globals[gidx] = + (Global *)acalloc(1, sizeof(Global), "globals"); + m->globals[gidx]->value = + (StackValue *)acalloc(1, sizeof(StackValue), "globals"); // Run the init_expr to get global value run_init_expr(m, type, &pos); @@ -671,9 +673,10 @@ void WARDuino::instantiate_module(Module *m, uint8_t *bytes, m->globals[index]->export_name = name; break; default: - dbg_warn(" ignoring non-function export '%s'" - " kind 0x%x index 0x%x\n", - name, kind, index); + dbg_warn( + " ignoring non-function export '%s'" + " kind 0x%x index 0x%x\n", + name, kind, index); } } break; diff --git a/src/WARDuino/internals.h b/src/WARDuino/internals.h index d2f9a81a2..b099df572 100644 --- a/src/WARDuino/internals.h +++ b/src/WARDuino/internals.h @@ -83,11 +83,11 @@ typedef struct Memory { } Memory; typedef struct Global { - char *export_name; // export name of the global - char *import_module; // import module name - char *import_field; // import field name - bool mutability; // 0: immutable, 1: mutable - StackValue *value; // current value + char *export_name; // export name of the global + char *import_module; // import module name + char *import_field; // import field name + bool mutability; // 0: immutable, 1: mutable + StackValue *value; // current value } Global; typedef struct Options { @@ -125,8 +125,8 @@ typedef struct Module { uint32_t start_function = -1; // function to run on module load Table table; Memory memory; - uint32_t global_count = 0; // number of globals - Global **globals = nullptr; // globals + uint32_t global_count = 0; // number of globals + Global **globals = nullptr; // globals // Runtime state uint8_t *pc_ptr = nullptr; // program counter int sp = -1; // operand stack pointer From eecb4c7804e15d0ca418cf60d88f28c912271163 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Tue, 11 Nov 2025 14:17:35 +0100 Subject: [PATCH 3/8] feat: prevent global.set of immutable global --- src/Interpreter/instructions.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Interpreter/instructions.cpp b/src/Interpreter/instructions.cpp index c24967cfc..0f03deda0 100644 --- a/src/Interpreter/instructions.cpp +++ b/src/Interpreter/instructions.cpp @@ -470,7 +470,10 @@ bool i_instr_get_global(Module *m) { */ bool i_instr_set_global(Module *m) { uint32_t arg = read_LEB_32(&m->pc_ptr); - m->globals[arg] = m->stack[m->sp--]; + if (!m->globals[arg]->mutability) { + sprintf(exception, "attempt to set immutable global"); + return false; + } *m->globals[arg]->value = m->stack[m->sp--]; #if TRACE debug(" - arg: 0x%x, got %s\n", arg, value_repr(&m->stack[m->sp + 1])); From 2d25e63c121f3a179b2ccc0abef5a0e06e0d34a9 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Tue, 11 Nov 2025 14:35:00 +0100 Subject: [PATCH 4/8] fix: add non-void return after FATAL in resolve_external_global --- src/Primitives/arduino.cpp | 2 +- src/Primitives/idf.cpp | 2 +- src/Primitives/zephyr.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Primitives/arduino.cpp b/src/Primitives/arduino.cpp index 1a52d3dde..d961a6f9f 100644 --- a/src/Primitives/arduino.cpp +++ b/src/Primitives/arduino.cpp @@ -1107,7 +1107,7 @@ bool resolve_external_global(char *symbol, Global **val) { } } FATAL("Could not find global %s \n", symbol); - // return false; // unreachable + return false; } //------------------------------------------------------ diff --git a/src/Primitives/idf.cpp b/src/Primitives/idf.cpp index 77d24c6d1..761c0a47b 100644 --- a/src/Primitives/idf.cpp +++ b/src/Primitives/idf.cpp @@ -326,7 +326,7 @@ bool resolve_external_global(char *symbol, Global **val) { } } FATAL("Could not find global %s \n", symbol); - // return false; // unreachable + return false; } //------------------------------------------------------ diff --git a/src/Primitives/zephyr.cpp b/src/Primitives/zephyr.cpp index daf5ecdac..a6c42ee0a 100644 --- a/src/Primitives/zephyr.cpp +++ b/src/Primitives/zephyr.cpp @@ -658,7 +658,7 @@ bool resolve_external_global(char *symbol, Global **val) { } } FATAL("Could not find global %s \n", symbol); - // return false; // unreachable + return false; } //------------------------------------------------------ From b60fd56751dbb54383f45a0888f9ceee95bda361 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Tue, 11 Nov 2025 14:40:17 +0100 Subject: [PATCH 5/8] chore: clang format --- src/Primitives/idf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Primitives/idf.cpp b/src/Primitives/idf.cpp index 761c0a47b..f42280ad2 100644 --- a/src/Primitives/idf.cpp +++ b/src/Primitives/idf.cpp @@ -326,7 +326,7 @@ bool resolve_external_global(char *symbol, Global **val) { } } FATAL("Could not find global %s \n", symbol); - return false; + return false; } //------------------------------------------------------ From e4233471fcc26dae484198b9225f41e31cbf001d Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Wed, 12 Nov 2025 00:49:30 +0100 Subject: [PATCH 6/8] test: add spec tests for globals --- tests/latch/core/global.asserts.wast | 63 ++++++++++ tests/latch/core/global.wast | 178 +++++++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 tests/latch/core/global.asserts.wast create mode 100644 tests/latch/core/global.wast diff --git a/tests/latch/core/global.asserts.wast b/tests/latch/core/global.asserts.wast new file mode 100644 index 000000000..35b99ad23 --- /dev/null +++ b/tests/latch/core/global.asserts.wast @@ -0,0 +1,63 @@ +;; https://github.com/WebAssembly/testsuite/tree/1b45c074228bc47f5f1c718a0929e7a3a2c5bb21 +(assert_return (invoke "get-a") (i32.const -2)) +(assert_return (invoke "get-b") (i64.const -5)) +(assert_return (invoke "get-x") (i32.const -12)) +(assert_return (invoke "get-y") (i64.const -15)) + +(assert_return (invoke "get-1") (f32.const -3)) +;; TODO ADD ASSERT RETURN: (invoke "get-2") returns (f64.const -4)) (will fail with latch 0.3.1 https://github.com/TOPLLab/latch/issues/34) +(assert_return (invoke "get-5") (f32.const -13)) +;; TODO ADD ASSERT RETURN: (invoke "get-6") returns (f64.const -14)) (will fail with latch 0.3.1 https://github.com/TOPLLab/latch/issues/34) + +(assert_return (invoke "set-2" (f64.const 4))) +(assert_return (invoke "set-x" (i32.const 6))) +(assert_return (invoke "set-y" (i64.const 7))) +(assert_return (invoke "set-5" (f32.const 8))) +(assert_return (invoke "set-6" (f64.const 9))) + +(assert_return (invoke "get-2") (f64.const 4)) +(assert_return (invoke "get-x") (i32.const 6)) +(assert_return (invoke "get-y") (i64.const 7)) +(assert_return (invoke "get-5") (f32.const 8)) +(assert_return (invoke "get-6") (f64.const 9)) + +(assert_return (invoke "as-select-first") (i32.const 6)) +(assert_return (invoke "as-select-mid") (i32.const 2)) +(assert_return (invoke "as-select-last") (i32.const 2)) + +(assert_return (invoke "as-loop-first") (i32.const 6)) +(assert_return (invoke "as-loop-mid") (i32.const 6)) +(assert_return (invoke "as-loop-last") (i32.const 6)) + +(assert_return (invoke "as-if-condition") (i32.const 2)) +(assert_return (invoke "as-if-then") (i32.const 6)) +(assert_return (invoke "as-if-else") (i32.const 6)) + +(assert_return (invoke "as-br_if-first") (i32.const 6)) +(assert_return (invoke "as-br_if-last") (i32.const 2)) + +(assert_return (invoke "as-br_table-first") (i32.const 6)) +(assert_return (invoke "as-br_table-last") (i32.const 2)) + +(assert_return (invoke "as-call_indirect-first") (i32.const 6)) +(assert_return (invoke "as-call_indirect-mid") (i32.const 2)) +(assert_trap (invoke "as-call_indirect-last") "undefined element") + +(assert_return (invoke "as-store-first")) +(assert_return (invoke "as-store-last")) +(assert_return (invoke "as-load-operand") (i32.const 1)) +(assert_return (invoke "as-memory.grow-value") (i32.const 1)) + +(assert_return (invoke "as-call-value") (i32.const 6)) + +(assert_return (invoke "as-return-value") (i32.const 6)) +(assert_return (invoke "as-drop-operand")) +(assert_return (invoke "as-br-value") (i32.const 6)) + +(assert_return (invoke "as-local.set-value" (i32.const 1)) (i32.const 6)) +(assert_return (invoke "as-local.tee-value" (i32.const 1)) (i32.const 6)) +(assert_return (invoke "as-global.set-value") (i32.const 6)) + +(assert_return (invoke "as-unary-operand") (i32.const 0)) +(assert_return (invoke "as-binary-operand") (i32.const 36)) +(assert_return (invoke "as-compare-operand") (i32.const 1)) diff --git a/tests/latch/core/global.wast b/tests/latch/core/global.wast new file mode 100644 index 000000000..22b0da433 --- /dev/null +++ b/tests/latch/core/global.wast @@ -0,0 +1,178 @@ +(module + (global $a i32 (i32.const -2)) + (global (;1;) f32 (f32.const -3)) + (global (;2;) (mut f64) (f64.const -4)) + (global $b i64 (i64.const -5)) + + (global $x (mut i32) (i32.const -12)) + (global (;5;) (mut f32) (f32.const -13)) + (global (;6;) (mut f64) (f64.const -14)) + (global $y (mut i64) (i64.const -15)) + + (func (export "get-a") (result i32) (global.get $a)) + (func (export "get-b") (result i64) (global.get $b)) + (func (export "get-x") (result i32) (global.get $x)) + (func (export "get-y") (result i64) (global.get $y)) + (func (export "set-x") (param i32) (global.set $x (local.get 0))) + (func (export "set-y") (param i64) (global.set $y (local.get 0))) + + (func (export "get-1") (result f32) (global.get 1)) + (func (export "get-2") (result f64) (global.get 2)) + (func (export "get-5") (result f32) (global.get 5)) + (func (export "get-6") (result f64) (global.get 6)) + (func (export "set-2") (param f64) (global.set 2 (local.get 0))) + (func (export "set-5") (param f32) (global.set 5 (local.get 0))) + (func (export "set-6") (param f64) (global.set 6 (local.get 0))) + + ;; As the argument of control constructs and instructions + + (memory 1) + + (func $dummy) + + (func (export "as-select-first") (result i32) + (select (global.get $x) (i32.const 2) (i32.const 3)) + ) + (func (export "as-select-mid") (result i32) + (select (i32.const 2) (global.get $x) (i32.const 3)) + ) + (func (export "as-select-last") (result i32) + (select (i32.const 2) (i32.const 3) (global.get $x)) + ) + + (func (export "as-loop-first") (result i32) + (loop (result i32) + (global.get $x) (call $dummy) (call $dummy) + ) + ) + (func (export "as-loop-mid") (result i32) + (loop (result i32) + (call $dummy) (global.get $x) (call $dummy) + ) + ) + (func (export "as-loop-last") (result i32) + (loop (result i32) + (call $dummy) (call $dummy) (global.get $x) + ) + ) + + (func (export "as-if-condition") (result i32) + (if (result i32) (global.get $x) + (then (call $dummy) (i32.const 2)) + (else (call $dummy) (i32.const 3)) + ) + ) + (func (export "as-if-then") (result i32) + (if (result i32) (i32.const 1) + (then (global.get $x)) (else (i32.const 2)) + ) + ) + (func (export "as-if-else") (result i32) + (if (result i32) (i32.const 0) + (then (i32.const 2)) (else (global.get $x)) + ) + ) + + (func (export "as-br_if-first") (result i32) + (block (result i32) + (br_if 0 (global.get $x) (i32.const 2)) + (return (i32.const 3)) + ) + ) + (func (export "as-br_if-last") (result i32) + (block (result i32) + (br_if 0 (i32.const 2) (global.get $x)) + (return (i32.const 3)) + ) + ) + + (func (export "as-br_table-first") (result i32) + (block (result i32) + (global.get $x) (i32.const 2) (br_table 0 0) + ) + ) + (func (export "as-br_table-last") (result i32) + (block (result i32) + (i32.const 2) (global.get $x) (br_table 0 0) + ) + ) + + (func $func (param i32 i32) (result i32) (local.get 0)) + (type $check (func (param i32 i32) (result i32))) + (table funcref (elem $func)) + (func (export "as-call_indirect-first") (result i32) + (block (result i32) + (call_indirect (type $check) + (global.get $x) (i32.const 2) (i32.const 0) + ) + ) + ) + (func (export "as-call_indirect-mid") (result i32) + (block (result i32) + (call_indirect (type $check) + (i32.const 2) (global.get $x) (i32.const 0) + ) + ) + ) + (func (export "as-call_indirect-last") (result i32) + (block (result i32) + (call_indirect (type $check) + (i32.const 2) (i32.const 0) (global.get $x) + ) + ) + ) + + (func (export "as-store-first") + (global.get $x) (i32.const 1) (i32.store) + ) + (func (export "as-store-last") + (i32.const 0) (global.get $x) (i32.store) + ) + (func (export "as-load-operand") (result i32) + (i32.load (global.get $x)) + ) + (func (export "as-memory.grow-value") (result i32) + (memory.grow (global.get $x)) + ) + + (func $f (param i32) (result i32) (local.get 0)) + (func (export "as-call-value") (result i32) + (call $f (global.get $x)) + ) + + (func (export "as-return-value") (result i32) + (global.get $x) (return) + ) + (func (export "as-drop-operand") + (drop (global.get $x)) + ) + (func (export "as-br-value") (result i32) + (block (result i32) (br 0 (global.get $x))) + ) + + (func (export "as-local.set-value") (param i32) (result i32) + (local.set 0 (global.get $x)) + (local.get 0) + ) + (func (export "as-local.tee-value") (param i32) (result i32) + (local.tee 0 (global.get $x)) + ) + (func (export "as-global.set-value") (result i32) + (global.set $x (global.get $x)) + (global.get $x) + ) + + (func (export "as-unary-operand") (result i32) + (i32.eqz (global.get $x)) + ) + (func (export "as-binary-operand") (result i32) + (i32.mul + (global.get $x) (global.get $x) + ) + ) + (func (export "as-compare-operand") (result i32) + (i32.gt_u + (global.get 0) (i32.const 1) + ) + ) +) From bd291246381b25bda7e4c6af64ad0e141050c647 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Fri, 14 Nov 2025 10:59:18 +0100 Subject: [PATCH 7/8] Add missing f64 global tests --- tests/latch/core/global.asserts.wast | 4 +- tests/latch/src/test.wast | 58 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 tests/latch/src/test.wast diff --git a/tests/latch/core/global.asserts.wast b/tests/latch/core/global.asserts.wast index 35b99ad23..6cd25ddb6 100644 --- a/tests/latch/core/global.asserts.wast +++ b/tests/latch/core/global.asserts.wast @@ -5,9 +5,9 @@ (assert_return (invoke "get-y") (i64.const -15)) (assert_return (invoke "get-1") (f32.const -3)) -;; TODO ADD ASSERT RETURN: (invoke "get-2") returns (f64.const -4)) (will fail with latch 0.3.1 https://github.com/TOPLLab/latch/issues/34) +(assert_return (invoke "get-2") (f64.const -4)) (assert_return (invoke "get-5") (f32.const -13)) -;; TODO ADD ASSERT RETURN: (invoke "get-6") returns (f64.const -14)) (will fail with latch 0.3.1 https://github.com/TOPLLab/latch/issues/34) +(assert_return (invoke "get-6") (f64.const -14)) (assert_return (invoke "set-2" (f64.const 4))) (assert_return (invoke "set-x" (i32.const 6))) diff --git a/tests/latch/src/test.wast b/tests/latch/src/test.wast new file mode 100644 index 000000000..0553d8733 --- /dev/null +++ b/tests/latch/src/test.wast @@ -0,0 +1,58 @@ +(module + (func (export "get-5-i32") (result i32) (i32.const 5)) + (func (export "get-5-i64") (result i64) (i64.const 5)) + (func (export "get-5-f32") (result f32) (f32.const 5)) + (func (export "get-5-f64") (result f64) (f64.const 5)) + + (func (export "check-eq-5-i32") (param i32) (result i32) (i32.eq (local.get 0) (i32.const 5))) + (func (export "check-eq-5-i64") (param i64) (result i32) (i64.eq (local.get 0) (i64.const 5))) + (func (export "check-eq-5-f32") (param f32) (result i32) (f32.eq (local.get 0) (f32.const 5))) + (func (export "check-eq-5-f64") (param f64) (result i32) (f64.eq (local.get 0) (f64.const 5))) + + ;; -- multivalue tests -- + + (func (export "get-multi-5-6") (result i32 i32) + (i32.const 5) + (i32.const 6) + ) + + (func (export "check-eq-multi-5-6") (param i32 i32) (result i32) + (i32.and + (i32.eq (local.get 0) (i32.const 5)) + (i32.eq (local.get 1) (i32.const 6)) + ) + ) + + ;; with blocks + (func (export "get-multi-block-5-6") (result i32 i32) + (block (result i32 i32) + (i32.const 5) + (i32.const 6) + ) + ) + + ;; with branching + (func (export "get-multi-branch-5-6") (result i32 i32) + (block (result i32 i32) + (br 0 + (i32.const 5) + (i32.const 6) + ) + ) + ) + + ;; with conditional + (func (export "get-multi-if-5-6") (result i32 i32) + (if (result i32 i32) + (i32.const 1) + (then + (i32.const 5) + (i32.const 6) + ) + (else + (i32.const 0) + (i32.const 0) + ) + ) + ) +) \ No newline at end of file From 557d6731e2c3fde777f5aab7e326652530925bda Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Fri, 14 Nov 2025 21:13:13 +0100 Subject: [PATCH 8/8] fix: remove redundant mutability check Checking whether the global.set target is mutable is a validation check that is therefore redundant to perform at runtime (and only slows down execution). --- src/Interpreter/instructions.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Interpreter/instructions.cpp b/src/Interpreter/instructions.cpp index 0f03deda0..04aea1609 100644 --- a/src/Interpreter/instructions.cpp +++ b/src/Interpreter/instructions.cpp @@ -470,10 +470,6 @@ bool i_instr_get_global(Module *m) { */ bool i_instr_set_global(Module *m) { uint32_t arg = read_LEB_32(&m->pc_ptr); - if (!m->globals[arg]->mutability) { - sprintf(exception, "attempt to set immutable global"); - return false; - } *m->globals[arg]->value = m->stack[m->sp--]; #if TRACE debug(" - arg: 0x%x, got %s\n", arg, value_repr(&m->stack[m->sp + 1]));