/* * Copyright © 2015-2023 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. */ #include "vtn_private.h" #include "spirv_info.h" #include "util/u_math.h" /* Handle SPIR-V structured control flow, mapping SPIR-V constructs into * equivalent NIR constructs. * * Because SPIR-V can represent more complex control flow than NIR, some * constructs are mapped into a combination of nir_if and nir_loop nodes. For * example, an selection construct with an "if-break" (an early branch into * the end of the construct) will be mapped into NIR as a loop (to allow the * break) with a nested if (to handle the actual selection). * * Note that using NIR loops this way requires us to propagate breaks and * continues that are meant to outer constructs when a nir_loop is used for a * SPIR-V construct other than Loop. * * The process of identifying and ordering the blocks before the NIR * translation is similar to what's done in Tint, using the "reverse * structured post-order traversal". See also the file comments * src/reader/spirv/function.cc in the Tint repository. */ enum vtn_construct_type { /* Not formally a SPIR-V construct but used to represent the entire * function. */ vtn_construct_type_function, /* Selection construct uses a nir_if and optionally a nir_loop to handle * if-breaks. */ vtn_construct_type_selection, /* Loop construct uses a nir_loop and optionally a nir_if to handle an * OpBranchConditional as part of the head of the loop. */ vtn_construct_type_loop, /* Continue construct maps to the NIR continue construct of the corresponding * loop. For convenience, unlike in SPIR-V, the parent of this construct is * always the loop construct. Continue construct is omitted for single-block * loops. */ vtn_construct_type_continue, /* Switch construct is not directly mapped into any NIR structure, the work * is handled by the case constructs. It does keep a nir_variable for * handling case fallback logic. */ vtn_construct_type_switch, /* Case construct uses a nir_if and optionally a nir_loop to handle early * breaks. Note switch_breaks are handled by each case. */ vtn_construct_type_case, }; static const char * vtn_construct_type_to_string(enum vtn_construct_type t) { #define CASE(typ) case vtn_construct_type_##typ: return #typ switch (t) { CASE(function); CASE(selection); CASE(loop); CASE(continue); CASE(switch); CASE(case); } #undef CASE unreachable("invalid construct type"); return ""; } struct vtn_construct { enum vtn_construct_type type; bool needs_nloop; bool needs_break_propagation; bool needs_continue_propagation; bool needs_fallthrough; struct vtn_construct *parent; struct vtn_construct *innermost_loop; struct vtn_construct *innermost_switch; struct vtn_construct *innermost_case; unsigned start_pos; unsigned end_pos; /* Usually the same as end_pos, but may be different in case of an "early * merge" after divergence caused by an OpBranchConditional. This can * happen in selection and loop constructs. */ unsigned merge_pos; /* Valid when not zero, indicates the block that starts the then and else * paths in a condition. This may be used by selection constructs. */ unsigned then_pos; unsigned else_pos; /* Indicates where the continue block is, marking the end of the body of * the loop. Note the block ordering will always give us first the loop * body blocks then the continue block. Used by loop construct. */ unsigned continue_pos; /* For the list of all constructs in vtn_function. */ struct list_head link; /* NIR nodes that are associated with this construct. See * vtn_construct_type for an overview. */ nir_loop *nloop; nir_if *nif; /* This variable will be set by an inner construct to indicate that a break * is necessary. We need to use variables here for situations when the * inner construct has a loop of its own for other reasons. */ nir_variable *break_var; /* Same logic but for continue. */ nir_variable *continue_var; /* This is used by each case to force entering in the case regardless of * the condition. We always set it when handling a branch that is a * switch_break or a switch_fallthrough. */ nir_variable *fallthrough_var; unsigned index; }; enum vtn_branch_type { vtn_branch_type_none, vtn_branch_type_forward, vtn_branch_type_if_break, vtn_branch_type_switch_break, vtn_branch_type_switch_fallthrough, vtn_branch_type_loop_break, vtn_branch_type_loop_continue, vtn_branch_type_loop_back_edge, vtn_branch_type_discard, vtn_branch_type_terminate_invocation, vtn_branch_type_ignore_intersection, vtn_branch_type_terminate_ray, vtn_branch_type_emit_mesh_tasks, vtn_branch_type_return, }; static const char * vtn_branch_type_to_string(enum vtn_branch_type t) { #define CASE(typ) case vtn_branch_type_##typ: return #typ switch (t) { CASE(none); CASE(forward); CASE(if_break); CASE(switch_break); CASE(switch_fallthrough); CASE(loop_break); CASE(loop_continue); CASE(loop_back_edge); CASE(discard); CASE(terminate_invocation); CASE(ignore_intersection); CASE(terminate_ray); CASE(emit_mesh_tasks); CASE(return); } #undef CASE unreachable("unknown branch type"); return ""; } struct vtn_successor { struct vtn_block *block; enum vtn_branch_type branch_type; }; static bool vtn_is_single_block_loop(const struct vtn_construct *c) { return c->type == vtn_construct_type_loop && c->start_pos == c->continue_pos; } static struct vtn_construct * vtn_find_innermost(enum vtn_construct_type type, struct vtn_construct *c) { while (c && c->type != type) c = c->parent; return c; } static void print_ordered_blocks(const struct vtn_function *func) { for (unsigned i = 0; i < func->ordered_blocks_count; i++) { struct vtn_block *block = func->ordered_blocks[i]; printf("[id=%-6u] %4u", block->label[1], block->pos); if (block->successors_count > 0) { printf(" ->"); for (unsigned j = 0; j < block->successors_count; j++) { printf(" "); if (block->successors[j].block) printf("%u/", block->successors[j].block->pos); printf("%s", vtn_branch_type_to_string(block->successors[j].branch_type)); } } if (!block->visited) printf(" NOT VISITED"); printf("\n"); } } static struct vtn_case * vtn_find_fallthrough_target(struct vtn_builder *b, const uint32_t *switch_merge, struct vtn_block *source_block, struct vtn_block *block) { if (block->visited) return NULL; if (block->label[1] == switch_merge[1]) return NULL; /* Don't consider the initial source block a fallthrough target of itself. */ if (block->switch_case && block != source_block) return block->switch_case; if (block->merge) return vtn_find_fallthrough_target(b, switch_merge, source_block, vtn_block(b, block->merge[1])); const uint32_t *branch = block->branch; vtn_assert(branch); switch (branch[0] & SpvOpCodeMask) { case SpvOpBranch: return vtn_find_fallthrough_target(b, switch_merge, source_block, vtn_block(b, branch[1])); case SpvOpBranchConditional: { struct vtn_case *target = vtn_find_fallthrough_target(b, switch_merge, source_block, vtn_block(b, branch[2])); if (!target) target = vtn_find_fallthrough_target(b, switch_merge, source_block, vtn_block(b, branch[3])); return target; } default: return NULL; } } static void structured_post_order_traversal(struct vtn_builder *b, struct vtn_block *block) { if (block->visited) return; block->visited = true; if (block->merge) { structured_post_order_traversal(b, vtn_block(b, block->merge[1])); SpvOp merge_op = block->merge[0] & SpvOpCodeMask; if (merge_op == SpvOpLoopMerge) { struct vtn_block *continue_block = vtn_block(b, block->merge[2]); structured_post_order_traversal(b, continue_block); } } const uint32_t *branch = block->branch; vtn_assert(branch); switch (branch[0] & SpvOpCodeMask) { case SpvOpBranch: block->successors_count = 1; block->successors = vtn_zalloc(b, struct vtn_successor); block->successors[0].block = vtn_block(b, branch[1]); structured_post_order_traversal(b, block->successors[0].block); break; case SpvOpBranchConditional: block->successors_count = 2; block->successors = vtn_zalloc_array(b, struct vtn_successor, 2); block->successors[0].block = vtn_block(b, branch[2]); block->successors[1].block = vtn_block(b, branch[3]); /* The result of the traversal will be reversed, so to provide a * more natural order, with THEN blocks appearing before ELSE blocks, * we need to traverse them in the reversed order. */ int order[] = { 1, 0 }; /* There's a catch when traversing case fallthroughs: we want to avoid * walking part of a case construct, then the fallthrough -- possibly * visiting another entire case construct, and back to the other part * of that original case construct. So if the THEN path is a fallthrough, * swap the visit order. */ if (block->successors[0].block->switch_case) { order[0] = !order[0]; order[1] = !order[1]; } structured_post_order_traversal(b, block->successors[order[0]].block); structured_post_order_traversal(b, block->successors[order[1]].block); break; case SpvOpSwitch: { /* TODO: Save this to use during Switch construct creation. */ struct list_head cases; list_inithead(&cases); vtn_parse_switch(b, block->branch, &cases); block->successors_count = list_length(&cases); block->successors = vtn_zalloc_array(b, struct vtn_successor, block->successors_count); /* The 'Rules for Structured Control-flow constructs' already guarantee * that the labels of the targets are ordered in a way that if * there is a fallthrough, they will appear consecutively. The only * exception is Default, which is always the first in the list. * * Because we are doing a DFS from the end of the cases, the * traversal already handle a Case falling through Default. * * The scenario that needs fixing is when no case falls to Default, but * Default falls to another case. For that scenario we move the Default * right before the case it falls to. */ struct vtn_case *default_case = list_first_entry(&cases, struct vtn_case, link); vtn_assert(default_case && default_case->is_default); struct vtn_case *fall_target = vtn_find_fallthrough_target(b, block->merge, default_case->block, default_case->block); if (fall_target) list_move_to(&default_case->link, &fall_target->link); /* Because the result of the traversal will be reversed, loop backwards * in the case list. */ unsigned i = 0; list_for_each_entry_rev(struct vtn_case, cse, &cases, link) { structured_post_order_traversal(b, cse->block); block->successors[i].block = cse->block; i++; } break; } case SpvOpKill: case SpvOpTerminateInvocation: case SpvOpIgnoreIntersectionKHR: case SpvOpTerminateRayKHR: case SpvOpReturn: case SpvOpReturnValue: case SpvOpEmitMeshTasksEXT: case SpvOpUnreachable: block->successors_count = 1; block->successors = vtn_zalloc(b, struct vtn_successor); break; default: unreachable("invalid branch opcode"); } b->func->ordered_blocks[b->func->ordered_blocks_count++] = block; } static void sort_blocks(struct vtn_builder *b) { struct vtn_block **ordered_blocks = vtn_zalloc_array(b, struct vtn_block *, b->func->block_count); b->func->ordered_blocks = ordered_blocks; structured_post_order_traversal(b, b->func->start_block); /* Reverse it, so that blocks appear before their successors. */ unsigned count = b->func->ordered_blocks_count; for (unsigned i = 0; i < (count / 2); i++) { unsigned j = count - i - 1; struct vtn_block *tmp = ordered_blocks[i]; ordered_blocks[i] = ordered_blocks[j]; ordered_blocks[j] = tmp; } for (unsigned i = 0; i < count; i++) ordered_blocks[i]->pos = i; } static void print_construct(const struct vtn_function *func, const struct vtn_construct *c) { for (const struct vtn_construct *p = c->parent; p; p = p->parent) printf(" "); printf("C%u/%s ", c->index, vtn_construct_type_to_string(c->type)); printf(" %u->%u", c->start_pos, c->end_pos); if (c->merge_pos) printf(" merge=%u", c->merge_pos); if (c->then_pos) printf(" then=%u", c->then_pos); if (c->else_pos) printf(" else=%u", c->else_pos); if (c->needs_nloop) printf(" nloop"); if (c->needs_break_propagation) printf(" break_prop"); if (c->needs_continue_propagation) printf(" continue_prop"); if (c->type == vtn_construct_type_loop) { if (vtn_is_single_block_loop(c)) printf(" single_block_loop"); else printf(" cont=%u", c->continue_pos); } if (c->type == vtn_construct_type_case) { struct vtn_block *block = func->ordered_blocks[c->start_pos]; if (block->switch_case->is_default) { printf(" [default]"); } else { printf(" [values:"); util_dynarray_foreach(&block->switch_case->values, uint64_t, val) printf(" %" PRIu64, *val); printf("]"); } } printf("\n"); } static void print_constructs(struct vtn_function *func) { list_for_each_entry(struct vtn_construct, c, &func->constructs, link) print_construct(func, c); } struct vtn_construct_stack { /* Array of `struct vtn_construct *`. */ struct util_dynarray data; }; static inline void init_construct_stack(struct vtn_construct_stack *stack, void *mem_ctx) { assert(mem_ctx); util_dynarray_init(&stack->data, mem_ctx); } static inline unsigned count_construct_stack(struct vtn_construct_stack *stack) { return util_dynarray_num_elements(&stack->data, struct vtn_construct *); } static inline struct vtn_construct * top_construct(struct vtn_construct_stack *stack) { assert(count_construct_stack(stack) > 0); return util_dynarray_top(&stack->data, struct vtn_construct *); } static inline void pop_construct(struct vtn_construct_stack *stack) { assert(count_construct_stack(stack) > 0); (void)util_dynarray_pop(&stack->data, struct vtn_construct *); } static inline void push_construct(struct vtn_construct_stack *stack, struct vtn_construct *c) { util_dynarray_append(&stack->data, struct vtn_construct *, c); } static int cmp_succ_block_pos(const void *pa, const void *pb) { const struct vtn_successor *sa = pa; const struct vtn_successor *sb = pb; const unsigned a = sa->block->pos; const unsigned b = sb->block->pos; if (a < b) return -1; if (a > b) return 1; return 0; } static void create_constructs(struct vtn_builder *b) { struct vtn_construct *func_construct = vtn_zalloc(b, struct vtn_construct); func_construct->type = vtn_construct_type_function; func_construct->start_pos = 0; func_construct->end_pos = b->func->ordered_blocks_count; for (unsigned i = 0; i < b->func->ordered_blocks_count; i++) { struct vtn_block *block = b->func->ordered_blocks[i]; if (block->merge) { SpvOp merge_op = block->merge[0] & SpvOpCodeMask; SpvOp branch_op = block->branch[0] & SpvOpCodeMask; const unsigned end_pos = vtn_block(b, block->merge[1])->pos; if (merge_op == SpvOpLoopMerge) { struct vtn_construct *loop = vtn_zalloc(b, struct vtn_construct); loop->type = vtn_construct_type_loop; loop->start_pos = block->pos; loop->end_pos = end_pos; loop->parent = block->parent; block->parent = loop; struct vtn_block *continue_block = vtn_block(b, block->merge[2]); loop->continue_pos = continue_block->pos; if (!vtn_is_single_block_loop(loop)) { struct vtn_construct *cont = vtn_zalloc(b, struct vtn_construct); cont->type = vtn_construct_type_continue; cont->parent = loop; cont->start_pos = loop->continue_pos; cont->end_pos = end_pos; cont->parent = loop; continue_block->parent = cont; } /* Not all combinations of OpLoopMerge and OpBranchConditional are valid, * workaround for invalid combinations by injecting an extra selection. * * Old versions of dxil-spirv generated this. */ if (branch_op == SpvOpBranchConditional) { vtn_assert(block->successors_count == 2); const unsigned then_pos = block->successors[0].block ? block->successors[0].block->pos : 0; const unsigned else_pos = block->successors[1].block ? block->successors[1].block->pos : 0; if (then_pos > loop->start_pos && then_pos < loop->continue_pos && else_pos > loop->start_pos && else_pos < loop->continue_pos) { vtn_warn("An OpSelectionMerge instruction is required to precede " "an OpBranchConditional instruction that has different " "True Label and False Label operands where neither are " "declared merge blocks or Continue Targets."); struct vtn_construct *sel = vtn_zalloc(b, struct vtn_construct); sel->type = vtn_construct_type_selection; sel->start_pos = loop->start_pos; sel->end_pos = loop->continue_pos; sel->then_pos = then_pos; sel->else_pos = else_pos; sel->parent = loop; block->parent = sel; } } } else if (branch_op == SpvOpSwitch) { vtn_assert(merge_op == SpvOpSelectionMerge); struct vtn_construct *swtch = vtn_zalloc(b, struct vtn_construct); swtch->type = vtn_construct_type_switch; swtch->start_pos = block->pos; swtch->end_pos = end_pos; swtch->parent = block->parent; block->parent = swtch; struct list_head cases; list_inithead(&cases); vtn_parse_switch(b, block->branch, &cases); vtn_foreach_case_safe(cse, &cases) { if (cse->block->pos < end_pos) { struct vtn_block *case_block = cse->block; struct vtn_construct *c = vtn_zalloc(b, struct vtn_construct); c->type = vtn_construct_type_case; c->parent = swtch; c->start_pos = case_block->pos; /* Upper bound, will be updated right after. */ c->end_pos = swtch->end_pos; vtn_assert(case_block->parent == NULL || case_block->parent == swtch); case_block->parent = c; } else { /* A target in OpSwitch must point either to one of the case * constructs or to the Merge block. No outer break/continue * is allowed. */ vtn_assert(cse->block->pos == end_pos); } list_delinit(&cse->link); } /* Case constructs don't overlap, so they end as the next one * begins. */ qsort(block->successors, block->successors_count, sizeof(struct vtn_successor), cmp_succ_block_pos); for (unsigned succ_idx = 1; succ_idx < block->successors_count; succ_idx++) { unsigned succ_pos = block->successors[succ_idx].block->pos; /* The successors are ordered, so once we see a successor point * to the merge block, we are done fixing the cases. */ if (succ_pos >= swtch->end_pos) break; struct vtn_construct *prev_cse = vtn_find_innermost(vtn_construct_type_case, block->successors[succ_idx - 1].block->parent); vtn_assert(prev_cse); prev_cse->end_pos = succ_pos; } } else { vtn_assert(merge_op == SpvOpSelectionMerge); vtn_assert(branch_op == SpvOpBranchConditional); struct vtn_construct *sel = vtn_zalloc(b, struct vtn_construct); sel->type = vtn_construct_type_selection; sel->start_pos = block->pos; sel->end_pos = end_pos; sel->parent = block->parent; block->parent = sel; vtn_assert(block->successors_count == 2); struct vtn_block *then_block = block->successors[0].block; struct vtn_block *else_block = block->successors[1].block; sel->then_pos = then_block ? then_block->pos : 0; sel->else_pos = else_block ? else_block->pos : 0; } } } /* Link the constructs with their parents and with the remaining blocks * that do not start one. This will also build the ordered list of * constructs. */ struct vtn_construct_stack stack; init_construct_stack(&stack, b); push_construct(&stack, func_construct); list_addtail(&func_construct->link, &b->func->constructs); for (unsigned i = 0; i < b->func->ordered_blocks_count; i++) { struct vtn_block *block = b->func->ordered_blocks[i]; while (block->pos == top_construct(&stack)->end_pos) pop_construct(&stack); /* Identify the start of a continue construct. */ if (top_construct(&stack)->type == vtn_construct_type_loop && !vtn_is_single_block_loop(top_construct(&stack)) && top_construct(&stack)->continue_pos == block->pos) { struct vtn_construct *c = vtn_find_innermost(vtn_construct_type_continue, block->parent); vtn_assert(c); vtn_assert(c->parent == top_construct(&stack)); list_addtail(&c->link, &b->func->constructs); push_construct(&stack, c); } if (top_construct(&stack)->type == vtn_construct_type_switch) { struct vtn_block *header = b->func->ordered_blocks[top_construct(&stack)->start_pos]; for (unsigned succ_idx = 0; succ_idx < header->successors_count; succ_idx++) { struct vtn_successor *succ = &header->successors[succ_idx]; if (block == succ->block) { struct vtn_construct *c = vtn_find_innermost(vtn_construct_type_case, succ->block->parent); if (c) { vtn_assert(c->parent == top_construct(&stack)); list_addtail(&c->link, &b->func->constructs); push_construct(&stack, c); } break; } } } if (block->merge) { switch (block->merge[0] & SpvOpCodeMask) { case SpvOpSelectionMerge: { struct vtn_construct *c = block->parent; vtn_assert(c->type == vtn_construct_type_selection || c->type == vtn_construct_type_switch); c->parent = top_construct(&stack); list_addtail(&c->link, &b->func->constructs); push_construct(&stack, c); break; } case SpvOpLoopMerge: { struct vtn_construct *c = block->parent; struct vtn_construct *loop = c; /* A loop might have an extra selection injected, skip it. */ if (c->type == vtn_construct_type_selection) loop = c->parent; vtn_assert(loop->type == vtn_construct_type_loop); loop->parent = top_construct(&stack); list_addtail(&loop->link, &b->func->constructs); push_construct(&stack, loop); if (loop != c) { /* Make sure we also "enter" the extra construct. */ list_addtail(&c->link, &b->func->constructs); push_construct(&stack, c); } break; } default: unreachable("invalid merge opcode"); } } block->parent = top_construct(&stack); } vtn_assert(count_construct_stack(&stack) == 1); vtn_assert(top_construct(&stack)->type == vtn_construct_type_function); unsigned index = 0; list_for_each_entry(struct vtn_construct, c, &b->func->constructs, link) c->index = index++; } static void validate_constructs(struct vtn_builder *b) { list_for_each_entry(struct vtn_construct, c, &b->func->constructs, link) { if (c->type == vtn_construct_type_function) vtn_assert(c->parent == NULL); else vtn_assert(c->parent); switch (c->type) { case vtn_construct_type_continue: vtn_assert(c->parent->type == vtn_construct_type_loop); break; case vtn_construct_type_case: vtn_assert(c->parent->type == vtn_construct_type_switch); break; default: /* Nothing to do. */ break; } } } static void find_innermost_constructs(struct vtn_builder *b) { list_for_each_entry(struct vtn_construct, c, &b->func->constructs, link) { if (c->type == vtn_construct_type_function) { c->innermost_loop = NULL; c->innermost_switch = NULL; c->innermost_case = NULL; continue; } if (c->type == vtn_construct_type_loop) c->innermost_loop = c; else c->innermost_loop = c->parent->innermost_loop; if (c->type == vtn_construct_type_switch) c->innermost_switch = c; else c->innermost_switch = c->parent->innermost_switch; if (c->type == vtn_construct_type_case) c->innermost_case = c; else c->innermost_case = c->parent->innermost_case; } list_for_each_entry(struct vtn_construct, c, &b->func->constructs, link) { vtn_assert(vtn_find_innermost(vtn_construct_type_loop, c) == c->innermost_loop); vtn_assert(vtn_find_innermost(vtn_construct_type_switch, c) == c->innermost_switch); vtn_assert(vtn_find_innermost(vtn_construct_type_case, c) == c->innermost_case); } } static void set_needs_continue_propagation(struct vtn_construct *c) { for (; c != c->innermost_loop; c = c->parent) c->needs_continue_propagation = true; } static void set_needs_break_propagation(struct vtn_construct *c, struct vtn_construct *to_break) { for (; c != to_break; c = c->parent) c->needs_break_propagation = true; } static enum vtn_branch_type branch_type_for_successor(struct vtn_builder *b, struct vtn_block *block, struct vtn_successor *succ) { unsigned pos = block->pos; unsigned succ_pos = succ->block->pos; struct vtn_construct *inner = block->parent; vtn_assert(inner); /* Identify the types of branches, applying the "Rules for Structured * Control-flow Constructs" from SPIR-V spec. */ struct vtn_construct *innermost_loop = inner->innermost_loop; if (innermost_loop) { /* Entering the innermost loop’s continue construct. */ if (!vtn_is_single_block_loop(innermost_loop) && succ_pos == innermost_loop->continue_pos) { set_needs_continue_propagation(inner); return vtn_branch_type_loop_continue; } /* Breaking from the innermost loop (and branching from back-edge block * to loop merge). */ if (succ_pos == innermost_loop->end_pos) { set_needs_break_propagation(inner, innermost_loop); return vtn_branch_type_loop_break; } /* Next loop iteration. There can be only a single loop back-edge * for each loop construct. */ if (succ_pos == innermost_loop->start_pos) { vtn_assert(inner->type == vtn_construct_type_continue || vtn_is_single_block_loop(innermost_loop)); return vtn_branch_type_loop_back_edge; } } struct vtn_construct *innermost_switch = inner->innermost_switch; if (innermost_switch) { struct vtn_construct *innermost_cse = inner->innermost_case; /* Breaking from the innermost switch construct. */ if (succ_pos == innermost_switch->end_pos) { /* Use a nloop if this is not a natural exit from a case construct. */ if (innermost_cse && pos != innermost_cse->end_pos - 1) { innermost_cse->needs_nloop = true; set_needs_break_propagation(inner, innermost_cse); } return vtn_branch_type_switch_break; } /* Branching from one case construct to another. */ if (inner != innermost_switch) { vtn_assert(innermost_cse); vtn_assert(innermost_cse->parent == innermost_switch); if (succ->block->switch_case) { /* Both cases should be from the same Switch construct. */ struct vtn_construct *target_cse = succ->block->parent->innermost_case; vtn_assert(target_cse->parent == innermost_switch); target_cse->needs_fallthrough = true; return vtn_branch_type_switch_fallthrough; } } } if (inner->type == vtn_construct_type_selection) { /* Branches from the header block that were not categorized above will * follow to the then/else paths or to the merge block, and are handled * by the nir_if node. */ if (block->merge) return vtn_branch_type_forward; /* Breaking from a selection construct. */ if (succ_pos == inner->end_pos) { /* Identify cases where the break would be a natural flow in the NIR * construct. We don't need the extra loop in such cases. * * Because then/else are not ordered, we need to find which one happens * later. For non early merges, the branch from the block right before * the second side of the if starts will also jumps naturally to the * end of the if. */ const bool has_early_merge = inner->merge_pos != inner->end_pos; const unsigned second_pos = MAX2(inner->then_pos, inner->else_pos); const bool natural_exit_from_if = pos + 1 == inner->end_pos || (!has_early_merge && (pos + 1 == second_pos)); inner->needs_nloop = !natural_exit_from_if; return vtn_branch_type_if_break; } } if (succ_pos < inner->end_pos) return vtn_branch_type_forward; const enum nir_spirv_debug_level level = NIR_SPIRV_DEBUG_LEVEL_ERROR; const size_t offset = 0; vtn_logf(b, level, offset, "SPIR-V parsing FAILED:\n" " Unrecognized branch from block pos %u (id=%u) " "to block pos %u (id=%u)", block->pos, block->label[1], succ->block->pos, succ->block->label[1]); vtn_logf(b, level, offset, " Inner construct '%s': %u -> %u (merge=%u then=%u else=%u)", vtn_construct_type_to_string(inner->type), inner->start_pos, inner->end_pos, inner->merge_pos, inner->then_pos, inner->else_pos); struct vtn_construct *outer = inner->parent; if (outer) { vtn_logf(b, level, offset, " Outer construct '%s': %u -> %u (merge=%u then=%u else=%u)", vtn_construct_type_to_string(outer->type), outer->start_pos, outer->end_pos, outer->merge_pos, outer->then_pos, outer->else_pos); } vtn_fail("Unable to identify branch type"); return vtn_branch_type_none; } static enum vtn_branch_type branch_type_for_terminator(struct vtn_builder *b, struct vtn_block *block) { vtn_assert(block->successors_count == 1); vtn_assert(block->successors[0].block == NULL); switch (block->branch[0] & SpvOpCodeMask) { case SpvOpKill: return vtn_branch_type_discard; case SpvOpTerminateInvocation: return vtn_branch_type_terminate_invocation; case SpvOpIgnoreIntersectionKHR: return vtn_branch_type_ignore_intersection; case SpvOpTerminateRayKHR: return vtn_branch_type_terminate_ray; case SpvOpEmitMeshTasksEXT: return vtn_branch_type_emit_mesh_tasks; case SpvOpReturn: case SpvOpReturnValue: case SpvOpUnreachable: return vtn_branch_type_return; default: unreachable("unexpected terminator operation"); return vtn_branch_type_none; } } static void set_branch_types(struct vtn_builder *b) { for (unsigned i = 0; i < b->func->ordered_blocks_count; i++) { struct vtn_block *block = b->func->ordered_blocks[i]; for (unsigned j = 0; j < block->successors_count; j++) { struct vtn_successor *succ = &block->successors[j]; if (succ->block) succ->branch_type = branch_type_for_successor(b, block, succ); else succ->branch_type = branch_type_for_terminator(b, block); vtn_assert(succ->branch_type != vtn_branch_type_none); } } } static void find_merge_pos(struct vtn_builder *b) { /* Merges are at the end of the construct by construction... */ list_for_each_entry(struct vtn_construct, c, &b->func->constructs, link) c->merge_pos = c->end_pos; /* ...except when we have an "early merge", i.e. a branch that converges * before the declared merge point. For these cases the actual merge is * stored in merge_pos. * * Look at all header blocks for constructs that may have such early * merge, and check whether they fit */ for (unsigned i = 0; i < b->func->ordered_blocks_count; i++) { if (!b->func->ordered_blocks[i]->merge) continue; struct vtn_block *header = b->func->ordered_blocks[i]; if (header->successors_count != 2) continue; /* Ignore single-block loops (i.e. header thats in a continue * construct). Because the loop has no body, no block will * be identified in the then/else sides, the vtn_emit_branch * calls will be enough. */ struct vtn_construct *c = header->parent; if (c->type != vtn_construct_type_selection) continue; const unsigned first_pos = MIN2(c->then_pos, c->else_pos); const unsigned second_pos = MAX2(c->then_pos, c->else_pos); /* The first side ends where the second starts. The second side ends * either the continue position (that is guaranteed to appear after the * body of a loop) or the actual end of the construct. * * Because of the way we ordered the blocks, if there's an early merge, * the first side of the if will have a branch inside the second side. */ const unsigned first_end = second_pos; const unsigned second_end = c->end_pos; unsigned early_merge_pos = 0; for (unsigned pos = first_pos; pos < first_end; pos++) { /* For each block in first... */ struct vtn_block *block = b->func->ordered_blocks[pos]; for (unsigned s = 0; s < block->successors_count; s++) { if (block->successors[s].block) { /* ...see if one of its successors branches to the second side. */ const unsigned succ_pos = block->successors[s].block->pos; if (succ_pos >= second_pos && succ_pos < second_end) { vtn_fail_if(early_merge_pos, "A single selection construct cannot " "have multiple early merges"); early_merge_pos = succ_pos; } } } if (early_merge_pos) { c->merge_pos = early_merge_pos; break; } } } } void vtn_build_structured_cfg(struct vtn_builder *b, const uint32_t *words, const uint32_t *end) { vtn_foreach_function(func, &b->functions) { b->func = func; sort_blocks(b); create_constructs(b); validate_constructs(b); find_innermost_constructs(b); find_merge_pos(b); set_branch_types(b); if (MESA_SPIRV_DEBUG(STRUCTURED)) { printf("\nBLOCKS (%u):\n", func->ordered_blocks_count); print_ordered_blocks(func); printf("\nCONSTRUCTS (%u):\n", list_length(&func->constructs)); print_constructs(func); printf("\n"); } } } static int vtn_set_break_vars_between(struct vtn_builder *b, struct vtn_construct *from, struct vtn_construct *to) { vtn_assert(from); vtn_assert(to); int count = 0; for (struct vtn_construct *c = from; c != to; c = c->parent) { if (c->break_var) { vtn_assert(c->nloop); count++; /* There's no need to set break_var for the from block an actual break will be emitted * by the callers. */ if (c != from) nir_store_var(&b->nb, c->break_var, nir_imm_true(&b->nb), 1); } else { /* There's a 1:1 correspondence between break_vars and nloops. */ vtn_assert(!c->nloop); } } return count; } static void vtn_emit_break_for_construct(struct vtn_builder *b, const struct vtn_block *block, struct vtn_construct *to_break) { vtn_assert(to_break); vtn_assert(to_break->nloop); bool has_intermediate = vtn_set_break_vars_between(b, block->parent, to_break); if (has_intermediate) nir_store_var(&b->nb, to_break->break_var, nir_imm_true(&b->nb), 1); nir_jump(&b->nb, nir_jump_break); } static void vtn_emit_continue_for_construct(struct vtn_builder *b, const struct vtn_block *block, struct vtn_construct *to_continue) { vtn_assert(to_continue); vtn_assert(to_continue->type == vtn_construct_type_loop); vtn_assert(to_continue->nloop); bool has_intermediate = vtn_set_break_vars_between(b, block->parent, to_continue); if (has_intermediate) { nir_store_var(&b->nb, to_continue->continue_var, nir_imm_true(&b->nb), 1); nir_jump(&b->nb, nir_jump_break); } else { nir_jump(&b->nb, nir_jump_continue); } } static void vtn_emit_branch(struct vtn_builder *b, const struct vtn_block *block, const struct vtn_successor *succ) { switch (succ->branch_type) { case vtn_branch_type_none: vtn_assert(!"invalid branch type"); break; case vtn_branch_type_forward: /* Nothing to do. */ break; case vtn_branch_type_if_break: { struct vtn_construct *inner_if = block->parent; vtn_assert(inner_if->type == vtn_construct_type_selection); if (inner_if->nloop) { vtn_emit_break_for_construct(b, block, inner_if); } else { /* Nothing to do. This is a natural exit from an if construct. */ } break; } case vtn_branch_type_switch_break: { struct vtn_construct *swtch = block->parent->innermost_switch; vtn_assert(swtch); struct vtn_construct *cse = block->parent->innermost_case; if (cse && cse->parent == swtch && cse->nloop) { vtn_emit_break_for_construct(b, block, cse); } else { /* Nothing to do. This case doesn't have a loop, so this is a * natural break from a case. */ } break; } case vtn_branch_type_switch_fallthrough: { struct vtn_construct *cse = block->parent->innermost_case; vtn_assert(cse); struct vtn_construct *swtch = cse->parent; vtn_assert(swtch->type == vtn_construct_type_switch); /* Successor is the start of another case construct with the same parent * switch construct. */ vtn_assert(succ->block->switch_case != NULL); struct vtn_construct *target = succ->block->parent->innermost_case; vtn_assert(target != NULL && target->type == vtn_construct_type_case); vtn_assert(target->parent == swtch); vtn_assert(target->fallthrough_var); nir_store_var(&b->nb, target->fallthrough_var, nir_imm_true(&b->nb), 1); if (cse->nloop) vtn_emit_break_for_construct(b, block, cse); break; } case vtn_branch_type_loop_break: { struct vtn_construct *loop = block->parent->innermost_loop; vtn_assert(loop); vtn_emit_break_for_construct(b, block, loop); break; } case vtn_branch_type_loop_continue: { struct vtn_construct *loop = block->parent->innermost_loop; vtn_assert(loop); vtn_emit_continue_for_construct(b, block, loop); break; } case vtn_branch_type_loop_back_edge: /* Nothing to do: naturally handled by NIR loop node. */ break; case vtn_branch_type_return: vtn_assert(block); vtn_emit_ret_store(b, block); nir_jump(&b->nb, nir_jump_return); break; case vtn_branch_type_discard: if (b->convert_discard_to_demote) { nir_demote(&b->nb); /* Workaround for outdated test cases from CTS and Tint which assume * that OpKill always terminates the invocation. Break from the * current loop if it exists in order to prevent infinite loops. */ struct vtn_construct *loop = block->parent->innermost_loop; if (loop) vtn_emit_break_for_construct(b, block, loop); } else { nir_discard(&b->nb); } break; case vtn_branch_type_terminate_invocation: nir_terminate(&b->nb); break; case vtn_branch_type_ignore_intersection: nir_ignore_ray_intersection(&b->nb); nir_jump(&b->nb, nir_jump_halt); break; case vtn_branch_type_terminate_ray: nir_terminate_ray(&b->nb); nir_jump(&b->nb, nir_jump_halt); break; case vtn_branch_type_emit_mesh_tasks: { vtn_assert(block); vtn_assert(block->branch); const uint32_t *w = block->branch; vtn_assert((w[0] & SpvOpCodeMask) == SpvOpEmitMeshTasksEXT); /* Launches mesh shader workgroups from the task shader. * Arguments are: vec(x, y, z), payload pointer */ nir_def *dimensions = nir_vec3(&b->nb, vtn_get_nir_ssa(b, w[1]), vtn_get_nir_ssa(b, w[2]), vtn_get_nir_ssa(b, w[3])); /* The payload variable is optional. * We don't have a NULL deref in NIR, so just emit the explicit * intrinsic when there is no payload. */ const unsigned count = w[0] >> SpvWordCountShift; if (count == 4) nir_launch_mesh_workgroups(&b->nb, dimensions); else if (count == 5) nir_launch_mesh_workgroups_with_payload_deref(&b->nb, dimensions, vtn_get_nir_ssa(b, w[4])); else vtn_fail("Invalid EmitMeshTasksEXT."); nir_jump(&b->nb, nir_jump_halt); break; } default: vtn_fail("Invalid branch type"); } } static nir_selection_control vtn_selection_control(struct vtn_builder *b, SpvSelectionControlMask control) { if (control == SpvSelectionControlMaskNone) return nir_selection_control_none; else if (control & SpvSelectionControlDontFlattenMask) return nir_selection_control_dont_flatten; else if (control & SpvSelectionControlFlattenMask) return nir_selection_control_flatten; else vtn_fail("Invalid selection control"); } static void vtn_emit_block(struct vtn_builder *b, struct vtn_block *block, vtn_instruction_handler handler) { const uint32_t *block_start = block->label; const uint32_t *block_end = block->merge ? block->merge : block->branch; 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_nop(&b->nb); if (block->parent->type == vtn_construct_type_switch) { /* Switch is handled as a sequence of NIR if for each of the cases. */ } else if (block->successors_count == 1) { vtn_assert(block->successors[0].branch_type != vtn_branch_type_none); vtn_emit_branch(b, block, &block->successors[0]); } else if (block->successors_count == 2) { struct vtn_successor *then_succ = &block->successors[0]; struct vtn_successor *else_succ = &block->successors[1]; struct vtn_construct *c = block->parent; nir_def *cond = vtn_get_nir_ssa(b, block->branch[1]); if (then_succ->block == else_succ->block) cond = nir_imm_true(&b->nb); /* The branches will already be emitted here, so for paths that * doesn't have blocks inside the construct, e.g. that are an * exit from the construct, nothing else is needed. */ nir_if *sel = nir_push_if(&b->nb, cond); vtn_emit_branch(b, block, then_succ); if (then_succ->block != else_succ->block) { nir_push_else(&b->nb, NULL); vtn_emit_branch(b, block, else_succ); } nir_pop_if(&b->nb, NULL); if (c->type == vtn_construct_type_selection && block->pos == c->start_pos) { /* This is the start of a selection construct. Record the nir_if in * the construct so we can close it properly and handle the then and * else cases in block iteration. */ vtn_assert(c->nif == NULL); c->nif = sel; vtn_assert(block->merge != NULL); SpvOp merge_op = block->merge[0] & SpvOpCodeMask; if (merge_op == SpvOpSelectionMerge) sel->control = vtn_selection_control(b, block->merge[2]); /* In most cases, vtn_emit_cf_func_structured() will place the cursor * in the correct side of the nir_if. However, in the case where the * selection construct is empty, we need to ensure that the cursor is * at least inside the nir_if or NIR will assert when we try to close * it with nir_pop_if(). */ b->nb.cursor = nir_before_cf_list(&sel->then_list); } else { vtn_fail_if(then_succ->branch_type == vtn_branch_type_forward && else_succ->branch_type == vtn_branch_type_forward && then_succ->block != else_succ->block, "An OpSelectionMerge instruction is required to precede " "an OpBranchConditional instruction that has different " "True Label and False Label operands where neither are " "declared merge blocks or Continue Targets."); if (then_succ->branch_type == vtn_branch_type_forward) { b->nb.cursor = nir_before_cf_list(&sel->then_list); } else if (else_succ->branch_type == vtn_branch_type_forward) { b->nb.cursor = nir_before_cf_list(&sel->else_list); } else { /* Leave it alone */ } } } } static nir_def * vtn_switch_case_condition(struct vtn_builder *b, struct vtn_construct *swtch, nir_def *sel, struct vtn_case *cse) { vtn_assert(swtch->type == vtn_construct_type_switch); if (cse->is_default) { nir_def *any = nir_imm_false(&b->nb); struct vtn_block *header = b->func->ordered_blocks[swtch->start_pos]; for (unsigned j = 0; j < header->successors_count; j++) { struct vtn_successor *succ = &header->successors[j]; struct vtn_case *other = succ->block->switch_case; if (other->is_default) continue; any = nir_ior(&b->nb, any, vtn_switch_case_condition(b, swtch, sel, other)); } return nir_inot(&b->nb, any); } else { nir_def *cond = nir_imm_false(&b->nb); util_dynarray_foreach(&cse->values, uint64_t, val) cond = nir_ior(&b->nb, cond, nir_ieq_imm(&b->nb, sel, *val)); return cond; } } static nir_loop_control vtn_loop_control(struct vtn_builder *b, SpvLoopControlMask control) { if (control == SpvLoopControlMaskNone) return nir_loop_control_none; else if (control & SpvLoopControlDontUnrollMask) return nir_loop_control_dont_unroll; else if (control & SpvLoopControlUnrollMask) return nir_loop_control_unroll; else if ((control & SpvLoopControlDependencyInfiniteMask) || (control & SpvLoopControlDependencyLengthMask) || (control & SpvLoopControlMinIterationsMask) || (control & SpvLoopControlMaxIterationsMask) || (control & SpvLoopControlIterationMultipleMask) || (control & SpvLoopControlPeelCountMask) || (control & SpvLoopControlPartialCountMask)) { /* We do not do anything special with these yet. */ return nir_loop_control_none; } else { vtn_fail("Invalid loop control"); } } static void vtn_emit_control_flow_propagation(struct vtn_builder *b, struct vtn_construct *top) { if (top->type == vtn_construct_type_function || top->type == vtn_construct_type_continue || top->type == vtn_construct_type_switch) return; /* Find the innermost parent with a NIR loop. */ struct vtn_construct *parent_with_nloop = NULL; for (struct vtn_construct *c = top->parent; c; c = c->parent) { if (c->nloop) { parent_with_nloop = c; break; } } if (parent_with_nloop == NULL) return; /* If there's another nloop in the parent chain, decide whether we need * to emit conditional continue/break after top construct is closed. */ if (top->needs_continue_propagation && parent_with_nloop == top->innermost_loop) { struct vtn_construct *loop = top->innermost_loop; vtn_assert(loop); vtn_assert(loop != top); nir_push_if(&b->nb, nir_load_var(&b->nb, loop->continue_var)); nir_jump(&b->nb, nir_jump_continue); nir_pop_if(&b->nb, NULL); } if (top->needs_break_propagation) { vtn_assert(parent_with_nloop->break_var); nir_break_if(&b->nb, nir_load_var(&b->nb, parent_with_nloop->break_var)); } } static inline nir_variable * vtn_create_local_bool(struct vtn_builder *b, const char *name) { return nir_local_variable_create(b->nb.impl, glsl_bool_type(), name); } void vtn_emit_cf_func_structured(struct vtn_builder *b, struct vtn_function *func, vtn_instruction_handler handler) { struct vtn_construct *current = list_first_entry(&func->constructs, struct vtn_construct, link); vtn_assert(current->type == vtn_construct_type_function); /* Walk the blocks in order keeping track of the constructs that started * but haven't ended yet. When constructs start and end, add extra code to * setup the NIR control flow (different for each construct), also add * extra code for propagating certain branch types. */ struct vtn_construct_stack stack; init_construct_stack(&stack, b); push_construct(&stack, current); for (unsigned i = 0; i < func->ordered_blocks_count; i++) { struct vtn_block *block = func->ordered_blocks[i]; struct vtn_construct *top = top_construct(&stack); /* Close out any past constructs and make sure the cursor is at the * right place to start this block. For each block, there are three * cases we care about here: * * 1. It is the block at the end (in our reverse structured post-order * traversal) of one or more constructs and closes them. * * 2. It is an early merge of a selection construct. * * 3. It is the start of the then or else case of a selection construct * and we may have previously been emitting code in the other side. */ /* Close (or early merge) any constructs that end at this block. */ bool merged_any_constructs = false; while (top->end_pos == block->pos || top->merge_pos == block->pos) { merged_any_constructs = true; if (top->nif) { const bool has_early_merge = top->merge_pos != top->end_pos; if (!has_early_merge) { nir_pop_if(&b->nb, top->nif); } else if (block->pos == top->merge_pos) { /* This is an early merge. */ nir_pop_if(&b->nb, top->nif); /* The extra dummy "if (true)" for the merged part avoids * generating multiple jumps in sequence and upsetting * NIR rules. We'll pop it in the case below when we reach * the end_pos block. */ nir_push_if(&b->nb, nir_imm_true(&b->nb)); /* Stop since this construct still has more blocks. */ break; } else { /* Pop the dummy if added for the blocks after the early merge. */ vtn_assert(block->pos == top->end_pos); nir_pop_if(&b->nb, NULL); } } if (top->nloop) { /* For constructs that are not SPIR-V loop, a NIR loop may be used * to provide richer control flow. So we add a nir break to cause * the loop stop at the first iteration, unless there's already a * jump at the end of the last block. */ if (top->type != vtn_construct_type_loop) { nir_block *last = nir_loop_last_block(top->nloop); if (!nir_block_ends_in_jump(last)) { b->nb.cursor = nir_after_block(last); nir_jump(&b->nb, nir_jump_break); } } nir_pop_loop(&b->nb, top->nloop); } vtn_emit_control_flow_propagation(b, top); pop_construct(&stack); top = top_construct(&stack); } /* We are fully inside the current top. */ vtn_assert(block->pos < top->end_pos); /* Move the cursor to the right side of a selection construct. * * If we merged any constructs, we don't need to move because * either: this is an early merge and we already set the cursor above; * or a construct ended, and this is a 'merge block' for that * construct, so it can't also be a 'Target' for an outer conditional. */ if (!merged_any_constructs && top->type == vtn_construct_type_selection && (block->pos == top->then_pos || block->pos == top->else_pos)) { vtn_assert(top->nif); struct vtn_block *header = func->ordered_blocks[top->start_pos]; vtn_assert(header->successors_count == 2); if (block->pos == top->then_pos) b->nb.cursor = nir_before_cf_list(&top->nif->then_list); else b->nb.cursor = nir_before_cf_list(&top->nif->else_list); } /* Open any constructs which start at this block. * * Constructs which are designated by Op*Merge are considered to start * at the block which contains the merge instruction. This means that * loops constructs start at the first block inside the loop while * selection and switch constructs start at the block containing the * OpBranchConditional or OpSwitch. */ while (current->link.next != &func->constructs) { struct vtn_construct *next = list_entry(current->link.next, struct vtn_construct, link); /* Stop once we find a construct that doesn't start in this block. */ if (next->start_pos != block->pos) break; switch (next->type) { case vtn_construct_type_function: unreachable("should've already entered function construct"); break; case vtn_construct_type_selection: { /* Add the wrapper loop now and the nir_if, along the contents of * this entire block, will get added inside the loop as part of * vtn_emit_block() below. */ if (next->needs_nloop) { next->break_var = vtn_create_local_bool(b, "if_break"); nir_store_var(&b->nb, next->break_var, nir_imm_false(&b->nb), 1); next->nloop = nir_push_loop(&b->nb); } break; } case vtn_construct_type_loop: { next->break_var = vtn_create_local_bool(b, "loop_break"); next->continue_var = vtn_create_local_bool(b, "loop_continue"); nir_store_var(&b->nb, next->break_var, nir_imm_false(&b->nb), 1); next->nloop = nir_push_loop(&b->nb); nir_store_var(&b->nb, next->continue_var, nir_imm_false(&b->nb), 1); next->nloop->control = vtn_loop_control(b, block->merge[3]); break; } case vtn_construct_type_continue: { struct vtn_construct *loop = next->parent; assert(loop->type == vtn_construct_type_loop); assert(!vtn_is_single_block_loop(loop)); nir_push_continue(&b->nb, loop->nloop); break; } case vtn_construct_type_switch: { /* Switch is not translated to any NIR node, all is handled by * each individual case construct. */ for (unsigned j = 0; j < block->successors_count; j++) { struct vtn_successor *s = &block->successors[j]; if (s->block && s->block->pos < next->end_pos) { struct vtn_construct *c = s->block->parent->innermost_case; vtn_assert(c->type == vtn_construct_type_case); if (c->needs_fallthrough) { c->fallthrough_var = vtn_create_local_bool(b, "fallthrough"); nir_store_var(&b->nb, c->fallthrough_var, nir_imm_false(&b->nb), 1); } } } break; } case vtn_construct_type_case: { struct vtn_construct *swtch = next->parent; struct vtn_block *header = func->ordered_blocks[swtch->start_pos]; nir_def *sel = vtn_get_nir_ssa(b, header->branch[1]); nir_def *case_condition = vtn_switch_case_condition(b, swtch, sel, block->switch_case); if (next->fallthrough_var) { case_condition = nir_ior(&b->nb, case_condition, nir_load_var(&b->nb, next->fallthrough_var)); } if (next->needs_nloop) { next->break_var = vtn_create_local_bool(b, "case_break"); nir_store_var(&b->nb, next->break_var, nir_imm_false(&b->nb), 1); next->nloop = nir_push_loop(&b->nb); } next->nif = nir_push_if(&b->nb, case_condition); break; } } current = next; push_construct(&stack, next); } vtn_emit_block(b, block, handler); } vtn_assert(count_construct_stack(&stack) == 1); }