// Copyright 2020 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_READER_SPIRV_PARSER_IMPL_TEST_HELPER_H_ #define SRC_TINT_READER_SPIRV_PARSER_IMPL_TEST_HELPER_H_ #include #include #include #include #include #include "src/tint/utils/compiler_macros.h" #if TINT_BUILD_SPV_READER TINT_BEGIN_DISABLE_WARNING(NEWLINE_EOF); TINT_BEGIN_DISABLE_WARNING(OLD_STYLE_CAST); TINT_BEGIN_DISABLE_WARNING(SIGN_CONVERSION); TINT_BEGIN_DISABLE_WARNING(WEAK_VTABLES); #include "source/opt/ir_context.h" TINT_END_DISABLE_WARNING(WEAK_VTABLES); TINT_END_DISABLE_WARNING(SIGN_CONVERSION); TINT_END_DISABLE_WARNING(OLD_STYLE_CAST); TINT_END_DISABLE_WARNING(NEWLINE_EOF); #endif #include "gtest/gtest.h" #include "src/tint/demangler.h" #include "src/tint/reader/spirv/fail_stream.h" #include "src/tint/reader/spirv/function.h" #include "src/tint/reader/spirv/namer.h" #include "src/tint/reader/spirv/parser_impl.h" #include "src/tint/reader/spirv/spirv_tools_helpers_test.h" #include "src/tint/reader/spirv/usage.h" namespace tint::reader::spirv { namespace test { /// A test class that wraps ParseImpl class ParserImplWrapperForTest { public: /// Constructor /// @param input the input data to parse explicit ParserImplWrapperForTest(const std::vector& input); /// Dumps SPIR-V if the conversion succeeded, then destroys the wrapper. ~ParserImplWrapperForTest(); /// Sets global state to force dumping of the assembly text of succesfully /// SPIR-V. static void DumpSuccessfullyConvertedSpirv() { dump_successfully_converted_spirv_ = true; } /// Marks the test has having deliberately invalid SPIR-V void DeliberatelyInvalidSpirv() { skip_dumping_spirv_ = true; } /// Marks the test's SPIR-V as not being suitable for dumping, for a stated /// reason. void SkipDumpingPending(std::string) { skip_dumping_spirv_ = true; } /// @returns a new function emitter for the given function ID. /// Assumes ParserImpl::BuildInternalRepresentation has been run and /// succeeded. /// @param function_id the SPIR-V identifier of the function FunctionEmitter function_emitter(uint32_t function_id) { auto* spirv_function = impl_.ir_context()->GetFunction(function_id); return FunctionEmitter(&impl_, *spirv_function); } /// Run the parser /// @returns true if the parse was successful, false otherwise. bool Parse() { return impl_.Parse(); } /// @returns the program. The program builder in the parser will be reset /// after this. Program program() { return impl_.program(); } /// @returns the namer object Namer& namer() { return impl_.namer(); } /// @returns a reference to the internal builder, without building the /// program. To be used only for testing. ProgramBuilder& builder() { return impl_.builder(); } /// @returns the accumulated error string const std::string error() { return impl_.error(); } /// @return true if failure has not yet occurred bool success() { return impl_.success(); } /// Logs failure, ands return a failure stream to accumulate diagnostic /// messages. By convention, a failure should only be logged along with /// a non-empty string diagnostic. /// @returns the failure stream FailStream& Fail() { return impl_.Fail(); } /// @returns a borrowed pointer to the internal representation of the module. /// This is null until BuildInternalModule has been called. spvtools::opt::IRContext* ir_context() { return impl_.ir_context(); } /// Builds the internal representation of the SPIR-V module. /// Assumes the module is somewhat well-formed. Normally you /// would want to validate the SPIR-V module before attempting /// to build this internal representation. Also computes a topological /// ordering of the functions. /// This is a no-op if the parser has already failed. /// @returns true if the parser is still successful. bool BuildInternalModule() { return impl_.BuildInternalModule(); } /// Builds an internal representation of the SPIR-V binary, /// and parses the module, except functions, into a Tint AST module. /// Diagnostics are emitted to the error stream. /// @returns true if it was successful. bool BuildAndParseInternalModuleExceptFunctions() { return impl_.BuildAndParseInternalModuleExceptFunctions(); } /// Builds an internal representation of the SPIR-V binary, /// and parses it into a Tint AST module. Diagnostics are emitted /// to the error stream. /// @returns true if it was successful. bool BuildAndParseInternalModule() { return impl_.BuildAndParseInternalModule(); } /// Registers user names for SPIR-V objects, from OpName, and OpMemberName. /// Also synthesizes struct field names. Ensures uniqueness for names for /// SPIR-V IDs, and uniqueness of names of fields within any single struct. /// This is a no-op if the parser has already failed. /// @returns true if parser is still successful. bool RegisterUserAndStructMemberNames() { return impl_.RegisterUserAndStructMemberNames(); } /// Register Tint AST types for SPIR-V types, including type aliases as /// needed. This is a no-op if the parser has already failed. /// @returns true if parser is still successful. bool RegisterTypes() { return impl_.RegisterTypes(); } /// Register sampler and texture usage for memory object declarations. /// This must be called after we've registered line numbers for all /// instructions. This is a no-op if the parser has already failed. /// @returns true if parser is still successful. bool RegisterHandleUsage() { return impl_.RegisterHandleUsage(); } /// Emits module-scope variables. /// This is a no-op if the parser has already failed. /// @returns true if parser is still successful. bool EmitModuleScopeVariables() { return impl_.EmitModuleScopeVariables(); } /// @returns the set of SPIR-V IDs for imports of the "GLSL.std.450" /// extended instruction set. const std::unordered_set& glsl_std_450_imports() const { return impl_.glsl_std_450_imports(); } /// Converts a SPIR-V type to a Tint type, and saves it for fast lookup. /// If the type is only used for builtins, then register that specially, /// and return null. If the type is a sampler, image, or sampled image, then /// return the Void type, because those opaque types are handled in a /// different way. /// On failure, logs an error and returns null. This should only be called /// after the internal representation of the module has been built. /// @param id the SPIR-V ID of a type. /// @returns a Tint type, or nullptr const Type* ConvertType(uint32_t id) { return impl_.ConvertType(id); } /// Gets the list of decorations for a SPIR-V result ID. Returns an empty /// vector if the ID is not a result ID, or if no decorations target that ID. /// The internal representation must have already been built. /// @param id SPIR-V ID /// @returns the list of decorations on the given ID DecorationList GetDecorationsFor(uint32_t id) const { return impl_.GetDecorationsFor(id); } /// Gets the list of decorations for the member of a struct. Returns an empty /// list if the `id` is not the ID of a struct, or if the member index is out /// of range, or if the target member has no decorations. /// The internal representation must have already been built. /// @param id SPIR-V ID of a struct /// @param member_index the member within the struct /// @returns the list of decorations on the member DecorationList GetDecorationsForMember(uint32_t id, uint32_t member_index) const { return impl_.GetDecorationsForMember(id, member_index); } /// Converts a SPIR-V struct member decoration into a number of AST /// decorations. If the decoration is recognized but deliberately dropped, /// then returns an empty list without a diagnostic. On failure, emits a /// diagnostic and returns an empty list. /// @param struct_type_id the ID of the struct type /// @param member_index the index of the member /// @param member_ty the type of the member /// @param decoration an encoded SPIR-V Decoration /// @returns the AST decorations auto ConvertMemberDecoration(uint32_t struct_type_id, uint32_t member_index, const Type* member_ty, const Decoration& decoration) { return impl_.ConvertMemberDecoration(struct_type_id, member_index, member_ty, decoration); } /// For a SPIR-V ID that might define a sampler, image, or sampled image /// value, return the SPIR-V instruction that represents the memory object /// declaration for the object. If we encounter an OpSampledImage along the /// way, follow the image operand when follow_image is true; otherwise follow /// the sampler operand. Returns nullptr if we can't trace back to a memory /// object declaration. Emits an error and returns nullptr when the scan /// fails due to a malformed module. This method can be used any time after /// BuildInternalModule has been invoked. /// @param id the SPIR-V ID of the sampler, image, or sampled image /// @param follow_image indicates whether to follow the image operand of /// OpSampledImage /// @returns the memory object declaration for the handle, or nullptr const spvtools::opt::Instruction* GetMemoryObjectDeclarationForHandle(uint32_t id, bool follow_image) { return impl_.GetMemoryObjectDeclarationForHandle(id, follow_image); } /// @param entry_point the SPIR-V ID of an entry point. /// @returns the entry point info for the given ID const std::vector& GetEntryPointInfo(uint32_t entry_point) { return impl_.GetEntryPointInfo(entry_point); } /// Returns the handle usage for a memory object declaration. /// @param id SPIR-V ID of a sampler or image OpVariable or /// OpFunctionParameter /// @returns the handle usage, or an empty usage object. Usage GetHandleUsage(uint32_t id) const { return impl_.GetHandleUsage(id); } /// Returns the SPIR-V instruction with the given ID, or nullptr. /// @param id the SPIR-V result ID /// @returns the instruction, or nullptr on error const spvtools::opt::Instruction* GetInstructionForTest(uint32_t id) const { return impl_.GetInstructionForTest(id); } /// @returns info about the gl_Position builtin variable. const ParserImpl::BuiltInPositionInfo& GetBuiltInPositionInfo() { return impl_.GetBuiltInPositionInfo(); } /// Returns the source record for the SPIR-V instruction with the given /// result ID. /// @param id the SPIR-V result id. /// @return the Source record, or a default one Source GetSourceForResultIdForTest(uint32_t id) const { return impl_.GetSourceForResultIdForTest(id); } private: ParserImpl impl_; /// When true, indicates the input SPIR-V module should not be emitted. /// It's either deliberately invalid, or not supported for some pending /// reason. bool skip_dumping_spirv_ = false; static bool dump_successfully_converted_spirv_; }; // Sets global state to force dumping of the assembly text of succesfully // SPIR-V. inline void DumpSuccessfullyConvertedSpirv() { ParserImplWrapperForTest::DumpSuccessfullyConvertedSpirv(); } /// Returns the WGSL printed string of a program. /// @param program the Program /// @returns the WGSL printed string the program. std::string ToString(const Program& program); /// Returns the WGSL printed string of a statement list. /// @param program the Program /// @param stmts the statement list /// @returns the WGSL printed string of a statement list. std::string ToString(const Program& program, utils::VectorRef stmts); /// Returns the WGSL printed string of an AST node. /// @param program the Program /// @param node the AST node /// @returns the WGSL printed string of the AST node. std::string ToString(const Program& program, const ast::Node* node); } // namespace test /// SPIR-V Parser test class template class SpvParserTestBase : public T { public: SpvParserTestBase() = default; ~SpvParserTestBase() override = default; /// Retrieves the parser from the helper /// @param input the SPIR-V binary to parse /// @returns a parser for the given binary std::unique_ptr parser(const std::vector& input) { auto parser = std::make_unique(input); // Don't run the Resolver when building the program. // We're not interested in type information with these tests. parser->builder().SetResolveOnBuild(false); return parser; } }; /// SpvParserTest the the base class for SPIR-V reader unit tests. /// Use this form when you don't need to template any further. using SpvParserTest = SpvParserTestBase<::testing::Test>; } // namespace tint::reader::spirv #endif // SRC_TINT_READER_SPIRV_PARSER_IMPL_TEST_HELPER_H_