spirv: parse unstructured CFG
v2 (Boris Brezillon): handle functions with return values
v3: call structurizer
v4: entire rewrite
v5: fix handling of already visited default branches
v2 (Jason Ekstrand): Stop walking hash tables
Signed-off-by: Karol Herbst <kherbst@redhat.com>
Reviewed-by: Jason Ekstrand <jason@jlekstrand.net>
Tested-by: Jesse Natalie <jenatali@microsoft.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/2401>
diff --git a/src/compiler/nir/nir.c b/src/compiler/nir/nir.c
index d1db749..d8ff265 100644
--- a/src/compiler/nir/nir.c
+++ b/src/compiler/nir/nir.c
@@ -732,8 +732,6 @@
{
switch (cursor.option) {
case nir_cursor_before_block:
- assert(nir_cf_node_prev(&cursor.block->cf_node) == NULL ||
- nir_cf_node_prev(&cursor.block->cf_node)->type != nir_cf_node_block);
if (exec_list_is_empty(&cursor.block->instr_list)) {
/* Empty block. After is as good as before. */
cursor.option = nir_cursor_after_block;
diff --git a/src/compiler/spirv/spirv_to_nir.c b/src/compiler/spirv/spirv_to_nir.c
index 2ef9b8c..747d1a0 100644
--- a/src/compiler/spirv/spirv_to_nir.c
+++ b/src/compiler/spirv/spirv_to_nir.c
@@ -5594,6 +5594,9 @@
if (entry_point->num_params && b->shader->info.stage == MESA_SHADER_KERNEL)
entry_point = vtn_emit_kernel_entry_point_wrapper(b, entry_point);
+ /* structurize the CFG */
+ nir_lower_goto_ifs(b->shader);
+
entry_point->is_entrypoint = true;
/* When multiple shader stages exist in the same SPIR-V module, we
diff --git a/src/compiler/spirv/vtn_cfg.c b/src/compiler/spirv/vtn_cfg.c
index 023b2de..9dc2a84 100644
--- a/src/compiler/spirv/vtn_cfg.c
+++ b/src/compiler/spirv/vtn_cfg.c
@@ -22,6 +22,7 @@
*/
#include "vtn_private.h"
+#include "spirv_info.h"
#include "nir/nir_vla.h"
static struct vtn_block *
@@ -817,6 +818,9 @@
vtn_foreach_instruction(b, words, end,
vtn_cfg_handle_prepass_instruction);
+ if (b->shader->info.stage == MESA_SHADER_KERNEL)
+ return;
+
vtn_foreach_cf_node(func_node, &b->functions) {
struct vtn_function *func = vtn_cf_node_as_function(func_node);
@@ -1186,6 +1190,141 @@
}
}
+static struct nir_block *
+vtn_new_unstructured_block(struct vtn_builder *b, struct vtn_function *func)
+{
+ struct nir_block *n = nir_block_create(b->shader);
+ exec_list_push_tail(&func->impl->body, &n->cf_node.node);
+ n->cf_node.parent = &func->impl->cf_node;
+ return n;
+}
+
+static void
+vtn_add_unstructured_block(struct vtn_builder *b,
+ struct vtn_function *func,
+ struct list_head *work_list,
+ struct vtn_block *block)
+{
+ if (!block->block) {
+ block->block = vtn_new_unstructured_block(b, func);
+ list_addtail(&block->node.link, work_list);
+ }
+}
+
+static void
+vtn_emit_cf_func_unstructured(struct vtn_builder *b, struct vtn_function *func,
+ vtn_instruction_handler handler)
+{
+ struct list_head work_list;
+ list_inithead(&work_list);
+
+ func->start_block->block = nir_start_block(func->impl);
+ list_addtail(&func->start_block->node.link, &work_list);
+ while (!list_is_empty(&work_list)) {
+ struct vtn_block *block =
+ list_first_entry(&work_list, struct vtn_block, node.link);
+ list_del(&block->node.link);
+
+ vtn_assert(block->block);
+
+ const uint32_t *block_start = block->label;
+ const uint32_t *block_end = block->branch;
+
+ b->nb.cursor = nir_after_block(block->block);
+ block_start = vtn_foreach_instruction(b, block_start, block_end,
+ vtn_handle_phis_first_pass);
+ vtn_foreach_instruction(b, block_start, block_end, handler);
+ block->end_nop = nir_intrinsic_instr_create(b->nb.shader,
+ nir_intrinsic_nop);
+ nir_builder_instr_insert(&b->nb, &block->end_nop->instr);
+
+ SpvOp op = *block_end & SpvOpCodeMask;
+ switch (op) {
+ case SpvOpBranch: {
+ struct vtn_block *branch_block = vtn_block(b, block->branch[1]);
+ vtn_add_unstructured_block(b, func, &work_list, branch_block);
+ nir_goto(&b->nb, branch_block->block);
+ break;
+ }
+
+ case SpvOpBranchConditional: {
+ nir_ssa_def *cond = vtn_ssa_value(b, block->branch[1])->def;
+ struct vtn_block *then_block = vtn_block(b, block->branch[2]);
+ struct vtn_block *else_block = vtn_block(b, block->branch[3]);
+
+ vtn_add_unstructured_block(b, func, &work_list, then_block);
+ if (then_block == else_block) {
+ nir_goto(&b->nb, then_block->block);
+ } else {
+ vtn_add_unstructured_block(b, func, &work_list, else_block);
+ nir_goto_if(&b->nb, then_block->block, nir_src_for_ssa(cond),
+ else_block->block);
+ }
+
+ break;
+ }
+
+ case SpvOpSwitch: {
+ struct list_head cases;
+ list_inithead(&cases);
+ vtn_parse_switch(b, NULL, block->branch, &cases);
+
+ nir_ssa_def *sel = vtn_get_nir_ssa(b, block->branch[1]);
+
+ struct vtn_case *def = NULL;
+ vtn_foreach_cf_node(case_node, &cases) {
+ struct vtn_case *cse = vtn_cf_node_as_case(case_node);
+ if (cse->is_default) {
+ assert(def == NULL);
+ def = cse;
+ continue;
+ }
+
+ nir_ssa_def *cond = nir_imm_false(&b->nb);
+ util_dynarray_foreach(&cse->values, uint64_t, val) {
+ nir_ssa_def *imm = nir_imm_intN_t(&b->nb, *val, sel->bit_size);
+ cond = nir_ior(&b->nb, cond, nir_ieq(&b->nb, sel, imm));
+ }
+
+ /* block for the next check */
+ nir_block *e = vtn_new_unstructured_block(b, func);
+ vtn_add_unstructured_block(b, func, &work_list, cse->block);
+
+ /* add branching */
+ nir_goto_if(&b->nb, cse->block->block, nir_src_for_ssa(cond), e);
+ b->nb.cursor = nir_after_block(e);
+ }
+
+ vtn_assert(def != NULL);
+ vtn_add_unstructured_block(b, func, &work_list, def->block);
+
+ /* now that all cases are handled, branch into the default block */
+ nir_goto(&b->nb, def->block->block);
+ break;
+ }
+
+ case SpvOpKill: {
+ nir_intrinsic_instr *discard =
+ nir_intrinsic_instr_create(b->nb.shader, nir_intrinsic_discard);
+ nir_builder_instr_insert(&b->nb, &discard->instr);
+ nir_goto(&b->nb, b->func->impl->end_block);
+ break;
+ }
+
+ case SpvOpUnreachable:
+ case SpvOpReturn:
+ case SpvOpReturnValue: {
+ vtn_emit_ret_store(b, block);
+ nir_goto(&b->nb, b->func->impl->end_block);
+ break;
+ }
+
+ default:
+ vtn_fail("Unhandled opcode %s", spirv_op_to_string(op));
+ }
+ }
+}
+
void
vtn_function_emit(struct vtn_builder *b, struct vtn_function *func,
vtn_instruction_handler instruction_handler)
@@ -1197,7 +1336,13 @@
b->has_loop_continue = false;
b->phi_table = _mesa_pointer_hash_table_create(b);
- vtn_emit_cf_list_structured(b, &func->body, NULL, NULL, instruction_handler);
+ if (b->shader->info.stage == MESA_SHADER_KERNEL) {
+ b->func->impl->structured = false;
+ vtn_emit_cf_func_unstructured(b, func, instruction_handler);
+ } else {
+ vtn_emit_cf_list_structured(b, &func->body, NULL, NULL,
+ instruction_handler);
+ }
vtn_foreach_instruction(b, func->start_block->label, func->end,
vtn_handle_phi_second_pass);
diff --git a/src/compiler/spirv/vtn_private.h b/src/compiler/spirv/vtn_private.h
index 8ee4f7b..6fc4f2e 100644
--- a/src/compiler/spirv/vtn_private.h
+++ b/src/compiler/spirv/vtn_private.h
@@ -242,6 +242,9 @@
/** Every block ends in a nop intrinsic so that we can find it again */
nir_intrinsic_instr *end_nop;
+
+ /** attached nir_block */
+ struct nir_block *block;
};
struct vtn_function {