/* Subroutines for insn-output.c for Intel 80386.
   Copyright (C) 1988 Free Software Foundation, Inc.

This file is part of GNU CC.

GNU CC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.  No author or distributor
accepts responsibility to anyone for the consequences of using it
or for whether it serves any particular purpose or works at all,
unless he says so in writing.  Refer to the GNU CC General Public
License for full details.

Everyone is granted permission to copy, modify and redistribute
GNU CC, but only under the conditions described in the
GNU CC General Public License.   A copy of this license is
supposed to have been given to you along with GNU CC so you
can know your rights and responsibilities.  It should be in a
file named COPYING.  Among other things, the copyright notice
and this notice must be preserved on all copies.  */

#ifndef FILE
#include <stdio.h>
#endif

#define FP_TOP (gen_rtx(REG, DFmode, FIRST_FLOAT_REG))

#define AT_SP(mode) (gen_rtx (MEM, (mode), stack_pointer_rtx))
#define AT_BP(mode) (gen_rtx (MEM, (mode), frame_pointer_rtx))

#define RET return ""

/* #define RETCOM(X) fprintf (asm_out_file, "%sX fp_pop_level=%d\n", \
			   COMMENT_BEGIN, fp_pop_level); RET */
#define RETCOM(X) return ""

#define POP_ONE_FP	\
  { /* fp_pop_level--; */	\
    fprintf (asm_out_file, "\tfstp %sst (0)\n", RP); }

extern FILE *asm_out_file;
static char *singlemove_string ();
static void output_movf ();
static void replace_float_constant ();
static int mentions_fp_top ();
static int call_top_dead_p ();
static int fp_top_dead_p1 ();
static rtx via_memory ();
static void output_asm_insn_double_reg_op ();

/* All output functions must increment or decrement this to indicate
   the net number of pops or pushes which they perform.  Note that it won't
   necessarily balance with the optimize running, since we might have
   two different calls with the same pop shared by cross jumping.
   However on optimize the reg dead heuristic seems to work.  */

int fp_pop_level = 0;

static char *hi_reg_name[] = HI_REGISTER_NAMES;
static char *qi_reg_name[] = QI_REGISTER_NAMES;

/* for fabs, fch, .. where the argument operand[1] must first be moved to
  constraints  "=fm" "0" */

#define FP_CALL1(op)  \
  { if (FP_REG_P (operands[0]))		\
      return op;			\
    output_movf (FP_TOP, operands[1]);	\
    output_asm_insn (op, operands);	\
    /* fp_pop_level--; */		\
    return "fstp%z0 %0"; }

/* handle case of call where op0/op1 is "=mf" and opn is "mrf"
   eg. fadd */
#define FP_CALL(op, rev, n) fp_call_internal (op, rev, n, operands, insn);

static char *
fp_call_internal (op, rev, n, operands, insn)
     char *op;
     char *rev;
     int n;
     rtx *operands;
     rtx insn;
{
  if (!FP_REG_P (operands[0]))
    {
      /* Here destination is in memory
	 and source is in the fp stack.  */
      output_movf (FP_TOP, operands[0]);
      output_asm_insn_double_reg_op (op, rev, insn);
      return "fstp%z0 %0";
    }

  if (FP_REG_P (operands[n]))
    {
      rtx temp = operands[1];
      char *tem1 = op;
      operands[1] = operands[n];
      op = rev;
      operands[n] = temp;
      rev = tem1;
    }

  if (REG_P (operands[n]))
    {
      rtx xops[2];
      via_memory (operands[n]);
      operands[n] = AT_SP (GET_MODE (operands[n]));
      xops[0] = stack_pointer_rtx;
      xops[1] = gen_rtx (CONST_INT, VOIDmode,
			 GET_MODE_SIZE (GET_MODE (operands[n])));
      output_asm_insn (op, operands + n);
      output_asm_insn (AS2 (add%L,%1,%0), xops);
    }
  else
    output_asm_insn (op, operands + n);

  RET;
}

/* Output assembler code to perform insn OP
   with two stack operands, and output on the stack.

   REV is the assembler insn that does the same thing but
   effectively interchanges the meanings of the two arguments.

   Somewhat counterintuitively, the "first" operand was pushed last.

   The output replaces either the top-of-stack or both of the arguments,
   depending on whether the other argument is wanted after this insn.  */

static void
output_asm_insn_double_reg_op (op, rev, insn)
     char *op;
     char *rev;
     rtx insn;
{
  fputc ('\t', asm_out_file);
  if (top_dead_p (insn))
    {
      /* Here we want the "reversed" insn, fsubr or fdivr.
	 But there is an assembler bug in all 80386 assemblers
	 which exchanges the meanings of fsubr and fsub, and of fdivr and fdiv!
	 So use the "unreversed" opcode (which will assemble into
	 the "reversed" insn).  */
      rev = op;

      while (*rev && *rev != '%')
	fputc (*rev++, asm_out_file);
      /* fp_pop_level--; */

      fprintf (asm_out_file, AS2 (p,%sst,%sst(1)), RP, RP);
    }
  else
    {
      while (*op && *op != '%')
	fputc (*op++, asm_out_file);
      fprintf (asm_out_file,AS2 ( ,%sst(1),%sst), RP, RP);
    }
  putc ('\n', asm_out_file);
}

