| // Copyright (c) 2022 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/spread_volatile_semantics.h" |
| |
| #include "source/opt/decoration_manager.h" |
| #include "source/spirv_constant.h" |
| |
| namespace spvtools { |
| namespace opt { |
| namespace { |
| constexpr uint32_t kOpDecorateInOperandBuiltinDecoration = 2u; |
| constexpr uint32_t kOpLoadInOperandMemoryOperands = 1u; |
| constexpr uint32_t kOpEntryPointInOperandEntryPoint = 1u; |
| constexpr uint32_t kOpEntryPointInOperandInterface = 3u; |
| |
| bool HasBuiltinDecoration(analysis::DecorationManager* decoration_manager, |
| uint32_t var_id, uint32_t built_in) { |
| return decoration_manager->FindDecoration( |
| var_id, uint32_t(spv::Decoration::BuiltIn), |
| [built_in](const Instruction& inst) { |
| return built_in == inst.GetSingleWordInOperand( |
| kOpDecorateInOperandBuiltinDecoration); |
| }); |
| } |
| |
| bool IsBuiltInForRayTracingVolatileSemantics(spv::BuiltIn built_in) { |
| switch (built_in) { |
| case spv::BuiltIn::SMIDNV: |
| case spv::BuiltIn::WarpIDNV: |
| case spv::BuiltIn::SubgroupSize: |
| case spv::BuiltIn::SubgroupLocalInvocationId: |
| case spv::BuiltIn::SubgroupEqMask: |
| case spv::BuiltIn::SubgroupGeMask: |
| case spv::BuiltIn::SubgroupGtMask: |
| case spv::BuiltIn::SubgroupLeMask: |
| case spv::BuiltIn::SubgroupLtMask: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool HasBuiltinForRayTracingVolatileSemantics( |
| analysis::DecorationManager* decoration_manager, uint32_t var_id) { |
| return decoration_manager->FindDecoration( |
| var_id, uint32_t(spv::Decoration::BuiltIn), [](const Instruction& inst) { |
| spv::BuiltIn built_in = spv::BuiltIn( |
| inst.GetSingleWordInOperand(kOpDecorateInOperandBuiltinDecoration)); |
| return IsBuiltInForRayTracingVolatileSemantics(built_in); |
| }); |
| } |
| |
| bool HasVolatileDecoration(analysis::DecorationManager* decoration_manager, |
| uint32_t var_id) { |
| return decoration_manager->HasDecoration(var_id, |
| uint32_t(spv::Decoration::Volatile)); |
| } |
| |
| } // namespace |
| |
| Pass::Status SpreadVolatileSemantics::Process() { |
| if (HasNoExecutionModel()) { |
| return Status::SuccessWithoutChange; |
| } |
| const bool is_vk_memory_model_enabled = |
| context()->get_feature_mgr()->HasCapability( |
| spv::Capability::VulkanMemoryModel); |
| CollectTargetsForVolatileSemantics(is_vk_memory_model_enabled); |
| |
| // If VulkanMemoryModel capability is not enabled, we have to set Volatile |
| // decoration for interface variables instead of setting Volatile for load |
| // instructions. If an interface (or pointers to it) is used by two load |
| // instructions in two entry points and one must be volatile while another |
| // is not, we have to report an error for the conflict. |
| if (!is_vk_memory_model_enabled && |
| HasInterfaceInConflictOfVolatileSemantics()) { |
| return Status::Failure; |
| } |
| |
| return SpreadVolatileSemanticsToVariables(is_vk_memory_model_enabled); |
| } |
| |
| Pass::Status SpreadVolatileSemantics::SpreadVolatileSemanticsToVariables( |
| const bool is_vk_memory_model_enabled) { |
| Status status = Status::SuccessWithoutChange; |
| for (Instruction& var : context()->types_values()) { |
| auto entry_function_ids = |
| EntryFunctionsToSpreadVolatileSemanticsForVar(var.result_id()); |
| if (entry_function_ids.empty()) { |
| continue; |
| } |
| |
| if (is_vk_memory_model_enabled) { |
| SetVolatileForLoadsInEntries(&var, entry_function_ids); |
| } else { |
| DecorateVarWithVolatile(&var); |
| } |
| status = Status::SuccessWithChange; |
| } |
| return status; |
| } |
| |
| bool SpreadVolatileSemantics::IsTargetUsedByNonVolatileLoadInEntryPoint( |
| uint32_t var_id, Instruction* entry_point) { |
| uint32_t entry_function_id = |
| entry_point->GetSingleWordInOperand(kOpEntryPointInOperandEntryPoint); |
| std::unordered_set<uint32_t> funcs; |
| context()->CollectCallTreeFromRoots(entry_function_id, &funcs); |
| return !VisitLoadsOfPointersToVariableInEntries( |
| var_id, |
| [](Instruction* load) { |
| // If it has a load without volatile memory operand, finish traversal |
| // and return false. |
| if (load->NumInOperands() <= kOpLoadInOperandMemoryOperands) { |
| return false; |
| } |
| uint32_t memory_operands = |
| load->GetSingleWordInOperand(kOpLoadInOperandMemoryOperands); |
| return (memory_operands & uint32_t(spv::MemoryAccessMask::Volatile)) != |
| 0; |
| }, |
| funcs); |
| } |
| |
| bool SpreadVolatileSemantics::HasInterfaceInConflictOfVolatileSemantics() { |
| for (Instruction& entry_point : get_module()->entry_points()) { |
| spv::ExecutionModel execution_model = |
| static_cast<spv::ExecutionModel>(entry_point.GetSingleWordInOperand(0)); |
| for (uint32_t operand_index = kOpEntryPointInOperandInterface; |
| operand_index < entry_point.NumInOperands(); ++operand_index) { |
| uint32_t var_id = entry_point.GetSingleWordInOperand(operand_index); |
| if (!EntryFunctionsToSpreadVolatileSemanticsForVar(var_id).empty() && |
| !IsTargetForVolatileSemantics(var_id, execution_model) && |
| IsTargetUsedByNonVolatileLoadInEntryPoint(var_id, &entry_point)) { |
| Instruction* inst = context()->get_def_use_mgr()->GetDef(var_id); |
| context()->EmitErrorMessage( |
| "Variable is a target for Volatile semantics for an entry point, " |
| "but it is not for another entry point", |
| inst); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| void SpreadVolatileSemantics::MarkVolatileSemanticsForVariable( |
| uint32_t var_id, Instruction* entry_point) { |
| uint32_t entry_function_id = |
| entry_point->GetSingleWordInOperand(kOpEntryPointInOperandEntryPoint); |
| auto itr = var_ids_to_entry_fn_for_volatile_semantics_.find(var_id); |
| if (itr == var_ids_to_entry_fn_for_volatile_semantics_.end()) { |
| var_ids_to_entry_fn_for_volatile_semantics_[var_id] = {entry_function_id}; |
| return; |
| } |
| itr->second.insert(entry_function_id); |
| } |
| |
| void SpreadVolatileSemantics::CollectTargetsForVolatileSemantics( |
| const bool is_vk_memory_model_enabled) { |
| for (Instruction& entry_point : get_module()->entry_points()) { |
| spv::ExecutionModel execution_model = |
| static_cast<spv::ExecutionModel>(entry_point.GetSingleWordInOperand(0)); |
| for (uint32_t operand_index = kOpEntryPointInOperandInterface; |
| operand_index < entry_point.NumInOperands(); ++operand_index) { |
| uint32_t var_id = entry_point.GetSingleWordInOperand(operand_index); |
| if (!IsTargetForVolatileSemantics(var_id, execution_model)) { |
| continue; |
| } |
| if (is_vk_memory_model_enabled || |
| IsTargetUsedByNonVolatileLoadInEntryPoint(var_id, &entry_point)) { |
| MarkVolatileSemanticsForVariable(var_id, &entry_point); |
| } |
| } |
| } |
| } |
| |
| void SpreadVolatileSemantics::DecorateVarWithVolatile(Instruction* var) { |
| analysis::DecorationManager* decoration_manager = |
| context()->get_decoration_mgr(); |
| uint32_t var_id = var->result_id(); |
| if (HasVolatileDecoration(decoration_manager, var_id)) { |
| return; |
| } |
| get_decoration_mgr()->AddDecoration( |
| spv::Op::OpDecorate, |
| {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {var_id}}, |
| {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, |
| {uint32_t(spv::Decoration::Volatile)}}}); |
| } |
| |
| bool SpreadVolatileSemantics::VisitLoadsOfPointersToVariableInEntries( |
| uint32_t var_id, const std::function<bool(Instruction*)>& handle_load, |
| const std::unordered_set<uint32_t>& function_ids) { |
| std::vector<uint32_t> worklist({var_id}); |
| auto* def_use_mgr = context()->get_def_use_mgr(); |
| while (!worklist.empty()) { |
| uint32_t ptr_id = worklist.back(); |
| worklist.pop_back(); |
| bool finish_traversal = !def_use_mgr->WhileEachUser( |
| ptr_id, [this, &worklist, &ptr_id, handle_load, |
| &function_ids](Instruction* user) { |
| BasicBlock* block = context()->get_instr_block(user); |
| if (block == nullptr || |
| function_ids.find(block->GetParent()->result_id()) == |
| function_ids.end()) { |
| return true; |
| } |
| |
| if (user->opcode() == spv::Op::OpAccessChain || |
| user->opcode() == spv::Op::OpInBoundsAccessChain || |
| user->opcode() == spv::Op::OpPtrAccessChain || |
| user->opcode() == spv::Op::OpInBoundsPtrAccessChain || |
| user->opcode() == spv::Op::OpCopyObject) { |
| if (ptr_id == user->GetSingleWordInOperand(0)) |
| worklist.push_back(user->result_id()); |
| return true; |
| } |
| |
| if (user->opcode() != spv::Op::OpLoad) { |
| return true; |
| } |
| |
| return handle_load(user); |
| }); |
| if (finish_traversal) return false; |
| } |
| return true; |
| } |
| |
| void SpreadVolatileSemantics::SetVolatileForLoadsInEntries( |
| Instruction* var, const std::unordered_set<uint32_t>& entry_function_ids) { |
| // Set Volatile memory operand for all load instructions if they do not have |
| // it. |
| for (auto entry_id : entry_function_ids) { |
| std::unordered_set<uint32_t> funcs; |
| context()->CollectCallTreeFromRoots(entry_id, &funcs); |
| VisitLoadsOfPointersToVariableInEntries( |
| var->result_id(), |
| [](Instruction* load) { |
| if (load->NumInOperands() <= kOpLoadInOperandMemoryOperands) { |
| load->AddOperand({SPV_OPERAND_TYPE_MEMORY_ACCESS, |
| {uint32_t(spv::MemoryAccessMask::Volatile)}}); |
| return true; |
| } |
| uint32_t memory_operands = |
| load->GetSingleWordInOperand(kOpLoadInOperandMemoryOperands); |
| memory_operands |= uint32_t(spv::MemoryAccessMask::Volatile); |
| load->SetInOperand(kOpLoadInOperandMemoryOperands, {memory_operands}); |
| return true; |
| }, |
| funcs); |
| } |
| } |
| |
| bool SpreadVolatileSemantics::IsTargetForVolatileSemantics( |
| uint32_t var_id, spv::ExecutionModel execution_model) { |
| analysis::DecorationManager* decoration_manager = |
| context()->get_decoration_mgr(); |
| if (execution_model == spv::ExecutionModel::Fragment) { |
| return get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 6) && |
| HasBuiltinDecoration(decoration_manager, var_id, |
| uint32_t(spv::BuiltIn::HelperInvocation)); |
| } |
| |
| if (execution_model == spv::ExecutionModel::IntersectionKHR || |
| execution_model == spv::ExecutionModel::IntersectionNV) { |
| if (HasBuiltinDecoration(decoration_manager, var_id, |
| uint32_t(spv::BuiltIn::RayTmaxKHR))) { |
| return true; |
| } |
| } |
| |
| switch (execution_model) { |
| case spv::ExecutionModel::RayGenerationKHR: |
| case spv::ExecutionModel::ClosestHitKHR: |
| case spv::ExecutionModel::MissKHR: |
| case spv::ExecutionModel::CallableKHR: |
| case spv::ExecutionModel::IntersectionKHR: |
| return HasBuiltinForRayTracingVolatileSemantics(decoration_manager, |
| var_id); |
| default: |
| return false; |
| } |
| } |
| |
| } // namespace opt |
| } // namespace spvtools |