/* * Copyright © 2024 Valve Corporation * * SPDX-License-Identifier: MIT */ #include "aco_ir.h" #include 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 block_needs_repair; /* uses in this block might need repair */ std::vector dom_needs_repair; /* whether a block dominates a block which needs repair */ std::vector has_def_block; std::unique_ptr def_blocks; std::unordered_map renames; std::vector> new_phis; std::vector visit_block; std::vector 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 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 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& 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 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(&state, block); if (block.kind & block_kind_loop_exit) { unsigned header = loop_header_indices.back(); loop_header_indices.pop_back(); progress |= repair_block(&state, program->blocks[header]); } } return progress; } } // namespace aco