/* Moves X to memory location 8 below stack pointer
   and returns an RTX for that memory location.
   X should be a register, in DFmode or SFmode.  */

static rtx
via_memory (x)
     rtx x;
{
  if (!REG_P (x))
    abort ();
  if (GET_MODE (x) == DFmode)
    {
      rtx xops[1];
      xops[0] = gen_rtx (REG, SImode, REGNO (x) + 1);
      output_asm_insn ("push%L %0", xops);
    }
  output_asm_insn ("push%L %0", &x);
}

/* Output an insn to copy the SFmode value in fp0 to OPERAND
   without clobbering fp0.  */

void
fp_store_sf (target)
     rtx target;
{
  if (REG_P (target))
    {
      rtx xoperands[3];
      xoperands[0] = stack_pointer_rtx;
      xoperands[1] = AT_SP (Pmode);
      xoperands[2] = gen_rtx (CONST_INT, VOIDmode, -4);
      output_asm_insn (AS2 (add%L,%2,%0), xoperands);
      output_asm_insn ("fst%S %1", xoperands);
      output_asm_insn ("pop%L %0", &target);
    }
  else if (GET_CODE (target) == MEM)
    output_asm_insn ("fst%S %0", &target);
}

/* Output an insn to pop an SF value from fp0 into TARGET.
   This destroys the value of fp0.  */

void
fp_pop_sf (target)
     rtx target;
{
  if (REG_P (target))
    {
      rtx xoperands[3];
      xoperands[0] = stack_pointer_rtx;
      xoperands[1] = AT_SP (Pmode);
      xoperands[2] = gen_rtx (CONST_INT, VOIDmode, -4);
      output_asm_insn (AS2 (add%L,%2,%0), xoperands);
      output_asm_insn ("fstp%S %1", xoperands);
      output_asm_insn ("pop%L %0", &target);
      /* fp_pop_level--; */
    }
  else if (GET_CODE (target) == MEM)
    {
      /* fp_pop_level--; */
      output_asm_insn ("fstp%S %0", &target);
    }
  else abort ();
}

/* Copy the top of the fpu stack into TARGET, without popping.  */

void
fp_store_df (target)
     rtx target;
{
  if (REG_P (target))
    {
      rtx xoperands[4];
      xoperands[0] = stack_pointer_rtx;
      xoperands[1] = gen_rtx (REG, SImode, REGNO (target) + 1);
      xoperands[2] = AT_SP (Pmode);
      xoperands[3] = gen_rtx (CONST_INT, VOIDmode, -8);
      output_asm_insn (AS2 (add%L,%3,%0), xoperands);
      output_asm_insn ("fst%Q %2", xoperands);
      output_asm_insn ("pop%L %0", &target);
      output_asm_insn ("pop%L %1", xoperands);
    }
  else if (GET_CODE (target) == MEM)
    output_asm_insn ("fst%Q %0", &target);
}

/* Copy the top of the fpu stack into TARGET, with popping.  */

void
fp_pop_df (target)
     rtx target;
{
  if (REG_P (target))
    {
      rtx xoperands[4];
      xoperands[0] = stack_pointer_rtx;
      xoperands[1] = gen_rtx (REG, SImode, REGNO (target) + 1);
      xoperands[2] = AT_SP (Pmode);
      xoperands[3] = gen_rtx (CONST_INT, VOIDmode, -8);
      output_asm_insn (AS2 (add%L,%3,%0), xoperands);
      /* fp_pop_level--; */
      output_asm_insn ("fstp%Q %2", xoperands);
      output_asm_insn ("pop%L %0", &target);
      output_asm_insn ("pop%L %1", xoperands);
    }
  else if (GET_CODE (target) == MEM)
    {
      /* fp_pop_level--; */
      output_asm_insn ("fstp%z0 %0", &target);
    }
}

#if 0
/* Pop the fp stack, convert value to integer and store in TARGET.
   TARGET may be memory or register, and may have QI, HI or SImode.  */

void
fp_pop_int (target)
     rtx target;
{
  if (REG_P (target) || GET_MODE (target) != SImode)
    {
      rtx xxops[2];
      xxops[0] = stack_pointer_rtx;
      xxops[1] = gen_rtx (CONST_INT, VOIDmode, 4);
      output_asm_insn (AS2 (sub%L,%1,%0), xxops);
      xxops[0] = AT_SP (Pmode);
      /* fp_pop_level--; */
      output_asm_insn ("fistps %0", xxops);
      output_asm_insn ("pop%L %0", &target);
    }
  else if (GET_CODE (target) == MEM)
    {
      /* fp_pop_level--; */
      output_asm_insn ("fistps %0", &target);
    }
  else abort ();
}
#endif

/* Push the SFmode value X onto the fpu stack.  */

void
fp_push_sf (x)
     rtx x;
{
  /* fp_pop_level++; */
  if (REG_P (x))
    {
      rtx xoperands[2];
      rtx xfops[3];
      output_asm_insn ("push%L %0", &x);
      xfops[0] = AT_SP (Pmode);
      xfops[2] = gen_rtx (CONST_INT, VOIDmode, 4);
      xfops[1] = stack_pointer_rtx;
      output_asm_insn ("fld%S %0 \n\tadd%L %2,%1", xfops);
    }
  else
    output_asm_insn ("fld%S %0", &x);
}

/* Push the DFmode value X onto the fpu stack.  */

void
fp_push_df (x)
     rtx x;
{
  /* fp_pop_level++; */

  if (REG_P (x))
    {
      rtx xoperands[2];
      rtx xfops[3];
      xoperands[0] = x;
      xoperands[1] = gen_rtx (REG, SImode, REGNO (x) + 1);
      output_asm_insn ("push%L %1", xoperands);
      output_asm_insn ("push%L %0", xoperands);
      xfops[0] = AT_SP (Pmode);
      xfops[2] = gen_rtx (CONST_INT, VOIDmode, 8);
      xfops[1] = stack_pointer_rtx;
      output_asm_insn ("fld%Q %0 \n\tadd%L %2,%1", xfops);
    }
  else if (GET_CODE (x) == MEM)
    output_asm_insn ("fld%Q %0", &x);
}

static char *output_move_const_single ();

static char *
singlemove_string (operands)
     rtx *operands;
{
  rtx x;
  if (GET_CODE (operands[0]) == MEM
      && GET_CODE (x = XEXP (operands[0], 0)) == PRE_DEC)
    {
      if (XEXP (x, 0) != stack_pointer_rtx)
	abort ();
      return "push%L %1";
    }
  else if (GET_CODE (operands[1]) == CONST_DOUBLE)
    {
      return output_move_const_single (operands);
    }
  else if (GET_CODE (operands[0]) == REG || GET_CODE (operands[1]) == REG)
    return AS2 (mov%L,%1,%0);
  else
    {
      output_asm_insn ("push%L %1", operands);
      return "pop%L %0";
    }
}

/* 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);
      if (GET_CODE (XEXP (addr, 1)) == REG)
	addr = XEXP (addr, 1);
      if (CONSTANT_P (XEXP (addr, 0)))
	addr = XEXP (addr, 1);
      if (CONSTANT_P (XEXP (addr, 1)))
	addr = XEXP (addr, 0);
    }
  if (GET_CODE (addr) == REG)
    return addr;
  return 0;
}

/* Output an insn to add the constant N to the register X.  */

static void
asm_add (n, x)
     int n;
     rtx x;
{
  rtx xops[2];
  xops[1] = x;
  if (n < 0)
    {
      xops[0] = gen_rtx (CONST_INT, VOIDmode, -n);
      output_asm_insn (AS2 (sub%L,%0,%1), xops);
    }
  else if (n > 0)
    {
      xops[0] = gen_rtx (CONST_INT, VOIDmode, n);
      output_asm_insn (AS2 (add%L,%0,%1), xops);
    }
}

/* Output assembler code to perform a doubleword move insn
   with operands OPERANDS.  */

char *
output_move_double (operands)
     rtx *operands;
{
  enum {REGOP, OFFSOP, MEMOP, PUSHOP, POPOP, CNSTOP, RNDOP } optype0, optype1;
  rtx latehalf[2];
  rtx addreg0 = 0, addreg1 = 0;

  /* First classify both operands.  */

  if (REG_P (operands[0]))
    optype0 = REGOP;
  else if (offsetable_memref_p (operands[0]))
    optype0 = OFFSOP;
  else if (GET_CODE (XEXP (operands[0], 0)) == POST_INC)
    optype0 = POPOP;
  else if (GET_CODE (XEXP (operands[0], 0)) == PRE_DEC)
    optype0 = PUSHOP;
  else if (GET_CODE (operands[0]) == MEM)
    optype0 = MEMOP;
  else
    optype0 = RNDOP;

  if (REG_P (operands[1]))
    optype1 = REGOP;
  else if (CONSTANT_P (operands[1])
	   || GET_CODE (operands[1]) == CONST_DOUBLE)
    optype1 = CNSTOP;
  else if (offsetable_memref_p (operands[1]))
    optype1 = OFFSOP;
  else if (GET_CODE (XEXP (operands[1], 0)) == POST_INC)
    optype1 = POPOP;
  else if (GET_CODE (XEXP (operands[1], 0)) == PRE_DEC)
    optype1 = PUSHOP;
  else if (GET_CODE (operands[1]) == MEM)
    optype1 = MEMOP;
  else
    optype1 = RNDOP;

  /* Check for the cases that the operand constraints are not
     supposed to allow to happen.  Abort if we get one,
     because generating code for these cases is painful.  */

  if (optype0 == RNDOP || optype1 == RNDOP)
    abort ();

  /* If one operand is decrementing and one is incrementing
     decrement the former register explicitly
     and change that operand into ordinary indexing.  */

  if (optype0 == PUSHOP && optype1 == POPOP)
    {
      operands[0] = XEXP (XEXP (operands[0], 0), 0);
      asm_add (-8, operands[0]);
      operands[0] = gen_rtx (MEM, DImode, operands[0]);
      optype0 = OFFSOP;
    }
  if (optype0 == POPOP && optype1 == PUSHOP)
    {
      operands[1] = XEXP (XEXP (operands[1], 0), 0);
      asm_add (-8, operands[1]);
      operands[1] = gen_rtx (MEM, DImode, operands[1]);
      optype1 = OFFSOP;
    }

  /* If an operand is an unoffsettable memory ref, find a register
     we can increment temporarily to make it refer to the second word.  */

  if (optype0 == MEMOP)
    addreg0 = find_addr_reg (operands[0]);

  if (optype1 == MEMOP)
    addreg1 = find_addr_reg (operands[1]);

  /* Ok, we can do one word at a time.
     Normally we do the low-numbered word first,
     but if either operand is autodecrementing then we
     do the high-numbered word first.

     In either case, set up in LATEHALF the operands to use
     for the high-numbered word and in some cases alter the
     operands in OPERANDS to be suitable for the low-numbered word.  */

  if (optype0 == REGOP)
    latehalf[0] = gen_rtx (REG, SImode, REGNO (operands[0]) + 1);
  else if (optype0 == OFFSOP)
    latehalf[0] = adj_offsetable_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_offsetable_operand (operands[1], 4);
  else if (optype1 == CNSTOP)
    {
      if (CONSTANT_P (operands[1]))
	latehalf[1] = const0_rtx;
      else if (GET_CODE (operands[1]) == CONST_DOUBLE)
	{
	  latehalf[1] = gen_rtx (CONST_INT, VOIDmode, XINT (operands[1], 1));
	  operands[1] = gen_rtx (CONST_INT, VOIDmode, XINT (operands[1], 0));
	}
    }
  else
    latehalf[1] = operands[1];

  /* If insn is effectively movd N (sp),-(sp) then we will do the
     high word first.  We should use the adjusted operand 1 (which is N+4 (sp))
     for the low word as well, to compensate for the first decrement of sp.  */
  if (optype0 == PUSHOP
      && REGNO (XEXP (XEXP (operands[0], 0), 0)) == STACK_POINTER_REGNUM
      && reg_overlap_mentioned_p (stack_pointer_rtx, operands[1]))
    operands[1] = latehalf[1];

  /* If one or both operands autodecrementing,
     do the two words, high-numbered first.  */

  /* Likewise,  the first move would clobber the source of the second one,
     do them in the other order.  This happens only for registers;
     such overlap can't happen in memory unless the user explicitly
     sets it up, and that is an undefined circumstance.  */

  if (optype0 == PUSHOP || optype1 == PUSHOP
      || (optype0 == REGOP && optype1 == REGOP
	  && REGNO (operands[0]) == REGNO (latehalf[1])))
    {
      /* Make any unoffsetable addresses point at high-numbered word.  */
      if (addreg0)
	asm_add (4, addreg0);
      if (addreg1)
	asm_add (4, addreg1);

      /* Do that word.  */
      output_asm_insn (singlemove_string (latehalf), latehalf);

      /* Undo the adds we just did.  */
      if (addreg0)
         asm_add (-4, addreg0);
      if (addreg1)
	asm_add (-4, addreg1);

      /* Do low-numbered word.  */
      return singlemove_string (operands);
    }

  /* Normal case: do the two words, low-numbered first.  */

  output_asm_insn (singlemove_string (operands), operands);

  /* Make any unoffsetable addresses point at high-numbered word.  */
  if (addreg0)
    asm_add (4, addreg0);
  if (addreg1)
    asm_add (4, addreg1);

  /* Do that word.  */
  output_asm_insn (singlemove_string (latehalf), latehalf);

  /* Undo the adds we just did.  */
  if (addreg0)
    asm_add (-4, addreg0);
  if (addreg1)
    asm_add (-4, addreg1);

  return "";
}

int
standard_80387_constant_p (x)
     rtx x;
{
  union { double d; int i[2];} u;
  register double d;
  u.i[0] = XINT (x, 0);
  u.i[1] = XINT (x, 1);
  d = u.d;

  if (d == 0)
    return 1;
  if (d == 1)
    return 2;
  return 0;
}

static char *
output_move_const_double (operands)
     rtx *operands;
{
  if (FP_REG_P (operands[0]))
    {
      int conval = standard_80387_constant_p (operands[1]);

      /* fp_pop_level++; */
      if (conval == 1)
	return "fldz";
      if (conval == 2)
	return "fld1";
      /* fp_pop_level--; */
    }

  output_move_double (operands);
}


