From 4a403be0c1b4540e22c8c608ea0bfecbf0f85e51 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Chigot?= <clement.chigot@atos.net>
Date: Thu, 11 Mar 2021 11:08:19 +0100
Subject: [PATCH] aix: implement R_TOCU and R_TOCL relocations

Implement support for largetoc on XCOFF.
R_TOCU and R_TOCL are referenced by the new BFD defines:
BFD_RELOC_PPC_TOC16_HI and BFD_RELOC_PPC_TOC16_LO.
A new toc storage class is added XMC_TE.

In order to correctly handle R_TOCU, the logic behind
xcoff_reloc_type_toc is changed to compute the whole TOC offset
instead of just the difference between the "link" offset and the
"assembly" offset.

In gas, add a function to transform addis format used by AIX
"addis RT, D(RA)" into the ELF format "addis RT, RA, SI".

bfd/
	* reloc.c (BFD_RELOC_PPC_TOC16_HI, BFD_RELOC_PPC_TOC16_LO):
	New relocations.
	* bfd-in2.h: Regenerate.
	* libbfd.h: Regenerate.
	* coff-rs6000.c (xcoff_calculate_relocation): Call
	xcoff_reloc_type_toc for R_TOCU and R_TOCL.
	(xcoff_howto_table): Remove src_mask for TOC relocations.
	Add R_TOCU and R_TOCL howtos.
	(_bfd_xcoff_reloc_type_lookup): Add cases for
	BFD_RELOC_PPC_TOC16_HI and BFD_RELOC_PPC_TOC16_LO.
	(xcoff_reloc_type_toc): Compute the whole offset.
	Implement R_TOCU and R_TOCL.
	* coff64-rs6000.c (xcoff64_calculate_relocation):
	Likewise.
	(xcoff64_howto_table): Likewise.
	(xcoff64_reloc_type_lookup): Likewise.
gas/
	* config/tc-ppc.c (ppc_xcoff_suffix): New function.
	(MAP, MAP32, MAP64): New macros for XCOFF.
	(ppc_xcoff_fixup_addis): New function.
	(ppc_is_toc_sym): Handle XMC_TE.
	(fixup_size): Add cases for BFD_RELOC_PPC_TOC16_HI and
	BFD_RELOC_PPC_TOC16_LO.
	(md_assemble): Call ppc_xcoff_fixup_addis for XCOFF.
	(ppc_change_csect): Handle XMC_TE.
	(ppc_tc): Enable .tc symbols to have only a XMC_TC or XMC_TE
	storage class.
	(ppc_symbol_new_hook): Handle XMC_TE.
	(ppc_frob_symbol): Likewise.
	(ppc_fix_adjustable): Likewise.
	(md_apply_fix): Handle BFD_RELOC_PPC_TOC16_HI and
	BFD_RELOC_PPC_TOC16_LO.
ld/
	* scripttempl/aix.sc: Add .te to .data section.
	* testsuite/ld-powerpc/aix52.exp: Add test structure for AIX7+.
	Add aix-largetoc-1 test.
	* testsuite/ld-powerpc/aix-largetoc-1-32.d: New test.
	* testsuite/ld-powerpc/aix-largetoc-1-64.d: New test.
	* testsuite/ld-powerpc/aix-largetoc-1.ex: New test.
	* testsuite/ld-powerpc/aix-largetoc-1.s: New test.
---
 bfd/ChangeLog                               |  19 +++
 bfd/bfd-in2.h                               |   2 +
 bfd/coff-rs6000.c                           |  70 +++++++--
 bfd/coff64-rs6000.c                         |  45 ++++--
 bfd/libbfd.h                                |   2 +
 bfd/reloc.c                                 |   4 +
 gas/ChangeLog                               |  18 +++
 gas/config/tc-ppc.c                         | 153 +++++++++++++++++++-
 ld/ChangeLog                                |  10 ++
 ld/scripttempl/aix.sc                       |   1 +
 ld/testsuite/ld-powerpc/aix-largetoc-1-32.d |  20 +++
 ld/testsuite/ld-powerpc/aix-largetoc-1-64.d |  20 +++
 ld/testsuite/ld-powerpc/aix-largetoc-1.ex   |   1 +
 ld/testsuite/ld-powerpc/aix-largetoc-1.s    |  25 ++++
 ld/testsuite/ld-powerpc/aix52.exp           |  17 +++
 15 files changed, 376 insertions(+), 31 deletions(-)
 create mode 100644 ld/testsuite/ld-powerpc/aix-largetoc-1-32.d
 create mode 100644 ld/testsuite/ld-powerpc/aix-largetoc-1-64.d
 create mode 100644 ld/testsuite/ld-powerpc/aix-largetoc-1.ex
 create mode 100644 ld/testsuite/ld-powerpc/aix-largetoc-1.s

diff --git a/bfd/ChangeLog b/bfd/ChangeLog
index 6c359ac1a88..d955f78a980 100644
--- a/bfd/ChangeLog
+++ b/bfd/ChangeLog
@@ -1,3 +1,22 @@
+2021-03-12  Clément Chigot  <clement.chigot@atos.net>
+
+	* reloc.c (BFD_RELOC_PPC_TOC16_HI, BFD_RELOC_PPC_TOC16_LO):
+	New relocations.
+	* bfd-in2.h: Regenerate.
+	* libbfd.h: Regenerate.
+	* coff-rs6000.c (xcoff_calculate_relocation): Call
+	xcoff_reloc_type_toc for R_TOCU and R_TOCL.
+	(xcoff_howto_table): Remove src_mask for TOC relocations.
+	Add R_TOCU and R_TOCL howtos.
+	(_bfd_xcoff_reloc_type_lookup): Add cases for
+	BFD_RELOC_PPC_TOC16_HI and BFD_RELOC_PPC_TOC16_LO.
+	(xcoff_reloc_type_toc): Compute the whole offset.
+	Implement R_TOCU and R_TOCL.
+	* coff64-rs6000.c (xcoff64_calculate_relocation):
+	Likewise.
+	(xcoff64_howto_table): Likewise.
+	(xcoff64_reloc_type_lookup): Likewise.
+
 2021-03-12  Clément Chigot  <clement.chigot@atos.net>
 
 	* coff-rs6000.c (xcoff_calculate_relocation): Correct and
diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index d142bb52213..9bd61b10e70 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -2899,6 +2899,8 @@ instruction.  */
   BFD_RELOC_PPC_B26,
   BFD_RELOC_PPC_BA26,
   BFD_RELOC_PPC_TOC16,
+  BFD_RELOC_PPC_TOC16_LO,
+  BFD_RELOC_PPC_TOC16_HI,
   BFD_RELOC_PPC_B16,
   BFD_RELOC_PPC_B16_BRTAKEN,
   BFD_RELOC_PPC_B16_BRNTAKEN,
diff --git a/bfd/coff-rs6000.c b/bfd/coff-rs6000.c
index 2cae299539d..b77ff0c234a 100644
--- a/bfd/coff-rs6000.c
+++ b/bfd/coff-rs6000.c
@@ -206,8 +206,8 @@ xcoff_calculate_relocation[XCOFF_MAX_CALCULATE_RELOCATION] =
   xcoff_reloc_type_fail, /*           (0x2d) */
   xcoff_reloc_type_fail, /*           (0x2e) */
   xcoff_reloc_type_fail, /*           (0x2f) */
-  xcoff_reloc_type_fail, /* R_TOCU    (0x30) */
-  xcoff_reloc_type_fail, /* R_TOCL    (0x31) */
+  xcoff_reloc_type_toc,  /* R_TOCU    (0x30) */
+  xcoff_reloc_type_toc,  /* R_TOCL    (0x31) */
 };
 
 xcoff_complain_function *const
@@ -745,7 +745,7 @@ reloc_howto_type xcoff_howto_table[] =
 	 0,			/* special_function */
 	 "R_TOC",		/* name */
 	 TRUE,			/* partial_inplace */
-	 0xffff,		/* src_mask */
+	 0,			/* src_mask */
 	 0xffff,		/* dst_mask */
 	 FALSE),		/* pcrel_offset */
 
@@ -760,7 +760,7 @@ reloc_howto_type xcoff_howto_table[] =
 	 0,			/* special_function */
 	 "R_TRL",		/* name */
 	 TRUE,			/* partial_inplace */
-	 0xffff,		/* src_mask */
+	 0,			/* src_mask */
 	 0xffff,		/* dst_mask */
 	 FALSE),		/* pcrel_offset */
 
@@ -775,7 +775,7 @@ reloc_howto_type xcoff_howto_table[] =
 	 0,			/* special_function */
 	 "R_GL",		/* name */
 	 TRUE,			/* partial_inplace */
-	 0xffff,		/* src_mask */
+	 0,			/* src_mask */
 	 0xffff,		/* dst_mask */
 	 FALSE),		/* pcrel_offset */
 
@@ -790,7 +790,7 @@ reloc_howto_type xcoff_howto_table[] =
 	 0,			/* special_function */
 	 "R_TCL",		/* name */
 	 TRUE,			/* partial_inplace */
-	 0xffff,		/* src_mask */
+	 0,			/* src_mask */
 	 0xffff,		/* dst_mask */
 	 FALSE),		/* pcrel_offset */
 
@@ -892,7 +892,7 @@ reloc_howto_type xcoff_howto_table[] =
 	 0,			/* special_function */
 	 "R_TRLA",		/* name */
 	 TRUE,			/* partial_inplace */
-	 0xffff,		/* src_mask */
+	 0,			/* src_mask */
 	 0xffff,		/* dst_mask */
 	 FALSE),		/* pcrel_offset */
 
@@ -1093,10 +1093,34 @@ reloc_howto_type xcoff_howto_table[] =
   EMPTY_HOWTO(0x2f),
 
   /* 0x30: High-order 16 bit TOC relative relocation.  */
