// Copyright (c) 2016 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "source/opt/instruction.h" #include #include "OpenCLDebugInfo100.h" #include "source/disassemble.h" #include "source/opt/fold.h" #include "source/opt/ir_context.h" #include "source/opt/reflect.h" namespace spvtools { namespace opt { namespace { // Indices used to get particular operands out of instructions using InOperand. constexpr uint32_t kTypeImageDimIndex = 1; constexpr uint32_t kLoadBaseIndex = 0; constexpr uint32_t kPointerTypeStorageClassIndex = 0; constexpr uint32_t kVariableStorageClassIndex = 0; constexpr uint32_t kTypeImageSampledIndex = 5; // Constants for OpenCL.DebugInfo.100 / NonSemantic.Shader.DebugInfo.100 // extension instructions. constexpr uint32_t kExtInstSetIdInIdx = 0; constexpr uint32_t kExtInstInstructionInIdx = 1; constexpr uint32_t kDebugScopeNumWords = 7; constexpr uint32_t kDebugScopeNumWordsWithoutInlinedAt = 6; constexpr uint32_t kDebugNoScopeNumWords = 5; // Number of operands of an OpBranchConditional instruction // with weights. constexpr uint32_t kOpBranchConditionalWithWeightsNumOperands = 5; } // namespace Instruction::Instruction(IRContext* c) : utils::IntrusiveNodeBase(), context_(c), opcode_(spv::Op::OpNop), has_type_id_(false), has_result_id_(false), unique_id_(c->TakeNextUniqueId()), dbg_scope_(kNoDebugScope, kNoInlinedAt) {} Instruction::Instruction(IRContext* c, spv::Op op) : utils::IntrusiveNodeBase(), context_(c), opcode_(op), has_type_id_(false), has_result_id_(false), unique_id_(c->TakeNextUniqueId()), dbg_scope_(kNoDebugScope, kNoInlinedAt) {} Instruction::Instruction(IRContext* c, const spv_parsed_instruction_t& inst, std::vector&& dbg_line) : utils::IntrusiveNodeBase(), context_(c), opcode_(static_cast(inst.opcode)), has_type_id_(inst.type_id != 0), has_result_id_(inst.result_id != 0), unique_id_(c->TakeNextUniqueId()), dbg_line_insts_(std::move(dbg_line)), dbg_scope_(kNoDebugScope, kNoInlinedAt) { operands_.reserve(inst.num_operands); for (uint32_t i = 0; i < inst.num_operands; ++i) { const auto& current_payload = inst.operands[i]; operands_.emplace_back( current_payload.type, inst.words + current_payload.offset, inst.words + current_payload.offset + current_payload.num_words); } assert((!IsLineInst() || dbg_line.empty()) && "Op(No)Line attaching to Op(No)Line found"); } Instruction::Instruction(IRContext* c, const spv_parsed_instruction_t& inst, const DebugScope& dbg_scope) : utils::IntrusiveNodeBase(), context_(c), opcode_(static_cast(inst.opcode)), has_type_id_(inst.type_id != 0), has_result_id_(inst.result_id != 0), unique_id_(c->TakeNextUniqueId()), dbg_scope_(dbg_scope) { operands_.reserve(inst.num_operands); for (uint32_t i = 0; i < inst.num_operands; ++i) { const auto& current_payload = inst.operands[i]; operands_.emplace_back( current_payload.type, inst.words + current_payload.offset, inst.words + current_payload.offset + current_payload.num_words); } } Instruction::Instruction(IRContext* c, spv::Op op, uint32_t ty_id, uint32_t res_id, const OperandList& in_operands) : utils::IntrusiveNodeBase(), context_(c), opcode_(op), has_type_id_(ty_id != 0), has_result_id_(res_id != 0), unique_id_(c->TakeNextUniqueId()), operands_(), dbg_scope_(kNoDebugScope, kNoInlinedAt) { size_t operands_size = in_operands.size(); if (has_type_id_) { operands_size++; } if (has_result_id_) { operands_size++; } operands_.reserve(operands_size); if (has_type_id_) { operands_.emplace_back(spv_operand_type_t::SPV_OPERAND_TYPE_TYPE_ID, std::initializer_list{ty_id}); } if (has_result_id_) { operands_.emplace_back(spv_operand_type_t::SPV_OPERAND_TYPE_RESULT_ID, std::initializer_list{res_id}); } operands_.insert(operands_.end(), in_operands.begin(), in_operands.end()); } Instruction::Instruction(Instruction&& that) : utils::IntrusiveNodeBase(), context_(that.context_), opcode_(that.opcode_), has_type_id_(that.has_type_id_), has_result_id_(that.has_result_id_), unique_id_(that.unique_id_), operands_(std::move(that.operands_)), dbg_line_insts_(std::move(that.dbg_line_insts_)), dbg_scope_(that.dbg_scope_) { for (auto& i : dbg_line_insts_) { i.dbg_scope_ = that.dbg_scope_; } } Instruction& Instruction::operator=(Instruction&& that) { context_ = that.context_; opcode_ = that.opcode_; has_type_id_ = that.has_type_id_; has_result_id_ = that.has_result_id_; unique_id_ = that.unique_id_; operands_ = std::move(that.operands_); dbg_line_insts_ = std::move(that.dbg_line_insts_); dbg_scope_ = that.dbg_scope_; return *this; } Instruction* Instruction::Clone(IRContext* c) const { Instruction* clone = new Instruction(c); clone->opcode_ = opcode_; clone->has_type_id_ = has_type_id_; clone->has_result_id_ = has_result_id_; clone->unique_id_ = c->TakeNextUniqueId(); clone->operands_ = operands_; clone->dbg_line_insts_ = dbg_line_insts_; for (auto& i : clone->dbg_line_insts_) { i.unique_id_ = c->TakeNextUniqueId(); if (i.IsDebugLineInst()) i.SetResultId(c->TakeNextId()); } clone->dbg_scope_ = dbg_scope_; return clone; } uint32_t Instruction::GetSingleWordOperand(uint32_t index) const { const auto& words = GetOperand(index).words; assert(words.size() == 1 && "expected the operand only taking one word"); return words.front(); } uint32_t Instruction::NumInOperandWords() const { uint32_t size = 0; for (uint32_t i = TypeResultIdCount(); i < operands_.size(); ++i) size += static_cast(operands_[i].words.size()); return size; } bool Instruction::HasBranchWeights() const { if (opcode_ == spv::Op::OpBranchConditional && NumOperands() == kOpBranchConditionalWithWeightsNumOperands) { return true; } return false; } void Instruction::ToBinaryWithoutAttachedDebugInsts( std::vector* binary) const { const uint32_t num_words = 1 + NumOperandWords(); binary->push_back((num_words << 16) | static_cast(opcode_)); for (const auto& operand : operands_) { binary->insert(binary->end(), operand.words.begin(), operand.words.end()); } } void Instruction::ReplaceOperands(const OperandList& new_operands) { operands_.clear(); operands_.insert(operands_.begin(), new_operands.begin(), new_operands.end()); } bool Instruction::IsReadOnlyLoad() const { if (IsLoad()) { Instruction* address_def = GetBaseAddress(); if (!address_def) { return false; } if (address_def->opcode() == spv::Op::OpVariable) { if (address_def->IsReadOnlyPointer()) { return true; } } if (address_def->opcode() == spv::Op::OpLoad) { const analysis::Type* address_type = context()->get_type_mgr()->GetType(address_def->type_id()); if (address_type->AsSampledImage() != nullptr) { const auto* image_type = address_type->AsSampledImage()->image_type()->AsImage(); if (image_type->sampled() == 1) { return true; } } } } return false; } Instruction* Instruction::GetBaseAddress() const { uint32_t base = GetSingleWordInOperand(kLoadBaseIndex); Instruction* base_inst = context()->get_def_use_mgr()->GetDef(base); bool done = false; while (!done) { switch (base_inst->opcode()) { case spv::Op::OpAccessChain: case spv::Op::OpInBoundsAccessChain: case spv::Op::OpPtrAccessChain: case spv::Op::OpInBoundsPtrAccessChain: case spv::Op::OpImageTexelPointer: case spv::Op::OpCopyObject: // All of these instructions have the base pointer use a base pointer // in in-operand 0. base = base_inst->GetSingleWordInOperand(0); base_inst = context()->get_def_use_mgr()->GetDef(base); break; default: done = true; break; } } return base_inst; } bool Instruction::IsReadOnlyPointer() const { if (context()->get_feature_mgr()->HasCapability(spv::Capability::Shader)) return IsReadOnlyPointerShaders(); else return IsReadOnlyPointerKernel(); } bool Instruction::IsVulkanStorageImage() const { if (opcode() != spv::Op::OpTypePointer) { return false; } spv::StorageClass storage_class = spv::StorageClass(GetSingleWordInOperand(kPointerTypeStorageClassIndex)); if (storage_class != spv::StorageClass::UniformConstant) { return false; } Instruction* base_type = context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1)); // Unpack the optional layer of arraying. if (base_type->opcode() == spv::Op::OpTypeArray || base_type->opcode() == spv::Op::OpTypeRuntimeArray) { base_type = context()->get_def_use_mgr()->GetDef( base_type->GetSingleWordInOperand(0)); } if (base_type->opcode() != spv::Op::OpTypeImage) { return false; } if (spv::Dim(base_type->GetSingleWordInOperand(kTypeImageDimIndex)) == spv::Dim::Buffer) { return false; } // Check if the image is sampled. If we do not know for sure that it is, // then assume it is a storage image. return base_type->GetSingleWordInOperand(kTypeImageSampledIndex) != 1; } bool Instruction::IsVulkanSampledImage() const { if (opcode() != spv::Op::OpTypePointer) { return false; } spv::StorageClass storage_class = spv::StorageClass(GetSingleWordInOperand(kPointerTypeStorageClassIndex)); if (storage_class != spv::StorageClass::UniformConstant) { return false; } Instruction* base_type = context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1)); // Unpack the optional layer of arraying. if (base_type->opcode() == spv::Op::OpTypeArray || base_type->opcode() == spv::Op::OpTypeRuntimeArray) { base_type = context()->get_def_use_mgr()->GetDef( base_type->GetSingleWordInOperand(0)); } if (base_type->opcode() != spv::Op::OpTypeImage) { return false; } if (spv::Dim(base_type->GetSingleWordInOperand(kTypeImageDimIndex)) == spv::Dim::Buffer) { return false; } // Check if the image is sampled. If we know for sure that it is, // then return true. return base_type->GetSingleWordInOperand(kTypeImageSampledIndex) == 1; } bool Instruction::IsVulkanStorageTexelBuffer() const { if (opcode() != spv::Op::OpTypePointer) { return false; } spv::StorageClass storage_class = spv::StorageClass(GetSingleWordInOperand(kPointerTypeStorageClassIndex)); if (storage_class != spv::StorageClass::UniformConstant) { return false; } Instruction* base_type = context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1)); // Unpack the optional layer of arraying. if (base_type->opcode() == spv::Op::OpTypeArray || base_type->opcode() == spv::Op::OpTypeRuntimeArray) { base_type = context()->get_def_use_mgr()->GetDef( base_type->GetSingleWordInOperand(0)); } if (base_type->opcode() != spv::Op::OpTypeImage) { return false; } if (spv::Dim(base_type->GetSingleWordInOperand(kTypeImageDimIndex)) != spv::Dim::Buffer) { return false; } // Check if the image is sampled. If we do not know for sure that it is, // then assume it is a storage texel buffer. return base_type->GetSingleWordInOperand(kTypeImageSampledIndex) != 1; } bool Instruction::IsVulkanStorageBuffer() const { // Is there a difference between a "Storage buffer" and a "dynamic storage // buffer" in SPIR-V and do we care about the difference? if (opcode() != spv::Op::OpTypePointer) { return false; } Instruction* base_type = context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1)); // Unpack the optional layer of arraying. if (base_type->opcode() == spv::Op::OpTypeArray || base_type->opcode() == spv::Op::OpTypeRuntimeArray) { base_type = context()->get_def_use_mgr()->GetDef( base_type->GetSingleWordInOperand(0)); } if (base_type->opcode() != spv::Op::OpTypeStruct) { return false; } spv::StorageClass storage_class = spv::StorageClass(GetSingleWordInOperand(kPointerTypeStorageClassIndex)); if (storage_class == spv::StorageClass::Uniform) { bool is_buffer_block = false; context()->get_decoration_mgr()->ForEachDecoration( base_type->result_id(), uint32_t(spv::Decoration::BufferBlock), [&is_buffer_block](const Instruction&) { is_buffer_block = true; }); return is_buffer_block; } else if (storage_class == spv::StorageClass::StorageBuffer) { bool is_block = false; context()->get_decoration_mgr()->ForEachDecoration( base_type->result_id(), uint32_t(spv::Decoration::Block), [&is_block](const Instruction&) { is_block = true; }); return is_block; } return false; } bool Instruction::IsVulkanStorageBufferVariable() const { if (opcode() != spv::Op::OpVariable) { return false; } spv::StorageClass storage_class = spv::StorageClass(GetSingleWordInOperand(kVariableStorageClassIndex)); if (storage_class == spv::StorageClass::StorageBuffer || storage_class == spv::StorageClass::Uniform) { Instruction* var_type = context()->get_def_use_mgr()->GetDef(type_id()); return var_type != nullptr && var_type->IsVulkanStorageBuffer(); } return false; } bool Instruction::IsVulkanUniformBuffer() const { if (opcode() != spv::Op::OpTypePointer) { return false; } spv::StorageClass storage_class = spv::StorageClass(GetSingleWordInOperand(kPointerTypeStorageClassIndex)); if (storage_class != spv::StorageClass::Uniform) { return false; } Instruction* base_type = context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1)); // Unpack the optional layer of arraying. if (base_type->opcode() == spv::Op::OpTypeArray || base_type->opcode() == spv::Op::OpTypeRuntimeArray) { base_type = context()->get_def_use_mgr()->GetDef( base_type->GetSingleWordInOperand(0)); } if (base_type->opcode() != spv::Op::OpTypeStruct) { return false; } bool is_block = false; context()->get_decoration_mgr()->ForEachDecoration( base_type->result_id(), uint32_t(spv::Decoration::Block), [&is_block](const Instruction&) { is_block = true; }); return is_block; } bool Instruction::IsReadOnlyPointerShaders() const { if (type_id() == 0) { return false; } Instruction* type_def = context()->get_def_use_mgr()->GetDef(type_id()); if (type_def->opcode() != spv::Op::OpTypePointer) { return false; } spv::StorageClass storage_class = spv::StorageClass( type_def->GetSingleWordInOperand(kPointerTypeStorageClassIndex)); switch (storage_class) { case spv::StorageClass::UniformConstant: if (!type_def->IsVulkanStorageImage() && !type_def->IsVulkanStorageTexelBuffer()) { return true; } break; case spv::StorageClass::Uniform: if (!type_def->IsVulkanStorageBuffer()) { return true; } break; case spv::StorageClass::PushConstant: case spv::StorageClass::Input: return true; default: break; } bool is_nonwritable = false; context()->get_decoration_mgr()->ForEachDecoration( result_id(), uint32_t(spv::Decoration::NonWritable), [&is_nonwritable](const Instruction&) { is_nonwritable = true; }); return is_nonwritable; } bool Instruction::IsReadOnlyPointerKernel() const { if (type_id() == 0) { return false; } Instruction* type_def = context()->get_def_use_mgr()->GetDef(type_id()); if (type_def->opcode() != spv::Op::OpTypePointer) { return false; } spv::StorageClass storage_class = spv::StorageClass( type_def->GetSingleWordInOperand(kPointerTypeStorageClassIndex)); return storage_class == spv::StorageClass::UniformConstant; } void Instruction::UpdateLexicalScope(uint32_t scope) { dbg_scope_.SetLexicalScope(scope); for (auto& i : dbg_line_insts_) { i.dbg_scope_.SetLexicalScope(scope); } if (!IsLineInst() && context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) { context()->get_debug_info_mgr()->AnalyzeDebugInst(this); } } void Instruction::UpdateDebugInlinedAt(uint32_t new_inlined_at) { dbg_scope_.SetInlinedAt(new_inlined_at); for (auto& i : dbg_line_insts_) { i.dbg_scope_.SetInlinedAt(new_inlined_at); } if (!IsLineInst() && context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) { context()->get_debug_info_mgr()->AnalyzeDebugInst(this); } } void Instruction::ClearDbgLineInsts() { if (context()->AreAnalysesValid(IRContext::kAnalysisDefUse)) { auto def_use_mgr = context()->get_def_use_mgr(); for (auto& l_inst : dbg_line_insts_) def_use_mgr->ClearInst(&l_inst); } clear_dbg_line_insts(); } void Instruction::UpdateDebugInfoFrom(const Instruction* from) { if (from == nullptr) return; ClearDbgLineInsts(); if (!from->dbg_line_insts().empty()) AddDebugLine(&from->dbg_line_insts().back()); SetDebugScope(from->GetDebugScope()); if (!IsLineInst() && context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) { context()->get_debug_info_mgr()->AnalyzeDebugInst(this); } } void Instruction::AddDebugLine(const Instruction* inst) { dbg_line_insts_.push_back(*inst); dbg_line_insts_.back().unique_id_ = context()->TakeNextUniqueId(); if (inst->IsDebugLineInst()) dbg_line_insts_.back().SetResultId(context_->TakeNextId()); if (context()->AreAnalysesValid(IRContext::kAnalysisDefUse)) context()->get_def_use_mgr()->AnalyzeInstDefUse(&dbg_line_insts_.back()); } bool Instruction::IsDebugLineInst() const { NonSemanticShaderDebugInfo100Instructions ext_opt = GetShader100DebugOpcode(); return ((ext_opt == NonSemanticShaderDebugInfo100DebugLine) || (ext_opt == NonSemanticShaderDebugInfo100DebugNoLine)); } bool Instruction::IsLineInst() const { return IsLine() || IsNoLine(); } bool Instruction::IsLine() const { if (opcode() == spv::Op::OpLine) return true; NonSemanticShaderDebugInfo100Instructions ext_opt = GetShader100DebugOpcode(); return ext_opt == NonSemanticShaderDebugInfo100DebugLine; } bool Instruction::IsNoLine() const { if (opcode() == spv::Op::OpNoLine) return true; NonSemanticShaderDebugInfo100Instructions ext_opt = GetShader100DebugOpcode(); return ext_opt == NonSemanticShaderDebugInfo100DebugNoLine; } Instruction* Instruction::InsertBefore(std::unique_ptr&& inst) { inst.get()->InsertBefore(this); return inst.release(); } Instruction* Instruction::InsertBefore( std::vector>&& list) { Instruction* first_node = list.front().get(); for (auto& inst : list) { inst.release()->InsertBefore(this); } list.clear(); return first_node; } bool Instruction::IsValidBasePointer() const { uint32_t tid = type_id(); if (tid == 0) { return false; } Instruction* type = context()->get_def_use_mgr()->GetDef(tid); if (type->opcode() != spv::Op::OpTypePointer) { return false; } auto feature_mgr = context()->get_feature_mgr(); if (feature_mgr->HasCapability(spv::Capability::Addresses)) { // TODO: The rules here could be more restrictive. return true; } if (opcode() == spv::Op::OpVariable || opcode() == spv::Op::OpFunctionParameter) { return true; } // With variable pointers, there are more valid base pointer objects. // Variable pointers implicitly declares Variable pointers storage buffer. spv::StorageClass storage_class = static_cast(type->GetSingleWordInOperand(0)); if ((feature_mgr->HasCapability( spv::Capability::VariablePointersStorageBuffer) && storage_class == spv::StorageClass::StorageBuffer) || (feature_mgr->HasCapability(spv::Capability::VariablePointers) && storage_class == spv::StorageClass::Workgroup)) { switch (opcode()) { case spv::Op::OpPhi: case spv::Op::OpSelect: case spv::Op::OpFunctionCall: case spv::Op::OpConstantNull: return true; default: break; } } uint32_t pointee_type_id = type->GetSingleWordInOperand(1); Instruction* pointee_type_inst = context()->get_def_use_mgr()->GetDef(pointee_type_id); if (pointee_type_inst->IsOpaqueType()) { return true; } return false; } OpenCLDebugInfo100Instructions Instruction::GetOpenCL100DebugOpcode() const { if (opcode() != spv::Op::OpExtInst) { return OpenCLDebugInfo100InstructionsMax; } if (!context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) { return OpenCLDebugInfo100InstructionsMax; } if (GetSingleWordInOperand(kExtInstSetIdInIdx) != context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) { return OpenCLDebugInfo100InstructionsMax; } return OpenCLDebugInfo100Instructions( GetSingleWordInOperand(kExtInstInstructionInIdx)); } NonSemanticShaderDebugInfo100Instructions Instruction::GetShader100DebugOpcode() const { if (opcode() != spv::Op::OpExtInst) { return NonSemanticShaderDebugInfo100InstructionsMax; } if (!context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo()) { return NonSemanticShaderDebugInfo100InstructionsMax; } if (GetSingleWordInOperand(kExtInstSetIdInIdx) != context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo()) { return NonSemanticShaderDebugInfo100InstructionsMax; } uint32_t opcode = GetSingleWordInOperand(kExtInstInstructionInIdx); if (opcode >= NonSemanticShaderDebugInfo100InstructionsMax) { return NonSemanticShaderDebugInfo100InstructionsMax; } return NonSemanticShaderDebugInfo100Instructions(opcode); } CommonDebugInfoInstructions Instruction::GetCommonDebugOpcode() const { if (opcode() != spv::Op::OpExtInst) { return CommonDebugInfoInstructionsMax; } const uint32_t opencl_set_id = context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo(); const uint32_t shader_set_id = context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo(); if (!opencl_set_id && !shader_set_id) { return CommonDebugInfoInstructionsMax; } const uint32_t used_set_id = GetSingleWordInOperand(kExtInstSetIdInIdx); if (used_set_id != opencl_set_id && used_set_id != shader_set_id) { return CommonDebugInfoInstructionsMax; } return CommonDebugInfoInstructions( GetSingleWordInOperand(kExtInstInstructionInIdx)); } bool Instruction::IsValidBaseImage() const { uint32_t tid = type_id(); if (tid == 0) { return false; } Instruction* type = context()->get_def_use_mgr()->GetDef(tid); return (type->opcode() == spv::Op::OpTypeImage || type->opcode() == spv::Op::OpTypeSampledImage); } bool Instruction::IsOpaqueType() const { if (opcode() == spv::Op::OpTypeStruct) { bool is_opaque = false; ForEachInOperand([&is_opaque, this](const uint32_t* op_id) { Instruction* type_inst = context()->get_def_use_mgr()->GetDef(*op_id); is_opaque |= type_inst->IsOpaqueType(); }); return is_opaque; } else if (opcode() == spv::Op::OpTypeArray) { uint32_t sub_type_id = GetSingleWordInOperand(0); Instruction* sub_type_inst = context()->get_def_use_mgr()->GetDef(sub_type_id); return sub_type_inst->IsOpaqueType(); } else { return opcode() == spv::Op::OpTypeRuntimeArray || spvOpcodeIsBaseOpaqueType(opcode()); } } bool Instruction::IsFoldable() const { return IsFoldableByFoldScalar() || IsFoldableByFoldVector() || context()->get_instruction_folder().HasConstFoldingRule(this); } bool Instruction::IsFoldableByFoldScalar() const { const InstructionFolder& folder = context()->get_instruction_folder(); if (!folder.IsFoldableOpcode(opcode())) { return false; } Instruction* type = context()->get_def_use_mgr()->GetDef(type_id()); if (!folder.IsFoldableScalarType(type)) { return false; } // Even if the type of the instruction is foldable, its operands may not be // foldable (e.g., comparisons of 64bit types). Check that all operand types // are foldable before accepting the instruction. return WhileEachInOperand([&folder, this](const uint32_t* op_id) { Instruction* def_inst = context()->get_def_use_mgr()->GetDef(*op_id); Instruction* def_inst_type = context()->get_def_use_mgr()->GetDef(def_inst->type_id()); return folder.IsFoldableScalarType(def_inst_type); }); } bool Instruction::IsFoldableByFoldVector() const { const InstructionFolder& folder = context()->get_instruction_folder(); if (!folder.IsFoldableOpcode(opcode())) { return false; } Instruction* type = context()->get_def_use_mgr()->GetDef(type_id()); if (!folder.IsFoldableVectorType(type)) { return false; } // Even if the type of the instruction is foldable, its operands may not be // foldable (e.g., comparisons of 64bit types). Check that all operand types // are foldable before accepting the instruction. return WhileEachInOperand([&folder, this](const uint32_t* op_id) { Instruction* def_inst = context()->get_def_use_mgr()->GetDef(*op_id); Instruction* def_inst_type = context()->get_def_use_mgr()->GetDef(def_inst->type_id()); return folder.IsFoldableVectorType(def_inst_type); }); } bool Instruction::IsFloatingPointFoldingAllowed() const { // TODO: Add the rules for kernels. For now it will be pessimistic. // For now, do not support capabilities introduced by SPV_KHR_float_controls. if (!context_->get_feature_mgr()->HasCapability(spv::Capability::Shader) || context_->get_feature_mgr()->HasCapability( spv::Capability::DenormPreserve) || context_->get_feature_mgr()->HasCapability( spv::Capability::DenormFlushToZero) || context_->get_feature_mgr()->HasCapability( spv::Capability::SignedZeroInfNanPreserve) || context_->get_feature_mgr()->HasCapability( spv::Capability::RoundingModeRTZ) || context_->get_feature_mgr()->HasCapability( spv::Capability::RoundingModeRTE)) { return false; } bool is_nocontract = false; context_->get_decoration_mgr()->WhileEachDecoration( result_id(), uint32_t(spv::Decoration::NoContraction), [&is_nocontract](const Instruction&) { is_nocontract = true; return false; }); return !is_nocontract; } std::string Instruction::PrettyPrint(uint32_t options) const { // Convert the module to binary. std::vector module_binary; context()->module()->ToBinary(&module_binary, /* skip_nop = */ false); // Convert the instruction to binary. This is used to identify the correct // stream of words to output from the module. std::vector inst_binary; ToBinaryWithoutAttachedDebugInsts(&inst_binary); // Do not generate a header. return spvInstructionBinaryToText( context()->grammar().target_env(), inst_binary.data(), inst_binary.size(), module_binary.data(), module_binary.size(), options | SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); } std::ostream& operator<<(std::ostream& str, const Instruction& inst) { str << inst.PrettyPrint(); return str; } void Instruction::Dump() const { std::cerr << "Instruction #" << unique_id() << "\n" << *this << "\n"; } bool Instruction::IsOpcodeCodeMotionSafe() const { switch (opcode_) { case spv::Op::OpNop: case spv::Op::OpUndef: case spv::Op::OpLoad: case spv::Op::OpAccessChain: case spv::Op::OpInBoundsAccessChain: case spv::Op::OpArrayLength: case spv::Op::OpVectorExtractDynamic: case spv::Op::OpVectorInsertDynamic: case spv::Op::OpVectorShuffle: case spv::Op::OpCompositeConstruct: case spv::Op::OpCompositeExtract: case spv::Op::OpCompositeInsert: case spv::Op::OpCopyObject: case spv::Op::OpTranspose: case spv::Op::OpConvertFToU: case spv::Op::OpConvertFToS: case spv::Op::OpConvertSToF: case spv::Op::OpConvertUToF: case spv::Op::OpUConvert: case spv::Op::OpSConvert: case spv::Op::OpFConvert: case spv::Op::OpQuantizeToF16: case spv::Op::OpBitcast: case spv::Op::OpSNegate: case spv::Op::OpFNegate: case spv::Op::OpIAdd: case spv::Op::OpFAdd: case spv::Op::OpISub: case spv::Op::OpFSub: case spv::Op::OpIMul: case spv::Op::OpFMul: case spv::Op::OpUDiv: case spv::Op::OpSDiv: case spv::Op::OpFDiv: case spv::Op::OpUMod: case spv::Op::OpSRem: case spv::Op::OpSMod: case spv::Op::OpFRem: case spv::Op::OpFMod: case spv::Op::OpVectorTimesScalar: case spv::Op::OpMatrixTimesScalar: case spv::Op::OpVectorTimesMatrix: case spv::Op::OpMatrixTimesVector: case spv::Op::OpMatrixTimesMatrix: case spv::Op::OpOuterProduct: case spv::Op::OpDot: case spv::Op::OpIAddCarry: case spv::Op::OpISubBorrow: case spv::Op::OpUMulExtended: case spv::Op::OpSMulExtended: case spv::Op::OpAny: case spv::Op::OpAll: case spv::Op::OpIsNan: case spv::Op::OpIsInf: case spv::Op::OpLogicalEqual: case spv::Op::OpLogicalNotEqual: case spv::Op::OpLogicalOr: case spv::Op::OpLogicalAnd: case spv::Op::OpLogicalNot: case spv::Op::OpSelect: case spv::Op::OpIEqual: case spv::Op::OpINotEqual: case spv::Op::OpUGreaterThan: case spv::Op::OpSGreaterThan: case spv::Op::OpUGreaterThanEqual: case spv::Op::OpSGreaterThanEqual: case spv::Op::OpULessThan: case spv::Op::OpSLessThan: case spv::Op::OpULessThanEqual: case spv::Op::OpSLessThanEqual: case spv::Op::OpFOrdEqual: case spv::Op::OpFUnordEqual: case spv::Op::OpFOrdNotEqual: case spv::Op::OpFUnordNotEqual: case spv::Op::OpFOrdLessThan: case spv::Op::OpFUnordLessThan: case spv::Op::OpFOrdGreaterThan: case spv::Op::OpFUnordGreaterThan: case spv::Op::OpFOrdLessThanEqual: case spv::Op::OpFUnordLessThanEqual: case spv::Op::OpFOrdGreaterThanEqual: case spv::Op::OpFUnordGreaterThanEqual: case spv::Op::OpShiftRightLogical: case spv::Op::OpShiftRightArithmetic: case spv::Op::OpShiftLeftLogical: case spv::Op::OpBitwiseOr: case spv::Op::OpBitwiseXor: case spv::Op::OpBitwiseAnd: case spv::Op::OpNot: case spv::Op::OpBitFieldInsert: case spv::Op::OpBitFieldSExtract: case spv::Op::OpBitFieldUExtract: case spv::Op::OpBitReverse: case spv::Op::OpBitCount: case spv::Op::OpSizeOf: return true; default: return false; } } bool Instruction::IsScalarizable() const { if (spvOpcodeIsScalarizable(opcode())) { return true; } if (opcode() == spv::Op::OpExtInst) { uint32_t instSetId = context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450(); if (GetSingleWordInOperand(kExtInstSetIdInIdx) == instSetId) { switch (GetSingleWordInOperand(kExtInstInstructionInIdx)) { case GLSLstd450Round: case GLSLstd450RoundEven: case GLSLstd450Trunc: case GLSLstd450FAbs: case GLSLstd450SAbs: case GLSLstd450FSign: case GLSLstd450SSign: case GLSLstd450Floor: case GLSLstd450Ceil: case GLSLstd450Fract: case GLSLstd450Radians: case GLSLstd450Degrees: case GLSLstd450Sin: case GLSLstd450Cos: case GLSLstd450Tan: case GLSLstd450Asin: case GLSLstd450Acos: case GLSLstd450Atan: case GLSLstd450Sinh: case GLSLstd450Cosh: case GLSLstd450Tanh: case GLSLstd450Asinh: case GLSLstd450Acosh: case GLSLstd450Atanh: case GLSLstd450Atan2: case GLSLstd450Pow: case GLSLstd450Exp: case GLSLstd450Log: case GLSLstd450Exp2: case GLSLstd450Log2: case GLSLstd450Sqrt: case GLSLstd450InverseSqrt: case GLSLstd450Modf: case GLSLstd450FMin: case GLSLstd450UMin: case GLSLstd450SMin: case GLSLstd450FMax: case GLSLstd450UMax: case GLSLstd450SMax: case GLSLstd450FClamp: case GLSLstd450UClamp: case GLSLstd450SClamp: case GLSLstd450FMix: case GLSLstd450Step: case GLSLstd450SmoothStep: case GLSLstd450Fma: case GLSLstd450Frexp: case GLSLstd450Ldexp: case GLSLstd450FindILsb: case GLSLstd450FindSMsb: case GLSLstd450FindUMsb: case GLSLstd450NMin: case GLSLstd450NMax: case GLSLstd450NClamp: return true; default: return false; } } } return false; } bool Instruction::IsOpcodeSafeToDelete() const { if (context()->IsCombinatorInstruction(this)) { return true; } switch (opcode()) { case spv::Op::OpDPdx: case spv::Op::OpDPdy: case spv::Op::OpFwidth: case spv::Op::OpDPdxFine: case spv::Op::OpDPdyFine: case spv::Op::OpFwidthFine: case spv::Op::OpDPdxCoarse: case spv::Op::OpDPdyCoarse: case spv::Op::OpFwidthCoarse: case spv::Op::OpImageQueryLod: return true; default: return false; } } bool Instruction::IsNonSemanticInstruction() const { if (!HasResultId()) return false; if (opcode() != spv::Op::OpExtInst) return false; auto import_inst = context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(0)); std::string import_name = import_inst->GetInOperand(0).AsString(); return import_name.find("NonSemantic.") == 0; } void DebugScope::ToBinary(uint32_t type_id, uint32_t result_id, uint32_t ext_set, std::vector* binary) const { uint32_t num_words = kDebugScopeNumWords; CommonDebugInfoInstructions dbg_opcode = CommonDebugInfoDebugScope; if (GetLexicalScope() == kNoDebugScope) { num_words = kDebugNoScopeNumWords; dbg_opcode = CommonDebugInfoDebugNoScope; } else if (GetInlinedAt() == kNoInlinedAt) { num_words = kDebugScopeNumWordsWithoutInlinedAt; } std::vector operands = { (num_words << 16) | static_cast(spv::Op::OpExtInst), type_id, result_id, ext_set, static_cast(dbg_opcode), }; binary->insert(binary->end(), operands.begin(), operands.end()); if (GetLexicalScope() != kNoDebugScope) { binary->push_back(GetLexicalScope()); if (GetInlinedAt() != kNoInlinedAt) binary->push_back(GetInlinedAt()); } } } // namespace opt } // namespace spvtools