mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
- Add SPI availability information to `APIAvailability` from attribute `@_spi_available`. - Correctly obtain effective availability for nested declarations without immediate availability attributes. Resolves rdar://159702280 <!-- If this pull request is targeting a release branch, please fill out the following form: https://github.com/swiftlang/.github/blob/main/PULL_REQUEST_TEMPLATE/release.md?plain=1 Otherwise, replace this comment with a description of your changes and rationale. Provide links to external references/discussions if appropriate. If this pull request resolves any GitHub issues, link them like so: Resolves <link to issue>, resolves <link to another issue>. For more information about linking a pull request to an issue, see: https://docs.github.com/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue --> <!-- Before merging this pull request, you must run the Swift continuous integration tests. For information about triggering CI builds via @swift-ci, see: https://github.com/apple/swift/blob/main/docs/ContinuousIntegration.md#swift-ci Thank you for your contribution to Swift! -->
986 lines
36 KiB
C++
986 lines
36 KiB
C++
//===--- TBDGen.cpp - Swift TBD Generation --------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file implements the entrypoints into TBD file generation.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/IRGen/TBDGen.h"
|
|
|
|
#include "swift/AST/ASTMangler.h"
|
|
#include "swift/AST/ASTVisitor.h"
|
|
#include "swift/AST/DiagnosticsFrontend.h"
|
|
#include "swift/AST/Module.h"
|
|
#include "swift/AST/ParameterList.h"
|
|
#include "swift/AST/PropertyWrappers.h"
|
|
#include "swift/AST/SourceFile.h"
|
|
#include "swift/AST/SynthesizedFileUnit.h"
|
|
#include "swift/AST/TBDGenRequests.h"
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/Basic/Defer.h"
|
|
#include "swift/Basic/LLVM.h"
|
|
#include "swift/Basic/SourceManager.h"
|
|
#include "swift/ClangImporter/ClangImporter.h"
|
|
#include "swift/IRGen/IRGenPublic.h"
|
|
#include "swift/IRGen/Linking.h"
|
|
#include "swift/SIL/FormalLinkage.h"
|
|
#include "swift/SIL/SILDeclRef.h"
|
|
#include "swift/SIL/SILModule.h"
|
|
#include "swift/SIL/SILSymbolVisitor.h"
|
|
#include "swift/SIL/SILVTableVisitor.h"
|
|
#include "swift/SIL/SILWitnessTable.h"
|
|
#include "swift/SIL/SILWitnessVisitor.h"
|
|
#include "swift/SIL/TypeLowering.h"
|
|
#include "clang/Basic/TargetInfo.h"
|
|
#include "llvm/ADT/StringSet.h"
|
|
#include "llvm/ADT/StringSwitch.h"
|
|
#include "llvm/IR/Mangler.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Support/Process.h"
|
|
#include "llvm/Support/YAMLParser.h"
|
|
#include "llvm/Support/YAMLTraits.h"
|
|
#include "llvm/TextAPI/InterfaceFile.h"
|
|
#include "llvm/TextAPI/Symbol.h"
|
|
#include "llvm/TextAPI/TextAPIReader.h"
|
|
#include "llvm/TextAPI/TextAPIWriter.h"
|
|
|
|
#include "APIGen.h"
|
|
#include "TBDGenVisitor.h"
|
|
|
|
using namespace swift;
|
|
using namespace swift::irgen;
|
|
using namespace swift::tbdgen;
|
|
using namespace llvm::yaml;
|
|
using StringSet = llvm::StringSet<>;
|
|
using EncodeKind = llvm::MachO::EncodeKind;
|
|
using SymbolFlags = llvm::MachO::SymbolFlags;
|
|
|
|
TBDGenVisitor::TBDGenVisitor(const TBDGenDescriptor &desc,
|
|
APIRecorder &recorder)
|
|
: TBDGenVisitor(desc.getTarget(), desc.getDataLayoutString(),
|
|
desc.getParentModule(), desc.getOptions(), recorder) {}
|
|
|
|
void TBDGenVisitor::addSymbolInternal(StringRef name, EncodeKind kind,
|
|
SymbolSource source, SymbolFlags flags) {
|
|
if (!source.isLinkerDirective() && Opts.LinkerDirectivesOnly)
|
|
return;
|
|
|
|
#ifndef NDEBUG
|
|
if (kind == EncodeKind::GlobalSymbol) {
|
|
if (!DuplicateSymbolChecker.insert(name).second) {
|
|
llvm::dbgs() << "TBDGen duplicate symbol: " << name << '\n';
|
|
assert(false && "TBDGen symbol appears twice");
|
|
}
|
|
}
|
|
#endif
|
|
recorder.addSymbol(name, kind, source,
|
|
DeclStack.empty() ? nullptr : DeclStack.back(), flags);
|
|
}
|
|
|
|
static std::vector<OriginallyDefinedInAttr::ActiveVersion>
|
|
getAllMovedPlatformVersions(Decl *D) {
|
|
StringRef Name = D->getDeclContext()->getParentModule()->getName().str();
|
|
|
|
std::vector<OriginallyDefinedInAttr::ActiveVersion> Results;
|
|
for (auto *attr: D->getAttrs()) {
|
|
if (auto *ODA = dyn_cast<OriginallyDefinedInAttr>(attr)) {
|
|
auto Active = ODA->isActivePlatform(D->getASTContext());
|
|
if (Active.has_value() && Active->LinkerModuleName != Name) {
|
|
Results.push_back(*Active);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Results;
|
|
}
|
|
|
|
static StringRef getLinkerPlatformName(LinkerPlatformId Id) {
|
|
switch (Id) {
|
|
#define LD_PLATFORM(Name, Id) case LinkerPlatformId::Name: return #Name;
|
|
#include "ldPlatformKinds.def"
|
|
}
|
|
llvm_unreachable("unrecognized platform id");
|
|
}
|
|
|
|
static std::optional<LinkerPlatformId> getLinkerPlatformId(StringRef Platform) {
|
|
return llvm::StringSwitch<std::optional<LinkerPlatformId>>(Platform)
|
|
#define LD_PLATFORM(Name, Id) .Case(#Name, LinkerPlatformId::Name)
|
|
#include "ldPlatformKinds.def"
|
|
.Default(std::nullopt);
|
|
}
|
|
|
|
StringRef InstallNameStore::getInstallName(LinkerPlatformId Id) const {
|
|
auto It = PlatformInstallName.find(Id);
|
|
if (It == PlatformInstallName.end())
|
|
return InstallName;
|
|
else
|
|
return It->second;
|
|
}
|
|
|
|
static std::string getScalaNodeText(Node *N) {
|
|
SmallString<32> Buffer;
|
|
return cast<ScalarNode>(N)->getValue(Buffer).str();
|
|
}
|
|
|
|
static std::set<LinkerPlatformId> getSequenceNodePlatformList(ASTContext &Ctx,
|
|
Node *N) {
|
|
std::set<LinkerPlatformId> Results;
|
|
for (auto &E: *cast<SequenceNode>(N)) {
|
|
auto Platform = getScalaNodeText(&E);
|
|
auto Id = getLinkerPlatformId(Platform);
|
|
if (Id.has_value()) {
|
|
Results.insert(*Id);
|
|
} else {
|
|
// Diagnose unrecognized platform name.
|
|
Ctx.Diags.diagnose(SourceLoc(), diag::unknown_platform_name, Platform);
|
|
}
|
|
}
|
|
return Results;
|
|
}
|
|
|
|
/// Parse an entry like this, where the "platforms" key-value pair is optional:
|
|
/// {
|
|
/// "module": "Foo",
|
|
/// "platforms": ["macOS"],
|
|
/// "install_name": "/System/MacOS"
|
|
/// },
|
|
static int
|
|
parseEntry(ASTContext &Ctx,
|
|
Node *Node, std::map<std::string, InstallNameStore> &Stores) {
|
|
if (auto *SN = cast<SequenceNode>(Node)) {
|
|
for (auto It = SN->begin(); It != SN->end(); ++It) {
|
|
auto *MN = cast<MappingNode>(&*It);
|
|
std::string ModuleName;
|
|
std::string InstallName;
|
|
std::optional<std::set<LinkerPlatformId>> Platforms;
|
|
for (auto &Pair: *MN) {
|
|
auto Key = getScalaNodeText(Pair.getKey());
|
|
auto* Value = Pair.getValue();
|
|
if (Key == "module") {
|
|
ModuleName = getScalaNodeText(Value);
|
|
} else if (Key == "platforms") {
|
|
Platforms = getSequenceNodePlatformList(Ctx, Value);
|
|
} else if (Key == "install_name") {
|
|
InstallName = getScalaNodeText(Value);
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
if (ModuleName.empty() || InstallName.empty())
|
|
return 1;
|
|
auto &Store = Stores.insert(std::make_pair(ModuleName,
|
|
InstallNameStore())).first->second;
|
|
if (Platforms.has_value()) {
|
|
// This install name is platform-specific.
|
|
for (auto Id: Platforms.value()) {
|
|
Store.PlatformInstallName[Id] = InstallName;
|
|
}
|
|
} else {
|
|
// The install name is the default one.
|
|
Store.InstallName = InstallName;
|
|
}
|
|
}
|
|
} else {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::unique_ptr<std::map<std::string, InstallNameStore>>
|
|
TBDGenVisitor::parsePreviousModuleInstallNameMap() {
|
|
StringRef FileName = Opts.ModuleInstallNameMapPath;
|
|
// Nothing to parse.
|
|
if (FileName.empty())
|
|
return nullptr;
|
|
namespace yaml = llvm::yaml;
|
|
ASTContext &Ctx = SwiftModule->getASTContext();
|
|
std::unique_ptr<std::map<std::string, InstallNameStore>> pResult(
|
|
new std::map<std::string, InstallNameStore>());
|
|
auto &AllInstallNames = *pResult;
|
|
|
|
// Load the input file.
|
|
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileBufOrErr =
|
|
llvm::MemoryBuffer::getFile(FileName);
|
|
if (!FileBufOrErr) {
|
|
Ctx.Diags.diagnose(SourceLoc(), diag::previous_installname_map_missing,
|
|
FileName);
|
|
return nullptr;
|
|
}
|
|
StringRef Buffer = FileBufOrErr->get()->getBuffer();
|
|
|
|
// Use a new source manager instead of the one from ASTContext because we
|
|
// don't want the Json file to be persistent.
|
|
SourceManager SM;
|
|
yaml::Stream Stream(llvm::MemoryBufferRef(Buffer, FileName),
|
|
SM.getLLVMSourceMgr());
|
|
for (auto DI = Stream.begin(); DI != Stream.end(); ++ DI) {
|
|
assert(DI != Stream.end() && "Failed to read a document");
|
|
yaml::Node *N = DI->getRoot();
|
|
assert(N && "Failed to find a root");
|
|
if (parseEntry(Ctx, N, AllInstallNames)) {
|
|
Ctx.Diags.diagnose(SourceLoc(), diag::previous_installname_map_corrupted,
|
|
FileName);
|
|
return nullptr;
|
|
}
|
|
}
|
|
return pResult;
|
|
}
|
|
|
|
static LinkerPlatformId
|
|
getLinkerPlatformId(OriginallyDefinedInAttr::ActiveVersion Ver,
|
|
ASTContext &Ctx) {
|
|
auto target =
|
|
Ver.ForTargetVariant ? Ctx.LangOpts.TargetVariant : Ctx.LangOpts.Target;
|
|
bool isSimulator = target ? target->isSimulatorEnvironment() : false;
|
|
|
|
switch(Ver.Platform) {
|
|
case swift::PlatformKind::none:
|
|
llvm_unreachable("cannot find platform kind");
|
|
case swift::PlatformKind::DriverKit:
|
|
llvm_unreachable("not used for this platform");
|
|
case swift::PlatformKind::Swift:
|
|
llvm_unreachable("not used for this platform");
|
|
case PlatformKind::anyAppleOS:
|
|
llvm_unreachable("not used for this platform");
|
|
case swift::PlatformKind::FreeBSD:
|
|
llvm_unreachable("not used for this platform");
|
|
case swift::PlatformKind::OpenBSD:
|
|
llvm_unreachable("not used for this platform");
|
|
case swift::PlatformKind::Windows:
|
|
llvm_unreachable("not used for this platform");
|
|
case swift::PlatformKind::Android:
|
|
llvm_unreachable("not used for this platform");
|
|
|
|
case swift::PlatformKind::iOS:
|
|
case swift::PlatformKind::iOSApplicationExtension:
|
|
if (target && target->isMacCatalystEnvironment())
|
|
return LinkerPlatformId::macCatalyst;
|
|
return isSimulator ? LinkerPlatformId::iOS_sim : LinkerPlatformId::iOS;
|
|
case swift::PlatformKind::tvOS:
|
|
case swift::PlatformKind::tvOSApplicationExtension:
|
|
return isSimulator ? LinkerPlatformId::tvOS_sim : LinkerPlatformId::tvOS;
|
|
case swift::PlatformKind::watchOS:
|
|
case swift::PlatformKind::watchOSApplicationExtension:
|
|
return isSimulator ? LinkerPlatformId::watchOS_sim
|
|
: LinkerPlatformId::watchOS;
|
|
case swift::PlatformKind::macOS:
|
|
case swift::PlatformKind::macOSApplicationExtension:
|
|
return LinkerPlatformId::macOS;
|
|
case swift::PlatformKind::macCatalyst:
|
|
case swift::PlatformKind::macCatalystApplicationExtension:
|
|
return LinkerPlatformId::macCatalyst;
|
|
case swift::PlatformKind::visionOS:
|
|
case swift::PlatformKind::visionOSApplicationExtension:
|
|
return isSimulator ? LinkerPlatformId::xrOS_sim:
|
|
LinkerPlatformId::xrOS;
|
|
}
|
|
llvm_unreachable("invalid platform kind");
|
|
}
|
|
|
|
static StringRef
|
|
getLinkerPlatformName(OriginallyDefinedInAttr::ActiveVersion Ver,
|
|
ASTContext &Ctx) {
|
|
return getLinkerPlatformName(getLinkerPlatformId(Ver, Ctx));
|
|
}
|
|
|
|
/// Find the most relevant introducing version of the decl stack we have visited
|
|
/// so far.
|
|
static std::optional<llvm::VersionTuple>
|
|
getInnermostIntroVersion(ArrayRef<Decl *> DeclStack, PlatformKind Platform) {
|
|
for (auto It = DeclStack.rbegin(); It != DeclStack.rend(); ++ It) {
|
|
if (auto Result = (*It)->getIntroducedOSVersion(Platform))
|
|
return Result;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
/// Using the introducing version of a symbol as the start version to redirect
|
|
/// linkage path isn't sufficient. This is because the executable can be deployed
|
|
/// to OS versions that were before the symbol was introduced. When that happens,
|
|
/// strictly using the introductory version can lead to NOT redirecting.
|
|
static llvm::VersionTuple calculateLdPreviousVersionStart(ASTContext &ctx,
|
|
llvm::VersionTuple introVer) {
|
|
auto minDep = ctx.LangOpts.getMinPlatformVersion();
|
|
if (minDep < introVer)
|
|
return llvm::VersionTuple(1, 0);
|
|
return introVer;
|
|
}
|
|
|
|
void TBDGenVisitor::addLinkerDirectiveSymbolsLdPrevious(
|
|
StringRef name, llvm::MachO::EncodeKind kind) {
|
|
if (kind != llvm::MachO::EncodeKind::GlobalSymbol)
|
|
return;
|
|
if(DeclStack.empty())
|
|
return;
|
|
auto TopLevelDecl = DeclStack.front();
|
|
auto MovedVers = getAllMovedPlatformVersions(TopLevelDecl);
|
|
if (MovedVers.empty())
|
|
return;
|
|
assert(!MovedVers.empty());
|
|
assert(previousInstallNameMap);
|
|
auto &Ctx = TopLevelDecl->getASTContext();
|
|
for (auto &Ver: MovedVers) {
|
|
auto IntroVer = getInnermostIntroVersion(DeclStack, Ver.Platform);
|
|
assert(IntroVer && "cannot find OS intro version");
|
|
if (!IntroVer.has_value())
|
|
continue;
|
|
// This decl is available after the top-level symbol has been moved here,
|
|
// so we don't need the linker directives.
|
|
if (*IntroVer >= Ver.Version)
|
|
continue;
|
|
auto PlatformNumber = getLinkerPlatformId(Ver, Ctx);
|
|
auto It = previousInstallNameMap->find(Ver.LinkerModuleName.str());
|
|
if (It == previousInstallNameMap->end()) {
|
|
Ctx.Diags.diagnose(SourceLoc(), diag::cannot_find_install_name,
|
|
Ver.LinkerModuleName, getLinkerPlatformName(Ver, Ctx));
|
|
continue;
|
|
}
|
|
auto InstallName = It->second.getInstallName(PlatformNumber);
|
|
if (InstallName.empty()) {
|
|
Ctx.Diags.diagnose(SourceLoc(), diag::cannot_find_install_name,
|
|
Ver.LinkerModuleName, getLinkerPlatformName(Ver, Ctx));
|
|
continue;
|
|
}
|
|
llvm::SmallString<64> Buffer;
|
|
llvm::raw_svector_ostream OS(Buffer);
|
|
// Empty compatible version indicates using the current compatible version.
|
|
StringRef ComptibleVersion = "";
|
|
OS << "$ld$previous$";
|
|
OS << InstallName << "$";
|
|
OS << ComptibleVersion << "$";
|
|
OS << std::to_string(static_cast<uint8_t>(PlatformNumber)) << "$";
|
|
static auto getMinor = [](std::optional<unsigned> Minor) {
|
|
return Minor.has_value() ? *Minor : 0;
|
|
};
|
|
auto verStart = calculateLdPreviousVersionStart(Ctx, *IntroVer);
|
|
OS << verStart.getMajor() << "." << getMinor(verStart.getMinor()) << "$";
|
|
OS << Ver.Version.getMajor() << "." << getMinor(Ver.Version.getMinor()) << "$";
|
|
OS << name << "$";
|
|
addSymbolInternal(OS.str(), EncodeKind::GlobalSymbol,
|
|
SymbolSource::forLinkerDirective(), SymbolFlags::Data);
|
|
}
|
|
}
|
|
|
|
void TBDGenVisitor::addLinkerDirectiveSymbolsLdHide(
|
|
StringRef name, llvm::MachO::EncodeKind kind) {
|
|
if (kind != llvm::MachO::EncodeKind::GlobalSymbol)
|
|
return;
|
|
if (DeclStack.empty())
|
|
return;
|
|
auto TopLevelDecl = DeclStack.front();
|
|
auto MovedVers = getAllMovedPlatformVersions(TopLevelDecl);
|
|
if (MovedVers.empty())
|
|
return;
|
|
assert(!MovedVers.empty());
|
|
|
|
// Using $ld$add and $ld$hide cannot encode platform name in the version number,
|
|
// so we can only handle one version.
|
|
// FIXME: use $ld$previous instead
|
|
auto MovedVer = MovedVers.front().Version;
|
|
auto Platform = MovedVers.front().Platform;
|
|
unsigned Major[2];
|
|
unsigned Minor[2];
|
|
Major[1] = MovedVer.getMajor();
|
|
Minor[1] = MovedVer.getMinor().has_value() ? *MovedVer.getMinor(): 0;
|
|
auto IntroVer = getInnermostIntroVersion(DeclStack, Platform);
|
|
assert(IntroVer && "cannot find the start point of availability");
|
|
if (!IntroVer.has_value())
|
|
return;
|
|
// This decl is available after the top-level symbol has been moved here,
|
|
// so we don't need the linker directives.
|
|
if (*IntroVer >= MovedVer)
|
|
return;
|
|
Major[0] = IntroVer->getMajor();
|
|
Minor[0] = IntroVer->getMinor().has_value() ? IntroVer->getMinor().value() : 0;
|
|
for (auto CurMaj = Major[0]; CurMaj <= Major[1]; ++ CurMaj) {
|
|
unsigned MinRange[2] = {0, 31};
|
|
if (CurMaj == Major[0])
|
|
MinRange[0] = Minor[0];
|
|
if (CurMaj == Major[1])
|
|
MinRange[1] = Minor[1];
|
|
for (auto CurMin = MinRange[0]; CurMin != MinRange[1]; ++ CurMin) {
|
|
llvm::SmallString<64> Buffer;
|
|
llvm::raw_svector_ostream OS(Buffer);
|
|
OS << "$ld$hide$os" << CurMaj << "." << CurMin << "$" << name;
|
|
addSymbolInternal(OS.str(), EncodeKind::GlobalSymbol,
|
|
SymbolSource::forLinkerDirective(), SymbolFlags::Data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TBDGenVisitor::addSymbol(StringRef name, SymbolSource source,
|
|
SymbolFlags flags, EncodeKind kind) {
|
|
// The linker expects to see mangled symbol names in TBD files,
|
|
// except when being passed objective c classes,
|
|
// so make sure to mangle before inserting the symbol.
|
|
SmallString<32> mangled;
|
|
if (kind == EncodeKind::ObjectiveCClass) {
|
|
mangled = name;
|
|
} else {
|
|
if (!DataLayout)
|
|
DataLayout = llvm::DataLayout(DataLayoutDescription);
|
|
llvm::Mangler::getNameWithPrefix(mangled, name, *DataLayout);
|
|
}
|
|
|
|
addSymbolInternal(mangled, kind, source, flags);
|
|
if (previousInstallNameMap) {
|
|
addLinkerDirectiveSymbolsLdPrevious(mangled, kind);
|
|
} else {
|
|
addLinkerDirectiveSymbolsLdHide(mangled, kind);
|
|
}
|
|
}
|
|
|
|
bool TBDGenVisitor::willVisitDecl(Decl *D) {
|
|
if (!D->isAvailableDuringLowering())
|
|
return false;
|
|
|
|
// A @_silgen_name("...") function without a body only exists to
|
|
// forward-declare a symbol from another library.
|
|
if (auto AFD = dyn_cast<AbstractFunctionDecl>(D))
|
|
if (!AFD->hasBody() && AFD->getAttrs().hasAttribute<SILGenNameAttr>())
|
|
return false;
|
|
|
|
DeclStack.push_back(D);
|
|
return true;
|
|
}
|
|
|
|
void TBDGenVisitor::didVisitDecl(Decl *D) {
|
|
assert(DeclStack.back() == D);
|
|
DeclStack.pop_back();
|
|
}
|
|
|
|
void TBDGenVisitor::addFunction(SILDeclRef declRef) {
|
|
// If there is a specific symbol name this should have at the IR level,
|
|
// use it instead.
|
|
if (auto asmName = declRef.getAsmName()) {
|
|
addSymbol(*asmName, SymbolSource::forSILDeclRef(declRef),
|
|
SymbolFlags::Text);
|
|
return;
|
|
}
|
|
|
|
|
|
addSymbol(declRef.mangle(), SymbolSource::forSILDeclRef(declRef),
|
|
SymbolFlags::Text);
|
|
}
|
|
|
|
void TBDGenVisitor::addFunction(StringRef name, SILDeclRef declRef) {
|
|
addSymbol(name, SymbolSource::forSILDeclRef(declRef), SymbolFlags::Text);
|
|
}
|
|
|
|
void TBDGenVisitor::addGlobalVar(VarDecl *VD) {
|
|
Mangle::ASTMangler mangler(VD->getASTContext());
|
|
addSymbol(mangler.mangleEntity(VD), SymbolSource::forGlobal(VD),
|
|
SymbolFlags::Data);
|
|
}
|
|
|
|
void TBDGenVisitor::addLinkEntity(LinkEntity entity) {
|
|
auto linkage =
|
|
LinkInfo::get(UniversalLinkInfo, SwiftModule, entity, ForDefinition);
|
|
|
|
SymbolFlags flags = entity.isData() ? SymbolFlags::Data : SymbolFlags::Text;
|
|
addSymbol(linkage.getName(), SymbolSource::forIRLinkEntity(entity), flags);
|
|
}
|
|
|
|
void TBDGenVisitor::addObjCInterface(ClassDecl *CD) {
|
|
// FIXME: We ought to have a symbol source for this.
|
|
SmallString<128> buffer;
|
|
addSymbol(CD->getObjCRuntimeName(buffer), SymbolSource::forUnknown(),
|
|
SymbolFlags::Data, EncodeKind::ObjectiveCClass);
|
|
recorder.addObjCInterface(CD);
|
|
}
|
|
|
|
void TBDGenVisitor::addObjCMethod(AbstractFunctionDecl *AFD) {
|
|
if (auto *CD = dyn_cast<ClassDecl>(AFD->getDeclContext()))
|
|
recorder.addObjCMethod(CD, SILDeclRef(AFD));
|
|
else if (auto *ED = dyn_cast<ExtensionDecl>(AFD->getDeclContext()))
|
|
recorder.addObjCMethod(ED, SILDeclRef(AFD));
|
|
}
|
|
|
|
void TBDGenVisitor::addProtocolWitnessThunk(RootProtocolConformance *C,
|
|
ValueDecl *requirementDecl) {
|
|
Mangle::ASTMangler Mangler(requirementDecl->getASTContext());
|
|
|
|
std::string decorated = Mangler.mangleWitnessThunk(C, requirementDecl);
|
|
// FIXME: We should have a SILDeclRef SymbolSource for this.
|
|
addSymbol(decorated, SymbolSource::forUnknown(), SymbolFlags::Text);
|
|
|
|
if (requirementDecl->isProtocolRequirement()) {
|
|
ValueDecl *PWT = C->getWitness(requirementDecl).getDecl();
|
|
if (const auto *AFD = dyn_cast<AbstractFunctionDecl>(PWT))
|
|
if (AFD->hasAsync())
|
|
addSymbol(decorated + "Tu", SymbolSource::forUnknown(),
|
|
SymbolFlags::Text);
|
|
auto *accessor = dyn_cast<AccessorDecl>(PWT);
|
|
if (accessor &&
|
|
requiresFeatureCoroutineAccessors(accessor->getAccessorKind()))
|
|
addSymbol(decorated + "Twc", SymbolSource::forUnknown(),
|
|
SymbolFlags::Text);
|
|
}
|
|
}
|
|
|
|
void TBDGenVisitor::addFirstFileSymbols() {
|
|
if (!Opts.ModuleLinkName.empty()) {
|
|
// FIXME: We ought to have a symbol source for this.
|
|
SmallString<32> buf;
|
|
addSymbol(irgen::encodeForceLoadSymbolName(buf, Opts.ModuleLinkName),
|
|
SymbolSource::forUnknown(), SymbolFlags::Data);
|
|
}
|
|
}
|
|
|
|
void TBDGenVisitor::visit(const TBDGenDescriptor &desc) {
|
|
SILSymbolVisitorOptions opts;
|
|
opts.VisitMembers = true;
|
|
opts.LinkerDirectivesOnly = Opts.LinkerDirectivesOnly;
|
|
opts.PublicOrPackageSymbolsOnly = Opts.PublicOrPackageSymbolsOnly;
|
|
opts.WitnessMethodElimination = Opts.WitnessMethodElimination;
|
|
opts.VirtualFunctionElimination = Opts.VirtualFunctionElimination;
|
|
opts.FragileResilientProtocols = Opts.FragileResilientProtocols;
|
|
|
|
auto silVisitorCtx = SILSymbolVisitorContext(SwiftModule, opts);
|
|
auto visitorCtx = IRSymbolVisitorContext{UniversalLinkInfo, silVisitorCtx};
|
|
|
|
// Add any autolinking force_load symbols.
|
|
addFirstFileSymbols();
|
|
|
|
if (auto *singleFile = desc.getSingleFile()) {
|
|
assert(SwiftModule == singleFile->getParentModule() &&
|
|
"mismatched file and module");
|
|
visitFile(singleFile, visitorCtx);
|
|
return;
|
|
}
|
|
|
|
llvm::SmallVector<ModuleDecl*, 4> Modules;
|
|
Modules.push_back(SwiftModule);
|
|
|
|
auto &ctx = SwiftModule->getASTContext();
|
|
for (auto Name: Opts.embedSymbolsFromModules) {
|
|
if (auto *MD = ctx.getModuleByName(Name)) {
|
|
// If it is a clang module, the symbols should be collected by TAPI.
|
|
if (!MD->isNonSwiftModule()) {
|
|
Modules.push_back(MD);
|
|
continue;
|
|
}
|
|
}
|
|
// Diagnose module name that cannot be found
|
|
ctx.Diags.diagnose(SourceLoc(), diag::unknown_swift_module_name, Name);
|
|
}
|
|
|
|
// Collect symbols in each module.
|
|
visitModules(Modules, visitorCtx);
|
|
}
|
|
|
|
/// The kind of version being parsed, used for diagnostics.
|
|
/// Note: Must match the order in DiagnosticsFrontend.def
|
|
enum DylibVersionKind_t: unsigned {
|
|
CurrentVersion,
|
|
CompatibilityVersion
|
|
};
|
|
|
|
/// Converts a version string into a packed version, truncating each component
|
|
/// if necessary to fit all 3 into a 32-bit packed structure.
|
|
///
|
|
/// For example, the version '1219.37.11' will be packed as
|
|
///
|
|
/// Major (1,219) Minor (37) Patch (11)
|
|
/// ┌───────────────────┬──────────┬──────────┐
|
|
/// │ 00001100 11000011 │ 00100101 │ 00001011 │
|
|
/// └───────────────────┴──────────┴──────────┘
|
|
///
|
|
/// If an individual component is greater than the highest number that can be
|
|
/// represented in its alloted space, it will be truncated to the maximum value
|
|
/// that fits in the alloted space, which matches the behavior of the linker.
|
|
static std::optional<llvm::MachO::PackedVersion>
|
|
parsePackedVersion(DylibVersionKind_t kind, StringRef versionString,
|
|
ASTContext &ctx) {
|
|
if (versionString.empty())
|
|
return std::nullopt;
|
|
|
|
llvm::MachO::PackedVersion version;
|
|
auto result = version.parse64(versionString);
|
|
if (!result.first) {
|
|
ctx.Diags.diagnose(SourceLoc(), diag::tbd_err_invalid_version,
|
|
(unsigned)kind, versionString);
|
|
return std::nullopt;
|
|
}
|
|
if (result.second) {
|
|
ctx.Diags.diagnose(SourceLoc(), diag::tbd_warn_truncating_version,
|
|
(unsigned)kind, versionString);
|
|
}
|
|
return version;
|
|
}
|
|
|
|
static bool isApplicationExtensionSafe(const LangOptions &LangOpts) {
|
|
// Existing linkers respect these flags to determine app extension safety.
|
|
return LangOpts.EnableAppExtensionRestrictions ||
|
|
llvm::sys::Process::GetEnv("LD_NO_ENCRYPT") ||
|
|
llvm::sys::Process::GetEnv("LD_APPLICATION_EXTENSION_SAFE");
|
|
}
|
|
|
|
TBDFile GenerateTBDRequest::evaluate(Evaluator &evaluator,
|
|
TBDGenDescriptor desc) const {
|
|
auto *M = desc.getParentModule();
|
|
auto &opts = desc.getOptions();
|
|
auto &ctx = M->getASTContext();
|
|
|
|
llvm::MachO::InterfaceFile file;
|
|
file.setFileType(llvm::MachO::FileType::TBD_V5);
|
|
file.setApplicationExtensionSafe(isApplicationExtensionSafe(ctx.LangOpts));
|
|
file.setInstallName(opts.InstallName);
|
|
file.setTwoLevelNamespace();
|
|
file.setSwiftABIVersion(irgen::getSwiftABIVersion());
|
|
|
|
if (auto packed = parsePackedVersion(CurrentVersion,
|
|
opts.CurrentVersion, ctx)) {
|
|
file.setCurrentVersion(*packed);
|
|
}
|
|
|
|
if (auto packed = parsePackedVersion(CompatibilityVersion,
|
|
opts.CompatibilityVersion, ctx)) {
|
|
file.setCompatibilityVersion(*packed);
|
|
}
|
|
|
|
llvm::MachO::Target target(ctx.LangOpts.Target);
|
|
file.addTarget(target);
|
|
llvm::MachO::TargetList targets{target};
|
|
// Add target variant
|
|
if (ctx.LangOpts.TargetVariant.has_value()) {
|
|
llvm::MachO::Target targetVar(*ctx.LangOpts.TargetVariant);
|
|
file.addTarget(targetVar);
|
|
targets.push_back(targetVar);
|
|
}
|
|
|
|
auto addSymbol = [&](StringRef symbol, EncodeKind kind, SymbolSource source,
|
|
Decl *decl, SymbolFlags flags) {
|
|
file.addSymbol(kind, symbol, targets, flags);
|
|
};
|
|
SimpleAPIRecorder recorder(addSymbol);
|
|
TBDGenVisitor visitor(desc, recorder);
|
|
visitor.visit(desc);
|
|
return file;
|
|
}
|
|
|
|
std::vector<std::string>
|
|
PublicSymbolsRequest::evaluate(Evaluator &evaluator,
|
|
TBDGenDescriptor desc) const {
|
|
std::vector<std::string> symbols;
|
|
auto addSymbol = [&](StringRef symbol, EncodeKind kind, SymbolSource source,
|
|
Decl *decl, SymbolFlags flags) {
|
|
if (kind == EncodeKind::GlobalSymbol)
|
|
symbols.push_back(symbol.str());
|
|
// TextAPI ObjC Class Kinds represents two symbols.
|
|
else if (kind == EncodeKind::ObjectiveCClass) {
|
|
symbols.push_back((llvm::MachO::ObjC2ClassNamePrefix + symbol).str());
|
|
symbols.push_back((llvm::MachO::ObjC2MetaClassNamePrefix + symbol).str());
|
|
}
|
|
};
|
|
SimpleAPIRecorder recorder(addSymbol);
|
|
TBDGenVisitor visitor(desc, recorder);
|
|
visitor.visit(desc);
|
|
return symbols;
|
|
}
|
|
|
|
std::vector<std::string> swift::getPublicSymbols(TBDGenDescriptor desc) {
|
|
auto &evaluator = desc.getParentModule()->getASTContext().evaluator;
|
|
return evaluateOrFatal(evaluator, PublicSymbolsRequest{desc});
|
|
}
|
|
void swift::writeTBDFile(ModuleDecl *M, llvm::raw_ostream &os,
|
|
const TBDGenOptions &opts) {
|
|
auto &evaluator = M->getASTContext().evaluator;
|
|
auto desc = TBDGenDescriptor::forModule(M, opts);
|
|
auto file = evaluateOrFatal(evaluator, GenerateTBDRequest{desc});
|
|
llvm::cantFail(llvm::MachO::TextAPIWriter::writeToStream(os, file),
|
|
"TBD writing should be error-free");
|
|
}
|
|
|
|
class APIGenRecorder final : public APIRecorder {
|
|
bool isSPI(const Decl *decl) {
|
|
assert(decl);
|
|
|
|
if (auto value = dyn_cast<ValueDecl>(decl)) {
|
|
auto accessScope =
|
|
value->getFormalAccessScope(/*useDC=*/nullptr,
|
|
/*treatUsableFromInlineAsPublic=*/true);
|
|
// Only declarations with a public access scope (`public` or `open`)
|
|
// can be APIs. Exported declarations with other access scopes (`package`)
|
|
// should be SPI.
|
|
if (!accessScope.isPublic())
|
|
return true;
|
|
}
|
|
|
|
return decl->isSPI() || getAvailability(decl).spiAvailable;
|
|
}
|
|
|
|
public:
|
|
APIGenRecorder(apigen::API &api, ModuleDecl *module)
|
|
: api(api), module(module) {
|
|
// If we're working with a serialized module, make the default location
|
|
// for symbols the path to the binary module.
|
|
if (FileUnit *MainFile = module->getFiles().front()) {
|
|
if (MainFile->getKind() == FileUnitKind::SerializedAST)
|
|
defaultLoc =
|
|
apigen::APILoc(MainFile->getModuleDefiningPath().str(), 0, 0);
|
|
}
|
|
}
|
|
~APIGenRecorder() {}
|
|
|
|
void addSymbol(StringRef symbol, EncodeKind kind, SymbolSource source,
|
|
Decl *decl, SymbolFlags flags) override {
|
|
if (kind != EncodeKind::GlobalSymbol)
|
|
return;
|
|
|
|
apigen::APIAvailability availability;
|
|
auto access = apigen::APIAccess::Public;
|
|
if (decl) {
|
|
if (!shouldRecordDecl(decl))
|
|
return;
|
|
|
|
availability = getAvailability(decl);
|
|
if (isSPI(decl))
|
|
access = apigen::APIAccess::Private;
|
|
}
|
|
|
|
api.addSymbol(symbol, getAPILocForDecl(decl), apigen::APILinkage::Exported,
|
|
apigen::APIFlags::None, access, availability);
|
|
}
|
|
|
|
void addObjCInterface(const ClassDecl *decl) override {
|
|
addOrGetObjCInterface(decl);
|
|
}
|
|
|
|
void addObjCCategory(const ExtensionDecl *decl) override {
|
|
addOrGetObjCCategory(decl);
|
|
}
|
|
|
|
void addObjCMethod(const GenericContext *ctx, SILDeclRef method) override {
|
|
SmallString<128> buffer;
|
|
StringRef name = getSelectorName(method, buffer);
|
|
apigen::APIAvailability availability;
|
|
bool isInstanceMethod = true;
|
|
auto access = apigen::APIAccess::Public;
|
|
auto decl = method.hasDecl() ? method.getDecl() : nullptr;
|
|
if (decl) {
|
|
if (!shouldRecordDecl(decl))
|
|
return;
|
|
|
|
availability = getAvailability(decl);
|
|
if (decl->getDescriptiveKind() == DescriptiveDeclKind::ClassMethod)
|
|
isInstanceMethod = false;
|
|
if (isSPI(decl))
|
|
access = apigen::APIAccess::Private;
|
|
}
|
|
|
|
apigen::ObjCContainerRecord *record = nullptr;
|
|
if (auto *cls = dyn_cast<ClassDecl>(ctx))
|
|
record = addOrGetObjCInterface(cls);
|
|
else if (auto *ext = dyn_cast<ExtensionDecl>(ctx))
|
|
record = addOrGetObjCCategory(ext);
|
|
|
|
if (record)
|
|
api.addObjCMethod(record, name, getAPILocForDecl(decl), access,
|
|
isInstanceMethod, false, availability);
|
|
}
|
|
|
|
private:
|
|
apigen::APILoc getAPILocForDecl(const Decl *decl) const {
|
|
if (!decl)
|
|
return defaultLoc;
|
|
|
|
SourceLoc loc = decl->getLoc();
|
|
if (!loc.isValid())
|
|
return defaultLoc;
|
|
|
|
auto &SM = decl->getASTContext().SourceMgr;
|
|
unsigned line, col;
|
|
std::tie(line, col) = SM.getPresumedLineAndColumnForLoc(loc);
|
|
auto displayName = SM.getDisplayNameForLoc(loc);
|
|
|
|
return apigen::APILoc(std::string(displayName), line, col);
|
|
}
|
|
|
|
bool shouldRecordDecl(const Decl *decl) const {
|
|
// We cannot reason about API access for Clang declarations from header
|
|
// files as we don't know the header group. API records for header
|
|
// declarations should be deferred to Clang tools.
|
|
if (getAPILocForDecl(decl).getFilename().ends_with_insensitive(".h"))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/// Follow the naming schema that IRGen uses for Categories (see
|
|
/// ClassDataBuilder).
|
|
using CategoryNameKey = std::pair<const ClassDecl *, const ModuleDecl *>;
|
|
llvm::DenseMap<CategoryNameKey, unsigned> CategoryCounts;
|
|
|
|
apigen::APIAvailability getAvailability(const Decl *decl) {
|
|
std::optional<bool> unavailable, spiAvailable;
|
|
std::string introduced, obsoleted;
|
|
bool hasFallbackUnavailability = false, hasFallbackSPIAvailability = false;
|
|
auto platform = targetPlatform(module->getASTContext().LangOpts);
|
|
const Decl *declForAvailability = decl->getInnermostDeclWithAvailability();
|
|
if (!declForAvailability)
|
|
return {};
|
|
for (auto attr : declForAvailability->getSemanticAvailableAttrs()) {
|
|
if (!attr.isPlatformSpecific()) {
|
|
hasFallbackUnavailability = attr.isUnconditionallyUnavailable();
|
|
hasFallbackSPIAvailability = attr.isSPI();
|
|
continue;
|
|
}
|
|
if (attr.getPlatform() != platform)
|
|
continue;
|
|
unavailable = attr.isUnconditionallyUnavailable();
|
|
spiAvailable = attr.isSPI();
|
|
if (attr.getIntroduced())
|
|
introduced = attr.getIntroduced()->getAsString();
|
|
if (attr.getObsoleted())
|
|
obsoleted = attr.getObsoleted()->getAsString();
|
|
}
|
|
return {introduced, obsoleted,
|
|
unavailable.value_or(hasFallbackUnavailability),
|
|
spiAvailable.value_or(hasFallbackSPIAvailability)};
|
|
}
|
|
|
|
StringRef getSelectorName(SILDeclRef method, SmallString<128> &buffer) {
|
|
auto methodOrCtorOrDtor = method.getDecl();
|
|
if (methodOrCtorOrDtor) {
|
|
if (auto *method = dyn_cast<FuncDecl>(methodOrCtorOrDtor))
|
|
return method->getObjCSelector().getString(buffer);
|
|
else if (auto *ctor = dyn_cast<ConstructorDecl>(methodOrCtorOrDtor))
|
|
return ctor->getObjCSelector().getString(buffer);
|
|
else if (isa<DestructorDecl>(methodOrCtorOrDtor))
|
|
return "dealloc";
|
|
}
|
|
llvm_unreachable("cannot get selector name from decl");
|
|
}
|
|
|
|
apigen::ObjCInterfaceRecord *addOrGetObjCInterface(const ClassDecl *decl) {
|
|
if (!shouldRecordDecl(decl))
|
|
return nullptr;
|
|
|
|
auto entry = classMap.find(decl);
|
|
if (entry != classMap.end())
|
|
return entry->second;
|
|
|
|
SmallString<128> nameBuffer;
|
|
auto name = decl->getObjCRuntimeName(nameBuffer);
|
|
StringRef superCls;
|
|
SmallString<128> buffer;
|
|
if (auto *super = decl->getSuperclassDecl())
|
|
superCls = super->getObjCRuntimeName(buffer);
|
|
apigen::APIAvailability availability = getAvailability(decl);
|
|
apigen::APIAccess access =
|
|
isSPI(decl) ? apigen::APIAccess::Private : apigen::APIAccess::Public;
|
|
apigen::APILinkage linkage =
|
|
decl->getFormalAccess() == AccessLevel::Public && decl->isObjC()
|
|
? apigen::APILinkage::Exported
|
|
: apigen::APILinkage::Internal;
|
|
auto cls = api.addObjCClass(name, linkage, getAPILocForDecl(decl), access,
|
|
availability, superCls);
|
|
classMap.try_emplace(decl, cls);
|
|
return cls;
|
|
}
|
|
|
|
void buildCategoryName(const ExtensionDecl *ext, const ClassDecl *cls,
|
|
SmallVectorImpl<char> &s) {
|
|
llvm::raw_svector_ostream os(s);
|
|
if (!ext->getObjCCategoryName().empty()) {
|
|
os << ext->getObjCCategoryName();
|
|
return;
|
|
}
|
|
ModuleDecl *module = ext->getParentModule();
|
|
os << module->getName();
|
|
unsigned categoryCount = CategoryCounts[{cls, module}]++;
|
|
if (categoryCount > 0)
|
|
os << categoryCount;
|
|
}
|
|
|
|
apigen::ObjCCategoryRecord *addOrGetObjCCategory(const ExtensionDecl *decl) {
|
|
if (!shouldRecordDecl(decl))
|
|
return nullptr;
|
|
|
|
auto entry = categoryMap.find(decl);
|
|
if (entry != categoryMap.end())
|
|
return entry->second;
|
|
|
|
SmallString<128> interfaceBuffer;
|
|
SmallString<128> nameBuffer;
|
|
ClassDecl *cls = decl->getSelfClassDecl();
|
|
auto interface = cls->getObjCRuntimeName(interfaceBuffer);
|
|
buildCategoryName(decl, cls, nameBuffer);
|
|
apigen::APIAvailability availability = getAvailability(decl);
|
|
apigen::APIAccess access =
|
|
isSPI(decl) ? apigen::APIAccess::Private : apigen::APIAccess::Public;
|
|
apigen::APILinkage linkage =
|
|
decl->getMaxAccessLevel() == AccessLevel::Public
|
|
? apigen::APILinkage::Exported
|
|
: apigen::APILinkage::Internal;
|
|
auto category =
|
|
api.addObjCCategory(nameBuffer, linkage, getAPILocForDecl(decl), access,
|
|
availability, interface);
|
|
categoryMap.try_emplace(decl, category);
|
|
return category;
|
|
}
|
|
|
|
apigen::API &api;
|
|
ModuleDecl *module;
|
|
apigen::APILoc defaultLoc;
|
|
|
|
llvm::DenseMap<const ClassDecl*, apigen::ObjCInterfaceRecord*> classMap;
|
|
llvm::DenseMap<const ExtensionDecl *, apigen::ObjCCategoryRecord *>
|
|
categoryMap;
|
|
};
|
|
|
|
apigen::API APIGenRequest::evaluate(Evaluator &evaluator,
|
|
TBDGenDescriptor desc) const {
|
|
auto *M = desc.getParentModule();
|
|
apigen::API api(M->getASTContext().LangOpts.Target);
|
|
APIGenRecorder recorder(api, M);
|
|
|
|
TBDGenVisitor visitor(desc, recorder);
|
|
visitor.visit(desc);
|
|
|
|
return api;
|
|
}
|
|
|
|
void swift::writeAPIJSONFile(ModuleDecl *M, llvm::raw_ostream &os,
|
|
bool PrettyPrint) {
|
|
TBDGenOptions opts;
|
|
auto &evaluator = M->getASTContext().evaluator;
|
|
auto desc = TBDGenDescriptor::forModule(M, opts);
|
|
auto api = evaluateOrFatal(evaluator, APIGenRequest{desc});
|
|
api.writeAPIJSONFile(os, PrettyPrint);
|
|
}
|
|
|
|
/// NOTE: This is part of an incomplete experimental feature. There are
|
|
/// currently no clients that depend on its output.
|
|
const SymbolSourceMap *
|
|
SymbolSourceMapRequest::evaluate(Evaluator &evaluator,
|
|
TBDGenDescriptor desc) const {
|
|
|
|
// FIXME: Once the evaluator supports returning a reference to a cached value
|
|
// in storage, this won't be necessary.
|
|
auto &Ctx = desc.getParentModule()->getASTContext();
|
|
auto *SymbolSources = Ctx.Allocate<SymbolSourceMap>();
|
|
|
|
auto addSymbol = [=](StringRef symbol, EncodeKind kind, SymbolSource source,
|
|
Decl *decl, SymbolFlags flags) {
|
|
SymbolSources->insert({symbol, source});
|
|
};
|
|
|
|
SimpleAPIRecorder recorder(addSymbol);
|
|
TBDGenVisitor visitor(desc, recorder);
|
|
visitor.visit(desc);
|
|
|
|
Ctx.addCleanup([SymbolSources]() { SymbolSources->~SymbolSourceMap(); });
|
|
|
|
return SymbolSources;
|
|
}
|