+2018-02-08 Andreas Krebbel <krebbel@linux.vnet.ibm.com>
+
+ * config/s390/s390-opts.h (enum indirect_branch): Define.
+ * config/s390/s390-protos.h (s390_return_addr_from_memory)
+ (s390_indirect_branch_via_thunk)
+ (s390_indirect_branch_via_inline_thunk): Add function prototypes.
+ (enum s390_indirect_branch_type): Define.
+ * config/s390/s390.c (struct s390_frame_layout, struct
+ machine_function): Remove.
+ (indirect_branch_prez10thunk_mask, indirect_branch_z10thunk_mask)
+ (indirect_branch_table_label_no, indirect_branch_table_name):
+ Define variables.
+ (INDIRECT_BRANCH_NUM_OPTIONS): Define macro.
+ (enum s390_indirect_branch_option): Define.
+ (s390_return_addr_from_memory): New function.
+ (s390_handle_string_attribute): New function.
+ (s390_attribute_table): Add new attribute handler.
+ (s390_execute_label): Handle UNSPEC_EXECUTE_JUMP patterns.
+ (s390_indirect_branch_via_thunk): New function.
+ (s390_indirect_branch_via_inline_thunk): New function.
+ (s390_function_ok_for_sibcall): When jumping via thunk disallow
+ sibling call optimization for non z10 compiles.
+ (s390_emit_call): Force indirect branch target to be a single
+ register. Add r1 clobber for non-z10 compiles.
+ (s390_emit_epilogue): Emit return jump via return_use expander.
+ (s390_reorg): Handle JUMP_INSNs as execute targets.
+ (s390_option_override_internal): Perform validity checks for the
+ new command line options.
+ (s390_indirect_branch_attrvalue): New function.
+ (s390_indirect_branch_settings): New function.
+ (s390_set_current_function): Invoke s390_indirect_branch_settings.
+ (s390_output_indirect_thunk_function): New function.
+ (s390_code_end): Implement target hook.
+ (s390_case_values_threshold): Implement target hook.
+ (TARGET_ASM_CODE_END, TARGET_CASE_VALUES_THRESHOLD): Define target
+ macros.
+ * config/s390/s390.h (struct s390_frame_layout)
+ (struct machine_function): Move here from s390.c.
+ (TARGET_INDIRECT_BRANCH_NOBP_RET)
+ (TARGET_INDIRECT_BRANCH_NOBP_JUMP)
+ (TARGET_INDIRECT_BRANCH_NOBP_JUMP_THUNK)
+ (TARGET_INDIRECT_BRANCH_NOBP_JUMP_INLINE_THUNK)
+ (TARGET_INDIRECT_BRANCH_NOBP_CALL)
+ (TARGET_DEFAULT_INDIRECT_BRANCH_TABLE)
+ (TARGET_INDIRECT_BRANCH_THUNK_NAME_EXRL)
+ (TARGET_INDIRECT_BRANCH_THUNK_NAME_EX)
+ (TARGET_INDIRECT_BRANCH_TABLE): Define macros.
+ * config/s390/s390.md (UNSPEC_EXECUTE_JUMP)
+ (INDIRECT_BRANCH_THUNK_REGNUM): Define constants.
+ (mnemonic attribute): Add values which aren't recognized
+ automatically.
+ ("*cjump_long", "*icjump_long", "*basr", "*basr_r"): Disable
+ pattern for branch conversion. Fix mnemonic attribute.
+ ("*c<code>", "*sibcall_br", "*sibcall_value_br", "*return"): Emit
+ indirect branch via thunk if requested.
+ ("indirect_jump", "<code>"): Expand patterns for branch conversion.
+ ("*indirect_jump"): Disable for branch conversion using out of
+ line thunks.
+ ("indirect_jump_via_thunk<mode>_z10")
+ ("indirect_jump_via_thunk<mode>")
+ ("indirect_jump_via_inlinethunk<mode>_z10")
+ ("indirect_jump_via_inlinethunk<mode>", "*casesi_jump")
+ ("casesi_jump_via_thunk<mode>_z10", "casesi_jump_via_thunk<mode>")
+ ("casesi_jump_via_inlinethunk<mode>_z10")
+ ("casesi_jump_via_inlinethunk<mode>", "*basr_via_thunk<mode>_z10")
+ ("*basr_via_thunk<mode>", "*basr_r_via_thunk_z10")
+ ("*basr_r_via_thunk", "return<mode>_prez10"): New pattern.
+ ("*indirect2_jump"): Disable for branch conversion.
+ ("casesi_jump"): Turn into expander and expand patterns for branch
+ conversion.
+ ("return_use"): New expander.
+ ("*return"): Emit return via thunk and rename it to ...
+ ("*return<mode>"): ... this one.
+ * config/s390/s390.opt: Add new options and and enum for the
+ option values.
+
2018-02-08 Richard Sandiford <richard.sandiford@linaro.org>
* lra-constraints.c (match_reload): Unconditionally use
PROCESSOR_max
};
+
+/* Values for -mindirect-branch and -mfunction-return options. */
+enum indirect_branch {
+ indirect_branch_unset = 0,
+ indirect_branch_keep,
+ indirect_branch_thunk,
+ indirect_branch_thunk_inline,
+ indirect_branch_thunk_extern
+};
#endif
extern bool s390_hard_regno_rename_ok (unsigned int, unsigned int);
extern int s390_class_max_nregs (enum reg_class, machine_mode);
extern bool s390_function_arg_vector (machine_mode, const_tree);
+extern bool s390_return_addr_from_memory(void);
#if S390_USE_TARGET_ATTRIBUTE
extern tree s390_valid_target_attribute_tree (tree args,
struct gcc_options *opts,
extern bool s390_extzv_shift_ok (int, int, unsigned HOST_WIDE_INT);
extern void s390_asm_output_function_label (FILE *, const char *, tree);
+enum s390_indirect_branch_type
+ {
+ s390_indirect_branch_type_jump = 0,
+ s390_indirect_branch_type_call,
+ s390_indirect_branch_type_return
+ };
+extern void s390_indirect_branch_via_thunk (unsigned int regno,
+ unsigned int return_addr_regno,
+ rtx comparison_operator,
+ enum s390_indirect_branch_type type);
+extern void s390_indirect_branch_via_inline_thunk (rtx execute_target);
#endif /* RTX_CODE */
/* s390-c.c routines */
bool literal_pool;
};
-/* The following structure is embedded in the machine
- specific part of struct function. */
-
-struct GTY (()) s390_frame_layout
-{
- /* Offset within stack frame. */
- HOST_WIDE_INT gprs_offset;
- HOST_WIDE_INT f0_offset;
- HOST_WIDE_INT f4_offset;
- HOST_WIDE_INT f8_offset;
- HOST_WIDE_INT backchain_offset;
-
- /* Number of first and last gpr where slots in the register
- save area are reserved for. */
- int first_save_gpr_slot;
- int last_save_gpr_slot;
-
- /* Location (FP register number) where GPRs (r0-r15) should
- be saved to.
- 0 - does not need to be saved at all
- -1 - stack slot */
-#define SAVE_SLOT_NONE 0
-#define SAVE_SLOT_STACK -1
- signed char gpr_save_slots[16];
-
- /* Number of first and last gpr to be saved, restored. */
- int first_save_gpr;
- int first_restore_gpr;
- int last_save_gpr;
- int last_restore_gpr;
-
- /* Bits standing for floating point registers. Set, if the
- respective register has to be saved. Starting with reg 16 (f0)
- at the rightmost bit.
- Bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
- fpr 15 13 11 9 14 12 10 8 7 5 3 1 6 4 2 0
- reg 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 */
- unsigned int fpr_bitmap;
-
- /* Number of floating point registers f8-f15 which must be saved. */
- int high_fprs;
-
- /* Set if return address needs to be saved.
- This flag is set by s390_return_addr_rtx if it could not use
- the initial value of r14 and therefore depends on r14 saved
- to the stack. */
- bool save_return_addr_p;
-
- /* Size of stack frame. */
- HOST_WIDE_INT frame_size;
-};
-
-/* Define the structure for the machine field in struct function. */
-
-struct GTY(()) machine_function
-{
- struct s390_frame_layout frame_layout;
-
- /* Literal pool base register. */
- rtx base_reg;
-
- /* True if we may need to perform branch splitting. */
- bool split_branches_pending_p;
-
- bool has_landing_pad_p;
-
- /* True if the current function may contain a tbegin clobbering
- FPRs. */
- bool tbegin_p;
-
- /* For -fsplit-stack support: A stack local which holds a pointer to
- the stack arguments for a function with a variable number of
- arguments. This is set at the start of the function and is used
- to initialize the overflow_arg_area field of the va_list
- structure. */
- rtx split_stack_varargs_pointer;
-};
-
/* Few accessor macros for struct cfun->machine->s390_frame_layout. */
#define cfun_frame_layout (cfun->machine->frame_layout)
bytes on a z10 (or higher) CPU. */
#define PREDICT_DISTANCE (TARGET_Z10 ? 384 : 2048)
+/* Masks per jump target register indicating which thunk need to be
+ generated. */
+static GTY(()) int indirect_branch_prez10thunk_mask = 0;
+static GTY(()) int indirect_branch_z10thunk_mask = 0;
+
+#define INDIRECT_BRANCH_NUM_OPTIONS 4
+
+enum s390_indirect_branch_option
+ {
+ s390_opt_indirect_branch_jump = 0,
+ s390_opt_indirect_branch_call,
+ s390_opt_function_return_reg,
+ s390_opt_function_return_mem
+ };
+
+static GTY(()) int indirect_branch_table_label_no[INDIRECT_BRANCH_NUM_OPTIONS] = { 0 };
+const char *indirect_branch_table_label[INDIRECT_BRANCH_NUM_OPTIONS] = \
+ { "LJUMP", "LCALL", "LRETREG", "LRETMEM" };
+const char *indirect_branch_table_name[INDIRECT_BRANCH_NUM_OPTIONS] = \
+ { ".s390_indirect_jump", ".s390_indirect_call",
+ ".s390_return_reg", ".s390_return_mem" };
+
+bool
+s390_return_addr_from_memory ()
+{
+ return cfun_gpr_save_slot(RETURN_REGNUM) == SAVE_SLOT_STACK;
+}
/* Indicate which ABI has been used for passing vector args.
0 - no vector type arguments have been passed where the ABI is relevant
return NULL_TREE;
}
+/* Check syntax of function decl attributes having a string type value. */
+
+static tree
+s390_handle_string_attribute (tree *node, tree name ATTRIBUTE_UNUSED,
+ tree args ATTRIBUTE_UNUSED,
+ int flags ATTRIBUTE_UNUSED,
+ bool *no_add_attrs)
+{
+ tree cst;
+
+ if (TREE_CODE (*node) != FUNCTION_DECL)
+ {
+ warning (OPT_Wattributes, "%qE attribute only applies to functions",
+ name);
+ *no_add_attrs = true;
+ }
+
+ 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;
+ }
+
+ if (is_attribute_p ("indirect_branch", name)
+ || is_attribute_p ("indirect_branch_call", name)
+ || is_attribute_p ("function_return", name)
+ || is_attribute_p ("function_return_reg", name)
+ || is_attribute_p ("function_return_mem", name))
+ {
+ if (strcmp (TREE_STRING_POINTER (cst), "keep") != 0
+ && strcmp (TREE_STRING_POINTER (cst), "thunk") != 0
+ && strcmp (TREE_STRING_POINTER (cst), "thunk-extern") != 0)
+ {
+ warning (OPT_Wattributes,
+ "argument to %qE attribute is not "
+ "(keep|thunk|thunk-extern)", name);
+ *no_add_attrs = true;
+ }
+ }
+
+ if (is_attribute_p ("indirect_branch_jump", name)
+ && 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;
+}
+
static const struct attribute_spec s390_attribute_table[] = {
{ "hotpatch", 2, 2, true, false, false, false,
s390_handle_hotpatch_attribute, NULL },
{ "s390_vector_bool", 0, 0, false, true, false, true,
s390_handle_vectorbool_attribute, NULL },
+ { "indirect_branch", 1, 1, true, false, false, false,
+ s390_handle_string_attribute, NULL },
+ { "indirect_branch_jump", 1, 1, true, false, false, false,
+ s390_handle_string_attribute, NULL },
+ { "indirect_branch_call", 1, 1, true, false, false, false,
+ s390_handle_string_attribute, NULL },
+ { "function_return", 1, 1, true, false, false, false,
+ s390_handle_string_attribute, NULL },
+ { "function_return_reg", 1, 1, true, false, false, false,
+ s390_handle_string_attribute, NULL },
+ { "function_return_mem", 1, 1, true, false, false, false,
+ s390_handle_string_attribute, NULL },
+
/* End element. */
{ NULL, 0, 0, false, false, false, false, NULL, NULL }
};
static rtx
s390_execute_label (rtx insn)
{
- if (NONJUMP_INSN_P (insn)
+ if (INSN_P (insn)
&& GET_CODE (PATTERN (insn)) == PARALLEL
&& GET_CODE (XVECEXP (PATTERN (insn), 0, 0)) == UNSPEC
- && XINT (XVECEXP (PATTERN (insn), 0, 0), 1) == UNSPEC_EXECUTE)
- return XVECEXP (XVECEXP (PATTERN (insn), 0, 0), 0, 2);
+ && (XINT (XVECEXP (PATTERN (insn), 0, 0), 1) == UNSPEC_EXECUTE
+ || XINT (XVECEXP (PATTERN (insn), 0, 0), 1) == UNSPEC_EXECUTE_JUMP))
+ {
+ if (XINT (XVECEXP (PATTERN (insn), 0, 0), 1) == UNSPEC_EXECUTE)
+ return XVECEXP (XVECEXP (PATTERN (insn), 0, 0), 0, 2);
+ else
+ {
+ gcc_assert (JUMP_P (insn));
+ /* For jump insns as execute target:
+ - There is one operand less in the parallel (the
+ modification register of the execute is always 0).
+ - The execute target label is wrapped into an
+ if_then_else in order to hide it from jump analysis. */
+ return XEXP (XVECEXP (XVECEXP (PATTERN (insn), 0, 0), 0, 0), 0);
+ }
+ }
return NULL_RTX;
}
rtx frame_pointer, return_reg, cfa_restores = NULL_RTX;
int area_bottom, area_top, offset = 0;
int next_offset;
- rtvec p;
int i;
if (TARGET_TPF_PROFILING)
&& s390_tune <= PROCESSOR_2097_Z10)
{
int return_regnum = find_unused_clobbered_reg();
- if (!return_regnum)
- return_regnum = 4;
+ if (!return_regnum
+ || (TARGET_INDIRECT_BRANCH_NOBP_RET_OPTION
+ && !TARGET_CPU_Z10
+ && return_regnum == INDIRECT_BRANCH_THUNK_REGNUM))
+ {
+ gcc_assert (INDIRECT_BRANCH_THUNK_REGNUM != 4);
+ return_regnum = 4;
+ }
return_reg = gen_rtx_REG (Pmode, return_regnum);
addr = plus_constant (Pmode, frame_pointer,
s390_restore_gprs_from_fprs ();
if (! sibcall)
- {
-
- /* Return to caller. */
-
- p = rtvec_alloc (2);
-
- RTVEC_ELT (p, 0) = ret_rtx;
- RTVEC_ELT (p, 1) = gen_rtx_USE (VOIDmode, return_reg);
- emit_jump_insn (gen_rtx_PARALLEL (VOIDmode, p));
- }
+ emit_jump_insn (gen_return_use (return_reg));
}
/* Implement TARGET_SET_UP_BY_PROLOGUE. */
final_end_function ();
}
+/* Output either an indirect jump or a an indirect call
+ (RETURN_ADDR_REGNO != INVALID_REGNUM) with target register REGNO
+ using a branch trampoline disabling branch target prediction. */
+
+void
+s390_indirect_branch_via_thunk (unsigned int regno,
+ unsigned int return_addr_regno,
+ rtx comparison_operator,
+ enum s390_indirect_branch_type type)
+{
+ enum s390_indirect_branch_option option;
+
+ if (type == s390_indirect_branch_type_return)
+ {
+ if (s390_return_addr_from_memory ())
+ option = s390_opt_function_return_mem;
+ else
+ option = s390_opt_function_return_reg;
+ }
+ else if (type == s390_indirect_branch_type_jump)
+ option = s390_opt_indirect_branch_jump;
+ else if (type == s390_indirect_branch_type_call)
+ option = s390_opt_indirect_branch_call;
+ else
+ gcc_unreachable ();
+
+ if (TARGET_INDIRECT_BRANCH_TABLE)
+ {
+ char label[32];
+
+ ASM_GENERATE_INTERNAL_LABEL (label,
+ indirect_branch_table_label[option],
+ indirect_branch_table_label_no[option]++);
+ ASM_OUTPUT_LABEL (asm_out_file, label);
+ }
+
+ if (return_addr_regno != INVALID_REGNUM)
+ {
+ gcc_assert (comparison_operator == NULL_RTX);
+ fprintf (asm_out_file, " \tbrasl\t%%r%d,", return_addr_regno);
+ }
+ else
+ {
+ fputs (" \tjg", asm_out_file);
+ if (comparison_operator != NULL_RTX)
+ print_operand (asm_out_file, comparison_operator, 'C');
+
+ fputs ("\t", asm_out_file);
+ }
+
+ if (TARGET_CPU_Z10)
+ fprintf (asm_out_file,
+ TARGET_INDIRECT_BRANCH_THUNK_NAME_EXRL "\n",
+ regno);
+ else
+ fprintf (asm_out_file,
+ TARGET_INDIRECT_BRANCH_THUNK_NAME_EX "\n",
+ INDIRECT_BRANCH_THUNK_REGNUM, regno);
+
+ if ((option == s390_opt_indirect_branch_jump
+ && cfun->machine->indirect_branch_jump == indirect_branch_thunk)
+ || (option == s390_opt_indirect_branch_call
+ && cfun->machine->indirect_branch_call == indirect_branch_thunk)
+ || (option == s390_opt_function_return_reg
+ && cfun->machine->function_return_reg == indirect_branch_thunk)
+ || (option == s390_opt_function_return_mem
+ && cfun->machine->function_return_mem == indirect_branch_thunk))
+ {
+ if (TARGET_CPU_Z10)
+ indirect_branch_z10thunk_mask |= (1 << regno);
+ else
+ indirect_branch_prez10thunk_mask |= (1 << regno);
+ }
+}
+
+/* Output an inline thunk for indirect jumps. EXECUTE_TARGET can
+ either be an address register or a label pointing to the location
+ of the jump instruction. */
+
+void
+s390_indirect_branch_via_inline_thunk (rtx execute_target)
+{
+ if (TARGET_INDIRECT_BRANCH_TABLE)
+ {
+ char label[32];
+
+ ASM_GENERATE_INTERNAL_LABEL (label,
+ indirect_branch_table_label[s390_opt_indirect_branch_jump],
+ indirect_branch_table_label_no[s390_opt_indirect_branch_jump]++);
+ ASM_OUTPUT_LABEL (asm_out_file, label);
+ }
+
+ if (!TARGET_ZARCH)
+ fputs ("\t.machinemode zarch\n", asm_out_file);
+
+ if (REG_P (execute_target))
+ fprintf (asm_out_file, "\tex\t%%r0,0(%%r%d)\n", REGNO (execute_target));
+ else
+ output_asm_insn ("\texrl\t%%r0,%0", &execute_target);
+
+ if (!TARGET_ZARCH)
+ fputs ("\t.machinemode esa\n", asm_out_file);
+
+ fputs ("0:\tj\t0b\n", asm_out_file);
+}
+
static bool
s390_valid_pointer_mode (scalar_int_mode mode)
{
if (!TARGET_64BIT && flag_pic && decl && !targetm.binds_local_p (decl))
return false;
+ /* The thunks for indirect branches require r1 if no exrl is
+ available. r1 might not be available when doing a sibling
+ call. */
+ if (TARGET_INDIRECT_BRANCH_NOBP_CALL
+ && !TARGET_CPU_Z10
+ && !decl)
+ return false;
+
/* Register 6 on s390 is available as an argument register but unfortunately
"caller saved". This makes functions needing this register for arguments
not suitable for sibcalls. */
{
bool plt_call = false;
rtx_insn *insn;
- rtx call;
- rtx clobber;
- rtvec vec;
+ rtx vec[4] = { NULL_RTX };
+ int elts = 0;
+ rtx *call = &vec[0];
+ rtx *clobber_ret_reg = &vec[1];
+ rtx *use = &vec[2];
+ rtx *clobber_thunk_reg = &vec[3];
+ int i;
/* Direct function calls need special treatment. */
if (GET_CODE (addr_location) == SYMBOL_REF)
addr_location = gen_rtx_REG (Pmode, SIBCALL_REGNUM);
}
+ if (TARGET_INDIRECT_BRANCH_NOBP_CALL
+ && GET_CODE (addr_location) != SYMBOL_REF
+ && !plt_call)
+ {
+ /* Indirect branch thunks require the target to be a single GPR. */
+ addr_location = force_reg (Pmode, addr_location);
+
+ /* Without exrl the indirect branch thunks need an additional
+ register for larl;ex */
+ if (!TARGET_CPU_Z10)
+ {
+ *clobber_thunk_reg = gen_rtx_REG (Pmode, INDIRECT_BRANCH_THUNK_REGNUM);
+ *clobber_thunk_reg = gen_rtx_CLOBBER (VOIDmode, *clobber_thunk_reg);
+ }
+ }
+
addr_location = gen_rtx_MEM (QImode, addr_location);
- call = gen_rtx_CALL (VOIDmode, addr_location, const0_rtx);
+ *call = gen_rtx_CALL (VOIDmode, addr_location, const0_rtx);
if (result_reg != NULL_RTX)
- call = gen_rtx_SET (result_reg, call);
+ *call = gen_rtx_SET (result_reg, *call);
if (retaddr_reg != NULL_RTX)
{
- clobber = gen_rtx_CLOBBER (VOIDmode, retaddr_reg);
+ *clobber_ret_reg = gen_rtx_CLOBBER (VOIDmode, retaddr_reg);
if (tls_call != NULL_RTX)
- vec = gen_rtvec (3, call, clobber,
- gen_rtx_USE (VOIDmode, tls_call));
- else
- vec = gen_rtvec (2, call, clobber);
+ *use = gen_rtx_USE (VOIDmode, tls_call);
+ }
+
- call = gen_rtx_PARALLEL (VOIDmode, vec);
+ for (i = 0; i < 4; i++)
+ if (vec[i] != NULL_RTX)
+ elts++;
+
+ if (elts > 1)
+ {
+ rtvec v;
+ int e = 0;
+
+ v = rtvec_alloc (elts);
+ for (i = 0; i < 4; i++)
+ if (vec[i] != NULL_RTX)
+ {
+ RTVEC_ELT (v, e) = vec[i];
+ e++;
+ }
+
+ *call = gen_rtx_PARALLEL (VOIDmode, v);
}
- insn = emit_call_insn (call);
+ insn = emit_call_insn (*call);
/* 31-bit PLT stubs and tls calls use the GOT register implicitly. */
if ((!TARGET_64BIT && plt_call) || tls_call != NULL_RTX)
target = emit_label (XEXP (label, 0));
INSN_ADDRESSES_NEW (target, -1);
- target = emit_insn (s390_execute_target (insn));
+ if (JUMP_P (insn))
+ {
+ target = emit_jump_insn (s390_execute_target (insn));
+ /* This is important in order to keep a table jump
+ pointing at the jump table label. Only this makes it
+ being recognized as table jump. */
+ JUMP_LABEL (target) = JUMP_LABEL (insn);
+ }
+ else
+ target = emit_insn (s390_execute_target (insn));
INSN_ADDRESSES_NEW (target, -1);
}
}
if (TARGET_64BIT && !TARGET_ZARCH_P (opts->x_target_flags))
error ("64-bit ABI not supported in ESA/390 mode");
+ if (opts->x_s390_indirect_branch == indirect_branch_thunk_inline
+ || opts->x_s390_indirect_branch_call == indirect_branch_thunk_inline
+ || opts->x_s390_function_return == indirect_branch_thunk_inline
+ || opts->x_s390_function_return_reg == indirect_branch_thunk_inline
+ || opts->x_s390_function_return_mem == indirect_branch_thunk_inline)
+ error ("thunk-inline is only supported with -mindirect-branch-jump");
+
+ if (opts->x_s390_indirect_branch != indirect_branch_keep)
+ {
+ if (!opts_set->x_s390_indirect_branch_call)
+ opts->x_s390_indirect_branch_call = opts->x_s390_indirect_branch;
+
+ if (!opts_set->x_s390_indirect_branch_jump)
+ opts->x_s390_indirect_branch_jump = opts->x_s390_indirect_branch;
+ }
+
+ if (opts->x_s390_function_return != indirect_branch_keep)
+ {
+ if (!opts_set->x_s390_function_return_reg)
+ opts->x_s390_function_return_reg = opts->x_s390_function_return;
+
+ if (!opts_set->x_s390_function_return_mem)
+ opts->x_s390_function_return_mem = opts->x_s390_function_return;
+ }
+
+ if (!TARGET_CPU_ZARCH)
+ {
+ if (opts->x_s390_indirect_branch_call != indirect_branch_keep
+ || opts->x_s390_indirect_branch_jump != indirect_branch_keep)
+ error ("-mindirect-branch* options require -march=z900 or higher");
+ if (opts->x_s390_function_return_reg != indirect_branch_keep
+ || opts->x_s390_function_return_mem != indirect_branch_keep)
+ error ("-mfunction-return* options require -march=z900 or higher");
+ }
+
+
/* Enable hardware transactions if available and not explicitly
disabled by user. E.g. with -m31 -march=zEC12 -mzarch */
if (!TARGET_OPT_HTM_P (opts_set->x_target_flags))
return ret;
}
+/* Set VAL to correct enum value according to the indirect-branch or
+ function-return attribute in ATTR. */
+
+static inline void
+s390_indirect_branch_attrvalue (tree attr, enum indirect_branch *val)
+{
+ const char *str = TREE_STRING_POINTER (TREE_VALUE (TREE_VALUE (attr)));
+ if (strcmp (str, "keep") == 0)
+ *val = indirect_branch_keep;
+ else if (strcmp (str, "thunk") == 0)
+ *val = indirect_branch_thunk;
+ else if (strcmp (str, "thunk-inline") == 0)
+ *val = indirect_branch_thunk_inline;
+ else if (strcmp (str, "thunk-extern") == 0)
+ *val = indirect_branch_thunk_extern;
+}
+
+/* Memorize the setting for -mindirect-branch* and -mfunction-return*
+ from either the cmdline or the function attributes in
+ cfun->machine. */
+
+static void
+s390_indirect_branch_settings (tree fndecl)
+{
+ tree attr;
+
+ if (!fndecl)
+ return;
+
+ /* Initialize with the cmdline options and let the attributes
+ override it. */
+ cfun->machine->indirect_branch_jump = s390_indirect_branch_jump;
+ cfun->machine->indirect_branch_call = s390_indirect_branch_call;
+
+ cfun->machine->function_return_reg = s390_function_return_reg;
+ cfun->machine->function_return_mem = s390_function_return_mem;
+
+ if ((attr = lookup_attribute ("indirect_branch",
+ DECL_ATTRIBUTES (fndecl))))
+ {
+ s390_indirect_branch_attrvalue (attr,
+ &cfun->machine->indirect_branch_jump);
+ s390_indirect_branch_attrvalue (attr,
+ &cfun->machine->indirect_branch_call);
+ }
+
+ if ((attr = lookup_attribute ("indirect_branch_jump",
+ DECL_ATTRIBUTES (fndecl))))
+ s390_indirect_branch_attrvalue (attr, &cfun->machine->indirect_branch_jump);
+
+ if ((attr = lookup_attribute ("indirect_branch_call",
+ DECL_ATTRIBUTES (fndecl))))
+ s390_indirect_branch_attrvalue (attr, &cfun->machine->indirect_branch_call);
+
+ if ((attr = lookup_attribute ("function_return",
+ DECL_ATTRIBUTES (fndecl))))
+ {
+ s390_indirect_branch_attrvalue (attr,
+ &cfun->machine->function_return_reg);
+ s390_indirect_branch_attrvalue (attr,
+ &cfun->machine->function_return_mem);
+ }
+
+ if ((attr = lookup_attribute ("function_return_reg",
+ DECL_ATTRIBUTES (fndecl))))
+ s390_indirect_branch_attrvalue (attr, &cfun->machine->function_return_reg);
+
+ if ((attr = lookup_attribute ("function_return_mem",
+ DECL_ATTRIBUTES (fndecl))))
+ s390_indirect_branch_attrvalue (attr, &cfun->machine->function_return_mem);
+}
+
/* Restore targets globals from NEW_TREE and invalidate s390_previous_fndecl
cache. */
if (old_tree != new_tree)
s390_activate_target_options (new_tree);
s390_previous_fndecl = fndecl;
+
+ s390_indirect_branch_settings (fndecl);
}
#endif
return TARGET_64BIT ? HOST_WIDE_INT_1U << 52 : HOST_WIDE_INT_UC (0x20000000);
}
+#ifdef HAVE_GAS_HIDDEN
+# define USE_HIDDEN_LINKONCE 1
+#else
+# define USE_HIDDEN_LINKONCE 0
+#endif
+
+/* Output an indirect branch trampoline for target register REGNO. */
+
+static void
+s390_output_indirect_thunk_function (unsigned int regno, bool z10_p)
+{
+ tree decl;
+ char thunk_label[32];
+ int i;
+
+ if (z10_p)
+ sprintf (thunk_label, TARGET_INDIRECT_BRANCH_THUNK_NAME_EXRL, regno);
+ else
+ sprintf (thunk_label, TARGET_INDIRECT_BRANCH_THUNK_NAME_EX,
+ INDIRECT_BRANCH_THUNK_REGNUM, regno);
+
+ decl = build_decl (BUILTINS_LOCATION, FUNCTION_DECL,
+ get_identifier (thunk_label),
+ 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;
+
+ if (USE_HIDDEN_LINKONCE)
+ {
+ 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, thunk_label);
+ fputs ("\t.hidden\t", asm_out_file);
+ assemble_name (asm_out_file, thunk_label);
+ putc ('\n', asm_out_file);
+ ASM_DECLARE_FUNCTION_NAME (asm_out_file, thunk_label, decl);
+ }
+ else
+ {
+ switch_to_section (text_section);
+ ASM_OUTPUT_LABEL (asm_out_file, thunk_label);
+ }
+
+ DECL_INITIAL (decl) = make_node (BLOCK);
+ current_function_decl = decl;
+ allocate_struct_function (decl, false);
+ init_function_start (decl);
+ cfun->is_thunk = true;
+ first_function_block_is_cold = false;
+ final_start_function (emit_barrier (), asm_out_file, 1);
+
+ /* This makes CFI at least usable for indirect jumps.
+
+ Stopping in the thunk: backtrace will point to the thunk target
+ is if it was interrupted by a signal. For a call this means that
+ the call chain will be: caller->callee->thunk */
+ if (flag_asynchronous_unwind_tables)
+ {
+ fputs ("\t.cfi_signal_frame\n", asm_out_file);
+ fprintf (asm_out_file, "\t.cfi_return_column %d\n", regno);
+ for (i = 0; i < FPR15_REGNUM; i++)
+ fprintf (asm_out_file, "\t.cfi_same_value %s\n", reg_names[i]);
+ }
+
+ if (z10_p)
+ {
+ /* exrl 0,1f */
+
+ /* We generate a thunk for z10 compiled code although z10 is
+ currently not enabled. Tell the assembler to accept the
+ instruction. */
+ if (!TARGET_CPU_Z10)
+ {
+ fputs ("\t.machine push\n", asm_out_file);
+ fputs ("\t.machine z10\n", asm_out_file);
+ }
+ /* We use exrl even if -mzarch hasn't been specified on the
+ command line so we have to tell the assembler to accept
+ it. */
+ if (!TARGET_ZARCH)
+ fputs ("\t.machinemode zarch\n", asm_out_file);
+
+ fputs ("\texrl\t0,1f\n", asm_out_file);
+
+ if (!TARGET_ZARCH)
+ fputs ("\t.machinemode esa\n", asm_out_file);
+
+ if (!TARGET_CPU_Z10)
+ fputs ("\t.machine pop\n", asm_out_file);
+ }
+ else if (TARGET_CPU_ZARCH)
+ {
+ /* larl %r1,1f */
+ fprintf (asm_out_file, "\tlarl\t%%r%d,1f\n",
+ INDIRECT_BRANCH_THUNK_REGNUM);
+
+ /* ex 0,0(%r1) */
+ fprintf (asm_out_file, "\tex\t0,0(%%r%d)\n",
+ INDIRECT_BRANCH_THUNK_REGNUM);
+ }
+ else
+ gcc_unreachable ();
+
+ /* 0: j 0b */
+ fputs ("0:\tj\t0b\n", asm_out_file);
+
+ /* 1: br <regno> */
+ fprintf (asm_out_file, "1:\tbr\t%%r%d\n", regno);
+
+ final_end_function ();
+ init_insn_lengths ();
+ free_after_compilation (cfun);
+ set_cfun (NULL);
+ current_function_decl = NULL;
+}
+
+/* Implement the asm.code_end target hook. */
+
+static void
+s390_code_end (void)
+{
+ int i;
+
+ for (i = 1; i < 16; i++)
+ {
+ if (indirect_branch_z10thunk_mask & (1 << i))
+ s390_output_indirect_thunk_function (i, true);
+
+ if (indirect_branch_prez10thunk_mask & (1 << i))
+ s390_output_indirect_thunk_function (i, false);
+ }
+
+ if (TARGET_INDIRECT_BRANCH_TABLE)
+ {
+ int o;
+ int i;
+
+ for (o = 0; o < INDIRECT_BRANCH_NUM_OPTIONS; o++)
+ {
+ if (indirect_branch_table_label_no[o] == 0)
+ continue;
+
+ switch_to_section (get_section (indirect_branch_table_name[o],
+ 0,
+ NULL_TREE));
+ for (i = 0; i < indirect_branch_table_label_no[o]; i++)
+ {
+ char label_start[32];
+
+ ASM_GENERATE_INTERNAL_LABEL (label_start,
+ indirect_branch_table_label[o], i);
+
+ fputs ("\t.long\t", asm_out_file);
+ assemble_name_raw (asm_out_file, label_start);
+ fputs ("-.\n", asm_out_file);
+ }
+ switch_to_section (current_function_section ());
+ }
+ }
+}
+
+/* Implement the TARGET_CASE_VALUES_THRESHOLD target hook. */
+
+unsigned int
+s390_case_values_threshold (void)
+{
+ /* Disabling branch prediction for indirect jumps makes jump tables
+ much more expensive. */
+ if (TARGET_INDIRECT_BRANCH_NOBP_JUMP)
+ return 20;
+
+ return default_case_values_threshold ();
+}
+
/* Initialize GCC target structure. */
#undef TARGET_ASM_ALIGNED_HI_OP
#undef TARGET_CONSTANT_ALIGNMENT
#define TARGET_CONSTANT_ALIGNMENT s390_constant_alignment
+#undef TARGET_ASM_CODE_END
+#define TARGET_ASM_CODE_END s390_code_end
+
+#undef TARGET_CASE_VALUES_THRESHOLD
+#define TARGET_CASE_VALUES_THRESHOLD s390_case_values_threshold
+
struct gcc_target targetm = TARGET_INITIALIZER;
#include "gt-s390.h"
s390_register_target_pragmas (); \
} while (0)
+#ifndef USED_FOR_TARGET
+/* The following structure is embedded in the machine
+ specific part of struct function. */
+
+struct GTY (()) s390_frame_layout
+{
+ /* Offset within stack frame. */
+ HOST_WIDE_INT gprs_offset;
+ HOST_WIDE_INT f0_offset;
+ HOST_WIDE_INT f4_offset;
+ HOST_WIDE_INT f8_offset;
+ HOST_WIDE_INT backchain_offset;
+
+ /* Number of first and last gpr where slots in the register
+ save area are reserved for. */
+ int first_save_gpr_slot;
+ int last_save_gpr_slot;
+
+ /* Location (FP register number) where GPRs (r0-r15) should
+ be saved to.
+ 0 - does not need to be saved at all
+ -1 - stack slot */
+#define SAVE_SLOT_NONE 0
+#define SAVE_SLOT_STACK -1
+ signed char gpr_save_slots[16];
+
+ /* Number of first and last gpr to be saved, restored. */
+ int first_save_gpr;
+ int first_restore_gpr;
+ int last_save_gpr;
+ int last_restore_gpr;
+
+ /* Bits standing for floating point registers. Set, if the
+ respective register has to be saved. Starting with reg 16 (f0)
+ at the rightmost bit.
+ Bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+ fpr 15 13 11 9 14 12 10 8 7 5 3 1 6 4 2 0
+ reg 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 */
+ unsigned int fpr_bitmap;
+
+ /* Number of floating point registers f8-f15 which must be saved. */
+ int high_fprs;
+
+ /* Set if return address needs to be saved.
+ This flag is set by s390_return_addr_rtx if it could not use
+ the initial value of r14 and therefore depends on r14 saved
+ to the stack. */
+ bool save_return_addr_p;
+
+ /* Size of stack frame. */
+ HOST_WIDE_INT frame_size;
+};
+
+
+/* Define the structure for the machine field in struct function. */
+
+struct GTY(()) machine_function
+{
+ struct s390_frame_layout frame_layout;
+
+ /* Literal pool base register. */
+ rtx base_reg;
+
+ /* True if we may need to perform branch splitting. */
+ bool split_branches_pending_p;
+
+ bool has_landing_pad_p;
+
+ /* True if the current function may contain a tbegin clobbering
+ FPRs. */
+ bool tbegin_p;
+
+ /* For -fsplit-stack support: A stack local which holds a pointer to
+ the stack arguments for a function with a variable number of
+ arguments. This is set at the start of the function and is used
+ to initialize the overflow_arg_area field of the va_list
+ structure. */
+ rtx split_stack_varargs_pointer;
+
+ enum indirect_branch indirect_branch_jump;
+ enum indirect_branch indirect_branch_call;
+
+ enum indirect_branch function_return_mem;
+ enum indirect_branch function_return_reg;
+};
+#endif
+
+#define TARGET_INDIRECT_BRANCH_NOBP_RET_OPTION \
+ (cfun->machine->function_return_reg != indirect_branch_keep \
+ || cfun->machine->function_return_mem != indirect_branch_keep)
+
+#define TARGET_INDIRECT_BRANCH_NOBP_RET \
+ ((cfun->machine->function_return_reg != indirect_branch_keep \
+ && !s390_return_addr_from_memory ()) \
+ || (cfun->machine->function_return_mem != indirect_branch_keep \
+ && s390_return_addr_from_memory ()))
+
+#define TARGET_INDIRECT_BRANCH_NOBP_JUMP \
+ (cfun->machine->indirect_branch_jump != indirect_branch_keep)
+
+#define TARGET_INDIRECT_BRANCH_NOBP_JUMP_THUNK \
+ (cfun->machine->indirect_branch_jump == indirect_branch_thunk \
+ || cfun->machine->indirect_branch_jump == indirect_branch_thunk_extern)
+
+#define TARGET_INDIRECT_BRANCH_NOBP_JUMP_INLINE_THUNK \
+ (cfun->machine->indirect_branch_jump == indirect_branch_thunk_inline)
+
+#define TARGET_INDIRECT_BRANCH_NOBP_CALL \
+ (cfun->machine->indirect_branch_call != indirect_branch_keep)
+
+#ifndef TARGET_DEFAULT_INDIRECT_BRANCH_TABLE
+#define TARGET_DEFAULT_INDIRECT_BRANCH_TABLE 0
+#endif
+
+#define TARGET_INDIRECT_BRANCH_THUNK_NAME_EXRL "__s390_indirect_jump_r%d"
+#define TARGET_INDIRECT_BRANCH_THUNK_NAME_EX "__s390_indirect_jump_r%duse_r%d"
+
+#define TARGET_INDIRECT_BRANCH_TABLE s390_indirect_branch_table
+
+
#endif /* S390_H */
UNSPEC_LTREF
UNSPEC_INSN
UNSPEC_EXECUTE
+ UNSPEC_EXECUTE_JUMP
; Atomic Support
UNSPEC_MB
[
; Sibling call register.
(SIBCALL_REGNUM 1)
+ ; A call-clobbered reg which can be used in indirect branch thunks
+ (INDIRECT_BRANCH_THUNK_REGNUM 1)
; Literal pool base register.
(BASE_REGNUM 13)
; Return address register.
z196_cracked"
(const_string "none"))
-(define_attr "mnemonic" "bcr_flush,unknown" (const_string "unknown"))
+; mnemonics which only get defined through if_then_else currently
+; don't get added to the list values automatically and hence need to
+; be listed here.
+(define_attr "mnemonic" "b,bas,bc,bcr_flush,unknown" (const_string "unknown"))
;; Length in bytes.
(match_operator 1 "s390_comparison" [(reg CC_REGNUM) (const_int 0)])
(match_operand 0 "address_operand" "ZQZR")
(pc)))]
- ""
+ "!TARGET_INDIRECT_BRANCH_NOBP_JUMP"
{
if (get_attr_op_type (insn) == OP_TYPE_RR)
return "b%C1r\t%0";
[(set (attr "op_type")
(if_then_else (match_operand 0 "register_operand" "")
(const_string "RR") (const_string "RX")))
+ (set (attr "mnemonic")
+ (if_then_else (match_operand 0 "register_operand" "")
+ (const_string "bcr") (const_string "bc")))
(set_attr "type" "branch")
(set_attr "atype" "agen")])
(ANY_RETURN)
(pc)))]
"s390_can_use_<code>_insn ()"
- "b%C0r\t%%r14"
- [(set_attr "op_type" "RR")
+{
+ if (TARGET_INDIRECT_BRANCH_NOBP_RET)
+ {
+ s390_indirect_branch_via_thunk (RETURN_REGNUM,
+ INVALID_REGNUM,
+ operands[0],
+ s390_indirect_branch_type_return);
+ return "";
+ }
+ else
+ return "b%C0r\t%%r14";
+}
+ [(set (attr "op_type")
+ (if_then_else (match_test "TARGET_INDIRECT_BRANCH_NOBP_RET")
+ (const_string "RIL")
+ (const_string "RR")))
+ (set (attr "mnemonic")
+ (if_then_else (match_test "TARGET_INDIRECT_BRANCH_NOBP_RET")
+ (const_string "brcl")
+ (const_string "bcr")))
(set_attr "type" "jsr")
(set_attr "atype" "agen")])
(match_operator 1 "s390_comparison" [(reg CC_REGNUM) (const_int 0)])
(pc)
(match_operand 0 "address_operand" "ZQZR")))]
- ""
+ "!TARGET_INDIRECT_BRANCH_NOBP_JUMP"
{
if (get_attr_op_type (insn) == OP_TYPE_RR)
return "b%D1r\t%0";
[(set (attr "op_type")
(if_then_else (match_operand 0 "register_operand" "")
(const_string "RR") (const_string "RX")))
+ (set (attr "mnemonic")
+ (if_then_else (match_operand 0 "register_operand" "")
+ (const_string "bcr") (const_string "bc")))
(set_attr "type" "branch")
(set_attr "atype" "agen")])
;
else
operands[0] = force_reg (Pmode, operands[0]);
+
+ if (TARGET_INDIRECT_BRANCH_NOBP_JUMP_THUNK)
+ {
+ operands[0] = force_reg (Pmode, operands[0]);
+ if (TARGET_CPU_Z10)
+ {
+ if (TARGET_64BIT)
+ emit_jump_insn (gen_indirect_jump_via_thunkdi_z10 (operands[0]));
+ else
+ emit_jump_insn (gen_indirect_jump_via_thunksi_z10 (operands[0]));
+ }
+ else
+ {
+ if (TARGET_64BIT)
+ emit_jump_insn (gen_indirect_jump_via_thunkdi (operands[0]));
+ else
+ emit_jump_insn (gen_indirect_jump_via_thunksi (operands[0]));
+ }
+ DONE;
+ }
+
+ if (TARGET_INDIRECT_BRANCH_NOBP_JUMP_INLINE_THUNK)
+ {
+ operands[0] = force_reg (Pmode, operands[0]);
+ rtx label_ref = gen_rtx_LABEL_REF (VOIDmode, gen_label_rtx ());
+ if (TARGET_CPU_Z10)
+ {
+ if (TARGET_64BIT)
+ emit_jump_insn (gen_indirect_jump_via_inlinethunkdi_z10 (operands[0],
+ label_ref));
+ else
+ emit_jump_insn (gen_indirect_jump_via_inlinethunksi_z10 (operands[0],
+ label_ref));
+ }
+ else
+ {
+ if (TARGET_64BIT)
+ emit_jump_insn (gen_indirect_jump_via_inlinethunkdi (operands[0],
+ label_ref,
+ force_reg (Pmode, label_ref)));
+ else
+ emit_jump_insn (gen_indirect_jump_via_inlinethunksi (operands[0],
+ label_ref,
+ force_reg (Pmode, label_ref)));
+ }
+ DONE;
+ }
})
-; The first constraint must be an "extra address constraint" in order
-; to trigger address reloading in LRA/reload
(define_insn "*indirect_jump"
[(set (pc)
- (match_operand 0 "address_operand" "ZR,a"))]
- ""
- "@
- b\t%a0
- br\t%0"
- [(set_attr "op_type" "RX,RR")
+ (match_operand 0 "address_operand" "ZR"))]
+ "!TARGET_INDIRECT_BRANCH_NOBP_JUMP_THUNK"
+{
+ if (get_attr_op_type (insn) == OP_TYPE_RR)
+ return "br\t%0";
+ else
+ return "b\t%a0";
+}
+ [(set (attr "op_type")
+ (if_then_else (match_operand 0 "register_operand" "")
+ (const_string "RR") (const_string "RX")))
+ (set (attr "mnemonic")
+ (if_then_else (match_operand 0 "register_operand" "")
+ (const_string "br") (const_string "b")))
(set_attr "type" "branch")
- (set_attr "atype" "agen")
- (set_attr "cpu_facility" "*")])
+ (set_attr "atype" "agen")])
+
+(define_insn "indirect_jump_via_thunk<mode>_z10"
+ [(set (pc)
+ (match_operand:P 0 "register_operand" "a"))]
+ "TARGET_INDIRECT_BRANCH_NOBP_JUMP_THUNK
+ && TARGET_CPU_Z10"
+{
+ s390_indirect_branch_via_thunk (REGNO (operands[0]),
+ INVALID_REGNUM,
+ NULL_RTX,
+ s390_indirect_branch_type_jump);
+ return "";
+}
+ [(set_attr "op_type" "RIL")
+ (set_attr "mnemonic" "jg")
+ (set_attr "type" "branch")
+ (set_attr "atype" "agen")])
+
+(define_insn "indirect_jump_via_thunk<mode>"
+ [(set (pc)
+ (match_operand:P 0 "register_operand" " a"))
+ (clobber (reg:P INDIRECT_BRANCH_THUNK_REGNUM))]
+ "TARGET_INDIRECT_BRANCH_NOBP_JUMP_THUNK
+ && !TARGET_CPU_Z10"
+{
+ s390_indirect_branch_via_thunk (REGNO (operands[0]),
+ INVALID_REGNUM,
+ NULL_RTX,
+ s390_indirect_branch_type_jump);
+ return "";
+}
+ [(set_attr "op_type" "RIL")
+ (set_attr "mnemonic" "jg")
+ (set_attr "type" "branch")
+ (set_attr "atype" "agen")])
+
+
+; The label_ref is wrapped into an if_then_else in order to hide it
+; from mark_jump_label. Without this the label_ref would become the
+; ONLY jump target of that jump breaking the control flow graph.
+(define_insn "indirect_jump_via_inlinethunk<mode>_z10"
+ [(unspec [(if_then_else (match_operand:P 1 "larl_operand" "X")
+ (const_int 0)
+ (const_int 0))
+ (const_int 0)] UNSPEC_EXECUTE_JUMP)
+ (set (pc) (match_operand:P 0 "register_operand" "a"))]
+ "TARGET_INDIRECT_BRANCH_NOBP_JUMP_INLINE_THUNK
+ && TARGET_CPU_Z10"
+{
+ s390_indirect_branch_via_inline_thunk (operands[1]);
+ return "";
+}
+ [(set_attr "op_type" "RIL")
+ (set_attr "type" "branch")
+ (set_attr "length" "10")])
+
+(define_insn "indirect_jump_via_inlinethunk<mode>"
+ [(unspec [(if_then_else (match_operand:P 1 "larl_operand" "X")
+ (const_int 0)
+ (const_int 0))
+ (match_operand:P 2 "register_operand" "a")] UNSPEC_EXECUTE_JUMP)
+ (set (pc) (match_operand:P 0 "register_operand" "a"))]
+ "TARGET_INDIRECT_BRANCH_NOBP_JUMP_INLINE_THUNK
+ && !TARGET_CPU_Z10"
+{
+ s390_indirect_branch_via_inline_thunk (operands[2]);
+ return "";
+}
+ [(set_attr "op_type" "RX")
+ (set_attr "type" "branch")
+ (set_attr "length" "8")])
; FIXME: LRA does not appear to be able to deal with MEMs being
; checked against address constraints like ZR above. So make this a
(define_insn "*indirect2_jump"
[(set (pc)
(match_operand 0 "nonimmediate_operand" "a,T"))]
- ""
+ "!TARGET_INDIRECT_BRANCH_NOBP_JUMP"
"@
br\t%0
bi\t%0"
; casesi instruction pattern(s).
;
-(define_insn "casesi_jump"
- [(set (pc) (match_operand 0 "address_operand" "ZR"))
- (use (label_ref (match_operand 1 "" "")))]
+(define_expand "casesi_jump"
+ [(parallel
+ [(set (pc) (match_operand 0 "address_operand"))
+ (use (label_ref (match_operand 1 "")))])]
""
+{
+ if (TARGET_INDIRECT_BRANCH_NOBP_JUMP_THUNK)
+ {
+ operands[0] = force_reg (GET_MODE (operands[0]), operands[0]);
+
+ if (TARGET_CPU_Z10)
+ {
+ if (TARGET_64BIT)
+ emit_jump_insn (gen_casesi_jump_via_thunkdi_z10 (operands[0],
+ operands[1]));
+ else
+ emit_jump_insn (gen_casesi_jump_via_thunksi_z10 (operands[0],
+ operands[1]));
+ }
+ else
+ {
+ if (TARGET_64BIT)
+ emit_jump_insn (gen_casesi_jump_via_thunkdi (operands[0],
+ operands[1]));
+ else
+ emit_jump_insn (gen_casesi_jump_via_thunksi (operands[0],
+ operands[1]));
+ }
+ DONE;
+ }
+
+ if (TARGET_INDIRECT_BRANCH_NOBP_JUMP_INLINE_THUNK)
+ {
+ operands[0] = force_reg (Pmode, operands[0]);
+ rtx label_ref = gen_rtx_LABEL_REF (VOIDmode, gen_label_rtx ());
+ if (TARGET_CPU_Z10)
+ {
+ if (TARGET_64BIT)
+ emit_jump_insn (gen_casesi_jump_via_inlinethunkdi_z10 (operands[0],
+ operands[1],
+ label_ref));
+ else
+ emit_jump_insn (gen_casesi_jump_via_inlinethunksi_z10 (operands[0],
+ operands[1],
+ label_ref));
+ }
+ else
+ {
+ if (TARGET_64BIT)
+ emit_jump_insn (gen_casesi_jump_via_inlinethunkdi (operands[0],
+ operands[1],
+ label_ref,
+ force_reg (Pmode, label_ref)));
+ else
+ emit_jump_insn (gen_casesi_jump_via_inlinethunksi (operands[0],
+ operands[1],
+ label_ref,
+ force_reg (Pmode, label_ref)));
+ }
+ DONE;
+ }
+})
+
+(define_insn "*casesi_jump"
+ [(set (pc) (match_operand 0 "address_operand" "ZR"))
+ (use (label_ref (match_operand 1 "" "")))]
+ "!TARGET_INDIRECT_BRANCH_NOBP_JUMP_THUNK"
{
if (get_attr_op_type (insn) == OP_TYPE_RR)
return "br\t%0";
[(set (attr "op_type")
(if_then_else (match_operand 0 "register_operand" "")
(const_string "RR") (const_string "RX")))
+ (set (attr "mnemonic")
+ (if_then_else (match_operand 0 "register_operand" "")
+ (const_string "br") (const_string "b")))
+ (set_attr "type" "branch")
+ (set_attr "atype" "agen")])
+
+(define_insn "casesi_jump_via_thunk<mode>_z10"
+ [(set (pc) (match_operand:P 0 "register_operand" "a"))
+ (use (label_ref (match_operand 1 "" "")))]
+ "TARGET_INDIRECT_BRANCH_NOBP_JUMP_THUNK
+ && TARGET_CPU_Z10"
+{
+ s390_indirect_branch_via_thunk (REGNO (operands[0]),
+ INVALID_REGNUM,
+ NULL_RTX,
+ s390_indirect_branch_type_jump);
+ return "";
+}
+ [(set_attr "op_type" "RIL")
+ (set_attr "mnemonic" "jg")
+ (set_attr "type" "branch")
+ (set_attr "atype" "agen")])
+
+(define_insn "casesi_jump_via_thunk<mode>"
+ [(set (pc) (match_operand:P 0 "register_operand" "a"))
+ (use (label_ref (match_operand 1 "" "")))
+ (clobber (reg:P INDIRECT_BRANCH_THUNK_REGNUM))]
+ "TARGET_INDIRECT_BRANCH_NOBP_JUMP_THUNK
+ && !TARGET_CPU_Z10"
+{
+ s390_indirect_branch_via_thunk (REGNO (operands[0]),
+ INVALID_REGNUM,
+ NULL_RTX,
+ s390_indirect_branch_type_jump);
+ return "";
+}
+ [(set_attr "op_type" "RIL")
+ (set_attr "mnemonic" "jg")
(set_attr "type" "branch")
(set_attr "atype" "agen")])
+
+; The label_ref is wrapped into an if_then_else in order to hide it
+; from mark_jump_label. Without this the label_ref would become the
+; ONLY jump target of that jump breaking the control flow graph.
+(define_insn "casesi_jump_via_inlinethunk<mode>_z10"
+ [(unspec [(if_then_else (match_operand:P 2 "larl_operand" "X")
+ (const_int 0)
+ (const_int 0))
+ (const_int 0)] UNSPEC_EXECUTE_JUMP)
+ (set (pc) (match_operand:P 0 "register_operand" "a"))
+ (use (label_ref (match_operand 1 "" "")))]
+ "TARGET_INDIRECT_BRANCH_NOBP_JUMP_INLINE_THUNK
+ && TARGET_CPU_Z10"
+{
+ s390_indirect_branch_via_inline_thunk (operands[2]);
+ return "";
+}
+ [(set_attr "op_type" "RIL")
+ (set_attr "type" "cs")
+ (set_attr "length" "10")])
+
+(define_insn "casesi_jump_via_inlinethunk<mode>"
+ [(unspec [(if_then_else (match_operand:P 2 "larl_operand" "X")
+ (const_int 0)
+ (const_int 0))
+ (match_operand:P 3 "register_operand" "a")] UNSPEC_EXECUTE_JUMP)
+ (set (pc) (match_operand:P 0 "register_operand" "a"))
+ (use (label_ref (match_operand 1 "" "")))]
+ "TARGET_INDIRECT_BRANCH_NOBP_JUMP_INLINE_THUNK
+ && !TARGET_CPU_Z10"
+{
+ s390_indirect_branch_via_inline_thunk (operands[3]);
+ return "";
+}
+ [(set_attr "op_type" "RX")
+ (set_attr "type" "cs")
+ (set_attr "length" "8")])
+
(define_expand "casesi"
[(match_operand:SI 0 "general_operand" "")
(match_operand:SI 1 "general_operand" "")
(match_operand 0 "const_int_operand" "n"))]
"SIBLING_CALL_P (insn)
&& GET_MODE (XEXP (XEXP (PATTERN (insn), 0), 0)) == Pmode"
- "br\t%%r1"
- [(set_attr "op_type" "RR")
+{
+ if (TARGET_INDIRECT_BRANCH_NOBP_CALL)
+ {
+ gcc_assert (TARGET_CPU_Z10);
+ s390_indirect_branch_via_thunk (SIBCALL_REGNUM,
+ INVALID_REGNUM,
+ NULL_RTX,
+ s390_indirect_branch_type_call);
+ return "";
+ }
+ else
+ return "br\t%%r1";
+}
+ [(set (attr "op_type")
+ (if_then_else (match_test "TARGET_INDIRECT_BRANCH_NOBP_CALL")
+ (const_string "RIL")
+ (const_string "RR")))
+ (set (attr "mnemonic")
+ (if_then_else (match_test "TARGET_INDIRECT_BRANCH_NOBP_CALL")
+ (const_string "jg")
+ (const_string "br")))
(set_attr "type" "branch")
(set_attr "atype" "agen")])
(match_operand 1 "const_int_operand" "n")))]
"SIBLING_CALL_P (insn)
&& GET_MODE (XEXP (XEXP (XEXP (PATTERN (insn), 1), 0), 0)) == Pmode"
- "br\t%%r1"
- [(set_attr "op_type" "RR")
+{
+ if (TARGET_INDIRECT_BRANCH_NOBP_CALL)
+ {
+ gcc_assert (TARGET_CPU_Z10);
+ s390_indirect_branch_via_thunk (SIBCALL_REGNUM,
+ INVALID_REGNUM,
+ NULL_RTX,
+ s390_indirect_branch_type_call);
+ return "";
+ }
+ else
+ return "br\t%%r1";
+}
+ [(set (attr "op_type")
+ (if_then_else (match_test "TARGET_INDIRECT_BRANCH_NOBP_CALL")
+ (const_string "RIL")
+ (const_string "RR")))
+ (set (attr "mnemonic")
+ (if_then_else (match_test "TARGET_INDIRECT_BRANCH_NOBP_CALL")
+ (const_string "jg")
+ (const_string "br")))
(set_attr "type" "branch")
(set_attr "atype" "agen")])
[(call (mem:QI (match_operand 0 "address_operand" "ZR"))
(match_operand 1 "const_int_operand" "n"))
(clobber (match_operand 2 "register_operand" "=r"))]
- "!SIBLING_CALL_P (insn) && GET_MODE (operands[2]) == Pmode"
+ "!TARGET_INDIRECT_BRANCH_NOBP_CALL
+ && !SIBLING_CALL_P (insn)
+ && GET_MODE (operands[2]) == Pmode"
{
if (get_attr_op_type (insn) == OP_TYPE_RR)
return "basr\t%2,%0";
[(set (attr "op_type")
(if_then_else (match_operand 0 "register_operand" "")
(const_string "RR") (const_string "RX")))
+ (set (attr "mnemonic")
+ (if_then_else (match_operand 0 "register_operand" "")
+ (const_string "basr") (const_string "bas")))
+ (set_attr "type" "jsr")
+ (set_attr "atype" "agen")
+ (set_attr "z196prop" "z196_cracked")])
+
+(define_insn "*basr_via_thunk<mode>_z10"
+ [(call (mem:QI (match_operand:P 0 "register_operand" "a"))
+ (match_operand 1 "const_int_operand" "n"))
+ (clobber (match_operand:P 2 "register_operand" "=&r"))]
+ "TARGET_INDIRECT_BRANCH_NOBP_CALL
+ && TARGET_CPU_Z10
+ && !SIBLING_CALL_P (insn)"
+{
+ s390_indirect_branch_via_thunk (REGNO (operands[0]),
+ REGNO (operands[2]),
+ NULL_RTX,
+ s390_indirect_branch_type_call);
+ return "";
+}
+ [(set_attr "op_type" "RIL")
+ (set_attr "mnemonic" "brasl")
+ (set_attr "type" "jsr")
+ (set_attr "atype" "agen")
+ (set_attr "z196prop" "z196_cracked")])
+
+(define_insn "*basr_via_thunk<mode>"
+ [(call (mem:QI (match_operand:P 0 "register_operand" "a"))
+ (match_operand 1 "const_int_operand" "n"))
+ (clobber (match_operand:P 2 "register_operand" "=&r"))
+ (clobber (reg:P INDIRECT_BRANCH_THUNK_REGNUM))]
+ "TARGET_INDIRECT_BRANCH_NOBP_CALL
+ && !TARGET_CPU_Z10
+ && !SIBLING_CALL_P (insn)"
+{
+ s390_indirect_branch_via_thunk (REGNO (operands[0]),
+ REGNO (operands[2]),
+ NULL_RTX,
+ s390_indirect_branch_type_call);
+ return "";
+}
+ [(set_attr "op_type" "RIL")
+ (set_attr "mnemonic" "brasl")
(set_attr "type" "jsr")
(set_attr "atype" "agen")
(set_attr "z196prop" "z196_cracked")])
(call (mem:QI (match_operand 1 "address_operand" "ZR"))
(match_operand 2 "const_int_operand" "n")))
(clobber (match_operand 3 "register_operand" "=r"))]
- "!SIBLING_CALL_P (insn) && GET_MODE (operands[3]) == Pmode"
+ "!TARGET_INDIRECT_BRANCH_NOBP_CALL
+ && !SIBLING_CALL_P (insn)
+ && GET_MODE (operands[3]) == Pmode"
{
if (get_attr_op_type (insn) == OP_TYPE_RR)
return "basr\t%3,%1";
[(set (attr "op_type")
(if_then_else (match_operand 1 "register_operand" "")
(const_string "RR") (const_string "RX")))
+ (set (attr "mnemonic")
+ (if_then_else (match_operand 1 "register_operand" "")
+ (const_string "basr") (const_string "bas")))
+ (set_attr "type" "jsr")
+ (set_attr "atype" "agen")
+ (set_attr "z196prop" "z196_cracked")])
+
+(define_insn "*basr_r_via_thunk_z10"
+ [(set (match_operand 0 "" "")
+ (call (mem:QI (match_operand 1 "register_operand" "a"))
+ (match_operand 2 "const_int_operand" "n")))
+ (clobber (match_operand 3 "register_operand" "=&r"))]
+ "TARGET_INDIRECT_BRANCH_NOBP_CALL
+ && TARGET_CPU_Z10
+ && !SIBLING_CALL_P (insn)
+ && GET_MODE (operands[3]) == Pmode"
+{
+ s390_indirect_branch_via_thunk (REGNO (operands[1]),
+ REGNO (operands[3]),
+ NULL_RTX,
+ s390_indirect_branch_type_call);
+ return "";
+}
+ [(set_attr "op_type" "RIL")
+ (set_attr "mnemonic" "brasl")
+ (set_attr "type" "jsr")
+ (set_attr "atype" "agen")
+ (set_attr "z196prop" "z196_cracked")])
+
+(define_insn "*basr_r_via_thunk"
+ [(set (match_operand 0 "" "")
+ (call (mem:QI (match_operand 1 "register_operand" "a"))
+ (match_operand 2 "const_int_operand" "n")))
+ (clobber (match_operand 3 "register_operand" "=&r"))
+ (clobber (reg:P INDIRECT_BRANCH_THUNK_REGNUM))]
+ "TARGET_INDIRECT_BRANCH_NOBP_CALL
+ && !TARGET_CPU_Z10
+ && !SIBLING_CALL_P (insn)
+ && GET_MODE (operands[3]) == Pmode"
+{
+ s390_indirect_branch_via_thunk (REGNO (operands[1]),
+ REGNO (operands[3]),
+ NULL_RTX,
+ s390_indirect_branch_type_call);
+ return "";
+}
+ [(set_attr "op_type" "RIL")
+ (set_attr "mnemonic" "brasl")
(set_attr "type" "jsr")
(set_attr "atype" "agen")
(set_attr "z196prop" "z196_cracked")])
(define_insn "<code>"
[(ANY_RETURN)]
"s390_can_use_<code>_insn ()"
- "br\t%%r14"
- [(set_attr "op_type" "RR")
+{
+ if (TARGET_INDIRECT_BRANCH_NOBP_RET)
+ {
+ /* The target is always r14 so there is no clobber
+ of r1 needed for pre z10 targets. */
+ s390_indirect_branch_via_thunk (RETURN_REGNUM,
+ INVALID_REGNUM,
+ NULL_RTX,
+ s390_indirect_branch_type_return);
+ return "";
+ }
+ else
+ return "br\t%%r14";
+}
+ [(set (attr "op_type")
+ (if_then_else (match_test "TARGET_INDIRECT_BRANCH_NOBP_RET")
+ (const_string "RIL")
+ (const_string "RR")))
+ (set (attr "mnemonic")
+ (if_then_else (match_test "TARGET_INDIRECT_BRANCH_NOBP_RET")
+ (const_string "jg")
+ (const_string "br")))
(set_attr "type" "jsr")
(set_attr "atype" "agen")])
-(define_insn "*return"
+
+(define_expand "return_use"
+ [(parallel
+ [(return)
+ (use (match_operand 0 "register_operand" "a"))])]
+ ""
+{
+ if (!TARGET_CPU_Z10
+ && TARGET_INDIRECT_BRANCH_NOBP_RET_OPTION)
+ {
+ if (TARGET_64BIT)
+ emit_jump_insn (gen_returndi_prez10 (operands[0]));
+ else
+ emit_jump_insn (gen_returnsi_prez10 (operands[0]));
+ DONE;
+ }
+})
+
+(define_insn "*return<mode>"
[(return)
- (use (match_operand 0 "register_operand" "a"))]
- "GET_MODE (operands[0]) == Pmode"
- "br\t%0"
- [(set_attr "op_type" "RR")
+ (use (match_operand:P 0 "register_operand" "a"))]
+ "TARGET_CPU_Z10 || !TARGET_INDIRECT_BRANCH_NOBP_RET_OPTION"
+{
+ if (TARGET_INDIRECT_BRANCH_NOBP_RET)
+ {
+ s390_indirect_branch_via_thunk (REGNO (operands[0]),
+ INVALID_REGNUM,
+ NULL_RTX,
+ s390_indirect_branch_type_return);
+ return "";
+ }
+ else
+ return "br\t%0";
+}
+ [(set (attr "op_type")
+ (if_then_else (match_test "TARGET_INDIRECT_BRANCH_NOBP_RET")
+ (const_string "RIL")
+ (const_string "RR")))
+ (set (attr "mnemonic")
+ (if_then_else (match_test "TARGET_INDIRECT_BRANCH_NOBP_RET")
+ (const_string "jg")
+ (const_string "br")))
+ (set_attr "type" "jsr")
+ (set_attr "atype" "agen")])
+
+(define_insn "return<mode>_prez10"
+ [(return)
+ (use (match_operand:P 0 "register_operand" "a"))
+ (clobber (reg:P INDIRECT_BRANCH_THUNK_REGNUM))]
+ "!TARGET_CPU_Z10 && TARGET_INDIRECT_BRANCH_NOBP_RET_OPTION"
+{
+ if (TARGET_INDIRECT_BRANCH_NOBP_RET)
+ {
+ s390_indirect_branch_via_thunk (REGNO (operands[0]),
+ INVALID_REGNUM,
+ NULL_RTX,
+ s390_indirect_branch_type_return);
+ return "";
+ }
+ else
+ return "br\t%0";
+}
+ [(set (attr "op_type")
+ (if_then_else (match_test "TARGET_INDIRECT_BRANCH_NOBP_RET")
+ (const_string "RIL")
+ (const_string "RR")))
+ (set (attr "mnemonic")
+ (if_then_else (match_test "TARGET_INDIRECT_BRANCH_NOBP_RET")
+ (const_string "jg")
+ (const_string "br")))
(set_attr "type" "jsr")
(set_attr "atype" "agen")])
mpic-data-is-text-relative
Target Report Var(s390_pic_data_is_text_relative) Init(TARGET_DEFAULT_PIC_DATA_IS_TEXT_RELATIVE)
Assume data segments are relative to text segment.
+
+
+mindirect-branch=
+Target Report RejectNegative Joined Enum(indirect_branch) Var(s390_indirect_branch) Init(indirect_branch_keep)
+Wrap all indirect branches into execute in order to disable branch
+prediction.
+
+mindirect-branch-jump=
+Target Report RejectNegative Joined Enum(indirect_branch) Var(s390_indirect_branch_jump) Init(indirect_branch_keep)
+Wrap indirect table jumps and computed gotos into execute in order to
+disable branch prediction. Using thunk or thunk-extern with this
+option requires the thunks to be considered signal handlers to order to
+generate correct CFI. For environments where unwinding (e.g. for
+exceptions) is required please use thunk-inline instead.
+
+mindirect-branch-call=
+Target Report RejectNegative Joined Enum(indirect_branch) Var(s390_indirect_branch_call) Init(indirect_branch_keep)
+Wrap all indirect calls into execute in order to disable branch prediction.
+
+mfunction-return=
+Target Report RejectNegative Joined Enum(indirect_branch) Var(s390_function_return) Init(indirect_branch_keep)
+Wrap all indirect return branches into execute in order to disable branch
+prediction.
+
+mfunction-return-mem=
+Target Report RejectNegative Joined Enum(indirect_branch) Var(s390_function_return_mem) Init(indirect_branch_keep)
+Wrap indirect return branches into execute in order to disable branch
+prediction. This affects only branches where the return address is
+going to be restored from memory.
+
+mfunction-return-reg=
+Target Report RejectNegative Joined Enum(indirect_branch) Var(s390_function_return_reg) Init(indirect_branch_keep)
+Wrap indirect return branches into execute in order to disable branch
+prediction. This affects only branches where the return address
+doesn't need to be restored from memory.
+
+Enum
+Name(indirect_branch) Type(enum indirect_branch)
+Known indirect branch choices (for use with the -mindirect-branch=/-mfunction-return= 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)
+
+mindirect-branch-table
+Target Report Var(s390_indirect_branch_table) Init(TARGET_DEFAULT_INDIRECT_BRANCH_TABLE)
+Generate sections .s390_indirect_jump, .s390_indirect_call,
+.s390_return_reg, and .s390_return_mem to contain the indirect branch
+locations which have been patched as part of using one of the
+-mindirect-branch* or -mfunction-return* options. The sections
+consist of an array of 32 bit elements. Each entry holds the offset
+from the entry to the patched location.
+2018-02-08 Andreas Krebbel <krebbel@linux.vnet.ibm.com>
+
+ * gcc.target/s390/nobp-function-pointer-attr.c: New test.
+ * gcc.target/s390/nobp-function-pointer-nothunk.c: New test.
+ * gcc.target/s390/nobp-function-pointer-z10.c: New test.
+ * gcc.target/s390/nobp-function-pointer-z900.c: New test.
+ * gcc.target/s390/nobp-indirect-jump-attr.c: New test.
+ * gcc.target/s390/nobp-indirect-jump-inline-attr.c: New test.
+ * gcc.target/s390/nobp-indirect-jump-inline-z10.c: New test.
+ * gcc.target/s390/nobp-indirect-jump-inline-z900.c: New test.
+ * gcc.target/s390/nobp-indirect-jump-nothunk.c: New test.
+ * gcc.target/s390/nobp-indirect-jump-z10.c: New test.
+ * gcc.target/s390/nobp-indirect-jump-z900.c: New test.
+ * gcc.target/s390/nobp-return-attr-all.c: New test.
+ * gcc.target/s390/nobp-return-attr-neg.c: New test.
+ * gcc.target/s390/nobp-return-mem-attr.c: New test.
+ * gcc.target/s390/nobp-return-mem-nothunk.c: New test.
+ * gcc.target/s390/nobp-return-mem-z10.c: New test.
+ * gcc.target/s390/nobp-return-mem-z900.c: New test.
+ * gcc.target/s390/nobp-return-reg-attr.c: New test.
+ * gcc.target/s390/nobp-return-reg-mixed.c: New test.
+ * gcc.target/s390/nobp-return-reg-nothunk.c: New test.
+ * gcc.target/s390/nobp-return-reg-z10.c: New test.
+ * gcc.target/s390/nobp-return-reg-z900.c: New test.
+ * gcc.target/s390/nobp-table-jump-inline-z10.c: New test.
+ * gcc.target/s390/nobp-table-jump-inline-z900.c: New test.
+ * gcc.target/s390/nobp-table-jump-z10.c: New test.
+ * gcc.target/s390/nobp-table-jump-z900.c: New test.
+
2018-02-08 Richard Biener <rguenther@suse.de>
PR tree-optimization/84233
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 --save-temps -mindirect-branch-table" } */
+
+int gl;
+
+void __attribute__((noinline,noclone))
+foo (int a)
+{
+ gl = a + 40;
+}
+
+int __attribute__((noinline,noclone))
+foo_value (int a)
+{
+ return a + 40;
+}
+
+void* __attribute__((noinline,noclone))
+get_fptr (int a)
+{
+ switch (a)
+ {
+ case 0: return &foo; break;
+ case 1: return &foo_value; break;
+ default: __builtin_abort ();
+ }
+}
+
+void (*f) (int);
+int (*g) (int);
+
+int __attribute__((indirect_branch_call("thunk")))
+main ()
+{
+ int res;
+
+ f = get_fptr(0);
+ f (2);
+ if (gl != 42)
+ __builtin_abort ();
+
+ g = get_fptr(1);
+ if (g (2) != 42)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 2 x main
+/* { dg-final { scan-assembler-times "brasl\t%r\[0-9\]*,__s390_indirect_jump" 2 } } */
+/* { dg-final { scan-assembler "exrl" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-O3 -march=z10 --save-temps -mindirect-branch-call=thunk-extern -mindirect-branch-table" } */
+
+int gl;
+
+void __attribute__((noinline,noclone))
+foo (int a)
+{
+ gl = a + 40;
+}
+
+int __attribute__((noinline,noclone))
+foo_value (int a)
+{
+ return a + 40;
+}
+
+void* __attribute__((noinline,noclone))
+get_fptr (int a)
+{
+ switch (a)
+ {
+ case 0: return &foo; break;
+ case 1: return &foo_value; break;
+ default: __builtin_abort ();
+ }
+}
+
+void (*f) (int);
+int (*g) (int);
+
+int
+main ()
+{
+ int res;
+
+ f = get_fptr(0);
+ f (2);
+ if (gl != 42)
+ __builtin_abort ();
+
+ g = get_fptr(1);
+ if (g (2) != 42)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 2 x main
+/* { dg-final { scan-assembler-times "brasl\t%r\[0-9\]*,__s390_indirect_jump" 2 } } */
+
+/* No thunks due to thunk-extern. */
+/* { dg-final { scan-assembler-not "exrl" } } */
+/* { dg-final { scan-assembler-not ".globl __s390_indirect_jump" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 --save-temps -mindirect-branch-call=thunk -mindirect-branch-table" } */
+
+int gl;
+
+void __attribute__((noinline,noclone))
+foo (int a)
+{
+ gl = a + 40;
+}
+
+int __attribute__((noinline,noclone))
+foo_value (int a)
+{
+ return a + 40;
+}
+
+void* __attribute__((noinline,noclone))
+get_fptr (int a)
+{
+ switch (a)
+ {
+ case 0: return &foo; break;
+ case 1: return &foo_value; break;
+ default: __builtin_abort ();
+ }
+}
+
+void (*f) (int);
+int (*g) (int);
+
+int
+main ()
+{
+ int res;
+
+ f = get_fptr(0);
+ f (2);
+ if (gl != 42)
+ __builtin_abort ();
+
+ g = get_fptr(1);
+ if (g (2) != 42)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 2 x main
+/* { dg-final { scan-assembler-times "brasl\t%r\[0-9\]*,__s390_indirect_jump" 2 } } */
+/* { dg-final { scan-assembler "exrl" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z900 --save-temps -mindirect-branch-call=thunk -mindirect-branch-table" } */
+
+int gl;
+
+void __attribute__((noinline,noclone))
+foo (int a)
+{
+ gl = a + 40;
+}
+
+int __attribute__((noinline,noclone))
+foo_value (int a)
+{
+ return a + 40;
+}
+
+void* __attribute__((noinline,noclone))
+get_fptr (int a)
+{
+ switch (a)
+ {
+ case 0: return &foo; break;
+ case 1: return &foo_value; break;
+ default: __builtin_abort ();
+ }
+}
+
+void (*f) (int);
+int (*g) (int);
+
+int
+main ()
+{
+ int res;
+
+ f = get_fptr(0);
+ f (2);
+ if (gl != 42)
+ __builtin_abort ();
+
+ g = get_fptr(1);
+ if (g (2) != 42)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 2 x main
+/* { dg-final { scan-assembler-times "brasl\t%r\[0-9\]*,__s390_indirect_jump" 2 } } */
+/* { dg-final { scan-assembler "ex\t" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 --save-temps -mindirect-branch-table" } */
+/* { dg-require-effective-target label_values } */
+
+/* This is a copy of the gcc.c-torture/execute/20040302-1.c
+ testcase. */
+
+int code[]={0,0,0,0,1};
+
+void
+foo(int x) {
+ volatile int b;
+ b = 0xffffffff;
+}
+
+void __attribute__((indirect_branch_jump("thunk")))
+bar(int *pc) {
+ static const void *l[] = {&&lab0, &&end};
+
+ foo(0);
+ goto *l[*pc];
+ lab0:
+ foo(0);
+ pc++;
+ goto *l[*pc];
+ end:
+ return;
+}
+
+int main() {
+ bar(code);
+ return 0;
+}
+
+/* 2x bar */
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 2 } } */
+/* { dg-final { scan-assembler "exrl" } } */
+
+/* { dg-final { scan-assembler "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 --save-temps -mindirect-branch-table" } */
+/* { dg-require-effective-target label_values } */
+
+/* This is a copy of the gcc.c-torture/execute/20040302-1.c
+ testcase. */
+
+int code[]={0,0,0,0,1};
+
+void foo(int x) {
+ volatile int b;
+ b = 0xffffffff;
+}
+
+void __attribute__((indirect_branch_jump("thunk-inline")))
+bar(int *pc) {
+ static const void *l[] = {&&lab0, &&end};
+
+ foo(0);
+ goto *l[*pc];
+ lab0:
+ foo(0);
+ pc++;
+ goto *l[*pc];
+ end:
+ return;
+}
+
+int
+main() {
+ bar(code);
+ return 0;
+}
+
+/* The two gotos in bar get merged. */
+/* { dg-final { scan-assembler-times "exrl" 1 } } */
+
+/* { dg-final { scan-assembler-not "jg\t__s390_indirect_jump" } } */
+/* { dg-final { scan-assembler "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 --save-temps -mindirect-branch-jump=thunk-inline -mindirect-branch-table" } */
+/* { dg-require-effective-target label_values } */
+
+/* This is a copy of the gcc.c-torture/execute/20040302-1.c
+ testcase. */
+
+int code[]={0,0,0,0,1};
+
+void
+foo(int x) {
+ volatile int b;
+ b = 0xffffffff;
+}
+
+void
+bar(int *pc) {
+ static const void *l[] = {&&lab0, &&end};
+
+ foo(0);
+ goto *l[*pc];
+ lab0:
+ foo(0);
+ pc++;
+ goto *l[*pc];
+ end:
+ return;
+}
+
+int
+main() {
+ bar(code);
+ return 0;
+}
+
+/* The two gotos in bar get merged. */
+/* { dg-final { scan-assembler-times "exrl" 1 } } */
+
+/* { dg-final { scan-assembler-not "jg\t__s390_indirect_jump" } } */
+/* { dg-final { scan-assembler "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z900 --save-temps -mindirect-branch-jump=thunk-inline -mindirect-branch-table" } */
+/* { dg-require-effective-target label_values } */
+
+/* This is a copy of the gcc.c-torture/execute/20040302-1.c
+ testcase. */
+
+int code[]={0,0,0,0,1};
+
+void
+foo(int x) {
+ volatile int b;
+ b = 0xffffffff;
+}
+
+void
+bar(int *pc) {
+ static const void *l[] = {&&lab0, &&end};
+
+ foo(0);
+ goto *l[*pc];
+ lab0:
+ foo(0);
+ pc++;
+ goto *l[*pc];
+ end:
+ return;
+}
+
+int
+main() {
+ bar(code);
+ return 0;
+}
+
+/* The two gotos in bar get merged. */
+/* { dg-final { scan-assembler-times "\tex\t" 1 } } */
+
+/* { dg-final { scan-assembler-not "jg\t__s390_indirect_jump" } } */
+/* { dg-final { scan-assembler "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-O3 -march=z10 --save-temps -mindirect-branch-jump=thunk-extern -mindirect-branch-table" } */
+/* { dg-require-effective-target label_values } */
+
+/* This is a copy of the gcc.c-torture/execute/20040302-1.c
+ testcase. */
+
+int code[]={0,0,0,0,1};
+
+void
+foo(int x) {
+ volatile int b;
+ b = 0xffffffff;
+}
+
+void
+bar(int *pc) {
+ static const void *l[] = {&&lab0, &&end};
+
+ foo(0);
+ goto *l[*pc];
+ lab0:
+ foo(0);
+ pc++;
+ goto *l[*pc];
+ end:
+ return;
+}
+
+int
+main() {
+ bar(code);
+ return 0;
+}
+
+/* 2 x bar
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 2 } } */
+
+/* No thunks due to thunk-extern. */
+/* { dg-final { scan-assembler-not "exrl" } } */
+/* { dg-final { scan-assembler-not ".globl __s390_indirect_jump" } } */
+
+/* { dg-final { scan-assembler "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 --save-temps -mindirect-branch-jump=thunk -mindirect-branch-table" } */
+/* { dg-require-effective-target label_values } */
+
+/* This is a copy of the gcc.c-torture/execute/20040302-1.c
+ testcase. */
+
+int code[]={0,0,0,0,1};
+
+void
+foo(int x) {
+ volatile int b;
+ b = 0xffffffff;
+}
+
+void
+bar(int *pc) {
+ static const void *l[] = {&&lab0, &&end};
+
+ foo(0);
+ goto *l[*pc];
+ lab0:
+ foo(0);
+ pc++;
+ goto *l[*pc];
+ end:
+ return;
+}
+
+int
+main() {
+ bar(code);
+ return 0;
+}
+
+/* 2x bar */
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 2 } } */
+/* { dg-final { scan-assembler "exrl" } } */
+
+/* { dg-final { scan-assembler "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z900 --save-temps -mindirect-branch-jump=thunk -mindirect-branch-table" } */
+/* { dg-require-effective-target label_values } */
+
+/* This is a copy of the gcc.c-torture/execute/20040302-1.c
+ testcase. */
+
+int code[]={0,0,0,0,1};
+
+void
+foo(int x) {
+ volatile int b;
+ b = 0xffffffff;
+}
+
+void
+bar(int *pc) {
+ static const void *l[] = {&&lab0, &&end};
+
+ foo(0);
+ goto *l[*pc];
+ lab0:
+ foo(0);
+ pc++;
+ goto *l[*pc];
+ end:
+ return;
+}
+
+int
+main() {
+ bar(code);
+ return 0;
+}
+
+/* 2 x bar
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 2 } } */
+/* { dg-final { scan-assembler "ex\t" } } */
+
+/* { dg-final { scan-assembler "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 -mzarch --save-temps -mindirect-branch-table" } */
+
+int gl = 0;
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ return a + 2;
+}
+
+void __attribute__((function_return("thunk"),noinline,noclone))
+foo (int a)
+{
+ int i;
+
+ if (a == 42)
+ return;
+
+ for (i = 0; i < a; i++)
+ gl += bar (i);
+}
+
+int
+main ()
+{
+ foo (3);
+ if (gl != 9)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* With -march=z10 -mzarch the shrink wrapped returns use compare and
+ swap relative to jump to the exit block instead of making use of
+ the conditional return pattern.
+ FIXME: Use compare and branch register for that!!!! */
+
+/* 2 x foo
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 2 } } */
+/* { dg-final { scan-assembler "exrl" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 -mzarch --save-temps -mfunction-return-mem=thunk -mindirect-branch-table" } */
+
+int gl = 0;
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ return a + 2;
+}
+
+void __attribute__((function_return("keep"),noinline,noclone))
+foo (int a)
+{
+ int i;
+
+ if (a == 42)
+ return;
+
+ for (i = 0; i < a; i++)
+ gl += bar (i);
+}
+
+int __attribute__((function_return("keep")))
+main ()
+{
+ foo (3);
+ if (gl != 9)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* { dg-final { scan-assembler-not "jg\t__s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "exrl" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 -mzarch --save-temps -mindirect-branch-table" } */
+
+int gl = 0;
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ return a + 2;
+}
+
+void __attribute__((function_return_mem("thunk"),noinline,noclone))
+foo (int a)
+{
+ int i;
+
+ if (a == 42)
+ return;
+
+ for (i = 0; i < a; i++)
+ gl += bar (i);
+}
+
+int
+main ()
+{
+ foo (3);
+ if (gl != 9)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* With -march=z10 -mzarch the shrink wrapped returns use compare and
+ swap relative to jump to the exit block instead of making use of
+ the conditional return pattern.
+ FIXME: Use compare and branch register for that!!!! */
+
+/* 2 x foo
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 2 } } */
+/* { dg-final { scan-assembler "exrl" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-O3 -march=z10 -mzarch --save-temps -mfunction-return-mem=thunk-extern -mindirect-branch-table" } */
+
+int gl = 0;
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ return a + 2;
+}
+
+void __attribute__((noinline,noclone))
+foo (int a)
+{
+ int i;
+
+ if (a == 42)
+ return;
+
+ for (i = 0; i < a; i++)
+ gl += bar (i);
+}
+
+int
+main ()
+{
+ foo (3);
+ if (gl != 9)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* With -march=z10 -mzarch the shrink wrapped returns use compare and
+ swap relative to jump to the exit block instead of making use of
+ the conditional return pattern.
+ FIXME: Use compare and branch register for that!!!! */
+
+/* 2 x foo, 1 x main
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 3 } } */
+
+/* No thunks due to thunk-extern. */
+/* { dg-final { scan-assembler-not "exrl" } } */
+/* { dg-final { scan-assembler-not ".globl __s390_indirect_jump" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 -mzarch --save-temps -mfunction-return-mem=thunk -mindirect-branch-table" } */
+
+int gl = 0;
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ return a + 2;
+}
+
+void __attribute__((noinline,noclone))
+foo (int a)
+{
+ int i;
+
+ if (a == 42)
+ return;
+
+ for (i = 0; i < a; i++)
+ gl += bar (i);
+}
+
+int
+main ()
+{
+ foo (3);
+ if (gl != 9)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* With -march=z10 -mzarch the shrink wrapped returns use compare and
+ swap relative to jump to the exit block instead of making use of
+ the conditional return pattern.
+ FIXME: Use compare and branch register for that!!!! */
+
+/* 2 x foo, 1 x main
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 3 } } */
+/* { dg-final { scan-assembler "exrl" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z900 --save-temps -mfunction-return-mem=thunk -mindirect-branch-table" } */
+
+int gl = 0;
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ return a + 2;
+}
+
+void __attribute__((noinline,noclone))
+foo (int a)
+{
+ int i;
+
+ if (a == 42)
+ return;
+
+ for (i = 0; i < a; i++)
+ gl += bar (i);
+}
+
+int
+main ()
+{
+ foo (3);
+ if (gl != 9)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 1 x foo, 1 x main
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 2 } } */
+
+/* 1 x foo, conditional return, shrink wrapped
+/* { dg-final { scan-assembler "jge\t__s390_indirect_jump" } } */
+
+/* 1 x foo, conditional return, shrink wrapped
+/* { dg-final { scan-assembler "jgle\t__s390_indirect_jump" } } */
+
+/* { dg-final { scan-assembler "ex\t" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 --save-temps -mindirect-branch-table" } */
+
+int gl = 0;
+
+int __attribute__((function_return_reg("thunk"),noinline,noclone))
+bar (int a)
+{
+ return a + 2;
+}
+
+void __attribute__((noinline,noclone))
+foo (int a)
+{
+ int i;
+
+ if (a == 42)
+ return;
+
+ for (i = 0; i < a; i++)
+ gl += bar (i);
+}
+
+int
+main ()
+{
+ foo (3);
+ if (gl != 9)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 1 x bar
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 1 } } */
+/* { dg-final { scan-assembler "exrl" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z900 --save-temps -mfunction-return-reg=thunk -mindirect-branch-table" } */
+
+/* We have to generate different thunks for indirect branches
+ depending on whether the code is compiled for pre z10 machines or
+ later. This testcase makes sure this works within the same compile
+ unit. */
+
+int __attribute__((noinline,noclone,target("arch=z10")))
+bar (int a)
+{
+ return a + 2;
+}
+
+int __attribute__((noinline,noclone,target("arch=z9-ec")))
+foo (int a)
+{
+ return a + 3;
+}
+
+int
+main ()
+{
+ if (bar (42) != 44)
+ __builtin_abort ();
+
+ if (foo (42) != 45)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 1 x bar, 1 x foo */
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 2 } } */
+/* 1 x foo */
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump_r1use" 1 } } */
+
+/* { dg-final { scan-assembler-times "ex\t" 1 } } */
+/* { dg-final { scan-assembler-times "exrl\t" 1 } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-O3 -march=z10 --save-temps -mfunction-return-reg=thunk-extern -mindirect-branch-table" } */
+
+int gl = 0;
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ return a + 2;
+}
+
+void __attribute__((noinline,noclone))
+foo (int a)
+{
+ int i;
+
+ if (a == 42)
+ return;
+
+ for (i = 0; i < a; i++)
+ gl += bar (i);
+}
+
+int
+main ()
+{
+ foo (3);
+ if (gl != 9)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 1 x bar
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 1 } } */
+
+/* No thunks due to thunk-extern. */
+/* { dg-final { scan-assembler-not "exrl" } } */
+/* { dg-final { scan-assembler-not ".globl __s390_indirect_jump" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 --save-temps -mfunction-return-reg=thunk -mindirect-branch-table" } */
+
+int gl = 0;
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ return a + 2;
+}
+
+void __attribute__((noinline,noclone))
+foo (int a)
+{
+ int i;
+
+ if (a == 42)
+ return;
+
+ for (i = 0; i < a; i++)
+ gl += bar (i);
+}
+
+int
+main ()
+{
+ foo (3);
+ if (gl != 9)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 1 x bar
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 1 } } */
+/* { dg-final { scan-assembler "exrl" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z900 --save-temps -mfunction-return-reg=thunk -mindirect-branch-table" } */
+
+int gl = 0;
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ return a + 2;
+}
+
+void __attribute__((noinline,noclone))
+foo (int a)
+{
+ int i;
+
+ if (a == 42)
+ return;
+
+ for (i = 0; i < a; i++)
+ gl += bar (i);
+}
+
+int
+main ()
+{
+ foo (3);
+ if (gl != 9)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 1 x bar
+/* { dg-final { scan-assembler-times "jg\t__s390_indirect_jump" 1 } } */
+/* { dg-final { scan-assembler "ex\t" } } */
+
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler "section\t.s390_return_reg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_mem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 -mzarch --save-temps -mindirect-branch-jump=thunk-inline -mindirect-branch-table" } */
+
+/* case-values-threshold will be set to 20 by the back-end when jump
+ thunk are requested. */
+
+int __attribute__((noinline,noclone)) foo1 (void) { return 1; }
+int __attribute__((noinline,noclone)) foo2 (void) { return 2; }
+int __attribute__((noinline,noclone)) foo3 (void) { return 3; }
+int __attribute__((noinline,noclone)) foo4 (void) { return 4; }
+int __attribute__((noinline,noclone)) foo5 (void) { return 5; }
+int __attribute__((noinline,noclone)) foo6 (void) { return 6; }
+int __attribute__((noinline,noclone)) foo7 (void) { return 7; }
+int __attribute__((noinline,noclone)) foo8 (void) { return 8; }
+int __attribute__((noinline,noclone)) foo9 (void) { return 9; }
+int __attribute__((noinline,noclone)) foo10 (void) { return 10; }
+int __attribute__((noinline,noclone)) foo11 (void) { return 11; }
+int __attribute__((noinline,noclone)) foo12 (void) { return 12; }
+int __attribute__((noinline,noclone)) foo13 (void) { return 13; }
+int __attribute__((noinline,noclone)) foo14 (void) { return 14; }
+int __attribute__((noinline,noclone)) foo15 (void) { return 15; }
+int __attribute__((noinline,noclone)) foo16 (void) { return 16; }
+int __attribute__((noinline,noclone)) foo17 (void) { return 17; }
+int __attribute__((noinline,noclone)) foo18 (void) { return 18; }
+int __attribute__((noinline,noclone)) foo19 (void) { return 19; }
+int __attribute__((noinline,noclone)) foo20 (void) { return 20; }
+
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ int ret = 0;
+
+ switch (a)
+ {
+ case 1: ret = foo1 (); break;
+ case 2: ret = foo2 (); break;
+ case 3: ret = foo3 (); break;
+ case 4: ret = foo4 (); break;
+ case 5: ret = foo5 (); break;
+ case 6: ret = foo6 (); break;
+ case 7: ret = foo7 (); break;
+ case 8: ret = foo8 (); break;
+ case 9: ret = foo9 (); break;
+ case 10: ret = foo10 (); break;
+ case 11: ret = foo11 (); break;
+ case 12: ret = foo12 (); break;
+ case 13: ret = foo13 (); break;
+ case 14: ret = foo14 (); break;
+ case 15: ret = foo15 (); break;
+ case 16: ret = foo16 (); break;
+ case 17: ret = foo17 (); break;
+ case 18: ret = foo18 (); break;
+ case 19: ret = foo19 (); break;
+ case 20: ret = foo20 (); break;
+ default:
+ __builtin_abort ();
+ }
+
+ return ret;
+}
+
+int
+main ()
+{
+ if (bar (3) != 3)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 1 x bar
+/* { dg-final { scan-assembler-times "exrl" 1 } } */
+
+/* { dg-final { scan-assembler "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_fromreg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_frommem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z900 -mzarch --save-temps -mindirect-branch-jump=thunk-inline -mindirect-branch-table" } */
+
+/* case-values-threshold will be set to 20 by the back-end when jump
+ thunk are requested. */
+
+int __attribute__((noinline,noclone)) foo1 (void) { return 1; }
+int __attribute__((noinline,noclone)) foo2 (void) { return 2; }
+int __attribute__((noinline,noclone)) foo3 (void) { return 3; }
+int __attribute__((noinline,noclone)) foo4 (void) { return 4; }
+int __attribute__((noinline,noclone)) foo5 (void) { return 5; }
+int __attribute__((noinline,noclone)) foo6 (void) { return 6; }
+int __attribute__((noinline,noclone)) foo7 (void) { return 7; }
+int __attribute__((noinline,noclone)) foo8 (void) { return 8; }
+int __attribute__((noinline,noclone)) foo9 (void) { return 9; }
+int __attribute__((noinline,noclone)) foo10 (void) { return 10; }
+int __attribute__((noinline,noclone)) foo11 (void) { return 11; }
+int __attribute__((noinline,noclone)) foo12 (void) { return 12; }
+int __attribute__((noinline,noclone)) foo13 (void) { return 13; }
+int __attribute__((noinline,noclone)) foo14 (void) { return 14; }
+int __attribute__((noinline,noclone)) foo15 (void) { return 15; }
+int __attribute__((noinline,noclone)) foo16 (void) { return 16; }
+int __attribute__((noinline,noclone)) foo17 (void) { return 17; }
+int __attribute__((noinline,noclone)) foo18 (void) { return 18; }
+int __attribute__((noinline,noclone)) foo19 (void) { return 19; }
+int __attribute__((noinline,noclone)) foo20 (void) { return 20; }
+
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ int ret = 0;
+
+ switch (a)
+ {
+ case 1: ret = foo1 (); break;
+ case 2: ret = foo2 (); break;
+ case 3: ret = foo3 (); break;
+ case 4: ret = foo4 (); break;
+ case 5: ret = foo5 (); break;
+ case 6: ret = foo6 (); break;
+ case 7: ret = foo7 (); break;
+ case 8: ret = foo8 (); break;
+ case 9: ret = foo9 (); break;
+ case 10: ret = foo10 (); break;
+ case 11: ret = foo11 (); break;
+ case 12: ret = foo12 (); break;
+ case 13: ret = foo13 (); break;
+ case 14: ret = foo14 (); break;
+ case 15: ret = foo15 (); break;
+ case 16: ret = foo16 (); break;
+ case 17: ret = foo17 (); break;
+ case 18: ret = foo18 (); break;
+ case 19: ret = foo19 (); break;
+ case 20: ret = foo20 (); break;
+ default:
+ __builtin_abort ();
+ }
+
+ return ret;
+}
+
+int
+main ()
+{
+ if (bar (3) != 3)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 1 x bar
+/* { dg-final { scan-assembler-times "\tex\t" 1 } } */
+
+/* { dg-final { scan-assembler "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_fromreg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_frommem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z10 -mzarch --save-temps -mindirect-branch-jump=thunk -mindirect-branch-table" } */
+/* case-values-threshold will be set to 20 by the back-end when jump
+ thunk are requested. */
+
+int __attribute__((noinline,noclone)) foo1 (void) { return 1; }
+int __attribute__((noinline,noclone)) foo2 (void) { return 2; }
+int __attribute__((noinline,noclone)) foo3 (void) { return 3; }
+int __attribute__((noinline,noclone)) foo4 (void) { return 4; }
+int __attribute__((noinline,noclone)) foo5 (void) { return 5; }
+int __attribute__((noinline,noclone)) foo6 (void) { return 6; }
+int __attribute__((noinline,noclone)) foo7 (void) { return 7; }
+int __attribute__((noinline,noclone)) foo8 (void) { return 8; }
+int __attribute__((noinline,noclone)) foo9 (void) { return 9; }
+int __attribute__((noinline,noclone)) foo10 (void) { return 10; }
+int __attribute__((noinline,noclone)) foo11 (void) { return 11; }
+int __attribute__((noinline,noclone)) foo12 (void) { return 12; }
+int __attribute__((noinline,noclone)) foo13 (void) { return 13; }
+int __attribute__((noinline,noclone)) foo14 (void) { return 14; }
+int __attribute__((noinline,noclone)) foo15 (void) { return 15; }
+int __attribute__((noinline,noclone)) foo16 (void) { return 16; }
+int __attribute__((noinline,noclone)) foo17 (void) { return 17; }
+int __attribute__((noinline,noclone)) foo18 (void) { return 18; }
+int __attribute__((noinline,noclone)) foo19 (void) { return 19; }
+int __attribute__((noinline,noclone)) foo20 (void) { return 20; }
+
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ int ret = 0;
+
+ switch (a)
+ {
+ case 1: ret = foo1 (); break;
+ case 2: ret = foo2 (); break;
+ case 3: ret = foo3 (); break;
+ case 4: ret = foo4 (); break;
+ case 5: ret = foo5 (); break;
+ case 6: ret = foo6 (); break;
+ case 7: ret = foo7 (); break;
+ case 8: ret = foo8 (); break;
+ case 9: ret = foo9 (); break;
+ case 10: ret = foo10 (); break;
+ case 11: ret = foo11 (); break;
+ case 12: ret = foo12 (); break;
+ case 13: ret = foo13 (); break;
+ case 14: ret = foo14 (); break;
+ case 15: ret = foo15 (); break;
+ case 16: ret = foo16 (); break;
+ case 17: ret = foo17 (); break;
+ case 18: ret = foo18 (); break;
+ case 19: ret = foo19 (); break;
+ case 20: ret = foo20 (); break;
+ default:
+ __builtin_abort ();
+ }
+
+ return ret;
+}
+
+int
+main ()
+{
+ if (bar (3) != 3)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 1 x bar
+/* { dg-final { scan-assembler-times "exrl" 1 } } */
+
+/* { dg-final { scan-assembler "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_fromreg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_frommem" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-O3 -march=z900 -mzarch --save-temps -mindirect-branch-jump=thunk -mindirect-branch-table" } */
+
+/* case-values-threshold will be set to 20 by the back-end when jump
+ thunk are requested. */
+
+int __attribute__((noinline,noclone)) foo1 (void) { return 1; }
+int __attribute__((noinline,noclone)) foo2 (void) { return 2; }
+int __attribute__((noinline,noclone)) foo3 (void) { return 3; }
+int __attribute__((noinline,noclone)) foo4 (void) { return 4; }
+int __attribute__((noinline,noclone)) foo5 (void) { return 5; }
+int __attribute__((noinline,noclone)) foo6 (void) { return 6; }
+int __attribute__((noinline,noclone)) foo7 (void) { return 7; }
+int __attribute__((noinline,noclone)) foo8 (void) { return 8; }
+int __attribute__((noinline,noclone)) foo9 (void) { return 9; }
+int __attribute__((noinline,noclone)) foo10 (void) { return 10; }
+int __attribute__((noinline,noclone)) foo11 (void) { return 11; }
+int __attribute__((noinline,noclone)) foo12 (void) { return 12; }
+int __attribute__((noinline,noclone)) foo13 (void) { return 13; }
+int __attribute__((noinline,noclone)) foo14 (void) { return 14; }
+int __attribute__((noinline,noclone)) foo15 (void) { return 15; }
+int __attribute__((noinline,noclone)) foo16 (void) { return 16; }
+int __attribute__((noinline,noclone)) foo17 (void) { return 17; }
+int __attribute__((noinline,noclone)) foo18 (void) { return 18; }
+int __attribute__((noinline,noclone)) foo19 (void) { return 19; }
+int __attribute__((noinline,noclone)) foo20 (void) { return 20; }
+
+
+int __attribute__((noinline,noclone))
+bar (int a)
+{
+ int ret = 0;
+
+ switch (a)
+ {
+ case 1: ret = foo1 (); break;
+ case 2: ret = foo2 (); break;
+ case 3: ret = foo3 (); break;
+ case 4: ret = foo4 (); break;
+ case 5: ret = foo5 (); break;
+ case 6: ret = foo6 (); break;
+ case 7: ret = foo7 (); break;
+ case 8: ret = foo8 (); break;
+ case 9: ret = foo9 (); break;
+ case 10: ret = foo10 (); break;
+ case 11: ret = foo11 (); break;
+ case 12: ret = foo12 (); break;
+ case 13: ret = foo13 (); break;
+ case 14: ret = foo14 (); break;
+ case 15: ret = foo15 (); break;
+ case 16: ret = foo16 (); break;
+ case 17: ret = foo17 (); break;
+ case 18: ret = foo18 (); break;
+ case 19: ret = foo19 (); break;
+ case 20: ret = foo20 (); break;
+ default:
+ __builtin_abort ();
+ }
+
+ return ret;
+}
+
+int
+main ()
+{
+ if (bar (3) != 3)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* 1 x bar
+/* { dg-final { scan-assembler-times "ex\t" 1 } } */
+
+/* { dg-final { scan-assembler "section\t.s390_indirect_jump" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_indirect_call" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_fromreg" } } */
+/* { dg-final { scan-assembler-not "section\t.s390_return_frommem" } } */