static char *
output_move_const_single (operands)
     rtx *operands;
{
  if (FP_REG_P (operands[0]))
    {
      int conval = standard_80387_constant_p (operands[1]);

      /* fp_pop_level++; */
      if (conval == 1)
	return "fldz";
      if (conval == 2)
	return "fld1";
      /* fp_pop_level--; */
    }
  if (GET_CODE (operands[1]) == CONST_DOUBLE)
    {
      union { int i[2]; double d;} u1;
      union { int i; float f;} u2;
      u1.i[0] = XINT (operands[1], 0);
      u1.i[1] = XINT (operands[1], 1);
      u2.f = u1.d;
      operands[1] = gen_rtx (CONST_INT, VOIDmode, u2.i);
    }
  return singlemove_string (operands);
}

/* Output an insn to move an SF value from FROM to TO.
   The kinds of operands are not restricted
   except that they may not both be in memory.  */

void
output_movsf (to, from)
     rtx from, to;
{
  rtx xops[2];
  xops[0] = to;
  xops[1] = from;
  if (FP_REG_P (from) || FP_REG_P (to))
    {
      from = xops[1];
    }

  if (FP_REG_P (from))
    {
#if 0
	{
	  if (REGNO (from) != REGNO (to))
	    {
	      output_asm_insn ("fld%S %1 \n\tfstp%S %0", xops);
	    }
	}
      else
#endif

      if (! FP_REG_P (to))
	fp_pop_sf (to);
    }
  else if (FP_REG_P (to))
    fp_push_sf (from);
  else
    output_asm_insn (singlemove_string (xops), xops);
}

/* Output an insn to move a DF value from FROM to TO.
   The kinds of operands are not restricted
   except that they may not both be in memory.  */

