mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-06-01 12:11:34 +00:00
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:
parent
781a4acb6f
commit
dc200f7d1d
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
82
src/writer/spirv/spv_dump.cc
Normal file
82
src/writer/spirv/spv_dump.cc
Normal 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
|
41
src/writer/spirv/spv_dump.h
Normal file
41
src/writer/spirv/spv_dump.h
Normal 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_
|
Loading…
x
Reference in New Issue
Block a user