]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blobdiff - gas/config/tc-riscv.c
Fix memory leak in RiscV assembler.
[thirdparty/binutils-gdb.git] / gas / config / tc-riscv.c
index f4478eac11e80e89a0afe3a97a1d80d65e64590a..4b7ff6dcc438e65fb5eff740fe609584c0ef3aa6 100644 (file)
@@ -1,5 +1,5 @@
 /* tc-riscv.c -- RISC-V assembler
-   Copyright (C) 2011-2022 Free Software Foundation, Inc.
+   Copyright (C) 2011-2023 Free Software Foundation, Inc.
 
    Contributed by Andrew Waterman (andrew@sifive.com).
    Based on MIPS target.
@@ -42,9 +42,13 @@ struct riscv_cl_insn
   /* The opcode's entry in riscv_opcodes.  */
   const struct riscv_opcode *insn_mo;
 
-  /* The encoded instruction bits.  */
+  /* The encoded instruction bits
+     (first bits enough to extract instruction length on a long opcode).  */
   insn_t insn_opcode;
 
+  /* The long encoded instruction bits ([0] is non-zero on a long opcode).  */
+  char insn_long_opcode[RISCV_MAX_INSN_LEN];
+
   /* The frag that contains the instruction.  */
   struct frag *frag;
 
@@ -68,10 +72,19 @@ enum riscv_csr_class
   CSR_CLASS_DEBUG,     /* debug CSR */
   CSR_CLASS_H,         /* hypervisor */
   CSR_CLASS_H_32,      /* hypervisor, rv32 only */
+  CSR_CLASS_SMAIA,             /* Smaia */
+  CSR_CLASS_SMAIA_32,          /* Smaia, rv32 only */
+  CSR_CLASS_SMCNTRPMF,         /* Smcntrpmf */
+  CSR_CLASS_SMCNTRPMF_32,      /* Smcntrpmf, rv32 only */
   CSR_CLASS_SMSTATEEN,         /* Smstateen only */
-  CSR_CLASS_SMSTATEEN_AND_H,   /* Smstateen only (with H) */
   CSR_CLASS_SMSTATEEN_32,      /* Smstateen RV32 only */
-  CSR_CLASS_SMSTATEEN_AND_H_32,        /* Smstateen RV32 only (with H) */
+  CSR_CLASS_SSAIA,             /* Ssaia */
+  CSR_CLASS_SSAIA_AND_H,       /* Ssaia with H */
+  CSR_CLASS_SSAIA_32,          /* Ssaia, rv32 only */
+  CSR_CLASS_SSAIA_AND_H_32,    /* Ssaia with H, rv32 only */
+  CSR_CLASS_SSSTATEEN,         /* S[ms]stateen only */
+  CSR_CLASS_SSSTATEEN_AND_H,   /* S[ms]stateen only (with H) */
+  CSR_CLASS_SSSTATEEN_AND_H_32,        /* S[ms]stateen RV32 only (with H) */
   CSR_CLASS_SSCOFPMF,          /* Sscofpmf only */
   CSR_CLASS_SSCOFPMF_32,       /* Sscofpmf RV32 only */
   CSR_CLASS_SSTC,              /* Sstc only */
@@ -160,6 +173,8 @@ static enum float_abi float_abi = FLOAT_ABI_DEFAULT;
 
 static unsigned elf_flags = 0;
 
+static bool probing_insn_operands;
+
 /* Set the default_isa_spec.  Return 0 if the spec isn't supported.
    Otherwise, return 1.  */
 
@@ -324,7 +339,8 @@ riscv_set_arch (const char *s)
   riscv_reset_subsets_list_arch_str ();
 
   riscv_set_rvc (false);
-  if (riscv_subset_supports (&riscv_rps_as, "c"))
+  if (riscv_subset_supports (&riscv_rps_as, "c")
+      || riscv_subset_supports (&riscv_rps_as, "zca"))
     riscv_set_rvc (true);
 
   if (riscv_subset_supports (&riscv_rps_as, "ztso"))
@@ -434,7 +450,7 @@ static bool explicit_attr = false;
 /* Indicate CSR or priv instructions are explicitly used.  */
 static bool explicit_priv_attr = false;
 
-static char *expr_end;
+static char *expr_parse_end;
 
 /* Macros for encoding relaxation state for RVC branches and far jumps.  */
 #define RELAX_BRANCH_ENCODE(uncond, rvc, length)       \
@@ -470,18 +486,14 @@ static char *expr_end;
 #define OPCODE_MATCHES(OPCODE, OP) \
   (((OPCODE) & MASK_##OP) == MATCH_##OP)
 
-/* Indicate if .option directives do affect instructions.  Set to true means
-   we need to add $x+arch at somewhere; Otherwise just add $x for instructions
-   should be enough.  */
-static bool need_arch_map_symbol = false;
-
 /* Create a new mapping symbol for the transition to STATE.  */
 
 static void
 make_mapping_symbol (enum riscv_seg_mstate state,
                     valueT value,
                     fragS *frag,
-                    bool reset_seg_arch_str)
+                    const char *arch_str,
+                    bool odd_data_padding)
 {
   const char *name;
   char *buff = NULL;
@@ -491,12 +503,11 @@ make_mapping_symbol (enum riscv_seg_mstate state,
       name = "$d";
       break;
     case MAP_INSN:
-      if (reset_seg_arch_str)
+      if (arch_str != NULL)
        {
-         const char *isa = riscv_rps_as.subset_list->arch_str;
-         size_t size = strlen (isa) + 3; /* "rv" + '\0'  */
+         size_t size = strlen (arch_str) + 3; /* "$x" + '\0'  */
          buff = xmalloc (size);
-         snprintf (buff, size, "$x%s", isa);
+         snprintf (buff, size, "$x%s", arch_str);
          name = buff;
        }
       else
@@ -508,7 +519,7 @@ make_mapping_symbol (enum riscv_seg_mstate state,
 
   symbolS *symbol = symbol_new (name, now_seg, frag, value);
   symbol_get_bfdsym (symbol)->flags |= (BSF_NO_FLAGS | BSF_LOCAL);
-  if (reset_seg_arch_str)
+  if (arch_str != NULL)
     {
       /* Store current $x+arch into tc_segment_info.  */
       seg_info (now_seg)->tc_segment_info_data.arch_map_symbol = symbol;
@@ -522,6 +533,7 @@ make_mapping_symbol (enum riscv_seg_mstate state,
      and .text.zero.fill.last.  */
   symbolS *first = frag->tc_frag_data.first_map_symbol;
   symbolS *last = frag->tc_frag_data.last_map_symbol;
+  symbolS *removed = NULL;
   if (value == 0)
     {
       if (first != NULL)
@@ -529,7 +541,7 @@ make_mapping_symbol (enum riscv_seg_mstate state,
          know (S_GET_VALUE (first) == S_GET_VALUE (symbol)
                && first == last);
          /* Remove the old one.  */
-         symbol_remove (first, &symbol_rootP, &symbol_lastP);
+         removed = first;
        }
       frag->tc_frag_data.first_map_symbol = symbol;
     }
@@ -539,9 +551,23 @@ make_mapping_symbol (enum riscv_seg_mstate state,
       know (S_GET_VALUE (last) <= S_GET_VALUE (symbol));
       /* Remove the old one.  */
       if (S_GET_VALUE (last) == S_GET_VALUE (symbol))
-       symbol_remove (last, &symbol_rootP, &symbol_lastP);
+       removed = last;
     }
   frag->tc_frag_data.last_map_symbol = symbol;
+
+  if (removed == NULL)
+    return;
+
+  if (odd_data_padding)
+    {
+      /* If the removed mapping symbol is $x+arch, then add it back to
+        the next $x.  */
+      const char *str = strncmp (S_GET_NAME (removed), "$xrv", 4) == 0
+                       ? S_GET_NAME (removed) + 2 : NULL;
+      make_mapping_symbol (MAP_INSN, frag->fr_fix + 1, frag, str,
+                          false/* odd_data_padding */);
+    }
+  symbol_remove (removed, &symbol_rootP, &symbol_lastP);
 }
 
 /* Set the mapping state for frag_now.  */
@@ -549,7 +575,7 @@ make_mapping_symbol (enum riscv_seg_mstate state,
 void
 riscv_mapping_state (enum riscv_seg_mstate to_state,
                     int max_chars,
-                    bool frag_align_code)
+                    bool fr_align_code)
 {
   enum riscv_seg_mstate from_state =
        seg_info (now_seg)->tc_segment_info_data.map_state;
@@ -574,19 +600,21 @@ riscv_mapping_state (enum riscv_seg_mstate to_state,
     }
   else if (seg_arch_symbol != 0
           && to_state == MAP_INSN
-          && !frag_align_code
+          && !fr_align_code
           && strcmp (riscv_rps_as.subset_list->arch_str,
                      S_GET_NAME (seg_arch_symbol) + 2) != 0)
     {
       reset_seg_arch_str = true;
-      need_arch_map_symbol = true;
     }
   else if (from_state == to_state)
     return;
 
   valueT value = (valueT) (frag_now_fix () - max_chars);
   seg_info (now_seg)->tc_segment_info_data.map_state = to_state;
-  make_mapping_symbol (to_state, value, frag_now, reset_seg_arch_str);
+  const char *arch_str = reset_seg_arch_str
+                        ? riscv_rps_as.subset_list->arch_str : NULL;
+  make_mapping_symbol (to_state, value, frag_now, arch_str,
+                      false/* odd_data_padding */);
 }
 
 /* Add the odd bytes of paddings for riscv_handle_align.  */
@@ -597,25 +625,9 @@ riscv_add_odd_padding_symbol (fragS *frag)
   /* If there was already a mapping symbol, it should be
      removed in the make_mapping_symbol.
 
-     Please see gas/testsuite/gas/riscv/mapping.s: .text.odd.align.  */
-  make_mapping_symbol (MAP_DATA, frag->fr_fix, frag, false);
-  make_mapping_symbol (MAP_INSN, frag->fr_fix + 1, frag, false);
-}
-
-/* If previous and current mapping symbol have same value, then remove the
-   current $x only if the previous is $x+arch; Otherwise, always remove the
-   previous.  */
-
-static void
-riscv_remove_mapping_symbol (symbolS *pre, symbolS *cur)
-{
-  know (pre != NULL && cur != NULL
-       && S_GET_VALUE (pre) == S_GET_VALUE (cur));
-  symbolS *removed = pre;
-  if (strncmp (S_GET_NAME (pre), "$xrv", 4) == 0
-      && strcmp (S_GET_NAME (cur), "$x") == 0)
-    removed = cur;
-  symbol_remove (removed, &symbol_rootP, &symbol_lastP);
+     Please see gas/testsuite/gas/riscv/mapping.s: .text.odd.align.*.  */
+  make_mapping_symbol (MAP_DATA, frag->fr_fix, frag,
+                      NULL/* arch_str */, true/* odd_data_padding */);
 }
 
 /* Remove any excess mapping symbols generated for alignment frags in
@@ -634,13 +646,6 @@ riscv_check_mapping_symbols (bfd *abfd ATTRIBUTE_UNUSED,
   if (seginfo == NULL || seginfo->frchainP == NULL)
     return;
 
-  /* If we don't set any .option arch directive, then the arch_map_symbol
-     in each segment must be the first instruction, and we don't need to
-     add $x+arch for them.  */
-  if (!need_arch_map_symbol
-      && seginfo->tc_segment_info_data.arch_map_symbol != 0)
-    S_SET_NAME (seginfo->tc_segment_info_data.arch_map_symbol, "$x");
-
   for (fragp = seginfo->frchainP->frch_root;
        fragp != NULL;
        fragp = fragp->fr_next)
@@ -667,7 +672,12 @@ riscv_check_mapping_symbols (bfd *abfd ATTRIBUTE_UNUSED,
 
                 Please see the gas/testsuite/gas/riscv/mapping.s:
                 .text.zero.fill.align.A and .text.zero.fill.align.B.  */
-             riscv_remove_mapping_symbol (last, next_first);
+             know (S_GET_VALUE (last) == S_GET_VALUE (next_first));
+             symbolS *removed = last;
+             if (strncmp (S_GET_NAME (last), "$xrv", 4) == 0
+                 && strcmp (S_GET_NAME (next_first), "$x") == 0)
+               removed = next_first;
+             symbol_remove (removed, &symbol_rootP, &symbol_lastP);
              break;
            }
 
@@ -719,6 +729,7 @@ create_insn (struct riscv_cl_insn *insn, const struct riscv_opcode *mo)
 {
   insn->insn_mo = mo;
   insn->insn_opcode = mo->match;
+  insn->insn_long_opcode[0] = 0;
   insn->frag = NULL;
   insn->where = 0;
   insn->fixp = NULL;
@@ -730,7 +741,10 @@ static void
 install_insn (const struct riscv_cl_insn *insn)
 {
   char *f = insn->frag->fr_literal + insn->where;
-  number_to_chars_littleendian (f, insn->insn_opcode, insn_length (insn));
+  if (insn->insn_long_opcode[0] != 0)
+    memcpy (f, insn->insn_long_opcode, insn_length (insn));
+  else
+    number_to_chars_littleendian (f, insn->insn_opcode, insn_length (insn));
 }
 
 /* Move INSN to offset WHERE in FRAG.  Adjust the fixups accordingly
@@ -905,7 +919,7 @@ opcode_name_lookup (char **s)
     *s = e;
 
   *e = save_c;
-  expr_end = e;
+  expr_parse_end = e;
 
   return o;
 }
@@ -939,7 +953,7 @@ hash_reg_name (enum reg_class class, const char *name, unsigned n)
 }
 
 static void
-hash_reg_names (enum reg_class class, const char * const names[], unsigned n)
+hash_reg_names (enum reg_class class, const char names[][NRC], unsigned n)
 {
   unsigned i;
 
@@ -1034,16 +1048,44 @@ riscv_csr_address (const char *csr_name,
     case CSR_CLASS_V:
       extension = "zve32x";
       break;
-    case CSR_CLASS_SMSTATEEN:
-    case CSR_CLASS_SMSTATEEN_AND_H:
+    case CSR_CLASS_SMAIA_32:
+      is_rv32_only = true;
+      /* Fall through.  */
+    case CSR_CLASS_SMAIA:
+      extension = "smaia";
+      break;
+    case CSR_CLASS_SMCNTRPMF_32:
+      is_rv32_only = true;
+      /* Fall through.  */
+    case CSR_CLASS_SMCNTRPMF:
+      need_check_version = true;
+      extension = "smcntrpmf";
+      break;
     case CSR_CLASS_SMSTATEEN_32:
-    case CSR_CLASS_SMSTATEEN_AND_H_32:
-      is_rv32_only = (csr_class == CSR_CLASS_SMSTATEEN_32
-                     || csr_class == CSR_CLASS_SMSTATEEN_AND_H_32);
-      is_h_required = (csr_class == CSR_CLASS_SMSTATEEN_AND_H
-                     || csr_class == CSR_CLASS_SMSTATEEN_AND_H_32);
+      is_rv32_only = true;
+      /* Fall through.  */
+    case CSR_CLASS_SMSTATEEN:
       extension = "smstateen";
       break;
+    case CSR_CLASS_SSAIA:
+    case CSR_CLASS_SSAIA_AND_H:
+    case CSR_CLASS_SSAIA_32:
+    case CSR_CLASS_SSAIA_AND_H_32:
+      is_rv32_only = (csr_class == CSR_CLASS_SSAIA_32
+                     || csr_class == CSR_CLASS_SSAIA_AND_H_32);
+      is_h_required = (csr_class == CSR_CLASS_SSAIA_AND_H
+                      || csr_class == CSR_CLASS_SSAIA_AND_H_32);
+      extension = "ssaia";
+      break;
+    case CSR_CLASS_SSSTATEEN_AND_H_32:
+      is_rv32_only = true;
+      /* Fall through.  */
+    case CSR_CLASS_SSSTATEEN_AND_H:
+      is_h_required = true;
+      /* Fall through.  */
+    case CSR_CLASS_SSSTATEEN:
+      extension = "ssstateen";
+      break;
     case CSR_CLASS_SSCOFPMF_32:
       is_rv32_only = true;
       /* Fall through.  */
@@ -1178,7 +1220,8 @@ arg_lookup (char **s, const char *const *array, size_t size, unsigned *regnop)
     return false;
 
   for (i = 0; i < size; i++)
-    if (array[i] != NULL && strncmp (array[i], *s, len) == 0)
+    if (array[i] != NULL && strncmp (array[i], *s, len) == 0
+       && array[i][len] == '\0')
       {
        *regnop = i;
        *s += len;
@@ -1188,13 +1231,29 @@ arg_lookup (char **s, const char *const *array, size_t size, unsigned *regnop)
   return false;
 }
 
+static bool
+flt_lookup (float f, const float *array, size_t size, unsigned *regnop)
+{
+  size_t i;
+
+  for (i = 0; i < size; i++)
+    if (array[i] == f)
+      {
+       *regnop = i;
+       return true;
+      }
+
+  return false;
+}
+
 #define USE_BITS(mask,shift) (used_bits |= ((insn_t)(mask) << (shift)))
 #define USE_IMM(n, s) \
   (used_bits |= ((insn_t)((1ull<<n)-1) << (s)))
 
 /* For consistency checking, verify that all bits are specified either
    by the match/mask part of the instruction definition, or by the
-   operand list. The `length` could be 0, 4 or 8, 0 for auto detection.  */
+   operand list. The `length` could be the actual instruction length or
+   0 for auto-detection.  */
 
 static bool
 validate_riscv_insn (const struct riscv_opcode *opc, int length)
@@ -1205,11 +1264,13 @@ validate_riscv_insn (const struct riscv_opcode *opc, int length)
   insn_t required_bits;
 
   if (length == 0)
-    insn_width = 8 * riscv_insn_length (opc->match);
-  else
-    insn_width = 8 * length;
+    length = riscv_insn_length (opc->match);
+  /* We don't support instructions longer than 64-bits yet.  */
+  if (length > 8)
+    length = 8;
+  insn_width = 8 * length;
 
-  required_bits = ~0ULL >> (64 - insn_width);
+  required_bits = ((insn_t)~0ULL) >> (64 - insn_width);
 
   if ((used_bits & opc->match) != (opc->match & required_bits))
     {
@@ -1290,6 +1351,7 @@ validate_riscv_insn (const struct riscv_opcode *opc, int length)
            case 'i':
            case 'j':
            case 'k': USE_BITS (OP_MASK_VIMM, OP_SH_VIMM); break;
+           case 'l': used_bits |= ENCODE_RVV_VI_UIMM6 (-1U); break;
            case 'm': USE_BITS (OP_MASK_VMASK, OP_SH_VMASK); break;
            case 'M': break; /* Macro operand, must be a mask register.  */
            case 'T': break; /* Macro operand, must be a vector register.  */
@@ -1328,7 +1390,6 @@ validate_riscv_insn (const struct riscv_opcode *opc, int length)
        case 'j': used_bits |= ENCODE_ITYPE_IMM (-1U); break;
        case 'a': used_bits |= ENCODE_JTYPE_IMM (-1U); break;
        case 'p': used_bits |= ENCODE_BTYPE_IMM (-1U); break;
-       case 'f': /* Fall through.  */
        case 'q': used_bits |= ENCODE_STYPE_IMM (-1U); break;
        case 'u': used_bits |= ENCODE_UTYPE_IMM (-1U); break;
        case 'z': break; /* Zero immediate.  */
@@ -1339,49 +1400,90 @@ validate_riscv_insn (const struct riscv_opcode *opc, int length)
        case 'F': /* Funct for .insn directive.  */
          switch (*++oparg)
            {
-             case '7': USE_BITS (OP_MASK_FUNCT7, OP_SH_FUNCT7); break;
-             case '3': USE_BITS (OP_MASK_FUNCT3, OP_SH_FUNCT3); break;
-             case '2': USE_BITS (OP_MASK_FUNCT2, OP_SH_FUNCT2); break;
-             default:
-               goto unknown_validate_operand;
+           case '7': USE_BITS (OP_MASK_FUNCT7, OP_SH_FUNCT7); break;
+           case '3': USE_BITS (OP_MASK_FUNCT3, OP_SH_FUNCT3); break;
+           case '2': USE_BITS (OP_MASK_FUNCT2, OP_SH_FUNCT2); break;
+           default:
+             goto unknown_validate_operand;
            }
          break;
        case 'O': /* Opcode for .insn directive.  */
          switch (*++oparg)
            {
-             case '4': USE_BITS (OP_MASK_OP, OP_SH_OP); break;
-             case '2': USE_BITS (OP_MASK_OP2, OP_SH_OP2); break;
-             default:
-               goto unknown_validate_operand;
+           case '4': USE_BITS (OP_MASK_OP, OP_SH_OP); break;
+           case '2': USE_BITS (OP_MASK_OP2, OP_SH_OP2); break;
+           default:
+             goto unknown_validate_operand;
            }
          break;
-       case 'X': /* Integer immediate.  */
-         {
-           size_t n;
-           size_t s;
-
-           switch (*++oparg)
-             {
-               case 'l': /* Literal.  */
-                 oparg += strcspn(oparg, ",") - 1;
-                 break;
-               case 's': /* 'XsN@S' ... N-bit signed immediate at bit S.  */
-                 goto use_imm;
-               case 'u': /* 'XuN@S' ... N-bit unsigned immediate at bit S.  */
-                 goto use_imm;
-               use_imm:
-                 n = strtol (oparg + 1, (char **)&oparg, 10);
-                 if (*oparg != '@')
-                   goto unknown_validate_operand;
-                 s = strtol (oparg + 1, (char **)&oparg, 10);
-                 oparg--;
-
-                 USE_IMM (n, s);
-                 break;
+       case 'W': /* Various operands for standard z extensions.  */
+         switch (*++oparg)
+           {
+           case 'i':
+             switch (*++oparg)
+               {
+               case 'f': used_bits |= ENCODE_STYPE_IMM (-1U); break;
+               default:
+                 goto unknown_validate_operand;
+               }
+             break;
+           case 'f':
+             switch (*++oparg)
+               {
+               case 'v': USE_BITS (OP_MASK_RS1, OP_SH_RS1); break;
                default:
                  goto unknown_validate_operand;
+               }
+             break;
+           case 'c':
+             switch (*++oparg)
+               {
+               /* byte immediate operators, load/store byte insns.  */
+               case 'h': used_bits |= ENCODE_ZCB_HALFWORD_UIMM (-1U); break;
+               /* halfword immediate operators, load/store halfword insns.  */
+               case 'b': used_bits |= ENCODE_ZCB_BYTE_UIMM (-1U); break;
+               case 'f': break;
+               default:
+                 goto unknown_validate_operand;
+               }
+             break;
+           default:
+             goto unknown_validate_operand;
+           }
+         break;
+       case 'X': /* Vendor-specific operands.  */
+         switch (*++oparg)
+           {
+           case 't': /* Vendor-specific (T-head) operands.  */
+             {
+               size_t n;
+               size_t s;
+               switch (*++oparg)
+                 {
+                 case 'l': /* Integer immediate, literal.  */
+                   oparg += strcspn(oparg, ",") - 1;
+                   break;
+                 case 's': /* Integer immediate, 'XtsN@S' ... N-bit signed immediate at bit S.  */
+                   goto use_imm;
+                 case 'u': /* Integer immediate, 'XtuN@S' ... N-bit unsigned immediate at bit S.  */
+                   goto use_imm;
+                 use_imm:
+                   n = strtol (oparg + 1, (char **)&oparg, 10);
+                   if (*oparg != '@')
+                     goto unknown_validate_operand;
+                   s = strtol (oparg + 1, (char **)&oparg, 10);
+                   oparg--;
+
+                   USE_IMM (n, s);
+                   break;
+                 default:
+                   goto unknown_validate_operand;
+                 }
              }
-         }
+             break;
+           default:
+             goto unknown_validate_operand;
+           }
          break;
        default:
        unknown_validate_operand:
@@ -1395,8 +1497,8 @@ validate_riscv_insn (const struct riscv_opcode *opc, int length)
   if (used_bits != required_bits)
     {
       as_bad (_("internal: bad RISC-V opcode "
-               "(bits 0x%lx undefined): %s %s"),
-             ~(unsigned long)(used_bits & required_bits),
+               "(bits %#llx undefined or invalid): %s %s"),
+             (unsigned long long)(used_bits ^ required_bits),
              opc->name, opc->args);
       return false;
     }
@@ -1862,6 +1964,7 @@ vector_macro (struct riscv_cl_insn *ip)
   int vs2 = (ip->insn_opcode >> OP_SH_VS2) & OP_MASK_VS2;
   int vm = (ip->insn_opcode >> OP_SH_VMASK) & OP_MASK_VMASK;
   int vtemp = (ip->insn_opcode >> OP_SH_VFUNCT6) & OP_MASK_VFUNCT6;
+  const char *vmslt_vx = ip->insn_mo->match ? "vmsltu.vx" : "vmslt.vx";
   int mask = ip->insn_mo->mask;
 
   switch (mask)
@@ -1870,42 +1973,7 @@ vector_macro (struct riscv_cl_insn *ip)
       if (vm)
        {
          /* Unmasked.  */
-         macro_build (NULL, "vmslt.vx", "Vd,Vt,sVm", vd, vs2, vs1, -1);
-         macro_build (NULL, "vmnand.mm", "Vd,Vt,Vs", vd, vd, vd);
-         break;
-       }
-      if (vtemp != 0)
-       {
-         /* Masked.  Have vtemp to avoid overlap constraints.  */
-         if (vd == vm)
-           {
-             macro_build (NULL, "vmslt.vx", "Vd,Vt,s", vtemp, vs2, vs1);
-             macro_build (NULL, "vmandnot.mm", "Vd,Vt,Vs", vd, vm, vtemp);
-           }
-         else
-           {
-             /* Preserve the value of vd if not updating by vm.  */
-             macro_build (NULL, "vmslt.vx", "Vd,Vt,s", vtemp, vs2, vs1);
-             macro_build (NULL, "vmandnot.mm", "Vd,Vt,Vs", vtemp, vm, vtemp);
-             macro_build (NULL, "vmandnot.mm", "Vd,Vt,Vs", vd, vd, vm);
-             macro_build (NULL, "vmor.mm", "Vd,Vt,Vs", vd, vtemp, vd);
-           }
-       }
-      else if (vd != vm)
-       {
-         /* Masked.  This may cause the vd overlaps vs2, when LMUL > 1.  */
-         macro_build (NULL, "vmslt.vx", "Vd,Vt,sVm", vd, vs2, vs1, vm);
-         macro_build (NULL, "vmxor.mm", "Vd,Vt,Vs", vd, vd, vm);
-       }
-      else
-       as_bad (_("must provide temp if destination overlaps mask"));
-      break;
-
-    case M_VMSGEU:
-      if (vm)
-       {
-         /* Unmasked.  */
-         macro_build (NULL, "vmsltu.vx", "Vd,Vt,sVm", vd, vs2, vs1, -1);
+         macro_build (NULL, vmslt_vx, "Vd,Vt,sVm", vd, vs2, vs1, -1);
          macro_build (NULL, "vmnand.mm", "Vd,Vt,Vs", vd, vd, vd);
          break;
        }
@@ -1914,13 +1982,13 @@ vector_macro (struct riscv_cl_insn *ip)
          /* Masked.  Have vtemp to avoid overlap constraints.  */
          if (vd == vm)
            {
-             macro_build (NULL, "vmsltu.vx", "Vd,Vt,s", vtemp, vs2, vs1);
+             macro_build (NULL, vmslt_vx, "Vd,Vt,sVm", vtemp, vs2, vs1, -1);
              macro_build (NULL, "vmandnot.mm", "Vd,Vt,Vs", vd, vm, vtemp);
            }
          else
            {
              /* Preserve the value of vd if not updating by vm.  */
-             macro_build (NULL, "vmsltu.vx", "Vd,Vt,s", vtemp, vs2, vs1);
+             macro_build (NULL, vmslt_vx, "Vd,Vt,sVm", vtemp, vs2, vs1, -1);
              macro_build (NULL, "vmandnot.mm", "Vd,Vt,Vs", vtemp, vm, vtemp);
              macro_build (NULL, "vmandnot.mm", "Vd,Vt,Vs", vd, vd, vm);
              macro_build (NULL, "vmor.mm", "Vd,Vt,Vs", vd, vtemp, vd);
@@ -1929,7 +1997,7 @@ vector_macro (struct riscv_cl_insn *ip)
       else if (vd != vm)
        {
          /* Masked.  This may cause the vd overlaps vs2, when LMUL > 1.  */
-         macro_build (NULL, "vmsltu.vx", "Vd,Vt,sVm", vd, vs2, vs1, vm);
+         macro_build (NULL, vmslt_vx, "Vd,Vt,sVm", vd, vs2, vs1, vm);
          macro_build (NULL, "vmxor.mm", "Vd,Vt,Vs", vd, vd, vm);
        }
       else
@@ -1960,16 +2028,20 @@ macro (struct riscv_cl_insn *ip, expressionS *imm_expr,
 
     case M_LA:
     case M_LLA:
+    case M_LGA:
       /* Load the address of a symbol into a register.  */
       if (!IS_SEXT_32BIT_NUM (imm_expr->X_add_number))
        as_bad (_("offset too large"));
 
       if (imm_expr->X_op == O_constant)
        load_const (rd, imm_expr);
-      else if (riscv_opts.pic && mask == M_LA) /* Global PIC symbol.  */
+      /* Global PIC symbol.  */
+      else if ((riscv_opts.pic && mask == M_LA)
+              || mask == M_LGA)
        pcrel_load (rd, rd, imm_expr, LOAD_ADDRESS_INSN,
                    BFD_RELOC_RISCV_GOT_HI20, BFD_RELOC_RISCV_PCREL_LO12_I);
-      else /* Local PIC symbol, or any non-PIC symbol.  */
+      /* Local PIC symbol, or any non-PIC symbol.  */
+      else
        pcrel_load (rd, rd, imm_expr, "addi",
                    BFD_RELOC_RISCV_PCREL_HI20, BFD_RELOC_RISCV_PCREL_LO12_I);
       break;
@@ -2080,7 +2152,6 @@ macro (struct riscv_cl_insn *ip, expressionS *imm_expr,
       break;
 
     case M_VMSGE:
-    case M_VMSGEU:
       vector_macro (ip);
       break;
 
@@ -2101,34 +2172,34 @@ macro (struct riscv_cl_insn *ip, expressionS *imm_expr,
 
 static const struct percent_op_match percent_op_utype[] =
 {
-  {"%tprel_hi", BFD_RELOC_RISCV_TPREL_HI20},
-  {"%pcrel_hi", BFD_RELOC_RISCV_PCREL_HI20},
-  {"%got_pcrel_hi", BFD_RELOC_RISCV_GOT_HI20},
-  {"%tls_ie_pcrel_hi", BFD_RELOC_RISCV_TLS_GOT_HI20},
-  {"%tls_gd_pcrel_hi", BFD_RELOC_RISCV_TLS_GD_HI20},
-  {"%hi", BFD_RELOC_RISCV_HI20},
+  {"tprel_hi", BFD_RELOC_RISCV_TPREL_HI20},
+  {"pcrel_hi", BFD_RELOC_RISCV_PCREL_HI20},
+  {"got_pcrel_hi", BFD_RELOC_RISCV_GOT_HI20},
+  {"tls_ie_pcrel_hi", BFD_RELOC_RISCV_TLS_GOT_HI20},
+  {"tls_gd_pcrel_hi", BFD_RELOC_RISCV_TLS_GD_HI20},
+  {"hi", BFD_RELOC_RISCV_HI20},
   {0, 0}
 };
 
 static const struct percent_op_match percent_op_itype[] =
 {
-  {"%lo", BFD_RELOC_RISCV_LO12_I},
-  {"%tprel_lo", BFD_RELOC_RISCV_TPREL_LO12_I},
-  {"%pcrel_lo", BFD_RELOC_RISCV_PCREL_LO12_I},
+  {"lo", BFD_RELOC_RISCV_LO12_I},
+  {"tprel_lo", BFD_RELOC_RISCV_TPREL_LO12_I},
+  {"pcrel_lo", BFD_RELOC_RISCV_PCREL_LO12_I},
   {0, 0}
 };
 
 static const struct percent_op_match percent_op_stype[] =
 {
-  {"%lo", BFD_RELOC_RISCV_LO12_S},
-  {"%tprel_lo", BFD_RELOC_RISCV_TPREL_LO12_S},
-  {"%pcrel_lo", BFD_RELOC_RISCV_PCREL_LO12_S},
+  {"lo", BFD_RELOC_RISCV_LO12_S},
+  {"tprel_lo", BFD_RELOC_RISCV_TPREL_LO12_S},
+  {"pcrel_lo", BFD_RELOC_RISCV_PCREL_LO12_S},
   {0, 0}
 };
 
 static const struct percent_op_match percent_op_rtype[] =
 {
-  {"%tprel_add", BFD_RELOC_RISCV_TPREL_ADD},
+  {"tprel_add", BFD_RELOC_RISCV_TPREL_ADD},
   {0, 0}
 };
 
@@ -2146,14 +2217,16 @@ parse_relocation (char **str, bfd_reloc_code_real_type *reloc,
                  const struct percent_op_match *percent_op)
 {
   for ( ; percent_op->str; percent_op++)
-    if (strncasecmp (*str, percent_op->str, strlen (percent_op->str)) == 0)
+    if (strncasecmp (*str + 1, percent_op->str, strlen (percent_op->str)) == 0)
       {
-       int len = strlen (percent_op->str);
+       size_t len = 1 + strlen (percent_op->str);
 
-       if (!ISSPACE ((*str)[len]) && (*str)[len] != '(')
+       while (ISSPACE ((*str)[len]))
+         ++len;
+       if ((*str)[len] != '(')
          continue;
 
-       *str += strlen (percent_op->str);
+       *str += len;
        *reloc = percent_op->reloc;
 
        /* Check whether the output BFD supports this relocation.
@@ -2178,7 +2251,7 @@ my_getExpression (expressionS *ep, char *str)
   save_in = input_line_pointer;
   input_line_pointer = str;
   expression (ep);
-  expr_end = input_line_pointer;
+  expr_parse_end = input_line_pointer;
   input_line_pointer = save_in;
 }
 
@@ -2186,28 +2259,18 @@ my_getExpression (expressionS *ep, char *str)
    expression in *EP and the relocation, if any, in RELOC.
    Return the number of relocation operators used (0 or 1).
 
-   On exit, EXPR_END points to the first character after the expression.  */
+   On exit, EXPR_PARSE_END points to the first character after the
+   expression.  */
 
 static size_t
 my_getSmallExpression (expressionS *ep, bfd_reloc_code_real_type *reloc,
                       char *str, const struct percent_op_match *percent_op)
 {
   size_t reloc_index;
-  unsigned crux_depth, str_depth, regno;
+  unsigned crux_depth, str_depth;
+  bool orig_probing = probing_insn_operands;
   char *crux;
 
-  /* First, check for integer registers.  No callers can accept a reg, but
-     we need to avoid accidentally creating a useless undefined symbol below,
-     if this is an instruction pattern that can't match.  A glibc build fails
-     if this is removed.  */
-  if (reg_lookup (&str, RCLASS_GPR, &regno))
-    {
-      ep->X_op = O_register;
-      ep->X_add_number = regno;
-      expr_end = str;
-      return 0;
-    }
-
   /* Search for the start of the main expression.
 
      End the loop with CRUX pointing to the start of the main expression and
@@ -2230,8 +2293,25 @@ my_getSmallExpression (expressionS *ep, bfd_reloc_code_real_type *reloc,
         && reloc_index < 1
         && parse_relocation (&str, reloc, percent_op));
 
+  if (*str == '%')
+    {
+       /* expression() will choke on anything looking like an (unrecognized)
+         relocation specifier.  Don't even call it, avoiding multiple (and
+         perhaps redundant) error messages; our caller will issue one.  */
+       ep->X_op = O_illegal;
+       return 0;
+    }
+
+  /* Anything inside parentheses or subject to a relocation operator cannot
+     be a register and hence can be treated the same as operands to
+     directives (other than .insn).  */
+  if (str_depth || reloc_index)
+    probing_insn_operands = false;
+
   my_getExpression (ep, crux);
-  str = expr_end;
+  str = expr_parse_end;
+
+  probing_insn_operands = orig_probing;
 
   /* Match every open bracket.  */
   while (crux_depth > 0 && (*str == ')' || *str == ' ' || *str == '\t'))
@@ -2241,7 +2321,7 @@ my_getSmallExpression (expressionS *ep, bfd_reloc_code_real_type *reloc,
   if (crux_depth > 0)
     as_bad ("unclosed '('");
 
-  expr_end = str;
+  expr_parse_end = str;
 
   return reloc_index;
 }
@@ -2250,7 +2330,7 @@ my_getSmallExpression (expressionS *ep, bfd_reloc_code_real_type *reloc,
 
 static size_t
 my_getOpcodeExpression (expressionS *ep, bfd_reloc_code_real_type *reloc,
-                       char *str, const struct percent_op_match *percent_op)
+                       char *str)
 {
   const struct opcode_name_t *o = opcode_name_lookup (&str);
 
@@ -2261,11 +2341,12 @@ my_getOpcodeExpression (expressionS *ep, bfd_reloc_code_real_type *reloc,
       return 0;
     }
 
-  return my_getSmallExpression (ep, reloc, str, percent_op);
+  return my_getSmallExpression (ep, reloc, str, percent_op_null);
 }
 
 /* Parse string STR as a vsetvli operand.  Store the expression in *EP.
-   On exit, EXPR_END points to the first character after the expression.  */
+   On exit, EXPR_PARSE_END points to the first character after the
+   expression.  */
 
 static void
 my_getVsetvliExpression (expressionS *ep, char *str)
@@ -2315,12 +2396,12 @@ my_getVsetvliExpression (expressionS *ep, char *str)
                         | (vsew_value << OP_SH_VSEW)
                         | (vta_value << OP_SH_VTA)
                         | (vma_value << OP_SH_VMA);
-      expr_end = str;
+      expr_parse_end = str;
     }
   else
     {
       my_getExpression (ep, str);
-      str = expr_end;
+      str = expr_parse_end;
     }
 }
 
@@ -2417,6 +2498,13 @@ riscv_is_priv_insn (insn_t insn)
          || ((insn ^ MATCH_SFENCE_VM) & MASK_SFENCE_VM) == 0);
 }
 
+static symbolS *deferred_sym_rootP;
+static symbolS *deferred_sym_lastP;
+/* Since symbols can't easily be freed, try to recycle ones which weren't
+   committed.  */
+static symbolS *orphan_sym_rootP;
+static symbolS *orphan_sym_lastP;
+
 /* This routine assembles an instruction into its binary format.  As a
    side effect, it sets the global variable imm_reloc to the type of
    relocation to do if one of the operands is an address expression.  */
@@ -2452,6 +2540,8 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
 
   insn = (struct riscv_opcode *) str_hash_find (hash, str);
 
+  probing_insn_operands = true;
+
   asargStart = asarg;
   for ( ; insn && insn->name && strcmp (insn->name, str) == 0; insn++)
     {
@@ -2468,11 +2558,22 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
       /* Reset error message of the previous round.  */
       error.msg = _("illegal operands");
       error.missing_ext = NULL;
+
+      /* Purge deferred symbols from the previous round, if any.  */
+      while (deferred_sym_rootP)
+       {
+         symbolS *sym = deferred_sym_rootP;
+
+         symbol_remove (sym, &deferred_sym_rootP, &deferred_sym_lastP);
+         symbol_append (sym, orphan_sym_lastP, &orphan_sym_rootP,
+                        &orphan_sym_lastP);
+       }
+
       create_insn (ip, insn);
 
       imm_expr->X_op = O_absent;
       *imm_reloc = BFD_RELOC_UNUSED;
-      p = percent_op_itype;
+      p = percent_op_null;
 
       for (oparg = insn->args;; ++oparg)
        {
@@ -2522,9 +2623,22 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                }
              if (*asarg != '\0')
                break;
+
              /* Successful assembly.  */
              error.msg = NULL;
              insn_with_csr = false;
+
+             /* Commit deferred symbols, if any.  */
+             while (deferred_sym_rootP)
+               {
+                 symbolS *sym = deferred_sym_rootP;
+
+                 symbol_remove (sym, &deferred_sym_rootP,
+                                &deferred_sym_lastP);
+                 symbol_append (sym, symbol_lastP, &symbol_rootP,
+                                &symbol_lastP);
+                 symbol_table_insert (sym);
+               }
              goto out;
 
            case 'C': /* RVC */
@@ -2579,7 +2693,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                    break;
                  ip->insn_opcode |= ENCODE_CITYPE_IMM (imm_expr->X_add_number);
                rvc_imm_done:
-                 asarg = expr_end;
+                 asarg = expr_parse_end;
                  imm_expr->X_op = O_absent;
                  continue;
                case '5':
@@ -2761,7 +2875,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                          }
                        INSERT_OPERAND (CFUNCT6, *ip, imm_expr->X_add_number);
                        imm_expr->X_op = O_absent;
-                       asarg = expr_end;
+                       asarg = expr_parse_end;
                        continue;
 
                      case '4':
@@ -2776,7 +2890,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                          }
                        INSERT_OPERAND (CFUNCT4, *ip, imm_expr->X_add_number);
                        imm_expr->X_op = O_absent;
-                       asarg = expr_end;
+                       asarg = expr_parse_end;
                        continue;
 
                      case '3':
@@ -2791,7 +2905,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                          }
                        INSERT_OPERAND (CFUNCT3, *ip, imm_expr->X_add_number);
                        imm_expr->X_op = O_absent;
-                       asarg = expr_end;
+                       asarg = expr_parse_end;
                        continue;
 
                      case '2':
@@ -2806,7 +2920,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                          }
                        INSERT_OPERAND (CFUNCT2, *ip, imm_expr->X_add_number);
                        imm_expr->X_op = O_absent;
-                       asarg = expr_end;
+                       asarg = expr_parse_end;
                        continue;
 
                      default:
@@ -2898,7 +3012,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                  ip->insn_opcode
                    |= ENCODE_RVV_VB_IMM (imm_expr->X_add_number);
                  imm_expr->X_op = O_absent;
-                 asarg = expr_end;
+                 asarg = expr_parse_end;
                  continue;
 
                case 'c': /* vtypei for vsetvli */
@@ -2910,7 +3024,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                  ip->insn_opcode
                    |= ENCODE_RVV_VC_IMM (imm_expr->X_add_number);
                  imm_expr->X_op = O_absent;
-                 asarg = expr_end;
+                 asarg = expr_parse_end;
                  continue;
 
                case 'i': /* vector arith signed immediate */
@@ -2922,7 +3036,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                              "value must be -16...15"));
                  INSERT_OPERAND (VIMM, *ip, imm_expr->X_add_number);
                  imm_expr->X_op = O_absent;
-                 asarg = expr_end;
+                 asarg = expr_parse_end;
                  continue;
 
                case 'j': /* vector arith unsigned immediate */
@@ -2934,7 +3048,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                              "value must be 0...31"));
                  INSERT_OPERAND (VIMM, *ip, imm_expr->X_add_number);
                  imm_expr->X_op = O_absent;
-                 asarg = expr_end;
+                 asarg = expr_parse_end;
                  continue;
 
                case 'k': /* vector arith signed immediate, minus 1 */
@@ -2946,7 +3060,19 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                              "value must be -15...16"));
                  INSERT_OPERAND (VIMM, *ip, imm_expr->X_add_number - 1);
                  imm_expr->X_op = O_absent;
