| 1 | // Copyright (c) 2018 Google LLC. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | #include "source/opt/vector_dce.h" |
| 16 | |
| 17 | #include <utility> |
| 18 | |
| 19 | namespace spvtools { |
| 20 | namespace opt { |
| 21 | namespace { |
| 22 | |
| 23 | const uint32_t = 0; |
| 24 | const uint32_t kInsertObjectIdInIdx = 0; |
| 25 | const uint32_t kInsertCompositeIdInIdx = 1; |
| 26 | |
| 27 | } // namespace |
| 28 | |
| 29 | Pass::Status VectorDCE::Process() { |
| 30 | bool modified = false; |
| 31 | for (Function& function : *get_module()) { |
| 32 | modified |= VectorDCEFunction(&function); |
| 33 | } |
| 34 | return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); |
| 35 | } |
| 36 | |
| 37 | bool VectorDCE::VectorDCEFunction(Function* function) { |
| 38 | LiveComponentMap live_components; |
| 39 | FindLiveComponents(function, &live_components); |
| 40 | return RewriteInstructions(function, live_components); |
| 41 | } |
| 42 | |
| 43 | void VectorDCE::FindLiveComponents(Function* function, |
| 44 | LiveComponentMap* live_components) { |
| 45 | std::vector<WorkListItem> work_list; |
| 46 | |
| 47 | // Prime the work list. We will assume that any instruction that does |
| 48 | // not result in a vector is live. |
| 49 | // |
| 50 | // Extending to structures and matrices is not as straight forward because of |
| 51 | // the nesting. We cannot simply us a bit vector to keep track of which |
| 52 | // components are live because of arbitrary nesting of structs. |
| 53 | function->ForEachInst( |
| 54 | [&work_list, this, live_components](Instruction* current_inst) { |
| 55 | if (!HasVectorOrScalarResult(current_inst) || |
| 56 | !context()->IsCombinatorInstruction(current_inst)) { |
| 57 | MarkUsesAsLive(current_inst, all_components_live_, live_components, |
| 58 | &work_list); |
| 59 | } |
| 60 | }); |
| 61 | |
| 62 | // Process the work list propagating liveness. |
| 63 | for (uint32_t i = 0; i < work_list.size(); i++) { |
| 64 | WorkListItem current_item = work_list[i]; |
| 65 | Instruction* current_inst = current_item.instruction; |
| 66 | |
| 67 | switch (current_inst->opcode()) { |
| 68 | case SpvOpCompositeExtract: |
| 69 | MarkExtractUseAsLive(current_inst, current_item.components, |
| 70 | live_components, &work_list); |
| 71 | break; |
| 72 | case SpvOpCompositeInsert: |
| 73 | MarkInsertUsesAsLive(current_item, live_components, &work_list); |
| 74 | break; |
| 75 | case SpvOpVectorShuffle: |
| 76 | MarkVectorShuffleUsesAsLive(current_item, live_components, &work_list); |
| 77 | break; |
| 78 | case SpvOpCompositeConstruct: |
| 79 | MarkCompositeContructUsesAsLive(current_item, live_components, |
| 80 | &work_list); |
| 81 | break; |
| 82 | default: |
| 83 | if (current_inst->IsScalarizable()) { |
| 84 | MarkUsesAsLive(current_inst, current_item.components, live_components, |
| 85 | &work_list); |
| 86 | } else { |
| 87 | MarkUsesAsLive(current_inst, all_components_live_, live_components, |
| 88 | &work_list); |
| 89 | } |
| 90 | break; |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | void VectorDCE::(const Instruction* current_inst, |
| 96 | const utils::BitVector& live_elements, |
| 97 | LiveComponentMap* live_components, |
| 98 | std::vector<WorkListItem>* work_list) { |
| 99 | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
| 100 | uint32_t operand_id = |
| 101 | current_inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); |
| 102 | Instruction* operand_inst = def_use_mgr->GetDef(operand_id); |
| 103 | |
| 104 | if (HasVectorOrScalarResult(operand_inst)) { |
| 105 | WorkListItem new_item; |
| 106 | new_item.instruction = operand_inst; |
| 107 | if (current_inst->NumInOperands() < 2) { |
| 108 | new_item.components = live_elements; |
| 109 | } else { |
| 110 | new_item.components.Set(current_inst->GetSingleWordInOperand(1)); |
| 111 | } |
| 112 | AddItemToWorkListIfNeeded(new_item, live_components, work_list); |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | void VectorDCE::MarkInsertUsesAsLive( |
| 117 | const VectorDCE::WorkListItem& current_item, |
| 118 | LiveComponentMap* live_components, |
| 119 | std::vector<VectorDCE::WorkListItem>* work_list) { |
| 120 | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
| 121 | |
| 122 | if (current_item.instruction->NumInOperands() > 2) { |
| 123 | uint32_t insert_position = |
| 124 | current_item.instruction->GetSingleWordInOperand(2); |
| 125 | |
| 126 | // Add the elements of the composite object that are used. |
| 127 | uint32_t operand_id = current_item.instruction->GetSingleWordInOperand( |
| 128 | kInsertCompositeIdInIdx); |
| 129 | Instruction* operand_inst = def_use_mgr->GetDef(operand_id); |
| 130 | |
| 131 | WorkListItem new_item; |
| 132 | new_item.instruction = operand_inst; |
| 133 | new_item.components = current_item.components; |
| 134 | new_item.components.Clear(insert_position); |
| 135 | |
| 136 | AddItemToWorkListIfNeeded(new_item, live_components, work_list); |
| 137 | |
| 138 | // Add the element being inserted if it is used. |
| 139 | if (current_item.components.Get(insert_position)) { |
| 140 | uint32_t obj_operand_id = |
| 141 | current_item.instruction->GetSingleWordInOperand( |
| 142 | kInsertObjectIdInIdx); |
| 143 | Instruction* obj_operand_inst = def_use_mgr->GetDef(obj_operand_id); |
| 144 | WorkListItem new_item_for_obj; |
| 145 | new_item_for_obj.instruction = obj_operand_inst; |
| 146 | new_item_for_obj.components.Set(0); |
| 147 | AddItemToWorkListIfNeeded(new_item_for_obj, live_components, work_list); |
| 148 | } |
| 149 | } else { |
| 150 | // If there are no indices, then this is a copy of the object being |
| 151 | // inserted. |
| 152 | uint32_t object_id = |
| 153 | current_item.instruction->GetSingleWordInOperand(kInsertObjectIdInIdx); |
| 154 | Instruction* object_inst = def_use_mgr->GetDef(object_id); |
| 155 | |
| 156 | WorkListItem new_item; |
| 157 | new_item.instruction = object_inst; |
| 158 | new_item.components = current_item.components; |
| 159 | AddItemToWorkListIfNeeded(new_item, live_components, work_list); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | void VectorDCE::MarkVectorShuffleUsesAsLive( |
| 164 | const WorkListItem& current_item, |
| 165 | VectorDCE::LiveComponentMap* live_components, |
| 166 | std::vector<WorkListItem>* work_list) { |
| 167 | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
| 168 | |
| 169 | WorkListItem first_operand; |
| 170 | first_operand.instruction = |
| 171 | def_use_mgr->GetDef(current_item.instruction->GetSingleWordInOperand(0)); |
| 172 | WorkListItem second_operand; |
| 173 | second_operand.instruction = |
| 174 | def_use_mgr->GetDef(current_item.instruction->GetSingleWordInOperand(1)); |
| 175 | |
| 176 | analysis::TypeManager* type_mgr = context()->get_type_mgr(); |
| 177 | analysis::Vector* first_type = |
| 178 | type_mgr->GetType(first_operand.instruction->type_id())->AsVector(); |
| 179 | uint32_t size_of_first_operand = first_type->element_count(); |
| 180 | |
| 181 | for (uint32_t in_op = 2; in_op < current_item.instruction->NumInOperands(); |
| 182 | ++in_op) { |
| 183 | uint32_t index = current_item.instruction->GetSingleWordInOperand(in_op); |
| 184 | if (current_item.components.Get(in_op - 2)) { |
| 185 | if (index < size_of_first_operand) { |
| 186 | first_operand.components.Set(index); |
| 187 | } else { |
| 188 | second_operand.components.Set(index - size_of_first_operand); |
| 189 | } |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | AddItemToWorkListIfNeeded(first_operand, live_components, work_list); |
| 194 | AddItemToWorkListIfNeeded(second_operand, live_components, work_list); |
| 195 | } |
| 196 | |
| 197 | void VectorDCE::MarkCompositeContructUsesAsLive( |
| 198 | VectorDCE::WorkListItem work_item, |
| 199 | VectorDCE::LiveComponentMap* live_components, |
| 200 | std::vector<VectorDCE::WorkListItem>* work_list) { |
| 201 | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
| 202 | analysis::TypeManager* type_mgr = context()->get_type_mgr(); |
| 203 | |
| 204 | uint32_t current_component = 0; |
| 205 | Instruction* current_inst = work_item.instruction; |
| 206 | uint32_t num_in_operands = current_inst->NumInOperands(); |
| 207 | for (uint32_t i = 0; i < num_in_operands; ++i) { |
| 208 | uint32_t id = current_inst->GetSingleWordInOperand(i); |
| 209 | Instruction* op_inst = def_use_mgr->GetDef(id); |
| 210 | |
| 211 | if (HasScalarResult(op_inst)) { |
| 212 | WorkListItem new_work_item; |
| 213 | new_work_item.instruction = op_inst; |
| 214 | if (work_item.components.Get(current_component)) { |
| 215 | new_work_item.components.Set(0); |
| 216 | } |
| 217 | AddItemToWorkListIfNeeded(new_work_item, live_components, work_list); |
| 218 | current_component++; |
| 219 | } else { |
| 220 | assert(HasVectorResult(op_inst)); |
| 221 | WorkListItem new_work_item; |
| 222 | new_work_item.instruction = op_inst; |
| 223 | uint32_t op_vector_size = |
| 224 | type_mgr->GetType(op_inst->type_id())->AsVector()->element_count(); |
| 225 | |
| 226 | for (uint32_t op_vector_idx = 0; op_vector_idx < op_vector_size; |
| 227 | op_vector_idx++, current_component++) { |
| 228 | if (work_item.components.Get(current_component)) { |
| 229 | new_work_item.components.Set(op_vector_idx); |
| 230 | } |
| 231 | } |
| 232 | AddItemToWorkListIfNeeded(new_work_item, live_components, work_list); |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | void VectorDCE::MarkUsesAsLive( |
| 238 | Instruction* current_inst, const utils::BitVector& live_elements, |
| 239 | LiveComponentMap* live_components, |
| 240 | std::vector<VectorDCE::WorkListItem>* work_list) { |
| 241 | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
| 242 | |
| 243 | current_inst->ForEachInId([&work_list, &live_elements, this, live_components, |
| 244 | def_use_mgr](uint32_t* operand_id) { |
| 245 | Instruction* operand_inst = def_use_mgr->GetDef(*operand_id); |
| 246 | |
| 247 | if (HasVectorResult(operand_inst)) { |
| 248 | WorkListItem new_item; |
| 249 | new_item.instruction = operand_inst; |
| 250 | new_item.components = live_elements; |
| 251 | AddItemToWorkListIfNeeded(new_item, live_components, work_list); |
| 252 | } else if (HasScalarResult(operand_inst)) { |
| 253 | WorkListItem new_item; |
| 254 | new_item.instruction = operand_inst; |
| 255 | new_item.components.Set(0); |
| 256 | AddItemToWorkListIfNeeded(new_item, live_components, work_list); |
| 257 | } |
| 258 | }); |
| 259 | } |
| 260 | |
| 261 | bool VectorDCE::HasVectorOrScalarResult(const Instruction* inst) const { |
| 262 | return HasScalarResult(inst) || HasVectorResult(inst); |
| 263 | } |
| 264 | |
| 265 | bool VectorDCE::HasVectorResult(const Instruction* inst) const { |
| 266 | analysis::TypeManager* type_mgr = context()->get_type_mgr(); |
| 267 | if (inst->type_id() == 0) { |
| 268 | return false; |
| 269 | } |
| 270 | |
| 271 | const analysis::Type* current_type = type_mgr->GetType(inst->type_id()); |
| 272 | switch (current_type->kind()) { |
| 273 | case analysis::Type::kVector: |
| 274 | return true; |
| 275 | default: |
| 276 | return false; |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | bool VectorDCE::HasScalarResult(const Instruction* inst) const { |
| 281 | analysis::TypeManager* type_mgr = context()->get_type_mgr(); |
| 282 | if (inst->type_id() == 0) { |
| 283 | return false; |
| 284 | } |
| 285 | |
| 286 | const analysis::Type* current_type = type_mgr->GetType(inst->type_id()); |
| 287 | switch (current_type->kind()) { |
| 288 | case analysis::Type::kBool: |
| 289 | case analysis::Type::kInteger: |
| 290 | case analysis::Type::kFloat: |
| 291 | return true; |
| 292 | default: |
| 293 | return false; |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | bool VectorDCE::RewriteInstructions( |
| 298 | Function* function, const VectorDCE::LiveComponentMap& live_components) { |
| 299 | bool modified = false; |
| 300 | function->ForEachInst( |
| 301 | [&modified, this, live_components](Instruction* current_inst) { |
| 302 | if (!context()->IsCombinatorInstruction(current_inst)) { |
| 303 | return; |
| 304 | } |
| 305 | |
| 306 | auto live_component = live_components.find(current_inst->result_id()); |
| 307 | if (live_component == live_components.end()) { |
| 308 | // If this instruction is not in live_components then it does not |
| 309 | // produce a vector, or it is never referenced and ADCE will remove |
| 310 | // it. No point in trying to differentiate. |
| 311 | return; |
| 312 | } |
| 313 | |
| 314 | // If no element in the current instruction is used replace it with an |
| 315 | // OpUndef. |
| 316 | if (live_component->second.Empty()) { |
| 317 | modified = true; |
| 318 | uint32_t undef_id = this->Type2Undef(current_inst->type_id()); |
| 319 | context()->KillNamesAndDecorates(current_inst); |
| 320 | context()->ReplaceAllUsesWith(current_inst->result_id(), undef_id); |
| 321 | context()->KillInst(current_inst); |
| 322 | return; |
| 323 | } |
| 324 | |
| 325 | switch (current_inst->opcode()) { |
| 326 | case SpvOpCompositeInsert: |
| 327 | modified |= |
| 328 | RewriteInsertInstruction(current_inst, live_component->second); |
| 329 | break; |
| 330 | case SpvOpCompositeConstruct: |
| 331 | // TODO: The members that are not live can be replaced by an undef |
| 332 | // or constant. This will remove uses of those values, and possibly |
| 333 | // create opportunities for ADCE. |
| 334 | break; |
| 335 | default: |
| 336 | // Do nothing. |
| 337 | break; |
| 338 | } |
| 339 | }); |
| 340 | return modified; |
| 341 | } |
| 342 | |
| 343 | bool VectorDCE::RewriteInsertInstruction( |
| 344 | Instruction* current_inst, const utils::BitVector& live_components) { |
| 345 | // If the value being inserted is not live, then we can skip the insert. |
| 346 | |
| 347 | if (current_inst->NumInOperands() == 2) { |
| 348 | // If there are no indices, then this is the same as a copy. |
| 349 | context()->KillNamesAndDecorates(current_inst->result_id()); |
| 350 | uint32_t object_id = |
| 351 | current_inst->GetSingleWordInOperand(kInsertObjectIdInIdx); |
| 352 | context()->ReplaceAllUsesWith(current_inst->result_id(), object_id); |
| 353 | return true; |
| 354 | } |
| 355 | |
| 356 | uint32_t insert_index = current_inst->GetSingleWordInOperand(2); |
| 357 | if (!live_components.Get(insert_index)) { |
| 358 | context()->KillNamesAndDecorates(current_inst->result_id()); |
| 359 | uint32_t composite_id = |
| 360 | current_inst->GetSingleWordInOperand(kInsertCompositeIdInIdx); |
| 361 | context()->ReplaceAllUsesWith(current_inst->result_id(), composite_id); |
| 362 | return true; |
| 363 | } |
| 364 | |
| 365 | // If the values already in the composite are not used, then replace it with |
| 366 | // an undef. |
| 367 | utils::BitVector temp = live_components; |
| 368 | temp.Clear(insert_index); |
| 369 | if (temp.Empty()) { |
| 370 | context()->ForgetUses(current_inst); |
| 371 | uint32_t undef_id = Type2Undef(current_inst->type_id()); |
| 372 | current_inst->SetInOperand(kInsertCompositeIdInIdx, {undef_id}); |
| 373 | context()->AnalyzeUses(current_inst); |
| 374 | return true; |
| 375 | } |
| 376 | |
| 377 | return false; |
| 378 | } |
| 379 | |
| 380 | void VectorDCE::AddItemToWorkListIfNeeded( |
| 381 | WorkListItem work_item, VectorDCE::LiveComponentMap* live_components, |
| 382 | std::vector<WorkListItem>* work_list) { |
| 383 | Instruction* current_inst = work_item.instruction; |
| 384 | auto it = live_components->find(current_inst->result_id()); |
| 385 | if (it == live_components->end()) { |
| 386 | live_components->emplace( |
| 387 | std::make_pair(current_inst->result_id(), work_item.components)); |
| 388 | work_list->emplace_back(work_item); |
| 389 | } else { |
| 390 | if (it->second.Or(work_item.components)) { |
| 391 | work_list->emplace_back(work_item); |
| 392 | } |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | } // namespace opt |
| 397 | } // namespace spvtools |
| 398 | |