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_block_elim_pass.h" |
18 | |
19 | #include <vector> |
20 | |
21 | #include "source/opt/iterator.h" |
22 | |
23 | namespace spvtools { |
24 | namespace opt { |
25 | namespace { |
26 | |
27 | const uint32_t kStoreValIdInIdx = 1; |
28 | |
29 | } // anonymous namespace |
30 | |
31 | bool LocalSingleBlockLoadStoreElimPass::HasOnlySupportedRefs(uint32_t ptrId) { |
32 | if (supported_ref_ptrs_.find(ptrId) != supported_ref_ptrs_.end()) return true; |
33 | if (get_def_use_mgr()->WhileEachUser(ptrId, [this](Instruction* user) { |
34 | SpvOp op = user->opcode(); |
35 | if (IsNonPtrAccessChain(op) || op == SpvOpCopyObject) { |
36 | if (!HasOnlySupportedRefs(user->result_id())) { |
37 | return false; |
38 | } |
39 | } else if (op != SpvOpStore && op != SpvOpLoad && op != SpvOpName && |
40 | !IsNonTypeDecorate(op)) { |
41 | return false; |
42 | } |
43 | return true; |
44 | })) { |
45 | supported_ref_ptrs_.insert(ptrId); |
46 | return true; |
47 | } |
48 | return false; |
49 | } |
50 | |
51 | bool LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElim( |
52 | Function* func) { |
53 | // Perform local store/load, load/load and store/store elimination |
54 | // on each block |
55 | bool modified = false; |
56 | std::vector<Instruction*> instructions_to_kill; |
57 | std::unordered_set<Instruction*> instructions_to_save; |
58 | for (auto bi = func->begin(); bi != func->end(); ++bi) { |
59 | var2store_.clear(); |
60 | var2load_.clear(); |
61 | auto next = bi->begin(); |
62 | for (auto ii = next; ii != bi->end(); ii = next) { |
63 | ++next; |
64 | switch (ii->opcode()) { |
65 | case SpvOpStore: { |
66 | // Verify store variable is target type |
67 | uint32_t varId; |
68 | Instruction* ptrInst = GetPtr(&*ii, &varId); |
69 | if (!IsTargetVar(varId)) continue; |
70 | if (!HasOnlySupportedRefs(varId)) continue; |
71 | // If a store to the whole variable, remember it for succeeding |
72 | // loads and stores. Otherwise forget any previous store to that |
73 | // variable. |
74 | if (ptrInst->opcode() == SpvOpVariable) { |
75 | // If a previous store to same variable, mark the store |
76 | // for deletion if not still used. |
77 | auto prev_store = var2store_.find(varId); |
78 | if (prev_store != var2store_.end() && |
79 | instructions_to_save.count(prev_store->second) == 0) { |
80 | instructions_to_kill.push_back(prev_store->second); |
81 | modified = true; |
82 | } |
83 | |
84 | bool kill_store = false; |
85 | auto li = var2load_.find(varId); |
86 | if (li != var2load_.end()) { |
87 | if (ii->GetSingleWordInOperand(kStoreValIdInIdx) == |
88 | li->second->result_id()) { |
89 | // We are storing the same value that already exists in the |
90 | // memory location. The store does nothing. |
91 | kill_store = true; |
92 | } |
93 | } |
94 | |
95 | if (!kill_store) { |
96 | var2store_[varId] = &*ii; |
97 | var2load_.erase(varId); |
98 | } else { |
99 | instructions_to_kill.push_back(&*ii); |
100 | modified = true; |
101 | } |
102 | } else { |
103 | assert(IsNonPtrAccessChain(ptrInst->opcode())); |
104 | var2store_.erase(varId); |
105 | var2load_.erase(varId); |
106 | } |
107 | } break; |
108 | case SpvOpLoad: { |
109 | // Verify store variable is target type |
110 | uint32_t varId; |
111 | Instruction* ptrInst = GetPtr(&*ii, &varId); |
112 | if (!IsTargetVar(varId)) continue; |
113 | if (!HasOnlySupportedRefs(varId)) continue; |
114 | uint32_t replId = 0; |
115 | if (ptrInst->opcode() == SpvOpVariable) { |
116 | // If a load from a variable, look for a previous store or |
117 | // load from that variable and use its value. |
118 | auto si = var2store_.find(varId); |
119 | if (si != var2store_.end()) { |
120 | replId = si->second->GetSingleWordInOperand(kStoreValIdInIdx); |
121 | } else { |
122 | auto li = var2load_.find(varId); |
123 | if (li != var2load_.end()) { |
124 | replId = li->second->result_id(); |
125 | } |
126 | } |
127 | } else { |
128 | // If a partial load of a previously seen store, remember |
129 | // not to delete the store. |
130 | auto si = var2store_.find(varId); |
131 | if (si != var2store_.end()) instructions_to_save.insert(si->second); |
132 | } |
133 | if (replId != 0) { |
134 | // replace load's result id and delete load |
135 | context()->KillNamesAndDecorates(&*ii); |
136 | context()->ReplaceAllUsesWith(ii->result_id(), replId); |
137 | instructions_to_kill.push_back(&*ii); |
138 | modified = true; |
139 | } else { |
140 | if (ptrInst->opcode() == SpvOpVariable) |
141 | var2load_[varId] = &*ii; // register load |
142 | } |
143 | } break; |
144 | case SpvOpFunctionCall: { |
145 | // Conservatively assume all locals are redefined for now. |
146 | // TODO(): Handle more optimally |
147 | var2store_.clear(); |
148 | var2load_.clear(); |
149 | } break; |
150 | default: |
151 | break; |
152 | } |
153 | } |
154 | } |
155 | |
156 | for (Instruction* inst : instructions_to_kill) { |
157 | context()->KillInst(inst); |
158 | } |
159 | |
160 | return modified; |
161 | } |
162 | |
163 | void LocalSingleBlockLoadStoreElimPass::Initialize() { |
164 | // Initialize Target Type Caches |
165 | seen_target_vars_.clear(); |
166 | seen_non_target_vars_.clear(); |
167 | |
168 | // Clear collections |
169 | supported_ref_ptrs_.clear(); |
170 | |
171 | // Initialize extensions whitelist |
172 | InitExtensions(); |
173 | } |
174 | |
175 | bool LocalSingleBlockLoadStoreElimPass::AllExtensionsSupported() const { |
176 | // If any extension not in whitelist, return false |
177 | for (auto& ei : get_module()->extensions()) { |
178 | const char* extName = |
179 | reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]); |
180 | if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) |
181 | return false; |
182 | } |
183 | return true; |
184 | } |
185 | |
186 | Pass::Status LocalSingleBlockLoadStoreElimPass::ProcessImpl() { |
187 | // Assumes relaxed logical addressing only (see instruction.h). |
188 | if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses)) |
189 | return Status::SuccessWithoutChange; |
190 | |
191 | // Do not process if module contains OpGroupDecorate. Additional |
192 | // support required in KillNamesAndDecorates(). |
193 | // TODO(greg-lunarg): Add support for OpGroupDecorate |
194 | for (auto& ai : get_module()->annotations()) |
195 | if (ai.opcode() == SpvOpGroupDecorate) return Status::SuccessWithoutChange; |
196 | // If any extensions in the module are not explicitly supported, |
197 | // return unmodified. |
198 | if (!AllExtensionsSupported()) return Status::SuccessWithoutChange; |
199 | // Process all entry point functions |
200 | ProcessFunction pfn = [this](Function* fp) { |
201 | return LocalSingleBlockLoadStoreElim(fp); |
202 | }; |
203 | |
204 | bool modified = context()->ProcessEntryPointCallTree(pfn); |
205 | return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; |
206 | } |
207 | |
208 | LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElimPass() = |
209 | default; |
210 | |
211 | Pass::Status LocalSingleBlockLoadStoreElimPass::Process() { |
212 | Initialize(); |
213 | return ProcessImpl(); |
214 | } |
215 | |
216 | void LocalSingleBlockLoadStoreElimPass::InitExtensions() { |
217 | extensions_whitelist_.clear(); |
218 | extensions_whitelist_.insert({ |
219 | "SPV_AMD_shader_explicit_vertex_parameter" , |
220 | "SPV_AMD_shader_trinary_minmax" , |
221 | "SPV_AMD_gcn_shader" , |
222 | "SPV_KHR_shader_ballot" , |
223 | "SPV_AMD_shader_ballot" , |
224 | "SPV_AMD_gpu_shader_half_float" , |
225 | "SPV_KHR_shader_draw_parameters" , |
226 | "SPV_KHR_subgroup_vote" , |
227 | "SPV_KHR_16bit_storage" , |
228 | "SPV_KHR_device_group" , |
229 | "SPV_KHR_multiview" , |
230 | "SPV_NVX_multiview_per_view_attributes" , |
231 | "SPV_NV_viewport_array2" , |
232 | "SPV_NV_stereo_view_rendering" , |
233 | "SPV_NV_sample_mask_override_coverage" , |
234 | "SPV_NV_geometry_shader_passthrough" , |
235 | "SPV_AMD_texture_gather_bias_lod" , |
236 | "SPV_KHR_storage_buffer_storage_class" , |
237 | "SPV_KHR_variable_pointers" , |
238 | "SPV_AMD_gpu_shader_int16" , |
239 | "SPV_KHR_post_depth_coverage" , |
240 | "SPV_KHR_shader_atomic_counter_ops" , |
241 | "SPV_EXT_shader_stencil_export" , |
242 | "SPV_EXT_shader_viewport_index_layer" , |
243 | "SPV_AMD_shader_image_load_store_lod" , |
244 | "SPV_AMD_shader_fragment_mask" , |
245 | "SPV_EXT_fragment_fully_covered" , |
246 | "SPV_AMD_gpu_shader_half_float_fetch" , |
247 | "SPV_GOOGLE_decorate_string" , |
248 | "SPV_GOOGLE_hlsl_functionality1" , |
249 | "SPV_GOOGLE_user_type" , |
250 | "SPV_NV_shader_subgroup_partitioned" , |
251 | "SPV_EXT_demote_to_helper_invocation" , |
252 | "SPV_EXT_descriptor_indexing" , |
253 | "SPV_NV_fragment_shader_barycentric" , |
254 | "SPV_NV_compute_shader_derivatives" , |
255 | "SPV_NV_shader_image_footprint" , |
256 | "SPV_NV_shading_rate" , |
257 | "SPV_NV_mesh_shader" , |
258 | "SPV_NV_ray_tracing" , |
259 | "SPV_KHR_ray_tracing" , |
260 | "SPV_KHR_ray_query" , |
261 | "SPV_EXT_fragment_invocation_density" , |
262 | "SPV_EXT_physical_storage_buffer" , |
263 | }); |
264 | } |
265 | |
266 | } // namespace opt |
267 | } // namespace spvtools |
268 | |