diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index b8763aa73f6..08cd2477896 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -94,6 +94,7 @@ '--disable-strings', '--disable-stack-switching', '--disable-relaxed-atomics', + '--disable-multibyte', ] diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index a68e3b3f1dc..01fd41fe9fe 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -502,6 +502,9 @@ BinaryenFeatures BinaryenFeatureCallIndirectOverlong(void) { BinaryenFeatures BinaryenFeatureRelaxedAtomics(void) { return static_cast(FeatureSet::RelaxedAtomics); } +BinaryenFeatures BinaryenFeatureMultibyte(void) { + return static_cast(FeatureSet::Multibyte); +} BinaryenFeatures BinaryenFeatureAll(void) { return static_cast(FeatureSet::All); } diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 6fc35cefc2c..29a7cb86086 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -244,6 +244,7 @@ BINARYEN_API BinaryenFeatures BinaryenFeatureFP16(void); BINARYEN_API BinaryenFeatures BinaryenFeatureBulkMemoryOpt(void); BINARYEN_API BinaryenFeatures BinaryenFeatureCallIndirectOverlong(void); BINARYEN_API BinaryenFeatures BinaryenFeatureRelaxedAtomics(void); +BINARYEN_API BinaryenFeatures BinaryenFeatureMultibyte(void); BINARYEN_API BinaryenFeatures BinaryenFeatureAll(void); // Modules diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index 6c97c82f729..238deab1225 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -260,6 +260,7 @@ struct ExpressionInterpreter : OverriddenVisitor { Flow visitArrayNewFixed(ArrayNewFixed* curr) { WASM_UNREACHABLE("TODO"); } Flow visitArrayGet(ArrayGet* curr) { WASM_UNREACHABLE("TODO"); } Flow visitArraySet(ArraySet* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitArrayStore(ArrayStore* curr) { WASM_UNREACHABLE("TODO"); } Flow visitArrayLen(ArrayLen* curr) { WASM_UNREACHABLE("TODO"); } Flow visitArrayCopy(ArrayCopy* curr) { WASM_UNREACHABLE("TODO"); } Flow visitArrayFill(ArrayFill* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 06d6aab2c97..2c02878db22 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -169,6 +169,7 @@ void ReFinalize::visitArrayNewElem(ArrayNewElem* curr) { curr->finalize(); } void ReFinalize::visitArrayNewFixed(ArrayNewFixed* curr) { curr->finalize(); } void ReFinalize::visitArrayGet(ArrayGet* curr) { curr->finalize(); } void ReFinalize::visitArraySet(ArraySet* curr) { curr->finalize(); } +void ReFinalize::visitArrayStore(ArrayStore* curr) { curr->finalize(); } void ReFinalize::visitArrayLen(ArrayLen* curr) { curr->finalize(); } void ReFinalize::visitArrayCopy(ArrayCopy* curr) { curr->finalize(); } void ReFinalize::visitArrayFill(ArrayFill* curr) { curr->finalize(); } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index a6f87543814..ed53227797f 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1067,6 +1067,20 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->value, type); } + void visitArrayStore(ArrayStore* curr, + std::optional ht = std::nullopt) { + if (!ht) { + if (!curr->ref->type.isRef()) { + self().noteUnknown(); + return; + } + ht = curr->ref->type.getHeapType(); + } + note(&curr->ref, Type(*ht, Nullable)); + note(&curr->index, Type::i32); + note(&curr->value, curr->valueType); + } + void visitArrayLen(ArrayLen* curr) { note(&curr->ref, Type(HeapType::array, Nullable)); } diff --git a/src/ir/cost.h b/src/ir/cost.h index 853040a3f19..258a31d24d9 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -745,6 +745,10 @@ struct CostAnalyzer : public OverriddenVisitor { return 2 + nullCheckCost(curr->ref) + visit(curr->ref) + visit(curr->index) + visit(curr->value); } + CostType visitArrayStore(ArrayStore* curr) { + return 2 + nullCheckCost(curr->ref) + visit(curr->ref) + + visit(curr->index) + visit(curr->value); + } CostType visitArrayLen(ArrayLen* curr) { return 1 + nullCheckCost(curr->ref) + visit(curr->ref); } diff --git a/src/ir/effects.h b/src/ir/effects.h index 1a7f4af616b..504126de592 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -979,6 +979,15 @@ class EffectAnalyzer { // traps when the arg is null or the index out of bounds parent.implicitTrap = true; } + void visitArrayStore(ArrayStore* curr) { + if (curr->ref->type.isNull()) { + parent.trap = true; + return; + } + parent.writesArray = true; + // traps when the arg is null or the index out of bounds + parent.implicitTrap = true; + } void visitArrayLen(ArrayLen* curr) { if (curr->ref->type.isNull()) { parent.trap = true; diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 06825343d1f..2aa32dab75b 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1095,6 +1095,13 @@ struct InfoCollector addChildParentLink(curr->ref, curr); addChildParentLink(curr->value, curr); } + void visitArrayStore(ArrayStore* curr) { + if (curr->ref->type == Type::unreachable) { + return; + } + addChildParentLink(curr->ref, curr); + addChildParentLink(curr->value, curr); + } void visitArrayLen(ArrayLen* curr) { // TODO: optimize when possible (perhaps we can infer a Literal for the @@ -1732,6 +1739,7 @@ void TNHOracle::scan(Function* func, } void visitArrayGet(ArrayGet* curr) { notePossibleTrap(curr->ref); } void visitArraySet(ArraySet* curr) { notePossibleTrap(curr->ref); } + void visitArrayStore(ArrayStore* curr) { notePossibleTrap(curr->ref); } void visitArrayLen(ArrayLen* curr) { notePossibleTrap(curr->ref); } void visitArrayCopy(ArrayCopy* curr) { notePossibleTrap(curr->srcRef); diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 5e8bac40a1f..653381a4c7b 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -406,6 +406,13 @@ struct SubtypingDiscoverer : public OverriddenVisitor { auto array = curr->ref->type.getHeapType().getArray(); self()->noteSubtype(curr->value, array.element.type); } + void visitArrayStore(ArrayStore* curr) { + if (!curr->ref->type.isArray()) { + return; + } + auto array = curr->ref->type.getHeapType().getArray(); + self()->noteSubtype(curr->value, array.element.type); + } void visitArrayLen(ArrayLen* curr) {} void visitArrayCopy(ArrayCopy* curr) { if (!curr->srcRef->type.isArray() || !curr->destRef->type.isArray()) { diff --git a/src/parser/contexts.h b/src/parser/contexts.h index c16ac92268e..b28801a1149 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -571,6 +571,14 @@ struct NullInstrParserCtx { MemoryOrder) { return Ok{}; } + template + Result<> makeArrayStore(Index, + const std::vector&, + Type, + int, + HeapTypeT) { + return Ok{}; + } Result<> makeAtomicRMW(Index, const std::vector&, AtomicRMWOp, @@ -2302,6 +2310,14 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { pos, irBuilder.makeStore(bytes, memarg.offset, memarg.align, type, *m)); } + Result<> makeArrayStore(Index pos, + const std::vector& annotations, + Type type, + int bytes, + HeapTypeT arrayType) { + return withLoc(pos, irBuilder.makeArrayStore(arrayType, bytes, type)); + } + Result<> makeAtomicRMW(Index pos, const std::vector& annotations, AtomicRMWOp op, diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 2a3a5ec5a4e..6cfd001db8b 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -1786,6 +1786,18 @@ Result<> makeStore(Ctx& ctx, Type type, int bytes, bool isAtomic) { + if (ctx.in.takeSExprStart("type"sv)) { + std::optional arrayType; + auto x = typeidx(ctx); + CHECK_ERR(x); + + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of type use"); + } + + arrayType = *x; + return ctx.makeArrayStore(pos, annotations, type, bytes, *arrayType); + } auto mem = maybeMemidx(ctx); CHECK_ERR(mem); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 6f2f558e1ec..0067dc99566 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -436,6 +436,25 @@ struct PrintExpressionContents return parent.printBlockType(sig); } + std::ostream& printStorePostfix(uint8_t bytes, Type valueType) { + if (bytes < 4 || (valueType == Type::i64 && bytes < 8)) { + if (bytes == 1) { + o << '8'; + } else if (bytes == 2) { + if (valueType == Type::f32) { + o << "_f16"; + } else { + o << "16"; + } + } else if (bytes == 4) { + o << "32"; + } else { + abort(); + } + } + return o; + } + void visitBlock(Block* curr) { printMedium(o, "block"); if (curr->name.is()) { @@ -588,21 +607,7 @@ struct PrintExpressionContents o << ".atomic"; } o << ".store"; - if (curr->bytes < 4 || (curr->valueType == Type::i64 && curr->bytes < 8)) { - if (curr->bytes == 1) { - o << '8'; - } else if (curr->bytes == 2) { - if (curr->valueType == Type::f32) { - o << "_f16"; - } else { - o << "16"; - } - } else if (curr->bytes == 4) { - o << "32"; - } else { - abort(); - } - } + printStorePostfix(curr->bytes, curr->valueType); restoreNormalColor(o); printMemoryName(curr->memory, o, wasm); printMemoryOrder(curr->order); @@ -2462,6 +2467,18 @@ struct PrintExpressionContents o << ' '; printHeapTypeName(curr->ref->type.getHeapType()); } + void visitArrayStore(ArrayStore* curr) { + prepareColor(o) << forceConcrete(curr->valueType); + o << ".store"; + printStorePostfix(curr->bytes, curr->valueType); + o << " "; + restoreNormalColor(o); + + o << '('; + printMinor(o, "type "); + printHeapTypeName(curr->ref->type.getHeapType()); + o << ')'; + } void visitArrayLen(ArrayLen* curr) { printMedium(o, "array.len"); } void visitArrayCopy(ArrayCopy* curr) { printMedium(o, "array.copy "); diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index c0607ef51be..7a23f9c0f05 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -797,6 +797,8 @@ struct TransferFn : OverriddenVisitor { } } + void visitArrayStore(ArrayStore* curr) { WASM_UNREACHABLE("TODO"); } + void visitArrayLen(ArrayLen* curr) { // The input must be an array. push(Type(HeapType::array, Nullable)); diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index f12735636a2..8d2037deb73 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -529,6 +529,7 @@ class TranslateToFuzzReader { Expression* makeStructSet(Type type); Expression* makeArrayGet(Type type); Expression* makeArraySet(Type type); + Expression* makeArrayStore(Type type); // Use a single method for the misc array operations, to not give them too // much representation (e.g. compared to struct operations, which only include // get/set). diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 0e7bab677f4..407b92b373b 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -2531,6 +2531,8 @@ Expression* TranslateToFuzzReader::_makenone() { .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeStructSet) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeArraySet) + .add(FeatureSet::ReferenceTypes | FeatureSet::GC | FeatureSet::Multibyte, + &Self::makeArrayStore) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeBrOn) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeArrayBulkMemoryOp); @@ -5463,6 +5465,47 @@ Expression* TranslateToFuzzReader::makeArraySet(Type type) { return builder.makeIf(check.condition, set); } +Expression* TranslateToFuzzReader::makeArrayStore(Type type) { + assert(type == Type::none); + std::vector i8Arrays; + for (auto array : mutableArrays) { + if (array.getArray().element.packedType == Field::i8) { + i8Arrays.push_back(array); + } + } + if (i8Arrays.empty()) { + return makeTrivial(type); + } + auto arrayType = pick(i8Arrays); + auto* ref = makeTrappingRefUse(arrayType); + auto* index = make(Type::i32); + auto field = arrayType.getArray().element; + // TODO: non-default lanes + uint8_t lanes = 1; + auto bytes = field.type.getByteSize() * lanes; + if (!bytes) { + return makeTrivial(Type::none); + } + auto valueType = field.type; + if (valueType.isRef()) { + // ArrayStore only works for non-ref types. + return makeTrivial(Type::none); + } + // We can only store things that fit in the field. + // We also don't want to store a value that is too large for the field, to + // avoid truncation. + // For now, just use the same type as the field. + auto* value = make(valueType); + if (allowOOB && oneIn(10)) { + return builder.makeArrayStore(bytes, valueType, ref, index, value); + } + auto check = makeArrayBoundsCheck(ref, index, funcContext->func, builder); + auto* store = builder.makeArrayStore(bytes, valueType, check.getRef, check.getIndex, value); + return builder.makeIf(check.condition, store, builder.makeNop()); +} + + + Expression* TranslateToFuzzReader::makeArrayBulkMemoryOp(Type type) { assert(type == Type::none); if (mutableArrays.empty()) { diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index 298fb5db349..b3b5363551f 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -108,6 +108,7 @@ struct ToolOptions : public Options { .addFeature(FeatureSet::FP16, "float 16 operations") .addFeature(FeatureSet::CustomDescriptors, "custom descriptors (RTTs) and exact references") + .addFeature(FeatureSet::Multibyte, "multibyte array loads and stores") .addFeature(FeatureSet::RelaxedAtomics, "acquire/release atomic memory operations") .add("--enable-typed-function-references", diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 52a6ad9dfeb..06bb10b913f 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -358,6 +358,7 @@ enum BrOnCastFlag { constexpr uint32_t ExactImport = 1 << 5; +constexpr uint32_t HasBackingArrayMask = 1 << 4; constexpr uint32_t HasMemoryOrderMask = 1 << 5; constexpr uint32_t HasMemoryIndexMask = 1 << 6; @@ -462,6 +463,7 @@ extern const char* BulkMemoryOptFeature; extern const char* CallIndirectOverlongFeature; extern const char* CustomDescriptorsFeature; extern const char* RelaxedAtomicsFeature; +extern const char* MultibyteFeature; enum Subsection { NameModule = 0, @@ -1693,6 +1695,8 @@ class WasmBinaryReader { void readExports(); + Result<> readStore(unsigned bytes, Type type); + // The strings in the strings section (which are referred to by StringConst). std::vector strings; void readStrings(); @@ -1739,11 +1743,11 @@ class WasmBinaryReader { void readRemovableIfUnusedHints(size_t payloadLen); void readJSCalledHints(size_t payloadLen); - std::tuple + std::tuple readMemoryAccess(bool isAtomic, bool isRMW); std::tuple getAtomicMemarg(); std::tuple getRMWMemarg(); - std::tuple getMemarg(); + std::tuple getMemarg(); MemoryOrder getMemoryOrder(bool isRMW = false); [[noreturn]] void throwError(std::string text) { diff --git a/src/wasm-builder.h b/src/wasm-builder.h index cd73babb6ca..abef5f189ed 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1129,6 +1129,20 @@ class Builder { ret->finalize(); return ret; } + ArrayStore* makeArrayStore(unsigned bytes, + Type valueType, + Expression* ref, + Expression* index, + Expression* value) { + auto* ret = wasm.allocator.alloc(); + ret->bytes = bytes; + ret->valueType = valueType; + ret->ref = ref; + ret->index = index; + ret->value = value; + ret->finalize(); + return ret; + } ArrayLen* makeArrayLen(Expression* ref) { auto* ret = wasm.allocator.alloc(); ret->ref = ref; diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 895fa8b9276..02f01e7aa23 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -745,6 +745,14 @@ DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArraySet, ref) DELEGATE_FIELD_INT(ArraySet, order) DELEGATE_FIELD_CASE_END(ArraySet) +DELEGATE_FIELD_CASE_START(ArrayStore) +DELEGATE_FIELD_CHILD(ArrayStore, value) +DELEGATE_FIELD_CHILD(ArrayStore, index) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArrayStore, ref) +DELEGATE_FIELD_INT(ArrayStore, bytes) +DELEGATE_FIELD_TYPE(ArrayStore, valueType) +DELEGATE_FIELD_CASE_END(ArrayStore) + DELEGATE_FIELD_CASE_START(ArrayLen) DELEGATE_FIELD_CHILD(ArrayLen, ref) DELEGATE_FIELD_CASE_END(ArrayLen) diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index dcec0e6e938..cfafa4fe3b4 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -92,6 +92,7 @@ DELEGATE(ArrayNewElem); DELEGATE(ArrayNewFixed); DELEGATE(ArrayGet); DELEGATE(ArraySet); +DELEGATE(ArrayStore); DELEGATE(ArrayLen); DELEGATE(ArrayCopy); DELEGATE(ArrayFill); diff --git a/src/wasm-features.h b/src/wasm-features.h index 7f4b0a451af..a9c03611210 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -56,11 +56,12 @@ struct FeatureSet { CallIndirectOverlong = 1 << 20, CustomDescriptors = 1 << 21, RelaxedAtomics = 1 << 22, + Multibyte = 1 << 23, MVP = None, // Keep in sync with llvm default features: // https://github.com/llvm/llvm-project/blob/c7576cb89d6c95f03968076e902d3adfd1996577/clang/lib/Basic/Targets/WebAssembly.cpp#L150-L153 Default = SignExt | MutableGlobals, - All = (1 << 23) - 1, + All = (1 << 24) - 1, }; static std::string toString(Feature f) { @@ -111,6 +112,8 @@ struct FeatureSet { return "custom-descriptors"; case RelaxedAtomics: return "relaxed-atomics"; + case Multibyte: + return "multibyte"; case MVP: case Default: case All: @@ -172,6 +175,7 @@ struct FeatureSet { return (features & CustomDescriptors) != 0; } bool hasRelaxedAtomics() const { return (features & RelaxedAtomics) != 0; } + bool hasMultibyte() const { return (features & Multibyte) != 0; } bool hasAll() const { return (features & All) != 0; } void set(FeatureSet f, bool v = true) { @@ -199,6 +203,7 @@ struct FeatureSet { void setBulkMemoryOpt(bool v = true) { set(BulkMemoryOpt, v); } void setCustomDescriptors(bool v = true) { set(CustomDescriptors, v); } void setRelaxedAtomics(bool v = true) { set(RelaxedAtomics, v); } + void setMultibyte(bool v = true) { set(Multibyte, v); } void setMVP() { features = MVP; } void setAll() { features = All; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 5ae570437ed..c9a8afd27b2 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -383,6 +383,20 @@ class ExpressionRunner : public OverriddenVisitor { return Literal(allocation); } + template + void writeBytes(T value, int numBytes, size_t index, Literals& values) { + if constexpr (std::is_same_v>) { + for (int i = 0; i < numBytes; ++i) { + values[index + i] = Literal(static_cast(value[i])); + } + } else { + for (int i = 0; i < numBytes; ++i) { + values[index + i] = + Literal(static_cast((value >> (i * 8)) & 0xff)); + } + } + } + public: // Indicates no limit of maxDepth or maxLoopIterations. static const Index NO_LIMIT = 0; @@ -2337,6 +2351,54 @@ class ExpressionRunner : public OverriddenVisitor { data->values[i] = truncateForPacking(value.getSingleValue(), field); return Flow(); } + Flow visitArrayStore(ArrayStore* curr) { + VISIT(ref, curr->ref) + VISIT(index, curr->index) + VISIT(value, curr->value) + auto data = ref.getSingleValue().getGCData(); + if (!data) { + trap("null ref"); + } + + Index i = index.getSingleValue().geti32(); + size_t size = data->values.size(); + // Use subtraction to avoid overflow. + if (i >= size || curr->bytes > (size - i)) { + trap("array oob"); + } + switch (curr->valueType.getBasic()) { + case Type::i32: + writeBytes( + value.getSingleValue().geti32(), curr->bytes, i, data->values); + break; + case Type::i64: + writeBytes( + value.getSingleValue().geti64(), curr->bytes, i, data->values); + break; + case Type::f32: + writeBytes(value.getSingleValue().reinterpreti32(), + curr->bytes, + i, + data->values); + break; + case Type::f64: + writeBytes(value.getSingleValue().reinterpreti64(), + curr->bytes, + i, + data->values); + break; + case Type::v128: + writeBytes(value.getSingleValue().getv128(), + curr->bytes, + i, + data->values); + break; + case Type::none: + case Type::unreachable: + WASM_UNREACHABLE("unimp basic type"); + } + return Flow(); + } Flow visitArrayLen(ArrayLen* curr) { VISIT(ref, curr->ref) auto data = ref.getSingleValue().getGCData(); diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index 82b7fc68450..91926e308fa 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -249,6 +249,7 @@ class IRBuilder : public UnifiedExpressionVisitor> { Result<> makeArrayNewFixed(HeapType type, uint32_t arity); Result<> makeArrayGet(HeapType type, bool signed_, MemoryOrder order); Result<> makeArraySet(HeapType type, MemoryOrder order); + Result<> makeArrayStore(HeapType arrayType, unsigned bytes, Type type); Result<> makeArrayLen(); Result<> makeArrayCopy(HeapType destType, HeapType srcType); Result<> makeArrayFill(HeapType type); diff --git a/src/wasm-stack.h b/src/wasm-stack.h index 961d2f5ab60..1351396eb29 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -131,7 +131,9 @@ class BinaryInstWriter : public OverriddenVisitor { uint64_t offset, Name memory, MemoryOrder order, - bool isRMW); + bool isRMW, + BackingType backing = BackingType::Memory); + void emitStore(uint8_t bytes, Type ValueType); int32_t getBreakIndex(Name name); WasmBinaryWriter& parent; diff --git a/src/wasm.h b/src/wasm.h index c35b1ea2531..4dbc8daab6c 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -72,6 +72,11 @@ enum class MemoryOrder : uint8_t { AcqRel, }; +enum class BackingType { + Memory, + Array, +}; + enum class IRProfile { Normal, Poppy }; // Operators @@ -736,6 +741,7 @@ class Expression { ArrayNewFixedId, ArrayGetId, ArraySetId, + ArrayStoreId, ArrayLenId, ArrayCopyId, ArrayFillId, @@ -1834,6 +1840,20 @@ class ArraySet : public SpecificExpression { void finalize(); }; +class ArrayStore : public SpecificExpression { +public: + ArrayStore() = default; + ArrayStore(MixedArena& allocator) {} + + uint8_t bytes; + Expression* ref; + Expression* index; + Expression* value; + Type valueType; + + void finalize(); +}; + class ArrayLen : public SpecificExpression { public: ArrayLen() = default; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 557527450d9..7f81cfe04e8 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1417,6 +1417,8 @@ void WasmBinaryWriter::writeFeaturesSection() { return BinaryConsts::CustomSections::MutableGlobalsFeature; case FeatureSet::TruncSat: return BinaryConsts::CustomSections::TruncSatFeature; + case FeatureSet::Multibyte: + return BinaryConsts::CustomSections::MultibyteFeature; case FeatureSet::SIMD: return BinaryConsts::CustomSections::SIMD128Feature; case FeatureSet::BulkMemory: @@ -3247,6 +3249,15 @@ void WasmBinaryReader::readVars() { } } +Result<> WasmBinaryReader::readStore(unsigned bytes, Type type) { + auto [mem, align, offset, backing] = getMemarg(); + if (backing == BackingType::Array) { + HeapType arrayType = getIndexedHeapType(); + return builder.makeArrayStore(arrayType, bytes, type); + } + return builder.makeStore(bytes, offset, align, type, mem); +} + Result<> WasmBinaryReader::readInst() { if (auto loc = sourceMapReader.readDebugLocationAt(pos)) { builder.setDebugLocation(loc); @@ -3586,96 +3597,87 @@ Result<> WasmBinaryReader::readInst() { case BinaryConsts::F64Const: return builder.makeConst(getFloat64Literal()); case BinaryConsts::I32LoadMem8S: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(1, true, offset, align, Type::i32, mem); } case BinaryConsts::I32LoadMem8U: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(1, false, offset, align, Type::i32, mem); } case BinaryConsts::I32LoadMem16S: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(2, true, offset, align, Type::i32, mem); } case BinaryConsts::I32LoadMem16U: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(2, false, offset, align, Type::i32, mem); } case BinaryConsts::I32LoadMem: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(4, false, offset, align, Type::i32, mem); } case BinaryConsts::I64LoadMem8S: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(1, true, offset, align, Type::i64, mem); } case BinaryConsts::I64LoadMem8U: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(1, false, offset, align, Type::i64, mem); } case BinaryConsts::I64LoadMem16S: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(2, true, offset, align, Type::i64, mem); } case BinaryConsts::I64LoadMem16U: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(2, false, offset, align, Type::i64, mem); } case BinaryConsts::I64LoadMem32S: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(4, true, offset, align, Type::i64, mem); } case BinaryConsts::I64LoadMem32U: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(4, false, offset, align, Type::i64, mem); } case BinaryConsts::I64LoadMem: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(8, false, offset, align, Type::i64, mem); } case BinaryConsts::F32LoadMem: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(4, false, offset, align, Type::f32, mem); } case BinaryConsts::F64LoadMem: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(8, false, offset, align, Type::f64, mem); } case BinaryConsts::I32StoreMem8: { - auto [mem, align, offset] = getMemarg(); - return builder.makeStore(1, offset, align, Type::i32, mem); + return readStore(1, Type::i32); } case BinaryConsts::I32StoreMem16: { - auto [mem, align, offset] = getMemarg(); - return builder.makeStore(2, offset, align, Type::i32, mem); + return readStore(2, Type::i32); } case BinaryConsts::I32StoreMem: { - auto [mem, align, offset] = getMemarg(); - return builder.makeStore(4, offset, align, Type::i32, mem); + return readStore(4, Type::i32); } case BinaryConsts::I64StoreMem8: { - auto [mem, align, offset] = getMemarg(); - return builder.makeStore(1, offset, align, Type::i64, mem); + return readStore(1, Type::i64); } case BinaryConsts::I64StoreMem16: { - auto [mem, align, offset] = getMemarg(); - return builder.makeStore(2, offset, align, Type::i64, mem); + return readStore(2, Type::i64); } case BinaryConsts::I64StoreMem32: { - auto [mem, align, offset] = getMemarg(); - return builder.makeStore(4, offset, align, Type::i64, mem); + return readStore(4, Type::i64); } case BinaryConsts::I64StoreMem: { - auto [mem, align, offset] = getMemarg(); - return builder.makeStore(8, offset, align, Type::i64, mem); + return readStore(8, Type::i64); } case BinaryConsts::F32StoreMem: { - auto [mem, align, offset] = getMemarg(); - return builder.makeStore(4, offset, align, Type::f32, mem); + return readStore(4, Type::f32); } case BinaryConsts::F64StoreMem: { - auto [mem, align, offset] = getMemarg(); - return builder.makeStore(8, offset, align, Type::f64, mem); + return readStore(8, Type::f64); } case BinaryConsts::AtomicPrefix: { auto op = getU32LEB(); @@ -3970,11 +3972,11 @@ Result<> WasmBinaryReader::readInst() { return builder.makeElemDrop(elem); } case BinaryConsts::F32_F16LoadMem: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(2, false, offset, align, Type::f32, mem); } case BinaryConsts::F32_F16StoreMem: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeStore(2, offset, align, Type::f32, mem); } } @@ -4519,98 +4521,98 @@ Result<> WasmBinaryReader::readInst() { case BinaryConsts::V128Const: return builder.makeConst(getVec128Literal()); case BinaryConsts::V128Store: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeStore(16, offset, align, Type::v128, mem); } case BinaryConsts::V128Load: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeLoad(16, false, offset, align, Type::v128, mem); } case BinaryConsts::V128Load8Splat: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoad(Load8SplatVec128, offset, align, mem); } case BinaryConsts::V128Load16Splat: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoad(Load16SplatVec128, offset, align, mem); } case BinaryConsts::V128Load32Splat: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoad(Load32SplatVec128, offset, align, mem); } case BinaryConsts::V128Load64Splat: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoad(Load64SplatVec128, offset, align, mem); } case BinaryConsts::V128Load8x8S: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoad(Load8x8SVec128, offset, align, mem); } case BinaryConsts::V128Load8x8U: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoad(Load8x8UVec128, offset, align, mem); } case BinaryConsts::V128Load16x4S: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoad(Load16x4SVec128, offset, align, mem); } case BinaryConsts::V128Load16x4U: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoad(Load16x4UVec128, offset, align, mem); } case BinaryConsts::V128Load32x2S: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoad(Load32x2SVec128, offset, align, mem); } case BinaryConsts::V128Load32x2U: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoad(Load32x2UVec128, offset, align, mem); } case BinaryConsts::V128Load32Zero: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoad(Load32ZeroVec128, offset, align, mem); } case BinaryConsts::V128Load64Zero: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoad(Load64ZeroVec128, offset, align, mem); } case BinaryConsts::V128Load8Lane: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoadStoreLane( Load8LaneVec128, offset, align, getLaneIndex(16), mem); } case BinaryConsts::V128Load16Lane: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoadStoreLane( Load16LaneVec128, offset, align, getLaneIndex(8), mem); } case BinaryConsts::V128Load32Lane: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoadStoreLane( Load32LaneVec128, offset, align, getLaneIndex(4), mem); } case BinaryConsts::V128Load64Lane: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoadStoreLane( Load64LaneVec128, offset, align, getLaneIndex(2), mem); } case BinaryConsts::V128Store8Lane: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoadStoreLane( Store8LaneVec128, offset, align, getLaneIndex(16), mem); } case BinaryConsts::V128Store16Lane: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoadStoreLane( Store16LaneVec128, offset, align, getLaneIndex(8), mem); } case BinaryConsts::V128Store32Lane: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoadStoreLane( Store32LaneVec128, offset, align, getLaneIndex(4), mem); } case BinaryConsts::V128Store64Lane: { - auto [mem, align, offset] = getMemarg(); + auto [mem, align, offset, backing] = getMemarg(); return builder.makeSIMDLoadStoreLane( Store64LaneVec128, offset, align, getLaneIndex(2), mem); } @@ -5574,10 +5576,12 @@ void WasmBinaryReader::readJSCalledHints(size_t payloadLen) { }); } -std::tuple +std::tuple WasmBinaryReader::readMemoryAccess(bool isAtomic, bool isRMW) { auto rawAlignment = getU32LEB(); + BackingType backing = BackingType::Memory; Index memIdx = 0; + Address offset = 0; bool hasMemoryOrder = rawAlignment & BinaryConsts::HasMemoryOrderMask; if (hasMemoryOrder && !isAtomic) { @@ -5595,6 +5599,12 @@ WasmBinaryReader::readMemoryAccess(bool isAtomic, bool isRMW) { rawAlignment = rawAlignment & ~BinaryConsts::HasMemoryIndexMask; } + if (rawAlignment & BinaryConsts::HasBackingArrayMask) { + backing = BackingType::Array; + // Clear the bit before we parse alignment + rawAlignment = rawAlignment & ~BinaryConsts::HasBackingArrayMask; + } + if (rawAlignment > 8) { throwError("Alignment must be of a reasonable size"); } @@ -5602,41 +5612,53 @@ WasmBinaryReader::readMemoryAccess(bool isAtomic, bool isRMW) { Address alignment = Bits::pow2(rawAlignment); MemoryOrder memoryOrder = isAtomic ? MemoryOrder::SeqCst : MemoryOrder::Unordered; - if (hasMemIdx) { - memIdx = getU32LEB(); - } - if (hasMemoryOrder) { - memoryOrder = getMemoryOrder(isRMW); - } - if (memIdx >= wasm.memories.size()) { - throwError("Memory index out of range while reading memory alignment."); + + if (backing == BackingType::Memory) { + if (hasMemIdx) { + memIdx = getU32LEB(); + } + if (hasMemoryOrder) { + memoryOrder = getMemoryOrder(isRMW); + } + if (memIdx >= wasm.memories.size()) { + throwError("Memory index out of range while reading memory alignment."); + } + auto* memory = wasm.memories[memIdx].get(); + offset = memory->addressType == Type::i32 ? getU32LEB() : getU64LEB(); + } else { + // TODO: don't allow memIdx or memoryOrder when backing type is array? } - auto* memory = wasm.memories[memIdx].get(); - Address offset = memory->addressType == Type::i32 ? getU32LEB() : getU64LEB(); - return {alignment, offset, memIdx, memoryOrder}; + return {alignment, offset, memIdx, memoryOrder, backing}; } std::tuple WasmBinaryReader::getAtomicMemarg() { - auto [alignment, offset, memIdx, memoryOrder] = + auto [alignment, offset, memIdx, memoryOrder, backing] = readMemoryAccess(/*isAtomic=*/true, /*isRMW=*/false); return {getMemoryName(memIdx), alignment, offset, memoryOrder}; } std::tuple WasmBinaryReader::getRMWMemarg() { - auto [alignment, offset, memIdx, memoryOrder] = + auto [alignment, offset, memIdx, memoryOrder, backing] = readMemoryAccess(/*isAtomic=*/true, /*isRMW=*/true); return {getMemoryName(memIdx), alignment, offset, memoryOrder}; } -std::tuple WasmBinaryReader::getMemarg() { - auto [alignment, offset, memIdx, _] = +std::tuple +WasmBinaryReader::getMemarg() { + auto [alignment, offset, memIdx, memoryOrder, backing] = readMemoryAccess(/*isAtomic=*/false, /*isRMW=*/false); - return {getMemoryName(memIdx), alignment, offset}; + if (backing == BackingType::Array) { + // ??? how does binaryen usually handle empty names or maybe we shouldn't + // return a name? + return {{}, alignment, offset, backing}; + } + return {getMemoryName(memIdx), alignment, offset, backing}; } + MemoryOrder WasmBinaryReader::getMemoryOrder(bool isRMW) { auto code = getInt8(); switch (code) { diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 23ff8764971..bed9962f6d3 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -550,6 +550,13 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } + Result<> visitArrayStore(ArrayStore* curr, + std::optional ht = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitArrayStore(curr, ht); + return popConstrainedChildren(children); + } + Result<> visitArrayCopy(ArrayCopy* curr, std::optional dest = std::nullopt, std::optional src = std::nullopt) { @@ -2308,6 +2315,15 @@ Result<> IRBuilder::makeArraySet(HeapType type, MemoryOrder order) { return Ok{}; } +Result<> IRBuilder::makeArrayStore(HeapType arrayType, unsigned bytes, Type type) { + ArrayStore curr; + curr.valueType = type; + CHECK_ERR( + ChildPopper{*this}.visitArrayStore(&curr, HeapTypes::getMutI8Array())); + push(builder.makeArrayStore(bytes, type, curr.ref, curr.index, curr.value)); + return Ok{}; +} + Result<> IRBuilder::makeArrayLen() { ArrayLen curr; CHECK_ERR(visitArrayLen(&curr)); diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 0199e217c6a..b5ada6222a1 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -56,6 +56,69 @@ void BinaryInstWriter::emitIfElse(If* curr) { o << int8_t(BinaryConsts::Else); } +void BinaryInstWriter::emitStore(uint8_t bytes, Type valueType) { + switch (valueType.getBasic()) { + case Type::i32: { + switch (bytes) { + case 1: + o << int8_t(BinaryConsts::I32StoreMem8); + break; + case 2: + o << int8_t(BinaryConsts::I32StoreMem16); + break; + case 4: + o << int8_t(BinaryConsts::I32StoreMem); + break; + default: + abort(); + } + break; + } + case Type::i64: { + switch (bytes) { + case 1: + o << int8_t(BinaryConsts::I64StoreMem8); + break; + case 2: + o << int8_t(BinaryConsts::I64StoreMem16); + break; + case 4: + o << int8_t(BinaryConsts::I64StoreMem32); + break; + case 8: + o << int8_t(BinaryConsts::I64StoreMem); + break; + default: + abort(); + } + break; + } + case Type::f32: { + switch (bytes) { + case 2: + o << int8_t(BinaryConsts::MiscPrefix) + << U32LEB(BinaryConsts::F32_F16StoreMem); + break; + case 4: + o << int8_t(BinaryConsts::F32StoreMem); + break; + default: + WASM_UNREACHABLE("invalid store size"); + } + break; + } + case Type::f64: + o << int8_t(BinaryConsts::F64StoreMem); + break; + case Type::v128: + o << int8_t(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::V128Store); + break; + case Type::none: + case Type::unreachable: + WASM_UNREACHABLE("unexpected type"); + } +} + void BinaryInstWriter::visitLoop(Loop* curr) { breakStack.push_back(curr->name); o << int8_t(BinaryConsts::Loop); @@ -371,67 +434,7 @@ void BinaryInstWriter::visitLoad(Load* curr) { void BinaryInstWriter::visitStore(Store* curr) { if (!curr->isAtomic()) { - switch (curr->valueType.getBasic()) { - case Type::i32: { - switch (curr->bytes) { - case 1: - o << int8_t(BinaryConsts::I32StoreMem8); - break; - case 2: - o << int8_t(BinaryConsts::I32StoreMem16); - break; - case 4: - o << int8_t(BinaryConsts::I32StoreMem); - break; - default: - abort(); - } - break; - } - case Type::i64: { - switch (curr->bytes) { - case 1: - o << int8_t(BinaryConsts::I64StoreMem8); - break; - case 2: - o << int8_t(BinaryConsts::I64StoreMem16); - break; - case 4: - o << int8_t(BinaryConsts::I64StoreMem32); - break; - case 8: - o << int8_t(BinaryConsts::I64StoreMem); - break; - default: - abort(); - } - break; - } - case Type::f32: { - switch (curr->bytes) { - case 2: - o << int8_t(BinaryConsts::MiscPrefix) - << U32LEB(BinaryConsts::F32_F16StoreMem); - break; - case 4: - o << int8_t(BinaryConsts::F32StoreMem); - break; - default: - WASM_UNREACHABLE("invalid store size"); - } - break; - } - case Type::f64: - o << int8_t(BinaryConsts::F64StoreMem); - break; - case Type::v128: - o << int8_t(BinaryConsts::SIMDPrefix) - << U32LEB(BinaryConsts::V128Store); - break; - case Type::none: - case Type::unreachable: - WASM_UNREACHABLE("unexpected type"); - } + emitStore(curr->bytes, curr->valueType); } else { o << int8_t(BinaryConsts::AtomicPrefix); switch (curr->valueType.getBasic()) { @@ -2586,6 +2589,17 @@ void BinaryInstWriter::visitArraySet(ArraySet* curr) { parent.writeIndexedHeapType(curr->ref->type.getHeapType()); } +void BinaryInstWriter::visitArrayStore(ArrayStore* curr) { + if (curr->ref->type.isNull()) { + emitUnreachable(); + return; + } + emitStore(curr->bytes, curr->valueType); + uint32_t alignmentBits = BinaryConsts::HasBackingArrayMask; + o << U32LEB(alignmentBits); + parent.writeIndexedHeapType(curr->ref->type.getHeapType()); +} + void BinaryInstWriter::visitArrayLen(ArrayLen* curr) { o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArrayLen); } @@ -3232,8 +3246,14 @@ void BinaryInstWriter::emitMemoryAccess(size_t alignment, uint64_t offset, Name memory, MemoryOrder order, - bool isRMW) { + bool isRMW, + BackingType backing) { uint32_t alignmentBits = Bits::log2(alignment ? alignment : bytes); + if (backing == BackingType::Array) { + alignmentBits |= BinaryConsts::HasBackingArrayMask; + o << U32LEB(alignmentBits); + return; + } uint32_t memoryIdx = parent.getMemoryIndex(memory); bool shouldWriteMemoryOrder = false; diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 9217021f09c..7b08e51bf0d 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -516,6 +516,7 @@ struct FunctionValidator : public WalkerPass> { void visitArrayNewFixed(ArrayNewFixed* curr); void visitArrayGet(ArrayGet* curr); void visitArraySet(ArraySet* curr); + void visitArrayStore(ArrayStore* curr); void visitArrayLen(ArrayLen* curr); void visitArrayCopy(ArrayCopy* curr); void visitArrayFill(ArrayFill* curr); @@ -3656,6 +3657,28 @@ void FunctionValidator::visitArraySet(ArraySet* curr) { shouldBeTrue(element.mutable_, curr, "array.set type must be mutable"); } +void FunctionValidator::visitArrayStore(ArrayStore* curr) { + shouldBeTrue( + getModule()->features.hasMultibyte(), curr, "array.store requires multibyte [--enable-multibyte]"); + shouldBeEqualOrFirstIsUnreachable( + curr->index->type, Type(Type::i32), curr, "array store index must be an i32"); + if (curr->type == Type::unreachable) { + return; + } + const char* mustBeArray = "array store target should be an array reference"; + if (curr->type == Type::unreachable || + !shouldBeTrue(curr->ref->type.isRef(), curr, mustBeArray) || + curr->ref->type.getHeapType().isBottom() || + !shouldBeTrue(curr->ref->type.isArray(), curr, mustBeArray)) { + return; + } + + auto heapType = curr->ref->type.getHeapType(); + const auto& element = heapType.getArray().element; + shouldBeTrue(element.packedType == Field::i8, curr, "array store type must be i8"); + shouldBeTrue(element.mutable_, curr, "array store type must be mutable"); +} + void FunctionValidator::visitArrayLen(ArrayLen* curr) { shouldBeTrue( getModule()->features.hasGC(), curr, "array.len requires gc [--enable-gc]"); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 959b6cd4bfe..933176bcb54 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -62,6 +62,7 @@ const char* BulkMemoryOptFeature = "bulk-memory-opt"; const char* CallIndirectOverlongFeature = "call-indirect-overlong"; const char* CustomDescriptorsFeature = "custom-descriptors"; const char* RelaxedAtomicsFeature = "relaxed-atomics"; +const char* MultibyteFeature = "multibyte"; } // namespace BinaryConsts::CustomSections @@ -1326,6 +1327,15 @@ void ArraySet::finalize() { } } +void ArrayStore::finalize() { + if (ref->type == Type::unreachable || index->type == Type::unreachable || + value->type == Type::unreachable) { + type = Type::unreachable; + } else { + type = Type::none; + } +} + void ArrayLen::finalize() { if (ref->type == Type::unreachable) { type = Type::unreachable; diff --git a/src/wasm2js.h b/src/wasm2js.h index b92a7f851e5..5b85324ffc6 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2364,6 +2364,10 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitArrayStore(ArrayStore* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } Ref visitArrayLen(ArrayLen* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index 2499ffff646..93cc77031e1 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -376,6 +376,7 @@ void test_features() { printf("BinaryenFeatureStrings: %d\n", BinaryenFeatureStrings()); printf("BinaryenFeatureRelaxedAtomics: %d\n", BinaryenFeatureRelaxedAtomics()); + printf("BinaryenFeatureMultibyte: %d\n", BinaryenFeatureMultibyte()); printf("BinaryenFeatureAll: %d\n", BinaryenFeatureAll()); } diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 4d68abcddd5..1e6adcd77b4 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -48,7 +48,8 @@ BinaryenFeatureRelaxedSIMD: 4096 BinaryenFeatureExtendedConst: 8192 BinaryenFeatureStrings: 16384 BinaryenFeatureRelaxedAtomics: 4194304 -BinaryenFeatureAll: 8388607 +BinaryenFeatureMultibyte: 8388608 +BinaryenFeatureAll: 16777215 (f32.neg (f32.const -33.61199951171875) ) diff --git a/test/lit/array-multibyte.wast b/test/lit/array-multibyte.wast new file mode 100644 index 00000000000..38b04898801 --- /dev/null +++ b/test/lit/array-multibyte.wast @@ -0,0 +1,126 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Check that array types and operations are emitted properly in the binary format. + +;; RUN: wasm-opt %s -all -S -o - | filecheck %s +;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s --check-prefix=ROUNDTRIP + +;; Check that we can roundtrip through the text format as well. + +;; RUN: wasm-opt %s -all -S -o - | wasm-opt -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $i8_array (array (mut i8))) + ;; ROUNDTRIP: (type $i8_array (array (mut i8))) + (type $i8_array (array (mut i8))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (global $arr (ref $i8_array) (array.new_default $i8_array + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: )) + ;; ROUNDTRIP: (type $1 (func)) + + ;; ROUNDTRIP: (global $arr (ref $i8_array) (array.new_default $i8_array + ;; ROUNDTRIP-NEXT: (i32.const 4) + ;; ROUNDTRIP-NEXT: )) + (global $arr (ref $i8_array) + (array.new_default $i8_array (i32.const 4)) + ) + + ;; CHECK: (func $stores (type $1) + ;; CHECK-NEXT: (i32.store8 (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store16 (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.store (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.store8 (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.store16 (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.store32 (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.store (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (func $stores (type $1) + ;; ROUNDTRIP-NEXT: (i32.store8 (type $i8_array) + ;; ROUNDTRIP-NEXT: (global.get $arr) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: ) + ;; ROUNDTRIP-NEXT: (i32.store16 (type $i8_array) + ;; ROUNDTRIP-NEXT: (global.get $arr) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: ) + ;; ROUNDTRIP-NEXT: (i32.store (type $i8_array) + ;; ROUNDTRIP-NEXT: (global.get $arr) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: ) + ;; ROUNDTRIP-NEXT: (f32.store (type $i8_array) + ;; ROUNDTRIP-NEXT: (global.get $arr) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: (f32.const 0) + ;; ROUNDTRIP-NEXT: ) + ;; ROUNDTRIP-NEXT: (i64.store8 (type $i8_array) + ;; ROUNDTRIP-NEXT: (global.get $arr) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: ) + ;; ROUNDTRIP-NEXT: (i64.store16 (type $i8_array) + ;; ROUNDTRIP-NEXT: (global.get $arr) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: ) + ;; ROUNDTRIP-NEXT: (i64.store32 (type $i8_array) + ;; ROUNDTRIP-NEXT: (global.get $arr) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: ) + ;; ROUNDTRIP-NEXT: (f64.store (type $i8_array) + ;; ROUNDTRIP-NEXT: (global.get $arr) + ;; ROUNDTRIP-NEXT: (i32.const 0) + ;; ROUNDTRIP-NEXT: (f64.const 0) + ;; ROUNDTRIP-NEXT: ) + ;; ROUNDTRIP-NEXT: ) + (func $stores + (i32.store8 (type $i8_array) (global.get $arr) (i32.const 0) (i32.const 0)) + (i32.store16 (type $i8_array) (global.get $arr) (i32.const 0) (i32.const 0)) + (i32.store (type $i8_array) (global.get $arr) (i32.const 0) (i32.const 0)) + (f32.store (type $i8_array) (global.get $arr) (i32.const 0) (f32.const 0)) + + (i64.store8 (type $i8_array) (global.get $arr) (i32.const 0) (i32.const 0)) + (i64.store16 (type $i8_array) (global.get $arr) (i32.const 0) (i32.const 0)) + (i64.store32 (type $i8_array) (global.get $arr) (i32.const 0) (i32.const 0)) + (f64.store (type $i8_array) (global.get $arr) (i32.const 0) (f64.const 0)) + ) +) diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index d9f1c51544b..a732c351e2f 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -134,6 +134,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-multibyte Enable multibyte array loads and stores +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-multibyte Disable multibyte array loads and stores +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-relaxed-atomics Enable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index 3ecda78f368..dae886461f5 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -141,6 +141,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-multibyte Enable multibyte array loads and stores +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-multibyte Disable multibyte array loads and stores +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-relaxed-atomics Enable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test index 4105874814a..50e9214e0ad 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -127,6 +127,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-multibyte Enable multibyte array loads and stores +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-multibyte Disable multibyte array loads and stores +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-relaxed-atomics Enable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test index 0c94205267c..4e97b05df74 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -169,6 +169,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-multibyte Enable multibyte array loads and stores +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-multibyte Disable multibyte array loads and stores +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-relaxed-atomics Enable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index f6837860017..fea0b8dbc2c 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -157,6 +157,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-multibyte Enable multibyte array loads and stores +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-multibyte Disable multibyte array loads and stores +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-relaxed-atomics Enable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 4389a6f59e1..af003ca425c 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -795,6 +795,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors ;; CHECK-NEXT: (RTTs) and exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-multibyte Enable multibyte array loads and +;; CHECK-NEXT: stores +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-multibyte Disable multibyte array loads +;; CHECK-NEXT: and stores +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-relaxed-atomics Enable acquire/release atomic ;; CHECK-NEXT: memory operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 471974be82e..4f19b6a85b0 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -827,6 +827,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors ;; CHECK-NEXT: (RTTs) and exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-multibyte Enable multibyte array loads and +;; CHECK-NEXT: stores +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-multibyte Disable multibyte array loads +;; CHECK-NEXT: and stores +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-relaxed-atomics Enable acquire/release atomic ;; CHECK-NEXT: memory operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index 12d258bed49..ee6ed87b432 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -217,6 +217,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-multibyte Enable multibyte array loads and stores +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-multibyte Disable multibyte array loads and stores +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-relaxed-atomics Enable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index 102add9dfd7..a5279b19fcc 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -275,6 +275,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-multibyte Enable multibyte array loads and stores +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-multibyte Disable multibyte array loads and stores +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-relaxed-atomics Enable acquire/release atomic memory ;; CHECK-NEXT: operations ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 7155ec4fd3e..dcf3fe1cc24 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -759,6 +759,12 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors ;; CHECK-NEXT: (RTTs) and exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-multibyte Enable multibyte array loads and +;; CHECK-NEXT: stores +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-multibyte Disable multibyte array loads +;; CHECK-NEXT: and stores +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-relaxed-atomics Enable acquire/release atomic ;; CHECK-NEXT: memory operations ;; CHECK-NEXT: diff --git a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt index 4d80eb6bb81..f528088e298 100644 --- a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt +++ b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt @@ -21,6 +21,7 @@ --enable-call-indirect-overlong --enable-custom-descriptors --enable-relaxed-atomics +--enable-multibyte (module (type $0 (func (result v128 externref))) (func $foo (type $0) (result v128 externref) diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index dc3c5e52c93..c9ae3fb3285 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,54 +1,50 @@ Metrics total - [exports] : 12 - [funcs] : 8 + [exports] : 15 + [funcs] : 17 [globals] : 26 [imports] : 14 [memories] : 1 [memory-data] : 16 - [table-data] : 2 + [table-data] : 6 [tables] : 2 [tags] : 1 - [total] : 581 - [vars] : 38 - ArrayNewFixed : 10 - AtomicCmpxchg : 1 - Binary : 28 - Block : 86 - BrOn : 2 - Break : 10 - Call : 21 - CallIndirect : 1 + [total] : 487 + [vars] : 73 + ArrayNewFixed : 8 + AtomicFence : 1 + Binary : 26 + Block : 84 + BrOn : 3 + Break : 6 + Call : 17 CallRef : 1 - Const : 105 - ContNew : 1 - DataDrop : 1 - Drop : 3 - GlobalGet : 44 - GlobalSet : 34 - If : 25 - Load : 7 - LocalGet : 30 - LocalSet : 18 - Loop : 9 - Nop : 9 - Pop : 2 - RefAs : 5 - RefCast : 5 - RefEq : 3 - RefFunc : 6 - RefI31 : 4 - RefNull : 10 - Return : 2 - SIMDExtract : 2 - Select : 5 - StringConst : 6 - StringEncode : 1 + Const : 93 + Drop : 14 + GlobalGet : 49 + GlobalSet : 38 + If : 22 + Load : 3 + LocalGet : 7 + LocalSet : 8 + Loop : 2 + Nop : 8 + RefCast : 1 + RefEq : 1 + RefFunc : 12 + RefI31 : 1 + RefNull : 6 + Return : 4 + SIMDExtract : 3 + Select : 1 + Store : 1 + StringConst : 3 + StringEq : 1 + StringMeasure : 1 StringWTF16Get : 1 - StructNew : 6 - Try : 3 - TryTable : 9 - TupleExtract : 8 - TupleMake : 12 - Unary : 28 - Unreachable : 17 + StructNew : 3 + Try : 2 + TryTable : 2 + TupleMake : 9 + Unary : 26 + Unreachable : 19 diff --git a/test/spec/array-multibyte.wast b/test/spec/array-multibyte.wast new file mode 100644 index 00000000000..a3d4676b4a6 --- /dev/null +++ b/test/spec/array-multibyte.wast @@ -0,0 +1,433 @@ +;; Current Syntax: (type $i8_array) +;; +;; (func $test2 (export "test2") +;; (i32.store (type $typeIdx) +;; (global.get $arr_4) +;; (i32.const 1) +;; (i32.const 1337) +;; ) +;; ) + +;; Alternative Syntax 1: type=array +;; +;; (func $test2 (export "test2") +;; (i32.store type=array +;; (global.get $arr_4) +;; (i32.const 1) +;; (i32.const 1337) +;; ) +;; ) + +;; Alternative Syntax 2: array +;; +;; (func $test2 (export "test2") +;; (i32.store array +;; (global.get $arr_4) +;; (i32.const 1) +;; (i32.const 1337) +;; ) +;; ) + +;; Alternative Syntax 3: new opcodes +;; +;; (func $test2 (export "test2") +;; (i32.array.store +;; (global.get $arr_4) +;; (i32.const 1) +;; (i32.const 1337) +;; ) +;; ) + +(module + (type $i8_array (array (mut i8))) + + (global $arr_4 (ref $i8_array) + (array.new_default $i8_array (i32.const 4)) + ) + + (global $arr_8 (ref $i8_array) + (array.new_default $i8_array (i32.const 8)) + ) + + ;; Read i32 from an i8 array using regular array get instructions. + ;; TODO remove this and use the load instructions when implemented. + (func $load_i32_from_array + (export "load_i32_from_array") + (param $arr (ref $i8_array)) + (param $idx i32) + (result i32) + + ;; 1. Load the first byte (least significant) + (array.get_u $i8_array (local.get $arr) (local.get $idx)) + + ;; 2. Load the second byte and shift it left by 8 bits + (array.get_u $i8_array (local.get $arr) (i32.add (local.get $idx) (i32.const 1))) + (i32.const 8) + (i32.shl) + (i32.or) + + ;; 3. Load the third byte and shift it left by 16 bits + (array.get_u $i8_array (local.get $arr) (i32.add (local.get $idx) (i32.const 2))) + (i32.const 16) + (i32.shl) + (i32.or) + + ;; 4. Load the fourth byte (most significant) and shift it left by 24 bits + (array.get_u $i8_array (local.get $arr) (i32.add (local.get $idx) (i32.const 3))) + (i32.const 24) + (i32.shl) + (i32.or) + ) + + ;; TODO remove this and use the load instructions when implemented. + (func $load_i64_from_array + (export "load_i64_from_array") + (param $arr (ref $i8_array)) + (param $idx i32) + (result i64) + (call $load_i32_from_array (local.get $arr) (local.get $idx)) + (i64.extend_i32_u) + + (call $load_i32_from_array (local.get $arr) (i32.add (local.get $idx) (i32.const 4))) + (i64.extend_i32_u) + (i64.const 32) + (i64.shl) + (i64.or) + ) + + (func $i32_set_i8 (export "i32_set_i8") (param $index i32) (param $value i32) + (i32.store8 (type $i8_array) + (global.get $arr_4) + (local.get $index) + (local.get $value) + ) + ) + + (func $i32_set_i16 (export "i32_set_i16") (param $index i32) (param $value i32) + (i32.store16 (type $i8_array) + (global.get $arr_4) + (local.get $index) + (local.get $value) + ) + ) + + (func $i32_set_i32 (export "i32_set_i32") (param $index i32) (param $value i32) + (i32.store (type $i8_array) + (global.get $arr_4) + (local.get $index) + (local.get $value) + ) + ) + + (func $f32_set (export "f32_set") (param $index i32) (param $value f32) + (f32.store (type $i8_array) + (global.get $arr_4) + (local.get $index) + (local.get $value) + ) + ) + + (func $i64_set_i8 (export "i64_set_i8") (param $index i32) (param $value i64) + (i64.store8 (type $i8_array) + (global.get $arr_8) + (local.get $index) + (local.get $value) + ) + ) + + (func $i64_set_i16 (export "i64_set_i16") (param $index i32) (param $value i64) + (i64.store16 (type $i8_array) + (global.get $arr_8) + (local.get $index) + (local.get $value) + ) + ) + + (func $i64_set_i32 (export "i64_set_i32") (param $index i32) (param $value i64) + (i64.store32 (type $i8_array) + (global.get $arr_8) + (local.get $index) + (local.get $value) + ) + ) + + (func $i64_set_i64 (export "i64_set_i64") (param $index i32) (param $value i64) + (i64.store (type $i8_array) + (global.get $arr_8) + (local.get $index) + (local.get $value) + ) + ) + + (func $f64_set (export "f64_set") (param $index i32) (param $value f64) + (f64.store (type $i8_array) + (global.get $arr_8) + (local.get $index) + (local.get $value) + ) + ) + + ;; ??? Do we even want to spec out this instruction since array.store is the + ;; same thing? + (func $i32_set_and_get_i8 (export "i32_set_and_get_i8") (param $value i32) (result i32) + (i32.store8 (type $i8_array) + (global.get $arr_4) + (i32.const 0) + (local.get $value) + ) + ;; TODO: when multibyte get is supported use that instead here. + (call $load_i32_from_array (global.get $arr_4) (i32.const 0)) + ) + + (func $i32_set_and_get_i16 (export "i32_set_and_get_i16") (param $value i32) (result i32) + (i32.store16 (type $i8_array) + (global.get $arr_4) + (i32.const 0) + (local.get $value) + ) + ;; TODO: when multibyte get is supported use that instead here. + (call $load_i32_from_array (global.get $arr_4) (i32.const 0)) + ) + + (func $i32_set_and_get_i32 (export "i32_set_and_get_i32") (param $value i32) (result i32) + (i32.store (type $i8_array) + (global.get $arr_4) + (i32.const 0) + (local.get $value) + ) + ;; TODO: when multibyte get is supported use that instead here. + (call $load_i32_from_array (global.get $arr_4) (i32.const 0)) + ) + + (func $set_and_get_f32 (export "set_and_get_f32") (param $value f32) (result f32) + (f32.store (type $i8_array) + (global.get $arr_4) + (i32.const 0) + (local.get $value) + ) + ;; TODO: when multibyte get is supported use that instead here. + (call $load_i32_from_array (global.get $arr_4) (i32.const 0)) + (f32.reinterpret_i32) + ) + + (func $i64_set_and_get_i8 (export "i64_set_and_get_i8") (param $value i64) (result i64) + (i64.store8 (type $i8_array) + (global.get $arr_8) + (i32.const 0) + (local.get $value) + ) + ;; TODO: when multibyte get is supported use that instead here. + (call $load_i64_from_array (global.get $arr_8) (i32.const 0)) + ) + + (func $i64_set_and_get_i16 (export "i64_set_and_get_i16") (param $value i64) (result i64) + (i64.store16 (type $i8_array) + (global.get $arr_8) + (i32.const 0) + (local.get $value) + ) + ;; TODO: when multibyte get is supported use that instead here. + (call $load_i64_from_array (global.get $arr_8) (i32.const 0)) + ) + + (func $i64_set_and_get_i32 (export "i64_set_and_get_i32") (param $value i64) (result i64) + (i64.store32 (type $i8_array) + (global.get $arr_8) + (i32.const 0) + (local.get $value) + ) + ;; TODO: when multibyte get is supported use that instead here. + (call $load_i64_from_array (global.get $arr_8) (i32.const 0)) + ) + + (func $i64_set_and_get_i64 (export "i64_set_and_get_i64") (param $value i64) (result i64) + (i64.store (type $i8_array) + (global.get $arr_8) + (i32.const 0) + (local.get $value) + ) + ;; TODO: when multibyte get is supported use that instead here. + (call $load_i64_from_array (global.get $arr_8) (i32.const 0)) + ) + + (func $set_and_get_f64 (export "set_and_get_f64") (param $value f64) (result f64) + (f64.store (type $i8_array) + (global.get $arr_8) + (i32.const 0) + (local.get $value) + ) + ;; TODO: when multibyte get is supported use that instead here. + (call $load_i64_from_array (global.get $arr_8) (i32.const 0)) + (f64.reinterpret_i64) + ) +) + +;; +;; 32 bit round trip tests +;; + +(assert_return (invoke "i32_set_and_get_i8" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "i32_set_and_get_i8" (i32.const 255)) (i32.const 255)) +;; ensure high bits are ignored +(assert_return (invoke "i32_set_and_get_i8" (i32.const 0xFFFFFF00)) (i32.const 0)) + +(assert_return (invoke "i32_set_and_get_i16" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "i32_set_and_get_i16" (i32.const 65535)) (i32.const 65535)) +;; ensure high bits are ignored +(assert_return (invoke "i32_set_and_get_i16" (i32.const 0xFFFF0000)) (i32.const 0)) + +(assert_return (invoke "i32_set_and_get_i32" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "i32_set_and_get_i32" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "i32_set_and_get_i32" (i32.const 256)) (i32.const 256)) +(assert_return (invoke "i32_set_and_get_i32" (i32.const -1)) (i32.const -1)) +(assert_return (invoke "i32_set_and_get_i32" (i32.const 2147483647)) (i32.const 2147483647)) +(assert_return (invoke "i32_set_and_get_i32" (i32.const -2147483648)) (i32.const -2147483648)) + +(assert_return (invoke "set_and_get_f32" (f32.const 0)) (f32.const 0)) +(assert_return (invoke "set_and_get_f32" (f32.const -1)) (f32.const -1)) +(assert_return (invoke "set_and_get_f32" (f32.const 3.3)) (f32.const 3.3)) +(assert_return (invoke "set_and_get_f32" (f32.const -2.000000238418579)) (f32.const -2.000000238418579)) +(assert_return (invoke "set_and_get_f32" (f32.const nan)) (f32.const nan)) + +;; +;; 64 bit round trip tests +;; + +(assert_return (invoke "i64_set_and_get_i8" (i64.const 0)) (i64.const 0)) +(assert_return (invoke "i64_set_and_get_i8" (i64.const 255)) (i64.const 255)) +;; ensure high bits are ignored +(assert_return (invoke "i64_set_and_get_i8" (i64.const 0xFFFFFFFFFFFFFF00)) (i64.const 0)) + +(assert_return (invoke "i64_set_and_get_i16" (i64.const 0)) (i64.const 0)) +(assert_return (invoke "i64_set_and_get_i16" (i64.const 65535)) (i64.const 65535)) +;; ensure high bits are ignored +(assert_return (invoke "i64_set_and_get_i16" (i64.const 0xFFFFFFFFFFFF0000)) (i64.const 0)) + +(assert_return (invoke "i64_set_and_get_i32" (i64.const 0)) (i64.const 0)) +(assert_return (invoke "i64_set_and_get_i32" (i64.const 2147483647)) (i64.const 2147483647)) +;; unsigned extend +(assert_return (invoke "i64_set_and_get_i32" (i64.const -2147483648)) (i64.const 2147483648)) +;; ensure high bits are ignored +(assert_return (invoke "i64_set_and_get_i32" (i64.const 0xFFFFFFFF00000000)) (i64.const 0)) + +(assert_return (invoke "i64_set_and_get_i64" (i64.const 0)) (i64.const 0)) +(assert_return (invoke "i64_set_and_get_i64" (i64.const 9223372036854775807)) (i64.const 9223372036854775807)) +(assert_return (invoke "i64_set_and_get_i64" (i64.const -9223372036854775808)) (i64.const -9223372036854775808)) + +(assert_return (invoke "set_and_get_f64" (f64.const 0)) (f64.const 0)) +(assert_return (invoke "set_and_get_f64" (f64.const -1)) (f64.const -1)) +(assert_return (invoke "set_and_get_f64" (f64.const 3.3)) (f64.const 3.3)) +(assert_return (invoke "set_and_get_f64" (f64.const -2.00000000000000044409)) (f64.const -2.00000000000000044409)) +(assert_return (invoke "set_and_get_f64" (f64.const nan)) (f64.const nan)) + +;; +;; Bounds checks (32 bit with a 4-byte array) +;; + +;; i32_set_i8: Writes 1 byte +;; Valid range: [0, 3] +(assert_trap (invoke "i32_set_i8" (i32.const -1) (i32.const 0)) "out of bounds") +(assert_return (invoke "i32_set_i8" (i32.const 0) (i32.const 0))) +(assert_return (invoke "i32_set_i8" (i32.const 1) (i32.const 0))) +(assert_return (invoke "i32_set_i8" (i32.const 2) (i32.const 0))) +(assert_return (invoke "i32_set_i8" (i32.const 3) (i32.const 0))) +(assert_trap (invoke "i32_set_i8" (i32.const 4) (i32.const 0)) "out of bounds") + +;; i32_set_i16: Writes 2 bytes +;; Valid range: offset + 2 <= 4 -> Max offset 2 +(assert_trap (invoke "i32_set_i16" (i32.const -1) (i32.const 0)) "out of bounds") +(assert_return (invoke "i32_set_i16" (i32.const 0) (i32.const 0))) +(assert_return (invoke "i32_set_i16" (i32.const 1) (i32.const 0))) +(assert_return (invoke "i32_set_i16" (i32.const 2) (i32.const 0))) +(assert_trap (invoke "i32_set_i16" (i32.const 3) (i32.const 0)) "out of bounds") + +;; i32_set_i32: Writes 4 bytes +;; Valid range: offset + 4 <= 4 -> Max offset 0 +(assert_trap (invoke "i32_set_i32" (i32.const -1) (i32.const 0)) "out of bounds") +(assert_return (invoke "i32_set_i32" (i32.const 0) (i32.const 0))) +(assert_trap (invoke "i32_set_i32" (i32.const 1) (i32.const 0)) "out of bounds") + +;; f32_set: Writes 4 bytes +;; Valid range: offset + 4 <= 4 -> Max offset 0 +(assert_trap (invoke "f32_set" (i32.const -1) (f32.const 0)) "out of bounds") +(assert_return (invoke "f32_set" (i32.const 0) (f32.const 0))) +(assert_trap (invoke "f32_set" (i32.const 1) (f32.const 0)) "out of bounds") + +;; +;; Bounds checks (64 bit with an 8-byte array) +;; + +;; i64_set_i8: Writes 1 byte +;; Valid range: [0, 7] +(assert_trap (invoke "i64_set_i8" (i32.const -1) (i64.const 0)) "out of bounds") +(assert_return (invoke "i64_set_i8" (i32.const 0) (i64.const 0))) +(assert_return (invoke "i64_set_i8" (i32.const 1) (i64.const 0))) +(assert_return (invoke "i64_set_i8" (i32.const 6) (i64.const 0))) +(assert_return (invoke "i64_set_i8" (i32.const 7) (i64.const 0))) +(assert_trap (invoke "i64_set_i8" (i32.const 8) (i64.const 0)) "out of bounds") + +;; i64_set_i16: Writes 2 bytes +;; Valid range: offset + 2 <= 8 -> Max offset 6 +(assert_trap (invoke "i64_set_i16" (i32.const -1) (i64.const 0)) "out of bounds") +(assert_return (invoke "i64_set_i16" (i32.const 0) (i64.const 0))) +(assert_return (invoke "i64_set_i16" (i32.const 1) (i64.const 0))) +(assert_return (invoke "i64_set_i16" (i32.const 5) (i64.const 0))) +(assert_return (invoke "i64_set_i16" (i32.const 6) (i64.const 0))) +(assert_trap (invoke "i64_set_i16" (i32.const 7) (i64.const 0)) "out of bounds") + +;; i64_set_i32: Writes 4 bytes +;; Valid range: offset + 4 <= 8 -> Max offset 4 +(assert_trap (invoke "i64_set_i32" (i32.const -1) (i64.const 0)) "out of bounds") +(assert_return (invoke "i64_set_i32" (i32.const 0) (i64.const 0))) +(assert_return (invoke "i64_set_i32" (i32.const 1) (i64.const 0))) +(assert_return (invoke "i64_set_i32" (i32.const 3) (i64.const 0))) +(assert_return (invoke "i64_set_i32" (i32.const 4) (i64.const 0))) +(assert_trap (invoke "i64_set_i32" (i32.const 5) (i64.const 0)) "out of bounds") + +;; i64_set_i64: Writes 8 bytes +;; Valid range: offset + 8 <= 8 -> Max offset 0 +(assert_trap (invoke "i64_set_i64" (i32.const -1) (i64.const 0)) "out of bounds") +(assert_return (invoke "i64_set_i64" (i32.const 0) (i64.const 0))) +(assert_trap (invoke "i64_set_i64" (i32.const 1) (i64.const 0)) "out of bounds") + +;; f64_set: Writes 8 bytes +;; Valid range: offset + 8 <= 8 -> Max offset 0 +(assert_trap (invoke "f64_set" (i32.const -1) (f64.const 0)) "out of bounds") +(assert_return (invoke "f64_set" (i32.const 0) (f64.const 0))) +(assert_trap (invoke "f64_set" (i32.const 1) (f64.const 0)) "out of bounds") + + +(assert_invalid + (module + (type $a (array i8)) + (func (export "i32_set_immutable") (param $a (ref $a)) + (i32.store (type $i8_array) (local.get $a) (i32.const 0) (i32.const 1)) + ) + ) + "array is immutable" +) + +(assert_invalid + (module + (type $a (array (mut i16))) + (func (export "i32_set_mut_i16") (param $a (ref $a)) + (i32.store (type $i8_array) (local.get $a) (i32.const 0) (i32.const 1)) + ) + ) + "array element type must be i8" +) + +;; Null dereference + +(module + (type $t (array (mut i8))) + ;; (func (export "array.get-null") + ;; (local (ref null $t)) (drop (array.get $t (local.get 0) (i32.const 0))) + ;; ) + (func (export "i32.store_array_null") + (local (ref null $t)) (i32.store (type $t) (local.get 0) (i32.const 0) (i32.const 0)) + ) +) + +;; (assert_trap (invoke "array.get-null") "null array") +(assert_trap (invoke "i32.store_array_null") "null array")