diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 2631af786..e7944dac0 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -398,6 +398,22 @@ void Debugger::printValue(const StackValue *v, const uint32_t idx, snprintf(buff, 255, R"("type":"F64","value":")" FMT(PRIx64) "\"", v->value.uint64); break; + case FUNCREF: + if (is_null_ref(v)) { + snprintf(buff, 255, R"("type":"funcref","value":null)"); + } else { + uint32_t fidx = (uint32_t)(uintptr_t)v->value.ref; + snprintf(buff, 255, R"("type":"funcref","value":%u)", fidx); + } + break; + case EXTERNREF: + if (is_null_ref(v)) { + snprintf(buff, 255, R"("type":"externref","value":null)"); + } else { + snprintf(buff, 255, R"("type":"externref","value":"%p")", + v->value.ref); + } + break; default: snprintf(buff, 255, R"("type":"%02x","value":")" FMT(PRIx64) "\"", v->value_type, v->value.uint64); @@ -610,6 +626,26 @@ void Debugger::dumpLocals(const Module *m) const { snprintf(_value_str, 255, R"("type":"F64","value":%.7f)", v->value.f64); break; + case FUNCREF: + if (is_null_ref(v)) { + snprintf(_value_str, 255, + R"("type":"funcref","value":null)"); + } else { + uint32_t fidx = (uint32_t)(uintptr_t)v->value.ref; + snprintf(_value_str, 255, R"("type":"funcref","value":%u)", + fidx); + } + break; + case EXTERNREF: + if (is_null_ref(v)) { + snprintf(_value_str, 255, + R"("type":"externref","value":null)"); + } else { + snprintf(_value_str, 255, + R"("type":"externref","value":"%p")", + v->value.ref); + } + break; default: snprintf(_value_str, 255, R"("type":"%02x","value":")" FMT(PRIx64) "\"", @@ -850,26 +886,68 @@ void Debugger::inspect(Module *m, const uint16_t sizeStateArray, break; } case tableState: { - this->channel->write( - R"(%s"table":{"max":%d, "init":%d, "elements":[)", - addComma ? "," : "", m->table.maximum, m->table.initial); + this->channel->write("%s\"tables\":[", addComma ? "," : ""); addComma = true; - for (uint32_t j = 0; j < m->table.size; j++) { - this->channel->write("%" PRIu32 "%s", m->table.entries[j], - (j + 1) == m->table.size ? "" : ","); + for (uint32_t tidx = 0; tidx < m->table_count; tidx++) { + ASSERT(tidx < m->table_count, "table index out of bounds"); + Table *table = &m->tables[tidx]; + + this->channel->write( + R"({"index":%d,"max":%d,"init":%d,"elements":[)", tidx, + table->maximum, table->initial); + + for (uint32_t j = 0; j < table->size; j++) { + this->channel->write("%" PRIu32 "%s", table->entries[j], + (j + 1) == table->size ? "" : ","); + } + this->channel->write("]}"); + if (tidx + 1 < m->table_count) { + this->channel->write(","); + } } - this->channel->write("]}"); // closing table + this->channel->write("]"); break; } case branchingTableState: { - this->channel->write( - R"(%s"br_table":{"size":"0x%x","labels":[)", - addComma ? "," : "", BR_TABLE_SIZE); - for (uint32_t j = 0; j < BR_TABLE_SIZE; j++) { - this->channel->write("%" PRIu32 "%s", m->br_table[j], - (j + 1) == BR_TABLE_SIZE ? "" : ","); + this->channel->write("%s\"br_tables\":[", addComma ? "," : ""); + addComma = true; + + for (uint32_t tidx = 0; tidx < m->table_count; tidx++) { + Table *table = &m->tables[tidx]; + + this->channel->write( + R"({"index":%u,"size":"0x%x","labels":[)", tidx, + table->size); + + for (uint32_t j = 0; j < table->size; j++) { + StackValue *entry = &table->entries[j]; + + if (is_null_ref(entry)) { + this->channel->write("null"); + } else if (entry->value_type == FUNCREF) { + uint32_t fidx = + (uint32_t)(uintptr_t)entry->value.ref; + this->channel->write("%u", fidx); + } else if (entry->value_type == EXTERNREF) { + this->channel->write("\"%p\"", entry->value.ref); + } else { + this->channel->write("\"0x%" PRIx64 "\"", + entry->value.uint64); + } + + if (j + 1 < table->size) { + this->channel->write(","); + } + } + + this->channel->write("]}"); + + if (tidx + 1 < m->table_count) { + this->channel->write(","); + } } - this->channel->write("]}"); + + this->channel->write("]"); break; } case memoryState: { @@ -1079,18 +1157,22 @@ void Debugger::freeState(Module *m, uint8_t *interruptData) { } case tableState: { debug("receiving table info\n"); - m->table.initial = read_B32(&first_msg); - m->table.maximum = read_B32(&first_msg); + uint32_t table_index = read_B32(&first_msg); + Table *table = &m->tables[table_index]; + table->initial = read_B32(&first_msg); + table->maximum = read_B32(&first_msg); uint32_t size = read_B32(&first_msg); - debug("init %d max %d size %d\n", m->table.initial, - m->table.maximum, size); - if (m->table.size != size) { - debug("old table size %d\n", m->table.size); - if (m->table.size != 0) free(m->table.entries); - m->table.entries = static_cast(acalloc( - size, sizeof(uint32_t), "Module->table.entries")); + debug("table[%u] init %d max %d size %d\n", table_index, + table->initial, table->maximum, size); + if (table->size != size) { + debug("old table[%u] size %d\n", table_index, table->size); + if (table->entries != nullptr) { + free(table->entries); + } + table->entries = static_cast( + acalloc(size, sizeof(StackValue), "Table.entries")); } - m->table.size = 0; // allows to accumulatively add entries + table->size = 0; break; } case memoryState: { @@ -1234,10 +1316,36 @@ bool Debugger::saveState(Module *m, uint8_t *interruptData) { break; } case tableState: { + uint32_t table_index = read_B32(&program_state); + Table *table = &m->tables[table_index]; + uint32_t quantity = read_B32(&program_state); + for (size_t i = 0; i < quantity; i++) { - uint32_t ne = read_B32(&program_state); - m->table.entries[m->table.size++] = ne; + if (table->size >= table->maximum) { + FATAL("table overflow during restoration\n"); + } + + uint8_t entry_type = *program_state++; + + if (entry_type != table->elem_type) { + debug("warning: entry type mismatch at index %u\n", i); + } + + StackValue *entry = &table->entries[table->size]; + entry->value_type = entry_type; + + if (entry_type == FUNCREF) { + uint32_t fidx = read_B32(&program_state); + entry->value.ref = (void *)(uintptr_t)fidx; + } else if (entry_type == EXTERNREF) { + set_null_ref(entry, EXTERNREF); + program_state += sizeof(void *); + } else { + FATAL("unknown table entry type 0x%02x\n", entry_type); + } + + table->size++; } break; } diff --git a/src/Interpreter/instructions.cpp b/src/Interpreter/instructions.cpp index 5ebaefac2..d1ac8807d 100644 --- a/src/Interpreter/instructions.cpp +++ b/src/Interpreter/instructions.cpp @@ -315,10 +315,10 @@ bool i_instr_call(Module *m) { * 0x11 call_indirect */ bool i_instr_call_indirect(Module *m) { - uint32_t tidx = read_LEB_32(&m->pc_ptr); // TODO: use tidx? - (void)tidx; - read_LEB_32(&m->pc_ptr); // reserved immediate - uint32_t val = m->stack[m->sp--].value.uint32; + uint32_t type_idx = read_LEB_32(&m->pc_ptr); + uint32_t table_idx = read_LEB_32(&m->pc_ptr); + uint32_t elem_idx = m->stack[m->sp--].value.uint32; + Table *table = &m->tables[table_idx]; if (m->options.mangle_table_index) { // val is the table address + the index (not sized for the // pointer size) so get the actual (sized) index @@ -328,19 +328,21 @@ bool i_instr_call_indirect(Module *m) { (uint32_t)((uint64_t)m->table.entries) - val); #endif // val = val - (uint32_t)((uint64_t)m->table.entries & 0xFFFFFFFF); - val = val - (uint32_t)((uint64_t)m->table.entries); + elem_idx = + elem_idx - (uint32_t)((uint64_t)m->tables[table_idx].entries); } - if (val >= m->table.maximum) { + if (elem_idx >= m->tables[table_idx].maximum) { sprintf(exception, "undefined element 0x%" PRIx32 " (max: 0x%" PRIx32 ") in table", - val, m->table.maximum); + elem_idx, m->tables[table_idx].maximum); return false; } - uint32_t fidx = m->table.entries[val]; + uint32_t fidx = + (uint32_t)(uintptr_t)m->tables[table_idx].entries[elem_idx].value.ref; #if TRACE - debug(" - call_indirect tidx: %d, val: 0x%x, fidx: 0x%x\n", tidx, val, - fidx); + debug(" - call_indirect tidx: %d, val: 0x%x, fidx: 0x%x\n", table_idx, + elem_idx, fidx); #endif if (fidx < m->import_count) { @@ -353,7 +355,7 @@ bool i_instr_call_indirect(Module *m) { sprintf(exception, "call stack exhausted"); return false; } - if (ftype->mask != m->types[tidx].mask) { + if (ftype->mask != m->types[type_idx].mask) { sprintf(exception, "indirect call type mismatch (call type and function type " "differ)"); @@ -379,9 +381,9 @@ bool i_instr_call_indirect(Module *m) { #if TRACE debug( - " - tidx: %d, table idx: %d, " + " - type idx: %d, table idx: %d, " "calling function fidx: %d at: 0x%p\n", - tidx, val, fidx, m->pc_ptr); + type_idx, table_idx, fidx, m->pc_ptr); #endif } return true; @@ -414,6 +416,25 @@ bool i_instr_select(Module *m) { return true; } +/** + * 0x1C select with type immediate + */ +bool i_instr_select_t(Module *m) { + uint32_t vec_len = read_LEB_32(&m->pc_ptr); + + for (uint32_t i = 0; i < vec_len; i++) { + uint8_t valtype = read_LEB(&m->pc_ptr, 7); + (void)valtype; // validation only + } + + uint32_t cond = m->stack[m->sp--].value.uint32; + m->sp--; + if (!cond) { + m->stack[m->sp] = m->stack[m->sp + 1]; + } + return true; +} + /** * 0x20 get_local * move the i-th local to the top of the stack @@ -1294,6 +1315,309 @@ bool i_instr_extension(Module *m, uint8_t opcode) { return true; } +/** + * 0xD0 ref.null + * Creates a null reference of the specified type + */ +bool i_instr_ref_null(Module *m) { + uint8_t reftype = read_LEB(&m->pc_ptr, 7); + + if (reftype != FUNCREF && reftype != EXTERNREF) { + sprintf(exception, "invalid reference type 0x%x for ref.null", reftype); + return false; + } + + m->sp++; + set_null_ref(&m->stack[m->sp], reftype); + +#if TRACE + debug(" - ref.null type=0x%x\n", reftype); +#endif + return true; +} + +/** + * 0xD1 ref.is_null + * Checks if a reference is null + */ +bool i_instr_ref_is_null(Module *m) { + StackValue *ref = &m->stack[m->sp]; + + if (!IS_REFTYPE(ref->value_type)) { + sprintf(exception, "ref.is_null requires reference type, got 0x%x", + ref->value_type); + return false; + } + + uint32_t result = is_null_ref(ref) ? 1 : 0; + m->stack[m->sp].value_type = I32; + m->stack[m->sp].value.uint32 = result; + +#if TRACE + debug(" - ref.is_null result=%d\n", result); +#endif + return true; +} + +/** + * 0xD2 ref.func + * Creates a function reference + */ +bool i_instr_ref_func(Module *m) { + uint32_t fidx = read_LEB_32(&m->pc_ptr); + + if (fidx >= m->function_count) { + sprintf(exception, "ref.func: invalid function index %" PRIu32, fidx); + return false; + } + + m->sp++; + m->stack[m->sp].value_type = FUNCREF; + m->stack[m->sp].value.ref = + reinterpret_cast(static_cast(fidx)); + +#if TRACE + debug(" - ref.func fidx=%" PRIu32 "\n", fidx); +#endif + return true; +} + +/** + * 0x25 table.get + * Gets an element from a table + */ +bool i_instr_table_get(Module *m) { + uint32_t tableidx = read_LEB_32(&m->pc_ptr); + + if (tableidx >= m->table_count) { + sprintf(exception, "table.get: invalid table index %u", tableidx); + return false; + } + + uint32_t index = m->stack[m->sp--].value.uint32; + Table *table = &m->tables[tableidx]; + + if (index >= table->size) { + sprintf(exception, "table.get: index %u out of bounds", index); + return false; + } + + if (table->entries[index].value_type != table->elem_type) { + sprintf(exception, "table.get: type mismatch in table slot"); + return false; + } + + m->stack[++m->sp] = table->entries[index]; + +#if TRACE + debug(" - table.get table=%d idx=%d\n", tableidx, index); +#endif + + return true; +} + +/** + * 0x26 table.set + * Sets an element in a table + */ +bool i_instr_table_set(Module *m) { + uint32_t tableidx = read_LEB_32(&m->pc_ptr); + + if (tableidx >= m->table_count) { + sprintf(exception, "table.set: invalid table index %u", tableidx); + return false; + } + StackValue val = m->stack[m->sp--]; + uint32_t index = m->stack[m->sp--].value.uint32; + Table *table = &m->tables[tableidx]; + + if (index >= table->size) { + sprintf(exception, "table.set: index %u out of bounds", index); + return false; + } + + if (val.value_type != table->elem_type) { + sprintf(exception, "table.set: type mismatch"); + return false; + } + + table->entries[index] = val; + +#if TRACE + debug(" - table.set table=%d idx=%d\n", tableidx, index); +#endif + return true; +} + +/** + * 0xFC 0x10 table.size + * Returns the current size of a table + */ +bool i_instr_table_size(Module *m) { + uint32_t tableidx = read_LEB_32(&m->pc_ptr); + + if (tableidx >= m->table_count) { + sprintf(exception, "table.size: invalid table index %u", tableidx); + return false; + } + + m->stack[++m->sp].value_type = I32; + m->stack[m->sp].value.uint32 = m->tables[tableidx].size; + +#if TRACE + debug(" - table.size table=%d size=%d\n", tableidx, + m->tables[tableidx].size); +#endif + return true; +} + +/** + * 0xFC 0x0F table.grow + * Grows a table by a given delta + */ +bool i_instr_table_grow(Module *m) { + uint32_t tableidx = read_LEB_32(&m->pc_ptr); + + if (tableidx >= m->table_count) { + sprintf(exception, "table.grow: invalid table index %u", tableidx); + return false; + } + + uint32_t delta = m->stack[m->sp--].value.uint32; + StackValue init_val = m->stack[m->sp--]; + Table *table = &m->tables[tableidx]; + + // verify init value type matches table type + if (init_val.value_type != table->elem_type) { + sprintf(exception, "table.grow: init value type mismatch"); + return false; + } + + uint32_t old_size = table->size; + uint32_t new_size = old_size + delta; + + // check if growth exceeds maximum + if (new_size > table->maximum || new_size < old_size) { + m->stack[++m->sp].value_type = I32; + m->stack[m->sp].value.int32 = -1; + return true; + } + + // grow the table + table->entries = + (StackValue *)arecalloc(table->entries, old_size, new_size, + sizeof(StackValue), "table.entries"); + + // initialize new slots with the init value + for (uint32_t i = old_size; i < new_size; i++) { + table->entries[i] = init_val; + } + + table->size = new_size; + + m->stack[++m->sp].value_type = I32; + m->stack[m->sp].value.uint32 = old_size; + +#if TRACE + debug(" - table.grow table=%d delta=%d old_size=%d\n", tableidx, delta, + old_size); +#endif + return true; +} + +/** + * 0xFC 0x11 table.fill + * Fills a range in a table with a value + */ +bool i_instr_table_fill(Module *m) { + uint32_t tableidx = read_LEB_32(&m->pc_ptr); + + if (tableidx >= m->table_count) { + sprintf(exception, "table.fill: invalid table index %u", tableidx); + return false; + } + + uint32_t n = m->stack[m->sp--].value.uint32; + StackValue val = m->stack[m->sp--]; + uint32_t i = m->stack[m->sp--].value.uint32; + + Table *table = &m->tables[tableidx]; + + // Verify value type matches table type + if (val.value_type != table->elem_type) { + sprintf(exception, "table.fill: type mismatch"); + return false; + } + + if (i + n > table->size) { + sprintf(exception, "table.fill: out of bounds"); + return false; + } + + // Fill the range with the value + for (uint32_t idx = i; idx < i + n; idx++) { + table->entries[idx] = val; + } + +#if TRACE + debug(" - table.fill table=%d start=%d len=%d\n", tableidx, i, n); +#endif + return true; +} + +/** + * 0xFC 0x0E table.copy + * Copies elements from one table to another + */ +// bool i_instr_table_copy(Module *m) { +// uint32_t dst_tableidx = read_LEB_32(&m->pc_ptr); +// uint32_t src_tableidx = read_LEB_32(&m->pc_ptr); + +// if (dst_tableidx >= m->table_count || src_tableidx >= m->table_count) { +// sprintf(exception, "table.copy: invalid table index"); +// return false; +// } + +// uint32_t n = m->stack[m->sp--].value.uint32; +// uint32_t s = m->stack[m->sp--].value.uint32; +// uint32_t d = m->stack[m->sp--].value.uint32; + +// Table *dst_table = &m->tables[dst_tableidx]; +// Table *src_table = &m->tables[src_tableidx]; + +// // Check bounds +// if (s + n > src_table->size || d + n > dst_table->size) { +// sprintf(exception, "table.copy: out of bounds"); +// return false; +// } + +// // Check type compatibility (source type must be subtype of dest type) +// // For now, require exact match +// if (src_table->elem_type != dst_table->elem_type) { +// sprintf(exception, "table.copy: incompatible table types"); +// return false; +// } + +// // Handle overlapping ranges correctly +// if (dst_tableidx == src_tableidx && d > s && d < s + n) { +// // Overlapping, copy backwards +// for (uint32_t i = n; i > 0; i--) { +// dst_table->entries[d + i - 1] = src_table->entries[s + i - 1]; +// } +// } else { +// // Non-overlapping or forward copy is safe +// for (uint32_t i = 0; i < n; i++) { +// dst_table->entries[d + i] = src_table->entries[s + i]; +// } +// } + +// #if TRACE +// debug(" - table.copy dst=%d src=%d d=%d s=%d n=%d\n", dst_tableidx, +// src_tableidx, d, s, n); +// #endif +// return true; +// } + /** * 0xe0 ... 0xe3 callback operations */ diff --git a/src/Interpreter/instructions.h b/src/Interpreter/instructions.h index f58f2cd46..572136d59 100644 --- a/src/Interpreter/instructions.h +++ b/src/Interpreter/instructions.h @@ -26,6 +26,8 @@ bool i_instr_drop(Module *m); bool i_instr_select(Module *m); +bool i_instr_select_t(Module *m); + bool i_instr_get_local(Module *m); bool i_instr_set_local(Module *m); @@ -74,4 +76,22 @@ bool i_instr_conversion(Module *m, uint8_t opcode); bool i_instr_extension(Module *m, uint8_t opcode); +bool i_instr_ref_null(Module *m); + +bool i_instr_ref_is_null(Module *m); + +bool i_instr_ref_func(Module *m); + +bool i_instr_table_get(Module *m); + +bool i_instr_table_set(Module *m); + +bool i_instr_table_fill(Module *m); + +// bool i_instr_table_copy(Module *m); + +bool i_instr_table_grow(Module *m); + +bool i_instr_table_size(Module *m); + bool i_instr_callback(Module *m, uint8_t opcode); diff --git a/src/Interpreter/interpreter.cpp b/src/Interpreter/interpreter.cpp index d30598822..1e5d5da70 100644 --- a/src/Interpreter/interpreter.cpp +++ b/src/Interpreter/interpreter.cpp @@ -82,9 +82,8 @@ void Interpreter::setup_call(Module *m, uint32_t fidx) { #if TRACE dbg_warn(" >> fn0x%x(%d) %s(", fidx, fidx, - func->export_name - ? func->export_name - : "") for (int p = ((int)type->param_count) - 1; p >= 0; p--) { + func->export_name ? func->export_name : ""); + for (int p = ((int)type->param_count) - 1; p >= 0; p--) { dbg_warn("%s%s", value_repr(&m->stack[m->sp - p]), p ? " " : ""); } dbg_warn("), %d locals, %d results\n", func->local_count, @@ -95,7 +94,7 @@ void Interpreter::setup_call(Module *m, uint32_t fidx) { m->fp = m->sp - ((int)type->param_count) + 1; // TODO: validate arguments vs formal params - // Push function locals + // Push function locals with proper initialization for (uint32_t lidx = 0; lidx < func->local_count; lidx++) { m->sp += 1; #if DEBUG || TRACE || WARN || INFO @@ -105,13 +104,45 @@ void Interpreter::setup_call(Module *m, uint32_t fidx) { } #endif memset(&m->stack[m->sp], 0, sizeof(StackValue)); - m->stack[m->sp].value_type = func->local_value_type[lidx]; + uint8_t local_type = func->local_value_type[lidx]; + m->stack[m->sp].value_type = local_type; + + // initialize reference types to null + if (IS_REFTYPE(local_type)) { + set_null_ref(&m->stack[m->sp], local_type); + } } // Set program counter to start of function m->pc_ptr = func->start_ptr; } +bool i_instr_table_init_indexed(Module *m) { + uint32_t tableidx = read_LEB_32(&m->pc_ptr); + uint32_t elemidx = read_LEB_32(&m->pc_ptr); + + if (tableidx >= m->table_count && tableidx != 0) { + sprintf(exception, "table.init: invalid table index %" PRIu32, + tableidx); + return false; + } + + Table *table = &m->tables[tableidx]; + + uint32_t n = m->stack[m->sp--].value.uint32; + uint32_t s = m->stack[m->sp--].value.uint32; + uint32_t d = m->stack[m->sp--].value.uint32; + + // TODO: Implement element segment copying + +#if TRACE + debug(" - table.init table=%d elem=%d d=%d s=%d n=%d\n", tableidx, + elemidx, d, s, n); +#endif + + return true; +} + uint32_t LOAD_SIZE[] = {4, 8, 4, 8, 1, 1, 2, 2, 1, 1, 2, 2, 4, 4}; uint32_t LOAD_TYPES[] = {I32, I64, F32, F64, I32, I32, I32, I32, I64, I64, I64, I64, I64, I64}; @@ -441,6 +472,69 @@ bool Interpreter::interpret(Module *m, bool waiting) { success &= i_instr_extension(m, opcode); continue; + // + // Reference type instructions + // + + // select with type immediate + case 0x1c: + success &= i_instr_select_t(m); + continue; + + // ref.null + case 0xd0: + success &= i_instr_ref_null(m); + continue; + // ref.is_null + case 0xd1: + success &= i_instr_ref_is_null(m); + continue; + + // ref.func + case 0xd2: + success &= i_instr_ref_func(m); + continue; + + // table.get + case 0x25: + success &= i_instr_table_get(m); + continue; + + // table.set + case 0x26: + success &= i_instr_table_set(m); + continue; + + case 0xfc: { + uint32_t sub_opcode = read_LEB_32(&m->pc_ptr); + switch (sub_opcode) { + case 0x0c: // table.init + success &= i_instr_table_init_indexed(m); + continue; + case 0x0e: // table.copy + // success &= i_instr_table_copy(m); + // continue; + + // TODO: unsupported for now (currently only 1 table) + sprintf(exception, "unrecognized 0xfc sub-opcode 0x%x", + sub_opcode); + return false; + case 0x0f: // table.grow + success &= i_instr_table_grow(m); + continue; + case 0x10: // table.size + success &= i_instr_table_size(m); + continue; + case 0x11: // table.fill + success &= i_instr_table_fill(m); + continue; + default: + sprintf(exception, "unrecognized 0xfc sub-opcode 0x%x", + sub_opcode); + return false; + } + } + // callback operations case 0xe0 ... 0xe3: success &= i_instr_callback(m, opcode); diff --git a/src/Primitives/emulated.cpp b/src/Primitives/emulated.cpp index 94bccf83a..4b1877166 100644 --- a/src/Primitives/emulated.cpp +++ b/src/Primitives/emulated.cpp @@ -116,6 +116,17 @@ def_prim(print_int, oneToNoneU32) { return true; } +def_prim(print_ref, oneToNoneU32) { + debug("EMU: print ref "); + if (is_null_ref(reinterpret_cast(&arg0))) { + printf("null\n"); + } else { + printf("ref %p\n", arg0.ref); + } + pop_args(1); + return true; +} + def_prim(print_string, twoToNoneU32) { uint32_t addr = arg1.uint32; uint32_t size = arg0.uint32; @@ -413,22 +424,40 @@ def_prim(ev3_touch_sensor, oneToOneU32) { } def_prim(subscribe_interrupt, threeToNoneU32) { - uint8_t pin = arg2.uint32; // GPIOPin - uint8_t tidx = arg1.uint32; // Table Idx pointing to Callback function + uint8_t pin = arg2.uint32; // GPIOPin + uint8_t encoded = arg1.uint32; // (table_index << 24) | element_index [[maybe_unused]] uint8_t mode = arg0.uint32; // Not used by emulator only printed debug("EMU: subscribe_interrupt(%u, %u, %u) \n", pin, tidx, mode); - if (m->table.size < tidx) { - debug("subscribe_interrupt: out of range table index %i\n", tidx); + // Temporary fix: backwards compatible encoding of table index and element + // index in tidx + // TODO + uint32_t table_index = encoded >> 24; + uint32_t elem_idx = encoded & 0x00FFFFFFu; + + if (table_index >= m->table_count) { + debug("subscribe_interrupt: invalid table_id %u (max %u)\n", + table_index, m->table_count); + table_index = 0; // fallback + } + + Table *table = &m->tables[table_index]; + + if (elem_idx >= table->size) { + debug( + "subscribe_interrupt: element index %u out of bounds in table %u " + "(size=%u)\n", + elem_idx, table_index, table->size); + pop_args(3); return false; } std::string topic = "interrupt_"; topic.append(std::to_string(pin)); - Callback c = Callback(m, topic, tidx); + Callback c = Callback(m, topic, encoded); CallbackHandler::add_callback(c); pop_args(3); return true; @@ -473,6 +502,7 @@ void install_primitives(Interpreter *interpreter) { install_primitive(micros); install_primitive(print_int); + install_primitive(print_ref); install_primitive(print_string); install_primitive(wifi_connect); diff --git a/src/Utils/macros.cpp b/src/Utils/macros.cpp index 9f6a28d90..dc8f69849 100644 --- a/src/Utils/macros.cpp +++ b/src/Utils/macros.cpp @@ -7,7 +7,7 @@ void end() { }; } -#if DEBUG || TRACE || WARN || INFO +#if DEBUG || TRACE || WARN || INFO || 1 char _value_str[256]; const char *debug_opcodes[] = {"0x00 unreachable", @@ -38,7 +38,7 @@ const char *debug_opcodes[] = {"0x00 unreachable", "0x19 UNDEFINED", "0x1a drop", "0x1b select", - "0x1c UNDEFINED", + "0x1c select_t", "0x1d UNDEFINED", "0x1e UNDEFINED", "0x1f UNDEFINED", @@ -47,8 +47,8 @@ const char *debug_opcodes[] = {"0x00 unreachable", "0x22 tee_local", "0x23 get_global", "0x24 set_global", - "0x25 UNDEFINED", - "0x26 UNDEFINED", + "0x25 table.get", + "0x26 table.set", "0x27 UNDEFINED", "0x28 i32.load", "0x29 i64.load", @@ -201,10 +201,29 @@ const char *debug_opcodes[] = {"0x00 unreachable", "0xbc i32.reinterpret/f32", "0xbd i64.reinterpret/f64", "0xbe f32.reinterpret/i32", - "0xbf f64.reinterpret/i64"}; + "0xbf f64.reinterpret/i64", + "0xc0 UNDEFINED", + "0xc1 UNDEFINED", + "0xc2 UNDEFINED", + "0xc3 UNDEFINED", + "0xc4 UNDEFINED", + "0xc5 UNDEFINED", + "0xc6 UNDEFINED", + "0xc7 UNDEFINED", + "0xc8 UNDEFINED", + "0xc9 UNDEFINED", + "0xca UNDEFINED", + "0xcb UNDEFINED", + "0xcc UNDEFINED", + "0xcd UNDEFINED", + "0xce UNDEFINED", + "0xcf UNDEFINED", + "0xd0 ref.null", + "0xd1 ref.is_null", + "0xd2 ref.func"}; const char *opcode_repr(uint8_t opcode) { - if (opcode < 192) { + if (opcode < 0xd3) { return debug_opcodes[opcode]; } else { return "OPCODE out of bounds"; @@ -225,6 +244,21 @@ char *value_repr(StackValue *v) { case F64: snprintf(_value_str, 255, "%.7g:f64", v->value.f64); break; + case FUNCREF: + if (is_null_ref(v)) { + snprintf(_value_str, 255, "null:funcref"); + } else { + uint32_t fidx = (uint32_t)(uintptr_t)v->value.ref; + snprintf(_value_str, 255, "0x%x:funcref", fidx); + } + break; + case EXTERNREF: + if (is_null_ref(v)) { + snprintf(_value_str, 255, "null:externref"); + } else { + snprintf(_value_str, 255, "%p:externref", v->value.ref); + } + break; default: snprintf(_value_str, 255, "BAD ENCODING %" PRIx64 ":%02x", v->value.uint64, -v->value_type); diff --git a/src/Utils/util.cpp b/src/Utils/util.cpp index fbe8d0072..684d2f1ee 100644 --- a/src/Utils/util.cpp +++ b/src/Utils/util.cpp @@ -139,6 +139,11 @@ bool deserialiseStackValue(uint8_t *input, bool decodeType, StackValue *value) { case F64: memcpy(&value->value.uint64, input, 8); break; + case FUNCREF: + case EXTERNREF: + memcpy(&value->value.ref, input, sizeof(void *)); + input += sizeof(void *); + break; default: return false; } diff --git a/src/WARDuino/CallbackHandler.cpp b/src/WARDuino/CallbackHandler.cpp index 0c87d76ff..2f6a3cb8b 100644 --- a/src/WARDuino/CallbackHandler.cpp +++ b/src/WARDuino/CallbackHandler.cpp @@ -221,8 +221,14 @@ void Callback::resolve_event(const Event &e) { module->stack[++module->sp].value.uint32 = payload.length(); module->stack[module->sp].value_type = I32; + // TODO: temporary backwards comptabile fix + uint32_t table_idx = table_index >> 24; + uint32_t elem_idx = table_index & 0x00FFFFFF; // Setup function - uint32_t fidx = module->table.entries[table_index]; + uint32_t fidx = (uint32_t)(uintptr_t)module->tables[table_idx] + .entries[elem_idx] + .value.ref; + WARDuino::instance()->interpreter->setup_call(module, fidx); // Validate argument count diff --git a/src/WARDuino/WARDuino.cpp b/src/WARDuino/WARDuino.cpp index 38865e83e..953a6d9e8 100644 --- a/src/WARDuino/WARDuino.cpp +++ b/src/WARDuino/WARDuino.cpp @@ -51,9 +51,10 @@ bool resolvesym(Interpreter *interpreter, char *filename, char *symbol, // char exception[4096]; // Static definition of block_types -uint32_t block_type_results[4][1] = {{I32}, {I64}, {F32}, {F64}}; +uint32_t block_type_results[6][1] = {{I32}, {I64}, {F32}, + {F64}, {FUNCREF}, {EXTERNREF}}; -Type block_types[5]; +Type block_types[7]; void initTypes() { block_types[0].form = BLOCK; @@ -70,6 +71,12 @@ void initTypes() { block_types[4].form = BLOCK; block_types[4].result_count = 1; block_types[4].results = block_type_results[3]; + block_types[5].form = BLOCK; + block_types[5].result_count = 1; + block_types[5].results = block_type_results[4]; + block_types[6].form = BLOCK; + block_types[6].result_count = 1; + block_types[6].results = block_type_results[5]; } Type *get_block_type(uint8_t value_type) { @@ -84,6 +91,10 @@ Type *get_block_type(uint8_t value_type) { return &block_types[3]; case F64: return &block_types[4]; + case FUNCREF: + return &block_types[5]; + case EXTERNREF: + return &block_types[6]; default: FATAL("invalid block_type value_type: %d\n", value_type); return nullptr; @@ -105,21 +116,22 @@ uint64_t get_type_mask(Type *type) { return mask; } -void parse_table_type(Module *m, uint8_t **pos) { - m->table.elem_type = read_LEB(pos, 7); - ASSERT(m->table.elem_type == ANYFUNC, "Table elem_type 0x%x unsupported", - m->table.elem_type); +void parse_table_type(Table *table, uint8_t **pos) { + table->elem_type = read_LEB(pos, 7); + ASSERT(table->elem_type == FUNCREF || table->elem_type == EXTERNREF, + "Table elem_type 0x%x unsupported", table->elem_type); uint32_t flags = read_LEB_32(pos); - uint32_t tsize = read_LEB_32(pos); // Initial size - m->table.initial = tsize; - m->table.size = tsize; + uint32_t tsize = read_LEB_32(pos); + table->initial = tsize; + table->size = tsize; + // Limit maximum to 64K if (flags & 0x1u) { - tsize = read_LEB_32(pos); // Max size - m->table.maximum = 0x10000 < tsize ? 0x10000 : tsize; + tsize = read_LEB_32(pos); + table->maximum = 0x10000 < tsize ? 0x10000 : tsize; } else { - m->table.maximum = 0x10000; + table->maximum = 0x10000; } debug(" table size: %d\n", tsize); } @@ -188,6 +200,19 @@ void skip_immediates(uint8_t **pos) { } read_LEB_32(pos); // default target break; + + case 0xd0: // ref.null + read_LEB(pos, 7); // reftype + break; + case 0xd2: // ref.func + read_LEB_32(pos); // funcidx + break; + + case 0x25: // table.get + case 0x26: // table.set + read_LEB_32(pos); // tableidx + break; + default: // no immediates break; } @@ -427,7 +452,12 @@ void WARDuino::instantiate_module(Module *m, uint8_t *bytes, type_index = read_LEB_32(&pos); break; case 0x01: // Table - parse_table_type(m, &pos); + m->table_count++; + m->tables = (Table *)arecalloc( + m->tables, m->table_count - 1, m->table_count, + sizeof(Table), "tables"); + parse_table_type(&m->tables[m->table_count - 1], + &pos); break; case 0x02: // Memory parse_memory_type(m, &pos); @@ -520,18 +550,16 @@ void WARDuino::instantiate_module(Module *m, uint8_t *bytes, } case 0x01: // Table { - ASSERT(!m->table.entries, - "More than 1 table not supported\n"); + // TODO: check correctness auto *tval = (Table *)val; - m->table.entries = (uint32_t *)val; - ASSERT(m->table.initial <= tval->maximum, - "Imported table is not large enough\n"); - dbg_warn(" setting table.entries to: %p\n", - *(uint32_t **)val); - m->table.entries = *(uint32_t **)val; - m->table.size = tval->size; - m->table.maximum = tval->maximum; - m->table.entries = tval->entries; + ASSERT(m->tables[m->table_count - 1].elem_type == + tval->elem_type, + "Imported table elem_type mismatch\n"); + m->tables[m->table_count - 1].size = tval->size; + m->tables[m->table_count - 1].maximum = + tval->maximum; + m->tables[m->table_count - 1].entries = + tval->entries; break; } case 0x02: // Memory @@ -605,15 +633,23 @@ void WARDuino::instantiate_module(Module *m, uint8_t *bytes, dbg_warn("Parsing Table(4) section\n"); uint32_t table_count = read_LEB_32(&pos); debug(" table count: 0x%x\n", table_count); - ASSERT(table_count == 1, "More than 1 table not supported"); - // Allocate the table - // for (uint32_t c=0; coptions.mangle_table_index = false; - m->table.entries = (uint32_t *)acalloc( - m->table.size, sizeof(uint32_t), "Module->table.entries"); - //} + + for (uint32_t t = 0; t < table_count; t++) { + m->table_count++; + m->tables = (Table *)arecalloc( + m->tables, m->table_count - 1, m->table_count, + sizeof(Table), "tables"); + Table *table = &m->tables[m->table_count - 1]; + parse_table_type(table, &pos); + // If it's not imported then don't mangle it + m->options.mangle_table_index = false; + table->entries = + (StackValue *)acalloc(table->size, sizeof(StackValue), + "module->tables[].entries"); + for (uint32_t i = 0; i < table->size; i++) { + set_null_ref(&table->entries[i], FUNCREF); + } + } break; } case 5: { @@ -700,52 +736,121 @@ void WARDuino::instantiate_module(Module *m, uint8_t *bytes, case 9: { dbg_warn("Parsing Element(9) section (length: 0x%x)\n", section_len); - uint32_t element_count = read_LEB_32(&pos); + uint32_t element_count = + read_LEB_32(&pos); // number of element segments + + m->elem_count = element_count; + // maybe not the best idea to have an ElemSegment also for + // active ones ... + m->elems = + (ElemSegment *)calloc(element_count, sizeof(ElemSegment)); + + // | Flag | Meaning | + // | ---- | -------------------------- | + // | 0 | active, funcidx | + // | 1 | passive, funcidx | + // | 2 | active + tableidx, funcidx | + // | 3 | declarative, funcidx | + // | 4 | active, expr | + // | 5 | passive, expr | + // | 6 | active + tableidx, expr | + // | 7 | declarative, expr | for (uint32_t c = 0; c < element_count; c++) { - uint32_t index = read_LEB_32(&pos); - ASSERT(index == 0, "Only 1 default table in MVP"); + ElemSegment *seg = &m->elems[c]; - // Run the init_expr to get offset - run_init_expr(m, I32, &pos); + uint32_t flags = read_LEB_32(&pos); - uint32_t offset = m->stack[m->sp--].value.uint32; + bool is_active = + (flags & 0x01) == 0; // flag = 0 | 2 | 4 | 6 + bool is_declarative = + (flags == 0x03 || flags == 0x07); // flag = 3 | 7 + bool use_expr_elems = + (flags & 0x04) != 0; // flag = 4 | 5 | 6 | 7 + bool has_explicit_table = + (flags == 0x02 || flags == 0x06); // flag = 2 | 6 - if (m->options.mangle_table_index) { - // offset is the table address + the index (not sized - // for the pointer size) so get the actual (sized) index - debug( - " origin offset: 0x%x, table addr: 0x%x, new " - "offset: 0x%x\n", - offset, (uint32_t)((uint64_t)m->table.entries), - offset - (uint32_t)((uint64_t)m->table.entries)); - // offset = offset - - // (uint32_t)((uint64_t)m->table.entries & 0xFFFFFFFF); - offset = - offset - (uint32_t)((uint64_t)m->table.entries); + uint32_t tableidx = 0; + seg->elem_type = FUNCREF; + + if (has_explicit_table) { + tableidx = read_LEB_32(&pos); + } + Table *table = &m->tables[tableidx]; + + uint32_t offset = 0; + if (is_active) { + run_init_expr(m, I32, &pos); + offset = m->stack[m->sp--].value.uint32; + // TODO: readd + // if (m->options.mangle_table_index) { + // offset -= + // (uint32_t)((uintptr_t)m->table.entries); + // } } - uint32_t num_elem = read_LEB_32(&pos); - dbg_warn(" table.entries: %p, offset: 0x%x\n", - m->table.entries, offset); - if (!m->options.disable_memory_bounds) { - ASSERT(offset + num_elem <= m->table.size, - "table overflow %" PRIu32 "+%" PRIu32 - " > %" PRIu32 "\n", - offset, num_elem, m->table.size); + // element type infromation: + // flags 0, 4 : implicit funcref + if (flags != 0x00 && flags != 0x04) { + // flags 5, 6, 7 : reftype (0x70 = funcref, 0x71 = + // externref) + if (use_expr_elems) { + seg->elem_type = read_LEB(&pos, 7); + } else { + // flags 1, 2, 3 : elemkind, must be 0x00 for + // funcref + uint8_t elemkind = read_LEB(&pos, 7); + ASSERT( + elemkind == 0x00, + "unsupported elemkind 0x%x in element segment", + elemkind); + seg->elem_type = FUNCREF; + } } - for (uint32_t n = 0; n < num_elem; n++) { - debug( - " write table entries %p, offset: 0x%x, n: 0x%x, " - "addr: %p\n", - m->table.entries, offset, n, - &m->table.entries[offset + n]); - m->table.entries[offset + n] = read_LEB_32(&pos); + + // number of elements in the segment + seg->count = read_LEB_32(&pos); + + if (!is_active && !is_declarative) { + seg->elems = (StackValue *)calloc(seg->count, + sizeof(StackValue)); + } + + for (uint32_t i = 0; i < seg->count; i++) { + StackValue val; + + if (use_expr_elems) { + run_init_expr(m, seg->elem_type, &pos); + val = m->stack[m->sp--]; + + } else { + uint32_t fidx = read_LEB_32(&pos); + + val.value_type = FUNCREF; + val.value.ref = (void *)(uintptr_t)fidx; + } + + // active: write directly to table + if (is_active) { + uint32_t idx = offset + i; + + ASSERT(idx < table->size, + "element out of bounds (table %u idx %u)", + tableidx, idx); + + table->entries[idx] = val; + } + + // passive: store for table.init + else if (!is_declarative) { + seg->elems[i] = val; + } + + // discard declaratice } } pos = start_pos + section_len; break; - // 9 and 11 are similar so keep them together, 10 is below 11 } case 11: { dbg_warn("Parsing Data(11) section (length: 0x%x)\n", @@ -986,10 +1091,17 @@ void WARDuino::free_module_state(Module *m) { m->globals = nullptr; } - if (m->table.entries != nullptr) { - free(m->table.entries); - m->table.entries = nullptr; + for (uint32_t t = 0; t < m->table_count; t++) { + if (m->tables[t].entries != nullptr) { + free(m->tables[t].entries); + m->tables[t].entries = nullptr; + } + } + if (m->tables != nullptr) { + free(m->tables); + m->tables = nullptr; } + m->table_count = 0; if (m->memory.bytes != nullptr) { free(m->memory.bytes); @@ -1036,10 +1148,8 @@ void WARDuino::free_module_state(Module *m) { m->memory.pages = 0; m->memory.initial = 0; m->memory.maximum = 0; - m->table.elem_type = 0; - m->table.initial = 0; - m->table.maximum = 0; - m->table.size = 0; + m->table_count = 0; + m->tables = nullptr; m->block_lookup.clear(); } diff --git a/src/WARDuino/internals.h b/src/WARDuino/internals.h index c13b4fd0b..1ad019910 100644 --- a/src/WARDuino/internals.h +++ b/src/WARDuino/internals.h @@ -26,10 +26,22 @@ #define I64_32_s 0x73 // -0x0d #define I64_32_u 0x72 // -0x0e +#define FUNCREF 0x70 // -0x10 +#define EXTERNREF 0x6f // -0x11 + #define ANYFUNC 0x70 // -0x10 #define FUNC 0x60 // -0x20 #define BLOCK 0x40 // -0x40 +#define IS_REFTYPE(t) ((t) == FUNCREF || (t) == EXTERNREF) +#define IS_NUMTYPE(t) ((t) >= F64 && (t) <= I32) +#define IS_VALTYPE(t) (IS_NUMTYPE(t) || IS_REFTYPE(t)) + +#define NULL_REF \ + ((void *)(uintptr_t)0xFFFFFFFF) // using nullptr / 0 as null reference + // causes conflicts with valid function + // index 0 + // Structures typedef struct Type { uint8_t form; @@ -71,6 +83,12 @@ typedef union FuncPtr { void (*void_f64)(double); double (*f64_f64)(double); + + void (*void_ref)(void *); + + void *(*ref_void)(); + + void *(*ref_ref)(void *); } FuncPtr; /// @@ -83,6 +101,7 @@ typedef struct StackValue { int64_t int64; float f32; double f64; + void *ref; } value; } StackValue; @@ -97,11 +116,11 @@ typedef struct Frame { /// typedef struct Table { - uint8_t elem_type = 0; // type of entries (only ANYFUNC in MVP) + uint8_t elem_type = 0; // type of entries (FUNCREF or EXTERNREF) uint32_t initial = 0; // initial table size uint32_t maximum = 0; // maximum table size uint32_t size = 0; // current table size - uint32_t *entries = nullptr; + StackValue *entries = nullptr; } Table; typedef struct Memory { @@ -119,6 +138,12 @@ typedef struct Global { StackValue *value; // current value } Global; +typedef struct ElemSegment { + uint8_t elem_type; + uint32_t count; + StackValue *elems; +} ElemSegment; + typedef struct Options { // when true: host memory addresses will be outside allocated memory area // so do not do bounds checking @@ -152,10 +177,14 @@ typedef struct Module { block_lookup; // map of module byte position to Blocks // same length as byte_count uint32_t start_function = -1; // function to run on module load - Table table; + + uint32_t table_count = 0; // number of tables + Table *tables = nullptr; // array of tables 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 + uint32_t elem_count = 0; // number of element segments + ElemSegment *elems = nullptr; // element segments // Runtime state uint8_t *pc_ptr = nullptr; // program counter int sp = -1; // operand stack pointer @@ -183,3 +212,12 @@ typedef struct PrimitiveEntry { void (*f_serialize_state)(std::vector &); Type *t; } PrimitiveEntry; + +inline bool is_null_ref(const StackValue *v) { + return IS_REFTYPE(v->value_type) && v->value.ref == NULL_REF; +} + +inline void set_null_ref(StackValue *v, uint8_t reftype) { + v->value_type = reftype; + v->value.ref = NULL_REF; +} \ No newline at end of file diff --git a/tests/latch/core/ref_func.asserts.wast b/tests/latch/core/ref_func.asserts.wast new file mode 100644 index 000000000..11f8ada0e --- /dev/null +++ b/tests/latch/core/ref_func.asserts.wast @@ -0,0 +1,12 @@ +;; https://github.com/WebAssembly/testsuite/blob/9828546c5a7b57d40c178bddcc4633b3a11c239f/ref_func.wast +(assert_return (invoke "is_null-f") (i32.const 0)) +(assert_return (invoke "is_null-g") (i32.const 0)) +(assert_return (invoke "is_null-v") (i32.const 0)) + +(assert_return (invoke "call-f" (i32.const 4)) (i32.const 4)) +(assert_return (invoke "call-g" (i32.const 4)) (i32.const 5)) +(assert_return (invoke "call-v" (i32.const 4)) (i32.const 4)) +(assert_return (invoke "set-g") ()) +(assert_return (invoke "call-v" (i32.const 4)) (i32.const 5)) +(assert_return (invoke "set-f") ()) +(assert_return (invoke "call-v" (i32.const 4)) (i32.const 4)) diff --git a/tests/latch/core/ref_func.wast b/tests/latch/core/ref_func.wast new file mode 100644 index 000000000..4fbd418a9 --- /dev/null +++ b/tests/latch/core/ref_func.wast @@ -0,0 +1,49 @@ +(module + (func $f) + (func $g (param $x i32) (result i32) + (i32.add (local.get $x) (i32.const 1)) + ) + + (global funcref (ref.func $f)) + (global funcref (ref.func $g)) + (global $v (mut funcref) (ref.func $f)) + + (global funcref (ref.func $gf1)) + (global funcref (ref.func $gf2)) + (func (drop (ref.func $ff1)) (drop (ref.func $ff2))) + (elem declare func $gf1 $ff1) + (elem declare funcref (ref.func $gf2) (ref.func $ff2)) + (func $gf1) + (func $gf2) + (func $ff1) + (func $ff2) + + (func (export "is_null-f") (result i32) + (ref.is_null (ref.func $f)) + ) + (func (export "is_null-g") (result i32) + (ref.is_null (ref.func $g)) + ) + (func (export "is_null-v") (result i32) + (ref.is_null (global.get $v)) + ) + + (func (export "set-f") (global.set $v (ref.func $f))) + (func (export "set-g") (global.set $v (ref.func $g))) + + (table $t 1 funcref) + (elem declare func $f $g) + + (func (export "call-f") (param $x i32) (result i32) + (table.set $t (i32.const 0) (ref.func $f)) + (call_indirect $t (param i32) (result i32) (local.get $x) (i32.const 0)) + ) + (func (export "call-g") (param $x i32) (result i32) + (table.set $t (i32.const 0) (ref.func $g)) + (call_indirect $t (param i32) (result i32) (local.get $x) (i32.const 0)) + ) + (func (export "call-v") (param $x i32) (result i32) + (table.set $t (i32.const 0) (global.get $v)) + (call_indirect $t (param i32) (result i32) (local.get $x) (i32.const 0)) + ) +) \ No newline at end of file diff --git a/tests/latch/core/ref_is_null.asserts.wast b/tests/latch/core/ref_is_null.asserts.wast new file mode 100644 index 000000000..104b13565 --- /dev/null +++ b/tests/latch/core/ref_is_null.asserts.wast @@ -0,0 +1,22 @@ +;; https://github.com/WebAssembly/testsuite/blob/6aacfd8929504d8e02a5144a14d184196ede6790/ref_is_null.wast +;; NOTE: these tests were modified in order to work with WARDuino +;; multiple tables and reference argument passing was not supported at the time of writing + +(assert_return (invoke "funcref") (i32.const 1)) +(assert_return (invoke "externref") (i32.const 1)) + +;; (assert_return (invoke "init" (ref.extern 0)) (i32.const 0)) + +(assert_return (invoke "funcref-elem" (i32.const 0)) (i32.const 1)) +;; (assert_return (invoke "externref-elem" (i32.const 0)) (i32.const 1)) + +(assert_return (invoke "funcref-elem" (i32.const 1)) (i32.const 0)) +;; (assert_return (invoke "externref-elem" (i32.const 1)) (i32.const 0)) + +(assert_return (invoke "deinit")) + +(assert_return (invoke "funcref-elem" (i32.const 0)) (i32.const 1)) +;; (assert_return (invoke "externref-elem" (i32.const 0)) (i32.const 1)) + +(assert_return (invoke "funcref-elem" (i32.const 1)) (i32.const 1)) +;; (assert_return (invoke "externref-elem" (i32.const 1)) (i32.const 1)) diff --git a/tests/latch/core/ref_is_null.wast b/tests/latch/core/ref_is_null.wast new file mode 100644 index 000000000..6b2d5c33a --- /dev/null +++ b/tests/latch/core/ref_is_null.wast @@ -0,0 +1,36 @@ +(module + (func $funcref (export "funcref") + ;; (param $x funcref) + (result i32) + (local $x funcref) + (local.set $x (ref.null func)) + (ref.is_null (local.get $x)) + ) + (func $externref (export "externref") + ;; (param $x externref) + (result i32) + (local $x externref) + (local.set $x (ref.null extern)) + (ref.is_null (local.get $x)) + ) + + (table $t1 2 funcref) + ;; (table $t2 2 externref) + (elem (table $t1) (i32.const 1) func $dummy) + (func $dummy) + + ;; (func (export "init") (param $r externref) + ;; (table.set $t2 (i32.const 1) (local.get $r)) + ;; ) + (func (export "deinit") + (table.set $t1 (i32.const 1) (ref.null func)) + ;; (table.set $t2 (i32.const 1) (ref.null extern)) + ) + + (func (export "funcref-elem") (param $x i32) (result i32) + (ref.is_null (table.get $t1 (local.get $x))) + ) + ;; (func (export "externref-elem") (param $x i32) (result i32) + ;; (call $externref (table.get $t2 (local.get $x))) + ;; ) +) \ No newline at end of file diff --git a/tests/latch/core/select.asserts.wast b/tests/latch/core/select.asserts.wast new file mode 100644 index 000000000..6b1d149a6 --- /dev/null +++ b/tests/latch/core/select.asserts.wast @@ -0,0 +1,136 @@ +;; https://github.com/WebAssembly/testsuite/blob/7ef86ddeed81458f9031a49a40b3a3f99c1c6a8a/select.wast +(assert_return (invoke "select-i32" (i32.const 1) (i32.const 2) (i32.const 1)) (i32.const 1)) +(assert_return (invoke "select-i64" (i64.const 2) (i64.const 1) (i32.const 1)) (i64.const 2)) +(assert_return (invoke "select-f32" (f32.const 1) (f32.const 2) (i32.const 1)) (f32.const 1)) +(assert_return (invoke "select-f64" (f64.const 1) (f64.const 2) (i32.const 1)) (f64.const 1)) + +(assert_return (invoke "select-i32" (i32.const 1) (i32.const 2) (i32.const 0)) (i32.const 2)) +(assert_return (invoke "select-i32" (i32.const 2) (i32.const 1) (i32.const 0)) (i32.const 1)) +(assert_return (invoke "select-i64" (i64.const 2) (i64.const 1) (i32.const -1)) (i64.const 2)) +(assert_return (invoke "select-i64" (i64.const 2) (i64.const 1) (i32.const 0xf0f0f0f0)) (i64.const 2)) + +(assert_return (invoke "select-f32" (f32.const nan) (f32.const 1) (i32.const 1)) (f32.const nan)) +(assert_return (invoke "select-f32" (f32.const nan:0x20304) (f32.const 1) (i32.const 1)) (f32.const nan:0x20304)) +(assert_return (invoke "select-f32" (f32.const nan) (f32.const 1) (i32.const 0)) (f32.const 1)) +(assert_return (invoke "select-f32" (f32.const nan:0x20304) (f32.const 1) (i32.const 0)) (f32.const 1)) +(assert_return (invoke "select-f32" (f32.const 2) (f32.const nan) (i32.const 1)) (f32.const 2)) +(assert_return (invoke "select-f32" (f32.const 2) (f32.const nan:0x20304) (i32.const 1)) (f32.const 2)) +(assert_return (invoke "select-f32" (f32.const 2) (f32.const nan) (i32.const 0)) (f32.const nan)) +(assert_return (invoke "select-f32" (f32.const 2) (f32.const nan:0x20304) (i32.const 0)) (f32.const nan:0x20304)) + +(assert_return (invoke "select-f64" (f64.const nan) (f64.const 1) (i32.const 1)) (f64.const nan)) +(assert_return (invoke "select-f64" (f64.const nan:0x20304) (f64.const 1) (i32.const 1)) (f64.const nan:0x20304)) +(assert_return (invoke "select-f64" (f64.const nan) (f64.const 1) (i32.const 0)) (f64.const 1)) +(assert_return (invoke "select-f64" (f64.const nan:0x20304) (f64.const 1) (i32.const 0)) (f64.const 1)) +(assert_return (invoke "select-f64" (f64.const 2) (f64.const nan) (i32.const 1)) (f64.const 2)) +(assert_return (invoke "select-f64" (f64.const 2) (f64.const nan:0x20304) (i32.const 1)) (f64.const 2)) +(assert_return (invoke "select-f64" (f64.const 2) (f64.const nan) (i32.const 0)) (f64.const nan)) +(assert_return (invoke "select-f64" (f64.const 2) (f64.const nan:0x20304) (i32.const 0)) (f64.const nan:0x20304)) + +(assert_return (invoke "select-i32-t" (i32.const 1) (i32.const 2) (i32.const 1)) (i32.const 1)) +(assert_return (invoke "select-i64-t" (i64.const 2) (i64.const 1) (i32.const 1)) (i64.const 2)) +(assert_return (invoke "select-f32-t" (f32.const 1) (f32.const 2) (i32.const 1)) (f32.const 1)) +(assert_return (invoke "select-f64-t" (f64.const 1) (f64.const 2) (i32.const 1)) (f64.const 1)) +;; (assert_return (invoke "select-funcref" (ref.null func) (ref.null func) (i32.const 1)) (ref.null func)) +;; (assert_return (invoke "select-externref" (ref.extern 1) (ref.extern 2) (i32.const 1)) (ref.extern 1)) + +(assert_return (invoke "select-i32-t" (i32.const 1) (i32.const 2) (i32.const 0)) (i32.const 2)) +(assert_return (invoke "select-i32-t" (i32.const 2) (i32.const 1) (i32.const 0)) (i32.const 1)) +(assert_return (invoke "select-i64-t" (i64.const 2) (i64.const 1) (i32.const -1)) (i64.const 2)) +(assert_return (invoke "select-i64-t" (i64.const 2) (i64.const 1) (i32.const 0xf0f0f0f0)) (i64.const 2)) +;; (assert_return (invoke "select-externref" (ref.extern 1) (ref.extern 2) (i32.const 0)) (ref.extern 2)) +;; (assert_return (invoke "select-externref" (ref.extern 2) (ref.extern 1) (i32.const 0)) (ref.extern 1)) + +(assert_return (invoke "select-f32-t" (f32.const nan) (f32.const 1) (i32.const 1)) (f32.const nan)) +(assert_return (invoke "select-f32-t" (f32.const nan:0x20304) (f32.const 1) (i32.const 1)) (f32.const nan:0x20304)) +(assert_return (invoke "select-f32-t" (f32.const nan) (f32.const 1) (i32.const 0)) (f32.const 1)) +(assert_return (invoke "select-f32-t" (f32.const nan:0x20304) (f32.const 1) (i32.const 0)) (f32.const 1)) +(assert_return (invoke "select-f32-t" (f32.const 2) (f32.const nan) (i32.const 1)) (f32.const 2)) +(assert_return (invoke "select-f32-t" (f32.const 2) (f32.const nan:0x20304) (i32.const 1)) (f32.const 2)) +(assert_return (invoke "select-f32-t" (f32.const 2) (f32.const nan) (i32.const 0)) (f32.const nan)) +(assert_return (invoke "select-f32-t" (f32.const 2) (f32.const nan:0x20304) (i32.const 0)) (f32.const nan:0x20304)) + +(assert_return (invoke "select-f64-t" (f64.const nan) (f64.const 1) (i32.const 1)) (f64.const nan)) +(assert_return (invoke "select-f64-t" (f64.const nan:0x20304) (f64.const 1) (i32.const 1)) (f64.const nan:0x20304)) +(assert_return (invoke "select-f64-t" (f64.const nan) (f64.const 1) (i32.const 0)) (f64.const 1)) +(assert_return (invoke "select-f64-t" (f64.const nan:0x20304) (f64.const 1) (i32.const 0)) (f64.const 1)) +(assert_return (invoke "select-f64-t" (f64.const 2) (f64.const nan) (i32.const 1)) (f64.const 2)) +(assert_return (invoke "select-f64-t" (f64.const 2) (f64.const nan:0x20304) (i32.const 1)) (f64.const 2)) +(assert_return (invoke "select-f64-t" (f64.const 2) (f64.const nan) (i32.const 0)) (f64.const nan)) +(assert_return (invoke "select-f64-t" (f64.const 2) (f64.const nan:0x20304) (i32.const 0)) (f64.const nan:0x20304)) + +(assert_return (invoke "as-select-first" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "as-select-first" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "as-select-mid" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "as-select-mid" (i32.const 1)) (i32.const 2)) +(assert_return (invoke "as-select-last" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "as-select-last" (i32.const 1)) (i32.const 3)) + +(assert_return (invoke "as-loop-first" (i32.const 0)) (i32.const 3)) +(assert_return (invoke "as-loop-first" (i32.const 1)) (i32.const 2)) +(assert_return (invoke "as-loop-mid" (i32.const 0)) (i32.const 3)) +(assert_return (invoke "as-loop-mid" (i32.const 1)) (i32.const 2)) +(assert_return (invoke "as-loop-last" (i32.const 0)) (i32.const 3)) +(assert_return (invoke "as-loop-last" (i32.const 1)) (i32.const 2)) + +(assert_return (invoke "as-if-condition" (i32.const 0))) +(assert_return (invoke "as-if-condition" (i32.const 1))) +(assert_return (invoke "as-if-then" (i32.const 0)) (i32.const 3)) +(assert_return (invoke "as-if-then" (i32.const 1)) (i32.const 2)) +(assert_return (invoke "as-if-else" (i32.const 0)) (i32.const 3)) +(assert_return (invoke "as-if-else" (i32.const 1)) (i32.const 2)) + +(assert_return (invoke "as-br_if-first" (i32.const 0)) (i32.const 3)) +(assert_return (invoke "as-br_if-first" (i32.const 1)) (i32.const 2)) +(assert_return (invoke "as-br_if-last" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "as-br_if-last" (i32.const 1)) (i32.const 2)) + +(assert_return (invoke "as-br_table-first" (i32.const 0)) (i32.const 3)) +(assert_return (invoke "as-br_table-first" (i32.const 1)) (i32.const 2)) +(assert_return (invoke "as-br_table-last" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "as-br_table-last" (i32.const 1)) (i32.const 2)) + +;; (assert_return (invoke "as-call_indirect-first" (i32.const 0)) (i32.const 3)) +;; (assert_return (invoke "as-call_indirect-first" (i32.const 1)) (i32.const 2)) +;; (assert_return (invoke "as-call_indirect-mid" (i32.const 0)) (i32.const 1)) +;; (assert_return (invoke "as-call_indirect-mid" (i32.const 1)) (i32.const 1)) +;; (assert_trap (invoke "as-call_indirect-last" (i32.const 0)) "undefined element") +;; (assert_trap (invoke "as-call_indirect-last" (i32.const 1)) "undefined element") + +(assert_return (invoke "as-store-first" (i32.const 0))) +(assert_return (invoke "as-store-first" (i32.const 1))) +(assert_return (invoke "as-store-last" (i32.const 0))) +(assert_return (invoke "as-store-last" (i32.const 1))) + +(assert_return (invoke "as-memory.grow-value" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "as-memory.grow-value" (i32.const 1)) (i32.const 3)) + +(assert_return (invoke "as-call-value" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "as-call-value" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "as-return-value" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "as-return-value" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "as-drop-operand" (i32.const 0))) +(assert_return (invoke "as-drop-operand" (i32.const 1))) +(assert_return (invoke "as-br-value" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "as-br-value" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "as-local.set-value" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "as-local.set-value" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "as-local.tee-value" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "as-local.tee-value" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "as-global.set-value" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "as-global.set-value" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "as-load-operand" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "as-load-operand" (i32.const 1)) (i32.const 1)) + +(assert_return (invoke "as-unary-operand" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "as-unary-operand" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "as-binary-operand" (i32.const 0)) (i32.const 4)) +(assert_return (invoke "as-binary-operand" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "as-test-operand" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "as-test-operand" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "as-compare-left" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "as-compare-left" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "as-compare-right" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "as-compare-right" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "as-convert-operand" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "as-convert-operand" (i32.const 1)) (i32.const 1)) diff --git a/tests/latch/core/select.wast b/tests/latch/core/select.wast new file mode 100644 index 000000000..43d3cbdcb --- /dev/null +++ b/tests/latch/core/select.wast @@ -0,0 +1,181 @@ +(module + ;; Auxiliary + (func $dummy) + (table $tab funcref (elem $dummy)) + (memory 1) + + (func (export "select-i32") (param i32 i32 i32) (result i32) + (select (local.get 0) (local.get 1) (local.get 2)) + ) + (func (export "select-i64") (param i64 i64 i32) (result i64) + (select (local.get 0) (local.get 1) (local.get 2)) + ) + (func (export "select-f32") (param f32 f32 i32) (result f32) + (select (local.get 0) (local.get 1) (local.get 2)) + ) + (func (export "select-f64") (param f64 f64 i32) (result f64) + (select (local.get 0) (local.get 1) (local.get 2)) + ) + + (func (export "select-i32-t") (param i32 i32 i32) (result i32) + (select (result i32) (local.get 0) (local.get 1) (local.get 2)) + ) + (func (export "select-i64-t") (param i64 i64 i32) (result i64) + (select (result i64) (local.get 0) (local.get 1) (local.get 2)) + ) + (func (export "select-f32-t") (param f32 f32 i32) (result f32) + (select (result f32) (local.get 0) (local.get 1) (local.get 2)) + ) + (func (export "select-f64-t") (param f64 f64 i32) (result f64) + (select (result f64) (local.get 0) (local.get 1) (local.get 2)) + ) + ;; (func (export "select-funcref") (param funcref funcref i32) (result funcref) + ;; (select (result funcref) (local.get 0) (local.get 1) (local.get 2)) + ;; ) + ;; (func (export "select-externref") (param externref externref i32) (result externref) + ;; (select (result externref) (local.get 0) (local.get 1) (local.get 2)) + ;; ) + + ;; As the argument of control constructs and instructions + + (func (export "as-select-first") (param i32) (result i32) + (select (select (i32.const 0) (i32.const 1) (local.get 0)) (i32.const 2) (i32.const 3)) + ) + (func (export "as-select-mid") (param i32) (result i32) + (select (i32.const 2) (select (i32.const 0) (i32.const 1) (local.get 0)) (i32.const 3)) + ) + (func (export "as-select-last") (param i32) (result i32) + (select (i32.const 2) (i32.const 3) (select (i32.const 0) (i32.const 1) (local.get 0))) + ) + + (func (export "as-loop-first") (param i32) (result i32) + (loop (result i32) (select (i32.const 2) (i32.const 3) (local.get 0)) (call $dummy) (call $dummy)) + ) + (func (export "as-loop-mid") (param i32) (result i32) + (loop (result i32) (call $dummy) (select (i32.const 2) (i32.const 3) (local.get 0)) (call $dummy)) + ) + (func (export "as-loop-last") (param i32) (result i32) + (loop (result i32) (call $dummy) (call $dummy) (select (i32.const 2) (i32.const 3) (local.get 0))) + ) + + (func (export "as-if-condition") (param i32) + (select (i32.const 2) (i32.const 3) (local.get 0)) (if (then (call $dummy))) + ) + (func (export "as-if-then") (param i32) (result i32) + (if (result i32) (i32.const 1) (then (select (i32.const 2) (i32.const 3) (local.get 0))) (else (i32.const 4))) + ) + (func (export "as-if-else") (param i32) (result i32) + (if (result i32) (i32.const 0) (then (i32.const 2)) (else (select (i32.const 2) (i32.const 3) (local.get 0)))) + ) + + (func (export "as-br_if-first") (param i32) (result i32) + (block (result i32) (br_if 0 (select (i32.const 2) (i32.const 3) (local.get 0)) (i32.const 4))) + ) + (func (export "as-br_if-last") (param i32) (result i32) + (block (result i32) (br_if 0 (i32.const 2) (select (i32.const 2) (i32.const 3) (local.get 0)))) + ) + + (func (export "as-br_table-first") (param i32) (result i32) + (block (result i32) (select (i32.const 2) (i32.const 3) (local.get 0)) (i32.const 2) (br_table 0 0)) + ) + (func (export "as-br_table-last") (param i32) (result i32) + (block (result i32) (i32.const 2) (select (i32.const 2) (i32.const 3) (local.get 0)) (br_table 0 0)) + ) + + ;; (func $func (param i32 i32) (result i32) (local.get 0)) + ;; (type $check (func (param i32 i32) (result i32))) + ;; (table $t funcref (elem $func)) + ;; (func (export "as-call_indirect-first") (param i32) (result i32) + ;; (block (result i32) + ;; (call_indirect $t (type $check) + ;; (select (i32.const 2) (i32.const 3) (local.get 0)) (i32.const 1) (i32.const 0) + ;; ) + ;; ) + ;; ) + ;; (func (export "as-call_indirect-mid") (param i32) (result i32) + ;; (block (result i32) + ;; (call_indirect $t (type $check) + ;; (i32.const 1) (select (i32.const 2) (i32.const 3) (local.get 0)) (i32.const 0) + ;; ) + ;; ) + ;; ) + ;; (func (export "as-call_indirect-last") (param i32) (result i32) + ;; (block (result i32) + ;; (call_indirect $t (type $check) + ;; (i32.const 1) (i32.const 4) (select (i32.const 2) (i32.const 3) (local.get 0)) + ;; ) + ;; ) + ;; ) + + (func (export "as-store-first") (param i32) + (select (i32.const 0) (i32.const 4) (local.get 0)) (i32.const 1) (i32.store) + ) + (func (export "as-store-last") (param i32) + (i32.const 8) (select (i32.const 1) (i32.const 2) (local.get 0)) (i32.store) + ) + + (func (export "as-memory.grow-value") (param i32) (result i32) + (memory.grow (select (i32.const 1) (i32.const 2) (local.get 0))) + ) + + (func $f (param i32) (result i32) (local.get 0)) + + (func (export "as-call-value") (param i32) (result i32) + (call $f (select (i32.const 1) (i32.const 2) (local.get 0))) + ) + (func (export "as-return-value") (param i32) (result i32) + (select (i32.const 1) (i32.const 2) (local.get 0)) (return) + ) + (func (export "as-drop-operand") (param i32) + (drop (select (i32.const 1) (i32.const 2) (local.get 0))) + ) + (func (export "as-br-value") (param i32) (result i32) + (block (result i32) (br 0 (select (i32.const 1) (i32.const 2) (local.get 0)))) + ) + (func (export "as-local.set-value") (param i32) (result i32) + (local i32) (local.set 0 (select (i32.const 1) (i32.const 2) (local.get 0))) (local.get 0) + ) + (func (export "as-local.tee-value") (param i32) (result i32) + (local.tee 0 (select (i32.const 1) (i32.const 2) (local.get 0))) + ) + (global $a (mut i32) (i32.const 10)) + (func (export "as-global.set-value") (param i32) (result i32) + (global.set $a (select (i32.const 1) (i32.const 2) (local.get 0))) + (global.get $a) + ) + (func (export "as-load-operand") (param i32) (result i32) + (i32.load (select (i32.const 0) (i32.const 4) (local.get 0))) + ) + + (func (export "as-unary-operand") (param i32) (result i32) + (i32.eqz (select (i32.const 0) (i32.const 1) (local.get 0))) + ) + (func (export "as-binary-operand") (param i32) (result i32) + (i32.mul + (select (i32.const 1) (i32.const 2) (local.get 0)) + (select (i32.const 1) (i32.const 2) (local.get 0)) + ) + ) + (func (export "as-test-operand") (param i32) (result i32) + (block (result i32) + (i32.eqz (select (i32.const 0) (i32.const 1) (local.get 0))) + ) + ) + + (func (export "as-compare-left") (param i32) (result i32) + (block (result i32) + (i32.le_s (select (i32.const 1) (i32.const 2) (local.get 0)) (i32.const 1)) + ) + ) + (func (export "as-compare-right") (param i32) (result i32) + (block (result i32) + (i32.ne (i32.const 1) (select (i32.const 0) (i32.const 1) (local.get 0))) + ) + ) + + (func (export "as-convert-operand") (param i32) (result i32) + (block (result i32) + (i32.wrap_i64 (select (i64.const 1) (i64.const 0) (local.get 0))) + ) + ) +) \ No newline at end of file diff --git a/tests/latch/core/table_grow_0.asserts.wast b/tests/latch/core/table_grow_0.asserts.wast new file mode 100644 index 000000000..b2c3646d7 --- /dev/null +++ b/tests/latch/core/table_grow_0.asserts.wast @@ -0,0 +1,24 @@ +;; ;; https://github.com/WebAssembly/testsuite/blob/6aacfd8929504d8e02a5144a14d184196ede6790/table_grow.wast +(assert_return (invoke "size") (i32.const 0)) +;; (assert_trap (invoke "set" (i32.const 0) (ref.extern 2)) "out of bounds table access") +;; (assert_trap (invoke "get" (i32.const 0)) "out of bounds table access") + +;; (assert_return (invoke "grow" (i32.const 1) (ref.null extern)) (i32.const 0)) +;; (assert_return (invoke "size") (i32.const 1)) +;; (assert_return (invoke "get" (i32.const 0)) (ref.null extern)) +;; (assert_return (invoke "set" (i32.const 0) (ref.extern 2))) +;; (assert_return (invoke "get" (i32.const 0)) (ref.extern 2)) +;; (assert_trap (invoke "set" (i32.const 1) (ref.extern 2)) "out of bounds table access") +;; (assert_trap (invoke "get" (i32.const 1)) "out of bounds table access") + +;; (assert_return (invoke "grow" (i32.const 4) (ref.extern 3)) (i32.const 1)) +;; (assert_return (invoke "size") (i32.const 5)) +;; (assert_return (invoke "get" (i32.const 0)) (ref.extern 2)) +;; (assert_return (invoke "set" (i32.const 0) (ref.extern 2))) +;; (assert_return (invoke "get" (i32.const 0)) (ref.extern 2)) +;; (assert_return (invoke "get" (i32.const 1)) (ref.extern 3)) +;; (assert_return (invoke "get" (i32.const 4)) (ref.extern 3)) +;; (assert_return (invoke "set" (i32.const 4) (ref.extern 4))) +;; (assert_return (invoke "get" (i32.const 4)) (ref.extern 4)) +;; (assert_trap (invoke "set" (i32.const 5) (ref.extern 2)) "out of bounds table access") +;; (assert_trap (invoke "get" (i32.const 5)) "out of bounds table access") diff --git a/tests/latch/core/table_grow_0.wast b/tests/latch/core/table_grow_0.wast new file mode 100644 index 000000000..7a1e321dc --- /dev/null +++ b/tests/latch/core/table_grow_0.wast @@ -0,0 +1,11 @@ +(module + (table $t 0 externref) + + (func (export "get") (param $i i32) (result externref) (table.get $t (local.get $i))) + (func (export "set") (param $i i32) (param $r externref) (table.set $t (local.get $i) (local.get $r))) + + (func (export "grow") (param $sz i32) (param $init externref) (result i32) + (table.grow $t (local.get $init) (local.get $sz)) + ) + (func (export "size") (result i32) (table.size $t)) +) \ No newline at end of file diff --git a/tests/latch/core/table_grow_1.asserts.wast b/tests/latch/core/table_grow_1.asserts.wast new file mode 100644 index 000000000..f91d526a1 --- /dev/null +++ b/tests/latch/core/table_grow_1.asserts.wast @@ -0,0 +1,2 @@ +;; https://github.com/WebAssembly/testsuite/blob/7ef86ddeed81458f9031a49a40b3a3f99c1c6a8a/table_grow.wast +(assert_return (invoke "grow") (i32.const -1)) \ No newline at end of file diff --git a/tests/latch/core/table_grow_1.wast b/tests/latch/core/table_grow_1.wast new file mode 100644 index 000000000..886ca8d1a --- /dev/null +++ b/tests/latch/core/table_grow_1.wast @@ -0,0 +1,7 @@ +(module + (table $t 0x10 funcref) + (elem declare func $grow) + (func $grow (export "grow") (result i32) + (table.grow $t (ref.func $grow) (i32.const 0xffff_fff0)) + ) +) \ No newline at end of file diff --git a/tests/latch/core/table_grow_2.asserts.wast b/tests/latch/core/table_grow_2.asserts.wast new file mode 100644 index 000000000..ffbc55735 --- /dev/null +++ b/tests/latch/core/table_grow_2.asserts.wast @@ -0,0 +1,6 @@ +;; https://github.com/WebAssembly/testsuite/blob/7ef86ddeed81458f9031a49a40b3a3f99c1c6a8a/table_grow.wast +(assert_return (invoke "grow" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 800)) (i32.const 3)) diff --git a/tests/latch/core/table_grow_2.wast b/tests/latch/core/table_grow_2.wast new file mode 100644 index 000000000..c98d195b7 --- /dev/null +++ b/tests/latch/core/table_grow_2.wast @@ -0,0 +1,6 @@ +(module + (table $t 0 externref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null extern) (local.get 0)) + ) +) \ No newline at end of file diff --git a/tests/latch/core/table_grow_3.asserts.wast b/tests/latch/core/table_grow_3.asserts.wast new file mode 100644 index 000000000..9739c3340 --- /dev/null +++ b/tests/latch/core/table_grow_3.asserts.wast @@ -0,0 +1,9 @@ +;; https://github.com/WebAssembly/testsuite/blob/7ef86ddeed81458f9031a49a40b3a3f99c1c6a8a/table_grow.wast +(assert_return (invoke "grow" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 2)) (i32.const 2)) +(assert_return (invoke "grow" (i32.const 6)) (i32.const 4)) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 10)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "grow" (i32.const 0x10000)) (i32.const -1)) diff --git a/tests/latch/core/table_grow_3.wast b/tests/latch/core/table_grow_3.wast new file mode 100644 index 000000000..584b1b328 --- /dev/null +++ b/tests/latch/core/table_grow_3.wast @@ -0,0 +1,6 @@ +(module + (table $t 0 10 externref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null extern) (local.get 0)) + ) +) \ No newline at end of file diff --git a/tests/latch/core/table_grow_4.asserts.wast b/tests/latch/core/table_grow_4.asserts.wast new file mode 100644 index 000000000..c74e04e84 --- /dev/null +++ b/tests/latch/core/table_grow_4.asserts.wast @@ -0,0 +1,8 @@ +;; https://github.com/WebAssembly/testsuite/blob/7ef86ddeed81458f9031a49a40b3a3f99c1c6a8a/table_grow.wast + +;; TODO: spec test parsing & latch must be modified such that reference types can be used in the tests +;; -> these are commented out for now + +;; (assert_return (invoke "check-table-null" (i32.const 0) (i32.const 9)) (ref.null func)) +(assert_return (invoke "grow" (i32.const 10)) (i32.const 10)) +;; (assert_return (invoke "check-table-null" (i32.const 0) (i32.const 19)) (ref.null func)) diff --git a/tests/latch/core/table_grow_4.wast b/tests/latch/core/table_grow_4.wast new file mode 100644 index 000000000..a7bcd8ec9 --- /dev/null +++ b/tests/latch/core/table_grow_4.wast @@ -0,0 +1,21 @@ +(module + (table $t 10 funcref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null func) (local.get 0)) + ) + (elem declare func 1) + (func (export "check-table-null") (param i32 i32) (result funcref) + (local funcref) + (local.set 2 (ref.func 1)) + (block + (loop + (local.set 2 (table.get $t (local.get 0))) + (br_if 1 (i32.eqz (ref.is_null (local.get 2)))) + (br_if 1 (i32.ge_u (local.get 0) (local.get 1))) + (local.set 0 (i32.add (local.get 0) (i32.const 1))) + (br_if 0 (i32.le_u (local.get 0) (local.get 1))) + ) + ) + (local.get 2) + ) +) \ No newline at end of file