-  EMPTY_HOWTO (R_TOCU),
+  HOWTO (R_TOCU,		/* type */
+	 16,			/* rightshift */
+	 1,			/* size (0 = byte, 1 = short, 2 = long) */
+	 16,			/* bitsize */
+	 FALSE,			/* pc_relative */
+	 0,			/* bitpos */
+	 complain_overflow_bitfield, /* complain_on_overflow */
+	 0,			/* special_function */
+	 "R_TOCU",		/* name */
+	 TRUE,			/* partial_inplace */
+	 0,			/* src_mask */
+	 0xffff,		/* dst_mask */
+	 FALSE),		/* pcrel_offset */
 
   /* 0x31: Low-order 16 bit TOC relative relocation.  */
-  EMPTY_HOWTO (R_TOCL),
+  HOWTO (R_TOCL,		/* type */
+	 0,			/* rightshift */
+	 1,			/* size (0 = byte, 1 = short, 2 = long) */
+	 16,			/* bitsize */
+	 FALSE,			/* pc_relative */
+	 0,			/* bitpos */
+	 complain_overflow_dont, /* complain_on_overflow */
+	 0,			/* special_function */
+	 "R_TOCL",		/* name */
+	 TRUE,			/* partial_inplace */
+	 0,			/* src_mask */
+	 0xffff,		/* dst_mask */
+	 FALSE),		/* pcrel_offset */
 
 };
 
@@ -1145,6 +1169,10 @@ _bfd_xcoff_reloc_type_lookup (bfd *abfd ATTRIBUTE_UNUSED,
       return &xcoff_howto_table[8];
     case BFD_RELOC_PPC_TOC16:
       return &xcoff_howto_table[3];
+    case BFD_RELOC_PPC_TOC16_HI:
+      return &xcoff_howto_table[0x30];
+    case BFD_RELOC_PPC_TOC16_LO:
+      return &xcoff_howto_table[0x31];
     case BFD_RELOC_PPC_B16:
       return &xcoff_howto_table[0x1d];
     case BFD_RELOC_32:
@@ -2904,7 +2932,7 @@ xcoff_reloc_type_toc (bfd *input_bfd,
 		      asection *input_section ATTRIBUTE_UNUSED,
 		      bfd *output_bfd,
 		      struct internal_reloc *rel,
-		      struct internal_syment *sym,
+		      struct internal_syment *sym ATTRIBUTE_UNUSED,
 		      struct reloc_howto_struct *howto ATTRIBUTE_UNUSED,
 		      bfd_vma val,
 		      bfd_vma addend ATTRIBUTE_UNUSED,
@@ -2935,8 +2963,16 @@ xcoff_reloc_type_toc (bfd *input_bfd,
 	      + h->toc_section->output_offset);
     }
 
-  *relocation = ((val - xcoff_data (output_bfd)->toc)
-		 - (sym->n_value - xcoff_data (input_bfd)->toc));
+  /* We can't use the preexisting value written down by the
+     assembly, as R_TOCU needs to be adjusted when the final
+     R_TOCL value is signed.  */
+  *relocation = val - xcoff_data (output_bfd)->toc;
+
+  if (rel->r_type == R_TOCU)
+    *relocation = ((*relocation + 0x8000) >> 16) & 0xffff;
+  if (rel->r_type == R_TOCL)
+    *relocation = *relocation & 0x0000ffff;
+
   return TRUE;
 }
 
@@ -3299,8 +3335,6 @@ xcoff_complain_overflow_unsigned_func (bfd *input_bfd,
    quite figure out when this is useful.  These relocs are
    not defined by the PowerOpen ABI.
 
-   R_TOCU
-   R_TOCL
    R_TLS
    R_TLS_IE
    R_TLS_LD
@@ -3402,6 +3436,14 @@ xcoff_complain_overflow_unsigned_func (bfd *input_bfd,
    The PowerPC ABI defines this as an absolute branch to a
    fixed address which may be modified to a relative branch.
    The PowerOpen ABI does not define this relocation type.
+
+   R_TOCU:
+   Upper TOC relative relocation. The value is the
+   high-order 16 bit of a TOC relative relocation.
+
+   R_TOCL:
+   Lower TOC relative relocation. The value is the
+   low-order 16 bit of a TOC relative relocation.
 */
 
 bfd_boolean
diff --git a/bfd/coff64-rs6000.c b/bfd/coff64-rs6000.c
index 0cb7e8b1278..55a68218652 100644
--- a/bfd/coff64-rs6000.c
+++ b/bfd/coff64-rs6000.c
@@ -228,8 +228,8 @@ xcoff64_calculate_relocation[XCOFF_MAX_CALCULATE_RELOCATION] =
   xcoff_reloc_type_fail, /*           (0x2d) */
   xcoff_reloc_type_fail, /*           (0x2e) */
   xcoff_reloc_type_fail, /*           (0x2f) */
-  xcoff_reloc_type_fail, /* R_TOCU    (0x30) */
-  xcoff_reloc_type_fail, /* R_TOCL    (0x31) */
+  xcoff_reloc_type_toc, /* R_TOCU    (0x30) */
+  xcoff_reloc_type_toc, /* R_TOCL    (0x31) */
 };
 
 /* coffcode.h needs these to be defined.  */
@@ -899,7 +899,7 @@ reloc_howto_type xcoff64_howto_table[] =
 	 0,			/* special_function */
 	 "R_TOC",		/* name */
 	 TRUE,			/* partial_inplace */
-	 0xffff,		/* src_mask */
+	 0,			/* src_mask */
 	 0xffff,		/* dst_mask */
 	 FALSE),		/* pcrel_offset */
 
