diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp index f5cdee24..5d2e306e 100644 --- a/include/spirv-tools/optimizer.hpp +++ b/include/spirv-tools/optimizer.hpp @@ -271,7 +271,8 @@ Optimizer::PassToken CreateInlineOpaquePass(); // The presence of access chain references and function calls can inhibit // the above optimization. // -// Only modules with logical addressing are currently processed. +// Only modules with relaxed logical addressing (see opt/instruction.h) are +// currently processed. // // This pass is most effective if preceeded by Inlining and // LocalAccessChainConvert. This pass will reduce the work needed to be done @@ -305,9 +306,9 @@ Optimizer::PassToken CreateDeadBranchElimPass(); // The presence of access chain references and function calls can inhibit // the above optimization. // -// Only shader modules with logical addressing are currently processed. -// Currently modules with any extensions enabled are not processed. This -// is left for future work. +// Only shader modules with relaxed logical addressing (see opt/instruction.h) +// are currently processed. Currently modules with any extensions enabled are +// not processed. This is left for future work. // // This pass is most effective if preceeded by Inlining and // LocalAccessChainConvert. LocalSingleStoreElim and LocalSingleBlockElim @@ -344,6 +345,9 @@ Optimizer::PassToken CreateLocalAccessChainConvertPass(); // not supported and will prohibit optimization of a function. Support of // these operations are future work. // +// Only shader modules with relaxed logical addressing (see opt/instruction.h) +// are currently processed. +// // This pass will reduce the work needed to be done by LocalSingleBlockElim // and LocalMultiStoreElim and can improve the effectiveness of other passes // such as DeadBranchElimination which depend on values for their analysis. @@ -384,8 +388,8 @@ Optimizer::PassToken CreateCommonUniformElimPass(); // time cost over standard dead code elimination. // // This pass only processes entry point functions. It also only processes -// shaders with logical addressing. It currently will not process functions -// with function calls. +// shaders with relaxed logical addressing (see opt/instruction.h). It currently +// will not process functions with function calls. // // This pass will be made more effective by first running passes that remove // dead control flow and inlines function calls. diff --git a/source/opcode.cpp b/source/opcode.cpp index 7a840465..c76f1b4f 100644 --- a/source/opcode.cpp +++ b/source/opcode.cpp @@ -417,3 +417,23 @@ bool spvOpcodeIsBlockTerminator(SpvOp opcode) { return spvOpcodeIsBranch(opcode) || spvOpcodeIsReturn(opcode) || opcode == SpvOpKill || opcode == SpvOpUnreachable; } + +bool spvOpcodeIsBaseOpaqueType(SpvOp opcode) { + switch (opcode) { + case SpvOpTypeImage: + case SpvOpTypeSampler: + case SpvOpTypeSampledImage: + case SpvOpTypeOpaque: + case SpvOpTypeEvent: + case SpvOpTypeDeviceEvent: + case SpvOpTypeReserveId: + case SpvOpTypeQueue: + case SpvOpTypePipe: + case SpvOpTypeForwardPointer: + case SpvOpTypePipeStorage: + case SpvOpTypeNamedBarrier: + return true; + default: + return false; + } +} diff --git a/source/opcode.h b/source/opcode.h index 25c8de96..6c73178f 100644 --- a/source/opcode.h +++ b/source/opcode.h @@ -106,4 +106,6 @@ bool spvOpcodeIsReturn(SpvOp opcode); // Returns true if the given opcode is a basic block terminator. bool spvOpcodeIsBlockTerminator(SpvOp opcode); +// Returns true if the given opcode always defines an opaque type. +bool spvOpcodeIsBaseOpaqueType(SpvOp opcode); #endif // LIBSPIRV_OPCODE_H_ diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp index 37dca13b..05b83313 100644 --- a/source/opt/aggressive_dead_code_elim_pass.cpp +++ b/source/opt/aggressive_dead_code_elim_pass.cpp @@ -36,6 +36,7 @@ const uint32_t kLoopMergeContinueBlockIdInIdx = 1; } // namespace bool AggressiveDCEPass::IsVarOfStorage(uint32_t varId, uint32_t storageClass) { + if (varId == 0) return false; const ir::Instruction* varInst = get_def_use_mgr()->GetDef(varId); const SpvOp op = varInst->opcode(); if (op != SpvOpVariable) return false; @@ -355,7 +356,9 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) { if (liveInst->opcode() == SpvOpLoad) { uint32_t varId; (void)GetPtr(liveInst, &varId); - ProcessLoad(varId); + if (varId != 0) { + ProcessLoad(varId); + } } // If function call, treat as if it loads from all pointer arguments else if (liveInst->opcode() == SpvOpFunctionCall) { @@ -466,7 +469,8 @@ Pass::Status AggressiveDCEPass::ProcessImpl() { // TODO(greg-lunarg): Handle additional capabilities if (!get_module()->HasCapability(SpvCapabilityShader)) return Status::SuccessWithoutChange; - // Current functionality assumes logical addressing only + // Current functionality assumes relaxed logical addressing (see + // instruction.h) // TODO(greg-lunarg): Handle non-logical addressing if (get_module()->HasCapability(SpvCapabilityAddresses)) return Status::SuccessWithoutChange; diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h index 07930009..7526d8a7 100644 --- a/source/opt/aggressive_dead_code_elim_pass.h +++ b/source/opt/aggressive_dead_code_elim_pass.h @@ -49,7 +49,8 @@ class AggressiveDCEPass : public MemPass { } private: - // Return true if |varId| is variable of |storageClass|. + // Return true if |varId| is a variable of |storageClass|. |varId| must either + // be 0 or the result of an instruction. bool IsVarOfStorage(uint32_t varId, uint32_t storageClass); // Return true if |varId| is variable of function storage class or is diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp index 914eb3fe..83c548cd 100644 --- a/source/opt/instruction.cpp +++ b/source/opt/instruction.cpp @@ -156,8 +156,11 @@ bool Instruction::IsReadOnlyLoad() const { } Instruction* Instruction::GetBaseAddress() const { - assert(IsLoad() && - "GetBaseAddress should only be called on load instructions."); + assert((IsLoad() || opcode() == SpvOpStore || opcode() == SpvOpAccessChain || + opcode() == SpvOpInBoundsAccessChain || + opcode() == SpvOpCopyObject) && + "GetBaseAddress should only be called on instructions that take a " + "pointer or image."); uint32_t base = GetSingleWordInOperand(kLoadBaseIndex); ir::Instruction* base_inst = context()->get_def_use_mgr()->GetDef(base); bool done = false; @@ -179,6 +182,9 @@ Instruction* Instruction::GetBaseAddress() const { break; } } + + assert(base_inst->IsValidBasePointer() && + "We cannot have a base pointer come from this load"); return base_inst; } @@ -389,5 +395,54 @@ Instruction* Instruction::InsertBefore(std::unique_ptr&& i) { i.get()->InsertBefore(this); return i.release(); } + +bool Instruction::IsValidBasePointer() const { + uint32_t tid = type_id(); + if (tid == 0) { + return false; + } + + ir::Instruction* type = context()->get_def_use_mgr()->GetDef(tid); + if (type->opcode() != SpvOpTypePointer) { + return false; + } + + if (context()->module()->HasCapability(SpvCapabilityAddresses)) { + // TODO: The rules here could be more restrictive. + return true; + } + + if (opcode() == SpvOpVariable || opcode() == SpvOpFunctionParameter) { + return true; + } + + uint32_t pointee_type_id = type->GetSingleWordInOperand(1); + ir::Instruction* pointee_type_inst = + context()->get_def_use_mgr()->GetDef(pointee_type_id); + + if (pointee_type_inst->IsOpaqueType()) { + return true; + } + return false; +} + +bool Instruction::IsOpaqueType() const { + if (opcode() == SpvOpTypeStruct) { + bool is_opaque = false; + ForEachInOperand([&is_opaque, this](const uint32_t* op_id) { + ir::Instruction* type_inst = context()->get_def_use_mgr()->GetDef(*op_id); + is_opaque |= type_inst->IsOpaqueType(); + }); + return is_opaque; + } else if (opcode() == SpvOpTypeArray) { + uint32_t sub_type_id = GetSingleWordInOperand(0); + ir::Instruction* sub_type_inst = + context()->get_def_use_mgr()->GetDef(sub_type_id); + return sub_type_inst->IsOpaqueType(); + } else { + return opcode() == SpvOpTypeRuntimeArray || + spvOpcodeIsBaseOpaqueType(opcode()); + } +} } // namespace ir } // namespace spvtools diff --git a/source/opt/instruction.h b/source/opt/instruction.h index edb04192..66fc82cc 100644 --- a/source/opt/instruction.h +++ b/source/opt/instruction.h @@ -35,6 +35,19 @@ class IRContext; class Module; class InstructionList; +// Relaxed logcial addressing: +// +// In the logical addressing model, pointers cannot be stored or loaded. This +// is a useful assumption because it simplifies the aliasing significantly. +// However, for the purpose of legalizing code generated from HLSL, we will have +// to allow storing and loading of pointers to opaque objects and runtime +// arrays. This relaxation of the rule still implies that function and private +// scope variables do not have any aliasing, so we can treat them as before. +// This will be call the relaxed logical addressing model. +// +// This relaxation of the rule will be allowed by |GetBaseAddress|, but it will +// enforce that no other pointers are stored or loaded. + // About operand: // // In the SPIR-V specification, the term "operand" is used to mean any single @@ -257,8 +270,9 @@ class Instruction : public utils::IntrusiveNodeBase { // Returns the instruction that gives the base address of an address // calculation. The instruction must be a load instruction. In logical // addressing mode, will return an OpVariable or OpFunctionParameter - // instruction. For physical addressing mode, could return other types of - // instructions. + // instruction. For relaxed logical addressing, it would also return a load of + // a pointer to an opaque object. For physical addressing mode, could return + // other types of instructions. Instruction* GetBaseAddress() const; // Returns true if the instruction is a load from memory into a result id. It @@ -312,6 +326,11 @@ class Instruction : public utils::IntrusiveNodeBase { return spvOpcodeIsBlockTerminator(opcode()); } + // Return true if |this| is an instruction that define an opaque type. Since + // runtime array have similar characteristics they are included as opaque + // types. + bool IsOpaqueType() const; + inline bool operator==(const Instruction&) const; inline bool operator!=(const Instruction&) const; inline bool operator<(const Instruction&) const; @@ -332,6 +351,12 @@ class Instruction : public utils::IntrusiveNodeBase { bool IsReadOnlyVariableShaders() const; bool IsReadOnlyVariableKernel() const; + // Returns true if it is valid to use the result of |inst| as the base + // pointer for a load or store. In this case, valid is defined by the relaxed + // logical addressing rules when using logical addressing. Normal validation + // rules for physical addressing. + bool IsValidBasePointer() const; + IRContext* context_; // IR Context SpvOp opcode_; // Opcode uint32_t type_id_; // Result type id. A value of 0 means no result type id. diff --git a/source/opt/local_single_block_elim_pass.cpp b/source/opt/local_single_block_elim_pass.cpp index f86f0aa0..392e4abe 100644 --- a/source/opt/local_single_block_elim_pass.cpp +++ b/source/opt/local_single_block_elim_pass.cpp @@ -188,7 +188,7 @@ bool LocalSingleBlockLoadStoreElimPass::AllExtensionsSupported() const { } Pass::Status LocalSingleBlockLoadStoreElimPass::ProcessImpl() { - // Assumes logical addressing only + // Assumes relaxed logical addressing only (see instruction.h). if (get_module()->HasCapability(SpvCapabilityAddresses)) return Status::SuccessWithoutChange; // Do not process if module contains OpGroupDecorate. Additional diff --git a/source/opt/local_single_store_elim_pass.cpp b/source/opt/local_single_store_elim_pass.cpp index cb40ba09..67cf516f 100644 --- a/source/opt/local_single_store_elim_pass.cpp +++ b/source/opt/local_single_store_elim_pass.cpp @@ -307,7 +307,7 @@ bool LocalSingleStoreElimPass::AllExtensionsSupported() const { } Pass::Status LocalSingleStoreElimPass::ProcessImpl() { - // Assumes logical addressing only + // Assumes relaxed logical addressing only (see instruction.h) if (get_module()->HasCapability(SpvCapabilityAddresses)) return Status::SuccessWithoutChange; // Do not process if module contains OpGroupDecorate. Additional diff --git a/source/opt/local_ssa_elim_pass.cpp b/source/opt/local_ssa_elim_pass.cpp index 2e4135ba..af84ebf9 100644 --- a/source/opt/local_ssa_elim_pass.cpp +++ b/source/opt/local_ssa_elim_pass.cpp @@ -79,7 +79,7 @@ Pass::Status LocalMultiStoreElimPass::ProcessImpl() { // TODO(greg-lunarg): Do SSA rewrite for non-structured control flow if (!get_module()->HasCapability(SpvCapabilityShader)) return Status::SuccessWithoutChange; - // Assumes logical addressing only + // Assumes relaxed logical addressing only (see instruction.h) // TODO(greg-lunarg): Add support for physical addressing if (get_module()->HasCapability(SpvCapabilityAddresses)) return Status::SuccessWithoutChange; diff --git a/source/opt/mem_pass.cpp b/source/opt/mem_pass.cpp index b21bf00b..f8aa31ae 100644 --- a/source/opt/mem_pass.cpp +++ b/source/opt/mem_pass.cpp @@ -26,7 +26,6 @@ namespace opt { namespace { -const uint32_t kAccessChainPtrIdInIdx = 0; const uint32_t kCopyObjectOperandInIdx = 0; const uint32_t kLoadPtrIdInIdx = 0; const uint32_t kLoopMergeMergeBlockIdInIdx = 0; @@ -48,6 +47,7 @@ bool MemPass::IsBaseTargetType(const ir::Instruction* typeInst) const { case SpvOpTypeImage: case SpvOpTypeSampler: case SpvOpTypeSampledImage: + case SpvOpTypePointer: return true; default: break; @@ -57,9 +57,13 @@ bool MemPass::IsBaseTargetType(const ir::Instruction* typeInst) const { bool MemPass::IsTargetType(const ir::Instruction* typeInst) const { if (IsBaseTargetType(typeInst)) return true; - if (typeInst->opcode() == SpvOpTypeArray) - return IsBaseTargetType( - get_def_use_mgr()->GetDef(typeInst->GetSingleWordOperand(1))); + if (typeInst->opcode() == SpvOpTypeArray) { + if (!IsTargetType( + get_def_use_mgr()->GetDef(typeInst->GetSingleWordOperand(1)))) { + return false; + } + return true; + } if (typeInst->opcode() != SpvOpTypeStruct) return false; // All struct members must be math type int nonMathComp = 0; @@ -92,21 +96,25 @@ bool MemPass::IsPtr(uint32_t ptrId) { ir::Instruction* MemPass::GetPtr(uint32_t ptrId, uint32_t* varId) { *varId = ptrId; ir::Instruction* ptrInst = get_def_use_mgr()->GetDef(*varId); + ir::Instruction* varInst; + + if (ptrInst->opcode() != SpvOpVariable && + ptrInst->opcode() != SpvOpFunctionParameter) { + varInst = ptrInst->GetBaseAddress(); + } else { + varInst = ptrInst; + } + if (varInst->opcode() == SpvOpVariable) { + *varId = varInst->result_id(); + } else { + *varId = 0; + } + while (ptrInst->opcode() == SpvOpCopyObject) { - *varId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx); - ptrInst = get_def_use_mgr()->GetDef(*varId); - } - ir::Instruction* varInst = ptrInst; - while (varInst->opcode() != SpvOpVariable && - varInst->opcode() != SpvOpFunctionParameter) { - if (IsNonPtrAccessChain(varInst->opcode())) { - *varId = varInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx); - } else { - assert(varInst->opcode() == SpvOpCopyObject); - *varId = varInst->GetSingleWordInOperand(kCopyObjectOperandInIdx); - } - varInst = get_def_use_mgr()->GetDef(*varId); + uint32_t temp = ptrInst->GetSingleWordInOperand(0); + ptrInst = get_def_use_mgr()->GetDef(temp); } + return ptrInst; } @@ -170,6 +178,11 @@ bool MemPass::IsLiveStore(ir::Instruction* storeInst) { // get store's variable uint32_t varId; (void)GetPtr(storeInst, &varId); + if (varId == 0) { + // If we do not know which variable we are accessing, assume the store is + // live. + return true; + } return IsLiveVar(varId); } @@ -490,12 +503,15 @@ void MemPass::SSABlockInit(std::list::iterator block_itr) { } bool MemPass::IsTargetVar(uint32_t varId) { + if (varId == 0) { + return false; + } + if (seen_non_target_vars_.find(varId) != seen_non_target_vars_.end()) return false; if (seen_target_vars_.find(varId) != seen_target_vars_.end()) return true; const ir::Instruction* varInst = get_def_use_mgr()->GetDef(varId); if (varInst->opcode() != SpvOpVariable) return false; - ; const uint32_t varTypeId = varInst->type_id(); const ir::Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId); if (varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) != diff --git a/source/opt/mem_pass.h b/source/opt/mem_pass.h index 49662881..17a9428f 100644 --- a/source/opt/mem_pass.h +++ b/source/opt/mem_pass.h @@ -58,11 +58,13 @@ class MemPass : public Pass { bool IsPtr(uint32_t ptrId); // Given the id of a pointer |ptrId|, return the top-most non-CopyObj. - // Also return the base variable's id in |varId|. + // Also return the base variable's id in |varId|. If no base variable is + // found, |varId| will be 0. ir::Instruction* GetPtr(uint32_t ptrId, uint32_t* varId); // Given a load or store |ip|, return the pointer instruction. - // Also return the base variable's id in |varId|. + // Also return the base variable's id in |varId|. If no base variable is + // found, |varId| will be 0. ir::Instruction* GetPtr(ir::Instruction* ip, uint32_t* varId); // Return true if all uses of |id| are only name or decorate ops. @@ -89,7 +91,7 @@ class MemPass : public Pass { // useless. If a load is deleted and its variable has no other loads, // delete all its variable's stores. void DCEInst(ir::Instruction* inst, - const std::function&); + const std::function&); // Call all the cleanup helper functions on |func|. bool CFGCleanup(ir::Function* func); diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp index add81d9b..44df87af 100644 --- a/test/opt/aggressive_dead_code_elim_test.cpp +++ b/test/opt/aggressive_dead_code_elim_test.cpp @@ -3500,6 +3500,113 @@ OpFunctionEnd SinglePassRunAndCheck(assembly, assembly, true, true); } +TEST_F(AggressiveDCETest, PointerVariable) { + // ADCE is able to handle code that contains a load whose base address + // comes from a load and not an OpVariable. I want to see an instruction + // removed to be sure that ADCE is not exiting early. + + const std::string before = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +OpDecorate %8 DescriptorSet 0 +OpDecorate %8 Binding 1 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%8 = OpVariable %_ptr_Uniform__struct_6 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%26 = OpLoad %_ptr_Uniform__struct_5 %24 +%27 = OpAccessChain %_ptr_Uniform_v4float %26 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +OpDecorate %8 DescriptorSet 0 +OpDecorate %8 Binding 1 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%8 = OpVariable %_ptr_Uniform__struct_6 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%25 = OpLoad %_ptr_Uniform__struct_5 %24 +%26 = OpAccessChain %_ptr_Uniform_v4float %25 %int_0 %uint_0 %int_0 +%27 = OpLoad %v4float %26 +OpStore %2 %27 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, true, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Check that logical addressing required diff --git a/test/opt/instruction_test.cpp b/test/opt/instruction_test.cpp index ae4f3a56..aa457ca9 100644 --- a/test/opt/instruction_test.cpp +++ b/test/opt/instruction_test.cpp @@ -31,6 +31,7 @@ using ir::Operand; using spvtest::MakeInstruction; using ::testing::Eq; using DescriptorTypeTest = PassTest<::testing::Test>; +using OpaqueTypeTest = PassTest<::testing::Test>; TEST(InstructionTest, CreateTrivial) { Instruction empty; @@ -509,4 +510,71 @@ TEST_F(DescriptorTypeTest, NonWritableIsReadOnly) { Instruction* variable = context->get_def_use_mgr()->GetDef(3); EXPECT_TRUE(variable->IsReadOnlyVariable()); } + +TEST_F(OpaqueTypeTest, BaseOpaqueTypesShader) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypeImage %5 2D 1 0 0 1 Unknown + %7 = OpTypeSampler + %8 = OpTypeSampledImage %6 + %9 = OpTypeRuntimeArray %5 + %2 = OpFunction %3 None %4 + %10 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + Instruction* image_type = context->get_def_use_mgr()->GetDef(6); + EXPECT_TRUE(image_type->IsOpaqueType()); + Instruction* sampler_type = context->get_def_use_mgr()->GetDef(7); + EXPECT_TRUE(sampler_type->IsOpaqueType()); + Instruction* sampled_image_type = context->get_def_use_mgr()->GetDef(8); + EXPECT_TRUE(sampled_image_type->IsOpaqueType()); + Instruction* runtime_array_type = context->get_def_use_mgr()->GetDef(9); + EXPECT_TRUE(runtime_array_type->IsOpaqueType()); + Instruction* float_type = context->get_def_use_mgr()->GetDef(5); + EXPECT_FALSE(float_type->IsOpaqueType()); + Instruction* void_type = context->get_def_use_mgr()->GetDef(3); + EXPECT_FALSE(void_type->IsOpaqueType()); +} + +TEST_F(OpaqueTypeTest, OpaqueStructTypes) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypeRuntimeArray %5 + %7 = OpTypeStruct %6 %6 + %8 = OpTypeStruct %5 %6 + %9 = OpTypeStruct %6 %5 + %10 = OpTypeStruct %7 + %2 = OpFunction %3 None %4 + %11 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + for (int i = 7; i <= 10; i++) { + Instruction* type = context->get_def_use_mgr()->GetDef(i); + EXPECT_TRUE(type->IsOpaqueType()); + } +} } // anonymous namespace diff --git a/test/opt/local_single_block_elim.cpp b/test/opt/local_single_block_elim.cpp index 218e8e7f..16e24590 100644 --- a/test/opt/local_single_block_elim.cpp +++ b/test/opt/local_single_block_elim.cpp @@ -819,6 +819,104 @@ OpFunctionEnd predefs_before + before, predefs_after + after, true, true); } +TEST_F(LocalSingleBlockLoadStoreElimTest, PointerVariable) { + // Test that checks if a pointer variable is removed. + + const std::string before = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%26 = OpLoad %_ptr_Uniform__struct_5 %24 +%27 = OpAccessChain %_ptr_Uniform_v4float %26 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%27 = OpAccessChain %_ptr_Uniform_v4float %7 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, + true, true); +} // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Other target variable types diff --git a/test/opt/local_single_store_elim_test.cpp b/test/opt/local_single_store_elim_test.cpp index a73bef30..8f61d240 100644 --- a/test/opt/local_single_store_elim_test.cpp +++ b/test/opt/local_single_store_elim_test.cpp @@ -722,6 +722,105 @@ OpFunctionEnd predefs_before + before, predefs_after + after, true, true); } +TEST_F(LocalSingleStoreElimTest, PointerVariable) { + // Test that checks if a pointer variable is removed. + + const std::string before = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%26 = OpLoad %_ptr_Uniform__struct_5 %24 +%27 = OpAccessChain %_ptr_Uniform_v4float %26 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%27 = OpAccessChain %_ptr_Uniform_v4float %7 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, + true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Other types diff --git a/test/opt/local_ssa_elim_test.cpp b/test/opt/local_ssa_elim_test.cpp index 4813a16d..942a6984 100644 --- a/test/opt/local_ssa_elim_test.cpp +++ b/test/opt/local_ssa_elim_test.cpp @@ -1513,6 +1513,105 @@ OpFunctionEnd predefs_before + func_before, predefs_after + func_after, true, true); } +TEST_F(LocalSSAElimTest, PointerVariable) { + // Test that checks if a pointer variable is removed. + + const std::string before = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%26 = OpLoad %_ptr_Uniform__struct_5 %24 +%27 = OpAccessChain %_ptr_Uniform_v4float %26 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%27 = OpAccessChain %_ptr_Uniform_v4float %7 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, + true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // No optimization in the presence of