From d733abde65adca030288819c2221953ed40d7a64 Mon Sep 17 00:00:00 2001
From: Kuan-Lin Chen <rufus@andestech.com>
Date: Thu, 14 Nov 2019 14:24:22 +0800
Subject: [PATCH] RISC-V: Support subtraction of .uleb128.

The data length of uleb128 is variable.  So linker must recalculate the
value of the subtraction.  The patch leave relocations in object files
so that linker can relocate again after relaxation.

bfd/ChangeLog:
	* bfd-in2.h: Regenerated.
	* elfnn-riscv.c (write_uleb128): New function.
	(perform_relocation): Perform R_RISCV_SUB_ULEB128 and
	 R_RISCV_SET_ULEB128 relocation.
	(riscv_elf_relocate_section): Likewise.
	* elfxx-riscv.c (howto_table): Add R_RISCV_SUB_ULEB128 and
	R_RISCV_SET_ULEB128.
	(riscv_elf_ignore_reloc): New function.
	* libbfd.h: Regenerated.
	* reloc.c (BFD_RELOC_RISCV_SET_ULEB128, BFD_RELOC_RISCV_SUB_ULEB128):
	New relocations to support .uleb128.

gas/ChangeLog:
	* config/tc-riscv.c (md_apply_fix): Add BFD_RELOC_RISCV_SET_ULEB128 and
	BFD_RELOC_RISCV_SUB_ULEB128.
	(riscv_insert_uleb128_fixes): New function.
	(riscv_md_end): Scan rs_leb128 fragments.
	(riscv_pseudo_table): Remove uleb128.

include/ChangeLog:
	* elf/riscv.h ((R_RISCV_SET_ULEB128, (R_RISCV_SUB_ULEB128): Define.

ld/ChangeLog:
	* testsuite/ld-riscv-elf/ld-riscv-elf.exp: Add uleb128.
	* testsuite/ld-riscv-elf/uleb128.d: New test.
	* testsuite/ld-riscv-elf/uleb128.s: New file.
---
 bfd/ChangeLog                              | 14 ++++
 bfd/bfd-in2.h                              |  2 +
 bfd/elfnn-riscv.c                          | 84 +++++++++++++++++++++-
 bfd/elfxx-riscv.c                          | 51 +++++++++++++
 bfd/libbfd.h                               |  2 +
 bfd/reloc.c                                |  4 ++
 gas/ChangeLog                              |  8 +++
 gas/config/tc-riscv.c                      | 55 +++++++++++++-
 include/ChangeLog                          |  4 ++
 include/elf/riscv.h                        |  2 +
 ld/ChangeLog                               |  6 ++
 ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp |  1 +
 ld/testsuite/ld-riscv-elf/uleb128.d        | 18 +++++
 ld/testsuite/ld-riscv-elf/uleb128.s        | 18 +++++
 14 files changed, 266 insertions(+), 3 deletions(-)
 create mode 100644 ld/testsuite/ld-riscv-elf/uleb128.d
 create mode 100644 ld/testsuite/ld-riscv-elf/uleb128.s

diff --git a/bfd/ChangeLog b/bfd/ChangeLog
index 45f2499735..94082f6213 100644
--- a/bfd/ChangeLog
+++ b/bfd/ChangeLog
@@ -1,3 +1,17 @@
+2020-01-22  Kuan-Lin Chen  <kuanlinchentw@gmail.com>
+
+	* bfd-in2.h: Regenerated.
+	* elfnn-riscv.c (write_uleb128): New function.
+	(perform_relocation): Perform R_RISCV_SUB_ULEB128 and
+	 R_RISCV_SET_ULEB128 relocation.
+	(riscv_elf_relocate_section): Likewise.
+	* elfxx-riscv.c (howto_table): Add R_RISCV_SUB_ULEB128 and
+	R_RISCV_SET_ULEB128.
+	(riscv_elf_ignore_reloc): New function.
+	* libbfd.h: Regenerated.
+	* reloc.c (BFD_RELOC_RISCV_SET_ULEB128, BFD_RELOC_RISCV_SUB_ULEB128):
+	New relocations to support .uleb128.
+
 2020-01-20  Nick Clifton  <nickc@redhat.com>
 
 	* po/pt.po: Updates Portuguese translation.
diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index 7c13bc8c91..e43c3a5557 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -4383,6 +4383,8 @@ number for the SBIC, SBIS, SBI and CBI instructions  */
   BFD_RELOC_RISCV_SET16,
   BFD_RELOC_RISCV_SET32,
   BFD_RELOC_RISCV_32_PCREL,
+  BFD_RELOC_RISCV_SET_ULEB128,
+  BFD_RELOC_RISCV_SUB_ULEB128,
 
 /* Renesas RL78 Relocations.  */
   BFD_RELOC_RL78_NEG8,
diff --git a/bfd/elfnn-riscv.c b/bfd/elfnn-riscv.c
index 46f0100ace..74c19bf0fc 100644
--- a/bfd/elfnn-riscv.c
+++ b/bfd/elfnn-riscv.c
@@ -1419,6 +1419,26 @@ riscv_global_pointer_value (struct bfd_link_info *info)
   return h->u.def.value + sec_addr (h->u.def.section);
 }
 
+/* Write VAL in uleb128 format to P, returning a pointer to the
+   following byte.
+   This code is copied from elf-attr.c.  */
+
+static bfd_byte *
+write_uleb128 (bfd_byte *p, unsigned int val)
+{
+  bfd_byte c;
+  do
+    {
+      c = val & 0x7f;
+      val >>= 7;
+      if (val)
+	c |= 0x80;
+      *(p++) = c;
+    }
+  while (val);
+  return p;
+}
+
 /* Emplace a static relocation.  */
 
 static bfd_reloc_status_type
@@ -1512,6 +1532,25 @@ perform_relocation (const reloc_howto_type *howto,
 	value = ENCODE_RVC_LUI_IMM (RISCV_CONST_HIGH_PART (value));
       break;
 
+    case R_RISCV_SET_ULEB128:
+    case R_RISCV_SUB_ULEB128:
+      {
+	unsigned int len = 0;
+	bfd_byte *endp, *p;
+
+	_bfd_read_unsigned_leb128 (input_bfd, contents + rel->r_offset, &len);
+
+	/* Clean the contents value to zero.  Do not reduce the length.  */
+	p = contents + rel->r_offset;
+	endp = p + len -1;
+	memset (p, 0x80, len - 1);
+	*(endp) = 0;
+	p = write_uleb128 (p, value) - 1;
+	if (p < endp)
+	  *p |= 0x80;
+	return bfd_reloc_ok;
+      }
+
     case R_RISCV_32:
     case R_RISCV_64:
     case R_RISCV_ADD8:
@@ -1767,6 +1806,8 @@ riscv_elf_relocate_section (bfd *output_bfd,
   struct elf_link_hash_entry **sym_hashes = elf_sym_hashes (input_bfd);
   bfd_vma *local_got_offsets = elf_local_got_offsets (input_bfd);
   bfd_boolean absolute;
+  bfd_vma uleb128_vma = 0;
+  Elf_Internal_Rela *uleb128_rel = NULL;
 
   if (!riscv_init_pcrel_relocs (&pcrel_relocs))
     return FALSE;
@@ -1778,7 +1819,7 @@ riscv_elf_relocate_section (bfd *output_bfd,
       struct elf_link_hash_entry *h;
       Elf_Internal_Sym *sym;
       asection *sec;
-      bfd_vma relocation;
+      bfd_vma relocation = 0;
       bfd_reloc_status_type r = bfd_reloc_ok;
       const char *name;
       bfd_vma off, ie_off;
@@ -1871,6 +1912,47 @@ riscv_elf_relocate_section (bfd *output_bfd,
 	case R_RISCV_DELETE:
 	  /* These require no special handling beyond perform_relocation.  */
 	  break;
+	case R_RISCV_SET_ULEB128:
+	  if (!uleb128_rel)
+	    {
+	      /* Save the minuend to use later.  */
+	      uleb128_vma = relocation;
+	      uleb128_rel = rel;
+	      continue;
+	    }
+	  else
+	    {
+	      if (uleb128_rel->r_offset != rel->r_offset)
+		{
+		  msg = ("R_RISCV_SET_ULEB128 and R_RISCV_SUB_ULEB128 are mismatched. ");
+		  r = bfd_reloc_dangerous;
+		  break;
+		}
+	      relocation = relocation - uleb128_vma;
+	      uleb128_rel = NULL;
+	      break;
+	    }
+
+	case R_RISCV_SUB_ULEB128:
+	  if (uleb128_rel)
+	    {
+	      if (uleb128_rel->r_offset != rel->r_offset)
+		{
+		  msg = ("R_RISCV_SET_ULEB128 and R_RISCV_SUB_ULEB128 are mismatched. ");
+		  r = bfd_reloc_dangerous;
+		  break;
+		}
+	      relocation = uleb128_vma - relocation;
+	      uleb128_rel = NULL;
+	      break;
+	    }
+	  else
+	    {
+	      /* Save the subtrahend to use later.  */
+	      uleb128_vma = relocation;
+	      uleb128_rel = rel;
+	      continue;
+	    }
 
 	case R_RISCV_GOT_HI20:
 	  if (h != NULL)
diff --git a/bfd/elfxx-riscv.c b/bfd/elfxx-riscv.c
index 66e343f4a5..244353573b 100644
--- a/bfd/elfxx-riscv.c
+++ b/bfd/elfxx-riscv.c
@@ -38,6 +38,8 @@
    relocations for the debug info.  */
 static bfd_reloc_status_type riscv_elf_add_sub_reloc
   (bfd *, arelent *, asymbol *, void *, asection *, bfd *, char **);
+static bfd_reloc_status_type riscv_elf_ignore_reloc
+  (bfd *, arelent *, asymbol *, void *, asection *, bfd *, char **);
 
 /* The relocation table used for SHT_RELA sections.  */
 
@@ -855,8 +857,41 @@ static reloc_howto_type howto_table[] =
 	 0,				/* src_mask */
 	 MINUS_ONE,			/* dst_mask */
 	 FALSE),			/* pcrel_offset */
+
+  /* The length of unsigned-leb128 is variable, just assume the
+     size is one byte here.  */
+  HOWTO (R_RISCV_SET_ULEB128,		/* type */
+	 0,				/* rightshift */
+	 0,				/* size */
+	 0,				/* bitsize */
+	 FALSE,				/* pc_relative */
+	 0,				/* bitpos */
+	 complain_overflow_dont,	/* complain_on_overflow */
+	 riscv_elf_ignore_reloc,	/* special_function */
+	 "R_RISCV_SET_ULEB128",		/* name */
+	 FALSE,				/* partial_inplace */
+	 0,				/* src_mask */
+	 0,				/* dst_mask */
+	 FALSE),			/* pcrel_offset */
+
+  /* The length of unsigned-leb128 is variable, just assume the
+     size is one byte here.  */
+  HOWTO (R_RISCV_SUB_ULEB128,		/* type */
+	 0,				/* rightshift */
+	 0,				/* size */
+	 0,				/* bitsize */
+	 FALSE,				/* pc_relative */
+	 0,				/* bitpos */
+	 complain_overflow_dont,	/* complain_on_overflow */
+	 riscv_elf_ignore_reloc,	/* special_function */
+	 "R_RISCV_SUB_ULEB128",		/* name */
+	 FALSE,				/* partial_inplace */
+	 0,				/* src_mask */
+	 0,				/* dst_mask */
+	 FALSE),			/* pcrel_offset */
 };
 
+
 /* A mapping from BFD reloc types to RISC-V ELF reloc types.  */
 
 struct elf_reloc_map
@@ -917,6 +952,8 @@ static const struct elf_reloc_map riscv_reloc_map[] =
   { BFD_RELOC_RISCV_SET16, R_RISCV_SET16 },
   { BFD_RELOC_RISCV_SET32, R_RISCV_SET32 },
   { BFD_RELOC_RISCV_32_PCREL, R_RISCV_32_PCREL },
+  { BFD_RELOC_RISCV_SET_ULEB128, R_RISCV_SET_ULEB128 },
+  { BFD_RELOC_RISCV_SUB_ULEB128, R_RISCV_SUB_ULEB128 },
 };
 
 /* Given a BFD reloc type, return a howto structure.  */
@@ -1011,6 +1048,20 @@ riscv_elf_add_sub_reloc (bfd *abfd,
   return bfd_reloc_ok;
 }
 
+/* Special handler for relocations which don't have to be relocated.
+   This function just simply return bfd_reloc_ok.  */
+static bfd_reloc_status_type
+riscv_elf_ignore_reloc (bfd *abfd ATTRIBUTE_UNUSED, arelent *reloc_entry,
+			asymbol *symbol ATTRIBUTE_UNUSED,
+			void *data ATTRIBUTE_UNUSED, asection *input_section,
+			bfd *output_bfd, char **error_message ATTRIBUTE_UNUSED)
+{
+  if (output_bfd != NULL)
+    reloc_entry->address += input_section->output_offset;
+
+  return bfd_reloc_ok;
+}
+
 /* Parsing subset version.
 
    Return Value:
diff --git a/bfd/libbfd.h b/bfd/libbfd.h
index d97d4e57a7..74d12d8b99 100644
--- a/bfd/libbfd.h
+++ b/bfd/libbfd.h
@@ -2327,6 +2327,8 @@ static const char *const bfd_reloc_code_real_names[] = { "@@uninitialized@@",
   "BFD_RELOC_RISCV_SET16",
   "BFD_RELOC_RISCV_SET32",
   "BFD_RELOC_RISCV_32_PCREL",
+  "BFD_RELOC_RISCV_SET_ULEB128",
+  "BFD_RELOC_RISCV_SUB_ULEB128",
   "BFD_RELOC_RL78_NEG8",
   "BFD_RELOC_RL78_NEG16",
   "BFD_RELOC_RL78_NEG24",
diff --git a/bfd/reloc.c b/bfd/reloc.c
index 33cd67150c..9b797e7920 100644
--- a/bfd/reloc.c
+++ b/bfd/reloc.c
@@ -5212,6 +5212,10 @@ ENUMX
   BFD_RELOC_RISCV_SET32
 ENUMX
   BFD_RELOC_RISCV_32_PCREL
+ENUMX
+  BFD_RELOC_RISCV_SET_ULEB128
+ENUMX
+  BFD_RELOC_RISCV_SUB_ULEB128
 ENUMDOC
   RISC-V relocations.
 
diff --git a/gas/ChangeLog b/gas/ChangeLog
index 8f414396f5..3beb4060c2 100644
--- a/gas/ChangeLog
+++ b/gas/ChangeLog
@@ -1,3 +1,11 @@
+2020-01-22  Kuan-Lin Chen  <kuanlinchentw@gmail.com>
+
+	* config/tc-riscv.c (md_apply_fix): Add BFD_RELOC_RISCV_SET_ULEB128 and
+	BFD_RELOC_RISCV_SUB_ULEB128.
+	(riscv_insert_uleb128_fixes): New function.
+	(riscv_md_end): Scan rs_leb128 fragments.
+	(riscv_pseudo_table): Remove uleb128.
+
 2020-01-21  Jan Beulich  <jbeulich@suse.com>
 
 	* testsuite/gas/i386/inval-crc32.s,
diff --git a/gas/config/tc-riscv.c b/gas/config/tc-riscv.c
index 21fff8ffab..61094fab3a 100644
--- a/gas/config/tc-riscv.c
+++ b/gas/config/tc-riscv.c
@@ -2392,6 +2392,9 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED)
     case BFD_RELOC_RISCV_SUB32:
     case BFD_RELOC_RISCV_SUB64:
     case BFD_RELOC_RISCV_RELAX:
+    /* cvt_frag_to_fill () has called output_leb128 ().  */
+    case BFD_RELOC_RISCV_SET_ULEB128:
+    case BFD_RELOC_RISCV_SUB_ULEB128:
       break;
 
     case BFD_RELOC_RISCV_TPREL_HI20:
@@ -3067,8 +3070,11 @@ s_riscv_leb128 (int sign)
   char *save_in = input_line_pointer;
 
   expression (&exp);
-  if (exp.X_op != O_constant)
-    as_bad (_("non-constant .%cleb128 is not supported"), sign ? 's' : 'u');
+  if (sign && exp.X_op != O_constant)
+    as_bad (_("non-constant .sleb128 is not supported"));
+  else if (!sign && exp.X_op != O_constant && exp.X_op != O_subtract)
+    as_bad (_(".uleb128 only supports constant or subtract expressions."));
+
   demand_empty_rest_of_line ();
 
   input_line_pointer = save_in;
@@ -3131,11 +3137,56 @@ riscv_set_public_attributes (void)
     riscv_write_out_arch_attr ();
 }
 
+/* Scan uleb128 subtraction expressions and insert fixups for them.
+   e.g., .uleb128 .L1 - .L0
+   Because relaxation may change the value of the subtraction, we
+   must resolve them at link-time.  */
+
+static void
+riscv_insert_uleb128_fixes (bfd *abfd ATTRIBUTE_UNUSED,
+			    asection *sec, void *xxx ATTRIBUTE_UNUSED)
+{
+  segment_info_type *seginfo = seg_info (sec);
+  struct frag *fragP;
+
+  subseg_set (sec, 0);
+
+  for (fragP = seginfo->frchainP->frch_root;
+       fragP; fragP = fragP->fr_next)
+    {
+      expressionS *exp, *exp_dup;
+
+      if (fragP->fr_type != rs_leb128  || fragP->fr_symbol == NULL)
+	continue;
+
+      exp = symbol_get_value_expression (fragP->fr_symbol);
+
+      if (exp->X_op != O_subtract)
+	continue;
+
+      /* Only unsigned leb128 can be handled.  */
+      gas_assert (fragP->fr_subtype == 0);
+      exp_dup = xmemdup (exp, sizeof (*exp), sizeof (*exp));
+      exp_dup->X_op = O_symbol;
+      exp_dup->X_op_symbol = NULL;
+
+      /* Insert relocations to resolve the subtraction at link-time.  */
+      fix_new_exp (fragP, fragP->fr_fix, 0,
+		   exp_dup, 0, BFD_RELOC_RISCV_SET_ULEB128);
+      exp_dup->X_add_symbol = exp->X_op_symbol;
+      fix_new_exp (fragP, fragP->fr_fix, 0,
+		   exp_dup, 0, BFD_RELOC_RISCV_SUB_ULEB128);
+    }
+}
+
 /* Called after all assembly has been done.  */
 
 void
 riscv_md_end (void)
 {
+
+  if (riscv_opts.relax)
+    bfd_map_over_sections (stdoutput, riscv_insert_uleb128_fixes, NULL);
   riscv_set_public_attributes ();
 }
 
diff --git a/include/ChangeLog b/include/ChangeLog
index c7fd4ab141..67ac887fe8 100644
--- a/include/ChangeLog
+++ b/include/ChangeLog
@@ -1,3 +1,7 @@
+2020-01-06  Kuan-Lin Chen  <kuanlinchentw@gmail.com>
+
+	* elf/riscv.h ((R_RISCV_SET_ULEB128, (R_RISCV_SUB_ULEB128): Define.
+
 2020-01-18  Nick Clifton  <nickc@redhat.com>
 
 	Binutils 2.34 branch created.
diff --git a/include/elf/riscv.h b/include/elf/riscv.h
index 5062a4998c..a839a0c843 100644
--- a/include/elf/riscv.h
+++ b/include/elf/riscv.h
@@ -88,6 +88,8 @@ START_RELOC_NUMBERS (elf_riscv_reloc_type)
   RELOC_NUMBER (R_RISCV_SET16, 55)
   RELOC_NUMBER (R_RISCV_SET32, 56)
   RELOC_NUMBER (R_RISCV_32_PCREL, 57)
+  RELOC_NUMBER (R_RISCV_SET_ULEB128, 58)
+  RELOC_NUMBER (R_RISCV_SUB_ULEB128, 59)
 END_RELOC_NUMBERS (R_RISCV_max)
 
 /* Processor specific flags for the ELF header e_flags field.  */
diff --git a/ld/ChangeLog b/ld/ChangeLog
index d8949bbc38..c9b5d194a5 100644
--- a/ld/ChangeLog
+++ b/ld/ChangeLog
@@ -1,3 +1,9 @@
+2020-01-06  Kuan-Lin Chen  <kuanlinchentw@gmail.com>
+
+	* testsuite/ld-riscv-elf/ld-riscv-elf.exp: Add uleb128.
+	* testsuite/ld-riscv-elf/uleb128.d: New test.
+	* testsuite/ld-riscv-elf/uleb128.s: New file.
+
 2020-01-22  Yuri Chornoivan  <yurchor@mageia.org>
 
 	PR 25417
diff --git a/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp b/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp
index 0e9750e496..412c00d1ec 100644
--- a/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp
+++ b/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp
@@ -38,6 +38,7 @@ if [istarget "riscv*-*-*"] {
     run_dump_test "attr-merge-priv-spec"
     run_dump_test "attr-merge-arch-failed-01"
     run_dump_test "attr-merge-stack-align-failed"
+    run_dump_test "uleb128"
     run_ld_link_tests {
 	{ "Weak reference 32" "-T weakref.ld -melf32lriscv" ""
 	    "-march=rv32i -mabi=ilp32" {weakref32.s}
diff --git a/ld/testsuite/ld-riscv-elf/uleb128.d b/ld/testsuite/ld-riscv-elf/uleb128.d
new file mode 100644
index 0000000000..a921478e98
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/uleb128.d
@@ -0,0 +1,18 @@
+#source: uleb128.s
+#as: -march=rv32ic
+#ld: -melf32lriscv
+#objdump: -d
+
+.*:[ 	]+file format .*
+
+Disassembly of section .text:
+
+.* <_start>:
+.*jal.*<bar>
+.*jal.*<bar>
+.*jal.*<bar>
+.*jal.*<bar>
+.*jal.*<bar>
+.*jal.*<bar>
+.*:[ 	]+0e0c.*
+#pass
diff --git a/ld/testsuite/ld-riscv-elf/uleb128.s b/ld/testsuite/ld-riscv-elf/uleb128.s
new file mode 100644
index 0000000000..f7d23be163
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/uleb128.s
@@ -0,0 +1,18 @@
+.text
+.globl bar
+.globl _start
+.option rvc
+.align 2
+_start:
+.L0:
+        .rept 6
+        call bar
+        .endr
+.align 2
+.L1:
+        .uleb128 .L1 - .L0
+        .uleb128 .L2 - .L0
+.L2:
+.align 2
+bar:
+        nop
-- 
2.17.0

