mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-24 16:51:06 +00:00
ADCE support for SPIR-V 1.4 entry points (#2561)
Fixes #2551 * Add support for 1.4 entry point interface lists * only input and output variables are automatically live * can clean up interfaces after DCE * added tests * allow opt tests to specify a target environment
This commit is contained in:
parent
2b65a71d45
commit
b74d92a8c3
@ -24,6 +24,7 @@
|
||||
#include "source/latest_version_glsl_std_450_header.h"
|
||||
#include "source/opt/iterator.h"
|
||||
#include "source/opt/reflect.h"
|
||||
#include "source/spirv_constant.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
@ -548,7 +549,26 @@ void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() {
|
||||
}
|
||||
// Keep all entry points.
|
||||
for (auto& entry : get_module()->entry_points()) {
|
||||
AddToWorklist(&entry);
|
||||
if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
|
||||
// In SPIR-V 1.4 and later, entry points must list all global variables
|
||||
// used. DCE can still remove non-input/output variables and update the
|
||||
// interface list. Mark the entry point as live and inputs and outputs as
|
||||
// live, but defer decisions all other interfaces.
|
||||
live_insts_.Set(entry.unique_id());
|
||||
// The actual function is live always.
|
||||
AddToWorklist(
|
||||
get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(1u)));
|
||||
for (uint32_t i = 3; i < entry.NumInOperands(); ++i) {
|
||||
auto* var = get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
|
||||
auto storage_class = var->GetSingleWordInOperand(0u);
|
||||
if (storage_class == SpvStorageClassInput ||
|
||||
storage_class == SpvStorageClassOutput) {
|
||||
AddToWorklist(var);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
AddToWorklist(&entry);
|
||||
}
|
||||
}
|
||||
// Keep workgroup size.
|
||||
for (auto& anno : get_module()->annotations()) {
|
||||
@ -780,6 +800,29 @@ bool AggressiveDCEPass::ProcessGlobalValues() {
|
||||
}
|
||||
}
|
||||
|
||||
if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
|
||||
// Remove the dead interface variables from the entry point interface list.
|
||||
for (auto& entry : get_module()->entry_points()) {
|
||||
std::vector<Operand> new_operands;
|
||||
for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
|
||||
if (i < 3) {
|
||||
// Execution model, function id and name are always valid.
|
||||
new_operands.push_back(entry.GetInOperand(i));
|
||||
} else {
|
||||
auto* var =
|
||||
get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
|
||||
if (!IsDead(var)) {
|
||||
new_operands.push_back(entry.GetInOperand(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (new_operands.size() != entry.NumInOperands()) {
|
||||
entry.SetInOperands(std::move(new_operands));
|
||||
get_def_use_mgr()->UpdateDefUse(&entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
|
@ -6389,6 +6389,113 @@ OpFunctionEnd
|
||||
SinglePassRunAndCheck<AggressiveDCEPass>(before, before, true, true);
|
||||
}
|
||||
|
||||
TEST_F(AggressiveDCETest, DeadInputInterfaceV13) {
|
||||
const std::string spirv = R"(
|
||||
; CHECK: OpEntryPoint GLCompute %main "main" [[var:%\w+]]
|
||||
; CHECK: [[var]] = OpVariable
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %main "main" %dead
|
||||
OpExecutionMode %main LocalSize 1 1 1
|
||||
OpName %main "main"
|
||||
%void = OpTypeVoid
|
||||
%int = OpTypeInt 32 0
|
||||
%ptr_input_int = OpTypePointer Input %int
|
||||
%dead = OpVariable %ptr_input_int Input
|
||||
%void_fn = OpTypeFunction %void
|
||||
%main = OpFunction %void None %void_fn
|
||||
%entry = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetTargetEnv(SPV_ENV_UNIVERSAL_1_3);
|
||||
SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
|
||||
}
|
||||
|
||||
TEST_F(AggressiveDCETest, DeadInputInterfaceV14) {
|
||||
const std::string spirv = R"(
|
||||
; CHECK: OpEntryPoint GLCompute %main "main" [[var:%\w+]]
|
||||
; CHECK: [[var]] = OpVariable
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %main "main" %dead
|
||||
OpExecutionMode %main LocalSize 1 1 1
|
||||
OpName %main "main"
|
||||
%void = OpTypeVoid
|
||||
%int = OpTypeInt 32 0
|
||||
%ptr_input_int = OpTypePointer Input %int
|
||||
%dead = OpVariable %ptr_input_int Input
|
||||
%void_fn = OpTypeFunction %void
|
||||
%main = OpFunction %void None %void_fn
|
||||
%entry = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
|
||||
SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
|
||||
}
|
||||
|
||||
TEST_F(AggressiveDCETest, DeadInterfaceV14) {
|
||||
const std::string spirv = R"(
|
||||
; CHECK-NOT: OpEntryPoint GLCompute %main "main" %
|
||||
; CHECK: OpEntryPoint GLCompute %main "main"
|
||||
; CHECK-NOT: OpVariable
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %main "main" %dead
|
||||
OpExecutionMode %main LocalSize 1 1 1
|
||||
OpName %main "main"
|
||||
%void = OpTypeVoid
|
||||
%int = OpTypeInt 32 0
|
||||
%ptr_private_int = OpTypePointer Private %int
|
||||
%dead = OpVariable %ptr_private_int Private
|
||||
%void_fn = OpTypeFunction %void
|
||||
%main = OpFunction %void None %void_fn
|
||||
%entry = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
|
||||
SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
|
||||
}
|
||||
|
||||
TEST_F(AggressiveDCETest, DeadInterfacesV14) {
|
||||
const std::string spirv = R"(
|
||||
; CHECK: OpEntryPoint GLCompute %main "main" %live1 %live2
|
||||
; CHECK-NOT: %dead
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %main "main" %live1 %dead1 %dead2 %live2
|
||||
OpExecutionMode %main LocalSize 1 1 1
|
||||
OpName %main "main"
|
||||
OpName %live1 "live1"
|
||||
OpName %live2 "live2"
|
||||
OpName %dead1 "dead1"
|
||||
OpName %dead2 "dead2"
|
||||
%void = OpTypeVoid
|
||||
%int = OpTypeInt 32 0
|
||||
%int0 = OpConstant %int 0
|
||||
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
|
||||
%live1 = OpVariable %ptr_ssbo_int StorageBuffer
|
||||
%live2 = OpVariable %ptr_ssbo_int StorageBuffer
|
||||
%dead1 = OpVariable %ptr_ssbo_int StorageBuffer
|
||||
%dead2 = OpVariable %ptr_ssbo_int StorageBuffer
|
||||
%void_fn = OpTypeFunction %void
|
||||
%main = OpFunction %void None %void_fn
|
||||
%entry = OpLabel
|
||||
OpStore %live1 %int0
|
||||
OpStore %live2 %int0
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
|
||||
SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
|
||||
}
|
||||
|
||||
// TODO(greg-lunarg): Add tests to verify handling of these cases:
|
||||
//
|
||||
// Check that logical addressing required
|
||||
|
@ -49,18 +49,18 @@ class PassTest : public TestT {
|
||||
[](spv_message_level_t, const char*, const spv_position_t&,
|
||||
const char* message) { std::cerr << message << std::endl; }),
|
||||
context_(nullptr),
|
||||
tools_(SPV_ENV_UNIVERSAL_1_3),
|
||||
manager_(new PassManager()),
|
||||
assemble_options_(SpirvTools::kDefaultAssembleOption),
|
||||
disassemble_options_(SpirvTools::kDefaultDisassembleOption) {}
|
||||
disassemble_options_(SpirvTools::kDefaultDisassembleOption),
|
||||
env_(SPV_ENV_UNIVERSAL_1_3) {}
|
||||
|
||||
// Runs the given |pass| on the binary assembled from the |original|.
|
||||
// Returns a tuple of the optimized binary and the boolean value returned
|
||||
// from pass Process() function.
|
||||
std::tuple<std::vector<uint32_t>, Pass::Status> OptimizeToBinary(
|
||||
Pass* pass, const std::string& original, bool skip_nop) {
|
||||
context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_3, consumer_, original,
|
||||
assemble_options_));
|
||||
context_ =
|
||||
std::move(BuildModule(env_, consumer_, original, assemble_options_));
|
||||
EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n"
|
||||
<< original << std::endl;
|
||||
if (!context()) {
|
||||
@ -97,8 +97,7 @@ class PassTest : public TestT {
|
||||
std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>(
|
||||
assembly, skip_nop, std::forward<Args>(args)...);
|
||||
if (do_validation) {
|
||||
spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3;
|
||||
spv_context spvContext = spvContextCreate(target_env);
|
||||
spv_context spvContext = spvContextCreate(env_);
|
||||
spv_diagnostic diagnostic = nullptr;
|
||||
spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()};
|
||||
spv_result_t error = spvValidateWithOptions(
|
||||
@ -109,8 +108,9 @@ class PassTest : public TestT {
|
||||
spvContextDestroy(spvContext);
|
||||
}
|
||||
std::string optimized_asm;
|
||||
SpirvTools tools(env_);
|
||||
EXPECT_TRUE(
|
||||
tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
|
||||
tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
|
||||
<< "Disassembling failed for shader:\n"
|
||||
<< assembly << std::endl;
|
||||
return std::make_tuple(optimized_asm, status);
|
||||
@ -134,8 +134,7 @@ class PassTest : public TestT {
|
||||
EXPECT_EQ(original == expected,
|
||||
status == Pass::Status::SuccessWithoutChange);
|
||||
if (do_validation) {
|
||||
spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3;
|
||||
spv_context spvContext = spvContextCreate(target_env);
|
||||
spv_context spvContext = spvContextCreate(env_);
|
||||
spv_diagnostic diagnostic = nullptr;
|
||||
spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()};
|
||||
spv_result_t error = spvValidateWithOptions(
|
||||
@ -146,8 +145,9 @@ class PassTest : public TestT {
|
||||
spvContextDestroy(spvContext);
|
||||
}
|
||||
std::string optimized_asm;
|
||||
SpirvTools tools(env_);
|
||||
EXPECT_TRUE(
|
||||
tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
|
||||
tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
|
||||
<< "Disassembling failed for shader:\n"
|
||||
<< original << std::endl;
|
||||
EXPECT_EQ(expected, optimized_asm);
|
||||
@ -202,8 +202,8 @@ class PassTest : public TestT {
|
||||
void RunAndCheck(const std::string& original, const std::string& expected) {
|
||||
assert(manager_->NumPasses());
|
||||
|
||||
context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, original,
|
||||
assemble_options_));
|
||||
context_ =
|
||||
std::move(BuildModule(env_, nullptr, original, assemble_options_));
|
||||
ASSERT_NE(nullptr, context());
|
||||
|
||||
manager_->Run(context());
|
||||
@ -212,7 +212,8 @@ class PassTest : public TestT {
|
||||
context()->module()->ToBinary(&binary, /* skip_nop = */ false);
|
||||
|
||||
std::string optimized;
|
||||
EXPECT_TRUE(tools_.Disassemble(binary, &optimized, disassemble_options_));
|
||||
SpirvTools tools(env_);
|
||||
EXPECT_TRUE(tools.Disassemble(binary, &optimized, disassemble_options_));
|
||||
EXPECT_EQ(expected, optimized);
|
||||
}
|
||||
|
||||
@ -233,14 +234,16 @@ class PassTest : public TestT {
|
||||
|
||||
spv_validator_options ValidatorOptions() { return &validator_options_; }
|
||||
|
||||
void SetTargetEnv(spv_target_env env) { env_ = env; }
|
||||
|
||||
private:
|
||||
MessageConsumer consumer_; // Message consumer.
|
||||
std::unique_ptr<IRContext> context_; // IR context
|
||||
SpirvTools tools_; // An instance for calling SPIRV-Tools functionalities.
|
||||
MessageConsumer consumer_; // Message consumer.
|
||||
std::unique_ptr<IRContext> context_; // IR context
|
||||
std::unique_ptr<PassManager> manager_; // The pass manager.
|
||||
uint32_t assemble_options_;
|
||||
uint32_t disassemble_options_;
|
||||
spv_validator_options_t validator_options_;
|
||||
spv_target_env env_;
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
|
Loading…
Reference in New Issue
Block a user