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]

Re: MIPS patch to avoid lazy binding in la macros


Hi Richard,

> I'm standing by the patch since I believe it does fix a real
> assembler bug.  However, if you still think it should be
> reverted after reading this message, I'll do so.

 Well, it's a "bug by design" or IOW a feature.  Gccs used not to emit
code sequences that would end up with an incorrect relocation.  If that
changed meanwhile, blame for gcc for not accounting for the behaviour of
gas.  The code is there since around 2.10, IIRC.

> >  Essentially you kill lazy binding for code as currently emitted by gcc
> > entirely this way.
> 
> True.  I'm getting used to mips-rewrite, so I forgot what older gccs
> (including trunk ;) do.  But more below.

 So what does exactly the branch do to select between GOT and CALL
relocations?

> > And I don't think you have to.  While you have
> > incorrect CALL relocations in the object file, indeed, they will be
> > converted to regular GOT references upon the final link as the function
> > will not get a stub (as this is an undefined weak symbol as I understand;
> > otherwise it wouldn't be zero or there would be other relocations related
> > to taking the function's address).  If the function gets a stub
> > regardless, there is a bug elsewhere, probably in BFD -- try to narrow it
> > down then.
> 
> Yes, the problem I came across was with undefined weak symbols.

 What version of gcc?

> I think the same sort of thing could happen for defined weak symbols
> as well.  Example:
> 
>         if (foo != dont_run_this)
>           foo ();

 That's a bit unusual, but indeed legal code.

> As I understand it, the idea behind the old behaviour was that
> gcc generated:
> 
>         la $25,foo
>         jal $31,$25
> 
> and we wanted GAS to treat "la $25,foo" as part of a call sequence.

 Any "la $25,foo" actually as gcc used not to use the register for
anything else when emitting SVR4 PIC code.

> For example, try compiling:
> 
>     void foo (void) __attribute__ ((weak));
>     void bar (void) { if (foo) foo (); }
> 
> I get:
> 
>         la      $2,foo
>         .set    noreorder
>         .set    nomacro
>         beq     $2,$0,$L1
>         sw      $28,24($sp)
>         .set    macro
>         .set    reorder
> 
>         move    $25,$2
>         jal     $31,$25
> $L1:
> 
> [I was wrong in my original mail when I said that gcc didn't copy
> the GPR to $25.  Doh!  Serves me right for not checking.]

 But you'll get a GOT reloc for the "la $2,foo" above, so everything is
OK.

> So in summary, gcc seems to be going out of its way not to emit
> "jal foo" when that's exactly what the assembler wants to see.
> I think the correct fix is to make historical[*] gccs use "jal %0"
> for the CONSTANT_ADDRESS_P case.

 Possibly.

> I don't think we should hack bfd to prevent lazy binding in the test
> case below.  Why should conforming code be penalised because of a
> strange gccism?

 But does any gcc ever emit such code?  Your test case is in assembly and
an assembly programmer has to be aware of the shortcomings of the
assembler used.  And for current gas it's beneficial for an assembly
programmer one can actually ask for a CALL reloc by using $25.  There's no
other way. 

> >  One could argue the whole heuristics is fragile, to say at least, and I
> > would wholeheartedly agree.  It should be replaced with explicit setting
> > of relocation attributes using "%call_hi", "%call_lo", "%got_hi" and
> > "%got_lo" (I think there should be two additional operations like "%call" 
> > and "%got"[*] for the "la" and related macros themselves).  I discussed it
> > at this list a few years ago and IIRC there was an agreement that is a
> > good idea with the heuristics being a temporary solution until gcc gets
> > improved.
> 
> Well, the code is still raw, but mips-rewrite does what you suggest,
> at least for n32 and n64 PIC.  There's a patch (pending review ;) to
> extend it to o32 PIC as well.

 Too bad you haven't sent it earlier here -- maybe someone would be
interested and would fix the problem for real.  For now here is what I
cooked up -- since it's been on my to-do list for at least two years, it
certainly deserved being finally written. ;-)  For 2.13.2.1 for now.  I'll
update that if that proves usable.

 The patch handles the following percent operators:

- %call16, %call_lo -- emit a BFD_RELOC_MIPS_CALL16 relocation (16-bit
  GOT) or a BFD_RELOC_MIPS_CALL_LO16 one (32-bit GOT) (hmm, I should
  probably make them just aliases),

- %call_hi -- emit a BFD_RELOC_MIPS_CALL_HI16 (32-bit GOT) or nullify the
  symbol reference (16-bit GOT),

- %got16, %got_lo -- emit a BFD_RELOC_MIPS_GOT16 relocation (16-bit GOT) 
  or a BFD_RELOC_MIPS_GOT_LO16 one (32-bit GOT) (the note above applies,
  too),

- %got_hi -- emit a BFD_RELOC_MIPS_GOT_HI16 (32-bit GOT) or nullify the
  symbol reference (16-bit GOT),

- %call -- use CALL relocations for the symbol reference unconditionally
  if the symbol is global, otherwise ignore,

- %got -- use GOT relocations for the symbol reference unconditionally.

Since none of the already defined %call16, %call_hi, %call_lo, %got,
%got_hi and %got_lo operators is currently handled in any way, I redefined
them the most consistent way.

 Given the example program I can produce the following objects:

