Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/KhronosGroup/SPIRV-Tools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGreg Fischer <greg@lunarg.com>2022-11-02 20:23:25 +0300
committerGitHub <noreply@github.com>2022-11-02 20:23:25 +0300
commitc8e1588cfa3ff9e3b5d600ef04f4261c4e68af90 (patch)
tree05fd13d996634c2c11280b092a88b812dab3fdb9
parenta52de681dd17f8b545ecd9ea2138f72b39bf449a (diff)
Add passes to eliminate dead output stores (#4970)
This adds two passes to accomplish this: one pass to analyze a shader to determine the input slots that are live. The second pass is run on the preceding shader to eliminate any stores to output slots that are not consumed by the following shader. These passes support vert, tesc, tese, geom, and frag shaders. These passes are currently only available through the API. These passes together with dead code elimination, and elimination of dead input and output components and variables (WIP), will allow users to do dead code elimination across shader boundaries.
-rw-r--r--Android.mk3
-rw-r--r--BUILD.gn6
-rw-r--r--include/spirv-tools/optimizer.hpp21
-rw-r--r--source/opt/CMakeLists.txt6
-rw-r--r--source/opt/analyze_live_input_pass.cpp45
-rw-r--r--source/opt/analyze_live_input_pass.h57
-rw-r--r--source/opt/eliminate_dead_output_stores_pass.cpp231
-rw-r--r--source/opt/eliminate_dead_output_stores_pass.h87
-rw-r--r--source/opt/ir_context.cpp25
-rw-r--r--source/opt/ir_context.h24
-rw-r--r--source/opt/liveness.cpp330
-rw-r--r--source/opt/liveness.h99
-rw-r--r--source/opt/module.cpp4
-rw-r--r--source/opt/optimizer.cpp15
-rw-r--r--source/opt/passes.h2
-rw-r--r--test/opt/CMakeLists.txt2
-rw-r--r--test/opt/analyze_live_input_test.cpp910
-rw-r--r--test/opt/eliminate_dead_output_stores_test.cpp952
18 files changed, 2817 insertions, 2 deletions
diff --git a/Android.mk b/Android.mk
index 80c61b080..3a28b6630 100644
--- a/Android.mk
+++ b/Android.mk
@@ -78,6 +78,7 @@ SPVTOOLS_SRC_FILES := \
SPVTOOLS_OPT_SRC_FILES := \
source/opt/aggressive_dead_code_elim_pass.cpp \
source/opt/amd_ext_to_khr.cpp \
+ source/opt/analyze_live_input_pass.cpp \
source/opt/basic_block.cpp \
source/opt/block_merge_pass.cpp \
source/opt/block_merge_util.cpp \
@@ -111,6 +112,7 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/eliminate_dead_functions_util.cpp \
source/opt/eliminate_dead_input_components_pass.cpp \
source/opt/eliminate_dead_members_pass.cpp \
+ source/opt/eliminate_dead_output_stores_pass.cpp \
source/opt/feature_manager.cpp \
source/opt/fix_func_call_arguments.cpp \
source/opt/fix_storage_class.cpp \
@@ -136,6 +138,7 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/ir_context.cpp \
source/opt/ir_loader.cpp \
source/opt/licm_pass.cpp \
+ source/opt/liveness.cpp \
source/opt/local_access_chain_convert_pass.cpp \
source/opt/local_redundancy_elimination.cpp \
source/opt/local_single_block_elim_pass.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index b7e20b343..e4c14f2a7 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -562,6 +562,8 @@ static_library("spvtools_opt") {
"source/opt/aggressive_dead_code_elim_pass.h",
"source/opt/amd_ext_to_khr.cpp",
"source/opt/amd_ext_to_khr.h",
+ "source/opt/analyze_live_input_pass.cpp",
+ "source/opt/analyze_live_input_pass.h",
"source/opt/basic_block.cpp",
"source/opt/basic_block.h",
"source/opt/block_merge_pass.cpp",
@@ -628,6 +630,8 @@ static_library("spvtools_opt") {
"source/opt/eliminate_dead_input_components_pass.h",
"source/opt/eliminate_dead_members_pass.cpp",
"source/opt/eliminate_dead_members_pass.h",
+ "source/opt/eliminate_dead_output_stores_pass.cpp",
+ "source/opt/eliminate_dead_output_stores_pass.h",
"source/opt/empty_pass.h",
"source/opt/feature_manager.cpp",
"source/opt/feature_manager.h",
@@ -681,6 +685,8 @@ static_library("spvtools_opt") {
"source/opt/iterator.h",
"source/opt/licm_pass.cpp",
"source/opt/licm_pass.h",
+ "source/opt/liveness.cpp",
+ "source/opt/liveness.h",
"source/opt/local_access_chain_convert_pass.cpp",
"source/opt/local_access_chain_convert_pass.h",
"source/opt/local_redundancy_elimination.cpp",
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 949735608..bca12520f 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -19,6 +19,7 @@
#include <ostream>
#include <string>
#include <unordered_map>
+#include <unordered_set>
#include <utility>
#include <vector>
@@ -893,6 +894,26 @@ Optimizer::PassToken CreateInterpolateFixupPass();
// types.
Optimizer::PassToken CreateEliminateDeadInputComponentsPass();
+// Analyzes shader and populates |live_locs| and |live_builtins|. Best results
+// will be obtained if shader has all dead code eliminated first. |live_locs|
+// and |live_builtins| are subsequently used when calling
+// CreateEliminateDeadOutputStoresPass on the preceding shader. Currently only
+// supports tesc, tese, geom, and frag shaders.
+Optimizer::PassToken CreateAnalyzeLiveInputPass(
+ std::unordered_set<uint32_t>* live_locs,
+ std::unordered_set<uint32_t>* live_builtins);
+
+// Removes stores to output locations not listed in |live_locs| or
+// |live_builtins|. Best results are obtained if constant propagation is
+// performed first. A subsequent call to ADCE will eliminate any dead code
+// created by the removal of the stores. A subsequent call to
+// CreateEliminateDeadOutputComponentsPass will eliminate any dead output
+// components created by the elimination of the stores. Currently only supports
+// vert, tesc, tese, and geom shaders.
+Optimizer::PassToken CreateEliminateDeadOutputStoresPass(
+ std::unordered_set<uint32_t>* live_locs,
+ std::unordered_set<uint32_t>* live_builtins);
+
// Creates a convert-to-sampled-image pass to convert images and/or
// samplers with given pairs of descriptor set and binding to sampled image.
// If a pair of an image and a sampler have the same pair of descriptor set and
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 75fe4c0fd..085c4302a 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -15,6 +15,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
fix_func_call_arguments.h
aggressive_dead_code_elim_pass.h
amd_ext_to_khr.h
+ analyze_live_input_pass.h
basic_block.h
block_merge_pass.h
block_merge_util.h
@@ -48,6 +49,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
eliminate_dead_functions_util.h
eliminate_dead_input_components_pass.h
eliminate_dead_members_pass.h
+ eliminate_dead_output_stores_pass.h
empty_pass.h
feature_manager.h
fix_storage_class.h
@@ -74,6 +76,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
ir_context.h
ir_loader.h
licm_pass.h
+ liveness.h
local_access_chain_convert_pass.h
local_redundancy_elimination.h
local_single_block_elim_pass.h
@@ -131,6 +134,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
fix_func_call_arguments.cpp
aggressive_dead_code_elim_pass.cpp
amd_ext_to_khr.cpp
+ analyze_live_input_pass.cpp
basic_block.cpp
block_merge_pass.cpp
block_merge_util.cpp
@@ -164,6 +168,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
eliminate_dead_functions_util.cpp
eliminate_dead_input_components_pass.cpp
eliminate_dead_members_pass.cpp
+ eliminate_dead_output_stores_pass.cpp
feature_manager.cpp
fix_storage_class.cpp
flatten_decoration_pass.cpp
@@ -188,6 +193,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
ir_context.cpp
ir_loader.cpp
licm_pass.cpp
+ liveness.cpp
local_access_chain_convert_pass.cpp
local_redundancy_elimination.cpp
local_single_block_elim_pass.cpp
diff --git a/source/opt/analyze_live_input_pass.cpp b/source/opt/analyze_live_input_pass.cpp
new file mode 100644
index 000000000..f54206f08
--- /dev/null
+++ b/source/opt/analyze_live_input_pass.cpp
@@ -0,0 +1,45 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+// Copyright (c) 2022 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/analyze_live_input_pass.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status AnalyzeLiveInputPass::Process() {
+ // Current functionality assumes shader capability
+ if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
+ return Status::SuccessWithoutChange;
+ Pass::Status status = DoLiveInputAnalysis();
+ return status;
+}
+
+Pass::Status AnalyzeLiveInputPass::DoLiveInputAnalysis() {
+ // Current functionality only supports frag, tesc, tese or geom shaders.
+ // Report failure for any other stage.
+ auto stage = context()->GetStage();
+ if (stage != SpvExecutionModelFragment &&
+ stage != SpvExecutionModelTessellationControl &&
+ stage != SpvExecutionModelTessellationEvaluation &&
+ stage != SpvExecutionModelGeometry)
+ return Status::Failure;
+ context()->get_liveness_mgr()->GetLiveness(live_locs_, live_builtins_);
+ return Status::SuccessWithoutChange;
+}
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/analyze_live_input_pass.h b/source/opt/analyze_live_input_pass.h
new file mode 100644
index 000000000..ab292effe
--- /dev/null
+++ b/source/opt/analyze_live_input_pass.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+// Copyright (c) 2022 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_ANALYZE_LIVE_INPUT_H_
+#define SOURCE_OPT_ANALYZE_LIVE_INPUT_H_
+
+#include <unordered_set>
+
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class AnalyzeLiveInputPass : public Pass {
+ public:
+ explicit AnalyzeLiveInputPass(std::unordered_set<uint32_t>* live_locs,
+ std::unordered_set<uint32_t>* live_builtins)
+ : live_locs_(live_locs), live_builtins_(live_builtins) {}
+
+ const char* name() const override { return "analyze-live-input"; }
+ Status Process() override;
+
+ // Return the mask of preserved Analyses.
+ IRContext::Analysis GetPreservedAnalyses() override {
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+ IRContext::kAnalysisDominatorAnalysis |
+ IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+ }
+
+ private:
+ // Do live input analysis
+ Status DoLiveInputAnalysis();
+
+ std::unordered_set<uint32_t>* live_locs_;
+ std::unordered_set<uint32_t>* live_builtins_;
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_ANALYZE_LIVE_INPUT_H_
diff --git a/source/opt/eliminate_dead_output_stores_pass.cpp b/source/opt/eliminate_dead_output_stores_pass.cpp
new file mode 100644
index 000000000..9378898fd
--- /dev/null
+++ b/source/opt/eliminate_dead_output_stores_pass.cpp
@@ -0,0 +1,231 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+// Copyright (c) 2022 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/eliminate_dead_output_stores_pass.h"
+
+#include "source/opt/instruction.h"
+#include "source/opt/ir_context.h"
+
+namespace {
+
+const uint32_t kDecorationLocationInIdx = 2;
+const uint32_t kOpDecorateMemberMemberInIdx = 1;
+const uint32_t kOpDecorateBuiltInLiteralInIdx = 2;
+const uint32_t kOpDecorateMemberBuiltInLiteralInIdx = 3;
+const uint32_t kOpAccessChainIdx0InIdx = 1;
+const uint32_t kOpConstantValueInIdx = 0;
+
+} // namespace
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status EliminateDeadOutputStoresPass::Process() {
+ // Current functionality assumes shader capability
+ if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
+ return Status::SuccessWithoutChange;
+ Pass::Status status = DoDeadOutputStoreElimination();
+ return status;
+}
+
+void EliminateDeadOutputStoresPass::InitializeElimination() {
+ kill_list_.clear();
+}
+
+bool EliminateDeadOutputStoresPass::IsLiveBuiltin(uint32_t bi) {
+ return live_builtins_->find(bi) != live_builtins_->end();
+}
+
+bool EliminateDeadOutputStoresPass::AnyLocsAreLive(uint32_t start,
+ uint32_t count) {
+ auto finish = start + count;
+ for (uint32_t u = start; u < finish; ++u) {
+ if (live_locs_->find(u) != live_locs_->end()) return true;
+ }
+ return false;
+}
+
+void EliminateDeadOutputStoresPass::KillAllStoresOfRef(Instruction* ref) {
+ analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+ if (ref->opcode() == SpvOpStore) {
+ kill_list_.push_back(ref);
+ return;
+ }
+ assert((ref->opcode() == SpvOpAccessChain ||
+ ref->opcode() == SpvOpInBoundsAccessChain) &&
+ "unexpected use of output variable");
+ def_use_mgr->ForEachUser(ref, [this](Instruction* user) {
+ if (user->opcode() == SpvOpStore) kill_list_.push_back(user);
+ });
+}
+
+void EliminateDeadOutputStoresPass::KillAllDeadStoresOfLocRef(
+ Instruction* ref, Instruction* var) {
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr();
+ analysis::LivenessManager* live_mgr = context()->get_liveness_mgr();
+ // Find variable location if present.
+ uint32_t start_loc = 0;
+ auto var_id = var->result_id();
+ bool no_loc = deco_mgr->WhileEachDecoration(
+ var_id, SpvDecorationLocation, [&start_loc](const Instruction& deco) {
+ assert(deco.opcode() == SpvOpDecorate && "unexpected decoration");
+ start_loc = deco.GetSingleWordInOperand(kDecorationLocationInIdx);
+ return false;
+ });
+ // Find patch decoration if present
+ bool is_patch = !deco_mgr->WhileEachDecoration(
+ var_id, SpvDecorationPatch, [](const Instruction& deco) {
+ if (deco.opcode() != SpvOpDecorate)
+ assert(false && "unexpected decoration");
+ return false;
+ });
+ // Compute offset and final type of reference. If no location found
+ // or any stored locations are live, return without removing stores.
+ auto ptr_type = type_mgr->GetType(var->type_id())->AsPointer();
+ assert(ptr_type && "unexpected var type");
+ auto var_type = ptr_type->pointee_type();
+ uint32_t ref_loc = start_loc;
+ auto curr_type = var_type;
+ if (ref->opcode() == SpvOpAccessChain ||
+ ref->opcode() == SpvOpInBoundsAccessChain)
+ live_mgr->AnalyzeAccessChainLoc(ref, &curr_type, &ref_loc, &no_loc,
+ is_patch, /* input */ false);
+ if (no_loc || AnyLocsAreLive(ref_loc, live_mgr->GetLocSize(curr_type)))
+ return;
+ // Kill all stores based on this reference
+ KillAllStoresOfRef(ref);
+}
+
+void EliminateDeadOutputStoresPass::KillAllDeadStoresOfBuiltinRef(
+ Instruction* ref, Instruction* var) {
+ auto deco_mgr = context()->get_decoration_mgr();
+ auto def_use_mgr = context()->get_def_use_mgr();
+ auto type_mgr = context()->get_type_mgr();
+ auto live_mgr = context()->get_liveness_mgr();
+ // Search for builtin decoration of base variable
+ uint32_t builtin = SpvBuiltInMax;
+ auto var_id = var->result_id();
+ (void)deco_mgr->WhileEachDecoration(
+ var_id, SpvDecorationBuiltIn, [&builtin](const Instruction& deco) {
+ assert(deco.opcode() == SpvOpDecorate && "unexpected decoration");
+ builtin = deco.GetSingleWordInOperand(kOpDecorateBuiltInLiteralInIdx);
+ return false;
+ });
+ // If analyzed builtin and not live, kill stores.
+ if (builtin != SpvBuiltInMax) {
+ if (live_mgr->IsAnalyzedBuiltin(builtin) && !IsLiveBuiltin(builtin))
+ KillAllStoresOfRef(ref);
+ return;
+ }
+ // Search for builtin decoration on indexed member
+ auto ref_op = ref->opcode();
+ if (ref_op != SpvOpAccessChain && ref_op != SpvOpInBoundsAccessChain) return;
+ uint32_t in_idx = kOpAccessChainIdx0InIdx;
+ analysis::Type* var_type = type_mgr->GetType(var->type_id());
+ analysis::Pointer* ptr_type = var_type->AsPointer();
+ auto curr_type = ptr_type->pointee_type();
+ auto arr_type = curr_type->AsArray();
+ if (arr_type) {
+ curr_type = arr_type->element_type();
+ ++in_idx;
+ }
+ auto str_type = curr_type->AsStruct();
+ auto str_type_id = type_mgr->GetId(str_type);
+ auto member_idx_id = ref->GetSingleWordInOperand(in_idx);
+ auto member_idx_inst = def_use_mgr->GetDef(member_idx_id);
+ assert(member_idx_inst->opcode() == SpvOpConstant &&
+ "unexpected non-constant index");
+ auto ac_idx = member_idx_inst->GetSingleWordInOperand(kOpConstantValueInIdx);
+ (void)deco_mgr->WhileEachDecoration(
+ str_type_id, SpvDecorationBuiltIn,
+ [ac_idx, &builtin](const Instruction& deco) {
+ assert(deco.opcode() == SpvOpMemberDecorate && "unexpected decoration");
+ auto deco_idx =
+ deco.GetSingleWordInOperand(kOpDecorateMemberMemberInIdx);
+ if (deco_idx == ac_idx) {
+ builtin =
+ deco.GetSingleWordInOperand(kOpDecorateMemberBuiltInLiteralInIdx);
+ return false;
+ }
+ return true;
+ });
+ assert(builtin != SpvBuiltInMax && "builtin not found");
+ // If analyzed builtin and not live, kill stores.
+ if (live_mgr->IsAnalyzedBuiltin(builtin) && !IsLiveBuiltin(builtin))
+ KillAllStoresOfRef(ref);
+}
+
+Pass::Status EliminateDeadOutputStoresPass::DoDeadOutputStoreElimination() {
+ // Current implementation only supports vert, tesc, tese, geom shaders
+ auto stage = context()->GetStage();
+ if (stage != SpvExecutionModelVertex &&
+ stage != SpvExecutionModelTessellationControl &&
+ stage != SpvExecutionModelTessellationEvaluation &&
+ stage != SpvExecutionModelGeometry)
+ return Status::Failure;
+ InitializeElimination();
+ analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr();
+ // Process all output variables
+ for (auto& var : context()->types_values()) {
+ if (var.opcode() != SpvOpVariable) {
+ continue;
+ }
+ analysis::Type* var_type = type_mgr->GetType(var.type_id());
+ analysis::Pointer* ptr_type = var_type->AsPointer();
+ if (ptr_type->storage_class() != SpvStorageClassOutput) {
+ continue;
+ }
+ // If builtin decoration on variable, process as builtin.
+ auto var_id = var.result_id();
+ bool is_builtin = false;
+ if (deco_mgr->HasDecoration(var_id, SpvDecorationBuiltIn)) {
+ is_builtin = true;
+ } else {
+ // If interface block with builtin members, process as builtin.
+ // Strip off outer array type if present.
+ auto curr_type = ptr_type->pointee_type();
+ auto arr_type = curr_type->AsArray();
+ if (arr_type) curr_type = arr_type->element_type();
+ auto str_type = curr_type->AsStruct();
+ if (str_type) {
+ auto str_type_id = type_mgr->GetId(str_type);
+ if (deco_mgr->HasDecoration(str_type_id, SpvDecorationBuiltIn))
+ is_builtin = true;
+ }
+ }
+ // For each store or access chain using var, if dead builtin or all its
+ // 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();
+ if (op == SpvOpEntryPoint || op == SpvOpName || op == SpvOpDecorate)
+ return;
+ if (is_builtin)
+ KillAllDeadStoresOfBuiltinRef(user, &var);
+ else
+ KillAllDeadStoresOfLocRef(user, &var);
+ });
+ }
+ for (auto& kinst : kill_list_) context()->KillInst(kinst);
+
+ return kill_list_.empty() ? Status::SuccessWithoutChange
+ : Status::SuccessWithChange;
+}
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/eliminate_dead_output_stores_pass.h b/source/opt/eliminate_dead_output_stores_pass.h
new file mode 100644
index 000000000..13785f349
--- /dev/null
+++ b/source/opt/eliminate_dead_output_stores_pass.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+// Copyright (c) 2022 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_ELIMINATE_DEAD_OUTPUT_STORES_H_
+#define SOURCE_OPT_ELIMINATE_DEAD_OUTPUT_STORES_H_
+
+#include <unordered_set>
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class EliminateDeadOutputStoresPass : public Pass {
+ public:
+ explicit EliminateDeadOutputStoresPass(
+ std::unordered_set<uint32_t>* live_locs,
+ std::unordered_set<uint32_t>* live_builtins)
+ : live_locs_(live_locs), live_builtins_(live_builtins) {}
+
+ const char* name() const override { return "eliminate-dead-output-stores"; }
+ Status Process() override;
+
+ // Return the mask of preserved Analyses.
+ IRContext::Analysis GetPreservedAnalyses() override {
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+ IRContext::kAnalysisDominatorAnalysis |
+ IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+ }
+
+ private:
+ // Initialize elimination
+ void InitializeElimination();
+
+ // Do dead output store analysis
+ Status DoDeadOutputStoreAnalysis();
+
+ // Do dead output store analysis
+ Status DoDeadOutputStoreElimination();
+
+ // Mark all locations live
+ void MarkAllLocsLive();
+
+ // Kill all stores resulting from |ref|.
+ void KillAllStoresOfRef(Instruction* ref);
+
+ // Kill all dead stores resulting from |user| of loc-based |var|.
+ void KillAllDeadStoresOfLocRef(Instruction* user, Instruction* var);
+
+ // Kill all dead stores resulting from |user| of builtin |var|.
+ void KillAllDeadStoresOfBuiltinRef(Instruction* user, Instruction* var);
+
+ // Return true if any of |count| locations starting at location |start| are
+ // live.
+ bool AnyLocsAreLive(uint32_t start, uint32_t count);
+
+ // Return true if builtin |bi| is live.
+ bool IsLiveBuiltin(uint32_t bi);
+
+ std::unordered_set<uint32_t>* live_locs_;
+ std::unordered_set<uint32_t>* live_builtins_;
+
+ std::vector<Instruction*> kill_list_;
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_ELIMINATE_DEAD_OUTPUT_STORES_H_
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index c9c3f1b5d..9116d4b26 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -29,6 +29,7 @@ static const int kSpvDecorateDecorationInIdx = 1;
static const int kSpvDecorateBuiltinInIdx = 2;
static const int kEntryPointInterfaceInIdx = 3;
static const int kEntryPointFunctionIdInIdx = 1;
+static const int kEntryPointExecutionModelInIdx = 0;
// Constants for OpenCL.DebugInfo.100 / NonSemantic.Shader.DebugInfo.100
// extension instructions.
@@ -152,6 +153,9 @@ void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
if (analyses_to_invalidate & kAnalysisConstants) {
constant_mgr_.reset(nullptr);
}
+ if (analyses_to_invalidate & kAnalysisLiveness) {
+ liveness_mgr_.reset(nullptr);
+ }
if (analyses_to_invalidate & kAnalysisTypes) {
type_mgr_.reset(nullptr);
}
@@ -1058,5 +1062,26 @@ bool IRContext::IsReachable(const opt::BasicBlock& bb) {
return GetDominatorAnalysis(enclosing_function)
->Dominates(enclosing_function->entry().get(), &bb);
}
+
+SpvExecutionModel IRContext::GetStage() {
+ const auto& entry_points = module()->entry_points();
+ if (entry_points.empty()) {
+ return SpvExecutionModelMax;
+ }
+
+ uint32_t stage = entry_points.begin()->GetSingleWordInOperand(
+ kEntryPointExecutionModelInIdx);
+ auto it = std::find_if(
+ entry_points.begin(), entry_points.end(), [stage](const Instruction& x) {
+ return x.GetSingleWordInOperand(kEntryPointExecutionModelInIdx) !=
+ stage;
+ });
+ if (it != entry_points.end()) {
+ EmitErrorMessage("Mixed stage shader module not supported", &(*it));
+ }
+
+ return static_cast<SpvExecutionModel>(stage);
+}
+
} // namespace opt
} // namespace spvtools
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 2f27942b4..9dee84e9b 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -35,6 +35,7 @@
#include "source/opt/dominator_analysis.h"
#include "source/opt/feature_manager.h"
#include "source/opt/fold.h"
+#include "source/opt/liveness.h"
#include "source/opt/loop_descriptor.h"
#include "source/opt/module.h"
#include "source/opt/register_pressure.h"
@@ -81,6 +82,7 @@ class IRContext {
kAnalysisConstants = 1 << 14,
kAnalysisTypes = 1 << 15,
kAnalysisDebugInfo = 1 << 16,
+ kAnalysisLiveness = 1 << 17,
kAnalysisEnd = 1 << 17
};
@@ -248,6 +250,15 @@ class IRContext {
return def_use_mgr_.get();
}
+ // Returns a pointer to a liveness manager. If the liveness manager is
+ // invalid, it is rebuilt first.
+ analysis::LivenessManager* get_liveness_mgr() {
+ if (!AreAnalysesValid(kAnalysisLiveness)) {
+ BuildLivenessManager();
+ }
+ return liveness_mgr_.get();
+ }
+
// Returns a pointer to a value number table. If the liveness analysis is
// invalid, it is rebuilt first.
ValueNumberTable* GetValueNumberTable() {
@@ -625,6 +636,10 @@ class IRContext {
// the function that contains |bb|.
bool IsReachable(const opt::BasicBlock& bb);
+ // Return the stage of the module. Will generate error if entry points don't
+ // all have the same stage.
+ SpvExecutionModel GetStage();
+
private:
// Builds the def-use manager from scratch, even if it was already valid.
void BuildDefUseManager() {
@@ -632,6 +647,12 @@ class IRContext {
valid_analyses_ = valid_analyses_ | kAnalysisDefUse;
}
+ // Builds the liveness manager from scratch, even if it was already valid.
+ void BuildLivenessManager() {
+ liveness_mgr_ = MakeUnique<analysis::LivenessManager>(this);
+ valid_analyses_ = valid_analyses_ | kAnalysisLiveness;
+ }
+
// Builds the instruction-block map for the whole module.
void BuildInstrToBlockMapping() {
instr_to_block_.clear();
@@ -852,6 +873,9 @@ class IRContext {
std::unique_ptr<StructuredCFGAnalysis> struct_cfg_analysis_;
+ // The liveness manager for |module_|.
+ std::unique_ptr<analysis::LivenessManager> liveness_mgr_;
+
// The maximum legal value for the id bound.
uint32_t max_id_bound_;
diff --git a/source/opt/liveness.cpp b/source/opt/liveness.cpp
new file mode 100644
index 000000000..08b1c6ebc
--- /dev/null
+++ b/source/opt/liveness.cpp
@@ -0,0 +1,330 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+// Copyright (c) 2022 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/liveness.h"
+
+#include "source/opt/ir_context.h"
+
+namespace {
+
+const uint32_t kDecorationLocationInIdx = 2;
+const uint32_t kOpDecorateMemberMemberInIdx = 1;
+const uint32_t kOpDecorateMemberLocationInIdx = 3;
+const uint32_t kOpDecorateBuiltInLiteralInIdx = 2;
+const uint32_t kOpDecorateMemberBuiltInLiteralInIdx = 3;
+
+} // namespace
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+
+LivenessManager::LivenessManager(IRContext* ctx) : ctx_(ctx), computed_(false) {
+ // Liveness sets computed when queried
+}
+
+void LivenessManager::InitializeAnalysis() {
+ live_locs_.clear();
+ live_builtins_.clear();
+ // Mark all builtins live for frag shader.
+ if (context()->GetStage() == SpvExecutionModelFragment) {
+ live_builtins_.insert(SpvBuiltInPointSize);
+ live_builtins_.insert(SpvBuiltInClipDistance);
+ live_builtins_.insert(SpvBuiltInCullDistance);
+ }
+}
+
+bool LivenessManager::IsAnalyzedBuiltin(uint32_t bi) {
+ // There are only three builtins that can be analyzed and removed between
+ // two stages: PointSize, ClipDistance and CullDistance. All others are
+ // always consumed implicitly by the downstream stage.
+ return bi == SpvBuiltInPointSize || bi == SpvBuiltInClipDistance ||
+ bi == SpvBuiltInCullDistance;
+}
+
+bool LivenessManager::AnalyzeBuiltIn(uint32_t id) {
+ auto deco_mgr = context()->get_decoration_mgr();
+ bool saw_builtin = false;
+ // Analyze all builtin decorations of |id|.
+ (void)deco_mgr->ForEachDecoration(
+ id, SpvDecorationBuiltIn,
+ [this, &saw_builtin](const Instruction& deco_inst) {
+ saw_builtin = true;
+ // No need to process builtins in frag shader. All assumed used.
+ if (context()->GetStage() == SpvExecutionModelFragment) return;
+ uint32_t builtin = SpvBuiltInMax;
+ if (deco_inst.opcode() == SpvOpDecorate)
+ builtin =
+ deco_inst.GetSingleWordInOperand(kOpDecorateBuiltInLiteralInIdx);
+ else if (deco_inst.opcode() == SpvOpMemberDecorate)
+ builtin = deco_inst.GetSingleWordInOperand(
+ kOpDecorateMemberBuiltInLiteralInIdx);
+ else
+ assert(false && "unexpected decoration");
+ if (IsAnalyzedBuiltin(builtin)) live_builtins_.insert(builtin);
+ });
+ return saw_builtin;
+}
+
+void LivenessManager::MarkLocsLive(uint32_t start, uint32_t count) {
+ auto finish = start + count;
+ for (uint32_t u = start; u < finish; ++u) {
+ live_locs_.insert(u);
+ }
+}
+
+uint32_t LivenessManager::GetLocSize(const analysis::Type* type) const {
+ auto arr_type = type->AsArray();
+ if (arr_type) {
+ auto comp_type = arr_type->element_type();
+ auto len_info = arr_type->length_info();
+ assert(len_info.words[0] == analysis::Array::LengthInfo::kConstant &&
+ "unexpected array length");
+ auto comp_len = len_info.words[1];
+ return comp_len * GetLocSize(comp_type);
+ }
+ auto struct_type = type->AsStruct();
+ if (struct_type) {
+ uint32_t size = 0u;
+ for (auto& el_type : struct_type->element_types())
+ size += GetLocSize(el_type);
+ return size;
+ }
+ auto mat_type = type->AsMatrix();
+ if (mat_type) {
+ auto cnt = mat_type->element_count();
+ auto comp_type = mat_type->element_type();
+ return cnt * GetLocSize(comp_type);
+ }
+ auto vec_type = type->AsVector();
+ if (vec_type) {
+ auto comp_type = vec_type->element_type();
+ if (comp_type->AsInteger()) return 1;
+ auto float_type = comp_type->AsFloat();
+ assert(float_type && "unexpected vector component type");
+ auto width = float_type->width();
+ if (width == 32 || width == 16) return 1;
+ assert(width == 64 && "unexpected float type width");
+ auto comp_cnt = vec_type->element_count();
+ return (comp_cnt > 2) ? 2 : 1;
+ }
+ assert((type->AsInteger() || type->AsFloat()) && "unexpected input type");
+ return 1;
+}
+
+const analysis::Type* LivenessManager::GetComponentType(
+ uint32_t index, const analysis::Type* agg_type) const {
+ auto arr_type = agg_type->AsArray();
+ if (arr_type) return arr_type->element_type();
+ auto struct_type = agg_type->AsStruct();
+ if (struct_type) return struct_type->element_types()[index];
+ auto mat_type = agg_type->AsMatrix();
+ if (mat_type) return mat_type->element_type();
+ auto vec_type = agg_type->AsVector();
+ assert(vec_type && "unexpected non-aggregate type");
+ return vec_type->element_type();
+}
+
+uint32_t LivenessManager::GetLocOffset(uint32_t index,
+ const analysis::Type* agg_type) const {
+ auto arr_type = agg_type->AsArray();
+ if (arr_type) return index * GetLocSize(arr_type->element_type());
+ auto struct_type = agg_type->AsStruct();
+ if (struct_type) {
+ uint32_t offset = 0u;
+ uint32_t cnt = 0u;
+ for (auto& el_type : struct_type->element_types()) {
+ if (cnt == index) break;
+ offset += GetLocSize(el_type);
+ ++cnt;
+ }
+ return offset;
+ }
+ auto mat_type = agg_type->AsMatrix();
+ if (mat_type) return index * GetLocSize(mat_type->element_type());
+ auto vec_type = agg_type->AsVector();
+ assert(vec_type && "unexpected non-aggregate type");
+ auto comp_type = vec_type->element_type();
+ auto flt_type = comp_type->AsFloat();
+ if (flt_type && flt_type->width() == 64u && index >= 2u) return 1;
+ return 0;
+}
+
+void LivenessManager::AnalyzeAccessChainLoc(const Instruction* ac,
+ const analysis::Type** curr_type,
+ uint32_t* offset, bool* no_loc,
+ bool is_patch, bool input) {
+ analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr();
+ // For tesc, tese and geom input variables, and tesc output variables,
+ // first array index does not contribute to offset.
+ auto stage = context()->GetStage();
+ bool skip_first_index = false;
+ if ((input && (stage == SpvExecutionModelTessellationControl ||
+ stage == SpvExecutionModelTessellationEvaluation ||
+ stage == SpvExecutionModelGeometry)) ||
+ (!input && stage == SpvExecutionModelTessellationControl))
+ skip_first_index = !is_patch;
+ uint32_t ocnt = 0;
+ ac->WhileEachInOperand([this, &ocnt, def_use_mgr, type_mgr, deco_mgr,
+ curr_type, offset, no_loc,
+ skip_first_index](const uint32_t* opnd) {
+ if (ocnt >= 1) {
+ // Skip first index's contribution to offset if indicated
+ if (ocnt == 1 && skip_first_index) {
+ auto arr_type = (*curr_type)->AsArray();
+ assert(arr_type && "unexpected wrapper type");
+ *curr_type = arr_type->element_type();
+ ocnt++;
+ return true;
+ }
+ // If any non-constant index, mark the entire current object and return.
+ auto idx_inst = def_use_mgr->GetDef(*opnd);
+ if (idx_inst->opcode() != SpvOpConstant) return false;
+ // If current type is struct, look for location decoration on member and
+ // reset offset if found.
+ auto index = idx_inst->GetSingleWordInOperand(0);
+ auto str_type = (*curr_type)->AsStruct();
+ if (str_type) {
+ uint32_t loc = 0;
+ auto str_type_id = type_mgr->GetId(str_type);
+ bool no_mem_loc = deco_mgr->WhileEachDecoration(
+ str_type_id, SpvDecorationLocation,
+ [&loc, index, no_loc](const Instruction& deco) {
+ assert(deco.opcode() == SpvOpMemberDecorate &&
+ "unexpected decoration");
+ if (deco.GetSingleWordInOperand(kOpDecorateMemberMemberInIdx) ==
+ index) {
+ loc =
+ deco.GetSingleWordInOperand(kOpDecorateMemberLocationInIdx);
+ *no_loc = false;
+ return false;
+ }
+ return true;
+ });
+ if (!no_mem_loc) {
+ *offset = loc;
+ *curr_type = GetComponentType(index, *curr_type);
+ ocnt++;
+ return true;
+ }
+ }
+
+ // Update offset and current type based on constant index.
+ *offset += GetLocOffset(index, *curr_type);
+ *curr_type = GetComponentType(index, *curr_type);
+ }
+ ocnt++;
+ return true;
+ });
+}
+
+void LivenessManager::MarkRefLive(const Instruction* ref, Instruction* var) {
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr();
+ // Find variable location if present.
+ uint32_t loc = 0;
+ auto var_id = var->result_id();
+ bool no_loc = deco_mgr->WhileEachDecoration(
+ var_id, SpvDecorationLocation, [&loc](const Instruction& deco) {
+ assert(deco.opcode() == SpvOpDecorate && "unexpected decoration");
+ loc = deco.GetSingleWordInOperand(kDecorationLocationInIdx);
+ return false;
+ });
+ // Find patch decoration if present
+ bool is_patch = !deco_mgr->WhileEachDecoration(
+ var_id, SpvDecorationPatch, [](const Instruction& deco) {
+ if (deco.opcode() != SpvOpDecorate)
+ assert(false && "unexpected decoration");
+ return false;
+ });
+ // If use is a load, mark all locations of var
+ auto ptr_type = type_mgr->GetType(var->type_id())->AsPointer();
+ assert(ptr_type && "unexpected var type");
+ auto var_type = ptr_type->pointee_type();
+ if (ref->opcode() == SpvOpLoad) {
+ assert(!no_loc && "missing input variable location");
+ MarkLocsLive(loc, GetLocSize(var_type));
+ return;
+ }
+ // Mark just those locations indicated by access chain
+ assert((ref->opcode() == SpvOpAccessChain ||
+ ref->opcode() == SpvOpInBoundsAccessChain) &&
+ "unexpected use of input variable");
+ // Traverse access chain, compute location offset and type of reference
+ // through constant indices and mark those locs live. Assert if no location
+ // found.
+ uint32_t offset = loc;
+ auto curr_type = var_type;
+ AnalyzeAccessChainLoc(ref, &curr_type, &offset, &no_loc, is_patch);
+ assert(!no_loc && "missing input variable location");
+ MarkLocsLive(offset, GetLocSize(curr_type));
+}
+
+void LivenessManager::ComputeLiveness() {
+ InitializeAnalysis();
+ analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ // Process all input variables
+ for (auto& var : context()->types_values()) {
+ if (var.opcode() != SpvOpVariable) {
+ continue;
+ }
+ analysis::Type* var_type = type_mgr->GetType(var.type_id());
+ analysis::Pointer* ptr_type = var_type->AsPointer();
+ if (ptr_type->storage_class() != SpvStorageClassInput) {
+ continue;
+ }
+ // If var is builtin, mark live if analyzed and continue to next variable
+ auto var_id = var.result_id();
+ if (AnalyzeBuiltIn(var_id)) continue;
+ // If interface block with builtin members, mark live if analyzed and
+ // continue to next variable. Input interface blocks will only appear
+ // in tesc, tese and geom shaders. Will need to strip off one level of
+ // arrayness to get to block type.
+ auto pte_type = ptr_type->pointee_type();
+ auto arr_type = pte_type->AsArray();
+ if (arr_type) {
+ auto elt_type = arr_type->element_type();
+ auto str_type = elt_type->AsStruct();
+ if (str_type) {
+ auto str_type_id = type_mgr->GetId(str_type);
+ if (AnalyzeBuiltIn(str_type_id)) continue;
+ }
+ }
+ // Mark all used locations of var live
+ def_use_mgr->ForEachUser(var_id, [this, &var](Instruction* user) {
+ auto op = user->opcode();
+ if (op == SpvOpEntryPoint || op == SpvOpName || op == SpvOpDecorate)
+ return;
+ MarkRefLive(user, &var);
+ });
+ }
+}
+
+void LivenessManager::GetLiveness(std::unordered_set<uint32_t>* live_locs,
+ std::unordered_set<uint32_t>* live_builtins) {
+ if (!computed_) {
+ ComputeLiveness();
+ computed_ = true;
+ }
+ *live_locs = live_locs_;
+ *live_builtins = live_builtins_;
+}
+
+} // namespace analysis
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/liveness.h b/source/opt/liveness.h
new file mode 100644
index 000000000..7d8a9fb40
--- /dev/null
+++ b/source/opt/liveness.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+// Copyright (c) 2022 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_LIVENESS_H_
+#define SOURCE_OPT_LIVENESS_H_
+
+#include <cstdint>
+#include <unordered_set>
+
+namespace spvtools {
+namespace opt {
+
+class IRContext;
+class Instruction;
+
+namespace analysis {
+
+class Type;
+
+// This class represents the liveness of the input variables of a module
+class LivenessManager {
+ public:
+ LivenessManager(IRContext* ctx);
+
+ // Copy liveness info into |live_locs| and |builtin_locs|.
+ void GetLiveness(std::unordered_set<uint32_t>* live_locs,
+ std::unordered_set<uint32_t>* live_builtins);
+
+ // Return true if builtin |bi| is being analyzed.
+ bool IsAnalyzedBuiltin(uint32_t bi);
+
+ // Determine starting loc |offset| and the type |cur_type| of
+ // access chain |ac|. Set |no_loc| to true if no loc found.
+ // |is_patch| indicates if patch variable. |input| is true
+ // if input variable, otherwise output variable.
+ void AnalyzeAccessChainLoc(const Instruction* ac,
+ const analysis::Type** curr_type, uint32_t* offset,
+ bool* no_loc, bool is_patch, bool input = true);
+
+ // Return size of |type_id| in units of locations
+ uint32_t GetLocSize(const analysis::Type* type) const;
+
+ private:
+ IRContext* context() const { return ctx_; }
+
+ // Initialize analysis
+ void InitializeAnalysis();
+
+ // Analyze |id| for builtin var and struct members. Return true if builtins
+ // found.
+ bool AnalyzeBuiltIn(uint32_t id);
+
+ // Mark all live locations resulting from |user| of |var| at |loc|.
+ void MarkRefLive(const Instruction* user, Instruction* var);
+
+ // Mark |count| locations starting at location |start|.
+ void MarkLocsLive(uint32_t start, uint32_t count);
+
+ // Return type of component of aggregate type |agg_type| at |index|
+ const analysis::Type* GetComponentType(uint32_t index,
+ const analysis::Type* agg_type) const;
+
+ // Return offset of |index| into aggregate type |agg_type| in units of
+ // input locations
+ uint32_t GetLocOffset(uint32_t index, const analysis::Type* agg_type) const;
+
+ // Populate live_locs_ and live_builtins_
+ void ComputeLiveness();
+
+ // IR context that owns this liveness manager.
+ IRContext* ctx_;
+
+ // True if live_locs_ and live_builtins_ are computed
+ bool computed_;
+
+ // Live locations
+ std::unordered_set<uint32_t> live_locs_;
+
+ // Live builtins
+ std::unordered_set<uint32_t> live_builtins_;
+};
+
+} // namespace analysis
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_LIVENESS_H_
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index c98af8f51..2f788ffda 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -159,7 +159,6 @@ void Module::ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const {
if (between_merge_and_branch && i->IsLineInst()) {
return;
}
- between_merge_and_branch = false;
if (last_line_inst != nullptr) {
// If the current instruction is OpLine or DebugLine and it is the same
// as the last line instruction that is still effective (can be applied
@@ -202,7 +201,7 @@ void Module::ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const {
if (!(skip_nop && i->IsNop())) {
const auto& scope = i->GetDebugScope();
- if (scope != last_scope) {
+ if (scope != last_scope && !between_merge_and_branch) {
// Can only emit nonsemantic instructions after all phi instructions
// in a block so don't emit scope instructions before phi instructions
// for NonSemantic.Shader.DebugInfo.100.
@@ -221,6 +220,7 @@ void Module::ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const {
i->ToBinaryWithoutAttachedDebugInsts(binary);
}
// Update the last line instruction.
+ between_merge_and_branch = false;
if (spvOpcodeIsBlockTerminator(opcode) || i->IsNoLine()) {
last_line_inst = nullptr;
} else if (opcode == SpvOpLoopMerge || opcode == SpvOpSelectionMerge) {
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 381589b53..75a47784c 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -60,6 +60,7 @@ struct Optimizer::Impl {
spv_target_env target_env; // Target environment.
opt::PassManager pass_manager; // Internal implementation pass manager.
+ std::unordered_set<uint32_t> live_locs; // Arg to debug dead output passes
};
Optimizer::Optimizer(spv_target_env env) : impl_(new Impl(env)) {
@@ -1019,6 +1020,20 @@ Optimizer::PassToken CreateEliminateDeadInputComponentsPass() {
MakeUnique<opt::EliminateDeadInputComponentsPass>());
}
+Optimizer::PassToken CreateAnalyzeLiveInputPass(
+ std::unordered_set<uint32_t>* live_locs,
+ std::unordered_set<uint32_t>* live_builtins) {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::AnalyzeLiveInputPass>(live_locs, live_builtins));
+}
+
+Optimizer::PassToken CreateEliminateDeadOutputStoresPass(
+ std::unordered_set<uint32_t>* live_locs,
+ std::unordered_set<uint32_t>* live_builtins) {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::EliminateDeadOutputStoresPass>(live_locs, live_builtins));
+}
+
Optimizer::PassToken CreateConvertToSampledImagePass(
const std::vector<opt::DescriptorSetAndBinding>&
descriptor_set_binding_pairs) {
diff --git a/source/opt/passes.h b/source/opt/passes.h
index 21354c77b..5344dcf95 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -19,6 +19,7 @@
#include "source/opt/aggressive_dead_code_elim_pass.h"
#include "source/opt/amd_ext_to_khr.h"
+#include "source/opt/analyze_live_input_pass.h"
#include "source/opt/block_merge_pass.h"
#include "source/opt/ccp_pass.h"
#include "source/opt/cfg_cleanup_pass.h"
@@ -36,6 +37,7 @@
#include "source/opt/eliminate_dead_functions_pass.h"
#include "source/opt/eliminate_dead_input_components_pass.h"
#include "source/opt/eliminate_dead_members_pass.h"
+#include "source/opt/eliminate_dead_output_stores_pass.h"
#include "source/opt/empty_pass.h"
#include "source/opt/fix_func_call_arguments.h"
#include "source/opt/fix_storage_class.h"
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 15966c184..36bf04c32 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -18,6 +18,7 @@ add_subdirectory(loop_optimizations)
add_spvtools_unittest(TARGET opt
SRCS aggressive_dead_code_elim_test.cpp
amd_ext_to_khr.cpp
+ analyze_live_input_test.cpp
assembly_builder_test.cpp
block_merge_test.cpp
ccp_test.cpp
@@ -44,6 +45,7 @@ add_spvtools_unittest(TARGET opt
eliminate_dead_functions_test.cpp
eliminate_dead_input_components_test.cpp
eliminate_dead_member_test.cpp
+ eliminate_dead_output_stores_test.cpp
feature_manager_test.cpp
fix_func_call_arguments_test.cpp
fix_storage_class_test.cpp
diff --git a/test/opt/analyze_live_input_test.cpp b/test/opt/analyze_live_input_test.cpp
new file mode 100644
index 000000000..d61dbdb85
--- /dev/null
+++ b/test/opt/analyze_live_input_test.cpp
@@ -0,0 +1,910 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+// Copyright (c) 2022 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <unordered_set>
+
+#include "gmock/gmock.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using AnalyzeLiveInputTest = PassTest<::testing::Test>;
+
+TEST_F(AnalyzeLiveInputTest, FragMultipleLocations) {
+ // Should report locations {2, 5}
+ //
+ // #version 450
+ //
+ // layout(location = 2) in Vertex
+ // {
+ // vec4 color0;
+ // vec4 color1;
+ // vec4 color2[3];
+ // } iVert;
+ //
+ // layout(location = 0) out vec4 oFragColor;
+ //
+ // void main()
+ // {
+ // oFragColor = iVert.color0 + iVert.color2[1];
+ // }
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %oFragColor %iVert
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %oFragColor "oFragColor"
+ OpName %Vertex "Vertex"
+ OpMemberName %Vertex 0 "color0"
+ OpMemberName %Vertex 1 "color1"
+ OpMemberName %Vertex 2 "color2"
+ OpName %iVert "iVert"
+ OpDecorate %oFragColor Location 0
+ OpDecorate %Vertex Block
+ OpDecorate %iVert Location 2
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %oFragColor = OpVariable %_ptr_Output_v4float Output
+ %uint = OpTypeInt 32 0
+ %uint_3 = OpConstant %uint 3
+%_arr_v4float_uint_3 = OpTypeArray %v4float %uint_3
+ %Vertex = OpTypeStruct %v4float %v4float %_arr_v4float_uint_3
+%_ptr_Input_Vertex = OpTypePointer Input %Vertex
+ %iVert = OpVariable %_ptr_Input_Vertex Input
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %int_2 = OpConstant %int 2
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %19 = OpAccessChain %_ptr_Input_v4float %iVert %int_0
+ %20 = OpLoad %v4float %19
+ %23 = OpAccessChain %_ptr_Input_v4float %iVert %int_2 %int_1
+ %24 = OpLoad %v4float %23
+ %25 = OpFAdd %v4float %20 %24
+ OpStore %oFragColor %25
+ 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;
+ auto result = SinglePassRunToBinary<AnalyzeLiveInputPass>(
+ text, true, &live_inputs, &live_builtins);
+
+ auto itr0 = live_inputs.find(0);
+ auto itr1 = live_inputs.find(1);
+ auto itr2 = live_inputs.find(2);
+ auto itr3 = live_inputs.find(3);
+ auto itr4 = live_inputs.find(4);
+ auto itr5 = live_inputs.find(5);
+ auto itr6 = live_inputs.find(6);
+
+ // Expect live_inputs == {2, 5}
+ EXPECT_TRUE(itr0 == live_inputs.end());
+ EXPECT_TRUE(itr1 == live_inputs.end());
+ EXPECT_TRUE(itr2 != live_inputs.end());
+ EXPECT_TRUE(itr3 == live_inputs.end());
+ EXPECT_TRUE(itr4 == live_inputs.end());
+ EXPECT_TRUE(itr5 != live_inputs.end());
+ EXPECT_TRUE(itr6 == live_inputs.end());
+}
+
+TEST_F(AnalyzeLiveInputTest, FragMatrix) {
+ // Should report locations {2, 8, 9, 10, 11}
+ //
+ // #version 450
+ //
+ // uniform ui_name {
+ // int i;
+ // } ui_inst;
+ //
+ // layout(location = 2) in Vertex
+ // {
+ // vec4 color0;
+ // vec4 color1;
+ // mat4 color2;
+ // mat4 color3;
+ // mat4 color4;
+ // } iVert;
+ //
+ // // Output variable for the color
+ // layout(location = 0) out vec4 oFragColor;
+ //
+ // void main()
+ // {
+ // oFragColor = iVert.color0 + iVert.color3[ui_inst.i];
+ // }
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %oFragColor %iVert %ui_inst
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %oFragColor "oFragColor"
+ OpName %Vertex "Vertex"
+ OpMemberName %Vertex 0 "color0"
+ OpMemberName %Vertex 1 "color1"
+ OpMemberName %Vertex 2 "color2"
+ OpMemberName %Vertex 3 "color3"
+ OpMemberName %Vertex 4 "color4"
+ OpName %iVert "iVert"
+ OpName %ui_name "ui_name"
+ OpMemberName %ui_name 0 "i"
+ OpName %ui_inst "ui_inst"
+ OpDecorate %oFragColor Location 0
+ OpDecorate %Vertex Block
+ OpDecorate %iVert Location 2
+ OpMemberDecorate %ui_name 0 Offset 0
+ OpDecorate %ui_name Block
+ OpDecorate %ui_inst DescriptorSet 0
+ OpDecorate %ui_inst Binding 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %oFragColor = OpVariable %_ptr_Output_v4float Output
+%mat4v4float = OpTypeMatrix %v4float 4
+ %Vertex = OpTypeStruct %v4float %v4float %mat4v4float %mat4v4float %mat4v4float
+%_ptr_Input_Vertex = OpTypePointer Input %Vertex
+ %iVert = OpVariable %_ptr_Input_Vertex Input
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %int_3 = OpConstant %int 3
+ %ui_name = OpTypeStruct %int
+%_ptr_Uniform_ui_name = OpTypePointer Uniform %ui_name
+ %ui_inst = OpVariable %_ptr_Uniform_ui_name Uniform
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %17 = OpAccessChain %_ptr_Input_v4float %iVert %int_0
+ %18 = OpLoad %v4float %17
+ %24 = OpAccessChain %_ptr_Uniform_int %ui_inst %int_0
+ %25 = OpLoad %int %24
+ %26 = OpAccessChain %_ptr_Input_v4float %iVert %int_3 %25
+ %27 = OpLoad %v4float %26
+ %28 = OpFAdd %v4float %18 %27
+ OpStore %oFragColor %28
+ 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;
+ auto result = SinglePassRunToBinary<AnalyzeLiveInputPass>(
+ text, true, &live_inputs, &live_builtins);
+
+ auto itr0 = live_inputs.find(0);
+ auto itr1 = live_inputs.find(1);
+ auto itr2 = live_inputs.find(2);
+ auto itr3 = live_inputs.find(3);
+ auto itr4 = live_inputs.find(4);
+ auto itr5 = live_inputs.find(5);
+ auto itr6 = live_inputs.find(6);
+ auto itr7 = live_inputs.find(7);
+ auto itr8 = live_inputs.find(8);
+ auto itr9 = live_inputs.find(9);
+ auto itr10 = live_inputs.find(10);
+ auto itr11 = live_inputs.find(11);
+ auto itr12 = live_inputs.find(12);
+ auto itr13 = live_inputs.find(13);
+ auto itr14 = live_inputs.find(14);
+ auto itr15 = live_inputs.find(15);
+
+ // Expect live_inputs == {2, 8, 9, 10, 11}
+ EXPECT_TRUE(itr0 == live_inputs.end());
+ EXPECT_TRUE(itr1 == live_inputs.end());
+ EXPECT_TRUE(itr2 != live_inputs.end());
+ EXPECT_TRUE(itr3 == live_inputs.end());
+ EXPECT_TRUE(itr4 == live_inputs.end());
+ EXPECT_TRUE(itr5 == live_inputs.end());
+ EXPECT_TRUE(itr6 == live_inputs.end());
+ EXPECT_TRUE(itr7 == live_inputs.end());
+ EXPECT_TRUE(itr8 != live_inputs.end());
+ EXPECT_TRUE(itr9 != live_inputs.end());
+ EXPECT_TRUE(itr10 != live_inputs.end());
+ EXPECT_TRUE(itr11 != live_inputs.end());
+ EXPECT_TRUE(itr12 == live_inputs.end());
+ EXPECT_TRUE(itr13 == live_inputs.end());
+ EXPECT_TRUE(itr14 == live_inputs.end());
+ EXPECT_TRUE(itr15 == live_inputs.end());
+}
+
+TEST_F(AnalyzeLiveInputTest, FragMemberLocs) {
+ // Should report location {1}
+ //
+ // #version 450
+ //
+ // in Vertex
+ // {
+ // layout (location = 1) vec4 Cd;
+ // layout (location = 0) vec2 uv;
+ // } iVert;
+ //
+ // layout (location = 0) out vec4 fragColor;
+ //
+ // void main()
+ // {
+ // vec4 color = vec4(iVert.Cd);
+ // fragColor = color;
+ // }
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %iVert %fragColor
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %color "color"
+ OpName %Vertex "Vertex"
+ OpMemberName %Vertex 0 "Cd"
+ OpMemberName %Vertex 1 "uv"
+ OpName %iVert "iVert"
+ OpName %fragColor "fragColor"
+ OpMemberDecorate %Vertex 0 Location 1
+ OpMemberDecorate %Vertex 1 Location 0
+ OpDecorate %Vertex Block
+ OpDecorate %fragColor Location 0
+ OpDecorate %_struct_27 Block
+ OpMemberDecorate %_struct_27 0 Location 1
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+ %v2float = OpTypeVector %float 2
+ %Vertex = OpTypeStruct %v4float %v2float
+%_ptr_Input_Vertex = OpTypePointer Input %Vertex
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %fragColor = OpVariable %_ptr_Output_v4float Output
+ %_struct_27 = OpTypeStruct %v4float
+%_ptr_Input__struct_27 = OpTypePointer Input %_struct_27
+ %iVert = OpVariable %_ptr_Input__struct_27 Input
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %color = OpVariable %_ptr_Function_v4float Function
+ %17 = OpAccessChain %_ptr_Input_v4float %iVert %int_0
+ %18 = OpLoad %v4float %17
+ %19 = OpCompositeExtract %float %18 0
+ %20 = OpCompositeExtract %float %18 1
+ %21 = OpCompositeExtract %float %18 2
+ %22 = OpCompositeExtract %float %18 3
+ %23 = OpCompositeConstruct %v4float %19 %20 %21 %22
+ OpStore %color %23
+ %26 = OpLoad %v4float %color
+ OpStore %fragColor %26
+ 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;
+ auto result = SinglePassRunToBinary<AnalyzeLiveInputPass>(
+ text, true, &live_inputs, &live_builtins);
+
+ auto itr0 = live_inputs.find(0);
+ auto itr1 = live_inputs.find(1);
+
+ // Expect live_inputs == {2, 5}
+ EXPECT_TRUE(itr0 == live_inputs.end());
+ EXPECT_TRUE(itr1 != live_inputs.end());
+}
+
+TEST_F(AnalyzeLiveInputTest, ArrayedInput) {
+ // Tests handling of arrayed input seen in Tesc, Tese and Geom shaders.
+ //
+ // Should report location {1, 10}.
+ //
+ // #version 450
+ //
+ // layout (vertices = 4) out;
+ //
+ // layout (location = 1) in Vertex
+ // {
+ // vec4 p;
+ // vec3 n;
+ // vec4 f[100];
+ // } iVert[];
+ //
+ // layout (location = 0) out vec4 position[4];
+ //
+ // void main()
+ // {
+ // vec4 pos = iVert[gl_InvocationID].p *
+ // iVert[gl_InvocationID].f[7];
+ // position[gl_InvocationID] = pos;
+ // }
+ const std::string text = R"(
+ OpCapability Tessellation
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint TessellationControl %main "main" %iVert %gl_InvocationID %position
+ OpExecutionMode %main OutputVertices 4
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %Vertex "Vertex"
+ OpMemberName %Vertex 0 "p"
+ OpMemberName %Vertex 1 "n"
+ OpMemberName %Vertex 2 "f"
+ OpName %iVert "iVert"
+ OpName %gl_InvocationID "gl_InvocationID"
+ OpName %position "position"
+ OpDecorate %Vertex Block
+ OpDecorate %iVert Location 1
+ OpDecorate %gl_InvocationID BuiltIn InvocationId
+ OpDecorate %position Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %v3float = OpTypeVector %float 3
+ %uint = OpTypeInt 32 0
+ %uint_100 = OpConstant %uint 100
+%_arr_v4float_uint_100 = OpTypeArray %v4float %uint_100
+ %Vertex = OpTypeStruct %v4float %v3float %_arr_v4float_uint_100
+ %uint_32 = OpConstant %uint 32
+%_arr_Vertex_uint_32 = OpTypeArray %Vertex %uint_32
+%_ptr_Input__arr_Vertex_uint_32 = OpTypePointer Input %_arr_Vertex_uint_32
+ %iVert = OpVariable %_ptr_Input__arr_Vertex_uint_32 Input
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_InvocationID = OpVariable %_ptr_Input_int Input
+ %int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %int_2 = OpConstant %int 2
+ %int_7 = OpConstant %int 7
+ %uint_4 = OpConstant %uint 4
+%_arr_v4float_uint_4 = OpTypeArray %v4float %uint_4
+%_ptr_Output__arr_v4float_uint_4 = OpTypePointer Output %_arr_v4float_uint_4
+ %position = OpVariable %_ptr_Output__arr_v4float_uint_4 Output
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %22 = OpLoad %int %gl_InvocationID
+ %25 = OpAccessChain %_ptr_Input_v4float %iVert %22 %int_0
+ %26 = OpLoad %v4float %25
+ %30 = OpAccessChain %_ptr_Input_v4float %iVert %22 %int_2 %int_7
+ %31 = OpLoad %v4float %30
+ %32 = OpFMul %v4float %26 %31
+ %40 = OpAccessChain %_ptr_Output_v4float %position %22
+ OpStore %40 %32
+ 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;
+ auto result = SinglePassRunToBinary<AnalyzeLiveInputPass>(
+ text, true, &live_inputs, &live_builtins);
+
+ auto itr0 = live_inputs.find(0);
+ auto itr1 = live_inputs.find(1);
+ auto itr2 = live_inputs.find(2);
+ auto itr3 = live_inputs.find(3);
+ auto itr4 = live_inputs.find(4);
+ auto itr5 = live_inputs.find(5);
+ auto itr6 = live_inputs.find(6);
+ auto itr7 = live_inputs.find(7);
+ auto itr8 = live_inputs.find(8);
+ auto itr9 = live_inputs.find(9);
+ auto itr10 = live_inputs.find(10);
+ auto itr11 = live_inputs.find(11);
+
+ // Expect live_inputs == {1, 10}
+ EXPECT_TRUE(itr0 == live_inputs.end());
+ EXPECT_TRUE(itr1 != live_inputs.end());
+ EXPECT_TRUE(itr2 == live_inputs.end());
+ EXPECT_TRUE(itr3 == live_inputs.end());
+ EXPECT_TRUE(itr4 == live_inputs.end());
+ EXPECT_TRUE(itr5 == live_inputs.end());
+ EXPECT_TRUE(itr6 == live_inputs.end());
+ EXPECT_TRUE(itr7 == live_inputs.end());
+ EXPECT_TRUE(itr8 == live_inputs.end());
+ EXPECT_TRUE(itr9 == live_inputs.end());
+ EXPECT_TRUE(itr10 != live_inputs.end());
+ EXPECT_TRUE(itr11 == live_inputs.end());
+}
+
+TEST_F(AnalyzeLiveInputTest, ArrayedInputMemberLocs) {
+ // Tests handling of member locs with arrayed input seen in Tesc, Tese
+ // and Geom shaders.
+ //
+ // Should report location {1, 12}.
+ //
+ // #version 450
+ //
+ // layout (vertices = 4) out;
+ //
+ // in Vertex
+ // {
+ // layout (location = 1) vec4 p;
+ // layout (location = 3) vec3 n;
+ // layout (location = 5) vec4 f[100];
+ // } iVert[];
+ //
+ // layout (location = 0) out vec4 position[4];
+ //
+ // void main()
+ // {
+ // vec4 pos = iVert[gl_InvocationID].p *
+ // iVert[gl_InvocationID].f[7];
+ // position[gl_InvocationID] = pos;
+ // }
+ const std::string text = R"(
+ OpCapability Tessellation
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint TessellationControl %main "main" %iVert %gl_InvocationID %position
+ OpExecutionMode %main OutputVertices 4
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %Vertex "Vertex"
+ OpMemberName %Vertex 0 "p"
+ OpMemberName %Vertex 1 "n"
+ OpMemberName %Vertex 2 "f"
+ OpName %iVert "iVert"
+ OpName %gl_InvocationID "gl_InvocationID"
+ OpName %position "position"
+ OpMemberDecorate %Vertex 0 Location 1
+ OpMemberDecorate %Vertex 1 Location 3
+ OpMemberDecorate %Vertex 2 Location 5
+ OpDecorate %Vertex Block
+ OpDecorate %gl_InvocationID BuiltIn InvocationId
+ OpDecorate %position Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %v3float = OpTypeVector %float 3
+ %uint = OpTypeInt 32 0
+ %uint_100 = OpConstant %uint 100
+%_arr_v4float_uint_100 = OpTypeArray %v4float %uint_100
+ %Vertex = OpTypeStruct %v4float %v3float %_arr_v4float_uint_100
+ %uint_32 = OpConstant %uint 32
+%_arr_Vertex_uint_32 = OpTypeArray %Vertex %uint_32
+%_ptr_Input__arr_Vertex_uint_32 = OpTypePointer Input %_arr_Vertex_uint_32
+ %iVert = OpVariable %_ptr_Input__arr_Vertex_uint_32 Input
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_InvocationID = OpVariable %_ptr_Input_int Input
+ %int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %int_2 = OpConstant %int 2
+ %int_7 = OpConstant %int 7
+ %uint_4 = OpConstant %uint 4
+%_arr_v4float_uint_4 = OpTypeArray %v4float %uint_4
+%_ptr_Output__arr_v4float_uint_4 = OpTypePointer Output %_arr_v4float_uint_4
+ %position = OpVariable %_ptr_Output__arr_v4float_uint_4 Output
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %22 = OpLoad %int %gl_InvocationID
+ %25 = OpAccessChain %_ptr_Input_v4float %iVert %22 %int_0
+ %26 = OpLoad %v4float %25
+ %30 = OpAccessChain %_ptr_Input_v4float %iVert %22 %int_2 %int_7
+ %31 = OpLoad %v4float %30
+ %32 = OpFMul %v4float %26 %31
+ %40 = OpAccessChain %_ptr_Output_v4float %position %22
+ OpStore %40 %32
+ 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;
+ auto result = SinglePassRunToBinary<AnalyzeLiveInputPass>(
+ text, true, &live_inputs, &live_builtins);
+
+ auto itr0 = live_inputs.find(0);
+ auto itr1 = live_inputs.find(1);
+ auto itr2 = live_inputs.find(2);
+ auto itr3 = live_inputs.find(3);
+ auto itr4 = live_inputs.find(4);
+ auto itr5 = live_inputs.find(5);
+ auto itr6 = live_inputs.find(6);
+ auto itr7 = live_inputs.find(7);
+ auto itr8 = live_inputs.find(8);
+ auto itr9 = live_inputs.find(9);
+ auto itr10 = live_inputs.find(10);
+ auto itr11 = live_inputs.find(11);
+ auto itr12 = live_inputs.find(12);
+ auto itr13 = live_inputs.find(13);
+
+ // Expect live_inputs == {1, 12}
+ EXPECT_TRUE(itr0 == live_inputs.end());
+ EXPECT_TRUE(itr1 != live_inputs.end());
+ EXPECT_TRUE(itr2 == live_inputs.end());
+ EXPECT_TRUE(itr3 == live_inputs.end());
+ EXPECT_TRUE(itr4 == live_inputs.end());
+ EXPECT_TRUE(itr5 == live_inputs.end());
+ EXPECT_TRUE(itr6 == live_inputs.end());
+ EXPECT_TRUE(itr7 == live_inputs.end());
+ EXPECT_TRUE(itr8 == live_inputs.end());
+ EXPECT_TRUE(itr9 == live_inputs.end());
+ EXPECT_TRUE(itr10 == live_inputs.end());
+ EXPECT_TRUE(itr11 == live_inputs.end());
+ EXPECT_TRUE(itr12 != live_inputs.end());
+ EXPECT_TRUE(itr13 == live_inputs.end());
+}
+
+TEST_F(AnalyzeLiveInputTest, Builtins) {
+ // Tests handling of builtin input seen in Tesc, Tese and Geom shaders.
+ //
+ // Should report builtin gl_PointSize only.
+ //
+ // #version 460
+ //
+ // layout(triangle_strip, max_vertices = 3) out;
+ // layout(triangles) in;
+ //
+ // void main()
+ // {
+ // for (int i = 0; i < 3; i++)
+ // {
+ // gl_Position = gl_in[i].gl_Position;
+ // gl_PointSize = gl_in[i].gl_PointSize;
+ //
+ // EmitVertex();
+ // }
+ //
+ // EndPrimitive();
+ // }
+ const std::string text = R"(
+ OpCapability Geometry
+ OpCapability GeometryPointSize
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Geometry %main "main" %_ %gl_in
+ OpExecutionMode %main Triangles
+ OpExecutionMode %main Invocations 1
+ OpExecutionMode %main OutputTriangleStrip
+ OpExecutionMode %main OutputVertices 3
+ OpSource GLSL 460
+ OpName %main "main"
+ OpName %i "i"
+ 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 %_ ""
+ OpName %gl_PerVertex_0 "gl_PerVertex"
+ OpMemberName %gl_PerVertex_0 0 "gl_Position"
+ OpMemberName %gl_PerVertex_0 1 "gl_PointSize"
+ OpMemberName %gl_PerVertex_0 2 "gl_ClipDistance"
+ OpMemberName %gl_PerVertex_0 3 "gl_CullDistance"
+ OpName %gl_in "gl_in"
+ 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
+ OpMemberDecorate %gl_PerVertex_0 0 BuiltIn Position
+ OpMemberDecorate %gl_PerVertex_0 1 BuiltIn PointSize
+ OpDecorate %gl_PerVertex_0 Block
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %int_3 = OpConstant %int 3
+ %bool = OpTypeBool
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %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
+%gl_PerVertex_0 = OpTypeStruct %v4float %float
+ %uint_3 = OpConstant %uint 3
+%_arr_gl_PerVertex_0_uint_3 = OpTypeArray %gl_PerVertex_0 %uint_3
+%_ptr_Input__arr_gl_PerVertex_0_uint_3 = OpTypePointer Input %_arr_gl_PerVertex_0_uint_3
+ %gl_in = OpVariable %_ptr_Input__arr_gl_PerVertex_0_uint_3 Input
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %int_1 = OpConstant %int 1
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Output_float = OpTypePointer Output %float
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %i = OpVariable %_ptr_Function_int Function
+ OpStore %i %int_0
+ OpBranch %10
+ %10 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %15 = OpLoad %int %i
+ %18 = OpSLessThan %bool %15 %int_3
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %32 = OpLoad %int %i
+ %34 = OpAccessChain %_ptr_Input_v4float %gl_in %32 %int_0
+ %35 = OpLoad %v4float %34
+ %37 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+ OpStore %37 %35
+ %39 = OpLoad %int %i
+ %41 = OpAccessChain %_ptr_Input_float %gl_in %39 %int_1
+ %42 = OpLoad %float %41
+ %44 = OpAccessChain %_ptr_Output_float %_ %int_1
+ OpStore %44 %42
+ OpEmitVertex
+ OpBranch %13
+ %13 = OpLabel
+ %45 = OpLoad %int %i
+ %46 = OpIAdd %int %45 %int_1
+ OpStore %i %46
+ OpBranch %10
+ %12 = OpLabel
+ OpEndPrimitive
+ 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;
+ auto result = SinglePassRunToBinary<AnalyzeLiveInputPass>(
+ text, true, &live_inputs, &live_builtins);
+
+ auto itr0 = live_builtins.find(SpvBuiltInPointSize);
+ auto itr1 = live_builtins.find(SpvBuiltInClipDistance);
+ auto itr2 = live_builtins.find(SpvBuiltInCullDistance);
+
+ // Expect live_builtins == { SpvBuiltInPointSize }
+ EXPECT_TRUE(itr0 != live_builtins.end());
+ EXPECT_TRUE(itr1 == live_builtins.end());
+ EXPECT_TRUE(itr2 == live_builtins.end());
+}
+
+TEST_F(AnalyzeLiveInputTest, ArrayedInputPatchLocs) {
+ // Tests handling of locs with arrayed input patch seen in Tese
+ //
+ // Should report location {3}.
+ //
+ // #version 450 core
+ //
+ // layout(triangles, ccw) in;
+ //
+ // layout(fractional_odd_spacing) in;
+ //
+ // layout(point_mode) in;
+ //
+ // layout(location=2) patch in float patchIn1[2];
+ //
+ // void main()
+ // {
+ // vec4 p = gl_in[1].gl_Position;
+ // gl_Position = p * patchIn1[1];
+ // }
+ const std::string text = R"(
+ OpCapability Tessellation
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint TessellationEvaluation %main "main" %gl_in %_ %patchIn1
+ OpExecutionMode %main Triangles
+ OpExecutionMode %main SpacingFractionalOdd
+ OpExecutionMode %main VertexOrderCcw
+ OpExecutionMode %main PointMode
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %p "p"
+ OpName %gl_PerVertex "gl_PerVertex"
+ OpMemberName %gl_PerVertex 0 "gl_Position"
+ OpName %gl_in "gl_in"
+ OpName %gl_PerVertex_0 "gl_PerVertex"
+ OpMemberName %gl_PerVertex_0 0 "gl_Position"
+ OpName %_ ""
+ OpName %patchIn1 "patchIn1"
+ OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+ OpDecorate %gl_PerVertex Block
+ OpMemberDecorate %gl_PerVertex_0 0 BuiltIn Position
+ OpDecorate %gl_PerVertex_0 Block
+ OpDecorate %patchIn1 Patch
+ OpDecorate %patchIn1 Location 2
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float
+ %uint_32 = OpConstant %uint 32
+%_arr_gl_PerVertex_uint_32 = OpTypeArray %gl_PerVertex %uint_32
+%_ptr_Input__arr_gl_PerVertex_uint_32 = OpTypePointer Input %_arr_gl_PerVertex_uint_32
+ %gl_in = OpVariable %_ptr_Input__arr_gl_PerVertex_uint_32 Input
+ %int = OpTypeInt 32 1
+ %int_1 = OpConstant %int 1
+ %int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_PerVertex_0 = OpTypeStruct %v4float
+%_ptr_Output_gl_PerVertex_0 = OpTypePointer Output %gl_PerVertex_0
+ %_ = OpVariable %_ptr_Output_gl_PerVertex_0 Output
+ %uint_2 = OpConstant %uint 2
+%_arr_float_uint_2 = OpTypeArray %float %uint_2
+%_ptr_Input__arr_float_uint_2 = OpTypePointer Input %_arr_float_uint_2
+ %patchIn1 = OpVariable %_ptr_Input__arr_float_uint_2 Input
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %p = OpVariable %_ptr_Function_v4float Function
+ %22 = OpAccessChain %_ptr_Input_v4float %gl_in %int_1 %int_0
+ %23 = OpLoad %v4float %22
+ OpStore %p %23
+ %27 = OpLoad %v4float %p
+ %33 = OpAccessChain %_ptr_Input_float %patchIn1 %int_1
+ %34 = OpLoad %float %33
+ %35 = OpVectorTimesScalar %v4float %27 %34
+ %37 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+ OpStore %37 %35
+ 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;
+ auto result = SinglePassRunToBinary<AnalyzeLiveInputPass>(
+ text, true, &live_inputs, &live_builtins);
+
+ auto itr0 = live_inputs.find(0);
+ auto itr1 = live_inputs.find(1);
+ auto itr2 = live_inputs.find(2);
+ auto itr3 = live_inputs.find(3);
+
+ // Expect live_inputs == {3}
+ EXPECT_TRUE(itr0 == live_inputs.end());
+ EXPECT_TRUE(itr1 == live_inputs.end());
+ EXPECT_TRUE(itr2 == live_inputs.end());
+ EXPECT_TRUE(itr3 != live_inputs.end());
+}
+
+TEST_F(AnalyzeLiveInputTest, FragMultipleLocationsF16) {
+ // Should report locations {2, 5}
+ //
+ // #version 450
+ //
+ // layout(location = 2) in Vertex
+ // {
+ // f16vec4 color0;
+ // f16vec4 color1;
+ // f16vec4 color2[3];
+ // } iVert;
+ //
+ // layout(location = 0) out f16vec4 oFragColor;
+ //
+ // void main()
+ // {
+ // oFragColor = iVert.color0 + iVert.color2[1];
+ // }
+ const std::string text = R"(
+ OpCapability Shader
+ OpCapability Float16
+ OpCapability StorageInputOutput16
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %oFragColor %iVert
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %oFragColor "oFragColor"
+ OpName %Vertex "Vertex"
+ OpMemberName %Vertex 0 "color0"
+ OpMemberName %Vertex 1 "color1"
+ OpMemberName %Vertex 2 "color2"
+ OpName %iVert "iVert"
+ OpDecorate %oFragColor Location 0
+ OpDecorate %Vertex Block
+ OpDecorate %iVert Location 2
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %half = OpTypeFloat 16
+ %v4half = OpTypeVector %half 4
+%_ptr_Output_v4half = OpTypePointer Output %v4half
+ %oFragColor = OpVariable %_ptr_Output_v4half Output
+ %uint = OpTypeInt 32 0
+ %uint_3 = OpConstant %uint 3
+%_arr_v4half_uint_3 = OpTypeArray %v4half %uint_3
+ %Vertex = OpTypeStruct %v4half %v4half %_arr_v4half_uint_3
+%_ptr_Input_Vertex = OpTypePointer Input %Vertex
+ %iVert = OpVariable %_ptr_Input_Vertex Input
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+%_ptr_Input_v4half = OpTypePointer Input %v4half
+ %int_2 = OpConstant %int 2
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %19 = OpAccessChain %_ptr_Input_v4half %iVert %int_0
+ %20 = OpLoad %v4half %19
+ %23 = OpAccessChain %_ptr_Input_v4half %iVert %int_2 %int_1
+ %24 = OpLoad %v4half %23
+ %25 = OpFAdd %v4half %20 %24
+ OpStore %oFragColor %25
+ 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;
+ auto result = SinglePassRunToBinary<AnalyzeLiveInputPass>(
+ text, true, &live_inputs, &live_builtins);
+
+ auto itr0 = live_inputs.find(0);
+ auto itr1 = live_inputs.find(1);
+ auto itr2 = live_inputs.find(2);
+ auto itr3 = live_inputs.find(3);
+ auto itr4 = live_inputs.find(4);
+ auto itr5 = live_inputs.find(5);
+ auto itr6 = live_inputs.find(6);
+
+ // Expect live_inputs == {2, 5}
+ EXPECT_TRUE(itr0 == live_inputs.end());
+ EXPECT_TRUE(itr1 == live_inputs.end());
+ EXPECT_TRUE(itr2 != live_inputs.end());
+ EXPECT_TRUE(itr3 == live_inputs.end());
+ EXPECT_TRUE(itr4 == live_inputs.end());
+ EXPECT_TRUE(itr5 != live_inputs.end());
+ EXPECT_TRUE(itr6 == live_inputs.end());
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/eliminate_dead_output_stores_test.cpp b/test/opt/eliminate_dead_output_stores_test.cpp
new file mode 100644
index 000000000..6e382c243
--- /dev/null
+++ b/test/opt/eliminate_dead_output_stores_test.cpp
@@ -0,0 +1,952 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+// Copyright (c) 2022 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <unordered_set>
+
+#include "gmock/gmock.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ElimDeadOutputStoresTest = PassTest<::testing::Test>;
+
+TEST_F(ElimDeadOutputStoresTest, VertMultipleLocations) {
+ // #version 450
+ //
+ // layout(location = 2) out Vertex
+ // {
+ // vec4 color0;
+ // vec4 color1;
+ // vec4 color2[3];
+ // } oVert;
+ //
+ // void main()
+ // {
+ // oVert.color0 = vec4(0.0,0.0,0.0,0.0);
+ // oVert.color1 = vec4(0.1,0.0,0.0,0.0);
+ // oVert.color2[0] = vec4(0.2,0.0,0.0,0.0);
+ // oVert.color2[1] = vec4(0.3,0.0,0.0,0.0);
+ // oVert.color2[2] = vec4(0.4,0.0,0.0,0.0);
+ // }
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %oVert
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %Vertex "Vertex"
+ OpMemberName %Vertex 0 "color0"
+ OpMemberName %Vertex 1 "color1"
+ OpMemberName %Vertex 2 "color2"
+ OpName %oVert "oVert"
+ OpDecorate %Vertex Block
+ OpDecorate %oVert Location 2
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %uint = OpTypeInt 32 0
+ %uint_3 = OpConstant %uint 3
+%_arr_v4float_uint_3 = OpTypeArray %v4float %uint_3
+ %Vertex = OpTypeStruct %v4float %v4float %_arr_v4float_uint_3
+%_ptr_Output_Vertex = OpTypePointer Output %Vertex
+ %oVert = OpVariable %_ptr_Output_Vertex Output
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %float_0 = OpConstant %float 0
+ %17 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %int_1 = OpConstant %int 1
+%float_0_100000001 = OpConstant %float 0.100000001
+ %22 = OpConstantComposite %v4float %float_0_100000001 %float_0 %float_0 %float_0
+ %int_2 = OpConstant %int 2
+%float_0_200000003 = OpConstant %float 0.200000003
+ %26 = OpConstantComposite %v4float %float_0_200000003 %float_0 %float_0 %float_0
+%float_0_300000012 = OpConstant %float 0.300000012
+ %29 = OpConstantComposite %v4float %float_0_300000012 %float_0 %float_0 %float_0
+%float_0_400000006 = OpConstant %float 0.400000006
+ %32 = OpConstantComposite %v4float %float_0_400000006 %float_0 %float_0 %float_0
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %19 = OpAccessChain %_ptr_Output_v4float %oVert %int_0
+ OpStore %19 %17
+;CHECK: OpStore %19 %17
+ %23 = OpAccessChain %_ptr_Output_v4float %oVert %int_1
+ OpStore %23 %22
+;CHECK-NOT: OpStore %23 %22
+ %27 = OpAccessChain %_ptr_Output_v4float %oVert %int_2 %int_0
+ OpStore %27 %26
+;CHECK-NOT: OpStore %27 %26
+ %30 = OpAccessChain %_ptr_Output_v4float %oVert %int_2 %int_1
+ OpStore %30 %29
+;CHECK: OpStore %30 %29
+ %33 = OpAccessChain %_ptr_Output_v4float %oVert %int_2 %int_2
+ OpStore %33 %32
+;CHECK-NOT: OpStore %33 %32
+ 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;
+ live_inputs.insert(2);
+ live_inputs.insert(5);
+ SinglePassRunAndMatch<EliminateDeadOutputStoresPass>(text, true, &live_inputs,
+ &live_builtins);
+}
+
+TEST_F(ElimDeadOutputStoresTest, VertMatrix) {
+ // #version 450
+ //
+ // layout(location = 2) out Vertex
+ // {
+ // vec4 color0;
+ // vec4 color1;
+ // mat4 color2;
+ // mat4 color3;
+ // mat4 color4;
+ // } oVert;
+ //
+ // void main()
+ // {
+ // oVert.color0 = vec4(0.0,0.0,0.0,0.0);
+ // oVert.color1 = vec4(0.1,0.0,0.0,0.0);
+ // oVert.color2[2] = vec4(0.2,0.0,0.0,0.0);
+ // oVert.color3[1] = vec4(0.3,0.0,0.0,0.0);
+ // oVert.color4[0] = vec4(0.4,0.0,0.0,0.0);
+ // }
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %oVert
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %Vertex "Vertex"
+ OpMemberName %Vertex 0 "color0"
+ OpMemberName %Vertex 1 "color1"
+ OpMemberName %Vertex 2 "color2"
+ OpMemberName %Vertex 3 "color3"
+ OpMemberName %Vertex 4 "color4"
+ OpName %oVert "oVert"
+ OpDecorate %Vertex Block
+ OpDecorate %oVert Location 2
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%mat4v4float = OpTypeMatrix %v4float 4
+ %Vertex = OpTypeStruct %v4float %v4float %mat4v4float %mat4v4float %mat4v4float
+%_ptr_Output_Vertex = OpTypePointer Output %Vertex
+ %oVert = OpVariable %_ptr_Output_Vertex Output
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %float_0 = OpConstant %float 0
+ %15 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %int_1 = OpConstant %int 1
+%float_0_100000001 = OpConstant %float 0.100000001
+ %20 = OpConstantComposite %v4float %float_0_100000001 %float_0 %float_0 %float_0
+ %int_2 = OpConstant %int 2
+%float_0_200000003 = OpConstant %float 0.200000003
+ %24 = OpConstantComposite %v4float %float_0_200000003 %float_0 %float_0 %float_0
+ %int_3 = OpConstant %int 3
+%float_0_300000012 = OpConstant %float 0.300000012
+ %28 = OpConstantComposite %v4float %float_0_300000012 %float_0 %float_0 %float_0
+ %int_4 = OpConstant %int 4
+%float_0_400000006 = OpConstant %float 0.400000006
+ %32 = OpConstantComposite %v4float %float_0_400000006 %float_0 %float_0 %float_0
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %17 = OpAccessChain %_ptr_Output_v4float %oVert %int_0
+ OpStore %17 %15
+; CHECK: OpStore %17 %15
+ %21 = OpAccessChain %_ptr_Output_v4float %oVert %int_1
+ OpStore %21 %20
+; CHECK-NOT: OpStore %21 %20
+ %25 = OpAccessChain %_ptr_Output_v4float %oVert %int_2 %int_2
+ OpStore %25 %24
+; CHECK-NOT: OpStore %25 %24
+ %29 = OpAccessChain %_ptr_Output_v4float %oVert %int_3 %int_1
+ OpStore %29 %28
+; CHECK: OpStore %29 %28
+ %33 = OpAccessChain %_ptr_Output_v4float %oVert %int_4 %int_0
+ OpStore %33 %32
+; CHECK-NOT: OpStore %33 %32
+ 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;
+ live_inputs.insert(2);
+ live_inputs.insert(8);
+ live_inputs.insert(9);
+ live_inputs.insert(10);
+ live_inputs.insert(11);
+ SinglePassRunAndMatch<EliminateDeadOutputStoresPass>(text, true, &live_inputs,
+ &live_builtins);
+}
+
+TEST_F(ElimDeadOutputStoresTest, VertMemberLocs) {
+ // #version 450
+ //
+ // out Vertex
+ // {
+ // layout (location = 1) vec4 Cd;
+ // layout (location = 0) vec2 uv;
+ // } oVert;
+ //
+ // layout (location = 0) in vec3 P;
+ //
+ // void main()
+ // {
+ // oVert.uv = vec2(0.1, 0.7);
+ // oVert.Cd = vec4(1, 0.5, 0, 1);
+ // gl_Position = vec4(P, 1);
+ // }
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %oVert %_ %P
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %Vertex "Vertex"
+ OpMemberName %Vertex 0 "Cd"
+ OpMemberName %Vertex 1 "uv"
+ OpName %oVert "oVert"
+ 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 %_ ""
+ OpName %P "P"
+ OpMemberDecorate %Vertex 0 Location 1
+ OpMemberDecorate %Vertex 1 Location 0
+ OpDecorate %Vertex Block
+ 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
+ OpDecorate %P Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %v2float = OpTypeVector %float 2
+ %Vertex = OpTypeStruct %v4float %v2float
+%_ptr_Output_Vertex = OpTypePointer Output %Vertex
+ %oVert = OpVariable %_ptr_Output_Vertex Output
+ %int = OpTypeInt 32 1
+ %int_1 = OpConstant %int 1
+%float_0_100000001 = OpConstant %float 0.100000001
+%float_0_699999988 = OpConstant %float 0.699999988
+ %16 = OpConstantComposite %v2float %float_0_100000001 %float_0_699999988
+%_ptr_Output_v2float = OpTypePointer Output %v2float
+ %int_0 = OpConstant %int 0
+ %float_1 = OpConstant %float 1
+ %float_0_5 = OpConstant %float 0.5
+ %float_0 = OpConstant %float 0
+ %23 = OpConstantComposite %v4float %float_1 %float_0_5 %float_0 %float_1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %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
+ %v3float = OpTypeVector %float 3
+%_ptr_Input_v3float = OpTypePointer Input %v3float
+ %P = OpVariable %_ptr_Input_v3float Input
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %18 = OpAccessChain %_ptr_Output_v2float %oVert %int_1
+ OpStore %18 %16
+; CHECK-NOT: OpStore %18 %16
+ %25 = OpAccessChain %_ptr_Output_v4float %oVert %int_0
+ OpStore %25 %23
+; CHECK: OpStore %25 %23
+ %35 = OpLoad %v3float %P
+ %36 = OpCompositeExtract %float %35 0
+ %37 = OpCompositeExtract %float %35 1
+ %38 = OpCompositeExtract %float %35 2
+ %39 = OpCompositeConstruct %v4float %36 %37 %38 %float_1
+ %40 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+ OpStore %40 %39
+ 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;
+ live_inputs.insert(1);
+ SinglePassRunAndMatch<EliminateDeadOutputStoresPass>(text, true, &live_inputs,
+ &live_builtins);
+}
+
+TEST_F(ElimDeadOutputStoresTest, ArrayedOutput) {
+ // Tests elimination of arrayed output as seen in Tesc shaders.
+ //
+ // #version 450
+ //
+ // layout (vertices = 4) out;
+ //
+ // layout (location = 0) in vec3 N[];
+ // layout (location = 1) in vec3 P[];
+ //
+ // layout (location = 5) out Vertex
+ // {
+ // vec4 c;
+ // vec3 n;
+ // vec3 f[10];
+ // } oVert[];
+ //
+ // void main()
+ // {
+ // oVert[gl_InvocationID].c = vec4(1, 0, 0, 1);
+ // oVert[gl_InvocationID].n = N[gl_InvocationID];
+ // oVert[gl_InvocationID].f[3] = vec3(0, 1, 0);
+ // vec4 worldSpacePos = vec4(P[gl_InvocationID], 1);
+ // gl_out[gl_InvocationID].gl_Position = worldSpacePos;
+ // }
+ const std::string text = R"(
+ OpCapability Tessellation
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint TessellationControl %main "main" %oVert %gl_InvocationID %N %P %gl_out
+ OpExecutionMode %main OutputVertices 4
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %Vertex "Vertex"
+ OpMemberName %Vertex 0 "c"
+ OpMemberName %Vertex 1 "n"
+ OpMemberName %Vertex 2 "f"
+ OpName %oVert "oVert"
+ OpName %gl_InvocationID "gl_InvocationID"
+ OpName %N "N"
+ OpName %P "P"
+ 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 %gl_out "gl_out"
+ OpDecorate %Vertex Block
+ OpDecorate %oVert Location 5
+ OpDecorate %gl_InvocationID BuiltIn InvocationId
+ OpDecorate %N Location 0
+ OpDecorate %P Location 1
+ 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
+ %v3float = OpTypeVector %float 3
+ %uint = OpTypeInt 32 0
+ %uint_10 = OpConstant %uint 10
+%_arr_v3float_uint_10 = OpTypeArray %v3float %uint_10
+ %Vertex = OpTypeStruct %v4float %v3float %_arr_v3float_uint_10
+ %uint_4 = OpConstant %uint 4
+%_arr_Vertex_uint_4 = OpTypeArray %Vertex %uint_4
+%_ptr_Output__arr_Vertex_uint_4 = OpTypePointer Output %_arr_Vertex_uint_4
+ %oVert = OpVariable %_ptr_Output__arr_Vertex_uint_4 Output
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_InvocationID = OpVariable %_ptr_Input_int Input
+ %int_0 = OpConstant %int 0
+ %float_1 = OpConstant %float 1
+ %float_0 = OpConstant %float 0
+ %24 = OpConstantComposite %v4float %float_1 %float_0 %float_0 %float_1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %int_1 = OpConstant %int 1
+ %uint_32 = OpConstant %uint 32
+%_arr_v3float_uint_32 = OpTypeArray %v3float %uint_32
+%_ptr_Input__arr_v3float_uint_32 = OpTypePointer Input %_arr_v3float_uint_32
+ %N = OpVariable %_ptr_Input__arr_v3float_uint_32 Input
+%_ptr_Input_v3float = OpTypePointer Input %v3float
+%_ptr_Output_v3float = OpTypePointer Output %v3float
+ %int_2 = OpConstant %int 2
+ %int_3 = OpConstant %int 3
+ %42 = OpConstantComposite %v3float %float_0 %float_1 %float_0
+ %P = OpVariable %_ptr_Input__arr_v3float_uint_32 Input
+ %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
+%_arr_gl_PerVertex_uint_4 = OpTypeArray %gl_PerVertex %uint_4
+%_ptr_Output__arr_gl_PerVertex_uint_4 = OpTypePointer Output %_arr_gl_PerVertex_uint_4
+ %gl_out = OpVariable %_ptr_Output__arr_gl_PerVertex_uint_4 Output
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %20 = OpLoad %int %gl_InvocationID
+ %26 = OpAccessChain %_ptr_Output_v4float %oVert %20 %int_0
+ OpStore %26 %24
+; CHECK: OpStore %26 %24
+ %35 = OpAccessChain %_ptr_Input_v3float %N %20
+ %36 = OpLoad %v3float %35
+ %38 = OpAccessChain %_ptr_Output_v3float %oVert %20 %int_1
+ OpStore %38 %36
+; CHECK-NOT: OpStore %38 %36
+ %43 = OpAccessChain %_ptr_Output_v3float %oVert %20 %int_2 %int_3
+ OpStore %43 %42
+; CHECK: OpStore %43 %42
+ %48 = OpAccessChain %_ptr_Input_v3float %P %20
+ %49 = OpLoad %v3float %48
+ %50 = OpCompositeExtract %float %49 0
+ %51 = OpCompositeExtract %float %49 1
+ %52 = OpCompositeExtract %float %49 2
+ %53 = OpCompositeConstruct %v4float %50 %51 %52 %float_1
+ %62 = OpAccessChain %_ptr_Output_v4float %gl_out %20 %int_0
+ OpStore %62 %53
+ 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;
+ live_inputs.insert(5);
+ live_inputs.insert(10);
+ SinglePassRunAndMatch<EliminateDeadOutputStoresPass>(text, true, &live_inputs,
+ &live_builtins);
+}
+
+TEST_F(ElimDeadOutputStoresTest, ArrayedOutputMemberLocs) {
+ // Tests elimination of member location with arrayed output as seen in
+ // Tesc shaders.
+ //
+ // #version 450
+ //
+ // layout (vertices = 4) out;
+ //
+ // layout (location = 0) in vec3 N[];
+ // layout (location = 1) in vec3 P[];
+ //
+ // out Vertex
+ // {
+ // layout (location = 1) vec4 c;
+ // layout (location = 3) vec3 n;
+ // layout (location = 5) vec3 f[10];
+ // } oVert[];
+ //
+ // void main()
+ // {
+ // oVert[gl_InvocationID].c = vec4(1, 0, 0, 1);
+ // oVert[gl_InvocationID].n = N[gl_InvocationID];
+ // oVert[gl_InvocationID].f[3] = vec3(0, 1, 0);
+ // vec4 worldSpacePos = vec4(P[gl_InvocationID], 1);
+ // gl_out[gl_InvocationID].gl_Position = worldSpacePos;
+ // }
+ const std::string text = R"(
+ OpCapability Tessellation
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint TessellationControl %main "main" %oVert %gl_InvocationID %N %P %gl_out
+ OpExecutionMode %main OutputVertices 4
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %Vertex "Vertex"
+ OpMemberName %Vertex 0 "c"
+ OpMemberName %Vertex 1 "n"
+ OpMemberName %Vertex 2 "f"
+ OpName %oVert "oVert"
+ OpName %gl_InvocationID "gl_InvocationID"
+ OpName %N "N"
+ OpName %P "P"
+ 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 %gl_out "gl_out"
+ OpMemberDecorate %Vertex 0 Location 1
+ OpMemberDecorate %Vertex 1 Location 3
+ OpMemberDecorate %Vertex 2 Location 5
+ OpDecorate %Vertex Block
+ OpDecorate %gl_InvocationID BuiltIn InvocationId
+ OpDecorate %N Location 0
+ OpDecorate %P Location 1
+ 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
+ %v3float = OpTypeVector %float 3
+ %uint = OpTypeInt 32 0
+ %uint_10 = OpConstant %uint 10
+%_arr_v3float_uint_10 = OpTypeArray %v3float %uint_10
+ %Vertex = OpTypeStruct %v4float %v3float %_arr_v3float_uint_10
+ %uint_4 = OpConstant %uint 4
+%_arr_Vertex_uint_4 = OpTypeArray %Vertex %uint_4
+%_ptr_Output__arr_Vertex_uint_4 = OpTypePointer Output %_arr_Vertex_uint_4
+ %oVert = OpVariable %_ptr_Output__arr_Vertex_uint_4 Output
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_InvocationID = OpVariable %_ptr_Input_int Input
+ %int_0 = OpConstant %int 0
+ %float_1 = OpConstant %float 1
+ %float_0 = OpConstant %float 0
+ %24 = OpConstantComposite %v4float %float_1 %float_0 %float_0 %float_1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %int_1 = OpConstant %int 1
+ %uint_32 = OpConstant %uint 32
+%_arr_v3float_uint_32 = OpTypeArray %v3float %uint_32
+%_ptr_Input__arr_v3float_uint_32 = OpTypePointer Input %_arr_v3float_uint_32
+ %N = OpVariable %_ptr_Input__arr_v3float_uint_32 Input
+%_ptr_Input_v3float = OpTypePointer Input %v3float
+%_ptr_Output_v3float = OpTypePointer Output %v3float
+ %int_2 = OpConstant %int 2
+ %int_3 = OpConstant %int 3
+ %42 = OpConstantComposite %v3float %float_0 %float_1 %float_0
+ %P = OpVariable %_ptr_Input__arr_v3float_uint_32 Input
+ %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
+%_arr_gl_PerVertex_uint_4 = OpTypeArray %gl_PerVertex %uint_4
+%_ptr_Output__arr_gl_PerVertex_uint_4 = OpTypePointer Output %_arr_gl_PerVertex_uint_4
+ %gl_out = OpVariable %_ptr_Output__arr_gl_PerVertex_uint_4 Output
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %20 = OpLoad %int %gl_InvocationID
+ %26 = OpAccessChain %_ptr_Output_v4float %oVert %20 %int_0
+ OpStore %26 %24
+;CHECK: OpStore %26 %24
+ %35 = OpAccessChain %_ptr_Input_v3float %N %20
+ %36 = OpLoad %v3float %35
+ %38 = OpAccessChain %_ptr_Output_v3float %oVert %20 %int_1
+ OpStore %38 %36
+;CHECK-NOT: OpStore %38 %36
+ %43 = OpAccessChain %_ptr_Output_v3float %oVert %20 %int_2 %int_3
+ OpStore %43 %42
+;CHECK: OpStore %43 %42
+ %48 = OpAccessChain %_ptr_Input_v3float %P %20
+ %49 = OpLoad %v3float %48
+ %50 = OpCompositeExtract %float %49 0
+ %51 = OpCompositeExtract %float %49 1
+ %52 = OpCompositeExtract %float %49 2
+ %53 = OpCompositeConstruct %v4float %50 %51 %52 %float_1
+ %62 = OpAccessChain %_ptr_Output_v4float %gl_out %20 %int_0
+ OpStore %62 %53
+ 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;
+ live_inputs.insert(1);
+ live_inputs.insert(8);
+ SinglePassRunAndMatch<EliminateDeadOutputStoresPass>(text, true, &live_inputs,
+ &live_builtins);
+}
+
+TEST_F(ElimDeadOutputStoresTest, ScalarBuiltins) {
+ // Tests elimination of scalar builtins as seen in vert shaders.
+ //
+ // #version 460
+ //
+ // layout (location = 0) in vec3 P;
+ //
+ // void main()
+ // {
+ // gl_Position = vec4(P, 1.0);
+ // gl_PointSize = 1.0;
+ // }
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %_ %P
+ OpSource GLSL 460
+ OpName %main "main"
+ 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 %_ ""
+ OpName %P "P"
+ 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
+ OpDecorate %P Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %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
+ %v3float = OpTypeVector %float 3
+%_ptr_Input_v3float = OpTypePointer Input %v3float
+ %P = OpVariable %_ptr_Input_v3float Input
+ %float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %19 = OpLoad %v3float %P
+ %21 = OpCompositeExtract %float %19 0
+ %22 = OpCompositeExtract %float %19 1
+ %23 = OpCompositeExtract %float %19 2
+ %24 = OpCompositeConstruct %v4float %21 %22 %23 %float_1
+ %26 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+ OpStore %26 %24
+;CHECK: OpStore %26 %24
+ %29 = OpAccessChain %_ptr_Output_float %_ %int_1
+ OpStore %29 %float_1
+;CHECK-NOT: OpStore %29 %float_1
+ 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;
+ // Omit SpvBuiltInPointSize
+ live_builtins.insert(SpvBuiltInClipDistance);
+ live_builtins.insert(SpvBuiltInCullDistance);
+ SinglePassRunAndMatch<EliminateDeadOutputStoresPass>(text, true, &live_inputs,
+ &live_builtins);
+}
+
+TEST_F(ElimDeadOutputStoresTest, ArrayedBuiltins) {
+ // Tests elimination of arrayed builtins as seen in geom, tesc, and tese
+ // shaders.
+ //
+ // #version 460
+ //
+ // layout(triangle_strip, max_vertices = 3) out;
+ // layout(triangles) in;
+ //
+ // void main()
+ // {
+ // for (int i = 0; i < 3; i++)
+ // {
+ // gl_Position = gl_in[i].gl_Position;
+ // gl_PointSize = gl_in[i].gl_PointSize;
+ //
+ // EmitVertex();
+ // }
+ //
+ // EndPrimitive();
+ // }
+ const std::string text = R"(
+ OpCapability Geometry
+ OpCapability GeometryPointSize
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Geometry %main "main" %_ %gl_in
+ OpExecutionMode %main Triangles
+ OpExecutionMode %main Invocations 1
+ OpExecutionMode %main OutputTriangleStrip
+ OpExecutionMode %main OutputVertices 3
+ OpSource GLSL 460
+ OpName %main "main"
+ OpName %i "i"
+ 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 %_ ""
+ OpName %gl_PerVertex_0 "gl_PerVertex"
+ OpMemberName %gl_PerVertex_0 0 "gl_Position"
+ OpMemberName %gl_PerVertex_0 1 "gl_PointSize"
+ OpMemberName %gl_PerVertex_0 2 "gl_ClipDistance"
+ OpMemberName %gl_PerVertex_0 3 "gl_CullDistance"
+ OpName %gl_in "gl_in"
+ 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
+ OpMemberDecorate %gl_PerVertex_0 0 BuiltIn Position
+ OpMemberDecorate %gl_PerVertex_0 1 BuiltIn PointSize
+ OpMemberDecorate %gl_PerVertex_0 2 BuiltIn ClipDistance
+ OpMemberDecorate %gl_PerVertex_0 3 BuiltIn CullDistance
+ OpDecorate %gl_PerVertex_0 Block
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %int_3 = OpConstant %int 3
+ %bool = OpTypeBool
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %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
+%gl_PerVertex_0 = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+ %uint_3 = OpConstant %uint 3
+%_arr_gl_PerVertex_0_uint_3 = OpTypeArray %gl_PerVertex_0 %uint_3
+%_ptr_Input__arr_gl_PerVertex_0_uint_3 = OpTypePointer Input %_arr_gl_PerVertex_0_uint_3
+ %gl_in = OpVariable %_ptr_Input__arr_gl_PerVertex_0_uint_3 Input
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %int_1 = OpConstant %int 1
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Output_float = OpTypePointer Output %float
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %i = OpVariable %_ptr_Function_int Function
+ OpStore %i %int_0
+ OpBranch %10
+ %10 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %15 = OpLoad %int %i
+ %18 = OpSLessThan %bool %15 %int_3
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %32 = OpLoad %int %i
+ %34 = OpAccessChain %_ptr_Input_v4float %gl_in %32 %int_0
+ %35 = OpLoad %v4float %34
+ %37 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+ OpStore %37 %35
+;CHECK: OpStore %37 %35
+ %39 = OpLoad %int %i
+ %41 = OpAccessChain %_ptr_Input_float %gl_in %39 %int_1
+ %42 = OpLoad %float %41
+ %44 = OpAccessChain %_ptr_Output_float %_ %int_1
+ OpStore %44 %42
+;CHECK-NOT: OpStore %44 %42
+ OpEmitVertex
+ OpBranch %13
+ %13 = OpLabel
+ %45 = OpLoad %int %i
+ %46 = OpIAdd %int %45 %int_1
+ OpStore %i %46
+ OpBranch %10
+ %12 = OpLabel
+ OpEndPrimitive
+ 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;
+ // Omit SpvBuiltInPointSize
+ live_builtins.insert(SpvBuiltInClipDistance);
+ live_builtins.insert(SpvBuiltInCullDistance);
+ SinglePassRunAndMatch<EliminateDeadOutputStoresPass>(text, true, &live_inputs,
+ &live_builtins);
+}
+
+TEST_F(ElimDeadOutputStoresTest, ArrayedOutputPatchLocs) {
+ // Tests elimination of location with arrayed patch output as seen in
+ // Tesc shaders.
+ //
+ // #version 450 core
+ //
+ // layout(vertices = 4) out;
+ //
+ // layout(location=0) patch out float patchOut0[2];
+ // layout(location=2) patch out float patchOut1[2];
+ //
+ // void main()
+ // {
+ // patchOut0[1] = 0.0; // Dead loc 1
+ // patchOut1[1] = 1.0; // Live loc 3
+ // }
+ const std::string text = R"(
+ OpCapability Tessellation
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint TessellationControl %main "main" %patchOut0 %patchOut1
+ OpExecutionMode %main OutputVertices 4
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %patchOut0 "patchOut0"
+ OpName %patchOut1 "patchOut1"
+ OpDecorate %patchOut0 Patch
+ OpDecorate %patchOut0 Location 0
+ OpDecorate %patchOut1 Patch
+ OpDecorate %patchOut1 Location 2
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+%_arr_float_uint_2 = OpTypeArray %float %uint_2
+%_ptr_Output__arr_float_uint_2 = OpTypePointer Output %_arr_float_uint_2
+ %patchOut0 = OpVariable %_ptr_Output__arr_float_uint_2 Output
+ %int = OpTypeInt 32 1
+ %int_1 = OpConstant %int 1
+ %float_0 = OpConstant %float 0
+%_ptr_Output_float = OpTypePointer Output %float
+ %patchOut1 = OpVariable %_ptr_Output__arr_float_uint_2 Output
+ %float_1 = OpConstant %float 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %16 = OpAccessChain %_ptr_Output_float %patchOut0 %int_1
+ OpStore %16 %float_0
+;CHECK-NOT: OpStore %16 %float_0
+ %19 = OpAccessChain %_ptr_Output_float %patchOut1 %int_1
+ OpStore %19 %float_1
+;CHECK: OpStore %19 %float_1
+ 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;
+ live_inputs.insert(3);
+ SinglePassRunAndMatch<EliminateDeadOutputStoresPass>(text, true, &live_inputs,
+ &live_builtins);
+}
+
+TEST_F(ElimDeadOutputStoresTest, VertMultipleLocationsF16) {
+ // #version 450
+ //
+ // layout(location = 2) out Vertex
+ // {
+ // f16vec4 color0;
+ // f16vec4 color1;
+ // f16vec4 color2[3];
+ // } oVert;
+ //
+ // void main()
+ // {
+ // oVert.color0 = f16vec4(0.0,0.0,0.0,0.0);
+ // oVert.color1 = f16vec4(0.1,0.0,0.0,0.0);
+ // oVert.color2[0] = f16vec4(0.2,0.0,0.0,0.0);
+ // oVert.color2[1] = f16vec4(0.3,0.0,0.0,0.0);
+ // oVert.color2[2] = f16vec4(0.4,0.0,0.0,0.0);
+ // }
+ const std::string text = R"(
+ OpCapability Shader
+ OpCapability Float16
+ OpCapability StorageInputOutput16
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %oVert
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %Vertex "Vertex"
+ OpMemberName %Vertex 0 "color0"
+ OpMemberName %Vertex 1 "color1"
+ OpMemberName %Vertex 2 "color2"
+ OpName %oVert "oVert"
+ OpDecorate %Vertex Block
+ OpDecorate %oVert Location 2
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %half = OpTypeFloat 32
+ %v4half = OpTypeVector %half 4
+ %uint = OpTypeInt 32 0
+ %uint_3 = OpConstant %uint 3
+%_arr_v4half_uint_3 = OpTypeArray %v4half %uint_3
+ %Vertex = OpTypeStruct %v4half %v4half %_arr_v4half_uint_3
+%_ptr_Output_Vertex = OpTypePointer Output %Vertex
+ %oVert = OpVariable %_ptr_Output_Vertex Output
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %half_0 = OpConstant %half 0
+ %17 = OpConstantComposite %v4half %half_0 %half_0 %half_0 %half_0
+%_ptr_Output_v4half = OpTypePointer Output %v4half
+ %int_1 = OpConstant %int 1
+%half_0_100000001 = OpConstant %half 0.100000001
+ %22 = OpConstantComposite %v4half %half_0_100000001 %half_0 %half_0 %half_0
+ %int_2 = OpConstant %int 2
+%half_0_200000003 = OpConstant %half 0.200000003
+ %26 = OpConstantComposite %v4half %half_0_200000003 %half_0 %half_0 %half_0
+%half_0_300000012 = OpConstant %half 0.300000012
+ %29 = OpConstantComposite %v4half %half_0_300000012 %half_0 %half_0 %half_0
+%half_0_400000006 = OpConstant %half 0.400000006
+ %32 = OpConstantComposite %v4half %half_0_400000006 %half_0 %half_0 %half_0
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %19 = OpAccessChain %_ptr_Output_v4half %oVert %int_0
+ OpStore %19 %17
+;CHECK: OpStore %19 %17
+ %23 = OpAccessChain %_ptr_Output_v4half %oVert %int_1
+ OpStore %23 %22
+;CHECK-NOT: OpStore %23 %22
+ %27 = OpAccessChain %_ptr_Output_v4half %oVert %int_2 %int_0
+ OpStore %27 %26
+;CHECK-NOT: OpStore %27 %26
+ %30 = OpAccessChain %_ptr_Output_v4half %oVert %int_2 %int_1
+ OpStore %30 %29
+;CHECK: OpStore %30 %29
+ %33 = OpAccessChain %_ptr_Output_v4half %oVert %int_2 %int_2
+ OpStore %33 %32
+;CHECK-NOT: OpStore %33 %32
+ 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;
+ live_inputs.insert(2);
+ live_inputs.insert(5);
+ SinglePassRunAndMatch<EliminateDeadOutputStoresPass>(text, true, &live_inputs,
+ &live_builtins);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools