Add SPIR-V dump to the SPIR-V generator
This Cl adds utility classes to dump out SPIR-V disassembly of the
builder and instructions.
Bug: tint:5
Change-Id: Ib4c57025ac63cb0be456bd819461c98ffa94367f
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/17560
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index aefe620..39156b5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -402,6 +402,8 @@
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 a5d8a3f..81d8fac 100644
--- a/src/writer/spirv/binary_writer.cc
+++ b/src/writer/spirv/binary_writer.cc
@@ -31,39 +31,48 @@
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<uint32_t>(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<uint32_t>(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<uint8_t*>(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 972f001..84fd53e 100644
--- a/src/writer/spirv/binary_writer.h
+++ b/src/writer/spirv/binary_writer.h
@@ -30,19 +30,27 @@
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<uint32_t>& 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<uint32_t> out_;
};
diff --git a/src/writer/spirv/binary_writer_test.cc b/src/writer/spirv/binary_writer_test.cc
index 6e6265b..564bb06 100644
--- a/src/writer/spirv/binary_writer_test.cc
+++ b/src/writer/spirv/binary_writer_test.cc
@@ -29,14 +29,14 @@
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 @@
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 @@
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<uint8_t*>(res.data()) + (6 * 4);
+ uint8_t* v = reinterpret_cast<uint8_t*>(res.data() + 1);
EXPECT_EQ(v[0], 'm');
EXPECT_EQ(v[1], 'y');
EXPECT_EQ(v[2], '_');
@@ -92,12 +92,12 @@
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<uint8_t*>(res.data()) + (6 * 4);
+ uint8_t* v = reinterpret_cast<uint8_t*>(res.data() + 1);
EXPECT_EQ(v[0], 'm');
EXPECT_EQ(v[1], 'y');
EXPECT_EQ(v[2], 's');
@@ -112,6 +112,20 @@
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 454aaae..953fb47 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 @@
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 @@
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 8d06910..f92f62e 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 @@
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 2f0747c..a530fd0 100644
--- a/src/writer/spirv/generator.cc
+++ b/src/writer/spirv/generator.cc
@@ -30,7 +30,9 @@
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 0000000..e001a3a
--- /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<uint32_t>& 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 0000000..295861c
--- /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 <string>
+
+#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_