summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/gen-s-parser.py3
-rw-r--r--src/gen-s-parser.inc58
-rw-r--r--src/ir/ReFinalize.cpp2
-rw-r--r--src/ir/cost.h8
-rw-r--r--src/ir/effects.h19
-rw-r--r--src/ir/module-utils.cpp7
-rw-r--r--src/ir/possible-contents.cpp17
-rw-r--r--src/passes/Print.cpp25
-rw-r--r--src/passes/RemoveUnusedModuleElements.cpp11
-rw-r--r--src/wasm-binary.h5
-rw-r--r--src/wasm-builder.h28
-rw-r--r--src/wasm-delegations-fields.def20
-rw-r--r--src/wasm-delegations.def2
-rw-r--r--src/wasm-interpreter.h136
-rw-r--r--src/wasm-s-parser.h2
-rw-r--r--src/wasm.h34
-rw-r--r--src/wasm/wasm-binary.cpp50
-rw-r--r--src/wasm/wasm-s-parser.cpp22
-rw-r--r--src/wasm/wasm-stack.cpp31
-rw-r--r--src/wasm/wasm-validator.cpp122
-rw-r--r--src/wasm/wasm.cpp18
-rw-r--r--src/wasm/wat-parser.cpp35
-rw-r--r--src/wasm2js.h8
-rw-r--r--test/binaryen.js/kitchen-sink.js.txt28
-rw-r--r--test/lit/heap-types.wast101
-rw-r--r--test/lit/wat-kitchen-sink.wast18
-rw-r--r--test/spec/bulk-array.wast528
27 files changed, 1095 insertions, 243 deletions
diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py
index d9c01f53f..70aa2863d 100755
--- a/scripts/gen-s-parser.py
+++ b/scripts/gen-s-parser.py
@@ -601,6 +601,9 @@ instructions = [
("array.set", "makeArraySet(s)"),
("array.len", "makeArrayLen(s)"),
("array.copy", "makeArrayCopy(s)"),
+ ("array.fill", "makeArrayFill(s)"),
+ ("array.init_data", "makeArrayInit(s, InitData)"),
+ ("array.init_elem", "makeArrayInit(s, InitElem)"),
("ref.is_func", "makeRefTest(s, Type(HeapType::func, NonNullable))"),
("ref.is_i31", "makeRefTest(s, Type(HeapType::i31, NonNullable))"),
("ref.as_non_null", "makeRefAs(s, RefAsNonNull)"),
diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc
index cbd2c027c..64027fc59 100644
--- a/src/gen-s-parser.inc
+++ b/src/gen-s-parser.inc
@@ -16,6 +16,9 @@ switch (buf[0]) {
case 'c':
if (op == "array.copy"sv) { return makeArrayCopy(s); }
goto parse_error;
+ case 'f':
+ if (op == "array.fill"sv) { return makeArrayFill(s); }
+ goto parse_error;
case 'g': {
switch (buf[9]) {
case '\0':
@@ -35,9 +38,20 @@ switch (buf[0]) {
default: goto parse_error;
}
}
- case 'i':
- if (op == "array.init_static"sv) { return makeArrayNewFixed(s); }
- goto parse_error;
+ case 'i': {
+ switch (buf[11]) {
+ case 'd':
+ if (op == "array.init_data"sv) { return makeArrayInit(s, InitData); }
+ goto parse_error;
+ case 'e':
+ if (op == "array.init_elem"sv) { return makeArrayInit(s, InitElem); }
+ goto parse_error;
+ case 's':
+ if (op == "array.init_static"sv) { return makeArrayNewFixed(s); }
+ goto parse_error;
+ default: goto parse_error;
+ }
+ }
case 'l':
if (op == "array.len"sv) { return makeArrayLen(s); }
goto parse_error;
@@ -3607,6 +3621,13 @@ switch (buf[0]) {
return *ret;
}
goto parse_error;
+ case 'f':
+ if (op == "array.fill"sv) {
+ auto ret = makeArrayFill(ctx, pos);
+ CHECK_ERR(ret);
+ return *ret;
+ }
+ goto parse_error;
case 'g': {
switch (buf[9]) {
case '\0':
@@ -3638,13 +3659,32 @@ switch (buf[0]) {
default: goto parse_error;
}
}
- case 'i':
- if (op == "array.init_static"sv) {
- auto ret = makeArrayNewFixed(ctx, pos);
- CHECK_ERR(ret);
- return *ret;
+ case 'i': {
+ switch (buf[11]) {
+ case 'd':
+ if (op == "array.init_data"sv) {
+ auto ret = makeArrayInit(ctx, pos, InitData);
+ CHECK_ERR(ret);
+ return *ret;
+ }
+ goto parse_error;
+ case 'e':
+ if (op == "array.init_elem"sv) {
+ auto ret = makeArrayInit(ctx, pos, InitElem);
+ CHECK_ERR(ret);
+ return *ret;
+ }
+ goto parse_error;
+ case 's':
+ if (op == "array.init_static"sv) {
+ auto ret = makeArrayNewFixed(ctx, pos);
+ CHECK_ERR(ret);
+ return *ret;
+ }
+ goto parse_error;
+ default: goto parse_error;
}
- goto parse_error;
+ }
case 'l':
if (op == "array.len"sv) {
auto ret = makeArrayLen(ctx, pos);
diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp
index 9c4946342..9d68a9fe8 100644
--- a/src/ir/ReFinalize.cpp
+++ b/src/ir/ReFinalize.cpp
@@ -170,6 +170,8 @@ void ReFinalize::visitArrayGet(ArrayGet* curr) { curr->finalize(); }
void ReFinalize::visitArraySet(ArraySet* curr) { curr->finalize(); }
void ReFinalize::visitArrayLen(ArrayLen* curr) { curr->finalize(); }
void ReFinalize::visitArrayCopy(ArrayCopy* curr) { curr->finalize(); }
+void ReFinalize::visitArrayFill(ArrayFill* curr) { curr->finalize(); }
+void ReFinalize::visitArrayInit(ArrayInit* curr) { curr->finalize(); }
void ReFinalize::visitRefAs(RefAs* curr) { curr->finalize(); }
void ReFinalize::visitStringNew(StringNew* curr) { curr->finalize(); }
void ReFinalize::visitStringConst(StringConst* curr) { curr->finalize(); }
diff --git a/src/ir/cost.h b/src/ir/cost.h
index 3e12318b6..c19441555 100644
--- a/src/ir/cost.h
+++ b/src/ir/cost.h
@@ -661,6 +661,14 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> {
return 6 + visit(curr->destRef) + visit(curr->destIndex) +
visit(curr->srcRef) + visit(curr->srcIndex) + visit(curr->length);
}
+ CostType visitArrayFill(ArrayFill* curr) {
+ return 6 + visit(curr->ref) + visit(curr->index) + visit(curr->value) +
+ visit(curr->size);
+ }
+ CostType visitArrayInit(ArrayInit* curr) {
+ return 6 + visit(curr->ref) + visit(curr->index) + visit(curr->offset) +
+ visit(curr->size);
+ }
CostType visitRefAs(RefAs* curr) { return 1 + visit(curr->value); }
CostType visitStringNew(StringNew* curr) {
return 8 + visit(curr->ptr) + maybeVisit(curr->length) +
diff --git a/src/ir/effects.h b/src/ir/effects.h
index 59ef8bace..c5251ae64 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -799,6 +799,25 @@ private:
// traps when a ref is null, or when out of bounds.
parent.implicitTrap = true;
}
+ void visitArrayFill(ArrayFill* curr) {
+ if (curr->ref->type.isNull()) {
+ parent.trap = true;
+ return;
+ }
+ parent.writesArray = true;
+ // Traps when the destination is null or when out of bounds.
+ parent.implicitTrap = true;
+ }
+ void visitArrayInit(ArrayInit* curr) {
+ if (curr->ref->type.isNull()) {
+ parent.trap = true;
+ return;
+ }
+ parent.writesArray = true;
+ // Traps when the destination is null, when out of bounds in source or
+ // destination, or when the source segment has been dropped.
+ parent.implicitTrap = true;
+ }
void visitRefAs(RefAs* curr) {
if (curr->op == ExternInternalize || curr->op == ExternExternalize) {
// These conversions are infallible.
diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp
index a2abcd5b5..efab9e20b 100644
--- a/src/ir/module-utils.cpp
+++ b/src/ir/module-utils.cpp
@@ -74,6 +74,13 @@ struct CodeScanner
counts.note(curr->type);
} else if (curr->is<ArrayNewFixed>()) {
counts.note(curr->type);
+ } else if (auto* copy = curr->dynCast<ArrayCopy>()) {
+ counts.note(copy->destRef->type);
+ counts.note(copy->srcRef->type);
+ } else if (auto* fill = curr->dynCast<ArrayFill>()) {
+ counts.note(fill->ref->type);
+ } else if (auto* init = curr->dynCast<ArrayInit>()) {
+ counts.note(init->ref->type);
} else if (auto* cast = curr->dynCast<RefCast>()) {
counts.note(cast->type);
} else if (auto* cast = curr->dynCast<RefTest>()) {
diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp
index 05faf7b1e..8ed2118fe 100644
--- a/src/ir/possible-contents.cpp
+++ b/src/ir/possible-contents.cpp
@@ -988,7 +988,22 @@ struct InfoCollector
auto* set = builder.makeArraySet(curr->destRef, curr->destIndex, get);
visitArraySet(set);
}
-
+ void visitArrayFill(ArrayFill* curr) {
+ if (curr->type == Type::unreachable) {
+ return;
+ }
+ Builder builder(*getModule());
+ auto* set = builder.makeArraySet(curr->ref, curr->index, curr->value);
+ visitArraySet(set);
+ }
+ void visitArrayInit(ArrayInit* curr) {
+ if (curr->type == Type::unreachable) {
+ return;
+ }
+ // TODO: Modeling the write to the array can be similar to the above, but
+ // how should the read from the segment be modeled?
+ WASM_UNREACHABLE("unimplemented");
+ }
void visitStringNew(StringNew* curr) {
if (curr->type == Type::unreachable) {
return;
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index 9f765bb79..650a17203 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -2273,7 +2273,6 @@ struct PrintExpressionContents
switch (curr->op) {
case NewData:
printMedium(o, "data");
-
break;
case NewElem:
printMedium(o, "elem");
@@ -2327,6 +2326,30 @@ struct PrintExpressionContents
o << ' ';
TypeNamePrinter(o, wasm).print(curr->srcRef->type.getHeapType());
}
+ void visitArrayFill(ArrayFill* curr) {
+ if (printUnreachableOrNullReplacement(curr->ref)) {
+ return;
+ }
+ printMedium(o, "array.fill ");
+ TypeNamePrinter(o, wasm).print(curr->ref->type.getHeapType());
+ }
+ void visitArrayInit(ArrayInit* curr) {
+ if (printUnreachableOrNullReplacement(curr->ref)) {
+ return;
+ }
+ switch (curr->op) {
+ case InitData:
+ printMedium(o, "array.init_data ");
+ break;
+ case InitElem:
+ printMedium(o, "array.init_elem ");
+ break;
+ default:
+ WASM_UNREACHABLE("unexpected op");
+ }
+ TypeNamePrinter(o, wasm).print(curr->ref->type.getHeapType());
+ o << " $" << curr->segment;
+ }
void visitRefAs(RefAs* curr) {
switch (curr->op) {
case RefAsNonNull:
diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp
index 943448cfa..228b3afc1 100644
--- a/src/passes/RemoveUnusedModuleElements.cpp
+++ b/src/passes/RemoveUnusedModuleElements.cpp
@@ -218,6 +218,17 @@ struct ReferenceFinder : public PostWalker<ReferenceFinder> {
}
WASM_UNREACHABLE("unexpected op");
}
+ void visitArrayInit(ArrayInit* curr) {
+ switch (curr->op) {
+ case InitData:
+ note({ModuleElementKind::DataSegment, curr->segment});
+ return;
+ case InitElem:
+ note({ModuleElementKind::ElementSegment, curr->segment});
+ return;
+ }
+ WASM_UNREACHABLE("unexpected op");
+ }
};
// Analyze a module to find what things are referenced and what things are used.
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index 4f224e1dd..e010135ee 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -1138,6 +1138,9 @@ enum ASTNodes {
BrOnNonI31 = 0x65,
ExternInternalize = 0x70,
ExternExternalize = 0x71,
+ ArrayFill = 0x0f,
+ ArrayInitData = 0x54,
+ ArrayInitElem = 0x55,
StringNewWTF8 = 0x80,
StringNewWTF16 = 0x81,
StringConst = 0x82,
@@ -1731,6 +1734,8 @@ public:
bool maybeVisitArraySet(Expression*& out, uint32_t code);
bool maybeVisitArrayLen(Expression*& out, uint32_t code);
bool maybeVisitArrayCopy(Expression*& out, uint32_t code);
+ bool maybeVisitArrayFill(Expression*& out, uint32_t code);
+ bool maybeVisitArrayInit(Expression*& out, uint32_t code);
bool maybeVisitStringNew(Expression*& out, uint32_t code);
bool maybeVisitStringConst(Expression*& out, uint32_t code);
bool maybeVisitStringMeasure(Expression*& out, uint32_t code);
diff --git a/src/wasm-builder.h b/src/wasm-builder.h
index dbddb248b..020badf16 100644
--- a/src/wasm-builder.h
+++ b/src/wasm-builder.h
@@ -991,6 +991,34 @@ public:
ret->finalize();
return ret;
}
+ ArrayFill* makeArrayFill(Expression* ref,
+ Expression* index,
+ Expression* value,
+ Expression* size) {
+ auto* ret = wasm.allocator.alloc<ArrayFill>();
+ ret->ref = ref;
+ ret->index = index;
+ ret->value = value;
+ ret->size = size;
+ ret->finalize();
+ return ret;
+ }
+ ArrayInit* makeArrayInit(ArrayInitOp op,
+ Name seg,
+ Expression* ref,
+ Expression* index,
+ Expression* offset,
+ Expression* size) {
+ auto* ret = wasm.allocator.alloc<ArrayInit>();
+ ret->op = op;
+ ret->segment = seg;
+ ret->ref = ref;
+ ret->index = index;
+ ret->offset = offset;
+ ret->size = size;
+ ret->finalize();
+ return ret;
+ }
RefAs* makeRefAs(RefAsOp op, Expression* value) {
auto* ret = wasm.allocator.alloc<RefAs>();
ret->op = op;
diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def
index 480789ca3..a4de0b2ad 100644
--- a/src/wasm-delegations-fields.def
+++ b/src/wasm-delegations-fields.def
@@ -710,6 +710,26 @@ switch (DELEGATE_ID) {
DELEGATE_END(ArrayCopy);
break;
}
+ case Expression::Id::ArrayFillId: {
+ DELEGATE_START(ArrayFill);
+ DELEGATE_FIELD_CHILD(ArrayFill, size);
+ DELEGATE_FIELD_CHILD(ArrayFill, value);
+ DELEGATE_FIELD_CHILD(ArrayFill, index);
+ DELEGATE_FIELD_CHILD(ArrayFill, ref);
+ DELEGATE_END(ArrayFill);
+ break;
+ }
+ case Expression::Id::ArrayInitId: {
+ DELEGATE_START(ArrayInit);
+ DELEGATE_FIELD_INT(ArrayInit, op);
+ DELEGATE_FIELD_NAME(ArrayInit, segment);
+ DELEGATE_FIELD_CHILD(ArrayInit, size);
+ DELEGATE_FIELD_CHILD(ArrayInit, offset);
+ DELEGATE_FIELD_CHILD(ArrayInit, index);
+ DELEGATE_FIELD_CHILD(ArrayInit, ref);
+ DELEGATE_END(ArrayInit);
+ break;
+ }
case Expression::Id::RefAsId: {
DELEGATE_START(RefAs);
DELEGATE_FIELD_INT(RefAs, op);
diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def
index 922bb7c87..0a6471f89 100644
--- a/src/wasm-delegations.def
+++ b/src/wasm-delegations.def
@@ -83,6 +83,8 @@ DELEGATE(ArrayGet);
DELEGATE(ArraySet);
DELEGATE(ArrayLen);
DELEGATE(ArrayCopy);
+DELEGATE(ArrayFill);
+DELEGATE(ArrayInit);
DELEGATE(RefAs);
DELEGATE(StringNew);
DELEGATE(StringConst);
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index 4b7d652b4..b5b03743d 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -1758,25 +1758,62 @@ public:
size_t destVal = destIndex.getSingleValue().getUnsigned();
size_t srcVal = srcIndex.getSingleValue().getUnsigned();
size_t lengthVal = length.getSingleValue().getUnsigned();
- if (lengthVal >= ArrayLimit) {
- hostLimit("allocation failure");
+ if (destVal + lengthVal > destData->values.size()) {
+ trap("oob");
+ }
+ if (srcVal + lengthVal > srcData->values.size()) {
+ trap("oob");
}
std::vector<Literal> copied;
copied.resize(lengthVal);
for (size_t i = 0; i < lengthVal; i++) {
- if (srcVal + i >= srcData->values.size()) {
- trap("oob");
- }
copied[i] = srcData->values[srcVal + i];
}
for (size_t i = 0; i < lengthVal; i++) {
- if (destVal + i >= destData->values.size()) {
- trap("oob");
- }
destData->values[destVal + i] = copied[i];
}
return Flow();
}
+ Flow visitArrayFill(ArrayFill* curr) {
+ NOTE_ENTER("ArrayFill");
+ Flow ref = self()->visit(curr->ref);
+ if (ref.breaking()) {
+ return ref;
+ }
+ Flow index = self()->visit(curr->index);
+ if (index.breaking()) {
+ return index;
+ }
+ Flow value = self()->visit(curr->value);
+ if (value.breaking()) {
+ return value;
+ }
+ Flow size = self()->visit(curr->size);
+ if (size.breaking()) {
+ return size;
+ }
+ auto data = ref.getSingleValue().getGCData();
+ if (!data) {
+ trap("null ref");
+ }
+ size_t indexVal = index.getSingleValue().getUnsigned();
+ Literal fillVal = value.getSingleValue();
+ size_t sizeVal = size.getSingleValue().getUnsigned();
+
+ auto field = curr->ref->type.getHeapType().getArray().element;
+ fillVal = truncateForPacking(fillVal, field);
+
+ size_t arraySize = data->values.size();
+ if (indexVal > arraySize || sizeVal > arraySize ||
+ indexVal + sizeVal > arraySize || indexVal + sizeVal < indexVal) {
+ trap("out of bounds array access in array.fill");
+ }
+ for (size_t i = 0; i < sizeVal; ++i) {
+ data->values[indexVal + i] = fillVal;
+ }
+ return {};
+ }
+ Flow visitArrayInit(MemoryFill* curr) { WASM_UNREACHABLE("unimp"); }
Flow visitRefAs(RefAs* curr) {
NOTE_ENTER("RefAs");
Flow flow = visit(curr->value);
@@ -2216,6 +2253,18 @@ public:
NOTE_ENTER("ArrayNewSeg");
return Flow(NONCONSTANT_FLOW);
}
+ Flow visitArrayCopy(ArrayCopy* curr) {
+ NOTE_ENTER("ArrayCopy");
+ return Flow(NONCONSTANT_FLOW);
+ }
+ Flow visitArrayFill(ArrayFill* curr) {
+ NOTE_ENTER("ArrayFill");
+ return Flow(NONCONSTANT_FLOW);
+ }
+ Flow visitArrayInit(ArrayInit* curr) {
+ NOTE_ENTER("ArrayInit");
+ return Flow(NONCONSTANT_FLOW);
+ }
Flow visitPop(Pop* curr) {
NOTE_ENTER("Pop");
return Flow(NONCONSTANT_FLOW);
@@ -3576,6 +3625,77 @@ public:
}
return Literal(std::make_shared<GCData>(heapType, contents), heapType);
}
+ Flow visitArrayInit(ArrayInit* curr) {
+ NOTE_ENTER("ArrayInit");
+ Flow ref = self()->visit(curr->ref);
+ if (ref.breaking()) {
+ return ref;
+ }
+ Flow index = self()->visit(curr->index);
+ if (index.breaking()) {
+ return index;
+ }
+ Flow offset = self()->visit(curr->offset);
+ if (offset.breaking()) {
+ return offset;
+ }
+ Flow size = self()->visit(curr->size);
+ if (size.breaking()) {
+ return size;
+ }
+ auto data = ref.getSingleValue().getGCData();
+ if (!data) {
+ trap("null ref");
+ }
+ size_t indexVal = index.getSingleValue().getUnsigned();
+ size_t offsetVal = offset.getSingleValue().getUnsigned();
+ size_t sizeVal = size.getSingleValue().getUnsigned();
+
+ size_t arraySize = data->values.size();
+ if ((uint64_t)indexVal + sizeVal > arraySize) {
+ trap("out of bounds array access in array.init");
+ }
+
+ Module& wasm = *self()->getModule();
+
+ switch (curr->op) {
+ case InitData: {
+ auto* seg = wasm.getDataSegment(curr->segment);
+ auto elem = curr->ref->type.getHeapType().getArray().element;
+ size_t elemSize = elem.getByteSize();
+ uint64_t readSize = (uint64_t)sizeVal * elemSize;
+ if (offsetVal + readSize > seg->data.size()) {
+ trap("out of bounds segment access in array.init_data");
+ }
+ if (offsetVal + sizeVal > 0 && droppedSegments.count(curr->segment)) {
+ trap("out of bounds segment access in array.init_data");
+ }
+ for (size_t i = 0; i < sizeVal; i++) {
+ void* addr = (void*)&seg->data[offsetVal + i * elemSize];
+ data->values[indexVal + i] = Literal::makeFromMemory(addr, elem);
+ }
+ return {};
+ }
+ case InitElem: {
+ auto* seg = wasm.getElementSegment(curr->segment);
+ if ((uint64_t)offsetVal + sizeVal > seg->data.size()) {
+ trap("out of bounds segment access in array.init");
+ }
+ // TODO: Check whether the segment has been dropped once we support
+ // dropping element segments.
+ for (size_t i = 0; i < sizeVal; i++) {
+ // TODO: This is not correct because it does not preserve the identity
+ // of references in the table! ArrayNewSeg suffers the same problem.
+ // Fixing it will require changing how we represent segments, at least
+ // in the interpreter.
+ data->values[indexVal + i] =
+ self()->visit(seg->data[i]).getSingleValue();
+ }
+ return {};
+ }
+ };
+ WASM_UNREACHABLE("unexpected op");
+ }
Flow visitTry(Try* curr) {
NOTE_ENTER("Try");
try {
diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h
index c684ae58a..c5598ef1e 100644
--- a/src/wasm-s-parser.h
+++ b/src/wasm-s-parser.h
@@ -306,6 +306,8 @@ private:
Expression* makeArraySet(Element& s);
Expression* makeArrayLen(Element& s);
Expression* makeArrayCopy(Element& s);
+ Expression* makeArrayFill(Element& s);
+ Expression* makeArrayInit(Element& s, ArrayInitOp op);
Expression* makeRefAs(Element& s, RefAsOp op);
Expression* makeRefAsNonNull(Element& s);
Expression* makeStringNew(Element& s, StringNewOp op, bool try_);
diff --git a/src/wasm.h b/src/wasm.h
index dd989602a..07a2f6bf9 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -568,6 +568,12 @@ enum ArrayNewSegOp {
NewElem,
};
+// TODO: Deduplicate with ArrayNewSegOp?
+enum ArrayInitOp {
+ InitData,
+ InitElem,
+};
+
enum BrOnOp {
BrOnNull,
BrOnNonNull,
@@ -722,6 +728,8 @@ public:
ArraySetId,
ArrayLenId,
ArrayCopyId,
+ ArrayFillId,
+ ArrayInitId,
RefAsId,
StringNewId,
StringConstId,
@@ -1669,6 +1677,32 @@ public:
void finalize();
};
+class ArrayFill : public SpecificExpression<Expression::ArrayFillId> {
+public:
+ ArrayFill(MixedArena& allocator) {}
+
+ Expression* ref;
+ Expression* index;
+ Expression* value;
+ Expression* size;
+
+ void finalize();
+};
+
+class ArrayInit : public SpecificExpression<Expression::ArrayInitId> {
+public:
+ ArrayInit(MixedArena& allocator) {}
+
+ ArrayInitOp op;
+ Name segment;
+ Expression* ref;
+ Expression* index;
+ Expression* offset;
+ Expression* size;
+
+ void finalize();
+};
+
class RefAs : public SpecificExpression<Expression::RefAsId> {
public:
RefAs(MixedArena& allocator) {}
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index f92f1ae26..e85740614 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -4040,6 +4040,12 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
if (maybeVisitArrayCopy(curr, opcode)) {
break;
}
+ if (maybeVisitArrayFill(curr, opcode)) {
+ break;
+ }
+ if (maybeVisitArrayInit(curr, opcode)) {
+ break;
+ }
if (maybeVisitStringNew(curr, opcode)) {
break;
}
@@ -7216,6 +7222,50 @@ bool WasmBinaryBuilder::maybeVisitArrayCopy(Expression*& out, uint32_t code) {
return true;
}
+bool WasmBinaryBuilder::maybeVisitArrayFill(Expression*& out, uint32_t code) {
+ if (code != BinaryConsts::ArrayFill) {
+ return false;
+ }
+ auto heapType = getIndexedHeapType();
+ auto* size = popNonVoidExpression();
+ auto* value = popNonVoidExpression();
+ auto* index = popNonVoidExpression();
+ auto* ref = popNonVoidExpression();
+ validateHeapTypeUsingChild(ref, heapType);
+ out = Builder(wasm).makeArrayFill(ref, index, value, size);
+ return true;
+}
+
+bool WasmBinaryBuilder::maybeVisitArrayInit(Expression*& out, uint32_t code) {
+ ArrayInitOp op;
+ switch (code) {
+ case BinaryConsts::ArrayInitData:
+ op = InitData;
+ break;
+ case BinaryConsts::ArrayInitElem:
+ op = InitElem;
+ break;
+ default:
+ return false;
+ }
+ auto heapType = getIndexedHeapType();
+ Index segIdx = getU32LEB();
+ auto* size = popNonVoidExpression();
+ auto* offset = popNonVoidExpression();
+ auto* index = popNonVoidExpression();
+ auto* ref = popNonVoidExpression();
+ validateHeapTypeUsingChild(ref, heapType);
+ auto* built =
+ Builder(wasm).makeArrayInit(op, Name(), ref, index, offset, size);
+ if (op == InitData) {
+ dataRefs[segIdx].push_back(&built->segment);
+ } else {
+ elemRefs[segIdx].push_back(&built->segment);
+ }
+ out = built;
+ return true;
+}
+
bool WasmBinaryBuilder::maybeVisitStringNew(Expression*& out, uint32_t code) {
StringNewOp op;
Expression* length = nullptr;
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index 51df46544..968c8bd45 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -3041,6 +3041,28 @@ Expression* SExpressionWasmBuilder::makeArrayCopy(Element& s) {
destRef, destIndex, srcRef, srcIndex, length);
}
+Expression* SExpressionWasmBuilder::makeArrayFill(Element& s) {
+ auto heapType = parseHeapType(*s[1]);
+ auto ref = parseExpression(*s[2]);
+ validateHeapTypeUsingChild(ref, heapType, s);
+ auto index = parseExpression(*s[3]);
+ auto value = parseExpression(*s[4]);
+ auto size = parseExpression(*s[5]);
+ return Builder(wasm).makeArrayFill(ref, index, value, size);
+}
+
+Expression* SExpressionWasmBuilder::makeArrayInit(Element& s, ArrayInitOp op) {
+ auto heapType = parseHeapType(*s[1]);
+ auto seg =
+ op == InitData ? getDataSegmentName(*s[2]) : getElemSegmentName(*s[2]);
+ auto ref = parseExpression(*s[3]);
+ validateHeapTypeUsingChild(ref, heapType, s);
+ auto index = parseExpression(*s[4]);
+ auto offset = parseExpression(*s[5]);
+ auto size = parseExpression(*s[6]);
+ return Builder(wasm).makeArrayInit(op, seg, ref, index, offset, size);
+}
+
Expression* SExpressionWasmBuilder::makeRefAs(Element& s, RefAsOp op) {
auto* value = parseExpression(s[1]);
if (!value->type.isRef() && value->type != Type::unreachable) {
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 9e22efc0c..286b049aa 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -2185,6 +2185,37 @@ void BinaryInstWriter::visitArrayCopy(ArrayCopy* curr) {
parent.writeIndexedHeapType(curr->srcRef->type.getHeapType());
}
+void BinaryInstWriter::visitArrayFill(ArrayFill* curr) {
+ if (curr->ref->type.isNull()) {
+ emitUnreachable();
+ return;
+ }
+ o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArrayFill);
+ parent.writeIndexedHeapType(curr->ref->type.getHeapType());
+}
+
+void BinaryInstWriter::visitArrayInit(ArrayInit* curr) {
+ if (curr->ref->type.isNull()) {
+ emitUnreachable();
+ return;
+ }
+ o << int8_t(BinaryConsts::GCPrefix);
+ switch (curr->op) {
+ case InitData:
+ o << U32LEB(BinaryConsts::ArrayInitData);
+ parent.writeIndexedHeapType(curr->ref->type.getHeapType());
+ o << U32LEB(parent.getDataSegmentIndex(curr->segment));
+ break;
+ case InitElem:
+ o << U32LEB(BinaryConsts::ArrayInitElem);
+ parent.writeIndexedHeapType(curr->ref->type.getHeapType());
+ o << U32LEB(parent.getElementSegmentIndex(curr->segment));
+ break;
+ default:
+ WASM_UNREACHABLE("unexpected op");
+ }
+}
+
void BinaryInstWriter::visitRefAs(RefAs* curr) {
switch (curr->op) {
case RefAsNonNull:
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index 951481dac..845f0f6b6 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -462,6 +462,8 @@ public:
void visitArraySet(ArraySet* curr);
void visitArrayLen(ArrayLen* curr);
void visitArrayCopy(ArrayCopy* curr);
+ void visitArrayFill(ArrayFill* curr);
+ void visitArrayInit(ArrayInit* curr);
void visitFunction(Function* curr);
// helpers
@@ -2882,8 +2884,10 @@ void FunctionValidator::visitArrayCopy(ArrayCopy* curr) {
if (!shouldBeSubType(curr->srcRef->type,
Type(HeapType::array, Nullable),
curr,
- "array.copy source should be an array reference") ||
- !shouldBeSubType(curr->destRef->type,
+ "array.copy source should be an array reference")) {
+ return;
+ }
+ if (!shouldBeSubType(curr->destRef->type,
Type(HeapType::array, Nullable),
curr,
"array.copy destination should be an array reference")) {
@@ -2891,17 +2895,16 @@ void FunctionValidator::visitArrayCopy(ArrayCopy* curr) {
}
auto srcHeapType = curr->srcRef->type.getHeapType();
auto destHeapType = curr->destRef->type.getHeapType();
- if (srcHeapType == HeapType::none || destHeapType == HeapType::none) {
+ if (srcHeapType == HeapType::none ||
+ !shouldBeTrue(srcHeapType.isArray(),
+ curr,
+ "array.copy source should be an array reference")) {
return;
}
- if (!shouldBeTrue(
- srcHeapType != HeapType::array,
- curr,
- "array.copy source needs to be a specific array reference") ||
- !shouldBeTrue(
- srcHeapType != HeapType::array,
- curr,
- "array.copy destination needs to be a specific array reference")) {
+ if (destHeapType == HeapType::none ||
+ !shouldBeTrue(destHeapType.isArray(),
+ curr,
+ "array.copy destination should be an array reference")) {
return;
}
const auto& srcElement = srcHeapType.getArray().element;
@@ -2910,7 +2913,102 @@ void FunctionValidator::visitArrayCopy(ArrayCopy* curr) {
destElement.type,
curr,
"array.copy must have the proper types");
- shouldBeTrue(destElement.mutable_, curr, "array.copy type must be mutable");
+ shouldBeEqual(srcElement.packedType,
+ destElement.packedType,
+ curr,
+ "array.copy types must match");
+ shouldBeTrue(
+ destElement.mutable_, curr, "array.copy destination must be mutable");
+}
+
+void FunctionValidator::visitArrayFill(ArrayFill* curr) {
+ shouldBeTrue(getModule()->features.hasGC(),
+ curr,
+ "array.fill requires gc [--enable-gc]");
+ shouldBeEqualOrFirstIsUnreachable(curr->index->type,
+ Type(Type::i32),
+ curr,
+ "array.fill index must be an i32");
+ shouldBeEqualOrFirstIsUnreachable(
+ curr->size->type, Type(Type::i32), curr, "array.fill size must be an i32");
+ if (curr->type == Type::unreachable) {
+ return;
+ }
+ if (!shouldBeSubType(curr->ref->type,
+ Type(HeapType::array, Nullable),
+ curr,
+ "array.fill destination should be an array reference")) {
+ return;
+ }
+ auto heapType = curr->ref->type.getHeapType();
+ if (heapType == HeapType::none ||
+ !shouldBeTrue(heapType.isArray(),
+ curr,
+ "array.fill destination should be an array reference")) {
+ return;
+ }
+ auto element = heapType.getArray().element;
+ shouldBeSubType(curr->value->type,
+ element.type,
+ curr,
+ "array.fill value must match destination element type");
+ shouldBeTrue(
+ element.mutable_, curr, "array.fill destination must be mutable");
+}
+
+void FunctionValidator::visitArrayInit(ArrayInit* curr) {
+ shouldBeTrue(getModule()->features.hasGC(),
+ curr,
+ "array.init_* requires gc [--enable-gc]");
+ shouldBeEqualOrFirstIsUnreachable(curr->index->type,
+ Type(Type::i32),
+ curr,
+ "array.init_* index must be an i32");
+ shouldBeEqualOrFirstIsUnreachable(curr->offset->type,
+ Type(Type::i32),
+ curr,
+ "array.init_* offset must be an i32");
+ shouldBeEqualOrFirstIsUnreachable(curr->size->type,
+ Type(Type::i32),
+ curr,
+ "array.init_* size must be an i32");
+ if (curr->type == Type::unreachable) {
+ return;
+ }
+ if (!shouldBeSubType(curr->ref->type,
+ Type(HeapType::array, Nullable),
+ curr,
+ "array.init_* destination must be an array reference")) {
+ return;
+ }
+ auto heapType = curr->ref->type.getHeapType();
+ if (heapType == HeapType::none ||
+ !shouldBeTrue(heapType.isArray(),
+ curr,
+ "array.init_* destination must be an array reference")) {
+ return;
+ }
+ auto element = heapType.getArray().element;
+ shouldBeTrue(
+ element.mutable_, curr, "array.init_* destination must be mutable");
+ if (curr->op == InitData) {
+ shouldBeTrue(getModule()->getDataSegmentOrNull(curr->segment),
+ curr,
+ "array.init_data segment must exist");
+ shouldBeTrue(element.type.isNumber(),
+ curr,
+ "array.init_data destination must be numeric");
+ } else {
+ assert(curr->op == InitElem);
+ auto* seg = getModule()->getElementSegmentOrNull(curr->segment);
+ if (!shouldBeTrue(seg, curr, "array.init_elem segment must exist")) {
+ return;
+ }
+ shouldBeSubType(seg->type,
+ element.type,
+ curr,
+ "array.init_elem segment type must match destination type");
+ }
}
void FunctionValidator::visitFunction(Function* curr) {
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index b21c1849a..47a04d7f8 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -1109,6 +1109,24 @@ void ArrayCopy::finalize() {
}
}
+void ArrayFill::finalize() {
+ if (ref->type == Type::unreachable || index->type == Type::unreachable ||
+ value->type == Type::unreachable || size->type == Type::unreachable) {
+ type = Type::unreachable;
+ } else {
+ type = Type::none;
+ }
+}
+
+void ArrayInit::finalize() {
+ if (ref->type == Type::unreachable || index->type == Type::unreachable ||
+ offset->type == Type::unreachable || size->type == Type::unreachable) {
+ type = Type::unreachable;
+ } else {
+ type = Type::none;
+ }
+}
+
void RefAs::finalize() {
if (value->type == Type::unreachable) {
type = Type::unreachable;
diff --git a/src/wasm/wat-parser.cpp b/src/wasm/wat-parser.cpp
index 567502c93..51527b5e3 100644
--- a/src/wasm/wat-parser.cpp
+++ b/src/wasm/wat-parser.cpp
@@ -800,6 +800,9 @@ struct NullInstrParserCtx {
InstrT makeArrayCopy(Index, HeapTypeT, HeapTypeT) {
return Ok{};
}
+ template<typename HeapTypeT> InstrT makeArrayFill(Index, HeapTypeT) {
+ return Ok{};
+ }
};
// Phase 1: Parse definition spans for top-level module elements and determine
@@ -2212,6 +2215,22 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx> {
return push(
pos, builder.makeArrayCopy(*destRef, *destIdx, *srcRef, *srcIdx, *len));
}
+
+ Result<> makeArrayFill(Index pos, HeapType type) {
+ if (!type.isArray()) {
+ return in.err(pos, "expected array type annotation");
+ }
+ auto size = pop(pos);
+ CHECK_ERR(size);
+ auto value = pop(pos);
+ CHECK_ERR(value);
+ auto index = pop(pos);
+ CHECK_ERR(index);
+ auto ref = pop(pos);
+ CHECK_ERR(ref);
+ CHECK_ERR(validateTypeAnnotation(pos, type, *ref));
+ return push(pos, builder.makeArrayFill(*ref, *index, *value, *size));
+ }
};
// ================
@@ -2369,6 +2388,9 @@ Result<typename Ctx::InstrT> makeArrayGet(Ctx&, Index, bool signed_ = false);
template<typename Ctx> Result<typename Ctx::InstrT> makeArraySet(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeArrayLen(Ctx&, Index);
template<typename Ctx> Result<typename Ctx::InstrT> makeArrayCopy(Ctx&, Index);
+template<typename Ctx> Result<typename Ctx::InstrT> makeArrayFill(Ctx&, Index);
+template<typename Ctx>
+Result<typename Ctx::InstrT> makeArrayInit(Ctx&, Index, ArrayInitOp);
template<typename Ctx>
Result<typename Ctx::InstrT> makeRefAs(Ctx&, Index, RefAsOp op);
template<typename Ctx>
@@ -3556,6 +3578,19 @@ Result<typename Ctx::InstrT> makeArrayCopy(Ctx& ctx, Index pos) {
}
template<typename Ctx>
+Result<typename Ctx::InstrT> makeArrayFill(Ctx& ctx, Index pos) {
+ auto type = typeidx(ctx);
+ CHECK_ERR(type);
+ return ctx.makeArrayFill(pos, *type);
+}
+
+template<typename Ctx>
+Result<typename Ctx::InstrT>
+makeArrayInit(Ctx& ctx, Index pos, ArrayInitOp op) {
+ return ctx.in.err("unimplemented instruction");
+}
+
+template<typename Ctx>
Result<typename Ctx::InstrT> makeRefAs(Ctx& ctx, Index pos, RefAsOp op) {
return ctx.in.err("unimplemented instruction");
}
diff --git a/src/wasm2js.h b/src/wasm2js.h
index c86788e5a..d33e45c4c 100644
--- a/src/wasm2js.h
+++ b/src/wasm2js.h
@@ -2339,6 +2339,14 @@ Ref Wasm2JSBuilder::processFunctionBody(Module* m,
unimplemented(curr);
WASM_UNREACHABLE("unimp");
}
+ Ref visitArrayFill(ArrayFill* curr) {
+ unimplemented(curr);
+ WASM_UNREACHABLE("unimp");
+ }
+ Ref visitArrayInit(ArrayInit* curr) {
+ unimplemented(curr);
+ WASM_UNREACHABLE("unimp");
+ }
Ref visitStringNew(StringNew* 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 187fa06f2..35b8ea438 100644
--- a/test/binaryen.js/kitchen-sink.js.txt
+++ b/test/binaryen.js/kitchen-sink.js.txt
@@ -102,20 +102,20 @@ 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
+RefAs: 72
+StringNew: 73
+StringConst: 74
+StringMeasure: 75
+StringEncode: 76
+StringConcat: 77
+StringEq: 78
+StringAs: 79
+StringWTF8Advance: 80
+StringWTF16Get: 81
+StringIterNext: 82
+StringIterMove: 83
+StringSliceWTF: 84
+StringSliceIter: 85
getExpressionInfo={"id":15,"type":4,"op":6}
(f32.neg
(f32.const -33.61199951171875)
diff --git a/test/lit/heap-types.wast b/test/lit/heap-types.wast
index ce7c7814e..9dc8f11a8 100644
--- a/test/lit/heap-types.wast
+++ b/test/lit/heap-types.wast
@@ -146,3 +146,104 @@
)
)
)
+
+(module
+ ;; CHECK: (type $vector (array (mut f64)))
+ ;; NOMNL: (type $vector (array (mut f64)))
+ (type $vector (array (mut f64)))
+ ;; CHECK: (func $test (type $ref|$vector|_i32_f64_i32_=>_none) (param $ref (ref $vector)) (param $index i32) (param $value f64) (param $size i32)
+ ;; CHECK-NEXT: (array.fill $vector
+ ;; CHECK-NEXT: (local.get $ref)
+ ;; CHECK-NEXT: (local.get $index)
+ ;; CHECK-NEXT: (local.get $value)
+ ;; CHECK-NEXT: (local.get $size)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $test (type $ref|$vector|_i32_f64_i32_=>_none) (param $ref (ref $vector)) (param $index i32) (param $value f64) (param $size i32)
+ ;; NOMNL-NEXT: (array.fill $vector
+ ;; NOMNL-NEXT: (local.get $ref)
+ ;; NOMNL-NEXT: (local.get $index)
+ ;; NOMNL-NEXT: (local.get $value)
+ ;; NOMNL-NEXT: (local.get $size)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $test (param $ref (ref $vector))
+ (param $index i32)
+ (param $value f64)
+ (param $size i32)
+ (array.fill $vector
+ (local.get $ref)
+ (local.get $index)
+ (local.get $value)
+ (local.get $size)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $vector (array (mut i32)))
+ ;; NOMNL: (type $vector (array (mut i32)))
+ (type $vector (array (mut i32)))
+ (data "")
+ ;; CHECK: (func $test (type $ref|$vector|_i32_i32_i32_=>_none) (param $ref (ref $vector)) (param $index i32) (param $offset i32) (param $size i32)
+ ;; CHECK-NEXT: (array.init_data $vector $0
+ ;; CHECK-NEXT: (local.get $ref)
+ ;; CHECK-NEXT: (local.get $index)
+ ;; CHECK-NEXT: (local.get $offset)
+ ;; CHECK-NEXT: (local.get $size)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $test (type $ref|$vector|_i32_i32_i32_=>_none) (param $ref (ref $vector)) (param $index i32) (param $offset i32) (param $size i32)
+ ;; NOMNL-NEXT: (array.init_data $vector $0
+ ;; NOMNL-NEXT: (local.get $ref)
+ ;; NOMNL-NEXT: (local.get $index)
+ ;; NOMNL-NEXT: (local.get $offset)
+ ;; NOMNL-NEXT: (local.get $size)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $test (param $ref (ref $vector))
+ (param $index i32)
+ (param $offset i32)
+ (param $size i32)
+ (array.init_data $vector 0
+ (local.get $ref)
+ (local.get $index)
+ (local.get $offset)
+ (local.get $size)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $vector (array (mut funcref)))
+ ;; NOMNL: (type $vector (array (mut funcref)))
+ (type $vector (array (mut funcref)))
+ (elem func)
+ ;; CHECK: (func $test (type $ref|$vector|_i32_i32_i32_=>_none) (param $ref (ref $vector)) (param $index i32) (param $offset i32) (param $size i32)
+ ;; CHECK-NEXT: (array.init_elem $vector $0
+ ;; CHECK-NEXT: (local.get $ref)
+ ;; CHECK-NEXT: (local.get $index)
+ ;; CHECK-NEXT: (local.get $offset)
+ ;; CHECK-NEXT: (local.get $size)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $test (type $ref|$vector|_i32_i32_i32_=>_none) (param $ref (ref $vector)) (param $index i32) (param $offset i32) (param $size i32)
+ ;; NOMNL-NEXT: (array.init_elem $vector $0
+ ;; NOMNL-NEXT: (local.get $ref)
+ ;; NOMNL-NEXT: (local.get $index)
+ ;; NOMNL-NEXT: (local.get $offset)
+ ;; NOMNL-NEXT: (local.get $size)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $test (param $ref (ref $vector))
+ (param $index i32)
+ (param $offset i32)
+ (param $size i32)
+ (array.init_elem $vector 0
+ (local.get $ref)
+ (local.get $index)
+ (local.get $offset)
+ (local.get $size)
+ )
+ )
+)
diff --git a/test/lit/wat-kitchen-sink.wast b/test/lit/wat-kitchen-sink.wast
index 934a77095..37f5fc58d 100644
--- a/test/lit/wat-kitchen-sink.wast
+++ b/test/lit/wat-kitchen-sink.wast
@@ -97,6 +97,8 @@
;; CHECK: (type $ref|$a2|_i32_ref|$a2|_i32_i32_=>_none (func (param (ref $a2) i32 (ref $a2) i32 i32)))
+ ;; CHECK: (type $ref|$a2|_i32_f32_i32_=>_none (func (param (ref $a2) i32 f32 i32)))
+
;; CHECK: (rec
;; CHECK-NEXT: (type $s0 (struct ))
(type $s0 (sub (struct)))
@@ -1627,6 +1629,22 @@
array.copy $a2 $a2
)
+ ;; CHECK: (func $array-fill (type $ref|$a2|_i32_f32_i32_=>_none) (param $0 (ref $a2)) (param $1 i32) (param $2 f32) (param $3 i32)
+ ;; CHECK-NEXT: (array.fill $a2
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: (local.get $3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $array-fill (param (ref $a2) i32 f32 i32)
+ local.get 0
+ local.get 1
+ local.get 2
+ local.get 3
+ array.fill $a2
+ )
+
;; CHECK: (func $use-types (type $ref|$s0|_ref|$s1|_ref|$s2|_ref|$s3|_ref|$s4|_ref|$s5|_ref|$s6|_ref|$s7|_ref|$s8|_ref|$a0|_ref|$a1|_ref|$a2|_ref|$a3|_ref|$subvoid|_ref|$submany|_=>_none) (param $0 (ref $s0)) (param $1 (ref $s1)) (param $2 (ref $s2)) (param $3 (ref $s3)) (param $4 (ref $s4)) (param $5 (ref $s5)) (param $6 (ref $s6)) (param $7 (ref $s7)) (param $8 (ref $s8)) (param $9 (ref $a0)) (param $10 (ref $a1)) (param $11 (ref $a2)) (param $12 (ref $a3)) (param $13 (ref $subvoid)) (param $14 (ref $submany))
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
diff --git a/test/spec/bulk-array.wast b/test/spec/bulk-array.wast
index 06c341000..565b21a2f 100644
--- a/test/spec/bulk-array.wast
+++ b/test/spec/bulk-array.wast
@@ -1,225 +1,357 @@
-(module
- ;; Array types used in tests.
- (type $i8 (array (mut i8)))
- (type $i16 (array (mut i16)))
- (type $i32 (array (mut i32)))
- (type $anyref (array (mut anyref)))
- (type $funcref (array (mut funcref)))
- (type $externref (array (mut externref)))
-
- ;; Array values used in tests. Reset in between tests with the "reset"
- ;; function.
- (global $i8 (mut (ref null $i8)) (ref.null none))
- (global $i16 (mut (ref null $i16)) (ref.null none))
- (global $i32 (mut (ref null $i32)) (ref.null none))
- (global $anyref (mut (ref null $anyref)) (ref.null none))
- (global $funcref (mut (ref null $funcref)) (ref.null none))
- (global $externref (mut (ref null $externref)) (ref.null none))
-
- ;; GC objects with distinct identities used in anyref tests.
- (global $g1 (export "g1") (mut anyref) (array.new_fixed $i8))
- (global $g2 (export "g2") (mut anyref) (array.new_fixed $i8))
- (global $g3 (export "g3") (mut anyref) (array.new_fixed $i8))
- (global $g4 (export "g4") (mut anyref) (array.new_fixed $i8))
- (global $g5 (export "g5") (mut anyref) (array.new_fixed $i8))
-
- ;; Functions with distinct return values used in funcref tests.
- (func $f1 (result i32) (i32.const 0))
- (func $f2 (result i32) (i32.const 1))
- (func $f3 (result i32) (i32.const 2))
- (func $f4 (result i32) (i32.const 3))
- (func $f5 (result i32) (i32.const 4))
-
- ;; Passive element segment used in array.init_elem tests.
- (elem $elem anyref
- (array.new_fixed $i8)
- (array.new_fixed $i8)
- (array.new_fixed $i8)
- (array.new_fixed $i8)
- (array.new_fixed $i8))
-
- (table $tab anyref 5 5)
-
- ;; Resets the array globals to known states.
- (func (export "reset")
- (global.set $i8
- (array.new_fixed $i8
- (i32.const 0)
- (i32.const 1)
- (i32.const 2)
- (i32.const 3)
- (i32.const 4)))
- (global.set $i16
- (array.new_fixed $i16
- (i32.const 0)
- (i32.const 1)
- (i32.const 2)
- (i32.const 3)
- (i32.const 4)))
- (global.set $i32
- (array.new_fixed $i32
- (i32.const 0)
- (i32.const 1)
- (i32.const 2)
- (i32.const 3)
- (i32.const 4)))
- (global.set $anyref
- (array.new_fixed $anyref
- (global.get $g1)
- (global.get $g2)
- (global.get $g3)
- (global.get $g4)
- (global.get $g5)))
- (global.set $funcref
- (array.new_fixed $funcref
- (ref.func $f1)
- (ref.func $f2)
- (ref.func $f3)
- (ref.func $f4)
- (ref.func $f5)))
- (global.set $externref
- (array.new_fixed $externref
- (extern.externalize (global.get $g1))
- (extern.externalize (global.get $g2))
- (extern.externalize (global.get $g3))
- (extern.externalize (global.get $g4))
- (extern.externalize (global.get $g5)))))
+;; Bulk instructions
+
+;; invalid uses
+
+;; array.copy
+(assert_invalid
+ (module
+ (type $a (array i8))
+ (type $b (array (mut i8)))
+
+ (func (export "array.copy-immutable") (param $1 (ref $a)) (param $2 (ref $b))
+ (array.copy $a $b (local.get $1) (i32.const 0) (local.get $2) (i32.const 0) (i32.const 0))
+ )
+ )
+ "destination array is immutable"
)
-;; array.fill
+(assert_invalid
+ (module
+ (type $a (array (mut i8)))
+ (type $b (array i16))
-;; basic i8
-;; basic i16
-;; basic i32
-;; basic anyref
-;; basic funcref
-;; basic externref
-;; basic ref subtype
-;; basic ref nullability subtype
+ (func (export "array.copy-packed-invalid") (param $1 (ref $a)) (param $2 (ref $b))
+ (array.copy $a $b (local.get $1) (i32.const 0) (local.get $2) (i32.const 0) (i32.const 0))
+ )
+ )
+ "array types do not match"
+)
-;; zero size in bounds
-;; zero size at bounds
-;; zero size out of bounds traps
+(assert_invalid
+ (module
+ (type $a (array (mut i8)))
+ (type $b (array (mut (ref $a))))
-;; out of bounds index traps
-;; out of bounds size traps
-;; out of bounds index + size traps
+ (func (export "array.copy-ref-invalid-1") (param $1 (ref $a)) (param $2 (ref $b))
+ (array.copy $a $b (local.get $1) (i32.const 0) (local.get $2) (i32.const 0) (i32.const 0))
+ )
+ )
+ "array types do not match"
+)
-;; null destination traps
+(assert_invalid
+ (module
+ (type $a (array (mut i8)))
+ (type $b (array (mut (ref $a))))
+ (type $c (array (mut (ref $b))))
+
+ (func (export "array.copy-ref-invalid-1") (param $1 (ref $b)) (param $2 (ref $c))
+ (array.copy $b $c (local.get $1) (i32.const 0) (local.get $2) (i32.const 0) (i32.const 0))
+ )
+ )
+ "array types do not match"
+)
-;; immutable field invalid
+;; array.fill
+(assert_invalid
+ (module
+ (type $a (array i8))
+
+ (func (export "array.fill-immutable") (param $1 (ref $a)) (param $2 i32)
+ (array.fill $a (local.get $1) (i32.const 0) (local.get $2) (i32.const 0))
+ )
+ )
+ "array is immutable"
+)
-;; ref supertype invalid
-;; ref nullability supertype invalid
+(assert_invalid
+ (module
+ (type $a (array (mut i8)))
-;; array.copy
+ (func (export "array.fill-invalid-1") (param $1 (ref $a)) (param $2 funcref)
+ (array.fill $a (local.get $1) (i32.const 0) (local.get $2) (i32.const 0))
+ )
+ )
+ "type mismatch"
+)
-;; basic i8
-;; basic i16
-;; basic i32
-;; basic anyref
-;; basic funcref
-;; basic externref
-;; basic ref subtype
-;; basic ref nullability subtype
-
-;; same i8 no overlap
-;; same i8 overlap src first
-;; same i8 overlap dest first
-;; same i8 overlap complete
-
-;; same i32 no overlap
-;; same i32 overlap src first
-;; same i32 overlap dest first
-;; same i32 overlap complete
-
-;; same anyref no overlap
-;; same anyref overloap
-;; same anyref src first
-;; same anyref dest first
-;; same anyref overlap complete
-
-;; zero size in bounds
-;; zero size at dest bounds
-;; zero size at src bounds
-;; zero size out of dest bounds traps
-;; zero size out of src bounds traps
-
-;; out of bounds dest index traps
-;; out of bounds src index traps
-;; out of bounds dest size traps
-;; out of bounds src index traps
-;; out of bounds dest index + size traps
-;; out of bounds src index + size traps
-
-;; null dest traps
-;; null src traps
-
-;; immutable dest field invalid
-;; immutable src field ok
-
-;; ref supertype invalid
-;; ref nullability supertype invalid
+(assert_invalid
+ (module
+ (type $b (array (mut funcref)))
+
+ (func (export "array.fill-invalid-1") (param $1 (ref $b)) (param $2 i32)
+ (array.fill $b (local.get $1) (i32.const 0) (local.get $2) (i32.const 0))
+ )
+ )
+ "type mismatch"
+)
;; array.init_data
+(assert_invalid
+ (module
+ (type $a (array i8))
-;; basic i8
-;; basic i16
-;; basic i32
-;; basic f32
+ (data $d1 "a")
-;; zero size in bounds
-;; zero size at dest bounds
-;; zero size at src bounds
-;; zero size out of dest bounds traps
-;; zero size out of src bounds traps
+ (func (export "array.init_data-immutable") (param $1 (ref $a))
+ (array.init_data $a $d1 (local.get $1) (i32.const 0) (i32.const 0) (i32.const 0))
+ )
+ )
+ "array is immutable"
+)
-;; out of bounds dest index traps
-;; out of bounds src index traps
-;; out of bounds dest size traps
-;; out of bounds src size traps
-;; out of bounds src multiplied size traps
-;; out of bounds dest index + size traps
-;; out of bounds src index + size traps
-;; out of bounds src index + multiplied size traps
+(assert_invalid
+ (module
+ (type $a (array (mut funcref)))
-;; null dest traps
-;; segment dropped traps
+ (data $d1 "a")
-;; immutable dest field invalid
+ (func (export "array.init_data-invalid-1") (param $1 (ref $a))
+ (array.init_data $a $d1 (local.get $1) (i32.const 0) (i32.const 0) (i32.const 0))
+ )
+ )
+ "array type is not numeric or vector"
+)
-;; ref supertype invalid
-;; ref nullability supertype invalid
+;; array.init_elem
+(assert_invalid
+ (module
+ (type $a (array funcref))
-;; out of bounds segment index invalid
+ (elem $e1 funcref)
-;; array.init_elem
+ (func (export "array.init_elem-immutable") (param $1 (ref $a))
+ (array.init_elem $a $e1 (local.get $1) (i32.const 0) (i32.const 0) (i32.const 0))
+ )
+ )
+ "array is immutable"
+)
+
+(assert_invalid
+ (module
+ (type $a (array (mut i8)))
-;; basic anyref
-;; basic funcref
-;; basic externref
-;; basic ref subtype
-;; basic ref nullability subtype
+ (elem $e1 funcref)
-;; zero size in bounds
-;; zero size at dest bounds
-;; zero size at src bounds
-;; zero size out of dest bounds traps
-;; zero size out of src bounds traps
+ (func (export "array.init_elem-invalid-1") (param $1 (ref $a))
+ (array.init_elem $a $e1 (local.get $1) (i32.const 0) (i32.const 0) (i32.const 0))
+ )
+ )
+ "type mismatch"
+)
-;; out of bounds dest index traps
-;; out of bounds src index traps
-;; out of bounds dest size traps
-;; out of bounds src size traps
-;; out of bounds dest index + size traps
-;; out of bounds src index + size traps
+(assert_invalid
+ (module
+ (type $a (array (mut funcref)))
-;; null dest traps
-;; segment dropped traps
+ (elem $e1 externref)
-;; immutable dest field invalid
+ (func (export "array.init_elem-invalid-2") (param $1 (ref $a))
+ (array.init_elem $a $e1 (local.get $1) (i32.const 0) (i32.const 0) (i32.const 0))
+ )
+ )
+ "type mismatch"
+)
-;; ref supertype invalid
-;; ref nullability supertype invalid
+(module
+ (type $t_f (func))
+ (type $arr8 (array i8))
+ (type $arr8_mut (array (mut i8)))
+ (type $arr16_mut (array (mut i16)))
+ (type $arrref (array (ref $t_f)))
+ (type $arrref_mut (array (mut funcref)))
+
+ (global $g_arr8 (ref $arr8) (array.new $arr8 (i32.const 10) (i32.const 12)))
+ (global $g_arr8_mut (mut (ref $arr8_mut)) (array.new_default $arr8_mut (i32.const 12)))
+ (global $g_arr16_mut (ref $arr16_mut) (array.new_default $arr16_mut (i32.const 6)))
+ (global $g_arrref (ref $arrref) (array.new $arrref (ref.func $dummy) (i32.const 12)))
+ (global $g_arrref_mut (ref $arrref_mut) (array.new_default $arrref_mut (i32.const 12)))
+
+ (table $t 1 funcref)
+
+ (data $d1 "abcdefghijkl")
+ (elem $e1 func $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy)
+
+ (func $dummy
+ )
+
+ (func (export "array_get_nth") (param $1 i32) (result i32)
+ (array.get_u $arr8_mut (global.get $g_arr8_mut) (local.get $1))
+ )
+
+ (func (export "array_get_nth_i16") (param $1 i32) (result i32)
+ (array.get_u $arr16_mut (global.get $g_arr16_mut) (local.get $1))
+ )
+
+ (func (export "array_call_nth") (param $1 i32)
+ (table.set $t (i32.const 0) (array.get $arrref_mut (global.get $g_arrref_mut) (local.get $1)))
+ (call_indirect $t (i32.const 0))
+ )
+
+ (func (export "array_copy-null-left")
+ (array.copy $arr8_mut $arr8 (ref.null $arr8_mut) (i32.const 0) (global.get $g_arr8) (i32.const 0) (i32.const 0))
+ )
+
+ (func (export "array_copy-null-right")
+ (array.copy $arr8_mut $arr8 (global.get $g_arr8_mut) (i32.const 0) (ref.null $arr8) (i32.const 0) (i32.const 0))
+ )
+
+ (func (export "array_fill-null")
+ (array.fill $arr8_mut (ref.null $arr8_mut) (i32.const 0) (i32.const 0) (i32.const 0))
+ )
+
+ (func (export "array_init_data-null")
+ (array.init_data $arr8_mut $d1 (ref.null $arr8_mut) (i32.const 0) (i32.const 0) (i32.const 0))
+ )
+
+ (func (export "array_init_elem-null")
+ (array.init_elem $arrref_mut $e1 (ref.null $arrref_mut) (i32.const 0) (i32.const 0) (i32.const 0))
+ )
+
+ (func (export "array_copy") (param $1 i32) (param $2 i32) (param $3 i32)
+ (array.copy $arr8_mut $arr8 (global.get $g_arr8_mut) (local.get $1) (global.get $g_arr8) (local.get $2) (local.get $3))
+ )
+
+ (func (export "array_fill") (param $1 i32) (param $2 i32) (param $3 i32)
+ (array.fill $arr8_mut (global.get $g_arr8_mut) (local.get $1) (local.get $2) (local.get $3))
+ )
+
+ (func (export "array_init_data") (param $1 i32) (param $2 i32) (param $3 i32)
+ (array.init_data $arr8_mut $d1 (global.get $g_arr8_mut) (local.get $1) (local.get $2) (local.get $3))
+ )
+
+ (func (export "array_init_data_i16") (param $1 i32) (param $2 i32) (param $3 i32)
+ (array.init_data $arr16_mut $d1 (global.get $g_arr16_mut) (local.get $1) (local.get $2) (local.get $3))
+ )
+
+ (func (export "array_init_elem") (param $1 i32) (param $2 i32) (param $3 i32)
+ (array.init_elem $arrref_mut $e1 (global.get $g_arrref_mut) (local.get $1) (local.get $2) (local.get $3))
+ )
+
+ (func (export "array_copy_overlap_test-1")
+ (local $1 (ref $arr8_mut))
+ (local.set $1
+ (array.new_data $arr8_mut $d1 (i32.const 0) (i32.const 12))
+ )
+ (array.copy $arr8_mut $arr8_mut (local.get $1) (i32.const 1) (local.get $1) (i32.const 0) (i32.const 11))
+ (global.set $g_arr8_mut (local.get $1))
+ )
+
+ (func (export "array_copy_overlap_test-2")
+ (local $1 (ref $arr8_mut))
+ (local.set $1
+ (array.new_data $arr8_mut $d1 (i32.const 0) (i32.const 12))
+ )
+ (array.copy $arr8_mut $arr8_mut (local.get $1) (i32.const 0) (local.get $1) (i32.const 1) (i32.const 11))
+ (global.set $g_arr8_mut (local.get $1))
+ )
+
+ (func (export "drop_segs")
+ (data.drop $d1)
+ ;; (elem.drop $e1) ;; TODO: implement elem.drop
+ )
+)
-;; out of bounds segment index invalid
+;; null array argument traps
+(assert_trap (invoke "array_copy-null-left") "null array reference")
+(assert_trap (invoke "array_copy-null-right") "null array reference")
+(assert_trap (invoke "array_fill-null") "null array reference")
+(assert_trap (invoke "array_init_data-null") "null array reference")
+(assert_trap (invoke "array_init_elem-null") "null array reference")
+
+;; OOB initial index traps
+(assert_trap (invoke "array_copy" (i32.const 13) (i32.const 0) (i32.const 0)) "out of bounds array access")
+(assert_trap (invoke "array_copy" (i32.const 0) (i32.const 13) (i32.const 0)) "out of bounds array access")
+(assert_trap (invoke "array_fill" (i32.const 13) (i32.const 0) (i32.const 0)) "out of bounds array access")
+(assert_trap (invoke "array_init_data" (i32.const 13) (i32.const 0) (i32.const 0)) "out of bounds array access")
+(assert_trap (invoke "array_init_data" (i32.const 0) (i32.const 13) (i32.const 0)) "out of bounds memory access")
+(assert_trap (invoke "array_init_elem" (i32.const 13) (i32.const 0) (i32.const 0)) "out of bounds array access")
+(assert_trap (invoke "array_init_elem" (i32.const 0) (i32.const 13) (i32.const 0)) "out of bounds table access")
+
+;; OOB length traps
+(assert_trap (invoke "array_copy" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access")
+(assert_trap (invoke "array_copy" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access")
+(assert_trap (invoke "array_fill" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access")
+(assert_trap (invoke "array_init_data" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access")
+(assert_trap (invoke "array_init_data" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access")
+(assert_trap (invoke "array_init_data_i16" (i32.const 0) (i32.const 0) (i32.const 7)) "out of bounds array access")
+(assert_trap (invoke "array_init_elem" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access")
+(assert_trap (invoke "array_init_elem" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access")
+
+;; start index = array size, len = 0 doesn't trap
+(assert_return (invoke "array_copy" (i32.const 12) (i32.const 0) (i32.const 0)))
+(assert_return (invoke "array_copy" (i32.const 0) (i32.const 12) (i32.const 0)))
+(assert_return (invoke "array_fill" (i32.const 12) (i32.const 0) (i32.const 0)))
+(assert_return (invoke "array_init_data" (i32.const 12) (i32.const 0) (i32.const 0)))
+(assert_return (invoke "array_init_data" (i32.const 0) (i32.const 12) (i32.const 0)))
+(assert_return (invoke "array_init_data_i16" (i32.const 0) (i32.const 6) (i32.const 0)))
+(assert_return (invoke "array_init_elem" (i32.const 12) (i32.const 0) (i32.const 0)))
+(assert_return (invoke "array_init_elem" (i32.const 0) (i32.const 12) (i32.const 0)))
+
+;; check arrays were not modified
+(assert_return (invoke "array_get_nth" (i32.const 0)) (i32.const 0))
+(assert_return (invoke "array_get_nth" (i32.const 5)) (i32.const 0))
+(assert_return (invoke "array_get_nth" (i32.const 11)) (i32.const 0))
+(assert_trap (invoke "array_get_nth" (i32.const 12)) "out of bounds array access")
+(assert_return (invoke "array_get_nth_i16" (i32.const 0)) (i32.const 0))
+(assert_return (invoke "array_get_nth_i16" (i32.const 2)) (i32.const 0))
+(assert_return (invoke "array_get_nth_i16" (i32.const 5)) (i32.const 0))
+(assert_trap (invoke "array_get_nth" (i32.const 12)) "out of bounds array access")
+(assert_trap (invoke "array_call_nth" (i32.const 0)) "uninitialized element")
+(assert_trap (invoke "array_call_nth" (i32.const 5)) "uninitialized element")
+(assert_trap (invoke "array_call_nth" (i32.const 11)) "uninitialized element")
+(assert_trap (invoke "array_call_nth" (i32.const 12)) "out of bounds array access")
+
+;; normal cases
+(assert_return (invoke "array_copy" (i32.const 0) (i32.const 0) (i32.const 2)))
+(assert_return (invoke "array_get_nth" (i32.const 0)) (i32.const 10))
+(assert_return (invoke "array_get_nth" (i32.const 1)) (i32.const 10))
+(assert_return (invoke "array_get_nth" (i32.const 2)) (i32.const 0))
+
+(assert_return (invoke "array_fill" (i32.const 2) (i32.const 11) (i32.const 2)))
+(assert_return (invoke "array_get_nth" (i32.const 1)) (i32.const 10))
+(assert_return (invoke "array_get_nth" (i32.const 2)) (i32.const 11))
+(assert_return (invoke "array_get_nth" (i32.const 3)) (i32.const 11))
+(assert_return (invoke "array_get_nth" (i32.const 4)) (i32.const 0))
+
+(assert_return (invoke "array_init_data" (i32.const 4) (i32.const 2) (i32.const 2)))
+(assert_return (invoke "array_get_nth" (i32.const 3)) (i32.const 11))
+(assert_return (invoke "array_get_nth" (i32.const 4)) (i32.const 99))
+(assert_return (invoke "array_get_nth" (i32.const 5)) (i32.const 100))
+(assert_return (invoke "array_get_nth" (i32.const 6)) (i32.const 0))
+
+(assert_return (invoke "array_init_data_i16" (i32.const 2) (i32.const 5) (i32.const 2)))
+(assert_return (invoke "array_get_nth_i16" (i32.const 1)) (i32.const 0))
+(assert_return (invoke "array_get_nth_i16" (i32.const 2)) (i32.const 0x6766))
+(assert_return (invoke "array_get_nth_i16" (i32.const 3)) (i32.const 0x6968))
+(assert_return (invoke "array_get_nth_i16" (i32.const 4)) (i32.const 0))
+
+(assert_return (invoke "array_init_elem" (i32.const 2) (i32.const 3) (i32.const 2)))
+(assert_trap (invoke "array_call_nth" (i32.const 1)) "uninitialized element")
+(assert_return (invoke "array_call_nth" (i32.const 2)))
+(assert_return (invoke "array_call_nth" (i32.const 3)))
+(assert_trap (invoke "array_call_nth" (i32.const 4)) "uninitialized element")
+
+;; test that overlapping array.copy works as if intermediate copy taken
+(assert_return (invoke "array_copy_overlap_test-1"))
+(assert_return (invoke "array_get_nth" (i32.const 0)) (i32.const 97))
+(assert_return (invoke "array_get_nth" (i32.const 1)) (i32.const 97))
+(assert_return (invoke "array_get_nth" (i32.const 2)) (i32.const 98))
+(assert_return (invoke "array_get_nth" (i32.const 5)) (i32.const 101))
+(assert_return (invoke "array_get_nth" (i32.const 10)) (i32.const 106))
+(assert_return (invoke "array_get_nth" (i32.const 11)) (i32.const 107))
+
+(assert_return (invoke "array_copy_overlap_test-2"))
+(assert_return (invoke "array_get_nth" (i32.const 0)) (i32.const 98))
+(assert_return (invoke "array_get_nth" (i32.const 1)) (i32.const 99))
+(assert_return (invoke "array_get_nth" (i32.const 5)) (i32.const 103))
+(assert_return (invoke "array_get_nth" (i32.const 9)) (i32.const 107))
+(assert_return (invoke "array_get_nth" (i32.const 10)) (i32.const 108))
+(assert_return (invoke "array_get_nth" (i32.const 11)) (i32.const 108))
+
+;; init_data/elem with dropped segments traps for non-zero length
+(assert_return (invoke "drop_segs"))
+(assert_return (invoke "array_init_data" (i32.const 0) (i32.const 0) (i32.const 0)))
+(assert_trap (invoke "array_init_data" (i32.const 0) (i32.const 0) (i32.const 1)) "out of bounds memory access")
+(assert_return (invoke "array_init_elem" (i32.const 0) (i32.const 0) (i32.const 0)))
+;; (assert_trap (invoke "array_init_elem" (i32.const 0) (i32.const 0) (i32.const 1)) "out of bounds table access") ;; TODO: implement elem.drop