From 60f68c8ac5a6b17acecbca75ec7b62c169beb0ba Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 18 Nov 2025 01:10:12 +0000 Subject: [PATCH] Implement multibyte array store instructions Add support for multibyte array store instructions as proposed in the WebAssembly multibyte proposal. These instructions allow storing values of various byte widths directly into i8 arrays, avoiding the need for manual bit manipulation and masking. Link: https://github.com/WebAssembly/multibyte --- scripts/clusterfuzz/run.py | 1 + src/binaryen-c.cpp | 3 + src/binaryen-c.h | 1 + src/interpreter/interpreter.cpp | 1 + src/ir/ReFinalize.cpp | 1 + src/ir/child-typer.h | 14 + src/ir/cost.h | 4 + src/ir/effects.h | 9 + src/ir/possible-contents.cpp | 8 + src/ir/subtype-exprs.h | 7 + src/parser/contexts.h | 13 + src/parser/parsers.h | 12 + src/passes/Print.cpp | 47 +- src/passes/TypeGeneralizing.cpp | 2 + src/tools/fuzzing.h | 1 + src/tools/fuzzing/fuzzing.cpp | 42 ++ src/tools/tool-options.h | 1 + src/wasm-binary.h | 8 +- src/wasm-builder.h | 14 + src/wasm-delegations-fields.def | 8 + src/wasm-delegations.def | 1 + src/wasm-features.h | 7 +- src/wasm-interpreter.h | 60 +++ src/wasm-ir-builder.h | 1 + src/wasm-stack.h | 4 +- src/wasm.h | 20 + src/wasm/wasm-binary.cpp | 166 ++++--- src/wasm/wasm-ir-builder.cpp | 17 + src/wasm/wasm-stack.cpp | 144 +++--- src/wasm/wasm-validator.cpp | 27 ++ src/wasm/wasm.cpp | 10 + src/wasm2js.h | 4 + test/binaryen.js/kitchen-sink.js.txt | 24 +- test/example/c-api-kitchen-sink.c | 1 + test/example/c-api-kitchen-sink.txt | 3 +- test/lit/array-multibyte.wast | 126 +++++ test/lit/help/wasm-as.test | 4 + test/lit/help/wasm-ctor-eval.test | 4 + test/lit/help/wasm-dis.test | 4 + test/lit/help/wasm-emscripten-finalize.test | 4 + test/lit/help/wasm-merge.test | 4 + test/lit/help/wasm-metadce.test | 6 + test/lit/help/wasm-opt.test | 6 + test/lit/help/wasm-reduce.test | 4 + test/lit/help/wasm-split.test | 4 + test/lit/help/wasm2js.test | 6 + ..._roundtrip_print-features_all-features.txt | 1 + ...e-to-fuzz_all-features_metrics_noprint.txt | 84 ++-- test/spec/array-multibyte.wast | 433 ++++++++++++++++++ 49 files changed, 1165 insertions(+), 211 deletions(-) create mode 100644 test/lit/array-multibyte.wast create mode 100644 test/spec/array-multibyte.wast 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..d70ff268678 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -571,6 +571,11 @@ 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 +2307,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..d980f49f74e 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,46 @@ 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..cc5a4ae422b 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,52 @@ 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..56b7f13c865 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,39 +5612,49 @@ 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) { diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 23ff8764971..ac158c2f26e 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,16 @@ 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..ae46df13165 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,32 @@ 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/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 3df38bcb411..c97c48ba5f9 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -34,7 +34,7 @@ Features.ExtendedConst: 8192 Features.Strings: 16384 Features.MultiMemory: 32768 Features.RelaxedAtomics: 4194304 -Features.All: 8388607 +Features.All: 16777215 InvalidId: 0 BlockId: 1 IfId: 2 @@ -101,17 +101,17 @@ ArrayNewId: 73 ArrayNewFixedId: 76 ArrayGetId: 77 ArraySetId: 78 -ArrayLenId: 79 -ArrayCopy: 80 -RefAs: 86 -StringNew: 87 -StringConst: 88 -StringMeasure: 89 -StringEncode: 90 -StringConcat: 91 -StringEq: 92 -StringWTF16Get: 94 -StringSliceWTF: 95 +ArrayLenId: 80 +ArrayCopy: 81 +RefAs: 87 +StringNew: 88 +StringConst: 89 +StringMeasure: 90 +StringEncode: 91 +StringConcat: 92 +StringEq: 93 +StringWTF16Get: 95 +StringSliceWTF: 96 getExpressionInfo={"id":15,"type":4,"op":6} (f32.neg (f32.const -33.61199951171875) 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")