From 0847adae13d0462579d7280606bbb8ea3d830ad3 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 18 Nov 2025 01:10:12 +0000 Subject: [PATCH 01/20] 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/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 | 169 ++++--- src/wasm/wasm-ir-builder.cpp | 17 + src/wasm/wasm-stack.cpp | 145 +++--- 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 + test/spec/array-multibyte.wast | 433 ++++++++++++++++++ 46 files changed, 1086 insertions(+), 167 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 f70373bc241..c5693a7c06f 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -262,6 +262,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 5e1570d0c97..fa8871d1536 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -171,6 +171,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 6f4ac000b03..4b0f3e6c80a 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1096,6 +1096,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 03a972f13f9..6ac48d82c76 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -753,6 +753,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 15786aab89e..6e964a65653 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1024,6 +1024,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 09ae0169674..67116f92b36 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1102,6 +1102,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 @@ -1739,6 +1746,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 e2cf5b14f20..b2dcac6949c 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -408,6 +408,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 0e2f959c0e4..c361458778a 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, @@ -2314,6 +2319,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 2da13ee90c1..cca5c0dd19f 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 c34e5d0dac9..13f377c0fe1 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -437,6 +437,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()) { @@ -589,21 +608,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); @@ -2477,6 +2482,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 a2fee096cc1..984d2ae41f2 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -802,6 +802,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/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 eb5ee36c0f4..00ccfcfae28 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, @@ -1696,6 +1698,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(); @@ -1743,11 +1747,11 @@ class WasmBinaryReader { void readJSCalledHints(size_t payloadLen); void readIdempotentHints(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 e59336283bc..d840454350a 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 14c27825d4b..36fd4ccae55 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 79592be9b15..75167991132 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 48404207a60..443612cdd4e 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; @@ -2347,6 +2361,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 4ee669aa10a..a8c731935db 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -251,6 +251,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 ba359577eeb..102d194f943 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, @@ -1860,6 +1866,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 3a242535bdc..d226a5d81cf 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: @@ -3251,6 +3253,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); @@ -3590,96 +3601,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(); @@ -3984,11 +3986,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); } } @@ -4533,98 +4535,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); } @@ -5584,10 +5586,12 @@ void WasmBinaryReader::readIdempotentHints(size_t payloadLen) { READ_BOOLEAN_HINT(Annotations::IdempotentHint, idempotent); } -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) { @@ -5605,6 +5609,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"); } @@ -5612,39 +5622,52 @@ 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 if (backing == BackingType::Array) { + if (hasMemIdx || hasMemoryOrder) { + throwError( + "Memory index and memory order are not allowed for array backing."); + } + } else { + WASM_UNREACHABLE("Invalid backing type"); } - 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) { + return {Name(), 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 abb47eec1bd..0a12649888e 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -567,6 +567,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) { @@ -2367,6 +2374,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 d612fb1b640..3e4230e2998 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -56,6 +56,70 @@ void BinaryInstWriter::emitIfElse(If* curr) { o << static_cast(BinaryConsts::Else); } +void BinaryInstWriter::emitStore(uint8_t bytes, Type valueType) { + switch (valueType.getBasic()) { + case Type::i32: { + switch (bytes) { + case 1: + o << static_cast(BinaryConsts::I32StoreMem8); + break; + case 2: + o << static_cast(BinaryConsts::I32StoreMem16); + break; + case 4: + o << static_cast(BinaryConsts::I32StoreMem); + break; + default: + abort(); + } + break; + } + case Type::i64: { + switch (bytes) { + case 1: + o << static_cast(BinaryConsts::I64StoreMem8); + break; + case 2: + o << static_cast(BinaryConsts::I64StoreMem16); + break; + case 4: + o << static_cast(BinaryConsts::I64StoreMem32); + break; + case 8: + o << static_cast(BinaryConsts::I64StoreMem); + break; + default: + abort(); + } + break; + } + case Type::f32: { + switch (bytes) { + case 2: + o << static_cast(BinaryConsts::MiscPrefix) + << U32LEB(BinaryConsts::F32_F16StoreMem); + break; + case 4: + o << static_cast(BinaryConsts::F32StoreMem); + break; + default: + WASM_UNREACHABLE("invalid store size"); + } + break; + } + case Type::f64: + o << static_cast(BinaryConsts::F64StoreMem); + break; + case Type::v128: + o << static_cast(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 << static_cast(BinaryConsts::Loop); @@ -380,67 +444,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 << static_cast(BinaryConsts::I32StoreMem8); - break; - case 2: - o << static_cast(BinaryConsts::I32StoreMem16); - break; - case 4: - o << static_cast(BinaryConsts::I32StoreMem); - break; - default: - abort(); - } - break; - } - case Type::i64: { - switch (curr->bytes) { - case 1: - o << static_cast(BinaryConsts::I64StoreMem8); - break; - case 2: - o << static_cast(BinaryConsts::I64StoreMem16); - break; - case 4: - o << static_cast(BinaryConsts::I64StoreMem32); - break; - case 8: - o << static_cast(BinaryConsts::I64StoreMem); - break; - default: - abort(); - } - break; - } - case Type::f32: { - switch (curr->bytes) { - case 2: - o << static_cast(BinaryConsts::MiscPrefix) - << U32LEB(BinaryConsts::F32_F16StoreMem); - break; - case 4: - o << static_cast(BinaryConsts::F32StoreMem); - break; - default: - WASM_UNREACHABLE("invalid store size"); - } - break; - } - case Type::f64: - o << static_cast(BinaryConsts::F64StoreMem); - break; - case Type::v128: - o << static_cast(BinaryConsts::SIMDPrefix) - << U32LEB(BinaryConsts::V128Store); - break; - case Type::none: - case Type::unreachable: - WASM_UNREACHABLE("unexpected type"); - } + emitStore(curr->bytes, curr->valueType); } else { o << static_cast(BinaryConsts::AtomicPrefix); switch (curr->valueType.getBasic()) { @@ -2783,6 +2787,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 << static_cast(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArrayLen); @@ -3440,8 +3455,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 ca550d7225a..83c618cad31 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -546,6 +546,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); @@ -3730,6 +3731,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 4b27dcdb547..c74670615e2 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 @@ -1331,6 +1332,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 bc43e623885..225474790e2 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2372,6 +2372,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 7c2dd577e86..5f59a298254 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -798,6 +798,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 8a5ac67c2d5..b468adea785 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -830,6 +830,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 0969ca927be..e5c68f28f09 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -762,6 +762,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/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") From 528beea6aad0e461e2e780a6884e01afb25851be Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 3 Mar 2026 21:38:28 +0000 Subject: [PATCH 02/20] review comments round 1 --- src/parser/parsers.h | 6 ++---- src/wasm-stack.h | 2 +- src/wasm/wasm-ir-builder.cpp | 7 +++++-- src/wasm/wasm-stack.cpp | 6 +++--- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/parser/parsers.h b/src/parser/parsers.h index cca5c0dd19f..d6b5d5878ff 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -1787,15 +1787,13 @@ Result<> makeStore(Ctx& ctx, int bytes, bool isAtomic) { if (ctx.in.takeSExprStart("type"sv)) { - std::optional arrayType; - auto x = typeidx(ctx); - CHECK_ERR(x); + auto arrayType = typeidx(ctx); + CHECK_ERR(arrayType); 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); diff --git a/src/wasm-stack.h b/src/wasm-stack.h index 1351396eb29..7600fc47ef9 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -133,7 +133,7 @@ class BinaryInstWriter : public OverriddenVisitor { MemoryOrder order, bool isRMW, BackingType backing = BackingType::Memory); - void emitStore(uint8_t bytes, Type ValueType); + void emitStoreOpcode(uint8_t bytes, Type valueType); int32_t getBreakIndex(Name name); WasmBinaryWriter& parent; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 0a12649888e..7af3e71ba68 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -2376,10 +2376,13 @@ Result<> IRBuilder::makeArraySet(HeapType type, MemoryOrder order) { Result<> IRBuilder::makeArrayStore(HeapType arrayType, unsigned bytes, Type type) { + if (!arrayType.isArray()) { + return Err{"expected array type annotation on array store"}; + } ArrayStore curr; curr.valueType = type; - CHECK_ERR( - ChildPopper{*this}.visitArrayStore(&curr, HeapTypes::getMutI8Array())); + CHECK_ERR(ChildPopper{*this}.visitArrayStore(&curr, arrayType)); + CHECK_ERR(validateTypeAnnotation(arrayType, curr.ref)); push(builder.makeArrayStore(bytes, type, curr.ref, curr.index, curr.value)); return Ok{}; } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 3e4230e2998..43787068d14 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -56,7 +56,7 @@ void BinaryInstWriter::emitIfElse(If* curr) { o << static_cast(BinaryConsts::Else); } -void BinaryInstWriter::emitStore(uint8_t bytes, Type valueType) { +void BinaryInstWriter::emitStoreOpcode(uint8_t bytes, Type valueType) { switch (valueType.getBasic()) { case Type::i32: { switch (bytes) { @@ -444,7 +444,7 @@ void BinaryInstWriter::visitLoad(Load* curr) { void BinaryInstWriter::visitStore(Store* curr) { if (!curr->isAtomic()) { - emitStore(curr->bytes, curr->valueType); + emitStoreOpcode(curr->bytes, curr->valueType); } else { o << static_cast(BinaryConsts::AtomicPrefix); switch (curr->valueType.getBasic()) { @@ -2792,7 +2792,7 @@ void BinaryInstWriter::visitArrayStore(ArrayStore* curr) { emitUnreachable(); return; } - emitStore(curr->bytes, curr->valueType); + emitStoreOpcode(curr->bytes, curr->valueType); uint32_t alignmentBits = BinaryConsts::HasBackingArrayMask; o << U32LEB(alignmentBits); parent.writeIndexedHeapType(curr->ref->type.getHeapType()); From 6727c79f6b5305b7490a609d1e226ba15093e92f Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 3 Mar 2026 21:49:47 +0000 Subject: [PATCH 03/20] Rename roundtrip. --- test/lit/array-multibyte.wast | 96 +++++++++++++++++------------------ 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/test/lit/array-multibyte.wast b/test/lit/array-multibyte.wast index 38b04898801..dc80cf3ddac 100644 --- a/test/lit/array-multibyte.wast +++ b/test/lit/array-multibyte.wast @@ -3,7 +3,7 @@ ;; 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 +;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s --check-prefix=RTRIP ;; Check that we can roundtrip through the text format as well. @@ -11,7 +11,7 @@ (module ;; CHECK: (type $i8_array (array (mut i8))) - ;; ROUNDTRIP: (type $i8_array (array (mut i8))) + ;; RTRIP: (type $i8_array (array (mut i8))) (type $i8_array (array (mut i8))) ;; CHECK: (type $1 (func)) @@ -19,11 +19,11 @@ ;; CHECK: (global $arr (ref $i8_array) (array.new_default $i8_array ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: )) - ;; ROUNDTRIP: (type $1 (func)) + ;; RTRIP: (type $1 (func)) - ;; ROUNDTRIP: (global $arr (ref $i8_array) (array.new_default $i8_array - ;; ROUNDTRIP-NEXT: (i32.const 4) - ;; ROUNDTRIP-NEXT: )) + ;; RTRIP: (global $arr (ref $i8_array) (array.new_default $i8_array + ;; RTRIP-NEXT: (i32.const 4) + ;; RTRIP-NEXT: )) (global $arr (ref $i8_array) (array.new_default $i8_array (i32.const 4)) ) @@ -70,48 +70,48 @@ ;; 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: ) + ;; RTRIP: (func $stores (type $1) + ;; RTRIP-NEXT: (i32.store8 (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (i32.store16 (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (i32.store (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (f32.store (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (f32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (i64.store8 (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (i64.store16 (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (i64.store32 (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (f64.store (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (f64.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-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)) From c6e675c729fc60eef60e6f47696579e78fdc958b Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 3 Mar 2026 21:49:58 +0000 Subject: [PATCH 04/20] Add more testing. --- test/lit/array-multibyte.wast | 397 ++++++++++++++++++++++++++++++---- 1 file changed, 351 insertions(+), 46 deletions(-) diff --git a/test/lit/array-multibyte.wast b/test/lit/array-multibyte.wast index dc80cf3ddac..72a03c30e8a 100644 --- a/test/lit/array-multibyte.wast +++ b/test/lit/array-multibyte.wast @@ -10,17 +10,17 @@ ;; RUN: wasm-opt %s -all -S -o - | wasm-opt -all -S -o - | filecheck %s (module + ;; CHECK: (type $0 (func)) + ;; CHECK: (type $i8_array (array (mut i8))) + ;; RTRIP: (type $0 (func)) + ;; RTRIP: (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: )) - ;; RTRIP: (type $1 (func)) - ;; RTRIP: (global $arr (ref $i8_array) (array.new_default $i8_array ;; RTRIP-NEXT: (i32.const 4) ;; RTRIP-NEXT: )) @@ -28,99 +28,404 @@ (array.new_default $i8_array (i32.const 4)) ) - ;; CHECK: (func $stores (type $1) + ;; CHECK: (func $stores (type $0) ;; CHECK-NEXT: (i32.store8 (type $i8_array) ;; CHECK-NEXT: (global.get $arr) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) ;; 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: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) ;; 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: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) ;; 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: (i32.const 1) + ;; CHECK-NEXT: (f32.const 2) ;; 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: (i32.const 1) + ;; CHECK-NEXT: (i64.const 2) ;; 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: (i32.const 1) + ;; CHECK-NEXT: (i64.const 2) ;; 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: (i32.const 1) + ;; CHECK-NEXT: (i64.const 2) ;; 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: (i32.const 1) + ;; CHECK-NEXT: (f64.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; RTRIP: (func $stores (type $1) + ;; RTRIP: (func $stores (type $0) ;; RTRIP-NEXT: (i32.store8 (type $i8_array) ;; RTRIP-NEXT: (global.get $arr) - ;; RTRIP-NEXT: (i32.const 0) - ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: (i32.const 2) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (i32.store16 (type $i8_array) ;; RTRIP-NEXT: (global.get $arr) - ;; RTRIP-NEXT: (i32.const 0) - ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: (i32.const 2) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (i32.store (type $i8_array) ;; RTRIP-NEXT: (global.get $arr) - ;; RTRIP-NEXT: (i32.const 0) - ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: (i32.const 2) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (f32.store (type $i8_array) ;; RTRIP-NEXT: (global.get $arr) - ;; RTRIP-NEXT: (i32.const 0) - ;; RTRIP-NEXT: (f32.const 0) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: (f32.const 2) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (i64.store8 (type $i8_array) ;; RTRIP-NEXT: (global.get $arr) - ;; RTRIP-NEXT: (i32.const 0) - ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: (i64.const 2) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (i64.store16 (type $i8_array) ;; RTRIP-NEXT: (global.get $arr) - ;; RTRIP-NEXT: (i32.const 0) - ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: (i64.const 2) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (i64.store32 (type $i8_array) ;; RTRIP-NEXT: (global.get $arr) - ;; RTRIP-NEXT: (i32.const 0) - ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: (i64.const 2) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (f64.store (type $i8_array) ;; RTRIP-NEXT: (global.get $arr) - ;; RTRIP-NEXT: (i32.const 0) - ;; RTRIP-NEXT: (f64.const 0) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: (f64.const 2) ;; RTRIP-NEXT: ) ;; RTRIP-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)) + (i32.store8 (type $i8_array) (global.get $arr) (i32.const 1) (i32.const 2)) + (i32.store16 (type $i8_array) (global.get $arr) (i32.const 1) (i32.const 2)) + (i32.store (type $i8_array) (global.get $arr) (i32.const 1) (i32.const 2)) + (f32.store (type $i8_array) (global.get $arr) (i32.const 1) (f32.const 2.0)) + + (i64.store8 (type $i8_array) (global.get $arr) (i32.const 1) (i64.const 2)) + (i64.store16 (type $i8_array) (global.get $arr) (i32.const 1) (i64.const 2)) + (i64.store32 (type $i8_array) (global.get $arr) (i32.const 1) (i64.const 2)) + (f64.store (type $i8_array) (global.get $arr) (i32.const 1) (f64.const 2.0)) + ) + + ;; CHECK: (func $stores_null (type $0) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $stores_null (type $0) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 2) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 2) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 2) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (f32.const 2) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i64.const 2) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i64.const 2) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i64.const 2) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (ref.null none) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (f64.const 2) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + (func $stores_null + (i32.store8 (type $i8_array) (ref.null $i8_array) (i32.const 1) (i32.const 2)) + (i32.store16 (type $i8_array) (ref.null $i8_array) (i32.const 1) (i32.const 2)) + (i32.store (type $i8_array) (ref.null $i8_array) (i32.const 1) (i32.const 2)) + (f32.store (type $i8_array) (ref.null $i8_array) (i32.const 1) (f32.const 2.0)) + + (i64.store8 (type $i8_array) (ref.null $i8_array) (i32.const 1) (i64.const 2)) + (i64.store16 (type $i8_array) (ref.null $i8_array) (i32.const 1) (i64.const 2)) + (i64.store32 (type $i8_array) (ref.null $i8_array) (i32.const 1) (i64.const 2)) + (f64.store (type $i8_array) (ref.null $i8_array) (i32.const 1) (f64.const 2.0)) + ) + + ;; CHECK: (func $stores_unreachable (type $0) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $stores_unreachable (type $0) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + (func $stores_unreachable + (i32.store8 (type $i8_array) (unreachable) (i32.const 1) (i32.const 2)) + (i32.store16 (type $i8_array) (unreachable) (i32.const 1) (i32.const 2)) + (i32.store (type $i8_array) (unreachable) (i32.const 1) (i32.const 2)) + (f32.store (type $i8_array) (unreachable) (i32.const 1) (f32.const 2.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)) + (i64.store8 (type $i8_array) (unreachable) (i32.const 1) (i64.const 2)) + (i64.store16 (type $i8_array) (unreachable) (i32.const 1) (i64.const 2)) + (i64.store32 (type $i8_array) (unreachable) (i32.const 1) (i64.const 2)) + (f64.store (type $i8_array) (unreachable) (i32.const 1) (f64.const 2.0)) ) ) From e65a5be45f4932bf57737b80281f43d31d898778 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 3 Mar 2026 22:26:48 +0000 Subject: [PATCH 05/20] Further testing. --- test/spec/array-multibyte.wast | 91 ++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 11 deletions(-) diff --git a/test/spec/array-multibyte.wast b/test/spec/array-multibyte.wast index a3d4676b4a6..a2f44479d94 100644 --- a/test/spec/array-multibyte.wast +++ b/test/spec/array-multibyte.wast @@ -95,6 +95,14 @@ (i64.or) ) + (func $get_array_4_byte (export "get_array_4_byte") (param $idx i32) (result i32) + (array.get_u $i8_array (global.get $arr_4) (local.get $idx)) + ) + + (func $get_array_8_byte (export "get_array_8_byte") (param $idx i32) (result i32) + (array.get_u $i8_array (global.get $arr_8) (local.get $idx)) + ) + (func $i32_set_i8 (export "i32_set_i8") (param $index i32) (param $value i32) (i32.store8 (type $i8_array) (global.get $arr_4) @@ -167,8 +175,9 @@ ) ) - ;; ??? Do we even want to spec out this instruction since array.store is the - ;; same thing? + ;; TODO: Do we even want to spec out this instruction since array.set is the + ;; same thing? See + ;; https://github.com/WebAssembly/multibyte-array-access/issues/2 (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) @@ -288,6 +297,8 @@ (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)) +(assert_return (invoke "set_and_get_f32" (f32.const nan:0x123456)) (f32.const nan:0x123456)) +(assert_return (invoke "set_and_get_f32" (f32.const -nan:0x654321)) (f32.const -nan:0x654321)) ;; ;; 64 bit round trip tests @@ -319,11 +330,60 @@ (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)) +(assert_return (invoke "set_and_get_f64" (f64.const nan:0x123456789abcd)) (f64.const nan:0x123456789abcd)) +(assert_return (invoke "set_and_get_f64" (f64.const -nan:0xedcba98765432)) (f64.const -nan:0xedcba98765432)) + +;; +;; Byte-wise store and unaligned store tests (32 bit) +;; + +(invoke "i32_set_i32" (i32.const 0) (i32.const 0x00000000)) ;; clear +(invoke "i32_set_i16" (i32.const 0) (i32.const 0x1234)) +(assert_return (invoke "get_array_4_byte" (i32.const 0)) (i32.const 0x34)) +(assert_return (invoke "get_array_4_byte" (i32.const 1)) (i32.const 0x12)) + +(invoke "i32_set_i32" (i32.const 0) (i32.const 0x12345678)) +(assert_return (invoke "get_array_4_byte" (i32.const 0)) (i32.const 0x78)) +(assert_return (invoke "get_array_4_byte" (i32.const 1)) (i32.const 0x56)) +(assert_return (invoke "get_array_4_byte" (i32.const 2)) (i32.const 0x34)) +(assert_return (invoke "get_array_4_byte" (i32.const 3)) (i32.const 0x12)) + +(invoke "i32_set_i16" (i32.const 1) (i32.const 0xABCD)) +(assert_return (invoke "get_array_4_byte" (i32.const 0)) (i32.const 0x78)) +(assert_return (invoke "get_array_4_byte" (i32.const 1)) (i32.const 0xCD)) +(assert_return (invoke "get_array_4_byte" (i32.const 2)) (i32.const 0xAB)) +(assert_return (invoke "get_array_4_byte" (i32.const 3)) (i32.const 0x12)) + +;; +;; Byte-wise store and unaligned store tests (64 bit) +;; + +(invoke "i64_set_i64" (i32.const 0) (i64.const 0x123456789ABCDEF0)) +(assert_return (invoke "get_array_8_byte" (i32.const 0)) (i32.const 0xF0)) +(assert_return (invoke "get_array_8_byte" (i32.const 1)) (i32.const 0xDE)) +(assert_return (invoke "get_array_8_byte" (i32.const 2)) (i32.const 0xBC)) +(assert_return (invoke "get_array_8_byte" (i32.const 3)) (i32.const 0x9A)) +(assert_return (invoke "get_array_8_byte" (i32.const 4)) (i32.const 0x78)) +(assert_return (invoke "get_array_8_byte" (i32.const 5)) (i32.const 0x56)) +(assert_return (invoke "get_array_8_byte" (i32.const 6)) (i32.const 0x34)) +(assert_return (invoke "get_array_8_byte" (i32.const 7)) (i32.const 0x12)) + +(invoke "i64_set_i32" (i32.const 3) (i64.const 0x11223344)) +(assert_return (invoke "get_array_8_byte" (i32.const 0)) (i32.const 0xF0)) +(assert_return (invoke "get_array_8_byte" (i32.const 1)) (i32.const 0xDE)) +(assert_return (invoke "get_array_8_byte" (i32.const 2)) (i32.const 0xBC)) +(assert_return (invoke "get_array_8_byte" (i32.const 3)) (i32.const 0x44)) +(assert_return (invoke "get_array_8_byte" (i32.const 4)) (i32.const 0x33)) +(assert_return (invoke "get_array_8_byte" (i32.const 5)) (i32.const 0x22)) +(assert_return (invoke "get_array_8_byte" (i32.const 6)) (i32.const 0x11)) +(assert_return (invoke "get_array_8_byte" (i32.const 7)) (i32.const 0x12)) ;; ;; Bounds checks (32 bit with a 4-byte array) ;; +(invoke "i32_set_i32" (i32.const 0) (i32.const 0)) + ;; 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") @@ -331,7 +391,7 @@ (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") +(assert_trap (invoke "i32_set_i8" (i32.const 4) (i32.const 0xFFFF)) "out of bounds") ;; i32_set_i16: Writes 2 bytes ;; Valid range: offset + 2 <= 4 -> Max offset 2 @@ -339,24 +399,29 @@ (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") +(assert_trap (invoke "i32_set_i16" (i32.const 3) (i32.const 0xFFFF)) "out of bounds") +(assert_return (invoke "get_array_4_byte" (i32.const 3)) (i32.const 0)) ;; 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") +(assert_trap (invoke "i32_set_i32" (i32.const 1) (i32.const 0xFFFFFFFF)) "out of bounds") +(assert_return (invoke "get_array_4_byte" (i32.const 1)) (i32.const 0)) ;; 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") +(assert_trap (invoke "f32_set" (i32.const 1) (f32.const 1.0)) "out of bounds") +(assert_return (invoke "get_array_4_byte" (i32.const 1)) (i32.const 0)) ;; ;; Bounds checks (64 bit with an 8-byte array) ;; +(invoke "i64_set_i64" (i32.const 0) (i64.const 0)) + ;; 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") @@ -364,7 +429,7 @@ (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") +(assert_trap (invoke "i64_set_i8" (i32.const 8) (i64.const 0xFFFF)) "out of bounds") ;; i64_set_i16: Writes 2 bytes ;; Valid range: offset + 2 <= 8 -> Max offset 6 @@ -373,7 +438,8 @@ (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") +(assert_trap (invoke "i64_set_i16" (i32.const 7) (i64.const 0xFFFF)) "out of bounds") +(assert_return (invoke "get_array_8_byte" (i32.const 7)) (i32.const 0)) ;; i64_set_i32: Writes 4 bytes ;; Valid range: offset + 4 <= 8 -> Max offset 4 @@ -382,19 +448,22 @@ (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") +(assert_trap (invoke "i64_set_i32" (i32.const 5) (i64.const 0xFFFFFFFF)) "out of bounds") +(assert_return (invoke "get_array_8_byte" (i32.const 5)) (i32.const 0)) ;; 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") +(assert_trap (invoke "i64_set_i64" (i32.const 1) (i64.const 0xFFFFFFFFFFFFFFFF)) "out of bounds") +(assert_return (invoke "get_array_8_byte" (i32.const 1)) (i32.const 0)) ;; 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_trap (invoke "f64_set" (i32.const 1) (f64.const 1.0)) "out of bounds") +(assert_return (invoke "get_array_8_byte" (i32.const 1)) (i32.const 0)) (assert_invalid From 4237ddbab1444ffddbc0af0de7830e7d57ac271a Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 3 Mar 2026 23:14:39 +0000 Subject: [PATCH 06/20] Remove valueType. --- src/ir/child-typer.h | 2 +- src/passes/Print.cpp | 4 ++-- src/wasm-builder.h | 2 -- src/wasm-delegations-fields.def | 1 - src/wasm-interpreter.h | 2 +- src/wasm.h | 1 - src/wasm/wasm-ir-builder.cpp | 17 +++++++++++------ src/wasm/wasm-stack.cpp | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 4b0f3e6c80a..518bd298811 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1107,7 +1107,7 @@ template struct ChildTyper : OverriddenVisitor { } note(&curr->ref, Type(*ht, Nullable)); note(&curr->index, Type::i32); - note(&curr->value, curr->valueType); + note(&curr->value, curr->value->type); } void visitArrayLen(ArrayLen* curr) { diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 13f377c0fe1..b87b8cb7cea 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2483,9 +2483,9 @@ struct PrintExpressionContents printHeapTypeName(curr->ref->type.getHeapType()); } void visitArrayStore(ArrayStore* curr) { - prepareColor(o) << forceConcrete(curr->valueType); + prepareColor(o) << forceConcrete(curr->value->type); o << ".store"; - printStorePostfix(curr->bytes, curr->valueType); + printStorePostfix(curr->bytes, curr->value->type); o << " "; restoreNormalColor(o); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index d840454350a..5b11460b100 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1130,13 +1130,11 @@ class Builder { 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; diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 36fd4ccae55..801164754ac 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -750,7 +750,6 @@ 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) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 443612cdd4e..acbd4141bce 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2376,7 +2376,7 @@ class ExpressionRunner : public OverriddenVisitor { if (i >= size || curr->bytes > (size - i)) { trap("array oob"); } - switch (curr->valueType.getBasic()) { + switch (curr->value->type.getBasic()) { case Type::i32: writeBytes( value.getSingleValue().geti32(), curr->bytes, i, data->values); diff --git a/src/wasm.h b/src/wasm.h index 102d194f943..ce154ef7963 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1875,7 +1875,6 @@ class ArrayStore : public SpecificExpression { Expression* ref; Expression* index; Expression* value; - Type valueType; void finalize(); }; diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 7af3e71ba68..aa012e9d4b7 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -567,10 +567,14 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } - Result<> visitArrayStore(ArrayStore* curr, - std::optional ht = std::nullopt) { + Result<> + visitArrayStoreWithType(ArrayStore* curr, HeapType arrayType, Type type) { std::vector children; - ConstraintCollector{builder, children}.visitArrayStore(curr, ht); + // Push in Wasm stack order (ref, index, value). + // popConstrainedChildren processes them in reverse order. + children.push_back({&curr->ref, {Type(arrayType, Nullable)}}); + children.push_back({&curr->index, {Type::i32}}); + children.push_back({&curr->value, {type}}); return popConstrainedChildren(children); } @@ -2379,11 +2383,12 @@ IRBuilder::makeArrayStore(HeapType arrayType, unsigned bytes, Type type) { if (!arrayType.isArray()) { return Err{"expected array type annotation on array store"}; } + ArrayStore curr; - curr.valueType = type; - CHECK_ERR(ChildPopper{*this}.visitArrayStore(&curr, arrayType)); + CHECK_ERR(ChildPopper{*this}.visitArrayStoreWithType(&curr, arrayType, type)); + CHECK_ERR(validateTypeAnnotation(arrayType, curr.ref)); - push(builder.makeArrayStore(bytes, type, curr.ref, curr.index, curr.value)); + push(builder.makeArrayStore(bytes, curr.ref, curr.index, curr.value)); return Ok{}; } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 43787068d14..70b61ef9665 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2792,7 +2792,7 @@ void BinaryInstWriter::visitArrayStore(ArrayStore* curr) { emitUnreachable(); return; } - emitStoreOpcode(curr->bytes, curr->valueType); + emitStoreOpcode(curr->bytes, curr->value->type); uint32_t alignmentBits = BinaryConsts::HasBackingArrayMask; o << U32LEB(alignmentBits); parent.writeIndexedHeapType(curr->ref->type.getHeapType()); From 6f4b1c7fbcfb7bbf09ed3a9d2dee41f6deb3ab46 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 3 Mar 2026 23:16:40 +0000 Subject: [PATCH 07/20] remove subtyping --- src/ir/subtype-exprs.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index b2dcac6949c..ba1c57afb8c 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -408,13 +408,7 @@ 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 visitArrayStore(ArrayStore* curr) {} void visitArrayLen(ArrayLen* curr) {} void visitArrayCopy(ArrayCopy* curr) { if (!curr->srcRef->type.isArray() || !curr->destRef->type.isArray()) { From 087191f13954764377c4c4d1bea8a34c9656aefc Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 4 Mar 2026 21:31:01 +0000 Subject: [PATCH 08/20] pass value type --- src/ir/child-typer.h | 5 +++-- src/wasm/wasm-ir-builder.cpp | 13 +++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 518bd298811..8c55a3c710c 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1097,7 +1097,8 @@ template struct ChildTyper : OverriddenVisitor { } void visitArrayStore(ArrayStore* curr, - std::optional ht = std::nullopt) { + std::optional ht = std::nullopt, + std::optional valueType = std::nullopt) { if (!ht) { if (!curr->ref->type.isRef()) { self().noteUnknown(); @@ -1107,7 +1108,7 @@ template struct ChildTyper : OverriddenVisitor { } note(&curr->ref, Type(*ht, Nullable)); note(&curr->index, Type::i32); - note(&curr->value, curr->value->type); + note(&curr->value, valueType ? *valueType : curr->value->type); } void visitArrayLen(ArrayLen* curr) { diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index aa012e9d4b7..d8801300da6 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -567,14 +567,11 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } - Result<> - visitArrayStoreWithType(ArrayStore* curr, HeapType arrayType, Type type) { + Result<> visitArrayStore(ArrayStore* curr, + std::optional ht = std::nullopt, + std::optional valueType = std::nullopt) { std::vector children; - // Push in Wasm stack order (ref, index, value). - // popConstrainedChildren processes them in reverse order. - children.push_back({&curr->ref, {Type(arrayType, Nullable)}}); - children.push_back({&curr->index, {Type::i32}}); - children.push_back({&curr->value, {type}}); + ConstraintCollector{builder, children}.visitArrayStore(curr, ht, valueType); return popConstrainedChildren(children); } @@ -2385,7 +2382,7 @@ IRBuilder::makeArrayStore(HeapType arrayType, unsigned bytes, Type type) { } ArrayStore curr; - CHECK_ERR(ChildPopper{*this}.visitArrayStoreWithType(&curr, arrayType, type)); + CHECK_ERR(ChildPopper{*this}.visitArrayStore(&curr, arrayType, type)); CHECK_ERR(validateTypeAnnotation(arrayType, curr.ref)); push(builder.makeArrayStore(bytes, curr.ref, curr.index, curr.value)); From 8e5377079bb9ef3344bb656e0979e9ee450cad8c Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 5 Mar 2026 00:34:24 +0000 Subject: [PATCH 09/20] add more tests --- test/spec/array-multibyte.wast | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/spec/array-multibyte.wast b/test/spec/array-multibyte.wast index a2f44479d94..aedc8bc85b4 100644 --- a/test/spec/array-multibyte.wast +++ b/test/spec/array-multibyte.wast @@ -341,6 +341,8 @@ (invoke "i32_set_i16" (i32.const 0) (i32.const 0x1234)) (assert_return (invoke "get_array_4_byte" (i32.const 0)) (i32.const 0x34)) (assert_return (invoke "get_array_4_byte" (i32.const 1)) (i32.const 0x12)) +(assert_return (invoke "get_array_4_byte" (i32.const 2)) (i32.const 0x00)) +(assert_return (invoke "get_array_4_byte" (i32.const 3)) (i32.const 0x00)) (invoke "i32_set_i32" (i32.const 0) (i32.const 0x12345678)) (assert_return (invoke "get_array_4_byte" (i32.const 0)) (i32.const 0x78)) From 32ed9df8504e927df2c1b4636a2da8846346caf9 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 5 Mar 2026 00:31:41 +0000 Subject: [PATCH 10/20] use separate prefix --- test/lit/array-multibyte.wast | 281 +++++++++++++++++++++++++++++++--- 1 file changed, 261 insertions(+), 20 deletions(-) diff --git a/test/lit/array-multibyte.wast b/test/lit/array-multibyte.wast index 72a03c30e8a..a282bb3402c 100644 --- a/test/lit/array-multibyte.wast +++ b/test/lit/array-multibyte.wast @@ -1,13 +1,10 @@ ;; 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=RTRIP ;; 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 +;; RUN: wasm-opt %s -all -S -o - | wasm-opt -all -S -o - | filecheck %s --check-prefix=TEXT (module ;; CHECK: (type $0 (func)) @@ -16,6 +13,9 @@ ;; RTRIP: (type $0 (func)) ;; RTRIP: (type $i8_array (array (mut i8))) + ;; TEXT: (type $0 (func)) + + ;; TEXT: (type $i8_array (array (mut i8))) (type $i8_array (array (mut i8))) ;; CHECK: (global $arr (ref $i8_array) (array.new_default $i8_array @@ -24,6 +24,9 @@ ;; RTRIP: (global $arr (ref $i8_array) (array.new_default $i8_array ;; RTRIP-NEXT: (i32.const 4) ;; RTRIP-NEXT: )) + ;; TEXT: (global $arr (ref $i8_array) (array.new_default $i8_array + ;; TEXT-NEXT: (i32.const 4) + ;; TEXT-NEXT: )) (global $arr (ref $i8_array) (array.new_default $i8_array (i32.const 4)) ) @@ -112,6 +115,48 @@ ;; RTRIP-NEXT: (f64.const 2) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) + ;; TEXT: (func $stores (type $0) + ;; TEXT-NEXT: (i32.store8 (type $i8_array) + ;; TEXT-NEXT: (global.get $arr) + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: (i32.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (i32.store16 (type $i8_array) + ;; TEXT-NEXT: (global.get $arr) + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: (i32.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (i32.store (type $i8_array) + ;; TEXT-NEXT: (global.get $arr) + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: (i32.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (f32.store (type $i8_array) + ;; TEXT-NEXT: (global.get $arr) + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: (f32.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (i64.store8 (type $i8_array) + ;; TEXT-NEXT: (global.get $arr) + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: (i64.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (i64.store16 (type $i8_array) + ;; TEXT-NEXT: (global.get $arr) + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: (i64.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (i64.store32 (type $i8_array) + ;; TEXT-NEXT: (global.get $arr) + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: (i64.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (f64.store (type $i8_array) + ;; TEXT-NEXT: (global.get $arr) + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: (f64.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: ) (func $stores (i32.store8 (type $i8_array) (global.get $arr) (i32.const 1) (i32.const 2)) (i32.store16 (type $i8_array) (global.get $arr) (i32.const 1) (i32.const 2)) @@ -125,7 +170,7 @@ ) ;; CHECK: (func $stores_null (type $0) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -137,7 +182,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -149,7 +194,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -161,7 +206,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -173,7 +218,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -185,7 +230,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -197,7 +242,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -209,7 +254,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -304,6 +349,104 @@ ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (unreachable) ;; RTRIP-NEXT: ) + ;; TEXT: (func $stores_null (type $0) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (ref.null none) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (ref.null none) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (ref.null none) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (ref.null none) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (f32.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (ref.null none) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i64.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (ref.null none) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i64.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (ref.null none) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i64.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (ref.null none) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (f64.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: ) (func $stores_null (i32.store8 (type $i8_array) (ref.null $i8_array) (i32.const 1) (i32.const 2)) (i32.store16 (type $i8_array) (ref.null $i8_array) (i32.const 1) (i32.const 2)) @@ -317,7 +460,7 @@ ) ;; CHECK: (func $stores_unreachable (type $0) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -329,7 +472,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -341,7 +484,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -353,7 +496,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -365,7 +508,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -377,7 +520,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -389,7 +532,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -401,7 +544,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayStore we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -417,6 +560,104 @@ ;; RTRIP: (func $stores_unreachable (type $0) ;; RTRIP-NEXT: (unreachable) ;; RTRIP-NEXT: ) + ;; TEXT: (func $stores_unreachable (type $0) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (f32.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i64.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i64.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i64.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (block + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (i32.const 1) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (drop + ;; TEXT-NEXT: (f64.const 2) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: (unreachable) + ;; TEXT-NEXT: ) + ;; TEXT-NEXT: ) (func $stores_unreachable (i32.store8 (type $i8_array) (unreachable) (i32.const 1) (i32.const 2)) (i32.store16 (type $i8_array) (unreachable) (i32.const 1) (i32.const 2)) From 3168b36b68153bfb33c0c802266d8a278b0683ec Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 5 Mar 2026 01:31:28 +0000 Subject: [PATCH 11/20] move enum --- src/wasm-binary.h | 5 +++++ src/wasm.h | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 00ccfcfae28..50e1b27b74b 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -43,6 +43,11 @@ enum { MaxLEB32Bytes = 5, }; +enum class BackingType { + Memory, + Array, +}; + template struct LEB { static_assert(sizeof(MiniT) == 1, "MiniT must be a byte"); diff --git a/src/wasm.h b/src/wasm.h index ce154ef7963..ca2f8ad3980 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -72,11 +72,6 @@ enum class MemoryOrder : uint8_t { AcqRel, }; -enum class BackingType { - Memory, - Array, -}; - enum class IRProfile { Normal, Poppy }; // Operators From bed10a28fd984dc1823d454aa20040a6ce8fef6d Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 5 Mar 2026 18:16:30 +0000 Subject: [PATCH 12/20] remove roundtrip text filecheck --- test/lit/array-multibyte.wast | 245 +--------------------------------- 1 file changed, 1 insertion(+), 244 deletions(-) diff --git a/test/lit/array-multibyte.wast b/test/lit/array-multibyte.wast index a282bb3402c..cdeda331bae 100644 --- a/test/lit/array-multibyte.wast +++ b/test/lit/array-multibyte.wast @@ -4,7 +4,7 @@ ;; 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 --check-prefix=TEXT +;; RUN: wasm-opt %s -all -S -o - | wasm-opt -all -S -o - (module ;; CHECK: (type $0 (func)) @@ -13,9 +13,7 @@ ;; RTRIP: (type $0 (func)) ;; RTRIP: (type $i8_array (array (mut i8))) - ;; TEXT: (type $0 (func)) - ;; TEXT: (type $i8_array (array (mut i8))) (type $i8_array (array (mut i8))) ;; CHECK: (global $arr (ref $i8_array) (array.new_default $i8_array @@ -24,9 +22,6 @@ ;; RTRIP: (global $arr (ref $i8_array) (array.new_default $i8_array ;; RTRIP-NEXT: (i32.const 4) ;; RTRIP-NEXT: )) - ;; TEXT: (global $arr (ref $i8_array) (array.new_default $i8_array - ;; TEXT-NEXT: (i32.const 4) - ;; TEXT-NEXT: )) (global $arr (ref $i8_array) (array.new_default $i8_array (i32.const 4)) ) @@ -115,48 +110,6 @@ ;; RTRIP-NEXT: (f64.const 2) ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: ) - ;; TEXT: (func $stores (type $0) - ;; TEXT-NEXT: (i32.store8 (type $i8_array) - ;; TEXT-NEXT: (global.get $arr) - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: (i32.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (i32.store16 (type $i8_array) - ;; TEXT-NEXT: (global.get $arr) - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: (i32.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (i32.store (type $i8_array) - ;; TEXT-NEXT: (global.get $arr) - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: (i32.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (f32.store (type $i8_array) - ;; TEXT-NEXT: (global.get $arr) - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: (f32.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (i64.store8 (type $i8_array) - ;; TEXT-NEXT: (global.get $arr) - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: (i64.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (i64.store16 (type $i8_array) - ;; TEXT-NEXT: (global.get $arr) - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: (i64.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (i64.store32 (type $i8_array) - ;; TEXT-NEXT: (global.get $arr) - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: (i64.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (f64.store (type $i8_array) - ;; TEXT-NEXT: (global.get $arr) - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: (f64.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: ) (func $stores (i32.store8 (type $i8_array) (global.get $arr) (i32.const 1) (i32.const 2)) (i32.store16 (type $i8_array) (global.get $arr) (i32.const 1) (i32.const 2)) @@ -349,104 +302,6 @@ ;; RTRIP-NEXT: ) ;; RTRIP-NEXT: (unreachable) ;; RTRIP-NEXT: ) - ;; TEXT: (func $stores_null (type $0) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (ref.null none) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (ref.null none) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (ref.null none) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (ref.null none) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (f32.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (ref.null none) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i64.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (ref.null none) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i64.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (ref.null none) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i64.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (ref.null none) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (f64.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: ) (func $stores_null (i32.store8 (type $i8_array) (ref.null $i8_array) (i32.const 1) (i32.const 2)) (i32.store16 (type $i8_array) (ref.null $i8_array) (i32.const 1) (i32.const 2)) @@ -560,104 +415,6 @@ ;; RTRIP: (func $stores_unreachable (type $0) ;; RTRIP-NEXT: (unreachable) ;; RTRIP-NEXT: ) - ;; TEXT: (func $stores_unreachable (type $0) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (f32.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i64.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i64.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i64.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (block - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (i32.const 1) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (drop - ;; TEXT-NEXT: (f64.const 2) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: (unreachable) - ;; TEXT-NEXT: ) - ;; TEXT-NEXT: ) (func $stores_unreachable (i32.store8 (type $i8_array) (unreachable) (i32.const 1) (i32.const 2)) (i32.store16 (type $i8_array) (unreachable) (i32.const 1) (i32.const 2)) From 18cc62d3668da8829bbadac8964b9d2ce2fb63ab Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 6 Mar 2026 16:45:55 +0000 Subject: [PATCH 13/20] remove until done --- test/binaryen.js/kitchen-sink.js.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index bcfd96ed96a..d46df456ba3 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -35,7 +35,6 @@ Features.Strings: 16384 Features.MultiMemory: 32768 Features.RelaxedAtomics: 4194304 Features.CustomPageSizes: 8388608 -Features.Multibyte: 16777216 Features.All: 33554431 InvalidId: 0 BlockId: 1 From 324e152521fdf41bd09a5cf9b2bc1de151b8348a Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Mon, 9 Mar 2026 23:43:49 +0000 Subject: [PATCH 14/20] unknown --- src/ir/child-typer.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 8c55a3c710c..6eff84d4d18 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1106,6 +1106,10 @@ template struct ChildTyper : OverriddenVisitor { } ht = curr->ref->type.getHeapType(); } + if (curr->value->type == Type::unreachable) { + self().noteUnknown(); + return; + } note(&curr->ref, Type(*ht, Nullable)); note(&curr->index, Type::i32); note(&curr->value, valueType ? *valueType : curr->value->type); From ddf7a10472226503e282ccda1e6ec44bb3df7a9e Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 10 Mar 2026 18:20:48 +0000 Subject: [PATCH 15/20] fix unknown --- src/ir/child-typer.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 6eff84d4d18..c21467e70a6 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1106,13 +1106,14 @@ template struct ChildTyper : OverriddenVisitor { } ht = curr->ref->type.getHeapType(); } - if (curr->value->type == Type::unreachable) { + auto actualValueType = valueType ? *valueType : curr->value->type; + if (actualValueType == Type::unreachable) { self().noteUnknown(); return; } note(&curr->ref, Type(*ht, Nullable)); note(&curr->index, Type::i32); - note(&curr->value, valueType ? *valueType : curr->value->type); + note(&curr->value, actualValueType); } void visitArrayLen(ArrayLen* curr) { From 9ffd9e7073465e72b5a9a36405f8dc3bdb34a607 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 10 Mar 2026 18:43:22 +0000 Subject: [PATCH 16/20] share printer and handle possible contents --- src/ir/possible-contents.cpp | 3 +++ src/passes/Print.cpp | 38 ++++++++++++++++-------------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index e41d3f79f44..4e12c8f3e6b 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -2837,6 +2837,9 @@ void Flower::flowAfterUpdate(LocationIndex locationIndex) { } else if (auto* set = parent->dynCast()) { assert(set->ref == child || set->value == child); writeToData(set->ref, set->value, 0); + } else if (auto* store = parent->dynCast()) { + assert(store->ref == child || store->value == child); + writeToData(store->ref, store->value, 0); } else if (auto* get = parent->dynCast()) { // Similar to struct.get. assert(get->ref == child); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index ec043e32066..5d72692b36b 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -437,21 +437,29 @@ 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) { + void printMemoryPostfix(uint8_t bytes, Type type) { + switch (bytes) { + case 1: o << '8'; - } else if (bytes == 2) { - if (valueType == Type::f32) { + break; + case 2: + if (type == Type::f32) { o << "_f16"; } else { o << "16"; } - } else if (bytes == 4) { + break; + case 4: o << "32"; - } else { + break; + default: abort(); - } + } + } + + std::ostream& printStorePostfix(uint8_t bytes, Type valueType) { + if (bytes < 4 || (valueType == Type::i64 && bytes < 8)) { + printMemoryPostfix(bytes, valueType); } return o; } @@ -575,19 +583,7 @@ struct PrintExpressionContents o << ".load"; if (curr->type != Type::unreachable && curr->bytes < curr->type.getByteSize()) { - if (curr->bytes == 1) { - o << '8'; - } else if (curr->bytes == 2) { - if (curr->type == Type::f32) { - o << "_f16"; - } else { - o << "16"; - } - } else if (curr->bytes == 4) { - o << "32"; - } else { - abort(); - } + printMemoryPostfix(curr->bytes, curr->type); if (curr->type != Type::f32) { o << (curr->signed_ ? "_s" : "_u"); } From 220bc36a3dc6c016b82a41e033a8d25577bfc1fe Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 11 Mar 2026 21:27:56 +0000 Subject: [PATCH 17/20] don't use multibyte in v8 --- scripts/fuzz_opt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index a24c7b158b9..254e1cc19a4 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -65,7 +65,7 @@ # V8 does not support shared memories when running with # shared-everything enabled, so do not fuzz shared-everything # for now. The remaining features are not yet implemented in v8. -DISALLOWED_FEATURES_IN_V8 = ['shared-everything', 'strings', 'stack-switching', 'relaxed-atomics'] +DISALLOWED_FEATURES_IN_V8 = ['shared-everything', 'strings', 'stack-switching', 'relaxed-atomics', 'multibyte'] # utilities From a5c00c1ae8f3438a99576f3aaf0e1f47f244f796 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 12 Mar 2026 16:42:29 +0000 Subject: [PATCH 18/20] allow multibyte --- scripts/clusterfuzz/run.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index f9d989ed7b9..abd1d5ad5ba 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -94,7 +94,6 @@ '--disable-strings', '--disable-stack-switching', '--disable-relaxed-atomics', - '--disable-multibyte', ] From c6c927f5d729aadb8ec849af278d14ed5a7d0579 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 13 Mar 2026 18:40:49 +0000 Subject: [PATCH 19/20] what do do here... --- src/ir/possible-contents.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 4e12c8f3e6b..6e41d8dbdbc 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -2247,7 +2247,7 @@ struct Flower { Expression* read); // Similar to readFromData, but does a write for a struct.set or array.set. - void writeToData(Expression* ref, Expression* value, Index fieldIndex); + void writeToData(Expression* ref, Expression* value, Index fieldIndex, bool multibyte = false); // We will need subtypes during the flow, so compute them once ahead of time. std::unique_ptr subTypes; @@ -2584,7 +2584,7 @@ Flower::Flower(Module& wasm, const PassOptions& options) for (const auto& [location, value] : roots) { #if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 std::cout << " init root\n"; - dump(location); + dump(getLocation(location)); value.dump(std::cout, &wasm); std::cout << '\n'; #endif @@ -2839,7 +2839,7 @@ void Flower::flowAfterUpdate(LocationIndex locationIndex) { writeToData(set->ref, set->value, 0); } else if (auto* store = parent->dynCast()) { assert(store->ref == child || store->value == child); - writeToData(store->ref, store->value, 0); + writeToData(store->ref, store->value, 0, /*multibyte*/ true); } else if (auto* get = parent->dynCast()) { // Similar to struct.get. assert(get->ref == child); @@ -3199,7 +3199,7 @@ void Flower::readFromData(Type declaredType, connectDuringFlow(coneReadLocation, ExpressionLocation{read, 0}); } -void Flower::writeToData(Expression* ref, Expression* value, Index fieldIndex) { +void Flower::writeToData(Expression* ref, Expression* value, Index fieldIndex, bool multibyte) { #if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 std::cout << " add special writes\n"; #endif @@ -3247,6 +3247,12 @@ void Flower::writeToData(Expression* ref, Expression* value, Index fieldIndex) { // As in readFromData, normalize to the proper cone. auto cone = refContents.getCone(); auto normalizedDepth = getNormalizedConeDepth(cone.type, cone.depth); + if (multibyte) { + // If I do this I get: + // Flower::updateContents(LocationIndex, PossibleContents): Assertion `!contents.isMany()' failed. + // valueContents = PossibleContents::fromType(value->type); + valueContents = PossibleContents::fromType(Type::i32); + } subTypes->iterSubTypes( cone.type.getHeapType(), normalizedDepth, [&](HeapType type, Index depth) { From c148eaf03660944d0e8fb1f47c0b8000bd8bfdd3 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 13 Mar 2026 22:00:41 +0000 Subject: [PATCH 20/20] handle multibyte array store type filtering --- src/ir/possible-contents.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 6e41d8dbdbc..1315fd9c3e6 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -2751,6 +2751,11 @@ bool Flower::updateContents(LocationIndex locationIndex, } else if (auto* globalLoc = std::get_if(&location)) { filterGlobalContents(contents, *globalLoc); filtered = true; + } else if (auto* dataLoc = std::get_if(&location)) { + // Multibyte array stores with differing widths can merge to Many, so + // filter again afterwards to fall back to the declared type limit. + filterDataContents(contents, *dataLoc); + filtered = true; } // Check if anything changed after filtering, if we did so. @@ -3017,6 +3022,11 @@ void Flower::filterDataContents(PossibleContents& contents, contents = PossibleContents::none(); return; } + if (contents.isMany()) { + // An unknown state (e.g. from combining writes of different types like in + // multibyte array stores) translates into the most generic bounded type. + contents = PossibleContents::fromType(field->type); + } if (field->isPacked()) { // We must handle packed fields carefully. @@ -3248,10 +3258,7 @@ void Flower::writeToData(Expression* ref, Expression* value, Index fieldIndex, b auto cone = refContents.getCone(); auto normalizedDepth = getNormalizedConeDepth(cone.type, cone.depth); if (multibyte) { - // If I do this I get: - // Flower::updateContents(LocationIndex, PossibleContents): Assertion `!contents.isMany()' failed. - // valueContents = PossibleContents::fromType(value->type); - valueContents = PossibleContents::fromType(Type::i32); + valueContents = PossibleContents::fromType(value->type); } subTypes->iterSubTypes(