diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 9519ad4eeae..d7a8882dc32 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -554,9 +554,7 @@ ("table.grow", "makeTableGrow()"), ("table.fill", "makeTableFill()"), ("table.copy", "makeTableCopy()"), - # TODO: - # table.init - # + ("table.init", "makeTableInit()"), # exception handling instructions ("try", "makeTry()"), ("try_table", "makeTryTable()"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 33cddcb26b7..1660c82fd2e 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -4885,6 +4885,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 8fae5d0731a..0f78d37b7e9 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 17717a32c14..831523085cc 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -736,6 +736,12 @@ template struct ChildTyper : OverriddenVisitor { 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 06512d656de..462f7a4750a 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -589,6 +589,9 @@ struct CostAnalyzer : public OverriddenVisitor { 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 be949a8bf57..fee8b344174 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -752,6 +752,10 @@ class EffectAnalyzer { 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 38fa3e5f6cf..e7454c7c6d5 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 f563cce1453..640240df932 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -238,6 +238,11 @@ struct SubtypingDiscoverer : public OverriddenVisitor { 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 5acb842d67b..7e70b67764a 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -674,6 +674,10 @@ struct NullInstrParserCtx { makeTableCopy(Index, const std::vector&, TableIdxT*, TableIdxT*) { return Ok{}; } + Result<> + makeTableInit(Index, const std::vector&, TableIdxT*, ElemIdxT) { + return Ok{}; + } Result<> makeThrow(Index, const std::vector&, TagIdxT) { return Ok{}; } @@ -2325,6 +2329,15 @@ struct ParseDefsCtx : TypeParserCtx { return withLoc(pos, irBuilder.makeTableCopy(*dest, *src)); } + Result<> makeTableInit(Index pos, + const std::vector& 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& annotations, Name tag) { return withLoc(pos, irBuilder.makeThrow(tag)); diff --git a/src/parser/parsers.h b/src/parser/parsers.h index e3434d1e653..b11cd5786ef 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -206,6 +206,8 @@ Result<> makeTableFill(Ctx&, Index, const std::vector&); template Result<> makeTableCopy(Ctx&, Index, const std::vector&); template +Result<> makeTableInit(Ctx&, Index, const std::vector&); +template Result<> makeThrow(Ctx&, Index, const std::vector&); template Result<> makeRethrow(Ctx&, Index, const std::vector&); @@ -2067,6 +2069,16 @@ makeTableCopy(Ctx& ctx, Index pos, const std::vector& annotations) { pos, annotations, destTable.getPtr(), srcTable.getPtr()); } +template +Result<> +makeTableInit(Ctx& ctx, Index pos, const std::vector& annotations) { + auto table = maybeTableidx(ctx); + CHECK_ERR(table); + auto elem = elemidx(ctx); + CHECK_ERR(elem); + return ctx.makeTableInit(pos, annotations, table.getPtr(), *elem); +} + template Result<> makeThrow(Ctx& ctx, Index pos, const std::vector& annotations) { diff --git a/src/passes/Directize.cpp b/src/passes/Directize.cpp index 6cb4e46d8c2..7faf2e23a4f 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 fd22f1b716f..c9607ef8121 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2037,6 +2037,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 65c9a2a63c9..71fc6a6fe6d 100644 --- a/src/passes/Table64Lowering.cpp +++ b/src/passes/Table64Lowering.cpp @@ -91,6 +91,10 @@ struct Table64Lowering : public WalkerPass> { 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 faf01f173e8..fad55e50699 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -499,6 +499,8 @@ struct TransferFn : OverriddenVisitor { // 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 22e666a52e3..6c72f1c73a8 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -86,13 +86,6 @@ class EvallingModuleRunner : public ModuleRunnerBase { return ModuleRunnerBase::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 usedGlobalNames; + // Set to true after we create the instance. + bool instanceInitialized = false; + CtorEvalExternalInterface( std::map> 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(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 38bf5d475a4..b5721cd0b34 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1071,6 +1071,7 @@ enum ASTNodes { TableSize = 0x10, TableFill = 0x11, TableCopy = 0x0e, + TableInit = 0x0c, RefNull = 0xd0, RefIsNull = 0xd1, RefFunc = 0xd2, @@ -1745,6 +1746,7 @@ class WasmBinaryReader { 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 a4f1c5cf915..72d2a1db03b 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -775,6 +775,20 @@ class Builder { ret->finalize(); return ret; } + TableInit* makeTableInit(Name segment, + Expression* dest, + Expression* offset, + Expression* size, + Name table) { + auto* ret = wasm.allocator.alloc(); + 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 03d46020e24..3be04022094 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 ea801ab9bb3..f4552a98b20 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 f59a005b645..ec215eab5eb 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1405,6 +1405,7 @@ class ExpressionRunner : public OverriddenVisitor { 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) { @@ -2342,6 +2343,10 @@ class ConstantExpressionRunner : public ExpressionRunner { 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); @@ -2809,23 +2814,24 @@ class ModuleRunnerBase : public ExpressionRunner { } } + 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); }); @@ -2853,9 +2859,10 @@ class ModuleRunnerBase : public ExpressionRunner { 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) { @@ -2871,7 +2878,7 @@ class ModuleRunnerBase : public ExpressionRunner { init.memory = segment->memory; init.segment = segment->name; init.dest = segment->offset; - init.offset = &offset; + init.offset = &zero; init.size = &size; init.finalize(); @@ -3239,6 +3246,54 @@ class ModuleRunnerBase : public ExpressionRunner { 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; @@ -3729,7 +3784,7 @@ class ModuleRunnerBase : public ExpressionRunner { 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 eff8c85ad8d..d7a1dde87b9 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -169,6 +169,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { [[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 56e94fef78b..8939f859b32 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -672,6 +672,7 @@ class Expression { TableGrowId, TableFillId, TableCopyId, + TableInitId, TryId, TryTableId, ThrowId, @@ -1415,6 +1416,20 @@ class TableCopy : public SpecificExpression { void finalize(); }; +class TableInit : public SpecificExpression { +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 { public: diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 5eaa8451510..50a683aafbd 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4198,6 +4198,9 @@ BinaryConsts::ASTNodes WasmBinaryReader::readExpression(Expression*& curr) { if (maybeVisitTableCopy(curr, opcode)) { break; } + if (maybeVisitTableInit(curr, opcode)) { + break; + } if (maybeVisitLoad(curr, opcode, BinaryConsts::MiscPrefix)) { break; } @@ -5621,6 +5624,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(); + 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 3db6238c4e1..2f2f3b595fe 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(); tryy->type = type; diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 19b98769b56..28546f74849 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2035,6 +2035,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 6e59ce8d81b..fa7bdfb6a4f 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -469,6 +469,7 @@ struct FunctionValidator : public WalkerPass> { 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); @@ -2437,12 +2438,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 ae70e4a2238..48ccfb5153c 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -874,6 +874,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_) { if (type_) { type = *type_; diff --git a/src/wasm2js.h b/src/wasm2js.h index 7fa923ceb07..15ae019ea22 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"); diff --git a/test/binaryen.js/exception-handling.js.txt b/test/binaryen.js/exception-handling.js.txt index d2a0c6dd62f..f8b71302538 100644 --- a/test/binaryen.js/exception-handling.js.txt +++ b/test/binaryen.js/exception-handling.js.txt @@ -34,7 +34,7 @@ ) ) -getExpressionInfo(throw) = {"id":53,"type":1,"tag":"e"} -getExpressionInfo(rethrow) = {"id":54,"type":1,"target":"l0"} -getExpressionInfo(try_catch) = {"id":51,"type":1,"name":"l0","hasCatchAll":0,"delegateTarget":"","isDelegate":0} -getExpressionInfo(try_delegate) = {"id":51,"type":0,"name":"try_outer","hasCatchAll":1,"delegateTarget":"","isDelegate":0} +getExpressionInfo(throw) = {"id":54,"type":1,"tag":"e"} +getExpressionInfo(rethrow) = {"id":55,"type":1,"target":"l0"} +getExpressionInfo(try_catch) = {"id":52,"type":1,"name":"l0","hasCatchAll":0,"delegateTarget":"","isDelegate":0} +getExpressionInfo(try_delegate) = {"id":52,"type":0,"name":"try_outer","hasCatchAll":1,"delegateTarget":"","isDelegate":0} diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 28922c2a0ab..212194c9c86 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -82,35 +82,35 @@ TableGetId: 45 TableSetId: 46 TableSizeId: 47 TableGrowId: 48 -TryId: 51 -ThrowId: 53 -RethrowId: 54 -TupleMakeId: 56 -TupleExtractId: 57 -RefI31Id: 58 -I31GetId: 59 -CallRefId: 60 -RefTestId: 61 -RefCastId: 62 -BrOnId: 63 -StructNewId: 64 -StructGetId: 65 -StructSetId: 66 -ArrayNewId: 67 -ArrayNewFixedId: 70 -ArrayGetId: 71 -ArraySetId: 72 -ArrayLenId: 73 -ArrayCopy: 74 -RefAs: 78 -StringNew: 79 -StringConst: 80 -StringMeasure: 81 -StringEncode: 82 -StringConcat: 83 -StringEq: 84 -StringWTF16Get: 85 -StringSliceWTF: 86 +TryId: 52 +ThrowId: 54 +RethrowId: 55 +TupleMakeId: 57 +TupleExtractId: 58 +RefI31Id: 59 +I31GetId: 60 +CallRefId: 61 +RefTestId: 62 +RefCastId: 63 +BrOnId: 64 +StructNewId: 65 +StructGetId: 66 +StructSetId: 67 +ArrayNewId: 68 +ArrayNewFixedId: 71 +ArrayGetId: 72 +ArraySetId: 73 +ArrayLenId: 74 +ArrayCopy: 75 +RefAs: 79 +StringNew: 80 +StringConst: 81 +StringMeasure: 82 +StringEncode: 83 +StringConcat: 84 +StringEq: 85 +StringWTF16Get: 86 +StringSliceWTF: 87 getExpressionInfo={"id":15,"type":4,"op":6} (f32.neg (f32.const -33.61199951171875) diff --git a/test/lit/basic/table-operations.wast b/test/lit/basic/table-operations.wast index 4ccbe6c6a4a..8999814107a 100644 --- a/test/lit/basic/table-operations.wast +++ b/test/lit/basic/table-operations.wast @@ -12,24 +12,24 @@ (module ;; CHECK-TEXT: (type $0 (func)) - ;; CHECK-TEXT: (type $1 (func (result i32))) + ;; CHECK-TEXT: (type $1 (func (param i32 i32 i32))) - ;; CHECK-TEXT: (type $2 (func (param i32) (result i32))) + ;; CHECK-TEXT: (type $2 (func (result i32))) - ;; CHECK-TEXT: (type $3 (func (param i32 funcref i32))) + ;; CHECK-TEXT: (type $3 (func (param i32) (result i32))) - ;; CHECK-TEXT: (type $4 (func (param i32 i32 i32))) + ;; CHECK-TEXT: (type $4 (func (param i32 funcref i32))) ;; CHECK-TEXT: (table $table-1 1 1 funcref) ;; CHECK-BIN: (type $0 (func)) - ;; CHECK-BIN: (type $1 (func (result i32))) + ;; CHECK-BIN: (type $1 (func (param i32 i32 i32))) - ;; CHECK-BIN: (type $2 (func (param i32) (result i32))) + ;; CHECK-BIN: (type $2 (func (result i32))) - ;; CHECK-BIN: (type $3 (func (param i32 funcref i32))) + ;; CHECK-BIN: (type $3 (func (param i32) (result i32))) - ;; CHECK-BIN: (type $4 (func (param i32 i32 i32))) + ;; CHECK-BIN: (type $4 (func (param i32 funcref i32))) ;; CHECK-BIN: (table $table-1 1 1 funcref) (table $table-1 funcref @@ -46,13 +46,17 @@ ;; CHECK-TEXT: (elem $implicit-elem_1 (table $table-2) (i32.const 0) func $bar $bar $bar) - ;; CHECK-TEXT: (func $foo (type $0) - ;; CHECK-TEXT-NEXT: (nop) - ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT: (elem $elem func) ;; CHECK-BIN: (elem $0 (table $table-1) (i32.const 0) func $foo) ;; CHECK-BIN: (elem $1 (table $table-2) (i32.const 0) func $bar $bar $bar) + ;; CHECK-BIN: (elem $elem func) + (elem $elem funcref) + + ;; CHECK-TEXT: (func $foo (type $0) + ;; CHECK-TEXT-NEXT: (nop) + ;; CHECK-TEXT-NEXT: ) ;; CHECK-BIN: (func $foo (type $0) ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) @@ -131,23 +135,23 @@ ) ) - ;; CHECK-TEXT: (func $get-table-size (type $1) (result i32) + ;; CHECK-TEXT: (func $get-table-size (type $2) (result i32) ;; CHECK-TEXT-NEXT: (table.size $table-1) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $get-table-size (type $1) (result i32) + ;; CHECK-BIN: (func $get-table-size (type $2) (result i32) ;; CHECK-BIN-NEXT: (table.size $table-1) ;; CHECK-BIN-NEXT: ) (func $get-table-size (result i32) (table.size $table-1) ) - ;; CHECK-TEXT: (func $table-grow (type $2) (param $sz i32) (result i32) + ;; CHECK-TEXT: (func $table-grow (type $3) (param $sz i32) (result i32) ;; CHECK-TEXT-NEXT: (table.grow $table-1 ;; CHECK-TEXT-NEXT: (ref.null nofunc) ;; CHECK-TEXT-NEXT: (local.get $sz) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $table-grow (type $2) (param $sz i32) (result i32) + ;; CHECK-BIN: (func $table-grow (type $3) (param $sz i32) (result i32) ;; CHECK-BIN-NEXT: (table.grow $table-1 ;; CHECK-BIN-NEXT: (ref.null nofunc) ;; CHECK-BIN-NEXT: (local.get $sz) @@ -157,14 +161,14 @@ (table.grow $table-1 (ref.null func) (local.get $sz)) ) - ;; CHECK-TEXT: (func $table-fill (type $3) (param $dest i32) (param $value funcref) (param $size i32) + ;; CHECK-TEXT: (func $table-fill (type $4) (param $dest i32) (param $value funcref) (param $size i32) ;; CHECK-TEXT-NEXT: (table.fill $table-1 ;; CHECK-TEXT-NEXT: (local.get $dest) ;; CHECK-TEXT-NEXT: (local.get $value) ;; CHECK-TEXT-NEXT: (local.get $size) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $table-fill (type $3) (param $dest i32) (param $value funcref) (param $size i32) + ;; CHECK-BIN: (func $table-fill (type $4) (param $dest i32) (param $value funcref) (param $size i32) ;; CHECK-BIN-NEXT: (table.fill $table-1 ;; CHECK-BIN-NEXT: (local.get $dest) ;; CHECK-BIN-NEXT: (local.get $value) @@ -179,14 +183,14 @@ ) ) - ;; CHECK-TEXT: (func $table-copy (type $4) (param $dest i32) (param $source i32) (param $size i32) + ;; CHECK-TEXT: (func $table-copy (type $1) (param $dest i32) (param $source i32) (param $size i32) ;; CHECK-TEXT-NEXT: (table.copy $table-1 $table-2 ;; CHECK-TEXT-NEXT: (local.get $dest) ;; CHECK-TEXT-NEXT: (local.get $source) ;; CHECK-TEXT-NEXT: (local.get $size) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $table-copy (type $4) (param $dest i32) (param $source i32) (param $size i32) + ;; CHECK-BIN: (func $table-copy (type $1) (param $dest i32) (param $source i32) (param $size i32) ;; CHECK-BIN-NEXT: (table.copy $table-1 $table-2 ;; CHECK-BIN-NEXT: (local.get $dest) ;; CHECK-BIN-NEXT: (local.get $source) @@ -200,16 +204,38 @@ (local.get $size) ) ) + + ;; CHECK-TEXT: (func $table-init (type $1) (param $dest i32) (param $offset i32) (param $size i32) + ;; CHECK-TEXT-NEXT: (table.init $table-1 $elem + ;; CHECK-TEXT-NEXT: (local.get $dest) + ;; CHECK-TEXT-NEXT: (local.get $offset) + ;; CHECK-TEXT-NEXT: (local.get $size) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $table-init (type $1) (param $dest i32) (param $offset i32) (param $size i32) + ;; CHECK-BIN-NEXT: (table.init $table-1 $elem + ;; CHECK-BIN-NEXT: (local.get $dest) + ;; CHECK-BIN-NEXT: (local.get $offset) + ;; CHECK-BIN-NEXT: (local.get $size) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $table-init (param $dest i32) (param $offset i32) (param $size i32) + (table.init $table-1 $elem + (local.get $dest) + (local.get $offset) + (local.get $size) + ) + ) ) ;; CHECK-BIN-NODEBUG: (type $0 (func)) -;; CHECK-BIN-NODEBUG: (type $1 (func (result i32))) +;; CHECK-BIN-NODEBUG: (type $1 (func (param i32 i32 i32))) -;; CHECK-BIN-NODEBUG: (type $2 (func (param i32) (result i32))) +;; CHECK-BIN-NODEBUG: (type $2 (func (result i32))) -;; CHECK-BIN-NODEBUG: (type $3 (func (param i32 funcref i32))) +;; CHECK-BIN-NODEBUG: (type $3 (func (param i32) (result i32))) -;; CHECK-BIN-NODEBUG: (type $4 (func (param i32 i32 i32))) +;; CHECK-BIN-NODEBUG: (type $4 (func (param i32 funcref i32))) ;; CHECK-BIN-NODEBUG: (table $0 1 1 funcref) @@ -219,6 +245,8 @@ ;; CHECK-BIN-NODEBUG: (elem $1 (table $1) (i32.const 0) func $1 $1 $1) +;; CHECK-BIN-NODEBUG: (elem $2 func) + ;; CHECK-BIN-NODEBUG: (func $0 (type $0) ;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -248,18 +276,18 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $3 (type $1) (result i32) +;; CHECK-BIN-NODEBUG: (func $3 (type $2) (result i32) ;; CHECK-BIN-NODEBUG-NEXT: (table.size $0) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $4 (type $2) (param $0 i32) (result i32) +;; CHECK-BIN-NODEBUG: (func $4 (type $3) (param $0 i32) (result i32) ;; CHECK-BIN-NODEBUG-NEXT: (table.grow $0 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $5 (type $3) (param $0 i32) (param $1 funcref) (param $2 i32) +;; CHECK-BIN-NODEBUG: (func $5 (type $4) (param $0 i32) (param $1 funcref) (param $2 i32) ;; CHECK-BIN-NODEBUG-NEXT: (table.fill $0 ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) @@ -267,10 +295,18 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $6 (type $4) (param $0 i32) (param $1 i32) (param $2 i32) +;; CHECK-BIN-NODEBUG: (func $6 (type $1) (param $0 i32) (param $1 i32) (param $2 i32) ;; CHECK-BIN-NODEBUG-NEXT: (table.copy $0 $1 ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $7 (type $1) (param $0 i32) (param $1 i32) (param $2 i32) +;; CHECK-BIN-NODEBUG-NEXT: (table.init $0 $2 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $2) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/ctor-eval/table.init.wat b/test/lit/ctor-eval/table.init.wat new file mode 100644 index 00000000000..7af2a09c6e1 --- /dev/null +++ b/test/lit/ctor-eval/table.init.wat @@ -0,0 +1,62 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-ctor-eval %s --ctors=run --kept-exports=run --quiet -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $none_=>_none (func)) + (type $none_=>_none (func)) + + ;; CHECK: (table $table 22 funcref) + (table $table 22 funcref) + + ;; CHECK: (elem $init (i32.const 0) $nop) + (elem $init (i32.const 0) $nop) + + ;; CHECK: (elem $later func $trap) + (elem $later $trap) + + (export "run" (func $run)) + + (func $run (type $none_=>_none) + ;; This call can be evalled away (it does nothing as the target is a nop). + (call_indirect $table (type $none_=>_none) + (i32.const 0) + ) + + ;; We stop at this table.init, which is not handled yet. The call after it + ;; should also remain where it is. + (table.init $table $later + (i32.const 0) + (i32.const 0) + (i32.const 1) + ) + (call_indirect $table (type $none_=>_none) + (i32.const 0) + ) + ) + + ;; CHECK: (export "run" (func $run_3)) + + ;; CHECK: (func $nop (type $none_=>_none) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $nop (type $none_=>_none) + (nop) + ) + + ;; CHECK: (func $trap (type $none_=>_none) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $trap (type $none_=>_none) + (unreachable) + ) +) +;; CHECK: (func $run_3 (type $none_=>_none) +;; CHECK-NEXT: (table.init $table $later +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (call_indirect $table (type $none_=>_none) +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) diff --git a/test/lit/passes/directize_all-features.wast b/test/lit/passes/directize_all-features.wast index 997992b0766..074558d95ae 100644 --- a/test/lit/passes/directize_all-features.wast +++ b/test/lit/passes/directize_all-features.wast @@ -1599,6 +1599,101 @@ ) ) +;; A table.init prevents optimization. +(module + ;; CHECK: (type $i32 (func (result i32))) + ;; IMMUT: (type $i32 (func (result i32))) + (type $i32 (func (result i32))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (table $table 111 funcref) + ;; IMMUT: (type $1 (func)) + + ;; IMMUT: (table $table 111 funcref) + (table $table 111 funcref) + (elem (i32.const 0) $func-A) + + ;; CHECK: (elem $0 (i32.const 0) $func-A) + + ;; CHECK: (elem $elem func $func-B) + ;; IMMUT: (elem $0 (i32.const 0) $func-A) + + ;; IMMUT: (elem $elem func $func-B) + (elem $elem $func-B) + + ;; CHECK: (export "a" (func $init)) + ;; IMMUT: (export "a" (func $init)) + (export "a" (func $init)) + ;; CHECK: (export "b" (func $call)) + ;; IMMUT: (export "b" (func $call)) + (export "b" (func $call)) + + ;; CHECK: (func $func-A (type $i32) (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; IMMUT: (func $func-A (type $i32) (result i32) + ;; IMMUT-NEXT: (i32.const 0) + ;; IMMUT-NEXT: ) + (func $func-A (result i32) + (i32.const 0) + ) + + ;; CHECK: (func $func-B (type $i32) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; IMMUT: (func $func-B (type $i32) (result i32) + ;; IMMUT-NEXT: (unreachable) + ;; IMMUT-NEXT: ) + (func $func-B (result i32) + (unreachable) + ) + + ;; CHECK: (func $init (type $1) + ;; CHECK-NEXT: (table.init $table $elem + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; IMMUT: (func $init (type $1) + ;; IMMUT-NEXT: (table.init $table $elem + ;; IMMUT-NEXT: (i32.const 0) + ;; IMMUT-NEXT: (i32.const 0) + ;; IMMUT-NEXT: (i32.const 1) + ;; IMMUT-NEXT: ) + ;; IMMUT-NEXT: ) + (func $init + (table.init $table $elem + (i32.const 0) + (i32.const 0) + (i32.const 1) + ) + ) + + ;; CHECK: (func $call (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call_indirect $table (type $i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; IMMUT: (func $call (type $1) + ;; IMMUT-NEXT: (drop + ;; IMMUT-NEXT: (call $func-A) + ;; IMMUT-NEXT: ) + ;; IMMUT-NEXT: ) + (func $call + (drop + ;; This cannot be turned into a direct call due to the table.init, unless we + ;; assume initial contents are immutable. + (call_indirect (type $i32) + (i32.const 0) + ) + ) + ) +) + ;; The elem's offset is way out of bounds, which we should not error on, and do ;; nothing otherwise. (module diff --git a/test/lit/passes/simplify-locals-table_copy.wast b/test/lit/passes/simplify-locals-table_copy.wast new file mode 100644 index 00000000000..a10c16b1b16 --- /dev/null +++ b/test/lit/passes/simplify-locals-table_copy.wast @@ -0,0 +1,73 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --simplify-locals -all -S -o - | filecheck %s + +(module + ;; CHECK: (table $table 10 funcref) + (table $table 10 funcref) + + ;; CHECK: (elem $zero (i32.const 0) $zero) + (elem $zero (i32.const 0) $zero) + + ;; CHECK: (elem $one func $one) + (elem $one $one) + + ;; CHECK: (func $move (type $0) (result funcref) + ;; CHECK-NEXT: (local $temp funcref) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (table.get $table + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $move (result funcref) + (local $temp funcref) + (local.set $temp + (table.get $table + (i32.const 0) + ) + ) + ;; We can move the table.get past the nop. + (nop) + (local.get $temp) + ) + + ;; CHECK: (func $no-move (type $0) (result funcref) + ;; CHECK-NEXT: (local $temp funcref) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (table.get $table + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.init $table $one + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + (func $no-move (result funcref) + (local $temp funcref) + (local.set $temp + (table.get $table + (i32.const 0) + ) + ) + ;; table.init writes to the table, so table reads cannot cross it. + (table.init $table $one (i32.const 0) (i32.const 0) (i32.const 1)) + (local.get $temp) + ) + + ;; CHECK: (func $zero (type $1) (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $zero (result i32) + (i32.const 0) + ) + + ;; CHECK: (func $one (type $1) (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + (func $one (result i32) + (i32.const 1) + ) +) diff --git a/test/lit/passes/table64-lowering.wast b/test/lit/passes/table64-lowering.wast index 7e49d3e7303..f3aaf4ef8a0 100644 --- a/test/lit/passes/table64-lowering.wast +++ b/test/lit/passes/table64-lowering.wast @@ -67,4 +67,17 @@ (func $test_table_fill (table.fill $t64 (i64.const 0) (ref.null func) (i64.const 10)) ) + + ;; CHECK: (func $test_table_init + ;; CHECK-NEXT: (table.init $t64 $elem64 + ;; CHECK-NEXT: (i32.wrap_i64 + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test_table_init + (table.init $t64 $elem64 (i64.const 0) (i32.const 5) (i32.const 10)) + ) ) diff --git a/test/lit/passes/unsubtyping.wast b/test/lit/passes/unsubtyping.wast index 5181b9a1cdd..590cc5ae104 100644 --- a/test/lit/passes/unsubtyping.wast +++ b/test/lit/passes/unsubtyping.wast @@ -818,6 +818,38 @@ ) ) +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $0 (func)) + + ;; CHECK: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (table $super 1 1 (ref null $super)) + (table $super 1 1 (ref null $super)) + + ;; CHECK: (elem $sub (ref null $sub)) + (elem $sub (ref null $sub)) + + ;; CHECK: (func $table-copy (type $0) + ;; CHECK-NEXT: (table.init $super $sub + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table-copy + ;; This requires $sub <: $super. + (table.init $super $sub + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) +) + (module ;; CHECK: (rec ;; CHECK-NEXT: (type $super (sub (struct))) diff --git a/test/spec/table_init.wast b/test/spec/table_init.wast new file mode 100644 index 00000000000..09cc0afdaf7 --- /dev/null +++ b/test/spec/table_init.wast @@ -0,0 +1,52 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s + +;; The elem is out of bounds, leading to a trap during initialization. +(assert_unlinkable + (module + (table $table 1 1 funcref) + (elem $elem (i32.const 1) $foo) + (func $foo) + ) + "trap" +) + +;; Now it is in bounds, with the elem offset reduced to 0. +(module + (table $table 1 1 funcref) + (elem $elem (i32.const 0) $foo) + (func $foo) +) + +;; The table begins with a function that returns zero. table.init will replace +;; it with one that returns 1. +(module + (type $i (func (result i32))) + + (table $table 10 funcref) + (elem $zero (i32.const 0) $zero) + (elem $one $one) + + (func $call (export "call") (result i32) + (call_indirect (type $i) (i32.const 0)) + ) + + (func $init (export "init") (result i32) + (table.init $table $one (i32.const 0) (i32.const 0) (i32.const 1)) + (call $call) + ) + + (func $zero (result i32) + (i32.const 0) + ) + + (func $one (result i32) + (i32.const 1) + ) +) + +;; First we get 0, then 1. +(assert_return (invoke "call") (i32.const 0)) +(assert_return (invoke "init") (i32.const 1)) +