diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/gen-s-parser.inc | 50 | ||||
-rw-r--r-- | src/ir/cost.h | 8 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 199 | ||||
-rw-r--r-- | src/passes/Print.cpp | 46 | ||||
-rw-r--r-- | src/wasm-binary.h | 4 | ||||
-rw-r--r-- | src/wasm-builder.h | 23 | ||||
-rw-r--r-- | src/wasm-delegations-fields.def | 13 | ||||
-rw-r--r-- | src/wasm-interpreter.h | 59 | ||||
-rw-r--r-- | src/wasm-s-parser.h | 3 | ||||
-rw-r--r-- | src/wasm-traversal.h | 1 | ||||
-rw-r--r-- | src/wasm.h | 27 | ||||
-rw-r--r-- | src/wasm/wasm-binary.cpp | 43 | ||||
-rw-r--r-- | src/wasm/wasm-s-parser.cpp | 19 | ||||
-rw-r--r-- | src/wasm/wasm-stack.cpp | 33 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 61 | ||||
-rw-r--r-- | src/wasm/wasm.cpp | 25 |
16 files changed, 445 insertions, 169 deletions
diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 7374e630a..c7c521a21 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -84,9 +84,25 @@ switch (op[0]) { case '\0': if (strcmp(op, "br_on_cast") == 0) { return makeBrOn(s, BrOnCast); } goto parse_error; - case '_': - if (strcmp(op, "br_on_cast_fail") == 0) { return makeBrOn(s, BrOnCastFail); } - goto parse_error; + case '_': { + switch (op[11]) { + case 'f': + if (strcmp(op, "br_on_cast_fail") == 0) { return makeBrOn(s, BrOnCastFail); } + goto parse_error; + case 's': { + switch (op[17]) { + case '\0': + if (strcmp(op, "br_on_cast_static") == 0) { return makeBrOnStatic(s, BrOnCast); } + goto parse_error; + case '_': + if (strcmp(op, "br_on_cast_static_fail") == 0) { return makeBrOnStatic(s, BrOnCastFail); } + goto parse_error; + default: goto parse_error; + } + } + default: goto parse_error; + } + } default: goto parse_error; } } @@ -2831,9 +2847,17 @@ switch (op[0]) { default: goto parse_error; } } - case 'c': - if (strcmp(op, "ref.cast") == 0) { return makeRefCast(s); } - goto parse_error; + case 'c': { + switch (op[8]) { + case '\0': + if (strcmp(op, "ref.cast") == 0) { return makeRefCast(s); } + goto parse_error; + case '_': + if (strcmp(op, "ref.cast_static") == 0) { return makeRefCastStatic(s); } + goto parse_error; + default: goto parse_error; + } + } case 'e': if (strcmp(op, "ref.eq") == 0) { return makeRefEq(s); } goto parse_error; @@ -2860,9 +2884,17 @@ switch (op[0]) { case 'n': if (strcmp(op, "ref.null") == 0) { return makeRefNull(s); } goto parse_error; - case 't': - if (strcmp(op, "ref.test") == 0) { return makeRefTest(s); } - goto parse_error; + case 't': { + switch (op[8]) { + case '\0': + if (strcmp(op, "ref.test") == 0) { return makeRefTest(s); } + goto parse_error; + case '_': + if (strcmp(op, "ref.test_static") == 0) { return makeRefTestStatic(s); } + goto parse_error; + default: goto parse_error; + } + } default: goto parse_error; } } diff --git a/src/ir/cost.h b/src/ir/cost.h index e3e6aebf3..086b7b9af 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -567,15 +567,17 @@ struct CostAnalyzer : public OverriddenVisitor<CostAnalyzer, CostType> { CostType visitI31New(I31New* curr) { return 3 + visit(curr->value); } CostType visitI31Get(I31Get* curr) { return 2 + visit(curr->i31); } CostType visitRefTest(RefTest* curr) { - return 2 + nullCheckCost(curr->ref) + visit(curr->ref) + visit(curr->rtt); + return 2 + nullCheckCost(curr->ref) + visit(curr->ref) + + maybeVisit(curr->rtt); } CostType visitRefCast(RefCast* curr) { - return 2 + nullCheckCost(curr->ref) + visit(curr->ref) + visit(curr->rtt); + return 2 + nullCheckCost(curr->ref) + visit(curr->ref) + + maybeVisit(curr->rtt); } CostType visitBrOn(BrOn* curr) { // BrOnCast has more work to do with the rtt, so add a little there. CostType base = curr->op == BrOnCast ? 3 : 2; - return base + nullCheckCost(curr->ref) + visit(curr->ref) + + return base + nullCheckCost(curr->ref) + maybeVisit(curr->ref) + maybeVisit(curr->rtt); } CostType visitRttCanon(RttCanon* curr) { diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index b9b426466..cc263d228 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1341,96 +1341,103 @@ struct OptimizeInstructions Builder builder(*getModule()); auto passOptions = getPassOptions(); - auto fallthrough = - Properties::getFallthrough(curr->ref, getPassOptions(), *getModule()); - - // If the value is a null, it will just flow through, and we do not need the - // cast. However, if that would change the type, then things are less - // simple: if the original type was non-nullable, replacing it with a null - // would change the type, which can happen in e.g. - // (ref.cast (ref.as_non_null (.. (ref.null) - if (fallthrough->is<RefNull>()) { - // Replace the expression with drops of the inputs, and a null. Note that - // we provide a null of the type the outside expects - that of the rtt, - // which is what was cast to. - Expression* rep = - builder.makeBlock({builder.makeDrop(curr->ref), - builder.makeDrop(curr->rtt), - builder.makeRefNull(curr->rtt->type.getHeapType())}); - if (curr->ref->type.isNonNullable()) { - // Avoid a type change by forcing to be non-nullable. In practice, this - // would have trapped before we get here, so this is just for - // validation. - rep = builder.makeRefAs(RefAsNonNull, rep); - } - replaceCurrent(rep); - return; - // TODO: The optimal ordering of this and the other ref.as_non_null stuff - // later down in this functions is unclear and may be worth looking - // into. - } - - // For the cast to be able to succeed, the value being cast must be a - // subtype of the desired type, as RTT subtyping is a subset of static - // subtyping. For example, trying to cast an array to a struct would be - // incompatible. - if (!canBeCastTo(curr->ref->type.getHeapType(), - curr->rtt->type.getHeapType())) { - // This cast cannot succeed. If the input is not a null, it will - // definitely trap. - if (fallthrough->type.isNonNullable()) { - // Our type will now be unreachable; update the parents. - refinalize = true; - replaceCurrent(builder.makeBlock({builder.makeDrop(curr->ref), - builder.makeDrop(curr->rtt), - builder.makeUnreachable()})); - return; - } - // Otherwise, we are not sure what it is, and need to wait for runtime to - // see if it is a null or not. (We've already handled the case where we - // can see the value is definitely a null at compile time, earlier.) - } - - if (passOptions.ignoreImplicitTraps || passOptions.trapsNeverHappen) { - // Aside from the issue of type incompatibility as mentioned above, the - // cast can trap if the types *are* compatible but it happens to be the - // case at runtime that the value is not of the desired subtype. If we - // do not consider such traps possible, we can ignore that. Note, though, - // that we cannot do this if we cannot replace the current type with the - // reference's type. - if (HeapType::isSubType(curr->ref->type.getHeapType(), - curr->rtt->type.getHeapType())) { - replaceCurrent(getResultOfFirst(curr->ref, - builder.makeDrop(curr->rtt), - getFunction(), - getModule(), - passOptions)); + // TODO: If no rtt, this is a static cast, and if the type matches then it + // will definitely succeed. The opts below could be expanded for that. + if (curr->rtt) { + + auto fallthrough = + Properties::getFallthrough(curr->ref, getPassOptions(), *getModule()); + + // If the value is a null, it will just flow through, and we do not need + // the cast. However, if that would change the type, then things are less + // simple: if the original type was non-nullable, replacing it with a null + // would change the type, which can happen in e.g. + // (ref.cast (ref.as_non_null (.. (ref.null) + if (fallthrough->is<RefNull>()) { + // Replace the expression with drops of the inputs, and a null. Note + // that we provide a null of the type the outside expects - that of the + // rtt, which is what was cast to. + Expression* rep = builder.makeBlock( + {builder.makeDrop(curr->ref), + builder.makeDrop(curr->rtt), + builder.makeRefNull(curr->rtt->type.getHeapType())}); + if (curr->ref->type.isNonNullable()) { + // Avoid a type change by forcing to be non-nullable. In practice, + // this would have trapped before we get here, so this is just for + // validation. + rep = builder.makeRefAs(RefAsNonNull, rep); + } + replaceCurrent(rep); return; + // TODO: The optimal ordering of this and the other ref.as_non_null + // stuff later down in this functions is unclear and may be worth + // looking into. + } + + // For the cast to be able to succeed, the value being cast must be a + // subtype of the desired type, as RTT subtyping is a subset of static + // subtyping. For example, trying to cast an array to a struct would be + // incompatible. + if (!canBeCastTo(curr->ref->type.getHeapType(), + curr->rtt->type.getHeapType())) { + // This cast cannot succeed. If the input is not a null, it will + // definitely trap. + if (fallthrough->type.isNonNullable()) { + // Our type will now be unreachable; update the parents. + refinalize = true; + replaceCurrent(builder.makeBlock({builder.makeDrop(curr->ref), + builder.makeDrop(curr->rtt), + builder.makeUnreachable()})); + return; + } + // Otherwise, we are not sure what it is, and need to wait for runtime + // to see if it is a null or not. (We've already handled the case where + // we can see the value is definitely a null at compile time, earlier.) + } + + if (passOptions.ignoreImplicitTraps || passOptions.trapsNeverHappen) { + // Aside from the issue of type incompatibility as mentioned above, the + // cast can trap if the types *are* compatible but it happens to be the + // case at runtime that the value is not of the desired subtype. If we + // do not consider such traps possible, we can ignore that. Note, + // though, that we cannot do this if we cannot replace the current type + // with the reference's type. + if (HeapType::isSubType(curr->ref->type.getHeapType(), + curr->rtt->type.getHeapType())) { + replaceCurrent(getResultOfFirst(curr->ref, + builder.makeDrop(curr->rtt), + getFunction(), + getModule(), + passOptions)); + return; + } } - } - // Repeated identical ref.cast operations are unnecessary, if using the - // exact same rtt - the result will be the same. Find the immediate child - // cast, if there is one, and see if it is identical. - // TODO: Look even further through incompatible casts? - auto* ref = curr->ref; - while (!ref->is<RefCast>()) { - auto* last = ref; - // RefCast falls through the value, so instead of calling getFallthrough() - // to look through all fallthroughs, we must iterate manually. Keep going - // until we reach either the end of things falling-through, or a cast. - ref = Properties::getImmediateFallthrough(ref, passOptions, *getModule()); - if (ref == last) { - break; + // Repeated identical ref.cast operations are unnecessary, if using the + // exact same rtt - the result will be the same. Find the immediate child + // cast, if there is one, and see if it is identical. + // TODO: Look even further through incompatible casts? + auto* ref = curr->ref; + while (!ref->is<RefCast>()) { + auto* last = ref; + // RefCast falls through the value, so instead of calling + // getFallthrough() to look through all fallthroughs, we must iterate + // manually. Keep going until we reach either the end of things + // falling-through, or a cast. + ref = + Properties::getImmediateFallthrough(ref, passOptions, *getModule()); + if (ref == last) { + break; + } } - } - if (auto* child = ref->dynCast<RefCast>()) { - // Check if the casts are identical. - if (ExpressionAnalyzer::equal(curr->rtt, child->rtt) && - !EffectAnalyzer(passOptions, *getModule(), curr->rtt) - .hasSideEffects()) { - replaceCurrent(curr->ref); - return; + if (auto* child = ref->dynCast<RefCast>()) { + // Check if the casts are identical. + if (ExpressionAnalyzer::equal(curr->rtt, child->rtt) && + !EffectAnalyzer(passOptions, *getModule(), curr->rtt) + .hasSideEffects()) { + replaceCurrent(curr->ref); + return; + } } } @@ -1469,14 +1476,18 @@ struct OptimizeInstructions return; } - // See above in RefCast. - if (!canBeCastTo(curr->ref->type.getHeapType(), - curr->rtt->type.getHeapType())) { - // This test cannot succeed, and will definitely return 0. - Builder builder(*getModule()); - replaceCurrent(builder.makeBlock({builder.makeDrop(curr->ref), - builder.makeDrop(curr->rtt), - builder.makeConst(int32_t(0))})); + // TODO: If no rtt, this is a static test, and if the type matches then it + // will definitely succeed. The opt below could be expanded for that. + if (curr->rtt) { + // See above in RefCast. + if (!canBeCastTo(curr->ref->type.getHeapType(), + curr->rtt->type.getHeapType())) { + // This test cannot succeed, and will definitely return 0. + Builder builder(*getModule()); + replaceCurrent(builder.makeBlock({builder.makeDrop(curr->ref), + builder.makeDrop(curr->rtt), + builder.makeConst(int32_t(0))})); + } } } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 00b09dea4..6198218d8 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -286,6 +286,12 @@ static std::ostream& printType(std::ostream& o, Type type, Module* wasm) { return o; } +static std::ostream& +printHeapType(std::ostream& o, HeapType type, Module* wasm) { + TypeNamePrinter(o, wasm).print(type); + return o; +} + static std::ostream& printPrefixedTypes(std::ostream& o, const char* prefix, Type type, @@ -1811,7 +1817,7 @@ struct PrintExpressionContents void visitMemoryGrow(MemoryGrow* curr) { printMedium(o, "memory.grow"); } void visitRefNull(RefNull* curr) { printMedium(o, "ref.null "); - TypeNamePrinter(o, wasm).print(curr->type.getHeapType()); + printHeapType(o, curr->type.getHeapType(), wasm); } void visitRefIs(RefIs* curr) { switch (curr->op) { @@ -1881,8 +1887,22 @@ struct PrintExpressionContents printMedium(o, "call_ref"); } } - void visitRefTest(RefTest* curr) { printMedium(o, "ref.test"); } - void visitRefCast(RefCast* curr) { printMedium(o, "ref.cast"); } + void visitRefTest(RefTest* curr) { + if (curr->rtt) { + printMedium(o, "ref.test"); + } else { + printMedium(o, "ref.test_static "); + printHeapType(o, curr->intendedType, wasm); + } + } + void visitRefCast(RefCast* curr) { + if (curr->rtt) { + printMedium(o, "ref.cast"); + } else { + printMedium(o, "ref.cast_static "); + printHeapType(o, curr->intendedType, wasm); + } + } void visitBrOn(BrOn* curr) { switch (curr->op) { case BrOnNull: @@ -1892,10 +1912,26 @@ struct PrintExpressionContents printMedium(o, "br_on_non_null "); break; case BrOnCast: - printMedium(o, "br_on_cast "); + if (curr->rtt) { + printMedium(o, "br_on_cast "); + } else { + printMedium(o, "br_on_cast_static "); + printName(curr->name, o); + o << ' '; + printHeapType(o, curr->intendedType, wasm); + return; + } break; case BrOnCastFail: - printMedium(o, "br_on_cast_fail "); + if (curr->rtt) { + printMedium(o, "br_on_cast_fail "); + } else { + printMedium(o, "br_on_cast_static_fail "); + printName(curr->name, o); + o << ' '; + printHeapType(o, curr->intendedType, wasm); + return; + } break; case BrOnFunc: printMedium(o, "br_on_func "); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index d8d712f62..0e4b4b873 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1071,6 +1071,10 @@ enum ASTNodes { RefCast = 0x41, BrOnCast = 0x42, BrOnCastFail = 0x43, + RefTestStatic = 0x44, + RefCastStatic = 0x45, + BrOnCastStatic = 0x46, + BrOnCastStaticFail = 0x47, RefIsFunc = 0x50, RefIsData = 0x51, RefIsI31 = 0x52, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 018b0bf8b..346f2f5ba 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -765,6 +765,13 @@ public: ret->finalize(); return ret; } + RefTest* makeRefTest(Expression* ref, HeapType intendedType) { + auto* ret = wasm.allocator.alloc<RefTest>(); + ret->ref = ref; + ret->intendedType = intendedType; + ret->finalize(); + return ret; + } RefCast* makeRefCast(Expression* ref, Expression* rtt) { auto* ret = wasm.allocator.alloc<RefCast>(); ret->ref = ref; @@ -772,6 +779,13 @@ public: ret->finalize(); return ret; } + RefCast* makeRefCast(Expression* ref, HeapType intendedType) { + auto* ret = wasm.allocator.alloc<RefCast>(); + ret->ref = ref; + ret->intendedType = intendedType; + ret->finalize(); + return ret; + } BrOn* makeBrOn(BrOnOp op, Name name, Expression* ref, Expression* rtt = nullptr) { auto* ret = wasm.allocator.alloc<BrOn>(); @@ -782,6 +796,15 @@ public: ret->finalize(); return ret; } + BrOn* makeBrOn(BrOnOp op, Name name, Expression* ref, HeapType intendedType) { + auto* ret = wasm.allocator.alloc<BrOn>(); + ret->op = op; + ret->name = name; + ret->ref = ref; + ret->intendedType = intendedType; + ret->finalize(); + return ret; + } RttCanon* makeRttCanon(HeapType heapType) { auto* ret = wasm.allocator.alloc<RttCanon>(); ret->type = Type(Rtt(0, heapType)); diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index b94aa5599..6bbb7abaa 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -76,6 +76,8 @@ // // DELEGATE_FIELD_TYPE(id, name) - called for a Type. // +// DELEGATE_FIELD_HEAPTYPE(id, name) - called for a HeapType. +// // DELEGATE_FIELD_ADDRESS(id, name) - called for an Address. #ifndef DELEGATE_START @@ -166,6 +168,10 @@ #error please define DELEGATE_FIELD_TYPE(id, name) #endif +#ifndef DELEGATE_FIELD_HEAPTYPE +#error please define DELEGATE_FIELD_HEAPTYPE(id, name) +#endif + #ifndef DELEGATE_FIELD_ADDRESS #error please define DELEGATE_FIELD_ADDRESS(id, name) #endif @@ -570,14 +576,16 @@ switch (DELEGATE_ID) { } case Expression::Id::RefTestId: { DELEGATE_START(RefTest); - DELEGATE_FIELD_CHILD(RefTest, rtt); + DELEGATE_FIELD_HEAPTYPE(RefTest, intendedType); + DELEGATE_FIELD_OPTIONAL_CHILD(RefTest, rtt); DELEGATE_FIELD_CHILD(RefTest, ref); DELEGATE_END(RefTest); break; } case Expression::Id::RefCastId: { DELEGATE_START(RefCast); - DELEGATE_FIELD_CHILD(RefCast, rtt); + DELEGATE_FIELD_HEAPTYPE(RefCast, intendedType); + DELEGATE_FIELD_OPTIONAL_CHILD(RefCast, rtt); DELEGATE_FIELD_CHILD(RefCast, ref); DELEGATE_END(RefCast); break; @@ -586,6 +594,7 @@ switch (DELEGATE_ID) { DELEGATE_START(BrOn); DELEGATE_FIELD_INT(BrOn, op); DELEGATE_FIELD_SCOPE_NAME_USE(BrOn, name); + DELEGATE_FIELD_HEAPTYPE(BrOn, intendedType); DELEGATE_FIELD_OPTIONAL_CHILD(BrOn, rtt); DELEGATE_FIELD_CHILD(BrOn, ref); DELEGATE_END(BrOn); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index ca456406f..53d3048b7 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1424,11 +1424,16 @@ public: cast.breaking = ref; return cast; } - Flow rtt = this->visit(curr->rtt); - if (rtt.breaking()) { - cast.outcome = cast.Break; - cast.breaking = rtt; - return cast; + Literal intendedRtt; + if (curr->rtt) { + // This is a dynamic check with an rtt. + Flow rtt = this->visit(curr->rtt); + if (rtt.breaking()) { + cast.outcome = cast.Break; + cast.breaking = rtt; + return cast; + } + intendedRtt = rtt.getSingleValue(); } cast.originalRef = ref.getSingleValue(); if (cast.originalRef.isNull()) { @@ -1443,8 +1448,6 @@ public: cast.outcome = cast.Failure; return cast; } - Literal seenRtt; - Literal intendedRtt = rtt.getSingleValue(); if (cast.originalRef.isFunction()) { // Function casts are simple in that they have no RTT hierarchies; instead // each reference has the canonical RTT for the signature. @@ -1460,24 +1463,42 @@ public: cast.breaking = NONCONSTANT_FLOW; return cast; } - seenRtt = Literal(Type(Rtt(0, func->type))); - if (!seenRtt.isSubRtt(intendedRtt)) { - cast.outcome = cast.Failure; - return cast; + if (curr->rtt) { + Literal seenRtt = Literal(Type(Rtt(0, func->type))); + if (!seenRtt.isSubRtt(intendedRtt)) { + cast.outcome = cast.Failure; + return cast; + } + cast.castRef = Literal( + func->name, Type(intendedRtt.type.getHeapType(), NonNullable)); + } else { + if (!HeapType::isSubType(func->type, curr->intendedType)) { + cast.outcome = cast.Failure; + return cast; + } + cast.castRef = + Literal(func->name, Type(curr->intendedType, NonNullable)); } - cast.castRef = - Literal(func->name, Type(intendedRtt.type.getHeapType(), NonNullable)); } else { // GC data store an RTT in each instance. assert(cast.originalRef.isData()); auto gcData = cast.originalRef.getGCData(); - seenRtt = gcData->rtt; - if (!seenRtt.isSubRtt(intendedRtt)) { - cast.outcome = cast.Failure; - return cast; + Literal seenRtt = gcData->rtt; + if (curr->rtt) { + if (!seenRtt.isSubRtt(intendedRtt)) { + cast.outcome = cast.Failure; + return cast; + } + cast.castRef = + Literal(gcData, Type(intendedRtt.type.getHeapType(), NonNullable)); + } else { + auto seenType = seenRtt.type.getHeapType(); + if (!HeapType::isSubType(seenType, curr->intendedType)) { + cast.outcome = cast.Failure; + return cast; + } + cast.castRef = Literal(gcData, Type(seenType, NonNullable)); } - cast.castRef = - Literal(gcData, Type(intendedRtt.type.getHeapType(), NonNullable)); } cast.outcome = cast.Success; return cast; diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index 03462a7cb..8348d3e6b 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -274,8 +274,11 @@ private: Expression* makeI31New(Element& s); Expression* makeI31Get(Element& s, bool signed_); Expression* makeRefTest(Element& s); + Expression* makeRefTestStatic(Element& s); Expression* makeRefCast(Element& s); + Expression* makeRefCastStatic(Element& s); Expression* makeBrOn(Element& s, BrOnOp op); + Expression* makeBrOnStatic(Element& s, BrOnOp op); Expression* makeRttCanon(Element& s); Expression* makeRttSub(Element& s); Expression* makeRttFreshSub(Element& s); diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index d3e2a19da..47e99226d 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -373,6 +373,7 @@ struct PostWalker : public Walker<SubType, VisitorType> { #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/wasm.h b/src/wasm.h index d0d904b7c..ffc97f71e 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1356,9 +1356,17 @@ public: RefTest(MixedArena& allocator) {} Expression* ref; - Expression* rtt; + + // If rtt is provided then this is a dynamic test with an rtt. If nullptr then + // this is a static cast and intendedType is set, and it contains the type we + // intend to cast to. + Expression* rtt = nullptr; + HeapType intendedType; void finalize(); + + // Returns the type we intend to cast to. + HeapType getIntendedType(); }; class RefCast : public SpecificExpression<Expression::RefCastId> { @@ -1366,9 +1374,15 @@ public: RefCast(MixedArena& allocator) {} Expression* ref; - Expression* rtt; + + // See above with RefTest. + Expression* rtt = nullptr; + HeapType intendedType; void finalize(); + + // Returns the type we intend to cast to. + HeapType getIntendedType(); }; class BrOn : public SpecificExpression<Expression::BrOnId> { @@ -1379,8 +1393,10 @@ public: Name name; Expression* ref; - // BrOnCast* has an rtt that is used in the cast. - Expression* rtt; + // BrOnCast* has, like RefCast and RefTest, either an rtt or a static intended + // type. + Expression* rtt = nullptr; + HeapType intendedType; // TODO: BrOnNull also has an optional extra value in the spec, which we do // not support. See also the discussion on @@ -1390,6 +1406,9 @@ public: void finalize(); + // Returns the type we intend to cast to. Relevant only for the cast variants. + HeapType getIntendedType(); + // Returns the type sent on the branch, if it is taken. Type getSentType(); }; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index dad6f9320..e1ff43988 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -6419,23 +6419,33 @@ bool WasmBinaryBuilder::maybeVisitI31Get(Expression*& out, uint32_t code) { } bool WasmBinaryBuilder::maybeVisitRefTest(Expression*& out, uint32_t code) { - if (code != BinaryConsts::RefTest) { - return false; + if (code == BinaryConsts::RefTest) { + auto* rtt = popNonVoidExpression(); + auto* ref = popNonVoidExpression(); + out = Builder(wasm).makeRefTest(ref, rtt); + return true; + } else if (code == BinaryConsts::RefTestStatic) { + auto intendedType = getIndexedHeapType(); + auto* ref = popNonVoidExpression(); + out = Builder(wasm).makeRefTest(ref, intendedType); + return true; } - auto* rtt = popNonVoidExpression(); - auto* ref = popNonVoidExpression(); - out = Builder(wasm).makeRefTest(ref, rtt); - return true; + return false; } bool WasmBinaryBuilder::maybeVisitRefCast(Expression*& out, uint32_t code) { - if (code != BinaryConsts::RefCast) { - return false; + if (code == BinaryConsts::RefCast) { + auto* rtt = popNonVoidExpression(); + auto* ref = popNonVoidExpression(); + out = Builder(wasm).makeRefCast(ref, rtt); + return true; + } else if (code == BinaryConsts::RefCastStatic) { + auto intendedType = getIndexedHeapType(); + auto* ref = popNonVoidExpression(); + out = Builder(wasm).makeRefCast(ref, intendedType); + return true; } - auto* rtt = popNonVoidExpression(); - auto* ref = popNonVoidExpression(); - out = Builder(wasm).makeRefCast(ref, rtt); - return true; + return false; } bool WasmBinaryBuilder::maybeVisitBrOn(Expression*& out, uint32_t code) { @@ -6448,9 +6458,11 @@ bool WasmBinaryBuilder::maybeVisitBrOn(Expression*& out, uint32_t code) { op = BrOnNonNull; break; case BinaryConsts::BrOnCast: + case BinaryConsts::BrOnCastStatic: op = BrOnCast; break; case BinaryConsts::BrOnCastFail: + case BinaryConsts::BrOnCastStaticFail: op = BrOnCastFail; break; case BinaryConsts::BrOnFunc: @@ -6475,6 +6487,13 @@ bool WasmBinaryBuilder::maybeVisitBrOn(Expression*& out, uint32_t code) { return false; } auto name = getBreakTarget(getU32LEB()).name; + if (code == BinaryConsts::BrOnCastStatic || + code == BinaryConsts::BrOnCastStaticFail) { + auto intendedType = getIndexedHeapType(); + auto* ref = popNonVoidExpression(); + out = Builder(wasm).makeBrOn(op, name, ref, intendedType); + return true; + } Expression* rtt = nullptr; if (op == BrOnCast || op == BrOnCastFail) { rtt = popNonVoidExpression(); diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index 28028c459..3cfe90e26 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -2574,12 +2574,24 @@ Expression* SExpressionWasmBuilder::makeRefTest(Element& s) { return Builder(wasm).makeRefTest(ref, rtt); } +Expression* SExpressionWasmBuilder::makeRefTestStatic(Element& s) { + auto heapType = parseHeapType(*s[1]); + auto* ref = parseExpression(*s[2]); + return Builder(wasm).makeRefTest(ref, heapType); +} + Expression* SExpressionWasmBuilder::makeRefCast(Element& s) { auto* ref = parseExpression(*s[1]); auto* rtt = parseExpression(*s[2]); return Builder(wasm).makeRefCast(ref, rtt); } +Expression* SExpressionWasmBuilder::makeRefCastStatic(Element& s) { + auto heapType = parseHeapType(*s[1]); + auto* ref = parseExpression(*s[2]); + return Builder(wasm).makeRefCast(ref, heapType); +} + Expression* SExpressionWasmBuilder::makeBrOn(Element& s, BrOnOp op) { auto name = getLabel(*s[1]); auto* ref = parseExpression(*s[2]); @@ -2591,6 +2603,13 @@ Expression* SExpressionWasmBuilder::makeBrOn(Element& s, BrOnOp op) { .validateAndMakeBrOn(op, name, ref, rtt); } +Expression* SExpressionWasmBuilder::makeBrOnStatic(Element& s, BrOnOp op) { + auto name = getLabel(*s[1]); + auto heapType = parseHeapType(*s[2]); + auto* ref = parseExpression(*s[3]); + return Builder(wasm).makeBrOn(op, name, ref, heapType); +} + Expression* SExpressionWasmBuilder::makeRttCanon(Element& s) { return Builder(wasm).makeRttCanon(parseHeapType(*s[1])); } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index e5460cf6f..b6424cdde 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -1942,11 +1942,23 @@ void BinaryInstWriter::visitCallRef(CallRef* curr) { } void BinaryInstWriter::visitRefTest(RefTest* curr) { - o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::RefTest); + o << int8_t(BinaryConsts::GCPrefix); + if (curr->rtt) { + o << U32LEB(BinaryConsts::RefTest); + } else { + o << U32LEB(BinaryConsts::RefTestStatic); + parent.writeIndexedHeapType(curr->intendedType); + } } void BinaryInstWriter::visitRefCast(RefCast* curr) { - o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::RefCast); + o << int8_t(BinaryConsts::GCPrefix); + if (curr->rtt) { + o << U32LEB(BinaryConsts::RefCast); + } else { + o << U32LEB(BinaryConsts::RefCastStatic); + parent.writeIndexedHeapType(curr->intendedType); + } } void BinaryInstWriter::visitBrOn(BrOn* curr) { @@ -1958,10 +1970,20 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { o << int8_t(BinaryConsts::BrOnNonNull); break; case BrOnCast: - o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnCast); + o << int8_t(BinaryConsts::GCPrefix); + if (curr->rtt) { + o << U32LEB(BinaryConsts::BrOnCast); + } else { + o << U32LEB(BinaryConsts::BrOnCastStatic); + } break; case BrOnCastFail: - o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnCastFail); + o << int8_t(BinaryConsts::GCPrefix); + if (curr->rtt) { + o << U32LEB(BinaryConsts::BrOnCastFail); + } else { + o << U32LEB(BinaryConsts::BrOnCastStaticFail); + } break; case BrOnFunc: o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnFunc); @@ -1985,6 +2007,9 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { WASM_UNREACHABLE("invalid br_on_*"); } o << U32LEB(getBreakIndex(curr->name)); + if ((curr->op == BrOnCast || curr->op == BrOnCastFail) && !curr->rtt) { + parent.writeIndexedHeapType(curr->intendedType); + } } void BinaryInstWriter::visitRttCanon(RttCanon* curr) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 0de417ed7..1f6421609 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2219,9 +2219,20 @@ void FunctionValidator::visitRefTest(RefTest* curr) { shouldBeTrue( curr->ref->type.isRef(), curr, "ref.test ref must have ref type"); } - if (curr->rtt->type != Type::unreachable) { - shouldBeTrue( - curr->rtt->type.isRtt(), curr, "ref.test rtt must have rtt type"); + if (curr->rtt) { + if (curr->rtt->type != Type::unreachable) { + shouldBeTrue( + curr->rtt->type.isRtt(), curr, "ref.test rtt must have rtt type"); + } + shouldBeEqual(curr->intendedType, + HeapType(), + curr, + "dynamic ref.test must not use intendedType field"); + } else { + shouldBeUnequal(curr->intendedType, + HeapType(), + curr, + "static ref.test must set intendedType field"); } } @@ -2232,9 +2243,20 @@ void FunctionValidator::visitRefCast(RefCast* curr) { shouldBeTrue( curr->ref->type.isRef(), curr, "ref.cast ref must have ref type"); } - if (curr->rtt->type != Type::unreachable) { - shouldBeTrue( - curr->rtt->type.isRtt(), curr, "ref.cast rtt must have rtt type"); + if (curr->rtt) { + if (curr->rtt->type != Type::unreachable) { + shouldBeTrue( + curr->rtt->type.isRtt(), curr, "ref.cast rtt must have rtt type"); + } + shouldBeEqual(curr->intendedType, + HeapType(), + curr, + "dynamic ref.cast must not use intendedType field"); + } else { + shouldBeUnequal(curr->intendedType, + HeapType(), + curr, + "static ref.cast must set intendedType field"); } } @@ -2247,14 +2269,29 @@ void FunctionValidator::visitBrOn(BrOn* curr) { curr->ref->type.isRef(), curr, "br_on_cast ref must have ref type"); } if (curr->op == BrOnCast || curr->op == BrOnCastFail) { - // Note that an unreachable rtt is not supported: the text and binary - // formats do not provide the type, so if it's unreachable we should not - // even create a br_on_cast in such a case, as we'd have no idea what it - // casts to. - shouldBeTrue( - curr->rtt->type.isRtt(), curr, "br_on_cast rtt must have rtt type"); + if (curr->rtt) { + // Note that an unreachable rtt is not supported: the text and binary + // formats do not provide the type, so if it's unreachable we should not + // even create a br_on_cast in such a case, as we'd have no idea what it + // casts to. + shouldBeTrue( + curr->rtt->type.isRtt(), curr, "br_on_cast rtt must have rtt type"); + shouldBeEqual(curr->intendedType, + HeapType(), + curr, + "dynamic br_on_cast* must not use intendedType field"); + } else { + shouldBeUnequal(curr->intendedType, + HeapType(), + curr, + "static br_on_cast* must set intendedType field"); + } } else { shouldBeTrue(curr->rtt == nullptr, curr, "non-cast BrOn must not have rtt"); + shouldBeEqual(curr->intendedType, + HeapType(), + curr, + "non-cast br_on* must not set intendedType field"); } noteBreak(curr->name, curr->getSentType(), curr); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 2861c4cee..45137fffe 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -895,23 +895,33 @@ void CallRef::finalize(Type type_) { } void RefTest::finalize() { - if (ref->type == Type::unreachable || rtt->type == Type::unreachable) { + if (ref->type == Type::unreachable || + (rtt && rtt->type == Type::unreachable)) { type = Type::unreachable; } else { type = Type::i32; } } +HeapType RefTest::getIntendedType() { + return rtt ? rtt->type.getHeapType() : intendedType; +} + void RefCast::finalize() { - if (ref->type == Type::unreachable || rtt->type == Type::unreachable) { + if (ref->type == Type::unreachable || + (rtt && rtt->type == Type::unreachable)) { type = Type::unreachable; } else { // The output of ref.cast may be null if the input is null (in that case the // null is passed through). - type = Type(rtt->type.getHeapType(), ref->type.getNullability()); + type = Type(getIntendedType(), ref->type.getNullability()); } } +HeapType RefCast::getIntendedType() { + return rtt ? rtt->type.getHeapType() : intendedType; +} + void BrOn::finalize() { if (ref->type == Type::unreachable || (rtt && rtt->type == Type::unreachable)) { @@ -938,7 +948,7 @@ void BrOn::finalize() { case BrOnCastFail: // If we do not branch, the cast worked, and we have something of the cast // type. - type = Type(rtt->type.getHeapType(), NonNullable); + type = Type(getIntendedType(), NonNullable); break; case BrOnNonFunc: type = Type(HeapType::func, NonNullable); @@ -954,6 +964,11 @@ void BrOn::finalize() { } } +HeapType BrOn::getIntendedType() { + assert(op == BrOnCast || op == BrOnCastFail); + return rtt ? rtt->type.getHeapType() : intendedType; +} + Type BrOn::getSentType() { switch (op) { case BrOnNull: @@ -971,7 +986,7 @@ Type BrOn::getSentType() { if (ref->type == Type::unreachable) { return Type::unreachable; } - return Type(rtt->type.getHeapType(), NonNullable); + return Type(getIntendedType(), NonNullable); case BrOnFunc: return Type::funcref; case BrOnData: |