summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Lively <tlively@google.com>2023-04-06 13:35:12 -0700
committerGitHub <noreply@github.com>2023-04-06 20:35:12 +0000
commit4f91c6a569614275d906a825d3f495541aa8802d (patch)
tree26b438a2aeec65e93a219da792b928b7c9c0eece
parent6afbc200b57acd1b9111de7729d47fea1d04c5f6 (diff)
downloadbinaryen-4f91c6a569614275d906a825d3f495541aa8802d.tar.gz
binaryen-4f91c6a569614275d906a825d3f495541aa8802d.tar.bz2
binaryen-4f91c6a569614275d906a825d3f495541aa8802d.zip
Implement array.fill, array.init_data, and array.init_elem (#5637)
These complement array.copy, which we already supported, as an initial complete set of bulk array operations. Replace the WIP spec tests with the upstream spec tests, lightly edited for compatibility with Binaryen.
-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