[PATCH v4] RISC-V: Add new relocation types for global array accesses with non-constant indices

Liqin Weng <[email protected]> Thu, 30 Jul 2026 09:03:30 +0800
Newsgroups gmane.comp.gnu.binutils
Message-ID <[email protected]>
From: wengliqin <[email protected]>

This patch introduces support for new RISC-V relocation types
(R_RISCV_BASE_IDX_*) to enable more efficient code generation for
global array accesses with non-constant array subscripts.

New Reloc includes :
R_RISCV_BASE_IDX_LO12/R_RISCV_BASE_IDX_LO12_S/R_RISCV_BASE_IDX_ADD

RISC-V PSABI specification: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/pull/489

bfd/ChangeLog:

	* bfd-in2.h : Add new relocation type.
	* elfnn-riscv.c :  Define relocation information for new
          relocation type.
	* elfxx-riscv.c : Add HOWTOs for new relocation type.
	* libbfd.h: Regenerate.
	* reloc.c: Document BFD_RELOC_RISCV_BASE_IDX_* type.

gas/ChangeLog:

	* config/tc-riscv.c: Add support for RISC-V Base IDX relocations
	* testsuite/gas/riscv/base-idx-add.s: New test for BASE_IDX*.
	* testsuite/gas/riscv/base-idx-add.d: New test.
	* testsuite/gas/riscv/base-idx-add.l: New test.

include/ChangeLog:

	* elf/riscv.h: Update relocation definitions for new BASE_IDX*
          Type.

opcodes/ChangeLog:

	* riscv-opc.c : add new pattern for add/shaxadd/add.uw/shxadd.uw
	* riscv-dis.c (print_insn_args): Add a no-op case for the '1'
	relaxation-marker operand, so that disassembling an instruction
	whose only matching opcode-table row now includes it (e.g. add,
	shNadd) no longer hits "undefined modifier" -- the marker
	carries no encoding bits, so nothing is printed for it.
---
This patch updates:
1. clang-format
2. add '--emit-reloc' options for base-idx tests

 bfd/bfd-in2.h                                 |   3 +
 bfd/elfnn-riscv.c                             |  97 +++++++++++++++-
 bfd/elfxx-riscv.c                             | 105 +++++++++++++++++-
 bfd/libbfd.h                                  |   3 +
 bfd/reloc.c                                   |   6 +
 gas/config/tc-riscv.c                         |  34 +++++-
 gas/testsuite/gas/riscv/base-idx-add.d        |   3 +
 gas/testsuite/gas/riscv/base-idx-add.l        |  10 ++
 gas/testsuite/gas/riscv/base-idx-add.s        |  19 ++++
 include/elf/riscv.h                           |   6 +
 .../ld-riscv-elf/base-idx-relax-far.ld        |  21 ++++
 .../base-idx-relax-medlow-far-emit-relocs.d   |  38 +++++++
 .../ld-riscv-elf/base-idx-relax-medlow-far.d  |  20 ++++
 .../base-idx-relax-medlow-near-emit-relocs.d  |  37 ++++++
 .../ld-riscv-elf/base-idx-relax-medlow-near.d |  19 ++++
 .../ld-riscv-elf/base-idx-relax-near.ld       |  23 ++++
 .../base-idx-relax-x0-emit-relocs.d           |  36 ++++++
 ld/testsuite/ld-riscv-elf/base-idx-relax-x0.d |  18 +++
 .../ld-riscv-elf/base-idx-relax-x0.ld         |  23 ++++
 ld/testsuite/ld-riscv-elf/base-idx-relax-x0.s |  22 ++++
 ld/testsuite/ld-riscv-elf/base-idx-relax.s    |  22 ++++
 ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp    |   6 +
 opcodes/riscv-dis.c                           |   6 +
 opcodes/riscv-opc.c                           |  20 ++--
 24 files changed, 577 insertions(+), 20 deletions(-)
 create mode 100644 gas/testsuite/gas/riscv/base-idx-add.d
 create mode 100644 gas/testsuite/gas/riscv/base-idx-add.l
 create mode 100644 gas/testsuite/gas/riscv/base-idx-add.s
 create mode 100644 ld/testsuite/ld-riscv-elf/base-idx-relax-far.ld
 create mode 100644 ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-far-emit-relocs.d
 create mode 100644 ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-far.d
 create mode 100644 ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-near-emit-relocs.d
 create mode 100644 ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-near.d
 create mode 100644 ld/testsuite/ld-riscv-elf/base-idx-relax-near.ld
 create mode 100644 ld/testsuite/ld-riscv-elf/base-idx-relax-x0-emit-relocs.d
 create mode 100644 ld/testsuite/ld-riscv-elf/base-idx-relax-x0.d
 create mode 100644 ld/testsuite/ld-riscv-elf/base-idx-relax-x0.ld
 create mode 100644 ld/testsuite/ld-riscv-elf/base-idx-relax-x0.s
 create mode 100644 ld/testsuite/ld-riscv-elf/base-idx-relax.s

diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index d4e4f8e4375..b958ed3c048 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -5364,6 +5364,9 @@ enum bfd_reloc_code_real
   BFD_RELOC_RISCV_32_PCREL,
   BFD_RELOC_RISCV_SET_ULEB128,
   BFD_RELOC_RISCV_SUB_ULEB128,
+  BFD_RELOC_RISCV_BASE_IDX_LO12_I,
+  BFD_RELOC_RISCV_BASE_IDX_LO12_S,
+  BFD_RELOC_RISCV_BASE_IDX_ADD,
 
   /* Renesas RL78 Relocations.  */
   BFD_RELOC_RL78_NEG8,
