summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/gen-s-parser.py2
-rw-r--r--src/binaryen-c.cpp49
-rw-r--r--src/binaryen-c.h27
-rw-r--r--src/gen-s-parser.inc14
-rw-r--r--src/ir/ReFinalize.cpp1
-rw-r--r--src/ir/cost.h3
-rw-r--r--src/ir/effects.h10
-rw-r--r--src/js/binaryen.js-post.js33
-rw-r--r--src/passes/Print.cpp4
-rw-r--r--src/passes/RemoveUnusedModuleElements.cpp1
-rw-r--r--src/shell-interface.h29
-rw-r--r--src/tools/wasm-ctor-eval.cpp24
-rw-r--r--src/wasm-binary.h3
-rw-r--r--src/wasm-builder.h8
-rw-r--r--src/wasm-delegations-fields.def8
-rw-r--r--src/wasm-delegations.def1
-rw-r--r--src/wasm-interpreter.h75
-rw-r--r--src/wasm-s-parser.h1
-rw-r--r--src/wasm.h13
-rw-r--r--src/wasm/wasm-binary.cpp23
-rw-r--r--src/wasm/wasm-s-parser.cpp18
-rw-r--r--src/wasm/wasm-stack.cpp5
-rw-r--r--src/wasm/wasm-validator.cpp19
-rw-r--r--src/wasm/wasm.cpp12
-rw-r--r--src/wasm2js.h4
-rw-r--r--test/binaryen.js/exception-handling.js.txt8
-rw-r--r--test/binaryen.js/kitchen-sink.js1
-rw-r--r--test/binaryen.js/kitchen-sink.js.txt43
-rw-r--r--test/example/c-api-kitchen-sink.c7
-rw-r--r--test/example/c-api-kitchen-sink.txt4
-rw-r--r--test/lit/table-operations.wast29
-rw-r--r--test/spec/table_grow.wast174
-rw-r--r--test/spec/table_size.wast90
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