$ cat call.s
	.abicalls
	.ent	baz
	.frame  $sp,8,$0
	.cprestore 0
	lui	$2,%call_hi(foo)
	addu	$2,$gp
	lw	$2,%call_lo(foo)($2)
	jal	%got(bar)
	.end	baz
$ mipsel-linux-as -KPIC call.s -o call.o
$ mipsel-linux-objdump -Sr call.o

call.o:     file format elf32-tradlittlemips

Disassembly of section .text:

00000000 <.text>:
   0:	afbc0000 	sw	gp,0(sp)
   4:	3c020000 	lui	v0,0x0
   8:	005c1021 	addu	v0,v0,gp
   c:	8c420000 	lw	v0,0(v0)
			c: R_MIPS_CALL16	foo
  10:	8f990000 	lw	t9,0(gp)
			10: R_MIPS_GOT16	bar
  14:	00000000 	nop
  18:	0320f809 	jalr	t9
  1c:	00000000 	nop
  20:	8fbc0000 	lw	gp,0(sp)
	...
$ mipsel-linux-as -KPIC -xgot call.s -o call.o
$ mipsel-linux-objdump -Sr call.o

call.o:     file format elf32-tradlittlemips

Disassembly of section .text:

00000000 <.text>:
   0:	afbc0000 	sw	gp,0(sp)
   4:	3c020000 	lui	v0,0x0
			4: R_MIPS_CALL_HI16	foo
   8:	005c1021 	addu	v0,v0,gp
   c:	8c420000 	lw	v0,0(v0)
			c: R_MIPS_CALL_LO16	foo
  10:	3c190000 	lui	t9,0x0
			10: R_MIPS_GOT_HI16	bar
  14:	033cc821 	addu	t9,t9,gp
  18:	8f390000 	lw	t9,0(t9)
			18: R_MIPS_GOT_LO16	bar
  1c:	00000000 	nop
  20:	0320f809 	jalr	t9
  24:	00000000 	nop
  28:	8fbc0000 	lw	gp,0(sp)
  2c:	00000000 	nop

These operators should be sufficient to get GOT vs CALL relocations
handling right both in gcc and in handcoded assembly.  If that's get
accepted by both gas and gcc, then we can thing of phasing out the current
heuristics. 

 BTW, there was actually a bug in the heuristics, where it would use a
CALL relocation for a local symbol.  Such a use is undefined by the ABI. 
This is now fixed. 

 For the curious a ChangeLog entry is more or less like this:

2003-02-24  Maciej W. Rozycki  <macro at ds2 dot pg dot gda dot pl>

	* config/tc-mips.c (small_ex_type): Define results for additional
	%call and %got-style operators.
	(append_insn): Handle CALL and GOT relocations.
	(macro_build): Check low 16-bit relocations more strictly.
	(load_address): Add reloc_type parameter for %call and %got
	overrides for CALL and GOT relocations.
	(macro): Use %call and %got overrides for CALL and GOT
	relocations.
	(macro2): Likewise.
	(mips_ip): Handle %call and %got-style operators.
	(percent_op_match): Define additional %call and %got-style
	operators.
	(my_getPercentOp): Permit meaningful %call and %got-style
	operators for OldABI as well.

  Maciej

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

