268 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8///9/// \file This file implements the underlying ActionCache implementations.10///11//===----------------------------------------------------------------------===//12 13#include "BuiltinCAS.h"14#include "llvm/ADT/TrieRawHashMap.h"15#include "llvm/CAS/ActionCache.h"16#include "llvm/CAS/OnDiskKeyValueDB.h"17#include "llvm/CAS/UnifiedOnDiskCache.h"18#include "llvm/Config/llvm-config.h"19#include "llvm/Support/BLAKE3.h"20#include "llvm/Support/Errc.h"21 22#define DEBUG_TYPE "cas-action-caches"23 24using namespace llvm;25using namespace llvm::cas;26 27namespace {28 29using HasherT = BLAKE3;30using HashType = decltype(HasherT::hash(std::declval<ArrayRef<uint8_t> &>()));31 32template <size_t Size> class CacheEntry {33public:34 CacheEntry() = default;35 CacheEntry(ArrayRef<uint8_t> Hash) { llvm::copy(Hash, Value.data()); }36 CacheEntry(const CacheEntry &Entry) { llvm::copy(Entry.Value, Value.data()); }37 ArrayRef<uint8_t> getValue() const { return Value; }38 39private:40 std::array<uint8_t, Size> Value;41};42 43/// Builtin InMemory ActionCache that stores the mapping in memory.44class InMemoryActionCache final : public ActionCache {45public:46 InMemoryActionCache()47 : ActionCache(builtin::BuiltinCASContext::getDefaultContext()) {}48 49 Error putImpl(ArrayRef<uint8_t> ActionKey, const CASID &Result,50 bool CanBeDistributed) final;51 Expected<std::optional<CASID>> getImpl(ArrayRef<uint8_t> ActionKey,52 bool CanBeDistributed) const final;53 54 Error validate() const final {55 return createStringError("InMemoryActionCache doesn't support validate()");56 }57 58private:59 using DataT = CacheEntry<sizeof(HashType)>;60 using InMemoryCacheT = ThreadSafeTrieRawHashMap<DataT, sizeof(HashType)>;61 62 InMemoryCacheT Cache;63};64 65/// Builtin basic OnDiskActionCache that uses one underlying OnDiskKeyValueDB.66class OnDiskActionCache final : public ActionCache {67public:68 Error putImpl(ArrayRef<uint8_t> ActionKey, const CASID &Result,69 bool CanBeDistributed) final;70 Expected<std::optional<CASID>> getImpl(ArrayRef<uint8_t> ActionKey,71 bool CanBeDistributed) const final;72 73 static Expected<std::unique_ptr<OnDiskActionCache>> create(StringRef Path);74 75 Error validate() const final;76 77private:78 static StringRef getHashName() { return "BLAKE3"; }79 80 OnDiskActionCache(std::unique_ptr<ondisk::OnDiskKeyValueDB> DB);81 82 std::unique_ptr<ondisk::OnDiskKeyValueDB> DB;83 using DataT = CacheEntry<sizeof(HashType)>;84};85 86/// Builtin unified ActionCache that wraps around UnifiedOnDiskCache to provide87/// access to its ActionCache.88class UnifiedOnDiskActionCache final : public ActionCache {89public:90 Error putImpl(ArrayRef<uint8_t> ActionKey, const CASID &Result,91 bool CanBeDistributed) final;92 Expected<std::optional<CASID>> getImpl(ArrayRef<uint8_t> ActionKey,93 bool CanBeDistributed) const final;94 95 UnifiedOnDiskActionCache(std::shared_ptr<ondisk::UnifiedOnDiskCache> UniDB);96 97 Error validate() const final;98 99private:100 std::shared_ptr<ondisk::UnifiedOnDiskCache> UniDB;101};102} // end namespace103 104static Error createResultCachePoisonedError(ArrayRef<uint8_t> KeyHash,105 const CASContext &Context,106 CASID Output,107 ArrayRef<uint8_t> ExistingOutput) {108 std::string Existing =109 CASID::create(&Context, toStringRef(ExistingOutput)).toString();110 SmallString<64> Key;111 toHex(KeyHash, /*LowerCase=*/true, Key);112 return createStringError(std::make_error_code(std::errc::invalid_argument),113 "cache poisoned for '" + Key + "' (new='" +114 Output.toString() + "' vs. existing '" +115 Existing + "')");116}117 118Expected<std::optional<CASID>>119InMemoryActionCache::getImpl(ArrayRef<uint8_t> Key,120 bool /*CanBeDistributed*/) const {121 auto Result = Cache.find(Key);122 if (!Result)123 return std::nullopt;124 return CASID::create(&getContext(), toStringRef(Result->Data.getValue()));125}126 127Error InMemoryActionCache::putImpl(ArrayRef<uint8_t> Key, const CASID &Result,128 bool /*CanBeDistributed*/) {129 DataT Expected(Result.getHash());130 const InMemoryCacheT::value_type &Cached = *Cache.insertLazy(131 Key, [&](auto ValueConstructor) { ValueConstructor.emplace(Expected); });132 133 const DataT &Observed = Cached.Data;134 if (Expected.getValue() == Observed.getValue())135 return Error::success();136 137 return createResultCachePoisonedError(Key, getContext(), Result,138 Observed.getValue());139}140 141namespace llvm::cas {142 143std::unique_ptr<ActionCache> createInMemoryActionCache() {144 return std::make_unique<InMemoryActionCache>();145}146 147} // namespace llvm::cas148 149OnDiskActionCache::OnDiskActionCache(150 std::unique_ptr<ondisk::OnDiskKeyValueDB> DB)151 : ActionCache(builtin::BuiltinCASContext::getDefaultContext()),152 DB(std::move(DB)) {}153 154Expected<std::unique_ptr<OnDiskActionCache>>155OnDiskActionCache::create(StringRef AbsPath) {156 std::unique_ptr<ondisk::OnDiskKeyValueDB> DB;157 if (Error E = ondisk::OnDiskKeyValueDB::open(AbsPath, getHashName(),158 sizeof(HashType), getHashName(),159 sizeof(DataT))160 .moveInto(DB))161 return std::move(E);162 return std::unique_ptr<OnDiskActionCache>(163 new OnDiskActionCache(std::move(DB)));164}165 166Expected<std::optional<CASID>>167OnDiskActionCache::getImpl(ArrayRef<uint8_t> Key,168 bool /*CanBeDistributed*/) const {169 std::optional<ArrayRef<char>> Val;170 if (Error E = DB->get(Key).moveInto(Val))171 return std::move(E);172 if (!Val)173 return std::nullopt;174 return CASID::create(&getContext(), toStringRef(*Val));175}176 177Error OnDiskActionCache::putImpl(ArrayRef<uint8_t> Key, const CASID &Result,178 bool /*CanBeDistributed*/) {179 auto ResultHash = Result.getHash();180 ArrayRef Expected((const char *)ResultHash.data(), ResultHash.size());181 ArrayRef<char> Observed;182 if (Error E = DB->put(Key, Expected).moveInto(Observed))183 return E;184 185 if (Expected == Observed)186 return Error::success();187 188 return createResultCachePoisonedError(189 Key, getContext(), Result,190 ArrayRef((const uint8_t *)Observed.data(), Observed.size()));191}192 193Error OnDiskActionCache::validate() const {194 // FIXME: without the matching CAS there is nothing we can check about the195 // cached values. The hash size is already validated by the DB validator.196 return DB->validate(nullptr);197}198 199UnifiedOnDiskActionCache::UnifiedOnDiskActionCache(200 std::shared_ptr<ondisk::UnifiedOnDiskCache> UniDB)201 : ActionCache(builtin::BuiltinCASContext::getDefaultContext()),202 UniDB(std::move(UniDB)) {}203 204Expected<std::optional<CASID>>205UnifiedOnDiskActionCache::getImpl(ArrayRef<uint8_t> Key,206 bool /*CanBeDistributed*/) const {207 std::optional<ArrayRef<char>> Val;208 if (Error E = UniDB->getKeyValueDB().get(Key).moveInto(Val))209 return std::move(E);210 if (!Val)211 return std::nullopt;212 auto ID = ondisk::UnifiedOnDiskCache::getObjectIDFromValue(*Val);213 return CASID::create(&getContext(),214 toStringRef(UniDB->getGraphDB().getDigest(ID)));215}216 217Error UnifiedOnDiskActionCache::putImpl(ArrayRef<uint8_t> Key,218 const CASID &Result,219 bool /*CanBeDistributed*/) {220 auto Expected = UniDB->getGraphDB().getReference(Result.getHash());221 if (LLVM_UNLIKELY(!Expected))222 return Expected.takeError();223 224 auto Value = ondisk::UnifiedOnDiskCache::getValueFromObjectID(*Expected);225 std::optional<ArrayRef<char>> Observed;226 if (Error E = UniDB->getKeyValueDB().put(Key, Value).moveInto(Observed))227 return E;228 229 auto ObservedID = ondisk::UnifiedOnDiskCache::getObjectIDFromValue(*Observed);230 if (*Expected == ObservedID)231 return Error::success();232 233 return createResultCachePoisonedError(234 Key, getContext(), Result, UniDB->getGraphDB().getDigest(ObservedID));235}236 237Error UnifiedOnDiskActionCache::validate() const {238 auto ValidateRef = [](FileOffset Offset, ArrayRef<char> Value) -> Error {239 auto ID = ondisk::UnifiedOnDiskCache::getObjectIDFromValue(Value);240 auto formatError = [&](Twine Msg) {241 return createStringError(242 llvm::errc::illegal_byte_sequence,243 "bad record at 0x" +244 utohexstr((unsigned)Offset.get(), /*LowerCase=*/true) + ": " +245 Msg.str());246 };247 if (ID.getOpaqueData() == 0)248 return formatError("zero is not a valid ref");249 return Error::success();250 };251 return UniDB->getKeyValueDB().validate(ValidateRef);252}253 254Expected<std::unique_ptr<ActionCache>>255cas::createOnDiskActionCache(StringRef Path) {256#if LLVM_ENABLE_ONDISK_CAS257 return OnDiskActionCache::create(Path);258#else259 return createStringError(inconvertibleErrorCode(), "OnDiskCache is disabled");260#endif261}262 263std::unique_ptr<ActionCache>264cas::builtin::createActionCacheFromUnifiedOnDiskCache(265 std::shared_ptr<ondisk::UnifiedOnDiskCache> UniDB) {266 return std::make_unique<UnifiedOnDiskActionCache>(std::move(UniDB));267}268