struct divergence_state {
const nir_divergence_options options;
const gl_shader_stage stage;
+
+ /** current control flow state */
+ /* True if some loop-active invocations might take a different control-flow path.
+ * A divergent break does not cause subsequent control-flow to be considered
+ * divergent because those invocations are no longer active in the loop.
+ * For a divergent if, both sides are considered divergent flow because
+ * the other side is still loop-active. */
+ bool divergent_loop_cf;
+ /* True if a divergent continue happened since the loop header */
+ bool divergent_loop_continue;
+ /* True if a divergent break happened since the loop header */
+ bool divergent_loop_break;
+
+ /* True if we visit the block for the fist time */
+ bool first_visit;
};
static bool
else
is_divergent = true;
break;
+ case nir_intrinsic_load_per_vertex_input:
+ is_divergent = instr->src[0].ssa->divergent ||
+ instr->src[1].ssa->divergent;
+ if (stage == MESA_SHADER_TESS_CTRL)
+ is_divergent |= !(options & nir_divergence_single_patch_per_tcs_subgroup);
+ if (stage == MESA_SHADER_TESS_EVAL)
+ is_divergent |= !(options & nir_divergence_single_patch_per_tes_subgroup);
+ else
+ is_divergent = true;
+ break;
case nir_intrinsic_load_input_vertex:
is_divergent = instr->src[1].ssa->divergent;
assert(stage == MESA_SHADER_FRAGMENT);
else
is_divergent = true;
break;
+ case nir_intrinsic_load_per_vertex_output:
+ assert(stage == MESA_SHADER_TESS_CTRL);
+ is_divergent = instr->src[0].ssa->divergent ||
+ instr->src[1].ssa->divergent ||
+ !(options & nir_divergence_single_patch_per_tcs_subgroup);
+ break;
case nir_intrinsic_load_layer_id:
case nir_intrinsic_load_front_face:
assert(stage == MESA_SHADER_FRAGMENT);
case nir_intrinsic_load_ssbo:
case nir_intrinsic_load_shared:
case nir_intrinsic_load_global:
+ case nir_intrinsic_load_global_constant:
case nir_intrinsic_load_uniform:
case nir_intrinsic_load_push_constant:
case nir_intrinsic_load_constant:
case nir_intrinsic_interp_deref_at_vertex:
case nir_intrinsic_load_tess_coord:
case nir_intrinsic_load_point_coord:
+ case nir_intrinsic_load_line_coord:
case nir_intrinsic_load_frag_coord:
case nir_intrinsic_load_sample_pos:
case nir_intrinsic_load_vertex_id_zero_base:
case nir_intrinsic_load_vertex_id:
- case nir_intrinsic_load_per_vertex_input:
- case nir_intrinsic_load_per_vertex_output:
case nir_intrinsic_load_instance_id:
case nir_intrinsic_load_invocation_id:
case nir_intrinsic_load_local_invocation_id:
return is_divergent;
}
+static bool
+visit_jump(nir_jump_instr *jump, struct divergence_state *state)
+{
+ switch (jump->type) {
+ case nir_jump_continue:
+ if (state->divergent_loop_continue)
+ return false;
+ if (state->divergent_loop_cf)
+ state->divergent_loop_continue = true;
+ return state->divergent_loop_continue;
+ case nir_jump_break:
+ if (state->divergent_loop_break)
+ return false;
+ if (state->divergent_loop_cf)
+ state->divergent_loop_break = true;
+ return state->divergent_loop_break;
+ case nir_jump_return:
+ unreachable("NIR divergence analysis: Unsupported return instruction.");
+ break;
+ case nir_jump_goto:
+ case nir_jump_goto_if:
+ unreachable("NIR divergence analysis: Unsupported goto_if instruction.");
+ break;
+ }
+ return false;
+}
+
+static bool
+set_ssa_def_not_divergent(nir_ssa_def *def, UNUSED void *_state)
+{
+ def->divergent = false;
+ return true;
+}
+
static bool
visit_block(nir_block *block, struct divergence_state *state)
{
bool has_changed = false;
nir_foreach_instr(instr, block) {
+ /* phis are handled when processing the branches */
+ if (instr->type == nir_instr_type_phi)
+ continue;
+
+ if (state->first_visit)
+ nir_foreach_ssa_def(instr, set_ssa_def_not_divergent, NULL);
+
switch (instr->type) {
case nir_instr_type_alu:
has_changed |= visit_alu(nir_instr_as_alu(instr));
case nir_instr_type_deref:
has_changed |= visit_deref(nir_instr_as_deref(instr), state);
break;
- /* phis are handled when processing the branches */
- case nir_instr_type_phi:
- break;
case nir_instr_type_jump:
+ has_changed |= visit_jump(nir_instr_as_jump(instr), state);
break;
+ case nir_instr_type_phi:
case nir_instr_type_call:
case nir_instr_type_parallel_copy:
unreachable("NIR divergence analysis: Unsupported instruction type.");
phi->dest.ssa.divergent = true;
return true;
}
+
return false;
}
* is divergent or a divergent loop continue condition
* is associated with a different ssa-def. */
static bool
-visit_loop_header_phi(nir_phi_instr *phi, nir_loop *loop)
+visit_loop_header_phi(nir_phi_instr *phi, nir_block *preheader, bool divergent_continue)
{
if (phi->dest.ssa.divergent)
return false;
- nir_cf_node *prev = nir_cf_node_prev(&loop->cf_node);
nir_ssa_def* same = NULL;
- bool all_same = true;
-
- /* first, check if all loop-carried values are from the same ssa-def */
nir_foreach_phi_src(src, phi) {
/* if any source value is divergent, the resulting value is divergent */
if (src->src.ssa->divergent) {
phi->dest.ssa.divergent = true;
return true;
}
- /* skip the loop preheader */
- if (src->pred == nir_cf_node_as_block(prev))
+ /* if this loop is uniform, we're done here */
+ if (!divergent_continue)
continue;
- if (src->src.ssa->parent_instr->type == nir_instr_type_ssa_undef)
- continue;
- if (!same)
- same = src->src.ssa;
- else if (same != src->src.ssa)
- all_same = false;
- }
-
- /* if all loop-carried values are the same, the resulting value is uniform */
- if (all_same)
- return false;
-
- /* check if the loop-carried values come from different ssa-defs
- * and the corresponding condition is divergent. */
- nir_foreach_phi_src(src, phi) {
/* skip the loop preheader */
- if (src->pred == nir_cf_node_as_block(prev))
+ if (src->pred == preheader)
continue;
-
- /* skip the unconditional back-edge */
- if (src->pred == nir_loop_last_block(loop))
- continue;
-
- /* if the value is undef, we don't need to check the condition */
+ /* skip undef values */
if (src->src.ssa->parent_instr->type == nir_instr_type_ssa_undef)
continue;
- nir_cf_node *current = src->pred->cf_node.parent;
- /* check recursively the conditions if any is divergent */
- while (current->type != nir_cf_node_loop) {
- assert (current->type == nir_cf_node_if);
- nir_if *if_node = nir_cf_node_as_if(current);
- if (if_node->condition.ssa->divergent) {
- phi->dest.ssa.divergent = true;
- return true;
- }
- current = current->parent;
+ /* check if all loop-carried values are from the same ssa-def */
+ if (!same)
+ same = src->src.ssa;
+ else if (same != src->src.ssa) {
+ phi->dest.ssa.divergent = true;
+ return true;
}
- assert(current == &loop->cf_node);
}
return false;
* not loop-invariant.
* (note: there should be no phi for loop-invariant variables.) */
static bool
-visit_loop_exit_phi(nir_phi_instr *phi, nir_loop *loop)
+visit_loop_exit_phi(nir_phi_instr *phi, bool divergent_break)
{
if (phi->dest.ssa.divergent)
return false;
- /* Check if any loop exit condition is divergent:
- * That is any break happens under divergent condition or
- * a break is preceeded by a divergent continue
- */
+ if (divergent_break) {
+ phi->dest.ssa.divergent = true;
+ return true;
+ }
+
+ /* if any source value is divergent, the resulting value is divergent */
nir_foreach_phi_src(src, phi) {
- /* if any source value is divergent, the resulting value is divergent */
if (src->src.ssa->divergent) {
phi->dest.ssa.divergent = true;
return true;
}
-
- nir_cf_node *current = src->pred->cf_node.parent;
-
- /* check recursively the conditions if any is divergent */
- while (current->type != nir_cf_node_loop) {
- assert(current->type == nir_cf_node_if);
- nir_if *if_node = nir_cf_node_as_if(current);
- if (if_node->condition.ssa->divergent) {
- phi->dest.ssa.divergent = true;
- return true;
- }
- current = current->parent;
- }
-
- /* check if any divergent continue happened before the break */
- nir_foreach_block_in_cf_node(block, &loop->cf_node) {
- if (block == src->pred)
- break;
- if (!nir_block_ends_in_jump(block))
- continue;
-
- nir_jump_instr *jump = nir_instr_as_jump(nir_block_last_instr(block));
- if (jump->type != nir_jump_continue)
- continue;
-
- current = block->cf_node.parent;
- bool is_divergent = false;
- while (current != &loop->cf_node) {
- /* the continue belongs to an inner loop */
- if (current->type == nir_cf_node_loop) {
- is_divergent = false;
- break;
- }
- assert(current->type == nir_cf_node_if);
- nir_if *if_node = nir_cf_node_as_if(current);
- is_divergent |= if_node->condition.ssa->divergent;
- current = current->parent;
- }
-
- if (is_divergent) {
- phi->dest.ssa.divergent = true;
- return true;
- }
- }
}
+
return false;
}
static bool
visit_if(nir_if *if_stmt, struct divergence_state *state)
{
- bool progress = visit_cf_list(&if_stmt->then_list, state) |
- visit_cf_list(&if_stmt->else_list, state);
+ bool progress = false;
+
+ struct divergence_state then_state = *state;
+ then_state.divergent_loop_cf |= if_stmt->condition.ssa->divergent;
+ progress |= visit_cf_list(&if_stmt->then_list, &then_state);
+
+ struct divergence_state else_state = *state;
+ else_state.divergent_loop_cf |= if_stmt->condition.ssa->divergent;
+ progress |= visit_cf_list(&if_stmt->else_list, &else_state);
/* handle phis after the IF */
nir_foreach_instr(instr, nir_cf_node_cf_tree_next(&if_stmt->cf_node)) {
if (instr->type != nir_instr_type_phi)
break;
- progress |= visit_if_merge_phi(nir_instr_as_phi(instr), if_stmt->condition.ssa->divergent);
+
+ if (state->first_visit)
+ nir_instr_as_phi(instr)->dest.ssa.divergent = false;
+ progress |= visit_if_merge_phi(nir_instr_as_phi(instr),
+ if_stmt->condition.ssa->divergent);
}
+ /* join loop divergence information from both branch legs */
+ state->divergent_loop_continue |= then_state.divergent_loop_continue ||
+ else_state.divergent_loop_continue;
+ state->divergent_loop_break |= then_state.divergent_loop_break ||
+ else_state.divergent_loop_break;
+
+ /* A divergent continue makes succeeding loop CF divergent:
+ * not all loop-active invocations participate in the remaining loop-body
+ * which means that a following break might be taken by some invocations, only */
+ state->divergent_loop_cf |= state->divergent_loop_continue;
+
return progress;
}
visit_loop(nir_loop *loop, struct divergence_state *state)
{
bool progress = false;
+ nir_block *loop_header = nir_loop_first_block(loop);
+ nir_block *loop_preheader = nir_block_cf_tree_prev(loop_header);
- /* handle loop header phis first */
- nir_foreach_instr(instr, nir_loop_first_block(loop)) {
+ /* handle loop header phis first: we have no knowledge yet about
+ * the loop's control flow or any loop-carried sources. */
+ nir_foreach_instr(instr, loop_header) {
if (instr->type != nir_instr_type_phi)
break;
- progress |= visit_loop_header_phi(nir_instr_as_phi(instr), loop);
- }
- bool repeat = true;
- while (repeat) {
- /* process loop body */
- repeat = visit_cf_list(&loop->body, state);
-
- if (repeat) {
- repeat = false;
- /* revisit loop header phis to see if something has changed */
- nir_foreach_instr(instr, nir_loop_first_block(loop)) {
- if (instr->type != nir_instr_type_phi)
- break;
- repeat |= visit_loop_header_phi(nir_instr_as_phi(instr), loop);
+ nir_phi_instr *phi = nir_instr_as_phi(instr);
+ if (!state->first_visit && phi->dest.ssa.divergent)
+ continue;
+
+ nir_foreach_phi_src(src, phi) {
+ if (src->pred == loop_preheader) {
+ phi->dest.ssa.divergent = src->src.ssa->divergent;
+ break;
}
- progress = true;
}
+ progress |= phi->dest.ssa.divergent;
}
+ /* setup loop state */
+ struct divergence_state loop_state = *state;
+ loop_state.divergent_loop_cf = false;
+ loop_state.divergent_loop_continue = false;
+ loop_state.divergent_loop_break = false;
+
+ /* process loop body until no further changes are made */
+ bool repeat;
+ do {
+ progress |= visit_cf_list(&loop->body, &loop_state);
+ repeat = false;
+
+ /* revisit loop header phis to see if something has changed */
+ nir_foreach_instr(instr, loop_header) {
+ if (instr->type != nir_instr_type_phi)
+ break;
+
+ repeat |= visit_loop_header_phi(nir_instr_as_phi(instr),
+ loop_preheader,
+ loop_state.divergent_loop_continue);
+ }
+
+ loop_state.divergent_loop_cf = false;
+ loop_state.first_visit = false;
+ } while (repeat);
+
/* handle phis after the loop */
nir_foreach_instr(instr, nir_cf_node_cf_tree_next(&loop->cf_node)) {
if (instr->type != nir_instr_type_phi)
break;
- progress |= visit_loop_exit_phi(nir_instr_as_phi(instr), loop);
+
+ if (state->first_visit)
+ nir_instr_as_phi(instr)->dest.ssa.divergent = false;
+ progress |= visit_loop_exit_phi(nir_instr_as_phi(instr),
+ loop_state.divergent_loop_break);
}
return progress;
return has_changed;
}
-static bool
-set_ssa_def_not_divergent(nir_ssa_def *def, UNUSED void *_state)
-{
- def->divergent = false;
- return true;
-}
-
void
nir_divergence_analysis(nir_shader *shader, nir_divergence_options options)
{
- nir_function_impl *impl = nir_shader_get_entrypoint(shader);
-
- /* Set all SSA defs to non-divergent to start off */
- nir_foreach_block(block, impl) {
- nir_foreach_instr(instr, block)
- nir_foreach_ssa_def(instr, set_ssa_def_not_divergent, NULL);
- }
-
struct divergence_state state = {
.options = options,
.stage = shader->info.stage,
+ .divergent_loop_cf = false,
+ .divergent_loop_continue = false,
+ .divergent_loop_break = false,
+ .first_visit = true,
};
- visit_cf_list(&impl->body, &state);
+ visit_cf_list(&nir_shader_get_entrypoint(shader)->body, &state);
}