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