mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
See: * https://github.com/llvm/llvm-project/pull/161045 * https://github.com/llvm/llvm-project/pull/160979
999 lines
34 KiB
C++
999 lines
34 KiB
C++
//===------------ SwiftCaching.cpp - Swift Compiler -----------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2020 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Implementation of the caching APIs in libSwiftScan
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/AST/DiagnosticEngine.h"
|
|
#include "swift/AST/DiagnosticsFrontend.h"
|
|
#include "swift/Basic/Defer.h"
|
|
#include "swift/Basic/FileTypes.h"
|
|
#include "swift/Basic/SourceManager.h"
|
|
#include "swift/DependencyScan/DependencyScanImpl.h"
|
|
#include "swift/DependencyScan/StringUtils.h"
|
|
#include "swift/Driver/FrontendUtil.h"
|
|
#include "swift/Frontend/CachedDiagnostics.h"
|
|
#include "swift/Frontend/CachingUtils.h"
|
|
#include "swift/Frontend/CompileJobCacheKey.h"
|
|
#include "swift/Frontend/CompileJobCacheResult.h"
|
|
#include "swift/Frontend/DiagnosticHelper.h"
|
|
#include "swift/Frontend/Frontend.h"
|
|
#include "swift/Frontend/MakeStyleDependencies.h"
|
|
#include "swift/Frontend/PrintingDiagnosticConsumer.h"
|
|
#include "clang/CAS/CASOptions.h"
|
|
#include "clang/Frontend/CompileJobCacheResult.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/IntrusiveRefCntPtr.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/CAS/ActionCache.h"
|
|
#include "llvm/CAS/BuiltinUnifiedCASDatabases.h"
|
|
#include "llvm/CAS/CASReference.h"
|
|
#include "llvm/CAS/ObjectStore.h"
|
|
#include "llvm/Support/Allocator.h"
|
|
#include "llvm/Support/Endian.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/PrefixMapper.h"
|
|
#include "llvm/Support/StringSaver.h"
|
|
#include "llvm/Support/VirtualOutputBackend.h"
|
|
#include "llvm/Support/VirtualOutputBackends.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <optional>
|
|
#include <variant>
|
|
|
|
namespace {
|
|
/// Helper class to manage CAS/Caching from libSwiftScan C APIs.
|
|
class SwiftScanCAS {
|
|
public:
|
|
llvm::cas::ObjectStore &getCAS() const { return *CAS; }
|
|
llvm::cas::ActionCache &getCache() const { return *Cache; }
|
|
|
|
// Construct SwiftScanCAS.
|
|
static llvm::Expected<SwiftScanCAS *>
|
|
createSwiftScanCAS(llvm::StringRef Path);
|
|
|
|
static llvm::Expected<SwiftScanCAS *>
|
|
createSwiftScanCAS(clang::CASOptions &CASOpts);
|
|
|
|
private:
|
|
SwiftScanCAS(std::shared_ptr<llvm::cas::ObjectStore> CAS,
|
|
std::shared_ptr<llvm::cas::ActionCache> Cache)
|
|
: CAS(CAS), Cache(Cache) {}
|
|
|
|
std::shared_ptr<llvm::cas::ObjectStore> CAS;
|
|
std::shared_ptr<llvm::cas::ActionCache> Cache;
|
|
};
|
|
|
|
struct SwiftCachedCompilationHandle {
|
|
SwiftCachedCompilationHandle(llvm::cas::ObjectRef Key,
|
|
llvm::cas::ObjectRef Output,
|
|
clang::cas::CompileJobCacheResult &&Result,
|
|
unsigned InputIndex, SwiftScanCAS &CAS)
|
|
: Key(Key), Output(Output), InputIndex(InputIndex), Result(Result),
|
|
DB(CAS) {}
|
|
SwiftCachedCompilationHandle(llvm::cas::ObjectRef Key,
|
|
llvm::cas::ObjectRef Output,
|
|
swift::cas::CompileJobCacheResult &&Result,
|
|
unsigned InputIndex, SwiftScanCAS &CAS)
|
|
: Key(Key), Output(Output), InputIndex(InputIndex), Result(Result),
|
|
DB(CAS) {}
|
|
|
|
llvm::cas::ObjectRef Key;
|
|
llvm::cas::ObjectRef Output;
|
|
unsigned InputIndex;
|
|
std::variant<swift::cas::CompileJobCacheResult,
|
|
clang::cas::CompileJobCacheResult>
|
|
Result;
|
|
SwiftScanCAS &DB;
|
|
};
|
|
|
|
struct SwiftCachedOutputHandle {
|
|
llvm::cas::ObjectRef Ref;
|
|
swift::file_types::ID Kind;
|
|
llvm::cas::ObjectStore &CAS;
|
|
std::optional<llvm::cas::ObjectProxy> LoadedProxy;
|
|
|
|
SwiftCachedOutputHandle(llvm::cas::ObjectRef Ref, swift::file_types::ID Kind,
|
|
llvm::cas::ObjectStore &CAS)
|
|
: Ref(Ref), Kind(Kind), CAS(CAS) {}
|
|
};
|
|
|
|
struct SwiftScanReplayInstance {
|
|
swift::CompilerInvocation Invocation;
|
|
llvm::BumpPtrAllocator StringAlloc;
|
|
llvm::SmallVector<const char *> Args;
|
|
};
|
|
|
|
struct SwiftCachedReplayResult {
|
|
llvm::SmallVector<char> outMsg;
|
|
llvm::SmallVector<char> errMsg;
|
|
llvm::raw_svector_ostream outOS;
|
|
llvm::raw_svector_ostream errOS;
|
|
|
|
SwiftCachedReplayResult() : outOS(outMsg), errOS(errMsg) {}
|
|
};
|
|
} // namespace
|
|
|
|
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(clang::CASOptions, swiftscan_cas_options_t)
|
|
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(SwiftScanCAS, swiftscan_cas_t)
|
|
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(SwiftCachedCompilationHandle,
|
|
swiftscan_cached_compilation_t)
|
|
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(SwiftCachedOutputHandle,
|
|
swiftscan_cached_output_t)
|
|
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(SwiftScanReplayInstance,
|
|
swiftscan_cache_replay_instance_t)
|
|
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(SwiftCachedReplayResult,
|
|
swiftscan_cache_replay_result_t)
|
|
|
|
//=== CAS Functions ----------------------------------------------------------//
|
|
|
|
swiftscan_cas_options_t swiftscan_cas_options_create() {
|
|
clang::CASOptions *CASOpts = new clang::CASOptions();
|
|
return wrap(CASOpts);
|
|
}
|
|
|
|
void swiftscan_cas_options_dispose(swiftscan_cas_options_t options) {
|
|
delete unwrap(options);
|
|
}
|
|
|
|
void swiftscan_cas_options_set_ondisk_path(swiftscan_cas_options_t options,
|
|
const char *path) {
|
|
unwrap(options)->CASPath = path;
|
|
}
|
|
|
|
void swiftscan_cas_options_set_plugin_path(swiftscan_cas_options_t options,
|
|
const char *path) {
|
|
unwrap(options)->PluginPath = path;
|
|
}
|
|
|
|
bool swiftscan_cas_options_set_plugin_option(swiftscan_cas_options_t options,
|
|
const char *name,
|
|
const char *value,
|
|
swiftscan_string_ref_t *error) {
|
|
unwrap(options)->PluginOptions.emplace_back(name, value);
|
|
return false;
|
|
}
|
|
|
|
swiftscan_cas_t
|
|
swiftscan_cas_create_from_options(swiftscan_cas_options_t options,
|
|
swiftscan_string_ref_t *error) {
|
|
clang::CASOptions *opts = unwrap(options);
|
|
auto cas = SwiftScanCAS::createSwiftScanCAS(*opts);
|
|
if (!cas) {
|
|
*error =
|
|
swift::c_string_utils::create_clone(toString(cas.takeError()).c_str());
|
|
return nullptr;
|
|
}
|
|
return wrap(*cas);
|
|
}
|
|
|
|
void swiftscan_cas_dispose(swiftscan_cas_t cas) { delete unwrap(cas); }
|
|
|
|
swiftscan_string_ref_t swiftscan_cas_store(swiftscan_cas_t cas, uint8_t *data,
|
|
unsigned size,
|
|
swiftscan_string_ref_t *error) {
|
|
auto failure = [&](llvm::Error E) {
|
|
*error =
|
|
swift::c_string_utils::create_clone(toString(std::move(E)).c_str());
|
|
return swift::c_string_utils::create_null();
|
|
};
|
|
|
|
auto &CAS = unwrap(cas)->getCAS();
|
|
llvm::StringRef StrContent((char *)data, size);
|
|
auto Result = CAS.storeFromString({}, StrContent);
|
|
if (!Result)
|
|
return failure(Result.takeError());
|
|
|
|
*error = swift::c_string_utils::create_null();
|
|
return swift::c_string_utils::create_clone(
|
|
CAS.getID(*Result).toString().c_str());
|
|
}
|
|
|
|
int64_t swiftscan_cas_get_ondisk_size(swiftscan_cas_t cas,
|
|
swiftscan_string_ref_t *error) {
|
|
auto &CAS = unwrap(cas)->getCAS();
|
|
std::optional<uint64_t> Size;
|
|
if (auto E = CAS.getStorageSize().moveInto(Size)) {
|
|
*error =
|
|
swift::c_string_utils::create_clone(toString(std::move(E)).c_str());
|
|
return -2;
|
|
}
|
|
|
|
*error = swift::c_string_utils::create_null();
|
|
return Size ? *Size : -1;
|
|
}
|
|
|
|
bool
|
|
swiftscan_cas_set_ondisk_size_limit(swiftscan_cas_t cas, int64_t size_limit,
|
|
swiftscan_string_ref_t *error) {
|
|
if (size_limit < 0) {
|
|
*error = swift::c_string_utils::create_clone(
|
|
"invalid size limit passing to swiftscan_cas_set_ondisk_size_limit");
|
|
return true;
|
|
}
|
|
auto &CAS = unwrap(cas)->getCAS();
|
|
std::optional<uint64_t> SizeLimit;
|
|
if (size_limit > 0)
|
|
SizeLimit = size_limit;
|
|
if (auto E = CAS.setSizeLimit(SizeLimit)) {
|
|
*error =
|
|
swift::c_string_utils::create_clone(toString(std::move(E)).c_str());
|
|
return true;
|
|
}
|
|
*error = swift::c_string_utils::create_null();
|
|
return false;
|
|
}
|
|
|
|
bool swiftscan_cas_prune_ondisk_data(swiftscan_cas_t cas,
|
|
swiftscan_string_ref_t *error) {
|
|
auto &CAS = unwrap(cas)->getCAS();
|
|
if (auto E = CAS.pruneStorageData()) {
|
|
*error =
|
|
swift::c_string_utils::create_clone(toString(std::move(E)).c_str());
|
|
return true;
|
|
}
|
|
*error = swift::c_string_utils::create_null();
|
|
return false;
|
|
}
|
|
|
|
/// Expand the invocation if there is responseFile into Args that are passed in
|
|
/// the parameter. Return swift-frontend arguments in an ArrayRef, which has the
|
|
/// first "-frontend" option dropped if needed.
|
|
static llvm::ArrayRef<const char *>
|
|
expandSwiftInvocation(int argc, const char **argv, llvm::StringSaver &Saver,
|
|
llvm::SmallVectorImpl<const char *> &ArgsStorage) {
|
|
ArgsStorage.reserve(argc);
|
|
for (int i = 0; i < argc; ++i)
|
|
ArgsStorage.push_back(Saver.save(argv[i]).data());
|
|
swift::driver::ExpandResponseFilesWithRetry(Saver, ArgsStorage);
|
|
|
|
// Drop the `-frontend` option if it is passed.
|
|
llvm::ArrayRef<const char*> FrontendArgs(ArgsStorage);
|
|
if (!FrontendArgs.empty() &&
|
|
llvm::StringRef(FrontendArgs.front()) == "-frontend")
|
|
FrontendArgs = FrontendArgs.drop_front();
|
|
return FrontendArgs;
|
|
}
|
|
|
|
static llvm::Expected<std::string>
|
|
computeCacheKey(llvm::cas::ObjectStore &CAS, llvm::ArrayRef<const char *> Args,
|
|
llvm::StringRef InputPath) {
|
|
auto BaseKey = swift::createCompileJobBaseCacheKey(CAS, Args);
|
|
if (!BaseKey)
|
|
return BaseKey.takeError();
|
|
|
|
// Parse the arguments to figure out the index for the input.
|
|
swift::CompilerInvocation Invocation;
|
|
swift::SourceManager SourceMgr;
|
|
swift::DiagnosticEngine Diags(SourceMgr);
|
|
|
|
if (Invocation.parseArgs(Args, Diags, nullptr, {}))
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Argument parsing failed");
|
|
|
|
auto computeKey = [&](unsigned Index) -> llvm::Expected<std::string> {
|
|
auto Key = swift::createCompileJobCacheKeyForOutput(CAS, *BaseKey, Index);
|
|
if (!Key)
|
|
return Key.takeError();
|
|
return CAS.getID(*Key).toString();
|
|
};
|
|
auto AllInputs =
|
|
Invocation.getFrontendOptions().InputsAndOutputs.getAllInputs();
|
|
// First pass, check for path equal.
|
|
for (unsigned Idx = 0; Idx < AllInputs.size(); ++Idx) {
|
|
if (AllInputs[Idx].getFileName() == InputPath)
|
|
return computeKey(Idx);
|
|
}
|
|
|
|
// If not found, slow second iteration with real_path.
|
|
llvm::SmallString<256> InputRealPath;
|
|
llvm::sys::fs::real_path(InputPath, InputRealPath, true);
|
|
for (unsigned Idx = 0; Idx < AllInputs.size(); ++Idx) {
|
|
llvm::SmallString<256> TestRealPath;
|
|
llvm::sys::fs::real_path(AllInputs[Idx].getFileName(), TestRealPath, true);
|
|
if (InputRealPath == TestRealPath)
|
|
return computeKey(Idx);
|
|
}
|
|
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"requested input not found from invocation");
|
|
}
|
|
|
|
static llvm::Expected<std::string>
|
|
computeCacheKeyFromIndex(llvm::cas::ObjectStore &CAS,
|
|
llvm::ArrayRef<const char *> Args,
|
|
unsigned InputIndex) {
|
|
auto BaseKey = swift::createCompileJobBaseCacheKey(CAS, Args);
|
|
if (!BaseKey)
|
|
return BaseKey.takeError();
|
|
|
|
auto Key =
|
|
swift::createCompileJobCacheKeyForOutput(CAS, *BaseKey, InputIndex);
|
|
if (!Key)
|
|
return Key.takeError();
|
|
return CAS.getID(*Key).toString();
|
|
}
|
|
|
|
swiftscan_string_ref_t
|
|
swiftscan_cache_compute_key(swiftscan_cas_t cas, int argc, const char **argv,
|
|
const char *input, swiftscan_string_ref_t *error) {
|
|
llvm::SmallVector<const char *> ArgsStorage;
|
|
llvm::BumpPtrAllocator Alloc;
|
|
llvm::StringSaver Saver(Alloc);
|
|
auto Args = expandSwiftInvocation(argc, argv, Saver, ArgsStorage);
|
|
|
|
auto ID = computeCacheKey(unwrap(cas)->getCAS(), Args, input);
|
|
if (!ID) {
|
|
*error =
|
|
swift::c_string_utils::create_clone(toString(ID.takeError()).c_str());
|
|
return swift::c_string_utils::create_null();
|
|
}
|
|
*error = swift::c_string_utils::create_null();
|
|
return swift::c_string_utils::create_clone(ID->c_str());
|
|
}
|
|
|
|
swiftscan_string_ref_t
|
|
swiftscan_cache_compute_key_from_input_index(swiftscan_cas_t cas, int argc,
|
|
const char **argv,
|
|
unsigned input_index,
|
|
swiftscan_string_ref_t *error) {
|
|
llvm::SmallVector<const char *> ArgsStorage;
|
|
llvm::BumpPtrAllocator Alloc;
|
|
llvm::StringSaver Saver(Alloc);
|
|
auto Args = expandSwiftInvocation(argc, argv, Saver, ArgsStorage);
|
|
|
|
auto ID =
|
|
computeCacheKeyFromIndex(unwrap(cas)->getCAS(), Args, input_index);
|
|
if (!ID) {
|
|
*error =
|
|
swift::c_string_utils::create_clone(toString(ID.takeError()).c_str());
|
|
return swift::c_string_utils::create_null();
|
|
}
|
|
*error = swift::c_string_utils::create_null();
|
|
return swift::c_string_utils::create_clone(ID->c_str());
|
|
}
|
|
|
|
// Create a non-owning string ref that is used in call backs.
|
|
static swiftscan_string_ref_t createNonOwningString(llvm::StringRef Str) {
|
|
if (Str.empty())
|
|
return swift::c_string_utils::create_null();
|
|
|
|
swiftscan_string_ref_t Result;
|
|
Result.data = Str.data();
|
|
Result.length = Str.size();
|
|
return Result;
|
|
}
|
|
|
|
static llvm::Error createUnsupportedSchemaError() {
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"unsupported compile result schema found");
|
|
}
|
|
|
|
static llvm::Expected<SwiftCachedCompilationHandle *>
|
|
createCachedCompilation(SwiftScanCAS &CAS, const llvm::cas::CASID &ID,
|
|
const llvm::cas::CASID &Key) {
|
|
auto Ref = CAS.getCAS().getReference(ID);
|
|
if (!Ref)
|
|
return nullptr;
|
|
|
|
auto Proxy = CAS.getCAS().getProxy(*Ref);
|
|
if (!Proxy)
|
|
return Proxy.takeError();
|
|
|
|
// Load input file name from the key CAS object. Input file name is the data
|
|
// blob in the root node.
|
|
auto KeyProxy = CAS.getCAS().getProxy(Key);
|
|
if (!KeyProxy)
|
|
return KeyProxy.takeError();
|
|
auto Input = KeyProxy->getData();
|
|
|
|
unsigned Index = llvm::support::endian::read<uint32_t>(
|
|
Input.data(), llvm::endianness::little);
|
|
{
|
|
swift::cas::CompileJobResultSchema Schema(CAS.getCAS());
|
|
if (Schema.isRootNode(*Proxy)) {
|
|
auto Result = Schema.load(Proxy->getRef());
|
|
if (!Result)
|
|
return Result.takeError();
|
|
return new SwiftCachedCompilationHandle(KeyProxy->getRef(), *Ref,
|
|
std::move(*Result), Index, CAS);
|
|
}
|
|
}
|
|
{
|
|
clang::cas::CompileJobResultSchema Schema(CAS.getCAS());
|
|
if (Schema.isRootNode(*Proxy)) {
|
|
auto Result = Schema.load(Proxy->getRef());
|
|
if (!Result)
|
|
return Result.takeError();
|
|
return new SwiftCachedCompilationHandle(KeyProxy->getRef(), *Ref,
|
|
std::move(*Result), Index, CAS);
|
|
}
|
|
}
|
|
return createUnsupportedSchemaError();
|
|
}
|
|
|
|
swiftscan_cached_compilation_t
|
|
swiftscan_cache_query(swiftscan_cas_t cas, const char *key, bool globally,
|
|
swiftscan_string_ref_t *error) {
|
|
auto failure = [&](llvm::Error E) {
|
|
*error =
|
|
swift::c_string_utils::create_clone(toString(std::move(E)).c_str());
|
|
return nullptr;
|
|
};
|
|
auto notfound = [&]() {
|
|
*error = swift::c_string_utils::create_null();
|
|
return nullptr;
|
|
};
|
|
|
|
auto &CAS = unwrap(cas)->getCAS();
|
|
auto &Cache = unwrap(cas)->getCache();
|
|
auto ID = CAS.parseID(key);
|
|
if (!ID)
|
|
return failure(ID.takeError());
|
|
|
|
auto Result = Cache.get(*ID, globally);
|
|
if (!Result)
|
|
return failure(Result.takeError());
|
|
if (!*Result)
|
|
return notfound();
|
|
|
|
auto Cached = createCachedCompilation(*unwrap(cas), **Result, *ID);
|
|
if (!Cached)
|
|
return failure(Cached.takeError());
|
|
|
|
if (!*Cached)
|
|
return notfound();
|
|
|
|
*error = swift::c_string_utils::create_null();
|
|
return wrap(*Cached);
|
|
}
|
|
|
|
void swiftscan_cache_query_async(
|
|
swiftscan_cas_t cas, const char *key, bool globally, void *ctx,
|
|
void (*callback)(void *ctx, swiftscan_cached_compilation_t,
|
|
swiftscan_string_ref_t error),
|
|
swiftscan_cache_cancellation_token_t *token) {
|
|
if (token)
|
|
*token = nullptr;
|
|
auto &CAS = unwrap(cas)->getCAS();
|
|
auto &Cache = unwrap(cas)->getCache();
|
|
auto failure = [ctx, callback](llvm::Error E) {
|
|
std::string ErrMsg = toString(std::move(E));
|
|
return callback(ctx, nullptr, createNonOwningString(ErrMsg));
|
|
};
|
|
auto notfound = [ctx, callback]() {
|
|
return callback(ctx, nullptr, swift::c_string_utils::create_null());
|
|
};
|
|
auto success = [ctx, callback](swiftscan_cached_compilation_t comp) {
|
|
return callback(ctx, comp, swift::c_string_utils::create_null());
|
|
};
|
|
|
|
auto ID = CAS.parseID(key);
|
|
if (!ID)
|
|
return failure(ID.takeError());
|
|
|
|
auto KeyID = *ID;
|
|
Cache.getAsync(*ID, globally,
|
|
[failure, notfound, success, cas, KeyID](
|
|
llvm::Expected<std::optional<llvm::cas::CASID>> Result) {
|
|
if (!Result)
|
|
return failure(Result.takeError());
|
|
if (!*Result)
|
|
return notfound();
|
|
|
|
auto Cached =
|
|
createCachedCompilation(*unwrap(cas), **Result, KeyID);
|
|
if (!Cached)
|
|
return failure(Cached.takeError());
|
|
|
|
if (!*Cached)
|
|
return notfound();
|
|
|
|
return success(wrap(*Cached));
|
|
});
|
|
}
|
|
|
|
unsigned swiftscan_cached_compilation_get_num_outputs(
|
|
swiftscan_cached_compilation_t id) {
|
|
auto *Comp = unwrap(id);
|
|
if (auto *Result =
|
|
std::get_if<swift::cas::CompileJobCacheResult>(&Comp->Result))
|
|
return Result->getNumOutputs();
|
|
|
|
auto *Result = std::get_if<clang::cas::CompileJobCacheResult>(&Comp->Result);
|
|
assert(Result && "unexpected variant");
|
|
return Result->getNumOutputs();
|
|
}
|
|
|
|
swiftscan_cached_output_t
|
|
swiftscan_cached_compilation_get_output(swiftscan_cached_compilation_t id,
|
|
unsigned idx) {
|
|
auto *Comp = unwrap(id);
|
|
if (auto *Result =
|
|
std::get_if<swift::cas::CompileJobCacheResult>(&Comp->Result))
|
|
return wrap(new SwiftCachedOutputHandle(Result->getOutput(idx).Object,
|
|
Result->getOutput(idx).Kind,
|
|
Comp->DB.getCAS()));
|
|
|
|
auto *Result = std::get_if<clang::cas::CompileJobCacheResult>(&Comp->Result);
|
|
assert(Result && "unexpected variant");
|
|
swift::file_types::ID Kind = swift::file_types::TY_INVALID;
|
|
switch (Result->getOutput(idx).Kind) {
|
|
case clang::cas::CompileJobCacheResult::OutputKind::MainOutput:
|
|
Kind = swift::file_types::TY_ClangModuleFile;
|
|
break;
|
|
case clang::cas::CompileJobCacheResult::OutputKind::Dependencies:
|
|
Kind = swift::file_types::TY_Dependencies;
|
|
break;
|
|
case clang::cas::CompileJobCacheResult::OutputKind::SerializedDiagnostics:
|
|
Kind = swift::file_types::TY_CachedDiagnostics;
|
|
break;
|
|
}
|
|
assert(Kind != swift::file_types::TY_INVALID && "Invalid kind");
|
|
return wrap(new SwiftCachedOutputHandle(Result->getOutput(idx).Object, Kind,
|
|
Comp->DB.getCAS()));
|
|
}
|
|
|
|
bool swiftscan_cached_compilation_is_uncacheable(
|
|
swiftscan_cached_compilation_t id) {
|
|
// Currently, all compilations are cacheable.
|
|
return false;
|
|
}
|
|
|
|
void swiftscan_cached_compilation_dispose(swiftscan_cached_compilation_t id) {
|
|
delete unwrap(id);
|
|
}
|
|
|
|
bool swiftscan_cached_output_load(swiftscan_cached_output_t output,
|
|
swiftscan_string_ref_t *error) {
|
|
auto *Out = unwrap(output);
|
|
auto failure = [&](llvm::Error E) {
|
|
*error =
|
|
swift::c_string_utils::create_clone(toString(std::move(E)).c_str());
|
|
return false;
|
|
};
|
|
auto notfound = [&]() {
|
|
*error = swift::c_string_utils::create_null();
|
|
return false;
|
|
};
|
|
auto success = [&]() {
|
|
*error = swift::c_string_utils::create_null();
|
|
return true;
|
|
};
|
|
// If proxy exists, there is nothing to be done.
|
|
if (Out->LoadedProxy)
|
|
return success();
|
|
|
|
if (auto Err = Out->CAS.getProxyIfExists(Out->Ref).moveInto(Out->LoadedProxy))
|
|
return failure(std::move(Err));
|
|
|
|
if (!Out->LoadedProxy)
|
|
return notfound();
|
|
|
|
llvm::DenseSet<llvm::cas::ObjectRef> Visited;
|
|
llvm::SmallVector<llvm::cas::ObjectRef> WorkList;
|
|
auto addToWorkList = [&](llvm::cas::ObjectRef Item) {
|
|
if (Visited.insert(Item).second)
|
|
WorkList.push_back(Item);
|
|
};
|
|
|
|
addToWorkList(Out->LoadedProxy->getRef());
|
|
while (!WorkList.empty()) {
|
|
auto Current = WorkList.pop_back_val();
|
|
auto Proxy = Out->CAS.getProxyIfExists(Current);
|
|
if (!Proxy)
|
|
return failure(Proxy.takeError());
|
|
|
|
if (!*Proxy)
|
|
return notfound();
|
|
|
|
if (auto Err = (*Proxy)->forEachReference(
|
|
[&](llvm::cas::ObjectRef Ref) -> llvm::Error {
|
|
addToWorkList(Ref);
|
|
return llvm::Error::success();
|
|
}))
|
|
return failure(std::move(Err));
|
|
}
|
|
return success();
|
|
}
|
|
|
|
namespace {
|
|
/// Asynchronously visits the graph of the object node to ensure it's fully
|
|
/// materialized.
|
|
class AsyncObjectLoader
|
|
: public std::enable_shared_from_this<AsyncObjectLoader> {
|
|
void *Ctx;
|
|
void (*Callback)(void *Ctx, bool, swiftscan_string_ref_t);
|
|
llvm::cas::ObjectStore &CAS;
|
|
|
|
// The output handle to update after load if set.
|
|
SwiftCachedOutputHandle *OutputHandle;
|
|
|
|
llvm::SmallDenseSet<llvm::cas::ObjectRef> ObjectsSeen;
|
|
unsigned NumPending = 0;
|
|
std::optional<llvm::cas::ObjectProxy> RootObj;
|
|
std::atomic<bool> MissingNode{false};
|
|
/// The first error that occurred.
|
|
std::optional<llvm::Error> ErrOccurred;
|
|
std::mutex Mutex;
|
|
|
|
public:
|
|
AsyncObjectLoader(void *Ctx,
|
|
void (*Callback)(void *Ctx, bool, swiftscan_string_ref_t),
|
|
llvm::cas::ObjectStore &CAS,
|
|
SwiftCachedOutputHandle *Handle = nullptr)
|
|
: Ctx(Ctx), Callback(Callback), CAS(CAS), OutputHandle(Handle) {}
|
|
|
|
void visit(llvm::cas::ObjectRef Ref, bool IsRootNode) {
|
|
{
|
|
std::lock_guard<std::mutex> Guard(Mutex);
|
|
if (!ObjectsSeen.insert(Ref).second)
|
|
return;
|
|
++NumPending;
|
|
}
|
|
auto This = shared_from_this();
|
|
CAS.getProxyAsync(
|
|
Ref, [This, IsRootNode](
|
|
llvm::Expected<std::optional<llvm::cas::ObjectProxy>> Obj) {
|
|
SWIFT_DEFER { This->finishedNode(); };
|
|
if (!Obj) {
|
|
This->encounteredError(Obj.takeError());
|
|
return;
|
|
}
|
|
if (!*Obj) {
|
|
This->MissingNode = true;
|
|
return;
|
|
}
|
|
if (IsRootNode)
|
|
This->RootObj = *Obj;
|
|
if (auto Err = (*Obj)->forEachReference(
|
|
[&This](llvm::cas::ObjectRef Sub) -> llvm::Error {
|
|
This->visit(Sub, /*IsRootNode*/ false);
|
|
return llvm::Error::success();
|
|
})) {
|
|
This->encounteredError(std::move(Err));
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
|
|
void finishedNode() {
|
|
{
|
|
std::lock_guard<std::mutex> Guard(Mutex);
|
|
assert(NumPending);
|
|
--NumPending;
|
|
if (NumPending != 0)
|
|
return;
|
|
}
|
|
if (ErrOccurred) {
|
|
std::string ErrMsg = toString(std::move(*ErrOccurred));
|
|
return Callback(Ctx, false, createNonOwningString(ErrMsg));
|
|
}
|
|
if (MissingNode)
|
|
return Callback(Ctx, false, swift::c_string_utils::create_null());
|
|
|
|
if (OutputHandle)
|
|
OutputHandle->LoadedProxy = RootObj;
|
|
return Callback(Ctx, true, swift::c_string_utils::create_null());
|
|
}
|
|
|
|
/// Only keeps the first error that occurred.
|
|
void encounteredError(llvm::Error &&E) {
|
|
std::lock_guard<std::mutex> Guard(Mutex);
|
|
if (ErrOccurred) {
|
|
llvm::consumeError(std::move(E));
|
|
return;
|
|
}
|
|
ErrOccurred = std::move(E);
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
void swiftscan_cached_output_load_async(
|
|
swiftscan_cached_output_t output, void *ctx,
|
|
void (*callback)(void *ctx, bool success, swiftscan_string_ref_t error),
|
|
swiftscan_cache_cancellation_token_t *token) {
|
|
if (token)
|
|
*token = nullptr;
|
|
|
|
auto *Out = unwrap(output);
|
|
if (Out->LoadedProxy)
|
|
return callback(ctx, true, swift::c_string_utils::create_null());
|
|
|
|
auto Loader =
|
|
std::make_shared<AsyncObjectLoader>(ctx, callback, Out->CAS, Out);
|
|
Loader->visit(Out->Ref, /*IsRootNode*/ true);
|
|
}
|
|
|
|
void swiftscan_cache_download_cas_object_async(
|
|
swiftscan_cas_t cas, const char *id, void *ctx,
|
|
void (*callback)(void *ctx, bool success, swiftscan_string_ref_t error),
|
|
swiftscan_cache_cancellation_token_t *token) {
|
|
if (token)
|
|
*token = nullptr;
|
|
|
|
auto failure = [ctx, callback](llvm::Error E) {
|
|
std::string ErrMsg = toString(std::move(E));
|
|
return callback(ctx, false, createNonOwningString(ErrMsg));
|
|
};
|
|
|
|
auto &CAS = unwrap(cas)->getCAS();
|
|
auto ID = CAS.parseID(id);
|
|
if (!ID)
|
|
return failure(ID.takeError());
|
|
|
|
auto Ref = CAS.getReference(*ID);
|
|
if (!Ref)
|
|
return callback(ctx, false, swift::c_string_utils::create_null());
|
|
|
|
auto Loader =
|
|
std::make_shared<AsyncObjectLoader>(ctx, callback, CAS);
|
|
Loader->visit(*Ref, /*IsRootNode*/ true);
|
|
}
|
|
|
|
bool swiftscan_cached_output_is_materialized(swiftscan_cached_output_t output) {
|
|
auto *Out = unwrap(output);
|
|
// Already loaded.
|
|
if (Out->LoadedProxy)
|
|
return true;
|
|
|
|
auto Loaded = Out->CAS.isMaterialized(Out->Ref);
|
|
if (!Loaded) {
|
|
// There is an error loading, output is not materialized. Discard error and
|
|
// return false.
|
|
consumeError(Loaded.takeError());
|
|
return false;
|
|
}
|
|
return *Loaded;
|
|
}
|
|
|
|
swiftscan_string_ref_t
|
|
swiftscan_cached_output_get_casid(swiftscan_cached_output_t output) {
|
|
auto *Out = unwrap(output);
|
|
auto ID = Out->CAS.getID(Out->Ref);
|
|
return swift::c_string_utils::create_clone(ID.toString().c_str());
|
|
}
|
|
|
|
void swiftscan_cached_compilation_make_global_async(
|
|
swiftscan_cached_compilation_t comp, void *ctx,
|
|
void (*callback)(void *ctx, swiftscan_string_ref_t error),
|
|
swiftscan_cache_cancellation_token_t *token) {
|
|
if (token)
|
|
*token = nullptr;
|
|
auto failure = [ctx, callback](llvm::Error E) {
|
|
std::string ErrMsg = toString(std::move(E));
|
|
return callback(ctx, createNonOwningString(ErrMsg));
|
|
};
|
|
auto success = [ctx, callback]() {
|
|
return callback(ctx, swift::c_string_utils::create_null());
|
|
};
|
|
|
|
auto *Compilation = unwrap(comp);
|
|
auto &CAS = Compilation->DB.getCAS();
|
|
auto &Cache = Compilation->DB.getCache();
|
|
|
|
auto ID = CAS.getID(Compilation->Key);
|
|
auto Result = CAS.getID(Compilation->Output);
|
|
|
|
Cache.putAsync(ID, Result, /*globally=*/true,
|
|
[failure, success](llvm::Error E) {
|
|
if (E)
|
|
return failure(std::move(E));
|
|
|
|
return success();
|
|
});
|
|
}
|
|
|
|
swiftscan_string_ref_t
|
|
swiftscan_cached_output_get_name(swiftscan_cached_output_t output) {
|
|
auto *Out = unwrap(output);
|
|
return swift::c_string_utils::create_clone(
|
|
swift::file_types::getTypeName(Out->Kind).str().c_str());
|
|
}
|
|
|
|
swiftscan_cache_replay_instance_t
|
|
swiftscan_cache_replay_instance_create(int argc, const char **argv,
|
|
swiftscan_string_ref_t *error) {
|
|
auto *Instance = new SwiftScanReplayInstance();
|
|
llvm::SmallVector<const char *> Compilation;
|
|
llvm::StringSaver Saver(Instance->StringAlloc);
|
|
auto Args = expandSwiftInvocation(argc, argv, Saver, Instance->Args);
|
|
|
|
// Capture the diagnostics when creating invocation.
|
|
std::string err_msg;
|
|
llvm::raw_string_ostream OS(err_msg);
|
|
swift::PrintingDiagnosticConsumer Diags(OS);
|
|
swift::SourceManager SrcMgr;
|
|
swift::DiagnosticEngine DE(SrcMgr);
|
|
DE.addConsumer(Diags);
|
|
|
|
if (Instance->Invocation.parseArgs(Args, DE, nullptr, {})) {
|
|
delete Instance;
|
|
*error = swift::c_string_utils::create_clone(err_msg.c_str());
|
|
return nullptr;
|
|
}
|
|
|
|
if (!Instance->Invocation.getCASOptions().EnableCaching) {
|
|
delete Instance;
|
|
*error = swift::c_string_utils::create_clone(
|
|
"caching is not enabled from command-line");
|
|
return nullptr;
|
|
}
|
|
|
|
// Clear the LLVMArgs as `llvm::cl::ParseCommandLineOptions` is not
|
|
// thread-safe to be called in libSwiftScan. The replay instance should not be
|
|
// used to do compilation so clearing `-Xllvm` should not affect replay
|
|
// result.
|
|
Instance->Invocation.getFrontendOptions().LLVMArgs.clear();
|
|
|
|
return wrap(Instance);
|
|
}
|
|
|
|
void swiftscan_cache_replay_instance_dispose(
|
|
swiftscan_cache_replay_instance_t instance) {
|
|
delete unwrap(instance);
|
|
}
|
|
|
|
namespace {
|
|
class StreamOutputFileImpl final
|
|
: public llvm::RTTIExtends<StreamOutputFileImpl,
|
|
llvm::vfs::OutputFileImpl> {
|
|
|
|
public:
|
|
StreamOutputFileImpl(llvm::raw_pwrite_stream &OS) : OS(OS) {}
|
|
llvm::Error keep() final { return llvm::Error::success(); }
|
|
llvm::Error discard() final { return llvm::Error::success(); }
|
|
llvm::raw_pwrite_stream &getOS() final { return OS; }
|
|
|
|
private:
|
|
llvm::raw_pwrite_stream &OS;
|
|
};
|
|
|
|
// The replay output backend. Currently, it redirects "-" to the
|
|
// stdout provided.
|
|
struct ReplayOutputBackend : public llvm::vfs::ProxyOutputBackend {
|
|
llvm::Expected<std::unique_ptr<llvm::vfs::OutputFileImpl>>
|
|
createFileImpl(llvm::StringRef Path,
|
|
std::optional<llvm::vfs::OutputConfig> Config) override {
|
|
if (Path == "-")
|
|
return std::make_unique<StreamOutputFileImpl>(StdOut);
|
|
return llvm::vfs::ProxyOutputBackend::createFileImpl(Path, Config);
|
|
}
|
|
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::OutputBackend>
|
|
cloneImpl() const override {
|
|
return llvm::makeIntrusiveRefCnt<ReplayOutputBackend>(
|
|
getUnderlyingBackend().clone(), StdOut);
|
|
}
|
|
|
|
ReplayOutputBackend(
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::OutputBackend> UnderlyingBackend,
|
|
llvm::raw_pwrite_stream &StdOut)
|
|
: ProxyOutputBackend(UnderlyingBackend), StdOut(StdOut) {}
|
|
|
|
llvm::raw_pwrite_stream &StdOut;
|
|
};
|
|
} // namespace
|
|
|
|
static llvm::Error replayCompilation(SwiftScanReplayInstance &Instance,
|
|
llvm::cas::ObjectStore &CAS,
|
|
SwiftCachedCompilationHandle &Comp,
|
|
llvm::raw_pwrite_stream &Out,
|
|
llvm::raw_pwrite_stream &Err) {
|
|
using namespace swift;
|
|
using namespace llvm;
|
|
CompilerInstance Inst;
|
|
CompilerInvocation &Invocation = Instance.Invocation;
|
|
|
|
// Find the input file from the invocation.
|
|
auto &InputsAndOutputs =
|
|
Instance.Invocation.getFrontendOptions().InputsAndOutputs;
|
|
auto AllInputs = InputsAndOutputs.getAllInputs();
|
|
if (Comp.InputIndex >= AllInputs.size())
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"InputFile index too large for compilation");
|
|
const auto &Input = AllInputs[Comp.InputIndex];
|
|
|
|
// Setup DiagnosticsConsumers.
|
|
DiagnosticHelper DH = DiagnosticHelper::create(
|
|
Inst, Invocation, Instance.Args, Err, /*QuasiPID=*/true);
|
|
|
|
std::string InstanceSetupError;
|
|
if (Inst.setupForReplay(Instance.Invocation, InstanceSetupError,
|
|
Instance.Args))
|
|
return createStringError(inconvertibleErrorCode(), InstanceSetupError);
|
|
|
|
auto *CDP = Inst.getCachingDiagnosticsProcessor();
|
|
assert(CDP && "CachingDiagnosticsProcessor needs to be setup for replay");
|
|
// No diags are captured in replay instance.
|
|
CDP->endDiagnosticCapture();
|
|
// Replay settings.
|
|
bool Remarks = Instance.Invocation.getCASOptions().EnableCachingRemarks;
|
|
bool UseCASBackend = Invocation.getIRGenOptions().UseCASBackend;
|
|
|
|
// OutputBackend for replay.
|
|
ReplayOutputBackend Backend(
|
|
makeIntrusiveRefCnt<llvm::vfs::OnDiskOutputBackend>(), Out);
|
|
|
|
if (!replayCachedCompilerOutputsForInput(
|
|
CAS, Comp.Output, Input, Comp.InputIndex, Inst.getDiags(), DH,
|
|
Backend, Instance.Invocation.getFrontendOptions(), *CDP, Remarks,
|
|
UseCASBackend)) {
|
|
Inst.getDiags().diagnose(SourceLoc(), diag::cache_replay_failed,
|
|
"failed to load all outputs");
|
|
}
|
|
|
|
Inst.getDiags().finishProcessing();
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
swiftscan_cache_replay_result_t
|
|
swiftscan_cache_replay_compilation(swiftscan_cache_replay_instance_t instance,
|
|
swiftscan_cached_compilation_t comp,
|
|
swiftscan_string_ref_t *error) {
|
|
SwiftScanReplayInstance &Instance = *unwrap(instance);
|
|
SwiftCachedCompilationHandle &Comp = *unwrap(comp);
|
|
SwiftCachedReplayResult *Result = new SwiftCachedReplayResult();
|
|
|
|
if (auto err = replayCompilation(Instance, Comp.DB.getCAS(), Comp,
|
|
Result->outOS, Result->errOS)) {
|
|
*error =
|
|
swift::c_string_utils::create_clone(toString(std::move(err)).c_str());
|
|
delete Result;
|
|
return nullptr;
|
|
}
|
|
*error = swift::c_string_utils::create_null();
|
|
return wrap(Result);
|
|
}
|
|
|
|
swiftscan_string_ref_t swiftscan_cache_replay_result_get_stdout(
|
|
swiftscan_cache_replay_result_t result) {
|
|
return createNonOwningString(unwrap(result)->outOS.str());
|
|
}
|
|
|
|
swiftscan_string_ref_t swiftscan_cache_replay_result_get_stderr(
|
|
swiftscan_cache_replay_result_t result) {
|
|
return createNonOwningString(unwrap(result)->errOS.str());
|
|
}
|
|
|
|
void swiftscan_cached_output_dispose(swiftscan_cached_output_t out) {
|
|
delete unwrap(out);
|
|
}
|
|
|
|
void swiftscan_cache_replay_result_dispose(
|
|
swiftscan_cache_replay_result_t result) {
|
|
delete unwrap(result);
|
|
}
|
|
|
|
// FIXME: implement cancellation.
|
|
void swiftscan_cache_action_cancel(swiftscan_cache_cancellation_token_t) {}
|
|
void swiftscan_cache_cancellation_token_dispose(
|
|
swiftscan_cache_cancellation_token_t) {}
|
|
|
|
llvm::Expected<SwiftScanCAS *>
|
|
SwiftScanCAS::createSwiftScanCAS(llvm::StringRef Path) {
|
|
clang::CASOptions Opts;
|
|
Opts.CASPath = Path;
|
|
|
|
return createSwiftScanCAS(Opts);
|
|
}
|
|
|
|
llvm::Expected<SwiftScanCAS *>
|
|
SwiftScanCAS::createSwiftScanCAS(clang::CASOptions &CASOpts) {
|
|
auto DB = CASOpts.getOrCreateDatabases();
|
|
if (!DB)
|
|
return DB.takeError();
|
|
|
|
return new SwiftScanCAS(std::move(DB->first), std::move(DB->second));
|
|
}
|