Add basic SPIR-V 1.4 support to the SPIR-V writer
Fixed: 422422592
* Adds command line option to specify SPIR-V binary version
* Adds a binary version to the spir-v writer options
* passed on to the binary writer for the module header
* When SPIR-V 1.4 is enabled, OpEntryPoint lists all global variables
Change-Id: I13ca8f247d42b53e24f0b3921b3d9c09dc362d77
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/245514
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Alan Baker <alanbaker@google.com>
Auto-Submit: Alan Baker <alanbaker@google.com>
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index 5b368a5..8778487 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -168,6 +168,7 @@
#if TINT_BUILD_SPV_WRITER
bool use_storage_input_output_16 = true;
+ tint::spirv::writer::SpvVersion spirv_version = tint::spirv::writer::SpvVersion::kSpv13;
#endif // TINT_BULD_SPV_WRITER
#if TINT_BUILD_MSL_WRITER
@@ -410,6 +411,16 @@
options.Add<BoolOption>("use-storage-input-output-16",
"Use the StorageInputOutput16 SPIR-V capability", Default{true});
TINT_DEFER(opts->use_storage_input_output_16 = *use_storage_input_output_16.value);
+
+ tint::Vector<EnumName<tint::spirv::writer::SpvVersion>, 2> version_enum_names{
+ EnumName(tint::spirv::writer::SpvVersion::kSpv13, "1.3"),
+ EnumName(tint::spirv::writer::SpvVersion::kSpv14, "1.4"),
+ };
+ auto& spirv_version = options.Add<EnumOption<tint::spirv::writer::SpvVersion>>(
+ "spirv-version", R"(Specify the SPIR-V binary version.
+Valid values are 1.3 and 1.4)",
+ version_enum_names, Default{tint::spirv::writer::SpvVersion::kSpv13});
+ TINT_DEFER(opts->spirv_version = *spirv_version.value);
#endif // TINT_BUILD_SPV_WRITER
#if TINT_BUILD_GLSL_WRITER
@@ -890,6 +901,7 @@
gen_options.disable_robustness = !options.enable_robustness;
gen_options.disable_workgroup_init = options.disable_workgroup_init;
gen_options.use_storage_input_output_16 = options.use_storage_input_output_16;
+ gen_options.spirv_version = options.spirv_version;
auto entry_point = inspector.GetEntryPoint(options.ep_name);
diff --git a/src/tint/lang/spirv/writer/common/binary_writer.cc b/src/tint/lang/spirv/writer/common/binary_writer.cc
index bf78190..ed6275f 100644
--- a/src/tint/lang/spirv/writer/common/binary_writer.cc
+++ b/src/tint/lang/spirv/writer/common/binary_writer.cc
@@ -52,9 +52,9 @@
ProcessInstruction(inst);
}
-void BinaryWriter::WriteHeader(uint32_t bound, uint32_t version) {
+void BinaryWriter::WriteHeader(uint32_t bound, uint32_t version, uint32_t spirv_version) {
out_.push_back(spv::MagicNumber);
- out_.push_back(0x00010300); // Version 1.3
+ out_.push_back(spirv_version);
out_.push_back(kGeneratorId | version);
out_.push_back(bound);
out_.push_back(0);
diff --git a/src/tint/lang/spirv/writer/common/binary_writer.h b/src/tint/lang/spirv/writer/common/binary_writer.h
index 0463810..4909235 100644
--- a/src/tint/lang/spirv/writer/common/binary_writer.h
+++ b/src/tint/lang/spirv/writer/common/binary_writer.h
@@ -44,7 +44,8 @@
/// Writes the SPIR-V header.
/// @param bound the bound to output
/// @param version the generator version number
- void WriteHeader(uint32_t bound, uint32_t version = 0);
+ /// @param spirv_version the SPIR-V binary version (default SPIR-V 1.3).
+ void WriteHeader(uint32_t bound, uint32_t version = 0, uint32_t spirv_version = 0x10300u);
/// Writes the given module data into a binary. Note, this does not emit the SPIR-V header. You
/// **must** call WriteHeader() before WriteModule() if you want the SPIR-V to be emitted.
diff --git a/src/tint/lang/spirv/writer/common/binary_writer_test.cc b/src/tint/lang/spirv/writer/common/binary_writer_test.cc
index 6ee91a3..c2254aa 100644
--- a/src/tint/lang/spirv/writer/common/binary_writer_test.cc
+++ b/src/tint/lang/spirv/writer/common/binary_writer_test.cc
@@ -46,6 +46,19 @@
EXPECT_EQ(res[4], 0u); // Reserved
}
+TEST_F(SpirvWriterBinaryWriterTest, Preamble_Spirv1p4) {
+ BinaryWriter bw;
+ bw.WriteHeader(5, 0, 0x10400);
+
+ auto res = bw.Result();
+ ASSERT_EQ(res.size(), 5u);
+ EXPECT_EQ(res[0], spv::MagicNumber);
+ EXPECT_EQ(res[1], 0x00010400u); // SPIR-V 1.4
+ EXPECT_EQ(res[2], 23u << 16); // Generator ID
+ EXPECT_EQ(res[3], 5u); // ID Bound
+ EXPECT_EQ(res[4], 0u); // Reserved
+}
+
TEST_F(SpirvWriterBinaryWriterTest, Float) {
Module m;
diff --git a/src/tint/lang/spirv/writer/common/options.h b/src/tint/lang/spirv/writer/common/options.h
index d1372b4..37741e5 100644
--- a/src/tint/lang/spirv/writer/common/options.h
+++ b/src/tint/lang/spirv/writer/common/options.h
@@ -129,6 +129,12 @@
input_attachment);
};
+/// Supported SPIR-V binary versions.
+enum class SpvVersion : uint32_t {
+ kSpv13 = 0x10300u, // SPIR-V 1.3
+ kSpv14 = 0x10400u, // SPIR-V 1.4
+};
+
/// Configuration options used for generating SPIR-V.
struct Options {
struct RangeOffsets {
@@ -210,6 +216,15 @@
/// Offsets of the minDepth and maxDepth push constants.
std::optional<RangeOffsets> depth_range_offsets = std::nullopt;
+ /// SPIR-V binary version.
+ SpvVersion spirv_version = SpvVersion::kSpv13;
+
+ /// Returns true if the binary version is less than major.minor.
+ bool SpirvVersionLess(uint32_t major, uint32_t minor) const {
+ return static_cast<uint32_t>(spirv_version) <
+ (((major & 0xffff) << 16) | ((minor & 0xffff) << 8));
+ }
+
/// Reflect the fields of this class so that it can be used by tint::ForeachField()
TINT_REFLECT(Options,
remapped_entry_point_name,
@@ -232,9 +247,17 @@
use_vulkan_memory_model,
scalarize_clamp_builtin,
dva_transform_handle,
- depth_range_offsets);
+ depth_range_offsets,
+ spirv_version);
};
} // namespace tint::spirv::writer
+namespace tint {
+
+/// Reflect enum information for SPIR-V version.
+TINT_REFLECT_ENUM_RANGE(spirv::writer::SpvVersion, kSpv13, kSpv14);
+
+} // namespace tint
+
#endif // SRC_TINT_LANG_SPIRV_WRITER_COMMON_OPTIONS_H_
diff --git a/src/tint/lang/spirv/writer/common/options_test.cc b/src/tint/lang/spirv/writer/common/options_test.cc
index 70c136a..9f4e3b7 100644
--- a/src/tint/lang/spirv/writer/common/options_test.cc
+++ b/src/tint/lang/spirv/writer/common/options_test.cc
@@ -39,5 +39,31 @@
TINT_ASSERT_ALL_FIELDS_REFLECTED(Options);
}
+using SpirvWriterOptionsTest = ::testing::Test;
+
+TEST_F(SpirvWriterOptionsTest, SpirvVersionLess) {
+ Options options;
+
+ // Default is SPIR-V 1.3
+ EXPECT_FALSE(options.SpirvVersionLess(1, 0));
+ EXPECT_FALSE(options.SpirvVersionLess(1, 1));
+ EXPECT_FALSE(options.SpirvVersionLess(1, 2));
+ EXPECT_FALSE(options.SpirvVersionLess(1, 3));
+ EXPECT_TRUE(options.SpirvVersionLess(1, 4));
+ EXPECT_TRUE(options.SpirvVersionLess(1, 5));
+ EXPECT_TRUE(options.SpirvVersionLess(1, 6));
+ EXPECT_TRUE(options.SpirvVersionLess(1, 7));
+
+ options.spirv_version = SpvVersion::kSpv14;
+ EXPECT_FALSE(options.SpirvVersionLess(1, 0));
+ EXPECT_FALSE(options.SpirvVersionLess(1, 1));
+ EXPECT_FALSE(options.SpirvVersionLess(1, 2));
+ EXPECT_FALSE(options.SpirvVersionLess(1, 3));
+ EXPECT_FALSE(options.SpirvVersionLess(1, 4));
+ EXPECT_TRUE(options.SpirvVersionLess(1, 5));
+ EXPECT_TRUE(options.SpirvVersionLess(1, 6));
+ EXPECT_TRUE(options.SpirvVersionLess(1, 7));
+}
+
} // namespace
} // namespace tint::spirv::writer
diff --git a/src/tint/lang/spirv/writer/printer/printer.cc b/src/tint/lang/spirv/writer/printer/printer.cc
index 8680fd38..602f165 100644
--- a/src/tint/lang/spirv/writer/printer/printer.cc
+++ b/src/tint/lang/spirv/writer/printer/printer.cc
@@ -219,7 +219,8 @@
// Serialize the module into binary SPIR-V.
BinaryWriter writer;
- writer.WriteHeader(module_.IdBound(), kWriterVersion);
+ writer.WriteHeader(module_.IdBound(), kWriterVersion,
+ static_cast<uint32_t>(options_.spirv_version));
writer.WriteModule(module_);
output_.spirv = std::move(writer.Result());
@@ -862,27 +863,35 @@
}
auto* ptr = var->Result()->Type()->As<core::type::Pointer>();
- if (!(ptr->AddressSpace() == core::AddressSpace::kIn ||
- ptr->AddressSpace() == core::AddressSpace::kOut)) {
- continue;
- }
+ if (options_.SpirvVersionLess(1, 4)) {
+ // In SPIR-V 1.3 or earlier, OpEntryPoint should list only statically used
+ // input/output variables.
+ if (!(ptr->AddressSpace() == core::AddressSpace::kIn ||
+ ptr->AddressSpace() == core::AddressSpace::kOut)) {
+ continue;
+ }
- // Determine if this IO variable is used by the entry point.
- bool used = false;
- for (const auto& use : var->Result()->UsagesUnsorted()) {
- auto* block = use->instruction->Block();
- while (block->Parent()) {
- block = block->Parent()->Block();
+ // Determine if this IO variable is used by the entry point.
+ bool used = false;
+ for (const auto& use : var->Result()->UsagesUnsorted()) {
+ auto* block = use->instruction->Block();
+ while (block->Parent()) {
+ block = block->Parent()->Block();
+ }
+ if (block == func->Block()) {
+ used = true;
+ break;
+ }
}
- if (block == func->Block()) {
- used = true;
- break;
+ if (!used) {
+ continue;
}
+ operands.push_back(Value(var));
+ } else {
+ // In SPIR-V 1.4 or later, OpEntryPoint must list all global variables statically
+ // used by the entry point. It may be a superset of the used variables though.
+ operands.push_back(Value(var));
}
- if (!used) {
- continue;
- }
- operands.push_back(Value(var));
// Add the `DepthReplacing` execution mode if `frag_depth` is used.
if (var->Attributes().builtin == core::BuiltinValue::kFragDepth) {
diff --git a/src/tint/lang/spirv/writer/writer_test.cc b/src/tint/lang/spirv/writer/writer_test.cc
index da107c7..43fa833 100644
--- a/src/tint/lang/spirv/writer/writer_test.cc
+++ b/src/tint/lang/spirv/writer/writer_test.cc
@@ -154,6 +154,88 @@
EXPECT_INST("OpEntryPoint GLCompute %main \"main\"");
}
+TEST_F(SpirvWriterTest, EntryPoint_FunctionVar_Spirv1p3) {
+ auto* func = b.ComputeFunction("main");
+ b.Append(func->Block(), [&] { //
+ b.Var("x", 0_u);
+ b.Return(func);
+ });
+
+ Options options;
+ options.remapped_entry_point_name = "";
+ ASSERT_TRUE(Generate(options)) << Error() << output_;
+ EXPECT_INST("OpEntryPoint GLCompute %main \"main\"\n");
+}
+
+TEST_F(SpirvWriterTest, EntryPoint_FunctionVar_Spirv1p4) {
+ auto* func = b.ComputeFunction("main");
+ b.Append(func->Block(), [&] { //
+ b.Var("x", 0_u);
+ b.Return(func);
+ });
+
+ Options options;
+ options.remapped_entry_point_name = "";
+ options.spirv_version = SpvVersion::kSpv14;
+ ASSERT_TRUE(Generate(options)) << Error() << output_;
+ EXPECT_INST("OpEntryPoint GLCompute %main \"main\"\n");
+}
+
+TEST_F(SpirvWriterTest, EntryPoint_StorageVar_Spirv1p3) {
+ auto* v = b.Var("v", core::AddressSpace::kStorage, ty.u32(), core::Access::kReadWrite);
+ mod.root_block->Append(v);
+ v->SetBindingPoint(0, 0);
+ auto* func = b.ComputeFunction("main");
+ b.Append(func->Block(), [&] { //
+ b.Load(v);
+ b.Return(func);
+ });
+
+ Options options;
+ options.remapped_entry_point_name = "";
+ ASSERT_TRUE(Generate(options)) << Error() << output_;
+ EXPECT_INST("OpEntryPoint GLCompute %main \"main\"\n");
+}
+
+TEST_F(SpirvWriterTest, EntryPoint_StorageVar_Spirv1p4) {
+ auto* v = b.Var("v", core::AddressSpace::kStorage, ty.u32(), core::Access::kReadWrite);
+ mod.root_block->Append(v);
+ v->SetBindingPoint(0, 0);
+ auto* func = b.ComputeFunction("main");
+ b.Append(func->Block(), [&] { //
+ b.Load(v);
+ b.Return(func);
+ });
+
+ Options options;
+ options.remapped_entry_point_name = "";
+ options.spirv_version = SpvVersion::kSpv14;
+ ASSERT_TRUE(Generate(options)) << Error() << output_;
+ EXPECT_INST("OpEntryPoint GLCompute %main \"main\" %1");
+}
+
+TEST_F(SpirvWriterTest, EntryPoint_StorageVar_CalledFunction_Spirv1p4) {
+ auto* v = b.Var("v", core::AddressSpace::kStorage, ty.u32(), core::Access::kReadWrite);
+ mod.root_block->Append(v);
+ v->SetBindingPoint(0, 0);
+ auto* foo = b.Function("foo", ty.void_());
+ b.Append(foo->Block(), [&] { //
+ b.Load(v);
+ b.Return(foo);
+ });
+ auto* func = b.ComputeFunction("main");
+ b.Append(func->Block(), [&] { //
+ b.Call(foo);
+ b.Return(func);
+ });
+
+ Options options;
+ options.remapped_entry_point_name = "";
+ options.spirv_version = SpvVersion::kSpv14;
+ ASSERT_TRUE(Generate(options)) << Error() << output_;
+ EXPECT_INST("OpEntryPoint GLCompute %main \"main\" %1");
+}
+
TEST_F(SpirvWriterTest, StripAllNames) {
auto* str =
ty.Struct(mod.symbols.New("MyStruct"), {