| // Copyright (c) 2017 Pierre Moreau | |
| // | |
| // 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 TEST_LINK_LINKER_FIXTURE_H_ | |
| #define TEST_LINK_LINKER_FIXTURE_H_ | |
| #include <iostream> | |
| #include <string> | |
| #include <vector> | |
| #include "effcee/effcee.h" | |
| #include "re2/re2.h" | |
| #include "source/spirv_constant.h" | |
| #include "spirv-tools/linker.hpp" | |
| #include "test/unit_spirv.h" | |
| namespace spvtest { | |
| using Binary = std::vector<uint32_t>; | |
| using Binaries = std::vector<Binary>; | |
| class LinkerTest : public ::testing::Test { | |
| public: | |
| LinkerTest() | |
| : context_(SPV_ENV_UNIVERSAL_1_2), | |
| tools_(SPV_ENV_UNIVERSAL_1_2), | |
| assemble_options_(spvtools::SpirvTools::kDefaultAssembleOption), | |
| disassemble_options_(spvtools::SpirvTools::kDefaultDisassembleOption) { | |
| const auto consumer = [this](spv_message_level_t level, const char*, | |
| const spv_position_t& position, | |
| const char* message) { | |
| if (!error_message_.empty()) error_message_ += "\n"; | |
| switch (level) { | |
| case SPV_MSG_FATAL: | |
| case SPV_MSG_INTERNAL_ERROR: | |
| case SPV_MSG_ERROR: | |
| error_message_ += "ERROR"; | |
| break; | |
| case SPV_MSG_WARNING: | |
| error_message_ += "WARNING"; | |
| break; | |
| case SPV_MSG_INFO: | |
| error_message_ += "INFO"; | |
| break; | |
| case SPV_MSG_DEBUG: | |
| error_message_ += "DEBUG"; | |
| break; | |
| } | |
| error_message_ += ": " + std::to_string(position.index) + ": " + message; | |
| }; | |
| context_.SetMessageConsumer(consumer); | |
| tools_.SetMessageConsumer(consumer); | |
| } | |
| void TearDown() override { error_message_.clear(); } | |
| // Assembles each of the given strings into SPIR-V binaries before linking | |
| // them together. SPV_ERROR_INVALID_TEXT is returned if the assembling failed | |
| // for any of the input strings, and SPV_ERROR_INVALID_POINTER if | |
| // |linked_binary| is a null pointer. | |
| spv_result_t AssembleAndLink( | |
| const std::vector<std::string>& bodies, spvtest::Binary* linked_binary, | |
| spvtools::LinkerOptions options = spvtools::LinkerOptions()) { | |
| if (!linked_binary) return SPV_ERROR_INVALID_POINTER; | |
| spvtest::Binaries binaries(bodies.size()); | |
| for (size_t i = 0u; i < bodies.size(); ++i) | |
| if (!tools_.Assemble(bodies[i], binaries.data() + i, assemble_options_)) | |
| return SPV_ERROR_INVALID_TEXT; | |
| return spvtools::Link(context_, binaries, linked_binary, options); | |
| } | |
| // Assembles and links a vector of SPIR-V bodies based on the |templateBody|. | |
| // Template arguments to be replaced are written as {a,b,...}. | |
| // SPV_ERROR_INVALID_TEXT is returned if the assembling failed for any of the | |
| // resulting bodies (or errors in the template), and SPV_ERROR_INVALID_POINTER | |
| // if |linked_binary| is a null pointer. | |
| spv_result_t ExpandAndLink( | |
| const std::string& templateBody, spvtest::Binary* linked_binary, | |
| spvtools::LinkerOptions options = spvtools::LinkerOptions()) { | |
| if (!linked_binary) return SPV_ERROR_INVALID_POINTER; | |
| // Find out how many template arguments there are, we assume they all have | |
| // the same number. We'll error later if they don't. | |
| re2::StringPiece temp(templateBody); | |
| re2::StringPiece x; | |
| int cnt = 0; | |
| if (!RE2::FindAndConsume(&temp, "{")) return SPV_ERROR_INVALID_TEXT; | |
| while (RE2::FindAndConsume(&temp, "([,}])", &x) && x[0] == ',') cnt++; | |
| cnt++; | |
| if (cnt <= 1) return SPV_ERROR_INVALID_TEXT; | |
| // Construct a regex for a single common strip and template expansion. | |
| std::string regex("([^{]*){"); | |
| for (int i = 0; i < cnt; i++) regex += (i > 0) ? ",([^,]*)" : "([^,]*)"; | |
| regex += "}"; | |
| RE2 pattern(regex); | |
| // Prepare the RE2::Args for processing. | |
| re2::StringPiece common; | |
| std::vector<re2::StringPiece> variants(cnt); | |
| std::vector<RE2::Arg> args(cnt + 1); | |
| args[0] = RE2::Arg(&common); | |
| std::vector<RE2::Arg*> pargs(cnt + 1); | |
| pargs[0] = &args[0]; | |
| for (int i = 0; i < cnt; i++) { | |
| args[i + 1] = RE2::Arg(&variants[i]); | |
| pargs[i + 1] = &args[i + 1]; | |
| } | |
| // Reset and construct the bodies bit by bit. | |
| std::vector<std::string> bodies(cnt); | |
| re2::StringPiece temp2(templateBody); | |
| while (RE2::ConsumeN(&temp2, pattern, pargs.data(), cnt + 1)) { | |
| for (int i = 0; i < cnt; i++) { | |
| bodies[i].append(common.begin(), common.end()); | |
| bodies[i].append(variants[i].begin(), variants[i].end()); | |
| } | |
| } | |
| RE2::Consume(&temp2, "([^{]*)", &common); | |
| for (int i = 0; i < cnt; i++) | |
| bodies[i].append(common.begin(), common.end()); | |
| // Run through the assemble and link stages of the process. | |
| return AssembleAndLink(bodies, linked_binary, options); | |
| } | |
| // Expand the |templateBody| and link the results as with ExpandAndLink, | |
| // then disassemble and test that the result matches the |expected|. | |
| void ExpandAndCheck( | |
| const std::string& templateBody, const std::string& expected, | |
| const spvtools::LinkerOptions options = spvtools::LinkerOptions()) { | |
| spvtest::Binary linked_binary; | |
| spv_result_t res = ExpandAndLink(templateBody, &linked_binary, options); | |
| EXPECT_EQ(SPV_SUCCESS, res) << GetErrorMessage() << "\nExpanded from:\n" | |
| << templateBody; | |
| if (res == SPV_SUCCESS) { | |
| std::string result; | |
| EXPECT_TRUE( | |
| tools_.Disassemble(linked_binary, &result, disassemble_options_)) | |
| << GetErrorMessage(); | |
| EXPECT_EQ(expected, result); | |
| } | |
| } | |
| void Match(const std::string& templateBody, | |
| const spvtest::Binary& linked_binary) { | |
| std::string result; | |
| EXPECT_TRUE( | |
| tools_.Disassemble(linked_binary, &result, disassemble_options_)) | |
| << GetErrorMessage(); | |
| auto match_res = effcee::Match(result, templateBody); | |
| EXPECT_EQ(effcee::Result::Status::Ok, match_res.status()) | |
| << match_res.message() << "\nExpanded from:\n" | |
| << templateBody << "\nChecking result:\n" | |
| << result; | |
| } | |
| // An alternative to ExpandAndCheck, which uses the |templateBody| as the | |
| // match pattern for the disassembled linked result. | |
| void ExpandAndMatch( | |
| const std::string& templateBody, | |
| const spvtools::LinkerOptions options = spvtools::LinkerOptions()) { | |
| spvtest::Binary linked_binary; | |
| spv_result_t res = ExpandAndLink(templateBody, &linked_binary, options); | |
| EXPECT_EQ(SPV_SUCCESS, res) << GetErrorMessage() << "\nExpanded from:\n" | |
| << templateBody; | |
| if (res == SPV_SUCCESS) { | |
| Match(templateBody, linked_binary); | |
| } | |
| } | |
| // Links the given SPIR-V binaries together; SPV_ERROR_INVALID_POINTER is | |
| // returned if |linked_binary| is a null pointer. | |
| spv_result_t Link( | |
| const spvtest::Binaries& binaries, spvtest::Binary* linked_binary, | |
| spvtools::LinkerOptions options = spvtools::LinkerOptions()) { | |
| if (!linked_binary) return SPV_ERROR_INVALID_POINTER; | |
| return spvtools::Link(context_, binaries, linked_binary, options); | |
| } | |
| // Disassembles |binary| and outputs the result in |text|. If |text| is a | |
| // null pointer, SPV_ERROR_INVALID_POINTER is returned. | |
| spv_result_t Disassemble(const spvtest::Binary& binary, std::string* text) { | |
| if (!text) return SPV_ERROR_INVALID_POINTER; | |
| return tools_.Disassemble(binary, text, disassemble_options_) | |
| ? SPV_SUCCESS | |
| : SPV_ERROR_INVALID_BINARY; | |
| } | |
| // Sets the options for the assembler. | |
| void SetAssembleOptions(uint32_t assemble_options) { | |
| assemble_options_ = assemble_options; | |
| } | |
| // Sets the options used by the disassembler. | |
| void SetDisassembleOptions(uint32_t disassemble_options) { | |
| disassemble_options_ = disassemble_options; | |
| } | |
| // Returns the accumulated error messages for the test. | |
| std::string GetErrorMessage() const { return error_message_; } | |
| bool Validate(const spvtest::Binary& binary) { | |
| return tools_.Validate(binary); | |
| } | |
| private: | |
| spvtools::Context context_; | |
| spvtools::SpirvTools | |
| tools_; // An instance for calling SPIRV-Tools functionalities. | |
| uint32_t assemble_options_; | |
| uint32_t disassemble_options_; | |
| std::string error_message_; | |
| }; | |
| } // namespace spvtest | |
| #endif // TEST_LINK_LINKER_FIXTURE_H_ |