[lld] [lld][ELF][RISCV] Add Zcmt table-jump relaxation support (PR #206338)
Ying Chen via llvm-commits
llvm-commits at lists.llvm.org
Sun Jun 28 07:38:30 PDT 2026
https://github.com/punkyc created https://github.com/llvm/llvm-project/pull/206338
Implement opt-in RISC-V Zcmt table-jump relaxation for lld.
The new `--riscv-relax-zcmt` option rewrites profitable relaxable
`R_RISCV_CALL`, `R_RISCV_CALL_PLT`, and `R_RISCV_JAL` sites to `cm.jt` or
`cm.jalt` after ordinary RISC-V relaxation reaches a fixed point. The linker
emits the psABI `.riscv.jvt` section, defines hidden `__jvt_base$` when a table
is selected, and updates output RISC-V attributes when linker-generated Zcmt
instructions are emitted.
The relaxation is enabled only for final non-position-independent executable
links. PIE, shared-object output, static PIE, and relocatable links are
disabled. Dynamically linked non-PIE executables are supported: a direct table
entry stores `sym.getVA() + addend`, while an `R_PLT_PC` entry stores
`sym.getPltVA() + addend`, preserving the executable PLT path for preemptible
symbols.
Candidate selection is payload-profitable only. Sites are grouped by semantic
target, addend, direct-vs-PLT target, and JT-vs-JALT kind. JT candidates use
indices 0..31 and JALT candidates use indices 32..255. If no positive net
payload saving is found, lld emits no `.riscv.jvt`, defines no `__jvt_base$`,
and rewrites nothing, matching the psABI rule that table-jump relaxation is
ignored when there is no additional gain. If an input explicitly references
`__jvt_base$` but no profitable table is selected, lld reports an error because
there is no generated JVT for the runtime to initialize.
The implementation also:
* emits `.riscv.jvt` as psABI `SHT_PROGBITS` with `SHF_ALLOC | SHF_EXECINSTR`,
minimum 64-byte alignment, and XLEN-sized entries;
* leaves CSR `jvt` initialization to the program/runtime;
* gates the transform on compatible RVC/Zcmt-capable inputs;
* rejects the incompatible Zcd/compressed-FP profile for this model;
* creates output RISC-V attributes when needed for linker-generated Zcmt code;
* removes stale call/JAL/relax relocations from `--emit-relocs` output for
rewritten sites.
The previous Zcmt lld work can be found in #77884, #163142, and #183450, that provide a valuable reference.
Per the LLVM AI tool policy, DOUBAO assisted with this PR: code correction check, the testcases. I have reviewed and understand all of it, ran the tests locally, and I am accountable for the contribution. The commits also carry an Assisted-by: DOUBAO trailer.
>From b71c13ff6072bd7250b2818e40d8150026bff66c Mon Sep 17 00:00:00 2001
From: punkyc <chenying at inchitech.com>
Date: Sun, 28 Jun 2026 16:27:10 +0800
Subject: [PATCH] Add Zcmt relaxation support
Implement --riscv-relax-zcmt for table jump relaxation, including
.riscv.jvt creation, __jvt_base$ handling, output attributes, mode/option
diagnostics, and focused ELF tests.
Assisted-by: DOUBAO
---
lld/ELF/Arch/RISCV.cpp | 593 +++++++++++++++++++++-
lld/ELF/Config.h | 1 +
lld/ELF/Driver.cpp | 6 +
lld/ELF/Options.td | 4 +
lld/test/ELF/riscv-zcmt-addend.s | 39 ++
lld/test/ELF/riscv-zcmt-attributes.s | 118 +++++
lld/test/ELF/riscv-zcmt-discard-script.s | 32 ++
lld/test/ELF/riscv-zcmt-emit-relocs.s | 31 ++
lld/test/ELF/riscv-zcmt-gates.s | 80 +++
lld/test/ELF/riscv-zcmt-jal-already-rvc.s | 41 ++
lld/test/ELF/riscv-zcmt-jvt-base.s | 60 +++
lld/test/ELF/riscv-zcmt-mixed-jt-jalt.s | 32 ++
lld/test/ELF/riscv-zcmt-no-profit.s | 20 +
lld/test/ELF/riscv-zcmt-norelax.s | 24 +
lld/test/ELF/riscv-zcmt-pic-disabled.s | 22 +
lld/test/ELF/riscv-zcmt-plt.s | 35 ++
lld/test/ELF/riscv-zcmt-relax.s | 59 +++
lld/test/ELF/riscv-zcmt-tie-order.s | 35 ++
lld/test/ELF/riscv-zcmt-vma-zero.s | 33 ++
lld/test/ELF/target-specific-options.s | 4 +-
20 files changed, 1260 insertions(+), 9 deletions(-)
create mode 100644 lld/test/ELF/riscv-zcmt-addend.s
create mode 100644 lld/test/ELF/riscv-zcmt-attributes.s
create mode 100644 lld/test/ELF/riscv-zcmt-discard-script.s
create mode 100644 lld/test/ELF/riscv-zcmt-emit-relocs.s
create mode 100644 lld/test/ELF/riscv-zcmt-gates.s
create mode 100644 lld/test/ELF/riscv-zcmt-jal-already-rvc.s
create mode 100644 lld/test/ELF/riscv-zcmt-jvt-base.s
create mode 100644 lld/test/ELF/riscv-zcmt-mixed-jt-jalt.s
create mode 100644 lld/test/ELF/riscv-zcmt-no-profit.s
create mode 100644 lld/test/ELF/riscv-zcmt-norelax.s
create mode 100644 lld/test/ELF/riscv-zcmt-pic-disabled.s
create mode 100644 lld/test/ELF/riscv-zcmt-plt.s
create mode 100644 lld/test/ELF/riscv-zcmt-relax.s
create mode 100644 lld/test/ELF/riscv-zcmt-tie-order.s
create mode 100644 lld/test/ELF/riscv-zcmt-vma-zero.s
diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index 576e7a4614572..24bc9b616a1ef 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -10,14 +10,17 @@
#include "OutputSections.h"
#include "RelocScan.h"
#include "Symbols.h"
+#include "SymbolTable.h"
#include "SyntheticSections.h"
#include "Target.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/ELFAttributes.h"
#include "llvm/Support/LEB128.h"
#include "llvm/Support/RISCVAttributeParser.h"
#include "llvm/Support/RISCVAttributes.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/TargetParser/RISCVISAInfo.h"
+#include <optional>
using namespace llvm;
using namespace llvm::object;
@@ -26,11 +29,63 @@ using namespace llvm::ELF;
using namespace lld;
using namespace lld::elf;
+static void addZcmtOutputAttributes(Ctx &ctx);
+static bool discardZcmtOutputAttributes(Ctx &ctx);
+static bool shouldCreateZcmtSyntheticSections(Ctx &ctx);
+
namespace {
+struct ZcmtJvtEntry {
+ const Symbol *sym = nullptr;
+ int64_t addend = 0;
+ bool usePlt = false;
+};
+
+class RISCVJvtSection final : public SyntheticSection {
+public:
+ explicit RISCVJvtSection(Ctx &ctx)
+ : SyntheticSection(ctx, ".riscv.jvt", SHT_PROGBITS,
+ SHF_ALLOC | SHF_EXECINSTR, 64) {
+ entsize = ctx.arg.wordsize;
+ }
+
+ size_t getSize() const override { return entries.size() * ctx.arg.wordsize; }
+ bool isNeeded() const override { return keep; }
+ void writeTo(uint8_t *buf) override {
+ for (const ZcmtJvtEntry &entry : entries) {
+ uint64_t va = 0;
+ if (entry.sym)
+ va = (entry.usePlt ? entry.sym->getPltVA(ctx)
+ : entry.sym->getVA(ctx)) +
+ entry.addend;
+ if (ctx.arg.is64) {
+ write64(ctx, buf, va);
+ buf += 8;
+ } else {
+ write32(ctx, buf, va);
+ buf += 4;
+ }
+ }
+ }
+
+ bool keep = false;
+ SmallVector<ZcmtJvtEntry, 0> entries;
+};
+
+struct ZcmtCandidateGroup {
+ Symbol *sym = nullptr;
+ int64_t addend = 0;
+ bool usePlt = false;
+ bool isJalt = false;
+ uint64_t benefit = 0;
+ uint64_t firstSeen = 0;
+ SmallVector<std::pair<InputSection *, uint64_t>, 0> sites;
+};
+
class RISCV final : public TargetInfo {
public:
RISCV(Ctx &);
+ void initTargetSpecificSections() override;
uint32_t calcEFlags() const override;
int64_t getImplicitAddend(const uint8_t *buf, RelType type) const override;
void writeGotHeader(uint8_t *buf) const override;
@@ -64,6 +119,20 @@ class RISCV final : public TargetInfo {
InputSection *baseSec = nullptr;
// r_offset and r_addend pairs.
SmallVector<std::pair<uint64_t, uint64_t>, 0> synthesizedAligns;
+
+ mutable std::unique_ptr<RISCVJvtSection> jvtSec;
+ mutable Defined *jvtSym = nullptr;
+ mutable DenseMap<std::pair<const InputSection *, uint64_t>, uint8_t>
+ zcmtSelectedSites;
+ mutable bool zcmtSelected = false;
+ mutable bool zcmtSelectionDone = false;
+ mutable bool zcmtWarnedDisabled = false;
+
+ bool zcmtEnabled() const;
+ std::optional<uint8_t> getZcmtIndex(const InputSection &sec,
+ uint64_t offset) const;
+ bool selectZcmtCandidates() const;
+ void discardEmptyJvt() const;
};
} // end anonymous namespace
@@ -74,6 +143,7 @@ class RISCV final : public TargetInfo {
#define INTERNAL_R_RISCV_GPREL_S 257
#define INTERNAL_R_RISCV_X0REL_I 258
#define INTERNAL_R_RISCV_X0REL_S 259
+#define INTERNAL_R_RISCV_ZCMT_JUMP 260
const uint64_t dtpOffset = 0x800;
@@ -128,6 +198,8 @@ static uint32_t setLO12_S(uint32_t insn, uint32_t imm) {
(extractBits(imm, 4, 0) << 7);
}
+static uint32_t getEFlags(Ctx &ctx, InputFile *f);
+
RISCV::RISCV(Ctx &ctx) : TargetInfo(ctx) {
copyRel = R_RISCV_COPY;
pltRel = R_RISCV_JUMP_SLOT;
@@ -158,6 +230,113 @@ RISCV::RISCV(Ctx &ctx) : TargetInfo(ctx) {
ipltEntrySize = 16;
}
+void RISCV::initTargetSpecificSections() {
+ if (!shouldCreateZcmtSyntheticSections(ctx))
+ return;
+ jvtSec = std::make_unique<RISCVJvtSection>(ctx);
+ jvtSec->keep = true;
+ ctx.inputSections.push_back(jvtSec.get());
+
+ if (Symbol *s = ctx.symtab->find("__jvt_base$")) {
+ if (s->isDefined() || s->isCommon()) {
+ Err(ctx) << "duplicate definition of __jvt_base$";
+ return;
+ }
+ ctx.synthesizedSymbols.push_back(s);
+ s->resolve(ctx, Defined{ctx, ctx.internalFile, StringRef(), STB_GLOBAL,
+ STV_HIDDEN, STT_NOTYPE, 0, 0, jvtSec.get()});
+ s->isUsedInRegularObj = true;
+ jvtSym = cast<Defined>(s);
+ }
+}
+
+static bool shouldCreateZcmtSyntheticSections(Ctx &ctx) {
+ return ctx.arg.relaxZcmt && !ctx.arg.shared && !ctx.arg.pie &&
+ !ctx.arg.relocatable && ctx.arg.relax;
+}
+
+static bool hasZcmtCompatibleProfile(Ctx &ctx, InputFile *file,
+ bool diagnose) {
+ for (InputSectionBase *sec : file->getSections()) {
+ if (!sec || sec == &InputSection::discarded ||
+ sec->type != SHT_RISCV_ATTRIBUTES)
+ continue;
+
+ RISCVAttributeParser parser;
+ if (Error e = parser.parse(sec->content(), llvm::endianness::little)) {
+ if (diagnose)
+ Warn(ctx) << "--riscv-relax-zcmt is disabled because " << sec
+ << " could not be parsed: " << std::move(e);
+ else
+ consumeError(std::move(e));
+ return false;
+ }
+
+ std::optional<StringRef> arch = parser.getAttributeString(RISCVAttrs::ARCH);
+ if (!arch)
+ continue;
+
+ auto maybeInfo = RISCVISAInfo::parseNormalizedArchString(*arch);
+ if (!maybeInfo) {
+ if (diagnose)
+ Warn(ctx) << "--riscv-relax-zcmt is disabled because " << sec << ": "
+ << *arch << ": " << maybeInfo.takeError();
+ else
+ consumeError(maybeInfo.takeError());
+ return false;
+ }
+
+ const RISCVISAInfo &info = **maybeInfo;
+ if (info.hasExtension("zcd")) {
+ if (diagnose)
+ Warn(ctx) << "--riscv-relax-zcmt is disabled because " << sec
+ << " advertises incompatible Zcd/compressed-FP profile";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool RISCV::zcmtEnabled() const {
+ if (!ctx.arg.relaxZcmt)
+ return false;
+
+ if (ctx.arg.shared || ctx.arg.pie) {
+ if (!zcmtWarnedDisabled) {
+ Warn(ctx) << "--riscv-relax-zcmt is disabled for PIE and shared links";
+ zcmtWarnedDisabled = true;
+ }
+ return false;
+ }
+
+ if (!ctx.arg.relax) {
+ if (!zcmtWarnedDisabled) {
+ Warn(ctx) << "--riscv-relax-zcmt requires --relax";
+ zcmtWarnedDisabled = true;
+ }
+ return false;
+ }
+
+ for (InputFile *file : ctx.objectFiles)
+ if ((getEFlags(ctx, file) & EF_RISCV_RVC) == 0) {
+ if (!zcmtWarnedDisabled) {
+ Warn(ctx) << "--riscv-relax-zcmt is disabled because " << file
+ << " is not marked RVC-capable";
+ zcmtWarnedDisabled = true;
+ }
+ return false;
+ }
+
+ for (InputFile *file : ctx.objectFiles)
+ if (!hasZcmtCompatibleProfile(ctx, file, !zcmtWarnedDisabled)) {
+ zcmtWarnedDisabled = true;
+ return false;
+ }
+
+ return true;
+}
+
static uint32_t getEFlags(Ctx &ctx, InputFile *f) {
if (ctx.arg.is64)
return cast<ObjFile<ELF64LE>>(f)->getObj().getHeader().e_flags;
@@ -697,6 +876,244 @@ static bool relaxable(ArrayRef<Relocation> relocs, size_t i) {
return i + 1 != relocs.size() && relocs[i + 1].type == R_RISCV_RELAX;
}
+static uint16_t encodeZcmtJump(uint8_t index) {
+ return 0xa002 | (uint16_t(index) << 2);
+}
+
+std::optional<uint8_t> RISCV::getZcmtIndex(const InputSection &sec,
+ uint64_t offset) const {
+ auto it = zcmtSelectedSites.find({&sec, offset});
+ if (it == zcmtSelectedSites.end())
+ return std::nullopt;
+ return it->second;
+}
+
+static std::optional<bool> getZcmtKind(const InputSection &sec,
+ const Relocation &r) {
+ uint32_t rd;
+ if (r.type == R_RISCV_JAL) {
+ uint32_t insn = read32le(sec.content().data() + r.offset);
+ rd = extractBits(insn, 11, 7);
+ } else {
+ uint64_t insnPair = read64le(sec.content().data() + r.offset);
+ rd = extractBits(insnPair, 32 + 11, 32 + 7);
+ }
+ if (rd == X_X0)
+ return false;
+ if (rd == X_RA)
+ return true;
+ return std::nullopt;
+}
+
+static bool sameZcmtGroup(const ZcmtCandidateGroup &group, const Symbol *sym,
+ int64_t addend, bool usePlt, bool isJalt) {
+ return group.sym == sym && group.addend == addend && group.usePlt == usePlt &&
+ group.isJalt == isJalt;
+}
+
+static uint64_t getZcmtBenefit(RelType origType, RelType relaxedType) {
+ if (origType == R_RISCV_CALL || origType == R_RISCV_CALL_PLT) {
+ if (relaxedType == R_RISCV_NONE)
+ return 6;
+ if (relaxedType == R_RISCV_JAL)
+ return 2;
+ return 0;
+ }
+ if (origType == R_RISCV_JAL &&
+ (relaxedType == R_RISCV_NONE || relaxedType == R_RISCV_JAL))
+ return 2;
+ return 0;
+}
+
+static void addZcmtCandidate(SmallVectorImpl<ZcmtCandidateGroup> &groups,
+ InputSection &sec, const Relocation &r,
+ uint64_t benefit, bool isJalt,
+ uint64_t ordinal) {
+ const bool usePlt = r.expr == R_PLT_PC;
+ for (ZcmtCandidateGroup &group : groups) {
+ if (!sameZcmtGroup(group, r.sym, r.addend, usePlt, isJalt))
+ continue;
+ group.benefit += benefit;
+ group.sites.push_back({&sec, r.offset});
+ return;
+ }
+
+ ZcmtCandidateGroup &group = groups.emplace_back();
+ group.sym = r.sym;
+ group.addend = r.addend;
+ group.usePlt = usePlt;
+ group.isJalt = isJalt;
+ group.benefit = benefit;
+ group.firstSeen = ordinal;
+ group.sites.push_back({&sec, r.offset});
+}
+
+bool RISCV::selectZcmtCandidates() const {
+ assert(jvtSec && "JVT synthetic section must exist before Zcmt selection");
+ zcmtSelectionDone = true;
+ zcmtSelectedSites.clear();
+ jvtSec->entries.clear();
+
+ SmallVector<ZcmtCandidateGroup, 0> jtGroups;
+ SmallVector<ZcmtCandidateGroup, 0> jaltGroups;
+ SmallVector<InputSection *, 0> storage;
+ uint64_t ordinal = 0;
+
+ for (OutputSection *osec : ctx.outputSections) {
+ if (!(osec->flags & SHF_EXECINSTR))
+ continue;
+ for (InputSection *sec : getInputSections(*osec, storage)) {
+ if (!sec->relaxAux || !sec->relaxAux->relocDeltas)
+ continue;
+
+ MutableArrayRef<Relocation> relocs = sec->relocs();
+ RelaxAux &aux = *sec->relaxAux;
+ for (auto [i, r] : llvm::enumerate(relocs)) {
+ if (r.type != R_RISCV_CALL && r.type != R_RISCV_CALL_PLT &&
+ r.type != R_RISCV_JAL)
+ continue;
+ if (!relaxable(relocs, i))
+ continue;
+ if (!r.sym || (r.sym->isUndefined() && !r.sym->isUndefWeak()))
+ continue;
+
+ std::optional<bool> kind = getZcmtKind(*sec, r);
+ if (!kind)
+ continue;
+
+ uint64_t benefit = getZcmtBenefit(r.type, aux.relocTypes[i]);
+ if (benefit == 0)
+ continue;
+
+ addZcmtCandidate(*kind ? jaltGroups : jtGroups, *sec, r, benefit,
+ *kind, ordinal++);
+ }
+ }
+ }
+
+ auto orderGroups = [](const ZcmtCandidateGroup &a,
+ const ZcmtCandidateGroup &b) {
+ if (a.benefit != b.benefit)
+ return a.benefit > b.benefit;
+ return a.firstSeen < b.firstSeen;
+ };
+ llvm::stable_sort(jtGroups, orderGroups);
+ llvm::stable_sort(jaltGroups, orderGroups);
+
+ SmallVector<uint64_t, 33> jtPrefix(1, 0);
+ for (size_t i = 0, e = std::min<size_t>(32, jtGroups.size()); i != e; ++i)
+ jtPrefix.push_back(jtPrefix.back() + jtGroups[i].benefit);
+ SmallVector<uint64_t, 225> jaltPrefix(1, 0);
+ for (size_t i = 0, e = std::min<size_t>(224, jaltGroups.size()); i != e; ++i)
+ jaltPrefix.push_back(jaltPrefix.back() + jaltGroups[i].benefit);
+
+ const int64_t entryWidth = ctx.arg.wordsize;
+ int64_t bestNet = 0;
+ size_t bestJt = 0;
+ size_t bestJalt = 0;
+ for (size_t k = 1, e = jtPrefix.size(); k != e; ++k) {
+ int64_t net = int64_t(jtPrefix[k]) - int64_t(k) * entryWidth;
+ if (net > bestNet) {
+ bestNet = net;
+ bestJt = k;
+ bestJalt = 0;
+ }
+ }
+
+ const size_t freeJt = std::min<size_t>(32, jtGroups.size());
+ for (size_t m = 1, e = jaltPrefix.size(); m != e; ++m) {
+ int64_t net = int64_t(jtPrefix[freeJt]) + int64_t(jaltPrefix[m]) -
+ int64_t(32 + m) * entryWidth;
+ if (net > bestNet) {
+ bestNet = net;
+ bestJt = freeJt;
+ bestJalt = m;
+ }
+ }
+
+ if (bestNet <= 0) {
+ if (jvtSym)
+ Err(ctx) << "__jvt_base$ is referenced but no profitable Zcmt jump "
+ "table was selected";
+ jvtSec->keep = false;
+ discardEmptyJvt();
+ return discardZcmtOutputAttributes(ctx);
+ }
+
+ auto addSelectedSites = [&](const ZcmtCandidateGroup &group, uint8_t index) {
+ for (auto [sec, offset] : group.sites)
+ zcmtSelectedSites[{sec, offset}] = index;
+ };
+ auto setEntry = [&](size_t index, const ZcmtCandidateGroup &group) {
+ jvtSec->entries[index] = {group.sym, group.addend, group.usePlt};
+ };
+
+ const size_t entryCount = bestJalt ? 32 + bestJalt : bestJt;
+ jvtSec->entries.resize(entryCount);
+ for (size_t i = 0; i != bestJt; ++i) {
+ addSelectedSites(jtGroups[i], i);
+ setEntry(i, jtGroups[i]);
+ }
+ for (size_t i = 0; i != bestJalt; ++i) {
+ const uint8_t index = 32 + i;
+ addSelectedSites(jaltGroups[i], index);
+ setEntry(index, jaltGroups[i]);
+ }
+
+ if (Symbol *s = ctx.symtab->find("__jvt_base$"))
+ if ((s->isDefined() || s->isCommon()) && s != jvtSym) {
+ Err(ctx) << "duplicate definition of __jvt_base$";
+ return false;
+ }
+ const bool hadEarlyJvtSym = jvtSym;
+ if (!jvtSym) {
+ jvtSym =
+ cast<Defined>(ctx.symtab->addSymbol(Defined{
+ ctx, ctx.internalFile, "__jvt_base$", STB_GLOBAL, STV_HIDDEN,
+ STT_NOTYPE, 0, 0, jvtSec.get()}));
+ }
+ if (ctx.in.symTab && !hadEarlyJvtSym)
+ ctx.in.symTab->addSymbol(jvtSym);
+
+ addZcmtOutputAttributes(ctx);
+ zcmtSelected = true;
+ return true;
+}
+
+void RISCV::discardEmptyJvt() const {
+ if (!jvtSec || !jvtSec->getParent())
+ return;
+
+ OutputSection *osec = jvtSec->getParent();
+ // A linker script may place .riscv.jvt into another output section such as
+ // .text. Only remove the parent when it is the orphan output section created
+ // solely for this synthetic input section.
+ const bool orphanJvt =
+ osec->name == ".riscv.jvt" &&
+ llvm::is_contained(ctx.script->orphanSections, jvtSec.get());
+ for (SectionCommand *cmd : osec->commands) {
+ auto *isd = dyn_cast<InputSectionDescription>(cmd);
+ if (!isd)
+ continue;
+ llvm::erase(isd->sectionBases, jvtSec.get());
+ llvm::erase(isd->sections, jvtSec.get());
+ }
+ llvm::erase(ctx.inputSections, jvtSec.get());
+ llvm::erase_if(ctx.script->orphanSections,
+ [&](const InputSectionBase *sec) { return sec == jvtSec.get(); });
+ jvtSec->parent = nullptr;
+ if (orphanJvt) {
+ llvm::erase(ctx.outputSections, osec);
+ llvm::erase_if(ctx.script->sectionCommands, [&](const SectionCommand *cmd) {
+ if (auto *osd = dyn_cast<OutputDesc>(cmd))
+ return &osd->osec == osec;
+ return false;
+ });
+ for (auto [i, sec] : llvm::enumerate(ctx.outputSections))
+ sec->sectionIndex = i + 1;
+ }
+}
+
static void tlsdescToIe(Ctx &ctx, uint8_t *loc, const Relocation &rel,
uint64_t val) {
switch (rel.type) {
@@ -755,6 +1172,7 @@ void RISCV::relocateAlloc(InputSection &sec, uint8_t *buf) const {
case R_RISCV_ALIGN:
case R_RISCV_RELAX:
case R_RISCV_TPREL_ADD:
+ case INTERNAL_R_RISCV_ZCMT_JUMP:
continue;
case R_RISCV_TLSDESC_HI20:
if (rel.expr == R_TLSDESC_PC) {
@@ -1025,8 +1443,25 @@ static bool relax(Ctx &ctx, int pass, InputSection &sec) {
}
break;
}
+ case R_RISCV_JAL:
+ if (std::optional<uint8_t> index =
+ static_cast<const RISCV &>(*ctx.target).getZcmtIndex(sec,
+ r.offset)) {
+ sec.relaxAux->relocTypes[i] = INTERNAL_R_RISCV_ZCMT_JUMP;
+ sec.relaxAux->writes.push_back(encodeZcmtJump(*index));
+ remove = 2;
+ }
+ break;
case R_RISCV_CALL:
case R_RISCV_CALL_PLT:
+ if (std::optional<uint8_t> index =
+ static_cast<const RISCV &>(*ctx.target).getZcmtIndex(sec,
+ r.offset)) {
+ sec.relaxAux->relocTypes[i] = INTERNAL_R_RISCV_ZCMT_JUMP;
+ sec.relaxAux->writes.push_back(encodeZcmtJump(*index));
+ remove = 6;
+ break;
+ }
// Prevent oscillation between states by disallowing the increment of
// `remove` after a few passes. The previous `remove` value is
// `cur-delta`.
@@ -1106,6 +1541,17 @@ bool RISCV::relaxOnce(int pass) const {
if (pass == 0)
initSymbolAnchors(ctx);
+ const bool enableZcmt = zcmtEnabled();
+ if (!enableZcmt && jvtSec && jvtSec->keep) {
+ if (jvtSym)
+ Err(ctx) << "__jvt_base$ is referenced but --riscv-relax-zcmt could not "
+ "emit a jump table";
+ jvtSec->keep = false;
+ discardEmptyJvt();
+ discardZcmtOutputAttributes(ctx);
+ zcmtSelectionDone = true;
+ }
+
SmallVector<InputSection *, 0> storage;
bool changed = false;
for (OutputSection *osec : ctx.outputSections) {
@@ -1115,6 +1561,8 @@ bool RISCV::relaxOnce(int pass) const {
if (sec->relaxAux)
changed |= relax(ctx, pass, *sec);
}
+ if (!changed && enableZcmt && !zcmtSelectionDone)
+ changed = selectZcmtCandidates();
return changed;
}
@@ -1310,6 +1758,10 @@ void RISCV::finalizeRelax(int passes) const {
skip = 4;
write32le(p, aux.writes[writesIdx++]);
break;
+ case INTERNAL_R_RISCV_ZCMT_JUMP:
+ skip = 2;
+ write16le(p, aux.writes[writesIdx++]);
+ break;
case R_RISCV_32:
// Used by relaxTlsLe to write a uint32_t then suppress the handling
// in relocateAlloc.
@@ -1340,6 +1792,21 @@ void RISCV::finalizeRelax(int passes) const {
} while (++i != e && rels[i].offset == cur);
delta = aux.relocDeltas[i - 1];
}
+ if (zcmtSelected) {
+ bool eraseNextRelax = false;
+ llvm::erase_if(sec->relocations, [&](const Relocation &rel) {
+ if (rel.type == INTERNAL_R_RISCV_ZCMT_JUMP) {
+ eraseNextRelax = true;
+ return true;
+ }
+ if (eraseNextRelax && rel.type == R_RISCV_RELAX) {
+ eraseNextRelax = false;
+ return true;
+ }
+ eraseNextRelax = false;
+ return false;
+ });
+ }
}
}
}
@@ -1358,12 +1825,16 @@ class RISCVAttributesSection final : public SyntheticSection {
}
size_t getSize() const override { return size; }
+ bool isNeeded() const override { return keep; }
void writeTo(uint8_t *buf) override;
+ void updateSize();
static constexpr StringRef vendor = "riscv";
DenseMap<unsigned, unsigned> intAttr;
DenseMap<unsigned, StringRef> strAttr;
size_t size = 0;
+ bool keep = true;
+ bool createdForZcmt = false;
};
} // namespace
@@ -1571,17 +2042,113 @@ mergeAttributesSection(Ctx &ctx,
}
}
+ merged.updateSize();
+ return &merged;
+}
+
+static void addZcmtOutputAttributes(Ctx &ctx) {
+ if (!ctx.in.riscvAttributes)
+ return;
+
+ auto &attrs = static_cast<RISCVAttributesSection &>(*ctx.in.riscvAttributes);
+ unsigned xlen = ctx.arg.is64 ? 64 : 32;
+ RISCVISAUtils::OrderedExtensionMap exts;
+ if (StringRef arch = attrs.strAttr.lookup(RISCVAttrs::ARCH);
+ !arch.empty()) {
+ auto maybeInfo = RISCVISAInfo::parseNormalizedArchString(arch);
+ if (!maybeInfo) {
+ Err(ctx) << ".riscv.attributes: " << arch << ": "
+ << maybeInfo.takeError();
+ return;
+ }
+ RISCVISAInfo &info = **maybeInfo;
+ xlen = info.getXLen();
+ exts = info.getExtensions();
+ }
+
+ auto addExt = [&](StringRef name, RISCVISAUtils::ExtensionVersion version) {
+ auto [it, inserted] = exts.try_emplace(name.str(), version);
+ if (!inserted && std::tie(it->second.Major, it->second.Minor) <
+ std::tie(version.Major, version.Minor))
+ it->second = version;
+ };
+ addExt("zca", {1, 0});
+ addExt("zicsr", {2, 0});
+ addExt("zcmt", {1, 0});
+
+ if (auto result = RISCVISAInfo::createFromExtMap(xlen, exts)) {
+ attrs.strAttr[RISCVAttrs::ARCH] = ctx.saver.save((*result)->toString());
+ attrs.updateSize();
+ } else {
+ Err(ctx) << result.takeError();
+ }
+}
+
+static bool discardZcmtOutputAttributes(Ctx &ctx) {
+ if (!ctx.in.riscvAttributes)
+ return false;
+
+ auto &attrs = static_cast<RISCVAttributesSection &>(*ctx.in.riscvAttributes);
+ // Drop the placeholder that was kept only so late Zcmt selection could
+ // populate attributes if it emitted table-jump instructions.
+ if (!attrs.createdForZcmt || attrs.size != 0)
+ return false;
+
+ attrs.keep = false;
+ OutputSection *osec = attrs.getParent();
+ if (!osec)
+ return false;
+
+ const bool orphanAttrs =
+ osec->name == ".riscv.attributes" &&
+ llvm::is_contained(ctx.script->orphanSections, &attrs);
+ for (SectionCommand *cmd : osec->commands) {
+ auto *isd = dyn_cast<InputSectionDescription>(cmd);
+ if (!isd)
+ continue;
+ llvm::erase(isd->sectionBases, &attrs);
+ llvm::erase(isd->sections, &attrs);
+ }
+ llvm::erase(ctx.inputSections, &attrs);
+ llvm::erase_if(ctx.script->orphanSections,
+ [&](const InputSectionBase *sec) { return sec == &attrs; });
+ attrs.parent = nullptr;
+
+ if (!orphanAttrs)
+ return false;
+
+ // Orphan SHT_RISCV_ATTRIBUTES sections get an automatic PT_RISCV_ATTRIBUTES.
+ llvm::erase_if(ctx.phdrs, [&](const std::unique_ptr<PhdrEntry> &phdr) {
+ return phdr->p_type == PT_RISCV_ATTRIBUTES && phdr->firstSec == osec &&
+ phdr->lastSec == osec;
+ });
+ if (ctx.out.programHeaders)
+ ctx.out.programHeaders->size =
+ (ctx.arg.is64 ? sizeof(Elf64_Phdr) : sizeof(Elf32_Phdr)) *
+ ctx.phdrs.size();
+
+ llvm::erase(ctx.outputSections, osec);
+ llvm::erase_if(ctx.script->sectionCommands, [&](const SectionCommand *cmd) {
+ if (auto *osd = dyn_cast<OutputDesc>(cmd))
+ return &osd->osec == osec;
+ return false;
+ });
+ for (auto [i, sec] : llvm::enumerate(ctx.outputSections))
+ sec->sectionIndex = i + 1;
+ return true;
+}
+
+void RISCVAttributesSection::updateSize() {
// The total size of headers: format-version [ <section-length> "vendor-name"
// [ <file-tag> <size>.
- size_t size = 5 + merged.vendor.size() + 1 + 5;
- for (auto &attr : merged.intAttr)
+ size_t newSize = 5 + vendor.size() + 1 + 5;
+ for (auto &attr : intAttr)
if (attr.second != 0)
- size += getULEB128Size(attr.first) + getULEB128Size(attr.second);
- for (auto &attr : merged.strAttr)
+ newSize += getULEB128Size(attr.first) + getULEB128Size(attr.second);
+ for (auto &attr : strAttr)
if (!attr.second.empty())
- size += getULEB128Size(attr.first) + attr.second.size() + 1;
- merged.size = size;
- return &merged;
+ newSize += getULEB128Size(attr.first) + attr.second.size() + 1;
+ size = newSize;
}
void RISCVAttributesSection::writeTo(uint8_t *buf) {
@@ -1619,8 +2186,18 @@ void elf::mergeRISCVAttributesSections(Ctx &ctx) {
llvm::find_if(ctx.inputSections,
[](auto *s) { return s->type == SHT_RISCV_ATTRIBUTES; }) -
ctx.inputSections.begin();
- if (place == ctx.inputSections.size())
+ if (place == ctx.inputSections.size()) {
+ if (shouldCreateZcmtSyntheticSections(ctx)) {
+ // Zcmt selection runs after output sections are created. Insert an empty
+ // attributes section now so it can be populated if relaxation emits Zcmt.
+ ctx.in.riscvAttributes = std::make_unique<RISCVAttributesSection>(ctx);
+ auto &attrs =
+ static_cast<RISCVAttributesSection &>(*ctx.in.riscvAttributes);
+ attrs.createdForZcmt = true;
+ ctx.inputSections.push_back(&attrs);
+ }
return;
+ }
// Extract all SHT_RISCV_ATTRIBUTES sections into `sections`.
SmallVector<InputSectionBase *, 0> sections;
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index 12b16ded61fca..fa054de5f8de8 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -397,6 +397,7 @@ struct Config {
bool rejectMismatch;
bool relax;
bool relaxGP;
+ bool relaxZcmt;
bool relocatable;
bool resolveGroups;
bool relrGlibc = false;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 7ec7dfcae6bca..a326df2dd4281 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -381,6 +381,8 @@ static void checkOptions(Ctx &ctx) {
if (ctx.arg.emachine != EM_RISCV) {
if (ctx.arg.relaxGP)
ErrAlways(ctx) << "--relax-gp is only supported on RISC-V targets";
+ if (ctx.arg.relaxZcmt)
+ ErrAlways(ctx) << "--riscv-relax-zcmt is only supported on RISC-V targets";
if (ctx.arg.zZicfilpUnlabeledReport != ReportPolicy::None)
ErrAlways(ctx) << "-z zicfilip-unlabeled-report is only supported on "
"RISC-V targets";
@@ -429,6 +431,8 @@ static void checkOptions(Ctx &ctx) {
ErrAlways(ctx) << "-r and --debug-names may not be used together";
if (!ctx.arg.zSectionHeader)
ErrAlways(ctx) << "-r and -z nosectionheader may not be used together";
+ if (ctx.arg.emachine == EM_RISCV && ctx.arg.relaxZcmt)
+ Warn(ctx) << "--riscv-relax-zcmt is disabled for relocatable links";
}
if (ctx.arg.executeOnly) {
@@ -1544,6 +1548,8 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
ctx.arg.rejectMismatch = !args.hasArg(OPT_no_warn_mismatch);
ctx.arg.relax = args.hasFlag(OPT_relax, OPT_no_relax, true);
ctx.arg.relaxGP = args.hasFlag(OPT_relax_gp, OPT_no_relax_gp, false);
+ ctx.arg.relaxZcmt =
+ args.hasFlag(OPT_riscv_relax_zcmt, OPT_no_riscv_relax_zcmt, false);
ctx.arg.rpath = getRpath(args);
ctx.arg.relocatable = args.hasArg(OPT_relocatable);
ctx.arg.resolveGroups =
diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td
index 64c42eb49607d..923a1e06db18b 100644
--- a/lld/ELF/Options.td
+++ b/lld/ELF/Options.td
@@ -426,6 +426,10 @@ defm relax_gp: BB<"relax-gp",
"Enable global pointer relaxation",
"Disable global pointer relaxation (default)">;
+defm riscv_relax_zcmt: BB<"riscv-relax-zcmt",
+ "Enable experimental RISC-V Zcmt table-jump relaxation",
+ "Disable experimental RISC-V Zcmt table-jump relaxation (default)">;
+
defm remap_inputs: EEq<"remap-inputs",
"Remap input files matching <from-glob> to <to-file>">,
MetaVarName<"<from-glob>=<to-file>">;
diff --git a/lld/test/ELF/riscv-zcmt-addend.s b/lld/test/ELF/riscv-zcmt-addend.s
new file mode 100644
index 0000000000000..b64ddc75949c2
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-addend.s
@@ -0,0 +1,39 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax %s -o %t.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt %t.o -o %t
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases %t | FileCheck %s
+
+# CHECK-LABEL: <_start>:
+# CHECK-NEXT: cm.jt 0x0
+# CHECK-NEXT: cm.jt 0x0
+# CHECK-NEXT: cm.jt 0x0
+# CHECK-NEXT: cm.jt 0x0
+# CHECK-NEXT: cm.jt 0x0
+# CHECK-NEXT: cm.jt 0x1
+# CHECK-NEXT: cm.jt 0x1
+# CHECK-NEXT: cm.jt 0x1
+# CHECK-NEXT: cm.jt 0x1
+# CHECK-NEXT: cm.jt 0x1
+# CHECK-NOT: cm.jt
+# CHECK-LABEL: <callee>:
+
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
+ .rept 5
+1:
+ auipc zero, 0
+ jalr zero, zero, 0
+ .reloc 1b, R_RISCV_CALL_PLT, callee+4
+ .reloc 1b, R_RISCV_RELAX
+ .endr
+ .space 4096
+callee:
+ nop
+ nop
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-attributes.s b/lld/test/ELF/riscv-zcmt-attributes.s
new file mode 100644
index 0000000000000..dbe90c9c0df1d
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-attributes.s
@@ -0,0 +1,118 @@
+# REQUIRES: riscv
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax noattr.s -o noattr.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt noattr.o -o noattr
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases noattr | FileCheck %s --check-prefix=JUMP
+# RUN: llvm-readelf -S -s noattr | FileCheck %s --check-prefix=HAS-JVT
+# RUN: llvm-readelf -A noattr | FileCheck %s --check-prefix=ATTR
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax noattr-nocandidate.s -o noattr-nocandidate.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt noattr-nocandidate.o -o noattr-nocandidate
+# RUN: llvm-readelf -h -S -l -s -A noattr-nocandidate | FileCheck %s \
+# RUN: --check-prefix=NO-ATTR --implicit-check-not=.riscv.attributes \
+# RUN: --implicit-check-not=RISCV_ATTRIBUTES --implicit-check-not=__jvt_base$ \
+# RUN: --implicit-check-not="TagName: arch"
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax nozcmt.s -o nozcmt.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt nozcmt.o -o nozcmt
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases nozcmt | FileCheck %s --check-prefix=JUMP
+# RUN: llvm-readelf -S -s nozcmt | FileCheck %s --check-prefix=HAS-JVT
+# RUN: llvm-readelf -A nozcmt | FileCheck %s --check-prefix=ATTR
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax candidate.s -o candidate.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax support-nozcmt.s -o support-nozcmt.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt candidate.o support-nozcmt.o -o mixed
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases mixed | FileCheck %s --check-prefix=JUMP
+# RUN: llvm-readelf -S -s mixed | FileCheck %s --check-prefix=HAS-JVT
+# RUN: llvm-readelf -A mixed | FileCheck %s --check-prefix=ATTR
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+relax compatible-nocandidate.s -o compatible-nocandidate.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt compatible-nocandidate.o -o compatible-nocandidate
+# RUN: llvm-readelf -S -s compatible-nocandidate | FileCheck /dev/null \
+# RUN: --implicit-check-not=.riscv.jvt --implicit-check-not=__jvt_base$
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax zcd.s -o zcd.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt zcd.o -o zcd 2>&1 | FileCheck %s --check-prefix=ZCD
+# RUN: llvm-readelf -S -s zcd | FileCheck /dev/null \
+# RUN: --implicit-check-not=.riscv.jvt --implicit-check-not=__jvt_base$
+
+# JUMP: cm.jt
+# HAS-JVT: .riscv.jvt
+# HAS-JVT: __jvt_base$
+# ATTR: TagName: arch
+# ATTR-NEXT: Value:
+# ATTR-SAME: zicsr
+# ATTR-SAME: zca
+# ATTR-SAME: zcmt
+# NO-ATTR: ELF Header:
+# ZCD: warning: --riscv-relax-zcmt is disabled because
+# ZCD-SAME: advertises incompatible Zcd/compressed-FP profile
+
+#--- noattr.s
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
+ .space 4096
+callee:
+ ret
+
+#--- noattr-nocandidate.s
+.option rvc
+.globl _start
+_start:
+ ret
+
+#--- nozcmt.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
+ .space 4096
+callee:
+ ret
+
+#--- candidate.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
+ .space 4096
+callee:
+ ret
+
+#--- support-nozcmt.s
+.attribute arch, "rv64i2p1_zca1p0"
+.option rvc
+.globl support
+support:
+ ret
+
+#--- compatible-nocandidate.s
+.attribute arch, "rv64i2p1_zca1p0"
+.option rvc
+.globl _start
+_start:
+ ret
+
+#--- zcd.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcd1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
+ .space 4096
+callee:
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-discard-script.s b/lld/test/ELF/riscv-zcmt-discard-script.s
new file mode 100644
index 0000000000000..36a1ff980ebc5
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-discard-script.s
@@ -0,0 +1,32 @@
+# REQUIRES: riscv
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax a.s -o a.o
+# RUN: ld.lld -T lds --riscv-relax-zcmt a.o -o a
+# RUN: llvm-readelf -S -s a | FileCheck %s --check-prefix=SEC \
+# RUN: --implicit-check-not=.riscv.jvt --implicit-check-not=__jvt_base$
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases a | FileCheck %s \
+# RUN: --check-prefix=DIS --implicit-check-not=cm.jt
+
+# SEC: .text
+# SEC-DAG: callee
+# SEC-DAG: _start
+
+# DIS-LABEL: <_start>:
+# DIS-NEXT: jal zero, {{.*}} <callee>
+
+#--- lds
+ENTRY(_start)
+SECTIONS {
+ .text : { *(.riscv.jvt) *(.text) }
+}
+
+#--- a.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ tail callee
+ .space 4096
+callee:
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-emit-relocs.s b/lld/test/ELF/riscv-zcmt-emit-relocs.s
new file mode 100644
index 0000000000000..a2935b6be1fa7
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-emit-relocs.s
@@ -0,0 +1,31 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax %s -o %t.o
+# RUN: ld.lld -e _start --emit-relocs --riscv-relax-zcmt %t.o -o %t
+# RUN: llvm-readelf -r %t | FileCheck %s --check-prefix=RELOCS \
+# RUN: --implicit-check-not=R_RISCV_CALL \
+# RUN: --implicit-check-not=R_RISCV_CALL_PLT \
+# RUN: --implicit-check-not=R_RISCV_JAL \
+# RUN: --implicit-check-not=R_RISCV_RELAX
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases %t | FileCheck %s \
+# RUN: --check-prefix=DIS
+
+# RELOCS: Relocation section '.rela.text'
+
+# DIS-LABEL: <_start>:
+# DIS-NEXT: cm.jt 0
+# DIS-NEXT: cm.jt 0
+# DIS-NEXT: cm.jt 0
+# DIS-NEXT: cm.jt 0
+# DIS-NEXT: cm.jt 0
+
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
+ .space 4096
+callee:
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-gates.s b/lld/test/ELF/riscv-zcmt-gates.s
new file mode 100644
index 0000000000000..05cccd9b40426
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-gates.s
@@ -0,0 +1,80 @@
+# REQUIRES: riscv
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax zcmt.s -o zcmt.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax norvc.s -o norvc.o
+
+# RUN: ld.lld -e _start --riscv-relax-zcmt zcmt.o -o enabled
+# RUN: llvm-readelf -S -s enabled | FileCheck %s --check-prefix=HAS-JVT
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases enabled \
+# RUN: | FileCheck %s --check-prefix=ZCMT
+
+# RUN: ld.lld -e _start zcmt.o -o default
+# RUN: llvm-readelf -S -s default | FileCheck /dev/null \
+# RUN: --implicit-check-not=.riscv.jvt --implicit-check-not=__jvt_base$
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases default \
+# RUN: | FileCheck %s --check-prefix=NOZCMT \
+# RUN: --implicit-check-not=cm.jt --implicit-check-not=cm.jalt
+
+# RUN: ld.lld -e _start --riscv-relax-zcmt --no-riscv-relax-zcmt zcmt.o -o no-zcmt
+# RUN: llvm-readelf -S -s no-zcmt | FileCheck /dev/null \
+# RUN: --implicit-check-not=.riscv.jvt --implicit-check-not=__jvt_base$
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases no-zcmt \
+# RUN: | FileCheck %s --check-prefix=NOZCMT \
+# RUN: --implicit-check-not=cm.jt --implicit-check-not=cm.jalt
+
+# RUN: ld.lld -e _start --no-relax --riscv-relax-zcmt zcmt.o -o no-relax 2>&1 \
+# RUN: | FileCheck %s --check-prefix=NO-RELAX-WARN
+# RUN: llvm-readelf -S -s no-relax | FileCheck /dev/null \
+# RUN: --implicit-check-not=.riscv.jvt --implicit-check-not=__jvt_base$
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases no-relax \
+# RUN: | FileCheck %s --check-prefix=NO-RELAX-DIS \
+# RUN: --implicit-check-not=cm.jt --implicit-check-not=cm.jalt
+
+# RUN: ld.lld -e _start --riscv-relax-zcmt norvc.o -o norvc 2>&1 \
+# RUN: | FileCheck %s --check-prefix=NORVC-WARN
+# RUN: llvm-readelf -S -s norvc | FileCheck /dev/null \
+# RUN: --implicit-check-not=.riscv.jvt --implicit-check-not=__jvt_base$
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases norvc \
+# RUN: | FileCheck %s --check-prefix=NOZCMT \
+# RUN: --implicit-check-not=cm.jt --implicit-check-not=cm.jalt
+
+# HAS-JVT: .riscv.jvt
+# HAS-JVT: __jvt_base$
+
+# ZCMT-LABEL: <_start>:
+# ZCMT-COUNT-5: cm.jt 0x0
+# ZCMT-NOT: cm.jt
+# ZCMT-LABEL: <callee>:
+
+# NOZCMT-LABEL: <_start>:
+# NOZCMT-NEXT: jal zero, {{.*}} <callee>
+
+# NO-RELAX-WARN: warning: --riscv-relax-zcmt requires --relax
+# NO-RELAX-DIS-LABEL: <_start>:
+# NO-RELAX-DIS-NEXT: auipc
+# NO-RELAX-DIS-NEXT: jalr zero, {{.*}} <callee>
+
+# NORVC-WARN: warning: --riscv-relax-zcmt is disabled because norvc.o is not marked RVC-capable
+
+#--- zcmt.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
+ .space 4096
+callee:
+ ret
+
+#--- norvc.s
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
+ .space 4096
+callee:
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-jal-already-rvc.s b/lld/test/ELF/riscv-zcmt-jal-already-rvc.s
new file mode 100644
index 0000000000000..331a57e8eca01
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-jal-already-rvc.s
@@ -0,0 +1,41 @@
+# REQUIRES: riscv
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax tail64.s -o tail64.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt tail64.o -o tail64
+# RUN: llvm-readelf -S -s tail64 | FileCheck /dev/null \
+# RUN: --implicit-check-not=.riscv.jvt --implicit-check-not=__jvt_base$
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases tail64 \
+# RUN: | FileCheck /dev/null --implicit-check-not=cm.jt \
+# RUN: --implicit-check-not=cm.jalt
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+c,+zcmt,+relax call32.s -o call32.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt call32.o -o call32
+# RUN: llvm-readelf -S -s call32 | FileCheck /dev/null \
+# RUN: --implicit-check-not=.riscv.jvt --implicit-check-not=__jvt_base$
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases call32 \
+# RUN: | FileCheck /dev/null --implicit-check-not=cm.jt \
+# RUN: --implicit-check-not=cm.jalt
+
+#--- tail64.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
+callee:
+ ret
+
+#--- call32.s
+.attribute arch, "rv32i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 67
+ call callee
+ .endr
+callee:
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-jvt-base.s b/lld/test/ELF/riscv-zcmt-jvt-base.s
new file mode 100644
index 0000000000000..d76e4817ae939
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-jvt-base.s
@@ -0,0 +1,60 @@
+# REQUIRES: riscv
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax duplicate.s -o duplicate.o
+# RUN: not ld.lld -e _start --riscv-relax-zcmt duplicate.o -o duplicate 2>&1 | FileCheck %s --check-prefix=DUP
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax ref-profit.s -o ref-profit.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt ref-profit.o -o ref-profit
+# RUN: llvm-readelf -s ref-profit | FileCheck %s --check-prefix=REF --implicit-check-not=__jvt_base$
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax ref-noprofit.s -o ref-noprofit.o
+# RUN: not ld.lld -e _start --riscv-relax-zcmt ref-noprofit.o -o ref-noprofit 2>&1 | FileCheck %s --check-prefix=REF-NOPROFIT
+
+# DUP: error: duplicate definition of __jvt_base$
+# REF: __jvt_base$
+# REF-NOPROFIT: error: __jvt_base$ is referenced but no profitable Zcmt jump table was selected
+
+#--- duplicate.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+.globl __jvt_base$
+__jvt_base$:
+ nop
+_start:
+ .rept 5
+ tail callee
+ .endr
+ .space 4096
+callee:
+ ret
+
+#--- ref-profit.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+.globl __jvt_base$
+_start:
+ la t0, __jvt_base$
+ csrw jvt, t0
+ .rept 5
+ tail callee
+ .endr
+ .space 4096
+callee:
+ ret
+
+#--- ref-noprofit.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+.globl __jvt_base$
+_start:
+ la t0, __jvt_base$
+ csrw jvt, t0
+ tail callee
+ .space 4096
+callee:
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-mixed-jt-jalt.s b/lld/test/ELF/riscv-zcmt-mixed-jt-jalt.s
new file mode 100644
index 0000000000000..94d1f57ffa162
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-mixed-jt-jalt.s
@@ -0,0 +1,32 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+c,+zcmt,+relax %s -o %t.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt %t.o -o %t
+# RUN: llvm-readelf -S -s %t | FileCheck %s --check-prefix=SEC
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases %t | FileCheck %s --check-prefix=DIS
+
+# SEC: .riscv.jvt
+# SEC-SAME: 000084
+# SEC: __jvt_base$
+# DIS-LABEL: <_start>:
+# DIS-NEXT: cm.jt 0x0
+# DIS-NEXT: cm.jt 0x0
+# DIS-NEXT: cm.jt 0x0
+# DIS-COUNT-67: cm.jalt 0x20
+# DIS-NOT: cm.jt
+# DIS-NOT: cm.jalt
+# DIS-LABEL: <callee>:
+
+.attribute arch, "rv32i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 3
+ tail callee
+ .endr
+ .rept 67
+ call callee
+ .endr
+ .space 4096
+callee:
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-no-profit.s b/lld/test/ELF/riscv-zcmt-no-profit.s
new file mode 100644
index 0000000000000..3f030bb50ca4f
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-no-profit.s
@@ -0,0 +1,20 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax %s -o %t.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt %t.o -o %t
+# RUN: llvm-readelf -S -s %t | FileCheck /dev/null \
+# RUN: --implicit-check-not=.riscv.jvt --implicit-check-not=__jvt_base$
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases %t | FileCheck %s \
+# RUN: --check-prefix=DIS --implicit-check-not=cm.jt
+
+# DIS-LABEL: <_start>:
+# DIS-NEXT: jal zero, {{.*}} <callee>
+
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ tail callee
+ .space 4096
+callee:
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-norelax.s b/lld/test/ELF/riscv-zcmt-norelax.s
new file mode 100644
index 0000000000000..71cc071c958c8
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-norelax.s
@@ -0,0 +1,24 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax %s -o %t.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt %t.o -o %t
+# RUN: llvm-readelf -S -s %t | FileCheck /dev/null \
+# RUN: --implicit-check-not=.riscv.jvt --implicit-check-not=__jvt_base$
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases %t | FileCheck %s \
+# RUN: --check-prefix=DIS --implicit-check-not=cm.jt
+
+# DIS-LABEL: <_start>:
+# DIS-NEXT: auipc
+# DIS-NEXT: jalr zero, {{.*}} <callee>
+
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option norelax
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
+ .space 4096
+callee:
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-pic-disabled.s b/lld/test/ELF/riscv-zcmt-pic-disabled.s
new file mode 100644
index 0000000000000..236d24feb1786
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-pic-disabled.s
@@ -0,0 +1,22 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax %s -o %t.o
+# RUN: ld.lld -pie -e _start --riscv-relax-zcmt %t.o -o %t.pie 2>&1 | FileCheck %s --check-prefix=PIC-WARN
+# RUN: ld.lld -shared --riscv-relax-zcmt %t.o -o %t.so 2>&1 | FileCheck %s --check-prefix=PIC-WARN
+# RUN: ld.lld -r --riscv-relax-zcmt %t.o -o %t.r 2>&1 | FileCheck %s --check-prefix=REL-WARN
+# RUN: llvm-readelf -S -s %t.pie %t.so %t.r | FileCheck /dev/null \
+# RUN: --implicit-check-not=.riscv.jvt --implicit-check-not=__jvt_base$
+
+# PIC-WARN: warning: --riscv-relax-zcmt is disabled for PIE and shared links
+# REL-WARN: warning: --riscv-relax-zcmt is disabled for relocatable links
+
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
+ .space 4096
+callee:
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-plt.s b/lld/test/ELF/riscv-zcmt-plt.s
new file mode 100644
index 0000000000000..33e9822663e6c
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-plt.s
@@ -0,0 +1,35 @@
+# REQUIRES: riscv
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax shared.s -o shared.o
+# RUN: ld.lld -shared shared.o -o shared.so
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax main.s -o main.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt main.o shared.so -o main
+# RUN: llvm-readelf -S -s main | FileCheck %s --check-prefix=SEC
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases main | FileCheck %s --check-prefix=DIS
+
+# SEC: .riscv.jvt
+# SEC: __jvt_base$
+# DIS-LABEL: <_start>:
+# DIS-NEXT: cm.jt 0
+# DIS-NEXT: cm.jt 0
+# DIS-NEXT: cm.jt 0
+# DIS-NEXT: cm.jt 0
+# DIS-NEXT: cm.jt 0
+
+#--- shared.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.text
+.globl foo
+foo:
+ ret
+
+#--- main.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail foo at plt
+ .endr
+ .space 4096
diff --git a/lld/test/ELF/riscv-zcmt-relax.s b/lld/test/ELF/riscv-zcmt-relax.s
new file mode 100644
index 0000000000000..1a99a439c0dda
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-relax.s
@@ -0,0 +1,59 @@
+# REQUIRES: riscv
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax jt.s -o jt.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt jt.o -o jt
+# RUN: llvm-readelf -S -s jt | FileCheck %s --check-prefix=JT-SEC
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases jt | FileCheck %s --check-prefix=JT-DIS
+
+# JT-SEC: .riscv.jvt
+# JT-SEC-SAME: PROGBITS
+# JT-SEC-SAME: AX
+# JT-SEC-SAME: 64
+# JT-SEC: __jvt_base$
+# JT-DIS-LABEL: <_start>:
+# JT-DIS-NEXT: cm.jt 0x0
+# JT-DIS-NEXT: cm.jt 0x0
+# JT-DIS-NEXT: cm.jt 0x0
+# JT-DIS-NEXT: cm.jt 0x0
+# JT-DIS-NEXT: cm.jt 0x0
+# JT-DIS-NOT: cm.jt
+# JT-DIS-LABEL: <callee>:
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+c,+zcmt,+relax jalt.s -o jalt.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt jalt.o -o jalt
+# RUN: llvm-readelf -S -s jalt | FileCheck %s --check-prefix=JALT-SEC
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases jalt | FileCheck %s --check-prefix=JALT-DIS
+
+# JALT-SEC: .riscv.jvt
+# JALT-SEC-SAME: 000084
+# JALT-SEC: __jvt_base$
+# JALT-DIS-LABEL: <_start>:
+# JALT-DIS-COUNT-67: cm.jalt 0x20
+# JALT-DIS-NOT: cm.jalt
+# JALT-DIS-LABEL: <callee>:
+
+#--- jt.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
+ .space 4096
+callee:
+ ret
+
+#--- jalt.s
+.attribute arch, "rv32i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 67
+ call callee
+ .endr
+ .space 4096
+callee:
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-tie-order.s b/lld/test/ELF/riscv-zcmt-tie-order.s
new file mode 100644
index 0000000000000..35578d3306cfe
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-tie-order.s
@@ -0,0 +1,35 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax %s -o %t.o
+# RUN: ld.lld -e _start --riscv-relax-zcmt %t.o -o %t
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases %t | FileCheck %s
+
+# CHECK-LABEL: <_start>:
+# CHECK-NEXT: cm.jt 0x0
+# CHECK-NEXT: cm.jt 0x0
+# CHECK-NEXT: cm.jt 0x0
+# CHECK-NEXT: cm.jt 0x0
+# CHECK-NEXT: cm.jt 0x0
+# CHECK-NEXT: cm.jt 0x1
+# CHECK-NEXT: cm.jt 0x1
+# CHECK-NEXT: cm.jt 0x1
+# CHECK-NEXT: cm.jt 0x1
+# CHECK-NEXT: cm.jt 0x1
+# CHECK-NOT: cm.jt
+# CHECK-LABEL: <callee0>:
+
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.globl _start
+_start:
+ .rept 5
+ tail callee0
+ .endr
+ .rept 5
+ tail callee1
+ .endr
+ .space 4096
+callee0:
+ ret
+callee1:
+ ret
diff --git a/lld/test/ELF/riscv-zcmt-vma-zero.s b/lld/test/ELF/riscv-zcmt-vma-zero.s
new file mode 100644
index 0000000000000..229bb755c2215
--- /dev/null
+++ b/lld/test/ELF/riscv-zcmt-vma-zero.s
@@ -0,0 +1,33 @@
+# REQUIRES: riscv
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+c,+zcmt,+relax a.s -o a.o
+# RUN: ld.lld -T lds --riscv-relax-zcmt a.o -o a
+# RUN: llvm-readelf -s a | FileCheck %s --check-prefix=SYM
+# RUN: llvm-objdump -d --no-show-raw-insn -M no-aliases a | FileCheck %s --check-prefix=DIS
+
+# SYM: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 callee
+# DIS-LABEL: <_start>:
+# DIS-NEXT: cm.jt 0
+# DIS-NEXT: cm.jt 0
+
+#--- lds
+ENTRY(_start)
+SECTIONS {
+ . = 0;
+ .text : { *(.text.callee) *(.text.start) }
+}
+
+#--- a.s
+.attribute arch, "rv64i2p1_zicsr2p0_zca1p0_zcmt1p0"
+.option rvc
+.section .text.callee,"ax", at progbits
+callee:
+ ret
+.section .text.start,"ax", at progbits
+.space 4096
+.globl _start
+_start:
+ .rept 5
+ tail callee
+ .endr
diff --git a/lld/test/ELF/target-specific-options.s b/lld/test/ELF/target-specific-options.s
index 3d34ad294eb7b..45347cc464d02 100644
--- a/lld/test/ELF/target-specific-options.s
+++ b/lld/test/ELF/target-specific-options.s
@@ -21,11 +21,13 @@
# CHECK-AARCH64-NEXT: error: -z gcs-report-dynamic only supported on AArch64
# CHECK-AARCH64-NEXT: error: -z gcs only supported on AArch64
-# RUN: not ld.lld a.o --relax-gp -z zicfilp-unlabeled-report=warning \
+# RUN: not ld.lld a.o --relax-gp --riscv-relax-zcmt \
+# RUN: -z zicfilp-unlabeled-report=warning \
# RUN: -z zicfilp-func-sig-report=warning -z zicfiss-report=warning \
# RUN: -z zicfilp=unlabeled -z zicfiss=always 2>&1 | \
# RUN: FileCheck %s --check-prefix=ERR-RISCV
# ERR-RISCV: error: --relax-gp is only supported on RISC-V targets
+# ERR-RISCV-NEXT: error: --riscv-relax-zcmt is only supported on RISC-V targets
# ERR-RISCV-NEXT: error: -z zicfilip-unlabeled-report is only supported on RISC-V targets
# ERR-RISCV-NEXT: error: -z zicfilip-func-sig-report is only supported on RISC-V targets
# ERR-RISCV-NEXT: error: -z zicfiss-report is only supported on RISC-V targets
More information about the llvm-commits
mailing list