summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Müllner <christoph.muellner@theobroma-systems.com>2020-11-02 14:29:09 +0100
committerChristoph Müllner <christoph.muellner@theobroma-systems.com>2020-12-09 17:52:40 +0100
commitd6836837c370a5f722eac033f49efd4d073cc0f7 (patch)
treec695d21ebfd8d13f4c19ccf7dbc72fbc80a21931
parent0bfd5a723294c025fff46411922043d72dff35ab (diff)
aarch64: Retpoline (Spectre-V2 mitigation) for aarch64.master-retpoline
The compiler option -mindirect-branch=<value> converts indirect branch-and-link-register and branch-register instructions according to <value>. The default is ``keep``, which keeps indirect branch-and-link-register and branch-register instructions unmodified. ``thunk`` converts indirect branch-and-link-register/branch-register instructions to a branch-and-link/branch to a function containing a retpoline (to stop speculative execution) followed by a branch-register to the target. ``thunk-inline`` is similar to ``thunk``, but inlines the retpoline before the branch-and-link-register/branch-register instruction. ``thunk-extern`` is also similar to ``thunk``, but does not insert the functions containing the retpoline. When using this option, these functions need to be provided in a separate object file. The retpoline functions exist for each register and are named ``__aarch64_indirect_thunk_xN`` (N being the register number). It is also possible to override the indirect-branch setting for individual fuctions using the function attribute ``indirect_branch``. The actual retpoline instruction sequence, which prevents speculative indirect branches looks like this:: str x30, [sp, #-16]! bl 101f 100: //speculation trap wfe b 100b 101: //do ROP adr x30, 102f ret 102: //non-spec code ldr x30, [sp], #16 This patch has been tested with the included testcases and various other source bases (benchmarks, retpoline-patched arm64 kernel, etc.).
-rw-r--r--gcc/config/aarch64/aarch64-opts.h9
-rw-r--r--gcc/config/aarch64/aarch64.c298
-rw-r--r--gcc/config/aarch64/aarch64.h2
-rw-r--r--gcc/config/aarch64/aarch64.opt20
-rw-r--r--gcc/doc/invoke.texi20
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-1.c25
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-2.c26
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-3.c26
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-4.c27
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-5.c25
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-6.c26
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-1.c28
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-2.c27
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-3.c29
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-4.c28
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-5.c24
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-6.c24
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-1.c22
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-2.c23
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-3.c23
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-4.c24
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-5.c20
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-6.c21
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-1.c26
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-2.c26
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-3.c26
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-4.c27
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-5.c23
-rw-r--r--gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-6.c24
29 files changed, 941 insertions, 8 deletions
diff --git a/gcc/config/aarch64/aarch64-opts.h b/gcc/config/aarch64/aarch64-opts.h
index ee7bed34924d..0113533892de 100644
--- a/gcc/config/aarch64/aarch64-opts.h
+++ b/gcc/config/aarch64/aarch64-opts.h
@@ -98,4 +98,13 @@ enum stack_protector_guard {
SSP_GLOBAL /* global canary */
};
+/* Values for -mindirect-branch option. */
+enum indirect_branch {
+ indirect_branch_unset = 0,
+ indirect_branch_keep,
+ indirect_branch_thunk,
+ indirect_branch_thunk_inline,
+ indirect_branch_thunk_extern
+};
+
#endif
diff --git a/gcc/config/aarch64/aarch64.c b/gcc/config/aarch64/aarch64.c
index 4799679f9e59..56366faeb69d 100644
--- a/gcc/config/aarch64/aarch64.c
+++ b/gcc/config/aarch64/aarch64.c
@@ -1508,6 +1508,44 @@ handle_aarch64_vector_pcs_attribute (tree *node, tree name, tree,
gcc_unreachable ();
}
+/* Check whether 'indirect_branch' fndecl attribute is valid. */
+
+static tree
+aarch64_handle_fndecl_attribute (tree *node, tree name, tree args, int,
+ bool *no_add_attrs)
+{
+ if (TREE_CODE (*node) != FUNCTION_DECL)
+ {
+ warning (OPT_Wattributes, "%qE attribute only applies to functions",
+ name);
+ *no_add_attrs = true;
+ }
+
+ if (is_attribute_p ("indirect_branch", name))
+ {
+ tree cst = TREE_VALUE (args);
+ if (TREE_CODE (cst) != STRING_CST)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute requires a string constant argument",
+ name);
+ *no_add_attrs = true;
+ }
+ else if (strcmp (TREE_STRING_POINTER (cst), "keep") != 0
+ && strcmp (TREE_STRING_POINTER (cst), "thunk") != 0
+ && strcmp (TREE_STRING_POINTER (cst), "thunk-inline") != 0
+ && strcmp (TREE_STRING_POINTER (cst), "thunk-extern") != 0)
+ {
+ warning (OPT_Wattributes,
+ "argument to %qE attribute is not "
+ "(keep|thunk|thunk-inline|thunk-extern)", name);
+ *no_add_attrs = true;
+ }
+ }
+
+ return NULL_TREE;
+}
+
/* Table of machine attributes. */
static const struct attribute_spec aarch64_attribute_table[] =
{
@@ -1521,6 +1559,8 @@ static const struct attribute_spec aarch64_attribute_table[] =
{ "Advanced SIMD type", 1, 1, false, true, false, true, NULL, NULL },
{ "SVE type", 3, 3, false, true, false, true, NULL, NULL },
{ "SVE sizeless type", 0, 0, false, true, false, true, NULL, NULL },
+ { "indirect_branch", 1, 1, true, false, false, false,
+ aarch64_handle_fndecl_attribute, NULL },
{ NULL, 0, 0, false, false, false, false, NULL, NULL }
};
@@ -15227,6 +15267,35 @@ aarch64_save_restore_target_globals (tree new_tree)
TREE_TARGET_GLOBALS (new_tree) = save_target_globals_default_opts ();
}
+static void
+aarch64_set_indirect_branch_type (tree fndecl)
+{
+ if (cfun->machine->indirect_branch_type == indirect_branch_unset)
+ {
+ tree attr = lookup_attribute ("indirect_branch",
+ DECL_ATTRIBUTES (fndecl));
+ if (attr != NULL)
+ {
+ tree args = TREE_VALUE (attr);
+ if (args == NULL)
+ gcc_unreachable ();
+ tree cst = TREE_VALUE (args);
+ if (strcmp (TREE_STRING_POINTER (cst), "keep") == 0)
+ cfun->machine->indirect_branch_type = indirect_branch_keep;
+ else if (strcmp (TREE_STRING_POINTER (cst), "thunk") == 0)
+ cfun->machine->indirect_branch_type = indirect_branch_thunk;
+ else if (strcmp (TREE_STRING_POINTER (cst), "thunk-inline") == 0)
+ cfun->machine->indirect_branch_type = indirect_branch_thunk_inline;
+ else if (strcmp (TREE_STRING_POINTER (cst), "thunk-extern") == 0)
+ cfun->machine->indirect_branch_type = indirect_branch_thunk_extern;
+ else
+ gcc_unreachable ();
+ }
+ else
+ cfun->machine->indirect_branch_type = aarch64_indirect_branch;
+ }
+}
+
/* Implement TARGET_SET_CURRENT_FUNCTION. Unpack the codegen decisions
like tuning and ISA features from the DECL_FUNCTION_SPECIFIC_TARGET
of the function, if such exists. This function may be called multiple
@@ -15237,7 +15306,18 @@ static void
aarch64_set_current_function (tree fndecl)
{
if (!fndecl || fndecl == aarch64_previous_fndecl)
- return;
+ {
+ if (fndecl != NULL_TREE)
+ {
+ aarch64_set_indirect_branch_type (fndecl);
+ }
+ return;
+ }
+
+ if (fndecl != NULL_TREE)
+ {
+ aarch64_set_indirect_branch_type (fndecl);
+ }
tree old_tree = (aarch64_previous_fndecl
? DECL_FUNCTION_SPECIFIC_TARGET (aarch64_previous_fndecl)
@@ -23659,27 +23739,225 @@ aarch64_asm_file_end ()
#endif
}
+/* Label count for call and return thunks. It is used to make unique
+ labels in call and return thunks. */
+static int indirectlabelno;
+
+/* Bit masks of integer registers, which contain branch target, used
+ by call and return thunks functions. */
+static int indirect_thunks_used;
+
+#ifndef INDIRECT_LABEL
+# define INDIRECT_LABEL "LIND"
+#endif
+
+/* Fills in the label name that should be used for the indirect thunk. */
+
+static void
+indirect_thunk_name (char name[32], int regno)
+{
+ sprintf (name, "__aarch64_indirect_thunk_%s",
+ reg_names[regno]);
+}
+
+/* Output a retpoline thunk for aarch64:
+
+ push lr
+ bl L2
+L1: wfe
+ b L1
+L2: mov lr, &L3
+ ret
+L3: pop lr
+ */
+
+static void
+output_indirect_thunk (bool save_lr)
+{
+ char indirectlabel1[32];
+ char indirectlabel2[32];
+ char indirectlabel3[32];
+
+ ASM_GENERATE_INTERNAL_LABEL (indirectlabel1, INDIRECT_LABEL,
+ indirectlabelno++);
+ ASM_GENERATE_INTERNAL_LABEL (indirectlabel2, INDIRECT_LABEL,
+ indirectlabelno++);
+ ASM_GENERATE_INTERNAL_LABEL (indirectlabel3, INDIRECT_LABEL,
+ indirectlabelno++);
+
+ if (save_lr)
+ {
+ /* push lr */
+ fputs ("\tstr\tx30, [sp, #-16]!\n", asm_out_file);
+ }
+
+ /* bl L2 */
+ fputs ("\tbl\t", asm_out_file);
+ assemble_name_raw (asm_out_file, indirectlabel2);
+ fputc ('\n', asm_out_file);
+
+ /* L1: wfe/dsb loop */
+ ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, indirectlabel1);
+ fprintf (asm_out_file, "\twfe\n");
+ fputs ("\tb\t", asm_out_file);
+ assemble_name_raw (asm_out_file, indirectlabel1);
+ fputc ('\n', asm_out_file);
+
+ /* L2: lr=&L3; ret */
+ ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, indirectlabel2);
+ fputs ("\tadr\tx30, ", asm_out_file);
+ assemble_name_raw (asm_out_file, indirectlabel3);
+ fputc ('\n', asm_out_file);
+ fputs ("\tret\n", asm_out_file);
+
+ /* L3: */
+ ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, indirectlabel3);
+
+ if (save_lr)
+ {
+ /* pop lr */
+ fputs ("\tldr\tx30, [sp], #16\n", asm_out_file);
+ }
+}
+
+static void
+output_indirect_thunk_function (int regno)
+{
+ char name[32];
+ tree decl;
+
+ /* Create __aarch64_indirect_thunk */
+ indirect_thunk_name (name, regno);
+ decl = build_decl (BUILTINS_LOCATION, FUNCTION_DECL,
+ get_identifier (name),
+ build_function_type_list (void_type_node, NULL_TREE));
+ DECL_RESULT (decl) = build_decl (BUILTINS_LOCATION, RESULT_DECL,
+ NULL_TREE, void_type_node);
+ TREE_PUBLIC (decl) = 1;
+ TREE_STATIC (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+
+ cgraph_node::create (decl)->set_comdat_group (DECL_ASSEMBLER_NAME (decl));
+
+ targetm.asm_out.unique_section (decl, 0);
+ switch_to_section (get_named_section (decl, NULL, 0));
+
+ targetm.asm_out.globalize_label (asm_out_file, name);
+ fputs ("\t.hidden\t", asm_out_file);
+ assemble_name (asm_out_file, name);
+ putc ('\n', asm_out_file);
+ ASM_DECLARE_FUNCTION_NAME (asm_out_file, name, decl);
+
+ DECL_INITIAL (decl) = make_node (BLOCK);
+ current_function_decl = decl;
+ allocate_struct_function (decl, false);
+ init_function_start (decl);
+ /* We're about to hide the function body from callees of final_* by
+ emitting it directly; tell them we're a thunk, if they care. */
+ cfun->is_thunk = true;
+ first_function_block_is_cold = false;
+ /* Make sure unwind info is emitted for the thunk if needed. */
+ final_start_function (emit_barrier (), asm_out_file, 1);
+
+ output_indirect_thunk (true);
+ rtx xop = gen_rtx_REG (word_mode, regno);
+ output_asm_insn ("br\t%0", &xop);
+
+ final_end_function ();
+ init_insn_lengths ();
+ free_after_compilation (cfun);
+ set_cfun (NULL);
+ current_function_decl = NULL;
+}
+
const char *
-aarch64_indirect_branch_asm (rtx addr)
+aarch64_indirect_branch_asm (rtx call_op)
{
- output_asm_insn ("br\t%0", &addr);
+ char thunk_name_buf[32];
+ char *thunk_name;
+ int regno = REGNO (call_op);
+
+ if (cfun->machine->indirect_branch_type == indirect_branch_thunk_inline)
+ {
+ output_indirect_thunk (true);
+ output_asm_insn ("br\t%0", &call_op);
+ }
+ else if (cfun->machine->indirect_branch_type == indirect_branch_thunk)
+ {
+ indirect_thunks_used |= 1 << regno;
+
+ indirect_thunk_name (thunk_name_buf, regno);
+ thunk_name = thunk_name_buf;
+ fprintf (asm_out_file, "\tb\t%s\n", thunk_name);
+ }
+ else if (cfun->machine->indirect_branch_type == indirect_branch_thunk_extern)
+ {
+ indirect_thunk_name (thunk_name_buf, regno);
+ thunk_name = thunk_name_buf;
+ fprintf (asm_out_file, "\tb\t%s\n", thunk_name);
+ }
+ else
+ {
+ output_asm_insn ("br\t%0", &call_op);
+ }
return "";
}
const char *
-aarch64_indirect_call_asm (rtx addr)
+aarch64_indirect_call_asm (rtx call_op)
{
- gcc_assert (REG_P (addr));
+ char thunk_name_buf[32];
+ char *thunk_name;
+ int regno = REGNO (call_op);
+
+ gcc_assert (REG_P (call_op));
if (aarch64_harden_sls_blr_p ())
{
- rtx stub_label = aarch64_sls_create_blr_label (REGNO (addr));
+ rtx stub_label = aarch64_sls_create_blr_label (REGNO (call_op));
output_asm_insn ("bl\t%0", &stub_label);
+ return "";
+ }
+
+ if (cfun->machine->indirect_branch_type == indirect_branch_thunk_inline)
+ {
+ output_indirect_thunk (true);
+ output_asm_insn ("blr\t%0", &call_op);
+ }
+ else if (cfun->machine->indirect_branch_type == indirect_branch_thunk)
+ {
+ indirect_thunks_used |= 1 << regno;
+
+ indirect_thunk_name (thunk_name_buf, regno);
+ thunk_name = thunk_name_buf;
+ fprintf (asm_out_file, "\tbl\t%s\n", thunk_name);
+ }
+ else if (cfun->machine->indirect_branch_type == indirect_branch_thunk_extern)
+ {
+ indirect_thunk_name (thunk_name_buf, regno);
+ thunk_name = thunk_name_buf;
+ fprintf (asm_out_file, "\tbl\t%s\n", thunk_name);
}
else
- output_asm_insn ("blr\t%0", &addr);
+ {
+ output_asm_insn ("blr\t%0", &call_op);
+ }
return "";
}
+/* Implements TARGET_ASM_CODE_END. */
+
+static void
+aarch64_code_end (void)
+{
+ int regno;
+
+ for (regno = R0_REGNUM; regno <= SP_REGNUM; regno++)
+ {
+ if (indirect_thunks_used & (1 << regno))
+ output_indirect_thunk_function (regno);
+ }
+}
+
/* Target-specific selftests. */
#if CHECKING_P
@@ -23752,6 +24030,9 @@ aarch64_run_selftests (void)
#define TARGET_ASM_CAN_OUTPUT_MI_THUNK \
hook_bool_const_tree_hwi_hwi_const_tree_true
+#undef TARGET_ASM_CODE_END
+#define TARGET_ASM_CODE_END aarch64_code_end
+
#undef TARGET_ASM_FILE_START
#define TARGET_ASM_FILE_START aarch64_start_file
@@ -24203,6 +24484,9 @@ aarch64_libgcc_floating_mode_supported_p
#undef TARGET_ATTRIBUTE_TABLE
#define TARGET_ATTRIBUTE_TABLE aarch64_attribute_table
+#undef TARGET_FUNCTION_ATTRIBUTE_INLINABLE_P
+#define TARGET_FUNCTION_ATTRIBUTE_INLINABLE_P hook_bool_const_tree_true
+
#undef TARGET_SIMD_CLONE_COMPUTE_VECSIZE_AND_SIMDLEN
#define TARGET_SIMD_CLONE_COMPUTE_VECSIZE_AND_SIMDLEN \
aarch64_simd_clone_compute_vecsize_and_simdlen
diff --git a/gcc/config/aarch64/aarch64.h b/gcc/config/aarch64/aarch64.h
index 0bdcc74ace58..d23bb8e89810 100644
--- a/gcc/config/aarch64/aarch64.h
+++ b/gcc/config/aarch64/aarch64.h
@@ -903,6 +903,8 @@ typedef struct GTY (()) machine_function
/* One entry for each general purpose register. */
rtx call_via[SP_REGNUM];
bool label_is_assembled;
+ /* How to generate indirect branch. */
+ ENUM_BITFIELD (indirect_branch) indirect_branch_type : 3;
} machine_function;
#endif
diff --git a/gcc/config/aarch64/aarch64.opt b/gcc/config/aarch64/aarch64.opt
index 1b3d942e0f5d..88b61d576009 100644
--- a/gcc/config/aarch64/aarch64.opt
+++ b/gcc/config/aarch64/aarch64.opt
@@ -138,6 +138,26 @@ mabi=
Target RejectNegative Joined Enum(aarch64_abi) Var(aarch64_abi) Init(AARCH64_ABI_DEFAULT)
Generate code that conforms to the specified ABI.
+mindirect-branch=
+Target Report RejectNegative Joined Enum(indirect_branch) Var(aarch64_indirect_branch) Init(indirect_branch_keep)
+Insert return thunk before br and blr.
+
+Enum
+Name(indirect_branch) Type(enum indirect_branch)
+Known indirect branch choices (for use with the -mindirect-branch= options):
+
+EnumValue
+Enum(indirect_branch) String(keep) Value(indirect_branch_keep)
+
+EnumValue
+Enum(indirect_branch) String(thunk) Value(indirect_branch_thunk)
+
+EnumValue
+Enum(indirect_branch) String(thunk-inline) Value(indirect_branch_thunk_inline)
+
+EnumValue
+Enum(indirect_branch) String(thunk-extern) Value(indirect_branch_thunk_extern)
+
moverride=
Target RejectNegative ToLower Joined Var(aarch64_override_tune_string)
-moverride=<string> Power users only! Override CPU optimization parameters.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 671b29702eae..b0d000b3380f 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -721,7 +721,7 @@ Objective-C and Objective-C++ Dialects}.
-moverride=@var{string} -mverbose-cost-dump @gol
-mstack-protector-guard=@var{guard} -mstack-protector-guard-reg=@var{sysreg} @gol
-mstack-protector-guard-offset=@var{offset} -mtrack-speculation @gol
--moutline-atomics }
+-moutline-atomics -mindirect-branch=@var{choice}}
@emph{Adapteva Epiphany Options}
@gccoptlist{-mhalf-reg-file -mprefer-short-insn-regs @gol
@@ -18178,6 +18178,24 @@ hardware SVE vector lengths.
The default is @samp{-msve-vector-bits=scalable}, which produces
vector-length agnostic code.
+
+@item -mindirect-branch=@var{choice}
+@opindex -mindirect-branch
+Convert indirect branch-and-link-register and branch-register with @var{choice}.
+The default is @samp{keep}, which keeps indirect branch-and-link-register and
+branch-register instructions unmodified.
+@samp{thunk} converts indirect branch-and-link-register/branch-register
+instructions to a branch-and-link/branch to a function containing a retpoline
+(to stop speculative execution) followed by a branch-register to the target.
+@samp{thunk-inline} similar to @samp{thunk}, but inlines the retpoline
+before the branch-and-link-register/branch-register instruction.
+@samp{thunk-extern} similar to @samp{thunk}, but does not insert the functions
+containing the retpoline. When using this option, these functions need to be
+provided in a separate object file. The retpoline functions exist for each
+register and are named __aarch64_indirect_thunk_xN (N being the register
+number). You can control this behavior for a specific function by using the
+function attribute @code{indirect_branch}. @xref{Function Attributes}.
+
@end table
@subsubsection @option{-march} and @option{-mcpu} Feature Modifiers
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-1.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-1.c
new file mode 100644
index 000000000000..424057e2db76
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-1.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch;
+
+void
+male_indirect_jump (long offset)
+{
+ dispatch(offset);
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:.LANCHOR0\\\]" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*__aarch64_indirect_thunk_x1" } } */
+
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "br\[ \t\]*x1" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-2.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-2.c
new file mode 100644
index 000000000000..325fe8ee8755
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-2.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch[256];
+
+void
+male_indirect_jump (long offset)
+{
+ dispatch[offset](offset);
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:.LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, x0, lsl 3\\\]" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*__aarch64_indirect_thunk_x1" } } */
+
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "br\[ \t\]*x1" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-3.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-3.c
new file mode 100644
index 000000000000..17c0601060cb
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-3.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch;
+
+int
+male_indirect_jump (long offset)
+{
+ dispatch(offset);
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:.LANCHOR0\\\]" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*__aarch64_indirect_thunk_x1" } } */
+
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "br\[ \t\]*x1" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-4.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-4.c
new file mode 100644
index 000000000000..42ae4322c04a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-4.c
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch[256];
+
+int
+male_indirect_jump (long offset)
+{
+ dispatch[offset](offset);
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:.LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, x0, lsl 3\\\]" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*__aarch64_indirect_thunk_x1" } } */
+
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "br\[ \t\]*x1" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-5.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-5.c
new file mode 100644
index 000000000000..e84350d51a1b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-5.c
@@ -0,0 +1,25 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk" } */
+
+extern void bar (void);
+
+void
+foo (void)
+{
+ bar ();
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x0, _GLOBAL_OFFSET_TABLE_" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x16, \\\[x0, #:gotpage_lo15:bar\\\]" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*__aarch64_indirect_thunk_x16" } } */
+
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "br\[ \t\]*x16" } } */
+
+/* { dg-final { scan-assembler-not "__aarch64_indirect_thunk\n" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-6.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-6.c
new file mode 100644
index 000000000000..4c21735dc3c8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-6.c
@@ -0,0 +1,26 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk" } */
+
+extern void bar (void);
+
+int
+foo (void)
+{
+ bar ();
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x0, _GLOBAL_OFFSET_TABLE_" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x0, \\\[x0, #:gotpage_lo15:bar\\\]" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*__aarch64_indirect_thunk_x0" } } */
+
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "br\[ \t\]*x0" } } */
+
+/* { dg-final { scan-assembler-not "__aarch64_indirect_thunk\n" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-1.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-1.c
new file mode 100644
index 000000000000..92260b85ef24
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-1.c
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch;
+
+extern void male_indirect_jump (long)
+ __attribute__ ((indirect_branch("thunk")));
+
+void
+male_indirect_jump (long offset)
+{
+ dispatch(offset);
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:.LANCHOR0\\\]" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*__aarch64_indirect_thunk_x1" } } */
+
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "br\[ \t\]*x1" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-2.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-2.c
new file mode 100644
index 000000000000..89b3f8b310f0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-2.c
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch[256];
+
+__attribute__ ((indirect_branch("thunk")))
+void
+male_indirect_jump (long offset)
+{
+ dispatch[offset](offset);
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:.LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, x0, lsl 3\\\]" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*__aarch64_indirect_thunk_x1" } } */
+
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "br\[ \t\]*x1" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-3.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-3.c
new file mode 100644
index 000000000000..47f5e1a8fcc7
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-3.c
@@ -0,0 +1,29 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch;
+extern int male_indirect_jump (long)
+ __attribute__ ((indirect_branch("thunk-inline")));
+
+int
+male_indirect_jump (long offset)
+{
+ dispatch(offset);
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:.LANCHOR0\\\]" } } */
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "blr\[ \t\]*x1" } } */
+
+/* { dg-final { scan-assembler-not "__aarch64_indirect_thunk" } } */
+
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-4.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-4.c
new file mode 100644
index 000000000000..35d640e36e41
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-4.c
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch[256];
+
+__attribute__ ((indirect_branch("thunk-inline")))
+int
+male_indirect_jump (long offset)
+{
+ dispatch[offset](offset);
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:.LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, x0, lsl 3\\\]" } } */
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "blr\[ \t\]*x1" } } */
+
+/* { dg-final { scan-assembler-not "__aarch64_indirect_thunk" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-5.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-5.c
new file mode 100644
index 000000000000..5dd9bf44d2b0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-5.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch;
+extern int male_indirect_jump (long)
+ __attribute__ ((indirect_branch("thunk-extern")));
+
+int
+male_indirect_jump (long offset)
+{
+ dispatch(offset);
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:.LANCHOR0\\\]" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*__aarch64_indirect_thunk_x1" } } */
+
+/* { dg-final { scan-assembler-not "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler-not "wfe" } } */
+/* { dg-final { scan-assembler-not "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler-not "adr\[ \t\]*x30, .LIND2" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-6.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-6.c
new file mode 100644
index 000000000000..5179a55dafd7
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-attr-6.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch[256];
+
+__attribute__ ((indirect_branch("thunk-extern")))
+int
+male_indirect_jump (long offset)
+{
+ dispatch[offset](offset);
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:.LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, x0, lsl 3\\\]" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*__aarch64_indirect_thunk_x1" } } */
+
+/* { dg-final { scan-assembler-not "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler-not "wfe" } } */
+/* { dg-final { scan-assembler-not "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler-not "adr\[ \t\]*x30, .LIND2" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-1.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-1.c
new file mode 100644
index 000000000000..931d21364079
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-1.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch;
+
+void
+male_indirect_jump (long offset)
+{
+ dispatch(offset);
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:.LANCHOR0\\\]" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*__aarch64_indirect_thunk_x1" } } */
+
+/* { dg-final { scan-assembler-not "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler-not "wfe" } } */
+/* { dg-final { scan-assembler-not "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler-not "adrp\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler-not "add\[ \t\]*x30, x30, :lo12:.LIND2" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-2.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-2.c
new file mode 100644
index 000000000000..93aaec3d6248
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-2.c
@@ -0,0 +1,23 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch[256];
+
+void
+male_indirect_jump (long offset)
+{
+ dispatch[offset](offset);
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:.LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, x0, lsl 3\\\]" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*__aarch64_indirect_thunk_x1" } } */
+
+/* { dg-final { scan-assembler-not "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler-not "wfe" } } */
+/* { dg-final { scan-assembler-not "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler-not "adrp\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler-not "add\[ \t\]*x30, x30, :lo12:.LIND2" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-3.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-3.c
new file mode 100644
index 000000000000..66a1e80320e1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-3.c
@@ -0,0 +1,23 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch;
+
+int
+male_indirect_jump (long offset)
+{
+ dispatch(offset);
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:.LANCHOR0\\\]" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*__aarch64_indirect_thunk_x1" } } */
+
+/* { dg-final { scan-assembler-not "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler-not "wfe" } } */
+/* { dg-final { scan-assembler-not "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler-not "adrp\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler-not "add\[ \t\]*x30, x30, :lo12:.LIND2" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-4.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-4.c
new file mode 100644
index 000000000000..5a26bb21e578
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-4.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch[256];
+
+int
+male_indirect_jump (long offset)
+{
+ dispatch[offset](offset);
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:.LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, x0, lsl 3\\\]" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*__aarch64_indirect_thunk_x1" } } */
+
+/* { dg-final { scan-assembler-not "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler-not "wfe" } } */
+/* { dg-final { scan-assembler-not "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler-not "adrp\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler-not "add\[ \t\]*x30, x30, :lo12:.LIND2" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-5.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-5.c
new file mode 100644
index 000000000000..6a3288e001f4
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-5.c
@@ -0,0 +1,20 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk-extern" } */
+
+extern void bar (void);
+
+void
+foo (void)
+{
+ bar ();
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x0, _GLOBAL_OFFSET_TABLE_" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x16, \\\[x0, #:gotpage_lo15:bar\\\]" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*__aarch64_indirect_thunk_x16" } } */
+
+/* { dg-final { scan-assembler-not "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler-not "wfe" } } */
+/* { dg-final { scan-assembler-not "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler-not "adrp\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler-not "add\[ \t\]*x30, x30, :lo12:.LIND2" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-6.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-6.c
new file mode 100644
index 000000000000..635b53b35b90
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-extern-6.c
@@ -0,0 +1,21 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk-extern" } */
+
+extern void bar (void);
+
+int
+foo (void)
+{
+ bar ();
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x0, _GLOBAL_OFFSET_TABLE_" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x0, \\\[x0, #:gotpage_lo15:bar\\\]" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*__aarch64_indirect_thunk_x0" } } */
+
+/* { dg-final { scan-assembler-not "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler-not "wfe" } } */
+/* { dg-final { scan-assembler-not "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler-not "adrp\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler-not "add\[ \t\]*x30, x30, :lo12:.LIND2" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-1.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-1.c
new file mode 100644
index 000000000000..4ed67ca19a1a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-1.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch;
+
+void
+male_indirect_jump (long offset)
+{
+ dispatch(offset);
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x16, #:lo12:.LANCHOR0\\\]" } } */
+/* { dg-final { scan-assembler "mov\[ \t\]*x16, x1" } } */
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "br\[ \t\]*x16" } } */
+
+/* { dg-final { scan-assembler-not "__aarch64_indirect_thunk" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-2.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-2.c
new file mode 100644
index 000000000000..059f73b7129e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-2.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch[256];
+
+void
+male_indirect_jump (long offset)
+{
+ dispatch[offset](offset);
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:.LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, x0, lsl 3\\\]" } } */
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "br\[ \t\]*x1" } } */
+
+/* { dg-final { scan-assembler-not "__aarch64_indirect_thunk" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-3.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-3.c
new file mode 100644
index 000000000000..81c60b7ef659
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-3.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch;
+
+int
+male_indirect_jump (long offset)
+{
+ dispatch(offset);
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:.LANCHOR0\\\]" } } */
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "blr\[ \t\]*x1" } } */
+
+/* { dg-final { scan-assembler-not "__aarch64_indirect_thunk" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-4.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-4.c
new file mode 100644
index 000000000000..ff1e3da0afec
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-4.c
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */
+
+typedef void (*dispatch_t)(long offset);
+
+dispatch_t dispatch[256];
+
+int
+male_indirect_jump (long offset)
+{
+ dispatch[offset](offset);
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x1, .LANCHOR0" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:.LANCHOR0" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, x0, lsl 3\\\]" } } */
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "blr\[ \t\]*x1" } } */
+
+/* { dg-final { scan-assembler-not "__aarch64_indirect_thunk" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-5.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-5.c
new file mode 100644
index 000000000000..05c8093d7ff2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-5.c
@@ -0,0 +1,23 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk-inline" } */
+
+extern void bar (void);
+
+void
+foo (void)
+{
+ bar ();
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x0, _GLOBAL_OFFSET_TABLE_" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x16, \\\[x0, #:gotpage_lo15:bar\\\]" } } */
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "br\[ \t\]*x16" } } */
+
+/* { dg-final { scan-assembler-not "__aarch64_indirect_thunk\n" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-6.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-6.c
new file mode 100644
index 000000000000..9cedefadd8d6
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-6.c
@@ -0,0 +1,24 @@
+/* { dg-do compile { target *-*-linux* } } */
+/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk-inline" } */
+
+extern void bar (void);
+
+int
+foo (void)
+{
+ bar ();
+ return 0;
+}
+
+/* { dg-final { scan-assembler "adrp\[ \t\]*x0, _GLOBAL_OFFSET_TABLE_" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x0, \\\[x0, #:gotpage_lo15:bar\\\]" } } */
+/* { dg-final { scan-assembler "str\[ \t\]*x30, \\\[sp, #-16\\\]!" } } */
+/* { dg-final { scan-assembler "bl\[ \t\]*\.LIND1" } } */
+/* { dg-final { scan-assembler "wfe" } } */
+/* { dg-final { scan-assembler "b\[ \t\]*\.LIND0" } } */
+/* { dg-final { scan-assembler "adr\[ \t\]*x30, .LIND2" } } */
+/* { dg-final { scan-assembler "ret" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x30, \\\[sp\\\], #16" } } */
+/* { dg-final { scan-assembler "blr\[ \t\]*x0" } } */
+
+/* { dg-final { scan-assembler-not "__aarch64_indirect_thunk\n" } } */