aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOleg Endo <olegendo@gcc.gnu.org>2012-10-08 02:00:46 +0000
committerOleg Endo <olegendo@gcc.gnu.org>2012-10-08 02:00:46 +0000
commitfce1e5fb8f22c020935ab99b9a05e55ed4a89c78 (patch)
treea07109dc211baaa3ed5080642628c9d5d4ceba88
parent7f7b06c14f2c60b092cf254c94490cfb35211135 (diff)
downloadgcc-fce1e5fb8f22c020935ab99b9a05e55ed4a89c78.zip
gcc-fce1e5fb8f22c020935ab99b9a05e55ed4a89c78.tar.gz
gcc-fce1e5fb8f22c020935ab99b9a05e55ed4a89c78.tar.bz2
re PR target/54760 ([SH] Add __builtin_thread_pointer, __builtin_set_thread_pointer)
PR target/54760 * config/sh/sh.md (*mov<mode>_gbr_load, *mov<mode>_gbr_store): New insns and accompanying unnamed splits. * config/sh/predicates.md (general_movsrc_operand, general_movdst_operand): Reject GBR addresses. * config/sh/sh-protos.h (sh_find_equiv_gbr_addr): New declaration. * config/sh/sh.c (sh_address_cost, sh_legitimate_address_p, sh_secondary_reload): Handle GBR addresses. (base_reg_disp): New class. (sh_find_base_reg_disp, sh_find_equiv_gbr_addr): New functions. PR target/54760 * gcc.target/sh/pr54760-2.c: New. * gcc.target/sh/pr54760-3.c: New. From-SVN: r192193
-rw-r--r--gcc/ChangeLog13
-rw-r--r--gcc/config/sh/predicates.md19
-rw-r--r--gcc/config/sh/sh-protos.h2
-rw-r--r--gcc/config/sh/sh.c176
-rw-r--r--gcc/config/sh/sh.md129
-rw-r--r--gcc/testsuite/ChangeLog6
-rw-r--r--gcc/testsuite/gcc.target/sh/pr54760-2.c223
-rw-r--r--gcc/testsuite/gcc.target/sh/pr54760-3.c69
8 files changed, 635 insertions, 2 deletions
diff --git a/gcc/ChangeLog b/gcc/ChangeLog
index 8f944d7..7fcb167 100644
--- a/gcc/ChangeLog
+++ b/gcc/ChangeLog
@@ -1,3 +1,16 @@
+2012-10-08 Oleg Endo <olegendo@gcc.gnu.org>
+
+ PR target/54760
+ * config/sh/sh.md (*mov<mode>_gbr_load, *mov<mode>_gbr_store): New
+ insns and accompanying unnamed splits.
+ * config/sh/predicates.md (general_movsrc_operand,
+ general_movdst_operand): Reject GBR addresses.
+ * config/sh/sh-protos.h (sh_find_equiv_gbr_addr): New declaration.
+ * config/sh/sh.c (sh_address_cost, sh_legitimate_address_p,
+ sh_secondary_reload): Handle GBR addresses.
+ (base_reg_disp): New class.
+ (sh_find_base_reg_disp, sh_find_equiv_gbr_addr): New functions.
+
2012-10-08 Hans-Peter Nilsson <hp@bitrange.com>
* config/mmix/mmix.c (mmix_output_octa): Don't assume
diff --git a/gcc/config/sh/predicates.md b/gcc/config/sh/predicates.md
index 05ad858..89b4d0f 100644
--- a/gcc/config/sh/predicates.md
+++ b/gcc/config/sh/predicates.md
@@ -409,6 +409,14 @@
if (MEM_P (op))
{
rtx inside = XEXP (op, 0);
+
+ /* Disallow mems with GBR address here. They have to go through
+ separate special patterns. */
+ if ((REG_P (inside) && REGNO (inside) == GBR_REG)
+ || (GET_CODE (inside) == PLUS && REG_P (XEXP (inside, 0))
+ && REGNO (XEXP (inside, 0)) == GBR_REG))
+ return 0;
+
if (GET_CODE (inside) == CONST)
inside = XEXP (inside, 0);
@@ -466,6 +474,17 @@
if (t_reg_operand (op, mode))
return 0;
+ if (MEM_P (op))
+ {
+ rtx inside = XEXP (op, 0);
+ /* Disallow mems with GBR address here. They have to go through
+ separate special patterns. */
+ if ((REG_P (inside) && REGNO (inside) == GBR_REG)
+ || (GET_CODE (inside) == PLUS && REG_P (XEXP (inside, 0))
+ && REGNO (XEXP (inside, 0)) == GBR_REG))
+ return 0;
+ }
+
/* Only pre dec allowed. */
if (MEM_P (op) && GET_CODE (XEXP (op, 0)) == POST_INC)
return 0;
diff --git a/gcc/config/sh/sh-protos.h b/gcc/config/sh/sh-protos.h
index d908110..61d4eab 100644
--- a/gcc/config/sh/sh-protos.h
+++ b/gcc/config/sh/sh-protos.h
@@ -161,7 +161,7 @@ extern bool sh_vector_mode_supported_p (enum machine_mode);
extern bool sh_cfun_trap_exit_p (void);
extern void sh_canonicalize_comparison (enum rtx_code&, rtx&, rtx&,
enum machine_mode mode = VOIDmode);
-
+extern rtx sh_find_equiv_gbr_addr (rtx cur_insn, rtx mem);
#endif /* RTX_CODE */
extern void sh_cpu_cpp_builtins (cpp_reader* pfile);
diff --git a/gcc/config/sh/sh.c b/gcc/config/sh/sh.c
index 3a0689d..d1ab28a 100644
--- a/gcc/config/sh/sh.c
+++ b/gcc/config/sh/sh.c
@@ -3610,6 +3610,10 @@ static int
sh_address_cost (rtx x, enum machine_mode mode,
addr_space_t as ATTRIBUTE_UNUSED, bool speed ATTRIBUTE_UNUSED)
{
+ /* 'GBR + 0'. Account one more because of R0 restriction. */
+ if (REG_P (x) && REGNO (x) == GBR_REG)
+ return 2;
+
/* Simple reg, post-inc, pre-dec addressing. */
if (REG_P (x) || GET_CODE (x) == POST_INC || GET_CODE (x) == PRE_DEC)
return 1;
@@ -3618,6 +3622,11 @@ sh_address_cost (rtx x, enum machine_mode mode,
if (GET_CODE (x) == PLUS
&& REG_P (XEXP (x, 0)) && CONST_INT_P (XEXP (x, 1)))
{
+ /* 'GBR + disp'. Account one more because of R0 restriction. */
+ if (REGNO (XEXP (x, 0)) == GBR_REG
+ && gbr_displacement (XEXP (x, 1), mode))
+ return 2;
+
const HOST_WIDE_INT offset = INTVAL (XEXP (x, 1));
if (offset == 0)
@@ -10185,11 +10194,16 @@ sh_legitimate_index_p (enum machine_mode mode, rtx op, bool consider_sh2a,
REG+disp
REG+r0
REG++
- --REG */
+ --REG
+ GBR
+ GBR+disp */
static bool
sh_legitimate_address_p (enum machine_mode mode, rtx x, bool strict)
{
+ if (REG_P (x) && REGNO (x) == GBR_REG)
+ return true;
+
if (MAYBE_BASE_REGISTER_RTX_P (x, strict))
return true;
else if ((GET_CODE (x) == POST_INC || GET_CODE (x) == PRE_DEC)
@@ -10202,6 +10216,9 @@ sh_legitimate_address_p (enum machine_mode mode, rtx x, bool strict)
rtx xop0 = XEXP (x, 0);
rtx xop1 = XEXP (x, 1);
+ if (REG_P (xop0) && REGNO (xop0) == GBR_REG)
+ return gbr_displacement (xop1, mode);
+
if (GET_MODE_SIZE (mode) <= 8
&& MAYBE_BASE_REGISTER_RTX_P (xop0, strict)
&& sh_legitimate_index_p (mode, xop1, TARGET_SH2A, false))
@@ -13014,6 +13031,17 @@ sh_secondary_reload (bool in_p, rtx x, reg_class_t rclass_i,
{
enum reg_class rclass = (enum reg_class) rclass_i;
+ if (MEM_P (x) && GET_CODE (XEXP (x, 0)) == PLUS
+ && REG_P (XEXP (XEXP (x, 0), 0))
+ && REGNO (XEXP (XEXP (x, 0), 0)) == GBR_REG)
+ return rclass == R0_REGS ? NO_REGS : R0_REGS;
+
+ if (MEM_P (x) && REG_P (XEXP (x, 0)) && REGNO (XEXP (x, 0)) == GBR_REG)
+ return rclass == R0_REGS ? NO_REGS : R0_REGS;
+
+ if (REG_P (x) && REGNO (x) == GBR_REG)
+ return NO_REGS;
+
if (in_p)
{
if (REGCLASS_HAS_FP_REG (rclass)
@@ -13248,4 +13276,150 @@ sh_can_use_simple_return_p (void)
return true;
}
+/*------------------------------------------------------------------------------
+ Address mode optimization support code
+*/
+
+typedef HOST_WIDE_INT disp_t;
+static const disp_t MIN_DISP = HOST_WIDE_INT_MIN;
+static const disp_t MAX_DISP = HOST_WIDE_INT_MAX;
+static const disp_t INVALID_DISP = MAX_DISP;
+
+/* A memory reference which is described by a base register and a
+ displacement. */
+class base_reg_disp
+{
+public:
+ base_reg_disp (rtx br, disp_t d);
+
+ bool is_reg (void) const;
+ bool is_disp (void) const;
+ rtx reg (void) const;
+ disp_t disp (void) const;
+
+private:
+ rtx reg_;
+ disp_t disp_;
+};
+
+inline
+base_reg_disp::base_reg_disp (rtx br, disp_t d)
+: reg_ (br), disp_ (d)
+{
+}
+
+inline bool
+base_reg_disp::is_reg (void) const
+{
+ return reg_ != NULL_RTX && disp_ != INVALID_DISP;
+}
+
+inline bool
+base_reg_disp::is_disp (void) const
+{
+ return reg_ == NULL_RTX && disp_ != INVALID_DISP;
+}
+
+inline rtx
+base_reg_disp::reg (void) const
+{
+ return reg_;
+}
+
+inline disp_t
+base_reg_disp::disp (void) const
+{
+ return disp_;
+}
+
+/* Find the base register and calculate the displacement for a given
+ address rtx 'x'.
+ This is done by walking the insn list backwards and following SET insns
+ that set the value of the specified reg 'x'. */
+static base_reg_disp
+sh_find_base_reg_disp (rtx insn, rtx x, disp_t disp = 0, rtx base_reg = NULL)
+{
+ if (REG_P (x))
+ {
+ if (REGNO (x) == GBR_REG)
+ return base_reg_disp (x, disp);
+
+ /* We've reached a hard-reg. This is probably the point where
+ function args are copied to pseudos. Do not go any further and
+ stick to the pseudo. If the original mem addr was in a hard reg
+ from the beginning, it will become the base reg. */
+ if (REGNO (x) < FIRST_PSEUDO_REGISTER)
+ return base_reg_disp (base_reg != NULL ? base_reg : x, disp);
+
+ /* Try to find the previous insn that sets the reg. */
+ for (rtx i = prev_nonnote_insn (insn); i != NULL;
+ i = prev_nonnote_insn (i))
+ {
+ if (!NONJUMP_INSN_P (i))
+ continue;
+
+ rtx p = PATTERN (i);
+ if (p != NULL && GET_CODE (p) == SET && REG_P (XEXP (p, 0))
+ && REGNO (XEXP (p, 0)) == REGNO (x))
+ {
+ /* If the recursion can't find out any more details about the
+ source of the set, then this reg becomes our new base reg. */
+ return sh_find_base_reg_disp (i, XEXP (p, 1), disp, XEXP (p, 0));
+ }
+ }
+
+ /* When here, no previous insn was found that sets the reg.
+ The input reg is already the base reg. */
+ return base_reg_disp (x, disp);
+ }
+
+ else if (GET_CODE (x) == PLUS)
+ {
+ base_reg_disp left_val = sh_find_base_reg_disp (insn, XEXP (x, 0));
+ base_reg_disp right_val = sh_find_base_reg_disp (insn, XEXP (x, 1));
+
+ /* Either left or right val must be a reg.
+ We don't handle the case of 'reg + reg' here. */
+ if (left_val.is_reg () && right_val.is_disp ())
+ return base_reg_disp (left_val.reg (), left_val.disp ()
+ + right_val.disp () + disp);
+ else if (right_val.is_reg () && left_val.is_disp ())
+ return base_reg_disp (right_val.reg (), right_val.disp ()
+ + left_val.disp () + disp);
+ else
+ return base_reg_disp (base_reg, disp);
+ }
+
+ else if (CONST_INT_P (x))
+ return base_reg_disp (NULL, disp + INTVAL (x));
+
+ /* Didn't find anything useful. */
+ return base_reg_disp (base_reg, disp);
+}
+
+/* Given an insn and a memory operand, try to find an equivalent GBR
+ based memory address and return the corresponding new memory address.
+ Return NULL_RTX if not found. */
+rtx
+sh_find_equiv_gbr_addr (rtx insn, rtx mem)
+{
+ if (!MEM_P (mem))
+ return NULL_RTX;
+
+ /* Leave post/pre inc/dec or any other side effect addresses alone. */
+ if (side_effects_p (XEXP (mem, 0)))
+ return NULL_RTX;
+
+ base_reg_disp gbr_disp = sh_find_base_reg_disp (insn, XEXP (mem, 0));
+
+ if (gbr_disp.is_reg () && REGNO (gbr_disp.reg ()) == GBR_REG)
+ {
+ rtx disp = GEN_INT (gbr_disp.disp ());
+ if (gbr_displacement (disp, GET_MODE (mem)))
+ return gen_rtx_PLUS (SImode, gen_rtx_REG (SImode, GBR_REG), disp);
+ }
+
+ return NULL_RTX;
+}
+
#include "gt-sh.h"
diff --git a/gcc/config/sh/sh.md b/gcc/config/sh/sh.md
index eeed561..989d52e 100644
--- a/gcc/config/sh/sh.md
+++ b/gcc/config/sh/sh.md
@@ -10061,6 +10061,135 @@ label:
[(set_attr "type" "move")])
;;------------------------------------------------------------------------------
+;; Thread pointer relative memory loads and stores.
+;;
+;; On SH there are GBR displacement address modes which can be utilized to
+;; access memory behind the thread pointer.
+;; Since we do not allow using GBR for general purpose memory accesses, these
+;; GBR addressing modes are formed by the combine pass.
+;; This could be done with fewer patterns than below by using a mem predicate
+;; for the GBR mem, but then reload would try to reload addresses with a
+;; zero displacement for some strange reason.
+
+(define_insn "*mov<mode>_gbr_load"
+ [(set (match_operand:QIHISI 0 "register_operand" "=z")
+ (mem:QIHISI (plus:SI (reg:SI GBR_REG)
+ (match_operand:QIHISI 1 "gbr_displacement"))))]
+ "TARGET_SH1"
+ "mov.<bwl> @(%O1,gbr),%0"
+ [(set_attr "type" "load")])
+
+(define_insn "*mov<mode>_gbr_load"
+ [(set (match_operand:QIHISI 0 "register_operand" "=z")
+ (mem:QIHISI (reg:SI GBR_REG)))]
+ "TARGET_SH1"
+ "mov.<bwl> @(0,gbr),%0"
+ [(set_attr "type" "load")])
+
+(define_insn "*mov<mode>_gbr_load"
+ [(set (match_operand:SI 0 "register_operand" "=z")
+ (sign_extend:SI
+ (mem:QIHI (plus:SI (reg:SI GBR_REG)
+ (match_operand:QIHI 1 "gbr_displacement")))))]
+ "TARGET_SH1"
+ "mov.<bw> @(%O1,gbr),%0"
+ [(set_attr "type" "load")])
+
+(define_insn "*mov<mode>_gbr_load"
+ [(set (match_operand:SI 0 "register_operand" "=z")
+ (sign_extend:SI (mem:QIHI (reg:SI GBR_REG))))]
+ "TARGET_SH1"
+ "mov.<bw> @(0,gbr),%0"
+ [(set_attr "type" "load")])
+
+(define_insn "*mov<mode>_gbr_store"
+ [(set (mem:QIHISI (plus:SI (reg:SI GBR_REG)
+ (match_operand:QIHISI 0 "gbr_displacement")))
+ (match_operand:QIHISI 1 "register_operand" "z"))]
+ "TARGET_SH1"
+ "mov.<bwl> %1,@(%O0,gbr)"
+ [(set_attr "type" "store")])
+
+(define_insn "*mov<mode>_gbr_store"
+ [(set (mem:QIHISI (reg:SI GBR_REG))
+ (match_operand:QIHISI 0 "register_operand" "z"))]
+ "TARGET_SH1"
+ "mov.<bwl> %0,@(0,gbr)"
+ [(set_attr "type" "store")])
+
+;; Sometimes memory accesses do not get combined with the store_gbr insn,
+;; in particular when the displacements are in the range of the regular move
+;; insns. Thus, in the first split pass after the combine pass we search
+;; for missed opportunities and try to fix them up ourselves.
+;; If an equivalent GBR address can be determined the load / store is split
+;; into one of the GBR load / store patterns.
+;; All of that must happen before reload (GBR address modes use R0 as the
+;; other operand) and there's no point of doing it if the GBR is not
+;; referenced in a function at all.
+(define_split
+ [(set (match_operand:QIHISI 0 "register_operand")
+ (match_operand:QIHISI 1 "memory_operand"))]
+ "TARGET_SH1 && !reload_in_progress && !reload_completed
+ && df_regs_ever_live_p (GBR_REG)"
+ [(set (match_dup 0) (match_dup 1))]
+{
+ rtx gbr_mem = sh_find_equiv_gbr_addr (curr_insn, operands[1]);
+ if (gbr_mem != NULL_RTX)
+ operands[1] = change_address (operands[1], GET_MODE (operands[1]), gbr_mem);
+ else
+ FAIL;
+})
+
+(define_split
+ [(set (match_operand:SI 0 "register_operand")
+ (sign_extend:SI (match_operand:QIHI 1 "memory_operand")))]
+ "TARGET_SH1 && !reload_in_progress && !reload_completed
+ && df_regs_ever_live_p (GBR_REG)"
+ [(set (match_dup 0) (sign_extend:SI (match_dup 1)))]
+{
+ rtx gbr_mem = sh_find_equiv_gbr_addr (curr_insn, operands[1]);
+ if (gbr_mem != NULL_RTX)
+ operands[1] = change_address (operands[1], GET_MODE (operands[1]), gbr_mem);
+ else
+ FAIL;
+})
+
+;; On SH2A we've got movu.b and movu.w for doing zero-extending mem loads.
+;; Split those so that a GBR load can be used.
+(define_split
+ [(set (match_operand:SI 0 "register_operand")
+ (zero_extend:SI (match_operand:QIHI 1 "memory_operand")))]
+ "TARGET_SH2A && !reload_in_progress && !reload_completed
+ && df_regs_ever_live_p (GBR_REG)"
+ [(set (match_dup 2) (match_dup 1))
+ (set (match_dup 0) (zero_extend:SI (match_dup 2)))]
+{
+ rtx gbr_mem = sh_find_equiv_gbr_addr (curr_insn, operands[1]);
+ if (gbr_mem != NULL_RTX)
+ {
+ operands[2] = gen_reg_rtx (GET_MODE (operands[1]));
+ operands[1] = change_address (operands[1], GET_MODE (operands[1]),
+ gbr_mem);
+ }
+ else
+ FAIL;
+})
+
+(define_split
+ [(set (match_operand:QIHISI 0 "memory_operand")
+ (match_operand:QIHISI 1 "register_operand"))]
+ "TARGET_SH1 && !reload_in_progress && !reload_completed
+ && df_regs_ever_live_p (GBR_REG)"
+ [(set (match_dup 0) (match_dup 1))]
+{
+ rtx gbr_mem = sh_find_equiv_gbr_addr (curr_insn, operands[0]);
+ if (gbr_mem != NULL_RTX)
+ operands[0] = change_address (operands[0], GET_MODE (operands[0]), gbr_mem);
+ else
+ FAIL;
+})
+
+;;------------------------------------------------------------------------------
;; case instruction for switch statements.
;; Operand 0 is index
diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog
index a1c4a32..5eb6c6d 100644
--- a/gcc/testsuite/ChangeLog
+++ b/gcc/testsuite/ChangeLog
@@ -1,3 +1,9 @@
+2012-10-08 Oleg Endo <olegendo@gcc.gnu.org>
+
+ PR target/54760
+ * gcc.target/sh/pr54760-2.c: New.
+ * gcc.target/sh/pr54760-3.c: New.
+
2012-10-07 Paolo Carlini <paolo.carlini@oracle.com>
PR c++/51422
diff --git a/gcc/testsuite/gcc.target/sh/pr54760-2.c b/gcc/testsuite/gcc.target/sh/pr54760-2.c
new file mode 100644
index 0000000..b8a5018
--- /dev/null
+++ b/gcc/testsuite/gcc.target/sh/pr54760-2.c
@@ -0,0 +1,223 @@
+/* Check that thread pointer relative memory accesses are converted to
+ gbr displacement address modes. If we see a gbr register store
+ instruction something is not working properly. */
+/* { dg-do compile { target "sh*-*-*" } } */
+/* { dg-options "-O1" } */
+/* { dg-skip-if "" { "sh*-*-*" } { "-m5*"} { "" } } */
+/* { dg-final { scan-assembler-times "stc\tgbr" 0 } } */
+
+/* ---------------------------------------------------------------------------
+ Simple GBR load.
+*/
+#define func(name, type, disp)\
+ int \
+ name ## _tp_load (void) \
+ { \
+ type* tp = (type*)__builtin_thread_pointer (); \
+ return tp[disp]; \
+ }
+
+func (test00, int, 0)
+func (test01, int, 5)
+func (test02, int, 255)
+
+func (test03, short, 0)
+func (test04, short, 5)
+func (test05, short, 255)
+
+func (test06, char, 0)
+func (test07, char, 5)
+func (test08, char, 255)
+
+func (test09, unsigned int, 0)
+func (test10, unsigned int, 5)
+func (test11, unsigned int, 255)
+
+func (test12, unsigned short, 0)
+func (test13, unsigned short, 5)
+func (test14, unsigned short, 255)
+
+func (test15, unsigned char, 0)
+func (test16, unsigned char, 5)
+func (test17, unsigned char, 255)
+
+#undef func
+
+/* ---------------------------------------------------------------------------
+ Simple GBR store.
+*/
+#define func(name, type, disp)\
+ void \
+ name ## _tp_store (int a) \
+ { \
+ type* tp = (type*)__builtin_thread_pointer (); \
+ tp[disp] = (type)a; \
+ }
+
+func (test00, int, 0)
+func (test01, int, 5)
+func (test02, int, 255)
+
+func (test03, short, 0)
+func (test04, short, 5)
+func (test05, short, 255)
+
+func (test06, char, 0)
+func (test07, char, 5)
+func (test08, char, 255)
+
+func (test09, unsigned int, 0)
+func (test10, unsigned int, 5)
+func (test11, unsigned int, 255)
+
+func (test12, unsigned short, 0)
+func (test13, unsigned short, 5)
+func (test14, unsigned short, 255)
+
+func (test15, unsigned char, 0)
+func (test16, unsigned char, 5)
+func (test17, unsigned char, 255)
+
+#undef func
+
+/* ---------------------------------------------------------------------------
+ Arithmetic on the result of a GBR load.
+*/
+#define func(name, type, disp, op, opname)\
+ int \
+ name ## _tp_load_arith_ ##opname (int a) \
+ { \
+ type* tp = (type*)__builtin_thread_pointer (); \
+ return tp[disp] op a; \
+ }
+
+#define funcs(op, opname) \
+ func (test00, int, 0, op, opname) \
+ func (test01, int, 5, op, opname) \
+ func (test02, int, 255, op, opname) \
+ func (test03, short, 0, op, opname) \
+ func (test04, short, 5, op, opname) \
+ func (test05, short, 255, op, opname) \
+ func (test06, char, 0, op, opname) \
+ func (test07, char, 5, op, opname) \
+ func (test08, char, 255, op, opname) \
+ func (test09, unsigned int, 0, op, opname) \
+ func (test10, unsigned int, 5, op, opname) \
+ func (test11, unsigned int, 255, op, opname) \
+ func (test12, unsigned short, 0, op, opname) \
+ func (test13, unsigned short, 5, op, opname) \
+ func (test14, unsigned short, 255, op, opname) \
+ func (test15, unsigned char, 0, op, opname) \
+ func (test16, unsigned char, 5, op, opname) \
+ func (test17, unsigned char, 255, op, opname) \
+
+funcs (+, plus)
+funcs (-, minus)
+funcs (*, mul)
+funcs (&, and)
+funcs (|, or)
+funcs (^, xor)
+
+#undef funcs
+#undef func
+
+/* ---------------------------------------------------------------------------
+ Arithmetic of the result of two GBR loads.
+*/
+#define func(name, type, disp0, disp1, op, opname)\
+ int \
+ name ## _tp_load_load_arith_ ##opname (void) \
+ { \
+ type* tp = (type*)__builtin_thread_pointer (); \
+ return tp[disp0] op tp[disp1]; \
+ }
+
+#define funcs(op, opname) \
+ func (test00, int, 0, 5, op, opname) \
+ func (test02, int, 1, 255, op, opname) \
+ func (test03, short, 0, 5, op, opname) \
+ func (test05, short, 1, 255, op, opname) \
+ func (test06, char, 0, 5, op, opname) \
+ func (test08, char, 1, 255, op, opname) \
+ func (test09, unsigned int, 0, 5, op, opname) \
+ func (test11, unsigned int, 1, 255, op, opname) \
+ func (test12, unsigned short, 0, 5, op, opname) \
+ func (test14, unsigned short, 1, 255, op, opname) \
+ func (test15, unsigned char, 0, 5, op, opname) \
+ func (test17, unsigned char, 1, 255, op, opname) \
+
+funcs (+, plus)
+funcs (-, minus)
+funcs (*, mul)
+funcs (&, and)
+funcs (|, or)
+funcs (^, xor)
+
+#undef funcs
+#undef func
+
+/* ---------------------------------------------------------------------------
+ GBR load GBR store copy.
+*/
+
+#define func(name, type, disp0, disp1)\
+ void \
+ name ## _tp_copy (void) \
+ { \
+ type* tp = (type*)__builtin_thread_pointer (); \
+ tp[disp0] = tp[disp1]; \
+ }
+
+func (test00, int, 0, 5)
+func (test02, int, 1, 255)
+func (test03, short, 0, 5)
+func (test05, short, 1, 255)
+func (test06, char, 0, 5)
+func (test08, char, 1, 255)
+func (test09, unsigned int, 0, 5)
+func (test11, unsigned int, 1, 255)
+func (test12, unsigned short, 0, 5)
+func (test14, unsigned short, 1, 255)
+func (test15, unsigned char, 0, 5)
+func (test17, unsigned char, 1, 255)
+
+#undef func
+
+/* ---------------------------------------------------------------------------
+ GBR load, arithmetic, GBR store
+*/
+
+#define func(name, type, disp, op, opname)\
+ void \
+ name ## _tp_load_arith_store_ ##opname (int a) \
+ { \
+ type* tp = (type*)__builtin_thread_pointer (); \
+ tp[disp] op a; \
+ }
+
+#define funcs(op, opname) \
+ func (test00, int, 0, op, opname) \
+ func (test01, int, 5, op, opname) \
+ func (test02, int, 255, op, opname) \
+ func (test03, short, 0, op, opname) \
+ func (test04, short, 5, op, opname) \
+ func (test05, short, 255, op, opname) \
+ func (test06, char, 0, op, opname) \
+ func (test07, char, 5, op, opname) \
+ func (test08, char, 255, op, opname) \
+ func (test09, unsigned int, 0, op, opname) \
+ func (test10, unsigned int, 5, op, opname) \
+ func (test11, unsigned int, 255, op, opname) \
+ func (test12, unsigned short, 0, op, opname) \
+ func (test13, unsigned short, 5, op, opname) \
+ func (test14, unsigned short, 255, op, opname) \
+ func (test15, unsigned char, 0, op, opname) \
+ func (test16, unsigned char, 5, op, opname) \
+ func (test17, unsigned char, 255, op, opname) \
+
+funcs (+=, plus)
+funcs (-=, minus)
+funcs (*=, mul)
+funcs (&=, and)
+funcs (|=, or)
+funcs (^=, xor)
diff --git a/gcc/testsuite/gcc.target/sh/pr54760-3.c b/gcc/testsuite/gcc.target/sh/pr54760-3.c
new file mode 100644
index 0000000..2b6f186
--- /dev/null
+++ b/gcc/testsuite/gcc.target/sh/pr54760-3.c
@@ -0,0 +1,69 @@
+/* Check that these thread relative memory accesses play along with
+ surrounding code.
+ These should be moved to C torture tests once there are target
+ independent thread_pointer built-in functions available. */
+/* { dg-do compile { target "sh*-*-*" } } */
+/* { dg-options "-O1" } */
+/* { dg-skip-if "" { "sh*-*-*" } { "-m5*"} { "" } } */
+
+int
+test00 (void* p, int x)
+{
+ int* tcb = (int*)__builtin_thread_pointer ();
+ int r = tcb[4];
+
+ __builtin_set_thread_pointer (p);
+
+ tcb = (int*)__builtin_thread_pointer ();
+ return tcb[255] + r;
+}
+
+int
+test01 (void)
+{
+ unsigned short* tcb = (unsigned short*)__builtin_thread_pointer ();
+ return tcb[500];
+}
+
+void
+test02 (int* x, int a, int b)
+{
+ int* tcb = (int*)__builtin_thread_pointer ();
+ tcb[50] = a;
+
+ __builtin_set_thread_pointer (x);
+
+ tcb = (int*)__builtin_thread_pointer ();
+ tcb[40] = b;
+}
+
+int
+test03 (const int* x, int c)
+{
+ volatile int* tcb = (volatile int*)__builtin_thread_pointer ();
+
+ int s = 0;
+ int i;
+ for (i = 0; i < c; ++i)
+ s ^= x[i] + tcb[40];
+
+ return s;
+}
+
+int
+test04 (const int* x, int c, int** xx, int d)
+{
+ int s = 0;
+ int i;
+ for (i = 0; i < c; ++i)
+ {
+ volatile int* tcb = (volatile int*)__builtin_thread_pointer ();
+ tcb[20] = s;
+
+ __builtin_set_thread_pointer (xx[i]);
+
+ tcb = (volatile int*)__builtin_thread_pointer ();
+ s ^= x[i] + tcb[40] + d;
+ }
+ return s;
+}