diff --git a/bfd/elfnn-riscv.c b/bfd/elfnn-riscv.c
index d3963ec9a58..192417f1a0c 100644
--- a/bfd/elfnn-riscv.c
+++ b/bfd/elfnn-riscv.c
@@ -1999,7 +1999,9 @@ perform_relocation (const reloc_howto_type *howto,
       break;
 
     case R_RISCV_LO12_I:
+    case R_RISCV_BASE_IDX_LO12_I:
     case R_RISCV_GPREL_I:
+    case R_RISCV_BASE_IDX_GPREL_I:
     case R_RISCV_TPREL_LO12_I:
     case R_RISCV_TPREL_I:
     case R_RISCV_PCREL_LO12_I:
@@ -2009,7 +2011,9 @@ perform_relocation (const reloc_howto_type *howto,
       break;
 
     case R_RISCV_LO12_S:
+    case R_RISCV_BASE_IDX_LO12_S:
     case R_RISCV_GPREL_S:
+    case R_RISCV_BASE_IDX_GPREL_S:
     case R_RISCV_TPREL_LO12_S:
     case R_RISCV_TPREL_S:
     case R_RISCV_PCREL_LO12_S:
@@ -2130,6 +2134,8 @@ perform_relocation (const reloc_howto_type *howto,
     case R_RISCV_32_PCREL:
     case R_RISCV_TLS_DTPREL32:
     case R_RISCV_TLS_DTPREL64:
+    case R_RISCV_BASE_IDX_ADD:
+    case R_RISCV_BASE_IDX_GPREL_ADD:
       break;
 
     case R_RISCV_DELETE:
@@ -2722,6 +2728,9 @@ riscv_elf_relocate_section (bfd *output_bfd,
 	      case R_RISCV_HI20:
 	      case R_RISCV_LO12_I:
 	      case R_RISCV_LO12_S:
+	      case R_RISCV_BASE_IDX_LO12_I:
+	      case R_RISCV_BASE_IDX_LO12_S:
+	      case R_RISCV_BASE_IDX_ADD:
 		goto do_relocation;
 
 	      case R_RISCV_PCREL_HI20:
@@ -2788,6 +2797,9 @@ riscv_elf_relocate_section (bfd *output_bfd,
 	case R_RISCV_RVC_LUI:
 	case R_RISCV_LO12_I:
 	case R_RISCV_LO12_S:
+	case R_RISCV_BASE_IDX_LO12_I:
+	case R_RISCV_BASE_IDX_LO12_S:
+	case R_RISCV_BASE_IDX_ADD:
 	case R_RISCV_SET6:
 	case R_RISCV_SET8:
 	case R_RISCV_SET16:
@@ -3089,6 +3101,48 @@ riscv_elf_relocate_section (bfd *output_bfd,
 	    break;
 	  }
 
+	case R_RISCV_BASE_IDX_GPREL_I:
+	case R_RISCV_BASE_IDX_GPREL_S:
+	  {
+	    /* After relaxation, the ld/st uses (gp + index) as its base
+	       register, so the immediate encodes the symbol's displacement
+	       from gp.  perform_relocation will encode the final value
+	       from (relocation + r_addend).  */
+	    bfd_vma gp = riscv_global_pointer_value (info);
+	    bfd_vma disp = relocation + rel->r_addend - gp;
+	    bool x0_base = VALID_ITYPE_IMM (relocation + rel->r_addend);
+	    bool gp_base = (r_type == R_RISCV_BASE_IDX_GPREL_I)
+			     ? VALID_ITYPE_IMM (disp)
+			     : VALID_STYPE_IMM (disp);
+	    if (!x0_base)
+	      {
+		/* Only when gp is actually used as the base do we need to
+		   express the immediate as a displacement from gp.  */
+		if (gp_base)
+		  rel->r_addend -= gp;
+		else
+		  r = bfd_reloc_overflow;
+	      }
+	    break;
+	  }
+
+	case R_RISCV_BASE_IDX_GPREL_ADD:
+	  {
+	    /* Rewrite rs2 of the add/shXadd instruction to use gp or x0 as the
+	       base register after relaxation.  The same encoding change
+	       applies to both add and shXadd — the opcode itself is left
+	       intact.  */
+	    bfd_vma insn = bfd_getl32 (contents + rel->r_offset);
+	    bool x0_base = VALID_ITYPE_IMM (relocation + rel->r_addend);
+	    insn &= ~(OP_MASK_RS2 << OP_SH_RS2);
+	    if (!x0_base)
+	      {
+		insn |= X_GP << OP_SH_RS2;
+	      }
+	    bfd_putl32 (insn, contents + rel->r_offset);
+	    break;
+	  }
+
 	case R_RISCV_PCREL_HI20:
 	  absolute = riscv_zero_pcrel_hi_reloc (rel, info, pc, &relocation,
 						contents, howto);
@@ -5130,8 +5184,8 @@ _bfd_riscv_relax_lui (bfd *abfd,
      Valid gp range conservatively because of alignment issue.
 
      Should we also consider the alignment issue for x0 base?  */
-  if (undefined_weak
-      || VALID_ITYPE_IMM (symval)
+  bool x0_base = undefined_weak || VALID_ITYPE_IMM (symval);
+  if (x0_base
       || (symval >= gp
 	  && VALID_ITYPE_IMM (symval - gp + max_alignment + reserve_size))
       || (symval < gp
@@ -5148,6 +5202,36 @@ _bfd_riscv_relax_lui (bfd *abfd,
 	  rel->r_info = ELFNN_R_INFO (sym, R_RISCV_GPREL_S);
 	  return true;
 
+	case R_RISCV_BASE_IDX_LO12_I:
+	  rel->r_info = ELFNN_R_INFO (sym, R_RISCV_BASE_IDX_GPREL_I);
+	  return true;
+
+	case R_RISCV_BASE_IDX_LO12_S:
+	  rel->r_info = ELFNN_R_INFO (sym, R_RISCV_BASE_IDX_GPREL_S);
+	  return true;
+
+	case R_RISCV_BASE_IDX_ADD:
+	  {
+	    /* Only when relaxing to use x0 as the base does rs2 of this
+	       add stay x0, making the instruction rd = rs1 + 0.  It;s will
+	       remove add a0, a0, zero.  */
+	    bfd_vma insn = bfd_getl32 (contents + rel->r_offset);
+	    unsigned rd = (insn >> OP_SH_RD) & OP_MASK_RD;
+	    unsigned rs1 = (insn >> OP_SH_RS1) & OP_MASK_RS1;
+	    if (x0_base
+		&& (insn & MASK_ADD) == MATCH_ADD
+		&& rd == rs1)
+	      {
+		*again = true;
+		return riscv_relax_delete_bytes (abfd, sec, rel->r_offset, 4,
+						 link_info, pcgp_relocs, rel,
+						 false);
+	      }
+
+	    rel->r_info = ELFNN_R_INFO (sym, R_RISCV_BASE_IDX_GPREL_ADD);
+	    return true;
+	  }
+
 	case R_RISCV_HI20:
 	  /* Delete unnecessary LUI and reuse the reloc.  */
 	  *again = true;
@@ -5160,7 +5244,7 @@ _bfd_riscv_relax_lui (bfd *abfd,
     }
 
   /* Can we relax LUI to C.LUI?  Alignment might move the section forward;
-     account for this assuming page alignment at worst. In the presence of 
+     account for this assuming page alignment at worst. In the presence of
      RELRO segment the linker aligns it by one page size, therefore sections
      after the segment can be moved more than one page. */
 
@@ -5537,9 +5621,10 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
 	  if (type == R_RISCV_CALL
 	      || type == R_RISCV_CALL_PLT)
 	    relax_func = _bfd_riscv_relax_call;
-	  else if (type == R_RISCV_HI20
-		   || type == R_RISCV_LO12_I
-		   || type == R_RISCV_LO12_S)
+	  else if (type == R_RISCV_HI20 || type == R_RISCV_LO12_I
+		   || type == R_RISCV_LO12_S || type == R_RISCV_BASE_IDX_LO12_I
+		   || type == R_RISCV_BASE_IDX_LO12_S
+		   || type == R_RISCV_BASE_IDX_ADD)
 	    relax_func = _bfd_riscv_relax_lui;
 	  else if (type == R_RISCV_TPREL_HI20
 		   || type == R_RISCV_TPREL_ADD
diff --git a/bfd/elfxx-riscv.c b/bfd/elfxx-riscv.c
index ee962b4f6f1..8232713b326 100644
--- a/bfd/elfxx-riscv.c
+++ b/bfd/elfxx-riscv.c
@@ -876,7 +876,63 @@ static const reloc_howto_type howto_table[] =
 	 false,				/* partial_inplace */
 	 0,				/* src_mask */
 	 ENCODE_ITYPE_IMM (-1U),	/* dst_mask */
-	 false) 			/* pcrel_offset */
+	 false),			/* pcrel_offset */
+
+  /* Reserved slots 66-76.  */
+  EMPTY_HOWTO (66),
+  EMPTY_HOWTO (67),
+  EMPTY_HOWTO (68),
+  EMPTY_HOWTO (69),
+  EMPTY_HOWTO (70),
+  EMPTY_HOWTO (71),
+  EMPTY_HOWTO (72),
+  EMPTY_HOWTO (73),
+  EMPTY_HOWTO (74),
+  EMPTY_HOWTO (75),
+  EMPTY_HOWTO (76),
+
+  /* Global array accesses with non-constant subscript (base + index).  */
+  HOWTO (R_RISCV_BASE_IDX_LO12_I,   /* type */
+	 0,			    /* rightshift */
+	 4,			    /* size */
+	 32,			    /* bitsize */
+	 false,			    /* pc_relative */
+	 0,			    /* bitpos */
+	 complain_overflow_dont,    /* complain_on_overflow */
+	 bfd_elf_generic_reloc,	    /* special_function */
+	 "R_RISCV_BASE_IDX_LO12_I", /* name */
+	 false,			    /* partial_inplace */
+	 0,			    /* src_mask */
+	 ENCODE_ITYPE_IMM (-1U),    /* dst_mask */
+	 false),		    /* pcrel_offset */
+
+  HOWTO (R_RISCV_BASE_IDX_LO12_S,   /* type */
+	 0,			    /* rightshift */
+	 4,			    /* size */
+	 32,			    /* bitsize */
+	 false,			    /* pc_relative */
+	 0,			    /* bitpos */
+	 complain_overflow_dont,    /* complain_on_overflow */
+	 bfd_elf_generic_reloc,	    /* special_function */
+	 "R_RISCV_BASE_IDX_LO12_S", /* name */
+	 false,			    /* partial_inplace */
+	 0,			    /* src_mask */
+	 ENCODE_STYPE_IMM (-1U),    /* dst_mask */
+	 false),		    /* pcrel_offset */
+
+  HOWTO (R_RISCV_BASE_IDX_ADD,	 /* type */
+	 0,			 /* rightshift */
+	 4,			 /* size */
+	 32,			 /* bitsize */
+	 false,			 /* pc_relative */
+	 0,			 /* bitpos */
+	 complain_overflow_dont, /* complain_on_overflow */
+	 bfd_elf_generic_reloc,	 /* special_function */
+	 "R_RISCV_BASE_IDX_ADD", /* name */
+	 false,			 /* partial_inplace */
+	 0,			 /* src_mask */
+	 0,			 /* dst_mask */
+	 false),		 /* pcrel_offset */
 };
 
 static const reloc_howto_type howto_table_internal[] =
@@ -961,6 +1017,50 @@ static const reloc_howto_type howto_table_internal[] =
 	 0,				/* src_mask */
 	 ENCODE_STYPE_IMM (-1U),	/* dst_mask */
 	 false),			/* pcrel_offset */
+
+  /* Global array accesses with non-constant subscript (base + index),
+     after relaxation.  */
+  HOWTO (R_RISCV_BASE_IDX_GPREL_I,   /* type */
+	 0,			     /* rightshift */
+	 4,			     /* size */
+	 32,			     /* bitsize */
+	 false,			     /* pc_relative */
+	 0,			     /* bitpos */
+	 complain_overflow_dont,     /* complain_on_overflow */
+	 bfd_elf_generic_reloc,	     /* special_function */
+	 "R_RISCV_BASE_IDX_GPREL_I", /* name */
+	 false,			     /* partial_inplace */
+	 0,			     /* src_mask */
+	 ENCODE_ITYPE_IMM (-1U),     /* dst_mask */
+	 false),		     /* pcrel_offset */
+
+  HOWTO (R_RISCV_BASE_IDX_GPREL_S,   /* type */
+	 0,			     /* rightshift */
+	 4,			     /* size */
+	 32,			     /* bitsize */
+	 false,			     /* pc_relative */
+	 0,			     /* bitpos */
+	 complain_overflow_dont,     /* complain_on_overflow */
+	 bfd_elf_generic_reloc,	     /* special_function */
+	 "R_RISCV_BASE_IDX_GPREL_S", /* name */
+	 false,			     /* partial_inplace */
+	 0,			     /* src_mask */
+	 ENCODE_STYPE_IMM (-1U),     /* dst_mask */
+	 false),		     /* pcrel_offset */
+
+  HOWTO (R_RISCV_BASE_IDX_GPREL_ADD,   /* type */
+	 0,			       /* rightshift */
+	 4,			       /* size */
+	 32,			       /* bitsize */
+	 false,			       /* pc_relative */
+	 0,			       /* bitpos */
+	 complain_overflow_dont,       /* complain_on_overflow */
+	 bfd_elf_generic_reloc,	       /* special_function */
+	 "R_RISCV_BASE_IDX_GPREL_ADD", /* name */
+	 false,			       /* partial_inplace */
+	 0,			       /* src_mask */
+	 0,			       /* dst_mask */
+	 false)
 };
 
 /* A mapping from BFD reloc types to RISC-V ELF reloc types.  */
@@ -1023,6 +1123,9 @@ static const struct elf_reloc_map riscv_reloc_map[] =
   { 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 },
+  { BFD_RELOC_RISCV_BASE_IDX_LO12_I, R_RISCV_BASE_IDX_LO12_I },
+  { BFD_RELOC_RISCV_BASE_IDX_LO12_S, R_RISCV_BASE_IDX_LO12_S },
+  { BFD_RELOC_RISCV_BASE_IDX_ADD, R_RISCV_BASE_IDX_ADD },
 };
 
 struct riscv_profiles
diff --git a/bfd/libbfd.h b/bfd/libbfd.h
index a27134dcd78..7e49498d0f1 100644
--- a/bfd/libbfd.h
+++ b/bfd/libbfd.h
@@ -2363,6 +2363,9 @@ static const char *const bfd_reloc_code_real_names[] = { "@@uninitialized@@",
   "BFD_RELOC_RISCV_TLSDESC_ADD_LO12",
   "BFD_RELOC_RISCV_TLSDESC_CALL",
   "BFD_RELOC_RISCV_ALIGN",
+  "BFD_RELOC_RISCV_BASE_IDX_LO12_I",
+  "BFD_RELOC_RISCV_BASE_IDX_LO12_S",
+  "BFD_RELOC_RISCV_BASE_IDX_ADD",
   "BFD_RELOC_RISCV_RVC_BRANCH",
   "BFD_RELOC_RISCV_RVC_JUMP",
   "BFD_RELOC_RISCV_RELAX",
diff --git a/bfd/reloc.c b/bfd/reloc.c
index 98343696a33..6f6ff98c4a5 100644
--- a/bfd/reloc.c
+++ b/bfd/reloc.c
@@ -4861,6 +4861,12 @@ ENUMX
   BFD_RELOC_RISCV_TLSDESC_ADD_LO12
 ENUMX
   BFD_RELOC_RISCV_TLSDESC_CALL
+ENUMX
+  BFD_RELOC_RISCV_BASE_IDX_LO12_I
+ENUMX
+  BFD_RELOC_RISCV_BASE_IDX_LO12_S
+ENUMX
+  BFD_RELOC_RISCV_BASE_IDX_ADD
 ENUMX
   BFD_RELOC_RISCV_ALIGN
 ENUMX
diff --git a/gas/config/tc-riscv.c b/gas/config/tc-riscv.c
index 6bcf53832a0..11d9db0f977 100644
--- a/gas/config/tc-riscv.c
+++ b/gas/config/tc-riscv.c
@@ -1953,9 +1953,11 @@ riscv_apply_const_reloc (bfd_reloc_code_real_type reloc_type, bfd_vma value)
       return ENCODE_UTYPE_IMM (RISCV_CONST_HIGH_PART (value));
 
     case BFD_RELOC_RISCV_LO12_S:
+    case BFD_RELOC_RISCV_BASE_IDX_LO12_S:
       return ENCODE_STYPE_IMM (value);
 
     case BFD_RELOC_RISCV_LO12_I:
+    case BFD_RELOC_RISCV_BASE_IDX_LO12_I:
       return ENCODE_ITYPE_IMM (value);
 
     default:
@@ -2022,7 +2024,8 @@ append_insn (struct riscv_cl_insn *ip, expressionS *address_expr,
   if (reloc_type == BFD_RELOC_RISCV_HI20
       || reloc_type == BFD_RELOC_RISCV_PCREL_HI20
       || reloc_type == BFD_RELOC_RISCV_TPREL_HI20
-      || reloc_type == BFD_RELOC_RISCV_TPREL_ADD)
+      || reloc_type == BFD_RELOC_RISCV_TPREL_ADD
+      || reloc_type == BFD_RELOC_RISCV_BASE_IDX_ADD)
     {
       frag_wane (frag_now);
       frag_new (0);
@@ -2455,6 +2458,7 @@ static const struct percent_op_match percent_op_utype[] =
 static const struct percent_op_match percent_op_itype[] =
 {
   {"lo", BFD_RELOC_RISCV_LO12_I},
+  {"base_idx_lo", BFD_RELOC_RISCV_BASE_IDX_LO12_I},
   {"tprel_lo", BFD_RELOC_RISCV_TPREL_LO12_I},
   {"pcrel_lo", BFD_RELOC_RISCV_PCREL_LO12_I},
   {"tlsdesc_load_lo", BFD_RELOC_RISCV_TLSDESC_LOAD_LO12},
@@ -2465,6 +2469,7 @@ static const struct percent_op_match percent_op_itype[] =
 static const struct percent_op_match percent_op_stype[] =
 {
   {"lo", BFD_RELOC_RISCV_LO12_S},
+  {"base_idx_lo", BFD_RELOC_RISCV_BASE_IDX_LO12_S},
   {"tprel_lo", BFD_RELOC_RISCV_TPREL_LO12_S},
   {"pcrel_lo", BFD_RELOC_RISCV_PCREL_LO12_S},
   {0, 0}
@@ -2474,6 +2479,7 @@ static const struct percent_op_match percent_op_relax_only[] =
 {
   {"tlsdesc_call", BFD_RELOC_RISCV_TLSDESC_CALL},
   {"tprel_add", BFD_RELOC_RISCV_TPREL_ADD},
+  {"base_idx_add", BFD_RELOC_RISCV_BASE_IDX_ADD},
   {0, 0}
 };
 
@@ -3644,9 +3650,23 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
 	      *imm_reloc = BFD_RELOC_RISCV_LO12_I;
 	      goto load_store;
 	    case '1':
-	      /* This is used for TLS relocations that acts as relaxation
-		 markers and do not change the instruction encoding,
-		 i.e. %tprel_add and %tlsdesc_call.  */
+	      /* This operand is a relaxation marker that does not change
+		 the instruction encoding, e.g. %tprel_add, %tlsdesc_call
+		 and %base_idx_add.  It is mandatory when a literal ','
+		 precedes it in the operand string (e.g. "d,s,1" for
+		 jalr's %tlsdesc_call), in which case that ',' is already
+		 consumed by the general ',' handling above and *asarg
+		 points straight at the marker text.  It is optional when
+		 it directly follows another operand letter with no
+		 separating ',' in the operand string (e.g. "d,s,t1" on
+		 add/shNadd/add.uw for %base_idx_add), in which case we
+		 must recognise here whether it was actually given --
+		 mirrors the "optional vector mask" handling of 'Vm'
+		 above.  */
+	      if (*asarg == '\0')
+		continue;
+	      if (*asarg == ',')
+		++asarg;
 	      p = percent_op_relax_only;
 	      goto alu_op;
 	    case '0': /* AMO displacement, which must be zero.  */
@@ -4687,6 +4707,8 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg)
     case BFD_RELOC_RISCV_HI20:
     case BFD_RELOC_RISCV_LO12_I:
     case BFD_RELOC_RISCV_LO12_S:
+    case BFD_RELOC_RISCV_BASE_IDX_LO12_I:
+    case BFD_RELOC_RISCV_BASE_IDX_LO12_S:
       bfd_putl32 (riscv_apply_const_reloc (fixP->fx_r_type, *valP)
 		  | bfd_getl32 (buf), buf);
       if (fixP->fx_addsy == NULL)
@@ -4694,6 +4716,10 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg)
       relaxable = true;
       break;
 
+    case BFD_RELOC_RISCV_BASE_IDX_ADD:
+      relaxable = true;
+      break;
+
     case BFD_RELOC_RISCV_GOT_HI20:
       /* R_RISCV_GOT_HI20 and the following R_RISCV_LO12_I are relaxable
 	 only if it is created as a result of la or lga assembler macros. */
diff --git a/gas/testsuite/gas/riscv/base-idx-add.d b/gas/testsuite/gas/riscv/base-idx-add.d
new file mode 100644
index 00000000000..488ffbc1956
--- /dev/null
+++ b/gas/testsuite/gas/riscv/base-idx-add.d
@@ -0,0 +1,3 @@
+#as: -march=rv64imafdc_zbb_zba
+#source: base-idx-add.s
+#error_output: base-idx-add.l
diff --git a/gas/testsuite/gas/riscv/base-idx-add.l b/gas/testsuite/gas/riscv/base-idx-add.l
new file mode 100644
index 00000000000..8321b40ccc6
--- /dev/null
+++ b/gas/testsuite/gas/riscv/base-idx-add.l
@@ -0,0 +1,10 @@
+.*: Assembler messages:
+.*: Error: illegal operands `amoadd.w x8,x9,%base_idx_add\(sym\)\(x10\)'
+.*: Error: illegal operands `add a5,a5,a0,0'
+.*: Error: illegal operands `sh1add a0,a0,a5,0'
+.*: Error: illegal operands `sh1add.uw a0,a0,a5,0'
+.*: Error: illegal operands `sh2add a0,a0,a5,0'
+.*: Error: illegal operands `sh2add.uw a0,a0,a5,0'
+.*: Error: illegal operands `sh3add a0,a0,a5,0'
+.*: Error: illegal operands `sh3add.uw a0,a0,a5,0'
+.*: Error: illegal operands `add.uw a0,a0,a5,0'
diff --git a/gas/testsuite/gas/riscv/base-idx-add.s b/gas/testsuite/gas/riscv/base-idx-add.s
new file mode 100644
index 00000000000..47d1b3b5aa3
--- /dev/null
+++ b/gas/testsuite/gas/riscv/base-idx-add.s
@@ -0,0 +1,19 @@
+.option arch, +a
+    # Don't allow base_idx_add in amoadd.
+	amoadd.w x8,x9,%base_idx_add(sym)(x10)
+	# Do require base_idx_add in 4-operand add.
+	add		a5,a5,a0,0
+	sh1add		a0,a0,a5,0
+	sh1add.uw	a0,a0,a5,0
+	sh2add		a0,a0,a5,0
+	sh2add.uw	a0,a0,a5,0
+	sh3add		a0,a0,a5,0
+	sh3add.uw	a0,a0,a5,0
+	add.uw		a0,a0,a5,0
+	.globl	sym
+	.section	.tbss,"awT",@nobits
+	.align	2
+	.type	sym, @object
+	.size	sym, 4
+sym:
+	.zero	4
diff --git a/include/elf/riscv.h b/include/elf/riscv.h
index f6ca3d4acd8..a19bee4b684 100644
--- a/include/elf/riscv.h
+++ b/include/elf/riscv.h
@@ -95,6 +95,9 @@ START_RELOC_NUMBERS (elf_riscv_reloc_type)
   RELOC_NUMBER (R_RISCV_TLSDESC_LOAD_LO12, 63)
   RELOC_NUMBER (R_RISCV_TLSDESC_ADD_LO12, 64)
   RELOC_NUMBER (R_RISCV_TLSDESC_CALL, 65)
+  RELOC_NUMBER (R_RISCV_BASE_IDX_LO12_I, 77)
+  RELOC_NUMBER (R_RISCV_BASE_IDX_LO12_S, 78)
+  RELOC_NUMBER (R_RISCV_BASE_IDX_ADD, 79)
 END_RELOC_NUMBERS (R_RISCV_max)
 
 /* Internal relocations used exclusively by the relaxation pass.  */
@@ -105,6 +108,9 @@ END_RELOC_NUMBERS (R_RISCV_max)
 #define R_RISCV_GPREL_S           (R_RISCV_max + 4)
 #define R_RISCV_TPREL_I           (R_RISCV_max + 5)
 #define R_RISCV_TPREL_S           (R_RISCV_max + 6)
+#define R_RISCV_BASE_IDX_GPREL_I (R_RISCV_max + 7)
+#define R_RISCV_BASE_IDX_GPREL_S (R_RISCV_max + 8)
+#define R_RISCV_BASE_IDX_GPREL_ADD (R_RISCV_max + 9)
 
 /* Processor specific flags for the ELF header e_flags field.  */
 
diff --git a/ld/testsuite/ld-riscv-elf/base-idx-relax-far.ld b/ld/testsuite/ld-riscv-elf/base-idx-relax-far.ld
new file mode 100644
index 00000000000..123f9c4cbe9
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/base-idx-relax-far.ld
@@ -0,0 +1,21 @@
+/* Data symbols reachable by lui/auipc+addi but far from __global_pointer$,
+   so BASE_IDX gp-relaxation cannot fire.  The original instruction sequence
+   is preserved.  */
+
+ENTRY(_start)
+MEMORY
+{
+	rom (rx)  : ORIGIN = 0x10000, LENGTH = 0x1000
+	ram (!rx) : ORIGIN = 0x100000, LENGTH = 0x1000
+}
+SECTIONS {
+	.text : {
+		*(.text*)
+	} >rom
+
+	.data : {
+		*(.data*)
+	} >ram
+
+	__global_pointer$ = 0x20000;
+}
diff --git a/ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-far-emit-relocs.d b/ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-far-emit-relocs.d
new file mode 100644
index 00000000000..5baa7c44f66
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-far-emit-relocs.d
@@ -0,0 +1,38 @@
+#source: base-idx-relax.s
+#as: -march=rv64i_zba -mabi=lp64 --defsym __medlow__=1
+#ld: -Tbase-idx-relax-far.ld -melf64lriscv --relax --emit-relocs
+#objdump: -dr -Mno-aliases
+
+.*:[ 	]+file format .*
+
+
+Disassembly of section .text:
+
+0+10000 <_start>:
+[ 	]+[0-9a-f]+:[ 	]+001005b7[ 	]+lui[ 	]+a1,0x100
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_HI20[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+00b50633[ 	]+add[ 	]+a2,a0,a1
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+08b5063b[ 	]+add\.uw[ 	]+a2,a0,a1
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+20b52633[ 	]+sh1add[ 	]+a2,a0,a1
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+20b54633[ 	]+sh2add[ 	]+a2,a0,a1
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+20b56633[ 	]+sh3add[ 	]+a2,a0,a1
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+20b5663b[ 	]+sh3add\.uw[ 	]+a2,a0,a1
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+00064683[ 	]+lbu[ 	]+a3,0\(a2\)
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_LO12_I[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+00d60023[ 	]+sb[ 	]+a3,0\(a2\)
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_LO12_S[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
diff --git a/ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-far.d b/ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-far.d
new file mode 100644
index 00000000000..6f6fc40fe0f
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-far.d
@@ -0,0 +1,20 @@
+#source: base-idx-relax.s
+#as: -march=rv64i_zba -mabi=lp64 --defsym __medlow__=1
+#ld: -Tbase-idx-relax-far.ld -melf64lriscv --relax
+#objdump: -d -Mno-aliases
+
+.*:[ 	]+file format .*
+
+
+Disassembly of section .text:
+
+0+10000 <_start>:
+[ 	]+[0-9a-f]+:[ 	]+001005b7[ 	]+lui[ 	]+a1,0x100
+[ 	]+[0-9a-f]+:[ 	]+00b50633[ 	]+add[ 	]+a2,a0,a1
+[ 	]+[0-9a-f]+:[ 	]+08b5063b[ 	]+add\.uw[ 	]+a2,a0,a1
+[ 	]+[0-9a-f]+:[ 	]+20b52633[ 	]+sh1add[ 	]+a2,a0,a1
+[ 	]+[0-9a-f]+:[ 	]+20b54633[ 	]+sh2add[ 	]+a2,a0,a1
+[ 	]+[0-9a-f]+:[ 	]+20b56633[ 	]+sh3add[ 	]+a2,a0,a1
+[ 	]+[0-9a-f]+:[ 	]+20b5663b[ 	]+sh3add\.uw[ 	]+a2,a0,a1
+[ 	]+[0-9a-f]+:[ 	]+00064683[ 	]+lbu[ 	]+a3,0\(a2\)
+[ 	]+[0-9a-f]+:[ 	]+00d60023[ 	]+sb[ 	]+a3,0\(a2\)
diff --git a/ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-near-emit-relocs.d b/ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-near-emit-relocs.d
new file mode 100644
index 00000000000..c87c61fa072
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-near-emit-relocs.d
@@ -0,0 +1,37 @@
+#source: base-idx-relax.s
+#as: -march=rv64i_zba -mabi=lp64 --defsym __medlow__=1
+#ld: -Tbase-idx-relax-near.ld -melf64lriscv --relax --emit-relocs
+#objdump: -dr -Mno-aliases
+
+.*:[ 	]+file format .*
+
+
+Disassembly of section .text:
+
+0+10000 <_start>:
+[ 	]+[0-9a-f]+:[ 	]+00350633[ 	]+add[ 	]+a2,a0,gp
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_NONE[ 	]+\*ABS\*\+0x4
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+0835063b[ 	]+add\.uw[ 	]+a2,a0,gp
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+20352633[ 	]+sh1add[ 	]+a2,a0,gp
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+20354633[ 	]+sh2add[ 	]+a2,a0,gp
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+20356633[ 	]+sh3add[ 	]+a2,a0,gp
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+2035663b[ 	]+sh3add\.uw[ 	]+a2,a0,gp
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+10064683[ 	]+lbu[ 	]+a3,256\(a2\)
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_I[ 	]+symL-0x20000
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+10d60023[ 	]+sb[ 	]+a3,256\(a2\)
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_S[ 	]+symL-0x20000
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
diff --git a/ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-near.d b/ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-near.d
new file mode 100644
index 00000000000..e554da2d0c1
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/base-idx-relax-medlow-near.d
@@ -0,0 +1,19 @@
+#source: base-idx-relax.s
+#as: -march=rv64i_zba -mabi=lp64 --defsym __medlow__=1
+#ld: -Tbase-idx-relax-near.ld -melf64lriscv --relax
+#objdump: -d -Mno-aliases
+
+.*:[ 	]+file format .*
+
+
+Disassembly of section .text:
+
+0+10000 <_start>:
+[ 	]+[0-9a-f]+:[ 	]+00350633[ 	]+add[ 	]+a2,a0,gp
+[ 	]+[0-9a-f]+:[ 	]+0835063b[ 	]+add\.uw[ 	]+a2,a0,gp
+[ 	]+[0-9a-f]+:[ 	]+20352633[ 	]+sh1add[ 	]+a2,a0,gp
+[ 	]+[0-9a-f]+:[ 	]+20354633[ 	]+sh2add[ 	]+a2,a0,gp
+[ 	]+[0-9a-f]+:[ 	]+20356633[ 	]+sh3add[ 	]+a2,a0,gp
+[ 	]+[0-9a-f]+:[ 	]+2035663b[ 	]+sh3add\.uw[ 	]+a2,a0,gp
+[ 	]+[0-9a-f]+:[ 	]+10064683[ 	]+lbu[ 	]+a3,256\(a2\)
+[ 	]+[0-9a-f]+:[ 	]+10d60023[ 	]+sb[ 	]+a3,256\(a2\)
diff --git a/ld/testsuite/ld-riscv-elf/base-idx-relax-near.ld b/ld/testsuite/ld-riscv-elf/base-idx-relax-near.ld
new file mode 100644
index 00000000000..31f3229b7b0
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/base-idx-relax-near.ld
@@ -0,0 +1,23 @@
+/* Data symbols sit right next to __global_pointer$ so the gp-relaxable
+   range is satisfied and R_RISCV_BASE_IDX_* / R_RISCV_PCREL_BASE_IDX_*
+   are fully relaxed.  */
+
+ENTRY(_start)
+MEMORY
+{
+	rom (rx)  : ORIGIN = 0x10000, LENGTH = 0x1000
+	ram (!rx) : ORIGIN = 0x20000, LENGTH = 0x1000
+}
+SECTIONS {
+	.text : {
+		*(.text*)
+	} >rom
+
+	.data : {
+		__global_pointer$ = .;
+		/* Pad so that symL sits at gp + 0x100, giving the relaxed
+		   lbu/sb a non-zero gp-relative immediate.  */
+		. = . + 0x100;
+		*(.data*)
+	} >ram
+}
diff --git a/ld/testsuite/ld-riscv-elf/base-idx-relax-x0-emit-relocs.d b/ld/testsuite/ld-riscv-elf/base-idx-relax-x0-emit-relocs.d
new file mode 100644
index 00000000000..7a00382c148
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/base-idx-relax-x0-emit-relocs.d
@@ -0,0 +1,36 @@
+#source: base-idx-relax-x0.s
+#as: -march=rv64i_zba -mabi=lp64 --defsym __medlow__=1
+#ld: -Tbase-idx-relax-x0.ld -melf64lriscv --relax --emit-relocs
+#objdump: -dr -Mno-aliases
+
+.*:[ 	]+file format .*
+
+
+Disassembly of section .text:
+
+0+10000 <_start>:
+[ 	]+[0-9a-f]+:[ 	]+0805053b[ 	]+add\.uw[ 	]+a0,a0,zero
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_NONE[ 	]+\*ABS\*\+0x4
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_NONE[ 	]+\*ABS\*\+0x4
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+20052533[ 	]+sh1add[ 	]+a0,a0,zero
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+20054533[ 	]+sh2add[ 	]+a0,a0,zero
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+20056533[ 	]+sh3add[ 	]+a0,a0,zero
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+2005253b[ 	]+sh1add\.uw[ 	]+a0,a0,zero
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_ADD[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+10054603[ 	]+lbu[ 	]+a2,256\(a0\)
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_I[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
+[ 	]+[0-9a-f]+:[ 	]+10c50023[ 	]+sb[ 	]+a2,256\(a0\)
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_BASE_IDX_GPREL_S[ 	]+symL
+[ 	]+[0-9a-f]+:[ 	]+R_RISCV_RELAX[ 	]+\*ABS\*
diff --git a/ld/testsuite/ld-riscv-elf/base-idx-relax-x0.d b/ld/testsuite/ld-riscv-elf/base-idx-relax-x0.d
new file mode 100644
index 00000000000..d7ed6dc2f74
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/base-idx-relax-x0.d
@@ -0,0 +1,18 @@
+#source: base-idx-relax-x0.s
+#as: -march=rv64i_zba -mabi=lp64 --defsym __medlow__=1
+#ld: -Tbase-idx-relax-x0.ld -melf64lriscv --relax
+#objdump: -d -Mno-aliases
+
+.*:[ 	]+file format .*
+
+
+Disassembly of section .text:
+
+0+10000 <_start>:
+[ 	]+[0-9a-f]+:[ 	]+0805053b[ 	]+add\.uw[ 	]+a0,a0,zero
+[ 	]+[0-9a-f]+:[ 	]+20052533[ 	]+sh1add[ 	]+a0,a0,zero
+[ 	]+[0-9a-f]+:[ 	]+20054533[ 	]+sh2add[ 	]+a0,a0,zero
+[ 	]+[0-9a-f]+:[ 	]+20056533[ 	]+sh3add[ 	]+a0,a0,zero
+[ 	]+[0-9a-f]+:[ 	]+2005253b[ 	]+sh1add\.uw[ 	]+a0,a0,zero
+[ 	]+[0-9a-f]+:[ 	]+10054603[ 	]+lbu[ 	]+a2,256\(a0\)
+[ 	]+[0-9a-f]+:[ 	]+10c50023[ 	]+sb[ 	]+a2,256\(a0\)
diff --git a/ld/testsuite/ld-riscv-elf/base-idx-relax-x0.ld b/ld/testsuite/ld-riscv-elf/base-idx-relax-x0.ld
new file mode 100644
index 00000000000..613637a8de5
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/base-idx-relax-x0.ld
@@ -0,0 +1,23 @@
+/* symL's address fits directly in a 12-bit signed immediate, so
+   base-idx relaxation can use x0 as the base register directly,
+   without folding in gp at all.  __global_pointer$ is placed far
+   away so gp-relaxation cannot fire and only the x0 path is
+   exercised.  */
+
+ENTRY(_start)
+MEMORY
+{
+	rom (rx)  : ORIGIN = 0x10000, LENGTH = 0x1000
+	ram (!rx) : ORIGIN = 0x100, LENGTH = 0x1000
+}
+SECTIONS {
+	.text : {
+		*(.text*)
+	} >rom
+
+	.data : {
+		*(.data*)
+	} >ram
+
+	__global_pointer$ = 0x800000;
+}
diff --git a/ld/testsuite/ld-riscv-elf/base-idx-relax-x0.s b/ld/testsuite/ld-riscv-elf/base-idx-relax-x0.s
new file mode 100644
index 00000000000..d3cffdeaef6
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/base-idx-relax-x0.s
@@ -0,0 +1,22 @@
+	.text
+	.global _start
+_start:
+
+.ifdef __medlow__
+	lui             a1, %hi(symL)
+	add	        a0, a0, a1, %base_idx_add(symL)
+	add.uw	        a0, a0, a1, %base_idx_add(symL)
+	sh1add	        a0, a0, a1, %base_idx_add(symL)
+	sh2add	        a0, a0, a1, %base_idx_add(symL)
+	sh3add	        a0, a0, a1, %base_idx_add(symL)
+	sh1add.uw	a0, a0, a1, %base_idx_add(symL)
+	lbu	        a2, %base_idx_lo(symL)(a0)
+	sb      	a2, %base_idx_lo(symL)(a0)
+.endif
+
+	.size	_start, .-_start
+
+	.data
+	.global symL
+symL:
+	.dword	0x1111222233334444
diff --git a/ld/testsuite/ld-riscv-elf/base-idx-relax.s b/ld/testsuite/ld-riscv-elf/base-idx-relax.s
new file mode 100644
index 00000000000..0a925d16b23
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/base-idx-relax.s
@@ -0,0 +1,22 @@
+	.text
+	.global _start
+_start:
+
+.ifdef __medlow__
+	lui	        a1, %hi(symL)
+	add	        a2, a0, a1, %base_idx_add(symL)
+	add.uw	        a2, a0, a1, %base_idx_add(symL)
+	sh1add	        a2, a0, a1, %base_idx_add(symL)
+	sh2add	        a2, a0, a1, %base_idx_add(symL)
+	sh3add	        a2, a0, a1, %base_idx_add(symL)
+	sh3add.uw	a2, a0, a1, %base_idx_add(symL)
+	lbu     	a3, %base_idx_lo(symL)(a2)
+	sb      	a3, %base_idx_lo(symL)(a2)
+.endif
+
+	.size	_start, .-_start
+
+	.data
+	.global symL
+symL:
+	.dword	0x1111222233334444
diff --git a/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp b/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp
index 8e26ccff10a..82e906503d4 100644
--- a/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp
+++ b/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp
@@ -154,6 +154,12 @@ if [istarget "riscv*-*-*"] {
     run_dump_test "code-model-relax-medany-02"
     run_dump_test "code-model-relax-medany-weakref-01"
     run_dump_test "code-model-relax-medany-weakref-02"
+    run_dump_test "base-idx-relax-medlow-near"
+    run_dump_test "base-idx-relax-medlow-near-emit-relocs"
+    run_dump_test "base-idx-relax-medlow-far"
+    run_dump_test "base-idx-relax-medlow-far-emit-relocs"
+    run_dump_test "base-idx-relax-x0"
+    run_dump_test "base-idx-relax-x0-emit-relocs"
     run_dump_test "attr-merge-arch-01"
     run_dump_test "attr-merge-arch-02"
     run_dump_test "attr-merge-arch-03"
diff --git a/opcodes/riscv-dis.c b/opcodes/riscv-dis.c
index 03c8cf1e344..5a53367fdd6 100644
--- a/opcodes/riscv-dis.c
+++ b/opcodes/riscv-dis.c
@@ -513,6 +513,12 @@ print_insn_args (const char *oparg, insn_t l, bfd_vma pc, disassemble_info *info
 	    print (info->stream, dis_style_immediate, "0");
 	  break;
 
+	case '1':
+	  /* This is a relaxation marker operand (e.g. %tprel_add,
+	     %tlsdesc_call, %base_idx_add) that does not correspond to
+	     any encoding bits, so there is nothing to print for it.  */
+	  break;
+
 	case 'r':
 	  print (info->stream, dis_style_register, "%s",
 		 pd->riscv_gpr_names[EXTRACT_OPERAND (RS3, l)]);
diff --git a/opcodes/riscv-opc.c b/opcodes/riscv-opc.c
index 54887c97880..b08a0069f40 100644
--- a/opcodes/riscv-opc.c
+++ b/opcodes/riscv-opc.c
@@ -556,8 +556,8 @@ const struct riscv_opcode riscv_opcodes[] =
 {"add",         0, INSN_CLASS_ZCA, "Ct,Cc,CK",  MATCH_C_ADDI4SPN, MASK_C_ADDI4SPN, match_c_addi4spn, INSN_ALIAS },
 {"add",         0, INSN_CLASS_ZCA, "Cc,Cc,CL",  MATCH_C_ADDI16SP, MASK_C_ADDI16SP, match_c_addi16sp, INSN_ALIAS },
 {"add",         0, INSN_CLASS_ZCA, "d,Cz,CV",   MATCH_C_MV, MASK_C_MV, match_c_add, INSN_ALIAS },
-{"add",         0, INSN_CLASS_I, "d,s,t",     MATCH_ADD, MASK_ADD, match_opcode, 0 },
-{"add",         0, INSN_CLASS_I, "d,s,t,1",   MATCH_ADD, MASK_ADD, match_opcode, 0 },
+/*make fourth operand (tprel_add/base_idx_add) of add an optional operand*/
+{"add",         0, INSN_CLASS_I, "d,s,t1",    MATCH_ADD, MASK_ADD, match_opcode, 0 },
 {"add",         0, INSN_CLASS_I, "d,s,j",     MATCH_ADDI, MASK_ADDI, match_opcode, INSN_ALIAS },
 {"la",          0, INSN_CLASS_I, "d,B",       0, (int) M_LA, match_rd_nonzero, INSN_MACRO },
 {"lla",         0, INSN_CLASS_I, "d,B",       0, (int) M_LLA, NULL, INSN_MACRO },
@@ -1378,16 +1378,18 @@ const struct riscv_opcode riscv_opcodes[] =
 {"rorw",      64, INSN_CLASS_ZBB_OR_ZBKB,  "d,s,<", MATCH_RORIW, MASK_RORIW, match_opcode, INSN_ALIAS },
 
 /* Zba instructions.  */
-{"sh1add",     0, INSN_CLASS_ZBA,  "d,s,t", MATCH_SH1ADD, MASK_SH1ADD, match_opcode, 0 },
-{"sh2add",     0, INSN_CLASS_ZBA,  "d,s,t", MATCH_SH2ADD, MASK_SH2ADD, match_opcode, 0 },
-{"sh3add",     0, INSN_CLASS_ZBA,  "d,s,t", MATCH_SH3ADD, MASK_SH3ADD, match_opcode, 0 },
-{"sh1add.uw", 64, INSN_CLASS_ZBA,  "d,s,t", MATCH_SH1ADD_UW, MASK_SH1ADD_UW, match_opcode, 0 },
-{"sh2add.uw", 64, INSN_CLASS_ZBA,  "d,s,t", MATCH_SH2ADD_UW, MASK_SH2ADD_UW, match_opcode, 0 },
-{"sh3add.uw", 64, INSN_CLASS_ZBA,  "d,s,t", MATCH_SH3ADD_UW, MASK_SH3ADD_UW, match_opcode, 0 },
+/*make fourth operand (base_idx_add) of shxadd/shxadd.uw an optional operand*/
+{"sh1add",     0, INSN_CLASS_ZBA,  "d,s,t1",  MATCH_SH1ADD, MASK_SH1ADD, match_opcode, 0 },
+{"sh2add",     0, INSN_CLASS_ZBA,  "d,s,t1",  MATCH_SH2ADD, MASK_SH2ADD, match_opcode, 0 },
+{"sh3add",     0, INSN_CLASS_ZBA,  "d,s,t1",  MATCH_SH3ADD, MASK_SH3ADD, match_opcode, 0 },
+{"sh1add.uw", 64, INSN_CLASS_ZBA,  "d,s,t1",  MATCH_SH1ADD_UW, MASK_SH1ADD_UW, match_opcode, 0 },
+{"sh2add.uw", 64, INSN_CLASS_ZBA,  "d,s,t1",  MATCH_SH2ADD_UW, MASK_SH2ADD_UW, match_opcode, 0 },
+{"sh3add.uw", 64, INSN_CLASS_ZBA,  "d,s,t1",  MATCH_SH3ADD_UW, MASK_SH3ADD_UW, match_opcode, 0 },
 {"zext.w",    64, INSN_CLASS_ZCB_AND_ZBA,  "Cs,Cw", MATCH_C_ZEXT_W, MASK_C_ZEXT_W, match_opcode, INSN_ALIAS },
 {"zext.w",    64, INSN_CLASS_ZBA,  "d,s",   MATCH_ADD_UW, MASK_ADD_UW | MASK_RS2, match_opcode, INSN_ALIAS },
 {"zext.w",    64, INSN_CLASS_I, "d,s",       0, (int) M_ZEXTW, NULL, INSN_MACRO },
-{"add.uw",    64, INSN_CLASS_ZBA,  "d,s,t", MATCH_ADD_UW, MASK_ADD_UW, match_opcode, 0 },
+/*make fourth operand (base_idx_add) of add.uw an optional operand*/
+{"add.uw",    64, INSN_CLASS_ZBA,  "d,s,t1", MATCH_ADD_UW, MASK_ADD_UW, match_opcode, 0 },
 {"slli.uw",   64, INSN_CLASS_ZBA,  "d,s,>", MATCH_SLLI_UW, MASK_SLLI_UW, match_opcode, 0 },
 
 /* Zbc or zbkc instructions.  */
-- 
2.43.0