Add option to ADCE to remove output variables from interface. (#4994)

This can cause interface incompatibility and should only be done
if ADCE has been applied to the following shader in the pipeline.
For this reason this capability is not available through the CLI
but rather only non-default through the API. This functionality is
intended as part of a larger cross-shader dead code elimination
sequence.
This commit is contained in:
Greg Fischer 2022-11-23 10:48:58 -07:00 committed by GitHub
parent 46ca66e699
commit 81ec2aaa0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 102 additions and 13 deletions

View File

@ -521,8 +521,12 @@ Optimizer::PassToken CreateDeadInsertElimPass();
// interface are considered live and are not eliminated. This mode is needed // interface are considered live and are not eliminated. This mode is needed
// by GPU-Assisted validation instrumentation, where a change in the interface // by GPU-Assisted validation instrumentation, where a change in the interface
// is not allowed. // is not allowed.
Optimizer::PassToken CreateAggressiveDCEPass(); //
Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface); // If |remove_outputs| is true, allow outputs to be removed from the interface.
// This is only safe if the caller knows that there is no corresponding input
// variable in the following shader. It is false by default.
Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface = false,
bool remove_outputs = false);
// Creates a remove-unused-interface-variables pass. // Creates a remove-unused-interface-variables pass.
// Removes variables referenced on the |OpEntryPoint| instruction that are not // Removes variables referenced on the |OpEntryPoint| instruction that are not

View File

@ -579,8 +579,10 @@ void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() {
auto* var = get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i)); auto* var = get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
auto storage_class = var->GetSingleWordInOperand(0u); auto storage_class = var->GetSingleWordInOperand(0u);
// Vulkan support outputs without an associated input, but not inputs // Vulkan support outputs without an associated input, but not inputs
// without an associated output. // without an associated output. Don't remove outputs unless explicitly
if (spv::StorageClass(storage_class) == spv::StorageClass::Output) { // allowed.
if (!remove_outputs_ &&
spv::StorageClass(storage_class) == spv::StorageClass::Output) {
AddToWorklist(var); AddToWorklist(var);
} }
} }

View File

@ -44,8 +44,10 @@ class AggressiveDCEPass : public MemPass {
using GetBlocksFunction = using GetBlocksFunction =
std::function<std::vector<BasicBlock*>*(const BasicBlock*)>; std::function<std::vector<BasicBlock*>*(const BasicBlock*)>;
AggressiveDCEPass(bool preserve_interface = false) AggressiveDCEPass(bool preserve_interface = false,
: preserve_interface_(preserve_interface) {} bool remove_outputs = false)
: preserve_interface_(preserve_interface),
remove_outputs_(remove_outputs) {}
const char* name() const override { return "eliminate-dead-code-aggressive"; } const char* name() const override { return "eliminate-dead-code-aggressive"; }
Status Process() override; Status Process() override;
@ -63,6 +65,11 @@ class AggressiveDCEPass : public MemPass {
// is not allowed. // is not allowed.
bool preserve_interface_; bool preserve_interface_;
// Output variables can be removed from the interface if this is true.
// This is safe if the caller knows that the corresponding input variable
// in the following shader has been removed. It is false by default.
bool remove_outputs_;
// Return true if |varId| is a variable of |storageClass|. |varId| must either // Return true if |varId| is a variable of |storageClass|. |varId| must either
// be 0 or the result of an instruction. // be 0 or the result of an instruction.
bool IsVarOfStorage(uint32_t varId, spv::StorageClass storageClass); bool IsVarOfStorage(uint32_t varId, spv::StorageClass storageClass);

View File

@ -785,14 +785,10 @@ Optimizer::PassToken CreateLocalMultiStoreElimPass() {
MakeUnique<opt::SSARewritePass>()); MakeUnique<opt::SSARewritePass>());
} }
Optimizer::PassToken CreateAggressiveDCEPass() { Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface,
bool remove_outputs) {
return MakeUnique<Optimizer::PassToken::Impl>( return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::AggressiveDCEPass>(false)); MakeUnique<opt::AggressiveDCEPass>(preserve_interface, remove_outputs));
}
Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface) {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::AggressiveDCEPass>(preserve_interface));
} }
Optimizer::PassToken CreateRemoveUnusedInterfaceVariablesPass() { Optimizer::PassToken CreateRemoveUnusedInterfaceVariablesPass() {

View File

@ -7777,6 +7777,86 @@ PS_OUTPUT MainPs ( )
SinglePassRunAndMatch<AggressiveDCEPass>(text, true); SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
} }
TEST_F(AggressiveDCETest, RemoveOutputTrue) {
// Remove dead n_out output variable from module
const std::string text = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main "main" %c_out %c_in %n_out
;CHECK: OpEntryPoint Vertex %main "main" %c_out %c_in
OpSource GLSL 450
OpName %main "main"
OpName %c_out "c_out"
OpName %c_in "c_in"
OpName %n_out "n_out"
OpDecorate %c_out Location 0
OpDecorate %c_in Location 0
OpDecorate %n_out Location 1
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%c_out = OpVariable %_ptr_Output_v4float Output
%_ptr_Input_v4float = OpTypePointer Input %v4float
%c_in = OpVariable %_ptr_Input_v4float Input
%v3float = OpTypeVector %float 3
%_ptr_Output_v3float = OpTypePointer Output %v3float
%n_out = OpVariable %_ptr_Output_v3float Output
%main = OpFunction %void None %3
%5 = OpLabel
%12 = OpLoad %v4float %c_in
OpStore %c_out %12
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_VULKAN_1_3);
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<AggressiveDCEPass>(text, true, false, true);
}
TEST_F(AggressiveDCETest, RemoveOutputFalse) {
// Remove dead n_out output variable from module
const std::string text = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main "main" %c_out %c_in %n_out
;CHECK: OpEntryPoint Vertex %main "main" %c_out %c_in %n_out
OpSource GLSL 450
OpName %main "main"
OpName %c_out "c_out"
OpName %c_in "c_in"
OpName %n_out "n_out"
OpDecorate %c_out Location 0
OpDecorate %c_in Location 0
OpDecorate %n_out Location 1
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%c_out = OpVariable %_ptr_Output_v4float Output
%_ptr_Input_v4float = OpTypePointer Input %v4float
%c_in = OpVariable %_ptr_Input_v4float Input
%v3float = OpTypeVector %float 3
%_ptr_Output_v3float = OpTypePointer Output %v3float
%n_out = OpVariable %_ptr_Output_v3float Output
%main = OpFunction %void None %3
%5 = OpLabel
%12 = OpLoad %v4float %c_in
OpStore %c_out %12
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_VULKAN_1_3);
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndMatch<AggressiveDCEPass>(text, true, false, false);
}
} // namespace } // namespace
} // namespace opt } // namespace opt
} // namespace spvtools } // namespace spvtools