Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: spirv-opt: Demote r/w output variables to private #5672

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion source/opt/eliminate_dead_output_stores_pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ constexpr uint32_t kOpDecorateBuiltInLiteralInIdx = 2;
constexpr uint32_t kOpDecorateMemberBuiltInLiteralInIdx = 3;
constexpr uint32_t kOpAccessChainIdx0InIdx = 1;
constexpr uint32_t kOpConstantValueInIdx = 0;
constexpr uint32_t kVariableStorageClassInIdx = 0;
constexpr uint32_t kSpvTypePointerTypeIdInIdx = 1;
} // namespace

Pass::Status EliminateDeadOutputStoresPass::Process() {
Expand All @@ -45,6 +47,64 @@ bool EliminateDeadOutputStoresPass::IsLiveBuiltin(uint32_t bi) {
return live_builtins_->find(bi) != live_builtins_->end();
}

bool EliminateDeadOutputStoresPass::IsVariableRead(
Instruction const* const variable) {
analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();

bool is_read = false;
def_use_mgr->ForEachUser(variable, [&is_read](Instruction* user) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to iterate over every user? If not, should WhileEachUser be used instead?

if (user->opcode() == spv::Op::OpLoad) is_read = true;
});

return is_read;
}

bool EliminateDeadOutputStoresPass::DemoteToPrivate(
Instruction* const variable) {
assert(spv::StorageClass(variable->GetSingleWordInOperand(
kVariableStorageClassInIdx)) == spv::StorageClass::Output);

auto type_mgr = context()->get_type_mgr();

// Set the variable's storage class to private.
variable->SetInOperand(kVariableStorageClassInIdx,
{uint32_t(spv::StorageClass::Private)});

// Get the pointee type of the variable.
auto const res_type = get_def_use_mgr()->GetDef(variable->type_id());
auto pointee_type_id =
res_type->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);

// Find a type that matches the pointee type and uses private storage.
pointee_type_id =
type_mgr->FindPointerToType(pointee_type_id, spv::StorageClass::Private);
if (pointee_type_id == 0) {
return false;
}
context()->UpdateDefUse(get_def_use_mgr()->GetDef(pointee_type_id));

// Remove variable decorations that are not valid for private storage.
get_def_use_mgr()->ForEachUser(variable, [this](Instruction* user) {
if (user->opcode() == spv::Op::OpDecorate) {
auto const decoration = spv::Decoration(user->GetSingleWordInOperand(1));
if (decoration == spv::Decoration::Location ||
decoration == spv::Decoration::Component) {
kill_list_.push_back(user);
}
}
});

// Update the variable's result type to match the pointee type.
variable->SetResultType(pointee_type_id);
context()->UpdateDefUse(variable);

// Move the variable after the result type.
variable->RemoveFromList();
variable->InsertAfter(get_def_use_mgr()->GetDef(pointee_type_id));

return true;
}

bool EliminateDeadOutputStoresPass::AnyLocsAreLive(uint32_t start,
uint32_t count) {
auto finish = start + count;
Expand Down Expand Up @@ -194,6 +254,17 @@ Pass::Status EliminateDeadOutputStoresPass::DoDeadOutputStoreElimination() {
if (ptr_type->storage_class() != spv::StorageClass::Output) {
continue;
}

// Stores to write-only output variables are safe to eliminate; however,
// stores to read-write output variables may be required for functional
// correctness. If a read-write output variable is detected, then it will
// be demoted to a private variable and any associated stores will not be
// eliminated.
if (IsVariableRead(&var)) {
if (!DemoteToPrivate(&var)) return Status::Failure;
continue;
}

// If builtin decoration on variable, process as builtin.
auto var_id = var.result_id();
bool is_builtin = false;
Expand All @@ -217,7 +288,7 @@ Pass::Status EliminateDeadOutputStoresPass::DoDeadOutputStoreElimination() {
// locations are dead, kill store or all access chain's stores
def_use_mgr->ForEachUser(
var_id, [this, &var, is_builtin](Instruction* user) {
auto op = user->opcode();
auto const op = user->opcode();
if (op == spv::Op::OpEntryPoint || op == spv::Op::OpName ||
op == spv::Op::OpDecorate || user->IsNonSemanticInstruction())
return;
Expand Down
6 changes: 6 additions & 0 deletions source/opt/eliminate_dead_output_stores_pass.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ class EliminateDeadOutputStoresPass : public Pass {
// Return true if builtin |bi| is live.
bool IsLiveBuiltin(uint32_t bi);

// Return true if an OpLoad is using this variable.
bool IsVariableRead(Instruction const* const var);

// Demote the variable's storage class from output to private.
bool DemoteToPrivate(Instruction* const var);

std::unordered_set<uint32_t>* live_locs_;
std::unordered_set<uint32_t>* live_builtins_;

Expand Down
67 changes: 67 additions & 0 deletions test/opt/eliminate_dead_output_stores_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,73 @@ TEST_F(ElimDeadOutputStoresTest, VertMultipleLocationsF16) {
&live_builtins);
}

TEST_F(ElimDeadOutputStoresTest, DemoteOutputVariableToPrivate) {
//
// #version 450
//
// layout(location=0) out vec4 pos;
// void main()
// {
// pos = vec4(0.0);
// gl_Position = pos;
// }

const std::string text = R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main "main" %pos %_
OpSource GLSL 450
OpName %main "main"
OpName %pos "pos"
OpName %gl_PerVertex "gl_PerVertex"
OpMemberName %gl_PerVertex 0 "gl_Position"
OpMemberName %gl_PerVertex 1 "gl_PointSize"
OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
OpMemberName %gl_PerVertex 3 "gl_CullDistance"
OpName %_ ""
OpDecorate %pos Location 0
OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
OpDecorate %gl_PerVertex Block
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
;CHECK: %_ptr_Private_v4float = OpTypePointer Private %v4float
%pos = OpVariable %_ptr_Output_v4float Output
;CHECK: %pos = OpVariable %_ptr_Private_v4float Private
%float_0 = OpConstant %float 0
%11 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%_arr_float_uint_1 = OpTypeArray %float %uint_1
%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
%_ = OpVariable %_ptr_Output_gl_PerVertex Output
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%main = OpFunction %void None %3
%5 = OpLabel
OpStore %pos %11
;CHECK: OpStore %pos %11
%20 = OpLoad %v4float %pos
%21 = OpAccessChain %_ptr_Output_v4float %_ %int_0
OpStore %21 %20
OpReturn
OpFunctionEnd)";

SetTargetEnv(SPV_ENV_VULKAN_1_3);
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);

std::unordered_set<uint32_t> live_inputs{};
std::unordered_set<uint32_t> live_builtins{};
SinglePassRunAndMatch<EliminateDeadOutputStoresPass>(text, true, &live_inputs,
&live_builtins);
}

} // namespace
} // namespace opt
} // namespace spvtools