summaryrefslogtreecommitdiff
path: root/src/wasm-interpreter.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/wasm-interpreter.h')
-rw-r--r--src/wasm-interpreter.h250
1 files changed, 117 insertions, 133 deletions
diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h
index 61a08c14f..c67ac6768 100644
--- a/src/wasm-interpreter.h
+++ b/src/wasm-interpreter.h
@@ -26,6 +26,7 @@
#include <cmath>
#include <limits.h>
#include <sstream>
+#include <variant>
#include "ir/module-utils.h"
#include "support/bits.h"
@@ -1404,154 +1405,139 @@ public:
// Helper for ref.test, ref.cast, and br_on_cast, which share almost all their
// logic except for what they return.
struct Cast {
- enum Outcome {
- // We took a break before doing anything.
- Break,
- // The input was null.
- Null,
- // The cast succeeded.
- Success,
- // The cast failed.
- Failure
- } outcome;
-
- Flow breaking;
- Literal originalRef;
- Literal castRef;
+ // The control flow that preempts the cast.
+ struct Breaking : Flow {
+ Breaking(Flow breaking) : Flow(breaking) {}
+ };
+ // The null input to the cast.
+ struct Null : Literal {
+ Null(Literal original) : Literal(original) {}
+ };
+ // The result of the successful cast.
+ struct Success : Literal {
+ Success(Literal result) : Literal(result) {}
+ };
+ // The input to a failed cast.
+ struct Failure : Literal {
+ Failure(Literal original) : Literal(original) {}
+ };
+
+ std::variant<Breaking, Null, Success, Failure> state;
+
+ template<class T> Cast(T state) : state(state) {}
+ Flow* getBreaking() { return std::get_if<Breaking>(&state); }
+ Literal* getNull() { return std::get_if<Null>(&state); }
+ Literal* getSuccess() { return std::get_if<Success>(&state); }
+ Literal* getFailure() { return std::get_if<Failure>(&state); }
+ Literal* getNullOrFailure() {
+ if (auto* original = getNull()) {
+ return original;
+ } else {
+ return getFailure();
+ }
+ }
};
template<typename T> Cast doCast(T* curr) {
- Cast cast;
Flow ref = this->visit(curr->ref);
if (ref.breaking()) {
- cast.outcome = cast.Break;
- cast.breaking = ref;
- return cast;
+ return typename Cast::Breaking{ref};
}
+ // The RTT value for the type we are trying to cast to.
Literal intendedRtt;
if (curr->rtt) {
- // This is a dynamic check with an 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;
+ return typename Cast::Breaking{ref};
}
intendedRtt = rtt.getSingleValue();
+ } else {
+ // If there is no explicit RTT, use the canonical RTT for the static type.
+ intendedRtt = Literal::makeCanonicalRtt(curr->intendedType);
}
- cast.originalRef = ref.getSingleValue();
- if (cast.originalRef.isNull()) {
- cast.outcome = cast.Null;
- return cast;
+ Literal original = ref.getSingleValue();
+ if (original.isNull()) {
+ return typename Cast::Null{original};
}
// The input may not be GC data or a function; for example it could be an
- // anyref of null (already handled above) or anything else (handled here,
- // but this is for future use as atm the binaryen interpreter cannot
- // represent external references).
- if (!cast.originalRef.isData() && !cast.originalRef.isFunction()) {
- cast.outcome = cast.Failure;
- return cast;
- }
- 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.
- // We must have a module in order to perform the cast, to get the type. If
- // we do not have one, or if the function is not present (which may happen
- // if we are optimizing a function before the entire module is built),
- // then this is something we cannot precompute.
- auto* func = module
- ? module->getFunctionOrNull(cast.originalRef.getFunc())
- : nullptr;
+ // externref or an i31. The cast definitely fails in these cases.
+ if (!original.isData() && !original.isFunction()) {
+ return typename Cast::Failure{original};
+ }
+ Literal actualRtt;
+ if (original.isFunction()) {
+ // Function references always have the canonical RTTs of the functions
+ // they reference. We must have a module to look up the function's type to
+ // get that canonical RTT.
+ auto* func =
+ module ? module->getFunctionOrNull(original.getFunc()) : nullptr;
if (!func) {
- cast.outcome = cast.Break;
- cast.breaking = NONCONSTANT_FLOW;
- 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));
+ return typename Cast::Breaking{NONCONSTANT_FLOW};
}
+ actualRtt = Literal::makeCanonicalRtt(func->type);
} else {
- // GC data store an RTT in each instance.
- assert(cast.originalRef.isData());
- auto gcData = cast.originalRef.getGCData();
- assert(bool(curr->rtt) == gcData->hasRtt());
- if (curr->rtt) {
- auto seenRtt = gcData->getRtt();
- if (!seenRtt.isSubRtt(intendedRtt)) {
- cast.outcome = cast.Failure;
- return cast;
- }
- cast.castRef =
- Literal(gcData, Type(intendedRtt.type.getHeapType(), NonNullable));
+ assert(original.isData());
+ actualRtt = original.getGCData()->rtt;
+ };
+ // We have the actual and intended RTTs, so perform the cast.
+ if (actualRtt.isSubRtt(intendedRtt)) {
+ Type resultType(intendedRtt.type.getHeapType(), NonNullable);
+ if (original.isFunction()) {
+ return typename Cast::Success{Literal{original.getFunc(), resultType}};
} else {
- auto seenType = gcData->getHeapType();
- if (!HeapType::isSubType(seenType, curr->intendedType)) {
- cast.outcome = cast.Failure;
- return cast;
- }
- cast.castRef = Literal(gcData, Type(seenType, NonNullable));
+ return
+ typename Cast::Success{Literal(original.getGCData(), resultType)};
}
+ } else {
+ return typename Cast::Failure{original};
}
- cast.outcome = cast.Success;
- return cast;
}
Flow visitRefTest(RefTest* curr) {
NOTE_ENTER("RefTest");
auto cast = doCast(curr);
- if (cast.outcome == cast.Break) {
- return cast.breaking;
+ if (auto* breaking = cast.getBreaking()) {
+ return *breaking;
+ } else {
+ return Literal(int32_t(bool(cast.getSuccess())));
}
- return Literal(int32_t(cast.outcome == cast.Success));
}
Flow visitRefCast(RefCast* curr) {
NOTE_ENTER("RefCast");
auto cast = doCast(curr);
- if (cast.outcome == cast.Break) {
- return cast.breaking;
- }
- if (cast.outcome == cast.Null) {
+ if (auto* breaking = cast.getBreaking()) {
+ return *breaking;
+ } else if (cast.getNull()) {
return Literal::makeNull(Type(curr->type.getHeapType(), Nullable));
+ } else if (auto* result = cast.getSuccess()) {
+ return *result;
}
- if (cast.outcome == cast.Failure) {
- trap("cast error");
- }
- assert(cast.outcome == cast.Success);
- return cast.castRef;
+ assert(cast.getFailure());
+ trap("cast error");
+ WASM_UNREACHABLE("unreachable");
}
Flow visitBrOn(BrOn* curr) {
NOTE_ENTER("BrOn");
- // BrOnCast* uses the casting infrastructure, so handle it first.
+ // BrOnCast* uses the casting infrastructure, so handle them first.
if (curr->op == BrOnCast || curr->op == BrOnCastFail) {
auto cast = doCast(curr);
- if (cast.outcome == cast.Break) {
- return cast.breaking;
- }
- if (cast.outcome == cast.Null || cast.outcome == cast.Failure) {
+ if (auto* breaking = cast.getBreaking()) {
+ return *breaking;
+ } else if (auto* original = cast.getNullOrFailure()) {
if (curr->op == BrOnCast) {
- return cast.originalRef;
+ return *original;
} else {
- return Flow(curr->name, cast.originalRef);
+ return Flow(curr->name, *original);
}
- }
- assert(cast.outcome == cast.Success);
- if (curr->op == BrOnCast) {
- return Flow(curr->name, cast.castRef);
} else {
- return cast.castRef;
+ auto* result = cast.getSuccess();
+ assert(result);
+ if (curr->op == BrOnCast) {
+ return Flow(curr->name, *result);
+ } else {
+ return *result;
+ }
}
}
// The others do a simpler check for the type.
@@ -1619,7 +1605,9 @@ public:
}
return {value};
}
- Flow visitRttCanon(RttCanon* curr) { return Literal(curr->type); }
+ Flow visitRttCanon(RttCanon* curr) {
+ return Literal::makeCanonicalRtt(curr->type.getHeapType());
+ }
Flow visitRttSub(RttSub* curr) {
Flow parent = this->visit(curr->parent);
if (parent.breaking()) {
@@ -1627,34 +1615,22 @@ public:
}
auto parentValue = parent.getSingleValue();
auto newSupers = std::make_unique<RttSupers>(parentValue.getRttSupers());
- newSupers->push_back(parentValue.type);
+ newSupers->push_back(parentValue.type.getHeapType());
if (curr->fresh) {
newSupers->back().makeFresh();
}
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");
- Flow rtt;
+ Literal rttVal;
if (curr->rtt) {
- rtt = this->visit(curr->rtt);
+ Flow rtt = this->visit(curr->rtt);
if (rtt.breaking()) {
return rtt;
}
+ rttVal = rtt.getSingleValue();
}
if (curr->type == Type::unreachable) {
// We cannot proceed to compute the heap type, as there isn't one. Just
@@ -1681,8 +1657,10 @@ public:
data[i] = value.getSingleValue();
}
}
- return Flow(
- Literal(makeGCData(curr->rtt, rtt, heapType, data), curr->type));
+ if (!curr->rtt) {
+ rttVal = Literal::makeCanonicalRtt(heapType);
+ }
+ return Literal(std::make_shared<GCData>(rttVal, data), curr->type);
}
Flow visitStructGet(StructGet* curr) {
NOTE_ENTER("StructGet");
@@ -1725,12 +1703,13 @@ public:
Flow visitArrayNew(ArrayNew* curr) {
NOTE_ENTER("ArrayNew");
- Flow rtt;
+ Literal rttVal;
if (curr->rtt) {
- rtt = this->visit(curr->rtt);
+ Flow rtt = this->visit(curr->rtt);
if (rtt.breaking()) {
return rtt;
}
+ rttVal = rtt.getSingleValue();
}
auto size = this->visit(curr->size);
if (size.breaking()) {
@@ -1765,17 +1744,20 @@ public:
data[i] = value;
}
}
- return Flow(
- Literal(makeGCData(curr->rtt, rtt, heapType, data), curr->type));
+ if (!curr->rtt) {
+ rttVal = Literal::makeCanonicalRtt(heapType);
+ }
+ return Literal(std::make_shared<GCData>(rttVal, data), curr->type);
}
Flow visitArrayInit(ArrayInit* curr) {
NOTE_ENTER("ArrayInit");
- Flow rtt;
+ Literal rttVal;
if (curr->rtt) {
- rtt = this->visit(curr->rtt);
+ Flow rtt = this->visit(curr->rtt);
if (rtt.breaking()) {
return rtt;
}
+ rttVal = rtt.getSingleValue();
}
Index num = curr->values.size();
if (num >= ArrayLimit) {
@@ -1802,8 +1784,10 @@ public:
}
data[i] = truncateForPacking(value.getSingleValue(), field);
}
- return Flow(
- Literal(makeGCData(curr->rtt, rtt, heapType, data), curr->type));
+ if (!curr->rtt) {
+ rttVal = Literal::makeCanonicalRtt(heapType);
+ }
+ return Literal(std::make_shared<GCData>(rttVal, data), curr->type);
}
Flow visitArrayGet(ArrayGet* curr) {
NOTE_ENTER("ArrayGet");