RISC-V: Relax RISCV_PCREL_* to RISCV_GPREL_*
[binutils-gdb.git] / bfd / elfnn-riscv.c
index bd88ed78eb180f01b539493b669aadee263e4899..f7cdb4e2b0f9451fa5d4e48bb5a629a74e80873d 100644 (file)
@@ -2684,10 +2684,155 @@ riscv_relax_delete_bytes (bfd *abfd, asection *sec, bfd_vma addr, size_t count)
   return TRUE;
 }
 
+/* A second format for recording PC-relative hi relocations.  This stores the
+   information required to relax them to GP-relative addresses.  */
+
+typedef struct riscv_pcgp_hi_reloc riscv_pcgp_hi_reloc;
+struct riscv_pcgp_hi_reloc
+{
+  bfd_vma hi_sec_off;
+  bfd_vma hi_addend;
+  bfd_vma hi_addr;
+  unsigned hi_sym;
+  asection *sym_sec;
+  riscv_pcgp_hi_reloc *next;
+};
+
+typedef struct riscv_pcgp_lo_reloc riscv_pcgp_lo_reloc;
+struct riscv_pcgp_lo_reloc
+{
+  bfd_vma hi_sec_off;
+  riscv_pcgp_lo_reloc *next;
+};
+
+typedef struct
+{
+  riscv_pcgp_hi_reloc *hi;
+  riscv_pcgp_lo_reloc *lo;
+} riscv_pcgp_relocs;
+
+static bfd_boolean
+riscv_init_pcgp_relocs (riscv_pcgp_relocs *p)
+{
+  p->hi = NULL;
+  p->lo = NULL;
+  return TRUE;
+}
+
+static void
+riscv_free_pcgp_relocs (riscv_pcgp_relocs *p,
+                       bfd *abfd ATTRIBUTE_UNUSED,
+                       asection *sec ATTRIBUTE_UNUSED)
+{
+  riscv_pcgp_hi_reloc *c;
+  riscv_pcgp_lo_reloc *l;
+
+  for (c = p->hi; c != NULL;)
+    {
+      riscv_pcgp_hi_reloc *next = c->next;
+      free (c);
+      c = next;
+    }
+
+  for (l = p->lo; l != NULL;)
+    {
+      riscv_pcgp_lo_reloc *next = l->next;
+      free (l);
+      l = next;
+    }
+}
+
+static bfd_boolean
+riscv_record_pcgp_hi_reloc (riscv_pcgp_relocs *p, bfd_vma hi_sec_off,
+                           bfd_vma hi_addend, bfd_vma hi_addr,
+                           unsigned hi_sym, asection *sym_sec)
+{
+  riscv_pcgp_hi_reloc *new = bfd_malloc (sizeof(*new));
+  if (!new)
+    return FALSE;
+  new->hi_sec_off = hi_sec_off;
+  new->hi_addend = hi_addend;
+  new->hi_addr = hi_addr;
+  new->hi_sym = hi_sym;
+  new->sym_sec = sym_sec;
+  new->next = p->hi;
+  p->hi = new;
+  return TRUE;
+}
+
+static riscv_pcgp_hi_reloc *
+riscv_find_pcgp_hi_reloc(riscv_pcgp_relocs *p, bfd_vma hi_sec_off)
+{
+  riscv_pcgp_hi_reloc *c;
+
+  for (c = p->hi; c != NULL; c = c->next)
+    if (c->hi_sec_off == hi_sec_off)
+      return c;
+  return NULL;
+}
+
+static bfd_boolean
+riscv_delete_pcgp_hi_reloc(riscv_pcgp_relocs *p, bfd_vma hi_sec_off)
+{
+  bfd_boolean out = FALSE;
+  riscv_pcgp_hi_reloc *c;
+
+  for (c = p->hi; c != NULL; c = c->next)
+      if (c->hi_sec_off == hi_sec_off)
+       out = TRUE;
+
+  return out;
+}
+
+static bfd_boolean
+riscv_use_pcgp_hi_reloc(riscv_pcgp_relocs *p, bfd_vma hi_sec_off)
+{
+  bfd_boolean out = FALSE;
+  riscv_pcgp_hi_reloc *c;
+
+  for (c = p->hi; c != NULL; c = c->next)
+    if (c->hi_sec_off == hi_sec_off)
+      out = TRUE;
+
+  return out;
+}
+
+static bfd_boolean
+riscv_record_pcgp_lo_reloc (riscv_pcgp_relocs *p, bfd_vma hi_sec_off)
+{
+  riscv_pcgp_lo_reloc *new = bfd_malloc (sizeof(*new));
+  if (!new)
+    return FALSE;
+  new->hi_sec_off = hi_sec_off;
+  new->next = p->lo;
+  p->lo = new;
+  return TRUE;
+}
+
+static bfd_boolean
+riscv_find_pcgp_lo_reloc (riscv_pcgp_relocs *p, bfd_vma hi_sec_off)
+{
+  riscv_pcgp_lo_reloc *c;
+
+  for (c = p->lo; c != NULL; c = c->next)
+    if (c->hi_sec_off == hi_sec_off)
+      return TRUE;
+  return FALSE;
+}
+
+static bfd_boolean
+riscv_delete_pcgp_lo_reloc (riscv_pcgp_relocs *p ATTRIBUTE_UNUSED,
+                           bfd_vma lo_sec_off ATTRIBUTE_UNUSED,
+                           size_t bytes ATTRIBUTE_UNUSED)
+{
+  return TRUE;
+}
+
 typedef bfd_boolean (*relax_func_t) (bfd *, asection *, asection *,
                                     struct bfd_link_info *,
                                     Elf_Internal_Rela *,
-                                    bfd_vma, bfd_vma, bfd_vma, bfd_boolean *);
+                                    bfd_vma, bfd_vma, bfd_vma, bfd_boolean *,
+                                    riscv_pcgp_relocs *);
 
 /* Relax AUIPC + JALR into JAL.  */
 
