This new RTL pass is the second part of the SIMT conversion. It takes the
output from gcn-simt-vectorize.cc, in which all instructions have been
converted to SIMT-compatible vector instructions, and rearranges the CFG such
that conditional branches are replaced with EXEC masking. Loop branches must
still remain, and short-cut branches are provided to skip code sections when no
thread would execute it.
gcc/ChangeLog:
* config.gcc (amdgcn-*-amdhsa): Add gcn-simt-cbranch.o to extra_objs.
* config/gcn/gcn-simt-cbranch.cc: New file.
* config/gcn/gcn-passes.def: Insert pass_gcn_simt_cbranch before
pass_outof_cfg_layout_mode.
* config/gcn/gcn-protos.h (make_pass_gcn_simt_cbranch): New
prototype.
* config/gcn/t-gcn-hsa (gcn-simt-cbranch.o): New rule.
(ALL_HOST_OBJS): Add gcn-simt-cbranch.o.
gcc/testsuite/ChangeLog:
* gcc.target/gcn/simt-cond-1.c: New test.
* gcc.target/gcn/simt-cond-10.c: Likewise.
* gcc.target/gcn/simt-cond-10b.c: Likewise.
* gcc.target/gcn/simt-cond-11.c: Likewise.
* gcc.target/gcn/simt-cond-11b.c: Likewise.
* gcc.target/gcn/simt-cond-12.c: Likewise.
* gcc.target/gcn/simt-cond-12b.c: Likewise.
* gcc.target/gcn/simt-cond-13.c: Likewise.
* gcc.target/gcn/simt-cond-13b.c: Likewise.
* gcc.target/gcn/simt-cond-14.c: Likewise.
* gcc.target/gcn/simt-cond-14b.c: Likewise.
* gcc.target/gcn/simt-cond-15.c: Likewise.
* gcc.target/gcn/simt-cond-15b.c: Likewise.
* gcc.target/gcn/simt-cond-16.c: Likewise.
* gcc.target/gcn/simt-cond-17.c: Likewise.
* gcc.target/gcn/simt-cond-18.c: Likewise.
* gcc.target/gcn/simt-cond-1b.c: Likewise.
* gcc.target/gcn/simt-cond-2.c: Likewise.
* gcc.target/gcn/simt-cond-2b.c: Likewise.
* gcc.target/gcn/simt-cond-3.c: Likewise.
* gcc.target/gcn/simt-cond-3b.c: Likewise.
* gcc.target/gcn/simt-cond-4.c: Likewise.
* gcc.target/gcn/simt-cond-4b.c: Likewise.
* gcc.target/gcn/simt-cond-5.c: Likewise.
* gcc.target/gcn/simt-cond-5b.c: Likewise.
* gcc.target/gcn/simt-cond-6.c: Likewise.
* gcc.target/gcn/simt-cond-6b.c: Likewise.
* gcc.target/gcn/simt-cond-7.c: Likewise.
* gcc.target/gcn/simt-cond-7b.c: Likewise.
* gcc.target/gcn/simt-cond-8.c: Likewise.
* gcc.target/gcn/simt-cond-8b.c: Likewise.
* gcc.target/gcn/simt-cond-9.c: Likewise.
* gcc.target/gcn/simt-cond-9b.c: Likewise.
---
gcc/config.gcc | 2 +-
gcc/config/gcn/gcn-passes.def | 1 +
gcc/config/gcn/gcn-protos.h | 1 +
gcc/config/gcn/gcn-simt-cbranch.cc | 1979 ++++++++++++++++++
gcc/config/gcn/t-gcn-hsa | 5 +-
gcc/testsuite/gcc.target/gcn/simt-cond-1.c | 40 +
gcc/testsuite/gcc.target/gcn/simt-cond-10.c | 42 +
gcc/testsuite/gcc.target/gcn/simt-cond-10b.c | 48 +
gcc/testsuite/gcc.target/gcn/simt-cond-11.c | 45 +
gcc/testsuite/gcc.target/gcn/simt-cond-11b.c | 53 +
gcc/testsuite/gcc.target/gcn/simt-cond-12.c | 48 +
gcc/testsuite/gcc.target/gcn/simt-cond-12b.c | 54 +
gcc/testsuite/gcc.target/gcn/simt-cond-13.c | 41 +
gcc/testsuite/gcc.target/gcn/simt-cond-13b.c | 48 +
gcc/testsuite/gcc.target/gcn/simt-cond-14.c | 44 +
gcc/testsuite/gcc.target/gcn/simt-cond-14b.c | 50 +
gcc/testsuite/gcc.target/gcn/simt-cond-15.c | 44 +
gcc/testsuite/gcc.target/gcn/simt-cond-15b.c | 48 +
gcc/testsuite/gcc.target/gcn/simt-cond-16.c | 52 +
gcc/testsuite/gcc.target/gcn/simt-cond-17.c | 59 +
gcc/testsuite/gcc.target/gcn/simt-cond-18.c | 42 +
gcc/testsuite/gcc.target/gcn/simt-cond-1b.c | 45 +
gcc/testsuite/gcc.target/gcn/simt-cond-2.c | 44 +
gcc/testsuite/gcc.target/gcn/simt-cond-2b.c | 48 +
gcc/testsuite/gcc.target/gcn/simt-cond-3.c | 44 +
gcc/testsuite/gcc.target/gcn/simt-cond-3b.c | 48 +
gcc/testsuite/gcc.target/gcn/simt-cond-4.c | 48 +
gcc/testsuite/gcc.target/gcn/simt-cond-4b.c | 52 +
gcc/testsuite/gcc.target/gcn/simt-cond-5.c | 50 +
gcc/testsuite/gcc.target/gcn/simt-cond-5b.c | 54 +
gcc/testsuite/gcc.target/gcn/simt-cond-6.c | 51 +
gcc/testsuite/gcc.target/gcn/simt-cond-6b.c | 55 +
gcc/testsuite/gcc.target/gcn/simt-cond-7.c | 55 +
gcc/testsuite/gcc.target/gcn/simt-cond-7b.c | 59 +
gcc/testsuite/gcc.target/gcn/simt-cond-8.c | 40 +
gcc/testsuite/gcc.target/gcn/simt-cond-8b.c | 46 +
gcc/testsuite/gcc.target/gcn/simt-cond-9.c | 42 +
gcc/testsuite/gcc.target/gcn/simt-cond-9b.c | 48 +
38 files changed, 3573 insertions(+), 2 deletions(-)
create mode 100644 gcc/config/gcn/gcn-simt-cbranch.cc
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-1.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-10.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-10b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-11.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-11b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-12.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-12b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-13.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-13b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-14.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-14b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-15.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-15b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-16.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-17.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-18.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-1b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-2.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-2b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-3.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-3b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-4.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-4b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-5.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-5b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-6.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-6b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-7.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-7b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-8.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-8b.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-9.c
create mode 100644 gcc/testsuite/gcc.target/gcn/simt-cond-9b.c
diff --git a/gcc/config.gcc b/gcc/config.gcc
index dd13a8e4c18..ba16078f8a7 100644
--- a/gcc/config.gcc
+++ b/gcc/config.gcc
@@ -1851,7 +1851,7 @@ amdgcn-*-amdhsa)
tmake_file="gcn/t-gcn-hsa"
native_system_header_dir=/include
extra_modes=gcn/gcn-modes.def
- extra_objs="${extra_objs} gcn-tree.o gcn-simt-vectorize.o"
+ extra_objs="${extra_objs} gcn-tree.o gcn-simt-vectorize.o
gcn-simt-cbranch.o"
extra_options="${extra_options} gcn/gcn-tables.opt"
case "$host" in
x86_64*-*-linux-gnu )
diff --git a/gcc/config/gcn/gcn-passes.def b/gcc/config/gcn/gcn-passes.def
index 345e97c7f84..cdf0942a947 100644
--- a/gcc/config/gcn/gcn-passes.def
+++ b/gcc/config/gcn/gcn-passes.def
@@ -22,3 +22,4 @@
/* This needs to be between into_cfglayout and outof_cfglayout to
ensure the edge changes are tracked properly. */
INSERT_PASS_BEFORE (pass_outof_cfg_layout_mode, 1, pass_gcn_simt_vectorize);
+INSERT_PASS_BEFORE (pass_outof_cfg_layout_mode, 1, pass_gcn_simt_cbranch);
diff --git a/gcc/config/gcn/gcn-protos.h b/gcc/config/gcn/gcn-protos.h
index e02a19e9001..ae6a83109c1 100644
--- a/gcc/config/gcn/gcn-protos.h
+++ b/gcc/config/gcn/gcn-protos.h
@@ -91,6 +91,7 @@ extern void print_operand (FILE *file, rtx x, int code);
extern bool regno_ok_for_index_p (int);
rtl_opt_pass *make_pass_gcn_simt_vectorize (gcc::context *);
+rtl_opt_pass *make_pass_gcn_simt_cbranch (gcc::context *);
enum gcn_cvt_t
{
diff --git a/gcc/config/gcn/gcn-simt-cbranch.cc
b/gcc/config/gcn/gcn-simt-cbranch.cc
new file mode 100644
index 00000000000..c91ee8f15e6
--- /dev/null
+++ b/gcc/config/gcn/gcn-simt-cbranch.cc
@@ -0,0 +1,1979 @@
+/* AMD GCN "simt-cbranch" pass implementation.
+
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ Contributed by BayLibre.
+
+ This file is part of GCC.
+
+ GCC is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ GCC is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with GCC; see the file COPYING3. If not see
+ <http://www.gnu.org/licenses/>. */
+
+
+/* Transform conditional branches to support SIMT masks. In SIMT mode,
+ the program must execute both the true and false branches of a
+ conditional, suitably masked. There are short-circuit branches for the
+ case where the condition is false for all threads, which serve as both
+ an optimization, and to make it clear to subsequent compiler passes that
+ the instructions should be treated as conditional (i.e. not eliminating
+ common expressions that will be masked differently).
+
+ For example:
+
+ A
+ if cond goto T else F
+ | \
+ T F
+ | /
+ Z
+
+ Becomes linear with short-cut branches:
+
+ A
+ true_mask = cond
+ false_mask = exec & ~cond
+ if (true_mask != 0)
+ / |
+ | exec = true_mask
+ | T (masked)
+ \ |
+ if (false_mask != 0)
+ / |
+ | exec = false_mask
+ | F (masked)
+ \ |
+ exec = true_mask | false_mask
+ Z
+
+ All the blocks in the function are sorted into a single, linear sequence
+ in which blocks that depend on other blocks are placed in the correct order,
+ but different code paths may be interleaved. A scoring system is used to try
+ to keep related blocks together (probably better for register pressure?),
+ without actually requiring it.
+
+ If there are circular dependencies (i.e. mark_dfs_back_edges emits
+ EDGE_DFS_BACK attributes) then they are treated as a loop. All blocks within
+ the loop must be executed before blocks that leave the loop. (This might
+ cause problems where there are early abort scenarios, but other threads loop
+ infinitely?) A loop may have multiple exit paths, all of which must be
+ executed (once all threads have stopped looping), so on each iteration the
+ exit conditions are accumulated, effectively transferring bits from EXEC into
+ one of the exit masks.
+
+ After both true and false branches have been executed, the masks are combined
+ using OR, rather than restoring from a saved value, because this method is
+ combinable across arbitrary numbers of nested or overlapping conditionals
+ and loops.
+
+ The function entry mask is not explicitly saved and restored, but as the
+ condition masks are merged back together, at each logical joint point, the
+ final mask should naturally return to the starting value before the function
+ exit point. The only exception is where some paths exit via "noreturn"
+ functions. In this case it is assumed that something like an "abort" is
+ being called -- the SIMT paradigm is broken -- so we just make sure these are
+ called before the regular return. If the "noreturn" actually does return
+ then the results are undefined.
+
+ At no point should EXEC ever be allowed to be completely zero. If this
+ were to happen then scalar values that happen to have been assigned to
+ VGPRs would be broken, and the program may never be able to recover EXEC
+ (among other UB).
+
+ All functions are converted to use SIMT conditionals, regardless of whether
+ they will be used for SIMT (it must assume that they might be called that
+ way). If a function is called in single-thread mode (EXEC == 1) then the
+ mask operations should function as booleans, the short-cut branches will end
+ up mimicking the original branch structure somewhat closely, and a trace of
+ actual working instructions should end up looking very similar to what it
+ would have done without the transformations (in theory).
+ */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "target.h"
+#include "tree-pass.h"
+#include "rtl.h"
+#include "cfghooks.h"
+#include "diagnostic-core.h"
+#include "cfgrtl.h"
+#include "cfganal.h"
+#include "tree.h"
+#include "expr.h"
+#include "explow.h"
+#include "print-rtl.h"
+
+const pass_data pass_data_gcn_simt_cbranch =
+{
+ RTL_PASS, // type
+ "simt_cbranch", // name
+ OPTGROUP_NONE, // optinfo_flags
+ TV_NONE, // tv_id
+ 0, // properties_required
+ 0, // properties_provided
+ 0, // properties_destroyed
+ 0, // todo_flags_start
+ TODO_df_verify | TODO_df_finish, // todo_flags_finish
+};
+
+class pass_gcn_simt_cbranch : public rtl_opt_pass
+{
+public:
+ pass_gcn_simt_cbranch (gcc::context *ctxt)
+ : rtl_opt_pass (pass_data_gcn_simt_cbranch, ctxt)
+ {}
+
+ // opt_pass methods:
+ virtual bool gate (function *);
+ virtual unsigned int execute (function *);
+};
+
+rtl_opt_pass *
+make_pass_gcn_simt_cbranch (gcc::context *ctxt)
+{
+ return new pass_gcn_simt_cbranch (ctxt);
+}
+
+bool
+pass_gcn_simt_cbranch::gate (function *)
+{
+ /* This pass is required for correctness whenever -msimt is enabled. */
+ return flag_simt_mode;
+}
+
+/* Transform the CFG in order to simplify later analysis and transformation.
+
+ Insert some fake edges to prevent abort/exit calls breaking dominance.
+ Simply looping back on itself is enough to prevent all the post-dominators
+ becoming EXIT_BLOCK. The EDGE_FAKE will be ignored during analysis and
+ removed during transformation.
+
+ Entry Entry
+ |\ |\,-----.
+ | Abort > | Abort |
+ | | `-----'
+ Exit Exit
+
+ This will not handle proper sibcalls, but GCN doesn't have machine
+ description patterns for those in any case.
+*/
+
+static void
+work_around_noreturn_blocks (function *fn)
+{
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, fn)
+ {
+ if (EDGE_COUNT (bb->succs) == 0)
+ {
+ /* Select a destination block that will preserve the dominators
+ information, and allow post-dominators to choose something
+ more useful than EXIT_BLOCK. */
+ basic_block dest = bb;
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "Inserting fake back edge bb %d -> bb %d\n",
+ bb->index, dest->index);
+ make_edge (bb, dest, EDGE_FAKE | EDGE_FALLTHRU);
+ }
+ }
+}
+
+/* Transform the CFG in order to simplify later analysis and transformation.
+
+ Whenever a block is both a fork and a join, split the block in two.
+
+ A B A B
+ \ / \ /
+ C > C' <- new
+ / \ |
+ X Y C
+ / \
+ X Y
+*/
+
+static void
+separate_forks_from_joins (function *fn)
+{
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, fn)
+ {
+ if (EDGE_COUNT (bb->preds) > 1 && EDGE_COUNT (bb->succs) > 1)
+ {
+ /* The split point is somewhat arbitrary, but it needs to be
+ after the initial label and note instructions. */
+ rtx_insn *insn;
+ FOR_BB_INSNS (bb, insn)
+ {
+ if (!LABEL_P (insn) && !NOTE_P (insn))
+ break;
+ }
+
+ split_block (bb, PREV_INSN (insn));
+ }
+ }
+}
+
+/* These are helper types for simplify_join_blocks. */
+
+struct fork_join {
+ basic_block fork, join;
+ vec<int> edges_to_redirect;
+
+ fork_join ()
+ {
+ edges_to_redirect.create (16);
+ }
+ /* Don't destruct the vec to avoid double free.
+ The garbage collector will get the left over vectors. */
+};
+
+static int
+fj_cmp (const void *A, const void *B)
+{
+ int lenA = (*(const struct fork_join *const *)A)->edges_to_redirect.length
();
+ int lenB = (*(const struct fork_join *const *)B)->edges_to_redirect.length
();
+
+ return (lenA == lenB ? 0
+ : lenA > lenB ? 1
+ : -1);
+}
+
+// FIXME: encapsulate first_new_bb
+static int first_new_bb = 0;
+static bool
+callback_join_edges (edge e, void *data)
+{
+ auto active_fj = (struct fork_join *)data;
+
+ while (e->src->index >= first_new_bb
+ || e->src->index == active_fj->join->index)
+ e = EDGE_PRED (e->src, 0);
+ return active_fj->edges_to_redirect.contains (e->src->index);
+}
+
+/* Transform the CFG in order to simplify later analysis and transformation.
+
+ For each conditional branch, ensure that the dominators and
+ post-dominators are not shared with other conditional regions.
+ That is, ensure that multiple forks do not share a join, by inserting
+ additional empty basic blocks.
+
+ A A
+ |\ |\
+ B C B C
+ | |\ > | |\
+ | E F | E F
+ |/,-' | |/
+ Z | Y <- new
+ |/
+ Z
+
+ We scan first and modify after so as not to break dominance info, but
+ also because the new blocks need to be created properly nested. */
+
+static void
+simplify_join_blocks (function *fn)
+{
+ if (dump_file)
+ fprintf (dump_file, "\nSimplifying shared join blocks...\n");
+
+ calculate_dominance_info (CDI_DOMINATORS);
+ calculate_dominance_info (CDI_POST_DOMINATORS);
+
+ auto_vec<struct fork_join> worklist;
+
+ /* Find the join blocks that are shared by multiple forks. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, fn)
+ {
+ /* Blocks without multiple successors can't be conditional. */
+ if (EDGE_COUNT (bb->succs) < 2)
+ continue;
+
+ basic_block join_bb = get_immediate_dominator (CDI_POST_DOMINATORS, bb);
+ if (EDGE_COUNT (join_bb->preds) < 2)
+ /* Short-cut the trivial case. */
+ continue;
+
+ struct fork_join fj;
+ fj.fork = bb;
+ fj.join = join_bb;
+
+ /* Make a list of all the edges that come from bb. */
+ for (edge e : join_bb->preds)
+ if (e->src == bb
+ || dominated_by_p (CDI_DOMINATORS, e->src, bb))
+ fj.edges_to_redirect.safe_push (e->src->index);
+
+ /* Ignore cases where all (or no, or one) edges come from bb. */
+ if (fj.edges_to_redirect.length () > 1
+ && fj.edges_to_redirect.length () != EDGE_COUNT (join_bb->preds))
+ {
+ worklist.safe_push (fj);
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "bb %d joins too many forks;"
+ " splitting edges originating at bb %d\n",
+ fj.join->index, fj.fork->index);
+ }
+ }
+
+ /* make_forwarder_block does not seem to leave the dominator info in a
+ usable state. */
+ free_dominance_info (CDI_POST_DOMINATORS);
+ free_dominance_info (CDI_DOMINATORS);
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "%d joins to fix up\n\n", worklist.length ());
+
+ if (worklist.is_empty ())
+ return;
+
+ /* Sort the worklist such that the smallest sets of joins come first.
+ By construction, these are the ones that feed into the larger sets,
+ so must come first. */
+ auto_vec<struct fork_join *, 8> sorted_worklist (worklist.length ());
+ for (auto& fj : worklist)
+ sorted_worklist.quick_push (&fj);
+ sorted_worklist.qsort (fj_cmp);
+
+ /* Insert new join blocks such that each one corresponds to one fork.
+ As we insert the blocks the pointers in worklist become obsolete,
+ so we track a simple set of mappings to the new correct value. */
+ first_new_bb = last_basic_block_for_fn (fn);
+ basic_block *bb_mappings = (basic_block *) alloca (sizeof (basic_block)
+ * first_new_bb);
+ memset (bb_mappings, 0, sizeof (basic_block) * first_new_bb);
+ for (auto fj : sorted_worklist)
+ {
+ int orig_bb_id = fj->join->index;
+ basic_block join_bb = (bb_mappings[orig_bb_id]
+ ? bb_mappings[orig_bb_id]
+ : fj->join);
+
+ edge newedge = make_forwarder_block (join_bb, callback_join_edges, fj);
+ bb_mappings[orig_bb_id] = newedge->dest;
+ }
+
+ /* The transformations might have exposed some more cases, so repeat. */
+ simplify_join_blocks (fn);
+}
+
+/* Helpers for unify_joining_back_edges. */
+
+static bool
+callback_back_edges (edge e, void *)
+{
+ return e->flags & EDGE_DFS_BACK;
+}
+
+static bool
+callback_forward_edges (edge e, void *)
+{
+ return !(e->flags & EDGE_DFS_BACK);
+}
+
+/* Transform the CFG in order to simplify later analysis and transformation.
+
+ For each block that receives multiple loop back edges, create a new block
+ that joins those before the back edge.
+
+ | .---. |,---.
+ |/,-. | A |
+ A | | |\ |
+ |`--' | > B | |
+ B | |\| |
+ |`----' | C--' <- new
+ Z Z
+
+ Do the same for the non-back edges, so that there's a clear dominator.
+
+ A B A B
+ |/,-. |/
+ L | > C <- new
+ |`--' |,-.
+ Z L |
+ |`-'
+ Z
+*/
+
+static void
+unify_joining_back_edges (function *fn)
+{
+ if (dump_file)
+ fprintf (dump_file, "\nUnifying shared loop back blocks...\n");
+
+ /* Find the join blocks that are shared by multiple back edges. */
+ mark_dfs_back_edges (fn);
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, fn)
+ {
+ int total_incoming_edges = EDGE_COUNT (bb->preds);
+ int incoming_back_edges = 0;
+
+ for (edge e : bb->preds)
+ if (e->flags & EDGE_DFS_BACK)
+ incoming_back_edges++;
+
+ if (incoming_back_edges > 1)
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "bb %d joins %d back-edges;"
+ " inserting a forwarder block\n",
+ bb->index, incoming_back_edges);
+
+ edge newloopback = make_forwarder_block (bb, callback_back_edges,
+ NULL);
+
+ /* Fix up the edge flags so we can use them in the analysis.
+ Note: the back edges shown in the dot graph dumps are
+ calculated independently of what we store here. */
+ newloopback->flags |= EDGE_DFS_BACK;
+ for (edge e : bb->preds)
+ e->flags &= ~EDGE_DFS_BACK;
+ }
+
+ if ((total_incoming_edges - incoming_back_edges) > 1)
+ make_forwarder_block (bb, callback_forward_edges, NULL);
+ }
+}
+
+/* Transform the CFG in order to simplify later analysis and transformation.
+
+ For each block that has two outgoing edges, ensure that none of the
+ successors have multiple incoming edges. Insert additional empty blocks to
+ give the separation.
+
+ A B A B
+ |\ / |\ |
+ C D > C X | <- new
+ |/ | |/
+ Z | D
+ |/
+ Z
+*/
+
+static void
+separate_forking_blocks (function *fn)
+{
+ if (dump_file)
+ fprintf (dump_file, "\nSeparating successive conditionals...\n");
+
+ /* Find the join blocks that are shared by multiple back edges. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, fn)
+ {
+ if (EDGE_COUNT (bb->succs) < 2)
+ continue;
+
+ for (edge e : bb->succs)
+ if (EDGE_COUNT (e->dest->preds) > 1)
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file,
+ "edge bb %d -> bb %d connects a fork to a join directly;"
+ " inserting a separator block\n", e->src->index,
+ e->dest->index);
+
+ split_edge (e);
+ }
+ }
+}
+
+/* Scan and record a region of basic blocks reachable from START
+ without passing through the blocks present in LIMITS.
+
+ All other fields are derived from START and LIMITS.
+
+ BLOCKS includes all blocks within the region, including any of the
+ blocks from LIMITS if they actually are reachable, but does not include
+ blocks from which there is no path to any of the LIMITS.
+
+ REACHABLE includes all the blocks reachable from START, all the way to
+ the function exit block, without passing through LIMITS. This is a
+ superset of BLOCKS.
+
+ ANCESTORS includes all the blocks that have a path to LIMITS without
+ passing through START. This will be a superset of BLOCKS. It is only
+ computed when DO_REVERSE_SCAN is set.
+
+ PARENTS includes all blocks that are not themselves within the region
+ described by BLOCKS, but have edges coming into the region, excluding
+ parents of blocks (only) in LIMITS, or of START itself. This is a subset
+ of ANCESTORS.
+
+ EXITS includes all blocks that are not themselves within the region
+ described by BLOCKS, but are alternative successors of blocks within
+ BLOCKS. This is a subset of REACHABLE. It is only computed when
+ DO_REVERSE_SCAN is set. */
+
+class bbregion {
+public:
+ basic_block start;
+ bitmap_head limits;
+
+ bitmap_head blocks;
+ bitmap_head reachable;
+ bitmap_head ancestors;
+ bitmap_head parents;
+ bitmap_head exits;
+
+ /* Empty constructor for deferred initialization via init. */
+ bbregion ()
+ : start(NULL), do_reverse_scan(false)
+ {
+ bitmap_initialize (&blocks, NULL);
+ bitmap_initialize (&reachable, NULL);
+ bitmap_initialize (&ancestors, NULL);
+ bitmap_initialize (&parents, NULL);
+ bitmap_initialize (&exits, NULL);
+ bitmap_initialize (&limits, NULL);
+ }
+
+ /* Construct and initialize in one step. */
+ bbregion (basic_block start, const_bitmap limits, function *fn)
+ : bbregion()
+ {
+ init (start, limits, fn);
+ }
+
+ /* For deferred init pattern only. */
+ void init (basic_block start, const_bitmap limits, function *fn)
+ {
+ this->start = start;
+ bitmap_copy (&this->limits, limits);
+
+ scan_region (fn);
+ }
+
+ ~bbregion ()
+ {
+ bitmap_clear (&blocks);
+ bitmap_clear (&reachable);
+ bitmap_clear (&ancestors);
+ bitmap_clear (&parents);
+ bitmap_clear (&exits);
+ bitmap_clear (&limits);
+ }
+
+ void dump (FILE *fd, const char *indent) const
+ {
+ fprintf (fd, "%sbbregion: bb %d -> ", indent, start->index);
+ dump_bitmap (fd, &limits);
+ fprintf (fd, "%s reachable: ", indent);
+ dump_bitmap (fd, &reachable);
+ fprintf (fd, "%s ancestors: ", indent);
+ dump_bitmap (fd, &ancestors);
+ fprintf (fd, "%s blocks: ", indent);
+ dump_bitmap (fd, &blocks);
+ fprintf (fd, "%s parents: ", indent);
+ dump_bitmap (fd, &parents);
+ fprintf (fd, "%s exits: ", indent);
+ dump_bitmap (fd, &exits);
+ }
+
+protected:
+ /* Set this to control the scan mode. Loops need the scan both forward
+ and backward to fully determine the bitmaps, but that's expensive
+ and not needed for conditionals, so we can turn it off here. */
+ bool do_reverse_scan;
+
+ /* Initiate the CFG scans and populate the bitmaps. */
+ void scan_region (function *fn)
+ {
+ /* Walking the region forward finds all reachable blocks, including
+ blocks reachable from loop exits and such like. This is sufficient
+ when "limit" includes the post-dominator of the region. */
+ scan_forward (start);
+
+ if (!do_reverse_scan)
+ {
+ bitmap_copy (&blocks, &reachable);
+ return;
+ }
+
+ /* Walking the region backwards finds all the ancestors of the blocks
+ within the region, which allows us to eliminate blocks that are
+ reachable from the start point, but do not themselves have a path
+ to the "limit" blocks. This is most useful for loop regions that
+ have early exit paths. */
+ bitmap_iterator bi;
+ unsigned num;
+ EXECUTE_IF_SET_IN_BITMAP (&limits, 0, num, bi)
+ scan_reverse (BASIC_BLOCK_FOR_FN (fn, num));
+
+ /* The intersection of the two scans gives the blocks inside the
+ region. */
+ bitmap_and (&blocks, &reachable, &ancestors);
+
+ if (bitmap_empty_p (&blocks) && !bitmap_empty_p (&reachable))
+ {
+ /* The reachable blocks did not include any of the limits.
+ This happens for early function exits (e.g. "abort"). */
+ bitmap_copy (&blocks, &reachable);
+ return;
+ }
+
+ /* Remove "parent" bits set by the forwards scan, if the reverse scan
+ showed that they're not actually parents. */
+ int delayed_clear = -1;
+ EXECUTE_IF_SET_IN_BITMAP (&parents, 0, num, bi)
+ {
+ if (delayed_clear >= 0)
+ {
+ bitmap_clear_bit (&parents, delayed_clear);
+ delayed_clear = -1;
+ }
+
+ bool found = false;
+ for (edge e : BASIC_BLOCK_FOR_FN (fn, num)->succs)
+ if (bitmap_bit_p (&blocks, e->dest->index))
+ {
+ found = true;
+ break;
+ }
+ if (!found)
+ delayed_clear = num;
+ }
+ if (delayed_clear >= 0)
+ bitmap_clear_bit (&parents, delayed_clear);
+ }
+
+ /* Recursive graph walk from BB to any of LIMITS, filling in REACHABLE
+ and PARENTS. REACHABLE serves as the visited list. */
+ void scan_forward (basic_block bb)
+ {
+ if (bitmap_bit_p (&reachable, bb->index))
+ return;
+
+ bitmap_set_bit (&reachable, bb->index);
+ bitmap_clear_bit (&parents, bb->index);
+
+ if (bitmap_bit_p (&limits, bb->index))
+ return;
+
+ if (bb != start)
+ for (edge e : bb->preds)
+ if (!(e->flags & EDGE_FAKE)
+ && !bitmap_bit_p (&reachable, e->src->index))
+ bitmap_set_bit (&parents, e->src->index);
+
+ for (edge e : bb->succs)
+ if (!(e->flags & EDGE_FAKE))
+ scan_forward (e->dest);
+ }
+
+ /* Recursive graph walk from BB to START, filling in ANCESTORS and EXITS.
+ ANCESTORS serves as the visited list. */
+ void scan_reverse (basic_block bb)
+ {
+ if (bitmap_bit_p (&ancestors, bb->index))
+ return;
+
+ bitmap_set_bit (&ancestors, bb->index);
+ bitmap_clear_bit (&exits, bb->index);
+
+ if (!bitmap_bit_p (&limits, bb->index))
+ for (edge e : bb->succs)
+ if (!(e->flags & EDGE_FAKE)
+ && !bitmap_bit_p (&ancestors, e->dest->index))
+ bitmap_set_bit (&exits, e->dest->index);
+
+ if (bb == start)
+ return;
+
+ for (edge e : bb->preds)
+ if (!(e->flags & EDGE_FAKE))
+ scan_reverse (e->src);
+ }
+};
+
+/* A subclass for bbregions that represents loops.
+
+ START is the loop-head block, that has the incoming back-edge.
+ LIMITS is the block at the start of the back-edge. */
+
+class loop_region : public bbregion
+{
+public:
+ loop_region (basic_block start, function *fn)
+ : bbregion()
+ {
+ caller = NULL;
+ for (edge e : start->preds)
+ {
+ if (e->flags & EDGE_FAKE)
+ continue;
+
+ if (e->flags & EDGE_DFS_BACK)
+ {
+ /* unify_joining_back_edges should have ensured there's only one
+ incoming back-edge. */
+ gcc_assert (this->start == NULL);
+
+ auto_bitmap limits;
+ bitmap_set_bit (limits, e->src->index);
+
+ /* Configure the scan to exclude exit paths. */
+ do_reverse_scan = true;
+ init (start, limits, fn);
+ }
+ else
+ {
+ /* unify_joining_back_edges should have ensured there's only one
+ incoming regular edge. */
+ gcc_assert (caller == NULL);
+ caller = e->src;
+ }
+ }
+ }
+
+ /* The parent block that enters the loop but is not in the loop. */
+ basic_block caller;
+
+ /* Each exit point has its own pseudoreg with its own mask. */
+ hash_map<int_hash<int, -1, -2>, rtx> exit_conditions;
+
+ void dump (FILE *fd, const char *indent) const
+ {
+ fprintf (fd, "%sloop_region: bb %d -> ", indent, start->index);
+ dump_bitmap (fd, &limits);
+ fprintf (fd, "%s caller: bb %d\n", indent, caller->index);
+ for (auto exit : exit_conditions)
+ {
+ fprintf (fd, "\n%s exit via bb %d: ", indent, exit.first);
+ dump_value_slim (fd, exit.second, 1);
+ }
+ fprintf (fd, "\n ");
+ ((const bbregion *) this)->dump (fd, indent);
+ }
+};
+
+/* A subclass for bbregions that represent fork/branch/join conditionals.
+
+ START is the fork block.
+ LIMITS contains the post-dominator block.
+ JOIN is also the post-dominator block. */
+
+class cond_region : public bbregion
+{
+public:
+ cond_region (basic_block fork, function *fn)
+ {
+ join = get_immediate_dominator (CDI_POST_DOMINATORS, fork);
+
+ auto_bitmap limits;
+ bitmap_set_bit (limits, join->index);
+ init (fork, limits, fn);
+
+ false_branch_condvar = NULL;
+ true_branch_condvar = NULL;
+ false_is_loop_exit = false;
+ true_is_loop_exit = false;
+ }
+
+ /* The join block. (Use bbregion::start for the fork block.) */
+ basic_block join;
+
+ /* The mask variables that will be set for the transformed CFG. */
+ rtx false_branch_condvar;
+ rtx true_branch_condvar;
+ bool false_is_loop_exit;
+ bool true_is_loop_exit;
+
+ void dump (FILE *fd, const char *indent) const
+ {
+ fprintf (fd, "%scond_region: fork bb %d -> join bb %d\n", indent,
+ start->index, join->index);
+ ((const bbregion *) this)->dump (fd, indent);
+ }
+};
+
+DEBUG_FUNCTION void
+debug (const bbregion *obj)
+{
+ obj->dump (stderr, "");
+}
+
+DEBUG_FUNCTION void
+debug (const bbregion obj)
+{
+ debug (&obj);
+}
+
+DEBUG_FUNCTION void
+debug (const cond_region *obj)
+{
+ obj->dump (stderr, "");
+}
+
+DEBUG_FUNCTION void
+debug (const cond_region obj)
+{
+ debug (&obj);
+}
+
+DEBUG_FUNCTION void
+debug (const loop_region *obj)
+{
+ obj->dump (stderr, "");
+}
+
+DEBUG_FUNCTION void
+debug (const loop_region obj)
+{
+ debug (&obj);
+}
+
+/* This class holds all the pass state that would otherwise be global or
+ need passing around. There's only one instance per pass invocation. */
+
+class simt_cbranch_state {
+public:
+ function *fn;
+
+ /* All the loops in the function.
+ LOOPHEADS and LOOPCALLERS are indices into LOOPS. */
+ auto_vec<loop_region *> loops;
+ hash_map<int_hash<int, -1, -2>, loop_region *> loopheads;
+ hash_map<int_hash<int, -1, -2>, loop_region *> loopcallers;
+
+ /* All the conditions in the function, indexed by fork and joins blocks.
+ FORKS and JOINS are indices into CONDITIONALS. */
+ auto_vec<cond_region *> conditionals;
+ hash_map<int_hash<int, -1, -2>, cond_region *> forks;
+ hash_map<int_hash<int, -1, -2>, cond_region *> joins;
+
+ /* Computed block ordering and required condition masks for each. */
+ auto_vec<int> output_bb_seq; /* Sorted by order-of-output. */
+ auto_vec<rtx> output_bb_condmasks; /* Indexed by BB ID. */
+
+ /* The data needed to plan each short-cut branch. */
+ struct short_cut_plan
+ {
+ basic_block src, dest; /* Blocks for the new edge. */
+ rtx_insn *setexec_insn; /* The place to split the SRC block. */
+ hash_set<rtx> skipped_cvars; /* Condition variables to be handled. */
+ };
+ auto_vec<short_cut_plan> cuts;
+
+ simt_cbranch_state (function *fn)
+ : fn(fn)
+ {
+ output_bb_seq.reserve (last_basic_block_for_fn (fn));
+ output_bb_condmasks.safe_grow_cleared (last_basic_block_for_fn (fn));
+ }
+
+ loop_region *check_overlaps (bbregion *conditional);
+ void scan_cfg ();
+ void determine_output_bb_sequence ();
+ void transform_cfg ();
+ void plan_short_cut_branches ();
+ void insert_short_cut_branches ();
+ void handle_simt_enter_exit ();
+
+ void execute ()
+ {
+ calculate_dominance_info (CDI_POST_DOMINATORS);
+ mark_dfs_back_edges (fn);
+
+ scan_cfg ();
+ determine_output_bb_sequence ();
+ transform_cfg ();
+ plan_short_cut_branches ();
+ insert_short_cut_branches ();
+ handle_simt_enter_exit ();
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "\n");
+
+ free_dominance_info (CDI_POST_DOMINATORS);
+ }
+};
+
+/* Returns the outermost loop that overlaps CONDITIONAL.
+ The function also serves to add potentially useful information to the
+ TDF_DETAILS dumps. */
+
+loop_region *
+simt_cbranch_state::check_overlaps (bbregion *conditional)
+{
+ loop_region *result = NULL;
+
+ for (auto loop : loops)
+ {
+ auto_bitmap intersection;
+ bitmap_and (intersection, &loop->blocks, &conditional->blocks);
+ if (dump_file && (dump_flags & TDF_DETAILS)
+ && bitmap_equal_p (&conditional->blocks, intersection))
+ fprintf (dump_file, " Condition is within loop at %d\n",
+ loop->start->index);
+ else if (dump_file && (dump_flags & TDF_DETAILS)
+ && !bitmap_empty_p (intersection))
+ fprintf (dump_file,
+ " Condition partially overlaps loop at %d\n",
+ loop->start->index);
+
+ if (bitmap_bit_p (&loop->exits, conditional->start->index))
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " Branch is loop exit\n");
+
+ /* Return the outermost loop if this exits multiple loops. */
+ if (!result
+ || !bitmap_bit_p (&result->blocks, loop->start->index))
+ result = loop;
+ }
+ }
+
+ return result;
+}
+
+/* Return true if all of the CFG direct dependencies (predecessors) of BB
+ are marked in DONE. */
+
+static bool
+block_dependencies_done_p (basic_block bb, bitmap done)
+{
+ for (edge e : bb->preds)
+ if (!(e->flags & EDGE_FAKE)
+ && !(e->flags & EDGE_DFS_BACK)
+ && !bitmap_bit_p (done, e->src->index))
+ return false;
+ return true;
+}
+
+/* Like emit_insn_before/after, but inserts the instruction at the head of
+ a block, after the special insns. */
+
+static rtx_insn *
+emit_insn_at_bb_head (rtx x, basic_block bb)
+{
+ rtx_insn *insn;
+ FOR_BB_INSNS (bb, insn)
+ {
+ if (NONDEBUG_INSN_P (insn))
+ return emit_insn_before (x, insn);
+ }
+ return emit_insn_after (x, BB_END (bb));
+}
+
+/* Scan the CFG and build the LOOPS and CONDITIONALS tables. */
+
+void
+simt_cbranch_state::scan_cfg ()
+{
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "\nScanning block dependencies...\n");
+
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, fn)
+ {
+ for (edge e : bb->preds)
+ {
+ if (e->flags & EDGE_DFS_BACK
+ && !(e->flags & EDGE_FAKE))
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " scanning loop...\n");
+
+ loop_region *loop = new loop_region (bb, fn);
+ loops.safe_push (loop);
+ loopheads.put (loop->start->index, loop);
+ loopcallers.put (loop->caller->index, loop);
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ loop->dump (dump_file, " ");
+ }
+ }
+
+ if (EDGE_COUNT (bb->succs) == 2)
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " scanning conditional...\n");
+
+ cond_region *region = new cond_region (bb, fn);
+ conditionals.safe_push (region);
+ forks.put (region->start->index, region);
+ joins.put (region->join->index, region);
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ region->dump (dump_file, " ");
+ }
+ }
+}
+
+/* Determine a suitable linear SIMT ordering for the entire CFG.
+ Populates the OUTPUT_BB_SEQ and OUTPUT_BB_CONDMASKS tables.
+
+ These values are used to score each block in the CFG. The score is used
+ when selecting a block from the deferred list. The order will have some
+ impact on optimization (register pressure, and block coalescing, perhaps),
+ but these values should not affect correctness (although special scores are
+ also used to keep the function exit last). */
+
+#define SCORE_INLOOP 8
+#define SCORE_NOEXIT 4
+#define SCORE_CONDITIONAL 1
+
+void
+simt_cbranch_state::determine_output_bb_sequence ()
+{
+ auto_vec<basic_block> deferred;
+ auto_bitmap visited;
+ auto_vec<int> scores (last_basic_block_for_fn (fn));
+ scores.quick_grow_cleared (last_basic_block_for_fn (fn));
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "\nScoring BB priorities...\n");
+
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, fn)
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " bb %d:", bb->index);
+
+ int score = 0;
+
+ /* Score priority based on nesting depth. Loops are
+ weighted higher than conditionals. */
+ for (auto loop : loops)
+ if (bitmap_bit_p (&loop->blocks, bb->index))
+ {
+ score += SCORE_INLOOP;
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " +loop");
+ }
+ for (auto c : conditionals)
+ {
+ if (bitmap_bit_p (&c->blocks, bb->index))
+ {
+ score += SCORE_CONDITIONAL;
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " +if");
+ }
+ }
+ if (FALLTHRU_EDGE (bb)->dest->index == EXIT_BLOCK
+ && NONDEBUG_INSN_P (BB_END (bb))
+ && GET_CODE (PATTERN (BB_END (bb))) == USE)
+
+ {
+ score -= 88888; /* Highly disparage epilogue blocks. */
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " -epilogue");
+ }
+
+ scores[bb->index] = score;
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " -> %d\n", score);
+ }
+ scores[EXIT_BLOCK] = -99999; /* Very highly disparage the EXIT_BLOCK. */
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " bb %d: -exit -> %d\n", EXIT_BLOCK,
+ scores[EXIT_BLOCK]);
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "\nDetermining an execution sequence ...\n");
+ bb = ENTRY_BLOCK_PTR_FOR_FN (fn);
+ while (1)
+ {
+ if (bb
+ && ((scores[bb->index] < 0 && !deferred.is_empty ())
+ || !block_dependencies_done_p (bb, visited)))
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "bb %d has unmet dependencies;"
+ " defer and select another...\n",
+ bb->index);
+
+ deferred.safe_push (bb);
+ bb = NULL;
+ }
+
+ if (bb && bitmap_bit_p (visited, bb->index))
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "bb %d already visited; select another...\n",
+ bb->index);
+
+ bb = NULL;
+ }
+
+ if (!bb)
+ {
+ int candidate_score = -9999999;
+ bb = NULL;
+ for (auto deferred_bb : deferred)
+ if (!bitmap_bit_p (visited, deferred_bb->index)
+ && block_dependencies_done_p (deferred_bb, visited)
+ && scores[deferred_bb->index] > candidate_score)
+ {
+ candidate_score = scores[deferred_bb->index];
+ bb = deferred_bb;
+ }
+
+ if (bb && dump_file && (dump_flags & TDF_DETAILS))
+ {
+ fprintf (dump_file, "Available blocks:");
+ for (auto db : deferred)
+ {
+ if (bitmap_bit_p (visited, db->index))
+ ;
+ else if (!block_dependencies_done_p (db, visited))
+ fprintf (dump_file, " bb %d (unmet deps)", db->index);
+ else
+ fprintf (dump_file, " bb %d (score:%d)", db->index,
+ scores[db->index]);
+ }
+ fprintf (dump_file, "\n => Select bb %d\n", bb->index);
+ }
+ }
+ if (!bb)
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "No blocks remain; finished.\n");
+
+ break;
+ }
+
+ /* The next BB is now selected. */
+ output_bb_seq.quick_push (bb->index);
+ bitmap_set_bit (visited, bb->index);
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "==========> BB %d is next block\n", bb->index);
+
+ switch (EDGE_COUNT (bb->succs))
+ {
+ case 0:
+ bb = NULL;
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "Block has no successor\n");
+ break;
+ case 1:
+ bb = EDGE_SUCC (bb, 0)->dest;
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "Fall through to natural successor\n");
+ break;
+ case 2:
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "Fork point; need to select a branch...\n");
+
+ cond_region &all = **forks.get (bb->index);
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ {
+ fprintf (dump_file, " fork/join region %d => %d ....\n",
+ bb->index, all.join->index);
+ all.dump (dump_file, " ");
+ check_overlaps (&all);
+ }
+
+ auto_bitmap limits;
+ bitmap_set_bit (limits, all.start->index);
+ bitmap_set_bit (limits, all.join->index);
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " false_branch region %d => %d ....\n",
+ FALLTHRU_EDGE (bb)->dest->index, all.join->index);
+ bbregion false_branch (FALLTHRU_EDGE (bb)->dest, limits, fn);
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ false_branch.dump (dump_file, " ");
+ loop_region *false_branch_exits_loop = check_overlaps
(&false_branch);
+ all.false_is_loop_exit = (false_branch_exits_loop != NULL);
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " true_branch region %d => %d ....\n",
+ BRANCH_EDGE (bb)->dest->index, all.join->index);
+ bbregion true_branch (BRANCH_EDGE (bb)->dest, limits, fn);
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ true_branch.dump (dump_file, " ");
+ loop_region *true_branch_exits_loop = check_overlaps
(&true_branch);
+ all.true_is_loop_exit = (true_branch_exits_loop != NULL);
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ {
+ auto_bitmap intersection;
+ bitmap_and (intersection, &false_branch.blocks,
&true_branch.blocks);
+ fprintf (dump_file, " overlap: %d blocks\n",
+ bitmap_count_bits (intersection));
+ }
+
+ all.false_branch_condvar = gen_reg_rtx (DImode);
+ all.true_branch_condvar = gen_reg_rtx (DImode);
+
+ if (false_branch_exits_loop)
+ false_branch_exits_loop->exit_conditions.put (bb->index,
+
all.false_branch_condvar);
+ if (true_branch_exits_loop)
+ true_branch_exits_loop->exit_conditions.put (bb->index,
+
all.true_branch_condvar);
+
+ if (false_branch_exits_loop)
+ {
+ deferred.safe_push (false_branch.start);
+ if (!bitmap_bit_p (&false_branch.reachable, all.join->index))
+ scores[false_branch.start->index] += SCORE_NOEXIT;
+
+ /* What was the branch will now be the fall-through. */
+ bb = true_branch.start;
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file,
+ "Use true branch as fallthrough; defer BB %d\n",
+ false_branch.start->index);
+ }
+ else
+ {
+ deferred.safe_push (true_branch.start);
+ if (!bitmap_bit_p (&true_branch.reachable, all.join->index))
+ scores[true_branch.start->index] += SCORE_NOEXIT;
+
+ /* Continue to use the same fall-through as before. */
+ bb = false_branch.start;
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file,
+ "Use false branch as fallthrough; defer BB %d\n",
+ true_branch.start->index);
+ }
+ }
+ break;
+ default:
+ gcc_unreachable ();
+ }
+ }
+
+ /* Calculate the EXEC masks for each block in the new chain. */
+ for (auto num : output_bb_seq)
+ {
+ bb = BASIC_BLOCK_FOR_FN (fn, num);
+
+ if (bb->index == ENTRY_BLOCK)
+ output_bb_condmasks[bb->index] = gen_rtx_REG (DImode, EXEC_REG);
+ else
+ {
+ loop_region **lp = loopheads.get (bb->index);
+
+ if (lp)
+ /* The loop condition variable will have been initialized
+ in the "caller" block, and updated each iteration. */
+ output_bb_condmasks[bb->index] = gen_rtx_REG (DImode, EXEC_REG);
+ else
+ {
+ for (edge e : bb->preds)
+ {
+ if (e->flags & EDGE_FAKE)
+ continue;
+
+ if (e->flags & EDGE_DFS_BACK)
+ /* The condition_out from the other edge will already
+ have the loop condition initializer. */
+ continue;
+
+ cond_region **c = forks.get (e->src->index);
+ rtx newcond;
+
+ if (c)
+ newcond = ((e->flags & EDGE_FALLTHRU)
+ ? (*c)->false_branch_condvar
+ : (*c)->true_branch_condvar);
+ else
+ newcond = output_bb_condmasks[e->src->index];
+ if (output_bb_condmasks[bb->index] == newcond)
+ ;
+ else if (!output_bb_condmasks[bb->index])
+ output_bb_condmasks[bb->index] = newcond;
+ else
+ {
+ while (GET_CODE (newcond) == IOR)
+ {
+ rtx reg = XEXP (newcond, 0);
+ gcc_assert (REG_P (reg));
+ output_bb_condmasks[bb->index] =
+ gen_rtx_IOR (DImode, reg,
+ output_bb_condmasks[bb->index]);
+ newcond = XEXP (newcond, 1);
+ }
+ gcc_assert (REG_P (newcond));
+ output_bb_condmasks[bb->index] =
+ gen_rtx_IOR (DImode, newcond,
+ output_bb_condmasks[bb->index]);
+ }
+ }
+ }
+ }
+ }
+
+ if (dump_file)
+ {
+ fprintf (dump_file, "\n\nFinal ordering and masks:\n");
+ for (int num : output_bb_seq)
+ {
+ fprintf (dump_file, "BB %d\n", num);
+
+ fprintf (dump_file, " condition in: ");
+ dump_value_slim (dump_file, output_bb_condmasks[num], 1);
+ fprintf (dump_file, "\n");
+
+ loop_region **lp = loopcallers.get (num);
+ if (lp)
+ {
+ loop_region *loop = *lp;
+ for (auto condvar : loop->exit_conditions)
+ {
+ fprintf (dump_file, " init exit condvar: ");
+ dump_value_slim (dump_file, condvar.second, 1);
+ fprintf (dump_file, " (for loop at bb %d, exit at bb %d)\n",
+ loop->start->index, condvar.first);
+ }
+ }
+
+ cond_region **cp = forks.get (num);
+ if (cp)
+ {
+ cond_region *c = *cp;
+ fprintf (dump_file, " write condition var (false branch): ");
+ dump_value_slim (dump_file, c->false_branch_condvar, 1);
+ if (c->false_is_loop_exit)
+ fprintf (dump_file, " (loop exit condition)");
+ fprintf (dump_file, "\n");
+ fprintf (dump_file, " write condition var (true branch): ");
+ dump_value_slim (dump_file, c->true_branch_condvar, 1);
+ if (c->true_is_loop_exit)
+ fprintf (dump_file, " (loop exit condition)");
+ fprintf (dump_file, "\n");
+ }
+ }
+ }
+}
+
+/* Do the SIMT CFG transformations, according to OUTPUT_BB_SEQ.
+
+ At the end, the CFG has been linearized, the old branch instructions
+ have been removed, the mask handling insns have been inserted, and
+ the CUTS table has been initialized, ready for plan_short_cut_branches. */
+
+void
+simt_cbranch_state::transform_cfg ()
+{
+ if (dump_file)
+ fprintf (dump_file, "\n\nTransforming the CFG....\n");
+
+ /* Build a linear sequence of blocks, keeping only back-edges from
+ the existing CFG. */
+ basic_block bb, prev_bb = BASIC_BLOCK_FOR_FN (fn, 0);
+ for (int num : output_bb_seq)
+ {
+ if (num == 0)
+ continue;
+
+ bb = BASIC_BLOCK_FOR_FN (fn, num);
+ edge e;
+ edge_iterator ei;
+ for (ei = ei_start (bb->preds); (e = ei_safe_edge (ei)); )
+ {
+ /* Back edges are for loops and remain unchanged. */
+ if (e->flags & (EDGE_DFS_BACK | EDGE_FAKE))
+ {
+ ei_next (&ei);
+ continue;
+ }
+
+ /* Delete the edge. */
+ remove_edge (e);
+ }
+
+ if (/* Don't overwrite the flags if the edge already exists. */
+ !find_edge (prev_bb, bb))
+ make_edge (prev_bb, bb, EDGE_FALLTHRU);
+
+ prev_bb = bb;
+ }
+
+ /* Ensure that the edge flags are set so our new sequence are the
+ fallthru path. */
+ FOR_EACH_BB_FN (bb, fn)
+ {
+ switch (EDGE_COUNT (bb->succs))
+ {
+ case 1:
+ EDGE_SUCC (bb, 0)->flags |= EDGE_FALLTHRU;
+ break;
+ case 2:
+ for (edge e : bb->succs)
+ if (e->flags & (EDGE_DFS_BACK | EDGE_FAKE))
+ e->flags &= ~EDGE_FALLTHRU;
+ break;
+ default:
+ gcc_unreachable ();
+ }
+ }
+
+ /* This indicates that a BB writes to a condition variable, which will
+ force the next BB to reset EXEC on entry. */
+ auto_bitmap exec_invalidated;
+
+ /* Replace redundant cjump insns with the condition variables. */
+ for (auto c : conditionals)
+ {
+ auto cjump = as_a <rtx_jump_insn*> (BB_END (c->start));
+ rtx oldcondop = XEXP (SET_SRC (pc_set (cjump)), 0);
+ rtx oldcondreg = XEXP (oldcondop, 0);
+ rtx oldcondval = XEXP (oldcondop, 1);
+
+ if (!REG_P (oldcondreg))
+ oldcondreg = force_reg (GET_MODE (oldcondreg), XEXP (oldcondop, 0));
+
+ /* gcn.md defines cjump relative to zero, although the ISA
+ doesn't actually require that. If that ever changes, the
+ assumptions here are invalid. */
+ gcc_checking_assert (CONST_INT_P (oldcondval)
+ && INTVAL (oldcondval) == 0);
+ gcc_checking_assert (GET_CODE (oldcondop) == NE
+ || GET_CODE (oldcondop) == EQ);
+
+ start_sequence ();
+ rtx execreg = gen_rtx_REG (DImode, EXEC_REG);
+
+ /* TODO: Consider tracking BImode and DImode conditions separately?
+ Keeping BImode values as BImode might be better in some ways,
+ but it will need special handling throughout. BImode can
+ only happen when the write is known to be unconditional, right?
+ If so this might be a useful microoptimization. */
+ if (GET_MODE (oldcondreg) == BImode)
+ {
+ rtx direg = gen_reg_rtx (DImode);
+ emit_insn (gen_cselectdi (direg, oldcondreg, execreg, const0_rtx));
+ oldcondreg = direg;
+ }
+
+ /* FIXME This whole sequence can be optimized using s_andn2 including
+ the SCC output for the branch that likely follows. */
+ rtx inversemask = gen_reg_rtx (DImode);
+ emit_insn (gen_one_cmpldi2 (inversemask, oldcondreg));
+ emit_insn (gen_anddi3 (inversemask, inversemask, execreg));
+
+ /* The bits in oldcondreg are correct for NE comparisons, and inverted
+ for EQ comparisons. Swap the masks here, accordingly. */
+ rtx mask = oldcondreg;
+ if (GET_CODE (oldcondop) == EQ)
+ {
+ mask = inversemask;
+ inversemask = oldcondreg;
+ }
+
+ /* Branch exit conditions are cumulative as they should be executed
+ once after the loop has finished looping.
+ Regular condition variables are overwritten each time they are
+ encountered. */
+ if (c->true_is_loop_exit)
+ emit_insn (gen_iordi3 (c->true_branch_condvar,
+ c->true_branch_condvar, mask));
+ else
+ emit_move_insn (c->true_branch_condvar, mask);
+
+ if (c->false_is_loop_exit)
+ emit_insn (gen_iordi3 (c->false_branch_condvar,
+ c->false_branch_condvar, inversemask));
+ else
+ emit_move_insn (c->false_branch_condvar, inversemask);
+
+ emit_insn_before (end_sequence (), cjump);
+ delete_insn (cjump);
+
+ bitmap_set_bit (exec_invalidated, c->start->index);
+ }
+
+ /* Insert the loop exit condition initializers. */
+ for (auto loop : loops)
+ {
+ start_sequence ();
+ emit_insn (gen_exec_barrier ());
+
+ /* Initialize the cumulative exit masks for each exit point. */
+ for (auto condvar : loop->exit_conditions)
+ emit_move_insn (condvar.second, const0_rtx);
+
+ emit_insn (gen_exec_barrier ());
+ emit_insn_after (end_sequence (), BB_END (loop->caller));
+
+ bitmap_set_bit (exec_invalidated, loop->caller->index);
+ }
+
+ /* Delete empty BBs. These come from when we simplified the dominator
+ relationships tree. */
+ basic_block next_bb = NULL;
+ for (bb = fn->cfg->x_entry_block_ptr->next_bb;
+ bb->index != EXIT_BLOCK;
+ bb = next_bb)
+ {
+ next_bb = bb->next_bb;
+
+ /* Is the block empty? */
+ if (!NOTE_INSN_BASIC_BLOCK_P (BB_END (bb)))
+ continue;
+
+ /* Is this a block that will have a branch written? */
+ if (EDGE_COUNT (bb->preds) != 1
+ || EDGE_COUNT (bb->succs) != 1)
+ continue;
+
+ if (dump_file)
+ fprintf (dump_file, "Cleaning up empty bb %d\n", bb->index);
+
+ /* The block can be deleted. */
+ redirect_edge_succ (EDGE_PRED (bb, 0), EDGE_SUCC (bb, 0)->dest);
+ delete_basic_block (bb);
+ }
+
+ /* Ensure that there's a block to set the exit mask.
+ (Unless the function has an infinite loop, or otherwise no path
+ to EXIT_BLOCK.) */
+ bb = BASIC_BLOCK_FOR_FN (fn, EXIT_BLOCK);
+ if (EDGE_COUNT (bb->preds) > 0
+ && (output_bb_condmasks[bb->index]
+ != output_bb_condmasks[EDGE_PRED (bb, 0)->src->index]))
+ {
+ basic_block newbb = split_edge (EDGE_PRED (bb, 0));
+ if (output_bb_condmasks.length () < (unsigned) newbb->index + 1)
+ output_bb_condmasks.safe_grow_cleared (newbb->index + 1);
+ output_bb_condmasks[newbb->index] = output_bb_condmasks[bb->index];
+ }
+
+ /* Insert the output_bb_condmasks variables. */
+ FOR_EACH_BB_FN (bb, fn)
+ {
+ gcc_assert (output_bb_condmasks[bb->index]);
+
+ if (REG_P (output_bb_condmasks[bb->index])
+ && REGNO (output_bb_condmasks[bb->index]) == EXEC_REG)
+ continue;
+
+ bool cond_already_set = true;
+ for (edge e : bb->preds)
+ {
+ if (bitmap_bit_p (exec_invalidated, e->src->index)
+ || (output_bb_condmasks[e->src->index]
+ != output_bb_condmasks[bb->index]))
+ cond_already_set = false;
+ }
+
+ if (cond_already_set)
+ continue;
+
+ short_cut_plan cut;
+ cut.src = bb;
+
+ start_sequence ();
+ emit_insn (gen_exec_barrier ());
+ /* TODO memoize the force_operand?? */
+ rtx mask = force_operand (output_bb_condmasks[bb->index], NULL);
+ cut.setexec_insn = emit_insn (gen_setexec (mask));
+ emit_insn (gen_exec_barrier ());
+ emit_insn_at_bb_head (end_sequence (), bb);
+
+ cuts.safe_push (cut);
+ }
+}
+
+/* Plan the short-cut branches.
+
+ The CUTS table has been initialized already, with the SRC block and
+ SETEXEC_INSN fields, in transform_cfg (because it was convenient to do so).
+ Populate the rest of the fields now. */
+
+void
+simt_cbranch_state::plan_short_cut_branches ()
+{
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "\nPlanning short-cut branches ...\n");
+
+ /* Plan the short-cut edges, for when all bits in the execution mask are
+ zero. This shorter code-path is an optimization, but it also functions
+ as an inhibitor for later compiler passes that aren't aware that the
+ blocks are masked and try to "optimize" apparently duplicate code.
+
+ We already identified the starting points, but we need to find the
+ end points. In the simplest case, in which the start BB uses a different
+ output_bb_condmasks to the only successor BB and there are no writes to
+ condition variables, we simply jump to the successor BB.
+
+ If the successor BB uses the same condition, or a subset-condition, then
+ we can skip both BBs, as so on.
+
+ If the short-cut jump would skip writing to a condition variable then
+ we know that that condition would also be zero. */
+ for (auto& cut : cuts)
+ {
+ basic_block bb = cut.src;
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ {
+ fprintf (dump_file,
+ "Considering short-cut branch starting before bb %d\n"
+ " cvars:",
+ bb->index);
+ }
+
+ /* In the case that the short-cut branch is taken, all the components
+ of the output_bb_condmasks mask must have been zero.
+ Make a note of this now so we can track it later.
+ output_bb_condmasks looks like (ior reg (ior reg reg)) */
+ hash_set<rtx> known_zero;
+ rtx x = output_bb_condmasks[bb->index];
+ while (x)
+ {
+ rtx reg = x;
+ if (GET_CODE (x) == IOR)
+ {
+ reg = XEXP (x, 0);
+ x = XEXP (x, 1);
+ }
+ else
+ x = NULL;
+
+ gcc_assert (REG_P (reg));
+ known_zero.add (reg);
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ {
+ fprintf (dump_file, " ");
+ dump_value_slim (dump_file, reg, 1);
+ }
+ }
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "\n");
+
+ /* Walk the fallthrough blocks starting at cut.src to determine how
+ skipping it will affect future decisions, and to see how many blocks
+ we can safely short-cut, in the case the mask is all-zeros. */
+ while (1)
+ {
+ /* We can't short-cut to the function exit, and loop back edges
+ should already have been taken care of, so we're done walking. */
+ if (EDGE_COUNT (bb->succs) == 0)
+ {
+ cut.dest = cut.src;
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file,
+ " Reached end of function;"
+ " should never need short cut\n");
+ break;
+ }
+ if (FALLTHRU_EDGE (bb)->flags & (EDGE_FAKE | EDGE_DFS_BACK))
+ {
+ cut.dest = bb;
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file,
+ " No onward route; short cut ends at bb %d\n",
+ bb->index);
+ break;
+ }
+ basic_block next_bb = FALLTHRU_EDGE (bb)->dest;
+
+ /* Skipping a block that writes to a condition mask that is then
+ used later would be an error. However, writing to a mask when
+ no lanes are active is guaranteed to write zero, so we just
+ ensure that this happens on both main branch and short-cut branch.
+ If it ends up redundant the write should be optimized away in
+ a later pass. */
+ cond_region **c = forks.get (bb->index);
+ if (c)
+ {
+ if (!(*c)->true_is_loop_exit)
+ {
+ cut.skipped_cvars.add ((*c)->true_branch_condvar);
+ known_zero.add ((*c)->true_branch_condvar);
+ }
+ if (!(*c)->false_is_loop_exit)
+ {
+ cut.skipped_cvars.add ((*c)->false_branch_condvar);
+ known_zero.add ((*c)->false_branch_condvar);
+ }
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ {
+ fprintf (dump_file, " Bypassed cvars: ");
+ dump_value_slim (dump_file, (*c)->true_branch_condvar, 1);
+ fprintf (dump_file, " ");
+ dump_value_slim (dump_file, (*c)->false_branch_condvar, 1);
+ fprintf (dump_file, "\n");
+ }
+ }
+
+ loop_region **l = loopcallers.get (bb->index);
+ if (l)
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " Bypassed loop exit cvars: ");
+ for (auto condvar : (*l)->exit_conditions)
+ {
+ cut.skipped_cvars.add (condvar.second);
+ known_zero.add (condvar.second);
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ {
+ fprintf (dump_file, " ");
+ dump_value_slim (dump_file, condvar.second, 1);
+ }
+ }
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "\n");
+ }
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ {
+ fprintf (dump_file, " Check conditions for next bb %d\n",
+ next_bb->index);
+ }
+
+ /* Check if the condition mask on the next block will also be zero,
+ or not known. If zero we will loop and short-cut that block
+ also.
+ output_bb_condmasks looks like (ior reg (ior reg reg)) */
+ bool confirmed_zero = true;
+ x = output_bb_condmasks[next_bb->index];
+ while (x)
+ {
+ rtx reg = x;
+ if (GET_CODE (x) == IOR)
+ {
+ reg = XEXP (x, 0);
+ x = XEXP (x, 1);
+ }
+ else
+ x = NULL;
+
+ gcc_assert (REG_P (reg));
+ if (REGNO (reg) != EXEC_REG
+ && !known_zero.contains (reg))
+ {
+ confirmed_zero = false;
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ {
+ fprintf (dump_file, " Possible non-zero cvar: ");
+ dump_value_slim (dump_file, reg, 1);
+ fprintf (dump_file, "\n");
+ }
+ break;
+ }
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ {
+ fprintf (dump_file, " Cvar would be zero: ");
+ dump_value_slim (dump_file, reg, 1);
+ fprintf (dump_file, "\n");
+ }
+ }
+ if (!confirmed_zero)
+ {
+ cut.dest = next_bb;
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " -> short-cut ends at bb %d\n",
+ next_bb->index);
+ break;
+ }
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, " -> short-cut can continue past bb %d\n",
+ next_bb->index);
+
+ bb = next_bb;
+ }
+ }
+}
+
+/* Insert the short-cut branches planning in CUTS, and clean up the CFG. */
+
+void
+simt_cbranch_state::insert_short_cut_branches ()
+{
+ if (dump_file)
+ fprintf (dump_file, "\nInserting short-cut branches ...\n");
+
+ /* Insert the short-cut edges. */
+ for (auto& cut : cuts)
+ {
+ if (cut.src == cut.dest)
+ continue;
+
+ rtx condvar = SET_SRC (XVECEXP (PATTERN (cut.setexec_insn), 0, 0));
+
+ /* Insert the short-cut route into the CFG. */
+ split_block (cut.src, PREV_INSN (cut.setexec_insn));
+ edge new_shortcut = make_edge (cut.src, cut.dest, 0);
+
+ /* Add the short-cut branch insn.
+ It would be fewer instructions to use EXECZ, but once the mask
+ has become zero it might be difficult to handle scalars safely. */
+ rtx shortcut_label = block_label (cut.dest);
+ start_sequence ();
+
+ emit_insn (gen_cbranchdi4 (gen_rtx_EQ (DImode, NULL, NULL),
+ condvar, const0_rtx, shortcut_label));
+ emit_insn_after (end_sequence (), BB_END (cut.src));
+ JUMP_LABEL (BB_END (cut.src)) = shortcut_label;
+
+ /* Add any mask management to the short-cut branch. */
+ if (!cut.skipped_cvars.is_empty ())
+ {
+ basic_block shortcut_block = split_edge (new_shortcut);
+ start_sequence ();
+ for (rtx x : cut.skipped_cvars)
+ emit_move_insn (x, const0_rtx);
+ emit_insn (gen_exec_barrier ());
+ emit_insn_after (end_sequence (), BB_END (shortcut_block));
+ }
+ }
+
+ /* Remove now-redundant fallthru edges at the loop-back point, if needed. */
+ for (auto loop : loops)
+ {
+ basic_block final_bb = NULL;
+ edge back_edge = NULL;
+
+ for (auto e : loop->start->preds)
+ if (e->flags & EDGE_DFS_BACK)
+ {
+ back_edge = e;
+ final_bb = e->src;
+ break;
+ }
+ gcc_checking_assert (final_bb);
+ gcc_checking_assert (back_edge);
+
+ rtx_insn *final_insn = BB_END (final_bb);
+ gcc_checking_assert (!any_condjump_p (final_insn));
+
+ if (EDGE_COUNT (final_bb->succs) == 2)
+ {
+ edge other_edge = FALLTHRU_EDGE (final_bb);
+ if (other_edge == back_edge)
+ other_edge = BRANCH_EDGE (final_bb);
+
+ remove_edge (other_edge);
+ }
+ back_edge->flags &= ~EDGE_FALLTHRU;
+
+ if (!JUMP_P (final_insn))
+ {
+ emit_jump_insn_after (gen_jump (block_label (loop->start)),
+ final_insn);
+ emit_barrier_after_bb (final_bb);
+ }
+ }
+
+ /* Remove now-redundant edges from no-exit blocks. */
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, fn)
+ {
+ /* Find blocks that have a fake edge; these are the no-exit blocks. */
+ for (edge e : bb->succs)
+ if (e->flags & EDGE_FAKE)
+ {
+ /* Delete both the fallthru and the fake edge. */
+ if (EDGE_COUNT (bb->succs) > 1)
+ remove_edge (EDGE_SUCC (bb, 1));
+ remove_edge (EDGE_SUCC (bb, 0));
+ break;
+ }
+ }
+}
+
+/* If the function has SIMT_ENTER or SIMT_EXIT statements, ensure that
+ the condition masks are converted from single-thread to full SIMT-mode
+ at the enter transition point, and vice-versa at the exit point.
+
+ E.g. a comparison in single-thread mode will have set the condition
+ variable to 0x1, and the SIMT_ENTER statement must broadcast that
+ to all the threads by changing the mask to 0xffffffff.
+
+ At the exit point, we absolutely do not want to retain any mask bits
+ greater than 0x1, but it's less clear how to reduce the mask to one bit.
+ If the mask is all ones then it's clear that the new 1-bit mask should
+ be also one, but if only some lanes are enabled then how should it reduce?
+ For now, this implementation just keeps "1" if any threads has "1". */
+
+void
+simt_cbranch_state::handle_simt_enter_exit ()
+{
+ if (dump_file)
+ fprintf (dump_file, "\nHandling simt_enter/exit insns ...\n");
+
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, fn)
+ {
+ rtx_insn *insn;
+ FOR_BB_INSNS (bb, insn)
+ {
+ if (!NONDEBUG_INSN_P (insn)
+ || (INSN_CODE (insn) != CODE_FOR_omp_simt_enter
+ && INSN_CODE (insn) != CODE_FOR_omp_simt_exit))
+ continue;
+
+ rtx newval = (INSN_CODE (insn) == CODE_FOR_omp_simt_enter
+ ? constm1_rtx : const1_rtx);
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file, "Found %s at bb %d insn %d\n",
+ get_insn_name (INSN_CODE (insn)), bb->index,
+ INSN_UID (insn));
+
+ for (auto c : conditionals)
+ {
+ start_sequence ();
+
+ rtx scc1 = gen_reg_rtx (BImode);
+ emit_insn (gen_cstoredi4 (scc1, gen_rtx_NE (BImode, 0, 0),
+ c->true_branch_condvar, const0_rtx));
+ emit_insn (gen_cselectdi (c->true_branch_condvar, scc1,
+ newval, const0_rtx));
+
+ rtx scc2 = gen_reg_rtx (BImode);
+ emit_insn (gen_cstoredi4 (scc2, gen_rtx_NE (BImode, 0, 0),
+ c->false_branch_condvar, const0_rtx));
+ emit_insn (gen_cselectdi (c->false_branch_condvar, scc2,
+ newval, const0_rtx));
+
+ emit_insn_after (end_sequence (), insn);
+ }
+ }
+ }
+}
+
+unsigned int
+pass_gcn_simt_cbranch::execute (function *fn)
+{
+ /* Insert some extra blocks and edges to make the dependencies clearer. */
+ work_around_noreturn_blocks (fn);
+ separate_forks_from_joins (fn);
+ simplify_join_blocks (fn);
+ unify_joining_back_edges (fn);
+ separate_forking_blocks (fn);
+
+ /* Do the real work of the pass. */
+ simt_cbranch_state pass_state (fn);
+ pass_state.execute ();
+
+ return 0;
+}
diff --git a/gcc/config/gcn/t-gcn-hsa b/gcc/config/gcn/t-gcn-hsa
index 127c6a5f759..fa0ad085330 100644
--- a/gcc/config/gcn/t-gcn-hsa
+++ b/gcc/config/gcn/t-gcn-hsa
@@ -51,7 +51,10 @@ gcn-tree.o: $(srcdir)/config/gcn/gcn-tree.cc
gcn-simt-vectorize.o: $(srcdir)/config/gcn/gcn-simt-vectorize.cc
$(COMPILE) $<
$(POSTCOMPILE)
-ALL_HOST_OBJS += gcn-tree.o gcn-simt-vectorize.o
+gcn-simt-cbranch.o: $(srcdir)/config/gcn/gcn-simt-cbranch.cc
+ $(COMPILE) $<
+ $(POSTCOMPILE)
+ALL_HOST_OBJS += gcn-tree.o gcn-simt-vectorize.o gcn-simt-cbranch.o
$(srcdir)/config/gcn/gcn-tables.opt: $(srcdir)/config/gcn/gcn-devices.def
$(srcdir)/config/gcn/gen-opt-tables.awk
$(AWK) -f $(srcdir)/config/gcn/gen-opt-tables.awk $< > $@
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-1.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-1.c
new file mode 100644
index 00000000000..60b04efe777
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-1.c
@@ -0,0 +1,40 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Simple if with no else. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 20 ? i : 0))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-10.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-10.c
new file mode 100644
index 00000000000..8e09c871c68
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-10.c
@@ -0,0 +1,42 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Loop with conditional early exit. */
+ for (i = 0; i < TID; i++)
+ {
+ if (i == TID)
+ break;
+ res[TID] += 1;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != i)
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-10b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-10b.c
new file mode 100644
index 00000000000..bf7c33b1205
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-10b.c
@@ -0,0 +1,48 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ int i;
+
+ res[TID] = 0;
+
+ /* Loop with conditional early exit. */
+ for (i = 0; i < 64; i++)
+ {
+ if (i == TID)
+ break;
+ res[TID] += 1;
+ }
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != i)
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-11.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-11.c
new file mode 100644
index 00000000000..1ecdf389f4e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-11.c
@@ -0,0 +1,45 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Loop with conditional goto following label. */
+ for (i = 0; i < 64; i++)
+ {
+ if (i == TID)
+ goto label;
+ res[TID] += 1;
+ }
+
+ res[TID] += 99999; /* This should never happen. */
+label:
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != i)
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-11b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-11b.c
new file mode 100644
index 00000000000..15b1f311e44
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-11b.c
@@ -0,0 +1,53 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ int i;
+
+ res[TID] = 0;
+
+ /* Loop with conditional goto following label. */
+ for (i = 0; i < 64; i++)
+ {
+ if (i == TID)
+ goto label;
+ res[TID] += 1;
+ }
+
+ res[TID] += 99999; /* This should never happen. */
+
+label:
+ ;
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != i)
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-12.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-12.c
new file mode 100644
index 00000000000..6b566aac998
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-12.c
@@ -0,0 +1,48 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ if (1)
+ res[TID] = 0;
+ else
+ {
+label:
+ res[TID] = 1000;
+ }
+
+ /* Loop with conditional goto preceeding label. */
+ for (i = 0; i < TID; i++)
+ {
+ if (TID > 30 && res[TID] < 1000)
+ goto label;
+ res[TID] += 1;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 30 ? i + 1000 : 93))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-12b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-12b.c
new file mode 100644
index 00000000000..809bba36168
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-12b.c
@@ -0,0 +1,54 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ int i;
+
+ if (1)
+ res[TID] = 0;
+ else
+ {
+label:
+ res[TID] = 1000;
+ }
+
+ /* Loop with conditional goto preceeding label. */
+ for (i = 0; i < TID; i++)
+ {
+ if (TID > 30 && res[TID] < 1000)
+ goto label;
+ res[TID] += 1;
+ }
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 30 ? i + 1000 : 93))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-13.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-13.c
new file mode 100644
index 00000000000..e8f98d15aaf
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-13.c
@@ -0,0 +1,41 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, j, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Nested loops. */
+ for (i = 0; i < TID; i++)
+ for (j = 0; j < TID; j++)
+ {
+ res[TID] += 1;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i == 0 ? 0 : i*i+(63-i)))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-13b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-13b.c
new file mode 100644
index 00000000000..8bf56085b8d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-13b.c
@@ -0,0 +1,48 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ int i, j;
+
+ res[TID] = 0;
+
+ /* Nested loops. */
+ for (i = 0; i < TID; i++)
+ for (j = 1; j <= TID; j++)
+ {
+ res[TID] += 1;
+ }
+
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i == 0 ? 0 : i*i+(63-i)))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-14.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-14.c
new file mode 100644
index 00000000000..4cb4b3ffd15
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-14.c
@@ -0,0 +1,44 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Back-to-back simple loops. */
+ for (i = 0; i < TID; i++)
+ {
+ res[TID] += 1;
+ }
+ for (i = 0; i < TID; i++)
+ {
+ res[TID] += 1;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != i * 2)
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-14b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-14b.c
new file mode 100644
index 00000000000..7d503a359c4
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-14b.c
@@ -0,0 +1,50 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ int i;
+
+ res[TID] = 0;
+
+ /* Back-to-back simple loops. */
+ for (i = 0; i < TID; i++)
+ {
+ res[TID] += 1;
+ }
+ for (i = 0; i < TID; i++)
+ {
+ res[TID] += 1;
+ }
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != i * 2)
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-15.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-15.c
new file mode 100644
index 00000000000..e31a0d14add
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-15.c
@@ -0,0 +1,44 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+int always_true = 1;
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Simple if nested inside scalar if. */
+ if (always_true)
+ {
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ }
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 20 ? i : 0))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-15b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-15b.c
new file mode 100644
index 00000000000..0657486d234
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-15b.c
@@ -0,0 +1,48 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+int always_true = 1;
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ res[TID] = 0;
+
+ /* Simple if nested inside scalar if. */
+ if (always_true)
+ {
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ }
+ }
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 20 ? i : 0))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-16.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-16.c
new file mode 100644
index 00000000000..736eb680237
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-16.c
@@ -0,0 +1,52 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Complicated CFG without any loops. */
+ if (TID < 32)
+ {
+ res[TID] = 1;
+
+ if (TID < 10)
+ goto label;
+ }
+ else
+ {
+ res[TID] = 2;
+
+label:
+ res[TID] += 10;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (i < 10 ? res[i] != 11
+ : i < 32 ? res[i] != 1
+ : res[i] != 12)
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-17.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-17.c
new file mode 100644
index 00000000000..077828c8a1e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-17.c
@@ -0,0 +1,59 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ int i, j;
+
+ res[TID] = 0;
+
+ /* Nested loops with conditional early exit. */
+ for (i = 0; i < 64; i++)
+ {
+ for (j = 0; j < 64; j++)
+ {
+ if (i == TID && j == TID)
+ goto label;
+
+ res[TID] += 1; /* Executes TID*64 + TID times. */
+ }
+
+ res[TID] += 1; /* Executes TID times. */
+ }
+
+ res[TID] += 1000; /* Never executes. */
+
+label:
+ res[TID] += 10000; /* Executes once. */
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != i*64 + i*2 + 10000)
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-18.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-18.c
new file mode 100644
index 00000000000..9065bf1032b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-18.c
@@ -0,0 +1,42 @@
+/* Ensure that SIMT conditionals work when short-cut branches are taken. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Nested if with no else. */
+ if (TID > 200)
+ {
+ res[TID] = TID;
+ if (TID > 300)
+ res[TID] += 1;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != 0)
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-1b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-1b.c
new file mode 100644
index 00000000000..878f92f3314
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-1b.c
@@ -0,0 +1,45 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ res[TID] = 0;
+
+ /* Simple if with no else. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ }
+}
+
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 20 ? i : 0))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-2.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-2.c
new file mode 100644
index 00000000000..056b542a636
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-2.c
@@ -0,0 +1,44 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Simple if-then-else. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ }
+ else
+ {
+ res[TID] = -TID;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 20 ? i : -i))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-2b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-2b.c
new file mode 100644
index 00000000000..ef50495b5a3
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-2b.c
@@ -0,0 +1,48 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ res[TID] = 0;
+
+ /* Simple if-then-else. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ }
+ else
+ {
+ res[TID] = -TID;
+ }
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 20 ? i : -i))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-3.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-3.c
new file mode 100644
index 00000000000..cb1e8c71655
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-3.c
@@ -0,0 +1,44 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Nested if with no else. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ if (TID > 30)
+ res[TID] += 1;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 30 ? i + 1
+ : i > 20 ? i
+ : 0))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-3b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-3b.c
new file mode 100644
index 00000000000..b2efa9d7190
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-3b.c
@@ -0,0 +1,48 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ res[TID] = 0;
+
+ /* Nested if with no else. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ if (TID > 30)
+ res[TID] += 1;
+ }
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 30 ? i + 1
+ : i > 20 ? i
+ : 0))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-4.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-4.c
new file mode 100644
index 00000000000..24a8da80ad3
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-4.c
@@ -0,0 +1,48 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Nested if with outer else. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ if (TID > 30)
+ res[TID] += 1;
+ }
+ else
+ {
+ res[TID] = -TID;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 30 ? i + 1
+ : i > 20 ? i
+ : -i))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-4b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-4b.c
new file mode 100644
index 00000000000..dd422f5e368
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-4b.c
@@ -0,0 +1,52 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ res[TID] = 0;
+
+ /* Nested if with outer else. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ if (TID > 30)
+ res[TID] += 1;
+ }
+ else
+ {
+ res[TID] = -TID;
+ }
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 30 ? i + 1
+ : i > 20 ? i
+ : -i))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-5.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-5.c
new file mode 100644
index 00000000000..2ce6e668e90
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-5.c
@@ -0,0 +1,50 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Nested if with inner and outer else. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ if (TID > 30)
+ res[TID] += 1;
+ else
+ res[TID] *= 2;
+ }
+ else
+ {
+ res[TID] = -TID;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 30 ? i + 1
+ : i > 20 ? i * 2
+ : -i))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-5b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-5b.c
new file mode 100644
index 00000000000..867ad8cbf56
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-5b.c
@@ -0,0 +1,54 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ res[TID] = 0;
+
+ /* Nested if with inner and outer else. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ if (TID > 30)
+ res[TID] += 1;
+ else
+ res[TID] *= 2;
+ }
+ else
+ {
+ res[TID] = -TID;
+ }
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 30 ? i + 1
+ : i > 20 ? i * 2
+ : -i))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-6.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-6.c
new file mode 100644
index 00000000000..d3be42c6cde
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-6.c
@@ -0,0 +1,51 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Nested if with inner and outer else, and extra block. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ if (TID > 30)
+ res[TID] += 1;
+ else
+ res[TID] *= 2;
+ res[TID] += 1;
+ }
+ else
+ {
+ res[TID] = -TID;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 30 ? i + 2
+ : i > 20 ? i * 2 + 1
+ : -i))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-6b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-6b.c
new file mode 100644
index 00000000000..97245811209
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-6b.c
@@ -0,0 +1,55 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ res[TID] = 0;
+
+ /* Nested if with inner and outer else, and extra block. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ if (TID > 30)
+ res[TID] += 1;
+ else
+ res[TID] *= 2;
+ res[TID] += 1;
+ }
+ else
+ {
+ res[TID] = -TID;
+ }
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 30 ? i + 2
+ : i > 20 ? i * 2 + 1
+ : -i))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-7.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-7.c
new file mode 100644
index 00000000000..0507f0dc514
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-7.c
@@ -0,0 +1,55 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Nested if with two inner and one outer else. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ if (TID > 30)
+ res[TID] += 1;
+ else
+ res[TID] *= 2;
+ }
+ else
+ {
+ res[TID] = -TID;
+ if (TID < 10)
+ res[TID] -= 1;
+ else
+ res[TID] *= 3;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 30 ? i + 1
+ : i > 20 ? i * 2
+ : i < 10 ? -i - 1
+ : -i * 3))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-7b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-7b.c
new file mode 100644
index 00000000000..0eb4d5e0d82
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-7b.c
@@ -0,0 +1,59 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ res[TID] = 0;
+
+ /* Nested if with two inner and one outer else. */
+ if (TID > 20)
+ {
+ res[TID] = TID;
+ if (TID > 30)
+ res[TID] += 1;
+ else
+ res[TID] *= 2;
+ }
+ else
+ {
+ res[TID] = -TID;
+ if (TID < 10)
+ res[TID] -= 1;
+ else
+ res[TID] *= 3;
+ }
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 30 ? i + 1
+ : i > 20 ? i * 2
+ : i < 10 ? -i - 1
+ : -i * 3))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-8.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-8.c
new file mode 100644
index 00000000000..a38deede74c
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-8.c
@@ -0,0 +1,40 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Simple loop. */
+ for (i = 0; i < TID; i++)
+ {
+ res[TID] += 1;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != i)
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-8b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-8b.c
new file mode 100644
index 00000000000..100e8067283
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-8b.c
@@ -0,0 +1,46 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ int i;
+
+ res[TID] = 0;
+
+ /* Simple loop. */
+ for (i = 0; i < TID; i++)
+ {
+ res[TID] += 1;
+ }
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != i)
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-9.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-9.c
new file mode 100644
index 00000000000..80663661a44
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-9.c
@@ -0,0 +1,42 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+
+ res[TID] = 0;
+
+ /* Loop with nested if. */
+ for (i = 0; i < TID; i++)
+ {
+ if (i == 6)
+ res[TID] += 1;
+ res[TID] += 1;
+ }
+
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 6 ? i + 1 : i))
+ __builtin_exit (1);
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/gcn/simt-cond-9b.c
b/gcc/testsuite/gcc.target/gcn/simt-cond-9b.c
new file mode 100644
index 00000000000..a0b39e0146b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/gcn/simt-cond-9b.c
@@ -0,0 +1,48 @@
+/* Ensure that SIMT conditionals work per-lane. */
+
+/* { dg-do run } */
+/* { dg-require-effective-target amdgcn_simt } */
+/* { dg-additional-options "-O1" } */
+
+#ifndef __SIMT__
+#error "This test requires -msimt"
+#endif
+
+#define TID __builtin_gcn_dim_pos (2)
+
+/* Global variables aren't thread-local. */
+int res[64];
+
+void __attribute__((noclone,noinline))
+f ()
+{
+ int i;
+
+ res[TID] = 0;
+
+ /* Loop with nested if. */
+ for (i = 0; i < TID; i++)
+ {
+ if (i == 6)
+ res[TID] += 1;
+ res[TID] += 1;
+ }
+}
+
+/* Note: main enters in single-threaded mode. */
+int
+main ()
+{
+ int i, A;
+
+ A = __builtin_gcn_simt_enter (1, 2);
+ f ();
+ __builtin_gcn_simt_exit (A);
+
+ /* Use single-threading to check the results because broken multithread
+ might also have broken checking. */
+ for (i = 0; i < 64; i++)
+ if (res[i] != (i > 6 ? i + 1 : i))
+ __builtin_exit (1);
+ return 0;
+}
--
2.54.0