Exemple #1
0
static void
init_label_info (rtx f)
{
  rtx insn;

  for (insn = f; insn; insn = NEXT_INSN (insn))
    {
      if (LABEL_P (insn))
	LABEL_NUSES (insn) = (LABEL_PRESERVE_P (insn) != 0);

      /* REG_LABEL_TARGET notes (including the JUMP_LABEL field) are
	 sticky and not reset here; that way we won't lose association
	 with a label when e.g. the source for a target register
	 disappears out of reach for targets that may use jump-target
	 registers.  Jump transformations are supposed to transform
	 any REG_LABEL_TARGET notes.  The target label reference in a
	 branch may disappear from the branch (and from the
	 instruction before it) for other reasons, like register
	 allocation.  */

      if (INSN_P (insn))
	{
	  rtx note, next;

	  for (note = REG_NOTES (insn); note; note = next)
	    {
	      next = XEXP (note, 1);
	      if (REG_NOTE_KIND (note) == REG_LABEL_OPERAND
		  && ! reg_mentioned_p (XEXP (note, 0), PATTERN (insn)))
		remove_note (insn, note);
	    }
	}
    }
}
Exemple #2
0
/* Call struct_equiv_make_checkpoint (P, INFO) if the current partial block
   is suitable to split off - i.e. there is no dangling cc0 user - and
   if the current cost of the common instructions, minus the cost for
   setting up the inputs, is higher than what has been recorded before
   in CHECKPOINT[N].  Also, if we do so, confirm or cancel any pending
   changes.  */
static void
struct_equiv_improve_checkpoint (struct struct_equiv_checkpoint *p,
				 struct equiv_info *info)
{
#ifdef HAVE_cc0
  if (reg_mentioned_p (cc0_rtx, info->cur.x_start)
      && !sets_cc0_p (info->cur.x_start))
    return;
#endif
  if (info->cur.input_count >= IMPOSSIBLE_MOVE_FACTOR)
    return;
  if (info->input_cost >= 0
      ? (COSTS_N_INSNS(info->cur.ninsns - p->ninsns)
	 > info->input_cost * (info->cur.input_count - p->input_count))
      : info->cur.ninsns > p->ninsns && !info->cur.input_count)
    {
      if (info->check_input_conflict && ! resolve_input_conflict (info))
	return;
      /* We have a profitable set of changes.  If this is the final pass,
	 commit them now.  Otherwise, we don't know yet if we can make any
	 change, so put the old code back for now.  */
      if (info->mode & STRUCT_EQUIV_FINAL)
	confirm_change_group ();
      else
	cancel_changes (0);
      struct_equiv_make_checkpoint (p, info);
    }
}
Exemple #3
0
static int
record_stack_refs (rtx *xp, void *data)
{
    rtx x = *xp;
    struct record_stack_refs_data *d =
        (struct record_stack_refs_data *) data;
    if (!x)
        return 0;
    switch (GET_CODE (x))
    {
    case MEM:
        if (!reg_mentioned_p (stack_pointer_rtx, x))
            return -1;
        /* We are not able to handle correctly all possible memrefs containing
           stack pointer, so this check is necessary.  */
        if (stack_memref_p (x))
        {
            d->reflist = record_one_stack_ref (d->insn, xp, d->reflist);
            return -1;
        }
        /* Try harder for DEBUG_INSNs, handle e.g. (mem (mem (sp + 16) + 4).  */
        return !DEBUG_INSN_P (d->insn);
    case REG:
        /* ??? We want be able to handle non-memory stack pointer
        references later.  For now just discard all insns referring to
         stack pointer outside mem expressions.  We would probably
         want to teach validate_replace to simplify expressions first.

         We can't just compare with STACK_POINTER_RTX because the
         reference to the stack pointer might be in some other mode.
         In particular, an explicit clobber in an asm statement will
         result in a QImode clobber.

         In DEBUG_INSNs, we want to replace all occurrences, otherwise
         they will cause -fcompare-debug failures.  */
        if (REGNO (x) == STACK_POINTER_REGNUM)
        {
            if (!DEBUG_INSN_P (d->insn))
                return 1;
            d->reflist = record_one_stack_ref (d->insn, xp, d->reflist);
            return -1;
        }
        break;
    default:
        break;
    }
    return 0;
}
static void
collect_pattern_seqs (void)
{
  htab_iterator hti0, hti1, hti2;
  p_hash_bucket hash_bucket;
  p_hash_elem e0, e1;
#if defined STACK_REGS || defined HAVE_cc0
  basic_block bb;
  bitmap_head dont_collect;

  /* Extra initialization step to ensure that no stack registers (if present)
     or cc0 code (if present) are live across abnormal edges.
     Set a flag in DONT_COLLECT for an insn if a stack register is live
     after the insn or the insn is cc0 setter or user.  */
  bitmap_initialize (&dont_collect, NULL);

#ifdef STACK_REGS
  FOR_EACH_BB (bb)
  {
    regset_head live;
    rtx insn;
    rtx prev;

    /* Initialize liveness propagation.  */
    INIT_REG_SET (&live);
    bitmap_copy (&live, DF_LR_OUT (bb));
    df_simulate_initialize_backwards (bb, &live);

    /* Propagate liveness info and mark insns where a stack reg is live.  */
    insn = BB_END (bb);
    for (insn = BB_END (bb); ; insn = prev)
      {
	prev = PREV_INSN (insn);
	if (INSN_P (insn))
	  {
	    int reg;
	    for (reg = FIRST_STACK_REG; reg <= LAST_STACK_REG; reg++)
	      {
		if (REGNO_REG_SET_P (&live, reg))
		  {
		    bitmap_set_bit (&dont_collect, INSN_UID (insn));
		    break;
		  }
	      }
	    
	  }
	if (insn == BB_HEAD (bb))
	  break;
	df_simulate_one_insn_backwards (bb, insn, &live);
	insn = prev;
      }

    /* Free unused data.  */
    CLEAR_REG_SET (&live);
  }
#endif

#ifdef HAVE_cc0
  /* Mark CC0 setters and users as ineligible for collection into sequences.
     This is an over-conservative fix, since it is OK to include
     a cc0_setter, but only if we also include the corresponding cc0_user,
     and vice versa.  */
  FOR_EACH_BB (bb)
  {
    rtx insn;
    rtx next_tail;

    next_tail = NEXT_INSN (BB_END (bb));

    for (insn = BB_HEAD (bb); insn != next_tail; insn = NEXT_INSN (insn))
      {
	if (INSN_P (insn) && reg_mentioned_p (cc0_rtx, PATTERN (insn)))
	  bitmap_set_bit (&dont_collect, INSN_UID (insn));
      }
  }
#endif

#endif /* defined STACK_REGS || defined HAVE_cc0 */

  /* Initialize PATTERN_SEQS to empty.  */
  pattern_seqs = 0;

  /* Try to match every abstractable insn with every other insn in the same
     HASH_BUCKET.  */

  FOR_EACH_HTAB_ELEMENT (hash_buckets, hash_bucket, p_hash_bucket, hti0)
    if (htab_elements (hash_bucket->seq_candidates) > 1)
      FOR_EACH_HTAB_ELEMENT (hash_bucket->seq_candidates, e0, p_hash_elem, hti1)
        FOR_EACH_HTAB_ELEMENT (hash_bucket->seq_candidates, e1, p_hash_elem,
                               hti2)
          if (e0 != e1
#if defined STACK_REGS || defined HAVE_cc0
              && !bitmap_bit_p (&dont_collect, INSN_UID (e0->insn))
              && !bitmap_bit_p (&dont_collect, INSN_UID (e1->insn))
#endif
             )
            match_seqs (e0, e1);
#if defined STACK_REGS || defined HAVE_cc0
  /* Free unused data.  */
  bitmap_clear (&dont_collect);
#endif
}
Exemple #5
0
/* Function to check whether the OP is a valid stack push/pop operation.
   For a valid stack operation, it must satisfy following conditions:
     1. Consecutive registers push/pop operations.
     2. Valid $fp/$gp/$lp push/pop operations.
     3. The last element must be stack adjustment rtx.
   See the prologue/epilogue implementation for details.  */
bool
nds32_valid_stack_push_pop_p (rtx op, bool push_p)
{
  int index;
  int total_count;
  int rest_count;
  int first_regno;
  int save_fp, save_gp, save_lp;
  rtx elt;
  rtx elt_reg;
  rtx elt_mem;
  rtx elt_plus;

  /* Get the counts of elements in the parallel rtx.  */
  total_count = XVECLEN (op, 0);

  /* Perform some quick check for that every element should be 'set'.  */
  for (index = 0; index < total_count; index++)
    {
      elt = XVECEXP (op, 0, index);
      if (GET_CODE (elt) != SET)
        return false;
    }

  /* For push operation, the parallel rtx looks like:
     (parallel [(set (mem (plus (reg:SI SP_REGNUM) (const_int -32)))
                     (reg:SI Rb))
                (set (mem (plus (reg:SI SP_REGNUM) (const_int -28)))
                     (reg:SI Rb+1))
                ...
                (set (mem (plus (reg:SI SP_REGNUM) (const_int -16)))
                     (reg:SI Re))
                (set (mem (plus (reg:SI SP_REGNUM) (const_int -12)))
                     (reg:SI FP_REGNUM))
                (set (mem (plus (reg:SI SP_REGNUM) (const_int -8)))
                     (reg:SI GP_REGNUM))
                (set (mem (plus (reg:SI SP_REGNUM) (const_int -4)))
                     (reg:SI LP_REGNUM))
                (set (reg:SI SP_REGNUM)
                     (plus (reg:SI SP_REGNUM) (const_int -32)))])

     For pop operation, the parallel rtx looks like:
     (parallel [(set (reg:SI Rb)
                     (mem (reg:SI SP_REGNUM)))
                (set (reg:SI Rb+1)
                     (mem (plus (reg:SI SP_REGNUM) (const_int 4))))
                ...
                (set (reg:SI Re)
                     (mem (plus (reg:SI SP_REGNUM) (const_int 16))))
                (set (reg:SI FP_REGNUM)
                     (mem (plus (reg:SI SP_REGNUM) (const_int 20))))
                (set (reg:SI GP_REGNUM)
                     (mem (plus (reg:SI SP_REGNUM) (const_int 24))))
                (set (reg:SI LP_REGNUM)
                     (mem (plus (reg:SI SP_REGNUM) (const_int 28))))
                (set (reg:SI SP_REGNUM)
                     (plus (reg:SI SP_REGNUM) (const_int 32)))]) */

  /* 1. Consecutive registers push/pop operations.
        We need to calculate how many registers should be consecutive.
        The $sp adjustment rtx, $fp push rtx, $gp push rtx,
        and $lp push rtx are excluded.  */

  /* Detect whether we have $fp, $gp, or $lp in the parallel rtx.  */
  save_fp = reg_mentioned_p (gen_rtx_REG (SImode, FP_REGNUM), op);
  save_gp = reg_mentioned_p (gen_rtx_REG (SImode, GP_REGNUM), op);
  save_lp = reg_mentioned_p (gen_rtx_REG (SImode, LP_REGNUM), op);
  /* Exclude last $sp adjustment rtx.  */
  rest_count = total_count - 1;
  /* Exclude $fp, $gp, and $lp if they are in the parallel rtx.  */
  if (save_fp)
    rest_count--;
  if (save_gp)
    rest_count--;
  if (save_lp)
    rest_count--;

  if (rest_count > 0)
    {
      elt = XVECEXP (op, 0, 0);
      /* Pick up register element.  */
      elt_reg = push_p ? SET_SRC (elt) : SET_DEST (elt);
      first_regno = REGNO (elt_reg);

      /* The 'push' operation is a kind of store operation.
         The 'pop' operation is a kind of load operation.
         Pass corresponding false/true as second argument (bool load_p).
         The par_index is supposed to start with index 0.  */
      if (!nds32_consecutive_registers_load_store_p (op,
						     !push_p ? true : false,
						     0,
						     first_regno,
						     rest_count))
        return false;
    }

  /* 2. Valid $fp/$gp/$lp push/pop operations.
        Remember to set start index for checking them.  */

  /* The rest_count is the start index for checking $fp/$gp/$lp.  */
  index = rest_count;
  /* If index < 0, this parallel rtx is definitely
     not a valid stack push/pop operation.  */
  if (index < 0)
    return false;

  /* Check $fp/$gp/$lp one by one.
     We use 'push_p' to pick up reg rtx and mem rtx.  */
  if (save_fp)
    {
      elt = XVECEXP (op, 0, index);
      elt_mem = push_p ? SET_DEST (elt) : SET_SRC (elt);
      elt_reg = push_p ? SET_SRC (elt) : SET_DEST (elt);
      index++;

      if (GET_CODE (elt_mem) != MEM
          || GET_CODE (elt_reg) != REG
          || REGNO (elt_reg) != FP_REGNUM)
        return false;
    }
  if (save_gp)
    {
      elt = XVECEXP (op, 0, index);
      elt_mem = push_p ? SET_DEST (elt) : SET_SRC (elt);
      elt_reg = push_p ? SET_SRC (elt) : SET_DEST (elt);
      index++;

      if (GET_CODE (elt_mem) != MEM
          || GET_CODE (elt_reg) != REG
          || REGNO (elt_reg) != GP_REGNUM)
        return false;
    }
  if (save_lp)
    {
      elt = XVECEXP (op, 0, index);
      elt_mem = push_p ? SET_DEST (elt) : SET_SRC (elt);
      elt_reg = push_p ? SET_SRC (elt) : SET_DEST (elt);
      index++;

      if (GET_CODE (elt_mem) != MEM
          || GET_CODE (elt_reg) != REG
          || REGNO (elt_reg) != LP_REGNUM)
        return false;
    }

  /* 3. The last element must be stack adjustment rtx.
        Its form of rtx should be:
          (set (reg:SI SP_REGNUM)
               (plus (reg:SI SP_REGNUM) (const_int X)))
        The X could be positive or negative value.  */

  /* Pick up the last element.  */
  elt = XVECEXP (op, 0, total_count - 1);

  /* Extract its destination and source rtx.  */
  elt_reg  = SET_DEST (elt);
  elt_plus = SET_SRC (elt);

  /* Check this is (set (stack_reg) (plus stack_reg const)) pattern.  */
  if (GET_CODE (elt_reg) != REG
      || GET_CODE (elt_plus) != PLUS
      || REGNO (elt_reg) != SP_REGNUM)
    return false;

  /* Pass all test, this is a valid rtx.  */
  return true;
}
Exemple #6
0
static void
combine_stack_adjustments_for_block (basic_block bb)
{
    HOST_WIDE_INT last_sp_adjust = 0;
    rtx last_sp_set = NULL_RTX;
    rtx last2_sp_set = NULL_RTX;
    struct csa_reflist *reflist = NULL;
    rtx insn, next, set;
    struct record_stack_refs_data data;
    bool end_of_block = false;

    for (insn = BB_HEAD (bb); !end_of_block ; insn = next)
    {
        end_of_block = insn == BB_END (bb);
        next = NEXT_INSN (insn);

        if (! INSN_P (insn))
            continue;

        set = single_set_for_csa (insn);
        if (set)
        {
            rtx dest = SET_DEST (set);
            rtx src = SET_SRC (set);

            /* Find constant additions to the stack pointer.  */
            if (dest == stack_pointer_rtx
                    && GET_CODE (src) == PLUS
                    && XEXP (src, 0) == stack_pointer_rtx
                    && CONST_INT_P (XEXP (src, 1)))
            {
                HOST_WIDE_INT this_adjust = INTVAL (XEXP (src, 1));

                /* If we've not seen an adjustment previously, record
                it now and continue.  */
                if (! last_sp_set)
                {
                    last_sp_set = insn;
                    last_sp_adjust = this_adjust;
                    continue;
                }

                /* If not all recorded refs can be adjusted, or the
                adjustment is now too large for a constant addition,
                 we cannot merge the two stack adjustments.

                 Also we need to be careful to not move stack pointer
                 such that we create stack accesses outside the allocated
                 area.  We can combine an allocation into the first insn,
                 or a deallocation into the second insn.  We can not
                 combine an allocation followed by a deallocation.

                 The only somewhat frequent occurrence of the later is when
                 a function allocates a stack frame but does not use it.
                 For this case, we would need to analyze rtl stream to be
                 sure that allocated area is really unused.  This means not
                 only checking the memory references, but also all registers
                 or global memory references possibly containing a stack
                 frame address.

                 Perhaps the best way to address this problem is to teach
                 gcc not to allocate stack for objects never used.  */

                /* Combine an allocation into the first instruction.  */
                if (STACK_GROWS_DOWNWARD ? this_adjust <= 0 : this_adjust >= 0)
                {
                    if (try_apply_stack_adjustment (last_sp_set, reflist,
                                                    last_sp_adjust + this_adjust,
                                                    this_adjust))
                    {
                        /* It worked!  */
                        maybe_move_args_size_note (last_sp_set, insn, false);
                        delete_insn (insn);
                        last_sp_adjust += this_adjust;
                        continue;
                    }
                }

                /* Otherwise we have a deallocation.  Do not combine with
                a previous allocation.  Combine into the second insn.  */
                else if (STACK_GROWS_DOWNWARD
                         ? last_sp_adjust >= 0 : last_sp_adjust <= 0)
                {
                    if (try_apply_stack_adjustment (insn, reflist,
                                                    last_sp_adjust + this_adjust,
                                                    -last_sp_adjust))
                    {
                        /* It worked!  */
                        maybe_move_args_size_note (insn, last_sp_set, true);
                        delete_insn (last_sp_set);
                        last_sp_set = insn;
                        last_sp_adjust += this_adjust;
                        free_csa_reflist (reflist);
                        reflist = NULL;
                        continue;
                    }
                }

                /* Combination failed.  Restart processing from here.  If
                deallocation+allocation conspired to cancel, we can
                 delete the old deallocation insn.  */
                if (last_sp_set)
                {
                    if (last_sp_adjust == 0)
                    {
                        maybe_move_args_size_note (insn, last_sp_set, true);
                        delete_insn (last_sp_set);
                    }
                    else
                        last2_sp_set = last_sp_set;
                }
                free_csa_reflist (reflist);
                reflist = NULL;
                last_sp_set = insn;
                last_sp_adjust = this_adjust;
                continue;
            }

            /* Find a store with pre-(dec|inc)rement or pre-modify of exactly
               the previous adjustment and turn it into a simple store.  This
               is equivalent to anticipating the stack adjustment so this must
               be an allocation.  */
            if (MEM_P (dest)
                    && ((STACK_GROWS_DOWNWARD
                         ? (GET_CODE (XEXP (dest, 0)) == PRE_DEC
                            && last_sp_adjust
                            == (HOST_WIDE_INT) GET_MODE_SIZE (GET_MODE (dest)))
                         : (GET_CODE (XEXP (dest, 0)) == PRE_INC
                            && last_sp_adjust
                            == -(HOST_WIDE_INT) GET_MODE_SIZE (GET_MODE (dest))))
                        || ((STACK_GROWS_DOWNWARD
                             ? last_sp_adjust >= 0 : last_sp_adjust <= 0)
                            && GET_CODE (XEXP (dest, 0)) == PRE_MODIFY
                            && GET_CODE (XEXP (XEXP (dest, 0), 1)) == PLUS
                            && XEXP (XEXP (XEXP (dest, 0), 1), 0)
                            == stack_pointer_rtx
                            && GET_CODE (XEXP (XEXP (XEXP (dest, 0), 1), 1))
                            == CONST_INT
                            && INTVAL (XEXP (XEXP (XEXP (dest, 0), 1), 1))
                            == -last_sp_adjust))
                    && XEXP (XEXP (dest, 0), 0) == stack_pointer_rtx
                    && !reg_mentioned_p (stack_pointer_rtx, src)
                    && memory_address_p (GET_MODE (dest), stack_pointer_rtx)
                    && try_apply_stack_adjustment (insn, reflist, 0,
                                                   -last_sp_adjust))
            {
                if (last2_sp_set)
                    maybe_move_args_size_note (last2_sp_set, last_sp_set, false);
                else
                    maybe_move_args_size_note (insn, last_sp_set, true);
                delete_insn (last_sp_set);
                free_csa_reflist (reflist);
                reflist = NULL;
                last_sp_set = NULL_RTX;
                last_sp_adjust = 0;
                continue;
            }
        }

        data.insn = insn;
        data.reflist = reflist;
        if (!CALL_P (insn) && last_sp_set
                && !for_each_rtx (&PATTERN (insn), record_stack_refs, &data))
        {
            reflist = data.reflist;
            continue;
        }
        reflist = data.reflist;

        /* Otherwise, we were not able to process the instruction.
        Do not continue collecting data across such a one.  */
        if (last_sp_set
                && (CALL_P (insn)
                    || reg_mentioned_p (stack_pointer_rtx, PATTERN (insn))))
        {
            if (last_sp_set && last_sp_adjust == 0)
            {
                force_move_args_size_note (bb, last2_sp_set, last_sp_set);
                delete_insn (last_sp_set);
            }
            free_csa_reflist (reflist);
            reflist = NULL;
            last2_sp_set = NULL_RTX;
            last_sp_set = NULL_RTX;
            last_sp_adjust = 0;
        }
    }

    if (last_sp_set && last_sp_adjust == 0)
    {
        force_move_args_size_note (bb, last2_sp_set, last_sp_set);
        delete_insn (last_sp_set);
    }

    if (reflist)
        free_csa_reflist (reflist);
}
Exemple #7
0
/* Function to output stack push operation.
   We need to deal with normal stack push multiple or stack v3push.  */
const char *
nds32_output_stack_push (rtx par_rtx)
{
  /* A string pattern for output_asm_insn().  */
  char pattern[100];
  /* The operands array which will be used in output_asm_insn().  */
  rtx operands[3];
  /* Pick up varargs first regno and last regno for further use.  */
  int rb_va_args = cfun->machine->va_args_first_regno;
  int re_va_args = cfun->machine->va_args_last_regno;
  int last_argument_regno = NDS32_FIRST_GPR_REGNUM
			    + NDS32_MAX_GPR_REGS_FOR_ARGS
			    - 1;
  /* Pick up callee-saved first regno and last regno for further use.  */
  int rb_callee_saved = cfun->machine->callee_saved_regs_first_regno;
  int re_callee_saved = cfun->machine->callee_saved_regs_last_regno;

  /* First we need to check if we are pushing argument registers not used
     for the named arguments.  If so, we have to create 'smw.adm' (push.s)
     instruction.  */
  if (reg_mentioned_p (gen_rtx_REG (SImode, last_argument_regno), par_rtx))
    {
      /* Set operands[0] and operands[1].  */
      operands[0] = gen_rtx_REG (SImode, rb_va_args);
      operands[1] = gen_rtx_REG (SImode, re_va_args);
      /* Create assembly code pattern: "Rb, Re, { }".  */
      snprintf (pattern, sizeof (pattern), "push.s\t%s", "%0, %1, { }");
      /* We use output_asm_insn() to output assembly code by ourself.  */
      output_asm_insn (pattern, operands);
      return "";
    }

  /* If we step here, we are going to do v3push or multiple push operation.  */

  /* The v3push/v3pop instruction should only be applied on
     none-isr and none-variadic function.  */
  if (TARGET_V3PUSH
      && !nds32_isr_function_p (current_function_decl)
      && (cfun->machine->va_args_size == 0))
    {
      /* For stack v3push:
           operands[0]: Re
           operands[1]: imm8u */

      /* This variable is to check if 'push25 Re,imm8u' is available.  */
      int sp_adjust;

      /* Set operands[0].  */
      operands[0] = gen_rtx_REG (SImode, re_callee_saved);

      /* Check if we can generate 'push25 Re,imm8u',
         otherwise, generate 'push25 Re,0'.  */
      sp_adjust = cfun->machine->local_size
		  + cfun->machine->out_args_size
		  + cfun->machine->callee_saved_area_padding_bytes;
      if (satisfies_constraint_Iu08 (GEN_INT (sp_adjust))
	  && NDS32_DOUBLE_WORD_ALIGN_P (sp_adjust))
	operands[1] = GEN_INT (sp_adjust);
      else
	operands[1] = GEN_INT (0);

      /* Create assembly code pattern.  */
      snprintf (pattern, sizeof (pattern), "push25\t%%0, %%1");
    }
  else
    {
      /* For normal stack push multiple:
         operands[0]: Rb
         operands[1]: Re
         operands[2]: En4 */

      /* This variable is used to check if we only need to generate En4 field.
         As long as Rb==Re=SP_REGNUM, we set this variable to 1.  */
      int push_en4_only_p = 0;

      /* Set operands[0] and operands[1].  */
      operands[0] = gen_rtx_REG (SImode, rb_callee_saved);
      operands[1] = gen_rtx_REG (SImode, re_callee_saved);

      /* 'smw.adm $sp,[$sp],$sp,0' means push nothing.  */
      if (!cfun->machine->fp_size
	  && !cfun->machine->gp_size
	  && !cfun->machine->lp_size
	  && REGNO (operands[0]) == SP_REGNUM
	  && REGNO (operands[1]) == SP_REGNUM)
	{
	  /* No need to generate instruction.  */
	  return "";
	}
      else
	{
	  /* If Rb==Re=SP_REGNUM, we only need to generate En4 field.  */
	  if (REGNO (operands[0]) == SP_REGNUM
	      && REGNO (operands[1]) == SP_REGNUM)
	    push_en4_only_p = 1;

	  /* Create assembly code pattern.
	     We need to handle the form: "Rb, Re, { $fp $gp $lp }".  */
	  snprintf (pattern, sizeof (pattern),
		    "push.s\t%s{%s%s%s }",
		    push_en4_only_p ? "" : "%0, %1, ",
		    cfun->machine->fp_size ? " $fp" : "",
		    cfun->machine->gp_size ? " $gp" : "",
		    cfun->machine->lp_size ? " $lp" : "");
	}
    }

  /* We use output_asm_insn() to output assembly code by ourself.  */
  output_asm_insn (pattern, operands);
  return "";
}