//===--- CachingUtils.cpp ---------------------------------------*- C++ -*-===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2018 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 // //===----------------------------------------------------------------------===// #include "swift/Frontend/CachingUtils.h" #include "swift/AST/DiagnosticsFrontend.h" #include "swift/Basic/Assertions.h" #include "swift/Basic/FileTypes.h" #include "swift/Basic/LLVM.h" #include "swift/Frontend/CASOutputBackends.h" #include "swift/Frontend/CompileJobCacheKey.h" #include "swift/Frontend/CompileJobCacheResult.h" #include "swift/Frontend/FrontendOptions.h" #include "swift/Frontend/MakeStyleDependencies.h" #include "swift/Option/Options.h" #include "clang/CAS/CASOptions.h" #include "clang/CAS/IncludeTree.h" #include "clang/Frontend/CompileJobCacheResult.h" #include "llvm/ADT/STLExtras.h" #include "llvm/CAS/BuiltinUnifiedCASDatabases.h" #include "llvm/CAS/CASFileSystem.h" #include "llvm/CAS/HierarchicalTreeBuilder.h" #include "llvm/CAS/ObjectStore.h" #include "llvm/CAS/TreeEntry.h" #include "llvm/MCCAS/MCCASObjectV1.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/OptTable.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/VirtualOutputBackends.h" #include "llvm/Support/VirtualOutputFile.h" #include #define DEBUG_TYPE "cache-util" using namespace swift; using namespace swift::cas; using namespace llvm; using namespace llvm::cas; using namespace llvm::vfs; namespace swift { llvm::IntrusiveRefCntPtr createSwiftCachingOutputBackend( llvm::cas::ObjectStore &CAS, llvm::cas::ActionCache &Cache, llvm::cas::ObjectRef BaseKey, const FrontendInputsAndOutputs &InputsAndOutputs, const FrontendOptions &Opts, FrontendOptions::ActionType Action) { return makeIntrusiveRefCnt( CAS, Cache, BaseKey, InputsAndOutputs, Opts, Action); } Error cas::CachedResultLoader::replay(CallbackTy Callback) { auto ResultProxy = CAS.getProxy(OutputRef); if (!ResultProxy) return ResultProxy.takeError(); { swift::cas::CompileJobResultSchema Schema(CAS); if (Schema.isRootNode(*ResultProxy)) { auto Result = Schema.load(OutputRef); if (!Result) return Result.takeError(); if (auto Err = Result->forEachOutput( [&](swift::cas::CompileJobCacheResult::Output Output) -> Error { return Callback(Output.Kind, Output.Object); })) return Err; return Error::success(); } } { clang::cas::CompileJobResultSchema Schema(CAS); if (Schema.isRootNode(*ResultProxy)) { auto Result = Schema.load(OutputRef); if (!Result) return Result.takeError(); if (auto Err = Result->forEachOutput( [&](clang::cas::CompileJobCacheResult::Output Output) -> Error { file_types::ID OutputKind = file_types::ID::TY_INVALID; switch (Output.Kind) { case clang::cas::CompileJobCacheResult::OutputKind::MainOutput: OutputKind = file_types::ID::TY_ClangModuleFile; break; case clang::cas::CompileJobCacheResult::OutputKind::Dependencies: OutputKind = file_types::ID::TY_Dependencies; break; case clang::cas::CompileJobCacheResult::OutputKind:: SerializedDiagnostics: OutputKind = file_types::ID::TY_CachedDiagnostics; break; } assert(OutputKind != file_types::ID::TY_INVALID && "Unexpected output kind in clang cached result"); return Callback(OutputKind, Output.Object); })) return Err; return Error::success(); } } return createStringError(inconvertibleErrorCode(), "unexpected output schema for cached result"); } static Expected> lookupCacheKey(ObjectStore &CAS, ActionCache &Cache, ObjectRef CacheKey) { // Lookup the cache key for the input file. auto OutID = CAS.getID(CacheKey); auto Lookup = Cache.get(OutID); if (!Lookup) return Lookup.takeError(); if (!*Lookup) return std::nullopt; return CAS.getReference(**Lookup); } bool replayCachedCompilerOutputs( ObjectStore &CAS, ActionCache &Cache, ObjectRef BaseKey, DiagnosticEngine &Diag, const FrontendOptions &Opts, CachingDiagnosticsProcessor &CDP, bool CacheRemarks, bool UseCASBackend) { bool CanReplayAllOutput = true; struct OutputEntry { std::string Path; CASID Key; file_types::ID Kind; const InputFile &Input; ObjectProxy Proxy; }; SmallVector OutputProxies; std::optional DiagnosticsOutput; auto replayOutputsForInputFile = [&](const InputFile &Input, const std::string &InputPath, unsigned InputIndex, const DenseMap &Outputs) { auto lookupFailed = [&CanReplayAllOutput] { CanReplayAllOutput = false; }; auto OutputKey = createCompileJobCacheKeyForOutput(CAS, BaseKey, InputIndex); if (!OutputKey) { Diag.diagnose(SourceLoc(), diag::error_cas, toString(OutputKey.takeError())); return lookupFailed(); } auto OutID = CAS.getID(*OutputKey); auto OutputRef = lookupCacheKey(CAS, Cache, *OutputKey); if (!OutputRef) { Diag.diagnose(SourceLoc(), diag::error_cas, toString(OutputRef.takeError())); return lookupFailed(); } if (!*OutputRef) { if (CacheRemarks) Diag.diagnose(SourceLoc(), diag::output_cache_miss, InputPath, OutID.toString()); return lookupFailed(); } CachedResultLoader Loader(CAS, **OutputRef); LLVM_DEBUG(llvm::dbgs() << "DEBUG: lookup cache key \'" << OutID.toString() << "\' for input \'" << InputPath << "\n";); if (auto Err = Loader.replay([&](file_types::ID Kind, ObjectRef Ref) -> Error { auto OutputPath = Outputs.find(Kind); if (OutputPath == Outputs.end()) return createStringError( inconvertibleErrorCode(), "unexpected output kind in the cached output"); auto Proxy = CAS.getProxy(Ref); if (!Proxy) return Proxy.takeError(); if (Kind == file_types::ID::TY_CachedDiagnostics) { assert(!DiagnosticsOutput && "more than 1 diagnotics found"); DiagnosticsOutput.emplace( OutputEntry{OutputPath->second, OutID, Kind, Input, *Proxy}); } else if (Kind == file_types::ID::TY_SymbolGraphFile && !Opts.SymbolGraphOutputDir.empty()) { auto Err = Proxy->forEachReference([&](ObjectRef Ref) -> Error { auto Proxy = CAS.getProxy(Ref); if (!Proxy) return Proxy.takeError(); auto PathRef = Proxy->getReference(0); auto ContentRef = Proxy->getReference(1); auto Path = CAS.getProxy(PathRef); auto Content = CAS.getProxy(ContentRef); if (!Path) return Path.takeError(); if (!Content) return Content.takeError(); SmallString<128> OutputPath(Opts.SymbolGraphOutputDir); llvm::sys::path::append(OutputPath, Path->getData()); OutputProxies.emplace_back(OutputEntry{ std::string(OutputPath), OutID, Kind, Input, *Content}); return Error::success(); }); if (Err) return Err; } else OutputProxies.emplace_back( OutputEntry{OutputPath->second, OutID, Kind, Input, *Proxy}); return Error::success(); })) { Diag.diagnose(SourceLoc(), diag::error_cas, toString(std::move(Err))); return lookupFailed(); } }; auto replayOutputFromInput = [&](const InputFile &Input, unsigned InputIndex) { auto InputPath = Input.getFileName(); DenseMap Outputs; if (!Input.outputFilename().empty()) Outputs.try_emplace(Opts.InputsAndOutputs.getPrincipalOutputType(), Input.outputFilename()); Input.getPrimarySpecificPaths() .SupplementaryOutputs.forEachSetOutputAndType( [&](const std::string &File, file_types::ID ID) { if (file_types::isProducedFromDiagnostics(ID)) return; Outputs.try_emplace(ID, File); }); // If this input doesn't produce any outputs, don't try to look up cache. // This can be a standalone emitModule action that only one input produces // output. if (Outputs.empty()) return; // Add cached diagnostic entry for lookup. Output path doesn't matter here. Outputs.try_emplace(file_types::ID::TY_CachedDiagnostics, ""); // Add symbol graph entry for lookup. Output path doesn't matter here. Outputs.try_emplace(file_types::ID::TY_SymbolGraphFile, ""); return replayOutputsForInputFile(Input, InputPath, InputIndex, Outputs); }; auto AllInputs = Opts.InputsAndOutputs.getAllInputs(); // If there are primary inputs, look up only the primary input files. // Otherwise, prepare to do cache lookup for all inputs. for (unsigned Index = 0; Index < AllInputs.size(); ++Index) { const auto &Input = AllInputs[Index]; if (Opts.InputsAndOutputs.hasPrimaryInputs() && !Input.isPrimary()) continue; replayOutputFromInput(Input, Index); } if (!CanReplayAllOutput) return false; // If there is not diagnostic output, this is a job that produces no output // and only diagnostics, like `typecheck-module-from-interface`, look up // diagnostics from first file. if (!DiagnosticsOutput) replayOutputsForInputFile( Opts.InputsAndOutputs.getFirstOutputProducingInput(), "", Opts.InputsAndOutputs.getIndexOfFirstOutputProducingInput(), {{file_types::ID::TY_CachedDiagnostics, ""}}); // Check again to make sure diagnostics is fetched successfully. if (!CanReplayAllOutput) return false; // Replay Diagnostics first so the output failures comes after. // Also if the diagnostics replay failed, proceed to re-compile. if (auto E = CDP.replayCachedDiagnostics( DiagnosticsOutput->Proxy.getData())) { Diag.diagnose(SourceLoc(), diag::error_replay_cached_diag, toString(std::move(E))); return false; } if (CacheRemarks) Diag.diagnose(SourceLoc(), diag::replay_output, "", DiagnosticsOutput->Key.toString()); // Replay the result only when everything is resolved. // Use on disk output backend directly here to write to disk. llvm::vfs::OnDiskOutputBackend Backend; for (auto &Output : OutputProxies) { auto File = Backend.createFile(Output.Path); if (!File) { Diag.diagnose(SourceLoc(), diag::error_opening_output, Output.Path, toString(File.takeError())); continue; } if (UseCASBackend && Output.Kind == file_types::ID::TY_Object) { auto Schema = std::make_unique(CAS); if (auto E = Schema->serializeObjectFile(Output.Proxy, *File)) { Diag.diagnose(SourceLoc(), diag::error_mccas, toString(std::move(E))); return false; } } else if (Output.Kind == file_types::ID::TY_Dependencies) { if (emitMakeDependenciesFromSerializedBuffer( Output.Proxy.getData(), *File, Opts, Output.Input, Diag)) return false; } else *File << Output.Proxy.getData(); if (auto E = File->keep()) { Diag.diagnose(SourceLoc(), diag::error_closing_output, Output.Path, toString(std::move(E))); continue; } if (CacheRemarks) Diag.diagnose(SourceLoc(), diag::replay_output, Output.Path, Output.Key.toString()); } return true; } std::unique_ptr loadCachedCompileResultFromCacheKey(ObjectStore &CAS, ActionCache &Cache, DiagnosticEngine &Diag, StringRef CacheKey, file_types::ID Kind, StringRef Filename) { auto failure = [&](Error Err) { Diag.diagnose(SourceLoc(), diag::error_cas, toString(std::move(Err))); return nullptr; }; auto ID = CAS.parseID(CacheKey); if (!ID) return failure(ID.takeError()); auto Ref = CAS.getReference(*ID); if (!Ref) return nullptr; auto OutputRef = lookupCacheKey(CAS, Cache, *Ref); if (!OutputRef) return failure(OutputRef.takeError()); if (!*OutputRef) return nullptr; CachedResultLoader Loader(CAS, **OutputRef); std::unique_ptr Buffer; if (auto Err = Loader.replay([&](file_types::ID Type, ObjectRef Ref) -> Error { if (Kind != Type) return Error::success(); auto Proxy = CAS.getProxy(Ref); if (!Proxy) return Proxy.takeError(); Buffer = Proxy->getMemoryBuffer(Filename); return Error::success(); })) return failure(std::move(Err)); return Buffer; } static llvm::Error createCASObjectNotFoundError(const llvm::cas::CASID &ID) { return createStringError(llvm::inconvertibleErrorCode(), "CASID missing from Object Store " + ID.toString()); } static Expected mergeCASFileSystem(ObjectStore &CAS, ArrayRef FSRoots) { llvm::cas::HierarchicalTreeBuilder Builder; for (auto &Root : FSRoots) { auto ID = CAS.parseID(Root); if (!ID) return ID.takeError(); auto Ref = CAS.getReference(*ID); if (!Ref) return createCASObjectNotFoundError(*ID); Builder.pushTreeContent(*Ref, ""); } auto NewRoot = Builder.create(CAS); if (!NewRoot) return NewRoot.takeError(); return NewRoot->getRef(); } Expected> createCASFileSystem(ObjectStore &CAS, ArrayRef FSRoots, ArrayRef IncludeTrees, ArrayRef IncludeTreeFileList) { assert(!FSRoots.empty() || !IncludeTrees.empty() || !IncludeTreeFileList.empty() && "no root ID provided"); if (FSRoots.size() == 1 && IncludeTrees.empty()) { auto ID = CAS.parseID(FSRoots.front()); if (!ID) return ID.takeError(); return createCASFileSystem(CAS, *ID); } auto NewRoot = mergeCASFileSystem(CAS, FSRoots); if (!NewRoot) return NewRoot.takeError(); auto FS = createCASFileSystem(CAS, CAS.getID(*NewRoot)); if (!FS) return FS.takeError(); auto CASFS = makeIntrusiveRefCnt(std::move(*FS)); std::vector Files; // Push all Include File System onto overlay. for (auto &Tree : IncludeTrees) { auto ID = CAS.parseID(Tree); if (!ID) return ID.takeError(); auto Ref = CAS.getReference(*ID); if (!Ref) return createCASObjectNotFoundError(*ID); auto IT = clang::cas::IncludeTreeRoot::get(CAS, *Ref); if (!IT) return IT.takeError(); auto ITF = IT->getFileList(); if (!ITF) return ITF.takeError(); auto Err = ITF->forEachFile( [&](clang::cas::IncludeTree::File File, clang::cas::IncludeTree::FileList::FileSizeTy Size) -> llvm::Error { Files.push_back({File.getRef(), Size}); return llvm::Error::success(); }); if (Err) return std::move(Err); } for (auto &List: IncludeTreeFileList) { auto ID = CAS.parseID(List); if (!ID) return ID.takeError(); auto Ref = CAS.getReference(*ID); if (!Ref) return createCASObjectNotFoundError(*ID); auto IT = clang::cas::IncludeTree::FileList::get(CAS, *Ref); if (!IT) return IT.takeError(); auto Err = IT->forEachFile( [&](clang::cas::IncludeTree::File File, clang::cas::IncludeTree::FileList::FileSizeTy Size) -> llvm::Error { Files.push_back({File.getRef(), Size}); return llvm::Error::success(); }); if (Err) return std::move(Err); } auto ITFS = clang::cas::createIncludeTreeFileSystem(CAS, Files); if (!ITFS) return ITFS.takeError(); CASFS->pushOverlay(std::move(*ITFS)); return CASFS; } std::vector remapPathsFromCommandLine( ArrayRef commandLine, llvm::function_ref RemapCallback) { // parse and remap options that is path and not cache invariant. unsigned MissingIndex; unsigned MissingCount; std::vector Args; std::for_each(commandLine.begin(), commandLine.end(), [&](const std::string &arg) { Args.push_back(arg.c_str()); }); std::unique_ptr Table = createSwiftOptTable(); llvm::opt::InputArgList ParsedArgs = Table->ParseArgs( Args, MissingIndex, MissingCount, options::FrontendOption); SmallVector newArgs; std::vector newCommandLine; llvm::BumpPtrAllocator Alloc; llvm::StringSaver Saver(Alloc); for (auto *Arg : ParsedArgs) { Arg->render(ParsedArgs, newArgs); const auto &Opt = Arg->getOption(); if (Opt.matches(options::OPT_INPUT) || (!Opt.hasFlag(options::CacheInvariant) && Opt.hasFlag(options::ArgumentIsPath))) { StringRef newPath = Saver.save(RemapCallback(Arg->getValue())); newArgs.back() = newPath.data(); } } std::for_each(newArgs.begin(), newArgs.end(), [&](const char *arg) { newCommandLine.emplace_back(arg); }); return newCommandLine; } } // namespace swift