| // Copyright (c) 2018 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "source/opt/reduce_load_size.h" |
| |
| #include <set> |
| #include <vector> |
| |
| #include "source/opt/instruction.h" |
| #include "source/opt/ir_builder.h" |
| #include "source/opt/ir_context.h" |
| #include "source/util/bit_vector.h" |
| |
| namespace spvtools { |
| namespace opt { |
| namespace { |
| constexpr uint32_t kExtractCompositeIdInIdx = 0; |
| constexpr uint32_t kVariableStorageClassInIdx = 0; |
| constexpr uint32_t kLoadPointerInIdx = 0; |
| } // namespace |
| |
| Pass::Status ReduceLoadSize::Process() { |
| bool modified = false; |
| |
| for (auto& func : *get_module()) { |
| func.ForEachInst([&modified, this](Instruction* inst) { |
| if (inst->opcode() == spv::Op::OpCompositeExtract) { |
| if (ShouldReplaceExtract(inst)) { |
| modified |= ReplaceExtract(inst); |
| } |
| } |
| }); |
| } |
| |
| return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; |
| } |
| |
| bool ReduceLoadSize::ReplaceExtract(Instruction* inst) { |
| assert(inst->opcode() == spv::Op::OpCompositeExtract && |
| "Wrong opcode. Should be OpCompositeExtract."); |
| analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
| analysis::TypeManager* type_mgr = context()->get_type_mgr(); |
| analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); |
| |
| uint32_t composite_id = |
| inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); |
| Instruction* composite_inst = def_use_mgr->GetDef(composite_id); |
| |
| if (composite_inst->opcode() != spv::Op::OpLoad) { |
| return false; |
| } |
| |
| analysis::Type* composite_type = type_mgr->GetType(composite_inst->type_id()); |
| if (composite_type->kind() == analysis::Type::kVector || |
| composite_type->kind() == analysis::Type::kMatrix) { |
| return false; |
| } |
| |
| Instruction* var = composite_inst->GetBaseAddress(); |
| if (var == nullptr || var->opcode() != spv::Op::OpVariable) { |
| return false; |
| } |
| |
| spv::StorageClass storage_class = static_cast<spv::StorageClass>( |
| var->GetSingleWordInOperand(kVariableStorageClassInIdx)); |
| switch (storage_class) { |
| case spv::StorageClass::Uniform: |
| case spv::StorageClass::UniformConstant: |
| case spv::StorageClass::Input: |
| break; |
| default: |
| return false; |
| } |
| |
| // Create a new access chain and load just after the old load. |
| // We cannot create the new access chain load in the position of the extract |
| // because the storage may have been written to in between. |
| InstructionBuilder ir_builder( |
| inst->context(), composite_inst, |
| IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse); |
| |
| uint32_t pointer_to_result_type_id = |
| type_mgr->FindPointerToType(inst->type_id(), storage_class); |
| assert(pointer_to_result_type_id != 0 && |
| "We did not find the pointer type that we need."); |
| |
| analysis::Integer int_type(32, false); |
| const analysis::Type* uint32_type = type_mgr->GetRegisteredType(&int_type); |
| std::vector<uint32_t> ids; |
| for (uint32_t i = 1; i < inst->NumInOperands(); ++i) { |
| uint32_t index = inst->GetSingleWordInOperand(i); |
| const analysis::Constant* index_const = |
| const_mgr->GetConstant(uint32_type, {index}); |
| ids.push_back(const_mgr->GetDefiningInstruction(index_const)->result_id()); |
| } |
| |
| Instruction* new_access_chain = ir_builder.AddAccessChain( |
| pointer_to_result_type_id, |
| composite_inst->GetSingleWordInOperand(kLoadPointerInIdx), ids); |
| Instruction* new_load = |
| ir_builder.AddLoad(inst->type_id(), new_access_chain->result_id()); |
| |
| context()->ReplaceAllUsesWith(inst->result_id(), new_load->result_id()); |
| context()->KillInst(inst); |
| return true; |
| } |
| |
| bool ReduceLoadSize::ShouldReplaceExtract(Instruction* inst) { |
| analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
| Instruction* op_inst = def_use_mgr->GetDef( |
| inst->GetSingleWordInOperand(kExtractCompositeIdInIdx)); |
| |
| if (op_inst->opcode() != spv::Op::OpLoad) { |
| return false; |
| } |
| |
| auto cached_result = should_replace_cache_.find(op_inst->result_id()); |
| if (cached_result != should_replace_cache_.end()) { |
| return cached_result->second; |
| } |
| |
| bool all_elements_used = false; |
| std::set<uint32_t> elements_used; |
| |
| all_elements_used = |
| !def_use_mgr->WhileEachUser(op_inst, [&elements_used](Instruction* use) { |
| if (use->IsCommonDebugInstr()) return true; |
| if (use->opcode() != spv::Op::OpCompositeExtract || |
| use->NumInOperands() == 1) { |
| return false; |
| } |
| elements_used.insert(use->GetSingleWordInOperand(1)); |
| return true; |
| }); |
| |
| bool should_replace = false; |
| if (all_elements_used) { |
| should_replace = false; |
| } else if (1.0 <= replacement_threshold_) { |
| should_replace = true; |
| } else { |
| analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); |
| analysis::TypeManager* type_mgr = context()->get_type_mgr(); |
| analysis::Type* load_type = type_mgr->GetType(op_inst->type_id()); |
| uint32_t total_size = 1; |
| switch (load_type->kind()) { |
| case analysis::Type::kArray: { |
| const analysis::Constant* size_const = |
| const_mgr->FindDeclaredConstant(load_type->AsArray()->LengthId()); |
| |
| if (size_const) { |
| assert(size_const->AsIntConstant()); |
| total_size = size_const->GetU32(); |
| } else { |
| // The size is spec constant, so it is unknown at this time. Assume |
| // it is very large. |
| total_size = UINT32_MAX; |
| } |
| } break; |
| case analysis::Type::kStruct: |
| total_size = static_cast<uint32_t>( |
| load_type->AsStruct()->element_types().size()); |
| break; |
| default: |
| break; |
| } |
| double percent_used = static_cast<double>(elements_used.size()) / |
| static_cast<double>(total_size); |
| should_replace = (percent_used < replacement_threshold_); |
| } |
| |
| should_replace_cache_[op_inst->result_id()] = should_replace; |
| return should_replace; |
| } |
| |
| } // namespace opt |
| } // namespace spvtools |