-                 asarg = expr_end;
+                 asarg = expr_parse_end;
+                 continue;
+
+               case 'l': /* 6-bit vector arith unsigned immediate */
+                 my_getExpression (imm_expr, asarg);
+                 check_absolute_expr (ip, imm_expr, FALSE);
+                 if (imm_expr->X_add_number < 0
+                     || imm_expr->X_add_number >= 64)
+                   as_bad (_("bad value for vector immediate field, "
+                             "value must be 0...63"));
+                 ip->insn_opcode |= ENCODE_RVV_VI_UIMM6 (imm_expr->X_add_number);
+                 imm_expr->X_op = O_absent;
+                 asarg = expr_parse_end;
                  continue;
 
                case 'm': /* optional vector mask */
@@ -3007,7 +3133,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                        imm_expr->X_add_number);
              INSERT_OPERAND (SHAMTW, *ip, imm_expr->X_add_number);
              imm_expr->X_op = O_absent;
-             asarg = expr_end;
+             asarg = expr_parse_end;
              continue;
 
            case '>': /* Shift amount, 0 - (XLEN-1).  */
@@ -3018,7 +3144,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                        imm_expr->X_add_number);
              INSERT_OPERAND (SHAMT, *ip, imm_expr->X_add_number);
              imm_expr->X_op = O_absent;
-             asarg = expr_end;
+             asarg = expr_parse_end;
              continue;
 
            case 'Z': /* CSRRxI immediate.  */
@@ -3029,7 +3155,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                        imm_expr->X_add_number);
              INSERT_OPERAND (RS1, *ip, imm_expr->X_add_number);
              imm_expr->X_op = O_absent;
-             asarg = expr_end;
+             asarg = expr_parse_end;
              continue;
 
            case 'E': /* Control register.  */
@@ -3046,7 +3172,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                            imm_expr->X_add_number);
                  INSERT_OPERAND (CSR, *ip, imm_expr->X_add_number);
                  imm_expr->X_op = O_absent;
-                 asarg = expr_end;
+                 asarg = expr_parse_end;
                }
              continue;
 
@@ -3143,7 +3269,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                  && imm_expr->X_op != O_constant)
                break;
              normalize_constant_expr (imm_expr);
-             asarg = expr_end;
+             asarg = expr_parse_end;
              continue;
 
            case 'A':
@@ -3153,7 +3279,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
              if (imm_expr->X_op != O_symbol)
                break;
              *imm_reloc = BFD_RELOC_32;
-             asarg = expr_end;
+             asarg = expr_parse_end;
              continue;
 
            case 'B':
@@ -3164,7 +3290,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                break;
              if (imm_expr->X_op == O_symbol)
                *imm_reloc = BFD_RELOC_32;
-             asarg = expr_end;
+             asarg = expr_parse_end;
              continue;
 
            case 'j': /* Sign-extended immediate.  */
@@ -3186,7 +3312,6 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
              p = percent_op_rtype;
              goto alu_op;
            case '0': /* AMO displacement, which must be zero.  */
-             p = percent_op_null;
            load_store:
              if (riscv_handle_implicit_zero_offset (imm_expr, asarg))
                continue;
@@ -3204,14 +3329,14 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                      || imm_expr->X_add_number < -(signed)RISCV_IMM_REACH/2)
                    break;
                }
-             asarg = expr_end;
+             asarg = expr_parse_end;
              continue;
 
            case 'p': /* PC-relative offset.  */
            branch:
              *imm_reloc = BFD_RELOC_12_PCREL;
              my_getExpression (imm_expr, asarg);
-             asarg = expr_end;
+             asarg = expr_parse_end;
              continue;
 
            case 'u': /* Upper 20 bits.  */
@@ -3228,19 +3353,19 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                  *imm_reloc = BFD_RELOC_RISCV_HI20;
                  imm_expr->X_add_number <<= RISCV_IMM_BITS;
                }
-             asarg = expr_end;
+             asarg = expr_parse_end;
              continue;
 
            case 'a': /* 20-bit PC-relative offset.  */
            jump:
              my_getExpression (imm_expr, asarg);
-             asarg = expr_end;
+             asarg = expr_parse_end;
              *imm_reloc = BFD_RELOC_RISCV_JMP;
              continue;
 
            case 'c':
              my_getExpression (imm_expr, asarg);
-             asarg = expr_end;
+             asarg = expr_parse_end;
              if (strcmp (asarg, "@plt") == 0)
                asarg += 4;
              *imm_reloc = BFD_RELOC_RISCV_CALL_PLT;
@@ -3250,7 +3375,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
              switch (*++oparg)
                {
                case '4':
-                 if (my_getOpcodeExpression (imm_expr, imm_reloc, asarg, p)
+                 if (my_getOpcodeExpression (imm_expr, imm_reloc, asarg)
                      || imm_expr->X_op != O_constant
                      || imm_expr->X_add_number < 0
                      || imm_expr->X_add_number >= 128
@@ -3263,11 +3388,11 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                    }
                  INSERT_OPERAND (OP, *ip, imm_expr->X_add_number);
                  imm_expr->X_op = O_absent;
-                 asarg = expr_end;
+                 asarg = expr_parse_end;
                  continue;
 
                case '2':
-                 if (my_getOpcodeExpression (imm_expr, imm_reloc, asarg, p)
+                 if (my_getOpcodeExpression (imm_expr, imm_reloc, asarg)
                      || imm_expr->X_op != O_constant
                      || imm_expr->X_add_number < 0
                      || imm_expr->X_add_number >= 3)
@@ -3278,7 +3403,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                    }
                  INSERT_OPERAND (OP2, *ip, imm_expr->X_add_number);
                  imm_expr->X_op = O_absent;
-                 asarg = expr_end;
+                 asarg = expr_parse_end;
                  continue;
 
                default:
@@ -3301,7 +3426,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                    }
                  INSERT_OPERAND (FUNCT7, *ip, imm_expr->X_add_number);
                  imm_expr->X_op = O_absent;
-                 asarg = expr_end;
+                 asarg = expr_parse_end;
                  continue;
 
                case '3':
@@ -3316,7 +3441,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                    }
                  INSERT_OPERAND (FUNCT3, *ip, imm_expr->X_add_number);
                  imm_expr->X_op = O_absent;
-                 asarg = expr_end;
+                 asarg = expr_parse_end;
                  continue;
 
                case '2':
@@ -3331,7 +3456,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                    }
                  INSERT_OPERAND (FUNCT2, *ip, imm_expr->X_add_number);
                  imm_expr->X_op = O_absent;
-                 asarg = expr_end;
+                 asarg = expr_parse_end;
                  continue;
 
                default:
@@ -3347,7 +3472,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                       (unsigned long)imm_expr->X_add_number);
              INSERT_OPERAND(BS, *ip, imm_expr->X_add_number);
              imm_expr->X_op = O_absent;
-             asarg = expr_end;
+             asarg = expr_parse_end;
              continue;
 
            case 'Y': /* rnum immediate */
@@ -3358,7 +3483,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                       (unsigned long)imm_expr->X_add_number);
              INSERT_OPERAND(RNUM, *ip, imm_expr->X_add_number);
              imm_expr->X_op = O_absent;
-             asarg = expr_end;
+             asarg = expr_parse_end;
              continue;
 
            case 'z':
@@ -3366,78 +3491,170 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
                  || imm_expr->X_op != O_constant
                  || imm_expr->X_add_number != 0)
                break;
-             asarg = expr_end;
+             asarg = expr_parse_end;
              imm_expr->X_op = O_absent;
              continue;
 
-           case 'f': /* Prefetch offset, pseudo S-type but lower 5-bits zero.  */
-             if (riscv_handle_implicit_zero_offset (imm_expr, asarg))
-               continue;
-             my_getExpression (imm_expr, asarg);
-             check_absolute_expr (ip, imm_expr, false);
-             if (((unsigned) (imm_expr->X_add_number) & 0x1fU)
-                 || imm_expr->X_add_number >= (signed) RISCV_IMM_REACH / 2
-                 || imm_expr->X_add_number < -(signed) RISCV_IMM_REACH / 2)
-               as_bad (_("improper prefetch offset (%ld)"),
-                       (long) imm_expr->X_add_number);
-             ip->insn_opcode |=
-               ENCODE_STYPE_IMM ((unsigned) (imm_expr->X_add_number) &
-                                 ~ 0x1fU);
-             imm_expr->X_op = O_absent;
-             asarg = expr_end;
-             continue;
-
-           case 'X': /* Integer immediate.  */
-             {
-               size_t n;
-               size_t s;
-               bool sign;
-
-               switch (*++oparg)
-                 {
-                   case 'l': /* Literal.  */
-                     n = strcspn (++oparg, ",");
-                     if (strncmp (oparg, asarg, n))
-                       as_bad (_("unexpected literal (%s)"), asarg);
-                     oparg += n - 1;
-                     asarg += n;
-                     continue;
-                   case 's': /* 'XsN@S' ... N-bit signed immediate at bit S.  */
-                     sign = true;
-                     goto parse_imm;
-                   case 'u': /* 'XuN@S' ... N-bit unsigned immediate at bit S.  */
-                     sign = false;
-                     goto parse_imm;
-                   parse_imm:
-                     n = strtol (oparg + 1, (char **)&oparg, 10);
-                     if (*oparg != '@')
-                       goto unknown_riscv_ip_operand;
-                     s = strtol (oparg + 1, (char **)&oparg, 10);
-                     oparg--;
-
+           case 'W': /* Various operands for standard z extensions.  */
+             switch (*++oparg)
+               {
+               case 'i':
+                 switch (*++oparg)
+                   {
+                   case 'f':
+                     /* Prefetch offset for 'Zicbop' extension.
+                        pseudo S-type but lower 5-bits zero.  */
+                     if (riscv_handle_implicit_zero_offset (imm_expr, asarg))
+                       continue;
                      my_getExpression (imm_expr, asarg);
                      check_absolute_expr (ip, imm_expr, false);
-                     if (!sign)
-                       {
-                         if (!VALIDATE_U_IMM (imm_expr->X_add_number, n))
-                           as_bad (_("improper immediate value (%"PRIu64")"),
-                                   imm_expr->X_add_number);
-                       }
-                     else
+                     if (((unsigned) (imm_expr->X_add_number) & 0x1fU)
+                         || imm_expr->X_add_number >= RISCV_IMM_REACH / 2
+                         || imm_expr->X_add_number < -RISCV_IMM_REACH / 2)
+                       as_bad (_ ("improper prefetch offset (%ld)"),
+                               (long) imm_expr->X_add_number);
+                     ip->insn_opcode |= ENCODE_STYPE_IMM (
+                         (unsigned) (imm_expr->X_add_number) & ~0x1fU);
+                     imm_expr->X_op = O_absent;
+                     asarg = expr_parse_end;
+                     continue;
+                   default:
+                     goto unknown_riscv_ip_operand;
+                   }
+                 break;
+
+               case 'f':
+                 switch (*++oparg)
+                   {
+                   case 'v':
+                     /* FLI.[HSDQ] value field for 'Zfa' extension.  */
+                     if (!arg_lookup (&asarg, riscv_fli_symval,
+                                      ARRAY_SIZE (riscv_fli_symval), &regno))
                        {
-                         if (!VALIDATE_S_IMM (imm_expr->X_add_number, n))
-                           as_bad (_("improper immediate value (%"PRIi64")"),
-                                   imm_expr->X_add_number);
+                         /* 0.0 is not a valid entry in riscv_fli_numval.  */
+                         errno = 0;
+                         float f = strtof (asarg, &asarg);
+                         if (errno != 0 || f == 0.0
+                             || !flt_lookup (f, riscv_fli_numval,
+                                            ARRAY_SIZE(riscv_fli_numval),
+                                            &regno))
+                           {
+                             as_bad (_("bad fli constant operand, "
+                                       "supported constants must be in "
+                                       "decimal or hexadecimal floating-point "
+                                       "literal form"));
+                             break;
+                           }
                        }
-                     INSERT_IMM (n, s, *ip, imm_expr->X_add_number);
+                     INSERT_OPERAND (RS1, *ip, regno);
+                     continue;
+                   default:
+                     goto unknown_riscv_ip_operand;
+                   }
+                 break;
+
+               case 'c':
+                 switch (*++oparg)
+                   {
+                   case 'h': /* Immediate field for c.lh/c.lhu/c.sh.  */
+                     /* Handle cases, such as c.sh rs2', (rs1').  */
+                     if (riscv_handle_implicit_zero_offset (imm_expr, asarg))
+                       continue;
+                     if (my_getSmallExpression (imm_expr, imm_reloc, asarg, p)
+                         || imm_expr->X_op != O_constant
+                         || !VALID_ZCB_HALFWORD_UIMM ((valueT) imm_expr->X_add_number))
+                       break;
+                     ip->insn_opcode |= ENCODE_ZCB_HALFWORD_UIMM (imm_expr->X_add_number);
+                     goto rvc_imm_done;
+                   case 'b': /* Immediate field for c.lbu/c.sb.  */
+                     /* Handle cases, such as c.lbu rd', (rs1').  */
+                     if (riscv_handle_implicit_zero_offset (imm_expr, asarg))
+                       continue;
+                     if (my_getSmallExpression (imm_expr, imm_reloc, asarg, p)
+                         || imm_expr->X_op != O_constant
+                         || !VALID_ZCB_BYTE_UIMM ((valueT) imm_expr->X_add_number))
+                       break;
+                     ip->insn_opcode |= ENCODE_ZCB_BYTE_UIMM (imm_expr->X_add_number);
+                     goto rvc_imm_done;
+                   case 'f': /* Operand for matching immediate 255.  */
+                     if (my_getSmallExpression (imm_expr, imm_reloc, asarg, p)
+                         || imm_expr->X_op != O_constant
+                         || imm_expr->X_add_number != 255)
+                       break;
+                     /* This operand is used for matching immediate 255, and
+                        we do not write anything to encoding by this operand.  */
+                     asarg = expr_parse_end;
                      imm_expr->X_op = O_absent;
-                     asarg = expr_end;
                      continue;
                    default:
                      goto unknown_riscv_ip_operand;
+                   }
+                 break;
+
+               default:
+                 goto unknown_riscv_ip_operand;
+               }
+             break;
+
+           case 'X': /* Vendor-specific operands.  */
+             switch (*++oparg)
+               {
+               case 't': /* Vendor-specific (T-head) operands.  */
+                 {
+                   size_t n;
+                   size_t s;
+                   bool sign;
+                   switch (*++oparg)
+                     {
+                     case 'l': /* Integer immediate, literal.  */
+                       n = strcspn (++oparg, ",");
+                       if (strncmp (oparg, asarg, n))
+                         as_bad (_("unexpected literal (%s)"), asarg);
+                       oparg += n - 1;
+                       asarg += n;
+                       continue;
+                     case 's': /* Integer immediate, 'XsN@S' ... N-bit signed immediate at bit S.  */
+                       sign = true;
+                       goto parse_imm;
+                     case 'u': /* Integer immediate, 'XuN@S' ... N-bit unsigned immediate at bit S.  */
+                       sign = false;
+                       goto parse_imm;
+                     parse_imm:
+                       n = strtol (oparg + 1, (char **)&oparg, 10);
+                       if (*oparg != '@')
+                         goto unknown_riscv_ip_operand;
+                       s = strtol (oparg + 1, (char **)&oparg, 10);
+                       oparg--;
+
+                       my_getExpression (imm_expr, asarg);
+                       check_absolute_expr (ip, imm_expr, false);
+                       if (!sign)
+                         {
+                           if (!VALIDATE_U_IMM (imm_expr->X_add_number, n))
+                             as_bad (_("improper immediate value (%"PRIu64")"),
+                                     imm_expr->X_add_number);
+                         }
+                       else
+                         {
+                           if (!VALIDATE_S_IMM (imm_expr->X_add_number, n))
+                             as_bad (_("improper immediate value (%"PRIi64")"),
+                                     imm_expr->X_add_number);
+                         }
+                       INSERT_IMM (n, s, *ip, imm_expr->X_add_number);
+                       imm_expr->X_op = O_absent;
+                       asarg = expr_parse_end;
+                       continue;
+                     default:
+                       goto unknown_riscv_ip_operand;
+                     }
                  }
-             }
+                 break;
+
+               default:
+                 goto unknown_riscv_ip_operand;
+               }
              break;
+
            default:
            unknown_riscv_ip_operand:
              as_fatal (_("internal: unknown argument type `%s'"),
@@ -3454,6 +3671,8 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr,
   if (save_c)
     *(asargStart  - 1) = save_c;
 
+  probing_insn_operands = false;
+
   return error;
 }
 
@@ -3480,7 +3699,9 @@ riscv_ip_hardcode (char *str,
          values[num++] = (insn_t) imm_expr->X_add_number;
          break;
        case O_big:
-         values[num++] = generic_bignum[0];
+         /* Extract lower 32-bits of a big number.
+            Assume that generic_bignum_to_int32 work on such number.  */
+         values[num++] = (insn_t) generic_bignum_to_int32 ();
          break;
        default:
          /* The first value isn't constant, so it should be
@@ -3497,7 +3718,7 @@ riscv_ip_hardcode (char *str,
   if (*input_line_pointer != '\0')
     return _("unrecognized values");
 
-  insn = XNEW (struct riscv_opcode);
+  insn = XCNEW (struct riscv_opcode);
   insn->match = values[num - 1];
   create_insn (ip, insn);
   unsigned int bytes = riscv_insn_length (insn->match);
@@ -3507,12 +3728,25 @@ riscv_ip_hardcode (char *str,
 
   if (imm_expr->X_op == O_big)
     {
-      if (bytes != imm_expr->X_add_number * CHARS_PER_LITTLENUM)
+      unsigned int llen = 0;
+      for (LITTLENUM_TYPE lval = generic_bignum[imm_expr->X_add_number - 1];
+          lval != 0; llen++)
+       lval >>= BITS_PER_CHAR;
+      unsigned int repr_bytes
+         = (imm_expr->X_add_number - 1) * CHARS_PER_LITTLENUM + llen;
+      if (bytes < repr_bytes)
        return _("value conflicts with instruction length");
-      char *f = frag_more (bytes);
-      for (num = 0; num < imm_expr->X_add_number; ++num)
-       number_to_chars_littleendian (f + num * CHARS_PER_LITTLENUM,
-                                     generic_bignum[num], CHARS_PER_LITTLENUM);
+      for (num = 0; num < imm_expr->X_add_number - 1; ++num)
+       number_to_chars_littleendian (
+           ip->insn_long_opcode + num * CHARS_PER_LITTLENUM,
+           generic_bignum[num],
+           CHARS_PER_LITTLENUM);
+      if (llen != 0)
+       number_to_chars_littleendian (
+           ip->insn_long_opcode + num * CHARS_PER_LITTLENUM,
+           generic_bignum[num],
+           llen);
+      memset(ip->insn_long_opcode + repr_bytes, 0, bytes - repr_bytes);
       return NULL;
     }
 
@@ -3540,7 +3774,7 @@ md_assemble (char *str)
        return;
     }
 
-  riscv_mapping_state (MAP_INSN, 0, 0/* frag_align_code */);
+  riscv_mapping_state (MAP_INSN, 0, false/* fr_align_code */);
 
   const struct riscv_ip_error error = riscv_ip (str, &insn, &imm_expr,
                                                &imm_reloc, op_hash);
@@ -3564,7 +3798,7 @@ md_assemble (char *str)
 const char *
 md_atof (int type, char *litP, int *sizeP)
 {
-  return ieee_md_atof (type, litP, sizeP, TARGET_BYTES_BIG_ENDIAN);
+  return ieee_md_atof (type, litP, sizeP, target_big_endian);
 }
 
 void
@@ -3735,6 +3969,53 @@ riscv_after_parse_args (void)
     flag_dwarf_cie_version = 3;
 }
 
+bool riscv_parse_name (const char *name, struct expressionS *ep,
+                      enum expr_mode mode)
+{
+  unsigned int regno;
+  symbolS *sym;
+
+  if (!probing_insn_operands)
+    return false;
+
+  gas_assert (mode == expr_normal);
+
+  regno = reg_lookup_internal (name, RCLASS_GPR);
+  if (regno == (unsigned int)-1)
+    return false;
+
+  if (symbol_find (name) != NULL)
+    return false;
+
+  /* Create a symbol without adding it to the symbol table yet.
+     Insertion will happen only once we commit to using the insn
+     we're probing operands for.  */
+  for (sym = deferred_sym_rootP; sym; sym = symbol_next (sym))
+    if (strcmp (name, S_GET_NAME (sym)) == 0)
+      break;
+  if (!sym)
+    {
+      for (sym = orphan_sym_rootP; sym; sym = symbol_next (sym))
+       if (strcmp (name, S_GET_NAME (sym)) == 0)
+         {
+           symbol_remove (sym, &orphan_sym_rootP, &orphan_sym_lastP);
+           break;
+         }
+      if (!sym)
+       sym = symbol_create (name, undefined_section,
+                            &zero_address_frag, 0);
+
+      symbol_append (sym, deferred_sym_lastP, &deferred_sym_rootP,
+                    &deferred_sym_lastP);
+    }
+
+  ep->X_op = O_symbol;
+  ep->X_add_symbol = sym;
+  ep->X_add_number = 0;
+
+  return true;
+}
+
 long
 md_pcrel_from (fixS *fixP)
 {
@@ -3778,6 +4059,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:
@@ -4078,7 +4362,8 @@ s_riscv_option (int x ATTRIBUTE_UNUSED)
       riscv_reset_subsets_list_arch_str ();
 
       riscv_set_rvc (false);
-      if (riscv_subset_supports (&riscv_rps_as, "c"))
+      if (riscv_subset_supports (&riscv_rps_as, "c")
+         || riscv_subset_supports (&riscv_rps_as, "zca"))
        riscv_set_rvc (true);
 
       if (riscv_subset_supports (&riscv_rps_as, "ztso"))
@@ -4114,7 +4399,7 @@ s_riscv_option (int x ATTRIBUTE_UNUSED)
     }
   else
     {
-      as_warn (_("unrecognized .option directive: %s\n"), name);
+      as_warn (_("unrecognized .option directive: %s"), name);
     }
   *input_line_pointer = ch;
   demand_empty_rest_of_line ();
@@ -4219,7 +4504,7 @@ riscv_frag_align_code (int n)
   fix_new_exp (frag_now, nops - frag_now->fr_literal, 0,
               &ex, false, BFD_RELOC_RISCV_ALIGN);
 
-  riscv_mapping_state (MAP_INSN, worst_case_bytes, 1/* frag_align_code */);
+  riscv_mapping_state (MAP_INSN, worst_case_bytes, true/* fr_align_code */);
 
   /* We need to start a new frag after the alignment which may be removed by
      the linker, to prevent the assembler from computing static offsets.
@@ -4293,10 +4578,10 @@ riscv_init_frag (fragS * fragP, int max_chars)
     case rs_fill:
     case rs_align:
     case rs_align_test:
-      riscv_mapping_state (MAP_DATA, max_chars, 0/* frag_align_code */);
+      riscv_mapping_state (MAP_DATA, max_chars, false/* fr_align_code */);
       break;
     case rs_align_code:
-      riscv_mapping_state (MAP_INSN, max_chars, 1/* frag_align_code */);
+      riscv_mapping_state (MAP_INSN, max_chars, true/* fr_align_code */);
       break;
     default:
       break;
@@ -4542,8 +4827,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;
@@ -4570,7 +4858,7 @@ s_riscv_insn (int x ATTRIBUTE_UNUSED)
   save_c = *input_line_pointer;
   *input_line_pointer = '\0';
 
-  riscv_mapping_state (MAP_INSN, 0, 0/* frag_align_code */);
+  riscv_mapping_state (MAP_INSN, 0, false/* fr_align_code */);
 
   struct riscv_ip_error error = riscv_ip (str, &insn, &imm_expr,
                                &imm_reloc, insn_type_hash);
@@ -4589,7 +4877,7 @@ s_riscv_insn (int x ATTRIBUTE_UNUSED)
       else
        as_bad ("%s `%s'", error.msg, error.statement);
     }
-  else if (imm_expr.X_op != O_big)
+  else
     {
       gas_assert (insn.insn_mo->pinfo != INSN_MACRO);
       append_insn (&insn, &imm_expr, imm_reloc);
@@ -4614,7 +4902,9 @@ riscv_write_out_attrs (void)
 
   /* Re-write architecture elf attribute.  */
   arch_str = riscv_rps_as.subset_list->arch_str;
-  bfd_elf_add_proc_attr_string (stdoutput, Tag_RISCV_arch, arch_str);
+  if (!bfd_elf_add_proc_attr_string (stdoutput, Tag_RISCV_arch, arch_str))
+    as_fatal (_("error adding attribute: %s"),
+             bfd_errmsg (bfd_get_error ()));
 
   /* For the file without any instruction, we don't set the default_priv_spec
      according to the privileged elf attributes since the md_assemble isn't
@@ -4649,9 +4939,14 @@ riscv_write_out_attrs (void)
   versions[i] = number;
 
   /* Re-write privileged elf attributes.  */
-  bfd_elf_add_proc_attr_int (stdoutput, Tag_RISCV_priv_spec, versions[0]);
-  bfd_elf_add_proc_attr_int (stdoutput, Tag_RISCV_priv_spec_minor, versions[1]);
-  bfd_elf_add_proc_attr_int (stdoutput, Tag_RISCV_priv_spec_revision, versions[2]);
+  if (!bfd_elf_add_proc_attr_int (stdoutput, Tag_RISCV_priv_spec,
+                                 versions[0])
+      || !bfd_elf_add_proc_attr_int (stdoutput, Tag_RISCV_priv_spec_minor,
+                                    versions[1])
+      || !bfd_elf_add_proc_attr_int (stdoutput, Tag_RISCV_priv_spec_revision,
+                                    versions[2]))
+    as_fatal (_("error adding attribute: %s"),
+             bfd_errmsg (bfd_get_error ()));
 }
 
 /* Add the default contents for the .riscv.attributes section.  */
@@ -4663,12 +4958,59 @@ riscv_set_public_attributes (void)
     riscv_write_out_attrs ();
 }
 
+/* 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.
+        Emit the SET relocation first in riscv.  */
+      exp_dup->X_add_symbol = exp->X_add_symbol;
+      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);
+      free ((void *) exp_dup);
+    }
+}
+
 /* Called after all assembly has been done.  */
 
 void
 riscv_md_finish (void)
 {
   riscv_set_public_attributes ();
+  if (riscv_opts.relax)
+    bfd_map_over_sections (stdoutput, riscv_insert_uleb128_fixes, NULL);
 }
 
 /* Adjust the symbol table.  */