@@ -914,7 +914,7 @@ reloc_howto_type xcoff64_howto_table[] =
 	 0,			/* special_function */
 	 "R_TRL",		/* name */
 	 TRUE,			/* partial_inplace */
-	 0xffff,		/* src_mask */
+	 0,			/* src_mask */
 	 0xffff,		/* dst_mask */
 	 FALSE),		/* pcrel_offset */
 
@@ -929,7 +929,7 @@ reloc_howto_type xcoff64_howto_table[] =
 	 0,			/* special_function */
 	 "R_GL",		/* name */
 	 TRUE,			/* partial_inplace */
-	 0xffff,		/* src_mask */
+	 0,			/* src_mask */
 	 0xffff,		/* dst_mask */
 	 FALSE),		/* pcrel_offset */
 
@@ -944,7 +944,7 @@ reloc_howto_type xcoff64_howto_table[] =
 	 0,			/* special_function */
 	 "R_TCL",		/* name */
 	 TRUE,			/* partial_inplace */
-	 0xffff,		/* src_mask */
+	 0,			/* src_mask */
 	 0xffff,		/* dst_mask */
 	 FALSE),		/* pcrel_offset */
 
@@ -1260,11 +1260,34 @@ reloc_howto_type xcoff64_howto_table[] =
   EMPTY_HOWTO(0x2e),
   EMPTY_HOWTO(0x2f),
 
-  /* 0x30: High-order 16 bit TOC relative relocation.  */
-  EMPTY_HOWTO (R_TOCU),
+  HOWTO (R_TOCU,		/* type */
+	 16,			/* rightshift */
+	 1,			/* size (0 = byte, 1 = short, 2 = long) */
+	 16,			/* bitsize */
+	 FALSE,			/* pc_relative */
+	 0,			/* bitpos */
+	 complain_overflow_bitfield, /* complain_on_overflow */
+	 0,			/* special_function */
+	 "R_TOCU",		/* name */
+	 TRUE,			/* partial_inplace */
+	 0,			/* src_mask */
+	 0xffff,		/* dst_mask */
+	 FALSE),		/* pcrel_offset */
 
   /* 0x31: Low-order 16 bit TOC relative relocation.  */
-  EMPTY_HOWTO (R_TOCL),
+  HOWTO (R_TOCL,		/* type */
+	 0,			/* rightshift */
+	 1,			/* size (0 = byte, 1 = short, 2 = long) */
+	 16,			/* bitsize */
+	 FALSE,			/* pc_relative */
+	 0,			/* bitpos */
+	 complain_overflow_dont, /* complain_on_overflow */
+	 0,			/* special_function */
+	 "R_TOCL",		/* name */
+	 TRUE,			/* partial_inplace */
+	 0,			/* src_mask */
+	 0xffff,		/* dst_mask */
+	 FALSE),		/* pcrel_offset */
 
 };
 
