diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index c5693a7c06f..d15abcfd4a0 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 visitArrayLoad(ArrayLoad* 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"); } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index fa8871d1536..24afae568c5 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::visitArrayLoad(ArrayLoad* curr) { curr->finalize(); } void ReFinalize::visitArrayStore(ArrayStore* curr) { curr->finalize(); } void ReFinalize::visitArrayLen(ArrayLen* curr) { curr->finalize(); } void ReFinalize::visitArrayCopy(ArrayCopy* curr) { curr->finalize(); } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index c21467e70a6..4be9c220cff 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -1096,6 +1096,19 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->value, type); } + void visitArrayLoad(ArrayLoad* 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); + } + void visitArrayStore(ArrayStore* curr, std::optional ht = std::nullopt, std::optional valueType = std::nullopt) { diff --git a/src/ir/cost.h b/src/ir/cost.h index 684c0dc34a4..1cf67abe9e6 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -753,6 +753,9 @@ struct CostAnalyzer : public OverriddenVisitor { return 2 + nullCheckCost(curr->ref) + visit(curr->ref) + visit(curr->index) + visit(curr->value); } + CostType visitArrayLoad(ArrayLoad* curr) { + return 1 + nullCheckCost(curr->ref) + visit(curr->ref) + visit(curr->index); + } CostType visitArrayStore(ArrayStore* curr) { return 2 + nullCheckCost(curr->ref) + visit(curr->ref) + visit(curr->index) + visit(curr->value); diff --git a/src/ir/effects.h b/src/ir/effects.h index d1d38af595a..2327bb86f8d 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -1147,6 +1147,15 @@ class EffectAnalyzer { parent.implicitTrap = true; writesArray(curr->ref->type.getHeapType(), curr->order); } + void visitArrayLoad(ArrayLoad* curr) { + if (curr->ref->type.isNull()) { + parent.trap = true; + return; + } + parent.implicitTrap = true; + readsArray(curr->ref->type.getHeapType(), MemoryOrder::Unordered); + } + void visitArrayStore(ArrayStore* curr) { if (curr->ref->type.isNull()) { parent.trap = true; diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 201f413060e..58799fa431f 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1105,6 +1105,13 @@ struct InfoCollector addChildParentLink(curr->ref, curr); addChildParentLink(curr->value, curr); } + void visitArrayLoad(ArrayLoad* curr) { + if (!isRelevant(curr->ref)) { + addRoot(curr); + return; + } + addChildParentLink(curr->ref, curr); + } void visitArrayStore(ArrayStore* curr) { if (curr->ref->type == Type::unreachable) { return; @@ -1755,6 +1762,7 @@ void TNHOracle::scan(Function* func, } void visitArrayGet(ArrayGet* curr) { notePossibleTrap(curr->ref); } void visitArraySet(ArraySet* curr) { notePossibleTrap(curr->ref); } + void visitArrayLoad(ArrayLoad* curr) { notePossibleTrap(curr->ref); } void visitArrayStore(ArrayStore* curr) { notePossibleTrap(curr->ref); } void visitArrayLen(ArrayLen* curr) { notePossibleTrap(curr->ref); } void visitArrayCopy(ArrayCopy* curr) { @@ -2865,6 +2873,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* load = parent->dynCast()) { + assert(load->ref == child); + readFromData(load->ref->type, 0, contents, load); } else if (auto* store = parent->dynCast()) { assert(store->ref == child || store->value == child); // TODO: model the stored value, and handle different but equal values in @@ -3108,15 +3119,25 @@ void Flower::filterPackedDataReads(PossibleContents& contents, auto signed_ = false; Expression* ref; Index index; + unsigned bytes = 0; + Type resultType = Type::none; if (auto* get = expr->dynCast()) { signed_ = get->signed_; ref = get->ref; index = get->index; + resultType = get->type; } else if (auto* get = expr->dynCast()) { signed_ = get->signed_; ref = get->ref; // Arrays are treated as having a single field. index = 0; + resultType = get->type; + } else if (auto* load = expr->dynCast()) { + signed_ = load->signed_; + ref = load->ref; + index = 0; + bytes = load->bytes; + resultType = load->type; } else { WASM_UNREACHABLE("bad packed read"); } @@ -3135,13 +3156,20 @@ void Flower::filterPackedDataReads(PossibleContents& contents, assert(ref->type.isRef()); auto field = GCTypeUtils::getField(ref->type.getHeapType(), index); assert(field); - if (!field->isPacked()) { - return; + if (!bytes) { + if (!field->isPacked()) { + return; + } + bytes = field->getByteSize(); } if (contents.isLiteral()) { // This is a constant. We can sign-extend it and use that value. - auto shifts = Literal(int32_t(32 - field->getByteSize() * 8)); + unsigned bits = resultType.getByteSize() == 8 ? 64 : 32; + if (bits <= bytes * 8) { + return; // No need to sign-extend for full size + } + auto shifts = Literal(int32_t(bits - bytes * 8)); auto lit = contents.getLiteral(); lit = lit.shl(shifts); lit = lit.shrS(shifts); diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 25c3e842adc..5d973de933b 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -412,6 +412,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { auto array = curr->ref->type.getHeapType().getArray(); self()->noteSubtype(curr->value, array.element.type); } + void visitArrayLoad(ArrayLoad* curr) {} void visitArrayStore(ArrayStore* curr) {} void visitArrayLen(ArrayLen* curr) {} void visitArrayCopy(ArrayCopy* curr) { diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 9ac9e780625..359f8fbd566 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -570,6 +570,11 @@ struct NullInstrParserCtx { return Ok{}; } template + Result<> makeArrayLoad( + Index, const std::vector&, Type, int, bool, HeapTypeT) { + return Ok{}; + } + template Result<> makeArrayStore(Index, const std::vector&, Type, int, HeapTypeT) { return Ok{}; @@ -2331,6 +2336,16 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { pos, irBuilder.makeStore(bytes, memarg.offset, memarg.align, type, *m)); } + Result<> makeArrayLoad(Index pos, + const std::vector& annotations, + Type type, + int bytes, + bool signed_, + HeapTypeT arrayType) { + return withLoc(pos, + irBuilder.makeArrayLoad(arrayType, bytes, signed_, type)); + } + Result<> makeArrayStore(Index pos, const std::vector& annotations, Type type, diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 8d317441b54..97613d4f983 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -1783,6 +1783,18 @@ Result<> makeLoad(Ctx& ctx, 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.makeArrayLoad( + pos, annotations, type, bytes, signed_, *arrayType); + } + auto mem = maybeMemidx(ctx); CHECK_ERR(mem); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index f887075fc2d..74cf4749b3d 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2478,6 +2478,23 @@ struct PrintExpressionContents o << ' '; printHeapTypeName(curr->ref->type.getHeapType()); } + void visitArrayLoad(ArrayLoad* curr) { + prepareColor(o) << forceConcrete(curr->type); + o << ".load"; + if (curr->type != Type::unreachable && + curr->bytes < curr->type.getByteSize()) { + printMemoryPostfix(curr->bytes, curr->type); + o << (curr->signed_ ? "_s" : "_u"); + } + o << " "; + restoreNormalColor(o); + + o << '('; + printMinor(o, "type "); + printHeapTypeName(curr->ref->type.getHeapType()); + o << ')'; + } + void visitArrayStore(ArrayStore* curr) { prepareColor(o) << forceConcrete(curr->value->type); o << ".store"; diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 984d2ae41f2..139ba8efbbd 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -802,6 +802,7 @@ struct TransferFn : OverriddenVisitor { } } + void visitArrayLoad(ArrayLoad* curr) { WASM_UNREACHABLE("TODO"); } void visitArrayStore(ArrayStore* curr) { WASM_UNREACHABLE("TODO"); } void visitArrayLen(ArrayLen* curr) { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index af031496e9a..d5e0c4a19aa 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1716,6 +1716,7 @@ class WasmBinaryReader { void readExports(); + Result<> readLoad(unsigned bytes, bool signed_, Type type); Result<> readStore(unsigned bytes, Type type); // The strings in the strings section (which are referred to by StringConst). diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 8340ce8e860..bac1becac49 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1132,6 +1132,21 @@ class Builder { ret->finalize(); return ret; } + ArrayLoad* makeArrayLoad(unsigned bytes, + bool signed_, + Expression* ref, + Expression* index, + Type type) { + auto* ret = wasm.allocator.alloc(); + ret->bytes = bytes; + ret->signed_ = signed_; + ret->ref = ref; + ret->index = index; + ret->type = type; + ret->finalize(); + return ret; + } + ArrayStore* makeArrayStore(unsigned bytes, Expression* ref, Expression* index, diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 801164754ac..142d1e7be70 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(ArrayLoad) +DELEGATE_FIELD_CHILD(ArrayLoad, index) +DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ArrayLoad, ref) +DELEGATE_FIELD_INT(ArrayLoad, bytes) +DELEGATE_FIELD_INT(ArrayLoad, signed_) +DELEGATE_FIELD_TYPE(ArrayLoad, type) +DELEGATE_FIELD_CASE_END(ArrayLoad) + DELEGATE_FIELD_CASE_START(ArrayStore) DELEGATE_FIELD_CHILD(ArrayStore, value) DELEGATE_FIELD_CHILD(ArrayStore, index) diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index 75167991132..eb7dfe84afb 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -92,6 +92,7 @@ DELEGATE(ArrayNewElem); DELEGATE(ArrayNewFixed); DELEGATE(ArrayGet); DELEGATE(ArraySet); +DELEGATE(ArrayLoad); DELEGATE(ArrayStore); DELEGATE(ArrayLen); DELEGATE(ArrayCopy); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index d7dae5c59ab..8b833857eff 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2378,6 +2378,51 @@ class ExpressionRunner : public OverriddenVisitor { data->values[i] = truncateForPacking(value.getSingleValue(), field); return Flow(); } + Flow visitArrayLoad(ArrayLoad* curr) { + VISIT(ref, curr->ref) + VISIT(index, curr->index) + auto data = ref.getSingleValue().getGCData(); + if (!data) { + trap("null ref"); + } + Index i = index.getSingleValue().geti32(); + size_t size = data->values.size(); + if (i >= size || curr->bytes > (size - i)) { + trap("array oob"); + } + uint64_t val = 0; + for (unsigned b = 0; b < curr->bytes; ++b) { + val |= static_cast(data->values[i + b].geti32()) << (b * 8); + } + switch (curr->type.getBasic()) { + case Type::i32: { + int32_t sval = static_cast(val); + if (curr->signed_) { + if (curr->bytes == 1) sval = int32_t(int8_t(sval)); + else if (curr->bytes == 2) sval = int32_t(int16_t(sval)); + } + return Literal(sval); + } + case Type::i64: { + int64_t sval = static_cast(val); + if (curr->signed_) { + if (curr->bytes == 1) sval = int64_t(int8_t(sval)); + else if (curr->bytes == 2) sval = int64_t(int16_t(sval)); + else if (curr->bytes == 4) sval = int64_t(int32_t(sval)); + } + return Literal(sval); + } + case Type::f32: { + return Literal(bit_cast(static_cast(val))); + } + case Type::f64: { + return Literal(bit_cast(static_cast(val))); + } + default: + WASM_UNREACHABLE("invalid type"); + } + } + Flow visitArrayStore(ArrayStore* curr) { VISIT(ref, curr->ref) VISIT(index, curr->index) diff --git a/src/wasm-ir-builder.h b/src/wasm-ir-builder.h index a8c731935db..d6aed8a1c32 100644 --- a/src/wasm-ir-builder.h +++ b/src/wasm-ir-builder.h @@ -251,6 +251,8 @@ 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<> + makeArrayLoad(HeapType arrayType, unsigned bytes, bool signed_, Type type); Result<> makeArrayStore(HeapType arrayType, unsigned bytes, Type type); Result<> makeArrayLen(); Result<> makeArrayCopy(HeapType destType, HeapType srcType); diff --git a/src/wasm-stack.h b/src/wasm-stack.h index 7600fc47ef9..2c9fdd616fa 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -133,6 +133,7 @@ class BinaryInstWriter : public OverriddenVisitor { MemoryOrder order, bool isRMW, BackingType backing = BackingType::Memory); + void emitLoadOpcode(unsigned bytes, bool signed_, Type type); void emitStoreOpcode(uint8_t bytes, Type valueType); int32_t getBreakIndex(Name name); diff --git a/src/wasm.h b/src/wasm.h index 716cf27a2cb..6292a61e2d1 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -739,6 +739,7 @@ class Expression { ArrayNewFixedId, ArrayGetId, ArraySetId, + ArrayLoadId, ArrayStoreId, ArrayLenId, ArrayCopyId, @@ -1872,6 +1873,19 @@ class ArraySet : public SpecificExpression { void finalize(); }; +class ArrayLoad : public SpecificExpression { +public: + ArrayLoad() = default; + ArrayLoad(MixedArena& allocator) {} + + uint8_t bytes; + bool signed_ = false; + Expression* ref; + Expression* index; + + void finalize(); +}; + class ArrayStore : public SpecificExpression { public: ArrayStore() = default; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f5a141ea0fb..44bf6143c90 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3288,6 +3288,15 @@ void WasmBinaryReader::readVars() { } } +Result<> WasmBinaryReader::readLoad(unsigned bytes, bool signed_, Type type) { + auto [mem, align, offset, backing] = getMemarg(); + if (backing == BackingType::Array) { + HeapType arrayType = getIndexedHeapType(); + return builder.makeArrayLoad(arrayType, bytes, signed_, type); + } + return builder.makeLoad(bytes, signed_, offset, align, type, mem); +} + Result<> WasmBinaryReader::readStore(unsigned bytes, Type type) { auto [mem, align, offset, backing] = getMemarg(); if (backing == BackingType::Array) { @@ -3635,62 +3644,34 @@ Result<> WasmBinaryReader::readInst() { return builder.makeConst(getFloat32Literal()); case BinaryConsts::F64Const: return builder.makeConst(getFloat64Literal()); - case BinaryConsts::I32LoadMem8S: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(1, true, offset, align, Type::i32, mem); - } - case BinaryConsts::I32LoadMem8U: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(1, false, offset, align, Type::i32, mem); - } - case BinaryConsts::I32LoadMem16S: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(2, true, offset, align, Type::i32, mem); - } - case BinaryConsts::I32LoadMem16U: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(2, false, offset, align, Type::i32, mem); - } - case BinaryConsts::I32LoadMem: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(4, false, offset, align, Type::i32, mem); - } - case BinaryConsts::I64LoadMem8S: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(1, true, offset, align, Type::i64, mem); - } - case BinaryConsts::I64LoadMem8U: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(1, false, offset, align, Type::i64, mem); - } - case BinaryConsts::I64LoadMem16S: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(2, true, offset, align, Type::i64, mem); - } - case BinaryConsts::I64LoadMem16U: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(2, false, offset, align, Type::i64, mem); - } - case BinaryConsts::I64LoadMem32S: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(4, true, offset, align, Type::i64, mem); - } - case BinaryConsts::I64LoadMem32U: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(4, false, offset, align, Type::i64, mem); - } - case BinaryConsts::I64LoadMem: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(8, false, offset, align, Type::i64, mem); - } - case BinaryConsts::F32LoadMem: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(4, false, offset, align, Type::f32, mem); - } - case BinaryConsts::F64LoadMem: { - auto [mem, align, offset, backing] = getMemarg(); - return builder.makeLoad(8, false, offset, align, Type::f64, mem); - } + case BinaryConsts::I32LoadMem8S: + return readLoad(1, true, Type::i32); + case BinaryConsts::I32LoadMem8U: + return readLoad(1, false, Type::i32); + case BinaryConsts::I32LoadMem16S: + return readLoad(2, true, Type::i32); + case BinaryConsts::I32LoadMem16U: + return readLoad(2, false, Type::i32); + case BinaryConsts::I32LoadMem: + return readLoad(4, false, Type::i32); + case BinaryConsts::I64LoadMem8S: + return readLoad(1, true, Type::i64); + case BinaryConsts::I64LoadMem8U: + return readLoad(1, false, Type::i64); + case BinaryConsts::I64LoadMem16S: + return readLoad(2, true, Type::i64); + case BinaryConsts::I64LoadMem16U: + return readLoad(2, false, Type::i64); + case BinaryConsts::I64LoadMem32S: + return readLoad(4, true, Type::i64); + case BinaryConsts::I64LoadMem32U: + return readLoad(4, false, Type::i64); + case BinaryConsts::I64LoadMem: + return readLoad(8, false, Type::i64); + case BinaryConsts::F32LoadMem: + return readLoad(4, false, Type::f32); + case BinaryConsts::F64LoadMem: + return readLoad(8, false, Type::f64); case BinaryConsts::I32StoreMem8: { return readStore(1, Type::i32); } diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index b635f989502..6857b0ea0cc 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<> visitArrayLoad(ArrayLoad* curr, + std::optional ht = std::nullopt) { + std::vector children; + ConstraintCollector{builder, children}.visitArrayLoad(curr, ht); + return popConstrainedChildren(children); + } + Result<> visitArrayStore(ArrayStore* curr, std::optional ht = std::nullopt, std::optional valueType = std::nullopt) { @@ -2395,6 +2402,22 @@ IRBuilder::makeArrayStore(HeapType arrayType, unsigned bytes, Type type) { return Ok{}; } +Result<> IRBuilder::makeArrayLoad(HeapType arrayType, + unsigned bytes, + bool signed_, + Type type) { + if (!arrayType.isArray()) { + return Err{"expected array type annotation on array load"}; + } + + ArrayLoad curr; + CHECK_ERR(ChildPopper{*this}.visitArrayLoad(&curr, arrayType)); + + CHECK_ERR(validateTypeAnnotation(arrayType, curr.ref)); + push(builder.makeArrayLoad(bytes, signed_, curr.ref, curr.index, type)); + 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 26efd9d762c..108f73edcab 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -120,6 +120,74 @@ void BinaryInstWriter::emitStoreOpcode(uint8_t bytes, Type valueType) { } } +void BinaryInstWriter::emitLoadOpcode(unsigned bytes, bool signed_, Type type) { + switch (type.getBasic()) { + case Type::i32: { + switch (bytes) { + case 1: + o << static_cast(signed_ ? BinaryConsts::I32LoadMem8S + : BinaryConsts::I32LoadMem8U); + break; + case 2: + o << static_cast(signed_ ? BinaryConsts::I32LoadMem16S + : BinaryConsts::I32LoadMem16U); + break; + case 4: + o << static_cast(BinaryConsts::I32LoadMem); + break; + default: + abort(); + } + break; + } + case Type::i64: { + switch (bytes) { + case 1: + o << static_cast(signed_ ? BinaryConsts::I64LoadMem8S + : BinaryConsts::I64LoadMem8U); + break; + case 2: + o << static_cast(signed_ ? BinaryConsts::I64LoadMem16S + : BinaryConsts::I64LoadMem16U); + break; + case 4: + o << static_cast(signed_ ? BinaryConsts::I64LoadMem32S + : BinaryConsts::I64LoadMem32U); + break; + case 8: + o << static_cast(BinaryConsts::I64LoadMem); + break; + default: + abort(); + } + break; + } + case Type::f32: { + switch (bytes) { + case 2: + o << static_cast(BinaryConsts::MiscPrefix) + << U32LEB(BinaryConsts::F32_F16LoadMem); + break; + case 4: + o << static_cast(BinaryConsts::F32LoadMem); + break; + default: + WASM_UNREACHABLE("invalid load size"); + } + break; + } + case Type::f64: + o << static_cast(BinaryConsts::F64LoadMem); + break; + case Type::v128: + o << static_cast(BinaryConsts::SIMDPrefix) + << U32LEB(BinaryConsts::V128Load); + break; + default: + WASM_UNREACHABLE("unexpected type"); + } +} + void BinaryInstWriter::visitLoop(Loop* curr) { breakStack.push_back(curr->name); o << static_cast(BinaryConsts::Loop); @@ -315,81 +383,13 @@ void BinaryInstWriter::visitGlobalSet(GlobalSet* curr) { } void BinaryInstWriter::visitLoad(Load* curr) { + if (curr->type == Type::unreachable) { + // the pointer is unreachable, so we are never reached; just don't emit + // a load + return; + } if (!curr->isAtomic()) { - switch (curr->type.getBasic()) { - case Type::i32: { - switch (curr->bytes) { - case 1: - o << static_cast(curr->signed_ - ? BinaryConsts::I32LoadMem8S - : BinaryConsts::I32LoadMem8U); - break; - case 2: - o << static_cast(curr->signed_ - ? BinaryConsts::I32LoadMem16S - : BinaryConsts::I32LoadMem16U); - break; - case 4: - o << static_cast(BinaryConsts::I32LoadMem); - break; - default: - abort(); - } - break; - } - case Type::i64: { - switch (curr->bytes) { - case 1: - o << static_cast(curr->signed_ - ? BinaryConsts::I64LoadMem8S - : BinaryConsts::I64LoadMem8U); - break; - case 2: - o << static_cast(curr->signed_ - ? BinaryConsts::I64LoadMem16S - : BinaryConsts::I64LoadMem16U); - break; - case 4: - o << static_cast(curr->signed_ - ? BinaryConsts::I64LoadMem32S - : BinaryConsts::I64LoadMem32U); - break; - case 8: - o << static_cast(BinaryConsts::I64LoadMem); - break; - default: - abort(); - } - break; - } - case Type::f32: { - switch (curr->bytes) { - case 2: - o << static_cast(BinaryConsts::MiscPrefix) - << U32LEB(BinaryConsts::F32_F16LoadMem); - break; - case 4: - o << static_cast(BinaryConsts::F32LoadMem); - break; - default: - WASM_UNREACHABLE("invalid load size"); - } - break; - } - case Type::f64: - o << static_cast(BinaryConsts::F64LoadMem); - break; - case Type::v128: - o << static_cast(BinaryConsts::SIMDPrefix) - << U32LEB(BinaryConsts::V128Load); - break; - case Type::unreachable: - // the pointer is unreachable, so we are never reached; just don't emit - // a load - return; - case Type::none: - WASM_UNREACHABLE("unexpected type"); - } + emitLoadOpcode(curr->bytes, curr->signed_, curr->type); } else { o << static_cast(BinaryConsts::AtomicPrefix); switch (curr->type.getBasic()) { @@ -428,8 +428,6 @@ void BinaryInstWriter::visitLoad(Load* curr) { } break; } - case Type::unreachable: - return; default: WASM_UNREACHABLE("unexpected type"); } @@ -2787,6 +2785,17 @@ void BinaryInstWriter::visitArraySet(ArraySet* curr) { parent.writeIndexedHeapType(curr->ref->type.getHeapType()); } +void BinaryInstWriter::visitArrayLoad(ArrayLoad* curr) { + if (curr->ref->type.isNull()) { + emitUnreachable(); + return; + } + emitLoadOpcode(curr->bytes, curr->signed_, curr->type); + uint32_t alignmentBits = BinaryConsts::HasBackingArrayMask; + o << U32LEB(alignmentBits); + parent.writeIndexedHeapType(curr->ref->type.getHeapType()); +} + void BinaryInstWriter::visitArrayStore(ArrayStore* curr) { if (curr->ref->type.isNull()) { emitUnreachable(); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index f6185e2f448..d5a90d3b6aa 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -555,6 +555,7 @@ struct FunctionValidator : public WalkerPass> { void visitArrayNewFixed(ArrayNewFixed* curr); void visitArrayGet(ArrayGet* curr); void visitArraySet(ArraySet* curr); + void visitArrayLoad(ArrayLoad* curr); void visitArrayStore(ArrayStore* curr); void visitArrayLen(ArrayLen* curr); void visitArrayCopy(ArrayCopy* curr); @@ -3817,6 +3818,31 @@ void FunctionValidator::visitArraySet(ArraySet* curr) { shouldBeTrue(element.mutable_, curr, "array.set type must be mutable"); } +void FunctionValidator::visitArrayLoad(ArrayLoad* curr) { + shouldBeTrue(getModule()->features.hasMultibyte(), + curr, + "array.load requires multibyte [--enable-multibyte]"); + shouldBeEqualOrFirstIsUnreachable(curr->index->type, + Type(Type::i32), + curr, + "array load index must be an i32"); + if (curr->type == Type::unreachable) { + return; + } + const char* mustBeArray = "array load 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 load type must be i8"); +} + void FunctionValidator::visitArrayStore(ArrayStore* curr) { shouldBeTrue(getModule()->features.hasMultibyte(), curr, diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index b9df7a232f6..536f33ae59f 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1337,6 +1337,12 @@ void ArrayGet::finalize() { } } +void ArrayLoad::finalize() { + if (ref->type == Type::unreachable || index->type == Type::unreachable) { + type = Type::unreachable; + } +} + void ArraySet::finalize() { if (ref->type == Type::unreachable || index->type == Type::unreachable || value->type == Type::unreachable) { diff --git a/src/wasm2js.h b/src/wasm2js.h index 3a0a5ea3302..e95fd53f968 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -2372,6 +2372,10 @@ Ref Wasm2JSBuilder::processExpression(Expression* curr, unimplemented(curr); WASM_UNREACHABLE("unimp"); } + Ref visitArrayLoad(ArrayLoad* curr) { + unimplemented(curr); + WASM_UNREACHABLE("unimp"); + } Ref visitArrayStore(ArrayStore* curr) { unimplemented(curr); WASM_UNREACHABLE("unimp"); diff --git a/test/lit/array-multibyte.wast b/test/lit/array-multibyte.wast index 1b56a338c54..eab1bc21f46 100644 --- a/test/lit/array-multibyte.wast +++ b/test/lit/array-multibyte.wast @@ -426,4 +426,586 @@ (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)) ) + ;; CHECK: (func $loads (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load8_u (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load8_s (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_u (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.load (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load8_u (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load16_u (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load32_u (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f64.load (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $loads (type $0) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.load8_u (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.load8_s (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.load16_u (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.load16_s (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.load (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (f32.load (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i64.load8_u (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i64.load16_u (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i64.load32_u (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i64.load (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (f64.load (type $i8_array) + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $loads + (drop (i32.load8_u (type $i8_array) (global.get $arr) (i32.const 1))) + (drop (i32.load8_s (type $i8_array) (global.get $arr) (i32.const 1))) + (drop (i32.load16_u (type $i8_array) (global.get $arr) (i32.const 1))) + (drop (i32.load16_s (type $i8_array) (global.get $arr) (i32.const 1))) + (drop (i32.load (type $i8_array) (global.get $arr) (i32.const 1))) + (drop (f32.load (type $i8_array) (global.get $arr) (i32.const 1))) + (drop (i64.load8_u (type $i8_array) (global.get $arr) (i32.const 1))) + (drop (i64.load16_u (type $i8_array) (global.get $arr) (i32.const 1))) + (drop (i64.load32_u (type $i8_array) (global.get $arr) (i32.const 1))) + (drop (i64.load (type $i8_array) (global.get $arr) (i32.const 1))) + (drop (f64.load (type $i8_array) (global.get $arr) (i32.const 1))) + ) + + ;; CHECK: (func $loads_null (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad 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: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad 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: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad 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: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad 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: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad 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: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad 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: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad 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: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad 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: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad 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: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad 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: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad 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: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $loads_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: (unreachable) + ;; RTRIP-NEXT: ) + ;; 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: (unreachable) + ;; RTRIP-NEXT: ) + ;; 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: (unreachable) + ;; RTRIP-NEXT: ) + ;; 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: (unreachable) + ;; RTRIP-NEXT: ) + ;; 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: (unreachable) + ;; RTRIP-NEXT: ) + ;; 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: (unreachable) + ;; RTRIP-NEXT: ) + ;; 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: (unreachable) + ;; RTRIP-NEXT: ) + ;; 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: (unreachable) + ;; RTRIP-NEXT: ) + ;; 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: (unreachable) + ;; RTRIP-NEXT: ) + ;; 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: (unreachable) + ;; RTRIP-NEXT: ) + ;; 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: (unreachable) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $loads_null + (drop (i32.load8_u (type $i8_array) (ref.null $i8_array) (i32.const 1))) + (drop (i32.load8_s (type $i8_array) (ref.null $i8_array) (i32.const 1))) + (drop (i32.load16_u (type $i8_array) (ref.null $i8_array) (i32.const 1))) + (drop (i32.load16_s (type $i8_array) (ref.null $i8_array) (i32.const 1))) + (drop (i32.load (type $i8_array) (ref.null $i8_array) (i32.const 1))) + (drop (f32.load (type $i8_array) (ref.null $i8_array) (i32.const 1))) + (drop (i64.load8_u (type $i8_array) (ref.null $i8_array) (i32.const 1))) + (drop (i64.load16_u (type $i8_array) (ref.null $i8_array) (i32.const 1))) + (drop (i64.load32_u (type $i8_array) (ref.null $i8_array) (i32.const 1))) + (drop (i64.load (type $i8_array) (ref.null $i8_array) (i32.const 1))) + (drop (f64.load (type $i8_array) (ref.null $i8_array) (i32.const 1))) + ) + + ;; CHECK: (func $loads_unreachable (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayLoad we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $loads_unreachable (type $0) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + (func $loads_unreachable + (drop (i32.load8_u (type $i8_array) (unreachable) (i32.const 1))) + (drop (i32.load8_s (type $i8_array) (unreachable) (i32.const 1))) + (drop (i32.load16_u (type $i8_array) (unreachable) (i32.const 1))) + (drop (i32.load16_s (type $i8_array) (unreachable) (i32.const 1))) + (drop (i32.load (type $i8_array) (unreachable) (i32.const 1))) + (drop (f32.load (type $i8_array) (unreachable) (i32.const 1))) + (drop (i64.load8_u (type $i8_array) (unreachable) (i32.const 1))) + (drop (i64.load16_u (type $i8_array) (unreachable) (i32.const 1))) + (drop (i64.load32_u (type $i8_array) (unreachable) (i32.const 1))) + (drop (i64.load (type $i8_array) (unreachable) (i32.const 1))) + (drop (f64.load (type $i8_array) (unreachable) (i32.const 1))) + ) + + ;; CHECK: (func $stores_index_unreachable (type $0) + ;; CHECK-NEXT: (i32.store8 (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $stores_index_unreachable (type $0) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + (func $stores_index_unreachable + (i32.store8 (type $i8_array) (global.get $arr) (unreachable) (i32.const 2)) + ) + + ;; CHECK: (func $stores_value_unreachable (type $0) + ;; CHECK-NEXT: (i32.store8 (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $stores_value_unreachable (type $0) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + (func $stores_value_unreachable + (i32.store8 (type $i8_array) (global.get $arr) (i32.const 1) (unreachable)) + ) + + ;; CHECK: (func $loads_index_unreachable (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load (type $i8_array) + ;; CHECK-NEXT: (global.get $arr) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $loads_index_unreachable (type $0) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (global.get $arr) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + (func $loads_index_unreachable + (drop (i32.load8_u (type $i8_array) (global.get $arr) (unreachable))) + ) ) diff --git a/test/spec/array-multibyte.wast b/test/spec/array-multibyte.wast index aedc8bc85b4..b38ed06be83 100644 --- a/test/spec/array-multibyte.wast +++ b/test/spec/array-multibyte.wast @@ -49,52 +49,6 @@ (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)) ) @@ -184,8 +138,7 @@ (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)) + (i32.load8_u (type $i8_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) @@ -194,8 +147,7 @@ (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)) + (i32.load16_u (type $i8_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) @@ -204,8 +156,7 @@ (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)) + (i32.load (type $i8_array) (global.get $arr_4) (i32.const 0)) ) (func $set_and_get_f32 (export "set_and_get_f32") (param $value f32) (result f32) @@ -214,8 +165,7 @@ (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)) + (i32.load (type $i8_array) (global.get $arr_4) (i32.const 0)) (f32.reinterpret_i32) ) @@ -225,8 +175,7 @@ (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)) + (i64.load8_u (type $i8_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) @@ -235,8 +184,7 @@ (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)) + (i64.load16_u (type $i8_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) @@ -245,8 +193,7 @@ (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)) + (i64.load32_u (type $i8_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) @@ -255,8 +202,7 @@ (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)) + (i64.load (type $i8_array) (global.get $arr_8) (i32.const 0)) ) (func $set_and_get_f64 (export "set_and_get_f64") (param $value f64) (result f64) @@ -265,10 +211,28 @@ (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)) + (i64.load (type $i8_array) (global.get $arr_8) (i32.const 0)) (f64.reinterpret_i64) ) + (func $load_i32_16_u (export "load_i32_16_u") (param $idx i32) (result i32) + (i32.load16_u (type $i8_array) (global.get $arr_4) (local.get $idx)) + ) + + (func $load_i32_16_s (export "load_i32_16_s") (param $idx i32) (result i32) + (i32.load16_s (type $i8_array) (global.get $arr_4) (local.get $idx)) + ) + + (func $load_i32 (export "load_i32") (param $idx i32) (result i32) + (i32.load (type $i8_array) (global.get $arr_4) (local.get $idx)) + ) + (func $load_i64 (export "load_i64") (param $idx i32) (result i64) + (i64.load (type $i8_array) (global.get $arr_8) (local.get $idx)) + ) + + (func $load_null (export "load_null") (result i32) + (local $null (ref null $i8_array)) + (i32.load (type $i8_array) (local.get $null) (i32.const 0)) + ) ) ;; @@ -380,6 +344,30 @@ (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)) +;; +;; Byte-wise load tests +;; + +(invoke "i32_set_i8" (i32.const 0) (i32.const 0x12)) +(invoke "i32_set_i8" (i32.const 1) (i32.const 0x34)) +(invoke "i32_set_i8" (i32.const 2) (i32.const 0x56)) +(invoke "i32_set_i8" (i32.const 3) (i32.const 0x78)) + +(assert_return (invoke "load_i32_16_u" (i32.const 0)) (i32.const 0x3412)) +(assert_return (invoke "load_i32_16_s" (i32.const 0)) (i32.const 0x3412)) + +;; Test sign extension +(invoke "i32_set_i8" (i32.const 0) (i32.const 0xFF)) +(invoke "i32_set_i8" (i32.const 1) (i32.const 0x7F)) +(assert_return (invoke "load_i32_16_u" (i32.const 0)) (i32.const 0x7FFF)) +(assert_return (invoke "load_i32_16_s" (i32.const 0)) (i32.const 0x7FFF)) + +(invoke "i32_set_i8" (i32.const 1) (i32.const 0xFF)) +(assert_return (invoke "load_i32_16_u" (i32.const 0)) (i32.const 0xFFFF)) +(assert_return (invoke "load_i32_16_s" (i32.const 0)) (i32.const -1)) + +(assert_return (invoke "load_i32" (i32.const 0)) (i32.const 0x7856FFFF)) + ;; ;; Bounds checks (32 bit with a 4-byte array) ;; @@ -488,6 +476,23 @@ "array element type must be i8" ) +;; New OOB Load Tests +(assert_trap (invoke "load_i32_16_u" (i32.const 3)) "out of bounds") +(assert_trap (invoke "load_i32" (i32.const 1)) "out of bounds") +(assert_trap (invoke "load_i64" (i32.const 1)) "out of bounds") + +;; Null reference for load +(assert_trap (invoke "load_null") "null array") + +;; Unaligned reads +(invoke "i32_set_i8" (i32.const 0) (i32.const 0x12)) +(invoke "i32_set_i8" (i32.const 1) (i32.const 0x34)) +(invoke "i32_set_i8" (i32.const 2) (i32.const 0x56)) +(invoke "i32_set_i8" (i32.const 3) (i32.const 0x78)) + +(assert_return (invoke "load_i32_16_u" (i32.const 1)) (i32.const 0x5634)) +(assert_return (invoke "load_i32_16_u" (i32.const 2)) (i32.const 0x7856)) + ;; Null dereference (module