ns32k.c   [plain text]


/* Subroutines for assembler code output on the NS32000.
   Copyright (C) 1988, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001
   Free Software Foundation, Inc.

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, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

#include "config.h"
#include "system.h"
#include "rtl.h"
#include "regs.h"
#include "hard-reg-set.h"
#include "real.h"
#include "insn-config.h"
#include "conditions.h"
#include "output.h"
#include "insn-attr.h"
#include "tree.h"
#include "function.h"
#include "expr.h"
#include "flags.h"
#include "recog.h"
#include "tm_p.h"
#include "target.h"
#include "target-def.h"
#include "toplev.h"

#ifdef OSF_OS
int ns32k_num_files = 0;
#endif

/* This duplicates reg_class_contents in reg_class.c, but maybe that isn't
   initialized in time. Also this is more convenient as an array of ints.
   We know that HARD_REG_SET fits in an unsigned int */

const unsigned int ns32k_reg_class_contents[N_REG_CLASSES][1] = REG_CLASS_CONTENTS;

const enum reg_class regclass_map[FIRST_PSEUDO_REGISTER] =
{
  GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS,
  GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS,
  FLOAT_REG0, LONG_FLOAT_REG0, FLOAT_REGS, FLOAT_REGS,
  FLOAT_REGS, FLOAT_REGS, FLOAT_REGS, FLOAT_REGS,
  FP_REGS, FP_REGS, FP_REGS, FP_REGS,
  FP_REGS, FP_REGS, FP_REGS, FP_REGS,
  FRAME_POINTER_REG, STACK_POINTER_REG
};

static const char *const ns32k_out_reg_names[] = OUTPUT_REGISTER_NAMES;

static rtx gen_indexed_expr PARAMS ((rtx, rtx, rtx));
static const char *singlemove_string PARAMS ((rtx *));
static void move_tail PARAMS ((rtx[], int, int));
static tree ns32k_handle_fntype_attribute PARAMS ((tree *, tree, tree, int, bool *));
const struct attribute_spec ns32k_attribute_table[];
static void ns32k_output_function_prologue PARAMS ((FILE *, HOST_WIDE_INT));
static void ns32k_output_function_epilogue PARAMS ((FILE *, HOST_WIDE_INT));
static void ns32k_encode_section_info PARAMS ((tree, int));

/* Initialize the GCC target structure.  */
#undef TARGET_ATTRIBUTE_TABLE
#define TARGET_ATTRIBUTE_TABLE ns32k_attribute_table

#undef TARGET_ASM_ALIGNED_HI_OP
#define TARGET_ASM_ALIGNED_HI_OP "\t.word\t"

#ifdef ENCORE_ASM
#undef TARGET_ASM_ALIGNED_SI_OP
#define TARGET_ASM_ALIGNED_SI_OP "\t.double\t"
#endif

#undef TARGET_ASM_FUNCTION_PROLOGUE
#define TARGET_ASM_FUNCTION_PROLOGUE ns32k_output_function_prologue
#undef TARGET_ASM_FUNCTION_EPILOGUE
#define TARGET_ASM_FUNCTION_EPILOGUE ns32k_output_function_epilogue
#undef TARGET_ENCODE_SECTION_INFO
#define TARGET_ENCODE_SECTION_INFO ns32k_encode_section_info

struct gcc_target targetm = TARGET_INITIALIZER;

/* Generate the assembly code for function entry.  FILE is a stdio
   stream to output the code to.  SIZE is an int: how many units of
   temporary storage to allocate.

   Refer to the array `regs_ever_live' to determine which registers to
   save; `regs_ever_live[I]' is nonzero if register number I is ever
   used in the function.  This function is responsible for knowing
   which registers should not be saved even if used.  */

/*
 * The function prologue for the ns32k is fairly simple.
 * If a frame pointer is needed (decided in reload.c ?) then
 * we need assembler of the form
 *
 *  # Save the oldframe pointer, set the new frame pointer, make space
 *  # on the stack and save any general purpose registers necessary
 *
 *  enter [<general purpose regs to save>], <local stack space>
 *
 *  movf  fn, tos    # Save any floating point registers necessary
 *  .
 *  .
 *
 * If a frame pointer is not needed we need assembler of the form
 *
 *  # Make space on the stack
 *
 *  adjspd <local stack space + 4>
 *
 *  # Save any general purpose registers necessary
 *
 *  save [<general purpose regs to save>]
 *
 *  movf  fn, tos    # Save any floating point registers necessary
 *  .
 *  .
 */

#if !defined (MERLIN_TARGET) && !defined (UTEK_ASM)

#if defined(IMMEDIATE_PREFIX) && IMMEDIATE_PREFIX
#define ADJSP(FILE, N) \
        fprintf (FILE, "\tadjspd %c%d\n", IMMEDIATE_PREFIX, (N))
#else
#define ADJSP(FILE, N) \
        fprintf (FILE, "\tadjspd %d\n", (N))
#endif

