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

Last change on this file since 6184 was 6184, checked in by nmedfort, 12 months ago

Initial version of PipelineKernel? + revised StreamSet? model.

File size: 10.4 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_VERSION_CODE(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 * codegen::CacheDaysLimit)
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        // TODO: To enable the quick lookup of previously cached objects, I need to reclaim ownership
95        // of the modules from the JIT engine before it destroys them.
96
97//        // Have we already seen this module before?
98//        const auto f = mCachedObject.find(moduleId);
99//        if (LLVM_UNLIKELY(f != mCachedObject.end())) {
100//            Module * const m = f->second.first; assert (m);
101//            kernel->setModule(m);
102//            kernel->prepareCachedKernel(idb);
103//            return true;
104//        }
105
106        // No, check for an existing cache file.
107        Path fileName(mCachePath);
108        sys::path::append(fileName, CACHE_PREFIX);
109        fileName.append(moduleId);
110        fileName.append(".kernel");
111
112        auto kernelBuffer = MemoryBuffer::getFile(fileName.c_str(), -1, false);
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, ".o");
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 .sig files
139                    time_t access_time = time(0);
140                    fs::last_write_time(fileName.c_str(), access_time);
141                    sys::path::replace_extension(fileName, ".kernel");
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// A new module has been compiled. If it is cacheable and no conflicting module
165// exists, write it out.
166void ParabixObjectCache::notifyObjectCompiled(const Module * M, MemoryBufferRef Obj) {
167    if (LLVM_LIKELY(M->getNamedMetadata(CACHEABLE))) {
168
169        const auto moduleId = M->getModuleIdentifier();
170        Path objectName(mCachePath);
171        sys::path::append(objectName, CACHE_PREFIX);
172        objectName.append(moduleId);
173        objectName.append(".o");
174
175        // Write the object code
176        std::error_code EC;
177        raw_fd_ostream objFile(objectName, EC, sys::fs::F_None);
178        objFile.write(Obj.getBufferStart(), Obj.getBufferSize());
179        objFile.close();
180
181        // and kernel prototype header
182        std::unique_ptr<Module> H(new Module(M->getModuleIdentifier(), M->getContext()));
183        for (const Function & f : M->getFunctionList()) {
184            if (f.hasExternalLinkage() && !f.empty()) {
185                Function::Create(f.getFunctionType(), Function::ExternalLinkage, f.getName(), H.get());
186            }
187        }
188
189        // then the signature (if one exists)
190        const MDString * const sig = getSignature(M);
191        if (sig) {
192            NamedMDNode * const md = H->getOrInsertNamedMetadata(SIGNATURE);
193            assert (md->getNumOperands() == 0);
194            MDString * const sigCopy = MDString::get(H->getContext(), sig->getString());
195            md->addOperand(MDNode::get(H->getContext(), {sigCopy}));
196        }
197
198        sys::path::replace_extension(objectName, ".kernel");
199        raw_fd_ostream kernelFile(objectName.str(), EC, sys::fs::F_None);
200        WriteBitcodeToFile(H.get(), kernelFile);
201        kernelFile.close();
202    }
203}
204
205void ParabixObjectCache::performIncrementalCacheCleanupStep() {
206    if (LLVM_LIKELY(mCleanupMutex.try_lock())) {
207        try {
208
209            // Simple clean-up policy: files that haven't been touched by the
210            // driver in MaxCacheEntryHours are deleted.
211
212            // TODO: possibly incrementally manage by size and/or total file count.
213            // TODO: possibly determine total filecount and set items per clean up step based on
214            // filecount
215
216            const auto now = std::time(nullptr);
217            while (LLVM_LIKELY(mCleanupIterator != fs::directory_iterator())) {
218                const auto i = mCleanupIterator;
219                ++mCleanupIterator;
220                const auto & e = i->path();
221                if (LLVM_LIKELY(fs::is_regular_file(e))) {
222                    const auto expiry = fs::last_write_time(e) + (CACHE_ENTRY_MAX_HOURS * SECONDS_PER_HOUR);
223                    if (now > expiry) {
224                        fs::remove(e);
225                        break;
226                    }
227                }
228            }
229        } catch (...) {
230            fs::path p(mCachePath.str());
231            mCleanupIterator = fs::directory_iterator(p);
232        }
233        mCleanupMutex.unlock();
234    }
235}
236
237std::unique_ptr<MemoryBuffer> ParabixObjectCache::getObject(const Module * module) {
238    const auto f = mCachedObject.find(module->getModuleIdentifier());
239    if (f == mCachedObject.end()) {
240        return nullptr;
241    }
242    // Return a copy of the buffer, for MCJIT to modify, if necessary.
243    return MemoryBuffer::getMemBufferCopy(f->second.second.get()->getBuffer());
244}
245
246ParabixObjectCache::ParabixObjectCache(const StringRef dir)
247: mCachePath(dir) {
248    fs::path p(mCachePath.str());
249    if (LLVM_LIKELY(!mCachePath.empty())) {
250        sys::fs::create_directories(mCachePath);
251    }
252    mCleanupIterator = fs::directory_iterator(p);
253}
254
255inline ParabixObjectCache::Path getDefaultPath() {
256    // $HOME/.cache/parabix/
257    ParabixObjectCache::Path cachePath;
258#if LLVM_VERSION_INTEGER < LLVM_VERSION_CODE(3, 7, 0)
259    sys::path::user_cache_directory(cachePath, "parabix");
260#else
261    sys::path::home_directory(cachePath);
262    sys::path::append(cachePath, ".cache", "parabix");
263#endif
264    return cachePath;
265}
266
267ParabixObjectCache::ParabixObjectCache()
268: ParabixObjectCache(getDefaultPath()) {
269
270}
Note: See TracBrowser for help on using the repository browser.