@@ -2698,7 +2843,8 @@ _bfd_riscv_relax_call (bfd *abfd, asection *sec, asection *sym_sec,
                       bfd_vma symval,
                       bfd_vma max_alignment,
                       bfd_vma reserve_size ATTRIBUTE_UNUSED,
-                      bfd_boolean *again)
+                      bfd_boolean *again,
+                      riscv_pcgp_relocs *pcgp_relocs ATTRIBUTE_UNUSED)
 {
   bfd_byte *contents = elf_section_data (sec)->this_hdr.contents;
   bfd_signed_vma foff = symval - (sec_addr (sec) + rel->r_offset);
@@ -2781,7 +2927,8 @@ _bfd_riscv_relax_lui (bfd *abfd,
                      bfd_vma symval,
                      bfd_vma max_alignment,
                      bfd_vma reserve_size,
-                     bfd_boolean *again)
+                     bfd_boolean *again,
+                     riscv_pcgp_relocs *pcgp_relocs ATTRIBUTE_UNUSED)
 {
   bfd_byte *contents = elf_section_data (sec)->this_hdr.contents;
   bfd_vma gp = riscv_global_pointer_value (link_info);
@@ -2870,7 +3017,8 @@ _bfd_riscv_relax_tls_le (bfd *abfd,
                         bfd_vma symval,
                         bfd_vma max_alignment ATTRIBUTE_UNUSED,
                         bfd_vma reserve_size ATTRIBUTE_UNUSED,
-                        bfd_boolean *again)
+                        bfd_boolean *again,
+                        riscv_pcgp_relocs *prcel_relocs ATTRIBUTE_UNUSED)
 {
   /* See if this symbol is in range of tp.  */
   if (RISCV_CONST_HIGH_PART (tpoff (link_info, symval)) != 0)
@@ -2909,7 +3057,8 @@ _bfd_riscv_relax_align (bfd *abfd, asection *sec,
                        bfd_vma symval,
                        bfd_vma max_alignment ATTRIBUTE_UNUSED,
                        bfd_vma reserve_size ATTRIBUTE_UNUSED,
-                       bfd_boolean *again ATTRIBUTE_UNUSED)
+                       bfd_boolean *again ATTRIBUTE_UNUSED,
+                       riscv_pcgp_relocs *pcrel_relocs ATTRIBUTE_UNUSED)
 {
   bfd_byte *contents = elf_section_data (sec)->this_hdr.contents;
   bfd_vma alignment = 1, pos;
@@ -2956,6 +3105,118 @@ _bfd_riscv_relax_align (bfd *abfd, asection *sec,
 
 /* Relax PC-relative references to GP-relative references.  */
 
+static bfd_boolean
+_bfd_riscv_relax_pc  (bfd *abfd,
+                     asection *sec,
+                     asection *sym_sec,
+                     struct bfd_link_info *link_info,
+                     Elf_Internal_Rela *rel,
+                     bfd_vma symval,
+                     bfd_vma max_alignment,
+                     bfd_vma reserve_size,
+                     bfd_boolean *again ATTRIBUTE_UNUSED,
+                     riscv_pcgp_relocs *pcgp_relocs)
+{
+  bfd_vma gp = riscv_global_pointer_value (link_info);
+
+  BFD_ASSERT (rel->r_offset + 4 <= sec->size);
+
+  /* Chain the _LO relocs to their cooresponding _HI reloc to compute the
+   * actual target address.  */
+  riscv_pcgp_hi_reloc hi_reloc = {0};
+  switch (ELFNN_R_TYPE (rel->r_info))
+    {
+    case R_RISCV_PCREL_LO12_I:
+    case R_RISCV_PCREL_LO12_S:
+      {
+       riscv_pcgp_hi_reloc *hi = riscv_find_pcgp_hi_reloc (pcgp_relocs,
+                                                           symval - sec_addr(sym_sec));
+       if (hi == NULL)
+         {
+           riscv_record_pcgp_lo_reloc (pcgp_relocs, symval - sec_addr(sym_sec));
+           return TRUE;
+         }
+
+       hi_reloc = *hi;
+       symval = hi_reloc.hi_addr;
+       sym_sec = hi_reloc.sym_sec;
+       if (!riscv_use_pcgp_hi_reloc(pcgp_relocs, hi->hi_sec_off))
+         (*_bfd_error_handler)
+          (_("%B(%A+0x%lx): Unable to clear RISCV_PCREL_HI20 reloc"
+             "for cooresponding RISCV_PCREL_LO12 reloc"),
+           abfd, sec, rel->r_offset);
+      }
+      break;
+
+    case R_RISCV_PCREL_HI20:
+      /* Mergeable symbols and code might later move out of range.  */
+      if (sym_sec->flags & (SEC_MERGE | SEC_CODE))
+       return TRUE;
+
+      /* If the cooresponding lo relocation has already been seen then it's not
+       * safe to relax this relocation.  */
+      if (riscv_find_pcgp_lo_reloc (pcgp_relocs, rel->r_offset))
+        return TRUE;
+
+      break;
+
+    default:
+      abort ();
+    }
+
+  if (gp)
+    {
+      /* If gp and the symbol are in the same output section, then
+        consider only that section's alignment.  */
+      struct bfd_link_hash_entry *h =
+       bfd_link_hash_lookup (link_info->hash, RISCV_GP_SYMBOL, FALSE, FALSE, TRUE);
+      if (h->u.def.section->output_section == sym_sec->output_section)
+       max_alignment = (bfd_vma) 1 << sym_sec->output_section->alignment_power;
+    }
+
+  /* Is the reference in range of x0 or gp?
+     Valid gp range conservatively because of alignment issue.  */
+  if (VALID_ITYPE_IMM (symval)
+      || (symval >= gp
+         && VALID_ITYPE_IMM (symval - gp + max_alignment + reserve_size))
+      || (symval < gp
+         && VALID_ITYPE_IMM (symval - gp - max_alignment - reserve_size)))
+    {
+      unsigned sym = hi_reloc.hi_sym;
+      switch (ELFNN_R_TYPE (rel->r_info))
+       {
+       case R_RISCV_PCREL_LO12_I:
+         rel->r_info = ELFNN_R_INFO (sym, R_RISCV_GPREL_I);
+         rel->r_addend += hi_reloc.hi_addend;
+         return riscv_delete_pcgp_lo_reloc (pcgp_relocs, rel->r_offset, 4);
+
+       case R_RISCV_PCREL_LO12_S:
+         rel->r_info = ELFNN_R_INFO (sym, R_RISCV_GPREL_S);
+         rel->r_addend += hi_reloc.hi_addend;
+         return riscv_delete_pcgp_lo_reloc (pcgp_relocs, rel->r_offset, 4);
+
+       case R_RISCV_PCREL_HI20:
+          riscv_record_pcgp_hi_reloc (pcgp_relocs,
+                                     rel->r_offset,
+                                     rel->r_addend,
+                                     symval,
+                                     ELFNN_R_SYM(rel->r_info),
+                                     sym_sec);
+         /* We can delete the unnecessary AUIPC and reloc.  */
+         rel->r_info = ELFNN_R_INFO (0, R_RISCV_DELETE);
+         rel->r_addend = 4;
+         return riscv_delete_pcgp_hi_reloc (pcgp_relocs, rel->r_offset);
+
+       default:
+         abort ();
+       }
+    }
+
+  return TRUE;
+}
+
+/* Relax PC-relative references to GP-relative references.  */
+
 static bfd_boolean
 _bfd_riscv_relax_delete (bfd *abfd,
                         asection *sec,
@@ -2965,7 +3226,8 @@ _bfd_riscv_relax_delete (bfd *abfd,
                         bfd_vma symval ATTRIBUTE_UNUSED,
                         bfd_vma max_alignment ATTRIBUTE_UNUSED,
                         bfd_vma reserve_size ATTRIBUTE_UNUSED,
-                        bfd_boolean *again ATTRIBUTE_UNUSED)
+                        bfd_boolean *again ATTRIBUTE_UNUSED,
+                        riscv_pcgp_relocs *pcgp_relocs ATTRIBUTE_UNUSED)
 {
   if (!riscv_relax_delete_bytes(abfd, sec, rel->r_offset, rel->r_addend))
     return FALSE;
@@ -2989,6 +3251,7 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
   bfd_boolean ret = FALSE;
   unsigned int i;
   bfd_vma max_alignment, reserve_size = 0;
+  riscv_pcgp_relocs pcgp_relocs;
 
   *again = FALSE;
 
@@ -3000,6 +3263,8 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
          && info->relax_pass == 0))
     return TRUE;
 
+  riscv_init_pcgp_relocs (&pcgp_relocs);
+
   /* Read this BFD's relocs if we haven't done so already.  */
   if (data->relocs)
     relocs = data->relocs;
@@ -3037,6 +3302,11 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
                   || type == R_RISCV_LO12_I
                   || type == R_RISCV_LO12_S)
            relax_func = _bfd_riscv_relax_lui;
+         else if (!bfd_link_pic(info)
+                  && (type == R_RISCV_PCREL_HI20
+                  || type == R_RISCV_PCREL_LO12_I
+                  || type == R_RISCV_PCREL_LO12_S))
+           relax_func = _bfd_riscv_relax_pc;
          else if (type == R_RISCV_TPREL_HI20
                   || type == R_RISCV_TPREL_ADD
                   || type == R_RISCV_TPREL_LO12_I
@@ -3127,7 +3397,8 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
       symval += rel->r_addend;
 
       if (!relax_func (abfd, sec, sym_sec, info, rel, symval,
-                      max_alignment, reserve_size, again))
+                      max_alignment, reserve_size, again,
+                      &pcgp_relocs))
        goto fail;
     }
 
@@ -3136,6 +3407,7 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
 fail:
   if (relocs != data->relocs)
     free (relocs);
+  riscv_free_pcgp_relocs(&pcgp_relocs, abfd, sec);
 
   return ret;
 }