diff options
29 files changed, 636 insertions, 45 deletions
diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 47ef4c600..80a62a197 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -590,6 +590,8 @@ instructions = [ ("struct.set", "makeStructSet(s)"), ("array.new", "makeArrayNewStatic(s, false)"), ("array.new_default", "makeArrayNewStatic(s, true)"), + ("array.new_data", "makeArrayNewSeg(s, NewData)"), + ("array.new_elem", "makeArrayNewSeg(s, NewElem)"), ("array.init_static", "makeArrayInitStatic(s)"), ("array.get", "makeArrayGet(s)"), ("array.get_s", "makeArrayGet(s, true)"), diff --git a/src/binaryen-c.h b/src/binaryen-c.h index f5347a65b..8da248f07 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1071,6 +1071,9 @@ BINARYEN_API BinaryenExpressionRef BinaryenArrayNew(BinaryenModuleRef module, BinaryenHeapType type, BinaryenExpressionRef size, BinaryenExpressionRef init); + +// TODO: BinaryenArrayNewSeg + BINARYEN_API BinaryenExpressionRef BinaryenArrayInit(BinaryenModuleRef module, BinaryenHeapType type, diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 67f58b5e6..929634c6c 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -47,9 +47,25 @@ switch (op[0]) { case '\0': if (op == "array.new"sv) { return makeArrayNewStatic(s, false); } goto parse_error; - case '_': - if (op == "array.new_default"sv) { return makeArrayNewStatic(s, true); } - goto parse_error; + case '_': { + switch (op[10]) { + case 'd': { + switch (op[11]) { + case 'a': + if (op == "array.new_data"sv) { return makeArrayNewSeg(s, NewData); } + goto parse_error; + case 'e': + if (op == "array.new_default"sv) { return makeArrayNewStatic(s, true); } + goto parse_error; + default: goto parse_error; + } + } + case 'e': + if (op == "array.new_elem"sv) { return makeArrayNewSeg(s, NewElem); } + goto parse_error; + default: goto parse_error; + } + } default: goto parse_error; } } @@ -3602,13 +3618,37 @@ switch (op[0]) { return *ret; } goto parse_error; - case '_': - if (op == "array.new_default"sv) { - auto ret = makeArrayNewStatic(ctx, pos, true); - CHECK_ERR(ret); - return *ret; + case '_': { + switch (op[10]) { + case 'd': { + switch (op[11]) { + case 'a': + if (op == "array.new_data"sv) { + auto ret = makeArrayNewSeg(ctx, pos, NewData); + CHECK_ERR(ret); + return *ret; + } + goto parse_error; + case 'e': + if (op == "array.new_default"sv) { + auto ret = makeArrayNewStatic(ctx, pos, true); + CHECK_ERR(ret); + return *ret; + } + goto parse_error; + default: goto parse_error; + } + } + case 'e': + if (op == "array.new_elem"sv) { + auto ret = makeArrayNewSeg(ctx, pos, NewElem); + CHECK_ERR(ret); + return *ret; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } default: goto parse_error; } } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 1e77630ab..5ae6a67ce 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -164,6 +164,7 @@ void ReFinalize::visitStructNew(StructNew* curr) { curr->finalize(); } void ReFinalize::visitStructGet(StructGet* curr) { curr->finalize(); } void ReFinalize::visitStructSet(StructSet* curr) { curr->finalize(); } void ReFinalize::visitArrayNew(ArrayNew* curr) { curr->finalize(); } +void ReFinalize::visitArrayNewSeg(ArrayNewSeg* curr) { curr->finalize(); } void ReFinalize::visitArrayInit(ArrayInit* curr) { curr->finalize(); } void ReFinalize::visitArrayGet(ArrayGet* curr) { curr->finalize(); } void ReFinalize::visitArraySet(ArraySet* curr) { curr->finalize(); } diff --git a/src/ir/cost.h b/src/ir/cost.h index ff9bade10..59b738b1b 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -636,6 +636,9 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> { CostType visitArrayNew(ArrayNew* curr) { return 4 + visit(curr->size) + maybeVisit(curr->init); } + CostType visitArrayNewSeg(ArrayNewSeg* curr) { + return 4 + visit(curr->offset) + visit(curr->size); + } CostType visitArrayInit(ArrayInit* curr) { CostType ret = 4; for (auto* child : curr->values) { diff --git a/src/ir/effects.h b/src/ir/effects.h index 2dfe616f9..70e9192c0 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -755,6 +755,11 @@ private: } } void visitArrayNew(ArrayNew* curr) {} + void visitArrayNewSeg(ArrayNewSeg* curr) { + // Traps on out of bounds access to segments or access to dropped + // segments. + parent.implicitTrap = true; + } void visitArrayInit(ArrayInit* curr) {} void visitArrayGet(ArrayGet* curr) { if (curr->ref->type.isNull()) { diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 9c132fd0c..0c4e28568 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -896,6 +896,26 @@ struct InfoCollector } addRoot(curr, PossibleContents::exactType(curr->type)); } + void visitArrayNewSeg(ArrayNewSeg* curr) { + if (curr->type == Type::unreachable) { + return; + } + auto heapType = curr->type.getHeapType(); + switch (curr->op) { + case NewData: { + Type elemType = heapType.getArray().element.type; + addRoot(DataLocation{heapType, 0}, + PossibleContents::fromType(elemType)); + return; + } + case NewElem: { + Type segType = getModule()->elementSegments[curr->segment]->type; + addRoot(DataLocation{heapType, 0}, PossibleContents::fromType(segType)); + return; + } + } + WASM_UNREACHABLE("unexpected op"); + } void visitArrayInit(ArrayInit* curr) { if (curr->type == Type::unreachable) { return; diff --git a/src/literal.h b/src/literal.h index 7d7c778bc..213713a1f 100644 --- a/src/literal.h +++ b/src/literal.h @@ -197,6 +197,10 @@ public: WASM_UNREACHABLE("unexpected type"); } } + + static Literal makeFromMemory(void* p, Type type); + static Literal makeFromMemory(void* p, const Field& field); + static Literal makeSignedMin(Type type) { switch (type.getBasic()) { case Type::i32: diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index d0f19f78e..6dbd54e73 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2205,6 +2205,26 @@ struct PrintExpressionContents o << ' '; TypeNamePrinter(o, wasm).print(curr->type.getHeapType()); } + void visitArrayNewSeg(ArrayNewSeg* curr) { + if (printUnreachableReplacement(curr)) { + return; + } + printMedium(o, "array.new_"); + switch (curr->op) { + case NewData: + printMedium(o, "data"); + + break; + case NewElem: + printMedium(o, "elem"); + break; + default: + WASM_UNREACHABLE("unexpected op"); + } + o << ' '; + TypeNamePrinter(o, wasm).print(curr->type.getHeapType()); + o << ' ' << curr->segment; + } void visitArrayInit(ArrayInit* curr) { if (printUnreachableReplacement(curr)) { return; @@ -2789,6 +2809,9 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> { void visitArrayNew(ArrayNew* curr) { maybePrintUnreachableReplacement(curr, curr->type); } + void visitArrayNewSeg(ArrayNewSeg* curr) { + maybePrintUnreachableReplacement(curr, curr->type); + } void visitArrayInit(ArrayInit* curr) { maybePrintUnreachableReplacement(curr, curr->type); } diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index 0cafd9d9d..93cc33563 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -32,6 +32,7 @@ namespace wasm { +// TODO: Add data segment, multiple memories (#5224) enum class ModuleElementKind { Function, Global, Tag, Table, ElementSegment }; typedef std::pair<ModuleElementKind, Name> ModuleElement; @@ -195,7 +196,10 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> { void visitAtomicNotify(AtomicNotify* curr) { usesMemory = true; } void visitAtomicFence(AtomicFence* curr) { usesMemory = true; } void visitMemoryInit(MemoryInit* curr) { usesMemory = true; } - void visitDataDrop(DataDrop* curr) { usesMemory = true; } + void visitDataDrop(DataDrop* curr) { + // TODO: Replace this with a use of a data segment (#5224). + usesMemory = true; + } void visitMemoryCopy(MemoryCopy* curr) { usesMemory = true; } void visitMemoryFill(MemoryFill* curr) { usesMemory = true; } void visitMemorySize(MemorySize* curr) { usesMemory = true; } @@ -227,6 +231,19 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> { maybeAdd(ModuleElement(ModuleElementKind::Tag, tag)); } } + void visitArrayNewSeg(ArrayNewSeg* curr) { + switch (curr->op) { + case NewData: + // TODO: Replace this with a use of the specific data segment (#5224). + usesMemory = true; + return; + case NewElem: + auto segment = module->elementSegments[curr->segment]->name; + maybeAdd(ModuleElement(ModuleElementKind::ElementSegment, segment)); + return; + } + WASM_UNREACHABLE("unexpected op"); + } }; struct RemoveUnusedModuleElements : public Pass { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index db123515a..b0128cbc5 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1107,6 +1107,7 @@ enum ASTNodes { StructSet = 0x06, StructNew = 0x07, StructNewDefault = 0x08, + ArrayNewElem = 0x10, ArrayGet = 0x13, ArrayGetS = 0x14, ArrayGetU = 0x15, @@ -1117,6 +1118,7 @@ enum ASTNodes { ArrayInitStatic = 0x1a, ArrayNew = 0x1b, ArrayNewDefault = 0x1c, + ArrayNewData = 0x1d, I31New = 0x20, I31GetS = 0x21, I31GetU = 0x22, @@ -1706,6 +1708,7 @@ public: bool maybeVisitStructGet(Expression*& out, uint32_t code); bool maybeVisitStructSet(Expression*& out, uint32_t code); bool maybeVisitArrayNew(Expression*& out, uint32_t code); + bool maybeVisitArrayNewSeg(Expression*& out, uint32_t code); bool maybeVisitArrayInit(Expression*& out, uint32_t code); bool maybeVisitArrayGet(Expression*& out, uint32_t code); bool maybeVisitArraySet(Expression*& out, uint32_t code); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index df036b3b4..1eb31157d 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -935,6 +935,20 @@ public: ret->finalize(); return ret; } + ArrayNewSeg* makeArrayNewSeg(ArrayNewSegOp op, + HeapType type, + Index seg, + Expression* offset, + Expression* size) { + auto* ret = wasm.allocator.alloc<ArrayNewSeg>(); + ret->op = op; + ret->segment = seg; + ret->offset = offset; + ret->size = size; + ret->type = Type(type, NonNullable); + ret->finalize(); + return ret; + } ArrayInit* makeArrayInit(HeapType type, const std::vector<Expression*>& values) { auto* ret = wasm.allocator.alloc<ArrayInit>(); diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index ff0f41d50..85a088dd8 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -664,6 +664,15 @@ switch (DELEGATE_ID) { DELEGATE_END(ArrayNew); break; } + case Expression::Id::ArrayNewSegId: { + DELEGATE_START(ArrayNewSeg); + DELEGATE_FIELD_INT(ArrayNewSeg, op); + DELEGATE_FIELD_INT(ArrayNewSeg, segment); + DELEGATE_FIELD_CHILD(ArrayNewSeg, size); + DELEGATE_FIELD_CHILD(ArrayNewSeg, offset); + DELEGATE_END(ArrayNewSeg); + break; + } case Expression::Id::ArrayInitId: { DELEGATE_START(ArrayInit); DELEGATE_FIELD_CHILD_VECTOR(ArrayInit, values); diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index b88a55556..dbdef3a86 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -77,6 +77,7 @@ DELEGATE(StructNew); DELEGATE(StructGet); DELEGATE(StructSet); DELEGATE(ArrayNew); +DELEGATE(ArrayNewSeg); DELEGATE(ArrayInit); DELEGATE(ArrayGet); DELEGATE(ArraySet); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 8f7418719..9599d81cd 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1700,6 +1700,7 @@ public: return Literal(std::make_shared<GCData>(curr->type.getHeapType(), data), curr->type.getHeapType()); } + Flow visitArrayNewSeg(ArrayNewSeg* curr) { WASM_UNREACHABLE("unimp"); } Flow visitArrayInit(ArrayInit* curr) { NOTE_ENTER("ArrayInit"); Index num = curr->values.size(); @@ -2187,6 +2188,10 @@ public: NOTE_ENTER("SIMDLoadStoreLane"); return Flow(NONCONSTANT_FLOW); } + Flow visitArrayNewSeg(ArrayNewSeg* curr) { + NOTE_ENTER("ArrayNewSeg"); + return Flow(NONCONSTANT_FLOW); + } Flow visitPop(Pop* curr) { NOTE_ENTER("Pop"); return Flow(NONCONSTANT_FLOW); @@ -3494,6 +3499,63 @@ public: } return {}; } + Flow visitArrayNewSeg(ArrayNewSeg* curr) { + NOTE_ENTER("ArrayNewSeg"); + auto offsetFlow = self()->visit(curr->offset); + if (offsetFlow.breaking()) { + return offsetFlow; + } + auto sizeFlow = self()->visit(curr->size); + if (sizeFlow.breaking()) { + return sizeFlow; + } + + auto offset = offsetFlow.getSingleValue().geti32(); + auto size = sizeFlow.getSingleValue().geti32(); + + auto heapType = curr->type.getHeapType(); + const auto& element = heapType.getArray().element; + auto elemType = heapType.getArray().element.type; + + Literals contents; + contents.reserve(size); + + switch (curr->op) { + case NewData: { + assert(curr->segment < wasm.dataSegments.size()); + assert(elemType.isNumber()); + const auto& seg = *wasm.dataSegments[curr->segment]; + auto elemBytes = element.getByteSize(); + auto end = (uint64_t)offset + size * elemBytes; + if ((size != 0ull && droppedSegments.count(curr->segment)) || + end > seg.data.size()) { + trap("out of bounds segment access in array.new_data"); + } + for (Index i = offset; i < end; i += elemBytes) { + auto addr = (void*)&seg.data[i]; + contents.push_back(Literal::makeFromMemory(addr, element)); + } + break; + } + case NewElem: { + assert(curr->segment < wasm.elementSegments.size()); + const auto& seg = *wasm.elementSegments[curr->segment]; + auto end = (uint64_t)offset + size; + // TODO: Handle dropped element segments once we support those. + if (end > seg.data.size()) { + trap("out of bounds segment access in array.new_elem"); + } + for (Index i = offset; i < end; ++i) { + auto val = self()->visit(seg.data[i]).getSingleValue(); + contents.push_back(val); + } + break; + } + default: + WASM_UNREACHABLE("unexpected op"); + } + return Literal(std::make_shared<GCData>(heapType, contents), heapType); + } Flow visitTry(Try* curr) { NOTE_ENTER("Try"); try { diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index 888f7d009..67459dba9 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -293,6 +293,7 @@ private: Expression* makeStructGet(Element& s, bool signed_ = false); Expression* makeStructSet(Element& s); Expression* makeArrayNewStatic(Element& s, bool default_); + Expression* makeArrayNewSeg(Element& s, ArrayNewSegOp op); Expression* makeArrayInitStatic(Element& s); Expression* makeArrayGet(Element& s, bool signed_ = false); Expression* makeArraySet(Element& s); diff --git a/src/wasm.h b/src/wasm.h index 2b890177e..3daff2c4c 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -573,6 +573,11 @@ enum RefAsOp { ExternExternalize, }; +enum ArrayNewSegOp { + NewData, + NewElem, +}; + enum BrOnOp { BrOnNull, BrOnNonNull, @@ -719,6 +724,7 @@ public: StructGetId, StructSetId, ArrayNewId, + ArrayNewSegId, ArrayInitId, ArrayGetId, ArraySetId, @@ -1605,6 +1611,18 @@ public: void finalize(); }; +class ArrayNewSeg : public SpecificExpression<Expression::ArrayNewSegId> { +public: + ArrayNewSeg(MixedArena& allocator) {} + + ArrayNewSegOp op; + Index segment; + Expression* offset; + Expression* size; + + void finalize(); +}; + class ArrayInit : public SpecificExpression<Expression::ArrayInitId> { public: ArrayInit(MixedArena& allocator) : values(allocator) {} diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 8d60a4829..7890c7a42 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -235,6 +235,57 @@ Literal Literal::makeNegOne(Type type) { return makeFromInt32(-1, type); } +Literal Literal::makeFromMemory(void* p, Type type) { + assert(type.isNumber()); + switch (type.getBasic()) { + case Type::i32: { + int32_t i; + memcpy(&i, p, sizeof(i)); + return Literal(i); + } + case Type::i64: { + int64_t i; + memcpy(&i, p, sizeof(i)); + return Literal(i); + } + case Type::f32: { + int32_t i; + memcpy(&i, p, sizeof(i)); + return Literal(bit_cast<float>(i)); + } + case Type::f64: { + int64_t i; + memcpy(&i, p, sizeof(i)); + return Literal(bit_cast<double>(i)); + } + case Type::v128: { + uint8_t bytes[16]; + memcpy(bytes, p, sizeof(bytes)); + return Literal(bytes); + } + default: + WASM_UNREACHABLE("unexpected type"); + } +} + +Literal Literal::makeFromMemory(void* p, const Field& field) { + switch (field.packedType) { + case Field::not_packed: + return makeFromMemory(p, field.type); + case Field::i8: { + int8_t i; + memcpy(&i, p, sizeof(i)); + return Literal(int32_t(i)); + } + case Field::i16: { + int16_t i; + memcpy(&i, p, sizeof(i)); + return Literal(int32_t(i)); + } + } + WASM_UNREACHABLE("unexpected type"); +} + Literal Literal::standardizeNaN(const Literal& input) { if (!std::isnan(input.getFloat())) { return input; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index d023a56c6..e2bb0075b 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3122,12 +3122,15 @@ void WasmBinaryBuilder::readDataSegments() { } curr->setName(Name::fromInt(i), false); curr->isPassive = flags & BinaryConsts::IsPassive; - Index memIdx = 0; - if (flags & BinaryConsts::HasIndex) { - memIdx = getU32LEB(); - } - memoryRefs[memIdx].push_back(&curr->memory); - if (!curr->isPassive) { + if (curr->isPassive) { + curr->memory = Name(); + curr->offset = nullptr; + } else { + Index memIdx = 0; + if (flags & BinaryConsts::HasIndex) { + memIdx = getU32LEB(); + } + memoryRefs[memIdx].push_back(&curr->memory); curr->offset = readExpression(); } auto size = getU32LEB(); @@ -3973,6 +3976,9 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) { if (maybeVisitArrayNew(curr, opcode)) { break; } + if (maybeVisitArrayNewSeg(curr, opcode)) { + break; + } if (maybeVisitArrayInit(curr, opcode)) { break; } @@ -7058,6 +7064,20 @@ bool WasmBinaryBuilder::maybeVisitArrayNew(Expression*& out, uint32_t code) { return false; } +bool WasmBinaryBuilder::maybeVisitArrayNewSeg(Expression*& out, uint32_t code) { + if (code == BinaryConsts::ArrayNewData || + code == BinaryConsts::ArrayNewElem) { + auto op = code == BinaryConsts::ArrayNewData ? NewData : NewElem; + auto heapType = getIndexedHeapType(); + auto seg = getU32LEB(); + auto* size = popNonVoidExpression(); + auto* offset = popNonVoidExpression(); + out = Builder(wasm).makeArrayNewSeg(op, heapType, seg, offset, size); + return true; + } + return false; +} + bool WasmBinaryBuilder::maybeVisitArrayInit(Expression*& out, uint32_t code) { if (code == BinaryConsts::ArrayInitStatic) { auto heapType = getIndexedHeapType(); diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index a54226194..a9fd2de77 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -2892,6 +2892,15 @@ Expression* SExpressionWasmBuilder::makeArrayNewStatic(Element& s, return Builder(wasm).makeArrayNew(heapType, size, init); } +Expression* SExpressionWasmBuilder::makeArrayNewSeg(Element& s, + ArrayNewSegOp op) { + auto heapType = parseHeapType(*s[1]); + Index seg = parseIndex(*s[2]); + Expression* offset = parseExpression(*s[3]); + Expression* size = parseExpression(*s[4]); + return Builder(wasm).makeArrayNewSeg(op, heapType, seg, offset, size); +} + Expression* SExpressionWasmBuilder::makeArrayInitStatic(Element& s) { auto heapType = parseHeapType(*s[1]); size_t i = 2; @@ -3292,9 +3301,6 @@ void SExpressionWasmBuilder::parseMemory(Element& s, bool preParseImport) { } void SExpressionWasmBuilder::parseData(Element& s) { - if (wasm.memories.empty()) { - throw ParseException("data but no memory", s.line, s.col); - } Index i = 1; Name name = Name::fromInt(dataCounter++); bool hasExplicitName = false; diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index f30e4be82..daeac60d6 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2132,6 +2132,22 @@ void BinaryInstWriter::visitArrayNew(ArrayNew* curr) { parent.writeIndexedHeapType(curr->type.getHeapType()); } +void BinaryInstWriter::visitArrayNewSeg(ArrayNewSeg* curr) { + o << int8_t(BinaryConsts::GCPrefix); + switch (curr->op) { + case NewData: + o << U32LEB(BinaryConsts::ArrayNewData); + break; + case NewElem: + o << U32LEB(BinaryConsts::ArrayNewElem); + break; + default: + WASM_UNREACHABLE("unexpected op"); + } + parent.writeIndexedHeapType(curr->type.getHeapType()); + o << U32LEB(curr->segment); +} + void BinaryInstWriter::visitArrayInit(ArrayInit* curr) { o << int8_t(BinaryConsts::GCPrefix); o << U32LEB(BinaryConsts::ArrayInitStatic); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index cb3841197..c1316f283 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -451,6 +451,7 @@ public: void visitStructGet(StructGet* curr); void visitStructSet(StructSet* curr); void visitArrayNew(ArrayNew* curr); + void visitArrayNewSeg(ArrayNewSeg* curr); void visitArrayInit(ArrayInit* curr); void visitArrayGet(ArrayGet* curr); void visitArraySet(ArraySet* curr); @@ -2683,6 +2684,73 @@ void FunctionValidator::visitArrayNew(ArrayNew* curr) { } } +void FunctionValidator::visitArrayNewSeg(ArrayNewSeg* curr) { + shouldBeTrue(getModule()->features.hasGC(), + curr, + "array.new_{data, elem} requires gc [--enable-gc]"); + shouldBeEqualOrFirstIsUnreachable( + curr->offset->type, + Type(Type::i32), + curr, + "array.new_{data, elem} offset must be an i32"); + shouldBeEqualOrFirstIsUnreachable( + curr->size->type, + Type(Type::i32), + curr, + "array.new_{data, elem} size must be an i32"); + switch (curr->op) { + case NewData: + if (!shouldBeTrue(curr->segment < getModule()->dataSegments.size(), + curr, + "array.new_data segment index out of bounds")) { + return; + } + break; + case NewElem: + if (!shouldBeTrue(curr->segment < getModule()->elementSegments.size(), + curr, + "array.new_elem segment index out of bounds")) { + return; + } + break; + default: + WASM_UNREACHABLE("unexpected op"); + } + if (curr->type == Type::unreachable) { + return; + } + if (!shouldBeTrue( + curr->type.isRef(), + curr, + "array.new_{data, elem} type should be an array reference")) { + return; + } + auto heapType = curr->type.getHeapType(); + if (!shouldBeTrue( + heapType.isArray(), + curr, + "array.new_{data, elem} type shoudl be an array reference")) { + return; + } + auto elemType = heapType.getArray().element.type; + switch (curr->op) { + case NewData: + shouldBeTrue(elemType.isNumber(), + curr, + "array.new_data result element type should be numeric"); + break; + case NewElem: + shouldBeSubType(getModule()->elementSegments[curr->segment]->type, + elemType, + curr, + "array.new_elem segment type should be a subtype of the " + "result element type"); + break; + default: + WASM_UNREACHABLE("unexpected op"); + } +} + void FunctionValidator::visitArrayInit(ArrayInit* curr) { shouldBeTrue(getModule()->features.hasGC(), curr, diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 6347d83e5..a92b62343 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1053,7 +1053,12 @@ void ArrayNew::finalize() { if (size->type == Type::unreachable || (init && init->type == Type::unreachable)) { type = Type::unreachable; - return; + } +} + +void ArrayNewSeg::finalize() { + if (offset->type == Type::unreachable || size->type == Type::unreachable) { + type = Type::unreachable; } } diff --git a/src/wasm/wat-parser.cpp b/src/wasm/wat-parser.cpp index 1c1349e47..7705c9e5c 100644 --- a/src/wasm/wat-parser.cpp +++ b/src/wasm/wat-parser.cpp @@ -1907,6 +1907,8 @@ template<typename Ctx> Result<typename Ctx::InstrT> makeStructSet(Ctx&, Index); template<typename Ctx> Result<typename Ctx::InstrT> makeArrayNewStatic(Ctx&, Index, bool default_); template<typename Ctx> +Result<typename Ctx::InstrT> makeArrayNewSeg(Ctx&, Index, ArrayNewSegOp op); +template<typename Ctx> Result<typename Ctx::InstrT> makeArrayInitStatic(Ctx&, Index); template<typename Ctx> Result<typename Ctx::InstrT> makeArrayGet(Ctx&, Index, bool signed_ = false); @@ -2892,6 +2894,12 @@ makeArrayNewStatic(Ctx& ctx, Index pos, bool default_) { } template<typename Ctx> +Result<typename Ctx::InstrT> +makeArrayNewSeg(Ctx& ctx, Index pos, ArrayNewSegOp op) { + return ctx.in.err("unimplemented instruction"); +} + +template<typename Ctx> Result<typename Ctx::InstrT> makeArrayInitStatic(Ctx& ctx, Index pos) { return ctx.in.err("unimplemented instruction"); } diff --git a/src/wasm2js.h b/src/wasm2js.h index 2ae22de09..982184510 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2304,6 +2304,10 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitArrayNewSeg(ArrayNewSeg* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } Ref visitArrayInit(ArrayInit* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 89c49f2d5..9200bac2a 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -97,25 +97,25 @@ StructNewId: 60 StructGetId: 61 StructSetId: 62 ArrayNewId: 63 -ArrayInitId: 64 -ArrayGetId: 65 -ArraySetId: 66 -ArrayLenId: 67 -ArrayCopy: 68 -RefAs: 69 -StringNew: 70 -StringConst: 71 -StringMeasure: 72 -StringEncode: 73 -StringConcat: 74 -StringEq: 75 -StringAs: 76 -StringWTF8Advance: 77 -StringWTF16Get: 78 -StringIterNext: 79 -StringIterMove: 80 -StringSliceWTF: 81 -StringSliceIter: 82 +ArrayInitId: 65 +ArrayGetId: 66 +ArraySetId: 67 +ArrayLenId: 68 +ArrayCopy: 69 +RefAs: 70 +StringNew: 71 +StringConst: 72 +StringMeasure: 73 +StringEncode: 74 +StringConcat: 75 +StringEq: 76 +StringAs: 77 +StringWTF8Advance: 78 +StringWTF16Get: 79 +StringIterNext: 80 +StringIterMove: 81 +StringSliceWTF: 82 +StringSliceIter: 83 getExpressionInfo={"id":15,"type":4,"op":6} (f32.neg (f32.const -33.61199951171875) diff --git a/test/lit/arrays.wast b/test/lit/arrays.wast index 31aead1cd..4a262ef13 100644 --- a/test/lit/arrays.wast +++ b/test/lit/arrays.wast @@ -10,24 +10,46 @@ ;; RUN: wasm-opt %s -all -S -o - | wasm-opt -all -S -o - | filecheck %s (module - (type $byte-array (array (mut i8))) ;; CHECK: (type $arrayref_=>_i32 (func (param arrayref) (result i32))) ;; CHECK: (type $ref|array|_=>_i32 (func (param (ref array)) (result i32))) ;; CHECK: (type $nullref_=>_i32 (func (param nullref) (result i32))) - ;; CHECK: (func $len (param $a (ref array)) (result i32) - ;; CHECK-NEXT: (array.len - ;; CHECK-NEXT: (local.get $a) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) + ;; CHECK: (type $none_=>_ref|$byte-array| (func (result (ref $byte-array)))) + + ;; CHECK: (type $none_=>_ref|$func-array| (func (result (ref $func-array)))) + + ;; CHECK: (type $byte-array (array (mut i8))) ;; ROUNDTRIP: (type $arrayref_=>_i32 (func (param arrayref) (result i32))) ;; ROUNDTRIP: (type $ref|array|_=>_i32 (func (param (ref array)) (result i32))) ;; ROUNDTRIP: (type $nullref_=>_i32 (func (param nullref) (result i32))) + ;; ROUNDTRIP: (type $none_=>_ref|$byte-array| (func (result (ref $byte-array)))) + + ;; ROUNDTRIP: (type $none_=>_ref|$func-array| (func (result (ref $func-array)))) + + ;; ROUNDTRIP: (type $byte-array (array (mut i8))) + (type $byte-array (array (mut i8))) + ;; CHECK: (type $func-array (array (mut funcref))) + ;; ROUNDTRIP: (type $func-array (array (mut funcref))) + (type $func-array (array (mut funcref))) + + ;; CHECK: (data "hello") + ;; ROUNDTRIP: (data "hello") + (data "hello") + ;; CHECK: (elem func $len $impossible-len $unreachable-len) + ;; ROUNDTRIP: (elem func $len $impossible-len $unreachable-len) + (elem func $len $impossible-len $unreachable-len) + + + ;; CHECK: (func $len (param $a (ref array)) (result i32) + ;; CHECK-NEXT: (array.len + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; ROUNDTRIP: (func $len (param $a (ref array)) (result i32) ;; ROUNDTRIP-NEXT: (array.len ;; ROUNDTRIP-NEXT: (local.get $a) @@ -85,4 +107,42 @@ (local.get $a) ) ) + + ;; CHECK: (func $new-data (result (ref $byte-array)) + ;; CHECK-NEXT: (array.new_data $byte-array 0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (func $new-data (result (ref $byte-array)) + ;; ROUNDTRIP-NEXT: (array.new_data $byte-array 0 + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: (i32.const 5) + ;; ROUNDTRIP-NEXT: ) + ;; ROUNDTRIP-NEXT: ) + (func $new-data (result (ref $byte-array)) + (array.new_data $byte-array 0 + (i32.const 0) + (i32.const 5) + ) + ) + + ;; CHECK: (func $new-elem (result (ref $func-array)) + ;; CHECK-NEXT: (array.new_elem $func-array 0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (func $new-elem (result (ref $func-array)) + ;; ROUNDTRIP-NEXT: (array.new_elem $func-array 0 + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: (i32.const 3) + ;; ROUNDTRIP-NEXT: ) + ;; ROUNDTRIP-NEXT: ) + (func $new-elem (result (ref $func-array)) + (array.new_elem $func-array 0 + (i32.const 0) + (i32.const 3) + ) + ) ) diff --git a/test/spec/array-new-data.wast b/test/spec/array-new-data.wast new file mode 100644 index 000000000..8ef62cb87 --- /dev/null +++ b/test/spec/array-new-data.wast @@ -0,0 +1,79 @@ +(module + (type $vec (array i8)) + (type $mvec (array (mut i8))) + + (data "\00\01\02\03\04") + + (func $new (export "new") (result (ref $vec)) + (array.new_data $vec 0 (i32.const 1) (i32.const 3)) + ) + + (func $get (param $i i32) (param $v (ref $vec)) (result i32) + (array.get_u $vec (local.get $v) (local.get $i)) + ) + (func (export "get") (param $i i32) (result i32) + (call $get (local.get $i) (call $new)) + ) + + (func $set_get (param $i i32) (param $v (ref $mvec)) (param $y i32) (result i32) + (array.set $mvec (local.get $v) (local.get $i) (local.get $y)) + (array.get_u $mvec (local.get $v) (local.get $i)) + ) + (func (export "set_get") (param $i i32) (param $y i32) (result i32) + (call $set_get (local.get $i) + (array.new_data $mvec 0 (i32.const 1) (i32.const 3)) + (local.get $y) + ) + ) + + (func $len (param $v (ref array)) (result i32) + (array.len (local.get $v)) + ) + (func (export "len") (result i32) + (call $len (call $new)) + ) +) + +(assert_return (invoke "get" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "set_get" (i32.const 1) (i32.const 7)) (i32.const 7)) +(assert_return (invoke "len") (i32.const 3)) + +(module + (type $vec (array i32)) + (type $mvec (array (mut i8))) + + (data "\00\01\00\00\00\02\00\00\00\03\00\00\00\04\00\00\00") + + (func $new (export "new") (result (ref $vec)) + (array.new_data $vec 0 (i32.const 1) (i32.const 3)) + ) + + (func $get (param $i i32) (param $v (ref $vec)) (result i32) + (array.get $vec (local.get $v) (local.get $i)) + ) + (func (export "get") (param $i i32) (result i32) + (call $get (local.get $i) (call $new)) + ) + + (func $set_get (param $i i32) (param $v (ref $mvec)) (param $y i32) (result i32) + (array.set $mvec (local.get $v) (local.get $i) (local.get $y)) + (array.get $mvec (local.get $v) (local.get $i)) + ) + (func (export "set_get") (param $i i32) (param $y i32) (result i32) + (call $set_get (local.get $i) + (array.new_data $mvec 0 (i32.const 1) (i32.const 3)) + (local.get $y) + ) + ) + + (func $len (param $v (ref array)) (result i32) + (array.len (local.get $v)) + ) + (func (export "len") (result i32) + (call $len (call $new)) + ) +) + +(assert_return (invoke "get" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "set_get" (i32.const 1) (i32.const 7)) (i32.const 7)) +(assert_return (invoke "len") (i32.const 3)) diff --git a/test/spec/array-new-elem.wast b/test/spec/array-new-elem.wast new file mode 100644 index 000000000..a61122bd6 --- /dev/null +++ b/test/spec/array-new-elem.wast @@ -0,0 +1,48 @@ +(module + (type $vec (array funcref)) + (type $mvec (array (mut funcref))) + (type $f (func (result i32))) + + (elem func $ret0 $ret1 $ret2 $ret3 $ret4) + + (func $ret0 (type $f) (i32.const 0)) + (func $ret1 (type $f) (i32.const 1)) + (func $ret2 (type $f) (i32.const 2)) + (func $ret3 (type $f) (i32.const 3)) + (func $ret4 (type $f) (i32.const 4)) + + (func $new (export "new") (result (ref $vec)) + (array.new_elem $vec 0 (i32.const 1) (i32.const 3)) + ) + + (func $get (param $i i32) (param $v (ref $vec)) (result i32) + (call_ref $f (ref.cast_static $f (array.get $vec (local.get $v) (local.get $i)))) + ) + (func (export "get") (param $i i32) (result i32) + (call $get (local.get $i) (call $new)) + ) + + (func $set_get (param $i i32) (param $v (ref $mvec)) (param $y i32) (result i32) + (array.set $mvec (local.get $v) (local.get $i) (array.get $mvec (local.get $v) (local.get $y))) + (call_ref $f (ref.cast_static $f (array.get $mvec (local.get $v) (local.get $i)))) + ) + (func (export "set_get") (param $i i32) (param $y i32) (result i32) + (call $set_get + (local.get $i) + (array.new_elem $mvec 0 (i32.const 1) (i32.const 3)) + (local.get $y) + ) + ) + + (func $len (param $v (ref array)) (result i32) + (array.len (local.get $v)) + ) + (func (export "len") (result i32) + (call $len (call $new)) + ) +) + +(assert_return (invoke "get" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "get" (i32.const 1)) (i32.const 2)) +(assert_return (invoke "set_get" (i32.const 0) (i32.const 2)) (i32.const 3)) +(assert_return (invoke "len") (i32.const 3)) |