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
LocalSingleStoreElim(Function * func)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
AllExtensionsSupported() const48 bool LocalSingleStoreElimPass::AllExtensionsSupported() const {
49 // If any extension not in allowlist, 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_allowlist_.find(extName) == extensions_allowlist_.end())
54 return false;
55 }
56 // only allow NonSemantic.Shader.DebugInfo.100, we cannot safely optimise
57 // around unknown extended
58 // instruction sets even if they are non-semantic
59 for (auto& inst : context()->module()->ext_inst_imports()) {
60 assert(inst.opcode() == SpvOpExtInstImport &&
61 "Expecting an import of an extension's instruction set.");
62 const char* extension_name =
63 reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
64 if (0 == std::strncmp(extension_name, "NonSemantic.", 12) &&
65 0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100",
66 32)) {
67 return false;
68 }
69 }
70 return true;
71 }
72
ProcessImpl()73 Pass::Status LocalSingleStoreElimPass::ProcessImpl() {
74 // Assumes relaxed logical addressing only (see instruction.h)
75 if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
76 return Status::SuccessWithoutChange;
77
78 // Do not process if any disallowed extensions are enabled
79 if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
80 // Process all entry point functions
81 ProcessFunction pfn = [this](Function* fp) {
82 return LocalSingleStoreElim(fp);
83 };
84 bool modified = context()->ProcessReachableCallTree(pfn);
85 return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
86 }
87
88 LocalSingleStoreElimPass::LocalSingleStoreElimPass() = default;
89
Process()90 Pass::Status LocalSingleStoreElimPass::Process() {
91 InitExtensionAllowList();
92 return ProcessImpl();
93 }
94
InitExtensionAllowList()95 void LocalSingleStoreElimPass::InitExtensionAllowList() {
96 extensions_allowlist_.insert({
97 "SPV_AMD_shader_explicit_vertex_parameter",
98 "SPV_AMD_shader_trinary_minmax",
99 "SPV_AMD_gcn_shader",
100 "SPV_KHR_shader_ballot",
101 "SPV_AMD_shader_ballot",
102 "SPV_AMD_gpu_shader_half_float",
103 "SPV_KHR_shader_draw_parameters",
104 "SPV_KHR_subgroup_vote",
105 "SPV_KHR_8bit_storage",
106 "SPV_KHR_16bit_storage",
107 "SPV_KHR_device_group",
108 "SPV_KHR_multiview",
109 "SPV_NVX_multiview_per_view_attributes",
110 "SPV_NV_viewport_array2",
111 "SPV_NV_stereo_view_rendering",
112 "SPV_NV_sample_mask_override_coverage",
113 "SPV_NV_geometry_shader_passthrough",
114 "SPV_AMD_texture_gather_bias_lod",
115 "SPV_KHR_storage_buffer_storage_class",
116 "SPV_KHR_variable_pointers",
117 "SPV_AMD_gpu_shader_int16",
118 "SPV_KHR_post_depth_coverage",
119 "SPV_KHR_shader_atomic_counter_ops",
120 "SPV_EXT_shader_stencil_export",
121 "SPV_EXT_shader_viewport_index_layer",
122 "SPV_AMD_shader_image_load_store_lod",
123 "SPV_AMD_shader_fragment_mask",
124 "SPV_EXT_fragment_fully_covered",
125 "SPV_AMD_gpu_shader_half_float_fetch",
126 "SPV_GOOGLE_decorate_string",
127 "SPV_GOOGLE_hlsl_functionality1",
128 "SPV_NV_shader_subgroup_partitioned",
129 "SPV_EXT_descriptor_indexing",
130 "SPV_NV_fragment_shader_barycentric",
131 "SPV_NV_compute_shader_derivatives",
132 "SPV_NV_shader_image_footprint",
133 "SPV_NV_shading_rate",
134 "SPV_NV_mesh_shader",
135 "SPV_NV_ray_tracing",
136 "SPV_KHR_ray_query",
137 "SPV_EXT_fragment_invocation_density",
138 "SPV_EXT_physical_storage_buffer",
139 "SPV_KHR_terminate_invocation",
140 "SPV_KHR_subgroup_uniform_control_flow",
141 "SPV_KHR_integer_dot_product",
142 "SPV_EXT_shader_image_int64",
143 "SPV_KHR_non_semantic_info",
144 });
145 }
ProcessVariable(Instruction * var_inst)146 bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) {
147 std::vector<Instruction*> users;
148 FindUses(var_inst, &users);
149
150 Instruction* store_inst = FindSingleStoreAndCheckUses(var_inst, users);
151
152 if (store_inst == nullptr) {
153 return false;
154 }
155
156 bool all_rewritten;
157 bool modified = RewriteLoads(store_inst, users, &all_rewritten);
158
159 // If all uses are rewritten and the variable has a DebugDeclare and the
160 // variable is not an aggregate, add a DebugValue after the store and remove
161 // the DebugDeclare.
162 uint32_t var_id = var_inst->result_id();
163 if (all_rewritten &&
164 context()->get_debug_info_mgr()->IsVariableDebugDeclared(var_id)) {
165 const analysis::Type* var_type =
166 context()->get_type_mgr()->GetType(var_inst->type_id());
167 const analysis::Type* store_type = var_type->AsPointer()->pointee_type();
168 if (!(store_type->AsStruct() || store_type->AsArray())) {
169 modified |= RewriteDebugDeclares(store_inst, var_id);
170 }
171 }
172
173 return modified;
174 }
175
RewriteDebugDeclares(Instruction * store_inst,uint32_t var_id)176 bool LocalSingleStoreElimPass::RewriteDebugDeclares(Instruction* store_inst,
177 uint32_t var_id) {
178 std::unordered_set<Instruction*> invisible_decls;
179 uint32_t value_id = store_inst->GetSingleWordInOperand(1);
180 bool modified =
181 context()->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(
182 store_inst, var_id, value_id, store_inst, &invisible_decls);
183
184 // For cases like the argument passing for an inlined function, the value
185 // assignment is out of DebugDeclare's scope, but we have to preserve the
186 // value assignment information using DebugValue. Generally, we need
187 // ssa-rewrite analysis to decide a proper value assignment but at this point
188 // we confirm that |var_id| has a single store. We can safely add DebugValue.
189 if (!invisible_decls.empty()) {
190 BasicBlock* store_block = context()->get_instr_block(store_inst);
191 DominatorAnalysis* dominator_analysis =
192 context()->GetDominatorAnalysis(store_block->GetParent());
193 for (auto* decl : invisible_decls) {
194 if (dominator_analysis->Dominates(store_inst, decl)) {
195 context()->get_debug_info_mgr()->AddDebugValueForDecl(decl, value_id,
196 decl, store_inst);
197 modified = true;
198 }
199 }
200 }
201 modified |= context()->get_debug_info_mgr()->KillDebugDeclares(var_id);
202 return modified;
203 }
204
FindSingleStoreAndCheckUses(Instruction * var_inst,const std::vector<Instruction * > & users) const205 Instruction* LocalSingleStoreElimPass::FindSingleStoreAndCheckUses(
206 Instruction* var_inst, const std::vector<Instruction*>& users) const {
207 // Make sure there is exactly 1 store.
208 Instruction* store_inst = nullptr;
209
210 // If |var_inst| has an initializer, then that will count as a store.
211 if (var_inst->NumInOperands() > 1) {
212 store_inst = var_inst;
213 }
214
215 for (Instruction* user : users) {
216 switch (user->opcode()) {
217 case SpvOpStore:
218 // Since we are in the relaxed addressing mode, the use has to be the
219 // base address of the store, and not the value being store. Otherwise,
220 // we would have a pointer to a pointer to function scope memory, which
221 // is not allowed.
222 if (store_inst == nullptr) {
223 store_inst = user;
224 } else {
225 // More than 1 store.
226 return nullptr;
227 }
228 break;
229 case SpvOpAccessChain:
230 case SpvOpInBoundsAccessChain:
231 if (FeedsAStore(user)) {
232 // Has a partial store. Cannot propagate that.
233 return nullptr;
234 }
235 break;
236 case SpvOpLoad:
237 case SpvOpImageTexelPointer:
238 case SpvOpName:
239 case SpvOpCopyObject:
240 break;
241 case SpvOpExtInst: {
242 auto dbg_op = user->GetCommonDebugOpcode();
243 if (dbg_op == CommonDebugInfoDebugDeclare ||
244 dbg_op == CommonDebugInfoDebugValue) {
245 break;
246 }
247 return nullptr;
248 }
249 default:
250 if (!user->IsDecoration()) {
251 // Don't know if this instruction modifies the variable.
252 // Conservatively assume it is a store.
253 return nullptr;
254 }
255 break;
256 }
257 }
258 return store_inst;
259 }
260
FindUses(const Instruction * var_inst,std::vector<Instruction * > * users) const261 void LocalSingleStoreElimPass::FindUses(
262 const Instruction* var_inst, std::vector<Instruction*>* users) const {
263 analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
264 def_use_mgr->ForEachUser(var_inst, [users, this](Instruction* user) {
265 users->push_back(user);
266 if (user->opcode() == SpvOpCopyObject) {
267 FindUses(user, users);
268 }
269 });
270 }
271
FeedsAStore(Instruction * inst) const272 bool LocalSingleStoreElimPass::FeedsAStore(Instruction* inst) const {
273 analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
274 return !def_use_mgr->WhileEachUser(inst, [this](Instruction* user) {
275 switch (user->opcode()) {
276 case SpvOpStore:
277 return false;
278 case SpvOpAccessChain:
279 case SpvOpInBoundsAccessChain:
280 case SpvOpCopyObject:
281 return !FeedsAStore(user);
282 case SpvOpLoad:
283 case SpvOpImageTexelPointer:
284 case SpvOpName:
285 return true;
286 default:
287 // Don't know if this instruction modifies the variable.
288 // Conservatively assume it is a store.
289 return user->IsDecoration();
290 }
291 });
292 }
293
RewriteLoads(Instruction * store_inst,const std::vector<Instruction * > & uses,bool * all_rewritten)294 bool LocalSingleStoreElimPass::RewriteLoads(
295 Instruction* store_inst, const std::vector<Instruction*>& uses,
296 bool* all_rewritten) {
297 BasicBlock* store_block = context()->get_instr_block(store_inst);
298 DominatorAnalysis* dominator_analysis =
299 context()->GetDominatorAnalysis(store_block->GetParent());
300
301 uint32_t stored_id;
302 if (store_inst->opcode() == SpvOpStore)
303 stored_id = store_inst->GetSingleWordInOperand(kStoreValIdInIdx);
304 else
305 stored_id = store_inst->GetSingleWordInOperand(kVariableInitIdInIdx);
306
307 *all_rewritten = true;
308 bool modified = false;
309 for (Instruction* use : uses) {
310 if (use->opcode() == SpvOpStore) continue;
311 auto dbg_op = use->GetCommonDebugOpcode();
312 if (dbg_op == CommonDebugInfoDebugDeclare ||
313 dbg_op == CommonDebugInfoDebugValue)
314 continue;
315 if (use->opcode() == SpvOpLoad &&
316 dominator_analysis->Dominates(store_inst, use)) {
317 modified = true;
318 context()->KillNamesAndDecorates(use->result_id());
319 context()->ReplaceAllUsesWith(use->result_id(), stored_id);
320 context()->KillInst(use);
321 } else {
322 *all_rewritten = false;
323 }
324 }
325
326 return modified;
327 }
328
329 } // namespace opt
330 } // namespace spvtools
331