@@ -1319,6 +1342,10 @@ xcoff64_reloc_type_lookup (bfd *abfd ATTRIBUTE_UNUSED,
       return &xcoff64_howto_table[8];
     case BFD_RELOC_PPC_TOC16:
       return &xcoff64_howto_table[3];
+    case BFD_RELOC_PPC_TOC16_HI:
+      return &xcoff64_howto_table[0x30];
+    case BFD_RELOC_PPC_TOC16_LO:
+      return &xcoff64_howto_table[0x31];
     case BFD_RELOC_PPC_B16:
       return &xcoff64_howto_table[0x1e];
     case BFD_RELOC_32:
diff --git a/bfd/libbfd.h b/bfd/libbfd.h
index 3a481ea468f..62b6bf82c50 100644
--- a/bfd/libbfd.h
+++ b/bfd/libbfd.h
@@ -1462,6 +1462,8 @@ static const char *const bfd_reloc_code_real_names[] = { "@@uninitialized@@",
   "BFD_RELOC_PPC_B26",
   "BFD_RELOC_PPC_BA26",
   "BFD_RELOC_PPC_TOC16",
+  "BFD_RELOC_PPC_TOC16_LO",
+  "BFD_RELOC_PPC_TOC16_HI",
   "BFD_RELOC_PPC_B16",
   "BFD_RELOC_PPC_B16_BRTAKEN",
   "BFD_RELOC_PPC_B16_BRNTAKEN",
diff --git a/bfd/reloc.c b/bfd/reloc.c
index 5ed7bb8e596..68645216c9d 100644
--- a/bfd/reloc.c
+++ b/bfd/reloc.c
@@ -2736,6 +2736,10 @@ ENUMX
   BFD_RELOC_PPC_BA26
 ENUMX
   BFD_RELOC_PPC_TOC16
+ENUMX
+  BFD_RELOC_PPC_TOC16_LO
+ENUMX
+  BFD_RELOC_PPC_TOC16_HI
 ENUMX
   BFD_RELOC_PPC_B16
 ENUMX
diff --git a/gas/ChangeLog b/gas/ChangeLog
index 91c9918191a..3713209f6d2 100644
--- a/gas/ChangeLog
+++ b/gas/ChangeLog
@@ -1,3 +1,21 @@
+2021-03-12  Clément Chigot  <clement.chigot@atos.net>
+
+	* config/tc-ppc.c (ppc_xcoff_suffix): New function.
+	(MAP, MAP32, MAP64): New macros for XCOFF.
+	(ppc_xcoff_fixup_addis): New function.
+	(ppc_is_toc_sym): Handle XMC_TE.
+	(fixup_size): Add cases for BFD_RELOC_PPC_TOC16_HI and
+	BFD_RELOC_PPC_TOC16_LO.
+	(md_assemble): Call ppc_xcoff_fixup_addis for XCOFF.
+	(ppc_change_csect): Handle XMC_TE.
+	(ppc_tc): Enable .tc symbols to have only a XMC_TC or XMC_TE
+	storage class.
+	(ppc_symbol_new_hook): Handle XMC_TE.
+	(ppc_frob_symbol): Likewise.
+	(ppc_fix_adjustable): Likewise.
+	(md_apply_fix): Handle BFD_RELOC_PPC_TOC16_HI and
+	BFD_RELOC_PPC_TOC16_LO.
+
 2021-03-10  Jan Beulich  <jbeulich@suse.com>
 
 	* testsuite/gas/i386/avx512f-intel.d,
diff --git a/gas/config/tc-ppc.c b/gas/config/tc-ppc.c
index 95000fd28a9..5ae90201e59 100644
--- a/gas/config/tc-ppc.c
+++ b/gas/config/tc-ppc.c
@@ -2646,6 +2646,87 @@ ppc_elf_adjust_symtab (void)
     }
 }
 #endif /* OBJ_ELF */
+
+#ifdef OBJ_XCOFF
+/* Parse XCOFF relocations.  */
+static bfd_reloc_code_real_type
+ppc_xcoff_suffix (char **str_p)
+{
+  struct map_bfd {
+    const char *string;
+    unsigned int length : 8;
+    unsigned int valid32 : 1;
+    unsigned int valid64 : 1;
+    unsigned int reloc;
+  };
+
+  char ident[20];
+  char *str = *str_p;
+  char *str2;
+  int ch;
+  int len;
+  const struct map_bfd *ptr;
+
+#define MAP(str, reloc)   { str, sizeof (str) - 1, 1, 1, reloc }
+#define MAP32(str, reloc) { str, sizeof (str) - 1, 1, 0, reloc }
+#define MAP64(str, reloc) { str, sizeof (str) - 1, 0, 1, reloc }
+
+  static const struct map_bfd mapping[] = {
+    MAP ("l",			BFD_RELOC_PPC_TOC16_LO),
+    MAP ("u",			BFD_RELOC_PPC_TOC16_HI),
+  };
+
+  if (*str++ != '@')
+    return BFD_RELOC_NONE;
+
+  for (ch = *str, str2 = ident;
+       (str2 < ident + sizeof (ident) - 1
+	&& (ISALNUM (ch) || ch == '@'));
+       ch = *++str)
+    {
+      *str2++ = TOLOWER (ch);
+    }
+
+  *str2 = '\0';
+  len = str2 - ident;
+
+  ch = ident[0];
+  for (ptr = &mapping[0]; ptr->length > 0; ptr++)
+    if (ch == ptr->string[0]
+	&& len == ptr->length
+	&& memcmp (ident, ptr->string, ptr->length) == 0
+	&& (ppc_obj64 ? ptr->valid64 : ptr->valid32))
+      {
+	*str_p = str;
+	return (bfd_reloc_code_real_type) ptr->reloc;
+      }
+
+  return BFD_RELOC_NONE;
+}
+
+/* Restore XCOFF addis instruction to ELF format.
+   AIX often generates addis instructions using "addis RT,D(RA)"
+   format instead of the ELF "addis RT,RA,SI" one.
+   On entry RT_E is at the comma after RT, D_E is at the open
+   parenthesis after D, and RA_E is at the close parenthesis after RA.  */
+static void
+ppc_xcoff_fixup_addis (char *rt_e, char *d_e, char *ra_e)
+{
+  size_t ra_size = ra_e - d_e - 1;
+  char *save_ra = xmalloc (ra_size);
+
+  /* Copy RA.  */
+  memcpy (save_ra, d_e + 1, ra_size);
+  /* Shuffle D to make room for RA, copying the comma too.  */
+  memmove (rt_e + ra_size + 1, rt_e, d_e - rt_e);
+  /* Erase the trailing ')', keeping any rubbish for potential errors.  */
+  memmove (ra_e, ra_e + 1, strlen (ra_e));
+  /* Write RA back.  */
+  memcpy (rt_e + 1, save_ra, ra_size);
+  free (save_ra);
+}
+
+#endif /* OBJ_XCOFF */
 
 #if defined (OBJ_XCOFF) || defined (OBJ_ELF)
 /* See whether a symbol is in the TOC section.  */
