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
23namespace spvtools {
24namespace opt {
25
26namespace {
27
28const uint32_t kStoreValIdInIdx = 1;
29const uint32_t kVariableInitIdInIdx = 1;
30
31} // anonymous namespace
32
33bool 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
48bool 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
59Pass::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
74LocalSingleStoreElimPass::LocalSingleStoreElimPass() = default;
75
76Pass::Status LocalSingleStoreElimPass::Process() {
77 InitExtensionWhiteList();
78 return ProcessImpl();
79}
80
81void 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}
126bool 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
139Instruction* 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
187void 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
198bool 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
220bool 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