| /* Subroutines for insn-output.c for Motorola 68000 family. |
| Copyright (C) 1987, 93, 94, 95, 96, 1997 Free Software Foundation, Inc. |
| |
| 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. */ |
| |
| |
| /* Some output-actions in m68k.md need these. */ |
| #include <stdio.h> |
| #include "config.h" |
| #include "rtl.h" |
| #include "regs.h" |
| #include "hard-reg-set.h" |
| #include "real.h" |
| #include "insn-config.h" |
| #include "conditions.h" |
| #include "insn-flags.h" |
| #include "output.h" |
| #include "insn-attr.h" |
| |
| /* Needed for use_return_insn. */ |
| #include "flags.h" |
| |
| #ifdef SUPPORT_SUN_FPA |
| |
| /* Index into this array by (register number >> 3) to find the |
| smallest class which contains that register. */ |
| enum reg_class regno_reg_class[] |
| = { DATA_REGS, ADDR_REGS, FP_REGS, |
| LO_FPA_REGS, LO_FPA_REGS, FPA_REGS, FPA_REGS }; |
| |
| #endif /* defined SUPPORT_SUN_FPA */ |
| |
| /* This flag is used to communicate between movhi and ASM_OUTPUT_CASE_END, |
| if SGS_SWITCH_TABLE. */ |
| int switch_table_difference_label_flag; |
| |
| static rtx find_addr_reg (); |
| rtx legitimize_pic_address (); |
| |
| |
| /* Alignment to use for loops and jumps */ |
| /* Specify power of two alignment used for loops. */ |
| char *m68k_align_loops_string; |
| /* Specify power of two alignment used for non-loop jumps. */ |
| char *m68k_align_jumps_string; |
| /* Specify power of two alignment used for functions. */ |
| char *m68k_align_funcs_string; |
| |
| /* Specify power of two alignment used for loops. */ |
| int m68k_align_loops; |
| /* Specify power of two alignment used for non-loop jumps. */ |
| int m68k_align_jumps; |
| /* Specify power of two alignment used for functions. */ |
| int m68k_align_funcs; |
| |
| /* Nonzero if the last compare/test insn had FP operands. The |
| sCC expanders peek at this to determine what to do for the |
| 68060, which has no fsCC instructions. */ |
| int m68k_last_compare_had_fp_operands; |
| |
| /* Sometimes certain combinations of command options do not make |
| sense on a particular target machine. You can define a macro |
| `OVERRIDE_OPTIONS' to take account of this. This macro, if |
| defined, is executed once just after all the command options have |
| been parsed. |
| |
| Don't use this macro to turn on various extra optimizations for |
| `-O'. That is what `OPTIMIZATION_OPTIONS' is for. */ |
| |
| void |
| override_options () |
| { |
| int def_align; |
| |
| def_align = 1; |
| |
| /* Validate -malign-loops= value, or provide default */ |
| if (m68k_align_loops_string) |
| { |
| m68k_align_loops = atoi (m68k_align_loops_string); |
| if (m68k_align_loops < 1 || m68k_align_loops > MAX_CODE_ALIGN) |
| fatal ("-malign-loops=%d is not between 1 and %d", |
| m68k_align_loops, MAX_CODE_ALIGN); |
| } |
| else |
| m68k_align_loops = def_align; |
| |
| /* Validate -malign-jumps= value, or provide default */ |
| if (m68k_align_jumps_string) |
| { |
| m68k_align_jumps = atoi (m68k_align_jumps_string); |
| if (m68k_align_jumps < 1 || m68k_align_jumps > MAX_CODE_ALIGN) |
| fatal ("-malign-jumps=%d is not between 1 and %d", |
| m68k_align_jumps, MAX_CODE_ALIGN); |
| } |
| else |
| m68k_align_jumps = def_align; |
| |
| /* Validate -malign-functions= value, or provide default */ |
| if (m68k_align_funcs_string) |
| { |
| m68k_align_funcs = atoi (m68k_align_funcs_string); |
| if (m68k_align_funcs < 1 || m68k_align_funcs > MAX_CODE_ALIGN) |
| fatal ("-malign-functions=%d is not between 1 and %d", |
| m68k_align_funcs, MAX_CODE_ALIGN); |
| } |
| else |
| m68k_align_funcs = def_align; |
| } |
| |
| /* Emit a (use pic_offset_table_rtx) if we used PIC relocation in the |
| function at any time during the compilation process. In the future |
| we should try and eliminate the USE if we can easily determine that |
| all PIC references were deleted from the current function. That would |
| save an address register */ |
| |
| void |
| finalize_pic () |
| { |
| if (flag_pic && current_function_uses_pic_offset_table) |
| { |
| rtx insn = gen_rtx (USE, VOIDmode, pic_offset_table_rtx); |
| emit_insn_after (insn, get_insns ()); |
| emit_insn (insn); |
| } |
| } |
| |
| |
| /* This function generates the assembly code for function entry. |
| STREAM is a stdio stream to output the code to. |
| SIZE is an int: how many units of temporary storage to allocate. |
| Refer to the array `regs_ever_live' to determine which registers |
| to save; `regs_ever_live[I]' is nonzero if register number I |
| is ever used in the function. This function is responsible for |
| knowing which registers should not be saved even if used. */ |
| |
| |
| /* Note that the order of the bit mask for fmovem is the opposite |
| of the order for movem! */ |
| |
| |
| void |
| output_function_prologue (stream, size) |
| FILE *stream; |
| int size; |
| { |
| register int regno; |
| register int mask = 0; |
| int num_saved_regs = 0; |
| extern char call_used_regs[]; |
| int fsize = (size + 3) & -4; |
| |
| |
| if (frame_pointer_needed) |
| { |
| if (fsize == 0 && TARGET_68040) |
| { |
| /* on the 68040, pea + move is faster than link.w 0 */ |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tpea (%s)\n\tmove.l %s,%s\n", |
| reg_names[FRAME_POINTER_REGNUM], reg_names[STACK_POINTER_REGNUM], |
| reg_names[FRAME_POINTER_REGNUM]); |
| #else |
| asm_fprintf (stream, "\tpea %s@\n\tmovel %s,%s\n", |
| reg_names[FRAME_POINTER_REGNUM], reg_names[STACK_POINTER_REGNUM], |
| reg_names[FRAME_POINTER_REGNUM]); |
| #endif |
| } |
| else if (fsize < 0x8000) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tlink.w %s,%0I%d\n", |
| reg_names[FRAME_POINTER_REGNUM], -fsize); |
| #else |
| asm_fprintf (stream, "\tlink %s,%0I%d\n", |
| reg_names[FRAME_POINTER_REGNUM], -fsize); |
| #endif |
| } |
| else if (TARGET_68020) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tlink.l %s,%0I%d\n", |
| reg_names[FRAME_POINTER_REGNUM], -fsize); |
| #else |
| asm_fprintf (stream, "\tlink %s,%0I%d\n", |
| reg_names[FRAME_POINTER_REGNUM], -fsize); |
| #endif |
| } |
| else |
| { |
| /* Adding negative number is faster on the 68040. */ |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tlink.w %s,%0I0\n\tadd.l %0I%d,%Rsp\n", |
| reg_names[FRAME_POINTER_REGNUM], -fsize); |
| #else |
| asm_fprintf (stream, "\tlink %s,%0I0\n\taddl %0I%d,%Rsp\n", |
| reg_names[FRAME_POINTER_REGNUM], -fsize); |
| #endif |
| } |
| } |
| else if (fsize) |
| { |
| if (fsize + 4 < 0x8000) |
| { |
| #ifdef NO_ADDSUB_Q |
| if (fsize + 4 <= 8) |
| { |
| if (!TARGET_5200) |
| { |
| /* asm_fprintf() cannot handle %. */ |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tsubq.w %OI%d,%Rsp\n", fsize + 4); |
| #else |
| asm_fprintf (stream, "\tsubqw %OI%d,%Rsp\n", fsize + 4); |
| #endif |
| } |
| else |
| { |
| /* asm_fprintf() cannot handle %. */ |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tsubq.l %OI%d,%Rsp\n", fsize + 4); |
| #else |
| asm_fprintf (stream, "\tsubql %OI%d,%Rsp\n", fsize + 4); |
| #endif |
| } |
| } |
| else if (fsize + 4 <= 16 && TARGET_CPU32) |
| { |
| /* On the CPU32 it is faster to use two subqw instructions to |
| subtract a small integer (8 < N <= 16) to a register. */ |
| /* asm_fprintf() cannot handle %. */ |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tsubq.w %OI8,%Rsp\n\tsubq.w %OI%d,%Rsp\n", |
| fsize + 4); |
| #else |
| asm_fprintf (stream, "\tsubqw %OI8,%Rsp\n\tsubqw %OI%d,%Rsp\n", |
| fsize + 4); |
| #endif |
| } |
| else |
| #endif /* NO_ADDSUB_Q */ |
| if (TARGET_68040) |
| { |
| /* Adding negative number is faster on the 68040. */ |
| /* asm_fprintf() cannot handle %. */ |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tadd.w %0I%d,%Rsp\n", - (fsize + 4)); |
| #else |
| asm_fprintf (stream, "\taddw %0I%d,%Rsp\n", - (fsize + 4)); |
| #endif |
| } |
| else |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tlea (%d,%Rsp),%Rsp\n", - (fsize + 4)); |
| #else |
| asm_fprintf (stream, "\tlea %Rsp@(%d),%Rsp\n", - (fsize + 4)); |
| #endif |
| } |
| } |
| else |
| { |
| /* asm_fprintf() cannot handle %. */ |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tadd.l %0I%d,%Rsp\n", - (fsize + 4)); |
| #else |
| asm_fprintf (stream, "\taddl %0I%d,%Rsp\n", - (fsize + 4)); |
| #endif |
| } |
| } |
| #ifdef SUPPORT_SUN_FPA |
| for (regno = 24; regno < 56; regno++) |
| if (regs_ever_live[regno] && ! call_used_regs[regno]) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tfpmovd %s,-(%Rsp)\n", |
| reg_names[regno]); |
| #else |
| asm_fprintf (stream, "\tfpmoved %s,%Rsp@-\n", |
| reg_names[regno]); |
| #endif |
| } |
| #endif |
| if (TARGET_68881) |
| { |
| for (regno = 16; regno < 24; regno++) |
| if (regs_ever_live[regno] && ! call_used_regs[regno]) |
| mask |= 1 << (regno - 16); |
| if ((mask & 0xff) != 0) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tfmovm %0I0x%x,-(%Rsp)\n", mask & 0xff); |
| #else |
| asm_fprintf (stream, "\tfmovem %0I0x%x,%Rsp@-\n", mask & 0xff); |
| #endif |
| } |
| mask = 0; |
| } |
| for (regno = 0; regno < 16; regno++) |
| if (regs_ever_live[regno] && ! call_used_regs[regno]) |
| { |
| mask |= 1 << (15 - regno); |
| num_saved_regs++; |
| } |
| if (frame_pointer_needed) |
| { |
| mask &= ~ (1 << (15 - FRAME_POINTER_REGNUM)); |
| num_saved_regs--; |
| } |
| |
| #if NEED_PROBE |
| #ifdef MOTOROLA |
| #ifdef CRDS |
| asm_fprintf (stream, "\ttstl %d(%Rsp)\n", NEED_PROBE - num_saved_regs * 4); |
| #else |
| asm_fprintf (stream, "\ttst.l %d(%Rsp)\n", NEED_PROBE - num_saved_regs * 4); |
| #endif |
| #else |
| asm_fprintf (stream, "\ttstl %Rsp@(%d)\n", NEED_PROBE - num_saved_regs * 4); |
| #endif |
| #endif |
| |
| if (num_saved_regs <= 2) |
| { |
| /* Store each separately in the same order moveml uses. |
| Using two movel instructions instead of a single moveml |
| is about 15% faster for the 68020 and 68030 at no expense |
| in code size */ |
| |
| int i; |
| |
| /* Undo the work from above. */ |
| for (i = 0; i< 16; i++) |
| if (mask & (1 << i)) |
| asm_fprintf (stream, |
| #ifdef MOTOROLA |
| "\t%Omove.l %s,-(%Rsp)\n", |
| #else |
| "\tmovel %s,%Rsp@-\n", |
| #endif |
| reg_names[15 - i]); |
| } |
| else if (mask) |
| { |
| if (TARGET_5200) |
| { |
| /* The coldfire does not support the predecrement form of the |
| movml instruction, so we must adjust the stack pointer and |
| then use the plain address register indirect mode. We also |
| have to invert the register save mask to use the new mode. |
| |
| FIXME: if num_saved_regs was calculated earlier, we could |
| combine the stack pointer adjustment with any adjustment |
| done when the initial stack frame is created. This would |
| save an instruction */ |
| |
| int newmask = 0; |
| int i; |
| |
| for (i = 0; i < 16; i++) |
| if (mask & (1 << i)) |
| newmask |= (1 << (15-i)); |
| |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tlea (%d,%Rsp),%Rsp\n", -num_saved_regs*4); |
| asm_fprintf (stream, "\tmovm.l %0I0x%x,(%Rsp)\n", newmask); |
| #else |
| asm_fprintf (stream, "\tlea %Rsp@(%d),%Rsp\n", -num_saved_regs*4); |
| asm_fprintf (stream, "\tmoveml %0I0x%x,%Rsp@\n", newmask); |
| #endif |
| } |
| else |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tmovm.l %0I0x%x,-(%Rsp)\n", mask); |
| #else |
| asm_fprintf (stream, "\tmoveml %0I0x%x,%Rsp@-\n", mask); |
| #endif |
| } |
| } |
| if (flag_pic && current_function_uses_pic_offset_table) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\t%Olea (%Rpc, %U_GLOBAL_OFFSET_TABLE_@GOTPC), %s\n", |
| reg_names[PIC_OFFSET_TABLE_REGNUM]); |
| #else |
| asm_fprintf (stream, "\tmovel %0I__GLOBAL_OFFSET_TABLE_, %s\n", |
| reg_names[PIC_OFFSET_TABLE_REGNUM]); |
| asm_fprintf (stream, "\tlea %Rpc@(0,%s:l),%s\n", |
| reg_names[PIC_OFFSET_TABLE_REGNUM], |
| reg_names[PIC_OFFSET_TABLE_REGNUM]); |
| #endif |
| } |
| } |
| |
| /* Return true if this function's epilogue can be output as RTL. */ |
| |
| int |
| use_return_insn () |
| { |
| int regno; |
| |
| if (!reload_completed || frame_pointer_needed || get_frame_size () != 0) |
| return 0; |
| |
| /* Copied from output_function_epilogue (). We should probably create a |
| separate layout routine to perform the common work. */ |
| |
| for (regno = 0 ; regno < FIRST_PSEUDO_REGISTER ; regno++) |
| if (regs_ever_live[regno] && ! call_used_regs[regno]) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* This function generates the assembly code for function exit, |
| on machines that need it. Args are same as for FUNCTION_PROLOGUE. |
| |
| The function epilogue should not depend on the current stack pointer! |
| It should use the frame pointer only, if there is a frame pointer. |
| This is mandatory because of alloca; we also take advantage of it to |
| omit stack adjustments before returning. */ |
| |
| void |
| output_function_epilogue (stream, size) |
| FILE *stream; |
| int size; |
| { |
| register int regno; |
| register int mask, fmask; |
| register int nregs; |
| int offset, foffset, fpoffset; |
| extern char call_used_regs[]; |
| int fsize = (size + 3) & -4; |
| int big = 0; |
| rtx insn = get_last_insn (); |
| int restore_from_sp = 0; |
| |
| /* If the last insn was a BARRIER, we don't have to write any code. */ |
| if (GET_CODE (insn) == NOTE) |
| insn = prev_nonnote_insn (insn); |
| if (insn && GET_CODE (insn) == BARRIER) |
| { |
| /* Output just a no-op so that debuggers don't get confused |
| about which function the pc is in at this address. */ |
| asm_fprintf (stream, "\tnop\n"); |
| return; |
| } |
| |
| #ifdef FUNCTION_BLOCK_PROFILER_EXIT |
| if (profile_block_flag == 2) |
| { |
| FUNCTION_BLOCK_PROFILER_EXIT (stream); |
| } |
| #endif |
| |
| #ifdef FUNCTION_EXTRA_EPILOGUE |
| FUNCTION_EXTRA_EPILOGUE (stream, size); |
| #endif |
| nregs = 0; fmask = 0; fpoffset = 0; |
| #ifdef SUPPORT_SUN_FPA |
| for (regno = 24 ; regno < 56 ; regno++) |
| if (regs_ever_live[regno] && ! call_used_regs[regno]) |
| nregs++; |
| fpoffset = nregs * 8; |
| #endif |
| nregs = 0; |
| if (TARGET_68881) |
| { |
| for (regno = 16; regno < 24; regno++) |
| if (regs_ever_live[regno] && ! call_used_regs[regno]) |
| { |
| nregs++; |
| fmask |= 1 << (23 - regno); |
| } |
| } |
| foffset = fpoffset + nregs * 12; |
| nregs = 0; mask = 0; |
| if (frame_pointer_needed) |
| regs_ever_live[FRAME_POINTER_REGNUM] = 0; |
| for (regno = 0; regno < 16; regno++) |
| if (regs_ever_live[regno] && ! call_used_regs[regno]) |
| { |
| nregs++; |
| mask |= 1 << regno; |
| } |
| offset = foffset + nregs * 4; |
| /* FIXME : leaf_function_p below is too strong. |
| What we really need to know there is if there could be pending |
| stack adjustment needed at that point. */ |
| restore_from_sp = ! frame_pointer_needed |
| || (! current_function_calls_alloca && leaf_function_p ()); |
| if (offset + fsize >= 0x8000 |
| && ! restore_from_sp |
| && (mask || fmask || fpoffset)) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\t%Omove.l %0I%d,%Ra1\n", -fsize); |
| #else |
| asm_fprintf (stream, "\tmovel %0I%d,%Ra1\n", -fsize); |
| #endif |
| fsize = 0, big = 1; |
| } |
| if (TARGET_5200 || nregs <= 2) |
| { |
| /* Restore each separately in the same order moveml does. |
| Using two movel instructions instead of a single moveml |
| is about 15% faster for the 68020 and 68030 at no expense |
| in code size. */ |
| |
| int i; |
| |
| /* Undo the work from above. */ |
| for (i = 0; i< 16; i++) |
| if (mask & (1 << i)) |
| { |
| if (big) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\t%Omove.l -%d(%s,%Ra1.l),%s\n", |
| offset + fsize, |
| reg_names[FRAME_POINTER_REGNUM], |
| reg_names[i]); |
| #else |
| asm_fprintf (stream, "\tmovel %s@(-%d,%Ra1:l),%s\n", |
| reg_names[FRAME_POINTER_REGNUM], |
| offset + fsize, reg_names[i]); |
| #endif |
| } |
| else if (restore_from_sp) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\t%Omove.l (%Rsp)+,%s\n", |
| reg_names[i]); |
| #else |
| asm_fprintf (stream, "\tmovel %Rsp@+,%s\n", |
| reg_names[i]); |
| #endif |
| } |
| else |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\t%Omove.l -%d(%s),%s\n", |
| offset + fsize, |
| reg_names[FRAME_POINTER_REGNUM], |
| reg_names[i]); |
| #else |
| asm_fprintf (stream, "\tmovel %s@(-%d),%s\n", |
| reg_names[FRAME_POINTER_REGNUM], |
| offset + fsize, reg_names[i]); |
| #endif |
| } |
| offset = offset - 4; |
| } |
| } |
| else if (mask) |
| { |
| if (big) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tmovm.l -%d(%s,%Ra1.l),%0I0x%x\n", |
| offset + fsize, |
| reg_names[FRAME_POINTER_REGNUM], |
| mask); |
| #else |
| asm_fprintf (stream, "\tmoveml %s@(-%d,%Ra1:l),%0I0x%x\n", |
| reg_names[FRAME_POINTER_REGNUM], |
| offset + fsize, mask); |
| #endif |
| } |
| else if (restore_from_sp) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tmovm.l (%Rsp)+,%0I0x%x\n", mask); |
| #else |
| asm_fprintf (stream, "\tmoveml %Rsp@+,%0I0x%x\n", mask); |
| #endif |
| } |
| else |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tmovm.l -%d(%s),%0I0x%x\n", |
| offset + fsize, |
| reg_names[FRAME_POINTER_REGNUM], |
| mask); |
| #else |
| asm_fprintf (stream, "\tmoveml %s@(-%d),%0I0x%x\n", |
| reg_names[FRAME_POINTER_REGNUM], |
| offset + fsize, mask); |
| #endif |
| } |
| } |
| if (fmask) |
| { |
| if (big) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tfmovm -%d(%s,%Ra1.l),%0I0x%x\n", |
| foffset + fsize, |
| reg_names[FRAME_POINTER_REGNUM], |
| fmask); |
| #else |
| asm_fprintf (stream, "\tfmovem %s@(-%d,%Ra1:l),%0I0x%x\n", |
| reg_names[FRAME_POINTER_REGNUM], |
| foffset + fsize, fmask); |
| #endif |
| } |
| else if (restore_from_sp) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tfmovm (%Rsp)+,%0I0x%x\n", fmask); |
| #else |
| asm_fprintf (stream, "\tfmovem %Rsp@+,%0I0x%x\n", fmask); |
| #endif |
| } |
| else |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tfmovm -%d(%s),%0I0x%x\n", |
| foffset + fsize, |
| reg_names[FRAME_POINTER_REGNUM], |
| fmask); |
| #else |
| asm_fprintf (stream, "\tfmovem %s@(-%d),%0I0x%x\n", |
| reg_names[FRAME_POINTER_REGNUM], |
| foffset + fsize, fmask); |
| #endif |
| } |
| } |
| if (fpoffset != 0) |
| for (regno = 55; regno >= 24; regno--) |
| if (regs_ever_live[regno] && ! call_used_regs[regno]) |
| { |
| if (big) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tfpmovd -%d(%s,%Ra1.l), %s\n", |
| fpoffset + fsize, |
| reg_names[FRAME_POINTER_REGNUM], |
| reg_names[regno]); |
| #else |
| asm_fprintf (stream, "\tfpmoved %s@(-%d,%Ra1:l), %s\n", |
| reg_names[FRAME_POINTER_REGNUM], |
| fpoffset + fsize, reg_names[regno]); |
| #endif |
| } |
| else if (restore_from_sp) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tfpmovd (%Rsp)+,%s\n", |
| reg_names[regno]); |
| #else |
| asm_fprintf (stream, "\tfpmoved %Rsp@+, %s\n", |
| reg_names[regno]); |
| #endif |
| } |
| else |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tfpmovd -%d(%s), %s\n", |
| fpoffset + fsize, |
| reg_names[FRAME_POINTER_REGNUM], |
| reg_names[regno]); |
| #else |
| asm_fprintf (stream, "\tfpmoved %s@(-%d), %s\n", |
| reg_names[FRAME_POINTER_REGNUM], |
| fpoffset + fsize, reg_names[regno]); |
| #endif |
| } |
| fpoffset -= 8; |
| } |
| if (frame_pointer_needed) |
| fprintf (stream, "\tunlk %s\n", |
| reg_names[FRAME_POINTER_REGNUM]); |
| else if (fsize) |
| { |
| #ifdef NO_ADDSUB_Q |
| if (fsize + 4 <= 8) |
| { |
| if (!TARGET_5200) |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\taddq.w %OI%d,%Rsp\n", fsize + 4); |
| #else |
| asm_fprintf (stream, "\taddqw %OI%d,%Rsp\n", fsize + 4); |
| #endif |
| } |
| else |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\taddq.l %OI%d,%Rsp\n", fsize + 4); |
| #else |
| asm_fprintf (stream, "\taddql %OI%d,%Rsp\n", fsize + 4); |
| #endif |
| } |
| } |
| else if (fsize + 4 <= 16 && TARGET_CPU32) |
| { |
| /* On the CPU32 it is faster to use two addqw instructions to |
| add a small integer (8 < N <= 16) to a register. */ |
| /* asm_fprintf() cannot handle %. */ |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\taddq.w %OI8,%Rsp\n\taddq.w %OI%d,%Rsp\n", |
| fsize + 4); |
| #else |
| asm_fprintf (stream, "\taddqw %OI8,%Rsp\n\taddqw %OI%d,%Rsp\n", |
| fsize + 4); |
| #endif |
| } |
| else |
| #endif /* NO_ADDSUB_Q */ |
| if (fsize + 4 < 0x8000) |
| { |
| if (TARGET_68040) |
| { |
| /* asm_fprintf() cannot handle %. */ |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tadd.w %0I%d,%Rsp\n", fsize + 4); |
| #else |
| asm_fprintf (stream, "\taddw %0I%d,%Rsp\n", fsize + 4); |
| #endif |
| } |
| else |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tlea (%d,%Rsp),%Rsp\n", fsize + 4); |
| #else |
| asm_fprintf (stream, "\tlea %Rsp@(%d),%Rsp\n", fsize + 4); |
| #endif |
| } |
| } |
| else |
| { |
| /* asm_fprintf() cannot handle %. */ |
| #ifdef MOTOROLA |
| asm_fprintf (stream, "\tadd.l %0I%d,%Rsp\n", fsize + 4); |
| #else |
| asm_fprintf (stream, "\taddl %0I%d,%Rsp\n", fsize + 4); |
| #endif |
| } |
| } |
| if (current_function_pops_args) |
| asm_fprintf (stream, "\trtd %0I%d\n", current_function_pops_args); |
| else |
| fprintf (stream, "\trts\n"); |
| } |
| |
| /* Similar to general_operand, but exclude stack_pointer_rtx. */ |
| |
| int |
| not_sp_operand (op, mode) |
| register rtx op; |
| enum machine_mode mode; |
| { |
| return op != stack_pointer_rtx && general_operand (op, mode); |
| } |
| |
| /* Return TRUE if X is a valid comparison operator for the dbcc |
| instruction. |
| |
| Note it rejects floating point comparison operators. |
| (In the future we could use Fdbcc). |
| |
| It also rejects some comparisons when CC_NO_OVERFLOW is set. */ |
| |
| int |
| valid_dbcc_comparison_p (x, mode) |
| rtx x; |
| enum machine_mode mode; |
| { |
| switch (GET_CODE (x)) |
| { |
| case EQ: case NE: case GTU: case LTU: |
| case GEU: case LEU: |
| return 1; |
| |
| /* Reject some when CC_NO_OVERFLOW is set. This may be over |
| conservative */ |
| case GT: case LT: case GE: case LE: |
| return ! (cc_prev_status.flags & CC_NO_OVERFLOW); |
| default: |
| return 0; |
| } |
| } |
| |
| /* Return non-zero if flags are currently in the 68881 flag register. */ |
| int |
| flags_in_68881 () |
| { |
| /* We could add support for these in the future */ |
| return cc_status.flags & CC_IN_68881; |
| } |
| |
| /* Output a dbCC; jCC sequence. Note we do not handle the |
| floating point version of this sequence (Fdbcc). We also |
| do not handle alternative conditions when CC_NO_OVERFLOW is |
| set. It is assumed that valid_dbcc_comparison_p and flags_in_68881 will |
| kick those out before we get here. */ |
| |
| output_dbcc_and_branch (operands) |
| rtx *operands; |
| { |
| switch (GET_CODE (operands[3])) |
| { |
| case EQ: |
| #ifdef MOTOROLA |
| output_asm_insn ("dbeq %0,%l1\n\tjbeq %l2", operands); |
| #else |
| output_asm_insn ("dbeq %0,%l1\n\tjeq %l2", operands); |
| #endif |
| break; |
| |
| case NE: |
| #ifdef MOTOROLA |
| output_asm_insn ("dbne %0,%l1\n\tjbne %l2", operands); |
| #else |
| output_asm_insn ("dbne %0,%l1\n\tjne %l2", operands); |
| #endif |
| break; |
| |
| case GT: |
| #ifdef MOTOROLA |
| output_asm_insn ("dbgt %0,%l1\n\tjbgt %l2", operands); |
| #else |
| output_asm_insn ("dbgt %0,%l1\n\tjgt %l2", operands); |
| #endif |
| break; |
| |
| case GTU: |
| #ifdef MOTOROLA |
| output_asm_insn ("dbhi %0,%l1\n\tjbhi %l2", operands); |
| #else |
| output_asm_insn ("dbhi %0,%l1\n\tjhi %l2", operands); |
| #endif |
| break; |
| |
| case LT: |
| #ifdef MOTOROLA |
| output_asm_insn ("dblt %0,%l1\n\tjblt %l2", operands); |
| #else |
| output_asm_insn ("dblt %0,%l1\n\tjlt %l2", operands); |
| #endif |
| break; |
| |
| case LTU: |
| #ifdef MOTOROLA |
| output_asm_insn ("dbcs %0,%l1\n\tjbcs %l2", operands); |
| #else |
| output_asm_insn ("dbcs %0,%l1\n\tjcs %l2", operands); |
| #endif |
| break; |
| |
| case GE: |
| #ifdef MOTOROLA |
| output_asm_insn ("dbge %0,%l1\n\tjbge %l2", operands); |
| #else |
| output_asm_insn ("dbge %0,%l1\n\tjge %l2", operands); |
| #endif |
| break; |
| |
| case GEU: |
| #ifdef MOTOROLA |
| output_asm_insn ("dbcc %0,%l1\n\tjbcc %l2", operands); |
| #else |
| output_asm_insn ("dbcc %0,%l1\n\tjcc %l2", operands); |
| #endif |
| break; |
| |
| case LE: |
| #ifdef MOTOROLA |
| output_asm_insn ("dble %0,%l1\n\tjble %l2", operands); |
| #else |
| output_asm_insn ("dble %0,%l1\n\tjle %l2", operands); |
| #endif |
| break; |
| |
| case LEU: |
| #ifdef MOTOROLA |
| output_asm_insn ("dbls %0,%l1\n\tjbls %l2", operands); |
| #else |
| output_asm_insn ("dbls %0,%l1\n\tjls %l2", operands); |
| #endif |
| break; |
| |
| default: |
| abort (); |
| } |
| |
| /* If the decrement is to be done in SImode, then we have |
| to compensate for the fact that dbcc decrements in HImode. */ |
| switch (GET_MODE (operands[0])) |
| { |
| case SImode: |
| #ifdef MOTOROLA |
| output_asm_insn ("clr%.w %0\n\tsubq%.l %#1,%0\n\tjbpl %l1", operands); |
| #else |
| output_asm_insn ("clr%.w %0\n\tsubq%.l %#1,%0\n\tjpl %l1", operands); |
| #endif |
| break; |
| |
| case HImode: |
| break; |
| |
| default: |
| abort (); |
| } |
| } |
| |
| char * |
| output_scc_di(op, operand1, operand2, dest) |
| rtx op; |
| rtx operand1; |
| rtx operand2; |
| rtx dest; |
| { |
| rtx loperands[7]; |
| enum rtx_code op_code = GET_CODE (op); |
| |
| /* This does not produce a usefull cc. */ |
| CC_STATUS_INIT; |
| |
| /* The m68k cmp.l instruction requires operand1 to be a reg as used |
| below. Swap the operands and change the op if these requirements |
| are not fulfilled. */ |
| if (GET_CODE (operand2) == REG && GET_CODE (operand1) != REG) |
| { |
| rtx tmp = operand1; |
| |
| operand1 = operand2; |
| operand2 = tmp; |
| op_code = swap_condition (op_code); |
| } |
| loperands[0] = operand1; |
| if (GET_CODE (operand1) == REG) |
| loperands[1] = gen_rtx (REG, SImode, REGNO (operand1) + 1); |
| else |
| loperands[1] = adj_offsettable_operand (operand1, 4); |
| if (operand2 != const0_rtx) |
| { |
| loperands[2] = operand2; |
| if (GET_CODE (operand2) == REG) |
| loperands[3] = gen_rtx (REG, SImode, REGNO (operand2) + 1); |
| else |
| loperands[3] = adj_offsettable_operand (operand2, 4); |
| } |
| loperands[4] = gen_label_rtx(); |
| if (operand2 != const0_rtx) |
| #ifdef MOTOROLA |
| #ifdef SGS_CMP_ORDER |
| output_asm_insn ("cmp%.l %0,%2\n\tjbne %l4\n\tcmp%.l %1,%3", loperands); |
| #else |
| output_asm_insn ("cmp%.l %2,%0\n\tjbne %l4\n\tcmp%.l %3,%1", loperands); |
| #endif |
| #else |
| #ifdef SGS_CMP_ORDER |
| output_asm_insn ("cmp%.l %0,%2\n\tjne %l4\n\tcmp%.l %1,%3", loperands); |
| #else |
| output_asm_insn ("cmp%.l %2,%0\n\tjne %l4\n\tcmp%.l %3,%1", loperands); |
| #endif |
| #endif |
| else |
| #ifdef MOTOROLA |
| output_asm_insn ("tst%.l %0\n\tjbne %l4\n\ttst%.l %1", loperands); |
| #else |
| output_asm_insn ("tst%.l %0\n\tjne %l4\n\ttst%.l %1", loperands); |
| #endif |
| loperands[5] = dest; |
| |
| switch (op_code) |
| { |
| case EQ: |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("seq %5", loperands); |
| break; |
| |
| case NE: |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("sne %5", loperands); |
| break; |
| |
| case GT: |
| loperands[6] = gen_label_rtx(); |
| #ifdef MOTOROLA |
| output_asm_insn ("shi %5\n\tjbra %l6", loperands); |
| #else |
| output_asm_insn ("shi %5\n\tjra %l6", loperands); |
| #endif |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("sgt %5", loperands); |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[6])); |
| break; |
| |
| case GTU: |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("shi %5", loperands); |
| break; |
| |
| case LT: |
| loperands[6] = gen_label_rtx(); |
| #ifdef MOTOROLA |
| output_asm_insn ("scs %5\n\tjbra %l6", loperands); |
| #else |
| output_asm_insn ("scs %5\n\tjra %l6", loperands); |
| #endif |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("slt %5", loperands); |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[6])); |
| break; |
| |
| case LTU: |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("scs %5", loperands); |
| break; |
| |
| case GE: |
| loperands[6] = gen_label_rtx(); |
| #ifdef MOTOROLA |
| output_asm_insn ("scc %5\n\tjbra %l6", loperands); |
| #else |
| output_asm_insn ("scc %5\n\tjra %l6", loperands); |
| #endif |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("sge %5", loperands); |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[6])); |
| break; |
| |
| case GEU: |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("scc %5", loperands); |
| break; |
| |
| case LE: |
| loperands[6] = gen_label_rtx(); |
| #ifdef MOTOROLA |
| output_asm_insn ("sls %5\n\tjbra %l6", loperands); |
| #else |
| output_asm_insn ("sls %5\n\tjra %l6", loperands); |
| #endif |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("sle %5", loperands); |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[6])); |
| break; |
| |
| case LEU: |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("sls %5", loperands); |
| break; |
| |
| default: |
| abort (); |
| } |
| return ""; |
| } |
| |
| char * |
| output_btst (operands, countop, dataop, insn, signpos) |
| rtx *operands; |
| rtx countop, dataop; |
| rtx insn; |
| int signpos; |
| { |
| operands[0] = countop; |
| operands[1] = dataop; |
| |
| if (GET_CODE (countop) == CONST_INT) |
| { |
| register int count = INTVAL (countop); |
| /* If COUNT is bigger than size of storage unit in use, |
| advance to the containing unit of same size. */ |
| if (count > signpos) |
| { |
| int offset = (count & ~signpos) / 8; |
| count = count & signpos; |
| operands[1] = dataop = adj_offsettable_operand (dataop, offset); |
| } |
| if (count == signpos) |
| cc_status.flags = CC_NOT_POSITIVE | CC_Z_IN_NOT_N; |
| else |
| cc_status.flags = CC_NOT_NEGATIVE | CC_Z_IN_NOT_N; |
| |
| /* These three statements used to use next_insns_test_no... |
| but it appears that this should do the same job. */ |
| if (count == 31 |
| && next_insn_tests_no_inequality (insn)) |
| return "tst%.l %1"; |
| if (count == 15 |
| && next_insn_tests_no_inequality (insn)) |
| return "tst%.w %1"; |
| if (count == 7 |
| && next_insn_tests_no_inequality (insn)) |
| return "tst%.b %1"; |
| |
| cc_status.flags = CC_NOT_NEGATIVE; |
| } |
| return "btst %0,%1"; |
| } |
| |
| /* Returns 1 if OP is either a symbol reference or a sum of a symbol |
| reference and a constant. */ |
| |
| int |
| symbolic_operand (op, mode) |
| register rtx op; |
| enum machine_mode mode; |
| { |
| switch (GET_CODE (op)) |
| { |
| case SYMBOL_REF: |
| case LABEL_REF: |
| return 1; |
| |
| case CONST: |
| op = XEXP (op, 0); |
| return ((GET_CODE (XEXP (op, 0)) == SYMBOL_REF |
| || GET_CODE (XEXP (op, 0)) == LABEL_REF) |
| && GET_CODE (XEXP (op, 1)) == CONST_INT); |
| |
| #if 0 /* Deleted, with corresponding change in m68k.h, |
| so as to fit the specs. No CONST_DOUBLE is ever symbolic. */ |
| case CONST_DOUBLE: |
| return GET_MODE (op) == mode; |
| #endif |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* Check for sign_extend or zero_extend. Used for bit-count operands. */ |
| |
| int |
| extend_operator(x, mode) |
| rtx x; |
| enum machine_mode mode; |
| { |
| if (mode != VOIDmode && GET_MODE(x) != mode) |
| return 0; |
| switch (GET_CODE(x)) |
| { |
| case SIGN_EXTEND : |
| case ZERO_EXTEND : |
| return 1; |
| default : |
| return 0; |
| } |
| } |
| |
| |
| /* Legitimize PIC addresses. If the address is already |
| position-independent, we return ORIG. Newly generated |
| position-independent addresses go to REG. If we need more |
| than one register, we lose. |
| |
| An address is legitimized by making an indirect reference |
| through the Global Offset Table with the name of the symbol |
| used as an offset. |
| |
| The assembler and linker are responsible for placing the |
| address of the symbol in the GOT. The function prologue |
| is responsible for initializing a5 to the starting address |
| of the GOT. |
| |
| The assembler is also responsible for translating a symbol name |
| into a constant displacement from the start of the GOT. |
| |
| A quick example may make things a little clearer: |
| |
| When not generating PIC code to store the value 12345 into _foo |
| we would generate the following code: |
| |
| movel #12345, _foo |
| |
| When generating PIC two transformations are made. First, the compiler |
| loads the address of foo into a register. So the first transformation makes: |
| |
| lea _foo, a0 |
| movel #12345, a0@ |
| |
| The code in movsi will intercept the lea instruction and call this |
| routine which will transform the instructions into: |
| |
| movel a5@(_foo:w), a0 |
| movel #12345, a0@ |
| |
| |
| That (in a nutshell) is how *all* symbol and label references are |
| handled. */ |
| |
| rtx |
| legitimize_pic_address (orig, mode, reg) |
| rtx orig, reg; |
| enum machine_mode mode; |
| { |
| rtx pic_ref = orig; |
| |
| /* First handle a simple SYMBOL_REF or LABEL_REF */ |
| if (GET_CODE (orig) == SYMBOL_REF || GET_CODE (orig) == LABEL_REF) |
| { |
| if (reg == 0) |
| abort (); |
| |
| pic_ref = gen_rtx (MEM, Pmode, |
| gen_rtx (PLUS, Pmode, |
| pic_offset_table_rtx, orig)); |
| current_function_uses_pic_offset_table = 1; |
| RTX_UNCHANGING_P (pic_ref) = 1; |
| emit_move_insn (reg, pic_ref); |
| return reg; |
| } |
| else if (GET_CODE (orig) == CONST) |
| { |
| rtx base, offset; |
| |
| /* Make sure this is CONST has not already been legitimized */ |
| if (GET_CODE (XEXP (orig, 0)) == PLUS |
| && XEXP (XEXP (orig, 0), 0) == pic_offset_table_rtx) |
| return orig; |
| |
| if (reg == 0) |
| abort (); |
| |
| /* legitimize both operands of the PLUS */ |
| if (GET_CODE (XEXP (orig, 0)) == PLUS) |
| { |
| base = legitimize_pic_address (XEXP (XEXP (orig, 0), 0), Pmode, reg); |
| orig = legitimize_pic_address (XEXP (XEXP (orig, 0), 1), Pmode, |
| base == reg ? 0 : reg); |
| } |
| else abort (); |
| |
| if (GET_CODE (orig) == CONST_INT) |
| return plus_constant_for_output (base, INTVAL (orig)); |
| pic_ref = gen_rtx (PLUS, Pmode, base, orig); |
| /* Likewise, should we set special REG_NOTEs here? */ |
| } |
| return pic_ref; |
| } |
| |
| |
| typedef enum { MOVL, SWAP, NEGW, NOTW, NOTB, MOVQ } CONST_METHOD; |
| |
| #define USE_MOVQ(i) ((unsigned)((i) + 128) <= 255) |
| |
| CONST_METHOD |
| const_method (constant) |
| rtx constant; |
| { |
| int i; |
| unsigned u; |
| |
| i = INTVAL (constant); |
| if (USE_MOVQ (i)) |
| return MOVQ; |
| |
| /* The Coldfire doesn't have byte or word operations. */ |
| /* FIXME: This may not be useful for the m68060 either */ |
| if (!TARGET_5200) |
| { |
| /* if -256 < N < 256 but N is not in range for a moveq |
| N^ff will be, so use moveq #N^ff, dreg; not.b dreg. */ |
| if (USE_MOVQ (i ^ 0xff)) |
| return NOTB; |
| /* Likewise, try with not.w */ |
| if (USE_MOVQ (i ^ 0xffff)) |
| return NOTW; |
| /* This is the only value where neg.w is useful */ |
| if (i == -65408) |
| return NEGW; |
| /* Try also with swap */ |
| u = i; |
| if (USE_MOVQ ((u >> 16) | (u << 16))) |
| return SWAP; |
| } |
| /* Otherwise, use move.l */ |
| return MOVL; |
| } |
| |
| const_int_cost (constant) |
| rtx constant; |
| { |
| switch (const_method (constant)) |
| { |
| case MOVQ : |
| /* Constants between -128 and 127 are cheap due to moveq */ |
| return 0; |
| case NOTB : |
| case NOTW : |
| case NEGW : |
| case SWAP : |
| /* Constants easily generated by moveq + not.b/not.w/neg.w/swap */ |
| return 1; |
| case MOVL : |
| return 2; |
| default : |
| abort (); |
| } |
| } |
| |
| char * |
| output_move_const_into_data_reg (operands) |
| rtx *operands; |
| { |
| int i; |
| |
| i = INTVAL (operands[1]); |
| switch (const_method (operands[1])) |
| { |
| case MOVQ : |
| #if defined (MOTOROLA) && !defined (CRDS) |
| return "moveq%.l %1,%0"; |
| #else |
| return "moveq %1,%0"; |
| #endif |
| case NOTB : |
| operands[1] = gen_rtx (CONST_INT, VOIDmode, i ^ 0xff); |
| #if defined (MOTOROLA) && !defined (CRDS) |
| return "moveq%.l %1,%0\n\tnot%.b %0"; |
| #else |
| return "moveq %1,%0\n\tnot%.b %0"; |
| #endif |
| case NOTW : |
| operands[1] = gen_rtx (CONST_INT, VOIDmode, i ^ 0xffff); |
| #if defined (MOTOROLA) && !defined (CRDS) |
| return "moveq%.l %1,%0\n\tnot%.w %0"; |
| #else |
| return "moveq %1,%0\n\tnot%.w %0"; |
| #endif |
| case NEGW : |
| #if defined (MOTOROLA) && !defined (CRDS) |
| return "moveq%.l %#-128,%0\n\tneg%.w %0"; |
| #else |
| return "moveq %#-128,%0\n\tneg%.w %0"; |
| #endif |
| case SWAP : |
| { |
| unsigned u = i; |
| |
| operands[1] = gen_rtx (CONST_INT, VOIDmode, (u << 16) | (u >> 16)); |
| #if defined (MOTOROLA) && !defined (CRDS) |
| return "moveq%.l %1,%0\n\tswap %0"; |
| #else |
| return "moveq %1,%0\n\tswap %0"; |
| #endif |
| } |
| case MOVL : |
| return "move%.l %1,%0"; |
| default : |
| abort (); |
| } |
| } |
| |
| char * |
| output_move_simode_const (operands) |
| rtx *operands; |
| { |
| if (operands[1] == const0_rtx |
| && (DATA_REG_P (operands[0]) |
| || GET_CODE (operands[0]) == MEM) |
| /* clr insns on 68000 read before writing. |
| This isn't so on the 68010, but we have no TARGET_68010. */ |
| && ((TARGET_68020 || TARGET_5200) |
| || !(GET_CODE (operands[0]) == MEM |
| && MEM_VOLATILE_P (operands[0])))) |
| return "clr%.l %0"; |
| else if (DATA_REG_P (operands[0])) |
| return output_move_const_into_data_reg (operands); |
| else if (ADDRESS_REG_P (operands[0]) |
| && INTVAL (operands[1]) < 0x8000 |
| && INTVAL (operands[1]) >= -0x8000) |
| return "move%.w %1,%0"; |
| else if (GET_CODE (operands[0]) == MEM |
| && GET_CODE (XEXP (operands[0], 0)) == PRE_DEC |
| && REGNO (XEXP (XEXP (operands[0], 0), 0)) == STACK_POINTER_REGNUM |
| && INTVAL (operands[1]) < 0x8000 |
| && INTVAL (operands[1]) >= -0x8000) |
| return "pea %a1"; |
| return "move%.l %1,%0"; |
| } |
| |
| char * |
| output_move_simode (operands) |
| rtx *operands; |
| { |
| if (GET_CODE (operands[1]) == CONST_INT) |
| return output_move_simode_const (operands); |
| else if ((GET_CODE (operands[1]) == SYMBOL_REF |
| || GET_CODE (operands[1]) == CONST) |
| && push_operand (operands[0], SImode)) |
| return "pea %a1"; |
| else if ((GET_CODE (operands[1]) == SYMBOL_REF |
| || GET_CODE (operands[1]) == CONST) |
| && ADDRESS_REG_P (operands[0])) |
| return "lea %a1,%0"; |
| return "move%.l %1,%0"; |
| } |
| |
| char * |
| output_move_himode (operands) |
| rtx *operands; |
| { |
| if (GET_CODE (operands[1]) == CONST_INT) |
| { |
| if (operands[1] == const0_rtx |
| && (DATA_REG_P (operands[0]) |
| || GET_CODE (operands[0]) == MEM) |
| /* clr insns on 68000 read before writing. |
| This isn't so on the 68010, but we have no TARGET_68010. */ |
| && ((TARGET_68020 || TARGET_5200) |
| || !(GET_CODE (operands[0]) == MEM |
| && MEM_VOLATILE_P (operands[0])))) |
| return "clr%.w %0"; |
| else if (DATA_REG_P (operands[0]) |
| && INTVAL (operands[1]) < 128 |
| && INTVAL (operands[1]) >= -128) |
| { |
| #if defined(MOTOROLA) && !defined(CRDS) |
| return "moveq%.l %1,%0"; |
| #else |
| return "moveq %1,%0"; |
| #endif |
| } |
| else if (INTVAL (operands[1]) < 0x8000 |
| && INTVAL (operands[1]) >= -0x8000) |
| return "move%.w %1,%0"; |
| } |
| else if (CONSTANT_P (operands[1])) |
| return "move%.l %1,%0"; |
| #ifndef SGS_NO_LI |
| /* Recognize the insn before a tablejump, one that refers |
| to a table of offsets. Such an insn will need to refer |
| to a label on the insn. So output one. Use the label-number |
| of the table of offsets to generate this label. This code, |
| and similar code below, assumes that there will be at most one |
| reference to each table. */ |
| if (GET_CODE (operands[1]) == MEM |
| && GET_CODE (XEXP (operands[1], 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (operands[1], 0), 1)) == LABEL_REF |
| && GET_CODE (XEXP (XEXP (operands[1], 0), 0)) != PLUS) |
| { |
| rtx labelref = XEXP (XEXP (operands[1], 0), 1); |
| #if defined (MOTOROLA) && !defined (SGS_SWITCH_TABLES) |
| #ifdef SGS |
| asm_fprintf (asm_out_file, "\tset %LLI%d,.+2\n", |
| CODE_LABEL_NUMBER (XEXP (labelref, 0))); |
| #else /* not SGS */ |
| asm_fprintf (asm_out_file, "\t.set %LLI%d,.+2\n", |
| CODE_LABEL_NUMBER (XEXP (labelref, 0))); |
| #endif /* not SGS */ |
| #else /* SGS_SWITCH_TABLES or not MOTOROLA */ |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "LI", |
| CODE_LABEL_NUMBER (XEXP (labelref, 0))); |
| #ifdef SGS_SWITCH_TABLES |
| /* Set flag saying we need to define the symbol |
| LD%n (with value L%n-LI%n) at the end of the switch table. */ |
| switch_table_difference_label_flag = 1; |
| #endif /* SGS_SWITCH_TABLES */ |
| #endif /* SGS_SWITCH_TABLES or not MOTOROLA */ |
| } |
| #endif /* SGS_NO_LI */ |
| return "move%.w %1,%0"; |
| } |
| |
| char * |
| output_move_qimode (operands) |
| rtx *operands; |
| { |
| rtx xoperands[4]; |
| |
| /* This is probably useless, since it loses for pushing a struct |
| of several bytes a byte at a time. */ |
| /* 68k family always modifies the stack pointer by at least 2, even for |
| byte pushes. The 5200 (coldfire) does not do this. */ |
| if (GET_CODE (operands[0]) == MEM |
| && GET_CODE (XEXP (operands[0], 0)) == PRE_DEC |
| && XEXP (XEXP (operands[0], 0), 0) == stack_pointer_rtx |
| && ! ADDRESS_REG_P (operands[1]) |
| && ! TARGET_5200) |
| { |
| xoperands[1] = operands[1]; |
| xoperands[2] |
| = gen_rtx (MEM, QImode, |
| gen_rtx (PLUS, VOIDmode, stack_pointer_rtx, const1_rtx)); |
| /* Just pushing a byte puts it in the high byte of the halfword. */ |
| /* We must put it in the low-order, high-numbered byte. */ |
| if (!reg_mentioned_p (stack_pointer_rtx, operands[1])) |
| { |
| xoperands[3] = stack_pointer_rtx; |
| #ifndef NO_ADDSUB_Q |
| output_asm_insn ("subq%.l %#2,%3\n\tmove%.b %1,%2", xoperands); |
| #else |
| output_asm_insn ("sub%.l %#2,%3\n\tmove%.b %1,%2", xoperands); |
| #endif |
| } |
| else |
| output_asm_insn ("move%.b %1,%-\n\tmove%.b %@,%2", xoperands); |
| return ""; |
| } |
| |
| /* clr and st insns on 68000 read before writing. |
| This isn't so on the 68010, but we have no TARGET_68010. */ |
| if (!ADDRESS_REG_P (operands[0]) |
| && ((TARGET_68020 || TARGET_5200) |
| || !(GET_CODE (operands[0]) == MEM && MEM_VOLATILE_P (operands[0])))) |
| { |
| if (operands[1] == const0_rtx) |
| return "clr%.b %0"; |
| if ((!TARGET_5200 || DATA_REG_P (operands[0])) |
| && GET_CODE (operands[1]) == CONST_INT |
| && (INTVAL (operands[1]) & 255) == 255) |
| { |
| CC_STATUS_INIT; |
| return "st %0"; |
| } |
| } |
| if (GET_CODE (operands[1]) == CONST_INT |
| && DATA_REG_P (operands[0]) |
| && INTVAL (operands[1]) < 128 |
| && INTVAL (operands[1]) >= -128) |
| { |
| #if defined(MOTOROLA) && !defined(CRDS) |
| return "moveq%.l %1,%0"; |
| #else |
| return "moveq %1,%0"; |
| #endif |
| } |
| if (GET_CODE (operands[1]) != CONST_INT && CONSTANT_P (operands[1])) |
| return "move%.l %1,%0"; |
| /* 68k family doesn't support byte moves to from address registers. The |
| 5200 (coldfire) does not have this restriction. */ |
| if ((ADDRESS_REG_P (operands[0]) || ADDRESS_REG_P (operands[1])) |
| && ! TARGET_5200) |
| return "move%.w %1,%0"; |
| return "move%.b %1,%0"; |
| } |
| |
| char * |
| output_move_stricthi (operands) |
| rtx *operands; |
| { |
| if (operands[1] == const0_rtx |
| /* clr insns on 68000 read before writing. |
| This isn't so on the 68010, but we have no TARGET_68010. */ |
| && ((TARGET_68020 || TARGET_5200) |
| || !(GET_CODE (operands[0]) == MEM && MEM_VOLATILE_P (operands[0])))) |
| return "clr%.w %0"; |
| return "move%.w %1,%0"; |
| } |
| |
| char * |
| output_move_strictqi (operands) |
| rtx *operands; |
| { |
| if (operands[1] == const0_rtx |
| /* clr insns on 68000 read before writing. |
| This isn't so on the 68010, but we have no TARGET_68010. */ |
| && ((TARGET_68020 || TARGET_5200) |
| || !(GET_CODE (operands[0]) == MEM && MEM_VOLATILE_P (operands[0])))) |
| return "clr%.b %0"; |
| return "move%.b %1,%0"; |
| } |
| |
| /* Return the best assembler insn template |
| for moving operands[1] into operands[0] as a fullword. */ |
| |
| static char * |
| singlemove_string (operands) |
| rtx *operands; |
| { |
| #ifdef SUPPORT_SUN_FPA |
| if (FPA_REG_P (operands[0]) || FPA_REG_P (operands[1])) |
| return "fpmoves %1,%0"; |
| #endif |
| if (GET_CODE (operands[1]) == CONST_INT) |
| return output_move_simode_const (operands); |
| return "move%.l %1,%0"; |
| } |
| |
| |
| /* Output assembler code to perform a doubleword move insn |
| with operands OPERANDS. */ |
| |
| char * |
| output_move_double (operands) |
| rtx *operands; |
| { |
| enum |
| { |
| REGOP, OFFSOP, MEMOP, PUSHOP, POPOP, CNSTOP, RNDOP |
| } optype0, optype1; |
| rtx latehalf[2]; |
| rtx middlehalf[2]; |
| rtx xops[2]; |
| rtx addreg0 = 0, addreg1 = 0; |
| int dest_overlapped_low = 0; |
| int size = GET_MODE_SIZE (GET_MODE (operands[0])); |
| |
| middlehalf[0] = 0; |
| middlehalf[1] = 0; |
| |
| /* First classify both operands. */ |
| |
| if (REG_P (operands[0])) |
| optype0 = REGOP; |
| else if (offsettable_memref_p (operands[0])) |
| optype0 = OFFSOP; |
| else if (GET_CODE (XEXP (operands[0], 0)) == POST_INC) |
| optype0 = POPOP; |
| else if (GET_CODE (XEXP (operands[0], 0)) == PRE_DEC) |
| optype0 = PUSHOP; |
| else if (GET_CODE (operands[0]) == MEM) |
| optype0 = MEMOP; |
| else |
| optype0 = RNDOP; |
| |
| if (REG_P (operands[1])) |
| optype1 = REGOP; |
| else if (CONSTANT_P (operands[1])) |
| optype1 = CNSTOP; |
| else if (offsettable_memref_p (operands[1])) |
| optype1 = OFFSOP; |
| else if (GET_CODE (XEXP (operands[1], 0)) == POST_INC) |
| optype1 = POPOP; |
| else if (GET_CODE (XEXP (operands[1], 0)) == PRE_DEC) |
| optype1 = PUSHOP; |
| else if (GET_CODE (operands[1]) == MEM) |
| optype1 = MEMOP; |
| else |
| optype1 = RNDOP; |
| |
| /* Check for the cases that the operand constraints are not |
| supposed to allow to happen. Abort if we get one, |
| because generating code for these cases is painful. */ |
| |
| if (optype0 == RNDOP || optype1 == RNDOP) |
| abort (); |
| |
| /* If one operand is decrementing and one is incrementing |
| decrement the former register explicitly |
| and change that operand into ordinary indexing. */ |
| |
| if (optype0 == PUSHOP && optype1 == POPOP) |
| { |
| operands[0] = XEXP (XEXP (operands[0], 0), 0); |
| if (size == 12) |
| output_asm_insn ("sub%.l %#12,%0", operands); |
| else |
| output_asm_insn ("subq%.l %#8,%0", operands); |
| if (GET_MODE (operands[1]) == XFmode) |
| operands[0] = gen_rtx (MEM, XFmode, operands[0]); |
| else if (GET_MODE (operands[0]) == DFmode) |
| operands[0] = gen_rtx (MEM, DFmode, operands[0]); |
| else |
| operands[0] = gen_rtx (MEM, DImode, operands[0]); |
| optype0 = OFFSOP; |
| } |
| if (optype0 == POPOP && optype1 == PUSHOP) |
| { |
| operands[1] = XEXP (XEXP (operands[1], 0), 0); |
| if (size == 12) |
| output_asm_insn ("sub%.l %#12,%1", operands); |
| else |
| output_asm_insn ("subq%.l %#8,%1", operands); |
| if (GET_MODE (operands[1]) == XFmode) |
| operands[1] = gen_rtx (MEM, XFmode, operands[1]); |
| else if (GET_MODE (operands[1]) == DFmode) |
| operands[1] = gen_rtx (MEM, DFmode, operands[1]); |
| else |
| operands[1] = gen_rtx (MEM, DImode, operands[1]); |
| optype1 = OFFSOP; |
| } |
| |
| /* If an operand is an unoffsettable memory ref, find a register |
| we can increment temporarily to make it refer to the second word. */ |
| |
| if (optype0 == MEMOP) |
| addreg0 = find_addr_reg (XEXP (operands[0], 0)); |
| |
| if (optype1 == MEMOP) |
| addreg1 = find_addr_reg (XEXP (operands[1], 0)); |
| |
| /* Ok, we can do one word at a time. |
| Normally we do the low-numbered word first, |
| but if either operand is autodecrementing then we |
| do the high-numbered word first. |
| |
| In either case, set up in LATEHALF the operands to use |
| for the high-numbered word and in some cases alter the |
| operands in OPERANDS to be suitable for the low-numbered word. */ |
| |
| if (size == 12) |
| { |
| if (optype0 == REGOP) |
| { |
| latehalf[0] = gen_rtx (REG, SImode, REGNO (operands[0]) + 2); |
| middlehalf[0] = gen_rtx (REG, SImode, REGNO (operands[0]) + 1); |
| } |
| else if (optype0 == OFFSOP) |
| { |
| middlehalf[0] = adj_offsettable_operand (operands[0], 4); |
| latehalf[0] = adj_offsettable_operand (operands[0], size - 4); |
| } |
| else |
| { |
| middlehalf[0] = operands[0]; |
| latehalf[0] = operands[0]; |
| } |
| |
| if (optype1 == REGOP) |
| { |
| latehalf[1] = gen_rtx (REG, SImode, REGNO (operands[1]) + 2); |
| middlehalf[1] = gen_rtx (REG, SImode, REGNO (operands[1]) + 1); |
| } |
| else if (optype1 == OFFSOP) |
| { |
| middlehalf[1] = adj_offsettable_operand (operands[1], 4); |
| latehalf[1] = adj_offsettable_operand (operands[1], size - 4); |
| } |
| else if (optype1 == CNSTOP) |
| { |
| if (GET_CODE (operands[1]) == CONST_DOUBLE) |
| { |
| REAL_VALUE_TYPE r; |
| long l[3]; |
| |
| REAL_VALUE_FROM_CONST_DOUBLE (r, operands[1]); |
| REAL_VALUE_TO_TARGET_LONG_DOUBLE (r, l); |
| operands[1] = GEN_INT (l[0]); |
| middlehalf[1] = GEN_INT (l[1]); |
| latehalf[1] = GEN_INT (l[2]); |
| } |
| else if (CONSTANT_P (operands[1])) |
| { |
| /* actually, no non-CONST_DOUBLE constant should ever |
| appear here. */ |
| abort (); |
| if (GET_CODE (operands[1]) == CONST_INT && INTVAL (operands[1]) < 0) |
| latehalf[1] = constm1_rtx; |
| else |
| latehalf[1] = const0_rtx; |
| } |
| } |
| else |
| { |
| middlehalf[1] = operands[1]; |
| latehalf[1] = operands[1]; |
| } |
| } |
| else |
| /* size is not 12: */ |
| { |
| if (optype0 == REGOP) |
| latehalf[0] = gen_rtx (REG, SImode, REGNO (operands[0]) + 1); |
| else if (optype0 == OFFSOP) |
| latehalf[0] = adj_offsettable_operand (operands[0], size - 4); |
| else |
| latehalf[0] = operands[0]; |
| |
| if (optype1 == REGOP) |
| latehalf[1] = gen_rtx (REG, SImode, REGNO (operands[1]) + 1); |
| else if (optype1 == OFFSOP) |
| latehalf[1] = adj_offsettable_operand (operands[1], size - 4); |
| else if (optype1 == CNSTOP) |
| split_double (operands[1], &operands[1], &latehalf[1]); |
| else |
| latehalf[1] = operands[1]; |
| } |
| |
| /* If insn is effectively movd N(sp),-(sp) then we will do the |
| high word first. We should use the adjusted operand 1 (which is N+4(sp)) |
| for the low word as well, to compensate for the first decrement of sp. */ |
| if (optype0 == PUSHOP |
| && REGNO (XEXP (XEXP (operands[0], 0), 0)) == STACK_POINTER_REGNUM |
| && reg_overlap_mentioned_p (stack_pointer_rtx, operands[1])) |
| operands[1] = middlehalf[1] = latehalf[1]; |
| |
| /* For (set (reg:DI N) (mem:DI ... (reg:SI N) ...)), |
| if the upper part of reg N does not appear in the MEM, arrange to |
| emit the move late-half first. Otherwise, compute the MEM address |
| into the upper part of N and use that as a pointer to the memory |
| operand. */ |
| if (optype0 == REGOP |
| && (optype1 == OFFSOP || optype1 == MEMOP)) |
| { |
| rtx testlow = gen_rtx (REG, SImode, REGNO (operands[0])); |
| |
| if (reg_overlap_mentioned_p (testlow, XEXP (operands[1], 0)) |
| && reg_overlap_mentioned_p (latehalf[0], XEXP (operands[1], 0))) |
| { |
| /* If both halves of dest are used in the src memory address, |
| compute the address into latehalf of dest. |
| Note that this can't happen if the dest is two data regs. */ |
| compadr: |
| xops[0] = latehalf[0]; |
| xops[1] = XEXP (operands[1], 0); |
| output_asm_insn ("lea %a1,%0", xops); |
| if( GET_MODE (operands[1]) == XFmode ) |
| { |
| operands[1] = gen_rtx (MEM, XFmode, latehalf[0]); |
| middlehalf[1] = adj_offsettable_operand (operands[1], size-8); |
| latehalf[1] = adj_offsettable_operand (operands[1], size-4); |
| } |
| else |
| { |
| operands[1] = gen_rtx (MEM, DImode, latehalf[0]); |
| latehalf[1] = adj_offsettable_operand (operands[1], size-4); |
| } |
| } |
| else if (size == 12 |
| && reg_overlap_mentioned_p (middlehalf[0], |
| XEXP (operands[1], 0))) |
| { |
| /* Check for two regs used by both source and dest. |
| Note that this can't happen if the dest is all data regs. |
| It can happen if the dest is d6, d7, a0. |
| But in that case, latehalf is an addr reg, so |
| the code at compadr does ok. */ |
| |
| if (reg_overlap_mentioned_p (testlow, XEXP (operands[1], 0)) |
| || reg_overlap_mentioned_p (latehalf[0], XEXP (operands[1], 0))) |
| goto compadr; |
| |
| /* JRV says this can't happen: */ |
| if (addreg0 || addreg1) |
| abort (); |
| |
| /* Only the middle reg conflicts; simply put it last. */ |
| output_asm_insn (singlemove_string (operands), operands); |
| output_asm_insn (singlemove_string (latehalf), latehalf); |
| output_asm_insn (singlemove_string (middlehalf), middlehalf); |
| return ""; |
| } |
| else if (reg_overlap_mentioned_p (testlow, XEXP (operands[1], 0))) |
| /* If the low half of dest is mentioned in the source memory |
| address, the arrange to emit the move late half first. */ |
| dest_overlapped_low = 1; |
| } |
| |
| /* If one or both operands autodecrementing, |
| do the two words, high-numbered first. */ |
| |
| /* Likewise, the first move would clobber the source of the second one, |
| do them in the other order. This happens only for registers; |
| such overlap can't happen in memory unless the user explicitly |
| sets it up, and that is an undefined circumstance. */ |
| |
| if (optype0 == PUSHOP || optype1 == PUSHOP |
| || (optype0 == REGOP && optype1 == REGOP |
| && ((middlehalf[1] && REGNO (operands[0]) == REGNO (middlehalf[1])) |
| || REGNO (operands[0]) == REGNO (latehalf[1]))) |
| || dest_overlapped_low) |
| { |
| /* Make any unoffsettable addresses point at high-numbered word. */ |
| if (addreg0) |
| { |
| if (size == 12) |
| output_asm_insn ("addq%.l %#8,%0", &addreg0); |
| else |
| output_asm_insn ("addq%.l %#4,%0", &addreg0); |
| } |
| if (addreg1) |
| { |
| if (size == 12) |
| output_asm_insn ("addq%.l %#8,%0", &addreg1); |
| else |
| output_asm_insn ("addq%.l %#4,%0", &addreg1); |
| } |
| |
| /* Do that word. */ |
| output_asm_insn (singlemove_string (latehalf), latehalf); |
| |
| /* Undo the adds we just did. */ |
| if (addreg0) |
| output_asm_insn ("subq%.l %#4,%0", &addreg0); |
| if (addreg1) |
| output_asm_insn ("subq%.l %#4,%0", &addreg1); |
| |
| if (size == 12) |
| { |
| output_asm_insn (singlemove_string (middlehalf), middlehalf); |
| if (addreg0) |
| output_asm_insn ("subq%.l %#4,%0", &addreg0); |
| if (addreg1) |
| output_asm_insn ("subq%.l %#4,%0", &addreg1); |
| } |
| |
| /* Do low-numbered word. */ |
| return singlemove_string (operands); |
| } |
| |
| /* Normal case: do the two words, low-numbered first. */ |
| |
| output_asm_insn (singlemove_string (operands), operands); |
| |
| /* Do the middle one of the three words for long double */ |
| if (size == 12) |
| { |
| if (addreg0) |
| output_asm_insn ("addq%.l %#4,%0", &addreg0); |
| if (addreg1) |
| output_asm_insn ("addq%.l %#4,%0", &addreg1); |
| |
| output_asm_insn (singlemove_string (middlehalf), middlehalf); |
| } |
| |
| /* Make any unoffsettable addresses point at high-numbered word. */ |
| if (addreg0) |
| output_asm_insn ("addq%.l %#4,%0", &addreg0); |
| if (addreg1) |
| output_asm_insn ("addq%.l %#4,%0", &addreg1); |
| |
| /* Do that word. */ |
| output_asm_insn (singlemove_string (latehalf), latehalf); |
| |
| /* Undo the adds we just did. */ |
| if (addreg0) |
| { |
| if (size == 12) |
| output_asm_insn ("subq%.l %#8,%0", &addreg0); |
| else |
| output_asm_insn ("subq%.l %#4,%0", &addreg0); |
| } |
| if (addreg1) |
| { |
| if (size == 12) |
| output_asm_insn ("subq%.l %#8,%0", &addreg1); |
| else |
| output_asm_insn ("subq%.l %#4,%0", &addreg1); |
| } |
| |
| return ""; |
| } |
| |
| /* Return a REG that occurs in ADDR with coefficient 1. |
| ADDR can be effectively incremented by incrementing REG. */ |
| |
| static rtx |
| find_addr_reg (addr) |
| rtx addr; |
| { |
| while (GET_CODE (addr) == PLUS) |
| { |
| if (GET_CODE (XEXP (addr, 0)) == REG) |
| addr = XEXP (addr, 0); |
| else if (GET_CODE (XEXP (addr, 1)) == REG) |
| addr = XEXP (addr, 1); |
| else if (CONSTANT_P (XEXP (addr, 0))) |
| addr = XEXP (addr, 1); |
| else if (CONSTANT_P (XEXP (addr, 1))) |
| addr = XEXP (addr, 0); |
| else |
| abort (); |
| } |
| if (GET_CODE (addr) == REG) |
| return addr; |
| abort (); |
| } |
| |
| /* Output assembler code to perform a 32 bit 3 operand add. */ |
| |
| char * |
| output_addsi3 (operands) |
| rtx *operands; |
| { |
| if (! operands_match_p (operands[0], operands[1])) |
| { |
| if (!ADDRESS_REG_P (operands[1])) |
| { |
| rtx tmp = operands[1]; |
| |
| operands[1] = operands[2]; |
| operands[2] = tmp; |
| } |
| |
| /* These insns can result from reloads to access |
| stack slots over 64k from the frame pointer. */ |
| if (GET_CODE (operands[2]) == CONST_INT |
| && INTVAL (operands[2]) + 0x8000 >= (unsigned) 0x10000) |
| return "move%.l %2,%0\n\tadd%.l %1,%0"; |
| #ifdef SGS |
| if (GET_CODE (operands[2]) == REG) |
| return "lea 0(%1,%2.l),%0"; |
| else |
| return "lea %c2(%1),%0"; |
| #else /* not SGS */ |
| #ifdef MOTOROLA |
| if (GET_CODE (operands[2]) == REG) |
| return "lea (%1,%2.l),%0"; |
| else |
| return "lea (%c2,%1),%0"; |
| #else /* not MOTOROLA (MIT syntax) */ |
| if (GET_CODE (operands[2]) == REG) |
| return "lea %1@(0,%2:l),%0"; |
| else |
| return "lea %1@(%c2),%0"; |
| #endif /* not MOTOROLA */ |
| #endif /* not SGS */ |
| } |
| if (GET_CODE (operands[2]) == CONST_INT) |
| { |
| #ifndef NO_ADDSUB_Q |
| if (INTVAL (operands[2]) > 0 |
| && INTVAL (operands[2]) <= 8) |
| return "addq%.l %2,%0"; |
| if (INTVAL (operands[2]) < 0 |
| && INTVAL (operands[2]) >= -8) |
| { |
| operands[2] = gen_rtx (CONST_INT, VOIDmode, |
| - INTVAL (operands[2])); |
| return "subq%.l %2,%0"; |
| } |
| /* On the CPU32 it is faster to use two addql instructions to |
| add a small integer (8 < N <= 16) to a register. |
| Likewise for subql. */ |
| if (TARGET_CPU32 && REG_P (operands[0])) |
| { |
| if (INTVAL (operands[2]) > 8 |
| && INTVAL (operands[2]) <= 16) |
| { |
| operands[2] = gen_rtx (CONST_INT, VOIDmode, |
| INTVAL (operands[2]) - 8); |
| return "addq%.l %#8,%0\n\taddq%.l %2,%0"; |
| } |
| if (INTVAL (operands[2]) < -8 |
| && INTVAL (operands[2]) >= -16) |
| { |
| operands[2] = gen_rtx (CONST_INT, VOIDmode, |
| - INTVAL (operands[2]) - 8); |
| return "subq%.l %#8,%0\n\tsubq%.l %2,%0"; |
| } |
| } |
| #endif |
| if (ADDRESS_REG_P (operands[0]) |
| && INTVAL (operands[2]) >= -0x8000 |
| && INTVAL (operands[2]) < 0x8000) |
| { |
| if (TARGET_68040) |
| return "add%.w %2,%0"; |
| else |
| #ifdef MOTOROLA |
| return "lea (%c2,%0),%0"; |
| #else |
| return "lea %0@(%c2),%0"; |
| #endif |
| } |
| } |
| return "add%.l %2,%0"; |
| } |
| |
| /* Store in cc_status the expressions that the condition codes will |
| describe after execution of an instruction whose pattern is EXP. |
| Do not alter them if the instruction would not alter the cc's. */ |
| |
| /* On the 68000, all the insns to store in an address register fail to |
| set the cc's. However, in some cases these instructions can make it |
| possibly invalid to use the saved cc's. In those cases we clear out |
| some or all of the saved cc's so they won't be used. */ |
| |
| notice_update_cc (exp, insn) |
| rtx exp; |
| rtx insn; |
| { |
| /* If the cc is being set from the fpa and the expression is not an |
| explicit floating point test instruction (which has code to deal with |
| this), reinit the CC. */ |
| if (((cc_status.value1 && FPA_REG_P (cc_status.value1)) |
| || (cc_status.value2 && FPA_REG_P (cc_status.value2))) |
| && !(GET_CODE (exp) == PARALLEL |
| && GET_CODE (XVECEXP (exp, 0, 0)) == SET |
| && XEXP (XVECEXP (exp, 0, 0), 0) == cc0_rtx)) |
| { |
| CC_STATUS_INIT; |
| } |
| else if (GET_CODE (exp) == SET) |
| { |
| if (GET_CODE (SET_SRC (exp)) == CALL) |
| { |
| CC_STATUS_INIT; |
| } |
| else if (ADDRESS_REG_P (SET_DEST (exp))) |
| { |
| if (cc_status.value1 |
| && reg_overlap_mentioned_p (SET_DEST (exp), cc_status.value1)) |
| cc_status.value1 = 0; |
| if (cc_status.value2 |
| && reg_overlap_mentioned_p (SET_DEST (exp), cc_status.value2)) |
| cc_status.value2 = 0; |
| } |
| else if (!FP_REG_P (SET_DEST (exp)) |
| && SET_DEST (exp) != cc0_rtx |
| && (FP_REG_P (SET_SRC (exp)) |
| || GET_CODE (SET_SRC (exp)) == FIX |
| || GET_CODE (SET_SRC (exp)) == FLOAT_TRUNCATE |
| || GET_CODE (SET_SRC (exp)) == FLOAT_EXTEND)) |
| { |
| CC_STATUS_INIT; |
| } |
| /* A pair of move insns doesn't produce a useful overall cc. */ |
| else if (!FP_REG_P (SET_DEST (exp)) |
| && !FP_REG_P (SET_SRC (exp)) |
| && GET_MODE_SIZE (GET_MODE (SET_SRC (exp))) > 4 |
| && (GET_CODE (SET_SRC (exp)) == REG |
| || GET_CODE (SET_SRC (exp)) == MEM |
| || GET_CODE (SET_SRC (exp)) == CONST_DOUBLE)) |
| { |
| CC_STATUS_INIT; |
| } |
| else if (GET_CODE (SET_SRC (exp)) == CALL) |
| { |
| CC_STATUS_INIT; |
| } |
| else if (XEXP (exp, 0) != pc_rtx) |
| { |
| cc_status.flags = 0; |
| cc_status.value1 = XEXP (exp, 0); |
| cc_status.value2 = XEXP (exp, 1); |
| } |
| } |
| else if (GET_CODE (exp) == PARALLEL |
| && GET_CODE (XVECEXP (exp, 0, 0)) == SET) |
| { |
| if (ADDRESS_REG_P (XEXP (XVECEXP (exp, 0, 0), 0))) |
| CC_STATUS_INIT; |
| else if (XEXP (XVECEXP (exp, 0, 0), 0) != pc_rtx) |
| { |
| cc_status.flags = 0; |
| cc_status.value1 = XEXP (XVECEXP (exp, 0, 0), 0); |
| cc_status.value2 = XEXP (XVECEXP (exp, 0, 0), 1); |
| } |
| } |
| else |
| CC_STATUS_INIT; |
| if (cc_status.value2 != 0 |
| && ADDRESS_REG_P (cc_status.value2) |
| && GET_MODE (cc_status.value2) == QImode) |
| CC_STATUS_INIT; |
| if (cc_status.value2 != 0 |
| && !(cc_status.value1 && FPA_REG_P (cc_status.value1))) |
| switch (GET_CODE (cc_status.value2)) |
| { |
| case PLUS: case MINUS: case MULT: |
| case DIV: case UDIV: case MOD: case UMOD: case NEG: |
| #if 0 /* These instructions always clear the overflow bit */ |
| case ASHIFT: case ASHIFTRT: case LSHIFTRT: |
| case ROTATE: case ROTATERT: |
| #endif |
| if (GET_MODE (cc_status.value2) != VOIDmode) |
| cc_status.flags |= CC_NO_OVERFLOW; |
| break; |
| case ZERO_EXTEND: |
| /* (SET r1 (ZERO_EXTEND r2)) on this machine |
| ends with a move insn moving r2 in r2's mode. |
| Thus, the cc's are set for r2. |
| This can set N bit spuriously. */ |
| cc_status.flags |= CC_NOT_NEGATIVE; |
| } |
| if (cc_status.value1 && GET_CODE (cc_status.value1) == REG |
| && cc_status.value2 |
| && reg_overlap_mentioned_p (cc_status.value1, cc_status.value2)) |
| cc_status.value2 = 0; |
| if (((cc_status.value1 && FP_REG_P (cc_status.value1)) |
| || (cc_status.value2 && FP_REG_P (cc_status.value2))) |
| && !((cc_status.value1 && FPA_REG_P (cc_status.value1)) |
| || (cc_status.value2 && FPA_REG_P (cc_status.value2)))) |
| cc_status.flags = CC_IN_68881; |
| } |
| |
| char * |
| output_move_const_double (operands) |
| rtx *operands; |
| { |
| #ifdef SUPPORT_SUN_FPA |
| if (TARGET_FPA && FPA_REG_P (operands[0])) |
| { |
| int code = standard_sun_fpa_constant_p (operands[1]); |
| |
| if (code != 0) |
| { |
| static char buf[40]; |
| |
| sprintf (buf, "fpmove%%.d %%%%%d,%%0", code & 0x1ff); |
| return buf; |
| } |
| return "fpmove%.d %1,%0"; |
| } |
| else |
| #endif |
| { |
| int code = standard_68881_constant_p (operands[1]); |
| |
| if (code != 0) |
| { |
| static char buf[40]; |
| |
| sprintf (buf, "fmovecr %%#0x%x,%%0", code & 0xff); |
| return buf; |
| } |
| return "fmove%.d %1,%0"; |
| } |
| } |
| |
| char * |
| output_move_const_single (operands) |
| rtx *operands; |
| { |
| #ifdef SUPPORT_SUN_FPA |
| if (TARGET_FPA) |
| { |
| int code = standard_sun_fpa_constant_p (operands[1]); |
| |
| if (code != 0) |
| { |
| static char buf[40]; |
| |
| sprintf (buf, "fpmove%%.s %%%%%d,%%0", code & 0x1ff); |
| return buf; |
| } |
| return "fpmove%.s %1,%0"; |
| } |
| else |
| #endif /* defined SUPPORT_SUN_FPA */ |
| { |
| int code = standard_68881_constant_p (operands[1]); |
| |
| if (code != 0) |
| { |
| static char buf[40]; |
| |
| sprintf (buf, "fmovecr %%#0x%x,%%0", code & 0xff); |
| return buf; |
| } |
| return "fmove%.s %f1,%0"; |
| } |
| } |
| |
| /* Return nonzero if X, a CONST_DOUBLE, has a value that we can get |
| from the "fmovecr" instruction. |
| The value, anded with 0xff, gives the code to use in fmovecr |
| to get the desired constant. */ |
| |
| /* This code has been fixed for cross-compilation. */ |
| |
| static int inited_68881_table = 0; |
| |
| char *strings_68881[7] = { |
| "0.0", |
| "1.0", |
| "10.0", |
| "100.0", |
| "10000.0", |
| "1e8", |
| "1e16" |
| }; |
| |
| int codes_68881[7] = { |
| 0x0f, |
| 0x32, |
| 0x33, |
| 0x34, |
| 0x35, |
| 0x36, |
| 0x37 |
| }; |
| |
| REAL_VALUE_TYPE values_68881[7]; |
| |
| /* Set up values_68881 array by converting the decimal values |
| strings_68881 to binary. */ |
| |
| void |
| init_68881_table () |
| { |
| int i; |
| REAL_VALUE_TYPE r; |
| enum machine_mode mode; |
| |
| mode = SFmode; |
| for (i = 0; i < 7; i++) |
| { |
| if (i == 6) |
| mode = DFmode; |
| r = REAL_VALUE_ATOF (strings_68881[i], mode); |
| values_68881[i] = r; |
| } |
| inited_68881_table = 1; |
| } |
| |
| int |
| standard_68881_constant_p (x) |
| rtx x; |
| { |
| REAL_VALUE_TYPE r; |
| int i; |
| enum machine_mode mode; |
| |
| #ifdef NO_ASM_FMOVECR |
| return 0; |
| #endif |
| |
| /* fmovecr must be emulated on the 68040, so it shouldn't be used at all. */ |
| if (TARGET_68040) |
| return 0; |
| |
| #ifndef REAL_ARITHMETIC |
| #if HOST_FLOAT_FORMAT != TARGET_FLOAT_FORMAT |
| if (! flag_pretend_float) |
| return 0; |
| #endif |
| #endif |
| |
| if (! inited_68881_table) |
| init_68881_table (); |
| |
| REAL_VALUE_FROM_CONST_DOUBLE (r, x); |
| |
| for (i = 0; i < 6; i++) |
| { |
| if (REAL_VALUES_EQUAL (r, values_68881[i])) |
| return (codes_68881[i]); |
| } |
| |
| if (GET_MODE (x) == SFmode) |
| return 0; |
| |
| if (REAL_VALUES_EQUAL (r, values_68881[6])) |
| return (codes_68881[6]); |
| |
| /* larger powers of ten in the constants ram are not used |
| because they are not equal to a `double' C constant. */ |
| return 0; |
| } |
| |
| /* If X is a floating-point constant, return the logarithm of X base 2, |
| or 0 if X is not a power of 2. */ |
| |
| int |
| floating_exact_log2 (x) |
| rtx x; |
| { |
| REAL_VALUE_TYPE r, r1; |
| int i; |
| |
| #ifndef REAL_ARITHMETIC |
| #if HOST_FLOAT_FORMAT != TARGET_FLOAT_FORMAT |
| if (! flag_pretend_float) |
| return 0; |
| #endif |
| #endif |
| |
| REAL_VALUE_FROM_CONST_DOUBLE (r, x); |
| |
| if (REAL_VALUES_LESS (r, dconst0)) |
| return 0; |
| |
| r1 = dconst1; |
| i = 0; |
| while (REAL_VALUES_LESS (r1, r)) |
| { |
| r1 = REAL_VALUE_LDEXP (dconst1, i); |
| if (REAL_VALUES_EQUAL (r1, r)) |
| return i; |
| i = i + 1; |
| } |
| return 0; |
| } |
| |
| #ifdef SUPPORT_SUN_FPA |
| /* Return nonzero if X, a CONST_DOUBLE, has a value that we can get |
| from the Sun FPA's constant RAM. |
| The value returned, anded with 0x1ff, gives the code to use in fpmove |
| to get the desired constant. */ |
| |
| static int inited_FPA_table = 0; |
| |
| char *strings_FPA[38] = { |
| /* small rationals */ |
| "0.0", |
| "1.0", |
| "0.5", |
| "-1.0", |
| "2.0", |
| "3.0", |
| "4.0", |
| "8.0", |
| "0.25", |
| "0.125", |
| "10.0", |
| "-0.5", |
| /* Decimal equivalents of double precision values */ |
| "2.718281828459045091", /* D_E */ |
| "6.283185307179586477", /* 2 pi */ |
| "3.141592653589793116", /* D_PI */ |
| "1.570796326794896619", /* pi/2 */ |
| "1.414213562373095145", /* D_SQRT2 */ |
| "0.7071067811865475244", /* 1/sqrt(2) */ |
| "-1.570796326794896619", /* -pi/2 */ |
| "1.442695040888963387", /* D_LOG2ofE */ |
| "3.321928024887362182", /* D_LOG2of10 */ |
| "0.6931471805599452862", /* D_LOGEof2 */ |
| "2.302585092994045901", /* D_LOGEof10 */ |
| "0.3010299956639811980", /* D_LOG10of2 */ |
| "0.4342944819032518167", /* D_LOG10ofE */ |
| /* Decimal equivalents of single precision values */ |
| "2.718281745910644531", /* S_E */ |
| "6.283185307179586477", /* 2 pi */ |
| "3.141592741012573242", /* S_PI */ |
| "1.570796326794896619", /* pi/2 */ |
| "1.414213538169860840", /* S_SQRT2 */ |
| "0.7071067811865475244", /* 1/sqrt(2) */ |
| "-1.570796326794896619", /* -pi/2 */ |
| "1.442695021629333496", /* S_LOG2ofE */ |
| "3.321928024291992188", /* S_LOG2of10 */ |
| "0.6931471824645996094", /* S_LOGEof2 */ |
| "2.302585124969482442", /* S_LOGEof10 */ |
| "0.3010300099849700928", /* S_LOG10of2 */ |
| "0.4342944920063018799", /* S_LOG10ofE */ |
| }; |
| |
| |
| int codes_FPA[38] = { |
| /* small rationals */ |
| 0x200, |
| 0xe, |
| 0xf, |
| 0x10, |
| 0x11, |
| 0xb1, |
| 0x12, |
| 0x13, |
| 0x15, |
| 0x16, |
| 0x17, |
| 0x2e, |
| /* double precision */ |
| 0x8, |
| 0x9, |
| 0xa, |
| 0xb, |
| 0xc, |
| 0xd, |
| 0x27, |
| 0x28, |
| 0x29, |
| 0x2a, |
| 0x2b, |
| 0x2c, |
| 0x2d, |
| /* single precision */ |
| 0x8, |
| 0x9, |
| 0xa, |
| 0xb, |
| 0xc, |
| 0xd, |
| 0x27, |
| 0x28, |
| 0x29, |
| 0x2a, |
| 0x2b, |
| 0x2c, |
| 0x2d |
| }; |
| |
| REAL_VALUE_TYPE values_FPA[38]; |
| |
| /* This code has been fixed for cross-compilation. */ |
| |
| void |
| init_FPA_table () |
| { |
| enum machine_mode mode; |
| int i; |
| REAL_VALUE_TYPE r; |
| |
| mode = DFmode; |
| for (i = 0; i < 38; i++) |
| { |
| if (i == 25) |
| mode = SFmode; |
| r = REAL_VALUE_ATOF (strings_FPA[i], mode); |
| values_FPA[i] = r; |
| } |
| inited_FPA_table = 1; |
| } |
| |
| |
| int |
| standard_sun_fpa_constant_p (x) |
| rtx x; |
| { |
| REAL_VALUE_TYPE r; |
| int i; |
| |
| #ifndef REAL_ARITHMETIC |
| #if HOST_FLOAT_FORMAT != TARGET_FLOAT_FORMAT |
| if (! flag_pretend_float) |
| return 0; |
| #endif |
| #endif |
| |
| if (! inited_FPA_table) |
| init_FPA_table (); |
| |
| REAL_VALUE_FROM_CONST_DOUBLE (r, x); |
| |
| for (i=0; i<12; i++) |
| { |
| if (REAL_VALUES_EQUAL (r, values_FPA[i])) |
| return (codes_FPA[i]); |
| } |
| |
| if (GET_MODE (x) == SFmode) |
| { |
| for (i=25; i<38; i++) |
| { |
| if (REAL_VALUES_EQUAL (r, values_FPA[i])) |
| return (codes_FPA[i]); |
| } |
| } |
| else |
| { |
| for (i=12; i<25; i++) |
| { |
| if (REAL_VALUES_EQUAL (r, values_FPA[i])) |
| return (codes_FPA[i]); |
| } |
| } |
| return 0x0; |
| } |
| #endif /* define SUPPORT_SUN_FPA */ |
| |
| /* A C compound statement to output to stdio stream STREAM the |
| assembler syntax for an instruction operand X. X is an RTL |
| expression. |
| |
| CODE is a value that can be used to specify one of several ways |
| of printing the operand. It is used when identical operands |
| must be printed differently depending on the context. CODE |
| comes from the `%' specification that was used to request |
| printing of the operand. If the specification was just `%DIGIT' |
| then CODE is 0; if the specification was `%LTR DIGIT' then CODE |
| is the ASCII code for LTR. |
| |
| If X is a register, this macro should print the register's name. |
| The names can be found in an array `reg_names' whose type is |
| `char *[]'. `reg_names' is initialized from `REGISTER_NAMES'. |
| |
| When the machine description has a specification `%PUNCT' (a `%' |
| followed by a punctuation character), this macro is called with |
| a null pointer for X and the punctuation character for CODE. |
| |
| The m68k specific codes are: |
| |
| '.' for dot needed in Motorola-style opcode names. |
| '-' for an operand pushing on the stack: |
| sp@-, -(sp) or -(%sp) depending on the style of syntax. |
| '+' for an operand pushing on the stack: |
| sp@+, (sp)+ or (%sp)+ depending on the style of syntax. |
| '@' for a reference to the top word on the stack: |
| sp@, (sp) or (%sp) depending on the style of syntax. |
| '#' for an immediate operand prefix (# in MIT and Motorola syntax |
| but & in SGS syntax, $ in CRDS/UNOS syntax). |
| '!' for the cc register (used in an `and to cc' insn). |
| '$' for the letter `s' in an op code, but only on the 68040. |
| '&' for the letter `d' in an op code, but only on the 68040. |
| '/' for register prefix needed by longlong.h. |
| |
| 'b' for byte insn (no effect, on the Sun; this is for the ISI). |
| 'd' to force memory addressing to be absolute, not relative. |
| 'f' for float insn (print a CONST_DOUBLE as a float rather than in hex) |
| 'w' for FPA insn (print a CONST_DOUBLE as a SunFPA constant rather |
| than directly). Second part of 'y' below. |
| 'x' for float insn (print a CONST_DOUBLE as a float rather than in hex), |
| or print pair of registers as rx:ry. |
| 'y' for a FPA insn (print pair of registers as rx:ry). This also outputs |
| CONST_DOUBLE's as SunFPA constant RAM registers if |
| possible, so it should not be used except for the SunFPA. |
| |
| */ |
| |
| void |
| print_operand (file, op, letter) |
| FILE *file; /* file to write to */ |
| rtx op; /* operand to print */ |
| int letter; /* %<letter> or 0 */ |
| { |
| int i; |
| |
| if (letter == '.') |
| { |
| #if defined (MOTOROLA) && !defined (CRDS) |
| asm_fprintf (file, "."); |
| #endif |
| } |
| else if (letter == '#') |
| { |
| asm_fprintf (file, "%0I"); |
| } |
| else if (letter == '-') |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (file, "-(%Rsp)"); |
| #else |
| asm_fprintf (file, "%Rsp@-"); |
| #endif |
| } |
| else if (letter == '+') |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (file, "(%Rsp)+"); |
| #else |
| asm_fprintf (file, "%Rsp@+"); |
| #endif |
| } |
| else if (letter == '@') |
| { |
| #ifdef MOTOROLA |
| asm_fprintf (file, "(%Rsp)"); |
| #else |
| asm_fprintf (file, "%Rsp@"); |
| #endif |
| } |
| else if (letter == '!') |
| { |
| asm_fprintf (file, "%Rfpcr"); |
| } |
| else if (letter == '$') |
| { |
| if (TARGET_68040_ONLY) |
| { |
| fprintf (file, "s"); |
| } |
| } |
| else if (letter == '&') |
| { |
| if (TARGET_68040_ONLY) |
| { |
| fprintf (file, "d"); |
| } |
| } |
| else if (letter == '/') |
| { |
| asm_fprintf (file, "%R"); |
| } |
| else if (GET_CODE (op) == REG) |
| { |
| #ifdef SUPPORT_SUN_FPA |
| if (REGNO (op) < 16 |
| && (letter == 'y' || letter == 'x') |
| && GET_MODE (op) == DFmode) |
| { |
| fprintf (file, "%s:%s", reg_names[REGNO (op)], |
| reg_names[REGNO (op)+1]); |
| } |
| else |
| #endif |
| { |
| if (letter == 'R') |
| /* Print out the second register name of a register pair. |
| I.e., R (6) => 7. */ |
| fputs (reg_names[REGNO (op) + 1], file); |
| else |
| fputs (reg_names[REGNO (op)], file); |
| } |
| } |
| else if (GET_CODE (op) == MEM) |
| { |
| output_address (XEXP (op, 0)); |
| if (letter == 'd' && ! TARGET_68020 |
| && CONSTANT_ADDRESS_P (XEXP (op, 0)) |
| && !(GET_CODE (XEXP (op, 0)) == CONST_INT |
| && INTVAL (XEXP (op, 0)) < 0x8000 |
| && INTVAL (XEXP (op, 0)) >= -0x8000)) |
| { |
| #ifdef MOTOROLA |
| fprintf (file, ".l"); |
| #else |
| fprintf (file, ":l"); |
| #endif |
| } |
| } |
| #ifdef SUPPORT_SUN_FPA |
| else if ((letter == 'y' || letter == 'w') |
| && GET_CODE (op) == CONST_DOUBLE |
| && (i = standard_sun_fpa_constant_p (op))) |
| { |
| fprintf (file, "%%%d", i & 0x1ff); |
| } |
| #endif |
| else if (GET_CODE (op) == CONST_DOUBLE && GET_MODE (op) == SFmode) |
| { |
| REAL_VALUE_TYPE r; |
| REAL_VALUE_FROM_CONST_DOUBLE (r, op); |
| ASM_OUTPUT_FLOAT_OPERAND (letter, file, r); |
| } |
| else if (GET_CODE (op) == CONST_DOUBLE && GET_MODE (op) == XFmode) |
| { |
| REAL_VALUE_TYPE r; |
| REAL_VALUE_FROM_CONST_DOUBLE (r, op); |
| ASM_OUTPUT_LONG_DOUBLE_OPERAND (file, r); |
| } |
| else if (GET_CODE (op) == CONST_DOUBLE && GET_MODE (op) == DFmode) |
| { |
| REAL_VALUE_TYPE r; |
| REAL_VALUE_FROM_CONST_DOUBLE (r, op); |
| ASM_OUTPUT_DOUBLE_OPERAND (file, r); |
| } |
| else |
| { |
| asm_fprintf (file, "%0I"); output_addr_const (file, op); |
| } |
| } |
| |
| |
| /* A C compound statement to output to stdio stream STREAM the |
| assembler syntax for an instruction operand that is a memory |
| reference whose address is ADDR. ADDR is an RTL expression. |
| |
| Note that this contains a kludge that knows that the only reason |
| we have an address (plus (label_ref...) (reg...)) when not generating |
| PIC code is in the insn before a tablejump, and we know that m68k.md |
| generates a label LInnn: on such an insn. |
| |
| It is possible for PIC to generate a (plus (label_ref...) (reg...)) |
| and we handle that just like we would a (plus (symbol_ref...) (reg...)). |
| |
| Some SGS assemblers have a bug such that "Lnnn-LInnn-2.b(pc,d0.l*2)" |
| fails to assemble. Luckily "Lnnn(pc,d0.l*2)" produces the results |
| we want. This difference can be accommodated by using an assembler |
| define such "LDnnn" to be either "Lnnn-LInnn-2.b", "Lnnn", or any other |
| string, as necessary. This is accomplished via the ASM_OUTPUT_CASE_END |
| macro. See m68k/sgs.h for an example; for versions without the bug. |
| Some assemblers refuse all the above solutions. The workaround is to |
| emit "K(pc,d0.l*2)" with K being a small constant known to give the |
| right behaviour. |
| |
| They also do not like things like "pea 1.w", so we simple leave off |
| the .w on small constants. |
| |
| This routine is responsible for distinguishing between -fpic and -fPIC |
| style relocations in an address. When generating -fpic code the |
| offset is output in word mode (eg movel a5@(_foo:w), a0). When generating |
| -fPIC code the offset is output in long mode (eg movel a5@(_foo:l), a0) */ |
| |
| #ifndef ASM_OUTPUT_CASE_FETCH |
| #ifdef MOTOROLA |
| #ifdef SGS |
| #define ASM_OUTPUT_CASE_FETCH(file, labelno, regname)\ |
| asm_fprintf (file, "%LLD%d(%Rpc,%s.", labelno, regname) |
| #else |
| #define ASM_OUTPUT_CASE_FETCH(file, labelno, regname)\ |
| asm_fprintf (file, "%LL%d-%LLI%d.b(%Rpc,%s.", labelno, labelno, regname) |
| #endif |
| #else |
| #define ASM_OUTPUT_CASE_FETCH(file, labelno, regname)\ |
| asm_fprintf (file, "%Rpc@(%LL%d-%LLI%d-2:b,%s:", labelno, labelno, regname) |
| #endif |
| #endif /* ASM_OUTPUT_CASE_FETCH */ |
| |
| void |
| print_operand_address (file, addr) |
| FILE *file; |
| rtx addr; |
| { |
| register rtx reg1, reg2, breg, ireg; |
| rtx offset; |
| |
| switch (GET_CODE (addr)) |
| { |
| case REG: |
| #ifdef MOTOROLA |
| fprintf (file, "(%s)", reg_names[REGNO (addr)]); |
| #else |
| fprintf (file, "%s@", reg_names[REGNO (addr)]); |
| #endif |
| break; |
| case PRE_DEC: |
| #ifdef MOTOROLA |
| fprintf (file, "-(%s)", reg_names[REGNO (XEXP (addr, 0))]); |
| #else |
| fprintf (file, "%s@-", reg_names[REGNO (XEXP (addr, 0))]); |
| #endif |
| break; |
| case POST_INC: |
| #ifdef MOTOROLA |
| fprintf (file, "(%s)+", reg_names[REGNO (XEXP (addr, 0))]); |
| #else |
| fprintf (file, "%s@+", reg_names[REGNO (XEXP (addr, 0))]); |
| #endif |
| break; |
| case PLUS: |
| reg1 = reg2 = ireg = breg = offset = 0; |
| if (CONSTANT_ADDRESS_P (XEXP (addr, 0))) |
| { |
| offset = XEXP (addr, 0); |
| addr = XEXP (addr, 1); |
| } |
| else if (CONSTANT_ADDRESS_P (XEXP (addr, 1))) |
| { |
| offset = XEXP (addr, 1); |
| addr = XEXP (addr, 0); |
| } |
| if (GET_CODE (addr) != PLUS) |
| { |
| ; |
| } |
| else if (GET_CODE (XEXP (addr, 0)) == SIGN_EXTEND) |
| { |
| reg1 = XEXP (addr, 0); |
| addr = XEXP (addr, 1); |
| } |
| else if (GET_CODE (XEXP (addr, 1)) == SIGN_EXTEND) |
| { |
| reg1 = XEXP (addr, 1); |
| addr = XEXP (addr, 0); |
| } |
| else if (GET_CODE (XEXP (addr, 0)) == MULT) |
| { |
| reg1 = XEXP (addr, 0); |
| addr = XEXP (addr, 1); |
| } |
| else if (GET_CODE (XEXP (addr, 1)) == MULT) |
| { |
| reg1 = XEXP (addr, 1); |
| addr = XEXP (addr, 0); |
| } |
| else if (GET_CODE (XEXP (addr, 0)) == REG) |
| { |
| reg1 = XEXP (addr, 0); |
| addr = XEXP (addr, 1); |
| } |
| else if (GET_CODE (XEXP (addr, 1)) == REG) |
| { |
| reg1 = XEXP (addr, 1); |
| addr = XEXP (addr, 0); |
| } |
| if (GET_CODE (addr) == REG || GET_CODE (addr) == MULT |
| || GET_CODE (addr) == SIGN_EXTEND) |
| { |
| if (reg1 == 0) |
| { |
| reg1 = addr; |
| } |
| else |
| { |
| reg2 = addr; |
| } |
| addr = 0; |
| } |
| #if 0 /* for OLD_INDEXING */ |
| else if (GET_CODE (addr) == PLUS) |
| { |
| if (GET_CODE (XEXP (addr, 0)) == REG) |
| { |
| reg2 = XEXP (addr, 0); |
| addr = XEXP (addr, 1); |
| } |
| else if (GET_CODE (XEXP (addr, 1)) == REG) |
| { |
| reg2 = XEXP (addr, 1); |
| addr = XEXP (addr, 0); |
| } |
| } |
| #endif |
| if (offset != 0) |
| { |
| if (addr != 0) |
| { |
| abort (); |
| } |
| addr = offset; |
| } |
| if ((reg1 && (GET_CODE (reg1) == SIGN_EXTEND |
| || GET_CODE (reg1) == MULT)) |
| || (reg2 != 0 && REGNO_OK_FOR_BASE_P (REGNO (reg2)))) |
| { |
| breg = reg2; |
| ireg = reg1; |
| } |
| else if (reg1 != 0 && REGNO_OK_FOR_BASE_P (REGNO (reg1))) |
| { |
| breg = reg1; |
| ireg = reg2; |
| } |
| if (ireg != 0 && breg == 0 && GET_CODE (addr) == LABEL_REF |
| && ! (flag_pic && ireg == pic_offset_table_rtx)) |
| { |
| int scale = 1; |
| if (GET_CODE (ireg) == MULT) |
| { |
| scale = INTVAL (XEXP (ireg, 1)); |
| ireg = XEXP (ireg, 0); |
| } |
| if (GET_CODE (ireg) == SIGN_EXTEND) |
| { |
| ASM_OUTPUT_CASE_FETCH (file, |
| CODE_LABEL_NUMBER (XEXP (addr, 0)), |
| reg_names[REGNO (XEXP (ireg, 0))]); |
| fprintf (file, "w"); |
| } |
| else |
| { |
| ASM_OUTPUT_CASE_FETCH (file, |
| CODE_LABEL_NUMBER (XEXP (addr, 0)), |
| reg_names[REGNO (ireg)]); |
| fprintf (file, "l"); |
| } |
| if (scale != 1) |
| { |
| #ifdef MOTOROLA |
| fprintf (file, "*%d", scale); |
| #else |
| fprintf (file, ":%d", scale); |
| #endif |
| } |
| putc (')', file); |
| break; |
| } |
| if (breg != 0 && ireg == 0 && GET_CODE (addr) == LABEL_REF |
| && ! (flag_pic && breg == pic_offset_table_rtx)) |
| { |
| ASM_OUTPUT_CASE_FETCH (file, |
| CODE_LABEL_NUMBER (XEXP (addr, 0)), |
| reg_names[REGNO (breg)]); |
| fprintf (file, "l)"); |
| break; |
| } |
| if (ireg != 0 || breg != 0) |
| { |
| int scale = 1; |
| if (breg == 0) |
| { |
| abort (); |
| } |
| if (! flag_pic && addr && GET_CODE (addr) == LABEL_REF) |
| { |
| abort (); |
| } |
| #ifdef MOTOROLA |
| if (addr != 0) |
| { |
| output_addr_const (file, addr); |
| if (flag_pic && (breg == pic_offset_table_rtx)) |
| fprintf (file, "@GOT"); |
| } |
| fprintf (file, "(%s", reg_names[REGNO (breg)]); |
| if (ireg != 0) |
| { |
| putc (',', file); |
| } |
| #else |
| fprintf (file, "%s@(", reg_names[REGNO (breg)]); |
| if (addr != 0) |
| { |
| output_addr_const (file, addr); |
| if ((flag_pic == 1) && (breg == pic_offset_table_rtx)) |
| fprintf (file, ":w"); |
| if ((flag_pic == 2) && (breg == pic_offset_table_rtx)) |
| fprintf (file, ":l"); |
| } |
| if (addr != 0 && ireg != 0) |
| { |
| putc (',', file); |
| } |
| #endif |
| if (ireg != 0 && GET_CODE (ireg) == MULT) |
| { |
| scale = INTVAL (XEXP (ireg, 1)); |
| ireg = XEXP (ireg, 0); |
| } |
| if (ireg != 0 && GET_CODE (ireg) == SIGN_EXTEND) |
| { |
| #ifdef MOTOROLA |
| fprintf (file, "%s.w", reg_names[REGNO (XEXP (ireg, 0))]); |
| #else |
| fprintf (file, "%s:w", reg_names[REGNO (XEXP (ireg, 0))]); |
| #endif |
| } |
| else if (ireg != 0) |
| { |
| #ifdef MOTOROLA |
| fprintf (file, "%s.l", reg_names[REGNO (ireg)]); |
| #else |
| fprintf (file, "%s:l", reg_names[REGNO (ireg)]); |
| #endif |
| } |
| if (scale != 1) |
| { |
| #ifdef MOTOROLA |
| fprintf (file, "*%d", scale); |
| #else |
| fprintf (file, ":%d", scale); |
| #endif |
| } |
| putc (')', file); |
| break; |
| } |
| else if (reg1 != 0 && GET_CODE (addr) == LABEL_REF |
| && ! (flag_pic && reg1 == pic_offset_table_rtx)) |
| { |
| ASM_OUTPUT_CASE_FETCH (file, |
| CODE_LABEL_NUMBER (XEXP (addr, 0)), |
| reg_names[REGNO (reg1)]); |
| fprintf (file, "l)"); |
| break; |
| } |
| /* FALL-THROUGH (is this really what we want? */ |
| default: |
| if (GET_CODE (addr) == CONST_INT |
| && INTVAL (addr) < 0x8000 |
| && INTVAL (addr) >= -0x8000) |
| { |
| #ifdef MOTOROLA |
| #ifdef SGS |
| /* Many SGS assemblers croak on size specifiers for constants. */ |
| fprintf (file, "%d", INTVAL (addr)); |
| #else |
| fprintf (file, "%d.w", INTVAL (addr)); |
| #endif |
| #else |
| fprintf (file, "%d:w", INTVAL (addr)); |
| #endif |
| } |
| else |
| { |
| output_addr_const (file, addr); |
| } |
| break; |
| } |
| } |
| |
| /* Check for cases where a clr insns can be omitted from code using |
| strict_low_part sets. For example, the second clrl here is not needed: |
| clrl d0; movw a0@+,d0; use d0; clrl d0; movw a0@+; use d0; ... |
| |
| MODE is the mode of this STRICT_LOW_PART set. FIRST_INSN is the clear |
| insn we are checking for redundancy. TARGET is the register set by the |
| clear insn. */ |
| |
| int |
| strict_low_part_peephole_ok (mode, first_insn, target) |
| enum machine_mode mode; |
| rtx first_insn; |
| rtx target; |
| { |
| rtx p; |
| |
| p = prev_nonnote_insn (first_insn); |
| |
| while (p) |
| { |
| /* If it isn't an insn, then give up. */ |
| if (GET_CODE (p) != INSN) |
| return 0; |
| |
| if (reg_set_p (target, p)) |
| { |
| rtx set = single_set (p); |
| rtx dest; |
| |
| /* If it isn't an easy to recognize insn, then give up. */ |
| if (! set) |
| return 0; |
| |
| dest = SET_DEST (set); |
| |
| /* If this sets the entire target register to zero, then our |
| first_insn is redundant. */ |
| if (rtx_equal_p (dest, target) |
| && SET_SRC (set) == const0_rtx) |
| return 1; |
| else if (GET_CODE (dest) == STRICT_LOW_PART |
| && GET_CODE (XEXP (dest, 0)) == REG |
| && REGNO (XEXP (dest, 0)) == REGNO (target) |
| && (GET_MODE_SIZE (GET_MODE (XEXP (dest, 0))) |
| <= GET_MODE_SIZE (mode))) |
| /* This is a strict low part set which modifies less than |
| we are using, so it is safe. */ |
| ; |
| else |
| return 0; |
| } |
| |
| p = prev_nonnote_insn (p); |
| |
| } |
| |
| return 0; |
| } |
| |
| /* Accept integer operands in the range 0..0xffffffff. We have to check the |
| range carefully since this predicate is used in DImode contexts. Also, we |
| need some extra crud to make it work when hosted on 64-bit machines. */ |
| |
| int |
| const_uint32_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| #if HOST_BITS_PER_WIDE_INT > 32 |
| /* All allowed constants will fit a CONST_INT. */ |
| return (GET_CODE (op) == CONST_INT |
| && (INTVAL (op) >= 0 && INTVAL (op) <= 0xffffffffL)); |
| #else |
| return ((GET_CODE (op) == CONST_INT && INTVAL (op) >= 0) |
| || (GET_CODE (op) == CONST_DOUBLE && CONST_DOUBLE_HIGH (op) == 0)); |
| #endif |
| } |
| |
| /* Accept integer operands in the range -0x80000000..0x7fffffff. We have |
| to check the range carefully since this predicate is used in DImode |
|