llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lld-elf @llvm/pr-subscribers-lld Author: None (llvmbot) <details> <summary>Changes</summary> Backport 0af07c078798b7c427e2981377781b5cc555a568 Requested by: @<!-- -->MaskRay --- Patch is 42.50 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/101532.diff 23 Files Affected: - (modified) lld/ELF/DWARF.cpp (+2-1) - (modified) lld/ELF/ICF.cpp (+7-1) - (modified) lld/ELF/InputFiles.cpp (+1) - (modified) lld/ELF/InputFiles.h (+1) - (modified) lld/ELF/InputSection.cpp (+53-14) - (modified) lld/ELF/InputSection.h (+10-4) - (modified) lld/ELF/LinkerScript.cpp (+2) - (modified) lld/ELF/MarkLive.cpp (+11-1) - (modified) lld/ELF/OutputSections.cpp (+129-3) - (modified) lld/ELF/OutputSections.h (+6) - (modified) lld/ELF/Relocations.cpp (+25-13) - (modified) lld/ELF/Relocations.h (+92) - (modified) lld/ELF/SyntheticSections.cpp (+4-2) - (modified) lld/ELF/Writer.cpp (+11-2) - (added) lld/test/ELF/crel-rel-mixed.s (+22) - (added) lld/test/ELF/crel.s (+90) - (modified) lld/test/ELF/debug-names.s (+1-1) - (modified) lld/test/ELF/gc-sections.s (+4) - (modified) lld/test/ELF/icf1.s (+3) - (modified) lld/test/ELF/icf4.s (+1-1) - (modified) lld/test/ELF/linkerscript/nocrossrefs.test (+3-1) - (added) lld/test/ELF/relocatable-crel-32.s (+71) - (added) lld/test/ELF/relocatable-crel.s (+107) ``````````diff diff --git a/lld/ELF/DWARF.cpp b/lld/ELF/DWARF.cpp index 5d58e0c60a952..517d26810a378 100644 --- a/lld/ELF/DWARF.cpp +++ b/lld/ELF/DWARF.cpp @@ -136,7 +136,8 @@ template <class ELFT> std::optional<RelocAddrEntry> LLDDwarfObj<ELFT>::find(const llvm::DWARFSection &s, uint64_t pos) const { auto &sec = static_cast<const LLDDWARFSection &>(s); - const RelsOrRelas<ELFT> rels = sec.sec->template relsOrRelas<ELFT>(); + const RelsOrRelas<ELFT> rels = + sec.sec->template relsOrRelas<ELFT>(/*supportsCrel=*/false); if (rels.areRelocsRel()) return findAux(*sec.sec, pos, rels.rels); return findAux(*sec.sec, pos, rels.relas); diff --git a/lld/ELF/ICF.cpp b/lld/ELF/ICF.cpp index a6b52d78fa806..44e8a71cc6286 100644 --- a/lld/ELF/ICF.cpp +++ b/lld/ELF/ICF.cpp @@ -324,6 +324,8 @@ bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) { const RelsOrRelas<ELFT> ra = a->template relsOrRelas<ELFT>(); const RelsOrRelas<ELFT> rb = b->template relsOrRelas<ELFT>(); + if (ra.areRelocsCrel()) + return constantEq(a, ra.crels, b, rb.crels); return ra.areRelocsRel() || rb.areRelocsRel() ? constantEq(a, ra.rels, b, rb.rels) : constantEq(a, ra.relas, b, rb.relas); @@ -374,6 +376,8 @@ template <class ELFT> bool ICF<ELFT>::equalsVariable(const InputSection *a, const InputSection *b) { const RelsOrRelas<ELFT> ra = a->template relsOrRelas<ELFT>(); const RelsOrRelas<ELFT> rb = b->template relsOrRelas<ELFT>(); + if (ra.areRelocsCrel()) + return variableEq(a, ra.crels, b, rb.crels); return ra.areRelocsRel() || rb.areRelocsRel() ? variableEq(a, ra.rels, b, rb.rels) : variableEq(a, ra.relas, b, rb.relas); @@ -505,7 +509,9 @@ template <class ELFT> void ICF<ELFT>::run() { for (unsigned cnt = 0; cnt != 2; ++cnt) { parallelForEach(sections, [&](InputSection *s) { const RelsOrRelas<ELFT> rels = s->template relsOrRelas<ELFT>(); - if (rels.areRelocsRel()) + if (rels.areRelocsCrel()) + combineRelocHashes(cnt, s, rels.crels); + else if (rels.areRelocsRel()) combineRelocHashes(cnt, s, rels.rels); else combineRelocHashes(cnt, s, rels.relas); diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp index 03ff4eadfe670..f1c0eb292361b 100644 --- a/lld/ELF/InputFiles.cpp +++ b/lld/ELF/InputFiles.cpp @@ -834,6 +834,7 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats, case SHT_STRTAB: case SHT_REL: case SHT_RELA: + case SHT_CREL: case SHT_NULL: break; case SHT_PROGBITS: diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h index 0617f41e1e13a..8566baf61e1ab 100644 --- a/lld/ELF/InputFiles.h +++ b/lld/ELF/InputFiles.h @@ -84,6 +84,7 @@ class InputFile { assert(fileKind == ObjKind || fileKind == BinaryKind); return sections; } + void cacheDecodedCrel(size_t i, InputSectionBase *s) { sections[i] = s; } // Returns object file symbols. It is a runtime error to call this // function on files of other types. diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp index 7857d857488c0..570e485455bad 100644 --- a/lld/ELF/InputSection.cpp +++ b/lld/ELF/InputSection.cpp @@ -133,21 +133,56 @@ void InputSectionBase::decompress() const { compressed = false; } -template <class ELFT> RelsOrRelas<ELFT> InputSectionBase::relsOrRelas() const { +template <class ELFT> +RelsOrRelas<ELFT> InputSectionBase::relsOrRelas(bool supportsCrel) const { if (relSecIdx == 0) return {}; RelsOrRelas<ELFT> ret; - typename ELFT::Shdr shdr = - cast<ELFFileBase>(file)->getELFShdrs<ELFT>()[relSecIdx]; + auto *f = cast<ObjFile<ELFT>>(file); + typename ELFT::Shdr shdr = f->template getELFShdrs<ELFT>()[relSecIdx]; + if (shdr.sh_type == SHT_CREL) { + // Return an iterator if supported by caller. + if (supportsCrel) { + ret.crels = Relocs<typename ELFT::Crel>( + (const uint8_t *)f->mb.getBufferStart() + shdr.sh_offset); + return ret; + } + InputSectionBase *const &relSec = f->getSections()[relSecIdx]; + // Otherwise, allocate a buffer to hold the decoded RELA relocations. When + // called for the first time, relSec is null (without --emit-relocs) or an + // InputSection with zero eqClass[0]. + if (!relSec || !cast<InputSection>(relSec)->eqClass[0]) { + auto *sec = makeThreadLocal<InputSection>(*f, shdr, name); + f->cacheDecodedCrel(relSecIdx, sec); + sec->type = SHT_RELA; + sec->eqClass[0] = SHT_RELA; + + RelocsCrel<ELFT::Is64Bits> entries(sec->content_); + sec->size = entries.size() * sizeof(typename ELFT::Rela); + auto *relas = makeThreadLocalN<typename ELFT::Rela>(entries.size()); + sec->content_ = reinterpret_cast<uint8_t *>(relas); + for (auto [i, r] : llvm::enumerate(entries)) { + relas[i].r_offset = r.r_offset; + relas[i].setSymbolAndType(r.r_symidx, r.r_type, false); + relas[i].r_addend = r.r_addend; + } + } + ret.relas = {ArrayRef( + reinterpret_cast<const typename ELFT::Rela *>(relSec->content_), + relSec->size / sizeof(typename ELFT::Rela))}; + return ret; + } + + const void *content = f->mb.getBufferStart() + shdr.sh_offset; + size_t size = shdr.sh_size; if (shdr.sh_type == SHT_REL) { - ret.rels = ArrayRef(reinterpret_cast<const typename ELFT::Rel *>( - file->mb.getBufferStart() + shdr.sh_offset), - shdr.sh_size / sizeof(typename ELFT::Rel)); + ret.rels = {ArrayRef(reinterpret_cast<const typename ELFT::Rel *>(content), + size / sizeof(typename ELFT::Rel))}; } else { assert(shdr.sh_type == SHT_RELA); - ret.relas = ArrayRef(reinterpret_cast<const typename ELFT::Rela *>( - file->mb.getBufferStart() + shdr.sh_offset), - shdr.sh_size / sizeof(typename ELFT::Rela)); + ret.relas = { + ArrayRef(reinterpret_cast<const typename ELFT::Rela *>(content), + size / sizeof(typename ELFT::Rela))}; } return ret; } @@ -1248,7 +1283,7 @@ SyntheticSection *EhInputSection::getParent() const { // .eh_frame is a sequence of CIE or FDE records. // This function splits an input section into records and returns them. template <class ELFT> void EhInputSection::split() { - const RelsOrRelas<ELFT> rels = relsOrRelas<ELFT>(); + const RelsOrRelas<ELFT> rels = relsOrRelas<ELFT>(/*supportsCrel=*/false); // getReloc expects the relocations to be sorted by r_offset. See the comment // in scanRelocs. if (rels.areRelocsRel()) { @@ -1414,10 +1449,14 @@ template void InputSection::writeTo<ELF32BE>(uint8_t *); template void InputSection::writeTo<ELF64LE>(uint8_t *); template void InputSection::writeTo<ELF64BE>(uint8_t *); -template RelsOrRelas<ELF32LE> InputSectionBase::relsOrRelas<ELF32LE>() const; -template RelsOrRelas<ELF32BE> InputSectionBase::relsOrRelas<ELF32BE>() const; -template RelsOrRelas<ELF64LE> InputSectionBase::relsOrRelas<ELF64LE>() const; -template RelsOrRelas<ELF64BE> InputSectionBase::relsOrRelas<ELF64BE>() const; +template RelsOrRelas<ELF32LE> +InputSectionBase::relsOrRelas<ELF32LE>(bool) const; +template RelsOrRelas<ELF32BE> +InputSectionBase::relsOrRelas<ELF32BE>(bool) const; +template RelsOrRelas<ELF64LE> +InputSectionBase::relsOrRelas<ELF64LE>(bool) const; +template RelsOrRelas<ELF64BE> +InputSectionBase::relsOrRelas<ELF64BE>(bool) const; template MergeInputSection::MergeInputSection(ObjFile<ELF32LE> &, const ELF32LE::Shdr &, StringRef); diff --git a/lld/ELF/InputSection.h b/lld/ELF/InputSection.h index c89a545e1543f..6659530a9c9c2 100644 --- a/lld/ELF/InputSection.h +++ b/lld/ELF/InputSection.h @@ -35,17 +35,21 @@ class OutputSection; LLVM_LIBRARY_VISIBILITY extern std::vector<Partition> partitions; -// Returned by InputSectionBase::relsOrRelas. At least one member is empty. +// Returned by InputSectionBase::relsOrRelas. At most one member is empty. template <class ELFT> struct RelsOrRelas { Relocs<typename ELFT::Rel> rels; Relocs<typename ELFT::Rela> relas; + Relocs<typename ELFT::Crel> crels; bool areRelocsRel() const { return rels.size(); } + bool areRelocsCrel() const { return crels.size(); } }; #define invokeOnRelocs(sec, f, ...) \ { \ const RelsOrRelas<ELFT> rs = (sec).template relsOrRelas<ELFT>(); \ - if (rs.areRelocsRel()) \ + if (rs.areRelocsCrel()) \ + f(__VA_ARGS__, rs.crels); \ + else if (rs.areRelocsRel()) \ f(__VA_ARGS__, rs.rels); \ else \ f(__VA_ARGS__, rs.relas); \ @@ -209,7 +213,8 @@ class InputSectionBase : public SectionBase { // used by --gc-sections. InputSectionBase *nextInSectionGroup = nullptr; - template <class ELFT> RelsOrRelas<ELFT> relsOrRelas() const; + template <class ELFT> + RelsOrRelas<ELFT> relsOrRelas(bool supportsCrel = true) const; // InputSections that are dependent on us (reverse dependency for GC) llvm::TinyPtrVector<InputSection *> dependentSections; @@ -483,7 +488,8 @@ class SyntheticSection : public InputSection { }; inline bool isStaticRelSecType(uint32_t type) { - return type == llvm::ELF::SHT_RELA || type == llvm::ELF::SHT_REL; + return type == llvm::ELF::SHT_RELA || type == llvm::ELF::SHT_CREL || + type == llvm::ELF::SHT_REL; } inline bool isDebugSection(const InputSectionBase &sec) { diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp index e2208da18dce0..055fa21d44ca6 100644 --- a/lld/ELF/LinkerScript.cpp +++ b/lld/ELF/LinkerScript.cpp @@ -61,6 +61,8 @@ static StringRef getOutputSectionName(const InputSectionBase *s) { assert(config->relocatable && (rel->flags & SHF_LINK_ORDER)); return s->name; } + if (s->type == SHT_CREL) + return saver().save(".crel" + out->name); if (s->type == SHT_RELA) return saver().save(".rela" + out->name); return saver().save(".rel" + out->name); diff --git a/lld/ELF/MarkLive.cpp b/lld/ELF/MarkLive.cpp index 45431e44a6c8c..16e5883c2002c 100644 --- a/lld/ELF/MarkLive.cpp +++ b/lld/ELF/MarkLive.cpp @@ -85,6 +85,13 @@ static uint64_t getAddend(InputSectionBase &sec, return rel.r_addend; } +// Currently, we assume all input CREL relocations have an explicit addend. +template <class ELFT> +static uint64_t getAddend(InputSectionBase &sec, + const typename ELFT::Crel &rel) { + return rel.r_addend; +} + template <class ELFT> template <class RelTy> void MarkLive<ELFT>::resolveReloc(InputSectionBase &sec, RelTy &rel, @@ -239,7 +246,8 @@ template <class ELFT> void MarkLive<ELFT>::run() { // all of them. We also want to preserve personality routines and LSDA // referenced by .eh_frame sections, so we scan them for that here. for (EhInputSection *eh : ctx.ehInputSections) { - const RelsOrRelas<ELFT> rels = eh->template relsOrRelas<ELFT>(); + const RelsOrRelas<ELFT> rels = + eh->template relsOrRelas<ELFT>(/*supportsCrel=*/false); if (rels.areRelocsRel()) scanEhFrameSection(*eh, rels.rels); else if (rels.relas.size()) @@ -310,6 +318,8 @@ template <class ELFT> void MarkLive<ELFT>::mark() { resolveReloc(sec, rel, false); for (const typename ELFT::Rela &rel : rels.relas) resolveReloc(sec, rel, false); + for (const typename ELFT::Crel &rel : rels.crels) + resolveReloc(sec, rel, false); for (InputSectionBase *isec : sec.dependentSections) enqueue(isec, 0); diff --git a/lld/ELF/OutputSections.cpp b/lld/ELF/OutputSections.cpp index 60de10061c53d..29f18f89274f3 100644 --- a/lld/ELF/OutputSections.cpp +++ b/lld/ELF/OutputSections.cpp @@ -18,6 +18,7 @@ #include "llvm/BinaryFormat/Dwarf.h" #include "llvm/Config/llvm-config.h" // LLVM_ENABLE_ZLIB #include "llvm/Support/Compression.h" +#include "llvm/Support/LEB128.h" #include "llvm/Support/Parallel.h" #include "llvm/Support/Path.h" #include "llvm/Support/TimeProfiler.h" @@ -115,7 +116,19 @@ void OutputSection::recordSection(InputSectionBase *isec) { // other InputSections. void OutputSection::commitSection(InputSection *isec) { if (LLVM_UNLIKELY(type != isec->type)) { - if (hasInputSections || typeIsSet) { + if (!hasInputSections && !typeIsSet) { + type = isec->type; + } else if (isStaticRelSecType(type) && isStaticRelSecType(isec->type) && + (type == SHT_CREL) != (isec->type == SHT_CREL)) { + // Combine mixed SHT_REL[A] and SHT_CREL to SHT_CREL. + type = SHT_CREL; + if (type == SHT_REL) { + if (name.consume_front(".rel")) + name = saver().save(".crel" + name); + } else if (name.consume_front(".rela")) { + name = saver().save(".crel" + name); + } + } else { if (typeIsSet || !canMergeToProgbits(type) || !canMergeToProgbits(isec->type)) { // The (NOLOAD) changes the section type to SHT_NOBITS, the intention is @@ -133,8 +146,6 @@ void OutputSection::commitSection(InputSection *isec) { } if (!typeIsSet) type = SHT_PROGBITS; - } else { - type = isec->type; } } if (!hasInputSections) { @@ -470,6 +481,11 @@ void OutputSection::writeTo(uint8_t *buf, parallel::TaskGroup &tg) { llvm::TimeTraceScope timeScope("Write sections", name); if (type == SHT_NOBITS) return; + if (type == SHT_CREL && !(flags & SHF_ALLOC)) { + buf += encodeULEB128(crelHeader, buf); + memcpy(buf, crelBody.data(), crelBody.size()); + return; + } // If the section is compressed due to // --compress-debug-section/--compress-sections, the content is already known. @@ -505,6 +521,12 @@ void OutputSection::writeTo(uint8_t *buf, parallel::TaskGroup &tg) { if (nonZeroFiller) fill(buf, sections.empty() ? size : sections[0]->outSecOff, filler); + if (type == SHT_CREL && !(flags & SHF_ALLOC)) { + buf += encodeULEB128(crelHeader, buf); + memcpy(buf, crelBody.data(), crelBody.size()); + return; + } + auto fn = [=](size_t begin, size_t end) { size_t numSections = sections.size(); for (size_t i = begin; i != end; ++i) { @@ -592,6 +614,103 @@ static void finalizeShtGroup(OutputSection *os, InputSection *section) { os->size = (1 + seen.size()) * sizeof(uint32_t); } +template <class uint> +LLVM_ATTRIBUTE_ALWAYS_INLINE static void +encodeOneCrel(raw_svector_ostream &os, Elf_Crel<sizeof(uint) == 8> &out, + uint offset, const Symbol &sym, uint32_t type, uint addend) { + const auto deltaOffset = static_cast<uint64_t>(offset - out.r_offset); + out.r_offset = offset; + int64_t symidx = in.symTab->getSymbolIndex(sym); + if (sym.type == STT_SECTION) { + auto *d = dyn_cast<Defined>(&sym); + if (d) { + SectionBase *section = d->section; + assert(section->isLive()); + addend = sym.getVA(addend) - section->getOutputSection()->addr; + } else { + // Encode R_*_NONE(symidx=0). + symidx = type = addend = 0; + } + } + + // Similar to llvm::ELF::encodeCrel. + uint8_t b = deltaOffset * 8 + (out.r_symidx != symidx) + + (out.r_type != type ? 2 : 0) + + (uint(out.r_addend) != addend ? 4 : 0); + if (deltaOffset < 0x10) { + os << char(b); + } else { + os << char(b | 0x80); + encodeULEB128(deltaOffset >> 4, os); + } + if (b & 1) { + encodeSLEB128(static_cast<int32_t>(symidx - out.r_symidx), os); + out.r_symidx = symidx; + } + if (b & 2) { + encodeSLEB128(static_cast<int32_t>(type - out.r_type), os); + out.r_type = type; + } + if (b & 4) { + encodeSLEB128(std::make_signed_t<uint>(addend - out.r_addend), os); + out.r_addend = addend; + } +} + +template <class ELFT> +static size_t relToCrel(raw_svector_ostream &os, Elf_Crel<ELFT::Is64Bits> &out, + InputSection *relSec, InputSectionBase *sec) { + const auto &file = *cast<ELFFileBase>(relSec->file); + if (relSec->type == SHT_REL) { + // REL conversion is complex and unsupported yet. + errorOrWarn(toString(relSec) + ": REL cannot be converted to CREL"); + return 0; + } + auto rels = relSec->getDataAs<typename ELFT::Rela>(); + for (auto rel : rels) { + encodeOneCrel<typename ELFT::uint>( + os, out, sec->getVA(rel.r_offset), file.getRelocTargetSym(rel), + rel.getType(config->isMips64EL), getAddend<ELFT>(rel)); + } + return rels.size(); +} + +// Compute the content of a non-alloc CREL section due to -r or --emit-relocs. +// Input CREL sections are decoded while REL[A] need to be converted. +template <bool is64> void OutputSection::finalizeNonAllocCrel() { + using uint = typename Elf_Crel_Impl<is64>::uint; + raw_svector_ostream os(crelBody); + uint64_t totalCount = 0; + Elf_Crel<is64> out{}; + assert(commands.size() == 1); + auto *isd = cast<InputSectionDescription>(commands[0]); + for (InputSection *relSec : isd->sections) { + const auto &file = *cast<ELFFileBase>(relSec->file); + InputSectionBase *sec = relSec->getRelocatedSection(); + if (relSec->type == SHT_CREL) { + RelocsCrel<is64> entries(relSec->content_); + totalCount += entries.size(); + for (Elf_Crel_Impl<is64> r : entries) { + encodeOneCrel<uint>(os, out, uint(sec->getVA(r.r_offset)), + file.getSymbol(r.r_symidx), r.r_type, r.r_addend); + } + continue; + } + + // Convert REL[A] to CREL. + if constexpr (is64) { + totalCount += config->isLE ? relToCrel<ELF64LE>(os, out, relSec, sec) + : relToCrel<ELF64BE>(os, out, relSec, sec); + } else { + totalCount += config->isLE ? relToCrel<ELF32LE>(os, out, relSec, sec) + : relToCrel<ELF32BE>(os, out, relSec, sec); + } + } + + crelHeader = totalCount * 8 + 4; + size = getULEB128Size(crelHeader) + crelBody.size(); +} + void OutputSection::finalize() { InputSection *first = getFirstInputSection(this); @@ -628,6 +747,13 @@ void OutputSection::finalize() { InputSectionBase *s = first->getRelocatedSection(); info = s->getOutputSection()->sectionIndex; flags |= SHF_INFO_LINK; + // Finalize the content of non-alloc CREL. + if (type == SHT_CREL) { + if (config->is64) + finalizeNonAllocCrel<true>(); + else + finalizeNonAllocCrel<false>(); + } } // Returns true if S is in one of the many forms the compiler driver may pass diff --git a/lld/ELF/OutputSections.h b/lld/ELF/OutputSections.h index 78fede48a23f2..8c0c52f34ac9f 100644 --- a/lld/ELF/OutputSections.h +++ b/lld/ELF/OutputSections.h @@ -84,6 +84,11 @@ class OutputSection final : public SectionBase { Expr alignExpr; Expr lmaExpr; Expr subalignExpr; + + // Used by non-alloc SHT_CREL to hold the header and content byte stream. + uint64_t crelHeader = 0; + SmallVector<char, 0> crelBody; + SmallVector<SectionCommand *, 0> commands; SmallVector<StringRef, 0> phdrs; std::optional<std::array<uint8_t, 4>> filler; @@ -106,6 +111,7 @@ class OutputSection final : public SectionBase { // DATA_RELRO_END. bool relro = false; + template <bool is64> void finalizeNonAllocCrel(); void finalize(); template <class ELFT> void writeTo(uint8_t *buf, llvm::parallel::TaskGroup &tg); diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp index 9a799cd286135..e19b1e6c8efb8 100644 --- a/lld/ELF/Relocations.cpp +++ b/lld/ELF/Relocations.cpp @@ -1441,10 +1441,11 @@ void RelocationScanner::scanOne(typename Relocs<RelTy>::const_iterator &i) { uint32_t symIndex = rel.getSymbol(config->isMips64EL); Symbol &sym = sec->getFile<ELFT>()->getSymbol(symIndex); RelType type; - if constexpr (ELFT::Is64Bits) { + if constexpr (ELFT::Is64Bits || RelTy::IsCrel) { type = rel.getType(config->isMips64EL); ++i; } else { + // CREL is unsupported for MIPS N32. if (config->mipsN32Abi) { type = getMipsN32RelType(i); } els... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/101532 _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits