diff --git a/Android.mk b/Android.mk index e28144c36..902c6825d 100644 --- a/Android.mk +++ b/Android.mk @@ -62,9 +62,11 @@ SPVTOOLS_OPT_SRC_FILES := \ source/opt/cfg_cleanup_pass.cpp \ source/opt/ccp_pass.cpp \ source/opt/compact_ids_pass.cpp \ + source/opt/composite.cpp \ source/opt/common_uniform_elim_pass.cpp \ source/opt/constants.cpp \ source/opt/dead_branch_elim_pass.cpp \ + source/opt/dead_insert_elim_pass.cpp \ source/opt/dead_variable_elimination.cpp \ source/opt/decoration_manager.cpp \ source/opt/def_use_manager.cpp \ diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp index b1e2cfe06..a8d8da0cc 100644 --- a/include/spirv-tools/optimizer.hpp +++ b/include/spirv-tools/optimizer.hpp @@ -370,6 +370,21 @@ Optimizer::PassToken CreateLocalSingleStoreElimPass(); // converted to inserts and extracts and local loads and stores are eliminated. Optimizer::PassToken CreateInsertExtractElimPass(); +// Creates a dead insert elimination pass. +// This pass processes each entry point function in the module, searching for +// unreferenced inserts into composite types. These are most often unused +// stores to vector components. They are unused because they are never +// referenced, or because there is another insert to the same component between +// the insert and the reference. After removing the inserts, dead code +// elimination is attempted on the inserted values. +// +// This pass performs best after access chains are converted to inserts and +// extracts and local loads and stores are eliminated. While executing this +// pass can be advantageous on its own, it is also advantageous to execute +// this pass after CreateInsertExtractPass() as it will remove any unused +// inserts created by that pass. +Optimizer::PassToken CreateDeadInsertElimPass(); + // Creates a pass to consolidate uniform references. // For each entry point function in the module, first change all constant index // access chain loads into equivalent composite extracts. Then consolidate diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt index 8dfe229a1..321d3f8a1 100644 --- a/source/opt/CMakeLists.txt +++ b/source/opt/CMakeLists.txt @@ -21,8 +21,10 @@ add_library(SPIRV-Tools-opt cfg.h common_uniform_elim_pass.h compact_ids_pass.h + composite.h constants.h dead_branch_elim_pass.h + dead_insert_elim_pass.h dead_variable_elimination.h decoration_manager.h def_use_manager.h @@ -83,8 +85,10 @@ add_library(SPIRV-Tools-opt cfg.cpp common_uniform_elim_pass.cpp compact_ids_pass.cpp + composite.cpp constants.cpp dead_branch_elim_pass.cpp + dead_insert_elim_pass.cpp dead_variable_elimination.cpp decoration_manager.cpp def_use_manager.cpp diff --git a/source/opt/composite.cpp b/source/opt/composite.cpp new file mode 100644 index 000000000..1fbb71f69 --- /dev/null +++ b/source/opt/composite.cpp @@ -0,0 +1,52 @@ +// Copyright (c) 2018 The Khronos Group Inc. +// Copyright (c) 2018 Valve Corporation +// Copyright (c) 2018 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 "composite.h" + +#include "ir_context.h" +#include "iterator.h" +#include "spirv/1.2/GLSL.std.450.h" + +#include + +namespace spvtools { +namespace opt { + +bool ExtInsMatch(const std::vector& extIndices, + const ir::Instruction* insInst, const uint32_t extOffset) { + uint32_t numIndices = static_cast(extIndices.size()) - extOffset; + if (numIndices != insInst->NumInOperands() - 2) return false; + for (uint32_t i = 0; i < numIndices; ++i) + if (extIndices[i + extOffset] != insInst->GetSingleWordInOperand(i + 2)) + return false; + return true; +} + +bool ExtInsConflict(const std::vector& extIndices, + const ir::Instruction* insInst, const uint32_t extOffset) { + if (extIndices.size() - extOffset == insInst->NumInOperands() - 2) + return false; + uint32_t extNumIndices = static_cast(extIndices.size()) - extOffset; + uint32_t insNumIndices = insInst->NumInOperands() - 2; + uint32_t numIndices = std::min(extNumIndices, insNumIndices); + for (uint32_t i = 0; i < numIndices; ++i) + if (extIndices[i + extOffset] != insInst->GetSingleWordInOperand(i + 2)) + return false; + return true; +} + +} // namespace opt +} // namespace spvtools diff --git a/source/opt/composite.h b/source/opt/composite.h new file mode 100644 index 000000000..2153c626c --- /dev/null +++ b/source/opt/composite.h @@ -0,0 +1,50 @@ +// Copyright (c) 2018 The Khronos Group Inc. +// Copyright (c) 2018 Valve Corporation +// Copyright (c) 2018 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 LIBSPIRV_UTIL_COMPOSITE_PASS_H_ +#define LIBSPIRV_UTIL_COMPOSITE_PASS_H_ + +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "ir_context.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// Return true if the extract indices in |extIndices| starting at |extOffset| +// match indices of insert |insInst|. +bool ExtInsMatch(const std::vector& extIndices, + const ir::Instruction* insInst, const uint32_t extOffset); + +// Return true if indices in |extIndices| starting at |extOffset| and +// indices of insert |insInst| conflict, specifically, if the insert +// changes bits specified by the extract, but changes either more bits +// or less bits than the extract specifies, meaning the exact value being +// inserted cannot be used to replace the extract. +bool ExtInsConflict(const std::vector& extIndices, + const ir::Instruction* insInst, const uint32_t extOffset); + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_UTIL_COMPOSITE_PASS_H_ diff --git a/source/opt/dead_insert_elim_pass.cpp b/source/opt/dead_insert_elim_pass.cpp new file mode 100644 index 000000000..b99a3fe76 --- /dev/null +++ b/source/opt/dead_insert_elim_pass.cpp @@ -0,0 +1,310 @@ +// Copyright (c) 2018 The Khronos Group Inc. +// Copyright (c) 2018 Valve Corporation +// Copyright (c) 2018 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 "dead_insert_elim_pass.h" + +#include "composite.h" +#include "ir_context.h" +#include "iterator.h" +#include "spirv/1.2/GLSL.std.450.h" + +#include + +namespace spvtools { +namespace opt { + +namespace { + +const uint32_t kTypeVectorCountInIdx = 1; +const uint32_t kTypeMatrixCountInIdx = 1; +const uint32_t kTypeArrayLengthIdInIdx = 1; +const uint32_t kTypeIntWidthInIdx = 0; +const uint32_t kConstantValueInIdx = 0; +const uint32_t kInsertObjectIdInIdx = 0; +const uint32_t kInsertCompositeIdInIdx = 1; + +} // anonymous namespace + +uint32_t DeadInsertElimPass::NumComponents(ir::Instruction* typeInst) { + switch (typeInst->opcode()) { + case SpvOpTypeVector: { + return typeInst->GetSingleWordInOperand(kTypeVectorCountInIdx); + } break; + case SpvOpTypeMatrix: { + return typeInst->GetSingleWordInOperand(kTypeMatrixCountInIdx); + } break; + case SpvOpTypeArray: { + uint32_t lenId = + typeInst->GetSingleWordInOperand(kTypeArrayLengthIdInIdx); + ir::Instruction* lenInst = get_def_use_mgr()->GetDef(lenId); + if (lenInst->opcode() != SpvOpConstant) return 0; + uint32_t lenTypeId = lenInst->type_id(); + ir::Instruction* lenTypeInst = get_def_use_mgr()->GetDef(lenTypeId); + // TODO(greg-lunarg): Support non-32-bit array length + if (lenTypeInst->GetSingleWordInOperand(kTypeIntWidthInIdx) != 32) + return 0; + return lenInst->GetSingleWordInOperand(kConstantValueInIdx); + } break; + case SpvOpTypeStruct: { + return typeInst->NumInOperands(); + } break; + default: { return 0; } break; + } +} + +void DeadInsertElimPass::MarkInsertChain(ir::Instruction* insertChain, + std::vector* pExtIndices, + uint32_t extOffset) { + // Not currently optimizing array inserts. + ir::Instruction* typeInst = get_def_use_mgr()->GetDef(insertChain->type_id()); + if (typeInst->opcode() == SpvOpTypeArray) return; + // Insert chains are only composed of inserts and phis + if (insertChain->opcode() != SpvOpCompositeInsert && + insertChain->opcode() != SpvOpPhi) + return; + // If extract indices are empty, mark all subcomponents if type + // is constant length. + if (pExtIndices == nullptr) { + uint32_t cnum = NumComponents(typeInst); + if (cnum > 0) { + std::vector extIndices; + for (uint32_t i = 0; i < cnum; i++) { + extIndices.clear(); + extIndices.push_back(i); + MarkInsertChain(insertChain, &extIndices, 0); + } + return; + } + } + ir::Instruction* insInst = insertChain; + while (insInst->opcode() == SpvOpCompositeInsert) { + // If no extract indices, mark insert and inserted object (which might + // also be an insert chain) and continue up the chain though the input + // composite. + // + // Note: We mark inserted objects in this function (rather than in + // EliminateDeadInsertsOnePass) because in some cases, we can do it + // more accurately here. + if (pExtIndices == nullptr) { + liveInserts_.insert(insInst->result_id()); + uint32_t objId = insInst->GetSingleWordInOperand(kInsertObjectIdInIdx); + MarkInsertChain(get_def_use_mgr()->GetDef(objId), nullptr, 0); + } + // If extract indices match insert, we are done. Mark insert and + // inserted object. + else if (ExtInsMatch(*pExtIndices, insInst, extOffset)) { + liveInserts_.insert(insInst->result_id()); + uint32_t objId = insInst->GetSingleWordInOperand(kInsertObjectIdInIdx); + MarkInsertChain(get_def_use_mgr()->GetDef(objId), nullptr, 0); + break; + } + // If non-matching intersection, mark insert + else if (ExtInsConflict(*pExtIndices, insInst, extOffset)) { + liveInserts_.insert(insInst->result_id()); + // If more extract indices than insert, we are done. Use remaining + // extract indices to mark inserted object. + uint32_t numInsertIndices = insInst->NumInOperands() - 2; + if (pExtIndices->size() - extOffset > numInsertIndices) { + uint32_t objId = insInst->GetSingleWordInOperand(kInsertObjectIdInIdx); + MarkInsertChain(get_def_use_mgr()->GetDef(objId), pExtIndices, + extOffset + numInsertIndices); + break; + } + // If fewer extract indices than insert, also mark inserted object and + // continue up chain. + else { + uint32_t objId = insInst->GetSingleWordInOperand(kInsertObjectIdInIdx); + MarkInsertChain(get_def_use_mgr()->GetDef(objId), nullptr, 0); + } + } + // Get next insert in chain + const uint32_t compId = + insInst->GetSingleWordInOperand(kInsertCompositeIdInIdx); + insInst = get_def_use_mgr()->GetDef(compId); + } + // If insert chain ended with phi, do recursive call on each operand + if (insInst->opcode() != SpvOpPhi) return; + // Mark phi visited to prevent potential infinite loop. If phi is already + // visited, return to avoid infinite loop + if (!visitedPhis_.insert(insInst->result_id()).second) return; + uint32_t icnt = 0; + insInst->ForEachInId([&icnt, &pExtIndices, &extOffset, this](uint32_t* idp) { + if (icnt % 2 == 0) { + ir::Instruction* pi = get_def_use_mgr()->GetDef(*idp); + MarkInsertChain(pi, pExtIndices, extOffset); + } + ++icnt; + }); + // Unmark phi when done visiting + visitedPhis_.erase(insInst->result_id()); +} + +bool DeadInsertElimPass::EliminateDeadInserts(ir::Function* func) { + bool modified = false; + bool lastmodified = true; + // Each pass can delete dead instructions, thus potentially revealing + // new dead insertions ie insertions with no uses. + while (lastmodified) { + lastmodified = EliminateDeadInsertsOnePass(func); + modified |= lastmodified; + } + return modified; +} + +bool DeadInsertElimPass::EliminateDeadInsertsOnePass(ir::Function* func) { + bool modified = false; + liveInserts_.clear(); + visitedPhis_.clear(); + // Mark all live inserts + for (auto bi = func->begin(); bi != func->end(); ++bi) { + for (auto ii = bi->begin(); ii != bi->end(); ++ii) { + // Only process Inserts and composite Phis + SpvOp op = ii->opcode(); + ir::Instruction* typeInst = get_def_use_mgr()->GetDef(ii->type_id()); + if (op != SpvOpCompositeInsert && + (op != SpvOpPhi || !spvOpcodeIsComposite(typeInst->opcode()))) + continue; + // The marking algorithm can be expensive for large arrays and the + // efficacy of eliminating dead inserts into arrays is questionable. + // Skip optimizing array inserts for now. Just mark them live. + // TODO(greg-lunarg): Eliminate dead array inserts + if (op == SpvOpCompositeInsert) { + if (typeInst->opcode() == SpvOpTypeArray) { + liveInserts_.insert(ii->result_id()); + continue; + } + } + const uint32_t id = ii->result_id(); + get_def_use_mgr()->ForEachUser(id, [&ii, this](ir::Instruction* user) { + switch (user->opcode()) { + case SpvOpCompositeInsert: + case SpvOpPhi: + // Use by insert or phi does not initiate marking + break; + case SpvOpCompositeExtract: { + // Capture extract indices + std::vector extIndices; + uint32_t icnt = 0; + user->ForEachInOperand([&icnt, &extIndices](const uint32_t* idp) { + if (icnt > 0) extIndices.push_back(*idp); + ++icnt; + }); + // Mark all inserts in chain that intersect with extract + MarkInsertChain(&*ii, &extIndices, 0); + } break; + default: { + // Mark inserts in chain for all components + MarkInsertChain(&*ii, nullptr, 0); + } break; + } + }); + } + } + // Find and disconnect dead inserts + std::vector dead_instructions; + for (auto bi = func->begin(); bi != func->end(); ++bi) { + for (auto ii = bi->begin(); ii != bi->end(); ++ii) { + if (ii->opcode() != SpvOpCompositeInsert) continue; + const uint32_t id = ii->result_id(); + if (liveInserts_.find(id) != liveInserts_.end()) continue; + const uint32_t replId = + ii->GetSingleWordInOperand(kInsertCompositeIdInIdx); + (void)context()->ReplaceAllUsesWith(id, replId); + dead_instructions.push_back(&*ii); + modified = true; + } + } + // DCE dead inserts + while (!dead_instructions.empty()) { + ir::Instruction* inst = dead_instructions.back(); + dead_instructions.pop_back(); + DCEInst(inst, [&dead_instructions](ir::Instruction* other_inst) { + auto i = std::find(dead_instructions.begin(), dead_instructions.end(), + other_inst); + if (i != dead_instructions.end()) { + dead_instructions.erase(i); + } + }); + } + return modified; +} + +void DeadInsertElimPass::Initialize(ir::IRContext* c) { + InitializeProcessing(c); + + // Initialize extension whitelist + InitExtensions(); +}; + +bool DeadInsertElimPass::AllExtensionsSupported() const { + // If any extension not in whitelist, return false + for (auto& ei : get_module()->extensions()) { + const char* extName = + reinterpret_cast(&ei.GetInOperand(0).words[0]); + if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) + return false; + } + return true; +} + +Pass::Status DeadInsertElimPass::ProcessImpl() { + // Do not process if any disallowed extensions are enabled + if (!AllExtensionsSupported()) return Status::SuccessWithoutChange; + // Process all entry point functions. + ProcessFunction pfn = [this](ir::Function* fp) { + return EliminateDeadInserts(fp); + }; + bool modified = ProcessEntryPointCallTree(pfn, get_module()); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +DeadInsertElimPass::DeadInsertElimPass() {} + +Pass::Status DeadInsertElimPass::Process(ir::IRContext* c) { + Initialize(c); + return ProcessImpl(); +} + +void DeadInsertElimPass::InitExtensions() { + extensions_whitelist_.clear(); + extensions_whitelist_.insert({ + "SPV_AMD_shader_explicit_vertex_parameter", + "SPV_AMD_shader_trinary_minmax", + "SPV_AMD_gcn_shader", + "SPV_KHR_shader_ballot", + "SPV_AMD_shader_ballot", + "SPV_AMD_gpu_shader_half_float", + "SPV_KHR_shader_draw_parameters", + "SPV_KHR_subgroup_vote", + "SPV_KHR_16bit_storage", + "SPV_KHR_device_group", + "SPV_KHR_multiview", + "SPV_NVX_multiview_per_view_attributes", + "SPV_NV_viewport_array2", + "SPV_NV_stereo_view_rendering", + "SPV_NV_sample_mask_override_coverage", + "SPV_NV_geometry_shader_passthrough", + "SPV_AMD_texture_gather_bias_lod", + "SPV_KHR_storage_buffer_storage_class", + "SPV_KHR_variable_pointers", + "SPV_AMD_gpu_shader_int16", + "SPV_KHR_post_depth_coverage", + "SPV_KHR_shader_atomic_counter_ops", + }); +} + +} // namespace opt +} // namespace spvtools diff --git a/source/opt/dead_insert_elim_pass.h b/source/opt/dead_insert_elim_pass.h new file mode 100644 index 000000000..df13c0cc9 --- /dev/null +++ b/source/opt/dead_insert_elim_pass.h @@ -0,0 +1,88 @@ +// Copyright (c) 2018 The Khronos Group Inc. +// Copyright (c) 2018 Valve Corporation +// Copyright (c) 2018 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 LIBSPIRV_OPT_DEAD_INSERT_ELIM_PASS_H_ +#define LIBSPIRV_OPT_DEAD_INSERT_ELIM_PASS_H_ + +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "ir_context.h" +#include "mem_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class DeadInsertElimPass : public MemPass { + public: + DeadInsertElimPass(); + const char* name() const override { return "eliminate-dead-insert"; } + Status Process(ir::IRContext*) override; + + private: + // Return the number of subcomponents in the composite type |typeId|. + // Return 0 if not a composite type or number of components is not a + // 32-bit constant. + uint32_t NumComponents(ir::Instruction* typeInst); + + // Mark all inserts in instruction chain ending at |insertChain| with + // indices that intersect with extract indices |extIndices| starting with + // index at |extOffset|. Chains are composed solely of Inserts and Phis. + // Mark all inserts in chain if |extIndices| is nullptr. + void MarkInsertChain(ir::Instruction* insertChain, + std::vector* extIndices, uint32_t extOffset); + + // Perform EliminateDeadInsertsOnePass(|func|) until no modification is + // made. Return true if modified. + bool EliminateDeadInserts(ir::Function* func); + + // DCE all dead struct, matrix and vector inserts in |func|. An insert is + // dead if the value it inserts is never used. Replace any reference to the + // insert with its original composite. Return true if modified. Dead inserts + // in dependence cycles are not currently eliminated. Dead inserts into + // arrays are not currently eliminated. + bool EliminateDeadInsertsOnePass(ir::Function* func); + + // Initialize extensions whitelist + void InitExtensions(); + + // Return true if all extensions in this module are allowed by this pass. + bool AllExtensionsSupported() const; + + void Initialize(ir::IRContext* c); + Pass::Status ProcessImpl(); + + // Live inserts + std::unordered_set liveInserts_; + + // Visited phis as insert chain is traversed; used to avoid infinite loop + std::unordered_set visitedPhis_; + + // Extensions supported by this pass. + std::unordered_set extensions_whitelist_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_DEAD_INSERT_ELIM_PASS_H_ diff --git a/source/opt/insert_extract_elim.cpp b/source/opt/insert_extract_elim.cpp index 4940fe616..152c07aa8 100644 --- a/source/opt/insert_extract_elim.cpp +++ b/source/opt/insert_extract_elim.cpp @@ -16,6 +16,7 @@ #include "insert_extract_elim.h" +#include "composite.h" #include "ir_context.h" #include "iterator.h" #include "spirv/1.2/GLSL.std.450.h" @@ -27,14 +28,10 @@ namespace opt { namespace { +const uint32_t kConstantValueInIdx = 0; const uint32_t kExtractCompositeIdInIdx = 0; const uint32_t kInsertObjectIdInIdx = 0; const uint32_t kInsertCompositeIdInIdx = 1; -const uint32_t kTypeVectorCountInIdx = 1; -const uint32_t kTypeMatrixCountInIdx = 1; -const uint32_t kTypeArrayLengthIdInIdx = 1; -const uint32_t kTypeIntWidthInIdx = 0; -const uint32_t kConstantValueInIdx = 0; const uint32_t kVectorShuffleVec1IdInIdx = 0; const uint32_t kVectorShuffleVec2IdInIdx = 1; const uint32_t kVectorShuffleCompsInIdx = 2; @@ -49,246 +46,6 @@ const uint32_t kFMixAIdInIdx = 4; } // anonymous namespace -bool InsertExtractElimPass::ExtInsMatch(const std::vector& extIndices, - const ir::Instruction* insInst, - const uint32_t extOffset) const { - uint32_t numIndices = static_cast(extIndices.size()) - extOffset; - if (numIndices != insInst->NumInOperands() - 2) return false; - for (uint32_t i = 0; i < numIndices; ++i) - if (extIndices[i + extOffset] != insInst->GetSingleWordInOperand(i + 2)) - return false; - return true; -} - -bool InsertExtractElimPass::ExtInsConflict( - const std::vector& extIndices, const ir::Instruction* insInst, - const uint32_t extOffset) const { - if (extIndices.size() - extOffset == insInst->NumInOperands() - 2) - return false; - uint32_t extNumIndices = static_cast(extIndices.size()) - extOffset; - uint32_t insNumIndices = insInst->NumInOperands() - 2; - uint32_t numIndices = std::min(extNumIndices, insNumIndices); - for (uint32_t i = 0; i < numIndices; ++i) - if (extIndices[i + extOffset] != insInst->GetSingleWordInOperand(i + 2)) - return false; - return true; -} - -bool InsertExtractElimPass::IsVectorType(uint32_t typeId) { - ir::Instruction* typeInst = get_def_use_mgr()->GetDef(typeId); - return typeInst->opcode() == SpvOpTypeVector; -} - -bool InsertExtractElimPass::IsComposite(uint32_t typeId) { - ir::Instruction* typeInst = get_def_use_mgr()->GetDef(typeId); - return spvOpcodeIsComposite(typeInst->opcode()); -} - -uint32_t InsertExtractElimPass::NumComponents(uint32_t typeId) { - ir::Instruction* typeInst = get_def_use_mgr()->GetDef(typeId); - switch (typeInst->opcode()) { - case SpvOpTypeVector: { - return typeInst->GetSingleWordInOperand(kTypeVectorCountInIdx); - } break; - case SpvOpTypeMatrix: { - return typeInst->GetSingleWordInOperand(kTypeMatrixCountInIdx); - } break; - case SpvOpTypeArray: { - uint32_t lenId = - typeInst->GetSingleWordInOperand(kTypeArrayLengthIdInIdx); - ir::Instruction* lenInst = get_def_use_mgr()->GetDef(lenId); - if (lenInst->opcode() != SpvOpConstant) return 0; - uint32_t lenTypeId = lenInst->type_id(); - ir::Instruction* lenTypeInst = get_def_use_mgr()->GetDef(lenTypeId); - // TODO(greg-lunarg): Support non-32-bit array length - if (lenTypeInst->GetSingleWordInOperand(kTypeIntWidthInIdx) != 32) - return 0; - return lenInst->GetSingleWordInOperand(kConstantValueInIdx); - } break; - case SpvOpTypeStruct: { - return typeInst->NumInOperands(); - } break; - default: { return 0; } break; - } -} - -void InsertExtractElimPass::MarkInsertChain(ir::Instruction* insertChain, - std::vector* pExtIndices, - uint32_t extOffset) { - // Not currently optimizing array inserts. - ir::Instruction* typeInst = get_def_use_mgr()->GetDef(insertChain->type_id()); - if (typeInst->opcode() == SpvOpTypeArray) return; - // Insert chains are only composed of inserts and phis - if (insertChain->opcode() != SpvOpCompositeInsert && - insertChain->opcode() != SpvOpPhi) - return; - // If extract indices are empty, mark all subcomponents if type - // is constant length. - if (pExtIndices == nullptr) { - uint32_t cnum = NumComponents(insertChain->type_id()); - if (cnum > 0) { - std::vector extIndices; - for (uint32_t i = 0; i < cnum; i++) { - extIndices.clear(); - extIndices.push_back(i); - MarkInsertChain(insertChain, &extIndices, 0); - } - return; - } - } - ir::Instruction* insInst = insertChain; - while (insInst->opcode() == SpvOpCompositeInsert) { - // If no extract indices, mark insert and inserted object (which might - // also be an insert chain) and continue up the chain though the input - // composite. - // - // Note: We mark inserted objects in this function (rather than in - // EliminateDeadInsertsOnePass) because in some cases, we can do it - // more accurately here. - if (pExtIndices == nullptr) { - liveInserts_.insert(insInst->result_id()); - uint32_t objId = insInst->GetSingleWordInOperand(kInsertObjectIdInIdx); - MarkInsertChain(get_def_use_mgr()->GetDef(objId), nullptr, 0); - } - // If extract indices match insert, we are done. Mark insert and - // inserted object. - else if (ExtInsMatch(*pExtIndices, insInst, extOffset)) { - liveInserts_.insert(insInst->result_id()); - uint32_t objId = insInst->GetSingleWordInOperand(kInsertObjectIdInIdx); - MarkInsertChain(get_def_use_mgr()->GetDef(objId), nullptr, 0); - break; - } - // If non-matching intersection, mark insert - else if (ExtInsConflict(*pExtIndices, insInst, extOffset)) { - liveInserts_.insert(insInst->result_id()); - // If more extract indices than insert, we are done. Use remaining - // extract indices to mark inserted object. - uint32_t numInsertIndices = insInst->NumInOperands() - 2; - if (pExtIndices->size() - extOffset > numInsertIndices) { - uint32_t objId = insInst->GetSingleWordInOperand(kInsertObjectIdInIdx); - MarkInsertChain(get_def_use_mgr()->GetDef(objId), pExtIndices, - extOffset + numInsertIndices); - break; - } - // If fewer extract indices than insert, also mark inserted object and - // continue up chain. - else { - uint32_t objId = insInst->GetSingleWordInOperand(kInsertObjectIdInIdx); - MarkInsertChain(get_def_use_mgr()->GetDef(objId), nullptr, 0); - } - } - // Get next insert in chain - const uint32_t compId = - insInst->GetSingleWordInOperand(kInsertCompositeIdInIdx); - insInst = get_def_use_mgr()->GetDef(compId); - } - // If insert chain ended with phi, do recursive call on each operand - if (insInst->opcode() != SpvOpPhi) return; - // Mark phi visited to prevent potential infinite loop. If phi is already - // visited, return to avoid infinite loop - if (!visitedPhis_.insert(insInst->result_id()).second) return; - uint32_t icnt = 0; - insInst->ForEachInId([&icnt, &pExtIndices, &extOffset, this](uint32_t* idp) { - if (icnt % 2 == 0) { - ir::Instruction* pi = get_def_use_mgr()->GetDef(*idp); - MarkInsertChain(pi, pExtIndices, extOffset); - } - ++icnt; - }); - // Unmark phi when done visiting - visitedPhis_.erase(insInst->result_id()); -} - -bool InsertExtractElimPass::EliminateDeadInserts(ir::Function* func) { - bool modified = false; - bool lastmodified = true; - // Each pass can delete dead instructions, thus potentially revealing - // new dead insertions ie insertions with no uses. - while (lastmodified) { - lastmodified = EliminateDeadInsertsOnePass(func); - modified |= lastmodified; - } - return modified; -} - -bool InsertExtractElimPass::EliminateDeadInsertsOnePass(ir::Function* func) { - bool modified = false; - liveInserts_.clear(); - visitedPhis_.clear(); - // Mark all live inserts - for (auto bi = func->begin(); bi != func->end(); ++bi) { - for (auto ii = bi->begin(); ii != bi->end(); ++ii) { - // Only process Inserts and composite Phis - SpvOp op = ii->opcode(); - if (op != SpvOpCompositeInsert && - (op != SpvOpPhi || !IsComposite(ii->type_id()))) - continue; - // The marking algorithm can be expensive for large arrays and the - // efficacy of eliminating dead inserts into arrays is questionable. - // Skip optimizing array inserts for now. Just mark them live. - // TODO(greg-lunarg): Eliminate dead array inserts - if (op == SpvOpCompositeInsert) { - ir::Instruction* typeInst = get_def_use_mgr()->GetDef(ii->type_id()); - if (typeInst->opcode() == SpvOpTypeArray) { - liveInserts_.insert(ii->result_id()); - continue; - } - } - const uint32_t id = ii->result_id(); - get_def_use_mgr()->ForEachUser(id, [&ii, this](ir::Instruction* user) { - switch (user->opcode()) { - case SpvOpCompositeInsert: - case SpvOpPhi: - // Use by insert or phi does not initiate marking - break; - case SpvOpCompositeExtract: { - // Capture extract indices - std::vector extIndices; - uint32_t icnt = 0; - user->ForEachInOperand([&icnt, &extIndices](const uint32_t* idp) { - if (icnt > 0) extIndices.push_back(*idp); - ++icnt; - }); - // Mark all inserts in chain that intersect with extract - MarkInsertChain(&*ii, &extIndices, 0); - } break; - default: { - // Mark inserts in chain for all components - MarkInsertChain(&*ii, nullptr, 0); - } break; - } - }); - } - } - // Find and disconnect dead inserts - std::vector dead_instructions; - for (auto bi = func->begin(); bi != func->end(); ++bi) { - for (auto ii = bi->begin(); ii != bi->end(); ++ii) { - if (ii->opcode() != SpvOpCompositeInsert) continue; - const uint32_t id = ii->result_id(); - if (liveInserts_.find(id) != liveInserts_.end()) continue; - const uint32_t replId = - ii->GetSingleWordInOperand(kInsertCompositeIdInIdx); - (void)context()->ReplaceAllUsesWith(id, replId); - dead_instructions.push_back(&*ii); - modified = true; - } - } - // DCE dead inserts - while (!dead_instructions.empty()) { - ir::Instruction* inst = dead_instructions.back(); - dead_instructions.pop_back(); - DCEInst(inst, [&dead_instructions](ir::Instruction* other_inst) { - auto i = std::find(dead_instructions.begin(), dead_instructions.end(), - other_inst); - if (i != dead_instructions.end()) { - dead_instructions.erase(i); - } - }); - } - return modified; -} - uint32_t InsertExtractElimPass::DoExtract(ir::Instruction* compInst, std::vector* pExtIndices, uint32_t extOffset) { @@ -437,7 +194,6 @@ bool InsertExtractElimPass::EliminateInsertExtract(ir::Function* func) { } } } - modified |= EliminateDeadInserts(func); return modified; } diff --git a/source/opt/insert_extract_elim.h b/source/opt/insert_extract_elim.h index 5e585fece..ed793b96c 100644 --- a/source/opt/insert_extract_elim.h +++ b/source/opt/insert_extract_elim.h @@ -40,50 +40,6 @@ class InsertExtractElimPass : public MemPass { Status Process(ir::IRContext*) override; private: - // Return true if the extract indices in |extIndices| starting at |extOffset| - // match indices of insert |insInst|. - bool ExtInsMatch(const std::vector& extIndices, - const ir::Instruction* insInst, - const uint32_t extOffset) const; - - // Return true if indices in |extIndices| starting at |extOffset| and - // indices of insert |insInst| conflict, specifically, if the insert - // changes bits specified by the extract, but changes either more bits - // or less bits than the extract specifies, meaning the exact value being - // inserted cannot be used to replace the extract. - bool ExtInsConflict(const std::vector& extIndices, - const ir::Instruction* insInst, - const uint32_t extOffset) const; - - // Return true if |typeId| is a vector type - bool IsVectorType(uint32_t typeId); - - // Return true if |typeId| is composite. - bool IsComposite(uint32_t typeId); - - // Return the number of subcomponents in the composite type |typeId|. - // Return 0 if not a composite type or number of components is not a - // 32-bit constant. - uint32_t NumComponents(uint32_t typeId); - - // Mark all inserts in instruction chain ending at |insertChain| with - // indices that intersect with extract indices |extIndices| starting with - // index at |extOffset|. Chains are composed solely of Inserts and Phis. - // Mark all inserts in chain if |extIndices| is nullptr. - void MarkInsertChain(ir::Instruction* insertChain, - std::vector* extIndices, uint32_t extOffset); - - // Perform EliminateDeadInsertsOnePass(|func|) until no modification is - // made. Return true if modified. - bool EliminateDeadInserts(ir::Function* func); - - // DCE all dead struct, matrix and vector inserts in |func|. An insert is - // dead if the value it inserts is never used. Replace any reference to the - // insert with its original composite. Return true if modified. Dead inserts - // in dependence cycles are not currently eliminated. Dead inserts into - // arrays are not currently eliminated. - bool EliminateDeadInsertsOnePass(ir::Function* func); - // Return id of component of |cinst| specified by |extIndices| starting with // index at |extOffset|. Return 0 if indices cannot be matched exactly. uint32_t DoExtract(ir::Instruction* cinst, std::vector* extIndices, @@ -104,12 +60,6 @@ class InsertExtractElimPass : public MemPass { void Initialize(ir::IRContext* c); Pass::Status ProcessImpl(); - // Live inserts - std::unordered_set liveInserts_; - - // Visited phis as insert chain is traversed; used to avoid infinite loop - std::unordered_set visitedPhis_; - // Extensions supported by this pass. std::unordered_set extensions_whitelist_; }; diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index 47ac3fdbd..2fe827640 100644 --- a/source/opt/optimizer.cpp +++ b/source/opt/optimizer.cpp @@ -107,7 +107,9 @@ Optimizer& Optimizer::RegisterLegalizationPasses() { // May need loop unrolling here see // https://github.com/Microsoft/DirectXShaderCompiler/pull/930 .RegisterPass(CreateDeadBranchElimPass()) - // Get rid of unused code that leave traces of the illegal code. + // Get rid of unused code that contain traces of illegal code + // or unused references to unbound external objects + .RegisterPass(CreateDeadInsertElimPass()) .RegisterPass(CreateAggressiveDCEPass()); } @@ -121,6 +123,7 @@ Optimizer& Optimizer::RegisterPerformancePasses() { .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) .RegisterPass(CreateLocalSingleStoreElimPass()) .RegisterPass(CreateInsertExtractElimPass()) + .RegisterPass(CreateDeadInsertElimPass()) .RegisterPass(CreateLocalMultiStoreElimPass()) .RegisterPass(CreateCCPPass()) .RegisterPass(CreateAggressiveDCEPass()) @@ -129,6 +132,7 @@ Optimizer& Optimizer::RegisterPerformancePasses() { .RegisterPass(CreateAggressiveDCEPass()) .RegisterPass(CreateBlockMergePass()) .RegisterPass(CreateInsertExtractElimPass()) + .RegisterPass(CreateDeadInsertElimPass()) .RegisterPass(CreateRedundancyEliminationPass()) .RegisterPass(CreateCFGCleanupPass()) // Currently exposing driver bugs resulting in crashes (#946) @@ -145,6 +149,7 @@ Optimizer& Optimizer::RegisterSizePasses() { .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) .RegisterPass(CreateLocalSingleStoreElimPass()) .RegisterPass(CreateInsertExtractElimPass()) + .RegisterPass(CreateDeadInsertElimPass()) .RegisterPass(CreateLocalMultiStoreElimPass()) .RegisterPass(CreateCCPPass()) .RegisterPass(CreateAggressiveDCEPass()) @@ -153,6 +158,7 @@ Optimizer& Optimizer::RegisterSizePasses() { .RegisterPass(CreateAggressiveDCEPass()) .RegisterPass(CreateBlockMergePass()) .RegisterPass(CreateInsertExtractElimPass()) + .RegisterPass(CreateDeadInsertElimPass()) .RegisterPass(CreateRedundancyEliminationPass()) .RegisterPass(CreateCFGCleanupPass()) // Currently exposing driver bugs resulting in crashes (#946) @@ -281,6 +287,11 @@ Optimizer::PassToken CreateInsertExtractElimPass() { MakeUnique()); } +Optimizer::PassToken CreateDeadInsertElimPass() { + return MakeUnique( + MakeUnique()); +} + Optimizer::PassToken CreateDeadBranchElimPass() { return MakeUnique( MakeUnique()); diff --git a/source/opt/passes.h b/source/opt/passes.h index fd34131d3..a2d4f0915 100644 --- a/source/opt/passes.h +++ b/source/opt/passes.h @@ -24,6 +24,7 @@ #include "common_uniform_elim_pass.h" #include "compact_ids_pass.h" #include "dead_branch_elim_pass.h" +#include "dead_insert_elim_pass.h" #include "dead_variable_elimination.h" #include "eliminate_dead_constant_pass.h" #include "eliminate_dead_functions_pass.h" diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt index b7a795581..0f5389796 100644 --- a/test/opt/CMakeLists.txt +++ b/test/opt/CMakeLists.txt @@ -81,6 +81,11 @@ add_spvtools_unittest(TARGET pass_insert_extract_elim LIBS SPIRV-Tools-opt ) +add_spvtools_unittest(TARGET pass_dead_insert_elim + SRCS dead_insert_elim_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + add_spvtools_unittest(TARGET pass_local_ssa_elim SRCS local_ssa_elim_test.cpp pass_utils.cpp LIBS SPIRV-Tools-opt diff --git a/test/opt/dead_insert_elim_test.cpp b/test/opt/dead_insert_elim_test.cpp new file mode 100644 index 000000000..882ee2ea5 --- /dev/null +++ b/test/opt/dead_insert_elim_test.cpp @@ -0,0 +1,685 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 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 "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using DeadInsertElimTest = PassTest<::testing::Test>; + +TEST_F(DeadInsertElimTest, InsertAfterInsertElim) { + // With two insertions to the same offset, the first is dead. + // + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 450 + // + // layout (location=0) in float In0; + // layout (location=1) in float In1; + // layout (location=2) in vec2 In2; + // layout (location=0) out vec4 OutColor; + // + // void main() + // { + // vec2 v = In2; + // v.x = In0 + In1; // dead + // v.x = 0.0; + // OutColor = v.xyxy; + // } + + const std::string before_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In2 %In0 %In1 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %In2 "In2" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %OutColor "OutColor" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpMemberName %_Globals_ 1 "g_n" +OpName %_ "" +OpDecorate %In2 Location 2 +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %OutColor Location 0 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpMemberDecorate %_Globals_ 1 Offset 4 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%In2 = OpVariable %_ptr_Input_v2float Input +%_ptr_Input_float = OpTypePointer Input %float +%In0 = OpVariable %_ptr_Input_float Input +%In1 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%int = OpTypeInt 32 1 +%_Globals_ = OpTypeStruct %uint %int +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +)"; + + const std::string after_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In2 %In0 %In1 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %In2 "In2" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %OutColor "OutColor" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpMemberName %_Globals_ 1 "g_n" +OpName %_ "" +OpDecorate %In2 Location 2 +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %OutColor Location 0 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpMemberDecorate %_Globals_ 1 Offset 4 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%In2 = OpVariable %_ptr_Input_v2float Input +%_ptr_Input_float = OpTypePointer Input %float +%In0 = OpVariable %_ptr_Input_float Input +%In1 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%int = OpTypeInt 32 1 +%_Globals_ = OpTypeStruct %uint %int +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +)"; + + const std::string before = + R"(%main = OpFunction %void None %11 +%25 = OpLabel +%26 = OpLoad %v2float %In2 +%27 = OpLoad %float %In0 +%28 = OpLoad %float %In1 +%29 = OpFAdd %float %27 %28 +%35 = OpCompositeInsert %v2float %29 %26 0 +%37 = OpCompositeInsert %v2float %float_0 %35 0 +%33 = OpVectorShuffle %v4float %37 %37 0 1 0 1 +OpStore %OutColor %33 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %10 +%23 = OpLabel +%24 = OpLoad %v2float %In2 +%29 = OpCompositeInsert %v2float %float_0 %24 0 +%30 = OpVectorShuffle %v4float %29 %29 0 1 0 1 +OpStore %OutColor %30 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + before_predefs + before, after_predefs + after, true, true); +} + +TEST_F(DeadInsertElimTest, DeadInsertInChainWithPhi) { + // Dead insert eliminated with phi in insertion chain. + // + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 450 + // + // layout (location=0) in vec4 In0; + // layout (location=1) in float In1; + // layout (location=2) in float In2; + // layout (location=0) out vec4 OutColor; + // + // layout(std140, binding = 0 ) uniform _Globals_ + // { + // bool g_b; + // }; + // + // void main() + // { + // vec4 v = In0; + // v.z = In1 + In2; + // if (g_b) v.w = 1.0; + // OutColor = vec4(v.x,v.y,0.0,v.w); + // } + + const std::string before_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %In2 "In2" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpName %_ "" +OpName %OutColor "OutColor" +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %In2 Location 2 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%In0 = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%In1 = OpVariable %_ptr_Input_float Input +%In2 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%_ptr_Function_float = OpTypePointer Function %float +%_Globals_ = OpTypeStruct %uint +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%bool = OpTypeBool +%uint_0 = OpConstant %uint 0 +%float_1 = OpConstant %float 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_0 = OpConstant %float 0 +)"; + + const std::string after_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %In2 "In2" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpName %_ "" +OpName %OutColor "OutColor" +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %In2 Location 2 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%In0 = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%In1 = OpVariable %_ptr_Input_float Input +%In2 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%_ptr_Function_float = OpTypePointer Function %float +%_Globals_ = OpTypeStruct %uint +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%bool = OpTypeBool +%uint_0 = OpConstant %uint 0 +%float_1 = OpConstant %float 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_0 = OpConstant %float 0 +)"; + + const std::string before = + R"(%main = OpFunction %void None %11 +%31 = OpLabel +%32 = OpLoad %v4float %In0 +%33 = OpLoad %float %In1 +%34 = OpLoad %float %In2 +%35 = OpFAdd %float %33 %34 +%51 = OpCompositeInsert %v4float %35 %32 2 +%37 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%38 = OpLoad %uint %37 +%39 = OpINotEqual %bool %38 %uint_0 +OpSelectionMerge %40 None +OpBranchConditional %39 %41 %40 +%41 = OpLabel +%53 = OpCompositeInsert %v4float %float_1 %51 3 +OpBranch %40 +%40 = OpLabel +%60 = OpPhi %v4float %51 %31 %53 %41 +%55 = OpCompositeExtract %float %60 0 +%57 = OpCompositeExtract %float %60 1 +%59 = OpCompositeExtract %float %60 3 +%49 = OpCompositeConstruct %v4float %55 %57 %float_0 %59 +OpStore %OutColor %49 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %10 +%27 = OpLabel +%28 = OpLoad %v4float %In0 +%33 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%34 = OpLoad %uint %33 +%35 = OpINotEqual %bool %34 %uint_0 +OpSelectionMerge %36 None +OpBranchConditional %35 %37 %36 +%37 = OpLabel +%38 = OpCompositeInsert %v4float %float_1 %28 3 +OpBranch %36 +%36 = OpLabel +%39 = OpPhi %v4float %28 %27 %38 %37 +%40 = OpCompositeExtract %float %39 0 +%41 = OpCompositeExtract %float %39 1 +%42 = OpCompositeExtract %float %39 3 +%43 = OpCompositeConstruct %v4float %40 %41 %float_0 %42 +OpStore %OutColor %43 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + before_predefs + before, after_predefs + after, true, true); +} + +TEST_F(DeadInsertElimTest, DeadInsertTwoPasses) { + // Dead insert which requires two passes to eliminate + // + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 450 + // + // layout (location=0) in vec4 In0; + // layout (location=1) in float In1; + // layout (location=2) in float In2; + // layout (location=0) out vec4 OutColor; + // + // layout(std140, binding = 0 ) uniform _Globals_ + // { + // bool g_b; + // bool g_b2; + // }; + // + // void main() + // { + // vec4 v1, v2; + // v1 = In0; + // v1.y = In1 + In2; // dead, second pass + // if (g_b) v1.x = 1.0; + // v2.x = v1.x; + // v2.y = v1.y; // dead, first pass + // if (g_b2) v2.x = 0.0; + // OutColor = vec4(v2.x,v2.x,0.0,1.0); + // } + + const std::string before_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %In2 "In2" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpMemberName %_Globals_ 1 "g_b2" +OpName %_ "" +OpName %OutColor "OutColor" +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %In2 Location 2 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpMemberDecorate %_Globals_ 1 Offset 4 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%In0 = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%In1 = OpVariable %_ptr_Input_float Input +%In2 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%_Globals_ = OpTypeStruct %uint %uint +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%bool = OpTypeBool +%uint_0 = OpConstant %uint 0 +%float_1 = OpConstant %float 1 +%int_1 = OpConstant %int 1 +%float_0 = OpConstant %float 0 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%27 = OpUndef %v4float +)"; + + const std::string after_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %In2 "In2" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpMemberName %_Globals_ 1 "g_b2" +OpName %_ "" +OpName %OutColor "OutColor" +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %In2 Location 2 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpMemberDecorate %_Globals_ 1 Offset 4 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%In0 = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%In1 = OpVariable %_ptr_Input_float Input +%In2 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%_Globals_ = OpTypeStruct %uint %uint +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%bool = OpTypeBool +%uint_0 = OpConstant %uint 0 +%float_1 = OpConstant %float 1 +%int_1 = OpConstant %int 1 +%float_0 = OpConstant %float 0 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%27 = OpUndef %v4float +)"; + + const std::string before = + R"(%main = OpFunction %void None %10 +%28 = OpLabel +%29 = OpLoad %v4float %In0 +%30 = OpLoad %float %In1 +%31 = OpLoad %float %In2 +%32 = OpFAdd %float %30 %31 +%33 = OpCompositeInsert %v4float %32 %29 1 +%34 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%35 = OpLoad %uint %34 +%36 = OpINotEqual %bool %35 %uint_0 +OpSelectionMerge %37 None +OpBranchConditional %36 %38 %37 +%38 = OpLabel +%39 = OpCompositeInsert %v4float %float_1 %33 0 +OpBranch %37 +%37 = OpLabel +%40 = OpPhi %v4float %33 %28 %39 %38 +%41 = OpCompositeExtract %float %40 0 +%42 = OpCompositeInsert %v4float %41 %27 0 +%43 = OpCompositeExtract %float %40 1 +%44 = OpCompositeInsert %v4float %43 %42 1 +%45 = OpAccessChain %_ptr_Uniform_uint %_ %int_1 +%46 = OpLoad %uint %45 +%47 = OpINotEqual %bool %46 %uint_0 +OpSelectionMerge %48 None +OpBranchConditional %47 %49 %48 +%49 = OpLabel +%50 = OpCompositeInsert %v4float %float_0 %44 0 +OpBranch %48 +%48 = OpLabel +%51 = OpPhi %v4float %44 %37 %50 %49 +%52 = OpCompositeExtract %float %51 0 +%53 = OpCompositeExtract %float %51 0 +%54 = OpCompositeConstruct %v4float %52 %53 %float_0 %float_1 +OpStore %OutColor %54 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %10 +%28 = OpLabel +%29 = OpLoad %v4float %In0 +%34 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%35 = OpLoad %uint %34 +%36 = OpINotEqual %bool %35 %uint_0 +OpSelectionMerge %37 None +OpBranchConditional %36 %38 %37 +%38 = OpLabel +%39 = OpCompositeInsert %v4float %float_1 %29 0 +OpBranch %37 +%37 = OpLabel +%40 = OpPhi %v4float %29 %28 %39 %38 +%41 = OpCompositeExtract %float %40 0 +%42 = OpCompositeInsert %v4float %41 %27 0 +%45 = OpAccessChain %_ptr_Uniform_uint %_ %int_1 +%46 = OpLoad %uint %45 +%47 = OpINotEqual %bool %46 %uint_0 +OpSelectionMerge %48 None +OpBranchConditional %47 %49 %48 +%49 = OpLabel +%50 = OpCompositeInsert %v4float %float_0 %42 0 +OpBranch %48 +%48 = OpLabel +%51 = OpPhi %v4float %42 %37 %50 %49 +%52 = OpCompositeExtract %float %51 0 +%53 = OpCompositeExtract %float %51 0 +%54 = OpCompositeConstruct %v4float %52 %53 %float_0 %float_1 +OpStore %OutColor %54 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + before_predefs + before, after_predefs + after, true, true); +} + +TEST_F(DeadInsertElimTest, DeadInsertInCycleToDo) { + // Dead insert in chain with cycle. Demonstrates analysis can handle + // cycles in chains. + // + // TODO(greg-lunarg): Improve algorithm to remove dead insert into v.y. Will + // likely require similar logic to ADCE. + // + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 450 + // + // layout (location=0) in vec4 In0; + // layout (location=1) in float In1; + // layout (location=2) in float In2; + // layout (location=0) out vec4 OutColor; + // + // layout(std140, binding = 0 ) uniform _Globals_ + // { + // int g_n ; + // }; + // + // void main() + // { + // vec2 v = vec2(0.0, 1.0); + // for (int i = 0; i < g_n; i++) { + // v.x = v.x + 1; + // v.y = v.y * 0.9; // dead + // } + // OutColor = vec4(v.x); + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %OutColor %In0 %In1 %In2 +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_n" +OpName %_ "" +OpName %OutColor "OutColor" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %In2 "In2" +OpMemberDecorate %_Globals_ 0 Offset 0 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +OpDecorate %OutColor Location 0 +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %In2 Location 2 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%float_0 = OpConstant %float 0 +%float_1 = OpConstant %float 1 +%16 = OpConstantComposite %v2float %float_0 %float_1 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%_Globals_ = OpTypeStruct %int +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +%_ptr_Uniform_int = OpTypePointer Uniform %int +%bool = OpTypeBool +%float_0_9 = OpConstant %float 0.9 +%int_1 = OpConstant %int 1 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%In0 = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%In1 = OpVariable %_ptr_Input_float Input +%In2 = OpVariable %_ptr_Input_float Input +%main = OpFunction %void None %10 +%29 = OpLabel +OpBranch %30 +%30 = OpLabel +%31 = OpPhi %v2float %16 %29 %32 %33 +%34 = OpPhi %int %int_0 %29 %35 %33 +OpLoopMerge %36 %33 None +OpBranch %37 +%37 = OpLabel +%38 = OpAccessChain %_ptr_Uniform_int %_ %int_0 +%39 = OpLoad %int %38 +%40 = OpSLessThan %bool %34 %39 +OpBranchConditional %40 %41 %36 +%41 = OpLabel +%42 = OpCompositeExtract %float %31 0 +%43 = OpFAdd %float %42 %float_1 +%44 = OpCompositeInsert %v2float %43 %31 0 +%45 = OpCompositeExtract %float %44 1 +%46 = OpFMul %float %45 %float_0_9 +%32 = OpCompositeInsert %v2float %46 %44 1 +OpBranch %33 +%33 = OpLabel +%35 = OpIAdd %int %34 %int_1 +OpBranch %30 +%36 = OpLabel +%47 = OpCompositeExtract %float %31 0 +%48 = OpCompositeConstruct %v4float %47 %47 %47 %47 +OpStore %OutColor %48 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, + true); +} + +// TODO(greg-lunarg): Add tests to verify handling of these cases: +// + +} // anonymous namespace diff --git a/test/opt/insert_extract_elim_test.cpp b/test/opt/insert_extract_elim_test.cpp index b3b9440de..afb0036f7 100644 --- a/test/opt/insert_extract_elim_test.cpp +++ b/test/opt/insert_extract_elim_test.cpp @@ -545,6 +545,7 @@ OpName %gl_FragColor "gl_FragColor" %_ptr_Function_S_t = OpTypePointer Function %S_t %int = OpTypeInt 32 1 %int_1 = OpConstant %int 1 +%float_1 = OpConstant %float 1 %uint = OpTypeInt 32 0 %uint_1 = OpConstant %uint 1 %_ptr_Function_float = OpTypePointer Function %float @@ -576,8 +577,9 @@ OpFunctionEnd %22 = OpLabel %s0 = OpVariable %_ptr_Function_S_t Function %23 = OpLoad %S_t %s0 +%24 = OpCompositeInsert %S_t %float_1 %23 1 1 %25 = OpLoad %v4float %BaseColor -%26 = OpCompositeInsert %S_t %25 %23 1 +%26 = OpCompositeInsert %S_t %25 %24 1 %27 = OpCompositeExtract %float %26 1 1 %28 = OpCompositeConstruct %v4float %27 %float_0 %float_0 %float_0 OpStore %gl_FragColor %28 @@ -855,663 +857,6 @@ OpFunctionEnd predefs + before, predefs + after, true, true); } -TEST_F(InsertExtractElimTest, InsertAfterInsertElim) { - // With two insertions to the same offset, the first is dead. - // - // Note: The SPIR-V assembly has had store/load elimination - // performed to allow the inserts and extracts to directly - // reference each other. - // - // #version 450 - // - // layout (location=0) in float In0; - // layout (location=1) in float In1; - // layout (location=2) in vec2 In2; - // layout (location=0) out vec4 OutColor; - // - // void main() - // { - // vec2 v = In2; - // v.x = In0 + In1; // dead - // v.x = 0.0; - // OutColor = v.xyxy; - // } - - const std::string before_predefs = - R"(OpCapability Shader -%1 = OpExtInstImport "GLSL.std.450" -OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %main "main" %In2 %In0 %In1 %OutColor -OpExecutionMode %main OriginUpperLeft -OpSource GLSL 450 -OpName %main "main" -OpName %In2 "In2" -OpName %In0 "In0" -OpName %In1 "In1" -OpName %OutColor "OutColor" -OpName %_Globals_ "_Globals_" -OpMemberName %_Globals_ 0 "g_b" -OpMemberName %_Globals_ 1 "g_n" -OpName %_ "" -OpDecorate %In2 Location 2 -OpDecorate %In0 Location 0 -OpDecorate %In1 Location 1 -OpDecorate %OutColor Location 0 -OpMemberDecorate %_Globals_ 0 Offset 0 -OpMemberDecorate %_Globals_ 1 Offset 4 -OpDecorate %_Globals_ Block -OpDecorate %_ DescriptorSet 0 -OpDecorate %_ Binding 0 -%void = OpTypeVoid -%11 = OpTypeFunction %void -%float = OpTypeFloat 32 -%v2float = OpTypeVector %float 2 -%_ptr_Function_v2float = OpTypePointer Function %v2float -%_ptr_Input_v2float = OpTypePointer Input %v2float -%In2 = OpVariable %_ptr_Input_v2float Input -%_ptr_Input_float = OpTypePointer Input %float -%In0 = OpVariable %_ptr_Input_float Input -%In1 = OpVariable %_ptr_Input_float Input -%uint = OpTypeInt 32 0 -%_ptr_Function_float = OpTypePointer Function %float -%float_0 = OpConstant %float 0 -%v4float = OpTypeVector %float 4 -%_ptr_Output_v4float = OpTypePointer Output %v4float -%OutColor = OpVariable %_ptr_Output_v4float Output -%int = OpTypeInt 32 1 -%_Globals_ = OpTypeStruct %uint %int -%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ -%_ = OpVariable %_ptr_Uniform__Globals_ Uniform -)"; - - const std::string after_predefs = - R"(OpCapability Shader -%1 = OpExtInstImport "GLSL.std.450" -OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %main "main" %In2 %In0 %In1 %OutColor -OpExecutionMode %main OriginUpperLeft -OpSource GLSL 450 -OpName %main "main" -OpName %In2 "In2" -OpName %In0 "In0" -OpName %In1 "In1" -OpName %OutColor "OutColor" -OpName %_Globals_ "_Globals_" -OpMemberName %_Globals_ 0 "g_b" -OpMemberName %_Globals_ 1 "g_n" -OpName %_ "" -OpDecorate %In2 Location 2 -OpDecorate %In0 Location 0 -OpDecorate %In1 Location 1 -OpDecorate %OutColor Location 0 -OpMemberDecorate %_Globals_ 0 Offset 0 -OpMemberDecorate %_Globals_ 1 Offset 4 -OpDecorate %_Globals_ Block -OpDecorate %_ DescriptorSet 0 -OpDecorate %_ Binding 0 -%void = OpTypeVoid -%10 = OpTypeFunction %void -%float = OpTypeFloat 32 -%v2float = OpTypeVector %float 2 -%_ptr_Function_v2float = OpTypePointer Function %v2float -%_ptr_Input_v2float = OpTypePointer Input %v2float -%In2 = OpVariable %_ptr_Input_v2float Input -%_ptr_Input_float = OpTypePointer Input %float -%In0 = OpVariable %_ptr_Input_float Input -%In1 = OpVariable %_ptr_Input_float Input -%uint = OpTypeInt 32 0 -%_ptr_Function_float = OpTypePointer Function %float -%float_0 = OpConstant %float 0 -%v4float = OpTypeVector %float 4 -%_ptr_Output_v4float = OpTypePointer Output %v4float -%OutColor = OpVariable %_ptr_Output_v4float Output -%int = OpTypeInt 32 1 -%_Globals_ = OpTypeStruct %uint %int -%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ -%_ = OpVariable %_ptr_Uniform__Globals_ Uniform -)"; - - const std::string before = - R"(%main = OpFunction %void None %11 -%25 = OpLabel -%26 = OpLoad %v2float %In2 -%27 = OpLoad %float %In0 -%28 = OpLoad %float %In1 -%29 = OpFAdd %float %27 %28 -%35 = OpCompositeInsert %v2float %29 %26 0 -%37 = OpCompositeInsert %v2float %float_0 %35 0 -%33 = OpVectorShuffle %v4float %37 %37 0 1 0 1 -OpStore %OutColor %33 -OpReturn -OpFunctionEnd -)"; - - const std::string after = - R"(%main = OpFunction %void None %10 -%23 = OpLabel -%24 = OpLoad %v2float %In2 -%29 = OpCompositeInsert %v2float %float_0 %24 0 -%30 = OpVectorShuffle %v4float %29 %29 0 1 0 1 -OpStore %OutColor %30 -OpReturn -OpFunctionEnd -)"; - - SinglePassRunAndCheck( - before_predefs + before, after_predefs + after, true, true); -} - -TEST_F(InsertExtractElimTest, DeadInsertInChainWithPhi) { - // Dead insert eliminated with phi in insertion chain. - // - // Note: The SPIR-V assembly has had store/load elimination - // performed to allow the inserts and extracts to directly - // reference each other. - // - // #version 450 - // - // layout (location=0) in vec4 In0; - // layout (location=1) in float In1; - // layout (location=2) in float In2; - // layout (location=0) out vec4 OutColor; - // - // layout(std140, binding = 0 ) uniform _Globals_ - // { - // bool g_b; - // }; - // - // void main() - // { - // vec4 v = In0; - // v.z = In1 + In2; - // if (g_b) v.w = 1.0; - // OutColor = vec4(v.x,v.y,0.0,v.w); - // } - - const std::string before_predefs = - R"(OpCapability Shader -%1 = OpExtInstImport "GLSL.std.450" -OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor -OpExecutionMode %main OriginUpperLeft -OpSource GLSL 450 -OpName %main "main" -OpName %In0 "In0" -OpName %In1 "In1" -OpName %In2 "In2" -OpName %_Globals_ "_Globals_" -OpMemberName %_Globals_ 0 "g_b" -OpName %_ "" -OpName %OutColor "OutColor" -OpDecorate %In0 Location 0 -OpDecorate %In1 Location 1 -OpDecorate %In2 Location 2 -OpMemberDecorate %_Globals_ 0 Offset 0 -OpDecorate %_Globals_ Block -OpDecorate %_ DescriptorSet 0 -OpDecorate %_ Binding 0 -OpDecorate %OutColor Location 0 -%void = OpTypeVoid -%11 = OpTypeFunction %void -%float = OpTypeFloat 32 -%v4float = OpTypeVector %float 4 -%_ptr_Function_v4float = OpTypePointer Function %v4float -%_ptr_Input_v4float = OpTypePointer Input %v4float -%In0 = OpVariable %_ptr_Input_v4float Input -%_ptr_Input_float = OpTypePointer Input %float -%In1 = OpVariable %_ptr_Input_float Input -%In2 = OpVariable %_ptr_Input_float Input -%uint = OpTypeInt 32 0 -%_ptr_Function_float = OpTypePointer Function %float -%_Globals_ = OpTypeStruct %uint -%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ -%_ = OpVariable %_ptr_Uniform__Globals_ Uniform -%int = OpTypeInt 32 1 -%int_0 = OpConstant %int 0 -%_ptr_Uniform_uint = OpTypePointer Uniform %uint -%bool = OpTypeBool -%uint_0 = OpConstant %uint 0 -%float_1 = OpConstant %float 1 -%_ptr_Output_v4float = OpTypePointer Output %v4float -%OutColor = OpVariable %_ptr_Output_v4float Output -%float_0 = OpConstant %float 0 -)"; - - const std::string after_predefs = - R"(OpCapability Shader -%1 = OpExtInstImport "GLSL.std.450" -OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor -OpExecutionMode %main OriginUpperLeft -OpSource GLSL 450 -OpName %main "main" -OpName %In0 "In0" -OpName %In1 "In1" -OpName %In2 "In2" -OpName %_Globals_ "_Globals_" -OpMemberName %_Globals_ 0 "g_b" -OpName %_ "" -OpName %OutColor "OutColor" -OpDecorate %In0 Location 0 -OpDecorate %In1 Location 1 -OpDecorate %In2 Location 2 -OpMemberDecorate %_Globals_ 0 Offset 0 -OpDecorate %_Globals_ Block -OpDecorate %_ DescriptorSet 0 -OpDecorate %_ Binding 0 -OpDecorate %OutColor Location 0 -%void = OpTypeVoid -%10 = OpTypeFunction %void -%float = OpTypeFloat 32 -%v4float = OpTypeVector %float 4 -%_ptr_Function_v4float = OpTypePointer Function %v4float -%_ptr_Input_v4float = OpTypePointer Input %v4float -%In0 = OpVariable %_ptr_Input_v4float Input -%_ptr_Input_float = OpTypePointer Input %float -%In1 = OpVariable %_ptr_Input_float Input -%In2 = OpVariable %_ptr_Input_float Input -%uint = OpTypeInt 32 0 -%_ptr_Function_float = OpTypePointer Function %float -%_Globals_ = OpTypeStruct %uint -%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ -%_ = OpVariable %_ptr_Uniform__Globals_ Uniform -%int = OpTypeInt 32 1 -%int_0 = OpConstant %int 0 -%_ptr_Uniform_uint = OpTypePointer Uniform %uint -%bool = OpTypeBool -%uint_0 = OpConstant %uint 0 -%float_1 = OpConstant %float 1 -%_ptr_Output_v4float = OpTypePointer Output %v4float -%OutColor = OpVariable %_ptr_Output_v4float Output -%float_0 = OpConstant %float 0 -)"; - - const std::string before = - R"(%main = OpFunction %void None %11 -%31 = OpLabel -%32 = OpLoad %v4float %In0 -%33 = OpLoad %float %In1 -%34 = OpLoad %float %In2 -%35 = OpFAdd %float %33 %34 -%51 = OpCompositeInsert %v4float %35 %32 2 -%37 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 -%38 = OpLoad %uint %37 -%39 = OpINotEqual %bool %38 %uint_0 -OpSelectionMerge %40 None -OpBranchConditional %39 %41 %40 -%41 = OpLabel -%53 = OpCompositeInsert %v4float %float_1 %51 3 -OpBranch %40 -%40 = OpLabel -%60 = OpPhi %v4float %51 %31 %53 %41 -%55 = OpCompositeExtract %float %60 0 -%57 = OpCompositeExtract %float %60 1 -%59 = OpCompositeExtract %float %60 3 -%49 = OpCompositeConstruct %v4float %55 %57 %float_0 %59 -OpStore %OutColor %49 -OpReturn -OpFunctionEnd -)"; - - const std::string after = - R"(%main = OpFunction %void None %10 -%27 = OpLabel -%28 = OpLoad %v4float %In0 -%33 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 -%34 = OpLoad %uint %33 -%35 = OpINotEqual %bool %34 %uint_0 -OpSelectionMerge %36 None -OpBranchConditional %35 %37 %36 -%37 = OpLabel -%38 = OpCompositeInsert %v4float %float_1 %28 3 -OpBranch %36 -%36 = OpLabel -%39 = OpPhi %v4float %28 %27 %38 %37 -%40 = OpCompositeExtract %float %39 0 -%41 = OpCompositeExtract %float %39 1 -%42 = OpCompositeExtract %float %39 3 -%43 = OpCompositeConstruct %v4float %40 %41 %float_0 %42 -OpStore %OutColor %43 -OpReturn -OpFunctionEnd -)"; - - SinglePassRunAndCheck( - before_predefs + before, after_predefs + after, true, true); -} - -TEST_F(InsertExtractElimTest, DeadInsertTwoPasses) { - // Dead insert which requires two passes to eliminate - // - // Note: The SPIR-V assembly has had store/load elimination - // performed to allow the inserts and extracts to directly - // reference each other. - // - // #version 450 - // - // layout (location=0) in vec4 In0; - // layout (location=1) in float In1; - // layout (location=2) in float In2; - // layout (location=0) out vec4 OutColor; - // - // layout(std140, binding = 0 ) uniform _Globals_ - // { - // bool g_b; - // bool g_b2; - // }; - // - // void main() - // { - // vec4 v1, v2; - // v1 = In0; - // v1.y = In1 + In2; // dead, second pass - // if (g_b) v1.x = 1.0; - // v2.x = v1.x; - // v2.y = v1.y; // dead, first pass - // if (g_b2) v2.x = 0.0; - // OutColor = vec4(v2.x,v2.x,0.0,1.0); - // } - - const std::string before_predefs = - R"(OpCapability Shader -%1 = OpExtInstImport "GLSL.std.450" -OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor -OpExecutionMode %main OriginUpperLeft -OpSource GLSL 450 -OpName %main "main" -OpName %In0 "In0" -OpName %In1 "In1" -OpName %In2 "In2" -OpName %_Globals_ "_Globals_" -OpMemberName %_Globals_ 0 "g_b" -OpMemberName %_Globals_ 1 "g_b2" -OpName %_ "" -OpName %OutColor "OutColor" -OpDecorate %In0 Location 0 -OpDecorate %In1 Location 1 -OpDecorate %In2 Location 2 -OpMemberDecorate %_Globals_ 0 Offset 0 -OpMemberDecorate %_Globals_ 1 Offset 4 -OpDecorate %_Globals_ Block -OpDecorate %_ DescriptorSet 0 -OpDecorate %_ Binding 0 -OpDecorate %OutColor Location 0 -%void = OpTypeVoid -%10 = OpTypeFunction %void -%float = OpTypeFloat 32 -%v4float = OpTypeVector %float 4 -%_ptr_Function_v4float = OpTypePointer Function %v4float -%_ptr_Input_v4float = OpTypePointer Input %v4float -%In0 = OpVariable %_ptr_Input_v4float Input -%_ptr_Input_float = OpTypePointer Input %float -%In1 = OpVariable %_ptr_Input_float Input -%In2 = OpVariable %_ptr_Input_float Input -%uint = OpTypeInt 32 0 -%_Globals_ = OpTypeStruct %uint %uint -%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ -%_ = OpVariable %_ptr_Uniform__Globals_ Uniform -%int = OpTypeInt 32 1 -%int_0 = OpConstant %int 0 -%_ptr_Uniform_uint = OpTypePointer Uniform %uint -%bool = OpTypeBool -%uint_0 = OpConstant %uint 0 -%float_1 = OpConstant %float 1 -%int_1 = OpConstant %int 1 -%float_0 = OpConstant %float 0 -%_ptr_Output_v4float = OpTypePointer Output %v4float -%OutColor = OpVariable %_ptr_Output_v4float Output -%27 = OpUndef %v4float -)"; - - const std::string after_predefs = - R"(OpCapability Shader -%1 = OpExtInstImport "GLSL.std.450" -OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor -OpExecutionMode %main OriginUpperLeft -OpSource GLSL 450 -OpName %main "main" -OpName %In0 "In0" -OpName %In1 "In1" -OpName %In2 "In2" -OpName %_Globals_ "_Globals_" -OpMemberName %_Globals_ 0 "g_b" -OpMemberName %_Globals_ 1 "g_b2" -OpName %_ "" -OpName %OutColor "OutColor" -OpDecorate %In0 Location 0 -OpDecorate %In1 Location 1 -OpDecorate %In2 Location 2 -OpMemberDecorate %_Globals_ 0 Offset 0 -OpMemberDecorate %_Globals_ 1 Offset 4 -OpDecorate %_Globals_ Block -OpDecorate %_ DescriptorSet 0 -OpDecorate %_ Binding 0 -OpDecorate %OutColor Location 0 -%void = OpTypeVoid -%10 = OpTypeFunction %void -%float = OpTypeFloat 32 -%v4float = OpTypeVector %float 4 -%_ptr_Function_v4float = OpTypePointer Function %v4float -%_ptr_Input_v4float = OpTypePointer Input %v4float -%In0 = OpVariable %_ptr_Input_v4float Input -%_ptr_Input_float = OpTypePointer Input %float -%In1 = OpVariable %_ptr_Input_float Input -%In2 = OpVariable %_ptr_Input_float Input -%uint = OpTypeInt 32 0 -%_Globals_ = OpTypeStruct %uint %uint -%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ -%_ = OpVariable %_ptr_Uniform__Globals_ Uniform -%int = OpTypeInt 32 1 -%int_0 = OpConstant %int 0 -%_ptr_Uniform_uint = OpTypePointer Uniform %uint -%bool = OpTypeBool -%uint_0 = OpConstant %uint 0 -%float_1 = OpConstant %float 1 -%int_1 = OpConstant %int 1 -%float_0 = OpConstant %float 0 -%_ptr_Output_v4float = OpTypePointer Output %v4float -%OutColor = OpVariable %_ptr_Output_v4float Output -%27 = OpUndef %v4float -)"; - - const std::string before = - R"(%main = OpFunction %void None %10 -%28 = OpLabel -%29 = OpLoad %v4float %In0 -%30 = OpLoad %float %In1 -%31 = OpLoad %float %In2 -%32 = OpFAdd %float %30 %31 -%33 = OpCompositeInsert %v4float %32 %29 1 -%34 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 -%35 = OpLoad %uint %34 -%36 = OpINotEqual %bool %35 %uint_0 -OpSelectionMerge %37 None -OpBranchConditional %36 %38 %37 -%38 = OpLabel -%39 = OpCompositeInsert %v4float %float_1 %33 0 -OpBranch %37 -%37 = OpLabel -%40 = OpPhi %v4float %33 %28 %39 %38 -%41 = OpCompositeExtract %float %40 0 -%42 = OpCompositeInsert %v4float %41 %27 0 -%43 = OpCompositeExtract %float %40 1 -%44 = OpCompositeInsert %v4float %43 %42 1 -%45 = OpAccessChain %_ptr_Uniform_uint %_ %int_1 -%46 = OpLoad %uint %45 -%47 = OpINotEqual %bool %46 %uint_0 -OpSelectionMerge %48 None -OpBranchConditional %47 %49 %48 -%49 = OpLabel -%50 = OpCompositeInsert %v4float %float_0 %44 0 -OpBranch %48 -%48 = OpLabel -%51 = OpPhi %v4float %44 %37 %50 %49 -%52 = OpCompositeExtract %float %51 0 -%53 = OpCompositeExtract %float %51 0 -%54 = OpCompositeConstruct %v4float %52 %53 %float_0 %float_1 -OpStore %OutColor %54 -OpReturn -OpFunctionEnd -)"; - - const std::string after = - R"(%main = OpFunction %void None %10 -%28 = OpLabel -%29 = OpLoad %v4float %In0 -%34 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 -%35 = OpLoad %uint %34 -%36 = OpINotEqual %bool %35 %uint_0 -OpSelectionMerge %37 None -OpBranchConditional %36 %38 %37 -%38 = OpLabel -%39 = OpCompositeInsert %v4float %float_1 %29 0 -OpBranch %37 -%37 = OpLabel -%40 = OpPhi %v4float %29 %28 %39 %38 -%41 = OpCompositeExtract %float %40 0 -%42 = OpCompositeInsert %v4float %41 %27 0 -%45 = OpAccessChain %_ptr_Uniform_uint %_ %int_1 -%46 = OpLoad %uint %45 -%47 = OpINotEqual %bool %46 %uint_0 -OpSelectionMerge %48 None -OpBranchConditional %47 %49 %48 -%49 = OpLabel -%50 = OpCompositeInsert %v4float %float_0 %42 0 -OpBranch %48 -%48 = OpLabel -%51 = OpPhi %v4float %42 %37 %50 %49 -%52 = OpCompositeExtract %float %51 0 -%53 = OpCompositeExtract %float %51 0 -%54 = OpCompositeConstruct %v4float %52 %53 %float_0 %float_1 -OpStore %OutColor %54 -OpReturn -OpFunctionEnd -)"; - - SinglePassRunAndCheck( - before_predefs + before, after_predefs + after, true, true); -} - -TEST_F(InsertExtractElimTest, DeadInsertInCycleToDo) { - // Dead insert in chain with cycle. Demonstrates analysis can handle - // cycles in chains. - // - // TODO(greg-lunarg): Improve algorithm to remove dead insert into v.y. Will - // likely require similar logic to ADCE. - // - // Note: The SPIR-V assembly has had store/load elimination - // performed to allow the inserts and extracts to directly - // reference each other. - // - // #version 450 - // - // layout (location=0) in vec4 In0; - // layout (location=1) in float In1; - // layout (location=2) in float In2; - // layout (location=0) out vec4 OutColor; - // - // layout(std140, binding = 0 ) uniform _Globals_ - // { - // int g_n ; - // }; - // - // void main() - // { - // vec2 v = vec2(0.0, 1.0); - // for (int i = 0; i < g_n; i++) { - // v.x = v.x + 1; - // v.y = v.y * 0.9; // dead - // } - // OutColor = vec4(v.x); - // } - - const std::string assembly = - R"(OpCapability Shader -%1 = OpExtInstImport "GLSL.std.450" -OpMemoryModel Logical GLSL450 -OpEntryPoint Fragment %main "main" %OutColor %In0 %In1 %In2 -OpExecutionMode %main OriginUpperLeft -OpSource GLSL 450 -OpName %main "main" -OpName %_Globals_ "_Globals_" -OpMemberName %_Globals_ 0 "g_n" -OpName %_ "" -OpName %OutColor "OutColor" -OpName %In0 "In0" -OpName %In1 "In1" -OpName %In2 "In2" -OpMemberDecorate %_Globals_ 0 Offset 0 -OpDecorate %_Globals_ Block -OpDecorate %_ DescriptorSet 0 -OpDecorate %_ Binding 0 -OpDecorate %OutColor Location 0 -OpDecorate %In0 Location 0 -OpDecorate %In1 Location 1 -OpDecorate %In2 Location 2 -%void = OpTypeVoid -%10 = OpTypeFunction %void -%float = OpTypeFloat 32 -%v2float = OpTypeVector %float 2 -%_ptr_Function_v2float = OpTypePointer Function %v2float -%float_0 = OpConstant %float 0 -%float_1 = OpConstant %float 1 -%16 = OpConstantComposite %v2float %float_0 %float_1 -%int = OpTypeInt 32 1 -%_ptr_Function_int = OpTypePointer Function %int -%int_0 = OpConstant %int 0 -%_Globals_ = OpTypeStruct %int -%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ -%_ = OpVariable %_ptr_Uniform__Globals_ Uniform -%_ptr_Uniform_int = OpTypePointer Uniform %int -%bool = OpTypeBool -%float_0_9 = OpConstant %float 0.9 -%int_1 = OpConstant %int 1 -%v4float = OpTypeVector %float 4 -%_ptr_Output_v4float = OpTypePointer Output %v4float -%OutColor = OpVariable %_ptr_Output_v4float Output -%_ptr_Input_v4float = OpTypePointer Input %v4float -%In0 = OpVariable %_ptr_Input_v4float Input -%_ptr_Input_float = OpTypePointer Input %float -%In1 = OpVariable %_ptr_Input_float Input -%In2 = OpVariable %_ptr_Input_float Input -%main = OpFunction %void None %10 -%29 = OpLabel -OpBranch %30 -%30 = OpLabel -%31 = OpPhi %v2float %16 %29 %32 %33 -%34 = OpPhi %int %int_0 %29 %35 %33 -OpLoopMerge %36 %33 None -OpBranch %37 -%37 = OpLabel -%38 = OpAccessChain %_ptr_Uniform_int %_ %int_0 -%39 = OpLoad %int %38 -%40 = OpSLessThan %bool %34 %39 -OpBranchConditional %40 %41 %36 -%41 = OpLabel -%42 = OpCompositeExtract %float %31 0 -%43 = OpFAdd %float %42 %float_1 -%44 = OpCompositeInsert %v2float %43 %31 0 -%45 = OpCompositeExtract %float %44 1 -%46 = OpFMul %float %45 %float_0_9 -%32 = OpCompositeInsert %v2float %46 %44 1 -OpBranch %33 -%33 = OpLabel -%35 = OpIAdd %int %34 %int_1 -OpBranch %30 -%36 = OpLabel -%47 = OpCompositeExtract %float %31 0 -%48 = OpCompositeConstruct %v4float %47 %47 %47 %47 -OpStore %OutColor %48 -OpReturn -OpFunctionEnd -)"; - - SinglePassRunAndCheck(assembly, assembly, true, - true); -} - // TODO(greg-lunarg): Add tests to verify handling of these cases: // diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp index c007b0aea..e072084e7 100644 --- a/tools/opt/opt.cpp +++ b/tools/opt/opt.cpp @@ -118,6 +118,10 @@ Options (in lexicographical order): --eliminate-dead-functions Deletes functions that cannot be reached from entry points or exported functions. + --eliminate-dead-insert + Deletes unreferenced inserts into composites, most notably + unused stores to vector components, that are not removed by + aggressive dead code elimination. --eliminate-dead-variables Deletes module scope variables that are not referenced. --eliminate-insert-extract @@ -425,6 +429,8 @@ OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer, optimizer->RegisterPass(CreateCommonUniformElimPass()); } else if (0 == strcmp(cur_arg, "--eliminate-dead-const")) { optimizer->RegisterPass(CreateEliminateDeadConstantPass()); + } else if (0 == strcmp(cur_arg, "--eliminate-dead-inserts")) { + optimizer->RegisterPass(CreateDeadInsertElimPass()); } else if (0 == strcmp(cur_arg, "--eliminate-dead-variables")) { optimizer->RegisterPass(CreateDeadVariableEliminationPass()); } else if (0 == strcmp(cur_arg, "--fold-spec-const-op-composite")) {