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>
This commit is contained in:
dan sinclair 2020-03-23 20:59:12 +00:00 committed by dan sinclair
parent 781a4acb6f
commit dc200f7d1d
9 changed files with 215 additions and 69 deletions

View File

@ -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()

View File

@ -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<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

View File

@ -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<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_;
};

View File

@ -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<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 @@ 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<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 @@ 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

View File

@ -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) {

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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_