blob: c495a4187718e0a5d6634088022d9324168b53fe [file] [log] [blame] [edit]
#include "ir/possible-contents.h"
#include "ir/subtypes.h"
#include "parser/wat-parser.h"
#include "wasm.h"
#include "gtest/gtest.h"
using namespace wasm;
// Asserts a == b, in any order.
template<typename T> void assertEqualSymmetric(const T& a, const T& b) {
EXPECT_EQ(a, b);
EXPECT_EQ(b, a);
EXPECT_PRED2([](const T& a, const T& b) { return !(a != b); }, a, b);
EXPECT_PRED2([](const T& a, const T& b) { return !(b != a); }, a, b);
}
// Asserts a != b, in any order.
template<typename T> void assertNotEqualSymmetric(const T& a, const T& b) {
EXPECT_NE(a, b);
EXPECT_NE(b, a);
EXPECT_PRED2([](const T& a, const T& b) { return !(a == b); }, a, b);
EXPECT_PRED2([](const T& a, const T& b) { return !(b == a); }, a, b);
}
// Asserts a combined with b (in any order) is equal to c.
template<typename T>
void assertCombination(const T& a, const T& b, const T& c) {
T temp1 = PossibleContents::combine(a, b);
assertEqualSymmetric(temp1, c);
// Also check the type, as nulls will compare equal even if their types
// differ. We want to make sure even the types are identical.
assertEqualSymmetric(temp1.getType(), c.getType());
T temp2 = PossibleContents::combine(b, a);
assertEqualSymmetric(temp2, c);
assertEqualSymmetric(temp2.getType(), c.getType());
// Verify the shorthand API works like the static one.
T temp3 = a;
temp3.combine(b);
assertEqualSymmetric(temp3, temp1);
T temp4 = b;
temp4.combine(a);
assertEqualSymmetric(temp4, temp2);
}
// Parse a module from text and return it.
static std::unique_ptr<Module> parse(std::string module) {
auto wasm = std::make_unique<Module>();
wasm->features = FeatureSet::All;
auto parsed = WATParser::parseModule(*wasm, module);
if (auto* err = parsed.getErr()) {
Fatal() << err->msg << "\n";
}
return wasm;
};
// We want to declare a bunch of globals that are used in various tests. Doing
// so in the global scope is risky, as their constructors use types, and the
// type system itself uses global constructors. Instead, we use a fixture for
// that.
class PossibleContentsTest : public testing::Test {
protected:
Type anyref = Type(HeapType::any, Nullable);
Type funcref = Type(HeapType::func, Nullable);
Type i31ref = Type(HeapType::i31, Nullable);
Type structref = Type(HeapType::struct_, Nullable);
PossibleContents none = PossibleContents::none();
PossibleContents i32Zero = PossibleContents::literal(Literal(int32_t(0)));
PossibleContents i32One = PossibleContents::literal(Literal(int32_t(1)));
PossibleContents f64One = PossibleContents::literal(Literal(double(1)));
PossibleContents anyNull =
PossibleContents::literal(Literal::makeNull(HeapType::any));
PossibleContents funcNull =
PossibleContents::literal(Literal::makeNull(HeapType::func));
PossibleContents i31Null =
PossibleContents::literal(Literal::makeNull(HeapType::i31));
PossibleContents i32Global1 =
PossibleContents::global("i32Global1", Type::i32);
PossibleContents i32Global2 =
PossibleContents::global("i32Global2", Type::i32);
PossibleContents f64Global = PossibleContents::global("f64Global", Type::f64);
PossibleContents anyGlobal = PossibleContents::global("anyGlobal", anyref);
PossibleContents funcGlobal = PossibleContents::global("funcGlobal", funcref);
PossibleContents nonNullFuncGlobal =
PossibleContents::global("funcGlobal", Type(HeapType::func, NonNullable));
PossibleContents nonNullFunc = PossibleContents::literal(
Literal("func", Signature(Type::none, Type::none)));
PossibleContents exactI32 = PossibleContents::exactType(Type::i32);
PossibleContents exactAnyref = PossibleContents::exactType(anyref);
PossibleContents exactFuncref = PossibleContents::exactType(funcref);
PossibleContents exactStructref = PossibleContents::exactType(structref);
PossibleContents exactI31ref = PossibleContents::exactType(i31ref);
PossibleContents exactNonNullAnyref =
PossibleContents::exactType(Type(HeapType::any, NonNullable));
PossibleContents exactNonNullFuncref =
PossibleContents::exactType(Type(HeapType::func, NonNullable));
PossibleContents exactNonNullI31ref =
PossibleContents::exactType(Type(HeapType::i31, NonNullable));
PossibleContents exactFuncSignatureType = PossibleContents::exactType(
Type(Signature(Type::none, Type::none), Nullable));
PossibleContents exactNonNullFuncSignatureType = PossibleContents::exactType(
Type(Signature(Type::none, Type::none), NonNullable));
PossibleContents many = PossibleContents::many();
PossibleContents coneAnyref = PossibleContents::fullConeType(anyref);
PossibleContents coneFuncref = PossibleContents::fullConeType(funcref);
PossibleContents coneFuncref1 = PossibleContents::coneType(funcref, 1);
};
TEST_F(PossibleContentsTest, TestComparisons) {
assertEqualSymmetric(none, none);
assertNotEqualSymmetric(none, i32Zero);
assertNotEqualSymmetric(none, i32Global1);
assertNotEqualSymmetric(none, exactI32);
assertNotEqualSymmetric(none, many);
assertEqualSymmetric(i32Zero, i32Zero);
assertNotEqualSymmetric(i32Zero, i32One);
assertNotEqualSymmetric(i32Zero, f64One);
assertNotEqualSymmetric(i32Zero, i32Global1);
assertNotEqualSymmetric(i32Zero, exactI32);
assertNotEqualSymmetric(i32Zero, many);
assertEqualSymmetric(i32Global1, i32Global1);
assertNotEqualSymmetric(i32Global1, i32Global2);
assertNotEqualSymmetric(i32Global1, exactI32);
assertNotEqualSymmetric(i32Global1, many);
assertEqualSymmetric(exactI32, exactI32);
assertNotEqualSymmetric(exactI32, exactAnyref);
assertNotEqualSymmetric(exactI32, many);
assertEqualSymmetric(many, many);
// Nulls
assertNotEqualSymmetric(i32Zero, anyNull);
assertNotEqualSymmetric(anyNull, funcNull);
assertEqualSymmetric(anyNull, anyNull);
assertEqualSymmetric(exactNonNullAnyref, exactNonNullAnyref);
assertNotEqualSymmetric(exactNonNullAnyref, exactAnyref);
}
TEST_F(PossibleContentsTest, TestHash) {
// Hashes should be deterministic.
EXPECT_EQ(none.hash(), none.hash());
EXPECT_EQ(many.hash(), many.hash());
// Hashes should be different. (In theory hash collisions could appear here,
// but if such simple things collide and the test fails then we should really
// rethink our hash functions!)
EXPECT_NE(none.hash(), many.hash());
EXPECT_NE(none.hash(), i32Zero.hash());
EXPECT_NE(none.hash(), i32One.hash());
EXPECT_NE(none.hash(), anyGlobal.hash());
EXPECT_NE(none.hash(), funcGlobal.hash());
EXPECT_NE(none.hash(), exactAnyref.hash());
EXPECT_NE(none.hash(), exactFuncSignatureType.hash());
EXPECT_NE(none.hash(), coneAnyref.hash());
EXPECT_NE(none.hash(), coneFuncref.hash());
EXPECT_NE(none.hash(), coneFuncref1.hash());
EXPECT_NE(i32Zero.hash(), i32One.hash());
EXPECT_NE(anyGlobal.hash(), funcGlobal.hash());
EXPECT_NE(exactAnyref.hash(), exactFuncSignatureType.hash());
EXPECT_NE(coneAnyref.hash(), coneFuncref.hash());
EXPECT_NE(coneAnyref.hash(), coneFuncref1.hash());
EXPECT_NE(coneFuncref.hash(), coneFuncref1.hash());
}
TEST_F(PossibleContentsTest, TestCombinations) {
// None with anything else becomes the other thing.
assertCombination(none, none, none);
assertCombination(none, i32Zero, i32Zero);
assertCombination(none, i32Global1, i32Global1);
assertCombination(none, exactI32, exactI32);
assertCombination(none, many, many);
// i32(0) will become Many, unless the value or the type is identical.
assertCombination(i32Zero, i32Zero, i32Zero);
assertCombination(i32Zero, i32One, exactI32);
assertCombination(i32Zero, f64One, many);
assertCombination(i32Zero, i32Global1, exactI32);
assertCombination(i32Zero, f64Global, many);
assertCombination(i32Zero, exactI32, exactI32);
assertCombination(i32Zero, exactAnyref, many);
assertCombination(i32Zero, many, many);
assertCombination(i32Global1, i32Global1, i32Global1);
assertCombination(i32Global1, i32Global2, exactI32);
assertCombination(i32Global1, f64Global, many);
assertCombination(i32Global1, exactI32, exactI32);
assertCombination(i32Global1, exactAnyref, many);
assertCombination(i32Global1, many, many);
assertCombination(exactI32, exactI32, exactI32);
assertCombination(exactI32, exactAnyref, many);
assertCombination(exactI32, many, many);
assertCombination(many, many, many);
// Exact references: An exact reference only stays exact when combined with
// the same heap type (nullability may be added, but nothing else). Otherwise
// we go to a cone type or to many.
assertCombination(exactFuncref, exactAnyref, many);
assertCombination(exactFuncref, anyGlobal, many);
assertCombination(exactFuncref, nonNullFunc, coneFuncref1);
assertCombination(exactFuncref, exactFuncref, exactFuncref);
assertCombination(exactFuncref, exactNonNullFuncref, exactFuncref);
// Nulls.
assertCombination(anyNull, i32Zero, many);
assertCombination(anyNull, anyNull, anyNull);
assertCombination(anyNull, exactAnyref, exactAnyref);
// Two nulls go to the lub.
assertCombination(anyNull, i31Null, anyNull);
// Incompatible nulls go to Many.
assertCombination(anyNull, funcNull, many);
assertCombination(exactNonNullAnyref, exactNonNullAnyref, exactNonNullAnyref);
// If one is a null and the other is not, it makes the one that is not a
// null be a nullable type - but keeps the heap type of the other (since the
// type of the null does not matter, all nulls compare equal).
assertCombination(anyNull, exactNonNullAnyref, exactAnyref);
assertCombination(anyNull, exactNonNullI31ref, exactI31ref);
// Funcrefs
// A function reference + a null becomes an exact type (of that sig), plus
// nullability.
assertCombination(nonNullFunc, funcNull, exactFuncSignatureType);
assertCombination(exactFuncSignatureType, funcNull, exactFuncSignatureType);
assertCombination(
exactNonNullFuncSignatureType, funcNull, exactFuncSignatureType);
assertCombination(
nonNullFunc, exactFuncSignatureType, exactFuncSignatureType);
assertCombination(
nonNullFunc, exactNonNullFuncSignatureType, exactNonNullFuncSignatureType);
assertCombination(nonNullFunc, exactI32, many);
// Globals vs nulls. The result is either the global or a null, so all we can
// say is that it is something of the global's type, or a null: a cone.
assertCombination(anyGlobal, anyNull, coneAnyref);
assertCombination(anyGlobal, i31Null, coneAnyref);
}
static PassOptions options;
TEST_F(PossibleContentsTest, TestOracleMinimal) {
// A minimal test of the public API of PossibleTypesOracle. See the lit test
// for coverage of all the internals (using lit makes the result more
// fuzzable).
auto wasm = parse(R"(
(module
(global $null (ref null any) (ref.null any))
(global $something i32 (i32.const 42))
)
)");
ContentOracle oracle(*wasm, options);
// This will be a null constant.
EXPECT_TRUE(oracle.getContents(GlobalLocation{"null"}).isNull());
// This will be 42.
EXPECT_EQ(oracle.getContents(GlobalLocation{"something"}).getLiteral(),
Literal(int32_t(42)));
}
// Asserts a and b have an intersection (or do not), and checks both orderings.
void assertHaveIntersection(PossibleContents a, PossibleContents b) {
EXPECT_TRUE(PossibleContents::haveIntersection(a, b));
EXPECT_TRUE(PossibleContents::haveIntersection(b, a));
#if BINARYEN_TEST_DEBUG
if (!PossibleContents::haveIntersection(a, b) ||
!PossibleContents::haveIntersection(b, a)) {
std::cout << "\nFailure: no intersection:\n" << a << '\n' << b << '\n';
abort();
}
#endif
}
void assertLackIntersection(PossibleContents a, PossibleContents b) {
EXPECT_FALSE(PossibleContents::haveIntersection(a, b));
EXPECT_FALSE(PossibleContents::haveIntersection(b, a));
}
TEST_F(PossibleContentsTest, TestIntersection) {
// None has no contents, so nothing to intersect.
assertLackIntersection(none, none);
assertLackIntersection(none, i32Zero);
assertLackIntersection(none, many);
// Many intersects with anything (but none).
assertHaveIntersection(many, many);
assertHaveIntersection(many, i32Zero);
// Different exact types cannot intersect.
assertLackIntersection(exactI32, exactAnyref);
assertLackIntersection(i32Zero, exactAnyref);
// But nullable ones can - the null can be the intersection, if they are not
// in separate hierarchies.
assertHaveIntersection(exactFuncSignatureType, funcNull);
assertLackIntersection(exactFuncSignatureType, exactAnyref);
assertLackIntersection(anyNull, funcNull);
// Identical types might.
assertHaveIntersection(exactI32, exactI32);
assertHaveIntersection(i32Zero, i32Zero);
assertHaveIntersection(exactFuncSignatureType, exactFuncSignatureType);
assertLackIntersection(i32Zero, i32One);
// Exact types only differing by nullability can intersect (not on the null,
// but on something else).
assertHaveIntersection(exactAnyref, exactNonNullAnyref);
// Due to subtyping, an intersection might exist.
assertHaveIntersection(funcGlobal, funcGlobal);
assertHaveIntersection(funcGlobal, exactFuncSignatureType);
assertHaveIntersection(nonNullFuncGlobal, exactFuncSignatureType);
assertHaveIntersection(funcGlobal, exactNonNullFuncSignatureType);
assertHaveIntersection(nonNullFuncGlobal, exactNonNullFuncSignatureType);
// Separate hierarchies.
assertLackIntersection(funcGlobal, anyGlobal);
}
TEST_F(PossibleContentsTest, TestIntersectWithCombinations) {
// Whenever we combine C = A + B, both A and B must intersect with C. This
// helper function gets a set of things and checks that property on them. It
// returns the set of all contents it ever observed (see below for how we use
// that).
auto doTest = [](std::unordered_set<PossibleContents> set) {
std::vector<PossibleContents> vec(set.begin(), set.end());
// Find the maximum depths for the normalized cone tests later down.
std::unordered_set<HeapType> heapTypes;
for (auto& contents : set) {
auto type = contents.getType();
if (type.isRef()) {
auto heapType = type.getHeapType();
if (!heapType.isBasic()) {
heapTypes.insert(heapType);
}
}
}
std::vector<HeapType> heapTypesVec(heapTypes.begin(), heapTypes.end());
SubTypes subTypes(heapTypesVec);
auto maxDepths = subTypes.getMaxDepths();
// Go over all permutations up to a certain size (this quickly becomes
// extremely slow, obviously, so keep this low).
size_t max = 3;
auto n = set.size();
// |indexes| contains the indexes of the items in vec for the current
// permutation.
std::vector<size_t> indexes(max);
std::fill(indexes.begin(), indexes.end(), 0);
while (1) {
// Test the current permutation: Combine all the relevant things, and then
// check they all have an intersection.
PossibleContents combination;
for (auto index : indexes) {
combination.combine(vec[index]);
}
// Note the combination in the set.
set.insert(combination);
#if BINARYEN_TEST_DEBUG
for (auto index : indexes) {
std::cout << index << ' ';
combination.combine(vec[index]);
}
std::cout << '\n';
#endif
for (auto index : indexes) {
auto item = vec[index];
if (item.isNone()) {
assertLackIntersection(combination, item);
continue;
}
#if BINARYEN_TEST_DEBUG
if (!PossibleContents::haveIntersection(combination, item)) {
std::cout << "\nFailure: no expected intersection. Indexes:\n";
for (auto index : indexes) {
std::cout << index << "\n ";
vec[index].dump(std::cout);
std::cout << '\n';
}
std::cout << "combo:\n";
combination.dump(std::cout);
std::cout << "\ncompared item (index " << index << "):\n";
item.dump(std::cout);
std::cout << '\n';
abort();
}
#endif
assertHaveIntersection(combination, item);
auto type = combination.getType();
if (type.isRef()) {
// If we normalize the combination's depth, the item must still have
// an intersection. That is, normalization must not have a bug that
// results in cones that are too shallow.
auto normalizedDepth = maxDepths[type.getHeapType()];
auto normalizedCone =
PossibleContents::coneType(type, normalizedDepth);
assertHaveIntersection(normalizedCone, item);
}
// Test intersect() method, which is supported with a full cone type.
// In that case we can test that the intersection of A with A + B is
// simply A.
if (combination.isFullConeType()) {
auto intersection = item;
intersection.intersect(combination);
EXPECT_EQ(intersection, item);
#if BINARYEN_TEST_DEBUG
if (intersection != item) {
std::cout << "\nFailure: wrong intersection.\n";
std::cout << "item: " << item << '\n';
std::cout << "combination: " << combination << '\n';
std::cout << "intersection: " << intersection << '\n';
abort();
}
#endif
// The intersection is contained in each of the things we intersected
// (but we can only compare to the full cone, as the API is restricted
// to that).
EXPECT_TRUE(
PossibleContents::isSubContents(intersection, combination));
}
}
// Move to the next permutation.
size_t i = 0;
while (1) {
indexes[i]++;
if (indexes[i] == n) {
// Overflow.
indexes[i] = 0;
i++;
if (i == max) {
// All done.
return set;
}
} else {
break;
}
}
}
WASM_UNREACHABLE("loop above returns manually");
};
// Start from an initial set of the hardcoded contents we have in our test
// fixture.
std::unordered_set<PossibleContents> initial = {none,
f64One,
anyNull,
funcNull,
i31Null,
i32Global1,
i32Global2,
f64Global,
anyGlobal,
funcGlobal,
nonNullFuncGlobal,
nonNullFunc,
exactI32,
exactAnyref,
exactFuncref,
exactStructref,
exactI31ref,
exactNonNullAnyref,
exactNonNullFuncref,
exactNonNullI31ref,
exactFuncSignatureType,
exactNonNullFuncSignatureType,
many,
coneAnyref,
coneFuncref,
coneFuncref1};
// Add some additional interesting types.
auto structType =
Type(HeapType(Struct({Field(Type::i32, Immutable)})), NonNullable);
initial.insert(PossibleContents::coneType(structType, 0));
auto arrayType =
Type(HeapType(Array(Field(Type::i32, Immutable))), NonNullable);
initial.insert(PossibleContents::coneType(arrayType, 0));
// After testing on the initial contents, also test using anything new that
// showed up while combining them.
auto subsequent = doTest(initial);
while (subsequent.size() > initial.size()) {
initial = subsequent;
subsequent = doTest(subsequent);
}
}
void assertIntersection(PossibleContents a,
PossibleContents b,
PossibleContents result) {
auto intersection = a;
intersection.intersect(b);
EXPECT_EQ(intersection, result);
EXPECT_EQ(PossibleContents::haveIntersection(a, b), !result.isNone());
}
TEST_F(PossibleContentsTest, TestStructCones) {
/*
A E
/ \
B C
\
D
*/
TypeBuilder builder(5);
builder.createRecGroup(0, 5);
builder[0].setOpen() = Struct(FieldList{});
builder[1].setOpen().subTypeOf(builder[0]) = Struct(FieldList{});
builder[2].setOpen().subTypeOf(builder[0]) = Struct(FieldList{});
builder[3].setOpen().subTypeOf(builder[2]) = Struct(FieldList{});
builder[4].setOpen() = Struct(FieldList{});
auto result = builder.build();
ASSERT_TRUE(result);
auto types = *result;
auto A = types[0];
auto B = types[1];
auto C = types[2];
auto D = types[3];
auto E = types[4];
ASSERT_FALSE(A.getDeclaredSuperType());
ASSERT_EQ(B.getDeclaredSuperType(), A);
ASSERT_EQ(C.getDeclaredSuperType(), A);
ASSERT_EQ(D.getDeclaredSuperType(), C);
auto nullA = Type(A, Nullable);
auto nullB = Type(B, Nullable);
auto nullC = Type(C, Nullable);
auto nullD = Type(D, Nullable);
auto nullE = Type(E, Nullable);
auto exactA = PossibleContents::exactType(nullA);
auto exactB = PossibleContents::exactType(nullB);
auto exactC = PossibleContents::exactType(nullC);
auto exactD = PossibleContents::exactType(nullD);
auto exactE = PossibleContents::exactType(nullE);
auto nnA = Type(A, NonNullable);
auto nnB = Type(B, NonNullable);
auto nnC = Type(C, NonNullable);
auto nnD = Type(D, NonNullable);
auto nnE = Type(E, NonNullable);
auto nnExactA = PossibleContents::exactType(nnA);
auto nnExactB = PossibleContents::exactType(nnB);
auto nnExactC = PossibleContents::exactType(nnC);
auto nnExactD = PossibleContents::exactType(nnD);
auto nnExactE = PossibleContents::exactType(nnE);
assertCombination(exactA, exactA, exactA);
assertCombination(exactA, exactA, PossibleContents::coneType(nullA, 0));
assertCombination(exactA, exactB, PossibleContents::coneType(nullA, 1));
assertCombination(exactA, exactC, PossibleContents::coneType(nullA, 1));
assertCombination(exactA, exactD, PossibleContents::coneType(nullA, 2));
assertCombination(exactA, exactE, PossibleContents::coneType(structref, 1));
assertCombination(
exactA, exactStructref, PossibleContents::coneType(structref, 1));
assertCombination(exactB, exactB, exactB);
assertCombination(exactB, exactC, PossibleContents::coneType(nullA, 1));
assertCombination(exactB, exactD, PossibleContents::coneType(nullA, 2));
assertCombination(exactB, exactE, PossibleContents::coneType(structref, 2));
assertCombination(
exactB, exactStructref, PossibleContents::coneType(structref, 2));
assertCombination(exactC, exactC, exactC);
assertCombination(exactC, exactD, PossibleContents::coneType(nullC, 1));
assertCombination(exactC, exactE, PossibleContents::coneType(structref, 2));
assertCombination(
exactC, exactStructref, PossibleContents::coneType(structref, 2));
assertCombination(exactD, exactD, exactD);
assertCombination(exactD, exactE, PossibleContents::coneType(structref, 3));
assertCombination(
exactD, exactStructref, PossibleContents::coneType(structref, 3));
assertCombination(exactE, exactE, exactE);
assertCombination(
exactE, exactStructref, PossibleContents::coneType(structref, 1));
assertCombination(exactStructref, exactStructref, exactStructref);
assertCombination(
exactStructref, exactAnyref, PossibleContents::coneType(anyref, 2));
// Combinations of cones.
assertCombination(PossibleContents::coneType(nullA, 5),
PossibleContents::coneType(nullA, 7),
PossibleContents::coneType(nullA, 7));
// Increment the cone of D as we go here, until it matters.
assertCombination(PossibleContents::coneType(nullA, 5),
PossibleContents::coneType(nullD, 2),
PossibleContents::coneType(nullA, 5));
assertCombination(PossibleContents::coneType(nullA, 5),
PossibleContents::coneType(nullD, 3),
PossibleContents::coneType(nullA, 5));
assertCombination(PossibleContents::coneType(nullA, 5),
PossibleContents::coneType(nullD, 4),
PossibleContents::coneType(nullA, 6));
assertCombination(PossibleContents::coneType(nullA, 5),
PossibleContents::coneType(nullE, 7),
PossibleContents::coneType(structref, 8));
assertCombination(PossibleContents::coneType(nullB, 4),
PossibleContents::coneType(structref, 1),
PossibleContents::coneType(structref, 6));
// Combinations of cones and exact types.
assertCombination(exactA,
PossibleContents::coneType(nullA, 3),
PossibleContents::coneType(nullA, 3));
assertCombination(exactA,
PossibleContents::coneType(nullD, 3),
PossibleContents::coneType(nullA, 5));
assertCombination(exactD,
PossibleContents::coneType(nullA, 3),
PossibleContents::coneType(nullA, 3));
assertCombination(exactA,
PossibleContents::coneType(nullE, 2),
PossibleContents::coneType(structref, 3));
assertCombination(exactA,
PossibleContents::coneType(structref, 1),
PossibleContents::coneType(structref, 1));
assertCombination(exactA,
PossibleContents::coneType(structref, 2),
PossibleContents::coneType(structref, 2));
assertCombination(exactStructref,
PossibleContents::coneType(nullB, 3),
PossibleContents::coneType(structref, 5));
// Full cones.
assertCombination(PossibleContents::fullConeType(nullA),
exactA,
PossibleContents::fullConeType(nullA));
assertCombination(PossibleContents::fullConeType(nullA),
PossibleContents::coneType(nullA, 2),
PossibleContents::fullConeType(nullA));
// All full cones with A remain full cones, except for E.
assertCombination(PossibleContents::fullConeType(nullA),
PossibleContents::fullConeType(nullA),
PossibleContents::fullConeType(nullA));
assertCombination(PossibleContents::fullConeType(nullA),
PossibleContents::fullConeType(nullB),
PossibleContents::fullConeType(nullA));
assertCombination(PossibleContents::fullConeType(nullA),
PossibleContents::fullConeType(nullC),
PossibleContents::fullConeType(nullA));
assertCombination(PossibleContents::fullConeType(nullA),
PossibleContents::fullConeType(nullD),
PossibleContents::fullConeType(nullA));
assertCombination(PossibleContents::fullConeType(nullA),
PossibleContents::fullConeType(nullE),
PossibleContents::fullConeType(structref));
// Intersections. Test with non-nullable types to avoid the null being a
// possible intersection.
assertHaveIntersection(nnExactA, nnExactA);
assertLackIntersection(nnExactA, nnExactB);
assertLackIntersection(nnExactA, nnExactC);
assertLackIntersection(nnExactA, nnExactD);
assertLackIntersection(nnExactA, nnExactE);
assertHaveIntersection(PossibleContents::coneType(nnA, 1), nnExactB);
assertHaveIntersection(PossibleContents::coneType(nnA, 1), nnExactC);
assertHaveIntersection(PossibleContents::coneType(nnA, 2), nnExactD);
assertLackIntersection(PossibleContents::coneType(nnA, 1), nnExactD);
assertLackIntersection(PossibleContents::coneType(nnA, 1), nnExactE);
assertLackIntersection(PossibleContents::coneType(nnA, 2), nnExactE);
assertHaveIntersection(PossibleContents::coneType(nnA, 1),
PossibleContents::coneType(nnC, 100));
assertLackIntersection(PossibleContents::coneType(nnA, 1),
PossibleContents::coneType(nnD, 100));
// Neither is a subtype of the other, but nulls are possible, so a null can be
// the intersection.
assertHaveIntersection(PossibleContents::fullConeType(nullA),
PossibleContents::fullConeType(nullE));
// Without null on one side, we cannot intersect.
assertLackIntersection(PossibleContents::fullConeType(nnA),
PossibleContents::fullConeType(nullE));
// Computing intersections is supported with a full cone type.
assertIntersection(none, PossibleContents::fullConeType(nnA), none);
assertIntersection(many,
PossibleContents::fullConeType(nnA),
PossibleContents::fullConeType(nnA));
assertIntersection(many,
PossibleContents::fullConeType(nullA),
PossibleContents::fullConeType(nullA));
assertIntersection(exactA, PossibleContents::fullConeType(nullA), exactA);
assertIntersection(nnExactA, PossibleContents::fullConeType(nullA), nnExactA);
assertIntersection(exactA, PossibleContents::fullConeType(nnA), nnExactA);
assertIntersection(exactB, PossibleContents::fullConeType(nullA), exactB);
assertIntersection(nnExactB, PossibleContents::fullConeType(nullA), nnExactB);
assertIntersection(exactB, PossibleContents::fullConeType(nnA), nnExactB);
auto literalNullA = PossibleContents::literal(Literal::makeNull(A));
assertIntersection(
literalNullA, PossibleContents::fullConeType(nullA), literalNullA);
assertIntersection(literalNullA, PossibleContents::fullConeType(nnA), none);
assertIntersection(
literalNullA, PossibleContents::fullConeType(nullB), literalNullA);
assertIntersection(literalNullA, PossibleContents::fullConeType(nnB), none);
assertIntersection(
literalNullA, PossibleContents::fullConeType(nullE), literalNullA);
assertIntersection(literalNullA, PossibleContents::fullConeType(nnE), none);
assertIntersection(exactA,
PossibleContents::fullConeType(nullB),
PossibleContents::literal(Literal::makeNull(B)));
assertIntersection(nnExactA, PossibleContents::fullConeType(nullB), none);
assertIntersection(exactA, PossibleContents::fullConeType(nnB), none);
// A and E have no intersection, so the only possibility is a null, and that
// null must be the bottom type.
assertIntersection(
exactA,
PossibleContents::fullConeType(nullE),
PossibleContents::literal(Literal::makeNull(HeapType::none)));
assertIntersection(PossibleContents::coneType(nnA, 1),
PossibleContents::fullConeType(nnB),
nnExactB);
assertIntersection(PossibleContents::coneType(nnB, 1),
PossibleContents::fullConeType(nnA),
PossibleContents::coneType(nnB, 1));
assertIntersection(PossibleContents::coneType(nnD, 2),
PossibleContents::fullConeType(nnA),
PossibleContents::coneType(nnD, 2));
assertIntersection(PossibleContents::coneType(nnA, 5),
PossibleContents::fullConeType(nnD),
PossibleContents::coneType(nnD, 3));
assertIntersection(PossibleContents::coneType(nnA, 1),
PossibleContents::fullConeType(nnD),
none);
// Globals stay as globals, but their type might get refined.
assertIntersection(
funcGlobal, PossibleContents::fullConeType(funcref), funcGlobal);
// No global filtering.
auto signature = Type(Signature(Type::none, Type::none), Nullable);
assertIntersection(
nonNullFunc, PossibleContents::fullConeType(signature), nonNullFunc);
// Filter a global to a more specific type.
assertIntersection(funcGlobal,
PossibleContents::fullConeType(signature),
PossibleContents::global("funcGlobal", signature));
// Filter a global's nullability only.
auto nonNullFuncRef = Type(HeapType::func, NonNullable);
assertIntersection(funcGlobal,
PossibleContents::fullConeType(nonNullFuncRef),
nonNullFuncGlobal);
// Incompatible global and cone types have no intersection.
assertIntersection(funcGlobal, PossibleContents::fullConeType(nullE), none);
// Incompatible hierarchies have no intersection.
assertIntersection(
literalNullA, PossibleContents::fullConeType(funcref), none);
// Computing intersections is also supported with a Literal.
assertIntersection(i32Zero, i32Zero, i32Zero);
assertIntersection(i32One, i32Zero, none);
assertIntersection(i32Global1, i32Zero, i32Zero);
assertIntersection(funcGlobal, i32Zero, none);
assertIntersection(
PossibleContents::fullConeType(Type::i32), i32Zero, i32Zero);
assertIntersection(PossibleContents::fullConeType(Type::f64), i32Zero, none);
// Computing intersections is also supported with empty contents.
assertIntersection(none, none, none);
assertIntersection(literalNullA, none, none);
assertIntersection(funcGlobal, none, none);
assertIntersection(PossibleContents::fullConeType(signature), none, none);
// Subcontents. This API only supports the case where one of the inputs is a
// full cone type.
// First, compare exact types to such a cone.
EXPECT_TRUE(PossibleContents::isSubContents(
exactA, PossibleContents::fullConeType(nullA)));
EXPECT_TRUE(PossibleContents::isSubContents(
nnExactA, PossibleContents::fullConeType(nnA)));
EXPECT_TRUE(PossibleContents::isSubContents(
nnExactA, PossibleContents::fullConeType(nullA)));
EXPECT_TRUE(PossibleContents::isSubContents(
nnExactD, PossibleContents::fullConeType(nullA)));
EXPECT_FALSE(PossibleContents::isSubContents(
exactA, PossibleContents::fullConeType(nnA)));
EXPECT_FALSE(PossibleContents::isSubContents(
exactA, PossibleContents::fullConeType(nullB)));
// Next, compare cones.
EXPECT_TRUE(
PossibleContents::isSubContents(PossibleContents::fullConeType(nullA),
PossibleContents::fullConeType(nullA)));
EXPECT_TRUE(
PossibleContents::isSubContents(PossibleContents::fullConeType(nnA),
PossibleContents::fullConeType(nullA)));
EXPECT_TRUE(PossibleContents::isSubContents(
PossibleContents::fullConeType(nnA), PossibleContents::fullConeType(nnA)));
EXPECT_TRUE(
PossibleContents::isSubContents(PossibleContents::fullConeType(nullD),
PossibleContents::fullConeType(nullA)));
EXPECT_FALSE(
PossibleContents::isSubContents(PossibleContents::fullConeType(nullA),
PossibleContents::fullConeType(nnA)));
EXPECT_FALSE(
PossibleContents::isSubContents(PossibleContents::fullConeType(nullA),
PossibleContents::fullConeType(nullD)));
// Trivial values.
EXPECT_TRUE(PossibleContents::isSubContents(
PossibleContents::none(), PossibleContents::fullConeType(nullA)));
EXPECT_FALSE(PossibleContents::isSubContents(
PossibleContents::many(), PossibleContents::fullConeType(nullA)));
EXPECT_TRUE(PossibleContents::isSubContents(
anyNull, PossibleContents::fullConeType(nullA)));
EXPECT_FALSE(PossibleContents::isSubContents(
anyNull, PossibleContents::fullConeType(nnA)));
// Tests cases with a full cone only on the left. Such a cone is only a sub-
// contents of Many.
EXPECT_FALSE(PossibleContents::isSubContents(
PossibleContents::fullConeType(nullA), exactA));
EXPECT_FALSE(PossibleContents::isSubContents(
PossibleContents::fullConeType(nullA), nnExactA));
EXPECT_FALSE(PossibleContents::isSubContents(
PossibleContents::fullConeType(nullA), PossibleContents::none()));
EXPECT_TRUE(PossibleContents::isSubContents(
PossibleContents::fullConeType(nullA), PossibleContents::many()));
EXPECT_FALSE(PossibleContents::isSubContents(
PossibleContents::fullConeType(nullA), anyNull));
EXPECT_FALSE(PossibleContents::isSubContents(
PossibleContents::fullConeType(nnA), anyNull));
}
TEST_F(PossibleContentsTest, TestOracleManyTypes) {
// Test for a node with many possible types. The pass limits how many it
// notices to not use excessive memory, so even though 4 are possible here,
// we'll just report that more than one is possible, a cone of data.
auto wasm = parse(R"(
(module
(type $A (sub (struct (field i32))))
(type $B (sub (struct (field i64))))
(type $C (sub (struct (field f32))))
(type $D (sub (struct (field f64))))
(func $foo (result (ref any))
(select (result (ref any))
(select (result (ref any))
(struct.new_default $A)
(struct.new_default $B)
(i32.const 0)
)
(select (result (ref any))
(struct.new_default $C)
(struct.new_default $D)
(i32.const 0)
)
(i32.const 0)
)
)
)
)");
ContentOracle oracle(*wasm, options);
// The body's contents must be a cone of data with depth 1.
auto bodyContents =
oracle.getContents(ResultLocation{wasm->getFunction("foo"), 0});
ASSERT_TRUE(bodyContents.isConeType());
EXPECT_EQ(bodyContents.getType().getHeapType(), HeapType::struct_);
EXPECT_EQ(bodyContents.getCone().depth, Index(1));
}
TEST_F(PossibleContentsTest, TestOracleNoFullCones) {
// PossibleContents should be normalized, so we never have full cones (depth
// infinity).
auto wasm = parse(R"(
(module
(type $A (sub (struct (field i32))))
(type $B (sub $A (struct (field i32))))
(type $C (sub $B (struct (field i32))))
(func $foo (export "foo")
;; Note we must declare $C so that $B and $C have uses and are not
;; removed automatically from consideration.
(param $a (ref $A)) (param $c (ref $C))
(result (ref any))
(local.get $a)
)
)
)");
ContentOracle oracle(*wasm, options);
// The function is exported, and all we know about the parameter $a is that it
// is some subtype of $A. This is normalized to depth 2 because that is the
// actual depth of subtypes.
auto bodyContents =
oracle.getContents(ResultLocation{wasm->getFunction("foo"), 0});
ASSERT_TRUE(bodyContents.isConeType());
EXPECT_EQ(bodyContents.getCone().depth, Index(2));
}