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