diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp --- a/lld/ELF/Arch/RISCV.cpp +++ b/lld/ELF/Arch/RISCV.cpp @@ -11,6 +11,11 @@ #include "Symbols.h" #include "SyntheticSections.h" #include "Target.h" +#include "llvm/Support/ELFAttributes.h" +#include "llvm/Support/LEB128.h" +#include "llvm/Support/RISCVAttributeParser.h" +#include "llvm/Support/RISCVAttributes.h" +#include "llvm/Support/RISCVISAInfo.h" #include "llvm/Support/TimeProfiler.h" using namespace llvm; @@ -816,6 +821,191 @@ } } +namespace { +// Representation of the combined .riscv.attributes input sections. +class RISCVAttributesSection final : public SyntheticSection { +public: + RISCVAttributesSection() + : SyntheticSection(0, llvm::ELF::SHT_RISCV_ATTRIBUTES, 1, + ".riscv.attributes") {} + + size_t getSize() const override { return size; } + void writeTo(uint8_t *buf) override; + + static constexpr llvm::StringRef vendor = "riscv"; + llvm::DenseMap intAttr; + llvm::DenseMap strAttr; + size_t size = 0; +}; +} // namespace + +static void mergeArch(RISCVISAInfo::OrderedExtensionMap &mergedExts, + unsigned &mergedXlen, const InputSectionBase *sec, + StringRef s) { + auto maybeInfo = llvm::RISCVISAInfo::parseArchString( + s, /*EnableExperimentalExtension=*/true, + /*ExperimentalExtensionVersionCheck=*/true); + if (!maybeInfo) { + errorOrWarn(toString(sec) + ": " + s + ": " + + llvm::toString(maybeInfo.takeError())); + return; + } + + // Merge extensions. + RISCVISAInfo &info = **maybeInfo; + if (mergedExts.empty()) { + mergedExts = info.getExtensions(); + mergedXlen = info.getXLen(); + } else { + for (const auto &ext : info.getExtensions()) { + if (auto it = mergedExts.find(ext.first); it != mergedExts.end()) { + if (std::tie(it->second.MajorVersion, it->second.MinorVersion) >= + std::tie(ext.second.MajorVersion, ext.second.MinorVersion)) + continue; + } + mergedExts[ext.first] = ext.second; + } + } +} + +static RISCVAttributesSection * +combineAttributesSection(const SmallVector §ions) { + const InputSectionBase *firstStackAlign = nullptr; + unsigned firstStackAlignValue = 0; + RISCVISAInfo::OrderedExtensionMap exts; + unsigned xlen = 0; + bool hasArch = false; + + in.riscvAttributes = std::make_unique(); + auto &combined = static_cast(*in.riscvAttributes); + + // Collect all tags values from attributes section. + const auto &attributesTags = RISCVAttrs::getRISCVAttributeTags(); + for (const InputSectionBase *sec : sections) { + RISCVAttributeParser parser; + if (Error e = parser.parse(sec->content(), support::little)) + message(toString(sec) + ": " + llvm::toString(std::move(e))); + for (const auto &tag : attributesTags) { + switch (RISCVAttrs::AttrType(tag.attr)) { + // Integer attributes. + case RISCVAttrs::STACK_ALIGN: + if (auto i = parser.getAttributeValue(tag.attr)) { + auto r = combined.intAttr.try_emplace(tag.attr, *i); + if (r.second) { + firstStackAlign = sec; + firstStackAlignValue = *i; + } else if (r.first->second != *i) { + errorOrWarn(toString(sec) + " has stack_align=" + Twine(*i) + + " but " + toString(firstStackAlign) + + " has stack_align=" + Twine(firstStackAlignValue)); + } + } + continue; + case RISCVAttrs::UNALIGNED_ACCESS: + if (auto i = parser.getAttributeValue(tag.attr)) + combined.intAttr[tag.attr] |= *i; + continue; + + // String attributes. + case RISCVAttrs::ARCH: + if (auto s = parser.getAttributeString(tag.attr)) { + hasArch = true; + mergeArch(exts, xlen, sec, *s); + } + continue; + + // Attributes which use the default handling. + case RISCVAttrs::PRIV_SPEC: + case RISCVAttrs::PRIV_SPEC_MINOR: + case RISCVAttrs::PRIV_SPEC_REVISION: + break; + } + + if (tag.attr % 2 == 0) { + if (auto i = parser.getAttributeValue(tag.attr)) { + auto r = combined.intAttr.try_emplace(tag.attr, *i); + if (!r.second && r.first->second != *i) + r.first->second = 0; + } + } else if (auto s = parser.getAttributeString(tag.attr)) { + auto r = combined.strAttr.try_emplace(tag.attr, *s); + if (!r.second && r.first->second != *s) + r.first->second = {}; + } + } + } + + if (hasArch) { + std::unique_ptr isaInfo = + std::make_unique(xlen, exts); + auto result = RISCVISAInfo::postProcessAndChecking(std::move(isaInfo)); + if (!result) + errorOrWarn(llvm::toString(result.takeError())); + combined.strAttr.try_emplace(RISCVAttrs::ARCH, + saver().save((*result)->toString())); + } + + // The total size of headers: format-version [ "vendor-name" + // [ . + size_t size = 5 + combined.vendor.size() + 1 + 5; + for (auto &attr : combined.intAttr) + if (attr.second != 0) + size += getULEB128Size(attr.first) + getULEB128Size(attr.second); + for (auto &attr : combined.strAttr) + if (!attr.second.empty()) + size += getULEB128Size(attr.first) + attr.second.size() + 1; + combined.size = size; + return &combined; +} + +void RISCVAttributesSection::writeTo(uint8_t *buf) { + const size_t size = getSize(); + uint8_t *const end = buf + size; + *buf = ELFAttrs::Format_Version; + write32(buf + 1, size - 1); + buf += 5; + + memcpy(buf, vendor.data(), vendor.size()); + buf += vendor.size() + 1; + + *buf = ELFAttrs::File; + write32(buf + 1, end - buf); + buf += 5; + + for (auto &attr : intAttr) { + if (attr.second == 0) + continue; + buf += encodeULEB128(attr.first, buf); + buf += encodeULEB128(attr.second, buf); + } + for (auto &attr : strAttr) { + if (attr.second.empty()) + continue; + buf += encodeULEB128(attr.first, buf); + memcpy(buf, attr.second.data(), attr.second.size()); + buf += attr.second.size() + 1; + } +} + +void elf::combineRISCVAttributesSections() { + // Extract all .riscv.attributes sections into `sections`. + SmallVector sections; + llvm::erase_if(ctx.inputSections, [&](InputSectionBase *s) { + if (s->type != SHT_RISCV_ATTRIBUTES) + return false; + sections.push_back(s); + return true; + }); + if (sections.empty()) + return; + auto place = llvm::find_if(ctx.inputSections, [](auto *s) { + return s->type == SHT_RISCV_ATTRIBUTES; + }); + + // Add the merged section. + ctx.inputSections.insert(place, combineAttributesSection(sections)); +} + TargetInfo *elf::getRISCVTargetInfo() { static RISCV target; return ⌖ diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -2820,6 +2820,10 @@ if (!config->relocatable) combineEhSections(); + // Combine .riscv.attributes sections. + if (config->emachine == EM_RISCV) + combineRISCVAttributesSections(); + { llvm::TimeTraceScope timeScope("Assign sections"); diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp --- a/lld/ELF/InputFiles.cpp +++ b/lld/ELF/InputFiles.cpp @@ -581,30 +581,6 @@ } } - if (sec.sh_type == SHT_RISCV_ATTRIBUTES && config->emachine == EM_RISCV) { - RISCVAttributeParser attributes; - ArrayRef contents = - check(this->getObj().getSectionContents(sec)); - StringRef name = check(obj.getSectionName(sec, shstrtab)); - this->sections[i] = &InputSection::discarded; - if (Error e = attributes.parse(contents, support::little)) { - InputSection isec(*this, sec, name); - warn(toString(&isec) + ": " + llvm::toString(std::move(e))); - } else { - // FIXME: Validate arch tag contains C if and only if EF_RISCV_RVC is - // present. - - // FIXME: Retain the first attribute section we see. Tools such as - // llvm-objdump make use of the attribute section to determine which - // standard extensions to enable. In a full implementation we would - // merge all attribute sections. - if (in.attributes == nullptr) { - in.attributes = std::make_unique(*this, sec, name); - this->sections[i] = in.attributes.get(); - } - } - } - if (sec.sh_type != SHT_GROUP) continue; StringRef signature = getShtGroupSignature(objSections, sec); diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h --- a/lld/ELF/SyntheticSections.h +++ b/lld/ELF/SyntheticSections.h @@ -1273,6 +1273,7 @@ // a partition. struct InStruct { std::unique_ptr attributes; + std::unique_ptr riscvAttributes; std::unique_ptr bss; std::unique_ptr bssRelRo; std::unique_ptr got; diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp --- a/lld/ELF/SyntheticSections.cpp +++ b/lld/ELF/SyntheticSections.cpp @@ -3801,6 +3801,7 @@ void InStruct::reset() { attributes.reset(); + riscvAttributes.reset(); bss.reset(); bssRelRo.reset(); got.reset(); diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h --- a/lld/ELF/Target.h +++ b/lld/ELF/Target.h @@ -216,6 +216,7 @@ uint64_t getPPC64TocBase(); uint64_t getAArch64Page(uint64_t expr); void riscvFinalizeRelax(int passes); +void combineRISCVAttributesSections(); LLVM_LIBRARY_VISIBILITY extern const TargetInfo *target; TargetInfo *getTarget(); diff --git a/lld/test/ELF/lto/riscv-attributes.ll b/lld/test/ELF/lto/riscv-attributes.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/riscv-attributes.ll @@ -0,0 +1,53 @@ +; REQUIRES: riscv + +; RUN: rm -rf %t && split-file %s %t && cd %t +; RUN: llvm-mc -filetype=obj -triple=riscv32 1.s -o 1.o +; RUN: llvm-mc -filetype=obj -triple=riscv32 2.s -o 2.o +; RUN: llvm-as a.ll -o a.bc +; RUN: ld.lld 1.o 2.o a.bc -o out +; RUN: llvm-readelf --arch-specific out | FileCheck %s + +; CHECK: BuildAttributes { +; CHECK-NEXT: FormatVersion: 0x41 +; CHECK-NEXT: Section 1 { +; CHECK-NEXT: SectionLength: 61 +; CHECK-NEXT: Vendor: riscv +; CHECK-NEXT: Tag: Tag_File (0x1) +; CHECK-NEXT: Size: 51 +; CHECK-NEXT: FileAttributes { +; CHECK-NEXT: Attribute { +; CHECK-NEXT: Tag: 4 +; CHECK-NEXT: Value: 16 +; CHECK-NEXT: TagName: stack_align +; CHECK-NEXT: Description: Stack alignment is 16-bytes +; CHECK-NEXT: } +; CHECK-NEXT: Attribute { +; CHECK-NEXT: Tag: 6 +; CHECK-NEXT: Value: 1 +; CHECK-NEXT: TagName: unaligned_access +; CHECK-NEXT: Description: Unaligned access +; CHECK-NEXT: } +; CHECK-NEXT: Attribute { +; CHECK-NEXT: Tag: 5 +; CHECK-NEXT: TagName: arch +; CHECK-NEXT: Value: rv32i2p0_m2p0_a2p0_f2p0_d2p0_c2p0_zbb1p0 +; CHECK-NEXT: } +; CHECK-NEXT: } +; CHECK-NEXT: } +; CHECK-NEXT: } + +;--- 1.s +.attribute 4, 16 +.attribute 5, "rv32i2p0_m2p0_a2p0_f2p0_d2p0_c2p0" +;--- 2.s +.attribute 4, 16 +.attribute 5, "rv32i2p0_m2p0_f2p0_d2p0_zbb1p0" +.attribute 6, 1 + +;--- a.ll +target datalayout = "e-m:e-p:32:32-i64:64-n32-S128" +target triple = "riscv32" + +define void @_start() { + ret void +} diff --git a/lld/test/ELF/riscv-attributes.s b/lld/test/ELF/riscv-attributes.s --- a/lld/test/ELF/riscv-attributes.s +++ b/lld/test/ELF/riscv-attributes.s @@ -1,10 +1,42 @@ # REQUIRES: riscv -# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=-relax %s -o %t.o -# RUN: ld.lld %t.o -o %t -# RUN: llvm-readelf --arch-specific %t | FileCheck %s -# RUN: ld.lld %t.o %t.o -o %t2 -# RUN: llvm-readelf --arch-specific %t2 | FileCheck %s +# RUN: rm -rf %t && split-file %s %t && cd %t +# RUN: llvm-mc -filetype=obj -triple=riscv64 a.s -o a.o +# RUN: ld.lld a.o -o out +# RUN: llvm-readelf --arch-specific out | FileCheck %s +# RUN: ld.lld a.o a.o -o out1 +# RUN: llvm-readelf --arch-specific out1 | FileCheck %s + +# RUN: llvm-mc -filetype=obj -triple=riscv64 b.s -o b.o +# RUN: llvm-mc -filetype=obj -triple=riscv64 c.s -o c.o +# RUN: ld.lld a.o b.o c.o -o out2 +# RUN: llvm-readelf --arch-specific out2 | FileCheck %s --check-prefix=CHECK2 + +# RUN: llvm-mc -filetype=obj -triple=riscv64 invalid_ext.s -o invalid_ext.o +# RUN: not ld.lld invalid_ext.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=INVALID_EXT --implicit-check-not=error: +# INVALID_EXT: error: invalid_ext.o:(.riscv.attributes): rv64i2p0_y2p0: invalid standard user-level extension 'y' + +## A zero value attribute is not printed. +# RUN: llvm-mc -filetype=obj -triple=riscv64 unaligned_access_0.s -o unaligned_access_0.o +# RUN: ld.lld -e 0 --fatal-warnings a.o unaligned_access_0.o -o unaligned_access_0 +# RUN: llvm-readobj -A unaligned_access_0 | FileCheck /dev/null --implicit-check-not='TagName: unaligned_access' + +## Differing stack_align values lead to an error. +# RUN: llvm-mc -filetype=obj -triple=riscv64 diff_stack_align.s -o diff_stack_align.o +# RUN: not ld.lld a.o b.o c.o diff_stack_align.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=STACK_ALIGN --implicit-check-not=error: +# STACK_ALIGN: error: diff_stack_align.o:(.riscv.attributes) has stack_align=32 but a.o:(.riscv.attributes) has stack_align=16 + +## The deprecated priv_spec is not handled as GNU ld does. +## Differing priv_spec attributes lead to an absent attribute. +# RUN: llvm-mc -filetype=obj -triple=riscv64 diff_priv_spec.s -o diff_priv_spec.o +# RUN: ld.lld -e 0 --fatal-warnings a.o b.o c.o diff_priv_spec.o -o diff_priv_spec +# RUN: llvm-readobj -A diff_priv_spec | FileCheck /dev/null --implicit-check-not='TagName: priv_spec' + +## Unknown tags are ignored with stdout messages. +# RUN: llvm-mc -filetype=obj -triple=riscv64 unknown0.s -o unknown0.o +# RUN: llvm-mc -filetype=obj -triple=riscv64 unknown1.s -o unknown1.o +# RUN: ld.lld -e 0 --fatal-warnings unknown0.o unknown0.o -o unknown0 | FileCheck %s --check-prefix=UNKNOWN_TAG +# UNKNOWN_TAG-COUNT-2: unknown0.o:(.riscv.attributes): invalid tag 0x9 at offset 0x10 # CHECK: BuildAttributes { # CHECK-NEXT: FormatVersion: 0x41 @@ -29,5 +61,81 @@ # CHECK-NEXT: } # CHECK-NEXT: } -.attribute 4, 16 -.attribute 5, "rv64i2p0_m2p0_a2p0_f2p0_d2p0_c2p0" +# CHECK2: BuildAttributes { +# CHECK2-NEXT: FormatVersion: 0x41 +# CHECK2-NEXT: Section 1 { +# CHECK2-NEXT: SectionLength: 93 +# CHECK2-NEXT: Vendor: riscv +# CHECK2-NEXT: Tag: Tag_File (0x1) +# CHECK2-NEXT: Size: 83 +# CHECK2-NEXT: FileAttributes { +# CHECK2-NEXT: Attribute { +# CHECK2-NEXT: Tag: 4 +# CHECK2-NEXT: Value: 16 +# CHECK2-NEXT: TagName: stack_align +# CHECK2-NEXT: Description: Stack alignment is 16-bytes +# CHECK2-NEXT: } +# CHECK2-NEXT: Attribute { +# CHECK2-NEXT: Tag: 6 +# CHECK2-NEXT: Value: 1 +# CHECK2-NEXT: TagName: unaligned_access +# CHECK2-NEXT: Description: Unaligned access +# CHECK2-NEXT: } +# CHECK2-NEXT: Attribute { +# CHECK2-NEXT: Tag: 8 +# CHECK2-NEXT: TagName: priv_spec +# CHECK2-NEXT: Value: 2 +# CHECK2-NEXT: } +# CHECK2-NEXT: Attribute { +# CHECK2-NEXT: Tag: 5 +# CHECK2-NEXT: TagName: arch +# CHECK2-NEXT: Value: rv64i2p0_m2p0_a2p0_f2p0_d2p0_c2p0_zkt1p0_zve32f1p0_zve32x1p0_zvl32b1p0 +# CHECK2-NEXT: } +# CHECK2-NEXT: } +# CHECK2-NEXT: } +# CHECK2-NEXT: } + +#--- a.s +.attribute stack_align, 16 +.attribute arch, "rv64i2p0_m2p0_a2p0_f2p0_d2p0_c2p0" +.attribute unaligned_access, 0 + +#--- b.s +.attribute stack_align, 16 +.attribute arch, "rv64i2p0_m2p0_a2p0_f2p0_d2p0_c2p0" +.attribute priv_spec, 2 + +#--- c.s +.attribute stack_align, 16 +.attribute arch, "rv64i2p0_f2p0_zkt1p0_zve32f1p0_zve32x1p0_zvl32b1p0" +.attribute unaligned_access, 1 +.attribute priv_spec, 2 + +#--- invalid_ext.s +.section .riscv.attributes,"",@0x70000003 +.byte 0x41 +.long .Lend-.riscv.attributes-1 +.asciz "riscv" +.Lbegin: +.byte 1 +.long .Lend-.Lbegin +.byte 5 # Tag_RISCV_arch +.asciz "rv64i2p0_y2p0" +.Lend: + +#--- unaligned_access_0.s +.attribute unaligned_access, 0 + +#--- diff_stack_align.s +.attribute stack_align, 32 + +#--- diff_priv_spec.s +.attribute priv_spec, 3 + +#--- unknown0.s +.attribute 9, "0" +.attribute 22, 1 + +#--- unknown1.s +.attribute 9, "1" +.attribute 22, 2 diff --git a/llvm/include/llvm/Support/RISCVISAInfo.h b/llvm/include/llvm/Support/RISCVISAInfo.h --- a/llvm/include/llvm/Support/RISCVISAInfo.h +++ b/llvm/include/llvm/Support/RISCVISAInfo.h @@ -42,6 +42,9 @@ typedef std::map OrderedExtensionMap; + RISCVISAInfo(unsigned XLen, OrderedExtensionMap &Exts) + : XLen(XLen), FLen(0), MinVLen(0), MaxELen(0), MaxELenFp(0), Exts(Exts) {} + /// Parse RISCV ISA info from arch string. static llvm::Expected> parseArchString(StringRef Arch, bool EnableExperimentalExtension, @@ -73,6 +76,8 @@ static bool isSupportedExtension(StringRef Ext); static bool isSupportedExtension(StringRef Ext, unsigned MajorVersion, unsigned MinorVersion); + static llvm::Expected> + postProcessAndChecking(std::unique_ptr &&ISAInfo); private: RISCVISAInfo(unsigned XLen) @@ -95,9 +100,6 @@ void updateFLen(); void updateMinVLen(); void updateMaxELen(); - - static llvm::Expected> - postProcessAndChecking(std::unique_ptr &&ISAInfo); }; } // namespace llvm diff --git a/llvm/lib/Support/RISCVISAInfo.cpp b/llvm/lib/Support/RISCVISAInfo.cpp --- a/llvm/lib/Support/RISCVISAInfo.cpp +++ b/llvm/lib/Support/RISCVISAInfo.cpp @@ -745,6 +745,11 @@ errc::invalid_argument, "zvl*b requires v or zve* extension to also be specified"); + if (HasF && HasZfinx) + return createStringError( + errc::invalid_argument, + "zfinx extension can't be specified along with f extension"); + // Additional dependency checks. // TODO: The 'q' extension requires rv64. // TODO: It is illegal to specify 'e' extensions with 'f' and 'd'.