Skip to content

[Linux][Runtime][IRGen] Mark metadata sections as retained and support section GC. #72061

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,8 @@ endif()
# Which default linker to use. Prefer LLVM_USE_LINKER if it set, otherwise use
# our own defaults. This should only be possible in a unified (not stand alone)
# build environment.
include(GoldVersion)

if(LLVM_USE_LINKER)
set(SWIFT_USE_LINKER_default "${LLVM_USE_LINKER}")
elseif(SWIFT_HOST_VARIANT_SDK STREQUAL "ANDROID")
Expand All @@ -994,7 +996,17 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
elseif(DISTRO_NAME STREQUAL "Amazon Linux 2023")
set(SWIFT_USE_LINKER_default "lld")
else()
set(SWIFT_USE_LINKER_default "gold")
get_gold_version(gold_version)
if(NOT gold_version)
message(STATUS "GNU Gold not found; using lld instead")
set(SWIFT_USE_LINKER_default "lld")
elseif(gold_version VERSION_LESS "2.36")
message(STATUS "GNU Gold is too old (${gold_version}); using lld instead")
set(SWIFT_USE_LINKER_default "lld")
else()
message(STATUS "Using GNU Gold ${gold_version}")
set(SWIFT_USE_LINKER_default "gold")
endif()
endif()
set(SWIFT_USE_LINKER ${SWIFT_USE_LINKER_default} CACHE STRING
"Build Swift with a non-default linker")
Expand Down
18 changes: 18 additions & 0 deletions cmake/modules/GoldVersion.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Find the version of ld.gold, if installed.
#
# Versions prior to 2.36 break Swift programs because they won't coalesce
# sections with different SHF_GNU_RETAIN flags.
function(get_gold_version result_var_name)
find_program(gold_executable "ld.gold")
if(gold_executable)
execute_process(
COMMAND "${gold_executable}" "--version"
COMMAND "head" "-n" "1"
COMMAND "sed" "-e" "s/^.* (\\([^)]*\\)).*$/\\1/g;s/.* \\([0-9][0-9]*\\(\\.[0-9][0-9]*\\)*\\).*/\\1/g"
OUTPUT_VARIABLE gold_version
OUTPUT_STRIP_TRAILING_WHITESPACE)
set("${result_var_name}" "${gold_version}" PARENT_SCOPE)
else()
set("${result_var_name}" "" PARENT_SCOPE)
endif()
endfunction()
161 changes: 112 additions & 49 deletions include/swift/RemoteInspection/ReflectionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ class ReflectionContext
auto Buf =
this->getReader().readBytes(ImageStart, sizeof(typename T::Header));
if (!Buf)
return false;
return {};
auto Header = reinterpret_cast<typename T::Header *>(Buf.get());
assert(Header->magic == T::MagicNumber && "invalid MachO file");

Expand All @@ -262,7 +262,7 @@ class ReflectionContext
RemoteAddress(CmdStartAddress.getAddressData() + Offset),
SegmentCmdHdrSize);
if (!CmdBuf)
return false;
return {};
auto CmdHdr = reinterpret_cast<typename T::SegmentCmd *>(CmdBuf.get());
if (strncmp(CmdHdr->segname, "__TEXT", sizeof(CmdHdr->segname)) == 0) {
TextCommand = CmdHdr;
Expand All @@ -274,7 +274,7 @@ class ReflectionContext

// No __TEXT segment, bail out.
if (!TextCommand)
return false;
return {};

// Find the load command offset.
auto loadCmdOffset = ImageStart.getAddressData() + Offset + sizeof(typename T::Header);
Expand All @@ -284,7 +284,7 @@ class ReflectionContext
auto LoadCmdBuf = this->getReader().readBytes(
RemoteAddress(LoadCmdAddress), sizeof(typename T::SegmentCmd));
if (!LoadCmdBuf)
return false;
return {};
auto LoadCmd = reinterpret_cast<typename T::SegmentCmd *>(LoadCmdBuf.get());

// The sections start immediately after the load command.
Expand All @@ -294,7 +294,7 @@ class ReflectionContext
auto Sections = this->getReader().readBytes(
RemoteAddress(SectAddress), NumSect * sizeof(typename T::Section));
if (!Sections)
return false;
return {};

auto Slide = ImageStart.getAddressData() - TextCommand->vmaddr;
auto SectionsBuf = reinterpret_cast<const char *>(Sections.get());
Expand Down Expand Up @@ -346,7 +346,7 @@ class ReflectionContext
ReflStrMdSec.first == nullptr &&
ConformMdSec.first == nullptr &&
MPEnumMdSec.first == nullptr)
return false;
return {};

ReflectionInfo info = {{FieldMdSec.first, FieldMdSec.second},
{AssocTySec.first, AssocTySec.second},
Expand All @@ -371,7 +371,7 @@ class ReflectionContext
RemoteAddress(CmdStartAddress.getAddressData() + Offset),
SegmentCmdHdrSize);
if (!CmdBuf)
return false;
return {};
auto CmdHdr = reinterpret_cast<typename T::SegmentCmd *>(CmdBuf.get());
// Look for any segment name starting with __DATA or __AUTH.
if (strncmp(CmdHdr->segname, "__DATA", 6) == 0 ||
Expand All @@ -398,7 +398,7 @@ class ReflectionContext
auto DOSHdrBuf = this->getReader().readBytes(
ImageStart, sizeof(llvm::object::dos_header));
if (!DOSHdrBuf)
return false;
return {};
auto DOSHdr =
reinterpret_cast<const llvm::object::dos_header *>(DOSHdrBuf.get());
auto COFFFileHdrAddr = ImageStart.getAddressData() +
Expand All @@ -408,7 +408,7 @@ class ReflectionContext
auto COFFFileHdrBuf = this->getReader().readBytes(
RemoteAddress(COFFFileHdrAddr), sizeof(llvm::object::coff_file_header));
if (!COFFFileHdrBuf)
return false;
return {};
auto COFFFileHdr = reinterpret_cast<const llvm::object::coff_file_header *>(
COFFFileHdrBuf.get());

Expand All @@ -419,7 +419,7 @@ class ReflectionContext
RemoteAddress(SectionTableAddr),
sizeof(llvm::object::coff_section) * COFFFileHdr->NumberOfSections);
if (!SectionTableBuf)
return false;
return {};

auto findCOFFSectionByName =
[&](llvm::StringRef Name) -> std::pair<RemoteRef<void>, uint64_t> {
Expand Down Expand Up @@ -481,7 +481,7 @@ class ReflectionContext
ReflStrMdSec.first == nullptr &&
ConformMdSec.first == nullptr &&
MPEnumMdSec.first == nullptr)
return false;
return {};

ReflectionInfo Info = {{FieldMdSec.first, FieldMdSec.second},
{AssocTySec.first, AssocTySec.second},
Expand All @@ -502,7 +502,7 @@ class ReflectionContext
auto Buf = this->getReader().readBytes(ImageStart,
sizeof(llvm::object::dos_header));
if (!Buf)
return false;
return {};

auto DOSHdr = reinterpret_cast<const llvm::object::dos_header *>(Buf.get());

Expand All @@ -512,10 +512,10 @@ class ReflectionContext
Buf = this->getReader().readBytes(RemoteAddress(PEHeaderAddress),
sizeof(llvm::COFF::PEMagic));
if (!Buf)
return false;
return {};

if (memcmp(Buf.get(), llvm::COFF::PEMagic, sizeof(llvm::COFF::PEMagic)))
return false;
return {};

return readPECOFFSections(ImageStart, PotentialModuleNames);
}
Expand Down Expand Up @@ -550,7 +550,7 @@ class ReflectionContext

const void *Buf = readData(0, sizeof(typename T::Header));
if (!Buf)
return false;
return {};
auto Hdr = reinterpret_cast<const typename T::Header *>(Buf);
assert(Hdr->getFileClass() == T::ELFClass && "invalid ELF file class");

Expand All @@ -560,9 +560,9 @@ class ReflectionContext
uint16_t SectionEntrySize = Hdr->e_shentsize;

if (sizeof(typename T::Section) > SectionEntrySize)
return false;
return {};
if (SectionHdrNumEntries == 0)
return false;
return {};

// Collect all the section headers, we need them to look up the
// reflection sections (by name) and the string table.
Expand All @@ -573,7 +573,7 @@ class ReflectionContext
uint64_t Offset = SectionHdrAddress + (I * SectionEntrySize);
auto SecBuf = readData(Offset, sizeof(typename T::Section));
if (!SecBuf)
return false;
return {};
const typename T::Section *SecHdr =
reinterpret_cast<const typename T::Section *>(SecBuf);

Expand All @@ -597,11 +597,34 @@ class ReflectionContext

auto StrTabBuf = readData(StrTabOffset, StrTabSize);
if (!StrTabBuf)
return false;
return {};
auto StrTab = reinterpret_cast<const char *>(StrTabBuf);
bool Error = false;

// GNU ld and lld both merge sections regardless of the
// `SHF_GNU_RETAIN` flag. gold, presently, does not. The Swift
// compiler has a couple of switches that control whether or not
// the reflection sections are stripped; when these are enabled,
// it will _not_ set `SHF_GNU_RETAIN` on the reflection metadata
// sections. However, `swiftrt.o` contains declarations of the
// sections _with_ the `SHF_GNU_RETAIN` flag set, which makes
// sense since at runtime we will only expect to be able to access
// reflection metadata that we said we wanted to exist at runtime.
//
// The upshot is that when linking with gold, we can end up with
// two sets of reflection metadata sections. In a normal build
// where the compiler flags are the same for every linked object,
// we'll have *either* all retained *or* all un-retained sections
// (the retained sections will still exist because of `swiftrt.o`,
// but will be empty). The only time we'd expect to have a mix is
// where some code was compiled with a different setting of the
// metadata stripping flags. If that happens, the code below will
// simply add both sets of reflection sections, with the retained
// ones added first.
//
// See also https://sourceware.org/bugzilla/show_bug.cgi?id=31415.
auto findELFSectionByName =
[&](llvm::StringRef Name) -> std::pair<RemoteRef<void>, uint64_t> {
[&](llvm::StringRef Name, bool Retained) -> std::pair<RemoteRef<void>, uint64_t> {
if (Error)
return {nullptr, 0};
// Now for all the sections, find their name.
Expand All @@ -616,6 +639,8 @@ class ReflectionContext
std::string SecName(Start, StringSize);
if (SecName != Name)
continue;
if (Retained != bool(Hdr->sh_flags & llvm::ELF::SHF_GNU_RETAIN))
continue;
RemoteAddress SecStart =
RemoteAddress(ImageStart.getAddressData() + Hdr->sh_addr);
auto SecSize = Hdr->sh_size;
Expand Down Expand Up @@ -649,48 +674,86 @@ class ReflectionContext

SwiftObjectFileFormatELF ObjectFileFormat;
auto FieldMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::fieldmd));
ObjectFileFormat.getSectionName(ReflectionSectionKind::fieldmd), true);
auto AssocTySec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::assocty));
ObjectFileFormat.getSectionName(ReflectionSectionKind::assocty), true);
auto BuiltinTySec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::builtin));
ObjectFileFormat.getSectionName(ReflectionSectionKind::builtin), true);
auto CaptureSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::capture));
ObjectFileFormat.getSectionName(ReflectionSectionKind::capture), true);
auto TypeRefMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::typeref));
ObjectFileFormat.getSectionName(ReflectionSectionKind::typeref), true);
auto ReflStrMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::reflstr));
ObjectFileFormat.getSectionName(ReflectionSectionKind::reflstr), true);
auto ConformMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::conform));
ObjectFileFormat.getSectionName(ReflectionSectionKind::conform), true);
auto MPEnumMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::mpenum));
ObjectFileFormat.getSectionName(ReflectionSectionKind::mpenum), true);

if (Error)
return false;
return {};

std::optional<uint32_t> result = {};

// We succeed if at least one of the sections is present in the
// ELF executable.
if (FieldMdSec.first == nullptr &&
AssocTySec.first == nullptr &&
BuiltinTySec.first == nullptr &&
CaptureSec.first == nullptr &&
TypeRefMdSec.first == nullptr &&
ReflStrMdSec.first == nullptr &&
ConformMdSec.first == nullptr &&
MPEnumMdSec.first == nullptr)
return false;
if (FieldMdSec.first || AssocTySec.first || BuiltinTySec.first ||
CaptureSec.first || TypeRefMdSec.first || ReflStrMdSec.first ||
ConformMdSec.first || MPEnumMdSec.first) {
ReflectionInfo info = {{FieldMdSec.first, FieldMdSec.second},
{AssocTySec.first, AssocTySec.second},
{BuiltinTySec.first, BuiltinTySec.second},
{CaptureSec.first, CaptureSec.second},
{TypeRefMdSec.first, TypeRefMdSec.second},
{ReflStrMdSec.first, ReflStrMdSec.second},
{ConformMdSec.first, ConformMdSec.second},
{MPEnumMdSec.first, MPEnumMdSec.second},
PotentialModuleNames};
result = this->addReflectionInfo(info);
}

ReflectionInfo info = {{FieldMdSec.first, FieldMdSec.second},
{AssocTySec.first, AssocTySec.second},
{BuiltinTySec.first, BuiltinTySec.second},
{CaptureSec.first, CaptureSec.second},
{TypeRefMdSec.first, TypeRefMdSec.second},
{ReflStrMdSec.first, ReflStrMdSec.second},
{ConformMdSec.first, ConformMdSec.second},
{MPEnumMdSec.first, MPEnumMdSec.second},
PotentialModuleNames};
// Also check for the non-retained versions of the sections; we'll
// only return a single reflection info ID if both are found (and it'll
// be the one for the retained sections if we have them), but we'll
// still add all the reflection information.
FieldMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::fieldmd), false);
AssocTySec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::assocty), false);
BuiltinTySec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::builtin), false);
CaptureSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::capture), false);
TypeRefMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::typeref), false);
ReflStrMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::reflstr), false);
ConformMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::conform), false);
MPEnumMdSec = findELFSectionByName(
ObjectFileFormat.getSectionName(ReflectionSectionKind::mpenum), false);

if (Error)
return {};

return this->addReflectionInfo(info);
if (FieldMdSec.first || AssocTySec.first || BuiltinTySec.first ||
CaptureSec.first || TypeRefMdSec.first || ReflStrMdSec.first ||
ConformMdSec.first || MPEnumMdSec.first) {
ReflectionInfo info = {{FieldMdSec.first, FieldMdSec.second},
{AssocTySec.first, AssocTySec.second},
{BuiltinTySec.first, BuiltinTySec.second},
{CaptureSec.first, CaptureSec.second},
{TypeRefMdSec.first, TypeRefMdSec.second},
{ReflStrMdSec.first, ReflStrMdSec.second},
{ConformMdSec.first, ConformMdSec.second},
{MPEnumMdSec.first, MPEnumMdSec.second},
PotentialModuleNames};
auto rid = this->addReflectionInfo(info);
if (!result)
result = rid;
}

return result;
}

/// Parses metadata information from an ELF image. Because the Section
Expand Down Expand Up @@ -746,7 +809,7 @@ class ReflectionContext
// Read the first few bytes to look for a magic header.
auto Magic = this->getReader().readBytes(ImageStart, sizeof(uint32_t));
if (!Magic)
return false;
return {};

uint32_t MagicWord;
memcpy(&MagicWord, Magic.get(), sizeof(MagicWord));
Expand Down
11 changes: 0 additions & 11 deletions lib/Driver/UnixToolChains.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,17 +231,6 @@ toolchains::GenericUnix::constructInvocation(const DynamicLinkJobAction &job,
#else
Arguments.push_back(context.Args.MakeArgString("-fuse-ld=" + Linker));
#endif
// Starting with lld 13, Swift stopped working with the lld --gc-sections
// implementation for ELF, unless -z nostart-stop-gc is also passed to lld:
//
// https://reviews.llvm.org/D96914
if (Linker == "lld" || (Linker.length() > 5 &&
Linker.substr(Linker.length() - 6) == "ld.lld")) {
Arguments.push_back("-Xlinker");
Arguments.push_back("-z");
Arguments.push_back("-Xlinker");
Arguments.push_back("nostart-stop-gc");
}
}

// Configure the toolchain.
Expand Down
Loading