diff options
33 files changed, 629 insertions, 114 deletions
diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 22c864cee..3736ce918 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -524,11 +524,11 @@ instructions = [ ("table.get", "makeTableGet(s)"), ("table.set", "makeTableSet(s)"), ("table.size", "makeTableSize(s)"), + ("table.grow", "makeTableGrow(s)"), # TODO: # table.init # table.fill # table.copy - # table.grow # # exception handling instructions ("try", "makeTry(s)"), diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index eab435caa..0a4b88a2b 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -1284,6 +1284,19 @@ BinaryenExpressionRef BinaryenTableSize(BinaryenModuleRef module, Builder(*(Module*)module).makeTableSize(name)); } +BinaryenExpressionRef BinaryenTableGrow(BinaryenModuleRef module, + const char* name, + BinaryenExpressionRef value, + BinaryenExpressionRef delta) { + if (value == nullptr) { + auto tableType = (*(Module*)module).getTableOrNull(name)->type; + value = BinaryenRefNull(module, (BinaryenType)tableType.getID()); + } + return static_cast<Expression*>( + Builder(*(Module*)module) + .makeTableGrow(name, (Expression*)value, (Expression*)delta)); +} + BinaryenExpressionRef BinaryenTry(BinaryenModuleRef module, const char* name, BinaryenExpressionRef body, @@ -1941,6 +1954,42 @@ void BinaryenTableSizeSetTable(BinaryenExpressionRef expr, const char* table) { assert(table); static_cast<TableSize*>(expression)->table = table; } +// TableGrow +const char* BinaryenTableGrowGetTable(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is<TableGrow>()); + return static_cast<TableGrow*>(expression)->table.c_str(); +} +void BinaryenTableGrowSetTable(BinaryenExpressionRef expr, const char* table) { + auto* expression = (Expression*)expr; + assert(expression->is<TableGrow>()); + assert(table); + static_cast<TableGrow*>(expression)->table = table; +} +BinaryenExpressionRef BinaryenTableGrowGetValue(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is<TableGrow>()); + return static_cast<TableGrow*>(expression)->value; +} +void BinaryenTableGrowSetValue(BinaryenExpressionRef expr, + BinaryenExpressionRef valueExpr) { + auto* expression = (Expression*)expr; + assert(expression->is<TableGrow>()); + assert(valueExpr); + static_cast<TableGrow*>(expression)->value = (Expression*)valueExpr; +} +BinaryenExpressionRef BinaryenTableGrowGetDelta(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is<TableGrow>()); + return static_cast<TableGrow*>(expression)->delta; +} +void BinaryenTableGrowSetDelta(BinaryenExpressionRef expr, + BinaryenExpressionRef deltaExpr) { + auto* expression = (Expression*)expr; + assert(expression->is<TableGrow>()); + assert(deltaExpr); + static_cast<TableGrow*>(expression)->delta = (Expression*)deltaExpr; +} // MemoryGrow BinaryenExpressionRef BinaryenMemoryGrowGetDelta(BinaryenExpressionRef expr) { auto* expression = (Expression*)expr; diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 3d57aca75..853f185e3 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -862,6 +862,11 @@ BinaryenTableSet(BinaryenModuleRef module, BinaryenExpressionRef value); BINARYEN_API BinaryenExpressionRef BinaryenTableSize(BinaryenModuleRef module, const char* name); +BINARYEN_API BinaryenExpressionRef +BinaryenTableGrow(BinaryenModuleRef module, + const char* name, + BinaryenExpressionRef value, + BinaryenExpressionRef delta); // Try: name can be NULL. delegateTarget should be NULL in try-catch. BINARYEN_API BinaryenExpressionRef BinaryenTry(BinaryenModuleRef module, @@ -1247,6 +1252,26 @@ BINARYEN_API const char* BinaryenTableSizeGetTable(BinaryenExpressionRef expr); // Sets the name of the table being accessed by a `table.size` expression. BINARYEN_API void BinaryenTableSizeSetTable(BinaryenExpressionRef expr, const char* table); + +// TableGrow + +// Gets the name of the table being accessed by a `table.grow` expression. +BINARYEN_API const char* BinaryenTableGrowGetTable(BinaryenExpressionRef expr); +// Sets the name of the table being accessed by a `table.grow` expression. +BINARYEN_API void BinaryenTableGrowSetTable(BinaryenExpressionRef expr, + const char* table); +// Gets the value expression of a `table.grow` expression. +BINARYEN_API BinaryenExpressionRef +BinaryenTableGrowGetValue(BinaryenExpressionRef expr); +// Sets the value expression of a `table.grow` expression. +BINARYEN_API void BinaryenTableGrowSetValue(BinaryenExpressionRef expr, + BinaryenExpressionRef valueExpr); +// Gets the delta of a `table.grow` expression. +BINARYEN_API BinaryenExpressionRef +BinaryenTableGrowGetDelta(BinaryenExpressionRef expr); +// Sets the delta of a `table.grow` expression. +BINARYEN_API void BinaryenTableGrowSetDelta(BinaryenExpressionRef expr, + BinaryenExpressionRef deltaExpr); // MemoryGrow // Gets the delta of a `memory.grow` expression. @@ -1254,7 +1279,7 @@ BINARYEN_API BinaryenExpressionRef BinaryenMemoryGrowGetDelta(BinaryenExpressionRef expr); // Sets the delta of a `memory.grow` expression. BINARYEN_API void BinaryenMemoryGrowSetDelta(BinaryenExpressionRef expr, - BinaryenExpressionRef delta); + BinaryenExpressionRef deltaExpr); // Load diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 0d61244fd..47ba1e1df 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -3043,9 +3043,17 @@ switch (op[0]) { switch (op[1]) { case 'a': { switch (op[6]) { - case 'g': - if (strcmp(op, "table.get") == 0) { return makeTableGet(s); } - goto parse_error; + case 'g': { + switch (op[7]) { + case 'e': + if (strcmp(op, "table.get") == 0) { return makeTableGet(s); } + goto parse_error; + case 'r': + if (strcmp(op, "table.grow") == 0) { return makeTableGrow(s); } + goto parse_error; + default: goto parse_error; + } + } case 's': { switch (op[7]) { case 'e': diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index a9cf8050d..79a4c9386 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -138,6 +138,7 @@ void ReFinalize::visitRefEq(RefEq* curr) { curr->finalize(); } void ReFinalize::visitTableGet(TableGet* curr) { curr->finalize(); } void ReFinalize::visitTableSet(TableSet* curr) { curr->finalize(); } void ReFinalize::visitTableSize(TableSize* curr) { curr->finalize(); } +void ReFinalize::visitTableGrow(TableGrow* curr) { curr->finalize(); } void ReFinalize::visitTry(Try* curr) { curr->finalize(); } void ReFinalize::visitThrow(Throw* curr) { curr->finalize(); } void ReFinalize::visitRethrow(Rethrow* curr) { curr->finalize(); } diff --git a/src/ir/cost.h b/src/ir/cost.h index 0463d3310..bf9e48308 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -545,6 +545,9 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> { return 2 + visit(curr->index) + visit(curr->value); } CostType visitTableSize(TableSize* curr) { return 1; } + CostType visitTableGrow(TableGrow* curr) { + return 100 + visit(curr->value) + visit(curr->delta); + } CostType visitTry(Try* curr) { // We assume no exception will be thrown in most cases return visit(curr->body); diff --git a/src/ir/effects.h b/src/ir/effects.h index 7074df755..e33a33818 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -534,7 +534,6 @@ private: parent.writesMemory = true; parent.implicitTrap = true; } - void visitTableSize(TableSize* curr) { parent.readsTable = true; } void visitConst(Const* curr) {} void visitUnary(Unary* curr) { switch (curr->op) { @@ -592,6 +591,7 @@ private: parent.isAtomic = true; } void visitMemoryGrow(MemoryGrow* curr) { + // TODO: find out if calls is necessary here parent.calls = true; // memory.grow technically does a read-modify-write operation on the // memory size in the successful case, modifying the set of valid @@ -613,6 +613,14 @@ private: parent.writesTable = true; parent.implicitTrap = true; } + void visitTableSize(TableSize* curr) { parent.readsTable = true; } + void visitTableGrow(TableGrow* curr) { + // table.grow technically does a read-modify-write operation on the + // table size in the successful case, modifying the set of valid + // indices, and just a read operation in the failure case + parent.readsTable = true; + parent.writesTable = true; + } void visitTry(Try* curr) {} void visitThrow(Throw* curr) { if (parent.tryDepth == 0) { diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 29729c4b1..cef5c989d 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -94,6 +94,7 @@ function initializeConstants() { 'TableGet', 'TableSet', 'TableSize', + 'TableGrow', 'Try', 'Throw', 'Rethrow', @@ -675,6 +676,9 @@ function wrapModule(module, self = {}) { }, 'size'(name) { return Module['_BinaryenTableSize'](module, strToStack(name)); + }, + 'grow'(name, value, delta) { + return Module['_BinaryenTableGrow'](module, strToStack(name), value, delta); } } @@ -2835,6 +2839,14 @@ Module['getExpressionInfo'] = function(expr) { 'type': type, 'table': UTF8ToString(Module['_BinaryenTableSizeGetTable'](expr)), }; + case Module['TableGrowId']: + return { + 'id': id, + 'type': type, + 'table': UTF8ToString(Module['_BinaryenTableGrowGetTable'](expr)), + 'value': Module['_BinaryenTableGrowGetValue'](expr), + 'delta': Module['_BinaryenTableGrowGetDelta'](expr), + }; case Module['LoadId']: return { 'id': id, @@ -3850,6 +3862,27 @@ Module['TableSize'] = makeExpressionWrapper({ }, }); +Module['TableGrow'] = makeExpressionWrapper({ + 'getTable'(expr) { + return UTF8ToString(Module['_BinaryenTableGrowGetTable'](expr)); + }, + 'setTable'(expr, name) { + preserveStack(() => { Module['_BinaryenTableGrowSetTable'](expr, strToStack(name)) }); + }, + 'getValue'(expr) { + return Module['_BinaryenTableGrowGetValue'](expr); + }, + 'setValue'(expr, valueExpr) { + Module['_BinaryenTableGrowSetValue'](expr, valueExpr); + }, + 'getDelta'(expr) { + return Module['_BinaryenTableGrowGetDelta'](expr); + }, + 'setDelta'(expr, deltaExpr) { + Module['_BinaryenTableGrowSetDelta'](expr, deltaExpr); + } +}); + Module['MemorySize'] = makeExpressionWrapper({}); Module['MemoryGrow'] = makeExpressionWrapper({ diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index fee59ce3b..96a1abe73 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -1856,6 +1856,10 @@ struct PrintExpressionContents printMedium(o, "table.size "); printName(curr->table, o); } + void visitTableGrow(TableGrow* curr) { + printMedium(o, "table.grow "); + printName(curr->table, o); + } void visitTry(Try* curr) { printMedium(o, "try"); if (curr->name.is()) { diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index 0f2c69e02..436e01885 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -131,6 +131,7 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> { void visitTableGet(TableGet* curr) { maybeAddTable(curr->table); } void visitTableSet(TableSet* curr) { maybeAddTable(curr->table); } void visitTableSize(TableSize* curr) { maybeAddTable(curr->table); } + void visitTableGrow(TableGrow* curr) { maybeAddTable(curr->table); } void visitThrow(Throw* curr) { maybeAdd(ModuleElement(ModuleElementKind::Tag, curr->tag)); } diff --git a/src/shell-interface.h b/src/shell-interface.h index 0c81680f5..6c6ddf7f2 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -223,27 +223,31 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface { memory.set<std::array<uint8_t, 16>>(addr, value); } - void tableStore(Name tableName, Address addr, const Literal& entry) override { + Index tableSize(Name tableName) override { + return (Index)tables[tableName].size(); + } + + void tableStore(Name tableName, Index index, const Literal& entry) override { auto& table = tables[tableName]; - if (addr >= table.size()) { + if (index >= table.size()) { trap("out of bounds table access"); } else { - table[addr] = entry; + table[index] = entry; } } - Literal tableLoad(Name tableName, Address addr) override { + Literal tableLoad(Name tableName, Index index) override { auto it = tables.find(tableName); if (it == tables.end()) { trap("tableGet on non-existing table"); } auto& table = it->second; - if (addr >= table.size()) { + if (index >= table.size()) { trap("out of bounds table access"); } - return table[addr]; + return table[index]; } bool growMemory(Address /*oldSize*/, Address newSize) override { @@ -256,6 +260,19 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface { return true; } + bool growTable(Name name, + const Literal& value, + Index /*oldSize*/, + Index newSize) override { + // Apply a reasonable limit on table size, 1GB, to avoid DOS on the + // interpreter. + if (newSize > 1024 * 1024 * 1024) { + return false; + } + tables[name].resize(newSize, value); + return true; + } + void trap(const char* why) override { std::cout << "[trap " << why << "]\n"; throw TrapException(); diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 189ce9f71..54324ae5a 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -343,17 +343,16 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface { std::to_string(index)); } - Literal tableGet(Name tableName, Index index) { - throw FailToEvalException("table.get: TODO"); + Index tableSize(Name tableName) override { + throw FailToEvalException("table size"); } - void tableSet(Name tableName, Index index, const Literal& value) { - throw FailToEvalException("table.set: TODO"); + Literal tableLoad(Name tableName, Index index) override { + throw FailToEvalException("table.get: TODO"); } - Literal tableSize(Name tableName) { - throw FailToEvalException("table.size: TODO"); - } + // called during initialization + void tableStore(Name tableName, Index index, const Literal& value) override {} int8_t load8s(Address addr) override { return doLoad<int8_t>(addr); } uint8_t load8u(Address addr) override { return doLoad<uint8_t>(addr); } @@ -377,12 +376,15 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface { doStore<int64_t>(addr, value); } - // called during initialization, but we don't keep track of a table - void tableStore(Name tableName, Address addr, const Literal& value) override { + bool growMemory(Address /*oldSize*/, Address /*newSize*/) override { + throw FailToEvalException("grow memory"); } - bool growMemory(Address /*oldSize*/, Address newSize) override { - throw FailToEvalException("grow memory"); + bool growTable(Name /*name*/, + const Literal& /*value*/, + Index /*oldSize*/, + Index /*newSize*/) override { + throw FailToEvalException("grow table"); } void trap(const char* why) override { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index ae2dd4656..e523d0994 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1025,6 +1025,7 @@ enum ASTNodes { // reference types opcodes + TableGrow = 0x0f, TableSize = 0x10, RefNull = 0xd0, RefIsNull = 0xd1, @@ -1637,6 +1638,7 @@ public: bool maybeVisitMemoryCopy(Expression*& out, uint32_t code); bool maybeVisitMemoryFill(Expression*& out, uint32_t code); bool maybeVisitTableSize(Expression*& out, uint32_t code); + bool maybeVisitTableGrow(Expression*& out, uint32_t code); bool maybeVisitI31New(Expression*& out, uint32_t code); bool maybeVisitI31Get(Expression*& out, uint32_t code); bool maybeVisitRefTest(Expression*& out, uint32_t code); @@ -1666,7 +1668,6 @@ public: void visitRefEq(RefEq* curr); void visitTableGet(TableGet* curr); void visitTableSet(TableSet* curr); - void visitTableSize(TableSize* curr); void visitTryOrTryInBlock(Expression*& out); void visitThrow(Throw* curr); void visitRethrow(Rethrow* curr); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 87167877a..b192afbac 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -670,6 +670,14 @@ public: ret->finalize(); return ret; } + TableGrow* makeTableGrow(Name table, Expression* value, Expression* delta) { + auto* ret = wasm.allocator.alloc<TableGrow>(); + ret->table = table; + ret->value = value; + ret->delta = delta; + ret->finalize(); + return ret; + } private: Try* makeTry(Name name, diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index dfb8aab4b..d1a2e1b83 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -522,6 +522,14 @@ switch (DELEGATE_ID) { DELEGATE_END(TableSize); break; } + case Expression::Id::TableGrowId: { + DELEGATE_START(TableGrow); + DELEGATE_FIELD_CHILD(TableGrow, delta); + DELEGATE_FIELD_CHILD(TableGrow, value); + DELEGATE_FIELD_NAME(TableGrow, table); + DELEGATE_END(TableGrow); + break; + } case Expression::Id::TryId: { DELEGATE_START(Try); DELEGATE_FIELD_SCOPE_NAME_USE(Try, delegateTarget); diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index 8d162dd90..c3f04674a 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -61,6 +61,7 @@ DELEGATE(RefEq); DELEGATE(TableGet); DELEGATE(TableSet); DELEGATE(TableSize); +DELEGATE(TableGrow); DELEGATE(Try); DELEGATE(Throw); DELEGATE(Rethrow); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 2179c4923..205c9f8cd 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1361,6 +1361,7 @@ public: Flow visitTableGet(TableGet* curr) { WASM_UNREACHABLE("unimp"); } Flow visitTableSet(TableSet* curr) { WASM_UNREACHABLE("unimp"); } Flow visitTableSize(TableSize* curr) { WASM_UNREACHABLE("unimp"); } + Flow visitTableGrow(TableGrow* curr) { WASM_UNREACHABLE("unimp"); } Flow visitTry(Try* curr) { WASM_UNREACHABLE("unimp"); } Flow visitThrow(Throw* curr) { NOTE_ENTER("Throw"); @@ -2172,6 +2173,10 @@ public: NOTE_ENTER("TableSize"); return Flow(NONCONSTANT_FLOW); } + Flow visitTableGrow(TableGrow* curr) { + NOTE_ENTER("TableGrow"); + return Flow(NONCONSTANT_FLOW); + } Flow visitLoad(Load* curr) { NOTE_ENTER("Load"); return Flow(NONCONSTANT_FLOW); @@ -2306,6 +2311,10 @@ public: Type result, SubType& instance) = 0; virtual bool growMemory(Address oldSize, Address newSize) = 0; + virtual bool growTable(Name name, + const Literal& value, + Index oldSize, + Index newSize) = 0; virtual void trap(const char* why) = 0; virtual void hostLimit(const char* why) = 0; virtual void throwException(const WasmException& exn) = 0; @@ -2452,11 +2461,12 @@ public: WASM_UNREACHABLE("unimp"); } - virtual void - tableStore(Name tableName, Address addr, const Literal& entry) { + virtual Index tableSize(Name tableName) = 0; + + virtual void tableStore(Name tableName, Index index, const Literal& entry) { WASM_UNREACHABLE("unimp"); } - virtual Literal tableLoad(Name tableName, Address addr) { + virtual Literal tableLoad(Name tableName, Index index) { WASM_UNREACHABLE("unimp"); } }; @@ -2805,25 +2815,61 @@ private: } Flow visitTableSet(TableSet* curr) { NOTE_ENTER("TableSet"); - Flow index = this->visit(curr->index); - if (index.breaking()) { - return index; + Flow indexFlow = this->visit(curr->index); + if (indexFlow.breaking()) { + return indexFlow; } - Flow flow = this->visit(curr->value); - if (flow.breaking()) { - return flow; + Flow valueFlow = this->visit(curr->value); + if (valueFlow.breaking()) { + return valueFlow; } auto info = instance.getTableInterfaceInfo(curr->table); - info.interface->tableStore( - info.name, index.getSingleValue().geti32(), flow.getSingleValue()); + info.interface->tableStore(info.name, + indexFlow.getSingleValue().geti32(), + valueFlow.getSingleValue()); return Flow(); } Flow visitTableSize(TableSize* curr) { NOTE_ENTER("TableSize"); - auto table = instance.wasm.getTable(curr->table); - // TODO: properly handle table size when TableGrow exists - return Literal::makeFromInt32(table->initial, Type::i32); + auto info = instance.getTableInterfaceInfo(curr->table); + Index tableSize = info.interface->tableSize(curr->table); + return Literal::makeFromInt32(tableSize, Type::i32); + } + + Flow visitTableGrow(TableGrow* curr) { + NOTE_ENTER("TableGrow"); + Flow valueFlow = this->visit(curr->value); + if (valueFlow.breaking()) { + return valueFlow; + } + Flow deltaFlow = this->visit(curr->delta); + if (deltaFlow.breaking()) { + return deltaFlow; + } + Name tableName = curr->table; + auto info = instance.getTableInterfaceInfo(tableName); + + Index tableSize = info.interface->tableSize(tableName); + Flow ret = Literal::makeFromInt32(tableSize, Type::i32); + Flow fail = Literal::makeFromInt32(-1, Type::i32); + Index delta = deltaFlow.getSingleValue().geti32(); + + if (tableSize >= uint32_t(-1) - delta) { + return fail; + } + auto maxTableSize = instance.self()->wasm.getTable(tableName)->max; + if (uint64_t(tableSize) + uint64_t(delta) > uint64_t(maxTableSize)) { + return fail; + } + Index newSize = tableSize + delta; + if (!info.interface->growTable( + tableName, valueFlow.getSingleValue(), tableSize, newSize)) { + // We failed to grow the table in practice, even though it was valid + // to try to do so. + return fail; + } + return ret; } Flow visitLocalGet(LocalGet* curr) { @@ -3546,7 +3592,6 @@ public: protected: Address memorySize; // in pages - static const Index maxDepth = 250; void trapIfGt(uint64_t lhs, uint64_t rhs, const char* msg) { diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index f5be7924f..d6f3b2f4e 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -268,6 +268,7 @@ private: Expression* makeTableGet(Element& s); Expression* makeTableSet(Element& s); Expression* makeTableSize(Element& s); + Expression* makeTableGrow(Element& s); Expression* makeTry(Element& s); Expression* makeTryOrCatchBody(Element& s, Type type, bool isTry); Expression* makeThrow(Element& s); diff --git a/src/wasm.h b/src/wasm.h index 197b8bd84..0c7b2a9ad 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -630,6 +630,7 @@ public: TableGetId, TableSetId, TableSizeId, + TableGrowId, TryId, ThrowId, RethrowId, @@ -1312,6 +1313,18 @@ public: void finalize(); }; +class TableGrow : public SpecificExpression<Expression::TableGrowId> { +public: + TableGrow() { type = Type::i32; } + TableGrow(MixedArena& allocator) : TableGrow() {} + + Name table; + Expression* value; + Expression* delta; + + void finalize(); +}; + class Try : public SpecificExpression<Expression::TryId> { public: Try(MixedArena& allocator) : catchTags(allocator), catchBodies(allocator) {} diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index e2dacdf70..9c8b83325 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2798,6 +2798,8 @@ void WasmBinaryBuilder::processNames() { set->table = getTableName(index); } else if (auto* size = ref->dynCast<TableSize>()) { size->table = getTableName(index); + } else if (auto* grow = ref->dynCast<TableGrow>()) { + grow->table = getTableName(index); } else { WASM_UNREACHABLE("Invalid type in table references"); } @@ -3621,6 +3623,9 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) { if (maybeVisitTableSize(curr, opcode)) { break; } + if (maybeVisitTableGrow(curr, opcode)) { + break; + } throwError("invalid code after misc prefix: " + std::to_string(opcode)); break; } @@ -4941,6 +4946,24 @@ bool WasmBinaryBuilder::maybeVisitTableSize(Expression*& out, uint32_t code) { return true; } +bool WasmBinaryBuilder::maybeVisitTableGrow(Expression*& out, uint32_t code) { + if (code != BinaryConsts::TableGrow) { + return false; + } + Index tableIdx = getU32LEB(); + if (tableIdx >= tables.size()) { + throwError("bad table index"); + } + auto* curr = allocator.alloc<TableGrow>(); + curr->delta = popNonVoidExpression(); + curr->value = popNonVoidExpression(); + curr->finalize(); + // Defer setting the table name for later, when we know it. + tableRefs[tableIdx].push_back(curr); + out = curr; + return true; +} + bool WasmBinaryBuilder::maybeVisitBinary(Expression*& out, uint8_t code) { Binary* curr; #define INT_TYPED_CODE(code) \ diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index cea666f29..1e15c1faa 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -2453,12 +2453,12 @@ Expression* SExpressionWasmBuilder::makeTableGet(Element& s) { Expression* SExpressionWasmBuilder::makeTableSet(Element& s) { auto tableName = s[1]->str(); - auto* index = parseExpression(s[2]); - auto* value = parseExpression(s[3]); auto* table = wasm.getTableOrNull(tableName); if (!table) { throw ParseException("invalid table name in table.set", s.line, s.col); } + auto* index = parseExpression(s[2]); + auto* value = parseExpression(s[3]); return Builder(wasm).makeTableSet(tableName, index, value); } @@ -2471,6 +2471,20 @@ Expression* SExpressionWasmBuilder::makeTableSize(Element& s) { return Builder(wasm).makeTableSize(tableName); } +Expression* SExpressionWasmBuilder::makeTableGrow(Element& s) { + auto tableName = s[1]->str(); + auto* table = wasm.getTableOrNull(tableName); + if (!table) { + throw ParseException("invalid table name in table.grow", s.line, s.col); + } + auto* value = parseExpression(s[2]); + if (!value->type.isRef()) { + throw ParseException("only reference types are valid for tables"); + } + auto* delta = parseExpression(s[3]); + return Builder(wasm).makeTableGrow(tableName, value, delta); +} + // try can be either in the form of try-catch or try-delegate. // try-catch is written in the folded wast format as // (try diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index f81a0b7a0..f5be5d2c5 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -1862,6 +1862,11 @@ void BinaryInstWriter::visitTableSize(TableSize* curr) { o << U32LEB(parent.getTableIndex(curr->table)); } +void BinaryInstWriter::visitTableGrow(TableGrow* curr) { + o << int8_t(BinaryConsts::MiscPrefix) << U32LEB(BinaryConsts::TableGrow); + o << U32LEB(parent.getTableIndex(curr->table)); +} + void BinaryInstWriter::visitTry(Try* curr) { breakStack.push_back(curr->name); o << int8_t(BinaryConsts::Try); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 0271884a4..f8669961c 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -367,6 +367,7 @@ public: void visitTableGet(TableGet* curr); void visitTableSet(TableSet* curr); void visitTableSize(TableSize* curr); + void visitTableGrow(TableGrow* curr); void noteDelegate(Name name, Expression* curr); void noteRethrow(Name name, Expression* curr); void visitTry(Try* curr); @@ -2072,6 +2073,24 @@ void FunctionValidator::visitTableSize(TableSize* curr) { shouldBeTrue(!!table, curr, "table.size table must exist"); } +void FunctionValidator::visitTableGrow(TableGrow* curr) { + shouldBeTrue(getModule()->features.hasReferenceTypes(), + curr, + "table.grow requires reference types to be enabled"); + auto* table = getModule()->getTableOrNull(curr->table); + if (shouldBeTrue(!!table, curr, "table.grow table must exist") && + curr->type != Type::unreachable) { + shouldBeSubType(curr->value->type, + table->type, + curr, + "table.grow value must have right type"); + shouldBeEqual(curr->delta->type, + Type(Type::i32), + curr, + "table.grow must match table index type"); + } +} + void FunctionValidator::noteDelegate(Name name, Expression* curr) { if (name != DELEGATE_CALLER_TARGET) { shouldBeTrue(delegateTargetNames.count(name) != 0, diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index c42a1089d..015df26ac 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -822,9 +822,9 @@ void RefNull::finalize() {} void RefIs::finalize() { if (value->type == Type::unreachable) { type = Type::unreachable; - return; + } else { + type = Type::i32; } - type = Type::i32; } void RefFunc::finalize() { @@ -861,6 +861,14 @@ void TableSize::finalize() { // Nothing to do - the type must have been set already during construction. } +void TableGrow::finalize() { + if (delta->type == Type::unreachable || value->type == Type::unreachable) { + type = Type::unreachable; + } else { + type = Type::i32; + } +} + void Try::finalize() { // If none of the component bodies' type is a supertype of the others, assume // the current type is already correct. TODO: Calculate a proper LUB. diff --git a/src/wasm2js.h b/src/wasm2js.h index 03d1fd1da..206f945b4 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2179,6 +2179,10 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitTableGrow(TableGrow* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } Ref visitTry(Try* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); diff --git a/test/binaryen.js/exception-handling.js.txt b/test/binaryen.js/exception-handling.js.txt index 09d8659a0..73e33799e 100644 --- a/test/binaryen.js/exception-handling.js.txt +++ b/test/binaryen.js/exception-handling.js.txt @@ -34,7 +34,7 @@ ) ) -getExpressionInfo(throw) = {"id":49,"type":1,"tag":"e"} -getExpressionInfo(rethrow) = {"id":50,"type":1,"target":"l0"} -getExpressionInfo(try_catch) = {"id":48,"type":1,"name":"l0","hasCatchAll":0,"delegateTarget":"","isDelegate":0} -getExpressionInfo(try_delegate) = {"id":48,"type":0,"name":"try_outer","hasCatchAll":1,"delegateTarget":"","isDelegate":0} +getExpressionInfo(throw) = {"id":50,"type":1,"tag":"e"} +getExpressionInfo(rethrow) = {"id":51,"type":1,"target":"l0"} +getExpressionInfo(try_catch) = {"id":49,"type":1,"name":"l0","hasCatchAll":0,"delegateTarget":"","isDelegate":0} +getExpressionInfo(try_delegate) = {"id":49,"type":0,"name":"try_outer","hasCatchAll":1,"delegateTarget":"","isDelegate":0} diff --git a/test/binaryen.js/kitchen-sink.js b/test/binaryen.js/kitchen-sink.js index 10d97cc0a..7288bb484 100644 --- a/test/binaryen.js/kitchen-sink.js +++ b/test/binaryen.js/kitchen-sink.js @@ -167,6 +167,7 @@ function test_ids() { console.log("TableGetId: " + binaryen.TableGetId); console.log("TableSetId: " + binaryen.TableSetId); console.log("TableSizeId: " + binaryen.TableSizeId); + console.log("TableGrowId: " + binaryen.TableGrowId); console.log("TryId: " + binaryen.TryId); console.log("ThrowId: " + binaryen.ThrowId); console.log("RethrowId: " + binaryen.RethrowId); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index f54991f0b..ed45591ea 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -91,27 +91,28 @@ RefEqId: 44 TableGetId: 45 TableSetId: 46 TableSizeId: 47 -TryId: 48 -ThrowId: 49 -RethrowId: 50 -TupleMakeId: 51 -TupleExtractId: 52 -I31NewId: 53 -I31GetId: 54 -CallRefId: 55 -RefTestId: 56 -RefCastId: 57 -BrOnId: 58 -RttCanonId: 59 -RttSubId: 60 -StructNewId: 61 -StructGetId: 62 -StructSetId: 63 -ArrayNewId: 64 -ArrayInitId: 65 -ArrayGetId: 66 -ArraySetId: 67 -ArrayLenId: 68 +TableGrowId: 48 +TryId: 49 +ThrowId: 50 +RethrowId: 51 +TupleMakeId: 52 +TupleExtractId: 53 +I31NewId: 54 +I31GetId: 55 +CallRefId: 56 +RefTestId: 57 +RefCastId: 58 +BrOnId: 59 +RttCanonId: 60 +RttSubId: 61 +StructNewId: 62 +StructGetId: 63 +StructSetId: 64 +ArrayNewId: 65 +ArrayInitId: 66 +ArrayGetId: 67 +ArraySetId: 68 +ArrayLenId: 69 getExpressionInfo={"id":15,"type":4,"op":6} (f32.neg (f32.const -33.61199951171875) diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index 327e20e1e..fb3fc87b0 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -1032,6 +1032,13 @@ void test_core() { const char* table = BinaryenTableSizeGetTable(tablesize); BinaryenTableSizeSetTable(tablesize, table); + BinaryenExpressionRef valueExpr = + BinaryenRefNull(module, BinaryenTypeFuncref()); + BinaryenExpressionRef sizeExpr = makeInt32(module, 0); + BinaryenExpressionRef growExpr = + BinaryenTableGrow(module, "0", valueExpr, sizeExpr); + BinaryenExpressionPrint(growExpr); + // Memory. One per module const char* segments[] = {"hello, world", "I am passive"}; diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index b1afb9954..f5e5d6d67 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -39,6 +39,10 @@ BinaryenFeatureAll: 32767 (i32.const 0) ) (table.size $0) +(table.grow $0 + (ref.null func) + (i32.const 0) +) (module (type $i32_i64_f32_f64_=>_i32 (func (param i32 i64 f32 f64) (result i32))) (type $i32_=>_none (func (param i32))) diff --git a/test/lit/table-operations.wast b/test/lit/table-operations.wast index dece2143a..ace31a9c7 100644 --- a/test/lit/table-operations.wast +++ b/test/lit/table-operations.wast @@ -14,11 +14,15 @@ ;; CHECK-BINARY: (type $none_=>_i32 (func (result i32))) + ;; CHECK-BINARY: (type $i32_=>_i32 (func (param i32) (result i32))) + ;; CHECK-BINARY: (table $table-1 1 1 funcref) ;; CHECK-TEXT: (type $none_=>_none (func)) ;; CHECK-TEXT: (type $none_=>_i32 (func (result i32))) + ;; CHECK-TEXT: (type $i32_=>_i32 (func (param i32) (result i32))) + ;; CHECK-TEXT: (table $table-1 1 1 funcref) (table $table-1 funcref (elem $foo) @@ -127,11 +131,29 @@ (func $get-table-size (result i32) (table.size $table-1) ) + + ;; CHECK-BINARY: (func $table-grow (param $sz i32) (result i32) + ;; CHECK-BINARY-NEXT: (table.grow $table-1 + ;; CHECK-BINARY-NEXT: (ref.null func) + ;; CHECK-BINARY-NEXT: (local.get $sz) + ;; CHECK-BINARY-NEXT: ) + ;; CHECK-BINARY-NEXT: ) + ;; CHECK-TEXT: (func $table-grow (param $sz i32) (result i32) + ;; CHECK-TEXT-NEXT: (table.grow $table-1 + ;; CHECK-TEXT-NEXT: (ref.null func) + ;; CHECK-TEXT-NEXT: (local.get $sz) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + (func $table-grow (param $sz i32) (result i32) + (table.grow $table-1 (ref.null func) (local.get $sz)) + ) ) ;; CHECK-NODEBUG: (type $none_=>_none (func)) ;; CHECK-NODEBUG: (type $none_=>_i32 (func (result i32))) +;; CHECK-NODEBUG: (type $i32_=>_i32 (func (param i32) (result i32))) + ;; CHECK-NODEBUG: (table $0 1 1 funcref) ;; CHECK-NODEBUG: (table $1 3 3 funcref) @@ -172,3 +194,10 @@ ;; CHECK-NODEBUG: (func $3 (result i32) ;; CHECK-NODEBUG-NEXT: (table.size $0) ;; CHECK-NODEBUG-NEXT: ) + +;; CHECK-NODEBUG: (func $4 (param $0 i32) (result i32) +;; CHECK-NODEBUG-NEXT: (table.grow $0 +;; CHECK-NODEBUG-NEXT: (ref.null func) +;; CHECK-NODEBUG-NEXT: (local.get $0) +;; CHECK-NODEBUG-NEXT: ) +;; CHECK-NODEBUG-NEXT: ) diff --git a/test/spec/table_grow.wast b/test/spec/table_grow.wast new file mode 100644 index 000000000..a3a25b071 --- /dev/null +++ b/test/spec/table_grow.wast @@ -0,0 +1,174 @@ +(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)) +) + +(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") + + +;; Reject growing to size outside i32 value range +;; TODO: parse error +;; (module +;; (table $t 0x10 funcref) +;; (elem declare func $f) +;; (func $f (export "grow") (result i32) +;; (table.grow $t (ref.func $f) (i32.const 0xffff_fff0)) +;; ) +;; ) + +;; (assert_return (invoke "grow") (i32.const -1)) + + +(module + (table $t 0 externref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null extern) (local.get 0)) + ) +) + +(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)) + + +(module + (table $t 0 10 externref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null extern) (local.get 0)) + ) +) + +(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)) + + +(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) + ) +) + +(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)) + + +;; Type errors + +(assert_invalid + (module + (table $t 0 externref) + (func $type-init-size-empty-vs-i32-externref (result i32) + (table.grow $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-size-empty-vs-i32 (result i32) + (table.grow $t (ref.null extern)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-init-empty-vs-externref (result i32) + (table.grow $t (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-size-f32-vs-i32 (result i32) + (table.grow $t (ref.null extern) (f32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 funcref) + (func $type-init-externref-vs-funcref (param $r externref) (result i32) + (table.grow $t (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 1 externref) + (func $type-result-i32-vs-empty + (table.grow $t (ref.null extern) (i32.const 0)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 1 externref) + (func $type-result-i32-vs-f32 (result f32) + (table.grow $t (ref.null extern) (i32.const 0)) + ) + ) + "type mismatch" +) diff --git a/test/spec/table_size.wast b/test/spec/table_size.wast index 5732ca1a8..ad293b5ee 100644 --- a/test/spec/table_size.wast +++ b/test/spec/table_size.wast @@ -1,5 +1,3 @@ -;; TODO: uncomment tests when table.grow is implemented - (module (table $t0 0 externref) (table $t1 1 externref) @@ -11,59 +9,59 @@ (func (export "size-t2") (result i32) (table.size $t2)) (func (export "size-t3") (result i32) (table.size $t3)) - ;; (func (export "grow-t0") (param $sz i32) - ;; (drop (table.grow $t0 (ref.null extern) (local.get $sz))) - ;; ) - ;; (func (export "grow-t1") (param $sz i32) - ;; (drop (table.grow $t1 (ref.null extern) (local.get $sz))) - ;; ) - ;; (func (export "grow-t2") (param $sz i32) - ;; (drop (table.grow $t2 (ref.null extern) (local.get $sz))) - ;; ) - ;; (func (export "grow-t3") (param $sz i32) - ;; (drop (table.grow $t3 (ref.null extern) (local.get $sz))) - ;; ) + (func (export "grow-t0") (param $sz i32) + (drop (table.grow $t0 (ref.null extern) (local.get $sz))) + ) + (func (export "grow-t1") (param $sz i32) + (drop (table.grow $t1 (ref.null extern) (local.get $sz))) + ) + (func (export "grow-t2") (param $sz i32) + (drop (table.grow $t2 (ref.null extern) (local.get $sz))) + ) + (func (export "grow-t3") (param $sz i32) + (drop (table.grow $t3 (ref.null extern) (local.get $sz))) + ) ) (assert_return (invoke "size-t0") (i32.const 0)) -;; (assert_return (invoke "grow-t0" (i32.const 1))) -;; (assert_return (invoke "size-t0") (i32.const 1)) -;; (assert_return (invoke "grow-t0" (i32.const 4))) -;; (assert_return (invoke "size-t0") (i32.const 5)) -;; (assert_return (invoke "grow-t0" (i32.const 0))) -;; (assert_return (invoke "size-t0") (i32.const 5)) +(assert_return (invoke "grow-t0" (i32.const 1))) +(assert_return (invoke "size-t0") (i32.const 1)) +(assert_return (invoke "grow-t0" (i32.const 4))) +(assert_return (invoke "size-t0") (i32.const 5)) +(assert_return (invoke "grow-t0" (i32.const 0))) +(assert_return (invoke "size-t0") (i32.const 5)) (assert_return (invoke "size-t1") (i32.const 1)) -;; (assert_return (invoke "grow-t1" (i32.const 1))) -;; (assert_return (invoke "size-t1") (i32.const 2)) -;; (assert_return (invoke "grow-t1" (i32.const 4))) -;; (assert_return (invoke "size-t1") (i32.const 6)) -;; (assert_return (invoke "grow-t1" (i32.const 0))) -;; (assert_return (invoke "size-t1") (i32.const 6)) +(assert_return (invoke "grow-t1" (i32.const 1))) +(assert_return (invoke "size-t1") (i32.const 2)) +(assert_return (invoke "grow-t1" (i32.const 4))) +(assert_return (invoke "size-t1") (i32.const 6)) +(assert_return (invoke "grow-t1" (i32.const 0))) +(assert_return (invoke "size-t1") (i32.const 6)) (assert_return (invoke "size-t2") (i32.const 0)) -;; (assert_return (invoke "grow-t2" (i32.const 3))) -;; (assert_return (invoke "size-t2") (i32.const 0)) -;; (assert_return (invoke "grow-t2" (i32.const 1))) -;; (assert_return (invoke "size-t2") (i32.const 1)) -;; (assert_return (invoke "grow-t2" (i32.const 0))) -;; (assert_return (invoke "size-t2") (i32.const 1)) -;; (assert_return (invoke "grow-t2" (i32.const 4))) -;; (assert_return (invoke "size-t2") (i32.const 1)) -;; (assert_return (invoke "grow-t2" (i32.const 1))) -;; (assert_return (invoke "size-t2") (i32.const 2)) +(assert_return (invoke "grow-t2" (i32.const 3))) +(assert_return (invoke "size-t2") (i32.const 0)) +(assert_return (invoke "grow-t2" (i32.const 1))) +(assert_return (invoke "size-t2") (i32.const 1)) +(assert_return (invoke "grow-t2" (i32.const 0))) +(assert_return (invoke "size-t2") (i32.const 1)) +(assert_return (invoke "grow-t2" (i32.const 4))) +(assert_return (invoke "size-t2") (i32.const 1)) +(assert_return (invoke "grow-t2" (i32.const 1))) +(assert_return (invoke "size-t2") (i32.const 2)) (assert_return (invoke "size-t3") (i32.const 3)) -;; (assert_return (invoke "grow-t3" (i32.const 1))) -;; (assert_return (invoke "size-t3") (i32.const 4)) -;; (assert_return (invoke "grow-t3" (i32.const 3))) -;; (assert_return (invoke "size-t3") (i32.const 7)) -;; (assert_return (invoke "grow-t3" (i32.const 0))) -;; (assert_return (invoke "size-t3") (i32.const 7)) -;; (assert_return (invoke "grow-t3" (i32.const 2))) -;; (assert_return (invoke "size-t3") (i32.const 7)) -;; (assert_return (invoke "grow-t3" (i32.const 1))) -;; (assert_return (invoke "size-t3") (i32.const 8)) +(assert_return (invoke "grow-t3" (i32.const 1))) +(assert_return (invoke "size-t3") (i32.const 4)) +(assert_return (invoke "grow-t3" (i32.const 3))) +(assert_return (invoke "size-t3") (i32.const 7)) +(assert_return (invoke "grow-t3" (i32.const 0))) +(assert_return (invoke "size-t3") (i32.const 7)) +(assert_return (invoke "grow-t3" (i32.const 2))) +(assert_return (invoke "size-t3") (i32.const 7)) +(assert_return (invoke "grow-t3" (i32.const 1))) +(assert_return (invoke "size-t3") (i32.const 8)) ;; Type errors |