summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2024-08-16 12:53:52 -0700
committerGitHub <noreply@github.com>2024-08-16 12:53:52 -0700
commit958ff4115e542ef1d0ae712f4961e342386efe54 (patch)
tree7805d641d9a73b32650a053931c4bd8c3814d804 /src
parent7209629bec3961fcc12b150ba6df546d3997b6c2 (diff)
downloadbinaryen-958ff4115e542ef1d0ae712f4961e342386efe54.tar.gz
binaryen-958ff4115e542ef1d0ae712f4961e342386efe54.tar.bz2
binaryen-958ff4115e542ef1d0ae712f4961e342386efe54.zip
Implement table.init (#6827)
Also use TableInit in the interpreter to initialize module's table state, which will now handle traps properly, fixing #6431
Diffstat (limited to 'src')
-rw-r--r--src/gen-s-parser.inc6
-rw-r--r--src/ir/ReFinalize.cpp1
-rw-r--r--src/ir/child-typer.h6
-rw-r--r--src/ir/cost.h3
-rw-r--r--src/ir/effects.h4
-rw-r--r--src/ir/possible-contents.cpp1
-rw-r--r--src/ir/subtype-exprs.h5
-rw-r--r--src/parser/contexts.h13
-rw-r--r--src/parser/parsers.h12
-rw-r--r--src/passes/Directize.cpp3
-rw-r--r--src/passes/Print.cpp6
-rw-r--r--src/passes/Table64Lowering.cpp4
-rw-r--r--src/passes/TypeGeneralizing.cpp2
-rw-r--r--src/tools/wasm-ctor-eval.cpp24
-rw-r--r--src/wasm-binary.h2
-rw-r--r--src/wasm-builder.h14
-rw-r--r--src/wasm-delegations-fields.def8
-rw-r--r--src/wasm-delegations.def1
-rw-r--r--src/wasm-interpreter.h95
-rw-r--r--src/wasm-ir-builder.h1
-rw-r--r--src/wasm.h15
-rw-r--r--src/wasm/wasm-binary.cpp20
-rw-r--r--src/wasm/wasm-ir-builder.cpp8
-rw-r--r--src/wasm/wasm-stack.cpp6
-rw-r--r--src/wasm/wasm-validator.cpp36
-rw-r--r--src/wasm/wasm.cpp8
-rw-r--r--src/wasm2js.h4
27 files changed, 276 insertions, 32 deletions
diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc
index d6eaf851d..2fa658e65 100644
--- a/src/gen-s-parser.inc
+++ b/src/gen-s-parser.inc
@@ -4936,6 +4936,12 @@ switch (buf[0]) {
default: goto parse_error;
}
}
+ case 'i':
+ if (op == "table.init"sv) {
+ CHECK_ERR(makeTableInit(ctx, pos, annotations));
+ return Ok{};
+ }
+ goto parse_error;
case 's': {
switch (buf[7]) {
case 'e':
diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp
index 8fae5d073..0f78d37b7 100644
--- a/src/ir/ReFinalize.cpp
+++ b/src/ir/ReFinalize.cpp
@@ -126,6 +126,7 @@ void ReFinalize::visitTableSize(TableSize* curr) { curr->finalize(); }
void ReFinalize::visitTableGrow(TableGrow* curr) { curr->finalize(); }
void ReFinalize::visitTableFill(TableFill* curr) { curr->finalize(); }
void ReFinalize::visitTableCopy(TableCopy* curr) { curr->finalize(); }
+void ReFinalize::visitTableInit(TableInit* curr) { curr->finalize(); }
void ReFinalize::visitTry(Try* curr) { curr->finalize(); }
void ReFinalize::visitTryTable(TryTable* curr) { curr->finalize(); }
void ReFinalize::visitThrow(Throw* curr) { curr->finalize(); }
diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h
index 2bfaefaff..725bc842e 100644
--- a/src/ir/child-typer.h
+++ b/src/ir/child-typer.h
@@ -742,6 +742,12 @@ template<typename Subtype> struct ChildTyper : OverriddenVisitor<Subtype> {
note(&curr->size, Type::i32);
}
+ void visitTableInit(TableInit* curr) {
+ note(&curr->dest, wasm.getTable(curr->table)->indexType);
+ note(&curr->offset, Type::i32);
+ note(&curr->size, Type::i32);
+ }
+
void visitTry(Try* curr) {
note(&curr->body, curr->type);
for (auto& expr : curr->catchBodies) {
diff --git a/src/ir/cost.h b/src/ir/cost.h
index ab8ae90ae..11370acab 100644
--- a/src/ir/cost.h
+++ b/src/ir/cost.h
@@ -595,6 +595,9 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
CostType visitTableCopy(TableCopy* curr) {
return 6 + visit(curr->dest) + visit(curr->source) + visit(curr->size);
}
+ CostType visitTableInit(TableInit* curr) {
+ return 6 + visit(curr->dest) + visit(curr->offset) + visit(curr->size);
+ }
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 be949a8bf..fee8b3441 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -752,6 +752,10 @@ private:
parent.writesTable = true;
parent.implicitTrap = true;
}
+ void visitTableInit(TableInit* curr) {
+ parent.writesTable = true;
+ parent.implicitTrap = true;
+ }
void visitTry(Try* curr) {
if (curr->delegateTarget.is()) {
parent.delegateTargets.insert(curr->delegateTarget);
diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp
index 38fa3e5f6..e7454c7c6 100644
--- a/src/ir/possible-contents.cpp
+++ b/src/ir/possible-contents.cpp
@@ -663,6 +663,7 @@ struct InfoCollector
void visitTableGrow(TableGrow* curr) { addRoot(curr); }
void visitTableFill(TableFill* curr) { addRoot(curr); }
void visitTableCopy(TableCopy* curr) { addRoot(curr); }
+ void visitTableInit(TableInit* curr) {}
void visitNop(Nop* curr) {}
void visitUnreachable(Unreachable* curr) {}
diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h
index f563cce14..640240df9 100644
--- a/src/ir/subtype-exprs.h
+++ b/src/ir/subtype-exprs.h
@@ -238,6 +238,11 @@ struct SubtypingDiscoverer : public OverriddenVisitor<SubType> {
self()->noteSubtype(self()->getModule()->getTable(curr->sourceTable)->type,
self()->getModule()->getTable(curr->destTable)->type);
}
+ void visitTableInit(TableInit* curr) {
+ auto* seg = self()->getModule()->getElementSegment(curr->segment);
+ self()->noteSubtype(seg->type,
+ self()->getModule()->getTable(curr->table)->type);
+ }
void visitTry(Try* curr) {
self()->noteSubtype(curr->body, curr);
for (auto* body : curr->catchBodies) {
diff --git a/src/parser/contexts.h b/src/parser/contexts.h
index 5acb842d6..7e70b6776 100644
--- a/src/parser/contexts.h
+++ b/src/parser/contexts.h
@@ -674,6 +674,10 @@ struct NullInstrParserCtx {
makeTableCopy(Index, const std::vector<Annotation>&, TableIdxT*, TableIdxT*) {
return Ok{};
}
+ Result<>
+ makeTableInit(Index, const std::vector<Annotation>&, TableIdxT*, ElemIdxT) {
+ return Ok{};
+ }
Result<> makeThrow(Index, const std::vector<Annotation>&, TagIdxT) {
return Ok{};
}
@@ -2325,6 +2329,15 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> {
return withLoc(pos, irBuilder.makeTableCopy(*dest, *src));
}
+ Result<> makeTableInit(Index pos,
+ const std::vector<Annotation>& annotations,
+ Name* table,
+ Name elem) {
+ auto t = getTable(pos, table);
+ CHECK_ERR(t);
+ return withLoc(pos, irBuilder.makeTableInit(elem, *t));
+ }
+
Result<>
makeThrow(Index pos, const std::vector<Annotation>& annotations, Name tag) {
return withLoc(pos, irBuilder.makeThrow(tag));
diff --git a/src/parser/parsers.h b/src/parser/parsers.h
index 23286bb28..85a1febb5 100644
--- a/src/parser/parsers.h
+++ b/src/parser/parsers.h
@@ -206,6 +206,8 @@ Result<> makeTableFill(Ctx&, Index, const std::vector<Annotation>&);
template<typename Ctx>
Result<> makeTableCopy(Ctx&, Index, const std::vector<Annotation>&);
template<typename Ctx>
+Result<> makeTableInit(Ctx&, Index, const std::vector<Annotation>&);
+template<typename Ctx>
Result<> makeThrow(Ctx&, Index, const std::vector<Annotation>&);
template<typename Ctx>
Result<> makeRethrow(Ctx&, Index, const std::vector<Annotation>&);
@@ -2069,6 +2071,16 @@ makeTableCopy(Ctx& ctx, Index pos, const std::vector<Annotation>& annotations) {
template<typename Ctx>
Result<>
+makeTableInit(Ctx& ctx, Index pos, const std::vector<Annotation>& annotations) {
+ auto table = maybeTableidx(ctx);
+ CHECK_ERR(table);
+ auto elem = elemidx(ctx);
+ CHECK_ERR(elem);
+ return ctx.makeTableInit(pos, annotations, table.getPtr(), *elem);
+}
+
+template<typename Ctx>
+Result<>
makeThrow(Ctx& ctx, Index pos, const std::vector<Annotation>& annotations) {
auto tag = tagidx(ctx);
CHECK_ERR(tag);
diff --git a/src/passes/Directize.cpp b/src/passes/Directize.cpp
index 6cb4e46d8..7faf2e23a 100644
--- a/src/passes/Directize.cpp
+++ b/src/passes/Directize.cpp
@@ -266,6 +266,9 @@ struct Directize : public Pass {
void visitTableCopy(TableCopy* curr) {
tablesWithSet.insert(curr->destTable);
}
+ void visitTableInit(TableInit* curr) {
+ tablesWithSet.insert(curr->table);
+ }
};
Finder(tablesWithSet).walkFunction(func);
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index 8bf30702c..3abe1f738 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -2055,6 +2055,12 @@ struct PrintExpressionContents
o << ' ';
curr->sourceTable.print(o);
}
+ void visitTableInit(TableInit* curr) {
+ printMedium(o, "table.init ");
+ curr->table.print(o);
+ o << ' ';
+ curr->segment.print(o);
+ }
void visitTry(Try* curr) {
printMedium(o, "try");
if (curr->name.is()) {
diff --git a/src/passes/Table64Lowering.cpp b/src/passes/Table64Lowering.cpp
index 65c9a2a63..71fc6a6fe 100644
--- a/src/passes/Table64Lowering.cpp
+++ b/src/passes/Table64Lowering.cpp
@@ -91,6 +91,10 @@ struct Table64Lowering : public WalkerPass<PostWalker<Table64Lowering>> {
wrapAddress64(curr->size, curr->destTable);
}
+ void visitTableInit(TableInit* curr) {
+ wrapAddress64(curr->dest, curr->table);
+ }
+
void visitCallIndirect(CallIndirect* curr) {
wrapAddress64(curr->target, curr->table);
}
diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp
index faf01f173..fad55e506 100644
--- a/src/passes/TypeGeneralizing.cpp
+++ b/src/passes/TypeGeneralizing.cpp
@@ -499,6 +499,8 @@ struct TransferFn : OverriddenVisitor<TransferFn> {
// Cannot generalize table types yet.
}
+ void visitTableInit(TableInit* curr) {}
+
void visitTry(Try* curr) { WASM_UNREACHABLE("TODO"); }
void visitTryTable(TryTable* curr) { WASM_UNREACHABLE("TODO"); }
void visitThrow(Throw* curr) { WASM_UNREACHABLE("TODO"); }
diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp
index 22e666a52..6c72f1c73 100644
--- a/src/tools/wasm-ctor-eval.cpp
+++ b/src/tools/wasm-ctor-eval.cpp
@@ -86,13 +86,6 @@ public:
return ModuleRunnerBase<EvallingModuleRunner>::visitGlobalGet(curr);
}
-
- Flow visitTableSet(TableSet* curr) {
- // TODO: Full dynamic table support. For now we stop evalling when we see a
- // table.set. (To support this we need to track sets and add code to
- // serialize them.)
- throw FailToEvalException("table.set: TODO");
- }
};
// Build an artificial `env` module based on a module's imports, so that the
@@ -174,6 +167,9 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
// not yet been re-added are a blind spot for it).
std::unordered_set<Name> usedGlobalNames;
+ // Set to true after we create the instance.
+ bool instanceInitialized = false;
+
CtorEvalExternalInterface(
std::map<Name, std::shared_ptr<EvallingModuleRunner>> linkedInstances_ =
{}) {
@@ -363,7 +359,8 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
}
Index tableSize(Name tableName) override {
- throw FailToEvalException("table size");
+ // See callTable above, we assume the table is not modified FIXME
+ return wasm->getTableOrNull(tableName)->initial;
}
Literal tableLoad(Name tableName, Index index) override {
@@ -371,7 +368,15 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
}
// called during initialization
- void tableStore(Name tableName, Index index, const Literal& value) override {}
+ void tableStore(Name tableName, Index index, const Literal& value) override {
+ // We allow stores to the table during initialization, but not after, as we
+ // assume the table does not change at runtime.
+ // TODO: Allow table changes by updating the table later like we do with the
+ // memory, by tracking and serializing them.
+ if (instanceInitialized) {
+ throw FailToEvalException("tableStore after init: TODO");
+ }
+ }
int8_t load8s(Address addr, Name memoryName) override {
return doLoad<int8_t>(addr, memoryName);
@@ -1294,6 +1299,7 @@ void evalCtors(Module& wasm,
try {
// create an instance for evalling
EvallingModuleRunner instance(wasm, &interface, linkedInstances);
+ interface.instanceInitialized = true;
// go one by one, in order, until we fail
// TODO: if we knew priorities, we could reorder?
for (auto& ctor : ctors) {
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index 989ce891a..32eb7657c 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -1078,6 +1078,7 @@ enum ASTNodes {
TableSize = 0x10,
TableFill = 0x11,
TableCopy = 0x0e,
+ TableInit = 0x0c,
RefNull = 0xd0,
RefIsNull = 0xd1,
RefFunc = 0xd2,
@@ -1752,6 +1753,7 @@ public:
bool maybeVisitTableGrow(Expression*& out, uint32_t code);
bool maybeVisitTableFill(Expression*& out, uint32_t code);
bool maybeVisitTableCopy(Expression*& out, uint32_t code);
+ bool maybeVisitTableInit(Expression*& out, uint32_t code);
bool maybeVisitRefI31(Expression*& out, uint32_t code);
bool maybeVisitI31Get(Expression*& out, uint32_t code);
bool maybeVisitRefTest(Expression*& out, uint32_t code);
diff --git a/src/wasm-builder.h b/src/wasm-builder.h
index a4f1c5cf9..72d2a1db0 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -775,6 +775,20 @@ public:
ret->finalize();
return ret;
}
+ TableInit* makeTableInit(Name segment,
+ Expression* dest,
+ Expression* offset,
+ Expression* size,
+ Name table) {
+ auto* ret = wasm.allocator.alloc<TableInit>();
+ ret->segment = segment;
+ ret->dest = dest;
+ ret->offset = offset;
+ ret->size = size;
+ ret->table = table;
+ ret->finalize();
+ return ret;
+ }
private:
Try* makeTry(Name name,
diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def
index 03d46020e..3be040220 100644
--- a/src/wasm-delegations-fields.def
+++ b/src/wasm-delegations-fields.def
@@ -545,6 +545,14 @@ DELEGATE_FIELD_NAME_KIND(TableCopy, sourceTable, ModuleItemKind::Table)
DELEGATE_FIELD_NAME_KIND(TableCopy, destTable, ModuleItemKind::Table)
DELEGATE_FIELD_CASE_END(TableCopy)
+DELEGATE_FIELD_CASE_START(TableInit)
+DELEGATE_FIELD_CHILD(TableInit, size)
+DELEGATE_FIELD_CHILD(TableInit, offset)
+DELEGATE_FIELD_CHILD(TableInit, dest)
+DELEGATE_FIELD_NAME_KIND(TableInit, segment, ModuleItemKind::ElementSegment)
+DELEGATE_FIELD_NAME_KIND(TableInit, table, ModuleItemKind::Table)
+DELEGATE_FIELD_CASE_END(TableInit)
+
DELEGATE_FIELD_CASE_START(Try)
DELEGATE_FIELD_SCOPE_NAME_USE(Try, delegateTarget)
DELEGATE_FIELD_CHILD_VECTOR(Try, catchBodies)
diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def
index ea801ab9b..f4552a98b 100644
--- a/src/wasm-delegations.def
+++ b/src/wasm-delegations.def
@@ -64,6 +64,7 @@ DELEGATE(TableSize);
DELEGATE(TableGrow);
DELEGATE(TableFill);
DELEGATE(TableCopy);
+DELEGATE(TableInit);
DELEGATE(Try);
DELEGATE(TryTable);
DELEGATE(Throw);
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index 7fd5b3cd3..644a141a2 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -1417,6 +1417,7 @@ public:
Flow visitTableGrow(TableGrow* curr) { WASM_UNREACHABLE("unimp"); }
Flow visitTableFill(TableFill* curr) { WASM_UNREACHABLE("unimp"); }
Flow visitTableCopy(TableCopy* curr) { WASM_UNREACHABLE("unimp"); }
+ Flow visitTableInit(TableInit* curr) { WASM_UNREACHABLE("unimp"); }
Flow visitTry(Try* curr) { WASM_UNREACHABLE("unimp"); }
Flow visitTryTable(TryTable* curr) { WASM_UNREACHABLE("unimp"); }
Flow visitThrow(Throw* curr) {
@@ -2354,6 +2355,10 @@ public:
NOTE_ENTER("TableCopy");
return Flow(NONCONSTANT_FLOW);
}
+ Flow visitTableInit(TableInit* curr) {
+ NOTE_ENTER("TableInit");
+ return Flow(NONCONSTANT_FLOW);
+ }
Flow visitLoad(Load* curr) {
NOTE_ENTER("Load");
return Flow(NONCONSTANT_FLOW);
@@ -2821,23 +2826,24 @@ private:
}
}
+ Const zero;
+ zero.value = Literal(uint32_t(0));
+ zero.finalize();
+
ModuleUtils::iterActiveElementSegments(wasm, [&](ElementSegment* segment) {
- Address offset =
- (uint32_t)self()->visit(segment->offset).getSingleValue().geti32();
-
- Table* table = wasm.getTable(segment->table);
- ExternalInterface* extInterface = externalInterface;
- Name tableName = segment->table;
- if (table->imported()) {
- auto inst = linkedInstances.at(table->module);
- extInterface = inst->externalInterface;
- tableName = inst->wasm.getExport(table->base)->value;
- }
+ Const size;
+ size.value = Literal(uint32_t(segment->data.size()));
+ size.finalize();
- for (Index i = 0; i < segment->data.size(); ++i) {
- Flow ret = self()->visit(segment->data[i]);
- extInterface->tableStore(tableName, offset + i, ret.getSingleValue());
- }
+ TableInit init;
+ init.table = segment->table;
+ init.segment = segment->name;
+ init.dest = segment->offset;
+ init.offset = &zero;
+ init.size = &size;
+ init.finalize();
+
+ self()->visit(&init);
droppedElementSegments.insert(segment->name);
});
@@ -2865,9 +2871,10 @@ private:
void initializeMemoryContents() {
initializeMemorySizes();
- Const offset;
- offset.value = Literal(uint32_t(0));
- offset.finalize();
+
+ Const zero;
+ zero.value = Literal(uint32_t(0));
+ zero.finalize();
// apply active memory segments
for (size_t i = 0, e = wasm.dataSegments.size(); i < e; ++i) {
@@ -2883,7 +2890,7 @@ private:
init.memory = segment->memory;
init.segment = segment->name;
init.dest = segment->offset;
- init.offset = &offset;
+ init.offset = &zero;
init.size = &size;
init.finalize();
@@ -3251,6 +3258,54 @@ public:
return {};
}
+ Flow visitTableInit(TableInit* curr) {
+ NOTE_ENTER("TableInit");
+ Flow dest = self()->visit(curr->dest);
+ if (dest.breaking()) {
+ return dest;
+ }
+ Flow offset = self()->visit(curr->offset);
+ if (offset.breaking()) {
+ return offset;
+ }
+ Flow size = self()->visit(curr->size);
+ if (size.breaking()) {
+ return size;
+ }
+ NOTE_EVAL1(dest);
+ NOTE_EVAL1(offset);
+ NOTE_EVAL1(size);
+
+ auto* segment = wasm.getElementSegment(curr->segment);
+
+ Address destVal(dest.getSingleValue().getUnsigned());
+ Address offsetVal(uint32_t(offset.getSingleValue().geti32()));
+ Address sizeVal(uint32_t(size.getSingleValue().geti32()));
+
+ if (offsetVal + sizeVal > 0 &&
+ droppedElementSegments.count(curr->segment)) {
+ trap("out of bounds segment access in table.init");
+ }
+ if (offsetVal + sizeVal > segment->data.size()) {
+ trap("out of bounds segment access in table.init");
+ }
+ auto info = getTableInstanceInfo(curr->table);
+ auto tableSize = info.interface()->tableSize(info.name);
+ if (destVal + sizeVal > tableSize) {
+ trap("out of bounds table access in table.init");
+ }
+ for (size_t i = 0; i < sizeVal; ++i) {
+ // FIXME: We should not call visit() here more than once at runtime. The
+ // values in the segment should be computed once during startup,
+ // and then read here as needed. For example, if we had a
+ // struct.new here then we should not allocate a new struct each
+ // time we table.init that data.
+ auto value = self()->visit(segment->data[offsetVal + i]).getSingleValue();
+ info.interface()->tableStore(info.name, destVal + i, value);
+ }
+ return {};
+ }
+
Flow visitLocalGet(LocalGet* curr) {
NOTE_ENTER("LocalGet");
auto index = curr->index;
@@ -3741,7 +3796,7 @@ public:
if (offsetVal + sizeVal > 0 && droppedDataSegments.count(curr->segment)) {
trap("out of bounds segment access in memory.init");
}
- if ((uint64_t)offsetVal + sizeVal > segment->data.size()) {
+ if (offsetVal + sizeVal > segment->data.size()) {
trap("out of bounds segment access in memory.init");
}
auto info = getMemoryInstanceInfo(curr->memory);
diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h
index eff8c85ad..d7a1dde87 100644
--- a/src/wasm-ir-builder.h
+++ b/src/wasm-ir-builder.h
@@ -169,6 +169,7 @@ public:
[[nodiscard]] Result<> makeTableGrow(Name table);
[[nodiscard]] Result<> makeTableFill(Name table);
[[nodiscard]] Result<> makeTableCopy(Name destTable, Name srcTable);
+ [[nodiscard]] Result<> makeTableInit(Name elem, Name table);
[[nodiscard]] Result<> makeTry(Name label, Type type);
[[nodiscard]] Result<> makeTryTable(Name label,
Type type,
diff --git a/src/wasm.h b/src/wasm.h
index e47b7c6ed..9d43a4f9f 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -678,6 +678,7 @@ public:
TableGrowId,
TableFillId,
TableCopyId,
+ TableInitId,
TryId,
TryTableId,
ThrowId,
@@ -1421,6 +1422,20 @@ public:
void finalize();
};
+class TableInit : public SpecificExpression<Expression::TableInitId> {
+public:
+ TableInit() = default;
+ TableInit(MixedArena& allocator) : TableInit() {}
+
+ Name segment;
+ Expression* dest;
+ Expression* offset;
+ Expression* size;
+ Name table;
+
+ void finalize();
+};
+
// 'try' from the old (Phase 3) EH proposal
class Try : public SpecificExpression<Expression::TryId> {
public:
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index 4082fbf5f..2abb32837 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -4223,6 +4223,9 @@ BinaryConsts::ASTNodes WasmBinaryReader::readExpression(Expression*& curr) {
if (maybeVisitTableCopy(curr, opcode)) {
break;
}
+ if (maybeVisitTableInit(curr, opcode)) {
+ break;
+ }
if (maybeVisitLoad(curr, opcode, BinaryConsts::MiscPrefix)) {
break;
}
@@ -5646,6 +5649,23 @@ bool WasmBinaryReader::maybeVisitTableCopy(Expression*& out, uint32_t code) {
return true;
}
+bool WasmBinaryReader::maybeVisitTableInit(Expression*& out, uint32_t code) {
+ if (code != BinaryConsts::TableInit) {
+ return false;
+ }
+ auto* curr = allocator.alloc<TableInit>();
+ curr->size = popNonVoidExpression();
+ curr->offset = popNonVoidExpression();
+ curr->dest = popNonVoidExpression();
+ Index segIdx = getU32LEB();
+ elemRefs[segIdx].push_back(&curr->segment);
+ Index memIdx = getU32LEB();
+ tableRefs[memIdx].push_back(&curr->table);
+ curr->finalize();
+ out = curr;
+ return true;
+}
+
bool WasmBinaryReader::maybeVisitBinary(Expression*& out, uint8_t code) {
Binary* curr;
#define INT_TYPED_CODE(code) \
diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp
index 3db6238c4..2f2f3b595 100644
--- a/src/wasm/wasm-ir-builder.cpp
+++ b/src/wasm/wasm-ir-builder.cpp
@@ -1523,6 +1523,14 @@ Result<> IRBuilder::makeTableCopy(Name destTable, Name srcTable) {
return Ok{};
}
+Result<> IRBuilder::makeTableInit(Name elem, Name table) {
+ TableInit curr;
+ curr.table = table;
+ CHECK_ERR(visitTableInit(&curr));
+ push(builder.makeTableInit(elem, curr.dest, curr.offset, curr.size, table));
+ return Ok{};
+}
+
Result<> IRBuilder::makeTry(Name label, Type type) {
auto* tryy = wasm.allocator.alloc<Try>();
tryy->type = type;
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index a9b582f43..1c2c2c42b 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -2053,6 +2053,12 @@ void BinaryInstWriter::visitTableCopy(TableCopy* curr) {
o << U32LEB(parent.getTableIndex(curr->sourceTable));
}
+void BinaryInstWriter::visitTableInit(TableInit* curr) {
+ o << int8_t(BinaryConsts::MiscPrefix) << U32LEB(BinaryConsts::TableInit);
+ o << U32LEB(parent.getElementSegmentIndex(curr->segment));
+ 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 b032e6bad..758ae158e 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -469,6 +469,7 @@ public:
void visitTableGrow(TableGrow* curr);
void visitTableFill(TableFill* curr);
void visitTableCopy(TableCopy* curr);
+ void visitTableInit(TableInit* curr);
void noteDelegate(Name name, Expression* curr);
void noteRethrow(Name name, Expression* curr);
void visitTry(Try* curr);
@@ -2443,12 +2444,41 @@ void FunctionValidator::visitTableCopy(TableCopy* curr) {
curr,
"table.copy source must have right type for dest");
}
+ shouldBeEqualOrFirstIsUnreachable(curr->dest->type,
+ destTable->indexType,
+ curr,
+ "table.copy dest must be valid");
+ shouldBeEqualOrFirstIsUnreachable(curr->source->type,
+ sourceTable->indexType,
+ curr,
+ "table.copy source must be valid");
+ Type sizeType =
+ sourceTable->is64() && destTable->is64() ? Type::i64 : Type::i32;
shouldBeEqualOrFirstIsUnreachable(
- curr->dest->type, Type(Type::i32), curr, "table.copy dest must be i32");
+ curr->size->type, sizeType, curr, "table.copy size must be valid");
+}
+
+void FunctionValidator::visitTableInit(TableInit* curr) {
+ shouldBeTrue(getModule()->features.hasBulkMemory(),
+ curr,
+ "table.init requires bulk-memory [--enable-bulk-memory]");
+ auto* segment = getModule()->getElementSegment(curr->segment);
+ auto* table = getModule()->getTableOrNull(curr->table);
+ if (shouldBeTrue(!!segment, curr, "table.init segment must exist") &&
+ shouldBeTrue(!!table, curr, "table.init table must exist")) {
+ shouldBeSubType(segment->type,
+ table->type,
+ curr,
+ "table.init source must have right type for dest");
+ }
shouldBeEqualOrFirstIsUnreachable(
- curr->source->type, Type(Type::i32), curr, "table.copy source must be i32");
+ curr->dest->type, table->indexType, curr, "table.init dest must be valid");
+ shouldBeEqualOrFirstIsUnreachable(curr->offset->type,
+ Type(Type::i32),
+ curr,
+ "table.init offset must be valid");
shouldBeEqualOrFirstIsUnreachable(
- curr->size->type, Type(Type::i32), curr, "table.copy size must be i32");
+ curr->size->type, Type(Type::i32), curr, "table.init size must be valid");
}
void FunctionValidator::noteDelegate(Name name, Expression* curr) {
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index d138e4226..ff641c7eb 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -875,6 +875,14 @@ void TableCopy::finalize() {
}
}
+void TableInit::finalize() {
+ type = Type::none;
+ if (dest->type == Type::unreachable || offset->type == Type::unreachable ||
+ size->type == Type::unreachable) {
+ type = Type::unreachable;
+ }
+}
+
void Try::finalize(std::optional<Type> type_) {
if (type_) {
type = *type_;
diff --git a/src/wasm2js.h b/src/wasm2js.h
index 7fa923ceb..15ae019ea 100644
--- a/src/wasm2js.h
+++ b/src/wasm2js.h
@@ -2241,6 +2241,10 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr,
visit(curr->source, EXPRESSION_RESULT),
visit(curr->size, EXPRESSION_RESULT));
}
+ Ref visitTableInit(TableInit* curr) {
+ unimplemented(curr);
+ WASM_UNREACHABLE("unimp");
+ }
Ref visitTry(Try* curr) {
unimplemented(curr);
WASM_UNREACHABLE("unimp");