diff --git a/llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV.def b/llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV.def
--- a/llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV.def
+++ b/llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV.def
@@ -55,3 +55,5 @@
 ELF_RELOC(R_RISCV_32_PCREL,          57)
 ELF_RELOC(R_RISCV_IRELATIVE,         58)
 ELF_RELOC(R_RISCV_PLT32,             59)
+ELF_RELOC(R_RISCV_SET_ULEB128,       60)
+ELF_RELOC(R_RISCV_SUB_ULEB128,       61)
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
@@ -94,6 +94,9 @@
 
       {"fixup_riscv_set_6b", 2, 6, 0},
       {"fixup_riscv_sub_6b", 2, 6, 0},
+
+      {"fixup_riscv_set_uleb128", 0, 0, 0},
+      {"fixup_riscv_sub_uleb128", 0, 0, 0},
   };
   static_assert((std::size(Infos)) == RISCV::NumTargetFixupKinds,
                 "Not all fixup kinds added to Infos array");
@@ -411,6 +414,8 @@
   case RISCV::fixup_riscv_sub_32:
   case RISCV::fixup_riscv_add_64:
   case RISCV::fixup_riscv_sub_64:
+  case RISCV::fixup_riscv_set_uleb128:
+  case RISCV::fixup_riscv_sub_uleb128:
   case FK_Data_1:
   case FK_Data_2:
   case FK_Data_4:
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp
@@ -169,6 +169,10 @@
     return ELF::R_RISCV_ADD64;
   case RISCV::fixup_riscv_sub_64:
     return ELF::R_RISCV_SUB64;
+  case RISCV::fixup_riscv_set_uleb128:
+    return ELF::R_RISCV_SET_ULEB128;
+  case RISCV::fixup_riscv_sub_uleb128:
+    return ELF::R_RISCV_SUB_ULEB128;
   }
 }
 
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.h
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.h
@@ -26,6 +26,7 @@
       : MCELFStreamer(C, std::move(MAB), std::move(MOW), std::move(MCE)) {}
 
   void emitValueImpl(const MCExpr *Value, unsigned Size, SMLoc Loc) override;
+  void emitULEB128Value(const MCExpr *Value) override;
 };
 
 namespace llvm {
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.cpp
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.cpp
@@ -23,11 +23,16 @@
 #include "llvm/MC/MCSectionELF.h"
 #include "llvm/MC/MCSubtargetInfo.h"
 #include "llvm/MC/MCValue.h"
+#include "llvm/Support/CommandLine.h"
 #include "llvm/Support/LEB128.h"
 #include "llvm/Support/RISCVAttributes.h"
 
 using namespace llvm;
 
+static cl::opt<bool>
+    EnableULEB128Reloc("riscv-enable-uleb128", cl::Hidden,
+                       cl::desc("Enable relocation emission for ULEB128."));
+
 // This part is for ELF object output.
 RISCVTargetELFStreamer::RISCVTargetELFStreamer(MCStreamer &S,
                                                const MCSubtargetInfo &STI)
@@ -200,6 +205,47 @@
   DF->getContents().resize(DF->getContents().size() + Size, 0);
 }
 
+void RISCVELFStreamer::emitULEB128Value(const MCExpr *Value) {
+  const MCExpr *A, *B;
+  if (!EnableULEB128Reloc || !requiresFixups(getContext(), Value, A, B))
+    return MCELFStreamer::emitULEB128Value(Value);
+
+  SMLoc Loc = Value->getLoc();
+
+  int64_t IntValue;
+  unsigned PadTo = 0;
+  if (!Value->evaluateAsAbsolute(IntValue, getAssemblerPtr())) {
+    const FeatureBitset &Features =
+        getContext().getSubtargetInfo()->getFeatureBits();
+    unsigned XLen = Features[RISCV::Feature64Bit] ? 64 : 32;
+
+    // Emit a placeholder to make sure we'll emit enough space could be filled
+    // by linker later.
+    IntValue = 0;
+    // Each byte in ULEB128 format can encoding at most 7 bits.
+    PadTo = divideCeil(XLen, 7);
+  }
+
+  if (IntValue < 0)
+    report_fatal_error("Can't encoding negative value in uleb128 format.");
+
+  MCDataFragment *DF = getOrCreateDataFragment();
+  flushPendingLabels(DF, DF->getContents().size());
+  MCDwarfLineEntry::make(this, getCurrentSectionOnly());
+
+  DF->getFixups().push_back(
+      MCFixup::create(DF->getContents().size(), A,
+                      MCFixupKind(RISCV::fixup_riscv_set_uleb128), Loc));
+  DF->getFixups().push_back(
+      MCFixup::create(DF->getContents().size(), B,
+                      MCFixupKind(RISCV::fixup_riscv_sub_uleb128), Loc));
+
+  SmallString<128> Tmp;
+  raw_svector_ostream OSE(Tmp);
+  encodeULEB128(IntValue, OSE, PadTo);
+  emitBytes(OSE.str());
+}
+
 namespace llvm {
 MCELFStreamer *createRISCVELFStreamer(MCContext &C,
                                       std::unique_ptr<MCAsmBackend> MAB,
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h
@@ -106,6 +106,10 @@
   // 6-bit fixup corresponding to R_RISCV_SUB6 for local label assignment in
   // DWARF CFA.
   fixup_riscv_sub_6b,
+  // fixup corresponding to R_RISCV_SET_ULEB128 for local label assignment.
+  fixup_riscv_set_uleb128,
+  // fixup corresponding to R_RISCV_SUB_ULEB128 for local label assignment.
+  fixup_riscv_sub_uleb128,
 
   // Used as a sentinel, must be the last
   fixup_riscv_invalid,
diff --git a/llvm/test/MC/RISCV/fixups-expr-uleb128.s b/llvm/test/MC/RISCV/fixups-expr-uleb128.s
new file mode 100644
--- /dev/null
+++ b/llvm/test/MC/RISCV/fixups-expr-uleb128.s
@@ -0,0 +1,93 @@
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax %s \
+# RUN:     | llvm-readobj -r - \
+# RUN:     | FileCheck -check-prefixes CHECK,NO-EMIT-RELOC %s
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=-relax %s \
+# RUN:     | llvm-readobj -r - \
+# RUN:     | FileCheck -check-prefixes CHECK,NO-EMIT-RELOC %s
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax \
+# RUN:         %s -riscv-enable-uleb128 \
+# RUN:     | llvm-readobj -r - \
+# RUN:     | FileCheck -check-prefixes CHECK,EMIT-RELOC,EMIT-RELOC-32 %s
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=-relax \
+# RUN:         %s -riscv-enable-uleb128 \
+# RUN:     | llvm-readobj -r - \
+# RUN:     | FileCheck -check-prefixes CHECK,EMIT-RELOC,EMIT-RELOC-32 %s
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax \
+# RUN:         %s -riscv-enable-uleb128=false \
+# RUN:     | llvm-readobj -r - \
+# RUN:     | FileCheck -check-prefixes CHECK,NO-EMIT-RELOC %s
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=-relax \
+# RUN:         %s -riscv-enable-uleb128=false \
+# RUN:     | llvm-readobj -r - \
+# RUN:     | FileCheck -check-prefixes CHECK,NO-EMIT-RELOC %s
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax %s \
+# RUN:     | llvm-readobj -r - \
+# RUN:     | FileCheck -check-prefixes CHECK,NO-EMIT-RELOC %s
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=-relax %s \
+# RUN:     | llvm-readobj -r - \
+# RUN:     | FileCheck -check-prefixes CHECK,NO-EMIT-RELOC %s
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax \
+# RUN:         %s -riscv-enable-uleb128 \
+# RUN:     | llvm-readobj -r - \
+# RUN:     | FileCheck -check-prefixes CHECK,EMIT-RELOC,EMIT-RELOC-64 %s
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=-relax \
+# RUN:         %s -riscv-enable-uleb128 \
+# RUN:     | llvm-readobj -r - \
+# RUN:     | FileCheck -check-prefixes CHECK,EMIT-RELOC,EMIT-RELOC-64 %s
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax \
+# RUN:         %s -riscv-enable-uleb128=false \
+# RUN:     | llvm-readobj -r - \
+# RUN:     | FileCheck -check-prefixes CHECK,NO-EMIT-RELOC %s
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=-relax \
+# RUN:         %s -riscv-enable-uleb128=false \
+# RUN:     | llvm-readobj -r - \
+# RUN:     | FileCheck -check-prefixes CHECK,NO-EMIT-RELOC %s
+
+
+# Check that subtraction expressions are emitted as two relocations always.
+
+.text
+.globl G1
+.globl G2
+.L1:
+G1:
+addi a0, a0, 0
+.L2:
+G2:
+
+.data
+.uleb128 G3-G1
+.uleb128 .L2-.L1
+.uleb128 G2-G1
+
+# NO-EMIT-RELOC-NOT: R_RISCV_SET_ULEB128
+# NO-EMIT-RELOC-NOT: R_RISCV_SUB_ULEB128
+
+# CHECK: Relocations [
+
+# EMIT-RELOC: Section (4) .rela.data {
+# EMIT-RELOC-32:      0x0 R_RISCV_SET_ULEB128 G3 0x0
+# EMIT-RELOC-32-NEXT: 0x0 R_RISCV_SUB_ULEB128 G1 0x0
+# EMIT-RELOC-32-NEXT: 0x5 R_RISCV_SET_ULEB128 .L2 0x0
+# EMIT-RELOC-32-NEXT: 0x5 R_RISCV_SUB_ULEB128 .L1 0x0
+# EMIT-RELOC-32-NEXT: 0x6 R_RISCV_SET_ULEB128 G2 0x0
+# EMIT-RELOC-32-NEXT: 0x6 R_RISCV_SUB_ULEB128 G1 0x0
+
+# EMIT-RELOC-64:      0x0 R_RISCV_SET_ULEB128 G3 0x0
+# EMIT-RELOC-64-NEXT: 0x0 R_RISCV_SUB_ULEB128 G1 0x0
+# EMIT-RELOC-64-NEXT: 0xA R_RISCV_SET_ULEB128 .L2 0x0
+# EMIT-RELOC-64-NEXT: 0xA R_RISCV_SUB_ULEB128 .L1 0x0
+# EMIT-RELOC-64-NEXT: 0xB R_RISCV_SET_ULEB128 G2 0x0
+# EMIT-RELOC-64-NEXT: 0xB R_RISCV_SUB_ULEB128 G1 0x0
+# EMIT-RELOC: }
+# CHECK: ]
+
+.text
+.globl G3
+G3:
+nop