diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c47a190aa2..ae349de7cb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -353,8 +353,11 @@ endif() if(${TINT_BUILD_SPV_PARSER}) list (APPEND TINT_TEST_SRCS reader/spv/fail_stream_test.cc + reader/spv/parser_impl_import_test.cc reader/spv/parser_impl_test.cc reader/spv/parser_test.cc + reader/spv/spirv_tools_helpers_test.cc + reader/spv/spirv_tools_helpers_test.h ) endif() diff --git a/src/reader/spv/fail_stream.h b/src/reader/spv/fail_stream.h index aa2c5882a8..714359b038 100644 --- a/src/reader/spv/fail_stream.h +++ b/src/reader/spv/fail_stream.h @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. diff --git a/src/reader/spv/parser_impl.cc b/src/reader/spv/parser_impl.cc index c31d2c6984..8adba19530 100644 --- a/src/reader/spv/parser_impl.cc +++ b/src/reader/spv/parser_impl.cc @@ -104,6 +104,45 @@ bool ParserImpl::BuildInternalModule() { return true; } +void ParserImpl::ResetInternalModule() { + ir_context_.reset(nullptr); + module_ = nullptr; + def_use_mgr_ = nullptr; + constant_mgr_ = nullptr; + type_mgr_ = nullptr; + deco_mgr_ = nullptr; + + import_map_.clear(); + glsl_std_450_imports_.clear(); +} + +bool ParserImpl::ParseInternalModule() { + return ParseExtendedInstructionImports(); + // TODO(dneto): fill in the rest +} + +bool ParserImpl::ParseExtendedInstructionImports() { + for (const spvtools::opt::Instruction& import : module_->ext_inst_imports()) { + std::string name( + reinterpret_cast(import.GetInOperand(0).words.data())); + // TODO(dneto): Handle other extended instruction sets when needed. + if (name == "GLSL.std.450") { + // Only create the AST import once, so we can use import name 'std::glsl'. + // This is a canonicalization. + if (glsl_std_450_imports_.empty()) { + auto ast_import = + std::make_unique(name, "std::glsl"); + import_map_[import.result_id()] = ast_import.get(); + ast_module_.AddImport(std::move(ast_import)); + } + glsl_std_450_imports_.insert(import.result_id()); + } else { + return Fail() << "Unrecognized extended instruction set: " << name; + } + } + return true; +} + } // namespace spv } // namespace reader } // namespace tint diff --git a/src/reader/spv/parser_impl.h b/src/reader/spv/parser_impl.h index f67d5ef39c..8c5de4729a 100644 --- a/src/reader/spv/parser_impl.h +++ b/src/reader/spv/parser_impl.h @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include #include "source/opt/constants.h" @@ -26,6 +28,8 @@ #include "source/opt/module.h" #include "source/opt/type_manager.h" #include "spirv-tools/libspirv.hpp" +#include "src/ast/import.h" +#include "src/ast/module.h" #include "src/reader/reader.h" #include "src/reader/spv/fail_stream.h" @@ -61,6 +65,20 @@ class ParserImpl : Reader { /// @returns the accumulated error string const std::string error() { return errors_.str(); } + /// 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 BuildInternalModule() && ParseInternalModule(); + } + + /// @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 glsl_std_450_imports_; + } + private: /// Builds the internal representation of the SPIR-V module. /// Assumes the module is somewhat well-formed. Normally you @@ -69,6 +87,17 @@ class ParserImpl : Reader { /// @returns true if successful. bool BuildInternalModule(); + /// Walks the internal representation of the module to populate + /// the AST form of the module. + /// @returns true on success + bool ParseInternalModule(); + + /// Destroys the internal representation of the SPIR-V module. + void ResetInternalModule(); + + /// Parses OpExtInstImport instructions. + bool ParseExtendedInstructionImports(); + // The SPIR-V binary we're parsing std::vector spv_binary_; @@ -93,6 +122,12 @@ class ParserImpl : Reader { spvtools::opt::analysis::ConstantManager* constant_mgr_ = nullptr; spvtools::opt::analysis::TypeManager* type_mgr_ = nullptr; spvtools::opt::analysis::DecorationManager* deco_mgr_ = nullptr; + + /// Maps a SPIR-V ID for an external instruction import to an AST import + std::unordered_map import_map_; + // The set of IDs that are imports of the GLSL.std.450 extended instruction + // sets. + std::unordered_set glsl_std_450_imports_; }; } // namespace spv diff --git a/src/reader/spv/parser_impl_import_test.cc b/src/reader/spv/parser_impl_import_test.cc new file mode 100644 index 0000000000..b6ee771b79 --- /dev/null +++ b/src/reader/spv/parser_impl_import_test.cc @@ -0,0 +1,74 @@ +// 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. + +#include "src/reader/spv/parser_impl.h" + +#include +#include + +#include "gmock/gmock.h" +#include "src/reader/spv/spirv_tools_helpers_test.h" + +namespace tint { +namespace reader { +namespace spv { +namespace { + +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::Not; +using ::testing::UnorderedElementsAre; + +using SpvParseImport = ::testing::Test; + +TEST_F(SpvParseImport, NoImport) { + ParserImpl p(test::Assemble("%1 = OpTypeVoid")); + EXPECT_TRUE(p.BuildAndParseInternalModule()); + EXPECT_TRUE(p.error().empty()); + const auto module_ast = p.module().to_str(); + EXPECT_THAT(module_ast, Not(HasSubstr("Import"))); +} + +TEST_F(SpvParseImport, ImportGlslStd450) { + ParserImpl p(test::Assemble(R"(%1 = OpExtInstImport "GLSL.std.450")")); + EXPECT_TRUE(p.BuildAndParseInternalModule()); + EXPECT_TRUE(p.error().empty()); + EXPECT_THAT(p.glsl_std_450_imports(), ElementsAre(1)); + const auto module_ast = p.module().to_str(); + EXPECT_THAT(module_ast, HasSubstr(R"(Import{"GLSL.std.450" as std::glsl})")); +} + +TEST_F(SpvParseImport, ImportGlslStd450Twice) { + ParserImpl p(test::Assemble(R"( + %1 = OpExtInstImport "GLSL.std.450" + %42 = OpExtInstImport "GLSL.std.450" + )")); + EXPECT_TRUE(p.BuildAndParseInternalModule()); + EXPECT_TRUE(p.error().empty()); + EXPECT_THAT(p.glsl_std_450_imports(), UnorderedElementsAre(1, 42)); + const auto module = p.module(); + EXPECT_EQ(module.imports().size(), 1); + const auto module_ast = module.to_str(); + // TODO(dneto): Use a matcher to show there is only one import. + EXPECT_THAT(module_ast, HasSubstr(R"(Import{"GLSL.std.450" as std::glsl})")); +} + +// TODO(dneto): We don't currently support other kinds of extended instruction +// imports. + +} // namespace +} // namespace spv +} // namespace reader +} // namespace tint diff --git a/src/reader/spv/parser_impl_test.cc b/src/reader/spv/parser_impl_test.cc index 9881fa385c..5604e8e26b 100644 --- a/src/reader/spv/parser_impl_test.cc +++ b/src/reader/spv/parser_impl_test.cc @@ -18,7 +18,7 @@ #include #include "gmock/gmock.h" -#include "spirv-tools/libspirv.hpp" +#include "src/reader/spv/spirv_tools_helpers_test.h" namespace tint { namespace reader { @@ -37,31 +37,8 @@ TEST_F(SpvParserImplTest, Uint32VecEmpty) { // TODO(dneto): What message? } -/// @returns the SPIR-V module assembled from the given text. Numeric IDs -/// are preserved. -std::vector Assemble(const std::string& spirv_assembly) { - // TODO(dneto): Use ScopedTrace? - - // (The target environment doesn't affect assembly. - spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0); - std::stringstream errors; - std::vector result; - tools.SetMessageConsumer([&errors](spv_message_level_t, const char*, - const spv_position_t& position, - const char* message) { - errors << "assembly error:" << position.line << ":" << position.column - << ": " << message; - }); - - const auto success = tools.Assemble( - spirv_assembly, &result, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); - EXPECT_TRUE(success) << errors.str(); - - return result; -} - TEST_F(SpvParserImplTest, InvalidModuleFails) { - auto invalid_spv = Assemble("%ty = OpTypeInt 3 0"); + auto invalid_spv = test::Assemble("%ty = OpTypeInt 3 0"); ParserImpl p{invalid_spv}; EXPECT_FALSE(p.Parse()); EXPECT_THAT( diff --git a/src/reader/spv/spirv_tools_helpers_test.cc b/src/reader/spv/spirv_tools_helpers_test.cc new file mode 100644 index 0000000000..00d6d51166 --- /dev/null +++ b/src/reader/spv/spirv_tools_helpers_test.cc @@ -0,0 +1,55 @@ +// 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. + +#include "src/reader/spv/spirv_tools_helpers_test.h" + +#include +#include +#include + +#include "gtest/gtest.h" +#include "spirv-tools/libspirv.hpp" + +namespace tint { +namespace reader { +namespace spv { +namespace test { + +/// @returns the SPIR-V module assembled from the given text. Numeric IDs +/// are preserved. +std::vector Assemble(const std::string& spirv_assembly) { + // TODO(dneto): Use ScopedTrace? + + // (The target environment doesn't affect assembly. + spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0); + std::stringstream errors; + std::vector result; + tools.SetMessageConsumer([&errors](spv_message_level_t, const char*, + const spv_position_t& position, + const char* message) { + errors << "assembly error:" << position.line << ":" << position.column + << ": " << message; + }); + + const auto success = tools.Assemble( + spirv_assembly, &result, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_TRUE(success) << errors.str(); + + return result; +} + +} // namespace test +} // namespace spv +} // namespace reader +} // namespace tint diff --git a/src/reader/spv/spirv_tools_helpers_test.h b/src/reader/spv/spirv_tools_helpers_test.h new file mode 100644 index 0000000000..b77d9546f4 --- /dev/null +++ b/src/reader/spv/spirv_tools_helpers_test.h @@ -0,0 +1,36 @@ +// 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_READER_SPV_SPIRV_TOOLS_HELPERS_TEST_H_ +#define SRC_READER_SPV_SPIRV_TOOLS_HELPERS_TEST_H_ + +#include +#include +#include + +namespace tint { +namespace reader { +namespace spv { +namespace test { + +/// @returns the SPIR-V module assembled from the given text. Numeric IDs +/// are preserved. +std::vector Assemble(const std::string& spirv_assembly); + +} // namespace test +} // namespace spv +} // namespace reader +} // namespace tint + +#endif // SRC_READER_SPV_SPIRV_TOOLS_HELPERS_TEST_H_