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-exception
6//
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/OnDiskCASLogger.h"
17#include "llvm/CAS/OnDiskKeyValueDB.h"
18#include "llvm/CAS/UnifiedOnDiskCache.h"
19#include "llvm/Config/llvm-config.h"
20#include "llvm/Support/BLAKE3.h"
21#include "llvm/Support/Errc.h"
22
23#define DEBUG_TYPE "cas-action-caches"
24
25using namespace llvm;
26using namespace llvm::cas;
27
28namespace {
29
30using HasherT = BLAKE3;
31using HashType = decltype(HasherT::hash(Data: std::declval<ArrayRef<uint8_t> &>()));
32
33template <size_t Size> class CacheEntry {
34public:
35 CacheEntry() = default;
36 CacheEntry(ArrayRef<uint8_t> Hash) { llvm::copy(Hash, Value.data()); }
37 CacheEntry(const CacheEntry &Entry) { llvm::copy(Entry.Value, Value.data()); }
38 ArrayRef<uint8_t> getValue() const { return Value; }
39
40private:
41 std::array<uint8_t, Size> Value;
42};
43
44/// Builtin InMemory ActionCache that stores the mapping in memory.
45class InMemoryActionCache final : public ActionCache {
46public:
47 InMemoryActionCache()
48 : ActionCache(builtin::BuiltinCASContext::getDefaultContext()) {}
49
50 Error putImpl(ArrayRef<uint8_t> ActionKey, const CASID &Result,
51 bool CanBeDistributed) final;
52 Expected<std::optional<CASID>> getImpl(ArrayRef<uint8_t> ActionKey,
53 bool CanBeDistributed) const final;
54
55 Error validate() const final {
56 return createStringError(Fmt: "InMemoryActionCache doesn't support validate()");
57 }
58
59private:
60 using DataT = CacheEntry<sizeof(HashType)>;
61 using InMemoryCacheT = ThreadSafeTrieRawHashMap<DataT, sizeof(HashType)>;
62
63 InMemoryCacheT Cache;
64};
65
66/// Builtin basic OnDiskActionCache that uses one underlying OnDiskKeyValueDB.
67class OnDiskActionCache final : public ActionCache {
68public:
69 Error putImpl(ArrayRef<uint8_t> ActionKey, const CASID &Result,
70 bool CanBeDistributed) final;
71 Expected<std::optional<CASID>> getImpl(ArrayRef<uint8_t> ActionKey,
72 bool CanBeDistributed) const final;
73
74 static Expected<std::unique_ptr<OnDiskActionCache>> create(StringRef Path);
75
76 Error validate() const final;
77
78private:
79 static StringRef getHashName() { return "BLAKE3"; }
80
81 OnDiskActionCache(std::unique_ptr<ondisk::OnDiskKeyValueDB> DB);
82
83 std::unique_ptr<ondisk::OnDiskKeyValueDB> DB;
84 using DataT = CacheEntry<sizeof(HashType)>;
85};
86
87/// Builtin unified ActionCache that wraps around UnifiedOnDiskCache to provide
88/// access to its ActionCache.
89class UnifiedOnDiskActionCache final : public ActionCache {
90public:
91 Error putImpl(ArrayRef<uint8_t> ActionKey, const CASID &Result,
92 bool CanBeDistributed) final;
93 Expected<std::optional<CASID>> getImpl(ArrayRef<uint8_t> ActionKey,
94 bool CanBeDistributed) const final;
95
96 UnifiedOnDiskActionCache(std::shared_ptr<ondisk::UnifiedOnDiskCache> UniDB);
97
98 Error validate() const final;
99
100private:
101 std::shared_ptr<ondisk::UnifiedOnDiskCache> UniDB;
102};
103} // end namespace
104
105static Error createResultCachePoisonedError(ArrayRef<uint8_t> KeyHash,
106 const CASContext &Context,
107 CASID Output,
108 ArrayRef<uint8_t> ExistingOutput) {
109 std::string Existing =
110 CASID::create(Context: &Context, Hash: toStringRef(Input: ExistingOutput)).toString();
111 SmallString<64> Key;
112 toHex(Input: KeyHash, /*LowerCase=*/true, Output&: Key);
113 return createStringError(EC: std::make_error_code(e: std::errc::invalid_argument),
114 S: "cache poisoned for '" + Key + "' (new='" +
115 Output.toString() + "' vs. existing '" +
116 Existing + "')");
117}
118
119Expected<std::optional<CASID>>
120InMemoryActionCache::getImpl(ArrayRef<uint8_t> Key,
121 bool /*CanBeDistributed*/) const {
122 auto Result = Cache.find(Hash: Key);
123 if (!Result)
124 return std::nullopt;
125 return CASID::create(Context: &getContext(), Hash: toStringRef(Input: Result->Data.getValue()));
126}
127
128Error InMemoryActionCache::putImpl(ArrayRef<uint8_t> Key, const CASID &Result,
129 bool /*CanBeDistributed*/) {
130 DataT Expected(Result.getHash());
131 const InMemoryCacheT::value_type &Cached = *Cache.insertLazy(
132 Hash: Key, OnConstruct: [&](auto ValueConstructor) { ValueConstructor.emplace(Expected); });
133
134 const DataT &Observed = Cached.Data;
135 if (Expected.getValue() == Observed.getValue())
136 return Error::success();
137
138 return createResultCachePoisonedError(KeyHash: Key, Context: getContext(), Output: Result,
139 ExistingOutput: Observed.getValue());
140}
141
142namespace llvm::cas {
143
144std::unique_ptr<ActionCache> createInMemoryActionCache() {
145 return std::make_unique<InMemoryActionCache>();
146}
147
148} // namespace llvm::cas
149
150OnDiskActionCache::OnDiskActionCache(
151 std::unique_ptr<ondisk::OnDiskKeyValueDB> DB)
152 : ActionCache(builtin::BuiltinCASContext::getDefaultContext()),
153 DB(std::move(DB)) {}
154
155Expected<std::unique_ptr<OnDiskActionCache>>
156OnDiskActionCache::create(StringRef AbsPath) {
157 std::shared_ptr<ondisk::OnDiskCASLogger> Logger;
158#ifndef _WIN32
159 if (Error E =
160 ondisk::OnDiskCASLogger::openIfEnabled(Path: AbsPath).moveInto(Value&: Logger))
161 return std::move(E);
162#endif
163 std::unique_ptr<ondisk::OnDiskKeyValueDB> DB;
164 if (Error E = ondisk::OnDiskKeyValueDB::open(
165 Path: AbsPath, HashName: getHashName(), KeySize: sizeof(HashType), ValueName: getHashName(),
166 ValueSize: sizeof(DataT), /*UnifiedCache=*/nullptr, Logger: std::move(Logger))
167 .moveInto(Value&: DB))
168 return std::move(E);
169 return std::unique_ptr<OnDiskActionCache>(
170 new OnDiskActionCache(std::move(DB)));
171}
172
173Expected<std::optional<CASID>>
174OnDiskActionCache::getImpl(ArrayRef<uint8_t> Key,
175 bool /*CanBeDistributed*/) const {
176 std::optional<ArrayRef<char>> Val;
177 if (Error E = DB->get(Key).moveInto(Value&: Val))
178 return std::move(E);
179 if (!Val)
180 return std::nullopt;
181 return CASID::create(Context: &getContext(), Hash: toStringRef(Input: *Val));
182}
183
184Error OnDiskActionCache::putImpl(ArrayRef<uint8_t> Key, const CASID &Result,
185 bool /*CanBeDistributed*/) {
186 auto ResultHash = Result.getHash();
187 ArrayRef Expected((const char *)ResultHash.data(), ResultHash.size());
188 ArrayRef<char> Observed;
189 if (Error E = DB->put(Key, Value: Expected).moveInto(Value&: Observed))
190 return E;
191
192 if (Expected == Observed)
193 return Error::success();
194
195 return createResultCachePoisonedError(
196 KeyHash: Key, Context: getContext(), Output: Result,
197 ExistingOutput: ArrayRef((const uint8_t *)Observed.data(), Observed.size()));
198}
199
200Error OnDiskActionCache::validate() const {
201 // FIXME: without the matching CAS there is nothing we can check about the
202 // cached values. The hash size is already validated by the DB validator.
203 return DB->validate(CheckValue: nullptr);
204}
205
206UnifiedOnDiskActionCache::UnifiedOnDiskActionCache(
207 std::shared_ptr<ondisk::UnifiedOnDiskCache> UniDB)
208 : ActionCache(builtin::BuiltinCASContext::getDefaultContext()),
209 UniDB(std::move(UniDB)) {}
210
211Expected<std::optional<CASID>>
212UnifiedOnDiskActionCache::getImpl(ArrayRef<uint8_t> Key,
213 bool /*CanBeDistributed*/) const {
214 std::optional<ArrayRef<char>> Val;
215 if (Error E = UniDB->getKeyValueDB().get(Key).moveInto(Value&: Val))
216 return std::move(E);
217 if (!Val)
218 return std::nullopt;
219 auto ID = ondisk::UnifiedOnDiskCache::getObjectIDFromValue(Value: *Val);
220 return CASID::create(Context: &getContext(),
221 Hash: toStringRef(Input: UniDB->getGraphDB().getDigest(Ref: ID)));
222}
223
224Error UnifiedOnDiskActionCache::putImpl(ArrayRef<uint8_t> Key,
225 const CASID &Result,
226 bool /*CanBeDistributed*/) {
227 auto Expected = UniDB->getGraphDB().getReference(Hash: Result.getHash());
228 if (LLVM_UNLIKELY(!Expected))
229 return Expected.takeError();
230
231 auto Value = ondisk::UnifiedOnDiskCache::getValueFromObjectID(ID: *Expected);
232 std::optional<ArrayRef<char>> Observed;
233 if (Error E = UniDB->getKeyValueDB().put(Key, Value).moveInto(Value&: Observed))
234 return E;
235
236 auto ObservedID = ondisk::UnifiedOnDiskCache::getObjectIDFromValue(Value: *Observed);
237 if (*Expected == ObservedID)
238 return Error::success();
239
240 return createResultCachePoisonedError(
241 KeyHash: Key, Context: getContext(), Output: Result, ExistingOutput: UniDB->getGraphDB().getDigest(Ref: ObservedID));
242}
243
244Error UnifiedOnDiskActionCache::validate() const {
245 return UniDB->validateActionCache();
246}
247
248Expected<std::unique_ptr<ActionCache>>
249cas::createOnDiskActionCache(StringRef Path) {
250#if LLVM_ENABLE_ONDISK_CAS
251 return OnDiskActionCache::create(AbsPath: Path);
252#else
253 return createStringError(inconvertibleErrorCode(), "OnDiskCache is disabled");
254#endif
255}
256
257std::unique_ptr<ActionCache>
258cas::builtin::createActionCacheFromUnifiedOnDiskCache(
259 std::shared_ptr<ondisk::UnifiedOnDiskCache> UniDB) {
260 return std::make_unique<UnifiedOnDiskActionCache>(args: std::move(UniDB));
261}
262