diff options
author | Max Graey <maxgraey@gmail.com> | 2021-10-07 22:43:30 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-07 12:43:30 -0700 |
commit | 2dff27c086e8f2a9913096ebf3dc93e97051d85a (patch) | |
tree | 3d689a659ad2339639fc4b2e73753b51e6d9295c | |
parent | ff68bca64cd669aff59c7e1cc0a6677d267a410f (diff) | |
download | binaryen-2dff27c086e8f2a9913096ebf3dc93e97051d85a.tar.gz binaryen-2dff27c086e8f2a9913096ebf3dc93e97051d85a.tar.bz2 binaryen-2dff27c086e8f2a9913096ebf3dc93e97051d85a.zip |
Add table.set operation (#4215)
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" +) |