• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_ssa_elim_pass.h"
18 
19 #include "iterator.h"
20 #include "cfa.h"
21 
22 namespace spvtools {
23 namespace opt {
24 
25 namespace {
26 
27 const uint32_t kEntryPointFunctionIdInIdx = 1;
28 const uint32_t kStorePtrIdInIdx = 0;
29 const uint32_t kStoreValIdInIdx = 1;
30 const uint32_t kLoadPtrIdInIdx = 0;
31 const uint32_t kAccessChainPtrIdInIdx = 0;
32 const uint32_t kTypePointerStorageClassInIdx = 0;
33 const uint32_t kTypePointerTypeIdInIdx = 1;
34 const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
35 const uint32_t kLoopMergeMergeBlockIdInIdx = 0;
36 const uint32_t kLoopMergeContinueBlockIdInIdx = 1;
37 const uint32_t kCopyObjectOperandInIdx = 0;
38 
39 } // anonymous namespace
40 
IsNonPtrAccessChain(const SpvOp opcode) const41 bool LocalMultiStoreElimPass::IsNonPtrAccessChain(
42     const SpvOp opcode) const {
43   return opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain;
44 }
45 
IsMathType(const ir::Instruction * typeInst) const46 bool LocalMultiStoreElimPass::IsMathType(
47     const ir::Instruction* typeInst) const {
48   switch (typeInst->opcode()) {
49     case SpvOpTypeInt:
50     case SpvOpTypeFloat:
51     case SpvOpTypeBool:
52     case SpvOpTypeVector:
53     case SpvOpTypeMatrix:
54       return true;
55     default:
56       break;
57   }
58   return false;
59 }
60 
IsTargetType(const ir::Instruction * typeInst) const61 bool LocalMultiStoreElimPass::IsTargetType(
62     const ir::Instruction* typeInst) const {
63   if (IsMathType(typeInst))
64     return true;
65   if (typeInst->opcode() == SpvOpTypeArray)
66     return IsMathType(def_use_mgr_->GetDef(typeInst->GetSingleWordOperand(1)));
67   if (typeInst->opcode() != SpvOpTypeStruct)
68     return false;
69   // All struct members must be math type
70   int nonMathComp = 0;
71   typeInst->ForEachInId([&nonMathComp,this](const uint32_t* tid) {
72     const ir::Instruction* compTypeInst = def_use_mgr_->GetDef(*tid);
73     if (!IsMathType(compTypeInst)) ++nonMathComp;
74   });
75   return nonMathComp == 0;
76 }
77 
GetPtr(ir::Instruction * ip,uint32_t * varId)78 ir::Instruction* LocalMultiStoreElimPass::GetPtr(
79       ir::Instruction* ip, uint32_t* varId) {
80   const SpvOp op = ip->opcode();
81   assert(op == SpvOpStore || op == SpvOpLoad);
82   *varId = ip->GetSingleWordInOperand(
83       op == SpvOpStore ? kStorePtrIdInIdx : kLoadPtrIdInIdx);
84   ir::Instruction* ptrInst = def_use_mgr_->GetDef(*varId);
85   ir::Instruction* varInst = ptrInst;
86   while (varInst->opcode() != SpvOpVariable) {
87     if (IsNonPtrAccessChain(varInst->opcode())) {
88       *varId = varInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx);
89     }
90     else {
91       assert(varInst->opcode() == SpvOpCopyObject);
92       *varId = varInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
93     }
94     varInst = def_use_mgr_->GetDef(*varId);
95   }
96   return ptrInst;
97 }
98 
IsTargetVar(uint32_t varId)99 bool LocalMultiStoreElimPass::IsTargetVar(uint32_t varId) {
100   if (seen_non_target_vars_.find(varId) != seen_non_target_vars_.end())
101     return false;
102   if (seen_target_vars_.find(varId) != seen_target_vars_.end())
103     return true;
104   const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
105   assert(varInst->opcode() == SpvOpVariable);
106   const uint32_t varTypeId = varInst->type_id();
107   const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
108   if (varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) !=
109       SpvStorageClassFunction) {
110     seen_non_target_vars_.insert(varId);
111     return false;
112   }
113   const uint32_t varPteTypeId =
114       varTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
115   ir::Instruction* varPteTypeInst = def_use_mgr_->GetDef(varPteTypeId);
116   if (!IsTargetType(varPteTypeInst)) {
117     seen_non_target_vars_.insert(varId);
118     return false;
119   }
120   seen_target_vars_.insert(varId);
121   return true;
122 }
123 
HasLoads(uint32_t ptrId) const124 bool LocalMultiStoreElimPass::HasLoads(uint32_t ptrId) const {
125   analysis::UseList* uses = def_use_mgr_->GetUses(ptrId);
126   if (uses == nullptr)
127     return false;
128   for (auto u : *uses) {
129     const SpvOp op = u.inst->opcode();
130     if (IsNonPtrAccessChain(op) || op == SpvOpCopyObject) {
131       if (HasLoads(u.inst->result_id()))
132         return true;
133     }
134     else {
135       // Conservatively assume that any non-store use is a load
136       // TODO(greg-lunarg): Improve analysis around function calls, etc
137       if (op != SpvOpStore && op != SpvOpName && !IsDecorate(op))
138         return true;
139     }
140   }
141   return false;
142 }
143 
IsLiveVar(uint32_t varId) const144 bool LocalMultiStoreElimPass::IsLiveVar(uint32_t varId) const {
145   // non-function scope vars are live
146   const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
147   assert(varInst->opcode() == SpvOpVariable);
148   const uint32_t varTypeId = varInst->type_id();
149   const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
150   if (varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) !=
151       SpvStorageClassFunction)
152     return true;
153   // test if variable is loaded from
154   return HasLoads(varId);
155 }
156 
AddStores(uint32_t ptr_id,std::queue<ir::Instruction * > * insts)157 void LocalMultiStoreElimPass::AddStores(
158     uint32_t ptr_id, std::queue<ir::Instruction*>* insts) {
159   analysis::UseList* uses = def_use_mgr_->GetUses(ptr_id);
160   if (uses != nullptr) {
161     for (auto u : *uses) {
162       if (IsNonPtrAccessChain(u.inst->opcode()))
163         AddStores(u.inst->result_id(), insts);
164       else if (u.inst->opcode() == SpvOpStore)
165         insts->push(u.inst);
166     }
167   }
168 }
169 
HasOnlyNamesAndDecorates(uint32_t id) const170 bool LocalMultiStoreElimPass::HasOnlyNamesAndDecorates(uint32_t id) const {
171   analysis::UseList* uses = def_use_mgr_->GetUses(id);
172   if (uses == nullptr)
173     return true;
174   for (auto u : *uses) {
175     const SpvOp op = u.inst->opcode();
176     if (op != SpvOpName && !IsDecorate(op))
177       return false;
178   }
179   return true;
180 }
181 
KillNamesAndDecorates(uint32_t id)182 void LocalMultiStoreElimPass::KillNamesAndDecorates(uint32_t id) {
183   // TODO(greg-lunarg): Remove id from any OpGroupDecorate and
184   // kill if no other operands.
185   analysis::UseList* uses = def_use_mgr_->GetUses(id);
186   if (uses == nullptr)
187     return;
188   std::list<ir::Instruction*> killList;
189   for (auto u : *uses) {
190     const SpvOp op = u.inst->opcode();
191     if (op != SpvOpName && !IsDecorate(op))
192       continue;
193     killList.push_back(u.inst);
194   }
195   for (auto kip : killList)
196     def_use_mgr_->KillInst(kip);
197 }
198 
KillNamesAndDecorates(ir::Instruction * inst)199 void LocalMultiStoreElimPass::KillNamesAndDecorates(ir::Instruction* inst) {
200   // TODO(greg-lunarg): Remove inst from any OpGroupDecorate and
201   // kill if not other operands.
202   const uint32_t rId = inst->result_id();
203   if (rId == 0)
204     return;
205   KillNamesAndDecorates(rId);
206 }
207 
DCEInst(ir::Instruction * inst)208 void LocalMultiStoreElimPass::DCEInst(ir::Instruction* inst) {
209   std::queue<ir::Instruction*> deadInsts;
210   deadInsts.push(inst);
211   while (!deadInsts.empty()) {
212     ir::Instruction* di = deadInsts.front();
213     // Don't delete labels
214     if (di->opcode() == SpvOpLabel) {
215       deadInsts.pop();
216       continue;
217     }
218     // Remember operands
219     std::vector<uint32_t> ids;
220     di->ForEachInId([&ids](uint32_t* iid) {
221       ids.push_back(*iid);
222     });
223     uint32_t varId = 0;
224     // Remember variable if dead load
225     if (di->opcode() == SpvOpLoad)
226       (void) GetPtr(di, &varId);
227     KillNamesAndDecorates(di);
228     def_use_mgr_->KillInst(di);
229     // For all operands with no remaining uses, add their instruction
230     // to the dead instruction queue.
231     for (auto id : ids)
232       if (HasOnlyNamesAndDecorates(id))
233         deadInsts.push(def_use_mgr_->GetDef(id));
234     // if a load was deleted and it was the variable's
235     // last load, add all its stores to dead queue
236     if (varId != 0 && !IsLiveVar(varId))
237       AddStores(varId, &deadInsts);
238     deadInsts.pop();
239   }
240 }
241 
HasOnlySupportedRefs(uint32_t varId)242 bool LocalMultiStoreElimPass::HasOnlySupportedRefs(uint32_t varId) {
243   if (supported_ref_vars_.find(varId) != supported_ref_vars_.end())
244     return true;
245   analysis::UseList* uses = def_use_mgr_->GetUses(varId);
246   if (uses == nullptr)
247     return true;
248   for (auto u : *uses) {
249     const SpvOp op = u.inst->opcode();
250     if (op != SpvOpStore && op != SpvOpLoad && op != SpvOpName &&
251         !IsDecorate(op))
252       return false;
253   }
254   supported_ref_vars_.insert(varId);
255   return true;
256 }
257 
InitSSARewrite(ir::Function & func)258 void LocalMultiStoreElimPass::InitSSARewrite(ir::Function& func) {
259   // Init predecessors
260   label2preds_.clear();
261   for (auto& blk : func) {
262     uint32_t blkId = blk.id();
263     blk.ForEachSuccessorLabel([&blkId, this](uint32_t sbid) {
264       label2preds_[sbid].push_back(blkId);
265     });
266   }
267   // Collect target (and non-) variable sets. Remove variables with
268   // non-load/store refs from target variable set
269   for (auto& blk : func) {
270     for (auto& inst : blk) {
271       switch (inst.opcode()) {
272         case SpvOpStore:
273         case SpvOpLoad: {
274           uint32_t varId;
275           (void) GetPtr(&inst, &varId);
276           if (!IsTargetVar(varId))
277             break;
278           if (HasOnlySupportedRefs(varId))
279             break;
280           seen_non_target_vars_.insert(varId);
281           seen_target_vars_.erase(varId);
282         } break;
283         default:
284           break;
285       }
286     }
287   }
288 }
289 
MergeBlockIdIfAny(const ir::BasicBlock & blk,uint32_t * cbid)290 uint32_t LocalMultiStoreElimPass::MergeBlockIdIfAny(const ir::BasicBlock& blk,
291     uint32_t* cbid) {
292   auto merge_ii = blk.cend();
293   --merge_ii;
294   *cbid = 0;
295   uint32_t mbid = 0;
296   if (merge_ii != blk.cbegin()) {
297     --merge_ii;
298     if (merge_ii->opcode() == SpvOpLoopMerge) {
299       mbid = merge_ii->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx);
300       *cbid = merge_ii->GetSingleWordInOperand(kLoopMergeContinueBlockIdInIdx);
301     }
302     else if (merge_ii->opcode() == SpvOpSelectionMerge) {
303       mbid = merge_ii->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
304     }
305   }
306   return mbid;
307 }
308 
ComputeStructuredSuccessors(ir::Function * func)309 void LocalMultiStoreElimPass::ComputeStructuredSuccessors(ir::Function* func) {
310   for (auto& blk : *func) {
311     // If no predecessors in function, make successor to pseudo entry
312     if (label2preds_[blk.id()].size() == 0)
313       block2structured_succs_[&pseudo_entry_block_].push_back(&blk);
314     // If header, make merge block first successor.
315     uint32_t cbid;
316     const uint32_t mbid = MergeBlockIdIfAny(blk, &cbid);
317     if (mbid != 0) {
318       block2structured_succs_[&blk].push_back(id2block_[mbid]);
319       if (cbid != 0)
320         block2structured_succs_[&blk].push_back(id2block_[cbid]);
321     }
322     // add true successors
323     blk.ForEachSuccessorLabel([&blk, this](uint32_t sbid) {
324       block2structured_succs_[&blk].push_back(id2block_[sbid]);
325     });
326   }
327 }
328 
ComputeStructuredOrder(ir::Function * func,std::list<ir::BasicBlock * > * order)329 void LocalMultiStoreElimPass::ComputeStructuredOrder(
330     ir::Function* func, std::list<ir::BasicBlock*>* order) {
331   // Compute structured successors and do DFS
332   ComputeStructuredSuccessors(func);
333   auto ignore_block = [](cbb_ptr) {};
334   auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
335   auto get_structured_successors = [this](const ir::BasicBlock* block) {
336       return &(block2structured_succs_[block]); };
337   // TODO(greg-lunarg): Get rid of const_cast by making moving const
338   // out of the cfa.h prototypes and into the invoking code.
339   auto post_order = [&](cbb_ptr b) {
340       order->push_front(const_cast<ir::BasicBlock*>(b)); };
341 
342   spvtools::CFA<ir::BasicBlock>::DepthFirstTraversal(
343       &pseudo_entry_block_, get_structured_successors, ignore_block,
344       post_order, ignore_edge);
345 }
346 
SSABlockInitSinglePred(ir::BasicBlock * block_ptr)347 void LocalMultiStoreElimPass::SSABlockInitSinglePred(ir::BasicBlock* block_ptr) {
348   // Copy map entry from single predecessor
349   const uint32_t label = block_ptr->id();
350   const uint32_t predLabel = label2preds_[label].front();
351   assert(visitedBlocks_.find(predLabel) != visitedBlocks_.end());
352   label2ssa_map_[label] = label2ssa_map_[predLabel];
353 }
354 
IsLiveAfter(uint32_t var_id,uint32_t label) const355 bool LocalMultiStoreElimPass::IsLiveAfter(uint32_t var_id, uint32_t label) const {
356   // For now, return very conservative result: true. This will result in
357   // correct, but possibly usused, phi code to be generated. A subsequent
358   // DCE pass should eliminate this code.
359   // TODO(greg-lunarg): Return more accurate information
360   (void) var_id;
361   (void) label;
362   return true;
363 }
364 
Type2Undef(uint32_t type_id)365 uint32_t LocalMultiStoreElimPass::Type2Undef(uint32_t type_id) {
366   const auto uitr = type2undefs_.find(type_id);
367   if (uitr != type2undefs_.end())
368     return uitr->second;
369   const uint32_t undefId = TakeNextId();
370   std::unique_ptr<ir::Instruction> undef_inst(
371     new ir::Instruction(SpvOpUndef, type_id, undefId, {}));
372   def_use_mgr_->AnalyzeInstDefUse(&*undef_inst);
373   module_->AddGlobalValue(std::move(undef_inst));
374   type2undefs_[type_id] = undefId;
375   return undefId;
376 }
377 
GetPointeeTypeId(const ir::Instruction * ptrInst) const378 uint32_t LocalMultiStoreElimPass::GetPointeeTypeId(
379     const ir::Instruction* ptrInst) const {
380   const uint32_t ptrTypeId = ptrInst->type_id();
381   const ir::Instruction* ptrTypeInst = def_use_mgr_->GetDef(ptrTypeId);
382   return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
383 }
384 
SSABlockInitLoopHeader(std::list<ir::BasicBlock * >::iterator block_itr)385 void LocalMultiStoreElimPass::SSABlockInitLoopHeader(
386     std::list<ir::BasicBlock*>::iterator block_itr) {
387   const uint32_t label = (*block_itr)->id();
388   // Determine backedge label.
389   uint32_t backLabel = 0;
390   for (uint32_t predLabel : label2preds_[label])
391     if (visitedBlocks_.find(predLabel) == visitedBlocks_.end()) {
392       assert(backLabel == 0);
393       backLabel = predLabel;
394       break;
395     }
396   assert(backLabel != 0);
397   // Determine merge block.
398   auto mergeInst = (*block_itr)->end();
399   --mergeInst;
400   --mergeInst;
401   uint32_t mergeLabel = mergeInst->GetSingleWordInOperand(
402       kLoopMergeMergeBlockIdInIdx);
403   // Collect all live variables and a default value for each across all
404   // non-backedge predecesors. Must be ordered map because phis are
405   // generated based on order and test results will otherwise vary across
406   // platforms.
407   std::map<uint32_t, uint32_t> liveVars;
408   for (uint32_t predLabel : label2preds_[label]) {
409     for (auto var_val : label2ssa_map_[predLabel]) {
410       uint32_t varId = var_val.first;
411       liveVars[varId] = var_val.second;
412     }
413   }
414   // Add all stored variables in loop. Set their default value id to zero.
415   for (auto bi = block_itr; (*bi)->id() != mergeLabel; ++bi) {
416     ir::BasicBlock* bp = *bi;
417     for (auto ii = bp->begin(); ii != bp->end(); ++ii) {
418       if (ii->opcode() != SpvOpStore)
419         continue;
420       uint32_t varId;
421       (void) GetPtr(&*ii, &varId);
422       if (!IsTargetVar(varId))
423         continue;
424       liveVars[varId] = 0;
425     }
426   }
427   // Insert phi for all live variables that require them. All variables
428   // defined in loop require a phi. Otherwise all variables
429   // with differing predecessor values require a phi.
430   auto insertItr = (*block_itr)->begin();
431   for (auto var_val : liveVars) {
432     const uint32_t varId = var_val.first;
433     if (!IsLiveAfter(varId, label))
434       continue;
435     const uint32_t val0Id = var_val.second;
436     bool needsPhi = false;
437     if (val0Id != 0) {
438       for (uint32_t predLabel : label2preds_[label]) {
439         // Skip back edge predecessor.
440         if (predLabel == backLabel)
441           continue;
442         const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
443         // Missing (undef) values always cause difference with (defined) value
444         if (var_val_itr == label2ssa_map_[predLabel].end()) {
445           needsPhi = true;
446           break;
447         }
448         if (var_val_itr->second != val0Id) {
449           needsPhi = true;
450           break;
451         }
452       }
453     }
454     else {
455       needsPhi = true;
456     }
457     // If val is the same for all predecessors, enter it in map
458     if (!needsPhi) {
459       label2ssa_map_[label].insert(var_val);
460       continue;
461     }
462     // Val differs across predecessors. Add phi op to block and
463     // add its result id to the map. For back edge predecessor,
464     // use the variable id. We will patch this after visiting back
465     // edge predecessor. For predecessors that do not define a value,
466     // use undef.
467     std::vector<ir::Operand> phi_in_operands;
468     uint32_t typeId = GetPointeeTypeId(def_use_mgr_->GetDef(varId));
469     for (uint32_t predLabel : label2preds_[label]) {
470       uint32_t valId;
471       if (predLabel == backLabel) {
472         valId = varId;
473       }
474       else {
475         const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
476         if (var_val_itr == label2ssa_map_[predLabel].end())
477           valId = Type2Undef(typeId);
478         else
479           valId = var_val_itr->second;
480       }
481       phi_in_operands.push_back(
482           {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}});
483       phi_in_operands.push_back(
484           {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {predLabel}});
485     }
486     const uint32_t phiId = TakeNextId();
487     std::unique_ptr<ir::Instruction> newPhi(
488       new ir::Instruction(SpvOpPhi, typeId, phiId, phi_in_operands));
489     // Only analyze the phi define now; analyze the phi uses after the
490     // phi backedge predecessor value is patched.
491     def_use_mgr_->AnalyzeInstDef(&*newPhi);
492     insertItr = insertItr.InsertBefore(std::move(newPhi));
493     ++insertItr;
494     label2ssa_map_[label].insert({ varId, phiId });
495   }
496 }
497 
SSABlockInitMultiPred(ir::BasicBlock * block_ptr)498 void LocalMultiStoreElimPass::SSABlockInitMultiPred(ir::BasicBlock* block_ptr) {
499   const uint32_t label = block_ptr->id();
500   // Collect all live variables and a default value for each across all
501   // predecesors. Must be ordered map because phis are generated based on
502   // order and test results will otherwise vary across platforms.
503   std::map<uint32_t, uint32_t> liveVars;
504   for (uint32_t predLabel : label2preds_[label]) {
505     assert(visitedBlocks_.find(predLabel) != visitedBlocks_.end());
506     for (auto var_val : label2ssa_map_[predLabel]) {
507       const uint32_t varId = var_val.first;
508       liveVars[varId] = var_val.second;
509     }
510   }
511   // For each live variable, look for a difference in values across
512   // predecessors that would require a phi and insert one.
513   auto insertItr = block_ptr->begin();
514   for (auto var_val : liveVars) {
515     const uint32_t varId = var_val.first;
516     if (!IsLiveAfter(varId, label))
517       continue;
518     const uint32_t val0Id = var_val.second;
519     bool differs = false;
520     for (uint32_t predLabel : label2preds_[label]) {
521       const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
522       // Missing values cause a difference because we'll need to create an
523       // undef for that predecessor.
524       if (var_val_itr == label2ssa_map_[predLabel].end()) {
525         differs = true;
526         break;
527       }
528       if (var_val_itr->second != val0Id) {
529         differs = true;
530         break;
531       }
532     }
533     // If val is the same for all predecessors, enter it in map
534     if (!differs) {
535       label2ssa_map_[label].insert(var_val);
536       continue;
537     }
538     // Val differs across predecessors. Add phi op to block and
539     // add its result id to the map
540     std::vector<ir::Operand> phi_in_operands;
541     const uint32_t typeId = GetPointeeTypeId(def_use_mgr_->GetDef(varId));
542     for (uint32_t predLabel : label2preds_[label]) {
543       const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
544       // If variable not defined on this path, use undef
545       const uint32_t valId = (var_val_itr != label2ssa_map_[predLabel].end()) ?
546           var_val_itr->second : Type2Undef(typeId);
547       phi_in_operands.push_back(
548           {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}});
549       phi_in_operands.push_back(
550           {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {predLabel}});
551     }
552     const uint32_t phiId = TakeNextId();
553     std::unique_ptr<ir::Instruction> newPhi(
554       new ir::Instruction(SpvOpPhi, typeId, phiId, phi_in_operands));
555     def_use_mgr_->AnalyzeInstDefUse(&*newPhi);
556     insertItr = insertItr.InsertBefore(std::move(newPhi));
557     ++insertItr;
558     label2ssa_map_[label].insert({varId, phiId});
559   }
560 }
561 
IsLoopHeader(ir::BasicBlock * block_ptr) const562 bool LocalMultiStoreElimPass::IsLoopHeader(ir::BasicBlock* block_ptr) const {
563   auto iItr = block_ptr->end();
564   --iItr;
565   if (iItr == block_ptr->begin())
566     return false;
567   --iItr;
568   return iItr->opcode() == SpvOpLoopMerge;
569 }
570 
SSABlockInit(std::list<ir::BasicBlock * >::iterator block_itr)571 void LocalMultiStoreElimPass::SSABlockInit(
572     std::list<ir::BasicBlock*>::iterator block_itr) {
573   const size_t numPreds = label2preds_[(*block_itr)->id()].size();
574   if (numPreds == 0)
575     return;
576   if (numPreds == 1)
577     SSABlockInitSinglePred(*block_itr);
578   else if (IsLoopHeader(*block_itr))
579     SSABlockInitLoopHeader(block_itr);
580   else
581     SSABlockInitMultiPred(*block_itr);
582 }
583 
PatchPhis(uint32_t header_id,uint32_t back_id)584 void LocalMultiStoreElimPass::PatchPhis(uint32_t header_id, uint32_t back_id) {
585   ir::BasicBlock* header = id2block_[header_id];
586   auto phiItr = header->begin();
587   for (; phiItr->opcode() == SpvOpPhi; ++phiItr) {
588     uint32_t cnt = 0;
589     uint32_t idx;
590     phiItr->ForEachInId([&cnt,&back_id,&idx](uint32_t* iid) {
591       if (cnt % 2 == 1 && *iid == back_id) idx = cnt - 1;
592       ++cnt;
593     });
594     // Use undef if variable not in backedge predecessor map
595     const uint32_t varId = phiItr->GetSingleWordInOperand(idx);
596     const auto valItr = label2ssa_map_[back_id].find(varId);
597     uint32_t valId = (valItr != label2ssa_map_[back_id].end()) ?
598       valItr->second :
599       Type2Undef(GetPointeeTypeId(def_use_mgr_->GetDef(varId)));
600     phiItr->SetInOperand(idx, { valId });
601     // Analyze uses now that they are complete
602     def_use_mgr_->AnalyzeInstUse(&*phiItr);
603   }
604 }
605 
EliminateMultiStoreLocal(ir::Function * func)606 bool LocalMultiStoreElimPass::EliminateMultiStoreLocal(ir::Function* func) {
607   InitSSARewrite(*func);
608   // Process all blocks in structured order. This is just one way (the
609   // simplest?) to make sure all predecessors blocks are processed before
610   // a block itself.
611   std::list<ir::BasicBlock*> structuredOrder;
612   ComputeStructuredOrder(func, &structuredOrder);
613   bool modified = false;
614   for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
615     // Skip pseudo entry block
616     if (*bi == &pseudo_entry_block_)
617       continue;
618     // Initialize this block's label2ssa_map_ entry using predecessor maps.
619     // Then process all stores and loads of targeted variables.
620     SSABlockInit(bi);
621     ir::BasicBlock* bp = *bi;
622     const uint32_t label = bp->id();
623     for (auto ii = bp->begin(); ii != bp->end(); ++ii) {
624       switch (ii->opcode()) {
625         case SpvOpStore: {
626           uint32_t varId;
627           (void) GetPtr(&*ii, &varId);
628           if (!IsTargetVar(varId))
629             break;
630           // Register new stored value for the variable
631           label2ssa_map_[label][varId] =
632               ii->GetSingleWordInOperand(kStoreValIdInIdx);
633         } break;
634         case SpvOpLoad: {
635           uint32_t varId;
636           (void) GetPtr(&*ii, &varId);
637           if (!IsTargetVar(varId))
638             break;
639           uint32_t replId = 0;
640           const auto ssaItr = label2ssa_map_.find(label);
641           if (ssaItr != label2ssa_map_.end()) {
642             const auto valItr = ssaItr->second.find(varId);
643             if (valItr != ssaItr->second.end())
644               replId = valItr->second;
645           }
646           // If variable is not defined, use undef
647           if (replId == 0) {
648             replId = Type2Undef(GetPointeeTypeId(def_use_mgr_->GetDef(varId)));
649           }
650           // Replace load's id with the last stored value id for variable
651           // and delete load. Kill any names or decorates using id before
652           // replacing to prevent incorrect replacement in those instructions.
653           const uint32_t loadId = ii->result_id();
654           KillNamesAndDecorates(loadId);
655           (void)def_use_mgr_->ReplaceAllUsesWith(loadId, replId);
656           def_use_mgr_->KillInst(&*ii);
657           modified = true;
658         } break;
659         default: {
660         } break;
661       }
662     }
663     visitedBlocks_.insert(label);
664     // Look for successor backedge and patch phis in loop header
665     // if found.
666     uint32_t header = 0;
667     bp->ForEachSuccessorLabel([&header,this](uint32_t succ) {
668       if (visitedBlocks_.find(succ) == visitedBlocks_.end()) return;
669       assert(header == 0);
670       header = succ;
671     });
672     if (header != 0)
673       PatchPhis(header, label);
674   }
675   // Remove all target variable stores.
676   for (auto bi = func->begin(); bi != func->end(); ++bi) {
677     for (auto ii = bi->begin(); ii != bi->end(); ++ii) {
678       if (ii->opcode() != SpvOpStore)
679         continue;
680       uint32_t varId;
681       (void) GetPtr(&*ii, &varId);
682       if (!IsTargetVar(varId))
683         continue;
684       assert(!HasLoads(varId));
685       DCEInst(&*ii);
686       modified = true;
687     }
688   }
689   return modified;
690 }
691 
Initialize(ir::Module * module)692 void LocalMultiStoreElimPass::Initialize(ir::Module* module) {
693 
694   module_ = module;
695 
696   // TODO(greg-lunarg): Reuse def/use from previous passes
697   def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
698 
699   // Initialize function and block maps
700   id2function_.clear();
701   id2block_.clear();
702   block2structured_succs_.clear();
703   for (auto& fn : *module_) {
704     id2function_[fn.result_id()] = &fn;
705     for (auto& blk : fn)
706       id2block_[blk.id()] = &blk;
707   }
708 
709   // Clear collections
710   seen_target_vars_.clear();
711   seen_non_target_vars_.clear();
712   visitedBlocks_.clear();
713   type2undefs_.clear();
714   supported_ref_vars_.clear();
715   block2structured_succs_.clear();
716   label2preds_.clear();
717   label2ssa_map_.clear();
718 
719   // Start new ids with next availablein module
720   next_id_ = module_->id_bound();
721 };
722 
AllExtensionsSupported() const723 bool LocalMultiStoreElimPass::AllExtensionsSupported() const {
724   // Currently disallows all extensions. This is just super conservative
725   // to allow this to go public and many can likely be allowed with little
726   // to no additional coding. One exception is SPV_KHR_variable_pointers
727   // which will require some additional work around HasLoads, AddStores
728   // and generally DCEInst.
729   // TODO(greg-lunarg): Enable more extensions.
730   for (auto& ei : module_->extensions()) {
731     (void) ei;
732     return false;
733   }
734   return true;
735 }
736 
ProcessImpl()737 Pass::Status LocalMultiStoreElimPass::ProcessImpl() {
738   // Assumes all control flow structured.
739   // TODO(greg-lunarg): Do SSA rewrite for non-structured control flow
740   if (!module_->HasCapability(SpvCapabilityShader))
741     return Status::SuccessWithoutChange;
742   // Assumes logical addressing only
743   // TODO(greg-lunarg): Add support for physical addressing
744   if (module_->HasCapability(SpvCapabilityAddresses))
745     return Status::SuccessWithoutChange;
746   // Do not process if module contains OpGroupDecorate. Additional
747   // support required in KillNamesAndDecorates().
748   // TODO(greg-lunarg): Add support for OpGroupDecorate
749   for (auto& ai : module_->annotations())
750     if (ai.opcode() == SpvOpGroupDecorate)
751       return Status::SuccessWithoutChange;
752   // Do not process if any disallowed extensions are enabled
753   if (!AllExtensionsSupported())
754       return Status::SuccessWithoutChange;
755   // Process functions
756   bool modified = false;
757   for (auto& e : module_->entry_points()) {
758     ir::Function* fn =
759         id2function_[e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)];
760     modified = EliminateMultiStoreLocal(fn) || modified;
761   }
762   FinalizeNextId(module_);
763   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
764 }
765 
LocalMultiStoreElimPass()766 LocalMultiStoreElimPass::LocalMultiStoreElimPass()
767     : module_(nullptr), def_use_mgr_(nullptr),
768       pseudo_entry_block_(std::unique_ptr<ir::Instruction>(
769           new ir::Instruction(SpvOpLabel, 0, 0, {}))),
770       next_id_(0) {}
771 
Process(ir::Module * module)772 Pass::Status LocalMultiStoreElimPass::Process(ir::Module* module) {
773   Initialize(module);
774   return ProcessImpl();
775 }
776 
777 }  // namespace opt
778 }  // namespace spvtools
779 
780