📄 sh.c
字号:
/* Output routines for GCC for Hitachi Super-H. Copyright (C) 1993, 1994, 1995 Free Software Foundation, Inc.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. *//* Contributed by Steve Chamberlain (sac@cygnus.com). Improved by Jim Wilson (wilson@cygnus.com). */#include "config.h"#include <stdio.h>#include "rtl.h"#include "tree.h"#include "flags.h"#include "insn-flags.h"#include "expr.h"#include "regs.h"#include "hard-reg-set.h"#include "output.h"#define MSW (TARGET_LITTLE_ENDIAN ? 1 : 0)#define LSW (TARGET_LITTLE_ENDIAN ? 0 : 1)/* ??? The pragma interrupt support will not work for SH3. *//* This is set by #pragma interrupt and #pragma trapa, and causes gcc to output code for the next function appropriate for an interrupt handler. */int pragma_interrupt;/* This is set by #pragma trapa, and is similar to the above, except that the compiler doesn't emit code to preserve all registers. */static int pragma_trapa;/* This is used for communication between SETUP_INCOMING_VARARGS and sh_expand_prologue. */int current_function_anonymous_args;/* Global variables from toplev.c and final.c that are used within, but not declared in any header file. */extern char *version_string;extern int *insn_addresses;/* Global variables for machine-dependent things. *//* Which cpu are we scheduling for. */enum processor_type sh_cpu;/* Saved operands from the last compare to use when we generate an scc or bcc insn. */rtx sh_compare_op0;rtx sh_compare_op1;/* Provides the class number of the smallest class containing reg number. */int regno_reg_class[FIRST_PSEUDO_REGISTER] ={ R0_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, PR_REGS, T_REGS, NO_REGS, MAC_REGS, MAC_REGS,};/* Provide reg_class from a letter such as appears in the machine description. */enum reg_class reg_class_from_letter[] ={ /* a */ NO_REGS, /* b */ NO_REGS, /* c */ NO_REGS, /* d */ NO_REGS, /* e */ NO_REGS, /* f */ NO_REGS, /* g */ NO_REGS, /* h */ NO_REGS, /* i */ NO_REGS, /* j */ NO_REGS, /* k */ NO_REGS, /* l */ PR_REGS, /* m */ NO_REGS, /* n */ NO_REGS, /* o */ NO_REGS, /* p */ NO_REGS, /* q */ NO_REGS, /* r */ NO_REGS, /* s */ NO_REGS, /* t */ T_REGS, /* u */ NO_REGS, /* v */ NO_REGS, /* w */ NO_REGS, /* x */ MAC_REGS, /* y */ NO_REGS, /* z */ R0_REGS};/* Print the operand address in x to the stream. */voidprint_operand_address (stream, x) FILE *stream; rtx x;{ switch (GET_CODE (x)) { case REG: fprintf (stream, "@%s", reg_names[REGNO (x)]); break; case PLUS: { rtx base = XEXP (x, 0); rtx index = XEXP (x, 1); switch (GET_CODE (index)) { case CONST_INT: fprintf (stream, "@(%d,%s)", INTVAL (index), reg_names[REGNO (base)]); break; case REG: fprintf (stream, "@(r0,%s)", reg_names[MAX (REGNO (base), REGNO (index))]); break; default: debug_rtx (x); abort (); } } break; case PRE_DEC: fprintf (stream, "@-%s", reg_names[REGNO (XEXP (x, 0))]); break; case POST_INC: fprintf (stream, "@%s+", reg_names[REGNO (XEXP (x, 0))]); break; default: output_addr_const (stream, x); break; }}/* Print operand x (an rtx) in assembler syntax to file stream according to modifier code. '.' print a .s if insn needs delay slot '@' print rte or rts depending upon pragma interruptness '#' output a nop if there is nothing to put in the delay slot 'O' print a constant without the # 'R' print the LSW of a dp value - changes if in little endian 'S' print the MSW of a dp value - changes if in little endian 'T' print the next word of a dp value - same as 'R' in big endian mode. */voidprint_operand (stream, x, code) FILE *stream; rtx x; int code;{ switch (code) { case '.': if (final_sequence && ! INSN_ANNULLED_BRANCH_P (XVECEXP (final_sequence, 0, 0))) fprintf (stream, ".s"); break; case '@': if (pragma_interrupt) fprintf (stream, "rte"); else fprintf (stream, "rts"); break; case '#': /* Output a nop if there's nothing in the delay slot. */ if (dbr_sequence_length () == 0) fprintf (stream, "\n\tnop"); break; case 'O': output_addr_const (stream, x); break; case 'R': fputs (reg_names[REGNO (x) + LSW], (stream)); break; case 'S': fputs (reg_names[REGNO (x) + MSW], (stream)); break; case 'T': /* Next word of a double. */ switch (GET_CODE (x)) { case REG: fputs (reg_names[REGNO (x) + 1], (stream)); break; case MEM: print_operand_address (stream, XEXP (adj_offsettable_operand (x, 4), 0)); break; } break; default: switch (GET_CODE (x)) { case REG: fputs (reg_names[REGNO (x)], (stream)); break; case MEM: output_address (XEXP (x, 0)); break; default: fputc ('#', stream); output_addr_const (stream, x); break; } break; }}/* Emit code to perform a block move. Choose the best method. OPERANDS[0] is the destination. OPERANDS[1] is the source. OPERANDS[2] is the size. OPERANDS[3] is the alignment safe to use. */intexpand_block_move (operands) rtx *operands;{ int align = INTVAL (operands[3]); int constp = (GET_CODE (operands[2]) == CONST_INT); int bytes = (constp ? INTVAL (operands[2]) : 0); /* If it isn't a constant number of bytes, or if it doesn't have 4 byte alignment, or if it isn't a multiple of 4 bytes, then fail. */ if (! constp || align < 4 || (bytes % 4 != 0)) return 0; if (bytes < 64) { char entry[30]; tree entry_name; rtx func_addr_rtx; rtx r4 = gen_rtx (REG, SImode, 4); rtx r5 = gen_rtx (REG, SImode, 5); sprintf (entry, "__movstrSI%d", bytes); entry_name = get_identifier (entry); func_addr_rtx = copy_to_mode_reg (Pmode, gen_rtx (SYMBOL_REF, Pmode, IDENTIFIER_POINTER (entry_name))); emit_insn (gen_move_insn (r4, XEXP (operands[0], 0))); emit_insn (gen_move_insn (r5, XEXP (operands[1], 0))); emit_insn (gen_block_move_real (func_addr_rtx)); return 1; } /* This is the same number of bytes as a memcpy call, but to a different less common function name, so this will occasionally use more space. */ if (! TARGET_SMALLCODE) { tree entry_name; rtx func_addr_rtx; int final_switch, while_loop; rtx r4 = gen_rtx (REG, SImode, 4); rtx r5 = gen_rtx (REG, SImode, 5); rtx r6 = gen_rtx (REG, SImode, 6); entry_name = get_identifier ("__movstr"); func_addr_rtx = copy_to_mode_reg (Pmode, gen_rtx (SYMBOL_REF, Pmode, IDENTIFIER_POINTER (entry_name))); emit_insn (gen_move_insn (r4, XEXP (operands[0], 0))); emit_insn (gen_move_insn (r5, XEXP (operands[1], 0))); /* r6 controls the size of the move. 16 is decremented from it for each 64 bytes moved. Then the negative bit left over is used as an index into a list of move instructions. e.g., a 72 byte move would be set up with size(r6) = 14, for one iteration through the big while loop, and a switch of -2 for the last part. */ final_switch = 16 - ((bytes / 4) % 16); while_loop = ((bytes / 4) / 16 - 1) * 16; emit_insn (gen_move_insn (r6, GEN_INT (while_loop + final_switch))); emit_insn (gen_block_lump_real (func_addr_rtx)); return 1; } return 0;}/* Prepare operands for a move define_expand; specifically, one of the operands must be in a register. */intprepare_move_operands (operands, mode) rtx operands[]; enum machine_mode mode;{ /* Copy the source to a register if both operands aren't registers. */ if (! reload_in_progress && ! reload_completed && ! register_operand (operands[0], mode) && ! register_operand (operands[1], mode)) operands[1] = copy_to_mode_reg (mode, operands[1]); return 0;}/* Prepare the operands for an scc instruction; make sure that the compare has been done. */rtxprepare_scc_operands (code) enum rtx_code code;{ rtx t_reg = gen_rtx (REG, SImode, T_REG); enum rtx_code oldcode = code; enum machine_mode mode; /* First need a compare insn. */ switch (code) { case NE: /* It isn't possible to handle this case. */ abort (); case LT: code = GT; break; case LE: code = GE; break; case LTU: code = GTU; break; case LEU: code = GEU; break; } if (code != oldcode) { rtx tmp = sh_compare_op0; sh_compare_op0 = sh_compare_op1; sh_compare_op1 = tmp; } mode = GET_MODE (sh_compare_op0); if (mode == VOIDmode) mode = GET_MODE (sh_compare_op1); sh_compare_op0 = force_reg (mode, sh_compare_op0); if (code != EQ && code != NE && (sh_compare_op1 != const0_rtx || code == GTU || code == GEU || code == LTU || code == LEU)) sh_compare_op1 = force_reg (mode, sh_compare_op1); emit_insn (gen_rtx (SET, VOIDmode, t_reg, gen_rtx (code, SImode, sh_compare_op0, sh_compare_op1))); return t_reg;}/* Called from the md file, set up the operands of a compare instruction. */voidfrom_compare (operands, code) rtx *operands; int code;{ if (code != EQ && code != NE) { /* Force args into regs, since we can't use constants here. */ sh_compare_op0 = force_reg (SImode, sh_compare_op0); if (sh_compare_op1 != const0_rtx || code == GTU || code == GEU || code == LTU || code == LEU) sh_compare_op1 = force_reg (SImode, sh_compare_op1); } operands[1] = sh_compare_op0; operands[2] = sh_compare_op1;}/* Functions to output assembly code. *//* Return a sequence of instructions to perform DI or DF move. Since the SH cannot move a DI or DF in one instruction, we have to take care when we see overlapping source and dest registers. */char *output_movedouble (insn, operands, mode) rtx insn; rtx operands[]; enum machine_mode mode;{ rtx dst = operands[0]; rtx src = operands[1]; if (GET_CODE (dst) == MEM && GET_CODE (XEXP (dst, 0)) == PRE_DEC) return "mov.l %T1,%0\n\tmov.l %1,%0"; if (register_operand (dst, mode) && register_operand (src, mode)) { if (REGNO (src) == MACH_REG) return "sts mach,%S0\n\tsts macl,%R0"; /* When mov.d r1,r2 do r2->r3 then r1->r2; when mov.d r1,r0 do r1->r0 then r2->r1. */ if (REGNO (src) + 1 == REGNO (dst)) return "mov %T1,%T0\n\tmov %1,%0"; else return "mov %1,%0\n\tmov %T1,%T0"; } else if (GET_CODE (src) == CONST_INT) { if (INTVAL (src) < 0) output_asm_insn ("mov #-1,%S0", operands); else output_asm_insn ("mov #0,%S0", operands); return "mov %1,%R0"; } else if (GET_CODE (src) == MEM) { int ptrreg = -1; int dreg = REGNO (dst); rtx inside = XEXP (src, 0); if (GET_CODE (inside) == REG) ptrreg = REGNO (inside); else if (GET_CODE (inside) == SUBREG) ptrreg = REGNO (SUBREG_REG (inside)) + SUBREG_WORD (inside); else if (GET_CODE (inside) == PLUS) { ptrreg = REGNO (XEXP (inside, 0)); /* ??? A r0+REG address shouldn't be possible here, because it isn't an offsettable address. Unfortunately, offsettable addresses use QImode to check the offset, and a QImode offsettable address requires r0 for the other operand, which is not currently supported, so we can't use the 'o' constraint. Thus we must check for and handle r0+REG addresses here. We punt for now, since this is likely very rare. */ if (GET_CODE (XEXP (inside, 1)) == REG) abort (); } else if (GET_CODE (inside) == LABEL_REF) return "mov.l %1,%0\n\tmov.l %1+4,%T0"; else if (GET_CODE (inside) == POST_INC) return "mov.l %1,%0\n\tmov.l %1,%T0"; else abort (); /* Work out the safe way to copy. Copy into the second half first. */ if (dreg == ptrreg) return "mov.l %T1,%T0\n\tmov.l %1,%0"; } return "mov.l %1,%0\n\tmov.l %T1,%T0";}/* Print an instruction which would have gone into a delay slot after another instruction, but couldn't because the other instruction expanded into a sequence where putting the slot insn at the end wouldn't work. */static voidprint_slot (insn) rtx insn;{ final_scan_insn (XVECEXP (insn, 0, 1), asm_out_file, optimize, 0, 1); INSN_DELETED_P (XVECEXP (insn, 0, 1)) = 1;}/* We can't tell if we need a register as a scratch for the jump until after branch shortening, and then it's too late to allocate a register the 'proper' way. These instruction sequences are rare anyway, so to avoid always using a reg up from our limited set, we'll grab one when we need one on output. *//* ??? Should fix compiler so that using a clobber scratch in jump instructions works, and then this will be unnecessary. */char *
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -