Initial revision
authorTorbjorn Granlund <tege@gnu.org>
Fri, 10 Apr 1992 22:36:23 +0000 (22:36 +0000)
committerTorbjorn Granlund <tege@gnu.org>
Fri, 10 Apr 1992 22:36:23 +0000 (22:36 +0000)
From-SVN: r724

gcc/config/pa/pa.c [new file with mode: 0644]

diff --git a/gcc/config/pa/pa.c b/gcc/config/pa/pa.c
new file mode 100644 (file)
index 0000000..e8d3224
--- /dev/null
@@ -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 <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 "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);
+}
+\f
+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));
+}
+
+\f
+/* 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;
+}
+     
+\f
+/* 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";
+}
+\f
+
+/* 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 "";
+}
+\f
+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 "";
+}
+\f
+/* 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 "";
+}
+\f
+
+/* 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");
+}
+\f
+/* 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);
+}
+\f
+/* 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));
+}
+\f
+/* 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));
+}