// Copyright 2016 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/asmjs/asm-types.h" #include <unordered_map> #include <unordered_set> #include "src/base/macros.h" #include "test/unittests/test-utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace v8 { namespace internal { namespace wasm { namespace { using ::testing::StrEq; class AsmTypeTest : public TestWithZone { public: using Type = AsmType; AsmTypeTest() : parents_({ {Type::Uint8Array(), {Type::Heap()}}, {Type::Int8Array(), {Type::Heap()}}, {Type::Uint16Array(), {Type::Heap()}}, {Type::Int16Array(), {Type::Heap()}}, {Type::Uint32Array(), {Type::Heap()}}, {Type::Int32Array(), {Type::Heap()}}, {Type::Float32Array(), {Type::Heap()}}, {Type::Float64Array(), {Type::Heap()}}, {Type::Float(), {Type::FloatishDoubleQ(), Type::FloatQDoubleQ(), Type::FloatQ(), Type::Floatish()}}, {Type::Floatish(), {Type::FloatishDoubleQ()}}, {Type::FloatQ(), {Type::FloatishDoubleQ(), Type::FloatQDoubleQ(), Type::Floatish()}}, {Type::FixNum(), {Type::Signed(), Type::Extern(), Type::Unsigned(), Type::Int(), Type::Intish()}}, {Type::Unsigned(), {Type::Int(), Type::Intish()}}, {Type::Signed(), {Type::Extern(), Type::Int(), Type::Intish()}}, {Type::Int(), {Type::Intish()}}, {Type::DoubleQ(), {Type::FloatishDoubleQ(), Type::FloatQDoubleQ()}}, {Type::Double(), {Type::FloatishDoubleQ(), Type::FloatQDoubleQ(), Type::DoubleQ(), Type::Extern()}}, }) {} protected: std::unordered_set<Type*> ParentsOf(Type* derived) const { const auto parents_iter = parents_.find(derived); if (parents_iter == parents_.end()) { return std::unordered_set<Type*>(); } return parents_iter->second; } class FunctionTypeBuilder { public: FunctionTypeBuilder(FunctionTypeBuilder&& b) V8_NOEXCEPT : function_type_(b.function_type_) { b.function_type_ = nullptr; } FunctionTypeBuilder& operator=(FunctionTypeBuilder&& b) V8_NOEXCEPT { if (this != &b) { function_type_ = b.function_type_; b.function_type_ = nullptr; } return *this; } FunctionTypeBuilder(Zone* zone, Type* return_type) : function_type_(Type::Function(zone, return_type)) {} private: static void AddAllArguments(AsmFunctionType*) {} template <typename Arg, typename... Others> static void AddAllArguments(AsmFunctionType* function_type, Arg* arg, Others... others) { CHECK_NOT_NULL(function_type); function_type->AddArgument((*arg)()); AddAllArguments(function_type, others...); } public: template <typename... Args> Type* operator()(Args... args) { Type* ret = function_type_; function_type_ = nullptr; AddAllArguments(ret->AsFunctionType(), args...); return ret; } private: Type* function_type_; }; FunctionTypeBuilder Function(Type* (*return_type)()) { return FunctionTypeBuilder(zone(), (*return_type)()); } template <typename... Overloads> Type* Overload(Overloads... overloads) { auto* ret = Type::OverloadedFunction(zone()); AddAllOverloads(ret->AsOverloadedFunctionType(), overloads...); return ret; } private: static void AddAllOverloads(AsmOverloadedFunctionType*) {} template <typename Overload, typename... Others> static void AddAllOverloads(AsmOverloadedFunctionType* function, Overload* overload, Others... others) { CHECK_NOT_NULL(function); function->AddOverload(overload); AddAllOverloads(function, others...); } const std::unordered_map<Type*, std::unordered_set<Type*>> parents_; }; // AsmValueTypeParents expose the bitmasks for the parents for each value type // in asm's type system. It inherits from AsmValueType so that the kAsm<Foo> // members are available when expanding the FOR_EACH_ASM_VALUE_TYPE_LIST macro. class AsmValueTypeParents : private AsmValueType { public: enum : uint32_t { #define V(CamelName, string_name, number, parent_types) \ CamelName = parent_types, FOR_EACH_ASM_VALUE_TYPE_LIST(V) #undef V }; private: DISALLOW_IMPLICIT_CONSTRUCTORS(AsmValueTypeParents); }; TEST_F(AsmTypeTest, ValidateBits) { // Generic validation tests for the bits in the type system's type // definitions. std::unordered_set<Type*> seen_types; std::unordered_set<uint32_t> seen_numbers; uint32_t total_types = 0; #define V(CamelName, string_name, number, parent_types) \ do { \ ++total_types; \ if (AsmValueTypeParents::CamelName != 0) { \ EXPECT_NE(0u, ParentsOf(AsmType::CamelName()).size()) << #CamelName; \ } \ seen_types.insert(Type::CamelName()); \ seen_numbers.insert(number); \ /* Every ASM type must have a valid number. */ \ EXPECT_NE(0, number) << Type::CamelName()->Name(); \ /* Inheritance cycles - unlikely, but we're paranoid and check for it */ \ /* anyways.*/ \ EXPECT_EQ(0u, (1 << (number)) & AsmValueTypeParents::CamelName); \ } while (0); FOR_EACH_ASM_VALUE_TYPE_LIST(V) #undef V // At least one type was expanded. EXPECT_GT(total_types, 0u); // Each value type is unique. EXPECT_EQ(total_types, seen_types.size()); // Each number is unique. EXPECT_EQ(total_types, seen_numbers.size()); } TEST_F(AsmTypeTest, SensibleParentsMap) { // This test ensures our parents map contains all the parents types that are // specified in the types' declaration. It does not report bogus inheritance. // Handy-dandy lambda for counting bits. Code borrowed from stack overflow. auto NumberOfSetBits = [](uintptr_t parent_mask) -> uint32_t { uint32_t parent_mask32 = static_cast<uint32_t>(parent_mask); CHECK_EQ(parent_mask, parent_mask32); parent_mask32 = parent_mask32 - ((parent_mask32 >> 1) & 0x55555555); parent_mask32 = (parent_mask32 & 0x33333333) + ((parent_mask32 >> 2) & 0x33333333); return (((parent_mask32 + (parent_mask32 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; }; #define V(CamelName, string_name, number, parent_types) \ do { \ const uintptr_t parents = \ reinterpret_cast<uintptr_t>(Type::CamelName()) & ~(1 << (number)); \ EXPECT_EQ(NumberOfSetBits(parents), \ 1 + ParentsOf(Type::CamelName()).size()) \ << Type::CamelName()->Name() << ", parents " \ << reinterpret_cast<void*>(parents) << ", type " \ << static_cast<void*>(Type::CamelName()); \ } while (false); FOR_EACH_ASM_VALUE_TYPE_LIST(V) #undef V } TEST_F(AsmTypeTest, Names) { #define V(CamelName, string_name, number, parent_types) \ do { \ EXPECT_THAT(Type::CamelName()->Name(), StrEq(string_name)); \ } while (false); FOR_EACH_ASM_VALUE_TYPE_LIST(V) #undef V EXPECT_THAT(Function(Type::Int)(Type::Double, Type::Float)->Name(), StrEq("(double, float) -> int")); EXPECT_THAT(Overload(Function(Type::Int)(Type::Double, Type::Float), Function(Type::Int)(Type::Int)) ->Name(), StrEq("(double, float) -> int /\\ (int) -> int")); EXPECT_THAT(Type::FroundType(zone())->Name(), StrEq("fround")); EXPECT_THAT(Type::MinMaxType(zone(), Type::Signed(), Type::Int())->Name(), StrEq("(int, int...) -> signed")); EXPECT_THAT(Type::MinMaxType(zone(), Type::Float(), Type::Floatish())->Name(), StrEq("(floatish, floatish...) -> float")); EXPECT_THAT(Type::MinMaxType(zone(), Type::Double(), Type::DoubleQ())->Name(), StrEq("(double?, double?...) -> double")); } TEST_F(AsmTypeTest, IsExactly) { Type* test_types[] = { #define CREATE(CamelName, string_name, number, parent_types) Type::CamelName(), FOR_EACH_ASM_VALUE_TYPE_LIST(CREATE) #undef CREATE Function(Type::Int)(Type::Double), Function(Type::Int)(Type::DoubleQ), Overload(Function(Type::Int)(Type::Double)), Function(Type::Int)(Type::Int, Type::Int), Type::MinMaxType(zone(), Type::Signed(), Type::Int()), Function(Type::Int)(Type::Float), Type::FroundType(zone()), }; for (size_t ii = 0; ii < arraysize(test_types); ++ii) { for (size_t jj = 0; jj < arraysize(test_types); ++jj) { EXPECT_EQ(ii == jj, AsmType::IsExactly(test_types[ii], test_types[jj])) << test_types[ii]->Name() << ((ii == jj) ? " is not exactly " : " is exactly ") << test_types[jj]->Name(); } } } bool FunctionsWithSameSignature(AsmType* a, AsmType* b) { if (a->AsFunctionType()) { if (b->AsFunctionType()) { return a->IsA(b); } } return false; } TEST_F(AsmTypeTest, IsA) { Type* test_types[] = { #define CREATE(CamelName, string_name, number, parent_types) Type::CamelName(), FOR_EACH_ASM_VALUE_TYPE_LIST(CREATE) #undef CREATE Function(Type::Int)(Type::Double), Function(Type::Int)(Type::Int, Type::Int), Function(Type::Int)(Type::DoubleQ), Overload(Function(Type::Int)(Type::Double)), Function(Type::Int)(Type::Int, Type::Int), Type::MinMaxType(zone(), Type::Signed(), Type::Int()), Function(Type::Int)(Type::Float), Type::FroundType(zone()), }; for (size_t ii = 0; ii < arraysize(test_types); ++ii) { for (size_t jj = 0; jj < arraysize(test_types); ++jj) { const bool Expected = (ii == jj) || ParentsOf(test_types[ii]).count(test_types[jj]) != 0 || FunctionsWithSameSignature(test_types[ii], test_types[jj]); EXPECT_EQ(Expected, test_types[ii]->IsA(test_types[jj])) << test_types[ii]->Name() << (Expected ? " is not a " : " is a ") << test_types[jj]->Name(); } } EXPECT_TRUE(Function(Type::Int)(Type::Int, Type::Int) ->IsA(Function(Type::Int)(Type::Int, Type::Int))); EXPECT_FALSE(Function(Type::Int)(Type::Int, Type::Int) ->IsA(Function(Type::Double)(Type::Int, Type::Int))); EXPECT_FALSE(Function(Type::Int)(Type::Int, Type::Int) ->IsA(Function(Type::Int)(Type::Double, Type::Int))); } TEST_F(AsmTypeTest, CanBeInvokedWith) { auto* min_max_int = Type::MinMaxType(zone(), Type::Signed(), Type::Int()); auto* i2s = Function(Type::Signed)(Type::Int); auto* ii2s = Function(Type::Signed)(Type::Int, Type::Int); auto* iii2s = Function(Type::Signed)(Type::Int, Type::Int, Type::Int); auto* iiii2s = Function(Type::Signed)(Type::Int, Type::Int, Type::Int, Type::Int); EXPECT_TRUE(min_max_int->AsCallableType()->CanBeInvokedWith( ii2s->AsFunctionType()->ReturnType(), ii2s->AsFunctionType()->Arguments())); EXPECT_TRUE(min_max_int->AsCallableType()->CanBeInvokedWith( iii2s->AsFunctionType()->ReturnType(), iii2s->AsFunctionType()->Arguments())); EXPECT_TRUE(min_max_int->AsCallableType()->CanBeInvokedWith( iiii2s->AsFunctionType()->ReturnType(), iiii2s->AsFunctionType()->Arguments())); EXPECT_FALSE(min_max_int->AsCallableType()->CanBeInvokedWith( i2s->AsFunctionType()->ReturnType(), i2s->AsFunctionType()->Arguments())); auto* min_max_double = Type::MinMaxType(zone(), Type::Double(), Type::Double()); auto* d2d = Function(Type::Double)(Type::Double); auto* dd2d = Function(Type::Double)(Type::Double, Type::Double); auto* ddd2d = Function(Type::Double)(Type::Double, Type::Double, Type::Double); auto* dddd2d = Function(Type::Double)(Type::Double, Type::Double, Type::Double, Type::Double); EXPECT_TRUE(min_max_double->AsCallableType()->CanBeInvokedWith( dd2d->AsFunctionType()->ReturnType(), dd2d->AsFunctionType()->Arguments())); EXPECT_TRUE(min_max_double->AsCallableType()->CanBeInvokedWith( ddd2d->AsFunctionType()->ReturnType(), ddd2d->AsFunctionType()->Arguments())); EXPECT_TRUE(min_max_double->AsCallableType()->CanBeInvokedWith( dddd2d->AsFunctionType()->ReturnType(), dddd2d->AsFunctionType()->Arguments())); EXPECT_FALSE(min_max_double->AsCallableType()->CanBeInvokedWith( d2d->AsFunctionType()->ReturnType(), d2d->AsFunctionType()->Arguments())); auto* min_max = Overload(min_max_int, min_max_double); EXPECT_FALSE(min_max->AsCallableType()->CanBeInvokedWith( i2s->AsFunctionType()->ReturnType(), i2s->AsFunctionType()->Arguments())); EXPECT_FALSE(min_max->AsCallableType()->CanBeInvokedWith( d2d->AsFunctionType()->ReturnType(), d2d->AsFunctionType()->Arguments())); EXPECT_TRUE(min_max->AsCallableType()->CanBeInvokedWith( ii2s->AsFunctionType()->ReturnType(), ii2s->AsFunctionType()->Arguments())); EXPECT_TRUE(min_max->AsCallableType()->CanBeInvokedWith( iii2s->AsFunctionType()->ReturnType(), iii2s->AsFunctionType()->Arguments())); EXPECT_TRUE(min_max->AsCallableType()->CanBeInvokedWith( iiii2s->AsFunctionType()->ReturnType(), iiii2s->AsFunctionType()->Arguments())); EXPECT_TRUE(min_max->AsCallableType()->CanBeInvokedWith( dd2d->AsFunctionType()->ReturnType(), dd2d->AsFunctionType()->Arguments())); EXPECT_TRUE(min_max->AsCallableType()->CanBeInvokedWith( ddd2d->AsFunctionType()->ReturnType(), ddd2d->AsFunctionType()->Arguments())); EXPECT_TRUE(min_max->AsCallableType()->CanBeInvokedWith( dddd2d->AsFunctionType()->ReturnType(), dddd2d->AsFunctionType()->Arguments())); auto* fround = Type::FroundType(zone()); ZoneVector<AsmType*> arg(zone()); arg.push_back(Type::Floatish()); EXPECT_TRUE(fround->AsCallableType()->CanBeInvokedWith(Type::Float(), arg)); arg.clear(); arg.push_back(Type::FloatQ()); EXPECT_TRUE(fround->AsCallableType()->CanBeInvokedWith(Type::Float(), arg)); arg.clear(); arg.push_back(Type::Float()); EXPECT_TRUE(fround->AsCallableType()->CanBeInvokedWith(Type::Float(), arg)); arg.clear(); arg.push_back(Type::DoubleQ()); EXPECT_TRUE(fround->AsCallableType()->CanBeInvokedWith(Type::Float(), arg)); arg.clear(); arg.push_back(Type::Double()); EXPECT_TRUE(fround->AsCallableType()->CanBeInvokedWith(Type::Float(), arg)); arg.clear(); arg.push_back(Type::Signed()); EXPECT_TRUE(fround->AsCallableType()->CanBeInvokedWith(Type::Float(), arg)); arg.clear(); arg.push_back(Type::Unsigned()); EXPECT_TRUE(fround->AsCallableType()->CanBeInvokedWith(Type::Float(), arg)); arg.clear(); arg.push_back(Type::FixNum()); EXPECT_TRUE(fround->AsCallableType()->CanBeInvokedWith(Type::Float(), arg)); auto* idf2v = Function(Type::Void)(Type::Int, Type::Double, Type::Float); auto* i2d = Function(Type::Double)(Type::Int); auto* i2f = Function(Type::Float)(Type::Int); auto* fi2d = Function(Type::Double)(Type::Float, Type::Int); auto* idif2i = Function(Type::Int)(Type::Int, Type::Double, Type::Int, Type::Float); auto* overload = Overload(idf2v, i2f, /*i2d missing, */ fi2d, idif2i); EXPECT_TRUE(overload->AsCallableType()->CanBeInvokedWith( idf2v->AsFunctionType()->ReturnType(), idf2v->AsFunctionType()->Arguments())); EXPECT_TRUE(overload->AsCallableType()->CanBeInvokedWith( i2f->AsFunctionType()->ReturnType(), i2f->AsFunctionType()->Arguments())); EXPECT_TRUE(overload->AsCallableType()->CanBeInvokedWith( fi2d->AsFunctionType()->ReturnType(), fi2d->AsFunctionType()->Arguments())); EXPECT_TRUE(overload->AsCallableType()->CanBeInvokedWith( idif2i->AsFunctionType()->ReturnType(), idif2i->AsFunctionType()->Arguments())); EXPECT_FALSE(overload->AsCallableType()->CanBeInvokedWith( i2d->AsFunctionType()->ReturnType(), i2d->AsFunctionType()->Arguments())); EXPECT_FALSE(i2f->AsCallableType()->CanBeInvokedWith( i2d->AsFunctionType()->ReturnType(), i2d->AsFunctionType()->Arguments())); } TEST_F(AsmTypeTest, ElementSizeInBytes) { Type* test_types[] = { #define CREATE(CamelName, string_name, number, parent_types) Type::CamelName(), FOR_EACH_ASM_VALUE_TYPE_LIST(CREATE) #undef CREATE Function(Type::Int)(Type::Double), Function(Type::Int)(Type::DoubleQ), Overload(Function(Type::Int)(Type::Double)), Function(Type::Int)(Type::Int, Type::Int), Type::MinMaxType(zone(), Type::Signed(), Type::Int()), Function(Type::Int)(Type::Float), Type::FroundType(zone()), }; auto ElementSizeInBytesForType = [](Type* type) -> int32_t { if (type == Type::Int8Array() || type == Type::Uint8Array()) { return 1; } if (type == Type::Int16Array() || type == Type::Uint16Array()) { return 2; } if (type == Type::Int32Array() || type == Type::Uint32Array() || type == Type::Float32Array()) { return 4; } if (type == Type::Float64Array()) { return 8; } return -1; }; for (size_t ii = 0; ii < arraysize(test_types); ++ii) { EXPECT_EQ(ElementSizeInBytesForType(test_types[ii]), test_types[ii]->ElementSizeInBytes()); } } TEST_F(AsmTypeTest, LoadType) { Type* test_types[] = { #define CREATE(CamelName, string_name, number, parent_types) Type::CamelName(), FOR_EACH_ASM_VALUE_TYPE_LIST(CREATE) #undef CREATE Function(Type::Int)(Type::Double), Function(Type::Int)(Type::DoubleQ), Overload(Function(Type::Int)(Type::Double)), Function(Type::Int)(Type::Int, Type::Int), Type::MinMaxType(zone(), Type::Signed(), Type::Int()), Function(Type::Int)(Type::Float), Type::FroundType(zone()), }; auto LoadTypeForType = [](Type* type) -> Type* { if (type == Type::Int8Array() || type == Type::Uint8Array() || type == Type::Int16Array() || type == Type::Uint16Array() || type == Type::Int32Array() || type == Type::Uint32Array()) { return Type::Intish(); } if (type == Type::Float32Array()) { return Type::FloatQ(); } if (type == Type::Float64Array()) { return Type::DoubleQ(); } return Type::None(); }; for (size_t ii = 0; ii < arraysize(test_types); ++ii) { EXPECT_EQ(LoadTypeForType(test_types[ii]), test_types[ii]->LoadType()); } } TEST_F(AsmTypeTest, StoreType) { Type* test_types[] = { #define CREATE(CamelName, string_name, number, parent_types) Type::CamelName(), FOR_EACH_ASM_VALUE_TYPE_LIST(CREATE) #undef CREATE Function(Type::Int)(Type::Double), Function(Type::Int)(Type::DoubleQ), Overload(Function(Type::Int)(Type::Double)), Function(Type::Int)(Type::Int, Type::Int), Type::MinMaxType(zone(), Type::Signed(), Type::Int()), Function(Type::Int)(Type::Float), Type::FroundType(zone()), }; auto StoreTypeForType = [](Type* type) -> Type* { if (type == Type::Int8Array() || type == Type::Uint8Array() || type == Type::Int16Array() || type == Type::Uint16Array() || type == Type::Int32Array() || type == Type::Uint32Array()) { return Type::Intish(); } if (type == Type::Float32Array()) { return Type::FloatishDoubleQ(); } if (type == Type::Float64Array()) { return Type::FloatQDoubleQ(); } return Type::None(); }; for (size_t ii = 0; ii < arraysize(test_types); ++ii) { EXPECT_EQ(StoreTypeForType(test_types[ii]), test_types[ii]->StoreType()) << test_types[ii]->Name(); } } } // namespace } // namespace wasm } // namespace internal } // namespace v8