[lld-macho] Allow linking with ABI compatible architectures

Linking fails when targeting `x86_64-apple-darwin` for runtimes. The issue
is that LLD strictly assumes the target architecture be present in the tbd
files (which isn't always true). For example, when targeting `x86_64h`, it should
work with `x86_64` because they are ABI compatible. This is also inline with what
ld64 does.

An environment variable (which ld64 also supports) is also added to preserve the
existing behavior of strict architecture matching.

Reviewed By: #lld-macho, int3

Differential Revision: https://reviews.llvm.org/D130683
This commit is contained in:
Vincent Lee 2022-07-27 23:31:21 -07:00
parent 520d29f381
commit f030132c72
4 changed files with 54 additions and 5 deletions

View File

@ -131,6 +131,7 @@ struct Configuration {
bool omitDebugInfo = false;
bool warnDylibInstallName = false;
bool ignoreOptimizationHints = false;
bool forceExactCpuSubtypeMatch = false;
uint32_t headerPad;
uint32_t dylibCompatibilityVersion = 0;
uint32_t dylibCurrentVersion = 0;

View File

@ -1399,6 +1399,8 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
config->callGraphProfileSort = args.hasFlag(
OPT_call_graph_profile_sort, OPT_no_call_graph_profile_sort, true);
config->printSymbolOrder = args.getLastArgValue(OPT_print_symbol_order);
config->forceExactCpuSubtypeMatch =
getenv("LD_DYLIB_CPU_SUBTYPES_MUST_MATCH");
for (const Arg *arg : args.filtered(OPT_alias)) {
config->aliasedSymbols.push_back(

View File

@ -1067,8 +1067,11 @@ template <class LP> void ObjFile::parse() {
auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
auto *hdr = reinterpret_cast<const Header *>(mb.getBufferStart());
Architecture arch = getArchitectureFromCpuType(hdr->cputype, hdr->cpusubtype);
if (arch != config->arch()) {
uint32_t cpuType;
std::tie(cpuType, std::ignore) = getCPUTypeFromArchitecture(config->arch());
if (hdr->cputype != cpuType) {
Architecture arch =
getArchitectureFromCpuType(hdr->cputype, hdr->cpusubtype);
auto msg = config->errorForArchMismatch
? static_cast<void (*)(const Twine &)>(error)
: warn;
@ -1865,6 +1868,36 @@ static bool skipPlatformCheckForCatalyst(const InterfaceFile &interface,
MachO::Target(config->arch(), PLATFORM_MACOS));
}
static bool isArchABICompatible(ArchitectureSet archSet,
Architecture targetArch) {
uint32_t cpuType;
uint32_t targetCpuType;
std::tie(targetCpuType, std::ignore) = getCPUTypeFromArchitecture(targetArch);
return llvm::any_of(archSet, [&](const auto &p) {
std::tie(cpuType, std::ignore) = getCPUTypeFromArchitecture(p);
return cpuType == targetCpuType;
});
}
static bool isTargetPlatformArchCompatible(
InterfaceFile::const_target_range interfaceTargets, Target target) {
if (is_contained(interfaceTargets, target))
return true;
if (config->forceExactCpuSubtypeMatch)
return false;
ArchitectureSet archSet;
for (const auto &p : interfaceTargets)
if (p.Platform == target.Platform)
archSet.set(p.Arch);
if (archSet.empty())
return false;
return isArchABICompatible(archSet, target.Arch);
}
DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella,
bool isBundleLoader, bool explicitlyLinked)
: InputFile(DylibKind, interface), refState(RefState::Unreferenced),
@ -1884,7 +1917,8 @@ DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella,
inputFiles.insert(this);
if (!is_contained(skipPlatformChecks, installName) &&
!is_contained(interface.targets(), config->platformInfo.target) &&
!isTargetPlatformArchCompatible(interface.targets(),
config->platformInfo.target) &&
!skipPlatformCheckForCatalyst(interface, explicitlyLinked)) {
error(toString(this) + " is incompatible with " +
std::string(config->platformInfo.target));
@ -1907,7 +1941,7 @@ DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella,
std::vector<const llvm::MachO::Symbol *> normalSymbols;
normalSymbols.reserve(interface.symbolsCount());
for (const auto *symbol : interface.symbols()) {
if (!symbol->getArchitectures().has(config->arch()))
if (!isArchABICompatible(symbol->getArchitectures(), config->arch()))
continue;
if (handleLDSymbol(symbol->getName()))
continue;
@ -1950,7 +1984,7 @@ void DylibFile::parseReexports(const InterfaceFile &interface) {
for (const InterfaceFileRef &intfRef : interface.reexportedLibraries()) {
InterfaceFile::const_target_range targets = intfRef.targets();
if (is_contained(skipPlatformChecks, intfRef.getInstallName()) ||
is_contained(targets, config->platformInfo.target))
isTargetPlatformArchCompatible(targets, config->platformInfo.target))
loadReexport(intfRef.getInstallName(), exportingFile, topLevel);
}
}

View File

@ -7,6 +7,18 @@
# RUN: %lld -o %t/test -lSystem -lc++ -framework CoreFoundation %t/libNested.tbd %t/test.o
# RUN: llvm-objdump --bind --no-show-raw-insn -d -r %t/test | FileCheck %s
## Targeting an arch not listed in the tbd should fallback to an ABI compatible arch
# RUN: %lld -arch x86_64h -o %t/test-compat -lSystem -lc++ -framework CoreFoundation %t/libNested.tbd %t/test.o
# RUN: llvm-objdump --bind --no-show-raw-insn -d -r %t/test-compat | FileCheck %s
## Setting LD_DYLIB_CPU_SUBTYPES_MUST_MATCH forces exact target arch match.
# RUN: env LD_DYLIB_CPU_SUBTYPES_MUST_MATCH=1 not %lld -arch x86_64h -o /dev/null -lSystem -lc++ -framework \
# RUN: CoreFoundation %t/libNested.tbd %t/test.o 2>&1 | FileCheck %s -check-prefix=INCOMPATIBLE
# INCOMPATIBLE: error: {{.*}}libSystem.tbd(/usr/lib/libSystem.dylib) is incompatible with x86_64h (macOS)
# INCOMPATIBLE-NEXT: error: {{.*}}libc++.tbd(/usr/lib/libc++.dylib) is incompatible with x86_64h (macOS)
# INCOMPATIBLE-NEXT: error: {{.*}}CoreFoundation.tbd(/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation) is incompatible with x86_64h (macOS)
## libReexportSystem.tbd tests that we can reference symbols from a 2nd-level
## tapi document, re-exported by a top-level tapi document, which itself is
## re-exported by another top-level tapi document.