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:
alan-baker 2019-05-07 14:52:22 -04:00 committed by GitHub
parent 2b65a71d45
commit b74d92a8c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 170 additions and 17 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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