Commit 4d72536e by Richard Sandiford Committed by Richard Sandiford

abi64.h (SETUP_INCOMING_VARARGS): Undefine.

	* config/mips/abi64.h (SETUP_INCOMING_VARARGS): Undefine.
	* config/mips/mips-protos.h (mips_setup_incoming_varargs): Declare.
	(function_arg): Constify CUMULATIVE_ARGS.
	(function_arg_partial_nregs, function_arg_pass_by_reference): Likewise.
	* config/mips/mips.h (UNITS_PER_FPVALUE): Zero when TARGET_SOFT_FLOAT.
	(UNITS_PER_DOUBLE): New macro.
	(SETUP_INCOMING_VARARGS): Define.  Use mips_setup_incoming_varargs.
	(CUMULATIVE_ARGS): Reformat.  Remove num_adjusts workaround and
	last_arg_fp field.  Replace arg_words and fp_arg_words with gp_regs,
	fp_regs and stack_words.
	(EABI_FLOAT_VARARGS_P): New macro.
	* config/mips/mips.c (struct mips_arg_info): New.
	(mips_arg_info): New function.
	(function_arg_advance): Use it.  Add adjustment instructions here
	rather than in function_arg.
	(function_arg): Constify CUMULATIVE_ARGS.  Use mips_arg_info.  Check
	for VOIDmode at the beginning of the function.
	(function_partial_nregs): Constify CUMULATIVE_ARGS.  Use mips_arg_info.
	(function_arg_pass_by_reference): Likewise.
	(mips_setup_incoming_varags): New, largely based on old abi64.h code.
	(mips_build_va_list): Test EABI_FLOAT_VARARGS_P.
	(mips_va_start): Likewise.  Use the new stack_words field of
	CUMULATIVE_ARGS to set up overflow area.  Reformat.
	(mips_va_arg): Test EABI_FLOAT_VARARGS_P.  Unify EABI handling of
	doubles and other types, aligning the overflow pointer for non-doubles
	too.  Remove some code duplication.  Replace hard-coded constants.

