source: icGREP/icgrep-devel/icgrep/toolchain/object_cache.cpp @ 6285

Last change on this file since 6285 was 6285, checked in by cameron, 4 months ago

-enable-cache-trace flag; base64 encoding for property kernels.

File size: 16.0 KB
Line 
1#include "object_cache.h"
2#include "object_cache_util.hpp"
3#include <kernels/kernel.h>
4#include <kernels/kernel_builder.h>
5#include <llvm/Support/raw_ostream.h>
6#include <llvm/Support/MemoryBuffer.h>
7#include <llvm/IR/Metadata.h>
8#include <llvm/Support/FileSystem.h>
9#include <llvm/Support/Path.h>
10#include <llvm/Support/Debug.h>
11#include <llvm/IR/Module.h>
12#include <toolchain/toolchain.h>
13#if LLVM_VERSION_INTEGER < LLVM_VERSION_CODE(4, 0, 0)
14#include <llvm/Bitcode/ReaderWriter.h>
15#else
16#include <llvm/Bitcode/BitcodeReader.h>
17#include <llvm/Bitcode/BitcodeWriter.h>
18#endif
19#include <llvm/IR/Verifier.h>
20#include <boost/lexical_cast.hpp>
21
22using namespace llvm;
23using namespace boost;
24
25using Path = ParabixObjectCache::Path;
26
27std::unique_ptr<ParabixObjectCache> ParabixObjectCache::mInstance;
28
29//===----------------------------------------------------------------------===//
30// Object cache (based on tools/lli/lli.cpp, LLVM 3.6.1)
31//
32// This object cache implementation writes cached objects to disk to the
33// directory specified by CacheDir, using a filename provided in the module
34// descriptor. The cache tries to load a saved object using that path if the
35// file exists.
36//
37
38#define MONTH_1 \
39    ((__DATE__ [0] == 'O' || __DATE__ [0] == 'N' || __DATE__ [0] == 'D') ? '1' : '0')
40#define MONTH_2 \
41    (__DATE__ [2] == 'n' ? (__DATE__ [1] == 'a' ? '1' : '6') \
42    : __DATE__ [2] == 'b' ? '2' \
43    : __DATE__ [2] == 'r' ? (__DATE__ [0] == 'M' ? '3' : '4') \
44    : __DATE__ [2] == 'y' ? '5' \
45    : __DATE__ [2] == 'l' ? '7' \
46    : __DATE__ [2] == 'g' ? '8' \
47    : __DATE__ [2] == 'p' ? '9' \
48    : __DATE__ [2] == 't' ? '0' \
49    : __DATE__ [2] == 'v' ? '1' : '2')
50#define DAY_1 (__DATE__[4] == ' ' ? '0' : __DATE__[4])
51#define DAY_2 (__DATE__[5])
52#define YEAR_1 (__DATE__[9])
53#define YEAR_2 (__DATE__[10])
54#define HOUR_1 (__TIME__[0])
55#define HOUR_2 (__TIME__[1])
56#define MINUTE_1 (__TIME__[3])
57#define MINUTE_2 (__TIME__[4])
58#define SECOND_1 (__TIME__[6])
59#define SECOND_2 (__TIME__[7])
60
61const static auto CACHE_PREFIX = PARABIX_VERSION +
62                          std::string{'@',
63                          MONTH_1, MONTH_2, DAY_1, DAY_2, YEAR_1, YEAR_2,
64                          HOUR_1, HOUR_2, MINUTE_1, MINUTE_2, SECOND_1, SECOND_2,
65                          '_'};
66
67const static auto CACHEABLE = "cacheable";
68
69const static auto SIGNATURE = "signature";
70
71/** ------------------------------------------------------------------------------------------------------------- *
72 * @brief getSignature
73 ** ------------------------------------------------------------------------------------------------------------- */
74const MDString * getSignature(const llvm::Module * const M) {
75    NamedMDNode * const sig = M->getNamedMetadata(SIGNATURE);
76    if (sig) {
77        assert ("empty metadata node" && sig->getNumOperands() == 1);
78        assert ("no signature payload" && sig->getOperand(0)->getNumOperands() == 1);
79        return dyn_cast<MDString>(sig->getOperand(0)->getOperand(0));
80    }
81    return nullptr;
82}
83
84/** ------------------------------------------------------------------------------------------------------------- *
85 * @brief loadCachedObjectFile
86 ** ------------------------------------------------------------------------------------------------------------- */
87bool ParabixObjectCache::loadCachedObjectFile(const std::unique_ptr<kernel::KernelBuilder> & idb, kernel::Kernel * const kernel) {
88    if (LLVM_LIKELY(kernel->isCachable())) {
89        assert (kernel->getModule() == nullptr);
90        const auto moduleId = kernel->getCacheName(idb);
91
92        // TODO: To enable the quick lookup of previously cached objects, I need to reclaim ownership
93        // of the modules from the JIT engine before it destroys them.
94
95//        // Have we already seen this module before?
96//        const auto f = mCachedObject.find(moduleId);
97//        if (LLVM_UNLIKELY(f != mCachedObject.end())) {
98//            Module * const m = f->second.first; assert (m);
99//            kernel->setModule(m);
100//            kernel->prepareCachedKernel(idb);
101//            return true;
102//        }
103
104        // No, check for an existing cache file.
105        Path fileName(mCachePath);
106        sys::path::append(fileName, CACHE_PREFIX);
107        fileName.append(moduleId);
108        fileName.append(KERNEL_FILE_EXTENSION);
109        auto kernelBuffer = MemoryBuffer::getFile(fileName, -1, false);
110        if (codegen::TraceObjectCache) {
111            errs() << "Found cache file: " << moduleId << KERNEL_FILE_EXTENSION << "\n";
112        }
113        if (kernelBuffer) {
114            #if LLVM_VERSION_INTEGER < LLVM_VERSION_CODE(4, 0, 0)
115            auto loadedFile = getLazyBitcodeModule(std::move(kernelBuffer.get()), idb->getContext());
116            #else
117            auto loadedFile = getOwningLazyBitcodeModule(std::move(kernelBuffer.get()), idb->getContext());
118            #endif
119            // if there was no error when parsing the bitcode
120            if (LLVM_LIKELY(loadedFile)) {
121                std::unique_ptr<Module> M(std::move(loadedFile.get()));
122                if (kernel->hasSignature()) {
123                    const MDString * const sig = getSignature(M.get());
124                    assert ("signature is missing from kernel file: possible module naming conflict or change in the LLVM metadata storage policy?" && sig);
125                    if (LLVM_UNLIKELY(sig == nullptr || !sig->getString().equals(kernel->makeSignature(idb)))) {
126                        goto invalid;
127                    }
128                }
129                sys::path::replace_extension(fileName, OBJECT_FILE_EXTENSION);
130                auto objectBuffer = MemoryBuffer::getFile(fileName.c_str(), -1, false);
131                if (LLVM_LIKELY(objectBuffer)) {
132                    Module * const m = M.release();
133                    // defaults to <path>/<moduleId>.kernel
134                    m->setModuleIdentifier(moduleId);
135                    kernel->setModule(m);
136                    kernel->prepareCachedKernel(idb);
137                    mCachedObject.emplace(moduleId, std::make_pair(m, std::move(objectBuffer.get())));
138                    // update the modified time of the .kernel, .o and .kernel files
139                    const auto access_time = currentTime();
140                    fs::last_write_time(fileName.c_str(), access_time);
141                    sys::path::replace_extension(fileName, KERNEL_FILE_EXTENSION);
142                    fs::last_write_time(fileName.c_str(), access_time);
143                    return true;
144                }
145            }
146        }
147
148invalid:
149
150        Module * const module = kernel->setModule(new Module(moduleId, idb->getContext()));
151        // mark this module as cachable
152        module->getOrInsertNamedMetadata(CACHEABLE);
153        // if this module has a signature, add it to the metadata
154        if (kernel->hasSignature()) {
155            NamedMDNode * const md = module->getOrInsertNamedMetadata(SIGNATURE);
156            assert (md->getNumOperands() == 0);
157            MDString * const sig = MDString::get(module->getContext(), kernel->makeSignature(idb));
158            md->addOperand(MDNode::get(module->getContext(), {sig}));
159        }
160    }
161    return false;
162}
163
164/** ------------------------------------------------------------------------------------------------------------- *
165 * @brief notifyObjectCompiled
166 *
167 * A new module has been compiled. If it is cacheable and no conflicting module exists, write it out.
168 ** ------------------------------------------------------------------------------------------------------------- */
169void ParabixObjectCache::notifyObjectCompiled(const Module * M, MemoryBufferRef Obj) {
170    if (LLVM_LIKELY(M->getNamedMetadata(CACHEABLE))) {
171
172        const auto moduleId = M->getModuleIdentifier();
173        Path objectName(mCachePath);
174        sys::path::append(objectName, CACHE_PREFIX);
175        objectName.append(moduleId);
176        objectName.append(OBJECT_FILE_EXTENSION);
177
178        // Write the object code
179        std::error_code EC;
180        raw_fd_ostream objFile(objectName, EC, sys::fs::F_None);
181        objFile.write(Obj.getBufferStart(), Obj.getBufferSize());
182        objFile.close();
183
184        // and kernel prototype header
185        std::unique_ptr<Module> H(new Module(M->getModuleIdentifier(), M->getContext()));
186        for (const Function & f : M->getFunctionList()) {
187            if (f.hasExternalLinkage() && !f.empty()) {
188                Function::Create(f.getFunctionType(), Function::ExternalLinkage, f.getName(), H.get());
189            }
190        }
191
192        // then the signature (if one exists)
193        const MDString * const sig = getSignature(M);
194        if (sig) {
195            NamedMDNode * const md = H->getOrInsertNamedMetadata(SIGNATURE);
196            assert (md->getNumOperands() == 0);
197            MDString * const sigCopy = MDString::get(H->getContext(), sig->getString());
198            md->addOperand(MDNode::get(H->getContext(), {sigCopy}));
199        }
200
201        sys::path::replace_extension(objectName, KERNEL_FILE_EXTENSION);
202        raw_fd_ostream kernelFile(objectName.str(), EC, sys::fs::F_None);
203        WriteBitcodeToFile(H.get(), kernelFile);
204        kernelFile.close();
205        if (codegen::TraceObjectCache) {
206            errs() << "Wrote cache file: " << moduleId << KERNEL_FILE_EXTENSION << "\n";
207        }
208    }
209}
210
211/** ------------------------------------------------------------------------------------------------------------- *
212 * @brief getObject
213 ** ------------------------------------------------------------------------------------------------------------- */
214std::unique_ptr<MemoryBuffer> ParabixObjectCache::getObject(const Module * module) {
215    const auto f = mCachedObject.find(module->getModuleIdentifier());
216    if (f == mCachedObject.end()) {
217        return nullptr;
218    }
219    // Return a copy of the buffer, for MCJIT to modify, if necessary.
220    return MemoryBuffer::getMemBufferCopy(f->second.second.get()->getBuffer());
221}
222
223/** ------------------------------------------------------------------------------------------------------------- *
224 * @brief checkForCachedKernel
225 ** ------------------------------------------------------------------------------------------------------------- */
226bool ParabixObjectCache::checkForCachedKernel(const std::unique_ptr<kernel::KernelBuilder> & b, kernel::Kernel * const kernel) noexcept {
227    return mInstance.get() && mInstance->loadCachedObjectFile(b, kernel);
228}
229
230/** ------------------------------------------------------------------------------------------------------------- *
231 * @brief requiresCacheCleanUp
232 ** ------------------------------------------------------------------------------------------------------------- */
233inline bool ParabixObjectCache::requiresCacheCleanUp() noexcept {
234    return FileLock(fs::path{mCachePath.str()}).locked();
235}
236
237/** ------------------------------------------------------------------------------------------------------------- *
238 * @brief initiateCacheCleanUp
239 ** ------------------------------------------------------------------------------------------------------------- */
240void ParabixObjectCache::initiateCacheCleanUp() noexcept {
241    if (LLVM_UNLIKELY(requiresCacheCleanUp())) {
242        // syslog?
243        if (fork() == 0) {
244            char * const cachePath = const_cast<char *>(mCachePath.c_str());
245            char * args[3] = {const_cast<char *>(CACHE_JANITOR_FILE_NAME), cachePath, nullptr};
246            Path janitorFileName(codegen::ProgramName);
247            sys::path::remove_filename(janitorFileName);
248            sys::path::append(janitorFileName, CACHE_JANITOR_FILE_NAME);
249            char * const janitorPath = const_cast<char *>(janitorFileName.c_str());
250            if (execvp(janitorPath, args) < 0) {
251                exit(errno);
252            }
253        }
254    }
255}
256
257/** ------------------------------------------------------------------------------------------------------------- *
258 * @brief getDefaultCachePath
259 ** ------------------------------------------------------------------------------------------------------------- */
260inline void getDefaultCachePath(Path & configPath) {
261    // default: $HOME/.cache/parabix/
262    sys::path::home_directory(configPath);
263    sys::path::append(configPath, ".cache", "parabix");
264}
265
266#if 0
267
268/** ------------------------------------------------------------------------------------------------------------- *
269 * @brief getConfigPath
270 ** ------------------------------------------------------------------------------------------------------------- */
271inline Path getConfigPath() {
272    // $HOME/.config/parabix/cache.cfg
273    Path configPath;
274    sys::path::home_directory(configPath);
275    sys::path::append(configPath, ".config", "parabix");
276    sys::fs::create_directories(configPath);
277    sys::path::append(configPath, "cache.cfg");
278    return configPath;
279}
280
281/** ------------------------------------------------------------------------------------------------------------- *
282 * @brief loadCacheSettings
283 ** ------------------------------------------------------------------------------------------------------------- */
284inline size_t parseInt(const StringRef & str, const StringRef & label) {
285    try {
286        return lexical_cast<size_t>(str.data(), str.size());
287    } catch(const bad_lexical_cast &) {
288        errs() << "configuration for " << label << " must be an integer";
289        exit(-1);
290    }
291}
292
293#endif
294
295/** ------------------------------------------------------------------------------------------------------------- *
296 * @brief loadCacheSettings
297 ** ------------------------------------------------------------------------------------------------------------- */
298inline void ParabixObjectCache::loadCacheSettings() noexcept {
299    getDefaultCachePath(mCachePath);
300    #if 0
301
302    const auto configPath = getConfigPath();
303    auto configFile = MemoryBuffer::getFile(configPath);
304
305    // default: $HOME/.cache/parabix/
306    sys::path::home_directory(mCachePath);
307    sys::path::append(mCachePath, ".cache", "parabix");
308    // default: 1 week
309    mCacheExpirationDelay = CACHE_ENTRY_EXPIRY_PERIOD;
310
311    if (LLVM_UNLIKELY(!!configFile)) {
312        const StringRef config = (*configFile)->getBuffer();
313        #define ASCII_WHITESPACE " \f\n\r\t\v"
314        #define ASCII_WHITESPACE_OR_EQUALS (ASCII_WHITESPACE "+")
315        size_t nameStart = 0;
316        for (;;) {
317
318            const auto nameEnd = config.find_first_of(ASCII_WHITESPACE_OR_EQUALS, nameStart);
319            if (nameEnd == StringRef::npos) break;
320            const auto afterEquals = config.find_first_of('=', nameEnd) + 1;
321            if (LLVM_UNLIKELY(afterEquals == StringRef::npos)) break;
322            const auto valueStart = config.find_first_not_of(ASCII_WHITESPACE, afterEquals);
323            if (LLVM_UNLIKELY(valueStart == StringRef::npos)) break;
324            const auto valueEnd = config.find_first_of(ASCII_WHITESPACE, valueStart);
325            if (LLVM_UNLIKELY(valueEnd == StringRef::npos)) break;
326            const auto name = config.slice(nameStart, nameEnd);
327            const auto value = config.slice(valueStart, valueEnd);
328
329            if (name.equals_lower("cachepath")) {
330                mCachePath.assign(value);
331            } else if (name.equals_lower("cachedayslimit")) {
332                mCacheExpirationDelay = parseInt(value, "cachedayslimit");
333            }
334            // get the next name start
335            nameStart = config.find_first_not_of(ASCII_WHITESPACE, valueEnd + 1);
336        }
337    }
338    #endif
339    sys::fs::create_directories(mCachePath);
340}
341
342/** ------------------------------------------------------------------------------------------------------------- *
343 * @brief saveCachePath
344 ** ------------------------------------------------------------------------------------------------------------- */
345inline void ParabixObjectCache::saveCacheSettings() noexcept {
346
347
348}
349
350/** ------------------------------------------------------------------------------------------------------------- *
351 * @brief initializeCacheSystems
352 ** ------------------------------------------------------------------------------------------------------------- */
353void ParabixObjectCache::initializeCacheSystems() noexcept {
354    if (LLVM_LIKELY(mInstance.get() == nullptr && codegen::EnableObjectCache)) {
355        mInstance.reset(new ParabixObjectCache());
356    }
357}
358
359ParabixObjectCache::ParabixObjectCache() {
360    loadCacheSettings();
361    initiateCacheCleanUp();
362}
Note: See TracBrowser for help on using the repository browser.