From: Kyrylo Tkachov <[email protected]>

A bytecode interpreter written with a switch at its loop head normally has one
jump-table dispatch.  Every opcode transition returns to the same indirect
branch instruction.  This can make indirect target prediction less effective.
The existing compgotos pass already gives each eligible predecessor its own
computed goto.  Apply the same transformation to the jump-table form.

The guarded tablejump is a short chain rather than one block:

  handler -> fetch and range check -> tablejump

Collect the single-predecessor fall-through chain and copy it into eligible
predecessors.  The copies share the table through the representation added by
the first patch.  Require a direct DFS back edge into the collected chain head
before the first tablejump duplication.  This limits the new transformation
to a loop head.  Recursive copies do not repeat the test.  Computed gotos keep
their existing behavior.

Use the existing max-goto-duplication-insns budget.  Charge the tablejump as
one unconditional-jump unit and charge every accompanying RTL instruction at
its minimum length.  A zero budget rejects the transformation.  Require the
target's can_share_jump_table_p hook to permit sharing and reject
partitioned functions.

Reject functions that use stack registers.  Stack-register conversion can
split outgoing edges to reconcile different stack layouts, but a shared-table
edge cannot be changed for only one dispatch.

Set PROP_rtl_allow_tablejump_duplication only while the pass scans and copies
dispatches.  Clear it before CFG cleanup and before any later pass runs.

Plugin tests check per-function target options, both default hook results,
existing instruction-copy vetoes, and output after eligibility becomes false.

On an AArch64 Vera system, explicit enablement improves 714.cpython_r by
10.3%, mawk by 6.9%, and QuickJS-ng by 24.9%.

gcc/ChangeLog:

        PR middle-end/126236
        * common.opt (fduplicate-switch-dispatch): New option.
        * doc/invoke.texi: Document it.
        * params.opt (max-goto-duplication-insns): Mention switch dispatches.
        * bb-reorder.cc (dispatch_jump_p): New function.
        (can_attach_dispatch_copy_p): New function.
        (find_dispatch_chain): New function.
        (maybe_duplicate_computed_goto): Rename to...
        (maybe_duplicate_dispatch): ...this.  Copy a dispatch chain and
        optionally require a DFS back edge into its head.
        (duplicate_computed_gotos): Mark DFS back edges, select eligible
        tablejumps, and scope PROP_rtl_allow_tablejump_duplication.

gcc/testsuite/ChangeLog:

        PR middle-end/126236
        * gcc.dg/plugin/plugin.exp: Register the capability test.
        * gcc.dg/plugin/tablejump_capability_plugin.cc: New plugin.
        * gcc.dg/plugin/tablejump-capability.c: New test.
        * gcc.dg/plugin/tablejump-test.h: New test header.
        * gcc.dg/switch-dispatch-2.c: New test.
        * gcc.dg/switch-dispatch-3.c: New test.
        * gcc.dg/torture/switch-dispatch-run-1.c: New test.
        * gcc.dg/torture/switch-dispatch-run-2.c: New test.
        * gcc.dg/torture/switch-dispatch-run-3.c: New test.
        * gcc.dg/torture/switch-dispatch-run-4.c: New test.
        * gcc.target/aarch64/switch-dispatch-1.c: New test.
        * gcc.target/aarch64/switch-dispatch-2.c: New test.
        * gcc.target/aarch64/switch-dispatch-3.c: New test.
        * gcc.target/aarch64/switch-dispatch-4.c: New test.
        * gcc.target/aarch64/switch-dispatch-5.c: New test.
        * gcc.target/aarch64/switch-dispatch-6.c: New test.
        * gcc.target/aarch64/switch-dispatch-7.c: New test.

Signed-off-by: Kyrylo Tkachov <[email protected]>
---
 gcc/bb-reorder.cc                             | 245 +++++++++++++++---
 gcc/common.opt                                |   4 +
 gcc/doc/invoke.texi                           |  15 ++
 gcc/params.opt                                |   2 +-
 gcc/testsuite/gcc.dg/plugin/plugin.exp        |   1 +
 .../gcc.dg/plugin/tablejump-capability.c      |  25 ++
 gcc/testsuite/gcc.dg/plugin/tablejump-test.h  |  24 ++
 .../plugin/tablejump_capability_plugin.cc     | 180 +++++++++++++
 gcc/testsuite/gcc.dg/switch-dispatch-2.c      |  33 +++
 gcc/testsuite/gcc.dg/switch-dispatch-3.c      |   7 +
 .../gcc.dg/torture/switch-dispatch-run-1.c    |  63 +++++
 .../gcc.dg/torture/switch-dispatch-run-2.c    |  67 +++++
 .../gcc.dg/torture/switch-dispatch-run-3.c    |  33 +++
 .../gcc.dg/torture/switch-dispatch-run-4.c    |  62 +++++
 .../gcc.target/aarch64/switch-dispatch-1.c    |  39 +++
 .../gcc.target/aarch64/switch-dispatch-2.c    |  32 +++
 .../gcc.target/aarch64/switch-dispatch-3.c    |  30 +++
 .../gcc.target/aarch64/switch-dispatch-4.c    |  10 +
 .../gcc.target/aarch64/switch-dispatch-5.c    |  10 +
 .../gcc.target/aarch64/switch-dispatch-6.c    |  10 +
 .../gcc.target/aarch64/switch-dispatch-7.c    |  11 +
 21 files changed, 867 insertions(+), 36 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/plugin/tablejump-capability.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/tablejump-test.h
 create mode 100644 gcc/testsuite/gcc.dg/plugin/tablejump_capability_plugin.cc
 create mode 100644 gcc/testsuite/gcc.dg/switch-dispatch-2.c
 create mode 100644 gcc/testsuite/gcc.dg/switch-dispatch-3.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/switch-dispatch-run-1.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/switch-dispatch-run-2.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/switch-dispatch-run-3.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/switch-dispatch-run-4.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/switch-dispatch-1.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/switch-dispatch-2.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/switch-dispatch-3.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/switch-dispatch-4.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/switch-dispatch-5.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/switch-dispatch-6.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/switch-dispatch-7.c

