| // Copyright (c) 2021 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 <cassert> |
| #include <sstream> |
| #include <string> |
| |
| #include "source/diagnostic.h" |
| #include "source/lint/divergence_analysis.h" |
| #include "source/lint/lints.h" |
| #include "source/opt/basic_block.h" |
| #include "source/opt/cfg.h" |
| #include "source/opt/control_dependence.h" |
| #include "source/opt/def_use_manager.h" |
| #include "source/opt/dominator_analysis.h" |
| #include "source/opt/instruction.h" |
| #include "source/opt/ir_context.h" |
| #include "spirv-tools/libspirv.h" |
| |
| namespace spvtools { |
| namespace lint { |
| namespace lints { |
| namespace { |
| // Returns the %name[id], where `name` is the first name associated with the |
| // given id, or just %id if one is not found. |
| std::string GetFriendlyName(opt::IRContext* context, uint32_t id) { |
| auto names = context->GetNames(id); |
| std::stringstream ss; |
| ss << "%"; |
| if (names.empty()) { |
| ss << id; |
| } else { |
| opt::Instruction* inst_name = names.begin()->second; |
| if (inst_name->opcode() == spv::Op::OpName) { |
| ss << names.begin()->second->GetInOperand(0).AsString(); |
| ss << "[" << id << "]"; |
| } else { |
| ss << id; |
| } |
| } |
| return ss.str(); |
| } |
| |
| bool InstructionHasDerivative(const opt::Instruction& inst) { |
| static const spv::Op derivative_opcodes[] = { |
| // Implicit derivatives. |
| spv::Op::OpImageSampleImplicitLod, |
| spv::Op::OpImageSampleDrefImplicitLod, |
| spv::Op::OpImageSampleProjImplicitLod, |
| spv::Op::OpImageSampleProjDrefImplicitLod, |
| spv::Op::OpImageSparseSampleImplicitLod, |
| spv::Op::OpImageSparseSampleDrefImplicitLod, |
| spv::Op::OpImageSparseSampleProjImplicitLod, |
| spv::Op::OpImageSparseSampleProjDrefImplicitLod, |
| // Explicit derivatives. |
| spv::Op::OpDPdx, |
| spv::Op::OpDPdy, |
| spv::Op::OpFwidth, |
| spv::Op::OpDPdxFine, |
| spv::Op::OpDPdyFine, |
| spv::Op::OpFwidthFine, |
| spv::Op::OpDPdxCoarse, |
| spv::Op::OpDPdyCoarse, |
| spv::Op::OpFwidthCoarse, |
| }; |
| return std::find(std::begin(derivative_opcodes), std::end(derivative_opcodes), |
| inst.opcode()) != std::end(derivative_opcodes); |
| } |
| |
| spvtools::DiagnosticStream Warn(opt::IRContext* context, |
| opt::Instruction* inst) { |
| if (inst == nullptr) { |
| return DiagnosticStream({0, 0, 0}, context->consumer(), "", SPV_WARNING); |
| } else { |
| // TODO(kuhar): Use line numbers based on debug info. |
| return DiagnosticStream( |
| {0, 0, 0}, context->consumer(), |
| inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES), |
| SPV_WARNING); |
| } |
| } |
| |
| void PrintDivergenceFlow(opt::IRContext* context, DivergenceAnalysis div, |
| uint32_t id) { |
| opt::analysis::DefUseManager* def_use = context->get_def_use_mgr(); |
| opt::CFG* cfg = context->cfg(); |
| while (id != 0) { |
| bool is_block = def_use->GetDef(id)->opcode() == spv::Op::OpLabel; |
| if (is_block) { |
| Warn(context, nullptr) |
| << "block " << GetFriendlyName(context, id) << " is divergent"; |
| uint32_t source = div.GetDivergenceSource(id); |
| // Skip intermediate blocks. |
| while (source != 0 && |
| def_use->GetDef(source)->opcode() == spv::Op::OpLabel) { |
| id = source; |
| source = div.GetDivergenceSource(id); |
| } |
| if (source == 0) break; |
| spvtools::opt::Instruction* branch = |
| cfg->block(div.GetDivergenceDependenceSource(id))->terminator(); |
| Warn(context, branch) |
| << "because it depends on a conditional branch on divergent value " |
| << GetFriendlyName(context, source) << ""; |
| id = source; |
| } else { |
| Warn(context, nullptr) |
| << "value " << GetFriendlyName(context, id) << " is divergent"; |
| uint32_t source = div.GetDivergenceSource(id); |
| opt::Instruction* def = def_use->GetDef(id); |
| opt::Instruction* source_def = |
| source == 0 ? nullptr : def_use->GetDef(source); |
| // First print data -> data dependencies. |
| while (source != 0 && source_def->opcode() != spv::Op::OpLabel) { |
| Warn(context, def_use->GetDef(id)) |
| << "because " << GetFriendlyName(context, id) << " uses value " |
| << GetFriendlyName(context, source) |
| << "in its definition, which is divergent"; |
| id = source; |
| def = source_def; |
| source = div.GetDivergenceSource(id); |
| source_def = def_use->GetDef(source); |
| } |
| if (source == 0) { |
| Warn(context, def) << "because it has a divergent definition"; |
| break; |
| } |
| Warn(context, def) << "because it is conditionally set in block " |
| << GetFriendlyName(context, source); |
| id = source; |
| } |
| } |
| } |
| } // namespace |
| |
| bool CheckDivergentDerivatives(opt::IRContext* context) { |
| DivergenceAnalysis div(*context); |
| for (opt::Function& func : *context->module()) { |
| div.Run(&func); |
| for (const opt::BasicBlock& bb : func) { |
| for (const opt::Instruction& inst : bb) { |
| if (InstructionHasDerivative(inst) && |
| div.GetDivergenceLevel(bb.id()) > |
| DivergenceAnalysis::DivergenceLevel::kPartiallyUniform) { |
| Warn(context, nullptr) |
| << "derivative with divergent control flow" |
| << " located in block " << GetFriendlyName(context, bb.id()); |
| PrintDivergenceFlow(context, div, bb.id()); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| } // namespace lints |
| } // namespace lint |
| } // namespace spvtools |