Skip to content

Commit e753652

Browse files
authored
[COMGR][Cache] No-Source Code Cache implementation (llvm#522)
This commit implements a compilation cache for `clang::driver::Command`. This is a reduced version, that doesn't handle source-code as input, only the late stages are cached (aiming to ship this quickly with less verification and negotiations with other teams). Highlights: * This cache can be enabled by setting the environment variable AMD_COMGR_CACHE=1 * By default, on linux, the cache is written to $HOME/.cache/comgr. * The cache takes into account llvm's version and device-libs bitcode: after a change in llvm's HEAD commit new compilations won't hit the cache. * Cache max size is 5gb and leaving more than 75% of the system's available space co-authored by anjenner and jmmartinez
2 parents 207155d + fb27f2c commit e753652

33 files changed

+1225
-24
lines changed

amd/comgr/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ option(COMGR_BUILD_SHARED_LIBS "Build the shared library"
6969
${build_shared_libs_default})
7070

7171
set(SOURCES
72+
src/comgr-cache.cpp
73+
src/comgr-cache-command.cpp
74+
src/comgr-clang-command.cpp
7275
src/comgr-compiler.cpp
7376
src/comgr.cpp
7477
src/comgr-device-libs.cpp

amd/comgr/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,26 @@ Comgr supports an environment variable to help locate LLVM:
114114
installation, which is currently used to locate the clang resource directory
115115
and clang binary path, allowing for additional optimizations.
116116

117+
Comgr utilizes a cache to preserve the results of compilations between executions.
118+
The cache's status (enabled/disabled), storage location for its results,
119+
and eviction policy can be manipulated through specific environment variables.
120+
If an issue arises during cache initialization, the execution will proceed with
121+
the cache turned off.
122+
123+
By default, the cache is turned off, set the environment variable
124+
`AMD_COMGR_CACHE=1` to enable it.
125+
126+
* `AMD_COMGR_CACHE`: When unset or set to 0, the cache is turned off.
127+
* `AMD_COMGR_CACHE_DIR`: If assigned a non-empty value, that value is used as
128+
the path for cache storage. If the variable is unset or set to an empty string `""`,
129+
it is directed to "$XDG_CACHE_HOME/comgr" (which defaults to
130+
"$USER/.cache/comgr" on Linux, and "%LOCALAPPDATA%\cache\comgr"
131+
on Microsoft Windows).
132+
* `AMD_COMGR_CACHE_POLICY`: If assigned a value, the string is interpreted and
133+
applied to the cache pruning policy. The cache is pruned only upon program
134+
termination. The string format aligns with [Clang's ThinLTO cache pruning policy](https://clang.llvm.org/docs/ThinLTO.html#cache-pruning).
135+
The default policy is set as: "prune_interval=1h:prune_expiration=0h:cache_size=75%:cache_size_bytes=30g:cache_size_files=0".
136+
117137
Comgr also supports some environment variables to aid in debugging. These
118138
include:
119139

amd/comgr/cmake/DeviceLibs.cmake

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ foreach(AMDGCN_LIB_TARGET ${AMD_DEVICE_LIBS_TARGETS})
5959
add_dependencies(amd_comgr ${AMDGCN_LIB_TARGET}_header)
6060

6161
list(APPEND TARGETS_INCLUDES "#include \"${header}\"")
62+
list(APPEND TARGETS_HEADERS "${INC_DIR}/${header}")
6263
endforeach()
6364

6465
list(JOIN TARGETS_INCLUDES "\n" TARGETS_INCLUDES)
@@ -110,4 +111,17 @@ list(APPEND TARGETS_DEFS "#undef AMD_DEVICE_LIBS_FUNCTION")
110111
list(JOIN TARGETS_DEFS "\n" TARGETS_DEFS)
111112
file(GENERATE OUTPUT ${GEN_LIBRARY_DEFS_INC_FILE} CONTENT "${TARGETS_DEFS}")
112113

114+
# compute the sha256 of the device libraries to detect changes and pass them to comgr (used by the cache)
115+
find_package(Python3 REQUIRED Interpreter)
116+
set(DEVICE_LIBS_ID_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/device-libs-id.py")
117+
set(DEVICE_LIBS_ID_HEADER ${INC_DIR}/libraries_sha.inc)
118+
add_custom_command(OUTPUT ${DEVICE_LIBS_ID_HEADER}
119+
COMMAND ${Python3_EXECUTABLE} ${DEVICE_LIBS_ID_SCRIPT} --varname DEVICE_LIBS_ID --output ${DEVICE_LIBS_ID_HEADER} ${TARGETS_HEADERS}
120+
DEPENDS ${DEVICE_LIBS_ID_SCRIPT} ${TARGETS_HEADERS}
121+
COMMENT "Generating ${INC_DIR}/libraries_sha.inc"
122+
)
123+
set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${INC_DIR}/libraries_sha.inc)
124+
add_custom_target(libraries_sha_header DEPENDS ${INC_DIR}/libraries_sha.inc)
125+
add_dependencies(amd_comgr libraries_sha_header)
126+
113127
include_directories(${INC_DIR})

amd/comgr/cmake/device-libs-id.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from argparse import ArgumentParser
2+
from hashlib import sha256
3+
from functools import reduce
4+
5+
if __name__ == "__main__":
6+
parser = ArgumentParser(description='Generate id by computing a hash of the generated headers')
7+
parser.add_argument("headers", nargs='+', help='List of headers to generate id from')
8+
parser.add_argument("--varname", help='Name of the variable to generate', required=True)
9+
parser.add_argument("--output", help='Name of the header to generate', required=True)
10+
11+
args = parser.parse_args()
12+
args.headers.sort()
13+
14+
hash = sha256()
15+
for x in args.headers:
16+
hash.update(open(x, 'rb').read())
17+
digest_uchar = hash.digest()
18+
digest_char = [e if e < 128 else e-256 for e in digest_uchar]
19+
digest_elts = ", ".join(map(str, digest_char))
20+
print(f"static const char {args.varname}[] = {{{digest_elts}, 0}};", file=open(args.output, 'w'))

amd/comgr/cmake/opencl_pch.cmake

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,7 @@ endfunction()
5050

5151
generate_pch(1.2)
5252
generate_pch(2.0)
53+
54+
# hash the opencl header and pass the result to comgr compilation
55+
file(SHA256 ${OPENCL_C_H} OPENCL_C_SHA)
56+
list(APPEND AMD_COMGR_PRIVATE_COMPILE_DEFINITIONS "OPENCL_C_SHA=${OPENCL_C_SHA}")

amd/comgr/src/comgr-cache-command.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#include "comgr-cache-command.h"
2+
#include "comgr-cache.h"
3+
#include "comgr-device-libs.h"
4+
#include "comgr-env.h"
5+
#include "comgr.h"
6+
7+
#include <clang/Basic/Version.h>
8+
#include <llvm/ADT/StringExtras.h>
9+
10+
#include <optional>
11+
12+
namespace COMGR {
13+
using namespace llvm;
14+
using namespace clang;
15+
16+
namespace {
17+
// std::isalnum is locale dependent and can have issues
18+
// depending on the stdlib version and application. We prefer to avoid it
19+
bool isalnum(char c) {
20+
char low[] = {'0', 'a', 'A'};
21+
char hi[] = {'9', 'z', 'Z'};
22+
for (unsigned i = 0; i != 3; ++i) {
23+
if (low[i] <= c && c <= hi[i])
24+
return true;
25+
}
26+
return false;
27+
}
28+
29+
} // namespace
30+
31+
std::optional<size_t> CachedCommandAdaptor::searchComgrTmpModel(StringRef S) {
32+
// Ideally, we would use std::regex_search with the regex
33+
// "comgr-[[:alnum:]]{6}". However, due to a bug in stdlibc++
34+
// (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85824) we have to roll our
35+
// own search of this regular expression. This bug resulted in a crash in
36+
// luxmarkv3, during the std::regex constructor.
37+
const StringRef Prefix = "comgr-";
38+
const size_t AlnumCount = 6;
39+
40+
size_t N = S.size();
41+
size_t Pos = S.find(Prefix);
42+
43+
size_t AlnumStart = Pos + Prefix.size();
44+
size_t AlnumEnd = AlnumStart + AlnumCount;
45+
if (Pos == StringRef::npos || N < AlnumEnd)
46+
return std::nullopt;
47+
48+
for (size_t i = AlnumStart; i < AlnumEnd; ++i) {
49+
if (!isalnum(S[i]))
50+
return std::nullopt;
51+
}
52+
53+
return Pos;
54+
}
55+
56+
void CachedCommandAdaptor::addString(CachedCommandAdaptor::HashAlgorithm &H,
57+
StringRef S) {
58+
// hash size + contents to avoid collisions
59+
// for example, we have to ensure that the result of hashing "AA" "BB" is
60+
// different from "A" "ABB"
61+
H.update(S.size());
62+
H.update(S);
63+
}
64+
65+
void CachedCommandAdaptor::addFileContents(
66+
CachedCommandAdaptor::HashAlgorithm &H, StringRef Buf) {
67+
// this is a workaround temporary paths getting in the output files of the
68+
// different commands in #line directives in preprocessed files, and the
69+
// ModuleID or source_filename in the bitcode.
70+
while (!Buf.empty()) {
71+
std::optional<size_t> ComgrTmpPos = searchComgrTmpModel(Buf);
72+
if (!ComgrTmpPos) {
73+
addString(H, Buf);
74+
break;
75+
}
76+
77+
StringRef ToHash = Buf.substr(0, *ComgrTmpPos);
78+
addString(H, ToHash);
79+
Buf = Buf.substr(ToHash.size() + StringRef("comgr-xxxxxx").size());
80+
}
81+
}
82+
83+
Expected<CachedCommandAdaptor::Identifier>
84+
CachedCommandAdaptor::getIdentifier() const {
85+
CachedCommandAdaptor::HashAlgorithm H;
86+
H.update(getClass());
87+
H.update(env::shouldEmitVerboseLogs());
88+
addString(H, getClangFullVersion());
89+
addString(H, getComgrHashIdentifier());
90+
addString(H, getDeviceLibrariesIdentifier());
91+
92+
if (Error E = addInputIdentifier(H))
93+
return E;
94+
95+
addOptionsIdentifier(H);
96+
97+
CachedCommandAdaptor::Identifier Id;
98+
toHex(H.final(), true, Id);
99+
return Id;
100+
}
101+
} // namespace COMGR

amd/comgr/src/comgr-cache-command.h

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*******************************************************************************
2+
*
3+
* University of Illinois/NCSA
4+
* Open Source License
5+
*
6+
* Copyright (c) 2018 Advanced Micro Devices, Inc. All Rights Reserved.
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* with the Software without restriction, including without limitation the
11+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12+
* sell copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* * Redistributions of source code must retain the above copyright notice,
16+
* this list of conditions and the following disclaimers.
17+
*
18+
* * Redistributions in binary form must reproduce the above copyright
19+
* notice, this list of conditions and the following disclaimers in the
20+
* documentation and/or other materials provided with the distribution.
21+
*
22+
* * Neither the names of Advanced Micro Devices, Inc. nor the names of its
23+
* contributors may be used to endorse or promote products derived from
24+
* this Software without specific prior written permission.
25+
*
26+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29+
* CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH
32+
* THE SOFTWARE.
33+
*
34+
******************************************************************************/
35+
36+
#ifndef COMGR_CACHE_COMMAND_H
37+
#define COMGR_CACHE_COMMAND_H
38+
39+
#include "amd_comgr.h"
40+
41+
#include <clang/Driver/Action.h>
42+
#include <llvm/Support/Error.h>
43+
#include <llvm/Support/MemoryBuffer.h>
44+
#include <llvm/Support/SHA256.h>
45+
46+
namespace llvm {
47+
class raw_ostream;
48+
}
49+
50+
namespace COMGR {
51+
class CachedCommandAdaptor {
52+
public:
53+
using ActionClass = clang::driver::Action::ActionClass;
54+
using HashAlgorithm = llvm::SHA256;
55+
using Identifier = llvm::SmallString<64>;
56+
57+
llvm::Expected<Identifier> getIdentifier() const;
58+
59+
virtual bool canCache() const = 0;
60+
virtual llvm::Error writeExecuteOutput(llvm::StringRef CachedBuffer) = 0;
61+
virtual llvm::Expected<llvm::StringRef> readExecuteOutput() = 0;
62+
virtual amd_comgr_status_t execute(llvm::raw_ostream &LogS) = 0;
63+
64+
virtual ~CachedCommandAdaptor() = default;
65+
66+
// helper to work around the comgr-xxxxx string appearing in files
67+
static void addFileContents(HashAlgorithm &H, llvm::StringRef Buf);
68+
static void addString(HashAlgorithm &H, llvm::StringRef S);
69+
static std::optional<size_t> searchComgrTmpModel(llvm::StringRef S);
70+
71+
protected:
72+
virtual ActionClass getClass() const = 0;
73+
virtual void addOptionsIdentifier(HashAlgorithm &) const = 0;
74+
virtual llvm::Error addInputIdentifier(HashAlgorithm &) const = 0;
75+
};
76+
} // namespace COMGR
77+
78+
#endif

0 commit comments

Comments
 (0)