static void
ns32k_output_function_prologue (file, size)
     FILE *file;
     HOST_WIDE_INT size;
{
  register int regno, g_regs_used = 0;
  int used_regs_buf[8], *bufp = used_regs_buf;
  int used_fregs_buf[17], *fbufp = used_fregs_buf;

  for (regno = R0_REGNUM; regno < F0_REGNUM; regno++)
    if (regs_ever_live[regno]
	&& ! call_used_regs[regno])
      {
        *bufp++ = regno; g_regs_used++;
      }
  *bufp = -1;

  for (; regno < FRAME_POINTER_REGNUM; regno++)
    if (regs_ever_live[regno] && !call_used_regs[regno])
      {
        *fbufp++ = regno;
      }
  *fbufp = -1;

  bufp = used_regs_buf;
  if (frame_pointer_needed)
    fprintf (file, "\tenter [");
  else
    {
      if (size)
        ADJSP (file, size + 4);
      if (g_regs_used && g_regs_used > 4)
        fprintf (file, "\tsave [");
      else
	{
	  while (*bufp >= 0)
            fprintf (file, "\tmovd r%d,tos\n", *bufp++);
	  g_regs_used = 0;
	}
    }

  while (*bufp >= 0)
    {
      fprintf (file, "r%d", *bufp++);
      if (*bufp >= 0)
	fputc (',', file);
    }

  if (frame_pointer_needed)
    fprintf (file, "],%d\n", size);
  else if (g_regs_used)
    fprintf (file, "]\n");

  fbufp = used_fregs_buf;
  while (*fbufp >= 0)
    {
      if ((*fbufp & 1) || (fbufp[0] != fbufp[1] - 1))
	fprintf (file, "\tmovf %s,tos\n", ns32k_out_reg_names[*fbufp++]);
      else
	{
	  fprintf (file, "\tmovl %s,tos\n",
		   ns32k_out_reg_names[fbufp[0]]);
	  fbufp += 2;
	}
    }

  if (flag_pic && current_function_uses_pic_offset_table)
    {
      fprintf (file, "\tsprd sb,tos\n");
      if (TARGET_REGPARM)
	{
	  fprintf (file, "\taddr __GLOBAL_OFFSET_TABLE_(pc),tos\n");
	  fprintf (file, "\tlprd sb,tos\n");
	}
      else
	{
	  fprintf (file, "\taddr __GLOBAL_OFFSET_TABLE_(pc),r0\n");
	  fprintf (file, "\tlprd sb,r0\n");
	}
    }
}

#else /* MERLIN_TARGET || UTEK_ASM  */

/* This differs from the standard one above in printing a bitmask
   rather than a register list in the enter or save instruction.  */

static void
ns32k_output_function_prologue (file, size)
     FILE *file;
     HOST_WIDE_INT size;
{
  register int regno, g_regs_used = 0;
  int used_regs_buf[8], *bufp = used_regs_buf;
  int used_fregs_buf[8], *fbufp = used_fregs_buf;

  for (regno = 0; regno < 8; regno++)
    if (regs_ever_live[regno]
	&& ! call_used_regs[regno])
      {
	*bufp++ = regno; g_regs_used++;
      }
  *bufp = -1;

  for (; regno < 16; regno++)
    if (regs_ever_live[regno] && !call_used_regs[regno]) {
      *fbufp++ = regno;
    }
  *fbufp = -1;

  bufp = used_regs_buf;
  if (frame_pointer_needed)
    fprintf (file, "\tenter ");
  else if (g_regs_used)
    fprintf (file, "\tsave ");

  if (frame_pointer_needed || g_regs_used)
    {
      char mask = 0;
      while (*bufp >= 0)
	mask |= 1 << *bufp++;
      fprintf (file, "$0x%x", (int) mask & 0xff);
    }

  if (frame_pointer_needed)
#ifdef UTEK_ASM
    fprintf (file, ",$%d\n", size);
#else
    fprintf (file, ",%d\n", size);
#endif
  else if (g_regs_used)
    fprintf (file, "\n");

  fbufp = used_fregs_buf;
  while (*fbufp >= 0)
    {
      if ((*fbufp & 1) || (fbufp[0] != fbufp[1] - 1))
	fprintf (file, "\tmovf f%d,tos\n", *fbufp++ - 8);
      else
	{
	  fprintf (file, "\tmovl f%d,tos\n", fbufp[0] - 8);
	  fbufp += 2;
	}
    }
}

#endif /* MERLIN_TARGET || UTEK_ASM  */

/* This function generates the assembly code for function exit,
   on machines that need it.

   The function epilogue should not depend on the current stack pointer,
   if EXIT_IGNORE_STACK is nonzero.  That doesn't apply here.

   If a frame pointer is needed (decided in reload.c ?) then
   we need assembler of the form

    movf  tos, fn	# Restore any saved floating point registers
    .
    .

    # Restore any saved general purpose registers, restore the stack
    # pointer from the frame pointer, restore the old frame pointer.
    exit [<general purpose regs to save>]

   If a frame pointer is not needed we need assembler of the form
    # Restore any general purpose registers saved

    movf  tos, fn	# Restore any saved floating point registers
    .
    .
    .
    restore [<general purpose regs to save>]

    # reclaim space allocated on stack

    adjspd <-(local stack space + 4)> */

#if !defined (MERLIN_TARGET) && !defined (UTEK_ASM)

static void
ns32k_output_function_epilogue (file, size)
     FILE *file;
     HOST_WIDE_INT size;
{
  register int regno, g_regs_used = 0, f_regs_used = 0;
  int used_regs_buf[8], *bufp = used_regs_buf;
  int used_fregs_buf[17], *fbufp = used_fregs_buf;

  if (flag_pic && current_function_uses_pic_offset_table)
    fprintf (file, "\tlprd sb,tos\n");

  *fbufp++ = -2;
  for (regno = F0_REGNUM; regno < FRAME_POINTER_REGNUM; regno++)
    if (regs_ever_live[regno] && !call_used_regs[regno])
      {
	*fbufp++ = regno; f_regs_used++;
      }
  fbufp--;

  for (regno = 0; regno < F0_REGNUM; regno++)
    if (regs_ever_live[regno]
	&& ! call_used_regs[regno])
      {
        *bufp++ = regno; g_regs_used++;
      }

  while (fbufp > used_fregs_buf)
    {
      if ((*fbufp & 1) && fbufp[0] == fbufp[-1] + 1)
	{
	  fprintf (file, "\tmovl tos,%s\n",
		   ns32k_out_reg_names[fbufp[-1]]);
	  fbufp -= 2;
	}
      else fprintf (file, "\tmovf tos,%s\n", ns32k_out_reg_names[*fbufp--]);
    }

  if (frame_pointer_needed)
    fprintf (file, "\texit [");
  else
    {
      if (g_regs_used && g_regs_used > 4)
        fprintf (file, "\trestore [");
      else
        {
	  while (bufp > used_regs_buf)
            fprintf (file, "\tmovd tos,r%d\n", *--bufp);
	  g_regs_used = 0;
        }
    }

  while (bufp > used_regs_buf)
    {
      fprintf (file, "r%d", *--bufp);
      if (bufp > used_regs_buf)
	fputc (',', file);
    }

  if (g_regs_used || frame_pointer_needed)
    fprintf (file, "]\n");

  if (size && !frame_pointer_needed)
    ADJSP (file, -(size + 4));

  if (current_function_pops_args)
    fprintf (file, "\tret %d\n", current_function_pops_args);
  else
    fprintf (file, "\tret 0\n");
}

