From 7fe98d6d73d318fed19918146fbf6d54ca39c3ad Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Fri, 14 Nov 2025 02:31:49 +0100 Subject: [PATCH 01/10] feat: multivalue --- src/Interpreter/instructions.cpp | 2 +- src/Interpreter/interpreter.cpp | 14 +++--- src/WARDuino.h | 2 +- src/WARDuino/WARDuino.cpp | 80 +++++++++++++++++++++++--------- 4 files changed, 67 insertions(+), 31 deletions(-) diff --git a/src/Interpreter/instructions.cpp b/src/Interpreter/instructions.cpp index 5ebaefac..da652051 100644 --- a/src/Interpreter/instructions.cpp +++ b/src/Interpreter/instructions.cpp @@ -130,9 +130,9 @@ bool i_instr_if(Module *m, uint8_t *block_ptr) { sprintf(exception, "call stack exhausted"); return false; } + uint32_t cond = m->stack[m->sp--].value.uint32; m->warduino->interpreter->push_block(m, block, m->sp); - uint32_t cond = m->stack[m->sp--].value.uint32; if (cond == 0) { // if false (I32) // branch to else block or after end of if if (block->else_ptr == nullptr) { diff --git a/src/Interpreter/interpreter.cpp b/src/Interpreter/interpreter.cpp index d3059882..77978abc 100644 --- a/src/Interpreter/interpreter.cpp +++ b/src/Interpreter/interpreter.cpp @@ -45,20 +45,18 @@ Block *Interpreter::pop_block(Module *m) { m->fp = frame->fp; // Restore frame pointer // Validate the return value - if (t->result_count == 1) { - if (m->stack[m->sp].value_type != t->results[0]) { + for (uint32_t i = 0; i < t->result_count; i++) { + if (m->stack[m->sp - (t->result_count - 1 - i)].value_type != t->results[i]) { sprintf(exception, "call type mismatch"); return nullptr; } } - // Restore stack pointer - if (t->result_count == 1) { - // Save top value as result - if (frame->sp < m->sp) { - m->stack[frame->sp + 1] = m->stack[m->sp]; - m->sp = frame->sp + 1; + if (t->result_count > 0) { + for (uint32_t i = 0; i < t->result_count; i++) { + m->stack[frame->sp + 1 + i] = m->stack[m->sp - (t->result_count - 1) + i]; } + m->sp = frame->sp + t->result_count; } else { if (frame->sp < m->sp) { m->sp = frame->sp; diff --git a/src/WARDuino.h b/src/WARDuino.h index 08852a95..421cbb90 100644 --- a/src/WARDuino.h +++ b/src/WARDuino.h @@ -60,7 +60,7 @@ class WARDuino { void update_module(Module *old_module, uint8_t *wasm, uint32_t wasm_len); bool invoke(Module *m, uint32_t fidx, uint32_t arity = 0, - StackValue *args = nullptr); + StackValue *args = nullptr, uint32_t max_results = 0, StackValue *out_results = nullptr, uint32_t *out_result_count = nullptr); uint32_t get_export_fidx(Module *m, const char *name); diff --git a/src/WARDuino/WARDuino.cpp b/src/WARDuino/WARDuino.cpp index 38865e83..f86db02f 100644 --- a/src/WARDuino/WARDuino.cpp +++ b/src/WARDuino/WARDuino.cpp @@ -72,24 +72,37 @@ void initTypes() { block_types[4].results = block_type_results[3]; } -Type *get_block_type(uint8_t value_type) { - switch (value_type) { - case 0x40: - return &block_types[0]; - case I32: - return &block_types[1]; - case I64: - return &block_types[2]; - case F32: - return &block_types[3]; - case F64: - return &block_types[4]; - default: - FATAL("invalid block_type value_type: %d\n", value_type); +Type *get_block_type(Module *m, uint8_t type) { + uint8_t *pos = &type; + int64_t type_s = read_LEB_signed(&pos, 33); + + if (type_s < 0) { + switch (type) { + case 0x40: + return &block_types[0]; // empty + case I32: + return &block_types[1]; + case I64: + return &block_types[2]; + case F32: + return &block_types[3]; + case F64: + return &block_types[4]; + default: + FATAL("invalid block_type value_type: %d\n", type); + return nullptr; + } + } else { + if ((uint32_t)type_s >= m->type_count) { + FATAL("block_type index out of bounds: %lld >= %u\n", + type_s, m->type_count); return nullptr; + } + return &m->types[type_s]; } } + // TODO: calculate this while parsing types uint64_t get_type_mask(Type *type) { uint64_t mask = 0x80; @@ -213,7 +226,7 @@ void find_blocks(Module *m) { case 0x04: // if block = (Block *)acalloc(1, sizeof(Block), "Block"); block->block_type = opcode; - block->type = get_block_type(*(pos + 1)); + block->type = get_block_type(m, *(pos + 1)); block->start_ptr = pos; blockstack[++top] = block; m->block_lookup[pos] = block; @@ -259,7 +272,7 @@ void WARDuino::run_init_expr(Module *m, uint8_t type, uint8_t **pc) { WARDuino::instance()->program_state = WARDUINOinit; Block block; block.block_type = 0x01; - block.type = get_block_type(type); + block.type = get_block_type(m, type); block.start_ptr = *pc; m->pc_ptr = *pc; @@ -908,8 +921,7 @@ WARDuino::WARDuino() { } // Return value of false means exception occurred -bool WARDuino::invoke(Module *m, uint32_t fidx, uint32_t arity, - StackValue *args) { +bool WARDuino::invoke(Module *m, uint32_t fidx, uint32_t arity, StackValue *args, uint32_t max_results, StackValue *out_results, uint32_t *out_result_count) { bool result; m->sp = -1; m->fp = -1; @@ -927,9 +939,29 @@ bool WARDuino::invoke(Module *m, uint32_t fidx, uint32_t arity, result = interpreter->interpret(m); dbg_trace("Interpretation ended\n"); dbg_dump_stack(m); - return result; + + if (!result) { + if (out_result_count) *out_result_count = 0; + return false; + } + + uint32_t rescount = 0; + Type *ftype = m->functions[fidx].type; + rescount = ftype->result_count; + + if (out_result_count) + { + *out_result_count = rescount > max_results ? max_results : rescount; + + for (uint32_t i = 0; i < *out_result_count; ++i) { + out_results[i] = m->stack[m->sp - (rescount - 1) + i]; + } + } + + return true; } + void WARDuino::setInterpreter(Interpreter *interpreter) { this->interpreter = interpreter; } @@ -939,9 +971,15 @@ int WARDuino::run_module(Module *m) { // execute main if (fidx != UNDEF) { - this->invoke(m, fidx); - return m->stack->value.uint32; + StackValue outputs[8]; + uint32_t out_count = 0; + bool ok = this->invoke(m, fidx, 0, nullptr, 8, outputs, &out_count); + if (!ok) { + return 0; + } + return (int)outputs[0].value.uint32; } + fflush(stdout); // wait m->warduino->debugger->pauseRuntime(m); From 7507cf1d61e0e3be81b5b65cebeeef927b3cc916 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Mon, 17 Nov 2025 00:39:59 +0100 Subject: [PATCH 02/10] Clang format --- src/Interpreter/interpreter.cpp | 6 ++++-- src/WARDuino.h | 4 +++- src/WARDuino/WARDuino.cpp | 17 ++++++++--------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Interpreter/interpreter.cpp b/src/Interpreter/interpreter.cpp index 77978abc..adbca8f6 100644 --- a/src/Interpreter/interpreter.cpp +++ b/src/Interpreter/interpreter.cpp @@ -46,7 +46,8 @@ Block *Interpreter::pop_block(Module *m) { // Validate the return value for (uint32_t i = 0; i < t->result_count; i++) { - if (m->stack[m->sp - (t->result_count - 1 - i)].value_type != t->results[i]) { + if (m->stack[m->sp - (t->result_count - 1 - i)].value_type != + t->results[i]) { sprintf(exception, "call type mismatch"); return nullptr; } @@ -54,7 +55,8 @@ Block *Interpreter::pop_block(Module *m) { // Restore stack pointer if (t->result_count > 0) { for (uint32_t i = 0; i < t->result_count; i++) { - m->stack[frame->sp + 1 + i] = m->stack[m->sp - (t->result_count - 1) + i]; + m->stack[frame->sp + 1 + i] = + m->stack[m->sp - (t->result_count - 1) + i]; } m->sp = frame->sp + t->result_count; } else { diff --git a/src/WARDuino.h b/src/WARDuino.h index 421cbb90..21f44fcc 100644 --- a/src/WARDuino.h +++ b/src/WARDuino.h @@ -60,7 +60,9 @@ class WARDuino { void update_module(Module *old_module, uint8_t *wasm, uint32_t wasm_len); bool invoke(Module *m, uint32_t fidx, uint32_t arity = 0, - StackValue *args = nullptr, uint32_t max_results = 0, StackValue *out_results = nullptr, uint32_t *out_result_count = nullptr); + StackValue *args = nullptr, uint32_t max_results = 0, + StackValue *out_results = nullptr, + uint32_t *out_result_count = nullptr); uint32_t get_export_fidx(Module *m, const char *name); diff --git a/src/WARDuino/WARDuino.cpp b/src/WARDuino/WARDuino.cpp index f86db02f..24cf3aa7 100644 --- a/src/WARDuino/WARDuino.cpp +++ b/src/WARDuino/WARDuino.cpp @@ -75,7 +75,7 @@ void initTypes() { Type *get_block_type(Module *m, uint8_t type) { uint8_t *pos = &type; int64_t type_s = read_LEB_signed(&pos, 33); - + if (type_s < 0) { switch (type) { case 0x40: @@ -94,15 +94,14 @@ Type *get_block_type(Module *m, uint8_t type) { } } else { if ((uint32_t)type_s >= m->type_count) { - FATAL("block_type index out of bounds: %lld >= %u\n", - type_s, m->type_count); + FATAL("block_type index out of bounds: %lld >= %u\n", type_s, + m->type_count); return nullptr; } return &m->types[type_s]; } } - // TODO: calculate this while parsing types uint64_t get_type_mask(Type *type) { uint64_t mask = 0x80; @@ -921,7 +920,9 @@ WARDuino::WARDuino() { } // Return value of false means exception occurred -bool WARDuino::invoke(Module *m, uint32_t fidx, uint32_t arity, StackValue *args, uint32_t max_results, StackValue *out_results, uint32_t *out_result_count) { +bool WARDuino::invoke(Module *m, uint32_t fidx, uint32_t arity, + StackValue *args, uint32_t max_results, + StackValue *out_results, uint32_t *out_result_count) { bool result; m->sp = -1; m->fp = -1; @@ -949,10 +950,9 @@ bool WARDuino::invoke(Module *m, uint32_t fidx, uint32_t arity, StackValue *args Type *ftype = m->functions[fidx].type; rescount = ftype->result_count; - if (out_result_count) - { + if (out_result_count) { *out_result_count = rescount > max_results ? max_results : rescount; - + for (uint32_t i = 0; i < *out_result_count; ++i) { out_results[i] = m->stack[m->sp - (rescount - 1) + i]; } @@ -961,7 +961,6 @@ bool WARDuino::invoke(Module *m, uint32_t fidx, uint32_t arity, StackValue *args return true; } - void WARDuino::setInterpreter(Interpreter *interpreter) { this->interpreter = interpreter; } From 37357def456136501fc4e53e423094a2a91532f5 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Sat, 22 Nov 2025 12:33:03 +0100 Subject: [PATCH 03/10] Fix printf type for int64_t in block_type fatal --- src/WARDuino/WARDuino.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WARDuino/WARDuino.cpp b/src/WARDuino/WARDuino.cpp index 24cf3aa7..51d257e8 100644 --- a/src/WARDuino/WARDuino.cpp +++ b/src/WARDuino/WARDuino.cpp @@ -94,8 +94,8 @@ Type *get_block_type(Module *m, uint8_t type) { } } else { if ((uint32_t)type_s >= m->type_count) { - FATAL("block_type index out of bounds: %lld >= %u\n", type_s, - m->type_count); + FATAL("block_type index out of bounds: %lld >= %u\n", + (long long)type_s, m->type_count); return nullptr; } return &m->types[type_s]; From 420f889657441c6e3076f97a738440f68c0f250a Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Sat, 22 Nov 2025 12:42:25 +0100 Subject: [PATCH 04/10] Fix printf type for int64_t in block_type fatal --- src/WARDuino/WARDuino.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WARDuino/WARDuino.cpp b/src/WARDuino/WARDuino.cpp index 51d257e8..ba6ff927 100644 --- a/src/WARDuino/WARDuino.cpp +++ b/src/WARDuino/WARDuino.cpp @@ -95,7 +95,7 @@ Type *get_block_type(Module *m, uint8_t type) { } else { if ((uint32_t)type_s >= m->type_count) { FATAL("block_type index out of bounds: %lld >= %u\n", - (long long)type_s, m->type_count); + (long long)type_s, (unsigned long)m->type_count); return nullptr; } return &m->types[type_s]; From 8224d3f06154d7aa5007b9476e85b81907073a15 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Sat, 22 Nov 2025 13:16:03 +0100 Subject: [PATCH 05/10] Fix printf type for uint32_t in block_type fatal --- src/WARDuino/WARDuino.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WARDuino/WARDuino.cpp b/src/WARDuino/WARDuino.cpp index ba6ff927..79a24ec4 100644 --- a/src/WARDuino/WARDuino.cpp +++ b/src/WARDuino/WARDuino.cpp @@ -95,7 +95,7 @@ Type *get_block_type(Module *m, uint8_t type) { } else { if ((uint32_t)type_s >= m->type_count) { FATAL("block_type index out of bounds: %lld >= %u\n", - (long long)type_s, (unsigned long)m->type_count); + (long long)type_s, (unsigned int)m->type_count); return nullptr; } return &m->types[type_s]; From ed9b97d1b23901b75752400b11c3c4df374c6961 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Thu, 4 Dec 2025 00:46:26 +0100 Subject: [PATCH 06/10] Add function spectests --- tests/latch/core/func_0.asserts.wast | 108 ++++++++++++ tests/latch/core/func_0.wast | 237 +++++++++++++++++++++++++++ 2 files changed, 345 insertions(+) create mode 100644 tests/latch/core/func_0.asserts.wast create mode 100644 tests/latch/core/func_0.wast diff --git a/tests/latch/core/func_0.asserts.wast b/tests/latch/core/func_0.asserts.wast new file mode 100644 index 00000000..c0533803 --- /dev/null +++ b/tests/latch/core/func_0.asserts.wast @@ -0,0 +1,108 @@ +;; https://github.com/WebAssembly/testsuite/blob/1144e51585d4571a443dbcbb3300d8d20d9b6d4d/func.wast + +(assert_return (invoke "type-use-1")) +(assert_return (invoke "type-use-2") (i32.const 0)) +(assert_return (invoke "type-use-3" (i32.const 1))) +(assert_return + (invoke "type-use-4" (i32.const 1) (f64.const 1) (i32.const 1)) + (i32.const 0) +) +(assert_return (invoke "type-use-5") (i32.const 0)) +(assert_return (invoke "type-use-6" (i32.const 1))) +(assert_return + (invoke "type-use-7" (i32.const 1) (f64.const 1) (i32.const 1)) + (i32.const 0) +) + +(assert_return (invoke "local-first-i32") (i32.const 0)) +;; (assert_return (invoke "local-first-i64") (i64.const 0)) +(assert_return (invoke "local-first-f32") (f32.const 0)) +;; (assert_return (invoke "local-first-f64") (f64.const 0)) +(assert_return (invoke "local-second-i32") (i32.const 0)) +;; (assert_return (invoke "local-second-i64") (i64.const 0)) +(assert_return (invoke "local-second-f32") (f32.const 0)) +;; (assert_return (invoke "local-second-f64") (f64.const 0)) +;; (assert_return (invoke "local-mixed") (f64.const 0)) + +(assert_return (invoke "param-first-i32" (i32.const 2) (i32.const 3)) (i32.const 2)) +(assert_return (invoke "param-first-i64" (i64.const 2) (i64.const 3)) (i64.const 2)) +(assert_return (invoke "param-first-f32" (f32.const 2) (f32.const 3)) (f32.const 2)) +(assert_return (invoke "param-first-f64" (f64.const 2) (f64.const 3)) (f64.const 2)) +(assert_return (invoke "param-second-i32" (i32.const 2) (i32.const 3)) (i32.const 3)) +(assert_return (invoke "param-second-i64" (i64.const 2) (i64.const 3)) (i64.const 3)) +(assert_return (invoke "param-second-f32" (f32.const 2) (f32.const 3)) (f32.const 3)) +(assert_return (invoke "param-second-f64" (f64.const 2) (f64.const 3)) (f64.const 3)) + +(assert_return (invoke "param-mixed" (f32.const 1) (i32.const 2) (i64.const 3) (i32.const 4) (f64.const 5.5) (i32.const 6)) (f64.const 5.5)) + +(assert_return (invoke "empty")) +(assert_return (invoke "value-void")) +(assert_return (invoke "value-i32") (i32.const 77)) +(assert_return (invoke "value-i64") (i64.const 7777)) +;; (assert_return (invoke "value-f32") (f32.const 77.7)) +(assert_return (invoke "value-f64") (f64.const 77.77)) +(assert_return (invoke "value-i32-f64") (i32.const 77) (f64.const 7)) +(assert_return (invoke "value-i32-i32-i32") (i32.const 1) (i32.const 2) (i32.const 3)) +(assert_return (invoke "value-block-void")) +(assert_return (invoke "value-block-i32") (i32.const 77)) +(assert_return (invoke "value-block-i32-i64") (i32.const 1) (i64.const 2)) + +(assert_return (invoke "return-empty")) +(assert_return (invoke "return-i32") (i32.const 78)) +(assert_return (invoke "return-i64") (i64.const 7878)) +;; (assert_return (invoke "return-f32") (f32.const 78.7)) +(assert_return (invoke "return-f64") (f64.const 78.78)) +(assert_return (invoke "return-i32-f64") (i32.const 78) (f64.const 78.78)) +(assert_return (invoke "return-i32-i32-i32") (i32.const 1) (i32.const 2) (i32.const 3)) +(assert_return (invoke "return-block-i32") (i32.const 77)) +(assert_return (invoke "return-block-i32-i64") (i32.const 1) (i64.const 2)) + +(assert_return (invoke "break-empty")) +(assert_return (invoke "break-i32") (i32.const 79)) +(assert_return (invoke "break-i64") (i64.const 7979)) +;; (assert_return (invoke "break-f32") (f32.const 79.9)) +(assert_return (invoke "break-f64") (f64.const 79.79)) +(assert_return (invoke "break-i32-f64") (i32.const 79) (f64.const 79.79)) +(assert_return (invoke "break-i32-i32-i32") (i32.const 1) (i32.const 2) (i32.const 3)) +(assert_return (invoke "break-block-i32") (i32.const 77)) +(assert_return (invoke "break-block-i32-i64") (i32.const 1) (i64.const 2)) + +(assert_return (invoke "break-br_if-empty" (i32.const 0))) +(assert_return (invoke "break-br_if-empty" (i32.const 2))) +(assert_return (invoke "break-br_if-num" (i32.const 0)) (i32.const 51)) +(assert_return (invoke "break-br_if-num" (i32.const 1)) (i32.const 50)) +(assert_return (invoke "break-br_if-num-num" (i32.const 0)) (i32.const 51) (i64.const 52)) +(assert_return (invoke "break-br_if-num-num" (i32.const 1)) (i32.const 50) (i64.const 51)) + +(assert_return (invoke "break-br_table-empty" (i32.const 0))) +(assert_return (invoke "break-br_table-empty" (i32.const 1))) +(assert_return (invoke "break-br_table-empty" (i32.const 5))) +(assert_return (invoke "break-br_table-empty" (i32.const -1))) +(assert_return (invoke "break-br_table-num" (i32.const 0)) (i32.const 50)) +(assert_return (invoke "break-br_table-num" (i32.const 1)) (i32.const 50)) +(assert_return (invoke "break-br_table-num" (i32.const 10)) (i32.const 50)) +(assert_return (invoke "break-br_table-num" (i32.const -100)) (i32.const 50)) +(assert_return (invoke "break-br_table-num-num" (i32.const 0)) (f32.const 50) (i64.const 51)) +(assert_return (invoke "break-br_table-num-num" (i32.const 1)) (f32.const 50) (i64.const 51)) +(assert_return (invoke "break-br_table-num-num" (i32.const 10)) (f32.const 50) (i64.const 51)) +(assert_return (invoke "break-br_table-num-num" (i32.const -100)) (f32.const 50) (i64.const 51)) +(assert_return (invoke "break-br_table-nested-empty" (i32.const 0))) +(assert_return (invoke "break-br_table-nested-empty" (i32.const 1))) +(assert_return (invoke "break-br_table-nested-empty" (i32.const 3))) +(assert_return (invoke "break-br_table-nested-empty" (i32.const -2))) +(assert_return (invoke "break-br_table-nested-num" (i32.const 0)) (i32.const 52)) +(assert_return (invoke "break-br_table-nested-num" (i32.const 1)) (i32.const 50)) +(assert_return (invoke "break-br_table-nested-num" (i32.const 2)) (i32.const 52)) +(assert_return (invoke "break-br_table-nested-num" (i32.const -3)) (i32.const 52)) +(assert_return (invoke "break-br_table-nested-num-num" (i32.const 0)) (i32.const 101) (i32.const 52)) +(assert_return (invoke "break-br_table-nested-num-num" (i32.const 1)) (i32.const 50) (i32.const 51)) +(assert_return (invoke "break-br_table-nested-num-num" (i32.const 2)) (i32.const 101) (i32.const 52)) +(assert_return (invoke "break-br_table-nested-num-num" (i32.const -3)) (i32.const 101) (i32.const 52)) + +(assert_return (invoke "large-sig" (i32.const 0) (i64.const 1) (f32.const 2) (f32.const 3) (i32.const 4) (f64.const 5) (f32.const 6) (i32.const 7) (i32.const 8) (i32.const 9) (f32.const 10) (f64.const 11) (f64.const 12) (f64.const 13) (i32.const 14) (i32.const 15) (f32.const 16)) (f64.const 5) (f32.const 2) (i32.const 0) (i32.const 8) (i32.const 7) (i64.const 1) (f32.const 3) (i32.const 9) + (i32.const 4) (f32.const 6) (f64.const 13) (f64.const 11) (i32.const 15) (f32.const 16) (i32.const 14) (f64.const 12)) + +(assert_return (invoke "init-local-i32") (i32.const 0)) +;; (assert_return (invoke "init-local-i64") (i64.const 0)) +(assert_return (invoke "init-local-f32") (f32.const 0)) +;; (assert_return (invoke "init-local-f64") (f64.const 0)) \ No newline at end of file diff --git a/tests/latch/core/func_0.wast b/tests/latch/core/func_0.wast new file mode 100644 index 00000000..92e12f1d --- /dev/null +++ b/tests/latch/core/func_0.wast @@ -0,0 +1,237 @@ +(module + ;; Auxiliary definition + (type $sig (func)) + (func $dummy) + + ;; Syntax + + (func) + (func (export "f")) + (func $f) + (func $h (export "g")) + + (func (local)) + (func (local) (local)) + (func (local i32)) + (func (local $x i32)) + (func (local i32 f64 i64)) + (func (local i32) (local f64)) + (func (local i32 f32) (local $x i64) (local) (local i32 f64)) + + (func (param)) + (func (param) (param)) + (func (param i32)) + (func (param $x i32)) + (func (param i32 f64 i64)) + (func (param i32) (param f64)) + (func (param i32 f32) (param $x i64) (param) (param i32 f64)) + + (func (result)) + (func (result) (result)) + (func (result i32) (unreachable)) + (func (result i32 f64 f32) (unreachable)) + (func (result i32) (result f64) (unreachable)) + (func (result i32 f32) (result i64) (result) (result i32 f64) (unreachable)) + + (type $sig-1 (func)) + (type $sig-2 (func (result i32))) + (type $sig-3 (func (param $x i32))) + (type $sig-4 (func (param i32 f64 i32) (result i32))) + + (func (export "type-use-1") (type $sig-1)) + (func (export "type-use-2") (type $sig-2) (i32.const 0)) + (func (export "type-use-3") (type $sig-3)) + (func (export "type-use-4") (type $sig-4) (i32.const 0)) + (func (export "type-use-5") (type $sig-2) (result i32) (i32.const 0)) + (func (export "type-use-6") (type $sig-3) (param i32)) + (func (export "type-use-7") + (type $sig-4) (param i32) (param f64 i32) (result i32) (i32.const 0) + ) + + (func (type $sig)) + (func (type $forward)) ;; forward reference + + (func $complex + (param i32 f32) (param $x i64) (param) (param i32) + (result) (result i32) (result) (result i64 i32) + (local f32) (local $y i32) (local i64 i32) (local) (local f64 i32) + (unreachable) (unreachable) + ) + (func $complex-sig + (type $sig) + (local f32) (local $y i32) (local i64 i32) (local) (local f64 i32) + (unreachable) (unreachable) + ) + + (type $forward (func)) + + ;; Typing of locals + + (func (export "local-first-i32") (result i32) (local i32 i32) (local.get 0)) + (func (export "local-first-i64") (result i64) (local i64 i64) (local.get 0)) + (func (export "local-first-f32") (result f32) (local f32 f32) (local.get 0)) + (func (export "local-first-f64") (result f64) (local f64 f64) (local.get 0)) + (func (export "local-second-i32") (result i32) (local i32 i32) (local.get 1)) + (func (export "local-second-i64") (result i64) (local i64 i64) (local.get 1)) + (func (export "local-second-f32") (result f32) (local f32 f32) (local.get 1)) + (func (export "local-second-f64") (result f64) (local f64 f64) (local.get 1)) + (func (export "local-mixed") (result f64) + (local f32) (local $x i32) (local i64 i32) (local) (local f64 i32) + (drop (f32.neg (local.get 0))) + (drop (i32.eqz (local.get 1))) + (drop (i64.eqz (local.get 2))) + (drop (i32.eqz (local.get 3))) + (drop (f64.neg (local.get 4))) + (drop (i32.eqz (local.get 5))) + (local.get 4) + ) + + ;; Typing of parameters + + (func (export "param-first-i32") (param i32 i32) (result i32) (local.get 0)) + (func (export "param-first-i64") (param i64 i64) (result i64) (local.get 0)) + (func (export "param-first-f32") (param f32 f32) (result f32) (local.get 0)) + (func (export "param-first-f64") (param f64 f64) (result f64) (local.get 0)) + (func (export "param-second-i32") (param i32 i32) (result i32) (local.get 1)) + (func (export "param-second-i64") (param i64 i64) (result i64) (local.get 1)) + (func (export "param-second-f32") (param f32 f32) (result f32) (local.get 1)) + (func (export "param-second-f64") (param f64 f64) (result f64) (local.get 1)) + (func (export "param-mixed") (param f32 i32) (param) (param $x i64) (param i32 f64 i32) + (result f64) + (drop (f32.neg (local.get 0))) + (drop (i32.eqz (local.get 1))) + (drop (i64.eqz (local.get 2))) + (drop (i32.eqz (local.get 3))) + (drop (f64.neg (local.get 4))) + (drop (i32.eqz (local.get 5))) + (local.get 4) + ) + + ;; Typing of results + + (func (export "empty")) + (func (export "value-void") (call $dummy)) + (func (export "value-i32") (result i32) (i32.const 77)) + (func (export "value-i64") (result i64) (i64.const 7777)) + (func (export "value-f32") (result f32) (f32.const 77.7)) + (func (export "value-f64") (result f64) (f64.const 77.77)) + (func (export "value-i32-f64") (result i32 f64) (i32.const 77) (f64.const 7)) + (func (export "value-i32-i32-i32") (result i32 i32 i32) + (i32.const 1) (i32.const 2) (i32.const 3) + ) + (func (export "value-block-void") (block (call $dummy) (call $dummy))) + (func (export "value-block-i32") (result i32) + (block (result i32) (call $dummy) (i32.const 77)) + ) + (func (export "value-block-i32-i64") (result i32 i64) + (block (result i32 i64) (call $dummy) (i32.const 1) (i64.const 2)) + ) + + (func (export "return-empty") (return)) + (func (export "return-i32") (result i32) (return (i32.const 78))) + (func (export "return-i64") (result i64) (return (i64.const 7878))) + (func (export "return-f32") (result f32) (return (f32.const 78.7))) + (func (export "return-f64") (result f64) (return (f64.const 78.78))) + (func (export "return-i32-f64") (result i32 f64) + (return (i32.const 78) (f64.const 78.78)) + ) + (func (export "return-i32-i32-i32") (result i32 i32 i32) + (return (i32.const 1) (i32.const 2) (i32.const 3)) + ) + (func (export "return-block-i32") (result i32) + (return (block (result i32) (call $dummy) (i32.const 77))) + ) + (func (export "return-block-i32-i64") (result i32 i64) + (return (block (result i32 i64) (call $dummy) (i32.const 1) (i64.const 2))) + ) + + (func (export "break-empty") (br 0)) + (func (export "break-i32") (result i32) (br 0 (i32.const 79))) + (func (export "break-i64") (result i64) (br 0 (i64.const 7979))) + (func (export "break-f32") (result f32) (br 0 (f32.const 79.9))) + (func (export "break-f64") (result f64) (br 0 (f64.const 79.79))) + (func (export "break-i32-f64") (result i32 f64) + (br 0 (i32.const 79) (f64.const 79.79)) + ) + (func (export "break-i32-i32-i32") (result i32 i32 i32) + (br 0 (i32.const 1) (i32.const 2) (i32.const 3)) + ) + (func (export "break-block-i32") (result i32) + (br 0 (block (result i32) (call $dummy) (i32.const 77))) + ) + (func (export "break-block-i32-i64") (result i32 i64) + (br 0 (block (result i32 i64) (call $dummy) (i32.const 1) (i64.const 2))) + ) + + (func (export "break-br_if-empty") (param i32) + (br_if 0 (local.get 0)) + ) + (func (export "break-br_if-num") (param i32) (result i32) + (drop (br_if 0 (i32.const 50) (local.get 0))) (i32.const 51) + ) + (func (export "break-br_if-num-num") (param i32) (result i32 i64) + (drop (drop (br_if 0 (i32.const 50) (i64.const 51) (local.get 0)))) + (i32.const 51) (i64.const 52) + ) + + (func (export "break-br_table-empty") (param i32) + (br_table 0 0 0 (local.get 0)) + ) + (func (export "break-br_table-num") (param i32) (result i32) + (br_table 0 0 (i32.const 50) (local.get 0)) (i32.const 51) + ) + (func (export "break-br_table-num-num") (param i32) (result f32 i64) + (br_table 0 0 (f32.const 50) (i64.const 51) (local.get 0)) + (f32.const 51) (i64.const 52) + ) + (func (export "break-br_table-nested-empty") (param i32) + (block (br_table 0 1 0 (local.get 0))) + ) + (func (export "break-br_table-nested-num") (param i32) (result i32) + (i32.add + (block (result i32) + (br_table 0 1 0 (i32.const 50) (local.get 0)) (i32.const 51) + ) + (i32.const 2) + ) + ) + (func (export "break-br_table-nested-num-num") (param i32) (result i32 i32) + (i32.add + (block (result i32 i32) + (br_table 0 1 0 (i32.const 50) (i32.const 51) (local.get 0)) + (i32.const 51) (i32.const -3) + ) + ) + (i32.const 52) + ) + + ;; Large signatures + + (func (export "large-sig") + (param i32 i64 f32 f32 i32 f64 f32 i32 i32 i32 f32 f64 f64 f64 i32 i32 f32) + (result f64 f32 i32 i32 i32 i64 f32 i32 i32 f32 f64 f64 i32 f32 i32 f64) + (local.get 5) + (local.get 2) + (local.get 0) + (local.get 8) + (local.get 7) + (local.get 1) + (local.get 3) + (local.get 9) + (local.get 4) + (local.get 6) + (local.get 13) + (local.get 11) + (local.get 15) + (local.get 16) + (local.get 14) + (local.get 12) + ) + + ;; Default initialization of locals + + (func (export "init-local-i32") (result i32) (local i32) (local.get 0)) + (func (export "init-local-i64") (result i64) (local i64) (local.get 0)) + (func (export "init-local-f32") (result f32) (local f32) (local.get 0)) + (func (export "init-local-f64") (result f64) (local f64) (local.get 0)) +) From e9f6a5f03f81b65fbe3340d09a5c595028c536b9 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Tue, 7 Apr 2026 02:14:20 +0200 Subject: [PATCH 07/10] Re-add tests involving 64 bit values Some tests with 64 bit values were temporarily commented out due to problems with Latch. These problems are now fixed, therefore the tests can be reintroduced. --- tests/latch/core/func_0.asserts.wast | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/latch/core/func_0.asserts.wast b/tests/latch/core/func_0.asserts.wast index c0533803..1a53d0b9 100644 --- a/tests/latch/core/func_0.asserts.wast +++ b/tests/latch/core/func_0.asserts.wast @@ -15,14 +15,14 @@ ) (assert_return (invoke "local-first-i32") (i32.const 0)) -;; (assert_return (invoke "local-first-i64") (i64.const 0)) +(assert_return (invoke "local-first-i64") (i64.const 0)) (assert_return (invoke "local-first-f32") (f32.const 0)) -;; (assert_return (invoke "local-first-f64") (f64.const 0)) +(assert_return (invoke "local-first-f64") (f64.const 0)) (assert_return (invoke "local-second-i32") (i32.const 0)) -;; (assert_return (invoke "local-second-i64") (i64.const 0)) +(assert_return (invoke "local-second-i64") (i64.const 0)) (assert_return (invoke "local-second-f32") (f32.const 0)) -;; (assert_return (invoke "local-second-f64") (f64.const 0)) -;; (assert_return (invoke "local-mixed") (f64.const 0)) +(assert_return (invoke "local-second-f64") (f64.const 0)) +(assert_return (invoke "local-mixed") (f64.const 0)) (assert_return (invoke "param-first-i32" (i32.const 2) (i32.const 3)) (i32.const 2)) (assert_return (invoke "param-first-i64" (i64.const 2) (i64.const 3)) (i64.const 2)) @@ -103,6 +103,6 @@ (i32.const 4) (f32.const 6) (f64.const 13) (f64.const 11) (i32.const 15) (f32.const 16) (i32.const 14) (f64.const 12)) (assert_return (invoke "init-local-i32") (i32.const 0)) -;; (assert_return (invoke "init-local-i64") (i64.const 0)) +(assert_return (invoke "init-local-i64") (i64.const 0)) (assert_return (invoke "init-local-f32") (f32.const 0)) -;; (assert_return (invoke "init-local-f64") (f64.const 0)) \ No newline at end of file +(assert_return (invoke "init-local-f64") (f64.const 0)) \ No newline at end of file From d011803118ae56e9f02b66d66db5839a8c9cb046 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Tue, 7 Apr 2026 02:17:29 +0200 Subject: [PATCH 08/10] Add missing function ptrs spectests --- tests/latch/core/func_ptrs_0.asserts.wast | 4 ++++ tests/latch/core/func_ptrs_0.wast | 25 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/latch/core/func_ptrs_0.asserts.wast create mode 100644 tests/latch/core/func_ptrs_0.wast diff --git a/tests/latch/core/func_ptrs_0.asserts.wast b/tests/latch/core/func_ptrs_0.asserts.wast new file mode 100644 index 00000000..700a4f4d --- /dev/null +++ b/tests/latch/core/func_ptrs_0.asserts.wast @@ -0,0 +1,4 @@ +(assert_return (invoke "one") (i32.const 13)) +(assert_return (invoke "two" (i32.const 13)) (i32.const 14)) +(assert_return (invoke "three" (i32.const 13)) (i32.const 11)) +;; (assert_return (invoke "four" (i32.const 83))) \ No newline at end of file diff --git a/tests/latch/core/func_ptrs_0.wast b/tests/latch/core/func_ptrs_0.wast new file mode 100644 index 00000000..962b7d3e --- /dev/null +++ b/tests/latch/core/func_ptrs_0.wast @@ -0,0 +1,25 @@ +(module + (type (func)) ;; 0: void -> void + (type $S (func)) ;; 1: void -> void + (type (func (param))) ;; 2: void -> void + (type (func (result i32))) ;; 3: void -> i32 + (type (func (param) (result i32))) ;; 4: void -> i32 + (type $T (func (param i32) (result i32))) ;; 5: i32 -> i32 + (type $U (func (param i32))) ;; 6: i32 -> void + + ;; (func $print (import "spectest" "print_i32") (type 6)) + + (func (type 0)) + (func (type $S)) + + (func (export "one") (type 4) (i32.const 13)) + (func (export "two") (type $T) (i32.add (local.get 0) (i32.const 1))) + + ;; Both signature and parameters are allowed (and required to match) + ;; since this allows the naming of parameters. + (func (export "three") (type $T) (param $a i32) (result i32) + (i32.sub (local.get 0) (i32.const 2)) + ) + + ;; (func (export "four") (type $U) (call $print (local.get 0))) +) \ No newline at end of file From 5549e1b861be937293e1d920cb7c6517bc970183 Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Tue, 7 Apr 2026 02:36:04 +0200 Subject: [PATCH 09/10] Add multi-value tutorial example --- tutorials/wat/main/multi_value.wat | 287 +++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 tutorials/wat/main/multi_value.wat diff --git a/tutorials/wat/main/multi_value.wat b/tutorials/wat/main/multi_value.wat new file mode 100644 index 00000000..f599e15d --- /dev/null +++ b/tutorials/wat/main/multi_value.wat @@ -0,0 +1,287 @@ +(module + (import "env" "print_string" (func $print.string (param i32 i32))) + (import "env" "print_int" (func $print.int (param i32))) + + (memory $mem 1) + (export "memory" (memory $mem)) + + (data (i32.const 0) "Test 1: Basic swap\n") + (data (i32.const 20) "Test 2: Triple return\n") + (data (i32.const 43) "Test 3: Nested calls\n") + (data (i32.const 65) "Test 4: Block multi-value\n") + (data (i32.const 92) "Test 5: Loop multi-value\n") + (data (i32.const 118) "Test 6: If multi-value\n") + (data (i32.const 142) "Test 7: Branch with values\n") + (data (i32.const 170) "Result: ") + (data (i32.const 179) "Test 8: br_table multi-value\n") + (data (i32.const 208) "Test 9: Complex composition\n") + + ;; Test 1: two return values (swap) + (func $swap (param i32 i32) (result i32 i32) + (local.get 1) + (local.get 0) + ) + + (func $test_swap (export "test_swap") + (call $print.string (i32.const 0) (i32.const 19)) + + (i32.const 5) + (i32.const 10) + (call $swap) + + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + ) + + ;; Test 2: three return values + (func $triple (param i32) (result i32 i32 i32) + ;; returns (x, x+10, x+20) + (local.get 0) + (local.get 0) + (i32.const 10) + (i32.add) + (local.get 0) + (i32.const 20) + (i32.add) + ) + + (func $test_triple (export "test_triple") + (call $print.string (i32.const 20) (i32.const 22)) + + (i32.const 5) + (call $triple) + + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + ) + + ;; Test 3: nested multi-value calls + (func $add_pair (param i32 i32) (result i32) + (local.get 0) + (local.get 1) + (i32.add) + ) + + (func $test_nested (export "test_nested") + (call $print.string (i32.const 43) (i32.const 21)) + + (i32.const 3) + (i32.const 7) + (call $swap) + (call $add_pair) + + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + ) + + ;; Test 4: block with multi-value type + (func $test_block (export "test_block") + (call $print.string (i32.const 65) (i32.const 26)) + + (block (result i32 i32) + (i32.const 42) + (i32.const 99) + ) + + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + ) + + ;; Test 5: loop with multi-value + (func $test_loop (export "test_loop") + (local $counter i32) + (local $val1 i32) + (local $val2 i32) + (call $print.string (i32.const 92) (i32.const 25)) + + (i32.const 0) + (local.set $counter) + + (i32.const 10) + (local.set $val1) + (i32.const 20) + (local.set $val2) + + ;; loop that accumulates + (block $exit + (loop $continue + + (local.get $counter) + (i32.const 3) + (i32.ge_u) + (br_if $exit) + + ;; incr both values + (local.get $val1) + (i32.const 1) + (i32.add) + (local.set $val1) + + (local.get $val2) + (i32.const 2) + (i32.add) + (local.set $val2) + + ;; incr counter + (local.get $counter) + (i32.const 1) + (i32.add) + (local.set $counter) + + (br $continue) + ) + ) + + ;; get results from loop + (block (result i32 i32) + (local.get $val1) + (local.get $val2) + ) + + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + ) + + ;; Test 6: if/else with multi-value result + (func $test_if (export "test_if") (param i32) + (call $print.string (i32.const 118) (i32.const 23)) + + (local.get 0) + (if (result i32 i32) + (then + (i32.const 100) + (i32.const 200) + ) + (else + (i32.const 300) + (i32.const 400) + ) + ) + + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + ) + + ;; Test 7: branch with multi-value preversation + (func $test_branch (export "test_branch") (param i32) + (call $print.string (i32.const 142) (i32.const 27)) + + (block (result i32 i32) + (i32.const 10) + (i32.const 20) + + (local.get 0) + (br_if 0) + + ;; if we don't branch, we replace the values + (drop) + (drop) + (i32.const 30) + (i32.const 40) + ) + + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + ) + + ;; Test 8: br_table with multi-value + (func $test_br_table (export "test_br_table") (param i32) + (local $result1 i32) + (local $result2 i32) + (call $print.string (i32.const 179) (i32.const 29)) + + (block (result i32 i32) + (block (result i32 i32) + (block (result i32 i32) + (i32.const 1) + (i32.const 2) + + (local.get 0) + (br_table 0 1 2 2) ;; this will branch to case 0, 1, or 2 based on input (with default to 2) + ) + ;; case 0 + (i32.const 10) + (i32.add) + (i32.const 10) + (i32.add) + (br 1) + ) + ;; case 1 + (i32.const 20) + (i32.add) + (i32.const 20) + (i32.add) + ) + ;; case 2 (and default) + (local.set $result2) + (local.set $result1) + + (call $print.string (i32.const 170) (i32.const 8)) + (local.get $result1) + (call $print.int) + (call $print.string (i32.const 170) (i32.const 8)) + (local.get $result2) + (call $print.int) + ) + + ;; Test 9: complex composition + (func $complex (param i32 i32) (result i32 i32 i32) + ;; returns (a+b, a-b, a*2) + (local.get 0) + (local.get 1) + (i32.add) + + (local.get 0) + (local.get 1) + (i32.sub) + + (local.get 0) + (i32.const 2) + (i32.mul) + ) + + (func $test_complex (export "test_complex") + (call $print.string (i32.const 208) (i32.const 28)) + (i32.const 10) + (i32.const 3) + (call $complex) + + (i32.add) ;; 20 + 7 = 27 + (i32.mul) ;; 27 * 13 = 351 + + (call $print.string (i32.const 170) (i32.const 8)) + (call $print.int) + ) + + + (func $main (export "main") + (call $test_swap) + (call $test_triple) + (call $test_nested) + (call $test_block) + (call $test_loop) + (call $test_if (i32.const 1)) + (call $test_if (i32.const 0)) + (call $test_branch (i32.const 1)) + (call $test_branch (i32.const 0)) + (call $test_br_table (i32.const 0)) + (call $test_br_table (i32.const 1)) + (call $test_br_table (i32.const 2)) + (call $test_complex) + ) +) \ No newline at end of file From 1b1bd322c2676fc8c4fda4b01a4d44f880f2a31f Mon Sep 17 00:00:00 2001 From: Abel Stuker Date: Tue, 21 Apr 2026 02:43:06 +0200 Subject: [PATCH 10/10] Fix overly complex WARDuino::invoke multivalue return handling --- src/WARDuino.h | 6 ++---- src/WARDuino/WARDuino.cpp | 32 ++++++++++---------------------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/WARDuino.h b/src/WARDuino.h index 21f44fcc..c1e5aa07 100644 --- a/src/WARDuino.h +++ b/src/WARDuino.h @@ -59,10 +59,8 @@ class WARDuino { void update_module(Module *old_module, uint8_t *wasm, uint32_t wasm_len); - bool invoke(Module *m, uint32_t fidx, uint32_t arity = 0, - StackValue *args = nullptr, uint32_t max_results = 0, - StackValue *out_results = nullptr, - uint32_t *out_result_count = nullptr); + std::vector invoke(Module *m, uint32_t fidx, uint32_t arity = 0, + StackValue *args = nullptr); uint32_t get_export_fidx(Module *m, const char *name); diff --git a/src/WARDuino/WARDuino.cpp b/src/WARDuino/WARDuino.cpp index 79a24ec4..b08de243 100644 --- a/src/WARDuino/WARDuino.cpp +++ b/src/WARDuino/WARDuino.cpp @@ -920,9 +920,8 @@ WARDuino::WARDuino() { } // Return value of false means exception occurred -bool WARDuino::invoke(Module *m, uint32_t fidx, uint32_t arity, - StackValue *args, uint32_t max_results, - StackValue *out_results, uint32_t *out_result_count) { +std::vector WARDuino::invoke(Module *m, uint32_t fidx, + uint32_t arity, StackValue *args) { bool result; m->sp = -1; m->fp = -1; @@ -942,23 +941,16 @@ bool WARDuino::invoke(Module *m, uint32_t fidx, uint32_t arity, dbg_dump_stack(m); if (!result) { - if (out_result_count) *out_result_count = 0; - return false; + return {}; } uint32_t rescount = 0; Type *ftype = m->functions[fidx].type; rescount = ftype->result_count; - - if (out_result_count) { - *out_result_count = rescount > max_results ? max_results : rescount; - - for (uint32_t i = 0; i < *out_result_count; ++i) { - out_results[i] = m->stack[m->sp - (rescount - 1) + i]; - } - } - - return true; + std::vector out(rescount); + for (uint32_t i = 0; i < rescount; ++i) + out[i] = m->stack[m->sp - (rescount - 1) + i]; + return out; } void WARDuino::setInterpreter(Interpreter *interpreter) { @@ -970,13 +962,9 @@ int WARDuino::run_module(Module *m) { // execute main if (fidx != UNDEF) { - StackValue outputs[8]; - uint32_t out_count = 0; - bool ok = this->invoke(m, fidx, 0, nullptr, 8, outputs, &out_count); - if (!ok) { - return 0; - } - return (int)outputs[0].value.uint32; + auto results = this->invoke(m, fidx); + if (results.empty()) return 0; + return (int)results[0].value.uint32; } fflush(stdout);