| // 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/combine_access_chains.h" | |
| #include <utility> | |
| #include "source/opt/constants.h" | |
| #include "source/opt/ir_builder.h" | |
| #include "source/opt/ir_context.h" | |
| namespace spvtools { | |
| namespace opt { | |
| Pass::Status CombineAccessChains::Process() { | |
| bool modified = false; | |
| for (auto& function : *get_module()) { | |
| modified |= ProcessFunction(function); | |
| } | |
| return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); | |
| } | |
| bool CombineAccessChains::ProcessFunction(Function& function) { | |
| bool modified = false; | |
| cfg()->ForEachBlockInReversePostOrder( | |
| function.entry().get(), [&modified, this](BasicBlock* block) { | |
| block->ForEachInst([&modified, this](Instruction* inst) { | |
| switch (inst->opcode()) { | |
| case SpvOpAccessChain: | |
| case SpvOpInBoundsAccessChain: | |
| case SpvOpPtrAccessChain: | |
| case SpvOpInBoundsPtrAccessChain: | |
| modified |= CombineAccessChain(inst); | |
| break; | |
| default: | |
| break; | |
| } | |
| }); | |
| }); | |
| return modified; | |
| } | |
| uint32_t CombineAccessChains::GetConstantValue( | |
| const analysis::Constant* constant_inst) { | |
| if (constant_inst->type()->AsInteger()->width() <= 32) { | |
| if (constant_inst->type()->AsInteger()->IsSigned()) { | |
| return static_cast<uint32_t>(constant_inst->GetS32()); | |
| } else { | |
| return constant_inst->GetU32(); | |
| } | |
| } else { | |
| assert(false); | |
| return 0u; | |
| } | |
| } | |
| uint32_t CombineAccessChains::GetArrayStride(const Instruction* inst) { | |
| uint32_t array_stride = 0; | |
| context()->get_decoration_mgr()->WhileEachDecoration( | |
| inst->type_id(), SpvDecorationArrayStride, | |
| [&array_stride](const Instruction& decoration) { | |
| assert(decoration.opcode() != SpvOpDecorateId); | |
| if (decoration.opcode() == SpvOpDecorate) { | |
| array_stride = decoration.GetSingleWordInOperand(1); | |
| } else { | |
| array_stride = decoration.GetSingleWordInOperand(2); | |
| } | |
| return false; | |
| }); | |
| return array_stride; | |
| } | |
| const analysis::Type* CombineAccessChains::GetIndexedType(Instruction* inst) { | |
| analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); | |
| analysis::TypeManager* type_mgr = context()->get_type_mgr(); | |
| Instruction* base_ptr = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0)); | |
| const analysis::Type* type = type_mgr->GetType(base_ptr->type_id()); | |
| assert(type->AsPointer()); | |
| type = type->AsPointer()->pointee_type(); | |
| std::vector<uint32_t> element_indices; | |
| uint32_t starting_index = 1; | |
| if (IsPtrAccessChain(inst->opcode())) { | |
| // Skip the first index of OpPtrAccessChain as it does not affect type | |
| // resolution. | |
| starting_index = 2; | |
| } | |
| for (uint32_t i = starting_index; i < inst->NumInOperands(); ++i) { | |
| Instruction* index_inst = | |
| def_use_mgr->GetDef(inst->GetSingleWordInOperand(i)); | |
| const analysis::Constant* index_constant = | |
| context()->get_constant_mgr()->GetConstantFromInst(index_inst); | |
| if (index_constant) { | |
| uint32_t index_value = GetConstantValue(index_constant); | |
| element_indices.push_back(index_value); | |
| } else { | |
| // This index must not matter to resolve the type in valid SPIR-V. | |
| element_indices.push_back(0); | |
| } | |
| } | |
| type = type_mgr->GetMemberType(type, element_indices); | |
| return type; | |
| } | |
| bool CombineAccessChains::CombineIndices(Instruction* ptr_input, | |
| Instruction* inst, | |
| std::vector<Operand>* new_operands) { | |
| analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); | |
| analysis::ConstantManager* constant_mgr = context()->get_constant_mgr(); | |
| Instruction* last_index_inst = def_use_mgr->GetDef( | |
| ptr_input->GetSingleWordInOperand(ptr_input->NumInOperands() - 1)); | |
| const analysis::Constant* last_index_constant = | |
| constant_mgr->GetConstantFromInst(last_index_inst); | |
| Instruction* element_inst = | |
| def_use_mgr->GetDef(inst->GetSingleWordInOperand(1)); | |
| const analysis::Constant* element_constant = | |
| constant_mgr->GetConstantFromInst(element_inst); | |
| // Combine the last index of the AccessChain (|ptr_inst|) with the element | |
| // operand of the PtrAccessChain (|inst|). | |
| const bool combining_element_operands = | |
| IsPtrAccessChain(inst->opcode()) && | |
| IsPtrAccessChain(ptr_input->opcode()) && ptr_input->NumInOperands() == 2; | |
| uint32_t new_value_id = 0; | |
| const analysis::Type* type = GetIndexedType(ptr_input); | |
| if (last_index_constant && element_constant) { | |
| // Combine the constants. | |
| uint32_t new_value = GetConstantValue(last_index_constant) + | |
| GetConstantValue(element_constant); | |
| const analysis::Constant* new_value_constant = | |
| constant_mgr->GetConstant(last_index_constant->type(), {new_value}); | |
| Instruction* new_value_inst = | |
| constant_mgr->GetDefiningInstruction(new_value_constant); | |
| new_value_id = new_value_inst->result_id(); | |
| } else if (!type->AsStruct() || combining_element_operands) { | |
| // Generate an addition of the two indices. | |
| InstructionBuilder builder( | |
| context(), inst, | |
| IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); | |
| Instruction* addition = builder.AddIAdd(last_index_inst->type_id(), | |
| last_index_inst->result_id(), | |
| element_inst->result_id()); | |
| new_value_id = addition->result_id(); | |
| } else { | |
| // Indexing into structs must be constant, so bail out here. | |
| return false; | |
| } | |
| new_operands->push_back({SPV_OPERAND_TYPE_ID, {new_value_id}}); | |
| return true; | |
| } | |
| bool CombineAccessChains::CreateNewInputOperands( | |
| Instruction* ptr_input, Instruction* inst, | |
| std::vector<Operand>* new_operands) { | |
| // Start by copying all the input operands of the feeder access chain. | |
| for (uint32_t i = 0; i != ptr_input->NumInOperands() - 1; ++i) { | |
| new_operands->push_back(ptr_input->GetInOperand(i)); | |
| } | |
| // Deal with the last index of the feeder access chain. | |
| if (IsPtrAccessChain(inst->opcode())) { | |
| // The last index of the feeder should be combined with the element operand | |
| // of |inst|. | |
| if (!CombineIndices(ptr_input, inst, new_operands)) return false; | |
| } else { | |
| // The indices aren't being combined so now add the last index operand of | |
| // |ptr_input|. | |
| new_operands->push_back( | |
| ptr_input->GetInOperand(ptr_input->NumInOperands() - 1)); | |
| } | |
| // Copy the remaining index operands. | |
| uint32_t starting_index = IsPtrAccessChain(inst->opcode()) ? 2 : 1; | |
| for (uint32_t i = starting_index; i < inst->NumInOperands(); ++i) { | |
| new_operands->push_back(inst->GetInOperand(i)); | |
| } | |
| return true; | |
| } | |
| bool CombineAccessChains::CombineAccessChain(Instruction* inst) { | |
| assert((inst->opcode() == SpvOpPtrAccessChain || | |
| inst->opcode() == SpvOpAccessChain || | |
| inst->opcode() == SpvOpInBoundsAccessChain || | |
| inst->opcode() == SpvOpInBoundsPtrAccessChain) && | |
| "Wrong opcode. Expected an access chain."); | |
| Instruction* ptr_input = | |
| context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0)); | |
| if (ptr_input->opcode() != SpvOpAccessChain && | |
| ptr_input->opcode() != SpvOpInBoundsAccessChain && | |
| ptr_input->opcode() != SpvOpPtrAccessChain && | |
| ptr_input->opcode() != SpvOpInBoundsPtrAccessChain) { | |
| return false; | |
| } | |
| if (Has64BitIndices(inst) || Has64BitIndices(ptr_input)) return false; | |
| // Handles the following cases: | |
| // 1. |ptr_input| is an index-less access chain. Replace the pointer | |
| // in |inst| with |ptr_input|'s pointer. | |
| // 2. |inst| is a index-less access chain. Change |inst| to an | |
| // OpCopyObject. | |
| // 3. |inst| is not a pointer access chain. | |
| // |inst|'s indices are appended to |ptr_input|'s indices. | |
| // 4. |ptr_input| is not pointer access chain. | |
| // |inst| is a pointer access chain. | |
| // |inst|'s element operand is combined with the last index in | |
| // |ptr_input| to form a new operand. | |
| // 5. |ptr_input| is a pointer access chain. | |
| // Like the above scenario, |inst|'s element operand is combined | |
| // with |ptr_input|'s last index. This results is either a | |
| // combined element operand or combined regular index. | |
| // TODO(alan-baker): Support this properly. Requires analyzing the | |
| // size/alignment of the type and converting the stride into an element | |
| // index. | |
| uint32_t array_stride = GetArrayStride(ptr_input); | |
| if (array_stride != 0) return false; | |
| if (ptr_input->NumInOperands() == 1) { | |
| // The input is effectively a no-op. | |
| inst->SetInOperand(0, {ptr_input->GetSingleWordInOperand(0)}); | |
| context()->AnalyzeUses(inst); | |
| } else if (inst->NumInOperands() == 1) { | |
| // |inst| is a no-op, change it to a copy. Instruction simplification will | |
| // clean it up. | |
| inst->SetOpcode(SpvOpCopyObject); | |
| } else { | |
| std::vector<Operand> new_operands; | |
| if (!CreateNewInputOperands(ptr_input, inst, &new_operands)) return false; | |
| // Update the instruction. | |
| inst->SetOpcode(UpdateOpcode(inst->opcode(), ptr_input->opcode())); | |
| inst->SetInOperands(std::move(new_operands)); | |
| context()->AnalyzeUses(inst); | |
| } | |
| return true; | |
| } | |
| SpvOp CombineAccessChains::UpdateOpcode(SpvOp base_opcode, SpvOp input_opcode) { | |
| auto IsInBounds = [](SpvOp opcode) { | |
| return opcode == SpvOpInBoundsPtrAccessChain || | |
| opcode == SpvOpInBoundsAccessChain; | |
| }; | |
| if (input_opcode == SpvOpInBoundsPtrAccessChain) { | |
| if (!IsInBounds(base_opcode)) return SpvOpPtrAccessChain; | |
| } else if (input_opcode == SpvOpInBoundsAccessChain) { | |
| if (!IsInBounds(base_opcode)) return SpvOpAccessChain; | |
| } | |
| return input_opcode; | |
| } | |
| bool CombineAccessChains::IsPtrAccessChain(SpvOp opcode) { | |
| return opcode == SpvOpPtrAccessChain || opcode == SpvOpInBoundsPtrAccessChain; | |
| } | |
| bool CombineAccessChains::Has64BitIndices(Instruction* inst) { | |
| for (uint32_t i = 1; i < inst->NumInOperands(); ++i) { | |
| Instruction* index_inst = | |
| context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(i)); | |
| const analysis::Type* index_type = | |
| context()->get_type_mgr()->GetType(index_inst->type_id()); | |
| if (!index_type->AsInteger() || index_type->AsInteger()->width() != 32) | |
| return true; | |
| } | |
| return false; | |
| } | |
| } // namespace opt | |
| } // namespace spvtools |