#else /* MERLIN_TARGET || UTEK_ASM  */

/* This differs from the standard one above in printing a bitmask
   rather than a register list in the exit or restore instruction.  */

static void
ns32k_output_function_epilogue (file, size)
     FILE *file;
     HOST_WIDE_INT size ATTRIBUTE_UNUSED;
{
  register int regno, g_regs_used = 0, f_regs_used = 0;
  int used_regs_buf[8], *bufp = used_regs_buf;
  int used_fregs_buf[8], *fbufp = used_fregs_buf;

  *fbufp++ = -2;
  for (regno = 8; regno < 16; regno++)
    if (regs_ever_live[regno] && !call_used_regs[regno]) {
      *fbufp++ = regno; f_regs_used++;
    }
  fbufp--;

  for (regno = 0; regno < 8; regno++)
    if (regs_ever_live[regno]
	&& ! call_used_regs[regno])
      {
	*bufp++ = regno; g_regs_used++;
      }

  while (fbufp > used_fregs_buf)
    {
      if ((*fbufp & 1) && fbufp[0] == fbufp[-1] + 1)
	{
	  fprintf (file, "\tmovl tos,f%d\n", fbufp[-1] - 8);
	  fbufp -= 2;
	}
      else fprintf (file, "\tmovf tos,f%d\n", *fbufp-- - 8);
    }

  if (frame_pointer_needed)
    fprintf (file, "\texit ");
  else if (g_regs_used)
    fprintf (file, "\trestore ");

  if (g_regs_used || frame_pointer_needed)
    {
      char mask = 0;

      while (bufp > used_regs_buf)
	{
	  /* Utek assembler takes care of reversing this */
	  mask |= 1 << *--bufp;
	}
      fprintf (file, "$0x%x\n", (int) mask & 0xff);
    }

#ifdef UTEK_ASM
  if (current_function_pops_args)
    fprintf (file, "\tret $%d\n", current_function_pops_args);
  else
    fprintf (file, "\tret $0\n");
#else
  if (current_function_pops_args)
    fprintf (file, "\tret %d\n", current_function_pops_args);
  else
    fprintf (file, "\tret 0\n");
#endif
}

#endif /* MERLIN_TARGET || UTEK_ASM  */

/* Value is 1 if hard register REGNO can hold a value of machine-mode MODE. */ 
int
hard_regno_mode_ok (regno, mode)
     int regno;
     enum machine_mode mode;
{
  int size = GET_MODE_UNIT_SIZE (mode);

  if (FLOAT_MODE_P (mode))
    {
      if (size == UNITS_PER_WORD && regno < L1_REGNUM)
	return 1;
      if (size == UNITS_PER_WORD * 2
	  && (((regno & 1) == 0 && regno < FRAME_POINTER_REGNUM)))
	return 1;
      return 0;
    }
  if (size == UNITS_PER_WORD * 2
      && (regno & 1) == 0 && regno < F0_REGNUM)
    return 1;
  if (size <= UNITS_PER_WORD
      && (regno < F0_REGNUM || regno == FRAME_POINTER_REGNUM
	  || regno == STACK_POINTER_REGNUM))
    return 1;
  return 0;
}

int register_move_cost (CLASS1, CLASS2)
     enum reg_class CLASS1;
     enum reg_class CLASS2;
{
  if (CLASS1 == NO_REGS || CLASS2 == NO_REGS)
    return 2;
  if ((SUBSET_P (CLASS1, FP_REGS) && !SUBSET_P (CLASS2, FP_REGS))
   || (!SUBSET_P (CLASS1, FP_REGS) && SUBSET_P (CLASS2, FP_REGS)))
    return 8;
  if (((CLASS1) == STACK_POINTER_REG && !SUBSET_P (CLASS2,GENERAL_REGS))
      || ((CLASS2) == STACK_POINTER_REG && !SUBSET_P (CLASS1,GENERAL_REGS)))
    return 6;
  if (((CLASS1) == FRAME_POINTER_REG && !SUBSET_P (CLASS2,GENERAL_REGS))
      || ((CLASS2) == FRAME_POINTER_REG && !SUBSET_P (CLASS1,GENERAL_REGS)))
    return 6;
  return 2;
}

#if 0
/* We made the insn definitions copy from floating point to general
  registers via the stack. */
int secondary_memory_needed (CLASS1, CLASS2, M)
     enum reg_class CLASS1;
     enum reg_class CLASS2;
     enum machine_mode M;
{
  int ret = ((SUBSET_P (CLASS1, FP_REGS) && !SUBSET_P (CLASS2, FP_REGS))
   || (!SUBSET_P (CLASS1, FP_REGS) && SUBSET_P (CLASS2, FP_REGS)));
  return ret;
}
#endif
    

/* ADDRESS_COST calls this.  This function is not optimal
   for the 32032 & 32332, but it probably is better than
   the default. */

int
calc_address_cost (operand)
     rtx operand;
{
  int i;
  int cost = 0;
  if (GET_CODE (operand) == MEM)
    cost += 3;
  if (GET_CODE (operand) == MULT)
    cost += 2;
  switch (GET_CODE (operand))
    {
    case REG:
      cost += 1;
      break;
    case POST_DEC:
    case PRE_DEC:
      break;
    case CONST_INT:
      if (INTVAL (operand) <= 7 && INTVAL (operand) >= -8)
	break;
      if (INTVAL (operand) < 0x2000 && INTVAL (operand) >= -0x2000)
	{
	  cost +=1;
	  break;
	}
    case CONST:
    case LABEL_REF:
    case SYMBOL_REF:
      cost +=3;
      break;
    case CONST_DOUBLE:
      cost += 5;
      break;
    case MEM:
      cost += calc_address_cost (XEXP (operand, 0));
      break;
    case MULT:
    case PLUS:
      for (i = 0; i < GET_RTX_LENGTH (GET_CODE (operand)); i++)
	{
	  cost += calc_address_cost (XEXP (operand, i));
	}
    default:
      break;
    }
  return cost;
}

/* Return the register class of a scratch register needed to copy IN into
   or out of a register in CLASS in MODE.  If it can be done directly,
   NO_REGS is returned.  */

enum reg_class
secondary_reload_class (class, mode, in)
     enum reg_class class;
     enum machine_mode mode ATTRIBUTE_UNUSED;
     rtx in;
{
  int regno = true_regnum (in);

  if (regno >= FIRST_PSEUDO_REGISTER)
    regno = -1;

  if ((class == FRAME_POINTER_REG && regno == STACK_POINTER_REGNUM)
      || ( class == STACK_POINTER_REG && regno == FRAME_POINTER_REGNUM))
    return GENERAL_REGS;
  else
    return NO_REGS;
}

/* Generate the rtx that comes from an address expression in the md file */
/* The expression to be build is BASE[INDEX:SCALE].  To recognize this,
   scale must be converted from an exponent (from ASHIFT) to a
   multiplier (for MULT). */

static rtx
gen_indexed_expr (base, index, scale)
     rtx base, index, scale;
{
  rtx addr;

  /* This generates an invalid addressing mode, if BASE is
     fp or sp.  This is handled by PRINT_OPERAND_ADDRESS.  */
  if (GET_CODE (base) != REG && GET_CODE (base) != CONST_INT)
    base = gen_rtx_MEM (SImode, base);
  addr = gen_rtx_MULT (SImode, index,
		       GEN_INT (1 << INTVAL (scale)));
  addr = gen_rtx_PLUS (SImode, base, addr);
  return addr;
}


/* Split one or more DImode RTL references into pairs of SImode
   references.  The RTL can be REG, offsettable MEM, integer constant, or
   CONST_DOUBLE.  "operands" is a pointer to an array of DImode RTL to
   split and "num" is its length.  lo_half and hi_half are output arrays
   that parallel "operands". */

void
split_di (operands, num, lo_half, hi_half)
     rtx operands[];
     int num;
     rtx lo_half[], hi_half[];
{
  while (num--)
    {
      if (GET_CODE (operands[num]) == REG)
	{
	  lo_half[num] = gen_rtx_REG (SImode, REGNO (operands[num]));
	  hi_half[num] = gen_rtx_REG (SImode, REGNO (operands[num]) + 1);
	}
      else if (CONSTANT_P (operands[num]))
	{
	  split_double (operands[num], &lo_half[num], &hi_half[num]);
	}
      else if (offsettable_memref_p (operands[num]))
	{
	  lo_half[num] = operands[num];
	  hi_half[num] = adjust_address (operands[num], SImode, 4);
	}
      else
	abort ();
    }
}

/* Return the best assembler insn template
   for moving operands[1] into operands[0] as a fullword.  */

static const char *
singlemove_string (operands)
     rtx *operands;
{
  if (GET_CODE (operands[1]) == CONST_INT
      && INTVAL (operands[1]) <= 7
      && INTVAL (operands[1]) >= -8)
    return "movqd %1,%0";
  return "movd %1,%0";
}

const char *
output_move_double (operands)
     rtx *operands;
{
  enum anon1 { REGOP, OFFSOP, PUSHOP, CNSTOP, RNDOP } optype0, optype1;
  rtx latehalf[2];

  /* First classify both operands.  */

  if (REG_P (operands[0]))
    optype0 = REGOP;
  else if (offsettable_memref_p (operands[0]))
    optype0 = OFFSOP;
  else if (GET_CODE (XEXP (operands[0], 0)) == PRE_DEC)
    optype0 = PUSHOP;
  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 (offsettable_memref_p (operands[1]))
    optype1 = OFFSOP;
  else if (GET_CODE (XEXP (operands[1], 0)) == PRE_DEC)
    optype1 = PUSHOP;
  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 ();

  /* 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] = adjust_address (operands[0], SImode, 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] = adjust_address (operands[1], SImode, 4);
  else if (optype1 == CNSTOP)
    split_double (operands[1], &operands[1], &latehalf[1]);
  else
    latehalf[1] = operands[1];

  /* If insn is effectively movd N(sp),tos 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.
     Given this, it doesn't matter which half we do "first".  */
  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.  */
  else if (optype0 == PUSHOP || optype1 == PUSHOP)
    {
      output_asm_insn (singlemove_string (latehalf), latehalf);
      return singlemove_string (operands);
    }

  /* If the first move would clobber the source of the second one,
     do them in the other order.  */

  /* Overlapping registers.  */
  if (optype0 == REGOP && optype1 == REGOP
      && REGNO (operands[0]) == REGNO (latehalf[1]))
    {
      /* Do that word.  */
      output_asm_insn (singlemove_string (latehalf), latehalf);
      /* Do low-numbered word.  */
      return singlemove_string (operands);
    }
  /* Loading into a register which overlaps a register used in the address.  */
  else if (optype0 == REGOP && optype1 != REGOP
	   && reg_overlap_mentioned_p (operands[0], operands[1]))
    {
      if (reg_mentioned_p (operands[0], XEXP (operands[1], 0))
	  && reg_mentioned_p (latehalf[0], XEXP (operands[1], 0)))
	{
	  /* If both halves of dest are used in the src memory address,
	     load the destination address into the low reg (operands[0]).
	     Then it works to load latehalf first.  */
	  rtx xops[2];
	  xops[0] = XEXP (operands[1], 0);
	  xops[1] = operands[0];
	  output_asm_insn ("addr %a0,%1", xops);
	  operands[1] = gen_rtx_MEM (DImode, operands[0]);
	  latehalf[1] = adjust_address (operands[1], SImode, 4);
	  /* The first half has the overlap, Do the late half first.  */
	  output_asm_insn (singlemove_string (latehalf), latehalf);
	  /* Then clobber.  */
	  return singlemove_string (operands);
	}
      if (reg_mentioned_p (operands[0], XEXP (operands[1], 0)))
	{
	  /* The first half has the overlap, Do the late half first.  */
	  output_asm_insn (singlemove_string (latehalf), latehalf);
	  /* Then clobber.  */
	  return singlemove_string (operands);
	}
    }

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

  output_asm_insn (singlemove_string (operands), operands);

  operands[0] = latehalf[0];
  operands[1] = latehalf[1];
  return singlemove_string (operands);
}


#define MAX_UNALIGNED_COPY (32)
/* Expand string/block move operations.

   operands[0] is the pointer to the destination.
   operands[1] is the pointer to the source.
   operands[2] is the number of bytes to move.
   operands[3] is the alignment.  */

static void
move_tail (operands, bytes, offset)
     rtx operands[];
     int bytes;
     int offset;
{
  if (bytes & 2)
    {
      emit_move_insn (adjust_address (operands[0], HImode, offset),
		      adjust_address (operands[1], HImode, offset));
      offset += 2;
    }
  if (bytes & 1)
    emit_move_insn (adjust_address (operands[0], QImode, offset),
		    adjust_address (operands[1], QImode, offset));
}

void
expand_block_move (operands)
     rtx operands[];
{
  rtx bytes_rtx	= operands[2];
  rtx align_rtx = operands[3];
  int constp	= (GET_CODE (bytes_rtx) == CONST_INT);
  int bytes	= (constp ? INTVAL (bytes_rtx) : 0);
  int align	= INTVAL (align_rtx);
  rtx src_reg = gen_rtx_REG (Pmode, 1);
  rtx dest_reg = gen_rtx_REG (Pmode, 2);
  rtx count_reg = gen_rtx_REG (SImode, 0);

  if (constp && bytes <= 0)
    return;

  if (constp && bytes < 20)
    {
      int words = bytes >> 2;

      if (words)
	{
	  if (words < 3 || flag_unroll_loops)
	    {
	      int offset = 0;

	      for (; words; words--, offset += 4)
		emit_move_insn (adjust_address (operands[0], SImode, offset),
				adjust_address (operands[1], SImode, offset));
	    }
	  else
	    {
	      /* Use movmd. It is slower than multiple movd's but more
		 compact. It is also slower than movsd for large copies
		 but causes less registers reloading so is better than movsd
		 for small copies. */
	      rtx src, dest;
	      dest = copy_addr_to_reg (XEXP (operands[0], 0));
	      src = copy_addr_to_reg (XEXP (operands[1], 0));
	    
	      emit_insn (gen_movstrsi2(dest, src, GEN_INT (words)));
	    }
	}
      move_tail (operands, bytes & 3, bytes & ~3);
      return;
    }

  if (align > UNITS_PER_WORD)
    align = UNITS_PER_WORD;

  /* Move the address into scratch registers.  */
  emit_insn (gen_rtx_CLOBBER (VOIDmode, dest_reg));
  emit_move_insn (dest_reg, XEXP (operands[0], 0));
  operands[0] = gen_rtx_MEM (SImode, dest_reg);
  emit_insn (gen_rtx_CLOBBER (VOIDmode, src_reg));
  emit_move_insn (src_reg, XEXP (operands[1], 0));
  operands[1] = gen_rtx_MEM (SImode, src_reg);
  emit_insn (gen_rtx_CLOBBER (VOIDmode, count_reg));

  if (constp && (align == UNITS_PER_WORD || bytes < MAX_UNALIGNED_COPY))
    {
      /* constant no of bytes and aligned or small enough copy to not bother
       * aligning. Emit insns to copy by words.
       */
      if (bytes >> 2)
	{
	  emit_move_insn (count_reg, GEN_INT (bytes >> 2));
	  emit_insn (gen_movstrsi1 (GEN_INT (4)));
	}
      /* insns to copy rest */
      move_tail (operands, bytes & 3, 0);
    }
  else if (align == UNITS_PER_WORD)
    {
      /* insns to copy by words */
      emit_insn (gen_lshrsi3 (count_reg, bytes_rtx, GEN_INT (2)));
      emit_insn (gen_movstrsi1 (GEN_INT (4)));
      if (constp)
	{
	  move_tail (operands, bytes & 3, 0);
	}
      else
	{
	  /* insns to copy rest */
	  emit_insn (gen_andsi3 (count_reg, bytes_rtx, GEN_INT (3)));
	  emit_insn (gen_movstrsi1 (const1_rtx));
	}
    }
  else
    {
      /* Not aligned and we may have a lot to copy so it is worth
       * aligning.
       */
      rtx aligned_label = gen_label_rtx ();
      rtx bytes_reg;

      bytes_reg = copy_to_mode_reg (SImode, bytes_rtx);
      if (!constp)
	{
	  /* Emit insns to test and skip over the alignment if it is
	   * not worth it. This doubles as a test to ensure that the alignment
	   * operation can't copy too many bytes
	   */
	  emit_insn (gen_cmpsi (bytes_reg, GEN_INT (MAX_UNALIGNED_COPY)));
	  emit_jump_insn (gen_blt (aligned_label));
	}

      /* Emit insns to do alignment at run time */
      emit_insn (gen_negsi2 (count_reg, src_reg));
      emit_insn (gen_andsi3 (count_reg, count_reg, GEN_INT (3)));
      emit_insn (gen_subsi3 (bytes_reg, bytes_reg, count_reg));
      emit_insn (gen_movstrsi1 (const1_rtx));
      if (!constp)
	emit_label (aligned_label);

      /* insns to copy by words */
      emit_insn (gen_lshrsi3 (count_reg, bytes_reg, GEN_INT (2)));
      emit_insn (gen_movstrsi1 (GEN_INT (4)));

      /* insns to copy rest */
      emit_insn (gen_andsi3 (count_reg, bytes_reg, GEN_INT (3)));
      emit_insn (gen_movstrsi1 (const1_rtx));
    }
}


/* Returns 1 if OP contains a global symbol reference */

int
global_symbolic_reference_mentioned_p (op, f)
     rtx op;
     int f;
{
  register const char *fmt;
  register int i;

  if (GET_CODE (op) == SYMBOL_REF)
    {
      if (! SYMBOL_REF_FLAG (op))
	return 1;
      else
        return 0;
    }
  else if (f && GET_CODE (op) != CONST)
    return 0;

  fmt = GET_RTX_FORMAT (GET_CODE (op));
  for (i = GET_RTX_LENGTH (GET_CODE (op)) - 1; i >= 0; i--)
    {
      if (fmt[i] == 'E')
	{
	  register int j;

	  for (j = XVECLEN (op, i) - 1; j >= 0; j--)
	    if (global_symbolic_reference_mentioned_p (XVECEXP (op, i, j), 0))
	      return 1;
	}
      else if (fmt[i] == 'e' 
	       && global_symbolic_reference_mentioned_p (XEXP (op, i), 0))
	return 1;
    }

  return 0;
}


/* Returns 1 if OP contains a symbol reference */

int
symbolic_reference_mentioned_p (op)
     rtx op;
{
  register const char *fmt;
  register int i;

  if (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == LABEL_REF)
    return 1;

  fmt = GET_RTX_FORMAT (GET_CODE (op));
  for (i = GET_RTX_LENGTH (GET_CODE (op)) - 1; i >= 0; i--)
    {
      if (fmt[i] == 'E')
	{
	  register int j;

	  for (j = XVECLEN (op, i) - 1; j >= 0; j--)
	    if (symbolic_reference_mentioned_p (XVECEXP (op, i, j)))
	      return 1;
	}
      else if (fmt[i] == 'e' && symbolic_reference_mentioned_p (XEXP (op, i)))
	return 1;
    }

  return 0;
}

/* Table of machine-specific attributes.  */

const struct attribute_spec ns32k_attribute_table[] =
{
  /* { name, min_len, max_len, decl_req, type_req, fn_type_req, handler } */
  /* Stdcall attribute says callee is responsible for popping arguments
     if they are not variable.  */
  { "stdcall", 0, 0, false, true,  true,  ns32k_handle_fntype_attribute },
  /* Cdecl attribute says the callee is a normal C declaration */
  { "cdecl",   0, 0, false, true,  true,  ns32k_handle_fntype_attribute },
  { NULL,      0, 0, false, false, false, NULL }
};

/* Handle an attribute requiring a FUNCTION_TYPE, FIELD_DECL or TYPE_DECL;
   arguments as in struct attribute_spec.handler.  */
static tree
ns32k_handle_fntype_attribute (node, name, args, flags, no_add_attrs)
     tree *node;
     tree name;
     tree args ATTRIBUTE_UNUSED;
     int flags ATTRIBUTE_UNUSED;
     bool *no_add_attrs;
{
  if (TREE_CODE (*node) != FUNCTION_TYPE
      && TREE_CODE (*node) != FIELD_DECL
      && TREE_CODE (*node) != TYPE_DECL)
    {
      warning ("`%s' attribute only applies to functions",
	       IDENTIFIER_POINTER (name));
      *no_add_attrs = true;
    }

  return NULL_TREE;
}


/* Value is the number of bytes of arguments automatically
   popped when returning from a subroutine call.
   FUNDECL is the declaration node of the function (as a tree),
   FUNTYPE is the data type of the function (as a tree),
   or for a library call it is an identifier node for the subroutine name.
   SIZE is the number of bytes of arguments passed on the stack.

   On the ns32k, the RET insn may be used to pop them if the number
     of args is fixed, but if the number is variable then the caller
     must pop them all.  RET can't be used for library calls now
     because the library is compiled with the Unix compiler.
   Use of RET is a selectable option, since it is incompatible with
   standard Unix calling sequences.  If the option is not selected,
   the caller must always pop the args.

   The attribute stdcall is equivalent to RET on a per module basis.  */

int
ns32k_return_pops_args (fundecl, funtype, size)
     tree fundecl ATTRIBUTE_UNUSED;
     tree funtype;
     int size;
{
  int rtd = TARGET_RTD;

  if (TREE_CODE (funtype) == IDENTIFIER_NODE)
    return rtd ? size : 0;

  /* Cdecl functions override -mrtd, and never pop the stack */
  if (lookup_attribute ("cdecl", TYPE_ATTRIBUTES (funtype)))
    return 0;

  /* Stdcall functions will pop the stack if not variable args */
  if (lookup_attribute ("stdcall", TYPE_ATTRIBUTES (funtype)))
    rtd = 1;

  if (rtd)
    {
      if (TYPE_ARG_TYPES (funtype) == NULL_TREE
	  || (TREE_VALUE (tree_last (TYPE_ARG_TYPES (funtype))) == void_type_node))
	return size;
    }

  return 0;
}

/* PRINT_OPERAND is defined to call this function,
   which is easier to debug than putting all the code in
   a macro definition in ns32k.h.  */

/* XXX time 12% of cpu time is in fprintf for non optimizing */
void
print_operand (file, x, code)
     FILE *file;
     rtx x;
     int code;
{
  if (code == '$')
    PUT_IMMEDIATE_PREFIX (file);
  else if (code == '?')
    PUT_EXTERNAL_PREFIX (file);
  else if (GET_CODE (x) == REG)
    fprintf (file, "%s", ns32k_out_reg_names[REGNO (x)]);
  else if (GET_CODE (x) == MEM)
    {
      output_address (XEXP (x, 0));
    }
  else if (GET_CODE (x) == CONST_DOUBLE && GET_MODE (x) != VOIDmode)
    {
      REAL_VALUE_TYPE r;

      REAL_VALUE_FROM_CONST_DOUBLE (r, x);
      PUT_IMMEDIATE_PREFIX (file);
      if (GET_MODE (x) == DFmode)
	{ 
#ifdef SEQUENT_ASM
	  /* Sequent likes its floating point constants as integers */
	  long l[2];
	  REAL_VALUE_TO_TARGET_DOUBLE (r, l);
	  fprintf (file, "0Dx%08x%08x",
		   l[!WORDS_BIG_ENDIAN], l[WORDS_BIG_ENDIAN]);
#else
	  char s[30];
	  real_to_decimal (s, &r, sizeof (s), 0, 1);
#ifdef ENCORE_ASM
	  fprintf (file, "0f%s", s);
#else
	  fprintf (file, "0d%s", s);
#endif
#endif
	}
      else
	{
#ifdef SEQUENT_ASM
	  long l;
	  REAL_VALUE_TO_TARGET_SINGLE (r, l);
	  fprintf (file, "0Fx%08lx", l);
#else
	  char s[30];
	  real_to_decimal (s, &r, sizeof (s), 0, 1);
	  fprintf (file, "0f%s", s);
#endif
	}
    }
  else
    {
      if (flag_pic
          && GET_CODE (x) == CONST
          && symbolic_reference_mentioned_p (x))
        {
	  fprintf (stderr, "illegal constant for pic-mode: \n");
	  print_rtl (stderr, x);
          fprintf (stderr, "\nGET_CODE (x) == %d, CONST == %d, symbolic_reference_mentioned_p (x) == %d\n",
		  GET_CODE (x), CONST, symbolic_reference_mentioned_p (x));
	  abort ();
	}
      else if (flag_pic
               && (GET_CODE (x) == SYMBOL_REF || GET_CODE (x) == LABEL_REF))
	{
	  output_addr_const (file, x);
	  fprintf (file, "(sb)");
	}
      else
        {
#ifdef NO_IMMEDIATE_PREFIX_IF_SYMBOLIC
          if (GET_CODE (x) == CONST_INT)
#endif
	    PUT_IMMEDIATE_PREFIX (file);
          output_addr_const (file, x);
	}
    }
}

/* PRINT_OPERAND_ADDRESS is defined to call this function,
   which is easier to debug than putting all the code in
   a macro definition in ns32k.h .  */

/* Completely rewritten to get this to work with Gas for PC532 Mach.
   This function didn't work and I just wasn't able (nor very willing) to
   figure out how it worked.
   90-11-25 Tatu Yl|nen <ylo@cs.hut.fi> */

void
print_operand_address (file, addr)
     register FILE *file;
     register rtx addr;
{
  static const char scales[] = { 'b', 'w', 'd', 0, 'q', };
  rtx offset, base, indexexp, tmp;
  int scale;
  extern int flag_pic;

  if (GET_CODE (addr) == PRE_DEC || GET_CODE (addr) == POST_DEC)
    {
      fprintf (file, "tos");
      return;
    }

  offset = NULL;
  base = NULL;
  indexexp = NULL;
  while (addr != NULL)
    {
      if (GET_CODE (addr) == PLUS)
	{
	  if (GET_CODE (XEXP (addr, 0)) == PLUS)
	    {
	      tmp = XEXP (addr, 1);
	      addr = XEXP (addr, 0);
	    }
	  else
	    {
	      tmp = XEXP (addr,0);
	      addr = XEXP (addr,1);
	    }
	}
      else
	{
	  tmp = addr;
	  addr = NULL;
	}
      switch (GET_CODE (tmp))
	{
	case PLUS:
	  abort ();
	case MEM:
	  if (base)
	    {
	      indexexp = base;
	      base = tmp;
	    }
	  else
	    base = tmp;
	  break;
	case REG:
	  if (REGNO (tmp) < F0_REGNUM)
	    if (base)
	      {
		indexexp = tmp;
	      }
	    else
	      base = tmp;
	  else
	    if (base)
	      {
		indexexp = base;
		base = tmp;
	      }
	    else
	      base = tmp;
	  break;
	case MULT:
	  indexexp = tmp;
	  break;
	case SYMBOL_REF:
	  if (flag_pic && ! CONSTANT_POOL_ADDRESS_P (tmp)
	      && ! SYMBOL_REF_FLAG (tmp))
	    {
	      if (base)
		{
		  if (indexexp)
		    abort ();
		  indexexp = base;
		}
	      base = tmp;
	      break;
	    }
	case CONST:
	  if (flag_pic && GET_CODE (tmp) == CONST)
	    {
	      rtx sym, off, tmp1;
	      tmp1 = XEXP (tmp,0);
	      if (GET_CODE (tmp1)  != PLUS)
		abort ();

	      sym = XEXP (tmp1,0);
	      if (GET_CODE (sym) != SYMBOL_REF)
	        {
	          off = sym;
		  sym = XEXP (tmp1,1);
		}
	      else
	        off = XEXP (tmp1,1);
	      if (GET_CODE (sym) == SYMBOL_REF)
		{
		  if (GET_CODE (off) != CONST_INT)
		    abort ();

		  if (CONSTANT_POOL_ADDRESS_P (sym)
		      || SYMBOL_REF_FLAG (sym))
		    {
		      SYMBOL_REF_FLAG (tmp) = 1;
		    }
		  else
		    {
		      if (base)
			{
			  if (indexexp)
			    abort ();

			  indexexp = base;
			}

		      if (offset != 0)
			abort ();

		      base = sym;
		      offset = off;
		      break;
		    }
		}
	    }
	case CONST_INT:
	case LABEL_REF:
	  if (offset)
	    offset = gen_rtx_PLUS (SImode, tmp, offset);
	  else
	    offset = tmp;
	  break;
	default:
	  abort ();
	}
    }
  if (! offset)
    offset = const0_rtx;

  if (base
#ifndef INDEX_RATHER_THAN_BASE
      && (flag_pic || TARGET_HIMEM)
      && GET_CODE (base) != SYMBOL_REF 
      && GET_CODE (offset) != CONST_INT
#else
  /* This is a re-implementation of the SEQUENT_ADDRESS_BUG fix.  */
#endif
      && !indexexp && GET_CODE (base) == REG
      && REG_OK_FOR_INDEX_P (base))
    {
      indexexp = base;
      base = NULL;
    }

  /* now, offset, base and indexexp are set */
#ifndef BASE_REG_NEEDED
  if (! base)
    {
#if defined (PC_RELATIVE) || defined (NO_ABSOLUTE_PREFIX_IF_SYMBOLIC)
      if (GET_CODE (offset) == CONST_INT)
#endif
	PUT_ABSOLUTE_PREFIX (file);
    }
#endif

  output_addr_const (file, offset);
  if (base) /* base can be (REG ...) or (MEM ...) */
    switch (GET_CODE (base))
      {
	/* now we must output base.  Possible alternatives are:
	   (rN)       (REG ...)
	   (sp)	      (REG ...)
	   (fp)       (REG ...)
	   (pc)       (REG ...)  used for SYMBOL_REF and LABEL_REF, output
	   (disp(fp)) (MEM ...)       just before possible [rX:y]
	   (disp(sp)) (MEM ...)
	   (disp(sb)) (MEM ...)
	   */
      case REG:
	fprintf (file, "(%s)", ns32k_out_reg_names[REGNO (base)]);
	break;
      case SYMBOL_REF:
	if (! flag_pic)
	  abort ();

        fprintf (file, "(");
	output_addr_const (file, base);
	fprintf (file, "(sb))");
        break;
      case MEM:
	addr = XEXP (base,0);
	base = NULL;
	offset = NULL;
	while (addr != NULL)
	  {
	    if (GET_CODE (addr) == PLUS)
	      {
		if (GET_CODE (XEXP (addr, 0)) == PLUS)
		  {
		    tmp = XEXP (addr, 1);
		    addr = XEXP (addr, 0);
		  }
		else
		  {
		    tmp = XEXP (addr, 0);
		    addr = XEXP (addr, 1);
		  }
	      }
	    else
	      {
		tmp = addr;
		addr = NULL;
	      }
	    switch (GET_CODE (tmp))
	      {
	      case REG:
		base = tmp;
		break;
	      case CONST:
	      case CONST_INT:
	      case SYMBOL_REF:
	      case LABEL_REF:
		if (offset)
		  offset = gen_rtx_PLUS (SImode, tmp, offset);
		else
		  offset = tmp;
		break;
	      default:
		abort ();
	      }
	  }
	if (! offset)
	  offset = const0_rtx;
	fprintf (file, "(");
	output_addr_const (file, offset);
	if (base)
	  fprintf (file, "(%s)", ns32k_out_reg_names[REGNO (base)]);
	else if (TARGET_SB)
	  fprintf (file, "(sb)");
	else
	  abort ();
	fprintf (file, ")");
	break;
      default:
	abort ();
      }
#ifdef PC_RELATIVE
  else if (GET_CODE (offset) != CONST_INT)
    fprintf (file, "(pc)");
#ifdef BASE_REG_NEEDED
  else if (TARGET_SB)
    fprintf (file, "(sb)");
  else
    abort ();
#endif
#endif /* PC_RELATIVE */

  /* now print index if we have one */
  if (indexexp)
    {
      if (GET_CODE (indexexp) == MULT)
	{
	  scale = INTVAL (XEXP (indexexp, 1)) >> 1;
	  indexexp = XEXP (indexexp, 0);
	}
      else
	scale = 0;
      if (GET_CODE (indexexp) != REG || REGNO (indexexp) >= F0_REGNUM)
	abort ();

#ifdef UTEK_ASM
      fprintf (file, "[%c`%s]",
	       scales[scale],
	       ns32k_out_reg_names[REGNO (indexexp)]);
#else
      fprintf (file, "[%s:%c]",
	       ns32k_out_reg_names[REGNO (indexexp)],
	       scales[scale]);
#endif
    }
}

/* National 32032 shifting is so bad that we can get
   better performance in many common cases by using other
   techniques.  */
const char *
output_shift_insn (operands)
     rtx *operands;
{
  if (GET_CODE (operands[2]) == CONST_INT
      && INTVAL (operands[2]) > 0
      && INTVAL (operands[2]) <= 3)
    {
      if (GET_CODE (operands[0]) == REG)
	{
	  if (GET_CODE (operands[1]) == REG)
	    {
	      if (REGNO (operands[0]) == REGNO (operands[1]))
		{
		  if (operands[2] == const1_rtx)
		    return "addd %0,%0";
		  else if (INTVAL (operands[2]) == 2)
		    return "addd %0,%0\n\taddd %0,%0";
		}
	      if (operands[2] == const1_rtx)
		return "movd %1,%0\n\taddd %0,%0";
	    
	      operands[1] = gen_indexed_expr (const0_rtx, operands[1], operands[2]);
	      return "addr %a1,%0";
	    }
	  if (operands[2] == const1_rtx)
	    return "movd %1,%0\n\taddd %0,%0";
	}
      else if (GET_CODE (operands[1]) == REG)
	{
	  operands[1] = gen_indexed_expr (const0_rtx, operands[1], operands[2]);
	  return "addr %a1,%0";
	}
      else if (INTVAL (operands[2]) == 1
	       && GET_CODE (operands[1]) == MEM
	       && rtx_equal_p (operands [0], operands[1]))
	{
	  rtx temp = XEXP (operands[1], 0);
	
	  if (GET_CODE (temp) == REG
	      || (GET_CODE (temp) == PLUS
		  && GET_CODE (XEXP (temp, 0)) == REG
		  && GET_CODE (XEXP (temp, 1)) == CONST_INT))
	    return "addd %0,%0";
	}
      else return "ashd %2,%0";
    }
  return "ashd %2,%0";
}

const char *
output_move_dconst (n, s)
	int n;
	const char *s;
{
  static char r[32];

  if (n > -9 && n < 8)
    strcpy (r, "movqd ");
  else if (n > 0 && n < 256)
    strcpy (r, "movzbd ");
  else if (n > 0 && n < 65536)
    strcpy (r, "movzwd ");
  else if (n < 0 && n > -129)
    strcpy (r, "movxbd ");
  else if (n < 0 && n > -32769)
    strcpy (r, "movxwd ");
  else
    strcpy (r, "movd ");
  strcat (r, s);
  return r;
}

/* If using PIC, mark a SYMBOL_REF for a non-global symbol or a code
   symbol. These symbols are referenced via pc and not via sb. */

static void
ns32k_encode_section_info (decl, first)
     tree decl;
     int first ATTRIBUTE_UNUSED;
{
  if (flag_pic)
    {
      rtx rtl = (TREE_CODE_CLASS (TREE_CODE (decl)) != 'd'
		 ? TREE_CST_RTL (decl) : DECL_RTL (decl));
      SYMBOL_REF_FLAG (XEXP (rtl, 0))
	= (TREE_CODE_CLASS (TREE_CODE (decl)) != 'd'
	   || ! TREE_PUBLIC (decl));
    }
}