mirror of
git://gcc.gnu.org/git/gcc.git
synced 2025-02-22 13:09:52 +08:00
h8300-protos.h: Add a prototype for compute_a_shift_cc.
* config/h8300/h8300-protos.h: Add a prototype for compute_a_shift_cc. * config/h8300/h8300.c (shift_one): Update the CC status. (shift_two): Likewise. (output_a_shift_cc): Set cc_inline and cc_special. (compute_a_shift_cc): New. * config/h8300/h8300.md (shift insns): Use compute_a_shift_cc. From-SVN: r65251
This commit is contained in:
parent
7c2aa9d72c
commit
45ca210658
@ -1,3 +1,13 @@
|
||||
2003-04-04 Kazu Hirata <kazu@cs.umass.edu>
|
||||
|
||||
* config/h8300/h8300-protos.h: Add a prototype for
|
||||
compute_a_shift_cc.
|
||||
* config/h8300/h8300.c (shift_one): Update the CC status.
|
||||
(shift_two): Likewise.
|
||||
(output_a_shift_cc): Set cc_inline and cc_special.
|
||||
(compute_a_shift_cc): New.
|
||||
* config/h8300/h8300.md (shift insns): Use compute_a_shift_cc.
|
||||
|
||||
2003-04-04 Richard Henderson <rth@redhat.com>
|
||||
|
||||
* cse.c (fold_rtx): Fix 03-30 change; do check insn non-null.
|
||||
|
@ -31,6 +31,7 @@ extern unsigned int compute_plussi_length PARAMS ((rtx *));
|
||||
extern int compute_plussi_cc PARAMS ((rtx *));
|
||||
extern const char *output_a_shift PARAMS ((rtx *));
|
||||
extern unsigned int compute_a_shift_length PARAMS ((rtx, rtx *));
|
||||
extern int compute_a_shift_cc PARAMS ((rtx, rtx *));
|
||||
extern const char *emit_a_rotate PARAMS ((enum rtx_code, rtx *));
|
||||
extern const char *output_simode_bld PARAMS ((int, rtx[]));
|
||||
extern void print_operand_address PARAMS ((FILE *, rtx));
|
||||
|
@ -2661,42 +2661,42 @@ static const struct shift_insn shift_one[2][3][3] =
|
||||
{
|
||||
/* SHIFT_ASHIFT */
|
||||
{
|
||||
{ "shll\t%X0", CC_NO_CARRY },
|
||||
{ "add.w\t%T0,%T0", CC_OVERFLOW_UNUSABLE | CC_NO_CARRY },
|
||||
{ "add.w\t%f0,%f0\n\taddx\t%y0,%y0\n\taddx\t%z0,%z0", 0 }
|
||||
{ "shll\t%X0", CC_SET_ZNV },
|
||||
{ "add.w\t%T0,%T0", CC_SET_ZN },
|
||||
{ "add.w\t%f0,%f0\n\taddx\t%y0,%y0\n\taddx\t%z0,%z0", CC_CLOBBER }
|
||||
},
|
||||
/* SHIFT_LSHIFTRT */
|
||||
{
|
||||
{ "shlr\t%X0", CC_NO_CARRY },
|
||||
{ "shlr\t%t0\n\trotxr\t%s0", 0 },
|
||||
{ "shlr\t%z0\n\trotxr\t%y0\n\trotxr\t%x0\n\trotxr\t%w0", 0 }
|
||||
{ "shlr\t%X0", CC_SET_ZNV },
|
||||
{ "shlr\t%t0\n\trotxr\t%s0", CC_CLOBBER },
|
||||
{ "shlr\t%z0\n\trotxr\t%y0\n\trotxr\t%x0\n\trotxr\t%w0", CC_CLOBBER }
|
||||
},
|
||||
/* SHIFT_ASHIFTRT */
|
||||
{
|
||||
{ "shar\t%X0", CC_OVERFLOW_UNUSABLE | CC_NO_CARRY },
|
||||
{ "shar\t%t0\n\trotxr\t%s0", 0 },
|
||||
{ "shar\t%z0\n\trotxr\t%y0\n\trotxr\t%x0\n\trotxr\t%w0", 0 }
|
||||
{ "shar\t%X0", CC_SET_ZNV },
|
||||
{ "shar\t%t0\n\trotxr\t%s0", CC_CLOBBER },
|
||||
{ "shar\t%z0\n\trotxr\t%y0\n\trotxr\t%x0\n\trotxr\t%w0", CC_CLOBBER }
|
||||
}
|
||||
},
|
||||
/* H8/300H */
|
||||
{
|
||||
/* SHIFT_ASHIFT */
|
||||
{
|
||||
{ "shll.b\t%X0", CC_NO_CARRY },
|
||||
{ "shll.w\t%T0", CC_NO_CARRY },
|
||||
{ "shll.l\t%S0", CC_NO_CARRY }
|
||||
{ "shll.b\t%X0", CC_SET_ZNV },
|
||||
{ "shll.w\t%T0", CC_SET_ZNV },
|
||||
{ "shll.l\t%S0", CC_SET_ZNV }
|
||||
},
|
||||
/* SHIFT_LSHIFTRT */
|
||||
{
|
||||
{ "shlr.b\t%X0", CC_NO_CARRY },
|
||||
{ "shlr.w\t%T0", CC_NO_CARRY },
|
||||
{ "shlr.l\t%S0", CC_NO_CARRY }
|
||||
{ "shlr.b\t%X0", CC_SET_ZNV },
|
||||
{ "shlr.w\t%T0", CC_SET_ZNV },
|
||||
{ "shlr.l\t%S0", CC_SET_ZNV }
|
||||
},
|
||||
/* SHIFT_ASHIFTRT */
|
||||
{
|
||||
{ "shar.b\t%X0", CC_OVERFLOW_UNUSABLE | CC_NO_CARRY },
|
||||
{ "shar.w\t%T0", CC_OVERFLOW_UNUSABLE | CC_NO_CARRY },
|
||||
{ "shar.l\t%S0", CC_OVERFLOW_UNUSABLE | CC_NO_CARRY }
|
||||
{ "shar.b\t%X0", CC_SET_ZNV },
|
||||
{ "shar.w\t%T0", CC_SET_ZNV },
|
||||
{ "shar.l\t%S0", CC_SET_ZNV }
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -2705,21 +2705,21 @@ static const struct shift_insn shift_two[3][3] =
|
||||
{
|
||||
/* SHIFT_ASHIFT */
|
||||
{
|
||||
{ "shll.b\t#2,%X0", CC_NO_CARRY },
|
||||
{ "shll.w\t#2,%T0", CC_NO_CARRY },
|
||||
{ "shll.l\t#2,%S0", CC_NO_CARRY }
|
||||
{ "shll.b\t#2,%X0", CC_SET_ZNV },
|
||||
{ "shll.w\t#2,%T0", CC_SET_ZNV },
|
||||
{ "shll.l\t#2,%S0", CC_SET_ZNV }
|
||||
},
|
||||
/* SHIFT_LSHIFTRT */
|
||||
{
|
||||
{ "shlr.b\t#2,%X0", CC_NO_CARRY },
|
||||
{ "shlr.w\t#2,%T0", CC_NO_CARRY },
|
||||
{ "shlr.l\t#2,%S0", CC_NO_CARRY }
|
||||
{ "shlr.b\t#2,%X0", CC_SET_ZNV },
|
||||
{ "shlr.w\t#2,%T0", CC_SET_ZNV },
|
||||
{ "shlr.l\t#2,%S0", CC_SET_ZNV }
|
||||
},
|
||||
/* SHIFT_ASHIFTRT */
|
||||
{
|
||||
{ "shar.b\t#2,%X0", CC_OVERFLOW_UNUSABLE | CC_NO_CARRY },
|
||||
{ "shar.w\t#2,%T0", CC_OVERFLOW_UNUSABLE | CC_NO_CARRY },
|
||||
{ "shar.l\t#2,%S0", CC_OVERFLOW_UNUSABLE | CC_NO_CARRY }
|
||||
{ "shar.b\t#2,%X0", CC_SET_ZNV },
|
||||
{ "shar.w\t#2,%T0", CC_SET_ZNV },
|
||||
{ "shar.l\t#2,%S0", CC_SET_ZNV }
|
||||
}
|
||||
};
|
||||
|
||||
@ -2814,8 +2814,11 @@ struct shift_info {
|
||||
or SHIFT_SPECIAL, and REMAINDER is nonzero. */
|
||||
const char *shift2;
|
||||
|
||||
/* Valid CC flags. */
|
||||
int cc_valid_p;
|
||||
/* CC status for SHIFT_INLINE. */
|
||||
int cc_inline;
|
||||
|
||||
/* CC status for SHIFT_SPECIAL. */
|
||||
int cc_special;
|
||||
};
|
||||
|
||||
static void get_shift_alg PARAMS ((enum shift_type,
|
||||
@ -2885,13 +2888,13 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
/* It is up to the caller to know that looping clobbers cc. */
|
||||
info->shift1 = shift_one[cpu_type][shift_type][shift_mode].assembler;
|
||||
info->shift2 = shift_two[shift_type][shift_mode].assembler;
|
||||
info->cc_valid_p = shift_one[cpu_type][shift_type][shift_mode].cc_valid;
|
||||
info->cc_inline = shift_one[cpu_type][shift_type][shift_mode].cc_valid;
|
||||
goto end;
|
||||
|
||||
case SHIFT_ROT_AND:
|
||||
info->shift1 = rotate_one[cpu_type][shift_type][shift_mode];
|
||||
info->shift2 = rotate_two[shift_type][shift_mode];
|
||||
info->cc_valid_p = 0;
|
||||
info->cc_inline = CC_CLOBBER;
|
||||
goto end;
|
||||
|
||||
case SHIFT_SPECIAL:
|
||||
@ -2899,7 +2902,8 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
info->remainder = 0;
|
||||
info->shift1 = shift_one[cpu_type][shift_type][shift_mode].assembler;
|
||||
info->shift2 = shift_two[shift_type][shift_mode].assembler;
|
||||
info->cc_valid_p = 0;
|
||||
info->cc_inline = shift_one[cpu_type][shift_type][shift_mode].cc_valid;
|
||||
info->cc_special = CC_CLOBBER;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2953,10 +2957,12 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
{
|
||||
info->special = "mov.b\t%t0,%s0\n\tsub.b\t%t0,%t0";
|
||||
info->shift1 = "shlr.b\t%s0";
|
||||
info->cc_inline = CC_SET_ZNV;
|
||||
}
|
||||
else
|
||||
{
|
||||
info->special = "mov.b\t%t0,%s0\n\textu.w\t%T0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
}
|
||||
goto end;
|
||||
case SHIFT_ASHIFTRT:
|
||||
@ -2968,6 +2974,7 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
else
|
||||
{
|
||||
info->special = "mov.b\t%t0,%s0\n\texts.w\t%T0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
}
|
||||
goto end;
|
||||
}
|
||||
@ -2988,7 +2995,10 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
if (TARGET_H8300)
|
||||
info->special = "mov.b\t%t0,%s0\n\tshll.b\t%s0\n\tsubx.b\t%t0,%t0\n\tshll.b\t%s0\n\tmov.b\t%t0,%s0\n\tbst.b\t#0,%s0";
|
||||
else if (TARGET_H8300H)
|
||||
info->special = "shll.b\t%t0\n\tsubx.b\t%s0,%s0\n\tshll.b\t%t0\n\trotxl.b\t%s0\n\texts.w\t%T0";
|
||||
{
|
||||
info->special = "shll.b\t%t0\n\tsubx.b\t%s0,%s0\n\tshll.b\t%t0\n\trotxl.b\t%s0\n\texts.w\t%T0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
}
|
||||
else /* TARGET_H8300S */
|
||||
abort ();
|
||||
goto end;
|
||||
@ -3065,9 +3075,11 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
{
|
||||
case SHIFT_ASHIFT:
|
||||
info->special = "shlr.w\t%e0\n\tmov.w\t%f0,%e0\n\txor.w\t%f0,%f0\n\trotxr.l\t%S0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
goto end;
|
||||
case SHIFT_LSHIFTRT:
|
||||
info->special = "shll.w\t%f0\n\tmov.w\t%e0,%f0\n\txor.w\t%e0,%e0\n\trotxl.l\t%S0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
goto end;
|
||||
case SHIFT_ASHIFTRT:
|
||||
abort ();
|
||||
@ -3095,6 +3107,7 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
else
|
||||
{
|
||||
info->special = "mov.w\t%e0,%f0\n\textu.l\t%S0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
}
|
||||
goto end;
|
||||
case SHIFT_ASHIFTRT:
|
||||
@ -3106,6 +3119,7 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
else
|
||||
{
|
||||
info->special = "mov.w\t%e0,%f0\n\texts.l\t%S0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
}
|
||||
goto end;
|
||||
}
|
||||
@ -3119,14 +3133,17 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
case SHIFT_ASHIFT:
|
||||
info->special = "mov.b\t%w0,%z0\n\tsub.b\t%y0,%y0\n\tsub.w\t%f0,%f0";
|
||||
info->shift1 = "shll.b\t%z0";
|
||||
info->cc_inline = CC_SET_ZNV;
|
||||
goto end;
|
||||
case SHIFT_LSHIFTRT:
|
||||
info->special = "mov.b\t%z0,%w0\n\tsub.b\t%x0,%x0\n\tsub.w\t%e0,%e0";
|
||||
info->shift1 = "shlr.b\t%w0";
|
||||
info->cc_inline = CC_SET_ZNV;
|
||||
goto end;
|
||||
case SHIFT_ASHIFTRT:
|
||||
info->special = "mov.b\t%z0,%w0\n\tbld\t#7,%w0\n\tsubx\t%x0,%x0\n\tsubx\t%x0,%x0\n\tsubx\t%x0,%x0";
|
||||
info->shift1 = "shar.b\t%w0";
|
||||
info->cc_inline = CC_SET_ZNV;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
@ -3142,9 +3159,11 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
goto end;
|
||||
case SHIFT_LSHIFTRT:
|
||||
info->special = "mov.w\t%e0,%f0\n\tmov.b\t%t0,%s0\n\textu.w\t%f0\n\textu.l\t%S0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
goto end;
|
||||
case SHIFT_ASHIFTRT:
|
||||
info->special = "mov.w\t%e0,%f0\n\tmov.b\t%t0,%s0\n\texts.w\t%f0\n\texts.l\t%S0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
@ -3160,7 +3179,10 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
goto end;
|
||||
case SHIFT_LSHIFTRT:
|
||||
if (TARGET_H8300H)
|
||||
info->special = "sub.w\t%f0,%f0\n\trotl.l\t%S0\n\trotl.l\t%S0\n\trotl.l\t%S0\n\trotl.l\t%S0\n\textu.l\t%S0";
|
||||
{
|
||||
info->special = "sub.w\t%f0,%f0\n\trotl.l\t%S0\n\trotl.l\t%S0\n\trotl.l\t%S0\n\trotl.l\t%S0\n\textu.l\t%S0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
}
|
||||
else
|
||||
info->special = "sub.w\t%f0,%f0\n\trotl.l\t#2,%S0\n\trotl.l\t#2,%S0\n\textu.l\t%S0";
|
||||
goto end;
|
||||
@ -3180,9 +3202,15 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
goto end;
|
||||
case SHIFT_LSHIFTRT:
|
||||
if (TARGET_H8300H)
|
||||
info->special = "sub.w\t%f0,%f0\n\trotl.l\t%S0\n\trotl.l\t%S0\n\trotl.l\t%S0\n\textu.l\t%S0";
|
||||
{
|
||||
info->special = "sub.w\t%f0,%f0\n\trotl.l\t%S0\n\trotl.l\t%S0\n\trotl.l\t%S0\n\textu.l\t%S0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
}
|
||||
else
|
||||
info->special = "sub.w\t%f0,%f0\n\trotl.l\t#2,%S0\n\trotl.l\t%S0\n\textu.l\t%S0";
|
||||
{
|
||||
info->special = "sub.w\t%f0,%f0\n\trotl.l\t#2,%S0\n\trotl.l\t%S0\n\textu.l\t%S0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
}
|
||||
goto end;
|
||||
case SHIFT_ASHIFTRT:
|
||||
abort ();
|
||||
@ -3231,12 +3259,15 @@ get_shift_alg (shift_type, shift_mode, count, info)
|
||||
{
|
||||
case SHIFT_ASHIFT:
|
||||
info->special = "shlr.l\t%S0\n\txor.l\t%S0,%S0\n\trotxr.l\t%S0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
goto end;
|
||||
case SHIFT_LSHIFTRT:
|
||||
info->special = "shll.l\t%S0\n\txor.l\t%S0,%S0\n\trotxl.l\t%S0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
goto end;
|
||||
case SHIFT_ASHIFTRT:
|
||||
info->special = "shll\t%e0\n\tsubx\t%w0,%w0\n\texts.w\t%T0\n\texts.l\t%S0";
|
||||
info->cc_special = CC_SET_ZNV;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
@ -3390,13 +3421,6 @@ output_a_shift (operands)
|
||||
/* Now emit one bit shifts for any residual. */
|
||||
for (; n > 0; n--)
|
||||
output_asm_insn (info.shift1, operands);
|
||||
|
||||
/* Keep track of CC. */
|
||||
if (info.cc_valid_p)
|
||||
{
|
||||
cc_status.value1 = operands[0];
|
||||
cc_status.flags |= info.cc_valid_p;
|
||||
}
|
||||
return "";
|
||||
|
||||
case SHIFT_ROT_AND:
|
||||
@ -3431,8 +3455,6 @@ output_a_shift (operands)
|
||||
else
|
||||
abort ();
|
||||
|
||||
cc_status.value1 = operands[0];
|
||||
cc_status.flags |= CC_NO_CARRY;
|
||||
output_asm_insn (insn_buf, operands);
|
||||
return "";
|
||||
}
|
||||
@ -3630,6 +3652,100 @@ compute_a_shift_length (insn, operands)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
compute_a_shift_cc (insn, operands)
|
||||
rtx insn ATTRIBUTE_UNUSED;
|
||||
rtx *operands;
|
||||
{
|
||||
rtx shift = operands[3];
|
||||
enum machine_mode mode = GET_MODE (shift);
|
||||
enum rtx_code code = GET_CODE (shift);
|
||||
enum shift_type shift_type;
|
||||
enum shift_mode shift_mode;
|
||||
struct shift_info info;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case QImode:
|
||||
shift_mode = QIshift;
|
||||
break;
|
||||
case HImode:
|
||||
shift_mode = HIshift;
|
||||
break;
|
||||
case SImode:
|
||||
shift_mode = SIshift;
|
||||
break;
|
||||
default:
|
||||
abort ();
|
||||
}
|
||||
|
||||
switch (code)
|
||||
{
|
||||
case ASHIFTRT:
|
||||
shift_type = SHIFT_ASHIFTRT;
|
||||
break;
|
||||
case LSHIFTRT:
|
||||
shift_type = SHIFT_LSHIFTRT;
|
||||
break;
|
||||
case ASHIFT:
|
||||
shift_type = SHIFT_ASHIFT;
|
||||
break;
|
||||
default:
|
||||
abort ();
|
||||
}
|
||||
|
||||
if (GET_CODE (operands[2]) != CONST_INT)
|
||||
{
|
||||
/* This case must be taken care of by one of the two splitters
|
||||
that convert a variable shift into a loop. */
|
||||
abort ();
|
||||
}
|
||||
else
|
||||
{
|
||||
int n = INTVAL (operands[2]);
|
||||
|
||||
/* If the count is negative, make it 0. */
|
||||
if (n < 0)
|
||||
n = 0;
|
||||
/* If the count is too big, truncate it.
|
||||
ANSI says shifts of GET_MODE_BITSIZE are undefined - we choose to
|
||||
do the intuitive thing. */
|
||||
else if ((unsigned int) n > GET_MODE_BITSIZE (mode))
|
||||
n = GET_MODE_BITSIZE (mode);
|
||||
|
||||
get_shift_alg (shift_type, shift_mode, n, &info);
|
||||
|
||||
switch (info.alg)
|
||||
{
|
||||
case SHIFT_SPECIAL:
|
||||
if (info.remainder == 0)
|
||||
return info.cc_special;
|
||||
|
||||
/* Fall through. */
|
||||
|
||||
case SHIFT_INLINE:
|
||||
return info.cc_inline;
|
||||
|
||||
case SHIFT_ROT_AND:
|
||||
/* This case always ends with an and instruction. */
|
||||
return CC_SET_ZNV;
|
||||
|
||||
case SHIFT_LOOP:
|
||||
/* A loop to shift by a "large" constant value.
|
||||
If we have shift-by-2 insns, use them. */
|
||||
if (info.shift2 != NULL)
|
||||
{
|
||||
if (n % 2)
|
||||
return info.cc_inline;
|
||||
}
|
||||
return CC_CLOBBER;
|
||||
|
||||
default:
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* A rotation by a non-constant will cause a loop to be generated, in
|
||||
which a rotation by one bit is used. A rotation by a constant,
|
||||
|
@ -2272,7 +2272,8 @@
|
||||
"* return output_a_shift (operands);"
|
||||
[(set (attr "length")
|
||||
(symbol_ref "compute_a_shift_length (insn, operands)"))
|
||||
(set_attr "cc" "clobber")])
|
||||
(set (attr "cc")
|
||||
(symbol_ref "compute_a_shift_cc (insn, operands)"))])
|
||||
|
||||
;; HI BIT SHIFTS
|
||||
|
||||
@ -2307,7 +2308,8 @@
|
||||
"* return output_a_shift (operands);"
|
||||
[(set (attr "length")
|
||||
(symbol_ref "compute_a_shift_length (insn, operands)"))
|
||||
(set_attr "cc" "clobber")])
|
||||
(set (attr "cc")
|
||||
(symbol_ref "compute_a_shift_cc (insn, operands)"))])
|
||||
|
||||
;; SI BIT SHIFTS
|
||||
|
||||
@ -2342,7 +2344,8 @@
|
||||
"* return output_a_shift (operands);"
|
||||
[(set (attr "length")
|
||||
(symbol_ref "compute_a_shift_length (insn, operands)"))
|
||||
(set_attr "cc" "clobber")])
|
||||
(set (attr "cc")
|
||||
(symbol_ref "compute_a_shift_cc (insn, operands)"))])
|
||||
|
||||
;; Split a variable shift into a loop. If the register containing
|
||||
;; the shift count dies, then we just use that register.
|
||||
|
Loading…
Reference in New Issue
Block a user