void
output_movdf (to, from)
     rtx from, to;
{
  rtx xops[2];
  xops[0] = to;
  xops[1] = from;
  if (FP_REG_P (from) || FP_REG_P (to))
    {
      from = xops[1];
      to = xops[0];
    }
  if (FP_REG_P (from))
    {
#if 0
	{
	  if (REGNO (from) != REGNO (to))
	    abort ();
/*	    output_asm_insn ("fld%Q %1 \n\t fstp%Q %0", xops);*/
	}
      else
	{
#endif
      if (! FP_REG_P (to))
	fp_pop_df (to);
    }
  else if (FP_REG_P (to))
    fp_push_df (from);
  else
    output_asm_insn (output_move_double (xops), xops);
}

/* does move of FROM to TO where the mode is the minimum of the
two */

static void
output_movf (to, from)
     rtx to, from;
{
  if (GET_MODE (from) == SFmode || GET_MODE (to) == SFmode)
    output_movsf (to, from);
  else
    output_movdf (to, from);
}

/* Return the best assembler insn template
   for moving operands[1] into operands[0] as a fullword.  */

void
function_prologue (file, size)
     FILE *file;
     int size;
{
  register int regno;
  int nregs, limit;
  rtx xops[4];
  static char dont_save_regs[] = CALL_USED_REGISTERS;
  /*  static char *reg_names[] = REGISTER_NAMES;  */
  extern int frame_pointer_needed;

  /* fp_pop_level = 0; */
  xops[0] = stack_pointer_rtx;
  xops[1] = frame_pointer_rtx;
  xops[2] = gen_rtx (CONST_INT, VOIDmode, size);
  if (frame_pointer_needed)
    {
      output_asm_insn ("push%L %1", xops);
      output_asm_insn (AS2 (mov%L,%0,%1), xops);
      if (size)
	output_asm_insn (AS2 (sub%L,%2,%0), xops);
    }

  /* Note If use enter it is NOT reversed args.
     This one is not reversed from intel!!
     I think enter is slower.  Also sdb doesn't like it.
     But if you want it the code is:
     {
     xops[3] = const0_rtx;
     output_asm_insn ("enter %2,%3", xops);
     }
     */
  nregs = 0;
  limit = (frame_pointer_needed ? FRAME_POINTER_REGNUM : STACK_POINTER_REGNUM);
  for (regno = 2; regno < limit; regno++)
    if (regs_ever_live[regno] && ! dont_save_regs[regno])
      {
	fprintf (file, "\tpush%s %se%s\n", L_SIZE, RP, hi_reg_name[regno]);
      }
}

void
function_epilogue (file, size)
     FILE *file;
     int size;
{
  register int regno;
  register int nregs, limit;
  int assure_sp_pos;
  static char dont_save_regs[] = CALL_USED_REGISTERS;
  /*  static char *reg_names[] = REGISTER_NAMES; */
  extern int frame_pointer_needed;
  extern int current_function_pops_args;
  extern int current_function_args_size;
  limit = (frame_pointer_needed ? FRAME_POINTER_REGNUM : STACK_POINTER_REGNUM);
  nregs = 0;


  for (regno = (limit -1); regno >= 2; regno--)
    if (regs_ever_live[regno] && ! dont_save_regs[regno])
      nregs++;

  /* sp is often  unreliable so we must go off the frame pointer,
   */

  if (nregs && frame_pointer_needed)
    {
      rtx xops[2];
      xops[0] = adj_offsetable_operand (AT_BP (Pmode),
					-size -(nregs*(UNITS_PER_WORD)));
      xops[1] = stack_pointer_rtx;
      output_asm_insn (AS2 (lea%L,%0,%1), xops);
    }
  for (regno = (limit -1); regno >= 2; regno--)
    {
      if (regs_ever_live[regno] && ! dont_save_regs[regno])
	{
	  fprintf (file, "\tpop%s ", L_SIZE);
	  fprintf (file, "%se%s\n", RP, hi_reg_name[regno]);
	}
    }

  if (frame_pointer_needed)  fprintf (file, "\tleave\n");
  if (current_function_pops_args && current_function_args_size)
    fprintf (file, "\tret %s%d\n", IP, current_function_args_size);
  else fprintf (file, "\tret\n")
    ;
  /* the following should be 1 for a function returning a float and
     0 otherwise */
  /* fprintf (file, "%s At return: fp_pop_level= %d\n", COMMENT_BEGIN, fp_pop_level); */
}

int
hard_regno_mode_ok (regno, mode)
     int regno;
     enum machine_mode mode;
{
  return
    (regno < 2 ? 1
     /* Used to reject floating modes here */
     : regno < 4 ? 1
     : regno >= 8 ? mode == DFmode || mode == SFmode
     : mode != QImode);
}

/* Print the name of a register based on its machine mode and number.
   If CODE is 'w', pretend the mode is HImode.  */

#define PRINT_REG(X, CODE, FILE) \
  do { fprintf (FILE, "%s", RP);			\
       switch ((CODE == 'w' ? 2 : GET_MODE_SIZE (GET_MODE (X)))) \
	 {						\
	 case 4:					\
	 case 8:					\
	   if (!FP_REG_P (X)) fputs ("e", FILE);	\
	 case 2:					\
	   fputs (hi_reg_name[REGNO (X)], FILE);	\
	   break;					\
	 case 1:					\
	   fputs (qi_reg_name[REGNO (X)], FILE);	\
	 }						\
     } while (0)

/* Meaning of CODE:
   f -- float insn (print a CONST_DOUBLE as a float rather than in hex).
   L,W,B,Q,S -- print the opcode suffix for specified size of operand.
   R -- print the prefix for register names.
   z -- print the opcode suffix for the size of the current operand.
   * -- print a star (in certain assembler syntax)
   w -- print the operand as if it's a "word" (HImode) even if it isn't.
   c -- don't print special prefixes before constant operands.
*/

void
print_operand (file, x, code)
     FILE *file;
     rtx x;
     int code;
{
  if (code)
    {
      switch (code)
	{
	case '*':
	  if (USE_STAR)
	    putc ('*', file);
	  return;

	case 'L':
	  PUT_OP_SIZE (code, 'l', file);
	  return;

	case 'W':
	  PUT_OP_SIZE (code, 'w', file);
	  return;

	case 'B':
	  PUT_OP_SIZE (code, 'b', file);
	  return;

	case 'Q':
	  PUT_OP_SIZE (code, 'l', file);
	  return;

	case 'S':
	  PUT_OP_SIZE (code, 's', file);
	  return;

	case 'R':
	  fprintf (file, "%s", RP);
	  return;

	case 'z':
	  /* this is the size of op from size of operand */
	  switch (GET_MODE_SIZE (GET_MODE (x)))
	    {
	    case 2:
	      PUT_OP_SIZE ('W', 'w', file);
	      return;
	    case 4:
	      if (GET_MODE (x) == SFmode)
		{
		  PUT_OP_SIZE ('S', 's', file);
		  return;
		}
	      else
		PUT_OP_SIZE ('L', 'l', file);
	      return;
	    case 8:
	      if (!FP_REG_P (x)) PUT_OP_SIZE ('Q', 'l', file);
	      return;
	    case 1:
	      PUT_OP_SIZE ('B', 'b', file);
	      return;
	    }
	}
    }
  if (GET_CODE (x) == REG)
    {
      PRINT_REG (x, code, file);
    }
  else if (GET_CODE (x) == MEM)
    {
      PRINT_PTR (x, file);
      if (CONSTANT_ADDRESS_P (XEXP (x, 0)))
	output_addr_const (file, XEXP (x, 0));
      else
	output_address (XEXP (x, 0));
    }
  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, "%.22e", u1.f);
      else
        {
	  PRINT_IMMED_PREFIX (file);
	  fprintf (file, "0x%x", u1.i);
	}
    }
  else if (GET_CODE (x) == CONST_DOUBLE && GET_MODE (x) == DFmode)
    {
      union { double d; int i[2]; } u;
      u.i[0] = XINT (x, 0);
      u.i[1] = XINT (x, 1);
      fprintf (file, "%.22e", u.d);
    }
  else 
    {
      if (code != 'c')
	{
	  if (GET_CODE (x) == CONST_INT)
	    PRINT_IMMED_PREFIX (file);
	  else if (GET_CODE (x) == CONST || GET_CODE (x) == SYMBOL_REF)
	    PRINT_OFFSET_PREFIX (file);
	}
      output_addr_const (file, x);
    }
}

/* Print a memory operand whose address is ADDR.  */

void
print_operand_address (file, addr)
     FILE *file;
     register rtx addr;
{
  /* static char *reg_name[] = REGISTER_NAMES; */
  register rtx reg1, reg2, breg, ireg;
  rtx offset;

  switch (GET_CODE (addr))
    {
    case REG:
      ADDR_BEG (file);
      fprintf (file, "%se", RP);
      fputs (hi_reg_name[REGNO (addr)], file);
      ADDR_END (file);
      break;

    case PLUS:
      reg1 = 0;
      reg2 = 0;
      ireg = 0;
      breg = 0;
      offset = 0;
      if (CONSTANT_ADDRESS_P (XEXP (addr, 0)))
	{
	  offset = XEXP (addr, 0);
	  addr = XEXP (addr, 1);
	}
      else if (CONSTANT_ADDRESS_P (XEXP (addr, 1)))
	{
	  offset = XEXP (addr, 1);
	  addr = XEXP (addr, 0);
	}
      if (GET_CODE (addr) != PLUS) ;
      else if (GET_CODE (XEXP (addr, 0)) == MULT)
	{
	  reg1 = XEXP (addr, 0);
	  addr = XEXP (addr, 1);
	}
      else if (GET_CODE (XEXP (addr, 1)) == MULT)
	{
	  reg1 = XEXP (addr, 1);
	  addr = XEXP (addr, 0);
	}
      else if (GET_CODE (XEXP (addr, 0)) == REG)
	{
	  reg1 = XEXP (addr, 0);
	  addr = XEXP (addr, 1);
	}
      else if (GET_CODE (XEXP (addr, 1)) == REG)
	{
	  reg1 = XEXP (addr, 1);
	  addr = XEXP (addr, 0);
	}
      if (GET_CODE (addr) == REG || GET_CODE (addr) == MULT)
	{
	  if (reg1 == 0) reg1 = addr;
	  else reg2 = addr;
	  addr = 0;
	}
      if (offset != 0)
	{
	  if (addr != 0) abort ();
	  addr = offset;
	}
      if ((reg1 && GET_CODE (reg1) == MULT)
	  || (reg2 != 0 && REGNO_OK_FOR_BASE_P (REGNO (reg2))))
	{
	  breg = reg2;
	  ireg = reg1;
	}
      else if (reg1 != 0 && REGNO_OK_FOR_BASE_P (REGNO (reg1)))
	{
	  breg = reg1;
	  ireg = reg2;
	}

      if (ireg != 0 || breg != 0)
	{
	  int scale = 1;

	  if (addr != 0)
	    {
	      if (GET_CODE (addr) == LABEL_REF)
		output_asm_label (addr);
	      else
		output_addr_const (file, addr);
	    }

  	  if (ireg != 0 && GET_CODE (ireg) == MULT)
	    {
	      scale = INTVAL (XEXP (ireg, 1));
	      ireg = XEXP (ireg, 0);
	    }
	  /* output breg+ireg*scale */
	  PRINT_B_I_S (breg, ireg, scale, file);
	  break;
	}

    default:
      if (GET_CODE (addr) == CONST_INT
	  && INTVAL (addr) < 0x8000
	  && INTVAL (addr) >= -0x8000)
	fprintf (file, "%d", INTVAL (addr));
      else
	output_addr_const (file, addr);
    }
}

/* Set the cc_status for the results of an insn whose pattern is EXP.
   On the 80386, we assume that only test and compare insns
   set the condition codes usefully,
   and that all other insns except for jumps and moves
   clobber the condition codes unpredictably.

   In fact, it would be legitimate to detect certain arithmetic
   instructions and record what they do to the cc's,
   but it would be necessary to be careful:
   for example, some fixed-point add insns generate code that
   doesn't set them.  In addition, correct handling of arith insn
   cc's requires setting CC_NO_OVERFLOW; but i386.md's jump insns
   don't make correct output in this case.  */

