From 188538df24e0fb2129a05830b85f17d53d7a418f Mon Sep 17 00:00:00 2001 From: Torbjorn Granlund Date: Fri, 10 Apr 1992 22:36:23 +0000 Subject: [PATCH] Initial revision From-SVN: r724 --- gcc/config/pa/pa.c | 2399 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2399 insertions(+) create mode 100644 gcc/config/pa/pa.c diff --git a/gcc/config/pa/pa.c b/gcc/config/pa/pa.c new file mode 100644 index 00000000000..e8d32246178 --- /dev/null +++ b/gcc/config/pa/pa.c @@ -0,0 +1,2399 @@ +/* Subroutines for insn-output.c for HPPA. + Copyright (C) 1992 Free Software Foundation, Inc. + Contributed by Tim Moore (moore@cs.utah.edu), based on sparc.c + +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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include +#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 "flags.h" +#include "tree.h" +#include "c-tree.h" +#include "expr.h" + +/* Save the operands last given to a compare for use when we + generate a scc or bcc insn. */ + +rtx hppa_compare_op0, hppa_compare_op1; +enum cmp_type hppa_branch_type; + +/* Set by the FUNCTION_PROFILER macro. */ +int hp_profile_labelno; + +/* Global variables set by FUNCTION_PROLOGUE. */ +/* Size of frame. Need to know this to emit return insns from + leaf procedures. */ +int apparent_fsize; +int actual_fsize; +int local_fsize, save_fregs; + +/* Name of where we pretend to think the frame pointer points. + Normally, this is "4", but if we are in a leaf procedure, + this is "something(30)". Will this work? */ +char *frame_base_name; + +static rtx find_addr_reg (); + +/* Return non-zero only if OP is a register of mode MODE, + or const0_rtx. */ +int +reg_or_0_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (op == const0_rtx || register_operand (op, mode)); +} + +int +call_operand_address (op, mode) + rtx op; + enum machine_mode mode; +{ + return (REG_P (op) || CONSTANT_P (op)); +} + +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); + default: + return 0; + } +} + +/* Return truth value of statement that OP is a symbolic memory + operand of mode MODE. */ + +int +symbolic_memory_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == SUBREG) + op = SUBREG_REG (op); + if (GET_CODE (op) != MEM) + return 0; + op = XEXP (op, 0); + return (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == CONST + || GET_CODE (op) == HIGH || GET_CODE (op) == LABEL_REF); +} + +/* Return 1 if the operand is either a register or a memory operand that is + not symbolic. */ + +int +reg_or_nonsymb_mem_operand (op, mode) + register rtx op; + enum machine_mode mode; +{ + if (register_operand (op, mode)) + return 1; + + if (memory_operand (op, mode) && ! symbolic_memory_operand (op, mode)) + return 1; + + return 0; +} + +int +move_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + if (register_operand (op, mode)) + return 1; + + if (op == CONST0_RTX (mode)) + return 1; + + if (GET_MODE (op) != mode) + return 0; + if (GET_CODE (op) == SUBREG) + op = SUBREG_REG (op); + if (GET_CODE (op) != MEM) + return 0; + + op = XEXP (op, 0); + if (GET_CODE (op) == LO_SUM) + return (register_operand (XEXP (op, 0), Pmode) + && CONSTANT_P (XEXP (op, 1))); + return memory_address_p (mode, op); +} + +int +pic_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return flag_pic && GET_CODE (op) == LABEL_REF; +} + +int +short_memory_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + if (GET_CODE (op) == MEM) + { + if (GET_CODE (XEXP (op, 0)) == REG) + return 1; + else if (GET_CODE (XEXP (op, 0)) == PLUS) + { + rtx op1 = XEXP (XEXP (op, 0), 0); + rtx op2 = XEXP (XEXP (op, 0), 1); + + if (GET_CODE (op1) == REG) + return (GET_CODE (op2) == CONST_INT && INT_5_BITS (op2)); + else if (GET_CODE (op2) == REG) + return (GET_CODE (op1) == CONST_INT && INT_5_BITS (op1)); + } + } + return 0; +} + +int +register_or_short_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + if (register_operand (op, mode)) + return 1; + if (GET_CODE (op) == SUBREG) + op = SUBREG_REG (op); + return short_memory_operand (op, mode); +} + +int +fp_reg_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return reg_renumber && FP_REG_P (op); +} + +extern int current_function_uses_pic_offset_table; +extern rtx force_reg (), validize_mem (); + +/* The rtx for the global offset table which is a special form + that *is* a position independent symbolic constant. */ +rtx pic_pc_rtx; + +/* Ensure that we are not using patterns that are not OK with PIC. */ + +int +check_pic (i) + int i; +{ + extern rtx recog_operand[]; + switch (flag_pic) + { + case 1: + if (GET_CODE (recog_operand[i]) == SYMBOL_REF + || (GET_CODE (recog_operand[i]) == CONST + && ! rtx_equal_p (pic_pc_rtx, recog_operand[i]))) + abort (); + case 2: + default: + return 1; + } +} + +/* Return truth value of whether OP is EQ or NE. */ + +int +eq_or_neq (op, mode) + rtx op; + enum machine_mode mode; +{ + return (GET_CODE (op) == EQ || GET_CODE (op) == NE); +} + +/* Return truth value of whether OP can be used as an operand in a + three operand arithmetic insn that accepts registers of mode MODE + or 14-bit signed integers. */ +int +arith_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (register_operand (op, mode) + || (GET_CODE (op) == CONST_INT && INT_14_BITS (op))); +} + +/* Return truth value of whether OP can be used as an operand in a + three operand arithmetic insn that accepts registers of mode MODE + or 11-bit signed integers. */ +int +arith11_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (register_operand (op, mode) + || (GET_CODE (op) == CONST_INT && INT_11_BITS (op))); +} + +int +arith_double_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (register_operand (op, mode) + || (GET_CODE (op) == CONST_DOUBLE + && GET_MODE (op) == mode + && VAL_14_BITS_P (CONST_DOUBLE_LOW (op)) + && (CONST_DOUBLE_HIGH (op) >= 0 + == ((CONST_DOUBLE_LOW (op) & 0x1000) == 0)))); +} + +/* Return truth value of whether OP is a integer which fits the + range constraining immediate operands in three-address insns. */ + +int +int5_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (GET_CODE (op) == CONST_INT && INT_5_BITS (op)); +} + +int +uint5_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (GET_CODE (op) == CONST_INT && INT_U5_BITS (op)); +} + + +int +int11_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (GET_CODE (op) == CONST_INT && INT_11_BITS (op)); +} + +int +arith5_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return register_operand (op, mode) || int5_operand (op, mode); +} + +/* Return truth value of statement that OP is a call-clobbered register. */ +int +clobbered_register (op, mode) + rtx op; + enum machine_mode mode; +{ + return (GET_CODE (op) == REG && call_used_regs[REGNO (op)]); +} + +/* True iff OP can be the source of a move to a general register. */ +int +srcsi_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + /* Not intended for other modes than SImode. */ + if (mode != SImode) + return 0; + + /* Accept any register or memory reference. */ + if (nonimmediate_operand (op, mode)) + return 1; + + /* OK if ldo or ldil can be used. */ + return (GET_CODE (op) == CONST_INT + && (INT_14_BITS (op) || (INTVAL (op) & 0x7ff) == 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. */ + +rtx +legitimize_pic_address (orig, mode, reg) + rtx orig, reg; + enum machine_mode mode; +{ + rtx pic_ref = orig; + + if (GET_CODE (orig) == SYMBOL_REF) + { + if (reg == 0) + abort (); + + if (flag_pic == 2) + { + emit_insn (gen_rtx (SET, VOIDmode, reg, + gen_rtx (HIGH, Pmode, orig))); + emit_insn (gen_rtx (SET, VOIDmode, reg, + gen_rtx (LO_SUM, Pmode, reg, orig))); + orig = reg; + } + 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; + + if (GET_CODE (XEXP (orig, 0)) == PLUS + && XEXP (XEXP (orig, 0), 0) == pic_offset_table_rtx) + return orig; + + if (reg == 0) + abort (); + + 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) + { + if (SMALL_INT (orig)) + return plus_constant_for_output (base, INTVAL (orig)); + orig = force_reg (Pmode, orig); + } + pic_ref = gen_rtx (PLUS, Pmode, base, orig); + /* Likewise, should we set special REG_NOTEs here? */ + } + return pic_ref; +} + +/* Set up PIC-specific rtl. This should not cause any insns + to be emitted. */ + +void +initialize_pic () +{ +} + +/* Emit special PIC prologues and epilogues. */ + +void +finalize_pic () +{ + /* The table we use to reference PIC data. */ + rtx global_offset_table; + /* Labels to get the PC in the prologue of this function. */ + rtx l1, l2; + rtx seq; + int orig_flag_pic = flag_pic; + + if (current_function_uses_pic_offset_table == 0) + return; + + if (! flag_pic) + abort (); + + flag_pic = 0; + l1 = gen_label_rtx (); + l2 = gen_label_rtx (); + + start_sequence (); + + emit_label (l1); + /* Note that we pun calls and jumps here! */ + emit_jump_insn (gen_rtx (PARALLEL, VOIDmode, + gen_rtvec (2, + gen_rtx (SET, VOIDmode, pc_rtx, gen_rtx (LABEL_REF, VOIDmode, l2)), + gen_rtx (SET, VOIDmode, gen_rtx (REG, SImode, 15), gen_rtx (LABEL_REF, VOIDmode, l2))))); + emit_label (l2); + + /* Initialize every time through, since we can't easily + know this to be permanent. */ + global_offset_table = gen_rtx (SYMBOL_REF, Pmode, "*__GLOBAL_OFFSET_TABLE_"); + pic_pc_rtx = gen_rtx (CONST, Pmode, + gen_rtx (MINUS, Pmode, + global_offset_table, + gen_rtx (CONST, Pmode, + gen_rtx (MINUS, Pmode, + gen_rtx (LABEL_REF, VOIDmode, l1), + pc_rtx)))); + + emit_insn (gen_rtx (SET, VOIDmode, pic_offset_table_rtx, + gen_rtx (HIGH, Pmode, pic_pc_rtx))); + emit_insn (gen_rtx (SET, VOIDmode, + pic_offset_table_rtx, + gen_rtx (LO_SUM, Pmode, + pic_offset_table_rtx, pic_pc_rtx))); + emit_insn (gen_rtx (SET, VOIDmode, + pic_offset_table_rtx, + gen_rtx (PLUS, SImode, + pic_offset_table_rtx, gen_rtx (REG, SImode, 15)))); + /* emit_insn (gen_rtx (ASM_INPUT, VOIDmode, "!#PROLOGUE# 1")); */ + LABEL_PRESERVE_P (l1) = 1; + LABEL_PRESERVE_P (l2) = 1; + flag_pic = orig_flag_pic; + + seq = gen_sequence (); + end_sequence (); + emit_insn_after (seq, get_insns ()); + + /* Need to emit this whether or not we obey regdecls, + since setjmp/longjmp can cause life info to screw up. */ + emit_insn (gen_rtx (USE, VOIDmode, pic_offset_table_rtx)); +} + +/* For the HPPA, REG and REG+CONST is cost 0 + and addresses involving symbolic constants are cost 2. + + PIC addresses are very expensive. + + It is no coincidence that this has the same structure + as GO_IF_LEGITIMATE_ADDRESS. */ +int +hppa_address_cost (X) + rtx X; +{ + if (GET_CODE (X) == PLUS) + return 1; + else if (GET_CODE (X) == LO_SUM) + return 1; + else if (GET_CODE (X) == HIGH) + return 2; + return 4; +} + +/* Emit insns to move operands[1] into operands[0]. + + Return 1 if we have written out everything that needs to be done to + do the move. Otherwise, return 0 and the caller will emit the move + normally. */ + +int +emit_move_sequence (operands, mode) + rtx *operands; + enum machine_mode mode; +{ + register rtx operand0 = operands[0]; + register rtx operand1 = operands[1]; + + /* Handle most common case first: storing into a register. */ + if (register_operand (operand0, mode)) + { + if (register_operand (operand1, mode) + || (GET_CODE (operand1) == CONST_INT && SMALL_INT (operand1)) + || (GET_CODE (operand1) == HIGH + && !symbolic_operand (XEXP (operand1, 0))) + /* Only `general_operands' can come here, so MEM is ok. */ + || GET_CODE (operand1) == MEM) + { + /* Run this case quickly. */ + emit_insn (gen_rtx (SET, VOIDmode, operand0, operand1)); + return 1; + } + } + else if (GET_CODE (operand0) == MEM) + { + if (register_operand (operand1, mode) || operand1 == const0_rtx) + { + /* Run this case quickly. */ + emit_insn (gen_rtx (SET, VOIDmode, operand0, operand1)); + return 1; + } + if (! reload_in_progress) + { + operands[0] = validize_mem (operand0); + operands[1] = operand1 = force_reg (mode, operand1); + } + } + + /* Simplify the source if we need to. */ +#if 0 + if (GET_CODE (operand1) == HIGH + && symbolic_operand (XEXP (operand1, 0), mode) + && !read_only_operand (XEXP (operand1, 0))) + { + rtx temp = reload_in_progress ? operand0 : gen_reg_rtx (mode); + + emit_insn (gen_rtx (SET, VOIDmode, temp, operand1)); + emit_insn (gen_rtx (SET, VOIDmode, + operand0, + gen_rtx (PLUS, mode, + temp, gen_rtx (REG, mode, 27)))); + return 1; + } +#endif + if (GET_CODE (operand1) != HIGH && immediate_operand (operand1, mode)) + { + if (symbolic_operand (operand1, mode)) + { + if (flag_pic) + { + rtx temp = reload_in_progress ? operand0 : gen_reg_rtx (Pmode); + operands[1] = legitimize_pic_address (operand1, mode, temp); + } + /* On the HPPA, references to data space are supposed to */ + /* use dp, register 27. */ + else if (read_only_operand (operand1)) + { + emit_insn (gen_rtx (SET, VOIDmode, + operand0, + gen_rtx (HIGH, mode, operand1))); + emit_insn (gen_rtx (SET, VOIDmode, + operand0, + gen_rtx (LO_SUM, mode, operand0, operand1))); + return 1; + } + else + { + /* If reload_in_progress, we can't use addil and r1; we */ + /* have to use the more expensive ldil sequence. */ + if (reload_in_progress) + { + emit_insn (gen_rtx (SET, VOIDmode, + operand0, + gen_rtx (HIGH, mode, operand1))); + emit_insn (gen_rtx (SET, VOIDmode, + operand0, + gen_rtx (PLUS, mode, + operand0, + gen_rtx (REG, mode, 27)))); + emit_insn (gen_rtx (SET, VOIDmode, + operand0, + gen_rtx (LO_SUM, mode, + operand0, operand1))); + } + else + { + rtx temp1 = gen_reg_rtx (mode), temp2 = gen_reg_rtx (mode); + + emit_insn (gen_rtx (SET, VOIDmode, + temp1, gen_rtx (HIGH, mode, operand1))); + emit_insn (gen_rtx (SET, VOIDmode, + temp2, + gen_rtx (PLUS, mode, + gen_rtx (REG, mode, 27), + temp1))); + emit_insn (gen_rtx (SET, VOIDmode, + operand0, + gen_rtx (LO_SUM, mode, + temp2, operand1))); + } + return 1; + } + } + else if (GET_CODE (operand1) == CONST_INT + ? (! SMALL_INT (operand1) + && (INTVAL (operand1) & 0x7ff) != 0) : 1) + { + rtx temp = reload_in_progress ? operand0 : gen_reg_rtx (mode); + emit_insn (gen_rtx (SET, VOIDmode, temp, + gen_rtx (HIGH, mode, operand1))); + operands[1] = gen_rtx (LO_SUM, mode, temp, operand1); + } + } + /* Now have insn-emit do whatever it normally does. */ + return 0; +} + +/* Does operand (which is a symbolic_operand) live in text space? If + so SYMBOL_REF_FLAG, which is set by ENCODE_SECTION_INFO, will be true.*/ + +int +read_only_operand (operand) + rtx operand; +{ + if (GET_CODE (operand) == CONST) + operand = XEXP (XEXP (operand, 0), 0); + if (GET_CODE (operand) == SYMBOL_REF) + return SYMBOL_REF_FLAG (operand) || CONSTANT_POOL_ADDRESS_P (operand); + return 1; +} + + +/* Return the best assembler insn template + for moving operands[1] into operands[0] as a fullword. */ + +static char * +singlemove_string (operands) + rtx *operands; +{ + if (GET_CODE (operands[0]) == MEM) + return "stw %r1,%0"; + if (GET_CODE (operands[1]) == MEM) + return "ldw %1,%0"; + if (GET_CODE (operands[1]) == CONST_INT) + if (INT_14_BITS (operands[1])) + return (INTVAL (operands[1]) == 0 ? "copy 0,%0" : "ldi %1,%0"); + else + return "ldil L'%1,%0\n\tldo R'%1(%0),%0"; + return "copy %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, 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 (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 (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 != REGOP && optype1 != REGOP) + abort (); + + /* Handle auto decrementing and incrementing loads and stores + specifically, since the structure of the function doesn't work + for them without major modification. Do it better when we learn + this port about the general inc/dec addressing of PA. + (This was written by tege. Chide him if it doesn't work.) */ + + if (optype0 == MEMOP) + { + rtx addr = XEXP (operands[0], 0); + if (GET_CODE (addr) == POST_INC || GET_CODE (addr) == POST_DEC + || GET_CODE (addr) == PRE_INC || GET_CODE (addr) == PRE_DEC) + { + operands[0] = gen_rtx (MEM, SImode, addr); + return "stw%M0 %1,%0\n\tstw%M0 %1,%0"; + } + } + if (optype1 == MEMOP) + { + /* We have to output the address syntax ourselves, since print_operand + doesn't deal with the addresses we want to use. Fix this later. */ + + rtx addr = XEXP (operands[1], 0); + if (GET_CODE (addr) == POST_INC || GET_CODE (addr) == POST_DEC) + { + rtx high_reg = gen_rtx (SUBREG, SImode, operands[0], 0); + + operands[1] = XEXP (addr, 0); + if (GET_CODE (operands[0]) != REG || GET_CODE (operands[1]) != REG) + abort (); + + if (!reg_overlap_mentioned_p (high_reg, addr)) + { + /* No overlap between high target register and address + register. (We do this in an non-obious way to + save a register file writeback) */ + if (GET_CODE (addr) == POST_INC) + return "ldws,ma 8(0,%1),%0\n\tldw -4(0,%1),%R0"; + return "ldws,ma -8(0,%1),%0\n\tldw 12(0,%1),%R0"; + } + else + { + /* This is an undefined situation. We should load into the + address register *and* update that register. Probably + we don't need to handle this at all. */ + if (GET_CODE (addr) == POST_INC) + return "ldw 4(0,%1),%R0\n\tldws,ma 8(0,%1),%0"; + return "ldw 4(0,%1),%R0\n\tldws,ma -8(0,%1),%0"; + } + } + else if (GET_CODE (addr) == PRE_INC || GET_CODE (addr) == PRE_DEC) + { + rtx high_reg = gen_rtx (SUBREG, SImode, operands[0], 0); + + operands[1] = XEXP (addr, 0); + if (GET_CODE (operands[0]) != REG || GET_CODE (operands[1]) != REG) + abort (); + + if (!reg_overlap_mentioned_p (high_reg, addr)) + { + /* No overlap between high target register and address + register. (We do this in an non-obious way to + save a register file writeback) */ + if (GET_CODE (addr) == PRE_INC) + return "ldws,mb 8(0,%1),%0\n\tldw 4(0,%1),%R0"; + return "ldws,mb -8(0,%1),%0\n\tldw 4(0,%1),%R0"; + } + else + { + /* This is an undefined situation. We should load into the + address register *and* update that register. Probably + we don't need to handle this at all. */ + if (GET_CODE (addr) == PRE_INC) + return "ldw 12(0,%1),%R0\n\tldws,mb 8(0,%1),%0"; + return "ldw -4(0,%1),%R0\n\tldws,mb -8(0,%1),%0"; + } + } + } + + /* 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. + + 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, SImode, REGNO (operands[0]) + 1); + else if (optype0 == OFFSOP) + latehalf[0] = adj_offsettable_operand (operands[0], 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], 4); + else if (optype1 == CNSTOP) + split_double (operands[1], &operands[1], &latehalf[1]); + else + latehalf[1] = operands[1]; + + /* If the first move would clobber the source of the second one, + do them in the other order. + + RMS says "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." + + but it happens on the HP-PA when loading parameter registers, + so I am going to define that circumstance, and make it work + as expected. */ + + if (optype0 == REGOP && (optype1 == MEMOP || optype1 == OFFSOP) + && reg_overlap_mentioned_p (operands[0], XEXP (operands[1], 0))) + { + /* XXX THIS PROBABLY DOESN'T WORK. */ + /* Do the late half first. */ + if (addreg1) + output_asm_insn ("addi 4,%0", &addreg1); + output_asm_insn (singlemove_string (latehalf), latehalf); + if (addreg1) + output_asm_insn ("addi -4,%0", &addreg1); + /* Then clobber. */ + 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 ("addi 4,%0", &addreg0); + if (addreg1) + output_asm_insn ("addi 4,%0", &addreg1); + + /* Do that word. */ + output_asm_insn (singlemove_string (latehalf), latehalf); + + /* Undo the adds we just did. */ + if (addreg0) + output_asm_insn ("addi -4,%0", &addreg0); + if (addreg1) + output_asm_insn ("addi -4,%0", &addreg1); + + return ""; +} + +char * +output_fp_move_double (operands) + rtx *operands; +{ + if (FP_REG_P (operands[0])) + { + if (FP_REG_P (operands[1])) + output_asm_insn ("fcpy,dbl %1,%0", operands); + else if (GET_CODE (operands[1]) == REG) + { + rtx xoperands[3]; + xoperands[0] = operands[0]; + xoperands[1] = operands[1]; + xoperands[2] = gen_rtx (REG, SImode, REGNO (operands[1]) + 1); + output_asm_insn + ("stw %1,-16(0,30)\n\tstw %2,-12(0,30)\n\tfldds -16(0,30),%0", + xoperands); + } + else + output_asm_insn ("fldds%F1 %1,%0", operands); + } + else if (FP_REG_P (operands[1])) + { + if (GET_CODE (operands[0]) == REG) + { + rtx xoperands[3]; + xoperands[2] = operands[1]; + xoperands[1] = gen_rtx (REG, SImode, REGNO (operands[0]) + 1); + xoperands[0] = operands[0]; + output_asm_insn + ("fstds %2,-16(0,30)\n\tldw -12(0,30),%1\n\tldw -16(0,30),%0", + xoperands); + } + else + output_asm_insn ("fstds%F0 %1,%0", operands); + } + else abort (); + 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 (); +} + +/* Load the address specified by OPERANDS[3] into the register + specified by OPERANDS[0]. + + OPERANDS[3] may be the result of a sum, hence it could either be: + + (1) CONST + (2) REG + (2) REG + CONST_INT + (3) REG + REG + CONST_INT + (4) REG + REG (special case of 3). + + Note that (3) is not a legitimate address. + All cases are handled here. */ + +void +output_load_address (operands) + rtx *operands; +{ + rtx base, offset; + + if (CONSTANT_P (operands[3])) + { + output_asm_insn ("ldi %3,%0", operands); + return; + } + + if (REG_P (operands[3])) + { + if (REGNO (operands[0]) != REGNO (operands[3])) + output_asm_insn ("copy %3,%0", operands); + return; + } + + if (GET_CODE (operands[3]) != PLUS) + abort (); + + base = XEXP (operands[3], 0); + offset = XEXP (operands[3], 1); + + if (GET_CODE (base) == CONST_INT) + { + rtx tmp = base; + base = offset; + offset = tmp; + } + + if (GET_CODE (offset) != CONST_INT) + { + /* Operand is (PLUS (REG) (REG)). */ + base = operands[3]; + offset = const0_rtx; + } + + if (REG_P (base)) + { + operands[6] = base; + operands[7] = offset; + if (INT_14_BITS (offset)) + output_asm_insn ("ldo %7(%6),%0", operands); + else + output_asm_insn ("addil L'%7,%6\n\tldo R'%7(1),%0", operands); + } + else if (GET_CODE (base) == PLUS) + { + operands[6] = XEXP (base, 0); + operands[7] = XEXP (base, 1); + operands[8] = offset; + + if (offset == const0_rtx) + output_asm_insn ("add %6,%7,%0", operands); + else if (INT_14_BITS (offset)) + output_asm_insn ("add %6,%7,%0\n\taddi %8,%0", operands); + else + output_asm_insn ("addil L'%8,%6\n\tldo R'%8(1),%0\n\tadd %0,%7,%0", operands); + } + else + abort (); +} + +/* Emit code to perform a block move. + + Restriction: If the length argument is non-constant, alignment + must be 4. + + OPERANDS[0] is the destination pointer as a REG, clobbered. + OPERANDS[1] is the source pointer as a REG, clobbered. + if SIZE_IS_CONSTANT + OPERANDS[2] is a register for temporary storage. + OPERANDS[4] is the size as a CONST_INT + else + OPERANDS[2] is a REG which will contain the size, clobbered. + OPERANDS[3] is a register for temporary storage. + OPERANDS[5] is the alignment safe to use, as a CONST_INT. */ + +char * +output_block_move (operands, size_is_constant) + rtx *operands; + int size_is_constant; +{ + int align = INTVAL (operands[5]); + unsigned long n_bytes; + + /* We can't move more than four bytes at a time because the PA + has no longer integer move insns. (Could use fp mem ops?) */ + if (align > 4) + align = 4; + + if (size_is_constant) + { + unsigned long n_items; + unsigned long offset; + rtx temp; + + n_bytes = INTVAL (operands[4]); + if (n_bytes == 0) + return ""; + + if (align >= 4) + { + /* Don't unroll too large blocks. */ + if (n_bytes > 64) + goto copy_with_loop; + + /* Read and store using two registers, and hide latency + by defering the stores until three instructions after + the corresponding load. The last load insn will read + the entire word were the last bytes are, possibly past + the end of the source block, but since loads are aligned, + this is harmless. */ + + output_asm_insn ("ldws,ma 4(0,%1),%2", operands); + + for (offset = 4; offset < n_bytes; offset += 4) + { + output_asm_insn ("ldws,ma 4(0,%1),%3", operands); + output_asm_insn ("stws,ma %2,4(0,%0)", operands); + + temp = operands[2]; + operands[2] = operands[3]; + operands[3] = temp; + } + if (n_bytes % 4 == 0) + /* Store the last word. */ + output_asm_insn ("stw %2,0(0,%0)", operands); + else + { + /* Store the last, partial word. */ + operands[4] = gen_rtx (CONST_INT, VOIDmode, n_bytes % 4); + output_asm_insn ("stbys,e %2,%4(0,%0)", operands); + } + return ""; + } + + if (align >= 2 && n_bytes >= 2) + { + output_asm_insn ("ldhs,ma 2(0,%1),%2", operands); + + for (offset = 2; offset + 2 <= n_bytes; offset += 2) + { + output_asm_insn ("ldhs,ma 2(0,%1),%3", operands); + output_asm_insn ("sths,ma %2,2(0,%0)", operands); + + temp = operands[2]; + operands[2] = operands[3]; + operands[3] = temp; + } + if (n_bytes % 2 != 0) + output_asm_insn ("ldb 0(0,%1),%3", operands); + + output_asm_insn ("sths,ma %2,2(0,%0)", operands); + + if (n_bytes % 2 != 0) + output_asm_insn ("stb %3,0(0,%0)", operands); + + return ""; + } + + output_asm_insn ("ldbs,ma 1(0,%1),%2", operands); + + for (offset = 1; offset + 1 <= n_bytes; offset += 1) + { + output_asm_insn ("ldbs,ma 1(0,%1),%3", operands); + output_asm_insn ("stbs,ma %2,1(0,%0)", operands); + + temp = operands[2]; + operands[2] = operands[3]; + operands[3] = temp; + } + output_asm_insn ("stb %2,0(0,%0)", operands); + + return ""; + } + + if (align != 4) + abort(); + + copy_with_loop: + + if (size_is_constant) + { + /* Size is an compile-time determined, and also not + very small (such small cases are handled above). */ + operands[4] = gen_rtx (CONST_INT, VOIDmode, n_bytes - 4); + output_asm_insn ("ldo %4(0),%2", operands); + } + else + { + /* Decrement counter by 4, and if it becomes negative, jump past the + word copying loop. */ + output_asm_insn ("addib,<,n -4,%2,.+16", operands); + } + + /* Copying loop. Note that the first load is in the anulled delay slot + of addib. Is it OK on PA to have a load in a delay slot, i.e. is a + possible page fault stopped in time? */ + output_asm_insn ("ldws,ma 4(0,%1),%3", operands); + output_asm_insn ("addib,>= -4,%2,.-4", operands); + output_asm_insn ("stws,ma %3,4(0,%0)", operands); + + /* The counter is negative, >= -4. The remaining number of bytes are + determined by the two least significant bits. */ + + if (size_is_constant) + { + if (n_bytes % 4 != 0) + { + /* Read the entire word of the source block tail. */ + output_asm_insn ("ldw 0(0,%1),%3", operands); + operands[4] = gen_rtx (CONST_INT, VOIDmode, n_bytes % 4); + output_asm_insn ("stbys,e %3,%4(0,%0)", operands); + } + } + else + { + /* Add 4 to counter. If it becomes zero, we're done. */ + output_asm_insn ("addib,=,n 4,%2,.+16", operands); + + /* Read the entire word of the source block tail. (Also this + load is in an anulled delay slot.) */ + output_asm_insn ("ldw 0(0,%1),%3", operands); + + /* Make %0 point at the first byte after the destination block. */ + output_asm_insn ("add %2,%0,%0", operands); + /* Store the leftmost bytes, up to, but not including, the address + in %0. */ + output_asm_insn ("stbys,e %3,0(0,%0)", operands); + } + return ""; +} + + +/* Output an ascii string. */ +output_ascii (file, p, size) + FILE *file; + unsigned char *p; + int size; +{ + int i; + int chars_output; + unsigned char partial_output[16]; /* Max space 4 chars can occupy. */ + + /* The HP assembler can only take strings of 256 characters at one + time. This is a limitation on input line length, *not* the + length of the string. Sigh. Even worse, it seems that the + restriction is in number of input characters (see \xnn & + \whatever). So we have to do this very carefully. */ + + fprintf (file, "\t.STRING \""); + + chars_output = 0; + for (i = 0; i < size; i += 4) + { + int co = 0; + int io = 0; + for (io = 0, co = 0; io < MIN (4, size - i); io++) + { + register unsigned int c = p[i + io]; + + if (c == '\"' || c == '\\') + partial_output[co++] = '\\'; + if (c >= ' ' && c < 0177) + partial_output[co++] = c; + else + { + unsigned int hexd; + partial_output[co++] = '\\'; + partial_output[co++] = 'x'; + hexd = c / 16 - 0 + '0'; + if (hexd > '9') + hexd -= '9' - 'a' + 1; + partial_output[co++] = hexd; + hexd = c % 16 - 0 + '0'; + if (hexd > '9') + hexd -= '9' - 'a' + 1; + partial_output[co++] = hexd; + } + } + if (chars_output + co > 243) + { + fprintf (file, "\"\n\t.STRING \""); + chars_output = 0; + } + fwrite (partial_output, 1, co, file); + chars_output += co; + co = 0; + } + fprintf (file, "\"\n"); +} + +/* You may have trouble believing this, but this is the HP825 stack + layout. Wow. + + Offset Contents + + Variable arguments (optional; any number may be allocated) + + SP-(4*(N+9)) arg word N + : : + SP-56 arg word 5 + SP-52 arg word 4 + + Fixed arguments (must be allocated; may remain unused) + + SP-48 arg word 3 + SP-44 arg word 2 + SP-40 arg word 1 + SP-36 arg word 0 + + Frame Marker + + SP-32 External Data Pointer (DP) + SP-28 External sr4 + SP-24 External/stub RP (RP') + SP-20 Current RP + SP-16 Static Link + SP-12 Clean up + SP-8 Calling Stub RP (RP'') + SP-4 Previous SP + + Top of Frame + + SP-0 Stack Pointer (points to next available address) + +*/ + +/* This function saves registers as follows. Registers marked with ' are + this function's registers (as opposed to the previous function's). + If a frame_pointer isn't needed, r4 is saved as a general register; + the space for the frame pointer is still allocated, though, to keep + things simple. + + + Top of Frame + + SP (FP') Previous FP + SP + 4 Alignment filler (sigh) + SP + 8 Space for locals reserved here. + . + . + . + SP + n All call saved register used. + . + . + . + SP + o All call saved fp registers used. + . + . + . + SP + p (SP') points to next available address. + +*/ + +/* Helper functions */ +void +print_stw (file, r, disp, base) + FILE *file; + int r, disp, base; +{ + if (VAL_14_BITS_P (disp)) + fprintf (file, "\tstw %d,%d(0,%d)\n", r, disp, base); + else + fprintf (file, "\taddil L'%d,%d\n\tstw %d,R'%d(0,1)\n", disp, base, + r, disp); +} + +void +print_ldw (file, r, disp, base) + FILE *file; + int r, disp, base; +{ + if (VAL_14_BITS_P (disp)) + fprintf (file, "\tldw %d(0,%d),%d\n", disp, base, r); + else + fprintf (file, "\taddil L'%d,%d\n\tldw R'%d(0,1),%d\n", disp, base, + disp, r); +} + +int +compute_frame_size (size, leaf_function) + int size; + int leaf_function; +{ + extern int current_function_outgoing_args_size; + int i; + + /* 8 is space for frame pointer + filler */ + local_fsize = actual_fsize = size + 8; + + /* fp is stored in a special place. */ + for (i = 18; i >= 5; i--) + if (regs_ever_live[i]) + actual_fsize += 4; + + if (regs_ever_live[3]) + actual_fsize += 4; + actual_fsize = (actual_fsize + 7) & ~7; + + if (!TARGET_SNAKE) + { + for (i = 47; i >= 44; i--) + if (regs_ever_live[i]) + { + actual_fsize += 8; save_fregs++; + } + } + else + { + for (i = 90; i >= 72; i -= 2) + if (regs_ever_live[i] || regs_ever_live[i + 1]) + { + actual_fsize += 8; save_fregs++; + } + } + return actual_fsize + current_function_outgoing_args_size; +} + +void +output_function_prologue (file, size, leaf_function) + FILE *file; + int size; + int leaf_function; +{ + extern char call_used_regs[]; + extern int frame_pointer_needed; + int i, offset; + + actual_fsize = compute_frame_size (size, leaf_function) + 32; + if (TARGET_SNAKE) + actual_fsize = (actual_fsize + 63) & ~63; + + /* Let's not try to bullshit more than we need to here. */ + /* This might be right a lot of the time */ + fprintf (file, "\t.PROC\n\t.CALLINFO FRAME=%d", actual_fsize); + if (regs_ever_live[2]) + fprintf (file, ",CALLS,SAVE_RP\n"); + else + fprintf (file, ",NO_CALLS\n"); + fprintf (file, "\t.ENTRY\n"); + + /* Instead of taking one argument, the counter label, as most normal + mcounts do, _mcount appears to behave differently on the HPPA. It + takes the return address of the caller, the address of this + routine, and the address of the label. Also, it isn't magic, so + caller saves have to be preserved. We get around this by calling + our own gcc_mcount, which takes arguments on the stack and saves + argument registers. */ + + if (profile_flag) + { + fprintf (file,"\tstw 2,-20(30)\n\tldo 48(30),30\n\ +\taddil L'LP$%04d-$global$,27\n\tldo R'LP$%04d-$global$(1),1\n\ +\tbl __gcc_mcount,2\n\tstw 1,-16(30)\n\tldo -48(30),30\n\tldw -20(30),2\n", + hp_profile_labelno, hp_profile_labelno); + } + /* Some registers have places to go in the current stack + structure. */ + +#if 0 + /* However, according to the hp docs, there's no need to save the + sp. */ + fprintf (file, "\tstw 30,-4(30)\n"); +#endif + + if (regs_ever_live[2]) + fprintf (file, "\tstw 2,-20(0,30)\n"); + + /* Reserve space for local variables. */ + if (frame_pointer_needed) + { + if (VAL_14_BITS_P (actual_fsize)) + fprintf (file, "\tcopy 4,1\n\tcopy 30,4\n\tstwm 1,%d(0,30)\n", + actual_fsize); + else + { + fprintf (file, "\tcopy 4,1\n\tcopy 30,4\n\tstw 1,0(0,4)\n"); + fprintf (file, "\taddil L'%d,30\n\tldo R'%d(1),30\n", + actual_fsize, actual_fsize); + } + } + else + /* Used to be abort (); */ + { + if (VAL_14_BITS_P (actual_fsize)) + fprintf (file, "\tldo %d(30),30\n", actual_fsize); + else + fprintf (file, "\taddil L'%d,30\n\tldo R'%d(1),30\n", + actual_fsize, actual_fsize); + } + + /* Normal register save. */ + if (frame_pointer_needed) + { + for (i = 18, offset = local_fsize; i >= 5; i--) + if (regs_ever_live[i] && ! call_used_regs[i]) + { + print_stw (file, i, offset, 4); offset += 4; + } + if (regs_ever_live[3] && ! call_used_regs[3]) + { + print_stw (file, 3, offset, 4); offset += 4; + } + } + else + { + for (i = 18, offset = local_fsize - actual_fsize; i >= 5; i--) + if (regs_ever_live[i] && ! call_used_regs[i]) + { + print_stw (file, i, offset, 30); offset += 4; + } + if (regs_ever_live[3] && ! call_used_regs[3]) + { + print_stw (file, 3, offset, 30); offset += 4; + } + } + + /* Align pointer properly (doubleword boundary). */ + offset = (offset + 7) & ~7; + + /* Floating point register store. */ + if (save_fregs) + if (frame_pointer_needed) + { + if (VAL_14_BITS_P (offset)) + fprintf (file, "\tldo %d(4),1\n", offset); + else + fprintf (file, "\taddil L'%d,4\n\tldo R'%d(1),1\n", offset, offset); + } + else + { + if (VAL_14_BITS_P (offset)) + fprintf (file, "\tldo %d(30),1\n", offset); + else + fprintf (file, "\taddil L'%d,30\n\tldo R'%d(1),1\n", offset, offset); + } + if (!TARGET_SNAKE) + { + for (i = 47; i >= 44; i--) + { + if (regs_ever_live[i]) + fprintf (file, "\tfstds,ma %s,8(0,1)\n", reg_names[i]); + } + } + else + { + for (i = 90; i >= 72; i -= 2) + if (regs_ever_live[i] || regs_ever_live[i + 1]) + { + fprintf (file, "\tfstds,ma %s,8(0,1)\n", reg_names[i]); + } + } +} + +void +output_function_epilogue (file, size, leaf_function) + FILE *file; + int size; + int leaf_function; +{ + extern char call_used_regs[]; + extern int frame_pointer_needed; + int i, offset; + + if (frame_pointer_needed) + { + for (i = 18, offset = local_fsize; i >= 5; i--) + if (regs_ever_live[i] && ! call_used_regs[i]) + { + print_ldw (file, i, offset, 4); offset += 4; + } + if (regs_ever_live[3] && ! call_used_regs[3]) + { + print_ldw (file, 3, offset, 4); offset += 4; + } + } + else + { + for (i = 18, offset = local_fsize - actual_fsize; i >= 5; i--) + if (regs_ever_live[i] && ! call_used_regs[i]) + { + print_ldw (file, i, offset, 30); offset += 4; + } + if (regs_ever_live[3] && ! call_used_regs[3]) + { + print_ldw (file, 3, offset, 30); offset += 4; + } + } + + /* Align pointer properly (doubleword boundary). */ + offset = (offset + 7) & ~7; + + /* Floating point register restore. */ + if (save_fregs) + if (frame_pointer_needed) + { + if (VAL_14_BITS_P (offset)) + fprintf (file, "\tldo %d(4),1\n", offset); + else + fprintf (file, "\taddil L'%d,4\n\tldo R'%d(1),1\n", offset, offset); + } + else + { + if (VAL_14_BITS_P (offset)) + fprintf (file, "\tldo %d(30),1\n", offset); + else + fprintf (file, "\taddil L'%d,30\n\tldo R'%d(1),1\n", offset, offset); + } + if (!TARGET_SNAKE) + { + for (i = 47; i >= 44; i--) + { + if (regs_ever_live[i]) + fprintf (file, "\tfldds,ma 8(0,1),%s\n", reg_names[i]); + } + } + else + { + for (i = 90; i >= 72; i -= 2) + if (regs_ever_live[i] || regs_ever_live[i + 1]) + { + fprintf (file, "\tfldds,ma 8(0,1),%s\n", reg_names[i]); + } + } + /* Reset stack pointer (and possibly frame pointer). The stack */ + /* pointer is initially set to fp + 8 to avoid a race condition. */ + if (frame_pointer_needed) + { + fprintf (file, "\tldo 8(4),30\n"); + if (regs_ever_live[2]) + fprintf (file, "\tldw -28(0,30),2\n"); + fprintf (file, "\tbv 0(2)\n\tldwm -8(30),4\n"); + } + else if (actual_fsize) + { + if (regs_ever_live[2] && VAL_14_BITS_P (actual_fsize + 20)) + fprintf (file, "\tldw %d(30),2\n\tbv 0(2)\n\tldo %d(30),30\n", + -(actual_fsize + 20), -actual_fsize); + else if (regs_ever_live[2]) + fprintf (file, + "\taddil L'%d,30\n\tldw %d(1),2\n\tbv 0(2)\n\tldo R'%d(1),30\n", + - actual_fsize, + - (actual_fsize + 20 + ((-actual_fsize) & ~0x7ff)), + /* - ((actual_fsize + 20) - (actual_fsize & ~0x7ff)), */ + - actual_fsize); + else if (VAL_14_BITS_P (actual_fsize)) + fprintf (file, "\tbv 0(2)\n\tldo %d(30),30\n", - actual_fsize); + else + fprintf (file, "\taddil L'%d,30\n\tbv 0(2)\n\tldo R'%d(1),30\n"); + } + else if (current_function_epilogue_delay_list) + { + fprintf (file, "\tbv 0(2)\n"); + final_scan_insn (XEXP (current_function_epilogue_delay_list, 0), + file, write_symbols, 1, 0, 1); + } + else + fprintf (file, "\tbv,n 0(2)\n"); + fprintf (file, "\t.EXIT\n\t.PROCEND\n"); +} + +rtx +gen_compare_reg (code, x, y) + enum rtx_code code; + rtx x, y; +{ + enum machine_mode mode = SELECT_CC_MODE (code, x); + rtx cc_reg = gen_rtx (REG, mode, 0); + + emit_insn (gen_rtx (SET, VOIDmode, cc_reg, + gen_rtx (COMPARE, mode, x, y))); + + return cc_reg; +} + +/* Return nonzero if TRIAL can go into the function epilogue's + delay slot. SLOT is the slot we are trying to fill. */ + +int +eligible_for_epilogue_delay (trial, slot) + rtx trial; + int slot; +{ + if (slot >= 1) + return 0; + if (GET_CODE (trial) != INSN + || GET_CODE (PATTERN (trial)) != SET) + return 0; + if (get_attr_length (trial) != 1) + return 0; + return (leaf_function && + get_attr_in_branch_delay (trial) == IN_BRANCH_DELAY_TRUE); +} + +rtx +gen_scond_fp (code, operand0) + enum rtx_code code; + rtx operand0; +{ + return gen_rtx (SET, VOIDmode, operand0, + gen_rtx (code, CCFPmode, + gen_rtx (REG, CCFPmode, 0), const0_rtx)); +} + +void +emit_bcond_fp (code, operand0) + enum rtx_code code; + rtx operand0; +{ + emit_jump_insn (gen_rtx (SET, VOIDmode, pc_rtx, + gen_rtx (IF_THEN_ELSE, VOIDmode, + gen_rtx (code, VOIDmode, + gen_rtx (REG, CCFPmode, 0), + const0_rtx), + gen_rtx (LABEL_REF, VOIDmode, operand0), + pc_rtx))); + +} + +rtx +gen_cmp_fp (code, operand0, operand1) + enum rtx_code code; + rtx operand0, operand1; +{ + return gen_rtx (SET, VOIDmode, gen_rtx (REG, CCFPmode, 0), + gen_rtx (code, CCFPmode, operand0, operand1)); +} + + +/* Print operand X (an rtx) in assembler syntax to file FILE. + CODE is a letter or dot (`z' in `%z0') or 0 if no letter was specified. + For `%' followed by punctuation, CODE is the punctuation and X is null. */ + +void +print_operand (file, x, code) + FILE *file; + rtx x; + int code; +{ + switch (code) + { + case '#': + /* Output a 'nop' if there's nothing for the delay slot. */ + if (dbr_sequence_length () == 0) + fputs ("\n\tnop", file); + return; + case '*': + /* Output an nullification completer if there's nothing for the */ + /* delay slot or nullification is requested. */ + if (dbr_sequence_length () == 0 || + (final_sequence && + INSN_ANNULLED_BRANCH_P (XVECEXP (final_sequence, 0, 0)))) + fputs (",n", file); + return; + case 'R': + /* Print out the second register name of a register pair. + I.e., R (6) => 7. */ + fputs (reg_names[REGNO (x)+1], file); + return; + case 'r': + /* A register or zero. */ + if (x == const0_rtx) + { + fputs ("0", file); + return; + } + else + break; + case 'O': + switch (GET_CODE (x)) + { + case PLUS: + fprintf (file, "add%s", + GET_CODE (XEXP (x, 1)) == CONST_INT ? "i" : ""); break; + case MINUS: + fprintf (file, "sub%s", + GET_CODE (XEXP (x, 0)) == CONST_INT ? "i" : ""); break; + case AND: + fprintf (file, "and%s", + GET_CODE (XEXP (x, 1)) == NOT ? "cm" : ""); break; + case IOR: + fprintf (file, "or"); break; + case XOR: + fprintf (file, "xor"); break; + case ASHIFT: + fprintf (file, "sh%dadd", INTVAL (XEXP (x, 1))); break; + /* Too lazy to handle bitfield conditions yet. */ + default: + printf ("Can't grok '%c' operator:\n", code); + debug_rtx (x); + abort (); + } + return; + case 'C': + case 'X': + switch (GET_CODE (x)) + { + case EQ: + fprintf (file, "="); break; + case NE: + if (code == 'C') + fprintf (file, "<>"); + else + fprintf (file, "!="); + break; + case GT: + fprintf (file, ">"); break; + case GE: + fprintf (file, ">="); break; + case GEU: + fprintf (file, ">>="); break; + case GTU: + fprintf (file, ">>"); break; + case LT: + fprintf (file, "<"); break; + case LE: + fprintf (file, "<="); break; + case LEU: + fprintf (file, "<<="); break; + case LTU: + fprintf (file, "<<"); break; + default: + printf ("Can't grok '%c' operator:\n", code); + debug_rtx (x); + abort (); + } + return; + case 'N': + case 'Y': + switch (GET_CODE (x)) + { + case EQ: + if (code == 'N') + fprintf (file, "<>"); + else + fprintf (file, "!="); + break; + case NE: + fprintf (file, "="); break; + case GT: + fprintf (file, "<="); break; + case GE: + fprintf (file, "<"); break; + case GEU: + fprintf (file, "<<"); break; + case GTU: + fprintf (file, "<<="); break; + case LT: + fprintf (file, ">="); break; + case LE: + fprintf (file, ">"); break; + case LEU: + fprintf (file, ">>"); break; + case LTU: + fprintf (file, ">>="); break; + default: + printf ("Can't grok '%c' operator:\n", code); + debug_rtx (x); + abort (); + } + return; + case 'M': + switch (GET_CODE (XEXP (x, 0))) + { + case PRE_DEC: + case PRE_INC: + fprintf (file, "s,mb"); + break; + case POST_DEC: + case POST_INC: + fprintf (file, "s,ma"); + break; + default: + break; + } + return; + case 'F': + switch (GET_CODE (XEXP (x, 0))) + { + case PRE_DEC: + case PRE_INC: + fprintf (file, ",mb"); + break; + case POST_DEC: + case POST_INC: + fprintf (file, ",ma"); + break; + default: + break; + } + return; + case 'G': + output_global_address (file, x); + return; + case 0: /* Don't do anything special */ + break; + default: + abort (); + } + if (GET_CODE (x) == REG) + fprintf (file, "%s", reg_names [REGNO (x)]); + else if (GET_CODE (x) == MEM) + { + int size = GET_MODE_SIZE (GET_MODE (x)); + rtx base = XEXP (XEXP (x, 0), 0); + switch (GET_CODE (XEXP (x, 0))) + { + case PRE_DEC: + case POST_DEC: + fprintf (file, "-%d(0,%s)", size, reg_names [REGNO (base)]); + break; + case PRE_INC: + case POST_INC: + fprintf (file, "%d(0,%s)", size, reg_names [REGNO (base)]); + break; + default: + output_address (XEXP (x, 0)); + break; + } + } + else if (GET_CODE (x) == CONST_DOUBLE && GET_MODE (x) == SFmode) + { + union { double d; int i[2]; } u; + union { float f; int i; } u1; + u.i[0] = XINT (x, 0); u.i[1] = XINT (x, 1); + u1.f = u.d; + if (code == 'f') + fprintf (file, "0r%.9g", u1.f); + else + fprintf (file, "0x%x", u1.i); + } + else if (GET_CODE (x) == CONST_DOUBLE && GET_MODE (x) != DImode) + { + union { double d; int i[2]; } u; + u.i[0] = XINT (x, 0); u.i[1] = XINT (x, 1); + fprintf (file, "0r%.20g", u.d); + } + else + output_addr_const (file, x); +} + +/* output a SYMBOL_REF or a CONST expression involving a SYMBOL_REF. */ + +void +output_global_address (file, x) + FILE *file; + rtx x; +{ + if (GET_CODE (x) == SYMBOL_REF && read_only_operand (x)) + assemble_name (file, XSTR (x, 0)); + else if (GET_CODE (x) == SYMBOL_REF) + { + assemble_name (file, XSTR (x, 0)); + fprintf (file, "-$global$"); + } + else if (GET_CODE (x) == CONST) + { + char *sep = ""; + int offset = 0; /* assembler wants -$global$ at end */ + rtx base; + + if (GET_CODE (XEXP (XEXP (x, 0), 0)) == SYMBOL_REF) + { + base = XEXP (XEXP (x, 0), 0); + output_addr_const (file, base); + } + else if (GET_CODE (XEXP (XEXP (x, 0), 0)) == CONST_INT) + offset = INTVAL (XEXP (XEXP (x, 0), 0)); + else abort (); + + if (GET_CODE (XEXP (XEXP (x, 0), 1)) == SYMBOL_REF) + { + base = XEXP (XEXP (x, 0), 1); + output_addr_const (file, base); + } + else if (GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT) + offset = INTVAL (XEXP (XEXP (x, 0),1)); + else abort (); + + if (GET_CODE (XEXP (x, 0)) == PLUS) + { + if (offset < 0) + { + offset = -offset; + sep = "-"; + } + else + sep = "+"; + } + else if (GET_CODE (XEXP (x, 0)) == MINUS + && (GET_CODE (XEXP (XEXP (x, 0), 0)) == SYMBOL_REF)) + sep = "-"; + else abort (); + + if (!read_only_operand (base)) + fprintf (file, "-$global$"); + fprintf (file, "%s", sep); + if (offset) fprintf (file,"%d", offset); + } + else + output_addr_const (file, x); +} + +/* MEM rtls here are never SYMBOL_REFs (I think), so fldws is safe. */ + +char * +output_floatsisf2 (operands) + rtx *operands; +{ + if (GET_CODE (operands[1]) == MEM) + return "fldws %1,%0\n\tfcnvxf,sgl,sgl %0,%0"; + else if (FP_REG_P (operands[1])) + return "fcnvxf,sgl,sgl %1,%0"; + return "stwm %r1,4(0,30)\n\tfldws,mb -4(0,30),%0\n\tfcnvxf,sgl,sgl %0,%0"; +} + +char * +output_floatsidf2 (operands) + rtx *operands; +{ + if (GET_CODE (operands[1]) == MEM) + return "fldws %1,%0\n\tfcnvxf,sgl,dbl %0,%0"; + else if (FP_REG_P (operands[1])) + return "fcnvxf,sgl,dbl %1,%0"; + return "stwm %r1,4(0,30)\n\tfldws,mb -4(0,30),%0\n\tfcnvxf,sgl,dbl %0,%0"; +} + +enum rtx_code +reverse_relop (code) + enum rtx_code code; +{ + switch (code) + { + case GT: + return LT; + case LT: + return GT; + case GE: + return LE; + case LE: + return GE; + case LTU: + return GTU; + case GTU: + return LTU; + case GEU: + return LEU; + case LEU: + return GEU; + default: + abort (); + } +} + +/* HP's millicode routines mean something special to the assembler. + Keep track of which ones we have used. */ + +enum millicodes { remI, remU, divI, divU, mulI, mulU, end1000 }; +static char imported[(int)end1000]; +static char *milli_names[] = {"remI", "remU", "divI", "divU", "mulI", "mulU"}; +static char import_string[] = ".IMPORT $$....,MILLICODE"; +#define MILLI_START 10 + +static int +import_milli (code) + enum millicodes code; +{ + char str[sizeof (import_string)]; + + if (!imported[(int)code]) + { + imported[(int)code] = 1; + strcpy (str, import_string); + strncpy (str + MILLI_START, milli_names[(int)code], 4); + output_asm_insn (str, 0); + } +} + +/* The register constraints have put the operands and return value in + the proper registers. */ + +char * +output_mul_insn (unsignedp) + int unsignedp; +{ + if (unsignedp) + { + import_milli (mulU); + return "bl $$mulU,31\n\tnop"; + } + else + { + import_milli (mulI); + return "bl $$mulI,31\n\tnop"; + } +} + +/* If operands isn't NULL, then it's a CONST_INT with which we can do + something */ + + +/* Emit the rtl for doing a division by a constant. */ + + /* Do magic division millicodes exist for this value? */ + +static int magic_milli[]= {0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, + 1, 1}; + +/* We'll use an array to keep track of the magic millicodes and + whether or not we've used them already. [n][0] is signed, [n][1] is + unsigned. */ + + +static int div_milli[16][2]; + +int +div_operand (op, mode) + rtx op; + enum machine_mode mode; +{ + return (mode == SImode + && ((GET_CODE (op) == REG && REGNO (op) == 25) + || (GET_CODE (op) == CONST_INT && INTVAL (op) > 0 + && INTVAL (op) < 16 && magic_milli[INTVAL (op)]))); +} + +int +emit_hpdiv_const (operands, unsignedp) + rtx *operands; + int unsignedp; +{ + if (GET_CODE (operands[2]) == CONST_INT + && INTVAL (operands[2]) > 0 + && INTVAL (operands[2]) < 16 + && magic_milli[INTVAL (operands[2])]) + { + emit_move_insn ( gen_rtx (REG, SImode, 26), operands[1]); + emit + (gen_rtx + (PARALLEL, VOIDmode, + gen_rtvec (5, gen_rtx (SET, VOIDmode, gen_rtx (REG, SImode, 29), + gen_rtx (unsignedp ? UDIV : DIV, SImode, + gen_rtx (REG, SImode, 26), + operands[2])), + gen_rtx (CLOBBER, VOIDmode, gen_rtx (SCRATCH, SImode, 0)), + gen_rtx (CLOBBER, VOIDmode, gen_rtx (REG, SImode, 26)), + gen_rtx (CLOBBER, VOIDmode, gen_rtx (REG, SImode, 25)), + gen_rtx (CLOBBER, VOIDmode, gen_rtx (REG, SImode, 31))))); + emit_move_insn (operands[0], gen_rtx (REG, SImode, 29)); + return 1; + } + return 0; +} + +char * +output_div_insn (operands, unsignedp) + rtx *operands; + int unsignedp; +{ + int divisor; + + /* If the divisor is a constant, try to use one of the special + opcodes .*/ + if (GET_CODE (operands[0]) == CONST_INT) + { + divisor = INTVAL (operands[0]); + if (!div_milli[divisor][unsignedp]) + { + if (unsignedp) + output_asm_insn (".IMPORT $$divU_%0,MILLICODE", operands); + else + output_asm_insn (".IMPORT $$divI_%0,MILLICODE", operands); + div_milli[divisor][unsignedp] = 1; + } + if (unsignedp) + return "bl $$divU_%0,31%#"; + return "bl $$divI_%0,31%#"; + } + /* Divisor isn't a special constant. */ + else + { + if (unsignedp) + { + import_milli (divU); + return "bl $$divU,31%#"; + } + else + { + import_milli (divI); + return "bl $$divI,31%#"; + } + } +} + +/* Output a $$rem millicode to do mod. */ + +char * +output_mod_insn (unsignedp) + int unsignedp; +{ + if (unsignedp) + { + import_milli (remU); + return "bl $$remU,31%#"; + } + else + { + import_milli (remI); + return "bl $$remI,31%#"; + } +} + +void +output_arg_descriptor (insn) + rtx insn; +{ + char *arg_regs[4]; + enum machine_mode arg_mode; + rtx prev_insn; + int i, output_flag = 0; + int regno; + + for (i = 0; i < 4; i++) + arg_regs[i] = 0; + + for (prev_insn = PREV_INSN (insn); GET_CODE (prev_insn) == INSN; + prev_insn = PREV_INSN (prev_insn)) + { + if (!(GET_CODE (PATTERN (prev_insn)) == USE && + GET_CODE (XEXP (PATTERN (prev_insn), 0)) == REG && + FUNCTION_ARG_REGNO_P (REGNO (XEXP (PATTERN (prev_insn), 0))))) + break; + arg_mode = GET_MODE (XEXP (PATTERN (prev_insn), 0)); + regno = REGNO (XEXP (PATTERN (prev_insn), 0)); + if (regno >= 23 && regno <= 26) + arg_regs[26 - regno] = "GR"; + else if (!TARGET_SNAKE) /* fp args */ + { + if (arg_mode == SFmode) + arg_regs[regno - 36] = "FR"; + else + { +#ifdef HP_FP_ARG_DESCRIPTOR_REVERSED + arg_regs[regno - 37] = "FR"; + arg_regs[regno - 36] = "FU"; +#else + arg_regs[regno - 37] = "FU"; + arg_regs[regno - 36] = "FR"; +#endif + } + } + else + { + if (arg_mode == SFmode) + arg_regs[(regno - 56) / 2] = "FR"; + else + { +#ifdef HP_FP_ARG_DESCRIPTOR_REVERSED + arg_regs[(regno - 58) / 2] = "FR"; + arg_regs[(regno - 58) / 2 + 1] = "FU"; +#else + arg_regs[(regno - 58) / 2] = "FU"; + arg_regs[(regno - 58) / 2 + 1] = "FR"; +#endif + } + } + } + fputs ("\t.CALL ", asm_out_file); + for (i = 0; i < 4; i++) + { + if (arg_regs[i]) + { + if (output_flag++) + fputc (',', asm_out_file); + fprintf (asm_out_file, "ARGW%d=%s", i, arg_regs[i]); + } + } + fputc ('\n', asm_out_file); +} + +/* Memory loads/stores to/from fp registers may need a scratch + register in which to reload the address. */ + +enum reg_class +secondary_reload_class (class, mode, in) + enum reg_class class; + enum machine_mode mode; + rtx in; +{ + int regno = true_regnum (in); + + if (regno >= FIRST_PSEUDO_REGISTER) + regno = -1; + + if (class == FP_REGS || class == SNAKE_FP_REGS || class == HI_SNAKE_FP_REGS) + { + if (regno == -1 || !REGNO_OK_FOR_FP_P (regno)) + return GENERAL_REGS; + } + return NO_REGS; +} + +enum direction +function_arg_padding (mode, type) + enum machine_mode mode; + tree type; +{ + int size; + + if (mode == BLKmode) + { + if (type && TREE_CODE (TYPE_SIZE (type)) == INTEGER_CST) + size = int_size_in_bytes (type) * BITS_PER_UNIT; + else + return upward; /* Don't know if this is right, but */ + /* same as old definition. */ + } + else + size = GET_MODE_BITSIZE (mode); + if (size < PARM_BOUNDARY) + return downward; + else if (size % PARM_BOUNDARY) + return upward; + else + return none; +} + +int +use_milli_regs (insn) + rtx insn; +{ + return (reg_mentioned_p (gen_rtx (REG, SImode, 1), insn) || + reg_mentioned_p (gen_rtx (REG, SImode, 25), insn) || + reg_mentioned_p (gen_rtx (REG, SImode, 26), insn) || + reg_mentioned_p (gen_rtx (REG, SImode, 29), insn) || + reg_mentioned_p (gen_rtx (REG, SImode, 31), insn)); +} + +/* Do what is necessary for `va_start'. The argument is ignored; + We look at the current function to determine if stdargs or varargs + is used and fill in an initial va_list. A pointer to this constructor + is returned. */ + +struct rtx_def * +hppa_builtin_saveregs (arglist) + tree arglist; +{ + rtx block, float_addr, offset, float_mem; + tree fntype = TREE_TYPE (current_function_decl); + int argadj = ((!(TYPE_ARG_TYPES (fntype) != 0 + && (TREE_VALUE (tree_last (TYPE_ARG_TYPES (fntype))) + != void_type_node))) + ? UNITS_PER_WORD : 0); + + if (argadj) + offset = plus_constant (current_function_arg_offset_rtx, argadj); + else + offset = current_function_arg_offset_rtx; + /* Allocate the va_list structure. */ + block = assign_stack_local (BLKmode, 4 * UNITS_PER_WORD, BITS_PER_UNIT); + RTX_UNCHANGING_P (block) = 1; + RTX_UNCHANGING_P (XEXP (block, 0)) = 1; + /* + * Store a pointer to where arguments should begin on the stack in + * __va_stack_start. + */ + emit_move_insn (change_address (block, Pmode, XEXP (block, 0)), + copy_to_reg + (plus_constant (current_function_internal_arg_pointer, + -16))); + /* Store where to start getting args from in the __va_int member. */ + emit_move_insn (change_address (block, Pmode, + plus_constant (XEXP (block, 0), + UNITS_PER_WORD)), + copy_to_reg (expand_binop (Pmode, add_optab, + current_function_internal_arg_pointer, + offset, + 0, 0, OPTAB_LIB_WIDEN))); + /* Store general registers on the stack. */ + move_block_from_reg (23, + gen_rtx (MEM, BLKmode, + plus_constant + (current_function_internal_arg_pointer, -16)), + 4); + /* + * Allocate space for the float args, and store it in the + * __va_float member. + */ + float_addr = copy_to_reg (XEXP (float_mem = + assign_stack_local (BLKmode, + 4 * UNITS_PER_WORD, -1), + 0)); + MEM_IN_STRUCT_P (float_mem) = 1; + RTX_UNCHANGING_P (float_mem) = 1; + RTX_UNCHANGING_P (XEXP (float_mem, 0)) = 1; + emit_move_insn (change_address (block, Pmode, + plus_constant (XEXP (block, 0), + 2 * UNITS_PER_WORD)), + copy_to_reg (expand_binop (Pmode, add_optab, + float_addr, + plus_constant (offset, 4 * + UNITS_PER_WORD), + 0, 0, OPTAB_LIB_WIDEN))); + /* Store fp registers. */ + emit_move_insn (gen_rtx (MEM, SFmode, float_addr), + gen_rtx (REG, SFmode, TARGET_SNAKE ? 60 : 39)); + emit_move_insn (gen_rtx (MEM, SFmode, gen_rtx (PLUS, Pmode, float_addr, + gen_rtx (CONST_INT, + Pmode, 4))), + gen_rtx (REG, SFmode, TARGET_SNAKE ? 58 : 38)); + emit_move_insn (gen_rtx (MEM, SFmode, gen_rtx (PLUS, Pmode, float_addr, + gen_rtx (CONST_INT, + Pmode, 8))), + gen_rtx (REG, SFmode, TARGET_SNAKE ? 56 : 37)); + emit_move_insn (gen_rtx (MEM, SFmode, gen_rtx (PLUS, Pmode, float_addr, + gen_rtx (CONST_INT, + Pmode, 12))), + gen_rtx (REG, SFmode, TARGET_SNAKE ? 54 : 36)); + /* + * Allocate space for the double args, and store it in the + * __va_double member. + */ + float_addr = copy_to_reg (XEXP (float_mem = + assign_stack_local (BLKmode, + 4 * UNITS_PER_WORD, -1), + 0)); + MEM_IN_STRUCT_P (float_mem) = 1; + RTX_UNCHANGING_P (float_mem) = 1; + RTX_UNCHANGING_P (XEXP (float_mem, 0)) = 1; + emit_move_insn (change_address (block, Pmode, + plus_constant (XEXP (block, 0), + 3 * UNITS_PER_WORD)), + copy_to_reg (expand_binop (Pmode, add_optab, + float_addr, + plus_constant (offset, 4 * + UNITS_PER_WORD), + 0, 0, OPTAB_LIB_WIDEN))); + /* Store fp registers as doubles. */ + + emit_move_insn (gen_rtx (MEM, DFmode, float_addr), + (gen_rtx (REG, DFmode, TARGET_SNAKE ? 60 : 39))); + emit_move_insn (gen_rtx (MEM, DFmode, gen_rtx (PLUS, Pmode, float_addr, + gen_rtx (CONST_INT, + Pmode, 8))), + gen_rtx (REG, DFmode, TARGET_SNAKE ? 56 : 37)); + return copy_to_reg (XEXP (block, 0)); +} -- 2.30.2