summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Muellner <christoph.muellner@theobroma-systems.com>2018-03-02 17:09:19 +0100
committerChristoph Muellner <christoph.muellner@theobroma-systems.com>2018-05-17 16:17:01 +0200
commit6b78b2be7018174f49e11a15aa5401b87a72fbbb (patch)
tree83fe33e15e2f43067e2ad95295965f94508bb63c
parent0dbcf2c2b2e4e778d17fddc22b6a5c0b500fff50 (diff)
aarch64: Retpoline (Spectre-V2 mitigation) for aarch64.master-dbcf2c2b2e
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.). Signed-off-by: Christoph Muellner <christoph.muellner@theobroma-systems.com> Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
-rw-r--r--gcc/config/aarch64/aarch64-opts.h9
-rw-r--r--gcc/config/aarch64/aarch64-protos.h2
-rw-r--r--gcc/config/aarch64/aarch64.c307
-rw-r--r--gcc/config/aarch64/aarch64.h4
-rw-r--r--gcc/config/aarch64/aarch64.md10
-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.c25
-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
31 files changed, 964 insertions, 7 deletions
diff --git a/gcc/config/aarch64/aarch64-opts.h b/gcc/config/aarch64/aarch64-opts.h
index 7a5c6d7664f4..11c114f37a1c 100644
--- a/gcc/config/aarch64/aarch64-opts.h
+++ b/gcc/config/aarch64/aarch64-opts.h
@@ -91,4 +91,13 @@ enum aarch64_sve_vector_bits_enum {
SVE_2048 = 2048
};
+/* 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-protos.h b/gcc/config/aarch64/aarch64-protos.h
index cda2895d28e7..66fa456d97b8 100644
--- a/gcc/config/aarch64/aarch64-protos.h
+++ b/gcc/config/aarch64/aarch64-protos.h
@@ -390,6 +390,8 @@ char *aarch64_output_simd_mov_immediate (rtx, unsigned,
enum simd_immediate_check w = AARCH64_CHECK_MOV);
char *aarch64_output_sve_mov_immediate (rtx);
char *aarch64_output_ptrue (machine_mode, char);
+const char *aarch64_output_branch_register (rtx);
+const char *aarch64_output_branch_and_link_register (rtx);
bool aarch64_pad_reg_upward (machine_mode, const_tree, bool);
bool aarch64_regno_ok_for_base_p (int, bool);
bool aarch64_regno_ok_for_index_p (int, bool);
diff --git a/gcc/config/aarch64/aarch64.c b/gcc/config/aarch64/aarch64.c
index a2003fe52875..20bf8241b061 100644
--- a/gcc/config/aarch64/aarch64.c
+++ b/gcc/config/aarch64/aarch64.c
@@ -40,6 +40,7 @@
#include "regs.h"
#include "emit-rtl.h"
#include "recog.h"
+#include "cgraph.h"
#include "diagnostic.h"
#include "insn-attr.h"
#include "alias.h"
@@ -11067,6 +11068,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
@@ -11077,7 +11107,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)
@@ -11241,6 +11282,42 @@ aarch64_handle_attr_tune (const char *str)
return false;
}
+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;
+}
+
/* Parse an architecture extensions target attribute string specified in STR.
For example "+fp+nosimd". Show any errors if needed. Return TRUE
if successful. Update aarch64_isa_flags to reflect the ISA features
@@ -15018,6 +15095,215 @@ aarch64_output_ptrue (machine_mode mode, char suffix)
return buf;
}
+/* 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_output_branch_register (rtx call_op)
+{
+ 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_output_branch_and_link_register (rtx call_op)
+{
+ 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 ("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", &call_op);
+ }
+ return "";
+}
+
+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);
+ }
+}
+
/* Split operands into moves from op[1] + op[2] into op[0]. */
void
@@ -17404,6 +17690,16 @@ aarch64_select_early_remat_modes (sbitmap modes)
}
}
+/* Table of valid machine attributes. */
+
+static const struct attribute_spec aarch64_attribute_table[] =
+{
+ { "indirect_branch", 1, 1, true, false, false,
+ aarch64_handle_fndecl_attribute, NULL },
+ /* End element. */
+ { NULL, 0, 0, false, false, false, NULL, NULL }
+};
+
/* Target-specific selftests. */
#if CHECKING_P
@@ -17473,6 +17769,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
@@ -17485,6 +17784,12 @@ aarch64_run_selftests (void)
#undef TARGET_ASM_TRAMPOLINE_TEMPLATE
#define TARGET_ASM_TRAMPOLINE_TEMPLATE aarch64_asm_trampoline_template
+#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_BUILD_BUILTIN_VA_LIST
#define TARGET_BUILD_BUILTIN_VA_LIST aarch64_build_builtin_va_list
diff --git a/gcc/config/aarch64/aarch64.h b/gcc/config/aarch64/aarch64.h
index 976f9afae54c..2b54ee456854 100644
--- a/gcc/config/aarch64/aarch64.h
+++ b/gcc/config/aarch64/aarch64.h
@@ -682,6 +682,10 @@ typedef struct GTY (()) machine_function
struct aarch64_frame frame;
/* One entry for each hard register. */
bool reg_is_wrapped_separately[LAST_SAVED_REGNUM];
+
+ /* How to generate indirec branch. */
+ ENUM_BITFIELD(indirect_branch) indirect_branch_type : 3;
+
} machine_function;
#endif
diff --git a/gcc/config/aarch64/aarch64.md b/gcc/config/aarch64/aarch64.md
index 68ea71875c02..c7bb2c498a41 100644
--- a/gcc/config/aarch64/aarch64.md
+++ b/gcc/config/aarch64/aarch64.md
@@ -298,7 +298,7 @@
(define_insn "indirect_jump"
[(set (pc) (match_operand:DI 0 "register_operand" "r"))]
""
- "br\\t%0"
+ "* return aarch64_output_branch_register (operands[0]);"
[(set_attr "type" "branch")]
)
@@ -800,7 +800,7 @@
(clobber (reg:DI LR_REGNUM))]
""
"@
- blr\\t%0
+ * return aarch64_output_branch_and_link_register (operands[0]);
bl\\t%c0"
[(set_attr "type" "call, call")]
)
@@ -826,7 +826,7 @@
(clobber (reg:DI LR_REGNUM))]
""
"@
- blr\\t%1
+ * return aarch64_output_branch_and_link_register (operands[1]);
bl\\t%c1"
[(set_attr "type" "call, call")]
)
@@ -862,7 +862,7 @@
(return)]
"SIBLING_CALL_P (insn)"
"@
- br\\t%0
+ * return aarch64_output_branch_register (operands[0]);
b\\t%c0"
[(set_attr "type" "branch, branch")]
)
@@ -875,7 +875,7 @@
(return)]
"SIBLING_CALL_P (insn)"
"@
- br\\t%1
+ * return aarch64_output_branch_register (operands[1]);
b\\t%c1"
[(set_attr "type" "branch, branch")]
)
diff --git a/gcc/config/aarch64/aarch64.opt b/gcc/config/aarch64/aarch64.opt
index 52eaf8c6f408..a4fbd231f9c7 100644
--- a/gcc/config/aarch64/aarch64.opt
+++ b/gcc/config/aarch64/aarch64.opt
@@ -214,3 +214,23 @@ Target RejectNegative Joined Enum(sve_vector_bits) Var(aarch64_sve_vector_bits)
mverbose-cost-dump
Common Undocumented Var(flag_aarch64_verbose_cost)
Enables verbose cost model dumping in the debug dump files.
+
+mindirect-branch=
+Target Report RejectNegative Joined Enum(indirect_branch) Var(aarch64_indirect_branch) Init(indirect_branch_keep)
+Convert indirect branch-and-link and branch to branch-and-link and return thunks.
+
+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)
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ca3772bbebfc..a4ec8fbf0002 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -604,7 +604,8 @@ Objective-C and Objective-C++ Dialects}.
-mpc-relative-literal-loads @gol
-msign-return-address=@var{scope} @gol
-march=@var{name} -mcpu=@var{name} -mtune=@var{name} @gol
--moverride=@var{string} -mverbose-cost-dump}
+-moverride=@var{string} -mverbose-cost-dump
+-mindirect-branch=@var{choice}}
@emph{Adapteva Epiphany Options}
@gccoptlist{-mhalf-reg-file -mprefer-short-insn-regs @gol
@@ -14784,6 +14785,23 @@ and 2048. @samp{scalable} is the default.
At present, @samp{-msve-vector-bits=128} produces the same output
as @samp{-msve-vector-bits=scalable}.
+@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..c5bd82a56d90
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:dispatch\\\]" } } */
+/* { 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..0107bbd9559d
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:dispatch" } } */
+/* { 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..d9b5cfed19b2
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:dispatch\\\]" } } */
+/* { 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..b89f333457c7
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:dispatch" } } */
+/* { 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..eb7ccda2ae66
--- /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\]*x0, \\\[x0, #:gotpage_lo15:bar\\\]" } } */
+/* { dg-final { scan-assembler "b\[ \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-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..2485e9248910
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:dispatch\\\]" } } */
+/* { 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..8e9425294b6e
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:dispatch" } } */
+/* { 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..47005c96d098
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:dispatch\\\]" } } */
+/* { 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..4ad462f7efa2
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:dispatch" } } */
+/* { 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..178ce26ff25f
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:dispatch\\\]" } } */
+/* { 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..75964a058391
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:dispatch" } } */
+/* { 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..da4936d7b99c
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:dispatch\\\]" } } */
+/* { 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..b7a6a693ebea
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:dispatch" } } */
+/* { 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..d4143b173612
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:dispatch\\\]" } } */
+/* { 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..f488a719f2a1
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:dispatch" } } */
+/* { 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..d20a668121a0
--- /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\]*x0, \\\[x0, #:gotpage_lo15:bar\\\]" } } */
+/* { dg-final { scan-assembler "b\[ \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-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..4c94efc8121f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-1.c
@@ -0,0 +1,25 @@
+/* { 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, dispatch" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:dispatch\\\]" } } */
+/* { 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-2.c b/gcc/testsuite/gcc.target/aarch64/indirect-thunk-inline-2.c
new file mode 100644
index 000000000000..3c5c263b5e6f
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:dispatch" } } */
+/* { 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..cd09d78b082b
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "ldr\[ \t\]*x1, \\\[x1, #:lo12:dispatch\\\]" } } */
+/* { 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..45b33ac9c02c
--- /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, dispatch" } } */
+/* { dg-final { scan-assembler "add\[ \t\]*x1, x1, :lo12:dispatch" } } */
+/* { 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..28b4c87b80af
--- /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\]*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 "br\[ \t\]*x0" } } */
+
+/* { 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" } } */