diff --git a/gcc/bb-reorder.cc b/gcc/bb-reorder.cc
index 4e863bdac99..17d2d7c0ebf 100644
--- a/gcc/bb-reorder.cc
+++ b/gcc/bb-reorder.cc
@@ -2706,59 +2706,211 @@ make_pass_reorder_blocks (gcc::context *ctxt)
   return new pass_reorder_blocks (ctxt);
 }
 
-/* Duplicate a block (that we already know ends in a computed jump) into its
-   predecessors, where possible.  Return whether anything is changed.  */
+/* Return true if INSN is a jump whose predecessors are worth giving a copy
+   of their own.  Every transition through such a jump uses the same indirect
+   branch instruction, which can make indirect target prediction less
+   effective.  */
+
 static bool
-maybe_duplicate_computed_goto (basic_block bb, int max_size)
+dispatch_jump_p (rtx_insn *insn)
 {
-  /* Make sure that the block is small enough.  */
-  rtx_insn *insn;
-  FOR_BB_INSNS (bb, insn)
-    if (INSN_P (insn))
-      {
-       max_size -= get_attr_min_length (insn);
-       if (max_size < 0)
-          return false;
-      }
+  if (computed_jump_p (insn))
+    return true;
+
+  /* A switch lowered to a jump table dispatches exactly like a factored
+     computed goto.  Copying such a block makes the copies share the one
+     table, so only the branch is replicated, not the table.  The target has
+     to be able to emit whatever the table entries are relative to once per
+     table rather than once per jump.  Sharing a table in a partitioned
+     function would require explicit ownership of the base label's section.  */
+  if (!(flag_duplicate_switch_dispatch
+       && !crtl->has_bb_partition
+       && tablejump_p (insn, NULL, NULL)
+       && targetm.can_share_jump_table_p (insn)))
+    return false;
+
+  gcc_checking_assert (targetm.asm_out.jump_table_base);
+
+#ifdef STACK_REGS
+  /* Stack-register conversion can split outgoing edges to reconcile stack
+     layouts.  Such edges cannot be redirected for one shared-table user.  */
+  for (int regno = FIRST_STACK_REG; regno <= LAST_STACK_REG; ++regno)
+    if (df_regs_ever_live_p (regno))
+      return false;
+#endif
+
+  return true;
+}
+
+/* Return true if a copy of the dispatch can be attached to the source of E,
+   taking over that edge.  */
+
+static bool
+can_attach_dispatch_copy_p (edge e)
+{
+  basic_block pred = e->src;
+
+  return (single_succ_p (pred)
+         && !(e->flags & EDGE_COMPLEX)
+         && pred->index >= NUM_FIXED_BLOCKS
+         && (!JUMP_P (BB_END (pred))
+             || (simplejump_p (BB_END (pred))
+                 && !CROSSING_JUMP_P (BB_END (pred)))));
+}
+
+/* Collect into CHAIN the blocks that have to be copied together with the
+   dispatch block BB, innermost first, and return the head of the chain: the
+   block whose predecessors the copies are attached to.  Anything above the
+   dispatch is reached only through the chain, so copying the lot into a
+   predecessor of the head is sound.  The chain is what makes a jump table
+   dispatch reachable at all, since the range check that guards it sits in a
+   block of its own.  Return NULL if BB alone is already larger than
+   MAX_SIZE.  */
+
+static basic_block
+find_dispatch_chain (basic_block bb, int max_size,
+                    vec<basic_block> *chain)
+{
+  int size = 0;
+
+  /* Count a tablejump as one instruction.  Its target sequence is an
+     indivisible part of every copy, while the budget still bounds the work
+     copied with it.  */
+  rtx_insn *dispatch = NULL;
+  if (tablejump_p (BB_END (bb), NULL, NULL))
+    dispatch = BB_END (bb);
+
+  for (basic_block b = bb; ; b = single_pred (b))
+    {
+      rtx_insn *insn;
+      FOR_BB_INSNS (b, insn)
+       if (INSN_P (insn))
+         {
+           size += (insn == dispatch
+                    ? uncond_jump_length : get_attr_min_length (insn));
+           if (size > max_size)
+             return NULL;
+         }
+
+      chain->safe_push (b);
+
+      /* Stop at the block the copies attach to, and at anything we may not
+        copy.  Only a jump table dispatch needs more than its own block, and
+        only a fall-through predecessor can be taken along: the copies are
+        laid out one after the other, so any other edge into the chain would
+        need a jump of its own.  */
+      if (!dispatch || !single_pred_p (b))
+       break;
+      edge in = single_pred_edge (b);
+      if (can_attach_dispatch_copy_p (in)
+         || !(in->flags & EDGE_FALLTHRU)
+         || in->src == ENTRY_BLOCK_PTR_FOR_FN (cfun)
+         || !can_duplicate_block_p (in->src))
+       break;
+    }
+
+  return chain->is_empty () ? NULL : chain->last ();
+}
+
+/* Duplicate the dispatch block BB, along with the blocks it can only be
+   reached through, into the predecessors of that chain, where possible.
+   If REQUIRE_BACK_EDGE is true, first require a DFS back edge into the head
+   of the chain.  Return whether anything is changed.  */
+static bool
+maybe_duplicate_dispatch (basic_block bb, int max_size,
+                         bool require_back_edge)
+{
+  auto_vec<basic_block, 4> chain;
+  basic_block head = find_dispatch_chain (bb, max_size, &chain);
+  if (!head)
+    return false;
+
+  if (require_back_edge)
+    {
+      bool found = false;
+      edge e;
+      edge_iterator ei;
+      FOR_EACH_EDGE (e, ei, head->preds)
+       if (e->flags & EDGE_DFS_BACK)
+         {
+           found = true;
+           break;
+         }
+      if (!found)
+       return false;
+    }
 
   bool changed = false;
   edge e;
   edge_iterator ei;
-  for (ei = ei_start (bb->preds); (e = ei_safe_edge (ei)); )
+  for (ei = ei_start (head->preds); (e = ei_safe_edge (ei)); )
     {
       basic_block pred = e->src;
 
-      /* Do not duplicate BB into PRED if we cannot merge a copy of BB
-        with PRED.  */
-      if (!single_succ_p (pred)
-         || e->flags & EDGE_COMPLEX
-         || pred->index < NUM_FIXED_BLOCKS
-         || (JUMP_P (BB_END (pred)) && !simplejump_p (BB_END (pred)))
-         || (JUMP_P (BB_END (pred)) && CROSSING_JUMP_P (BB_END (pred))))
+      /* Do not duplicate the chain into PRED if we cannot merge a copy of
+        its head with PRED.  */
+      if (!can_attach_dispatch_copy_p (e))
        {
          ei_next (&ei);
          continue;
        }
 
       if (dump_file)
-       fprintf (dump_file, "Duplicating computed goto bb %d into bb %d\n",
-                bb->index, e->src->index);
+       fprintf (dump_file, "Duplicating dispatch bb %d into bb %d\n",
+                bb->index, pred->index);
 
-      /* Remember if PRED can be duplicated; if so, the copy of BB merged
-        with PRED can be duplicated as well.  */
+      /* Remember if PRED can be duplicated; if so, the copy of the chain
+        merged with PRED can be duplicated as well.  */
       bool can_dup_more = can_duplicate_block_p (pred);
 
-      /* Make a copy of BB, merge it into PRED.  */
-      basic_block copy = duplicate_block (bb, e, NULL);
-      emit_barrier_after_bb (copy);
-      reorder_insns_nobb (BB_HEAD (copy), BB_END (copy), BB_END (pred));
-      merge_blocks (pred, copy);
+      /* Copy the chain head first, so that the copies end up in the
+        instruction stream in the order they are executed in.  Only the head
+        takes over the edge from PRED; the edges inside the chain are
+        fall-through and so are moved onto the copies directly.  */
+      auto_vec<basic_block, 4> copies;
+      for (unsigned i = chain.length (); i-- > 0; )
+       {
+         basic_block copy;
+         if (copies.is_empty ())
+           copy = duplicate_block (chain[i], e, NULL);
+         else
+           {
+             edge in = find_edge (copies.last (), chain[i]);
+             profile_count count = in->count ();
+             if (chain[i]->count < count)
+               count = chain[i]->count;
+             copy = duplicate_block (chain[i], NULL, NULL);
+             copy->count = count;
+             chain[i]->count -= count;
+             redirect_edge_succ (in, copy);
+           }
+         copies.safe_push (copy);
+       }
+
+      basic_block first = copies[0];
+      basic_block last = copies.last ();
+
+      /* Move the copies after PRED and fold the head into it.  */
+      emit_barrier_after_bb (last);
+      reorder_insns_nobb (BB_HEAD (first), BB_END (last), BB_END (pred));
+      merge_blocks (pred, first);
+
+      /* The rest of the chain stays as separate blocks that PRED falls
+        through to, so put them in the block order to match.  */
+      basic_block after = pred;
+      for (unsigned i = 1; i < copies.length (); i++)
+       {
+         unlink_block (copies[i]);
+         link_block (copies[i], after);
+         after = copies[i];
+       }
 
       changed = true;
 
-      /* Try to merge the resulting merged PRED into further predecessors.  */
+      /* Try to merge the resulting dispatch block into further
+        predecessors.  */
       if (can_dup_more)
-       maybe_duplicate_computed_goto (pred, max_size);
+       maybe_duplicate_dispatch (first == last ? pred : last, max_size, false);
     }
 
   return changed;
@@ -2768,7 +2920,11 @@ maybe_duplicate_computed_goto (basic_block bb, int 
max_size)
    computed gotos that were factored early on in the compilation process to
    speed up edge based data flow.  We used to not unfactor them again, which
    can seriously pessimize code with many computed jumps in the source code,
-   such as interpreters.  See e.g. PR15242.  */
+   such as interpreters.  See e.g. PR15242.
+
+   With -fduplicate-switch-dispatch the same is done for a switch lowered to
+   a jump table, which an interpreter written without the labels-as-values
+   extension ends up with.  See PR126236.  */
 static void
 duplicate_computed_gotos (function *fun)
 {
@@ -2783,13 +2939,32 @@ duplicate_computed_gotos (function *fun)
     = uncond_jump_length * param_max_goto_duplication_insns;
 
   bool changed = false;
+  bool back_edges_marked = false;
+
+  gcc_checking_assert (!(fun->curr_properties
+                        & PROP_rtl_allow_tablejump_duplication));
+  fun->curr_properties |= PROP_rtl_allow_tablejump_duplication;
 
-  /* Try to duplicate all blocks that end in a computed jump and that
+  /* Try to duplicate all blocks that end in a dispatching jump and that
      can be duplicated at all.  */
   basic_block bb;
   FOR_EACH_BB_FN (bb, fun)
-    if (computed_jump_p (BB_END (bb)) && can_duplicate_block_p (bb))
-      changed |= maybe_duplicate_computed_goto (bb, max_size);
+    {
+      if (!dispatch_jump_p (BB_END (bb))
+         || !can_duplicate_block_p (bb))
+       continue;
+
+      bool tablejump = tablejump_p (BB_END (bb), NULL, NULL);
+      if (tablejump && !back_edges_marked)
+       {
+         mark_dfs_back_edges ();
+         back_edges_marked = true;
+       }
+
+      changed |= maybe_duplicate_dispatch (bb, max_size, tablejump);
+    }
+
+  fun->curr_properties &= ~PROP_rtl_allow_tablejump_duplication;
 
   /* Some blocks may have become unreachable.  */
   if (changed)
diff --git a/gcc/common.opt b/gcc/common.opt
index 69a55b8d40d..92c166500d2 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1706,6 +1706,10 @@ fdump-unnumbered-links
 Common Var(flag_dump_unnumbered_links)
 Suppress output of previous and next insn numbers in debugging dumps.
 
+fduplicate-switch-dispatch
+Common Var(flag_duplicate_switch_dispatch) Optimization
+Give eligible predecessors of a loop switch their own copy of the table branch.
+
 fdwarf2-cfi-asm
 Common Var(flag_dwarf2_cfi_asm) Init(HAVE_GAS_CFI_DIRECTIVE)
 Enable CFI tables via GAS assembler directives.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 498a3608151..54c85eae25b 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -615,6 +615,7 @@ Objective-C and Objective-C++ Dialects}.
 -fdata-sections  -fdce  -fdelayed-branch
 -fdelete-null-pointer-checks  -fdep-fusion  -fdevirtualize
 -fdevirtualize-speculatively  -fdevirtualize-at-ltrans  -fdse
+-fduplicate-switch-dispatch
 -fearly-inlining  -fexcess-precision=@var{style}
 -fexpensive-optimizations  -fext-dce
 -ffast-math  -ffat-lto-objects  -ffinite-loops
@@ -14292,6 +14293,20 @@ Enabled by default at @option{-O1} and higher.
 Perform dead store elimination (DSE) on RTL@.
 Enabled by default at @option{-O1} and higher.
 
+@opindex fduplicate-switch-dispatch
+@opindex fno-duplicate-switch-dispatch
+@item -fduplicate-switch-dispatch
+If supported by the target machine, give eligible predecessors of a switch at
+a loop head their own copy of the jump-table branch.  A bytecode interpreter
+written with such a @code{switch} otherwise sends every opcode transition
+through one indirect branch instruction.  This can make indirect target
+prediction less effective.  GCC already performs the same transformation for
+the computed gotos of an interpreter written with the labels-as-values
+extension.
+
+It trades size for speed, so the amount copied is bounded by
+@option{--param=max-goto-duplication-insns}.
+
 @opindex fif-conversion
 @opindex fno-if-conversion
 @item -fif-conversion
diff --git a/gcc/params.opt b/gcc/params.opt
index 0c6a3e51457..0cf0f7b4e8a 100644
--- a/gcc/params.opt
+++ b/gcc/params.opt
@@ -616,7 +616,7 @@ The maximum amount of memory to be allocated by GCSE, in 
kilobytes.
 
 -param=max-goto-duplication-insns=
 Common Joined UInteger Var(param_max_goto_duplication_insns) Init(8) Param 
Optimization
-The maximum number of insns to duplicate when unfactoring computed gotos.
+The maximum number of insns to duplicate when unfactoring computed gotos or 
switch dispatches.
 
 -param=max-grow-copy-bb-insns=
 Common Joined UInteger Var(param_max_grow_copy_bb_insns) Init(8) Param 
Optimization
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp 
b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index deef8e8570c..0062ac14051 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -54,6 +54,7 @@ gcc_parallel_test_enable 0
 # Specify the plugin source file and the associated test files in a list.
 # plugin_test_list={ {plugin1 test1 test2 ...} {plugin2 test1 ...} ... }
 set plugin_test_list [list \
+    { tablejump_capability_plugin.cc tablejump-capability.c } \
     { selfassign.cc self-assign-test-1.c self-assign-test-2.c } \
     { ggcplug.cc ggcplug-test-1.c } \
     { one_time_plugin.cc one_time-test-1.c } \
diff --git a/gcc/testsuite/gcc.dg/plugin/tablejump-capability.c 
b/gcc/testsuite/gcc.dg/plugin/tablejump-capability.c
new file mode 100644
index 00000000000..93d50fcfa09
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/tablejump-capability.c
@@ -0,0 +1,25 @@
+/* PR middle-end/126236 */
+/* { dg-do assemble { target aarch64*-*-* } } */
+/* { dg-options "-O2 -fchecking -fduplicate-switch-dispatch" } */
+
+void allowed (const unsigned char *, volatile unsigned *)
+  __attribute__ ((noipa, target ("arch=armv8-a+crc")));
+void rejected (const unsigned char *, volatile unsigned *)
+  __attribute__ ((noipa, target ("arch=armv8-a+nocrc")));
+void vetoed (const unsigned char *, volatile unsigned *)
+  __attribute__ ((noipa, target ("arch=armv8-a+crc")));
+
+#define TEST_NAME allowed
+#include "tablejump-test.h"
+#undef TEST_NAME
+#define TEST_NAME rejected
+#include "tablejump-test.h"
+#undef TEST_NAME
+#define TEST_NAME vetoed
+#include "tablejump-test.h"
+
+int
+ordinary (int x)
+{
+  return x + 1;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/tablejump-test.h 
b/gcc/testsuite/gcc.dg/plugin/tablejump-test.h
new file mode 100644
index 00000000000..3e90c1efd63
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/tablejump-test.h
@@ -0,0 +1,24 @@
+/* PR middle-end/126236 */
+/* A loop with shared dispatches and repeated table entries.  */
+
+void
+TEST_NAME (const unsigned char *pc, volatile unsigned *state)
+{
+  for (;;)
+    switch (*pc++)
+      {
+      case 0: return;
+      case 1: case 12: state[0] += 1; break;
+      case 2: state[1] ^= 2; break;
+      case 3: state[2] -= 3; break;
+      case 4: state[3] |= 4; break;
+      case 5: state[4] &= 5; break;
+      case 6: state[5] += 6; break;
+      case 7: state[6] ^= 7; break;
+      case 8: state[7] -= 8; break;
+      case 9: state[8] |= 9; break;
+      case 10: state[9] &= 10; break;
+      case 11: state[10] += 11; break;
+      default: state[11] ^= 12; break;
+      }
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/tablejump_capability_plugin.cc 
b/gcc/testsuite/gcc.dg/plugin/tablejump_capability_plugin.cc
new file mode 100644
index 00000000000..8b82ec9e2df
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/tablejump_capability_plugin.cc
@@ -0,0 +1,180 @@
+/* PR middle-end/126236 */
+/* { dg-options "-O0" } */
+
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "rtl.h"
+#include "tree.h"
+#include "function.h"
+#include "memmodel.h"
+#include "emit-rtl.h"
+#include "tree-pass.h"
+#include "target.h"
+#include "context.h"
+#include "plugin-version.h"
+
+int plugin_is_GPL_compatible;
+
+static bool (*saved_can_share) (rtx_insn *);
+static bool (*saved_cannot_copy) (rtx_insn *);
+static void (*saved_base) (FILE *, rtx_jump_table_data *, unsigned long);
+static unsigned queries, late_queries, veto_queries;
+static unsigned functions, expected_bases, emitted_bases;
+static bool veto;
+
+/* Return whether the current function enables CRC instructions.  */
+
+static bool
+crc_enabled_p ()
+{
+#ifdef AARCH64_HAVE_ISA
+  return TARGET_CRC32;
+#else
+  gcc_unreachable ();
+#endif
+}
+
+/* Check the real tablejump INSN and allow sharing according to the current
+   function's target options.  */
+
+static bool
+can_share (rtx_insn *insn)
+{
+  gcc_assert (tablejump_p (insn, NULL, NULL));
+  auto base = targetm.asm_out.jump_table_base;
+  targetm.asm_out.jump_table_base = NULL;
+  gcc_assert (!saved_can_share (insn));
+  targetm.asm_out.jump_table_base = base;
+  gcc_assert (saved_can_share (insn));
+  ++queries;
+  return crc_enabled_p ();
+}
+
+/* Reject sharing and record any query outside the duplication pass.  */
+
+static bool
+reject_late (rtx_insn *)
+{
+  ++late_queries;
+  return false;
+}
+
+/* Reject copying a tablejump INSN in the selected function.  */
+
+static bool
+cannot_copy (rtx_insn *insn)
+{
+  if (veto && tablejump_p (insn, NULL, NULL))
+    {
+      ++veto_queries;
+      return true;
+    }
+  return saved_cannot_copy && saved_cannot_copy (insn);
+}
+
+/* Emit TABLE's base with LABELNO to STREAM while eligibility is false.  */
+
+static void
+emit_base (FILE *stream, rtx_jump_table_data *table, unsigned long labelno)
+{
+  gcc_assert (targetm.can_share_jump_table_p == reject_late);
+  gcc_assert (JUMP_TABLE_SHARED_P (PATTERN (table)));
+  ++emitted_bases;
+  saved_base (stream, table, labelno);
+}
+
+const pass_data capability_pass_data =
+{
+  RTL_PASS, "tablejump_capability", OPTGROUP_NONE, TV_NONE,
+  PROP_rtl, 0, 0, 0, 0
+};
+
+class capability_pass : public rtl_opt_pass
+{
+public:
+  capability_pass (gcc::context *ctxt, bool before)
+    : rtl_opt_pass (capability_pass_data, ctxt), m_before (before)
+  {}
+
+  unsigned int execute (function *fun) final override
+  {
+    const char *name = IDENTIFIER_POINTER (DECL_NAME (fun->decl));
+    bool ordinary = !strcmp (name, "ordinary");
+    bool allowed = !strcmp (name, "allowed");
+    unsigned tables = 0, shared = 0, jumps = 0;
+    for (rtx_insn *insn = get_insns (); insn; insn = NEXT_INSN (insn))
+      {
+       if (JUMP_TABLE_DATA_P (insn))
+         {
+           ++tables;
+           shared += JUMP_TABLE_SHARED_P (PATTERN (insn));
+         }
+       jumps += tablejump_p (insn, NULL, NULL);
+      }
+    gcc_assert (tables == (ordinary ? 0 : 1));
+    gcc_assert (!late_queries);
+    if (!ordinary)
+      gcc_assert (crc_enabled_p () == !!strcmp (name, "rejected"));
+
+    if (m_before)
+      {
+       gcc_assert (!shared && jumps == tables);
+       queries = veto_queries = 0;
+       veto = !strcmp (name, "vetoed");
+       targetm.can_share_jump_table_p = can_share;
+       targetm.cannot_copy_insn_p = cannot_copy;
+      }
+    else
+      {
+       gcc_assert ((queries != 0) == !ordinary);
+       gcc_assert (shared == (allowed ? 1 : 0));
+       gcc_assert (allowed ? jumps > tables : jumps == tables);
+       gcc_assert ((veto_queries != 0) == veto);
+       expected_bases += shared;
+       ++functions;
+       targetm.can_share_jump_table_p = reject_late;
+       targetm.cannot_copy_insn_p = saved_cannot_copy;
+      }
+    return 0;
+  }
+
+private:
+  bool m_before;
+};
+
+/* Check that all driver functions were tested and all shared bases emitted.  
*/
+
+static void
+finish_unit (void *, void *)
+{
+  gcc_assert (functions == 4);
+  gcc_assert (expected_bases == 1 && emitted_bases == expected_bases);
+  gcc_assert (!late_queries);
+}
+
+int
+plugin_init (struct plugin_name_args *info, struct plugin_gcc_version *version)
+{
+  if (!plugin_default_version_check (version, &gcc_version))
+    return 1;
+  saved_can_share = targetm.can_share_jump_table_p;
+  saved_cannot_copy = targetm.cannot_copy_insn_p;
+  saved_base = targetm.asm_out.jump_table_base;
+  targetm.can_share_jump_table_p = reject_late;
+  targetm.asm_out.jump_table_base = emit_base;
+  for (unsigned i = 0; i != 2; ++i)
+    {
+      struct register_pass_info pass;
+      pass.pass = new capability_pass (g, i == 0);
+      pass.reference_pass_name = "compgotos";
+      pass.ref_pass_instance_number = 1;
+      pass.pos_op = i == 0 ? PASS_POS_INSERT_BEFORE : PASS_POS_INSERT_AFTER;
+      register_callback (info->base_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
+                        &pass);
+    }
+  register_callback (info->base_name, PLUGIN_FINISH_UNIT, finish_unit, NULL);
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/switch-dispatch-2.c 
b/gcc/testsuite/gcc.dg/switch-dispatch-2.c
new file mode 100644
index 00000000000..0b380986ab9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/switch-dispatch-2.c
@@ -0,0 +1,33 @@
+/* PR middle-end/126236 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fduplicate-switch-dispatch 
--param=case-values-threshold=1 -fdump-rtl-compgotos-details" } */
+
+/* The two paths converge on an attachable switch dispatch, but there is no
+   back edge into it.  */
+
+int
+plain (int x, int path, volatile int *state)
+{
+  if (path)
+    state[12] += path;
+  else
+    state[13] ^= x;
+
+  switch (x)
+    {
+    case 0: state[0] += 1; break;
+    case 1: state[1] ^= 2; break;
+    case 2: state[2] -= 3; break;
+    case 3: state[3] |= 4; break;
+    case 4: state[4] &= 5; break;
+    case 5: state[5] += 6; break;
+    case 6: state[6] ^= 7; break;
+    case 7: state[7] -= 8; break;
+    case 8: state[8] |= 9; break;
+    case 9: state[9] &= 10; break;
+    default: state[10] ^= 11; break;
+    }
+  return state[11];
+}
+
+/* { dg-final { scan-rtl-dump-not "Duplicating dispatch" "compgotos" } } */
diff --git a/gcc/testsuite/gcc.dg/switch-dispatch-3.c 
b/gcc/testsuite/gcc.dg/switch-dispatch-3.c
new file mode 100644
index 00000000000..87c8218377f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/switch-dispatch-3.c
@@ -0,0 +1,7 @@
+/* PR middle-end/126236 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fduplicate-switch-dispatch 
--param=max-goto-duplication-insns=0 -fdump-rtl-compgotos-details" } */
+
+#include "torture/switch-dispatch-run-1.c"
+
+/* { dg-final { scan-rtl-dump-not "Duplicating dispatch" "compgotos" } } */
diff --git a/gcc/testsuite/gcc.dg/torture/switch-dispatch-run-1.c 
b/gcc/testsuite/gcc.dg/torture/switch-dispatch-run-1.c
new file mode 100644
index 00000000000..c04a52033cf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/switch-dispatch-run-1.c
@@ -0,0 +1,63 @@
+/* PR middle-end/126236 */
+/* { dg-do run } */
+/* { dg-additional-options "-fduplicate-switch-dispatch" } */
+
+/* A bytecode interpreter whose switch sits at the head of the execution
+   loop.  Every opcode, the default and an out of range index are executed,
+   so the range check has to survive being copied into each handler.  */
+
+unsigned int state[16];
+
+__attribute__((noinline)) void
+interp (const unsigned char *pc)
+{
+  for (;;)
+    switch (*pc++)
+      {
+      case 0: return;
+      case 1: state[0] += 1; break;
+      case 2: state[1] ^= 2; break;
+      case 3: state[2] -= 3; break;
+      case 4: state[3] |= 4; break;
+      case 5: state[4] &= 5; break;
+      case 6: state[5] += 6; break;
+      case 7: state[6] ^= 7; break;
+      case 8: state[7] -= 8; break;
+      case 9: state[8] |= 9; break;
+      case 10: state[9] &= 10; break;
+      case 11: state[10] += 11; break;
+      default: state[11] ^= 12; break;
+      }
+}
+
+static const unsigned char prog[] = {
+  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,   /* every explicit case */
+  12, 200, 255,                                /* the default, in and out of 
range */
+  1, 1, 1, 11, 11,                     /* repeats */
+  0
+};
+
+int
+main (void)
+{
+  unsigned int expect[16] = { 0 };
+
+  for (unsigned i = 0; i < 16; i++)
+    state[i] = i + 1;
+  for (unsigned i = 0; i < 16; i++)
+    expect[i] = i + 1;
+
+  expect[0] += 1; expect[1] ^= 2; expect[2] -= 3; expect[3] |= 4;
+  expect[4] &= 5; expect[5] += 6; expect[6] ^= 7; expect[7] -= 8;
+  expect[8] |= 9; expect[9] &= 10; expect[10] += 11;
+  expect[11] ^= 12; expect[11] ^= 12; expect[11] ^= 12;
+  expect[0] += 1; expect[0] += 1; expect[0] += 1;
+  expect[10] += 11; expect[10] += 11;
+
+  interp (prog);
+
+  for (unsigned i = 0; i < 16; i++)
+    if (state[i] != expect[i])
+      __builtin_abort ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/torture/switch-dispatch-run-2.c 
b/gcc/testsuite/gcc.dg/torture/switch-dispatch-run-2.c
new file mode 100644
index 00000000000..86023be21a8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/switch-dispatch-run-2.c
@@ -0,0 +1,67 @@
+/* PR middle-end/126236 */
+/* { dg-do run } */
+/* { dg-additional-options "-fduplicate-switch-dispatch" } */
+
+/* Gaps and shared case labels make table entries repeat, so the successors
+   of a copy are a multiset.  A case target that is a plain forwarder makes
+   cfg cleanup want to redirect an edge of the shared table.  */
+
+int sink;
+
+__attribute__((noinline)) int
+run (const signed char *pc, int n)
+{
+  int acc = 0;
+
+  for (;;)
+    switch (*pc++)
+      {
+      case 3:
+      case 5:
+      case 7:
+       acc += 2;
+       break;
+      case 4:
+       goto fwd;
+      case 6:
+       acc *= 3;
+       break;
+      case 9:
+       acc -= 1;
+       break;
+      case 12:
+       acc ^= 0x55;
+       break;
+      case 17:
+       return acc + n;
+      default:
+       acc += 1000;
+       break;
+      fwd:
+       acc += 7;
+       break;
+      }
+}
+
+static const signed char prog[] = {
+  3, 5, 7, 4, 6, 9, 12, 1, -8, 100, 4, 4, 6, 17
+};
+
+int
+main (void)
+{
+  int acc = 0;
+
+  acc += 2; acc += 2; acc += 2;                /* 3 5 7 */
+  acc += 7;                            /* 4 */
+  acc *= 3;                            /* 6 */
+  acc -= 1;                            /* 9 */
+  acc ^= 0x55;                         /* 12 */
+  acc += 1000; acc += 1000; acc += 1000;       /* 1 -8 100 */
+  acc += 7; acc += 7;                  /* 4 4 */
+  acc *= 3;                            /* 6 */
+
+  if (run (prog, 11) != acc + 11)
+    __builtin_abort ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/torture/switch-dispatch-run-3.c 
b/gcc/testsuite/gcc.dg/torture/switch-dispatch-run-3.c
new file mode 100644
index 00000000000..4a773a0c72d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/switch-dispatch-run-3.c
@@ -0,0 +1,33 @@
+/* PR middle-end/126236 */
+/* { dg-do run } */
+/* { dg-additional-options "-fduplicate-switch-dispatch 
--param=case-values-threshold=1" } */
+
+/* Every predecessor of the dispatch chain can take a copy, so the original
+   dispatch block and its shared table's former owner become unreachable.  */
+
+volatile unsigned int state[3];
+
+__attribute__((noinline, noclone)) void
+interp (const unsigned char *pc)
+{
+  goto first;
+  for (;;)
+    switch (*pc++)
+      {
+      case 0: return;
+      case 1: first: state[0] += 1; break;
+      case 2: state[1] += 2; break;
+      default: state[2] ^= 3; break;
+      }
+}
+
+int
+main (void)
+{
+  static const unsigned char prog[] = { 2, 3, 1, 0 };
+
+  interp (prog);
+  if (state[0] != 2 || state[1] != 2 || state[2] != 3)
+    __builtin_abort ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/torture/switch-dispatch-run-4.c 
b/gcc/testsuite/gcc.dg/torture/switch-dispatch-run-4.c
new file mode 100644
index 00000000000..b821c95eedc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/switch-dispatch-run-4.c
@@ -0,0 +1,62 @@
+/* PR middle-end/126236 */
+/* { dg-do run } */
+/* { dg-additional-options "-fduplicate-switch-dispatch" } */
+
+/* Keep floating values live across the dispatches.  Targets with stack
+   registers must retain a single table owner for stack conversion.  */
+
+__attribute__((noinline, noclone)) long double
+dispatch (const unsigned char *pc, long double seed, volatile unsigned *out)
+{
+  long double a = seed + 1, b = seed + 2, c = seed + 3, d = seed + 4;
+
+  for (;;)
+    switch (*pc++)
+      {
+      case 0: return a + 3 * b + 5 * c + 7 * d;
+      case 1: a += b; out[0] += 1; break;
+      case 2: b += c; out[1] += 2; break;
+      case 3: c += d; out[2] += 3; break;
+      case 4: d += a; out[3] += 4; break;
+      case 5: a -= c; out[0] += 5; break;
+      case 6: b -= d; out[1] += 6; break;
+      case 7: c -= a; out[2] += 7; break;
+      case 8: d -= b; out[3] += 8; break;
+      case 9: a *= 2; out[0] += 9; break;
+      case 10: b *= 2; out[1] += 10; break;
+      case 11: c *= 2; out[2] += 11; break;
+      default: d *= 2; out[3] += 12; break;
+      }
+}
+
+int
+main (void)
+{
+  static const unsigned char code[] = {
+    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 255, 1, 2, 3, 0
+  };
+  for (unsigned seed = 0; seed != 16; ++seed)
+    {
+      volatile unsigned out[4] = { 0 };
+      long double a = seed + 1, b = seed + 2, c = seed + 3, d = seed + 4;
+      a += b;
+      b += c;
+      c += d;
+      d += a;
+      a -= c;
+      b -= d;
+      c -= a;
+      d -= b;
+      a *= 2;
+      b *= 2;
+      c *= 2;
+      d *= 2;
+      a += b;
+      b += c;
+      c += d;
+      if (dispatch (code, seed, out) != a + 3 * b + 5 * c + 7 * d
+          || out[0] != 16 || out[1] != 20 || out[2] != 24 || out[3] != 24)
+        __builtin_abort ();
+    }
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/switch-dispatch-1.c 
b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-1.c
new file mode 100644
index 00000000000..5d60bed79d9
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-1.c
@@ -0,0 +1,39 @@
+/* PR middle-end/126236 */
+/* { dg-do assemble } */
+/* { dg-options "-O2 -fduplicate-switch-dispatch -save-temps" } */
+
+/* Each of the 11 continuing handlers, the default handler and the loop entry
+   get a dispatch of their own, so 13 register indirect branches.  All of them
+   share the one table, whose entries are relative to a single base label,
+   which must be defined exactly once.  */
+
+void
+interp (const unsigned char *pc, volatile unsigned *state)
+{
+  for (;;)
+    switch (*pc++)
+      {
+      case 0: return;
+      case 1: state[0] += 1; break;
+      case 2: state[1] ^= 2; break;
+      case 3: state[2] -= 3; break;
+      case 4: state[3] |= 4; break;
+      case 5: state[4] &= 5; break;
+      case 6: state[5] += 6; break;
+      case 7: state[6] ^= 7; break;
+      case 8: state[7] -= 8; break;
+      case 9: state[8] |= 9; break;
+      case 10: state[9] &= 10; break;
+      case 11: state[10] += 11; break;
+      default: state[11] ^= 12; break;
+      }
+}
+
+/* { dg-final { scan-assembler-times "\\tbr\\tx" 13 } } */
+/* { dg-final { scan-assembler-times "\\.Lrtx\[0-9\]+:" 1 } } */
+
+/* A copy can sit far from that one base label, further than ADR reaches, so 
the
+   copies materialise it with ADRP and ADD.  */
+/* { dg-final { scan-assembler-times "adrp\\tx\[0-9\]+, \\.Lrtx" 13 } } */
+/* { dg-final { scan-assembler-times "add\\tx\[0-9\]+, x\[0-9\]+, 
:lo12:\\.Lrtx" 13 } } */
+/* { dg-final { scan-assembler-not "\\tadr\\tx\[0-9\]+, \\.Lrtx" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/switch-dispatch-2.c 
b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-2.c
new file mode 100644
index 00000000000..9824a805683
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-2.c
@@ -0,0 +1,32 @@
+/* PR middle-end/126236 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fduplicate-switch-dispatch -mbranch-protection=bti" } */
+
+/* Duplicating the dispatch must not duplicate the landing pads: one BTI J per
+   case handler, not one per handler per dispatch copy.  */
+
+void
+interp (const unsigned char *pc, volatile unsigned *state)
+{
+  for (;;)
+    switch (*pc++)
+      {
+      case 0: return;
+      case 1: state[0] += 1; break;
+      case 2: state[1] ^= 2; break;
+      case 3: state[2] -= 3; break;
+      case 4: state[3] |= 4; break;
+      case 5: state[4] &= 5; break;
+      case 6: state[5] += 6; break;
+      case 7: state[6] ^= 7; break;
+      case 8: state[7] -= 8; break;
+      case 9: state[8] |= 9; break;
+      case 10: state[9] &= 10; break;
+      case 11: state[10] += 11; break;
+      default: state[11] ^= 12; break;
+      }
+}
+
+/* 12 table targets plus the function entry.  */
+/* { dg-final { scan-assembler-times "bti\\tj|hint\\t36" 12 } } */
+/* { dg-final { scan-assembler-times "bti\\tc|hint\\t34" 1 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/switch-dispatch-3.c 
b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-3.c
new file mode 100644
index 00000000000..46455328128
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-3.c
@@ -0,0 +1,30 @@
+/* PR middle-end/126236 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fduplicate-switch-dispatch -fdump-rtl-compgotos-details" 
} */
+
+/* The dispatch of an interpreter loop is duplicated into the handlers that
+   branch back to it.  There are 11 such handlers plus the default one.  */
+
+void
+interp (const unsigned char *pc, volatile unsigned *state)
+{
+  for (;;)
+    switch (*pc++)
+      {
+      case 0: return;
+      case 1: state[0] += 1; break;
+      case 2: state[1] ^= 2; break;
+      case 3: state[2] -= 3; break;
+      case 4: state[3] |= 4; break;
+      case 5: state[4] &= 5; break;
+      case 6: state[5] += 6; break;
+      case 7: state[6] ^= 7; break;
+      case 8: state[7] -= 8; break;
+      case 9: state[8] |= 9; break;
+      case 10: state[9] &= 10; break;
+      case 11: state[10] += 11; break;
+      default: state[11] ^= 12; break;
+      }
+}
+
+/* { dg-final { scan-rtl-dump "Duplicating dispatch" "compgotos" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/switch-dispatch-4.c 
b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-4.c
new file mode 100644
index 00000000000..e7d1b387261
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-4.c
@@ -0,0 +1,10 @@
+/* PR middle-end/126236 */
+/* { dg-do assemble } */
+/* { dg-options "-O2 -fduplicate-switch-dispatch -mcmodel=tiny -save-temps" } 
*/
+
+#include "switch-dispatch-1.c"
+
+/* { dg-final { scan-assembler-times "\\tbr\\tx" 13 } } */
+/* { dg-final { scan-assembler-times "\\.Lrtx\[0-9\]+:" 1 } } */
+/* { dg-final { scan-assembler-times "\\tadr\\tx\[0-9\]+, \\.Lrtx" 13 } } */
+/* { dg-final { scan-assembler-not "adrp\\tx\[0-9\]+, \\.Lrtx" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/switch-dispatch-5.c 
b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-5.c
new file mode 100644
index 00000000000..b35663b6e9b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-5.c
@@ -0,0 +1,10 @@
+/* PR middle-end/126236 */
+/* { dg-do assemble } */
+/* { dg-options "-O2 -fduplicate-switch-dispatch -mcmodel=large -fno-pie 
-save-temps" } */
+
+#include "switch-dispatch-1.c"
+
+/* { dg-final { scan-assembler-times "\\tbr\\tx" 13 } } */
+/* { dg-final { scan-assembler-times "\\.Lrtx\[0-9\]+:" 1 } } */
+/* { dg-final { scan-assembler-times "adrp\\tx\[0-9\]+, \\.Lrtx" 13 } } */
+/* { dg-final { scan-assembler-times "add\\tx\[0-9\]+, x\[0-9\]+, 
:lo12:\\.Lrtx" 13 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/switch-dispatch-6.c 
b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-6.c
new file mode 100644
index 00000000000..be26e047dcf
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-6.c
@@ -0,0 +1,10 @@
+/* PR middle-end/126236 */
+/* { dg-do assemble } */
+/* { dg-options "-O2 -fno-duplicate-switch-dispatch -fno-pie -save-temps" } */
+
+#include "switch-dispatch-1.c"
+
+/* { dg-final { scan-assembler-times "\\tbr\\tx" 1 } } */
+/* { dg-final { scan-assembler-times "\\.Lrtx\[0-9\]+:" 1 } } */
+/* { dg-final { scan-assembler-times "\\tadr\\tx\[0-9\]+, \\.Lrtx" 1 } } */
+/* { dg-final { scan-assembler-not "adrp\\tx\[0-9\]+, \\.Lrtx" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/switch-dispatch-7.c 
b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-7.c
new file mode 100644
index 00000000000..7645827f662
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/switch-dispatch-7.c
@@ -0,0 +1,11 @@
+/* PR middle-end/126236 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fduplicate-switch-dispatch -mcmodel=tiny -fno-pie 
-fselective-scheduling2 -fsel-sched-pipelining -fno-gcse 
-fno-move-loop-invariants -fdump-rtl-compgotos-details" } */
+
+/* Keep the table address in the dispatch blocks.  Selective scheduling must
+   not redirect shared-table edges to create loop preheaders.  */
+
+#include "switch-dispatch-1.c"
+
+/* { dg-final { scan-rtl-dump "Duplicating dispatch" "compgotos" } } */
+/* { dg-final { scan-assembler-times "\\.Lrtx\[0-9\]+:" 1 } } */
-- 
2.50.1 (Apple Git-155)

Reply via email to