mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2026-05-30 03:08:12 +02:00
The NIR control flow modification API churns the block structure, splitting blocks, stitching them back together, and so on. Preserving information about block dominance is hard (and probably not worthwhile). This patch makes nir_cf_extract() throw away all metadata, like we do when adding/removing jumps. We then make the dead control flow pass compute dominance information right before it uses it. This is necessary because earlier work by the pass may have invalidated it. Signed-off-by: Kenneth Graunke <kenneth@whitecape.org> Reviewed-by: Connor Abbott <cwabbott0@gmail.com> Reviewed-by: Jason Ekstrand <jason.ekstrand@intel.com>
805 lines
25 KiB
C
805 lines
25 KiB
C
/*
|
|
* Copyright © 2014 Intel Corporation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*
|
|
* Authors:
|
|
* Connor Abbott (cwabbott0@gmail.com)
|
|
*
|
|
*/
|
|
|
|
#include "nir_control_flow_private.h"
|
|
|
|
/**
|
|
* \name Control flow modification
|
|
*
|
|
* These functions modify the control flow tree while keeping the control flow
|
|
* graph up-to-date. The invariants respected are:
|
|
* 1. Each then statement, else statement, or loop body must have at least one
|
|
* control flow node.
|
|
* 2. Each if-statement and loop must have one basic block before it and one
|
|
* after.
|
|
* 3. Two basic blocks cannot be directly next to each other.
|
|
* 4. If a basic block has a jump instruction, there must be only one and it
|
|
* must be at the end of the block.
|
|
* 5. The CFG must always be connected - this means that we must insert a fake
|
|
* CFG edge for loops with no break statement.
|
|
*
|
|
* The purpose of the second one is so that we have places to insert code during
|
|
* GCM, as well as eliminating the possibility of critical edges.
|
|
*/
|
|
/*@{*/
|
|
|
|
static bool
|
|
block_ends_in_jump(nir_block *block)
|
|
{
|
|
return !exec_list_is_empty(&block->instr_list) &&
|
|
nir_block_last_instr(block)->type == nir_instr_type_jump;
|
|
}
|
|
|
|
static inline void
|
|
block_add_pred(nir_block *block, nir_block *pred)
|
|
{
|
|
_mesa_set_add(block->predecessors, pred);
|
|
}
|
|
|
|
static inline void
|
|
block_remove_pred(nir_block *block, nir_block *pred)
|
|
{
|
|
struct set_entry *entry = _mesa_set_search(block->predecessors, pred);
|
|
|
|
assert(entry);
|
|
|
|
_mesa_set_remove(block->predecessors, entry);
|
|
}
|
|
|
|
static void
|
|
link_blocks(nir_block *pred, nir_block *succ1, nir_block *succ2)
|
|
{
|
|
pred->successors[0] = succ1;
|
|
if (succ1 != NULL)
|
|
block_add_pred(succ1, pred);
|
|
|
|
pred->successors[1] = succ2;
|
|
if (succ2 != NULL)
|
|
block_add_pred(succ2, pred);
|
|
}
|
|
|
|
static void
|
|
unlink_blocks(nir_block *pred, nir_block *succ)
|
|
{
|
|
if (pred->successors[0] == succ) {
|
|
pred->successors[0] = pred->successors[1];
|
|
pred->successors[1] = NULL;
|
|
} else {
|
|
assert(pred->successors[1] == succ);
|
|
pred->successors[1] = NULL;
|
|
}
|
|
|
|
block_remove_pred(succ, pred);
|
|
}
|
|
|
|
static void
|
|
unlink_block_successors(nir_block *block)
|
|
{
|
|
if (block->successors[1] != NULL)
|
|
unlink_blocks(block, block->successors[1]);
|
|
if (block->successors[0] != NULL)
|
|
unlink_blocks(block, block->successors[0]);
|
|
}
|
|
|
|
static void
|
|
link_non_block_to_block(nir_cf_node *node, nir_block *block)
|
|
{
|
|
if (node->type == nir_cf_node_if) {
|
|
/*
|
|
* We're trying to link an if to a block after it; this just means linking
|
|
* the last block of the then and else branches.
|
|
*/
|
|
|
|
nir_if *if_stmt = nir_cf_node_as_if(node);
|
|
|
|
nir_cf_node *last_then = nir_if_last_then_node(if_stmt);
|
|
assert(last_then->type == nir_cf_node_block);
|
|
nir_block *last_then_block = nir_cf_node_as_block(last_then);
|
|
|
|
nir_cf_node *last_else = nir_if_last_else_node(if_stmt);
|
|
assert(last_else->type == nir_cf_node_block);
|
|
nir_block *last_else_block = nir_cf_node_as_block(last_else);
|
|
|
|
if (!block_ends_in_jump(last_then_block)) {
|
|
unlink_block_successors(last_then_block);
|
|
link_blocks(last_then_block, block, NULL);
|
|
}
|
|
|
|
if (!block_ends_in_jump(last_else_block)) {
|
|
unlink_block_successors(last_else_block);
|
|
link_blocks(last_else_block, block, NULL);
|
|
}
|
|
} else {
|
|
assert(node->type == nir_cf_node_loop);
|
|
|
|
/*
|
|
* We can only get to this codepath if we're inserting a new loop, or
|
|
* at least a loop with no break statements; we can't insert break
|
|
* statements into a loop when we haven't inserted it into the CFG
|
|
* because we wouldn't know which block comes after the loop
|
|
* and therefore, which block should be the successor of the block with
|
|
* the break). Therefore, we need to insert a fake edge (see invariant
|
|
* #5).
|
|
*/
|
|
|
|
nir_loop *loop = nir_cf_node_as_loop(node);
|
|
|
|
nir_cf_node *last = nir_loop_last_cf_node(loop);
|
|
assert(last->type == nir_cf_node_block);
|
|
nir_block *last_block = nir_cf_node_as_block(last);
|
|
|
|
last_block->successors[1] = block;
|
|
block_add_pred(block, last_block);
|
|
}
|
|
}
|
|
|
|
static void
|
|
link_block_to_non_block(nir_block *block, nir_cf_node *node)
|
|
{
|
|
if (node->type == nir_cf_node_if) {
|
|
/*
|
|
* We're trying to link a block to an if after it; this just means linking
|
|
* the block to the first block of the then and else branches.
|
|
*/
|
|
|
|
nir_if *if_stmt = nir_cf_node_as_if(node);
|
|
|
|
nir_cf_node *first_then = nir_if_first_then_node(if_stmt);
|
|
assert(first_then->type == nir_cf_node_block);
|
|
nir_block *first_then_block = nir_cf_node_as_block(first_then);
|
|
|
|
nir_cf_node *first_else = nir_if_first_else_node(if_stmt);
|
|
assert(first_else->type == nir_cf_node_block);
|
|
nir_block *first_else_block = nir_cf_node_as_block(first_else);
|
|
|
|
unlink_block_successors(block);
|
|
link_blocks(block, first_then_block, first_else_block);
|
|
} else {
|
|
/*
|
|
* For similar reasons as the corresponding case in
|
|
* link_non_block_to_block(), don't worry about if the loop header has
|
|
* any predecessors that need to be unlinked.
|
|
*/
|
|
|
|
assert(node->type == nir_cf_node_loop);
|
|
|
|
nir_loop *loop = nir_cf_node_as_loop(node);
|
|
|
|
nir_cf_node *loop_header = nir_loop_first_cf_node(loop);
|
|
assert(loop_header->type == nir_cf_node_block);
|
|
nir_block *loop_header_block = nir_cf_node_as_block(loop_header);
|
|
|
|
unlink_block_successors(block);
|
|
link_blocks(block, loop_header_block, NULL);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Replace a block's successor with a different one.
|
|
*/
|
|
static void
|
|
replace_successor(nir_block *block, nir_block *old_succ, nir_block *new_succ)
|
|
{
|
|
if (block->successors[0] == old_succ) {
|
|
block->successors[0] = new_succ;
|
|
} else {
|
|
assert(block->successors[1] == old_succ);
|
|
block->successors[1] = new_succ;
|
|
}
|
|
|
|
block_remove_pred(old_succ, block);
|
|
block_add_pred(new_succ, block);
|
|
}
|
|
|
|
/**
|
|
* Takes a basic block and inserts a new empty basic block before it, making its
|
|
* predecessors point to the new block. This essentially splits the block into
|
|
* an empty header and a body so that another non-block CF node can be inserted
|
|
* between the two. Note that this does *not* link the two basic blocks, so
|
|
* some kind of cleanup *must* be performed after this call.
|
|
*/
|
|
|
|
static nir_block *
|
|
split_block_beginning(nir_block *block)
|
|
{
|
|
nir_block *new_block = nir_block_create(ralloc_parent(block));
|
|
new_block->cf_node.parent = block->cf_node.parent;
|
|
exec_node_insert_node_before(&block->cf_node.node, &new_block->cf_node.node);
|
|
|
|
struct set_entry *entry;
|
|
set_foreach(block->predecessors, entry) {
|
|
nir_block *pred = (nir_block *) entry->key;
|
|
replace_successor(pred, block, new_block);
|
|
}
|
|
|
|
/* Any phi nodes must stay part of the new block, or else their
|
|
* sourcse will be messed up. This will reverse the order of the phi's, but
|
|
* order shouldn't matter.
|
|
*/
|
|
nir_foreach_instr_safe(block, instr) {
|
|
if (instr->type != nir_instr_type_phi)
|
|
break;
|
|
|
|
exec_node_remove(&instr->node);
|
|
instr->block = new_block;
|
|
exec_list_push_head(&new_block->instr_list, &instr->node);
|
|
}
|
|
|
|
return new_block;
|
|
}
|
|
|
|
static void
|
|
rewrite_phi_preds(nir_block *block, nir_block *old_pred, nir_block *new_pred)
|
|
{
|
|
nir_foreach_instr_safe(block, instr) {
|
|
if (instr->type != nir_instr_type_phi)
|
|
break;
|
|
|
|
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
|
nir_foreach_phi_src(phi, src) {
|
|
if (src->pred == old_pred) {
|
|
src->pred = new_pred;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
insert_phi_undef(nir_block *block, nir_block *pred)
|
|
{
|
|
nir_function_impl *impl = nir_cf_node_get_function(&block->cf_node);
|
|
nir_foreach_instr(block, instr) {
|
|
if (instr->type != nir_instr_type_phi)
|
|
break;
|
|
|
|
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
|
nir_ssa_undef_instr *undef =
|
|
nir_ssa_undef_instr_create(ralloc_parent(phi),
|
|
phi->dest.ssa.num_components);
|
|
nir_instr_insert_before_cf_list(&impl->body, &undef->instr);
|
|
nir_phi_src *src = ralloc(phi, nir_phi_src);
|
|
src->pred = pred;
|
|
src->src.parent_instr = &phi->instr;
|
|
src->src.is_ssa = true;
|
|
src->src.ssa = &undef->def;
|
|
|
|
list_addtail(&src->src.use_link, &undef->def.uses);
|
|
|
|
exec_list_push_tail(&phi->srcs, &src->node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Moves the successors of source to the successors of dest, leaving both
|
|
* successors of source NULL.
|
|
*/
|
|
|
|
static void
|
|
move_successors(nir_block *source, nir_block *dest)
|
|
{
|
|
nir_block *succ1 = source->successors[0];
|
|
nir_block *succ2 = source->successors[1];
|
|
|
|
if (succ1) {
|
|
unlink_blocks(source, succ1);
|
|
rewrite_phi_preds(succ1, source, dest);
|
|
}
|
|
|
|
if (succ2) {
|
|
unlink_blocks(source, succ2);
|
|
rewrite_phi_preds(succ2, source, dest);
|
|
}
|
|
|
|
unlink_block_successors(dest);
|
|
link_blocks(dest, succ1, succ2);
|
|
}
|
|
|
|
/* Given a basic block with no successors that has been inserted into the
|
|
* control flow tree, gives it the successors it would normally have assuming
|
|
* it doesn't end in a jump instruction. Also inserts phi sources with undefs
|
|
* if necessary.
|
|
*/
|
|
static void
|
|
block_add_normal_succs(nir_block *block)
|
|
{
|
|
if (exec_node_is_tail_sentinel(block->cf_node.node.next)) {
|
|
nir_cf_node *parent = block->cf_node.parent;
|
|
if (parent->type == nir_cf_node_if) {
|
|
nir_cf_node *next = nir_cf_node_next(parent);
|
|
assert(next->type == nir_cf_node_block);
|
|
nir_block *next_block = nir_cf_node_as_block(next);
|
|
|
|
link_blocks(block, next_block, NULL);
|
|
} else {
|
|
assert(parent->type == nir_cf_node_loop);
|
|
nir_loop *loop = nir_cf_node_as_loop(parent);
|
|
|
|
nir_cf_node *head = nir_loop_first_cf_node(loop);
|
|
assert(head->type == nir_cf_node_block);
|
|
nir_block *head_block = nir_cf_node_as_block(head);
|
|
|
|
link_blocks(block, head_block, NULL);
|
|
insert_phi_undef(head_block, block);
|
|
}
|
|
} else {
|
|
nir_cf_node *next = nir_cf_node_next(&block->cf_node);
|
|
if (next->type == nir_cf_node_if) {
|
|
nir_if *next_if = nir_cf_node_as_if(next);
|
|
|
|
nir_cf_node *first_then = nir_if_first_then_node(next_if);
|
|
assert(first_then->type == nir_cf_node_block);
|
|
nir_block *first_then_block = nir_cf_node_as_block(first_then);
|
|
|
|
nir_cf_node *first_else = nir_if_first_else_node(next_if);
|
|
assert(first_else->type == nir_cf_node_block);
|
|
nir_block *first_else_block = nir_cf_node_as_block(first_else);
|
|
|
|
link_blocks(block, first_then_block, first_else_block);
|
|
} else {
|
|
assert(next->type == nir_cf_node_loop);
|
|
nir_loop *next_loop = nir_cf_node_as_loop(next);
|
|
|
|
nir_cf_node *first = nir_loop_first_cf_node(next_loop);
|
|
assert(first->type == nir_cf_node_block);
|
|
nir_block *first_block = nir_cf_node_as_block(first);
|
|
|
|
link_blocks(block, first_block, NULL);
|
|
insert_phi_undef(first_block, block);
|
|
}
|
|
}
|
|
}
|
|
|
|
static nir_block *
|
|
split_block_end(nir_block *block)
|
|
{
|
|
nir_block *new_block = nir_block_create(ralloc_parent(block));
|
|
new_block->cf_node.parent = block->cf_node.parent;
|
|
exec_node_insert_after(&block->cf_node.node, &new_block->cf_node.node);
|
|
|
|
if (block_ends_in_jump(block)) {
|
|
/* Figure out what successor block would've had if it didn't have a jump
|
|
* instruction, and make new_block have that successor.
|
|
*/
|
|
block_add_normal_succs(new_block);
|
|
} else {
|
|
move_successors(block, new_block);
|
|
}
|
|
|
|
return new_block;
|
|
}
|
|
|
|
static nir_block *
|
|
split_block_before_instr(nir_instr *instr)
|
|
{
|
|
assert(instr->type != nir_instr_type_phi);
|
|
nir_block *new_block = split_block_beginning(instr->block);
|
|
|
|
nir_foreach_instr_safe(instr->block, cur_instr) {
|
|
if (cur_instr == instr)
|
|
break;
|
|
|
|
exec_node_remove(&cur_instr->node);
|
|
cur_instr->block = new_block;
|
|
exec_list_push_tail(&new_block->instr_list, &cur_instr->node);
|
|
}
|
|
|
|
return new_block;
|
|
}
|
|
|
|
/* Splits a basic block at the point specified by the cursor. The "before" and
|
|
* "after" arguments are filled out with the blocks resulting from the split
|
|
* if non-NULL. Note that the "beginning" of the block is actually interpreted
|
|
* as before the first non-phi instruction, and it's illegal to split a block
|
|
* before a phi instruction.
|
|
*/
|
|
|
|
static void
|
|
split_block_cursor(nir_cursor cursor,
|
|
nir_block **_before, nir_block **_after)
|
|
{
|
|
nir_block *before, *after;
|
|
switch (cursor.option) {
|
|
case nir_cursor_before_block:
|
|
after = cursor.block;
|
|
before = split_block_beginning(cursor.block);
|
|
break;
|
|
|
|
case nir_cursor_after_block:
|
|
before = cursor.block;
|
|
after = split_block_end(cursor.block);
|
|
break;
|
|
|
|
case nir_cursor_before_instr:
|
|
after = cursor.instr->block;
|
|
before = split_block_before_instr(cursor.instr);
|
|
break;
|
|
|
|
case nir_cursor_after_instr:
|
|
/* We lower this to split_block_before_instr() so that we can keep the
|
|
* after-a-jump-instr case contained to split_block_end().
|
|
*/
|
|
if (nir_instr_is_last(cursor.instr)) {
|
|
before = cursor.instr->block;
|
|
after = split_block_end(cursor.instr->block);
|
|
} else {
|
|
after = cursor.instr->block;
|
|
before = split_block_before_instr(nir_instr_next(cursor.instr));
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (_before)
|
|
*_before = before;
|
|
if (_after)
|
|
*_after = after;
|
|
}
|
|
|
|
/**
|
|
* Inserts a non-basic block between two basic blocks and links them together.
|
|
*/
|
|
|
|
static void
|
|
insert_non_block(nir_block *before, nir_cf_node *node, nir_block *after)
|
|
{
|
|
node->parent = before->cf_node.parent;
|
|
exec_node_insert_after(&before->cf_node.node, &node->node);
|
|
link_block_to_non_block(before, node);
|
|
link_non_block_to_block(node, after);
|
|
}
|
|
|
|
/* walk up the control flow tree to find the innermost enclosed loop */
|
|
static nir_loop *
|
|
nearest_loop(nir_cf_node *node)
|
|
{
|
|
while (node->type != nir_cf_node_loop) {
|
|
node = node->parent;
|
|
}
|
|
|
|
return nir_cf_node_as_loop(node);
|
|
}
|
|
|
|
/*
|
|
* update the CFG after a jump instruction has been added to the end of a block
|
|
*/
|
|
|
|
void
|
|
nir_handle_add_jump(nir_block *block)
|
|
{
|
|
nir_instr *instr = nir_block_last_instr(block);
|
|
nir_jump_instr *jump_instr = nir_instr_as_jump(instr);
|
|
|
|
unlink_block_successors(block);
|
|
|
|
nir_function_impl *impl = nir_cf_node_get_function(&block->cf_node);
|
|
nir_metadata_preserve(impl, nir_metadata_none);
|
|
|
|
if (jump_instr->type == nir_jump_break ||
|
|
jump_instr->type == nir_jump_continue) {
|
|
nir_loop *loop = nearest_loop(&block->cf_node);
|
|
|
|
if (jump_instr->type == nir_jump_continue) {
|
|
nir_cf_node *first_node = nir_loop_first_cf_node(loop);
|
|
assert(first_node->type == nir_cf_node_block);
|
|
nir_block *first_block = nir_cf_node_as_block(first_node);
|
|
link_blocks(block, first_block, NULL);
|
|
} else {
|
|
nir_cf_node *after = nir_cf_node_next(&loop->cf_node);
|
|
assert(after->type == nir_cf_node_block);
|
|
nir_block *after_block = nir_cf_node_as_block(after);
|
|
link_blocks(block, after_block, NULL);
|
|
|
|
/* If we inserted a fake link, remove it */
|
|
nir_cf_node *last = nir_loop_last_cf_node(loop);
|
|
assert(last->type == nir_cf_node_block);
|
|
nir_block *last_block = nir_cf_node_as_block(last);
|
|
if (last_block->successors[1] != NULL)
|
|
unlink_blocks(last_block, after_block);
|
|
}
|
|
} else {
|
|
assert(jump_instr->type == nir_jump_return);
|
|
link_blocks(block, impl->end_block, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
remove_phi_src(nir_block *block, nir_block *pred)
|
|
{
|
|
nir_foreach_instr(block, instr) {
|
|
if (instr->type != nir_instr_type_phi)
|
|
break;
|
|
|
|
nir_phi_instr *phi = nir_instr_as_phi(instr);
|
|
nir_foreach_phi_src_safe(phi, src) {
|
|
if (src->pred == pred) {
|
|
list_del(&src->src.use_link);
|
|
exec_node_remove(&src->node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Removes the successor of a block with a jump, and inserts a fake edge for
|
|
* infinite loops. Note that the jump to be eliminated may be free-floating.
|
|
*/
|
|
|
|
static void
|
|
unlink_jump(nir_block *block, nir_jump_type type, bool add_normal_successors)
|
|
{
|
|
nir_block *next = block->successors[0];
|
|
|
|
if (block->successors[0])
|
|
remove_phi_src(block->successors[0], block);
|
|
if (block->successors[1])
|
|
remove_phi_src(block->successors[1], block);
|
|
|
|
unlink_block_successors(block);
|
|
if (add_normal_successors)
|
|
block_add_normal_succs(block);
|
|
|
|
/* If we've just removed a break, and the block we were jumping to (after
|
|
* the loop) now has zero predecessors, we've created a new infinite loop.
|
|
*
|
|
* NIR doesn't allow blocks (other than the start block) to have zero
|
|
* predecessors. In particular, dominance assumes all blocks are reachable.
|
|
* So, we insert a "fake link" by making successors[1] point after the loop.
|
|
*
|
|
* Note that we have to do this after unlinking/recreating the block's
|
|
* successors. If we removed a "break" at the end of the loop, then
|
|
* block == last_block, so block->successors[0] would already be "next",
|
|
* and adding a fake link would create two identical successors. Doing
|
|
* this afterward works, as we'll have changed block->successors[0] to
|
|
* be the top of the loop.
|
|
*/
|
|
if (type == nir_jump_break && next->predecessors->entries == 0) {
|
|
nir_loop *loop =
|
|
nir_cf_node_as_loop(nir_cf_node_prev(&next->cf_node));
|
|
|
|
/* insert fake link */
|
|
nir_cf_node *last = nir_loop_last_cf_node(loop);
|
|
assert(last->type == nir_cf_node_block);
|
|
nir_block *last_block = nir_cf_node_as_block(last);
|
|
|
|
last_block->successors[1] = next;
|
|
block_add_pred(next, last_block);
|
|
}
|
|
}
|
|
|
|
void
|
|
nir_handle_remove_jump(nir_block *block, nir_jump_type type)
|
|
{
|
|
unlink_jump(block, type, true);
|
|
|
|
nir_function_impl *impl = nir_cf_node_get_function(&block->cf_node);
|
|
nir_metadata_preserve(impl, nir_metadata_none);
|
|
}
|
|
|
|
static void
|
|
update_if_uses(nir_cf_node *node)
|
|
{
|
|
if (node->type != nir_cf_node_if)
|
|
return;
|
|
|
|
nir_if *if_stmt = nir_cf_node_as_if(node);
|
|
|
|
if_stmt->condition.parent_if = if_stmt;
|
|
if (if_stmt->condition.is_ssa) {
|
|
list_addtail(&if_stmt->condition.use_link,
|
|
&if_stmt->condition.ssa->if_uses);
|
|
} else {
|
|
list_addtail(&if_stmt->condition.use_link,
|
|
&if_stmt->condition.reg.reg->if_uses);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stitch two basic blocks together into one. The aggregate must have the same
|
|
* predecessors as the first and the same successors as the second.
|
|
*/
|
|
|
|
static void
|
|
stitch_blocks(nir_block *before, nir_block *after)
|
|
{
|
|
/*
|
|
* We move after into before, so we have to deal with up to 2 successors vs.
|
|
* possibly a large number of predecessors.
|
|
*
|
|
* TODO: special case when before is empty and after isn't?
|
|
*/
|
|
|
|
if (block_ends_in_jump(before)) {
|
|
assert(exec_list_is_empty(&after->instr_list));
|
|
if (after->successors[0])
|
|
remove_phi_src(after->successors[0], after);
|
|
if (after->successors[1])
|
|
remove_phi_src(after->successors[1], after);
|
|
unlink_block_successors(after);
|
|
exec_node_remove(&after->cf_node.node);
|
|
} else {
|
|
move_successors(after, before);
|
|
|
|
foreach_list_typed(nir_instr, instr, node, &after->instr_list) {
|
|
instr->block = before;
|
|
}
|
|
|
|
exec_list_append(&before->instr_list, &after->instr_list);
|
|
exec_node_remove(&after->cf_node.node);
|
|
}
|
|
}
|
|
|
|
void
|
|
nir_cf_node_insert(nir_cursor cursor, nir_cf_node *node)
|
|
{
|
|
nir_block *before, *after;
|
|
|
|
split_block_cursor(cursor, &before, &after);
|
|
|
|
if (node->type == nir_cf_node_block) {
|
|
nir_block *block = nir_cf_node_as_block(node);
|
|
exec_node_insert_after(&before->cf_node.node, &block->cf_node.node);
|
|
block->cf_node.parent = before->cf_node.parent;
|
|
/* stitch_blocks() assumes that any block that ends with a jump has
|
|
* already been setup with the correct successors, so we need to set
|
|
* up jumps here as the block is being inserted.
|
|
*/
|
|
if (block_ends_in_jump(block))
|
|
nir_handle_add_jump(block);
|
|
|
|
stitch_blocks(block, after);
|
|
stitch_blocks(before, block);
|
|
} else {
|
|
update_if_uses(node);
|
|
insert_non_block(before, node, after);
|
|
}
|
|
}
|
|
|
|
static bool
|
|
replace_ssa_def_uses(nir_ssa_def *def, void *void_impl)
|
|
{
|
|
nir_function_impl *impl = void_impl;
|
|
void *mem_ctx = ralloc_parent(impl);
|
|
|
|
nir_ssa_undef_instr *undef =
|
|
nir_ssa_undef_instr_create(mem_ctx, def->num_components);
|
|
nir_instr_insert_before_cf_list(&impl->body, &undef->instr);
|
|
nir_ssa_def_rewrite_uses(def, nir_src_for_ssa(&undef->def));
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
cleanup_cf_node(nir_cf_node *node, nir_function_impl *impl)
|
|
{
|
|
switch (node->type) {
|
|
case nir_cf_node_block: {
|
|
nir_block *block = nir_cf_node_as_block(node);
|
|
/* We need to walk the instructions and clean up defs/uses */
|
|
nir_foreach_instr_safe(block, instr) {
|
|
if (instr->type == nir_instr_type_jump) {
|
|
nir_jump_type jump_type = nir_instr_as_jump(instr)->type;
|
|
unlink_jump(block, jump_type, false);
|
|
} else {
|
|
nir_foreach_ssa_def(instr, replace_ssa_def_uses, impl);
|
|
nir_instr_remove(instr);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case nir_cf_node_if: {
|
|
nir_if *if_stmt = nir_cf_node_as_if(node);
|
|
foreach_list_typed(nir_cf_node, child, node, &if_stmt->then_list)
|
|
cleanup_cf_node(child, impl);
|
|
foreach_list_typed(nir_cf_node, child, node, &if_stmt->else_list)
|
|
cleanup_cf_node(child, impl);
|
|
|
|
list_del(&if_stmt->condition.use_link);
|
|
break;
|
|
}
|
|
|
|
case nir_cf_node_loop: {
|
|
nir_loop *loop = nir_cf_node_as_loop(node);
|
|
foreach_list_typed(nir_cf_node, child, node, &loop->body)
|
|
cleanup_cf_node(child, impl);
|
|
break;
|
|
}
|
|
case nir_cf_node_function: {
|
|
nir_function_impl *impl = nir_cf_node_as_function(node);
|
|
foreach_list_typed(nir_cf_node, child, node, &impl->body)
|
|
cleanup_cf_node(child, impl);
|
|
break;
|
|
}
|
|
default:
|
|
unreachable("Invalid CF node type");
|
|
}
|
|
}
|
|
|
|
void
|
|
nir_cf_extract(nir_cf_list *extracted, nir_cursor begin, nir_cursor end)
|
|
{
|
|
nir_block *block_begin, *block_end, *block_before, *block_after;
|
|
|
|
/* In the case where begin points to an instruction in some basic block and
|
|
* end points to the end of the same basic block, we rely on the fact that
|
|
* splitting on an instruction moves earlier instructions into a new basic
|
|
* block. If the later instructions were moved instead, then the end cursor
|
|
* would be pointing to the same place that begin used to point to, which
|
|
* is obviously not what we want.
|
|
*/
|
|
split_block_cursor(begin, &block_before, &block_begin);
|
|
split_block_cursor(end, &block_end, &block_after);
|
|
|
|
extracted->impl = nir_cf_node_get_function(&block_begin->cf_node);
|
|
exec_list_make_empty(&extracted->list);
|
|
|
|
/* Dominance and other block-related information is toast. */
|
|
nir_metadata_preserve(extracted->impl, nir_metadata_none);
|
|
|
|
nir_cf_node *cf_node = &block_begin->cf_node;
|
|
nir_cf_node *cf_node_end = &block_end->cf_node;
|
|
while (true) {
|
|
nir_cf_node *next = nir_cf_node_next(cf_node);
|
|
|
|
exec_node_remove(&cf_node->node);
|
|
cf_node->parent = NULL;
|
|
exec_list_push_tail(&extracted->list, &cf_node->node);
|
|
|
|
if (cf_node == cf_node_end)
|
|
break;
|
|
|
|
cf_node = next;
|
|
}
|
|
|
|
stitch_blocks(block_before, block_after);
|
|
}
|
|
|
|
void
|
|
nir_cf_reinsert(nir_cf_list *cf_list, nir_cursor cursor)
|
|
{
|
|
nir_block *before, *after;
|
|
|
|
split_block_cursor(cursor, &before, &after);
|
|
|
|
foreach_list_typed_safe(nir_cf_node, node, node, &cf_list->list) {
|
|
exec_node_remove(&node->node);
|
|
node->parent = before->cf_node.parent;
|
|
exec_node_insert_node_before(&after->cf_node.node, &node->node);
|
|
}
|
|
|
|
stitch_blocks(before,
|
|
nir_cf_node_as_block(nir_cf_node_next(&before->cf_node)));
|
|
stitch_blocks(nir_cf_node_as_block(nir_cf_node_prev(&after->cf_node)),
|
|
after);
|
|
}
|
|
|
|
void
|
|
nir_cf_delete(nir_cf_list *cf_list)
|
|
{
|
|
foreach_list_typed(nir_cf_node, node, node, &cf_list->list) {
|
|
cleanup_cf_node(node, cf_list->impl);
|
|
}
|
|
}
|