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 "local_single_store_elim_pass.h"
18
19 #include "cfa.h"
20 #include "iterator.h"
21 #include "spirv/1.0/GLSL.std.450.h"
22
23 static const int kSpvEntryPointFunctionId = 1;
24 static const int kSpvStorePtrId = 0;
25 static const int kSpvStoreValId = 1;
26 static const int kSpvLoadPtrId = 0;
27 static const int kSpvAccessChainPtrId = 0;
28 static const int kSpvTypePointerStorageClass = 0;
29 static const int kSpvTypePointerTypeId = 1;
30
31 // Universal Limit of ResultID + 1
32 static const int kInvalidId = 0x400000;
33
34 namespace spvtools {
35 namespace opt {
36
IsNonPtrAccessChain(const SpvOp opcode) const37 bool LocalSingleStoreElimPass::IsNonPtrAccessChain(const SpvOp opcode) const {
38 return opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain;
39 }
40
IsMathType(const ir::Instruction * typeInst) const41 bool LocalSingleStoreElimPass::IsMathType(
42 const ir::Instruction* typeInst) const {
43 switch (typeInst->opcode()) {
44 case SpvOpTypeInt:
45 case SpvOpTypeFloat:
46 case SpvOpTypeBool:
47 case SpvOpTypeVector:
48 case SpvOpTypeMatrix:
49 return true;
50 default:
51 break;
52 }
53 return false;
54 }
55
IsTargetType(const ir::Instruction * typeInst) const56 bool LocalSingleStoreElimPass::IsTargetType(
57 const ir::Instruction* typeInst) const {
58 if (IsMathType(typeInst))
59 return true;
60 if (typeInst->opcode() == SpvOpTypeArray)
61 return IsMathType(def_use_mgr_->GetDef(typeInst->GetSingleWordOperand(1)));
62 if (typeInst->opcode() != SpvOpTypeStruct)
63 return false;
64 // All struct members must be math type
65 int nonMathComp = 0;
66 typeInst->ForEachInId([&nonMathComp,this](const uint32_t* tid) {
67 ir::Instruction* compTypeInst = def_use_mgr_->GetDef(*tid);
68 if (!IsMathType(compTypeInst)) ++nonMathComp;
69 });
70 return nonMathComp == 0;
71 }
72
GetPtr(ir::Instruction * ip,uint32_t * varId)73 ir::Instruction* LocalSingleStoreElimPass::GetPtr(
74 ir::Instruction* ip, uint32_t* varId) {
75 *varId = ip->GetSingleWordInOperand(
76 ip->opcode() == SpvOpStore ? kSpvStorePtrId : kSpvLoadPtrId);
77 ir::Instruction* ptrInst = def_use_mgr_->GetDef(*varId);
78 ir::Instruction* varInst = ptrInst;
79 while (IsNonPtrAccessChain(varInst->opcode())) {
80 *varId = varInst->GetSingleWordInOperand(kSpvAccessChainPtrId);
81 varInst = def_use_mgr_->GetDef(*varId);
82 }
83 return ptrInst;
84 }
85
IsTargetVar(uint32_t varId)86 bool LocalSingleStoreElimPass::IsTargetVar(uint32_t varId) {
87 if (seen_non_target_vars_.find(varId) != seen_non_target_vars_.end())
88 return false;
89 if (seen_target_vars_.find(varId) != seen_target_vars_.end())
90 return true;
91 const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
92 assert(varInst->opcode() == SpvOpVariable);
93 const uint32_t varTypeId = varInst->type_id();
94 const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
95 if (varTypeInst->GetSingleWordInOperand(kSpvTypePointerStorageClass) !=
96 SpvStorageClassFunction) {
97 seen_non_target_vars_.insert(varId);
98 return false;
99 }
100 const uint32_t varPteTypeId =
101 varTypeInst->GetSingleWordInOperand(kSpvTypePointerTypeId);
102 ir::Instruction* varPteTypeInst = def_use_mgr_->GetDef(varPteTypeId);
103 if (!IsTargetType(varPteTypeInst)) {
104 seen_non_target_vars_.insert(varId);
105 return false;
106 }
107 seen_target_vars_.insert(varId);
108 return true;
109 }
110
HasOnlySupportedRefs(uint32_t ptrId)111 bool LocalSingleStoreElimPass::HasOnlySupportedRefs(uint32_t ptrId) {
112 if (supported_ref_ptrs_.find(ptrId) != supported_ref_ptrs_.end())
113 return true;
114 analysis::UseList* uses = def_use_mgr_->GetUses(ptrId);
115 assert(uses != nullptr);
116 for (auto u : *uses) {
117 SpvOp op = u.inst->opcode();
118 if (IsNonPtrAccessChain(op)) {
119 if (!HasOnlySupportedRefs(u.inst->result_id()))
120 return false;
121 }
122 else if (op != SpvOpStore && op != SpvOpLoad && op != SpvOpName)
123 return false;
124 }
125 supported_ref_ptrs_.insert(ptrId);
126 return true;
127 }
128
SingleStoreAnalyze(ir::Function * func)129 void LocalSingleStoreElimPass::SingleStoreAnalyze(ir::Function* func) {
130 ssa_var2store_.clear();
131 non_ssa_vars_.clear();
132 store2idx_.clear();
133 store2blk_.clear();
134 for (auto bi = func->begin(); bi != func->end(); ++bi) {
135 uint32_t instIdx = 0;
136 for (auto ii = bi->begin(); ii != bi->end(); ++ii, ++instIdx) {
137 switch (ii->opcode()) {
138 case SpvOpStore: {
139 // Verify store variable is target type
140 uint32_t varId;
141 ir::Instruction* ptrInst = GetPtr(&*ii, &varId);
142 if (non_ssa_vars_.find(varId) != non_ssa_vars_.end())
143 continue;
144 if (!HasOnlySupportedRefs(varId)) {
145 non_ssa_vars_.insert(varId);
146 continue;
147 }
148 if (IsNonPtrAccessChain(ptrInst->opcode())) {
149 non_ssa_vars_.insert(varId);
150 ssa_var2store_.erase(varId);
151 continue;
152 }
153 // Verify target type and function storage class
154 if (!IsTargetVar(varId)) {
155 non_ssa_vars_.insert(varId);
156 continue;
157 }
158 // Ignore variables with multiple stores
159 if (ssa_var2store_.find(varId) != ssa_var2store_.end()) {
160 non_ssa_vars_.insert(varId);
161 ssa_var2store_.erase(varId);
162 continue;
163 }
164 // Remember pointer to variable's store and it's
165 // ordinal position in block
166 ssa_var2store_[varId] = &*ii;
167 store2idx_[&*ii] = instIdx;
168 store2blk_[&*ii] = &*bi;
169 } break;
170 default:
171 break;
172 } // switch
173 }
174 }
175 }
176
ReplaceAndDeleteLoad(ir::Instruction * loadInst,uint32_t replId)177 void LocalSingleStoreElimPass::ReplaceAndDeleteLoad(
178 ir::Instruction* loadInst, uint32_t replId) {
179 (void) def_use_mgr_->ReplaceAllUsesWith(loadInst->result_id(), replId);
180 DCEInst(loadInst);
181 }
182
183 LocalSingleStoreElimPass::GetBlocksFunction
AugmentedCFGSuccessorsFunction() const184 LocalSingleStoreElimPass::AugmentedCFGSuccessorsFunction() const {
185 return [this](const ir::BasicBlock* block) {
186 auto asmi = augmented_successors_map_.find(block);
187 if (asmi != augmented_successors_map_.end())
188 return &(*asmi).second;
189 auto smi = successors_map_.find(block);
190 return &(*smi).second;
191 };
192 }
193
194 LocalSingleStoreElimPass::GetBlocksFunction
AugmentedCFGPredecessorsFunction() const195 LocalSingleStoreElimPass::AugmentedCFGPredecessorsFunction() const {
196 return [this](const ir::BasicBlock* block) {
197 auto apmi = augmented_predecessors_map_.find(block);
198 if (apmi != augmented_predecessors_map_.end())
199 return &(*apmi).second;
200 auto pmi = predecessors_map_.find(block);
201 return &(*pmi).second;
202 };
203 }
204
CalculateImmediateDominators(ir::Function * func)205 void LocalSingleStoreElimPass::CalculateImmediateDominators(
206 ir::Function* func) {
207 // Compute CFG
208 vector<ir::BasicBlock*> ordered_blocks;
209 predecessors_map_.clear();
210 successors_map_.clear();
211 for (auto& blk : *func) {
212 ordered_blocks.push_back(&blk);
213 blk.ForEachSuccessorLabel([&blk, &ordered_blocks, this](uint32_t sbid) {
214 successors_map_[&blk].push_back(label2block_[sbid]);
215 predecessors_map_[label2block_[sbid]].push_back(&blk);
216 });
217 }
218 // Compute Augmented CFG
219 augmented_successors_map_.clear();
220 augmented_predecessors_map_.clear();
221 successors_map_[&pseudo_exit_block_] = {};
222 predecessors_map_[&pseudo_entry_block_] = {};
223 auto succ_func = [this](const ir::BasicBlock* b)
224 { return &successors_map_[b]; };
225 auto pred_func = [this](const ir::BasicBlock* b)
226 { return &predecessors_map_[b]; };
227 CFA<ir::BasicBlock>::ComputeAugmentedCFG(
228 ordered_blocks,
229 &pseudo_entry_block_,
230 &pseudo_exit_block_,
231 &augmented_successors_map_,
232 &augmented_predecessors_map_,
233 succ_func,
234 pred_func);
235 // Compute Dominators
236 vector<const ir::BasicBlock*> postorder;
237 auto ignore_block = [](cbb_ptr) {};
238 auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
239 spvtools::CFA<ir::BasicBlock>::DepthFirstTraversal(
240 ordered_blocks[0], AugmentedCFGSuccessorsFunction(),
241 ignore_block, [&](cbb_ptr b) { postorder.push_back(b); },
242 ignore_edge);
243 auto edges = spvtools::CFA<ir::BasicBlock>::CalculateDominators(
244 postorder, AugmentedCFGPredecessorsFunction());
245 idom_.clear();
246 for (auto edge : edges)
247 idom_[edge.first] = edge.second;
248 }
249
Dominates(ir::BasicBlock * blk0,uint32_t idx0,ir::BasicBlock * blk1,uint32_t idx1)250 bool LocalSingleStoreElimPass::Dominates(
251 ir::BasicBlock* blk0, uint32_t idx0,
252 ir::BasicBlock* blk1, uint32_t idx1) {
253 if (blk0 == blk1)
254 return idx0 <= idx1;
255 ir::BasicBlock* b = blk1;
256 while (idom_[b] != b) {
257 b = idom_[b];
258 if (b == blk0)
259 return true;
260 }
261 return false;
262 }
263
SingleStoreProcess(ir::Function * func)264 bool LocalSingleStoreElimPass::SingleStoreProcess(ir::Function* func) {
265 CalculateImmediateDominators(func);
266 bool modified = false;
267 for (auto bi = func->begin(); bi != func->end(); ++bi) {
268 uint32_t instIdx = 0;
269 for (auto ii = bi->begin(); ii != bi->end(); ++ii, ++instIdx) {
270 if (ii->opcode() != SpvOpLoad)
271 continue;
272 uint32_t varId;
273 ir::Instruction* ptrInst = GetPtr(&*ii, &varId);
274 // Skip access chain loads
275 if (IsNonPtrAccessChain(ptrInst->opcode()))
276 continue;
277 if (ptrInst->opcode() != SpvOpVariable)
278 continue;
279 const auto vsi = ssa_var2store_.find(varId);
280 if (vsi == ssa_var2store_.end())
281 continue;
282 if (non_ssa_vars_.find(varId) != non_ssa_vars_.end())
283 continue;
284 // store must dominate load
285 if (!Dominates(store2blk_[vsi->second], store2idx_[vsi->second], &*bi, instIdx))
286 continue;
287 // Use store value as replacement id
288 uint32_t replId = vsi->second->GetSingleWordInOperand(kSpvStoreValId);
289 // replace all instances of the load's id with the SSA value's id
290 ReplaceAndDeleteLoad(&*ii, replId);
291 modified = true;
292 }
293 }
294 return modified;
295 }
296
HasLoads(uint32_t varId) const297 bool LocalSingleStoreElimPass::HasLoads(uint32_t varId) const {
298 analysis::UseList* uses = def_use_mgr_->GetUses(varId);
299 if (uses == nullptr)
300 return false;
301 for (auto u : *uses) {
302 SpvOp op = u.inst->opcode();
303 // TODO(): The following is slightly conservative. Could be
304 // better handling of non-store/name.
305 if (IsNonPtrAccessChain(op) || op == SpvOpCopyObject) {
306 if (HasLoads(u.inst->result_id()))
307 return true;
308 }
309 else if (op != SpvOpStore && op != SpvOpName)
310 return true;
311 }
312 return false;
313 }
314
IsLiveVar(uint32_t varId) const315 bool LocalSingleStoreElimPass::IsLiveVar(uint32_t varId) const {
316 // non-function scope vars are live
317 const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
318 assert(varInst->opcode() == SpvOpVariable);
319 const uint32_t varTypeId = varInst->type_id();
320 const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
321 if (varTypeInst->GetSingleWordInOperand(kSpvTypePointerStorageClass) !=
322 SpvStorageClassFunction)
323 return true;
324 // test if variable is loaded from
325 return HasLoads(varId);
326 }
327
IsLiveStore(ir::Instruction * storeInst)328 bool LocalSingleStoreElimPass::IsLiveStore(ir::Instruction* storeInst) {
329 // get store's variable
330 uint32_t varId;
331 (void) GetPtr(storeInst, &varId);
332 return IsLiveVar(varId);
333 }
334
AddStores(uint32_t ptr_id,std::queue<ir::Instruction * > * insts)335 void LocalSingleStoreElimPass::AddStores(
336 uint32_t ptr_id, std::queue<ir::Instruction*>* insts) {
337 analysis::UseList* uses = def_use_mgr_->GetUses(ptr_id);
338 if (uses != nullptr) {
339 for (auto u : *uses) {
340 if (IsNonPtrAccessChain(u.inst->opcode()))
341 AddStores(u.inst->result_id(), insts);
342 else if (u.inst->opcode() == SpvOpStore)
343 insts->push(u.inst);
344 }
345 }
346 }
347
DCEInst(ir::Instruction * inst)348 void LocalSingleStoreElimPass::DCEInst(ir::Instruction* inst) {
349 std::queue<ir::Instruction*> deadInsts;
350 deadInsts.push(inst);
351 while (!deadInsts.empty()) {
352 ir::Instruction* di = deadInsts.front();
353 // Don't delete labels
354 if (di->opcode() == SpvOpLabel) {
355 deadInsts.pop();
356 continue;
357 }
358 // Remember operands
359 std::queue<uint32_t> ids;
360 di->ForEachInId([&ids](uint32_t* iid) {
361 ids.push(*iid);
362 });
363 uint32_t varId = 0;
364 // Remember variable if dead load
365 if (di->opcode() == SpvOpLoad)
366 (void) GetPtr(di, &varId);
367 def_use_mgr_->KillInst(di);
368 // For all operands with no remaining uses, add their instruction
369 // to the dead instruction queue.
370 while (!ids.empty()) {
371 uint32_t id = ids.front();
372 analysis::UseList* uses = def_use_mgr_->GetUses(id);
373 if (uses == nullptr)
374 deadInsts.push(def_use_mgr_->GetDef(id));
375 ids.pop();
376 }
377 // if a load was deleted and it was the variable's
378 // last load, add all its stores to dead queue
379 if (varId != 0 && !IsLiveVar(varId))
380 AddStores(varId, &deadInsts);
381 deadInsts.pop();
382 }
383 }
384
SingleStoreDCE()385 bool LocalSingleStoreElimPass::SingleStoreDCE() {
386 bool modified = false;
387 for (auto v : ssa_var2store_) {
388 // check that it hasn't already been DCE'd
389 if (v.second->opcode() != SpvOpStore)
390 continue;
391 if (non_ssa_vars_.find(v.first) != non_ssa_vars_.end())
392 continue;
393 if (!IsLiveStore(v.second)) {
394 DCEInst(v.second);
395 modified = true;
396 }
397 }
398 return modified;
399 }
400
LocalSingleStoreElim(ir::Function * func)401 bool LocalSingleStoreElimPass::LocalSingleStoreElim(ir::Function* func) {
402 bool modified = false;
403 SingleStoreAnalyze(func);
404 if (ssa_var2store_.empty())
405 return false;
406 modified |= SingleStoreProcess(func);
407 modified |= SingleStoreDCE();
408 return modified;
409 }
410
Initialize(ir::Module * module)411 void LocalSingleStoreElimPass::Initialize(ir::Module* module) {
412 module_ = module;
413
414 // Initialize function and block maps
415 id2function_.clear();
416 label2block_.clear();
417 for (auto& fn : *module_) {
418 id2function_[fn.result_id()] = &fn;
419 for (auto& blk : fn) {
420 uint32_t bid = blk.id();
421 label2block_[bid] = &blk;
422 }
423 }
424
425 // Initialize Target Type Caches
426 seen_target_vars_.clear();
427 seen_non_target_vars_.clear();
428
429 // Initialize Supported Ref Pointer Cache
430 supported_ref_ptrs_.clear();
431
432 // TODO: Reuse def/use (and other state) from previous passes
433 def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
434
435 // Initialize next unused Id
436 next_id_ = module_->id_bound();
437 };
438
ProcessImpl()439 Pass::Status LocalSingleStoreElimPass::ProcessImpl() {
440 // Assumes logical addressing only
441 if (module_->HasCapability(SpvCapabilityAddresses))
442 return Status::SuccessWithoutChange;
443 bool modified = false;
444 // Call Mem2Reg on all remaining functions.
445 for (auto& e : module_->entry_points()) {
446 ir::Function* fn =
447 id2function_[e.GetSingleWordOperand(kSpvEntryPointFunctionId)];
448 modified = LocalSingleStoreElim(fn) || modified;
449 }
450 FinalizeNextId(module_);
451 return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
452 }
453
LocalSingleStoreElimPass()454 LocalSingleStoreElimPass::LocalSingleStoreElimPass()
455 : module_(nullptr), def_use_mgr_(nullptr),
456 pseudo_entry_block_(std::unique_ptr<ir::Instruction>(
457 new ir::Instruction(SpvOpLabel, 0, 0, {}))),
458 pseudo_exit_block_(std::unique_ptr<ir::Instruction>(
459 new ir::Instruction(SpvOpLabel, 0, kInvalidId, {}))),
460 next_id_(0) {}
461
Process(ir::Module * module)462 Pass::Status LocalSingleStoreElimPass::Process(ir::Module* module) {
463 Initialize(module);
464 return ProcessImpl();
465 }
466
467 } // namespace opt
468 } // namespace spvtools
469
470