summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Graey <maxgraey@gmail.com>2021-10-07 22:43:30 +0300
committerGitHub <noreply@github.com>2021-10-07 12:43:30 -0700
commit2dff27c086e8f2a9913096ebf3dc93e97051d85a (patch)
tree3d689a659ad2339639fc4b2e73753b51e6d9295c
parentff68bca64cd669aff59c7e1cc0a6677d267a410f (diff)
downloadbinaryen-2dff27c086e8f2a9913096ebf3dc93e97051d85a.tar.gz
binaryen-2dff27c086e8f2a9913096ebf3dc93e97051d85a.tar.bz2
binaryen-2dff27c086e8f2a9913096ebf3dc93e97051d85a.zip
Add table.set operation (#4215)
-rwxr-xr-xscripts/gen-s-parser.py10
-rw-r--r--src/binaryen-c.cpp86
-rw-r--r--src/binaryen-c.h45
-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.h44
-rw-r--r--src/js/binaryen.js-post.js64
-rw-r--r--src/passes/Directize.cpp1
-rw-r--r--src/passes/Print.cpp4
-rw-r--r--src/passes/RemoveUnusedModuleElements.cpp1
-rw-r--r--src/shell-interface.h3
-rw-r--r--src/tools/wasm-ctor-eval.cpp4
-rw-r--r--src/wasm-binary.h2
-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.h21
-rw-r--r--src/wasm-s-parser.h1
-rw-r--r--src/wasm.h13
-rw-r--r--src/wasm/wasm-binary.cpp18
-rw-r--r--src/wasm/wasm-s-parser.cpp11
-rw-r--r--src/wasm/wasm-stack.cpp5
-rw-r--r--src/wasm/wasm-validator.cpp17
-rw-r--r--src/wasm/wasm.cpp8
-rw-r--r--src/wasm2js.h4
-rw-r--r--test/binaryen.js/exception-handling.js.txt8
-rw-r--r--test/binaryen.js/kitchen-sink.js2
-rw-r--r--test/binaryen.js/kitchen-sink.js.txt44
-rw-r--r--test/binaryen.js/sideffects.js2
-rw-r--r--test/binaryen.js/sideffects.js.txt14
-rw-r--r--test/example/c-api-kitchen-sink.c14
-rw-r--r--test/example/c-api-kitchen-sink.txt7
-rw-r--r--test/lit/table-operations.wast46
-rw-r--r--test/spec/table_set.wast119
35 files changed, 603 insertions, 50 deletions
diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py
index 0c71644aa..b296d85ac 100755
--- a/scripts/gen-s-parser.py
+++ b/scripts/gen-s-parser.py
@@ -520,8 +520,16 @@ instructions = [
("ref.is_null", "makeRefIs(s, RefIsNull)"),
("ref.func", "makeRefFunc(s)"),
("ref.eq", "makeRefEq(s)"),
- # TODO Add table instructions
+ # table instructions
("table.get", "makeTableGet(s)"),
+ ("table.set", "makeTableSet(s)"),
+ # TODO:
+ # table.init
+ # table.fill
+ # table.copy
+ # table.grow
+ # table.size
+ #
# exception handling instructions
("try", "makeTry(s)"),
("throw", "makeThrow(s)"),
diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp
index 72598d0dc..ef6c3abdd 100644
--- a/src/binaryen-c.cpp
+++ b/src/binaryen-c.cpp
@@ -1287,6 +1287,24 @@ BinaryenExpressionRef BinaryenRefEq(BinaryenModuleRef module,
Builder(*(Module*)module).makeRefEq((Expression*)left, (Expression*)right));
}
+BinaryenExpressionRef BinaryenTableGet(BinaryenModuleRef module,
+ const char* name,
+ BinaryenExpressionRef index,
+ BinaryenType type) {
+ return static_cast<Expression*>(
+ Builder(*(Module*)module)
+ .makeTableGet(name, (Expression*)index, Type(type)));
+}
+
+BinaryenExpressionRef BinaryenTableSet(BinaryenModuleRef module,
+ const char* name,
+ BinaryenExpressionRef index,
+ BinaryenExpressionRef value) {
+ return static_cast<Expression*>(
+ Builder(*(Module*)module)
+ .makeTableSet(name, (Expression*)index, (Expression*)value));
+}
+
BinaryenExpressionRef BinaryenTry(BinaryenModuleRef module,
const char* name,
BinaryenExpressionRef body,
@@ -1872,6 +1890,66 @@ void BinaryenGlobalSetSetValue(BinaryenExpressionRef expr,
assert(valueExpr);
static_cast<GlobalSet*>(expression)->value = (Expression*)valueExpr;
}
+// TableGet
+const char* BinaryenTableGetGetTable(BinaryenExpressionRef expr) {
+ auto* expression = (Expression*)expr;
+ assert(expression->is<TableGet>());
+ return static_cast<TableGet*>(expression)->table.c_str();
+}
+void BinaryenTableGetSetTable(BinaryenExpressionRef expr, const char* table) {
+ auto* expression = (Expression*)expr;
+ assert(expression->is<TableGet>());
+ assert(table);
+ static_cast<TableGet*>(expression)->table = table;
+}
+BinaryenExpressionRef BinaryenTableGetGetIndex(BinaryenExpressionRef expr) {
+ auto* expression = (Expression*)expr;
+ assert(expression->is<TableGet>());
+ return static_cast<TableGet*>(expression)->index;
+}
+void BinaryenTableGetSetIndex(BinaryenExpressionRef expr,
+ BinaryenExpressionRef indexExpr) {
+ auto* expression = (Expression*)expr;
+ assert(expression->is<TableGet>());
+ assert(indexExpr);
+ static_cast<TableGet*>(expression)->index = (Expression*)indexExpr;
+}
+// TableSet
+const char* BinaryenTableSetGetTable(BinaryenExpressionRef expr) {
+ auto* expression = (Expression*)expr;
+ assert(expression->is<TableSet>());
+ return static_cast<TableSet*>(expression)->table.c_str();
+}
+void BinaryenTableSetSetTable(BinaryenExpressionRef expr, const char* table) {
+ auto* expression = (Expression*)expr;
+ assert(expression->is<TableSet>());
+ assert(table);
+ static_cast<TableSet*>(expression)->table = table;
+}
+BinaryenExpressionRef BinaryenTableSetGetIndex(BinaryenExpressionRef expr) {
+ auto* expression = (Expression*)expr;
+ assert(expression->is<TableSet>());
+ return static_cast<TableSet*>(expression)->index;
+}
+void BinaryenTableSetSetIndex(BinaryenExpressionRef expr,
+ BinaryenExpressionRef indexExpr) {
+ auto* expression = (Expression*)expr;
+ assert(expression->is<TableSet>());
+ assert(indexExpr);
+ static_cast<TableSet*>(expression)->index = (Expression*)indexExpr;
+}
+BinaryenExpressionRef BinaryenTableSetGetValue(BinaryenExpressionRef expr) {
+ auto* expression = (Expression*)expr;
+ assert(expression->is<TableSet>());
+ return static_cast<TableSet*>(expression)->value;
+}
+void BinaryenTableSetSetValue(BinaryenExpressionRef expr,
+ BinaryenExpressionRef valueExpr) {
+ auto* expression = (Expression*)expr;
+ assert(expression->is<TableSet>());
+ assert(valueExpr);
+ static_cast<TableSet*>(expression)->value = (Expression*)valueExpr;
+}
// MemoryGrow
BinaryenExpressionRef BinaryenMemoryGrowGetDelta(BinaryenExpressionRef expr) {
auto* expression = (Expression*)expr;
@@ -4313,6 +4391,14 @@ BinaryenSideEffects BinaryenSideEffectWritesMemory(void) {
return static_cast<BinaryenSideEffects>(
EffectAnalyzer::SideEffects::WritesMemory);
}
+BinaryenSideEffects BinaryenSideEffectReadsTable(void) {
+ return static_cast<BinaryenSideEffects>(
+ EffectAnalyzer::SideEffects::ReadsTable);
+}
+BinaryenSideEffects BinaryenSideEffectWritesTable(void) {
+ return static_cast<BinaryenSideEffects>(
+ EffectAnalyzer::SideEffects::WritesTable);
+}
BinaryenSideEffects BinaryenSideEffectImplicitTrap(void) {
return static_cast<BinaryenSideEffects>(
EffectAnalyzer::SideEffects::ImplicitTrap);
diff --git a/src/binaryen-c.h b/src/binaryen-c.h
index f9c8719dd..d9e1afc8e 100644
--- a/src/binaryen-c.h
+++ b/src/binaryen-c.h
@@ -851,6 +851,15 @@ BINARYEN_API BinaryenExpressionRef BinaryenRefFunc(BinaryenModuleRef module,
BINARYEN_API BinaryenExpressionRef BinaryenRefEq(BinaryenModuleRef module,
BinaryenExpressionRef left,
BinaryenExpressionRef right);
+BINARYEN_API BinaryenExpressionRef BinaryenTableGet(BinaryenModuleRef module,
+ const char* name,
+ BinaryenExpressionRef index,
+ BinaryenType type);
+BINARYEN_API BinaryenExpressionRef
+BinaryenTableSet(BinaryenModuleRef module,
+ const char* name,
+ BinaryenExpressionRef index,
+ BinaryenExpressionRef value);
// Try: name can be NULL. delegateTarget should be NULL in try-catch.
BINARYEN_API BinaryenExpressionRef
BinaryenTry(BinaryenModuleRef module,
@@ -1195,6 +1204,40 @@ BinaryenGlobalSetGetValue(BinaryenExpressionRef expr);
BINARYEN_API void BinaryenGlobalSetSetValue(BinaryenExpressionRef expr,
BinaryenExpressionRef valueExpr);
+// TableGet
+
+// Gets the name of the table being accessed by a `table.get` expression.
+BINARYEN_API const char* BinaryenTableGetGetTable(BinaryenExpressionRef expr);
+// Sets the name of the table being accessed by a `table.get` expression.
+BINARYEN_API void BinaryenTableGetSetTable(BinaryenExpressionRef expr,
+ const char* table);
+// Gets the index expression of a `table.get` expression.
+BINARYEN_API BinaryenExpressionRef
+BinaryenTableGetGetIndex(BinaryenExpressionRef expr);
+// Sets the index expression of a `table.get` expression.
+BINARYEN_API void BinaryenTableGetSetIndex(BinaryenExpressionRef expr,
+ BinaryenExpressionRef indexExpr);
+
+// TableSet
+
+// Gets the name of the table being accessed by a `table.set` expression.
+BINARYEN_API const char* BinaryenTableSetGetTable(BinaryenExpressionRef expr);
+// Sets the name of the table being accessed by a `table.set` expression.
+BINARYEN_API void BinaryenTableSetSetTable(BinaryenExpressionRef expr,
+ const char* table);
+// Gets the index expression of a `table.set` expression.
+BINARYEN_API BinaryenExpressionRef
+BinaryenTableSetGetIndex(BinaryenExpressionRef expr);
+// Sets the index expression of a `table.set` expression.
+BINARYEN_API void BinaryenTableSetSetIndex(BinaryenExpressionRef expr,
+ BinaryenExpressionRef indexExpr);
+// Gets the value expression of a `table.set` expression.
+BINARYEN_API BinaryenExpressionRef
+BinaryenTableSetGetValue(BinaryenExpressionRef expr);
+// Sets the value expression of a `table.set` expression.
+BINARYEN_API void BinaryenTableSetSetValue(BinaryenExpressionRef expr,
+ BinaryenExpressionRef valueExpr);
+
// MemoryGrow
// Gets the delta of a `memory.grow` expression.
@@ -2625,6 +2668,8 @@ BINARYEN_API BinaryenSideEffects BinaryenSideEffectReadsGlobal(void);
BINARYEN_API BinaryenSideEffects BinaryenSideEffectWritesGlobal(void);
BINARYEN_API BinaryenSideEffects BinaryenSideEffectReadsMemory(void);
BINARYEN_API BinaryenSideEffects BinaryenSideEffectWritesMemory(void);
+BINARYEN_API BinaryenSideEffects BinaryenSideEffectReadsTable(void);
+BINARYEN_API BinaryenSideEffects BinaryenSideEffectWritesTable(void);
BINARYEN_API BinaryenSideEffects BinaryenSideEffectImplicitTrap(void);
BINARYEN_API BinaryenSideEffects BinaryenSideEffectTrapsNeverHappen(void);
BINARYEN_API BinaryenSideEffects BinaryenSideEffectIsAtomic(void);
diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc
index 984ebf6b2..cca1c3445 100644
--- a/src/gen-s-parser.inc
+++ b/src/gen-s-parser.inc
@@ -3041,9 +3041,17 @@ switch (op[0]) {
}
case 't': {
switch (op[1]) {
- case 'a':
- if (strcmp(op, "table.get") == 0) { return makeTableGet(s); }
- goto parse_error;
+ case 'a': {
+ switch (op[6]) {
+ case 'g':
+ if (strcmp(op, "table.get") == 0) { return makeTableGet(s); }
+ goto parse_error;
+ case 's':
+ if (strcmp(op, "table.set") == 0) { return makeTableSet(s); }
+ goto parse_error;
+ default: goto parse_error;
+ }
+ }
case 'h': {
switch (op[2]) {
case 'e':
diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp
index 4f9c7c849..0d107a759 100644
--- a/src/ir/ReFinalize.cpp
+++ b/src/ir/ReFinalize.cpp
@@ -136,6 +136,7 @@ void ReFinalize::visitRefFunc(RefFunc* curr) {
}
void ReFinalize::visitRefEq(RefEq* curr) { curr->finalize(); }
void ReFinalize::visitTableGet(TableGet* curr) { curr->finalize(); }
+void ReFinalize::visitTableSet(TableSet* 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 12797b0b5..7cf0de926 100644
--- a/src/ir/cost.h
+++ b/src/ir/cost.h
@@ -541,6 +541,9 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
return 1 + visit(curr->left) + visit(curr->right);
}
CostType visitTableGet(TableGet* curr) { return 1 + visit(curr->index); }
+ CostType visitTableSet(TableSet* curr) {
+ return 2 + visit(curr->index) + visit(curr->value);
+ }
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 22e1e8149..44b2cba94 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -75,6 +75,8 @@ public:
std::set<Name> globalsWritten;
bool readsMemory = false;
bool writesMemory = false;
+ bool readsTable = false;
+ bool writesTable = false;
// TODO: More specific type-based alias analysis, and not just at the
// struct/array level.
bool readsStruct = false;
@@ -129,6 +131,7 @@ public:
return globalsRead.size() + globalsWritten.size() > 0;
}
bool accessesMemory() const { return calls || readsMemory || writesMemory; }
+ bool accessesTable() const { return calls || readsTable || writesTable; }
bool accessesStruct() const { return calls || readsStruct || writesStruct; }
bool accessesArray() const { return calls || readsArray || writesArray; }
// Check whether this may transfer control flow to somewhere outside of this
@@ -144,12 +147,12 @@ public:
// Changes something in globally-stored state.
bool writesGlobalState() const {
- return globalsWritten.size() || writesMemory || writesStruct ||
- writesArray || isAtomic || calls;
+ return globalsWritten.size() || writesMemory || writesTable ||
+ writesStruct || writesArray || isAtomic || calls;
}
bool readsGlobalState() const {
- return globalsRead.size() || readsMemory || readsStruct || readsArray ||
- isAtomic || calls;
+ return globalsRead.size() || readsMemory || readsTable || readsStruct ||
+ readsArray || isAtomic || calls;
}
bool hasNonTrapSideEffects() const {
@@ -184,7 +187,7 @@ public:
}
bool hasAnything() const {
- return hasSideEffects() || accessesLocal() || readsMemory ||
+ return hasSideEffects() || accessesLocal() || readsMemory || readsTable ||
accessesGlobal();
}
@@ -198,6 +201,8 @@ public:
(other.transfersControlFlow() && hasSideEffects()) ||
((writesMemory || calls) && other.accessesMemory()) ||
((other.writesMemory || other.calls) && accessesMemory()) ||
+ ((writesTable || calls) && other.accessesTable()) ||
+ ((other.writesTable || other.calls) && accessesTable()) ||
((writesStruct || calls) && other.accessesStruct()) ||
((other.writesStruct || other.calls) && accessesStruct()) ||
((writesArray || calls) && other.accessesArray()) ||
@@ -261,6 +266,8 @@ public:
calls = calls || other.calls;
readsMemory = readsMemory || other.readsMemory;
writesMemory = writesMemory || other.writesMemory;
+ readsTable = readsTable || other.readsTable;
+ writesTable = writesTable || other.writesTable;
readsStruct = readsStruct || other.readsStruct;
writesStruct = writesStruct || other.writesStruct;
readsArray = readsArray || other.readsArray;
@@ -590,8 +597,11 @@ private:
void visitRefFunc(RefFunc* curr) {}
void visitRefEq(RefEq* curr) {}
void visitTableGet(TableGet* curr) {
- // TODO: track readsTable/writesTable, like memory?
- // Traps when the index is out of bounds for the table.
+ parent.readsTable = true;
+ parent.implicitTrap = true;
+ }
+ void visitTableSet(TableSet* curr) {
+ parent.writesTable = true;
parent.implicitTrap = true;
}
void visitTry(Try* curr) {}
@@ -708,12 +718,14 @@ public:
WritesGlobal = 1 << 5,
ReadsMemory = 1 << 6,
WritesMemory = 1 << 7,
- ImplicitTrap = 1 << 8,
- IsAtomic = 1 << 9,
- Throws = 1 << 10,
- DanglingPop = 1 << 11,
- TrapsNeverHappen = 1 << 12,
- Any = (1 << 13) - 1
+ ReadsTable = 1 << 8,
+ WritesTable = 1 << 9,
+ ImplicitTrap = 1 << 10,
+ IsAtomic = 1 << 11,
+ Throws = 1 << 12,
+ DanglingPop = 1 << 13,
+ TrapsNeverHappen = 1 << 14,
+ Any = (1 << 15) - 1
};
uint32_t getSideEffects() const {
uint32_t effects = 0;
@@ -741,6 +753,12 @@ public:
if (writesMemory) {
effects |= SideEffects::WritesMemory;
}
+ if (readsTable) {
+ effects |= SideEffects::ReadsTable;
+ }
+ if (writesTable) {
+ effects |= SideEffects::WritesTable;
+ }
if (implicitTrap) {
effects |= SideEffects::ImplicitTrap;
}
diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js
index 4c5f5ed3b..8fbd2b7ec 100644
--- a/src/js/binaryen.js-post.js
+++ b/src/js/binaryen.js-post.js
@@ -91,6 +91,8 @@ function initializeConstants() {
'RefIs',
'RefFunc',
'RefEq',
+ 'TableGet',
+ 'TableSet',
'Try',
'Throw',
'Rethrow',
@@ -549,6 +551,8 @@ function initializeConstants() {
'WritesGlobal',
'ReadsMemory',
'WritesMemory',
+ 'ReadsTable',
+ 'WritesTable',
'ImplicitTrap',
'IsAtomic',
'Throws',
@@ -661,6 +665,15 @@ function wrapModule(module, self = {}) {
}
}
+ self['table'] = {
+ 'get'(name, index, type) {
+ return Module['_BinaryenTableGet'](module, strToStack(name), index, type);
+ },
+ 'set'(name, index, value) {
+ return Module['_BinaryenTableSet'](module, strToStack(name), index, value);
+ }
+ }
+
self['memory'] = {
'size'() {
return Module['_BinaryenMemorySize'](module);
@@ -2797,6 +2810,21 @@ Module['getExpressionInfo'] = function(expr) {
'name': UTF8ToString(Module['_BinaryenGlobalSetGetName'](expr)),
'value': Module['_BinaryenGlobalSetGetValue'](expr)
};
+ case Module['TableGetId']:
+ return {
+ 'id': id,
+ 'type': type,
+ 'table': UTF8ToString(Module['_BinaryenTableGetGetTable'](expr)),
+ 'index': Module['_BinaryenTableGetGetIndex'](expr)
+ };
+ case Module['TableSetId']:
+ return {
+ 'id': id,
+ 'type': type,
+ 'table': UTF8ToString(Module['_BinaryenTableSetGetTable'](expr)),
+ 'index': Module['_BinaryenTableSetGetIndex'](expr),
+ 'value': Module['_BinaryenTableSetGetValue'](expr)
+ };
case Module['LoadId']:
return {
'id': id,
@@ -3767,6 +3795,42 @@ Module['GlobalSet'] = makeExpressionWrapper({
}
});
+Module['TableGet'] = makeExpressionWrapper({
+ 'getTable'(expr) {
+ return UTF8ToString(Module['_BinaryenTableGetGetTable'](expr));
+ },
+ 'setTable'(expr, name) {
+ preserveStack(() => { Module['_BinaryenTableGetSetTable'](expr, strToStack(name)) });
+ },
+ 'getIndex'(expr) {
+ return Module['_BinaryenTableGetGetIndex'](expr);
+ },
+ 'setIndex'(expr, indexExpr) {
+ Module['_BinaryenTableGetSetIndex'](expr, indexExpr);
+ }
+});
+
+Module['TableSet'] = makeExpressionWrapper({
+ 'getTable'(expr) {
+ return UTF8ToString(Module['_BinaryenTableSetGetTable'](expr));
+ },
+ 'setTable'(expr, name) {
+ preserveStack(() => { Module['_BinaryenTableSetSetTable'](expr, strToStack(name)) });
+ },
+ 'getIndex'(expr) {
+ return Module['_BinaryenTableSetGetIndex'](expr);
+ },
+ 'setIndex'(expr, indexExpr) {
+ Module['_BinaryenTableSetSetIndex'](expr, indexExpr);
+ },
+ 'getValue'(expr) {
+ return Module['_BinaryenTableSetGetValue'](expr);
+ },
+ 'setValue'(expr, valueExpr) {
+ Module['_BinaryenTableSetSetValue'](expr, valueExpr);
+ }
+});
+
Module['MemorySize'] = makeExpressionWrapper({});
Module['MemoryGrow'] = makeExpressionWrapper({
diff --git a/src/passes/Directize.cpp b/src/passes/Directize.cpp
index 5a9b1b984..92953c47b 100644
--- a/src/passes/Directize.cpp
+++ b/src/passes/Directize.cpp
@@ -171,6 +171,7 @@ private:
}
};
+// TODO: handle table.get / table.set here as well
struct Directize : public Pass {
void run(PassRunner* runner, Module* module) override {
std::unordered_map<Name, TableUtils::FlatTable> validTables;
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index 4d6207b01..a5db73132 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -1846,6 +1846,10 @@ struct PrintExpressionContents
printMedium(o, "table.get ");
printName(curr->table, o);
}
+ void visitTableSet(TableSet* curr) {
+ printMedium(o, "table.set ");
+ 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 7436676ef..32db977ec 100644
--- a/src/passes/RemoveUnusedModuleElements.cpp
+++ b/src/passes/RemoveUnusedModuleElements.cpp
@@ -129,6 +129,7 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
maybeAdd(ModuleElement(ModuleElementKind::Function, curr->func));
}
void visitTableGet(TableGet* curr) { maybeAddTable(curr->table); }
+ void visitTableSet(TableSet* 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 66608c37c..0c81680f5 100644
--- a/src/shell-interface.h
+++ b/src/shell-interface.h
@@ -233,7 +233,6 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface {
}
Literal tableLoad(Name tableName, Address addr) override {
-
auto it = tables.find(tableName);
if (it == tables.end()) {
trap("tableGet on non-existing table");
@@ -241,7 +240,7 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface {
auto& table = it->second;
if (addr >= table.size()) {
- trap("tableGet overflow");
+ trap("out of bounds table access");
}
return table[addr];
diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp
index 61897dc60..a0ae99a8b 100644
--- a/src/tools/wasm-ctor-eval.cpp
+++ b/src/tools/wasm-ctor-eval.cpp
@@ -347,6 +347,10 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface {
throw FailToEvalException("table.get: TODO");
}
+ void tableSet(Name tableName, Index index, const Literal& value) {
+ throw FailToEvalException("table.set: TODO");
+ }
+
int8_t load8s(Address addr) override { return doLoad<int8_t>(addr); }
uint8_t load8u(Address addr) override { return doLoad<uint8_t>(addr); }
int16_t load16s(Address addr) override { return doLoad<int16_t>(addr); }
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index c0225c270..b211c536c 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -475,6 +475,7 @@ enum ASTNodes {
GlobalSet = 0x24,
TableGet = 0x25,
+ TableSet = 0x26,
I32LoadMem = 0x28,
I64LoadMem = 0x29,
@@ -1662,6 +1663,7 @@ public:
void visitRefFunc(RefFunc* curr);
void visitRefEq(RefEq* curr);
void visitTableGet(TableGet* curr);
+ void visitTableSet(TableSet* 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 d1cb5f16d..467edf1bd 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -656,6 +656,14 @@ public:
ret->finalize();
return ret;
}
+ TableSet* makeTableSet(Name table, Expression* index, Expression* value) {
+ auto* ret = wasm.allocator.alloc<TableSet>();
+ ret->table = table;
+ ret->index = index;
+ ret->value = value;
+ ret->finalize();
+ return ret;
+ }
private:
Try* makeTry(Name name,
diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def
index c214b5b6b..b6828fbc2 100644
--- a/src/wasm-delegations-fields.def
+++ b/src/wasm-delegations-fields.def
@@ -508,6 +508,14 @@ switch (DELEGATE_ID) {
DELEGATE_END(TableGet);
break;
}
+ case Expression::Id::TableSetId: {
+ DELEGATE_START(TableSet);
+ DELEGATE_FIELD_CHILD(TableSet, value);
+ DELEGATE_FIELD_CHILD(TableSet, index);
+ DELEGATE_FIELD_NAME(TableSet, table);
+ DELEGATE_END(TableSet);
+ 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 873eb2902..c040dd551 100644
--- a/src/wasm-delegations.def
+++ b/src/wasm-delegations.def
@@ -59,6 +59,7 @@ DELEGATE(RefIs);
DELEGATE(RefFunc);
DELEGATE(RefEq);
DELEGATE(TableGet);
+DELEGATE(TableSet);
DELEGATE(Try);
DELEGATE(Throw);
DELEGATE(Rethrow);
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index 6ea9f9c9e..2952869c6 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -1359,6 +1359,7 @@ public:
return Literal(int32_t(left == right));
}
Flow visitTableGet(TableGet* curr) { WASM_UNREACHABLE("unimp"); }
+ Flow visitTableSet(TableSet* curr) { WASM_UNREACHABLE("unimp"); }
Flow visitTry(Try* curr) { WASM_UNREACHABLE("unimp"); }
Flow visitThrow(Throw* curr) {
NOTE_ENTER("Throw");
@@ -2162,6 +2163,10 @@ public:
NOTE_ENTER("TableGet");
return Flow(NONCONSTANT_FLOW);
}
+ Flow visitTableSet(TableSet* curr) {
+ NOTE_ENTER("TableSet");
+ return Flow(NONCONSTANT_FLOW);
+ }
Flow visitLoad(Load* curr) {
NOTE_ENTER("Load");
return Flow(NONCONSTANT_FLOW);
@@ -2794,6 +2799,22 @@ private:
index.getSingleValue().geti32());
}
+ Flow visitTableSet(TableSet* curr) {
+ NOTE_ENTER("TableSet");
+ Flow index = this->visit(curr->index);
+ if (index.breaking()) {
+ return index;
+ }
+ Flow flow = this->visit(curr->value);
+ if (flow.breaking()) {
+ return flow;
+ }
+ auto info = instance.getTableInterfaceInfo(curr->table);
+ info.interface->tableStore(
+ info.name, index.getSingleValue().geti32(), flow.getSingleValue());
+ return Flow();
+ }
+
Flow visitLocalGet(LocalGet* curr) {
NOTE_ENTER("LocalGet");
auto index = curr->index;
diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h
index 693b15ea2..942eae19b 100644
--- a/src/wasm-s-parser.h
+++ b/src/wasm-s-parser.h
@@ -266,6 +266,7 @@ private:
Expression* makeRefFunc(Element& s);
Expression* makeRefEq(Element& s);
Expression* makeTableGet(Element& s);
+ Expression* makeTableSet(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 697931082..fbec9247a 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -625,6 +625,7 @@ public:
RefFuncId,
RefEqId,
TableGetId,
+ TableSetId,
TryId,
ThrowId,
RethrowId,
@@ -1276,6 +1277,18 @@ public:
void finalize();
};
+class TableSet : public SpecificExpression<Expression::TableSetId> {
+public:
+ TableSet(MixedArena& allocator) {}
+
+ Name table;
+
+ Expression* index;
+ Expression* value;
+
+ 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 04dad3c9c..7e72b056f 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -2791,6 +2791,8 @@ void WasmBinaryBuilder::processNames() {
callIndirect->table = getTableName(index);
} else if (auto* get = ref->dynCast<TableGet>()) {
get->table = getTableName(index);
+ } else if (auto* set = ref->dynCast<TableSet>()) {
+ set->table = getTableName(index);
} else {
WASM_UNREACHABLE("Invalid type in table references");
}
@@ -3524,6 +3526,9 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
case BinaryConsts::TableGet:
visitTableGet((curr = allocator.alloc<TableGet>())->cast<TableGet>());
break;
+ case BinaryConsts::TableSet:
+ visitTableSet((curr = allocator.alloc<TableSet>())->cast<TableSet>());
+ break;
case BinaryConsts::Try:
visitTryOrTryInBlock(curr);
break;
@@ -6217,6 +6222,19 @@ void WasmBinaryBuilder::visitTableGet(TableGet* curr) {
tableRefs[tableIdx].push_back(curr);
}
+void WasmBinaryBuilder::visitTableSet(TableSet* curr) {
+ BYN_TRACE("zz node: TableSet\n");
+ Index tableIdx = getU32LEB();
+ if (tableIdx >= tables.size()) {
+ throwError("bad table index");
+ }
+ curr->value = popNonVoidExpression();
+ curr->index = popNonVoidExpression();
+ curr->finalize();
+ // Defer setting the table name for later, when we know it.
+ tableRefs[tableIdx].push_back(curr);
+}
+
void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
BYN_TRACE("zz node: Try\n");
auto* curr = allocator.alloc<Try>();
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index 573699eae..417a0ac0e 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -2427,6 +2427,17 @@ Expression* SExpressionWasmBuilder::makeTableGet(Element& s) {
return Builder(wasm).makeTableGet(tableName, index, table->type);
}
+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);
+ }
+ return Builder(wasm).makeTableSet(tableName, index, value);
+}
+
// 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 58bd29f6e..b74b73ba1 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -1852,6 +1852,11 @@ void BinaryInstWriter::visitTableGet(TableGet* curr) {
o << U32LEB(parent.getTableIndex(curr->table));
}
+void BinaryInstWriter::visitTableSet(TableSet* curr) {
+ o << int8_t(BinaryConsts::TableSet);
+ 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 fae95980f..0e362df9c 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -365,6 +365,7 @@ public:
void visitRefFunc(RefFunc* curr);
void visitRefEq(RefEq* curr);
void visitTableGet(TableGet* curr);
+ void visitTableSet(TableSet* curr);
void noteDelegate(Name name, Expression* curr);
void noteRethrow(Name name, Expression* curr);
void visitTry(Try* curr);
@@ -2046,6 +2047,22 @@ void FunctionValidator::visitTableGet(TableGet* curr) {
}
}
+void FunctionValidator::visitTableSet(TableSet* curr) {
+ shouldBeTrue(getModule()->features.hasReferenceTypes(),
+ curr,
+ "table.set requires reference types to be enabled");
+ shouldBeEqualOrFirstIsUnreachable(
+ curr->index->type, Type(Type::i32), curr, "table.set index must be an i32");
+ auto* table = getModule()->getTableOrNull(curr->table);
+ if (shouldBeTrue(!!table, curr, "table.set table must exist") &&
+ curr->type != Type::unreachable) {
+ shouldBeSubType(curr->value->type,
+ table->type,
+ curr,
+ "table.set value must have right 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 19ba5f265..26e6b369b 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -825,6 +825,14 @@ void TableGet::finalize() {
// Otherwise, the type should have been set already.
}
+void TableSet::finalize() {
+ if (index->type == Type::unreachable || value->type == Type::unreachable) {
+ type = Type::unreachable;
+ } else {
+ type = Type::none;
+ }
+}
+
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 ef6c9af36..ac7e94421 100644
--- a/src/wasm2js.h
+++ b/src/wasm2js.h
@@ -2171,6 +2171,10 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m,
unimplemented(curr);
WASM_UNREACHABLE("unimp");
}
+ Ref visitTableSet(TableSet* 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 a1a1879bd..75ca8b5cb 100644
--- a/test/binaryen.js/exception-handling.js.txt
+++ b/test/binaryen.js/exception-handling.js.txt
@@ -34,7 +34,7 @@
)
)
-getExpressionInfo(throw) = {"id":47,"type":1,"tag":"e"}
-getExpressionInfo(rethrow) = {"id":48,"type":1,"target":"l0"}
-getExpressionInfo(try_catch) = {"id":46,"type":1,"name":"l0","hasCatchAll":0,"delegateTarget":"","isDelegate":0}
-getExpressionInfo(try_delegate) = {"id":46,"type":0,"name":"try_outer","hasCatchAll":1,"delegateTarget":"","isDelegate":0}
+getExpressionInfo(throw) = {"id":48,"type":1,"tag":"e"}
+getExpressionInfo(rethrow) = {"id":49,"type":1,"target":"l0"}
+getExpressionInfo(try_catch) = {"id":47,"type":1,"name":"l0","hasCatchAll":0,"delegateTarget":"","isDelegate":0}
+getExpressionInfo(try_delegate) = {"id":47,"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 be7363ae5..f2da5d0a0 100644
--- a/test/binaryen.js/kitchen-sink.js
+++ b/test/binaryen.js/kitchen-sink.js
@@ -164,6 +164,8 @@ function test_ids() {
console.log("RefIsId: " + binaryen.RefIsId);
console.log("RefFuncId: " + binaryen.RefFuncId);
console.log("RefEqId: " + binaryen.RefEqId);
+ console.log("TableGetId: " + binaryen.TableGetId);
+ console.log("TableSetId: " + binaryen.TableSetId);
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 f971e1a07..54a18cd54 100644
--- a/test/binaryen.js/kitchen-sink.js.txt
+++ b/test/binaryen.js/kitchen-sink.js.txt
@@ -88,27 +88,29 @@ RefNullId: 41
RefIsId: 42
RefFuncId: 43
RefEqId: 44
-TryId: 46
-ThrowId: 47
-RethrowId: 48
-TupleMakeId: 49
-TupleExtractId: 50
-I31NewId: 51
-I31GetId: 52
-CallRefId: 53
-RefTestId: 54
-RefCastId: 55
-BrOnId: 56
-RttCanonId: 57
-RttSubId: 58
-StructNewId: 59
-StructGetId: 60
-StructSetId: 61
-ArrayNewId: 62
-ArrayInitId: 63
-ArrayGetId: 64
-ArraySetId: 65
-ArrayLenId: 66
+TableGetId: 45
+TableSetId: 46
+TryId: 47
+ThrowId: 48
+RethrowId: 49
+TupleMakeId: 50
+TupleExtractId: 51
+I31NewId: 52
+I31GetId: 53
+CallRefId: 54
+RefTestId: 55
+RefCastId: 56
+BrOnId: 57
+RttCanonId: 58
+RttSubId: 59
+StructNewId: 60
+StructGetId: 61
+StructSetId: 62
+ArrayNewId: 63
+ArrayInitId: 64
+ArrayGetId: 65
+ArraySetId: 66
+ArrayLenId: 67
getExpressionInfo={"id":15,"type":4,"op":6}
(f32.neg
(f32.const -33.61199951171875)
diff --git a/test/binaryen.js/sideffects.js b/test/binaryen.js/sideffects.js
index eb6f4fc63..654c51608 100644
--- a/test/binaryen.js/sideffects.js
+++ b/test/binaryen.js/sideffects.js
@@ -7,6 +7,8 @@ console.log("SideEffects.ReadsGlobal=" + binaryen.SideEffects.ReadsGlobal);
console.log("SideEffects.WritesGlobal=" + binaryen.SideEffects.WritesGlobal);
console.log("SideEffects.ReadsMemory=" + binaryen.SideEffects.ReadsMemory);
console.log("SideEffects.WritesMemory=" + binaryen.SideEffects.WritesMemory);
+console.log("SideEffects.ReadsTable=" + binaryen.SideEffects.ReadsTable);
+console.log("SideEffects.WritesTable=" + binaryen.SideEffects.WritesTable);
console.log("SideEffects.ImplicitTrap=" + binaryen.SideEffects.ImplicitTrap);
console.log("SideEffects.IsAtomic=" + binaryen.SideEffects.IsAtomic);
console.log("SideEffects.Throws=" + binaryen.SideEffects.Throws);
diff --git a/test/binaryen.js/sideffects.js.txt b/test/binaryen.js/sideffects.js.txt
index 8e511d089..b582d70bd 100644
--- a/test/binaryen.js/sideffects.js.txt
+++ b/test/binaryen.js/sideffects.js.txt
@@ -7,9 +7,11 @@ SideEffects.ReadsGlobal=16
SideEffects.WritesGlobal=32
SideEffects.ReadsMemory=64
SideEffects.WritesMemory=128
-SideEffects.ImplicitTrap=256
-SideEffects.IsAtomic=512
-SideEffects.Throws=1024
-SideEffects.DanglingPop=2048
-SideEffects.TrapsNeverHappen=4096
-SideEffects.Any=8191
+SideEffects.ReadsTable=256
+SideEffects.WritesTable=512
+SideEffects.ImplicitTrap=1024
+SideEffects.IsAtomic=2048
+SideEffects.Throws=4096
+SideEffects.DanglingPop=8192
+SideEffects.TrapsNeverHappen=16384
+SideEffects.Any=32767
diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c
index 5db1d28dc..894fc1454 100644
--- a/test/example/c-api-kitchen-sink.c
+++ b/test/example/c-api-kitchen-sink.c
@@ -1012,6 +1012,20 @@ void test_core() {
BinaryenAddPassiveElementSegment(module, "p2", funcNames, 1);
BinaryenRemoveElementSegment(module, "p2");
+ BinaryenExpressionRef funcrefExpr1 =
+ BinaryenRefFunc(module, "kitchen()sinker", BinaryenTypeFuncref());
+
+ BinaryenExpressionPrint(BinaryenTableSet(
+ module, "0", BinaryenConst(module, BinaryenLiteralInt32(0)), funcrefExpr1));
+
+ BinaryenExpressionRef funcrefExpr2 =
+ BinaryenTableGet(module,
+ "0",
+ BinaryenConst(module, BinaryenLiteralInt32(0)),
+ BinaryenTypeFuncref());
+
+ BinaryenExpressionPrint(funcrefExpr2);
+
// 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 3372ba8b6..c28e0536d 100644
--- a/test/example/c-api-kitchen-sink.txt
+++ b/test/example/c-api-kitchen-sink.txt
@@ -31,6 +31,13 @@ BinaryenFeatureAll: 32767
(f32.neg
(f32.const -33.61199951171875)
)
+(table.set $0
+ (i32.const 0)
+ (ref.func "$kitchen()sinker")
+)
+(table.get $0
+ (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 6e303b495..555708c07 100644
--- a/test/lit/table-operations.wast
+++ b/test/lit/table-operations.wast
@@ -79,6 +79,40 @@
)
)
)
+
+ ;; CHECK-BINARY: (func $set-get
+ ;; CHECK-BINARY-NEXT: (table.set $table-1
+ ;; CHECK-BINARY-NEXT: (i32.const 0)
+ ;; CHECK-BINARY-NEXT: (ref.func $foo)
+ ;; CHECK-BINARY-NEXT: )
+ ;; CHECK-BINARY-NEXT: (drop
+ ;; CHECK-BINARY-NEXT: (table.get $table-1
+ ;; CHECK-BINARY-NEXT: (i32.const 0)
+ ;; CHECK-BINARY-NEXT: )
+ ;; CHECK-BINARY-NEXT: )
+ ;; CHECK-BINARY-NEXT: )
+ ;; CHECK-TEXT: (func $set-get
+ ;; CHECK-TEXT-NEXT: (table.set $table-1
+ ;; CHECK-TEXT-NEXT: (i32.const 0)
+ ;; CHECK-TEXT-NEXT: (ref.func $foo)
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: (drop
+ ;; CHECK-TEXT-NEXT: (table.get $table-1
+ ;; CHECK-TEXT-NEXT: (i32.const 0)
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ ;; CHECK-TEXT-NEXT: )
+ (func $set-get
+ (table.set $table-1
+ (i32.const 0)
+ (ref.func $foo)
+ )
+ (drop
+ (table.get $table-1
+ (i32.const 0)
+ )
+ )
+ )
)
;; CHECK-NODEBUG: (type $none_=>_none (func))
@@ -106,3 +140,15 @@
;; CHECK-NODEBUG-NEXT: )
;; CHECK-NODEBUG-NEXT: )
;; CHECK-NODEBUG-NEXT: )
+
+;; CHECK-NODEBUG: (func $2
+;; CHECK-NODEBUG-NEXT: (table.set $0
+;; CHECK-NODEBUG-NEXT: (i32.const 0)
+;; CHECK-NODEBUG-NEXT: (ref.func $0)
+;; CHECK-NODEBUG-NEXT: )
+;; CHECK-NODEBUG-NEXT: (drop
+;; CHECK-NODEBUG-NEXT: (table.get $0
+;; CHECK-NODEBUG-NEXT: (i32.const 0)
+;; CHECK-NODEBUG-NEXT: )
+;; CHECK-NODEBUG-NEXT: )
+;; CHECK-NODEBUG-NEXT: )
diff --git a/test/spec/table_set.wast b/test/spec/table_set.wast
new file mode 100644
index 000000000..6034f146f
--- /dev/null
+++ b/test/spec/table_set.wast
@@ -0,0 +1,119 @@
+(module
+ (table $t2 1 externref)
+ (table $t3 2 funcref)
+ (elem (table $t3) (i32.const 1) func $dummy)
+ (func $dummy)
+
+ (func (export "get-externref") (param $i i32) (result externref)
+ (table.get $t2 (local.get $i))
+ )
+ (func $f3 (export "get-funcref") (param $i i32) (result funcref)
+ (table.get $t3 (local.get $i))
+ )
+
+ (func (export "set-externref") (param $i i32) (param $r externref)
+ (table.set $t2 (local.get $i) (local.get $r))
+ )
+ (func (export "set-funcref") (param $i i32) (param $r funcref)
+ (table.set $t3 (local.get $i) (local.get $r))
+ )
+ (func (export "set-funcref-from") (param $i i32) (param $j i32)
+ (table.set $t3 (local.get $i) (table.get $t3 (local.get $j)))
+ )
+
+ (func (export "is_null-funcref") (param $i i32) (result i32)
+ (ref.is_null (call $f3 (local.get $i)))
+ )
+)
+
+(assert_return (invoke "get-externref" (i32.const 0)) (ref.null extern))
+;; (assert_return (invoke "set-externref" (i32.const 0) (ref.extern 1)))
+;; (assert_return (invoke "get-externref" (i32.const 0)) (ref.extern 1))
+(assert_return (invoke "set-externref" (i32.const 0) (ref.null extern)))
+(assert_return (invoke "get-externref" (i32.const 0)) (ref.null extern))
+
+(assert_return (invoke "get-funcref" (i32.const 0)) (ref.null func))
+(assert_return (invoke "set-funcref-from" (i32.const 0) (i32.const 1)))
+(assert_return (invoke "is_null-funcref" (i32.const 0)) (i32.const 0))
+(assert_return (invoke "set-funcref" (i32.const 0) (ref.null func)))
+(assert_return (invoke "get-funcref" (i32.const 0)) (ref.null func))
+
+(assert_trap (invoke "set-externref" (i32.const 2) (ref.null extern)) "out of bounds table access")
+(assert_trap (invoke "set-funcref" (i32.const 3) (ref.null func)) "out of bounds table access")
+(assert_trap (invoke "set-externref" (i32.const -1) (ref.null extern)) "out of bounds table access")
+(assert_trap (invoke "set-funcref" (i32.const -1) (ref.null func)) "out of bounds table access")
+
+;; (assert_trap (invoke "set-externref" (i32.const 2) (ref.extern 0)) "out of bounds table access")
+(assert_trap (invoke "set-funcref-from" (i32.const 3) (i32.const 1)) "out of bounds table access")
+;; (assert_trap (invoke "set-externref" (i32.const -1) (ref.extern 0)) "out of bounds table access")
+(assert_trap (invoke "set-funcref-from" (i32.const -1) (i32.const 1)) "out of bounds table access")
+
+
+;; Type errors
+
+(assert_invalid
+ (module
+ (table $t 10 externref)
+ (func $type-index-value-empty-vs-i32-externref
+ (table.set $t)
+ )
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (table $t 10 externref)
+ (func $type-index-empty-vs-i32
+ (table.set $t (ref.null extern))
+ )
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (table $t 10 externref)
+ (func $type-value-empty-vs-externref
+ (table.set $t (i32.const 1))
+ )
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (table $t 10 externref)
+ (func $type-size-f32-vs-i32
+ (table.set $t (f32.const 1) (ref.null extern))
+ )
+ )
+ "type mismatch"
+)
+(assert_invalid
+ (module
+ (table $t 10 funcref)
+ (func $type-value-externref-vs-funcref (param $r externref)
+ (table.set $t (i32.const 1) (local.get $r))
+ )
+ )
+ "type mismatch"
+)
+
+(assert_invalid
+ (module
+ (table $t1 1 externref)
+ (table $t2 1 funcref)
+ (func $type-value-externref-vs-funcref-multi (param $r externref)
+ (table.set $t2 (i32.const 0) (local.get $r))
+ )
+ )
+ "type mismatch"
+)
+
+(assert_invalid
+ (module
+ (table $t 10 externref)
+ (func $type-result-empty-vs-num (result i32)
+ (table.set $t (i32.const 0) (ref.null extern))
+ )
+ )
+ "type mismatch"
+)