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 diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 505872c6c5f..b4271f5663e 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 BinaryenFeatureCustomPageSizes(void) { return static_cast(FeatureSet::CustomPageSizes); } diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 1fc01346d02..80d0451796d 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 BinaryenFeatureCustomPageSizes(void); BINARYEN_API BinaryenFeatures BinaryenFeatureAll(void); 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..c21467e70a6 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1096,6 +1096,26 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->value, type); } + void visitArrayStore(ArrayStore* curr, + std::optional ht = std::nullopt, + std::optional valueType = std::nullopt) { + if (!ht) { + if (!curr->ref->type.isRef()) { + self().noteUnknown(); + return; + } + ht = curr->ref->type.getHeapType(); + } + 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, actualValueType); + } + 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 2ff70910d46..137b11488f6 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1143,6 +1143,15 @@ class EffectAnalyzer { parent.implicitTrap = true; writesArray(curr->ref->type.getHeapType(), curr->order); } + 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) { trapOnNull(curr->ref); // No need to model this as reading the array since the length cannot be diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 84affdbf25b..1315fd9c3e6 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 @@ -1742,6 +1749,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); @@ -2239,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; @@ -2576,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 @@ -2743,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. @@ -2829,6 +2842,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, /*multibyte*/ true); } else if (auto* get = parent->dynCast()) { // Similar to struct.get. assert(get->ref == child); @@ -3006,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. @@ -3188,7 +3209,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 @@ -3236,6 +3257,9 @@ 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) { + valueContents = PossibleContents::fromType(value->type); + } subTypes->iterSubTypes( cone.type.getHeapType(), normalizedDepth, [&](HeapType type, Index depth) { diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index e2cf5b14f20..ba1c57afb8c 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -408,6 +408,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { 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()) { diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 54282cfb018..9ac9e780625 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -569,6 +569,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, @@ -2326,6 +2331,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 b10e8510e13..8d317441b54 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -1818,6 +1818,16 @@ Result<> makeStore(Ctx& ctx, Type type, int bytes, bool isAtomic) { + if (ctx.in.takeSExprStart("type"sv)) { + auto arrayType = typeidx(ctx); + CHECK_ERR(arrayType); + + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of type use"); + } + + 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 e69da21e359..5d72692b36b 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -437,6 +437,33 @@ struct PrintExpressionContents return parent.printBlockType(sig); } + void printMemoryPostfix(uint8_t bytes, Type type) { + switch (bytes) { + case 1: + o << '8'; + break; + case 2: + if (type == Type::f32) { + o << "_f16"; + } else { + o << "16"; + } + break; + case 4: + o << "32"; + break; + default: + abort(); + } + } + + std::ostream& printStorePostfix(uint8_t bytes, Type valueType) { + if (bytes < 4 || (valueType == Type::i64 && bytes < 8)) { + printMemoryPostfix(bytes, valueType); + } + return o; + } + void visitBlock(Block* curr) { printMedium(o, "block"); if (curr->name.is()) { @@ -556,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"); } @@ -589,21 +604,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 +2478,18 @@ struct PrintExpressionContents o << ' '; printHeapTypeName(curr->ref->type.getHeapType()); } + void visitArrayStore(ArrayStore* curr) { + prepareColor(o) << forceConcrete(curr->value->type); + o << ".store"; + printStorePostfix(curr->bytes, curr->value->type); + 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 164ae38564d..cc39daad4fc 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") .addFeature(FeatureSet::CustomPageSizes, "custom page sizes") diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 298c000dd3a..4aab3450717 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -44,6 +44,11 @@ enum { MaxLEB32Bytes = 5, }; +enum class BackingType { + Memory, + Array, +}; + template struct LEB { static_assert(sizeof(MiniT) == 1, "MiniT must be a byte"); @@ -359,6 +364,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; @@ -463,6 +469,7 @@ extern const char* BulkMemoryOptFeature; extern const char* CallIndirectOverlongFeature; extern const char* CustomDescriptorsFeature; extern const char* RelaxedAtomicsFeature; +extern const char* MultibyteFeature; extern const char* CustomPageSizesFeature; enum Subsection { @@ -1709,6 +1716,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(); @@ -1756,11 +1765,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 1740e369d5d..928080ffd65 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1132,6 +1132,18 @@ class Builder { ret->finalize(); return ret; } + ArrayStore* makeArrayStore(unsigned bytes, + Expression* ref, + Expression* index, + Expression* value) { + auto* ret = wasm.allocator.alloc(); + ret->bytes = bytes; + 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..801164754ac 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -745,6 +745,13 @@ 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_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 adfa1e1a30b..5d8bbe66ae8 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -57,11 +57,12 @@ struct FeatureSet { CustomDescriptors = 1 << 21, RelaxedAtomics = 1 << 22, CustomPageSizes = 1 << 23, + Multibyte = 1 << 24, 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 << 24) - 1, + All = (1 << 25) - 1, }; static std::string toString(Feature f) { @@ -114,6 +115,8 @@ struct FeatureSet { return "relaxed-atomics"; case CustomPageSizes: return "custom-page-sizes"; + case Multibyte: + return "multibyte"; case MVP: case Default: case All: @@ -176,6 +179,7 @@ struct FeatureSet { } bool hasRelaxedAtomics() const { return (features & RelaxedAtomics) != 0; } bool hasCustomPageSizes() const { return (features & CustomPageSizes) != 0; } + bool hasMultibyte() const { return (features & Multibyte) != 0; } bool hasAll() const { return (features & All) != 0; } void set(FeatureSet f, bool v = true) { @@ -203,6 +207,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 eb2e2742d2a..65b8a11a8c8 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -382,6 +382,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; @@ -2346,6 +2360,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->value->type.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..7600fc47ef9 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 emitStoreOpcode(uint8_t bytes, Type valueType); int32_t getBreakIndex(Name name); WasmBinaryWriter& parent; diff --git a/src/wasm.h b/src/wasm.h index 3a1a49af862..fb56fd55f25 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -738,6 +738,7 @@ class Expression { ArrayNewFixedId, ArrayGetId, ArraySetId, + ArrayStoreId, ArrayLenId, ArrayCopyId, ArrayFillId, @@ -1870,6 +1871,19 @@ 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; + + void finalize(); +}; + class ArrayLen : public SpecificExpression { public: ArrayLen() = default; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index b4b11e23491..1d7b70502de 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1434,6 +1434,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: @@ -3286,6 +3288,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); @@ -3625,96 +3636,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(); @@ -4019,11 +4021,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); } } @@ -4568,98 +4570,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); } @@ -5626,10 +5628,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) { @@ -5647,6 +5651,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"); } @@ -5654,39 +5664,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..d8801300da6 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -567,6 +567,14 @@ struct IRBuilder::ChildPopper return popConstrainedChildren(children); } + Result<> visitArrayStore(ArrayStore* curr, + std::optional ht = std::nullopt, + std::optional valueType = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitArrayStore(curr, ht, valueType); + return popConstrainedChildren(children); + } + Result<> visitArrayCopy(ArrayCopy* curr, std::optional dest = std::nullopt, std::optional src = std::nullopt) { @@ -2367,6 +2375,20 @@ Result<> IRBuilder::makeArraySet(HeapType type, MemoryOrder order) { return Ok{}; } +Result<> +IRBuilder::makeArrayStore(HeapType arrayType, unsigned bytes, Type type) { + if (!arrayType.isArray()) { + return Err{"expected array type annotation on array store"}; + } + + ArrayStore curr; + CHECK_ERR(ChildPopper{*this}.visitArrayStore(&curr, arrayType, type)); + + CHECK_ERR(validateTypeAnnotation(arrayType, curr.ref)); + push(builder.makeArrayStore(bytes, 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..70b61ef9665 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::emitStoreOpcode(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"); - } + emitStoreOpcode(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; + } + emitStoreOpcode(curr->bytes, curr->value->type); + 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 3e2e9d015ef..5aac28ff6bc 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -548,6 +548,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); @@ -3754,6 +3755,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 c05a23dbc9a..6af2d4fb8ca 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"; const char* CustomPageSizesFeature = "custom-page-sizes"; } // namespace BinaryConsts::CustomSections @@ -1332,6 +1333,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 3e6c0e0ec1f..3a0a5ea3302 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 eff9d912789..d46df456ba3 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -35,7 +35,7 @@ Features.Strings: 16384 Features.MultiMemory: 32768 Features.RelaxedAtomics: 4194304 Features.CustomPageSizes: 8388608 -Features.All: 16777215 +Features.All: 33554431 InvalidId: 0 BlockId: 1 IfId: 2 @@ -102,17 +102,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 1ae179ca265..1d699965baa 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -378,6 +378,7 @@ void test_features() { BinaryenFeatureRelaxedAtomics()); printf("BinaryenFeatureCustomPageSizes: %d\n", BinaryenFeatureCustomPageSizes()); + 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 ed09c2b418c..f6fc361ec63 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -49,7 +49,8 @@ BinaryenFeatureExtendedConst: 8192 BinaryenFeatureStrings: 16384 BinaryenFeatureRelaxedAtomics: 4194304 BinaryenFeatureCustomPageSizes: 8388608 -BinaryenFeatureAll: 16777215 +BinaryenFeatureMultibyte: 16777216 +BinaryenFeatureAll: 33554431 (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..cdeda331bae --- /dev/null +++ b/test/lit/array-multibyte.wast @@ -0,0 +1,429 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; 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 - + +(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: (global $arr (ref $i8_array) (array.new_default $i8_array + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-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)) + ) + + ;; CHECK: (func $stores (type $0) + ;; CHECK-NEXT: (i32.store8 (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; 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 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; 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 1) + ;; CHECK-NEXT: (f32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.store8 (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; 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 1) + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.store32 (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; 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 1) + ;; CHECK-NEXT: (f64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $stores (type $0) + ;; RTRIP-NEXT: (i32.store8 (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; 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 1) + ;; RTRIP-NEXT: (i32.const 2) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (i32.store (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; 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 1) + ;; RTRIP-NEXT: (f32.const 2) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (i64.store8 (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; 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 1) + ;; RTRIP-NEXT: (i64.const 2) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (i64.store32 (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; 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 1) + ;; RTRIP-NEXT: (f64.const 2) + ;; RTRIP-NEXT: ) + ;; RTRIP-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)) + (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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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 ;; (replaces unreachable ArrayStore we can't emit) + ;; 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) (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)) + ) +) diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index 22f58c37806..463a8c4270c 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 b1b2ce1a6c4..5f02cb6d8e9 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 89a99f23df3..73a8cd2fe47 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 be3d3b9e7eb..21d4c5ef261 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 ad5d0223fa9..0386c8adf8e 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 8aca529b521..f1b9f25e167 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 a3d6257929d..8aed53a603a 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 d424fd222f0..f403191fed6 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 8bf70f829d5..e94060c0208 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -278,6 +278,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 bb541389735..06248ab0b55 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 ce3e9222f7b..8172cc77f9c 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 @@ -22,6 +22,7 @@ --enable-custom-descriptors --enable-relaxed-atomics --enable-custom-page-sizes +--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..aedc8bc85b4 --- /dev/null +++ b/test/spec/array-multibyte.wast @@ -0,0 +1,504 @@ +;; 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 $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) + (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) + ) + ) + + ;; 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) + (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)) +(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 +;; + +(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)) +(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)) +(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)) +(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") +(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 0xFFFF)) "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 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 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 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") +(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 0xFFFF)) "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 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 +(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 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 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 1.0)) "out of bounds") +(assert_return (invoke "get_array_8_byte" (i32.const 1)) (i32.const 0)) + + +(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")