| 1 | // Copyright (c) 2017 The Khronos Group Inc. |
| 2 | // Copyright (c) 2017 Valve Corporation |
| 3 | // Copyright (c) 2017 LunarG Inc. |
| 4 | // |
| 5 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | // you may not use this file except in compliance with the License. |
| 7 | // You may obtain a copy of the License at |
| 8 | // |
| 9 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | // |
| 11 | // Unless required by applicable law or agreed to in writing, software |
| 12 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | // See the License for the specific language governing permissions and |
| 15 | // limitations under the License. |
| 16 | |
| 17 | #include "source/opt/local_single_store_elim_pass.h" |
| 18 | |
| 19 | #include "source/cfa.h" |
| 20 | #include "source/latest_version_glsl_std_450_header.h" |
| 21 | #include "source/opt/iterator.h" |
| 22 | |
| 23 | namespace spvtools { |
| 24 | namespace opt { |
| 25 | |
| 26 | namespace { |
| 27 | |
| 28 | const uint32_t kStoreValIdInIdx = 1; |
| 29 | const uint32_t kVariableInitIdInIdx = 1; |
| 30 | |
| 31 | } // anonymous namespace |
| 32 | |
| 33 | bool LocalSingleStoreElimPass::LocalSingleStoreElim(Function* func) { |
| 34 | bool modified = false; |
| 35 | |
| 36 | // Check all function scope variables in |func|. |
| 37 | BasicBlock* entry_block = &*func->begin(); |
| 38 | for (Instruction& inst : *entry_block) { |
| 39 | if (inst.opcode() != SpvOpVariable) { |
| 40 | break; |
| 41 | } |
| 42 | |
| 43 | modified |= ProcessVariable(&inst); |
| 44 | } |
| 45 | return modified; |
| 46 | } |
| 47 | |
| 48 | bool LocalSingleStoreElimPass::AllExtensionsSupported() const { |
| 49 | // If any extension not in whitelist, return false |
| 50 | for (auto& ei : get_module()->extensions()) { |
| 51 | const char* extName = |
| 52 | reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]); |
| 53 | if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) |
| 54 | return false; |
| 55 | } |
| 56 | return true; |
| 57 | } |
| 58 | |
| 59 | Pass::Status LocalSingleStoreElimPass::ProcessImpl() { |
| 60 | // Assumes relaxed logical addressing only (see instruction.h) |
| 61 | if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses)) |
| 62 | return Status::SuccessWithoutChange; |
| 63 | |
| 64 | // Do not process if any disallowed extensions are enabled |
| 65 | if (!AllExtensionsSupported()) return Status::SuccessWithoutChange; |
| 66 | // Process all entry point functions |
| 67 | ProcessFunction pfn = [this](Function* fp) { |
| 68 | return LocalSingleStoreElim(fp); |
| 69 | }; |
| 70 | bool modified = context()->ProcessEntryPointCallTree(pfn); |
| 71 | return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; |
| 72 | } |
| 73 | |
| 74 | LocalSingleStoreElimPass::LocalSingleStoreElimPass() = default; |
| 75 | |
| 76 | Pass::Status LocalSingleStoreElimPass::Process() { |
| 77 | InitExtensionWhiteList(); |
| 78 | return ProcessImpl(); |
| 79 | } |
| 80 | |
| 81 | void LocalSingleStoreElimPass::InitExtensionWhiteList() { |
| 82 | extensions_whitelist_.insert({ |
| 83 | "SPV_AMD_shader_explicit_vertex_parameter" , |
| 84 | "SPV_AMD_shader_trinary_minmax" , |
| 85 | "SPV_AMD_gcn_shader" , |
| 86 | "SPV_KHR_shader_ballot" , |
| 87 | "SPV_AMD_shader_ballot" , |
| 88 | "SPV_AMD_gpu_shader_half_float" , |
| 89 | "SPV_KHR_shader_draw_parameters" , |
| 90 | "SPV_KHR_subgroup_vote" , |
| 91 | "SPV_KHR_16bit_storage" , |
| 92 | "SPV_KHR_device_group" , |
| 93 | "SPV_KHR_multiview" , |
| 94 | "SPV_NVX_multiview_per_view_attributes" , |
| 95 | "SPV_NV_viewport_array2" , |
| 96 | "SPV_NV_stereo_view_rendering" , |
| 97 | "SPV_NV_sample_mask_override_coverage" , |
| 98 | "SPV_NV_geometry_shader_passthrough" , |
| 99 | "SPV_AMD_texture_gather_bias_lod" , |
| 100 | "SPV_KHR_storage_buffer_storage_class" , |
| 101 | "SPV_KHR_variable_pointers" , |
| 102 | "SPV_AMD_gpu_shader_int16" , |
| 103 | "SPV_KHR_post_depth_coverage" , |
| 104 | "SPV_KHR_shader_atomic_counter_ops" , |
| 105 | "SPV_EXT_shader_stencil_export" , |
| 106 | "SPV_EXT_shader_viewport_index_layer" , |
| 107 | "SPV_AMD_shader_image_load_store_lod" , |
| 108 | "SPV_AMD_shader_fragment_mask" , |
| 109 | "SPV_EXT_fragment_fully_covered" , |
| 110 | "SPV_AMD_gpu_shader_half_float_fetch" , |
| 111 | "SPV_GOOGLE_decorate_string" , |
| 112 | "SPV_GOOGLE_hlsl_functionality1" , |
| 113 | "SPV_NV_shader_subgroup_partitioned" , |
| 114 | "SPV_EXT_descriptor_indexing" , |
| 115 | "SPV_NV_fragment_shader_barycentric" , |
| 116 | "SPV_NV_compute_shader_derivatives" , |
| 117 | "SPV_NV_shader_image_footprint" , |
| 118 | "SPV_NV_shading_rate" , |
| 119 | "SPV_NV_mesh_shader" , |
| 120 | "SPV_NV_ray_tracing" , |
| 121 | "SPV_KHR_ray_query" , |
| 122 | "SPV_EXT_fragment_invocation_density" , |
| 123 | "SPV_EXT_physical_storage_buffer" , |
| 124 | }); |
| 125 | } |
| 126 | bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) { |
| 127 | std::vector<Instruction*> users; |
| 128 | FindUses(var_inst, &users); |
| 129 | |
| 130 | Instruction* store_inst = FindSingleStoreAndCheckUses(var_inst, users); |
| 131 | |
| 132 | if (store_inst == nullptr) { |
| 133 | return false; |
| 134 | } |
| 135 | |
| 136 | return RewriteLoads(store_inst, users); |
| 137 | } |
| 138 | |
| 139 | Instruction* LocalSingleStoreElimPass::FindSingleStoreAndCheckUses( |
| 140 | Instruction* var_inst, const std::vector<Instruction*>& users) const { |
| 141 | // Make sure there is exactly 1 store. |
| 142 | Instruction* store_inst = nullptr; |
| 143 | |
| 144 | // If |var_inst| has an initializer, then that will count as a store. |
| 145 | if (var_inst->NumInOperands() > 1) { |
| 146 | store_inst = var_inst; |
| 147 | } |
| 148 | |
| 149 | for (Instruction* user : users) { |
| 150 | switch (user->opcode()) { |
| 151 | case SpvOpStore: |
| 152 | // Since we are in the relaxed addressing mode, the use has to be the |
| 153 | // base address of the store, and not the value being store. Otherwise, |
| 154 | // we would have a pointer to a pointer to function scope memory, which |
| 155 | // is not allowed. |
| 156 | if (store_inst == nullptr) { |
| 157 | store_inst = user; |
| 158 | } else { |
| 159 | // More than 1 store. |
| 160 | return nullptr; |
| 161 | } |
| 162 | break; |
| 163 | case SpvOpAccessChain: |
| 164 | case SpvOpInBoundsAccessChain: |
| 165 | if (FeedsAStore(user)) { |
| 166 | // Has a partial store. Cannot propagate that. |
| 167 | return nullptr; |
| 168 | } |
| 169 | break; |
| 170 | case SpvOpLoad: |
| 171 | case SpvOpImageTexelPointer: |
| 172 | case SpvOpName: |
| 173 | case SpvOpCopyObject: |
| 174 | break; |
| 175 | default: |
| 176 | if (!user->IsDecoration()) { |
| 177 | // Don't know if this instruction modifies the variable. |
| 178 | // Conservatively assume it is a store. |
| 179 | return nullptr; |
| 180 | } |
| 181 | break; |
| 182 | } |
| 183 | } |
| 184 | return store_inst; |
| 185 | } |
| 186 | |
| 187 | void LocalSingleStoreElimPass::FindUses( |
| 188 | const Instruction* var_inst, std::vector<Instruction*>* users) const { |
| 189 | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
| 190 | def_use_mgr->ForEachUser(var_inst, [users, this](Instruction* user) { |
| 191 | users->push_back(user); |
| 192 | if (user->opcode() == SpvOpCopyObject) { |
| 193 | FindUses(user, users); |
| 194 | } |
| 195 | }); |
| 196 | } |
| 197 | |
| 198 | bool LocalSingleStoreElimPass::FeedsAStore(Instruction* inst) const { |
| 199 | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
| 200 | return !def_use_mgr->WhileEachUser(inst, [this](Instruction* user) { |
| 201 | switch (user->opcode()) { |
| 202 | case SpvOpStore: |
| 203 | return false; |
| 204 | case SpvOpAccessChain: |
| 205 | case SpvOpInBoundsAccessChain: |
| 206 | case SpvOpCopyObject: |
| 207 | return !FeedsAStore(user); |
| 208 | case SpvOpLoad: |
| 209 | case SpvOpImageTexelPointer: |
| 210 | case SpvOpName: |
| 211 | return true; |
| 212 | default: |
| 213 | // Don't know if this instruction modifies the variable. |
| 214 | // Conservatively assume it is a store. |
| 215 | return user->IsDecoration(); |
| 216 | } |
| 217 | }); |
| 218 | } |
| 219 | |
| 220 | bool LocalSingleStoreElimPass::RewriteLoads( |
| 221 | Instruction* store_inst, const std::vector<Instruction*>& uses) { |
| 222 | BasicBlock* store_block = context()->get_instr_block(store_inst); |
| 223 | DominatorAnalysis* dominator_analysis = |
| 224 | context()->GetDominatorAnalysis(store_block->GetParent()); |
| 225 | |
| 226 | uint32_t stored_id; |
| 227 | if (store_inst->opcode() == SpvOpStore) |
| 228 | stored_id = store_inst->GetSingleWordInOperand(kStoreValIdInIdx); |
| 229 | else |
| 230 | stored_id = store_inst->GetSingleWordInOperand(kVariableInitIdInIdx); |
| 231 | |
| 232 | std::vector<Instruction*> uses_in_store_block; |
| 233 | bool modified = false; |
| 234 | for (Instruction* use : uses) { |
| 235 | if (use->opcode() == SpvOpLoad) { |
| 236 | if (dominator_analysis->Dominates(store_inst, use)) { |
| 237 | modified = true; |
| 238 | context()->KillNamesAndDecorates(use->result_id()); |
| 239 | context()->ReplaceAllUsesWith(use->result_id(), stored_id); |
| 240 | context()->KillInst(use); |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | return modified; |
| 246 | } |
| 247 | |
| 248 | } // namespace opt |
| 249 | } // namespace spvtools |
| 250 | |