@@ -2655,6 +2736,7 @@ ppc_is_toc_sym (symbolS *sym)
 {
 #ifdef OBJ_XCOFF
   return (symbol_get_tc (sym)->symbol_class == XMC_TC
+	  || symbol_get_tc (sym)->symbol_class == XMC_TE
 	  || symbol_get_tc (sym)->symbol_class == XMC_TC0);
 #endif
 #ifdef OBJ_ELF
@@ -2920,6 +3002,8 @@ fixup_size (bfd_reloc_code_real_type reloc, bfd_boolean *pc_relative)
     case BFD_RELOC_PPC_GOT_TPREL16_HI:
     case BFD_RELOC_PPC_GOT_TPREL16_LO:
     case BFD_RELOC_PPC_TOC16:
+    case BFD_RELOC_PPC_TOC16_HI:
+    case BFD_RELOC_PPC_TOC16_LO:
     case BFD_RELOC_PPC_TPREL16:
     case BFD_RELOC_PPC_TPREL16_HA:
     case BFD_RELOC_PPC_TPREL16_HI:
@@ -3162,6 +3246,28 @@ md_assemble (char *str)
   while (ISSPACE (*str))
     ++str;
 
+#ifdef OBJ_XCOFF
+  /* AIX often generates addis instructions using "addis RT, D(RA)"
+     format instead of the classic "addis RT, RA, SI" one.
+     Restore it to the default format as it's the one encoded
+     in ppc opcodes.  */
+    if (!strcmp (opcode->name, "addis"))
+      {
+	char *rt_e = strchr (str, ',');
+	if (rt_e != NULL
+	    && strchr (rt_e + 1, ',') == NULL)
+	  {
+	    char *d_e = strchr (rt_e + 1, '(');
+	    if (d_e != NULL && d_e != rt_e + 1)
+	      {
+		char *ra_e = strrchr (d_e + 1, ')');
+		if (ra_e != NULL && ra_e != d_e + 1)
+		  ppc_xcoff_fixup_addis (rt_e, d_e, ra_e);
+	      }
+	  }
+      }
+#endif
+
   /* PowerPC operands are just expressions.  The only real issue is
      that a few operand types are optional.  If an instruction has
      multiple optional operands and one is omitted, then all optional
@@ -3558,6 +3664,9 @@ md_assemble (char *str)
 		}
 	    }
 #endif /* OBJ_ELF */
+#ifdef OBJ_XCOFF
+	  reloc = ppc_xcoff_suffix (&str);
+#endif /* OBJ_XCOFF */
 
 	  if (reloc != BFD_RELOC_NONE)
 	    ;
@@ -4336,6 +4445,7 @@ ppc_change_csect (symbolS *sym, offsetT align)
 	case XMC_RW:
 	case XMC_TC0:
 	case XMC_TC:
+	case XMC_TE:
 	case XMC_DS:
 	case XMC_UA:
 	case XMC_BS:
@@ -5383,7 +5493,21 @@ ppc_tc (int ignore ATTRIBUTE_UNUSED)
     S_SET_SEGMENT (sym, now_seg);
     symbol_set_frag (sym, frag_now);
     S_SET_VALUE (sym, (valueT) frag_now_fix ());
-    symbol_get_tc (sym)->symbol_class = XMC_TC;
+
+    /* AIX assembler seems to allow any storage class to be set in .tc.
+       But for now, only XMC_TC and XMC_TE are supported by us.  */
+    switch (symbol_get_tc (sym)->symbol_class)
+      {
+      case XMC_TC:
+      case XMC_TE:
+	break;
+
+      default:
+	as_bad (_(".tc with storage class %d not yet supported"),
+		symbol_get_tc (sym)->symbol_class);
+	ignore_rest_of_line ();
+	return;
+      }
     symbol_get_tc (sym)->output = 1;
 
     ppc_frob_label (sym);
@@ -5585,6 +5709,8 @@ ppc_symbol_new_hook (symbolS *sym)
 	tc->symbol_class = XMC_TB;
       else if (strcmp (s, "TC0]") == 0 || strcmp (s, "T0]") == 0)
 	tc->symbol_class = XMC_TC0;
+      else if (strcmp (s, "TE]") == 0)
+	tc->symbol_class = XMC_TE;
       break;
     case 'U':
       if (strcmp (s, "UA]") == 0)
@@ -5757,7 +5883,7 @@ ppc_frob_symbol (symbolS *sym)
 	  a->x_csect.x_scnlen.l = 0;
 	  a->x_csect.x_smtyp = XTY_ER;
 	}
