spirv-reader: Register usage for handle vars and func parameters

Bug: tint:109
Change-Id: I03b684dc75bf20b95e0bd38a9c5e7b78836ec7db
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/33141
Commit-Queue: David Neto <dneto@google.com>
Auto-Submit: David Neto <dneto@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
This commit is contained in:
David Neto 2020-11-18 02:57:49 +00:00 committed by Commit Bot service account
parent 62c8f07015
commit 064882de2c
5 changed files with 502 additions and 6 deletions

View File

@ -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<uint32_t, std::vector<const spvtools::opt::Instruction*>>
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([&params](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

View File

@ -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<const spvtools::opt::Function*> 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<uint32_t, const spvtools::opt::Instruction*>
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<const spvtools::opt::Instruction*, Usage> handle_usage_;
};
} // namespace spirv

View File

@ -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<SampledImageCase>>;
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<RawImageCase>>;
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

View File

@ -14,6 +14,8 @@
#include "src/reader/spirv/usage.h"
#include <sstream>
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

View File

@ -16,6 +16,7 @@
#define SRC_READER_SPIRV_USAGE_H_
#include <ostream>
#include <string>
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;