diff options
Diffstat (limited to 'gcc_arm/config/arm/thumb.c')
-rwxr-xr-x | gcc_arm/config/arm/thumb.c | 2132 |
1 files changed, 2132 insertions, 0 deletions
diff --git a/gcc_arm/config/arm/thumb.c b/gcc_arm/config/arm/thumb.c new file mode 100755 index 0000000..778cda9 --- /dev/null +++ b/gcc_arm/config/arm/thumb.c @@ -0,0 +1,2132 @@ +/* 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; +static int current_function_has_far_jump = 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; +} + +/* 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); + } +} + +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); + + /* 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. */ + + + /* These are the tests used in + output_constant_pool() to decide if the constant + pool will be marked. Only necessary if debugging + info is being emitted. Only necessary for + references to memory whose address is given by a + symbol. */ + + if (optimize > 0 + && flag_expensive_optimizations + && write_symbols != NO_DEBUG + && GET_CODE (src) == MEM + && GET_CODE (XEXP (src, 0)) == SYMBOL_REF) + replace_symbols_in_block + (DECL_INITIAL (current_function_decl), src, newsrc); + + /* 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 (); +} + +/* CYGNUS LOCAL nickc/thumb-pe */ + +#ifdef THUMB_PE +/* Return non-zero if FUNC is a naked function. */ + +static int +arm_naked_function_p (func) + tree func; +{ + tree a; + + if (TREE_CODE (func) != FUNCTION_DECL) + abort (); + + a = lookup_attribute ("naked", DECL_MACHINE_ATTRIBUTES (func)); + return a != NULL_TREE; +} +#endif +/* END CYGNUS LOCAL nickc/thumb-pe */ + +/* 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; + +/* CYGNUS LOCAL nickc/thumb-pe */ +#ifdef THUMB_PE + return lookup_attribute ("interfacearm", DECL_MACHINE_ATTRIBUTES (func)) != NULL_TREE; +#else + return FALSE; +#endif +/* END CYGNUS LOCAL */ +} + + +/* 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 << 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 at most we will want to pop FP and SP. */ + 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) + { + if (reg_containing_return_addr != LINK_REGISTER) + { + asm_fprintf (f, "\tmov\t%s, %s\n", + reg_names [LINK_REGISTER], reg_names [ARG_4_REGISTER]); + reg_containing_return_addr = LINK_REGISTER; + } + + asm_fprintf (f, "\tmov\t%s, %s\n", + reg_names [ARG_4_REGISTER], reg_names [IP_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; + + if (current_function_has_far_jump) + return 1; + + 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) + { + current_function_has_far_jump = 1; + return 1; + } + } + + return 0; +} + +static int return_used_this_function = 0; + +char * +output_return () +{ + int regno; + int live_regs_mask = 0; + + /* CYGNUS LOCAL nickc/thumb-pe */ +#ifdef THUMB_PE + /* If a function is naked, don't use the "return" insn. */ + if (arm_naked_function_p (current_function_decl)) + return ""; +#endif + /* END CYGNUS LOCAL nickc/thumb-pe */ + + 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; + +/* CYGNUS LOCAL nickc/thumb-pe */ +#ifdef THUMB_PE + if (arm_naked_function_p (current_function_decl)) + return; +#endif +/* CYGNUS LOCAL nickc/thumb-pe */ + + 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; + + /* CYGNUS LOCAL nickc/thumb-pe */ +#ifdef THUMB_PE + /* Naked functions don't have prologues. */ + if (arm_naked_function_p (current_function_decl)) + return; +#endif + /* END CYGNUS LOCAL nickc/thumb-pe */ + + 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; + + /* CYGNUS LOCAL nickc/thumb-pe */ +#ifdef THUMB_PE + /* Naked functions don't have epilogues. */ + if (arm_naked_function_p (current_function_decl)) + return; +#endif + /* END CYGNUS LOCAL nickc/thumb-pe */ + + 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; + current_function_has_far_jump = 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 '_': + fputs (user_label_prefix, 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; + + case 'c': + /* We use 'c' operands with symbols for .vtinherit */ + if (GET_CODE (x) == SYMBOL_REF) + output_addr_const(f, x); + 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"); + } + + if (flag_pic) + { + warning ("Position independent code not supported. Ignored"); + flag_pic = 0; + } +} + +/* CYGNUS LOCAL nickc/thumb-pe */ + +#ifdef THUMB_PE +/* Return nonzero if ATTR is a valid attribute for DECL. + ATTRIBUTES are any existing attributes and ARGS are the arguments + supplied with ATTR. + + Supported attributes: + + naked: don't output any prologue or epilogue code, the user is assumed + to do the right thing. + + interfacearm: Always assume that this function will be entered in ARM + mode, not Thumb mode, and that the caller wishes to be returned to in + ARM mode. */ +int +arm_valid_machine_decl_attribute (decl, attributes, attr, args) + tree decl; + tree attributes; + tree attr; + tree args; +{ + if (args != NULL_TREE) + return 0; + + if (is_attribute_p ("naked", attr)) + return TREE_CODE (decl) == FUNCTION_DECL; + + if (is_attribute_p ("interfacearm", attr)) + return TREE_CODE (decl) == FUNCTION_DECL; + + return 0; +} +#endif /* THUMB_PE */ +/* END CYGNUS LOCAL nickc/thumb-pe */ + +/* s_register_operand is the same as register_operand, but it doesn't accept + (SUBREG (MEM)...). + + This function exists because at the time it was put in it led to better + code. SUBREG(MEM) always needs a reload in the places where + s_register_operand is used, and this seemed to lead to excessive + reloading. */ + +int +s_register_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (GET_MODE (op) != mode && mode != VOIDmode) + return 0; + + if (GET_CODE (op) == SUBREG) + op = SUBREG_REG (op); + + /* We don't consider registers whose class is NO_REGS + to be a register operand. */ + /* XXX might have to check for lo regs only for thumb ??? */ + return (GET_CODE (op) == REG + && (REGNO (op) >= FIRST_PSEUDO_REGISTER + || REGNO_REG_CLASS (REGNO (op)) != NO_REGS)); +} |