diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aefe620678..39156b5948 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -402,6 +402,8 @@ if(${TINT_BUILD_SPV_WRITER}) writer/spirv/builder_entry_point_test.cc writer/spirv/instruction_test.cc writer/spirv/operand_test.cc + writer/spirv/spv_dump.cc + writer/spirv/spv_dump.h ) endif() diff --git a/src/writer/spirv/binary_writer.cc b/src/writer/spirv/binary_writer.cc index a5d8a3f6a9..81d8fac2e3 100644 --- a/src/writer/spirv/binary_writer.cc +++ b/src/writer/spirv/binary_writer.cc @@ -31,39 +31,48 @@ BinaryWriter::BinaryWriter() = default; BinaryWriter::~BinaryWriter() = default; -bool BinaryWriter::Write(const Builder& builder) { - out_.resize(builder.total_size(), 0); +void BinaryWriter::WriteBuilder(const Builder& builder) { + out_.reserve(builder.total_size()); + builder.iterate( + [this](const Instruction& inst) { this->process_instruction(inst); }); +} - out_[idx_++] = spv::MagicNumber; - out_[idx_++] = 0x00010300; // Version 1.3 - out_[idx_++] = kGeneratorId; - out_[idx_++] = builder.id_bound(); - out_[idx_++] = 0; +void BinaryWriter::WriteInstruction(const Instruction& inst) { + process_instruction(inst); +} - builder.iterate([this](const Instruction& inst) { - out_[idx_++] = - inst.word_length() << 16 | static_cast(inst.opcode()); +void BinaryWriter::WriteHeader(uint32_t bound) { + out_.push_back(spv::MagicNumber); + out_.push_back(0x00010300); // Version 1.3 + out_.push_back(kGeneratorId); + out_.push_back(bound); + out_.push_back(0); +} - for (const auto& op : inst.operands()) { - process_op(op); - } - }); - return true; +void BinaryWriter::process_instruction(const Instruction& inst) { + out_.push_back(inst.word_length() << 16 | + static_cast(inst.opcode())); + + for (const auto& op : inst.operands()) { + process_op(op); + } } void BinaryWriter::process_op(const Operand& op) { if (op.IsFloat()) { + // Allocate space for the float + out_.push_back(0); auto f = op.to_f(); - memcpy(out_.data() + idx_, &f, 4); + uint8_t* ptr = reinterpret_cast(out_.data() + (out_.size() - 1)); + memcpy(ptr, &f, 4); } else if (op.IsInt()) { - out_[idx_] = op.to_i(); + out_.push_back(op.to_i()); } else { + auto idx = out_.size(); const auto& str = op.to_s(); - // This depends on the vector being initialized to 0 values so the string - // is correctly padded. - memcpy(out_.data() + idx_, str.c_str(), str.size() + 1); + out_.resize(out_.size() + op.length(), 0); + memcpy(out_.data() + idx, str.c_str(), str.size() + 1); } - idx_ += op.length(); } } // namespace spirv diff --git a/src/writer/spirv/binary_writer.h b/src/writer/spirv/binary_writer.h index 972f001f27..84fd53e09b 100644 --- a/src/writer/spirv/binary_writer.h +++ b/src/writer/spirv/binary_writer.h @@ -30,19 +30,27 @@ class BinaryWriter { BinaryWriter(); ~BinaryWriter(); - /// Writes the given builder data into a binary + /// Writes the SPIR-V header. + /// @param bound the bound to output + void WriteHeader(uint32_t bound); + + /// Writes the given builder data into a binary. Note, this does not emit + /// the SPIR-V header. You |must| call |WriteHeader| before |WriteBuilder| + /// if you want the SPIR-V to be emitted. /// @param builder the builder to assemble from - /// @returns true on success - bool Write(const Builder& builder); + void WriteBuilder(const Builder& builder); + + /// Writes the given instruction into the binary. + /// @param inst the instruction to assemble + void WriteInstruction(const Instruction& inst); /// @returns the assembled SPIR-V const std::vector& result() const { return out_; } private: + void process_instruction(const Instruction& inst); void process_op(const Operand& op); - /// Word index of the next word to fill. - size_t idx_ = 0; std::vector out_; }; diff --git a/src/writer/spirv/binary_writer_test.cc b/src/writer/spirv/binary_writer_test.cc index 6e6265b0a8..564bb066a3 100644 --- a/src/writer/spirv/binary_writer_test.cc +++ b/src/writer/spirv/binary_writer_test.cc @@ -29,14 +29,14 @@ using BinaryWriterTest = testing::Test; TEST_F(BinaryWriterTest, Preamble) { Builder b; BinaryWriter bw; - ASSERT_TRUE(bw.Write(b)); + bw.WriteHeader(5); auto res = bw.result(); ASSERT_EQ(res.size(), 5); EXPECT_EQ(res[0], spv::MagicNumber); EXPECT_EQ(res[1], 0x00010300); // SPIR-V 1.3 EXPECT_EQ(res[2], 0); // Generator ID - EXPECT_EQ(res[3], 1); // ID Bound + EXPECT_EQ(res[3], 5); // ID Bound EXPECT_EQ(res[4], 0); // Reserved } @@ -44,12 +44,12 @@ TEST_F(BinaryWriterTest, Float) { Builder b; b.push_preamble(spv::Op::OpNop, {Operand::Float(2.4f)}); BinaryWriter bw; - ASSERT_TRUE(bw.Write(b)); + bw.WriteBuilder(b); auto res = bw.result(); - ASSERT_EQ(res.size(), 7); + ASSERT_EQ(res.size(), 2); float f; - memcpy(&f, res.data() + 6, 4); + memcpy(&f, res.data() + 1, 4); EXPECT_EQ(f, 2.4f); } @@ -57,23 +57,23 @@ TEST_F(BinaryWriterTest, Int) { Builder b; b.push_preamble(spv::Op::OpNop, {Operand::Int(2)}); BinaryWriter bw; - ASSERT_TRUE(bw.Write(b)); + bw.WriteBuilder(b); auto res = bw.result(); - ASSERT_EQ(res.size(), 7); - EXPECT_EQ(res[6], 2); + ASSERT_EQ(res.size(), 2); + EXPECT_EQ(res[1], 2); } TEST_F(BinaryWriterTest, String) { Builder b; b.push_preamble(spv::Op::OpNop, {Operand::String("my_string")}); BinaryWriter bw; - ASSERT_TRUE(bw.Write(b)); + bw.WriteBuilder(b); auto res = bw.result(); - ASSERT_EQ(res.size(), 9); + ASSERT_EQ(res.size(), 4); - uint8_t* v = reinterpret_cast(res.data()) + (6 * 4); + uint8_t* v = reinterpret_cast(res.data() + 1); EXPECT_EQ(v[0], 'm'); EXPECT_EQ(v[1], 'y'); EXPECT_EQ(v[2], '_'); @@ -92,12 +92,12 @@ TEST_F(BinaryWriterTest, String_Multiple4Length) { Builder b; b.push_preamble(spv::Op::OpNop, {Operand::String("mystring")}); BinaryWriter bw; - ASSERT_TRUE(bw.Write(b)); + bw.WriteBuilder(b); auto res = bw.result(); - ASSERT_EQ(res.size(), 9); + ASSERT_EQ(res.size(), 4); - uint8_t* v = reinterpret_cast(res.data()) + (6 * 4); + uint8_t* v = reinterpret_cast(res.data() + 1); EXPECT_EQ(v[0], 'm'); EXPECT_EQ(v[1], 'y'); EXPECT_EQ(v[2], 's'); @@ -112,6 +112,20 @@ TEST_F(BinaryWriterTest, String_Multiple4Length) { EXPECT_EQ(v[11], '\0'); } +TEST_F(BinaryWriterTest, TestInstructionWriter) { + Instruction i1{spv::Op::OpNop, {Operand::Int(2)}}; + Instruction i2{spv::Op::OpNop, {Operand::Int(4)}}; + + BinaryWriter bw; + bw.WriteInstruction(i1); + bw.WriteInstruction(i2); + + auto res = bw.result(); + ASSERT_EQ(res.size(), 4); + EXPECT_EQ(res[1], 2); + EXPECT_EQ(res[3], 4); +} + } // namespace spirv } // namespace writer } // namespace tint diff --git a/src/writer/spirv/builder_entry_point_test.cc b/src/writer/spirv/builder_entry_point_test.cc index 454aaae48a..953fb4765e 100644 --- a/src/writer/spirv/builder_entry_point_test.cc +++ b/src/writer/spirv/builder_entry_point_test.cc @@ -20,6 +20,7 @@ #include "src/ast/entry_point.h" #include "src/ast/pipeline_stage.h" #include "src/writer/spirv/builder.h" +#include "src/writer/spirv/spv_dump.h" namespace tint { namespace writer { @@ -36,12 +37,8 @@ TEST_F(BuilderTest, EntryPoint) { auto preamble = b.preamble(); ASSERT_EQ(preamble.size(), 1); - EXPECT_EQ(preamble[0].opcode(), spv::Op::OpEntryPoint); - - ASSERT_TRUE(preamble[0].operands().size() >= 3); - EXPECT_EQ(preamble[0].operands()[0].to_i(), SpvExecutionModelFragment); - EXPECT_EQ(preamble[0].operands()[1].to_i(), 2); - EXPECT_EQ(preamble[0].operands()[2].to_s(), "main"); + EXPECT_EQ(DumpInstruction(preamble[0]), R"(OpEntryPoint Fragment %2 "main" +)"); } TEST_F(BuilderTest, EntryPoint_WithoutName) { @@ -53,12 +50,9 @@ TEST_F(BuilderTest, EntryPoint_WithoutName) { auto preamble = b.preamble(); ASSERT_EQ(preamble.size(), 1); - EXPECT_EQ(preamble[0].opcode(), spv::Op::OpEntryPoint); - - ASSERT_TRUE(preamble[0].operands().size() >= 3); - EXPECT_EQ(preamble[0].operands()[0].to_i(), SpvExecutionModelGLCompute); - EXPECT_EQ(preamble[0].operands()[1].to_i(), 3); - EXPECT_EQ(preamble[0].operands()[2].to_s(), "compute_main"); + EXPECT_EQ(DumpInstruction(preamble[0]), + R"(OpEntryPoint GLCompute %3 "compute_main" +)"); } TEST_F(BuilderTest, EntryPoint_BadFunction) { diff --git a/src/writer/spirv/builder_test.cc b/src/writer/spirv/builder_test.cc index 8d06910b6e..f92f62ea9b 100644 --- a/src/writer/spirv/builder_test.cc +++ b/src/writer/spirv/builder_test.cc @@ -21,6 +21,7 @@ #include "spirv/unified1/spirv.hpp11" #include "src/ast/import.h" #include "src/ast/module.h" +#include "src/writer/spirv/spv_dump.h" namespace tint { namespace writer { @@ -36,28 +37,21 @@ TEST_F(BuilderTest, InsertsPreambleWithImport) { ASSERT_TRUE(b.Build(m)); ASSERT_EQ(b.preamble().size(), 4); - auto pre = b.preamble(); - EXPECT_EQ(pre[0].opcode(), spv::Op::OpCapability); - EXPECT_EQ(pre[0].operands()[0].to_i(), SpvCapabilityShader); - EXPECT_EQ(pre[1].opcode(), spv::Op::OpCapability); - EXPECT_EQ(pre[1].operands()[0].to_i(), SpvCapabilityVulkanMemoryModel); - EXPECT_EQ(pre[2].opcode(), spv::Op::OpExtInstImport); - EXPECT_EQ(pre[2].operands()[1].to_s(), "GLSL.std.450"); - EXPECT_EQ(pre[3].opcode(), spv::Op::OpMemoryModel); + EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader +OpCapability VulkanMemoryModel +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical Vulkan +)"); } TEST_F(BuilderTest, InsertsPreambleWithoutImport) { ast::Module m; Builder b; ASSERT_TRUE(b.Build(m)); - ASSERT_EQ(b.preamble().size(), 3); - - auto pre = b.preamble(); - EXPECT_EQ(pre[0].opcode(), spv::Op::OpCapability); - EXPECT_EQ(pre[0].operands()[0].to_i(), SpvCapabilityShader); - EXPECT_EQ(pre[1].opcode(), spv::Op::OpCapability); - EXPECT_EQ(pre[1].operands()[0].to_i(), SpvCapabilityVulkanMemoryModel); - EXPECT_EQ(pre[2].opcode(), spv::Op::OpMemoryModel); + EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader +OpCapability VulkanMemoryModel +OpMemoryModel Logical Vulkan +)"); } TEST_F(BuilderTest, TracksIdBounds) { diff --git a/src/writer/spirv/generator.cc b/src/writer/spirv/generator.cc index 2f0747c9ad..a530fd0a9b 100644 --- a/src/writer/spirv/generator.cc +++ b/src/writer/spirv/generator.cc @@ -30,7 +30,9 @@ bool Generator::Generate() { return false; } - return writer_.Write(builder_); + writer_.WriteHeader(builder_.id_bound()); + writer_.WriteBuilder(builder_); + return true; } } // namespace spirv diff --git a/src/writer/spirv/spv_dump.cc b/src/writer/spirv/spv_dump.cc new file mode 100644 index 0000000000..e001a3a99b --- /dev/null +++ b/src/writer/spirv/spv_dump.cc @@ -0,0 +1,82 @@ +// 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/writer/spirv/spv_dump.h" + +#include "spirv-tools/libspirv.hpp" +#include "src/writer/spirv/binary_writer.h" + +namespace tint { +namespace writer { +namespace spirv { +namespace { + +std::string Disassemble(const std::vector& data) { + std::string spv_errors; + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0; + + auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*, + const spv_position_t& position, + const char* message) { + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + spv_errors += "error: line " + std::to_string(position.index) + ": " + + message + "\n"; + break; + case SPV_MSG_WARNING: + spv_errors += "warning: line " + std::to_string(position.index) + ": " + + message + "\n"; + break; + case SPV_MSG_INFO: + spv_errors += "info: line " + std::to_string(position.index) + ": " + + message + "\n"; + break; + case SPV_MSG_DEBUG: + break; + } + }; + + spvtools::SpirvTools tools(target_env); + tools.SetMessageConsumer(msg_consumer); + + std::string result; + if (!tools.Disassemble(data, &result, + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER)) { + printf("%s\n", spv_errors.c_str()); + } + return result; +} + +} // namespace + +std::string DumpBuilder(const Builder& builder) { + BinaryWriter writer; + writer.WriteHeader(builder.id_bound()); + writer.WriteBuilder(builder); + return Disassemble(writer.result()); +} + +std::string DumpInstruction(const Instruction& inst) { + BinaryWriter writer; + writer.WriteHeader(kDefaultMaxIdBound); + writer.WriteInstruction(inst); + return Disassemble(writer.result()); +} + +} // namespace spirv +} // namespace writer +} // namespace tint diff --git a/src/writer/spirv/spv_dump.h b/src/writer/spirv/spv_dump.h new file mode 100644 index 0000000000..295861cc3b --- /dev/null +++ b/src/writer/spirv/spv_dump.h @@ -0,0 +1,41 @@ +// 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_WRITER_SPIRV_SPV_DUMP_H_ +#define SRC_WRITER_SPIRV_SPV_DUMP_H_ + +#include + +#include "src/writer/spirv/builder.h" +#include "src/writer/spirv/instruction.h" + +namespace tint { +namespace writer { +namespace spirv { + +/// Dumps the given builder to a SPIR-V disassembly string +/// @param builder the builder to convert +/// @returns the builder as a SPIR-V disassembly string +std::string DumpBuilder(const Builder& builder); + +/// Dumps the given instruction to a SPIR-V disassembly string +/// @param inst the instruction to dump +/// @returns the instruction as a SPIR-V disassembly string +std::string DumpInstruction(const Instruction& inst); + +} // namespace spirv +} // namespace writer +} // namespace tint + +#endif // SRC_WRITER_SPIRV_SPV_DUMP_H_