re PR target/53988 ([SH] tst Rm,Rn not used for QI/HImode)
gcc/ PR target/53988 * config/sh/sh-protos.h (sh_find_set_of_reg): Add option to ignore reg-reg copies. (sh_extending_set_of_reg): New struct. (sh_find_extending_set_of_reg, sh_split_tst_subregs, sh_remove_reg_dead_or_unused_notes): New Declarations. * config/sh/sh.c (sh_remove_reg_dead_or_unused_notes, sh_find_extending_set_of_reg, sh_split_tst_subregs, sh_extending_set_of_reg::use_as_extended_reg): New functions. * config/sh/sh.md (*tst<mode>_t_zero): Rename to *tst<mode>_t_subregs, convert to insn_and_split and use new function sh_split_tst_subregs. gcc/testsuite/ PR target/53988 * gcc.target/sh/pr53988-1.c: New. From-SVN: r219623
This commit is contained in:
parent
ce2c3163d2
commit
83e3f98b8d
6 changed files with 349 additions and 16 deletions
|
@ -1,3 +1,17 @@
|
|||
2015-01-14 Oleg Endo <olegendo@gcc.gnu.org>
|
||||
|
||||
PR target/53988
|
||||
* config/sh/sh-protos.h (sh_find_set_of_reg): Add option to ignore
|
||||
reg-reg copies.
|
||||
(sh_extending_set_of_reg): New struct.
|
||||
(sh_find_extending_set_of_reg, sh_split_tst_subregs,
|
||||
sh_remove_reg_dead_or_unused_notes): New Declarations.
|
||||
* config/sh/sh.c (sh_remove_reg_dead_or_unused_notes,
|
||||
sh_find_extending_set_of_reg, sh_split_tst_subregs,
|
||||
sh_extending_set_of_reg::use_as_extended_reg): New functions.
|
||||
* config/sh/sh.md (*tst<mode>_t_zero): Rename to *tst<mode>_t_subregs,
|
||||
convert to insn_and_split and use new function sh_split_tst_subregs.
|
||||
|
||||
2015-01-14 Sandra Loosemore <sandra@codesourcery.com>
|
||||
|
||||
* doc/invoke.texi (Option Summary): Reclassify -fuse-ld as a linker
|
||||
|
|
|
@ -181,7 +181,8 @@ struct set_of_reg
|
|||
'prev_nonnote_insn_bb'. When the insn is found, try to extract the rtx
|
||||
of the reg set. */
|
||||
template <typename F> inline set_of_reg
|
||||
sh_find_set_of_reg (rtx reg, rtx_insn* insn, F stepfunc)
|
||||
sh_find_set_of_reg (rtx reg, rtx_insn* insn, F stepfunc,
|
||||
bool ignore_reg_reg_copies = false)
|
||||
{
|
||||
set_of_reg result;
|
||||
result.insn = insn;
|
||||
|
@ -206,6 +207,19 @@ sh_find_set_of_reg (rtx reg, rtx_insn* insn, F stepfunc)
|
|||
return result;
|
||||
|
||||
result.set_src = XEXP (result.set_rtx, 1);
|
||||
|
||||
if (ignore_reg_reg_copies && REG_P (result.set_src))
|
||||
{
|
||||
reg = result.set_src;
|
||||
continue;
|
||||
}
|
||||
if (ignore_reg_reg_copies && SUBREG_P (result.set_src)
|
||||
&& REG_P (SUBREG_REG (result.set_src)))
|
||||
{
|
||||
reg = SUBREG_REG (result.set_src);
|
||||
continue;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -213,10 +227,50 @@ sh_find_set_of_reg (rtx reg, rtx_insn* insn, F stepfunc)
|
|||
return result;
|
||||
}
|
||||
|
||||
/* Result value of sh_find_extending_set_of_reg. */
|
||||
struct sh_extending_set_of_reg : public set_of_reg
|
||||
{
|
||||
/* The mode the set is extending from (QImode or HImode), or VOIDmode if
|
||||
this is not a zero/sign extending set. */
|
||||
machine_mode from_mode;
|
||||
|
||||
/* ZERO_EXTEND, SIGN_EXTEND or UNKNOWN. */
|
||||
rtx_code ext_code;
|
||||
|
||||
sh_extending_set_of_reg (rtx_insn* i)
|
||||
{
|
||||
insn = i;
|
||||
set_rtx = NULL;
|
||||
set_src = NULL;
|
||||
from_mode = VOIDmode;
|
||||
ext_code = UNKNOWN;
|
||||
}
|
||||
|
||||
sh_extending_set_of_reg (const set_of_reg& rhs)
|
||||
{
|
||||
*((set_of_reg*)this) = rhs;
|
||||
from_mode = VOIDmode;
|
||||
ext_code = UNKNOWN;
|
||||
}
|
||||
|
||||
/* Returns the reg rtx of the sign or zero extending result, that can be
|
||||
safely used at the specified insn in SImode. If the set source is an
|
||||
implicitly sign extending mem load, the mem load is converted into an
|
||||
explicitly sign extending mem load. */
|
||||
rtx use_as_extended_reg (rtx_insn* use_at_insn) const;
|
||||
};
|
||||
|
||||
extern sh_extending_set_of_reg sh_find_extending_set_of_reg (rtx reg,
|
||||
rtx_insn* insn);
|
||||
|
||||
extern bool sh_is_logical_t_store_expr (rtx op, rtx_insn* insn);
|
||||
extern rtx sh_try_omit_signzero_extend (rtx extended_op, rtx_insn* insn);
|
||||
extern bool sh_split_movrt_negc_to_movt_xor (rtx_insn* curr_insn,
|
||||
rtx operands[]);
|
||||
extern void sh_split_tst_subregs (rtx_insn* curr_insn,
|
||||
machine_mode subreg_mode, int subreg_offset,
|
||||
rtx operands[]);
|
||||
extern void sh_remove_reg_dead_or_unused_notes (rtx_insn* i, int regno);
|
||||
#endif /* RTX_CODE */
|
||||
|
||||
extern void sh_cpu_cpp_builtins (cpp_reader* pfile);
|
||||
|
|
|
@ -13769,6 +13769,17 @@ sh_insn_operands_modified_between_p (rtx_insn* operands_insn,
|
|||
return false;
|
||||
}
|
||||
|
||||
/* Given an insn and a reg number, remove reg dead or reg unused notes to
|
||||
mark it as being used after the insn. */
|
||||
void
|
||||
sh_remove_reg_dead_or_unused_notes (rtx_insn* i, int regno)
|
||||
{
|
||||
if (rtx n = find_regno_note (i, REG_DEAD, regno))
|
||||
remove_note (i, n);
|
||||
if (rtx n = find_regno_note (i, REG_UNUSED, regno))
|
||||
remove_note (i, n);
|
||||
}
|
||||
|
||||
/* Given an op rtx and an insn, try to find out whether the result of the
|
||||
specified op consists only of logical operations on T bit stores. */
|
||||
bool
|
||||
|
@ -13881,6 +13892,175 @@ sh_split_movrt_negc_to_movt_xor (rtx_insn* curr_insn, rtx operands[])
|
|||
return false;
|
||||
}
|
||||
|
||||
/* Given a reg and the current insn, see if the value of the reg originated
|
||||
from a sign or zero extension and return the discovered information. */
|
||||
sh_extending_set_of_reg
|
||||
sh_find_extending_set_of_reg (rtx reg, rtx_insn* curr_insn)
|
||||
{
|
||||
if (reg == NULL)
|
||||
return sh_extending_set_of_reg (curr_insn);
|
||||
|
||||
if (SUBREG_P (reg))
|
||||
reg = SUBREG_REG (reg);
|
||||
|
||||
if (!REG_P (reg))
|
||||
return sh_extending_set_of_reg (curr_insn);
|
||||
|
||||
/* FIXME: Also search the predecessor basic blocks. It seems that checking
|
||||
only the adjacent predecessor blocks would cover most of the cases.
|
||||
Also try to look through the first extension that we hit. There are some
|
||||
cases, where a zero_extend is followed an (implicit) sign_extend, and it
|
||||
fails to see the sign_extend. */
|
||||
sh_extending_set_of_reg result =
|
||||
sh_find_set_of_reg (reg, curr_insn, prev_nonnote_insn_bb, true);
|
||||
|
||||
if (result.set_src != NULL)
|
||||
{
|
||||
if (GET_CODE (result.set_src) == SIGN_EXTEND
|
||||
|| GET_CODE (result.set_src) == ZERO_EXTEND)
|
||||
{
|
||||
if (dump_file)
|
||||
fprintf (dump_file, "sh_find_szexnteded_reg: reg %d is "
|
||||
"explicitly sign/zero extended in insn %d\n",
|
||||
REGNO (reg), INSN_UID (result.insn));
|
||||
result.from_mode = GET_MODE (XEXP (result.set_src, 0));
|
||||
result.ext_code = GET_CODE (result.set_src);
|
||||
}
|
||||
else if (MEM_P (result.set_src)
|
||||
&& (GET_MODE (result.set_src) == QImode
|
||||
|| GET_MODE (result.set_src) == HImode))
|
||||
{
|
||||
/* On SH QIHImode memory loads always sign extend. However, in
|
||||
some cases where it seems that the higher bits are not
|
||||
interesting, the loads will not be expanded as sign extending
|
||||
insns, but as QIHImode loads into QIHImode regs. We report that
|
||||
the reg has been sign extended by the mem load. When it is used
|
||||
as such, we must convert the mem load into a sign extending insn,
|
||||
see also sh_extending_set_of_reg::use_as_extended_reg. */
|
||||
if (dump_file)
|
||||
fprintf (dump_file, "sh_find_extending_set_of_reg: reg %d is "
|
||||
"implicitly sign extended in insn %d\n",
|
||||
REGNO (reg), INSN_UID (result.insn));
|
||||
result.from_mode = GET_MODE (result.set_src);
|
||||
result.ext_code = SIGN_EXTEND;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Given a reg that is known to be sign or zero extended at some insn,
|
||||
take the appropriate measures so that the extended value can be used as
|
||||
a reg at the specified insn and return the resulting reg rtx. */
|
||||
rtx
|
||||
sh_extending_set_of_reg::use_as_extended_reg (rtx_insn* use_at_insn) const
|
||||
{
|
||||
gcc_assert (insn != NULL && set_src != NULL && set_rtx != NULL);
|
||||
gcc_assert (ext_code == SIGN_EXTEND || ext_code == ZERO_EXTEND);
|
||||
gcc_assert (from_mode == QImode || from_mode == HImode);
|
||||
|
||||
if (MEM_P (set_src) && ext_code == SIGN_EXTEND)
|
||||
{
|
||||
if (dump_file)
|
||||
fprintf (dump_file,
|
||||
"use_as_extended_reg: converting non-extending mem load in "
|
||||
"insn %d into sign-extending load\n", INSN_UID (insn));
|
||||
|
||||
rtx r = gen_reg_rtx (SImode);
|
||||
rtx_insn* i0;
|
||||
if (from_mode == QImode)
|
||||
i0 = emit_insn_after (gen_extendqisi2 (r, set_src), insn);
|
||||
else if (from_mode == HImode)
|
||||
i0 = emit_insn_after (gen_extendhisi2 (r, set_src), insn);
|
||||
else
|
||||
gcc_unreachable ();
|
||||
|
||||
emit_insn_after (
|
||||
gen_move_insn (XEXP (set_rtx, 0),
|
||||
gen_lowpart (GET_MODE (set_src), r)), i0);
|
||||
set_insn_deleted (insn);
|
||||
return r;
|
||||
}
|
||||
else
|
||||
{
|
||||
rtx extension_dst = XEXP (set_rtx, 0);
|
||||
if (modified_between_p (extension_dst, insn, use_at_insn))
|
||||
{
|
||||
if (dump_file)
|
||||
fprintf (dump_file,
|
||||
"use_as_extended_reg: dest reg %d of extending insn %d is "
|
||||
"modified, inserting a reg-reg copy\n",
|
||||
REGNO (extension_dst), INSN_UID (insn));
|
||||
|
||||
rtx r = gen_reg_rtx (SImode);
|
||||
emit_insn_after (gen_move_insn (r, extension_dst), insn);
|
||||
return r;
|
||||
}
|
||||
else
|
||||
{
|
||||
sh_remove_reg_dead_or_unused_notes (insn, REGNO (extension_dst));
|
||||
return extension_dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Given the current insn, which is assumed to be the *tst<mode>_t_subregs insn,
|
||||
perform the necessary checks on the operands and split it accordingly. */
|
||||
void
|
||||
sh_split_tst_subregs (rtx_insn* curr_insn, machine_mode subreg_mode,
|
||||
int subreg_offset, rtx operands[])
|
||||
{
|
||||
gcc_assert (subreg_mode == QImode || subreg_mode == HImode);
|
||||
|
||||
sh_extending_set_of_reg eop0 = sh_find_extending_set_of_reg (operands[0],
|
||||
curr_insn);
|
||||
sh_extending_set_of_reg eop1 = sh_find_extending_set_of_reg (operands[1],
|
||||
curr_insn);
|
||||
|
||||
/* If one of the operands is known to be zero extended, that's already
|
||||
sufficient to mask out the unwanted high bits. */
|
||||
if (eop0.ext_code == ZERO_EXTEND && eop0.from_mode == subreg_mode)
|
||||
{
|
||||
emit_insn (gen_tstsi_t (eop0.use_as_extended_reg (curr_insn),
|
||||
operands[1]));
|
||||
return;
|
||||
}
|
||||
if (eop1.ext_code == ZERO_EXTEND && eop1.from_mode == subreg_mode)
|
||||
{
|
||||
emit_insn (gen_tstsi_t (operands[0],
|
||||
eop1.use_as_extended_reg (curr_insn)));
|
||||
return;
|
||||
}
|
||||
|
||||
/* None of the operands seem to be zero extended.
|
||||
If both are sign extended it's OK, too. */
|
||||
if (eop0.ext_code == SIGN_EXTEND && eop1.ext_code == SIGN_EXTEND
|
||||
&& eop0.from_mode == subreg_mode && eop1.from_mode == subreg_mode)
|
||||
{
|
||||
emit_insn (gen_tstsi_t (eop0.use_as_extended_reg (curr_insn),
|
||||
eop1.use_as_extended_reg (curr_insn)));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Otherwise we have to insert a zero extension on one of the operands to
|
||||
mask out the unwanted high bits.
|
||||
Prefer the operand that has no known extension. */
|
||||
if (eop0.ext_code != UNKNOWN && eop1.ext_code == UNKNOWN)
|
||||
std::swap (operands[0], operands[1]);
|
||||
|
||||
rtx tmp0 = gen_reg_rtx (SImode);
|
||||
rtx tmp1 = simplify_gen_subreg (subreg_mode, operands[0],
|
||||
GET_MODE (operands[0]), subreg_offset);
|
||||
emit_insn (subreg_mode == QImode
|
||||
? gen_zero_extendqisi2 (tmp0, tmp1)
|
||||
: gen_zero_extendhisi2 (tmp0, tmp1));
|
||||
emit_insn (gen_tstsi_t (tmp0, operands[1]));
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
Mode switching support code.
|
||||
*/
|
||||
|
||||
static void
|
||||
sh_emit_mode_set (int entity ATTRIBUTE_UNUSED, int mode,
|
||||
int prev_mode, HARD_REG_SET regs_live ATTRIBUTE_UNUSED)
|
||||
|
@ -13949,6 +14129,10 @@ sh_mode_priority (int entity ATTRIBUTE_UNUSED, int n)
|
|||
return ((TARGET_FPU_SINGLE != 0) ^ (n) ? FP_MODE_SINGLE : FP_MODE_DOUBLE);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
Misc
|
||||
*/
|
||||
|
||||
/* Return true if we use LRA instead of reload pass. */
|
||||
static bool
|
||||
sh_lra_p (void)
|
||||
|
|
|
@ -666,30 +666,40 @@
|
|||
[(set_attr "type" "mt_group")])
|
||||
|
||||
;; This pattern might be risky because it also tests the upper bits and not
|
||||
;; only the subreg. However, it seems that combine will get to this only
|
||||
;; when testing sign/zero extended values. In this case the extended upper
|
||||
;; bits do not matter.
|
||||
(define_insn "*tst<mode>_t_zero"
|
||||
;; only the subreg. We have to check whether the operands have been sign
|
||||
;; or zero extended. In the worst case, a zero extension has to be inserted
|
||||
;; to mask out the unwanted bits.
|
||||
(define_insn_and_split "*tst<mode>_t_subregs"
|
||||
[(set (reg:SI T_REG)
|
||||
(eq:SI
|
||||
(subreg:QIHI
|
||||
(and:SI (match_operand:SI 0 "arith_reg_operand" "%r")
|
||||
(match_operand:SI 1 "arith_reg_operand" "r")) <lowpart_le>)
|
||||
(and:SI (match_operand:SI 0 "arith_reg_operand")
|
||||
(match_operand:SI 1 "arith_reg_operand")) <lowpart_le>)
|
||||
(const_int 0)))]
|
||||
"TARGET_SH1 && TARGET_LITTLE_ENDIAN"
|
||||
"tst %0,%1"
|
||||
[(set_attr "type" "mt_group")])
|
||||
"TARGET_SH1 && TARGET_LITTLE_ENDIAN && can_create_pseudo_p ()"
|
||||
"#"
|
||||
"&& 1"
|
||||
[(const_int 0)]
|
||||
{
|
||||
sh_split_tst_subregs (curr_insn, <MODE>mode, <lowpart_le>, operands);
|
||||
DONE;
|
||||
})
|
||||
|
||||
(define_insn "*tst<mode>_t_zero"
|
||||
(define_insn_and_split "*tst<mode>_t_subregs"
|
||||
[(set (reg:SI T_REG)
|
||||
(eq:SI
|
||||
(subreg:QIHI
|
||||
(and:SI (match_operand:SI 0 "arith_reg_operand" "%r")
|
||||
(match_operand:SI 1 "arith_reg_operand" "r")) <lowpart_be>)
|
||||
(and:SI (match_operand:SI 0 "arith_reg_operand")
|
||||
(match_operand:SI 1 "arith_reg_operand")) <lowpart_be>)
|
||||
(const_int 0)))]
|
||||
"TARGET_SH1 && TARGET_BIG_ENDIAN"
|
||||
"tst %0,%1"
|
||||
[(set_attr "type" "mt_group")])
|
||||
"TARGET_SH1 && TARGET_BIG_ENDIAN && can_create_pseudo_p ()"
|
||||
"#"
|
||||
"&& 1"
|
||||
[(const_int 0)]
|
||||
{
|
||||
sh_split_tst_subregs (curr_insn, <MODE>mode, <lowpart_be>, operands);
|
||||
DONE;
|
||||
})
|
||||
|
||||
;; Extract LSB, negate and store in T bit.
|
||||
(define_insn "tstsi_t_and_not"
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
2015-01-14 Oleg Endo <olegendo@gcc.gnu.org>
|
||||
|
||||
PR target/53988
|
||||
* gcc.target/sh/pr53988-1.c: New.
|
||||
|
||||
2015-01-14 Paolo Carlini <paolo.carlini@oracle.com>
|
||||
|
||||
PR c++/58671
|
||||
|
|
66
gcc/testsuite/gcc.target/sh/pr53988-1.c
Normal file
66
gcc/testsuite/gcc.target/sh/pr53988-1.c
Normal file
|
@ -0,0 +1,66 @@
|
|||
/* Check that sign/zero extensions are emitted where needed when the
|
||||
tst Rm,Rn instruction is used. */
|
||||
/* { dg-do compile } */
|
||||
/* { dg-options "-O1" } */
|
||||
/* { dg-skip-if "" { "sh*-*-*" } { "-m5*"} { "" } } */
|
||||
/* { dg-final { scan-assembler-times "tst\tr" 8 } } */
|
||||
/* { dg-final { scan-assembler-times "mov.b" 4 } } */
|
||||
/* { dg-final { scan-assembler-times "mov.w" 4 } } */
|
||||
/* { dg-final { scan-assembler-times "extu.b" 4 } } */
|
||||
/* { dg-final { scan-assembler-times "extu.w" 2 } } */
|
||||
|
||||
int
|
||||
test_00 (char* x, char* y)
|
||||
{
|
||||
/* 2x mov.b (sign extending) */
|
||||
return *x & *y ? -40 : 60;
|
||||
}
|
||||
|
||||
int
|
||||
test_01 (short* x, short* y)
|
||||
{
|
||||
/* 2x mov.w (sign extending) */
|
||||
return *x & *y ? -40 : 60;
|
||||
}
|
||||
|
||||
int
|
||||
test_02 (char x, char y)
|
||||
{
|
||||
/* 1x extu.b */
|
||||
return x & y ? -40 : 60;
|
||||
}
|
||||
|
||||
int
|
||||
test_03 (short x, short y)
|
||||
{
|
||||
/* 1x extu.w */
|
||||
return x & y ? -40 : 60;
|
||||
}
|
||||
|
||||
int
|
||||
test_04 (char* x, unsigned char y)
|
||||
{
|
||||
/* 1x mov.b, 1x extu.b */
|
||||
return *x & y ? -40 : 60;
|
||||
}
|
||||
|
||||
int
|
||||
test_05 (short* x, unsigned char y)
|
||||
{
|
||||
/* 1x mov.w, 1x extu.b */
|
||||
return *x & y ? -40 : 60;
|
||||
}
|
||||
|
||||
int
|
||||
test_06 (short x, short* y, int z, int w)
|
||||
{
|
||||
/* 1x mov.w, 1x extu.w */
|
||||
return x & y[0] ? z : w;
|
||||
}
|
||||
|
||||
int
|
||||
test_07 (char x, char* y, int z, int w)
|
||||
{
|
||||
/* 1x mov.b, 1x extu.b */
|
||||
return x & y[0] ? z : w;
|
||||
}
|
Loading…
Add table
Reference in a new issue