summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gen-s-parser.inc50
-rw-r--r--src/ir/cost.h8
-rw-r--r--src/passes/OptimizeInstructions.cpp199
-rw-r--r--src/passes/Print.cpp46
-rw-r--r--src/wasm-binary.h4
-rw-r--r--src/wasm-builder.h23
-rw-r--r--src/wasm-delegations-fields.def13
-rw-r--r--src/wasm-interpreter.h59
-rw-r--r--src/wasm-s-parser.h3
-rw-r--r--src/wasm-traversal.h1
-rw-r--r--src/wasm.h27
-rw-r--r--src/wasm/wasm-binary.cpp43
-rw-r--r--src/wasm/wasm-s-parser.cpp19
-rw-r--r--src/wasm/wasm-stack.cpp33
-rw-r--r--src/wasm/wasm-validator.cpp61
-rw-r--r--src/wasm/wasm.cpp25
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: