This is the mail archive of the binutils@sources.redhat.com mailing list for the binutils project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[patch] MIPS/ELF: %call_r/%got_r operators for relocation override


Hello,

 Here is a patch implementing new "%call_r" and "%got_r" operators for
explicit selection of GOT or CALL relocations.  This is for hand-coded
assembly or old (pre-3.4) gcc implementations for which explicit
relocation selection with "%call16", "%call_hi", "%got", etc. is
unnecessarily tough, requiring knowledge of exact target ABI
configuration.  OTOH, it lets gas be cleaned-up, specifically the
imperfect heuristics for the "la" macro ($jp vs other registers) can be
removed without sacrificing the ability for programs to use lazy binding
via the use of CALL relocations or to defeat lazy binding where
inappropriate via the use of GOT relocations.

 The idea and a preliminary, imperfect patch for 2.13.2.1 was originally
discussed in the thread starting at: 
'http://sources.redhat.com/ml/binutils/2003-02/msg00317.html'.  Here is a
version I consider appropriate for a release.  Due to a huge size I've
skipped the test case here -- I have made the whole patch available at
'ftp://ftp.ds2.pg.gda.pl/pub/macro/misc/binutils-2.14.90-20030818-mips-gas-percent-op.patch.gz'. 

 There is one caveat -- for the new ABI, for "lw", "sw", etc. (as opposed
to "la" or "dla") , if a CALL relocation is requested and the involved
symbol turns out to be local, a BFD_RELOC_MIPS_GOT_DISP relocation is
emitted instead of a pair of
BFD_RELOC_MIPS_GOT_PAGE/BFD_RELOC_MIPS_GOT_OFST that would happen if there
was no operator.  I don't consider it a big problem -- that's exactly what
happens if e.g. "la treg,sym; lw reg,0(treg)" is used instead and changing
the behaviour would require a massive surgery to tc_gen_reloc(). 

2003-08-25  Maciej W. Rozycki  <macro@ds2.pg.gda.pl>

	* config/tc-mips.c (parse_special, my_getSpecialExpression): New
	functions to handle %call_r and %got_r operators.
	(my_getSmallExpression): Skip over the new operators.
	(load_address, macro, macro2, mips_ip): Emit GOT or CALL
	relocations as directed by the new operators.

 Any comments?

 I've made a patch 2.14 available at the address shown above as well.
Neither of the patches introduces new regressions.

  Maciej

-- 
+  Maciej W. Rozycki, Technical University of Gdansk, Poland   +
+--------------------------------------------------------------+
+        e-mail: macro@ds2.pg.gda.pl, PGP key available        +

binutils-2.14.90-20030818-mips-gas-percent-op.patch
diff -up --recursive --new-file binutils-2.14.90-20030818.macro/gas/config/tc-mips.c binutils-2.14.90-20030818/gas/config/tc-mips.c
--- binutils-2.14.90-20030818.macro/gas/config/tc-mips.c	2003-07-29 03:25:19.000000000 +0000
+++ binutils-2.14.90-20030818/gas/config/tc-mips.c	2003-08-25 01:56:23.000000000 +0000
@@ -847,6 +847,8 @@ static void mips16_immed
    unsigned long *, bfd_boolean *, unsigned short *);
 static size_t my_getSmallExpression
   (expressionS *, bfd_reloc_code_real_type *, char *);
+static void my_getSpecialExpression
+  (expressionS *, bfd_reloc_code_real_type *, char *);
 static void my_getExpression (expressionS *, char *);
 static void s_align (int);
 static void s_change_sec (int);
@@ -3629,7 +3631,8 @@ load_register (int *counter, int reg, ex
 /* Load an address into a register.  */
 
 static void
-load_address (int *counter, int reg, expressionS *ep, int *used_at)
+load_address (int *counter, int reg, expressionS *ep,
+	      bfd_reloc_code_real_type *reloc_type, int *used_at)
 {
   char *p = NULL;
 
@@ -3728,10 +3731,12 @@ load_address (int *counter, int reg, exp
   else if (mips_pic == SVR4_PIC && ! mips_big_got)
     {
       expressionS ex;
+      int lw_reloc_type;
 
       /* If this is a reference to an external symbol, we want
 	   lw		$reg,<sym>($gp)		(BFD_RELOC_MIPS_GOT16)
-	 Otherwise we want
+	 BFD_RELOC_MIPS_CALL16 may be used instead if requested with
+	 %call_r.  Otherwise we want
 	   lw		$reg,<sym>($gp)		(BFD_RELOC_MIPS_GOT16)
 	   nop
 	   addiu	$reg,$reg,<sym>		(BFD_RELOC_LO16)
@@ -3740,9 +3745,14 @@ load_address (int *counter, int reg, exp
 	 If we have NewABI, we want
 	   lw		$reg,<sym+cst>($gp)	(BFD_RELOC_MIPS_GOT_DISP)
          unless we're referencing a global symbol with a non-zero
-         offset, in which case cst must be added separately.  */
+	 offset, in which case cst must be added separately.
+	 For external symbols BFD_RELOC_MIPS_CALL16 may be used instead
+	 if requested with %call_r.  */
       if (HAVE_NEWABI)
 	{
+	  lw_reloc_type = BFD_RELOC_MIPS_GOT_DISP;
+	  if (*reloc_type == BFD_RELOC_MIPS_CALL16)
+	    lw_reloc_type = *reloc_type;
 	  frag_grow (12);
 
 	  if (ep->X_add_number)
@@ -3751,7 +3761,7 @@ load_address (int *counter, int reg, exp
 		ex.X_add_number = ep->X_add_number;
 	      ep->X_add_number = 0;
 	      macro_build (NULL, counter, ep, ADDRESS_LOAD_INSN, "t,o(b)",
-			   reg, BFD_RELOC_MIPS_GOT_DISP, mips_gp_register);
+			   reg, lw_reloc_type, mips_gp_register);
 	      if (ex.X_add_number < -0x8000 || ex.X_add_number >= 0x8000)
 		as_bad (_("PIC code offset overflow (max 16 signed bits)"));
 	      ex.X_op = O_constant;
@@ -3765,7 +3775,7 @@ load_address (int *counter, int reg, exp
 	    }
 
 	  macro_build (p, counter, ep, ADDRESS_LOAD_INSN, "t,o(b)", reg,
-		       BFD_RELOC_MIPS_GOT_DISP, mips_gp_register);
+		       lw_reloc_type, mips_gp_register);
 
 	  if (! p)
 	    {
@@ -3777,16 +3787,24 @@ load_address (int *counter, int reg, exp
 	}
       else
 	{
+	  lw_reloc_type = BFD_RELOC_MIPS_GOT16;
+	  if (*reloc_type == BFD_RELOC_MIPS_CALL16)
+	    lw_reloc_type = *reloc_type;
 	  ex.X_add_number = ep->X_add_number;
 	  ep->X_add_number = 0;
 	  frag_grow (20);
 	  macro_build (NULL, counter, ep, ADDRESS_LOAD_INSN, "t,o(b)", reg,
-		       BFD_RELOC_MIPS_GOT16,
-		       mips_gp_register);
+		       lw_reloc_type, mips_gp_register);
 	  macro_build (NULL, counter, NULL, "nop", "");
-	  p = frag_var (rs_machine_dependent, 4, 0,
-			RELAX_ENCODE (0, 4, -8, 0, 0, mips_opts.warn_about_macros),
+	  p = frag_var (rs_machine_dependent, 12, 0,
+			RELAX_ENCODE (8, 12, 0, 8, 0,
+				      mips_opts.warn_about_macros),
 			ep->X_add_symbol, 0, NULL);
+	  macro_build (p, counter, ep, ADDRESS_LOAD_INSN, "t,o(b)", reg,
+		       BFD_RELOC_MIPS_GOT16, mips_gp_register);
+	  p += 4;
+	  macro_build (p, counter, NULL, "nop", "");
+	  p += 4;
 	  macro_build (p, counter, ep, ADDRESS_ADDI_INSN, "t,r,j", reg, reg,
 		       BFD_RELOC_LO16);
 
@@ -3803,6 +3821,8 @@ load_address (int *counter, int reg, exp
   else if (mips_pic == SVR4_PIC)
     {
       expressionS ex;
+      int hi_reloc_type = BFD_RELOC_MIPS_GOT_HI16;
+      int lo_reloc_type = BFD_RELOC_MIPS_GOT_LO16;
       int off;
 
       /* This is the large GOT case.  If this is a reference to an
@@ -3810,6 +3830,8 @@ load_address (int *counter, int reg, exp
 	   lui		$reg,<sym>		(BFD_RELOC_MIPS_GOT_HI16)
 	   addu		$reg,$reg,$gp
 	   lw		$reg,<sym>($reg)	(BFD_RELOC_MIPS_GOT_LO16)
+	 BFD_RELOC_MIPS_CALL_HI16 and BFD_RELOC_MIPS_CALL_LO16 may
+	 be used instead if requested with %call_r.
 
 	 Otherwise, for a reference to a local symbol in old ABI, we want
 	   lw		$reg,<sym>($gp)		(BFD_RELOC_MIPS_GOT16)
@@ -3821,6 +3843,11 @@ load_address (int *counter, int reg, exp
 	   lw		$reg,<sym>($gp)		(BFD_RELOC_MIPS_GOT_PAGE)
 	   addiu	$reg,$reg,<sym>		(BFD_RELOC_MIPS_GOT_OFST)
       */
+      if (*reloc_type == BFD_RELOC_MIPS_CALL16)
+	{
+	  hi_reloc_type = BFD_RELOC_MIPS_CALL_HI16;
+	  lo_reloc_type = BFD_RELOC_MIPS_CALL_LO16;
+	}
       if (HAVE_NEWABI)
 	{
 	  frag_grow (24);
@@ -3828,12 +3855,11 @@ load_address (int *counter, int reg, exp
 	  frag_now->tc_frag_data.tc_fr_offset =
 	    ex.X_add_number = ep->X_add_number;
 	  ep->X_add_number = 0;
-	  macro_build (NULL, counter, ep, "lui", "t,u", reg,
-		       BFD_RELOC_MIPS_GOT_HI16);
+	  macro_build (NULL, counter, ep, "lui", "t,u", reg, hi_reloc_type);
 	  macro_build (NULL, counter, NULL, ADDRESS_ADD_INSN, "d,v,t", reg,
 		       reg, mips_gp_register);
 	  macro_build (NULL, counter, ep, ADDRESS_LOAD_INSN, "t,o(b)", reg,
-		       BFD_RELOC_MIPS_GOT_LO16, reg);
+		       lo_reloc_type, reg);
 	  if (ex.X_add_number < -0x8000 || ex.X_add_number >= 0x8000)
 	    as_bad (_("PIC code offset overflow (max 16 signed bits)"));
 	  else if (ex.X_add_number)
@@ -3862,12 +3888,11 @@ load_address (int *counter, int reg, exp
 	  else
 	    off = 0;
 	  frag_grow (32);
-	  macro_build (NULL, counter, ep, "lui", "t,u", reg,
-		       BFD_RELOC_MIPS_GOT_HI16);
+	  macro_build (NULL, counter, ep, "lui", "t,u", reg, hi_reloc_type);
 	  macro_build (NULL, counter, NULL, ADDRESS_ADD_INSN, "d,v,t", reg,
 		       reg, mips_gp_register);
 	  macro_build (NULL, counter, ep, ADDRESS_LOAD_INSN, "t,o(b)", reg,
-		       BFD_RELOC_MIPS_GOT_LO16, reg);
+		       lo_reloc_type, reg);
 	  p = frag_var (rs_machine_dependent, 12 + off, 0,
 			RELAX_ENCODE (12, 12 + off, off, 8 + off, 0,
 				      mips_opts.warn_about_macros),
@@ -4805,6 +4830,8 @@ macro (struct mips_cl_insn *ip)
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	     or if tempreg is PIC_CALL_REG
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_CALL16)
+	     BFD_RELOC_MIPS_CALL16 or BFD_RELOC_MIPS_GOT16 may be used
+	     instead if requested with %call_r or %got_r, respectively.
 	     For a local symbol, we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       nop
@@ -4815,9 +4842,10 @@ macro (struct mips_cl_insn *ip)
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       addiu	$tempreg,$tempreg,<constant>
-	     For a local symbol, we want the same instruction
-	     sequence, but we output a BFD_RELOC_LO16 reloc on the
-	     addiu instruction.
+	     BFD_RELOC_MIPS_CALL16 may be used instead if requested
+	     with %call_r.  For a local symbol, we want the same
+	     instruction sequence, but we output a BFD_RELOC_LO16 reloc
+	     on the addiu instruction (no %call_r override).
 
 	     If we have a large constant, and this is a reference to
 	     an external symbol, we want
@@ -4825,16 +4853,19 @@ macro (struct mips_cl_insn *ip)
 	       lui	$at,<hiconstant>
 	       addiu	$at,$at,<loconstant>
 	       addu	$tempreg,$tempreg,$at
-	     For a local symbol, we want the same instruction
-	     sequence, but we output a BFD_RELOC_LO16 reloc on the
-	     addiu instruction.
-	   */
+	     BFD_RELOC_MIPS_CALL16 may be used instead if requested
+	     with %call_r.  For a local symbol, we want the same
+	     instruction sequence, but we output a BFD_RELOC_LO16 reloc
+	     on the addiu instruction (no %call_r override).  */
 
 	  expr1.X_add_number = offset_expr.X_add_number;
 	  offset_expr.X_add_number = 0;
 	  frag_grow (32);
 	  if (expr1.X_add_number == 0 && tempreg == PIC_CALL_REG)
-	    lw_reloc_type = (int) BFD_RELOC_MIPS_CALL16;
+	    lw_reloc_type = BFD_RELOC_MIPS_CALL16;
+	  if (*offset_reloc == BFD_RELOC_MIPS_GOT16
+	      || *offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    lw_reloc_type = *offset_reloc;
 	  macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN, "t,o(b)",
 		       tempreg, lw_reloc_type, mips_gp_register);
 	  if (expr1.X_add_number == 0)
@@ -4852,36 +4883,44 @@ macro (struct mips_cl_insn *ip)
 		  macro_build (NULL, &icnt, NULL, "nop", "");
 		  off = 4;
 		}
-	      p = frag_var (rs_machine_dependent, 8 - off, 0,
-			    RELAX_ENCODE (0, 8 - off, -4 - off, 4 - off, 0,
+
+	      p = frag_var (rs_machine_dependent, 12, 0,
+			    RELAX_ENCODE (4 + off, 12, 0, 8, 0,
 					  (breg == 0
 					   ? mips_opts.warn_about_macros
 					   : 0)),
 			    offset_expr.X_add_symbol, 0, NULL);
-	      if (breg == 0)
-		{
-		  macro_build (p, &icnt, NULL, "nop", "");
-		  p += 4;
-		}
+	      macro_build (p, &icnt, &offset_expr, ADDRESS_LOAD_INSN, "t,o(b)",
+			   tempreg, BFD_RELOC_MIPS_GOT16, mips_gp_register);
+	      p += 4;
+	      macro_build (p, &icnt, NULL, "nop", "");
+	      p += 4;
 	      macro_build (p, &icnt, &expr1, ADDRESS_ADDI_INSN,
 			   "t,r,j", tempreg, tempreg, BFD_RELOC_LO16);
-	      /* FIXME: If breg == 0, and the next instruction uses
-		 $tempreg, then if this variant case is used an extra
-		 nop will be generated.  */
 	    }
 	  else if (expr1.X_add_number >= -0x8000
 		   && expr1.X_add_number < 0x8000)
 	    {
+	      char *p;
+
 	      macro_build (NULL, &icnt, NULL, "nop", "");
 	      macro_build (NULL, &icnt, &expr1, ADDRESS_ADDI_INSN,
 			   "t,r,j", tempreg, tempreg, BFD_RELOC_LO16);
-	      frag_var (rs_machine_dependent, 0, 0,
-			RELAX_ENCODE (0, 0, -12, -4, 0, 0),
-			offset_expr.X_add_symbol, 0, NULL);
+	      p = frag_var (rs_machine_dependent, 12, 0,
+			    RELAX_ENCODE (12, 12, 0, 8, 0, 0),
+			    offset_expr.X_add_symbol, 0, NULL);
+	      macro_build (p, &icnt, &offset_expr, ADDRESS_LOAD_INSN, "t,o(b)",
+			   tempreg, BFD_RELOC_MIPS_GOT16, mips_gp_register);
+	      p += 4;
+	      macro_build (p, &icnt, NULL, "nop", "");
+	      p += 4;
+	      macro_build (p, &icnt, &expr1, ADDRESS_ADDI_INSN, "t,r,j",
+			   tempreg, tempreg, BFD_RELOC_LO16);
 	    }
 	  else
 	    {
 	      int off1;
+	      char *p;
 
 	      /* If we are going to add in a base register, and the
 		 target register and the base register are the same,
@@ -4897,9 +4936,8 @@ macro (struct mips_cl_insn *ip)
 		  macro_build (NULL, &icnt, NULL, "nop", "");
 		  macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
 			       treg, AT, breg);
-		  breg = 0;
 		  tempreg = treg;
-		  off1 = -8;
+		  off1 = 8;
 		}
 
 	      /* Set mips_optimize around the lui instruction to avoid
@@ -4913,9 +4951,46 @@ macro (struct mips_cl_insn *ip)
 			   AT, AT, BFD_RELOC_LO16);
 	      macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
 			   tempreg, tempreg, AT);
-	      frag_var (rs_machine_dependent, 0, 0,
-			RELAX_ENCODE (0, 0, -16 + off1, -8, 0, 0),
-			offset_expr.X_add_symbol, 0, NULL);
+
+	      p = frag_var (rs_machine_dependent, 16 + off1, 0,
+			    RELAX_ENCODE (16 + off1, 16 + off1, 0, 8 + off1,
+					  0, 0),
+			    offset_expr.X_add_symbol, 0, NULL);
+	      macro_build (p, &icnt, &offset_expr, ADDRESS_LOAD_INSN, "t,o(b)",
+			   tempreg, BFD_RELOC_MIPS_GOT16, mips_gp_register);
+	      p += 4;
+	      /* If we are going to add in a base register, and the
+		 target register and the base register are the same,
+		 then we are using AT as a temporary register.  Since
+		 we want to load the constant into AT, we add our
+		 current AT (from the global offset table) and the
+		 register into the register now, and pretend we were
+		 not using a base register.  */
+	      if (breg == treg)
+		{
+		  macro_build (p, &icnt, NULL, "nop", "");
+		  p += 4;
+		  macro_build (p, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
+			       treg, AT, breg);
+		  p += 4;
+		}
+
+	      /* Set mips_optimize around the lui instruction to avoid
+		 inserting an unnecessary nop after the lw.  */
+	      hold_mips_optimize = mips_optimize;
+	      mips_optimize = 2;
+	      macro_build_lui (p, &icnt, &expr1, AT);
+	      p += 4;
+	      mips_optimize = hold_mips_optimize;
+
+
+	      macro_build (p, &icnt, &expr1, ADDRESS_ADDI_INSN, "t,r,j",
+			   AT, AT, BFD_RELOC_LO16);
+	      p += 4;
+	      macro_build (p, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
+			   tempreg, tempreg, AT);
+	      if (breg == treg)
+		breg = 0;
 	      used_at = 1;
 	    }
 	}
@@ -4925,17 +5000,22 @@ macro (struct mips_cl_insn *ip)
 	  int lw_reloc_type = (int) BFD_RELOC_MIPS_GOT_DISP;
 	  int adj = 0;
 
-	  /* If this is a reference to an external, and there is no
-	     constant, or local symbol (*), with or without a
+	  /* If this is a reference to an external symbol, and there
+	     is no constant, or local symbol (*), with or without a
 	     constant, we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT_DISP)
 	     or if tempreg is PIC_CALL_REG
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_CALL16)
+	     For external symbols BFD_RELOC_MIPS_CALL16 or
+	     BFD_RELOC_MIPS_GOT_DISP may be used instead if requested
+	     with %call_r or %got_r, respectively.
 
 	     If we have a small constant, and this is a reference to
 	     an external symbol, we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT_DISP)
 	       addiu	$tempreg,$tempreg,<constant>
+	     BFD_RELOC_MIPS_CALL16 may be used instead if requested
+	     with %call_r.
 
 	     If we have a large constant, and this is a reference to
 	     an external symbol, we want
@@ -4943,6 +5023,8 @@ macro (struct mips_cl_insn *ip)
 	       lui	$at,<hiconstant>
 	       addiu	$at,$at,<loconstant>
 	       addu	$tempreg,$tempreg,$at
+	     BFD_RELOC_MIPS_CALL16 may be used instead if requested
+	     with %call_r.
 
 	     (*) Other assemblers seem to prefer GOT_PAGE/GOT_OFST for
 	     local symbols, even though it introduces an additional
@@ -4950,7 +5032,11 @@ macro (struct mips_cl_insn *ip)
 
 	  frag_grow (28);
 	  if (offset_expr.X_add_number == 0 && tempreg == PIC_CALL_REG)
-	    lw_reloc_type = (int) BFD_RELOC_MIPS_CALL16;
+	    lw_reloc_type = BFD_RELOC_MIPS_CALL16;
+	  if (*offset_reloc == BFD_RELOC_MIPS_GOT16)
+	    lw_reloc_type = BFD_RELOC_MIPS_GOT_DISP;
+	  if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    lw_reloc_type = *offset_reloc;
 	  if (offset_expr.X_add_number)
 	    {
 	      frag_now->tc_frag_data.tc_fr_offset =
@@ -5057,7 +5143,10 @@ macro (struct mips_cl_insn *ip)
 	       lui	$tempreg,<sym>		(BFD_RELOC_MIPS_CALL_HI16)
 	       addu	$tempreg,$tempreg,$gp
 	       lw	$tempreg,<sym>($tempreg) (BFD_RELOC_MIPS_CALL_LO16)
-	     For a local symbol, we want
+	     BFD_RELOC_MIPS_CALL_HI16 and BFD_RELOC_MIPS_CALL_LO16 or
+	     BFD_RELOC_MIPS_GOT_HI16 and BFD_RELOC_MIPS_GOT_LO16 may
+	     be used instead if requested with %call_r or %got_r,
+	     respectively.  For a local symbol, we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       addiu	$tempreg,$tempreg,<sym>	(BFD_RELOC_LO16)
@@ -5069,7 +5158,9 @@ macro (struct mips_cl_insn *ip)
 	       lw	$tempreg,<sym>($tempreg) (BFD_RELOC_MIPS_GOT_LO16)
 	       nop
 	       addiu	$tempreg,$tempreg,<constant>
-	     For a local symbol, we want
+	     BFD_RELOC_MIPS_CALL_HI16 and BFD_RELOC_MIPS_CALL_LO16 may
+	     be used instead if requested with %call_r.  For a local
+	     symbol, we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       addiu	$tempreg,$tempreg,<constant> (BFD_RELOC_LO16)
@@ -5082,7 +5173,9 @@ macro (struct mips_cl_insn *ip)
 	       lui	$at,<hiconstant>
 	       addiu	$at,$at,<loconstant>
 	       addu	$tempreg,$tempreg,$at
-	     For a local symbol, we want
+	     BFD_RELOC_MIPS_CALL_HI16 and BFD_RELOC_MIPS_CALL_LO16 may
+	     be used instead if requested with %call_r.  For a local
+	     symbol, we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       lui	$at,<hiconstant>
 	       addiu	$at,$at,<loconstant>	(BFD_RELOC_LO16)
@@ -5101,6 +5194,16 @@ macro (struct mips_cl_insn *ip)
 	      lui_reloc_type = (int) BFD_RELOC_MIPS_CALL_HI16;
 	      lw_reloc_type = (int) BFD_RELOC_MIPS_CALL_LO16;
 	    }
+	  if (*offset_reloc == BFD_RELOC_MIPS_GOT16)
+	    {
+	      lui_reloc_type = BFD_RELOC_MIPS_GOT_HI16;
+	      lw_reloc_type = BFD_RELOC_MIPS_GOT_LO16;
+	    }
+	  if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    {
+	      lui_reloc_type = BFD_RELOC_MIPS_CALL_HI16;
+	      lw_reloc_type = BFD_RELOC_MIPS_CALL_LO16;
+	    }
 	  macro_build (NULL, &icnt, &offset_expr, "lui", "t,u",
 		       tempreg, lui_reloc_type);
 	  macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
@@ -5261,6 +5364,10 @@ macro (struct mips_cl_insn *ip)
 	       lui	$tempreg,<sym>		(BFD_RELOC_MIPS_CALL_HI16)
 	       add	$tempreg,$tempreg,$gp
 	       lw	$tempreg,<sym>($tempreg) (BFD_RELOC_MIPS_CALL_LO16)
+	     BFD_RELOC_MIPS_CALL_HI16 and BFD_RELOC_MIPS_CALL_LO16 or
+	     BFD_RELOC_MIPS_GOT_HI16 and BFD_RELOC_MIPS_GOT_LO16 may
+	     be used instead if requested with %call_r or %got_r,
+	     respectively.
 
 	     If we have a small constant, and this is a reference to
 	     an external symbol, we want
@@ -5268,6 +5375,8 @@ macro (struct mips_cl_insn *ip)
 	       add	$tempreg,$tempreg,$gp
 	       lw	$tempreg,<sym>($tempreg) (BFD_RELOC_MIPS_GOT_LO16)
 	       addi	$tempreg,$tempreg,<constant>
+	     BFD_RELOC_MIPS_CALL_HI16 and BFD_RELOC_MIPS_CALL_LO16 may
+	     be used instead if requested with %call_r.
 
 	     If we have a large constant, and this is a reference to
 	     an external symbol, we want
@@ -5277,6 +5386,8 @@ macro (struct mips_cl_insn *ip)
 	       lui	$at,<hiconstant>
 	       addi	$at,$at,<loconstant>
 	       add	$tempreg,$tempreg,$at
+	     BFD_RELOC_MIPS_CALL_HI16 and BFD_RELOC_MIPS_CALL_LO16 may
+	     be used instead if requested with %call_r.
 
 	     If we have NewABI, and we know it's a local symbol, we want
 	       lw	$reg,<sym>($gp)		(BFD_RELOC_MIPS_GOT_PAGE)
@@ -5291,8 +5402,18 @@ macro (struct mips_cl_insn *ip)
 
 	  if (expr1.X_add_number == 0 && tempreg == PIC_CALL_REG)
 	    {
-	      lui_reloc_type = (int) BFD_RELOC_MIPS_CALL_HI16;
-	      lw_reloc_type = (int) BFD_RELOC_MIPS_CALL_LO16;
+	      lui_reloc_type = BFD_RELOC_MIPS_CALL_HI16;
+	      lw_reloc_type = BFD_RELOC_MIPS_CALL_LO16;
+	    }
+	  if (*offset_reloc == BFD_RELOC_MIPS_GOT16)
+	    {
+	      lui_reloc_type = BFD_RELOC_MIPS_GOT_HI16;
+	      lw_reloc_type = BFD_RELOC_MIPS_GOT_LO16;
+	    }
+	  if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    {
+	      lui_reloc_type = BFD_RELOC_MIPS_CALL_HI16;
+	      lw_reloc_type = BFD_RELOC_MIPS_CALL_LO16;
 	    }
 	  macro_build (NULL, &icnt, &offset_expr, "lui", "t,u",
 		       tempreg, lui_reloc_type);
@@ -5473,8 +5594,9 @@ macro (struct mips_cl_insn *ip)
 	       jalr	$ra,$25
 	       nop
 	       lw	$gp,cprestore($sp)
-	     The cprestore value is set using the .cprestore
-	     pseudo-op.  If we are using a big GOT, we want
+	     BFD_RELOC_MIPS_GOT16 may be used instead if requested
+	     with %got_r.  The cprestore value is set using the
+	     .cprestore pseudo-op.  If we are using a big GOT, we want
 	       lui	$25,<sym>		(BFD_RELOC_MIPS_CALL_HI16)
 	       addu	$25,$25,$gp
 	       lw	$25,<sym>($25)		(BFD_RELOC_MIPS_CALL_LO16)
@@ -5482,7 +5604,9 @@ macro (struct mips_cl_insn *ip)
 	       jalr	$ra,$25
 	       nop
 	       lw	$gp,cprestore($sp)
-	     If the symbol is not external, we want
+	     BFD_RELOC_MIPS_GOT_HI16 and BFD_RELOC_MIPS_GOT_LO16 may
+	     be used instead if requested with %got_r.  If the symbol
+	     is not external, we want
 	       lw	$25,<sym>($gp)		(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       addiu	$25,$25,<sym>		(BFD_RELOC_LO16)
@@ -5498,9 +5622,13 @@ macro (struct mips_cl_insn *ip)
 	    {
 	      if (! mips_big_got)
 		{
+		  int lw_reloc_type = BFD_RELOC_MIPS_CALL16;
+
+		  if (*offset_reloc == BFD_RELOC_MIPS_GOT16)
+		    lw_reloc_type = BFD_RELOC_MIPS_GOT_DISP;
 		  frag_grow (4);
 		  macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN,
-			       "t,o(b)", PIC_CALL_REG, BFD_RELOC_MIPS_CALL16,
+			       "t,o(b)", PIC_CALL_REG, lw_reloc_type,
 			       mips_gp_register);
 		  frag_var (rs_machine_dependent, 0, 0,
 			    RELAX_ENCODE (0, 0, -4, 0, 0, 0),
@@ -5508,14 +5636,22 @@ macro (struct mips_cl_insn *ip)
 		}
 	      else
 		{
+		  int lui_reloc_type = BFD_RELOC_MIPS_CALL_HI16;
+		  int lw_reloc_type = BFD_RELOC_MIPS_CALL_LO16;
+
+		  if (*offset_reloc == BFD_RELOC_MIPS_GOT16)
+		    {
+		      lui_reloc_type = BFD_RELOC_MIPS_GOT_HI16;
+		      lw_reloc_type = BFD_RELOC_MIPS_GOT_LO16;
+		    }
 		  frag_grow (20);
 		  macro_build (NULL, &icnt, &offset_expr, "lui", "t,u",
-			       PIC_CALL_REG, BFD_RELOC_MIPS_CALL_HI16);
+			       PIC_CALL_REG, lui_reloc_type);
 		  macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
 			       PIC_CALL_REG, PIC_CALL_REG, mips_gp_register);
 		  macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN,
 			       "t,o(b)", PIC_CALL_REG,
-			       BFD_RELOC_MIPS_CALL_LO16, PIC_CALL_REG);
+			       lw_reloc_type, PIC_CALL_REG);
 		  p = frag_var (rs_machine_dependent, 8, 0,
 				RELAX_ENCODE (12, 8, 0, 4, 0, 0),
 				offset_expr.X_add_symbol, 0, NULL);
@@ -5534,8 +5670,12 @@ macro (struct mips_cl_insn *ip)
 	      frag_grow (40);
 	      if (! mips_big_got)
 		{
+		  int lw_reloc_type = BFD_RELOC_MIPS_CALL16;
+
+		  if (*offset_reloc == BFD_RELOC_MIPS_GOT16)
+		    lw_reloc_type = BFD_RELOC_MIPS_GOT16;
 		  macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN,
-			       "t,o(b)", PIC_CALL_REG, BFD_RELOC_MIPS_CALL16,
+			       "t,o(b)", PIC_CALL_REG, lw_reloc_type,
 			       mips_gp_register);
 		  macro_build (NULL, &icnt, NULL, "nop", "");
 		  p = frag_var (rs_machine_dependent, 4, 0,
@@ -5544,19 +5684,26 @@ macro (struct mips_cl_insn *ip)
 		}
 	      else
 		{
+		  int lui_reloc_type = BFD_RELOC_MIPS_CALL_HI16;
+		  int lw_reloc_type = BFD_RELOC_MIPS_CALL_LO16;
 		  int gpdel;
 
 		  if (reg_needs_delay (mips_gp_register))
 		    gpdel = 4;
 		  else
 		    gpdel = 0;
+		  if (*offset_reloc == BFD_RELOC_MIPS_GOT16)
+		    {
+		      lui_reloc_type = BFD_RELOC_MIPS_GOT_HI16;
+		      lw_reloc_type = BFD_RELOC_MIPS_GOT_LO16;
+		    }
 		  macro_build (NULL, &icnt, &offset_expr, "lui", "t,u",
-			       PIC_CALL_REG, BFD_RELOC_MIPS_CALL_HI16);
+			       PIC_CALL_REG, lui_reloc_type);
 		  macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
 			       PIC_CALL_REG, PIC_CALL_REG, mips_gp_register);
 		  macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN,
 			       "t,o(b)", PIC_CALL_REG,
-			       BFD_RELOC_MIPS_CALL_LO16, PIC_CALL_REG);
+			       lw_reloc_type, PIC_CALL_REG);
 		  macro_build (NULL, &icnt, NULL, "nop", "");
 		  p = frag_var (rs_machine_dependent, 12 + gpdel, 0,
 				RELAX_ENCODE (16, 12 + gpdel, gpdel,
@@ -6038,7 +6185,8 @@ macro (struct mips_cl_insn *ip)
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       <op>	$treg,0($tempreg)
-	     Otherwise we want
+	     BFD_RELOC_MIPS_CALL16 may be used if requested with
+	     %call_r.  Otherwise we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       addiu	$tempreg,$tempreg,<sym>	(BFD_RELOC_LO16)
@@ -6057,15 +6205,59 @@ macro (struct mips_cl_insn *ip)
 	  assert (offset_expr.X_op == O_symbol);
 	  if (HAVE_NEWABI)
 	    {
-	      macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN,
-			   "t,o(b)", tempreg, BFD_RELOC_MIPS_GOT_PAGE,
-			   mips_gp_register);
-	      if (breg != 0)
-		macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
-			     tempreg, tempreg, breg);
-	      macro_build (NULL, &icnt, &offset_expr, s, fmt, treg,
-			   BFD_RELOC_MIPS_GOT_OFST, tempreg);
+	      if (*offset_reloc != BFD_RELOC_MIPS_CALL16)
+		{
+		  macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN,
+			       "t,o(b)", tempreg, BFD_RELOC_MIPS_GOT_PAGE,
+			       mips_gp_register);
+		  if (breg != 0)
+		    macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
+				 tempreg, tempreg, breg);
+		  macro_build (NULL, &icnt, &offset_expr, s, fmt,
+			       treg, BFD_RELOC_MIPS_GOT_OFST, tempreg);
+    
+		}
+	      else
+		{
+		  int off = 0;
 
+		  expr1.X_add_number = offset_expr.X_add_number;
+		  offset_expr.X_add_number = 0;
+		  if (expr1.X_add_number < -0x8000
+		      || expr1.X_add_number >= 0x8000)
+		    as_bad (_("PIC code offset overflow (max 16 signed bits)"));
+		  if (breg != 0)
+		    off = 4;
+		  frag_grow (32);
+		  macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN,
+			       "t,o(b)", tempreg, BFD_RELOC_MIPS_CALL16,
+			       mips_gp_register);
+		  macro_build (NULL, &icnt, NULL, "nop", "");
+		  if (breg != 0)
+		    macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
+				 tempreg, tempreg, breg);
+		  macro_build (NULL, &icnt, &expr1, s, fmt, treg,
+			       BFD_RELOC_LO16, tempreg);
+		  p = frag_var (rs_machine_dependent, 12 + off, 0,
+				RELAX_ENCODE (12 + off, 12 + off, 0, 8 + off,
+					      0, 0),
+				offset_expr.X_add_symbol, 0, NULL);
+		  offset_expr.X_add_number = expr1.X_add_number;
+		  macro_build (p, &icnt, &offset_expr, ADDRESS_LOAD_INSN,
+			       "t,o(b)", tempreg, BFD_RELOC_MIPS_GOT_DISP,
+			       mips_gp_register);
+		  p += 4;
+		  macro_build (p, &icnt, NULL, "nop", "");
+		  if (breg != 0)
+		    {
+		      p += 4;
+		      macro_build (p, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
+				   tempreg, tempreg, breg);
+		    }
+		  p += 4;
+		  macro_build (p, &icnt, &offset_expr, s, fmt, treg,
+			       BFD_RELOC_LO16, tempreg);
+		}
 	      if (! used_at)
 		return;
 
@@ -6076,13 +6268,20 @@ macro (struct mips_cl_insn *ip)
 	  if (expr1.X_add_number < -0x8000
 	      || expr1.X_add_number >= 0x8000)
 	    as_bad (_("PIC code offset overflow (max 16 signed bits)"));
+	  if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    lw_reloc_type = *offset_reloc;
 	  frag_grow (20);
 	  macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN, "t,o(b)",
 		       tempreg, lw_reloc_type, mips_gp_register);
 	  macro_build (NULL, &icnt, NULL, "nop", "");
-	  p = frag_var (rs_machine_dependent, 4, 0,
-			RELAX_ENCODE (0, 4, -8, 0, 0, 0),
+	  p = frag_var (rs_machine_dependent, 12, 0,
+			RELAX_ENCODE (8, 12, 0, 8, 0, 0),
 			offset_expr.X_add_symbol, 0, NULL);
+	  macro_build (p, &icnt, &offset_expr, ADDRESS_LOAD_INSN, "t,o(b)",
+		       tempreg, BFD_RELOC_MIPS_GOT16, mips_gp_register);
+	  p += 4;
+	  macro_build (p, &icnt, NULL, "nop", "");
+	  p += 4;
 	  macro_build (p, &icnt, &offset_expr, ADDRESS_ADDI_INSN,
 		       "t,r,j", tempreg, tempreg, BFD_RELOC_LO16);
 	  if (breg != 0)
@@ -6093,6 +6292,8 @@ macro (struct mips_cl_insn *ip)
 	}
       else if (mips_pic == SVR4_PIC && ! HAVE_NEWABI)
 	{
+	  int lui_reloc_type = BFD_RELOC_MIPS_GOT_HI16;
+	  int lw_reloc_type = BFD_RELOC_MIPS_GOT_LO16;
 	  int gpdel;
 	  char *p;
 
@@ -6101,7 +6302,9 @@ macro (struct mips_cl_insn *ip)
 	       addu	$tempreg,$tempreg,$gp
 	       lw	$tempreg,<sym>($tempreg) (BFD_RELOC_MIPS_GOT_LO16)
 	       <op>	$treg,0($tempreg)
-	     Otherwise we want
+	     BFD_RELOC_MIPS_CALL_HI16 and BFD_RELOC_MIPS_CALL_LO16 may
+	     be used instead if requested with %call_r.  Otherwise we
+	     want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       addiu	$tempreg,$tempreg,<sym>	(BFD_RELOC_LO16)
@@ -6122,13 +6325,18 @@ macro (struct mips_cl_insn *ip)
 	    gpdel = 4;
 	  else
 	    gpdel = 0;
+	  if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    {
+	      lui_reloc_type = BFD_RELOC_MIPS_CALL_HI16;
+	      lw_reloc_type = BFD_RELOC_MIPS_CALL_LO16;
+	    }
 	  frag_grow (36);
 	  macro_build (NULL, &icnt, &offset_expr, "lui", "t,u", tempreg,
-		       BFD_RELOC_MIPS_GOT_HI16);
+		       lui_reloc_type);
 	  macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
 		       tempreg, tempreg, mips_gp_register);
 	  macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN, "t,o(b)",
-		       tempreg, BFD_RELOC_MIPS_GOT_LO16, tempreg);
+		       tempreg, lw_reloc_type, tempreg);
 	  p = frag_var (rs_machine_dependent, 12 + gpdel, 0,
 			RELAX_ENCODE (12, 12 + gpdel, gpdel, 8 + gpdel, 0, 0),
 			offset_expr.X_add_symbol, 0, NULL);
@@ -6152,6 +6360,8 @@ macro (struct mips_cl_insn *ip)
 	}
       else if (mips_pic == SVR4_PIC && HAVE_NEWABI)
 	{
+	  int lui_reloc_type = BFD_RELOC_MIPS_GOT_HI16;
+	  int lw_reloc_type = BFD_RELOC_MIPS_GOT_LO16;
 	  char *p;
 	  int bregsz = breg != 0 ? 4 : 0;
 
@@ -6160,7 +6370,9 @@ macro (struct mips_cl_insn *ip)
 	       add	$tempreg,$tempreg,$gp
 	       lw	$tempreg,<sym>($tempreg) (BFD_RELOC_MIPS_GOT_LO16)
 	       <op>	$treg,<ofst>($tempreg)
-	     Otherwise, for local symbols, we want:
+	     BFD_RELOC_MIPS_CALL_HI16 and BFD_RELOC_MIPS_CALL_LO16 may
+	     be used instead if requested with %call_r.  Otherwise,
+	     for local symbols, we want:
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT_PAGE)
 	       <op>	$treg,<sym>($tempreg)   (BFD_RELOC_MIPS_GOT_OFST)  */
 	  assert (offset_expr.X_op == O_symbol);
@@ -6170,13 +6382,18 @@ macro (struct mips_cl_insn *ip)
 	  if (expr1.X_add_number < -0x8000
 	      || expr1.X_add_number >= 0x8000)
 	    as_bad (_("PIC code offset overflow (max 16 signed bits)"));
+	  if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    {
+	      lui_reloc_type = BFD_RELOC_MIPS_CALL_HI16;
+	      lw_reloc_type = BFD_RELOC_MIPS_CALL_LO16;
+	    }
 	  frag_grow (36);
 	  macro_build (NULL, &icnt, &offset_expr, "lui", "t,u", tempreg,
-		       BFD_RELOC_MIPS_GOT_HI16);
+		       lui_reloc_type);
 	  macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
 		       tempreg, tempreg, mips_gp_register);
 	  macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN, "t,o(b)",
-		       tempreg, BFD_RELOC_MIPS_GOT_LO16, tempreg);
+		       tempreg, lw_reloc_type, tempreg);
 	  if (breg != 0)
 	    macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
 			 tempreg, tempreg, breg);
@@ -6647,14 +6864,17 @@ macro (struct mips_cl_insn *ip)
 	}
       else if (mips_pic == SVR4_PIC && ! mips_big_got)
 	{
+	  int lw_reloc_type = BFD_RELOC_MIPS_GOT16;
 	  int off;
+	  char *p;
 
 	  /* If this is a reference to an external symbol, we want
 	       lw	$at,<sym>($gp)		(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       <op>	$treg,0($at)
 	       <op>	$treg+1,4($at)
-	     Otherwise we want
+	     BFD_RELOC_MIPS_CALL16 may be used if requested with
+	     %call_r.  Otherwise we want
 	       lw	$at,<sym>($gp)		(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       <op>	$treg,<sym>($at)	(BFD_RELOC_LO16)
@@ -6672,9 +6892,11 @@ macro (struct mips_cl_insn *ip)
 	    off = 0;
 	  else
 	    off = 4;
+	  if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    lw_reloc_type = *offset_reloc;
 	  frag_grow (24 + off);
 	  macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN, "t,o(b)",
-		       AT, BFD_RELOC_MIPS_GOT16, mips_gp_register);
+		       AT, lw_reloc_type, mips_gp_register);
 	  macro_build (NULL, &icnt, NULL, "nop", "");
 	  if (breg != 0)
 	    macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
@@ -6692,13 +6914,41 @@ macro (struct mips_cl_insn *ip)
 	  macro_build (NULL, &icnt, &expr1, s, fmt, coproc ? treg : treg + 1,
 		       BFD_RELOC_LO16, AT);
 	  mips_optimize = hold_mips_optimize;
+	  expr1.X_add_number -= 4;
 
-	  (void) frag_var (rs_machine_dependent, 0, 0,
-			   RELAX_ENCODE (0, 0, -16 - off, -8, 1, 0),
-			   offset_expr.X_add_symbol, 0, NULL);
+	  p = frag_var (rs_machine_dependent, 16 + off, 0,
+			RELAX_ENCODE (16 + off, 16 + off, 0, 8 + off, 1, 0),
+			offset_expr.X_add_symbol, 0, NULL);
+	  macro_build (p, &icnt, &offset_expr, ADDRESS_LOAD_INSN, "t,o(b)",
+		       AT, BFD_RELOC_MIPS_GOT16, mips_gp_register);
+	  p += 4;
+	  macro_build (p, &icnt, NULL, "nop", "");
+	  p += 4;
+	  if (breg != 0)
+	    {
+	      macro_build (p, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
+			   AT, breg, AT);
+	      p += 4;
+	    }
+	  /* Itbl support may require additional care here.  */
+	  macro_build (p, &icnt, &expr1, s, fmt, coproc ? treg + 1 : treg,
+		       BFD_RELOC_LO16, AT);
+	  p += 4;
+	  expr1.X_add_number += 4;
+
+	  /* Set mips_optimize to 2 to avoid inserting an undesired
+	      nop.  */
+	  hold_mips_optimize = mips_optimize;
+	  mips_optimize = 2;
+	  /* Itbl support may require additional care here.  */
+	  macro_build (p, &icnt, &expr1, s, fmt, coproc ? treg : treg + 1,
+		       BFD_RELOC_LO16, AT);
+	  mips_optimize = hold_mips_optimize;
 	}
       else if (mips_pic == SVR4_PIC)
 	{
+	  int lui_reloc_type = BFD_RELOC_MIPS_GOT_HI16;
+	  int lw_reloc_type = BFD_RELOC_MIPS_GOT_LO16;
 	  int gpdel, off;
 	  char *p;
 
@@ -6709,7 +6959,9 @@ macro (struct mips_cl_insn *ip)
 	       nop
 	       <op>	$treg,0($at)
 	       <op>	$treg+1,4($at)
-	     Otherwise we want
+	     BFD_RELOC_MIPS_CALL_HI16 and BFD_RELOC_MIPS_CALL_LO16 may
+	     be used instead if requested with %call_r.  Otherwise we
+	     want
 	       lw	$at,<sym>($gp)		(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       <op>	$treg,<sym>($at)	(BFD_RELOC_LO16)
@@ -6731,13 +6983,18 @@ macro (struct mips_cl_insn *ip)
 	    off = 0;
 	  else
 	    off = 4;
+	  if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    {
+	      lui_reloc_type = BFD_RELOC_MIPS_CALL_HI16;
+	      lw_reloc_type = BFD_RELOC_MIPS_CALL_LO16;
+	    }
 	  frag_grow (56);
 	  macro_build (NULL, &icnt, &offset_expr, "lui", "t,u", AT,
-		       BFD_RELOC_MIPS_GOT_HI16);
+		       lui_reloc_type);
 	  macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
 		       AT, AT, mips_gp_register);
 	  macro_build (NULL, &icnt, &offset_expr, ADDRESS_LOAD_INSN, "t,o(b)",
-		       AT, BFD_RELOC_MIPS_GOT_LO16, AT);
+		       AT, lw_reloc_type, AT);
 	  macro_build (NULL, &icnt, NULL, "nop", "");
 	  if (breg != 0)
 	    macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
@@ -7617,7 +7874,7 @@ macro2 (struct mips_cl_insn *ip)
       off = 3;
     ulwa:
       used_at = 1;
-      load_address (&icnt, AT, &offset_expr, &used_at);
+      load_address (&icnt, AT, &offset_expr, offset_reloc, &used_at);
       if (breg != 0)
 	macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
 		     AT, AT, breg);
@@ -7638,7 +7895,7 @@ macro2 (struct mips_cl_insn *ip)
     case M_ULH_A:
     case M_ULHU_A:
       used_at = 1;
-      load_address (&icnt, AT, &offset_expr, &used_at);
+      load_address (&icnt, AT, &offset_expr, offset_reloc, &used_at);
       if (breg != 0)
 	macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
 		     AT, AT, breg);
@@ -7708,7 +7965,7 @@ macro2 (struct mips_cl_insn *ip)
       off = 3;
     uswa:
       used_at = 1;
-      load_address (&icnt, AT, &offset_expr, &used_at);
+      load_address (&icnt, AT, &offset_expr, offset_reloc, &used_at);
       if (breg != 0)
 	macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
 		     AT, AT, breg);
@@ -7728,7 +7985,7 @@ macro2 (struct mips_cl_insn *ip)
 
     case M_USH_A:
       used_at = 1;
-      load_address (&icnt, AT, &offset_expr, &used_at);
+      load_address (&icnt, AT, &offset_expr, offset_reloc, &used_at);
       if (breg != 0)
 	macro_build (NULL, &icnt, NULL, ADDRESS_ADD_INSN, "d,v,t",
 		     AT, AT, breg);
@@ -8855,8 +9112,8 @@ mips_ip (char *str, struct mips_cl_insn 
 	      continue;
 
 	    case 'A':
-	      my_getExpression (&offset_expr, s);
-	      *imm_reloc = BFD_RELOC_32;
+	      *offset_reloc = BFD_RELOC_32;
+	      my_getSpecialExpression (&offset_expr, offset_reloc, s);
 	      s = expr_end;
 	      continue;
 
@@ -9152,9 +9409,9 @@ mips_ip (char *str, struct mips_cl_insn 
 	      continue;
 
 	    case 'a':		/* 26 bit address */
-	      my_getExpression (&offset_expr, s);
-	      s = expr_end;
 	      *offset_reloc = BFD_RELOC_MIPS_JMP;
+	      my_getSpecialExpression (&offset_expr, offset_reloc, s);
+	      s = expr_end;
 	      continue;
 
 	    case 'N':		/* 3 bit branch condition code */
@@ -10029,6 +10286,42 @@ parse_relocation (char **str, bfd_reloc_
 }
 
 
+#ifdef OBJ_ELF
+static const struct percent_special_match
+{
+  const char *str;
+  bfd_reloc_code_real_type reloc;
+} percent_special[] =
+{
+  {"%call_r", BFD_RELOC_MIPS_CALL16},
+  {"%got_r", BFD_RELOC_MIPS_GOT16},
+};
+#endif
+
+/* Return true if *STR points to a special operator.  When returning
+   true, move *STR over the operator and store its code in *SPECIAL.
+   Leave both *STR and *SPECIAL alone when returning false.  */
+
+static bfd_boolean
+parse_special (char **str, bfd_reloc_code_real_type *reloc)
+{
+#ifdef OBJ_ELF
+  size_t i;
+
+  for (i = 0; i < ARRAY_SIZE (percent_special); i++)
+    if (strncasecmp (*str, percent_special[i].str,
+		     strlen (percent_special[i].str)) == 0)
+      {
+	*str += strlen (percent_special[i].str);
+	*reloc = percent_special[i].reloc;
+
+	return TRUE;
+      }
+#endif
+  return FALSE;
+}
+
+
 /* Parse string STR as a 16-bit relocatable operand.  Store the
    expression in *EP and the relocations in the array starting
    at RELOC.  Return the number of relocation operators used.
@@ -10062,6 +10355,13 @@ my_getSmallExpression (expressionS *ep, 
       while (*str == ' ' || *str == '\t' || *str == '(')
 	if (*str++ == '(')
 	  str_depth++;
+      if (reloc_index == 0
+	  && parse_special (&str, &reversed_reloc[reloc_index]))
+	{
+	  crux = str;
+	  crux_depth = str_depth;
+	  break;
+	}
     }
   while (*str == '%'
 	 && reloc_index < (HAVE_NEWABI ? 3 : 1)
@@ -10092,6 +10392,56 @@ my_getSmallExpression (expressionS *ep, 
   return reloc_index;
 }
 
+
+/* Parse string STR as an operand with a single optional special percent
+   operator.  Store the expression in *EP and the relocation hint
+   resulting from the operator (if any) in *RELOC.
+
+   On exit, EXPR_END points to the first character after the expression.
+   If no special percent operator is used, *RELOC is unchanged.  */
+
+static void
+my_getSpecialExpression (expressionS *ep, bfd_reloc_code_real_type *reloc,
+			 char *str)
+{
+  int crux_depth, str_depth;
+  char *crux;
+
+  /* Search for the start of the main expression, interpreting a single
+     operator.  End the loop with CRUX pointing to the start of the main
+     expression and with CRUX_DEPTH containing the number of open
+     brackets at that point.  */
+  str_depth = 0;
+  crux = str;
+  crux_depth = str_depth;
+
+  /* Skip over whitespace and brackets, keeping count of the number of
+     brackets.  */
+  while (*str == ' ' || *str == '\t' || *str == '(')
+    if (*str++ == '(')
+      str_depth++;
+
+  if (*str == '%' && parse_special (&str, reloc))
+    {
+      crux = str;
+      crux_depth = str_depth;
+    }
+
+  my_getExpression (ep, crux);
+  str = expr_end;
+
+  /* Match every open bracket.  */
+  while (crux_depth > 0 && (*str == ')' || *str == ' ' || *str == '\t'))
+    if (*str++ == ')')
+      crux_depth--;
+
+  if (crux_depth > 0)
+    as_bad ("unclosed '('");
+
+  expr_end = str;
+}
+
+
 static void
 my_getExpression (expressionS *ep, char *str)
 {


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]