Skip to content

Commit deab049

Browse files
[CAS] Add ActionCache to LLVMCAS Library (#114097)
ActionCache is used to store a mapping from CASID to CASID. The current implementation of the ActionCache can only be used to associate the key/value from the same hash context. ActionCache has two operations: `put` to store the key/value and `get` to lookup the key/value mapping. ActionCache uses the same TrieRawHashMap data structure to store the mapping, where is CASID of the key is the hash to index the map. While CASIDs for key/value are often associcate with actual CAS ObjectStore, it doesn't provide the guarantee of the existence of such object in any ObjectStore.
1 parent 0db57ab commit deab049

File tree

9 files changed

+322
-3
lines changed

9 files changed

+322
-3
lines changed

llvm/docs/ContentAddressableStorage.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,19 @@ Expected<ObjectProxy> Loaded = ObjectStore.getProxy(C);
8383
It also provides APIs to convert between `ObjectRef`, `ObjectProxy` and
8484
`CASID`.
8585

86+
### ActionCache
87+
88+
`ActionCache` is a key value storage can be used to associate two CASIDs. It is
89+
usually used with an `ObjectStore` to map an input CASObject to an output CASObject
90+
with their CASIDs.
91+
92+
`ActionCache` has APIs like following:
93+
94+
```
95+
CASID A, B;
96+
Error E = ActionCache.put(A, B);
97+
Expected<std::optional<CASID>> Result = ActionCache.get(A);
98+
```
8699

87100

88101
## CAS Library Implementation Guide
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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
10+
/// This file contains the declaration of the ActionCache class, which is the
11+
/// base class for ActionCache implementations.
12+
///
13+
//===----------------------------------------------------------------------===//
14+
15+
#ifndef LLVM_CAS_ACTIONCACHE_H
16+
#define LLVM_CAS_ACTIONCACHE_H
17+
18+
#include "llvm/ADT/StringRef.h"
19+
#include "llvm/CAS/CASID.h"
20+
#include "llvm/CAS/CASReference.h"
21+
#include "llvm/Support/Error.h"
22+
23+
namespace llvm::cas {
24+
25+
class ObjectStore;
26+
class CASID;
27+
class ObjectProxy;
28+
29+
/// A key for caching an operation.
30+
/// It is implemented as a bag of bytes and provides a convenient constructor
31+
/// for CAS types.
32+
class CacheKey {
33+
public:
34+
StringRef getKey() const { return Key; }
35+
36+
CacheKey(const CASID &ID);
37+
CacheKey(const ObjectProxy &Proxy);
38+
CacheKey(const ObjectStore &CAS, const ObjectRef &Ref);
39+
40+
private:
41+
std::string Key;
42+
};
43+
44+
/// A cache from a key (that describes an action) to the result of performing
45+
/// that action.
46+
///
47+
/// Actions are expected to be pure. Storing mappings from one action to
48+
/// multiple results will result in error (cache poisoning).
49+
class ActionCache {
50+
virtual void anchor();
51+
52+
public:
53+
/// Get a previously computed result for \p ActionKey.
54+
///
55+
/// \param CanBeDistributed is a hint to the underlying implementation that if
56+
/// it is true, the lookup is profitable to be done on a distributed caching
57+
/// level, not just locally. The implementation is free to ignore this flag.
58+
Expected<std::optional<CASID>> get(const CacheKey &ActionKey,
59+
bool CanBeDistributed = false) const {
60+
return getImpl(arrayRefFromStringRef(ActionKey.getKey()), CanBeDistributed);
61+
}
62+
63+
/// Cache \p Result for the \p ActionKey computation.
64+
///
65+
/// \param CanBeDistributed is a hint to the underlying implementation that if
66+
/// it is true, the association is profitable to be done on a distributed
67+
/// caching level, not just locally. The implementation is free to ignore this
68+
/// flag.
69+
Error put(const CacheKey &ActionKey, const CASID &Result,
70+
bool CanBeDistributed = false) {
71+
assert(Result.getContext().getHashSchemaIdentifier() ==
72+
getContext().getHashSchemaIdentifier() &&
73+
"Hash schema mismatch");
74+
return putImpl(arrayRefFromStringRef(ActionKey.getKey()), Result,
75+
CanBeDistributed);
76+
}
77+
78+
virtual ~ActionCache() = default;
79+
80+
protected:
81+
// Implementation detail for \p get method.
82+
virtual Expected<std::optional<CASID>>
83+
getImpl(ArrayRef<uint8_t> ResolvedKey, bool CanBeDistributed) const = 0;
84+
85+
// Implementation details for \p put method.
86+
virtual Error putImpl(ArrayRef<uint8_t> ResolvedKey, const CASID &Result,
87+
bool CanBeDistributed) = 0;
88+
89+
ActionCache(const CASContext &Context) : Context(Context) {}
90+
91+
const CASContext &getContext() const { return Context; }
92+
93+
private:
94+
const CASContext &Context;
95+
};
96+
97+
/// Create an action cache in memory.
98+
std::unique_ptr<ActionCache> createInMemoryActionCache();
99+
100+
} // end namespace llvm::cas
101+
102+
#endif // LLVM_CAS_ACTIONCACHE_H

llvm/lib/CAS/ActionCache.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
#include "llvm/CAS/ActionCache.h"
10+
#include "llvm/CAS/CASID.h"
11+
#include "llvm/CAS/ObjectStore.h"
12+
13+
using namespace llvm;
14+
using namespace llvm::cas;
15+
16+
void ActionCache::anchor() {}
17+
18+
CacheKey::CacheKey(const CASID &ID) : Key(toStringRef(ID.getHash()).str()) {}
19+
CacheKey::CacheKey(const ObjectProxy &Proxy)
20+
: CacheKey(Proxy.getCAS(), Proxy.getRef()) {}
21+
CacheKey::CacheKey(const ObjectStore &CAS, const ObjectRef &Ref)
22+
: Key(toStringRef(CAS.getID(Ref).getHash())) {}

llvm/lib/CAS/ActionCaches.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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/Support/BLAKE3.h"
17+
18+
#define DEBUG_TYPE "cas-action-caches"
19+
20+
using namespace llvm;
21+
using namespace llvm::cas;
22+
23+
namespace {
24+
25+
using HasherT = BLAKE3;
26+
using HashType = decltype(HasherT::hash(std::declval<ArrayRef<uint8_t> &>()));
27+
28+
template <size_t Size> class CacheEntry {
29+
public:
30+
CacheEntry() = default;
31+
CacheEntry(ArrayRef<uint8_t> Hash) { llvm::copy(Hash, Value.data()); }
32+
CacheEntry(const CacheEntry &Entry) { llvm::copy(Entry.Value, Value.data()); }
33+
ArrayRef<uint8_t> getValue() const { return Value; }
34+
35+
private:
36+
std::array<uint8_t, Size> Value;
37+
};
38+
39+
/// Builtin InMemory ActionCache that stores the mapping in memory.
40+
class InMemoryActionCache final : public ActionCache {
41+
public:
42+
InMemoryActionCache()
43+
: ActionCache(builtin::BuiltinCASContext::getDefaultContext()) {}
44+
45+
Error putImpl(ArrayRef<uint8_t> ActionKey, const CASID &Result,
46+
bool CanBeDistributed) final;
47+
Expected<std::optional<CASID>> getImpl(ArrayRef<uint8_t> ActionKey,
48+
bool CanBeDistributed) const final;
49+
50+
private:
51+
using DataT = CacheEntry<sizeof(HashType)>;
52+
using InMemoryCacheT = ThreadSafeTrieRawHashMap<DataT, sizeof(HashType)>;
53+
54+
InMemoryCacheT Cache;
55+
};
56+
} // end namespace
57+
58+
static Error createResultCachePoisonedError(ArrayRef<uint8_t> KeyHash,
59+
const CASContext &Context,
60+
CASID Output,
61+
ArrayRef<uint8_t> ExistingOutput) {
62+
std::string Existing =
63+
CASID::create(&Context, toStringRef(ExistingOutput)).toString();
64+
SmallString<64> Key;
65+
toHex(KeyHash, /*LowerCase=*/true, Key);
66+
return createStringError(std::make_error_code(std::errc::invalid_argument),
67+
"cache poisoned for '" + Key + "' (new='" +
68+
Output.toString() + "' vs. existing '" +
69+
Existing + "')");
70+
}
71+
72+
Expected<std::optional<CASID>>
73+
InMemoryActionCache::getImpl(ArrayRef<uint8_t> Key,
74+
bool /*CanBeDistributed*/) const {
75+
auto Result = Cache.find(Key);
76+
if (!Result)
77+
return std::nullopt;
78+
return CASID::create(&getContext(), toStringRef(Result->Data.getValue()));
79+
}
80+
81+
Error InMemoryActionCache::putImpl(ArrayRef<uint8_t> Key, const CASID &Result,
82+
bool /*CanBeDistributed*/) {
83+
DataT Expected(Result.getHash());
84+
const InMemoryCacheT::value_type &Cached = *Cache.insertLazy(
85+
Key, [&](auto ValueConstructor) { ValueConstructor.emplace(Expected); });
86+
87+
const DataT &Observed = Cached.Data;
88+
if (Expected.getValue() == Observed.getValue())
89+
return Error::success();
90+
91+
return createResultCachePoisonedError(Key, getContext(), Result,
92+
Observed.getValue());
93+
}
94+
95+
namespace llvm::cas {
96+
97+
std::unique_ptr<ActionCache> createInMemoryActionCache() {
98+
return std::make_unique<InMemoryActionCache>();
99+
}
100+
101+
} // namespace llvm::cas

llvm/lib/CAS/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
add_llvm_component_library(LLVMCAS
2+
ActionCache.cpp
3+
ActionCaches.cpp
24
BuiltinCAS.cpp
35
InMemoryCAS.cpp
46
ObjectStore.cpp
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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
10+
/// This file implements the tests for ActionCaches.
11+
///
12+
//===----------------------------------------------------------------------===//
13+
14+
#include "llvm/CAS/ActionCache.h"
15+
#include "CASTestConfig.h"
16+
#include "llvm/CAS/ObjectStore.h"
17+
#include "llvm/Testing/Support/Error.h"
18+
#include "gtest/gtest.h"
19+
20+
using namespace llvm;
21+
using namespace llvm::cas;
22+
23+
TEST_P(CASTest, ActionCacheHit) {
24+
std::shared_ptr<ObjectStore> CAS = createObjectStore();
25+
std::unique_ptr<ActionCache> Cache = createActionCache();
26+
27+
std::optional<ObjectProxy> ID;
28+
ASSERT_THAT_ERROR(CAS->createProxy({}, "1").moveInto(ID), Succeeded());
29+
std::optional<CASID> ResultID;
30+
ASSERT_THAT_ERROR(Cache->put(*ID, *ID), Succeeded());
31+
ASSERT_THAT_ERROR(Cache->get(*ID).moveInto(ResultID), Succeeded());
32+
ASSERT_TRUE(ResultID);
33+
std::optional<ObjectRef> Result = CAS->getReference(*ResultID);
34+
ASSERT_TRUE(Result);
35+
ASSERT_EQ(*ID, *Result);
36+
}
37+
38+
TEST_P(CASTest, ActionCacheMiss) {
39+
std::shared_ptr<ObjectStore> CAS = createObjectStore();
40+
std::unique_ptr<ActionCache> Cache = createActionCache();
41+
42+
std::optional<ObjectProxy> ID1, ID2;
43+
ASSERT_THAT_ERROR(CAS->createProxy({}, "1").moveInto(ID1), Succeeded());
44+
ASSERT_THAT_ERROR(CAS->createProxy({}, "2").moveInto(ID2), Succeeded());
45+
ASSERT_THAT_ERROR(Cache->put(*ID1, *ID2), Succeeded());
46+
// This is a cache miss for looking up a key doesn't exist.
47+
std::optional<CASID> Result1;
48+
ASSERT_THAT_ERROR(Cache->get(*ID2).moveInto(Result1), Succeeded());
49+
ASSERT_FALSE(Result1);
50+
51+
ASSERT_THAT_ERROR(Cache->put(*ID2, *ID1), Succeeded());
52+
// Cache hit after adding the value.
53+
std::optional<CASID> Result2;
54+
ASSERT_THAT_ERROR(Cache->get(*ID2).moveInto(Result2), Succeeded());
55+
ASSERT_TRUE(Result2);
56+
std::optional<ObjectRef> Ref = CAS->getReference(*Result2);
57+
ASSERT_TRUE(Ref);
58+
ASSERT_EQ(*ID1, *Ref);
59+
}
60+
61+
TEST_P(CASTest, ActionCacheRewrite) {
62+
std::shared_ptr<ObjectStore> CAS = createObjectStore();
63+
std::unique_ptr<ActionCache> Cache = createActionCache();
64+
65+
std::optional<ObjectProxy> ID1, ID2;
66+
ASSERT_THAT_ERROR(CAS->createProxy({}, "1").moveInto(ID1), Succeeded());
67+
ASSERT_THAT_ERROR(CAS->createProxy({}, "2").moveInto(ID2), Succeeded());
68+
ASSERT_THAT_ERROR(Cache->put(*ID1, *ID1), Succeeded());
69+
// Writing to the same key with different value is error.
70+
ASSERT_THAT_ERROR(Cache->put(*ID1, *ID2), Failed());
71+
// Writing the same value multiple times to the same key is fine.
72+
ASSERT_THAT_ERROR(Cache->put(*ID1, *ID1), Succeeded());
73+
}

llvm/unittests/CAS/CASTestConfig.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313
using namespace llvm;
1414
using namespace llvm::cas;
1515

16-
CASTestingEnv createInMemory(int I) {
17-
std::unique_ptr<ObjectStore> CAS = createInMemoryCAS();
18-
return CASTestingEnv{std::move(CAS)};
16+
static CASTestingEnv createInMemory(int I) {
17+
return CASTestingEnv{createInMemoryCAS(), createInMemoryActionCache()};
1918
}
2019

2120
INSTANTIATE_TEST_SUITE_P(InMemoryCAS, CASTest,

llvm/unittests/CAS/CASTestConfig.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9+
#include "llvm/CAS/ActionCache.h"
910
#include "llvm/CAS/ObjectStore.h"
1011
#include "gtest/gtest.h"
1112

@@ -14,6 +15,7 @@
1415

1516
struct CASTestingEnv {
1617
std::unique_ptr<llvm::cas::ObjectStore> CAS;
18+
std::unique_ptr<llvm::cas::ActionCache> Cache;
1719
};
1820

1921
class CASTest
@@ -25,6 +27,10 @@ class CASTest
2527
auto TD = GetParam()(++(*NextCASIndex));
2628
return std::move(TD.CAS);
2729
}
30+
std::unique_ptr<llvm::cas::ActionCache> createActionCache() {
31+
auto TD = GetParam()(++(*NextCASIndex));
32+
return std::move(TD.Cache);
33+
}
2834
void SetUp() { NextCASIndex = 0; }
2935
void TearDown() { NextCASIndex = std::nullopt; }
3036
};

llvm/unittests/CAS/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS
55
)
66

77
add_llvm_unittest(CASTests
8+
ActionCacheTest.cpp
89
CASTestConfig.cpp
910
ObjectStoreTest.cpp
1011
)

0 commit comments

Comments
 (0)