-      else if (symbol_get_tc (sym)->symbol_class == XMC_TC)
+      else if (ppc_is_toc_sym (sym))
 	{
 	  symbolS *next;
 
@@ -5767,7 +5893,7 @@ ppc_frob_symbol (symbolS *sym)
 	  while (symbol_get_tc (next)->symbol_class == XMC_TC0)
 	    next = symbol_next (next);
 	  if (next == (symbolS *) NULL
-	      || symbol_get_tc (next)->symbol_class != XMC_TC)
+	      || (!ppc_is_toc_sym (next)))
 	    {
 	      if (ppc_after_toc_frag == (fragS *) NULL)
 		a->x_csect.x_scnlen.l = (bfd_section_size (data_section)
@@ -6053,7 +6179,8 @@ ppc_fix_adjustable (fixS *fix)
 
 	  if (sy_tc->symbol_class == XMC_TC0)
 	    continue;
-	  if (sy_tc->symbol_class != XMC_TC)
+	  if (sy_tc->symbol_class != XMC_TC
+	      && sy_tc->symbol_class != XMC_TE)
 	    break;
 	  if (val == resolve_symbol_value (sy))
 	    {
@@ -6072,6 +6199,7 @@ ppc_fix_adjustable (fixS *fix)
   if (tc->subseg == 0
       && tc->symbol_class != XMC_TC0
       && tc->symbol_class != XMC_TC
+      && tc->symbol_class != XMC_TE
       && symseg != bss_section
       /* Don't adjust if this is a reloc in the toc section.  */
       && (symseg != data_section
@@ -6516,8 +6644,7 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg)
 	  && (operand->insert == NULL || ppc_obj64)
 	  && fixP->fx_addsy != NULL
 	  && symbol_get_tc (fixP->fx_addsy)->subseg != 0
-	  && symbol_get_tc (fixP->fx_addsy)->symbol_class != XMC_TC
-	  && symbol_get_tc (fixP->fx_addsy)->symbol_class != XMC_TC0
+	  && !ppc_is_toc_sym (fixP->fx_addsy)
 	  && S_GET_SEGMENT (fixP->fx_addsy) != bss_section)
 	{
 	  value = fixP->fx_offset;
@@ -6531,7 +6658,7 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg)
        if (fixP->fx_r_type == BFD_RELOC_16
            && fixP->fx_addsy != NULL
            && ppc_is_toc_sym (fixP->fx_addsy))
-         fixP->fx_r_type = BFD_RELOC_PPC_TOC16;
+	 fixP->fx_r_type = BFD_RELOC_PPC_TOC16;
 #endif
     }
 
@@ -6984,6 +7111,8 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg)
 	case BFD_RELOC_PPC_EMB_RELSDA:
 	case BFD_RELOC_PPC64_TOC:
 	case BFD_RELOC_PPC_TOC16:
+	case BFD_RELOC_PPC_TOC16_LO:
+	case BFD_RELOC_PPC_TOC16_HI:
 	case BFD_RELOC_PPC64_TOC16_LO:
 	case BFD_RELOC_PPC64_TOC16_HI:
 	case BFD_RELOC_PPC64_TOC16_HA:
@@ -7061,7 +7190,9 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg)
 	symbol_get_bfdsym (fixP->fx_addsy)->flags |= BSF_KEEP;
     }
 #else
-  if (fixP->fx_r_type != BFD_RELOC_PPC_TOC16)
+  if (fixP->fx_r_type != BFD_RELOC_PPC_TOC16
+      && fixP->fx_r_type != BFD_RELOC_PPC_TOC16_HI
+      && fixP->fx_r_type != BFD_RELOC_PPC_TOC16_LO)
     fixP->fx_addnumber = 0;
   else
     {
@@ -7069,6 +7200,12 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg)
 	 of the symbol.  */
       fixP->fx_addnumber = (- bfd_section_vma (S_GET_SEGMENT (fixP->fx_addsy))
 			    - S_GET_VALUE (ppc_toc_csect));
+
+      /* The high bits must be adjusted for the low bits being signed.  */
+      if (fixP->fx_r_type == BFD_RELOC_PPC_TOC16_HI) {
+	fixP->fx_addnumber += 0x8000;
+      }
+
       /* Set *valP to avoid errors.  */
       *valP = value;
     }
diff --git a/ld/ChangeLog b/ld/ChangeLog
index acb8aabc991..024ea0df493 100644
--- a/ld/ChangeLog
+++ b/ld/ChangeLog
@@ -1,3 +1,13 @@
+2021-03-12  Clément Chigot  <clement.chigot@atos.net>
+
+	* scripttempl/aix.sc: Add .te to .data section.
+	* testsuite/ld-powerpc/aix52.exp: Add test structure for AIX7+.
+	Add aix-largetoc-1 test.
+	* testsuite/ld-powerpc/aix-largetoc-1-32.d: New test.
+	* testsuite/ld-powerpc/aix-largetoc-1-64.d: New test.
+	* testsuite/ld-powerpc/aix-largetoc-1.ex: New test.
+	* testsuite/ld-powerpc/aix-largetoc-1.s: New test.
+
 2021-03-12  Alan Modra  <amodra@gmail.com>
 
 	* testsuite/ld-gc/gc.exp: Pass "-image-base 0" to ld for PE, and
