//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// /// /// \file This file implements the underlying ActionCache implementations. /// //===----------------------------------------------------------------------===// #include "BuiltinCAS.h" #include "llvm/ADT/TrieRawHashMap.h" #include "llvm/CAS/ActionCache.h" #include "llvm/CAS/OnDiskKeyValueDB.h" #include "llvm/CAS/UnifiedOnDiskCache.h" #include "llvm/Config/llvm-config.h" #include "llvm/Support/BLAKE3.h" #include "llvm/Support/Errc.h" #define DEBUG_TYPE "cas-action-caches" using namespace llvm; using namespace llvm::cas; namespace { using HasherT = BLAKE3; using HashType = decltype(HasherT::hash(std::declval &>())); template class CacheEntry { public: CacheEntry() = default; CacheEntry(ArrayRef Hash) { llvm::copy(Hash, Value.data()); } CacheEntry(const CacheEntry &Entry) { llvm::copy(Entry.Value, Value.data()); } ArrayRef getValue() const { return Value; } private: std::array Value; }; /// Builtin InMemory ActionCache that stores the mapping in memory. class InMemoryActionCache final : public ActionCache { public: InMemoryActionCache() : ActionCache(builtin::BuiltinCASContext::getDefaultContext()) {} Error putImpl(ArrayRef ActionKey, const CASID &Result, bool CanBeDistributed) final; Expected> getImpl(ArrayRef ActionKey, bool CanBeDistributed) const final; Error validate() const final { return createStringError("InMemoryActionCache doesn't support validate()"); } private: using DataT = CacheEntry; using InMemoryCacheT = ThreadSafeTrieRawHashMap; InMemoryCacheT Cache; }; /// Builtin basic OnDiskActionCache that uses one underlying OnDiskKeyValueDB. class OnDiskActionCache final : public ActionCache { public: Error putImpl(ArrayRef ActionKey, const CASID &Result, bool CanBeDistributed) final; Expected> getImpl(ArrayRef ActionKey, bool CanBeDistributed) const final; static Expected> create(StringRef Path); Error validate() const final; private: static StringRef getHashName() { return "BLAKE3"; } OnDiskActionCache(std::unique_ptr DB); std::unique_ptr DB; using DataT = CacheEntry; }; /// Builtin unified ActionCache that wraps around UnifiedOnDiskCache to provide /// access to its ActionCache. class UnifiedOnDiskActionCache final : public ActionCache { public: Error putImpl(ArrayRef ActionKey, const CASID &Result, bool CanBeDistributed) final; Expected> getImpl(ArrayRef ActionKey, bool CanBeDistributed) const final; UnifiedOnDiskActionCache(std::shared_ptr UniDB); Error validate() const final; private: std::shared_ptr UniDB; }; } // end namespace static Error createResultCachePoisonedError(ArrayRef KeyHash, const CASContext &Context, CASID Output, ArrayRef ExistingOutput) { std::string Existing = CASID::create(&Context, toStringRef(ExistingOutput)).toString(); SmallString<64> Key; toHex(KeyHash, /*LowerCase=*/true, Key); return createStringError(std::make_error_code(std::errc::invalid_argument), "cache poisoned for '" + Key + "' (new='" + Output.toString() + "' vs. existing '" + Existing + "')"); } Expected> InMemoryActionCache::getImpl(ArrayRef Key, bool /*CanBeDistributed*/) const { auto Result = Cache.find(Key); if (!Result) return std::nullopt; return CASID::create(&getContext(), toStringRef(Result->Data.getValue())); } Error InMemoryActionCache::putImpl(ArrayRef Key, const CASID &Result, bool /*CanBeDistributed*/) { DataT Expected(Result.getHash()); const InMemoryCacheT::value_type &Cached = *Cache.insertLazy( Key, [&](auto ValueConstructor) { ValueConstructor.emplace(Expected); }); const DataT &Observed = Cached.Data; if (Expected.getValue() == Observed.getValue()) return Error::success(); return createResultCachePoisonedError(Key, getContext(), Result, Observed.getValue()); } namespace llvm::cas { std::unique_ptr createInMemoryActionCache() { return std::make_unique(); } } // namespace llvm::cas OnDiskActionCache::OnDiskActionCache( std::unique_ptr DB) : ActionCache(builtin::BuiltinCASContext::getDefaultContext()), DB(std::move(DB)) {} Expected> OnDiskActionCache::create(StringRef AbsPath) { std::unique_ptr DB; if (Error E = ondisk::OnDiskKeyValueDB::open(AbsPath, getHashName(), sizeof(HashType), getHashName(), sizeof(DataT)) .moveInto(DB)) return std::move(E); return std::unique_ptr( new OnDiskActionCache(std::move(DB))); } Expected> OnDiskActionCache::getImpl(ArrayRef Key, bool /*CanBeDistributed*/) const { std::optional> Val; if (Error E = DB->get(Key).moveInto(Val)) return std::move(E); if (!Val) return std::nullopt; return CASID::create(&getContext(), toStringRef(*Val)); } Error OnDiskActionCache::putImpl(ArrayRef Key, const CASID &Result, bool /*CanBeDistributed*/) { auto ResultHash = Result.getHash(); ArrayRef Expected((const char *)ResultHash.data(), ResultHash.size()); ArrayRef Observed; if (Error E = DB->put(Key, Expected).moveInto(Observed)) return E; if (Expected == Observed) return Error::success(); return createResultCachePoisonedError( Key, getContext(), Result, ArrayRef((const uint8_t *)Observed.data(), Observed.size())); } Error OnDiskActionCache::validate() const { // FIXME: without the matching CAS there is nothing we can check about the // cached values. The hash size is already validated by the DB validator. return DB->validate(nullptr); } UnifiedOnDiskActionCache::UnifiedOnDiskActionCache( std::shared_ptr UniDB) : ActionCache(builtin::BuiltinCASContext::getDefaultContext()), UniDB(std::move(UniDB)) {} Expected> UnifiedOnDiskActionCache::getImpl(ArrayRef Key, bool /*CanBeDistributed*/) const { std::optional> Val; if (Error E = UniDB->getKeyValueDB().get(Key).moveInto(Val)) return std::move(E); if (!Val) return std::nullopt; auto ID = ondisk::UnifiedOnDiskCache::getObjectIDFromValue(*Val); return CASID::create(&getContext(), toStringRef(UniDB->getGraphDB().getDigest(ID))); } Error UnifiedOnDiskActionCache::putImpl(ArrayRef Key, const CASID &Result, bool /*CanBeDistributed*/) { auto Expected = UniDB->getGraphDB().getReference(Result.getHash()); if (LLVM_UNLIKELY(!Expected)) return Expected.takeError(); auto Value = ondisk::UnifiedOnDiskCache::getValueFromObjectID(*Expected); std::optional> Observed; if (Error E = UniDB->getKeyValueDB().put(Key, Value).moveInto(Observed)) return E; auto ObservedID = ondisk::UnifiedOnDiskCache::getObjectIDFromValue(*Observed); if (*Expected == ObservedID) return Error::success(); return createResultCachePoisonedError( Key, getContext(), Result, UniDB->getGraphDB().getDigest(ObservedID)); } Error UnifiedOnDiskActionCache::validate() const { auto ValidateRef = [this](FileOffset Offset, ArrayRef Value) -> Error { auto ID = ondisk::UnifiedOnDiskCache::getObjectIDFromValue(Value); auto formatError = [&](Twine Msg) { return createStringError( llvm::errc::illegal_byte_sequence, "bad record at 0x" + utohexstr((unsigned)Offset.get(), /*LowerCase=*/true) + " ref=0x" + utohexstr(ID.getOpaqueData(), /*LowerCase=*/true) + ": " + Msg.str()); }; if (ID.getOpaqueData() == 0) return formatError("zero is not a valid ref"); // Check containsObject first, because other API assumes a valid ObjectID. if (!UniDB->getGraphDB().containsObject(ID, /*CheckUpstream=*/false)) return formatError("ref is not in cas index"); auto Hash = UniDB->getGraphDB().getDigest(ID); auto Ref = UniDB->getGraphDB().getExistingReference(Hash, /*CheckUpstream=*/false); assert(Ref && "missing object passed containsObject check?"); if (!Ref) return formatError("ref is not in cas index after contains"); if (*Ref != ID) return formatError("ref does not match indexed offset " + utohexstr(Ref->getOpaqueData(), /*LowerCase=*/true) + " for hash " + toHex(Hash)); return Error::success(); }; return UniDB->getKeyValueDB().validate(ValidateRef); } Expected> cas::createOnDiskActionCache(StringRef Path) { #if LLVM_ENABLE_ONDISK_CAS return OnDiskActionCache::create(Path); #else return createStringError(inconvertibleErrorCode(), "OnDiskCache is disabled"); #endif } std::unique_ptr cas::builtin::createActionCacheFromUnifiedOnDiskCache( std::shared_ptr UniDB) { return std::make_unique(std::move(UniDB)); }