| /* |
| * Copyright © 2024 Valve Corporation |
| * |
| * SPDX-License-Identifier: MIT |
| */ |
| |
| #include "aco_ir.h" |
| |
| #include <functional> |
| |
| namespace aco { |
| |
| namespace { |
| |
| /** |
| * This is a limited SSA repair pass which inserts the phis necessary for the definition of a |
| * linear temporary to dominate it's uses in the linear CFG. The definition must still dominate it's |
| * uses in the logical CFG. If a path in which the temporary is defined is not taken, the value used |
| * is undefined. |
| * |
| * aco::lower_phis() must be run after to lower the logical phis created by this pass. |
| */ |
| |
| struct repair_state { |
| Program* program; |
| Block* block; |
| |
| std::vector<bool> block_needs_repair; /* uses in this block might need repair */ |
| std::vector<bool> dom_needs_repair; /* whether a block dominates a block which needs repair */ |
| |
| std::vector<bool> has_def_block; |
| std::unique_ptr<uint32_t[]> def_blocks; |
| std::unordered_map<uint64_t, Temp> renames; |
| |
| std::vector<aco_ptr<Instruction>> new_phis; |
| |
| std::vector<bool> visit_block; |
| std::vector<Temp> temps; |
| |
| bool is_temp_defined_at_pred(uint32_t block_idx, uint32_t def_block) const |
| { |
| return block_idx >= def_block && visit_block[block_idx] && temps[block_idx].id(); |
| } |
| }; |
| |
| Temp |
| create_phis(repair_state* state, Temp tmp, uint32_t use_block, uint32_t def_block) |
| { |
| Program* program = state->program; |
| |
| assert(program->blocks[def_block].logical_idom != -1); |
| assert(program->blocks[use_block].logical_idom != -1); |
| assert(use_block > def_block); |
| |
| std::fill(state->visit_block.begin() + def_block, state->visit_block.begin() + use_block + 1, |
| false); |
| |
| for (int32_t i = use_block; i >= (int)def_block; i--) { |
| bool block_needs_tmp = (uint32_t)i == use_block; |
| for (uint32_t succ : program->blocks[i].logical_succs) |
| block_needs_tmp |= succ > (uint32_t)i && state->visit_block[succ]; |
| state->visit_block[i] = block_needs_tmp; |
| |
| if (block_needs_tmp && (uint32_t)i != def_block) { |
| uint64_t k = i | ((uint64_t)tmp.id() << 32); |
| auto it = state->renames.find(k); |
| if (it != state->renames.end()) |
| state->temps[i] = it->second; |
| else |
| state->temps[i] = Temp(0, tmp.regClass()); |
| } |
| } |
| |
| state->temps[def_block] = tmp; |
| for (uint32_t i = def_block + 1; i <= use_block; i++) { |
| if (!state->visit_block[i] || state->temps[i].id()) |
| continue; |
| |
| Block& block = program->blocks[i]; |
| |
| bool undef = true; |
| for (unsigned pred : block.logical_preds) |
| undef &= pred < i && !state->is_temp_defined_at_pred(pred, def_block); |
| if (undef) { |
| state->temps[i] = Temp(0, tmp.regClass()); |
| continue; |
| } |
| |
| /* If a logical dominator has a temporary, we don't need to create a phi and can just use |
| * that temporary instead. For linear temporaries, we also need to check if it dominates in |
| * the linear CFG, because logical dominators do not necessarily dominate a block in the |
| * linear CFG (for example, because of continue_or_break or empty exec skips). */ |
| uint32_t dom = block.index; |
| bool skip_phis = false; |
| do { |
| dom = program->blocks[dom].logical_idom; |
| if (state->is_temp_defined_at_pred(dom, def_block) && |
| dominates_linear(program->blocks[dom], block)) { |
| skip_phis = true; |
| break; |
| } |
| } while (dom != def_block); |
| if (skip_phis) { |
| state->temps[i] = state->temps[dom]; |
| continue; |
| } |
| |
| /* This pass doesn't support creating loop header phis */ |
| assert(!(block.kind & block_kind_loop_header)); |
| |
| Temp def = program->allocateTmp(tmp.regClass()); |
| aco_ptr<Instruction> phi{ |
| create_instruction(aco_opcode::p_phi, Format::PSEUDO, block.logical_preds.size(), 1)}; |
| for (unsigned j = 0; j < block.logical_preds.size(); j++) { |
| uint32_t pred = block.logical_preds[j]; |
| assert(state->is_temp_defined_at_pred(pred, def_block)); |
| phi->operands[j] = Operand(state->temps[pred]); |
| } |
| phi->definitions[0] = Definition(def); |
| |
| if (&block == state->block) |
| state->new_phis.emplace_back(std::move(phi)); |
| else |
| block.instructions.emplace(block.instructions.begin(), std::move(phi)); |
| |
| uint64_t k = i | ((uint64_t)tmp.id() << 32); |
| state->renames.emplace(k, def); |
| state->temps[i] = def; |
| } |
| |
| return state->temps[use_block]; |
| } |
| |
| template <bool LoopHeader> |
| bool |
| repair_block(repair_state* state, Block& block) |
| { |
| bool needs_repair = state->block_needs_repair[block.index]; |
| bool dom_needs_repair = state->dom_needs_repair[block.index]; |
| bool progress = false; |
| |
| state->block = █ |
| for (aco_ptr<Instruction>& instr : block.instructions) { |
| if (dom_needs_repair) { |
| for (Definition def : instr->definitions) { |
| if (def.isTemp()) { |
| state->def_blocks[def.tempId()] = block.index; |
| state->has_def_block[def.tempId()] = true; |
| } |
| } |
| } |
| |
| bool phi = is_phi(instr) || instr->opcode == aco_opcode::p_boolean_phi; |
| |
| /* Skip the section below if we don't need to repair. If we don't need to update def_blocks |
| * either, then we can just break. We always need to process phis because their actual uses |
| * are in the predecessors, which might need repair. */ |
| if (!phi && !needs_repair) { |
| if (!dom_needs_repair) |
| break; |
| continue; |
| } |
| |
| unsigned start = 0; |
| unsigned num_operands = instr->operands.size(); |
| if (phi && (block.kind & block_kind_loop_header)) { |
| if (LoopHeader) |
| start++; |
| else |
| num_operands = 1; |
| } else if (LoopHeader) { |
| break; |
| } |
| |
| for (unsigned i = start; i < num_operands; i++) { |
| Operand& op = instr->operands[i]; |
| if (!op.isTemp() || !op.getTemp().is_linear() || !state->has_def_block[op.tempId()]) |
| continue; |
| |
| uint32_t def_block = state->def_blocks[op.tempId()]; |
| uint32_t use_block = block.index; |
| if (instr->opcode == aco_opcode::p_boolean_phi || instr->opcode == aco_opcode::p_phi) { |
| use_block = block.logical_preds[i]; |
| if (!state->block_needs_repair[use_block]) |
| continue; |
| } else if (instr->opcode == aco_opcode::p_linear_phi) { |
| use_block = block.linear_preds[i]; |
| if (!state->block_needs_repair[use_block]) |
| continue; |
| } |
| |
| if (!dominates_linear(state->program->blocks[def_block], |
| state->program->blocks[use_block])) { |
| assert(dominates_logical(state->program->blocks[def_block], |
| state->program->blocks[use_block])); |
| op.setTemp(create_phis(state, op.getTemp(), use_block, def_block)); |
| progress = true; |
| } |
| } |
| } |
| |
| /* These are inserted later to not invalidate any iterators. */ |
| block.instructions.insert(block.instructions.begin(), |
| std::move_iterator(state->new_phis.begin()), |
| std::move_iterator(state->new_phis.end())); |
| state->new_phis.clear(); |
| |
| return progress; |
| } |
| |
| } /* end namespace */ |
| |
| bool |
| repair_ssa(Program* program) |
| { |
| repair_state state; |
| state.program = program; |
| |
| state.block_needs_repair.resize(program->blocks.size()); |
| state.dom_needs_repair.resize(program->blocks.size()); |
| |
| state.has_def_block.resize(program->peekAllocationId()); |
| state.def_blocks.reset(new uint32_t[program->peekAllocationId()]); |
| |
| state.visit_block.resize(program->blocks.size()); |
| state.temps.resize(program->blocks.size()); |
| |
| for (Block& block : program->blocks) { |
| if (block.logical_idom == -1) |
| continue; |
| |
| if (state.block_needs_repair[block.logical_idom] || |
| !dominates_linear(program->blocks[block.logical_idom], block)) { |
| state.block_needs_repair[block.index] = true; |
| |
| /* Set dom_needs_repair=true for logical dominators. */ |
| uint32_t parent = block.logical_idom; |
| while (!state.dom_needs_repair[parent]) { |
| state.dom_needs_repair[parent] = true; |
| parent = program->blocks[parent].logical_idom; |
| } |
| } |
| } |
| |
| std::vector<unsigned> loop_header_indices; |
| |
| bool progress = false; |
| for (Block& block : program->blocks) { |
| if (block.kind & block_kind_loop_header) |
| loop_header_indices.push_back(block.index); |
| |
| progress |= repair_block<false>(&state, block); |
| |
| if (block.kind & block_kind_loop_exit) { |
| unsigned header = loop_header_indices.back(); |
| loop_header_indices.pop_back(); |
| |
| progress |= repair_block<true>(&state, program->blocks[header]); |
| } |
| } |
| |
| return progress; |
| } |
| |
| } // namespace aco |