aboutsummaryrefslogtreecommitdiff
path: root/gcc/config/rs6000/rs6000.c
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/config/rs6000/rs6000.c')
-rw-r--r--gcc/config/rs6000/rs6000.c299
1 files changed, 294 insertions, 5 deletions
diff --git a/gcc/config/rs6000/rs6000.c b/gcc/config/rs6000/rs6000.c
index be377ebf440..0d5c8b4d4b0 100644
--- a/gcc/config/rs6000/rs6000.c
+++ b/gcc/config/rs6000/rs6000.c
@@ -760,7 +760,15 @@ static tree rs6000_build_builtin_va_list (void);
static tree rs6000_gimplify_va_arg (tree, tree, tree *, tree *);
static bool rs6000_must_pass_in_stack (enum machine_mode, tree);
static bool rs6000_vector_mode_supported_p (enum machine_mode);
+static int get_vec_cmp_insn (enum rtx_code, enum machine_mode,
+ enum machine_mode);
+static rtx rs6000_emit_vector_compare (enum rtx_code, rtx, rtx,
+ enum machine_mode);
+static int get_vsel_insn (enum machine_mode);
+static void rs6000_emit_vector_select (rtx, rtx, rtx, rtx);
+
+const int INSN_NOT_AVAILABLE = -1;
static enum machine_mode rs6000_eh_return_filter_mode (void);
/* Hash table stuff for keeping track of TOC entries. */
@@ -11202,6 +11210,271 @@ output_e500_flip_eq_bit (rtx dst, rtx src)
return string;
}
+/* Return insn index for the vector compare instruction for given CODE,
+ and DEST_MODE, OP_MODE. Return INSN_NOT_AVAILABLE if valid insn is
+ not available. */
+
+static int
+get_vec_cmp_insn (enum rtx_code code,
+ enum machine_mode dest_mode,
+ enum machine_mode op_mode)
+{
+ if (!TARGET_ALTIVEC)
+ return INSN_NOT_AVAILABLE;
+
+ switch (code)
+ {
+ case EQ:
+ if (dest_mode == V16QImode && op_mode == V16QImode)
+ return UNSPEC_VCMPEQUB;
+ if (dest_mode == V8HImode && op_mode == V8HImode)
+ return UNSPEC_VCMPEQUH;
+ if (dest_mode == V4SImode && op_mode == V4SImode)
+ return UNSPEC_VCMPEQUW;
+ if (dest_mode == V4SImode && op_mode == V4SFmode)
+ return UNSPEC_VCMPEQFP;
+ break;
+ case GE:
+ if (dest_mode == V4SImode && op_mode == V4SFmode)
+ return UNSPEC_VCMPGEFP;
+ case GT:
+ if (dest_mode == V16QImode && op_mode == V16QImode)
+ return UNSPEC_VCMPGTSB;
+ if (dest_mode == V8HImode && op_mode == V8HImode)
+ return UNSPEC_VCMPGTSH;
+ if (dest_mode == V4SImode && op_mode == V4SImode)
+ return UNSPEC_VCMPGTSW;
+ if (dest_mode == V4SImode && op_mode == V4SFmode)
+ return UNSPEC_VCMPGTFP;
+ break;
+ case GTU:
+ if (dest_mode == V16QImode && op_mode == V16QImode)
+ return UNSPEC_VCMPGTUB;
+ if (dest_mode == V8HImode && op_mode == V8HImode)
+ return UNSPEC_VCMPGTUH;
+ if (dest_mode == V4SImode && op_mode == V4SImode)
+ return UNSPEC_VCMPGTUW;
+ break;
+ default:
+ break;
+ }
+ return INSN_NOT_AVAILABLE;
+}
+
+/* Emit vector compare for operands OP0 and OP1 using code RCODE.
+ DMODE is expected destination mode. This is a recursive function. */
+
+static rtx
+rs6000_emit_vector_compare (enum rtx_code rcode,
+ rtx op0, rtx op1,
+ enum machine_mode dmode)
+{
+ int vec_cmp_insn;
+ rtx mask;
+ enum machine_mode dest_mode;
+ enum machine_mode op_mode = GET_MODE (op1);
+
+#ifdef ENABLE_CHECKING
+ if (!TARGET_ALTIVEC)
+ abort ();
+
+ if (GET_MODE (op0) != GET_MODE (op1))
+ abort ();
+#endif
+
+ /* Floating point vector compare instructions uses destination V4SImode.
+ Move destination to appropriate mode later. */
+ if (dmode == V4SFmode)
+ dest_mode = V4SImode;
+ else
+ dest_mode = dmode;
+
+ mask = gen_reg_rtx (dest_mode);
+ vec_cmp_insn = get_vec_cmp_insn (rcode, dest_mode, op_mode);
+
+ if (vec_cmp_insn == INSN_NOT_AVAILABLE)
+ {
+ bool swap_operands = false;
+ bool try_again = false;
+ switch (rcode)
+ {
+ case LT:
+ rcode = GT;
+ swap_operands = true;
+ try_again = true;
+ break;
+ case LTU:
+ rcode = GTU;
+ swap_operands = true;
+ try_again = true;
+ break;
+ case NE:
+ /* Treat A != B as ~(A==B). */
+ {
+ enum insn_code nor_code;
+ rtx eq_rtx = rs6000_emit_vector_compare (EQ, op0, op1,
+ dest_mode);
+
+ nor_code = one_cmpl_optab->handlers[(int)dest_mode].insn_code;
+ if (nor_code == CODE_FOR_nothing)
+ abort ();
+ emit_insn (GEN_FCN (nor_code) (mask, eq_rtx));
+
+ if (dmode != dest_mode)
+ {
+ rtx temp = gen_reg_rtx (dest_mode);
+ convert_move (temp, mask, 0);
+ return temp;
+ }
+ return mask;
+ }
+ break;
+ case GE:
+ case GEU:
+ case LE:
+ case LEU:
+ /* Try GT/GTU/LT/LTU OR EQ */
+ {
+ rtx c_rtx, eq_rtx;
+ enum insn_code ior_code;
+ enum rtx_code new_code;
+
+ if (rcode == GE)
+ new_code = GT;
+ else if (rcode == GEU)
+ new_code = GTU;
+ else if (rcode == LE)
+ new_code = LT;
+ else if (rcode == LEU)
+ new_code = LTU;
+ else
+ abort ();
+
+ c_rtx = rs6000_emit_vector_compare (new_code,
+ op0, op1, dest_mode);
+ eq_rtx = rs6000_emit_vector_compare (EQ, op0, op1,
+ dest_mode);
+
+ ior_code = ior_optab->handlers[(int)dest_mode].insn_code;
+ if (ior_code == CODE_FOR_nothing)
+ abort ();
+ emit_insn (GEN_FCN (ior_code) (mask, c_rtx, eq_rtx));
+ if (dmode != dest_mode)
+ {
+ rtx temp = gen_reg_rtx (dest_mode);
+ convert_move (temp, mask, 0);
+ return temp;
+ }
+ return mask;
+ }
+ break;
+ default:
+ abort ();
+ }
+
+ if (try_again)
+ {
+ vec_cmp_insn = get_vec_cmp_insn (rcode, dest_mode, op_mode);
+ if (vec_cmp_insn == INSN_NOT_AVAILABLE)
+ /* You only get two chances. */
+ abort ();
+ }
+
+ if (swap_operands)
+ {
+ rtx tmp;
+ tmp = op0;
+ op0 = op1;
+ op1 = tmp;
+ }
+ }
+
+ emit_insn (gen_rtx_fmt_ee (SET,
+ VOIDmode,
+ mask,
+ gen_rtx_fmt_Ei (UNSPEC, dest_mode,
+ gen_rtvec (2, op0, op1),
+ vec_cmp_insn)));
+ if (dmode != dest_mode)
+ {
+ rtx temp = gen_reg_rtx (dest_mode);
+ convert_move (temp, mask, 0);
+ return temp;
+ }
+ return mask;
+}
+
+/* Return vector select instruction for MODE. Return INSN_NOT_AVAILABLE, if
+ valid insn doesn exist for given mode. */
+
+static int
+get_vsel_insn (enum machine_mode mode)
+{
+ switch (mode)
+ {
+ case V4SImode:
+ return UNSPEC_VSEL4SI;
+ break;
+ case V4SFmode:
+ return UNSPEC_VSEL4SF;
+ break;
+ case V8HImode:
+ return UNSPEC_VSEL8HI;
+ break;
+ case V16QImode:
+ return UNSPEC_VSEL16QI;
+ break;
+ default:
+ return INSN_NOT_AVAILABLE;
+ break;
+ }
+ return INSN_NOT_AVAILABLE;
+}
+
+/* Emit vector select insn where DEST is destination using
+ operands OP1, OP2 and MASK. */
+
+static void
+rs6000_emit_vector_select (rtx dest, rtx op1, rtx op2, rtx mask)
+{
+ rtx t, temp;
+ enum machine_mode dest_mode = GET_MODE (dest);
+ int vsel_insn_index = get_vsel_insn (GET_MODE (dest));
+
+ temp = gen_reg_rtx (dest_mode);
+
+ t = gen_rtx_fmt_ee (SET, VOIDmode, temp,
+ gen_rtx_fmt_Ei (UNSPEC, dest_mode,
+ gen_rtvec (3, op1, op2, mask),
+ vsel_insn_index));
+ emit_insn (t);
+ emit_move_insn (dest, temp);
+ return;
+}
+
+/* Emit vector conditional expression.
+ DEST is destination. OP1 and OP2 are two VEC_COND_EXPR operands.
+ CC_OP0 and CC_OP1 are the two operands for the relation operation COND. */
+
+int
+rs6000_emit_vector_cond_expr (rtx dest, rtx op1, rtx op2,
+ rtx cond, rtx cc_op0, rtx cc_op1)
+{
+ enum machine_mode dest_mode = GET_MODE (dest);
+ enum rtx_code rcode = GET_CODE (cond);
+ rtx mask;
+
+ if (!TARGET_ALTIVEC)
+ return 0;
+
+ /* Get the vector mask for the given relational operations. */
+ mask = rs6000_emit_vector_compare (rcode, cc_op0, cc_op1, dest_mode);
+
+ rs6000_emit_vector_select (dest, op1, op2, mask);
+
+ return 1;
+}
+
/* Emit a conditional move: move TRUE_COND to DEST if OP of the
operands of the last comparison is nonzero/true, FALSE_COND if it
is zero/false. Return 0 if the hardware has no such operation. */
@@ -11216,6 +11489,7 @@ rs6000_emit_cmove (rtx dest, rtx op, rtx true_cond, rtx false_cond)
enum machine_mode compare_mode = GET_MODE (op0);
enum machine_mode result_mode = GET_MODE (dest);
rtx temp;
+ bool is_against_zero;
/* These modes should always match. */
if (GET_MODE (op1) != compare_mode
@@ -11240,6 +11514,17 @@ rs6000_emit_cmove (rtx dest, rtx op, rtx true_cond, rtx false_cond)
&& GET_MODE_CLASS (compare_mode) == MODE_FLOAT)
return 0;
+ is_against_zero = op1 == CONST0_RTX (compare_mode);
+
+ /* A floating-point subtract might overflow, underflow, or produce
+ an inexact result, thus changing the floating-point flags, so it
+ can't be generated if we care about that. It's safe if one side
+ of the construct is zero, since then no subtract will be
+ generated. */
+ if (GET_MODE_CLASS (compare_mode) == MODE_FLOAT
+ && flag_trapping_math && ! is_against_zero)
+ return 0;
+
/* Eliminate half of the comparisons by switching operands, this
makes the remaining code simpler. */
if (code == UNLT || code == UNGT || code == UNORDERED || code == NE
@@ -11272,14 +11557,18 @@ rs6000_emit_cmove (rtx dest, rtx op, rtx true_cond, rtx false_cond)
|| (! rtx_equal_p (op0, true_cond)
&& ! rtx_equal_p (op1, true_cond))))
return 0;
+
/* At this point we know we can use fsel. */
/* Reduce the comparison to a comparison against zero. */
- temp = gen_reg_rtx (compare_mode);
- emit_insn (gen_rtx_SET (VOIDmode, temp,
- gen_rtx_MINUS (compare_mode, op0, op1)));
- op0 = temp;
- op1 = CONST0_RTX (compare_mode);
+ if (! is_against_zero)
+ {
+ temp = gen_reg_rtx (compare_mode);
+ emit_insn (gen_rtx_SET (VOIDmode, temp,
+ gen_rtx_MINUS (compare_mode, op0, op1)));
+ op0 = temp;
+ op1 = CONST0_RTX (compare_mode);
+ }
/* If we don't care about NaNs we can reduce some of the comparisons
down to faster ones. */