| /* Subroutines for insn-output.c for Clipper |
| Copyright (C) 1987, 1988, 1991, 1997 Free Software Foundation, Inc. |
| Contributed by Holger Teutsch (holger@hotbso.rhein-main.de) |
| |
| 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 "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" |
| #include "tree.h" |
| #include "c-tree.h" |
| #include "expr.h" |
| #include "flags.h" |
| #include "machmode.h" |
| |
| extern char regs_ever_live[]; |
| |
| extern int frame_pointer_needed; |
| |
| static int frame_size; |
| |
| /* |
| * compute size of a clipper stack frame where 'lsize' is the required |
| * space for local variables. |
| */ |
| |
| int |
| clipper_frame_size (lsize) |
| int lsize; |
| { |
| int i,size; /* total size of frame */ |
| int save_size; |
| save_size = 0; /* compute size for reg saves */ |
| |
| for (i = 16; i < 32; i++) |
| if (regs_ever_live[i] && !call_used_regs[i]) |
| save_size += 8; |
| |
| for (i = 0; i < 16; i++) |
| if (regs_ever_live[i] && !call_used_regs[i]) |
| save_size += 4; |
| |
| size = lsize + save_size; |
| |
| size = (size + 7) & ~7; /* align to 64 Bit */ |
| return size; |
| } |
| |
| /* |
| * prologue and epilogue output |
| * function is entered with pc pushed, i.e. stack is 32 bit aligned |
| * |
| * current_function_args_size == 0 means that the current function's args |
| * are passed totally in registers i.e fp is not used as ap. |
| * If frame_size is also 0 the current function does not push anything and |
| * can run with misaligned stack -> subq $4,sp / add $4,sp on entry and exit |
| * can be omitted. |
| * |
| */ |
| void |
| output_function_prologue (file, lsize) |
| FILE *file; |
| int lsize; /* size for locals */ |
| { |
| int i, offset; |
| int size; |
| |
| frame_size = size = clipper_frame_size (lsize); |
| |
| if (frame_pointer_needed) |
| { |
| fputs ("\tpushw fp,sp\n", file); |
| fputs ("\tmovw sp,fp\n", file); |
| } |
| else if (size != 0 || current_function_args_size != 0) |
| { |
| size += 4; /* keep stack aligned */ |
| frame_size = size; /* must push data or access args */ |
| } |
| |
| if (size) |
| { |
| if (size < 16) |
| fprintf (file, "\tsubq $%d,sp\n", size); |
| else |
| fprintf (file, "\tsubi $%d,sp\n", size); |
| |
| /* register save slots are relative to sp, because we have small positive |
| displacements and this works whether we have a frame pointer or not */ |
| |
| offset = 0; |
| for (i = 16; i < 32; i++) |
| if (regs_ever_live[i] && !call_used_regs[i]) |
| { |
| if (offset == 0) |
| fprintf (file, "\tstord f%d,(sp)\n", i-16); |
| else |
| fprintf (file, "\tstord f%d,%d(sp)\n", i-16, offset); |
| offset += 8; |
| } |
| |
| for (i = 0; i < 16; i++) |
| if (regs_ever_live[i] && !call_used_regs[i]) |
| { |
| if (offset == 0) |
| fprintf (file, "\tstorw r%d,(sp)\n", i); |
| else |
| fprintf (file, "\tstorw r%d,%d(sp)\n", i, offset); |
| offset += 4; |
| } |
| } |
| } |
| |
| void |
| output_function_epilogue (file, size) |
| FILE *file; |
| int size; /* ignored */ |
| { |
| int i, offset; |
| |
| if (frame_pointer_needed) |
| { |
| offset = -frame_size; |
| |
| for (i = 16; i < 32; i++) |
| if (regs_ever_live[i] && !call_used_regs[i]) |
| { |
| fprintf (file, "\tloadd %d(fp),f%d\n", offset, i-16); |
| offset += 8; |
| } |
| |
| for (i = 0; i < 16; i++) |
| if (regs_ever_live[i] && !call_used_regs[i]) |
| { |
| fprintf (file, "\tloadw %d(fp),r%d\n", offset, i); |
| offset += 4; |
| } |
| |
| fputs ("\tmovw fp,sp\n\tpopw sp,fp\n\tret sp\n", |
| file); |
| } |
| |
| else /* no frame pointer */ |
| { |
| offset = 0; |
| |
| for (i = 16; i < 32; i++) |
| if (regs_ever_live[i] && !call_used_regs[i]) |
| { |
| if (offset == 0) |
| fprintf (file, "\tloadd (sp),f%d\n", i-16); |
| else |
| fprintf (file, "\tloadd %d(sp),f%d\n", offset, i-16); |
| offset += 8; |
| } |
| |
| for (i = 0; i < 16; i++) |
| if (regs_ever_live[i] && !call_used_regs[i]) |
| { |
| if (offset == 0) |
| fprintf (file, "\tloadw (sp),r%d\n", i); |
| else |
| fprintf (file, "\tloadw %d(sp),r%d\n", offset, i); |
| offset += 4; |
| } |
| |
| if (frame_size > 0) |
| { |
| if (frame_size < 16) |
| fprintf (file, "\taddq $%d,sp\n", frame_size); |
| else |
| fprintf (file, "\taddi $%d,sp\n", frame_size); |
| } |
| |
| fputs ("\tret sp\n", file); |
| } |
| } |
| |
| /* |
| * blockmove |
| * |
| * clipper_movstr () |
| */ |
| void |
| clipper_movstr (operands) |
| rtx *operands; |
| { |
| rtx dst,src,cnt,tmp,top,bottom,xops[3]; |
| int align; |
| int fixed; |
| |
| extern FILE *asm_out_file; |
| |
| dst = operands[0]; |
| src = operands[1]; |
| /* don't change this operands[2]; gcc 2.3.3 doesn't honor clobber note */ |
| align = INTVAL (operands[3]); |
| tmp = operands[4]; |
| cnt = operands[5]; |
| |
| if (GET_CODE (operands[2]) == CONST_INT) /* fixed size move */ |
| { |
| if ((fixed = INTVAL (operands[2])) <= 0) |
| abort (); |
| |
| if (fixed <16) |
| output_asm_insn ("loadq %2,%5", operands); |
| else |
| output_asm_insn ("loadi %2,%5", operands); |
| } |
| else |
| { |
| fixed = 0; |
| bottom = (rtx)gen_label_rtx (); /* need a bottom label */ |
| xops[0] = cnt; xops[1] = bottom; |
| output_asm_insn ("movw %2,%5", operands); /* count is scratch reg 5 */ |
| output_asm_insn ("brle %l1", xops); |
| } |
| |
| |
| top = (rtx)gen_label_rtx (); /* top of loop label */ |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (top)); |
| |
| |
| xops[0] = src; xops[1] = tmp; xops[2] = dst; |
| |
| if (fixed && (align & 0x3) == 0) /* word aligned move with known size */ |
| { |
| if (fixed >= 4) |
| { |
| rtx xops1[2]; |
| output_asm_insn( |
| "loadw %a0,%1\n\taddq $4,%0\n\tstorw %1,%a2\n\taddq $4,%2", |
| xops); |
| |
| xops1[0] = cnt; xops1[1] = top; |
| output_asm_insn ("subq $4,%0\n\tbrgt %l1", xops1); |
| } |
| |
| if (fixed & 0x2) |
| { |
| output_asm_insn ("loadh %a0,%1\n\tstorh %1,%a2", xops); |
| if (fixed & 0x1) |
| output_asm_insn ("loadb 2%a0,%1\n\tstorb %1,2%a2", xops); |
| } |
| else |
| if (fixed & 0x1) |
| output_asm_insn ("loadb %a0,%1\n\tstorb %1,%a2", xops); |
| } |
| else |
| { |
| output_asm_insn( |
| "loadb %a0,%1\n\taddq $1,%0\n\tstorb %1,%a2\n\taddq $1,%2", |
| xops); |
| |
| xops[0] = cnt; xops[1] = top; |
| output_asm_insn ("subq $1,%0\n\tbrgt %l1", xops); |
| } |
| |
| if (fixed == 0) |
| ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, "L", CODE_LABEL_NUMBER (bottom)); |
| } |
| |
| |
| print_operand_address (file, addr) |
| FILE *file; |
| register rtx addr; |
| { |
| rtx op0,op1; |
| |
| retry: |
| switch (GET_CODE (addr)) |
| { |
| case REG: |
| fprintf (file, "(%s)", reg_names[REGNO (addr)]); |
| break; |
| |
| case PLUS: |
| /* can be 'symbol + reg' or 'reg + reg' */ |
| |
| op0 = XEXP (addr, 0); |
| op1 = XEXP (addr, 1); |
| |
| if (GET_CODE (op0) == REG && GET_CODE (op1) == REG) |
| { |
| fprintf (file, "[%s](%s)", |
| reg_names[REGNO (op0)], reg_names[REGNO (op1)]); |
| break; |
| } |
| |
| if (GET_CODE (op0) == REG && CONSTANT_ADDRESS_P (op1)) |
| { |
| output_addr_const (file, op1); |
| fprintf (file, "(%s)", reg_names[REGNO (op0)]); |
| break; |
| } |
| |
| if (GET_CODE (op1) == REG && CONSTANT_ADDRESS_P (op0)) |
| { |
| output_addr_const (file, op0); |
| fprintf (file, "(%s)", reg_names[REGNO (op1)]); |
| break; |
| } |
| abort (); /* Oh no */ |
| |
| default: |
| output_addr_const (file, addr); |
| } |
| } |
| |
| |
| char * |
| rev_cond_name (op) |
| rtx op; |
| { |
| switch (GET_CODE (op)) |
| { |
| case EQ: |
| return "ne"; |
| case NE: |
| return "eq"; |
| case LT: |
| return "ge"; |
| case LE: |
| return "gt"; |
| case GT: |
| return "le"; |
| case GE: |
| return "lt"; |
| case LTU: |
| return "geu"; |
| case LEU: |
| return "gtu"; |
| case GTU: |
| return "leu"; |
| case GEU: |
| return "ltu"; |
| |
| default: |
| abort (); |
| } |
| } |
| |
| |
| /* Do what is necessary for `va_start'. The argument is ignored; |
| We fill in an initial va_list. A pointer to this constructor |
| is returned. */ |
| |
| |
| struct rtx_def * |
| clipper_builtin_saveregs (arglist) |
| tree arglist; |
| { |
| extern int current_function_varargs; |
| rtx block, addr, argsize, scratch, r0_addr,r1_addr,f0_addr,f1_addr; |
| |
| /* Allocate the va_list constructor + save area for r0,r1,f0,f1 */ |
| |
| block = assign_stack_local (BLKmode, |
| (6 + 6) * UNITS_PER_WORD, 2 * BITS_PER_WORD); |
| |
| RTX_UNCHANGING_P (block) = 1; |
| RTX_UNCHANGING_P (XEXP (block, 0)) = 1; |
| |
| addr = copy_to_reg (XEXP (block, 0)); |
| |
| f0_addr = gen_rtx (PLUS, Pmode, addr, gen_rtx (CONST_INT, Pmode, 24)); |
| f1_addr = gen_rtx (PLUS, Pmode, addr, gen_rtx (CONST_INT, Pmode, 32)); |
| r0_addr = gen_rtx (PLUS, Pmode, addr, gen_rtx (CONST_INT, Pmode, 40)); |
| r1_addr = gen_rtx (PLUS, Pmode, addr, gen_rtx (CONST_INT, Pmode, 44)); |
| |
| |
| /* Store float regs */ |
| |
| emit_move_insn (gen_rtx (MEM, DFmode, f0_addr), gen_rtx (REG, DFmode, 16)); |
| emit_move_insn (gen_rtx (MEM, DFmode, f1_addr), gen_rtx (REG, DFmode, 17)); |
| |
| /* Store int regs */ |
| |
| emit_move_insn (gen_rtx (MEM, SImode, r0_addr), gen_rtx (REG, SImode, 0)); |
| emit_move_insn (gen_rtx (MEM, SImode, r1_addr), gen_rtx (REG, SImode, 1)); |
| |
| /* Store the arg pointer in the __va_stk member. */ |
| |
| emit_move_insn (gen_rtx (MEM, SImode, addr), |
| copy_to_reg (virtual_incoming_args_rtx)); |
| |
| |
| /* now move addresses of the saved regs into the pointer array */ |
| |
| scratch = gen_reg_rtx (Pmode); |
| |
| emit_move_insn (scratch, r0_addr); |
| emit_move_insn (gen_rtx (MEM, SImode, |
| gen_rtx (PLUS, Pmode, addr, |
| gen_rtx (CONST_INT, Pmode, 4))), |
| scratch); |
| |
| emit_move_insn (scratch, f0_addr); |
| emit_move_insn (gen_rtx (MEM, SImode, |
| gen_rtx (PLUS, Pmode, addr, |
| gen_rtx (CONST_INT, Pmode, 8))), |
| scratch); |
| |
| emit_move_insn (scratch, r1_addr); |
| emit_move_insn (gen_rtx (MEM, SImode, |
| gen_rtx (PLUS, Pmode, addr, |
| gen_rtx (CONST_INT, Pmode, 12))), |
| scratch); |
| |
| emit_move_insn (scratch, f1_addr); |
| emit_move_insn (gen_rtx (MEM, SImode, |
| gen_rtx (PLUS, Pmode, addr, |
| gen_rtx (CONST_INT, Pmode, 16))), |
| scratch); |
| |
| |
| if (flag_check_memory_usage) |
| { |
| emit_library_call (chkr_set_right_libfunc, 1, VOIDmode, 3, addr, |
| ptr_mode, GEN_INT (5 * GET_MODE_SIZE (SImode)), |
| TYPE_MODE (sizetype), |
| GEN_INT (MEMORY_USE_RW), QImode); |
| |
| emit_library_call (chkr_set_right_libfunc, 1, VOIDmode, 3, f0_addr, |
| ptr_mode, GEN_INT (GET_MODE_SIZE (DFmode)), |
| TYPE_MODE (sizetype), GEN_INT (MEMORY_USE_RW), QImode); |
| emit_library_call (chkr_set_right_libfunc, 1, VOIDmode, 3, f1_addr, |
| ptr_mode, GEN_INT (GET_MODE_SIZE (DFmode)), |
| TYPE_MODE (sizetype), GEN_INT (MEMORY_USE_RW), QImode); |
| emit_library_call (chkr_set_right_libfunc, 1, VOIDmode, 3, r0_addr, |
| ptr_mode, GEN_INT (GET_MODE_SIZE (SImode)), |
| TYPE_MODE (sizetype), GEN_INT (MEMORY_USE_RW), QImode); |
| emit_library_call (chkr_set_right_libfunc, 1, VOIDmode, 3, r1_addr, |
| ptr_mode, GEN_INT (GET_MODE_SIZE (SImode)), |
| TYPE_MODE (sizetype), GEN_INT (MEMORY_USE_RW), QImode); |
| } |
| |
| /* Return the address of the va_list constructor, but don't put it in a |
| register. This fails when not optimizing and produces worse code when |
| optimizing. */ |
| return XEXP (block, 0); |
| } |
| |
| |
| /* Return truth value of whether OP can be used as an word register |
| operand. Reject (SUBREG:SI (REG:SF )) */ |
| |
| int |
| int_reg_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return (register_operand (op, mode) && |
| (GET_CODE (op) != SUBREG || |
| GET_MODE_CLASS (GET_MODE (SUBREG_REG (op))) == MODE_INT)); |
| } |
| |
| /* Return truth value of whether OP can be used as a float register |
| operand. Reject (SUBREG:SF (REG:SI )) )) */ |
| |
| int |
| fp_reg_operand (op, mode) |
| rtx op; |
| enum machine_mode mode; |
| { |
| return (register_operand (op, mode) && |
| (GET_CODE (op) != SUBREG || |
| GET_MODE_CLASS (GET_MODE (SUBREG_REG (op))) == MODE_FLOAT)); |
| } |
| |