From-SVN: r51167
parent e6f884cd
2002-03-22 Richard Sandiford <rsandifo@redhat.com> 2002-03-22 Richard Sandiford <rsandifo@redhat.com>
* config/mips/abi64.h (SETUP_INCOMING_VARARGS): Undefine.
* config/mips/mips-protos.h (mips_setup_incoming_varargs): Declare.
(function_arg): Constify CUMULATIVE_ARGS.
(function_arg_partial_nregs, function_arg_pass_by_reference): Likewise.
* config/mips/mips.h (UNITS_PER_FPVALUE): Zero when TARGET_SOFT_FLOAT.
(UNITS_PER_DOUBLE): New macro.
(SETUP_INCOMING_VARARGS): Define. Use mips_setup_incoming_varargs.
(CUMULATIVE_ARGS): Reformat. Remove num_adjusts workaround and
last_arg_fp field. Replace arg_words and fp_arg_words with gp_regs,
fp_regs and stack_words.
(EABI_FLOAT_VARARGS_P): New macro.
* config/mips/mips.c (struct mips_arg_info): New.
(mips_arg_info): New function.
(function_arg_advance): Use it. Add adjustment instructions here
rather than in function_arg.
(function_arg): Constify CUMULATIVE_ARGS. Use mips_arg_info. Check
for VOIDmode at the beginning of the function.
(function_partial_nregs): Constify CUMULATIVE_ARGS. Use mips_arg_info.
(function_arg_pass_by_reference): Likewise.
(mips_setup_incoming_varags): New, largely based on old abi64.h code.
(mips_build_va_list): Test EABI_FLOAT_VARARGS_P.
(mips_va_start): Likewise. Use the new stack_words field of
CUMULATIVE_ARGS to set up overflow area. Reformat.
(mips_va_arg): Test EABI_FLOAT_VARARGS_P. Unify EABI handling of
doubles and other types, aligning the overflow pointer for non-doubles
too. Remove some code duplication. Replace hard-coded constants.
2002-03-22 Richard Sandiford <rsandifo@redhat.com>
* config/mips/mips.h (FUNCTION_ARG_REGNO_P): Simplify. * config/mips/mips.h (FUNCTION_ARG_REGNO_P): Simplify.
(CLASS_UNITS): Undefine. (CLASS_UNITS): Undefine.
(CLASS_MAX_NREGS): Use FP_INC. (CLASS_MAX_NREGS): Use FP_INC.
......
...@@ -102,96 +102,6 @@ Boston, MA 02111-1307, USA. */ ...@@ -102,96 +102,6 @@ Boston, MA 02111-1307, USA. */
#undef FUNCTION_VALUE #undef FUNCTION_VALUE
#define FUNCTION_VALUE(VALTYPE, FUNC) mips_function_value (VALTYPE, FUNC) #define FUNCTION_VALUE(VALTYPE, FUNC) mips_function_value (VALTYPE, FUNC)
/* For varargs, we must save the current argument, because it is the fake
argument va_alist, and will need to be converted to the real argument.
For stdarg, we do not need to save the current argument, because it
is a real argument. */
#define SETUP_INCOMING_VARARGS(CUM,MODE,TYPE,PRETEND_SIZE,NO_RTL) \
{ unsigned int mips_off \
= (! current_function_varargs) && (! (CUM).last_arg_fp); \
unsigned int mips_fp_off \
= (! current_function_varargs) && ((CUM).last_arg_fp); \
if (((mips_abi != ABI_32 && mips_abi != ABI_O64) \
&& (CUM).arg_words < MAX_ARGS_IN_REGISTERS - mips_off) \
|| (mips_abi == ABI_EABI \
&& ! TARGET_SOFT_FLOAT \
&& (CUM).fp_arg_words < MAX_ARGS_IN_REGISTERS - mips_fp_off)) \
{ \
int mips_save_gp_regs \
= MAX_ARGS_IN_REGISTERS - (CUM).arg_words - mips_off; \
int mips_save_fp_regs \
= (mips_abi != ABI_EABI ? 0 \
: MAX_ARGS_IN_REGISTERS - (CUM).fp_arg_words - mips_fp_off); \
\
if (mips_save_gp_regs < 0) \
mips_save_gp_regs = 0; \
if (mips_save_fp_regs < 0) \
mips_save_fp_regs = 0; \
PRETEND_SIZE = ((mips_save_gp_regs * UNITS_PER_WORD) \
+ (mips_save_fp_regs * UNITS_PER_FPREG)); \
\
if (! (NO_RTL)) \
{ \
if ((CUM).arg_words < MAX_ARGS_IN_REGISTERS - mips_off) \
{ \
rtx ptr, mem; \
if (mips_abi != ABI_EABI) \
ptr = virtual_incoming_args_rtx; \
else \
ptr = plus_constant (virtual_incoming_args_rtx, \
- (mips_save_gp_regs \
* UNITS_PER_WORD)); \
mem = gen_rtx_MEM (BLKmode, ptr); \
/* va_arg is an array access in this case, which causes \
it to get MEM_IN_STRUCT_P set. We must set it here \
so that the insn scheduler won't assume that these \
stores can't possibly overlap with the va_arg loads. */ \
if (mips_abi != ABI_EABI && BYTES_BIG_ENDIAN) \
MEM_SET_IN_STRUCT_P (mem, 1); \
move_block_from_reg \
((CUM).arg_words + GP_ARG_FIRST + mips_off, \
mem, \
mips_save_gp_regs, \
mips_save_gp_regs * UNITS_PER_WORD); \
} \
if (mips_abi == ABI_EABI \
&& ! TARGET_SOFT_FLOAT \
&& (CUM).fp_arg_words < MAX_ARGS_IN_REGISTERS - mips_fp_off) \
{ \
enum machine_mode mode = TARGET_SINGLE_FLOAT ? SFmode : DFmode; \
int size = GET_MODE_SIZE (mode); \
int off; \
int i; \
/* We can't use move_block_from_reg, because it will use \
the wrong mode. */ \
off = - (mips_save_gp_regs * UNITS_PER_WORD); \
if (! TARGET_SINGLE_FLOAT) \
off &= ~ 7; \
if (! TARGET_FLOAT64 || TARGET_SINGLE_FLOAT) \
off -= (mips_save_fp_regs / 2) * size; \
else \
off -= mips_save_fp_regs * size; \
for (i = 0; i < mips_save_fp_regs; i++) \
{ \
rtx tem = \
gen_rtx_MEM (mode, \
plus_constant (virtual_incoming_args_rtx, \
off)); \
emit_move_insn (tem, \
gen_rtx_REG (mode, \
((CUM).fp_arg_words \
+ FP_ARG_FIRST \
+ i \
+ mips_fp_off))); \
off += size; \
if (! TARGET_FLOAT64 || TARGET_SINGLE_FLOAT) \
++i; \
} \
} \
} \
} \
}
#define STRICT_ARGUMENT_NAMING (mips_abi != ABI_32 && mips_abi != ABI_O64) #define STRICT_ARGUMENT_NAMING (mips_abi != ABI_32 && mips_abi != ABI_O64)
/* A C expression that indicates when an argument must be passed by /* A C expression that indicates when an argument must be passed by
......
...@@ -56,16 +56,21 @@ extern unsigned int mips_hard_regno_nregs PARAMS ((int, ...@@ -56,16 +56,21 @@ extern unsigned int mips_hard_regno_nregs PARAMS ((int,
enum machine_mode)); enum machine_mode));
extern int mips_return_in_memory PARAMS ((tree)); extern int mips_return_in_memory PARAMS ((tree));
extern struct rtx_def *function_arg PARAMS ((CUMULATIVE_ARGS *, extern struct rtx_def *function_arg PARAMS ((const CUMULATIVE_ARGS *,
enum machine_mode, tree, int)); enum machine_mode, tree, int));
extern void function_arg_advance PARAMS ((CUMULATIVE_ARGS *, extern void function_arg_advance PARAMS ((CUMULATIVE_ARGS *,
enum machine_mode, enum machine_mode,
tree, int)); tree, int));
extern int function_arg_partial_nregs PARAMS ((CUMULATIVE_ARGS *, extern int function_arg_partial_nregs
PARAMS ((const CUMULATIVE_ARGS *,
enum machine_mode,
tree, int));
extern int mips_setup_incoming_varargs
PARAMS ((const CUMULATIVE_ARGS *,
enum machine_mode, enum machine_mode,
tree, int)); tree, int));
extern int function_arg_pass_by_reference extern int function_arg_pass_by_reference
PARAMS ((CUMULATIVE_ARGS *, PARAMS ((const CUMULATIVE_ARGS *,
enum machine_mode, tree, int)); enum machine_mode, tree, int));
extern int mips16_constant_after_function_p PARAMS ((tree)); extern int mips16_constant_after_function_p PARAMS ((tree));
extern int mips_output_external PARAMS ((FILE *, tree, extern int mips_output_external PARAMS ((FILE *, tree,
......
...@@ -86,6 +86,7 @@ enum internal_test { ...@@ -86,6 +86,7 @@ enum internal_test {
struct constant; struct constant;
struct mips_arg_info;
static enum internal_test map_test_to_internal_test PARAMS ((enum rtx_code)); static enum internal_test map_test_to_internal_test PARAMS ((enum rtx_code));
static int mips16_simple_memory_operand PARAMS ((rtx, rtx, static int mips16_simple_memory_operand PARAMS ((rtx, rtx,
enum machine_mode)); enum machine_mode));
...@@ -95,6 +96,10 @@ static void block_move_loop PARAMS ((rtx, rtx, ...@@ -95,6 +96,10 @@ static void block_move_loop PARAMS ((rtx, rtx,
int, int,
rtx, rtx)); rtx, rtx));
static void block_move_call PARAMS ((rtx, rtx, rtx)); static void block_move_call PARAMS ((rtx, rtx, rtx));
static void mips_arg_info PARAMS ((const CUMULATIVE_ARGS *,
enum machine_mode,
tree, int,
struct mips_arg_info *));
static rtx mips_add_large_offset_to_sp PARAMS ((HOST_WIDE_INT, static rtx mips_add_large_offset_to_sp PARAMS ((HOST_WIDE_INT,
FILE *)); FILE *));
static void mips_annotate_frame_insn PARAMS ((rtx, rtx)); static void mips_annotate_frame_insn PARAMS ((rtx, rtx));
...@@ -150,6 +155,34 @@ struct machine_function { ...@@ -150,6 +155,34 @@ struct machine_function {
rtx mips16_gp_pseudo_rtx; rtx mips16_gp_pseudo_rtx;
}; };
/* Information about a single argument. */
struct mips_arg_info
{
/* True if the argument is a record or union type. */
bool struct_p;
/* True if the argument is passed in a floating-point register, or
would have been if we hadn't run out of registers. */
bool fpr_p;
/* The argument's size, in bytes. */
unsigned int num_bytes;
/* The number of words passed in registers, rounded up. */
unsigned int reg_words;
/* The offset of the first register from GP_ARG_FIRST or FP_ARG_FIRST,
or MAX_ARGS_IN_REGISTERS if the argument is passed entirely
on the stack. */
unsigned int reg_offset;
/* The number of words that must be passed on the stack, rounded up. */
unsigned int stack_words;
/* The offset from the start of the stack overflow area of the argument's
first stack word. Only meaningful when STACK_WORDS is non-zero. */
unsigned int stack_offset;
};
/* Global variables for machine-dependent things. */ /* Global variables for machine-dependent things. */
...@@ -3918,236 +3951,212 @@ init_cumulative_args (cum, fntype, libname) ...@@ -3918,236 +3951,212 @@ init_cumulative_args (cum, fntype, libname)
} }
} }
/* Advance the argument to the next argument position. */ static void
mips_arg_info (cum, mode, type, named, info)
void const CUMULATIVE_ARGS *cum;
function_arg_advance (cum, mode, type, named) enum machine_mode mode;
CUMULATIVE_ARGS *cum; /* current arg information */ tree type;
enum machine_mode mode; /* current arg mode */ int named;
tree type; /* type of the argument or 0 if lib support */ struct mips_arg_info *info;
int named; /* whether or not the argument was named */
{ {
if (TARGET_DEBUG_E_MODE) bool even_reg_p;
{ unsigned int num_words, max_regs;
fprintf (stderr,
"function_adv({gp reg found = %d, arg # = %2d, words = %2d}, %4s, ",
cum->gp_reg_found, cum->arg_number, cum->arg_words,
GET_MODE_NAME (mode));
fprintf (stderr, HOST_PTR_PRINTF, (const PTR) type);
fprintf (stderr, ", %d )\n\n", named);
}
cum->arg_number++; info->struct_p = (type != 0
switch (mode) && (TREE_CODE (type) == RECORD_TYPE
{ || TREE_CODE (type) == UNION_TYPE
case VOIDmode: || TREE_CODE (type) == QUAL_UNION_TYPE));
break;
default: /* Decide whether this argument should go in a floating-point register,
if (GET_MODE_CLASS (mode) != MODE_COMPLEX_INT assuming one is free. Later code checks for availablity. */
&& GET_MODE_CLASS (mode) != MODE_COMPLEX_FLOAT)
abort ();
cum->gp_reg_found = 1; info->fpr_p = false;
cum->arg_words += ((GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) if (GET_MODE_CLASS (mode) == MODE_FLOAT
/ UNITS_PER_WORD); && GET_MODE_SIZE (mode) <= UNITS_PER_FPVALUE)
{
switch (mips_abi)
{
case ABI_32:
case ABI_O64:
info->fpr_p = (!cum->gp_reg_found && cum->arg_number < 2);
break; break;
case BLKmode: case ABI_EABI:
cum->gp_reg_found = 1; info->fpr_p = true;
cum->arg_words += ((int_size_in_bytes (type) + UNITS_PER_WORD - 1)
/ UNITS_PER_WORD);
break; break;
case SFmode: case ABI_MEABI:
if (mips_abi == ABI_EABI && ! TARGET_SOFT_FLOAT) /* The MIPS eabi says only structures containing doubles get
cum->fp_arg_words++; passed in a fp register, so force a structure containing
else a float to be passed in the integer registers. */
cum->arg_words++; info->fpr_p = (named && !(mode == SFmode && info->struct_p));
if (! cum->gp_reg_found && cum->arg_number <= 2)
cum->fp_code += 1 << ((cum->arg_number - 1) * 2);
break; break;
case DFmode: default:
if (mips_abi == ABI_EABI && ! TARGET_SOFT_FLOAT && ! TARGET_SINGLE_FLOAT) info->fpr_p = named;
cum->fp_arg_words += (TARGET_64BIT ? 1 : 2);
else
cum->arg_words += (TARGET_64BIT ? 1 : 2);
if (! cum->gp_reg_found && ! TARGET_SINGLE_FLOAT && cum->arg_number <= 2)
cum->fp_code += 2 << ((cum->arg_number - 1) * 2);
break; break;
}
}
case DImode: /* Now decide whether the argument must go in an even-numbered register. */
case TImode:
cum->gp_reg_found = 1;
cum->arg_words += (TARGET_64BIT ? 1 : 2);
break;
case QImode: even_reg_p = false;
case HImode: if (info->fpr_p)
case SImode: {
cum->gp_reg_found = 1; /* Under the O64 ABI, the second float argument goes in $f13 if it
cum->arg_words++; is a double, but $f14 if it is a single. Otherwise, on a
break; 32-bit double-float machine, each FP argument must start in a
new register pair. */
even_reg_p = ((mips_abi == ABI_O64 && mode == SFmode) || FP_INC > 1);
}
else if (!TARGET_64BIT)
{
if (GET_MODE_CLASS (mode) == MODE_INT
|| GET_MODE_CLASS (mode) == MODE_FLOAT)
even_reg_p = (GET_MODE_SIZE (mode) > UNITS_PER_WORD);
else if (type != NULL_TREE && TYPE_ALIGN (type) > BITS_PER_WORD)
even_reg_p = true;
} }
/* Set REG_OFFSET to the register count we're interested in.
The EABI allocates the floating-point registers separately,
but the other ABIs allocate them like integer registers. */
info->reg_offset = (mips_abi == ABI_EABI && info->fpr_p
? cum->fp_regs
: cum->gp_regs);
if (even_reg_p)
info->reg_offset += info->reg_offset & 1;
/* The alignment applied to registers is also applied to stack arguments. */
info->stack_offset = cum->stack_words;
if (even_reg_p)
info->stack_offset += info->stack_offset & 1;
if (mode == BLKmode)
info->num_bytes = int_size_in_bytes (type);
else
info->num_bytes = GET_MODE_SIZE (mode);
num_words = (info->num_bytes + UNITS_PER_WORD - 1) / UNITS_PER_WORD;
max_regs = MAX_ARGS_IN_REGISTERS - info->reg_offset;
/* Partition the argument between registers and stack. */
info->reg_words = MIN (num_words, max_regs);
info->stack_words = num_words - info->reg_words;
} }
/* Return an RTL expression containing the register for the given mode,
or 0 if the argument is to be passed on the stack. */
struct rtx_def * /* Advance the argument to the next argument position. */
function_arg (cum, mode, type, named)
void
function_arg_advance (cum, mode, type, named)
CUMULATIVE_ARGS *cum; /* current arg information */ CUMULATIVE_ARGS *cum; /* current arg information */
enum machine_mode mode; /* current arg mode */ enum machine_mode mode; /* current arg mode */
tree type; /* type of the argument or 0 if lib support */ tree type; /* type of the argument or 0 if lib support */
int named; /* != 0 for normal args, == 0 for ... args */ int named; /* whether or not the argument was named */
{ {
rtx ret; struct mips_arg_info info;
int regbase = -1;
int bias = 0;
unsigned int *arg_words = &cum->arg_words;
int struct_p = (type != 0
&& (TREE_CODE (type) == RECORD_TYPE
|| TREE_CODE (type) == UNION_TYPE
|| TREE_CODE (type) == QUAL_UNION_TYPE));
if (TARGET_DEBUG_E_MODE) mips_arg_info (cum, mode, type, named, &info);
{
fprintf (stderr,
"function_arg( {gp reg found = %d, arg # = %2d, words = %2d}, %4s, ",
cum->gp_reg_found, cum->arg_number, cum->arg_words,
GET_MODE_NAME (mode));
fprintf (stderr, HOST_PTR_PRINTF, (const PTR) type);
fprintf (stderr, ", %d ) = ", named);
}
/* The following is a hack in order to pass 1 byte structures
the same way that the MIPS compiler does (namely by passing
the structure in the high byte or half word of the register).
This also makes varargs work. If we have such a structure,
we save the adjustment RTL, and the call define expands will
emit them. For the VOIDmode argument (argument after the
last real argument), pass back a parallel vector holding each
of the adjustments. */
cum->last_arg_fp = 0; /* ??? This scheme requires everything smaller than the word size to
switch (mode) shifted to the left, but when TARGET_64BIT and ! TARGET_INT64,
{ that would mean every int needs to be shifted left, which is very
case SFmode: inefficient. Let's not carry this compatibility to the 64 bit
if (mips_abi == ABI_32 || mips_abi == ABI_O64) calling convention for now. */
{
if (cum->gp_reg_found || cum->arg_number >= 2 || TARGET_SOFT_FLOAT)
regbase = GP_ARG_FIRST;
else
{
regbase = FP_ARG_FIRST;
/* If the first arg was a float in a floating point register, if (info.struct_p
then set bias to align this float arg properly. */ && info.reg_words == 1
if (cum->arg_words == 1) && info.num_bytes < UNITS_PER_WORD
bias = 1; && !TARGET_64BIT
} && mips_abi != ABI_EABI
} && mips_abi != ABI_MEABI)
else if (mips_abi == ABI_EABI && ! TARGET_SOFT_FLOAT)
{ {
if (! TARGET_64BIT) rtx amount = GEN_INT (BITS_PER_WORD - info.num_bytes * BITS_PER_UNIT);
cum->fp_arg_words += cum->fp_arg_words & 1; rtx reg = gen_rtx_REG (word_mode, GP_ARG_FIRST + info.reg_offset);
cum->last_arg_fp = 1;
arg_words = &cum->fp_arg_words;
regbase = FP_ARG_FIRST;
}
/* The MIPS eabi says only structures containing doubles get passed in a
fp register, so force a structure containing a float to be passed in
the integer registers. */
else if (mips_abi == ABI_MEABI && struct_p)
regbase = GP_ARG_FIRST;
else
regbase = (TARGET_SOFT_FLOAT || ! named ? GP_ARG_FIRST : FP_ARG_FIRST);
break;
case DFmode: if (TARGET_64BIT)
if (! TARGET_64BIT) cum->adjust[cum->num_adjusts++] = gen_ashldi3 (reg, reg, amount);
{
if (mips_abi == ABI_EABI
&& ! TARGET_SOFT_FLOAT && ! TARGET_SINGLE_FLOAT)
cum->fp_arg_words += cum->fp_arg_words & 1;
else else
cum->arg_words += cum->arg_words & 1; cum->adjust[cum->num_adjusts++] = gen_ashlsi3 (reg, reg, amount);
} }
if (mips_abi == ABI_32 || mips_abi == ABI_O64) if (!info.fpr_p)
regbase = ((cum->gp_reg_found cum->gp_reg_found = true;
|| TARGET_SOFT_FLOAT || TARGET_SINGLE_FLOAT
|| cum->arg_number >= 2)
? GP_ARG_FIRST : FP_ARG_FIRST);
else if (mips_abi == ABI_EABI
&& ! TARGET_SOFT_FLOAT && ! TARGET_SINGLE_FLOAT)
{
cum->last_arg_fp = 1;
arg_words = &cum->fp_arg_words;
regbase = FP_ARG_FIRST;
}
else
regbase = (TARGET_SOFT_FLOAT || TARGET_SINGLE_FLOAT || ! named
? GP_ARG_FIRST : FP_ARG_FIRST);
break;
default: /* See the comment above the cumulative args structure in mips.h
if (GET_MODE_CLASS (mode) != MODE_COMPLEX_INT for an explanation of what this code does. It assumes the O32
&& GET_MODE_CLASS (mode) != MODE_COMPLEX_FLOAT) ABI, which passes at most 2 arguments in float registers. */
abort (); if (cum->arg_number < 2 && info.fpr_p)
cum->fp_code += (mode == SFmode ? 1 : 2) << ((cum->arg_number - 1) * 2);
/* Drops through. */ if (mips_abi != ABI_EABI || !info.fpr_p)
case BLKmode: cum->gp_regs = info.reg_offset + info.reg_words;
if (type != NULL_TREE && TYPE_ALIGN (type) > (unsigned) BITS_PER_WORD else if (info.reg_words > 0)
&& ! TARGET_64BIT && mips_abi != ABI_EABI) cum->fp_regs += FP_INC;
cum->arg_words += (cum->arg_words & 1);
regbase = GP_ARG_FIRST;
break;
case VOIDmode: if (info.stack_words > 0)
case QImode: cum->stack_words = info.stack_offset + info.stack_words;
case HImode:
case SImode:
regbase = GP_ARG_FIRST;
break;
case DImode: cum->arg_number++;
case TImode: }
if (! TARGET_64BIT)
cum->arg_words += (cum->arg_words & 1);
regbase = GP_ARG_FIRST;
}
if (*arg_words >= (unsigned) MAX_ARGS_IN_REGISTERS) /* Return an RTL expression containing the register for the given mode,
{ or 0 if the argument is to be passed on the stack. */
if (TARGET_DEBUG_E_MODE)
fprintf (stderr, "<stack>%s\n", struct_p ? ", [struct]" : "");
ret = 0; struct rtx_def *
} function_arg (cum, mode, type, named)
else const CUMULATIVE_ARGS *cum; /* current arg information */
{ enum machine_mode mode; /* current arg mode */
if (regbase == -1) tree type; /* type of the argument or 0 if lib support */
abort (); int named; /* != 0 for normal args, == 0 for ... args */
{
struct mips_arg_info info;
if (! type || TREE_CODE (type) != RECORD_TYPE /* We will be called with a mode of VOIDmode after the last argument
|| mips_abi == ABI_32 || mips_abi == ABI_EABI has been seen. Whatever we return will be passed to the call
|| mips_abi == ABI_O64 || mips_abi == ABI_MEABI insn. If we need any shifts for small structures, return them in
|| ! named a PARALLEL; in that case, stuff the mips16 fp_code in as the
|| ! TYPE_SIZE_UNIT (type) mode. Otherwise, if we need a mips16 fp_code, return a REG
|| ! host_integerp (TYPE_SIZE_UNIT (type), 1)) with the code stored as the mode. */
if (mode == VOIDmode)
{ {
if (cum->num_adjusts > 0)
return gen_rtx_PARALLEL ((enum machine_mode) cum->fp_code,
gen_rtvec_v (cum->num_adjusts,
(rtx *) cum->adjust));
else if (TARGET_MIPS16 && cum->fp_code != 0)
return gen_rtx_REG ((enum machine_mode) cum->fp_code, 0);
unsigned int arg_reg = (regbase + *arg_words + bias);
ret = gen_rtx_REG (mode, arg_reg);
if (mips_abi == ABI_MEABI
&& regbase == FP_ARG_FIRST
&& ! cum->prototype)
{
/* To make K&R varargs work we need to pass floating
point arguments in both integer and FP registers. */
ret = gen_rtx_PARALLEL (mode,
gen_rtvec (2,
gen_rtx_EXPR_LIST (VOIDmode,
gen_rtx_REG (mode,
arg_reg + GP_ARG_FIRST - FP_ARG_FIRST),
const0_rtx), gen_rtx_EXPR_LIST (VOIDmode, ret, const0_rtx)));
}
}
else else
return 0;
}
mips_arg_info (cum, mode, type, named, &info);
/* Return straight away if the whole argument is passed on the stack. */
if (info.reg_offset == MAX_ARGS_IN_REGISTERS)
return 0;
if (type != 0
&& TREE_CODE (type) == RECORD_TYPE
&& (mips_abi == ABI_N32 || mips_abi == ABI_64)
&& TYPE_SIZE_UNIT (type)
&& host_integerp (TYPE_SIZE_UNIT (type), 1)
&& named
&& mode != DFmode)
{ {
/* The Irix 6 n32/n64 ABIs say that if any 64 bit chunk of the /* The Irix 6 n32/n64 ABIs say that if any 64 bit chunk of the
structure contains a double in its entirety, then that 64 bit structure contains a double in its entirety, then that 64 bit
...@@ -4163,35 +4172,22 @@ function_arg (cum, mode, type, named) ...@@ -4163,35 +4172,22 @@ function_arg (cum, mode, type, named)
&& int_bit_position (field) % BITS_PER_WORD == 0) && int_bit_position (field) % BITS_PER_WORD == 0)
break; break;
/* If the whole struct fits a DFmode register, if (field != 0)
we don't need the PARALLEL. */
if (! field || mode == DFmode)
ret = gen_rtx_REG (mode, regbase + *arg_words + bias);
else
{ {
/* Now handle the special case by returning a PARALLEL /* Now handle the special case by returning a PARALLEL
indicating where each 64 bit chunk goes. */ indicating where each 64 bit chunk goes. INFO.REG_WORDS
unsigned int chunks; chunks are passed in registers. */
HOST_WIDE_INT bitpos;
unsigned int regno;
unsigned int i; unsigned int i;
HOST_WIDE_INT bitpos;
/* ??? If this is a packed structure, then the last hunk won't rtx ret;
be 64 bits. */
chunks
= tree_low_cst (TYPE_SIZE_UNIT (type), 1) / UNITS_PER_WORD;
if (chunks + *arg_words + bias > (unsigned) MAX_ARGS_IN_REGISTERS)
chunks = MAX_ARGS_IN_REGISTERS - *arg_words - bias;
/* assign_parms checks the mode of ENTRY_PARM, so we must /* assign_parms checks the mode of ENTRY_PARM, so we must
use the actual mode here. */ use the actual mode here. */
ret = gen_rtx_PARALLEL (mode, rtvec_alloc (chunks)); ret = gen_rtx_PARALLEL (mode, rtvec_alloc (info.reg_words));
bitpos = 0; bitpos = 0;
regno = regbase + *arg_words + bias;
field = TYPE_FIELDS (type); field = TYPE_FIELDS (type);
for (i = 0; i < chunks; i++) for (i = 0; i < info.reg_words; i++)
{ {
rtx reg; rtx reg;
...@@ -4205,119 +4201,136 @@ function_arg (cum, mode, type, named) ...@@ -4205,119 +4201,136 @@ function_arg (cum, mode, type, named)
&& TREE_CODE (TREE_TYPE (field)) == REAL_TYPE && TREE_CODE (TREE_TYPE (field)) == REAL_TYPE
&& !TARGET_SOFT_FLOAT && !TARGET_SOFT_FLOAT
&& TYPE_PRECISION (TREE_TYPE (field)) == BITS_PER_WORD) && TYPE_PRECISION (TREE_TYPE (field)) == BITS_PER_WORD)
reg = gen_rtx_REG (DFmode, reg = gen_rtx_REG (DFmode, FP_ARG_FIRST + info.reg_offset + i);
regno + FP_ARG_FIRST - GP_ARG_FIRST);
else else
reg = gen_rtx_REG (word_mode, regno); reg = gen_rtx_REG (DImode, GP_ARG_FIRST + info.reg_offset + i);
XVECEXP (ret, 0, i) XVECEXP (ret, 0, i)
= gen_rtx_EXPR_LIST (VOIDmode, reg, = gen_rtx_EXPR_LIST (VOIDmode, reg,
GEN_INT (bitpos / BITS_PER_UNIT)); GEN_INT (bitpos / BITS_PER_UNIT));
bitpos += 64; bitpos += BITS_PER_WORD;
regno++;
}
} }
} return ret;
if (TARGET_DEBUG_E_MODE)
fprintf (stderr, "%s%s\n", reg_names[regbase + *arg_words + bias],
struct_p ? ", [struct]" : "");
/* The following is a hack in order to pass 1 byte structures
the same way that the MIPS compiler does (namely by passing
the structure in the high byte or half word of the register).
This also makes varargs work. If we have such a structure,
we save the adjustment RTL, and the call define expands will
emit them. For the VOIDmode argument (argument after the
last real argument), pass back a parallel vector holding each
of the adjustments. */
/* ??? function_arg can be called more than once for each argument.
As a result, we compute more adjustments than we need here.
See the CUMULATIVE_ARGS definition in mips.h. */
/* ??? This scheme requires everything smaller than the word size to
shifted to the left, but when TARGET_64BIT and ! TARGET_INT64,
that would mean every int needs to be shifted left, which is very
inefficient. Let's not carry this compatibility to the 64 bit
calling convention for now. */
if (struct_p && int_size_in_bytes (type) < UNITS_PER_WORD
&& ! TARGET_64BIT
&& mips_abi != ABI_EABI
&& mips_abi != ABI_MEABI)
{
rtx amount = GEN_INT (BITS_PER_WORD
- int_size_in_bytes (type) * BITS_PER_UNIT);
rtx reg = gen_rtx_REG (word_mode, regbase + *arg_words + bias);
if (TARGET_64BIT)
cum->adjust[cum->num_adjusts++] = gen_ashldi3 (reg, reg, amount);
else
cum->adjust[cum->num_adjusts++] = gen_ashlsi3 (reg, reg, amount);
} }
} }
/* We will be called with a mode of VOIDmode after the last argument if (mips_abi == ABI_MEABI && info.fpr_p && !cum->prototype)
has been seen. Whatever we return will be passed to the call
insn. If we need any shifts for small structures, return them in
a PARALLEL; in that case, stuff the mips16 fp_code in as the
mode. Otherwise, if we have need a mips16 fp_code, return a REG
with the code stored as the mode. */
if (mode == VOIDmode)
{ {
if (cum->num_adjusts > 0) /* To make K&R varargs work we need to pass floating
ret = gen_rtx (PARALLEL, (enum machine_mode) cum->fp_code, point arguments in both integer and FP registers. */
gen_rtvec_v (cum->num_adjusts, cum->adjust)); return gen_rtx_PARALLEL
else if (TARGET_MIPS16 && cum->fp_code != 0) (mode,
ret = gen_rtx (REG, (enum machine_mode) cum->fp_code, 0); gen_rtvec (2,
gen_rtx_EXPR_LIST (VOIDmode,
gen_rtx_REG (mode,
GP_ARG_FIRST
+ info.reg_offset),
const0_rtx),
gen_rtx_EXPR_LIST (VOIDmode,
gen_rtx_REG (mode,
FP_ARG_FIRST
+ info.reg_offset),
const0_rtx)));
} }
return ret; if (info.fpr_p)
return gen_rtx_REG (mode, FP_ARG_FIRST + info.reg_offset);
else
return gen_rtx_REG (mode, GP_ARG_FIRST + info.reg_offset);
} }
int int
function_arg_partial_nregs (cum, mode, type, named) function_arg_partial_nregs (cum, mode, type, named)
CUMULATIVE_ARGS *cum; /* current arg information */ const CUMULATIVE_ARGS *cum; /* current arg information */
enum machine_mode mode; /* current arg mode */ enum machine_mode mode; /* current arg mode */
tree type; /* type of the argument or 0 if lib support */ tree type; /* type of the argument or 0 if lib support */
int named ATTRIBUTE_UNUSED;/* != 0 for normal args, == 0 for ... args */ int named; /* != 0 for normal args, == 0 for ... args */
{ {
if ((mode == BLKmode struct mips_arg_info info;
|| GET_MODE_CLASS (mode) != MODE_COMPLEX_INT
|| GET_MODE_CLASS (mode) != MODE_COMPLEX_FLOAT)
&& cum->arg_words < (unsigned) MAX_ARGS_IN_REGISTERS
&& mips_abi != ABI_EABI)
{
int words;
if (mode == BLKmode)
words = ((int_size_in_bytes (type) + UNITS_PER_WORD - 1)
/ UNITS_PER_WORD);
else
words = (GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD;
if (words + cum->arg_words <= (unsigned) MAX_ARGS_IN_REGISTERS) mips_arg_info (cum, mode, type, named, &info);
return 0; /* structure fits in registers */ return info.stack_words > 0 ? info.reg_words : 0;
}
if (TARGET_DEBUG_E_MODE) int
fprintf (stderr, "function_arg_partial_nregs = %d\n", mips_setup_incoming_varargs (cum, mode, type, no_rtl)
MAX_ARGS_IN_REGISTERS - cum->arg_words); const CUMULATIVE_ARGS *cum;
enum machine_mode mode;
tree type;
int no_rtl;
{
CUMULATIVE_ARGS local_cum;
int gp_saved, fp_saved;
return MAX_ARGS_IN_REGISTERS - cum->arg_words; if (mips_abi == ABI_32 || mips_abi == ABI_O64)
} return 0;
else if (mode == DImode /* The caller has advanced CUM up to, but not beyond, the last named
&& cum->arg_words == MAX_ARGS_IN_REGISTERS - (unsigned)1 argument. Advance a local copy of CUM past the last "real" named
&& ! TARGET_64BIT && mips_abi != ABI_EABI) argument, to find out how many registers are left over.
For K&R varargs, the last named argument is a dummy word-sized one,
so CUM already contains the information we need. For stdarg, it is
a real argument (such as the format in printf()) and we need to
step over it. */
local_cum = *cum;
if (!current_function_varargs)
FUNCTION_ARG_ADVANCE (local_cum, mode, type, 1);
/* Found out how many registers we need to save. */
gp_saved = MAX_ARGS_IN_REGISTERS - local_cum.gp_regs;
fp_saved = (EABI_FLOAT_VARARGS_P
? MAX_ARGS_IN_REGISTERS - local_cum.fp_regs
: 0);
if (!no_rtl)
{ {
if (TARGET_DEBUG_E_MODE) if (gp_saved > 0)
fprintf (stderr, "function_arg_partial_nregs = 1\n"); {
rtx ptr, mem;
return 1; ptr = virtual_incoming_args_rtx;
if (mips_abi == ABI_EABI)
ptr = plus_constant (ptr, -gp_saved * UNITS_PER_WORD);
mem = gen_rtx_MEM (BLKmode, ptr);
/* va_arg is an array access in this case, which causes
it to get MEM_IN_STRUCT_P set. We must set it here
so that the insn scheduler won't assume that these
stores can't possibly overlap with the va_arg loads. */
if (mips_abi != ABI_EABI && BYTES_BIG_ENDIAN)
MEM_SET_IN_STRUCT_P (mem, 1);
move_block_from_reg (local_cum.gp_regs + GP_ARG_FIRST, mem,
gp_saved, gp_saved * UNITS_PER_WORD);
} }
if (fp_saved > 0)
{
/* We can't use move_block_from_reg, because it will use
the wrong mode. */
enum machine_mode mode;
int off, i;
return 0; /* Set OFF to the offset from virtual_incoming_args_rtx of
the first float register. The FP save area lies below
the integer one, and is aligned to UNITS_PER_FPVALUE bytes. */
off = -gp_saved * UNITS_PER_WORD;
off &= ~(UNITS_PER_FPVALUE - 1);
off -= fp_saved * UNITS_PER_FPREG;
mode = TARGET_SINGLE_FLOAT ? SFmode : DFmode;
for (i = local_cum.fp_regs; i < MAX_ARGS_IN_REGISTERS; i += FP_INC)
{
rtx ptr = plus_constant (virtual_incoming_args_rtx, off);
emit_move_insn (gen_rtx_MEM (mode, ptr),
gen_rtx_REG (mode, FP_ARG_FIRST + i));
off += UNITS_PER_FPVALUE;
}
}
}
return (gp_saved * UNITS_PER_WORD) + (fp_saved * UNITS_PER_FPREG);
} }
/* Create the va_list data type. /* Create the va_list data type.
...@@ -4332,9 +4345,9 @@ function_arg_partial_nregs (cum, mode, type, named) ...@@ -4332,9 +4345,9 @@ function_arg_partial_nregs (cum, mode, type, named)
These are downcounted as float or non-float arguments are used, These are downcounted as float or non-float arguments are used,
and when they get to zero, the argument must be obtained from the and when they get to zero, the argument must be obtained from the
overflow region. overflow region.
If TARGET_SOFT_FLOAT or TARGET_SINGLE_FLOAT, then no FPR save area exists, If !EABI_FLOAT_VARARGS_P, then no FPR save area exists, and a single
and a single pointer is enough. It's started at the GPR save area, pointer is enough. It's started at the GPR save area, and is
and is advanced, period. advanced, period.
Note that the GPR save area is not constant size, due to optimization Note that the GPR save area is not constant size, due to optimization
in the prologue. Hence, we can't use a design with two pointers in the prologue. Hence, we can't use a design with two pointers
and two offsets, although we could have designed this with two pointers and two offsets, although we could have designed this with two pointers
...@@ -4344,7 +4357,7 @@ function_arg_partial_nregs (cum, mode, type, named) ...@@ -4344,7 +4357,7 @@ function_arg_partial_nregs (cum, mode, type, named)
tree tree
mips_build_va_list () mips_build_va_list ()
{ {
if (mips_abi == ABI_EABI && !TARGET_SOFT_FLOAT && !TARGET_SINGLE_FLOAT) if (EABI_FLOAT_VARARGS_P)
{ {
tree f_ovfl, f_gtop, f_ftop, f_goff, f_foff, record; tree f_ovfl, f_gtop, f_ftop, f_goff, f_foff, record;
...@@ -4392,35 +4405,22 @@ mips_va_start (stdarg_p, valist, nextarg) ...@@ -4392,35 +4405,22 @@ mips_va_start (stdarg_p, valist, nextarg)
tree valist; tree valist;
rtx nextarg; rtx nextarg;
{ {
int int_arg_words; const CUMULATIVE_ARGS *cum = &current_function_args_info;
tree t;
/* Find out how many non-float named formals */
int_arg_words = current_function_args_info.arg_words;
if (mips_abi == ABI_EABI) if (mips_abi == ABI_EABI)
{ {
int gpr_save_area_size; int gpr_save_area_size;
/* Note UNITS_PER_WORD is 4 bytes or 8, depending on TARGET_64BIT. */
if (int_arg_words < 8 )
/* Adjust for the prologue's economy measure */
gpr_save_area_size = (8 - int_arg_words) * UNITS_PER_WORD;
else
gpr_save_area_size = 0;
if (!TARGET_SOFT_FLOAT && !TARGET_SINGLE_FLOAT) gpr_save_area_size
= (MAX_ARGS_IN_REGISTERS - cum->gp_regs) * UNITS_PER_WORD;
if (EABI_FLOAT_VARARGS_P)
{ {
tree f_ovfl, f_gtop, f_ftop, f_goff, f_foff; tree f_ovfl, f_gtop, f_ftop, f_goff, f_foff;
tree ovfl, gtop, ftop, goff, foff; tree ovfl, gtop, ftop, goff, foff;
tree gprv; tree t;
int float_formals, fpr_offset, size_excess, floats_passed_in_regs; int fpr_offset;
int fpr_save_offset; int fpr_save_area_size;
float_formals = current_function_args_info.fp_arg_words;
/* If mips2, the number of formals is half the reported # of words */
if (!TARGET_64BIT)
float_formals /= 2;
floats_passed_in_regs = (TARGET_64BIT ? 8 : 4);
f_ovfl = TYPE_FIELDS (va_list_type_node); f_ovfl = TYPE_FIELDS (va_list_type_node);
f_gtop = TREE_CHAIN (f_ovfl); f_gtop = TREE_CHAIN (f_ovfl);
...@@ -4434,84 +4434,49 @@ mips_va_start (stdarg_p, valist, nextarg) ...@@ -4434,84 +4434,49 @@ mips_va_start (stdarg_p, valist, nextarg)
goff = build (COMPONENT_REF, TREE_TYPE (f_goff), valist, f_goff); goff = build (COMPONENT_REF, TREE_TYPE (f_goff), valist, f_goff);
foff = build (COMPONENT_REF, TREE_TYPE (f_foff), valist, f_foff); foff = build (COMPONENT_REF, TREE_TYPE (f_foff), valist, f_foff);
/* Emit code setting a pointer into the overflow (shared-stack) area. /* Emit code to initialize OVFL, which points to the next varargs
If there were more than 8 non-float formals, or more than 8 stack argument. CUM->STACK_WORDS gives the number of stack
float formals, then this pointer isn't to the base of the area. words used by named arguments. */
In that case, it must point to where the first vararg is. */
size_excess = 0;
if (float_formals > floats_passed_in_regs)
size_excess += (float_formals-floats_passed_in_regs) * 8;
if (int_arg_words > 8)
size_excess += (int_arg_words-8) * UNITS_PER_WORD;
/* FIXME: for mips2, the above size_excess can be wrong. Because the
overflow stack holds mixed size items, there can be alignments,
so that an 8 byte double following a 4 byte int will be on an
8 byte boundary. This means that the above calculation should
take into account the exact sequence of floats and non-floats
which make up the excess. That calculation should be rolled
into the code which sets the current_function_args_info struct.
The above then reduces to a fetch from that struct. */
t = make_tree (TREE_TYPE (ovfl), virtual_incoming_args_rtx); t = make_tree (TREE_TYPE (ovfl), virtual_incoming_args_rtx);
if (size_excess) if (cum->stack_words > 0)
t = build (PLUS_EXPR, TREE_TYPE (ovfl), t, t = build (PLUS_EXPR, TREE_TYPE (ovfl), t,
build_int_2 (size_excess, 0)); build_int_2 (cum->stack_words * UNITS_PER_WORD, 0));
t = build (MODIFY_EXPR, TREE_TYPE (ovfl), ovfl, t); t = build (MODIFY_EXPR, TREE_TYPE (ovfl), ovfl, t);
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
/* Emit code setting a ptr to the base of the overflow area. */ /* Emit code to initialize GTOP, the top of the GPR save area. */
t = make_tree (TREE_TYPE (gtop), virtual_incoming_args_rtx); t = make_tree (TREE_TYPE (gtop), virtual_incoming_args_rtx);
t = build (MODIFY_EXPR, TREE_TYPE (gtop), gtop, t); t = build (MODIFY_EXPR, TREE_TYPE (gtop), gtop, t);
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
/* Emit code setting a pointer to the GPR save area. /* Emit code to initialize FTOP, the top of the FPR save area.
More precisely, a pointer to off-the-end of the FPR save area. This address is gpr_save_area_bytes below GTOP, rounded
If mips4, this is gpr_save_area_size below the overflow area. down to the next fp-aligned boundary. */
If mips2, also round down to an 8-byte boundary, since the FPR t = make_tree (TREE_TYPE (ftop), virtual_incoming_args_rtx);
save area is 8-byte aligned, and GPR is 4-byte-aligned. fpr_offset = gpr_save_area_size + UNITS_PER_FPVALUE - 1;
Therefore there can be a 4-byte gap between the save areas. */ fpr_offset &= ~(UNITS_PER_FPVALUE - 1);
gprv = make_tree (TREE_TYPE (ftop), virtual_incoming_args_rtx); if (fpr_offset)
fpr_save_offset = gpr_save_area_size; t = build (PLUS_EXPR, TREE_TYPE (ftop), t,
if (!TARGET_64BIT) build_int_2 (-fpr_offset, -1));
{ t = build (MODIFY_EXPR, TREE_TYPE (ftop), ftop, t);
if (fpr_save_offset & 7)
fpr_save_offset += 4;
}
if (fpr_save_offset)
gprv = build (PLUS_EXPR, TREE_TYPE (ftop), gprv,
build_int_2 (-fpr_save_offset,-1));
t = build (MODIFY_EXPR, TREE_TYPE (ftop), ftop, gprv);
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
/* Emit code initting an offset to the size of the GPR save area */ /* Emit code to initialize GOFF, the offset from GTOP of the
next GPR argument. */
t = build (MODIFY_EXPR, TREE_TYPE (goff), goff, t = build (MODIFY_EXPR, TREE_TYPE (goff), goff,
build_int_2 (gpr_save_area_size,0)); build_int_2 (gpr_save_area_size, 0));
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
/* Emit code initting an offset from ftop to the first float /* Likewise emit code to initialize FOFF, the offset from FTOP
vararg. This varies in size, since any float of the next FPR argument. */
varargs are put in the FPR save area after the formals. fpr_save_area_size
Note it's 8 bytes/formal regardless of TARGET_64BIT. = (MAX_ARGS_IN_REGISTERS - cum->fp_regs) * UNITS_PER_FPREG;
However, mips2 stores 4 GPRs, mips4 stores 8 GPRs.
If there are 8 or more float formals, init to zero.
(In fact, the formals aren't stored in the bottom of the
FPR save area: they are elsewhere, and the size of the FPR
save area is economized by the prologue. But this code doesn't
care. This design is unaffected by that fact.) */
if (float_formals >= floats_passed_in_regs)
fpr_offset = 0;
else
fpr_offset = (floats_passed_in_regs - float_formals) * 8;
t = build (MODIFY_EXPR, TREE_TYPE (foff), foff, t = build (MODIFY_EXPR, TREE_TYPE (foff), foff,
build_int_2 (fpr_offset,0)); build_int_2 (fpr_save_area_size, 0));
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
} }
else else
{ {
/* TARGET_SOFT_FLOAT or TARGET_SINGLE_FLOAT */
/* Everything is in the GPR save area, or in the overflow /* Everything is in the GPR save area, or in the overflow
area which is contiguous with it. */ area which is contiguous with it. */
...@@ -4534,10 +4499,10 @@ mips_va_start (stdarg_p, valist, nextarg) ...@@ -4534,10 +4499,10 @@ mips_va_start (stdarg_p, valist, nextarg)
/* ??? This had been conditional on /* ??? This had been conditional on
_MIPS_SIM == _MIPS_SIM_ABI64 || _MIPS_SIM == _MIPS_SIM_NABI32 _MIPS_SIM == _MIPS_SIM_ABI64 || _MIPS_SIM == _MIPS_SIM_NABI32
and both iris5.h and iris6.h define _MIPS_SIM. */ and both iris5.h and iris6.h define _MIPS_SIM. */
if (mips_abi == ABI_N32 || mips_abi == ABI_64) if (mips_abi == ABI_N32
ofs = (int_arg_words >= 8 ? -UNITS_PER_WORD : 0); || mips_abi == ABI_64
else if (mips_abi == ABI_MEABI) || mips_abi == ABI_MEABI)
ofs = (int_arg_words >= 8 ? -UNITS_PER_WORD : 0); ofs = (cum->gp_regs < MAX_ARGS_IN_REGISTERS ? 0 : -UNITS_PER_WORD);
else else
ofs = -UNITS_PER_WORD; ofs = -UNITS_PER_WORD;
} }
...@@ -4562,13 +4527,12 @@ mips_va_arg (valist, type) ...@@ -4562,13 +4527,12 @@ mips_va_arg (valist, type)
if (mips_abi == ABI_EABI) if (mips_abi == ABI_EABI)
{ {
int indirect; bool indirect;
rtx r, lab_over = NULL_RTX, lab_false; rtx r;
tree f_ovfl, f_gtop, f_ftop, f_goff, f_foff;
tree ovfl, gtop, ftop, goff, foff;
indirect indirect
= function_arg_pass_by_reference (NULL, TYPE_MODE (type), type, 0); = function_arg_pass_by_reference (NULL, TYPE_MODE (type), type, 0);
if (indirect) if (indirect)
{ {
size = POINTER_SIZE / BITS_PER_UNIT; size = POINTER_SIZE / BITS_PER_UNIT;
...@@ -4577,48 +4541,45 @@ mips_va_arg (valist, type) ...@@ -4577,48 +4541,45 @@ mips_va_arg (valist, type)
addr_rtx = gen_reg_rtx (Pmode); addr_rtx = gen_reg_rtx (Pmode);
if (TARGET_SOFT_FLOAT || TARGET_SINGLE_FLOAT) if (!EABI_FLOAT_VARARGS_P)
{ {
/* Case of all args in a merged stack. No need to check bounds, /* Case of all args in a merged stack. No need to check bounds,
just advance valist along the stack. */ just advance valist along the stack. */
tree gpr = valist; tree gpr = valist;
if (! indirect if (!indirect
&& ! TARGET_64BIT && !TARGET_64BIT
&& TYPE_ALIGN (type) > (unsigned) BITS_PER_WORD) && TYPE_ALIGN (type) > (unsigned) BITS_PER_WORD)
{ {
/* Align the pointer using: ap = (ap + align - 1) & -align,
where align is 2 * UNITS_PER_WORD. */
t = build (PLUS_EXPR, TREE_TYPE (gpr), gpr, t = build (PLUS_EXPR, TREE_TYPE (gpr), gpr,
build_int_2 (2*UNITS_PER_WORD - 1, 0)); build_int_2 (2 * UNITS_PER_WORD - 1, 0));
t = build (BIT_AND_EXPR, TREE_TYPE (t), t, t = build (BIT_AND_EXPR, TREE_TYPE (t), t,
build_int_2 (-2*UNITS_PER_WORD, -1)); build_int_2 (-2 * UNITS_PER_WORD, -1));
t = build (MODIFY_EXPR, TREE_TYPE (gpr), gpr, t); t = build (MODIFY_EXPR, TREE_TYPE (gpr), gpr, t);
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
} }
/* Emit code to set addr_rtx to the valist, and postincrement
the valist by the size of the argument, rounded up to the
next word. */
t = build (POSTINCREMENT_EXPR, TREE_TYPE (gpr), gpr, t = build (POSTINCREMENT_EXPR, TREE_TYPE (gpr), gpr,
size_int (rsize)); size_int (rsize));
r = expand_expr (t, addr_rtx, Pmode, EXPAND_NORMAL); r = expand_expr (t, addr_rtx, Pmode, EXPAND_NORMAL);
if (r != addr_rtx) if (r != addr_rtx)
emit_move_insn (addr_rtx, r); emit_move_insn (addr_rtx, r);
/* flush the POSTINCREMENT */ /* Flush the POSTINCREMENT. */
emit_queue(); emit_queue();
if (indirect)
{
r = gen_rtx_MEM (Pmode, addr_rtx);
set_mem_alias_set (r, get_varargs_alias_set ());
emit_move_insn (addr_rtx, r);
} }
else else
{ {
if (BYTES_BIG_ENDIAN && rsize != size) /* Not a simple merged stack. */
addr_rtx = plus_constant (addr_rtx, rsize - size);
}
return addr_rtx;
}
/* Not a simple merged stack. Need ptrs and indexes left by va_start. */ tree f_ovfl, f_gtop, f_ftop, f_goff, f_foff;
tree ovfl, top, off;
rtx lab_over = NULL_RTX, lab_false;
f_ovfl = TYPE_FIELDS (va_list_type_node); f_ovfl = TYPE_FIELDS (va_list_type_node);
f_gtop = TREE_CHAIN (f_ovfl); f_gtop = TREE_CHAIN (f_ovfl);
...@@ -4626,128 +4587,107 @@ mips_va_arg (valist, type) ...@@ -4626,128 +4587,107 @@ mips_va_arg (valist, type)
f_goff = TREE_CHAIN (f_ftop); f_goff = TREE_CHAIN (f_ftop);
f_foff = TREE_CHAIN (f_goff); f_foff = TREE_CHAIN (f_goff);
ovfl = build (COMPONENT_REF, TREE_TYPE (f_ovfl), valist, f_ovfl); /* We maintain separate pointers and offsets for floating-point
gtop = build (COMPONENT_REF, TREE_TYPE (f_gtop), valist, f_gtop); and integer arguments, but we need similar code in both cases.
ftop = build (COMPONENT_REF, TREE_TYPE (f_ftop), valist, f_ftop); Let:
goff = build (COMPONENT_REF, TREE_TYPE (f_goff), valist, f_goff);
foff = build (COMPONENT_REF, TREE_TYPE (f_foff), valist, f_foff);
lab_false = gen_label_rtx (); TOP be the top of the register save area;
lab_over = gen_label_rtx (); OFF be the offset from TOP of the next register;
ADDR_RTX be the address of the argument; and
RSIZE be the number of bytes used to store the argument.
if (TREE_CODE (type) == REAL_TYPE) The code we want is:
{
/* Emit code to branch if foff == 0. */ 1: off &= -rsize; // round down
r = expand_expr (foff, NULL_RTX, TYPE_MODE (TREE_TYPE (foff)), 2: if (off != 0)
EXPAND_NORMAL); 3: {
emit_cmp_and_jump_insns (r, const0_rtx, EQ, const1_rtx, GET_MODE (r), 4: addr_rtx = top - off;
1, lab_false); 5: off -= rsize;
6: }
7: else
8: {
9: ovfl += ((intptr_t) ovfl + rsize - 1) & -rsize;
10: addr_rtx = ovfl;
11: ovfl += rsize;
12: }
/* Emit code for addr_rtx = ftop - foff */ [1] and [9] can sometimes be optimized away. */
t = build (MINUS_EXPR, TREE_TYPE (ftop), ftop, foff );
r = expand_expr (t, addr_rtx, Pmode, EXPAND_NORMAL);
if (r != addr_rtx)
emit_move_insn (addr_rtx, r);
/* Emit code for foff-=8. lab_false = gen_label_rtx ();
Advances the offset up FPR save area by one double */ lab_over = gen_label_rtx ();
t = build (MINUS_EXPR, TREE_TYPE (foff), foff, build_int_2 (8, 0));
t = build (MODIFY_EXPR, TREE_TYPE (foff), foff, t);
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
emit_queue(); ovfl = build (COMPONENT_REF, TREE_TYPE (f_ovfl), valist, f_ovfl);
emit_jump (lab_over);
emit_barrier ();
emit_label (lab_false);
if (!TARGET_64BIT) if (TREE_CODE (type) == REAL_TYPE)
{ {
/* For mips2, the overflow area contains mixed size items. top = build (COMPONENT_REF, TREE_TYPE (f_ftop), valist, f_ftop);
If a 4-byte int is followed by an 8-byte float, then off = build (COMPONENT_REF, TREE_TYPE (f_foff), valist, f_foff);
natural alignment causes a 4 byte gap.
So, dynamically adjust ovfl up to a multiple of 8. */
t = build (BIT_AND_EXPR, TREE_TYPE (ovfl), ovfl,
build_int_2 (7, 0));
t = build (PLUS_EXPR, TREE_TYPE (ovfl), ovfl, t);
t = build (MODIFY_EXPR, TREE_TYPE (ovfl), ovfl, t);
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
}
/* Emit code for addr_rtx = the ovfl pointer into overflow area.
Regardless of mips2, postincrement the ovfl pointer by 8. */
t = build (POSTINCREMENT_EXPR, TREE_TYPE(ovfl), ovfl,
size_int (8));
r = expand_expr (t, addr_rtx, Pmode, EXPAND_NORMAL);
if (r != addr_rtx)
emit_move_insn (addr_rtx, r);
emit_queue(); /* When floating-point registers are saved to the stack,
emit_label (lab_over); each one will take up UNITS_PER_FPVALUE bytes, regardless
return addr_rtx; of the float's precision. */
rsize = UNITS_PER_FPVALUE;
} }
else else
{ {
/* not REAL_TYPE */ top = build (COMPONENT_REF, TREE_TYPE (f_gtop), valist, f_gtop);
int step_size; off = build (COMPONENT_REF, TREE_TYPE (f_goff), valist, f_goff);
if (rsize > UNITS_PER_WORD)
if (! TARGET_64BIT
&& TREE_CODE (type) == INTEGER_TYPE
&& TYPE_PRECISION (type) == 64)
{ {
/* In mips2, int takes 32 bits of the GPR save area, but /* [1] Emit code for: off &= -rsize. */
longlong takes an aligned 64 bits. So, emit code t = build (BIT_AND_EXPR, TREE_TYPE (off), off,
to zero the low order bits of goff, thus aligning build_int_2 (-rsize, -1));
the later calculation of (gtop-goff) upwards. */ t = build (MODIFY_EXPR, TREE_TYPE (off), off, t);
t = build (BIT_AND_EXPR, TREE_TYPE (goff), goff,
build_int_2 (-8, -1));
t = build (MODIFY_EXPR, TREE_TYPE (goff), goff, t);
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
} }
}
/* Emit code to branch if goff == 0. */ /* [2] Emit code to branch if off == 0. */
r = expand_expr (goff, NULL_RTX, TYPE_MODE (TREE_TYPE (goff)), r = expand_expr (off, NULL_RTX, TYPE_MODE (TREE_TYPE (off)),
EXPAND_NORMAL); EXPAND_NORMAL);
emit_cmp_and_jump_insns (r, const0_rtx, EQ, const1_rtx, GET_MODE (r), emit_cmp_and_jump_insns (r, const0_rtx, EQ, const1_rtx, GET_MODE (r),
1, lab_false); 1, lab_false);
/* Emit code for addr_rtx = gtop - goff. */ /* [4] Emit code for: addr_rtx = top - off. */
t = build (MINUS_EXPR, TREE_TYPE (gtop), gtop, goff); t = build (MINUS_EXPR, TREE_TYPE (top), top, off);
r = expand_expr (t, addr_rtx, Pmode, EXPAND_NORMAL); r = expand_expr (t, addr_rtx, Pmode, EXPAND_NORMAL);
if (r != addr_rtx) if (r != addr_rtx)
emit_move_insn (addr_rtx, r); emit_move_insn (addr_rtx, r);
/* Note that mips2 int is 32 bit, but mips2 longlong is 64. */ /* [5] Emit code for: off -= rsize. */
if (! TARGET_64BIT && TYPE_PRECISION (type) == 64) t = build (MINUS_EXPR, TREE_TYPE (off), off, build_int_2 (rsize, 0));
step_size = 8; t = build (MODIFY_EXPR, TREE_TYPE (off), off, t);
else
step_size = UNITS_PER_WORD;
/* Emit code for goff = goff - step_size.
Advances the offset up GPR save area over the item. */
t = build (MINUS_EXPR, TREE_TYPE (goff), goff,
build_int_2 (step_size, 0));
t = build (MODIFY_EXPR, TREE_TYPE (goff), goff, t);
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
/* [7] Emit code to jump over the else clause, then the label
that starts it. */
emit_queue(); emit_queue();
emit_jump (lab_over); emit_jump (lab_over);
emit_barrier (); emit_barrier ();
emit_label (lab_false); emit_label (lab_false);
/* Emit code for addr_rtx -> overflow area, postinc by step_size */ if (rsize > UNITS_PER_WORD)
t = build (POSTINCREMENT_EXPR, TREE_TYPE(ovfl), ovfl, {
size_int (step_size)); /* [9] Emit: ovfl += ((intptr_t) ovfl + rsize - 1) & -rsize. */
t = build (PLUS_EXPR, TREE_TYPE (ovfl), ovfl,
build_int_2 (rsize - 1, 0));
t = build (BIT_AND_EXPR, TREE_TYPE (ovfl), t,
build_int_2 (-rsize, -1));
t = build (MODIFY_EXPR, TREE_TYPE (ovfl), ovfl, t);
expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL);
}
/* [10, 11]. Emit code to store ovfl in addr_rtx, then
post-increment ovfl by rsize. */
t = build (POSTINCREMENT_EXPR, TREE_TYPE (ovfl), ovfl,
size_int (rsize));
r = expand_expr (t, addr_rtx, Pmode, EXPAND_NORMAL); r = expand_expr (t, addr_rtx, Pmode, EXPAND_NORMAL);
if (r != addr_rtx) if (r != addr_rtx)
emit_move_insn (addr_rtx, r); emit_move_insn (addr_rtx, r);
emit_queue(); emit_queue();
emit_label (lab_over); emit_label (lab_over);
}
if (BYTES_BIG_ENDIAN && rsize != size)
addr_rtx = plus_constant (addr_rtx, rsize - size);
if (indirect) if (indirect)
{ {
addr_rtx = force_reg (Pmode, addr_rtx); addr_rtx = force_reg (Pmode, addr_rtx);
...@@ -4755,9 +4695,12 @@ mips_va_arg (valist, type) ...@@ -4755,9 +4695,12 @@ mips_va_arg (valist, type)
set_mem_alias_set (r, get_varargs_alias_set ()); set_mem_alias_set (r, get_varargs_alias_set ());
emit_move_insn (addr_rtx, r); emit_move_insn (addr_rtx, r);
} }
else
return addr_rtx; {
if (BYTES_BIG_ENDIAN && rsize != size)
addr_rtx = plus_constant (addr_rtx, rsize - size);
} }
return addr_rtx;
} }
else else
{ {
...@@ -8083,7 +8026,7 @@ mips_function_value (valtype, func) ...@@ -8083,7 +8026,7 @@ mips_function_value (valtype, func)
int int
function_arg_pass_by_reference (cum, mode, type, named) function_arg_pass_by_reference (cum, mode, type, named)
CUMULATIVE_ARGS *cum ATTRIBUTE_UNUSED; const CUMULATIVE_ARGS *cum;
enum machine_mode mode; enum machine_mode mode;
tree type; tree type;
int named ATTRIBUTE_UNUSED; int named ATTRIBUTE_UNUSED;
...@@ -8103,16 +8046,9 @@ function_arg_pass_by_reference (cum, mode, type, named) ...@@ -8103,16 +8046,9 @@ function_arg_pass_by_reference (cum, mode, type, named)
/* ??? cum can be NULL when called from mips_va_arg. The problem handled /* ??? cum can be NULL when called from mips_va_arg. The problem handled
here hopefully is not relevant to mips_va_arg. */ here hopefully is not relevant to mips_va_arg. */
if (cum && MUST_PASS_IN_STACK (mode, type) if (cum && MUST_PASS_IN_STACK (mode, type)
&& mips_abi != ABI_MEABI) && mips_abi != ABI_MEABI
{ && FUNCTION_ARG (*cum, mode, type, named) != 0)
/* Don't pass the actual CUM to FUNCTION_ARG, because we would
get double copies of any offsets generated for small structs
passed in registers. */
CUMULATIVE_ARGS temp;
temp = *cum;
if (FUNCTION_ARG (temp, mode, type, named) != 0)
return 1; return 1;
}
/* Otherwise, we only do this if EABI is selected. */ /* Otherwise, we only do this if EABI is selected. */
if (mips_abi != ABI_EABI) if (mips_abi != ABI_EABI)
......
...@@ -1595,7 +1595,10 @@ do { \ ...@@ -1595,7 +1595,10 @@ do { \
#define FP_INC (TARGET_FLOAT64 || TARGET_SINGLE_FLOAT ? 1 : 2) #define FP_INC (TARGET_FLOAT64 || TARGET_SINGLE_FLOAT ? 1 : 2)
/* The largest size of value that can be held in floating-point registers. */ /* The largest size of value that can be held in floating-point registers. */
#define UNITS_PER_FPVALUE (FP_INC * UNITS_PER_FPREG) #define UNITS_PER_FPVALUE (TARGET_SOFT_FLOAT ? 0 : FP_INC * UNITS_PER_FPREG)
/* The number of bytes in a double. */
#define UNITS_PER_DOUBLE (TYPE_PRECISION (double_type_node) / BITS_PER_UNIT)
/* A C expression for the size in bits of the type `int' on the /* A C expression for the size in bits of the type `int' on the
target machine. If you don't define this, the default is one target machine. If you don't define this, the default is one
...@@ -2738,6 +2741,10 @@ extern struct mips_frame_info current_frame_info; ...@@ -2738,6 +2741,10 @@ extern struct mips_frame_info current_frame_info;
#define RETURN_IN_MEMORY(TYPE) \ #define RETURN_IN_MEMORY(TYPE) \
mips_return_in_memory (TYPE) mips_return_in_memory (TYPE)
#define SETUP_INCOMING_VARARGS(CUM,MODE,TYPE,PRETEND_SIZE,NO_RTL) \
(PRETEND_SIZE) = mips_setup_incoming_varargs (&(CUM), (MODE), \
(TYPE), (NO_RTL))
#define TARGET_FLOAT_FORMAT IEEE_FLOAT_FORMAT #define TARGET_FLOAT_FORMAT IEEE_FLOAT_FORMAT
...@@ -2748,7 +2755,52 @@ extern struct mips_frame_info current_frame_info; ...@@ -2748,7 +2755,52 @@ extern struct mips_frame_info current_frame_info;
and about the args processed so far, enough to enable macros and about the args processed so far, enough to enable macros
such as FUNCTION_ARG to determine where the next arg should go. such as FUNCTION_ARG to determine where the next arg should go.
On the mips16, we need to keep track of which floating point This structure has to cope with two different argument allocation
schemes. Most MIPS ABIs view the arguments as a struct, of which the
first N words go in registers and the rest go on the stack. If I < N,
the Ith word might go in Ith integer argument register or the
Ith floating-point one. In some cases, it has to go in both (see
function_arg). For these ABIs, we only need to remember the number
of words passed so far.
The EABI instead allocates the integer and floating-point arguments
separately. The first N words of FP arguments go in FP registers,
the rest go on the stack. Likewise, the first N words of the other
arguments go in integer registers, and the rest go on the stack. We
need to maintain three counts: the number of integer registers used,
the number of floating-point registers used, and the number of words
passed on the stack.
We could keep separate information for the two ABIs (a word count for
the standard ABIs, and three separate counts for the EABI). But it
seems simpler to view the standard ABIs as forms of EABI that do not
allocate floating-point registers.
So for the standard ABIs, the first N words are allocated to integer
registers, and function_arg decides on an argument-by-argument basis
whether that argument should really go in an integer register, or in
a floating-point one. */
typedef struct mips_args {
/* Always true for varargs functions. Otherwise true if at least
one argument has been passed in an integer register. */
int gp_reg_found;
/* The number of arguments seen so far. */
unsigned int arg_number;
/* For EABI, the number of integer registers used so far. For other
ABIs, the number of words passed in registers (whether integer
or floating-point). */
unsigned int gp_regs;
/* For EABI, the number of floating-point registers used so far. */
unsigned int fp_regs;
/* The number of words passed on the stack. */
unsigned int stack_words;
/* On the mips16, we need to keep track of which floating point
arguments were passed in general registers, but would have been arguments were passed in general registers, but would have been
passed in the FP regs if this were a 32 bit function, so that we passed in the FP regs if this were a 32 bit function, so that we
can move them to the FP regs if we wind up calling a 32 bit can move them to the FP regs if we wind up calling a 32 bit
...@@ -2759,21 +2811,19 @@ extern struct mips_frame_info current_frame_info; ...@@ -2759,21 +2811,19 @@ extern struct mips_frame_info current_frame_info;
argument. Thus 6 == 1 * 4 + 2 means a DFmode argument followed by argument. Thus 6 == 1 * 4 + 2 means a DFmode argument followed by
an SFmode argument. ??? A more sophisticated approach will be an SFmode argument. ??? A more sophisticated approach will be
needed if MIPS_ABI != ABI_32. */ needed if MIPS_ABI != ABI_32. */
int fp_code;
typedef struct mips_args {
int gp_reg_found; /* whether a gp register was found yet */ /* True if the function has a prototype. */
unsigned int arg_number; /* argument number */ int prototype;
unsigned int arg_words; /* # total words the arguments take */
unsigned int fp_arg_words; /* # words for FP args (MIPS_EABI only) */ /* When a structure does not take up a full register, the argument
int last_arg_fp; /* nonzero if last arg was FP (EABI only) */ should sometimes be shifted left so that it occupies the high part
int fp_code; /* Mode of FP arguments (mips16) */ of the register. These two fields describe an array of ashl
unsigned int num_adjusts; /* number of adjustments made */ patterns for doing this. See function_arg_advance, which creates
/* Adjustments made to args pass in regs. */ the shift patterns, and function_arg, which returns them when given
/* ??? The size is doubled to work around a a VOIDmode argument. */
bug in the code that sets the adjustments unsigned int num_adjusts;
in function_arg. */ struct rtx_def *adjust[MAX_ARGS_IN_REGISTERS];
int prototype; /* True if the function has a prototype. */
struct rtx_def *adjust[MAX_ARGS_IN_REGISTERS*2];
} CUMULATIVE_ARGS; } CUMULATIVE_ARGS;
/* Initialize a variable CUM of type CUMULATIVE_ARGS /* Initialize a variable CUM of type CUMULATIVE_ARGS
...@@ -2828,6 +2878,12 @@ typedef struct mips_args { ...@@ -2828,6 +2878,12 @@ typedef struct mips_args {
? PARM_BOUNDARY \ ? PARM_BOUNDARY \
: GET_MODE_ALIGNMENT(MODE))) : GET_MODE_ALIGNMENT(MODE)))
/* True if using EABI and varargs can be passed in floating-point
registers. Under these conditions, we need a more complex form
of va_list, which tracks GPR, FPR and stack arguments separately. */
#define EABI_FLOAT_VARARGS_P \
(mips_abi == ABI_EABI && UNITS_PER_FPVALUE >= UNITS_PER_DOUBLE)
/* Tell prologue and epilogue if register REGNO should be saved / restored. */ /* Tell prologue and epilogue if register REGNO should be saved / restored. */
......
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