#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) {
if (function.IsDeclaration()) {
return false;
}
bool modified = false;
cfg()->ForEachBlockInReversePostOrder(
function.entry().get(), [&modified, this](BasicBlock* block) {
block->ForEachInst([&modified, this](Instruction* inst) {
switch (inst->opcode()) {
case spv::Op::OpAccessChain:
case spv::Op::OpInBoundsAccessChain:
case spv::Op::OpPtrAccessChain:
case spv::Op::OpInBoundsPtrAccessChain:
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(), uint32_t(spv::Decoration::ArrayStride),
[&array_stride](const Instruction& decoration) {
assert(decoration.opcode() != spv::Op::OpDecorateId);
if (decoration.opcode() == spv::Op::OpDecorate) {
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())) {
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 {
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);
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) {
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) {
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 {
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) {
for (uint32_t i = 0; i != ptr_input->NumInOperands() - 1; ++i) {
new_operands->push_back(ptr_input->GetInOperand(i));
}
if (IsPtrAccessChain(inst->opcode())) {
if (!CombineIndices(ptr_input, inst, new_operands)) return false;
} else {
new_operands->push_back(
ptr_input->GetInOperand(ptr_input->NumInOperands() - 1));
}
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() == spv::Op::OpPtrAccessChain ||
inst->opcode() == spv::Op::OpAccessChain ||
inst->opcode() == spv::Op::OpInBoundsAccessChain ||
inst->opcode() == spv::Op::OpInBoundsPtrAccessChain) &&
"Wrong opcode. Expected an access chain.");
Instruction* ptr_input =
context()->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0));
if (ptr_input->opcode() != spv::Op::OpAccessChain &&
ptr_input->opcode() != spv::Op::OpInBoundsAccessChain &&
ptr_input->opcode() != spv::Op::OpPtrAccessChain &&
ptr_input->opcode() != spv::Op::OpInBoundsPtrAccessChain) {
return false;
}
if (Has64BitIndices(inst) || Has64BitIndices(ptr_input)) return false;
uint32_t array_stride = GetArrayStride(ptr_input);
if (array_stride != 0) return false;
if (ptr_input->NumInOperands() == 1) {
inst->SetInOperand(0, {ptr_input->GetSingleWordInOperand(0)});
context()->AnalyzeUses(inst);
} else if (inst->NumInOperands() == 1) {
inst->SetOpcode(spv::Op::OpCopyObject);
} else {
std::vector<Operand> new_operands;
if (!CreateNewInputOperands(ptr_input, inst, &new_operands)) return false;
inst->SetOpcode(UpdateOpcode(inst->opcode(), ptr_input->opcode()));
inst->SetInOperands(std::move(new_operands));
context()->AnalyzeUses(inst);
}
return true;
}
spv::Op CombineAccessChains::UpdateOpcode(spv::Op base_opcode,
spv::Op input_opcode) {
auto IsInBounds = [](spv::Op opcode) {
return opcode == spv::Op::OpInBoundsPtrAccessChain ||
opcode == spv::Op::OpInBoundsAccessChain;
};
if (input_opcode == spv::Op::OpInBoundsPtrAccessChain) {
if (!IsInBounds(base_opcode)) return spv::Op::OpPtrAccessChain;
} else if (input_opcode == spv::Op::OpInBoundsAccessChain) {
if (!IsInBounds(base_opcode)) return spv::Op::OpAccessChain;
}
return input_opcode;
}
bool CombineAccessChains::IsPtrAccessChain(spv::Op opcode) {
return opcode == spv::Op::OpPtrAccessChain ||
opcode == spv::Op::OpInBoundsPtrAccessChain;
}
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;
}
}
}