diff --git a/ld/scripttempl/aix.sc b/ld/scripttempl/aix.sc
index abf15fcb13b..aa129d98fb5 100644
--- a/ld/scripttempl/aix.sc
+++ b/ld/scripttempl/aix.sc
@@ -52,6 +52,7 @@ SECTIONS
     *(.tc0)
     *(.tc)
     *(.td)
+    *(.te)
     ${RELOCATING+PROVIDE (_edata = .);}
   }
   .bss : {
diff --git a/ld/testsuite/ld-powerpc/aix-largetoc-1-32.d b/ld/testsuite/ld-powerpc/aix-largetoc-1-32.d
new file mode 100644
index 00000000000..d39bc696eae
--- /dev/null
+++ b/ld/testsuite/ld-powerpc/aix-largetoc-1-32.d
@@ -0,0 +1,20 @@
+#source: aix-largetoc-1.s
+#as: -a32
+#ld: -b32 -shared -bE:aix-largetoc-1.ex
+#objdump: -dr
+#target: [is_xcoff_format]
+
+.*
+
+Disassembly of section \.text:
+
+.* <\.foo>:
+.*:	3d 22 00 00 	cau     r9,r2,0
+.*: R_TOCU	a-.*
+.*:	39 29 00 00 	cal     r9,0\(r9\)
+.*: R_TOCL	a-.*
+.*:	3d 22 00 00 	cau     r9,r2,0
+.*: R_TOCU	b-.*
+.*:	39 29 00 04 	cal     r9,4\(r9\)
+.*: R_TOCL	b-.*
+#...
diff --git a/ld/testsuite/ld-powerpc/aix-largetoc-1-64.d b/ld/testsuite/ld-powerpc/aix-largetoc-1-64.d
new file mode 100644
index 00000000000..daf6e0feb00
--- /dev/null
+++ b/ld/testsuite/ld-powerpc/aix-largetoc-1-64.d
@@ -0,0 +1,20 @@
+#source: aix-largetoc-1.s
+#as: -a64
+#ld: -b64 -shared -bE:aix-largetoc-1.ex
+#objdump: -dr
+#target: [is_xcoff_format]
+
+.*
+
+Disassembly of section \.text:
+
+.* <\.foo>:
+.*:	3d 22 00 00 	addis   r9,r2,0
+.*: R_TOCU	a-.*
+.*:	39 29 00 00 	addi    r9,r9,0
+.*: R_TOCL	a-.*
+.*:	3d 22 00 00 	addis   r9,r2,0
+.*: R_TOCU	b-.*
+.*:	39 29 00 08 	addi    r9,r9,8
+.*: R_TOCL	b-.*
+#...
diff --git a/ld/testsuite/ld-powerpc/aix-largetoc-1.ex b/ld/testsuite/ld-powerpc/aix-largetoc-1.ex
new file mode 100644
index 00000000000..257cc5642cb
--- /dev/null
+++ b/ld/testsuite/ld-powerpc/aix-largetoc-1.ex
@@ -0,0 +1 @@
+foo
diff --git a/ld/testsuite/ld-powerpc/aix-largetoc-1.s b/ld/testsuite/ld-powerpc/aix-largetoc-1.s
new file mode 100644
index 00000000000..278390300c2
--- /dev/null
+++ b/ld/testsuite/ld-powerpc/aix-largetoc-1.s
@@ -0,0 +1,25 @@
+	.globl a
+	.csect .data[RW]
+a:
+	.long	1
+	.toc
+	.tc a[TE],a
+	.tc b[TE],a
+
+	.globl foo
+	.globl .foo
+	.csect foo[DS],3
+foo:
+	.if size == 32
+	.long .foo, TOC[tc0], 0
+	.else
+	.llong .foo, TOC[tc0], 0
+	.endif
+
+	.csect .text[PR]
+.foo:
+	addis 9,a[TE]@u(2)
+	la 9,a[TE]@l(9)
+
+	addis 9,b[TE]@u(2)
+	la 9,b[TE]@l(9)
diff --git a/ld/testsuite/ld-powerpc/aix52.exp b/ld/testsuite/ld-powerpc/aix52.exp
index d5e8b2f8afd..3dfbae00625 100644
--- a/ld/testsuite/ld-powerpc/aix52.exp
+++ b/ld/testsuite/ld-powerpc/aix52.exp
@@ -266,3 +266,20 @@ run_dump_test "aix-glink-3-32"
 run_dump_test "aix-glink-3-64"
 run_dump_test "aix-weak-3-32"
 run_dump_test "aix-weak-3-64"
+
+
+# Tests added for features in AIX 7+.
+
+set aix7tests {
+    {"Large TOC test 1" "-shared -bE:aix-largetoc-1.ex"
+	"" {aix-largetoc-1.s}
+	{{objdump -dr aix-largetoc-1-SIZE.d}}
+	"aix-largetoc-1.so"}
+}
+
+foreach test $aix7tests {
+    foreach { name ldopts asopts sources tools output } $test {
+	run_aix_test 32 $name $ldopts $asopts $sources $tools $output
+	run_aix_test 64 $name $ldopts $asopts $sources $tools $output
+    }
+}