[lld] [llvm] [LLD][RISCV] Use ISA mapping symbols for per-region RVC relaxation (PR #201310)
Kito Cheng via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 3 03:37:11 PDT 2026
https://github.com/kito-cheng updated https://github.com/llvm/llvm-project/pull/201310
>From 077f1be0b8da0d3df5f5dc19917a46e470512bac Mon Sep 17 00:00:00 2001
From: Kito Cheng <kito.cheng at sifive.com>
Date: Mon, 20 Apr 2026 01:52:36 -0700
Subject: [PATCH 1/2] [RISCV][MC] Reference ISA mapping symbols in
R_RISCV_RELAX relocations
Build on the ISA mapping symbols emitted by the previous patch to let
the linker determine per-region ISA capabilities during relaxation.
Before any ISA-changing directive R_RISCV_RELAX continues to use a null
symbol, preserving backward compatibility with existing object files.
Deduplication (introduced in the previous patch) ensures that redundant
option directives do not create extra symbols or change RELAX targets.
[1] https://github.com/riscv/riscv-elf-psabi-doc/pull/393
Assisted-by: Opus 4.6
---
lld/test/ELF/riscv-relax-align.s | 2 +-
lld/test/ELF/riscv-relax-emit-relocs.s | 12 +-
lld/test/ELF/riscv-relocatable-align.s | 6 +-
lld/test/ELF/riscv-tlsdesc-relax.s | 2 +-
.../RISCV/MCTargetDesc/RISCVAsmBackend.cpp | 9 +-
.../RISCV/MCTargetDesc/RISCVAsmBackend.h | 4 +
.../RISCV/MCTargetDesc/RISCVELFStreamer.cpp | 34 +++++-
.../RISCV/MCTargetDesc/RISCVELFStreamer.h | 13 ++-
.../CodeGen/RISCV/option-relax-relocation.ll | 4 +-
.../CodeGen/RISCV/relax-per-target-feature.ll | 2 +-
.../MC/RISCV/Relocations/align-after-relax.s | 2 +-
.../RISCV/Relocations/align-non-executable.s | 2 +-
llvm/test/MC/RISCV/Relocations/align-norvc.s | 4 +-
llvm/test/MC/RISCV/Relocations/leb128.s | 4 +-
llvm/test/MC/RISCV/align.s | 4 +-
llvm/test/MC/RISCV/linker-relaxation.s | 44 ++++----
.../MC/RISCV/option-arch-relax-mapping-sym.s | 51 +++++++++
llvm/test/MC/RISCV/option-exact.s | 4 +-
llvm/test/MC/RISCV/option-pushpop.s | 8 +-
llvm/test/MC/RISCV/option-relax.s | 4 +-
.../MC/RISCV/option-rvc-relax-mapping-sym.s | 106 ++++++++++++++++++
llvm/test/MC/RISCV/pcrel-fixups.s | 22 ++--
llvm/test/MC/RISCV/xqcibi-linker-relaxation.s | 8 +-
llvm/test/MC/RISCV/xqcibi-relocations.s | 4 +-
llvm/test/MC/RISCV/xqcili-linker-relaxation.s | 4 +-
25 files changed, 281 insertions(+), 78 deletions(-)
create mode 100644 llvm/test/MC/RISCV/option-arch-relax-mapping-sym.s
create mode 100644 llvm/test/MC/RISCV/option-rvc-relax-mapping-sym.s
diff --git a/lld/test/ELF/riscv-relax-align.s b/lld/test/ELF/riscv-relax-align.s
index b3db2c943d85d..d2dd6a3d63cfa 100644
--- a/lld/test/ELF/riscv-relax-align.s
+++ b/lld/test/ELF/riscv-relax-align.s
@@ -84,7 +84,7 @@
# CHECKR: <_start>:
# CHECKR-NEXT: lui a0, 0x0
# CHECKR-NEXT: 0000000000000000: R_RISCV_HI20 _start
-# CHECKR-NEXT: 0000000000000000: R_RISCV_RELAX *ABS*
+# CHECKR-NEXT: 0000000000000000: R_RISCV_RELAX {{\$xrv.*}}
# CHECKR-EMPTY:
# CHECKR-NEXT: <a>:
# CHECKR-NEXT: addi a0, a0, 0x2
diff --git a/lld/test/ELF/riscv-relax-emit-relocs.s b/lld/test/ELF/riscv-relax-emit-relocs.s
index 358a288777e93..95e3cf17ead36 100644
--- a/lld/test/ELF/riscv-relax-emit-relocs.s
+++ b/lld/test/ELF/riscv-relax-emit-relocs.s
@@ -22,10 +22,10 @@
# CHECK: <_start>:
# CHECK-NEXT: jal ra, 0x10008 <f>
# CHECK-NEXT: R_RISCV_JAL f
-# CHECK-NEXT: R_RISCV_RELAX *ABS*
+# CHECK-NEXT: R_RISCV_RELAX {{\$xrv.*}}
# CHECK-NEXT: jal ra, 0x10008 <f>
# CHECK-NEXT: R_RISCV_JAL f
-# CHECK-NEXT: R_RISCV_RELAX *ABS*
+# CHECK-NEXT: R_RISCV_RELAX {{\$xrv.*}}
# CHECK-EMPTY:
# CHECK-NEXT: <f>:
# CHECK-NEXT: jalr zero, 0x0(ra)
@@ -34,11 +34,11 @@
# CHECKR: <_start>:
# CHECKR-NEXT: auipc ra, 0x0
# CHECKR-NEXT: R_RISCV_CALL_PLT f
-# CHECKR-NEXT: R_RISCV_RELAX *ABS*
+# CHECKR-NEXT: R_RISCV_RELAX {{\$xrv.*}}
# CHECKR-NEXT: jalr ra, 0x0(ra)
# CHECKR-NEXT: auipc ra, 0x0
# CHECKR-NEXT: R_RISCV_CALL_PLT f
-# CHECKR-NEXT: R_RISCV_RELAX *ABS*
+# CHECKR-NEXT: R_RISCV_RELAX {{\$xrv.*}}
# CHECKR-NEXT: jalr ra, 0x0(ra)
# CHECKR-NEXT: addi zero, zero, 0x0
# CHECKR-NEXT: R_RISCV_ALIGN *ABS*+0x4
@@ -49,11 +49,11 @@
# CHECKNORELAX: <_start>:
# CHECKNORELAX-NEXT: auipc ra, 0x0
# CHECKNORELAX-NEXT: R_RISCV_CALL_PLT f
-# CHECKNORELAX-NEXT: R_RISCV_RELAX *ABS*
+# CHECKNORELAX-NEXT: R_RISCV_RELAX {{\$xrv.*}}
# CHECKNORELAX-NEXT: jalr ra, 0x10(ra)
# CHECKNORELAX-NEXT: auipc ra, 0x0
# CHECKNORELAX-NEXT: R_RISCV_CALL_PLT f
-# CHECKNORELAX-NEXT: R_RISCV_RELAX *ABS*
+# CHECKNORELAX-NEXT: R_RISCV_RELAX {{\$xrv.*}}
# CHECKNORELAX-NEXT: jalr ra, 0x8(ra)
# CHECKNORELAX-EMPTY:
# CHECKNORELAX-NEXT: <f>:
diff --git a/lld/test/ELF/riscv-relocatable-align.s b/lld/test/ELF/riscv-relocatable-align.s
index 24b5b108a4790..1f9d123d6b93f 100644
--- a/lld/test/ELF/riscv-relocatable-align.s
+++ b/lld/test/ELF/riscv-relocatable-align.s
@@ -33,7 +33,7 @@
# CHECK-NEXT: <_start>:
# CHECK-NEXT: c: 00000097 auipc ra, 0x0
# CHECK-NEXT: 000000000000000c: R_RISCV_CALL_PLT foo
-# CHECK-NEXT: 000000000000000c: R_RISCV_RELAX *ABS*
+# CHECK-NEXT: 000000000000000c: R_RISCV_RELAX {{\$xrv.*}}
# CHECK-NEXT: 10: 000080e7 jalr ra, 0x0(ra) <_start>
# CHECK-NEXT: 14: 0001 c.nop
# CHECK-NEXT: 0000000000000014: R_RISCV_ALIGN *ABS*+0x6
@@ -54,7 +54,7 @@
# CHECK-NEXT: <c0>:
# CHECK-NEXT: 2c: 00000097 auipc ra, 0x0
# CHECK-NEXT: 000000000000002c: R_RISCV_CALL_PLT foo
-# CHECK-NEXT: 000000000000002c: R_RISCV_RELAX *ABS*
+# CHECK-NEXT: 000000000000002c: R_RISCV_RELAX {{\$xrv.*}}
# CHECK-NEXT: 30: 000080e7 jalr ra, 0x0(ra) <c0>
# CHECK-NEXT: 34: 0001 c.nop
# CHECK-NEXT: 0000000000000034: R_RISCV_ALIGN *ABS*+0x2
@@ -83,7 +83,7 @@
# CHECK2: <_start>:
# CHECK2-NEXT: 0: 00000097 auipc ra, 0x0
# CHECK2-NEXT: 0000000000000000: R_RISCV_CALL_PLT foo
-# CHECK2-NEXT: 0000000000000000: R_RISCV_RELAX *ABS*
+# CHECK2-NEXT: 0000000000000000: R_RISCV_RELAX {{\$xrv.*}}
# CHECK2-NEXT: 4: 000080e7 jalr ra, 0x0(ra) <_start>
# CHECK2-NEXT: 8: 0001 c.nop
# CHECK2-NEXT: 0000000000000008: R_RISCV_ALIGN *ABS*+0x6
diff --git a/lld/test/ELF/riscv-tlsdesc-relax.s b/lld/test/ELF/riscv-tlsdesc-relax.s
index 5718d4175be11..77917872a8539 100644
--- a/lld/test/ELF/riscv-tlsdesc-relax.s
+++ b/lld/test/ELF/riscv-tlsdesc-relax.s
@@ -65,7 +65,7 @@
# LE64-NEXT: c.add a0, tp
# LE64-NEXT: jal {{.*}} <foo>
# LE64-NEXT: R_RISCV_JAL foo
-# LE64-NEXT: R_RISCV_RELAX *ABS*
+# LE64-NEXT: R_RISCV_RELAX {{\$xrv.*}}
# LE64-LABEL: <.Ltlsdesc_hi1>:
# LE64-NEXT: addi a0, zero, 0x7ff
# LE64-NEXT: R_RISCV_TLSDESC_HI20 b
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
index 4e3ed3f5ae31f..785b9d4c1ea62 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "RISCVAsmBackend.h"
+#include "RISCVELFStreamer.h"
#include "RISCVFixupKinds.h"
#include "llvm/ADT/APInt.h"
#include "llvm/MC/MCAsmInfo.h"
@@ -899,10 +900,14 @@ bool RISCVAsmBackend::addReloc(const MCFragment &F, const MCFixup &Fixup,
if (NeedsRelax) {
// Some Fixups get a RELAX relocation, record it (directly) after we add
- // the relocation.
+ // the relocation. If there is an ISA mapping symbol associated with this
+ // fragment, reference it so the linker can determine which ISA extensions
+ // are available for relaxation in this code region.
+ MCSymbol *ISASym =
+ ELFStreamer ? ELFStreamer->getFragmentISASym(F) : nullptr;
MCFixup RelaxFixup =
MCFixup::create(Fixup.getOffset(), nullptr, ELF::R_RISCV_RELAX);
- MCValue RelaxTarget = MCValue::get(nullptr);
+ MCValue RelaxTarget = MCValue::get(ISASym);
uint64_t RelaxValue;
Asm->getWriter().recordRelocation(F, RelaxFixup, RelaxTarget, RelaxValue);
}
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.h
index 4b05284c9537d..d327bedf844d8 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.h
@@ -30,6 +30,8 @@ class RISCVAsmBackend : public MCAsmBackend {
// Temporary symbol used to check whether a PC-relative fixup is resolved.
MCSymbol *PCRelTemp = nullptr;
+ class RISCVELFStreamer *ELFStreamer = nullptr;
+
bool isPCRelFixupResolved(const MCSymbol *SymA, const MCFragment &F);
StringMap<MCSymbol *> VendorSymbols;
@@ -75,6 +77,8 @@ class RISCVAsmBackend : public MCAsmBackend {
const MCSubtargetInfo *STI) const override;
const MCTargetOptions &getTargetOptions() const { return TargetOptions; }
+
+ void setELFStreamer(RISCVELFStreamer *S) { ELFStreamer = S; }
};
}
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.cpp
index 0bac00910b1fa..d368988534471 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.cpp
@@ -21,6 +21,7 @@
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCELFObjectWriter.h"
#include "llvm/MC/MCSubtargetInfo.h"
+#include "llvm/Support/RISCVAttributes.h"
using namespace llvm;
@@ -47,6 +48,10 @@ RISCVTargetELFStreamer::RISCVTargetELFStreamer(MCStreamer &S,
ArchString = InitialArchString;
getStreamer().setMappingSymbolArch(ArchString);
}
+
+ // Give the backend a pointer to this ELF streamer so it can look up the
+ // per-fragment ISA mapping symbol when recording R_RISCV_RELAX relocations.
+ MAB.setELFStreamer(&getStreamer());
}
RISCVELFStreamer::RISCVELFStreamer(MCContext &C,
@@ -91,6 +96,10 @@ void RISCVTargetELFStreamer::emitAttribute(unsigned Attribute, unsigned Value) {
void RISCVTargetELFStreamer::emitTextAttribute(unsigned Attribute,
StringRef String) {
getStreamer().setAttributeItem(Attribute, String, /*OverwriteExisting=*/true);
+
+ // Track arch for emitting R_RISCV_RELAX with correct ISA string.
+ if (Attribute == RISCVAttrs::ARCH)
+ setArchString(String);
}
void RISCVTargetELFStreamer::emitIntTextAttribute(unsigned Attribute,
@@ -174,6 +183,9 @@ void RISCVELFStreamer::reset() {
MappingSymbolArch.clear();
LastEmittedArch.clear();
LastEmittedArchInSection.clear();
+ CurrentISARelaxSym = nullptr;
+ FragmentISASym.clear();
+ LastISARelaxSymInSection.clear();
// Call target streamer reset last: it may call setMappingSymbolArch to
// re-seed the initial ISA after our state has been cleared.
static_cast<RISCVTargetStreamer *>(getTargetStreamer())->reset();
@@ -197,21 +209,24 @@ void RISCVELFStreamer::emitInstructionsMappingSymbol() {
bool NeedSymbol =
LastEMS != EMS_Instructions || LastEmittedArch != MappingSymbolArch;
if (NeedSymbol) {
- if (MappingSymbolArch.empty())
+ if (MappingSymbolArch.empty()) {
emitMappingSymbol("$x");
- else
- emitMappingSymbol("$x" + MappingSymbolArch);
+ CurrentISARelaxSym = nullptr;
+ } else {
+ CurrentISARelaxSym = emitMappingSymbol("$x" + MappingSymbolArch);
+ }
LastEmittedArch = MappingSymbolArch;
}
LastEMS = EMS_Instructions;
}
-void RISCVELFStreamer::emitMappingSymbol(StringRef Name) {
+MCSymbol *RISCVELFStreamer::emitMappingSymbol(StringRef Name) {
auto *Symbol =
static_cast<MCSymbolELF *>(getContext().createLocalSymbol(Name));
emitLabel(Symbol);
Symbol->setType(ELF::STT_NOTYPE);
Symbol->setBinding(ELF::STB_LOCAL);
+ return Symbol;
}
void RISCVELFStreamer::setMappingSymbolArch(StringRef Arch) {
@@ -227,9 +242,11 @@ void RISCVELFStreamer::changeSection(MCSection *Section, uint32_t Subsection) {
const MCSection *Prev = getPreviousSection().first;
LastMappingSymbols[Prev] = LastEMS;
LastEmittedArchInSection[Prev] = LastEmittedArch;
+ LastISARelaxSymInSection[Prev] = CurrentISARelaxSym;
LastEMS = LastMappingSymbols.lookup(Section);
auto It = LastEmittedArchInSection.find(Section);
LastEmittedArch = It != LastEmittedArchInSection.end() ? It->second : "";
+ CurrentISARelaxSym = LastISARelaxSymInSection.lookup(Section);
MCELFStreamer::changeSection(Section, Subsection);
}
@@ -237,7 +254,16 @@ void RISCVELFStreamer::changeSection(MCSection *Section, uint32_t Subsection) {
void RISCVELFStreamer::emitInstruction(const MCInst &Inst,
const MCSubtargetInfo &STI) {
emitInstructionsMappingSymbol();
+
+ MCFragment *PreFrag = getCurrentFragment();
MCELFStreamer::emitInstruction(Inst, STI);
+ MCFragment *PostFrag = getCurrentFragment();
+
+ if (CurrentISARelaxSym) {
+ FragmentISASym[PreFrag] = CurrentISARelaxSym;
+ if (PostFrag != PreFrag)
+ FragmentISASym[PostFrag] = CurrentISARelaxSym;
+ }
}
void RISCVELFStreamer::emitBytes(StringRef Data) {
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.h
index ac738307922d7..6f7e68d2f8496 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFStreamer.h
@@ -10,6 +10,7 @@
#define LLVM_LIB_TARGET_RISCV_MCTARGETDESC_RISCVELFSTREAMER_H
#include "RISCVTargetStreamer.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/MC/MCELFStreamer.h"
@@ -19,7 +20,7 @@ class RISCVELFStreamer : public MCELFStreamer {
void reset() override;
void emitDataMappingSymbol();
void emitInstructionsMappingSymbol();
- void emitMappingSymbol(StringRef Name);
+ MCSymbol *emitMappingSymbol(StringRef Name);
enum ElfMappingSymbol { EMS_None, EMS_Instructions, EMS_Data };
@@ -38,6 +39,10 @@ class RISCVELFStreamer : public MCELFStreamer {
std::string LastEmittedArch;
DenseMap<const MCSection *, std::string> LastEmittedArchInSection;
+ MCSymbol *CurrentISARelaxSym = nullptr;
+ DenseMap<const MCFragment *, MCSymbol *> FragmentISASym;
+ DenseMap<const MCSection *, MCSymbol *> LastISARelaxSymInSection;
+
public:
RISCVELFStreamer(MCContext &C, std::unique_ptr<MCAsmBackend> MAB,
std::unique_ptr<MCObjectWriter> MOW,
@@ -50,6 +55,12 @@ class RISCVELFStreamer : public MCELFStreamer {
void emitValueImpl(const MCExpr *Value, unsigned Size, SMLoc Loc) override;
void setMappingSymbolArch(StringRef Arch);
+
+ // Returns the ISA mapping symbol for the given fragment, or nullptr if the
+ // fragment precedes any ISA-changing directive.
+ MCSymbol *getFragmentISASym(const MCFragment &F) const {
+ return FragmentISASym.lookup(&F);
+ }
};
class RISCVTargetELFStreamer : public RISCVTargetStreamer {
diff --git a/llvm/test/CodeGen/RISCV/option-relax-relocation.ll b/llvm/test/CodeGen/RISCV/option-relax-relocation.ll
index 62178e11f8713..a2bda8aff995b 100644
--- a/llvm/test/CodeGen/RISCV/option-relax-relocation.ll
+++ b/llvm/test/CodeGen/RISCV/option-relax-relocation.ll
@@ -11,14 +11,14 @@
; RELAX-NOT: R_RISCV_RELAX
; CHECK-NEXT: auipc ra, 0x0
; CHECK-NEXT: R_RISCV_CALL_PLT f
-; RELAX-NEXT: R_RISCV_RELAX *ABS*
+; RELAX-NEXT: R_RISCV_RELAX $xrv64i2p1
; CHECK-NEXT: jalr ra
; CHECK-NEXT: j {{.*}}
; RELAX-NEXT: R_RISCV_JAL {{.*}}
; RELAX-NOT: R_RISCV_RELAX
; CHECK-NEXT: j {{.*}}
; RELAX-NEXT: R_RISCV_JAL {{.*}}
-; RELAX-NEXT: R_RISCV_RELAX *ABS*
+; RELAX-NEXT: R_RISCV_RELAX $xrv64i2p1
; NORELAX-NEXT: li a0, 0x0
; RELAX-EMPTY:
diff --git a/llvm/test/CodeGen/RISCV/relax-per-target-feature.ll b/llvm/test/CodeGen/RISCV/relax-per-target-feature.ll
index 0706b0eae97b8..c4e27c367e5a5 100644
--- a/llvm/test/CodeGen/RISCV/relax-per-target-feature.ll
+++ b/llvm/test/CodeGen/RISCV/relax-per-target-feature.ll
@@ -11,7 +11,7 @@ declare dso_local i32 @ext(i32)
; CHECK-NEXT: c.li a0, 0x1f
; CHECK-NEXT: auipc t1, 0x0
; CHECK-NEXT: R_RISCV_CALL_PLT ext
-; CHECK-NEXT: R_RISCV_RELAX *ABS*
+; CHECK-NEXT: R_RISCV_RELAX $xrv64i2p1
; CHECK-NEXT: jalr zero, 0x0(t1)
define dso_local i32 @f() #0 {
entry:
diff --git a/llvm/test/MC/RISCV/Relocations/align-after-relax.s b/llvm/test/MC/RISCV/Relocations/align-after-relax.s
index 95bef513dff97..abb3cee33cd96 100644
--- a/llvm/test/MC/RISCV/Relocations/align-after-relax.s
+++ b/llvm/test/MC/RISCV/Relocations/align-after-relax.s
@@ -10,7 +10,7 @@
# CHECK-NEXT: c: 00 00 00 00 .word 0x00000000
# CHECK: 10: auipc ra, 0x0
# CHECK-NEXT: R_RISCV_CALL_PLT foo
-# CHECK-NEXT: R_RISCV_RELAX *ABS*
+# CHECK-NEXT: R_RISCV_RELAX {{.*}}
# CHECK: 18: c.nop
# CHECK-NEXT: R_RISCV_ALIGN *ABS*+0x6
diff --git a/llvm/test/MC/RISCV/Relocations/align-non-executable.s b/llvm/test/MC/RISCV/Relocations/align-non-executable.s
index 299f110b65698..a2c65baecb8f9 100644
--- a/llvm/test/MC/RISCV/Relocations/align-non-executable.s
+++ b/llvm/test/MC/RISCV/Relocations/align-non-executable.s
@@ -18,7 +18,7 @@
# CHECK: Relocations [
# CHECK-NEXT: Section ({{.*}}) .rela.dummy {
# CHECK-NEXT: 0x0 R_RISCV_CALL_PLT func 0x0
-# RELAX-NEXT: 0x0 R_RISCV_RELAX - 0x0
+# RELAX-NEXT: 0x0 R_RISCV_RELAX {{.*}} 0x0
# RELAX-NEXT: 0x8 R_RISCV_ADD64 .L2 0x0
# RELAX-NEXT: 0x8 R_RISCV_SUB64 .L1 0x0
# CHECK-NEXT: }
diff --git a/llvm/test/MC/RISCV/Relocations/align-norvc.s b/llvm/test/MC/RISCV/Relocations/align-norvc.s
index c3fe71e65a205..054e07615f9c3 100644
--- a/llvm/test/MC/RISCV/Relocations/align-norvc.s
+++ b/llvm/test/MC/RISCV/Relocations/align-norvc.s
@@ -6,13 +6,13 @@
# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax -riscv-align-rvc=0 %s -o %t0
# RUN: llvm-objdump -dr -M no-aliases %t0 | FileCheck %s --check-prefix=CHECK0
-# CHECK: 00000000: R_RISCV_RELAX *ABS*
+# CHECK: 00000000: R_RISCV_RELAX {{.*}}
# CHECK-NEXT: 4: 0001 <unknown>
# CHECK-NEXT: 00000004: R_RISCV_ALIGN *ABS*+0x6
# CHECK-NEXT: 6: 00000013 addi zero, zero, 0x0
# CHECK-NEXT: a: 00000537 lui a0, 0x0
-# CHECK0: 00000000: R_RISCV_RELAX *ABS*
+# CHECK0: 00000000: R_RISCV_RELAX {{.*}}
# CHECK0-NEXT: 4: 00000013 addi zero, zero, 0x0
# CHECK0-NEXT: 00000004: R_RISCV_ALIGN *ABS*+0x4
# CHECK0-NEXT: 8: 00000537 lui a0, 0x0
diff --git a/llvm/test/MC/RISCV/Relocations/leb128.s b/llvm/test/MC/RISCV/Relocations/leb128.s
index b765e3d56f111..4b805d77ea9d9 100644
--- a/llvm/test/MC/RISCV/Relocations/leb128.s
+++ b/llvm/test/MC/RISCV/Relocations/leb128.s
@@ -23,7 +23,7 @@
# CHECK0: Relocations [
# CHECK0-NEXT: .rela.alloc_w {
# CHECK0-NEXT: 0x2 R_RISCV_CALL_PLT foo 0x0
-# RELAX0-NEXT: 0x2 R_RISCV_RELAX - 0x0
+# RELAX0-NEXT: 0x2 R_RISCV_RELAX {{.*}} 0x0
# CHECK0-NEXT: }
# CHECK0-NEXT: ]
@@ -34,7 +34,7 @@
# RELAX-NEXT: 0x1 R_RISCV_SET_ULEB128 w2 0x0
# RELAX-NEXT: 0x1 R_RISCV_SUB_ULEB128 w1 0x0
# CHECK-NEXT: 0x2 R_RISCV_CALL_PLT foo 0x0
-# RELAX-NEXT: 0x2 R_RISCV_RELAX - 0x0
+# RELAX-NEXT: 0x2 R_RISCV_RELAX {{.*}} 0x0
# RELAX-NEXT: 0xA R_RISCV_SET_ULEB128 w2 0x0
# RELAX-NEXT: 0xA R_RISCV_SUB_ULEB128 w1 0x0
# RELAX-NEXT: 0xB R_RISCV_SET_ULEB128 w2 0x78
diff --git a/llvm/test/MC/RISCV/align.s b/llvm/test/MC/RISCV/align.s
index 92862d903ce14..a309e3d7ff8b7 100644
--- a/llvm/test/MC/RISCV/align.s
+++ b/llvm/test/MC/RISCV/align.s
@@ -158,7 +158,7 @@ data2:
# NORELAX-RELOC: .rela.text3a
# RELAX-RELOC: .rela.text3a {
# RELAX-RELOC-NEXT: 0x0 R_RISCV_CALL_PLT foo 0x0
-# RELAX-RELOC-NEXT: 0x0 R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: 0x0 R_RISCV_RELAX {{.*}} 0x0
# RELAX-RELOC-NEXT: 0x8 R_RISCV_BRANCH .Ltmp[[#]] 0x0
# RELAX-RELOC-NEXT: 0xC R_RISCV_BRANCH .Ltmp[[#]] 0x0
# RELAX-RELOC-NEXT: 0x10 R_RISCV_ALIGN - 0x6
@@ -185,7 +185,7 @@ bnez t1, 2b
# RELAX-RELOC-NEXT: 0xE R_RISCV_BRANCH .Ltmp[[#]] 0x0
# RELAX-RELOC-NEXT: 0x12 R_RISCV_BRANCH .Ltmp[[#]] 0x0
# RELAX-RELOC-NEXT: 0x16 R_RISCV_CALL_PLT foo 0x0
-# RELAX-RELOC-NEXT: 0x16 R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: 0x16 R_RISCV_RELAX {{.*}} 0x0
# RELAX-RELOC-NEXT: }
.section .text3b, "ax"
bnez t1, 1f
diff --git a/llvm/test/MC/RISCV/linker-relaxation.s b/llvm/test/MC/RISCV/linker-relaxation.s
index c5c4e4877ff2e..7e45925537f02 100644
--- a/llvm/test/MC/RISCV/linker-relaxation.s
+++ b/llvm/test/MC/RISCV/linker-relaxation.s
@@ -15,50 +15,50 @@ call foo
# NORELAX-RELOC-NEXT: R_RISCV_CALL_PLT foo 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_CALL_PLT foo 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
jal foo
# NORELAX-RELOC-NEXT: R_RISCV_JAL foo 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_JAL foo 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
lui t1, %hi(foo)
# NORELAX-RELOC-NEXT: R_RISCV_HI20 foo 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_HI20 foo 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
addi t1, t1, %lo(foo)
# NORELAX-RELOC-NEXT: R_RISCV_LO12_I foo 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_LO12_I foo 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
sb t1, %lo(foo)(a2)
# NORELAX-RELOC-NEXT: R_RISCV_LO12_S foo 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_LO12_S foo 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
1:
auipc t1, %pcrel_hi(foo)
# NORELAX-RELOC-NEXT: R_RISCV_PCREL_HI20 foo 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_PCREL_HI20 foo 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
addi t1, t1, %pcrel_lo(1b)
# NORELAX-RELOC-NEXT: R_RISCV_PCREL_LO12_I .Ltmp0 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_PCREL_LO12_I .Ltmp0 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
sb t1, %pcrel_lo(1b)(a2)
# NORELAX-RELOC-NEXT: R_RISCV_PCREL_LO12_S .Ltmp0 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_PCREL_LO12_S .Ltmp0 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
# Check behaviour when a locally defined symbol is referenced.
@@ -71,13 +71,13 @@ call bar
# NORELAX-RELOC-NOT: R_RISCV_CALL
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_CALL_PLT bar 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
jal bar
# NORELAX-RELOC-NOT: R_RISCV_JAL
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_JAL bar 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
beq s1, s1, bar
# NORELAX-RELOC-NOT: R_RISCV_BRANCH
@@ -87,73 +87,73 @@ lui t1, %hi(bar)
# NORELAX-RELOC-NEXT: R_RISCV_HI20 bar 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_HI20 bar 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
addi t1, t1, %lo(bar)
# NORELAX-RELOC-NEXT: R_RISCV_LO12_I bar 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_LO12_I bar 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
sb t1, %lo(bar)(a2)
# NORELAX-RELOC-NEXT: R_RISCV_LO12_S bar 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_LO12_S bar 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
2:
auipc t1, %pcrel_hi(bar)
# NORELAX-RELOC-NOT: R_RISCV_PCREL_HI20
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_PCREL_HI20 bar 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
addi t1, t1, %pcrel_lo(2b)
# NORELAX-RELOC-NOT: R_RISCV_PCREL_LO12_I
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_PCREL_LO12_I .Ltmp1 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
sb t1, %pcrel_lo(2b)(a2)
# NORELAX-RELOC-NOT: R_RISCV_PCREL_LO12_S
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_PCREL_LO12_S .Ltmp1 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
auipc t1, %got_pcrel_hi(bar)
# NORELAX-RELOC-NEXT: R_RISCV_GOT_HI20 bar 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_GOT_HI20 bar 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
lui t1, %tprel_hi(baz)
# NORELAX-RELOC-NEXT: R_RISCV_TPREL_HI20 baz 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_TPREL_HI20 baz 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
auipc t1, %tlsdesc_hi(baz)
# NORELAX-RELOC-NEXT: R_RISCV_TLSDESC_HI20 baz 0x0
# NORELAX-RELOC-NOT: R_RISCV_RELAX
# RELAX-RELOC-NEXT: R_RISCV_TLSDESC_HI20 baz 0x0
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
## %hi/%lo on an absolute symbol (not yet defined) leads to relocations when relaxation is enabled.
lui t2, %hi(abs)
# NORELAX-RELOC-NOT: R_RISCV_
# RELAX-RELOC-NEXT: R_RISCV_HI20 - 0x12345
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
addi t2, t2, %lo(abs)
# NORELAX-RELOC-NOT: R_RISCV_
# RELAX-RELOC-NEXT: R_RISCV_LO12_I - 0x12345
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
.set abs, 0x12345
lui t3, %hi(abs)
# RELAX-RELOC-NEXT: R_RISCV_HI20 - 0x12345
-# RELAX-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# RELAX-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
# Check that a relocation is not emitted for a symbol difference which has
# been folded to a fixup with an absolute value. This can happen when a
diff --git a/llvm/test/MC/RISCV/option-arch-relax-mapping-sym.s b/llvm/test/MC/RISCV/option-arch-relax-mapping-sym.s
new file mode 100644
index 0000000000000..dc5b4896b420f
--- /dev/null
+++ b/llvm/test/MC/RISCV/option-arch-relax-mapping-sym.s
@@ -0,0 +1,51 @@
+# Test that .option arch emits ISA mapping symbols referenced by R_RISCV_RELAX,
+# enabling the linker to determine per-region relaxation capabilities.
+#
+# RUN: llvm-mc -filetype=obj -triple riscv64 -mattr=+relax %s -o %t
+# RUN: llvm-readobj -r --symbols %t | FileCheck %s
+
+call no_arch_yet
+
+# CHECK: R_RISCV_CALL_PLT no_arch_yet
+# CHECK-NEXT: R_RISCV_RELAX $xrv64i2p1 0x0
+
+.option arch, rv64imafdc
+call with_c
+
+# CHECK: R_RISCV_CALL_PLT with_c
+# CHECK-NEXT: R_RISCV_RELAX $xrv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0 0x0
+
+.option arch, rv64imafd
+call without_c
+
+# CHECK: R_RISCV_CALL_PLT without_c
+# CHECK-NEXT: R_RISCV_RELAX $xrv64i2p1_m2p0_a2p1_f2p2_d2p2_zicsr2p0_zmmul1p0_zaamo1p0_zalrsc1p0 0x0
+
+.option arch, rv64imafdc
+call with_c_again
+
+# CHECK: R_RISCV_CALL_PLT with_c_again
+# CHECK-NEXT: R_RISCV_RELAX $xrv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0 0x0
+
+.option push
+.option arch, rv64imafd
+call inside_push_no_c
+
+# CHECK: R_RISCV_CALL_PLT inside_push_no_c
+# CHECK-NEXT: R_RISCV_RELAX $xrv64i2p1_m2p0_a2p1_f2p2_d2p2_zicsr2p0_zmmul1p0_zaamo1p0_zalrsc1p0 0x0
+
+.option pop
+call after_pop_with_c
+
+# CHECK: R_RISCV_CALL_PLT after_pop_with_c
+# CHECK-NEXT: R_RISCV_RELAX $xrv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0 0x0
+
+# The initial base-ISA symbol appears at 0x0.
+# CHECK: Name: $xrv64i2p1
+# CHECK-NEXT: Value: 0x0
+# The with-C symbol appears at 0x8 (start of second instruction pair).
+# CHECK: Name: $xrv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zmmul1p0_zaamo1p0_zalrsc1p0_zca1p0_zcd1p0
+# CHECK-NEXT: Value: 0x8
+# The without-C symbol appears at 0x10.
+# CHECK: Name: $xrv64i2p1_m2p0_a2p1_f2p2_d2p2_zicsr2p0_zmmul1p0_zaamo1p0_zalrsc1p0
+# CHECK-NEXT: Value: 0x10
diff --git a/llvm/test/MC/RISCV/option-exact.s b/llvm/test/MC/RISCV/option-exact.s
index 83e25817bfc08..d71c2eb172a0c 100644
--- a/llvm/test/MC/RISCV/option-exact.s
+++ b/llvm/test/MC/RISCV/option-exact.s
@@ -31,7 +31,7 @@ c.lw a0, 0(a0)
# CHECK-ASM-SAME: # encoding: [0x97'A',A,A,A,0xe7'A',0x80'A',A,A]
# CHECK-OBJDUMP: auipc ra, 0
# CHECK-OBJDUMP-NEXT: R_RISCV_CALL_PLT undefined
-# CHECK-OBJDUMP-NEXT: R_RISCV_RELAX *ABS*
+# CHECK-OBJDUMP-NEXT: R_RISCV_RELAX {{.*}}
# CHECK-OBJDUMP-NEXT: jalr ra
call undefined at plt
@@ -98,7 +98,7 @@ c.lw a0, 0(a0)
# CHECK-ASM-SAME: # encoding: [0x97'A',A,A,A,0xe7'A',0x80'A',A,A]
# CHECK-OBJDUMP: auipc ra, 0
# CHECK-OBJDUMP-NEXT: R_RISCV_CALL_PLT undefined
-# CHECK-OBJDUMP-NEXT: R_RISCV_RELAX *ABS*
+# CHECK-OBJDUMP-NEXT: R_RISCV_RELAX {{.*}}
# CHECK-OBJDUMP-NEXT: jalr ra, 0(ra)
call undefined at plt
diff --git a/llvm/test/MC/RISCV/option-pushpop.s b/llvm/test/MC/RISCV/option-pushpop.s
index 68d60be9f888d..411442e822616 100644
--- a/llvm/test/MC/RISCV/option-pushpop.s
+++ b/llvm/test/MC/RISCV/option-pushpop.s
@@ -21,7 +21,7 @@
# CHECK-INST: call foo
# CHECK-RELOC: R_RISCV_CALL_PLT foo 0x0
-# CHECK-RELOC-NOT: R_RISCV_RELAX - 0x0
+# CHECK-RELOC-NOT: R_RISCV_RELAX {{.*}} 0x0
call foo
# CHECK-INST: addi s0, sp, 1020
@@ -36,7 +36,7 @@ addi s0, sp, 1020
# CHECK-INST: .option relax
# CHECK-INST: call bar
# CHECK-RELOC-NEXT: R_RISCV_CALL_PLT bar 0x0
-# CHECK-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# CHECK-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
call bar
.option push # Push relax=true, rvc=false
@@ -58,14 +58,14 @@ addi s0, sp, 1020
# CHECK-INST: call bar
# CHECK-RELOC-NEXT: R_RISCV_CALL_PLT bar 0x0
-# CHECK-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# CHECK-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
call bar
.option pop # Pop relax=false, rvc=false
# CHECK-INST: .option pop
# CHECK-INST: call baz
# CHECK-RELOC: R_RISCV_CALL_PLT baz 0x0
-# CHECK-RELOC-NOT: R_RISCV_RELAX - 0x0
+# CHECK-RELOC-NOT: R_RISCV_RELAX {{.*}} 0x0
call baz
# CHECK-INST: addi s0, sp, 1020
diff --git a/llvm/test/MC/RISCV/option-relax.s b/llvm/test/MC/RISCV/option-relax.s
index e591c824eb437..8c30b5470060e 100644
--- a/llvm/test/MC/RISCV/option-relax.s
+++ b/llvm/test/MC/RISCV/option-relax.s
@@ -34,7 +34,7 @@ beq s1, s1, .L1
# CHECK-INST: call bar
# CHECK-RELOC-NEXT: R_RISCV_CALL_PLT bar 0x0
-# CHECK-RELOC-NEXT: R_RISCV_RELAX - 0x0
+# CHECK-RELOC-NEXT: R_RISCV_RELAX {{.*}} 0x0
call bar
.dword .L2-.L1
@@ -49,7 +49,7 @@ beq s1, s1, .L1
# CHECK-INST: call baz
# CHECK-RELOC-NEXT: R_RISCV_CALL_PLT baz 0x0
-# CHECK-RELOC-NOT: R_RISCV_RELAX - 0x0
+# CHECK-RELOC-NOT: R_RISCV_RELAX {{.*}} 0x0
call baz
.dword .L2-.L1
diff --git a/llvm/test/MC/RISCV/option-rvc-relax-mapping-sym.s b/llvm/test/MC/RISCV/option-rvc-relax-mapping-sym.s
new file mode 100644
index 0000000000000..ed3872f01631d
--- /dev/null
+++ b/llvm/test/MC/RISCV/option-rvc-relax-mapping-sym.s
@@ -0,0 +1,106 @@
+# Test .option rvc/.option norvc ISA mapping symbol emission and R_RISCV_RELAX
+# symbol references across four combinations of initial relax/rvc state
+# (RISC-V ELF PSABI PR #393).
+#
+# Each RUN assembles the same source with a different -mattr combination.
+# RELAX-NORVC (+relax, no C) - baseline RVC off, relaxation on
+# RELAX-RVC (+relax, +c) - baseline RVC on, relaxation on
+# NORELAX-NORVC (no mattr) - baseline RVC off, relaxation off
+# NORELAX-RVC (+c) - baseline RVC on, relaxation off
+#
+# RUN: llvm-mc -filetype=obj -triple riscv64 -mattr=+relax %s \
+# RUN: -o %t.relax_norvc
+# RUN: llvm-readobj -r --symbols %t.relax_norvc \
+# RUN: | FileCheck --check-prefix=RELAX-NORVC %s
+#
+# RUN: llvm-mc -filetype=obj -triple riscv64 -mattr=+relax,+c %s \
+# RUN: -o %t.relax_rvc
+# RUN: llvm-readobj -r --symbols %t.relax_rvc \
+# RUN: | FileCheck --check-prefix=RELAX-RVC %s
+#
+# RUN: llvm-mc -filetype=obj -triple riscv64 %s \
+# RUN: -o %t.norelax_norvc
+# RUN: llvm-readobj -r --symbols %t.norelax_norvc \
+# RUN: | FileCheck --check-prefix=NORELAX-NORVC %s
+#
+# RUN: llvm-mc -filetype=obj -triple riscv64 -mattr=+c %s \
+# RUN: -o %t.norelax_rvc
+# RUN: llvm-readobj -r --symbols %t.norelax_rvc \
+# RUN: | FileCheck --check-prefix=NORELAX-RVC %s
+
+# An initial "$x<ISAString>" symbol is emitted before the very first
+# instruction so R_RISCV_RELAX always has a concrete ISA reference.
+nop
+call before_option
+
+# RELAX-NORVC: R_RISCV_CALL_PLT before_option
+# RELAX-NORVC-NEXT: R_RISCV_RELAX $xrv64i2p1 0x0
+# RELAX-RVC: R_RISCV_CALL_PLT before_option
+# RELAX-RVC-NEXT: R_RISCV_RELAX $xrv64i2p1_c2p0_zca1p0 0x0
+# NORELAX-NORVC: R_RISCV_CALL_PLT before_option
+# NORELAX-NORVC-NOT: R_RISCV_RELAX
+# NORELAX-RVC: R_RISCV_CALL_PLT before_option
+# NORELAX-RVC-NOT: R_RISCV_RELAX
+
+# .option rvc enables RVC; an ISA mapping symbol is emitted (deduplication:
+# only when the ISA actually changes). When starting with +c the directive
+# is a no-op so the ISA sym and R_RISCV_RELAX target remain unchanged.
+.option rvc
+call after_rvc
+
+# RELAX-NORVC: R_RISCV_CALL_PLT after_rvc
+# RELAX-NORVC-NEXT: R_RISCV_RELAX $xrv64i2p1_c2p0_zca1p0 0x0
+# RELAX-RVC: R_RISCV_CALL_PLT after_rvc
+# RELAX-RVC-NEXT: R_RISCV_RELAX $xrv64i2p1_c2p0_zca1p0 0x0
+# NORELAX-NORVC: R_RISCV_CALL_PLT after_rvc
+# NORELAX-NORVC-NOT: R_RISCV_RELAX
+# NORELAX-RVC: R_RISCV_CALL_PLT after_rvc
+# NORELAX-RVC-NOT: R_RISCV_RELAX
+
+# .option norvc disables RVC; a new ISA mapping symbol without C is emitted.
+.option norvc
+call after_norvc
+
+# RELAX-NORVC: R_RISCV_CALL_PLT after_norvc
+# RELAX-NORVC-NEXT: R_RISCV_RELAX $xrv64i2p1 0x0
+# RELAX-RVC: R_RISCV_CALL_PLT after_norvc
+# RELAX-RVC-NEXT: R_RISCV_RELAX $xrv64i2p1 0x0
+# NORELAX-NORVC: R_RISCV_CALL_PLT after_norvc
+# NORELAX-NORVC-NOT: R_RISCV_RELAX
+# NORELAX-RVC: R_RISCV_CALL_PLT after_norvc
+# NORELAX-RVC-NOT: R_RISCV_RELAX
+
+# Verify ISA mapping symbols at the correct offsets. In all cases the initial
+# ISA symbol is emitted at 0x0.
+#
+# Layout with RVC off (nop = 4 bytes):
+# 0x00 initial sym + nop, 0x04 call before_option,
+# 0x0C .option rvc (new sym), call after_rvc,
+# 0x14 .option norvc (new sym), call after_norvc
+#
+# RELAX-NORVC: Name: $xrv64i2p1
+# RELAX-NORVC-NEXT: Value: 0x0
+# RELAX-NORVC: Name: $xrv64i2p1_c2p0_zca1p0
+# RELAX-NORVC-NEXT: Value: 0xC
+# RELAX-NORVC: Name: $xrv64i2p1
+# RELAX-NORVC-NEXT: Value: 0x14
+# NORELAX-NORVC: Name: $xrv64i2p1
+# NORELAX-NORVC-NEXT: Value: 0x0
+# NORELAX-NORVC: Name: $xrv64i2p1_c2p0_zca1p0
+# NORELAX-NORVC-NEXT: Value: 0xC
+# NORELAX-NORVC: Name: $xrv64i2p1
+# NORELAX-NORVC-NEXT: Value: 0x14
+#
+# Layout with RVC on (nop = c.nop = 2 bytes):
+# 0x00 initial sym + c.nop, 0x02 call before_option,
+# 0x0A .option rvc (no-op), call after_rvc,
+# 0x12 .option norvc (new sym), call after_norvc
+#
+# RELAX-RVC: Name: $xrv64i2p1_c2p0_zca1p0
+# RELAX-RVC-NEXT: Value: 0x0
+# RELAX-RVC: Name: $xrv64i2p1
+# RELAX-RVC-NEXT: Value: 0x12
+# NORELAX-RVC: Name: $xrv64i2p1_c2p0_zca1p0
+# NORELAX-RVC-NEXT: Value: 0x0
+# NORELAX-RVC: Name: $xrv64i2p1
+# NORELAX-RVC-NEXT: Value: 0x12
diff --git a/llvm/test/MC/RISCV/pcrel-fixups.s b/llvm/test/MC/RISCV/pcrel-fixups.s
index 22e317a7c9690..dbdd802957110 100644
--- a/llvm/test/MC/RISCV/pcrel-fixups.s
+++ b/llvm/test/MC/RISCV/pcrel-fixups.s
@@ -26,10 +26,10 @@ function:
# RELAX: auipc a0, 0
# RELAX: R_RISCV_PCREL_HI20 local_function
-# RELAX: R_RISCV_RELAX *ABS*
+# RELAX: R_RISCV_RELAX {{.*}}
# RELAX: addi a1, a0, 0
# RELAX: R_RISCV_PCREL_LO12_I .Lpcrel_label1
-# RELAX: R_RISCV_RELAX *ABS*
+# RELAX: R_RISCV_RELAX {{.*}}
.p2align 2 # Cause a new fragment be emitted here
.Lpcrel_label2:
@@ -42,10 +42,10 @@ function:
# RELAX: auipc a0, 0
# RELAX: R_RISCV_PCREL_HI20 local_function
-# RELAX: R_RISCV_RELAX *ABS*
+# RELAX: R_RISCV_RELAX {{.*}}
# RELAX: addi a1, a0, 0
# RELAX: R_RISCV_PCREL_LO12_I .Lpcrel_label2
-# RELAX: R_RISCV_RELAX *ABS*
+# RELAX: R_RISCV_RELAX {{.*}}
.type local_function, at function
local_function:
@@ -64,10 +64,10 @@ local_function:
# RELAX: auipc a0, 0
# RELAX: R_RISCV_PCREL_HI20 local_function
-# RELAX: R_RISCV_RELAX *ABS*
+# RELAX: R_RISCV_RELAX {{.*}}
# RELAX: addi a1, a0, 0
# RELAX: R_RISCV_PCREL_LO12_I .Lpcrel_label3
-# RELAX: R_RISCV_RELAX *ABS*
+# RELAX: R_RISCV_RELAX {{.*}}
# Check handling of symbol binding.
@@ -81,10 +81,10 @@ local_function:
# RELAX: auipc a0, 0
# RELAX: R_RISCV_PCREL_HI20 global_function
-# RELAX: R_RISCV_RELAX *ABS*
+# RELAX: R_RISCV_RELAX {{.*}}
# RELAX: addi a1, a0, 0
# RELAX: R_RISCV_PCREL_LO12_I .Lpcrel_label4
-# RELAX: R_RISCV_RELAX *ABS*
+# RELAX: R_RISCV_RELAX {{.*}}
.Lpcrel_label5:
auipc a0, %pcrel_hi(weak_function)
@@ -96,10 +96,10 @@ local_function:
# RELAX: auipc a0, 0
# RELAX: R_RISCV_PCREL_HI20 weak_function
-# RELAX: R_RISCV_RELAX *ABS*
+# RELAX: R_RISCV_RELAX {{.*}}
# RELAX: addi a1, a0, 0
# RELAX: R_RISCV_PCREL_LO12_I .Lpcrel_label5
-# RELAX: R_RISCV_RELAX *ABS*
+# RELAX: R_RISCV_RELAX {{.*}}
.Lpcrel_label6:
auipc a0, %pcrel_hi(ifunc)
@@ -108,7 +108,7 @@ local_function:
# RELAX: auipc a0, 0
# RELAX: R_RISCV_PCREL_HI20 ifunc
-# RELAX: R_RISCV_RELAX *ABS*
+# RELAX: R_RISCV_RELAX {{.*}}
.global global_function
.type global_function, at function
diff --git a/llvm/test/MC/RISCV/xqcibi-linker-relaxation.s b/llvm/test/MC/RISCV/xqcibi-linker-relaxation.s
index 6122981f82e2b..033a94e6a1138 100644
--- a/llvm/test/MC/RISCV/xqcibi-linker-relaxation.s
+++ b/llvm/test/MC/RISCV/xqcibi-linker-relaxation.s
@@ -16,7 +16,7 @@ branch_over_relaxable:
# CHECK: qc.e.jal 0x0 <branch_over_relaxable>
# CHECK-NEXT: R_RISCV_VENDOR QUALCOMM
# CHECK-NEXT: R_RISCV_QC_E_CALL_PLT foo
-# CHECK-NEXT: R_RISCV_RELAX *ABS*
+# CHECK-NEXT: R_RISCV_RELAX {{.*}}
bne a0, a1, branch_over_relaxable
# CHECK-NEXT: bne a0, a1, 0x6 <branch_over_relaxable+0x6>
# CHECK-NEXT: R_RISCV_BRANCH branch_over_relaxable
@@ -45,7 +45,7 @@ short_jump_over_relaxable:
call foo
# CHECK: auipc ra, 0x0
# CHECK-NEXT: R_RISCV_CALL_PLT foo
-# CHECK-NEXT: R_RISCV_RELAX *ABS*
+# CHECK-NEXT: R_RISCV_RELAX {{.*}}
# CHECK-NEXT: jalr ra, 0x0(ra) <short_jump_over_relaxable>
j short_jump_over_relaxable
# CHECK-NEXT: c.j 0x20 <short_jump_over_relaxable+0x8>
@@ -63,7 +63,7 @@ mid_jump_over_fixed:
j mid_jump_over_fixed
# CHECK-NEXT: jal zero, 0x1026 <mid_jump_over_fixed+0x1002>
# CHECK-NEXT: R_RISCV_JAL mid_jump_over_fixed
-# CHECK-NEXT: R_RISCV_RELAX *ABS*
+# CHECK-NEXT: R_RISCV_RELAX {{.*}}
ret
# CHECK-NEXT: c.jr ra
@@ -72,7 +72,7 @@ mid_jump_over_relaxable:
call foo
# CHECK: auipc ra, 0x0
# CHECK-NEXT: R_RISCV_CALL_PLT foo
-# CHECK-NEXT: R_RISCV_RELAX *ABS*
+# CHECK-NEXT: R_RISCV_RELAX {{.*}}
# CHECK-NEXT: jalr ra, 0x0(ra) <mid_jump_over_relaxable>
.space 0x1000
# CHECK-NEXT: ...
diff --git a/llvm/test/MC/RISCV/xqcibi-relocations.s b/llvm/test/MC/RISCV/xqcibi-relocations.s
index 0ab860667faeb..07f8e69c83b5a 100644
--- a/llvm/test/MC/RISCV/xqcibi-relocations.s
+++ b/llvm/test/MC/RISCV/xqcibi-relocations.s
@@ -91,14 +91,14 @@ qc.e.bgeui s2, 24, same_section
# OBJ: qc.beqi t1, 0xa, 0x42 <same_section_extern+0x16>
# OBJ-NEXT: j 0x3e <same_section_extern+0x12>
# OBJ-NEXT: R_RISCV_JAL undef{{$}}
-# OBJ-NEXT: R_RISCV_RELAX *ABS*{{$}}
+# OBJ-NEXT: R_RISCV_RELAX {{.*}}{{$}}
qc.bnei t1, 10, undef
# ASM: qc.e.bgeui s0, 40, undef
# OBJ-NEXT: qc.e.bltui s0, 0x28, 0x4c <same_section_extern+0x20>
# OBJ-NEXT: j 0x48 <same_section_extern+0x1c>
# OBJ-NEXT: R_RISCV_JAL undef{{$}}
-# OBJ-NEXT: R_RISCV_RELAX *ABS*{{$}}
+# OBJ-NEXT: R_RISCV_RELAX {{.*}}{{$}}
qc.e.bgeui s0, 40, undef
.section .text.second, "ax", @progbits
diff --git a/llvm/test/MC/RISCV/xqcili-linker-relaxation.s b/llvm/test/MC/RISCV/xqcili-linker-relaxation.s
index 41a5f39c17278..33a83974b0f0b 100644
--- a/llvm/test/MC/RISCV/xqcili-linker-relaxation.s
+++ b/llvm/test/MC/RISCV/xqcili-linker-relaxation.s
@@ -15,7 +15,7 @@
# CHECK-NEXT: qc.e.li a0, 0x0
# CHECK-NEXT: R_RISCV_VENDOR QUALCOMM{{$}}
# CHECK-NEXT: R_RISCV_QC_E_32 sym{{$}}
-# CHECK-NEXT: R_RISCV_RELAX *ABS*{{$}}
+# CHECK-NEXT: R_RISCV_RELAX {{.*}}{{$}}
.L1:
# CHECK: <.L1>:
ret
@@ -30,7 +30,7 @@
# CHECK-NEXT: qc.li a0, 0x0
# CHECK-NEXT: R_RISCV_VENDOR QUALCOMM{{$}}
# CHECK-NEXT: R_RISCV_QC_ABS20_U sym{{$}}
-# CHECK-NEXT: R_RISCV_RELAX *ABS*{{$}}
+# CHECK-NEXT: R_RISCV_RELAX {{.*}}{{$}}
.L2:
# CHECK: <.L2>:
ret
>From 98e5b682297b43c818c733ef51bb822b5d37cfcf Mon Sep 17 00:00:00 2001
From: Kito Cheng <kito.cheng at sifive.com>
Date: Mon, 6 Apr 2026 08:20:18 -0700
Subject: [PATCH 2/2] [LLD][RISCV] Use ISA mapping symbols for per-region RVC
relaxation
When a R_RISCV_RELAX companion relocation references an ISA mapping
symbol ($x<isa-string>), use the ISA string to determine whether the C
or Zca extension is available for relaxation in that code region, rather
than relying solely on the file-level EF_RISCV_RVC flag.
Backward compatibility is preserved: when R_RISCV_RELAX has no mapping
symbol (null symbol, as produced by assemblers without R_RISCV_RELAX mapping
symbol support), the linker continues to use EF_RISCV_RVC as before.
RISCVRelaxCapabilityManager owns a StringMap<RISCVRelaxCapability> cache
(keyed by ISA string content) so each unique ISA string is parsed at
most once per relaxation pass. RISCVRelaxCapability currently carries
one field (rvc); add new fields there as more per-region decisions are
needed.
[1] https://github.com/riscv/riscv-elf-psabi-doc/pull/393
Assisted-by: Opus 4.6
---
lld/ELF/Arch/RISCV.cpp | 55 +++++++++++++++--
.../riscv-relax-call-bad-isa-mapping-sym.s | 55 +++++++++++++++++
.../riscv-relax-call-isa-mapping-sym-compat.s | 30 ++++++++++
.../riscv-relax-call-isa-mapping-sym-norvc.s | 41 +++++++++++++
.../ELF/riscv-relax-call-isa-mapping-sym.s | 40 +++++++++++++
.../ELF/riscv-relax-call-null-relax-sym.s | 59 +++++++++++++++++++
6 files changed, 275 insertions(+), 5 deletions(-)
create mode 100644 lld/test/ELF/riscv-relax-call-bad-isa-mapping-sym.s
create mode 100644 lld/test/ELF/riscv-relax-call-isa-mapping-sym-compat.s
create mode 100644 lld/test/ELF/riscv-relax-call-isa-mapping-sym-norvc.s
create mode 100644 lld/test/ELF/riscv-relax-call-isa-mapping-sym.s
create mode 100644 lld/test/ELF/riscv-relax-call-null-relax-sym.s
diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index 576e7a4614572..1de79e6f3f033 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -12,6 +12,7 @@
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
+#include "llvm/ADT/StringMap.h"
#include "llvm/Support/ELFAttributes.h"
#include "llvm/Support/LEB128.h"
#include "llvm/Support/RISCVAttributeParser.h"
@@ -890,10 +891,52 @@ void elf::initSymbolAnchors(Ctx &ctx) {
}
}
+struct RISCVRelaxCapability {
+ bool rvc = false; // C or Zca: enables c.j / c.jal relaxation
+ bool valid = false;
+};
+
+class RISCVRelaxCapabilityManager {
+ // Maps ISA string (after "$x" prefix) to its computed capabilities.
+ StringMap<RISCVRelaxCapability> cache;
+
+public:
+ RISCVRelaxCapability get(Ctx &ctx, const InputSection &sec,
+ const Relocation &relaxReloc) {
+ const Symbol *sym = relaxReloc.sym;
+ if (sym && sym->getName().size() > 2 && sym->getName().starts_with("$x")) {
+ StringRef isaStr = sym->getName().drop_front(2);
+ auto [it, inserted] = cache.try_emplace(isaStr);
+ if (inserted) {
+ if (auto maybeInfo = RISCVISAInfo::parseNormalizedArchString(isaStr)) {
+ it->second = {/*rvc=*/(*maybeInfo)->hasExtension("c") ||
+ (*maybeInfo)->hasExtension("zca"),
+ /*valid=*/true};
+ } else {
+ // Warn once per unique unparseable ISA string; the EF_RISCV_RVC
+ // fallback below then handles subsequent lookups.
+ Warn(ctx) << sec.file << ": R_RISCV_RELAX ISA mapping symbol '"
+ << sym->getName() << "' has an unparseable ISA string: "
+ << llvm::toString(maybeInfo.takeError())
+ << "; falling back to EF_RISCV_RVC";
+ }
+ }
+ if (it->second.valid)
+ return it->second;
+ }
+ // Fall back to the file-level EF_RISCV_RV flag.
+ return {/*rvc=*/static_cast<bool>(getEFlags(ctx, sec.file) & EF_RISCV_RVC),
+ /*valid=*/true};
+ }
+};
+
// Relax R_RISCV_CALL/R_RISCV_CALL_PLT auipc+jalr to c.j, c.jal, or jal.
static void relaxCall(Ctx &ctx, const InputSection &sec, size_t i, uint64_t loc,
- Relocation &r, uint32_t &remove) {
- const bool rvc = getEFlags(ctx, sec.file) & EF_RISCV_RVC;
+ Relocation &r, uint32_t &remove,
+ const Relocation &relaxReloc,
+ RISCVRelaxCapabilityManager &capMgr) {
+ const RISCVRelaxCapability cap = capMgr.get(ctx, sec, relaxReloc);
+ const bool rvc = cap.rvc;
const Symbol &sym = *r.sym;
const uint64_t insnPair = read64le(sec.content().data() + r.offset);
const uint32_t rd = extractBits(insnPair, 32 + 11, 32 + 7);
@@ -993,7 +1036,8 @@ static void relaxHi20Lo12(Ctx &ctx, const InputSection &sec, size_t i,
}
}
-static bool relax(Ctx &ctx, int pass, InputSection &sec) {
+static bool relax(Ctx &ctx, int pass, InputSection &sec,
+ RISCVRelaxCapabilityManager &capMgr) {
const uint64_t secAddr = sec.getVA();
const MutableArrayRef<Relocation> relocs = sec.relocs();
auto &aux = *sec.relaxAux;
@@ -1032,7 +1076,7 @@ static bool relax(Ctx &ctx, int pass, InputSection &sec) {
// `cur-delta`.
if (relaxable(relocs, i)) {
remove = pass < 4 ? 6 : cur - delta;
- relaxCall(ctx, sec, i, loc, r, remove);
+ relaxCall(ctx, sec, i, loc, r, remove, relocs[i + 1], capMgr);
}
break;
case R_RISCV_TPREL_HI20:
@@ -1108,12 +1152,13 @@ bool RISCV::relaxOnce(int pass) const {
SmallVector<InputSection *, 0> storage;
bool changed = false;
+ RISCVRelaxCapabilityManager capMgr;
for (OutputSection *osec : ctx.outputSections) {
if (!(osec->flags & SHF_EXECINSTR))
continue;
for (InputSection *sec : getInputSections(*osec, storage))
if (sec->relaxAux)
- changed |= relax(ctx, pass, *sec);
+ changed |= relax(ctx, pass, *sec, capMgr);
}
return changed;
}
diff --git a/lld/test/ELF/riscv-relax-call-bad-isa-mapping-sym.s b/lld/test/ELF/riscv-relax-call-bad-isa-mapping-sym.s
new file mode 100644
index 0000000000000..b66f2ba546f1d
--- /dev/null
+++ b/lld/test/ELF/riscv-relax-call-bad-isa-mapping-sym.s
@@ -0,0 +1,55 @@
+# REQUIRES: riscv
+## Verify that when R_RISCV_RELAX references an ISA mapping symbol whose
+## "$x<...>" suffix cannot be parsed as a normalized ISA string, the linker
+## emits a warning and falls back to the file-level EF_RISCV_RVC flag.
+
+# RUN: yaml2obj %s -o %t.o
+# RUN: ld.lld %t.o -Ttext=0x10000 -o %t 2>&1 | FileCheck %s --check-prefix=WARN
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases %t | FileCheck %s
+
+# WARN: warning: {{.*}}: R_RISCV_RELAX ISA mapping symbol '$xnotaparseablearchstring' has an unparseable ISA string:{{.*}}falling back to EF_RISCV_RVC
+
+## EF_RISCV_RVC is set on the object, so the fallback relaxes to c.j.
+# CHECK-LABEL: <compat>:
+# CHECK-NEXT: {{.*}}: c.j {{.*}} <compat_target>
+
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_REL
+ Machine: EM_RISCV
+ Flags: [ EF_RISCV_RVC ]
+Sections:
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ AddressAlign: 0x2
+ ## tail compat_target ; c.ret
+ Content: '17030000670003008280'
+ - Name: .rela.text
+ Type: SHT_RELA
+ Flags: [ SHF_INFO_LINK ]
+ Link: .symtab
+ Info: .text
+ AddressAlign: 0x8
+ Relocations:
+ - Offset: 0x0
+ Symbol: compat_target
+ Type: R_RISCV_CALL_PLT
+ - Offset: 0x0
+ Symbol: '$xnotaparseablearchstring'
+ Type: R_RISCV_RELAX
+Symbols:
+ - Name: '$xnotaparseablearchstring'
+ Section: .text
+ Binding: STB_LOCAL
+ Value: 0x0
+ - Name: compat
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x0
+ - Name: compat_target
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x8
diff --git a/lld/test/ELF/riscv-relax-call-isa-mapping-sym-compat.s b/lld/test/ELF/riscv-relax-call-isa-mapping-sym-compat.s
new file mode 100644
index 0000000000000..7bb817d9ad3a9
--- /dev/null
+++ b/lld/test/ELF/riscv-relax-call-isa-mapping-sym-compat.s
@@ -0,0 +1,30 @@
+# REQUIRES: riscv
+## Tests that, even without a .option arch directive, R_RISCV_RELAX still
+## references the initial "$x<InitialISA>" mapping symbol emitted before the
+## first instruction. For -mattr=+c,+relax that initial ISA includes the C
+## extension, so the linker picks c.j via the ISA-mapping path (not the
+## EF_RISCV_RVC fallback).
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax %s -o %t.o
+# RUN: llvm-readobj -r %t.o | FileCheck %s --check-prefix=RELOC
+# RUN: ld.lld %t.o -Ttext=0x10000 -o %t
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases %t | FileCheck %s
+
+## Confirm that even without .option arch, the object gets a concrete ISA
+## mapping symbol (not '-') on its R_RISCV_RELAX.
+# RELOC: R_RISCV_RELAX $xrv64i2p1_c2p0_zca1p0
+
+# CHECK-LABEL: <compat>:
+# CHECK-NEXT: {{.*}}: c.j {{.*}} <compat_target>
+
+## No .option arch directive. R_RISCV_RELAX still references the initial
+## "$x<InitialISA>" mapping symbol emitted before the first instruction, which
+## for -mattr=+c,+relax includes the C extension; the linker therefore picks
+## c.j via the ISA-mapping path (not the EF_RISCV_RVC fallback).
+.globl compat
+compat:
+ tail compat_target
+
+.globl compat_target
+compat_target:
+ ret
diff --git a/lld/test/ELF/riscv-relax-call-isa-mapping-sym-norvc.s b/lld/test/ELF/riscv-relax-call-isa-mapping-sym-norvc.s
new file mode 100644
index 0000000000000..e24694e696ec9
--- /dev/null
+++ b/lld/test/ELF/riscv-relax-call-isa-mapping-sym-norvc.s
@@ -0,0 +1,41 @@
+# REQUIRES: riscv
+## Tests that the .option norvc / .option rvc toggle drives per-region RVC
+## relaxation through ISA mapping symbols. The file has EF_RISCV_RVC set (+c),
+## but the norvc region emits an ISA mapping symbol without C, so the linker
+## must NOT use compressed jumps there. After .option rvc, C is back and c.j
+## is used again.
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax %s -o %t.o
+# RUN: ld.lld %t.o -Ttext=0x10000 -o %t
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases %t | FileCheck %s
+
+# CHECK-LABEL: <before_norvc>:
+# CHECK-NEXT: {{.*}}: c.j {{.*}} <target>
+
+# CHECK-LABEL: <norvc_region>:
+# CHECK-NEXT: {{.*}}: jal zero, {{.*}} <target>
+
+# CHECK-LABEL: <after_rvc>:
+# CHECK-NEXT: {{.*}}: c.j {{.*}} <target>
+
+## Toggle: start with RVC enabled (file has EF_RISCV_RVC via +c), disable via
+## .option norvc (emits ISA mapping symbol without C), then re-enable via
+## .option rvc (emits ISA mapping symbol with C).
+.option arch, rv64imafdc
+.globl before_norvc
+before_norvc:
+ tail target ## has C mapping symbol → c.j
+
+.option norvc
+.globl norvc_region
+norvc_region:
+ tail target ## no-C mapping symbol → jal zero (not c.j)
+
+.option rvc
+.globl after_rvc
+after_rvc:
+ tail target ## C mapping symbol restored → c.j
+
+.globl target
+target:
+ ret
diff --git a/lld/test/ELF/riscv-relax-call-isa-mapping-sym.s b/lld/test/ELF/riscv-relax-call-isa-mapping-sym.s
new file mode 100644
index 0000000000000..da4fb53cce222
--- /dev/null
+++ b/lld/test/ELF/riscv-relax-call-isa-mapping-sym.s
@@ -0,0 +1,40 @@
+# REQUIRES: riscv
+## Tests that R_RISCV_RELAX ISA mapping symbols ($x<isa-string>, emitted by
+## .option arch) control per-region RVC relaxation.
+## Within a single object that has EF_RISCV_RVC set at the file level:
+## - a region with an ISA-with-C mapping symbol MUST use compressed jumps.
+## - a region with an ISA-without-C mapping symbol must NOT use compressed jumps,
+## even though EF_RISCV_RVC is set for the whole file.
+
+## Build with +c,+relax so EF_RISCV_RVC is set at the file level.
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax %s -o %t.o
+# RUN: ld.lld %t.o -Ttext=0x10000 -o %t
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases %t | FileCheck %s
+
+## The call inside the rv64imafdc region should relax to compressed c.j.
+# CHECK-LABEL: <with_c>:
+# CHECK-NEXT: {{.*}}: c.j {{.*}} <target>
+
+## The call inside the rv64imafd region must NOT use a compressed jump,
+## even though EF_RISCV_RVC is set for the whole file.
+# CHECK-LABEL: <without_c>:
+# CHECK-NEXT: {{.*}}: jal zero, {{.*}} <target>
+
+## Region 1: .option arch sets ISA to rv64imafdc (C extension present).
+## The R_RISCV_RELAX for 'tail' will reference the $xrv64..._c2p0_... symbol.
+.option arch, rv64imafdc
+.globl with_c
+with_c:
+ tail target
+
+## Region 2: .option arch removes the C extension.
+## The R_RISCV_RELAX for 'tail' will reference the $xrv64..._zicsr... symbol
+## (no c2p0), preventing compressed-jump relaxation.
+.option arch, rv64imafd
+.globl without_c
+without_c:
+ tail target
+
+.globl target
+target:
+ ret
diff --git a/lld/test/ELF/riscv-relax-call-null-relax-sym.s b/lld/test/ELF/riscv-relax-call-null-relax-sym.s
new file mode 100644
index 0000000000000..66b5a53d0bf24
--- /dev/null
+++ b/lld/test/ELF/riscv-relax-call-null-relax-sym.s
@@ -0,0 +1,59 @@
+# REQUIRES: riscv
+## Backward-compatibility test for R_RISCV_RELAX with a null symbol (symbol
+## index 0, shown as '-' by llvm-readobj). Older assemblers always emit
+## R_RISCV_RELAX with a null symbol, so the linker must fall back to the
+## file-level EF_RISCV_RVC flag to decide whether compressed-jump relaxation
+## is available.
+
+# RUN: sed 's/EF_RISCV_FLAGS/EF_RISCV_RVC/' %s | yaml2obj -o %t.rvc.o
+# RUN: sed 's/EF_RISCV_FLAGS//' %s | yaml2obj -o %t.norvc.o
+
+# RUN: ld.lld %t.rvc.o -Ttext=0x10000 -o %t.rvc
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases %t.rvc | FileCheck %s --check-prefix=RVC
+
+# RUN: ld.lld %t.norvc.o -Ttext=0x10000 -o %t.norvc
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases %t.norvc | FileCheck %s --check-prefix=NORVC
+
+## EF_RISCV_RVC set: fallback enables c.j relaxation.
+# RVC-LABEL: <compat>:
+# RVC-NEXT: {{.*}}: c.j {{.*}} <compat_target>
+
+## EF_RISCV_RVC cleared: fallback picks rvc=false, so the relaxation lands on
+## uncompressed jal.
+# NORVC-LABEL: <compat>:
+# NORVC-NEXT: {{.*}}: jal zero, {{.*}} <compat_target>
+
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_REL
+ Machine: EM_RISCV
+ Flags: [ EF_RISCV_FLAGS ]
+Sections:
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ AddressAlign: 0x4
+ Content: '170300006700030067800000'
+ - Name: .rela.text
+ Type: SHT_RELA
+ Flags: [ SHF_INFO_LINK ]
+ Link: .symtab
+ Info: .text
+ AddressAlign: 0x8
+ Relocations:
+ - Offset: 0x0
+ Symbol: compat_target
+ Type: R_RISCV_CALL_PLT
+ - Offset: 0x0
+ Type: R_RISCV_RELAX
+Symbols:
+ - Name: compat
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x0
+ - Name: compat_target
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x8
More information about the llvm-commits
mailing list