spirv-reader: func decl with handle or ptr-to-handle
Support function declarations where formal parameters
are textures, samplers, or pointers to them.
Still need to update call sites.
Bug: tint:1039
Change-Id: I5bb3ca73190b2e27c28205e78aa433108efec252
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/109540
Commit-Queue: David Neto <dneto@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index 0aafdf9..43247fc 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -1514,7 +1514,24 @@
ParameterList ast_params;
function_.ForEachParam([this, &ast_params](const spvtools::opt::Instruction* param) {
- auto* type = parser_impl_.ConvertType(param->type_id());
+ const Type* type = nullptr;
+ auto* spirv_type = type_mgr_->GetType(param->type_id());
+ TINT_ASSERT(Reader, spirv_type);
+ if (spirv_type->AsImage() || spirv_type->AsSampler() ||
+ (spirv_type->AsPointer() &&
+ (static_cast<spv::StorageClass>(spirv_type->AsPointer()->storage_class()) ==
+ spv::StorageClass::UniformConstant))) {
+ // When we see image, sampler, pointer-to-image, or pointer-to-sampler, use the
+ // handle type deduced according to usage. Handle types are automatically generated as
+ // pointer-to-handle. Extract the handle type itself.
+ const auto* ptr_type = parser_impl_.GetTypeForHandleMemObjDecl(*param);
+ TINT_ASSERT(Reader, ptr_type);
+ // In WGSL, pass handles instead of pointers to them.
+ type = ptr_type->type;
+ } else {
+ type = parser_impl_.ConvertType(param->type_id());
+ }
+
if (type != nullptr) {
auto* ast_param = parser_impl_.MakeParameter(param->result_id(), type, AttributeList{});
// Parameters are treated as const declarations.
@@ -5407,7 +5424,7 @@
}
const Texture* FunctionEmitter::GetImageType(const spvtools::opt::Instruction& image) {
- const Pointer* ptr_type = parser_impl_.GetTypeForHandleVar(image);
+ const Pointer* ptr_type = parser_impl_.GetTypeForHandleMemObjDecl(image);
if (!parser_impl_.success()) {
Fail();
return {};
@@ -5471,7 +5488,7 @@
}
// Find the texture type.
- const Pointer* texture_ptr_type = parser_impl_.GetTypeForHandleVar(*image);
+ const Pointer* texture_ptr_type = parser_impl_.GetTypeForHandleMemObjDecl(*image);
if (!texture_ptr_type) {
return Fail();
}
@@ -5736,7 +5753,8 @@
// If necessary, convert the result to the signedness of the instruction
// result type. Compare the SPIR-V image's sampled component type with the
// component of the result type of the SPIR-V instruction.
- auto* spirv_image_type = parser_impl_.GetSpirvTypeForHandleMemoryObjectDeclaration(*image);
+ auto* spirv_image_type =
+ parser_impl_.GetSpirvTypeForHandleOrHandleMemoryObjectDeclaration(*image);
if (!spirv_image_type || (opcode(spirv_image_type) != spv::Op::OpTypeImage)) {
return Fail() << "invalid image type for image memory object declaration "
<< image->PrettyPrint();
diff --git a/src/tint/reader/spirv/function_decl_test.cc b/src/tint/reader/spirv/function_decl_test.cc
index 8af9da2..9413d53 100644
--- a/src/tint/reader/spirv/function_decl_test.cc
+++ b/src/tint/reader/spirv/function_decl_test.cc
@@ -52,6 +52,19 @@
)";
}
+std::string CommonHandleTypes() {
+ return CommonTypes() + R"(
+ %v2float = OpTypeVector %float 2
+ %v4float = OpTypeVector %float 4
+ %v2_0 = OpConstantNull %v2float
+ %sampler = OpTypeSampler
+ %tex2d_f32 = OpTypeImage %float 2D 0 0 0 1 Unknown
+ %sampled_image_2d_f32 = OpTypeSampledImage %tex2d_f32
+ %ptr_sampler = OpTypePointer UniformConstant %sampler
+ %ptr_tex2d_f32 = OpTypePointer UniformConstant %tex2d_f32
+ )";
+}
+
std::string MainBody() {
return R"(
%100 = OpFunction %void None %voidfn
@@ -147,5 +160,70 @@
EXPECT_THAT(got, HasSubstr(expect));
}
+// ;%s = OpVariable %ptr_sampler UniformConstant
+// ;%t = OpVariable %ptr_tex2d_f32 UniformConstant
+
+TEST_F(SpvParserTest, Emit_FunctionDecl_ParamPtrTexture_ParamPtrSampler) {
+ auto p = parser(test::Assemble(Preamble() + CommonHandleTypes() + R"(
+
+ ; This is how Glslang generates functions that take texture and sampler arguments.
+ ; It passes them by pointer.
+ %fn_ty = OpTypeFunction %void %ptr_tex2d_f32 %ptr_sampler
+
+ %200 = OpFunction %void None %fn_ty
+ %14 = OpFunctionParameter %ptr_tex2d_f32
+ %15 = OpFunctionParameter %ptr_sampler
+ %mixed_entry = OpLabel
+ ; access the texture, to give the handles usages.
+ %im = OpLoad %tex2d_f32 %14
+ %sam = OpLoad %sampler %15
+ %imsam = OpSampledImage %sampled_image_2d_f32 %im %sam
+ %20 = OpImageSampleImplicitLod %v4float %imsam %v2_0
+ OpReturn
+ OpFunctionEnd
+ )" + MainBody()));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
+ auto fe = p->function_emitter(200);
+ EXPECT_TRUE(fe.Emit());
+
+ auto got = test::ToString(p->program());
+ std::string expect = R"(fn x_200(x_14 : texture_2d<f32>, x_15 : sampler) {
+ let x_20 : vec4<f32> = textureSample(x_14, x_15, vec2<f32>());
+ return;
+}
+)";
+ EXPECT_EQ(got, expect);
+}
+
+TEST_F(SpvParserTest, Emit_FunctionDecl_ParamTexture_ParamSampler) {
+ auto assembly = Preamble() + CommonHandleTypes() + R"(
+
+ ; It is valid in SPIR-V to pass textures and samplers by value.
+ %fn_ty = OpTypeFunction %void %tex2d_f32 %sampler
+
+ %200 = OpFunction %void None %fn_ty
+ %14 = OpFunctionParameter %tex2d_f32
+ %15 = OpFunctionParameter %sampler
+ %mixed_entry = OpLabel
+ ; access the texture, to give the handles usages.
+ %imsam = OpSampledImage %sampled_image_2d_f32 %14 %15
+ %20 = OpImageSampleImplicitLod %v4float %imsam %v2_0
+ OpReturn
+ OpFunctionEnd
+ )" + MainBody();
+ auto p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error() << assembly;
+ auto fe = p->function_emitter(200);
+ EXPECT_TRUE(fe.Emit());
+
+ auto got = test::ToString(p->program());
+ std::string expect = R"(fn x_200(x_14 : texture_2d<f32>, x_15 : sampler) {
+ let x_20 : vec4<f32> = textureSample(x_14, x_15, vec2<f32>());
+ return;
+}
+)";
+ EXPECT_EQ(got, expect);
+}
+
} // namespace
} // namespace tint::reader::spirv
diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc
index f055f4c..aa213d1 100644
--- a/src/tint/reader/spirv/parser_impl.cc
+++ b/src/tint/reader/spirv/parser_impl.cc
@@ -1538,7 +1538,7 @@
const Type* ast_type = nullptr;
if (spirv_storage_class == spv::StorageClass::UniformConstant) {
// These are opaque handles: samplers or textures
- ast_type = GetTypeForHandleVar(var);
+ ast_type = GetTypeForHandleMemObjDecl(var);
if (!ast_type) {
return false;
}
@@ -2355,8 +2355,8 @@
}
}
-const spvtools::opt::Instruction* ParserImpl::GetSpirvTypeForHandleMemoryObjectDeclaration(
- const spvtools::opt::Instruction& var) {
+const spvtools::opt::Instruction* ParserImpl::GetSpirvTypeForHandleOrHandleMemoryObjectDeclaration(
+ const spvtools::opt::Instruction& mem_obj_decl) {
if (!success()) {
return nullptr;
}
@@ -2371,14 +2371,29 @@
// 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 || (opcode(ptr_type) != spv::Op::OpTypePointer)) {
- Fail() << "Invalid type for variable or function parameter " << var.PrettyPrint();
+ const auto* type = def_use_mgr_->GetDef(mem_obj_decl.type_id());
+ if (!type) {
+ Fail() << "Invalid type for variable or function parameter " << mem_obj_decl.PrettyPrint();
return nullptr;
}
- const auto* raw_handle_type = def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
+ switch (opcode(type)) {
+ case spv::Op::OpTypeSampler:
+ case spv::Op::OpTypeImage:
+ return type;
+ case spv::Op::OpTypePointer:
+ // The remaining cases.
+ break;
+ default:
+ Fail() << "Invalid type for variable or function parameter "
+ << mem_obj_decl.PrettyPrint();
+ return nullptr;
+ }
+
+ // Look at the pointee type instead.
+ const auto* raw_handle_type = def_use_mgr_->GetDef(type->GetSingleWordInOperand(1));
if (!raw_handle_type) {
- Fail() << "Invalid pointer type for variable or function parameter " << var.PrettyPrint();
+ Fail() << "Invalid pointer type for variable or function parameter "
+ << mem_obj_decl.PrettyPrint();
return nullptr;
}
switch (opcode(raw_handle_type)) {
@@ -2390,38 +2405,41 @@
case spv::Op::OpTypeRuntimeArray:
Fail() << "arrays of textures or samplers are not supported in WGSL; can't "
"translate variable or function parameter: "
- << var.PrettyPrint();
+ << mem_obj_decl.PrettyPrint();
return nullptr;
case spv::Op::OpTypeSampledImage:
- Fail() << "WGSL does not support combined image-samplers: " << var.PrettyPrint();
+ Fail() << "WGSL does not support combined image-samplers: "
+ << mem_obj_decl.PrettyPrint();
return nullptr;
default:
Fail() << "invalid type for image or sampler variable or function "
"parameter: "
- << var.PrettyPrint();
+ << mem_obj_decl.PrettyPrint();
return nullptr;
}
return raw_handle_type;
}
-const Pointer* ParserImpl::GetTypeForHandleVar(const spvtools::opt::Instruction& var) {
- auto where = handle_type_.find(&var);
+const Pointer* ParserImpl::GetTypeForHandleMemObjDecl(
+ const spvtools::opt::Instruction& mem_obj_decl) {
+ auto where = handle_type_.find(&mem_obj_decl);
if (where != handle_type_.end()) {
return where->second;
}
const spvtools::opt::Instruction* raw_handle_type =
- GetSpirvTypeForHandleMemoryObjectDeclaration(var);
+ GetSpirvTypeForHandleOrHandleMemoryObjectDeclaration(mem_obj_decl);
if (!raw_handle_type) {
return nullptr;
}
- // The variable could be a sampler or image.
+ // The memory object declaration 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];
+ Usage usage = handle_usage_[&mem_obj_decl];
if (!usage.IsValid()) {
- Fail() << "Invalid sampler or texture usage for variable " << var.PrettyPrint() << "\n"
+ Fail() << "Invalid sampler or texture usage for variable or function parameter "
+ << mem_obj_decl.PrettyPrint() << "\n"
<< usage;
return nullptr;
}
@@ -2447,7 +2465,7 @@
// Get NonWritable and NonReadable attributes of the variable.
bool is_nonwritable = false;
bool is_nonreadable = false;
- for (const auto& deco : GetDecorationsFor(var.result_id())) {
+ for (const auto& deco : GetDecorationsFor(mem_obj_decl.result_id())) {
if (deco.size() != 1) {
continue;
}
@@ -2460,11 +2478,11 @@
}
if (is_nonwritable && is_nonreadable) {
Fail() << "storage image variable is both NonWritable and NonReadable"
- << var.PrettyPrint();
+ << mem_obj_decl.PrettyPrint();
}
if (!is_nonwritable && !is_nonreadable) {
Fail() << "storage image variable is neither NonWritable nor NonReadable"
- << var.PrettyPrint();
+ << mem_obj_decl.PrettyPrint();
}
// Let's make it one of the storage textures.
if (is_nonwritable) {
@@ -2507,8 +2525,9 @@
break;
default:
Fail() << "WGSL arrayed textures must be 2d_array or cube_array: "
- "invalid multisampled texture variable "
- << namer_.Name(var.result_id()) << ": " << var.PrettyPrint();
+ "invalid multisampled texture variable or function parameter "
+ << namer_.Name(mem_obj_decl.result_id()) << ": "
+ << mem_obj_decl.PrettyPrint();
return nullptr;
}
}
@@ -2540,8 +2559,9 @@
} else if (image_type->is_multisampled()) {
if (dim != ast::TextureDimension::k2d) {
Fail() << "WGSL multisampled textures must be 2d and non-arrayed: "
- "invalid multisampled texture variable "
- << namer_.Name(var.result_id()) << ": " << var.PrettyPrint();
+ "invalid multisampled texture variable or function parameter "
+ << namer_.Name(mem_obj_decl.result_id()) << ": "
+ << mem_obj_decl.PrettyPrint();
}
// Multisampled textures are never depth textures.
ast_store_type = ty_.MultisampledTexture(dim, ast_sampled_component_type);
@@ -2557,16 +2577,16 @@
ast_store_type = ty_.StorageTexture(dim, format, access);
}
} else {
- Fail() << "unsupported: UniformConstant variable is not a recognized "
- "sampler or texture"
- << var.PrettyPrint();
+ Fail() << "unsupported: UniformConstant variable or function parameter is not a recognized "
+ "sampler or texture "
+ << mem_obj_decl.PrettyPrint();
return nullptr;
}
// Form the pointer type.
auto* result = ty_.Pointer(ast_store_type, ast::AddressSpace::kHandle);
// Remember it for later.
- handle_type_[&var] = result;
+ handle_type_[&mem_obj_decl] = result;
return result;
}
diff --git a/src/tint/reader/spirv/parser_impl.h b/src/tint/reader/spirv/parser_impl.h
index 6e79ecc..389f113 100644
--- a/src/tint/reader/spirv/parser_impl.h
+++ b/src/tint/reader/spirv/parser_impl.h
@@ -642,20 +642,21 @@
/// Returns the SPIR-V type for the sampler or image type for the given
/// variable in UniformConstant address space, or function parameter pointing
- /// into the UniformConstant address space . Returns null and emits an
- /// error on failure.
- /// @param var the OpVariable instruction or OpFunctionParameter
+ /// into the UniformConstant address space, or image or sampler type.
+ /// Returns null and emits an error on failure.
+ /// @param mem_obj_decl the OpVariable instruction or OpFunctionParameter
/// @returns the Tint AST type for the sampler or texture, or null on error
- const spvtools::opt::Instruction* GetSpirvTypeForHandleMemoryObjectDeclaration(
- const spvtools::opt::Instruction& var);
+ const spvtools::opt::Instruction* GetSpirvTypeForHandleOrHandleMemoryObjectDeclaration(
+ const spvtools::opt::Instruction& mem_obj_decl);
/// Returns the AST type for the pointer-to-sampler or pointer-to-texture type
- /// for the given variable in UniformConstant address space. Returns null and
+ /// for the given variable in UniformConstant address space or function
+ /// parameter of type pointer-to-UniformConstant. Returns null and
/// emits an error on failure.
- /// @param var the OpVariable instruction
+ /// @param mem_obj_decl the OpVariable instruction
/// @returns the Tint AST type for the poiner-to-{sampler|texture} or null on
/// error
- const Pointer* GetTypeForHandleVar(const spvtools::opt::Instruction& var);
+ const Pointer* GetTypeForHandleMemObjDecl(const spvtools::opt::Instruction& mem_obj_decl);
/// Returns the AST variable for the SPIR-V ID of a module-scope variable,
/// or null if there isn't one.