- struct vtn_block *block = start;
- while (block != end) {
- if (block->merge && (*block->merge & SpvOpCodeMask) == SpvOpLoopMerge &&
- !block->loop) {
- struct vtn_loop *loop = ralloc(b, struct vtn_loop);
-
- loop->node.type = vtn_cf_node_type_loop;
- loop->node.parent = cf_parent;
- list_inithead(&loop->body);
- list_inithead(&loop->cont_body);
- loop->control = block->merge[3];
-
- list_addtail(&loop->node.link, cf_list);
- block->loop = loop;
-
- struct vtn_block *new_loop_break = vtn_block(b, block->merge[1]);
- struct vtn_block *new_loop_cont = vtn_block(b, block->merge[2]);
-
- /* Note: This recursive call will start with the current block as
- * its start block. If we weren't careful, we would get here
- * again and end up in infinite recursion. This is why we set
- * block->loop above and check for it before creating one. This
- * way, we only create the loop once and the second call that
- * tries to handle this loop goes to the cases below and gets
- * handled as a regular block.
- *
- * Note: When we make the recursive walk calls, we pass NULL for
- * the switch break since you have to break out of the loop first.
- * We do, however, still pass the current switch case because it's
- * possible that the merge block for the loop is the start of
- * another case.
- */
- vtn_cfg_walk_blocks(b, &loop->node, &loop->body,
- block, switch_case, NULL,
- new_loop_break, new_loop_cont, NULL );
- vtn_cfg_walk_blocks(b, &loop->node, &loop->cont_body,
- new_loop_cont, NULL, NULL,
- new_loop_break, NULL, block);
-
- enum vtn_branch_type branch_type =
- vtn_get_branch_type(b, new_loop_break, switch_case, switch_break,
- loop_break, loop_cont);
-
- if (branch_type != vtn_branch_type_none) {
- /* Stop walking through the CFG when this inner loop's break block
- * ends up as the same block as the outer loop's continue block
- * because we are already going to visit it.
- */
- vtn_assert(branch_type == vtn_branch_type_loop_continue);
- return;
+ vtn_fail_if(block->merge_cf_node != NULL,
+ "The merge block declared by a header block cannot be a "
+ "merge block declared by any other header block.");
+
+ block->merge_cf_node = cf_node;
+}
+
+#define VTN_DECL_CF_NODE_FIND(_type) \
+static inline struct vtn_##_type * \
+vtn_cf_node_find_##_type(struct vtn_cf_node *node) \
+{ \
+ while (node && node->type != vtn_cf_node_type_##_type) \
+ node = node->parent; \
+ return (struct vtn_##_type *)node; \
+}
+
+VTN_DECL_CF_NODE_FIND(if)
+VTN_DECL_CF_NODE_FIND(loop)
+VTN_DECL_CF_NODE_FIND(case)
+VTN_DECL_CF_NODE_FIND(switch)
+VTN_DECL_CF_NODE_FIND(function)
+
+static enum vtn_branch_type
+vtn_handle_branch(struct vtn_builder *b,
+ struct vtn_cf_node *cf_parent,
+ struct vtn_block *target_block)
+{
+ struct vtn_loop *loop = vtn_cf_node_find_loop(cf_parent);
+
+ /* Detect a loop back-edge first. That way none of the code below
+ * accidentally operates on a loop back-edge.
+ */
+ if (loop && target_block == loop->header_block)
+ return vtn_branch_type_loop_back_edge;
+
+ /* Try to detect fall-through */
+ if (target_block->switch_case) {
+ /* When it comes to handling switch cases, we can break calls to
+ * vtn_handle_branch into two cases: calls from within a case construct
+ * and calls for the jump to each case construct. In the second case,
+ * cf_parent is the vtn_switch itself and vtn_cf_node_find_case() will
+ * return the outer switch case in which this switch is contained. It's
+ * fine if the target block is a switch case from an outer switch as
+ * long as it is also the switch break for this switch.
+ */
+ struct vtn_case *switch_case = vtn_cf_node_find_case(cf_parent);
+
+ /* This doesn't get called for the OpSwitch */
+ vtn_fail_if(switch_case == NULL,
+ "A switch case can only be entered through an OpSwitch or "
+ "falling through from another switch case.");
+
+ /* Because block->switch_case is only set on the entry block for a given
+ * switch case, we only ever get here if we're jumping to the start of a
+ * switch case. It's possible, however, that a switch case could jump
+ * to itself via a back-edge. That *should* get caught by the loop
+ * handling case above but if we have a back edge without a loop merge,
+ * we could en up here.
+ */
+ vtn_fail_if(target_block->switch_case == switch_case,
+ "A switch cannot fall-through to itself. Likely, there is "
+ "a back-edge which is not to a loop header.");
+
+ vtn_fail_if(target_block->switch_case->node.parent !=
+ switch_case->node.parent,
+ "A switch case fall-through must come from the same "
+ "OpSwitch construct");
+
+ vtn_fail_if(switch_case->fallthrough != NULL &&
+ switch_case->fallthrough != target_block->switch_case,
+ "Each case construct can have at most one branch to "
+ "another case construct");
+
+ switch_case->fallthrough = target_block->switch_case;
+
+ /* We don't immediately return vtn_branch_type_switch_fallthrough
+ * because it may also be a loop or switch break for an inner loop or
+ * switch and that takes precedence.
+ */
+ }
+
+ if (loop && target_block == loop->cont_block)
+ return vtn_branch_type_loop_continue;
+
+ /* We walk blocks as a breadth-first search on the control-flow construct
+ * tree where, when we find a construct, we add the vtn_cf_node for that
+ * construct and continue iterating at the merge target block (if any).
+ * Therefore, we want merges whose with parent == cf_parent to be treated
+ * as regular branches. We only want to consider merges if they break out
+ * of the current CF construct.
+ */
+ if (target_block->merge_cf_node != NULL &&
+ target_block->merge_cf_node->parent != cf_parent) {
+ switch (target_block->merge_cf_node->type) {
+ case vtn_cf_node_type_if:
+ for (struct vtn_cf_node *node = cf_parent;
+ node != target_block->merge_cf_node; node = node->parent) {
+ vtn_fail_if(node == NULL || node->type != vtn_cf_node_type_if,
+ "Branching to the merge block of a selection "
+ "construct can only be used to break out of a "
+ "selection construct");
+
+ struct vtn_if *if_stmt = vtn_cf_node_as_if(node);
+
+ /* This should be guaranteed by our iteration */
+ assert(if_stmt->merge_block != target_block);
+
+ vtn_fail_if(if_stmt->merge_block != NULL,
+ "Branching to the merge block of a selection "
+ "construct can only be used to break out of the "
+ "inner most nested selection level");