spirv-reader: Infer a handle type when needed
Fixed: tint:374
Change-Id: I4785a48d456a6b5ba1fa8c9950b4c72d00853fc9
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/33981
Commit-Queue: David Neto <dneto@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Auto-Submit: David Neto <dneto@google.com>
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index f41c4ed..d6ed08b 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -1604,63 +1604,128 @@
ast::type::Type* ParserImpl::GetTypeForHandleVar(
const spvtools::opt::Instruction& var) {
- // This can be a sampler or image.
- // Determine it from the usage inferred for the variable.
- const Usage& usage = handle_usage_[&var];
+ // The WGSL handle type is determined by looking at information from
+ // several sources:
+ // - the usage of the handle by image access instructions
+ // - the SPIR-V type declaration
+ // Each source does not have enough information to completely determine
+ // the result.
+
+ // Messages are phrased in terms of images and samplers because those
+ // are the only SPIR-V handles supported by WGSL.
+
+ // Get the SPIR-V handle type.
+ const auto* ptr_type = def_use_mgr_->GetDef(var.type_id());
+ if (!ptr_type) {
+ Fail() << "Invalid type for variable " << var.PrettyPrint();
+ return nullptr;
+ }
+ const auto* raw_handle_type =
+ def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
+ if (!raw_handle_type) {
+ Fail() << "Invalid pointer type for variable " << var.PrettyPrint();
+ return nullptr;
+ }
+ switch (raw_handle_type->opcode()) {
+ case SpvOpTypeSampler:
+ case SpvOpTypeImage:
+ // The expected cases.
+ break;
+ case SpvOpTypeArray:
+ case SpvOpTypeRuntimeArray:
+ Fail()
+ << "arrays of textures or samplers are not supported in WGSL; can't "
+ "translate variable "
+ << var.PrettyPrint();
+ return nullptr;
+ default:
+ Fail() << "invalid type for image or sampler variable "
+ << var.PrettyPrint();
+ return nullptr;
+ }
+
+ // The variable could be a sampler or image.
+ // Where possible, determine which one it is from the usage inferred
+ // for the variable.
+ Usage usage = handle_usage_[&var];
if (!usage.IsValid()) {
Fail() << "Invalid sampler or texture usage for variable "
<< var.PrettyPrint() << "\n"
<< usage;
return nullptr;
}
+ // Infer a handle type, if usage didn't already tell us.
if (!usage.IsComplete()) {
- // TODO(dneto): In SPIR-V you could statically reference a texture or
- // sampler without using it in a way that gives us a clue on how to declare
- // it. In that case look at the underlying OpTypeSampler or OpTypeImage; in
- // the OpTypeImage see if it has a format. Sampled images alway have
- // Unknown format. For WGSL, storage images always have a format. And then
- // check for NonWritable or NonReadabl
- Fail() << "Unsupported: Incomplete usage on samper or texture usage for "
- "variable "
- << var.PrettyPrint() << "\n"
- << usage;
- return nullptr;
+ // In SPIR-V you could statically reference a texture or sampler without
+ // using it in a way that gives us a clue on how to declare it. Look inside
+ // the store type to infer a usage.
+ if (raw_handle_type->opcode() == SpvOpTypeSampler) {
+ usage.AddSampler();
+ } else {
+ // It's a texture.
+ if (raw_handle_type->NumInOperands() != 7) {
+ Fail() << "invalid SPIR-V image type: expected 7 operands: "
+ << raw_handle_type->PrettyPrint();
+ return nullptr;
+ }
+ const auto sampled_param = raw_handle_type->GetSingleWordInOperand(5);
+ const auto format_param = raw_handle_type->GetSingleWordInOperand(6);
+ // Only storage images have a format.
+ if ((format_param != SpvImageFormatUnknown) ||
+ sampled_param == 2 /* without sampler */) {
+ // Get NonWritable and NonReadable attributes of the variable.
+ bool is_nonwritable = false;
+ bool is_nonreadable = false;
+ for (const auto& deco : GetDecorationsFor(var.result_id())) {
+ if (deco.size() != 1) {
+ continue;
+ }
+ if (deco[0] == SpvDecorationNonWritable) {
+ is_nonwritable = true;
+ }
+ if (deco[0] == SpvDecorationNonReadable) {
+ is_nonreadable = true;
+ }
+ }
+ if (is_nonwritable && is_nonreadable) {
+ Fail() << "storage image variable is both NonWritable and NonReadable"
+ << var.PrettyPrint();
+ }
+ if (!is_nonwritable && !is_nonreadable) {
+ Fail()
+ << "storage image variable is neither NonWritable nor NonReadable"
+ << var.PrettyPrint();
+ }
+ // Let's make it one of the storage textures.
+ if (is_nonwritable) {
+ usage.AddStorageReadTexture();
+ } else {
+ usage.AddStorageWriteTexture();
+ }
+ } else {
+ usage.AddSampledTexture();
+ }
+ }
+ if (!usage.IsComplete()) {
+ Fail()
+ << "internal error: should have inferred a complete handle type. got "
+ << usage.to_str();
+ return nullptr;
+ }
}
+
+ // Construct the Tint handle type.
ast::type::Type* ast_store_type = nullptr;
if (usage.IsSampler()) {
ast_store_type = ast_module_.create<ast::type::SamplerType>(
usage.IsComparisonSampler() ? ast::type::SamplerKind::kComparisonSampler
: ast::type::SamplerKind::kSampler);
} else if (usage.IsTexture()) {
- const auto* ptr_type = def_use_mgr_->GetDef(var.type_id());
- if (!ptr_type) {
- Fail() << "Invalid type for variable " << var.PrettyPrint();
- return nullptr;
- }
- const auto* raw_image_type =
- def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
- if (!raw_image_type) {
- Fail() << "Invalid pointer type for variable " << var.PrettyPrint();
- return nullptr;
- }
- switch (raw_image_type->opcode()) {
- case SpvOpTypeImage: // The expected case.
- break;
- case SpvOpTypeArray:
- case SpvOpTypeRuntimeArray:
- Fail() << "arrays of textures are not supported in WGSL; can't "
- "translate variable "
- << var.PrettyPrint();
- return nullptr;
- default:
- Fail() << "invalid type for image variable " << var.PrettyPrint();
- return nullptr;
- }
const spvtools::opt::analysis::Image* image_type =
- type_mgr_->GetType(raw_image_type->result_id())->AsImage();
+ type_mgr_->GetType(raw_handle_type->result_id())->AsImage();
if (!image_type) {
Fail() << "internal error: Couldn't look up image type"
- << raw_image_type->PrettyPrint();
+ << raw_handle_type->PrettyPrint();
return nullptr;
}
@@ -1676,7 +1741,7 @@
(image_type->format() == SpvImageFormatUnknown)) {
// Make a sampled texture type.
auto* ast_sampled_component_type =
- ConvertType(raw_image_type->GetSingleWordInOperand(0));
+ ConvertType(raw_handle_type->GetSingleWordInOperand(0));
// Vulkan ignores the depth parameter on OpImage, so pay attention to the
// usage as well. That is, it's valid for a Vulkan shader to use an
@@ -1693,31 +1758,9 @@
dim, ast_sampled_component_type);
}
} else {
- // Make a storage texture.
- bool is_nonwritable = false;
- bool is_nonreadable = false;
- for (const auto& deco : GetDecorationsFor(var.result_id())) {
- if (deco.size() != 1) {
- continue;
- }
- if (deco[0] == SpvDecorationNonWritable) {
- is_nonwritable = true;
- }
- if (deco[0] == SpvDecorationNonReadable) {
- is_nonreadable = true;
- }
- }
- if (is_nonwritable && is_nonreadable) {
- Fail() << "storage image variable is both NonWritable and NonReadable"
- << var.PrettyPrint();
- }
- if (!is_nonwritable && !is_nonreadable) {
- Fail()
- << "storage image variable is neither NonWritable nor NonReadable"
- << var.PrettyPrint();
- }
- const auto access = is_nonwritable ? ast::AccessControl::kReadOnly
- : ast::AccessControl::kWriteOnly;
+ const auto access = usage.IsStorageReadTexture()
+ ? ast::AccessControl::kReadOnly
+ : ast::AccessControl::kWriteOnly;
const auto format = enum_converter_.ToImageFormat(image_type->format());
if (format == ast::type::ImageFormat::kNone) {
return nullptr;
@@ -1731,6 +1774,7 @@
<< var.PrettyPrint();
return nullptr;
}
+
// Form the pointer type.
return ast_module_.create<ast::type::PointerType>(
ast_store_type, ast::StorageClass::kUniformConstant);
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index bfafb4b..e25b138 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -44,6 +44,23 @@
#include "src/reader/spirv/usage.h"
#include "src/source.h"
+/// This is the implementation of the SPIR-V parser for Tint.
+
+/// Notes on terminology:
+///
+/// A WGSL "handle" is an opaque object used for accessing a resource via
+/// special builtins. In SPIR-V, a handle is stored a variable in the
+/// UniformConstant storage class. The handles supported by SPIR-V are:
+/// - images, both sampled texture and storage image
+/// - samplers
+/// - combined image+sampler
+/// - acceleration structures for raytracing.
+///
+/// WGSL only supports samplers and images, but calls images "textures".
+/// When emitting errors, we aim to use terminology most likely to be
+/// familiar to Vulkan SPIR-V developers. We will tend to use "image"
+/// and "sampler" instead of "handle".
+
namespace tint {
namespace reader {
namespace spirv {
diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc
index 06109be..79e1401 100644
--- a/src/reader/spirv/parser_impl_handle_test.cc
+++ b/src/reader/spirv/parser_impl_handle_test.cc
@@ -1061,6 +1061,117 @@
// Test emission of handle variables.
+// Test emission of variables where we don't have enough clues from their
+// use in image access instructions in executable code. For these we have
+// to infer usage from the SPIR-V sampler or image type.
+struct DeclUnderspecifiedHandleCase {
+ std::string decorations; // SPIR-V decorations
+ std::string inst; // SPIR-V variable declarations
+ std::string var_decl; // WGSL variable declaration
+};
+inline std::ostream& operator<<(std::ostream& out,
+ const DeclUnderspecifiedHandleCase& c) {
+ out << "DeclUnderspecifiedHandleCase(" << c.inst << "\n" << c.var_decl << ")";
+ return out;
+}
+
+using SpvParserTest_DeclUnderspecifiedHandle =
+ SpvParserTestBase<::testing::TestWithParam<DeclUnderspecifiedHandleCase>>;
+
+TEST_P(SpvParserTest_DeclUnderspecifiedHandle, Variable) {
+ const auto assembly = Preamble() + R"(
+ OpDecorate %10 DescriptorSet 0
+ OpDecorate %10 Binding 0
+)" + GetParam().decorations +
+ CommonTypes() + GetParam().inst +
+ R"(
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+ EXPECT_TRUE(p->error().empty()) << p->error();
+ const auto module = p->module().to_str();
+ EXPECT_THAT(module, HasSubstr(GetParam().var_decl)) << module;
+}
+
+INSTANTIATE_TEST_SUITE_P(Samplers,
+ SpvParserTest_DeclUnderspecifiedHandle,
+ ::testing::Values(
+
+ DeclUnderspecifiedHandleCase{"", R"(
+ %ptr = OpTypePointer UniformConstant %sampler
+ %10 = OpVariable %ptr UniformConstant
+)",
+ R"(
+ DecoratedVariable{
+ Decorations{
+ SetDecoration{0}
+ BindingDecoration{0}
+ }
+ x_10
+ uniform_constant
+ __sampler_sampler
+ })"}));
+
+INSTANTIATE_TEST_SUITE_P(Images,
+ SpvParserTest_DeclUnderspecifiedHandle,
+ ::testing::Values(
+
+ DeclUnderspecifiedHandleCase{"", R"(
+ %10 = OpVariable %ptr_f_texture_1d UniformConstant
+)",
+ R"(
+ DecoratedVariable{
+ Decorations{
+ SetDecoration{0}
+ BindingDecoration{0}
+ }
+ x_10
+ uniform_constant
+ __sampled_texture_1d__f32
+ })"},
+ DeclUnderspecifiedHandleCase{R"(
+ OpDecorate %10 NonWritable
+)",
+ R"(
+ %10 = OpVariable %ptr_f_storage_1d UniformConstant
+)",
+ R"(
+ DecoratedVariable{
+ Decorations{
+ SetDecoration{0}
+ BindingDecoration{0}
+ }
+ x_10
+ uniform_constant
+ __storage_texture_read_only_1d_rg32float
+ })"},
+ DeclUnderspecifiedHandleCase{R"(
+ OpDecorate %10 NonReadable
+)",
+ R"(
+ %10 = OpVariable %ptr_f_storage_1d UniformConstant
+)",
+ R"(
+ DecoratedVariable{
+ Decorations{
+ SetDecoration{0}
+ BindingDecoration{0}
+ }
+ x_10
+ uniform_constant
+ __storage_texture_write_only_1d_rg32float
+ })"}
+
+ ));
+
+// Test emission of variables when we have sampled image accesses in
+// executable code.
+
struct DeclSampledImageCase {
std::string inst; // The provoking image access instruction.
std::string var_decl; // WGSL variable declaration
@@ -1068,7 +1179,7 @@
};
inline std::ostream& operator<<(std::ostream& out,
const DeclSampledImageCase& c) {
- out << "UsageSampledImageCase(" << c.inst << "\n"
+ out << "DeclSampledImageCase(" << c.inst << "\n"
<< c.var_decl << "\n"
<< c.texture_builtin << ")";
return out;