diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -131,6 +131,7 @@ bool omitDebugInfo = false; bool warnDylibInstallName = false; bool ignoreOptimizationHints = false; + bool forceExactCpuSubtypeMatch = false; uint32_t headerPad; uint32_t dylibCompatibilityVersion = 0; uint32_t dylibCurrentVersion = 0; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -1399,6 +1399,8 @@ 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( diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -1067,8 +1067,11 @@ auto *buf = reinterpret_cast(mb.getBufferStart()); auto *hdr = reinterpret_cast(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(error) : warn; @@ -1865,6 +1868,36 @@ 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 @@ 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 @@ std::vector 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 @@ 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); } } diff --git a/lld/test/MachO/tapi-link.s b/lld/test/MachO/tapi-link.s --- a/lld/test/MachO/tapi-link.s +++ b/lld/test/MachO/tapi-link.s @@ -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.