• 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_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