diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc index 1bfe0e4b24..91bfb2132f 100644 --- a/src/reader/spirv/parser_impl.cc +++ b/src/reader/spirv/parser_impl.cc @@ -57,10 +57,15 @@ #include "src/ast/type/alias_type.h" #include "src/ast/type/array_type.h" #include "src/ast/type/bool_type.h" +#include "src/ast/type/depth_texture_type.h" #include "src/ast/type/f32_type.h" #include "src/ast/type/i32_type.h" #include "src/ast/type/matrix_type.h" +#include "src/ast/type/multisampled_texture_type.h" #include "src/ast/type/pointer_type.h" +#include "src/ast/type/sampled_texture_type.h" +#include "src/ast/type/sampler_type.h" +#include "src/ast/type/storage_texture_type.h" #include "src/ast/type/struct_type.h" #include "src/ast/type/type.h" #include "src/ast/type/u32_type.h" @@ -74,6 +79,7 @@ #include "src/ast/variable_decoration.h" #include "src/reader/spirv/enum_converter.h" #include "src/reader/spirv/function.h" +#include "src/reader/spirv/usage.h" #include "src/type_manager.h" namespace tint { @@ -438,6 +444,9 @@ bool ParserImpl::BuildInternalModule() { type_mgr_ = ir_context_->get_type_mgr(); deco_mgr_ = ir_context_->get_decoration_mgr(); + topologically_ordered_functions_ = + FunctionTraverser(*module_).TopologicallyOrderedFunctions(); + return success_; } @@ -524,6 +533,9 @@ bool ParserImpl::ParseInternalModuleExceptFunctions() { if (!RegisterEntryPoints()) { return false; } + if (!RegisterHandleUsage()) { + return false; + } if (!RegisterTypes()) { return false; } @@ -1479,8 +1491,7 @@ bool ParserImpl::EmitFunctions() { if (!success_) { return false; } - for (const auto* f : - FunctionTraverser(*module_).TopologicallyOrderedFunctions()) { + for (const auto* f : topologically_ordered_functions_) { if (!success_) { return false; } @@ -1506,11 +1517,13 @@ bool ParserImpl::EmitFunctions() { const spvtools::opt::Instruction* ParserImpl::GetMemoryObjectDeclarationForHandle(uint32_t id, bool follow_image) { - auto local_fail = [this, id, + auto saved_id = id; + auto local_fail = [this, saved_id, id, follow_image]() -> const spvtools::opt::Instruction* { const auto* inst = def_use_mgr_->GetDef(id); Fail() << "Could not find memory object declaration for the " << (follow_image ? "image" : "sampler") << " underlying id " << id + << " (from original id " << saved_id << ") " << (inst ? inst->PrettyPrint() : std::string()); return nullptr; }; @@ -1589,6 +1602,159 @@ ParserImpl::GetMemoryObjectDeclarationForHandle(uint32_t id, } } +bool ParserImpl::RegisterHandleUsage() { + if (!success_) { + return false; + } + + // Map a function ID to the list of its function parameter instructions, in + // order. + std::unordered_map> + function_params; + for (const auto* f : topologically_ordered_functions_) { + // Record the instructions defining this function's parameters. + auto& params = function_params[f->result_id()]; + f->ForEachParam([¶ms](const spvtools::opt::Instruction* param) { + params.push_back(param); + }); + } + + // Returns the memory object declaration for an image underlying the first + // operand of the given image instruction. + auto get_image = [this](const spvtools::opt::Instruction& image_inst) { + return this->GetMemoryObjectDeclarationForHandle( + image_inst.GetSingleWordInOperand(0), true); + }; + // Returns the memory object declaration for a sampler underlying the first + // operand of the given image instruction. + auto get_sampler = [this](const spvtools::opt::Instruction& image_inst) { + return this->GetMemoryObjectDeclarationForHandle( + image_inst.GetSingleWordInOperand(0), false); + }; + + // Scan the bodies of functions for image operations, recording their implied + // usage properties on the memory object declarations (i.e. variables or + // function parameters). We scan the functions in an order so that callees + // precede callers. That way the usage on a function parameter is already + // computed before we see the call to that function. So when we reach + // a function call, we can add the usage from the callee formal parameters. + for (const auto* f : topologically_ordered_functions_) { + for (const auto& bb : *f) { + for (const auto& inst : bb) { + switch (inst.opcode()) { + // Single texel reads and writes + + case SpvOpImageRead: + handle_usage_[get_image(inst)].AddStorageReadTexture(); + break; + case SpvOpImageWrite: + handle_usage_[get_image(inst)].AddStorageWriteTexture(); + break; + case SpvOpImageFetch: + handle_usage_[get_image(inst)].AddSampledTexture(); + break; + + // Sampling and gathering from a sampled image. + + case SpvOpImageSampleImplicitLod: + case SpvOpImageSampleExplicitLod: + case SpvOpImageSampleProjImplicitLod: + case SpvOpImageSampleProjExplicitLod: + case SpvOpImageGather: + handle_usage_[get_image(inst)].AddSampledTexture(); + handle_usage_[get_sampler(inst)].AddSampler(); + break; + case SpvOpImageSampleDrefImplicitLod: + case SpvOpImageSampleDrefExplicitLod: + case SpvOpImageSampleProjDrefImplicitLod: + case SpvOpImageSampleProjDrefExplicitLod: + case SpvOpImageDrefGather: + // Depth reference access implies usage as a depth texture, which + // in turn is a sampled texture. + handle_usage_[get_image(inst)].AddDepthTexture(); + handle_usage_[get_sampler(inst)].AddComparisonSampler(); + break; + + // Image queries + + case SpvOpImageQuerySizeLod: + case SpvOpImageQuerySize: + // Applies to NonReadable, and hence a write-only storage image + handle_usage_[get_image(inst)].AddStorageWriteTexture(); + break; + case SpvOpImageQueryLod: + handle_usage_[get_image(inst)].AddSampledTexture(); + handle_usage_[get_sampler(inst)].AddSampler(); + break; + case SpvOpImageQueryLevels: + // We can't tell anything more than that it's an image. + handle_usage_[get_image(inst)].AddTexture(); + break; + case SpvOpImageQuerySamples: + handle_usage_[get_image(inst)].AddMultisampledTexture(); + break; + + // Function calls + + case SpvOpFunctionCall: { + // Propagate handle usages from callee function formal parameters to + // the matching caller parameters. This is where we rely on the + // fact that callees have been processed earlier in the flow. + const auto num_in_operands = inst.NumInOperands(); + // The first operand of the call is the function ID. + // The remaining operands are the operands to the function. + if (num_in_operands < 1) { + return Fail() << "Call instruction must have at least one operand" + << inst.PrettyPrint(); + } + const auto function_id = inst.GetSingleWordInOperand(0); + const auto& formal_params = function_params[function_id]; + if (formal_params.size() != (num_in_operands - 1)) { + return Fail() << "Called function has " << formal_params.size() + << " parameters, but function call has " + << (num_in_operands - 1) << " parameters" + << inst.PrettyPrint(); + } + for (uint32_t i = 1; i < num_in_operands; ++i) { + auto where = handle_usage_.find(formal_params[i - 1]); + if (where == handle_usage_.end()) { + // We haven't recorded any handle usage on the formal parameter. + continue; + } + const Usage& formal_param_usage = where->second; + const auto operand_id = inst.GetSingleWordInOperand(i); + const auto* operand_as_sampler = + GetMemoryObjectDeclarationForHandle(operand_id, false); + const auto* operand_as_image = + GetMemoryObjectDeclarationForHandle(operand_id, true); + if (operand_as_sampler) { + handle_usage_[operand_as_sampler].Add(formal_param_usage); + } + if (operand_as_image && + (operand_as_image != operand_as_sampler)) { + handle_usage_[operand_as_image].Add(formal_param_usage); + } + } + break; + } + + default: + break; + } + } + } + } + return success_; +} + +Usage ParserImpl::GetHandleUsage(uint32_t id) const { + const auto where = handle_usage_.find(def_use_mgr_->GetDef(id)); + if (where != handle_usage_.end()) { + return where->second; + } + return Usage(); +} + } // namespace spirv } // namespace reader } // namespace tint diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h index ee6e1b2f21..48f8d20e17 100644 --- a/src/reader/spirv/parser_impl.h +++ b/src/reader/spirv/parser_impl.h @@ -41,6 +41,7 @@ #include "src/reader/spirv/enum_converter.h" #include "src/reader/spirv/fail_stream.h" #include "src/reader/spirv/namer.h" +#include "src/reader/spirv/usage.h" #include "src/source.h" namespace tint { @@ -189,7 +190,8 @@ class ParserImpl : Reader { /// Builds the internal representation of the SPIR-V module. /// Assumes the module is somewhat well-formed. Normally you /// would want to validate the SPIR-V module before attempting - /// to build this internal representation. + /// to build this internal representation. Also computes a topological + /// ordering of the functions. /// This is a no-op if the parser has already failed. /// @returns true if the parser is still successful. bool BuildInternalModule(); @@ -234,6 +236,12 @@ class ParserImpl : Reader { /// @returns true if parser is still successful. bool RegisterTypes(); + /// Register sampler and texture usage for memory object declarations. + /// This must be called after we've registered line numbers for all + /// instructions. This is a no-op if the parser has already failed. + /// @returns true if parser is still successful. + bool RegisterHandleUsage(); + /// Emit const definitions for scalar specialization constants generated /// by one of OpConstantTrue, OpConstantFalse, or OpSpecConstant. /// This is a no-op if the parser has already failed. @@ -375,8 +383,8 @@ class ParserImpl : Reader { return scalar_spec_constants_.find(id) != scalar_spec_constants_.end(); } - /// For a SPIR-V ID that defines a sampler, image, or sampled image value, - /// return the SPIR-V instruction that represents the memory object + /// For a SPIR-V ID that might define a sampler, image, or sampled image + /// value, return the SPIR-V instruction that represents the memory object /// declaration for the object. If we encounter an OpSampledImage along the /// way, follow the image operand when follow_image is true; otherwise follow /// the sampler operand. Returns nullptr if we can't trace back to a memory @@ -391,6 +399,12 @@ class ParserImpl : Reader { uint32_t id, bool follow_image); + /// Returns the handle usage for a memory object declaration. + /// @param id SPIR-V ID of a sampler or image OpVariable or + /// OpFunctionParameter + /// @returns the handle usage, or an empty usage object. + Usage GetHandleUsage(uint32_t id) const; + private: /// Converts a specific SPIR-V type to a Tint type. Integer case ast::type::Type* ConvertType(const spvtools::opt::analysis::Integer* int_ty); @@ -468,6 +482,9 @@ class ParserImpl : Reader { spvtools::opt::analysis::TypeManager* type_mgr_ = nullptr; spvtools::opt::analysis::DecorationManager* deco_mgr_ = nullptr; + // The functions ordered so that callees precede their callers. + std::vector topologically_ordered_functions_; + // Maps an instruction to its source location. If no OpLine information // is in effect for the instruction, map the instruction to its position // in the SPIR-V module, counting by instructions, where the first @@ -525,6 +542,10 @@ class ParserImpl : Reader { // GetMemoryObjectDeclarationForHandle. std::unordered_map mem_obj_decl_sampler_; + + // Maps a memory-object-declaration instruction to any sampler or texture + // usages implied by usages of the memory-object-declaration. + std::unordered_map handle_usage_; }; } // namespace spirv diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc index 95911f0794..6708c67558 100644 --- a/src/reader/spirv/parser_impl_handle_test.cc +++ b/src/reader/spirv/parser_impl_handle_test.cc @@ -52,10 +52,20 @@ std::string CommonTypes() { %uint_2 = OpConstant %uint 2 %uint_100 = OpConstant %uint 100 + %v2uint = OpTypeVector %uint 2 %v4uint = OpTypeVector %uint 4 %v4int = OpTypeVector %int 4 + %v2float = OpTypeVector %float 2 + %v3float = OpTypeVector %float 3 %v4float = OpTypeVector %float 4 + %float_null = OpConstantNull %float + %v2float_null = OpConstantNull %v2float + %v3float_null = OpConstantNull %v3float + %v4float_null = OpConstantNull %v4float + + %depth = OpConstant %float 0.2 + ; Define types for all sampler and texture types that can map to WGSL, ; modulo texel formats for storage textures. For now, we limit ; ourselves to 2-channel 32-bit texel formats. @@ -759,6 +769,293 @@ TEST_F(SpvParserTest, GetMemoryObjectDeclarationForHandle_FuncParam_Image) { EXPECT_EQ(image->result_id(), 20u); } +// Test RegisterHandleUsage, sampled image cases + +struct SampledImageCase { + std::string inst; + std::string expected_sampler_usage; + std::string expected_image_usage; +}; +inline std::ostream& operator<<(std::ostream& out, const SampledImageCase& c) { + out << "SampledImageCase(" << c.inst << ", " << c.expected_sampler_usage + << ", " << c.expected_image_usage << ")"; + return out; +} + +using SpvParserTest_RegisterHandleUsage_SampledImage = + SpvParserTestBase<::testing::TestWithParam>; + +TEST_P(SpvParserTest_RegisterHandleUsage_SampledImage, Variable) { + const auto assembly = Preamble() + CommonTypes() + R"( + %si_ty = OpTypeSampledImage %f_texture_2d + %coords = OpConstantNull %v2float + + %10 = OpVariable %ptr_sampler UniformConstant + %20 = OpVariable %ptr_f_texture_2d UniformConstant + + %main = OpFunction %void None %voidfn + %entry = OpLabel + + %sam = OpLoad %sampler %10 + %im = OpLoad %f_texture_2d %20 + %sampled_image = OpSampledImage %si_ty %im %sam +)" + GetParam().inst + R"( + + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildInternalModule()); + EXPECT_TRUE(p->RegisterHandleUsage()); + EXPECT_TRUE(p->error().empty()); + Usage su = p->GetHandleUsage(10); + Usage iu = p->GetHandleUsage(20); + + EXPECT_THAT(su.to_str(), Eq(GetParam().expected_sampler_usage)); + EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage)); +} + +TEST_P(SpvParserTest_RegisterHandleUsage_SampledImage, FunctionParam) { + const auto assembly = Preamble() + CommonTypes() + R"( + %f_ty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_2d + %si_ty = OpTypeSampledImage %f_texture_2d + %coords = OpConstantNull %v2float + %component = OpConstant %uint 1 + + %10 = OpVariable %ptr_sampler UniformConstant + %20 = OpVariable %ptr_f_texture_2d UniformConstant + + %func = OpFunction %void None %f_ty + %110 = OpFunctionParameter %ptr_sampler + %120 = OpFunctionParameter %ptr_f_texture_2d + %func_entry = OpLabel + %sam = OpLoad %sampler %110 + %im = OpLoad %f_texture_2d %120 + %sampled_image = OpSampledImage %si_ty %im %sam + +)" + GetParam().inst + R"( + + OpReturn + OpFunctionEnd + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %foo = OpFunctionCall %void %func %10 %20 + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildInternalModule()) << p->error() << assembly << std::endl; + EXPECT_TRUE(p->RegisterHandleUsage()) << p->error() << assembly << std::endl; + EXPECT_TRUE(p->error().empty()) << p->error() << assembly << std::endl; + Usage su = p->GetHandleUsage(10); + Usage iu = p->GetHandleUsage(20); + + std::cout << p->GetHandleUsage(10) << std::endl; + std::cout << p->GetHandleUsage(20) << std::endl; + std::cout << p->GetHandleUsage(110) << std::endl; + std::cout << p->GetHandleUsage(120) << std::endl; + + EXPECT_THAT(su.to_str(), Eq(GetParam().expected_sampler_usage)); + EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage)); +} + +INSTANTIATE_TEST_SUITE_P( + Samples, + SpvParserTest_RegisterHandleUsage_SampledImage, + ::testing::Values( + + // OpImageGather + SampledImageCase{"%result = OpImageGather " + "%v4float %sampled_image %coords %uint_1", + "Usage(Sampler( ))", "Usage(Texture( is_sampled ))"}, + // OpImageDrefGather + SampledImageCase{"%result = OpImageDrefGather " + "%v4float %sampled_image %coords %depth", + "Usage(Sampler( comparison ))", + "Usage(Texture( is_sampled depth ))"}, + + // Sample the texture. + + // OpImageSampleImplicitLod + SampledImageCase{"%result = OpImageSampleImplicitLod " + "%v4float %sampled_image %coords", + "Usage(Sampler( ))", "Usage(Texture( is_sampled ))"}, + // OpImageSampleExplicitLod + SampledImageCase{"%result = OpImageSampleExplicitLod " + "%v4float %sampled_image %coords Lod %float_null", + "Usage(Sampler( ))", "Usage(Texture( is_sampled ))"}, + // OpImageSampleDrefImplicitLod + SampledImageCase{"%result = OpImageSampleDrefImplicitLod " + "%v4float %sampled_image %coords %depth", + "Usage(Sampler( comparison ))", + "Usage(Texture( is_sampled depth ))"}, + // OpImageSampleDrefExplicitLod + SampledImageCase{ + "%result = OpImageSampleDrefExplicitLod " + "%v4float %sampled_image %coords %depth Lod %float_null", + "Usage(Sampler( comparison ))", + "Usage(Texture( is_sampled depth ))"}, + + // Sample the texture, with *Proj* variants, even though WGSL doesn't + // support them. + + // OpImageSampleProjImplicitLod + SampledImageCase{"%result = OpImageSampleProjImplicitLod " + "%v4float %sampled_image %coords", + "Usage(Sampler( ))", "Usage(Texture( is_sampled ))"}, + // OpImageSampleProjExplicitLod + SampledImageCase{"%result = OpImageSampleProjExplicitLod " + "%v4float %sampled_image %coords Lod %float_null", + "Usage(Sampler( ))", "Usage(Texture( is_sampled ))"}, + // OpImageSampleProjDrefImplicitLod + SampledImageCase{"%result = OpImageSampleProjDrefImplicitLod " + "%v4float %sampled_image %coords %depth", + "Usage(Sampler( comparison ))", + "Usage(Texture( is_sampled depth ))"}, + // OpImageSampleProjDrefExplicitLod + SampledImageCase{ + "%result = OpImageSampleProjDrefExplicitLod " + "%v4float %sampled_image %coords %depth Lod %float_null", + "Usage(Sampler( comparison ))", + "Usage(Texture( is_sampled depth ))"}, + + // OpImageQueryLod + SampledImageCase{ + "%result = OpImageQueryLod %v2float %sampled_image %coords", + "Usage(Sampler( ))", "Usage(Texture( is_sampled ))"} + + )); + +// Test RegisterHandleUsage, raw image cases. +// For these we test the use of an image value directly, and not combined +// with the sampler. The image still could be of sampled image type. + +struct RawImageCase { + std::string type; // Example: f_storage_1d or f_texture_1d + std::string inst; + std::string expected_image_usage; +}; +inline std::ostream& operator<<(std::ostream& out, const RawImageCase& c) { + out << "RawImageCase(" << c.type << ", " << c.inst << ", " + << c.expected_image_usage << ")"; + return out; +} + +using SpvParserTest_RegisterHandleUsage_RawImage = + SpvParserTestBase<::testing::TestWithParam>; + +TEST_P(SpvParserTest_RegisterHandleUsage_RawImage, Variable) { + const auto assembly = Preamble() + CommonTypes() + R"( + %20 = OpVariable %ptr_)" + + GetParam().type + R"( UniformConstant + + %main = OpFunction %void None %voidfn + %entry = OpLabel + + %im = OpLoad %)" + GetParam().type + + R"( %20 +)" + GetParam().inst + R"( + + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildInternalModule()); + EXPECT_TRUE(p->RegisterHandleUsage()); + EXPECT_TRUE(p->error().empty()); + + Usage iu = p->GetHandleUsage(20); + EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage)); + + Usage su = p->GetHandleUsage(20); +} + +TEST_P(SpvParserTest_RegisterHandleUsage_RawImage, FunctionParam) { + const auto assembly = Preamble() + CommonTypes() + R"( + %f_ty = OpTypeFunction %void %ptr_)" + + GetParam().type + R"( + + %20 = OpVariable %ptr_)" + + GetParam().type + R"( UniformConstant + + %func = OpFunction %void None %f_ty + %i_param = OpFunctionParameter %ptr_)" + + GetParam().type + R"( + %func_entry = OpLabel + %im = OpLoad %)" + GetParam().type + + R"( %i_param + +)" + GetParam().inst + R"( + + OpReturn + OpFunctionEnd + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %foo = OpFunctionCall %void %func %20 + OpReturn + OpFunctionEnd + )"; + auto* p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildInternalModule()); + EXPECT_TRUE(p->RegisterHandleUsage()); + EXPECT_TRUE(p->error().empty()); + Usage iu = p->GetHandleUsage(20); + + EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage)); +} + +INSTANTIATE_TEST_SUITE_P( + Samples, + SpvParserTest_RegisterHandleUsage_RawImage, + ::testing::Values( + + // OpImageRead + RawImageCase{"f_storage_1d", + "%result = OpImageRead %v4float %im %uint_1", + "Usage(Texture( read ))"}, + + // OpImageWrite + RawImageCase{"f_storage_1d", "OpImageWrite %im %uint_1 %v4float_null", + "Usage(Texture( write ))"}, + + // OpImageFetch + RawImageCase{"f_texture_1d", + "%result = OpImageFetch " + "%v4float %im %float_null", + "Usage(Texture( is_sampled ))"}, + + // Image queries + + // OpImageQuerySizeLod + // Applies to NonReadable, hence write-only storage + RawImageCase{"f_storage_2d", + "%result = OpImageQuerySizeLod " + "%v2uint %im %uint_1", + "Usage(Texture( write ))"}, + + // OpImageQuerySize + // Applies to NonReadable, hence write-only storage + RawImageCase{"f_storage_2d", + "%result = OpImageQuerySize " + "%v2uint %im", + "Usage(Texture( write ))"}, + + // OpImageQueryLevels + RawImageCase{"f_texture_2d", + "%result = OpImageQueryLevels " + "%uint %im", + "Usage(Texture( ))"}, + + // OpImageQuerySamples + RawImageCase{"f_texture_2d_ms", + "%result = OpImageQuerySamples " + "%uint %im", + "Usage(Texture( is_sampled ms ))"} + + )); + } // namespace } // namespace spirv } // namespace reader diff --git a/src/reader/spirv/usage.cc b/src/reader/spirv/usage.cc index 23635e16e8..902d9238af 100644 --- a/src/reader/spirv/usage.cc +++ b/src/reader/spirv/usage.cc @@ -14,6 +14,8 @@ #include "src/reader/spirv/usage.h" +#include + namespace tint { namespace reader { namespace spirv { @@ -181,6 +183,12 @@ void Usage::AddDepthTexture() { is_depth_ = true; } +std::string Usage::to_str() const { + std::ostringstream ss; + ss << *this; + return ss.str(); +} + } // namespace spirv } // namespace reader } // namespace tint diff --git a/src/reader/spirv/usage.h b/src/reader/spirv/usage.h index 370b6a6631..2336f88c1f 100644 --- a/src/reader/spirv/usage.h +++ b/src/reader/spirv/usage.h @@ -16,6 +16,7 @@ #define SRC_READER_SPIRV_USAGE_H_ #include +#include namespace tint { namespace reader { @@ -102,6 +103,9 @@ class Usage { /// Records usage as a depth texture. void AddDepthTexture(); + /// Returns this usage object as a string. + std::string to_str() const; + private: // Sampler properties. bool is_sampler_ = false;