blob: 85dd78c341806c258722457654c7d13c7aee3a89 [file] [log] [blame]
/* Subroutines used for code generation on IBM S/390 and zSeries
Copyright (C) 1999-2021 Free Software Foundation, Inc.
Contributed by Hartmut Penner (hpenner@de.ibm.com) and
Ulrich Weigand (uweigand@de.ibm.com) and
Andreas Krebbel (Andreas.Krebbel@de.ibm.com).
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3, or (at your option) any later
version.
GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#define IN_TARGET_CODE 1
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "backend.h"
#include "target.h"
#include "target-globals.h"
#include "rtl.h"
#include "tree.h"
#include "gimple.h"
#include "cfghooks.h"
#include "cfgloop.h"
#include "df.h"
#include "memmodel.h"
#include "tm_p.h"
#include "stringpool.h"
#include "attribs.h"
#include "expmed.h"
#include "optabs.h"
#include "regs.h"
#include "emit-rtl.h"
#include "recog.h"
#include "cgraph.h"
#include "diagnostic-core.h"
#include "diagnostic.h"
#include "alias.h"
#include "fold-const.h"
#include "print-tree.h"
#include "stor-layout.h"
#include "varasm.h"
#include "calls.h"
#include "conditions.h"
#include "output.h"
#include "insn-attr.h"
#include "flags.h"
#include "except.h"
#include "dojump.h"
#include "explow.h"
#include "stmt.h"
#include "expr.h"
#include "reload.h"
#include "cfgrtl.h"
#include "cfganal.h"
#include "lcm.h"
#include "cfgbuild.h"
#include "cfgcleanup.h"
#include "debug.h"
#include "langhooks.h"
#include "internal-fn.h"
#include "gimple-fold.h"
#include "tree-eh.h"
#include "gimplify.h"
#include "opts.h"
#include "tree-pass.h"
#include "context.h"
#include "builtins.h"
#include "rtl-iter.h"
#include "intl.h"
#include "tm-constrs.h"
#include "tree-vrp.h"
#include "symbol-summary.h"
#include "ipa-prop.h"
#include "ipa-fnsummary.h"
#include "sched-int.h"
/* This file should be included last. */
#include "target-def.h"
static bool s390_hard_regno_mode_ok (unsigned int, machine_mode);
/* Remember the last target of s390_set_current_function. */
static GTY(()) tree s390_previous_fndecl;
/* Define the specific costs for a given cpu. */
struct processor_costs
{
/* multiplication */
const int m; /* cost of an M instruction. */
const int mghi; /* cost of an MGHI instruction. */
const int mh; /* cost of an MH instruction. */
const int mhi; /* cost of an MHI instruction. */
const int ml; /* cost of an ML instruction. */
const int mr; /* cost of an MR instruction. */
const int ms; /* cost of an MS instruction. */
const int msg; /* cost of an MSG instruction. */
const int msgf; /* cost of an MSGF instruction. */
const int msgfr; /* cost of an MSGFR instruction. */
const int msgr; /* cost of an MSGR instruction. */
const int msr; /* cost of an MSR instruction. */
const int mult_df; /* cost of multiplication in DFmode. */
const int mxbr;
/* square root */
const int sqxbr; /* cost of square root in TFmode. */
const int sqdbr; /* cost of square root in DFmode. */
const int sqebr; /* cost of square root in SFmode. */
/* multiply and add */
const int madbr; /* cost of multiply and add in DFmode. */
const int maebr; /* cost of multiply and add in SFmode. */
/* division */
const int dxbr;
const int ddbr;
const int debr;
const int dlgr;
const int dlr;
const int dr;
const int dsgfr;
const int dsgr;
};
#define s390_cost ((const struct processor_costs *)(s390_cost_pointer))
static const
struct processor_costs z900_cost =
{
COSTS_N_INSNS (5), /* M */
COSTS_N_INSNS (10), /* MGHI */
COSTS_N_INSNS (5), /* MH */
COSTS_N_INSNS (4), /* MHI */
COSTS_N_INSNS (5), /* ML */
COSTS_N_INSNS (5), /* MR */
COSTS_N_INSNS (4), /* MS */
COSTS_N_INSNS (15), /* MSG */
COSTS_N_INSNS (7), /* MSGF */
COSTS_N_INSNS (7), /* MSGFR */
COSTS_N_INSNS (10), /* MSGR */
COSTS_N_INSNS (4), /* MSR */
COSTS_N_INSNS (7), /* multiplication in DFmode */
COSTS_N_INSNS (13), /* MXBR */
COSTS_N_INSNS (136), /* SQXBR */
COSTS_N_INSNS (44), /* SQDBR */
COSTS_N_INSNS (35), /* SQEBR */
COSTS_N_INSNS (18), /* MADBR */
COSTS_N_INSNS (13), /* MAEBR */
COSTS_N_INSNS (134), /* DXBR */
COSTS_N_INSNS (30), /* DDBR */
COSTS_N_INSNS (27), /* DEBR */
COSTS_N_INSNS (220), /* DLGR */
COSTS_N_INSNS (34), /* DLR */
COSTS_N_INSNS (34), /* DR */
COSTS_N_INSNS (32), /* DSGFR */
COSTS_N_INSNS (32), /* DSGR */
};
static const
struct processor_costs z990_cost =
{
COSTS_N_INSNS (4), /* M */
COSTS_N_INSNS (2), /* MGHI */
COSTS_N_INSNS (2), /* MH */
COSTS_N_INSNS (2), /* MHI */
COSTS_N_INSNS (4), /* ML */
COSTS_N_INSNS (4), /* MR */
COSTS_N_INSNS (5), /* MS */
COSTS_N_INSNS (6), /* MSG */
COSTS_N_INSNS (4), /* MSGF */
COSTS_N_INSNS (4), /* MSGFR */
COSTS_N_INSNS (4), /* MSGR */
COSTS_N_INSNS (4), /* MSR */
COSTS_N_INSNS (1), /* multiplication in DFmode */
COSTS_N_INSNS (28), /* MXBR */
COSTS_N_INSNS (130), /* SQXBR */
COSTS_N_INSNS (66), /* SQDBR */
COSTS_N_INSNS (38), /* SQEBR */
COSTS_N_INSNS (1), /* MADBR */
COSTS_N_INSNS (1), /* MAEBR */
COSTS_N_INSNS (60), /* DXBR */
COSTS_N_INSNS (40), /* DDBR */
COSTS_N_INSNS (26), /* DEBR */
COSTS_N_INSNS (176), /* DLGR */
COSTS_N_INSNS (31), /* DLR */
COSTS_N_INSNS (31), /* DR */
COSTS_N_INSNS (31), /* DSGFR */
COSTS_N_INSNS (31), /* DSGR */
};
static const
struct processor_costs z9_109_cost =
{
COSTS_N_INSNS (4), /* M */
COSTS_N_INSNS (2), /* MGHI */
COSTS_N_INSNS (2), /* MH */
COSTS_N_INSNS (2), /* MHI */
COSTS_N_INSNS (4), /* ML */
COSTS_N_INSNS (4), /* MR */
COSTS_N_INSNS (5), /* MS */
COSTS_N_INSNS (6), /* MSG */
COSTS_N_INSNS (4), /* MSGF */
COSTS_N_INSNS (4), /* MSGFR */
COSTS_N_INSNS (4), /* MSGR */
COSTS_N_INSNS (4), /* MSR */
COSTS_N_INSNS (1), /* multiplication in DFmode */
COSTS_N_INSNS (28), /* MXBR */
COSTS_N_INSNS (130), /* SQXBR */
COSTS_N_INSNS (66), /* SQDBR */
COSTS_N_INSNS (38), /* SQEBR */
COSTS_N_INSNS (1), /* MADBR */
COSTS_N_INSNS (1), /* MAEBR */
COSTS_N_INSNS (60), /* DXBR */
COSTS_N_INSNS (40), /* DDBR */
COSTS_N_INSNS (26), /* DEBR */
COSTS_N_INSNS (30), /* DLGR */
COSTS_N_INSNS (23), /* DLR */
COSTS_N_INSNS (23), /* DR */
COSTS_N_INSNS (24), /* DSGFR */
COSTS_N_INSNS (24), /* DSGR */
};
static const
struct processor_costs z10_cost =
{
COSTS_N_INSNS (10), /* M */
COSTS_N_INSNS (10), /* MGHI */
COSTS_N_INSNS (10), /* MH */
COSTS_N_INSNS (10), /* MHI */
COSTS_N_INSNS (10), /* ML */
COSTS_N_INSNS (10), /* MR */
COSTS_N_INSNS (10), /* MS */
COSTS_N_INSNS (10), /* MSG */
COSTS_N_INSNS (10), /* MSGF */
COSTS_N_INSNS (10), /* MSGFR */
COSTS_N_INSNS (10), /* MSGR */
COSTS_N_INSNS (10), /* MSR */
COSTS_N_INSNS (1) , /* multiplication in DFmode */
COSTS_N_INSNS (50), /* MXBR */
COSTS_N_INSNS (120), /* SQXBR */
COSTS_N_INSNS (52), /* SQDBR */
COSTS_N_INSNS (38), /* SQEBR */
COSTS_N_INSNS (1), /* MADBR */
COSTS_N_INSNS (1), /* MAEBR */
COSTS_N_INSNS (111), /* DXBR */
COSTS_N_INSNS (39), /* DDBR */
COSTS_N_INSNS (32), /* DEBR */
COSTS_N_INSNS (160), /* DLGR */
COSTS_N_INSNS (71), /* DLR */
COSTS_N_INSNS (71), /* DR */
COSTS_N_INSNS (71), /* DSGFR */
COSTS_N_INSNS (71), /* DSGR */
};
static const
struct processor_costs z196_cost =
{
COSTS_N_INSNS (7), /* M */
COSTS_N_INSNS (5), /* MGHI */
COSTS_N_INSNS (5), /* MH */
COSTS_N_INSNS (5), /* MHI */
COSTS_N_INSNS (7), /* ML */
COSTS_N_INSNS (7), /* MR */
COSTS_N_INSNS (6), /* MS */
COSTS_N_INSNS (8), /* MSG */
COSTS_N_INSNS (6), /* MSGF */
COSTS_N_INSNS (6), /* MSGFR */
COSTS_N_INSNS (8), /* MSGR */
COSTS_N_INSNS (6), /* MSR */
COSTS_N_INSNS (1) , /* multiplication in DFmode */
COSTS_N_INSNS (40), /* MXBR B+40 */
COSTS_N_INSNS (100), /* SQXBR B+100 */
COSTS_N_INSNS (42), /* SQDBR B+42 */
COSTS_N_INSNS (28), /* SQEBR B+28 */
COSTS_N_INSNS (1), /* MADBR B */
COSTS_N_INSNS (1), /* MAEBR B */
COSTS_N_INSNS (101), /* DXBR B+101 */
COSTS_N_INSNS (29), /* DDBR */
COSTS_N_INSNS (22), /* DEBR */
COSTS_N_INSNS (160), /* DLGR cracked */
COSTS_N_INSNS (160), /* DLR cracked */
COSTS_N_INSNS (160), /* DR expanded */
COSTS_N_INSNS (160), /* DSGFR cracked */
COSTS_N_INSNS (160), /* DSGR cracked */
};
static const
struct processor_costs zEC12_cost =
{
COSTS_N_INSNS (7), /* M */
COSTS_N_INSNS (5), /* MGHI */
COSTS_N_INSNS (5), /* MH */
COSTS_N_INSNS (5), /* MHI */
COSTS_N_INSNS (7), /* ML */
COSTS_N_INSNS (7), /* MR */
COSTS_N_INSNS (6), /* MS */
COSTS_N_INSNS (8), /* MSG */
COSTS_N_INSNS (6), /* MSGF */
COSTS_N_INSNS (6), /* MSGFR */
COSTS_N_INSNS (8), /* MSGR */
COSTS_N_INSNS (6), /* MSR */
COSTS_N_INSNS (1) , /* multiplication in DFmode */
COSTS_N_INSNS (40), /* MXBR B+40 */
COSTS_N_INSNS (100), /* SQXBR B+100 */
COSTS_N_INSNS (42), /* SQDBR B+42 */
COSTS_N_INSNS (28), /* SQEBR B+28 */
COSTS_N_INSNS (1), /* MADBR B */
COSTS_N_INSNS (1), /* MAEBR B */
COSTS_N_INSNS (131), /* DXBR B+131 */
COSTS_N_INSNS (29), /* DDBR */
COSTS_N_INSNS (22), /* DEBR */
COSTS_N_INSNS (160), /* DLGR cracked */
COSTS_N_INSNS (160), /* DLR cracked */
COSTS_N_INSNS (160), /* DR expanded */
COSTS_N_INSNS (160), /* DSGFR cracked */
COSTS_N_INSNS (160), /* DSGR cracked */
};
const struct s390_processor processor_table[] =
{
{ "z900", "z900", PROCESSOR_2064_Z900, &z900_cost, 5 },
{ "z990", "z990", PROCESSOR_2084_Z990, &z990_cost, 6 },
{ "z9-109", "z9-109", PROCESSOR_2094_Z9_109, &z9_109_cost, 7 },
{ "z9-ec", "z9-ec", PROCESSOR_2094_Z9_EC, &z9_109_cost, 7 },
{ "z10", "z10", PROCESSOR_2097_Z10, &z10_cost, 8 },
{ "z196", "z196", PROCESSOR_2817_Z196, &z196_cost, 9 },
{ "zEC12", "zEC12", PROCESSOR_2827_ZEC12, &zEC12_cost, 10 },
{ "z13", "z13", PROCESSOR_2964_Z13, &zEC12_cost, 11 },
{ "z14", "arch12", PROCESSOR_3906_Z14, &zEC12_cost, 12 },
{ "z15", "arch13", PROCESSOR_8561_Z15, &zEC12_cost, 13 },
{ "arch14", "arch14", PROCESSOR_ARCH14, &zEC12_cost, 14 },
{ "native", "", PROCESSOR_NATIVE, NULL, 0 }
};
extern int reload_completed;
/* Kept up to date using the SCHED_VARIABLE_ISSUE hook. */
static rtx_insn *last_scheduled_insn;
#define NUM_SIDES 2
#define MAX_SCHED_UNITS 4
static int last_scheduled_unit_distance[MAX_SCHED_UNITS][NUM_SIDES];
/* Estimate of number of cycles a long-running insn occupies an
execution unit. */
static int fxd_longrunning[NUM_SIDES];
static int fpd_longrunning[NUM_SIDES];
/* The maximum score added for an instruction whose unit hasn't been
in use for MAX_SCHED_MIX_DISTANCE steps. Increase this value to
give instruction mix scheduling more priority over instruction
grouping. */
#define MAX_SCHED_MIX_SCORE 2
/* The maximum distance up to which individual scores will be
calculated. Everything beyond this gives MAX_SCHED_MIX_SCORE.
Increase this with the OOO windows size of the machine. */
#define MAX_SCHED_MIX_DISTANCE 70
/* Structure used to hold the components of a S/390 memory
address. A legitimate address on S/390 is of the general
form
base + index + displacement
where any of the components is optional.
base and index are registers of the class ADDR_REGS,
displacement is an unsigned 12-bit immediate constant. */
/* The max number of insns of backend generated memset/memcpy/memcmp
loops. This value is used in the unroll adjust hook to detect such
loops. Current max is 9 coming from the memcmp loop. */
#define BLOCK_MEM_OPS_LOOP_INSNS 9
struct s390_address
{
rtx base;
rtx indx;
rtx disp;
bool pointer;
bool literal_pool;
};
/* Few accessor macros for struct cfun->machine->s390_frame_layout. */
#define cfun_frame_layout (cfun->machine->frame_layout)
#define cfun_save_high_fprs_p (!!cfun_frame_layout.high_fprs)
#define cfun_save_arg_fprs_p (!!(TARGET_64BIT \
? cfun_frame_layout.fpr_bitmap & 0x0f \
: cfun_frame_layout.fpr_bitmap & 0x03))
#define cfun_gprs_save_area_size ((cfun_frame_layout.last_save_gpr_slot - \
cfun_frame_layout.first_save_gpr_slot + 1) * UNITS_PER_LONG)
#define cfun_set_fpr_save(REGNO) (cfun->machine->frame_layout.fpr_bitmap |= \
(1 << (REGNO - FPR0_REGNUM)))
#define cfun_fpr_save_p(REGNO) (!!(cfun->machine->frame_layout.fpr_bitmap & \
(1 << (REGNO - FPR0_REGNUM))))
#define cfun_gpr_save_slot(REGNO) \
cfun->machine->frame_layout.gpr_save_slots[REGNO]
/* Number of GPRs and FPRs used for argument passing. */
#define GP_ARG_NUM_REG 5
#define FP_ARG_NUM_REG (TARGET_64BIT? 4 : 2)
#define VEC_ARG_NUM_REG 8
/* A couple of shortcuts. */
#define CONST_OK_FOR_J(x) \
CONST_OK_FOR_CONSTRAINT_P((x), 'J', "J")
#define CONST_OK_FOR_K(x) \
CONST_OK_FOR_CONSTRAINT_P((x), 'K', "K")
#define CONST_OK_FOR_Os(x) \
CONST_OK_FOR_CONSTRAINT_P((x), 'O', "Os")
#define CONST_OK_FOR_Op(x) \
CONST_OK_FOR_CONSTRAINT_P((x), 'O', "Op")
#define CONST_OK_FOR_On(x) \
CONST_OK_FOR_CONSTRAINT_P((x), 'O', "On")
#define REGNO_PAIR_OK(REGNO, MODE) \
(s390_hard_regno_nregs ((REGNO), (MODE)) == 1 || !((REGNO) & 1))
/* That's the read ahead of the dynamic branch prediction unit in
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;
}
/* Return nonzero if it's OK to use fused multiply-add for MODE. */
bool
s390_fma_allowed_p (machine_mode mode)
{
if (TARGET_VXE && mode == TFmode)
return flag_vx_long_double_fma;
return true;
}
/* Indicate which ABI has been used for passing vector args.
0 - no vector type arguments have been passed where the ABI is relevant
1 - the old ABI has been used
2 - a vector type argument has been passed either in a vector register
or on the stack by value */
static int s390_vector_abi = 0;
/* Set the vector ABI marker if TYPE is subject to the vector ABI
switch. The vector ABI affects only vector data types. There are
two aspects of the vector ABI relevant here:
1. vectors >= 16 bytes have an alignment of 8 bytes with the new
ABI and natural alignment with the old.
2. vector <= 16 bytes are passed in VRs or by value on the stack
with the new ABI but by reference on the stack with the old.
If ARG_P is true TYPE is used for a function argument or return
value. The ABI marker then is set for all vector data types. If
ARG_P is false only type 1 vectors are being checked. */
static void
s390_check_type_for_vector_abi (const_tree type, bool arg_p, bool in_struct_p)
{
static hash_set<const_tree> visited_types_hash;
if (s390_vector_abi)
return;
if (type == NULL_TREE || TREE_CODE (type) == ERROR_MARK)
return;
if (visited_types_hash.contains (type))
return;
visited_types_hash.add (type);
if (VECTOR_TYPE_P (type))
{
int type_size = int_size_in_bytes (type);
/* Outside arguments only the alignment is changing and this
only happens for vector types >= 16 bytes. */
if (!arg_p && type_size < 16)
return;
/* In arguments vector types > 16 are passed as before (GCC
never enforced the bigger alignment for arguments which was
required by the old vector ABI). However, it might still be
ABI relevant due to the changed alignment if it is a struct
member. */
if (arg_p && type_size > 16 && !in_struct_p)
return;
s390_vector_abi = TARGET_VX_ABI ? 2 : 1;
}
else if (POINTER_TYPE_P (type) || TREE_CODE (type) == ARRAY_TYPE)
{
/* ARRAY_TYPE: Since with neither of the ABIs we have more than
natural alignment there will never be ABI dependent padding
in an array type. That's why we do not set in_struct_p to
true here. */
s390_check_type_for_vector_abi (TREE_TYPE (type), arg_p, in_struct_p);
}
else if (TREE_CODE (type) == FUNCTION_TYPE || TREE_CODE (type) == METHOD_TYPE)
{
tree arg_chain;
/* Check the return type. */
s390_check_type_for_vector_abi (TREE_TYPE (type), true, false);
for (arg_chain = TYPE_ARG_TYPES (type);
arg_chain;
arg_chain = TREE_CHAIN (arg_chain))
s390_check_type_for_vector_abi (TREE_VALUE (arg_chain), true, false);
}
else if (RECORD_OR_UNION_TYPE_P (type))
{
tree field;
for (field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
{
if (TREE_CODE (field) != FIELD_DECL)
continue;
s390_check_type_for_vector_abi (TREE_TYPE (field), arg_p, true);
}
}
}
/* System z builtins. */
#include "s390-builtins.h"
const unsigned int bflags_builtin[S390_BUILTIN_MAX + 1] =
{
#undef B_DEF
#undef OB_DEF
#undef OB_DEF_VAR
#define B_DEF(NAME, PATTERN, ATTRS, BFLAGS, ...) BFLAGS,
#define OB_DEF(...)
#define OB_DEF_VAR(...)
#include "s390-builtins.def"
0
};
const unsigned int opflags_builtin[S390_BUILTIN_MAX + 1] =
{
#undef B_DEF
#undef OB_DEF
#undef OB_DEF_VAR
#define B_DEF(NAME, PATTERN, ATTRS, BFLAGS, OPFLAGS, ...) OPFLAGS,
#define OB_DEF(...)
#define OB_DEF_VAR(...)
#include "s390-builtins.def"
0
};
const unsigned int bflags_overloaded_builtin[S390_OVERLOADED_BUILTIN_MAX + 1] =
{
#undef B_DEF
#undef OB_DEF
#undef OB_DEF_VAR
#define B_DEF(...)
#define OB_DEF(NAME, FIRST_VAR_NAME, LAST_VAR_NAME, BFLAGS, ...) BFLAGS,
#define OB_DEF_VAR(...)
#include "s390-builtins.def"
0
};
const unsigned int
bflags_overloaded_builtin_var[S390_OVERLOADED_BUILTIN_VAR_MAX + 1] =
{
#undef B_DEF
#undef OB_DEF
#undef OB_DEF_VAR
#define B_DEF(...)
#define OB_DEF(...)
#define OB_DEF_VAR(NAME, PATTERN, FLAGS, OPFLAGS, FNTYPE) FLAGS,
#include "s390-builtins.def"
0
};
const unsigned int
opflags_overloaded_builtin_var[S390_OVERLOADED_BUILTIN_VAR_MAX + 1] =
{
#undef B_DEF
#undef OB_DEF
#undef OB_DEF_VAR
#define B_DEF(...)
#define OB_DEF(...)
#define OB_DEF_VAR(NAME, PATTERN, FLAGS, OPFLAGS, FNTYPE) OPFLAGS,
#include "s390-builtins.def"
0
};
tree s390_builtin_types[BT_MAX];
tree s390_builtin_fn_types[BT_FN_MAX];
tree s390_builtin_decls[S390_BUILTIN_MAX +
S390_OVERLOADED_BUILTIN_MAX +
S390_OVERLOADED_BUILTIN_VAR_MAX];
static enum insn_code const code_for_builtin[S390_BUILTIN_MAX + 1] = {
#undef B_DEF
#undef OB_DEF
#undef OB_DEF_VAR
#define B_DEF(NAME, PATTERN, ...) CODE_FOR_##PATTERN,
#define OB_DEF(...)
#define OB_DEF_VAR(...)
#include "s390-builtins.def"
CODE_FOR_nothing
};
static void
s390_init_builtins (void)
{
/* These definitions are being used in s390-builtins.def. */
tree returns_twice_attr = tree_cons (get_identifier ("returns_twice"),
NULL, NULL);
tree noreturn_attr = tree_cons (get_identifier ("noreturn"), NULL, NULL);
tree c_uint64_type_node;
/* The uint64_type_node from tree.c is not compatible to the C99
uint64_t data type. What we want is c_uint64_type_node from
c-common.c. But since backend code is not supposed to interface
with the frontend we recreate it here. */
if (TARGET_64BIT)
c_uint64_type_node = long_unsigned_type_node;
else
c_uint64_type_node = long_long_unsigned_type_node;
#undef DEF_TYPE
#define DEF_TYPE(INDEX, NODE, CONST_P) \
if (s390_builtin_types[INDEX] == NULL) \
s390_builtin_types[INDEX] = (!CONST_P) ? \
(NODE) : build_type_variant ((NODE), 1, 0);
#undef DEF_POINTER_TYPE
#define DEF_POINTER_TYPE(INDEX, INDEX_BASE) \
if (s390_builtin_types[INDEX] == NULL) \
s390_builtin_types[INDEX] = \
build_pointer_type (s390_builtin_types[INDEX_BASE]);
#undef DEF_DISTINCT_TYPE
#define DEF_DISTINCT_TYPE(INDEX, INDEX_BASE) \
if (s390_builtin_types[INDEX] == NULL) \
s390_builtin_types[INDEX] = \
build_distinct_type_copy (s390_builtin_types[INDEX_BASE]);
#undef DEF_VECTOR_TYPE
#define DEF_VECTOR_TYPE(INDEX, INDEX_BASE, ELEMENTS) \
if (s390_builtin_types[INDEX] == NULL) \
s390_builtin_types[INDEX] = \
build_vector_type (s390_builtin_types[INDEX_BASE], ELEMENTS);
#undef DEF_OPAQUE_VECTOR_TYPE
#define DEF_OPAQUE_VECTOR_TYPE(INDEX, INDEX_BASE, ELEMENTS) \
if (s390_builtin_types[INDEX] == NULL) \
s390_builtin_types[INDEX] = \
build_opaque_vector_type (s390_builtin_types[INDEX_BASE], ELEMENTS);
#undef DEF_FN_TYPE
#define DEF_FN_TYPE(INDEX, args...) \
if (s390_builtin_fn_types[INDEX] == NULL) \
s390_builtin_fn_types[INDEX] = \
build_function_type_list (args, NULL_TREE);
#undef DEF_OV_TYPE
#define DEF_OV_TYPE(...)
#include "s390-builtin-types.def"
#undef B_DEF
#define B_DEF(NAME, PATTERN, ATTRS, BFLAGS, OPFLAGS, FNTYPE) \
if (s390_builtin_decls[S390_BUILTIN_##NAME] == NULL) \
s390_builtin_decls[S390_BUILTIN_##NAME] = \
add_builtin_function ("__builtin_" #NAME, \
s390_builtin_fn_types[FNTYPE], \
S390_BUILTIN_##NAME, \
BUILT_IN_MD, \
NULL, \
ATTRS);
#undef OB_DEF
#define OB_DEF(NAME, FIRST_VAR_NAME, LAST_VAR_NAME, BFLAGS, FNTYPE) \
if (s390_builtin_decls[S390_OVERLOADED_BUILTIN_##NAME + S390_BUILTIN_MAX] \
== NULL) \
s390_builtin_decls[S390_OVERLOADED_BUILTIN_##NAME + S390_BUILTIN_MAX] = \
add_builtin_function ("__builtin_" #NAME, \
s390_builtin_fn_types[FNTYPE], \
S390_OVERLOADED_BUILTIN_##NAME + S390_BUILTIN_MAX, \
BUILT_IN_MD, \
NULL, \
0);
#undef OB_DEF_VAR
#define OB_DEF_VAR(...)
#include "s390-builtins.def"
}
/* Return true if ARG is appropriate as argument number ARGNUM of
builtin DECL. The operand flags from s390-builtins.def have to
passed as OP_FLAGS. */
bool
s390_const_operand_ok (tree arg, int argnum, int op_flags, tree decl)
{
if (O_UIMM_P (op_flags))
{
unsigned HOST_WIDE_INT bitwidths[] = { 1, 2, 3, 4, 5, 8, 12, 16, 32, 4 };
unsigned HOST_WIDE_INT bitmasks[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 12 };
unsigned HOST_WIDE_INT bitwidth = bitwidths[op_flags - O_U1];
unsigned HOST_WIDE_INT bitmask = bitmasks[op_flags - O_U1];
gcc_assert(ARRAY_SIZE(bitwidths) == (O_M12 - O_U1 + 1));
gcc_assert(ARRAY_SIZE(bitmasks) == (O_M12 - O_U1 + 1));
if (!tree_fits_uhwi_p (arg)
|| tree_to_uhwi (arg) > (HOST_WIDE_INT_1U << bitwidth) - 1
|| (bitmask && tree_to_uhwi (arg) & ~bitmask))
{
if (bitmask)
{
gcc_assert (bitmask < 16);
char values[120] = "";
for (unsigned HOST_WIDE_INT i = 0; i <= bitmask; i++)
{
char buf[5];
if (i & ~bitmask)
continue;
int ret = snprintf (buf, 5, HOST_WIDE_INT_PRINT_UNSIGNED, i & bitmask);
gcc_assert (ret < 5);
strcat (values, buf);
if (i < bitmask)
strcat (values, ", ");
}
error ("constant argument %d for builtin %qF is invalid (%s)",
argnum, decl, values);
}
else
error ("constant argument %d for builtin %qF is out of range (0..%wu)",
argnum, decl, (HOST_WIDE_INT_1U << bitwidth) - 1);
return false;
}
}
if (O_SIMM_P (op_flags))
{
int bitwidths[] = { 2, 3, 4, 5, 8, 12, 16, 32 };
int bitwidth = bitwidths[op_flags - O_S2];
if (!tree_fits_shwi_p (arg)
|| tree_to_shwi (arg) < -(HOST_WIDE_INT_1 << (bitwidth - 1))
|| tree_to_shwi (arg) > ((HOST_WIDE_INT_1 << (bitwidth - 1)) - 1))
{
error ("constant argument %d for builtin %qF is out of range "
"(%wd..%wd)", argnum, decl,
-(HOST_WIDE_INT_1 << (bitwidth - 1)),
(HOST_WIDE_INT_1 << (bitwidth - 1)) - 1);
return false;
}
}
return true;
}
/* Expand an expression EXP that calls a built-in function,
with result going to TARGET if that's convenient
(and in mode MODE if that's convenient).
SUBTARGET may be used as the target for computing one of EXP's operands.
IGNORE is nonzero if the value is to be ignored. */
static rtx
s390_expand_builtin (tree exp, rtx target, rtx subtarget ATTRIBUTE_UNUSED,
machine_mode mode ATTRIBUTE_UNUSED,
int ignore ATTRIBUTE_UNUSED)
{
#define MAX_ARGS 6
tree fndecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0);
unsigned int fcode = DECL_MD_FUNCTION_CODE (fndecl);
enum insn_code icode;
rtx op[MAX_ARGS], pat;
int arity;
bool nonvoid;
tree arg;
call_expr_arg_iterator iter;
unsigned int all_op_flags = opflags_for_builtin (fcode);
machine_mode last_vec_mode = VOIDmode;
if (TARGET_DEBUG_ARG)
{
fprintf (stderr,
"s390_expand_builtin, code = %4d, %s, bflags = 0x%x\n",
(int)fcode, IDENTIFIER_POINTER (DECL_NAME (fndecl)),
bflags_for_builtin (fcode));
}
if (S390_USE_TARGET_ATTRIBUTE)
{
unsigned int bflags;
bflags = bflags_for_builtin (fcode);
if ((bflags & B_HTM) && !TARGET_HTM)
{
error ("builtin %qF is not supported without %<-mhtm%> "
"(default with %<-march=zEC12%> and higher).", fndecl);
return const0_rtx;
}
if (((bflags & B_VX) || (bflags & B_VXE)) && !TARGET_VX)
{
error ("builtin %qF requires %<-mvx%> "
"(default with %<-march=z13%> and higher).", fndecl);
return const0_rtx;
}
if ((bflags & B_VXE) && !TARGET_VXE)
{
error ("Builtin %qF requires z14 or higher.", fndecl);
return const0_rtx;
}
if ((bflags & B_VXE2) && !TARGET_VXE2)
{
error ("Builtin %qF requires z15 or higher.", fndecl);
return const0_rtx;
}
if ((bflags & B_NNPA) && !TARGET_NNPA)
{
error ("Builtin %qF requires arch14 or higher.", fndecl);
return const0_rtx;
}
}
if (fcode >= S390_OVERLOADED_BUILTIN_VAR_OFFSET
&& fcode < S390_ALL_BUILTIN_MAX)
{
gcc_unreachable ();
}
else if (fcode < S390_OVERLOADED_BUILTIN_OFFSET)
{
icode = code_for_builtin[fcode];
/* Set a flag in the machine specific cfun part in order to support
saving/restoring of FPRs. */
if (fcode == S390_BUILTIN_tbegin || fcode == S390_BUILTIN_tbegin_retry)
cfun->machine->tbegin_p = true;
}
else if (fcode < S390_OVERLOADED_BUILTIN_VAR_OFFSET)
{
error ("unresolved overloaded builtin");
return const0_rtx;
}
else
internal_error ("bad builtin fcode");
if (icode == 0)
internal_error ("bad builtin icode");
nonvoid = TREE_TYPE (TREE_TYPE (fndecl)) != void_type_node;
if (nonvoid)
{
machine_mode tmode = insn_data[icode].operand[0].mode;
if (!target
|| GET_MODE (target) != tmode
|| !(*insn_data[icode].operand[0].predicate) (target, tmode))
target = gen_reg_rtx (tmode);
/* There are builtins (e.g. vec_promote) with no vector
arguments but an element selector. So we have to also look
at the vector return type when emitting the modulo
operation. */
if (VECTOR_MODE_P (insn_data[icode].operand[0].mode))
last_vec_mode = insn_data[icode].operand[0].mode;
}
arity = 0;
FOR_EACH_CALL_EXPR_ARG (arg, iter, exp)
{
rtx tmp_rtx;
const struct insn_operand_data *insn_op;
unsigned int op_flags = all_op_flags & ((1 << O_SHIFT) - 1);
all_op_flags = all_op_flags >> O_SHIFT;
if (arg == error_mark_node)
return NULL_RTX;
if (arity >= MAX_ARGS)
return NULL_RTX;
if (O_IMM_P (op_flags)
&& TREE_CODE (arg) != INTEGER_CST)
{
error ("constant value required for builtin %qF argument %d",
fndecl, arity + 1);
return const0_rtx;
}
if (!s390_const_operand_ok (arg, arity + 1, op_flags, fndecl))
return const0_rtx;
insn_op = &insn_data[icode].operand[arity + nonvoid];
op[arity] = expand_expr (arg, NULL_RTX, insn_op->mode, EXPAND_NORMAL);
/* expand_expr truncates constants to the target mode only if it
is "convenient". However, our checks below rely on this
being done. */
if (CONST_INT_P (op[arity])
&& SCALAR_INT_MODE_P (insn_op->mode)
&& GET_MODE (op[arity]) != insn_op->mode)
op[arity] = GEN_INT (trunc_int_for_mode (INTVAL (op[arity]),
insn_op->mode));
/* Wrap the expanded RTX for pointer types into a MEM expr with
the proper mode. This allows us to use e.g. (match_operand
"memory_operand"..) in the insn patterns instead of (mem
(match_operand "address_operand)). This is helpful for
patterns not just accepting MEMs. */
if (POINTER_TYPE_P (TREE_TYPE (arg))
&& insn_op->predicate != address_operand)
op[arity] = gen_rtx_MEM (insn_op->mode, op[arity]);
/* Expand the module operation required on element selectors. */
if (op_flags == O_ELEM)
{
gcc_assert (last_vec_mode != VOIDmode);
op[arity] = simplify_expand_binop (SImode, code_to_optab (AND),
op[arity],
GEN_INT (GET_MODE_NUNITS (last_vec_mode) - 1),
NULL_RTX, 1, OPTAB_DIRECT);
}
/* Record the vector mode used for an element selector. This assumes:
1. There is no builtin with two different vector modes and an element selector
2. The element selector comes after the vector type it is referring to.
This currently the true for all the builtins but FIXME we
should better check for that. */
if (VECTOR_MODE_P (insn_op->mode))
last_vec_mode = insn_op->mode;
if (insn_op->predicate (op[arity], insn_op->mode))
{
arity++;
continue;
}
/* A memory operand is rejected by the memory_operand predicate.
Try making the address legal by copying it into a register. */
if (MEM_P (op[arity])
&& insn_op->predicate == memory_operand
&& (GET_MODE (XEXP (op[arity], 0)) == Pmode
|| GET_MODE (XEXP (op[arity], 0)) == VOIDmode))
{
op[arity] = replace_equiv_address (op[arity],
copy_to_mode_reg (Pmode,
XEXP (op[arity], 0)));
}
/* Some of the builtins require different modes/types than the
pattern in order to implement a specific API. Instead of
adding many expanders which do the mode change we do it here.
E.g. s390_vec_add_u128 required to have vector unsigned char
arguments is mapped to addti3. */
else if (insn_op->mode != VOIDmode
&& GET_MODE (op[arity]) != VOIDmode
&& GET_MODE (op[arity]) != insn_op->mode
&& ((tmp_rtx = simplify_gen_subreg (insn_op->mode, op[arity],
GET_MODE (op[arity]), 0))
!= NULL_RTX))
{
op[arity] = tmp_rtx;
}
/* The predicate rejects the operand although the mode is fine.
Copy the operand to register. */
if (!insn_op->predicate (op[arity], insn_op->mode)
&& (GET_MODE (op[arity]) == insn_op->mode
|| GET_MODE (op[arity]) == VOIDmode
|| (insn_op->predicate == address_operand
&& GET_MODE (op[arity]) == Pmode)))
{
/* An address_operand usually has VOIDmode in the expander
so we cannot use this. */
machine_mode target_mode =
(insn_op->predicate == address_operand
? (machine_mode) Pmode : insn_op->mode);
op[arity] = copy_to_mode_reg (target_mode, op[arity]);
}
if (!insn_op->predicate (op[arity], insn_op->mode))
{
error ("invalid argument %d for builtin %qF", arity + 1, fndecl);
return const0_rtx;
}
arity++;
}
switch (arity)
{
case 0:
pat = GEN_FCN (icode) (target);
break;
case 1:
if (nonvoid)
pat = GEN_FCN (icode) (target, op[0]);
else
pat = GEN_FCN (icode) (op[0]);
break;
case 2:
if (nonvoid)
pat = GEN_FCN (icode) (target, op[0], op[1]);
else
pat = GEN_FCN (icode) (op[0], op[1]);
break;
case 3:
if (nonvoid)
pat = GEN_FCN (icode) (target, op[0], op[1], op[2]);
else
pat = GEN_FCN (icode) (op[0], op[1], op[2]);
break;
case 4:
if (nonvoid)
pat = GEN_FCN (icode) (target, op[0], op[1], op[2], op[3]);
else
pat = GEN_FCN (icode) (op[0], op[1], op[2], op[3]);
break;
case 5:
if (nonvoid)
pat = GEN_FCN (icode) (target, op[0], op[1], op[2], op[3], op[4]);
else
pat = GEN_FCN (icode) (op[0], op[1], op[2], op[3], op[4]);
break;
case 6:
if (nonvoid)
pat = GEN_FCN (icode) (target, op[0], op[1], op[2], op[3], op[4], op[5]);
else
pat = GEN_FCN (icode) (op[0], op[1], op[2], op[3], op[4], op[5]);
break;
default:
gcc_unreachable ();
}
if (!pat)
return NULL_RTX;
emit_insn (pat);
if (nonvoid)
return target;
else
return const0_rtx;
}
static const int s390_hotpatch_hw_max = 1000000;
static int s390_hotpatch_hw_before_label = 0;
static int s390_hotpatch_hw_after_label = 0;
/* Check whether the hotpatch attribute is applied to a function and, if it has
an argument, the argument is valid. */
static tree
s390_handle_hotpatch_attribute (tree *node, tree name, tree args,
int flags ATTRIBUTE_UNUSED, bool *no_add_attrs)
{
tree expr;
tree expr2;
int err;
if (TREE_CODE (*node) != FUNCTION_DECL)
{
warning (OPT_Wattributes, "%qE attribute only applies to functions",
name);
*no_add_attrs = true;
}
if (args != NULL && TREE_CHAIN (args) != NULL)
{
expr = TREE_VALUE (args);
expr2 = TREE_VALUE (TREE_CHAIN (args));
}
if (args == NULL || TREE_CHAIN (args) == NULL)
err = 1;
else if (TREE_CODE (expr) != INTEGER_CST
|| !INTEGRAL_TYPE_P (TREE_TYPE (expr))
|| wi::gtu_p (wi::to_wide (expr), s390_hotpatch_hw_max))
err = 1;
else if (TREE_CODE (expr2) != INTEGER_CST
|| !INTEGRAL_TYPE_P (TREE_TYPE (expr2))
|| wi::gtu_p (wi::to_wide (expr2), s390_hotpatch_hw_max))
err = 1;
else
err = 0;
if (err)
{
error ("requested %qE attribute is not a comma separated pair of"
" non-negative integer constants or too large (max. %d)", name,
s390_hotpatch_hw_max);
*no_add_attrs = true;
}
return NULL_TREE;
}
/* Expand the s390_vector_bool type attribute. */
static tree
s390_handle_vectorbool_attribute (tree *node, tree name ATTRIBUTE_UNUSED,
tree args ATTRIBUTE_UNUSED,
int flags ATTRIBUTE_UNUSED, bool *no_add_attrs)
{
tree type = *node, result = NULL_TREE;
machine_mode mode;
while (POINTER_TYPE_P (type)
|| TREE_CODE (type) == FUNCTION_TYPE
|| TREE_CODE (type) == METHOD_TYPE
|| TREE_CODE (type) == ARRAY_TYPE)
type = TREE_TYPE (type);
mode = TYPE_MODE (type);
switch (mode)
{
case E_DImode: case E_V2DImode:
result = s390_builtin_types[BT_BV2DI];
break;
case E_SImode: case E_V4SImode:
result = s390_builtin_types[BT_BV4SI];
break;
case E_HImode: case E_V8HImode:
result = s390_builtin_types[BT_BV8HI];
break;
case E_QImode: case E_V16QImode:
result = s390_builtin_types[BT_BV16QI];
break;
default:
break;
}
*no_add_attrs = true; /* No need to hang on to the attribute. */
if (result)
*node = lang_hooks.types.reconstruct_complex_type (*node, result);
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 }
};
/* Return the alignment for LABEL. We default to the -falign-labels
value except for the literal pool base label. */
int
s390_label_align (rtx_insn *label)
{
rtx_insn *prev_insn = prev_active_insn (label);
rtx set, src;
if (prev_insn == NULL_RTX)
goto old;
set = single_set (prev_insn);
if (set == NULL_RTX)
goto old;
src = SET_SRC (set);
/* Don't align literal pool base labels. */
if (GET_CODE (src) == UNSPEC
&& XINT (src, 1) == UNSPEC_MAIN_BASE)
return 0;
old:
return align_labels.levels[0].log;
}
static GTY(()) rtx got_symbol;
/* Return the GOT table symbol. The symbol will be created when the
function is invoked for the first time. */
static rtx
s390_got_symbol (void)
{
if (!got_symbol)
{
got_symbol = gen_rtx_SYMBOL_REF (Pmode, "_GLOBAL_OFFSET_TABLE_");
SYMBOL_REF_FLAGS (got_symbol) = SYMBOL_FLAG_LOCAL;
}
return got_symbol;
}
static scalar_int_mode
s390_libgcc_cmp_return_mode (void)
{
return TARGET_64BIT ? DImode : SImode;
}
static scalar_int_mode
s390_libgcc_shift_count_mode (void)
{
return TARGET_64BIT ? DImode : SImode;
}
static scalar_int_mode
s390_unwind_word_mode (void)
{
return TARGET_64BIT ? DImode : SImode;
}
/* Return true if the back end supports mode MODE. */
static bool
s390_scalar_mode_supported_p (scalar_mode mode)
{
/* In contrast to the default implementation reject TImode constants on 31bit
TARGET_ZARCH for ABI compliance. */
if (!TARGET_64BIT && TARGET_ZARCH && mode == TImode)
return false;
if (DECIMAL_FLOAT_MODE_P (mode))
return default_decimal_float_supported_p ();
return default_scalar_mode_supported_p (mode);
}
/* Return true if the back end supports vector mode MODE. */
static bool
s390_vector_mode_supported_p (machine_mode mode)
{
machine_mode inner;
if (!VECTOR_MODE_P (mode)
|| !TARGET_VX
|| GET_MODE_SIZE (mode) > 16)
return false;
inner = GET_MODE_INNER (mode);
switch (inner)
{
case E_QImode:
case E_HImode:
case E_SImode:
case E_DImode:
case E_TImode:
case E_SFmode:
case E_DFmode:
case E_TFmode:
return true;
default:
return false;
}
}
/* Set the has_landing_pad_p flag in struct machine_function to VALUE. */
void
s390_set_has_landing_pad_p (bool value)
{
cfun->machine->has_landing_pad_p = value;
}
/* If two condition code modes are compatible, return a condition code
mode which is compatible with both. Otherwise, return
VOIDmode. */
static machine_mode
s390_cc_modes_compatible (machine_mode m1, machine_mode m2)
{
if (m1 == m2)
return m1;
switch (m1)
{
case E_CCZmode:
if (m2 == CCUmode || m2 == CCTmode || m2 == CCZ1mode
|| m2 == CCSmode || m2 == CCSRmode || m2 == CCURmode)
return m2;
return VOIDmode;
case E_CCSmode:
case E_CCUmode:
case E_CCTmode:
case E_CCSRmode:
case E_CCURmode:
case E_CCZ1mode:
if (m2 == CCZmode)
return m1;
return VOIDmode;
default:
return VOIDmode;
}
return VOIDmode;
}
/* Return true if SET either doesn't set the CC register, or else
the source and destination have matching CC modes and that
CC mode is at least as constrained as REQ_MODE. */
static bool
s390_match_ccmode_set (rtx set, machine_mode req_mode)
{
machine_mode set_mode;
gcc_assert (GET_CODE (set) == SET);
/* These modes are supposed to be used only in CC consumer
patterns. */
gcc_assert (req_mode != CCVIALLmode && req_mode != CCVIANYmode
&& req_mode != CCVFALLmode && req_mode != CCVFANYmode);
if (GET_CODE (SET_DEST (set)) != REG || !CC_REGNO_P (REGNO (SET_DEST (set))))
return 1;
set_mode = GET_MODE (SET_DEST (set));
switch (set_mode)
{
case E_CCZ1mode:
case E_CCSmode:
case E_CCSRmode:
case E_CCSFPSmode:
case E_CCUmode:
case E_CCURmode:
case E_CCOmode:
case E_CCLmode:
case E_CCL1mode:
case E_CCL2mode:
case E_CCL3mode:
case E_CCT1mode:
case E_CCT2mode:
case E_CCT3mode:
case E_CCVEQmode:
case E_CCVIHmode:
case E_CCVIHUmode:
case E_CCVFHmode:
case E_CCVFHEmode:
if (req_mode != set_mode)
return 0;
break;
case E_CCZmode:
if (req_mode != CCSmode && req_mode != CCUmode && req_mode != CCTmode
&& req_mode != CCSRmode && req_mode != CCURmode
&& req_mode != CCZ1mode)
return 0;
break;
case E_CCAPmode:
case E_CCANmode:
if (req_mode != CCAmode)
return 0;
break;
default:
gcc_unreachable ();
}
return (GET_MODE (SET_SRC (set)) == set_mode);
}
/* Return true if every SET in INSN that sets the CC register
has source and destination with matching CC modes and that
CC mode is at least as constrained as REQ_MODE.
If REQ_MODE is VOIDmode, always return false. */
bool
s390_match_ccmode (rtx_insn *insn, machine_mode req_mode)
{
int i;
/* s390_tm_ccmode returns VOIDmode to indicate failure. */
if (req_mode == VOIDmode)
return false;
if (GET_CODE (PATTERN (insn)) == SET)
return s390_match_ccmode_set (PATTERN (insn), req_mode);
if (GET_CODE (PATTERN (insn)) == PARALLEL)
for (i = 0; i < XVECLEN (PATTERN (insn), 0); i++)
{
rtx set = XVECEXP (PATTERN (insn), 0, i);
if (GET_CODE (set) == SET)
if (!s390_match_ccmode_set (set, req_mode))
return false;
}
return true;
}
/* If a test-under-mask instruction can be used to implement
(compare (and ... OP1) OP2), return the CC mode required
to do that. Otherwise, return VOIDmode.
MIXED is true if the instruction can distinguish between
CC1 and CC2 for mixed selected bits (TMxx), it is false
if the instruction cannot (TM). */
machine_mode
s390_tm_ccmode (rtx op1, rtx op2, bool mixed)
{
int bit0, bit1;
/* ??? Fixme: should work on CONST_WIDE_INT as well. */
if (GET_CODE (op1) != CONST_INT || GET_CODE (op2) != CONST_INT)
return VOIDmode;
/* Selected bits all zero: CC0.
e.g.: int a; if ((a & (16 + 128)) == 0) */
if (INTVAL (op2) == 0)
return CCTmode;
/* Selected bits all one: CC3.
e.g.: int a; if ((a & (16 + 128)) == 16 + 128) */
if (INTVAL (op2) == INTVAL (op1))
return CCT3mode;
/* Exactly two bits selected, mixed zeroes and ones: CC1 or CC2. e.g.:
int a;
if ((a & (16 + 128)) == 16) -> CCT1
if ((a & (16 + 128)) == 128) -> CCT2 */
if (mixed)
{
bit1 = exact_log2 (INTVAL (op2));
bit0 = exact_log2 (INTVAL (op1) ^ INTVAL (op2));
if (bit0 != -1 && bit1 != -1)
return bit0 > bit1 ? CCT1mode : CCT2mode;
}
return VOIDmode;
}
/* Given a comparison code OP (EQ, NE, etc.) and the operands
OP0 and OP1 of a COMPARE, return the mode to be used for the
comparison. */
machine_mode
s390_select_ccmode (enum rtx_code code, rtx op0, rtx op1)
{
switch (code)
{
case EQ:
case NE:
if ((GET_CODE (op0) == NEG || GET_CODE (op0) == ABS)
&& GET_MODE_CLASS (GET_MODE (op0)) == MODE_INT)
return CCAPmode;
if (GET_CODE (op0) == PLUS && GET_CODE (XEXP (op0, 1)) == CONST_INT
&& CONST_OK_FOR_K (INTVAL (XEXP (op0, 1))))
return CCAPmode;
if ((GET_CODE (op0) == PLUS || GET_CODE (op0) == MINUS
|| GET_CODE (op1) == NEG)
&& GET_MODE_CLASS (GET_MODE (op0)) == MODE_INT)
return CCLmode;
if (GET_CODE (op0) == AND)
{
/* Check whether we can potentially do it via TM. */
machine_mode ccmode;
ccmode = s390_tm_ccmode (XEXP (op0, 1), op1, 1);
if (ccmode != VOIDmode)
{
/* Relax CCTmode to CCZmode to allow fall-back to AND
if that turns out to be beneficial. */
return ccmode == CCTmode ? CCZmode : ccmode;
}
}
if (register_operand (op0, HImode)
&& GET_CODE (op1) == CONST_INT
&& (INTVAL (op1) == -1 || INTVAL (op1) == 65535))
return CCT3mode;
if (register_operand (op0, QImode)
&& GET_CODE (op1) == CONST_INT
&& (INTVAL (op1) == -1 || INTVAL (op1) == 255))
return CCT3mode;
return CCZmode;
case LE:
case LT:
case GE:
case GT:
/* The only overflow condition of NEG and ABS happens when
-INT_MAX is used as parameter, which stays negative. So
we have an overflow from a positive value to a negative.
Using CCAP mode the resulting cc can be used for comparisons. */
if ((GET_CODE (op0) == NEG || GET_CODE (op0) == ABS)
&& GET_MODE_CLASS (GET_MODE (op0)) == MODE_INT)
return CCAPmode;
/* If constants are involved in an add instruction it is possible to use
the resulting cc for comparisons with zero. Knowing the sign of the
constant the overflow behavior gets predictable. e.g.:
int a, b; if ((b = a + c) > 0)
with c as a constant value: c < 0 -> CCAN and c >= 0 -> CCAP */
if (GET_CODE (op0) == PLUS && GET_CODE (XEXP (op0, 1)) == CONST_INT
&& (CONST_OK_FOR_K (INTVAL (XEXP (op0, 1)))
|| (CONST_OK_FOR_CONSTRAINT_P (INTVAL (XEXP (op0, 1)), 'O', "Os")
/* Avoid INT32_MIN on 32 bit. */
&& (!TARGET_ZARCH || INTVAL (XEXP (op0, 1)) != -0x7fffffff - 1))))
{
if (INTVAL (XEXP((op0), 1)) < 0)
return CCANmode;
else
return CCAPmode;
}
/* Fall through. */
case LTGT:
if (HONOR_NANS (op0) || HONOR_NANS (op1))
return CCSFPSmode;
/* Fall through. */
case UNORDERED:
case ORDERED:
case UNEQ:
case UNLE:
case UNLT:
case UNGE:
case UNGT:
if ((GET_CODE (op0) == SIGN_EXTEND || GET_CODE (op0) == ZERO_EXTEND)
&& GET_CODE (op1) != CONST_INT)
return CCSRmode;
return CCSmode;
case LTU:
case GEU:
if (GET_CODE (op0) == PLUS
&& GET_MODE_CLASS (GET_MODE (op0)) == MODE_INT)
return CCL1mode;
if ((GET_CODE (op0) == SIGN_EXTEND || GET_CODE (op0) == ZERO_EXTEND)
&& GET_CODE (op1) != CONST_INT)
return CCURmode;
return CCUmode;
case LEU:
case GTU:
if (GET_CODE (op0) == MINUS
&& GET_MODE_CLASS (GET_MODE (op0)) == MODE_INT)
return CCL2mode;
if ((GET_CODE (op0) == SIGN_EXTEND || GET_CODE (op0) == ZERO_EXTEND)
&& GET_CODE (op1) != CONST_INT)
return CCURmode;
return CCUmode;
default:
gcc_unreachable ();
}
}
/* Replace the comparison OP0 CODE OP1 by a semantically equivalent one
that we can implement more efficiently. */
static void
s390_canonicalize_comparison (int *code, rtx *op0, rtx *op1,
bool op0_preserve_value)
{
if (op0_preserve_value)
return;
/* Convert ZERO_EXTRACT back to AND to enable TM patterns. */
if ((*code == EQ || *code == NE)
&& *op1 == const0_rtx
&& GET_CODE (*op0) == ZERO_EXTRACT
&& GET_CODE (XEXP (*op0, 1)) == CONST_INT
&& GET_CODE (XEXP (*op0, 2)) == CONST_INT
&& SCALAR_INT_MODE_P (GET_MODE (XEXP (*op0, 0))))
{
rtx inner = XEXP (*op0, 0);
HOST_WIDE_INT modesize = GET_MODE_BITSIZE (GET_MODE (inner));
HOST_WIDE_INT len = INTVAL (XEXP (*op0, 1));
HOST_WIDE_INT pos = INTVAL (XEXP (*op0, 2));
if (len > 0 && len < modesize
&& pos >= 0 && pos + len <= modesize
&& modesize <= HOST_BITS_PER_WIDE_INT)
{
unsigned HOST_WIDE_INT block;
block = (HOST_WIDE_INT_1U << len) - 1;
block <<= modesize - pos - len;
*op0 = gen_rtx_AND (GET_MODE (inner), inner,
gen_int_mode (block, GET_MODE (inner)));
}
}
/* Narrow AND of memory against immediate to enable TM. */
if ((*code == EQ || *code == NE)
&& *op1 == const0_rtx
&& GET_CODE (*op0) == AND
&& GET_CODE (XEXP (*op0, 1)) == CONST_INT
&& SCALAR_INT_MODE_P (GET_MODE (XEXP (*op0, 0))))
{
rtx inner = XEXP (*op0, 0);
rtx mask = XEXP (*op0, 1);
/* Ignore paradoxical SUBREGs if all extra bits are masked out. */
if (GET_CODE (inner) == SUBREG
&& SCALAR_INT_MODE_P (GET_MODE (SUBREG_REG (inner)))
&& (GET_MODE_SIZE (GET_MODE (inner))
>= GET_MODE_SIZE (GET_MODE (SUBREG_REG (inner))))
&& ((INTVAL (mask)
& GET_MODE_MASK (GET_MODE (inner))
& ~GET_MODE_MASK (GET_MODE (SUBREG_REG (inner))))
== 0))
inner = SUBREG_REG (inner);
/* Do not change volatile MEMs. */
if (MEM_P (inner) && !MEM_VOLATILE_P (inner))
{
int part = s390_single_part (XEXP (*op0, 1),
GET_MODE (inner), QImode, 0);
if (part >= 0)
{
mask = gen_int_mode (s390_extract_part (mask, QImode, 0), QImode);
inner = adjust_address_nv (inner, QImode, part);
*op0 = gen_rtx_AND (QImode, inner, mask);
}
}
}
/* Narrow comparisons against 0xffff to HImode if possible. */
if ((*code == EQ || *code == NE)
&& GET_CODE (*op1) == CONST_INT
&& INTVAL (*op1) == 0xffff
&& SCALAR_INT_MODE_P (GET_MODE (*op0))
&& (nonzero_bits (*op0, GET_MODE (*op0))
& ~HOST_WIDE_INT_UC (0xffff)) == 0)
{
*op0 = gen_lowpart (HImode, *op0);
*op1 = constm1_rtx;
}
/* Remove redundant UNSPEC_STRCMPCC_TO_INT conversions if possible. */
if (GET_CODE (*op0) == UNSPEC
&& XINT (*op0, 1) == UNSPEC_STRCMPCC_TO_INT
&& XVECLEN (*op0, 0) == 1
&& GET_MODE (XVECEXP (*op0, 0, 0)) == CCUmode
&& GET_CODE (XVECEXP (*op0, 0, 0)) == REG
&& REGNO (XVECEXP (*op0, 0, 0)) == CC_REGNUM
&& *op1 == const0_rtx)
{
enum rtx_code new_code = UNKNOWN;
switch (*code)
{
case EQ: new_code = EQ; break;
case NE: new_code = NE; break;
case LT: new_code = GTU; break;
case GT: new_code = LTU; break;
case LE: new_code = GEU; break;
case GE: new_code = LEU; break;
default: break;
}
if (new_code != UNKNOWN)
{
*op0 = XVECEXP (*op0, 0, 0);
*code = new_code;
}
}
/* Remove redundant UNSPEC_CC_TO_INT conversions if possible. */
if (GET_CODE (*op0) == UNSPEC
&& XINT (*op0, 1) == UNSPEC_CC_TO_INT
&& XVECLEN (*op0, 0) == 1
&& GET_CODE (XVECEXP (*op0, 0, 0)) == REG
&& REGNO (XVECEXP (*op0, 0, 0)) == CC_REGNUM
&& CONST_INT_P (*op1))
{
enum rtx_code new_code = UNKNOWN;
switch (GET_MODE (XVECEXP (*op0, 0, 0)))
{
case E_CCZmode:
case E_CCRAWmode:
switch (*code)
{
case EQ: new_code = EQ; break;
case NE: new_code = NE; break;
default: break;
}
break;
default: break;
}
if (new_code != UNKNOWN)
{
/* For CCRAWmode put the required cc mask into the second
operand. */
if (GET_MODE (XVECEXP (*op0, 0, 0)) == CCRAWmode
&& INTVAL (*op1) >= 0 && INTVAL (*op1) <= 3)
*op1 = gen_rtx_CONST_INT (VOIDmode, 1 << (3 - INTVAL (*op1)));
*op0 = XVECEXP (*op0, 0, 0);
*code = new_code;
}
}
/* Simplify cascaded EQ, NE with const0_rtx. */
if ((*code == NE || *code == EQ)
&& (GET_CODE (*op0) == EQ || GET_CODE (*op0) == NE)
&& GET_MODE (*op0) == SImode
&& GET_MODE (XEXP (*op0, 0)) == CCZ1mode
&& REG_P (XEXP (*op0, 0))
&& XEXP (*op0, 1) == const0_rtx
&& *op1 == const0_rtx)
{
if ((*code == EQ && GET_CODE (*op0) == NE)
|| (*code == NE && GET_CODE (*op0) == EQ))
*code = EQ;
else
*code = NE;
*op0 = XEXP (*op0, 0);
}
/* Prefer register over memory as first operand. */
if (MEM_P (*op0) && REG_P (*op1))
{
rtx tem = *op0; *op0 = *op1; *op1 = tem;
*code = (int)swap_condition ((enum rtx_code)*code);
}
/* A comparison result is compared against zero. Replace it with
the (perhaps inverted) original comparison.
This probably should be done by simplify_relational_operation. */
if ((*code == EQ || *code == NE)
&& *op1 == const0_rtx
&& COMPARISON_P (*op0)
&& CC_REG_P (XEXP (*op0, 0)))
{
enum rtx_code new_code;
if (*code == EQ)
new_code = reversed_comparison_code_parts (GET_CODE (*op0),
XEXP (*op0, 0),
XEXP (*op0, 1), NULL);
else
new_code = GET_CODE (*op0);
if (new_code != UNKNOWN)
{
*code = new_code;
*op1 = XEXP (*op0, 1);
*op0 = XEXP (*op0, 0);
}
}
/* ~a==b -> ~(a^b)==0 ~a!=b -> ~(a^b)!=0 */
if (TARGET_Z15
&& (*code == EQ || *code == NE)
&& (GET_MODE (*op0) == DImode || GET_MODE (*op0) == SImode)
&& GET_CODE (*op0) == NOT)
{
machine_mode mode = GET_MODE (*op0);
*op0 = gen_rtx_XOR (mode, XEXP (*op0, 0), *op1);
*op0 = gen_rtx_NOT (mode, *op0);
*op1 = const0_rtx;
}
/* a&b == -1 -> ~a|~b == 0 a|b == -1 -> ~a&~b == 0 */
if (TARGET_Z15
&& (*code == EQ || *code == NE)
&& (GET_CODE (*op0) == AND || GET_CODE (*op0) == IOR)
&& (GET_MODE (*op0) == DImode || GET_MODE (*op0) == SImode)
&& CONST_INT_P (*op1)
&& *op1 == constm1_rtx)
{
machine_mode mode = GET_MODE (*op0);
rtx op00 = gen_rtx_NOT (mode, XEXP (*op0, 0));
rtx op01 = gen_rtx_NOT (mode, XEXP (*op0, 1));
if (GET_CODE (*op0) == AND)
*op0 = gen_rtx_IOR (mode, op00, op01);
else
*op0 = gen_rtx_AND (mode, op00, op01);
*op1 = const0_rtx;
}
}
/* Emit a compare instruction suitable to implement the comparison
OP0 CODE OP1. Return the correct condition RTL to be placed in
the IF_THEN_ELSE of the conditional branch testing the result. */
rtx
s390_emit_compare (enum rtx_code code, rtx op0, rtx op1)
{
machine_mode mode = s390_select_ccmode (code, op0, op1);
rtx cc;
/* Force OP1 into register in order to satisfy VXE TFmode patterns. */
if (TARGET_VXE && GET_MODE (op1) == TFmode)
op1 = force_reg (TFmode, op1);
if (GET_MODE_CLASS (GET_MODE (op0)) == MODE_CC)
{
/* Do not output a redundant compare instruction if a
compare_and_swap pattern already computed the result and the
machine modes are compatible. */
gcc_assert (s390_cc_modes_compatible (GET_MODE (op0), mode)
== GET_MODE (op0));
cc = op0;
}
else
{
cc = gen_rtx_REG (mode, CC_REGNUM);
emit_insn (gen_rtx_SET (cc, gen_rtx_COMPARE (mode, op0, op1)));
}
return gen_rtx_fmt_ee (code, VOIDmode, cc, const0_rtx);
}
/* If MEM is not a legitimate compare-and-swap memory operand, return a new
MEM, whose address is a pseudo containing the original MEM's address. */
static rtx
s390_legitimize_cs_operand (rtx mem)
{
rtx tmp;
if (!contains_symbol_ref_p (mem))
return mem;
tmp = gen_reg_rtx (Pmode);
emit_move_insn (tmp, copy_rtx (XEXP (mem, 0)));
return change_address (mem, VOIDmode, tmp);
}
/* Emit a SImode compare and swap instruction setting MEM to NEW_RTX if OLD
matches CMP.
Return the correct condition RTL to be placed in the IF_THEN_ELSE of the
conditional branch testing the result. */
static rtx
s390_emit_compare_and_swap (enum rtx_code code, rtx old, rtx mem,
rtx cmp, rtx new_rtx, machine_mode ccmode)
{
rtx cc;
mem = s390_legitimize_cs_operand (mem);
cc = gen_rtx_REG (ccmode, CC_REGNUM);
switch (GET_MODE (mem))
{
case E_SImode:
emit_insn (gen_atomic_compare_and_swapsi_internal (old, mem, cmp,
new_rtx, cc));
break;
case E_DImode:
emit_insn (gen_atomic_compare_and_swapdi_internal (old, mem, cmp,
new_rtx, cc));
break;
case E_TImode:
emit_insn (gen_atomic_compare_and_swapti_internal (old, mem, cmp,
new_rtx, cc));
break;
case E_QImode:
case E_HImode:
default:
gcc_unreachable ();
}
return s390_emit_compare (code, cc, const0_rtx);
}
/* Emit a jump instruction to TARGET and return it. If COND is
NULL_RTX, emit an unconditional jump, else a conditional jump under
condition COND. */
rtx_insn *
s390_emit_jump (rtx target, rtx cond)
{
rtx insn;
target = gen_rtx_LABEL_REF (VOIDmode, target);
if (cond)
target = gen_rtx_IF_THEN_ELSE (VOIDmode, cond, target, pc_rtx);
insn = gen_rtx_SET (pc_rtx, target);
return emit_jump_insn (insn);
}
/* Return branch condition mask to implement a branch
specified by CODE. Return -1 for invalid comparisons. */
int
s390_branch_condition_mask (rtx code)
{
const int CC0 = 1 << 3;
const int CC1 = 1 << 2;
const int CC2 = 1 << 1;
const int CC3 = 1 << 0;
gcc_assert (GET_CODE (XEXP (code, 0)) == REG);
gcc_assert (REGNO (XEXP (code, 0)) == CC_REGNUM);
gcc_assert (XEXP (code, 1) == const0_rtx
|| (GET_MODE (XEXP (code, 0)) == CCRAWmode
&& CONST_INT_P (XEXP (code, 1))));
switch (GET_MODE (XEXP (code, 0)))
{
case E_CCZmode:
case E_CCZ1mode:
switch (GET_CODE (code))
{
case EQ: return CC0;
case NE: return CC1 | CC2 | CC3;
default: return -1;
}
break;
case E_CCT1mode:
switch (GET_CODE (code))
{
case EQ: return CC1;
case NE: return CC0 | CC2 | CC3;
default: return -1;
}
break;
case E_CCT2mode:
switch (GET_CODE (code))
{
case EQ: return CC2;
case NE: return CC0 | CC1 | CC3;
default: return -1;
}
break;
case E_CCT3mode:
switch (GET_CODE (code))
{
case EQ: return CC3;
case NE: return CC0 | CC1 | CC2;
default: return -1;
}
break;
case E_CCLmode:
switch (GET_CODE (code))
{
case EQ: return CC0 | CC2;
case NE: return CC1 | CC3;
default: return -1;
}
break;
case E_CCL1mode:
switch (GET_CODE (code))
{
case LTU: return CC2 | CC3; /* carry */
case GEU: return CC0 | CC1; /* no carry */
default: return -1;
}
break;
case E_CCL2mode:
switch (GET_CODE (code))
{
case GTU: return CC0 | CC1; /* borrow */
case LEU: return CC2 | CC3; /* no borrow */
default: return -1;
}
break;
case E_CCL3mode:
switch (GET_CODE (code))
{
case EQ: return CC0 | CC2;
case NE: return CC1 | CC3;
case LTU: return CC1;
case GTU: return CC3;
case LEU: return CC1 | CC2;
case GEU: return CC2 | CC3;
default: return -1;
}
case E_CCUmode:
switch (GET_CODE (code))
{
case EQ: return CC0;
case NE: return CC1 | CC2 | CC3;
case LTU: return CC1;
case GTU: return CC2;
case LEU: return CC0 | CC1;
case GEU: return CC0 | CC2;
default: return -1;
}
break;
case E_CCURmode:
switch (GET_CODE (code))
{
case EQ: return CC0;
case NE: return CC2 | CC1 | CC3;
case LTU: return CC2;
case GTU: return CC1;
case LEU: return CC0 | CC2;
case GEU: return CC0 | CC1;
default: return -1;
}
break;
case E_CCAPmode:
switch (GET_CODE (code))
{
case EQ: return CC0;
case NE: return CC1 | CC2 | CC3;
case LT: return CC1 | CC3;
case GT: return CC2;
case LE: return CC0 | CC1 | CC3;
case GE: return CC0 | CC2;
default: return -1;
}
break;
case E_CCANmode:
switch (GET_CODE (code))
{
case EQ: return CC0;
case NE: return CC1 | CC2 | CC3;
case LT: return CC1;
case GT: return CC2 | CC3;
case LE: return CC0 | CC1;
case GE: return CC0 | CC2 | CC3;
default: return -1;
}
break;
case E_CCOmode:
switch (GET_CODE (code))
{
case EQ: return CC0 | CC1 | CC2;
case NE: return CC3;
default: return -1;
}
break;
case E_CCSmode:
case E_CCSFPSmode:
switch (GET_CODE (code))
{
case EQ: return CC0;
case NE: return CC1 | CC2 | CC3;
case LT: return CC1;
case GT: return CC2;
case LE: return CC0 | CC1;
case GE: return CC0 | CC2;
case UNORDERED: return CC3;
case ORDERED: return CC0 | CC1 | CC2;
case UNEQ: return CC0 | CC3;
case UNLT: return CC1 | CC3;
case UNGT: return CC2 | CC3;
case UNLE: return CC0 | CC1 | CC3;
case UNGE: return CC0 | CC2 | CC3;
case LTGT: return CC1 | CC2;
default: return -1;
}
break;
case E_CCSRmode:
switch (GET_CODE (code))
{
case EQ: return CC0;
case NE: return CC2 | CC1 | CC3;
case LT: return CC2;
case GT: return CC1;
case LE: return CC0 | CC2;
case GE: return CC0 | CC1;
case UNORDERED: return CC3;
case ORDERED: return CC0 | CC2 | CC1;
case UNEQ: return CC0 | CC3;
case UNLT: return CC2 | CC3;
case UNGT: return CC1 | CC3;
case UNLE: return CC0 | CC2 | CC3;
case UNGE: return CC0 | CC1 | CC3;
case LTGT: return CC2 | CC1;
default: return -1;
}
break;
/* Vector comparison modes. */
/* CC2 will never be set. It however is part of the negated
masks. */
case E_CCVIALLmode:
switch (GET_CODE (code))
{
case EQ:
case GTU:
case GT:
case GE: return CC0;
/* The inverted modes are in fact *any* modes. */
case NE:
case LEU:
case LE:
case LT: return CC3 | CC1 | CC2;
default: return -1;
}
case E_CCVIANYmode:
switch (GET_CODE (code))
{
case EQ:
case GTU:
case GT:
case GE: return CC0 | CC1;
/* The inverted modes are in fact *all* modes. */
case NE:
case LEU:
case LE:
case LT: return CC3 | CC2;
default: return -1;
}
case E_CCVFALLmode:
switch (GET_CODE (code))
{
case EQ:
case GT:
case GE: return CC0;
/* The inverted modes are in fact *any* modes. */
case NE:
case UNLE:
case UNLT: return CC3 | CC1 | CC2;
default: return -1;
}
case E_CCVFANYmode:
switch (GET_CODE (code))
{
case EQ:
case GT:
case GE: return CC0 | CC1;
/* The inverted modes are in fact *all* modes. */
case NE:
case UNLE:
case UNLT: return CC3 | CC2;
default: return -1;
}
case E_CCRAWmode:
switch (GET_CODE (code))
{
case EQ:
return INTVAL (XEXP (code, 1));
case NE:
return (INTVAL (XEXP (code, 1))) ^ 0xf;
default:
gcc_unreachable ();
}
default:
return -1;
}
}
/* Return branch condition mask to implement a compare and branch
specified by CODE. Return -1 for invalid comparisons. */
int
s390_compare_and_branch_condition_mask (rtx code)
{
const int CC0 = 1 << 3;
const int CC1 = 1 << 2;
const int CC2 = 1 << 1;
switch (GET_CODE (code))
{
case EQ:
return CC0;
case NE:
return CC1 | CC2;
case LT:
case LTU:
return CC1;
case GT:
case GTU:
return CC2;
case LE:
case LEU:
return CC0 | CC1;
case GE:
case GEU:
return CC0 | CC2;
default:
gcc_unreachable ();
}
return -1;
}
/* If INV is false, return assembler mnemonic string to implement
a branch specified by CODE. If INV is true, return mnemonic
for the corresponding inverted branch. */
static const char *
s390_branch_condition_mnemonic (rtx code, int inv)
{
int mask;
static const char *const mnemonic[16] =
{
NULL, "o", "h", "nle",
"l", "nhe", "lh", "ne",
"e", "nlh", "he", "nl",
"le", "nh", "no", NULL
};
if (GET_CODE (XEXP (code, 0)) == REG
&& REGNO (XEXP (code, 0)) == CC_REGNUM
&& (XEXP (code, 1) == const0_rtx
|| (GET_MODE (XEXP (code, 0)) == CCRAWmode
&& CONST_INT_P (XEXP (code, 1)))))
mask = s390_branch_condition_mask (code);
else
mask = s390_compare_and_branch_condition_mask (code);
gcc_assert (mask >= 0);
if (inv)
mask ^= 15;
gcc_assert (mask >= 1 && mask <= 14);
return mnemonic[mask];
}
/* Return the part of op which has a value different from def.
The size of the part is determined by mode.
Use this function only if you already know that op really
contains such a part. */
unsigned HOST_WIDE_INT
s390_extract_part (rtx op, machine_mode mode, int def)
{
unsigned HOST_WIDE_INT value = 0;
int max_parts = HOST_BITS_PER_WIDE_INT / GET_MODE_BITSIZE (mode);
int part_bits = GET_MODE_BITSIZE (mode);
unsigned HOST_WIDE_INT part_mask = (HOST_WIDE_INT_1U << part_bits) - 1;
int i;
for (i = 0; i < max_parts; i++)
{
if (i == 0)
value = UINTVAL (op);
else
value >>= part_bits;
if ((value & part_mask) != (def & part_mask))
return value & part_mask;
}
gcc_unreachable ();
}
/* If OP is an integer constant of mode MODE with exactly one
part of mode PART_MODE unequal to DEF, return the number of that
part. Otherwise, return -1. */
int
s390_single_part (rtx op,
machine_mode mode,
machine_mode part_mode,
int def)
{
unsigned HOST_WIDE_INT value = 0;
int n_parts = GET_MODE_SIZE (mode) / GET_MODE_SIZE (part_mode);
unsigned HOST_WIDE_INT part_mask
= (HOST_WIDE_INT_1U << GET_MODE_BITSIZE (part_mode)) - 1;
int i, part = -1;
if (GET_CODE (op) != CONST_INT)
return -1;
for (i = 0; i < n_parts; i++)
{
if (i == 0)
value = UINTVAL (op);
else
value >>= GET_MODE_BITSIZE (part_mode);
if ((value & part_mask) != (def & part_mask))
{
if (part != -1)
return -1;
else
part = i;
}
}
return part == -1 ? -1 : n_parts - 1 - part;
}
/* Return true if IN contains a contiguous bitfield in the lower SIZE
bits and no other bits are set in (the lower SIZE bits of) IN.
PSTART and PEND can be used to obtain the start and end
position (inclusive) of the bitfield relative to 64
bits. *PSTART / *PEND gives the position of the first/last bit
of the bitfield counting from the highest order bit starting
with zero. */
bool
s390_contiguous_bitmask_nowrap_p (unsigned HOST_WIDE_INT in, int size,
int *pstart, int *pend)
{
int start;
int end = -1;
int lowbit = HOST_BITS_PER_WIDE_INT - 1;
int highbit = HOST_BITS_PER_WIDE_INT - size;
unsigned HOST_WIDE_INT bitmask = HOST_WIDE_INT_1U;
gcc_assert (!!pstart == !!pend);
for (start = lowbit; start >= highbit; bitmask <<= 1, start--)
if (end == -1)
{
/* Look for the rightmost bit of a contiguous range of ones. */
if (bitmask & in)
/* Found it. */
end = start;
}
else
{
/* Look for the firt zero bit after the range of ones. */
if (! (bitmask & in))
/* Found it. */
break;
}
/* We're one past the last one-bit. */
start++;
if (end == -1)
/* No one bits found. */
return false;
if (start > highbit)
{
unsigned HOST_WIDE_INT mask;
/* Calculate a mask for all bits beyond the contiguous bits. */
mask = ((~HOST_WIDE_INT_0U >> highbit)
& (~HOST_WIDE_INT_0U << (lowbit - start + 1)));
if (mask & in)
/* There are more bits set beyond the first range of one bits. */
return false;
}
if (pstart)
{
*pstart = start;
*pend = end;
}
return true;
}
/* Same as s390_contiguous_bitmask_nowrap_p but also returns true
if ~IN contains a contiguous bitfield. In that case, *END is <
*START.
If WRAP_P is true, a bitmask that wraps around is also tested.
When a wraparoud occurs *START is greater than *END (in
non-null pointers), and the uppermost (64 - SIZE) bits are thus
part of the range. If WRAP_P is false, no wraparound is
tested. */
bool
s390_contiguous_bitmask_p (unsigned HOST_WIDE_INT in, bool wrap_p,
int size, int *start, int *end)
{
int bs = HOST_BITS_PER_WIDE_INT;
bool b;
gcc_assert (!!start == !!end);
if ((in & ((~HOST_WIDE_INT_0U) >> (bs - size))) == 0)
/* This cannot be expressed as a contiguous bitmask. Exit early because
the second call of s390_contiguous_bitmask_nowrap_p would accept this as
a valid bitmask. */
return false;
b = s390_contiguous_bitmask_nowrap_p (in, size, start, end);
if (b)
return true;
if (! wrap_p)
return false;
b = s390_contiguous_bitmask_nowrap_p (~in, size, start, end);
if (b && start)
{
int s = *start;
int e = *end;
gcc_assert (s >= 1);
*start = ((e + 1) & (bs - 1));
*end = ((s - 1 + bs) & (bs - 1));
}
return b;
}
/* Return true if OP contains the same contiguous bitfield in *all*
its elements. START and END can be used to obtain the start and
end position of the bitfield.
START/STOP give the position of the first/last bit of the bitfield
counting from the lowest order bit starting with zero. In order to
use these values for S/390 instructions this has to be converted to
"bits big endian" style. */
bool
s390_contiguous_bitmask_vector_p (rtx op, int *start, int *end)
{
unsigned HOST_WIDE_INT mask;
int size;
rtx elt;
bool b;
/* Handle floats by bitcasting them to ints. */
op = gen_lowpart (related_int_vector_mode (GET_MODE (op)).require (), op);
gcc_assert (!!start == !!end);
if (!const_vec_duplicate_p (op, &elt)
|| !CONST_INT_P (elt))
return false;
size = GET_MODE_UNIT_BITSIZE (GET_MODE (op));
/* We cannot deal with V1TI/V1TF. This would require a vgmq. */
if (size > 64)
return false;
mask = UINTVAL (elt);
b = s390_contiguous_bitmask_p (mask, true, size, start, end);
if (b)
{
if (start)
{
*start -= (HOST_BITS_PER_WIDE_INT - size);
*end -= (HOST_BITS_PER_WIDE_INT - size);
}
return true;
}
else
return false;
}
/* Return true if C consists only of byte chunks being either 0 or
0xff. If MASK is !=NULL a byte mask is generated which is
appropriate for the vector generate byte mask instruction. */
bool
s390_bytemask_vector_p (rtx op, unsigned *mask)
{
int i;
unsigned tmp_mask = 0;
int nunit, unit_size;
if (!VECTOR_MODE_P (GET_MODE (op))
|| GET_CODE (op) != CONST_VECTOR
|| !CONST_INT_P (XVECEXP (op, 0, 0)))
return false;
nunit = GET_MODE_NUNITS (GET_MODE (op));
unit_size = GET_MODE_UNIT_SIZE (GET_MODE (op));
for (i = 0; i < nunit; i++)
{
unsigned HOST_WIDE_INT c;
int j;
if (!CONST_INT_P (XVECEXP (op, 0, i)))
return false;
c = UINTVAL (XVECEXP (op, 0, i));
for (j = 0; j < unit_size; j++)
{
if ((c & 0xff) != 0 && (c & 0xff) != 0xff)
return false;
tmp_mask |= (c & 1) << ((nunit - 1 - i) * unit_size + j);
c = c >> BITS_PER_UNIT;
}
}
if (mask != NULL)
*mask = tmp_mask;
return true;
}
/* Check whether a rotate of ROTL followed by an AND of CONTIG is
equivalent to a shift followed by the AND. In particular, CONTIG
should not overlap the (rotated) bit 0/bit 63 gap. Negative values
for ROTL indicate a rotate to the right. */
bool
s390_extzv_shift_ok (int bitsize, int rotl, unsigned HOST_WIDE_INT contig)
{
int start, end;
bool ok;
ok = s390_contiguous_bitmask_nowrap_p (contig, bitsize, &start, &end);
gcc_assert (ok);
if (rotl >= 0)
return (64 - end >= rotl);
else
{
/* Translate "- rotate right" in BITSIZE mode to "rotate left" in
DIMode. */
rotl = -rotl + (64 - bitsize);
return (start >= rotl);
}
}
/* Check whether we can (and want to) split a double-word
move in mode MODE from SRC to DST into two single-word
moves, moving the subword FIRST_SUBWORD first. */
bool
s390_split_ok_p (rtx dst, rtx src, machine_mode mode, int first_subword)
{
/* Floating point and vector registers cannot be split. */
if (FP_REG_P (src) || FP_REG_P (dst) || VECTOR_REG_P (src) || VECTOR_REG_P (dst))
return false;
/* Non-offsettable memory references cannot be split. */
if ((GET_CODE (src) == MEM && !offsettable_memref_p (src))
|| (GET_CODE (dst) == MEM && !offsettable_memref_p (dst)))
return false;
/* Moving the first subword must not clobber a register
needed to move the second subword. */
if (register_operand (dst, mode))
{
rtx subreg = operand_subword (dst, first_subword, 0, mode);
if (reg_overlap_mentioned_p (subreg, src))
return false;
}
return true;
}
/* Return true if it can be proven that [MEM1, MEM1 + SIZE]
and [MEM2, MEM2 + SIZE] do overlap and false
otherwise. */
bool
s390_overlap_p (rtx mem1, rtx mem2, HOST_WIDE_INT size)
{
rtx addr1, addr2, addr_delta;
HOST_WIDE_INT delta;
if (GET_CODE (mem1) != MEM || GET_CODE (mem2) != MEM)
return true;
if (size == 0)
return false;
addr1 = XEXP (mem1, 0);
addr2 = XEXP (mem2, 0);
addr_delta = simplify_binary_operation (MINUS, Pmode, addr2, addr1);
/* This overlapping check is used by peepholes merging memory block operations.
Overlapping operations would otherwise be recognized by the S/390 hardware
and would fall back to a slower implementation. Allowing overlapping
operations would lead to slow code but not to wrong code. Therefore we are
somewhat optimistic if we cannot prove that the memory blocks are
overlapping.
That's why we return false here although this may accept operations on
overlapping memory areas. */
if (!addr_delta || GET_CODE (addr_delta) != CONST_INT)
return false;
delta = INTVAL (addr_delta);
if (delta == 0
|| (delta > 0 && delta < size)
|| (delta < 0 && -delta < size))
return true;
return false;
}
/* Check whether the address of memory reference MEM2 equals exactly
the address of memory reference MEM1 plus DELTA. Return true if
we can prove this to be the case, false otherwise. */
bool
s390_offset_p (rtx mem1, rtx mem2, rtx delta)
{
rtx addr1, addr2, addr_delta;
if (GET_CODE (mem1) != MEM || GET_CODE (mem2) != MEM)
return false;
addr1 = XEXP (mem1, 0);
addr2 = XEXP (mem2, 0);
addr_delta = simplify_binary_operation (MINUS, Pmode, addr2, addr1);
if (!addr_delta || !rtx_equal_p (addr_delta, delta))
return false;
return true;
}
/* Expand logical operator CODE in mode MODE with operands OPERANDS. */
void
s390_expand_logical_operator (enum rtx_code code, machine_mode mode,
rtx *operands)
{
machine_mode wmode = mode;
rtx dst = operands[0];
rtx src1 = operands[1];
rtx src2 = operands[2];
rtx op, clob, tem;
/* If we cannot handle the operation directly, use a temp register. */
if (!s390_logical_operator_ok_p (operands))
dst = gen_reg_rtx (mode);
/* QImode and HImode patterns make sense only if we have a destination
in memory. Otherwise perform the operation in SImode. */
if ((mode == QImode || mode == HImode) && GET_CODE (dst) != MEM)
wmode = SImode;
/* Widen operands if required. */
if (mode != wmode)
{
if (GET_CODE (dst) == SUBREG
&& (tem = simplify_subreg (wmode, dst, mode, 0)) != 0)
dst = tem;
else if (REG_P (dst))
dst = gen_rtx_SUBREG (wmode, dst, 0);
else
dst = gen_reg_rtx (wmode);
if (GET_CODE (src1) == SUBREG
&& (tem = simplify_subreg (wmode, src1, mode, 0)) != 0)
src1 = tem;
else if (GET_MODE (src1) != VOIDmode)
src1 = gen_rtx_SUBREG (wmode, force_reg (mode, src1), 0);
if (GET_CODE (src2) == SUBREG
&& (tem = simplify_subreg (wmode, src2, mode, 0)) != 0)
src2 = tem;
else if (GET_MODE (src2) != VOIDmode)
src2 = gen_rtx_SUBREG (wmode, force_reg (mode, src2), 0);
}
/* Emit the instruction. */
op = gen_rtx_SET (dst, gen_rtx_fmt_ee (code, wmode, src1, src2));
clob = gen_rtx_CLOBBER (VOIDmode, gen_rtx_REG (CCmode, CC_REGNUM));
emit_insn (gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, op, clob)));
/* Fix up the destination if needed. */
if (dst != operands[0])
emit_move_insn (operands[0], gen_lowpart (mode, dst));
}
/* Check whether OPERANDS are OK for a logical operation (AND, IOR, XOR). */
bool
s390_logical_operator_ok_p (rtx *operands)
{
/* If the destination operand is in memory, it needs to coincide
with one of the source operands. After reload, it has to be
the first source operand. */
if (GET_CODE (operands[0]) == MEM)
return rtx_equal_p (operands[0], operands[1])
|| (!reload_completed && rtx_equal_p (operands[0], operands[2]));
return true;
}
/* Narrow logical operation CODE of memory operand MEMOP with immediate
operand IMMOP to switch from SS to SI type instructions. */
void
s390_narrow_logical_operator (enum rtx_code code, rtx *memop, rtx *immop)
{
int def = code == AND ? -1 : 0;
HOST_WIDE_INT mask;
int part;
gcc_assert (GET_CODE (*memop) == MEM);
gcc_assert (!MEM_VOLATILE_P (*memop));
mask = s390_extract_part (*immop, QImode, def);
part = s390_single_part (*immop, GET_MODE (*memop), QImode, def);
gcc_assert (part >= 0);
*memop = adjust_address (*memop, QImode, part);
*immop = gen_int_mode (mask, QImode);
}
/* How to allocate a 'struct machine_function'. */
static struct machine_function *
s390_init_machine_status (void)
{
return ggc_cleared_alloc<machine_function> ();
}
/* Map for smallest class containing reg regno. */
const enum reg_class regclass_map[FIRST_PSEUDO_REGISTER] =
{ GENERAL_REGS, ADDR_REGS, ADDR_REGS, ADDR_REGS, /* 0 */
ADDR_REGS, ADDR_REGS, ADDR_REGS, ADDR_REGS, /* 4 */
ADDR_REGS, ADDR_REGS, ADDR_REGS, ADDR_REGS, /* 8 */
ADDR_REGS, ADDR_REGS, ADDR_REGS, ADDR_REGS, /* 12 */
FP_REGS, FP_REGS, FP_REGS, FP_REGS, /* 16 */
FP_REGS, FP_REGS, FP_REGS, FP_REGS, /* 20 */
FP_REGS, FP_REGS, FP_REGS, FP_REGS, /* 24 */
FP_REGS, FP_REGS, FP_REGS, FP_REGS, /* 28 */
ADDR_REGS, CC_REGS, ADDR_REGS, ADDR_REGS, /* 32 */
ACCESS_REGS, ACCESS_REGS, VEC_REGS, VEC_REGS, /* 36 */
VEC_REGS, VEC_REGS, VEC_REGS, VEC_REGS, /* 40 */
VEC_REGS, VEC_REGS, VEC_REGS, VEC_REGS, /* 44 */
VEC_REGS, VEC_REGS, VEC_REGS, VEC_REGS, /* 48 */
VEC_REGS, VEC_REGS /* 52 */
};
/* Return attribute type of insn. */
static enum attr_type
s390_safe_attr_type (rtx_insn *insn)
{
if (recog_memoized (insn) >= 0)
return get_attr_type (insn);
else
return TYPE_NONE;
}
/* Return attribute relative_long of insn. */
static bool
s390_safe_relative_long_p (rtx_insn *insn)
{
if (recog_memoized (insn) >= 0)
return get_attr_relative_long (insn) == RELATIVE_LONG_YES;
else
return false;
}
/* Return true if DISP is a valid short displacement. */
static bool
s390_short_displacement (rtx disp)
{
/* No displacement is OK. */
if (!disp)
return true;
/* Without the long displacement facility we don't need to
distingiush between long and short displacement. */
if (!TARGET_LONG_DISPLACEMENT)
return true;
/* Integer displacement in range. */
if (GET_CODE (disp) == CONST_INT)
return INTVAL (disp) >= 0 && INTVAL (disp) < 4096;
/* GOT offset is not OK, the GOT can be large. */
if (GET_CODE (disp) == CONST
&& GET_CODE (XEXP (disp, 0)) == UNSPEC
&& (XINT (XEXP (disp, 0), 1) == UNSPEC_GOT
|| XINT (XEXP (disp, 0), 1) == UNSPEC_GOTNTPOFF))
return false;
/* All other symbolic constants are literal pool references,
which are OK as the literal pool must be small. */
if (GET_CODE (disp) == CONST)
return true;
return false;
}
/* Attempts to split `ref', which should be UNSPEC_LTREF, into (base + `disp').
If successful, also determines the
following characteristics of `ref': `is_ptr' - whether it can be an
LA argument, `is_base_ptr' - whether the resulting base is a well-known
base register (stack/frame pointer, etc), `is_pool_ptr` - whether it is
considered a literal pool pointer for purposes of avoiding two different
literal pool pointers per insn during or after reload (`B' constraint). */
static bool
s390_decompose_constant_pool_ref (rtx *ref, rtx *disp, bool *is_ptr,
bool *is_base_ptr, bool *is_pool_ptr)
{
if (!*ref)
return true;
if (GET_CODE (*ref) == UNSPEC)
switch (XINT (*ref, 1))
{
case UNSPEC_LTREF:
if (!*disp)
*disp = gen_rtx_UNSPEC (Pmode,
gen_rtvec (1, XVECEXP (*ref, 0, 0)),
UNSPEC_LTREL_OFFSET);
else
return false;
*ref = XVECEXP (*ref, 0, 1);
break;
default:
return false;
}
if (!REG_P (*ref) || GET_MODE (*ref) != Pmode)
return false;
if (REGNO (*ref) == STACK_POINTER_REGNUM
|| REGNO (*ref) == FRAME_POINTER_REGNUM
|| ((reload_completed || reload_in_progress)
&& frame_pointer_needed
&& REGNO (*ref) == HARD_FRAME_POINTER_REGNUM)
|| REGNO (*ref) == ARG_POINTER_REGNUM
|| (flag_pic
&& REGNO (*ref) == PIC_OFFSET_TABLE_REGNUM))
*is_ptr = *is_base_ptr = true;
if ((reload_completed || reload_in_progress)
&& *ref == cfun->machine->base_reg)
*is_ptr = *is_base_ptr = *is_pool_ptr = true;
return true;
}
/* Decompose a RTL expression ADDR for a memory address into
its components, returned in OUT.
Returns false if ADDR is not a valid memory address, true
otherwise. If OUT is NULL, don't return the components,
but check for validity only.
Note: Only addresses in canonical form are recognized.
LEGITIMIZE_ADDRESS should convert non-canonical forms to the
canonical form so that they will be recognized. */
static int
s390_decompose_address (rtx addr, struct s390_address *out)
{
HOST_WIDE_INT offset = 0;
rtx base = NULL_RTX;
rtx indx = NULL_RTX;
rtx disp = NULL_RTX;
rtx orig_disp;
bool pointer = false;
bool base_ptr = false;
bool indx_ptr = false;
bool literal_pool = false;
/* We may need to substitute the literal pool base register into the address
below. However, at this point we do not know which register is going to
be used as base, so we substitute the arg pointer register. This is going
to be treated as holding a pointer below -- it shouldn't be used for any
other purpose. */
rtx fake_pool_base = gen_rtx_REG (Pmode, ARG_POINTER_REGNUM);
/* Decompose address into base + index + displacement. */
if (GET_CODE (addr) == REG || GET_CODE (addr) == UNSPEC)
base = addr;
else if (GET_CODE (addr) == PLUS)
{
rtx op0 = XEXP (addr, 0);
rtx op1 = XEXP (addr, 1);
enum rtx_code code0 = GET_CODE (op0);
enum rtx_code code1 = GET_CODE (op1);
if (code0 == REG || code0 == UNSPEC)
{
if (code1 == REG || code1 == UNSPEC)
{
indx = op0; /* index + base */
base = op1;
}
else
{
base = op0; /* base + displacement */
disp = op1;
}
}
else if (code0 == PLUS)
{
indx = XEXP (op0, 0); /* index + base + disp */
base = XEXP (op0, 1);
disp = op1;
}
else
{
return false;
}
}
else
disp = addr; /* displacement */
/* Extract integer part of displacement. */
orig_disp = disp;
if (disp)
{
if (GET_CODE (disp) == CONST_INT)
{
offset = INTVAL (disp);
disp = NULL_RTX;
}
else if (GET_CODE (disp) == CONST
&& GET_CODE (XEXP (disp, 0)) == PLUS
&& GET_CODE (XEXP (XEXP (disp, 0), 1)) == CONST_INT)
{
offset = INTVAL (XEXP (XEXP (disp, 0), 1));
disp = XEXP (XEXP (disp, 0), 0);
}
}
/* Strip off CONST here to avoid special case tests later. */
if (disp && GET_CODE (disp) == CONST)
disp = XEXP (disp, 0);
/* We can convert literal pool addresses to
displacements by basing them off the base register. */
if (disp && GET_CODE (disp) == SYMBOL_REF && CONSTANT_POOL_ADDRESS_P (disp))
{
if (base || indx)
return false;
base = fake_pool_base, literal_pool = true;
/* Mark up the displacement. */
disp = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, disp),
UNSPEC_LTREL_OFFSET);
}
/* Validate base register. */
if (!s390_decompose_constant_pool_ref (&base, &disp, &pointer, &base_ptr,
&literal_pool))
return false;
/* Validate index register. */
if (!s390_decompose_constant_pool_ref (&indx, &disp, &pointer, &indx_ptr,
&literal_pool))
return false;
/* Prefer to use pointer as base, not index. */
if (base && indx && !base_ptr
&& (indx_ptr || (!REG_POINTER (base) && REG_POINTER (indx))))
{
rtx tmp = base;
base = indx;
indx = tmp;
}
/* Validate displacement. */
if (!disp)
{
/* If virtual registers are involved, the displacement will change later
anyway as the virtual registers get eliminated. This could make a
valid displacement invalid, but it is more likely to make an invalid
displacement valid, because we sometimes access the register save area
via negative offsets to one of those registers.
Thus we don't check the displacement for validity here. If after
elimination the displacement turns out to be invalid after all,
this is fixed up by reload in any case. */
/* LRA maintains always displacements up to date and we need to
know the displacement is right during all LRA not only at the
final elimination. */
if (lra_in_progress
|| (base != arg_pointer_rtx
&& indx != arg_pointer_rtx
&& base != return_address_pointer_rtx
&& indx != return_address_pointer_rtx
&& base != frame_pointer_rtx
&& indx != frame_pointer_rtx
&& base != virtual_stack_vars_rtx
&& indx != virtual_stack_vars_rtx))
if (!DISP_IN_RANGE (offset))
return false;
}
else
{
/* All the special cases are pointers. */
pointer = true;
/* In the small-PIC case, the linker converts @GOT
and @GOTNTPOFF offsets to possible displacements. */
if (GET_CODE (disp) == UNSPEC
&& (XINT (disp, 1) == UNSPEC_GOT
|| XINT (disp, 1) == UNSPEC_GOTNTPOFF)
&& flag_pic == 1)
{
;
}
/* Accept pool label offsets. */
else if (GET_CODE (disp) == UNSPEC
&& XINT (disp, 1) == UNSPEC_POOL_OFFSET)
;
/* Accept literal pool references. */
else if (GET_CODE (disp) == UNSPEC
&& XINT (disp, 1) == UNSPEC_LTREL_OFFSET)
{
/* In case CSE pulled a non literal pool reference out of
the pool we have to reject the address. This is
especially important when loading the GOT pointer on non
zarch CPUs. In this case the literal pool contains an lt
relative offset to the _GLOBAL_OFFSET_TABLE_ label which
will most likely exceed the displacement. */
if (GET_CODE (XVECEXP (disp, 0, 0)) != SYMBOL_REF
|| !CONSTANT_POOL_ADDRESS_P (XVECEXP (disp, 0, 0)))
return false;
orig_disp = gen_rtx_CONST (Pmode, disp);
if (offset)
{
/* If we have an offset, make sure it does not
exceed the size of the constant pool entry.
Otherwise we might generate an out-of-range
displacement for the base register form. */
rtx sym = XVECEXP (disp, 0, 0);
if (offset >= GET_MODE_SIZE (get_pool_mode (sym)))
return false;
orig_disp = plus_constant (Pmode, orig_disp, offset);
}
}
else
return false;
}
if (!base && !indx)
pointer = true;
if (out)
{
out->base = base;
out->indx = indx;
out->disp = orig_disp;
out->pointer = pointer;
out->literal_pool = literal_pool;
}
return true;
}
/* Decompose a RTL expression OP for an address style operand into its
components, and return the base register in BASE and the offset in
OFFSET. While OP looks like an address it is never supposed to be
used as such.
Return true if OP is a valid address operand, false if not. */
bool
s390_decompose_addrstyle_without_index (rtx op, rtx *base,
HOST_WIDE_INT *offset)
{
rtx off = NULL_RTX;
/* We can have an integer constant, an address register,
or a sum of the two. */
if (CONST_SCALAR_INT_P (op))
{
off = op;
op = NULL_RTX;
}
if (op && GET_CODE (op) == PLUS && CONST_SCALAR_INT_P (XEXP (op, 1)))
{
off = XEXP (op, 1);
op = XEXP (op, 0);
}
while (op && GET_CODE (op) == SUBREG)
op = SUBREG_REG (op);
if (op && GET_CODE (op) != REG)
return false;
if (offset)
{
if (off == NULL_RTX)
*offset = 0;
else if (CONST_INT_P (off))
*offset = INTVAL (off);
else if (CONST_WIDE_INT_P (off))
/* The offset will anyway be cut down to 12 bits so take just
the lowest order chunk of the wide int. */
*offset = CONST_WIDE_INT_ELT (off, 0);
else
gcc_unreachable ();
}
if (base)
*base = op;
return true;
}
/* Check that OP is a valid shift count operand.
It should be of the following structure:
(subreg (and (plus (reg imm_op)) 2^k-1) 7)
where subreg, and and plus are optional.
If IMPLICIT_MASK is > 0 and OP contains and
(AND ... immediate)
it is checked whether IMPLICIT_MASK and the immediate match.
Otherwise, no checking is performed.
*/
bool
s390_valid_shift_count (rtx op, HOST_WIDE_INT implicit_mask)
{
/* Strip subreg. */
while (GET_CODE (op) == SUBREG && subreg_lowpart_p (op))
op = XEXP (op, 0);
/* Check for an and with proper constant. */
if (GET_CODE (op) == AND)
{
rtx op1 = XEXP (op, 0);
rtx imm = XEXP (op, 1);
if (GET_CODE (op1) == SUBREG && subreg_lowpart_p (op1))
op1 = XEXP (op1, 0);
if (!(register_operand (op1, GET_MODE (op1)) || GET_CODE (op1) == PLUS))
return false;
if (!immediate_operand (imm, GET_MODE (imm)))
return false;
HOST_WIDE_INT val = INTVAL (imm);
if (implicit_mask > 0
&& (val & implicit_mask) != implicit_mask)
return false;
op = op1;
}
/* Check the rest. */
return s390_decompose_addrstyle_without_index (op, NULL, NULL);
}
/* Return true if CODE is a valid address without index. */
bool
s390_legitimate_address_without_index_p (rtx op)
{
struct s390_address addr;
if (!s390_decompose_address (XEXP (op, 0), &addr))
return false;
if (addr.indx)
return false;
return true;
}
/* Return TRUE if ADDR is an operand valid for a load/store relative
instruction. Be aware that the alignment of the operand needs to
be checked separately.
Valid addresses are single references or a sum of a reference and a
constant integer. Return these parts in SYMREF and ADDEND. You can
pass NULL in REF and/or ADDEND if you are not interested in these
values. */
static bool
s390_loadrelative_operand_p (rtx addr, rtx *symref, HOST_WIDE_INT *addend)
{
HOST_WIDE_INT tmpaddend = 0;
if (GET_CODE (addr) == CONST)
addr = XEXP (addr, 0);
if (GET_CODE (addr) == PLUS)
{
if (!CONST_INT_P (XEXP (addr, 1)))
return false;
tmpaddend = INTVAL (XEXP (addr, 1));
addr = XEXP (addr, 0);
}
if (GET_CODE (addr) == SYMBOL_REF
|| (GET_CODE (addr) == UNSPEC
&& (XINT (addr, 1) == UNSPEC_GOTENT
|| XINT (addr, 1) == UNSPEC_PLT31)))
{
if (symref)
*symref = addr;
if (addend)
*addend = tmpaddend;
return true;
}
return false;
}
/* Return true if the address in OP is valid for constraint letter C
if wrapped in a MEM rtx. Set LIT_POOL_OK to true if it literal
pool MEMs should be accepted. Only the Q, R, S, T constraint
letters are allowed for C. */
static int
s390_check_qrst_address (char c, rtx op, bool lit_pool_ok)
{
rtx symref;
struct s390_address addr;
bool decomposed = false;
if (!address_operand (op, GET_MODE (op)))
return 0;
/* This check makes sure that no symbolic address (except literal
pool references) are accepted by the R or T constraints. */
if (s390_loadrelative_operand_p (op, &symref, NULL)
&& (!lit_pool_ok
|| !SYMBOL_REF_P (symref)
|| !CONSTANT_POOL_ADDRESS_P (symref)))
return 0;
/* Ensure literal pool references are only accepted if LIT_POOL_OK. */
if (!lit_pool_ok)
{
if (!s390_decompose_address (op, &addr))
return 0;
if (addr.literal_pool)
return 0;
decomposed = true;
}
/* With reload, we sometimes get intermediate address forms that are
actually invalid as-is, but we need to accept them in the most
generic cases below ('R' or 'T'), since reload will in fact fix
them up. LRA behaves differently here; we never see such forms,
but on the other hand, we need to strictly reject every invalid
address form. After both reload and LRA invalid address forms
must be rejected, because nothing will fix them up later. Perform
this check right up front. */
if (lra_in_progress || reload_completed)
{
if (!decomposed && !s390_decompose_address (op, &addr))
return 0;
decomposed = true;
}
switch (c)
{
case 'Q': /* no index short displacement */
if (!decomposed && !s390_decompose_address (op, &addr))
return 0;
if (addr.indx)
return 0;
if (!s390_short_displacement (addr.disp))
return 0;
break;
case 'R': /* with index short displacement */
if (TARGET_LONG_DISPLACEMENT)
{
if (!decomposed && !s390_decompose_address (op, &addr))
return 0;
if (!s390_short_displacement (addr.disp))
return 0;
}
/* Any invalid address here will be fixed up by reload,
so accept it for the most generic constraint. */
break;
case 'S': /* no index long displacement */
if (!decomposed && !s390_decompose_address (op, &addr))
return 0;
if (addr.indx)
return 0;
break;
case 'T': /* with index long displacement */
/* Any invalid address here will be fixed up by reload,
so accept it for the most generic constraint. */
break;
default:
return 0;
}
return 1;
}
/* Evaluates constraint strings described by the regular expression
([A|B|Z](Q|R|S|T))|Y and returns 1 if OP is a valid operand for
the constraint given in STR, or 0 else. */
int
s390_mem_constraint (const char *str, rtx op)
{
char c = str[0];
switch (c)
{
case 'A':
/* Check for offsettable variants of memory constraints. */
if (!MEM_P (op) || MEM_VOLATILE_P (op))
return 0;
if ((reload_completed || reload_in_progress)
? !offsettable_memref_p (op) : !offsettable_nonstrict_memref_p (op))
return 0;
return s390_check_qrst_address (str[1], XEXP (op, 0), true);
case 'B':
/* Check for non-literal-pool variants of memory constraints. */
if (!MEM_P (op))
return 0;
return s390_check_qrst_address (str[1], XEXP (op, 0), false);
case 'Q':
case 'R':
case 'S':
case 'T':
if (GET_CODE (op) != MEM)
return 0;
return s390_check_qrst_address (c, XEXP (op, 0), true);
case 'Y':
/* Simply check for the basic form of a shift count. Reload will
take care of making sure we have a proper base register. */
if (!s390_decompose_addrstyle_without_index (op, NULL, NULL))
return 0;
break;
case 'Z':
return s390_check_qrst_address (str[1], op, true);
default:
return 0;
}
return 1;
}
/* Evaluates constraint strings starting with letter O. Input
parameter C is the second letter following the "O" in the constraint
string. Returns 1 if VALUE meets the respective constraint and 0
otherwise. */
int
s390_O_constraint_str (const char c, HOST_WIDE_INT value)
{
if (!TARGET_EXTIMM)
return 0;
switch (c)
{
case 's':
return trunc_int_for_mode (value, SImode) == value;
case 'p':
return value == 0
|| s390_single_part (GEN_INT (value), DImode, SImode, 0) == 1;
case 'n':
return s390_single_part (GEN_INT (value - 1), DImode, SImode, -1) == 1;
default:
gcc_unreachable ();
}
}
/* Evaluates constraint strings starting with letter N. Parameter STR
contains the letters following letter "N" in the constraint string.
Returns true if VALUE matches the constraint. */
int
s390_N_constraint_str (const char *str, HOST_WIDE_INT value)
{
machine_mode mode, part_mode;
int def;
int part, part_goal;
if (str[0] == 'x')
part_goal = -1;
else
part_goal = str[0] - '0';
switch (str[1])
{
case 'Q':
part_mode = QImode;
break;
case 'H':
part_mode = HImode;
break;
case 'S':
part_mode = SImode;
break;
default:
return 0;
}
switch (str[2])
{
case 'H':
mode = HImode;
break;
case 'S':
mode = SImode;
break;
case 'D':
mode = DImode;
break;
default:
return 0;
}
switch (str[3])
{
case '0':
def = 0;
break;
case 'F':
def = -1;
break;
default:
return 0;
}
if (GET_MODE_SIZE (mode) <= GET_MODE_SIZE (part_mode))
return 0;
part = s390_single_part (GEN_INT (value), mode, part_mode, def);
if (part < 0)
return 0;
if (part_goal != -1 && part_goal != part)
return 0;
return 1;
}
/* Returns true if the input parameter VALUE is a float zero. */
int
s390_float_const_zero_p (rtx value)
{
return (GET_MODE_CLASS (GET_MODE (value)) == MODE_FLOAT
&& value == CONST0_RTX (GET_MODE (value)));
}
/* Implement TARGET_REGISTER_MOVE_COST. */
static int
s390_register_move_cost (machine_mode mode,
reg_class_t from, reg_class_t to)
{
/* On s390, copy between fprs and gprs is expensive. */
/* It becomes somewhat faster having ldgr/lgdr. */
if (TARGET_Z10 && GET_MODE_SIZE (mode) == 8)
{
/* ldgr is single cycle. */
if (reg_classes_intersect_p (from, GENERAL_REGS)
&& reg_classes_intersect_p (to, FP_REGS))
return 1;
/* lgdr needs 3 cycles. */
if (reg_classes_intersect_p (to, GENERAL_REGS)
&& reg_classes_intersect_p (from, FP_REGS))
return 3;
}
/* Otherwise copying is done via memory. */
if ((reg_classes_intersect_p (from, GENERAL_REGS)
&& reg_classes_intersect_p (to, FP_REGS))
|| (reg_classes_intersect_p (from, FP_REGS)
&& reg_classes_intersect_p (to, GENERAL_REGS)))
return 10;
/* We usually do not want to copy via CC. */
if (reg_classes_intersect_p (from, CC_REGS)
|| reg_classes_intersect_p (to, CC_REGS))
return 5;
return 1;
}
/* Implement TARGET_MEMORY_MOVE_COST. */
static int
s390_memory_move_cost (machine_mode mode ATTRIBUTE_UNUSED,
reg_class_t rclass ATTRIBUTE_UNUSED,
bool in ATTRIBUTE_UNUSED)
{
return 2;
}
/* Compute a (partial) cost for rtx X. Return true if the complete
cost has been computed, and false if subexpressions should be
scanned. In either case, *TOTAL contains the cost result. The
initial value of *TOTAL is the default value computed by
rtx_cost. It may be left unmodified. OUTER_CODE contains the
code of the superexpression of x. */
static bool
s390_rtx_costs (rtx x, machine_mode mode, int outer_code,
int opno ATTRIBUTE_UNUSED,
int *total, bool speed ATTRIBUTE_UNUSED)
{
int code = GET_CODE (x);
switch (code)
{
case CONST:
case CONST_INT:
case LABEL_REF:
case SYMBOL_REF:
case CONST_DOUBLE:
case CONST_WIDE_INT:
case MEM:
*total = 0;
return true;
case SET:
{
/* Without this a conditional move instruction would be
accounted as 3 * COSTS_N_INSNS (set, if_then_else,
comparison operator). That's a bit pessimistic. */
if (!TARGET_Z196 || GET_CODE (SET_SRC (x)) != IF_THEN_ELSE)
return false;
rtx cond = XEXP (SET_SRC (x), 0);
if (!CC_REG_P (XEXP (cond, 0)) || !CONST_INT_P (XEXP (cond, 1)))
return false;
/* It is going to be a load/store on condition. Make it
slightly more expensive than a normal load. */
*total = COSTS_N_INSNS (1) + 1;
rtx dst = SET_DEST (x);
rtx then = XEXP (SET_SRC (x), 1);
rtx els = XEXP (SET_SRC (x), 2);
/* It is a real IF-THEN-ELSE. An additional move will be
needed to implement that. */
if (!TARGET_Z15
&& reload_completed
&& !rtx_equal_p (dst, then)
&& !rtx_equal_p (dst, els))
*total += COSTS_N_INSNS (1) / 2;
/* A minor penalty for constants we cannot directly handle. */
if ((CONST_INT_P (then) || CONST_INT_P (els))
&& (!TARGET_Z13 || MEM_P (dst)
|| (CONST_INT_P (then) && !satisfies_constraint_K (then))
|| (CONST_INT_P (els) && !satisfies_constraint_K (els))))
*total += COSTS_N_INSNS (1) / 2;
/* A store on condition can only handle register src operands. */
if (MEM_P (dst) && (!REG_P (then) || !REG_P (els)))
*total += COSTS_N_INSNS (1) / 2;
return true;
}
case IOR:
/* nnrk, nngrk */
if (TARGET_Z15
&& (mode == SImode || mode == DImode)
&& GET_CODE (XEXP (x, 0)) == NOT
&& GET_CODE (XEXP (x, 1)) == NOT)
{
*total = COSTS_N_INSNS (1);
if (!REG_P (XEXP (XEXP (x, 0), 0)))
*total += 1;
if (!REG_P (XEXP (XEXP (x, 1), 0)))
*total += 1;
return true;
}
/* risbg */
if (GET_CODE (XEXP (x, 0)) == AND
&& GET_CODE (XEXP (x, 1)) == ASHIFT
&& REG_P (XEXP (XEXP (x, 0), 0))
&& REG_P (XEXP (XEXP (x, 1), 0))
&& CONST_INT_P (XEXP (XEXP (x, 0), 1))
&& CONST_INT_P (XEXP (XEXP (x, 1), 1))
&& (UINTVAL (XEXP (XEXP (x, 0), 1)) ==
(HOST_WIDE_INT_1U << UINTVAL (XEXP (XEXP (x, 1), 1))) - 1))
{
*total = COSTS_N_INSNS (2);
return true;
}
/* ~AND on a 128 bit mode. This can be done using a vector
instruction. */
if (TARGET_VXE
&& GET_CODE (XEXP (x, 0)) == NOT
&& GET_CODE (XEXP (x, 1)) == NOT
&& REG_P (XEXP (XEXP (x, 0), 0))
&& REG_P (XEXP (XEXP (x, 1), 0))
&& GET_MODE_SIZE (GET_MODE (XEXP (XEXP (x, 0), 0))) == 16
&& s390_hard_regno_mode_ok (VR0_REGNUM,
GET_MODE (XEXP (XEXP (x, 0), 0))))
{
*total = COSTS_N_INSNS (1);
return true;
}
*total = COSTS_N_INSNS (1);
return false;
case AND:
/* nork, nogrk */
if (TARGET_Z15
&& (mode == SImode || mode == DImode)
&& GET_CODE (XEXP (x, 0)) == NOT
&& GET_CODE (XEXP (x, 1)) == NOT)
{
*total = COSTS_N_INSNS (1);
if (!REG_P (XEXP (XEXP (x, 0), 0)))
*total += 1;
if (!REG_P (XEXP (XEXP (x, 1), 0)))
*total += 1;
return true;
}
/* fallthrough */
case ASHIFT:
case ASHIFTRT:
case LSHIFTRT:
case ROTATE:
case ROTATERT:
case XOR:
case NEG:
case NOT:
case PLUS:
case MINUS:
*total = COSTS_N_INSNS (1);
return false;
case MULT:
switch (mode)
{
case E_SImode:
{
rtx left = XEXP (x, 0);
rtx right = XEXP (x, 1);
if (GET_CODE (right) == CONST_INT
&& CONST_OK_FOR_K (INTVAL (right)))
*total = s390_cost->mhi;
else if (GET_CODE (left) == SIGN_EXTEND)
*total = s390_cost->mh;
else
*total = s390_cost->ms; /* msr, ms, msy */
break;
}
case E_DImode:
{
rtx left = XEXP (x, 0);
rtx right = XEXP (x, 1);
if (TARGET_ZARCH)
{
if (GET_CODE (right) == CONST_INT
&& CONST_OK_FOR_K (INTVAL (right)))
*total = s390_cost->mghi;
else if (GET_CODE (left) == SIGN_EXTEND)
*total = s390_cost->msgf;
else
*total = s390_cost->msg; /* msgr, msg */
}
else /* TARGET_31BIT */
{
if (GET_CODE (left) == SIGN_EXTEND
&& GET_CODE (right) == SIGN_EXTEND)
/* mulsidi case: mr, m */
*total = s390_cost->m;
else if (GET_CODE (left) == ZERO_EXTEND
&& GET_CODE (right) == ZERO_EXTEND)
/* umulsidi case: ml, mlr */
*total = s390_cost->ml;
else
/* Complex calculation is required. */
*total = COSTS_N_INSNS (40);
}
break;
}
case E_SFmode:
case E_DFmode:
*total = s390_cost->mult_df;
break;
case E_TFmode:
*total = s390_cost->mxbr;
break;
default:
return false;
}
return false;
case FMA:
switch (mode)
{
case E_DFmode:
*total = s390_cost->madbr;
break;
case E_SFmode:
*total = s390_cost->maebr;
break;
default:
return false;
}
/* Negate in the third argument is free: FMSUB. */
if (GET_CODE (XEXP (x, 2)) == NEG)
{
*total += (rtx_cost (XEXP (x, 0), mode, FMA, 0, speed)
+ rtx_cost (XEXP (x, 1), mode, FMA, 1, speed)
+ rtx_cost (XEXP (XEXP (x, 2), 0), mode, FMA, 2, speed));
return true;
}
return false;
case UDIV:
case UMOD:
if (mode == TImode) /* 128 bit division */
*total = s390_cost->dlgr;
else if (mode == DImode)
{
rtx right = XEXP (x, 1);
if (GET_CODE (right) == ZERO_EXTEND) /* 64 by 32 bit division */
*total = s390_cost->dlr;
else /* 64 by 64 bit division */
*total = s390_cost->dlgr;
}
else if (mode == SImode) /* 32 bit division */
*total = s390_cost->dlr;
return false;
case DIV:
case MOD:
if (mode == DImode)
{
rtx right = XEXP (x, 1);
if (GET_CODE (right) == ZERO_EXTEND) /* 64 by 32 bit division */
if (TARGET_ZARCH)
*total = s390_cost->dsgfr;
else
*total = s390_cost->dr;
else /* 64 by 64 bit division */
*total = s390_cost->dsgr;
}
else if (mode == SImode) /* 32 bit division */
*total = s390_cost->dlr;
else if (mode == SFmode)
{
*total = s390_cost->debr;
}
else if (mode == DFmode)
{
*total = s390_cost->ddbr;
}
else if (mode == TFmode)
{
*total = s390_cost->dxbr;
}
return false;
case SQRT:
if (mode == SFmode)
*total = s390_cost->sqebr;
else if (mode == DFmode)
*total = s390_cost->sqdbr;
else /* TFmode */
*total = s390_cost->sqxbr;
return false;
case SIGN_EXTEND:
case ZERO_EXTEND:
if (outer_code == MULT || outer_code == DIV || outer_code == MOD
|| outer_code == PLUS || outer_code == MINUS
|| outer_code == COMPARE)
*total = 0;
return false;
case COMPARE:
*total = COSTS_N_INSNS (1);
/* nxrk, nxgrk ~(a^b)==0 */
if (TARGET_Z15
&& GET_CODE (XEXP (x, 0)) == NOT
&& XEXP (x, 1) == const0_rtx
&& GET_CODE (XEXP (XEXP (x, 0), 0)) == XOR
&& (GET_MODE (XEXP (x, 0)) == SImode || GET_MODE (XEXP (x, 0)) == DImode)
&& mode == CCZmode)
{
if (!REG_P (XEXP (XEXP (XEXP (x, 0), 0), 0)))
*total += 1;
if (!REG_P (XEXP (XEXP (XEXP (x, 0), 0), 1)))
*total += 1;
return true;
}
/* nnrk, nngrk, nork, nogrk */
if (TARGET_Z15
&& (GET_CODE (XEXP (x, 0)) == AND || GET_CODE (XEXP (x, 0)) == IOR)
&& XEXP (x, 1) == const0_rtx
&& (GET_MODE (XEXP (x, 0)) == SImode || GET_MODE (XEXP (x, 0)) == DImode)
&& GET_CODE (XEXP (XEXP (x, 0), 0)) == NOT
&& GET_CODE (XEXP (XEXP (x, 0), 1)) == NOT
&& mode == CCZmode)
{
if (!REG_P (XEXP (XEXP (XEXP (x, 0), 0), 0)))
*total += 1;
if (!REG_P (XEXP (XEXP (XEXP (x, 0), 1), 0)))
*total += 1;
return true;
}
if (GET_CODE (XEXP (x, 0)) == AND
&& GET_CODE (XEXP (x, 1)) == CONST_INT
&& GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT)
{
rtx op0 = XEXP (XEXP (x, 0), 0);
rtx op1 = XEXP (XEXP (x, 0), 1);
rtx op2 = XEXP (x, 1);
if (memory_operand (op0, GET_MODE (op0))
&& s390_tm_ccmode (op1, op2, 0) != VOIDmode)
return true;
if (register_operand (op0, GET_MODE (op0))
&& s390_tm_ccmode (op1, op2, 1) != VOIDmode)
return true;
}
return false;
default:
return false;
}
}
/* Return the cost of an address rtx ADDR. */
static int
s390_address_cost (rtx addr, machine_mode mode ATTRIBUTE_UNUSED,
addr_space_t as ATTRIBUTE_UNUSED,
bool speed ATTRIBUTE_UNUSED)
{
struct s390_address ad;
if (!s390_decompose_address (addr, &ad))
return 1000;
return ad.indx? COSTS_N_INSNS (1) + 1 : COSTS_N_INSNS (1);
}
/* Implement targetm.vectorize.builtin_vectorization_cost. */
static int
s390_builtin_vectorization_cost (enum vect_cost_for_stmt type_of_cost,
tree vectype,
int misalign ATTRIBUTE_UNUSED)
{
switch (type_of_cost)
{
case scalar_stmt:
case scalar_load:
case scalar_store:
case vector_stmt:
case vector_load:
case vector_store:
case vector_gather_load:
case vector_scatter_store:
case vec_to_scalar:
case scalar_to_vec:
case cond_branch_not_taken:
case vec_perm:
case vec_promote_demote:
case unaligned_load:
case unaligned_store:
return 1;
case cond_branch_taken:
return 3;
case vec_construct:
return TYPE_VECTOR_SUBPARTS (vectype) - 1;
default:
gcc_unreachable ();
}
}
/* If OP is a SYMBOL_REF of a thread-local symbol, return its TLS mode,
otherwise return 0. */
int
tls_symbolic_operand (rtx op)
{
if (GET_CODE (op) != SYMBOL_REF)
return 0;
return SYMBOL_REF_TLS_MODEL (op);
}
/* Split DImode access register reference REG (on 64-bit) into its constituent
low and high parts, and store them into LO and HI. Note that gen_lowpart/
gen_highpart cannot be used as they assume all registers are word-sized,
while our access registers have only half that size. */
void
s390_split_access_reg (rtx reg, rtx *lo, rtx *hi)
{
gcc_assert (TARGET_64BIT);
gcc_assert (ACCESS_REG_P (reg));
gcc_assert (GET_MODE (reg) == DImode);
gcc_assert (!(REGNO (reg) & 1));
*lo = gen_rtx_REG (SImode, REGNO (reg) + 1);
*hi = gen_rtx_REG (SImode, REGNO (reg));
}
/* Return true if OP contains a symbol reference */
bool
symbolic_reference_mentioned_p (rtx op)
{
const char *fmt;
int i;
if (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == LABEL_REF)
return 1;
fmt = GET_RTX_FORMAT (GET_CODE (op));
for (i = GET_RTX_LENGTH (GET_CODE (op)) - 1; i >= 0; i--)
{
if (fmt[i] == 'E')
{
int j;
for (j = XVECLEN (op, i) - 1; j >= 0; j--)
if (symbolic_reference_mentioned_p (XVECEXP (op, i, j)))
return 1;
}
else if (fmt[i] == 'e' && symbolic_reference_mentioned_p (XEXP (op, i)))
return 1;
}
return 0;
}
/* Return true if OP contains a reference to a thread-local symbol. */
bool
tls_symbolic_reference_mentioned_p (rtx op)
{
const char *fmt;
int i;
if (GET_CODE (op) == SYMBOL_REF)
return tls_symbolic_operand (op);
fmt = GET_RTX_FORMAT (GET_CODE (op));
for (i = GET_RTX_LENGTH (GET_CODE (op)) - 1; i >= 0; i--)
{
if (fmt[i] == 'E')
{
int j;
for (j = XVECLEN (op, i) - 1; j >= 0; j--)
if (tls_symbolic_reference_mentioned_p (XVECEXP (op, i, j)))
return true;
}
else if (fmt[i] == 'e' && tls_symbolic_reference_mentioned_p (XEXP (op, i)))
return true;
}
return false;
}
/* Return true if OP is a legitimate general operand when
generating PIC code. It is given that flag_pic is on
and that OP satisfies CONSTANT_P. */
int
legitimate_pic_operand_p (rtx op)
{
/* Accept all non-symbolic constants. */
if (!SYMBOLIC_CONST (op))
return 1;
/* Accept addresses that can be expressed relative to (pc). */
if (larl_operand (op, VOIDmode))
return 1;
/* Reject everything else; must be handled
via emit_symbolic_move. */
return 0;
}
/* Returns true if the constant value OP is a legitimate general operand.
It is given that OP satisfies CONSTANT_P. */
static bool
s390_legitimate_constant_p (machine_mode mode, rtx op)
{
if (TARGET_VX && VECTOR_MODE_P (mode) && GET_CODE (op) == CONST_VECTOR)
{
if (GET_MODE_SIZE (mode) != 16)
return 0;
if (!satisfies_constraint_j00 (op)
&& !satisfies_constraint_jm1 (op)
&& !satisfies_constraint_jKK (op)
&& !satisfies_constraint_jxx (op)
&& !satisfies_constraint_jyy (op))
return 0;
}
/* Accept all non-symbolic constants. */
if (!SYMBOLIC_CONST (op))
return 1;
/* Accept immediate LARL operands. */
if (larl_operand (op, mode))
return 1;
/* Thread-local symbols are never legal constants. This is
so that emit_call knows that computing such addresses
might require a function call. */
if (TLS_SYMBOLIC_CONST (op))
return 0;
/* In the PIC case, symbolic constants must *not* be
forced into the literal pool. We accept them here,
so that they will be handled by emit_symbolic_move. */
if (flag_pic)
return 1;
/* All remaining non-PIC symbolic constants are
forced into the literal pool. */
return 0;
}
/* Determine if it's legal to put X into the constant pool. This
is not possible if X contains the address of a symbol that is
not constant (TLS) or not known at final link time (PIC). */
static bool
s390_cannot_force_const_mem (machine_mode mode, rtx x)
{
switch (GET_CODE (x))
{
case CONST_INT:
case CONST_DOUBLE:
case CONST_WIDE_INT:
case CONST_VECTOR:
/* Accept all non-symbolic constants. */
return false;
case NEG:
/* Accept an unary '-' only on scalar numeric constants. */
switch (GET_CODE (XEXP (x, 0)))
{
case CONST_INT:
case CONST_DOUBLE:
case CONST_WIDE_INT:
return false;
default:
return true;
}
case LABEL_REF:
/* Labels are OK iff we are non-PIC. */
return flag_pic != 0;
case SYMBOL_REF:
/* 'Naked' TLS symbol references are never OK,
non-TLS symbols are OK iff we are non-PIC. */
if (tls_symbolic_operand (x))
return true;
else
return flag_pic != 0;
case CONST:
return s390_cannot_force_const_mem (mode, XEXP (x, 0));
case PLUS:
case MINUS:
return s390_cannot_force_const_mem (mode, XEXP (x, 0))
|| s390_cannot_force_const_mem (mode, XEXP (x, 1));
case UNSPEC:
switch (XINT (x, 1))
{
/* Only lt-relative or GOT-relative UNSPECs are OK. */
case UNSPEC_LTREL_OFFSET:
case UNSPEC_GOT:
case UNSPEC_GOTOFF:
case UNSPEC_PLTOFF:
case UNSPEC_TLSGD:
case UNSPEC_TLSLDM:
case UNSPEC_NTPOFF:
case UNSPEC_DTPOFF:
case UNSPEC_GOTNTPOFF:
case UNSPEC_INDNTPOFF:
return false;
/* If the literal pool shares the code section, be put
execute template placeholders into the pool as well. */
case UNSPEC_INSN:
default:
return true;
}
break;
default:
gcc_unreachable ();
}
}
/* Returns true if the constant value OP is a legitimate general
operand during and after reload. The difference to
legitimate_constant_p is that this function will not accept
a constant that would need to be forced to the literal pool
before it can be used as operand.
This function accepts all constants which can be loaded directly
into a GPR. */
bool
legitimate_reload_constant_p (rtx op)
{
/* Accept la(y) operands. */
if (GET_CODE (op) == CONST_INT
&& DISP_IN_RANGE (INTVAL (op)))
return true;
/* Accept l(g)hi/l(g)fi operands. */
if (GET_CODE (op) == CONST_INT
&& (CONST_OK_FOR_K (INTVAL (op)) || CONST_OK_FOR_Os (INTVAL (op))))
return true;
/* Accept lliXX operands. */
if (TARGET_ZARCH
&& GET_CODE (op) == CONST_INT
&& trunc_int_for_mode (INTVAL (op), word_mode) == INTVAL (op)
&& s390_single_part (op, word_mode, HImode, 0) >= 0)
return true;
if (TARGET_EXTIMM
&& GET_CODE (op) == CONST_INT
&& trunc_int_for_mode (INTVAL (op), word_mode) == INTVAL (op)
&& s390_single_part (op, word_mode, SImode, 0) >= 0)
return true;
/* Accept larl operands. */
if (larl_operand (op, VOIDmode))
return true;
/* Accept floating-point zero operands that fit into a single GPR. */
if (GET_CODE (op) == CONST_DOUBLE
&& s390_float_const_zero_p (op)
&& GET_MODE_SIZE (GET_MODE (op)) <= UNITS_PER_WORD)
return true;
/* Accept double-word operands that can be split. */
if (GET_CODE (op) == CONST_WIDE_INT
|| (GET_CODE (op) == CONST_INT
&& trunc_int_for_mode (INTVAL (op), word_mode) != INTVAL (op)))
{
machine_mode dword_mode = word_mode == SImode ? DImode : TImode;
rtx hi = operand_subword (op, 0, 0, dword_mode);
rtx lo = operand_subword (op, 1, 0, dword_mode);
return legitimate_reload_constant_p (hi)
&& legitimate_reload_constant_p (lo);
}
/* Everything else cannot be handled without reload. */
return false;
}
/* Returns true if the constant value OP is a legitimate fp operand
during and after reload.
This function accepts all constants which can be loaded directly
into an FPR. */
static bool
legitimate_reload_fp_constant_p (rtx op)
{
/* Accept floating-point zero operands if the load zero instruction
can be used. Prior to z196 the load fp zero instruction caused a
performance penalty if the result is used as BFP number. */
if (TARGET_Z196
&& GET_CODE (op) == CONST_DOUBLE
&& s390_float_const_zero_p (op))
return true;
return false;
}
/* Returns true if the constant value OP is a legitimate vector operand
during and after reload.
This function accepts all constants which can be loaded directly
into an VR. */
static bool
legitimate_reload_vector_constant_p (rtx op)
{
if (TARGET_VX && GET_MODE_SIZE (GET_MODE (op)) == 16
&& (satisfies_constraint_j00 (op)
|| satisfies_constraint_jm1 (op)
|| satisfies_constraint_jKK (op)
|| satisfies_constraint_jxx (op)
|| satisfies_constraint_jyy (op)))
return true;
return false;
}
/* Given an rtx OP being reloaded into a reg required to be in class RCLASS,
return the class of reg to actually use. */
static reg_class_t
s390_preferred_reload_class (rtx op, reg_class_t rclass)
{
switch (GET_CODE (op))
{
/* Constants we cannot reload into general registers
must be forced into the literal pool. */
case CONST_VECTOR:
case CONST_DOUBLE:
case CONST_INT:
case CONST_WIDE_INT:
if (reg_class_subset_p (GENERAL_REGS, rclass)
&& legitimate_reload_constant_p (op))
return GENERAL_REGS;
else if (reg_class_subset_p (ADDR_REGS, rclass)
&& legitimate_reload_constant_p (op))
return ADDR_REGS;
else if (reg_class_subset_p (FP_REGS, rclass)
&& legitimate_reload_fp_constant_p (op))
return FP_REGS;
else if (reg_class_subset_p (VEC_REGS, rclass)
&& legitimate_reload_vector_constant_p (op))
return VEC_REGS;
return NO_REGS;
/* If a symbolic constant or a PLUS is reloaded,
it is most likely being used as an address, so
prefer ADDR_REGS. If 'class' is not a superset
of ADDR_REGS, e.g. FP_REGS, reject this reload. */
case CONST:
/* Symrefs cannot be pushed into the literal pool with -fPIC
so we *MUST NOT* return NO_REGS for these cases
(s390_cannot_force_const_mem will return true).
On the other hand we MUST return NO_REGS for symrefs with
invalid addend which might have been pushed to the literal
pool (no -fPIC). Usually we would expect them to be
handled via secondary reload but this does not happen if
they are used as literal pool slot replacement in reload
inheritance (see emit_input_reload_insns). */
if (GET_CODE (XEXP (op, 0)) == PLUS
&& GET_CODE (XEXP (XEXP(op, 0), 0)) == SYMBOL_REF
&& GET_CODE (XEXP (XEXP(op, 0), 1)) == CONST_INT)
{
if (flag_pic && reg_class_subset_p (ADDR_REGS, rclass))
return ADDR_REGS;
else
return NO_REGS;
}
/* fallthrough */
case LABEL_REF:
case SYMBOL_REF:
if (!legitimate_reload_constant_p (op))
return NO_REGS;
/* fallthrough */
case PLUS:
/* load address will be used. */
if (reg_class_subset_p (ADDR_REGS, rclass))
return ADDR_REGS;
else
return NO_REGS;
default:
break;
}
return rclass;
}
/* Return true if ADDR is SYMBOL_REF + addend with addend being a
multiple of ALIGNMENT and the SYMBOL_REF being naturally
aligned. */
bool
s390_check_symref_alignment (rtx addr, HOST_WIDE_INT alignment)
{
HOST_WIDE_INT addend;
rtx symref;
/* The "required alignment" might be 0 (e.g. for certain structs
accessed via BLKmode). Early abort in this case, as well as when
an alignment > 8 is required. */
if (alignment < 2 || alignment > 8)
return false;
if (!s390_loadrelative_operand_p (addr, &symref, &addend))
return false;
if (addend & (alignment - 1))
return false;
if (GET_CODE (symref) == SYMBOL_REF)
{
/* s390_encode_section_info is not called for anchors, since they don't
have corresponding VAR_DECLs. Therefore, we cannot rely on
SYMBOL_FLAG_NOTALIGN{2,4,8}_P returning useful information. */
if (SYMBOL_REF_ANCHOR_P (symref))
{
HOST_WIDE_INT block_offset = SYMBOL_REF_BLOCK_OFFSET (symref);
unsigned int block_alignment = (SYMBOL_REF_BLOCK (symref)->alignment
/ BITS_PER_UNIT);
gcc_assert (block_offset >= 0);
return ((block_offset & (alignment - 1)) == 0
&& block_alignment >= alignment);
}
/* We have load-relative instructions for 2-byte, 4-byte, and
8-byte alignment so allow only these. */
switch (alignment)
{
case 8: return !SYMBOL_FLAG_NOTALIGN8_P (symref);
case 4: return !SYMBOL_FLAG_NOTALIGN4_P (symref);
case 2: return !SYMBOL_FLAG_NOTALIGN2_P (symref);
default: return false;
}
}
if (GET_CODE (symref) == UNSPEC
&& alignment <= UNITS_PER_LONG)
return true;
return false;
}
/* ADDR is moved into REG using larl. If ADDR isn't a valid larl
operand SCRATCH is used to reload the even part of the address and
adding one. */
void
s390_reload_larl_operand (rtx reg, rtx addr, rtx scratch)
{
HOST_WIDE_INT addend;
rtx symref;
if (!s390_loadrelative_operand_p (addr, &symref, &addend))
gcc_unreachable ();
if (!(addend & 1))
/* Easy case. The addend is even so larl will do fine. */
emit_move_insn (reg, addr);
else
{
/* We can leave the scratch register untouched if the target
register is a valid base register. */
if (REGNO (reg) < FIRST_PSEUDO_REGISTER
&& REGNO_REG_CLASS (REGNO (reg)) == ADDR_REGS)
scratch = reg;
gcc_assert (REGNO (scratch) < FIRST_PSEUDO_REGISTER);
gcc_assert (REGNO_REG_CLASS (REGNO (scratch)) == ADDR_REGS);
if (addend != 1)
emit_move_insn (scratch,
gen_rtx_CONST (Pmode,
gen_rtx_PLUS (Pmode, symref,
GEN_INT (addend - 1))));
else
emit_move_insn (scratch, symref);
/* Increment the address using la in order to avoid clobbering cc. */
s390_load_address (reg, gen_rtx_PLUS (Pmode, scratch, const1_rtx));
}
}
/* Generate what is necessary to move between REG and MEM using
SCRATCH. The direction is given by TOMEM. */
void
s390_reload_symref_address (rtx reg, rtx mem, rtx scratch, bool tomem)
{
/* Reload might have pulled a constant out of the literal pool.
Force it back in. */
if (CONST_INT_P (mem) || GET_CODE (mem) == CONST_DOUBLE
|| GET_CODE (mem) == CONST_WIDE_INT
|| GET_CODE (mem) == CONST_VECTOR
|| GET_CODE (mem) == CONST)
mem = force_const_mem (GET_MODE (reg), mem);
gcc_assert (MEM_P (mem));
/* For a load from memory we can leave the scratch register
untouched if the target register is a valid base register. */
if (!tomem
&& REGNO (reg) < FIRST_PSEUDO_REGISTER
&& REGNO_REG_CLASS (REGNO (reg)) == ADDR_REGS
&& GET_MODE (reg) == GET_MODE (scratch))
scratch = reg;
/* Load address into scratch register. Since we can't have a
secondary reload for a secondary reload we have to cover the case
where larl would need a secondary reload here as well. */
s390_reload_larl_operand (scratch, XEXP (mem, 0), scratch);
/* Now we can use a standard load/store to do the move. */
if (tomem)
emit_move_insn (replace_equiv_address (mem, scratch), reg);
else
emit_move_insn (reg, replace_equiv_address (mem, scratch));
}
/* Inform reload about cases where moving X with a mode MODE to a register in
RCLASS requires an extra scratch or immediate register. Return the class
needed for the immediate register. */
static reg_class_t
s390_secondary_reload (bool in_p, rtx x, reg_class_t rclass_i,
machine_mode mode, secondary_reload_info *sri)
{
enum reg_class rclass = (enum reg_class) rclass_i;
/* Intermediate register needed. */
if (reg_classes_intersect_p (CC_REGS, rclass))
return GENERAL_REGS;
if (TARGET_VX)
{
/* The vst/vl vector move instructions allow only for short
displacements. */
if (MEM_P (x)
&& GET_CODE (XEXP (x, 0)) == PLUS
&& GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT
&& !SHORT_DISP_IN_RANGE(INTVAL (XEXP (XEXP (x, 0), 1)))
&& reg_class_subset_p (rclass, VEC_REGS)
&& (!reg_class_subset_p (rclass, FP_REGS)
|| (GET_MODE_SIZE (mode) > 8
&& s390_class_max_nregs (FP_REGS, mode) == 1)))
{
if (in_p)
sri->icode = (TARGET_64BIT ?
CODE_FOR_reloaddi_la_in :
CODE_FOR_reloadsi_la_in);
else
sri->icode = (TARGET_64BIT ?
CODE_FOR_reloaddi_la_out :
CODE_FOR_reloadsi_la_out);
}
}
if (TARGET_Z10)
{
HOST_WIDE_INT offset;
rtx symref;
/* On z10 several optimizer steps may generate larl operands with
an odd addend. */
if (in_p
&& s390_loadrelative_operand_p (x, &symref, &offset)
&& mode == Pmode
&& !SYMBOL_FLAG_NOTALIGN2_P (symref)
&& (offset & 1) == 1)
sri->icode = ((mode == DImode) ? CODE_FOR_reloaddi_larl_odd_addend_z10
: CODE_FOR_reloadsi_larl_odd_addend_z10);
/* Handle all the (mem (symref)) accesses we cannot use the z10
instructions for. */
if (MEM_P (x)
&& s390_loadrelative_operand_p (XEXP (x, 0), NULL, NULL)
&& (mode == QImode
|| !reg_class_subset_p (rclass, GENERAL_REGS)
|| GET_MODE_SIZE (mode) > UNITS_PER_WORD
|| !s390_check_symref_alignment (XEXP (x, 0),
GET_MODE_SIZE (mode))))
{
#define __SECONDARY_RELOAD_CASE(M,m) \
case E_##M##mode: \
if (TARGET_64BIT) \
sri->icode = in_p ? CODE_FOR_reload##m##di_toreg_z10 : \
CODE_FOR_reload##m##di_tomem_z10; \
else \
sri->icode = in_p ? CODE_FOR_reload##m##si_toreg_z10 : \
CODE_FOR_reload##m##si_tomem_z10; \
break;
switch (GET_MODE (x))
{
__SECONDARY_RELOAD_CASE (QI, qi);
__SECONDARY_RELOAD_CASE (HI, hi);
__SECONDARY_RELOAD_CASE (SI, si);
__SECONDARY_RELOAD_CASE (DI, di);
__SECONDARY_RELOAD_CASE (TI, ti);
__SECONDARY_RELOAD_CASE (SF, sf);
__SECONDARY_RELOAD_CASE (DF, df);
__SECONDARY_RELOAD_CASE (TF, tf);
__SECONDARY_RELOAD_CASE (SD, sd);
__SECONDARY_RELOAD_CASE (DD, dd);
__SECONDARY_RELOAD_CASE (TD, td);
__SECONDARY_RELOAD_CASE (V1QI, v1qi);
__SECONDARY_RELOAD_CASE (V2QI, v2qi);
__SECONDARY_RELOAD_CASE (V4QI, v4qi);
__SECONDARY_RELOAD_CASE (V8QI, v8qi);
__SECONDARY_RELOAD_CASE (V16QI, v16qi);
__SECONDARY_RELOAD_CASE (V1HI, v1hi);
__SECONDARY_RELOAD_CASE (V2HI, v2hi);
__SECONDARY_RELOAD_CASE (V4HI, v4hi);
__SECONDARY_RELOAD_CASE (V8HI, v8hi);
__SECONDARY_RELOAD_CASE (V1SI, v1si);
__SECONDARY_RELOAD_CASE (V2SI, v2si);
__SECONDARY_RELOAD_CASE (V4SI, v4si);
__SECONDARY_RELOAD_CASE (V1DI, v1di);
__SECONDARY_RELOAD_CASE (V2DI, v2di);
__SECONDARY_RELOAD_CASE (V1TI, v1ti);
__SECONDARY_RELOAD_CASE (V1SF, v1sf);
__SECONDARY_RELOAD_CASE (V2SF, v2sf);
__SECONDARY_RELOAD_CASE (V4SF, v4sf);
__SECONDARY_RELOAD_CASE (V1DF, v1df);
__SECONDARY_RELOAD_CASE (V2DF, v2df);
__SECONDARY_RELOAD_CASE (V1TF, v1tf);
default:
gcc_unreachable ();
}
#undef __SECONDARY_RELOAD_CASE
}
}
/* We need a scratch register when loading a PLUS expression which
is not a legitimate operand of the LOAD ADDRESS instruction. */
/* LRA can deal with transformation of plus op very well -- so we
don't need to prompt LRA in this case. */
if (! lra_in_progress && in_p && s390_plus_operand (x, mode))
sri->icode = (TARGET_64BIT ?
CODE_FOR_reloaddi_plus : CODE_FOR_reloadsi_plus);
/* Performing a multiword move from or to memory we have to make sure the
second chunk in memory is addressable without causing a displacement
overflow. If that would be the case we calculate the address in
a scratch register. */
if (MEM_P (x)
&& GET_CODE (XEXP (x, 0)) == PLUS
&& GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT
&& !DISP_IN_RANGE (INTVAL (XEXP (XEXP (x, 0), 1))
+ GET_MODE_SIZE (mode) - 1))
{
/* For GENERAL_REGS a displacement overflow is no problem if occurring
in a s_operand address since we may fallback to lm/stm. So we only
have to care about overflows in the b+i+d case. */
if ((reg_classes_intersect_p (GENERAL_REGS, rclass)
&& s390_class_max_nregs (GENERAL_REGS, mode) > 1
&& GET_CODE (XEXP (XEXP (x, 0), 0)) == PLUS)
/* For FP_REGS no lm/stm is available so this check is triggered
for displacement overflows in b+i+d and b+d like addresses. */
|| (reg_classes_intersect_p (FP_REGS, rclass)
&& s390_class_max_nregs (FP_REGS, mode) > 1))
{
if (in_p)
sri->icode = (TARGET_64BIT ?
CODE_FOR_reloaddi_la_in :
CODE_FOR_reloadsi_la_in);
else
sri->icode = (TARGET_64BIT ?
CODE_FOR_reloaddi_la_out :
CODE_FOR_reloadsi_la_out);
}
}
/* A scratch address register is needed when a symbolic constant is
copied to r0 compiling with -fPIC. In other cases the target
register might be used as temporary (see legitimize_pic_address). */
if (in_p && SYMBOLIC_CONST (x) && flag_pic == 2 && rclass != ADDR_REGS)
sri->icode = (TARGET_64BIT ?
CODE_FOR_reloaddi_PIC_addr :
CODE_FOR_reloadsi_PIC_addr);
/* Either scratch or no register needed. */
return NO_REGS;
}
/* Implement TARGET_SECONDARY_MEMORY_NEEDED.
We need secondary memory to move data between GPRs and FPRs.
- With DFP the ldgr lgdr instructions are available. Due to the
different alignment we cannot use them for SFmode. For 31 bit a
64 bit value in GPR would be a register pair so here we still
need to go via memory.
- With z13 we can do the SF/SImode moves with vlgvf. Due to the
overlapping of FPRs and VRs we still disallow TF/TD modes to be
in full VRs so as before also on z13 we do these moves via
memory.
FIXME: Should we try splitting it into two vlgvg's/vlvg's instead? */
static bool
s390_secondary_memory_needed (machine_mode mode,
reg_class_t class1, reg_class_t class2)
{
return (((reg_classes_intersect_p (class1, VEC_REGS)
&& reg_classes_intersect_p (class2, GENERAL_REGS))
|| (reg_classes_intersect_p (class1, GENERAL_REGS)
&& reg_classes_intersect_p (class2, VEC_REGS)))
&& (TARGET_TPF || !TARGET_DFP || !TARGET_64BIT
|| GET_MODE_SIZE (mode) != 8)
&& (!TARGET_VX || (SCALAR_FLOAT_MODE_P (mode)
&& GET_MODE_SIZE (mode) > 8)));
}
/* Implement TARGET_SECONDARY_MEMORY_NEEDED_MODE.
get_secondary_mem widens its argument to BITS_PER_WORD which loses on 64bit
because the movsi and movsf patterns don't handle r/f moves. */
static machine_mode
s390_secondary_memory_needed_mode (machine_mode mode)
{
if (GET_MODE_BITSIZE (mode) < 32)
return mode_for_size (32, GET_MODE_CLASS (mode), 0).require ();
return mode;
}
/* Generate code to load SRC, which is PLUS that is not a
legitimate operand for the LA instruction, into TARGET.
SCRATCH may be used as scratch register. */
void
s390_expand_plus_operand (rtx target, rtx src,
rtx scratch)
{
rtx sum1, sum2;
struct s390_address ad;
/* src must be a PLUS; get its two operands. */
gcc_assert (GET_CODE (src) == PLUS);
gcc_assert (GET_MODE (src) == Pmode);
/* Check if any of the two operands is already scheduled
for replacement by reload. This can happen e.g. when
float registers occur in an address. */
sum1 = find_replacement (&XEXP (src, 0));
sum2 = find_replacement (&XEXP (src, 1));
src = gen_rtx_PLUS (Pmode, sum1, sum2);
/* If the address is already strictly valid, there's nothing to do. */
if (!s390_decompose_address (src, &ad)
|| (ad.base && !REGNO_OK_FOR_BASE_P (REGNO (ad.base)))
|| (ad.indx && !REGNO_OK_FOR_INDEX_P (REGNO (ad.indx))))
{
/* Otherwise, one of the operands cannot be an address register;
we reload its value into the scratch register. */
if (true_regnum (sum1) < 1 || true_regnum (sum1) > 15)
{
emit_move_insn (scratch, sum1);
sum1 = scratch;
}
if (true_regnum (sum2) < 1 || true_regnum (sum2) > 15)
{
emit_move_insn (scratch, sum2);
sum2 = scratch;
}
/* According to the way these invalid addresses are generated
in reload.c, it should never happen (at least on s390) that
*neither* of the PLUS components, after find_replacements
was applied, is an address register. */
if (sum1 == scratch && sum2 == scratch)
{
debug_rtx (src);
gcc_unreachable ();
}
src = gen_rtx_PLUS (Pmode, sum1, sum2);
}
/* Emit the LOAD ADDRESS pattern. Note that reload of PLUS
is only ever performed on addresses, so we can mark the
sum as legitimate for LA in any case. */
s390_load_address (target, src);
}
/* Return true if ADDR is a valid memory address.
STRICT specifies whether strict register checking applies. */
static bool
s390_legitimate_address_p (machine_mode mode, rtx addr, bool strict)
{
struct s390_address ad;
if (TARGET_Z10
&& larl_operand (addr, VOIDmode)
&& (mode == VOIDmode
|| s390_check_symref_alignment (addr, GET_MODE_SIZE (mode))))
return true;
if (!s390_decompose_address (addr, &ad))
return false;
/* The vector memory instructions only support short displacements.
Reject invalid displacements early to prevent plenty of lay
instructions to be generated later which then cannot be merged
properly. */
if (TARGET_VX
&& VECTOR_MODE_P (mode)
&& ad.disp != NULL_RTX
&& CONST_INT_P (ad.disp)
&& !SHORT_DISP_IN_RANGE (INTVAL (ad.disp)))
return false;
if (strict)
{
if (ad.base && !REGNO_OK_FOR_BASE_P (REGNO (ad.base)))
return false;
if (ad.indx && !REGNO_OK_FOR_INDEX_P (REGNO (ad.indx)))
return false;
}
else
{
if (ad.base
&& !(REGNO (ad.base) >= FIRST_PSEUDO_REGISTER
|| REGNO_REG_CLASS (REGNO (ad.base)) == ADDR_REGS))
return false;
if (ad.indx
&& !(REGNO (ad.indx) >= FIRST_PSEUDO_REGISTER
|| REGNO_REG_CLASS (REGNO (ad.indx)) == ADDR_REGS))
return false;
}
return true;
}
/* Return true if OP is a valid operand for the LA instruction.
In 31-bit, we need to prove that the result is used as an
address, as LA performs only a 31-bit addition. */
bool
legitimate_la_operand_p (rtx op)
{
struct s390_address addr;
if (!s390_decompose_address (op, &addr))
return false;
return (TARGET_64BIT || addr.pointer);
}
/* Return true if it is valid *and* preferable to use LA to
compute the sum of OP1 and OP2. */
bool
preferred_la_operand_p (rtx op1, rtx op2)
{
struct s390_address addr;
if (op2 != const0_rtx)
op1 = gen_rtx_PLUS (Pmode, op1, op2);
if (!s390_decompose_address (op1, &addr))
return false;
if (addr.base && !REGNO_OK_FOR_BASE_P (REGNO (addr.base)))
return false;
if (addr.indx && !REGNO_OK_FOR_INDEX_P (REGNO (addr.indx)))
return false;
/* Avoid LA instructions with index (and base) register on z196 or
later; it is preferable to use regular add instructions when
possible. Starting with zEC12 the la with index register is
"uncracked" again but still slower than a regular add. */
if (addr.indx && s390_tune >= PROCESSOR_2817_Z196)
return false;
if (!TARGET_64BIT && !addr.pointer)
return false;
if (addr.pointer)
return true;
if ((addr.base && REG_P (addr.base) && REG_POINTER (addr.base))
|| (addr.indx && REG_P (addr.indx) && REG_POINTER (addr.indx)))
return true;
return false;
}
/* Emit a forced load-address operation to load SRC into DST.
This will use the LOAD ADDRESS instruction even in situations
where legitimate_la_operand_p (SRC) returns false. */
void
s390_load_address (rtx dst, rtx src)
{
if (TARGET_64BIT)
emit_move_insn (dst, src);
else
emit_insn (gen_force_la_31 (dst, src));
}
/* Return true if it ok to use SYMBOL_REF in a relative address. */
bool
s390_rel_address_ok_p (rtx symbol_ref)
{
tree decl;
if (symbol_ref == s390_got_symbol () || CONSTANT_POOL_ADDRESS_P (symbol_ref))
return true;
decl = SYMBOL_REF_DECL (symbol_ref);
if (!flag_pic || SYMBOL_REF_LOCAL_P (symbol_ref))
return (s390_pic_data_is_text_relative
|| (decl
&& TREE_CODE (decl) == FUNCTION_DECL));
return false;
}
/* Return a legitimate reference for ORIG (an address) using the
register REG. If REG is 0, a new pseudo is generated.
There are two types of references that must be handled:
1. Global data references must load the address from the GOT, via
the PIC reg. An insn is emitted to do this load, and the reg is
returned.
2. Static data references, constant pool addresses, and code labels
compute the address as an offset from the GOT, whose base is in
the PIC reg. Static data objects have SYMBOL_FLAG_LOCAL set to
differentiate them from global data objects. The returned
address is the PIC reg + an unspec constant.
TARGET_LEGITIMIZE_ADDRESS_P rejects symbolic references unless the PIC
reg also appears in the address. */
rtx
legitimize_pic_address (rtx orig, rtx reg)
{
rtx addr = orig;
rtx addend = const0_rtx;
rtx new_rtx = orig;
gcc_assert (!TLS_SYMBOLIC_CONST (addr));
if (GET_CODE (addr) == CONST)
addr = XEXP (addr, 0);
if (GET_CODE (addr) == PLUS)
{
addend = XEXP (addr, 1);
addr = XEXP (addr, 0);
}
if ((GET_CODE (addr) == LABEL_REF
|| (SYMBOL_REF_P (addr) && s390_rel_address_ok_p (addr))
|| (GET_CODE (addr) == UNSPEC &&
(XINT (addr, 1) == UNSPEC_GOTENT
|| XINT (addr, 1) == UNSPEC_PLT31)))
&& GET_CODE (addend) == CONST_INT)
{
/* This can be locally addressed. */
/* larl_operand requires UNSPECs to be wrapped in a const rtx. */
rtx const_addr = (GET_CODE (addr) == UNSPEC ?
gen_rtx_CONST (Pmode, addr) : addr);
if (larl_operand (const_addr, VOIDmode)
&& INTVAL (addend) < HOST_WIDE_INT_1 << 31
&& INTVAL (addend) >= -(HOST_WIDE_INT_1 << 31))
{
if (INTVAL (addend) & 1)
{
/* LARL can't handle odd offsets, so emit a pair of LARL
and LA. */
rtx temp = reg? reg : gen_reg_rtx (Pmode);
if (!DISP_IN_RANGE (INTVAL (addend)))
{
HOST_WIDE_INT even = INTVAL (addend) - 1;
addr = gen_rtx_PLUS (Pmode, addr, GEN_INT (even));
addr = gen_rtx_CONST (Pmode, addr);
addend = const1_rtx;
}
emit_move_insn (temp, addr);
new_rtx = gen_rtx_PLUS (Pmode, temp, addend);
if (reg != 0)
{
s390_load_address (reg, new_rtx);
new_rtx = reg;
}
}
else
{
/* If the offset is even, we can just use LARL. This
will happen automatically. */
}
}
else
{
/* No larl - Access local symbols relative to the GOT. */
rtx temp = reg? reg : gen_reg_rtx (Pmode);
if (reload_in_progress || reload_completed)
df_set_regs_ever_live (PIC_OFFSET_TABLE_REGNUM, true);
addr = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), UNSPEC_GOTOFF);
if (addend != const0_rtx)
addr = gen_rtx_PLUS (Pmode, addr, addend);
addr = gen_rtx_CONST (Pmode, addr);
addr = force_const_mem (Pmode, addr);
emit_move_insn (temp, addr);
new_rtx = gen_rtx_PLUS (Pmode, pic_offset_table_rtx, temp);
if (reg != 0)
{
s390_load_address (reg, new_rtx);
new_rtx = reg;
}
}
}
else if (GET_CODE (addr) == SYMBOL_REF && addend == const0_rtx)
{
/* A non-local symbol reference without addend.
The symbol ref is wrapped into an UNSPEC to make sure the
proper operand modifier (@GOT or @GOTENT) will be emitted.
This will tell the linker to put the symbol into the GOT.
Additionally the code dereferencing the GOT slot is emitted here.
An addend to the symref needs to be added afterwards.
legitimize_pic_address calls itself recursively to handle
that case. So no need to do it here. */
if (reg == 0)
reg = gen_reg_rtx (Pmode);
if (TARGET_Z10)
{
/* Use load relative if possible.
lgrl <target>, sym@GOTENT */
new_rtx = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), UNSPEC_GOTENT);
new_rtx = gen_rtx_CONST (Pmode, new_rtx);
new_rtx = gen_const_mem (GET_MODE (reg), new_rtx);
emit_move_insn (reg, new_rtx);
new_rtx = reg;
}
else if (flag_pic == 1)
{
/* Assume GOT offset is a valid displacement operand (< 4k
or < 512k with z990). This is handled the same way in
both 31- and 64-bit code (@GOT).
lg <target>, sym@GOT(r12) */
if (reload_in_progress || reload_completed)
df_set_regs_ever_live (PIC_OFFSET_TABLE_REGNUM, true);
new_rtx = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), UNSPEC_GOT);
new_rtx = gen_rtx_CONST (Pmode, new_rtx);
new_rtx = gen_rtx_PLUS (Pmode, pic_offset_table_rtx, new_rtx);
new_rtx = gen_const_mem (Pmode, new_rtx);
emit_move_insn (reg, new_rtx);
new_rtx = reg;
}
else
{
/* If the GOT offset might be >= 4k, we determine the position
of the GOT entry via a PC-relative LARL (@GOTENT).
larl temp, sym@GOTENT
lg <target>, 0(temp) */
rtx temp = reg ? reg : gen_reg_rtx (Pmode);
gcc_assert (REGNO (temp) >= FIRST_PSEUDO_REGISTER
|| REGNO_REG_CLASS (REGNO (temp)) == ADDR_REGS);
new_rtx = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), UNSPEC_GOTENT);
new_rtx = gen_rtx_CONST (Pmode, new_rtx);
emit_move_insn (temp, new_rtx);
new_rtx = gen_const_mem (Pmode, temp);
emit_move_insn (reg, new_rtx);
new_rtx = reg;
}
}
else if (GET_CODE (addr) == UNSPEC && GET_CODE (addend) == CONST_INT)
{
gcc_assert (XVECLEN (addr, 0) == 1);
switch (XINT (addr, 1))
{
/* These address symbols (or PLT slots) relative to the GOT
(not GOT slots!). In general this will exceed the
displacement range so these value belong into the literal
pool. */
case UNSPEC_GOTOFF:
case UNSPEC_PLTOFF:
new_rtx = force_const_mem (Pmode, orig);
break;
/* For -fPIC the GOT size might exceed the displacement
range so make sure the value is in the literal pool. */
case UNSPEC_GOT:
if (flag_pic == 2)
new_rtx = force_const_mem (Pmode, orig);
break;
/* For @GOTENT larl is used. This is handled like local
symbol refs. */
case UNSPEC_GOTENT:
gcc_unreachable ();
break;
/* For @PLT larl is used. This is handled like local
symbol refs. */
case UNSPEC_PLT31:
gcc_unreachable ();
break;
/* Everything else cannot happen. */
default:
gcc_unreachable ();
}
}
else if (addend != const0_rtx)
{
/* Otherwise, compute the sum. */
rtx base = legitimize_pic_address (addr, reg);
new_rtx = legitimize_pic_address (addend,
base == reg ? NULL_RTX : reg);
if (GET_CODE (new_rtx) == CONST_INT)
new_rtx = plus_constant (Pmode, base, INTVAL (new_rtx));
else
{
if (GET_CODE (new_rtx) == PLUS && CONSTANT_P (XEXP (new_rtx, 1)))
{
base = gen_rtx_PLUS (Pmode, base, XEXP (new_rtx, 0));
new_rtx = XEXP (new_rtx, 1);
}
new_rtx = gen_rtx_PLUS (Pmode, base, new_rtx);
}
if (GET_CODE (new_rtx) == CONST)
new_rtx = XEXP (new_rtx, 0);
new_rtx = force_operand (new_rtx, 0);
}
return new_rtx;
}
/* Load the thread pointer into a register. */
rtx
s390_get_thread_pointer (void)
{
rtx tp = gen_reg_rtx (Pmode);
emit_insn (gen_get_thread_pointer (Pmode, tp));
mark_reg_pointer (tp, BITS_PER_WORD);
return tp;
}
/* Emit a tls call insn. The call target is the SYMBOL_REF stored
in s390_tls_symbol which always refers to __tls_get_offset.
The returned offset is written to RESULT_REG and an USE rtx is
generated for TLS_CALL. */
static GTY(()) rtx s390_tls_symbol;
static void
s390_emit_tls_call_insn (rtx result_reg, rtx tls_call)
{
rtx insn;
if (!flag_pic)
emit_insn (s390_load_got ());
if (!s390_tls_symbol)
{
s390_tls_symbol = gen_rtx_SYMBOL_REF (Pmode, "__tls_get_offset");
SYMBOL_REF_FLAGS (s390_tls_symbol) |= SYMBOL_FLAG_FUNCTION;
}
insn = s390_emit_call (s390_tls_symbol, tls_call, result_reg,
gen_rtx_REG (Pmode, RETURN_REGNUM));
use_reg (&CALL_INSN_FUNCTION_USAGE (insn), result_reg);
RTL_CONST_CALL_P (insn) = 1;
}
/* ADDR contains a thread-local SYMBOL_REF. Generate code to compute
this (thread-local) address. REG may be used as temporary. */
static rtx
legitimize_tls_address (rtx addr, rtx reg)
{
rtx new_rtx, tls_call, temp, base, r2;
rtx_insn *insn;
if (GET_CODE (addr) == SYMBOL_REF)
switch (tls_symbolic_operand (addr))
{
case TLS_MODEL_GLOBAL_DYNAMIC:
start_sequence ();
r2 = gen_rtx_REG (Pmode, 2);
tls_call = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), UNSPEC_TLSGD);
new_rtx = gen_rtx_CONST (Pmode, tls_call);
new_rtx = force_const_mem (Pmode, new_rtx);
emit_move_insn (r2, new_rtx);
s390_emit_tls_call_insn (r2, tls_call);
insn = get_insns ();
end_sequence ();
new_rtx = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), UNSPEC_NTPOFF);
temp = gen_reg_rtx (Pmode);
emit_libcall_block (insn, temp, r2, new_rtx);
new_rtx = gen_rtx_PLUS (Pmode, s390_get_thread_pointer (), temp);
if (reg != 0)
{
s390_load_address (reg, new_rtx);
new_rtx = reg;
}
break;
case TLS_MODEL_LOCAL_DYNAMIC:
start_sequence ();
r2 = gen_rtx_REG (Pmode, 2);
tls_call = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, const0_rtx), UNSPEC_TLSLDM);
new_rtx = gen_rtx_CONST (Pmode, tls_call);
new_rtx = force_const_mem (Pmode, new_rtx);
emit_move_insn (r2, new_rtx);
s390_emit_tls_call_insn (r2, tls_call);
insn = get_insns ();
end_sequence ();
new_rtx = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, const0_rtx), UNSPEC_TLSLDM_NTPOFF);
temp = gen_reg_rtx (Pmode);
emit_libcall_block (insn, temp, r2, new_rtx);
new_rtx = gen_rtx_PLUS (Pmode, s390_get_thread_pointer (), temp);
base = gen_reg_rtx (Pmode);
s390_load_address (base, new_rtx);
new_rtx = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), UNSPEC_DTPOFF);
new_rtx = gen_rtx_CONST (Pmode, new_rtx);
new_rtx = force_const_mem (Pmode, new_rtx);
temp = gen_reg_rtx (Pmode);
emit_move_insn (temp, new_rtx);
new_rtx = gen_rtx_PLUS (Pmode, base, temp);
if (reg != 0)
{
s390_load_address (reg, new_rtx);
new_rtx = reg;
}
break;
case TLS_MODEL_INITIAL_EXEC:
if (flag_pic == 1)
{
/* Assume GOT offset < 4k. This is handled the same way
in both 31- and 64-bit code. */
if (reload_in_progress || reload_completed)
df_set_regs_ever_live (PIC_OFFSET_TABLE_REGNUM, true);
new_rtx = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), UNSPEC_GOTNTPOFF);
new_rtx = gen_rtx_CONST (Pmode, new_rtx);
new_rtx = gen_rtx_PLUS (Pmode, pic_offset_table_rtx, new_rtx);
new_rtx = gen_const_mem (Pmode, new_rtx);
temp = gen_reg_rtx (Pmode);
emit_move_insn (temp, new_rtx);
}
else
{
/* If the GOT offset might be >= 4k, we determine the position
of the GOT entry via a PC-relative LARL. */
new_rtx = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), UNSPEC_INDNTPOFF);
new_rtx = gen_rtx_CONST (Pmode, new_rtx);
temp = gen_reg_rtx (Pmode);
emit_move_insn (temp, new_rtx);
new_rtx = gen_const_mem (Pmode, temp);
temp = gen_reg_rtx (Pmode);
emit_move_insn (temp, new_rtx);
}
new_rtx = gen_rtx_PLUS (Pmode, s390_get_thread_pointer (), temp);
if (reg != 0)
{
s390_load_address (reg, new_rtx);
new_rtx = reg;
}
break;
case TLS_MODEL_LOCAL_EXEC:
new_rtx = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), UNSPEC_NTPOFF);
new_rtx = gen_rtx_CONST (Pmode, new_rtx);
new_rtx = force_const_mem (Pmode, new_rtx);
temp = gen_reg_rtx (Pmode);
emit_move_insn (temp, new_rtx);
new_rtx = gen_rtx_PLUS (Pmode, s390_get_thread_pointer (), temp);
if (reg != 0)
{
s390_load_address (reg, new_rtx);
new_rtx = reg;
}
break;
default:
gcc_unreachable ();
}
else if (GET_CODE (addr) == CONST && GET_CODE (XEXP (addr, 0)) == UNSPEC)
{
switch (XINT (XEXP (addr, 0), 1))
{
case UNSPEC_NTPOFF:
case UNSPEC_INDNTPOFF:
new_rtx = addr;
break;
default:
gcc_unreachable ();
}
}
else if (GET_CODE (addr) == CONST && GET_CODE (XEXP (addr, 0)) == PLUS
&& GET_CODE (XEXP (XEXP (addr, 0), 1)) == CONST_INT)
{
new_rtx = XEXP (XEXP (addr, 0), 0);
if (GET_CODE (new_rtx) != SYMBOL_REF)
new_rtx = gen_rtx_CONST (Pmode, new_rtx);
new_rtx = legitimize_tls_address (new_rtx, reg);
new_rtx = plus_constant (Pmode, new_rtx,
INTVAL (XEXP (XEXP (addr, 0), 1)));
new_rtx = force_operand (new_rtx, 0);
}
/* (const (neg (unspec (symbol_ref)))) -> (neg (const (unspec (symbol_ref)))) */
else if (GET_CODE (addr) == CONST && GET_CODE (XEXP (addr, 0)) == NEG)
{
new_rtx = XEXP (XEXP (addr, 0), 0);
if (GET_CODE (new_rtx) != SYMBOL_REF)
new_rtx = gen_rtx_CONST (Pmode, new_rtx);
new_rtx = legitimize_tls_address (new_rtx, reg);
new_rtx = gen_rtx_NEG (Pmode, new_rtx);
new_rtx = force_operand (new_rtx, 0);
}
else
gcc_unreachable (); /* for now ... */
return new_rtx;
}
/* Emit insns making the address in operands[1] valid for a standard
move to operands[0]. operands[1] is replaced by an address which
should be used instead of the former RTX to emit the move
pattern. */
void
emit_symbolic_move (rtx *operands)
{
rtx temp = !can_create_pseudo_p () ? operands[0] : gen_reg_rtx (Pmode);
if (GET_CODE (operands[0]) == MEM)
operands[1] = force_reg (Pmode, operands[1]);
else if (TLS_SYMBOLIC_CONST (operands[1]))
operands[1] = legitimize_tls_address (operands[1], temp);
else if (flag_pic)
operands[1] = legitimize_pic_address (operands[1], temp);
}
/* Try machine-dependent ways of modifying an illegitimate address X
to be legitimate. If we find one, return the new, valid address.
OLDX is the address as it was before break_out_memory_refs was called.
In some cases it is useful to look at this to decide what needs to be done.
MODE is the mode of the operand pointed to by X.
When -fpic is used, special handling is needed for symbolic references.
See comments by legitimize_pic_address for details. */
static rtx
s390_legitimize_address (rtx x, rtx oldx ATTRIBUTE_UNUSED,
machine_mode mode ATTRIBUTE_UNUSED)
{
rtx constant_term = const0_rtx;
if (TLS_SYMBOLIC_CONST (x))
{
x = legitimize_tls_address (x, 0);
if (s390_legitimate_address_p (mode, x, FALSE))
return x;
}
else if (GET_CODE (x) == PLUS
&& (TLS_SYMBOLIC_CONST (XEXP (x, 0))
|| TLS_SYMBOLIC_CONST (XEXP (x, 1))))
{
return x;
}
else if (flag_pic)
{
if (SYMBOLIC_CONST (x)
|| (GET_CODE (x) == PLUS
&& (SYMBOLIC_CONST (XEXP (x, 0))
|| SYMBOLIC_CONST (XEXP (x, 1)))))
x = legitimize_pic_address (x, 0);
if (s390_legitimate_address_p (mode, x, FALSE))
return x;
}
x = eliminate_constant_term (x, &constant_term);
/* Optimize loading of large displacements by splitting them
into the multiple of 4K and the rest; this allows the
former to be CSE'd if possible.
Don't do this if the displacement is added to a register
pointing into the stack frame, as the offsets will
change later anyway. */
if (GET_CODE (constant_term) == CONST_INT
&& !TARGET_LONG_DISPLACEMENT
&& !DISP_IN_RANGE (INTVAL (constant_term))
&& !(REG_P (x) && REGNO_PTR_FRAME_P (REGNO (x))))
{
HOST_WIDE_INT lower = INTVAL (constant_term) & 0xfff;
HOST_WIDE_INT upper = INTVAL (constant_term) ^ lower;
rtx temp = gen_reg_rtx (Pmode);
rtx val = force_operand (GEN_INT (upper), temp);
if (val != temp)
emit_move_insn (temp, val);
x = gen_rtx_PLUS (Pmode, x, temp);
constant_term = GEN_INT (lower);
}
if (GET_CODE (x) == PLUS)
{
if (GET_CODE (XEXP (x, 0)) == REG)
{
rtx temp = gen_reg_rtx (Pmode);
rtx val = force_operand (XEXP (x, 1), temp);
if (val != temp)
emit_move_insn (temp, val);
x = gen_rtx_PLUS (Pmode, XEXP (x, 0), temp);
}
else if (GET_CODE (XEXP (x, 1)) == REG)
{
rtx temp = gen_reg_rtx (Pmode);
rtx val = force_operand (XEXP (x, 0), temp);
if (val != temp)
emit_move_insn (temp, val);
x = gen_rtx_PLUS (Pmode, temp, XEXP (x, 1));
}
}
if (constant_term != const0_rtx)
x = gen_rtx_PLUS (Pmode, x, constant_term);
return x;
}
/* Try a machine-dependent way of reloading an illegitimate address AD
operand. If we find one, push the reload and return the new address.
MODE is the mode of the enclosing MEM. OPNUM is the operand number
and TYPE is the reload type of the current reload. */
rtx
legitimize_reload_address (rtx ad, machine_mode mode ATTRIBUTE_UNUSED,
int opnum, int type)
{
if (!optimize || TARGET_LONG_DISPLACEMENT)
return NULL_RTX;
if (GET_CODE (ad) == PLUS)
{
rtx tem = simplify_binary_operation (PLUS, Pmode,
XEXP (ad, 0), XEXP (ad, 1));
if (tem)
ad = tem;
}
if (GET_CODE (ad) == PLUS
&& GET_CODE (XEXP (ad, 0)) == REG
&& GET_CODE (XEXP (ad, 1)) == CONST_INT
&& !DISP_IN_RANGE (INTVAL (XEXP (ad, 1))))
{
HOST_WIDE_INT lower = INTVAL (XEXP (ad, 1)) & 0xfff;
HOST_WIDE_INT upper = INTVAL (XEXP (ad, 1)) ^ lower;
rtx cst, tem, new_rtx;
cst = GEN_INT (upper);
if (!legitimate_reload_constant_p (cst))
cst = force_const_mem (Pmode, cst);
tem = gen_rtx_PLUS (Pmode, XEXP (ad, 0), cst);
new_rtx = gen_rtx_PLUS (Pmode, tem, GEN_INT (lower));
push_reload (XEXP (tem, 1), 0, &XEXP (tem, 1), 0,
BASE_REG_CLASS, Pmode, VOIDmode, 0, 0,
opnum, (enum reload_type) type);
return new_rtx;
}
return NULL_RTX;
}
/* Emit code to move LEN bytes from DST to SRC. */
bool
s390_expand_cpymem (rtx dst, rtx src, rtx len)
{
/* When tuning for z10 or higher we rely on the Glibc functions to
do the right thing. Only for constant lengths below 64k we will
generate inline code. */
if (s390_tune >= PROCESSOR_2097_Z10
&& (GET_CODE (len) != CONST_INT || INTVAL (len) > (1<<16)))
return false;
/* Expand memcpy for constant length operands without a loop if it
is shorter that way.
With a constant length argument a
memcpy loop (without pfd) is 36 bytes -> 6 * mvc */
if (GET_CODE (len) == CONST_INT
&& INTVAL (len) >= 0
&& INTVAL (len) <= 256 * 6
&& (!TARGET_MVCLE || INTVAL (len) <= 256))
{
HOST_WIDE_INT o, l;
for (l = INTVAL (len), o = 0; l > 0; l -= 256, o += 256)
{
rtx newdst = adjust_address (dst, BLKmode, o);
rtx newsrc = adjust_address (src, BLKmode, o);
emit_insn (gen_cpymem_short (newdst, newsrc,
GEN_INT (l > 256 ? 255 : l - 1)));
}
}
else if (TARGET_MVCLE)
{
emit_insn (gen_cpymem_long (dst, src, convert_to_mode (Pmode, len, 1)));
}
else
{
rtx dst_addr, src_addr, count, blocks, temp;
rtx_code_label *loop_start_label = gen_label_rtx ();
rtx_code_label *loop_end_label = gen_label_rtx ();
rtx_code_label *end_label = gen_label_rtx ();
machine_mode mode;
mode = GET_MODE (len);
if (mode == VOIDmode)
mode = Pmode;
dst_addr = gen_reg_rtx (Pmode);
src_addr = gen_reg_rtx (Pmode);
count = gen_reg_rtx (mode);
blocks = gen_reg_rtx (mode);
convert_move (count, len, 1);
emit_cmp_and_jump_insns (count, const0_rtx,
EQ, NULL_RTX, mode, 1, end_label);
emit_move_insn (dst_addr, force_operand (XEXP (dst, 0), NULL_RTX));
emit_move_insn (src_addr, force_operand (XEXP (src, 0), NULL_RTX));
dst = change_address (dst, VOIDmode, dst_addr);
src = change_address (src, VOIDmode, src_addr);
temp = expand_binop (mode, add_optab, count, constm1_rtx, count, 1,
OPTAB_DIRECT);
if (temp != count)
emit_move_insn (count, temp);
temp = expand_binop (mode, lshr_optab, count, GEN_INT (8), blocks, 1,
OPTAB_DIRECT);
if (temp != blocks)
emit_move_insn (blocks, temp);
emit_cmp_and_jump_insns (blocks, const0_rtx,
EQ, NULL_RTX, mode, 1, loop_end_label);
emit_label (loop_start_label);
if (TARGET_Z10
&& (GET_CODE (len) != CONST_INT || INTVAL (len) > 768))
{
rtx prefetch;
/* Issue a read prefetch for the +3 cache line. */
prefetch = gen_prefetch (gen_rtx_PLUS (Pmode, src_addr, GEN_INT (768)),
const0_rtx, const0_rtx);
PREFETCH_SCHEDULE_BARRIER_P (prefetch) = true;
emit_insn (prefetch);
/* Issue a write prefetch for the +3 cache line. */
prefetch = gen_prefetch (gen_rtx_PLUS (Pmode, dst_addr, GEN_INT (768)),
const1_rtx, const0_rtx);
PREFETCH_SCHEDULE_BARRIER_P (prefetch) = true;
emit_insn (prefetch);
}
emit_insn (gen_cpymem_short (dst, src, GEN_INT (255)));
s390_load_address (dst_addr,
gen_rtx_PLUS (Pmode, dst_addr, GEN_INT (256)));
s390_load_address (src_addr,
gen_rtx_PLUS (Pmode, src_addr, GEN_INT (256)));
temp = expand_binop (mode, add_optab, blocks, constm1_rtx, blocks, 1,
OPTAB_DIRECT);
if (temp != blocks)
emit_move_insn (blocks, temp);
emit_cmp_and_jump_insns (blocks, const0_rtx,
EQ, NULL_RTX, mode, 1, loop_end_label);
emit_jump (loop_start_label);
emit_label (loop_end_label);
emit_insn (gen_cpymem_short (dst, src,
convert_to_mode (Pmode, count, 1)));
emit_label (end_label);
}
return true;
}
/* Emit code to set LEN bytes at DST to VAL.
Make use of clrmem if VAL is zero. */
void
s390_expand_setmem (rtx dst, rtx len, rtx val)
{
if (GET_CODE (len) == CONST_INT && INTVAL (len) <= 0)
return;
gcc_assert (GET_CODE (val) == CONST_INT || GET_MODE (val) == QImode);
/* Expand setmem/clrmem for a constant length operand without a
loop if it will be shorter that way.
clrmem loop (with PFD) is 30 bytes -> 5 * xc
clrmem loop (without PFD) is 24 bytes -> 4 * xc
setmem loop (with PFD) is 38 bytes -> ~4 * (mvi/stc + mvc)
setmem loop (without PFD) is 32 bytes -> ~4 * (mvi/stc + mvc) */
if (GET_CODE (len) == CONST_INT
&& ((val == const0_rtx
&& (INTVAL (len) <= 256 * 4
|| (INTVAL (len) <= 256 * 5 && TARGET_SETMEM_PFD(val,len))))
|| (val != const0_rtx && INTVAL (len) <= 257 * 4))
&& (!TARGET_MVCLE || INTVAL (len) <= 256))
{
HOST_WIDE_INT o, l;
if (val == const0_rtx)
/* clrmem: emit 256 byte blockwise XCs. */
for (l = INTVAL (len), o = 0; l > 0; l -= 256, o += 256)
{
rtx newdst = adjust_address (dst, BLKmode, o);
emit_insn (gen_clrmem_short (newdst,
GEN_INT (l > 256 ? 255 : l - 1)));
}
else
/* setmem: emit 1(mvi) + 256(mvc) byte blockwise memsets by
setting first byte to val and using a 256 byte mvc with one
byte overlap to propagate the byte. */
for (l = INTVAL (len), o = 0; l > 0; l -= 257, o += 257)
{
rtx newdst = adjust_address (dst, BLKmode, o);
emit_move_insn (adjust_address (dst, QImode, o), val);
if (l > 1)
{
rtx newdstp1 = adjust_address (dst, BLKmode, o + 1);
emit_insn (gen_cpymem_short (newdstp1, newdst,
GEN_INT (l > 257 ? 255 : l - 2)));
}
}
}
else if (TARGET_MVCLE)
{
val = force_not_mem (convert_modes (Pmode, QImode, val, 1));
if (TARGET_64BIT)
emit_insn (gen_setmem_long_di (dst, convert_to_mode (Pmode, len, 1),
val));
else
emit_insn (gen_setmem_long_si (dst, convert_to_mode (Pmode, len, 1),
val));
}
else
{
rtx dst_addr, count, blocks, temp, dstp1 = NULL_RTX;
rtx_code_label *loop_start_label = gen_label_rtx ();
rtx_code_label *onebyte_end_label = gen_label_rtx ();
rtx_code_label *zerobyte_end_label = gen_label_rtx ();
rtx_code_label *restbyte_end_label = gen_label_rtx ();
machine_mode mode;
mode = GET_MODE (len);
if (mode == VOIDmode)
mode = Pmode;
dst_addr = gen_reg_rtx (Pmode);
count = gen_reg_rtx (mode);
blocks = gen_reg_rtx (mode);
convert_move (count, len, 1);
emit_cmp_and_jump_insns (count, const0_rtx,
EQ, NULL_RTX, mode, 1, zerobyte_end_label,
profile_probability::very_unlikely ());
/* We need to make a copy of the target address since memset is
supposed to return it unmodified. We have to make it here
already since the new reg is used at onebyte_end_label. */
emit_move_insn (dst_addr, force_operand (XEXP (dst, 0), NULL_RTX));
dst = change_address (dst, VOIDmode, dst_addr);
if (val != const0_rtx)
{
/* When using the overlapping mvc the original target
address is only accessed as single byte entity (even by
the mvc reading this value). */
set_mem_size (dst, 1);
dstp1 = adjust_address (dst, VOIDmode, 1);
emit_cmp_and_jump_insns (count,
const1_rtx, EQ, NULL_RTX, mode, 1,
onebyte_end_label,
profile_probability::very_unlikely ());
}
/* There is one unconditional (mvi+mvc)/xc after the loop
dealing with the rest of the bytes, subtracting two (mvi+mvc)
or one (xc) here leaves this number of bytes to be handled by
it. */
temp = expand_binop (mode, add_optab, count,
val == const0_rtx ? constm1_rtx : GEN_INT (-2),
count, 1, OPTAB_DIRECT);
if (temp != count)
emit_move_insn (count, temp);
temp = expand_binop (mode, lshr_optab, count, GEN_INT (8), blocks, 1,
OPTAB_DIRECT);
if (temp != blocks)
emit_move_insn (blocks, temp);
emit_cmp_and_jump_insns (blocks, const0_rtx,
EQ, NULL_RTX, mode, 1, restbyte_end_label);
emit_jump (loop_start_label);
if (val != const0_rtx)
{
/* The 1 byte != 0 special case. Not handled efficiently
since we require two jumps for that. However, this
should be very rare. */
emit_label (onebyte_end_label);
emit_move_insn (adjust_address (dst, QImode, 0), val);
emit_jump (zerobyte_end_label);
}
emit_label (loop_start_label);
if (TARGET_SETMEM_PFD (val, len))
{
/* Issue a write prefetch. */
rtx distance = GEN_INT (TARGET_SETMEM_PREFETCH_DISTANCE);
rtx prefetch = gen_prefetch (gen_rtx_PLUS (Pmode, dst_addr, distance),
const1_rtx, const0_rtx);
emit_insn (prefetch);
PREFETCH_SCHEDULE_BARRIER_P (prefetch) = true;
}
if (val == const0_rtx)
emit_insn (gen_clrmem_short (dst, GEN_INT (255)));
else
{
/* Set the first byte in the block to the value and use an
overlapping mvc for the block. */
emit_move_insn (adjust_address (dst, QImode, 0), val);
emit_insn (gen_cpymem_short (dstp1, dst, GEN_INT (254)));
}
s390_load_address (dst_addr,
gen_rtx_PLUS (Pmode, dst_addr, GEN_INT (256)));
temp = expand_binop (mode, add_optab, blocks, constm1_rtx, blocks, 1,
OPTAB_DIRECT);
if (temp != blocks)
emit_move_insn (blocks, temp);
emit_cmp_and_jump_insns (blocks, const0_rtx,
NE, NULL_RTX, mode, 1, loop_start_label);
emit_label (restbyte_end_label);
if (val == const0_rtx)
emit_insn (gen_clrmem_short (dst, convert_to_mode (Pmode, count, 1)));
else
{
/* Set the first byte in the block to the value and use an
overlapping mvc for the block. */
emit_move_insn (adjust_address (dst, QImode, 0), val);
/* execute only uses the lowest 8 bits of count that's
exactly what we need here. */
emit_insn (gen_cpymem_short (dstp1, dst,
convert_to_mode (Pmode, count, 1)));
}
emit_label (zerobyte_end_label);
}
}
/* Emit code to compare LEN bytes at OP0 with those at OP1,
and return the result in TARGET. */
bool
s390_expand_cmpmem (rtx target, rtx op0, rtx op1, rtx len)
{
rtx ccreg = gen_rtx_REG (CCUmode, CC_REGNUM);
rtx tmp;
/* When tuning for z10 or higher we rely on the Glibc functions to
do the right thing. Only for constant lengths below 64k we will
generate inline code. */
if (s390_tune >= PROCESSOR_2097_Z10
&& (GET_CODE (len) != CONST_INT || INTVAL (len) > (1<<16)))
return false;
/* As the result of CMPINT is inverted compared to what we need,
we have to swap the operands. */
tmp = op0; op0 = op1; op1 = tmp;
if (GET_CODE (len) == CONST_INT && INTVAL (len) >= 0 && INTVAL (len) <= 256)
{
if (INTVAL (len) > 0)
{
emit_insn (gen_cmpmem_short (op0, op1, GEN_INT (INTVAL (len) - 1)));
emit_insn (gen_cmpint (target, ccreg));
}
else
emit_move_insn (target, const0_rtx);
}
else if (TARGET_MVCLE)
{
emit_insn (gen_cmpmem_long (op0, op1, convert_to_mode (Pmode, len, 1)));
emit_insn (gen_cmpint (target, ccreg));
}
else
{
rtx addr0, addr1, count, blocks, temp;
rtx_code_label *loop_start_label = gen_label_rtx ();
rtx_code_label *loop_end_label = gen_label_rtx ();
rtx_code_label *end_label = gen_label_rtx ();
machine_mode mode;
mode = GET_MODE (len);
if (mode == VOIDmode)
mode = Pmode;
addr0 = gen_reg_rtx (Pmode);
addr1 = gen_reg_rtx (Pmode);
count = gen_reg_rtx (mode);
blocks = gen_reg_rtx (mode);
convert_move (count, len, 1);
emit_cmp_and_jump_insns (count, const0_rtx,
EQ, NULL_RTX, mode, 1, end_label);
emit_move_insn (addr0, force_operand (XEXP (op0, 0), NULL_RTX));
emit_move_insn (addr1, force_operand (XEXP (op1, 0), NULL_RTX));
op0 = change_address (op0, VOIDmode, addr0);
op1 = change_address (op1, VOIDmode, addr1);
temp = expand_binop (mode, add_optab, count, constm1_rtx, count, 1,
OPTAB_DIRECT);
if (temp != count)
emit_move_insn (count, temp);
temp = expand_binop (mode, lshr_optab, count, GEN_INT (8), blocks, 1,
OPTAB_DIRECT);
if (temp != blocks)
emit_move_insn (blocks, temp);
emit_cmp_and_jump_insns (blocks, const0_rtx,
EQ, NULL_RTX, mode, 1, loop_end_label);
emit_label (loop_start_label);
if (TARGET_Z10
&& (GET_CODE (len) != CONST_INT || INTVAL (len) > 512))
{
rtx prefetch;
/* Issue a read prefetch for the +2 cache line of operand 1. */
prefetch = gen_prefetch (gen_rtx_PLUS (Pmode, addr0, GEN_INT (512)),
const0_rtx, const0_rtx);
emit_insn (prefetch);
PREFETCH_SCHEDULE_BARRIER_P (prefetch) = true;
/* Issue a read prefetch for the +2 cache line of operand 2. */
prefetch = gen_prefetch (gen_rtx_PLUS (Pmode, addr1, GEN_INT (512)),
const0_rtx, const0_rtx);
emit_insn (prefetch);
PREFETCH_SCHEDULE_BARRIER_P (prefetch) = true;
}
emit_insn (gen_cmpmem_short (op0, op1, GEN_INT (255)));
temp = gen_rtx_NE (VOIDmode, ccreg, const0_rtx);
temp = gen_rtx_IF_THEN_ELSE (VOIDmode, temp,
gen_rtx_LABEL_REF (VOIDmode, end_label), pc_rtx);
temp = gen_rtx_SET (pc_rtx, temp);
emit_jump_insn (temp);
s390_load_address (addr0,
gen_rtx_PLUS (Pmode, addr0, GEN_INT (256)));
s390_load_address (addr1,
gen_rtx_PLUS (Pmode, addr1, GEN_INT (256)));
temp = expand_binop (mode, add_optab, blocks, constm1_rtx, blocks, 1,
OPTAB_DIRECT);
if (temp != blocks)
emit_move_insn (blocks, temp);
emit_cmp_and_jump_insns (blocks, const0_rtx,
EQ, NULL_RTX, mode, 1, loop_end_label);
emit_jump (loop_start_label);
emit_label (loop_end_label);
emit_insn (gen_cmpmem_short (op0, op1,
convert_to_mode (Pmode, count, 1)));
emit_label (end_label);
emit_insn (gen_cmpint (target, ccreg));
}
return true;
}
/* Emit a conditional jump to LABEL for condition code mask MASK using
comparsion operator COMPARISON. Return the emitted jump insn. */
static rtx_insn *
s390_emit_ccraw_jump (HOST_WIDE_INT mask, enum rtx_code comparison, rtx label)
{
rtx temp;
gcc_assert (comparison == EQ || comparison == NE);
gcc_assert (mask > 0 && mask < 15);
temp = gen_rtx_fmt_ee (comparison, VOIDmode,
gen_rtx_REG (CCRAWmode, CC_REGNUM), GEN_INT (mask));
temp = gen_rtx_IF_THEN_ELSE (VOIDmode, temp,
gen_rtx_LABEL_REF (VOIDmode, label), pc_rtx);
temp = gen_rtx_SET (pc_rtx, temp);
return emit_jump_insn (temp);
}
/* Emit the instructions to implement strlen of STRING and store the
result in TARGET. The string has the known ALIGNMENT. This
version uses vector instructions and is therefore not appropriate
for targets prior to z13. */
void
s390_expand_vec_strlen (rtx target, rtx string, rtx alignment)
{
rtx highest_index_to_load_reg = gen_reg_rtx (Pmode);
rtx str_reg = gen_reg_rtx (V16QImode);
rtx str_addr_base_reg = gen_reg_rtx (Pmode);
rtx str_idx_reg = gen_reg_rtx (Pmode);
rtx result_reg = gen_reg_rtx (V16QImode);
rtx is_aligned_label = gen_label_rtx ();
rtx into_loop_label = NULL_RTX;
rtx loop_start_label = gen_label_rtx ();
rtx temp;
rtx len = gen_reg_rtx (QImode);
rtx cond;
rtx mem;
s390_load_address (str_addr_base_reg, XEXP (string, 0));
emit_move_insn (str_idx_reg, const0_rtx);
if (INTVAL (alignment) < 16)
{
/* Check whether the address happens to be aligned properly so
jump directly to the aligned loop. */
emit_cmp_and_jump_insns (gen_rtx_AND (Pmode,
str_addr_base_reg, GEN_INT (15)),
const0_rtx, EQ, NULL_RTX,
Pmode, 1, is_aligned_label);
temp = gen_reg_rtx (Pmode);
temp = expand_binop (Pmode, and_optab, str_addr_base_reg,
GEN_INT (15), temp, 1, OPTAB_DIRECT);
gcc_assert (REG_P (temp));
highest_index_to_load_reg =
expand_binop (Pmode, sub_optab, GEN_INT (15), temp,
highest_index_to_load_reg, 1, OPTAB_DIRECT);
gcc_assert (REG_P (highest_index_to_load_reg));
emit_insn (gen_vllv16qi (str_reg,
convert_to_mode (SImode, highest_index_to_load_reg, 1),
gen_rtx_MEM (BLKmode, str_addr_base_reg)));
into_loop_label = gen_label_rtx ();
s390_emit_jump (into_loop_label, NULL_RTX);
emit_barrier ();
}
emit_label (is_aligned_label);
LABEL_NUSES (is_aligned_label) = INTVAL (alignment) < 16 ? 2 : 1;
/* Reaching this point we are only performing 16 bytes aligned
loads. */
emit_move_insn (highest_index_to_load_reg, GEN_INT (15));
emit_label (loop_start_label);
LABEL_NUSES (loop_start_label) = 1;
/* Load 16 bytes of the string into VR. */
mem = gen_rtx_MEM (V16QImode,
gen_rtx_PLUS (Pmode, str_idx_reg, str_addr_base_reg));
set_mem_align (mem, 128);
emit_move_insn (str_reg, mem);
if (into_loop_label != NULL_RTX)
{
emit_label (into_loop_label);
LABEL_NUSES (into_loop_label) = 1;
}
/* Increment string index by 16 bytes. */
expand_binop (Pmode, add_optab, str_idx_reg, GEN_INT (16),
str_idx_reg, 1, OPTAB_DIRECT);
emit_insn (gen_vec_vfenesv16qi (result_reg, str_reg, str_reg,
GEN_INT (VSTRING_FLAG_ZS | VSTRING_FLAG_CS)));
add_int_reg_note (s390_emit_ccraw_jump (8, NE, loop_start_label),
REG_BR_PROB,
profile_probability::very_likely ().to_reg_br_prob_note ());
emit_insn (gen_vec_extractv16qiqi (len, result_reg, GEN_INT (7)));
/* If the string pointer wasn't aligned we have loaded less then 16
bytes and the remaining bytes got filled with zeros (by vll).
Now we have to check whether the resulting index lies within the
bytes actually part of the string. */
cond = s390_emit_compare (GT, convert_to_mode (Pmode, len, 1),
highest_index_to_load_reg);
s390_load_address (highest_index_to_load_reg,
gen_rtx_PLUS (Pmode, highest_index_to_load_reg,
const1_rtx));
if (TARGET_64BIT)
emit_insn (gen_movdicc (str_idx_reg, cond,
highest_index_to_load_reg, str_idx_reg));
else
emit_insn (gen_movsicc (str_idx_reg, cond,
highest_index_to_load_reg, str_idx_reg));
add_reg_br_prob_note (s390_emit_jump (is_aligned_label, cond),
profile_probability::very_unlikely ());
expand_binop (Pmode, add_optab, str_idx_reg,
GEN_INT (-16), str_idx_reg, 1, OPTAB_DIRECT);
/* FIXME: len is already zero extended - so avoid the llgcr emitted
here. */
temp = expand_binop (Pmode, add_optab, str_idx_reg,
convert_to_mode (Pmode, len, 1),
target, 1, OPTAB_DIRECT);
if (temp != target)
emit_move_insn (target, temp);
}
void
s390_expand_vec_movstr (rtx result, rtx dst, rtx src)
{
rtx temp = gen_reg_rtx (Pmode);
rtx src_addr = XEXP (src, 0);
rtx dst_addr = XEXP (dst, 0);
rtx src_addr_reg = gen_reg_rtx (Pmode);
rtx dst_addr_reg = gen_reg_rtx (Pmode);
rtx offset = gen_reg_rtx (Pmode);
rtx vsrc = gen_reg_rtx (V16QImode);
rtx vpos = gen_reg_rtx (V16QImode);
rtx loadlen = gen_reg_rtx (SImode);
rtx gpos_qi = gen_reg_rtx(QImode);
rtx gpos = gen_reg_rtx (SImode);
rtx done_label = gen_label_rtx ();
rtx loop_label = gen_label_rtx ();
rtx exit_label = gen_label_rtx ();
rtx full_label = gen_label_rtx ();
/* Perform a quick check for string ending on the first up to 16
bytes and exit early if successful. */
emit_insn (gen_vlbb (vsrc, src, GEN_INT (6)));
emit_insn (gen_lcbb (loadlen, src_addr, GEN_INT (6)));
emit_insn (gen_vfenezv16qi (vpos, vsrc, vsrc));
emit_insn (gen_vec_extractv16qiqi (gpos_qi, vpos, GEN_INT (7)));
emit_move_insn (gpos, gen_rtx_SUBREG (SImode, gpos_qi, 0));
/* gpos is the byte index if a zero was found and 16 otherwise.
So if it is lower than the loaded bytes we have a hit. */
emit_cmp_and_jump_insns (gpos, loadlen, GE, NULL_RTX, SImode, 1,
full_label);
emit_insn (gen_vstlv16qi (vsrc, gpos, dst));
force_expand_binop (Pmode, add_optab, dst_addr, gpos, result,
1, OPTAB_DIRECT);
emit_jump (exit_label);
emit_barrier ();
emit_label (full_label);
LABEL_NUSES (full_label) = 1;
/* Calculate `offset' so that src + offset points to the last byte
before 16 byte alignment. */
/* temp = src_addr & 0xf */
force_expand_binop (Pmode, and_optab, src_addr, GEN_INT (15), temp,
1, OPTAB_DIRECT);
/* offset = 0xf - temp */
emit_move_insn (offset, GEN_INT (15));
force_expand_binop (Pmode, sub_optab, offset, temp, offset,
1, OPTAB_DIRECT);
/* Store `offset' bytes in the dstination string. The quick check
has loaded at least `offset' bytes into vsrc. */
emit_insn (gen_vstlv16qi (vsrc, gen_lowpart (SImode, offset), dst));
/* Advance to the next byte to be loaded. */
force_expand_binop (Pmode, add_optab, offset, const1_rtx, offset,
1, OPTAB_DIRECT);
/* Make sure the addresses are single regs which can be used as a
base. */
emit_move_insn (src_addr_reg, src_addr);
emit_move_insn (dst_addr_reg, dst_addr);
/* MAIN LOOP */
emit_label (loop_label);
LABEL_NUSES (loop_label) = 1;
emit_move_insn (vsrc,
gen_rtx_MEM (V16QImode,
gen_rtx_PLUS (Pmode, src_addr_reg, offset)));
emit_insn (gen_vec_vfenesv16qi (vpos, vsrc, vsrc,
GEN_INT (VSTRING_FLAG_ZS | VSTRING_FLAG_CS)));
add_int_reg_note (s390_emit_ccraw_jump (8, EQ, done_label),
REG_BR_PROB, profile_probability::very_unlikely ()
.to_reg_br_prob_note ());
emit_move_insn (gen_rtx_MEM (V16QImode,
gen_rtx_PLUS (Pmode, dst_addr_reg, offset)),
vsrc);
/* offset += 16 */
force_expand_binop (Pmode, add_optab, offset, GEN_INT (16),
offset, 1, OPTAB_DIRECT);
emit_jump (loop_label);
emit_barrier ();
/* REGULAR EXIT */
/* We are done. Add the offset of the zero character to the dst_addr
pointer to get the result. */
emit_label (done_label);
LABEL_NUSES (done_label) = 1;
force_expand_binop (Pmode, add_optab, dst_addr_reg, offset, dst_addr_reg,
1, OPTAB_DIRECT);
emit_insn (gen_vec_extractv16qiqi (gpos_qi, vpos, GEN_INT (7)));
emit_move_insn (gpos, gen_rtx_SUBREG (SImode, gpos_qi, 0));
emit_insn (gen_vstlv16qi (vsrc, gpos, gen_rtx_MEM (BLKmode, dst_addr_reg)));
force_expand_binop (Pmode, add_optab, dst_addr_reg, gpos, result,
1, OPTAB_DIRECT);
/* EARLY EXIT */
emit_label (exit_label);
LABEL_NUSES (exit_label) = 1;
}
/* Expand conditional increment or decrement using alc/slb instructions.
Should generate code setting DST to either SRC or SRC + INCREMENT,
depending on the result of the comparison CMP_OP0 CMP_CODE CMP_OP1.
Returns true if successful, false otherwise.
That makes it possible to implement some if-constructs without jumps e.g.:
(borrow = CC0 | CC1 and carry = CC2 | CC3)
unsigned int a, b, c;
if (a < b) c++; -> CCU b > a -> CC2; c += carry;
if (a < b) c--; -> CCL3 a - b -> borrow; c -= borrow;
if (a <= b) c++; -> CCL3 b - a -> borrow; c += carry;
if (a <= b) c--; -> CCU a <= b -> borrow; c -= borrow;
Checks for EQ and NE with a nonzero value need an additional xor e.g.:
if (a == b) c++; -> CCL3 a ^= b; 0 - a -> borrow; c += carry;
if (a == b) c--; -> CCU a ^= b; a <= 0 -> CC0 | CC1; c -= borrow;
if (a != b) c++; -> CCU a ^= b; a > 0 -> CC2; c += carry;
if (a != b) c--; -> CCL3 a ^= b; 0 - a -> borrow; c -= borrow; */
bool
s390_expand_addcc (enum rtx_code cmp_code, rtx cmp_op0, rtx cmp_op1,
rtx dst, rtx src, rtx increment)
{
machine_mode cmp_mode;
machine_mode cc_mode;
rtx op_res;
rtx insn;
rtvec p;
int ret;
if ((GET_MODE (cmp_op0) == SImode || GET_MODE (cmp_op0) == VOIDmode)
&& (GET_MODE (cmp_op1) == SImode || GET_MODE (cmp_op1) == VOIDmode))
cmp_mode = SImode;
else if ((GET_MODE (cmp_op0) == DImode || GET_MODE (cmp_op0) == VOIDmode)
&& (GET_MODE (cmp_op1) == DImode || GET_MODE (cmp_op1) == VOIDmode))
cmp_mode = DImode;
else
return false;
/* Try ADD LOGICAL WITH CARRY. */
if (increment == const1_rtx)
{
/* Determine CC mode to use. */
if (cmp_code == EQ || cmp_code == NE)
{
if (cmp_op1 != const0_rtx)
{
cmp_op0 = expand_simple_binop (cmp_mode, XOR, cmp_op0, cmp_op1,
NULL_RTX, 0, OPTAB_WIDEN);
cmp_op1 = const0_rtx;
}
cmp_code = cmp_code == EQ ? LEU : GTU;
}
if (cmp_code == LTU || cmp_code == LEU)
{
rtx tem = cmp_op0;
cmp_op0 = cmp_op1;
cmp_op1 = tem;
cmp_code = swap_condition (cmp_code);
}
switch (cmp_code)
{
case GTU:
cc_mode = CCUmode;
break;
case GEU:
cc_mode = CCL3mode;
break;
default:
return false;
}
/* Emit comparison instruction pattern. */
if (!register_operand (cmp_op0, cmp_mode))
cmp_op0 = force_reg (cmp_mode, cmp_op0);
insn = gen_rtx_SET (gen_rtx_REG (cc_mode, CC_REGNUM),
gen_rtx_COMPARE (cc_mode, cmp_op0, cmp_op1));
/* We use insn_invalid_p here to add clobbers if required. */
ret = insn_invalid_p (emit_insn (insn), false);
gcc_assert (!ret);
/* Emit ALC instruction pattern. */
op_res = gen_rtx_fmt_ee (cmp_code, GET_MODE (dst),
gen_rtx_REG (cc_mode, CC_REGNUM),
const0_rtx);
if (src != const0_rtx)
{
if (!register_operand (src, GET_MODE (dst)))
src = force_reg (GET_MODE (dst), src);
op_res = gen_rtx_PLUS (GET_MODE (dst), op_res, src);
op_res = gen_rtx_PLUS (GET_MODE (dst), op_res, const0_rtx);
}
p = rtvec_alloc (2);
RTVEC_ELT (p, 0) =
gen_rtx_SET (dst, op_res);
RTVEC_ELT (p, 1) =
gen_rtx_CLOBBER (VOIDmode, gen_rtx_REG (CCmode, CC_REGNUM));
emit_insn (gen_rtx_PARALLEL (VOIDmode, p));
return true;
}
/* Try SUBTRACT LOGICAL WITH BORROW. */
if (increment == constm1_rtx)
{
/* Determine CC mode to use. */
if (cmp_code == EQ || cmp_code == NE)
{
if (cmp_op1 != const0_rtx)
{
cmp_op0 = expand_simple_binop (cmp_mode, XOR, cmp_op0, cmp_op1,
NULL_RTX, 0, OPTAB_WIDEN);
cmp_op1 = const0_rtx;
}
cmp_code = cmp_code == EQ ? LEU : GTU;
}
if (cmp_code == GTU || cmp_code == GEU)
{
rtx tem = cmp_op0;
cmp_op0 = cmp_op1;
cmp_op1 = tem;
cmp_code = swap_condition (cmp_code);
}
switch (cmp_code)
{
case LEU:
cc_mode = CCUmode;
break;
case LTU:
cc_mode = CCL3mode;
break;
default:
return false;
}
/* Emit comparison instruction pattern. */
if (!register_operand (cmp_op0, cmp_mode))
cmp_op0 = force_reg (cmp_mode, cmp_op0);
insn = gen_rtx_SET (gen_rtx_REG (cc_mode, CC_REGNUM),
gen_rtx_COMPARE (cc_mode, cmp_op0, cmp_op1));
/* We use insn_invalid_p here to add clobbers if required. */
ret = insn_invalid_p (emit_insn (insn), false);
gcc_assert (!ret);
/* Emit SLB instruction pattern. */
if (!register_operand (src, GET_MODE (dst)))
src = force_reg (GET_MODE (dst), src);
op_res = gen_rtx_MINUS (GET_MODE (dst),
gen_rtx_MINUS (GET_MODE (dst), src, const0_rtx),
gen_rtx_fmt_ee (cmp_code, GET_MODE (dst),
gen_rtx_REG (cc_mode, CC_REGNUM),
const0_rtx));
p = rtvec_alloc (2);
RTVEC_ELT (p, 0) =
gen_rtx_SET (dst, op_res);
RTVEC_ELT (p, 1) =
gen_rtx_CLOBBER (VOIDmode, gen_rtx_REG (CCmode, CC_REGNUM));
emit_insn (gen_rtx_PARALLEL (VOIDmode, p));
return true;
}
return false;
}
/* Expand code for the insv template. Return true if successful. */
bool
s390_expand_insv (rtx dest, rtx op1, rtx op2, rtx src)
{
int bitsize = INTVAL (op1);
int bitpos = INTVAL (op2);
machine_mode mode = GET_MODE (dest);
machine_mode smode;
int smode_bsize, mode_bsize;
rtx op, clobber;
if (bitsize + bitpos > GET_MODE_BITSIZE (mode))
return false;
/* Just a move. */
if (bitpos == 0
&& bitsize == GET_MODE_BITSIZE (GET_MODE (src))
&& mode == GET_MODE (src))
{
emit_move_insn (dest, src);
return true;
}
/* Generate INSERT IMMEDIATE (IILL et al). */
/* (set (ze (reg)) (const_int)). */
if (TARGET_ZARCH
&& register_operand (dest, word_mode)
&& (bitpos % 16) == 0
&& (bitsize % 16) == 0
&& const_int_operand (src, VOIDmode))
{
HOST_WIDE_INT val = INTVAL (src);
int regpos = bitpos + bitsize;
while (regpos > bitpos)
{
machine_mode putmode;
int putsize;
if (TARGET_EXTIMM && (regpos % 32 == 0) && (regpos >= bitpos + 32))
putmode = SImode;
else
putmode = HImode;
putsize = GET_MODE_BITSIZE (putmode);
regpos -= putsize;
emit_move_insn (gen_rtx_ZERO_EXTRACT (word_mode, dest,
GEN_INT (putsize),
GEN_INT (regpos)),
gen_int_mode (val, putmode));
val >>= putsize;
}
gcc_assert (regpos == bitpos);
return true;
}
smode = smallest_int_mode_for_size (bitsize);
smode_bsize = GET_MODE_BITSIZE (smode);
mode_bsize = GET_MODE_BITSIZE (mode);
/* Generate STORE CHARACTERS UNDER MASK (STCM et al). */
if (bitpos == 0
&& (bitsize % BITS_PER_UNIT) == 0
&& MEM_P (dest)
&& (register_operand (src, word_mode)
|| const_int_operand (src, VOIDmode)))
{
/* Emit standard pattern if possible. */
if (smode_bsize == bitsize)
{
emit_move_insn (adjust_address (dest, smode, 0),
gen_lowpart (smode, src));
return true;
}
/* (set (ze (mem)) (const_int)). */
else if (const_int_operand (src, VOIDmode))
{
int size = bitsize / BITS_PER_UNIT;
rtx src_mem = adjust_address (force_const_mem (word_mode, src),
BLKmode,
UNITS_PER_WORD - size);
dest = adjust_address (dest, BLKmode, 0);
set_mem_size (dest, size);
s390_expand_cpymem (dest, src_mem, GEN_INT (size));
return true;
}
/* (set (ze (mem)) (reg)). */
else if (register_operand (src, word_mode))
{
if (bitsize <= 32)
emit_move_insn (gen_rtx_ZERO_EXTRACT (word_mode, dest, op1,
const0_rtx), src);
else
{
/* Emit st,stcmh sequence. */
int stcmh_width = bitsize - 32;
int size = stcmh_width / BITS_PER_UNIT;
emit_move_insn (adjust_address (dest, SImode, size),
gen_lowpart (SImode, src));
set_mem_size (dest, size);
emit_move_insn (gen_rtx_ZERO_EXTRACT (word_mode, dest,
GEN_INT (stcmh_width),
const0_rtx),
gen_rtx_LSHIFTRT (word_mode, src, GEN_INT (32)));
}
return true;
}
}
/* Generate INSERT CHARACTERS UNDER MASK (IC, ICM et al). */
if ((bitpos % BITS_PER_UNIT) == 0
&& (bitsize % BITS_PER_UNIT) == 0
&& (bitpos & 32) == ((bitpos + bitsize - 1) & 32)
&& MEM_P (src)
&& (mode == DImode || mode == SImode)
&& mode != smode
&& register_operand (dest, mode))
{
/* Emit a strict_low_part pattern if possible. */
if (smode_bsize == bitsize && bitpos == mode_bsize - smode_bsize)
{
rtx low_dest = gen_lowpart (smode, dest);
rtx low_src = gen_lowpart (smode, src);
switch (smode)
{
case E_QImode: emit_insn (gen_movstrictqi (low_dest, low_src)); return true;
case E_HImode: emit_insn (gen_movstricthi (low_dest, low_src)); return true;
case E_SImode: emit_insn (gen_movstrictsi (low_dest, low_src)); return true;
default: break;
}
}
/* ??? There are more powerful versions of ICM that are not
completely represented in the md file. */
}
/* For z10, generate ROTATE THEN INSERT SELECTED BITS (RISBG et al). */
if (TARGET_Z10 && (mode == DImode || mode == SImode))
{
machine_mode mode_s = GET_MODE (src);
if (CONSTANT_P (src))
{
/* For constant zero values the representation with AND
appears to be folded in more situations than the (set
(zero_extract) ...).
We only do this when the start and end of the bitfield
remain in the same SImode chunk. That way nihf or nilf
can be used.
The AND patterns might still generate a risbg for this. */
if (src == const0_rtx && bitpos / 32 == (bitpos + bitsize - 1) / 32)
return false;
else
src = force_reg (mode, src);
}
else if (mode_s != mode)
{
gcc_assert (GET_MODE_BITSIZE (mode_s) >= bitsize);
src = force_reg (mode_s, src);
src = gen_lowpart (mode, src);
}
op = gen_rtx_ZERO_EXTRACT (mode, dest, op1, op2),
op = gen_rtx_SET (op, src);
if (!TARGET_ZEC12)
{
clobber = gen_rtx_CLOBBER (VOIDmode, gen_rtx_REG (CCmode, CC_REGNUM));
op = gen_rtx_PARALLEL (VOIDmode, gen_rtvec (2, op, clobber));
}
emit_insn (op);
return true;
}
return false;
}
/* A subroutine of s390_expand_cs_hqi and s390_expand_atomic which returns a
register that holds VAL of mode MODE shifted by COUNT bits. */
static inline rtx
s390_expand_mask_and_shift (rtx val, machine_mode mode, rtx count)
{
val = expand_simple_binop (SImode, AND, val, GEN_INT (GET_MODE_MASK (mode)),
NULL_RTX, 1, OPTAB_DIRECT);
return expand_simple_binop (SImode, ASHIFT, val, count,
NULL_RTX, 1, OPTAB_DIRECT);
}
/* Generate a vector comparison COND of CMP_OP1 and CMP_OP2 and store
the result in TARGET. */
void
s390_expand_vec_compare (rtx target, enum rtx_code cond,
rtx cmp_op1, rtx cmp_op2)
{
machine_mode mode = GET_MODE (target);
bool neg_p = false, swap_p = false;
rtx tmp;
if (GET_MODE_CLASS (GET_MODE (cmp_op1)) == MODE_VECTOR_FLOAT)
{
cmp_op2 = force_reg (GET_MODE (cmp_op1), cmp_op2);
switch (cond)
{
/* NE a != b -> !(a == b) */
case NE: cond = EQ; neg_p = true; break;
case UNGT:
emit_insn (gen_vec_cmpungt (target, cmp_op1, cmp_op2));
return;
case UNGE:
emit_insn (gen_vec_cmpunge (target, cmp_op1, cmp_op2));
return;
case LE: cond = GE; swap_p = true; break;
/* UNLE: (a u<= b) -> (b u>= a). */
case UNLE:
emit_insn (gen_vec_cmpunge (target, cmp_op2, cmp_op1));
return;
/* LT: a < b -> b > a */
case LT: cond = GT; swap_p = true; break;
/* UNLT: (a u< b) -> (b u> a). */
case UNLT:
emit_insn (gen_vec_cmpungt (target, cmp_op2, cmp_op1));
return;
case UNEQ:
emit_insn (gen_vec_cmpuneq (target, cmp_op1, cmp_op2));
return;
case LTGT:
emit_insn (gen_vec_cmpltgt (target, cmp_op1, cmp_op2));
return;
case ORDERED:
emit_insn (gen_vec_cmpordered (target, cmp_op1, cmp_op2));
return;
case UNORDERED:
emit_insn (gen_vec_cmpunordered (target, cmp_op1, cmp_op2));
return;
default: break;
}
}
else
{
/* Turn x < 0 into x >> (bits per element - 1) */
if (cond == LT && cmp_op2 == CONST0_RTX (mode))
{
int shift = GET_MODE_BITSIZE (GET_MODE_INNER (mode)) - 1;
rtx res = expand_simple_binop (mode, ASHIFTRT, cmp_op1,
GEN_INT (shift), target,
0, OPTAB_DIRECT);
if (res != target)
emit_move_insn (target, res);
return;
}
cmp_op2 = force_reg (GET_MODE (cmp_op1), cmp_op2);
switch (cond)
{
/* NE: a != b -> !(a == b) */
case NE: cond = EQ; neg_p = true; break;
/* GE: a >= b -> !(b > a) */
case GE: cond = GT; neg_p = true; swap_p = true; break;
/* GEU: a >= b -> !(b > a) */
case GEU: cond = GTU; neg_p = true; swap_p = true; break;
/* LE: a <= b -> !(a > b) */
case LE: cond = GT; neg_p = true; break;
/* LEU: a <= b -> !(a > b) */
case LEU: cond = GTU; neg_p = true; break;
/* LT: a < b -> b > a */
case LT: cond = GT; swap_p = true; break;
/* LTU: a < b -> b > a */
case LTU: cond = GTU; swap_p = true; break;
default: break;
}
}
if (swap_p)
{
tmp = cmp_op1; cmp_op1 = cmp_op2; cmp_op2 = tmp;
}
emit_insn (gen_rtx_SET (target, gen_rtx_fmt_ee (cond,
mode,
cmp_op1, cmp_op2)));
if (neg_p)
emit_insn (gen_rtx_SET (target, gen_rtx_NOT (mode, target)));
}
/* Expand the comparison CODE of CMP1 and CMP2 and copy 1 or 0 into
TARGET if either all (ALL_P is true) or any (ALL_P is false) of the
elements in CMP1 and CMP2 fulfill the comparison.
This function is only used to emit patterns for the vx builtins and
therefore only handles comparison codes required by the
builtins. */
void
s390_expand_vec_compare_cc (rtx target, enum rtx_code code,
rtx cmp1, rtx cmp2, bool all_p)
{
machine_mode cc_producer_mode, cc_consumer_mode, scratch_mode;
rtx tmp_reg = gen_reg_rtx (SImode);
bool swap_p = false;
if (GET_MODE_CLASS (GET_MODE (cmp1)) == MODE_VECTOR_INT)
{
switch (code)
{
case EQ:
case NE:
cc_producer_mode = CCVEQmode;
break;
case GE:
case LT:
code = swap_condition (code);
swap_p = true;
/* fallthrough */
case GT:
case LE:
cc_producer_mode = CCVIHmode;
break;
case GEU:
case LTU:
code = swap_condition (code);
swap_p = true;
/* fallthrough */
case GTU:
case LEU:
cc_producer_mode = CCVIHUmode;
break;
default:
gcc_unreachable ();
}
scratch_mode = GET_MODE (cmp1);
/* These codes represent inverted CC interpretations. Inverting
an ALL CC mode results in an ANY CC mode and the other way
around. Invert the all_p flag here to compensate for
that. */
if (code == NE || code == LE || code == LEU)
all_p = !all_p;
cc_consumer_mode = all_p ? CCVIALLmode : CCVIANYmode;
}
else if (GET_MODE_CLASS (GET_MODE (cmp1)) == MODE_VECTOR_FLOAT)
{
bool inv_p = false;
switch (code)
{
case EQ: cc_producer_mode = CCVEQmode; break;
case NE: cc_producer_mode = CCVEQmode; inv_p = true; break;
case GT: cc_producer_mode = CCVFHmode; break;
case GE: cc_producer_mode = CCVFHEmode; break;
case UNLE: cc_producer_mode = CCVFHmode; inv_p = true; break;
case UNLT: cc_producer_mode = CCVFHEmode; inv_p = true; break;
case LT: cc_producer_mode = CCVFHmode; code = GT; swap_p = true; break;
case LE: cc_producer_mode = CCVFHEmode; code = GE; swap_p = true; break;
default: gcc_unreachable ();
}
scratch_mode = related_int_vector_mode (GET_MODE (cmp1)).require ();
if (inv_p)
all_p = !all_p;
cc_consumer_mode = all_p ? CCVFALLmode : CCVFANYmode;
}
else
gcc_unreachable ();
if (swap_p)
{
rtx tmp = cmp2;
cmp2 = cmp1;
cmp1 = tmp;
}
emit_insn (gen_rtx_PARALLEL (VOIDmode,
gen_rtvec (2, gen_rtx_SET (
gen_rtx_REG (cc_producer_mode, CC_REGNUM),
gen_rtx_COMPARE (cc_producer_mode, cmp1, cmp2)),
gen_rtx_CLOBBER (VOIDmode,
gen_rtx_SCRATCH (scratch_mode)))));
emit_move_insn (target, const0_rtx);
emit_move_insn (tmp_reg, const1_rtx);
emit_move_insn (target,
gen_rtx_IF_THEN_ELSE (SImode,
gen_rtx_fmt_ee (code, VOIDmode,
gen_rtx_REG (cc_consumer_mode, CC_REGNUM),
const0_rtx),
tmp_reg, target));
}
/* Invert the comparison CODE applied to a CC mode. This is only safe
if we know whether there result was created by a floating point
compare or not. For the CCV modes this is encoded as part of the
mode. */
enum rtx_code
s390_reverse_condition (machine_mode mode, enum rtx_code code)
{
/* Reversal of FP compares takes care -- an ordered compare
becomes an unordered compare and vice versa. */
if (mode == CCVFALLmode || mode == CCVFANYmode || mode == CCSFPSmode)
return reverse_condition_maybe_unordered (code);
else if (mode == CCVIALLmode || mode == CCVIANYmode)
return reverse_condition (code);
else
gcc_unreachable ();
}
/* Generate a vector comparison expression loading either elements of
THEN or ELS into TARGET depending on the comparison COND of CMP_OP1
and CMP_OP2. */
void
s390_expand_vcond (rtx target, rtx then, rtx els,
enum rtx_code cond, rtx cmp_op1, rtx cmp_op2)
{
rtx tmp;
machine_mode result_mode;
rtx result_target;
machine_mode target_mode = GET_MODE (target);
machine_mode cmp_mode = GET_MODE (cmp_op1);
rtx op = (cond == LT) ? els : then;
/* Try to optimize x < 0 ? -1 : 0 into (signed) x >> 31
and x < 0 ? 1 : 0 into (unsigned) x >> 31. Likewise
for short and byte (x >> 15 and x >> 7 respectively). */
if ((cond == LT || cond == GE)
&& target_mode == cmp_mode
&& cmp_op2 == CONST0_RTX (cmp_mode)
&& op == CONST0_RTX (target_mode)
&& s390_vector_mode_supported_p (target_mode)
&& GET_MODE_CLASS (target_mode) == MODE_VECTOR_INT)
{
rtx negop = (cond == LT) ? then : els;
int shift = GET_MODE_BITSIZE (GET_MODE_INNER (target_mode)) - 1;
/* if x < 0 ? 1 : 0 or if x >= 0 ? 0 : 1 */
if (negop == CONST1_RTX (target_mode))
{
rtx res = expand_simple_binop (cmp_mode, LSHIFTRT, cmp_op1,
GEN_INT (shift), target,
1, OPTAB_DIRECT);
if (res != target)
emit_move_insn (target, res);
return;
}
/* if x < 0 ? -1 : 0 or if x >= 0 ? 0 : -1 */
else if (all_ones_operand (negop, target_mode))
{
rtx res = expand_simple_binop (cmp_mode, ASHIFTRT, cmp_op1,
GEN_INT (shift), target,
0, OPTAB_DIRECT);
if (res != target)
emit_move_insn (target, res);
return;
}
}
/* We always use an integral type vector to hold the comparison
result. */
result_mode = related_int_vector_mode (cmp_mode).require ();
result_target = gen_reg_rtx (result_mode);
/* We allow vector immediates as comparison operands that
can be handled by the optimization above but not by the
following code. Hence, force them into registers here. */
if (!REG_P (cmp_op1))
cmp_op1 = force_reg (GET_MODE (cmp_op1), cmp_op1);
s390_expand_vec_compare (result_target, cond, cmp_op1, cmp_op2);
/* If the results are supposed to be either -1 or 0 we are done
since this is what our compare instructions generate anyway. */
if (all_ones_operand (then, GET_MODE (then))
&& const0_operand (els, GET_MODE (els)))
{
emit_move_insn (target, gen_rtx_SUBREG (target_mode,
result_target, 0));
return;
}
/* Otherwise we will do a vsel afterwards. */
/* This gets triggered e.g.
with gcc.c-torture/compile/pr53410-1.c */
if (!REG_P (then))
then = force_reg (target_mode, then);
if (!REG_P (els))
els = force_reg (target_mode, els);
tmp = gen_rtx_fmt_ee (EQ, VOIDmode,
result_target,
CONST0_RTX (result_mode));
/* We compared the result against zero above so we have to swap then
and els here. */
tmp = gen_rtx_IF_THEN_ELSE (target_mode, tmp, els, then);
gcc_assert (target_mode == GET_MODE (then));
emit_insn (gen_rtx_SET (target, tmp));
}
/* Emit the RTX necessary to initialize the vector TARGET with values
in VALS. */
void
s390_expand_vec_init (rtx target, rtx vals)
{
machine_mode mode = GET_MODE (target);
machine_mode inner_mode = GET_MODE_INNER (mode);
int n_elts = GET_MODE_NUNITS (mode);
bool all_same = true, all_regs = true, all_const_int = true;
rtx x;
int i;
for (i = 0; i < n_elts; ++i)
{
x = XVECEXP (vals, 0, i);
if (!CONST_INT_P (x))
all_const_int = false;
if (i > 0 && !rtx_equal_p (x, XVECEXP (vals, 0, 0)))
all_same = false;
if (!REG_P (x))
all_regs = false;
}
/* Use vector gen mask or vector gen byte mask if possible. */
if (all_same && all_const_int)
{
rtx vec = gen_rtx_CONST_VECTOR (mode, XVEC (vals, 0));
if (XVECEXP (vals, 0, 0) == const0_rtx
|| s390_contiguous_bitmask_vector_p (vec, NULL, NULL)
|| s390_bytemask_vector_p (vec, NULL))
{
emit_insn (gen_rtx_SET (target, vec));
return;
}
}
/* Use vector replicate instructions. vlrep/vrepi/vrep */
if (all_same)
{
rtx elem = XVECEXP (vals, 0, 0);
/* vec_splats accepts general_operand as source. */
if (!general_operand (elem, GET_MODE (elem)))
elem = force_reg (inner_mode, elem);
emit_insn (gen_rtx_SET (target, gen_rtx_VEC_DUPLICATE (mode, elem)));
return;
}
if (all_regs
&& REG_P (target)
&& n_elts == 2
&& GET_MODE_SIZE (inner_mode) == 8)
{
/* Use vector load pair. */
emit_insn (gen_rtx_SET (target,
gen_rtx_VEC_CONCAT (mode,
XVECEXP (vals, 0, 0),
XVECEXP (vals, 0, 1))));
return;
}
/* Use vector load logical element and zero. */
if (TARGET_VXE && (mode == V4SImode || mode == V4SFmode))
{
bool found = true;
x = XVECEXP (vals, 0, 0);
if (memory_operand (x, inner_mode))
{
for (i = 1; i < n_elts; ++i)
found = found && XVECEXP (vals, 0, i) == const0_rtx;
if (found)
{
machine_mode half_mode = (inner_mode == SFmode
? V2SFmode : V2SImode);
emit_insn (gen_rtx_SET (target,
gen_rtx_VEC_CONCAT (mode,
gen_rtx_VEC_CONCAT (half_mode,
x,
const0_rtx),
gen_rtx_VEC_CONCAT (half_mode,
const0_rtx,
const0_rtx))));
return;
}
}
}
/* We are about to set the vector elements one by one. Zero out the
full register first in order to help the data flow framework to
detect it as full VR set. */
emit_insn (gen_rtx_SET (target, CONST0_RTX (mode)));
/* Unfortunately the vec_init expander is not allowed to fail. So
we have to implement the fallback ourselves. */
for (i = 0; i < n_elts; i++)
{
rtx elem = XVECEXP (vals, 0, i);
if (!general_operand (elem, GET_MODE (elem)))
elem = force_reg (inner_mode, elem);
emit_insn (gen_rtx_SET (target,
gen_rtx_UNSPEC (mode,
gen_rtvec (3, elem,
GEN_INT (i), target),
UNSPEC_VEC_SET)));
}
}
/* Return a parallel of constant integers to be used as permutation
vector for a vector merge operation in MODE. If HIGH_P is true the
left-most elements of the source vectors are merged otherwise the
right-most elements. */
rtx
s390_expand_merge_perm_const (machine_mode mode, bool high_p)
{
int nelts = GET_MODE_NUNITS (mode);
rtx perm[16];
int addend = high_p ? 0 : nelts;
for (int i = 0; i < nelts; i++)
perm[i] = GEN_INT ((i + addend) / 2 + (i % 2) * nelts);
return gen_rtx_PARALLEL (VOIDmode, gen_rtvec_v (nelts, perm));
}
/* Emit RTL to implement a vector merge operation of SRC1 and SRC2
which creates the result in TARGET. HIGH_P determines whether a
merge hi or lo will be generated. */
void
s390_expand_merge (rtx target, rtx src1, rtx src2, bool high_p)
{
machine_mode mode = GET_MODE (target);
opt_machine_mode opt_mode_2x = mode_for_vector (GET_MODE_INNER (mode),
2 * GET_MODE_NUNITS (mode));
gcc_assert (opt_mode_2x.exists ());
machine_mode mode_double_nelts = opt_mode_2x.require ();
rtx constv = s390_expand_merge_perm_const (mode, high_p);
src1 = force_reg (GET_MODE (src1), src1);
src2 = force_reg (GET_MODE (src2), src2);
rtx x = gen_rtx_VEC_CONCAT (mode_double_nelts, src1, src2);
x = gen_rtx_VEC_SELECT (mode, x, constv);
emit_insn (gen_rtx_SET (target, x));
}
/* Emit a vector constant that contains 1s in each element's sign bit position
and 0s in other positions. MODE is the desired constant's mode. */
extern rtx
s390_build_signbit_mask (machine_mode mode)
{
if (mode == TFmode && TARGET_VXE)
{
wide_int mask_val = wi::set_bit_in_zero (127, 128);
rtx mask = immed_wide_int_const (mask_val, TImode);
return gen_lowpart (TFmode, mask);
}
/* Generate the integral element mask value. */
machine_mode inner_mode = GET_MODE_INNER (mode);
int inner_bitsize = GET_MODE_BITSIZE (inner_mode);
wide_int mask_val = wi::set_bit_in_zero (inner_bitsize - 1, inner_bitsize);
/* Emit the element mask rtx. Use gen_lowpart in order to cast the integral
value to the desired mode. */
machine_mode int_mode = related_int_vector_mode (mode).require ();
rtx mask = immed_wide_int_const (mask_val, GET_MODE_INNER (int_mode));
mask = gen_lowpart (inner_mode, mask);
/* Emit the vector mask rtx by mode the element mask rtx. */
int nunits = GET_MODE_NUNITS (mode);
rtvec v = rtvec_alloc (nunits);
for (int i = 0; i < nunits; i++)
RTVEC_ELT (v, i) = mask;
return gen_rtx_CONST_VECTOR (mode, v);
}
/* Structure to hold the initial parameters for a compare_and_swap operation
in HImode and QImode. */
struct alignment_context
{
rtx memsi; /* SI aligned memory location. */
rtx shift; /* Bit offset with regard to lsb. */
rtx modemask; /* Mask of the HQImode shifted by SHIFT bits. */
rtx modemaski; /* ~modemask */
bool aligned; /* True if memory is aligned, false else. */
};
/* A subroutine of s390_expand_cs_hqi and s390_expand_atomic to initialize
structure AC for transparent simplifying, if the memory alignment is known
to be at least 32bit. MEM is the memory location for the actual operation
and MODE its mode. */
static void
init_alignment_context (struct alignment_context *ac, rtx mem,
machine_mode mode)
{
ac->shift = GEN_INT (GET_MODE_SIZE (SImode) - GET_MODE_SIZE (mode));
ac->aligned = (MEM_ALIGN (mem) >= GET_MODE_BITSIZE (SImode));
if (ac->aligned)
ac->memsi = adjust_address (mem, SImode, 0); /* Memory is aligned. */
else
{
/* Alignment is unknown. */
rtx byteoffset, addr, align;
/* Force the address into a register. */
addr = force_reg (Pmode, XEXP (mem, 0));
/* Align it to SImode. */
align = expand_simple_binop (Pmode, AND, addr,
GEN_INT (-GET_MODE_SIZE (SImode)),
NULL_RTX, 1, OPTAB_DIRECT);
/* Generate MEM. */
ac->memsi = gen_rtx_MEM (SImode, align);
MEM_VOLATILE_P (ac->memsi) = MEM_VOLATILE_P (mem);
set_mem_alias_set (ac->memsi, ALIAS_SET_MEMORY_BARRIER);
set_mem_align (ac->memsi, GET_MODE_BITSIZE (SImode));
/* Calculate shiftcount. */
byteoffset = expand_simple_binop (Pmode, AND, addr,
GEN_INT (GET_MODE_SIZE (SImode) - 1),
NULL_RTX, 1, OPTAB_DIRECT);
/* As we already have some offset, evaluate the remaining distance. */
ac->shift = expand_simple_binop (SImode, MINUS, ac->shift, byteoffset,
NULL_RTX, 1, OPTAB_DIRECT);
}
/* Shift is the byte count, but we need the bitcount. */
ac->shift = expand_simple_binop (SImode, ASHIFT, ac->shift, GEN_INT (3),
NULL_RTX, 1, OPTAB_DIRECT);
/* Calculate masks. */
ac->modemask = expand_simple_binop (SImode, ASHIFT,
GEN_INT (GET_MODE_MASK (mode)),
ac->shift, NULL_RTX, 1, OPTAB_DIRECT);
ac->modemaski = expand_simple_unop (SImode, NOT, ac->modemask,
NULL_RTX, 1);
}
/* A subroutine of s390_expand_cs_hqi. Insert INS into VAL. If possible,
use a single insv insn into SEQ2. Otherwise, put prep insns in SEQ1 and
perform the merge in SEQ2. */
static rtx
s390_two_part_insv (struct alignment_context *ac, rtx *seq1, rtx *seq2,
machine_mode mode, rtx val, rtx ins)
{
rtx tmp;
if (ac->aligned)
{
start_sequence ();
tmp = copy_to_mode_reg (SImode, val);
if (s390_expand_insv (tmp, GEN_INT (GET_MODE_BITSIZE (mode)),
const0_rtx, ins))
{
*seq1 = NULL;
*seq2 = get_insns ();
end_sequence ();
return tmp;
}
end_sequence ();
}
/* Failed to use insv. Generate a two part shift and mask. */
start_sequence ();
tmp = s390_expand_mask_and_shift (ins, mode, ac->shift);
*seq1 = get_insns ();
end_sequence ();
start_sequence ();
tmp = expand_simple_binop (SImode, IOR, tmp, val, NULL_RTX, 1, OPTAB_DIRECT);
*seq2 = get_insns ();
end_sequence ();
return tmp;
}
/* Expand an atomic compare and swap operation for HImode and QImode. MEM is
the memory location, CMP the old value to compare MEM with and NEW_RTX the
value to set if CMP == MEM. */
static void
s390_expand_cs_hqi (machine_mode mode, rtx btarget, rtx vtarget, rtx mem,
rtx cmp, rtx new_rtx, bool is_weak)
{
struct alignment_context ac;
rtx cmpv, newv, val, cc, seq0, seq1, seq2, seq3;
rtx res = gen_reg_rtx (SImode);
rtx_code_label *csloop = NULL, *csend = NULL;
gcc_assert (MEM_P (mem));
init_alignment_context (&ac, mem, mode);
/* Load full word. Subsequent loads are performed by CS. */
val = expand_simple_binop (SImode, AND, ac.memsi, ac.modemaski,
NULL_RTX, 1, OPTAB_DIRECT);
/* Prepare insertions of cmp and new_rtx into the loaded value. When
possible, we try to use insv to make this happen efficiently. If
that fails we'll generate code both inside and outside the loop. */
cmpv = s390_two_part_insv (&ac, &seq0, &seq2, mode, val, cmp);
newv = s390_two_part_insv (&ac, &seq1, &seq3, mode, val, new_rtx);
if (seq0)
emit_insn (seq0);
if (seq1)
emit_insn (seq1);
/* Start CS loop. */
if (!is_weak)
{
/* Begin assuming success. */
emit_move_insn (btarget, const1_rtx);
csloop = gen_label_rtx ();
csend = gen_label_rtx ();
emit_label (csloop);
}
/* val = "<mem>00..0<mem>"
* cmp = "00..0<cmp>00..0"
* new = "00..0<new>00..0"
*/
emit_insn (seq2);
emit_insn (seq3);
cc = s390_emit_compare_and_swap (EQ, res, ac.memsi, cmpv, newv, CCZ1mode);
if (is_weak)
emit_insn (gen_cstorecc4 (btarget, cc, XEXP (cc, 0), XEXP (cc, 1)));
else
{
rtx tmp;
/* Jump to end if we're done (likely?). */
s390_emit_jump (csend, cc);
/* Check for changes outside mode, and loop internal if so.
Arrange the moves so that the compare is adjacent to the
branch so that we can generate CRJ. */
tmp = copy_to_reg (val);
force_expand_binop (SImode, and_optab, res, ac.modemaski, val,
1, OPTAB_DIRECT);
cc = s390_emit_compare (NE, val, tmp);
s390_emit_jump (csloop, cc);
/* Failed. */
emit_move_insn (btarget, const0_rtx);
emit_label (csend);
}
/* Return the correct part of the bitfield. */
convert_move (vtarget, expand_simple_binop (SImode, LSHIFTRT, res, ac.shift,
NULL_RTX, 1, OPTAB_DIRECT), 1);
}
/* Variant of s390_expand_cs for SI, DI and TI modes. */
static void
s390_expand_cs_tdsi (machine_mode mode, rtx btarget, rtx vtarget, rtx mem,
rtx cmp, rtx new_rtx, bool is_weak)
{
rtx output = vtarget;
rtx_code_label *skip_cs_label = NULL;
bool do_const_opt = false;
if (!register_operand (output, mode))
output = gen_reg_rtx (mode);
/* If IS_WEAK is true and the INPUT value is a constant, compare the memory
with the constant first and skip the compare_and_swap because its very
expensive and likely to fail anyway.
Note 1: This is done only for IS_WEAK. C11 allows optimizations that may
cause spurious in that case.
Note 2: It may be useful to do this also for non-constant INPUT.
Note 3: Currently only targets with "load on condition" are supported
(z196 and newer). */
if (TARGET_Z196
&& (mode == SImode || mode == DImode))
do_const_opt = (is_weak && CONST_INT_P (cmp));
if (do_const_opt)
{
rtx cc = gen_rtx_REG (CCZmode, CC_REGNUM);
skip_cs_label = gen_label_rtx ();
emit_move_insn (btarget, const0_rtx);
if (CONST_INT_P (cmp) && INTVAL (cmp) == 0)
{
rtvec lt = rtvec_alloc (2);
/* Load-and-test + conditional jump. */
RTVEC_ELT (lt, 0)
= gen_rtx_SET (cc, gen_rtx_COMPARE (CCZmode, mem, cmp));
RTVEC_ELT (lt, 1) = gen_rtx_SET (output, mem);
emit_insn (gen_rtx_PARALLEL (VOIDmode, lt));
}
else
{
emit_move_insn (output, mem);
emit_insn (gen_rtx_SET (cc, gen_rtx_COMPARE (CCZmode, output, cmp)));
}
s390_emit_jump (skip_cs_label, gen_rtx_NE (VOIDmode, cc, const0_rtx));
add_reg_br_prob_note (get_last_insn (),
profile_probability::very_unlikely ());
/* If the jump is not taken, OUTPUT is the expected value. */
cmp = output;
/* Reload newval to a register manually, *after* the compare and jump
above. Otherwise Reload might place it before the jump. */
}
else
cmp = force_reg (mode, cmp);
new_rtx = force_reg (mode, new_rtx);
s390_emit_compare_and_swap (EQ, output, mem, cmp, new_rtx,
(do_const_opt) ? CCZmode : CCZ1mode);
if (skip_cs_label != NULL)
emit_label (skip_cs_label);
/* We deliberately accept non-register operands in the predicate
to ensure the write back to the output operand happens *before*
the store-flags code below. This makes it easier for combine
to merge the store-flags code with a potential test-and-branch
pattern following (immediately!) afterwards. */
if (output != vtarget)
emit_move_insn (vtarget, output);
if (do_const_opt)
{
rtx cc, cond, ite;
/* Do not use gen_cstorecc4 here because it writes either 1 or 0, but
btarget has already been initialized with 0 above. */
cc = gen_rtx_REG (CCZmode, CC_REGNUM);
cond = gen_rtx_EQ (VOIDmode, cc, const0_rtx);
ite = gen_rtx_IF_THEN_ELSE (SImode, cond, const1_rtx, btarget);
emit_insn (gen_rtx_SET (btarget, ite));
}
else
{
rtx cc, cond;
cc = gen_rtx_REG (CCZ1mode, CC_REGNUM);
cond = gen_rtx_EQ (SImode, cc, const0_rtx);
emit_insn (gen_cstorecc4 (btarget, cond, cc, const0_rtx));
}
}
/* Expand an atomic compare and swap operation. MEM is the memory location,
CMP the old value to compare MEM with and NEW_RTX the value to set if
CMP == MEM. */
void
s390_expand_cs (machine_mode mode, rtx btarget, rtx vtarget, rtx mem,
rtx cmp, rtx new_rtx, bool is_weak)
{
switch (mode)
{
case E_TImode:
case E_DImode:
case E_SImode:
s390_expand_cs_tdsi (mode, btarget, vtarget, mem, cmp, new_rtx, is_weak);
break;
case E_HImode:
case E_QImode:
s390_expand_cs_hqi (mode, btarget, vtarget, mem, cmp, new_rtx, is_weak);
break;
default:
gcc_unreachable ();
}
}
/* Expand an atomic_exchange operation simulated with a compare-and-swap loop.
The memory location MEM is set to INPUT. OUTPUT is set to the previous value
of MEM. */
void
s390_expand_atomic_exchange_tdsi (rtx output, rtx mem, rtx input)
{
machine_mode mode = GET_MODE (mem);
rtx_code_label *csloop;
if (TARGET_Z196
&& (mode == DImode || mode == SImode)
&& CONST_INT_P (input) && INTVAL (input) == 0)
{
emit_move_insn (output, const0_rtx);
if (mode == DImode)
emit_insn (gen_atomic_fetch_anddi (output, mem, const0_rtx, input));
else
emit_insn (gen_atomic_fetch_andsi (output, mem, const0_rtx, input));
return;
}
input = force_reg (mode, input);
emit_move_insn (output, mem);
csloop = gen_label_rtx ();
emit_label (csloop);
s390_emit_jump (csloop, s390_emit_compare_and_swap (NE, output, mem, output,
input, CCZ1mode));
}
/* Expand an atomic operation CODE of mode MODE. MEM is the memory location
and VAL the value to play with. If AFTER is true then store the value
MEM holds after the operation, if AFTER is false then store the value MEM
holds before the operation. If TARGET is zero then discard that value, else
store it to TARGET. */
void
s390_expand_atomic (machine_mode mode, enum rtx_code code,
rtx target, rtx mem, rtx val, bool after)
{
struct alignment_context ac;
rtx cmp;
rtx new_rtx = gen_reg_rtx (SImode);
rtx orig = gen_reg_rtx (SImode);
rtx_code_label *csloop = gen_label_rtx ();
gcc_assert (!target || register_operand (target, VOIDmode));
gcc_assert (MEM_P (mem));
init_alignment_context (&ac, mem, mode);
/* Shift val to the correct bit positions.
Preserve "icm", but prevent "ex icm". */
if (!(ac.aligned && code == SET && MEM_P (val)))
val = s390_expand_mask_and_shift (val, mode, ac.shift);
/* Further preparation insns. */
if (code == PLUS || code == MINUS)
emit_move_insn (orig, val);
else if (code == MULT || code == AND) /* val = "11..1<val>11..1" */
val = expand_simple_binop (SImode, XOR, val, ac.modemaski,
NULL_RTX, 1, OPTAB_DIRECT);
/* Load full word. Subsequent loads are performed by CS. */
cmp = force_reg (SImode, ac.memsi);
/* Start CS loop. */
emit_label (csloop);
emit_move_insn (new_rtx, cmp);
/* Patch new with val at correct position. */
switch (code)
{
case PLUS:
case MINUS:
val = expand_simple_binop (SImode, code, new_rtx, orig,
NULL_RTX, 1, OPTAB_DIRECT);
val = expand_simple_binop (SImode, AND, val, ac.modemask,
NULL_RTX, 1, OPTAB_DIRECT);
/* FALLTHRU */
case SET:
if (ac.aligned && MEM_P (val))
store_bit_field (new_rtx, GET_MODE_BITSIZE (mode), 0,
0, 0, SImode, val, false);
else
{
new_rtx = expand_simple_binop (SImode, AND, new_rtx, ac.modemaski,
NULL_RTX, 1, OPTAB_DIRECT);
new_rtx = expand_simple_binop (SImode, IOR, new_rtx, val,
NULL_RTX, 1, OPTAB_DIRECT);
}
break;
case AND:
case IOR:
case XOR:
new_rtx = expand_simple_binop (SImode, code, new_rtx, val,
NULL_RTX, 1, OPTAB_DIRECT);
break;
case MULT: /* NAND */
new_rtx = expand_simple_binop (SImode, AND, new_rtx, val,
NULL_RTX, 1, OPTAB_DIRECT);
new_rtx = expand_simple_binop (SImode, XOR, new_rtx, ac.modemask,
NULL_RTX, 1, OPTAB_DIRECT);
break;
default:
gcc_unreachable ();
}
s390_emit_jump (csloop, s390_emit_compare_and_swap (NE, cmp,
ac.memsi, cmp, new_rtx,
CCZ1mode));
/* Return the correct part of the bitfield. */
if (target)
convert_move (target, expand_simple_binop (SImode, LSHIFTRT,
after ? new_rtx : cmp, ac.shift,
NULL_RTX, 1, OPTAB_DIRECT), 1);
}
/* This is called from dwarf2out.c via TARGET_ASM_OUTPUT_DWARF_DTPREL.
We need to emit DTP-relative relocations. */
static void s390_output_dwarf_dtprel (FILE *, int, rtx) ATTRIBUTE_UNUSED;
static void
s390_output_dwarf_dtprel (FILE *file, int size, rtx x)
{
switch (size)
{
case 4:
fputs ("\t.long\t", file);
break;
case 8:
fputs ("\t.quad\t", file);
break;
default:
gcc_unreachable ();
}
output_addr_const (file, x);
fputs ("@DTPOFF", file);
}
/* Return the proper mode for REGNO being represented in the dwarf
unwind table. */
machine_mode
s390_dwarf_frame_reg_mode (int regno)
{
machine_mode save_mode = default_dwarf_frame_reg_mode (regno);
/* Make sure not to return DImode for any GPR with -m31 -mzarch. */
if (GENERAL_REGNO_P (regno))
save_mode = Pmode;
/* The rightmost 64 bits of vector registers are call-clobbered. */
if (GET_MODE_SIZE (save_mode) > 8)
save_mode = DImode;
return save_mode;
}
#ifdef TARGET_ALTERNATE_LONG_DOUBLE_MANGLING
/* Implement TARGET_MANGLE_TYPE. */
static const char *
s390_mangle_type (const_tree type)
{
type = TYPE_MAIN_VARIANT (type);
if (TREE_CODE (type) != VOID_TYPE && TREE_CODE (type) != BOOLEAN_TYPE
&& TREE_CODE (type) != INTEGER_TYPE && TREE_CODE (type) != REAL_TYPE)
return NULL;
if (type == s390_builtin_types[BT_BV16QI]) return "U6__boolc";
if (type == s390_builtin_types[BT_BV8HI]) return "U6__bools";
if (type == s390_builtin_types[BT_BV4SI]) return "U6__booli";
if (type == s390_builtin_types[BT_BV2DI]) return "U6__booll";
if (TYPE_MAIN_VARIANT (type) == long_double_type_node
&& TARGET_LONG_DOUBLE_128)
return "g";
/* For all other types, use normal C++ mangling. */
return NULL;
}
#endif
/* In the name of slightly smaller debug output, and to cater to
general assembler lossage, recognize various UNSPEC sequences
and turn them back into a direct symbol reference. */
static rtx
s390_delegitimize_address (rtx orig_x)
{
rtx x, y;
orig_x = delegitimize_mem_from_attrs (orig_x);
x = orig_x;
/* Extract the symbol ref from:
(plus:SI (reg:SI 12 %r12)
(const:SI (unspec:SI [(symbol_ref/f:SI ("*.LC0"))]
UNSPEC_GOTOFF/PLTOFF)))
and
(plus:SI (reg:SI 12 %r12)
(const:SI (plus:SI (unspec:SI [(symbol_ref:SI ("L"))]
UNSPEC_GOTOFF/PLTOFF)
(const_int 4 [0x4])))) */
if (GET_CODE (x) == PLUS
&& REG_P (XEXP (x, 0))
&& REGNO (XEXP (x, 0)) == PIC_OFFSET_TABLE_REGNUM
&& GET_CODE (XEXP (x, 1)) == CONST)
{
HOST_WIDE_INT offset = 0;
/* The const operand. */
y = XEXP (XEXP (x, 1), 0);
if (GET_CODE (y) == PLUS
&& GET_CODE (XEXP (y, 1)) == CONST_INT)
{
offset = INTVAL (XEXP (y, 1));
y = XEXP (y, 0);
}
if (GET_CODE (y) == UNSPEC
&& (XINT (y, 1) == UNSPEC_GOTOFF
|| XINT (y, 1) == UNSPEC_PLTOFF))
return plus_constant (Pmode, XVECEXP (y, 0, 0), offset);
}
if (GET_CODE (x) != MEM)
return orig_x;
x = XEXP (x, 0);
if (GET_CODE (x) == PLUS
&& GET_CODE (XEXP (x, 1)) == CONST
&& GET_CODE (XEXP (x, 0)) == REG
&& REGNO (XEXP (x, 0)) == PIC_OFFSET_TABLE_REGNUM)
{
y = XEXP (XEXP (x, 1), 0);
if (GET_CODE (y) == UNSPEC
&& XINT (y, 1) == UNSPEC_GOT)
y = XVECEXP (y, 0, 0);
else
return orig_x;
}
else if (GET_CODE (x) == CONST)
{
/* Extract the symbol ref from:
(mem:QI (const:DI (unspec:DI [(symbol_ref:DI ("foo"))]
UNSPEC_PLT/GOTENT))) */
y = XEXP (x, 0);
if (GET_CODE (y) == UNSPEC
&& (XINT (y, 1) == UNSPEC_GOTENT
|| XINT (y, 1) == UNSPEC_PLT31))
y = XVECEXP (y, 0, 0);
else
return orig_x;
}
else
return orig_x;
if (GET_MODE (orig_x) != Pmode)
{
if (GET_MODE (orig_x) == BLKmode)
return orig_x;
y = lowpart_subreg (GET_MODE (orig_x), y, Pmode);
if (y == NULL_RTX)
return orig_x;
}
return y;
}
/* Output operand OP to stdio stream FILE.
OP is an address (register + offset) which is not used to address data;
instead the rightmost bits are interpreted as the value. */
static void
print_addrstyle_operand (FILE *file, rtx op)
{
HOST_WIDE_INT offset;
rtx base;
/* Extract base register and offset. */
if (!s390_decompose_addrstyle_without_index (op, &base, &offset))
gcc_unreachable ();
/* Sanity check. */
if (base)
{
gcc_assert (GET_CODE (base) == REG);
gcc_assert (REGNO (base) < FIRST_PSEUDO_REGISTER);
gcc_assert (REGNO_REG_CLASS (REGNO (base)) == ADDR_REGS);
}
/* Offsets are constricted to twelve bits. */
fprintf (file, HOST_WIDE_INT_PRINT_DEC, offset & ((1 << 12) - 1));
if (base)
fprintf (file, "(%s)", reg_names[REGNO (base)]);
}
/* Print the shift count operand OP to FILE.
OP is an address-style operand in a form which
s390_valid_shift_count permits. Subregs and no-op
and-masking of the operand are stripped. */
static void
print_shift_count_operand (FILE *file, rtx op)
{
/* No checking of the and mask required here. */
if (!s390_valid_shift_count (op, 0))
gcc_unreachable ();
while (op && GET_CODE (op) == SUBREG)
op = SUBREG_REG (op);
if (GET_CODE (op) == AND)
op = XEXP (op, 0);
print_addrstyle_operand (file, op);
}
/* Assigns the number of NOP halfwords to be emitted before and after the
function label to *HW_BEFORE and *HW_AFTER. Both pointers must not be NULL.
If hotpatching is disabled for the function, the values are set to zero.
*/
static void
s390_function_num_hotpatch_hw (tree decl,
int *hw_before,
int *hw_after)
{
tree attr;
attr = lookup_attribute ("hotpatch", DECL_ATTRIBUTES (decl));
/* Handle the arguments of the hotpatch attribute. The values
specified via attribute might override the cmdline argument
values. */
if (attr)
{
tree args = TREE_VALUE (attr);
*hw_before = TREE_INT_CST_LOW (TREE_VALUE (args));
*hw_after = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args)));
}
else
{
/* Use the values specified by the cmdline arguments. */
*hw_before = s390_hotpatch_hw_before_label;
*hw_after = s390_hotpatch_hw_after_label;
}
}
/* Write the current .machine and .machinemode specification to the assembler
file. */
#ifdef HAVE_AS_MACHINE_MACHINEMODE
static void
s390_asm_output_machine_for_arch (FILE *asm_out_file)
{
fprintf (asm_out_file, "\t.machinemode %s\n",
(TARGET_ZARCH) ? "zarch" : "esa");
fprintf (asm_out_file, "\t.machine \"%s",
processor_table[s390_arch].binutils_name);
if (S390_USE_ARCHITECTURE_MODIFIERS)
{
int cpu_flags;
cpu_flags = processor_flags_table[(int) s390_arch];
if (TARGET_HTM && !(cpu_flags & PF_TX))
fprintf (asm_out_file, "+htm");
else if (!TARGET_HTM && (cpu_flags & PF_TX))
fprintf (asm_out_file, "+nohtm");
if (TARGET_VX && !(cpu_flags & PF_VX))
fprintf (asm_out_file, "+vx");
else if (!TARGET_VX && (cpu_flags & PF_VX))
fprintf (asm_out_file, "+novx");
}
fprintf (asm_out_file, "\"\n");
}
/* Write an extra function header before the very start of the function. */
void
s390_asm_output_function_prefix (FILE *asm_out_file,
const char *fnname ATTRIBUTE_UNUSED)
{
if (DECL_FUNCTION_SPECIFIC_TARGET (current_function_decl) == NULL)
return;
/* Since only the function specific options are saved but not the indications
which options are set, it's too much work here to figure out which options
have actually changed. Thus, generate .machine and .machinemode whenever a
function has the target attribute or pragma. */
fprintf (asm_out_file, "\t.machinemode push\n");
fprintf (asm_out_file, "\t.machine push\n");
s390_asm_output_machine_for_arch (asm_out_file);
}
/* Write an extra function footer after the very end of the function. */
void
s390_asm_declare_function_size (FILE *asm_out_file,
const char *fnname, tree decl)
{
if (!flag_inhibit_size_directive)
ASM_OUTPUT_MEASURED_SIZE (asm_out_file, fnname);
if (DECL_FUNCTION_SPECIFIC_TARGET (decl) == NULL)
return;
fprintf (asm_out_file, "\t.machine pop\n");
fprintf (asm_out_file, "\t.machinemode pop\n");
}
#endif
/* Write the extra assembler code needed to declare a function properly. */
void
s390_asm_output_function_label (FILE *asm_out_file, const char *fname,
tree decl)
{
int hw_before, hw_after;
s390_function_num_hotpatch_hw (decl, &hw_before, &hw_after);
if (hw_before > 0)
{
unsigned int function_alignment;
int i;
/* Add a trampoline code area before the function label and initialize it
with two-byte nop instructions. This area can be overwritten with code
that jumps to a patched version of the function. */
asm_fprintf (asm_out_file, "\tnopr\t%%r0"
"\t# pre-label NOPs for hotpatch (%d halfwords)\n",
hw_before);
for (i = 1; i < hw_before; i++)
fputs ("\tnopr\t%r0\n", asm_out_file);
/* Note: The function label must be aligned so that (a) the bytes of the
following nop do not cross a cacheline boundary, and (b) a jump address
(eight bytes for 64 bit targets, 4 bytes for 32 bit targets) can be
stored directly before the label without crossing a cacheline
boundary. All this is necessary to make sure the trampoline code can
be changed atomically.
This alignment is done automatically using the FOUNCTION_BOUNDARY, but
if there are NOPs before the function label, the alignment is placed
before them. So it is necessary to duplicate the alignment after the
NOPs. */
function_alignment = MAX (8, DECL_ALIGN (decl) / BITS_PER_UNIT);
if (! DECL_USER_ALIGN (decl))
function_alignment
= MAX (function_alignment,
(unsigned int) align_functions.levels[0].get_value ());
fputs ("\t# alignment for hotpatch\n", asm_out_file);
ASM_OUTPUT_ALIGN (asm_out_file, align_functions.levels[0].log);
}
if (S390_USE_TARGET_ATTRIBUTE && TARGET_DEBUG_ARG)
{
asm_fprintf (asm_out_file, "\t# fn:%s ar%d\n", fname, s390_arch);
asm_fprintf (asm_out_file, "\t# fn:%s tu%d\n", fname, s390_tune);
asm_fprintf (asm_out_file, "\t# fn:%s sg%d\n", fname, s390_stack_guard);
asm_fprintf (asm_out_file, "\t# fn:%s ss%d\n", fname, s390_stack_size);
asm_fprintf (asm_out_file, "\t# fn:%s bc%d\n", fname, s390_branch_cost);
asm_fprintf (asm_out_file, "\t# fn:%s wf%d\n", fname,
s390_warn_framesize);
asm_fprintf (asm_out_file, "\t# fn:%s ba%d\n", fname, TARGET_BACKCHAIN);
asm_fprintf (asm_out_file, "\t# fn:%s hd%d\n", fname, TARGET_HARD_DFP);
asm_fprintf (asm_out_file, "\t# fn:%s hf%d\n", fname, !TARGET_SOFT_FLOAT);
asm_fprintf (asm_out_file, "\t# fn:%s ht%d\n", fname, TARGET_OPT_HTM);
asm_fprintf (asm_out_file, "\t# fn:%s vx%d\n", fname, TARGET_OPT_VX);
asm_fprintf (asm_out_file, "\t# fn:%s ps%d\n", fname,
TARGET_PACKED_STACK);
asm_fprintf (asm_out_file, "\t# fn:%s se%d\n", fname, TARGET_SMALL_EXEC);
asm_fprintf (asm_out_file, "\t# fn:%s mv%d\n", fname, TARGET_MVCLE);
asm_fprintf (asm_out_file, "\t# fn:%s zv%d\n", fname, TARGET_ZVECTOR);
asm_fprintf (asm_out_file, "\t# fn:%s wd%d\n", fname,
s390_warn_dynamicstack_p);
}
ASM_OUTPUT_LABEL (asm_out_file, fname);
if (hw_after > 0)
asm_fprintf (asm_out_file,
"\t# post-label NOPs for hotpatch (%d halfwords)\n",
hw_after);
}
/* Output machine-dependent UNSPECs occurring in address constant X
in assembler syntax to stdio stream FILE. Returns true if the
constant X could be recognized, false otherwise. */
static bool
s390_output_addr_const_extra (FILE *file, rtx x)
{
if (GET_CODE (x) == UNSPEC && XVECLEN (x, 0) == 1)
switch (XINT (x, 1))
{
case UNSPEC_GOTENT:
output_addr_const (file, XVECEXP (x, 0, 0));
fprintf (file, "@GOTENT");
return true;
case UNSPEC_GOT:
output_addr_const (file, XVECEXP (x, 0, 0));
fprintf (file, "@GOT");
return true;
case UNSPEC_GOTOFF:
output_addr_const (file, XVECEXP (x, 0, 0));
fprintf (file, "@GOTOFF");
return true;
case UNSPEC_PLT31:
output_addr_const (file, XVECEXP (x, 0, 0));
fprintf (file, "@PLT");
return true;
case UNSPEC_PLTOFF:
output_addr_const (file, XVECEXP (x, 0, 0));
fprintf (file, "@PLTOFF");
return true;
case UNSPEC_TLSGD:
output_addr_const (file, XVECEXP (x, 0, 0));
fprintf (file, "@TLSGD");
return true;
case UNSPEC_TLSLDM:
assemble_name (file, get_some_local_dynamic_name ());
fprintf (file, "@TLSLDM");
return true;
case UNSPEC_DTPOFF:
output_addr_const (file, XVECEXP (x, 0, 0));
fprintf (file, "@DTPOFF");
return true;
case UNSPEC_NTPOFF:
output_addr_const (file, XVECEXP (x, 0, 0));
fprintf (file, "@NTPOFF");
return true;
case UNSPEC_GOTNTPOFF:
output_addr_const (file, XVECEXP (x, 0, 0));
fprintf (file, "@GOTNTPOFF");
return true;
case UNSPEC_INDNTPOFF:
output_addr_const (file, XVECEXP (x, 0, 0));
fprintf (file, "@INDNTPOFF");
return true;
}
if (GET_CODE (x) == UNSPEC && XVECLEN (x, 0) == 2)
switch (XINT (x, 1))
{
case UNSPEC_POOL_OFFSET:
x = gen_rtx_MINUS (GET_MODE (x), XVECEXP (x, 0, 0), XVECEXP (x, 0, 1));
output_addr_const (file, x);
return true;
}
return false;
}
/* Output address operand ADDR in assembler syntax to
stdio stream FILE. */
void
print_operand_address (FILE *file, rtx addr)
{
struct s390_address ad;
memset (&ad, 0, sizeof (s390_address));
if (s390_loadrelative_operand_p (addr, NULL, NULL))
{
if (!TARGET_Z10)
{
output_operand_lossage ("symbolic memory references are "
"only supported on z10 or later");
return;
}
output_addr_const (file, addr);
return;
}
if (!s390_decompose_address (addr, &ad)
|| (ad.base && !REGNO_OK_FOR_BASE_P (REGNO (ad.base)))
|| (ad.indx && !REGNO_OK_FOR_INDEX_P (REGNO (ad.indx))))
output_operand_lossage ("cannot decompose address");
if (ad.disp)
output_addr_const (file, ad.disp);
else
fprintf (file, "0");
if (ad.base && ad.indx)
fprintf (file, "(%s,%s)", reg_names[REGNO (ad.indx)],
reg_names[REGNO (ad.base)]);
else if (ad.base)
fprintf (file, "(%s)", reg_names[REGNO (ad.base)]);
}
/* Output operand X in assembler syntax to stdio stream FILE.
CODE specified the format flag. The following format flags
are recognized:
'A': On z14 or higher: If operand is a mem print the alignment
hint usable with vl/vst prefixed by a comma.
'C': print opcode suffix for branch condition.
'D': print opcode suffix for inverse branch condition.
'E': print opcode suffix for branch on index instruction.
'G': print the size of the operand in bytes.
'J': print tls_load/tls_gdcall/tls_ldcall suffix
'K': print @PLT suffix for call targets and load address values.
'M': print the second word of a TImode operand.
'N': print the second word of a DImode operand.
'O': print only the displacement of a memory reference or address.
'R': print only the base register of a memory reference or address.
'S': print S-type memory reference (base+displacement).
'Y': print address style operand without index (e.g. shift count or setmem
operand).
'b': print integer X as if it's an unsigned byte.
'c': print integer X as if it's an signed byte.
'e': "end" contiguous bitmask X in either DImode or vector inner mode.
'f': "end" contiguous bitmask X in SImode.
'h': print integer X as if it's a signed halfword.
'i': print the first nonzero HImode part of X.
'j': print the first HImode part unequal to -1 of X.
'k': print the first nonzero SImode part of X.
'm': print the first SImode part unequal to -1 of X.
'o': print integer X as if it's an unsigned 32bit word.
's': "start" of contiguous bitmask X in either DImode or vector inner mode.
't': CONST_INT: "start" of contiguous bitmask X in SImode.
CONST_VECTOR: Generate a bitmask for vgbm instruction.
'x': print integer X as if it's an unsigned halfword.
'v': print register number as vector register (v1 instead of f1).
'V': print the second word of a TFmode operand as vector register.
*/
void
print_operand (FILE *file, rtx x, int code)
{
HOST_WIDE_INT ival;
switch (code)
{
case 'A':
if (TARGET_VECTOR_LOADSTORE_ALIGNMENT_HINTS && MEM_P (x))
{
if (MEM_ALIGN (x) >= 128)
fprintf (file, ",4");
else if (MEM_ALIGN (x) == 64)
fprintf (file, ",3");
}
return;
case 'C':
fprintf (file, s390_branch_condition_mnemonic (x, FALSE));
return;
case 'D':
fprintf (file, s390_branch_condition_mnemonic (x, TRUE));
return;
case 'E':
if (GET_CODE (x) == LE)
fprintf (file, "l");
else if (GET_CODE (x) == GT)
fprintf (file, "h");
else
output_operand_lossage ("invalid comparison operator "
"for 'E' output modifier");
return;
case 'J':
if (GET_CODE (x) == SYMBOL_REF)
{
fprintf (file, "%s", ":tls_load:");
output_addr_const (file, x);
}
else if (GET_CODE (x) == UNSPEC && XINT (x, 1) == UNSPEC_TLSGD)
{
fprintf (file, "%s", ":tls_gdcall:");
output_addr_const (file, XVECEXP (x, 0, 0));
}
else if (GET_CODE (x) == UNSPEC && XINT (x, 1) == UNSPEC_TLSLDM)
{
fprintf (file, "%s", ":tls_ldcall:");
const char *name = get_some_local_dynamic_name ();
gcc_assert (name);
assemble_name (file, name);
}
else
output_operand_lossage ("invalid reference for 'J' output modifier");
return;
case 'G':
fprintf (file, "%u", GET_MODE_SIZE (GET_MODE (x)));
return;
case 'O':
{
struct s390_address ad;
int ret;
ret = s390_decompose_address (MEM_P (x) ? XEXP (x, 0) : x, &ad);
if (!ret
|| (ad.base && !REGNO_OK_FOR_BASE_P (REGNO (ad.base)))
|| ad.indx)
{
output_operand_lossage ("invalid address for 'O' output modifier");
return;
}
if (ad.disp)
output_addr_const (file, ad.disp);
else
fprintf (file, "0");
}
return;
case 'R':
{
struct s390_address ad;
int ret;
ret = s390_decompose_address (MEM_P (x) ? XEXP (x, 0) : x, &ad);
if (!ret
|| (ad.base && !REGNO_OK_FOR_BASE_P (REGNO (ad.base)))
|| ad.indx)
{
output_operand_lossage ("invalid address for 'R' output modifier");
return;
}
if (ad.base)
fprintf (file, "%s", reg_names[REGNO (ad.base)]);
else
fprintf (file, "0");
}
return;
case 'S':
{
struct s390_address ad;
int ret;
if (!MEM_P (x))
{
output_operand_lossage ("memory reference expected for "
"'S' output modifier");
return;
}
ret = s390_decompose_address (XEXP (x, 0), &ad);
if (!ret
|| (ad.base && !REGNO_OK_FOR_BASE_P (REGNO (ad.base)))
|| ad.indx)
{
output_operand_lossage ("invalid address for 'S' output modifier");
return;
}
if (ad.disp)
output_addr_const (file, ad.disp);
else
fprintf (file, "0");
if (ad.base)
fprintf (file, "(%s)", reg_names[REGNO (ad.base)]);
}
return;
case 'N':
if (GET_CODE (x) == REG)
x = gen_rtx_REG (GET_MODE (x), REGNO (x) + 1);
else if (GET_CODE (x) == MEM)
x = change_address (x, VOIDmode,
plus_constant (Pmode, XEXP (x, 0), 4));
else
output_operand_lossage ("register or memory expression expected "
"for 'N' output modifier");
break;
case 'M':
if (GET_CODE (x) == REG)
x = gen_rtx_REG (GET_MODE (x), REGNO (x) + 1);
else if (GET_CODE (x) == MEM)
x = change_address (x, VOIDmode,
plus_constant (Pmode, XEXP (x, 0), 8));
else
output_operand_lossage ("register or memory expression expected "
"for 'M' output modifier");
break;
case 'Y':
print_shift_count_operand (file, x);
return;
case 'K':
/* Append @PLT to both local and non-local symbols in order to support
Linux Kernel livepatching: patches contain individual functions and
are loaded further than 2G away from vmlinux, and therefore they must
call even static functions via PLT. ld will optimize @PLT away for
normal code, and keep it for patches.
Do not indiscriminately add @PLT in 31-bit mode due to the %r12
restriction, use UNSPEC_PLT31 instead.
@PLT only makes sense for functions, data is taken care of by
-mno-pic-data-is-text-relative.
Adding @PLT interferes with handling of weak symbols in non-PIC code,
since their addresses are loaded with larl, which then always produces
a non-NULL result, so skip them here as well. */
if (TARGET_64BIT
&& GET_CODE (x) == SYMBOL_REF
&& SYMBOL_REF_FUNCTION_P (x)
&& !(SYMBOL_REF_WEAK (x) && !flag_pic))
fprintf (file, "@PLT");
return;
}
switch (GET_CODE (x))
{
case REG:
/* Print FP regs as fx instead of vx when they are accessed
through non-vector mode. */
if ((code == 'v' || code == 'V')
|| VECTOR_NOFP_REG_P (x)
|| (FP_REG_P (x) && VECTOR_MODE_P (GET_MODE (x)))
|| (VECTOR_REG_P (x)
&& (GET_MODE_SIZE (GET_MODE (x)) /
s390_class_max_nregs (FP_REGS, GET_MODE (x))) > 8))
fprintf (file, "%%v%s", reg_names[REGNO (x) + (code == 'V')] + 2);
else
fprintf (file, "%s", reg_names[REGNO (x)]);
break;
case MEM:
output_address (GET_MODE (x), XEXP (x, 0));
break;
case CONST:
case CODE_LABEL:
case LABEL_REF:
case SYMBOL_REF:
output_addr_const (file, x);
break;
case CONST_INT:
ival = INTVAL (x);
switch (code)
{
case 0:
break;
case 'b':
ival &= 0xff;
break;
case 'c':
ival = ((ival & 0xff) ^ 0x80) - 0x80;
break;
case 'x':
ival &= 0xffff;
break;
case 'h':
ival = ((ival & 0xffff) ^ 0x8000) - 0x8000;
break;
case 'i':
ival = s390_extract_part (x, HImode, 0);
break;
case 'j':
ival = s390_extract_part (x, HImode, -1);
break;
case 'k':
ival = s390_extract_part (x, SImode, 0);
break;
case 'm':
ival = s390_extract_part (x, SImode, -1);
break;
case 'o':
ival &= 0xffffffff;
break;
case 'e': case 'f':
case 's': case 't':
{
int start, end;
int len;
bool ok;
len = (code == 's' || code == 'e' ? 64 : 32);
ok = s390_contiguous_bitmask_p (ival, true, len, &start, &end);
gcc_assert (ok);
if (code == 's' || code == 't')
ival = start;
else
ival = end;
}
break;
default:
output_operand_lossage ("invalid constant for output modifier '%c'", code);
}
fprintf (file, HOST_WIDE_INT_PRINT_DEC, ival);
break;
case CONST_WIDE_INT:
if (code == 'b')
fprintf (file, HOST_WIDE_INT_PRINT_DEC,
CONST_WIDE_INT_ELT (x, 0) & 0xff);
else if (code == 'x')
fprintf (file, HOST_WIDE_INT_PRINT_DEC,
CONST_WIDE_INT_ELT (x, 0) & 0xffff);
else if (code == 'h')
fprintf (file, HOST_WIDE_INT_PRINT_DEC,
((CONST_WIDE_INT_ELT (x, 0) & 0xffff) ^ 0x8000) - 0x8000);
else
{
if (code == 0)
output_operand_lossage ("invalid constant - try using "
"an output modifier");
else
output_operand_lossage ("invalid constant for output modifier '%c'",
code);
}
break;
case CONST_VECTOR:
switch (code)
{
case 'h':
gcc_assert (const_vec_duplicate_p (x));
fprintf (file, HOST_WIDE_INT_PRINT_DEC,
((INTVAL (XVECEXP (x, 0, 0)) & 0xffff) ^ 0x8000) - 0x8000);
break;
case 'e':
case 's':
{
int start, end;
bool ok;
ok = s390_contiguous_bitmask_vector_p (x, &start, &end);
gcc_assert (ok);
ival = (code == 's') ? start : end;
fprintf (file, HOST_WIDE_INT_PRINT_DEC, ival);
}
break;
case 't':
{
unsigned mask;
bool ok = s390_bytemask_vector_p (x, &mask);
gcc_assert (ok);
fprintf (file, "%u", mask);
}
break;
default:
output_operand_lossage ("invalid constant vector for output "
"modifier '%c'", code);
}
break;
default:
if (code == 0)
output_operand_lossage ("invalid expression - try using "
"an output modifier");
else
output_operand_lossage ("invalid expression for output "
"modifier '%c'", code);
break;
}
}
/* Target hook for assembling integer objects. We need to define it
here to work a round a bug in some versions of GAS, which couldn't
handle values smaller than INT_MIN when printed in decimal. */
static bool
s390_assemble_integer (rtx x, unsigned int size, int aligned_p)
{
if (size == 8 && aligned_p
&& GET_CODE (x) == CONST_INT && INTVAL (x) < INT_MIN)
{
fprintf (asm_out_file, "\t.quad\t" HOST_WIDE_INT_PRINT_HEX "\n",
INTVAL (x));
return true;
}
return default_assemble_integer (x, size, aligned_p);
}
/* Returns true if register REGNO is used for forming
a memory address in expression X. */
static bool
reg_used_in_mem_p (int regno, rtx x)
{
enum rtx_code code = GET_CODE (x);
int i, j;
const char *fmt;
if (code == MEM)
{
if (refers_to_regno_p (regno, XEXP (x, 0)))
return true;
}
else if (code == SET
&& GET_CODE (SET_DEST (x)) == PC)
{
if (refers_to_regno_p (regno, SET_SRC (x)))
return true;
}
fmt = GET_RTX_FORMAT (code);
for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--)
{
if (fmt[i] == 'e'
&& reg_used_in_mem_p (regno, XEXP (x, i)))
return true;
else if (fmt[i] == 'E')
for (j = 0; j < XVECLEN (x, i); j++)
if (reg_used_in_mem_p (regno, XVECEXP (x, i, j)))
return true;
}
return false;
}
/* Returns true if expression DEP_RTX sets an address register
used by instruction INSN to address memory. */
static bool
addr_generation_dependency_p (rtx dep_rtx, rtx_insn *insn)
{
rtx target, pat;
if (NONJUMP_INSN_P (dep_rtx))
dep_rtx = PATTERN (dep_rtx);
if (GET_CODE (dep_rtx) == SET)
{
target = SET_DEST (dep_rtx);
if (GET_CODE (target) == STRICT_LOW_PART)
target = XEXP (target, 0);
while (GET_CODE (target) == SUBREG)
target = SUBREG_REG (target);
if (GET_CODE (target) == REG)
{
int regno = REGNO (target);
if (s390_safe_attr_type (insn) == TYPE_LA)
{
pat = PATTERN (insn);
if (GET_CODE (pat) == PARALLEL)
{
gcc_assert (XVECLEN (pat, 0) == 2);
pat = XVECEXP (pat, 0, 0);
}
gcc_assert (GET_CODE (pat) == SET);
return refers_to_regno_p (regno, SET_SRC (pat));
}
else if (get_attr_atype (insn) == ATYPE_AGEN)
return reg_used_in_mem_p (regno, PATTERN (insn));
}
}
return false;
}
/* Return 1, if dep_insn sets register used in insn in the agen unit. */
int
s390_agen_dep_p (rtx_insn *dep_insn, rtx_insn *insn)
{
rtx dep_rtx = PATTERN (dep_insn);
int i;
if (GET_CODE (dep_rtx) == SET
&& addr_generation_dependency_p (dep_rtx, insn))
return 1;
else if (GET_CODE (dep_rtx) == PARALLEL)
{
for (i = 0; i < XVECLEN (dep_rtx, 0); i++)
{
if (addr_generation_dependency_p (XVECEXP (dep_rtx, 0, i), insn))
return 1;
}
}
return 0;
}
/* A C statement (sans semicolon) to update the integer scheduling priority
INSN_PRIORITY (INSN). Increase the priority to execute the INSN earlier,
reduce the priority to execute INSN later. Do not define this macro if
you do not need to adjust the scheduling priorities of insns.
A STD instruction should be scheduled earlier,
in order to use the bypass. */
static int
s390_adjust_priority (rtx_insn *insn, int priority)
{
if (! INSN_P (insn))
return priority;
if (s390_tune <= PROCESSOR_2064_Z900)
return priority;
switch (s390_safe_attr_type (insn))
{
case TYPE_FSTOREDF:
case TYPE_FSTORESF:
priority = priority << 3;
break;
case TYPE_STORE:
case TYPE_STM:
priority = priority << 1;
break;
default:
break;
}
return priority;
}
/* The number of instructions that can be issued per cycle. */
static int
s390_issue_rate (void)
{
switch (s390_tune)
{
case PROCESSOR_2084_Z990:
case PROCESSOR_2094_Z9_109:
case PROCESSOR_2094_Z9_EC:
case PROCESSOR_2817_Z196:
return 3;
case PROCESSOR_2097_Z10:
return 2;
case PROCESSOR_2064_Z900:
/* Starting with EC12 we use the sched_reorder hook to take care
of instruction dispatch constraints. The algorithm only
picks the best instruction and assumes only a single
instruction gets issued per cycle. */
case PROCESSOR_2827_ZEC12:
case PROCESSOR_2964_Z13:
case PROCESSOR_3906_Z14:
case PROCESSOR_ARCH14:
default:
return 1;
}
}
static int
s390_first_cycle_multipass_dfa_lookahead (void)
{
return 4;
}
static void
annotate_constant_pool_refs_1 (rtx *x)
{
int i, j;
const char *fmt;
gcc_assert (GET_CODE (*x) != SYMBOL_REF
|| !CONSTANT_POOL_ADDRESS_P (*x));
/* Literal pool references can only occur inside a MEM ... */
if (GET_CODE (*x) == MEM)
{
rtx memref = XEXP (*x, 0);
if (GET_CODE (memref) == SYMBOL_REF
&& CONSTANT_POOL_ADDRESS_P (memref))
{
rtx base = cfun->machine->base_reg;
rtx addr = gen_rtx_UNSPEC (Pmode, gen_rtvec (2, memref, base),
UNSPEC_LTREF);
*x = replace_equiv_address (*x, addr);
return;
}
if (GET_CODE (memref) == CONST
&& GET_CODE (XEXP (memref, 0)) == PLUS
&& GET_CODE (XEXP (XEXP (memref, 0), 1)) == CONST_INT
&& GET_CODE (XEXP (XEXP (memref, 0), 0)) == SYMBOL_REF
&& CONSTANT_POOL_ADDRESS_P (XEXP (XEXP (memref, 0), 0)))
{
HOST_WIDE_INT off = INTVAL (XEXP (XEXP (memref, 0), 1));
rtx sym = XEXP (XEXP (memref, 0), 0);
rtx base = cfun->machine->base_reg;
rtx addr = gen_rtx_UNSPEC (Pmode, gen_rtvec (2, sym, base),
UNSPEC_LTREF);
*x = replace_equiv_address (*x, plus_constant (Pmode, addr, off));
return;
}
}
/* ... or a load-address type pattern. */
if (GET_CODE (*x) == SET)
{
rtx addrref = SET_SRC (*x);
if (GET_CODE (addrref) == SYMBOL_REF
&& CONSTANT_POOL_ADDRESS_P (addrref))
{
rtx base = cfun->machine->base_reg;
rtx addr = gen_rtx_UNSPEC (Pmode, gen_rtvec (2, addrref, base),
UNSPEC_LTREF);
SET_SRC (*x) = addr;
return;
}
if (GET_CODE (addrref) == CONST
&& GET_CODE (XEXP (addrref, 0)) == PLUS
&& GET_CODE (XEXP (XEXP (addrref, 0), 1)) == CONST_INT
&& GET_CODE (XEXP (XEXP (addrref, 0), 0)) == SYMBOL_REF
&& CONSTANT_POOL_ADDRESS_P (XEXP (XEXP (addrref, 0), 0)))
{
HOST_WIDE_INT off = INTVAL (XEXP (XEXP (addrref, 0), 1));
rtx sym = XEXP (XEXP (addrref, 0), 0);
rtx base = cfun->machine->base_reg;
rtx addr = gen_rtx_UNSPEC (Pmode, gen_rtvec (2, sym, base),
UNSPEC_LTREF);
SET_SRC (*x) = plus_constant (Pmode, addr, off);
return;
}
}
fmt = GET_RTX_FORMAT (GET_CODE (*x));
for (i = GET_RTX_LENGTH (GET_CODE (*x)) - 1; i >= 0; i--)
{
if (fmt[i] == 'e')
{
annotate_constant_pool_refs_1 (&XEXP (*x, i));
}
else if (fmt[i] == 'E')
{
for (j = 0; j < XVECLEN (*x, i); j++)
annotate_constant_pool_refs_1 (&XVECEXP (*x, i, j));
}
}
}
/* Annotate every literal pool reference in INSN by an UNSPEC_LTREF expression.
Fix up MEMs as required.
Skip insns which support relative addressing, because they do not use a base
register. */
static void
annotate_constant_pool_refs (rtx_insn *insn)
{
if (s390_safe_relative_long_p (insn))
return;
annotate_constant_pool_refs_1 (&PATTERN (insn));
}
static void
find_constant_pool_ref_1 (rtx x, rtx *ref)
{
int i, j;
const char *fmt;
/* Likewise POOL_ENTRY insns. */
if (GET_CODE (x) == UNSPEC_VOLATILE
&& XINT (x, 1) == UNSPECV_POOL_ENTRY)
return;
gcc_assert (GET_CODE (x) != SYMBOL_REF
|| !CONSTANT_POOL_ADDRESS_P (x));
if (GET_CODE (x) == UNSPEC && XINT (x, 1) == UNSPEC_LTREF)
{
rtx sym = XVECEXP (x, 0, 0);
gcc_assert (GET_CODE (sym) == SYMBOL_REF
&& CONSTANT_POOL_ADDRESS_P (sym));
if (*ref == NULL_RTX)
*ref = sym;
else
gcc_assert (*ref == sym);
return;
}
fmt = GET_RTX_FORMAT (GET_CODE (x));
for (i = GET_RTX_LENGTH (GET_CODE (x)) - 1; i >= 0; i--)
{
if (fmt[i] == 'e')
{
find_constant_pool_ref_1 (XEXP (x, i), ref);
}
else if (fmt[i] == 'E')
{
for (j = 0; j < XVECLEN (x, i); j++)
find_constant_pool_ref_1 (XVECEXP (x, i, j), ref);
}
}
}
/* Find an annotated literal pool symbol referenced in INSN,
and store it at REF. Will abort if INSN contains references to
more than one such pool symbol; multiple references to the same
symbol are allowed, however.
The rtx pointed to by REF must be initialized to NULL_RTX
by the caller before calling this routine.
Skip insns which support relative addressing, because they do not use a base
register. */
static void
find_constant_pool_ref (rtx_insn *insn, rtx *ref)
{
if (s390_safe_relative_long_p (insn))
return;
find_constant_pool_ref_1 (PATTERN (insn), ref);
}
static void
replace_constant_pool_ref_1 (rtx *x, rtx ref, rtx offset)
{
int i, j;
const char *fmt;
gcc_assert (*x != ref);
if (GET_CODE (*x) == UNSPEC
&& XINT (*x, 1) == UNSPEC_LTREF
&& XVECEXP (*x, 0, 0) == ref)
{
*x = gen_rtx_PLUS (Pmode, XVECEXP (*x, 0, 1), offset);
return;
}
if (GET_CODE (*x) == PLUS
&& GET_CODE (XEXP (*x, 1)) == CONST_INT
&& GET_CODE (XEXP (*x, 0)) == UNSPEC
&& XINT (XEXP (*x, 0), 1) == UNSPEC_LTREF
&& XVECEXP (XEXP (*x, 0), 0, 0) == ref)
{
rtx addr = gen_rtx_PLUS (Pmode, XVECEXP (XEXP (*x, 0), 0, 1), offset);
*x = plus_constant (Pmode, addr, INTVAL (XEXP (*x, 1)));
return;
}
fmt = GET_RTX_FORMAT (GET_CODE (*x));
for (i = GET_RTX_LENGTH (GET_CODE (*x)) - 1; i >= 0; i--)
{
if (fmt[i] == 'e')
{
replace_constant_pool_ref_1 (&XEXP (*x, i), ref, offset);
}
else if (fmt[i] == 'E')
{
for (j = 0; j < XVECLEN (*x, i); j++)
replace_constant_pool_ref_1 (&XVECEXP (*x, i, j), ref, offset);
}
}
}
/* Replace every reference to the annotated literal pool
symbol REF in INSN by its base plus OFFSET.
Skip insns which support relative addressing, because they do not use a base
register. */
static void
replace_constant_pool_ref (rtx_insn *insn, rtx ref, rtx offset)
{
if (s390_safe_relative_long_p (insn))
return;
replace_constant_pool_ref_1 (&PATTERN (insn), ref, offset);
}
/* We keep a list of constants which we have to add to internal
constant tables in the middle of large functions. */
static machine_mode constant_modes[] =
{
TFmode, FPRX2mode, TImode, TDmode,
V16QImode, V8HImode, V4SImode, V2DImode, V1TImode,
V4SFmode, V2DFmode, V1TFmode,
DFmode, DImode, DDmode,
V8QImode, V4HImode, V2SImode, V1DImode, V2SFmode, V1DFmode,
SFmode, SImode, SDmode,
V4QImode, V2HImode, V1SImode, V1SFmode,
HImode,
V2QImode, V1HImode,
QImode,
V1QImode
};
#define NR_C_MODES (sizeof (constant_modes) / sizeof (constant_modes[0]))
struct constant
{
struct constant *next;
rtx value;
rtx_code_label *label;
};
struct constant_pool
{
struct constant_pool *next;
rtx_insn *first_insn;
rtx_insn *pool_insn;
bitmap insns;
rtx_insn *emit_pool_after;
struct constant *constants[NR_C_MODES];
struct constant *execute;
rtx_code_label *label;
int size;
};
/* Allocate new constant_pool structure. */
static struct constant_pool *
s390_alloc_pool (void)
{
struct constant_pool *pool;
size_t i;
pool = (struct constant_pool *) xmalloc (sizeof *pool);
pool->next = NULL;
for (i = 0; i < NR_C_MODES; i++)
pool->constants[i] = NULL;
pool->execute = NULL;
pool->label = gen_label_rtx ();
pool->first_insn = NULL;
pool->pool_insn = NULL;
pool->insns = BITMAP_ALLOC (NULL);
pool->size = 0;
pool->emit_pool_after = NULL;
return pool;
}
/* Create new constant pool covering instructions starting at INSN
and chain it to the end of POOL_LIST. */
static struct constant_pool *
s390_start_pool (struct constant_pool **pool_list, rtx_insn *insn)
{
struct constant_pool *pool, **prev;
pool = s390_alloc_pool ();
pool->first_insn = insn;
for (prev = pool_list; *prev; prev = &(*prev)->next)
;
*prev = pool;
return pool;
}
/* End range of instructions covered by POOL at INSN and emit
placeholder insn representing the pool. */
static void
s390_end_pool (struct constant_pool *pool, rtx_insn *insn)
{
rtx pool_size = GEN_INT (pool->size + 8 /* alignment slop */);
if (!insn)
insn = get_last_insn ();
pool->pool_insn = emit_insn_after (gen_pool (pool_size), insn);
INSN_ADDRESSES_NEW (pool->pool_insn, -1);
}
/* Add INSN to the list of insns covered by POOL. */
static void
s390_add_pool_insn (struct constant_pool *pool, rtx insn)
{
bitmap_set_bit (pool->insns, INSN_UID (insn));
}
/* Return pool out of POOL_LIST that covers INSN. */
static struct constant_pool *
s390_find_pool (struct constant_pool *pool_list, rtx insn)
{
struct constant_pool *pool;
for (pool = pool_list; pool; pool = pool->next)
if (bitmap_bit_p (pool->insns, INSN_UID (insn)))
break;
return pool;
}
/* Add constant VAL of mode MODE to the constant pool POOL. */
static void
s390_add_constant (struct constant_pool *pool, rtx val, machine_mode mode)
{
struct constant *c;
size_t i;
for (i = 0; i < NR_C_MODES; i++)
if (constant_modes[i] == mode)
break;
gcc_assert (i != NR_C_MODES);
for (c = pool->constants[i]; c != NULL; c = c->next)
if (rtx_equal_p (val, c->value))
break;
if (c == NULL)
{
c = (struct constant *) xmalloc (sizeof *c);
c->value = val;
c->label = gen_label_rtx ();
c->next = pool->constants[i];
pool->constants[i] = c;
pool->size += GET_MODE_SIZE (mode);
}
}
/* Return an rtx that represents the offset of X from the start of
pool POOL. */
static rtx
s390_pool_offset (struct constant_pool *pool, rtx x)
{
rtx label;
label = gen_rtx_LABEL_REF (GET_MODE (x), pool->label);
x = gen_rtx_UNSPEC (GET_MODE (x), gen_rtvec (2, x, label),
UNSPEC_POOL_OFFSET);
return gen_rtx_CONST (GET_MODE (x), x);
}
/* Find constant VAL of mode MODE in the constant pool POOL.
Return an RTX describing the distance from the start of
the pool to the location of the new constant. */
static rtx
s390_find_constant (struct constant_pool *pool, rtx val,
machine_mode mode)
{
struct constant *c;
size_t i;
for (i = 0; i < NR_C_MODES; i++)
if (constant_modes[i] == mode)
break;
gcc_assert (i != NR_C_MODES);
for (c = pool->constants[i]; c != NULL; c = c->next)
if (rtx_equal_p (val, c->value))
break;
gcc_assert (c);
return s390_pool_offset (pool, gen_rtx_LABEL_REF (Pmode, c->label));
}
/* Check whether INSN is an execute. Return the label_ref to its
execute target template if so, NULL_RTX otherwise. */
static rtx
s390_execute_label (rtx 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
|| 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;
}
/* Find execute target for INSN in the constant pool POOL.
Return an RTX describing the distance from the start of
the pool to the location of the execute target. */
static rtx
s390_find_execute (struct constant_pool *pool, rtx insn)
{
struct constant *c;
for (c = pool->execute; c != NULL; c = c->next)
if (INSN_UID (insn) == INSN_UID (c->value))
break;
gcc_assert (c);
return s390_pool_offset (pool, gen_rtx_LABEL_REF (Pmode, c->label));
}
/* For an execute INSN, extract the execute target template. */
static rtx
s390_execute_target (rtx insn)
{
rtx pattern = PATTERN (insn);
gcc_assert (s390_execute_label (insn));
if (XVECLEN (pattern, 0) == 2)
{
pattern = copy_rtx (XVECEXP (pattern, 0, 1));
}
else
{
rtvec vec = rtvec_alloc (XVECLEN (pattern, 0) - 1);
int i;
for (i = 0; i < XVECLEN (pattern, 0) - 1; i++)
RTVEC_ELT (vec, i) = copy_rtx (XVECEXP (pattern, 0, i + 1));
pattern = gen_rtx_PARALLEL (VOIDmode, vec);
}
return pattern;
}
/* Indicate that INSN cannot be duplicated. This is the case for
execute insns that carry a unique label. */
static bool
s390_cannot_copy_insn_p (rtx_insn *insn)
{
rtx label = s390_execute_label (insn);
return label && label != const0_rtx;
}
/* Dump out the constants in POOL. If REMOTE_LABEL is true,
do not emit the pool base label. */
static void
s390_dump_pool (struct constant_pool *pool, bool remote_label)
{
struct constant *c;
rtx_insn *insn = pool->pool_insn;
size_t i;
/* Switch to rodata section. */
insn = emit_insn_after (gen_pool_section_start (), insn);
INSN_ADDRESSES_NEW (insn, -1);
/* Ensure minimum pool alignment. */
insn = emit_insn_after (gen_pool_align (GEN_INT (8)), insn);
INSN_ADDRESSES_NEW (insn, -1);
/* Emit pool base label. */
if (!remote_label)
{
insn = emit_label_after (pool->label, insn);
INSN_ADDRESSES_NEW (insn, -1);
}
/* Dump constants in descending alignment requirement order,
ensuring proper alignment for every constant. */
for (i = 0; i < NR_C_MODES; i++)
for (c = pool->constants[i]; c; c = c->next)
{
/* Convert UNSPEC_LTREL_OFFSET unspecs to pool-relative references. */
rtx value = copy_rtx (c->value);
if (GET_CODE (value) == CONST
&& GET_CODE (XEXP (value, 0)) == UNSPEC
&& XINT (XEXP (value, 0), 1) == UNSPEC_LTREL_OFFSET
&& XVECLEN (XEXP (value, 0), 0) == 1)
value = s390_pool_offset (pool, XVECEXP (XEXP (value, 0), 0, 0));
insn = emit_label_after (c->label, insn);
INSN_ADDRESSES_NEW (insn, -1);
value = gen_rtx_UNSPEC_VOLATILE (constant_modes[i],
gen_rtvec (1, value),
UNSPECV_POOL_ENTRY);
insn = emit_insn_after (value, insn);
INSN_ADDRESSES_NEW (insn, -1);
}
/* Ensure minimum alignment for instructions. */
insn = emit_insn_after (gen_pool_align (GEN_INT (2)), insn);
INSN_ADDRESSES_NEW (insn, -1);
/* Output in-pool execute template insns. */
for (c = pool->execute; c; c = c->next)
{
insn = emit_label_after (c->label, insn);
INSN_ADDRESSES_NEW (insn, -1);
insn = emit_insn_after (s390_execute_target (c->value), insn);
INSN_ADDRESSES_NEW (insn, -1);
}
/* Switch back to previous section. */
insn = emit_insn_after (gen_pool_section_end (), insn);
INSN_ADDRESSES_NEW (insn, -1);
insn = emit_barrier_after (insn);
INSN_ADDRESSES_NEW (insn, -1);
/* Remove placeholder insn. */
remove_insn (pool->pool_insn);
}
/* Free all memory used by POOL. */
static void
s390_free_pool (struct constant_pool *pool)
{
struct constant *c, *next;
size_t i;
for (i = 0; i < NR_C_MODES; i++)
for (c = pool->constants[i]; c; c = next)
{
next = c->next;
free (c);
}
for (c = pool->execute; c; c = next)
{
next = c->next;
free (c);
}
BITMAP_FREE (pool->insns);
free (pool);
}
/* Collect main literal pool. Return NULL on overflow. */
static struct constant_pool *
s390_mainpool_start (void)
{
struct constant_pool *pool;
rtx_insn *insn;
pool = s390_alloc_pool ();
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
{
if (NONJUMP_INSN_P (insn)
&& GET_CODE (PATTERN (insn)) == SET
&& GET_CODE (SET_SRC (PATTERN (insn))) == UNSPEC_VOLATILE
&& XINT (SET_SRC (PATTERN (insn)), 1) == UNSPECV_MAIN_POOL)
{
/* There might be two main_pool instructions if base_reg
is call-clobbered; one for shrink-wrapped code and one
for the rest. We want to keep the first. */
if (pool->pool_insn)
{
insn = PREV_INSN (insn);
delete_insn (NEXT_INSN (insn));
continue;
}
pool->pool_insn = insn;
}
if (NONJUMP_INSN_P (insn) || CALL_P (insn))
{
rtx pool_ref = NULL_RTX;
find_constant_pool_ref (insn, &pool_ref);
if (pool_ref)
{
rtx constant = get_pool_constant (pool_ref);
machine_mode mode = get_pool_mode (pool_ref);
s390_add_constant (pool, constant, mode);
}
}
/* If hot/cold partitioning is enabled we have to make sure that
the literal pool is emitted in the same section where the
initialization of the literal pool base pointer takes place.
emit_pool_after is only used in the non-overflow case on non
Z cpus where we can emit the literal pool at the end of the
function body within the text section. */
if (NOTE_P (insn)
&& NOTE_KIND (insn) == NOTE_INSN_SWITCH_TEXT_SECTIONS
&& !pool->emit_pool_after)
pool->emit_pool_after = PREV_INSN (insn);
}
gcc_assert (pool->pool_insn || pool->size == 0);
if (pool->size >= 4096)
{
/* We're going to chunkify the pool, so remove the main
pool placeholder insn. */
remove_insn (pool->pool_insn);
s390_free_pool (pool);
pool = NULL;
}
/* If the functions ends with the section where the literal pool
should be emitted set the marker to its end. */
if (pool && !pool->emit_pool_after)
pool->emit_pool_after = get_last_insn ();
return pool;
}
/* POOL holds the main literal pool as collected by s390_mainpool_start.
Modify the current function to output the pool constants as well as
the pool register setup instruction. */
static void
s390_mainpool_finish (struct constant_pool *pool)
{
rtx base_reg = cfun->machine->base_reg;
rtx set;
rtx_insn *insn;
/* If the pool is empty, we're done. */
if (pool->size == 0)
{
/* We don't actually need a base register after all. */
cfun->machine->base_reg = NULL_RTX;
if (pool->pool_insn)
remove_insn (pool->pool_insn);
s390_free_pool (pool);
return;
}
/* We need correct insn addresses. */
shorten_branches (get_insns ());
/* Use a LARL to load the pool register. The pool is
located in the .rodata section, so we emit it after the function. */
set = gen_main_base_64 (base_reg, pool->label);
insn = emit_insn_after (set, pool->pool_insn);
INSN_ADDRESSES_NEW (insn, -1);
remove_insn (pool->pool_insn);
insn = get_last_insn ();
pool->pool_insn = emit_insn_after (gen_pool (const0_rtx), insn);
INSN_ADDRESSES_NEW (pool->pool_insn, -1);
s390_dump_pool (pool, 0);
/* Replace all literal pool references. */
for (rtx_insn *insn = get_insns (); insn; insn = NEXT_INSN (insn))
{
if (NONJUMP_INSN_P (insn) || CALL_P (insn))
{
rtx addr, pool_ref = NULL_RTX;
find_constant_pool_ref (insn, &pool_ref);
if (pool_ref)
{
if (s390_execute_label (insn))
addr = s390_find_execute (pool, insn);
else
addr = s390_find_constant (pool, get_pool_constant (pool_ref),
get_pool_mode (pool_ref));
replace_constant_pool_ref (insn, pool_ref, addr);
INSN_CODE (insn) = -1;
}
}
}
/* Free the pool. */
s390_free_pool (pool);
}
/* Chunkify the literal pool. */
#define S390_POOL_CHUNK_MIN 0xc00
#define S390_POOL_CHUNK_MAX 0xe00
static struct constant_pool *
s390_chunkify_start (void)
{
struct constant_pool *curr_pool = NULL, *pool_list = NULL;
bitmap far_labels;
rtx_insn *insn;
/* We need correct insn addresses. */
shorten_branches (get_insns ());
/* Scan all insns and move literals to pool chunks. */
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
{
if (NONJUMP_INSN_P (insn) || CALL_P (insn))
{
rtx pool_ref = NULL_RTX;
find_constant_pool_ref (insn, &pool_ref);
if (pool_ref)
{
rtx constant = get_pool_constant (pool_ref);
machine_mode mode = get_pool_mode (pool_ref);
if (!curr_pool)
curr_pool = s390_start_pool (&pool_list, insn);
s390_add_constant (curr_pool, constant, mode);
s390_add_pool_insn (curr_pool, insn);
}
}
if (JUMP_P (insn) || JUMP_TABLE_DATA_P (insn) || LABEL_P (insn))
{
if (curr_pool)
s390_add_pool_insn (curr_pool, insn);
}
if (NOTE_P (insn) && NOTE_KIND (insn) == NOTE_INSN_VAR_LOCATION)
continue;
if (!curr_pool
|| INSN_ADDRESSES_SIZE () <= (size_t) INSN_UID (insn)
|| INSN_ADDRESSES (INSN_UID (insn)) == -1)
continue;
if (curr_pool->size < S390_POOL_CHUNK_MAX)
continue;
s390_end_pool (curr_pool, NULL);
curr_pool = NULL;
}
if (curr_pool)
s390_end_pool (curr_pool, NULL);
/* Find all labels that are branched into
from an insn belonging to a different chunk. */
far_labels = BITMAP_ALLOC (NULL);
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
{
rtx_jump_table_data *table;
/* Labels marked with LABEL_PRESERVE_P can be target
of non-local jumps, so we have to mark them.
The same holds for named labels.
Don't do that, however, if it is the label before
a jump table. */
if (LABEL_P (insn)
&& (LABEL_PRESERVE_P (insn) || LABEL_NAME (insn)))
{
rtx_insn *vec_insn = NEXT_INSN (insn);
if (! vec_insn || ! JUMP_TABLE_DATA_P (vec_insn))
bitmap_set_bit (far_labels, CODE_LABEL_NUMBER (insn));
}
/* Check potential targets in a table jump (casesi_jump). */
else if (tablejump_p (insn, NULL, &table))
{
rtx vec_pat = PATTERN (table);
int i, diff_p = GET_CODE (vec_pat) == ADDR_DIFF_VEC;
for (i = 0; i < XVECLEN (vec_pat, diff_p); i++)
{
rtx label = XEXP (XVECEXP (vec_pat, diff_p, i), 0);
if (s390_find_pool (pool_list, label)
!= s390_find_pool (pool_list, insn))
bitmap_set_bit (far_labels, CODE_LABEL_NUMBER (label));
}
}
/* If we have a direct jump (conditional or unconditional),
check all potential targets. */
else if (JUMP_P (insn))
{
rtx pat = PATTERN (insn);
if (GET_CODE (pat) == PARALLEL)
pat = XVECEXP (pat, 0, 0);
if (GET_CODE (pat) == SET)
{
rtx label = JUMP_LABEL (insn);
if (label && !ANY_RETURN_P (label))
{
if (s390_find_pool (pool_list, label)
!= s390_find_pool (pool_list, insn))
bitmap_set_bit (far_labels, CODE_LABEL_NUMBER (label));
}
}
}
}
/* Insert base register reload insns before every pool. */
for (curr_pool = pool_list; curr_pool; curr_pool = curr_pool->next)
{
rtx new_insn = gen_reload_base_64 (cfun->machine->base_reg,
curr_pool->label);
rtx_insn *insn = curr_pool->first_insn;
INSN_ADDRESSES_NEW (emit_insn_before (new_insn, insn), -1);
}
/* Insert base register reload insns at every far label. */
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
if (LABEL_P (insn)
&& bitmap_bit_p (far_labels, CODE_LABEL_NUMBER (insn)))
{
struct constant_pool *pool = s390_find_pool (pool_list, insn);
if (pool)
{
rtx new_insn = gen_reload_base_64 (cfun->machine->base_reg,
pool->label);
INSN_ADDRESSES_NEW (emit_insn_after (new_insn, insn), -1);
}
}
BITMAP_FREE (far_labels);
/* Recompute insn addresses. */
init_insn_lengths ();
shorten_branches (get_insns ());
return pool_list;
}
/* POOL_LIST is a chunk list as prepared by s390_chunkify_start.
After we have decided to use this list, finish implementing
all changes to the current function as required. */
static void
s390_chunkify_finish (struct constant_pool *pool_list)
{
struct constant_pool *curr_pool = NULL;
rtx_insn *insn;
/* Replace all literal pool references. */
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
{
curr_pool = s390_find_pool (pool_list, insn);
if (!curr_pool)
continue;
if (NONJUMP_INSN_P (insn) || CALL_P (insn))
{
rtx addr, pool_ref = NULL_RTX;
find_constant_pool_ref (insn, &pool_ref);
if (pool_ref)
{
if (s390_execute_label (insn))
addr = s390_find_execute (curr_pool, insn);
else
addr = s390_find_constant (curr_pool,
get_pool_constant (pool_ref),
get_pool_mode (pool_ref));
replace_constant_pool_ref (insn, pool_ref, addr);
INSN_CODE (insn) = -1;
}
}
}
/* Dump out all literal pools. */
for (curr_pool = pool_list; curr_pool; curr_pool = curr_pool->next)
s390_dump_pool (curr_pool, 0);
/* Free pool list. */
while (pool_list)
{
struct constant_pool *next = pool_list->next;
s390_free_pool (pool_list);
pool_list = next;
}
}
/* Output the constant pool entry EXP in mode MODE with alignment ALIGN. */
void
s390_output_pool_entry (rtx exp, machine_mode mode, unsigned int align)
{
switch (GET_MODE_CLASS (mode))
{
case MODE_FLOAT:
case MODE_DECIMAL_FLOAT:
gcc_assert (GET_CODE (exp) == CONST_DOUBLE);
assemble_real (*CONST_DOUBLE_REAL_VALUE (exp),
as_a <scalar_float_mode> (mode), align);
break;
case MODE_INT:
assemble_integer (exp, GET_MODE_SIZE (mode), align, 1);
mark_symbol_refs_as_used (exp);
break;
case MODE_VECTOR_INT:
case MODE_VECTOR_FLOAT:
{
int i;
machine_mode inner_mode;
gcc_assert (GET_CODE (exp) == CONST_VECTOR);
inner_mode = GET_MODE_INNER (GET_MODE (exp));
for (i = 0; i < XVECLEN (exp, 0); i++)
s390_output_pool_entry (XVECEXP (exp, 0, i),
inner_mode,
i == 0
? align
: GET_MODE_BITSIZE (inner_mode));
}
break;
default:
gcc_unreachable ();
}
}
/* Return true if MEM refers to an integer constant in the literal pool. If
VAL is not nullptr, then also fill it with the constant's value. */
bool
s390_const_int_pool_entry_p (rtx mem, HOST_WIDE_INT *val)
{
/* Try to match the following:
- (mem (unspec [(symbol_ref) (reg)] UNSPEC_LTREF)).
- (mem (symbol_ref)). */
if (!MEM_P (mem))
return false;
rtx addr = XEXP (mem, 0);
rtx sym;
if (GET_CODE (addr) == UNSPEC && XINT (addr, 1) == UNSPEC_LTREF)
sym = XVECEXP (addr, 0, 0);
else
sym = addr;
if (!SYMBOL_REF_P (sym) || !CONSTANT_POOL_ADDRESS_P (sym))
return false;
rtx val_rtx = get_pool_constant (sym);
if (!CONST_INT_P (val_rtx))
return false;
if (val != nullptr)
*val = INTVAL (val_rtx);
return true;
}
/* Return an RTL expression representing the value of the return address
for the frame COUNT steps up from the current frame. FRAME is the
frame pointer of that frame. */
rtx
s390_return_addr_rtx (int count, rtx frame ATTRIBUTE_UNUSED)
{
int offset;
rtx addr;
/* Without backchain, we fail for all but the current frame. */
if (!TARGET_BACKCHAIN && count > 0)
return NULL_RTX;
/* For the current frame, we need to make sure the initial
value of RETURN_REGNUM is actually saved. */
if (count == 0)
return get_hard_reg_initial_val (Pmode, RETURN_REGNUM);
if (TARGET_PACKED_STACK)
offset = -2 * UNITS_PER_LONG;
else
offset = RETURN_REGNUM * UNITS_PER_LONG;
addr = plus_constant (Pmode, frame, offset);
addr = memory_address (Pmode, addr);
return gen_rtx_MEM (Pmode, addr);
}
/* Return an RTL expression representing the back chain stored in
the current stack frame. */
rtx
s390_back_chain_rtx (void)
{
rtx chain;
gcc_assert (TARGET_BACKCHAIN);
if (TARGET_PACKED_STACK)
chain = plus_constant (Pmode, stack_pointer_rtx,
STACK_POINTER_OFFSET - UNITS_PER_LONG);
else
chain = stack_pointer_rtx;
chain = gen_rtx_MEM (Pmode, chain);
return chain;
}
/* Find first call clobbered register unused in a function.
This could be used as base register in a leaf function
or for holding the return address before epilogue. */
static int
find_unused_clobbered_reg (void)
{
int i;
for (i = 0; i < 6; i++)
if (!df_regs_ever_live_p (i))
return i;
return 0;
}
/* Helper function for s390_regs_ever_clobbered. Sets the fields in DATA for all
clobbered hard regs in SETREG. */
static void
s390_reg_clobbered_rtx (rtx setreg, const_rtx set_insn ATTRIBUTE_UNUSED, void *data)
{
char *regs_ever_clobbered = (char *)data;
unsigned int i, regno;
machine_mode mode = GET_MODE (setreg);
if (GET_CODE (setreg) == SUBREG)
{
rtx inner = SUBREG_REG (setreg);
if (!GENERAL_REG_P (inner) && !FP_REG_P (inner))
return;
regno = subreg_regno (setreg);
}
else if (GENERAL_REG_P (setreg) || FP_REG_P (setreg))
regno = REGNO (setreg);
else
return;
for (i = regno;
i < end_hard_regno (mode, regno);
i++)
regs_ever_clobbered[i] = 1;
}
/* Walks through all basic blocks of the current function looking
for clobbered hard regs using s390_reg_clobbered_rtx. The fields
of the passed integer array REGS_EVER_CLOBBERED are set to one for
each of those regs. */
static void
s390_regs_ever_clobbered (char regs_ever_clobbered[])
{
basic_block cur_bb;
rtx_insn *cur_insn;
unsigned int i;
memset (regs_ever_clobbered, 0, 32);
/* For non-leaf functions we have to consider all call clobbered regs to be
clobbered. */
if (!crtl->is_leaf)
{
for (i = 0; i < 32; i++)
regs_ever_clobbered[i] = call_used_regs[i];
}
/* Make the "magic" eh_return registers live if necessary. For regs_ever_live
this work is done by liveness analysis (mark_regs_live_at_end).
Special care is needed for functions containing landing pads. Landing pads
may use the eh registers, but the code which sets these registers is not
contained in that function. Hence s390_regs_ever_clobbered is not able to
deal with this automatically. */
if (crtl->calls_eh_return || cfun->machine->has_landing_pad_p)
for (i = 0; EH_RETURN_DATA_REGNO (i) != INVALID_REGNUM ; i++)
if (crtl->calls_eh_return
|| (cfun->machine->has_landing_pad_p
&& df_regs_ever_live_p (EH_RETURN_DATA_REGNO (i))))
regs_ever_clobbered[EH_RETURN_DATA_REGNO (i)] = 1;
/* For nonlocal gotos all call-saved registers have to be saved.
This flag is also set for the unwinding code in libgcc.
See expand_builtin_unwind_init. For regs_ever_live this is done by
reload. */
if (crtl->saves_all_registers)
for (i = 0; i < 32; i++)
if (!call_used_regs[i])
regs_ever_clobbered[i] = 1;
FOR_EACH_BB_FN (cur_bb, cfun)
{
FOR_BB_INSNS (cur_bb, cur_insn)
{
rtx pat;
if (!INSN_P (cur_insn))
continue;
pat = PATTERN (cur_insn);
/* Ignore GPR restore insns. */
if (epilogue_completed && RTX_FRAME_RELATED_P (cur_insn))
{
if (GET_CODE (pat) == SET
&& GENERAL_REG_P (SET_DEST (pat)))
{
/* lgdr */
if (GET_MODE (SET_SRC (pat)) == DImode
&& FP_REG_P (SET_SRC (pat)))
continue;
/* l / lg */
if (GET_CODE (SET_SRC (pat)) == MEM)
continue;
}
/* lm / lmg */
if (GET_CODE (pat) == PARALLEL
&& load_multiple_operation (pat, VOIDmode))
continue;
}
note_stores (cur_insn,
s390_reg_clobbered_rtx,
regs_ever_clobbered);
}
}
}
/* Determine the frame area which actually has to be accessed
in the function epilogue. The values are stored at the
given pointers AREA_BOTTOM (address of the lowest used stack
address) and AREA_TOP (address of the first item which does
not belong to the stack frame). */
static void
s390_frame_area (int *area_bottom, int *area_top)
{
int b, t;
b = INT_MAX;
t = INT_MIN;
if (cfun_frame_layout.first_restore_gpr != -1)
{
b = (cfun_frame_layout.gprs_offset
+ cfun_frame_layout.first_restore_gpr * UNITS_PER_LONG);
t = b + (cfun_frame_layout.last_restore_gpr
- cfun_frame_layout.first_restore_gpr + 1) * UNITS_PER_LONG;
}
if (TARGET_64BIT && cfun_save_high_fprs_p)
{
b = MIN (b, cfun_frame_layout.f8_offset);
t = MAX (t, (cfun_frame_layout.f8_offset
+ cfun_frame_layout.high_fprs * 8));
}
if (!TARGET_64BIT)
{
if (cfun_fpr_save_p (FPR4_REGNUM))
{
b = MIN (b, cfun_frame_layout.f4_offset);
t = MAX (t, cfun_frame_layout.f4_offset + 8);
}
if (cfun_fpr_save_p (FPR6_REGNUM))
{
b = MIN (b, cfun_frame_layout.f4_offset + 8);
t = MAX (t, cfun_frame_layout.f4_offset + 16);
}
}
*area_bottom = b;
*area_top = t;
}
/* Update gpr_save_slots in the frame layout trying to make use of
FPRs as GPR save slots.
This is a helper routine of s390_register_info. */
static void
s390_register_info_gprtofpr ()
{
int save_reg_slot = FPR0_REGNUM;
int i, j;
if (TARGET_TPF || !TARGET_Z10 || !TARGET_HARD_FLOAT || !crtl->is_leaf)
return;
/* builtin_eh_return needs to be able to modify the return address
on the stack. It could also adjust the FPR save slot instead but
is it worth the trouble?! */
if (crtl->calls_eh_return)
return;
for (i = 15; i >= 6; i--)
{
if (cfun_gpr_save_slot (i) == SAVE_SLOT_NONE)
continue;
/* Advance to the next FP register which can be used as a
GPR save slot. */
while ((!call_used_regs[save_reg_slot]
|| df_regs_ever_live_p (save_reg_slot)
|| cfun_fpr_save_p (save_reg_slot))
&& FP_REGNO_P (save_reg_slot))
save_reg_slot++;
if (!FP_REGNO_P (save_reg_slot))
{
/* We only want to use ldgr/lgdr if we can get rid of
stm/lm entirely. So undo the gpr slot allocation in
case we ran out of FPR save slots. */
for (j = 6; j <= 15; j++)
if (FP_REGNO_P (cfun_gpr_save_slot (j)))
cfun_gpr_save_slot (j) = SAVE_SLOT_STACK;
break;
}
cfun_gpr_save_slot (i) = save_reg_slot++;
}
}
/* Set the bits in fpr_bitmap for FPRs which need to be saved due to
stdarg.
This is a helper routine for s390_register_info. */
static void
s390_register_info_stdarg_fpr ()
{
int i;
int min_fpr;
int max_fpr;
/* Save the FP argument regs for stdarg. f0, f2 for 31 bit and
f0-f4 for 64 bit. */
if (!cfun->stdarg
|| !TARGET_HARD_FLOAT
|| !cfun->va_list_fpr_size
|| crtl->args.info.fprs >= FP_ARG_NUM_REG)
return;
min_fpr = crtl->args.info.fprs;
max_fpr = min_fpr + cfun->va_list_fpr_size - 1;
if (max_fpr >= FP_ARG_NUM_REG)
max_fpr = FP_ARG_NUM_REG - 1;
/* FPR argument regs start at f0. */
min_fpr += FPR0_REGNUM;
max_fpr += FPR0_REGNUM;
for (i = min_fpr; i <= max_fpr; i++)
cfun_set_fpr_save (i);
}
/* Reserve the GPR save slots for GPRs which need to be saved due to
stdarg.
This is a helper routine for s390_register_info. */
static void
s390_register_info_stdarg_gpr ()
{
int i;
int min_gpr;
int max_gpr;
if (!cfun->stdarg
|| !cfun->va_list_gpr_size
|| crtl->args.info.gprs >= GP_ARG_NUM_REG)
return;
min_gpr = crtl->args.info.gprs;
max_gpr = min_gpr + cfun->va_list_gpr_size - 1;
if (max_gpr >= GP_ARG_NUM_REG)
max_gpr = GP_ARG_NUM_REG - 1;
/* GPR argument regs start at r2. */
min_gpr += GPR2_REGNUM;
max_gpr += GPR2_REGNUM;
/* If r6 was supposed to be saved into an FPR and now needs to go to
the stack for vararg we have to adjust the restore range to make
sure that the restore is done from stack as well. */
if (FP_REGNO_P (cfun_gpr_save_slot (GPR6_REGNUM))
&& min_gpr <= GPR6_REGNUM
&& max_gpr >= GPR6_REGNUM)
{
if (cfun_frame_layout.first_restore_gpr == -1
|| cfun_frame_layout.first_restore_gpr > GPR6_REGNUM)
cfun_frame_layout.first_restore_gpr = GPR6_REGNUM;
if (cfun_frame_layout.last_restore_gpr == -1
|| cfun_frame_layout.last_restore_gpr < GPR6_REGNUM)
cfun_frame_layout.last_restore_gpr = GPR6_REGNUM;
}
if (cfun_frame_layout.first_save_gpr == -1
|| cfun_frame_layout.first_save_gpr > min_gpr)
cfun_frame_layout.first_save_gpr = min_gpr;
if (cfun_frame_layout.last_save_gpr == -1
|| cfun_frame_layout.last_save_gpr < max_gpr)
cfun_frame_layout.last_save_gpr = max_gpr;
for (i = min_gpr; i <= max_gpr; i++)
cfun_gpr_save_slot (i) = SAVE_SLOT_STACK;
}
/* Calculate the save and restore ranges for stm(g) and lm(g) in the
prologue and epilogue. */
static void
s390_register_info_set_ranges ()
{
int i, j;
/* Find the first and the last save slot supposed to use the stack
to set the restore range.
Vararg regs might be marked as save to stack but only the
call-saved regs really need restoring (i.e. r6). This code
assumes that the vararg regs have not yet been recorded in
cfun_gpr_save_slot. */
for (i = 0; i < 16 && cfun_gpr_save_slot (i) != SAVE_SLOT_STACK; i++);
for (j = 15; j > i && cfun_gpr_save_slot (j) != SAVE_SLOT_STACK; j--);
cfun_frame_layout.first_restore_gpr = (i == 16) ? -1 : i;
cfun_frame_layout.last_restore_gpr = (i == 16) ? -1 : j;
cfun_frame_layout.first_save_gpr = (i == 16) ? -1 : i;
cfun_frame_layout.last_save_gpr = (i == 16) ? -1 : j;
}
/* The GPR and FPR save slots in cfun->machine->frame_layout are set
for registers which need to be saved in function prologue.
This function can be used until the insns emitted for save/restore
of the regs are visible in the RTL stream. */
static void
s390_register_info ()
{
int i;
char clobbered_regs[32];
gcc_assert (!epilogue_completed);
if (reload_completed)
/* After reload we rely on our own routine to determine which
registers need saving. */
s390_regs_ever_clobbered (clobbered_regs);
else
/* During reload we use regs_ever_live as a base since reload
does changes in there which we otherwise would not be aware
of. */
for (i = 0; i < 32; i++)
clobbered_regs[i] = df_regs_ever_live_p (i);
for (i = 0; i < 32; i++)
clobbered_regs[i] = clobbered_regs[i] && !global_regs[i];
/* Mark the call-saved FPRs which need to be saved.
This needs to be done before checking the special GPRs since the
stack pointer usage depends on whether high FPRs have to be saved
or not. */
cfun_frame_layout.fpr_bitmap = 0;
cfun_frame_layout.high_fprs = 0;
for (i = FPR0_REGNUM; i <= FPR15_REGNUM; i++)
if (clobbered_regs[i] && !call_used_regs[i])
{
cfun_set_fpr_save (i);
if (i >= FPR8_REGNUM)
cfun_frame_layout.high_fprs++;
}
/* Register 12 is used for GOT address, but also as temp in prologue
for split-stack stdarg functions (unless r14 is available). */
clobbered_regs[12]
|= ((flag_pic && df_regs_ever_live_p (PIC_OFFSET_TABLE_REGNUM))
|| (flag_split_stack && cfun->stdarg
&& (crtl->is_leaf || TARGET_TPF_PROFILING
|| has_hard_reg_initial_val (Pmode, RETURN_REGNUM))));
clobbered_regs[BASE_REGNUM]
|= (cfun->machine->base_reg
&& REGNO (cfun->machine->base_reg) == BASE_REGNUM);
clobbered_regs[HARD_FRAME_POINTER_REGNUM]
|= !!frame_pointer_needed;
/* On pre z900 machines this might take until machine dependent
reorg to decide.
save_return_addr_p will only be set on non-zarch machines so
there is no risk that r14 goes into an FPR instead of a stack
slot. */
clobbered_regs[RETURN_REGNUM]
|= (!crtl->is_leaf
|| TARGET_TPF_PROFILING
|| cfun_frame_layout.save_return_addr_p
|| crtl->calls_eh_return);
clobbered_regs[STACK_POINTER_REGNUM]
|= (!crtl->is_leaf
|| TARGET_TPF_PROFILING
|| cfun_save_high_fprs_p
|| get_frame_size () > 0
|| (reload_completed && cfun_frame_layout.frame_size > 0)
|| cfun->calls_alloca);
memset (cfun_frame_layout.gpr_save_slots, SAVE_SLOT_NONE, 16);
for (i = 6; i < 16; i++)
if (clobbered_regs[i])
cfun_gpr_save_slot (i) = SAVE_SLOT_STACK;
s390_register_info_stdarg_fpr ();
s390_register_info_gprtofpr ();
s390_register_info_set_ranges ();
/* stdarg functions might need to save GPRs 2 to 6. This might
override the GPR->FPR save decision made by
s390_register_info_gprtofpr for r6 since vararg regs must go to
the stack. */
s390_register_info_stdarg_gpr ();
}
/* Return true if REGNO is a global register, but not one
of the special ones that need to be saved/restored in anyway. */
static inline bool
global_not_special_regno_p (int regno)
{
return (global_regs[regno]
/* These registers are special and need to be
restored in any case. */
&& !(regno == STACK_POINTER_REGNUM
|| regno == RETURN_REGNUM
|| regno == BASE_REGNUM
|| (flag_pic && regno == (int)PIC_OFFSET_TABLE_REGNUM)));
}
/* This function is called by s390_optimize_prologue in order to get
rid of unnecessary GPR save/restore instructions. The register info
for the GPRs is re-computed and the ranges are re-calculated. */
static void
s390_optimize_register_info ()
{
char clobbered_regs[32];
int i;
gcc_assert (epilogue_completed);
s390_regs_ever_clobbered (clobbered_regs);
/* Global registers do not need to be saved and restored unless it
is one of our special regs. (r12, r13, r14, or r15). */
for (i = 0; i < 32; i++)
clobbered_regs[i] = clobbered_regs[i] && !global_not_special_regno_p (i);
/* There is still special treatment needed for cases invisible to
s390_regs_ever_clobbered. */
clobbered_regs[RETURN_REGNUM]
|= (TARGET_TPF_PROFILING
/* When expanding builtin_return_addr in ESA mode we do not
know whether r14 will later be needed as scratch reg when
doing branch splitting. So the builtin always accesses the
r14 save slot and we need to stick to the save/restore
decision for r14 even if it turns out that it didn't get
clobbered. */
|| cfun_frame_layout.save_return_addr_p
|| crtl->calls_eh_return);
memset (cfun_frame_layout.gpr_save_slots, SAVE_SLOT_NONE, 6);
for (i = 6; i < 16; i++)
if (!clobbered_regs[i])
cfun_gpr_save_slot (i) = SAVE_SLOT_NONE;
s390_register_info_set_ranges ();
s390_register_info_stdarg_gpr ();
}
/* Fill cfun->machine with info about frame of current function. */
static void
s390_frame_info (void)
{
HOST_WIDE_INT lowest_offset;
cfun_frame_layout.first_save_gpr_slot = cfun_frame_layout.first_save_gpr;
cfun_frame_layout.last_save_gpr_slot = cfun_frame_layout.last_save_gpr;
/* The va_arg builtin uses a constant distance of 16 *
UNITS_PER_LONG (r0-r15) to reach the FPRs from the reg_save_area
pointer. So even if we are going to save the stack pointer in an
FPR we need the stack space in order to keep the offsets
correct. */
if (cfun->stdarg && cfun_save_arg_fprs_p)
{
cfun_frame_layout.last_save_gpr_slot = STACK_POINTER_REGNUM;
if (cfun_frame_layout.first_save_gpr_slot == -1)
cfun_frame_layout.first_save_gpr_slot = STACK_POINTER_REGNUM;
}
cfun_frame_layout.frame_size = get_frame_size ();
if (!TARGET_64BIT && cfun_frame_layout.frame_size > 0x7fff0000)
fatal_error (input_location,
"total size of local variables exceeds architecture limit");
if (!TARGET_PACKED_STACK)
{
/* Fixed stack layout. */
cfun_frame_layout.backchain_offset = 0;
cfun_frame_layout.f0_offset = 16 * UNITS_PER_LONG;
cfun_frame_layout.f4_offset = cfun_frame_layout.f0_offset + 2 * 8;
cfun_frame_layout.f8_offset = -cfun_frame_layout.high_fprs * 8;
cfun_frame_layout.gprs_offset = (cfun_frame_layout.first_save_gpr_slot
* UNITS_PER_LONG);
}
else if (TARGET_BACKCHAIN)
{
/* Kernel stack layout - packed stack, backchain, no float */
gcc_assert (TARGET_SOFT_FLOAT);
cfun_frame_layout.backchain_offset = (STACK_POINTER_OFFSET
- UNITS_PER_LONG);
/* The distance between the backchain and the return address
save slot must not change. So we always need a slot for the
stack pointer which resides in between. */
cfun_frame_layout.last_save_gpr_slot = STACK_POINTER_REGNUM;
cfun_frame_layout.gprs_offset
= cfun_frame_layout.backchain_offset - cfun_gprs_save_area_size;
/* FPRs will not be saved. Nevertheless pick sane values to
keep area calculations valid. */
cfun_frame_layout.f0_offset =
cfun_frame_layout.f4_offset =
cfun_frame_layout.f8_offset = cfun_frame_layout.gprs_offset;
}
else
{
int num_fprs;
/* Packed stack layout without backchain. */
/* With stdarg FPRs need their dedicated slots. */
num_fprs = (TARGET_64BIT && cfun->stdarg ? 2
: (cfun_fpr_save_p (FPR4_REGNUM) +
cfun_fpr_save_p (FPR6_REGNUM)));
cfun_frame_layout.f4_offset = STACK_POINTER_OFFSET - 8 * num_fprs;
num_fprs = (cfun->stdarg ? 2
: (cfun_fpr_save_p (FPR0_REGNUM)
+ cfun_fpr_save_p (FPR2_REGNUM)));
cfun_frame_layout.f0_offset = cfun_frame_layout.f4_offset - 8 * num_fprs;
cfun_frame_layout.gprs_offset
= cfun_frame_layout.f0_offset - cfun_gprs_save_area_size;
cfun_frame_layout.f8_offset = (cfun_frame_layout.gprs_offset
- cfun_frame_layout.high_fprs * 8);
}
if (cfun_save_high_fprs_p)
cfun_frame_layout.frame_size += cfun_frame_layout.high_fprs * 8;
if (!crtl->is_leaf)
cfun_frame_layout.frame_size += crtl->outgoing_args_size;
/* In the following cases we have to allocate a STACK_POINTER_OFFSET
sized area at the bottom of the stack. This is required also for
leaf functions. When GCC generates a local stack reference it
will always add STACK_POINTER_OFFSET to all these references. */
if (crtl->is_leaf
&& !TARGET_TPF_PROFILING
&& cfun_frame_layout.frame_size == 0
&& !cfun->calls_alloca)
return;
/* Calculate the number of bytes we have used in our own register
save area. With the packed stack layout we can re-use the
remaining bytes for normal stack elements. */
if (TARGET_PACKED_STACK)
lowest_offset = MIN (MIN (cfun_frame_layout.f0_offset,
cfun_frame_layout.f4_offset),
cfun_frame_layout.gprs_offset);
else
lowest_offset = 0;
if (TARGET_BACKCHAIN)
lowest_offset = MIN (lowest_offset, cfun_frame_layout.backchain_offset);
cfun_frame_layout.frame_size += STACK_POINTER_OFFSET - lowest_offset;
/* If under 31 bit an odd number of gprs has to be saved we have to
adjust the frame size to sustain 8 byte alignment of stack
frames. */
cfun_frame_layout.frame_size = ((cfun_frame_layout.frame_size +
STACK_BOUNDARY / BITS_PER_UNIT - 1)
& ~(STACK_BOUNDARY / BITS_PER_UNIT - 1));
}
/* Generate frame layout. Fills in register and frame data for the current
function in cfun->machine. This routine can be called multiple times;
it will re-do the complete frame layout every time. */
static void
s390_init_frame_layout (void)
{
HOST_WIDE_INT frame_size;
int base_used;
/* After LRA the frame layout is supposed to be read-only and should
not be re-computed. */
if (reload_completed)
return;
do
{
frame_size = cfun_frame_layout.frame_size;
/* Try to predict whether we'll need the base register. */
base_used = crtl->uses_const_pool
|| (!DISP_IN_RANGE (frame_size)
&& !CONST_OK_FOR_K (frame_size));
/* Decide which register to use as literal pool base. In small
leaf functions, try to use an unused call-clobbered register
as base register to avoid save/restore overhead. */
if (!base_used)
cfun->machine->base_reg = NULL_RTX;
else
{
int br = 0;
if (crtl->is_leaf)
/* Prefer r5 (most likely to be free). */
for (br = 5; br >= 2 && df_regs_ever_live_p (br); br--)
;
cfun->machine->base_reg =
gen_rtx_REG (Pmode, (br >= 2) ? br : BASE_REGNUM);
}
s390_register_info ();
s390_frame_info ();
}
while (frame_size != cfun_frame_layout.frame_size);
}
/* Remove the FPR clobbers from a tbegin insn if it can be proven that
the TX is nonescaping. A transaction is considered escaping if
there is at least one path from tbegin returning CC0 to the
function exit block without an tend.
The check so far has some limitations:
- only single tbegin/tend BBs are supported
- the first cond jump after tbegin must separate the CC0 path from ~CC0
- when CC is copied to a GPR and the CC0 check is done with the GPR
this is not supported
*/
static void
s390_optimize_nonescaping_tx (void)
{
const unsigned int CC0 = 1 << 3;
basic_block tbegin_bb = NULL;
basic_block tend_bb = NULL;
basic_block bb;
rtx_insn *insn;
bool result = true;
int bb_index;
rtx_insn *tbegin_insn = NULL;
if (!cfun->machine->tbegin_p)
return;
for (bb_index = 0; bb_index < n_basic_blocks_for_fn (cfun); bb_index++)
{
bb = BASIC_BLOCK_FOR_FN (cfun, bb_index);
if (!bb)
continue;
FOR_BB_INSNS (bb, insn)
{
rtx ite, cc, pat, target;
unsigned HOST_WIDE_INT mask;
if (!INSN_P (insn) || INSN_CODE (insn) <= 0)
continue;
pat = PATTERN (insn);
if (GET_CODE (pat) == PARALLEL)
pat = XVECEXP (pat, 0, 0);
if (GET_CODE (pat) != SET
|| GET_CODE (SET_SRC (pat)) != UNSPEC_VOLATILE)
continue;
if (XINT (SET_SRC (pat), 1) == UNSPECV_TBEGIN)
{
rtx_insn *tmp;
tbegin_insn = insn;
/* Just return if the tbegin doesn't have clobbers. */
if (GET_CODE (PATTERN (insn)) != PARALLEL)
return;
if (tbegin_bb != NULL)
return;
/* Find the next conditional jump. */
for (tmp = NEXT_INSN (insn);
tmp != NULL_RTX;
tmp = NEXT_INSN (tmp))
{
if (reg_set_p (gen_rtx_REG (CCmode, CC_REGNUM), tmp))
return;
if (!JUMP_P (tmp))
continue;
ite = SET_SRC (PATTERN (tmp));
if (GET_CODE (ite) != IF_THEN_ELSE)
continue;
cc = XEXP (XEXP (ite, 0), 0);
if (!REG_P (cc) || !CC_REGNO_P (REGNO (cc))
|| GET_MODE (cc) != CCRAWmode
|| GET_CODE (XEXP (XEXP (ite, 0), 1)) != CONST_INT)
return;
if (bb->succs->length () != 2)
return;
mask = INTVAL (XEXP (XEXP (ite, 0), 1));
if (GET_CODE (XEXP (ite, 0)) == NE)
mask ^= 0xf;
if (mask == CC0)
target = XEXP (ite, 1);
else if (mask == (CC0 ^ 0xf))
target = XEXP (ite, 2);
else
return;
{
edge_iterator ei;
edge e1, e2;
ei = ei_start (bb->succs);
e1 = ei_safe_edge (ei);
ei_next (&ei);
e2 = ei_safe_edge (ei);
if (e2->flags & EDGE_FALLTHRU)
{
e2 = e1;
e1 = ei_safe_edge (ei);
}
if (!(e1->flags & EDGE_FALLTHRU))
return;
tbegin_bb = (target == pc_rtx) ? e1->dest : e2->dest;
}
if (tmp == BB_END (bb))
break;
}
}
if (XINT (SET_SRC (pat), 1) == UNSPECV_TEND)
{
if (tend_bb != NULL)
return;
tend_bb = bb;
}
}
}
/* Either we successfully remove the FPR clobbers here or we are not
able to do anything for this TX. Both cases don't qualify for
another look. */
cfun->machine->tbegin_p = false;
if (tbegin_bb == NULL || tend_bb == NULL)
return;
calculate_dominance_info (CDI_POST_DOMINATORS);
result = dominated_by_p (CDI_POST_DOMINATORS, tbegin_bb, tend_bb);
free_dominance_info (CDI_POST_DOMINATORS);
if (!result)
return;
PATTERN (tbegin_insn) = gen_rtx_PARALLEL (VOIDmode,
gen_rtvec (2,
XVECEXP (PATTERN (tbegin_insn), 0, 0),
XVECEXP (PATTERN (tbegin_insn), 0, 1)));
INSN_CODE (tbegin_insn) = -1;
df_insn_rescan (tbegin_insn);
return;
}
/* Implement TARGET_HARD_REGNO_NREGS. Because all registers in a class
have the same size, this is equivalent to CLASS_MAX_NREGS. */
static unsigned int
s390_hard_regno_nregs (unsigned int regno, machine_mode mode)
{
return s390_class_max_nregs (REGNO_REG_CLASS (regno), mode);
}
/* Implement TARGET_HARD_REGNO_MODE_OK.
Integer modes <= word size fit into any GPR.
Integer modes > word size fit into successive GPRs, starting with
an even-numbered register.
SImode and DImode fit into FPRs as well.
Floating point modes <= word size fit into any FPR or GPR.
Floating point modes > word size (i.e. DFmode on 32-bit) fit
into any FPR, or an even-odd GPR pair.
TFmode fits only into an even-odd FPR pair.
Complex floating point modes fit either into two FPRs, or into
successive GPRs (again starting with an even number).
TCmode fits only into two successive even-odd FPR pairs.
Condition code modes fit only into the CC register. */
static bool
s390_hard_regno_mode_ok (unsigned int regno, machine_mode mode)
{
if (!TARGET_VX && VECTOR_NOFP_REGNO_P (regno))
return false;
switch (REGNO_REG_CLASS (regno))
{
case VEC_REGS:
return ((GET_MODE_CLASS (mode) == MODE_INT
&& s390_class_max_nregs (VEC_REGS, mode) == 1)
|| mode == DFmode
|| (TARGET_VXE && mode == SFmode)
|| s390_vector_mode_supported_p (mode));
break;
case FP_REGS:
if (TARGET_VX
&& ((GET_MODE_CLASS (mode) == MODE_INT
&& s390_class_max_nregs (FP_REGS, mode) == 1)
|| mode == DFmode
|| s390_vector_mode_supported_p (mode)))
return true;
if (REGNO_PAIR_OK (regno, mode))
{
if (mode == SImode || mode == DImode)
return true;
if (FLOAT_MODE_P (mode) && GET_MODE_CLASS (mode) != MODE_VECTOR_FLOAT)
return true;
}
break;
case ADDR_REGS:
if (FRAME_REGNO_P (regno) && mode == Pmode)
return true;
/* fallthrough */
case GENERAL_REGS:
if (REGNO_PAIR_OK (regno, mode))
{
if (TARGET_ZARCH
|| (mode != TFmode && mode != TCmode && mode != TDmode))
return true;
}
break;
case CC_REGS:
if (GET_MODE_CLASS (mode) == MODE_CC)
return true;
break;
case ACCESS_REGS:
if (REGNO_PAIR_OK (regno, mode))
{
if (mode == SImode || mode == Pmode)
return true;
}
break;
default:
return false;
}
return false;
}
/* Implement TARGET_MODES_TIEABLE_P. */
static bool
s390_modes_tieable_p (machine_mode mode1, machine_mode mode2)
{
return ((mode1 == SFmode || mode1 == DFmode)
== (mode2 == SFmode || mode2 == DFmode));
}
/* Return nonzero if register OLD_REG can be renamed to register NEW_REG. */
bool
s390_hard_regno_rename_ok (unsigned int old_reg, unsigned int new_reg)
{
/* Once we've decided upon a register to use as base register, it must
no longer be used for any other purpose. */
if (cfun->machine->base_reg)
if (REGNO (cfun->machine->base_reg) == old_reg
|| REGNO (cfun->machine->base_reg) == new_reg)
return false;
/* Prevent regrename from using call-saved regs which haven't
actually been saved. This is necessary since regrename assumes
the backend save/restore decisions are based on
df_regs_ever_live. Since we have our own routine we have to tell
regrename manually about it. */
if (GENERAL_REGNO_P (new_reg)
&& !call_used_regs[new_reg]
&& cfun_gpr_save_slot (new_reg) == SAVE_SLOT_NONE)
return false;
return true;
}
/* Return nonzero if register REGNO can be used as a scratch register
in peephole2. */
static bool
s390_hard_regno_scratch_ok (unsigned int regno)
{
/* See s390_hard_regno_rename_ok. */
if (GENERAL_REGNO_P (regno)
&& !call_used_regs[regno]
&& cfun_gpr_save_slot (regno) == SAVE_SLOT_NONE)
return false;
return true;
}
/* Implement TARGET_HARD_REGNO_CALL_PART_CLOBBERED. When generating
code that runs in z/Architecture mode, but conforms to the 31-bit
ABI, GPRs can hold 8 bytes; the ABI guarantees only that the lower 4
bytes are saved across calls, however. */
static bool
s390_hard_regno_call_part_clobbered (unsigned int, unsigned int regno,
machine_mode mode)
{
/* For r12 we know that the only bits we actually care about are
preserved across function calls. Since r12 is a fixed reg all
accesses to r12 are generated by the backend.
This workaround is necessary until gcse implements proper
tracking of partially clobbered registers. */
if (!TARGET_64BIT
&& TARGET_ZARCH
&& GET_MODE_SIZE (mode) > 4
&& (!flag_pic || regno != PIC_OFFSET_TABLE_REGNUM)
&& ((regno >= 6 && regno <= 15) || regno == 32))
return true;
if (TARGET_VX
&& GET_MODE_SIZE (mode) > 8
&& (((TARGET_64BIT && regno >= 24 && regno <= 31))
|| (!TARGET_64BIT && (regno == 18 || regno == 19))))
return true;
return false;
}
/* Maximum number of registers to represent a value of mode MODE
in a register of class RCLASS. */
int
s390_class_max_nregs (enum reg_class rclass, machine_mode mode)
{
int reg_size;
bool reg_pair_required_p = false;
switch (rclass)
{
case FP_REGS:
case VEC_REGS:
reg_size = TARGET_VX ? 16 : 8;
/* TF and TD modes would fit into a VR but we put them into a
register pair since we do not have 128bit FP instructions on
full VRs. */
if (TARGET_VX
&& SCALAR_FLOAT_MODE_P (mode)
&& GET_MODE_SIZE (mode) >= 16
&& !(TARGET_VXE && mode == TFmode))
reg_pair_required_p = true;
/* Even if complex types would fit into a single FPR/VR we force
them into a register pair to deal with the parts more easily.
(FIXME: What about complex ints?) */
if (GET_MODE_CLASS (mode) == MODE_COMPLEX_FLOAT)
reg_pair_required_p = true;
break;
case ACCESS_REGS:
reg_size = 4;
break;
default:
reg_size = UNITS_PER_WORD;
break;
}
if (reg_pair_required_p)
return 2 * ((GET_MODE_SIZE (mode) / 2 + reg_size - 1) / reg_size);
return (GET_MODE_SIZE (mode) + reg_size - 1) / reg_size;
}
/* Return nonzero if mode M describes a 128-bit float in a floating point
register pair. */
static bool
s390_is_fpr128 (machine_mode m)
{
return m == FPRX2mode || (!TARGET_VXE && m == TFmode);
}
/* Return nonzero if mode M describes a 128-bit float in a vector
register. */
static bool
s390_is_vr128 (machine_mode m)
{
return m == V1TFmode || (TARGET_VXE && m == TFmode);
}
/* Implement TARGET_CAN_CHANGE_MODE_CLASS. */
static bool
s390_can_change_mode_class (machine_mode from_mode,
machine_mode to_mode,
reg_class_t rclass)
{
machine_mode small_mode;
machine_mode big_mode;
/* 128-bit values have different representations in floating point and
vector registers. */
if (reg_classes_intersect_p (VEC_REGS, rclass)
&& ((s390_is_fpr128 (from_mode) && s390_is_vr128 (to_mode))
|| (s390_is_vr128 (from_mode) && s390_is_fpr128 (to_mode))))
return false;
if (GET_MODE_SIZE (from_mode) == GET_MODE_SIZE (to_mode))
return true;
if (GET_MODE_SIZE (from_mode) < GET_MODE_SIZE (to_mode))
{
small_mode = from_mode;
big_mode = to_mode;
}
else
{
small_mode = to_mode;
big_mode = from_mode;
}
/* Values residing in VRs are little-endian style. All modes are
placed left-aligned in an VR. This means that we cannot allow
switching between modes with differing sizes. Also if the vector
facility is available we still place TFmode values in VR register
pairs, since the only instructions we have operating on TFmodes
only deal with register pairs. Therefore we have to allow DFmode
subregs of TFmodes to enable the TFmode splitters. */
if (reg_classes_intersect_p (VEC_REGS, rclass)
&& (GET_MODE_SIZE (small_mode) < 8
|| s390_class_max_nregs (VEC_REGS, big_mode) == 1))
return false;
/* Likewise for access registers, since they have only half the
word size on 64-bit. */
if (reg_classes_intersect_p (ACCESS_REGS, rclass))
return false;
return true;
}
/* Return true if we use LRA instead of reload pass. */
static bool
s390_lra_p (void)
{
return s390_lra_flag;
}
/* Return true if register FROM can be eliminated via register TO. */
static bool
s390_can_eliminate (const int from, const int to)
{
/* We have not marked the base register as fixed.
Instead, we have an elimination rule BASE_REGNUM -> BASE_REGNUM.
If a function requires the base register, we say here that this
elimination cannot be performed. This will cause reload to free
up the base register (as if it were fixed). On the other hand,
if the current function does *not* require the base register, we
say here the elimination succeeds, which in turn allows reload
to allocate the base register for any other purpose. */
if (from == BASE_REGNUM && to == BASE_REGNUM)
{
s390_init_frame_layout ();
return cfun->machine->base_reg == NULL_RTX;
}
/* Everything else must point into the stack frame. */
gcc_assert (to == STACK_POINTER_REGNUM
|| to == HARD_FRAME_POINTER_REGNUM);
gcc_assert (from == FRAME_POINTER_REGNUM
|| from == ARG_POINTER_REGNUM
|| from == RETURN_ADDRESS_POINTER_REGNUM);
/* Make sure we actually saved the return address. */
if (from == RETURN_ADDRESS_POINTER_REGNUM)
if (!crtl->calls_eh_return
&& !cfun->stdarg
&& !cfun_frame_layout.save_return_addr_p)
return false;
return true;
}
/* Return offset between register FROM and TO initially after prolog. */
HOST_WIDE_INT
s390_initial_elimination_offset (int from, int to)
{
HOST_WIDE_INT offset;
/* ??? Why are we called for non-eliminable pairs? */
if (!s390_can_eliminate (from, to))
return 0;
switch (from)
{
case FRAME_POINTER_REGNUM:
offset = (get_frame_size()
+ STACK_POINTER_OFFSET
+ crtl->outgoing_args_size);
break;
case ARG_POINTER_REGNUM:
s390_init_frame_layout ();
offset = cfun_frame_layout.frame_size + STACK_POINTER_OFFSET;
break;
case RETURN_ADDRESS_POINTER_REGNUM:
s390_init_frame_layout ();
if (cfun_frame_layout.first_save_gpr_slot == -1)
{
/* If it turns out that for stdarg nothing went into the reg
save area we also do not need the return address
pointer. */
if (cfun->stdarg && !cfun_save_arg_fprs_p)
return 0;
gcc_unreachable ();
}
/* In order to make the following work it is not necessary for
r14 to have a save slot. It is sufficient if one other GPR
got one. Since the GPRs are always stored without gaps we
are able to calculate where the r14 save slot would
reside. */
offset = (cfun_frame_layout.frame_size + cfun_frame_layout.gprs_offset +
(RETURN_REGNUM - cfun_frame_layout.first_save_gpr_slot) *
UNITS_PER_LONG);
break;
case BASE_REGNUM:
offset = 0;
break;
default:
gcc_unreachable ();
}
return offset;
}
/* Emit insn to save fpr REGNUM at offset OFFSET relative
to register BASE. Return generated insn. */
static rtx
save_fpr (rtx base, int offset, int regnum)
{
rtx addr;
addr = gen_rtx_MEM (DFmode, plus_constant (Pmode, base, offset));
if (regnum >= 16 && regnum <= (16 + FP_ARG_NUM_REG))
set_mem_alias_set (addr, get_varargs_alias_set ());
else
set_mem_alias_set (addr, get_frame_alias_set ());
return emit_move_insn (addr, gen_rtx_REG (DFmode, regnum));
}
/* Emit insn to restore fpr REGNUM from offset OFFSET relative
to register BASE. Return generated insn. */
static rtx
restore_fpr (rtx base, int offset, int regnum)
{
rtx addr;
addr = gen_rtx_MEM (DFmode, plus_constant (Pmode, base, offset));
set_mem_alias_set (addr, get_frame_alias_set ());
return emit_move_insn (gen_rtx_REG (DFmode, regnum), addr);
}
/* Generate insn to save registers FIRST to LAST into
the register save area located at offset OFFSET
relative to register BASE. */
static rtx
save_gprs (rtx base, int offset, int first, int last)
{
rtx addr, insn, note;
int i;
addr = plus_constant (Pmode, base, offset);
addr = gen_rtx_MEM (Pmode, addr);
set_mem_alias_set (addr, get_frame_alias_set ());
/* Special-case single register. */
if (first == last)
{
if (TARGET_64BIT)
insn = gen_movdi (addr, gen_rtx_REG (Pmode, first));
else
insn = gen_movsi (addr, gen_rtx_REG (Pmode, first));
if (!global_not_special_regno_p (first))
RTX_FRAME_RELATED_P (insn) = 1;
return insn;
}
insn = gen_store_multiple (addr,
gen_rtx_REG (Pmode, first),
GEN_INT (last - first + 1));
if (first <= 6 && cfun->stdarg)
for (i = 0; i < XVECLEN (PATTERN (insn), 0); i++)
{
rtx mem = XEXP (XVECEXP (PATTERN (insn), 0, i), 0);
if (first + i <= 6)
set_mem_alias_set (mem, get_varargs_alias_set ());
}
/* We need to set the FRAME_RELATED flag on all SETs
inside the store-multiple pattern.
However, we must not emit DWARF records for registers 2..5
if they are stored for use by variable arguments ...
??? Unfortunately, it is not enough to simply not the
FRAME_RELATED flags for those SETs, because the first SET
of the PARALLEL is always treated as if it had the flag
set, even if it does not. Therefore we emit a new pattern
without those registers as REG_FRAME_RELATED_EXPR note. */
if (first >= 6 && !global_not_special_regno_p (first))
{
rtx pat = PATTERN (insn);
for (i = 0; i < XVECLEN (pat, 0); i++)
if (GET_CODE (XVECEXP (pat, 0, i)) == SET
&& !global_not_special_regno_p (REGNO (SET_SRC (XVECEXP (pat,
0, i)))))
RTX_FRAME_RELATED_P (XVECEXP (pat, 0, i)) = 1;
RTX_FRAME_RELATED_P (insn) = 1;
}
else if (last >= 6)
{
int start;
for (start = first >= 6 ? first : 6; start <= last; start++)
if (!global_not_special_regno_p (start))
break;
if (start > last)
return insn;
addr = plus_constant (Pmode, base,
offset + (start - first) * UNITS_PER_LONG);
if (start == last)
{
if (TARGET_64BIT)
note = gen_movdi (gen_rtx_MEM (Pmode, addr),
gen_rtx_REG (Pmode, start));
else
note = gen_movsi (gen_rtx_MEM (Pmode, addr),
gen_rtx_REG (Pmode, start));
note = PATTERN (note);
add_reg_note (insn, REG_FRAME_RELATED_EXPR, note);
RTX_FRAME_RELATED_P (insn) = 1;
return insn;
}
note = gen_store_multiple (gen_rtx_MEM (Pmode, addr),
gen_rtx_REG (Pmode, start),
GEN_INT (last - start + 1));
note = PATTERN (note);
add_reg_note (insn, REG_FRAME_RELATED_EXPR, note);
for (i = 0; i < XVECLEN (note, 0); i++)
if (GET_CODE (XVECEXP (note, 0, i)) == SET
&& !global_not_special_regno_p (REGNO (SET_SRC (XVECEXP (note,
0, i)))))
RTX_FRAME_RELATED_P (XVECEXP (note, 0, i)) = 1;
RTX_FRAME_RELATED_P (insn) = 1;
}
return insn;
}
/* Generate insn to restore registers FIRST to LAST from
the register save area located at offset OFFSET
relative to register BASE. */
static rtx
restore_gprs (rtx base, int offset, int first, int last)
{
rtx addr, insn;
addr = plus_constant (Pmode, base, offset);
addr = gen_rtx_MEM (Pmode, addr);
set_mem_alias_set (addr, get_frame_alias_set ());
/* Special-case single register. */
if (first == last)
{
if (TARGET_64BIT)
insn = gen_movdi (gen_rtx_REG (Pmode, first), addr);
else
insn = gen_movsi (gen_rtx_REG (Pmode, first), addr);
RTX_FRAME_RELATED_P (insn) = 1;
return insn;
}
insn = gen_load_multiple (gen_rtx_REG (Pmode, first),
addr,
GEN_INT (last - first + 1));
RTX_FRAME_RELATED_P (insn) = 1;
return insn;
}
/* Return insn sequence to load the GOT register. */
rtx_insn *
s390_load_got (void)
{
rtx_insn *insns;
/* We cannot use pic_offset_table_rtx here since we use this
function also for non-pic if __tls_get_offset is called and in
that case PIC_OFFSET_TABLE_REGNUM as well as pic_offset_table_rtx
aren't usable. */
rtx got_rtx = gen_rtx_REG (Pmode, 12);
start_sequence ();
emit_move_insn (got_rtx, s390_got_symbol ());
insns = get_insns ();
end_sequence ();
return insns;
}
/* This ties together stack memory (MEM with an alias set of frame_alias_set)
and the change to the stack pointer. */
static void
s390_emit_stack_tie (void)
{
rtx mem = gen_frame_mem (BLKmode,
gen_rtx_REG (Pmode, STACK_POINTER_REGNUM));
emit_insn (gen_stack_tie (mem));
}
/* Copy GPRS into FPR save slots. */
static void
s390_save_gprs_to_fprs (void)
{
int i;
if (!TARGET_Z10 || !TARGET_HARD_FLOAT || !crtl->is_leaf)
return;
for (i = 6; i < 16; i++)
{
if (FP_REGNO_P (cfun_gpr_save_slot (i)))
{
rtx_insn *insn =
emit_move_insn (gen_rtx_REG (DImode, cfun_gpr_save_slot (i)),
gen_rtx_REG (DImode, i));
RTX_FRAME_RELATED_P (insn) = 1;
/* This prevents dwarf2cfi from interpreting the set. Doing
so it might emit def_cfa_register infos setting an FPR as
new CFA. */
add_reg_note (insn, REG_CFA_REGISTER, copy_rtx (PATTERN (insn)));
}
}
}
/* Restore GPRs from FPR save slots. */
static void
s390_restore_gprs_from_fprs (void)
{
int i;
if (!TARGET_Z10 || !TARGET_HARD_FLOAT || !crtl->is_leaf)
return;
/* Restore the GPRs starting with the stack pointer. That way the
stack pointer already has its original value when it comes to
restoring the hard frame pointer. So we can set the cfa reg back
to the stack pointer. */
for (i = STACK_POINTER_REGNUM; i >= 6; i--)
{
rtx_insn *insn;
if (!FP_REGNO_P (cfun_gpr_save_slot (i)))
continue;
rtx fpr = gen_rtx_REG (DImode, cfun_gpr_save_slot (i));
if (i == STACK_POINTER_REGNUM)
insn = emit_insn (gen_stack_restore_from_fpr (fpr));
else
insn = emit_move_insn (gen_rtx_REG (DImode, i), fpr);
df_set_regs_ever_live (i, true);
add_reg_note (insn, REG_CFA_RESTORE, gen_rtx_REG (DImode, i));
/* If either the stack pointer or the frame pointer get restored
set the CFA value to its value at function start. Doing this
for the frame pointer results in .cfi_def_cfa_register 15
what is ok since if the stack pointer got modified it has
been restored already. */
if (i == STACK_POINTER_REGNUM || i == HARD_FRAME_POINTER_REGNUM)
add_reg_note (insn, REG_CFA_DEF_CFA,
plus_constant (Pmode, stack_pointer_rtx,
STACK_POINTER_OFFSET));
RTX_FRAME_RELATED_P (insn) = 1;
}
}
/* A pass run immediately before shrink-wrapping and prologue and epilogue
generation. */
namespace {
const pass_data pass_data_s390_early_mach =
{
RTL_PASS, /* type */
"early_mach", /* name */
OPTGROUP_NONE, /* optinfo_flags */
TV_MACH_DEP, /* tv_id */
0, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
( TODO_df_verify | TODO_df_finish ), /* todo_flags_finish */
};
class pass_s390_early_mach : public rtl_opt_pass
{
public:
pass_s390_early_mach (gcc::context *ctxt)
: rtl_opt_pass (pass_data_s390_early_mach, ctxt)
{}
/* opt_pass methods: */
virtual unsigned int execute (function *);
}; // class pass_s390_early_mach
unsigned int
pass_s390_early_mach::execute (function *fun)
{
rtx_insn *insn;
/* Try to get rid of the FPR clobbers. */
s390_optimize_nonescaping_tx ();
/* Re-compute register info. */
s390_register_info ();
/* If we're using a base register, ensure that it is always valid for
the first non-prologue instruction. */
if (fun->machine->base_reg)
emit_insn_at_entry (gen_main_pool (fun->machine->base_reg));
/* Annotate all constant pool references to let the scheduler know
they implicitly use the base register. */
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
if (INSN_P (insn))
{
annotate_constant_pool_refs (insn);
df_insn_rescan (insn);
}
return 0;
}
} // anon namespace
rtl_opt_pass *
make_pass_s390_early_mach (gcc::context *ctxt)
{
return new pass_s390_early_mach (ctxt);
}
/* Calculate TARGET = REG + OFFSET as s390_emit_prologue would do it.
- push too big immediates to the literal pool and annotate the refs
- emit frame related notes for stack pointer changes. */
static rtx
s390_prologue_plus_offset (rtx target, rtx reg, rtx offset, bool frame_related_p)
{
rtx_insn *insn;
rtx orig_offset = offset;
gcc_assert (REG_P (target));
gcc_assert (REG_P (reg));
gcc_assert (CONST_INT_P (offset));
if (offset == const0_rtx) /* lr/lgr */
{
insn = emit_move_insn (target, reg);
}
else if (DISP_IN_RANGE (INTVAL (offset))) /* la */
{
insn = emit_move_insn (target, gen_rtx_PLUS (Pmode, reg,
offset));
}
else
{
if (!satisfies_constraint_K (offset) /* ahi/aghi */
&& (!TARGET_EXTIMM
|| (!satisfies_constraint_Op (offset) /* alfi/algfi */
&& !satisfies_constraint_On (offset)))) /* slfi/slgfi */
offset = force_const_mem (Pmode, offset);
if (target != reg)
{
insn = emit_move_insn (target, reg);
RTX_FRAME_RELATED_P (insn) = frame_related_p ? 1 : 0;
}
insn = emit_insn (gen_add2_insn (target, offset));
if (!CONST_INT_P (offset))
{
annotate_constant_pool_refs (insn);
if (frame_related_p)
add_reg_note (insn, REG_FRAME_RELATED_EXPR,
gen_rtx_SET (target,
gen_rtx_PLUS (Pmode, target,
orig_offset)));
}
}
RTX_FRAME_RELATED_P (insn) = frame_related_p ? 1 : 0;
/* If this is a stack adjustment and we are generating a stack clash
prologue, then add a REG_STACK_CHECK note to signal that this insn
should be left alone. */
if (flag_stack_clash_protection && target == stack_pointer_rtx)
add_reg_note (insn, REG_STACK_CHECK, const0_rtx);
return insn;
}
/* Emit a compare instruction with a volatile memory access as stack
probe. It does not waste store tags and does not clobber any
registers apart from the condition code. */
static void
s390_emit_stack_probe (rtx addr)
{
rtx mem = gen_rtx_MEM (word_mode, addr);
MEM_VOLATILE_P (mem) = 1;
emit_insn (gen_probe_stack (mem));
}
/* Use a runtime loop if we have to emit more probes than this. */
#define MIN_UNROLL_PROBES 3
/* Allocate SIZE bytes of stack space, using TEMP_REG as a temporary
if necessary. LAST_PROBE_OFFSET contains the offset of the closest
probe relative to the stack pointer.
Note that SIZE is negative.
The return value is true if TEMP_REG has been clobbered. */
static bool
allocate_stack_space (rtx size, HOST_WIDE_INT last_probe_offset,
rtx temp_reg)
{
bool temp_reg_clobbered_p = false;
HOST_WIDE_INT probe_interval
= 1 << param_stack_clash_protection_probe_interval;
HOST_WIDE_INT guard_size
= 1 << param_stack_clash_protection_guard_size;
if (flag_stack_clash_protection)
{
if (last_probe_offset + -INTVAL (size) < guard_size)
dump_stack_clash_frame_info (NO_PROBE_SMALL_FRAME, true);
else
{
rtx offset = GEN_INT (probe_interval - UNITS_PER_LONG);
HOST_WIDE_INT rounded_size = -INTVAL (size) & -probe_interval;
HOST_WIDE_INT num_probes = rounded_size / probe_interval;
HOST_WIDE_INT residual = -INTVAL (size) - rounded_size;
if (num_probes < MIN_UNROLL_PROBES)
{
/* Emit unrolled probe statements. */
for (unsigned int i = 0; i < num_probes; i++)
{
s390_prologue_plus_offset (stack_pointer_rtx,
stack_pointer_rtx,
GEN_INT (-probe_interval), true);
s390_emit_stack_probe (gen_rtx_PLUS (Pmode,
stack_pointer_rtx,
offset));
}
if (num_probes > 0)
last_probe_offset = INTVAL (offset);
dump_stack_clash_frame_info (PROBE_INLINE, residual != 0);
}
else
{
/* Emit a loop probing the pages. */
rtx_code_label *loop_start_label = gen_label_rtx ();
/* From now on temp_reg will be the CFA register. */
s390_prologue_plus_offset (temp_reg, stack_pointer_rtx,
GEN_INT (-rounded_size), true);
emit_label (loop_start_label);
s390_prologue_plus_offset (stack_pointer_rtx,
stack_pointer_rtx,
GEN_INT (-probe_interval), false);
s390_emit_stack_probe (gen_rtx_PLUS (Pmode,
stack_pointer_rtx,
offset));
emit_cmp_and_jump_insns (stack_pointer_rtx, temp_reg,
GT, NULL_RTX,
Pmode, 1, loop_start_label);
/* Without this make_edges ICEes. */
JUMP_LABEL (get_last_insn ()) = loop_start_label;
LABEL_NUSES (loop_start_label) = 1;
/* That's going to be a NOP since stack pointer and
temp_reg are supposed to be the same here. We just
emit it to set the CFA reg back to r15. */
s390_prologue_plus_offset (stack_pointer_rtx, temp_reg,
const0_rtx, true);
temp_reg_clobbered_p = true;
last_probe_offset = INTVAL (offset);
dump_stack_clash_frame_info (PROBE_LOOP, residual != 0);
}
/* Handle any residual allocation request. */
s390_prologue_plus_offset (stack_pointer_rtx,
stack_pointer_rtx,
GEN_INT (-residual), true);
last_probe_offset += residual;
if (last_probe_offset >= probe_interval)
s390_emit_stack_probe (gen_rtx_PLUS (Pmode,
stack_pointer_rtx,
GEN_INT (residual
- UNITS_PER_LONG)));
return temp_reg_clobbered_p;
}
}
/* Subtract frame size from stack pointer. */
s390_prologue_plus_offset (stack_pointer_rtx,
stack_pointer_rtx,
size, true);
return temp_reg_clobbered_p;
}
/* Expand the prologue into a bunch of separate insns. */
void
s390_emit_prologue (void)
{
rtx insn, addr;
rtx temp_reg;
int i;
int offset;
int next_fpr = 0;
/* Choose best register to use for temp use within prologue.
TPF with profiling must avoid the register 14 - the tracing function
needs the original contents of r14 to be preserved. */
if (!has_hard_reg_initial_val (Pmode, RETURN_REGNUM)
&& !crtl->is_leaf
&& !TARGET_TPF_PROFILING)
temp_reg = gen_rtx_REG (Pmode, RETURN_REGNUM);
else if (flag_split_stack && cfun->stdarg)
temp_reg = gen_rtx_REG (Pmode, 12);
else
temp_reg = gen_rtx_REG (Pmode, 1);
/* When probing for stack-clash mitigation, we have to track the distance
between the stack pointer and closest known reference.
Most of the time we have to make a worst case assumption. The
only exception is when TARGET_BACKCHAIN is active, in which case
we know *sp (offset 0) was written. */
HOST_WIDE_INT probe_interval
= 1 << param_stack_clash_protection_probe_interval;
HOST_WIDE_INT last_probe_offset
= (TARGET_BACKCHAIN
? (TARGET_PACKED_STACK ? STACK_POINTER_OFFSET - UNITS_PER_LONG : 0)
: probe_interval - (STACK_BOUNDARY / UNITS_PER_WORD));
s390_save_gprs_to_fprs ();
/* Save call saved gprs. */
if (cfun_frame_layout.first_save_gpr != -1)
{
insn = save_gprs (stack_pointer_rtx,
cfun_frame_layout.gprs_offset +
UNITS_PER_LONG * (cfun_frame_layout.first_save_gpr
- cfun_frame_layout.first_save_gpr_slot),
cfun_frame_layout.first_save_gpr,
cfun_frame_layout.last_save_gpr);
/* This is not 100% correct. If we have more than one register saved,
then LAST_PROBE_OFFSET can move even closer to sp. */
last_probe_offset
= (cfun_frame_layout.gprs_offset +
UNITS_PER_LONG * (cfun_frame_layout.first_save_gpr
- cfun_frame_layout.first_save_gpr_slot));
emit_insn (insn);
}
/* Dummy insn to mark literal pool slot. */
if (cfun->machine->base_reg)
emit_insn (gen_main_pool (cfun->machine->base_reg));
offset = cfun_frame_layout.f0_offset;
/* Save f0 and f2. */
for (i = FPR0_REGNUM; i <= FPR0_REGNUM + 1; i++)
{
if (cfun_fpr_save_p (i))
{
save_fpr (stack_pointer_rtx, offset, i);
if (offset < last_probe_offset)
last_probe_offset = offset;
offset += 8;
}
else if (!TARGET_PACKED_STACK || cfun->stdarg)
offset += 8;
}
/* Save f4 and f6. */
offset = cfun_frame_layout.f4_offset;
for (i = FPR4_REGNUM; i <= FPR4_REGNUM + 1; i++)
{
if (cfun_fpr_save_p (i))
{
insn = save_fpr (stack_pointer_rtx, offset, i);
if (offset < last_probe_offset)
last_probe_offset = offset;
offset += 8;
/* If f4 and f6 are call clobbered they are saved due to
stdargs and therefore are not frame related. */
if (!call_used_regs[i])
RTX_FRAME_RELATED_P (insn) = 1;
}
else if (!TARGET_PACKED_STACK || call_used_regs[i])
offset += 8;
}
if (TARGET_PACKED_STACK
&& cfun_save_high_fprs_p
&& cfun_frame_layout.f8_offset + cfun_frame_layout.high_fprs * 8 > 0)
{
offset = (cfun_frame_layout.f8_offset
+ (cfun_frame_layout.high_fprs - 1) * 8);
for (i = FPR15_REGNUM; i >= FPR8_REGNUM && offset >= 0; i--)
if (cfun_fpr_save_p (i))
{
insn = save_fpr (stack_pointer_rtx, offset, i);
if (offset < last_probe_offset)
last_probe_offset = offset;
RTX_FRAME_RELATED_P (insn) = 1;
offset -= 8;
}
if (offset >= cfun_frame_layout.f8_offset)
next_fpr = i;
}
if (!TARGET_PACKED_STACK)
next_fpr = cfun_save_high_fprs_p ? FPR15_REGNUM : 0;
if (flag_stack_usage_info)
current_function_static_stack_size = cfun_frame_layout.frame_size;
/* Decrement stack pointer. */
if (cfun_frame_layout.frame_size > 0)
{
rtx frame_off = GEN_INT (-cfun_frame_layout.frame_size);
rtx_insn *stack_pointer_backup_loc;
bool temp_reg_clobbered_p;
if (s390_stack_size)
{
HOST_WIDE_INT stack_guard;
if (s390_stack_guard)
stack_guard = s390_stack_guard;
else
{
/* If no value for stack guard is provided the smallest power of 2
larger than the current frame size is chosen. */
stack_guard = 1;
while (stack_guard < cfun_frame_layout.frame_size)
stack_guard <<= 1;
}
if (cfun_frame_layout.frame_size >= s390_stack_size)
{
warning (0, "frame size of function %qs is %wd"
" bytes exceeding user provided stack limit of "
"%d bytes. "
"An unconditional trap is added.",
current_function_name(), cfun_frame_layout.frame_size,
s390_stack_size);
emit_insn (gen_trap ());
emit_barrier ();
}
else
{
/* stack_guard has to be smaller than s390_stack_size.
Otherwise we would emit an AND with zero which would
not match the test under mask pattern. */
if (stack_guard >= s390_stack_size)
{
warning (0, "frame size of function %qs is %wd"
" bytes which is more than half the stack size. "
"The dynamic check would not be reliable. "
"No check emitted for this function.",
current_function_name(),
cfun_frame_layout.frame_size);
}
else
{
HOST_WIDE_INT stack_check_mask = ((s390_stack_size - 1)
& ~(stack_guard - 1));
rtx t = gen_rtx_AND (Pmode, stack_pointer_rtx,
GEN_INT (stack_check_mask));
if (TARGET_64BIT)
emit_insn (gen_ctrapdi4 (gen_rtx_EQ (VOIDmode,
t, const0_rtx),
t, const0_rtx, const0_rtx));
else
emit_insn (gen_ctrapsi4 (gen_rtx_EQ (VOIDmode,
t, const0_rtx),
t, const0_rtx, const0_rtx));
}
}
}
if (s390_warn_framesize > 0
&& cfun_frame_layout.frame_size >= s390_warn_framesize)
warning (0, "frame size of %qs is %wd bytes",
current_function_name (), cfun_frame_layout.frame_size);
if (s390_warn_dynamicstack_p && cfun->calls_alloca)
warning (0, "%qs uses dynamic stack allocation", current_function_name ());
/* Save the location where we could backup the incoming stack
pointer. */
stack_pointer_backup_loc = get_last_insn ();
temp_reg_clobbered_p = allocate_stack_space (frame_off, last_probe_offset,
temp_reg);
if (TARGET_BACKCHAIN || next_fpr)
{
if (temp_reg_clobbered_p)
{
/* allocate_stack_space had to make use of temp_reg and
we need it to hold a backup of the incoming stack
pointer. Calculate back that value from the current
stack pointer. */
s390_prologue_plus_offset (temp_reg, stack_pointer_rtx,
GEN_INT (cfun_frame_layout.frame_size),
false);
}
else
{
/* allocate_stack_space didn't actually required
temp_reg. Insert the stack pointer backup insn
before the stack pointer decrement code - knowing now
that the value will survive. */
emit_insn_after (gen_move_insn (temp_reg, stack_pointer_rtx),
stack_pointer_backup_loc);
}
}
/* Set backchain. */
if (TARGET_BACKCHAIN)
{
if (cfun_frame_layout.backchain_offset)
addr = gen_rtx_MEM (Pmode,
plus_constant (Pmode, stack_pointer_rtx,
cfun_frame_layout.backchain_offset));
else
addr = gen_rtx_MEM (Pmode, stack_pointer_rtx);
set_mem_alias_set (addr, get_frame_alias_set ());
insn = emit_insn (gen_move_insn (addr, temp_reg));
}
/* If we support non-call exceptions (e.g. for Java),
we need to make sure the backchain pointer is set up
before any possibly trapping memory access. */
if (TARGET_BACKCHAIN && cfun->can_throw_non_call_exceptions)
{
addr = gen_rtx_MEM (BLKmode, gen_rtx_SCRATCH (VOIDmode));
emit_clobber (addr);
}
}
else if (flag_stack_clash_protection)
dump_stack_clash_frame_info (NO_PROBE_NO_FRAME, false);
/* Save fprs 8 - 15 (64 bit ABI). */
if (cfun_save_high_fprs_p && next_fpr)
{
/* If the stack might be accessed through a different register
we have to make sure that the stack pointer decrement is not
moved below the use of the stack slots. */
s390_emit_stack_tie ();
insn = emit_insn (gen_add2_insn (temp_reg,
GEN_INT (cfun_frame_layout.f8_offset)));
offset = 0;
for (i = FPR8_REGNUM; i <= next_fpr; i++)
if (cfun_fpr_save_p (i))
{
rtx addr = plus_constant (Pmode, stack_pointer_rtx,
cfun_frame_layout.frame_size
+ cfun_frame_layout.f8_offset
+ offset);
insn = save_fpr (temp_reg, offset, i);
offset += 8;
RTX_FRAME_RELATED_P (insn) = 1;
add_reg_note (insn, REG_FRAME_RELATED_EXPR,
gen_rtx_SET (gen_rtx_MEM (DFmode, addr),
gen_rtx_REG (DFmode, i)));
}
}
/* Set frame pointer, if needed. */
if (frame_pointer_needed)
{
insn = emit_move_insn (hard_frame_pointer_rtx, stack_pointer_rtx);
RTX_FRAME_RELATED_P (insn) = 1;
}
/* Set up got pointer, if needed. */
if (flag_pic && df_regs_ever_live_p (PIC_OFFSET_TABLE_REGNUM))
{
rtx_insn *insns = s390_load_got ();
for (rtx_insn *insn = insns; insn; insn = NEXT_INSN (insn))
annotate_constant_pool_refs (insn);
emit_insn (insns);
}
#if TARGET_TPF != 0
if (TARGET_TPF_PROFILING)
{
/* Generate a BAS instruction to serve as a function entry
intercept to facilitate the use of tracing algorithms located
at the branch target. */
emit_insn (gen_prologue_tpf (
GEN_INT (s390_tpf_trace_hook_prologue_check),
GEN_INT (s390_tpf_trace_hook_prologue_target)));
/* Emit a blockage here so that all code lies between the
profiling mechanisms. */
emit_insn (gen_blockage ());
}
#endif
}
/* Expand the epilogue into a bunch of separate insns. */
void
s390_emit_epilogue (bool sibcall)
{
rtx frame_pointer, return_reg = NULL_RTX, cfa_restores = NULL_RTX;
int area_bottom, area_top, offset = 0;
int next_offset;
int i;
#if TARGET_TPF != 0
if (TARGET_TPF_PROFILING)
{
/* Generate a BAS instruction to serve as a function entry
intercept to facilitate the use of tracing algorithms located
at the branch target. */
/* Emit a blockage here so that all code lies between the
profiling mechanisms. */
emit_insn (gen_blockage ());
emit_insn (gen_epilogue_tpf (
GEN_INT (s390_tpf_trace_hook_epilogue_check),
GEN_INT (s390_tpf_trace_hook_epilogue_target)));
}
#endif
/* Check whether to use frame or stack pointer for restore. */
frame_pointer = (frame_pointer_needed
? hard_frame_pointer_rtx : stack_pointer_rtx);
s390_frame_area (&area_bottom, &area_top);
/* Check whether we can access the register save area.
If not, increment the frame pointer as required. */
if (area_top <= area_bottom)
{
/* Nothing to restore. */
}
else if (DISP_IN_RANGE (cfun_frame_layout.frame_size + area_bottom)
&& DISP_IN_RANGE (cfun_frame_layout.frame_size + area_top - 1))
{
/* Area is in range. */
offset = cfun_frame_layout.frame_size;
}
else
{
rtx_insn *insn;
rtx frame_off, cfa;
offset = area_bottom < 0 ? -area_bottom : 0;
frame_off = GEN_INT (cfun_frame_layout.frame_size - offset);
cfa = gen_rtx_SET (frame_pointer,
gen_rtx_PLUS (Pmode, frame_pointer, frame_off));
if (DISP_IN_RANGE (INTVAL (frame_off)))
{
rtx set;
set = gen_rtx_SET (frame_pointer,
gen_rtx_PLUS (Pmode, frame_pointer, frame_off));
insn = emit_insn (set);
}
else
{
if (!CONST_OK_FOR_K (INTVAL (frame_off)))
frame_off = force_const_mem (Pmode, frame_off);
insn = emit_insn (gen_add2_insn (frame_pointer, frame_off));
annotate_constant_pool_refs (insn);
}
add_reg_note (insn, REG_CFA_ADJUST_CFA, cfa);
RTX_FRAME_RELATED_P (insn) = 1;
}
/* Restore call saved fprs. */
if (TARGET_64BIT)
{
if (cfun_save_high_fprs_p)
{
next_offset = cfun_frame_layout.f8_offset;
for (i = FPR8_REGNUM; i <= FPR15_REGNUM; i++)
{
if (cfun_fpr_save_p (i))
{
restore_fpr (frame_pointer,
offset + next_offset, i);
cfa_restores
= alloc_reg_note (REG_CFA_RESTORE,
gen_rtx_REG (DFmode, i), cfa_restores);
next_offset += 8;
}
}
}
}
else
{
next_offset = cfun_frame_layout.f4_offset;
/* f4, f6 */
for (i = FPR4_REGNUM; i <= FPR4_REGNUM + 1; i++)
{
if (cfun_fpr_save_p (i))
{
restore_fpr (frame_pointer,
offset + next_offset, i);
cfa_restores
= alloc_reg_note (REG_CFA_RESTORE,
gen_rtx_REG (DFmode, i), cfa_restores);
next_offset += 8;
}
else if (!TARGET_PACKED_STACK)
next_offset += 8;
}
}
/* Restore call saved gprs. */
if (cfun_frame_layout.first_restore_gpr != -1)
{
rtx insn, addr;
int i;
/* Check for global register and save them
to stack location from where they get restored. */
for (i = cfun_frame_layout.first_restore_gpr;
i <= cfun_frame_layout.last_restore_gpr;
i++)
{
if (global_not_special_regno_p (i))
{
addr = plus_constant (Pmode, frame_pointer,
offset + cfun_frame_layout.gprs_offset
+ (i - cfun_frame_layout.first_save_gpr_slot)
* UNITS_PER_LONG);
addr = gen_rtx_MEM (Pmode, addr);
set_mem_alias_set (addr, get_frame_alias_set ());
emit_move_insn (addr, gen_rtx_REG (Pmode, i));
}
else
cfa_restores
= alloc_reg_note (REG_CFA_RESTORE,
gen_rtx_REG (Pmode, i), cfa_restores);
}
/* Fetch return address from stack before load multiple,
this will do good for scheduling.
Only do this if we already decided that r14 needs to be
saved to a stack slot. (And not just because r14 happens to
be in between two GPRs which need saving.) Otherwise it
would be difficult to take that decision back in
s390_optimize_prologue.
This optimization is only helpful on in-order machines. */
if (! sibcall
&& cfun_gpr_save_slot (RETURN_REGNUM) == SAVE_SLOT_STACK
&& s390_tune <= PROCESSOR_2097_Z10)
{
int return_regnum = find_unused_clobbered_reg();
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,
offset + cfun_frame_layout.gprs_offset
+ (RETURN_REGNUM
- cfun_frame_layout.first_save_gpr_slot)
* UNITS_PER_LONG);
addr = gen_rtx_MEM (Pmode, addr);
set_mem_alias_set (addr, get_frame_alias_set ());
emit_move_insn (return_reg, addr);
/* Once we did that optimization we have to make sure
s390_optimize_prologue does not try to remove the store
of r14 since we will not be able to find the load issued
here. */
cfun_frame_layout.save_return_addr_p = true;
}
insn = restore_gprs (frame_pointer,
offset + cfun_frame_layout.gprs_offset
+ (cfun_frame_layout.first_restore_gpr
- cfun_frame_layout.first_save_gpr_slot)
* UNITS_PER_LONG,
cfun_frame_layout.first_restore_gpr,
cfun_frame_layout.last_restore_gpr);
insn = emit_insn (insn);
REG_NOTES (insn) = cfa_restores;
add_reg_note (insn, REG_CFA_DEF_CFA,
plus_constant (Pmode, stack_pointer_rtx,
STACK_POINTER_OFFSET));
RTX_FRAME_RELATED_P (insn) = 1;
}
s390_restore_gprs_from_fprs ();
if (! sibcall)
{
if (!return_reg && !s390_can_use_return_insn ())
/* We planned to emit (return), be we are not allowed to. */
return_reg = gen_rtx_REG (Pmode, RETURN_REGNUM);
if (return_reg)
/* Emit (return) and (use). */
emit_jump_insn (gen_return_use (return_reg));
else
/* The fact that RETURN_REGNUM is used is already reflected by
EPILOGUE_USES. Emit plain (return). */
emit_jump_insn (gen_return ());
}
}
/* Implement TARGET_SET_UP_BY_PROLOGUE. */
static void
s300_set_up_by_prologue (hard_reg_set_container *regs)
{
if (cfun->machine->base_reg
&& !call_used_regs[REGNO (cfun->machine->base_reg)])
SET_HARD_REG_BIT (regs->set, REGNO (cfun->machine->base_reg));
}
/* -fsplit-stack support. */
/* A SYMBOL_REF for __morestack. */
static GTY(()) rtx morestack_ref;
/* When using -fsplit-stack, the allocation routines set a field in
the TCB to the bottom of the stack plus this much space, measured
in bytes. */
#define SPLIT_STACK_AVAILABLE 1024
/* Emit the parmblock for __morestack into .rodata section. It
consists of 3 pointer size entries:
- frame size
- size of stack arguments
- offset between parm block and __morestack return label */
void
s390_output_split_stack_data (rtx parm_block, rtx call_done,
rtx frame_size, rtx args_size)
{
rtx ops[] = { parm_block, call_done };
switch_to_section (targetm.asm_out.function_rodata_section
(current_function_decl, false));
if (TARGET_64BIT)
output_asm_insn (".align\t8", NULL);
else
output_asm_insn (".align\t4", NULL);
(*targetm.asm_out.internal_label) (asm_out_file, "L",
CODE_LABEL_NUMBER (parm_block));
if (TARGET_64BIT)
{
output_asm_insn (".quad\t%0", &frame_size);
output_asm_insn (".quad\t%0", &args_size);
output_asm_insn (".quad\t%1-%0", ops);
}
else
{
output_asm_insn (".long\t%0", &frame_size);
output_asm_insn (".long\t%0", &args_size);
output_asm_insn (".long\t%1-%0", ops);
}
switch_to_section (current_function_section ());
}
/* Emit -fsplit-stack prologue, which goes before the regular function
prologue. */
void
s390_expand_split_stack_prologue (void)
{
rtx r1, guard, cc = NULL;
rtx_insn *insn;
/* Offset from thread pointer to __private_ss. */
int psso = TARGET_64BIT ? 0x38 : 0x20;
/* Pointer size in bytes. */
/* Frame size and argument size - the two parameters to __morestack. */
HOST_WIDE_INT frame_size = cfun_frame_layout.frame_size;
/* Align argument size to 8 bytes - simplifies __morestack code. */
HOST_WIDE_INT args_size = crtl->args.size >= 0
? ((crtl->args.size + 7) & ~7)
: 0;
/* Label to be called by __morestack. */
rtx_code_label *call_done = NULL;
rtx_code_label *parm_base = NULL;
rtx tmp;
gcc_assert (flag_split_stack && reload_completed);
r1 = gen_rtx_REG (Pmode, 1);
/* If no stack frame will be allocated, don't do anything. */
if (!frame_size)
{
if (cfun->machine->split_stack_varargs_pointer != NULL_RTX)
{
/* If va_start is used, just use r15. */
emit_move_insn (r1,
gen_rtx_PLUS (Pmode, stack_pointer_rtx,
GEN_INT (STACK_POINTER_OFFSET)));
}
return;
}
if (morestack_ref == NULL_RTX)
{
morestack_ref = gen_rtx_SYMBOL_REF (Pmode, "__morestack");
SYMBOL_REF_FLAGS (morestack_ref) |= (SYMBOL_FLAG_LOCAL
| SYMBOL_FLAG_FUNCTION);
}
if (CONST_OK_FOR_K (frame_size) || CONST_OK_FOR_Op (frame_size))
{
/* If frame_size will fit in an add instruction, do a stack space
check, and only call __morestack if there's not enough space. */
/* Get thread pointer. r1 is the only register we can always destroy - r0
could contain a static chain (and cannot be used to address memory
anyway), r2-r6 can contain parameters, and r6-r15 are callee-saved. */
emit_insn (gen_get_thread_pointer (Pmode, r1));
/* Aim at __private_ss. */
guard = gen_rtx_MEM (Pmode, plus_constant (Pmode, r1, psso));
/* If less that 1kiB used, skip addition and compare directly with
__private_ss. */
if (frame_size > SPLIT_STACK_AVAILABLE)
{
emit_move_insn (r1, guard);
if (TARGET_64BIT)
emit_insn (gen_adddi3 (r1, r1, GEN_INT (frame_size)));
else
emit_insn (gen_addsi3 (r1, r1, GEN_INT (frame_size)));
guard = r1;
}
/* Compare the (maybe adjusted) guard with the stack pointer. */
cc = s390_emit_compare (LT, stack_pointer_rtx, guard);
}
call_done = gen_label_rtx ();
parm_base = gen_label_rtx ();
LABEL_NUSES (parm_base)++;
LABEL_NUSES (call_done)++;
/* %r1 = litbase. */
insn = emit_move_insn (r1, gen_rtx_LABEL_REF (VOIDmode, parm_base));
add_reg_note (insn, REG_LABEL_OPERAND, parm_base);
LABEL_NUSES (parm_base)++;
/* Now, we need to call __morestack. It has very special calling
conventions: it preserves param/return/static chain registers for
calling main function body, and looks for its own parameters at %r1. */
if (cc != NULL)
tmp = gen_split_stack_cond_call (Pmode,
morestack_ref,
parm_base,
call_done,
GEN_INT (frame_size),
GEN_INT (args_size),
cc);
else
tmp = gen_split_stack_call (Pmode,
morestack_ref,
parm_base,
call_done,
GEN_INT (frame_size),
GEN_INT (args_size));
insn = emit_jump_insn (tmp);
JUMP_LABEL (insn) = call_done;
add_reg_note (insn, REG_LABEL_OPERAND, parm_base);
add_reg_note (insn, REG_LABEL_OPERAND, call_done);
if (cc != NULL)
{
/* Mark the jump as very unlikely to be taken. */
add_reg_br_prob_note (insn,
profile_probability::very_unlikely ());
if (cfun->machine->split_stack_varargs_pointer != NULL_RTX)
{
/* If va_start is used, and __morestack was not called, just use
r15. */
emit_move_insn (r1,
gen_rtx_PLUS (Pmode, stack_pointer_rtx,
GEN_INT (STACK_POINTER_OFFSET)));
}
}
else
{
emit_barrier ();
}
/* __morestack will call us here. */
emit_label (call_done);
}
/* We may have to tell the dataflow pass that the split stack prologue
is initializing a register. */
static void
s390_live_on_entry (bitmap regs)
{
if (cfun->machine->split_stack_varargs_pointer != NULL_RTX)
{
gcc_assert (flag_split_stack);
bitmap_set_bit (regs, 1);
}
}
/* Return true if the function can use simple_return to return outside
of a shrink-wrapped region. At present shrink-wrapping is supported
in all cases. */
bool
s390_can_use_simple_return_insn (void)
{
return true;
}
/* Return true if the epilogue is guaranteed to contain only a return
instruction and if a direct return can therefore be used instead.
One of the main advantages of using direct return instructions
is that we can then use conditional returns. */
bool
s390_can_use_return_insn (void)
{
int i;
if (!reload_completed)
return false;
if (crtl->profile)
return false;
if (TARGET_TPF_PROFILING)
return false;
for (i = 0; i < 16; i++)
if (cfun_gpr_save_slot (i) != SAVE_SLOT_NONE)
return false;
/* For 31 bit this is not covered by the frame_size check below
since f4, f6 are saved in the register save area without needing
additional stack space. */
if (!TARGET_64BIT
&& (cfun_fpr_save_p (FPR4_REGNUM) || cfun_fpr_save_p (FPR6_REGNUM)))
return false;
if (cfun->machine->base_reg
&& !call_used_regs[REGNO (cfun->machine->base_reg)])
return false;
return cfun_frame_layout.frame_size == 0;
}
/* The VX ABI differs for vararg functions. Therefore we need the
prototype of the callee to be available when passing vector type
values. */
static const char *
s390_invalid_arg_for_unprototyped_fn (const_tree typelist, const_tree funcdecl, const_tree val)
{
return ((TARGET_VX_ABI
&& typelist == 0
&& VECTOR_TYPE_P (TREE_TYPE (val))
&& (funcdecl == NULL_TREE
|| (TREE_CODE (funcdecl) == FUNCTION_DECL
&& DECL_BUILT_IN_CLASS (funcdecl) != BUILT_IN_MD)))
? N_("vector argument passed to unprototyped function")
: NULL);
}
/* Return the size in bytes of a function argument of
type TYPE and/or mode MODE. At least one of TYPE or
MODE must be specified. */
static int
s390_function_arg_size (machine_mode mode, const_tree type)
{
if (type)
return int_size_in_bytes (type);
/* No type info available for some library calls ... */
if (mode != BLKmode)
return GET_MODE_SIZE (mode);
/* If we have neither type nor mode, abort */
gcc_unreachable ();
}
/* Return true if a function argument of type TYPE and mode MODE
is to be passed in a vector register, if available. */
bool
s390_function_arg_vector (machine_mode mode, const_tree type)
{
if (!TARGET_VX_ABI)
return false;
if (s390_function_arg_size (mode, type) > 16)
return false;
/* No type info available for some library calls ... */
if (!type)
return VECTOR_MODE_P (mode);
/* The ABI says that record types with a single member are treated
just like that member would be. */
int empty_base_seen = 0;
const_tree orig_type = type;
while (TREE_CODE (type) == RECORD_TYPE)
{
tree field, single = NULL_TREE;
for (field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
{
if (TREE_CODE (field) != FIELD_DECL)
continue;
if (DECL_FIELD_ABI_IGNORED (field))
{
if (lookup_attribute ("no_unique_address",
DECL_ATTRIBUTES (field)))
empty_base_seen |= 2;
else
empty_base_seen |= 1;
continue;
}
if (single == NULL_TREE)
single = TREE_TYPE (field);
else
return false;
}
if (single == NULL_TREE)
return false;
else
{
/* If the field declaration adds extra byte due to
e.g. padding this is not accepted as vector type. */
if (int_size_in_bytes (single) <= 0
|| int_size_in_bytes (single) != int_size_in_bytes (type))
return false;
type = single;
}
}
if (!VECTOR_TYPE_P (type))
return false;
if (warn_psabi && empty_base_seen)
{
static unsigned last_reported_type_uid;
unsigned uid = TYPE_UID (TYPE_MAIN_VARIANT (orig_type));
if (uid != last_reported_type_uid)
{
const char *url = CHANGES_ROOT_URL "gcc-10/changes.html#empty_base";
last_reported_type_uid = uid;
if (empty_base_seen & 1)
inform (input_location,
"parameter passing for argument of type %qT when C++17 "
"is enabled changed to match C++14 %{in GCC 10.1%}",
orig_type, url);
else
inform (input_location,
"parameter passing for argument of type %qT with "
"%<[[no_unique_address]]%> members changed "
"%{in GCC 10.1%}", orig_type, url);
}
}
return true;
}
/* Return true if a function argument of type TYPE and mode MODE
is to be passed in a floating-point register, if available. */
static bool
s390_function_arg_float (machine_mode mode, const_tree type)
{
if (s390_function_arg_size (mode, type) > 8)
return false;
/* Soft-float changes the ABI: no floating-point registers are used. */
if (TARGET_SOFT_FLOAT)
return false;
/* No type info available for some library calls ... */
if (!type)
return mode == SFmode || mode == DFmode || mode == SDmode || mode == DDmode;
/* The ABI says that record types with a single member are treated
just like that member would be. */
int empty_base_seen = 0;
const_tree orig_type = type;
while (TREE_CODE (type) == RECORD_TYPE)
{
tree field, single = NULL_TREE;
for (field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
{
if (TREE_CODE (field) != FIELD_DECL)
continue;
if (DECL_FIELD_ABI_IGNORED (field))
{
if (lookup_attribute ("no_unique_address",
DECL_ATTRIBUTES (field)))
empty_base_seen |= 2;
else
empty_base_seen |= 1;
continue;
}
if (single == NULL_TREE)
single = TREE_TYPE (field);
else
return false;
}
if (single == NULL_TREE)
return false;
else
type = single;
}
if (TREE_CODE (type) != REAL_TYPE)
return false;
if (warn_psabi && empty_base_seen)
{
static unsigned last_reported_type_uid;
unsigned uid = TYPE_UID (TYPE_MAIN_VARIANT (orig_type));
if (uid != last_reported_type_uid)
{
const char *url = CHANGES_ROOT_URL "gcc-10/changes.html#empty_base";
last_reported_type_uid = uid;
if (empty_base_seen & 1)
inform (input_location,
"parameter passing for argument of type %qT when C++17 "
"is enabled changed to match C++14 %{in GCC 10.1%}",
orig_type, url);
else
inform (input_location,
"parameter passing for argument of type %qT with "
"%<[[no_unique_address]]%> members changed "
"%{in GCC 10.1%}", orig_type, url);
}
}
return true;
}
/* Return true if a function argument of type TYPE and mode MODE
is to be passed in an integer register, or a pair of integer
registers, if available. */
static bool
s390_function_arg_integer (machine_mode mode, const_tree type)
{
int size = s390_function_arg_size (mode, type);
if (size > 8)
return false;
/* No type info available for some library calls ... */
if (!type)
return GET_MODE_CLASS (mode) == MODE_INT
|| (TARGET_SOFT_FLOAT && SCALAR_FLOAT_MODE_P (mode));
/* We accept small integral (and similar) types. */
if (INTEGRAL_TYPE_P (type)
|| POINTER_TYPE_P (type)
|| TREE_CODE (type) == NULLPTR_TYPE
|| TREE_CODE (type) == OFFSET_TYPE
|| (TARGET_SOFT_FLOAT && TREE_CODE (type) == REAL_TYPE))
return true;
/* We also accept structs of size 1, 2, 4, 8 that are not
passed in floating-point registers. */
if (AGGREGATE_TYPE_P (type)
&& exact_log2 (size) >= 0
&& !s390_function_arg_float (mode, type))
return true;
return false;
}
/* Return 1 if a function argument ARG is to be passed by reference.
The ABI specifies that only structures of size 1, 2, 4, or 8 bytes
are passed by value, all other structures (and complex numbers) are
passed by reference. */
static bool
s390_pass_by_reference (cumulative_args_t, const function_arg_info &arg)
{
int size = s390_function_arg_size (arg.mode, arg.type);
if (s390_function_arg_vector (arg.mode, arg.type))
return false;
if (size > 8)
return true;
if (tree type = arg.type)
{
if (AGGREGATE_TYPE_P (type) && exact_log2 (size) < 0)
return true;
if (TREE_CODE (type) == COMPLEX_TYPE
|| TREE_CODE (type) == VECTOR_TYPE)
return true;
}
return false;
}
/* Update the data in CUM to advance over argument ARG. */
static void
s390_function_arg_advance (cumulative_args_t cum_v,
const function_arg_info &arg)
{
CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
if (s390_function_arg_vector (arg.mode, arg.type))
{
/* We are called for unnamed vector stdarg arguments which are
passed on the stack. In this case this hook does not have to
do anything since stack arguments are tracked by common
code. */
if (!arg.named)
return;
cum->vrs += 1;
}
else if (s390_function_arg_float (arg.mode, arg.type))
{
cum->fprs += 1;
}
else if (s390_function_arg_integer (arg.mode, arg.type))
{
int size = s390_function_arg_size (arg.mode, arg.type);
cum->gprs += ((size + UNITS_PER_LONG - 1) / UNITS_PER_LONG);
}
else
gcc_unreachable ();
}
/* Define where to put the arguments to a function.
Value is zero to push the argument on the stack,
or a hard register in which to store the argument.
CUM is a variable of type CUMULATIVE_ARGS which gives info about
the preceding args and about the function being called.
ARG is a description of the argument.
On S/390, we use general purpose registers 2 through 6 to
pass integer, pointer, and certain structure arguments, and
floating point registers 0 and 2 (0, 2, 4, and 6 on 64-bit)
to pass floating point arguments. All remaining arguments
are pushed to the stack. */
static rtx
s390_function_arg (cumulative_args_t cum_v, const function_arg_info &arg)
{
CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
if (!arg.named)
s390_check_type_for_vector_abi (arg.type, true, false);
if (s390_function_arg_vector (arg.mode, arg.type))
{
/* Vector arguments being part of the ellipsis are passed on the
stack. */
if (!arg.named || (cum->vrs + 1 > VEC_ARG_NUM_REG))
return NULL_RTX;
return gen_rtx_REG (arg.mode, cum->vrs + FIRST_VEC_ARG_REGNO);
}
else if (s390_function_arg_float (arg.mode, arg.type))
{
if (cum->fprs + 1 > FP_ARG_NUM_REG)
return NULL_RTX;
else
return gen_rtx_REG (arg.mode, cum->fprs + 16);
}
else if (s390_function_arg_integer (arg.mode, arg.type))
{
int size = s390_function_arg_size (arg.mode, arg.type);
int n_gprs = (size + UNITS_PER_LONG - 1) / UNITS_PER_LONG;
if (cum->gprs + n_gprs > GP_ARG_NUM_REG)
return NULL_RTX;
else if (n_gprs == 1 || UNITS_PER_WORD == UNITS_PER_LONG)
return gen_rtx_REG (arg.mode, cum->gprs + 2);
else if (n_gprs == 2)
{
rtvec p = rtvec_alloc (2);
RTVEC_ELT (p, 0)
= gen_rtx_EXPR_LIST (SImode, gen_rtx_REG (SImode, cum->gprs + 2),
const0_rtx);
RTVEC_ELT (p, 1)
= gen_rtx_EXPR_LIST (SImode, gen_rtx_REG (SImode, cum->gprs + 3),
GEN_INT (4));
return gen_rtx_PARALLEL (arg.mode, p);
}
}
/* After the real arguments, expand_call calls us once again with an
end marker. Whatever we return here is passed as operand 2 to the
call expanders.
We don't need this feature ... */
else if (arg.end_marker_p ())
return const0_rtx;
gcc_unreachable ();
}
/* Implement TARGET_FUNCTION_ARG_BOUNDARY. Vector arguments are
left-justified when placed on the stack during parameter passing. */
static pad_direction
s390_function_arg_padding (machine_mode mode, const_tree type)
{
if (s390_function_arg_vector (mode, type))
return PAD_UPWARD;
return default_function_arg_padding (mode, type);
}
/* Return true if return values of type TYPE should be returned
in a memory buffer whose address is passed by the caller as
hidden first argument. */
static bool
s390_return_in_memory (const_tree type, const_tree fundecl ATTRIBUTE_UNUSED)
{
/* We accept small integral (and similar) types. */
if (INTEGRAL_TYPE_P (type)
|| POINTER_TYPE_P (type)
|| TREE_CODE (type) == OFFSET_TYPE
|| TREE_CODE (type) == REAL_TYPE)
return int_size_in_bytes (type) > 8;
/* vector types which fit into a VR. */
if (TARGET_VX_ABI
&& VECTOR_TYPE_P (type)
&& int_size_in_bytes (type) <= 16)
return false;
/* Aggregates and similar constructs are always returned
in memory. */
if (AGGREGATE_TYPE_P (type)
|| TREE_CODE (type) == COMPLEX_TYPE
|| VECTOR_TYPE_P (type))
return true;
/* ??? We get called on all sorts of random stuff from
aggregate_value_p. We can't abort, but it's not clear
what's safe to return. Pretend it's a struct I guess. */
return true;
}
/* Function arguments and return values are promoted to word size. */
static machine_mode
s390_promote_function_mode (const_tree type, machine_mode mode,
int *punsignedp,
const_tree fntype ATTRIBUTE_UNUSED,
int for_return ATTRIBUTE_UNUSED)
{
if (INTEGRAL_MODE_P (mode)
&& GET_MODE_SIZE (mode) < UNITS_PER_LONG)
{
if (type != NULL_TREE && POINTER_TYPE_P (type))
*punsignedp = POINTERS_EXTEND_UNSIGNED;
return Pmode;
}
return mode;
}
/* Define where to return a (scalar) value of type RET_TYPE.
If RET_TYPE is null, define where to return a (scalar)
value of mode MODE from a libcall. */
static rtx
s390_function_and_libcall_value (machine_mode mode,
const_tree ret_type,
const_tree fntype_or_decl,
bool outgoing ATTRIBUTE_UNUSED)
{
/* For vector return types it is important to use the RET_TYPE
argument whenever available since the middle-end might have
changed the mode to a scalar mode. */
bool vector_ret_type_p = ((ret_type && VECTOR_TYPE_P (ret_type))
|| (!ret_type && VECTOR_MODE_P (mode)));
/* For normal functions perform the promotion as
promote_function_mode would do. */
if (ret_type)
{
int unsignedp = TYPE_UNSIGNED (ret_type);
mode = promote_function_mode (ret_type, mode, &unsignedp,
fntype_or_decl, 1);
}
gcc_assert (GET_MODE_CLASS (mode) == MODE_INT
|| SCALAR_FLOAT_MODE_P (mode)
|| (TARGET_VX_ABI && vector_ret_type_p));
gcc_assert (GET_MODE_SIZE (mode) <= (TARGET_VX_ABI ? 16 : 8));
if (TARGET_VX_ABI && vector_ret_type_p)
return gen_rtx_REG (mode, FIRST_VEC_ARG_REGNO);
else if (TARGET_HARD_FLOAT && SCALAR_FLOAT_MODE_P (mode))
return gen_rtx_REG (mode, 16);
else if (GET_MODE_SIZE (mode) <= UNITS_PER_LONG
|| UNITS_PER_LONG == UNITS_PER_WORD)
return gen_rtx_REG (mode, 2);
else if (GET_MODE_SIZE (mode) == 2 * UNITS_PER_LONG)
{
/* This case is triggered when returning a 64 bit value with
-m31 -mzarch. Although the value would fit into a single
register it has to be forced into a 32 bit register pair in
order to match the ABI. */
rtvec p = rtvec_alloc (2);
RTVEC_ELT (p, 0)
= gen_rtx_EXPR_LIST (SImode, gen_rtx_REG (SImode, 2), const0_rtx);
RTVEC_ELT (p, 1)
= gen_rtx_EXPR_LIST (SImode, gen_rtx_REG (SImode, 3), GEN_INT (4));
return gen_rtx_PARALLEL (mode, p);
}
gcc_unreachable ();
}
/* Define where to return a scalar return value of type RET_TYPE. */
static rtx
s390_function_value (const_tree ret_type, const_tree fn_decl_or_type,
bool outgoing)
{
return s390_function_and_libcall_value (TYPE_MODE (ret_type), ret_type,
fn_decl_or_type, outgoing);
}
/* Define where to return a scalar libcall return value of mode
MODE. */
static rtx
s390_libcall_value (machine_mode mode, const_rtx fun ATTRIBUTE_UNUSED)
{
return s390_function_and_libcall_value (mode, NULL_TREE,
NULL_TREE, true);
}
/* Create and return the va_list datatype.
On S/390, va_list is an array type equivalent to
typedef struct __va_list_tag
{
long __gpr;
long __fpr;
void *__overflow_arg_area;
void *__reg_save_area;
} va_list[1];
where __gpr and __fpr hold the number of general purpose
or floating point arguments used up to now, respectively,
__overflow_arg_area points to the stack location of the
next argument passed on the stack, and __reg_save_area
always points to the start of the register area in the
call frame of the current function. The function prologue
saves all registers used for argument passing into this
area if the function uses variable arguments. */
static tree
s390_build_builtin_va_list (void)
{
tree f_gpr, f_fpr, f_ovf, f_sav, record, type_decl;
record = lang_hooks.types.make_type (RECORD_TYPE);
type_decl =
build_decl (BUILTINS_LOCATION,
TYPE_DECL, get_identifier ("__va_list_tag"), record);
f_gpr = build_decl (BUILTINS_LOCATION,
FIELD_DECL, get_identifier ("__gpr"),
long_integer_type_node);
f_fpr = build_decl (BUILTINS_LOCATION,
FIELD_DECL, get_identifier ("__fpr"),
long_integer_type_node);
f_ovf = build_decl (BUILTINS_LOCATION,
FIELD_DECL, get_identifier ("__overflow_arg_area"),
ptr_type_node);
f_sav = build_decl (BUILTINS_LOCATION,
FIELD_DECL, get_identifier ("__reg_save_area"),
ptr_type_node);
va_list_gpr_counter_field = f_gpr;
va_list_fpr_counter_field = f_fpr;
DECL_FIELD_CONTEXT (f_gpr) = record;
DECL_FIELD_CONTEXT (f_fpr) = record;
DECL_FIELD_CONTEXT (f_ovf) = record;
DECL_FIELD_CONTEXT (f_sav) = record;
TYPE_STUB_DECL (record) = type_decl;
TYPE_NAME (record) = type_decl;
TYPE_FIELDS (record) = f_gpr;
DECL_CHAIN (f_gpr) = f_fpr;
DECL_CHAIN (f_fpr) = f_ovf;
DECL_CHAIN (f_ovf) = f_sav;
layout_type (record);
/* The correct type is an array type of one element. */
return build_array_type (record, build_index_type (size_zero_node));
}
/* Implement va_start by filling the va_list structure VALIST.
STDARG_P is always true, and ignored.
NEXTARG points to the first anonymous stack argument.
The following global variables are used to initialize
the va_list structure:
crtl->args.info:
holds number of gprs and fprs used for named arguments.
crtl->args.arg_offset_rtx:
holds the offset of the first anonymous stack argument
(relative to the virtual arg pointer). */
static void
s390_va_start (tree valist, rtx nextarg ATTRIBUTE_UNUSED)
{
HOST_WIDE_INT n_gpr, n_fpr;
int off;
tree f_gpr, f_fpr, f_ovf, f_sav;
tree gpr, fpr, ovf, sav, t;
f_gpr = TYPE_FIELDS (TREE_TYPE (va_list_type_node));
f_fpr = DECL_CHAIN (f_gpr);
f_ovf = DECL_CHAIN (f_fpr);
f_sav = DECL_CHAIN (f_ovf);
valist = build_simple_mem_ref (valist);
gpr = build3 (COMPONENT_REF, TREE_TYPE (f_gpr), valist, f_gpr, NULL_TREE);
fpr = build3 (COMPONENT_REF, TREE_TYPE (f_fpr), valist, f_fpr, NULL_TREE);
ovf = build3 (COMPONENT_REF, TREE_TYPE (f_ovf), valist, f_ovf, NULL_TREE);
sav = build3 (COMPONENT_REF, TREE_TYPE (f_sav), valist, f_sav, NULL_TREE);
/* Count number of gp and fp argument registers used. */
n_gpr = crtl->args.info.gprs;
n_fpr = crtl->args.info.fprs;
if (cfun->va_list_gpr_size)
{
t = build2 (MODIFY_EXPR, TREE_TYPE (gpr), gpr,
build_int_cst (NULL_TREE, n_gpr));
TREE_SIDE_EFFECTS (t) = 1;
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
}
if (cfun->va_list_fpr_size)
{
t = build2 (MODIFY_EXPR, TREE_TYPE (fpr), fpr,
build_int_cst (NULL_TREE, n_fpr));
TREE_SIDE_EFFECTS (t) = 1;
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
}
if (flag_split_stack
&& (lookup_attribute ("no_split_stack", DECL_ATTRIBUTES (cfun->decl))
== NULL)
&& cfun->machine->split_stack_varargs_pointer == NULL_RTX)
{
rtx reg;
rtx_insn *seq;
reg = gen_reg_rtx (Pmode);
cfun->machine->split_stack_varargs_pointer = reg;
start_sequence ();
emit_move_insn (reg, gen_rtx_REG (Pmode, 1));
seq = get_insns ();
end_sequence ();
push_topmost_sequence ();
emit_insn_after (seq, entry_of_function ());
pop_topmost_sequence ();
}
/* Find the overflow area.
FIXME: This currently is too pessimistic when the vector ABI is
enabled. In that case we *always* set up the overflow area
pointer. */
if (n_gpr + cfun->va_list_gpr_size > GP_ARG_NUM_REG
|| n_fpr + cfun->va_list_fpr_size > FP_ARG_NUM_REG
|| TARGET_VX_ABI)
{
if (cfun->machine->split_stack_varargs_pointer == NULL_RTX)
t = make_tree (TREE_TYPE (ovf), virtual_incoming_args_rtx);
else
t = make_tree (TREE_TYPE (ovf), cfun->machine->split_stack_varargs_pointer);
off = INTVAL (crtl->args.arg_offset_rtx);
off = off < 0 ? 0 : off;
if (TARGET_DEBUG_ARG)
fprintf (stderr, "va_start: n_gpr = %d, n_fpr = %d off %d\n",
(int)n_gpr, (int)n_fpr, off);
t = fold_build_pointer_plus_hwi (t, off);
t = build2 (MODIFY_EXPR, TREE_TYPE (ovf), ovf, t);
TREE_SIDE_EFFECTS (t) = 1;
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
}
/* Find the register save area. */
if ((cfun->va_list_gpr_size && n_gpr < GP_ARG_NUM_REG)
|| (cfun->va_list_fpr_size && n_fpr < FP_ARG_NUM_REG))
{
t = make_tree (TREE_TYPE (sav), return_address_pointer_rtx);
t = fold_build_pointer_plus_hwi (t, -RETURN_REGNUM * UNITS_PER_LONG);
t = build2 (MODIFY_EXPR, TREE_TYPE (sav), sav, t);
TREE_SIDE_EFFECTS (t) = 1;
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
}
}
/* Implement va_arg by updating the va_list structure
VALIST as required to retrieve an argument of type
TYPE, and returning that argument.
Generates code equivalent to:
if (integral value) {
if (size <= 4 && args.gpr < 5 ||
size > 4 && args.gpr < 4 )
ret = args.reg_save_area[args.gpr+8]
else
ret = *args.overflow_arg_area++;
} else if (vector value) {
ret = *args.overflow_arg_area;
args.overflow_arg_area += size / 8;
} else if (float value) {
if (args.fgpr < 2)
ret = args.reg_save_area[args.fpr+64]
else
ret = *args.overflow_arg_area++;
} else if (aggregate value) {
if (args.gpr < 5)
ret = *args.reg_save_area[args.gpr]
else
ret = **args.overflow_arg_area++;
} */
static tree
s390_gimplify_va_arg (tree valist, tree type, gimple_seq *pre_p,
gimple_seq *post_p ATTRIBUTE_UNUSED)
{
tree f_gpr, f_fpr, f_ovf, f_sav;
tree gpr, fpr, ovf, sav, reg, t, u;
int indirect_p, size, n_reg, sav_ofs, sav_scale, max_reg;
tree lab_false, lab_over = NULL_TREE;
tree addr = create_tmp_var (ptr_type_node, "addr");
bool left_align_p; /* How a value < UNITS_PER_LONG is aligned within
a stack slot. */
f_gpr = TYPE_FIELDS (TREE_TYPE (va_list_type_node));
f_fpr = DECL_CHAIN (f_gpr);
f_ovf = DECL_CHAIN (f_fpr);
f_sav = DECL_CHAIN (f_ovf);
gpr = build3 (COMPONENT_REF, TREE_TYPE (f_gpr), valist, f_gpr, NULL_TREE);
fpr = build3 (COMPONENT_REF, TREE_TYPE (f_fpr), valist, f_fpr, NULL_TREE);
sav = build3 (COMPONENT_REF, TREE_TYPE (f_sav), valist, f_sav, NULL_TREE);
/* The tree for args* cannot be shared between gpr/fpr and ovf since
both appear on a lhs. */
valist = unshare_expr (valist);
ovf = build3 (COMPONENT_REF, TREE_TYPE (f_ovf), valist, f_ovf, NULL_TREE);
size = int_size_in_bytes (type);
s390_check_type_for_vector_abi (type, true, false);
if (pass_va_arg_by_reference (type))
{
if (TARGET_DEBUG_ARG)
{
fprintf (stderr, "va_arg: aggregate type");
debug_tree (type);
}
/* Aggregates are passed by reference. */
indirect_p = 1;
reg = gpr;
n_reg = 1;
/* kernel stack layout on 31 bit: It is assumed here that no padding
will be added by s390_frame_info because for va_args always an even
number of gprs has to be saved r15-r2 = 14 regs. */
sav_ofs = 2 * UNITS_PER_LONG;
sav_scale = UNITS_PER_LONG;
size = UNITS_PER_LONG;
max_reg = GP_ARG_NUM_REG - n_reg;
left_align_p = false;
}
else if (s390_function_arg_vector (TYPE_MODE (type), type))
{
if (TARGET_DEBUG_ARG)
{
fprintf (stderr, "va_arg: vector type");
debug_tree (type);
}
indirect_p = 0;
reg = NULL_TREE;
n_reg = 0;
sav_ofs = 0;
sav_scale = 8;
max_reg = 0;
left_align_p = true;
}
else if (s390_function_arg_float (TYPE_MODE (type), type))
{
if (TARGET_DEBUG_ARG)
{
fprintf (stderr, "va_arg: float type");
debug_tree (type);
}
/* FP args go in FP registers, if present. */
indirect_p = 0;
reg = fpr;
n_reg = 1;
sav_ofs = 16 * UNITS_PER_LONG;
sav_scale = 8;
max_reg = FP_ARG_NUM_REG - n_reg;
left_align_p = false;
}
else
{
if (TARGET_DEBUG_ARG)
{
fprintf (stderr, "va_arg: other type");
debug_tree (type);
}
/* Otherwise into GP registers. */
indirect_p = 0;
reg = gpr;
n_reg = (size + UNITS_PER_LONG - 1) / UNITS_PER_LONG;
/* kernel stack layout on 31 bit: It is assumed here that no padding
will be added by s390_frame_info because for va_args always an even
number of gprs has to be saved r15-r2 = 14 regs. */
sav_ofs = 2 * UNITS_PER_LONG;
if (size < UNITS_PER_LONG)
sav_ofs += UNITS_PER_LONG - size;
sav_scale = UNITS_PER_LONG;
max_reg = GP_ARG_NUM_REG - n_reg;
left_align_p = false;
}
/* Pull the value out of the saved registers ... */
if (reg != NULL_TREE)
{
/*
if (reg > ((typeof (reg))max_reg))
goto lab_false;
addr = sav + sav_ofs + reg * save_scale;
goto lab_over;
lab_false:
*/
lab_false = create_artificial_label (UNKNOWN_LOCATION);
lab_over = create_artificial_label (UNKNOWN_LOCATION);
t = fold_convert (TREE_TYPE (reg), size_int (max_reg));
t = build2 (GT_EXPR, boolean_type_node, reg, t);
u = build1 (GOTO_EXPR, void_type_node, lab_false);
t = build3 (COND_EXPR, void_type_node, t, u, NULL_TREE);
gimplify_and_add (t, pre_p);
t = fold_build_pointer_plus_hwi (sav, sav_ofs);
u = build2 (MULT_EXPR, TREE_TYPE (reg), reg,
fold_convert (TREE_TYPE (reg), size_int (sav_scale)));
t = fold_build_pointer_plus (t, u);
gimplify_assign (addr, t, pre_p);
gimple_seq_add_stmt (pre_p, gimple_build_goto (lab_over));
gimple_seq_add_stmt (pre_p, gimple_build_label (lab_false));
}
/* ... Otherwise out of the overflow area. */
t = ovf;
if (size < UNITS_PER_LONG && !left_align_p)
t = fold_build_pointer_plus_hwi (t, UNITS_PER_LONG - size);
gimplify_expr (&t, pre_p, NULL, is_gimple_val, fb_rvalue);
gimplify_assign (addr, t, pre_p);
if (size < UNITS_PER_LONG && left_align_p)
t = fold_build_pointer_plus_hwi (t, UNITS_PER_LONG);
else
t = fold_build_pointer_plus_hwi (t, size);
gimplify_assign (ovf, t, pre_p);
if (reg != NULL_TREE)
gimple_seq_add_stmt (pre_p, gimple_build_label (lab_over));
/* Increment register save count. */
if (n_reg > 0)
{
u = build2 (PREINCREMENT_EXPR, TREE_TYPE (reg), reg,
fold_convert (TREE_TYPE (reg), size_int (n_reg)));
gimplify_and_add (u, pre_p);
}
if (indirect_p)
{
t = build_pointer_type_for_mode (build_pointer_type (type),
ptr_mode, true);
addr = fold_convert (t, addr);
addr = build_va_arg_indirect_ref (addr);
}
else
{
t = build_pointer_type_for_mode (type, ptr_mode, true);
addr = fold_convert (t, addr);
}
return build_va_arg_indirect_ref (addr);
}
/* Emit rtl for the tbegin or tbegin_retry (RETRY != NULL_RTX)
expanders.
DEST - Register location where CC will be stored.
TDB - Pointer to a 256 byte area where to store the transaction.
diagnostic block. NULL if TDB is not needed.
RETRY - Retry count value. If non-NULL a retry loop for CC2
is emitted
CLOBBER_FPRS_P - If true clobbers for all FPRs are emitted as part
of the tbegin instruction pattern. */
void
s390_expand_tbegin (rtx dest, rtx tdb, rtx retry, bool clobber_fprs_p)
{
rtx retry_plus_two = gen_reg_rtx (SImode);
rtx retry_reg = gen_reg_rtx (SImode);
rtx_code_label *retry_label = NULL;
if (retry != NULL_RTX)
{
emit_move_insn (retry_reg, retry);
emit_insn (gen_addsi3 (retry_plus_two, retry_reg, const2_rtx));
emit_insn (gen_addsi3 (retry_reg, retry_reg, const1_rtx));
retry_label = gen_label_rtx ();
emit_label (retry_label);
}
if (clobber_fprs_p)
{
if (TARGET_VX)
emit_insn (gen_tbegin_1_z13 (gen_rtx_CONST_INT (VOIDmode, TBEGIN_MASK),
tdb));
else
emit_insn (gen_tbegin_1 (gen_rtx_CONST_INT (VOIDmode, TBEGIN_MASK),
tdb));
}
else
emit_insn (gen_tbegin_nofloat_1 (gen_rtx_CONST_INT (VOIDmode, TBEGIN_MASK),
tdb));
emit_move_insn (dest, gen_rtx_UNSPEC (SImode,
gen_rtvec (1, gen_rtx_REG (CCRAWmode,
CC_REGNUM)),
UNSPEC_CC_TO_INT));
if (retry != NULL_RTX)
{
const int CC0 = 1 << 3;
const int CC1 = 1 << 2;
const int CC3 = 1 << 0;
rtx jump;
rtx count = gen_reg_rtx (SImode);
rtx_code_label *leave_label = gen_label_rtx ();
/* Exit for success and permanent failures. */
jump = s390_emit_jump (leave_label,
gen_rtx_EQ (VOIDmode,
gen_rtx_REG (CCRAWmode, CC_REGNUM),
gen_rtx_CONST_INT (VOIDmode, CC0 | CC1 | CC3)));
LABEL_NUSES (leave_label) = 1;
/* CC2 - transient failure. Perform retry with ppa. */
emit_move_insn (count, retry_plus_two);
emit_insn (gen_subsi3 (count, count, retry_reg));
emit_insn (gen_tx_assist (count));
jump = emit_jump_insn (gen_doloop_si64 (retry_label,
retry_reg,
retry_reg));
JUMP_LABEL (jump) = retry_label;
LABEL_NUSES (retry_label) = 1;
emit_label (leave_label);
}
}
/* Return the decl for the target specific builtin with the function
code FCODE. */
static tree
s390_builtin_decl (unsigned fcode, bool initialized_p ATTRIBUTE_UNUSED)
{
if (fcode >= S390_BUILTIN_MAX)
return error_mark_node;
return s390_builtin_decls[fcode];
}
/* We call mcount before the function prologue. So a profiled leaf
function should stay a leaf function. */
static bool
s390_keep_leaf_when_profiled ()
{
return true;
}
/* Output assembly code for the trampoline template to
stdio stream FILE.
On S/390, we use gpr 1 internally in the trampoline code;
gpr 0 is used to hold the static chain. */
static void
s390_asm_trampoline_template (FILE *file)
{
rtx op[2];
op[0] = gen_rtx_REG (Pmode, 0);
op[1] = gen_rtx_REG (Pmode, 1);
if (TARGET_64BIT)
{
output_asm_insn ("basr\t%1,0", op); /* 2 byte */
output_asm_insn ("lmg\t%0,%1,14(%1)", op); /* 6 byte */
output_asm_insn ("br\t%1", op); /* 2 byte */
ASM_OUTPUT_SKIP (file, (HOST_WIDE_INT)(TRAMPOLINE_SIZE - 10));
}
else
{
output_asm_insn ("basr\t%1,0", op); /* 2 byte */
output_asm_insn ("lm\t%0,%1,6(%1)", op); /* 4 byte */
output_asm_insn ("br\t%1", op); /* 2 byte */
ASM_OUTPUT_SKIP (file, (HOST_WIDE_INT)(TRAMPOLINE_SIZE - 8));
}
}
/* Emit RTL insns to initialize the variable parts of a trampoline.
FNADDR is an RTX for the address of the function's pure code.
CXT is an RTX for the static chain value for the function. */
static void
s390_trampoline_init (rtx m_tramp, tree fndecl, rtx cxt)
{
rtx fnaddr = XEXP (DECL_RTL (fndecl), 0);
rtx mem;
emit_block_move (m_tramp, assemble_trampoline_template (),
GEN_INT (2 * UNITS_PER_LONG), BLOCK_OP_NORMAL);
mem = adjust_address (m_tramp, Pmode, 2 * UNITS_PER_LONG);
emit_move_insn (mem, cxt);
mem = adjust_address (m_tramp, Pmode, 3 * UNITS_PER_LONG);
emit_move_insn (mem, fnaddr);
}
static void
output_asm_nops (const char *user, int hw)
{
asm_fprintf (asm_out_file, "\t# NOPs for %s (%d halfwords)\n", user, hw);
while (hw > 0)
{
if (hw >= 3)
{
output_asm_insn ("brcl\t0,0", NULL);
hw -= 3;
}
else if (hw >= 2)
{
output_asm_insn ("bc\t0,0", NULL);
hw -= 2;
}
else
{
output_asm_insn ("bcr\t0,0", NULL);
hw -= 1;
}
}
}
/* Output assembler code to FILE to call a profiler hook. */
void
s390_function_profiler (FILE *file, int labelno ATTRIBUTE_UNUSED)
{
rtx op[4];
fprintf (file, "# function profiler \n");
op[0] = gen_rtx_REG (Pmode, RETURN_REGNUM);
op[1] = gen_rtx_REG (Pmode, STACK_POINTER_REGNUM);
op[1] = gen_rtx_MEM (Pmode, plus_constant (Pmode, op[1], UNITS_PER_LONG));
op[3] = GEN_INT (UNITS_PER_LONG);
op[2] = gen_rtx_SYMBOL_REF (Pmode, flag_fentry ? "__fentry__" : "_mcount");
SYMBOL_REF_FLAGS (op[2]) |= SYMBOL_FLAG_FUNCTION;
if (flag_pic && !TARGET_64BIT)
{
op[2] = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, op[2]), UNSPEC_PLT31);
op[2] = gen_rtx_CONST (Pmode, op[2]);
}
if (flag_record_mcount)
fprintf (file, "1:\n");
if (flag_fentry)
{
if (flag_nop_mcount)
output_asm_nops ("-mnop-mcount", /* brasl */ 3);
else if (cfun->static_chain_decl)
warning (OPT_Wcannot_profile, "nested functions cannot be profiled "
"with %<-mfentry%> on s390");
else
output_asm_insn ("brasl\t0,%2%K2", op);
}
else if (TARGET_64BIT)
{
if (flag_nop_mcount)
output_asm_nops ("-mnop-mcount", /* stg */ 3 + /* brasl */ 3 +
/* lg */ 3);
else
{
output_asm_insn ("stg\t%0,%1", op);
if (flag_dwarf2_cfi_asm)
output_asm_insn (".cfi_rel_offset\t%0,%3", op);
output_asm_insn ("brasl\t%0,%2%K2", op);
output_asm_insn ("lg\t%0,%1", op);
if (flag_dwarf2_cfi_asm)
output_asm_insn (".cfi_restore\t%0", op);
}
}
else
{
if (flag_nop_mcount)
output_asm_nops ("-mnop-mcount", /* st */ 2 + /* brasl */ 3 +
/* l */ 2);
else
{
output_asm_insn ("st\t%0,%1", op);
if (flag_dwarf2_cfi_asm)
output_asm_insn (".cfi_rel_offset\t%0,%3", op);
output_asm_insn ("brasl\t%0,%2%K2", op);
output_asm_insn ("l\t%0,%1", op);
if (flag_dwarf2_cfi_asm)
output_asm_insn (".cfi_restore\t%0", op);
}
}
if (flag_record_mcount)
{
fprintf (file, "\t.section __mcount_loc, \"a\",@progbits\n");
fprintf (file, "\t.%s 1b\n", TARGET_64BIT ? "quad" : "long");
fprintf (file, "\t.previous\n");
}
}
/* Encode symbol attributes (local vs. global, tls model) of a SYMBOL_REF
into its SYMBOL_REF_FLAGS. */
static void
s390_encode_section_info (tree decl, rtx rtl, int first)
{
default_encode_section_info (decl, rtl, first);
if (TREE_CODE (decl) == VAR_DECL)
{
/* Store the alignment to be able to check if we can use
a larl/load-relative instruction. We only handle the cases
that can go wrong (i.e. no FUNC_DECLs). */
if (DECL_ALIGN (decl) == 0 || DECL_ALIGN (decl) % 16)
SYMBOL_FLAG_SET_NOTALIGN2 (XEXP (rtl, 0));
else if (DECL_ALIGN (decl) % 32)
SYMBOL_FLAG_SET_NOTALIGN4 (XEXP (rtl, 0));
else if (DECL_ALIGN (decl) % 64)
SYMBOL_FLAG_SET_NOTALIGN8 (XEXP (rtl, 0));
}
/* Literal pool references don't have a decl so they are handled
differently here. We rely on the information in the MEM_ALIGN
entry to decide upon the alignment. */
if (MEM_P (rtl)
&& GET_CODE (XEXP (rtl, 0)) == SYMBOL_REF
&& TREE_CONSTANT_POOL_ADDRESS_P (XEXP (rtl, 0)))
{
if (MEM_ALIGN (rtl) == 0 || MEM_ALIGN (rtl) % 16)
SYMBOL_FLAG_SET_NOTALIGN2 (XEXP (rtl, 0));
else if (MEM_ALIGN (rtl) % 32)
SYMBOL_FLAG_SET_NOTALIGN4 (XEXP (rtl, 0));
else if (MEM_ALIGN (rtl) % 64)
SYMBOL_FLAG_SET_NOTALIGN8 (XEXP (rtl, 0));
}
}
/* Output thunk to FILE that implements a C++ virtual function call (with
multiple inheritance) to FUNCTION. The thunk adjusts the this pointer
by DELTA, and unless VCALL_OFFSET is zero, applies an additional adjustment
stored at VCALL_OFFSET in the vtable whose address is located at offset 0
relative to the resulting this pointer. */
static void
s390_output_mi_thunk (FILE *file, tree thunk ATTRIBUTE_UNUSED,
HOST_WIDE_INT delta, HOST_WIDE_INT vcall_offset,
tree function)
{
const char *fnname = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (thunk));
rtx op[10];
int nonlocal = 0;
assemble_start_function (thunk, fnname);
/* Make sure unwind info is emitted for the thunk if needed. */
final_start_function (emit_barrier (), file, 1);
/* Operand 0 is the target function. */
op[0] = XEXP (DECL_RTL (function), 0);
if (flag_pic && !SYMBOL_REF_LOCAL_P (op[0]))
{
nonlocal = 1;
if (!TARGET_64BIT)
{
op[0] = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, op[0]), UNSPEC_GOT);
op[0] = gen_rtx_CONST (Pmode, op[0]);
}
}
/* Operand 1 is the 'this' pointer. */
if (aggregate_value_p (TREE_TYPE (TREE_TYPE (function)), function))
op[1] = gen_rtx_REG (Pmode, 3);
else
op[1] = gen_rtx_REG (Pmode, 2);
/* Operand 2 is the delta. */
op[2] = GEN_INT (delta);
/* Operand 3 is the vcall_offset. */
op[3] = GEN_INT (vcall_offset);
/* Operand 4 is the temporary register. */
op[4] = gen_rtx_REG (Pmode, 1);
/* Operands 5 to 8 can be used as labels. */
op[5] = NULL_RTX;
op[6] = NULL_RTX;
op[7] = NULL_RTX;
op[8] = NULL_RTX;
/* Operand 9 can be used for temporary register. */
op[9] = NULL_RTX;
/* Generate code. */
if (TARGET_64BIT)
{
/* Setup literal pool pointer if required. */
if ((!DISP_IN_RANGE (delta)
&& !CONST_OK_FOR_K (delta)
&& !CONST_OK_FOR_Os (delta))
|| (!DISP_IN_RANGE (vcall_offset)
&& !CONST_OK_FOR_K (vcall_offset)
&& !CONST_OK_FOR_Os (vcall_offset)))
{
op[5] = gen_label_rtx ();
output_asm_insn ("larl\t%4,%5", op);
}
/* Add DELTA to this pointer. */
if (delta)
{
if (CONST_OK_FOR_J (delta))
output_asm_insn ("la\t%1,%2(%1)", op);
else if (DISP_IN_RANGE (delta))
output_asm_insn ("lay\t%1,%2(%1)", op);
else if (CONST_OK_FOR_K (delta))
output_asm_insn ("aghi\t%1,%2", op);
else if (CONST_OK_FOR_Os (delta))
output_asm_insn ("agfi\t%1,%2", op);
else
{
op[6] = gen_label_rtx ();
output_asm_insn ("agf\t%1,%6-%5(%4)", op);
}
}
/* Perform vcall adjustment. */
if (vcall_offset)
{
if (DISP_IN_RANGE (vcall_offset))
{
output_asm_insn ("lg\t%4,0(%1)", op);
output_asm_insn ("ag\t%1,%3(%4)", op);
}
else if (CONST_OK_FOR_K (vcall_offset))
{
output_asm_insn ("lghi\t%4,%3", op);
output_asm_insn ("ag\t%4,0(%1)", op);
output_asm_insn ("ag\t%1,0(%4)", op);
}
else if (CONST_OK_FOR_Os (vcall_offset))
{
output_asm_insn ("lgfi\t%4,%3", op);
output_asm_insn ("ag\t%4,0(%1)", op);
output_asm_insn ("ag\t%1,0(%4)", op);
}
else
{
op[7] = gen_label_rtx ();
output_asm_insn ("llgf\t%4,%7-%5(%4)", op);
output_asm_insn ("ag\t%4,0(%1)", op);
output_asm_insn ("ag\t%1,0(%4)", op);
}
}
/* Jump to target. */
output_asm_insn ("jg\t%0%K0", op);
/* Output literal pool if required. */
if (op[5])
{
output_asm_insn (".align\t4", op);
targetm.asm_out.internal_label (file, "L",
CODE_LABEL_NUMBER (op[5]));
}
if (op[6])
{
targetm.asm_out.internal_label (file, "L",
CODE_LABEL_NUMBER (op[6]));
output_asm_insn (".long\t%2", op);
}
if (op[7])
{
targetm.asm_out.internal_label (file, "L",
CODE_LABEL_NUMBER (op[7]));
output_asm_insn (".long\t%3", op);
}
}
else
{
/* Setup base pointer if required. */
if (!vcall_offset
|| (!DISP_IN_RANGE (delta)
&& !CONST_OK_FOR_K (delta)
&& !CONST_OK_FOR_Os (delta))
|| (!DISP_IN_RANGE (delta)
&& !CONST_OK_FOR_K (vcall_offset)
&& !CONST_OK_FOR_Os (vcall_offset)))
{
op[5] = gen_label_rtx ();
output_asm_insn ("basr\t%4,0", op);
targetm.asm_out.internal_label (file, "L",
CODE_LABEL_NUMBER (op[5]));
}
/* Add DELTA to this pointer. */
if (delta)
{
if (CONST_OK_FOR_J (delta))
output_asm_insn ("la\t%1,%2(%1)", op);
else if (DISP_IN_RANGE (delta))
output_asm_insn ("lay\t%1,%2(%1)", op);
else if (CONST_OK_FOR_K (delta))
output_asm_insn ("ahi\t%1,%2", op);
else if (CONST_OK_FOR_Os (delta))
output_asm_insn ("afi\t%1,%2", op);
else
{
op[6] = gen_label_rtx ();
output_asm_insn ("a\t%1,%6-%5(%4)", op);
}
}
/* Perform vcall adjustment. */
if (vcall_offset)
{
if (CONST_OK_FOR_J (vcall_offset))
{
output_asm_insn ("l\t%4,0(%1)", op);
output_asm_insn ("a\t%1,%3(%4)", op);
}
else if (DISP_IN_RANGE (vcall_offset))
{
output_asm_insn ("l\t%4,0(%1)", op);
output_asm_insn ("ay\t%1,%3(%4)", op);
}
else if (CONST_OK_FOR_K (vcall_offset))
{
output_asm_insn ("lhi\t%4,%3", op);
output_asm_insn ("a\t%4,0(%1)", op);
output_asm_insn ("a\t%1,0(%4)", op);
}
else if (CONST_OK_FOR_Os (vcall_offset))
{
output_asm_insn ("iilf\t%4,%3", op);
output_asm_insn ("a\t%4,0(%1)", op);
output_asm_insn ("a\t%1,0(%4)", op);
}
else
{
op[7] = gen_label_rtx ();
output_asm_insn ("l\t%4,%7-%5(%4)", op);
output_asm_insn ("a\t%4,0(%1)", op);
output_asm_insn ("a\t%1,0(%4)", op);
}
/* We had to clobber the base pointer register.
Re-setup the base pointer (with a different base). */
op[5] = gen_label_rtx ();
output_asm_insn ("basr\t%4,0", op);
targetm.asm_out.internal_label (file, "L",
CODE_LABEL_NUMBER (op[5]));
}
/* Jump to target. */
op[8] = gen_label_rtx ();
if (!flag_pic)
output_asm_insn ("l\t%4,%8-%5(%4)", op);
else if (!nonlocal)
output_asm_insn ("a\t%4,%8-%5(%4)", op);
/* We cannot call through .plt, since .plt requires %r12 loaded. */
else if (flag_pic == 1)
{
output_asm_insn ("a\t%4,%8-%5(%4)", op);
output_asm_insn ("l\t%4,%0(%4)", op);
}
else if (flag_pic == 2)
{
op[9] = gen_rtx_REG (Pmode, 0);
output_asm_insn ("l\t%9,%8-4-%5(%4)", op);
output_asm_insn ("a\t%4,%8-%5(%4)", op);
output_asm_insn ("ar\t%4,%9", op);
output_asm_insn ("l\t%4,0(%4)", op);
}
output_asm_insn ("br\t%4", op);
/* Output literal pool. */
output_asm_insn (".align\t4", op);
if (nonlocal && flag_pic == 2)
output_asm_insn (".long\t%0", op);
if (nonlocal)
{
op[0] = gen_rtx_SYMBOL_REF (Pmode, "_GLOBAL_OFFSET_TABLE_");
SYMBOL_REF_FLAGS (op[0]) = SYMBOL_FLAG_LOCAL;
}
targetm.asm_out.internal_label (file, "L", CODE_LABEL_NUMBER (op[8]));
if (!flag_pic)
output_asm_insn (".long\t%0", op);
else
output_asm_insn (".long\t%0-%5", op);
if (op[6])
{
targetm.asm_out.internal_label (file, "L",
CODE_LABEL_NUMBER (op[6]));
output_asm_insn (".long\t%2", op);
}
if (op[7])
{
targetm.asm_out.internal_label (file, "L",
CODE_LABEL_NUMBER (op[7]));
output_asm_insn (".long\t%3", op);
}
}
final_end_function ();
assemble_end_function (thunk, fnname);
}
/* Output either an indirect jump or 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)
{
return (mode == SImode || (TARGET_64BIT && mode == DImode));
}
/* Checks whether the given CALL_EXPR would use a caller
saved register. This is used to decide whether sibling call
optimization could be performed on the respective function
call. */
static bool
s390_call_saved_register_used (tree call_expr)
{
CUMULATIVE_ARGS cum_v;
cumulative_args_t cum;
tree parameter;
rtx parm_rtx;
int reg, i;
INIT_CUMULATIVE_ARGS (cum_v, NULL, NULL, 0, 0);
cum = pack_cumulative_args (&cum_v);
for (i = 0; i < call_expr_nargs (call_expr); i++)
{
parameter = CALL_EXPR_ARG (call_expr, i);
gcc_assert (parameter);
/* For an undeclared variable passed as parameter we will get
an ERROR_MARK node here. */
if (TREE_CODE (parameter) == ERROR_MARK)
return true;
/* We assume that in the target function all parameters are
named. This only has an impact on vector argument register
usage none of which is call-saved. */
function_arg_info arg (TREE_TYPE (parameter), /*named=*/true);
apply_pass_by_reference_rules (&cum_v, arg);
parm_rtx = s390_function_arg (cum, arg);
s390_function_arg_advance (cum, arg);
if (!parm_rtx)
continue;
if (REG_P (parm_rtx))
{
for (reg = 0; reg < REG_NREGS (parm_rtx); reg++)
if (!call_used_or_fixed_reg_p (reg + REGNO (parm_rtx)))
return true;
}
if (GET_CODE (parm_rtx) == PARALLEL)
{
int i;
for (i = 0; i < XVECLEN (parm_rtx, 0); i++)
{
rtx r = XEXP (XVECEXP (parm_rtx, 0, i), 0);
gcc_assert (REG_P (r));
for (reg = 0; reg < REG_NREGS (r); reg++)
if (!call_used_or_fixed_reg_p (reg + REGNO (r)))
return true;
}
}
}
return false;
}
/* Return true if the given call expression can be
turned into a sibling call.
DECL holds the declaration of the function to be called whereas
EXP is the call expression itself. */
static bool
s390_function_ok_for_sibcall (tree decl, tree exp)
{
/* The TPF epilogue uses register 1. */
if (TARGET_TPF_PROFILING)
return false;
/* The 31 bit PLT code uses register 12 (GOT pointer - caller saved)
which would have to be restored before the sibcall. */
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. */
return !s390_call_saved_register_used (exp);
}
/* Return the fixed registers used for condition codes. */
static bool
s390_fixed_condition_code_regs (unsigned int *p1, unsigned int *p2)
{
*p1 = CC_REGNUM;
*p2 = INVALID_REGNUM;
return true;
}
/* This function is used by the call expanders of the machine description.
It emits the call insn itself together with the necessary operations
to adjust the target address and returns the emitted insn.
ADDR_LOCATION is the target address rtx
TLS_CALL the location of the thread-local symbol
RESULT_REG the register where the result of the call should be stored
RETADDR_REG the register where the return address should be stored
If this parameter is NULL_RTX the call is considered
to be a sibling call. */
rtx_insn *
s390_emit_call (rtx addr_location, rtx tls_call, rtx result_reg,
rtx retaddr_reg)
{
bool plt31_call_p = false;
rtx_insn *insn;
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)
{
/* When calling a global routine in PIC mode, we must
replace the symbol itself with the PLT stub. */
if (flag_pic && !SYMBOL_REF_LOCAL_P (addr_location) && !TARGET_64BIT)
{
if (retaddr_reg != NULL_RTX)
{
addr_location = gen_rtx_UNSPEC (Pmode,
gen_rtvec (1, addr_location),
UNSPEC_PLT31);
addr_location = gen_rtx_CONST (Pmode, addr_location);
plt31_call_p = true;
}
else
/* For -fpic code the PLT entries might use r12 which is
call-saved. Therefore we cannot do a sibcall when
calling directly using a symbol ref. When reaching
this point we decided (in s390_function_ok_for_sibcall)
to do a sibcall for a function pointer but one of the
optimizers was able to get rid of the function pointer
by propagating the symbol ref into the call. This
optimization is illegal for S/390 so we turn the direct
call into a indirect call again. */
addr_location = force_reg (Pmode, addr_location);
}
}
/* If it is already an indirect call or the code above moved the
SYMBOL_REF to somewhere else make sure the address can be found in
register 1. */
if (retaddr_reg == NULL_RTX
&& GET_CODE (addr_location) != SYMBOL_REF
&& !plt31_call_p)
{
emit_move_insn (gen_rtx_REG (Pmode, SIBCALL_REGNUM), addr_location);
addr_location = gen_rtx_REG (Pmode, SIBCALL_REGNUM);
}
if (TARGET_INDIRECT_BRANCH_NOBP_CALL
&& GET_CODE (addr_location) != SYMBOL_REF
&& !plt31_call_p)
{
/* 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);
if (result_reg != NULL_RTX)
*call = gen_rtx_SET (result_reg, *call);
if (retaddr_reg != NULL_RTX)
{
*clobber_ret_reg = gen_rtx_CLOBBER (VOIDmode, retaddr_reg);
if (tls_call != NULL_RTX)
*use = gen_rtx_USE (VOIDmode, tls_call);
}
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);
/* 31-bit PLT stubs and tls calls use the GOT register implicitly. */
if (plt31_call_p || tls_call != NULL_RTX)
{
/* s390_function_ok_for_sibcall should
have denied sibcalls in this case. */
gcc_assert (retaddr_reg != NULL_RTX);
use_reg (&CALL_INSN_FUNCTION_USAGE (insn), gen_rtx_REG (Pmode, 12));
}
return insn;
}
/* Implement TARGET_CONDITIONAL_REGISTER_USAGE. */
static void
s390_conditional_register_usage (void)
{
int i;
if (flag_pic)
fixed_regs[PIC_OFFSET_TABLE_REGNUM] = 1;
fixed_regs[BASE_REGNUM] = 0;
fixed_regs[RETURN_REGNUM] = 0;
if (TARGET_64BIT)
{
for (i = FPR8_REGNUM; i <= FPR15_REGNUM; i++)
call_used_regs[i] = 0;
}
else
{
call_used_regs[FPR4_REGNUM] = 0;
call_used_regs[FPR6_REGNUM] = 0;
}
if (TARGET_SOFT_FLOAT)
{
for (i = FPR0_REGNUM; i <= FPR15_REGNUM; i++)
fixed_regs[i] = 1;
}
/* Disable v16 - v31 for non-vector target. */
if (!TARGET_VX)
{
for (i = VR16_REGNUM; i <= VR31_REGNUM; i++)
fixed_regs[i] = call_used_regs[i] = 1;
}
}
/* Corresponding function to eh_return expander. */
static GTY(()) rtx s390_tpf_eh_return_symbol;
void
s390_emit_tpf_eh_return (rtx target)
{
rtx_insn *insn;
rtx reg, orig_ra;
if (!s390_tpf_eh_return_symbol)
{
s390_tpf_eh_return_symbol = gen_rtx_SYMBOL_REF (Pmode, "__tpf_eh_return");
SYMBOL_REF_FLAGS (s390_tpf_eh_return_symbol) |= SYMBOL_FLAG_FUNCTION;
}
reg = gen_rtx_REG (Pmode, 2);
orig_ra = gen_rtx_REG (Pmode, 3);
emit_move_insn (reg, target);
emit_move_insn (orig_ra, get_hard_reg_initial_val (Pmode, RETURN_REGNUM));
insn = s390_emit_call (s390_tpf_eh_return_symbol, NULL_RTX, reg,
gen_rtx_REG (Pmode, RETURN_REGNUM));
use_reg (&CALL_INSN_FUNCTION_USAGE (insn), reg);
use_reg (&CALL_INSN_FUNCTION_USAGE (insn), orig_ra);
emit_move_insn (EH_RETURN_HANDLER_RTX, reg);
}
/* Rework the prologue/epilogue to avoid saving/restoring
registers unnecessarily. */
static void
s390_optimize_prologue (void)
{
rtx_insn *insn, *new_insn, *next_insn;
/* Do a final recompute of the frame-related data. */
s390_optimize_register_info ();
/* If all special registers are in fact used, there's nothing we
can do, so no point in walking the insn list. */
if (cfun_frame_layout.first_save_gpr <= BASE_REGNUM
&& cfun_frame_layout.last_save_gpr >= BASE_REGNUM)
return;
/* Search for prologue/epilogue insns and replace them. */
for (insn = get_insns (); insn; insn = next_insn)
{
int first, last, off;
rtx set, base, offset;
rtx pat;
next_insn = NEXT_INSN (insn);
if (! NONJUMP_INSN_P (insn) || ! RTX_FRAME_RELATED_P (insn))
continue;
pat = PATTERN (insn);
/* Remove ldgr/lgdr instructions used for saving and restore
GPRs if possible. */
if (TARGET_Z10)
{
rtx tmp_pat = pat;
if (INSN_CODE (insn) == CODE_FOR_stack_restore_from_fpr)
tmp_pat = XVECEXP (pat, 0, 0);
if (GET_CODE (tmp_pat) == SET
&& GET_MODE (SET_SRC (tmp_pat)) == DImode
&& REG_P (SET_SRC (tmp_pat))
&& REG_P (SET_DEST (tmp_pat)))
{
int src_regno = REGNO (SET_SRC (tmp_pat));
int dest_regno = REGNO (SET_DEST (tmp_pat));
int gpr_regno;
int fpr_regno;
if (!((GENERAL_REGNO_P (src_regno)
&& FP_REGNO_P (dest_regno))
|| (FP_REGNO_P (src_regno)
&& GENERAL_REGNO_P (dest_regno))))
continue;
gpr_regno = GENERAL_REGNO_P (src_regno) ? src_regno : dest_regno;
fpr_regno = FP_REGNO_P (src_regno) ? src_regno : dest_regno;
/* GPR must be call-saved, FPR must be call-clobbered. */
if (!call_used_regs[fpr_regno]
|| call_used_regs[gpr_regno])
continue;
/* It must not happen that what we once saved in an FPR now
needs a stack slot. */
gcc_assert (cfun_gpr_save_slot (gpr_regno) != SAVE_SLOT_STACK);
if (cfun_gpr_save_slot (gpr_regno) == SAVE_SLOT_NONE)
{
remove_insn (insn);
continue;
}
}
}
if (GET_CODE (pat) == PARALLEL
&& store_multiple_operation (pat, VOIDmode))
{
set = XVECEXP (pat, 0, 0);
first = REGNO (SET_SRC (set));
last = first + XVECLEN (pat, 0) - 1;
offset = const0_rtx;
base = eliminate_constant_term (XEXP (SET_DEST (set), 0), &offset);
off = INTVAL (offset);
if (GET_CODE (base) != REG || off < 0)
continue;
if (cfun_frame_layout.first_save_gpr != -1
&& (cfun_frame_layout.first_save_gpr < first
|| cfun_frame_layout.last_save_gpr > last))
continue;
if (REGNO (base) != STACK_POINTER_REGNUM
&& REGNO (base) != HARD_FRAME_POINTER_REGNUM)
continue;
if (first > BASE_REGNUM || last < BASE_REGNUM)
continue;
if (cfun_frame_layout.first_save_gpr != -1)
{
rtx s_pat = save_gprs (base,
off + (cfun_frame_layout.first_save_gpr
- first) * UNITS_PER_LONG,
cfun_frame_layout.first_save_gpr,
cfun_frame_layout.last_save_gpr);
new_insn = emit_insn_before (s_pat, insn);
INSN_ADDRESSES_NEW (new_insn, -1);
}
remove_insn (insn);
continue;
}
if (cfun_frame_layout.first_save_gpr == -1
&& GET_CODE (pat) == SET
&& GENERAL_REG_P (SET_SRC (pat))
&& GET_CODE (SET_DEST (pat)) == MEM)
{
set = pat;
first = REGNO (SET_SRC (set));
offset = const0_rtx;
base = eliminate_constant_term (XEXP (SET_DEST (set), 0), &offset);
off = INTVAL (offset);
if (GET_CODE (base) != REG || off < 0)
continue;
if (REGNO (base) != STACK_POINTER_REGNUM
&& REGNO (base) != HARD_FRAME_POINTER_REGNUM)
continue;
remove_insn (insn);
continue;
}
if (GET_CODE (pat) == PARALLEL
&& load_multiple_operation (pat, VOIDmode))
{
set = XVECEXP (pat, 0, 0);
first = REGNO (SET_DEST (set));
last = first + XVECLEN (pat, 0) - 1;
offset = const0_rtx;
base = eliminate_constant_term (XEXP (SET_SRC (set), 0), &offset);
off = INTVAL (offset);
if (GET_CODE (base) != REG || off < 0)
continue;
if (cfun_frame_layout.first_restore_gpr != -1
&& (cfun_frame_layout.first_restore_gpr < first
|| cfun_frame_layout.last_restore_gpr > last))
continue;
if (REGNO (base) != STACK_POINTER_REGNUM
&& REGNO (base) != HARD_FRAME_POINTER_REGNUM)
continue;
if (first > BASE_REGNUM || last < BASE_REGNUM)
continue;
if (cfun_frame_layout.first_restore_gpr != -1)
{
rtx rpat = restore_gprs (base,
off + (cfun_frame_layout.first_restore_gpr
- first) * UNITS_PER_LONG,
cfun_frame_layout.first_restore_gpr,
cfun_frame_layout.last_restore_gpr);
/* Remove REG_CFA_RESTOREs for registers that we no
longer need to save. */
REG_NOTES (rpat) = REG_NOTES (insn);
for (rtx *ptr = &REG_NOTES (rpat); *ptr; )
if (REG_NOTE_KIND (*ptr) == REG_CFA_RESTORE
&& ((int) REGNO (XEXP (*ptr, 0))
< cfun_frame_layout.first_restore_gpr))
*ptr = XEXP (*ptr, 1);
else
ptr = &XEXP (*ptr, 1);
new_insn = emit_insn_before (rpat, insn);
RTX_FRAME_RELATED_P (new_insn) = 1;
INSN_ADDRESSES_NEW (new_insn, -1);
}
remove_insn (insn);
continue;
}
if (cfun_frame_layout.first_restore_gpr == -1
&& GET_CODE (pat) == SET
&& GENERAL_REG_P (SET_DEST (pat))
&& GET_CODE (SET_SRC (pat)) == MEM)
{
set = pat;
first = REGNO (SET_DEST (set));
offset = const0_rtx;
base = eliminate_constant_term (XEXP (SET_SRC (set), 0), &offset);
off = INTVAL (offset);
if (GET_CODE (base) != REG || off < 0)
continue;
if (REGNO (base) != STACK_POINTER_REGNUM
&& REGNO (base) != HARD_FRAME_POINTER_REGNUM)
continue;
remove_insn (insn);
continue;
}
}
}
/* On z10 and later the dynamic branch prediction must see the
backward jump within a certain windows. If not it falls back to
the static prediction. This function rearranges the loop backward
branch in a way which makes the static prediction always correct.
The function returns true if it added an instruction. */
static bool
s390_fix_long_loop_prediction (rtx_insn *insn)
{
rtx set = single_set (insn);
rtx code_label, label_ref;
rtx_insn *uncond_jump;
rtx_insn *cur_insn;
rtx tmp;
int distance;
/* This will exclude branch on count and branch on index patterns
since these are correctly statically predicted.
The additional check for a PARALLEL is required here since
single_set might be != NULL for PARALLELs where the set of the
iteration variable is dead. */
if (GET_CODE (PATTERN (insn)) == PARALLEL
|| !set
|| SET_DEST (set) != pc_rtx
|| GET_CODE (SET_SRC(set)) != IF_THEN_ELSE)
return false;
/* Skip conditional returns. */
if (ANY_RETURN_P (XEXP (SET_SRC (set), 1))
&& XEXP (SET_SRC (set), 2) == pc_rtx)
return false;
label_ref = (GET_CODE (XEXP (SET_SRC (set), 1)) == LABEL_REF ?
XEXP (SET_SRC (set), 1) : XEXP (SET_SRC (set), 2));
gcc_assert (GET_CODE (label_ref) == LABEL_REF);
code_label = XEXP (label_ref, 0);
if (INSN_ADDRESSES (INSN_UID (code_label)) == -1
|| INSN_ADDRESSES (INSN_UID (insn)) == -1
|| (INSN_ADDRESSES (INSN_UID (insn))
- INSN_ADDRESSES (INSN_UID (code_label)) < PREDICT_DISTANCE))
return false;
for (distance = 0, cur_insn = PREV_INSN (insn);
distance < PREDICT_DISTANCE - 6;
distance += get_attr_length (cur_insn), cur_insn = PREV_INSN (cur_insn))
if (!cur_insn || JUMP_P (cur_insn) || LABEL_P (cur_insn))
return false;
rtx_code_label *new_label = gen_label_rtx ();
uncond_jump = emit_jump_insn_after (
gen_rtx_SET (pc_rtx,
gen_rtx_LABEL_REF (VOIDmode, code_label)),
insn);
emit_label_after (new_label, uncond_jump);
tmp = XEXP (SET_SRC (set), 1);
XEXP (SET_SRC (set), 1) = XEXP (SET_SRC (set), 2);
XEXP (SET_SRC (set), 2) = tmp;
INSN_CODE (insn) = -1;
XEXP (label_ref, 0) = new_label;
JUMP_LABEL (insn) = new_label;
JUMP_LABEL (uncond_jump) = code_label;
return true;
}
/* Returns 1 if INSN reads the value of REG for purposes not related
to addressing of memory, and 0 otherwise. */
static int
s390_non_addr_reg_read_p (rtx reg, rtx_insn *insn)
{
return reg_referenced_p (reg, PATTERN (insn))
&& !reg_used_in_mem_p (REGNO (reg), PATTERN (insn));
}
/* Starting from INSN find_cond_jump looks downwards in the insn
stream for a single jump insn which is the last user of the
condition code set in INSN. */
static rtx_insn *
find_cond_jump (rtx_insn *insn)
{
for (; insn; insn = NEXT_INSN (insn))
{
rtx ite, cc;
if (LABEL_P (insn))
break;
if (!JUMP_P (insn))
{
if (reg_mentioned_p (gen_rtx_REG (CCmode, CC_REGNUM), insn))
break;
continue;
}
/* This will be triggered by a return. */
if (GET_CODE (PATTERN (insn)) != SET)
break;
gcc_assert (SET_DEST (PATTERN (insn)) == pc_rtx);
ite = SET_SRC (PATTERN (insn));
if (GET_CODE (ite) != IF_THEN_ELSE)
break;
cc = XEXP (XEXP (ite, 0), 0);
if (!REG_P (cc) || !CC_REGNO_P (REGNO (cc)))
break;
if (find_reg_note (insn, REG_DEAD, cc))
return insn;
break;
}
return NULL;
}
/* Swap the condition in COND and the operands in OP0 and OP1 so that
the semantics does not change. If NULL_RTX is passed as COND the
function tries to find the conditional jump starting with INSN. */
static void
s390_swap_cmp (rtx cond, rtx *op0, rtx *op1, rtx_insn *insn)
{
rtx tmp = *op0;
if (cond == NULL_RTX)
{
rtx_insn *jump = find_cond_jump (NEXT_INSN (insn));
rtx set = jump ? single_set (jump) : NULL_RTX;
if (set == NULL_RTX)
return;
cond = XEXP (SET_SRC (set), 0);
}
*op0 = *op1;
*op1 = tmp;
PUT_CODE (cond, swap_condition (GET_CODE (cond)));
}
/* On z10, instructions of the compare-and-branch family have the
property to access the register occurring as second operand with
its bits complemented. If such a compare is grouped with a second
instruction that accesses the same register non-complemented, and
if that register's value is delivered via a bypass, then the
pipeline recycles, thereby causing significant performance decline.
This function locates such situations and exchanges the two
operands of the compare. The function return true whenever it
added an insn. */
static bool
s390_z10_optimize_cmp (rtx_insn *insn)
{
rtx_insn *prev_insn, *next_insn;
bool insn_added_p = false;
rtx cond, *op0, *op1;
if (GET_CODE (PATTERN (insn)) == PARALLEL)
{
/* Handle compare and branch and branch on count
instructions. */
rtx pattern = single_set (insn);
if (!pattern
|| SET_DEST (pattern) != pc_rtx
|| GET_CODE (SET_SRC (pattern)) != IF_THEN_ELSE)
return false;
cond = XEXP (SET_SRC (pattern), 0);
op0 = &XEXP (cond, 0);
op1 = &XEXP (cond, 1);
}
else if (GET_CODE (PATTERN (insn)) == SET)
{
rtx src, dest;
/* Handle normal compare instructions. */
src = SET_SRC (PATTERN (insn));
dest = SET_DEST (PATTERN (insn));
if (!REG_P (dest)
|| !CC_REGNO_P (REGNO (dest))
|| GET_CODE (src) != COMPARE)
return false;
/* s390_swap_cmp will try to find the conditional
jump when passing NULL_RTX as condition. */
cond = NULL_RTX;
op0 = &XEXP (src, 0);
op1 = &XEXP (src, 1);
}
else
return false;
if (!REG_P (*op0) || !REG_P (*op1))
return false;
if (GET_MODE_CLASS (GET_MODE (*op0)) != MODE_INT)
return false;
/* Swap the COMPARE arguments and its mask if there is a
conflicting access in the previous insn. */
prev_insn = prev_active_insn (insn);
if (prev_insn != NULL_RTX && INSN_P (prev_insn)
&& reg_referenced_p (*op1, PATTERN (prev_insn)))
s390_swap_cmp (cond, op0, op1, insn);
/* Check if there is a conflict with the next insn. If there
was no conflict with the previous insn, then swap the
COMPARE arguments and its mask. If we already swapped
the operands, or if swapping them would cause a conflict
with the previous insn, issue a NOP after the COMPARE in
order to separate the two instuctions. */
next_insn = next_active_insn (insn);
if (next_insn != NULL_RTX && INSN_P (next_insn)
&& s390_non_addr_reg_read_p (*op1, next_insn))
{
if (prev_insn != NULL_RTX && INSN_P (prev_insn)
&& s390_non_addr_reg_read_p (*op0, prev_insn))
{
if (REGNO (*op1) == 0)
emit_insn_after (gen_nop_lr1 (), insn);
else
emit_insn_after (gen_nop_lr0 (), insn);
insn_added_p = true;
}
else
s390_swap_cmp (cond, op0, op1, insn);
}
return insn_added_p;
}
/* Number of INSNs to be scanned backward in the last BB of the loop
and forward in the first BB of the loop. This usually should be a
bit more than the number of INSNs which could go into one
group. */
#define S390_OSC_SCAN_INSN_NUM 5
/* Scan LOOP for static OSC collisions and return true if a osc_break
should be issued for this loop. */
static bool
s390_adjust_loop_scan_osc (struct loop* loop)
{
HARD_REG_SET modregs, newregs;
rtx_insn *insn, *store_insn = NULL;
rtx set;
struct s390_address addr_store, addr_load;
subrtx_iterator::array_type array;
int insn_count;
CLEAR_HARD_REG_SET (modregs);
insn_count = 0;
FOR_BB_INSNS_REVERSE (loop->latch, insn)
{
if (!INSN_P (insn) || INSN_CODE (insn) <= 0)
continue;
insn_count++;
if (insn_count > S390_OSC_SCAN_INSN_NUM)
return false;
find_all_hard_reg_sets (insn, &newregs, true);
modregs |= newregs;
set = single_set (insn);
if (!set)
continue;
if (MEM_P (SET_DEST (set))
&& s390_decompose_address (XEXP (SET_DEST (set), 0), &addr_store))
{
store_insn = insn;
break;
}
}
if (store_insn == NULL_RTX)
return false;
insn_count = 0;
FOR_BB_INSNS (loop->header, insn)
{
if (!INSN_P (insn) || INSN_CODE (insn) <= 0)
continue;
if (insn == store_insn)
return false;
insn_count++;
if (insn_count > S390_OSC_SCAN_INSN_NUM)
return false;
find_all_hard_reg_sets (insn, &newregs, true);
modregs |= newregs;
set = single_set (insn);
if (!set)
continue;
/* An intermediate store disrupts static OSC checking
anyway. */
if (MEM_P (SET_DEST (set))
&& s390_decompose_address (XEXP (SET_DEST (set), 0), NULL))
return false;
FOR_EACH_SUBRTX (iter, array, SET_SRC (set), NONCONST)
if (MEM_P (*iter)
&& s390_decompose_address (XEXP (*iter, 0), &addr_load)
&& rtx_equal_p (addr_load.base, addr_store.base)
&& rtx_equal_p (addr_load.indx, addr_store.indx)
&& rtx_equal_p (addr_load.disp, addr_store.disp))
{
if ((addr_load.base != NULL_RTX
&& TEST_HARD_REG_BIT (modregs, REGNO (addr_load.base)))
|| (addr_load.indx != NULL_RTX
&& TEST_HARD_REG_BIT (modregs, REGNO (addr_load.indx))))
return true;
}
}
return false;
}
/* Look for adjustments which can be done on simple innermost
loops. */
static void
s390_adjust_loops ()
{
df_analyze ();
compute_bb_for_insn ();
/* Find the loops. */
loop_optimizer_init (AVOID_CFG_MODIFICATIONS);
for (auto loop : loops_list (cfun, LI_ONLY_INNERMOST))
{
if (dump_file)
{
flow_loop_dump (loop, dump_file, NULL, 0);
fprintf (dump_file, ";; OSC loop scan Loop: ");
}
if (loop->latch == NULL
|| pc_set (BB_END (loop->latch)) == NULL_RTX
|| !s390_adjust_loop_scan_osc (loop))
{
if (dump_file)
{
if (loop->latch == NULL)
fprintf (dump_file, " muliple backward jumps\n");
else
{
fprintf (dump_file, " header insn: %d latch insn: %d ",
INSN_UID (BB_HEAD (loop->header)),
INSN_UID (BB_END (loop->latch)));
if (pc_set (BB_END (loop->latch)) == NULL_RTX)
fprintf (dump_file, " loop does not end with jump\n");
else
fprintf (dump_file, " not instrumented\n");
}
}
}
else
{
rtx_insn *new_insn;
if (dump_file)
fprintf (dump_file, " adding OSC break insn: ");
new_insn = emit_insn_before (gen_osc_break (),
BB_END (loop->latch));
INSN_ADDRESSES_NEW (new_insn, -1);
}
}
loop_optimizer_finalize ();
df_finish_pass (false);
}
/* Perform machine-dependent processing. */
static void
s390_reorg (void)
{
struct constant_pool *pool;
rtx_insn *insn;
int hw_before, hw_after;
if (s390_tune == PROCESSOR_2964_Z13)
s390_adjust_loops ();
/* Make sure all splits have been performed; splits after
machine_dependent_reorg might confuse insn length counts. */
split_all_insns_noflow ();
/* Install the main literal pool and the associated base
register load insns. The literal pool might be > 4096 bytes in
size, so that some of its elements cannot be directly accessed.
To fix this, we split the single literal pool into multiple
pool chunks, reloading the pool base register at various
points throughout the function to ensure it always points to
the pool chunk the following code expects. */
/* Collect the literal pool. */
pool = s390_mainpool_start ();
if (pool)
{
/* Finish up literal pool related changes. */
s390_mainpool_finish (pool);
}
else
{
/* If literal pool overflowed, chunkify it. */
pool = s390_chunkify_start ();
s390_chunkify_finish (pool);
}
/* Generate out-of-pool execute target insns. */
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
{
rtx label;
rtx_insn *target;
label = s390_execute_label (insn);
if (!label)
continue;
gcc_assert (label != const0_rtx);
target = emit_label (XEXP (label, 0));
INSN_ADDRESSES_NEW (target, -1);
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);
}
/* Try to optimize prologue and epilogue further. */
s390_optimize_prologue ();
/* Walk over the insns and do some >=z10 specific changes. */
if (s390_tune >= PROCESSOR_2097_Z10)
{
rtx_insn *insn;
bool insn_added_p = false;
/* The insn lengths and addresses have to be up to date for the
following manipulations. */
shorten_branches (get_insns ());
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
{
if (!INSN_P (insn) || INSN_CODE (insn) <= 0)
continue;
if (JUMP_P (insn))
insn_added_p |= s390_fix_long_loop_prediction (insn);
if ((GET_CODE (PATTERN (insn)) == PARALLEL
|| GET_CODE (PATTERN (insn)) == SET)
&& s390_tune == PROCESSOR_2097_Z10)
insn_added_p |= s390_z10_optimize_cmp (insn);
}
/* Adjust branches if we added new instructions. */
if (insn_added_p)
shorten_branches (get_insns ());
}
s390_function_num_hotpatch_hw (current_function_decl, &hw_before, &hw_after);
if (hw_after > 0)
{
rtx_insn *insn;
/* Insert NOPs for hotpatching. */
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
/* Emit NOPs
1. inside the area covered by debug information to allow setting
breakpoints at the NOPs,
2. before any insn which results in an asm instruction,
3. before in-function labels to avoid jumping to the NOPs, for
example as part of a loop,
4. before any barrier in case the function is completely empty
(__builtin_unreachable ()) and has neither internal labels nor
active insns.
*/
if (active_insn_p (insn) || BARRIER_P (insn) || LABEL_P (insn))
break;
/* Output a series of NOPs before the first active insn. */
while (insn && hw_after > 0)
{
if (hw_after >= 3)
{
emit_insn_before (gen_nop_6_byte (), insn);
hw_after -= 3;
}
else if (hw_after >= 2)
{
emit_insn_before (gen_nop_4_byte (), insn);
hw_after -= 2;
}
else
{
emit_insn_before (gen_nop_2_byte (), insn);
hw_after -= 1;
}
}
}
}
/* Return true if INSN is a fp load insn writing register REGNO. */
static inline bool
s390_fpload_toreg (rtx_insn *insn, unsigned int regno)
{
rtx set;
enum attr_type flag = s390_safe_attr_type (insn);
if (flag != TYPE_FLOADSF && flag != TYPE_FLOADDF)
return false;
set = single_set (insn);
if (set == NULL_RTX)
return false;
if (!REG_P (SET_DEST (set)) || !MEM_P (SET_SRC (set)))
return false;
if (REGNO (SET_DEST (set)) != regno)
return false;
return true;
}
/* This value describes the distance to be avoided between an
arithmetic fp instruction and an fp load writing the same register.
Z10_EARLYLOAD_DISTANCE - 1 as well as Z10_EARLYLOAD_DISTANCE + 1 is
fine but the exact value has to be avoided. Otherwise the FP
pipeline will throw an exception causing a major penalty. */
#define Z10_EARLYLOAD_DISTANCE 7
/* Rearrange the ready list in order to avoid the situation described
for Z10_EARLYLOAD_DISTANCE. A problematic load instruction is
moved to the very end of the ready list. */
static void
s390_z10_prevent_earlyload_conflicts (rtx_insn **ready, int *nready_p)
{
unsigned int regno;
int nready = *nready_p;
rtx_insn *tmp;
int i;
rtx_insn *insn;
rtx set;
enum attr_type flag;
int distance;
/* Skip DISTANCE - 1 active insns. */
for (insn = last_scheduled_insn, distance = Z10_EARLYLOAD_DISTANCE - 1;
distance > 0 && insn != NULL_RTX;
distance--, insn = prev_active_insn (insn))
if (CALL_P (insn) || JUMP_P (insn))
return;
if (insn == NULL_RTX)
return;
set = single_set (insn);
if (set == NULL_RTX || !REG_P (SET_DEST (set))
|| GET_MODE_CLASS (GET_MODE (SET_DEST (set))) != MODE_FLOAT)
return;
flag = s390_safe_attr_type (insn);
if (flag == TYPE_FLOADSF || flag == TYPE_FLOADDF)
return;
regno = REGNO (SET_DEST (set));
i = nready - 1;
while (!s390_fpload_toreg (ready[i], regno) && i > 0)
i--;
if (!i)
return;
tmp = ready[i];
memmove (&ready[1], &ready[0], sizeof (rtx_insn *) * i);
ready[0] = tmp;
}
/* Returns TRUE if BB is entered via a fallthru edge and all other
incoming edges are less than likely. */
static bool
s390_bb_fallthru_entry_likely (basic_block bb)
{
edge e, fallthru_edge;
edge_iterator ei;
if (!bb)
return false;
fallthru_edge = find_fallthru_edge (bb->preds);
if (!fallthru_edge)
return false;
FOR_EACH_EDGE (e, ei, bb->preds)
if (e != fallthru_edge
&& e->probability >= profile_probability::likely ())
return false;
return true;
}
struct s390_sched_state
{
/* Number of insns in the group. */
int group_state;
/* Execution side of the group. */
int side;
/* Group can only hold two insns. */
bool group_of_two;
} s390_sched_state;
static struct s390_sched_state sched_state = {0, 1, false};
#define S390_SCHED_ATTR_MASK_CRACKED 0x1
#define S390_SCHED_ATTR_MASK_EXPANDED 0x2
#define S390_SCHED_ATTR_MASK_ENDGROUP 0x4
#define S390_SCHED_ATTR_MASK_GROUPALONE 0x8
#define S390_SCHED_ATTR_MASK_GROUPOFTWO 0x10
static unsigned int
s390_get_sched_attrmask (rtx_insn *insn)
{
unsigned int mask = 0;
switch (s390_tune)
{
case PROCESSOR_2827_ZEC12:
if (get_attr_zEC12_cracked (insn))
mask |= S390_SCHED_ATTR_MASK_CRACKED;
if (get_attr_zEC12_expanded (insn))
mask |= S390_SCHED_ATTR_MASK_EXPANDED;
if (get_attr_zEC12_endgroup (insn))
mask |= S390_SCHED_ATTR_MASK_ENDGROUP;
if (get_attr_zEC12_groupalone (insn))
mask |= S390_SCHED_ATTR_MASK_GROUPALONE;
break;
case PROCESSOR_2964_Z13:
if (get_attr_z13_cracked (insn))
mask |= S390_SCHED_ATTR_MASK_CRACKED;
if (get_attr_z13_expanded (insn))
mask |= S390_SCHED_ATTR_MASK_EXPANDED;
if (get_attr_z13_endgroup (insn))
mask |= S390_SCHED_ATTR_MASK_ENDGROUP;
if (get_attr_z13_groupalone (insn))
mask |= S390_SCHED_ATTR_MASK_GROUPALONE;
if (get_attr_z13_groupoftwo (insn))
mask |= S390_SCHED_ATTR_MASK_GROUPOFTWO;
break;
case PROCESSOR_3906_Z14:
if (get_attr_z14_cracked (insn))
mask |= S390_SCHED_ATTR_MASK_CRACKED;
if (get_attr_z14_expanded (insn))
mask |= S390_SCHED_ATTR_MASK_EXPANDED;
if (get_attr_z14_endgroup (insn))
mask |= S390_SCHED_ATTR_MASK_ENDGROUP;
if (get_attr_z14_groupalone (insn))
mask |= S390_SCHED_ATTR_MASK_GROUPALONE;
if (get_attr_z14_groupoftwo (insn))
mask |= S390_SCHED_ATTR_MASK_GROUPOFTWO;
break;
case PROCESSOR_8561_Z15:
case PROCESSOR_ARCH14:
if (get_attr_z15_cracked (insn))
mask |= S390_SCHED_ATTR_MASK_CRACKED;
if (get_attr_z15_expanded (insn))
mask |= S390_SCHED_ATTR_MASK_EXPANDED;
if (get_attr_z15_endgroup (insn))
mask |= S390_SCHED_ATTR_MASK_ENDGROUP;
if (get_attr_z15_groupalone (insn))
mask |= S390_SCHED_ATTR_MASK_GROUPALONE;
if (get_attr_z15_groupoftwo (insn))
mask |= S390_SCHED_ATTR_MASK_GROUPOFTWO;
break;
default:
gcc_unreachable ();
}
return mask;
}
static unsigned int
s390_get_unit_mask (rtx_insn *insn, int *units)
{
unsigned int mask = 0;
switch (s390_tune)
{
case PROCESSOR_2964_Z13:
*units = 4;
if (get_attr_z13_unit_lsu (insn))
mask |= 1 << 0;
if (get_attr_z13_unit_fxa (insn))
mask |= 1 << 1;
if (get_attr_z13_unit_fxb (insn))
mask |= 1 << 2;
if (get_attr_z13_unit_vfu (insn))
mask |= 1 << 3;
break;
case PROCESSOR_3906_Z14:
*units = 4;
if (get_attr_z14_unit_lsu (insn))
mask |= 1 << 0;
if (get_attr_z14_unit_fxa (insn))
mask |= 1 << 1;
if (get_attr_z14_unit_fxb (insn))
mask |= 1 << 2;
if (get_attr_z14_unit_vfu (insn))
mask |= 1 << 3;
break;
case PROCESSOR_8561_Z15:
case PROCESSOR_ARCH14:
*units = 4;
if (get_attr_z15_unit_lsu (insn))
mask |= 1 << 0;
if (get_attr_z15_unit_fxa (insn))
mask |= 1 << 1;
if (get_attr_z15_unit_fxb (insn))
mask |= 1 << 2;
if (get_attr_z15_unit_vfu (insn))
mask |= 1 << 3;
break;
default:
gcc_unreachable ();
}
return mask;
}
static bool
s390_is_fpd (rtx_insn *insn)
{
if (insn == NULL_RTX)
return false;
return get_attr_z13_unit_fpd (insn) || get_attr_z14_unit_fpd (insn)
|| get_attr_z15_unit_fpd (insn);
}
static bool
s390_is_fxd (rtx_insn *insn)
{
if (insn == NULL_RTX)
return false;
return get_attr_z13_unit_fxd (insn) || get_attr_z14_unit_fxd (insn)
|| get_attr_z15_unit_fxd (insn);
}
/* Returns TRUE if INSN is a long-running instruction. */
static bool
s390_is_longrunning (rtx_insn *insn)
{
if (insn == NULL_RTX)
return false;
return s390_is_fxd (insn) || s390_is_fpd (insn);
}
/* Return the scheduling score for INSN. The higher the score the
better. The score is calculated from the OOO scheduling attributes
of INSN and the scheduling state sched_state. */
static int
s390_sched_score (rtx_insn *insn)
{
unsigned int mask = s390_get_sched_attrmask (insn);
int score = 0;
switch (sched_state.group_state)
{
case 0:
/* Try to put insns into the first slot which would otherwise
break a group. */
if ((mask & S390_SCHED_ATTR_MASK_CRACKED) != 0
|| (mask & S390_SCHED_ATTR_MASK_EXPANDED) != 0)
score += 5;
if ((mask & S390_SCHED_ATTR_MASK_GROUPALONE) != 0)
score += 10;
break;
case 1:
/* Prefer not cracked insns while trying to put together a
group. */
if ((mask & S390_SCHED_ATTR_MASK_CRACKED) == 0
&& (mask & S390_SCHED_ATTR_MASK_EXPANDED) == 0
&& (mask & S390_SCHED_ATTR_MASK_GROUPALONE) == 0)
score += 10;
if ((mask & S390_SCHED_ATTR_MASK_ENDGROUP) == 0)
score += 5;
/* If we are in a group of two already, try to schedule another
group-of-two insn to avoid shortening another group. */
if (sched_state.group_of_two
&& (mask & S390_SCHED_ATTR_MASK_GROUPOFTWO) != 0)
score += 15;
break;
case 2:
/* Prefer not cracked insns while trying to put together a
group. */
if ((mask & S390_SCHED_ATTR_MASK_CRACKED) == 0
&& (mask & S390_SCHED_ATTR_MASK_EXPANDED) == 0
&& (mask & S390_SCHED_ATTR_MASK_GROUPALONE) == 0)
score += 10;
/* Prefer endgroup insns in the last slot. */
if ((mask & S390_SCHED_ATTR_MASK_ENDGROUP) != 0)
score += 10;
/* Try to avoid group-of-two insns in the last slot as they will
shorten this group as well as the next one. */
if ((mask & S390_SCHED_ATTR_MASK_GROUPOFTWO) != 0)
score = MAX (0, score - 15);
break;
}
if (s390_tune >= PROCESSOR_2964_Z13)
{
int units, i;
unsigned unit_mask, m = 1;
unit_mask = s390_get_unit_mask (insn, &units);
gcc_assert (units <= MAX_SCHED_UNITS);
/* Add a score in range 0..MAX_SCHED_MIX_SCORE depending on how long
ago the last insn of this unit type got scheduled. This is
supposed to help providing a proper instruction mix to the
CPU. */
for (i = 0; i < units; i++, m <<= 1)
if (m & unit_mask)
score += (last_scheduled_unit_distance[i][sched_state.side]
* MAX_SCHED_MIX_SCORE / MAX_SCHED_MIX_DISTANCE);
int other_side = 1 - sched_state.side;
/* Try to delay long-running insns when side is busy. */
if (s390_is_longrunning (insn))
{
if (s390_is_fxd (insn))
{
if (fxd_longrunning[sched_state.side]
&& fxd_longrunning[other_side]
<= fxd_longrunning[sched_state.side])
score = MAX (0, score - 10);
else if (fxd_longrunning[other_side]
>= fxd_longrunning[sched_state.side])
score += 10;
}
if (s390_is_fpd (insn))
{
if (fpd_longrunning[sched_state.side]
&& fpd_longrunning[other_side]
<= fpd_longrunning[sched_state.side])
score = MAX (0, score - 10);
else if (fpd_longrunning[other_side]
>= fpd_longrunning[sched_state.side])
score += 10;
}
}
}
return score;
}
/* This function is called via hook TARGET_SCHED_REORDER before
issuing one insn from list READY which contains *NREADYP entries.
For target z10 it reorders load instructions to avoid early load
conflicts in the floating point pipeline */
static int
s390_sched_reorder (FILE *file, int verbose,
rtx_insn **ready, int *nreadyp, int clock ATTRIBUTE_UNUSED)
{
if (s390_tune == PROCESSOR_2097_Z10
&& reload_completed
&& *nreadyp > 1)
s390_z10_prevent_earlyload_conflicts (ready, nreadyp);
if (s390_tune >= PROCESSOR_2827_ZEC12
&& reload_completed
&& *nreadyp > 1)
{
int i;
int last_index = *nreadyp - 1;
int max_index = -1;
int max_score = -1;
rtx_insn *tmp;
/* Just move the insn with the highest score to the top (the
end) of the list. A full sort is not needed since a conflict
in the hazard recognition cannot happen. So the top insn in
the ready list will always be taken. */
for (i = last_index; i >= 0; i--)
{
int score;
if (recog_memoized (ready[i]) < 0)
continue;
score = s390_sched_score (ready[i]);
if (score > max_score)
{
max_score = score;
max_index = i;
}
}
if (max_index != -1)
{
if (max_index != last_index)
{
tmp = ready[max_index];
ready[max_index] = ready[last_index];
ready[last_index] = tmp;
if (verbose > 5)
fprintf (file,
";;\t\tBACKEND: move insn %d to the top of list\n",
INSN_UID (ready[last_index]));
}
else if (verbose > 5)
fprintf (file,
";;\t\tBACKEND: best insn %d already on top\n",
INSN_UID (ready[last_index]));
}
if (verbose > 5)
{
fprintf (file, "ready list ooo attributes - sched state: %d\n",
sched_state.group_state);
for (i = last_index; i >= 0; i--)
{
unsigned int sched_mask;
rtx_insn *insn = ready[i];
if (recog_memoized (insn) < 0)
continue;
sched_mask = s390_get_sched_attrmask (insn);
fprintf (file, ";;\t\tBACKEND: insn %d score: %d: ",
INSN_UID (insn),
s390_sched_score (insn));
#define PRINT_SCHED_ATTR(M, ATTR) fprintf (file, "%s ",\
((M) & sched_mask) ? #ATTR : "");
PRINT_SCHED_ATTR (S390_SCHED_ATTR_MASK_CRACKED, cracked);
PRINT_SCHED_ATTR (S390_SCHED_ATTR_MASK_EXPANDED, expanded);
PRINT_SCHED_ATTR (S390_SCHED_ATTR_MASK_ENDGROUP, endgroup);
PRINT_SCHED_ATTR (S390_SCHED_ATTR_MASK_GROUPALONE, groupalone);
#undef PRINT_SCHED_ATTR
if (s390_tune >= PROCESSOR_2964_Z13)
{
unsigned int unit_mask, m = 1;
int units, j;
unit_mask = s390_get_unit_mask (insn, &units);
fprintf (file, "(units:");
for (j = 0; j < units; j++, m <<= 1)
if (m & unit_mask)
fprintf (file, " u%d", j);
fprintf (file, ")");
}
fprintf (file, "\n");
}
}
}
return s390_issue_rate ();
}
/* This function is called via hook TARGET_SCHED_VARIABLE_ISSUE after
the scheduler has issued INSN. It stores the last issued insn into
last_scheduled_insn in order to make it available for
s390_sched_reorder. */
static int
s390_sched_variable_issue (FILE *file, int verbose, rtx_insn *insn, int more)
{
last_scheduled_insn = insn;
bool ends_group = false;
if (s390_tune >= PROCESSOR_2827_ZEC12
&& reload_completed
&& recog_memoized (insn) >= 0)
{
unsigned int mask = s390_get_sched_attrmask (insn);
if ((mask & S390_SCHED_ATTR_MASK_GROUPOFTWO) != 0)
sched_state.group_of_two = true;
/* If this is a group-of-two insn, we actually ended the last group
and this insn is the first one of the new group. */
if (sched_state.group_state == 2 && sched_state.group_of_two)
{
sched_state.side = sched_state.side ? 0 : 1;
sched_state.group_state = 0;
}
/* Longrunning and side bookkeeping. */
for (int i = 0; i < 2; i++)
{
fxd_longrunning[i] = MAX (0, fxd_longrunning[i] - 1);
fpd_longrunning[i] = MAX (0, fpd_longrunning[i] - 1);
}
unsigned latency = insn_default_latency (insn);
if (s390_is_longrunning (insn))
{
if (s390_is_fxd (insn))
fxd_longrunning[sched_state.side] = latency;
else
fpd_longrunning[sched_state.side] = latency;
}
if (s390_tune >= PROCESSOR_2964_Z13)
{
int units, i;
unsigned unit_mask, m = 1;
unit_mask = s390_get_unit_mask (insn, &units);
gcc_assert (units <= MAX_SCHED_UNITS);
for (i = 0; i < units; i++, m <<= 1)
if (m & unit_mask)
last_scheduled_unit_distance[i][sched_state.side] = 0;
else if (last_scheduled_unit_distance[i][sched_state.side]
< MAX_SCHED_MIX_DISTANCE)
last_scheduled_unit_distance[i][sched_state.side]++;
}
if ((mask & S390_SCHED_ATTR_MASK_CRACKED) != 0
|| (mask & S390_SCHED_ATTR_MASK_EXPANDED) != 0
|| (mask & S390_SCHED_ATTR_MASK_GROUPALONE) != 0
|| (mask & S390_SCHED_ATTR_MASK_ENDGROUP) != 0)
{
sched_state.group_state = 0;
ends_group = true;
}
else
{
switch (sched_state.group_state)
{
case 0:
sched_state.group_state++;
break;
case 1:
sched_state.group_state++;
if (sched_state.group_of_two)
{
sched_state.group_state = 0;
ends_group = true;
}
break;
case 2:
sched_state.group_state++;
ends_group = true;
break;
}
}
if (verbose > 5)
{
unsigned int sched_mask;
sched_mask = s390_get_sched_attrmask (insn);
fprintf (file, ";;\t\tBACKEND: insn %d: ", INSN_UID (insn));
#define PRINT_SCHED_ATTR(M, ATTR) fprintf (file, "%s ", ((M) & sched_mask) ? #ATTR : "");
PRINT_SCHED_ATTR (S390_SCHED_ATTR_MASK_CRACKED, cracked);
PRINT_SCHED_ATTR (S390_SCHED_ATTR_MASK_EXPANDED, expanded);
PRINT_SCHED_ATTR (S390_SCHED_ATTR_MASK_ENDGROUP, endgroup);
PRINT_SCHED_ATTR (S390_SCHED_ATTR_MASK_GROUPALONE, groupalone);
#undef PRINT_SCHED_ATTR
if (s390_tune >= PROCESSOR_2964_Z13)
{
unsigned int unit_mask, m = 1;
int units, j;
unit_mask = s390_get_unit_mask (insn, &units);
fprintf (file, "(units:");
for (j = 0; j < units; j++, m <<= 1)
if (m & unit_mask)
fprintf (file, " %d", j);
fprintf (file, ")");
}
fprintf (file, " sched state: %d\n", sched_state.group_state);
if (s390_tune >= PROCESSOR_2964_Z13)
{
int units, j;
s390_get_unit_mask (insn, &units);
fprintf (file, ";;\t\tBACKEND: units on this side unused for: ");
for (j = 0; j < units; j++)
fprintf (file, "%d:%d ", j,
last_scheduled_unit_distance[j][sched_state.side]);
fprintf (file, "\n");
}
}
/* If this insn ended a group, the next will be on the other side. */
if (ends_group)
{
sched_state.group_state = 0;
sched_state.side = sched_state.side ? 0 : 1;
sched_state.group_of_two = false;
}
}
if (GET_CODE (PATTERN (insn)) != USE
&& GET_CODE (PATTERN (insn)) != CLOBBER)
return more - 1;
else
return more;
}
static void
s390_sched_init (FILE *file ATTRIBUTE_UNUSED,
int verbose ATTRIBUTE_UNUSED,
int max_ready ATTRIBUTE_UNUSED)
{
/* If the next basic block is most likely entered via a fallthru edge
we keep the last sched state. Otherwise we start a new group.
The scheduler traverses basic blocks in "instruction stream" ordering
so if we see a fallthru edge here, sched_state will be of its
source block.
current_sched_info->prev_head is the insn before the first insn of the
block of insns to be scheduled.
*/
rtx_insn *insn = current_sched_info->prev_head
? NEXT_INSN (current_sched_info->prev_head) : NULL;
basic_block bb = insn ? BLOCK_FOR_INSN (insn) : NULL;
if (s390_tune < PROCESSOR_2964_Z13 || !s390_bb_fallthru_entry_likely (bb))
{
last_scheduled_insn = NULL;
memset (last_scheduled_unit_distance, 0,
MAX_SCHED_UNITS * NUM_SIDES * sizeof (int));
sched_state.group_state = 0;
sched_state.group_of_two = false;
}
}
/* This target hook implementation for TARGET_LOOP_UNROLL_ADJUST calculates
a new number struct loop *loop should be unrolled if tuned for cpus with
a built-in stride prefetcher.
The loop is analyzed for memory accesses by calling check_dpu for
each rtx of the loop. Depending on the loop_depth and the amount of
memory accesses a new number <=nunroll is returned to improve the
behavior of the hardware prefetch unit. */
static unsigned
s390_loop_unroll_adjust (unsigned nunroll, struct loop *loop)
{
basic_block *bbs;
rtx_insn *insn;
unsigned i;
unsigned mem_count = 0;
if (s390_tune < PROCESSOR_2097_Z10)
return nunroll;
/* Count the number of memory references within the loop body. */
bbs = get_loop_body (loop);
subrtx_iterator::array_type array;
for (i = 0; i < loop->num_nodes; i++)
FOR_BB_INSNS (bbs[i], insn)
if (INSN_P (insn) && INSN_CODE (insn) != -1)
{
rtx set;
/* The runtime of small loops with memory block operations
will be determined by the memory operation. Doing
unrolling doesn't help here. Measurements to confirm
this where only done on recent CPU levels. So better do
not change anything for older CPUs. */
if (s390_tune >= PROCESSOR_2964_Z13
&& loop->ninsns <= BLOCK_MEM_OPS_LOOP_INSNS
&& ((set = single_set (insn)) != NULL_RTX)
&& ((GET_MODE (SET_DEST (set)) == BLKmode
&& (GET_MODE (SET_SRC (set)) == BLKmode
|| SET_SRC (set) == const0_rtx))
|| (GET_CODE (SET_SRC (set)) == COMPARE
&& GET_MODE (XEXP (SET_SRC (set), 0)) == BLKmode
&& GET_MODE (XEXP (SET_SRC (set), 1)) == BLKmode)))
return 1;
FOR_EACH_SUBRTX (iter, array, PATTERN (insn), NONCONST)
if (MEM_P (*iter))
mem_count += 1;
}
free (bbs);
/* Prevent division by zero, and we do not need to adjust nunroll in this case. */
if (mem_count == 0)
return nunroll;
switch (loop_depth(loop))
{
case 1:
return MIN (nunroll, 28 / mem_count);
case 2:
return MIN (nunroll, 22 / mem_count);
default:
return MIN (nunroll, 16 / mem_count);
}
}
/* Restore the current options. This is a hook function and also called
internally. */
static void
s390_function_specific_restore (struct gcc_options *opts,
struct gcc_options */* opts_set */,
struct cl_target_option *ptr ATTRIBUTE_UNUSED)
{
opts->x_s390_cost_pointer = (long)processor_table[opts->x_s390_tune].cost;
}
static void
s390_default_align (struct gcc_options *opts)
{
/* Set the default function alignment to 16 in order to get rid of
some unwanted performance effects. */
if (opts->x_flag_align_functions && !opts->x_str_align_functions
&& opts->x_s390_tune >= PROCESSOR_2964_Z13)
opts->x_str_align_functions = "16";
}
static void
s390_override_options_after_change (void)
{
s390_default_align (&global_options);
}
static void
s390_option_override_internal (struct gcc_options *opts,
struct gcc_options *opts_set)
{
/* Architecture mode defaults according to ABI. */
if (!(opts_set->x_target_flags & MASK_ZARCH))
{
if (TARGET_64BIT)
opts->x_target_flags |= MASK_ZARCH;
else
opts->x_target_flags &= ~MASK_ZARCH;
}
/* Set the march default in case it hasn't been specified on cmdline. */
if (!opts_set->x_s390_arch)
opts->x_s390_arch = PROCESSOR_2064_Z900;
opts->x_s390_arch_flags = processor_flags_table[(int) opts->x_s390_arch];
/* Determine processor to tune for. */
if (!opts_set->x_s390_tune)
opts->x_s390_tune = opts->x_s390_arch;
opts->x_s390_tune_flags = processor_flags_table[opts->x_s390_tune];
/* Sanity checks. */
if (opts->x_s390_arch == PROCESSOR_NATIVE
|| opts->x_s390_tune == PROCESSOR_NATIVE)
gcc_unreachable ();
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;
}
/* 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))
{
if (TARGET_CPU_HTM_P (opts) && TARGET_ZARCH_P (opts->x_target_flags))
opts->x_target_flags |= MASK_OPT_HTM;
else
opts->x_target_flags &= ~MASK_OPT_HTM;
}
if (TARGET_OPT_VX_P (opts_set->x_target_flags))
{
if (TARGET_OPT_VX_P (opts->x_target_flags))
{
if (!TARGET_CPU_VX_P (opts))
error ("hardware vector support not available on %s",
processor_table[(int)opts->x_s390_arch].name);
if (TARGET_SOFT_FLOAT_P (opts->x_target_flags))
error ("hardware vector support not available with "
"%<-msoft-float%>");
}
}
else
{
if (TARGET_CPU_VX_P (opts))
/* Enable vector support if available and not explicitly disabled
by user. E.g. with -m31 -march=z13 -mzarch */
opts->x_target_flags |= MASK_OPT_VX;
else
opts->x_target_flags &= ~MASK_OPT_VX;
}
/* Use hardware DFP if available and not explicitly disabled by
user. E.g. with -m31 -march=z10 -mzarch */
if (!TARGET_HARD_DFP_P (opts_set->x_target_flags))
{
if (TARGET_DFP_P (opts))
opts->x_target_flags |= MASK_HARD_DFP;
else
opts->x_target_flags &= ~MASK_HARD_DFP;
}
if (TARGET_HARD_DFP_P (opts->x_target_flags) && !TARGET_DFP_P (opts))
{
if (TARGET_HARD_DFP_P (opts_set->x_target_flags))
{
if (!TARGET_CPU_DFP_P (opts))
error ("hardware decimal floating point instructions"
" not available on %s",
processor_table[(int)opts->x_s390_arch].name);
if (!TARGET_ZARCH_P (opts->x_target_flags))
error ("hardware decimal floating point instructions"
" not available in ESA/390 mode");
}
else
opts->x_target_flags &= ~MASK_HARD_DFP;
}
if (TARGET_SOFT_FLOAT_P (opts_set->x_target_flags)
&& TARGET_SOFT_FLOAT_P (opts->x_target_flags))
{
if (TARGET_HARD_DFP_P (opts_set->x_target_flags)
&& TARGET_HARD_DFP_P (opts->x_target_flags))
error ("%<-mhard-dfp%> can%'t be used in conjunction with "
"%<-msoft-float%>");
opts->x_target_flags &= ~MASK_HARD_DFP;
}
if (TARGET_BACKCHAIN_P (opts->x_target_flags)
&& TARGET_PACKED_STACK_P (opts->x_target_flags)
&& TARGET_HARD_FLOAT_P (opts->x_target_flags))
error ("%<-mbackchain%> %<-mpacked-stack%> %<-mhard-float%> are not "
"supported in combination");
if (opts->x_s390_stack_size)
{
if (opts->x_s390_stack_guard >= opts->x_s390_stack_size)
error ("stack size must be greater than the stack guard value");
else if (opts->x_s390_stack_size > 1 << 16)
error ("stack size must not be greater than 64k");
}
else if (opts->x_s390_stack_guard)
error ("%<-mstack-guard%> implies use of %<-mstack-size%>");
/* Our implementation of the stack probe requires the probe interval
to be used as displacement in an address operand. The maximum
probe interval currently is 64k. This would exceed short
displacements. Trim that value down to 4k if that happens. This
might result in too many probes being generated only on the
oldest supported machine level z900. */
if (!DISP_IN_RANGE ((1 << param_stack_clash_protection_probe_interval)))
param_stack_clash_protection_probe_interval = 12;
#if TARGET_TPF != 0
if (!CONST_OK_FOR_J (opts->x_s390_tpf_trace_hook_prologue_check))
error ("-mtpf-trace-hook-prologue-check requires integer in range 0..4095");
if (!CONST_OK_FOR_J (opts->x_s390_tpf_trace_hook_prologue_target))
error ("-mtpf-trace-hook-prologue-target requires integer in range 0..4095");
if (!CONST_OK_FOR_J (opts->x_s390_tpf_trace_hook_epilogue_check))
error ("-mtpf-trace-hook-epilogue-check requires integer in range 0..4095");
if (!CONST_OK_FOR_J (opts->x_s390_tpf_trace_hook_epilogue_target))
error ("-mtpf-trace-hook-epilogue-target requires integer in range 0..4095");
if (s390_tpf_trace_skip)
{
opts->x_s390_tpf_trace_hook_prologue_target = TPF_TRACE_PROLOGUE_SKIP_TARGET;
opts->x_s390_tpf_trace_hook_epilogue_target = TPF_TRACE_EPILOGUE_SKIP_TARGET;
}
#endif
#ifdef TARGET_DEFAULT_LONG_DOUBLE_128
if (!TARGET_LONG_DOUBLE_128_P (opts_set->x_target_flags))
opts->x_target_flags |= MASK_LONG_DOUBLE_128;
#endif
if (opts->x_s390_tune >= PROCESSOR_2097_Z10)
{
SET_OPTION_IF_UNSET (opts, opts_set, param_max_unrolled_insns,
100);
SET_OPTION_IF_UNSET (opts, opts_set, param_max_unroll_times, 32);
SET_OPTION_IF_UNSET (opts, opts_set, param_max_completely_peeled_insns,
2000);
SET_OPTION_IF_UNSET (opts, opts_set, param_max_completely_peel_times,
64);
}
SET_OPTION_IF_UNSET (opts, opts_set, param_max_pending_list_length,
256);
/* values for loop prefetching */
SET_OPTION_IF_UNSET (opts, opts_set, param_l1_cache_line_size, 256);
SET_OPTION_IF_UNSET (opts, opts_set, param_l1_cache_size, 128);
/* s390 has more than 2 levels and the size is much larger. Since
we are always running virtualized assume that we only get a small
part of the caches above l1. */
SET_OPTION_IF_UNSET (opts, opts_set, param_l2_cache_size, 1500);
SET_OPTION_IF_UNSET (opts, opts_set,
param_prefetch_min_insn_to_mem_ratio, 2);
SET_OPTION_IF_UNSET (opts, opts_set, param_simultaneous_prefetches, 6);
/* Use the alternative scheduling-pressure algorithm by default. */
SET_OPTION_IF_UNSET (opts, opts_set, param_sched_pressure_algorithm, 2);
SET_OPTION_IF_UNSET (opts, opts_set, param_min_vect_loop_bound, 2);
/* Set the default alignment. */
s390_default_align (opts);
/* Call target specific restore function to do post-init work. At the moment,
this just sets opts->x_s390_cost_pointer. */
s390_function_specific_restore (opts, opts_set, NULL);
/* Check whether -mfentry is supported. It cannot be used in 31-bit mode,
because 31-bit PLT stubs assume that %r12 contains GOT address, which is
not the case when the code runs before the prolog. */
if (opts->x_flag_fentry && !TARGET_64BIT)
error ("%<-mfentry%> is supported only for 64-bit CPUs");
}
static void
s390_option_override (void)
{
unsigned int i;
cl_deferred_option *opt;
vec<cl_deferred_option> *v =
(vec<cl_deferred_option> *) s390_deferred_options;
if (v)
FOR_EACH_VEC_ELT (*v, i, opt)
{
switch (opt->opt_index)
{
case OPT_mhotpatch_:
{
int val1;
int val2;
char *s = strtok (ASTRDUP (opt->arg), ",");
char *t = strtok (NULL, "\0");
if (t != NULL)
{
val1 = integral_argument (s);
val2 = integral_argument (t);
}
else
{
val1 = -1;
val2 = -1;
}
if (val1 == -1 || val2 == -1)
{
/* argument is not a plain number */
error ("arguments to %qs should be non-negative integers",
"-mhotpatch=n,m");
break;
}
else if (val1 > s390_hotpatch_hw_max
|| val2 > s390_hotpatch_hw_max)
{
error ("argument to %qs is too large (max. %d)",
"-mhotpatch=n,m", s390_hotpatch_hw_max);
break;
}
s390_hotpatch_hw_before_label = val1;
s390_hotpatch_hw_after_label = val2;
break;
}
default:
gcc_unreachable ();
}
}
/* Set up function hooks. */
init_machine_status = s390_init_machine_status;
s390_option_override_internal (&global_options, &global_options_set);
/* Save the initial options in case the user does function specific
options. */
target_option_default_node
= build_target_option_node (&global_options, &global_options_set);
target_option_current_node = target_option_default_node;
/* This cannot reside in s390_option_optimization_table since HAVE_prefetch
requires the arch flags to be evaluated already. Since prefetching
is beneficial on s390, we enable it if available. */
if (flag_prefetch_loop_arrays < 0 && HAVE_prefetch && optimize >= 3)
flag_prefetch_loop_arrays = 1;
if (!s390_pic_data_is_text_relative && !flag_pic)
error ("%<-mno-pic-data-is-text-relative%> cannot be used without "
"%<-fpic%>/%<-fPIC%>");
if (TARGET_TPF)
{
/* Don't emit DWARF3/4 unless specifically selected. The TPF
debuggers do not yet support DWARF 3/4. */
if (!OPTION_SET_P (dwarf_strict))
dwarf_strict = 1;
if (!OPTION_SET_P (dwarf_version))
dwarf_version = 2;
}
}
#if S390_USE_TARGET_ATTRIBUTE
/* Inner function to process the attribute((target(...))), take an argument and
set the current options from the argument. If we have a list, recursively go
over the list. */
static bool
s390_valid_target_attribute_inner_p (tree args,
struct gcc_options *opts,
struct gcc_options *new_opts_set,
bool force_pragma)
{
char *next_optstr;
bool ret = true;
#define S390_ATTRIB(S,O,A) { S, sizeof (S)-1, O, A, 0 }
#define S390_PRAGMA(S,O,A) { S, sizeof (S)-1, O, A, 1 }
static const struct
{
const char *string;
size_t len;
int opt;
int has_arg;
int only_as_pragma;
} attrs[] = {
/* enum options */
S390_ATTRIB ("arch=", OPT_march_, 1),
S390_ATTRIB ("tune=", OPT_mtune_, 1),
/* uinteger options */
S390_ATTRIB ("stack-guard=", OPT_mstack_guard_, 1),
S390_ATTRIB ("stack-size=", OPT_mstack_size_, 1),
S390_ATTRIB ("branch-cost=", OPT_mbranch_cost_, 1),
S390_ATTRIB ("warn-framesize=", OPT_mwarn_framesize_, 1),
/* flag options */
S390_ATTRIB ("backchain", OPT_mbackchain, 0),
S390_ATTRIB ("hard-dfp", OPT_mhard_dfp, 0),
S390_ATTRIB ("hard-float", OPT_mhard_float, 0),
S390_ATTRIB ("htm", OPT_mhtm, 0),
S390_ATTRIB ("vx", OPT_mvx, 0),
S390_ATTRIB ("packed-stack", OPT_mpacked_stack, 0),
S390_ATTRIB ("small-exec", OPT_msmall_exec, 0),
S390_ATTRIB ("soft-float", OPT_msoft_float, 0),
S390_ATTRIB ("mvcle", OPT_mmvcle, 0),
S390_PRAGMA ("zvector", OPT_mzvector, 0),
/* boolean options */
S390_ATTRIB ("warn-dynamicstack", OPT_mwarn_dynamicstack, 0),
};
#undef S390_ATTRIB
#undef S390_PRAGMA
/* If this is a list, recurse to get the options. */
if (TREE_CODE (args) == TREE_LIST)
{
bool ret = true;
int num_pragma_values;
int i;
/* Note: attribs.c:decl_attributes prepends the values from
current_target_pragma to the list of target attributes. To determine
whether we're looking at a value of the attribute or the pragma we
assume that the first [list_length (current_target_pragma)] values in
the list are the values from the pragma. */
num_pragma_values = (!force_pragma && current_target_pragma != NULL)
? list_length (current_target_pragma) : 0;
for (i = 0; args; args = TREE_CHAIN (args), i++)
{
bool is_pragma;
is_pragma = (force_pragma || i < num_pragma_values);
if (TREE_VALUE (args)
&& !s390_valid_target_attribute_inner_p (TREE_VALUE (args),
opts, new_opts_set,
is_pragma))
{
ret = false;
}
}
return ret;
}
else if (TREE_CODE (args) != STRING_CST)
{
error ("attribute %<target%> argument not a string");
return false;
}
/* Handle multiple arguments separated by commas. */
next_optstr = ASTRDUP (TREE_STRING_POINTER (args));
while (next_optstr && *next_optstr != '\0')
{
char *p = next_optstr;
char *orig_p = p;
char *comma = strchr (next_optstr, ',');
size_t len, opt_len;
int opt;
bool opt_set_p;
char ch;
unsigned i;
int mask = 0;
enum cl_var_type var_type;
bool found;
if (comma)
{
*comma = '\0';
len = comma - next_optstr;
next_optstr = comma + 1;
}
else
{
len = strlen (p);
next_optstr = NULL;
}
/* Recognize no-xxx. */
if (len > 3 && p[0] == 'n' && p[1] == 'o' && p[2] == '-')
{
opt_set_p = false;
p += 3;
len -= 3;
}
else
opt_set_p = true;
/* Find the option. */
ch = *p;
found = false;
for (i = 0; i < ARRAY_SIZE (attrs); i++)
{
opt_len = attrs[i].len;
if (ch == attrs[i].string[0]
&& ((attrs[i].has_arg) ? len > opt_len : len == opt_len)
&& memcmp (p, attrs[i].string, opt_len) == 0)
{
opt = attrs[i].opt;
if (!opt_set_p && cl_options[opt].cl_reject_negative)
continue;
mask = cl_options[opt].var_value;
var_type = cl_options[opt].var_type;
found = true;
break;
}
}
/* Process the option. */
if (!found)
{
error ("attribute(target(\"%s\")) is unknown", orig_p);
return false;
}
else if (attrs[i].only_as_pragma && !force_pragma)
{
/* Value is not allowed for the target attribute. */
error ("value %qs is not supported by attribute %<target%>",
attrs[i].string);
return false;
}
else if (var_type == CLVC_BIT_SET || var_type == CLVC_BIT_CLEAR)
{
if (var_type == CLVC_BIT_CLEAR)
opt_set_p = !opt_set_p;
if (opt_set_p)
opts->x_target_flags |= mask;
else
opts->x_target_flags &= ~mask;
new_opts_set->x_target_flags |= mask;
}
else if (cl_options[opt].var_type == CLVC_BOOLEAN)
{
int value;
if (cl_options[opt].cl_uinteger)
{
/* Unsigned integer argument. Code based on the function
decode_cmdline_option () in opts-common.c. */
value = integral_argument (p + opt_len);
}
else
value = (opt_set_p) ? 1 : 0;
if (value != -1)
{
struct cl_decoded_option decoded;
/* Value range check; only implemented for numeric and boolean
options at the moment. */
generate_option (opt, NULL, value, CL_TARGET, &decoded);
s390_handle_option (opts, new_opts_set, &decoded, input_location);
set_option (opts, new_opts_set, opt, value,
p + opt_len, DK_UNSPECIFIED, input_location,
global_dc);
}
else
{
error ("attribute(target(\"%s\")) is unknown", orig_p);
ret = false;
}
}
else if (cl_options[opt].var_type == CLVC_ENUM)
{
bool arg_ok;
int value;
arg_ok = opt_enum_arg_to_value (opt, p + opt_len, &value, CL_TARGET);
if (arg_ok)
set_option (opts, new_opts_set, opt, value,
p + opt_len, DK_UNSPECIFIED, input_location,
global_dc);
else
{
error ("attribute(target(\"%s\")) is unknown", orig_p);
ret = false;
}
}
else
gcc_unreachable ();
}
return ret;
}
/* Return a TARGET_OPTION_NODE tree of the target options listed or NULL. */
tree
s390_valid_target_attribute_tree (tree args,
struct gcc_options *opts,
const struct gcc_options *opts_set,
bool force_pragma)
{
tree t = NULL_TREE;
struct gcc_options new_opts_set;
memset (&new_opts_set, 0, sizeof (new_opts_set));
/* Process each of the options on the chain. */
if (! s390_valid_target_attribute_inner_p (args, opts, &new_opts_set,
force_pragma))
return error_mark_node;
/* If some option was set (even if it has not changed), rerun
s390_option_override_internal, and then save the options away. */
if (new_opts_set.x_target_flags
|| new_opts_set.x_s390_arch
|| new_opts_set.x_s390_tune
|| new_opts_set.x_s390_stack_guard
|| new_opts_set.x_s390_stack_size
|| new_opts_set.x_s390_branch_cost
|| new_opts_set.x_s390_warn_framesize
|| new_opts_set.x_s390_warn_dynamicstack_p)
{
const unsigned char *src = (const unsigned char *)opts_set;
unsigned char *dest = (unsigned char *)&new_opts_set;
unsigned int i;
/* Merge the original option flags into the new ones. */
for (i = 0; i < sizeof(*opts_set); i++)
dest[i] |= src[i];
/* Do any overrides, such as arch=xxx, or tune=xxx support. */
s390_option_override_internal (opts, &new_opts_set);
/* Save the current options unless we are validating options for
#pragma. */
t = build_target_option_node (opts, &new_opts_set);
}
return t;
}
/* Hook to validate attribute((target("string"))). */
static bool
s390_valid_target_attribute_p (tree fndecl,
tree ARG_UNUSED (name),
tree args,
int ARG_UNUSED (flags))
{
struct gcc_options func_options, func_options_set;
tree new_target, new_optimize;
bool ret = true;
/* attribute((target("default"))) does nothing, beyond
affecting multi-versioning. */
if (TREE_VALUE (args)
&& TREE_CODE (TREE_VALUE (args)) == STRING_CST
&& TREE_CHAIN (args) == NULL_TREE
&& strcmp (TREE_STRING_POINTER (TREE_VALUE (args)), "default") == 0)
return true;
tree old_optimize
= build_optimization_node (&global_options, &global_options_set);
/* Get the optimization options of the current function. */
tree func_optimize = DECL_FUNCTION_SPECIFIC_OPTIMIZATION (fndecl);
if (!func_optimize)
func_optimize = old_optimize;
/* Init func_options. */
memset (&func_options, 0, sizeof (func_options));
init_options_struct (&func_options, NULL);
lang_hooks.init_options_struct (&func_options);
memset (&func_options_set, 0, sizeof (func_options_set));
cl_optimization_restore (&func_options, &func_options_set,
TREE_OPTIMIZATION (func_optimize));
/* Initialize func_options to the default before its target options can
be set. */
cl_target_option_restore (&func_options, &func_options_set,
TREE_TARGET_OPTION (target_option_default_node));
new_target = s390_valid_target_attribute_tree (args, &func_options,
&global_options_set,
(args ==
current_target_pragma));
new_optimize = build_optimization_node (&func_options, &func_options_set);
if (new_target == error_mark_node)
ret = false;
else if (fndecl && new_target)
{
DECL_FUNCTION_SPECIFIC_TARGET (fndecl) = new_target;
if (old_optimize != new_optimize)
DECL_FUNCTION_SPECIFIC_OPTIMIZATION (fndecl) = new_optimize;
}
return ret;
}
/* Hook to determine if one function can safely inline another. */
static bool
s390_can_inline_p (tree caller, tree callee)
{
tree caller_tree = DECL_FUNCTION_SPECIFIC_TARGET (caller);
tree callee_tree = DECL_FUNCTION_SPECIFIC_TARGET (callee);
if (!callee_tree)
callee_tree = target_option_default_node;
if (!caller_tree)
caller_tree = target_option_default_node;
if (callee_tree == caller_tree)
return true;
struct cl_target_option *caller_opts = TREE_TARGET_OPTION (caller_tree);
struct cl_target_option *callee_opts = TREE_TARGET_OPTION (callee_tree);
bool ret = true;
if ((caller_opts->x_target_flags & ~(MASK_SOFT_FLOAT | MASK_HARD_DFP))
!= (callee_opts->x_target_flags & ~(MASK_SOFT_FLOAT | MASK_HARD_DFP)))
ret = false;
/* Don't inline functions to be compiled for a more recent arch into a
function for an older arch. */
else if (caller_opts->x_s390_arch < callee_opts->x_s390_arch)
ret = false;
/* Inlining a hard float function into a soft float function is only
allowed if the hard float function doesn't actually make use of
floating point.
We are called from FEs for multi-versioning call optimization, so
beware of ipa_fn_summaries not available. */
else if (((TARGET_SOFT_FLOAT_P (caller_opts->x_target_flags)
&& !TARGET_SOFT_FLOAT_P (callee_opts->x_target_flags))
|| (!TARGET_HARD_DFP_P (caller_opts->x_target_flags)
&& TARGET_HARD_DFP_P (callee_opts->x_target_flags)))
&& (! ipa_fn_summaries
|| ipa_fn_summaries->get
(cgraph_node::get (callee))->fp_expressions))
ret = false;
return ret;
}
#endif
/* 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);
}
#if S390_USE_TARGET_ATTRIBUTE
/* Restore targets globals from NEW_TREE and invalidate s390_previous_fndecl
cache. */
void
s390_activate_target_options (tree new_tree)
{
cl_target_option_restore (&global_options, &global_options_set,
TREE_TARGET_OPTION (new_tree));
if (TREE_TARGET_GLOBALS (new_tree))
restore_target_globals (TREE_TARGET_GLOBALS (new_tree));
else if (new_tree == target_option_default_node)
restore_target_globals (&default_target_globals);
else
TREE_TARGET_GLOBALS (new_tree) = save_target_globals_default_opts ();
s390_previous_fndecl = NULL_TREE;
}
#endif
/* Establish appropriate back-end context for processing the function
FNDECL. The argument might be NULL to indicate processing at top
level, outside of any function scope. */
static void
s390_set_current_function (tree fndecl)
{
#if S390_USE_TARGET_ATTRIBUTE
/* Only change the context if the function changes. This hook is called
several times in the course of compiling a function, and we don't want to
slow things down too much or call target_reinit when it isn't safe. */
if (fndecl == s390_previous_fndecl)
{
s390_indirect_branch_settings (fndecl);
return;
}
tree old_tree;
if (s390_previous_fndecl == NULL_TREE)
old_tree = target_option_current_node;
else if (DECL_FUNCTION_SPECIFIC_TARGET (s390_previous_fndecl))
old_tree = DECL_FUNCTION_SPECIFIC_TARGET (s390_previous_fndecl);
else
old_tree = target_option_default_node;
if (fndecl == NULL_TREE)
{
if (old_tree != target_option_current_node)
s390_activate_target_options (target_option_current_node);
return;
}
tree new_tree = DECL_FUNCTION_SPECIFIC_TARGET (fndecl);
if (new_tree == NULL_TREE)
new_tree = target_option_default_node;
if (old_tree != new_tree)
s390_activate_target_options (new_tree);
s390_previous_fndecl = fndecl;
#endif
s390_indirect_branch_settings (fndecl);
}
/* Implement TARGET_USE_BY_PIECES_INFRASTRUCTURE_P. */
static bool
s390_use_by_pieces_infrastructure_p (unsigned HOST_WIDE_INT size,
unsigned int align ATTRIBUTE_UNUSED,
enum by_pieces_operation op ATTRIBUTE_UNUSED,
bool speed_p ATTRIBUTE_UNUSED)
{
return (size == 1 || size == 2
|| size == 4 || (TARGET_ZARCH && size == 8));
}
/* Implement TARGET_ATOMIC_ASSIGN_EXPAND_FENV hook. */
static void
s390_atomic_assign_expand_fenv (tree *hold, tree *clear, tree *update)
{
tree sfpc = s390_builtin_decls[S390_BUILTIN_s390_sfpc];
tree efpc = s390_builtin_decls[S390_BUILTIN_s390_efpc];
tree call_efpc = build_call_expr (efpc, 0);
tree fenv_var = create_tmp_var_raw (unsigned_type_node);
#define FPC_EXCEPTION_MASK HOST_WIDE_INT_UC (0xf8000000)
#define FPC_FLAGS_MASK HOST_WIDE_INT_UC (0x00f80000)
#define FPC_DXC_MASK HOST_WIDE_INT_UC (0x0000ff00)
#define FPC_EXCEPTION_MASK_SHIFT HOST_WIDE_INT_UC (24)
#define FPC_FLAGS_SHIFT HOST_WIDE_INT_UC (16)
#define FPC_DXC_SHIFT HOST_WIDE_INT_UC (8)
/* Generates the equivalent of feholdexcept (&fenv_var)
fenv_var = __builtin_s390_efpc ();
__builtin_s390_sfpc (fenv_var & mask) */
tree old_fpc = build4 (TARGET_EXPR, unsigned_type_node, fenv_var, call_efpc,
NULL_TREE, NULL_TREE);
tree new_fpc
= build2 (BIT_AND_EXPR, unsigned_type_node, fenv_var,
build_int_cst (unsigned_type_node,
~(FPC_DXC_MASK | FPC_FLAGS_MASK
| FPC_EXCEPTION_MASK)));
tree set_new_fpc = build_call_expr (sfpc, 1, new_fpc);
*hold = build2 (COMPOUND_EXPR, void_type_node, old_fpc, set_new_fpc);
/* Generates the equivalent of feclearexcept (FE_ALL_EXCEPT)
__builtin_s390_sfpc (__builtin_s390_efpc () & mask) */
new_fpc = build2 (BIT_AND_EXPR, unsigned_type_node, call_efpc,
build_int_cst (unsigned_type_node,
~(FPC_DXC_MASK | FPC_FLAGS_MASK)));
*clear = build_call_expr (sfpc, 1, new_fpc);
/* Generates the equivalent of feupdateenv (fenv_var)
old_fpc = __builtin_s390_efpc ();
__builtin_s390_sfpc (fenv_var);
__atomic_feraiseexcept ((old_fpc & FPC_FLAGS_MASK) >> FPC_FLAGS_SHIFT); */
old_fpc = create_tmp_var_raw (unsigned_type_node);
tree store_old_fpc = build4 (TARGET_EXPR, void_type_node, old_fpc, call_efpc,
NULL_TREE, NULL_TREE);
set_new_fpc = build_call_expr (sfpc, 1, fenv_var);
tree raise_old_except = build2 (BIT_AND_EXPR, unsigned_type_node, old_fpc,
build_int_cst (unsigned_type_node,
FPC_FLAGS_MASK));
raise_old_except = build2 (RSHIFT_EXPR, unsigned_type_node, raise_old_except,
build_int_cst (unsigned_type_node,
FPC_FLAGS_SHIFT));
tree atomic_feraiseexcept
= builtin_decl_implicit (BUILT_IN_ATOMIC_FERAISEEXCEPT);
raise_old_except = build_call_expr (atomic_feraiseexcept,
1, raise_old_except);
*update = build2 (COMPOUND_EXPR, void_type_node,
build2 (COMPOUND_EXPR, void_type_node,
store_old_fpc, set_new_fpc),
raise_old_except);
#undef FPC_EXCEPTION_MASK
#undef FPC_FLAGS_MASK
#undef FPC_DXC_MASK
#undef FPC_EXCEPTION_MASK_SHIFT
#undef FPC_FLAGS_SHIFT
#undef FPC_DXC_SHIFT
}
/* Return the vector mode to be used for inner mode MODE when doing
vectorization. */
static machine_mode
s390_preferred_simd_mode (scalar_mode mode)
{
if (TARGET_VXE)
switch (mode)
{
case E_SFmode:
return V4SFmode;
default:;
}
if (TARGET_VX)
switch (mode)
{
case E_DFmode:
return V2DFmode;
case E_DImode:
return V2DImode;
case E_SImode:
return V4SImode;
case E_HImode:
return V8HImode;
case E_QImode:
return V16QImode;
default:;
}
return word_mode;
}
/* Our hardware does not require vectors to be strictly aligned. */
static bool
s390_support_vector_misalignment (machine_mode mode ATTRIBUTE_UNUSED,
const_tree type ATTRIBUTE_UNUSED,
int misalignment ATTRIBUTE_UNUSED,
bool is_packed ATTRIBUTE_UNUSED)
{
if (TARGET_VX)
return true;
return default_builtin_support_vector_misalignment (mode, type, misalignment,
is_packed);
}
/* The vector ABI requires vector types to be aligned on an 8 byte
boundary (our stack alignment). However, we allow this to be
overriden by the user, while this definitely breaks the ABI. */
static HOST_WIDE_INT
s390_vector_alignment (const_tree type)
{
tree size = TYPE_SIZE (type);
if (!TARGET_VX_ABI)
return default_vector_alignment (type);
if (TYPE_USER_ALIGN (type))
return TYPE_ALIGN (type);
if (tree_fits_uhwi_p (size)
&& tree_to_uhwi (size) < BIGGEST_ALIGNMENT)
return tree_to_uhwi (size);
return BIGGEST_ALIGNMENT;
}
/* Implement TARGET_CONSTANT_ALIGNMENT. Alignment on even addresses for
LARL instruction. */
static HOST_WIDE_INT
s390_constant_alignment (const_tree, HOST_WIDE_INT align)
{
return MAX (align, 16);
}
#ifdef HAVE_AS_MACHINE_MACHINEMODE
/* Implement TARGET_ASM_FILE_START. */
static void
s390_asm_file_start (void)
{
default_file_start ();
s390_asm_output_machine_for_arch (asm_out_file);
}
#endif
/* Implement TARGET_ASM_FILE_END. */
static void
s390_asm_file_end (void)
{
#ifdef HAVE_AS_GNU_ATTRIBUTE
varpool_node *vnode;
cgraph_node *cnode;
FOR_EACH_VARIABLE (vnode)
if (TREE_PUBLIC (vnode->decl))
s390_check_type_for_vector_abi (TREE_TYPE (vnode->decl), false, false);
FOR_EACH_FUNCTION (cnode)
if (TREE_PUBLIC (cnode->decl))
s390_check_type_for_vector_abi (TREE_TYPE (cnode->decl), false, false);
if (s390_vector_abi != 0)
fprintf (asm_out_file, "\t.gnu_attribute 8, %d\n",
s390_vector_abi);
#endif
file_end_indicate_exec_stack ();
if (flag_split_stack)
file_end_indicate_split_stack ();
}
/* Return true if TYPE is a vector bool type. */
static inline bool
s390_vector_bool_type_p (const_tree type)
{
return TYPE_VECTOR_OPAQUE (type);
}
/* Return the diagnostic message string if the binary operation OP is
not permitted on TYPE1 and TYPE2, NULL otherwise. */
static const char*
s390_invalid_binary_op (int op ATTRIBUTE_UNUSED, const_tree type1, const_tree type2)
{
bool bool1_p, bool2_p;
bool plusminus_p;
bool muldiv_p;
bool compare_p;
machine_mode mode1, mode2;
if (!TARGET_ZVECTOR)
return NULL;
if (!VECTOR_TYPE_P (type1) || !VECTOR_TYPE_P (type2))
return NULL;
bool1_p = s390_vector_bool_type_p (type1);
bool2_p = s390_vector_bool_type_p (type2);
/* Mixing signed and unsigned types is forbidden for all
operators. */
if (!bool1_p && !bool2_p
&& TYPE_UNSIGNED (type1) != TYPE_UNSIGNED (type2))
return N_("types differ in signedness");
plusminus_p = (op == PLUS_EXPR || op == MINUS_EXPR);
muldiv_p = (op == MULT_EXPR || op == RDIV_EXPR || op == TRUNC_DIV_EXPR
|| op == CEIL_DIV_EXPR || op == FLOOR_DIV_EXPR
|| op == ROUND_DIV_EXPR);
compare_p = (op == LT_EXPR || op == LE_EXPR || op == GT_EXPR || op == GE_EXPR
|| op == EQ_EXPR || op == NE_EXPR);
if (bool1_p && bool2_p && (plusminus_p || muldiv_p))
return N_("binary operator does not support two vector bool operands");
if (bool1_p != bool2_p && (muldiv_p || compare_p))
return N_("binary operator does not support vector bool operand");
mode1 = TYPE_MODE (type1);
mode2 = TYPE_MODE (type2);
if (bool1_p != bool2_p && plusminus_p
&& (GET_MODE_CLASS (mode1) == MODE_VECTOR_FLOAT
|| GET_MODE_CLASS (mode2) == MODE_VECTOR_FLOAT))
return N_("binary operator does not support mixing vector "
"bool with floating point vector operands");
return NULL;
}
#if ENABLE_S390_EXCESS_FLOAT_PRECISION == 1
/* Implement TARGET_C_EXCESS_PRECISION to maintain historic behavior with older
glibc versions
For historical reasons, float_t and double_t had been typedef'ed to
double on s390, causing operations on float_t to operate in a higher
precision than is necessary. However, it is not the case that SFmode
operations have implicit excess precision, and we generate more optimal
code if we let the compiler know no implicit extra precision is added.
With a glibc with that "historic" definition, configure will enable this hook
to set FLT_EVAL_METHOD to 1 for -fexcess-precision=standard (e.g., as implied
by -std=cXY). That means when we are compiling with -fexcess-precision=fast,
the value we set for FLT_EVAL_METHOD will be out of line with the actual
precision of float_t.
Newer versions of glibc will be modified to derive the definition of float_t
from FLT_EVAL_METHOD on s390x, as on many other architectures. There,
configure will disable this hook by default, so that we defer to the default
of FLT_EVAL_METHOD_PROMOTE_TO_FLOAT and a resulting typedef of float_t to
float. Note that in that scenario, float_t and FLT_EVAL_METHOD will be in
line independent of -fexcess-precision. */
static enum flt_eval_method
s390_excess_precision (enum excess_precision_type type)
{
switch (type)
{
case EXCESS_PRECISION_TYPE_IMPLICIT:
case EXCESS_PRECISION_TYPE_FAST:
/* The fastest type to promote to will always be the native type,
whether that occurs with implicit excess precision or
otherwise. */
return FLT_EVAL_METHOD_PROMOTE_TO_FLOAT;
case EXCESS_PRECISION_TYPE_STANDARD:
/* Otherwise, when we are in a standards compliant mode, to
ensure consistency with the implementation in glibc, report that
float is evaluated to the range and precision of double. */
return FLT_EVAL_METHOD_PROMOTE_TO_DOUBLE;
case EXCESS_PRECISION_TYPE_FLOAT16:
error ("%<-fexcess-precision=16%> is not supported on this target");
break;
default:
gcc_unreachable ();
}
return FLT_EVAL_METHOD_UNPREDICTABLE;
}
#endif
void
s390_rawmemchr (machine_mode elt_mode, rtx dst, rtx src, rtx pat)
{
machine_mode vec_mode = mode_for_vector (as_a <scalar_int_mode> (elt_mode),
16 / GET_MODE_SIZE (elt_mode)).require();
rtx lens = gen_reg_rtx (V16QImode);
rtx pattern = gen_reg_rtx (vec_mode);
rtx loop_start = gen_label_rtx ();
rtx loop_end = gen_label_rtx ();
rtx addr = gen_reg_rtx (Pmode);
rtx offset = gen_reg_rtx (Pmode);
rtx loadlen = gen_reg_rtx (SImode);
rtx matchlen = gen_reg_rtx (SImode);
rtx mem;
pat = GEN_INT (trunc_int_for_mode (INTVAL (pat), elt_mode));
emit_insn (gen_rtx_SET (pattern, gen_rtx_VEC_DUPLICATE (vec_mode, pat)));
emit_move_insn (addr, XEXP (src, 0));
// alignment
emit_insn (gen_vlbb (lens, gen_rtx_MEM (BLKmode, addr), GEN_INT (6)));
emit_insn (gen_lcbb (loadlen, addr, GEN_INT (6)));
lens = convert_to_mode (vec_mode, lens, 1);
emit_insn (gen_vec_vfees (vec_mode, lens, lens, pattern, GEN_INT (0)));
lens = convert_to_mode (V4SImode, lens, 1);
emit_insn (gen_vec_extractv4sisi (matchlen, lens, GEN_INT (1)));
lens = convert_to_mode (vec_mode, lens, 1);
emit_cmp_and_jump_insns (matchlen, loadlen, LT, NULL_RTX, SImode, 1, loop_end);
force_expand_binop (Pmode, add_optab, addr, GEN_INT(16), addr, 1, OPTAB_DIRECT);
force_expand_binop (Pmode, and_optab, addr, GEN_INT(~HOST_WIDE_INT_UC(0xf)), addr, 1, OPTAB_DIRECT);
// now, addr is 16-byte aligned
mem = gen_rtx_MEM (vec_mode, addr);
set_mem_align (mem, 128);
emit_move_insn (lens, mem);
emit_insn (gen_vec_vfees (vec_mode, lens, lens, pattern, GEN_INT (VSTRING_FLAG_CS)));
add_int_reg_note (s390_emit_ccraw_jump (4, EQ, loop_end),
REG_BR_PROB,
profile_probability::very_unlikely ().to_reg_br_prob_note ());
emit_label (loop_start);
LABEL_NUSES (loop_start) = 1;
force_expand_binop (Pmode, add_optab, addr, GEN_INT (16), addr, 1, OPTAB_DIRECT);
mem = gen_rtx_MEM (vec_mode, addr);
set_mem_align (mem, 128);
emit_move_insn (lens, mem);
emit_insn (gen_vec_vfees (vec_mode, lens, lens, pattern, GEN_INT (VSTRING_FLAG_CS)));
add_int_reg_note (s390_emit_ccraw_jump (4, NE, loop_start),
REG_BR_PROB,
profile_probability::very_likely ().to_reg_br_prob_note ());
emit_label (loop_end);
LABEL_NUSES (loop_end) = 1;
if (TARGET_64BIT)
{
lens = convert_to_mode (V2DImode, lens, 1);
emit_insn (gen_vec_extractv2didi (offset, lens, GEN_INT (0)));
}
else
{
lens = convert_to_mode (V4SImode, lens, 1);
emit_insn (gen_vec_extractv4sisi (offset, lens, GEN_INT (1)));
}
force_expand_binop (Pmode, add_optab, addr, offset, dst, 1, OPTAB_DIRECT);
}
/* Implement the TARGET_ASAN_SHADOW_OFFSET hook. */
static unsigned HOST_WIDE_INT
s390_asan_shadow_offset (void)
{
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 && flag_dwarf2_cfi_asm)
{
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
{
/* 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);
}
/* 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 ();
}
/* Evaluate the insns between HEAD and TAIL and do back-end to install
back-end specific dependencies.
Establish an ANTI dependency between r11 and r15 restores from FPRs
to prevent the instructions scheduler from reordering them since
this would break CFI. No further handling in the sched_reorder
hook is required since the r11 and r15 restore will never appear in
the same ready list with that change. */
void
s390_sched_dependencies_evaluation (rtx_insn *head, rtx_insn *tail)
{
if (!frame_pointer_needed || !epilogue_completed)
return;
while (head != tail && DEBUG_INSN_P (head))
head = NEXT_INSN (head);
rtx_insn *r15_restore = NULL, *r11_restore = NULL;
for (rtx_insn *insn = tail; insn != head; insn = PREV_INSN (insn))
{
rtx set = single_set (insn);
if (!INSN_P (insn)
|| !RTX_FRAME_RELATED_P (insn)
|| set == NULL_RTX
|| !REG_P (SET_DEST (set))
|| !FP_REG_P (SET_SRC (set)))
continue;
if (REGNO (SET_DEST (set)) == HARD_FRAME_POINTER_REGNUM)
r11_restore = insn;
if (REGNO (SET_DEST (set)) == STACK_POINTER_REGNUM)
r15_restore = insn;
}
if (r11_restore == NULL || r15_restore == NULL)
return;
add_dependence (r11_restore, r15_restore, REG_DEP_ANTI);
}
/* Implement TARGET_SHIFT_TRUNCATION_MASK for integer shifts. */
static unsigned HOST_WIDE_INT
s390_shift_truncation_mask (machine_mode mode)
{
return mode == DImode || mode == SImode ? 63 : 0;
}
/* Return TRUE iff CONSTRAINT is an "f" constraint, possibly with additional
modifiers. */
static bool
f_constraint_p (const char *constraint)
{
bool seen_f_p = false;
bool seen_v_p = false;
for (size_t i = 0, c_len = strlen (constraint); i < c_len;
i += CONSTRAINT_LEN (constraint[i], constraint + i))
{
if (constraint[i] == 'f')
seen_f_p = true;
if (constraint[i] == 'v')
seen_v_p = true;
}
/* Treat "fv" constraints as "v", because LRA will choose the widest register
* class. */
return seen_f_p && !seen_v_p;
}
/* Return TRUE iff X is a hard floating-point (and not a vector) register. */
static bool
s390_hard_fp_reg_p (rtx x)
{
if (!(REG_P (x) && HARD_REGISTER_P (x) && REG_ATTRS (x)))
return false;
tree decl = REG_EXPR (x);
if (!(HAS_DECL_ASSEMBLER_NAME_P (decl) && DECL_ASSEMBLER_NAME_SET_P (decl)))
return false;
const char *name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (decl));
return name[0] == '*' && name[1] == 'f';
}
/* Implement TARGET_MD_ASM_ADJUST hook in order to fix up "f"
constraints when long doubles are stored in vector registers. */
static rtx_insn *
s390_md_asm_adjust (vec<rtx> &outputs, vec<rtx> &inputs,
vec<machine_mode> &input_modes,
vec<const char *> &constraints, vec<rtx> & /*clobbers*/,
HARD_REG_SET & /*clobbered_regs*/, location_t /*loc*/)
{
if (!TARGET_VXE)
/* Long doubles are stored in FPR pairs - nothing to do. */
return NULL;
rtx_insn *after_md_seq = NULL, *after_md_end = NULL;
unsigned ninputs = inputs.length ();
unsigned noutputs = outputs.length ();
for (unsigned i = 0; i < noutputs; i++)
{
if (GET_MODE (outputs[i]) != TFmode)
/* Not a long double - nothing to do. */
continue;
const char *constraint = constraints[i];
bool allows_mem, allows_reg, is_inout;
bool ok = parse_output_constraint (&constraint, i, ninputs, noutputs,
&allows_mem, &allows_reg, &is_inout);
gcc_assert (ok);
if (!f_constraint_p (constraint))
/* Long double with a constraint other than "=f" - nothing to do. */
continue;
gcc_assert (allows_reg);
gcc_assert (!is_inout);
/* Copy output value from a FPR pair into a vector register. */
rtx fprx2;
push_to_sequence2 (after_md_seq, after_md_end);
if (s390_hard_fp_reg_p (outputs[i]))
{
fprx2 = gen_rtx_REG (FPRX2mode, REGNO (outputs[i]));
/* The first half is already at the correct location, copy only the
* second one. Use the UNSPEC pattern instead of the SUBREG one,
* since s390_can_change_mode_class() rejects
* (subreg:DF (reg:TF %fN) 8) and thus subreg validation fails. */
rtx v1 = gen_rtx_REG (V2DFmode, REGNO (outputs[i]));
rtx v3 = gen_rtx_REG (V2DFmode, REGNO (outputs[i]) + 1);
emit_insn (gen_vec_permiv2df (v1, v1, v3, const0_rtx));
}
else
{
fprx2 = gen_reg_rtx (FPRX2mode);
emit_insn (gen_fprx2_to_tf (outputs[i], fprx2));
}
after_md_seq = get_insns ();
after_md_end = get_last_insn ();
end_sequence ();
outputs[i] = fprx2;
}
for (unsigned i = 0; i < ninputs; i++)
{
if (GET_MODE (inputs[i]) != TFmode)
/* Not a long double - nothing to do. */
continue;
const char *constraint = constraints[noutputs + i];
bool allows_mem, allows_reg;
bool ok = parse_input_constraint (&constraint, i, ninputs, noutputs, 0,
constraints.address (), &allows_mem,
&allows_reg);
gcc_assert (ok);
if (!f_constraint_p (constraint))
/* Long double with a constraint other than "f" (or "=f" for inout
operands) - nothing to do. */
continue;
gcc_assert (allows_reg);
/* Copy input value from a vector register into a FPR pair. */
rtx fprx2;
if (s390_hard_fp_reg_p (inputs[i]))
{
fprx2 = gen_rtx_REG (FPRX2mode, REGNO (inputs[i]));
/* Copy only the second half. */
rtx v1 = gen_rtx_REG (V2DFmode, REGNO (inputs[i]) + 1);
rtx v2 = gen_rtx_REG (V2DFmode, REGNO (inputs[i]));
emit_insn (gen_vec_permiv2df (v1, v2, v1, GEN_INT (3)));
}
else
{
fprx2 = gen_reg_rtx (FPRX2mode);
emit_insn (gen_tf_to_fprx2 (fprx2, inputs[i]));
}
inputs[i] = fprx2;
input_modes[i] = FPRX2mode;
}
return after_md_seq;
}
#define MAX_VECT_LEN 16
struct expand_vec_perm_d
{
rtx target, op0, op1;
unsigned char perm[MAX_VECT_LEN];
machine_mode vmode;
unsigned char nelt;
bool testing_p;
};
/* Try to expand the vector permute operation described by D using the
vector merge instructions vml and vmh. Return true if vector merge
could be used. */
static bool
expand_perm_with_merge (const struct expand_vec_perm_d &d)
{
bool merge_lo_p = true;
bool merge_hi_p = true;
if (d.nelt % 2)
return false;
// For V4SI this checks for: { 0, 4, 1, 5 }
for (int telt = 0; telt < d.nelt; telt++)
if (d.perm[telt] != telt / 2 + (telt % 2) * d.nelt)
{
merge_hi_p = false;
break;
}
if (!merge_hi_p)
{
// For V4SI this checks for: { 2, 6, 3, 7 }
for (int telt = 0; telt < d.nelt; telt++)
if (d.perm[telt] != (telt + d.nelt) / 2 + (telt % 2) * d.nelt)
{
merge_lo_p = false;
break;
}
}
else
merge_lo_p = false;
if (d.testing_p)
return merge_lo_p || merge_hi_p;
if (merge_lo_p || merge_hi_p)
s390_expand_merge (d.target, d.op0, d.op1, merge_hi_p);
return merge_lo_p || merge_hi_p;
}
/* Try to expand the vector permute operation described by D using the
vector permute doubleword immediate instruction vpdi. Return true
if vpdi could be used.
VPDI allows 4 different immediate values (0, 1, 4, 5). The 0 and 5
cases are covered by vmrhg and vmrlg already. So we only care
about the 1, 4 cases here.
1 - First element of src1 and second of src2
4 - Second element of src1 and first of src2 */
static bool
expand_perm_with_vpdi (const struct expand_vec_perm_d &d)
{
bool vpdi1_p = false;
bool vpdi4_p = false;
rtx op0_reg, op1_reg;
// Only V2DI and V2DF are supported here.
if (d.nelt != 2)
return false;
if (d.perm[0] == 0 && d.perm[1] == 3)
vpdi1_p = true;
if (d.perm[0] == 1 && d.perm[1] == 2)
vpdi4_p = true;
if (!vpdi1_p && !vpdi4_p)
return false;
if (d.testing_p)
return true;
op0_reg = force_reg (GET_MODE (d.op0), d.op0);
op1_reg = force_reg (GET_MODE (d.op1), d.op1);
if (vpdi1_p)
emit_insn (gen_vpdi1 (d.vmode, d.target, op0_reg, op1_reg));
if (vpdi4_p)
emit_insn (gen_vpdi4 (d.vmode, d.target, op0_reg, op1_reg));
return true;
}
/* Try to find the best sequence for the vector permute operation
described by D. Return true if the operation could be
expanded. */
static bool
vectorize_vec_perm_const_1 (const struct expand_vec_perm_d &d)
{
if (expand_perm_with_merge (d))
return true;
if (expand_perm_with_vpdi (d))
return true;
return false;
}
/* Return true if we can emit instructions for the constant
permutation vector in SEL. If OUTPUT, IN0, IN1 are non-null the
hook is supposed to emit the required INSNs. */
bool
s390_vectorize_vec_perm_const (machine_mode vmode, rtx target, rtx op0, rtx op1,
const vec_perm_indices &sel)
{
struct expand_vec_perm_d d;
unsigned int i, nelt;
if (!s390_vector_mode_supported_p (vmode) || GET_MODE_SIZE (vmode) != 16)
return false;
d.target = target;
d.op0 = op0;
d.op1 = op1;
d.vmode = vmode;
gcc_assert (VECTOR_MODE_P (d.vmode));
d.nelt = nelt = GET_MODE_NUNITS (d.vmode);
d.testing_p = target == NULL_RTX;
gcc_assert (target == NULL_RTX || REG_P (target));
gcc_assert (sel.length () == nelt);
for (i = 0; i < nelt; i++)
{
unsigned char e = sel[i];
gcc_assert (e < 2 * nelt);
d.perm[i] = e;
}
return vectorize_vec_perm_const_1 (d);
}
/* Initialize GCC target structure. */
#undef TARGET_ASM_ALIGNED_HI_OP
#define TARGET_ASM_ALIGNED_HI_OP "\t.word\t"
#undef TARGET_ASM_ALIGNED_DI_OP
#define TARGET_ASM_ALIGNED_DI_OP "\t.quad\t"
#undef TARGET_ASM_INTEGER
#define TARGET_ASM_INTEGER s390_assemble_integer
#undef TARGET_ASM_OPEN_PAREN
#define TARGET_ASM_OPEN_PAREN ""
#undef TARGET_ASM_CLOSE_PAREN
#define TARGET_ASM_CLOSE_PAREN ""
#undef TARGET_OPTION_OVERRIDE
#define TARGET_OPTION_OVERRIDE s390_option_override
#ifdef TARGET_THREAD_SSP_OFFSET
#undef TARGET_STACK_PROTECT_GUARD
#define TARGET_STACK_PROTECT_GUARD hook_tree_void_null
#endif
#undef TARGET_ENCODE_SECTION_INFO
#define TARGET_ENCODE_SECTION_INFO s390_encode_section_info
#undef TARGET_SCALAR_MODE_SUPPORTED_P
#define TARGET_SCALAR_MODE_SUPPORTED_P s390_scalar_mode_supported_p
#ifdef HAVE_AS_TLS
#undef TARGET_HAVE_TLS
#define TARGET_HAVE_TLS true
#endif
#undef TARGET_CANNOT_FORCE_CONST_MEM
#define TARGET_CANNOT_FORCE_CONST_MEM s390_cannot_force_const_mem
#undef TARGET_DELEGITIMIZE_ADDRESS
#define TARGET_DELEGITIMIZE_ADDRESS s390_delegitimize_address
#undef TARGET_LEGITIMIZE_ADDRESS
#define TARGET_LEGITIMIZE_ADDRESS s390_legitimize_address
#undef TARGET_RETURN_IN_MEMORY
#define TARGET_RETURN_IN_MEMORY s390_return_in_memory
#undef TARGET_INIT_BUILTINS
#define TARGET_INIT_BUILTINS s390_init_builtins
#undef TARGET_EXPAND_BUILTIN
#define TARGET_EXPAND_BUILTIN s390_expand_builtin
#undef TARGET_BUILTIN_DECL
#define TARGET_BUILTIN_DECL s390_builtin_decl
#undef TARGET_ASM_OUTPUT_ADDR_CONST_EXTRA
#define TARGET_ASM_OUTPUT_ADDR_CONST_EXTRA s390_output_addr_const_extra
#undef TARGET_ASM_OUTPUT_MI_THUNK
#define TARGET_ASM_OUTPUT_MI_THUNK s390_output_mi_thunk
#undef TARGET_ASM_CAN_OUTPUT_MI_THUNK
#define TARGET_ASM_CAN_OUTPUT_MI_THUNK hook_bool_const_tree_hwi_hwi_const_tree_true
#if ENABLE_S390_EXCESS_FLOAT_PRECISION == 1
/* This hook is only needed to maintain the historic behavior with glibc
versions that typedef float_t to double. */
#undef TARGET_C_EXCESS_PRECISION
#define TARGET_C_EXCESS_PRECISION s390_excess_precision
#endif
#undef TARGET_SCHED_ADJUST_PRIORITY
#define TARGET_SCHED_ADJUST_PRIORITY s390_adjust_priority
#undef TARGET_SCHED_ISSUE_RATE
#define TARGET_SCHED_ISSUE_RATE s390_issue_rate
#undef TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DFA_LOOKAHEAD
#define TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DFA_LOOKAHEAD s390_first_cycle_multipass_dfa_lookahead
#undef TARGET_SCHED_VARIABLE_ISSUE
#define TARGET_SCHED_VARIABLE_ISSUE s390_sched_variable_issue
#undef TARGET_SCHED_REORDER
#define TARGET_SCHED_REORDER s390_sched_reorder
#undef TARGET_SCHED_INIT
#define TARGET_SCHED_INIT s390_sched_init
#undef TARGET_CANNOT_COPY_INSN_P
#define TARGET_CANNOT_COPY_INSN_P s390_cannot_copy_insn_p
#undef TARGET_RTX_COSTS
#define TARGET_RTX_COSTS s390_rtx_costs
#undef TARGET_ADDRESS_COST
#define TARGET_ADDRESS_COST s390_address_cost
#undef TARGET_REGISTER_MOVE_COST
#define TARGET_REGISTER_MOVE_COST s390_register_move_cost
#undef TARGET_MEMORY_MOVE_COST
#define TARGET_MEMORY_MOVE_COST s390_memory_move_cost
#undef TARGET_VECTORIZE_BUILTIN_VECTORIZATION_COST
#define TARGET_VECTORIZE_BUILTIN_VECTORIZATION_COST \
s390_builtin_vectorization_cost
#undef TARGET_MACHINE_DEPENDENT_REORG
#define TARGET_MACHINE_DEPENDENT_REORG s390_reorg
#undef TARGET_VALID_POINTER_MODE
#define TARGET_VALID_POINTER_MODE s390_valid_pointer_mode
#undef TARGET_BUILD_BUILTIN_VA_LIST
#define TARGET_BUILD_BUILTIN_VA_LIST s390_build_builtin_va_list
#undef TARGET_EXPAND_BUILTIN_VA_START
#define TARGET_EXPAND_BUILTIN_VA_START s390_va_start
#undef TARGET_ASAN_SHADOW_OFFSET
#define TARGET_ASAN_SHADOW_OFFSET s390_asan_shadow_offset
#undef TARGET_GIMPLIFY_VA_ARG_EXPR
#define TARGET_GIMPLIFY_VA_ARG_EXPR s390_gimplify_va_arg
#undef TARGET_PROMOTE_FUNCTION_MODE
#define TARGET_PROMOTE_FUNCTION_MODE s390_promote_function_mode
#undef TARGET_PASS_BY_REFERENCE
#define TARGET_PASS_BY_REFERENCE s390_pass_by_reference
#undef TARGET_OVERRIDE_OPTIONS_AFTER_CHANGE
#define TARGET_OVERRIDE_OPTIONS_AFTER_CHANGE s390_override_options_after_change
#undef TARGET_FUNCTION_OK_FOR_SIBCALL
#define TARGET_FUNCTION_OK_FOR_SIBCALL s390_function_ok_for_sibcall
#undef TARGET_FUNCTION_ARG
#define TARGET_FUNCTION_ARG s390_function_arg
#undef TARGET_FUNCTION_ARG_ADVANCE
#define TARGET_FUNCTION_ARG_ADVANCE s390_function_arg_advance
#undef TARGET_FUNCTION_ARG_PADDING
#define TARGET_FUNCTION_ARG_PADDING s390_function_arg_padding
#undef TARGET_FUNCTION_VALUE
#define TARGET_FUNCTION_VALUE s390_function_value
#undef TARGET_LIBCALL_VALUE
#define TARGET_LIBCALL_VALUE s390_libcall_value
#undef TARGET_STRICT_ARGUMENT_NAMING
#define TARGET_STRICT_ARGUMENT_NAMING hook_bool_CUMULATIVE_ARGS_true
#undef TARGET_KEEP_LEAF_WHEN_PROFILED
#define TARGET_KEEP_LEAF_WHEN_PROFILED s390_keep_leaf_when_profiled
#undef TARGET_FIXED_CONDITION_CODE_REGS
#define TARGET_FIXED_CONDITION_CODE_REGS s390_fixed_condition_code_regs
#undef TARGET_CC_MODES_COMPATIBLE
#define TARGET_CC_MODES_COMPATIBLE s390_cc_modes_compatible
#undef TARGET_INVALID_WITHIN_DOLOOP
#define TARGET_INVALID_WITHIN_DOLOOP hook_constcharptr_const_rtx_insn_null
#ifdef HAVE_AS_TLS
#undef TARGET_ASM_OUTPUT_DWARF_DTPREL
#define TARGET_ASM_OUTPUT_DWARF_DTPREL s390_output_dwarf_dtprel
#endif
#undef TARGET_DWARF_FRAME_REG_MODE
#define TARGET_DWARF_FRAME_REG_MODE s390_dwarf_frame_reg_mode
#ifdef TARGET_ALTERNATE_LONG_DOUBLE_MANGLING
#undef TARGET_MANGLE_TYPE
#define TARGET_MANGLE_TYPE s390_mangle_type
#endif
#undef TARGET_SCALAR_MODE_SUPPORTED_P
#define TARGET_SCALAR_MODE_SUPPORTED_P s390_scalar_mode_supported_p
#undef TARGET_VECTOR_MODE_SUPPORTED_P
#define TARGET_VECTOR_MODE_SUPPORTED_P s390_vector_mode_supported_p
#undef TARGET_PREFERRED_RELOAD_CLASS
#define TARGET_PREFERRED_RELOAD_CLASS s390_preferred_reload_class
#undef TARGET_SECONDARY_RELOAD
#define TARGET_SECONDARY_RELOAD s390_secondary_reload
#undef TARGET_SECONDARY_MEMORY_NEEDED
#define TARGET_SECONDARY_MEMORY_NEEDED s390_secondary_memory_needed
#undef TARGET_SECONDARY_MEMORY_NEEDED_MODE
#define TARGET_SECONDARY_MEMORY_NEEDED_MODE s390_secondary_memory_needed_mode
#undef TARGET_LIBGCC_CMP_RETURN_MODE
#define TARGET_LIBGCC_CMP_RETURN_MODE s390_libgcc_cmp_return_mode
#undef TARGET_LIBGCC_SHIFT_COUNT_MODE
#define TARGET_LIBGCC_SHIFT_COUNT_MODE s390_libgcc_shift_count_mode
#undef TARGET_LEGITIMATE_ADDRESS_P
#define TARGET_LEGITIMATE_ADDRESS_P s390_legitimate_address_p
#undef TARGET_LEGITIMATE_CONSTANT_P
#define TARGET_LEGITIMATE_CONSTANT_P s390_legitimate_constant_p
#undef TARGET_LRA_P
#define TARGET_LRA_P s390_lra_p
#undef TARGET_CAN_ELIMINATE
#define TARGET_CAN_ELIMINATE s390_can_eliminate
#undef TARGET_CONDITIONAL_REGISTER_USAGE
#define TARGET_CONDITIONAL_REGISTER_USAGE s390_conditional_register_usage
#undef TARGET_LOOP_UNROLL_ADJUST
#define TARGET_LOOP_UNROLL_ADJUST s390_loop_unroll_adjust
#undef TARGET_ASM_TRAMPOLINE_TEMPLATE
#define TARGET_ASM_TRAMPOLINE_TEMPLATE s390_asm_trampoline_template
#undef TARGET_TRAMPOLINE_INIT
#define TARGET_TRAMPOLINE_INIT s390_trampoline_init
/* PR 79421 */
#undef TARGET_CUSTOM_FUNCTION_DESCRIPTORS
#define TARGET_CUSTOM_FUNCTION_DESCRIPTORS 1
#undef TARGET_UNWIND_WORD_MODE
#define TARGET_UNWIND_WORD_MODE s390_unwind_word_mode
#undef TARGET_CANONICALIZE_COMPARISON
#define TARGET_CANONICALIZE_COMPARISON s390_canonicalize_comparison
#undef TARGET_HARD_REGNO_SCRATCH_OK
#define TARGET_HARD_REGNO_SCRATCH_OK s390_hard_regno_scratch_ok
#undef TARGET_HARD_REGNO_NREGS
#define TARGET_HARD_REGNO_NREGS s390_hard_regno_nregs
#undef TARGET_HARD_REGNO_MODE_OK
#define TARGET_HARD_REGNO_MODE_OK s390_hard_regno_mode_ok
#undef TARGET_MODES_TIEABLE_P
#define TARGET_MODES_TIEABLE_P s390_modes_tieable_p
#undef TARGET_HARD_REGNO_CALL_PART_CLOBBERED
#define TARGET_HARD_REGNO_CALL_PART_CLOBBERED \
s390_hard_regno_call_part_clobbered
#undef TARGET_ATTRIBUTE_TABLE
#define TARGET_ATTRIBUTE_TABLE s390_attribute_table
#undef TARGET_FUNCTION_ATTRIBUTE_INLINABLE_P
#define TARGET_FUNCTION_ATTRIBUTE_INLINABLE_P hook_bool_const_tree_true
#undef TARGET_SET_UP_BY_PROLOGUE
#define TARGET_SET_UP_BY_PROLOGUE s300_set_up_by_prologue
#undef TARGET_EXTRA_LIVE_ON_ENTRY
#define TARGET_EXTRA_LIVE_ON_ENTRY s390_live_on_entry
#undef TARGET_USE_BY_PIECES_INFRASTRUCTURE_P
#define TARGET_USE_BY_PIECES_INFRASTRUCTURE_P \
s390_use_by_pieces_infrastructure_p
#undef TARGET_ATOMIC_ASSIGN_EXPAND_FENV
#define TARGET_ATOMIC_ASSIGN_EXPAND_FENV s390_atomic_assign_expand_fenv
#undef TARGET_INVALID_ARG_FOR_UNPROTOTYPED_FN
#define TARGET_INVALID_ARG_FOR_UNPROTOTYPED_FN s390_invalid_arg_for_unprototyped_fn
#undef TARGET_VECTORIZE_PREFERRED_SIMD_MODE
#define TARGET_VECTORIZE_PREFERRED_SIMD_MODE s390_preferred_simd_mode
#undef TARGET_VECTORIZE_SUPPORT_VECTOR_MISALIGNMENT
#define TARGET_VECTORIZE_SUPPORT_VECTOR_MISALIGNMENT s390_support_vector_misalignment
#undef TARGET_VECTOR_ALIGNMENT
#define TARGET_VECTOR_ALIGNMENT s390_vector_alignment
#undef TARGET_INVALID_BINARY_OP
#define TARGET_INVALID_BINARY_OP s390_invalid_binary_op
#ifdef HAVE_AS_MACHINE_MACHINEMODE
#undef TARGET_ASM_FILE_START
#define TARGET_ASM_FILE_START s390_asm_file_start
#endif
#undef TARGET_ASM_FILE_END
#define TARGET_ASM_FILE_END s390_asm_file_end
#undef TARGET_SET_CURRENT_FUNCTION
#define TARGET_SET_CURRENT_FUNCTION s390_set_current_function
#if S390_USE_TARGET_ATTRIBUTE
#undef TARGET_OPTION_VALID_ATTRIBUTE_P
#define TARGET_OPTION_VALID_ATTRIBUTE_P s390_valid_target_attribute_p
#undef TARGET_CAN_INLINE_P
#define TARGET_CAN_INLINE_P s390_can_inline_p
#endif
#undef TARGET_OPTION_RESTORE
#define TARGET_OPTION_RESTORE s390_function_specific_restore
#undef TARGET_CAN_CHANGE_MODE_CLASS
#define TARGET_CAN_CHANGE_MODE_CLASS s390_can_change_mode_class
#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
#undef TARGET_SCHED_DEPENDENCIES_EVALUATION_HOOK
#define TARGET_SCHED_DEPENDENCIES_EVALUATION_HOOK \
s390_sched_dependencies_evaluation
#undef TARGET_SHIFT_TRUNCATION_MASK
#define TARGET_SHIFT_TRUNCATION_MASK s390_shift_truncation_mask
/* Use only short displacement, since long displacement is not available for
the floating point instructions. */
#undef TARGET_MAX_ANCHOR_OFFSET
#define TARGET_MAX_ANCHOR_OFFSET 0xfff
#undef TARGET_MD_ASM_ADJUST
#define TARGET_MD_ASM_ADJUST s390_md_asm_adjust
#undef TARGET_VECTORIZE_VEC_PERM_CONST
#define TARGET_VECTORIZE_VEC_PERM_CONST s390_vectorize_vec_perm_const
struct gcc_target targetm = TARGET_INITIALIZER;
#include "gt-s390.h"