notice_update_cc (exp)
     rtx exp;
{
  if (GET_CODE (exp) == SET)
    {
      /* Jumps do not alter the cc's.  */
      if (SET_DEST (exp) == pc_rtx)
	return;
      /* Moving register or memory into a register:
	 it doesn't alter the cc's, but it might invalidate
	 the RTX's which we remember the cc's came from.
	 (Note that moving a constant 0 or 1 MAY set the cc's).  */
      if (REG_P (SET_DEST (exp))
	  && (REG_P (SET_SRC (exp)) || GET_CODE (SET_SRC (exp)) == MEM))
	{
	  if (cc_status.value1
	      && reg_overlap_mentioned_p (SET_DEST (exp), cc_status.value1))
	    cc_status.value1 = 0;
	  if (cc_status.value2
	      && reg_overlap_mentioned_p (SET_DEST (exp), cc_status.value2))
	    cc_status.value2 = 0;
	}
      /* Moving register into memory doesn't alter the cc's.
	 It may invalidate the RTX's which we remember the cc's came from.  */
      if (GET_CODE (SET_DEST (exp)) == MEM && REG_P (SET_SRC (exp)))
	{
	  if (cc_status.value1 && GET_CODE (cc_status.value1) == MEM)
	    cc_status.value1 = 0;
	  if (cc_status.value2 && GET_CODE (cc_status.value2) == MEM)
	    cc_status.value2 = 0;
	  return;
	}
      /* Tests and compares set the cc's in predictable ways.  */
      else if (SET_DEST (exp) == cc0_rtx)
	{
	  CC_STATUS_INIT;
	  cc_status.value1 = SET_SRC (exp);
	  return;
	}
      else
	{
	  CC_STATUS_INIT;
	}
    }
  else if (GET_CODE (exp) == PARALLEL
	   && GET_CODE (XVECEXP (exp, 0, 0)) == SET)
    {
      if (SET_DEST (XVECEXP (exp, 0, 0)) == pc_rtx)
	return;
      if (SET_DEST (XVECEXP (exp, 0, 0)) == cc0_rtx)
	{
	  CC_STATUS_INIT;
	  cc_status.value1 = SET_SRC (XVECEXP (exp, 0, 0));
	  return;
	}
      CC_STATUS_INIT;
    }
  else
    {
      CC_STATUS_INIT;
    }
}

/* Nonzero if the top of the fpu stack dies in this insn.  */

int
top_dead_p (insn)
     rtx insn;
{
  extern int optimize;
  if (optimize)
    return (find_regno_note (insn, REG_DEAD, FIRST_FLOAT_REG)
	    || find_regno_note (insn, REG_DEAD, FIRST_FLOAT_REG + 1));

  if (GET_CODE (insn) == CALL_INSN)
    return call_top_dead_p (insn);

  return fp_top_dead_p1 (insn);
}

/* Following is used after a call_value insn
   if obey_regdecls there will not be the REG_DEAD notes
   to go by (there won't be any cross jumping to worry about
   either), and we depend on seeing if the FP_TOP is used
   in the next two insn's.  Otherwise we depend on the
   REG_DEAD notes.
   */

static int
call_top_dead_p (insn)
     rtx insn;
{
  int i;
  for (i = 0; i < 3; i++)
    {
      insn = NEXT_INSN (insn);
      if (insn == 0)
	return 1;
      if (GET_CODE (PATTERN (insn)) == SET
	  && SET_DEST (PATTERN (insn)) != stack_pointer_rtx)
	return (!(mentions_fp_top (SET_SRC (PATTERN (insn)))));
      if (GET_CODE (PATTERN (insn)) == CALL)
	return 1;
      if (GET_CODE (PATTERN (insn)) == USE)
	return (! FP_REG_P (XEXP (PATTERN (insn), 0)));
    }
  return 1;
}

/* Return 1 if current val of fpu top-of-stack appears unused
   in rest of this basic block.  */

static int
fp_top_dead_p1 (insn)
     rtx insn;
{
  for (insn = NEXT_INSN (insn); insn; insn = NEXT_INSN (insn))
    {
      switch (GET_CODE (insn))
	{
	case CALL_INSN:
	  /* Function calls clobber this value, so it's dead.  */
	case JUMP_INSN:
	case CODE_LABEL:
	  return 1;

	case INSN:
	  if (GET_CODE (PATTERN (insn)) == SET)
	    {
	      if ((mentions_fp_top (SET_SRC (PATTERN (insn)))))
		return 0;
	      else if (FP_REG_P (SET_DEST (PATTERN (insn))))
		return 1;
	    }
	  else if (mentions_fp_top (PATTERN (insn)))
	    return 0;
	  break;
	}
    }
  return 1;
}

/* Return 1 if X involves an FPU register.  */

static int
mentions_fp_top (x)
     rtx x;
{
  register RTX_CODE code;

  code = GET_CODE (x);
  switch (code)
    {
    case LABEL_REF:
    case SYMBOL_REF:
    case CONST_INT:
    case CONST:
    case CC0:
    case PC:
    case CLOBBER:
    case MEM:
      return 0;

    case REG:
      return FP_REGNO_P (REGNO (x));
    }

  /* Recursively scan the operands of this expression.  */
  {
    register char *fmt = GET_RTX_FORMAT (code);
    register int i;

    for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--)
      {
	if (fmt[i] == 'e')
	  {
	    if (mentions_fp_top (XEXP (x, i)))
	      return 1;
	  }
	if (fmt[i] == 'E')
	  {
	    register int j;
	    for (j = 0; j < XVECLEN (x, i); j++)
	      if (mentions_fp_top (XVECEXP (x, i, j)))
		return 1;
	  }
      }
  }
  return 0;
}

/* Some asm-dependent functions. */

#ifdef MASM
#include "masm386.c"
#endif
