diff options
33 files changed, 791 insertions, 160 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 1fa9d2ace..b72a81c13 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -181,6 +181,7 @@ def get_important_initial_contents(): os.path.join('lit', 'passes', 'optimize-instructions-gc-iit.wast'), os.path.join('lit', 'passes', 'optimize-instructions-call_ref.wast'), os.path.join('lit', 'passes', 'inlining_splitting.wast'), + os.path.join('heap-types.wast'), ] RECENT_DAYS = 30 diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 556f537e7..cc9ffc7c3 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -557,13 +557,18 @@ instructions = [ ("rtt.fresh_sub", "makeRttFreshSub(s)"), ("struct.new_with_rtt", "makeStructNew(s, false)"), ("struct.new_default_with_rtt", "makeStructNew(s, true)"), + ("struct.new", "makeStructNewStatic(s, false)"), + ("struct.new_default", "makeStructNewStatic(s, true)"), ("struct.get", "makeStructGet(s)"), ("struct.get_s", "makeStructGet(s, true)"), ("struct.get_u", "makeStructGet(s, false)"), ("struct.set", "makeStructSet(s)"), ("array.new_with_rtt", "makeArrayNew(s, false)"), ("array.new_default_with_rtt", "makeArrayNew(s, true)"), + ("array.new", "makeArrayNewStatic(s, false)"), + ("array.new_default", "makeArrayNewStatic(s, true)"), ("array.init", "makeArrayInit(s)"), + ("array.init_static", "makeArrayInitStatic(s)"), ("array.get", "makeArrayGet(s)"), ("array.get_s", "makeArrayGet(s, true)"), ("array.get_u", "makeArrayGet(s, false)"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index c7c521a21..69ce0dbbc 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -33,20 +33,44 @@ switch (op[0]) { default: goto parse_error; } } - case 'i': - if (strcmp(op, "array.init") == 0) { return makeArrayInit(s); } - goto parse_error; + case 'i': { + switch (op[10]) { + case '\0': + if (strcmp(op, "array.init") == 0) { return makeArrayInit(s); } + goto parse_error; + case '_': + if (strcmp(op, "array.init_static") == 0) { return makeArrayInitStatic(s); } + goto parse_error; + default: goto parse_error; + } + } case 'l': if (strcmp(op, "array.len") == 0) { return makeArrayLen(s); } goto parse_error; case 'n': { - switch (op[10]) { - case 'd': - if (strcmp(op, "array.new_default_with_rtt") == 0) { return makeArrayNew(s, true); } - goto parse_error; - case 'w': - if (strcmp(op, "array.new_with_rtt") == 0) { return makeArrayNew(s, false); } + switch (op[9]) { + case '\0': + if (strcmp(op, "array.new") == 0) { return makeArrayNewStatic(s, false); } goto parse_error; + case '_': { + switch (op[10]) { + case 'd': { + switch (op[17]) { + case '\0': + if (strcmp(op, "array.new_default") == 0) { return makeArrayNewStatic(s, true); } + goto parse_error; + case '_': + if (strcmp(op, "array.new_default_with_rtt") == 0) { return makeArrayNew(s, true); } + goto parse_error; + default: goto parse_error; + } + } + case 'w': + if (strcmp(op, "array.new_with_rtt") == 0) { return makeArrayNew(s, false); } + goto parse_error; + default: goto parse_error; + } + } default: goto parse_error; } } @@ -2980,13 +3004,29 @@ switch (op[0]) { } } case 'n': { - switch (op[11]) { - case 'd': - if (strcmp(op, "struct.new_default_with_rtt") == 0) { return makeStructNew(s, true); } - goto parse_error; - case 'w': - if (strcmp(op, "struct.new_with_rtt") == 0) { return makeStructNew(s, false); } + switch (op[10]) { + case '\0': + if (strcmp(op, "struct.new") == 0) { return makeStructNewStatic(s, false); } goto parse_error; + case '_': { + switch (op[11]) { + case 'd': { + switch (op[18]) { + case '\0': + if (strcmp(op, "struct.new_default") == 0) { return makeStructNewStatic(s, true); } + goto parse_error; + case '_': + if (strcmp(op, "struct.new_default_with_rtt") == 0) { return makeStructNew(s, true); } + goto parse_error; + default: goto parse_error; + } + } + case 'w': + if (strcmp(op, "struct.new_with_rtt") == 0) { return makeStructNew(s, false); } + goto parse_error; + default: goto parse_error; + } + } default: goto parse_error; } } diff --git a/src/ir/ExpressionAnalyzer.cpp b/src/ir/ExpressionAnalyzer.cpp index e98b14777..db972e707 100644 --- a/src/ir/ExpressionAnalyzer.cpp +++ b/src/ir/ExpressionAnalyzer.cpp @@ -194,6 +194,7 @@ bool ExpressionAnalyzer::flexibleEqual(Expression* left, #define DELEGATE_FIELD_NAME(id, name) COMPARE_FIELD(name) #define DELEGATE_FIELD_SIGNATURE(id, name) COMPARE_FIELD(name) #define DELEGATE_FIELD_TYPE(id, name) COMPARE_FIELD(name) +#define DELEGATE_FIELD_HEAPTYPE(id, name) COMPARE_FIELD(name) #define DELEGATE_FIELD_ADDRESS(id, name) COMPARE_FIELD(name) #define COMPARE_LIST(name) \ @@ -317,6 +318,7 @@ struct Hasher { #define DELEGATE_FIELD_NAME(id, name) visitNonScopeName(cast->name) #define DELEGATE_FIELD_TYPE(id, name) visitType(cast->name); +#define DELEGATE_FIELD_HEAPTYPE(id, name) visitHeapType(cast->name); #define DELEGATE_FIELD_ADDRESS(id, name) visitAddress(cast->name); // Note that we only note the scope name, but do not also visit it. That means @@ -359,6 +361,7 @@ struct Hasher { } void visitNonScopeName(Name curr) { rehash(digest, uint64_t(curr.str)); } void visitType(Type curr) { rehash(digest, curr.getID()); } + void visitHeapType(HeapType curr) { rehash(digest, curr.getID()); } void visitAddress(Address curr) { rehash(digest, curr.addr); } }; diff --git a/src/ir/ExpressionManipulator.cpp b/src/ir/ExpressionManipulator.cpp index 0bb94a66d..8ae3ddc1c 100644 --- a/src/ir/ExpressionManipulator.cpp +++ b/src/ir/ExpressionManipulator.cpp @@ -81,6 +81,7 @@ flexibleCopy(Expression* original, Module& wasm, CustomCopier custom) { #define DELEGATE_FIELD_SCOPE_NAME_USE(id, name) COPY_FIELD(name) #define DELEGATE_FIELD_SIGNATURE(id, name) COPY_FIELD(name) #define DELEGATE_FIELD_TYPE(id, name) COPY_FIELD(name) +#define DELEGATE_FIELD_HEAPTYPE(id, name) COPY_FIELD(name) #define DELEGATE_FIELD_ADDRESS(id, name) COPY_FIELD(name) #define COPY_FIELD_LIST(name) \ diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h index 54a3f1c16..f2fa43f18 100644 --- a/src/ir/branch-utils.h +++ b/src/ir/branch-utils.h @@ -62,6 +62,7 @@ template<typename T> void operateOnScopeNameUses(Expression* expr, T func) { #define DELEGATE_FIELD_SCOPE_NAME_DEF(id, name) #define DELEGATE_FIELD_SIGNATURE(id, name) #define DELEGATE_FIELD_TYPE(id, name) +#define DELEGATE_FIELD_HEAPTYPE(id, name) #define DELEGATE_FIELD_ADDRESS(id, name) #define DELEGATE_FIELD_CHILD_VECTOR(id, name) #define DELEGATE_FIELD_INT_ARRAY(id, name) @@ -125,6 +126,7 @@ template<typename T> void operateOnScopeNameDefs(Expression* expr, T func) { #define DELEGATE_FIELD_NAME_VECTOR(id, name) #define DELEGATE_FIELD_SIGNATURE(id, name) #define DELEGATE_FIELD_TYPE(id, name) +#define DELEGATE_FIELD_HEAPTYPE(id, name) #define DELEGATE_FIELD_ADDRESS(id, name) #define DELEGATE_FIELD_CHILD_VECTOR(id, name) #define DELEGATE_FIELD_INT_ARRAY(id, name) diff --git a/src/ir/cost.h b/src/ir/cost.h index 086b7b9af..29fcb8dd4 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -593,7 +593,7 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> { // at least some baseline cost, plus writing the fields. (If we use default // values for the fields, then it is possible they are all 0 and if so, we // can get that almost for free as well, so don't add anything there.) - CostType ret = 4 + visit(curr->rtt) + curr->operands.size(); + CostType ret = 4 + maybeVisit(curr->rtt) + curr->operands.size(); for (auto* child : curr->operands) { ret += visit(child); } @@ -606,10 +606,11 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> { return 2 + nullCheckCost(curr->ref) + visit(curr->ref) + visit(curr->value); } CostType visitArrayNew(ArrayNew* curr) { - return 4 + visit(curr->rtt) + visit(curr->size) + maybeVisit(curr->init); + return 4 + maybeVisit(curr->rtt) + visit(curr->size) + + maybeVisit(curr->init); } CostType visitArrayInit(ArrayInit* curr) { - CostType ret = 4 + visit(curr->rtt); + CostType ret = 4 + maybeVisit(curr->rtt); for (auto* child : curr->values) { ret += visit(child); } diff --git a/src/ir/iteration.h b/src/ir/iteration.h index 3de6d1435..19bc44af1 100644 --- a/src/ir/iteration.h +++ b/src/ir/iteration.h @@ -98,6 +98,7 @@ public: #define DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(id, name) #define DELEGATE_FIELD_SIGNATURE(id, name) #define DELEGATE_FIELD_TYPE(id, name) +#define DELEGATE_FIELD_HEAPTYPE(id, name) #define DELEGATE_FIELD_ADDRESS(id, name) #include "wasm-delegations-fields.def" diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index d191a35f1..5d23278fa 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -497,6 +497,34 @@ inline void collectHeapTypes(Module& wasm, counts.note(curr->type); } else if (curr->is<RttCanon>() || curr->is<RttSub>()) { counts.note(curr->type.getRtt().heapType); + } else if (auto* make = curr->dynCast<StructNew>()) { + // Some operations emit a HeapType in the binary format, if they are + // static and not dynamic (if dynamic, the RTT provides the heap type). + if (!make->rtt && make->type != Type::unreachable) { + counts.note(make->type.getHeapType()); + } + } else if (auto* make = curr->dynCast<ArrayNew>()) { + if (!make->rtt && make->type != Type::unreachable) { + counts.note(make->type.getHeapType()); + } + } else if (auto* make = curr->dynCast<ArrayInit>()) { + if (!make->rtt && make->type != Type::unreachable) { + counts.note(make->type.getHeapType()); + } + } else if (auto* cast = curr->dynCast<RefCast>()) { + if (!cast->rtt && cast->type != Type::unreachable) { + counts.note(cast->getIntendedType()); + } + } else if (auto* cast = curr->dynCast<RefTest>()) { + if (!cast->rtt && cast->type != Type::unreachable) { + counts.note(cast->getIntendedType()); + } + } else if (auto* cast = curr->dynCast<BrOn>()) { + if (cast->op == BrOnCast || cast->op == BrOnCastFail) { + if (!cast->rtt && cast->type != Type::unreachable) { + counts.note(cast->getIntendedType()); + } + } } else if (auto* get = curr->dynCast<StructGet>()) { counts.note(get->ref->type); } else if (auto* set = curr->dynCast<StructSet>()) { diff --git a/src/ir/properties.h b/src/ir/properties.h index f3cab1fdc..841d154f7 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -340,6 +340,7 @@ inline Index getNumChildren(Expression* curr) { #define DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(id, name) #define DELEGATE_FIELD_SIGNATURE(id, name) #define DELEGATE_FIELD_TYPE(id, name) +#define DELEGATE_FIELD_HEAPTYPE(id, name) #define DELEGATE_FIELD_ADDRESS(id, name) #include "wasm-delegations-fields.def" diff --git a/src/literal.h b/src/literal.h index f7ce78f7a..6f2267752 100644 --- a/src/literal.h +++ b/src/literal.h @@ -61,6 +61,8 @@ class Literal { // as the Literal class itself. // To support the experimental RttFreshSub instruction, we not only store // the type, but also a reference to an allocation. + // The above describes dynamic data, that is with an actual RTT. The static + // case just has a static type in its GCData. // See struct RttSuper below for more details. std::unique_ptr<RttSupers> rttSupers; // TODO: Literals of type `externref` can only be `null` currently but we @@ -694,10 +696,16 @@ std::ostream& operator<<(std::ostream& o, wasm::Literal literal); std::ostream& operator<<(std::ostream& o, wasm::Literals literals); // A GC Struct or Array is a set of values with a run-time type saying what it -// is. +// is. In the case of static (rtt-free) typing, the rtt is not present and +// instead we have a static type. struct GCData { + // Either the RTT or the type must be present, but not both. Literal rtt; + HeapType type; + Literals values; + + GCData(HeapType type, Literals values) : type(type), values(values) {} GCData(Literal rtt, Literals values) : rtt(rtt), values(values) {} }; diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index f2846671c..796e0fa05 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -402,7 +402,9 @@ struct Heap2LocalOptimizer { } // Drop the RTT (as it may have side effects; leave it to other passes). - contents.push_back(builder.makeDrop(allocation->rtt)); + if (allocation->rtt) { + contents.push_back(builder.makeDrop(allocation->rtt)); + } // Replace the allocation with a null reference. This changes the type // from non-nullable to nullable, but as we optimize away the code that // the allocation reaches, we will handle that. diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 6198218d8..6a41961a2 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -1985,15 +1985,18 @@ struct PrintExpressionContents } void visitStructNew(StructNew* curr) { - if (printUnreachableReplacement(curr->rtt)) { + if (printUnreachableReplacement(curr)) { return; } - printMedium(o, "struct.new_"); + printMedium(o, "struct.new"); if (curr->isWithDefault()) { - o << "default_"; + printMedium(o, "_default"); } - o << "with_rtt "; - TypeNamePrinter(o, wasm).print(curr->rtt->type.getHeapType()); + if (curr->rtt) { + printMedium(o, "_with_rtt"); + } + o << ' '; + TypeNamePrinter(o, wasm).print(curr->type.getHeapType()); } void printFieldName(HeapType type, Index index) { @@ -2035,16 +2038,29 @@ struct PrintExpressionContents printFieldName(heapType, curr->index); } void visitArrayNew(ArrayNew* curr) { - printMedium(o, "array.new_"); + if (printUnreachableReplacement(curr)) { + return; + } + printMedium(o, "array.new"); if (curr->isWithDefault()) { - o << "default_"; + printMedium(o, "_default"); + } + if (curr->rtt) { + printMedium(o, "_with_rtt"); } - o << "with_rtt "; - TypeNamePrinter(o, wasm).print(curr->rtt->type.getHeapType()); + o << ' '; + TypeNamePrinter(o, wasm).print(curr->type.getHeapType()); } void visitArrayInit(ArrayInit* curr) { - printMedium(o, "array.init "); - TypeNamePrinter(o, wasm).print(curr->rtt->type.getHeapType()); + if (printUnreachableReplacement(curr)) { + return; + } + printMedium(o, "array.init"); + if (!curr->rtt) { + printMedium(o, "_static"); + } + o << ' '; + TypeNamePrinter(o, wasm).print(curr->type.getHeapType()); } void visitArrayGet(ArrayGet* curr) { if (printUnreachableReplacement(curr->ref)) { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 0e4b4b873..057a2a981 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1052,6 +1052,8 @@ enum ASTNodes { StructGetS = 0x04, StructGetU = 0x05, StructSet = 0x06, + StructNew = 0x07, + StructNewDefault = 0x08, ArrayNewWithRtt = 0x11, ArrayNewDefaultWithRtt = 0x12, ArrayGet = 0x13, @@ -1061,6 +1063,9 @@ enum ASTNodes { ArrayLen = 0x17, ArrayCopy = 0x18, ArrayInit = 0x19, + ArrayInitStatic = 0x1a, + ArrayNew = 0x1b, + ArrayNewDefault = 0x1c, I31New = 0x20, I31GetS = 0x21, I31GetU = 0x22, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 346f2f5ba..f992b1e03 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -836,6 +836,13 @@ public: ret->finalize(); return ret; } + template<typename T> StructNew* makeStructNew(HeapType type, const T& args) { + auto* ret = wasm.allocator.alloc<StructNew>(); + ret->operands.set(args); + ret->type = Type(type, NonNullable); + ret->finalize(); + return ret; + } StructGet* makeStructGet(Index index, Expression* ref, Type type, bool signed_ = false) { auto* ret = wasm.allocator.alloc<StructGet>(); @@ -863,6 +870,15 @@ public: ret->finalize(); return ret; } + ArrayNew* + makeArrayNew(HeapType type, Expression* size, Expression* init = nullptr) { + auto* ret = wasm.allocator.alloc<ArrayNew>(); + ret->size = size; + ret->init = init; + ret->type = Type(type, NonNullable); + ret->finalize(); + return ret; + } ArrayInit* makeArrayInit(Expression* rtt, const std::vector<Expression*>& values) { auto* ret = wasm.allocator.alloc<ArrayInit>(); @@ -871,6 +887,14 @@ public: ret->finalize(); return ret; } + ArrayInit* makeArrayInit(HeapType type, + const std::vector<Expression*>& values) { + auto* ret = wasm.allocator.alloc<ArrayInit>(); + ret->values.set(values); + ret->type = Type(type, NonNullable); + ret->finalize(); + return ret; + } ArrayGet* makeArrayGet(Expression* ref, Expression* index, bool signed_ = false) { auto* ret = wasm.allocator.alloc<ArrayGet>(); diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 459f80afb..f5524d181 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -614,7 +614,7 @@ switch (DELEGATE_ID) { } case Expression::Id::StructNewId: { DELEGATE_START(StructNew); - DELEGATE_FIELD_CHILD(StructNew, rtt); + DELEGATE_FIELD_OPTIONAL_CHILD(StructNew, rtt); DELEGATE_FIELD_CHILD_VECTOR(StructNew, operands); DELEGATE_END(StructNew); break; @@ -637,7 +637,7 @@ switch (DELEGATE_ID) { } case Expression::Id::ArrayNewId: { DELEGATE_START(ArrayNew); - DELEGATE_FIELD_CHILD(ArrayNew, rtt); + DELEGATE_FIELD_OPTIONAL_CHILD(ArrayNew, rtt); DELEGATE_FIELD_CHILD(ArrayNew, size); DELEGATE_FIELD_OPTIONAL_CHILD(ArrayNew, init); DELEGATE_END(ArrayNew); @@ -645,7 +645,7 @@ switch (DELEGATE_ID) { } case Expression::Id::ArrayInitId: { DELEGATE_START(ArrayInit); - DELEGATE_FIELD_CHILD(ArrayInit, rtt); + DELEGATE_FIELD_OPTIONAL_CHILD(ArrayInit, rtt); DELEGATE_FIELD_CHILD_VECTOR(ArrayInit, values); DELEGATE_END(ArrayInit); break; @@ -707,5 +707,6 @@ switch (DELEGATE_ID) { #undef DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR #undef DELEGATE_FIELD_SIGNATURE #undef DELEGATE_FIELD_TYPE +#undef DELEGATE_FIELD_HEAPTYPE #undef DELEGATE_FIELD_ADDRESS #undef DELEGATE_GET_FIELD diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 53d3048b7..4e2d40e08 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1483,8 +1483,8 @@ public: // GC data store an RTT in each instance. assert(cast.originalRef.isData()); auto gcData = cast.originalRef.getGCData(); - Literal seenRtt = gcData->rtt; if (curr->rtt) { + Literal seenRtt = gcData->rtt; if (!seenRtt.isSubRtt(intendedRtt)) { cast.outcome = cast.Failure; return cast; @@ -1492,7 +1492,7 @@ public: cast.castRef = Literal(gcData, Type(intendedRtt.type.getHeapType(), NonNullable)); } else { - auto seenType = seenRtt.type.getHeapType(); + auto seenType = gcData->type; if (!HeapType::isSubType(seenType, curr->intendedType)) { cast.outcome = cast.Failure; return cast; @@ -1628,13 +1628,42 @@ public: } return Literal(std::move(newSupers), curr->type); } + + // Generates GC data for either dynamic (with an RTT) or static (with a type) + // typing. Dynamic typing will provide an rtt expression and an rtt flow with + // the value, while static typing only provides a heap type directly. + template<typename T> + std::shared_ptr<GCData> + makeGCData(Expression* rttExpr, Flow& rttFlow, HeapType type, T& data) { + if (rttExpr) { + return std::make_shared<GCData>(rttFlow.getSingleValue(), data); + } else { + return std::make_shared<GCData>(type, data); + } + } + Flow visitStructNew(StructNew* curr) { NOTE_ENTER("StructNew"); - auto rtt = this->visit(curr->rtt); - if (rtt.breaking()) { - return rtt; + Flow rtt; + if (curr->rtt) { + rtt = this->visit(curr->rtt); + if (rtt.breaking()) { + return rtt; + } + } + if (curr->type == Type::unreachable) { + // We cannot proceed to compute the heap type, as there isn't one. Just + // find why we are unreachable, and stop there. + for (auto* operand : curr->operands) { + auto value = this->visit(operand); + if (value.breaking()) { + return value; + } + } + WASM_UNREACHABLE("unreachable but no unreachable child"); } - const auto& fields = curr->rtt->type.getHeapType().getStruct().fields; + auto heapType = curr->type.getHeapType(); + const auto& fields = heapType.getStruct().fields; Literals data(fields.size()); for (Index i = 0; i < fields.size(); i++) { if (curr->isWithDefault()) { @@ -1647,8 +1676,8 @@ public: data[i] = value.getSingleValue(); } } - return Flow(Literal(std::make_shared<GCData>(rtt.getSingleValue(), data), - curr->type)); + return Flow( + Literal(makeGCData(curr->rtt, rtt, heapType, data), curr->type)); } Flow visitStructGet(StructGet* curr) { NOTE_ENTER("StructGet"); @@ -1691,15 +1720,26 @@ public: Flow visitArrayNew(ArrayNew* curr) { NOTE_ENTER("ArrayNew"); - auto rtt = this->visit(curr->rtt); - if (rtt.breaking()) { - return rtt; + Flow rtt; + if (curr->rtt) { + rtt = this->visit(curr->rtt); + if (rtt.breaking()) { + return rtt; + } } auto size = this->visit(curr->size); if (size.breaking()) { return size; } - const auto& element = curr->rtt->type.getHeapType().getArray().element; + if (curr->type == Type::unreachable) { + // We cannot proceed to compute the heap type, as there isn't one. Just + // visit the unreachable child, and stop there. + auto init = this->visit(curr->init); + assert(init.breaking()); + return init; + } + auto heapType = curr->type.getHeapType(); + const auto& element = heapType.getArray().element; Index num = size.getSingleValue().geti32(); if (num >= ArrayLimit) { hostLimit("allocation failure"); @@ -1720,20 +1760,35 @@ public: data[i] = value; } } - return Flow(Literal(std::make_shared<GCData>(rtt.getSingleValue(), data), - curr->type)); + return Flow( + Literal(makeGCData(curr->rtt, rtt, heapType, data), curr->type)); } Flow visitArrayInit(ArrayInit* curr) { NOTE_ENTER("ArrayInit"); - auto rtt = this->visit(curr->rtt); - if (rtt.breaking()) { - return rtt; + Flow rtt; + if (curr->rtt) { + rtt = this->visit(curr->rtt); + if (rtt.breaking()) { + return rtt; + } } Index num = curr->values.size(); if (num >= ArrayLimit) { hostLimit("allocation failure"); } - auto field = curr->type.getHeapType().getArray().element; + if (curr->type == Type::unreachable) { + // We cannot proceed to compute the heap type, as there isn't one. Just + // find why we are unreachable, and stop there. + for (auto* value : curr->values) { + auto result = this->visit(value); + if (result.breaking()) { + return result; + } + } + WASM_UNREACHABLE("unreachable but no unreachable child"); + } + auto heapType = curr->type.getHeapType(); + auto field = heapType.getArray().element; Literals data(num); for (Index i = 0; i < num; i++) { auto value = this->visit(curr->values[i]); @@ -1742,8 +1797,8 @@ public: } data[i] = truncateForPacking(value.getSingleValue(), field); } - return Flow(Literal(std::make_shared<GCData>(rtt.getSingleValue(), data), - curr->type)); + return Flow( + Literal(makeGCData(curr->rtt, rtt, heapType, data), curr->type)); } Flow visitArrayGet(ArrayGet* curr) { NOTE_ENTER("ArrayGet"); diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index f195314b1..ef677cbf7 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -284,11 +284,14 @@ private: Expression* makeRttSub(Element& s); Expression* makeRttFreshSub(Element& s); Expression* makeStructNew(Element& s, bool default_); + Expression* makeStructNewStatic(Element& s, bool default_); Index getStructIndex(Element& type, Element& field); Expression* makeStructGet(Element& s, bool signed_ = false); Expression* makeStructSet(Element& s); Expression* makeArrayNew(Element& s, bool default_); + Expression* makeArrayNewStatic(Element& s, bool default_); Expression* makeArrayInit(Element& s); + Expression* makeArrayInitStatic(Element& s); Expression* makeArrayGet(Element& s, bool signed_ = false); Expression* makeArraySet(Element& s); Expression* makeArrayLen(Element& s); diff --git a/src/wasm.h b/src/wasm.h index ffc97f71e..9c6062e48 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1438,7 +1438,10 @@ class StructNew : public SpecificExpression<Expression::StructNewId> { public: StructNew(MixedArena& allocator) : operands(allocator) {} - Expression* rtt; + // A dynamic StructNew has an rtt, while a static one declares the type using + // the type field. + Expression* rtt = nullptr; + // A struct.new_with_default has empty operands. This does leave the case of a // struct with no fields ambiguous, but it doesn't make a difference in that // case, and binaryen doesn't guarantee roundtripping binaries anyhow. @@ -1481,7 +1484,10 @@ public: // used. Expression* init = nullptr; Expression* size; - Expression* rtt; + + // A dynamic ArrayNew has an rtt, while a static one declares the type using + // the type field. + Expression* rtt = nullptr; bool isWithDefault() { return !init; } @@ -1493,7 +1499,10 @@ public: ArrayInit(MixedArena& allocator) : values(allocator) {} ExpressionList values; - Expression* rtt; + + // A dynamic ArrayInit has an rtt, while a static one declares the type using + // the type field. + Expression* rtt = nullptr; void finalize(); }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index e1ff43988..74895c868 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -6527,23 +6527,36 @@ bool WasmBinaryBuilder::maybeVisitRttSub(Expression*& out, uint32_t code) { } bool WasmBinaryBuilder::maybeVisitStructNew(Expression*& out, uint32_t code) { - if (code != BinaryConsts::StructNewWithRtt && - code != BinaryConsts::StructNewDefaultWithRtt) { - return false; - } - auto heapType = getIndexedHeapType(); - auto* rtt = popNonVoidExpression(); - validateHeapTypeUsingChild(rtt, heapType); - std::vector<Expression*> operands; - if (code == BinaryConsts::StructNewWithRtt) { - auto numOperands = heapType.getStruct().fields.size(); - operands.resize(numOperands); - for (Index i = 0; i < numOperands; i++) { - operands[numOperands - i - 1] = popNonVoidExpression(); + if (code == BinaryConsts::StructNew || + code == BinaryConsts::StructNewDefault) { + auto heapType = getIndexedHeapType(); + std::vector<Expression*> operands; + if (code == BinaryConsts::StructNew) { + auto numOperands = heapType.getStruct().fields.size(); + operands.resize(numOperands); + for (Index i = 0; i < numOperands; i++) { + operands[numOperands - i - 1] = popNonVoidExpression(); + } + } + out = Builder(wasm).makeStructNew(heapType, operands); + return true; + } else if (code == BinaryConsts::StructNewWithRtt || + code == BinaryConsts::StructNewDefaultWithRtt) { + auto heapType = getIndexedHeapType(); + auto* rtt = popNonVoidExpression(); + validateHeapTypeUsingChild(rtt, heapType); + std::vector<Expression*> operands; + if (code == BinaryConsts::StructNewWithRtt) { + auto numOperands = heapType.getStruct().fields.size(); + operands.resize(numOperands); + for (Index i = 0; i < numOperands; i++) { + operands[numOperands - i - 1] = popNonVoidExpression(); + } } + out = Builder(wasm).makeStructNew(rtt, operands); + return true; } - out = Builder(wasm).makeStructNew(rtt, operands); - return true; + return false; } bool WasmBinaryBuilder::maybeVisitStructGet(Expression*& out, uint32_t code) { @@ -6588,36 +6601,54 @@ bool WasmBinaryBuilder::maybeVisitStructSet(Expression*& out, uint32_t code) { } bool WasmBinaryBuilder::maybeVisitArrayNew(Expression*& out, uint32_t code) { - if (code != BinaryConsts::ArrayNewWithRtt && - code != BinaryConsts::ArrayNewDefaultWithRtt) { - return false; + if (code == BinaryConsts::ArrayNew || code == BinaryConsts::ArrayNewDefault) { + auto heapType = getIndexedHeapType(); + auto* size = popNonVoidExpression(); + Expression* init = nullptr; + if (code == BinaryConsts::ArrayNew) { + init = popNonVoidExpression(); + } + out = Builder(wasm).makeArrayNew(heapType, size, init); + return true; + } else if (code == BinaryConsts::ArrayNewWithRtt || + code == BinaryConsts::ArrayNewDefaultWithRtt) { + auto heapType = getIndexedHeapType(); + auto* rtt = popNonVoidExpression(); + validateHeapTypeUsingChild(rtt, heapType); + auto* size = popNonVoidExpression(); + Expression* init = nullptr; + if (code == BinaryConsts::ArrayNewWithRtt) { + init = popNonVoidExpression(); + } + out = Builder(wasm).makeArrayNew(rtt, size, init); + return true; } - auto heapType = getIndexedHeapType(); - auto* rtt = popNonVoidExpression(); - validateHeapTypeUsingChild(rtt, heapType); - auto* size = popNonVoidExpression(); - Expression* init = nullptr; - if (code == BinaryConsts::ArrayNewWithRtt) { - init = popNonVoidExpression(); - } - out = Builder(wasm).makeArrayNew(rtt, size, init); - return true; + return false; } bool WasmBinaryBuilder::maybeVisitArrayInit(Expression*& out, uint32_t code) { - if (code != BinaryConsts::ArrayInit) { - return false; - } - auto heapType = getIndexedHeapType(); - auto size = getU32LEB(); - auto* rtt = popNonVoidExpression(); - validateHeapTypeUsingChild(rtt, heapType); - std::vector<Expression*> values(size); - for (size_t i = 0; i < size; i++) { - values[size - i - 1] = popNonVoidExpression(); + if (code == BinaryConsts::ArrayInitStatic) { + auto heapType = getIndexedHeapType(); + auto size = getU32LEB(); + std::vector<Expression*> values(size); + for (size_t i = 0; i < size; i++) { + values[size - i - 1] = popNonVoidExpression(); + } + out = Builder(wasm).makeArrayInit(heapType, values); + return true; + } else if (code == BinaryConsts::ArrayInit) { + auto heapType = getIndexedHeapType(); + auto size = getU32LEB(); + auto* rtt = popNonVoidExpression(); + validateHeapTypeUsingChild(rtt, heapType); + std::vector<Expression*> values(size); + for (size_t i = 0; i < size; i++) { + values[size - i - 1] = popNonVoidExpression(); + } + out = Builder(wasm).makeArrayInit(rtt, values); + return true; } - out = Builder(wasm).makeArrayInit(rtt, values); - return true; + return false; } bool WasmBinaryBuilder::maybeVisitArrayGet(Expression*& out, uint32_t code) { diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index 697e4e58d..e9ea6aaa5 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -2652,6 +2652,21 @@ Expression* SExpressionWasmBuilder::makeStructNew(Element& s, bool default_) { return Builder(wasm).makeStructNew(rtt, operands); } +Expression* SExpressionWasmBuilder::makeStructNewStatic(Element& s, + bool default_) { + auto heapType = parseHeapType(*s[1]); + auto numOperands = s.size() - 2; + if (default_ && numOperands > 0) { + throw ParseException("arguments provided for struct.new", s.line, s.col); + } + std::vector<Expression*> operands; + operands.resize(numOperands); + for (Index i = 0; i < numOperands; i++) { + operands[i] = parseExpression(*s[i + 2]); + } + return Builder(wasm).makeStructNew(heapType, operands); +} + Index SExpressionWasmBuilder::getStructIndex(Element& type, Element& field) { if (field.dollared()) { auto name = field.str(); @@ -2708,6 +2723,18 @@ Expression* SExpressionWasmBuilder::makeArrayNew(Element& s, bool default_) { return Builder(wasm).makeArrayNew(rtt, size, init); } +Expression* SExpressionWasmBuilder::makeArrayNewStatic(Element& s, + bool default_) { + auto heapType = parseHeapType(*s[1]); + Expression* init = nullptr; + size_t i = 2; + if (!default_) { + init = parseExpression(*s[i++]); + } + auto* size = parseExpression(*s[i++]); + return Builder(wasm).makeArrayNew(heapType, size, init); +} + Expression* SExpressionWasmBuilder::makeArrayInit(Element& s) { auto heapType = parseHeapType(*s[1]); size_t i = 2; @@ -2720,6 +2747,16 @@ Expression* SExpressionWasmBuilder::makeArrayInit(Element& s) { return Builder(wasm).makeArrayInit(rtt, values); } +Expression* SExpressionWasmBuilder::makeArrayInitStatic(Element& s) { + auto heapType = parseHeapType(*s[1]); + size_t i = 2; + std::vector<Expression*> values; + while (i < s.size()) { + values.push_back(parseExpression(*s[i++])); + } + return Builder(wasm).makeArrayInit(heapType, values); +} + Expression* SExpressionWasmBuilder::makeArrayGet(Element& s, bool signed_) { auto heapType = parseHeapType(*s[1]); auto ref = parseExpression(*s[2]); diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index b6424cdde..70b52cfbb 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2025,12 +2025,20 @@ void BinaryInstWriter::visitRttSub(RttSub* curr) { void BinaryInstWriter::visitStructNew(StructNew* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->isWithDefault()) { - o << U32LEB(BinaryConsts::StructNewDefaultWithRtt); + if (curr->rtt) { + if (curr->isWithDefault()) { + o << U32LEB(BinaryConsts::StructNewDefaultWithRtt); + } else { + o << U32LEB(BinaryConsts::StructNewWithRtt); + } } else { - o << U32LEB(BinaryConsts::StructNewWithRtt); + if (curr->isWithDefault()) { + o << U32LEB(BinaryConsts::StructNewDefault); + } else { + o << U32LEB(BinaryConsts::StructNew); + } } - parent.writeIndexedHeapType(curr->rtt->type.getHeapType()); + parent.writeIndexedHeapType(curr->type.getHeapType()); } void BinaryInstWriter::visitStructGet(StructGet* curr) { @@ -2057,17 +2065,30 @@ void BinaryInstWriter::visitStructSet(StructSet* curr) { void BinaryInstWriter::visitArrayNew(ArrayNew* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->isWithDefault()) { - o << U32LEB(BinaryConsts::ArrayNewDefaultWithRtt); + if (curr->rtt) { + if (curr->isWithDefault()) { + o << U32LEB(BinaryConsts::ArrayNewDefaultWithRtt); + } else { + o << U32LEB(BinaryConsts::ArrayNewWithRtt); + } } else { - o << U32LEB(BinaryConsts::ArrayNewWithRtt); + if (curr->isWithDefault()) { + o << U32LEB(BinaryConsts::ArrayNewDefault); + } else { + o << U32LEB(BinaryConsts::ArrayNew); + } } - parent.writeIndexedHeapType(curr->rtt->type.getHeapType()); + parent.writeIndexedHeapType(curr->type.getHeapType()); } void BinaryInstWriter::visitArrayInit(ArrayInit* curr) { - o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::ArrayInit); - parent.writeIndexedHeapType(curr->rtt->type.getHeapType()); + o << int8_t(BinaryConsts::GCPrefix); + if (curr->rtt) { + o << U32LEB(BinaryConsts::ArrayInit); + } else { + o << U32LEB(BinaryConsts::ArrayInitStatic); + } + parent.writeIndexedHeapType(curr->type.getHeapType()); o << U32LEB(curr->values.size()); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 1f6421609..d03fc2446 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2332,11 +2332,19 @@ void FunctionValidator::visitStructNew(StructNew* curr) { if (curr->type == Type::unreachable) { return; } - if (!shouldBeTrue( - curr->rtt->type.isRtt(), curr, "struct.new rtt must be rtt")) { - return; + if (curr->rtt) { + if (!shouldBeTrue( + curr->rtt->type.isRtt(), curr, "struct.new rtt must be rtt")) { + return; + } + } + auto heapType = curr->type.getHeapType(); + if (curr->rtt) { + shouldBeEqual(curr->rtt->type.getHeapType(), + heapType, + curr, + "struct.new heap type must match rtt"); } - auto heapType = curr->rtt->type.getHeapType(); if (!shouldBeTrue( heapType.isStruct(), curr, "struct.new heap type must be struct")) { return; @@ -2428,11 +2436,19 @@ void FunctionValidator::visitArrayNew(ArrayNew* curr) { if (curr->type == Type::unreachable) { return; } - if (!shouldBeTrue( - curr->rtt->type.isRtt(), curr, "array.new rtt must be rtt")) { - return; + if (curr->rtt) { + if (!shouldBeTrue( + curr->rtt->type.isRtt(), curr, "array.new rtt must be rtt")) { + return; + } + } + auto heapType = curr->type.getHeapType(); + if (curr->rtt) { + shouldBeEqual(curr->rtt->type.getHeapType(), + heapType, + curr, + "array.new heap type must match rtt"); } - auto heapType = curr->rtt->type.getHeapType(); if (!shouldBeTrue( heapType.isArray(), curr, "array.new heap type must be array")) { return; @@ -2462,11 +2478,19 @@ void FunctionValidator::visitArrayInit(ArrayInit* curr) { if (curr->type == Type::unreachable) { return; } - if (!shouldBeTrue( - curr->rtt->type.isRtt(), curr, "array.init rtt must be rtt")) { - return; + if (curr->rtt) { + if (!shouldBeTrue( + curr->rtt->type.isRtt(), curr, "array.init rtt must be rtt")) { + return; + } + } + auto heapType = curr->type.getHeapType(); + if (curr->rtt) { + shouldBeEqual(curr->rtt->type.getHeapType(), + heapType, + curr, + "array.init heap type must match rtt"); } - auto heapType = curr->rtt->type.getHeapType(); if (!shouldBeTrue( heapType.isArray(), curr, "array.init heap type must be array")) { return; diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 0f1017e00..792e43cbe 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1018,14 +1018,18 @@ void RttSub::finalize() { } void StructNew::finalize() { - if (rtt->type == Type::unreachable) { + if (rtt && rtt->type == Type::unreachable) { type = Type::unreachable; return; } if (handleUnreachableOperands(this)) { return; } - type = Type(rtt->type.getHeapType(), NonNullable); + // A dynamic StructNew infers the type from the rtt. A static one has the type + // already in the type field. + if (rtt) { + type = Type(rtt->type.getHeapType(), NonNullable); + } } void StructGet::finalize() { @@ -1045,16 +1049,21 @@ void StructSet::finalize() { } void ArrayNew::finalize() { - if (rtt->type == Type::unreachable || size->type == Type::unreachable || + if ((rtt && rtt->type == Type::unreachable) || + size->type == Type::unreachable || (init && init->type == Type::unreachable)) { type = Type::unreachable; return; } - type = Type(rtt->type.getHeapType(), NonNullable); + // A dynamic ArrayNew infers the type from the rtt. A static one has the type + // already in the type field. + if (rtt) { + type = Type(rtt->type.getHeapType(), NonNullable); + } } void ArrayInit::finalize() { - if (rtt->type == Type::unreachable) { + if (rtt && rtt->type == Type::unreachable) { type = Type::unreachable; return; } @@ -1064,7 +1073,11 @@ void ArrayInit::finalize() { return; } } - type = Type(rtt->type.getHeapType(), NonNullable); + // A dynamic ArrayInit infers the type from the rtt. A static one has the type + // already in the type field. + if (rtt) { + type = Type(rtt->type.getHeapType(), NonNullable); + } } void ArrayGet::finalize() { diff --git a/test/heap-types.wast b/test/heap-types.wast index be6da5330..3c0a5a0ef 100644 --- a/test/heap-types.wast +++ b/test/heap-types.wast @@ -422,4 +422,35 @@ ) ) ) + (func $static-constructions + (drop + (struct.new_default $struct.A) + ) + (drop + (struct.new $struct.A + (i32.const 1) + (f32.const 2.345) + (f64.const 3.14159) + ) + ) + (drop + (array.new $vector + (f64.const 3.14159) + (i32.const 3) + ) + ) + (drop + (array.new_default $matrix + (i32.const 10) + ) + ) + (drop + (array.init_static $vector + (f64.const 1) + (f64.const 2) + (f64.const 4) + (f64.const 8) + ) + ) + ) ) diff --git a/test/heap-types.wast.from-wast b/test/heap-types.wast.from-wast index 9ca3b580b..f4e7245bb 100644 --- a/test/heap-types.wast.from-wast +++ b/test/heap-types.wast.from-wast @@ -1,11 +1,11 @@ (module (type $struct.A (struct (field i32) (field f32) (field $named f64))) (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A))))) - (type $none_=>_none (func)) (type $vector (array (mut f64))) + (type $none_=>_none (func)) (type $grandchild (struct (field i32) (field i64))) - (type $struct.C (struct (field $named-mut (mut f32)))) (type $matrix (array (mut (ref null $vector)))) + (type $struct.C (struct (field $named-mut (mut f32)))) (type $parent (struct )) (type $child (struct (field i32))) (type $bytes (array (mut i8))) @@ -521,4 +521,35 @@ ) ) ) + (func $static-constructions + (drop + (struct.new_default $struct.A) + ) + (drop + (struct.new $struct.A + (i32.const 1) + (f32.const 2.3450000286102295) + (f64.const 3.14159) + ) + ) + (drop + (array.new $vector + (f64.const 3.14159) + (i32.const 3) + ) + ) + (drop + (array.new_default $matrix + (i32.const 10) + ) + ) + (drop + (array.init_static $vector + (f64.const 1) + (f64.const 2) + (f64.const 4) + (f64.const 8) + ) + ) + ) ) diff --git a/test/heap-types.wast.fromBinary b/test/heap-types.wast.fromBinary index 7ac581848..f6b364b14 100644 --- a/test/heap-types.wast.fromBinary +++ b/test/heap-types.wast.fromBinary @@ -1,8 +1,8 @@ (module (type $struct.A (struct (field i32) (field f32) (field $named f64))) (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A))))) - (type $none_=>_none (func)) (type $vector (array (mut f64))) + (type $none_=>_none (func)) (type $grandchild (struct (field i32) (field i64))) (type $matrix (array (mut (ref null $vector)))) (type $struct.C (struct (field $named-mut (mut f32)))) @@ -476,5 +476,36 @@ ) ) ) + (func $static-constructions + (drop + (struct.new_default $struct.A) + ) + (drop + (struct.new $struct.A + (i32.const 1) + (f32.const 2.3450000286102295) + (f64.const 3.14159) + ) + ) + (drop + (array.new $vector + (f64.const 3.14159) + (i32.const 3) + ) + ) + (drop + (array.new_default $matrix + (i32.const 10) + ) + ) + (drop + (array.init_static $vector + (f64.const 1) + (f64.const 2) + (f64.const 4) + (f64.const 8) + ) + ) + ) ) diff --git a/test/heap-types.wast.fromBinary.noDebugInfo b/test/heap-types.wast.fromBinary.noDebugInfo index 6a8991150..c33ba0cc8 100644 --- a/test/heap-types.wast.fromBinary.noDebugInfo +++ b/test/heap-types.wast.fromBinary.noDebugInfo @@ -1,8 +1,8 @@ (module (type ${i32_f32_f64} (struct (field i32) (field f32) (field f64))) (type ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|} (struct (field i8) (field (mut i16)) (field (ref ${i32_f32_f64})) (field (mut (ref ${i32_f32_f64}))))) - (type $none_=>_none (func)) (type $[mut:f64] (array (mut f64))) + (type $none_=>_none (func)) (type ${i32_i64} (struct (field i32) (field i64))) (type $[mut:ref?|[mut:f64]|] (array (mut (ref null $[mut:f64])))) (type ${mut:f32} (struct (field (mut f32)))) @@ -476,5 +476,36 @@ ) ) ) + (func $23 + (drop + (struct.new_default ${i32_f32_f64}) + ) + (drop + (struct.new ${i32_f32_f64} + (i32.const 1) + (f32.const 2.3450000286102295) + (f64.const 3.14159) + ) + ) + (drop + (array.new $[mut:f64] + (f64.const 3.14159) + (i32.const 3) + ) + ) + (drop + (array.new_default $[mut:ref?|[mut:f64]|] + (i32.const 10) + ) + ) + (drop + (array.init_static $[mut:f64] + (f64.const 1) + (f64.const 2) + (f64.const 4) + (f64.const 8) + ) + ) + ) ) diff --git a/test/lit/binary/heap-types.wast b/test/lit/binary/heap-types.wast new file mode 100644 index 000000000..f455d7376 --- /dev/null +++ b/test/lit/binary/heap-types.wast @@ -0,0 +1,150 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that heap types are emitted properly in the binary format. This file +;; contains small modules that each use a single instruction with a heap type. +;; If we forgot to update collectHeapTypes then we would not write out their +;; type, and hit an error during --roundtrip. + +;; RUN: foreach %s %t wasm-opt -all --roundtrip -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --roundtrip -S --nominal -o - | filecheck %s --check-prefix NOMNL + +(module + ;; CHECK: (type $struct.A (struct (field i32))) + ;; NOMNL: (type $struct.A (struct (field i32))) + (type $struct.A (struct i32)) + ;; NOMNL: (type $struct.B (struct (field i32))) + (type $struct.B (struct i32)) + ;; CHECK: (func $test + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.test_static $struct.A + ;; CHECK-NEXT: (ref.null $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $test + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (ref.test_static $struct.B + ;; NOMNL-NEXT: (ref.null $struct.A) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + (func $test + (drop + (ref.test_static $struct.B (ref.null $struct.A)) + ) + ) +) + +(module + ;; CHECK: (type $struct.A (struct (field i32))) + ;; NOMNL: (type $struct.A (struct (field i32))) + (type $struct.A (struct i32)) + ;; NOMNL: (type $struct.B (struct (field i32))) + (type $struct.B (struct i32)) + ;; CHECK: (func $test + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_static $struct.A + ;; CHECK-NEXT: (ref.null $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $test + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (ref.cast_static $struct.B + ;; NOMNL-NEXT: (ref.null $struct.A) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + (func $test + (drop + (ref.cast_static $struct.B (ref.null $struct.A)) + ) + ) +) + +(module + ;; CHECK: (type $struct.A (struct (field i32))) + ;; NOMNL: (type $struct.A (struct (field i32))) + (type $struct.A (struct i32)) + ;; CHECK: (func $test + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $test + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (struct.new_default $struct.A) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + (func $test + (drop + (struct.new_default $struct.A) + ) + ) +) + +(module + ;; CHECK: (type $vector (array (mut f64))) + ;; NOMNL: (type $vector (array (mut f64))) + (type $vector (array (mut f64))) + ;; CHECK: (func $test + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.new $vector + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $test + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (array.new $vector + ;; NOMNL-NEXT: (f64.const 3.14159) + ;; NOMNL-NEXT: (i32.const 3) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + (func $test + (drop + (array.new $vector + (f64.const 3.14159) + (i32.const 3) + ) + ) + ) +) + +(module + ;; CHECK: (type $vector (array (mut f64))) + ;; NOMNL: (type $vector (array (mut f64))) + (type $vector (array (mut f64))) + ;; CHECK: (func $test + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.init_static $vector + ;; CHECK-NEXT: (f64.const 1) + ;; CHECK-NEXT: (f64.const 2) + ;; CHECK-NEXT: (f64.const 4) + ;; CHECK-NEXT: (f64.const 8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $test + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (array.init_static $vector + ;; NOMNL-NEXT: (f64.const 1) + ;; NOMNL-NEXT: (f64.const 2) + ;; NOMNL-NEXT: (f64.const 4) + ;; NOMNL-NEXT: (f64.const 8) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + (func $test + (drop + (array.init_static $vector + (f64.const 1) + (f64.const 2) + (f64.const 4) + (f64.const 8) + ) + ) + ) +) diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index d49836b99..90ac8a418 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -254,7 +254,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.new_with_rtt $struct.A + ;; CHECK-NEXT: (block ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (rtt.canon $struct.A) @@ -1937,4 +1937,27 @@ ) ) ) + + ;; CHECK: (func $simple-no-rtt + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $struct.A)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $simple-no-rtt + (drop + ;; This allocation has no rtt, so we have nothing to drop from it when + ;; we optimize. + (struct.new_default $struct.A) + ) + ) ) diff --git a/test/lit/passes/precompute-gc.wast b/test/lit/passes/precompute-gc.wast index 0a08c02c0..44eff1c5f 100644 --- a/test/lit/passes/precompute-gc.wast +++ b/test/lit/passes/precompute-gc.wast @@ -510,4 +510,24 @@ ) ) ) + + ;; CHECK: (func $new_block_unreachable (result anyref) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $new_block_unreachable (result anyref) + (struct.new_with_rtt $struct + ;; The value is a block with an unreachable. precompute will get rid of the + ;; block, after which fuzz-exec should not crash - this is a regression test + ;; for us being careful in how we execute an unreachable struct.new + (block $label$1 (result i32) + (unreachable) + ) + (rtt.canon $struct) + ) + ) ) diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt index 4cb8f4142..179a75082 100644 --- a/test/passes/Oz_fuzz-exec_all-features.txt +++ b/test/passes/Oz_fuzz-exec_all-features.txt @@ -534,32 +534,25 @@ ) (call $log (ref.test_static $struct - (array.new_with_rtt $bytes + (array.new $bytes (i32.const 20) (i32.const 10) - (rtt.canon $bytes) ) ) ) (call $log (ref.test_static $struct - (struct.new_default_with_rtt $struct - (rtt.canon $struct) - ) + (struct.new_default $struct) ) ) (call $log (ref.test_static $extendedstruct - (struct.new_default_with_rtt $struct - (rtt.canon $struct) - ) + (struct.new_default $struct) ) ) (call $log (ref.test_static $struct - (struct.new_default_with_rtt $extendedstruct - (rtt.canon $extendedstruct) - ) + (struct.new_default $extendedstruct) ) ) ) @@ -571,9 +564,7 @@ (drop (br_on_cast_static $block $struct (br_on_cast_static $extendedblock $extendedstruct - (struct.new_default_with_rtt $struct - (rtt.canon $struct) - ) + (struct.new_default $struct) ) ) ) @@ -598,9 +589,7 @@ (block $failblock (result (ref $struct)) (drop (br_on_cast_static_fail $failblock $extendedstruct - (struct.new_default_with_rtt $struct - (rtt.canon $struct) - ) + (struct.new_default $struct) ) ) (call $log diff --git a/test/passes/Oz_fuzz-exec_all-features.wast b/test/passes/Oz_fuzz-exec_all-features.wast index 54bff9e5d..ea29f259a 100644 --- a/test/passes/Oz_fuzz-exec_all-features.wast +++ b/test/passes/Oz_fuzz-exec_all-features.wast @@ -556,35 +556,28 @@ ;; Testing something completely wrong (struct vs array) returns 0. (call $log (ref.test_static $struct - (array.new_with_rtt $bytes + (array.new $bytes (i32.const 20) (i32.const 10) - (rtt.canon $bytes) ) ) ) ;; Testing a thing with the same type returns 1. (call $log (ref.test_static $struct - (struct.new_default_with_rtt $struct - (rtt.canon $struct) - ) + (struct.new_default $struct) ) ) ;; A bad downcast returns 0: we create a struct, which is not a extendedstruct. (call $log (ref.test_static $extendedstruct - (struct.new_default_with_rtt $struct - (rtt.canon $struct) - ) + (struct.new_default $struct) ) ) ;; Casting to a supertype works. (call $log (ref.test_static $struct - (struct.new_default_with_rtt $extendedstruct - (rtt.canon $extendedstruct) - ) + (struct.new_default $extendedstruct) ) ) ) @@ -592,7 +585,7 @@ (local $any anyref) ;; create a simple $struct, store it in an anyref (local.set $any - (struct.new_default_with_rtt $struct (rtt.canon $struct)) + (struct.new_default $struct) ) (drop (block $block (result ($ref $struct)) @@ -621,7 +614,7 @@ (local $any anyref) ;; create a simple $struct, store it in an anyref (local.set $any - (struct.new_default_with_rtt $struct (rtt.canon $struct)) + (struct.new_default $struct) ) (drop (block $failblock (result anyref) |