From 90af4a2b9c6f173b8f22b808c1ebc01c712d24b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D1=80=D0=B0=D0=BD=D0=B8=D0=BC=D0=B8=D1=80=20=D0=9A?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D1=9F=D0=B8=D1=9B?= Date: Fri, 15 Feb 2019 19:50:39 -0800 Subject: [PATCH] Updated spirv-tools. --- 3rdparty/spirv-tools/Android.mk | 1 + 3rdparty/spirv-tools/BUILD.gn | 4 +- .../include/generated/build-version.inc | 2 +- .../include/spirv-tools/optimizer.hpp | 5 + .../spirv-tools/source/opt/CMakeLists.txt | 2 + .../source/opt/block_merge_util.cpp | 5 + .../opt/eliminate_dead_members_pass.cpp | 624 ++++++++++ .../source/opt/eliminate_dead_members_pass.h | 145 +++ .../source/opt/instrument_pass.cpp | 18 +- 3rdparty/spirv-tools/source/opt/optimizer.cpp | 7 + 3rdparty/spirv-tools/source/opt/passes.h | 1 + .../merge_blocks_reduction_opportunity.cpp | 19 +- 3rdparty/spirv-tools/source/val/validate.cpp | 14 +- .../source/val/validate_decorations.cpp | 3 + .../source/val/validate_function.cpp | 24 +- .../spirv-tools/source/val/validate_type.cpp | 6 +- 3rdparty/spirv-tools/test/opt/CMakeLists.txt | 1 + .../spirv-tools/test/opt/block_merge_test.cpp | 43 + .../test/opt/eliminate_dead_member_test.cpp | 1031 +++++++++++++++++ .../test/opt/inst_bindless_check_test.cpp | 323 ++++++ .../spirv-tools/test/opt/optimizer_test.cpp | 177 ++- .../test/reduce/merge_blocks_test.cpp | 138 +++ 22 files changed, 2483 insertions(+), 110 deletions(-) create mode 100644 3rdparty/spirv-tools/source/opt/eliminate_dead_members_pass.cpp create mode 100644 3rdparty/spirv-tools/source/opt/eliminate_dead_members_pass.h create mode 100644 3rdparty/spirv-tools/test/opt/eliminate_dead_member_test.cpp diff --git a/3rdparty/spirv-tools/Android.mk b/3rdparty/spirv-tools/Android.mk index 986f39cad..8f4f5fea7 100644 --- a/3rdparty/spirv-tools/Android.mk +++ b/3rdparty/spirv-tools/Android.mk @@ -99,6 +99,7 @@ SPVTOOLS_OPT_SRC_FILES := \ source/opt/eliminate_dead_constant_pass.cpp \ source/opt/eliminate_dead_functions_pass.cpp \ source/opt/eliminate_dead_functions_util.cpp \ + source/opt/eliminate_dead_members_pass.cpp \ source/opt/feature_manager.cpp \ source/opt/flatten_decoration_pass.cpp \ source/opt/fold.cpp \ diff --git a/3rdparty/spirv-tools/BUILD.gn b/3rdparty/spirv-tools/BUILD.gn index 0df5e138c..2d63ca13b 100644 --- a/3rdparty/spirv-tools/BUILD.gn +++ b/3rdparty/spirv-tools/BUILD.gn @@ -305,7 +305,6 @@ source_set("spvtools_headers") { static_library("spvtools") { deps = [ - ":spvtools_core_enums_unified1", ":spvtools_core_tables_unified1", ":spvtools_generators_inc", ":spvtools_glsl_tables_glsl1-0", @@ -376,6 +375,7 @@ static_library("spvtools") { ] public_deps = [ + ":spvtools_core_enums_unified1", ":spvtools_headers", ] @@ -498,6 +498,8 @@ static_library("spvtools_opt") { "source/opt/eliminate_dead_functions_pass.h", "source/opt/eliminate_dead_functions_util.cpp", "source/opt/eliminate_dead_functions_util.h", + "source/opt/eliminate_dead_members_pass.cpp", + "source/opt/eliminate_dead_members_pass.h", "source/opt/feature_manager.cpp", "source/opt/feature_manager.h", "source/opt/flatten_decoration_pass.cpp", diff --git a/3rdparty/spirv-tools/include/generated/build-version.inc b/3rdparty/spirv-tools/include/generated/build-version.inc index 86995c085..06a3ac15d 100644 --- a/3rdparty/spirv-tools/include/generated/build-version.inc +++ b/3rdparty/spirv-tools/include/generated/build-version.inc @@ -1 +1 @@ -"v2019.2-dev", "SPIRV-Tools v2019.2-dev c3405c13ff287fcc881a3244dafd761d40edaa50" +"v2019.2-dev", "SPIRV-Tools v2019.2-dev f237f9cff729a0b78c8e979a4da7f4428bfeeaf2" diff --git a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp index a496f9daf..e2d5e46f9 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp @@ -226,6 +226,11 @@ Optimizer::PassToken CreateStripReflectInfoPass(); // functions are not needed because they will never be called. Optimizer::PassToken CreateEliminateDeadFunctionsPass(); +// Creates an eliminate-dead-members pass. +// An eliminate-dead-members pass will remove all unused members of structures. +// This will not affect the data layout of the remaining members. +Optimizer::PassToken CreateEliminateDeadMembersPass(); + // Creates a set-spec-constant-default-value pass from a mapping from spec-ids // to the default values in the form of string. // A set-spec-constant-default-value pass sets the default values for the diff --git a/3rdparty/spirv-tools/source/opt/CMakeLists.txt b/3rdparty/spirv-tools/source/opt/CMakeLists.txt index e1c26f089..f2ffcca8c 100644 --- a/3rdparty/spirv-tools/source/opt/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/opt/CMakeLists.txt @@ -38,6 +38,7 @@ set(SPIRV_TOOLS_OPT_SOURCES eliminate_dead_constant_pass.h eliminate_dead_functions_pass.h eliminate_dead_functions_util.h + eliminate_dead_members_pass.h feature_manager.h flatten_decoration_pass.h fold.h @@ -133,6 +134,7 @@ set(SPIRV_TOOLS_OPT_SOURCES eliminate_dead_constant_pass.cpp eliminate_dead_functions_pass.cpp eliminate_dead_functions_util.cpp + eliminate_dead_members_pass.cpp feature_manager.cpp flatten_decoration_pass.cpp fold.cpp diff --git a/3rdparty/spirv-tools/source/opt/block_merge_util.cpp b/3rdparty/spirv-tools/source/opt/block_merge_util.cpp index 5d6048aef..107723dfd 100644 --- a/3rdparty/spirv-tools/source/opt/block_merge_util.cpp +++ b/3rdparty/spirv-tools/source/opt/block_merge_util.cpp @@ -86,6 +86,11 @@ bool CanMergeWithSuccessor(IRContext* context, BasicBlock* block) { return false; } + // Don't bother trying to merge unreachable blocks. + if (auto dominators = context->GetDominatorAnalysis(block->GetParent())) { + if (!dominators->IsReachable(block)) return false; + } + Instruction* merge_inst = block->GetMergeInst(); const bool pred_is_header = IsHeader(block); if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) { diff --git a/3rdparty/spirv-tools/source/opt/eliminate_dead_members_pass.cpp b/3rdparty/spirv-tools/source/opt/eliminate_dead_members_pass.cpp new file mode 100644 index 000000000..d9cfb5cab --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/eliminate_dead_members_pass.cpp @@ -0,0 +1,624 @@ +// Copyright (c) 2019 Google LLC +// +// 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_members_pass.h" + +#include "ir_builder.h" +#include "source/opt/ir_context.h" + +namespace { +const uint32_t kRemovedMember = 0xFFFFFFFF; +} + +namespace spvtools { +namespace opt { + +Pass::Status EliminateDeadMembersPass::Process() { + if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader)) + return Status::SuccessWithoutChange; + + FindLiveMembers(); + if (RemoveDeadMembers()) { + return Status::SuccessWithChange; + } + return Status::SuccessWithoutChange; +} + +void EliminateDeadMembersPass::FindLiveMembers() { + // Until we have implemented the rewritting of OpSpecConsantOp instructions, + // we have to mark them as fully used just to be safe. + for (auto& inst : get_module()->types_values()) { + if (inst.opcode() != SpvOpSpecConstantOp) { + continue; + } + MarkTypeAsFullyUsed(inst.type_id()); + } + + for (const Function& func : *get_module()) { + FindLiveMembers(func); + } +} + +void EliminateDeadMembersPass::FindLiveMembers(const Function& function) { + function.ForEachInst( + [this](const Instruction* inst) { FindLiveMembers(inst); }); +} + +void EliminateDeadMembersPass::FindLiveMembers(const Instruction* inst) { + switch (inst->opcode()) { + case SpvOpStore: + MarkMembersAsLiveForStore(inst); + break; + case SpvOpCopyMemory: + case SpvOpCopyMemorySized: + MarkMembersAsLiveForCopyMemory(inst); + break; + case SpvOpCompositeExtract: + MarkMembersAsLiveForExtract(inst); + break; + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpPtrAccessChain: + case SpvOpInBoundsPtrAccessChain: + MarkMembersAsLiveForAccessChain(inst); + break; + case SpvOpReturnValue: + // This should be an issue only if we are returning from the entry point. + // However, for now I will keep it more conservative because functions are + // often inlined leaving only the entry points. + MarkOperandTypeAsFullyUsed(inst, 0); + break; + case SpvOpArrayLength: + MarkMembersAsLiveForArrayLength(inst); + break; + case SpvOpLoad: + case SpvOpCompositeInsert: + case SpvOpCompositeConstruct: + break; + default: + // This path is here for safety. All instructions that can reference + // structs in a function body should be handled above. However, this will + // keep the pass valid, but not optimal, as new instructions get added + // or if something was missed. + MarkStructOperandsAsFullyUsed(inst); + break; + } +} + +void EliminateDeadMembersPass::MarkMembersAsLiveForStore( + const Instruction* inst) { + // We should only have to mark the members as live if the store is to + // memory that is read outside of the shader. Other passes can remove all + // store to memory that is not visible outside of the shader, so we do not + // complicate the code for now. + assert(inst->opcode() == SpvOpStore); + uint32_t object_id = inst->GetSingleWordInOperand(1); + Instruction* object_inst = context()->get_def_use_mgr()->GetDef(object_id); + uint32_t object_type_id = object_inst->type_id(); + MarkTypeAsFullyUsed(object_type_id); +} + +void EliminateDeadMembersPass::MarkTypeAsFullyUsed(uint32_t type_id) { + Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); + assert(type_inst != nullptr); + if (type_inst->opcode() != SpvOpTypeStruct) { + return; + } + + // Mark every member of the current struct as used. + for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) { + used_members_[type_id].insert(i); + } + + // Mark any sub struct as fully used. + for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) { + MarkTypeAsFullyUsed(type_inst->GetSingleWordInOperand(i)); + } +} + +void EliminateDeadMembersPass::MarkMembersAsLiveForCopyMemory( + const Instruction* inst) { + uint32_t target_id = inst->GetSingleWordInOperand(0); + Instruction* target_inst = get_def_use_mgr()->GetDef(target_id); + uint32_t pointer_type_id = target_inst->type_id(); + Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id); + uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1); + MarkTypeAsFullyUsed(type_id); +} + +void EliminateDeadMembersPass::MarkMembersAsLiveForExtract( + const Instruction* inst) { + assert(inst->opcode() == SpvOpCompositeExtract); + + uint32_t composite_id = inst->GetSingleWordInOperand(0); + Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id); + uint32_t type_id = composite_inst->type_id(); + + for (uint32_t i = 1; i < inst->NumInOperands(); ++i) { + Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); + uint32_t member_idx = inst->GetSingleWordInOperand(i); + switch (type_inst->opcode()) { + case SpvOpTypeStruct: + used_members_[type_id].insert(member_idx); + type_id = type_inst->GetSingleWordInOperand(member_idx); + break; + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + case SpvOpTypeVector: + case SpvOpTypeMatrix: + type_id = type_inst->GetSingleWordInOperand(0); + break; + default: + assert(false); + } + } +} + +void EliminateDeadMembersPass::MarkMembersAsLiveForAccessChain( + const Instruction* inst) { + assert(inst->opcode() == SpvOpAccessChain || + inst->opcode() == SpvOpInBoundsAccessChain || + inst->opcode() == SpvOpPtrAccessChain || + inst->opcode() == SpvOpInBoundsPtrAccessChain); + + uint32_t pointer_id = inst->GetSingleWordInOperand(0); + Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id); + uint32_t pointer_type_id = pointer_inst->type_id(); + Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id); + uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1); + + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + + // For a pointer access chain, we need to skip the |element| index. It is not + // a reference to the member of a struct, and it does not change the type. + uint32_t i = (inst->opcode() == SpvOpAccessChain || + inst->opcode() == SpvOpInBoundsAccessChain + ? 1 + : 2); + for (; i < inst->NumInOperands(); ++i) { + Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); + switch (type_inst->opcode()) { + case SpvOpTypeStruct: { + const analysis::IntConstant* member_idx = + const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i)) + ->AsIntConstant(); + assert(member_idx); + if (member_idx->type()->AsInteger()->width() == 32) { + used_members_[type_id].insert(member_idx->GetU32()); + type_id = type_inst->GetSingleWordInOperand(member_idx->GetU32()); + } else { + used_members_[type_id].insert( + static_cast(member_idx->GetU64())); + type_id = type_inst->GetSingleWordInOperand( + static_cast(member_idx->GetU64())); + } + } break; + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + case SpvOpTypeVector: + case SpvOpTypeMatrix: + type_id = type_inst->GetSingleWordInOperand(0); + break; + default: + assert(false); + } + } +} + +void EliminateDeadMembersPass::MarkOperandTypeAsFullyUsed( + const Instruction* inst, uint32_t in_idx) { + uint32_t op_id = inst->GetSingleWordInOperand(in_idx); + Instruction* op_inst = get_def_use_mgr()->GetDef(op_id); + MarkTypeAsFullyUsed(op_inst->type_id()); +} + +void EliminateDeadMembersPass::MarkMembersAsLiveForArrayLength( + const Instruction* inst) { + assert(inst->opcode() == SpvOpArrayLength); + uint32_t object_id = inst->GetSingleWordInOperand(0); + Instruction* object_inst = get_def_use_mgr()->GetDef(object_id); + uint32_t pointer_type_id = object_inst->type_id(); + Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id); + uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1); + used_members_[type_id].insert(inst->GetSingleWordInOperand(1)); +} + +bool EliminateDeadMembersPass::RemoveDeadMembers() { + bool modified = false; + + // First update all of the OpTypeStruct instructions. + get_module()->ForEachInst([&modified, this](Instruction* inst) { + switch (inst->opcode()) { + case SpvOpTypeStruct: + modified |= UpdateOpTypeStruct(inst); + break; + default: + break; + } + }); + + // Now update all of the instructions that reference the OpTypeStructs. + get_module()->ForEachInst([&modified, this](Instruction* inst) { + switch (inst->opcode()) { + case SpvOpMemberName: + modified |= UpdateOpMemberNameOrDecorate(inst); + break; + case SpvOpMemberDecorate: + modified |= UpdateOpMemberNameOrDecorate(inst); + break; + case SpvOpGroupMemberDecorate: + modified |= UpdateOpGroupMemberDecorate(inst); + break; + case SpvOpSpecConstantComposite: + case SpvOpConstantComposite: + case SpvOpCompositeConstruct: + modified |= UpdateConstantComposite(inst); + break; + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpPtrAccessChain: + case SpvOpInBoundsPtrAccessChain: + modified |= UpdateAccessChain(inst); + break; + case SpvOpCompositeExtract: + modified |= UpdateCompsiteExtract(inst); + break; + case SpvOpCompositeInsert: + modified |= UpdateCompositeInsert(inst); + break; + case SpvOpArrayLength: + modified |= UpdateOpArrayLength(inst); + break; + case SpvOpSpecConstantOp: + assert(false && "Not yet implemented."); + // with OpCompositeExtract, OpCompositeInsert + // For kernels: OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain, + // OpInBoundsPtrAccessChain + break; + default: + break; + } + }); + return modified; +} + +bool EliminateDeadMembersPass::UpdateOpTypeStruct(Instruction* inst) { + assert(inst->opcode() == SpvOpTypeStruct); + + const auto& live_members = used_members_[inst->result_id()]; + if (live_members.size() == inst->NumInOperands()) { + return false; + } + + Instruction::OperandList new_operands; + for (uint32_t idx : live_members) { + new_operands.emplace_back(inst->GetInOperand(idx)); + } + + inst->SetInOperands(std::move(new_operands)); + context()->UpdateDefUse(inst); + return true; +} + +bool EliminateDeadMembersPass::UpdateOpMemberNameOrDecorate(Instruction* inst) { + assert(inst->opcode() == SpvOpMemberName || + inst->opcode() == SpvOpMemberDecorate); + + uint32_t type_id = inst->GetSingleWordInOperand(0); + auto live_members = used_members_.find(type_id); + if (live_members == used_members_.end()) { + return false; + } + + uint32_t orig_member_idx = inst->GetSingleWordInOperand(1); + uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx); + + if (new_member_idx == kRemovedMember) { + context()->KillInst(inst); + return true; + } + + if (new_member_idx == orig_member_idx) { + return false; + } + + inst->SetInOperand(1, {new_member_idx}); + return true; +} + +bool EliminateDeadMembersPass::UpdateOpGroupMemberDecorate(Instruction* inst) { + assert(inst->opcode() == SpvOpGroupMemberDecorate); + + bool modified = false; + + Instruction::OperandList new_operands; + new_operands.emplace_back(inst->GetInOperand(0)); + for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) { + uint32_t type_id = inst->GetSingleWordInOperand(i); + uint32_t member_idx = inst->GetSingleWordInOperand(i + 1); + uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx); + + if (new_member_idx == kRemovedMember) { + modified = true; + continue; + } + + new_operands.emplace_back(inst->GetOperand(i)); + if (new_member_idx != member_idx) { + new_operands.emplace_back( + Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}})); + modified = true; + } else { + new_operands.emplace_back(inst->GetOperand(i + 1)); + } + } + + if (!modified) { + return false; + } + + if (new_operands.size() == 1) { + context()->KillInst(inst); + return true; + } + + inst->SetInOperands(std::move(new_operands)); + context()->UpdateDefUse(inst); + return true; +} + +bool EliminateDeadMembersPass::UpdateConstantComposite(Instruction* inst) { + assert(inst->opcode() == SpvOpConstantComposite || + inst->opcode() == SpvOpCompositeConstruct); + uint32_t type_id = inst->type_id(); + + bool modified = false; + Instruction::OperandList new_operands; + for (uint32_t i = 0; i < inst->NumInOperands(); ++i) { + uint32_t new_idx = GetNewMemberIndex(type_id, i); + if (new_idx == kRemovedMember) { + modified = true; + } else { + new_operands.emplace_back(inst->GetInOperand(i)); + } + } + inst->SetInOperands(std::move(new_operands)); + context()->UpdateDefUse(inst); + return modified; +} + +bool EliminateDeadMembersPass::UpdateAccessChain(Instruction* inst) { + assert(inst->opcode() == SpvOpAccessChain || + inst->opcode() == SpvOpInBoundsAccessChain || + inst->opcode() == SpvOpPtrAccessChain || + inst->opcode() == SpvOpInBoundsPtrAccessChain); + + uint32_t pointer_id = inst->GetSingleWordInOperand(0); + Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id); + uint32_t pointer_type_id = pointer_inst->type_id(); + Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id); + uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1); + + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + Instruction::OperandList new_operands; + bool modified = false; + new_operands.emplace_back(inst->GetInOperand(0)); + + // For pointer access chains we want to copy the element operand. + if (inst->opcode() == SpvOpPtrAccessChain || + inst->opcode() == SpvOpInBoundsPtrAccessChain) { + new_operands.emplace_back(inst->GetInOperand(1)); + } + + for (uint32_t i = static_cast(new_operands.size()); + i < inst->NumInOperands(); ++i) { + Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); + switch (type_inst->opcode()) { + case SpvOpTypeStruct: { + const analysis::IntConstant* member_idx = + const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i)) + ->AsIntConstant(); + assert(member_idx); + uint32_t orig_member_idx; + if (member_idx->type()->AsInteger()->width() == 32) { + orig_member_idx = member_idx->GetU32(); + } else { + orig_member_idx = static_cast(member_idx->GetU64()); + } + uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx); + assert(new_member_idx != kRemovedMember); + if (orig_member_idx != new_member_idx) { + InstructionBuilder ir_builder( + context(), inst, + IRContext::kAnalysisDefUse | + IRContext::kAnalysisInstrToBlockMapping); + uint32_t const_id = + ir_builder.GetUintConstant(new_member_idx)->result_id(); + new_operands.emplace_back(Operand({SPV_OPERAND_TYPE_ID, {const_id}})); + modified = true; + } else { + new_operands.emplace_back(inst->GetInOperand(i)); + } + // The type will have already been rewritten, so use the new member + // index. + type_id = type_inst->GetSingleWordInOperand(new_member_idx); + } break; + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + case SpvOpTypeVector: + case SpvOpTypeMatrix: + new_operands.emplace_back(inst->GetInOperand(i)); + type_id = type_inst->GetSingleWordInOperand(0); + break; + default: + assert(false); + break; + } + } + + if (!modified) { + return false; + } + inst->SetInOperands(std::move(new_operands)); + context()->UpdateDefUse(inst); + return true; +} + +uint32_t EliminateDeadMembersPass::GetNewMemberIndex(uint32_t type_id, + uint32_t member_idx) { + auto live_members = used_members_.find(type_id); + if (live_members == used_members_.end()) { + return member_idx; + } + + auto current_member = live_members->second.find(member_idx); + if (current_member == live_members->second.end()) { + return kRemovedMember; + } + + return static_cast( + std::distance(live_members->second.begin(), current_member)); +} + +bool EliminateDeadMembersPass::UpdateCompsiteExtract(Instruction* inst) { + uint32_t object_id = inst->GetSingleWordInOperand(0); + Instruction* object_inst = get_def_use_mgr()->GetDef(object_id); + uint32_t type_id = object_inst->type_id(); + + Instruction::OperandList new_operands; + bool modified = false; + new_operands.emplace_back(inst->GetInOperand(0)); + for (uint32_t i = 1; i < inst->NumInOperands(); ++i) { + uint32_t member_idx = inst->GetSingleWordInOperand(i); + uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx); + assert(new_member_idx != kRemovedMember); + if (member_idx != new_member_idx) { + modified = true; + } + new_operands.emplace_back( + Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}})); + + Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); + switch (type_inst->opcode()) { + case SpvOpTypeStruct: + assert(i != 1 || (inst->opcode() != SpvOpPtrAccessChain && + inst->opcode() != SpvOpInBoundsPtrAccessChain)); + // The type will have already been rewriten, so use the new member + // index. + type_id = type_inst->GetSingleWordInOperand(new_member_idx); + break; + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + case SpvOpTypeVector: + case SpvOpTypeMatrix: + type_id = type_inst->GetSingleWordInOperand(0); + break; + default: + assert(false); + } + } + + if (!modified) { + return false; + } + inst->SetInOperands(std::move(new_operands)); + context()->UpdateDefUse(inst); + return true; +} + +bool EliminateDeadMembersPass::UpdateCompositeInsert(Instruction* inst) { + uint32_t composite_id = inst->GetSingleWordInOperand(1); + Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id); + uint32_t type_id = composite_inst->type_id(); + + Instruction::OperandList new_operands; + bool modified = false; + new_operands.emplace_back(inst->GetInOperand(0)); + new_operands.emplace_back(inst->GetInOperand(1)); + for (uint32_t i = 2; i < inst->NumInOperands(); ++i) { + uint32_t member_idx = inst->GetSingleWordInOperand(i); + uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx); + if (new_member_idx == kRemovedMember) { + context()->KillInst(inst); + return true; + } + + if (member_idx != new_member_idx) { + modified = true; + } + new_operands.emplace_back( + Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}})); + + Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); + switch (type_inst->opcode()) { + case SpvOpTypeStruct: + // The type will have already been rewritten, so use the new member + // index. + type_id = type_inst->GetSingleWordInOperand(new_member_idx); + break; + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + case SpvOpTypeVector: + case SpvOpTypeMatrix: + type_id = type_inst->GetSingleWordInOperand(0); + break; + default: + assert(false); + } + } + + if (!modified) { + return false; + } + inst->SetInOperands(std::move(new_operands)); + context()->UpdateDefUse(inst); + return true; +} + +bool EliminateDeadMembersPass::UpdateOpArrayLength(Instruction* inst) { + uint32_t struct_id = inst->GetSingleWordInOperand(0); + Instruction* struct_inst = get_def_use_mgr()->GetDef(struct_id); + uint32_t pointer_type_id = struct_inst->type_id(); + Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id); + uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1); + + uint32_t member_idx = inst->GetSingleWordInOperand(1); + uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx); + assert(new_member_idx != kRemovedMember); + + if (member_idx == new_member_idx) { + return false; + } + + inst->SetInOperand(1, {new_member_idx}); + context()->UpdateDefUse(inst); + return true; +} + +void EliminateDeadMembersPass::MarkStructOperandsAsFullyUsed( + const Instruction* inst) { + if (inst->type_id() != 0) { + MarkTypeAsFullyUsed(inst->type_id()); + } + + inst->ForEachInId([this](const uint32_t* id) { + Instruction* instruction = get_def_use_mgr()->GetDef(*id); + if (instruction->type_id() != 0) { + MarkTypeAsFullyUsed(instruction->type_id()); + } + }); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/eliminate_dead_members_pass.h b/3rdparty/spirv-tools/source/opt/eliminate_dead_members_pass.h new file mode 100644 index 000000000..8729f51c2 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/eliminate_dead_members_pass.h @@ -0,0 +1,145 @@ +// Copyright (c) 2019 Google LLC +// +// 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_MEMBERS_PASS_H_ +#define SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_ + +#include "source/opt/def_use_manager.h" +#include "source/opt/function.h" +#include "source/opt/mem_pass.h" +#include "source/opt/module.h" + +namespace spvtools { +namespace opt { + +// Remove unused members from structures. The remaining members will remain at +// the same offset. +class EliminateDeadMembersPass : public MemPass { + public: + const char* name() const override { return "eliminate-dead-members"; } + Status Process() override; + + IRContext::Analysis GetPreservedAnalyses() override { + return IRContext::kAnalysisDefUse | + IRContext::kAnalysisInstrToBlockMapping | + IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG | + IRContext::kAnalysisDominatorAnalysis | + IRContext::kAnalysisLoopAnalysis | + IRContext::kAnalysisScalarEvolution | + IRContext::kAnalysisRegisterPressure | + IRContext::kAnalysisValueNumberTable | + IRContext::kAnalysisStructuredCFG | + IRContext::kAnalysisBuiltinVarId | + IRContext::kAnalysisIdToFuncMapping; + } + + private: + // Populate |used_members_| with the member of structures that are live in the + // current context. + void FindLiveMembers(); + + // Add to |used_members_| the member of structures that are live in + // |function|. + void FindLiveMembers(const Function& function); + // Add to |used_members_| the member of structures that are live in |inst|. + void FindLiveMembers(const Instruction* inst); + + // Add to |used_members_| the members that are live in the |OpStore| + // instruction |inst|. + void MarkMembersAsLiveForStore(const Instruction* inst); + + // Add to |used_members_| the members that are live in the |OpCopyMemory*| + // instruction |inst|. + void MarkMembersAsLiveForCopyMemory(const Instruction* inst); + + // Add to |used_members_| the members that are live in the + // |OpCompositeExtract| instruction |inst|. + void MarkMembersAsLiveForExtract(const Instruction* inst); + + // Add to |used_members_| the members that are live in the |Op*AccessChain| + // instruction |inst|. + void MarkMembersAsLiveForAccessChain(const Instruction* inst); + + // Add the member referenced by the OpArrayLength instruction |inst| to + // |uses_members_|. + void MarkMembersAsLiveForArrayLength(const Instruction* inst); + + // Remove dead members from structs and updates any instructions that need to + // be updated as a consequence. Return true if something changed. + bool RemoveDeadMembers(); + + // Update |inst|, which must be an |OpMemberName| or |OpMemberDecorate| + // instruction, so it references the correct member after the struct is + // updated. Return true if something changed. + bool UpdateOpMemberNameOrDecorate(Instruction* inst); + + // Update |inst|, which must be an |OpGroupMemberDecorate| instruction, so it + // references the correct member after the struct is updated. Return true if + // something changed. + bool UpdateOpGroupMemberDecorate(Instruction* inst); + + // Update the |OpTypeStruct| instruction |inst| my removing the members that + // are not live. Return true if something changed. + bool UpdateOpTypeStruct(Instruction* inst); + + // Update the |OpConstantComposite| instruction |inst| to match the change + // made to the type that was being generated. Return true if something + // changed. + bool UpdateConstantComposite(Instruction* inst); + + // Update the |Op*AccessChain| instruction |inst| to reference the correct + // members. All members referenced in the access chain must be live. This + // function must be called after the |OpTypeStruct| instruction for the type + // has been updated. Return true if something changed. + bool UpdateAccessChain(Instruction* inst); + + // Update the |OpCompositeExtract| instruction |inst| to reference the correct + // members. All members referenced in the instruction must be live. This + // function must be called after the |OpTypeStruct| instruction for the type + // has been updated. Return true if something changed. + bool UpdateCompsiteExtract(Instruction* inst); + + // Update the |OpCompositeInsert| instruction |inst| to reference the correct + // members. If the member being inserted is not live, then |inst| is killed. + // This function must be called after the |OpTypeStruct| instruction for the + // type has been updated. Return true if something changed. + bool UpdateCompositeInsert(Instruction* inst); + + // Update the |OpArrayLength| instruction |inst| to reference the correct + // member. The member referenced in the instruction must be live. Return true + // if something changed. + bool UpdateOpArrayLength(Instruction* inst); + + // Add all of the members of type |type_id| and members of any subtypes to + // |used_members_|. + void MarkTypeAsFullyUsed(uint32_t type_id); + + // Add all of the members of the type of the operand |in_idx| in |inst| and + // members of any subtypes to |uses_members_|. + void MarkOperandTypeAsFullyUsed(const Instruction* inst, uint32_t in_idx); + + // Return the index of the member that use to be the |member_idx|th member of + // |type_id|. If the member has been removed, |kRemovedMember| is returned. + uint32_t GetNewMemberIndex(uint32_t type_id, uint32_t member_idx); + + // A map from a type id to a set of indices representing the members of the + // type that are used, and must be kept. + std::unordered_map> used_members_; + void MarkStructOperandsAsFullyUsed(const Instruction* inst); +}; + +} // namespace opt +} // namespace spvtools + +#endif // SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/instrument_pass.cpp b/3rdparty/spirv-tools/source/opt/instrument_pass.cpp index 58bfd27fe..abd6cb8a4 100644 --- a/3rdparty/spirv-tools/source/opt/instrument_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/instrument_pass.cpp @@ -614,8 +614,14 @@ bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx, ++function_idx; } std::vector> new_blks; - // Start count after function instruction + // Start count after function and param instructions uint32_t instruction_idx = funcIdx2offset_[function_idx] + 1; + func->ForEachParam( + [this, &instruction_idx](const Instruction* i) { + (void)i; + ++instruction_idx; + }, + true); // Using block iterators here because of block erasures and insertions. for (auto bi = func->begin(); bi != func->end(); ++bi) { // Count block's label @@ -784,8 +790,14 @@ void InstrumentPass::InitializeInstrument() { auto prev_fn = get_module()->begin(); auto curr_fn = prev_fn; for (++curr_fn; curr_fn != get_module()->end(); ++curr_fn) { - // Count function and end instructions + // Count function, end and param instructions uint32_t func_size = 2; + prev_fn->ForEachParam( + [this, &func_size](const Instruction* i) { + (void)i; + ++func_size; + }, + true); for (auto& blk : *prev_fn) { // Count label func_size += 1; @@ -794,7 +806,7 @@ void InstrumentPass::InitializeInstrument() { func_size += static_cast(inst.dbg_line_insts().size()); } } - funcIdx2offset_[func_idx] = func_size; + funcIdx2offset_[func_idx] = funcIdx2offset_[func_idx - 1] + func_size; ++prev_fn; ++func_idx; } diff --git a/3rdparty/spirv-tools/source/opt/optimizer.cpp b/3rdparty/spirv-tools/source/opt/optimizer.cpp index 9d3cad7df..344f3dc3b 100644 --- a/3rdparty/spirv-tools/source/opt/optimizer.cpp +++ b/3rdparty/spirv-tools/source/opt/optimizer.cpp @@ -332,6 +332,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { RegisterPass(CreateDeadInsertElimPass()); } else if (pass_name == "eliminate-dead-variables") { RegisterPass(CreateDeadVariableEliminationPass()); + } else if (pass_name == "eliminate-dead-members") { + RegisterPass(CreateEliminateDeadMembersPass()); } else if (pass_name == "fold-spec-const-op-composite") { RegisterPass(CreateFoldSpecConstantOpAndCompositePass()); } else if (pass_name == "loop-unswitch") { @@ -542,6 +544,11 @@ Optimizer::PassToken CreateEliminateDeadFunctionsPass() { MakeUnique()); } +Optimizer::PassToken CreateEliminateDeadMembersPass() { + return MakeUnique( + MakeUnique()); +} + Optimizer::PassToken CreateSetSpecConstantDefaultValuePass( const std::unordered_map& id_value_map) { return MakeUnique( diff --git a/3rdparty/spirv-tools/source/opt/passes.h b/3rdparty/spirv-tools/source/opt/passes.h index f7b675e28..6f8081c94 100644 --- a/3rdparty/spirv-tools/source/opt/passes.h +++ b/3rdparty/spirv-tools/source/opt/passes.h @@ -31,6 +31,7 @@ #include "source/opt/dead_variable_elimination.h" #include "source/opt/eliminate_dead_constant_pass.h" #include "source/opt/eliminate_dead_functions_pass.h" +#include "source/opt/eliminate_dead_members_pass.h" #include "source/opt/flatten_decoration_pass.h" #include "source/opt/fold_spec_constant_op_and_composite_pass.h" #include "source/opt/freeze_spec_constant_value_pass.h" diff --git a/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity.cpp b/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity.cpp index 8ee4562a7..8de1e09af 100644 --- a/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity.cpp +++ b/3rdparty/spirv-tools/source/reduce/merge_blocks_reduction_opportunity.cpp @@ -34,9 +34,21 @@ MergeBlocksReductionOpportunity::MergeBlocksReductionOpportunity( } bool MergeBlocksReductionOpportunity::PreconditionHolds() { - // By construction, it is not possible for the merging of A->B to disable the - // merging of C->D, even when B and C are the same block. - return true; + // Merge block opportunities can disable each other. + // Example: Given blocks: A->B->C. + // A is a loop header; B and C are blocks in the loop; C ends with OpReturn. + // There are two opportunities: B and C can be merged with their predecessors. + // Merge C. B now ends with OpReturn. We now just have: A->B. + // Merge B is now disabled, as this would lead to A, a loop header, ending + // with an OpReturn, which is invalid. + + const auto predecessors = context_->cfg()->preds(successor_block_->id()); + assert(1 == predecessors.size() && + "For a successor to be merged into its predecessor, exactly one " + "predecessor must be present."); + const uint32_t predecessor_id = predecessors[0]; + BasicBlock* predecessor_block = context_->get_instr_block(predecessor_id); + return blockmergeutil::CanMergeWithSuccessor(context_, predecessor_block); } void MergeBlocksReductionOpportunity::Apply() { @@ -50,6 +62,7 @@ void MergeBlocksReductionOpportunity::Apply() { "predecessor must be present."); const uint32_t predecessor_id = predecessors[0]; + // We need an iterator pointing to the predecessor, hence the loop. for (auto bi = function_->begin(); bi != function_->end(); ++bi) { if (bi->id() == predecessor_id) { blockmergeutil::MergeWithSuccessor(context_, function_, bi); diff --git a/3rdparty/spirv-tools/source/val/validate.cpp b/3rdparty/spirv-tools/source/val/validate.cpp index 4024f6169..b145b19d8 100644 --- a/3rdparty/spirv-tools/source/val/validate.cpp +++ b/3rdparty/spirv-tools/source/val/validate.cpp @@ -385,7 +385,6 @@ spv_result_t ValidateBinaryUsingContextAndValidationState( Instruction* inst = const_cast(&instruction); vstate->RegisterInstruction(inst); } - if (auto error = UpdateIdUse(*vstate, &instruction)) return error; } if (!vstate->has_memory_model_specified()) @@ -399,6 +398,19 @@ spv_result_t ValidateBinaryUsingContextAndValidationState( // Catch undefined forward references before performing further checks. if (auto error = ValidateForwardDecls(*vstate)) return error; + // ID usage needs be handled in its own iteration of the instructions, + // between the two others. It depends on the first loop to have been + // finished, so that all instructions have been registered. And the following + // loop depends on all of the usage data being populated. Thus it cannot live + // in either of those iterations. + // It should also live after the forward declaration check, since it will + // have problems with missing forward declarations, but give less useful error + // messages. + for (size_t i = 0; i < vstate->ordered_instructions().size(); ++i) { + auto& instruction = vstate->ordered_instructions()[i]; + if (auto error = UpdateIdUse(*vstate, &instruction)) return error; + } + // Validate individual opcodes. for (size_t i = 0; i < vstate->ordered_instructions().size(); ++i) { auto& instruction = vstate->ordered_instructions()[i]; diff --git a/3rdparty/spirv-tools/source/val/validate_decorations.cpp b/3rdparty/spirv-tools/source/val/validate_decorations.cpp index f0ad17749..cccf69127 100644 --- a/3rdparty/spirv-tools/source/val/validate_decorations.cpp +++ b/3rdparty/spirv-tools/source/val/validate_decorations.cpp @@ -1185,6 +1185,9 @@ spv_result_t CheckFPRoundingModeForShaders(ValidationState_t& vstate, // Validates Object operand of an OpStore for (const auto& use : inst.uses()) { const auto store = use.first; + if (store->opcode() == SpvOpFConvert) continue; + if (spvOpcodeIsDebug(store->opcode())) continue; + if (spvOpcodeIsDecoration(store->opcode())) continue; if (store->opcode() != SpvOpStore) { return vstate.diag(SPV_ERROR_INVALID_ID, &inst) << "FPRoundingMode decoration can be applied only to the " diff --git a/3rdparty/spirv-tools/source/val/validate_function.cpp b/3rdparty/spirv-tools/source/val/validate_function.cpp index 96c477698..2f485ce71 100644 --- a/3rdparty/spirv-tools/source/val/validate_function.cpp +++ b/3rdparty/spirv-tools/source/val/validate_function.cpp @@ -41,18 +41,22 @@ spv_result_t ValidateFunction(ValidationState_t& _, const Instruction* inst) { << _.getIdName(return_id) << "'."; } + const std::vector acceptable = { + SpvOpDecorate, + SpvOpEnqueueKernel, + SpvOpEntryPoint, + SpvOpExecutionMode, + SpvOpExecutionModeId, + SpvOpFunctionCall, + SpvOpGetKernelNDrangeSubGroupCount, + SpvOpGetKernelNDrangeMaxSubGroupSize, + SpvOpGetKernelWorkGroupSize, + SpvOpGetKernelPreferredWorkGroupSizeMultiple, + SpvOpGetKernelLocalSizeForSubgroupCount, + SpvOpGetKernelMaxNumSubgroups, + SpvOpName}; for (auto& pair : inst->uses()) { const auto* use = pair.first; - const std::vector acceptable = { - SpvOpFunctionCall, - SpvOpEntryPoint, - SpvOpEnqueueKernel, - SpvOpGetKernelNDrangeSubGroupCount, - SpvOpGetKernelNDrangeMaxSubGroupSize, - SpvOpGetKernelWorkGroupSize, - SpvOpGetKernelPreferredWorkGroupSizeMultiple, - SpvOpGetKernelLocalSizeForSubgroupCount, - SpvOpGetKernelMaxNumSubgroups}; if (std::find(acceptable.begin(), acceptable.end(), use->opcode()) == acceptable.end()) { return _.diag(SPV_ERROR_INVALID_ID, use) diff --git a/3rdparty/spirv-tools/source/val/validate_type.cpp b/3rdparty/spirv-tools/source/val/validate_type.cpp index 8e027e488..6246ad5af 100644 --- a/3rdparty/spirv-tools/source/val/validate_type.cpp +++ b/3rdparty/spirv-tools/source/val/validate_type.cpp @@ -324,10 +324,12 @@ spv_result_t ValidateTypeFunction(ValidationState_t& _, << num_args << " arguments."; } - // The only valid uses of OpTypeFunction are in an OpFunction instruction. + // The only valid uses of OpTypeFunction are in an OpFunction, debugging, or + // decoration instruction. for (auto& pair : inst->uses()) { const auto* use = pair.first; - if (use->opcode() != SpvOpFunction) { + if (use->opcode() != SpvOpFunction && !spvOpcodeIsDebug(use->opcode()) && + !spvOpcodeIsDecoration(use->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, use) << "Invalid use of function type result id " << _.getIdName(inst->id()) << "."; diff --git a/3rdparty/spirv-tools/test/opt/CMakeLists.txt b/3rdparty/spirv-tools/test/opt/CMakeLists.txt index 631538515..c8deb39a9 100644 --- a/3rdparty/spirv-tools/test/opt/CMakeLists.txt +++ b/3rdparty/spirv-tools/test/opt/CMakeLists.txt @@ -34,6 +34,7 @@ add_spvtools_unittest(TARGET opt def_use_test.cpp eliminate_dead_const_test.cpp eliminate_dead_functions_test.cpp + eliminate_dead_member_test.cpp feature_manager_test.cpp flatten_decoration_test.cpp fold_spec_const_op_composite_test.cpp diff --git a/3rdparty/spirv-tools/test/opt/block_merge_test.cpp b/3rdparty/spirv-tools/test/opt/block_merge_test.cpp index 64ee9523f..b0f914a71 100644 --- a/3rdparty/spirv-tools/test/opt/block_merge_test.cpp +++ b/3rdparty/spirv-tools/test/opt/block_merge_test.cpp @@ -483,6 +483,8 @@ TEST_F(BlockMergeTest, RemoveStructuredDeclaration) { ; CHECK-NOT: OpLoopMerge ; CHECK: OpReturn ; CHECK: [[continue:%\w+]] = OpLabel +; CHECK-NEXT: OpBranch [[block:%\w+]] +; CHECK: [[block]] = OpLabel ; CHECK-NEXT: OpBranch [[header]] OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" @@ -880,6 +882,47 @@ OpFunctionEnd prefix + suffix_after, true, true); } +TEST_F(BlockMergeTest, UnreachableLoop) { + const std::string spirv = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource ESSL 310 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%bool = OpTypeBool +%false = OpConstantFalse %bool +%main = OpFunction %void None %4 +%9 = OpLabel +OpBranch %10 +%11 = OpLabel +OpLoopMerge %12 %13 None +OpBranchConditional %false %13 %14 +%13 = OpLabel +OpSelectionMerge %15 None +OpBranchConditional %false %16 %17 +%16 = OpLabel +OpBranch %15 +%17 = OpLabel +OpBranch %15 +%15 = OpLabel +OpBranch %11 +%14 = OpLabel +OpReturn +%12 = OpLabel +OpBranch %10 +%10 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(spirv, spirv, true, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // More complex control flow diff --git a/3rdparty/spirv-tools/test/opt/eliminate_dead_member_test.cpp b/3rdparty/spirv-tools/test/opt/eliminate_dead_member_test.cpp new file mode 100644 index 000000000..87c79d753 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/eliminate_dead_member_test.cpp @@ -0,0 +1,1031 @@ +// Copyright (c) 2019 Google LLC +// +// 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 "assembly_builder.h" +#include "gmock/gmock.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using EliminateDeadMemberTest = opt::PassTest<::testing::Test>; + +TEST_F(EliminateDeadMemberTest, RemoveMember1) { + // Test that the member "y" is removed. + // Update OpMemberName for |y| and |z|. + // Update OpMemberDecorate for |y| and |z|. + // Update OpAccessChain for access to |z|. + const std::string text = R"( +; CHECK: OpName +; CHECK-NEXT: OpMemberName %type__Globals 0 "x" +; CHECK-NEXT: OpMemberName %type__Globals 1 "z" +; CHECK-NOT: OpMemberName +; CHECK: OpMemberDecorate %type__Globals 0 Offset 0 +; CHECK: OpMemberDecorate %type__Globals 1 Offset 8 +; CHECK: %type__Globals = OpTypeStruct %float %float +; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %int_0 +; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1 + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %in_var_Position "in.var.Position" + OpName %main "main" + OpDecorate %gl_Position BuiltIn Position + OpDecorate %in_var_Position Location 0 + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 8 + OpDecorate %type__Globals Block + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %float = OpTypeFloat 32 + %int_2 = OpConstant %int 2 +%type__Globals = OpTypeStruct %float %float %float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %15 = OpTypeFunction %void +%_ptr_Uniform_float = OpTypePointer Uniform %float + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform +%in_var_Position = OpVariable %_ptr_Input_v4float Input +%gl_Position = OpVariable %_ptr_Output_v4float Output + %main = OpFunction %void None %15 + %17 = OpLabel + %18 = OpLoad %v4float %in_var_Position + %19 = OpAccessChain %_ptr_Uniform_float %_Globals %int_0 + %20 = OpLoad %float %19 + %21 = OpCompositeExtract %float %18 0 + %22 = OpFAdd %float %21 %20 + %23 = OpCompositeInsert %v4float %22 %18 0 + %24 = OpCompositeExtract %float %18 1 + %25 = OpCompositeInsert %v4float %24 %23 1 + %26 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2 + %27 = OpLoad %float %26 + %28 = OpCompositeExtract %float %18 2 + %29 = OpFAdd %float %28 %27 + %30 = OpCompositeInsert %v4float %29 %25 2 + OpStore %gl_Position %30 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(EliminateDeadMemberTest, RemoveMemberWithGroupDecorations) { + // Test that the member "y" is removed. + // Update OpGroupMemberDecorate for %type__Globals member 1 and 2. + // Update OpAccessChain for access to %type__Globals member 2. + const std::string text = R"( +; CHECK: OpDecorate [[gr1:%\w+]] Offset 0 +; CHECK: OpDecorate [[gr2:%\w+]] Offset 4 +; CHECK: OpDecorate [[gr3:%\w+]] Offset 8 +; CHECK: [[gr1]] = OpDecorationGroup +; CHECK: [[gr2]] = OpDecorationGroup +; CHECK: [[gr3]] = OpDecorationGroup +; CHECK: OpGroupMemberDecorate [[gr1]] %type__Globals 0 +; CHECK-NOT: OpGroupMemberDecorate [[gr2]] +; CHECK: OpGroupMemberDecorate [[gr3]] %type__Globals 1 +; CHECK: %type__Globals = OpTypeStruct %float %float +; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %int_0 +; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1 + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpName %_Globals "$Globals" + OpDecorate %gl_Position BuiltIn Position + OpDecorate %in_var_Position Location 0 + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpDecorate %gr1 Offset 0 + OpDecorate %gr2 Offset 4 + OpDecorate %gr3 Offset 8 + OpDecorate %type__Globals Block + %gr1 = OpDecorationGroup + %gr2 = OpDecorationGroup + %gr3 = OpDecorationGroup + OpGroupMemberDecorate %gr1 %type__Globals 0 + OpGroupMemberDecorate %gr2 %type__Globals 1 + OpGroupMemberDecorate %gr3 %type__Globals 2 + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %float = OpTypeFloat 32 + %int_2 = OpConstant %int 2 +%type__Globals = OpTypeStruct %float %float %float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %15 = OpTypeFunction %void +%_ptr_Uniform_float = OpTypePointer Uniform %float + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform +%in_var_Position = OpVariable %_ptr_Input_v4float Input +%gl_Position = OpVariable %_ptr_Output_v4float Output + %main = OpFunction %void None %15 + %17 = OpLabel + %18 = OpLoad %v4float %in_var_Position + %19 = OpAccessChain %_ptr_Uniform_float %_Globals %int_0 + %20 = OpLoad %float %19 + %21 = OpCompositeExtract %float %18 0 + %22 = OpFAdd %float %21 %20 + %23 = OpCompositeInsert %v4float %22 %18 0 + %24 = OpCompositeExtract %float %18 1 + %25 = OpCompositeInsert %v4float %24 %23 1 + %26 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2 + %27 = OpLoad %float %26 + %28 = OpCompositeExtract %float %18 2 + %29 = OpFAdd %float %28 %27 + %30 = OpCompositeInsert %v4float %29 %25 2 + OpStore %gl_Position %30 + OpReturn + OpFunctionEnd +)"; + + // Skipping validation because of a bug in the validator. See issue #2376. + SinglePassRunAndMatch(text, false); +} + +TEST_F(EliminateDeadMemberTest, RemoveMemberUpdateConstant) { + // Test that the member "x" is removed. + // Update the OpConstantComposite instruction. + const std::string text = R"( +; CHECK: OpName +; CHECK-NEXT: OpMemberName %type__Globals 0 "y" +; CHECK-NEXT: OpMemberName %type__Globals 1 "z" +; CHECK-NOT: OpMemberName +; CHECK: OpMemberDecorate %type__Globals 0 Offset 4 +; CHECK: OpMemberDecorate %type__Globals 1 Offset 8 +; CHECK: %type__Globals = OpTypeStruct %float %float +; CHECK: OpConstantComposite %type__Globals %float_1 %float_2 +; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_0 +; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1 + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %in_var_Position "in.var.Position" + OpName %main "main" + OpDecorate %gl_Position BuiltIn Position + OpDecorate %in_var_Position Location 0 + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 8 + OpDecorate %type__Globals Block + %int = OpTypeInt 32 1 + %int_1 = OpConstant %int 1 + %float = OpTypeFloat 32 + %float_0 = OpConstant %float 0 + %float_1 = OpConstant %float 1 + %float_2 = OpConstant %float 2 + %int_2 = OpConstant %int 2 +%type__Globals = OpTypeStruct %float %float %float + %13 = OpConstantComposite %type__Globals %float_0 %float_1 %float_2 +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %19 = OpTypeFunction %void +%_ptr_Uniform_float = OpTypePointer Uniform %float + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform +%in_var_Position = OpVariable %_ptr_Input_v4float Input +%gl_Position = OpVariable %_ptr_Output_v4float Output + %main = OpFunction %void None %19 + %21 = OpLabel + %22 = OpLoad %v4float %in_var_Position + %23 = OpAccessChain %_ptr_Uniform_float %_Globals %int_1 + %24 = OpLoad %float %23 + %25 = OpCompositeExtract %float %22 0 + %26 = OpFAdd %float %25 %24 + %27 = OpCompositeInsert %v4float %26 %22 0 + %28 = OpCompositeExtract %float %22 1 + %29 = OpCompositeInsert %v4float %28 %27 1 + %30 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2 + %31 = OpLoad %float %30 + %32 = OpCompositeExtract %float %22 2 + %33 = OpFAdd %float %32 %31 + %34 = OpCompositeInsert %v4float %33 %29 2 + OpStore %gl_Position %34 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(EliminateDeadMemberTest, RemoveMemberUpdateCompositeConstruct) { + // Test that the member "x" is removed. + // Update the OpConstantComposite instruction. + const std::string text = R"( +; CHECK: OpName +; CHECK-NEXT: OpMemberName %type__Globals 0 "y" +; CHECK-NEXT: OpMemberName %type__Globals 1 "z" +; CHECK-NOT: OpMemberName +; CHECK: OpMemberDecorate %type__Globals 0 Offset 4 +; CHECK: OpMemberDecorate %type__Globals 1 Offset 8 +; CHECK: %type__Globals = OpTypeStruct %float %float +; CHECK: OpCompositeConstruct %type__Globals %float_1 %float_2 +; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_0 +; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1 + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %in_var_Position "in.var.Position" + OpName %main "main" + OpDecorate %gl_Position BuiltIn Position + OpDecorate %in_var_Position Location 0 + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 8 + OpDecorate %type__Globals Block + %int = OpTypeInt 32 1 + %int_1 = OpConstant %int 1 + %float = OpTypeFloat 32 + %float_0 = OpConstant %float 0 + %float_1 = OpConstant %float 1 + %float_2 = OpConstant %float 2 + %int_2 = OpConstant %int 2 +%type__Globals = OpTypeStruct %float %float %float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %19 = OpTypeFunction %void +%_ptr_Uniform_float = OpTypePointer Uniform %float + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform +%in_var_Position = OpVariable %_ptr_Input_v4float Input +%gl_Position = OpVariable %_ptr_Output_v4float Output + %main = OpFunction %void None %19 + %21 = OpLabel + %13 = OpCompositeConstruct %type__Globals %float_0 %float_1 %float_2 + %22 = OpLoad %v4float %in_var_Position + %23 = OpAccessChain %_ptr_Uniform_float %_Globals %int_1 + %24 = OpLoad %float %23 + %25 = OpCompositeExtract %float %22 0 + %26 = OpFAdd %float %25 %24 + %27 = OpCompositeInsert %v4float %26 %22 0 + %28 = OpCompositeExtract %float %22 1 + %29 = OpCompositeInsert %v4float %28 %27 1 + %30 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2 + %31 = OpLoad %float %30 + %32 = OpCompositeExtract %float %22 2 + %33 = OpFAdd %float %32 %31 + %34 = OpCompositeInsert %v4float %33 %29 2 + OpStore %gl_Position %34 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract1) { + // Test that the members "x" and "z" are removed. + // Update the OpCompositeExtract instruction. + // Remove the OpCompositeInsert instruction since the member being inserted is + // dead. + const std::string text = R"( +; CHECK: OpName +; CHECK-NEXT: OpMemberName %type__Globals 0 "y" +; CHECK-NOT: OpMemberName +; CHECK: OpMemberDecorate %type__Globals 0 Offset 4 +; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset +; CHECK: %type__Globals = OpTypeStruct %float +; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals +; CHECK: OpCompositeExtract %float [[ld]] 0 +; CHECK-NOT: OpCompositeInsert +; CHECK: OpReturn + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %main "main" + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 8 + OpDecorate %type__Globals Block + %float = OpTypeFloat 32 +%type__Globals = OpTypeStruct %float %float %float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %void = OpTypeVoid + %7 = OpTypeFunction %void + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform + %main = OpFunction %void None %7 + %8 = OpLabel + %9 = OpLoad %type__Globals %_Globals + %10 = OpCompositeExtract %float %9 1 + %11 = OpCompositeInsert %type__Globals %10 %9 2 + OpReturn + OpFunctionEnd + +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract2) { + // Test that the members "x" and "z" are removed. + // Update the OpCompositeExtract instruction. + // Update the OpCompositeInsert instruction. + const std::string text = R"( +; CHECK: OpName +; CHECK-NEXT: OpMemberName %type__Globals 0 "y" +; CHECK-NOT: OpMemberName +; CHECK: OpMemberDecorate %type__Globals 0 Offset 4 +; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset +; CHECK: %type__Globals = OpTypeStruct %float +; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals +; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0 +; CHECK: OpCompositeInsert %type__Globals [[ex]] [[ld]] 0 +; CHECK: OpReturn + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %main "main" + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 8 + OpDecorate %type__Globals Block + %float = OpTypeFloat 32 +%type__Globals = OpTypeStruct %float %float %float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %void = OpTypeVoid + %7 = OpTypeFunction %void + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform + %main = OpFunction %void None %7 + %8 = OpLabel + %9 = OpLoad %type__Globals %_Globals + %10 = OpCompositeExtract %float %9 1 + %11 = OpCompositeInsert %type__Globals %10 %9 1 + OpReturn + OpFunctionEnd + +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract3) { + // Test that the members "x" and "z" are removed, and one member from the + // substruct. Update the OpCompositeExtract instruction. Update the + // OpCompositeInsert instruction. + const std::string text = R"( +; CHECK: OpName +; CHECK-NEXT: OpMemberName %type__Globals 0 "y" +; CHECK-NOT: OpMemberName +; CHECK: OpMemberDecorate %type__Globals 0 Offset 16 +; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset +; CHECK: OpMemberDecorate [[struct:%\w+]] 0 Offset 4 +; CHECK: [[struct:%\w+]] = OpTypeStruct %float +; CHECK: %type__Globals = OpTypeStruct [[struct]] +; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals +; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0 0 +; CHECK: OpCompositeInsert %type__Globals [[ex]] [[ld]] 0 0 +; CHECK: OpReturn + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %main "main" + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 16 + OpMemberDecorate %type__Globals 2 Offset 24 + OpMemberDecorate %_struct_6 0 Offset 0 + OpMemberDecorate %_struct_6 1 Offset 4 + OpDecorate %type__Globals Block + %float = OpTypeFloat 32 + %_struct_6 = OpTypeStruct %float %float +%type__Globals = OpTypeStruct %float %_struct_6 %float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %void = OpTypeVoid + %7 = OpTypeFunction %void + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform + %main = OpFunction %void None %7 + %8 = OpLabel + %9 = OpLoad %type__Globals %_Globals + %10 = OpCompositeExtract %float %9 1 1 + %11 = OpCompositeInsert %type__Globals %10 %9 1 1 + OpReturn + OpFunctionEnd + +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract4) { + // Test that the members "x" and "z" are removed, and one member from the + // substruct. Update the OpCompositeExtract instruction. Update the + // OpCompositeInsert instruction. + const std::string text = R"( +; CHECK: OpName +; CHECK-NEXT: OpMemberName %type__Globals 0 "y" +; CHECK-NOT: OpMemberName +; CHECK: OpMemberDecorate %type__Globals 0 Offset 16 +; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset +; CHECK: OpMemberDecorate [[struct:%\w+]] 0 Offset 4 +; CHECK: [[struct:%\w+]] = OpTypeStruct %float +; CHECK: [[array:%\w+]] = OpTypeArray [[struct]] +; CHECK: %type__Globals = OpTypeStruct [[array]] +; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals +; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0 1 0 +; CHECK: OpCompositeInsert %type__Globals [[ex]] [[ld]] 0 1 0 +; CHECK: OpReturn + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %main "main" + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 16 + OpMemberDecorate %type__Globals 2 Offset 80 + OpMemberDecorate %_struct_6 0 Offset 0 + OpMemberDecorate %_struct_6 1 Offset 4 + OpDecorate %array ArrayStride 16 + OpDecorate %type__Globals Block + %uint = OpTypeInt 32 0 ; 32-bit int, sign-less + %uint_4 = OpConstant %uint 4 + %float = OpTypeFloat 32 + %_struct_6 = OpTypeStruct %float %float + %array = OpTypeArray %_struct_6 %uint_4 +%type__Globals = OpTypeStruct %float %array %float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %void = OpTypeVoid + %7 = OpTypeFunction %void + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform + %main = OpFunction %void None %7 + %8 = OpLabel + %9 = OpLoad %type__Globals %_Globals + %10 = OpCompositeExtract %float %9 1 1 1 + %11 = OpCompositeInsert %type__Globals %10 %9 1 1 1 + OpReturn + OpFunctionEnd + +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateArrayLength) { + // Test that the members "x" and "y" are removed. + // Member "z" is live because of the OpArrayLength instruction. + // Update the OpArrayLength instruction. + const std::string text = R"( +; CHECK: OpName +; CHECK-NEXT: OpMemberName %type__Globals 0 "z" +; CHECK-NOT: OpMemberName +; CHECK: OpMemberDecorate %type__Globals 0 Offset 16 +; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset +; CHECK: %type__Globals = OpTypeStruct %_runtimearr_float +; CHECK: OpArrayLength %uint %_Globals 0 + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %main "main" + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 16 + OpDecorate %type__Globals Block + %uint = OpTypeInt 32 0 + %float = OpTypeFloat 32 +%_runtimearr_float = OpTypeRuntimeArray %float +%type__Globals = OpTypeStruct %float %float %_runtimearr_float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %void = OpTypeVoid + %9 = OpTypeFunction %void + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform + %main = OpFunction %void None %9 + %10 = OpLabel + %11 = OpLoad %type__Globals %_Globals + %12 = OpArrayLength %uint %_Globals 2 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(EliminateDeadMemberTest, KeepMembersOpStore) { + // Test that all members are kept because of an OpStore. + // No change expected. + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %_Globals "$Globals2" + OpName %main "main" + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 16 + OpDecorate %type__Globals Block + %uint = OpTypeInt 32 0 + %float = OpTypeFloat 32 +%type__Globals = OpTypeStruct %float %float %float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %void = OpTypeVoid + %9 = OpTypeFunction %void + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform + %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform + %main = OpFunction %void None %9 + %10 = OpLabel + %11 = OpLoad %type__Globals %_Globals + OpStore %_Globals2 %11 + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(EliminateDeadMemberTest, KeepMembersOpCopyMemory) { + // Test that all members are kept because of an OpCopyMemory. + // No change expected. + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %_Globals "$Globals2" + OpName %main "main" + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 16 + OpDecorate %type__Globals Block + %uint = OpTypeInt 32 0 + %float = OpTypeFloat 32 +%type__Globals = OpTypeStruct %float %float %float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %void = OpTypeVoid + %9 = OpTypeFunction %void + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform + %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform + %main = OpFunction %void None %9 + %10 = OpLabel + OpCopyMemory %_Globals2 %_Globals + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(EliminateDeadMemberTest, KeepMembersOpCopyMemorySized) { + // Test that all members are kept because of an OpCopyMemorySized. + // No change expected. + const std::string text = R"( + OpCapability Shader + OpCapability Addresses + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %_Globals "$Globals2" + OpName %main "main" + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 16 + OpDecorate %type__Globals Block + %uint = OpTypeInt 32 0 + %uint_20 = OpConstant %uint 20 + %float = OpTypeFloat 32 +%type__Globals = OpTypeStruct %float %float %float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %void = OpTypeVoid + %9 = OpTypeFunction %void + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform + %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform + %main = OpFunction %void None %9 + %10 = OpLabel + OpCopyMemorySized %_Globals2 %_Globals %uint_20 + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(EliminateDeadMemberTest, KeepMembersOpReturnValue) { + // Test that all members are kept because of an OpCopyMemorySized. + // No change expected. + const std::string text = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %_Globals "$Globals2" + OpName %main "main" + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 16 + OpDecorate %type__Globals Block + %uint = OpTypeInt 32 0 + %uint_20 = OpConstant %uint 20 + %float = OpTypeFloat 32 +%type__Globals = OpTypeStruct %float %float %float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %void = OpTypeVoid + %9 = OpTypeFunction %type__Globals + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform + %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform + %main = OpFunction %type__Globals None %9 + %10 = OpLabel + %11 = OpLoad %type__Globals %_Globals + OpReturnValue %11 + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(EliminateDeadMemberTest, RemoveMemberAccessChainWithArrays) { + // Leave only 1 member in each of the structs. + // Update OpMemberName, OpMemberDecorate, and OpAccessChain. + const std::string text = R"( +; CHECK: OpName +; CHECK-NEXT: OpMemberName %type__Globals 0 "y" +; CHECK-NOT: OpMemberName +; CHECK: OpMemberDecorate %type__Globals 0 Offset 16 +; CHECK: OpMemberDecorate [[struct:%\w+]] 0 Offset 4 +; CHECK: [[struct]] = OpTypeStruct %float +; CHECK: [[array:%\w+]] = OpTypeArray [[struct]] +; CHECK: %type__Globals = OpTypeStruct [[array]] +; CHECK: [[undef:%\w+]] = OpUndef %uint +; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals [[undef]] %uint_0 [[undef]] %uint_0 + OpCapability Shader + OpCapability VariablePointersStorageBuffer + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %main "main" + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 16 + OpMemberDecorate %type__Globals 2 Offset 48 + OpMemberDecorate %_struct_4 0 Offset 0 + OpMemberDecorate %_struct_4 1 Offset 4 + OpDecorate %_arr__struct_4_uint_2 ArrayStride 16 + OpDecorate %type__Globals Block + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %uint_2 = OpConstant %uint 2 + %uint_3 = OpConstant %uint 3 + %float = OpTypeFloat 32 + %_struct_4 = OpTypeStruct %float %float +%_arr__struct_4_uint_2 = OpTypeArray %_struct_4 %uint_2 +%type__Globals = OpTypeStruct %float %_arr__struct_4_uint_2 %float +%_arr_type__Globals_uint_3 = OpTypeArray %type__Globals %uint_3 +%_ptr_Uniform__arr_type__Globals_uint_3 = OpTypePointer Uniform %_arr_type__Globals_uint_3 + %void = OpTypeVoid + %15 = OpTypeFunction %void +%_ptr_Uniform_float = OpTypePointer Uniform %float + %_Globals = OpVariable %_ptr_Uniform__arr_type__Globals_uint_3 Uniform + %main = OpFunction %void None %15 + %17 = OpLabel + %18 = OpUndef %uint + %19 = OpAccessChain %_ptr_Uniform_float %_Globals %18 %uint_1 %18 %uint_1 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(EliminateDeadMemberTest, RemoveMemberInboundsAccessChain) { + // Test that the member "y" is removed. + // Update OpMemberName for |y| and |z|. + // Update OpMemberDecorate for |y| and |z|. + // Update OpInboundsAccessChain for access to |z|. + const std::string text = R"( +; CHECK: OpName +; CHECK-NEXT: OpMemberName %type__Globals 0 "x" +; CHECK-NEXT: OpMemberName %type__Globals 1 "z" +; CHECK-NOT: OpMemberName +; CHECK: OpMemberDecorate %type__Globals 0 Offset 0 +; CHECK: OpMemberDecorate %type__Globals 1 Offset 8 +; CHECK: %type__Globals = OpTypeStruct %float %float +; CHECK: OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %int_0 +; CHECK: OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %uint_1 + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %in_var_Position "in.var.Position" + OpName %main "main" + OpDecorate %gl_Position BuiltIn Position + OpDecorate %in_var_Position Location 0 + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 8 + OpDecorate %type__Globals Block + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %float = OpTypeFloat 32 + %int_2 = OpConstant %int 2 +%type__Globals = OpTypeStruct %float %float %float +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %15 = OpTypeFunction %void +%_ptr_Uniform_float = OpTypePointer Uniform %float + %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform +%in_var_Position = OpVariable %_ptr_Input_v4float Input +%gl_Position = OpVariable %_ptr_Output_v4float Output + %main = OpFunction %void None %15 + %17 = OpLabel + %18 = OpLoad %v4float %in_var_Position + %19 = OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %int_0 + %20 = OpLoad %float %19 + %21 = OpCompositeExtract %float %18 0 + %22 = OpFAdd %float %21 %20 + %23 = OpCompositeInsert %v4float %22 %18 0 + %24 = OpCompositeExtract %float %18 1 + %25 = OpCompositeInsert %v4float %24 %23 1 + %26 = OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %int_2 + %27 = OpLoad %float %26 + %28 = OpCompositeExtract %float %18 2 + %29 = OpFAdd %float %28 %27 + %30 = OpCompositeInsert %v4float %29 %25 2 + OpStore %gl_Position %30 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(EliminateDeadMemberTest, RemoveMemberPtrAccessChain) { + // Test that the member "y" is removed. + // Update OpMemberName for |y| and |z|. + // Update OpMemberDecorate for |y| and |z|. + // Update OpInboundsAccessChain for access to |z|. + const std::string text = R"( +; CHECK: OpName +; CHECK-NEXT: OpMemberName %type__Globals 0 "x" +; CHECK-NEXT: OpMemberName %type__Globals 1 "z" +; CHECK-NOT: OpMemberName +; CHECK: OpMemberDecorate %type__Globals 0 Offset 0 +; CHECK: OpMemberDecorate %type__Globals 1 Offset 16 +; CHECK: %type__Globals = OpTypeStruct %float %float +; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0 +; CHECK: OpPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_1 %uint_0 +; CHECK: OpPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_0 %uint_1 + OpCapability Shader + OpCapability VariablePointersStorageBuffer + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %main "main" + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 16 + OpDecorate %type__Globals Block + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %uint_2 = OpConstant %uint 2 + %uint_3 = OpConstant %uint 3 + %float = OpTypeFloat 32 +%type__Globals = OpTypeStruct %float %float %float +%_arr_type__Globals_uint_3 = OpTypeArray %type__Globals %uint_3 +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals +%_ptr_Uniform__arr_type__Globals_uint_3 = OpTypePointer Uniform %_arr_type__Globals_uint_3 + %void = OpTypeVoid + %14 = OpTypeFunction %void +%_ptr_Uniform_float = OpTypePointer Uniform %float + %_Globals = OpVariable %_ptr_Uniform__arr_type__Globals_uint_3 Uniform + %main = OpFunction %void None %14 + %16 = OpLabel + %17 = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0 + %18 = OpPtrAccessChain %_ptr_Uniform_float %17 %uint_1 %uint_0 + %19 = OpPtrAccessChain %_ptr_Uniform_float %17 %uint_0 %uint_2 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(EliminateDeadMemberTest, RemoveMemberInBoundsPtrAccessChain) { + // Test that the member "y" is removed. + // Update OpMemberName for |y| and |z|. + // Update OpMemberDecorate for |y| and |z|. + // Update OpInboundsAccessChain for access to |z|. + const std::string text = R"( +; CHECK: OpName +; CHECK-NEXT: OpMemberName %type__Globals 0 "x" +; CHECK-NEXT: OpMemberName %type__Globals 1 "z" +; CHECK-NOT: OpMemberName +; CHECK: OpMemberDecorate %type__Globals 0 Offset 0 +; CHECK: OpMemberDecorate %type__Globals 1 Offset 16 +; CHECK: %type__Globals = OpTypeStruct %float %float +; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0 +; CHECK: OpInBoundsPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_1 %uint_0 +; CHECK: OpInBoundsPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_0 %uint_1 + OpCapability Shader + OpCapability Addresses + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpSource HLSL 600 + OpName %type__Globals "type.$Globals" + OpMemberName %type__Globals 0 "x" + OpMemberName %type__Globals 1 "y" + OpMemberName %type__Globals 2 "z" + OpName %_Globals "$Globals" + OpName %main "main" + OpDecorate %_Globals DescriptorSet 0 + OpDecorate %_Globals Binding 0 + OpMemberDecorate %type__Globals 0 Offset 0 + OpMemberDecorate %type__Globals 1 Offset 4 + OpMemberDecorate %type__Globals 2 Offset 16 + OpDecorate %type__Globals Block + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %uint_2 = OpConstant %uint 2 + %uint_3 = OpConstant %uint 3 + %float = OpTypeFloat 32 +%type__Globals = OpTypeStruct %float %float %float +%_arr_type__Globals_uint_3 = OpTypeArray %type__Globals %uint_3 +%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals +%_ptr_Uniform__arr_type__Globals_uint_3 = OpTypePointer Uniform %_arr_type__Globals_uint_3 + %void = OpTypeVoid + %14 = OpTypeFunction %void +%_ptr_Uniform_float = OpTypePointer Uniform %float + %_Globals = OpVariable %_ptr_Uniform__arr_type__Globals_uint_3 Uniform + %main = OpFunction %void None %14 + %16 = OpLabel + %17 = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0 + %18 = OpInBoundsPtrAccessChain %_ptr_Uniform_float %17 %uint_1 %uint_0 + %19 = OpInBoundsPtrAccessChain %_ptr_Uniform_float %17 %uint_0 %uint_2 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(EliminateDeadMemberTest, DontRemoveModfStructResultTypeMembers) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %PS_TerrainElevation "PS_TerrainElevation" + OpExecutionMode %PS_TerrainElevation OriginUpperLeft + OpSource HLSL 600 + %float = OpTypeFloat 32 + %void = OpTypeVoid + %21 = OpTypeFunction %void +%ModfStructType = OpTypeStruct %float %float +%PS_TerrainElevation = OpFunction %void None %21 + %22 = OpLabel + %23 = OpUndef %float + %24 = OpExtInst %ModfStructType %1 ModfStruct %23 + %25 = OpCompositeExtract %float %24 1 + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp b/3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp index bde794ca5..7c8f72d3a 100644 --- a/3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp +++ b/3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp @@ -1580,6 +1580,329 @@ OpFunctionEnd true); } +TEST_F(InstBindlessTest, MultipleDebugFunctions) { + // Same source as Simple, but compiled -g and not optimized, especially not + // inlined. The OpSource has had the source extracted for the sake of brevity. + + const std::string defs_before = + R"(OpCapability Shader +%2 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor +OpExecutionMode %MainPs OriginUpperLeft +%1 = OpString "foo5.frag" +OpSource HLSL 500 %1 +OpName %MainPs "MainPs" +OpName %PS_INPUT "PS_INPUT" +OpMemberName %PS_INPUT 0 "vTextureCoords" +OpName %PS_OUTPUT "PS_OUTPUT" +OpMemberName %PS_OUTPUT 0 "vColor" +OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;" +OpName %i "i" +OpName %ps_output "ps_output" +OpName %g_tColor "g_tColor" +OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t" +OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx" +OpName %_ "" +OpName %g_sAniso "g_sAniso" +OpName %i_0 "i" +OpName %i_vTextureCoords "i.vTextureCoords" +OpName %_entryPointOutput_vColor "@entryPointOutput.vColor" +OpName %param "param" +OpDecorate %g_tColor DescriptorSet 0 +OpDecorate %g_tColor Binding 0 +OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0 +OpDecorate %PerViewConstantBuffer_t Block +OpDecorate %g_sAniso DescriptorSet 0 +OpDecorate %g_sAniso Binding 1 +OpDecorate %i_vTextureCoords Location 0 +OpDecorate %_entryPointOutput_vColor Location 0 +%void = OpTypeVoid +%4 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%PS_INPUT = OpTypeStruct %v2float +%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT +%v4float = OpTypeVector %float 4 +%PS_OUTPUT = OpTypeStruct %v4float +%13 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT +%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%21 = OpTypeImage %float 2D 0 0 0 1 Unknown +%uint = OpTypeInt 32 0 +%uint_128 = OpConstant %uint 128 +%_arr_21_uint_128 = OpTypeArray %21 %uint_128 +%_ptr_UniformConstant__arr_21_uint_128 = OpTypePointer UniformConstant %_arr_21_uint_128 +%g_tColor = OpVariable %_ptr_UniformConstant__arr_21_uint_128 UniformConstant +%PerViewConstantBuffer_t = OpTypeStruct %uint +%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t +%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant +%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint +%_ptr_UniformConstant_21 = OpTypePointer UniformConstant %21 +%36 = OpTypeSampler +%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36 +%g_sAniso = OpVariable %_ptr_UniformConstant_36 UniformConstant +%40 = OpTypeSampledImage %21 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string defs_after = + R"(OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord +OpExecutionMode %MainPs OriginUpperLeft +%5 = OpString "foo5.frag" +OpSource HLSL 500 %5 +OpName %MainPs "MainPs" +OpName %PS_INPUT "PS_INPUT" +OpMemberName %PS_INPUT 0 "vTextureCoords" +OpName %PS_OUTPUT "PS_OUTPUT" +OpMemberName %PS_OUTPUT 0 "vColor" +OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;" +OpName %i "i" +OpName %ps_output "ps_output" +OpName %g_tColor "g_tColor" +OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t" +OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx" +OpName %_ "" +OpName %g_sAniso "g_sAniso" +OpName %i_0 "i" +OpName %i_vTextureCoords "i.vTextureCoords" +OpName %_entryPointOutput_vColor "@entryPointOutput.vColor" +OpName %param "param" +OpDecorate %g_tColor DescriptorSet 0 +OpDecorate %g_tColor Binding 0 +OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0 +OpDecorate %PerViewConstantBuffer_t Block +OpDecorate %g_sAniso DescriptorSet 0 +OpDecorate %g_sAniso Binding 1 +OpDecorate %i_vTextureCoords Location 0 +OpDecorate %_entryPointOutput_vColor Location 0 +OpDecorate %_runtimearr_uint ArrayStride 4 +OpDecorate %_struct_77 Block +OpMemberDecorate %_struct_77 0 Offset 0 +OpMemberDecorate %_struct_77 1 Offset 4 +OpDecorate %79 DescriptorSet 7 +OpDecorate %79 Binding 0 +OpDecorate %gl_FragCoord BuiltIn FragCoord +%void = OpTypeVoid +%18 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%PS_INPUT = OpTypeStruct %v2float +%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT +%v4float = OpTypeVector %float 4 +%PS_OUTPUT = OpTypeStruct %v4float +%23 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT +%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%27 = OpTypeImage %float 2D 0 0 0 1 Unknown +%uint = OpTypeInt 32 0 +%uint_128 = OpConstant %uint 128 +%_arr_27_uint_128 = OpTypeArray %27 %uint_128 +%_ptr_UniformConstant__arr_27_uint_128 = OpTypePointer UniformConstant %_arr_27_uint_128 +%g_tColor = OpVariable %_ptr_UniformConstant__arr_27_uint_128 UniformConstant +%PerViewConstantBuffer_t = OpTypeStruct %uint +%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t +%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant +%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint +%_ptr_UniformConstant_27 = OpTypePointer UniformConstant %27 +%35 = OpTypeSampler +%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35 +%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant +%37 = OpTypeSampledImage %27 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output +%uint_0 = OpConstant %uint 0 +%bool = OpTypeBool +%70 = OpTypeFunction %void %uint %uint %uint %uint +%_runtimearr_uint = OpTypeRuntimeArray %uint +%_struct_77 = OpTypeStruct %uint %_runtimearr_uint +%_ptr_StorageBuffer__struct_77 = OpTypePointer StorageBuffer %_struct_77 +%79 = OpVariable %_ptr_StorageBuffer__struct_77 StorageBuffer +%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint +%uint_9 = OpConstant %uint 9 +%uint_4 = OpConstant %uint 4 +%uint_1 = OpConstant %uint 1 +%uint_23 = OpConstant %uint 23 +%uint_2 = OpConstant %uint 2 +%uint_3 = OpConstant %uint 3 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%gl_FragCoord = OpVariable %_ptr_Input_v4float Input +%v4uint = OpTypeVector %uint 4 +%uint_5 = OpConstant %uint 5 +%uint_6 = OpConstant %uint 6 +%uint_7 = OpConstant %uint 7 +%uint_8 = OpConstant %uint 8 +%uint_93 = OpConstant %uint 93 +%125 = OpConstantNull %v4float +)"; + + const std::string func1_before = + R"(%MainPs = OpFunction %void None %4 +%6 = OpLabel +%i_0 = OpVariable %_ptr_Function_PS_INPUT Function +%param = OpVariable %_ptr_Function_PS_INPUT Function +OpLine %1 21 0 +%54 = OpLoad %v2float %i_vTextureCoords +%55 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0 +OpStore %55 %54 +%59 = OpLoad %PS_INPUT %i_0 +OpStore %param %59 +%60 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param +%61 = OpCompositeExtract %v4float %60 0 +OpStore %_entryPointOutput_vColor %61 +OpReturn +OpFunctionEnd +)"; + + const std::string func1_after = + R"(%MainPs = OpFunction %void None %18 +%42 = OpLabel +%i_0 = OpVariable %_ptr_Function_PS_INPUT Function +%param = OpVariable %_ptr_Function_PS_INPUT Function +OpLine %5 21 0 +%43 = OpLoad %v2float %i_vTextureCoords +%44 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0 +OpStore %44 %43 +%45 = OpLoad %PS_INPUT %i_0 +OpStore %param %45 +%46 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param +%47 = OpCompositeExtract %v4float %46 0 +OpStore %_entryPointOutput_vColor %47 +OpReturn +OpFunctionEnd +)"; + + const std::string func2_before = + R"(%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %13 +%i = OpFunctionParameter %_ptr_Function_PS_INPUT +%16 = OpLabel +%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function +OpLine %1 24 0 +%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0 +%32 = OpLoad %uint %31 +%34 = OpAccessChain %_ptr_UniformConstant_21 %g_tColor %32 +%35 = OpLoad %21 %34 +%39 = OpLoad %36 %g_sAniso +%41 = OpSampledImage %40 %35 %39 +%43 = OpAccessChain %_ptr_Function_v2float %i %int_0 +%44 = OpLoad %v2float %43 +%45 = OpImageSampleImplicitLod %v4float %41 %44 +%47 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0 +OpStore %47 %45 +OpLine %1 25 0 +%48 = OpLoad %PS_OUTPUT %ps_output +OpReturnValue %48 +OpFunctionEnd +)"; + + const std::string func2_after = + R"(%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %23 +%i = OpFunctionParameter %_ptr_Function_PS_INPUT +%48 = OpLabel +%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function +OpLine %5 24 0 +%49 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0 +%50 = OpLoad %uint %49 +%51 = OpAccessChain %_ptr_UniformConstant_27 %g_tColor %50 +%52 = OpLoad %27 %51 +%53 = OpLoad %35 %g_sAniso +%54 = OpSampledImage %37 %52 %53 +%55 = OpAccessChain %_ptr_Function_v2float %i %int_0 +%56 = OpLoad %v2float %55 +%62 = OpULessThan %bool %50 %uint_128 +OpSelectionMerge %63 None +OpBranchConditional %62 %64 %65 +%64 = OpLabel +%66 = OpLoad %27 %51 +%67 = OpSampledImage %37 %66 %53 +%68 = OpImageSampleImplicitLod %v4float %67 %56 +OpBranch %63 +%65 = OpLabel +%124 = OpFunctionCall %void %69 %uint_93 %uint_0 %50 %uint_128 +OpBranch %63 +%63 = OpLabel +%126 = OpPhi %v4float %68 %64 %125 %65 +%58 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0 +OpStore %58 %126 +OpLine %5 25 0 +%59 = OpLoad %PS_OUTPUT %ps_output +OpReturnValue %59 +OpFunctionEnd +)"; + + const std::string output_func = + R"(%69 = OpFunction %void None %70 +%71 = OpFunctionParameter %uint +%72 = OpFunctionParameter %uint +%73 = OpFunctionParameter %uint +%74 = OpFunctionParameter %uint +%75 = OpLabel +%81 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_0 +%84 = OpAtomicIAdd %uint %81 %uint_4 %uint_0 %uint_9 +%85 = OpIAdd %uint %84 %uint_9 +%86 = OpArrayLength %uint %79 1 +%87 = OpULessThanEqual %bool %85 %86 +OpSelectionMerge %88 None +OpBranchConditional %87 %89 %88 +%89 = OpLabel +%90 = OpIAdd %uint %84 %uint_0 +%92 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %90 +OpStore %92 %uint_9 +%94 = OpIAdd %uint %84 %uint_1 +%95 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %94 +OpStore %95 %uint_23 +%97 = OpIAdd %uint %84 %uint_2 +%98 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %97 +OpStore %98 %71 +%100 = OpIAdd %uint %84 %uint_3 +%101 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %100 +OpStore %101 %uint_4 +%104 = OpLoad %v4float %gl_FragCoord +%106 = OpBitcast %v4uint %104 +%107 = OpCompositeExtract %uint %106 0 +%108 = OpIAdd %uint %84 %uint_4 +%109 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %108 +OpStore %109 %107 +%110 = OpCompositeExtract %uint %106 1 +%112 = OpIAdd %uint %84 %uint_5 +%113 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %112 +OpStore %113 %110 +%115 = OpIAdd %uint %84 %uint_6 +%116 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %115 +OpStore %116 %72 +%118 = OpIAdd %uint %84 %uint_7 +%119 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %118 +OpStore %119 %73 +%121 = OpIAdd %uint %84 %uint_8 +%122 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %121 +OpStore %122 %74 +OpBranch %88 +%88 = OpLabel +OpReturn +OpFunctionEnd +)"; + + // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck( + defs_before + func1_before + func2_before, + defs_after + func1_after + func2_after + output_func, true, true); +} + TEST_F(InstBindlessTest, RuntimeArray) { // This test verifies that the pass will correctly instrument shader // with runtime descriptor array. This test was created by editing the diff --git a/3rdparty/spirv-tools/test/opt/optimizer_test.cpp b/3rdparty/spirv-tools/test/opt/optimizer_test.cpp index a7246b299..52838d5b1 100644 --- a/3rdparty/spirv-tools/test/opt/optimizer_test.cpp +++ b/3rdparty/spirv-tools/test/opt/optimizer_test.cpp @@ -242,112 +242,111 @@ TEST(Optimizer, WebGPUModeSetsCorrectPasses) { EXPECT_EQ(registered_passes[i], expected_passes[i]); } -TEST(Optimizer, WebGPUModeFlattenDecorationsRuns) { - const std::string input = R"(OpCapability Shader -OpCapability VulkanMemoryModelKHR -OpExtension "SPV_KHR_vulkan_memory_model" -OpMemoryModel Logical VulkanKHR -OpEntryPoint Fragment %main "main" %hue %saturation %value -OpExecutionMode %main OriginUpperLeft -OpDecorate %group Flat -OpDecorate %group NoPerspective -%group = OpDecorationGroup -%void = OpTypeVoid -%void_fn = OpTypeFunction %void -%float = OpTypeFloat 32 -%_ptr_Input_float = OpTypePointer Input %float -%hue = OpVariable %_ptr_Input_float Input -%saturation = OpVariable %_ptr_Input_float Input -%value = OpVariable %_ptr_Input_float Input -%main = OpFunction %void None %void_fn -%entry = OpLabel -OpReturn -OpFunctionEnd -)"; +struct WebGPUPassCase { + // Input SPIR-V + std::string input; + // Expected result SPIR-V + std::string expected; + // Specific pass under test, used for logging messages. + std::string pass; +}; - const std::string expected = R"(OpCapability Shader -OpCapability VulkanMemoryModelKHR -OpExtension "SPV_KHR_vulkan_memory_model" -OpMemoryModel Logical VulkanKHR -OpEntryPoint Fragment %1 "main" %2 %3 %4 -OpExecutionMode %1 OriginUpperLeft -%void = OpTypeVoid -%7 = OpTypeFunction %void -%float = OpTypeFloat 32 -%_ptr_Input_float = OpTypePointer Input %float -%2 = OpVariable %_ptr_Input_float Input -%3 = OpVariable %_ptr_Input_float Input -%4 = OpVariable %_ptr_Input_float Input -%1 = OpFunction %void None %7 -%10 = OpLabel -OpReturn -OpFunctionEnd -)"; +using WebGPUPassTest = PassTest<::testing::TestWithParam>; +TEST_P(WebGPUPassTest, Ran) { SpirvTools tools(SPV_ENV_WEBGPU_0); std::vector binary; - tools.Assemble(input, &binary); + tools.Assemble(GetParam().input, &binary); Optimizer opt(SPV_ENV_WEBGPU_0); opt.RegisterWebGPUPasses(); std::vector optimized; - ValidatorOptions validator_options; + class ValidatorOptions validator_options; ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized, validator_options, true)); std::string disassembly; tools.Disassemble(optimized.data(), optimized.size(), &disassembly); - EXPECT_EQ(expected, disassembly); + EXPECT_EQ(GetParam().expected, disassembly) + << "Was expecting pass '" << GetParam().pass << "' to have been run.\n"; } -TEST(Optimizer, WebGPUModeStripDebugRuns) { - const std::string input = R"(OpCapability Shader -OpCapability VulkanMemoryModelKHR -OpExtension "SPV_KHR_vulkan_memory_model" -OpMemoryModel Logical VulkanKHR -OpEntryPoint Vertex %func "shader" -OpName %main "main" -OpName %void_fn "void_fn" -%void = OpTypeVoid -%void_f = OpTypeFunction %void -%func = OpFunction %void None %void_f -%label = OpLabel -OpReturn -OpFunctionEnd -)"; - - const std::string expected = R"(OpCapability Shader -OpCapability VulkanMemoryModelKHR -OpExtension "SPV_KHR_vulkan_memory_model" -OpMemoryModel Logical VulkanKHR -OpEntryPoint Vertex %1 "shader" -%void = OpTypeVoid -%5 = OpTypeFunction %void -%1 = OpFunction %void None %5 -%6 = OpLabel -OpReturn -OpFunctionEnd -)"; - - SpirvTools tools(SPV_ENV_WEBGPU_0); - std::vector binary; - tools.Assemble(input, &binary); - - Optimizer opt(SPV_ENV_WEBGPU_0); - opt.RegisterWebGPUPasses(); - - std::vector optimized; - ValidatorOptions validator_options; - ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized, - validator_options, true)); - - std::string disassembly; - tools.Disassemble(optimized.data(), optimized.size(), &disassembly); - - EXPECT_EQ(expected, disassembly); -} +INSTANTIATE_TEST_SUITE_P( + WebGPU, WebGPUPassTest, + ::testing::ValuesIn(std::vector{ + // FlattenDecorations + {// input + "OpCapability Shader\n" + "OpCapability VulkanMemoryModelKHR\n" + "OpExtension \"SPV_KHR_vulkan_memory_model\"\n" + "OpMemoryModel Logical VulkanKHR\n" + "OpEntryPoint Fragment %main \"main\" %hue %saturation %value\n" + "OpExecutionMode %main OriginUpperLeft\n" + "OpDecorate %group Flat\n" + "OpDecorate %group NoPerspective\n" + "%group = OpDecorationGroup\n" + "%void = OpTypeVoid\n" + "%void_fn = OpTypeFunction %void\n" + "%float = OpTypeFloat 32\n" + "%_ptr_Input_float = OpTypePointer Input %float\n" + "%hue = OpVariable %_ptr_Input_float Input\n" + "%saturation = OpVariable %_ptr_Input_float Input\n" + "%value = OpVariable %_ptr_Input_float Input\n" + "%main = OpFunction %void None %void_fn\n" + "%entry = OpLabel\n" + "OpReturn\n" + "OpFunctionEnd\n", + // expected + "OpCapability Shader\n" + "OpCapability VulkanMemoryModelKHR\n" + "OpExtension \"SPV_KHR_vulkan_memory_model\"\n" + "OpMemoryModel Logical VulkanKHR\n" + "OpEntryPoint Fragment %1 \"main\" %2 %3 %4\n" + "OpExecutionMode %1 OriginUpperLeft\n" + "%void = OpTypeVoid\n" + "%7 = OpTypeFunction %void\n" + "%float = OpTypeFloat 32\n" + "%_ptr_Input_float = OpTypePointer Input %float\n" + "%2 = OpVariable %_ptr_Input_float Input\n" + "%3 = OpVariable %_ptr_Input_float Input\n" + "%4 = OpVariable %_ptr_Input_float Input\n" + "%1 = OpFunction %void None %7\n" + "%10 = OpLabel\n" + "OpReturn\n" + "OpFunctionEnd\n", + // pass + "flatten-decorations"}, + // Strip Debug + {// input + "OpCapability Shader\n" + "OpCapability VulkanMemoryModelKHR\n" + "OpExtension \"SPV_KHR_vulkan_memory_model\"\n" + "OpMemoryModel Logical VulkanKHR\n" + "OpEntryPoint Vertex %func \"shader\"\n" + "OpName %main \"main\"\n" + "OpName %void_fn \"void_fn\"\n" + "%void = OpTypeVoid\n" + "%void_f = OpTypeFunction %void\n" + "%func = OpFunction %void None %void_f\n" + "%label = OpLabel\n" + "OpReturn\n" + "OpFunctionEnd\n", + // expected + "OpCapability Shader\n" + "OpCapability VulkanMemoryModelKHR\n" + "OpExtension \"SPV_KHR_vulkan_memory_model\"\n" + "OpMemoryModel Logical VulkanKHR\n" + "OpEntryPoint Vertex %1 \"shader\"\n" + "%void = OpTypeVoid\n" + "%5 = OpTypeFunction %void\n" + "%1 = OpFunction %void None %5\n" + "%6 = OpLabel\n" + "OpReturn\n" + "OpFunctionEnd\n", + // pass + "strip-debug"}})); } // namespace } // namespace opt diff --git a/3rdparty/spirv-tools/test/reduce/merge_blocks_test.cpp b/3rdparty/spirv-tools/test/reduce/merge_blocks_test.cpp index e57578e36..55b27bdb8 100644 --- a/3rdparty/spirv-tools/test/reduce/merge_blocks_test.cpp +++ b/3rdparty/spirv-tools/test/reduce/merge_blocks_test.cpp @@ -508,6 +508,144 @@ TEST(MergeBlocksReductionPassTest, MergeWithOpPhi) { CheckEqual(env, after, context.get()); } +void MergeBlocksReductionPassTest_LoopReturn_Helper(bool reverse) { + // A merge block opportunity stores a block that can be merged with its + // predecessor. + // Given blocks A -> B -> C: + // This test demonstrates how merging B->C can invalidate + // the opportunity of merging A->B, and vice-versa. E.g. + // B->C are merged: B is now terminated with OpReturn. + // A->B can now no longer be merged because A is a loop header, which + // cannot be terminated with OpReturn. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 1 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantFalse %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + OpBranch %10 + %10 = OpLabel ; A (loop header) + OpLoopMerge %13 %12 None + OpBranch %11 + %12 = OpLabel ; (unreachable continue block) + OpBranch %10 + %11 = OpLabel ; B + OpBranch %15 + %15 = OpLabel ; C + OpReturn + %13 = OpLabel ; (unreachable merge block) + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + ASSERT_NE(context.get(), nullptr); + auto opportunities = + MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities( + context.get()); + + // A->B and B->C + ASSERT_EQ(opportunities.size(), 2); + + // Test applying opportunities in both orders. + if (reverse) { + std::reverse(opportunities.begin(), opportunities.end()); + } + + size_t num_applied = 0; + for (auto& ri : opportunities) { + if (ri->PreconditionHolds()) { + ri->TryToApply(); + ++num_applied; + } + } + + // Only 1 opportunity can be applied, as both disable each other. + ASSERT_EQ(num_applied, 1); + + std::string after = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 1 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantFalse %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + OpBranch %10 + %10 = OpLabel ; A-B (loop header) + OpLoopMerge %13 %12 None + OpBranch %15 + %12 = OpLabel ; (unreachable continue block) + OpBranch %10 + %15 = OpLabel ; C + OpReturn + %13 = OpLabel ; (unreachable merge block) + OpReturn + OpFunctionEnd + )"; + + // The only difference is the labels. + std::string after_reversed = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 1 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantFalse %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + OpBranch %10 + %10 = OpLabel ; A (loop header) + OpLoopMerge %13 %12 None + OpBranch %11 + %12 = OpLabel ; (unreachable continue block) + OpBranch %10 + %11 = OpLabel ; B-C + OpReturn + %13 = OpLabel ; (unreachable merge block) + OpReturn + OpFunctionEnd + )"; + + CheckEqual(env, reverse ? after_reversed : after, context.get()); +} + +TEST(MergeBlocksReductionPassTest, LoopReturn) { + MergeBlocksReductionPassTest_LoopReturn_Helper(false); +} + +TEST(MergeBlocksReductionPassTest, LoopReturnReverse) { + MergeBlocksReductionPassTest_LoopReturn_Helper(true); +} + } // namespace } // namespace reduce } // namespace spvtools