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 "iterator.h"
18 #include "local_single_block_elim_pass.h"
19
20 static const int kSpvEntryPointFunctionId = 1;
21 static const int kSpvStorePtrId = 0;
22 static const int kSpvStoreValId = 1;
23 static const int kSpvLoadPtrId = 0;
24 static const int kSpvAccessChainPtrId = 0;
25 static const int kSpvTypePointerStorageClass = 0;
26 static const int kSpvTypePointerTypeId = 1;
27
28 namespace spvtools {
29 namespace opt {
30
IsNonPtrAccessChain(const SpvOp opcode) const31 bool LocalSingleBlockLoadStoreElimPass::IsNonPtrAccessChain(
32 const SpvOp opcode) const {
33 return opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain;
34 }
35
IsMathType(const ir::Instruction * typeInst) const36 bool LocalSingleBlockLoadStoreElimPass::IsMathType(
37 const ir::Instruction* typeInst) const {
38 switch (typeInst->opcode()) {
39 case SpvOpTypeInt:
40 case SpvOpTypeFloat:
41 case SpvOpTypeBool:
42 case SpvOpTypeVector:
43 case SpvOpTypeMatrix:
44 return true;
45 default:
46 break;
47 }
48 return false;
49 }
50
IsTargetType(const ir::Instruction * typeInst) const51 bool LocalSingleBlockLoadStoreElimPass::IsTargetType(
52 const ir::Instruction* typeInst) const {
53 if (IsMathType(typeInst))
54 return true;
55 if (typeInst->opcode() == SpvOpTypeArray)
56 return IsMathType(def_use_mgr_->GetDef(typeInst->GetSingleWordOperand(1)));
57 if (typeInst->opcode() != SpvOpTypeStruct)
58 return false;
59 // All struct members must be math type
60 int nonMathComp = 0;
61 typeInst->ForEachInId([&nonMathComp,this](const uint32_t* tid) {
62 ir::Instruction* compTypeInst = def_use_mgr_->GetDef(*tid);
63 if (!IsMathType(compTypeInst)) ++nonMathComp;
64 });
65 return nonMathComp == 0;
66 }
67
GetPtr(ir::Instruction * ip,uint32_t * varId)68 ir::Instruction* LocalSingleBlockLoadStoreElimPass::GetPtr(
69 ir::Instruction* ip, uint32_t* varId) {
70 *varId = ip->GetSingleWordInOperand(
71 ip->opcode() == SpvOpStore ? kSpvStorePtrId : kSpvLoadPtrId);
72 ir::Instruction* ptrInst = def_use_mgr_->GetDef(*varId);
73 ir::Instruction* varInst = ptrInst;
74 while (IsNonPtrAccessChain(varInst->opcode())) {
75 *varId = varInst->GetSingleWordInOperand(kSpvAccessChainPtrId);
76 varInst = def_use_mgr_->GetDef(*varId);
77 }
78 return ptrInst;
79 }
80
IsTargetVar(uint32_t varId)81 bool LocalSingleBlockLoadStoreElimPass::IsTargetVar(uint32_t varId) {
82 if (seen_non_target_vars_.find(varId) != seen_non_target_vars_.end())
83 return false;
84 if (seen_target_vars_.find(varId) != seen_target_vars_.end())
85 return true;
86 const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
87 assert(varInst->opcode() == SpvOpVariable);
88 const uint32_t varTypeId = varInst->type_id();
89 const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
90 if (varTypeInst->GetSingleWordInOperand(kSpvTypePointerStorageClass) !=
91 SpvStorageClassFunction) {
92 seen_non_target_vars_.insert(varId);
93 return false;
94 }
95 const uint32_t varPteTypeId =
96 varTypeInst->GetSingleWordInOperand(kSpvTypePointerTypeId);
97 ir::Instruction* varPteTypeInst = def_use_mgr_->GetDef(varPteTypeId);
98 if (!IsTargetType(varPteTypeInst)) {
99 seen_non_target_vars_.insert(varId);
100 return false;
101 }
102 seen_target_vars_.insert(varId);
103 return true;
104 }
105
ReplaceAndDeleteLoad(ir::Instruction * loadInst,uint32_t replId)106 void LocalSingleBlockLoadStoreElimPass::ReplaceAndDeleteLoad(
107 ir::Instruction* loadInst, uint32_t replId) {
108 const uint32_t loadId = loadInst->result_id();
109 (void) def_use_mgr_->ReplaceAllUsesWith(loadId, replId);
110 // TODO(greg-lunarg): Consider moving DCE into separate pass
111 DCEInst(loadInst);
112 }
113
HasLoads(uint32_t ptrId) const114 bool LocalSingleBlockLoadStoreElimPass::HasLoads(uint32_t ptrId) const {
115 analysis::UseList* uses = def_use_mgr_->GetUses(ptrId);
116 if (uses == nullptr)
117 return false;
118 for (auto u : *uses) {
119 SpvOp op = u.inst->opcode();
120 if (IsNonPtrAccessChain(op)) {
121 if (HasLoads(u.inst->result_id()))
122 return true;
123 }
124 else {
125 // Conservatively assume that calls will do a load
126 // TODO(): Improve analysis around function calls
127 if (op == SpvOpLoad || op == SpvOpFunctionCall)
128 return true;
129 }
130 }
131 return false;
132 }
133
IsLiveVar(uint32_t varId) const134 bool LocalSingleBlockLoadStoreElimPass::IsLiveVar(uint32_t varId) const {
135 // non-function scope vars are live
136 const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
137 assert(varInst->opcode() == SpvOpVariable);
138 const uint32_t varTypeId = varInst->type_id();
139 const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
140 if (varTypeInst->GetSingleWordInOperand(kSpvTypePointerStorageClass) !=
141 SpvStorageClassFunction)
142 return true;
143 // test if variable is loaded from
144 return HasLoads(varId);
145 }
146
IsLiveStore(ir::Instruction * storeInst)147 bool LocalSingleBlockLoadStoreElimPass::IsLiveStore(
148 ir::Instruction* storeInst) {
149 // get store's variable
150 uint32_t varId;
151 (void) GetPtr(storeInst, &varId);
152 return IsLiveVar(varId);
153 }
154
AddStores(uint32_t ptr_id,std::queue<ir::Instruction * > * insts)155 void LocalSingleBlockLoadStoreElimPass::AddStores(
156 uint32_t ptr_id, std::queue<ir::Instruction*>* insts) {
157 analysis::UseList* uses = def_use_mgr_->GetUses(ptr_id);
158 if (uses != nullptr) {
159 for (auto u : *uses) {
160 if (IsNonPtrAccessChain(u.inst->opcode()))
161 AddStores(u.inst->result_id(), insts);
162 else if (u.inst->opcode() == SpvOpStore)
163 insts->push(u.inst);
164 }
165 }
166 }
167
DCEInst(ir::Instruction * inst)168 void LocalSingleBlockLoadStoreElimPass::DCEInst(ir::Instruction* inst) {
169 std::queue<ir::Instruction*> deadInsts;
170 deadInsts.push(inst);
171 while (!deadInsts.empty()) {
172 ir::Instruction* di = deadInsts.front();
173 // Don't delete labels
174 if (di->opcode() == SpvOpLabel) {
175 deadInsts.pop();
176 continue;
177 }
178 // Remember operands
179 std::vector<uint32_t> ids;
180 di->ForEachInId([&ids](uint32_t* iid) {
181 ids.push_back(*iid);
182 });
183 uint32_t varId = 0;
184 // Remember variable if dead load
185 if (di->opcode() == SpvOpLoad)
186 (void) GetPtr(di, &varId);
187 def_use_mgr_->KillInst(di);
188 // For all operands with no remaining uses, add their instruction
189 // to the dead instruction queue.
190 for (auto id : ids) {
191 analysis::UseList* uses = def_use_mgr_->GetUses(id);
192 if (uses == nullptr)
193 deadInsts.push(def_use_mgr_->GetDef(id));
194 }
195 // if a load was deleted and it was the variable's
196 // last load, add all its stores to dead queue
197 if (varId != 0 && !IsLiveVar(varId))
198 AddStores(varId, &deadInsts);
199 deadInsts.pop();
200 }
201 }
202
LocalSingleBlockLoadStoreElim(ir::Function * func)203 bool LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElim(
204 ir::Function* func) {
205 // Verify no CopyObject ops in function. This is a pre-SSA pass and
206 // is generally not useful for code already in CSSA form.
207 for (auto& blk : *func)
208 for (auto& inst : blk)
209 if (inst.opcode() == SpvOpCopyObject)
210 return false;
211 // Perform local store/load and load/load elimination on each block
212 bool modified = false;
213 for (auto bi = func->begin(); bi != func->end(); ++bi) {
214 var2store_.clear();
215 var2load_.clear();
216 pinned_vars_.clear();
217 for (auto ii = bi->begin(); ii != bi->end(); ++ii) {
218 switch (ii->opcode()) {
219 case SpvOpStore: {
220 // Verify store variable is target type
221 uint32_t varId;
222 ir::Instruction* ptrInst = GetPtr(&*ii, &varId);
223 if (!IsTargetVar(varId))
224 continue;
225 // Register the store
226 if (ptrInst->opcode() == SpvOpVariable) {
227 // if not pinned, look for WAW
228 if (pinned_vars_.find(varId) == pinned_vars_.end()) {
229 auto si = var2store_.find(varId);
230 if (si != var2store_.end()) {
231 def_use_mgr_->KillInst(si->second);
232 }
233 }
234 var2store_[varId] = &*ii;
235 }
236 else {
237 assert(IsNonPtrAccessChain(ptrInst->opcode()));
238 var2store_.erase(varId);
239 }
240 pinned_vars_.erase(varId);
241 var2load_.erase(varId);
242 } break;
243 case SpvOpLoad: {
244 // Verify store variable is target type
245 uint32_t varId;
246 ir::Instruction* ptrInst = GetPtr(&*ii, &varId);
247 if (!IsTargetVar(varId))
248 continue;
249 // Look for previous store or load
250 uint32_t replId = 0;
251 if (ptrInst->opcode() == SpvOpVariable) {
252 auto si = var2store_.find(varId);
253 if (si != var2store_.end()) {
254 replId = si->second->GetSingleWordInOperand(kSpvStoreValId);
255 }
256 else {
257 auto li = var2load_.find(varId);
258 if (li != var2load_.end()) {
259 replId = li->second->result_id();
260 }
261 }
262 }
263 if (replId != 0) {
264 // replace load's result id and delete load
265 ReplaceAndDeleteLoad(&*ii, replId);
266 modified = true;
267 }
268 else {
269 if (ptrInst->opcode() == SpvOpVariable)
270 var2load_[varId] = &*ii; // register load
271 pinned_vars_.insert(varId);
272 }
273 } break;
274 case SpvOpFunctionCall: {
275 // Conservatively assume all locals are redefined for now.
276 // TODO(): Handle more optimally
277 var2store_.clear();
278 var2load_.clear();
279 pinned_vars_.clear();
280 } break;
281 default:
282 break;
283 }
284 }
285 // Go back and delete useless stores in block
286 // TODO(greg-lunarg): Consider moving DCE into separate pass
287 for (auto ii = bi->begin(); ii != bi->end(); ++ii) {
288 if (ii->opcode() != SpvOpStore)
289 continue;
290 if (IsLiveStore(&*ii))
291 continue;
292 DCEInst(&*ii);
293 }
294 }
295 return modified;
296 }
297
Initialize(ir::Module * module)298 void LocalSingleBlockLoadStoreElimPass::Initialize(ir::Module* module) {
299
300 module_ = module;
301
302 // Initialize function and block maps
303 id2function_.clear();
304 for (auto& fn : *module_)
305 id2function_[fn.result_id()] = &fn;
306
307 // Initialize Target Type Caches
308 seen_target_vars_.clear();
309 seen_non_target_vars_.clear();
310
311 // TODO(): Reuse def/use from previous passes
312 def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
313
314 // Start new ids with next availablein module
315 next_id_ = module_->id_bound();
316
317 };
318
ProcessImpl()319 Pass::Status LocalSingleBlockLoadStoreElimPass::ProcessImpl() {
320 // Assumes logical addressing only
321 if (module_->HasCapability(SpvCapabilityAddresses))
322 return Status::SuccessWithoutChange;
323 bool modified = false;
324 // Call Mem2Reg on all remaining functions.
325 for (auto& e : module_->entry_points()) {
326 ir::Function* fn =
327 id2function_[e.GetSingleWordOperand(kSpvEntryPointFunctionId)];
328 modified = LocalSingleBlockLoadStoreElim(fn) || modified;
329 }
330 FinalizeNextId(module_);
331 return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
332 }
333
LocalSingleBlockLoadStoreElimPass()334 LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElimPass()
335 : module_(nullptr), def_use_mgr_(nullptr), next_id_(0) {}
336
Process(ir::Module * module)337 Pass::Status LocalSingleBlockLoadStoreElimPass::Process(ir::Module* module) {
338 Initialize(module);
339 return ProcessImpl();
340 }
341
342 } // namespace opt
343 } // namespace spvtools
344
345