| // Copyright 2021 The Tint Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef SRC_TINT_RESOLVER_RESOLVER_TEST_HELPER_H_ |
| #define SRC_TINT_RESOLVER_RESOLVER_TEST_HELPER_H_ |
| |
| #include <functional> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "gtest/gtest.h" |
| #include "src/tint/program_builder.h" |
| #include "src/tint/resolver/resolver.h" |
| #include "src/tint/sem/abstract_float.h" |
| #include "src/tint/sem/abstract_int.h" |
| #include "src/tint/sem/expression.h" |
| #include "src/tint/sem/statement.h" |
| #include "src/tint/sem/variable.h" |
| |
| namespace tint::resolver { |
| |
| /// Helper class for testing |
| class TestHelper : public ProgramBuilder { |
| public: |
| /// Constructor |
| TestHelper(); |
| |
| /// Destructor |
| ~TestHelper() override; |
| |
| /// @return a pointer to the Resolver |
| Resolver* r() const { return resolver_.get(); } |
| |
| /// @return a pointer to the validator |
| const Validator* v() const { return resolver_->GetValidatorForTesting(); } |
| |
| /// Returns the statement that holds the given expression. |
| /// @param expr the ast::Expression |
| /// @return the ast::Statement of the ast::Expression, or nullptr if the |
| /// expression is not owned by a statement. |
| const ast::Statement* StmtOf(const ast::Expression* expr) { |
| auto* sem_stmt = Sem().Get(expr)->Stmt(); |
| return sem_stmt ? sem_stmt->Declaration() : nullptr; |
| } |
| |
| /// Returns the BlockStatement that holds the given statement. |
| /// @param stmt the ast::Statement |
| /// @return the ast::BlockStatement that holds the ast::Statement, or nullptr |
| /// if the statement is not owned by a BlockStatement. |
| const ast::BlockStatement* BlockOf(const ast::Statement* stmt) { |
| auto* sem_stmt = Sem().Get(stmt); |
| return sem_stmt ? sem_stmt->Block()->Declaration() : nullptr; |
| } |
| |
| /// Returns the BlockStatement that holds the given expression. |
| /// @param expr the ast::Expression |
| /// @return the ast::Statement of the ast::Expression, or nullptr if the |
| /// expression is not indirectly owned by a BlockStatement. |
| const ast::BlockStatement* BlockOf(const ast::Expression* expr) { |
| auto* sem_stmt = Sem().Get(expr)->Stmt(); |
| return sem_stmt ? sem_stmt->Block()->Declaration() : nullptr; |
| } |
| |
| /// Returns the semantic variable for the given identifier expression. |
| /// @param expr the identifier expression |
| /// @return the resolved sem::Variable of the identifier, or nullptr if |
| /// the expression did not resolve to a variable. |
| const sem::Variable* VarOf(const ast::Expression* expr) { |
| auto* sem_ident = Sem().Get(expr); |
| auto* var_user = sem_ident ? sem_ident->As<sem::VariableUser>() : nullptr; |
| return var_user ? var_user->Variable() : nullptr; |
| } |
| |
| /// Checks that all the users of the given variable are as expected |
| /// @param var the variable to check |
| /// @param expected_users the expected users of the variable |
| /// @return true if all users are as expected |
| bool CheckVarUsers(const ast::Variable* var, |
| std::vector<const ast::Expression*>&& expected_users) { |
| auto& var_users = Sem().Get(var)->Users(); |
| if (var_users.size() != expected_users.size()) { |
| return false; |
| } |
| for (size_t i = 0; i < var_users.size(); i++) { |
| if (var_users[i]->Declaration() != expected_users[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /// @param type a type |
| /// @returns the name for `type` that closely resembles how it would be |
| /// declared in WGSL. |
| std::string FriendlyName(const ast::Type* type) { return type->FriendlyName(Symbols()); } |
| |
| /// @param type a type |
| /// @returns the name for `type` that closely resembles how it would be |
| /// declared in WGSL. |
| std::string FriendlyName(const sem::Type* type) { return type->FriendlyName(Symbols()); } |
| |
| private: |
| std::unique_ptr<Resolver> resolver_; |
| }; |
| |
| class ResolverTest : public TestHelper, public testing::Test {}; |
| |
| template <typename T> |
| class ResolverTestWithParam : public TestHelper, public testing::TestWithParam<T> {}; |
| |
| namespace builder { |
| |
| template <uint32_t N, typename T> |
| struct vec {}; |
| |
| template <typename T> |
| using vec2 = vec<2, T>; |
| |
| template <typename T> |
| using vec3 = vec<3, T>; |
| |
| template <typename T> |
| using vec4 = vec<4, T>; |
| |
| template <uint32_t N, uint32_t M, typename T> |
| struct mat {}; |
| |
| template <typename T> |
| using mat2x2 = mat<2, 2, T>; |
| |
| template <typename T> |
| using mat2x3 = mat<2, 3, T>; |
| |
| template <typename T> |
| using mat3x2 = mat<3, 2, T>; |
| |
| template <typename T> |
| using mat3x3 = mat<3, 3, T>; |
| |
| template <typename T> |
| using mat4x4 = mat<4, 4, T>; |
| |
| template <uint32_t N, typename T> |
| struct array {}; |
| |
| template <typename TO, int ID = 0> |
| struct alias {}; |
| |
| template <typename TO> |
| using alias1 = alias<TO, 1>; |
| |
| template <typename TO> |
| using alias2 = alias<TO, 2>; |
| |
| template <typename TO> |
| using alias3 = alias<TO, 3>; |
| |
| template <typename TO> |
| struct ptr {}; |
| |
| using ast_type_func_ptr = const ast::Type* (*)(ProgramBuilder& b); |
| using ast_expr_func_ptr = const ast::Expression* (*)(ProgramBuilder& b, double elem_value); |
| using sem_type_func_ptr = const sem::Type* (*)(ProgramBuilder& b); |
| |
| template <typename T> |
| struct DataType {}; |
| |
| /// Helper that represents no-type. Returns nullptr for all static methods. |
| template <> |
| struct DataType<void> { |
| /// @return nullptr |
| static inline const ast::Type* AST(ProgramBuilder&) { return nullptr; } |
| /// @return nullptr |
| static inline const sem::Type* Sem(ProgramBuilder&) { return nullptr; } |
| }; |
| |
| /// Helper for building bool types and expressions |
| template <> |
| struct DataType<bool> { |
| /// The element type |
| using ElementType = bool; |
| |
| /// false as bool is not a composite type |
| static constexpr bool is_composite = false; |
| |
| /// @param b the ProgramBuilder |
| /// @return a new AST bool type |
| static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.bool_(); } |
| /// @param b the ProgramBuilder |
| /// @return the semantic bool type |
| static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::Bool>(); } |
| /// @param b the ProgramBuilder |
| /// @param elem_value the b |
| /// @return a new AST expression of the bool type |
| static inline const ast::Expression* Expr(ProgramBuilder& b, double elem_value) { |
| return b.Expr(std::equal_to<double>()(elem_value, 0)); |
| } |
| /// @returns the WGSL name for the type |
| static inline std::string Name() { return "bool"; } |
| }; |
| |
| /// Helper for building i32 types and expressions |
| template <> |
| struct DataType<i32> { |
| /// The element type |
| using ElementType = i32; |
| |
| /// false as i32 is not a composite type |
| static constexpr bool is_composite = false; |
| |
| /// @param b the ProgramBuilder |
| /// @return a new AST i32 type |
| static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.i32(); } |
| /// @param b the ProgramBuilder |
| /// @return the semantic i32 type |
| static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::I32>(); } |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value i32 will be initialized with |
| /// @return a new AST i32 literal value expression |
| static inline const ast::Expression* Expr(ProgramBuilder& b, double elem_value) { |
| return b.Expr(static_cast<i32>(elem_value)); |
| } |
| /// @returns the WGSL name for the type |
| static inline std::string Name() { return "i32"; } |
| }; |
| |
| /// Helper for building u32 types and expressions |
| template <> |
| struct DataType<u32> { |
| /// The element type |
| using ElementType = u32; |
| |
| /// false as u32 is not a composite type |
| static constexpr bool is_composite = false; |
| |
| /// @param b the ProgramBuilder |
| /// @return a new AST u32 type |
| static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.u32(); } |
| /// @param b the ProgramBuilder |
| /// @return the semantic u32 type |
| static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::U32>(); } |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value u32 will be initialized with |
| /// @return a new AST u32 literal value expression |
| static inline const ast::Expression* Expr(ProgramBuilder& b, double elem_value) { |
| return b.Expr(static_cast<u32>(elem_value)); |
| } |
| /// @returns the WGSL name for the type |
| static inline std::string Name() { return "u32"; } |
| }; |
| |
| /// Helper for building f32 types and expressions |
| template <> |
| struct DataType<f32> { |
| /// The element type |
| using ElementType = f32; |
| |
| /// false as f32 is not a composite type |
| static constexpr bool is_composite = false; |
| |
| /// @param b the ProgramBuilder |
| /// @return a new AST f32 type |
| static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.f32(); } |
| /// @param b the ProgramBuilder |
| /// @return the semantic f32 type |
| static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::F32>(); } |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value f32 will be initialized with |
| /// @return a new AST f32 literal value expression |
| static inline const ast::Expression* Expr(ProgramBuilder& b, double elem_value) { |
| return b.Expr(static_cast<f32>(elem_value)); |
| } |
| /// @returns the WGSL name for the type |
| static inline std::string Name() { return "f32"; } |
| }; |
| |
| /// Helper for building f16 types and expressions |
| template <> |
| struct DataType<f16> { |
| /// The element type |
| using ElementType = f16; |
| |
| /// false as f16 is not a composite type |
| static constexpr bool is_composite = false; |
| |
| /// @param b the ProgramBuilder |
| /// @return a new AST f16 type |
| static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.f16(); } |
| /// @param b the ProgramBuilder |
| /// @return the semantic f16 type |
| static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::F16>(); } |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value f16 will be initialized with |
| /// @return a new AST f16 literal value expression |
| static inline const ast::Expression* Expr(ProgramBuilder& b, double elem_value) { |
| return b.Expr(static_cast<f16>(elem_value)); |
| } |
| /// @returns the WGSL name for the type |
| static inline std::string Name() { return "f16"; } |
| }; |
| |
| /// Helper for building abstract float types and expressions |
| template <> |
| struct DataType<AFloat> { |
| /// The element type |
| using ElementType = AFloat; |
| |
| /// false as AFloat is not a composite type |
| static constexpr bool is_composite = false; |
| |
| /// @returns nullptr, as abstract floats are un-typeable |
| static inline const ast::Type* AST(ProgramBuilder&) { return nullptr; } |
| /// @param b the ProgramBuilder |
| /// @return the semantic abstract-float type |
| static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::AbstractFloat>(); } |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value the abstract-float literal will be constructed with |
| /// @return a new AST abstract-float literal value expression |
| static inline const ast::Expression* Expr(ProgramBuilder& b, double elem_value) { |
| return b.Expr(AFloat(elem_value)); |
| } |
| /// @returns the WGSL name for the type |
| static inline std::string Name() { return "abstract-float"; } |
| }; |
| |
| /// Helper for building abstract integer types and expressions |
| template <> |
| struct DataType<AInt> { |
| /// The element type |
| using ElementType = AInt; |
| |
| /// false as AFloat is not a composite type |
| static constexpr bool is_composite = false; |
| |
| /// @returns nullptr, as abstract integers are un-typeable |
| static inline const ast::Type* AST(ProgramBuilder&) { return nullptr; } |
| /// @param b the ProgramBuilder |
| /// @return the semantic abstract-int type |
| static inline const sem::Type* Sem(ProgramBuilder& b) { return b.create<sem::AbstractInt>(); } |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value the abstract-int literal will be constructed with |
| /// @return a new AST abstract-int literal value expression |
| static inline const ast::Expression* Expr(ProgramBuilder& b, double elem_value) { |
| return b.Expr(AInt(elem_value)); |
| } |
| /// @returns the WGSL name for the type |
| static inline std::string Name() { return "abstract-int"; } |
| }; |
| |
| /// Helper for building vector types and expressions |
| template <uint32_t N, typename T> |
| struct DataType<vec<N, T>> { |
| /// The element type |
| using ElementType = T; |
| |
| /// true as vectors are a composite type |
| static constexpr bool is_composite = true; |
| |
| /// @param b the ProgramBuilder |
| /// @return a new AST vector type |
| static inline const ast::Type* AST(ProgramBuilder& b) { |
| return b.ty.vec(DataType<T>::AST(b), N); |
| } |
| /// @param b the ProgramBuilder |
| /// @return the semantic vector type |
| static inline const sem::Type* Sem(ProgramBuilder& b) { |
| return b.create<sem::Vector>(DataType<T>::Sem(b), N); |
| } |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value each element in the vector will be initialized |
| /// with |
| /// @return a new AST vector value expression |
| static inline const ast::Expression* Expr(ProgramBuilder& b, double elem_value) { |
| return b.Construct(AST(b), ExprArgs(b, elem_value)); |
| } |
| |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value each element will be initialized with |
| /// @return the list of expressions that are used to construct the vector |
| static inline ast::ExpressionList ExprArgs(ProgramBuilder& b, double elem_value) { |
| ast::ExpressionList args; |
| for (uint32_t i = 0; i < N; i++) { |
| args.emplace_back(DataType<T>::Expr(b, elem_value)); |
| } |
| return args; |
| } |
| /// @returns the WGSL name for the type |
| static inline std::string Name() { |
| return "vec" + std::to_string(N) + "<" + DataType<T>::Name() + ">"; |
| } |
| }; |
| |
| /// Helper for building matrix types and expressions |
| template <uint32_t N, uint32_t M, typename T> |
| struct DataType<mat<N, M, T>> { |
| /// The element type |
| using ElementType = T; |
| |
| /// true as matrices are a composite type |
| static constexpr bool is_composite = true; |
| |
| /// @param b the ProgramBuilder |
| /// @return a new AST matrix type |
| static inline const ast::Type* AST(ProgramBuilder& b) { |
| return b.ty.mat(DataType<T>::AST(b), N, M); |
| } |
| /// @param b the ProgramBuilder |
| /// @return the semantic matrix type |
| static inline const sem::Type* Sem(ProgramBuilder& b) { |
| auto* column_type = b.create<sem::Vector>(DataType<T>::Sem(b), M); |
| return b.create<sem::Matrix>(column_type, N); |
| } |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value each element in the matrix will be initialized |
| /// with |
| /// @return a new AST matrix value expression |
| static inline const ast::Expression* Expr(ProgramBuilder& b, double elem_value) { |
| return b.Construct(AST(b), ExprArgs(b, elem_value)); |
| } |
| |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value each element will be initialized with |
| /// @return the list of expressions that are used to construct the matrix |
| static inline ast::ExpressionList ExprArgs(ProgramBuilder& b, double elem_value) { |
| ast::ExpressionList args; |
| for (uint32_t i = 0; i < N; i++) { |
| args.emplace_back(DataType<vec<M, T>>::Expr(b, elem_value)); |
| } |
| return args; |
| } |
| /// @returns the WGSL name for the type |
| static inline std::string Name() { |
| return "mat" + std::to_string(N) + "x" + std::to_string(M) + "<" + DataType<T>::Name() + |
| ">"; |
| } |
| }; |
| |
| /// Helper for building alias types and expressions |
| template <typename T, int ID> |
| struct DataType<alias<T, ID>> { |
| /// The element type |
| using ElementType = T; |
| |
| /// true if the aliased type is a composite type |
| static constexpr bool is_composite = DataType<T>::is_composite; |
| |
| /// @param b the ProgramBuilder |
| /// @return a new AST alias type |
| static inline const ast::Type* AST(ProgramBuilder& b) { |
| auto name = b.Symbols().Register("alias_" + std::to_string(ID)); |
| if (!b.AST().LookupType(name)) { |
| auto* type = DataType<T>::AST(b); |
| b.AST().AddTypeDecl(b.ty.alias(name, type)); |
| } |
| return b.create<ast::TypeName>(name); |
| } |
| /// @param b the ProgramBuilder |
| /// @return the semantic aliased type |
| static inline const sem::Type* Sem(ProgramBuilder& b) { return DataType<T>::Sem(b); } |
| |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value nested elements will be initialized with |
| /// @return a new AST expression of the alias type |
| template <bool IS_COMPOSITE = is_composite> |
| static inline traits::EnableIf<!IS_COMPOSITE, const ast::Expression*> Expr(ProgramBuilder& b, |
| double elem_value) { |
| // Cast |
| return b.Construct(AST(b), DataType<T>::Expr(b, elem_value)); |
| } |
| |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value nested elements will be initialized with |
| /// @return a new AST expression of the alias type |
| template <bool IS_COMPOSITE = is_composite> |
| static inline traits::EnableIf<IS_COMPOSITE, const ast::Expression*> Expr(ProgramBuilder& b, |
| double elem_value) { |
| // Construct |
| return b.Construct(AST(b), DataType<T>::ExprArgs(b, elem_value)); |
| } |
| /// @returns the WGSL name for the type |
| static inline std::string Name() { return "alias_" + std::to_string(ID); } |
| }; |
| |
| /// Helper for building pointer types and expressions |
| template <typename T> |
| struct DataType<ptr<T>> { |
| /// The element type |
| using ElementType = T; |
| |
| /// true if the pointer type is a composite type |
| static constexpr bool is_composite = false; |
| |
| /// @param b the ProgramBuilder |
| /// @return a new AST alias type |
| static inline const ast::Type* AST(ProgramBuilder& b) { |
| return b.create<ast::Pointer>(DataType<T>::AST(b), ast::StorageClass::kPrivate, |
| ast::Access::kReadWrite); |
| } |
| /// @param b the ProgramBuilder |
| /// @return the semantic aliased type |
| static inline const sem::Type* Sem(ProgramBuilder& b) { |
| return b.create<sem::Pointer>(DataType<T>::Sem(b), ast::StorageClass::kPrivate, |
| ast::Access::kReadWrite); |
| } |
| |
| /// @param b the ProgramBuilder |
| /// @return a new AST expression of the alias type |
| static inline const ast::Expression* Expr(ProgramBuilder& b, double /*unused*/) { |
| auto sym = b.Symbols().New("global_for_ptr"); |
| b.Global(sym, DataType<T>::AST(b), ast::StorageClass::kPrivate); |
| return b.AddressOf(sym); |
| } |
| /// @returns the WGSL name for the type |
| static inline std::string Name() { return "ptr<" + DataType<T>::Name() + ">"; } |
| }; |
| |
| /// Helper for building array types and expressions |
| template <uint32_t N, typename T> |
| struct DataType<array<N, T>> { |
| /// The element type |
| using ElementType = T; |
| |
| /// true as arrays are a composite type |
| static constexpr bool is_composite = true; |
| |
| /// @param b the ProgramBuilder |
| /// @return a new AST array type |
| static inline const ast::Type* AST(ProgramBuilder& b) { |
| return b.ty.array(DataType<T>::AST(b), u32(N)); |
| } |
| /// @param b the ProgramBuilder |
| /// @return the semantic array type |
| static inline const sem::Type* Sem(ProgramBuilder& b) { |
| auto* el = DataType<T>::Sem(b); |
| return b.create<sem::Array>( |
| /* element */ el, |
| /* count */ N, |
| /* align */ el->Align(), |
| /* size */ el->Size(), |
| /* stride */ el->Align(), |
| /* implicit_stride */ el->Align()); |
| } |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value each element in the array will be initialized |
| /// with |
| /// @return a new AST array value expression |
| static inline const ast::Expression* Expr(ProgramBuilder& b, double elem_value) { |
| return b.Construct(AST(b), ExprArgs(b, elem_value)); |
| } |
| |
| /// @param b the ProgramBuilder |
| /// @param elem_value the value each element will be initialized with |
| /// @return the list of expressions that are used to construct the array |
| static inline ast::ExpressionList ExprArgs(ProgramBuilder& b, double elem_value) { |
| ast::ExpressionList args; |
| for (uint32_t i = 0; i < N; i++) { |
| args.emplace_back(DataType<T>::Expr(b, elem_value)); |
| } |
| return args; |
| } |
| /// @returns the WGSL name for the type |
| static inline std::string Name() { |
| return "array<" + DataType<T>::Name() + ", " + std::to_string(N) + ">"; |
| } |
| }; |
| |
| /// Struct of all creation pointer types |
| struct CreatePtrs { |
| /// ast node type create function |
| ast_type_func_ptr ast; |
| /// ast expression type create function |
| ast_expr_func_ptr expr; |
| /// sem type create function |
| sem_type_func_ptr sem; |
| }; |
| |
| /// Returns a CreatePtrs struct instance with all creation pointer types for |
| /// type `T` |
| template <typename T> |
| constexpr CreatePtrs CreatePtrsFor() { |
| return {DataType<T>::AST, DataType<T>::Expr, DataType<T>::Sem}; |
| } |
| |
| } // namespace builder |
| |
| } // namespace tint::resolver |
| |
| #endif // SRC_TINT_RESOLVER_RESOLVER_TEST_HELPER_H_ |