binutils-2.13.2.1-mips-gas-percent-op.patch
diff -up --recursive --new-file binutils-2.13.2.1.macro/gas/config/tc-mips.c binutils-2.13.2.1/gas/config/tc-mips.c
--- binutils-2.13.2.1.macro/gas/config/tc-mips.c	2002-11-05 22:03:40.000000000 +0000
+++ binutils-2.13.2.1/gas/config/tc-mips.c	2003-02-24 02:38:08.000000000 +0000
@@ -736,7 +736,8 @@ static void set_at PARAMS ((int *counter
 static void check_absolute_expr PARAMS ((struct mips_cl_insn * ip,
 					 expressionS *));
 static void load_register PARAMS ((int *, int, expressionS *, int));
-static void load_address PARAMS ((int *, int, expressionS *, int *));
+static void load_address PARAMS ((int *, int, expressionS *,
+				  bfd_reloc_code_real_type *, int *));
 static void move_register PARAMS ((int *, int, int));
 static void macro PARAMS ((struct mips_cl_insn * ip));
 static void mips16_macro PARAMS ((struct mips_cl_insn * ip));
@@ -806,18 +807,20 @@ enum small_ex_type
   S_EX_HI,
   S_EX_LO,
   S_EX_GP_REL,
-  S_EX_GOT,
-  S_EX_CALL16,
+  S_EX_GOT16,
   S_EX_GOT_DISP,
   S_EX_GOT_PAGE,
   S_EX_GOT_OFST,
   S_EX_GOT_HI,
   S_EX_GOT_LO,
+  S_EX_GOT,
   S_EX_NEG,
   S_EX_HIGHER,
   S_EX_HIGHEST,
+  S_EX_CALL16,
   S_EX_CALL_HI,
-  S_EX_CALL_LO
+  S_EX_CALL_LO,
+  S_EX_CALL,
 };
 
 /* Table and functions used to map between CPU/ISA names, and
@@ -1871,6 +1874,8 @@ append_insn (place, ip, address_expr, re
 	      break;
 
 	    case BFD_RELOC_HI16_S:
+	    case BFD_RELOC_MIPS_GOT_HI16:
+	    case BFD_RELOC_MIPS_CALL_HI16:
 	      ip->insn_opcode |= ((address_expr->X_add_number + 0x8000)
 				  >> 16) & 0xffff;
 	      break;
@@ -1880,6 +1885,10 @@ append_insn (place, ip, address_expr, re
 	      break;
 
 	    case BFD_RELOC_LO16:
+	    case BFD_RELOC_MIPS_GOT16:
+	    case BFD_RELOC_MIPS_CALL16:
+	    case BFD_RELOC_MIPS_GOT_LO16:
+	    case BFD_RELOC_MIPS_CALL_LO16:
 	      ip->insn_opcode |= address_expr->X_add_number & 0xffff;
 	      break;
 
@@ -2835,20 +2844,23 @@ macro_build (place, counter, ep, name, f
 	case 'j':
 	case 'o':
 	  *r = (bfd_reloc_code_real_type) va_arg (args, int);
-	  assert (*r == BFD_RELOC_GPREL16
-		  || *r == BFD_RELOC_MIPS_LITERAL
-		  || *r == BFD_RELOC_MIPS_HIGHER
-		  || *r == BFD_RELOC_HI16_S
-		  || *r == BFD_RELOC_LO16
-		  || *r == BFD_RELOC_MIPS_GOT16
-		  || *r == BFD_RELOC_MIPS_CALL16
-		  || *r == BFD_RELOC_MIPS_GOT_DISP
-		  || *r == BFD_RELOC_MIPS_GOT_PAGE
-		  || *r == BFD_RELOC_MIPS_GOT_OFST
-		  || *r == BFD_RELOC_MIPS_GOT_LO16
-		  || *r == BFD_RELOC_MIPS_CALL_LO16
-		  || (ep->X_op == O_subtract
-		      && *r == BFD_RELOC_PCREL_LO16));
+	  assert (ep != NULL
+		  && (ep->X_op == O_constant
+		      || (ep->X_op == O_symbol
+			  && (*r == BFD_RELOC_GPREL16
+			      || *r == BFD_RELOC_MIPS_LITERAL
+			      || *r == BFD_RELOC_MIPS_HIGHER
+			      || *r == BFD_RELOC_HI16_S
+			      || *r == BFD_RELOC_LO16
+			      || *r == BFD_RELOC_MIPS_GOT16
+			      || *r == BFD_RELOC_MIPS_CALL16
+			      || *r == BFD_RELOC_MIPS_GOT_DISP
+			      || *r == BFD_RELOC_MIPS_GOT_PAGE
+			      || *r == BFD_RELOC_MIPS_GOT_OFST
+			      || *r == BFD_RELOC_MIPS_GOT_LO16
+			      || *r == BFD_RELOC_MIPS_CALL_LO16))
+			  || (ep->X_op == O_subtract
+			      && *r == BFD_RELOC_PCREL_LO16)));
 	  continue;
 
 	case 'u':
@@ -3556,10 +3568,11 @@ load_register (counter, reg, ep, dbl)
 /* Load an address into a register.  */
 
 static void
-load_address (counter, reg, ep, used_at)
+load_address (counter, reg, ep, reloc_type, used_at)
      int *counter;
      int reg;
      expressionS *ep;
+     bfd_reloc_code_real_type *reloc_type;
      int *used_at;
 {
   char *p = NULL;
@@ -3664,26 +3677,36 @@ load_address (counter, reg, ep, used_at)
     }
   else if (mips_pic == SVR4_PIC && ! mips_big_got)
     {
+      int lw_reloc_type = (int) BFD_RELOC_MIPS_GOT16;
       expressionS ex;
 
       /* 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.  Otherwise we want
 	   lw		$reg,<sym>($gp)		(BFD_RELOC_MIPS_GOT16)
 	   nop
 	   addiu	$reg,$reg,<sym>		(BFD_RELOC_LO16)
 	 If there is a constant, it must be added in after.  */
       ex.X_add_number = ep->X_add_number;
       ep->X_add_number = 0;
+      if (*reloc_type == BFD_RELOC_MIPS_CALL16)
+	lw_reloc_type = *offset_reloc;
       frag_grow (20);
       macro_build ((char *) NULL, counter, ep,
 		   HAVE_32BIT_ADDRESSES ? "lw" : "ld", "t,o(b)",
-		   reg, (int) BFD_RELOC_MIPS_GOT16, mips_gp_register);
+		   reg, lw_reloc_type, mips_gp_register);
       macro_build ((char *) NULL, counter, (expressionS *) 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, (offsetT) 0, (char *) NULL);
       macro_build (p, counter, ep,
+		   HAVE_32BIT_ADDRESSES ? "lw" : "ld", "t,o(b)",
+		   reg, (int) BFD_RELOC_MIPS_GOT16, mips_gp_register);
+      p += 4;
+      macro_build (p, counter, (expressionS *) NULL, "nop", "");
+      p += 4;
+      macro_build (p, counter, ep,
 		   HAVE_32BIT_ADDRESSES ? "addiu" : "daddiu",
 		   "t,r,j", reg, reg, (int) BFD_RELOC_LO16);
       if (ex.X_add_number != 0)
@@ -3698,6 +3721,8 @@ load_address (counter, reg, ep, used_at)
     }
   else if (mips_pic == SVR4_PIC)
     {
+      int lui_reloc_type = (int) BFD_RELOC_MIPS_GOT_HI16;
+      int lw_reloc_type = (int) BFD_RELOC_MIPS_GOT_LO16;
       expressionS ex;
       int off;
 
@@ -3706,7 +3731,9 @@ load_address (counter, reg, ep, used_at)
 	   lui		$reg,<sym>		(BFD_RELOC_MIPS_GOT_HI16)
 	   addu		$reg,$reg,$gp
 	   lw		$reg,<sym>($reg)	(BFD_RELOC_MIPS_GOT_LO16)
-	 Otherwise, for a reference to a local symbol, we want
+	 BFD_RELOC_MIPS_CALL_HI16 and BFD_RELOC_MIPS_CALL_LO16 may
+	 be used instead if requested with %call.  Otherwise, for a
+	 reference to a local symbol, we want
 	   lw		$reg,<sym>($gp)		(BFD_RELOC_MIPS_GOT16)
 	   nop
 	   addiu	$reg,$reg,<sym>		(BFD_RELOC_LO16)
@@ -3731,15 +3758,20 @@ load_address (counter, reg, ep, used_at)
 	    off = 4;
 	  else
 	    off = 0;
+	  if (*reloc_type == BFD_RELOC_MIPS_CALL16)
+	    {
+	      lui_reloc_type = (int) BFD_RELOC_MIPS_CALL_HI16;
+	      lw_reloc_type = (int) BFD_RELOC_MIPS_CALL_LO16;
+	    }
 	  frag_grow (32);
 	  macro_build ((char *) NULL, counter, ep, "lui", "t,u", reg,
-		       (int) BFD_RELOC_MIPS_GOT_HI16);
+		       lui_reloc_type);
 	  macro_build ((char *) NULL, counter, (expressionS *) NULL,
 		       HAVE_32BIT_ADDRESSES ? "addu" : "daddu", "d,v,t", reg,
 		       reg, mips_gp_register);
 	  macro_build ((char *) NULL, counter, ep,
 		       HAVE_32BIT_ADDRESSES ? "lw" : "ld",
-		       "t,o(b)", reg, (int) BFD_RELOC_MIPS_GOT_LO16, reg);
+		       "t,o(b)", reg, lw_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),
@@ -4695,6 +4727,8 @@ macro (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 or %got, respectively.
 	     For a local symbol, we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       nop
@@ -4705,9 +4739,10 @@ macro (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.  For a local symbol, we want the same
+	     instruction sequence, but we output a BFD_RELOC_LO16
+	     reloc on the addiu instruction (no %call override).
 
 	     If we have a large constant, and this is a reference to
 	     an external symbol, we want
@@ -4715,14 +4750,18 @@ macro (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.  For a local symbol, we want the same
+	     instruction sequence, but we output a BFD_RELOC_LO16
+	     reloc on the addiu instruction (no %call 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;
+	  if (*offset_reloc == BFD_RELOC_MIPS_GOT16
+	      || *offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    lw_reloc_type = *offset_reloc;
 	  macro_build ((char *) NULL, &icnt, &offset_expr,
 		       HAVE_32BIT_ADDRESSES ? "lw" : "ld",
 		       "t,o(b)", tempreg, lw_reloc_type, mips_gp_register);
@@ -4742,39 +4781,53 @@ macro (ip)
 			       "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, (expressionS *) NULL, "nop", "");
-		  p += 4;
-		}
+	      macro_build (p, &icnt, &offset_expr,
+			   HAVE_32BIT_ADDRESSES ? "lw" : "ld",
+			   "t,o(b)", tempreg,
+			   (int) BFD_RELOC_MIPS_GOT16, mips_gp_register);
+	      p += 4;
+	      macro_build (p, &icnt, (expressionS *) NULL, "nop", "");
+	      p += 4;
 	      macro_build (p, &icnt, &expr1,
 			   HAVE_32BIT_ADDRESSES ? "addiu" : "daddiu",
 			   "t,r,j", tempreg, tempreg, (int) 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 ((char *) NULL, &icnt, (expressionS *) NULL,
 			   "nop", "");
 	      macro_build ((char *) NULL, &icnt, &expr1,
 			   HAVE_32BIT_ADDRESSES ? "addiu" : "daddiu",
 			   "t,r,j", tempreg, tempreg, (int) 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,
+			   HAVE_32BIT_ADDRESSES ? "lw" : "ld",
+			   "t,o(b)", tempreg,
+			   (int) BFD_RELOC_MIPS_GOT16, mips_gp_register);
+	      p += 4;
+	      macro_build (p, &icnt, (expressionS *) NULL, "nop", "");
+	      p += 4;
+	      macro_build (p, &icnt, &expr1,
+			   HAVE_32BIT_ADDRESSES ? "addiu" : "daddiu",
+			   "t,r,j", tempreg, tempreg, (int) 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,
@@ -4792,9 +4845,8 @@ macro (ip)
 		  macro_build ((char *) NULL, &icnt, (expressionS *) NULL,
 			       HAVE_32BIT_ADDRESSES ? "addu" : "daddu",
 			       "d,v,t", treg, AT, breg);
-		  breg = 0;
 		  tempreg = treg;
-		  off1 = -8;
+		  off1 = 8;
 		}
 
 	      /* Set mips_optimize around the lui instruction to avoid
@@ -4810,9 +4862,51 @@ macro (ip)
 	      macro_build ((char *) NULL, &icnt, (expressionS *) NULL,
 			   HAVE_32BIT_ADDRESSES ? "addu" : "daddu",
 			   "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,
+			   HAVE_32BIT_ADDRESSES ? "lw" : "ld",
+			   "t,o(b)", tempreg,
+			   (int) 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, (expressionS *) NULL,
+			       "nop", "");
+		  p += 4;
+		  macro_build (p, &icnt, (expressionS *) NULL,
+			       HAVE_32BIT_ADDRESSES ? "addu" : "daddu",
+			       "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,
+			   HAVE_32BIT_ADDRESSES ? "addiu" : "daddiu",
+			   "t,r,j", AT, AT, (int) BFD_RELOC_LO16);
+	      p += 4;
+	      macro_build (p, &icnt, (expressionS *) NULL,
+			   HAVE_32BIT_ADDRESSES ? "addu" : "daddu",
+			   "d,v,t", tempreg, tempreg, AT);
+	      if (breg == treg)
+		breg = 0;
 	      used_at = 1;
 	    }
 	}
@@ -4832,7 +4926,10 @@ macro (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 or %got,
+	     respectively.  For a local symbol, we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       addiu	$tempreg,$tempreg,<sym>	(BFD_RELOC_LO16)
@@ -4844,7 +4941,9 @@ macro (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.  For a local
+	     symbol, we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       addiu	$tempreg,$tempreg,<constant> (BFD_RELOC_LO16)
@@ -4857,7 +4956,9 @@ macro (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.  For a local
+	     symbol, we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       lui	$at,<hiconstant>
 	       addiu	$at,$at,<loconstant>	(BFD_RELOC_LO16)
@@ -4867,6 +4968,9 @@ macro (ip)
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT_DISP)
 	     If tempreg is PIC_CALL_REG pointing to a external symbol, we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_CALL16)
+	     BFD_RELOC_MIPS_CALL16 or BFD_RELOC_MIPS_GOT_DISP may be
+	     used instead if requested with %call or %got,
+	     respectively.
 	   */
 	  if (HAVE_NEWABI)
 	    {
@@ -4874,6 +4978,11 @@ macro (ip)
 				? BFD_RELOC_MIPS_CALL16
 				: BFD_RELOC_MIPS_GOT_DISP);
 
+	      if (*offset_reloc == BFD_RELOC_MIPS_GOT16)
+		lw_reloc_type = (int) BFD_RELOC_MIPS_GOT_DISP;
+	      if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+		lw_reloc_type = (int) BFD_RELOC_MIPS_CALL16;
+
 	      macro_build ((char *) NULL, &icnt, &offset_expr,
 			   HAVE_32BIT_ADDRESSES ? "lw" : "ld",
 			   "t,o(b)", tempreg, reloc_type, mips_gp_register);
@@ -4900,6 +5009,16 @@ macro (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 = (int) BFD_RELOC_MIPS_GOT_HI16;
+	      lw_reloc_type = (int) BFD_RELOC_MIPS_GOT_LO16;
+	    }
+	  if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    {
+	      lui_reloc_type = (int) BFD_RELOC_MIPS_CALL_HI16;
+	      lw_reloc_type = (int) BFD_RELOC_MIPS_CALL_LO16;
+	    }
 	  macro_build ((char *) NULL, &icnt, &offset_expr, "lui", "t,u",
 		       tempreg, lui_reloc_type);
 	  macro_build ((char *) NULL, &icnt, (expressionS *) NULL,
@@ -5193,10 +5312,14 @@ macro (ip)
 	      frag_grow (40);
 	      if (! mips_big_got)
 		{
+		  int lw_reloc_type = (int) BFD_RELOC_MIPS_CALL16;
+
+		  if (*offset_reloc == BFD_RELOC_MIPS_GOT16)
+		    lw_reloc_type = *offset_reloc;
 		  macro_build ((char *) NULL, &icnt, &offset_expr,
 			       HAVE_32BIT_ADDRESSES ? "lw" : "ld",
 			       "t,o(b)", PIC_CALL_REG,
-			       (int) BFD_RELOC_MIPS_CALL16, mips_gp_register);
+			       lw_reloc_type, mips_gp_register);
 		  macro_build ((char *) NULL, &icnt, (expressionS *) NULL,
 			       "nop", "");
 		  p = frag_var (rs_machine_dependent, 4, 0,
@@ -5205,15 +5328,22 @@ macro (ip)
 		}
 	      else
 		{
+		  int lui_reloc_type = (int) BFD_RELOC_MIPS_CALL_HI16;
+		  int lw_reloc_type = (int) 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 = (int) BFD_RELOC_MIPS_GOT_HI16;
+		      lw_reloc_type = (int) BFD_RELOC_MIPS_GOT_LO16;
+		    }
 		  macro_build ((char *) NULL, &icnt, &offset_expr, "lui",
 			       "t,u", PIC_CALL_REG,
-			       (int) BFD_RELOC_MIPS_CALL_HI16);
+			       lui_reloc_type);
 		  macro_build ((char *) NULL, &icnt, (expressionS *) NULL,
 			       HAVE_32BIT_ADDRESSES ? "addu" : "daddu",
 			       "d,v,t", PIC_CALL_REG, PIC_CALL_REG,
@@ -5221,7 +5351,7 @@ macro (ip)
 		  macro_build ((char *) NULL, &icnt, &offset_expr,
 			       HAVE_32BIT_ADDRESSES ? "lw" : "ld",
 			       "t,o(b)", PIC_CALL_REG,
-			       (int) BFD_RELOC_MIPS_CALL_LO16, PIC_CALL_REG);
+			       lw_reloc_type, PIC_CALL_REG);
 		  macro_build ((char *) NULL, &icnt, (expressionS *) NULL,
 			       "nop", "");
 		  p = frag_var (rs_machine_dependent, 12 + gpdel, 0,
@@ -5681,13 +5811,15 @@ macro (ip)
 	}
       else if (mips_pic == SVR4_PIC && ! mips_big_got)
 	{
+	  int lw_reloc_type = (int) BFD_RELOC_MIPS_GOT16;
 	  char *p;
 
 	  /* If this is a reference to an external symbol, we want
 	       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.  Otherwise we want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       addiu	$tempreg,$tempreg,<sym>	(BFD_RELOC_LO16)
@@ -5704,15 +5836,23 @@ macro (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 ((char *) NULL, &icnt, &offset_expr,
 		       HAVE_32BIT_ADDRESSES ? "lw" : "ld", "t,o(b)", tempreg,
-		       (int) BFD_RELOC_MIPS_GOT16, mips_gp_register);
+		       lw_reloc_type, mips_gp_register);
 	  macro_build ((char *) NULL, &icnt, (expressionS *) 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,
+		       HAVE_32BIT_ADDRESSES ? "lw" : "ld", "t,o(b)", tempreg,
+		       (int) BFD_RELOC_MIPS_GOT16, mips_gp_register);
+	  p += 4;
+	  macro_build (p, &icnt, (expressionS *) NULL, "nop", "");
+	  p += 4;
+	  macro_build (p, &icnt, &offset_expr,
 		       HAVE_32BIT_ADDRESSES ? "addiu" : "daddiu",
 		       "t,r,j", tempreg, tempreg, (int) BFD_RELOC_LO16);
 	  if (breg != 0)
@@ -5724,6 +5864,8 @@ macro (ip)
 	}
       else if (mips_pic == SVR4_PIC)
 	{
+	  int lui_reloc_type = (int) BFD_RELOC_MIPS_GOT_HI16;
+	  int lw_reloc_type = (int) BFD_RELOC_MIPS_GOT_LO16;
 	  int gpdel;
 	  char *p;
 
@@ -5732,7 +5874,9 @@ macro (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.  Otherwise we
+	     want
 	       lw	$tempreg,<sym>($gp)	(BFD_RELOC_MIPS_GOT16)
 	       nop
 	       addiu	$tempreg,$tempreg,<sym>	(BFD_RELOC_LO16)
@@ -5781,15 +5925,20 @@ macro (ip)
 	    gpdel = 4;
 	  else
 	    gpdel = 0;
+	  if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    {
+	      lui_reloc_type = (int) BFD_RELOC_MIPS_CALL_HI16;
+	      lw_reloc_type = (int) BFD_RELOC_MIPS_CALL_LO16;
+	    }
 	  frag_grow (36);
 	  macro_build ((char *) NULL, &icnt, &offset_expr, "lui", "t,u",
-		       tempreg, (int) BFD_RELOC_MIPS_GOT_HI16);
+		       tempreg, lui_reloc_type);
 	  macro_build ((char *) NULL, &icnt, (expressionS *) NULL,
 		       HAVE_32BIT_ADDRESSES ? "addu" : "daddu",
 		       "d,v,t", tempreg, tempreg, mips_gp_register);
 	  macro_build ((char *) NULL, &icnt, &offset_expr,
 		       HAVE_32BIT_ADDRESSES ? "lw" : "ld",
-		       "t,o(b)", tempreg, (int) BFD_RELOC_MIPS_GOT_LO16,
+		       "t,o(b)", tempreg, lw_reloc_type,
 		       tempreg);
 	  p = frag_var (rs_machine_dependent, 12 + gpdel, 0,
 			RELAX_ENCODE (12, 12 + gpdel, gpdel, 8 + gpdel, 0, 0),
@@ -6282,7 +6431,9 @@ macro (ip)
 	}
       else if (mips_pic == SVR4_PIC && ! mips_big_got)
 	{
+	  int lw_reloc_type = (int) 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)
@@ -6307,10 +6458,12 @@ macro (ip)
 	    off = 0;
 	  else
 	    off = 4;
+	  if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    lw_reloc_type = *offset_reloc;
 	  frag_grow (24 + off);
 	  macro_build ((char *) NULL, &icnt, &offset_expr,
 		       HAVE_32BIT_ADDRESSES ? "lw" : "ld", "t,o(b)", AT,
-		       (int) BFD_RELOC_MIPS_GOT16, mips_gp_register);
+		       lw_reloc_type, mips_gp_register);
 	  macro_build ((char *) NULL, &icnt, (expressionS *) NULL, "nop", "");
 	  if (breg != 0)
 	    macro_build ((char *) NULL, &icnt, (expressionS *) NULL,
@@ -6331,13 +6484,45 @@ macro (ip)
 		       coproc ? treg : treg + 1,
 		       (int) 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,
+		       HAVE_32BIT_ADDRESSES ? "lw" : "ld", "t,o(b)", AT,
+		       (int) BFD_RELOC_MIPS_GOT16, mips_gp_register);
+	  p += 4;
+	  macro_build (p, &icnt, (expressionS *) NULL, "nop", "");
+	  p += 4;
+	  if (breg != 0)
+	    {
+	      macro_build (p, &icnt, (expressionS *) NULL,
+			   HAVE_32BIT_ADDRESSES ? "addu" : "daddu",
+			   "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,
+		       (int) 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,
+		       (int) BFD_RELOC_LO16, AT);
+	  mips_optimize = hold_mips_optimize;
 	}
       else if (mips_pic == SVR4_PIC)
 	{
+	  int lui_reloc_type = (int) BFD_RELOC_MIPS_GOT_HI16;
+	  int lw_reloc_type = (int) BFD_RELOC_MIPS_GOT_LO16;
 	  int gpdel, off;
 	  char *p;
 
@@ -6370,15 +6555,20 @@ macro (ip)
 	    off = 0;
 	  else
 	    off = 4;
+	  if (*offset_reloc == BFD_RELOC_MIPS_CALL16)
+	    {
+	      lui_reloc_type = (int) BFD_RELOC_MIPS_CALL_HI16;
+	      lw_reloc_type = (int) BFD_RELOC_MIPS_CALL_LO16;
+	    }
 	  frag_grow (56);
 	  macro_build ((char *) NULL, &icnt, &offset_expr, "lui", "t,u",
-		       AT, (int) BFD_RELOC_MIPS_GOT_HI16);
+		       AT, lui_reloc_type);
 	  macro_build ((char *) NULL, &icnt, (expressionS *) NULL,
 		       HAVE_32BIT_ADDRESSES ? "addu" : "daddu",
 		       "d,v,t", AT, AT, mips_gp_register);
 	  macro_build ((char *) NULL, &icnt, &offset_expr,
 		       HAVE_32BIT_ADDRESSES ? "lw" : "ld",
-		       "t,o(b)", AT, (int) BFD_RELOC_MIPS_GOT_LO16, AT);
+		       "t,o(b)", AT, lw_reloc_type, AT);
 	  macro_build ((char *) NULL, &icnt, (expressionS *) NULL, "nop", "");
 	  if (breg != 0)
 	    macro_build ((char *) NULL, &icnt, (expressionS *) NULL,
@@ -7248,7 +7438,7 @@ macro2 (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 ((char *) NULL, &icnt, (expressionS *) NULL,
 		     HAVE_32BIT_ADDRESSES ? "addu" : "daddu",
@@ -7270,7 +7460,7 @@ macro2 (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 ((char *) NULL, &icnt, (expressionS *) NULL,
 		     HAVE_32BIT_ADDRESSES ? "addu" : "daddu",
@@ -7344,7 +7534,7 @@ macro2 (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 ((char *) NULL, &icnt, (expressionS *) NULL,
 		     HAVE_32BIT_ADDRESSES ? "addu" : "daddu",
@@ -7365,7 +7555,7 @@ macro2 (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 ((char *) NULL, &icnt, (expressionS *) NULL,
 		     HAVE_32BIT_ADDRESSES ? "addu" : "daddu",
@@ -8398,8 +8588,12 @@ mips_ip (str, ip)
 	      continue;
 
 	    case 'A':
-	      my_getExpression (&offset_expr, s);
-	      *imm_reloc = BFD_RELOC_32;
+	      c = my_getSmallExpression (&offset_expr, s);
+	      *offset_reloc = BFD_RELOC_32;
+	      if (c == S_EX_GOT)
+		*offset_reloc = BFD_RELOC_MIPS_GOT16;
+	      else if (c == S_EX_CALL)
+		*offset_reloc = BFD_RELOC_MIPS_CALL16;
 	      s = expr_end;
 	      continue;
 
@@ -8627,6 +8821,30 @@ mips_ip (str, ip)
 			*imm_reloc = BFD_RELOC_MIPS_HIGHEST;
 		      else if (c == S_EX_HIGHER)
 			*imm_reloc = BFD_RELOC_MIPS_HIGHER;
+		      else if (c == S_EX_GOT || c == S_EX_GOT16
+			       || c == S_EX_GOT_LO)
+			*imm_reloc = mips_big_got ? BFD_RELOC_MIPS_GOT_LO16
+						  : BFD_RELOC_MIPS_GOT16;
+		      else if (c == S_EX_GOT_HI)
+			{
+			  if (mips_big_got)
+			    *imm_reloc = BFD_RELOC_MIPS_GOT_HI16;
+			  else
+			    if (imm_expr.X_op == O_symbol)
+			      imm_expr.X_op = O_constant;
+			}
+		      else if (c == S_EX_CALL || c == S_EX_CALL16
+			       || c == S_EX_CALL_LO)
+			*imm_reloc = mips_big_got ? BFD_RELOC_MIPS_CALL_LO16
+						  : BFD_RELOC_MIPS_CALL16;
+		      else if (c == S_EX_CALL_HI)
+			{
+			  if (mips_big_got)
+			    *imm_reloc = BFD_RELOC_MIPS_CALL_HI16;
+			  else
+			    if (imm_expr.X_op == O_symbol)
+			      imm_expr.X_op = O_constant;
+			}
 		      else if (c == S_EX_GP_REL)
 			{
 			  /* This occurs in NewABI only.  */
@@ -8713,11 +8931,12 @@ mips_ip (str, ip)
 
 	      /* If this value won't fit into a 16 bit offset, then go
 		 find a macro that will generate the 32 bit offset
-		 code pattern.  */
-	      if (c == S_EX_NONE
-		  && (offset_expr.X_op != O_constant
-		      || offset_expr.X_add_number >= 0x8000
-		      || offset_expr.X_add_number < -0x8000))
+		 code pattern.  Ditto for %got and %call expressions.  */
+	      if ((c == S_EX_NONE
+		   && (offset_expr.X_op != O_constant
+		       || offset_expr.X_add_number >= 0x8000
+		       || offset_expr.X_add_number < -0x8000))
+		  || c == S_EX_GOT || c == S_EX_CALL)
 		break;
 
 	      if (c == S_EX_HI)
@@ -8728,6 +8947,12 @@ mips_ip (str, ip)
 		    (offset_expr.X_add_number >> 16) & 0xffff;
 		}
 	      *offset_reloc = BFD_RELOC_LO16;
+	      if (c == S_EX_GOT16 || c == S_EX_GOT_LO)
+		*offset_reloc = mips_big_got ? BFD_RELOC_MIPS_GOT_LO16
+					     : BFD_RELOC_MIPS_GOT16;
+	      if (c == S_EX_CALL16 || c == S_EX_CALL_LO)
+		*offset_reloc = mips_big_got ? BFD_RELOC_MIPS_CALL_LO16
+					     : BFD_RELOC_MIPS_CALL16;
 	      s = expr_end;
 	      continue;
 
@@ -8755,6 +8980,22 @@ mips_ip (str, ip)
 #ifdef OBJ_ELF
 		      else if (c == S_EX_HIGHEST)
 			*imm_reloc = BFD_RELOC_MIPS_HIGHEST;
+		      else if (c == S_EX_GOT || c == S_EX_GOT_HI)
+			{
+			  if (mips_big_got)
+			    *imm_reloc = BFD_RELOC_MIPS_GOT_HI16;
+			  else
+			    if (imm_expr.X_op == O_symbol)
+			      imm_expr.X_op = O_constant;
+			}
+		      else if (c == S_EX_CALL || c == S_EX_CALL_HI)
+			{
+			  if (mips_big_got)
+			    *imm_reloc = BFD_RELOC_MIPS_CALL_HI16;
+			  else
+			    if (imm_expr.X_op == O_symbol)
+			      imm_expr.X_op = O_constant;
+			}
 		      else if (c == S_EX_GP_REL)
 			{
 			  /* This occurs in NewABI only.  */
@@ -8789,9 +9030,13 @@ mips_ip (str, ip)
 	      continue;
 
 	    case 'a':		/* 26 bit address */
-	      my_getExpression (&offset_expr, s);
-	      s = expr_end;
+	      c = my_getSmallExpression (&offset_expr, s);
 	      *offset_reloc = BFD_RELOC_MIPS_JMP;
+	      if (c == S_EX_GOT)
+		*offset_reloc = BFD_RELOC_MIPS_GOT16;
+	      else if (c == S_EX_CALL)
+		*offset_reloc = BFD_RELOC_MIPS_CALL16;
+	      s = expr_end;
 	      continue;
 
 	    case 'N':		/* 3 bit branch condition code */
@@ -9593,9 +9838,11 @@ static struct percent_op_match
 {
   {"%lo", S_EX_LO},
 #ifdef OBJ_ELF
+  {"%call16", S_EX_CALL16},
   {"%call_hi", S_EX_CALL_HI},
   {"%call_lo", S_EX_CALL_LO},
-  {"%call16", S_EX_CALL16},
+  {"%call", S_EX_CALL},
+  {"%got16", S_EX_GOT16},
   {"%got_disp", S_EX_GOT_DISP},
   {"%got_page", S_EX_GOT_PAGE},
   {"%got_ofst", S_EX_GOT_OFST},
@@ -9704,8 +9951,13 @@ my_getPercentOp (str, len, nestlevel)
 	{
 	  int type = percent_op[i].type;
 
-	  /* Only %hi and %lo are allowed for OldABI.  */
-	  if (! HAVE_NEWABI && type != S_EX_HI && type != S_EX_LO)
+	  /* Only %hi, %lo, %got, %call, %got16, %call16, %got_hi,
+	     %got_lo, %call_hi and %call_lo are allowed for OldABI.  */
+	  if (! HAVE_NEWABI && type != S_EX_HI && type != S_EX_LO
+	      && type != S_EX_GOT && type != S_EX_CALL
+	      && type != S_EX_GOT16 && type != S_EX_CALL16
+	      && type != S_EX_GOT_HI && type != S_EX_GOT_LO
+	      && type != S_EX_CALL_HI && type != S_EX_CALL_LO)
 	    return S_EX_NONE;
 
 	  *len = strlen (percent_op[i].str);


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