📄 pdp11.c
字号:
/* Subroutines for gcc2 for pdp11. Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2001 Free Software Foundation, Inc. Contributed by Michael K. Gschwind (mike@vlsivie.tuwien.ac.at).This file is part of GNU CC.GNU CC is free software; you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe 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 ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with GNU CC; see the file COPYING. If not, write tothe Free Software Foundation, 59 Temple Place - Suite 330,Boston, MA 02111-1307, USA. */#include "config.h"#include "system.h"#include "rtl.h"#include "regs.h"#include "hard-reg-set.h"#include "real.h"#include "insn-config.h"#include "conditions.h"#include "function.h"#include "output.h"#include "insn-attr.h"#include "flags.h"#include "recog.h"#include "tree.h"#include "expr.h"#include "toplev.h"#include "tm_p.h"#include "target.h"#include "target-def.h"/*#define FPU_REG_P(X) ((X)>=8 && (X)<14)#define CPU_REG_P(X) ((X)>=0 && (X)<8)*//* this is the current value returned by the macro FIRST_PARM_OFFSET defined in tm.h */int current_first_parm_offset;/* This is where the condition code register lives. *//* rtx cc0_reg_rtx; - no longer needed? */static rtx find_addr_reg PARAMS ((rtx)); static const char *singlemove_string PARAMS ((rtx *));static bool pdp11_assemble_integer PARAMS ((rtx, unsigned int, int));static void pdp11_output_function_prologue PARAMS ((FILE *, HOST_WIDE_INT));static void pdp11_output_function_epilogue PARAMS ((FILE *, HOST_WIDE_INT));/* Initialize the GCC target structure. */#undef TARGET_ASM_BYTE_OP#define TARGET_ASM_BYTE_OP NULL#undef TARGET_ASM_ALIGNED_HI_OP#define TARGET_ASM_ALIGNED_HI_OP NULL#undef TARGET_ASM_ALIGNED_SI_OP#define TARGET_ASM_ALIGNED_SI_OP NULL#undef TARGET_ASM_INTEGER#define TARGET_ASM_INTEGER pdp11_assemble_integer#undef TARGET_ASM_FUNCTION_PROLOGUE#define TARGET_ASM_FUNCTION_PROLOGUE pdp11_output_function_prologue#undef TARGET_ASM_FUNCTION_EPILOGUE#define TARGET_ASM_FUNCTION_EPILOGUE pdp11_output_function_epilogue#undef TARGET_ASM_OPEN_PAREN#define TARGET_ASM_OPEN_PAREN "["#undef TARGET_ASM_CLOSE_PAREN#define TARGET_ASM_CLOSE_PAREN "]"struct gcc_target targetm = TARGET_INITIALIZER;/* Nonzero if OP is a valid second operand for an arithmetic insn. */intarith_operand (op, mode) rtx op; enum machine_mode mode;{ return (register_operand (op, mode) || GET_CODE (op) == CONST_INT);}intconst_immediate_operand (op, mode) rtx op; enum machine_mode mode ATTRIBUTE_UNUSED;{ return (GET_CODE (op) == CONST_INT);}int immediate15_operand (op, mode) rtx op; enum machine_mode mode ATTRIBUTE_UNUSED;{ return (GET_CODE (op) == CONST_INT && ((INTVAL (op) & 0x8000) == 0x0000));}intexpand_shift_operand (op, mode) rtx op; enum machine_mode mode ATTRIBUTE_UNUSED;{ return (GET_CODE (op) == CONST_INT && abs (INTVAL(op)) > 1 && abs (INTVAL(op)) <= 4);}/* 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 macro is responsible for knowing which registers should not be saved even if used. */#ifdef TWO_BSDstatic voidpdp11_output_function_prologue (stream, size) FILE *stream; HOST_WIDE_INT size;{ fprintf (stream, "\tjsr r5, csv\n"); if (size) { fprintf (stream, "\t/*abuse empty parameter slot for locals!*/\n"); if (size > 2) fprintf(stream, "\tsub $%#o, sp\n", size - 2); }}#else /* !TWO_BSD */static voidpdp11_output_function_prologue (stream, size) FILE *stream; HOST_WIDE_INT size;{ HOST_WIDE_INT fsize = ((size) + 1) & ~1; int regno; int via_ac = -1; fprintf (stream, "\n\t; /* function prologue %s*/\n", current_function_name); /* if we are outputting code for main, the switch FPU to right mode if TARGET_FPU */ if (MAIN_NAME_P (DECL_NAME (current_function_decl)) && TARGET_FPU) { fprintf(stream, "\t;/* switch cpu to double float, single integer */\n"); fprintf(stream, "\tsetd\n"); fprintf(stream, "\tseti\n\n"); } if (frame_pointer_needed) { fprintf(stream, "\tmov r5, -(sp)\n"); fprintf(stream, "\tmov sp, r5\n"); } else { /* DON'T SAVE FP */ } /* make frame */ if (fsize) fprintf (stream, "\tsub $%#o, sp\n", fsize); /* save CPU registers */ for (regno = 0; regno < 8; regno++) if (regs_ever_live[regno] && ! call_used_regs[regno]) if (! ((regno == FRAME_POINTER_REGNUM) && frame_pointer_needed)) fprintf (stream, "\tmov %s, -(sp)\n", reg_names[regno]); /* fpu regs saving */ /* via_ac specifies the ac to use for saving ac4, ac5 */ via_ac = -1; for (regno = 8; regno < FIRST_PSEUDO_REGISTER ; regno++) { /* ac0 - ac3 */ if (LOAD_FPU_REG_P(regno) && regs_ever_live[regno] && ! call_used_regs[regno]) { fprintf (stream, "\tstd %s, -(sp)\n", reg_names[regno]); via_ac = regno; } /* maybe make ac4, ac5 call used regs?? */ /* ac4 - ac5 */ if (NO_LOAD_FPU_REG_P(regno) && regs_ever_live[regno] && ! call_used_regs[regno]) { if (via_ac == -1) abort(); fprintf (stream, "\tldd %s, %s\n", reg_names[regno], reg_names[via_ac]); fprintf (stream, "\tstd %s, -(sp)\n", reg_names[via_ac]); } } fprintf (stream, "\t;/* end of prologue */\n\n"); }#endif /* !TWO_BSD *//* The function epilogue should not depend on the current stack pointer! It should use the frame pointer only. This is mandatory because of alloca; we also take advantage of it to omit stack adjustments before returning. *//* maybe we can make leaf functions faster by switching to the second register file - this way we don't have to save regs! leaf functions are ~ 50% of all functions (dynamically!) set/clear bit 11 (dec. 2048) of status word for switching register files - but how can we do this? the pdp11/45 manual says bit may only be set (p.24), but not cleared! switching to kernel is probably more expensive, so we'll leave it like this and not use the second set of registers... maybe as option if you want to generate code for kernel mode? */#ifdef TWO_BSDstatic voidpdp11_output_function_epilogue (stream, size) FILE *stream; HOST_WIDE_INT size ATTRIBUTE_UNUSED;{ fprintf (stream, "\t/* SP ignored by cret? */\n"); fprintf (stream, "\tjmp cret\n");}#else /* !TWO_BSD */static voidpdp11_output_function_epilogue (stream, size) FILE *stream; HOST_WIDE_INT size;{ HOST_WIDE_INT fsize = ((size) + 1) & ~1; int i, j, k; int via_ac; fprintf (stream, "\n\t; /*function epilogue */\n"); if (frame_pointer_needed) { /* hope this is safe - m68k does it also .... */ regs_ever_live[FRAME_POINTER_REGNUM] = 0; for (i =7, j = 0 ; i >= 0 ; i--) if (regs_ever_live[i] && ! call_used_regs[i]) j++; /* remember # of pushed bytes for CPU regs */ k = 2*j; /* change fp -> r5 due to the compile error on libgcc2.c */ for (i =7 ; i >= 0 ; i--) if (regs_ever_live[i] && ! call_used_regs[i]) fprintf(stream, "\tmov %#o(r5), %s\n",(-fsize-2*j--)&0xffff, reg_names[i]); /* get ACs */ via_ac = FIRST_PSEUDO_REGISTER -1; for (i = FIRST_PSEUDO_REGISTER; i > 7; i--) if (regs_ever_live[i] && ! call_used_regs[i]) { via_ac = i; k += 8; } for (i = FIRST_PSEUDO_REGISTER; i > 7; i--) { if (LOAD_FPU_REG_P(i) && regs_ever_live[i] && ! call_used_regs[i]) { fprintf(stream, "\tldd %#o(r5), %s\n", (-fsize-k)&0xffff, reg_names[i]); k -= 8; } if (NO_LOAD_FPU_REG_P(i) && regs_ever_live[i] && ! call_used_regs[i]) { if (! LOAD_FPU_REG_P(via_ac)) abort(); fprintf(stream, "\tldd %#o(r5), %s\n", (-fsize-k)&0xffff, reg_names[via_ac]); fprintf(stream, "\tstd %s, %s\n", reg_names[via_ac], reg_names[i]); k -= 8; } } fprintf(stream, "\tmov r5, sp\n"); fprintf (stream, "\tmov (sp)+, r5\n"); } else { via_ac = FIRST_PSEUDO_REGISTER -1; /* get ACs */ for (i = FIRST_PSEUDO_REGISTER; i > 7; i--) if (regs_ever_live[i] && call_used_regs[i]) via_ac = i; for (i = FIRST_PSEUDO_REGISTER; i > 7; i--) { if (LOAD_FPU_REG_P(i) && regs_ever_live[i] && ! call_used_regs[i]) fprintf(stream, "\tldd (sp)+, %s\n", reg_names[i]); if (NO_LOAD_FPU_REG_P(i) && regs_ever_live[i] && ! call_used_regs[i]) { if (! LOAD_FPU_REG_P(via_ac)) abort(); fprintf(stream, "\tldd (sp)+, %s\n", reg_names[via_ac]); fprintf(stream, "\tstd %s, %s\n", reg_names[via_ac], reg_names[i]); } } for (i=7; i >= 0; i--) if (regs_ever_live[i] && !call_used_regs[i]) fprintf(stream, "\tmov (sp)+, %s\n", reg_names[i]); if (fsize) fprintf((stream), "\tadd $%#o, sp\n", (fsize)&0xffff); } fprintf (stream, "\trts pc\n"); fprintf (stream, "\t;/* end of epilogue*/\n\n\n"); }#endif /* !TWO_BSD */ /* Return the best assembler insn template for moving operands[1] into operands[0] as a fullword. */static const char *singlemove_string (operands) rtx *operands;{ if (operands[1] != const0_rtx) return "mov %1,%0"; return "clr %0";}/* Output assembler code to perform a doubleword move insn with operands OPERANDS. */const char *output_move_double (operands) rtx *operands;{ enum { REGOP, OFFSOP, MEMOP, PUSHOP, POPOP, CNSTOP, RNDOP } optype0, optype1; rtx latehalf[2]; rtx addreg0 = 0, addreg1 = 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])#if 0 || GET_CODE (operands[1]) == CONST_DOUBLE#endif ) 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); output_asm_insn ("sub $4,%0", operands); operands[0] = gen_rtx_MEM (SImode, operands[0]); optype0 = OFFSOP; } if (optype0 == POPOP && optype1 == PUSHOP) { operands[1] = XEXP (XEXP (operands[1], 0), 0); output_asm_insn ("sub $4,%1", operands); operands[1] = gen_rtx_MEM (SImode, 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 (optype0 == REGOP) latehalf[0] = gen_rtx_REG (HImode, REGNO (operands[0]) + 1); else if (optype0 == OFFSOP) latehalf[0] = adjust_address (operands[0], HImode, 2); else latehalf[0] = operands[0]; if (optype1 == REGOP) latehalf[1] = gen_rtx_REG (HImode, REGNO (operands[1]) + 1); else if (optype1 == OFFSOP) latehalf[1] = adjust_address (operands[1], HImode, 2); else if (optype1 == CNSTOP) { if (CONSTANT_P (operands[1])) { /* now the mess begins, high word is in lower word??? that's what ashc makes me think, but I don't remember :-( */ latehalf[1] = GEN_INT (INTVAL(operands[1]) >> 16); operands[1] = GEN_INT (INTVAL(operands[1]) & 0xff); } else if (GET_CODE (operands[1]) == CONST_DOUBLE) { /* immediate 32 bit values not allowed */ abort(); } } 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] = latehalf[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 && REGNO (operands[0]) == REGNO (latehalf[1]))) { /* Make any unoffsettable addresses point at high-numbered word. */ if (addreg0) output_asm_insn ("add $2,%0", &addreg0); if (addreg1) output_asm_insn ("add $2,%0", &addreg1); /* Do that word. */ output_asm_insn (singlemove_string (latehalf), latehalf); /* Undo the adds we just did. */ if (addreg0) output_asm_insn ("sub $2,%0", &addreg0); if (addreg1) output_asm_insn ("sub $2,%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); /* Make any unoffsettable addresses point at high-numbered word. */ if (addreg0) output_asm_insn ("add $2,%0", &addreg0); if (addreg1)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -