blob: f1bfcea821bc6b3b5111474946094e1150067955 [file] [log] [blame]
//===----- ModuleInterfaceBuilder.cpp - Compiles .swiftinterface files ----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2019 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
//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "textual-module-interface"
#include "ModuleInterfaceBuilder.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/FileSystem.h"
#include "swift/AST/Module.h"
#include "llvm/ADT/StringSet.h"
#include "swift/Basic/Defer.h"
#include "swift/Frontend/Frontend.h"
#include "swift/Frontend/ModuleInterfaceSupport.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/Serialization/SerializationOptions.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/xxhash.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/CrashRecoveryContext.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/StringSaver.h"
using namespace swift;
using FileDependency = SerializationOptions::FileDependency;
namespace path = llvm::sys::path;
/// If the file dependency in \p FullDepPath is inside the \p Base directory,
/// this returns its path relative to \p Base. Otherwise it returns None.
static Optional<StringRef> getRelativeDepPath(StringRef DepPath,
StringRef Base) {
// If Base is the root directory, or DepPath does not start with Base, bail.
if (Base.size() <= 1 || !DepPath.startswith(Base)) {
return None;
}
assert(DepPath.size() > Base.size() &&
"should never depend on a directory");
// Is the DepName something like ${Base}/foo.h"?
if (path::is_separator(DepPath[Base.size()]))
return DepPath.substr(Base.size() + 1);
// Is the DepName something like "${Base}foo.h", where Base
// itself contains a trailing slash?
if (path::is_separator(Base.back()))
return DepPath.substr(Base.size());
// We have something next to Base, like "Base.h", that's somehow
// become a dependency.
return None;
}
void ModuleInterfaceBuilder::configureSubInvocationInputsAndOutputs(
StringRef OutPath) {
auto &SubFEOpts = subInvocation.getFrontendOptions();
SubFEOpts.RequestedAction = FrontendOptions::ActionType::EmitModuleOnly;
SubFEOpts.InputsAndOutputs.addPrimaryInputFile(interfacePath);
SupplementaryOutputPaths SOPs;
SOPs.ModuleOutputPath = OutPath.str();
// Pick a primary output path that will cause problems to use.
StringRef MainOut = "/<unused>";
SubFEOpts.InputsAndOutputs
.setMainAndSupplementaryOutputs({MainOut}, {SOPs});
}
void ModuleInterfaceBuilder::configureSubInvocation(
const SearchPathOptions &SearchPathOpts,
const LangOptions &LangOpts,
ClangModuleLoader *ClangLoader) {
// Start with a SubInvocation that copies various state from our
// invoking ASTContext.
subInvocation.setImportSearchPaths(SearchPathOpts.ImportSearchPaths);
subInvocation.setFrameworkSearchPaths(SearchPathOpts.FrameworkSearchPaths);
subInvocation.setSDKPath(SearchPathOpts.SDKPath);
subInvocation.setInputKind(InputFileKind::SwiftModuleInterface);
subInvocation.setRuntimeResourcePath(SearchPathOpts.RuntimeResourcePath);
subInvocation.setTargetTriple(LangOpts.Target);
subInvocation.setModuleName(moduleName);
subInvocation.setClangModuleCachePath(moduleCachePath);
subInvocation.getFrontendOptions().PrebuiltModuleCachePath =
prebuiltCachePath;
subInvocation.getFrontendOptions().TrackSystemDeps = trackSystemDependencies;
// Respect the detailed-record preprocessor setting of the parent context.
// This, and the "raw" clang module format it implicitly enables, are
// required by sourcekitd.
if (ClangLoader) {
auto &Opts = ClangLoader->getClangInstance().getPreprocessorOpts();
if (Opts.DetailedRecord) {
subInvocation.getClangImporterOptions().DetailedPreprocessingRecord = true;
}
}
// Inhibit warnings from the SubInvocation since we are assuming the user
// is not in a position to fix them.
subInvocation.getDiagnosticOptions().SuppressWarnings = true;
// Inherit this setting down so that it can affect error diagnostics (mostly
// by making them non-fatal).
subInvocation.getLangOptions().DebuggerSupport = LangOpts.DebuggerSupport;
// Disable this; deinitializers always get printed with `@objc` even in
// modules that don't import Foundation.
subInvocation.getLangOptions().EnableObjCAttrRequiresFoundation = false;
// Tell the subinvocation to serialize dependency hashes if asked to do so.
auto &frontendOpts = subInvocation.getFrontendOptions();
frontendOpts.SerializeModuleInterfaceDependencyHashes =
serializeDependencyHashes;
// Tell the subinvocation to remark on rebuilds from an interface if asked
// to do so.
frontendOpts.RemarkOnRebuildFromModuleInterface =
remarkOnRebuildFromInterface;
}
bool ModuleInterfaceBuilder::extractSwiftInterfaceVersionAndArgs(
swift::version::Version &Vers, llvm::StringSaver &SubArgSaver,
SmallVectorImpl<const char *> &SubArgs) {
auto FileOrError = swift::vfs::getFileOrSTDIN(fs, interfacePath);
if (!FileOrError) {
diags.diagnose(diagnosticLoc, diag::error_open_input_file,
interfacePath, FileOrError.getError().message());
return true;
}
auto SB = FileOrError.get()->getBuffer();
auto VersRe = getSwiftInterfaceFormatVersionRegex();
auto FlagRe = getSwiftInterfaceModuleFlagsRegex();
SmallVector<StringRef, 1> VersMatches, FlagMatches;
if (!VersRe.match(SB, &VersMatches)) {
diags.diagnose(diagnosticLoc,
diag::error_extracting_version_from_module_interface);
return true;
}
if (!FlagRe.match(SB, &FlagMatches)) {
diags.diagnose(diagnosticLoc,
diag::error_extracting_flags_from_module_interface);
return true;
}
assert(VersMatches.size() == 2);
assert(FlagMatches.size() == 2);
Vers = swift::version::Version(VersMatches[1], SourceLoc(), &diags);
llvm::cl::TokenizeGNUCommandLine(FlagMatches[1], SubArgSaver, SubArgs);
return false;
}
bool ModuleInterfaceBuilder::collectDepsForSerialization(
CompilerInstance &SubInstance, SmallVectorImpl<FileDependency> &Deps,
bool IsHashBased) {
auto &Opts = SubInstance.getASTContext().SearchPathOpts;
SmallString<128> SDKPath(Opts.SDKPath);
path::native(SDKPath);
SmallString<128> ResourcePath(Opts.RuntimeResourcePath);
path::native(ResourcePath);
auto DTDeps = SubInstance.getDependencyTracker()->getDependencies();
SmallVector<StringRef, 16> InitialDepNames(DTDeps.begin(), DTDeps.end());
InitialDepNames.push_back(interfacePath);
InitialDepNames.insert(InitialDepNames.end(),
extraDependencies.begin(), extraDependencies.end());
llvm::StringSet<> AllDepNames;
SmallString<128> Scratch;
for (const auto &InitialDepName : InitialDepNames) {
path::native(InitialDepName, Scratch);
StringRef DepName = Scratch.str();
assert(moduleCachePath.empty() || !DepName.startswith(moduleCachePath));
// Serialize the paths of dependencies in the SDK relative to it.
Optional<StringRef> SDKRelativePath = getRelativeDepPath(DepName, SDKPath);
StringRef DepNameToStore = SDKRelativePath.getValueOr(DepName);
bool IsSDKRelative = SDKRelativePath.hasValue();
// Forwarding modules add the underlying prebuilt module to their
// dependency list -- don't serialize that.
if (!prebuiltCachePath.empty() && DepName.startswith(prebuiltCachePath))
continue;
if (AllDepNames.insert(DepName).second && dependencyTracker) {
dependencyTracker->addDependency(DepName, /*isSystem*/IsSDKRelative);
}
// Don't serialize compiler-relative deps so the cache is relocatable.
if (DepName.startswith(ResourcePath))
continue;
auto Status = fs.status(DepName);
if (!Status)
return true;
/// Lazily load the dependency buffer if we need it. If we're not
/// dealing with a hash-based dependencies, and if the dependency is
/// not a .swiftmodule, we can avoid opening the buffer.
std::unique_ptr<llvm::MemoryBuffer> DepBuf = nullptr;
auto getDepBuf = [&]() -> llvm::MemoryBuffer * {
if (DepBuf) return DepBuf.get();
if (auto Buf = fs.getBufferForFile(DepName, /*FileSize=*/-1,
/*RequiresNullTerminator=*/false)) {
DepBuf = std::move(Buf.get());
return DepBuf.get();
}
return nullptr;
};
if (IsHashBased) {
auto buf = getDepBuf();
if (!buf) return true;
uint64_t hash = xxHash64(buf->getBuffer());
Deps.push_back(
FileDependency::hashBased(DepNameToStore, IsSDKRelative,
Status->getSize(), hash));
} else {
uint64_t mtime =
Status->getLastModificationTime().time_since_epoch().count();
Deps.push_back(
FileDependency::modTimeBased(DepNameToStore, IsSDKRelative,
Status->getSize(), mtime));
}
}
return false;
}
bool ModuleInterfaceBuilder::buildSwiftModule(
StringRef OutPath, bool ShouldSerializeDeps,
std::unique_ptr<llvm::MemoryBuffer> *ModuleBuffer) {
bool SubError = false;
bool RunSuccess = llvm::CrashRecoveryContext().RunSafelyOnThread([&] {
// Note that we don't assume cachePath is the same as the Clang
// module cache path at this point.
if (!moduleCachePath.empty())
(void)llvm::sys::fs::create_directories(moduleCachePath);
configureSubInvocationInputsAndOutputs(OutPath);
FrontendOptions &FEOpts = subInvocation.getFrontendOptions();
const auto &InputInfo = FEOpts.InputsAndOutputs.firstInput();
StringRef InPath = InputInfo.file();
const auto &OutputInfo =
InputInfo.getPrimarySpecificPaths().SupplementaryOutputs;
StringRef OutPath = OutputInfo.ModuleOutputPath;
llvm::BumpPtrAllocator SubArgsAlloc;
llvm::StringSaver SubArgSaver(SubArgsAlloc);
SmallVector<const char *, 16> SubArgs;
swift::version::Version Vers;
if (extractSwiftInterfaceVersionAndArgs(Vers, SubArgSaver, SubArgs)) {
SubError = true;
return;
}
// For now: we support anything with the same "major version" and assume
// minor versions might be interesting for debugging, or special-casing a
// compatible field variant.
if (Vers.asMajorVersion() != InterfaceFormatVersion.asMajorVersion()) {
diags.diagnose(diagnosticLoc,
diag::unsupported_version_of_module_interface,
interfacePath, Vers);
SubError = true;
return;
}
SmallString<32> ExpectedModuleName = subInvocation.getModuleName();
if (subInvocation.parseArgs(SubArgs, diags)) {
SubError = true;
return;
}
if (subInvocation.getModuleName() != ExpectedModuleName) {
auto DiagKind = diag::serialization_name_mismatch;
if (subInvocation.getLangOptions().DebuggerSupport)
DiagKind = diag::serialization_name_mismatch_repl;
diags.diagnose(diagnosticLoc, DiagKind, subInvocation.getModuleName(),
ExpectedModuleName);
SubError = true;
return;
}
// Build the .swiftmodule; this is a _very_ abridged version of the logic
// in performCompile in libFrontendTool, specialized, to just the one
// module-serialization task we're trying to do here.
LLVM_DEBUG(llvm::dbgs() << "Setting up instance to compile "
<< InPath << " to " << OutPath << "\n");
CompilerInstance SubInstance;
SubInstance.getSourceMgr().setFileSystem(&fs);
ForwardingDiagnosticConsumer FDC(diags);
SubInstance.addDiagnosticConsumer(&FDC);
SubInstance.createDependencyTracker(FEOpts.TrackSystemDeps);
SWIFT_DEFER {
// Make sure to emit a generic top-level error if a module fails to
// load. This is not only good for users; it also makes sure that we've
// emitted an error in the parent diagnostic engine, which is what
// determines whether the process exits with a proper failure status.
if (SubInstance.getASTContext().hadError()) {
diags.diagnose(diagnosticLoc, diag::serialization_load_failed,
moduleName);
}
};
if (SubInstance.setup(subInvocation)) {
SubError = true;
return;
}
LLVM_DEBUG(llvm::dbgs() << "Performing sema\n");
SubInstance.performSema();
if (SubInstance.getASTContext().hadError()) {
LLVM_DEBUG(llvm::dbgs() << "encountered errors\n");
SubError = true;
return;
}
SILOptions &SILOpts = subInvocation.getSILOptions();
auto Mod = SubInstance.getMainModule();
auto &TC = SubInstance.getSILTypes();
auto SILMod = performSILGeneration(Mod, TC, SILOpts);
if (!SILMod) {
LLVM_DEBUG(llvm::dbgs() << "SILGen did not produce a module\n");
SubError = true;
return;
}
// Setup the callbacks for serialization, which can occur during the
// optimization pipeline.
SerializationOptions SerializationOpts;
std::string OutPathStr = OutPath;
SerializationOpts.OutputPath = OutPathStr.c_str();
SerializationOpts.ModuleLinkName = FEOpts.ModuleLinkName;
// Record any non-SDK module interface files for the debug info.
StringRef SDKPath = SubInstance.getASTContext().SearchPathOpts.SDKPath;
if (!getRelativeDepPath(InPath, SDKPath))
SerializationOpts.ModuleInterface = InPath;
SmallVector<FileDependency, 16> Deps;
bool serializeHashes = FEOpts.SerializeModuleInterfaceDependencyHashes;
if (collectDepsForSerialization(SubInstance, Deps, serializeHashes)) {
SubError = true;
return;
}
if (ShouldSerializeDeps)
SerializationOpts.Dependencies = Deps;
SILMod->setSerializeSILAction([&]() {
// We don't want to serialize module docs in the cache -- they
// will be serialized beside the interface file.
serializeToBuffers(Mod, SerializationOpts, ModuleBuffer,
/*ModuleDocBuffer*/nullptr,
/*SourceInfoBuffer*/nullptr,
SILMod.get());
});
LLVM_DEBUG(llvm::dbgs() << "Running SIL processing passes\n");
if (SubInstance.performSILProcessing(SILMod.get())) {
LLVM_DEBUG(llvm::dbgs() << "encountered errors\n");
SubError = true;
return;
}
SubError = SubInstance.getDiags().hadAnyError();
});
return !RunSuccess || SubError;
}