Commit fce1e5fb by Oleg Endo

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
parent 7f7b06c1
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
......
......@@ -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;
......
......@@ -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);
......
......@@ -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"
......@@ -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
......
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
......
/* 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)
/* 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;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment