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

Last change on this file since 5769 was 5761, checked in by nmedfort, 23 months ago

Cache signature is now written into .kernel bitcode file. Minor bug fix and revision of GrepEngine::DoGrepThreadMethod?

File size: 9.7 KB
Line 
1#include "toolchain.h"
2#include "object_cache.h"
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 <sys/file.h>
13#include <sys/stat.h>
14#include <fcntl.h>
15#include <boost/filesystem.hpp>
16#include <boost/range/iterator_range.hpp>
17#include <boost/container/flat_set.hpp>
18#if LLVM_VERSION_INTEGER < LLVM_4_0_0
19#include <llvm/Bitcode/ReaderWriter.h>
20#else
21#include <llvm/Bitcode/BitcodeReader.h>
22#include <llvm/Bitcode/BitcodeWriter.h>
23#endif
24#include <llvm/IR/Verifier.h>
25#include <ctime>
26
27using namespace llvm;
28namespace fs = boost::filesystem;
29
30#ifdef NDEBUG
31#define CACHE_ENTRY_MAX_HOURS (24 * 15)
32#else
33#define CACHE_ENTRY_MAX_HOURS (1)
34#endif
35
36#define SECONDS_PER_HOUR (3600)
37//===----------------------------------------------------------------------===//
38// Object cache (based on tools/lli/lli.cpp, LLVM 3.6.1)
39//
40// This object cache implementation writes cached objects to disk to the
41// directory specified by CacheDir, using a filename provided in the module
42// descriptor. The cache tries to load a saved object using that path if the
43// file exists.
44//
45
46#define MONTH_1 \
47    ((__DATE__ [0] == 'O' || __DATE__ [0] == 'N' || __DATE__ [0] == 'D') ? '1' : '0')
48#define MONTH_2 \
49    (__DATE__ [2] == 'n' ? (__DATE__ [1] == 'a' ? '1' : '6') \
50    : __DATE__ [2] == 'b' ? '2' \
51    : __DATE__ [2] == 'r' ? (__DATE__ [0] == 'M' ? '3' : '4') \
52    : __DATE__ [2] == 'y' ? '5' \
53    : __DATE__ [2] == 'l' ? '7' \
54    : __DATE__ [2] == 'g' ? '8' \
55    : __DATE__ [2] == 'p' ? '9' \
56    : __DATE__ [2] == 't' ? '0' \
57    : __DATE__ [2] == 'v' ? '1' : '2')
58#define DAY_1 (__DATE__[4] == ' ' ? '0' : __DATE__[4])
59#define DAY_2 (__DATE__[5])
60#define YEAR_1 (__DATE__[9])
61#define YEAR_2 (__DATE__[10])
62#define HOUR_1 (__TIME__[0])
63#define HOUR_2 (__TIME__[1])
64#define MINUTE_1 (__TIME__[3])
65#define MINUTE_2 (__TIME__[4])
66#define SECOND_1 (__TIME__[6])
67#define SECOND_2 (__TIME__[7])
68
69const static auto CACHE_PREFIX = PARABIX_VERSION +
70                          std::string{'@',
71                          MONTH_1, MONTH_2, DAY_1, DAY_2, YEAR_1, YEAR_2,
72                          HOUR_1, HOUR_2, MINUTE_1, MINUTE_2, SECOND_1, SECOND_2,
73                          '_'};
74
75const static auto CACHEABLE = "cacheable";
76
77const static auto SIGNATURE = "signature";
78
79const MDString * getSignature(const llvm::Module * const M) {
80    NamedMDNode * const sig = M->getNamedMetadata(SIGNATURE);
81    if (sig) {
82        assert ("empty metadata node" && sig->getNumOperands() == 1);
83        assert ("no signature payload" && sig->getOperand(0)->getNumOperands() == 1);
84        return dyn_cast<MDString>(sig->getOperand(0)->getOperand(0));
85    }
86    return nullptr;
87}
88
89bool ParabixObjectCache::loadCachedObjectFile(const std::unique_ptr<kernel::KernelBuilder> & idb, kernel::Kernel * const kernel) {
90    if (LLVM_LIKELY(kernel->isCachable())) {
91        assert (kernel->getModule() == nullptr);
92        const auto moduleId = kernel->getCacheName(idb);
93
94        // Have we already seen this module before?
95        const auto f = mCachedObject.find(moduleId);
96        if (LLVM_UNLIKELY(f != mCachedObject.end())) {
97            Module * const m = f->second.first; assert (m);
98            kernel->setModule(m);
99            kernel->prepareCachedKernel(idb);
100            return true;
101        }
102
103        // No, check for an existing cache file.
104        Path fileName(mCachePath);
105        sys::path::append(fileName, CACHE_PREFIX);
106        fileName.append(moduleId);
107        fileName.append(".kernel");
108
109        auto kernelBuffer = MemoryBuffer::getFile(fileName.c_str(), -1, false);
110        if (kernelBuffer) {
111            #if LLVM_VERSION_INTEGER < LLVM_4_0_0
112            auto loadedFile = getLazyBitcodeModule(std::move(kernelBuffer.get()), idb->getContext());
113            #else
114            auto loadedFile = getOwningLazyBitcodeModule(std::move(kernelBuffer.get()), idb->getContext());
115            #endif
116            // if there was no error when parsing the bitcode
117            if (LLVM_LIKELY(loadedFile)) {
118                std::unique_ptr<Module> M(std::move(loadedFile.get()));
119                if (kernel->hasSignature()) {
120                    const MDString * const sig = getSignature(M.get());
121                    assert ("signature is missing from kernel file: possible module naming conflict?" && sig);
122                    if (LLVM_UNLIKELY(sig == nullptr || !sig->getString().equals(kernel->makeSignature(idb)))) {
123                        goto invalid;
124                    }
125                }
126                sys::path::replace_extension(fileName, ".o");
127                auto objectBuffer = MemoryBuffer::getFile(fileName.c_str(), -1, false);
128                if (LLVM_LIKELY(objectBuffer)) {
129                    Module * const m = M.release();
130                    // defaults to <path>/<moduleId>.kernel
131                    m->setModuleIdentifier(moduleId);
132                    kernel->setModule(m);
133                    kernel->prepareCachedKernel(idb);
134                    mCachedObject.emplace(moduleId, std::make_pair(m, std::move(objectBuffer.get())));
135                    // update the modified time of the .kernel, .o and .sig files
136                    time_t access_time = time(0);
137                    fs::last_write_time(fileName.c_str(), access_time);
138                    sys::path::replace_extension(fileName, ".kernel");
139                    fs::last_write_time(fileName.c_str(), access_time);
140                    return true;
141                }
142            }
143        }
144
145invalid:
146
147        Module * const module = kernel->setModule(new Module(moduleId, idb->getContext()));
148        // mark this module as cachable
149        module->getOrInsertNamedMetadata(CACHEABLE);
150        // if this module has a signature, add it to the metadata
151        if (kernel->hasSignature()) {
152            NamedMDNode * const md = module->getOrInsertNamedMetadata(SIGNATURE);
153            assert (md->getNumOperands() == 0);
154            MDString * const sig = MDString::get(module->getContext(), kernel->makeSignature(idb));
155            md->addOperand(MDNode::get(module->getContext(), {sig}));
156        }
157    }
158    return false;
159}
160
161// A new module has been compiled. If it is cacheable and no conflicting module
162// exists, write it out.
163void ParabixObjectCache::notifyObjectCompiled(const Module * M, MemoryBufferRef Obj) {
164    if (LLVM_LIKELY(M->getNamedMetadata(CACHEABLE))) {
165        const auto moduleId = M->getModuleIdentifier();
166        Path objectName(mCachePath);
167        sys::path::append(objectName, CACHE_PREFIX);
168        objectName.append(moduleId);
169        objectName.append(".o");
170
171        // Write the object code
172        std::error_code EC;
173        raw_fd_ostream objFile(objectName, EC, sys::fs::F_None);
174        objFile.write(Obj.getBufferStart(), Obj.getBufferSize());
175        objFile.close();
176
177        // and kernel prototype header
178        std::unique_ptr<Module> H(new Module(M->getModuleIdentifier(), M->getContext()));
179        for (const Function & f : M->getFunctionList()) {
180            if (f.hasExternalLinkage() && !f.empty()) {
181                Function::Create(f.getFunctionType(), Function::ExternalLinkage, f.getName(), H.get());
182            }
183        }
184
185        // then the signature (if one exists)
186        const MDString * const sig = getSignature(M);
187        if (sig) {
188            NamedMDNode * const md = H->getOrInsertNamedMetadata(SIGNATURE);
189            assert (md->getNumOperands() == 0);
190            MDString * const sigCopy = MDString::get(H->getContext(), sig->getString());
191            md->addOperand(MDNode::get(H->getContext(), {sigCopy}));
192        }
193
194        sys::path::replace_extension(objectName, ".kernel");
195        raw_fd_ostream kernelFile(objectName.str(), EC, sys::fs::F_None);
196        WriteBitcodeToFile(H.get(), kernelFile);
197        kernelFile.close();
198    }
199}
200
201void ParabixObjectCache::performIncrementalCacheCleanupStep() {
202    if (mCacheCleanupIterator != fs::directory_iterator()) {
203        const auto e = mCacheCleanupIterator->path();
204        mCacheCleanupIterator++;
205        // Simple clean-up policy: files that haven't been touched by the
206        // driver in MaxCacheEntryHours are deleted.
207        // TODO: possibly incrementally manage by size and/or total file count.
208        // TODO: possibly determine total filecount and set items per clean up step based on
209        // filecount
210        if (fs::is_regular_file(e)) {
211            const auto age = std::time(nullptr) - fs::last_write_time(e);
212            if (age > (CACHE_ENTRY_MAX_HOURS * SECONDS_PER_HOUR)) {
213                fs::remove(e);
214            }
215        }
216    }
217}
218
219std::unique_ptr<MemoryBuffer> ParabixObjectCache::getObject(const Module * module) {
220    const auto f = mCachedObject.find(module->getModuleIdentifier());
221    if (f == mCachedObject.end()) {
222        return nullptr;
223    }
224    // Return a copy of the buffer, for MCJIT to modify, if necessary.
225    return MemoryBuffer::getMemBufferCopy(f->second.second.get()->getBuffer());
226}
227
228ParabixObjectCache::ParabixObjectCache(const StringRef dir)
229: mCachePath(dir) {
230    fs::path p(mCachePath.str());
231    if (LLVM_LIKELY(!mCachePath.empty())) {
232        sys::fs::create_directories(mCachePath);
233    }
234    fs::directory_iterator it(p);
235    mCacheCleanupIterator = it;
236}
237
238inline ParabixObjectCache::Path getDefaultPath() {
239    // $HOME/.cache/parabix/
240    ParabixObjectCache::Path cachePath;
241#if LLVM_VERSION_INTEGER < LLVM_3_7_0
242    sys::path::user_cache_directory(cachePath, "parabix");
243#else
244    sys::path::home_directory(cachePath);
245    sys::path::append(cachePath, ".cache", "parabix");
246#endif
247    return cachePath;
248}
249
250ParabixObjectCache::ParabixObjectCache()
251: ParabixObjectCache(getDefaultPath()) {
252
253}
254
255
Note: See TracBrowser for help on using the repository browser.