2014 lines
53 KiB
C
2014 lines
53 KiB
C
/* Output routines for GCC for ARM/Thumb
|
||
Copyright (C) 1996 Cygnus Software Technologies Ltd
|
||
The basis of this contribution was generated by
|
||
Richard Earnshaw, Advanced RISC Machines Ltd
|
||
|
||
This file is part of GNU CC.
|
||
|
||
GNU CC is free software; you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation; either version 2, or (at your option)
|
||
any later version.
|
||
|
||
GNU CC is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with GNU CC; see the file COPYING. If not, write to
|
||
the Free Software Foundation, 59 Temple Place - Suite 330,
|
||
Boston, MA 02111-1307, USA. */
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include "config.h"
|
||
#include "rtl.h"
|
||
#include "hard-reg-set.h"
|
||
#include "regs.h"
|
||
#include "output.h"
|
||
#include "insn-flags.h"
|
||
#include "insn-attr.h"
|
||
#include "flags.h"
|
||
#include "tree.h"
|
||
#include "expr.h"
|
||
|
||
|
||
int current_function_anonymous_args = 0;
|
||
|
||
/* Used to parse -mstructure_size_boundary command line option. */
|
||
char * structure_size_string = NULL;
|
||
int arm_structure_size_boundary = 32; /* Used to be 8 */
|
||
|
||
|
||
/* Predicates */
|
||
int
|
||
reload_memory_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
int regno = true_regnum (op);
|
||
|
||
return (! CONSTANT_P (op)
|
||
&& (regno == -1
|
||
|| (GET_CODE (op) == REG
|
||
&& REGNO (op) >= FIRST_PSEUDO_REGISTER)));
|
||
}
|
||
|
||
/* Return nonzero if op is suitable for the RHS of a cmp instruction. */
|
||
int
|
||
thumb_cmp_operand (op, mode)
|
||
rtx op;
|
||
enum machine_mode mode;
|
||
{
|
||
return ((GET_CODE (op) == CONST_INT
|
||
&& (unsigned HOST_WIDE_INT) (INTVAL (op)) < 256)
|
||
|| register_operand (op, mode));
|
||
}
|
||
|
||
int
|
||
thumb_shiftable_const (val)
|
||
HOST_WIDE_INT val;
|
||
{
|
||
unsigned HOST_WIDE_INT x = val;
|
||
unsigned HOST_WIDE_INT mask = 0xff;
|
||
int i;
|
||
|
||
for (i = 0; i < 25; i++)
|
||
if ((val & (mask << i)) == val)
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
int
|
||
thumb_trivial_epilogue ()
|
||
{
|
||
int regno;
|
||
|
||
/* ??? If this function ever returns 1, we get a function without any
|
||
epilogue at all. It appears that the intent was to cause a "return"
|
||
insn to be emitted, but that does not happen. */
|
||
return 0;
|
||
|
||
#if 0
|
||
if (get_frame_size ()
|
||
|| current_function_outgoing_args_size
|
||
|| current_function_pretend_args_size)
|
||
return 0;
|
||
|
||
for (regno = 8; regno < 13; regno++)
|
||
if (regs_ever_live[regno] && ! call_used_regs[regno])
|
||
return 0;
|
||
|
||
return 1;
|
||
#endif
|
||
}
|
||
|
||
|
||
/* Routines for handling the constant pool */
|
||
/* This is unashamedly hacked from the version in sh.c, since the problem is
|
||
extremely similar. */
|
||
|
||
/* Thumb instructions cannot load a large constant into a register,
|
||
constants have to come from a pc relative load. The reference of a pc
|
||
relative load instruction must be less than 1k infront of the instruction.
|
||
This means that we often have to dump a constant inside a function, and
|
||
generate code to branch around it.
|
||
|
||
It is important to minimize this, since the branches will slow things
|
||
down and make things bigger.
|
||
|
||
Worst case code looks like:
|
||
|
||
ldr rn, L1
|
||
b L2
|
||
align
|
||
L1: .long value
|
||
L2:
|
||
..
|
||
|
||
ldr rn, L3
|
||
b L4
|
||
align
|
||
L3: .long value
|
||
L4:
|
||
..
|
||
|
||
We fix this by performing a scan before scheduling, which notices which
|
||
instructions need to have their operands fetched from the constant table
|
||
and builds the table.
|
||
|
||
|
||
The algorithm is:
|
||
|
||
scan, find an instruction which needs a pcrel move. Look forward, find the
|
||
last barrier which is within MAX_COUNT bytes of the requirement.
|
||
If there isn't one, make one. Process all the instructions between
|
||
the find and the barrier.
|
||
|
||
In the above example, we can tell that L3 is within 1k of L1, so
|
||
the first move can be shrunk from the 2 insn+constant sequence into
|
||
just 1 insn, and the constant moved to L3 to make:
|
||
|
||
ldr rn, L1
|
||
..
|
||
ldr rn, L3
|
||
b L4
|
||
align
|
||
L1: .long value
|
||
L3: .long value
|
||
L4:
|
||
|
||
Then the second move becomes the target for the shortening process.
|
||
|
||
*/
|
||
|
||
typedef struct
|
||
{
|
||
rtx value; /* Value in table */
|
||
HOST_WIDE_INT next_offset;
|
||
enum machine_mode mode; /* Mode of value */
|
||
} pool_node;
|
||
|
||
/* The maximum number of constants that can fit into one pool, since
|
||
the pc relative range is 0...1020 bytes and constants are at least 4
|
||
bytes long */
|
||
|
||
#define MAX_POOL_SIZE (1020/4)
|
||
static pool_node pool_vector[MAX_POOL_SIZE];
|
||
static int pool_size;
|
||
static rtx pool_vector_label;
|
||
|
||
/* Add a constant to the pool and return its label. */
|
||
|
||
static HOST_WIDE_INT
|
||
add_constant (x, mode)
|
||
rtx x;
|
||
enum machine_mode mode;
|
||
{
|
||
int i;
|
||
rtx lab;
|
||
HOST_WIDE_INT offset;
|
||
|
||
if (mode == SImode && GET_CODE (x) == MEM && CONSTANT_P (XEXP (x, 0))
|
||
&& CONSTANT_POOL_ADDRESS_P (XEXP (x, 0)))
|
||
x = get_pool_constant (XEXP (x, 0));
|
||
|
||
/* First see if we've already got it */
|
||
|
||
for (i = 0; i < pool_size; i++)
|
||
{
|
||
if (x->code == pool_vector[i].value->code
|
||
&& mode == pool_vector[i].mode)
|
||
{
|
||
if (x->code == CODE_LABEL)
|
||
{
|
||
if (XINT (x, 3) != XINT (pool_vector[i].value, 3))
|
||
continue;
|
||
}
|
||
if (rtx_equal_p (x, pool_vector[i].value))
|
||
return pool_vector[i].next_offset - GET_MODE_SIZE (mode);
|
||
}
|
||
}
|
||
|
||
/* Need a new one */
|
||
|
||
pool_vector[pool_size].next_offset = GET_MODE_SIZE (mode);
|
||
offset = 0;
|
||
if (pool_size == 0)
|
||
pool_vector_label = gen_label_rtx ();
|
||
else
|
||
pool_vector[pool_size].next_offset
|
||
+= (offset = pool_vector[pool_size - 1].next_offset);
|
||
|
||
pool_vector[pool_size].value = x;
|
||
pool_vector[pool_size].mode = mode;
|
||
pool_size++;
|
||
return offset;
|
||
}
|
||
|
||
/* Output the literal table */
|
||
|
||
static void
|
||
dump_table (scan)
|
||
rtx scan;
|
||
{
|
||
int i;
|
||
|
||
scan = emit_label_after (gen_label_rtx (), scan);
|
||
scan = emit_insn_after (gen_align_4 (), scan);
|
||
scan = emit_label_after (pool_vector_label, scan);
|
||
|
||
for (i = 0; i < pool_size; i++)
|
||
{
|
||
pool_node *p = pool_vector + i;
|
||
|
||
switch (GET_MODE_SIZE (p->mode))
|
||
{
|
||
case 4:
|
||
scan = emit_insn_after (gen_consttable_4 (p->value), scan);
|
||
break;
|
||
|
||
case 8:
|
||
scan = emit_insn_after (gen_consttable_8 (p->value), scan);
|
||
break;
|
||
|
||
default:
|
||
abort ();
|
||
break;
|
||
}
|
||
}
|
||
|
||
scan = emit_insn_after (gen_consttable_end (), scan);
|
||
scan = emit_barrier_after (scan);
|
||
pool_size = 0;
|
||
}
|
||
|
||
/* Non zero if the src operand needs to be fixed up */
|
||
static
|
||
int
|
||
fixit (src, mode)
|
||
rtx src;
|
||
enum machine_mode mode;
|
||
{
|
||
return ((CONSTANT_P (src)
|
||
&& (GET_CODE (src) != CONST_INT
|
||
|| ! (CONST_OK_FOR_LETTER_P (INTVAL (src), 'I')
|
||
|| CONST_OK_FOR_LETTER_P (INTVAL (src), 'J')
|
||
|| (mode != DImode
|
||
&& CONST_OK_FOR_LETTER_P (INTVAL (src), 'K')))))
|
||
|| (mode == SImode && GET_CODE (src) == MEM
|
||
&& GET_CODE (XEXP (src, 0)) == SYMBOL_REF
|
||
&& CONSTANT_POOL_ADDRESS_P (XEXP (src, 0))));
|
||
}
|
||
|
||
/* Find the last barrier less than MAX_COUNT bytes from FROM, or create one. */
|
||
|
||
#define MAX_COUNT_SI 1000
|
||
|
||
static rtx
|
||
find_barrier (from)
|
||
rtx from;
|
||
{
|
||
int count = 0;
|
||
rtx found_barrier = 0;
|
||
rtx label;
|
||
|
||
while (from && count < MAX_COUNT_SI)
|
||
{
|
||
if (GET_CODE (from) == BARRIER)
|
||
return from;
|
||
|
||
/* Count the length of this insn */
|
||
if (GET_CODE (from) == INSN
|
||
&& GET_CODE (PATTERN (from)) == SET
|
||
&& CONSTANT_P (SET_SRC (PATTERN (from)))
|
||
&& CONSTANT_POOL_ADDRESS_P (SET_SRC (PATTERN (from))))
|
||
{
|
||
rtx src = SET_SRC (PATTERN (from));
|
||
count += 2;
|
||
}
|
||
else
|
||
count += get_attr_length (from);
|
||
|
||
from = NEXT_INSN (from);
|
||
}
|
||
|
||
/* We didn't find a barrier in time to
|
||
dump our stuff, so we'll make one */
|
||
label = gen_label_rtx ();
|
||
|
||
if (from)
|
||
from = PREV_INSN (from);
|
||
else
|
||
from = get_last_insn ();
|
||
|
||
/* Walk back to be just before any jump */
|
||
while (GET_CODE (from) == JUMP_INSN
|
||
|| GET_CODE (from) == NOTE
|
||
|| GET_CODE (from) == CODE_LABEL)
|
||
from = PREV_INSN (from);
|
||
|
||
from = emit_jump_insn_after (gen_jump (label), from);
|
||
JUMP_LABEL (from) = label;
|
||
found_barrier = emit_barrier_after (from);
|
||
emit_label_after (label, found_barrier);
|
||
return found_barrier;
|
||
}
|
||
|
||
/* Non zero if the insn is a move instruction which needs to be fixed. */
|
||
|
||
static int
|
||
broken_move (insn)
|
||
rtx insn;
|
||
{
|
||
if (!INSN_DELETED_P (insn)
|
||
&& GET_CODE (insn) == INSN
|
||
&& GET_CODE (PATTERN (insn)) == SET)
|
||
{
|
||
rtx pat = PATTERN (insn);
|
||
rtx src = SET_SRC (pat);
|
||
rtx dst = SET_DEST (pat);
|
||
enum machine_mode mode = GET_MODE (dst);
|
||
if (dst == pc_rtx)
|
||
return 0;
|
||
return fixit (src, mode);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
#ifdef DBX_DEBUGGING_INFO
|
||
|
||
/* Recursively search through all of the blocks in a function
|
||
checking to see if any of the variables created in that
|
||
function match the RTX called 'orig'. If they do then
|
||
replace them with the RTX called 'new'. */
|
||
|
||
static void
|
||
replace_symbols_in_block (tree block, rtx orig, rtx new)
|
||
{
|
||
for (; block; block = BLOCK_CHAIN (block))
|
||
{
|
||
tree sym;
|
||
|
||
if (! TREE_USED (block))
|
||
continue;
|
||
|
||
for (sym = BLOCK_VARS (block); sym; sym = TREE_CHAIN (sym))
|
||
{
|
||
if ( (DECL_NAME (sym) == 0 && TREE_CODE (sym) != TYPE_DECL)
|
||
|| DECL_IGNORED_P (sym)
|
||
|| TREE_CODE (sym) != VAR_DECL
|
||
|| DECL_EXTERNAL (sym)
|
||
|| ! rtx_equal_p (DECL_RTL (sym), orig)
|
||
)
|
||
continue;
|
||
|
||
DECL_RTL (sym) = new;
|
||
}
|
||
|
||
replace_symbols_in_block (BLOCK_SUBBLOCKS (block), orig, new);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
void
|
||
thumb_reorg (first)
|
||
rtx first;
|
||
{
|
||
rtx insn;
|
||
for (insn = first; insn; insn = NEXT_INSN (insn))
|
||
{
|
||
if (broken_move (insn))
|
||
{
|
||
/* This is a broken move instruction, scan ahead looking for
|
||
a barrier to stick the constant table behind */
|
||
rtx scan;
|
||
rtx barrier = find_barrier (insn);
|
||
|
||
/* Now find all the moves between the points and modify them */
|
||
for (scan = insn; scan != barrier; scan = NEXT_INSN (scan))
|
||
{
|
||
if (broken_move (scan))
|
||
{
|
||
/* This is a broken move instruction, add it to the pool */
|
||
rtx pat = PATTERN (scan);
|
||
rtx src = SET_SRC (pat);
|
||
rtx dst = SET_DEST (pat);
|
||
enum machine_mode mode = GET_MODE (dst);
|
||
HOST_WIDE_INT offset;
|
||
rtx newinsn;
|
||
rtx newsrc;
|
||
|
||
/* If this is an HImode constant load, convert it into
|
||
an SImode constant load. Since the register is always
|
||
32 bits this is safe. We have to do this, since the
|
||
load pc-relative instruction only does a 32-bit load. */
|
||
if (mode == HImode)
|
||
{
|
||
mode = SImode;
|
||
if (GET_CODE (dst) != REG)
|
||
abort ();
|
||
PUT_MODE (dst, SImode);
|
||
}
|
||
|
||
offset = add_constant (src, mode);
|
||
newsrc = gen_rtx (MEM, mode,
|
||
plus_constant (gen_rtx (LABEL_REF,
|
||
VOIDmode,
|
||
pool_vector_label),
|
||
offset));
|
||
|
||
/* Build a jump insn wrapper around the move instead
|
||
of an ordinary insn, because we want to have room for
|
||
the target label rtx in fld[7], which an ordinary
|
||
insn doesn't have. */
|
||
newinsn = emit_jump_insn_after (gen_rtx (SET, VOIDmode,
|
||
dst, newsrc), scan);
|
||
JUMP_LABEL (newinsn) = pool_vector_label;
|
||
|
||
/* But it's still an ordinary insn */
|
||
PUT_CODE (newinsn, INSN);
|
||
|
||
#ifdef DBX_DEBUGGING_INFO
|
||
/* If debugging information is going to be emitted then we must
|
||
make sure that any refences to symbols which are removed by
|
||
the above code are also removed in the descriptions of the
|
||
function's variables. Failure to do this means that the
|
||
debugging information emitted could refer to symbols which
|
||
are not emited by output_constant_pool() because
|
||
mark_constant_pool() never sees them as being used. */
|
||
|
||
if (optimize > 0 /* These are the tests used in output_constant_pool() */
|
||
&& flag_expensive_optimizations /* to decide if the constant pool will be marked. */
|
||
&& write_symbols == DBX_DEBUG /* Only necessary if debugging info is being emitted. */
|
||
&& GET_CODE (src) == MEM /* Only necessary for references to memory ... */
|
||
&& GET_CODE (XEXP (src, 0)) == SYMBOL_REF) /* ... whose address is given by a symbol. */
|
||
{
|
||
replace_symbols_in_block (DECL_INITIAL (current_function_decl), src, newsrc);
|
||
}
|
||
#endif
|
||
|
||
/* Kill old insn */
|
||
delete_insn (scan);
|
||
scan = newinsn;
|
||
}
|
||
}
|
||
dump_table (barrier);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* Routines for generating rtl */
|
||
|
||
void
|
||
thumb_expand_movstrqi (operands)
|
||
rtx *operands;
|
||
{
|
||
rtx out = copy_to_mode_reg (SImode, XEXP (operands[0], 0));
|
||
rtx in = copy_to_mode_reg (SImode, XEXP (operands[1], 0));
|
||
HOST_WIDE_INT len = INTVAL (operands[2]);
|
||
HOST_WIDE_INT offset = 0;
|
||
|
||
while (len >= 12)
|
||
{
|
||
emit_insn (gen_movmem12b (out, in));
|
||
len -= 12;
|
||
}
|
||
if (len >= 8)
|
||
{
|
||
emit_insn (gen_movmem8b (out, in));
|
||
len -= 8;
|
||
}
|
||
if (len >= 4)
|
||
{
|
||
rtx reg = gen_reg_rtx (SImode);
|
||
emit_insn (gen_movsi (reg, gen_rtx (MEM, SImode, in)));
|
||
emit_insn (gen_movsi (gen_rtx (MEM, SImode, out), reg));
|
||
len -= 4;
|
||
offset += 4;
|
||
}
|
||
if (len >= 2)
|
||
{
|
||
rtx reg = gen_reg_rtx (HImode);
|
||
emit_insn (gen_movhi (reg, gen_rtx (MEM, HImode,
|
||
plus_constant (in, offset))));
|
||
emit_insn (gen_movhi (gen_rtx (MEM, HImode, plus_constant (out, offset)),
|
||
reg));
|
||
len -= 2;
|
||
offset += 2;
|
||
}
|
||
if (len)
|
||
{
|
||
rtx reg = gen_reg_rtx (QImode);
|
||
emit_insn (gen_movqi (reg, gen_rtx (MEM, QImode,
|
||
plus_constant (in, offset))));
|
||
emit_insn (gen_movqi (gen_rtx (MEM, QImode, plus_constant (out, offset)),
|
||
reg));
|
||
}
|
||
}
|
||
|
||
|
||
/* Routines for reloading */
|
||
|
||
void
|
||
thumb_reload_out_si (operands)
|
||
rtx operands;
|
||
{
|
||
abort ();
|
||
}
|
||
|
||
/* Return non-zero if FUNC must be entered in ARM mode. */
|
||
int
|
||
is_called_in_ARM_mode (func)
|
||
tree func;
|
||
{
|
||
if (TREE_CODE (func) != FUNCTION_DECL)
|
||
abort ();
|
||
|
||
/* Ignore the problem about functions whoes address is taken. */
|
||
if (TARGET_CALLEE_INTERWORKING && TREE_PUBLIC (func))
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
/* Routines for emitting code */
|
||
|
||
void
|
||
final_prescan_insn(insn)
|
||
rtx insn;
|
||
{
|
||
extern int *insn_addresses;
|
||
|
||
if (flag_print_asm_name)
|
||
fprintf (asm_out_file, "%s 0x%04x\n", ASM_COMMENT_START,
|
||
insn_addresses[INSN_UID (insn)]);
|
||
}
|
||
|
||
|
||
static void thumb_pushpop ( FILE *, int, int ); /* Forward declaration. */
|
||
|
||
#ifdef __GNUC__
|
||
inline
|
||
#endif
|
||
static int
|
||
number_of_first_bit_set (mask)
|
||
int mask;
|
||
{
|
||
int bit;
|
||
|
||
for (bit = 0;
|
||
(mask & (1 << bit)) == 0;
|
||
++ bit)
|
||
continue;
|
||
|
||
return bit;
|
||
}
|
||
|
||
#define ARG_1_REGISTER 0
|
||
#define ARG_2_REGISTER 1
|
||
#define ARG_3_REGISTER 2
|
||
#define ARG_4_REGISTER 3
|
||
#define WORK_REGISTER 7
|
||
#define FRAME_POINTER 11
|
||
#define IP_REGISTER 12
|
||
#define STACK_POINTER STACK_POINTER_REGNUM
|
||
#define LINK_REGISTER 14
|
||
#define PROGRAM_COUNTER 15
|
||
|
||
/* Generate code to return from a thumb function. If
|
||
'reg_containing_return_addr' is -1, then the return address is
|
||
actually on the stack, at the stack pointer. */
|
||
|
||
static void
|
||
thumb_exit (f, reg_containing_return_addr)
|
||
FILE * f;
|
||
int reg_containing_return_addr;
|
||
{
|
||
int regs_available_for_popping;
|
||
int regs_to_pop;
|
||
int pops_needed;
|
||
int reg;
|
||
int available;
|
||
int required;
|
||
int mode;
|
||
int size;
|
||
int restore_a4 = FALSE;
|
||
|
||
/* Compute the registers we need to pop. */
|
||
|
||
regs_to_pop = 0;
|
||
pops_needed = 0;
|
||
|
||
if (reg_containing_return_addr == -1)
|
||
{
|
||
regs_to_pop |= 1 << LINK_REGISTER;
|
||
++ pops_needed;
|
||
}
|
||
|
||
if (TARGET_BACKTRACE)
|
||
{
|
||
/* Restore frame pointer and stack pointer. */
|
||
|
||
regs_to_pop |= (1 << FRAME_POINTER) | (1 << STACK_POINTER);
|
||
pops_needed += 2;
|
||
}
|
||
|
||
/* If there is nothing to pop then just emit the BX instruction and return. */
|
||
|
||
if (pops_needed == 0)
|
||
{
|
||
asm_fprintf (f, "\tbx\t%s\n", reg_names[ reg_containing_return_addr ]);
|
||
|
||
return;
|
||
}
|
||
|
||
/* Otherwise if we are not supporting interworking and we have not created
|
||
a backtrace structure and the function was not entered in ARM mode then
|
||
just pop the return address straight into the PC. */
|
||
|
||
else if ( ! TARGET_THUMB_INTERWORK
|
||
&& ! TARGET_BACKTRACE
|
||
&& ! is_called_in_ARM_mode (current_function_decl))
|
||
{
|
||
asm_fprintf (f, "\tpop\t{pc}\n" );
|
||
|
||
return;
|
||
}
|
||
|
||
/* Find out how many of the (return) argument registers we can corrupt. */
|
||
|
||
regs_available_for_popping = 0;
|
||
|
||
#ifdef RTX_CODE
|
||
/* If we can deduce the registers used from the function's return value.
|
||
This is more reliable that examining regs_ever_live[] because that
|
||
will be set if the register is ever used in the function, not just if
|
||
the register is used to hold a return value. */
|
||
|
||
if (current_function_return_rtx != 0)
|
||
{
|
||
mode = GET_MODE (current_function_return_rtx);
|
||
}
|
||
else
|
||
#endif
|
||
{
|
||
mode = DECL_MODE (DECL_RESULT (current_function_decl));
|
||
}
|
||
|
||
size = GET_MODE_SIZE (mode);
|
||
|
||
if (size == 0)
|
||
{
|
||
/* In a void function we can use any argument register.
|
||
In a function that returns a structure on the stack
|
||
we can use the second and third argument registers. */
|
||
|
||
if (mode == VOIDmode)
|
||
regs_available_for_popping = (1 << ARG_1_REGISTER) | (1 << ARG_2_REGISTER) | (1 << ARG_3_REGISTER);
|
||
else
|
||
regs_available_for_popping = (1 << ARG_2_REGISTER) | (1 << ARG_3_REGISTER);
|
||
}
|
||
else if (size <= 4) regs_available_for_popping = (1 << ARG_2_REGISTER) | (1 << ARG_3_REGISTER);
|
||
else if (size <= 8) regs_available_for_popping = (1 << ARG_3_REGISTER);
|
||
|
||
/* Match registers to be popped with registers into which we pop them. */
|
||
|
||
for (available = regs_available_for_popping,
|
||
required = regs_to_pop;
|
||
required != 0 && available != 0;
|
||
available &= ~(available & - available),
|
||
required &= ~(required & - required))
|
||
-- pops_needed;
|
||
|
||
/* If we have any popping registers left over, remove them. */
|
||
|
||
if (available > 0)
|
||
regs_available_for_popping &= ~ available;
|
||
|
||
/* Otherwise if we need another popping register we can use
|
||
the fourth argument register. */
|
||
|
||
else if (pops_needed)
|
||
{
|
||
/* If we have not found any free argument registers and
|
||
reg a4 contains the return address, we must move it. */
|
||
|
||
if (regs_available_for_popping == 0 && reg_containing_return_addr == ARG_4_REGISTER)
|
||
{
|
||
asm_fprintf (f, "\tmov\t%s, %s\n",
|
||
reg_names[ LINK_REGISTER ],
|
||
reg_names[ ARG_4_REGISTER ]);
|
||
reg_containing_return_addr = LINK_REGISTER;
|
||
}
|
||
else if (size > 12)
|
||
{
|
||
/* Register a4 is being used to hold part of the return value,
|
||
but we have dire need of a free, low register. */
|
||
|
||
restore_a4 = TRUE;
|
||
|
||
asm_fprintf (f, "\tmov\t%s, %s\n",
|
||
reg_names[ IP_REGISTER ],
|
||
reg_names[ ARG_4_REGISTER ]);
|
||
}
|
||
|
||
if (reg_containing_return_addr != ARG_4_REGISTER)
|
||
{
|
||
/* The fourth argument register is available. */
|
||
|
||
regs_available_for_popping |= 1 << ARG_4_REGISTER;
|
||
|
||
-- pops_needed;
|
||
}
|
||
}
|
||
|
||
/* Pop as many registers as we can. */
|
||
|
||
thumb_pushpop (f, regs_available_for_popping, FALSE );
|
||
|
||
/* Process the registers we popped. */
|
||
|
||
if (reg_containing_return_addr == -1)
|
||
{
|
||
/* The return address was popped into the lowest numbered register. */
|
||
|
||
regs_to_pop &= ~ (1 << LINK_REGISTER);
|
||
|
||
reg_containing_return_addr = number_of_first_bit_set (regs_available_for_popping);
|
||
|
||
/* Remove this register for the mask of available registers, so that
|
||
the return address will not be corrupted by futher pops. */
|
||
|
||
regs_available_for_popping &= ~ (1 << reg_containing_return_addr);
|
||
}
|
||
|
||
/* If we popped other registers then handle them here. */
|
||
|
||
if (regs_available_for_popping)
|
||
{
|
||
int frame_pointer;
|
||
|
||
/* Work out which register currently contains the frame pointer. */
|
||
|
||
frame_pointer = number_of_first_bit_set (regs_available_for_popping);
|
||
|
||
/* Move it into the correct place. */
|
||
|
||
asm_fprintf (f, "\tmov\tfp, %s\n", reg_names[ frame_pointer ]);
|
||
|
||
/* (Temporarily) remove it from the mask of popped registers. */
|
||
|
||
regs_available_for_popping &= ~ (1 << frame_pointer);
|
||
regs_to_pop &= ~ (1 << FRAME_POINTER);
|
||
|
||
if (regs_available_for_popping)
|
||
{
|
||
int stack_pointer;
|
||
|
||
/* We popped the stack pointer as well, find the register that contains it. */
|
||
|
||
stack_pointer = number_of_first_bit_set (regs_available_for_popping);
|
||
|
||
/* Move it into the stack register. */
|
||
|
||
asm_fprintf (f, "\tmov\tsp, %s\n", reg_names[ stack_pointer ]);
|
||
|
||
/* At this point we have popped all necessary registers, so
|
||
do not worry about restoring regs_available_for_popping
|
||
to its correct value:
|
||
|
||
assert (pops_needed == 0)
|
||
assert (regs_available_for_popping == (1 << stack_frame_pointer))
|
||
assert (regs_to_pop == (1 << STACK_POINTER)) */
|
||
}
|
||
else
|
||
{
|
||
/* Since we have just move the popped value into the frame
|
||
pointer, the popping register is available for reuse, and
|
||
we know that we still have the stack pointer left to pop. */
|
||
|
||
regs_available_for_popping |= (1 << frame_pointer);
|
||
}
|
||
}
|
||
|
||
/* If we still have registers left on the stack, but we no longer
|
||
have any registers into which we can pop them, then we must
|
||
move the return address into the link register and make
|
||
available the register that contained it. */
|
||
|
||
if (regs_available_for_popping == 0 && pops_needed > 0)
|
||
{
|
||
regs_available_for_popping |= 1 << reg_containing_return_addr;
|
||
|
||
asm_fprintf (f, "\tmov\t%s, %s\n",
|
||
reg_names[ LINK_REGISTER ],
|
||
reg_names[ reg_containing_return_addr ]);
|
||
|
||
reg_containing_return_addr = LINK_REGISTER;
|
||
}
|
||
|
||
/* If we have registers left on the stack then pop some more.
|
||
We know that we will only be popping one register here for
|
||
the following reasons:
|
||
|
||
1. We know that at most we want to pop LR, FP and SP.
|
||
2. We have already popped at least one register.
|
||
3. If there were 3 registers available for popping then
|
||
we have already popped all three of the registers.
|
||
4. If there were 2 registers available for popping then
|
||
we have already popped LR and FP, so there can only
|
||
be one register left on the stack: SP. And since we
|
||
had two registers available for popping we will have
|
||
left the LR in one of those registers and leaving
|
||
only one register left for popping the SP.
|
||
5. If there was only 1 register available for popping
|
||
then we can only be popping one register here. */
|
||
|
||
if (pops_needed > 0)
|
||
{
|
||
int popped_into;
|
||
int move_to;
|
||
|
||
thumb_pushpop (f, regs_available_for_popping, FALSE);
|
||
|
||
/* We have popped either FP or SP. Move whichever one
|
||
it is into the correct register. */
|
||
|
||
popped_into = number_of_first_bit_set (regs_available_for_popping);
|
||
move_to = number_of_first_bit_set (regs_to_pop);
|
||
|
||
asm_fprintf (f, "\tmov\t%s, %s\n", reg_names[ move_to ], reg_names[ popped_into ]);
|
||
|
||
regs_to_pop &= ~ (1 << move_to);
|
||
|
||
-- pops_needed;
|
||
}
|
||
|
||
/* If we still have not popped everything then we must have
|
||
only had one register available to us and we are now
|
||
popping the SP. */
|
||
|
||
if (pops_needed > 0)
|
||
{
|
||
int popped_into;
|
||
|
||
thumb_pushpop (f, regs_available_for_popping, FALSE);
|
||
|
||
popped_into = number_of_first_bit_set (regs_available_for_popping);
|
||
|
||
asm_fprintf (f, "\tmov\tsp, %s\n", reg_names[ popped_into ]);
|
||
|
||
/*
|
||
assert (regs_to_pop == (1 << STACK_POINTER))
|
||
assert (pops_needed == 1)
|
||
*/
|
||
}
|
||
|
||
/* If necessary restore the a4 register. */
|
||
if (restore_a4)
|
||
{
|
||
asm_fprintf (f, "\tmov\t%s, %s\n", reg_names[ LINK_REGISTER ], reg_names[ ARG_4_REGISTER ]);
|
||
asm_fprintf (f, "\tmov\t%s, %s\n", reg_names[ ARG_4_REGISTER ], reg_names[ IP_REGISTER ]);
|
||
reg_containing_return_addr = LINK_REGISTER;
|
||
}
|
||
|
||
/* Return to caller. */
|
||
|
||
asm_fprintf (f, "\tbx\t%s\n", reg_names[ reg_containing_return_addr ]);
|
||
}
|
||
|
||
/* Emit code to push or pop registers to or from the stack. */
|
||
|
||
static void
|
||
thumb_pushpop (f, mask, push)
|
||
FILE *f;
|
||
int mask;
|
||
int push;
|
||
{
|
||
int regno;
|
||
int lo_mask = mask & 0xFF;
|
||
|
||
if (lo_mask == 0 && ! push && (mask & (1 << 15)))
|
||
{
|
||
/* Special case. Do not generate a POP PC statement here, do it in thumb_exit() */
|
||
|
||
thumb_exit (f, -1);
|
||
return;
|
||
}
|
||
|
||
asm_fprintf (f, "\t%s\t{", push ? "push" : "pop");
|
||
|
||
/* Look at the low registers first. */
|
||
|
||
for (regno = 0; regno < 8; regno ++, lo_mask >>= 1)
|
||
{
|
||
if (lo_mask & 1)
|
||
{
|
||
asm_fprintf (f, reg_names[regno]);
|
||
|
||
if ((lo_mask & ~1) != 0)
|
||
asm_fprintf (f, ", ");
|
||
}
|
||
}
|
||
|
||
if (push && (mask & (1 << 14)))
|
||
{
|
||
/* Catch pushing the LR. */
|
||
|
||
if (mask & 0xFF)
|
||
asm_fprintf (f, ", ");
|
||
|
||
asm_fprintf (f, reg_names[14]);
|
||
}
|
||
else if (!push && (mask & (1 << 15)))
|
||
{
|
||
/* Catch popping the PC. */
|
||
|
||
if (TARGET_THUMB_INTERWORK || TARGET_BACKTRACE)
|
||
{
|
||
/* The PC is never poped directly, instead
|
||
it is popped into r3 and then BX is used. */
|
||
|
||
asm_fprintf (f, "}\n");
|
||
|
||
thumb_exit (f, -1);
|
||
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
if (mask & 0xFF)
|
||
asm_fprintf (f, ", ");
|
||
|
||
asm_fprintf (f, reg_names[15]);
|
||
}
|
||
}
|
||
|
||
asm_fprintf (f, "}\n");
|
||
}
|
||
|
||
/* Returns non-zero if the current function contains a far jump */
|
||
|
||
int
|
||
far_jump_used_p (void)
|
||
{
|
||
rtx insn;
|
||
|
||
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
|
||
{
|
||
if (GET_CODE (insn) == JUMP_INSN
|
||
/* Ignore tablejump patterns. */
|
||
&& GET_CODE (PATTERN (insn)) != ADDR_VEC
|
||
&& GET_CODE (PATTERN (insn)) != ADDR_DIFF_VEC
|
||
&& get_attr_far_jump (insn) == FAR_JUMP_YES)
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int return_used_this_function = 0;
|
||
|
||
char *
|
||
output_return ()
|
||
{
|
||
int regno;
|
||
int live_regs_mask = 0;
|
||
|
||
return_used_this_function = 1;
|
||
|
||
for (regno = 0; regno < 8; regno++)
|
||
if (regs_ever_live[regno] && ! call_used_regs[regno])
|
||
live_regs_mask |= 1 << regno;
|
||
|
||
if (live_regs_mask == 0)
|
||
{
|
||
if (leaf_function_p () && ! far_jump_used_p())
|
||
{
|
||
thumb_exit (asm_out_file, 14);
|
||
}
|
||
else if ( TARGET_THUMB_INTERWORK
|
||
|| TARGET_BACKTRACE
|
||
|| is_called_in_ARM_mode (current_function_decl))
|
||
{
|
||
thumb_exit (asm_out_file, -1);
|
||
}
|
||
else
|
||
asm_fprintf (asm_out_file, "\tpop\t{pc}\n");
|
||
}
|
||
else
|
||
{
|
||
asm_fprintf (asm_out_file, "\tpop\t{");
|
||
|
||
for (regno = 0; live_regs_mask; regno ++, live_regs_mask >>= 1)
|
||
if (live_regs_mask & 1)
|
||
{
|
||
asm_fprintf (asm_out_file, reg_names[regno]);
|
||
if (live_regs_mask & ~1)
|
||
asm_fprintf (asm_out_file, ", ");
|
||
}
|
||
|
||
if ( TARGET_THUMB_INTERWORK
|
||
|| TARGET_BACKTRACE
|
||
|| is_called_in_ARM_mode (current_function_decl))
|
||
{
|
||
asm_fprintf (asm_out_file, "}\n");
|
||
thumb_exit (asm_out_file, -1);
|
||
}
|
||
else
|
||
asm_fprintf (asm_out_file, ", pc}\n");
|
||
}
|
||
|
||
return "";
|
||
}
|
||
|
||
void
|
||
thumb_function_prologue (f, frame_size)
|
||
FILE *f;
|
||
int frame_size;
|
||
{
|
||
int amount = frame_size + current_function_outgoing_args_size;
|
||
int live_regs_mask = 0;
|
||
int high_regs_pushed = 0;
|
||
int store_arg_regs = 0;
|
||
int regno;
|
||
|
||
if (is_called_in_ARM_mode (current_function_decl))
|
||
{
|
||
char * name;
|
||
if (GET_CODE (DECL_RTL (current_function_decl)) != MEM)
|
||
abort();
|
||
if (GET_CODE (XEXP (DECL_RTL (current_function_decl), 0)) != SYMBOL_REF)
|
||
abort();
|
||
name = XSTR (XEXP (DECL_RTL (current_function_decl), 0), 0);
|
||
|
||
/* Generate code sequence to switch us into Thumb mode. */
|
||
/* The .code 32 directive has already been emitted by
|
||
ASM_DECLARE_FUNCITON_NAME */
|
||
asm_fprintf (f, "\torr\tr12, pc, #1\n");
|
||
asm_fprintf (f, "\tbx\tr12\n");
|
||
|
||
/* Generate a label, so that the debugger will notice the
|
||
change in instruction sets. This label is also used by
|
||
the assembler to bypass the ARM code when this function
|
||
is called from a Thumb encoded function elsewhere in the
|
||
same file. Hence the definition of STUB_NAME here must
|
||
agree with the definition in gas/config/tc-arm.c */
|
||
|
||
#define STUB_NAME ".real_start_of"
|
||
|
||
asm_fprintf (f, "\t.code\t16\n");
|
||
asm_fprintf (f, "\t.globl %s%U%s\n", STUB_NAME, name);
|
||
asm_fprintf (f, "\t.thumb_func\n");
|
||
asm_fprintf (f, "%s%U%s:\n", STUB_NAME, name);
|
||
}
|
||
|
||
if (current_function_anonymous_args && current_function_pretend_args_size)
|
||
store_arg_regs = 1;
|
||
|
||
if (current_function_pretend_args_size)
|
||
{
|
||
if (store_arg_regs)
|
||
{
|
||
asm_fprintf (f, "\tpush\t{");
|
||
for (regno = 4 - current_function_pretend_args_size / 4 ; regno < 4;
|
||
regno++)
|
||
asm_fprintf (f, "%s%s", reg_names[regno], regno == 3 ? "" : ", ");
|
||
asm_fprintf (f, "}\n");
|
||
}
|
||
else
|
||
asm_fprintf (f, "\tsub\t%Rsp, %Rsp, #%d\n",
|
||
current_function_pretend_args_size);
|
||
}
|
||
|
||
for (regno = 0; regno < 8; regno++)
|
||
if (regs_ever_live[regno] && ! call_used_regs[regno])
|
||
live_regs_mask |= 1 << regno;
|
||
|
||
if (live_regs_mask || ! leaf_function_p () || far_jump_used_p())
|
||
live_regs_mask |= 1 << 14;
|
||
|
||
if (TARGET_BACKTRACE)
|
||
{
|
||
char * name;
|
||
int offset;
|
||
int work_register = 0;
|
||
|
||
|
||
/* We have been asked to create a stack backtrace structure.
|
||
The code looks like this:
|
||
|
||
0 .align 2
|
||
0 func:
|
||
0 sub SP, #16 Reserve space for 4 registers.
|
||
2 push {R7} Get a work register.
|
||
4 add R7, SP, #20 Get the stack pointer before the push.
|
||
6 str R7, [SP, #8] Store the stack pointer (before reserving the space).
|
||
8 mov R7, PC Get hold of the start of this code plus 12.
|
||
10 str R7, [SP, #16] Store it.
|
||
12 mov R7, FP Get hold of the current frame pointer.
|
||
14 str R7, [SP, #4] Store it.
|
||
16 mov R7, LR Get hold of the current return address.
|
||
18 str R7, [SP, #12] Store it.
|
||
20 add R7, SP, #16 Point at the start of the backtrace structure.
|
||
22 mov FP, R7 Put this value into the frame pointer. */
|
||
|
||
if ((live_regs_mask & 0xFF) == 0)
|
||
{
|
||
/* See if the a4 register is free. */
|
||
|
||
if (regs_ever_live[ 3 ] == 0)
|
||
work_register = 3;
|
||
else /* We must push a register of our own */
|
||
live_regs_mask |= (1 << 7);
|
||
}
|
||
|
||
if (work_register == 0)
|
||
{
|
||
/* Select a register from the list that will be pushed to use as our work register. */
|
||
|
||
for (work_register = 8; work_register--;)
|
||
if ((1 << work_register) & live_regs_mask)
|
||
break;
|
||
}
|
||
|
||
name = reg_names[ work_register ];
|
||
|
||
asm_fprintf (f, "\tsub\tsp, sp, #16\t@ Create stack backtrace structure\n");
|
||
|
||
if (live_regs_mask)
|
||
thumb_pushpop (f, live_regs_mask, 1);
|
||
|
||
for (offset = 0, work_register = 1 << 15; work_register; work_register >>= 1)
|
||
if (work_register & live_regs_mask)
|
||
offset += 4;
|
||
|
||
asm_fprintf (f, "\tadd\t%s, sp, #%d\n",
|
||
name, offset + 16 + current_function_pretend_args_size);
|
||
|
||
asm_fprintf (f, "\tstr\t%s, [sp, #%d]\n", name, offset + 4);
|
||
|
||
/* Make sure that the instruction fetching the PC is in the right place
|
||
to calculate "start of backtrace creation code + 12". */
|
||
|
||
if (live_regs_mask)
|
||
{
|
||
asm_fprintf (f, "\tmov\t%s, pc\n", name);
|
||
asm_fprintf (f, "\tstr\t%s, [sp, #%d]\n", name, offset + 12);
|
||
asm_fprintf (f, "\tmov\t%s, fp\n", name);
|
||
asm_fprintf (f, "\tstr\t%s, [sp, #%d]\n", name, offset);
|
||
}
|
||
else
|
||
{
|
||
asm_fprintf (f, "\tmov\t%s, fp\n", name);
|
||
asm_fprintf (f, "\tstr\t%s, [sp, #%d]\n", name, offset);
|
||
asm_fprintf (f, "\tmov\t%s, pc\n", name);
|
||
asm_fprintf (f, "\tstr\t%s, [sp, #%d]\n", name, offset + 12);
|
||
}
|
||
|
||
asm_fprintf (f, "\tmov\t%s, lr\n", name);
|
||
asm_fprintf (f, "\tstr\t%s, [sp, #%d]\n", name, offset + 8);
|
||
asm_fprintf (f, "\tadd\t%s, sp, #%d\n", name, offset + 12);
|
||
asm_fprintf (f, "\tmov\tfp, %s\t\t@ Backtrace structure created\n", name);
|
||
}
|
||
else if (live_regs_mask)
|
||
thumb_pushpop (f, live_regs_mask, 1);
|
||
|
||
for (regno = 8; regno < 13; regno++)
|
||
{
|
||
if (regs_ever_live[regno] && ! call_used_regs[regno])
|
||
high_regs_pushed++;
|
||
}
|
||
|
||
if (high_regs_pushed)
|
||
{
|
||
int pushable_regs = 0;
|
||
int mask = live_regs_mask & 0xff;
|
||
int next_hi_reg;
|
||
|
||
for (next_hi_reg = 12; next_hi_reg > 7; next_hi_reg--)
|
||
{
|
||
if (regs_ever_live[next_hi_reg] && ! call_used_regs[next_hi_reg])
|
||
break;
|
||
}
|
||
|
||
pushable_regs = mask;
|
||
|
||
if (pushable_regs == 0)
|
||
{
|
||
/* desperation time -- this probably will never happen */
|
||
if (regs_ever_live[3] || ! call_used_regs[3])
|
||
asm_fprintf (f, "\tmov\t%s, %s\n", reg_names[12], reg_names[3]);
|
||
mask = 1 << 3;
|
||
}
|
||
|
||
while (high_regs_pushed > 0)
|
||
{
|
||
for (regno = 7; regno >= 0; regno--)
|
||
{
|
||
if (mask & (1 << regno))
|
||
{
|
||
asm_fprintf (f, "\tmov\t%s, %s\n", reg_names[regno],
|
||
reg_names[next_hi_reg]);
|
||
high_regs_pushed--;
|
||
if (high_regs_pushed)
|
||
for (next_hi_reg--; next_hi_reg > 7; next_hi_reg--)
|
||
{
|
||
if (regs_ever_live[next_hi_reg]
|
||
&& ! call_used_regs[next_hi_reg])
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
mask &= ~ ((1 << regno) - 1);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
thumb_pushpop (f, mask, 1);
|
||
}
|
||
|
||
if (pushable_regs == 0 && (regs_ever_live[3] || ! call_used_regs[3]))
|
||
asm_fprintf (f, "\tmov\t%s, %s\n", reg_names[3], reg_names[12]);
|
||
}
|
||
}
|
||
|
||
void
|
||
thumb_expand_prologue ()
|
||
{
|
||
HOST_WIDE_INT amount = (get_frame_size ()
|
||
+ current_function_outgoing_args_size);
|
||
int regno;
|
||
int live_regs_mask;
|
||
|
||
if (amount)
|
||
{
|
||
live_regs_mask = 0;
|
||
for (regno = 0; regno < 8; regno++)
|
||
if (regs_ever_live[regno] && ! call_used_regs[regno])
|
||
live_regs_mask |= 1 << regno;
|
||
|
||
if (amount < 512)
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx,
|
||
GEN_INT (-amount)));
|
||
else
|
||
{
|
||
rtx reg, spare;
|
||
|
||
if ((live_regs_mask & 0xff) == 0) /* Very unlikely */
|
||
emit_insn (gen_movsi (spare = gen_rtx (REG, SImode, 12),
|
||
reg = gen_rtx (REG, SImode, 4)));
|
||
else
|
||
{
|
||
for (regno = 0; regno < 8; regno++)
|
||
if (live_regs_mask & (1 << regno))
|
||
break;
|
||
reg = gen_rtx (REG, SImode, regno);
|
||
}
|
||
|
||
emit_insn (gen_movsi (reg, GEN_INT (-amount)));
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, reg));
|
||
if ((live_regs_mask & 0xff) == 0)
|
||
emit_insn (gen_movsi (reg, spare));
|
||
}
|
||
}
|
||
|
||
if (frame_pointer_needed)
|
||
{
|
||
if (current_function_outgoing_args_size)
|
||
{
|
||
rtx offset = GEN_INT (current_function_outgoing_args_size);
|
||
|
||
if (current_function_outgoing_args_size < 1024)
|
||
emit_insn (gen_addsi3 (frame_pointer_rtx, stack_pointer_rtx,
|
||
offset));
|
||
else
|
||
{
|
||
emit_insn (gen_movsi (frame_pointer_rtx, offset));
|
||
emit_insn (gen_addsi3 (frame_pointer_rtx, frame_pointer_rtx,
|
||
stack_pointer_rtx));
|
||
}
|
||
}
|
||
else
|
||
emit_insn (gen_movsi (frame_pointer_rtx, stack_pointer_rtx));
|
||
}
|
||
|
||
/* if (profile_flag || profile_block_flag) */
|
||
emit_insn (gen_blockage ());
|
||
}
|
||
|
||
void
|
||
thumb_expand_epilogue ()
|
||
{
|
||
HOST_WIDE_INT amount = (get_frame_size ()
|
||
+ current_function_outgoing_args_size);
|
||
int regno;
|
||
|
||
if (amount)
|
||
{
|
||
if (amount < 512)
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx,
|
||
GEN_INT (amount)));
|
||
else
|
||
{
|
||
rtx reg = gen_rtx (REG, SImode, 3); /* Always free in the epilogue */
|
||
|
||
emit_insn (gen_movsi (reg, GEN_INT (amount)));
|
||
emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, reg));
|
||
}
|
||
/* if (profile_flag || profile_block_flag) */
|
||
emit_insn (gen_blockage ());
|
||
}
|
||
}
|
||
|
||
void
|
||
thumb_function_epilogue (f, frame_size)
|
||
FILE *f;
|
||
int frame_size;
|
||
{
|
||
/* ??? Probably not safe to set this here, since it assumes that a
|
||
function will be emitted as assembly immediately after we generate
|
||
RTL for it. This does not happen for inline functions. */
|
||
return_used_this_function = 0;
|
||
#if 0 /* TODO : comment not really needed */
|
||
fprintf (f, "%s THUMB Epilogue\n", ASM_COMMENT_START);
|
||
#endif
|
||
}
|
||
|
||
/* The bits which aren't usefully expanded as rtl. */
|
||
char *
|
||
thumb_unexpanded_epilogue ()
|
||
{
|
||
int regno;
|
||
int live_regs_mask = 0;
|
||
int high_regs_pushed = 0;
|
||
int leaf_function = leaf_function_p ();
|
||
int had_to_push_lr;
|
||
|
||
if (return_used_this_function)
|
||
return "";
|
||
|
||
for (regno = 0; regno < 8; regno++)
|
||
if (regs_ever_live[regno] && ! call_used_regs[regno])
|
||
live_regs_mask |= 1 << regno;
|
||
|
||
for (regno = 8; regno < 13; regno++)
|
||
{
|
||
if (regs_ever_live[regno] && ! call_used_regs[regno])
|
||
high_regs_pushed ++;
|
||
}
|
||
|
||
/* The prolog may have pushed some high registers to use as
|
||
work registers. eg the testuite file:
|
||
gcc/testsuite/gcc/gcc.c-torture/execute/complex-2.c
|
||
compiles to produce:
|
||
push {r4, r5, r6, r7, lr}
|
||
mov r7, r9
|
||
mov r6, r8
|
||
push {r6, r7}
|
||
as part of the prolog. We have to undo that pushing here. */
|
||
|
||
if (high_regs_pushed)
|
||
{
|
||
int mask = live_regs_mask;
|
||
int next_hi_reg;
|
||
int size;
|
||
int mode;
|
||
|
||
#ifdef RTX_CODE
|
||
/* If we can deduce the registers used from the function's return value.
|
||
This is more reliable that examining regs_ever_live[] because that
|
||
will be set if the register is ever used in the function, not just if
|
||
the register is used to hold a return value. */
|
||
|
||
if (current_function_return_rtx != 0)
|
||
{
|
||
mode = GET_MODE (current_function_return_rtx);
|
||
}
|
||
else
|
||
#endif
|
||
{
|
||
mode = DECL_MODE (DECL_RESULT (current_function_decl));
|
||
}
|
||
|
||
size = GET_MODE_SIZE (mode);
|
||
|
||
/* Unless we are returning a type of size > 12 register r3 is available. */
|
||
if (size < 13)
|
||
mask |= 1 << 3;
|
||
|
||
if (mask == 0)
|
||
{
|
||
/* Oh dear! We have no low registers into which we can pop high registers! */
|
||
|
||
fatal ("No low registers available for popping high registers");
|
||
}
|
||
|
||
for (next_hi_reg = 8; next_hi_reg < 13; next_hi_reg++)
|
||
if (regs_ever_live[next_hi_reg] && ! call_used_regs[next_hi_reg])
|
||
break;
|
||
|
||
while (high_regs_pushed)
|
||
{
|
||
/* Find low register(s) into which the high register(s) can be popped. */
|
||
for (regno = 0; regno < 8; regno++)
|
||
{
|
||
if (mask & (1 << regno))
|
||
high_regs_pushed--;
|
||
if (high_regs_pushed == 0)
|
||
break;
|
||
}
|
||
|
||
mask &= (2 << regno) - 1; /* A noop if regno == 8 */
|
||
|
||
/* Pop the values into the low register(s). */
|
||
thumb_pushpop (asm_out_file, mask, 0);
|
||
|
||
/* Move the value(s) into the high registers. */
|
||
for (regno = 0; regno < 8; regno++)
|
||
{
|
||
if (mask & (1 << regno))
|
||
{
|
||
asm_fprintf (asm_out_file, "\tmov\t%s, %s\n",
|
||
reg_names[next_hi_reg], reg_names[regno]);
|
||
for (next_hi_reg++; next_hi_reg < 13; next_hi_reg++)
|
||
if (regs_ever_live[next_hi_reg] &&
|
||
! call_used_regs[next_hi_reg])
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
had_to_push_lr = (live_regs_mask || ! leaf_function || far_jump_used_p());
|
||
|
||
if (TARGET_BACKTRACE && ((live_regs_mask & 0xFF) == 0) && regs_ever_live[ ARG_4_REGISTER ] != 0)
|
||
{
|
||
/* The stack backtrace structure creation code had to
|
||
push R7 in order to get a work register, so we pop
|
||
it now. */
|
||
|
||
live_regs_mask |= (1 << WORK_REGISTER);
|
||
}
|
||
|
||
if (current_function_pretend_args_size == 0 || TARGET_BACKTRACE)
|
||
{
|
||
if (had_to_push_lr
|
||
&& ! is_called_in_ARM_mode (current_function_decl))
|
||
live_regs_mask |= 1 << PROGRAM_COUNTER;
|
||
|
||
/* Either no argument registers were pushed or a backtrace
|
||
structure was created which includes an adjusted stack
|
||
pointer, so just pop everything. */
|
||
|
||
if (live_regs_mask)
|
||
thumb_pushpop (asm_out_file, live_regs_mask, FALSE);
|
||
|
||
/* We have either just popped the return address into the
|
||
PC or it is was kept in LR for the entire function or
|
||
it is still on the stack because we do not want to
|
||
return by doing a pop {pc}. */
|
||
|
||
if ((live_regs_mask & (1 << PROGRAM_COUNTER)) == 0)
|
||
thumb_exit (asm_out_file,
|
||
(had_to_push_lr
|
||
&& is_called_in_ARM_mode (current_function_decl)) ? -1 : LINK_REGISTER);
|
||
}
|
||
else
|
||
{
|
||
/* Pop everything but the return address. */
|
||
live_regs_mask &= ~ (1 << PROGRAM_COUNTER);
|
||
|
||
if (live_regs_mask)
|
||
thumb_pushpop (asm_out_file, live_regs_mask, FALSE);
|
||
|
||
if (had_to_push_lr)
|
||
{
|
||
/* Get the return address into a temporary register. */
|
||
thumb_pushpop (asm_out_file, 1 << ARG_4_REGISTER, 0);
|
||
}
|
||
|
||
/* Remove the argument registers that were pushed onto the stack. */
|
||
asm_fprintf (asm_out_file, "\tadd\t%s, %s, #%d\n",
|
||
reg_names [STACK_POINTER],
|
||
reg_names [STACK_POINTER],
|
||
current_function_pretend_args_size);
|
||
|
||
thumb_exit (asm_out_file, had_to_push_lr ? ARG_4_REGISTER : LINK_REGISTER);
|
||
}
|
||
|
||
return "";
|
||
}
|
||
|
||
/* Handle the case of a double word load into a low register from
|
||
a computed memory address. The computed address may involve a
|
||
register which is overwritten by the load. */
|
||
|
||
char *
|
||
thumb_load_double_from_address (operands)
|
||
rtx * operands;
|
||
{
|
||
rtx addr;
|
||
rtx base;
|
||
rtx offset;
|
||
rtx arg1;
|
||
rtx arg2;
|
||
|
||
if (GET_CODE (operands[0]) != REG)
|
||
fatal ("thumb_load_double_from_address: destination is not a register");
|
||
|
||
if (GET_CODE (operands[1]) != MEM)
|
||
fatal ("thumb_load_double_from_address: source is not a computed memory address");
|
||
|
||
/* Get the memory address. */
|
||
|
||
addr = XEXP (operands[1], 0);
|
||
|
||
/* Work out how the memory address is computed. */
|
||
|
||
switch (GET_CODE (addr))
|
||
{
|
||
case REG:
|
||
operands[2] = gen_rtx (MEM, SImode, plus_constant (XEXP (operands[1], 0), 4));
|
||
|
||
if (REGNO (operands[0]) == REGNO (addr))
|
||
{
|
||
output_asm_insn ("ldr\t%H0, %2\t\t%@ created by thumb_load_double_from_address", operands);
|
||
output_asm_insn ("ldr\t%0, %1\t\t%@ created by thumb_load_double_from_address", operands);
|
||
}
|
||
else
|
||
{
|
||
output_asm_insn ("ldr\t%0, %1\t\t%@ created by thumb_load_double_from_address", operands);
|
||
output_asm_insn ("ldr\t%H0, %2\t\t%@ created by thumb_load_double_from_address", operands);
|
||
}
|
||
break;
|
||
|
||
case CONST:
|
||
/* Compute <address> + 4 for the high order load. */
|
||
|
||
operands[2] = gen_rtx (MEM, SImode, plus_constant (XEXP (operands[1], 0), 4));
|
||
|
||
output_asm_insn ("ldr\t%0, %1\t\t%@ created by thumb_load_double_from_address", operands);
|
||
output_asm_insn ("ldr\t%H0, %2\t\t%@ created by thumb_load_double_from_address", operands);
|
||
break;
|
||
|
||
case PLUS:
|
||
arg1 = XEXP (addr, 0);
|
||
arg2 = XEXP (addr, 1);
|
||
|
||
if (CONSTANT_P (arg1))
|
||
base = arg2, offset = arg1;
|
||
else
|
||
base = arg1, offset = arg2;
|
||
|
||
if (GET_CODE (base) != REG)
|
||
fatal ("thumb_load_double_from_address: base is not a register");
|
||
|
||
/* Catch the case of <address> = <reg> + <reg> */
|
||
|
||
if (GET_CODE (offset) == REG)
|
||
{
|
||
int reg_offset = REGNO (offset);
|
||
int reg_base = REGNO (base);
|
||
int reg_dest = REGNO (operands[0]);
|
||
|
||
/* Add the base and offset registers together into the higher destination register. */
|
||
|
||
fprintf (asm_out_file, "\tadd\t%s, %s, %s\t\t%s created by thumb_load_double_from_address",
|
||
reg_names[ reg_dest + 1 ],
|
||
reg_names[ reg_base ],
|
||
reg_names[ reg_offset ],
|
||
ASM_COMMENT_START);
|
||
|
||
/* Load the lower destination register from the address in the higher destination register. */
|
||
|
||
fprintf (asm_out_file, "\tldr\t%s, [%s, #0]\t\t%s created by thumb_load_double_from_address",
|
||
reg_names[ reg_dest ],
|
||
reg_names[ reg_dest + 1],
|
||
ASM_COMMENT_START);
|
||
|
||
/* Load the higher destination register from its own address plus 4. */
|
||
|
||
fprintf (asm_out_file, "\tldr\t%s, [%s, #4]\t\t%s created by thumb_load_double_from_address",
|
||
reg_names[ reg_dest + 1 ],
|
||
reg_names[ reg_dest + 1 ],
|
||
ASM_COMMENT_START);
|
||
}
|
||
else
|
||
{
|
||
/* Compute <address> + 4 for the high order load. */
|
||
|
||
operands[2] = gen_rtx (MEM, SImode, plus_constant (XEXP (operands[1], 0), 4));
|
||
|
||
/* If the computed address is held in the low order register
|
||
then load the high order register first, otherwise always
|
||
load the low order register first. */
|
||
|
||
if (REGNO (operands[0]) == REGNO (base))
|
||
{
|
||
output_asm_insn ("ldr\t%H0, %2\t\t%@ created by thumb_load_double_from_address", operands);
|
||
output_asm_insn ("ldr\t%0, %1\t\t%@ created by thumb_load_double_from_address", operands);
|
||
}
|
||
else
|
||
{
|
||
output_asm_insn ("ldr\t%0, %1\t\t%@ created by thumb_load_double_from_address", operands);
|
||
output_asm_insn ("ldr\t%H0, %2\t\t%@ created by thumb_load_double_from_address", operands);
|
||
}
|
||
}
|
||
break;
|
||
|
||
case LABEL_REF:
|
||
/* With no registers to worry about we can just load the value directly. */
|
||
operands[2] = gen_rtx (MEM, SImode, plus_constant (XEXP (operands[1], 0), 4));
|
||
|
||
output_asm_insn ("ldr\t%H0, %2\t\t%@ created by thumb_load_double_from_address", operands);
|
||
output_asm_insn ("ldr\t%0, %1\t\t%@ created by thumb_load_double_from_address", operands);
|
||
break;
|
||
|
||
default:
|
||
debug_rtx (operands[1]);
|
||
fatal ("thumb_load_double_from_address: Unhandled address calculation");
|
||
break;
|
||
}
|
||
|
||
return "";
|
||
}
|
||
|
||
char *
|
||
output_move_mem_multiple (n, operands)
|
||
int n;
|
||
rtx *operands;
|
||
{
|
||
rtx tmp;
|
||
|
||
switch (n)
|
||
{
|
||
case 2:
|
||
if (REGNO (operands[2]) > REGNO (operands[3]))
|
||
{
|
||
tmp = operands[2];
|
||
operands[2] = operands[3];
|
||
operands[3] = tmp;
|
||
}
|
||
output_asm_insn ("ldmia\t%1!, {%2, %3}", operands);
|
||
output_asm_insn ("stmia\t%0!, {%2, %3}", operands);
|
||
break;
|
||
|
||
case 3:
|
||
if (REGNO (operands[2]) > REGNO (operands[3]))
|
||
{
|
||
tmp = operands[2];
|
||
operands[2] = operands[3];
|
||
operands[3] = tmp;
|
||
}
|
||
if (REGNO (operands[3]) > REGNO (operands[4]))
|
||
{
|
||
tmp = operands[3];
|
||
operands[3] = operands[4];
|
||
operands[4] = tmp;
|
||
}
|
||
if (REGNO (operands[2]) > REGNO (operands[3]))
|
||
{
|
||
tmp = operands[2];
|
||
operands[2] = operands[3];
|
||
operands[3] = tmp;
|
||
}
|
||
output_asm_insn ("ldmia\t%1!, {%2, %3, %4}", operands);
|
||
output_asm_insn ("stmia\t%0!, {%2, %3, %4}", operands);
|
||
break;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
|
||
return "";
|
||
}
|
||
|
||
|
||
int
|
||
thumb_epilogue_size ()
|
||
{
|
||
return 42; /* The answer to .... */
|
||
}
|
||
|
||
static char *conds[] =
|
||
{
|
||
"eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc",
|
||
"hi", "ls", "ge", "lt", "gt", "le"
|
||
};
|
||
|
||
static char *
|
||
thumb_condition_code (x, invert)
|
||
rtx x;
|
||
int invert;
|
||
{
|
||
int val;
|
||
|
||
switch (GET_CODE (x))
|
||
{
|
||
case EQ: val = 0; break;
|
||
case NE: val = 1; break;
|
||
case GEU: val = 2; break;
|
||
case LTU: val = 3; break;
|
||
case GTU: val = 8; break;
|
||
case LEU: val = 9; break;
|
||
case GE: val = 10; break;
|
||
case LT: val = 11; break;
|
||
case GT: val = 12; break;
|
||
case LE: val = 13; break;
|
||
default:
|
||
abort ();
|
||
}
|
||
|
||
return conds[val ^ invert];
|
||
}
|
||
|
||
void
|
||
thumb_print_operand (f, x, code)
|
||
FILE *f;
|
||
rtx x;
|
||
int code;
|
||
{
|
||
if (code)
|
||
{
|
||
switch (code)
|
||
{
|
||
case '@':
|
||
fputs (ASM_COMMENT_START, f);
|
||
return;
|
||
|
||
case 'D':
|
||
if (x)
|
||
fputs (thumb_condition_code (x, 1), f);
|
||
return;
|
||
|
||
case 'd':
|
||
if (x)
|
||
fputs (thumb_condition_code (x, 0), f);
|
||
return;
|
||
|
||
/* An explanation of the 'Q', 'R' and 'H' register operands:
|
||
|
||
In a pair of registers containing a DI or DF value the 'Q'
|
||
operand returns the register number of the register containing
|
||
the least signficant part of the value. The 'R' operand returns
|
||
the register number of the register containing the most
|
||
significant part of the value.
|
||
|
||
The 'H' operand returns the higher of the two register numbers.
|
||
On a run where WORDS_BIG_ENDIAN is true the 'H' operand is the
|
||
same as the 'Q' operand, since the most signficant part of the
|
||
value is held in the lower number register. The reverse is true
|
||
on systems where WORDS_BIG_ENDIAN is false.
|
||
|
||
The purpose of these operands is to distinguish between cases
|
||
where the endian-ness of the values is important (for example
|
||
when they are added together), and cases where the endian-ness
|
||
is irrelevant, but the order of register operations is important.
|
||
For example when loading a value from memory into a register
|
||
pair, the endian-ness does not matter. Provided that the value
|
||
from the lower memory address is put into the lower numbered
|
||
register, and the value from the higher address is put into the
|
||
higher numbered register, the load will work regardless of whether
|
||
the value being loaded is big-wordian or little-wordian. The
|
||
order of the two register loads can matter however, if the address
|
||
of the memory location is actually held in one of the registers
|
||
being overwritten by the load. */
|
||
case 'Q':
|
||
if (REGNO (x) > 15)
|
||
abort ();
|
||
fputs (reg_names[REGNO (x) + (WORDS_BIG_ENDIAN ? 1 : 0)], f);
|
||
return;
|
||
|
||
case 'R':
|
||
if (REGNO (x) > 15)
|
||
abort ();
|
||
fputs (reg_names[REGNO (x) + (WORDS_BIG_ENDIAN ? 0 : 1)], f);
|
||
return;
|
||
|
||
case 'H':
|
||
if (REGNO (x) > 15)
|
||
abort ();
|
||
fputs (reg_names[REGNO (x) + 1], f);
|
||
return;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
}
|
||
if (GET_CODE (x) == REG)
|
||
fputs (reg_names[REGNO (x)], f);
|
||
else if (GET_CODE (x) == MEM)
|
||
output_address (XEXP (x, 0));
|
||
else if (GET_CODE (x) == CONST_INT)
|
||
{
|
||
fputc ('#', f);
|
||
output_addr_const (f, x);
|
||
}
|
||
else
|
||
abort ();
|
||
}
|
||
|
||
#ifdef AOF_ASSEMBLER
|
||
int arm_text_section_count = 1;
|
||
|
||
char *
|
||
aof_text_section (in_readonly)
|
||
int in_readonly;
|
||
{
|
||
static char buf[100];
|
||
if (in_readonly)
|
||
return "";
|
||
sprintf (buf, "\tCODE16\n\tAREA |C$$code%d|, CODE, READONLY",
|
||
arm_text_section_count++);
|
||
return buf;
|
||
}
|
||
|
||
static int arm_data_section_count = 1;
|
||
|
||
char *
|
||
aof_data_section ()
|
||
{
|
||
static char buf[100];
|
||
sprintf (buf, "\tAREA |C$$data%d|, DATA", arm_data_section_count++);
|
||
return buf;
|
||
}
|
||
|
||
/* The AOF thumb assembler is religiously strict about declarations of
|
||
imported and exported symbols, so that it is impossible to declare a
|
||
function as imported near the begining of the file, and then to export
|
||
it later on. It is, however, possible to delay the decision until all
|
||
the functions in the file have been compiled. To get around this, we
|
||
maintain a list of the imports and exports, and delete from it any that
|
||
are subsequently defined. At the end of compilation we spit the
|
||
remainder of the list out before the END directive. */
|
||
|
||
struct import
|
||
{
|
||
struct import *next;
|
||
char *name;
|
||
};
|
||
|
||
static struct import *imports_list = NULL;
|
||
|
||
void
|
||
thumb_aof_add_import (name)
|
||
char *name;
|
||
{
|
||
struct import *new;
|
||
|
||
for (new = imports_list; new; new = new->next)
|
||
if (new->name == name)
|
||
return;
|
||
|
||
new = (struct import *) xmalloc (sizeof (struct import));
|
||
new->next = imports_list;
|
||
imports_list = new;
|
||
new->name = name;
|
||
}
|
||
|
||
void
|
||
thumb_aof_delete_import (name)
|
||
char *name;
|
||
{
|
||
struct import **old;
|
||
|
||
for (old = &imports_list; *old; old = & (*old)->next)
|
||
{
|
||
if ((*old)->name == name)
|
||
{
|
||
*old = (*old)->next;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
void
|
||
thumb_aof_dump_imports (f)
|
||
FILE *f;
|
||
{
|
||
while (imports_list)
|
||
{
|
||
fprintf (f, "\tIMPORT\t");
|
||
assemble_name (f, imports_list->name);
|
||
fputc ('\n', f);
|
||
imports_list = imports_list->next;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
/* Decide whether a type should be returned in memory (true)
|
||
or in a register (false). This is called by the macro
|
||
RETURN_IN_MEMORY. */
|
||
|
||
int
|
||
thumb_return_in_memory (type)
|
||
tree type;
|
||
{
|
||
if (! AGGREGATE_TYPE_P (type))
|
||
{
|
||
/* All simple types are returned in registers. */
|
||
|
||
return 0;
|
||
}
|
||
else if (int_size_in_bytes (type) > 4)
|
||
{
|
||
/* All structures/unions bigger than one word are returned in memory. */
|
||
|
||
return 1;
|
||
}
|
||
else if (TREE_CODE (type) == RECORD_TYPE)
|
||
{
|
||
tree field;
|
||
|
||
/* For a struct the APCS says that we must return in a register if
|
||
every addressable element has an offset of zero. For practical
|
||
purposes this means that the structure can have at most one non-
|
||
bit-field element and that this element must be the first one in
|
||
the structure. */
|
||
|
||
/* Find the first field, ignoring non FIELD_DECL things which will
|
||
have been created by C++. */
|
||
for (field = TYPE_FIELDS (type);
|
||
field && TREE_CODE (field) != FIELD_DECL;
|
||
field = TREE_CHAIN (field))
|
||
continue;
|
||
|
||
if (field == NULL)
|
||
return 0; /* An empty structure. Allowed by an extension to ANSI C. */
|
||
|
||
/* Now check the remaining fields, if any. */
|
||
for (field = TREE_CHAIN (field); field; field = TREE_CHAIN (field))
|
||
{
|
||
if (TREE_CODE (field) != FIELD_DECL)
|
||
continue;
|
||
|
||
if (! DECL_BIT_FIELD_TYPE (field))
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
else if (TREE_CODE (type) == UNION_TYPE)
|
||
{
|
||
tree field;
|
||
|
||
/* Unions can be returned in registers if every element is
|
||
integral, or can be returned in an integer register. */
|
||
|
||
for (field = TYPE_FIELDS (type);
|
||
field;
|
||
field = TREE_CHAIN (field))
|
||
{
|
||
if (TREE_CODE (field) != FIELD_DECL)
|
||
continue;
|
||
|
||
if (RETURN_IN_MEMORY (TREE_TYPE (field)))
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
/* XXX Not sure what should be done for other aggregates, so put them in
|
||
memory. */
|
||
return 1;
|
||
}
|
||
|
||
void thumb_override_options()
|
||
{
|
||
if (structure_size_string != NULL)
|
||
{
|
||
int size = strtol (structure_size_string, NULL, 0);
|
||
|
||
if (size == 8 || size == 32)
|
||
arm_structure_size_boundary = size;
|
||
else
|
||
warning ("Structure size boundary can only be set to 8 or 32");
|
||
}
|
||
}
|