blob: e0a910f7400eb587b243b3f2c5aa363e85039a78 [file] [log] [blame]
/*
* Copyright © 2023 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
#include "brw_fs.h"
#include "brw_cfg.h"
#include "brw_ir_analysis.h"
/**
* An opportunistic SSA-def analysis pass.
*
* VGRFs are considered defs (SSA values) when:
*
* 1. One instruction wholly defines the register (including all offsets)
* 2. The single defining write dominates all uses
* 3. All sources of the definition are also defs (for non-VGRF files)
*
* We don't consider non-VGRF sources to prevent an instruction from forming
* an SSA def. The other files represent immediates, pushed uniforms, inputs
* to shaders, thread payload fields, and so on. In theory, we could mutate
* FIXED_GRF register values, but we don't today, so it isn't an issue.
*
* Limitations:
* - We do not track uses, only definitions.
* - We do not handle flags, address registers, or accumulators yet.
*
* Usage:
*
* const def_analysis &defs = s.def_analysis.require();
* fs_inst *def = defs.get(inst->src[i]); // returns NULL if non-SSA
* bblock_t *block = defs.get_block(inst->src[i]); // block containing def
*
* Def analysis requires the dominator tree, but not liveness information.
*/
using namespace brw;
static fs_inst *const UNSEEN = (fs_inst *) (uintptr_t) 1;
void
def_analysis::mark_invalid(int nr)
{
def_blocks[nr] = NULL;
def_insts[nr] = NULL;
}
void
def_analysis::update_for_reads(const idom_tree &idom,
bblock_t *block,
fs_inst *inst)
{
/* We don't track accumulator use for def analysis, so if an instruction
* implicitly reads the accumulator, we don't consider it to produce a def.
*/
if (inst->reads_accumulator_implicitly())
mark_invalid(inst->dst.nr);
for (int i = 0; i < inst->sources; i++) {
const int nr = inst->src[i].nr;
if (inst->src[i].file != VGRF) {
/* Similarly, explicit reads of accumulators, address registers,
* and flags make the destination not a def, as we don't track those.
*/
if (inst->src[i].file == ARF &&
(nr == BRW_ARF_ADDRESS ||
nr == BRW_ARF_ACCUMULATOR ||
nr == BRW_ARF_FLAG))
mark_invalid(inst->dst.nr);
continue;
}
def_use_counts[nr]++;
if (def_insts[nr]) {
/* Mark the source def invalid in two cases:
*
* 1. The register is used before being written
* 2. The def doesn't dominate our use.
*
*/
if (def_insts[nr] == UNSEEN ||
!idom.dominates(def_blocks[nr], block))
mark_invalid(nr);
}
/* Additionally, if one of our sources is not a def, then our
* destination may have multiple dynamic assignments.
*/
if (!def_insts[nr] && inst->dst.file == VGRF)
mark_invalid(inst->dst.nr);
}
}
bool
def_analysis::fully_defines(const fs_visitor *v, fs_inst *inst)
{
return v->alloc.sizes[inst->dst.nr] * REG_SIZE == inst->size_written &&
!inst->is_partial_write();
}
void
def_analysis::update_for_write(const fs_visitor *v,
bblock_t *block,
fs_inst *inst)
{
const int nr = inst->dst.nr;
if (inst->dst.file != VGRF || !def_insts[nr])
return;
/* If this is our first write to the destination, and it fully defines
* the destination, then consider it an SSA def for now.
*/
if (def_insts[nr] == UNSEEN && fully_defines(v, inst)) {
def_insts[nr] = inst;
def_blocks[nr] = block;
} else {
/* Otherwise this is a second write or a partial write, in which
* case we know with certainty that this isn't an SSA def.
*/
mark_invalid(nr);
}
}
def_analysis::def_analysis(const fs_visitor *v)
{
const idom_tree &idom = v->idom_analysis.require();
def_count = v->alloc.count;
def_insts = new fs_inst*[def_count]();
def_blocks = new bblock_t*[def_count]();
def_use_counts = new uint32_t[def_count]();
for (unsigned i = 0; i < def_count; i++)
def_insts[i] = UNSEEN;
foreach_block_and_inst(block, fs_inst, inst, v->cfg) {
if (inst->opcode != SHADER_OPCODE_UNDEF) {
update_for_reads(idom, block, inst);
update_for_write(v, block, inst);
}
}
bool iterate;
do {
iterate = false;
for (unsigned d = 0; d < def_count; d++) {
/* Anything still unseen was never written and thus dead code. */
if (def_insts[d] == UNSEEN)
def_insts[d] = NULL;
fs_inst *def = def_insts[d];
if (!def)
continue;
for (int i = 0; i < def->sources; i++) {
if (def->src[i].file != VGRF)
continue;
const int nr = def->src[i].nr;
/* If our "def" reads a non-SSA source, then it isn't a def. */
if (!def_insts[nr] || def_insts[nr] == UNSEEN) {
mark_invalid(def->dst.nr);
iterate = true;
break;
}
}
}
} while (iterate);
}
def_analysis::~def_analysis()
{
delete[] def_insts;
delete[] def_blocks;
delete[] def_use_counts;
}
bool
def_analysis::validate(const fs_visitor *v) const
{
for (unsigned i = 0; i < def_count; i++) {
assert(!def_insts[i] == !def_blocks[i]);
}
return true;
}
unsigned
def_analysis::ssa_count() const
{
unsigned defs = 0;
for (unsigned i = 0; i < def_count; i++) {
if (def_insts[i])
++defs;
}
return defs;
}
void
def_analysis::print_stats(const fs_visitor *v) const
{
const unsigned defs = ssa_count();
fprintf(stderr, "DEFS: %u registers, %u SSA, %u non-SSA => %.1f SSA\n",
def_count, defs, def_count - defs,
100.0f * float(defs) / float(def_count));
}