1 /*
2  * Copyright (c) 2019,2020 NVIDIA CORPORATION.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <stdio.h>
18 #include <unistd.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <pwd.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 
25 #include "GB_jit_cache.h"
26 
27 namespace jit {
28 
29 
30 // Get the directory in home to use for storing the cache
get_user_home_cache_dir()31 std::string get_user_home_cache_dir() {
32   auto home_dir = std::getenv("HOME");
33   if (home_dir != nullptr) {
34     return std::string(home_dir) + "/.GraphBLAS/";
35   } else {
36     return std::string();
37   }
38 }
39 
40 // Default `GRAPHBLAS_CACHE_PATH` to `$HOME/.GraphBLAS`.
41 // This definition can be overridden at compile time by specifying a
42 // `-DGRAPHBLAS_CACHE_PATH=/kernel/cache/path` CMake argument.
43 // This path is used in the `getCacheDir()` function below.
44 #if !defined(GRAPHBLAS_CACHE_PATH)
45 #define GRAPHBLAS_CACHE_PATH  get_user_home_cache_dir()
46 #endif
47 
48 /**
49  * @brief Get the string path to the JITIFY kernel cache directory.
50  *
51  * This path can be overridden at runtime by defining an environment variable
52  * named `GRAPHBLAS_CACHE_PATH`. The value of this variable must be a path
53  * under which the process' user has read/write priveleges.
54  *
55  * This function returns a path to the cache directory, creating it if it
56  * doesn't exist.
57  *
58  * The default cache directory is `$HOME/.GraphBLAS`. If no overrides
59  * are used and if $HOME is not defined, returns an empty path and file
60  * caching is not used.
61  **/
getCacheDir()62 std::string getCacheDir() {
63   // The environment variable always overrides the
64   // default/compile-time value of `GRAPHBLAS_CACHE_PATH`
65   auto kernel_cache_path_env = std::getenv("GRAPHBLAS_CACHE_PATH");
66   auto kernel_cache_path = (kernel_cache_path_env != nullptr ? kernel_cache_path_env
67                                        : GRAPHBLAS_CACHE_PATH);
68 
69   struct stat st;
70   if ( (stat( kernel_cache_path.c_str(), &st) != 0) ) {
71     // `mkdir -p` the kernel cache path if it doesn't exist
72     printf("cache is going to path %s\n", kernel_cache_path.c_str());
73     int status;
74     status = mkdir(kernel_cache_path.c_str(), 0777);
75     if (status != 0 ) return std::string();
76     //boost::filesystem::create_directories(kernel_cache_path);
77   }
78   return std::string(kernel_cache_path);
79 }
80 
GBJitCache()81 GBJitCache::GBJitCache() { }
82 
~GBJitCache()83 GBJitCache::~GBJitCache() { }
84 
85 std::mutex GBJitCache::_kernel_cache_mutex;
86 std::mutex GBJitCache::_program_cache_mutex;
87 
getProgram(std::string const & prog_name,std::string const & cuda_source,std::vector<std::string> const & given_headers,std::vector<std::string> const & given_options,jitify::experimental::file_callback_type file_callback)88 named_prog<jitify::experimental::Program> GBJitCache::getProgram(
89     std::string const& prog_name,
90     std::string const& cuda_source,
91     std::vector<std::string> const& given_headers,
92     std::vector<std::string> const& given_options,
93     jitify::experimental::file_callback_type file_callback)
94 {
95     // Lock for thread safety
96     std::lock_guard<std::mutex> lock(_program_cache_mutex);
97     //printf(" jit_cache get program %s\n", prog_name.c_str());
98 
99     return getCached(prog_name, program_map,
100         [&](){
101             return jitify::experimental::Program(cuda_source,
102                                         given_headers,
103                                         given_options,
104                                         file_callback);
105         }
106     );
107 }
108 
getKernelInstantiation(std::string const & kern_name,named_prog<jitify::experimental::Program> const & named_program,std::vector<std::string> const & arguments)109 named_prog<jitify::experimental::KernelInstantiation> GBJitCache::getKernelInstantiation(
110     std::string const& kern_name,
111     named_prog<jitify::experimental::Program> const& named_program,
112     std::vector<std::string> const& arguments)
113 {
114     // Lock for thread safety
115     std::lock_guard<std::mutex> lock(_kernel_cache_mutex);
116 
117     std::string prog_name = std::get<0>(named_program);
118     jitify::experimental::Program& program = *std::get<1>(named_program);
119 
120     // Make instance name e.g. "prog_binop.kernel_v_v_int_int_long int_Add"
121     std::string kern_inst_name = prog_name + '.' + kern_name;
122     for ( auto&& arg : arguments ) kern_inst_name += '_' + arg;
123 
124     //printf(" got kernel instance %s\n",kern_inst_name.c_str());
125 
126     return getCached(kern_inst_name, kernel_inst_map,
127         [&](){return program.kernel(kern_name)
128                             .instantiate(arguments);
129         }
130     );
131 }
132 
133 // Another overload for getKernelInstantiation which might be useful to get
134 // kernel instantiations in one step
135 // ------------------------------------------------------------------------
136 /*
137 jitify::experimental::KernelInstantiation GBJitCache::getKernelInstantiation(
138     std::string const& kern_name,
139     std::string const& prog_name,
140     std::string const& cuda_source = "",
141     std::vector<std::string> const& given_headers = {},
142     std::vector<std::string> const& given_options = {},
143     file_callback_type file_callback = nullptr)
144 {
145     auto program = getProgram(prog_name,
146                               cuda_source,
147                               given_headers,
148                               given_options,
149                               file_callback);
150     return getKernelInstantiation(kern_name, program);
151 }
152 */
153 
cacheFile(std::string file_name)154 GBJitCache::cacheFile::cacheFile(std::string file_name)
155  : _file_name{file_name}
156 { }
157 
~cacheFile()158 GBJitCache::cacheFile::~cacheFile() { }
159 
read()160 std::string GBJitCache::cacheFile::read()
161 {
162     // Open file (duh)
163     int fd = open ( _file_name.c_str(), O_RDWR );
164     if ( fd == -1 ) {
165         // TODO: connect errors to GrB_error result
166         //printf(" failed to open cache file %s\n",_file_name.c_str());
167         successful_read = false;
168         return std::string();
169     }
170 
171     // Lock the file descriptor. we the only ones now
172     if ( lockf(fd, F_LOCK, 0) == -1 ) {
173         successful_read = false;
174         return std::string();
175     }
176 
177     // Get file descriptor from file pointer
178     FILE *fp = fdopen( fd, "rb" );
179 
180     // Get file length
181     fseek( fp , 0L , SEEK_END);
182     size_t file_size = ftell( fp );
183     rewind( fp );
184 
185     // Allocate memory of file length size
186     std::string content;
187     content.resize(file_size);
188     char *buffer = &content[0];
189 
190     // Copy file into buffer
191     if( fread(buffer, file_size, 1, fp) != 1 ) {
192         //printf(" failed to read cache file %s\n",_file_name.c_str());
193         successful_read = false;
194         fclose(fp);
195         free(buffer);
196         return std::string();
197     }
198     fclose(fp);
199     successful_read = true;
200     printf(" read cache file %s\n",_file_name.c_str());
201 
202     return content;
203 }
204 
write(std::string content)205 void GBJitCache::cacheFile::write(std::string content)
206 {
207     // Open file and create if it doesn't exist, with access 0600
208     int fd = open ( _file_name.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR );
209     if ( fd == -1 ) {
210         printf(" failed to open cache file for write %s\n",_file_name.c_str());
211         successful_write = false;
212         return;
213     }
214 
215     // Lock the file descriptor. we the only ones now
216     if ( lockf(fd, F_LOCK, 0) == -1 ) {
217         successful_write = false;
218         return;
219     }
220 
221     // Get file descriptor from file pointer
222     FILE *fp = fdopen( fd, "wb" );
223 
224     // Copy string into file
225     if( fwrite(content.c_str(), content.length(), 1, fp) != 1 ) {
226         printf(" failed to write cache file %s\n",_file_name.c_str());
227         successful_write = false;
228         fclose(fp);
229         return;
230     }
231     fclose(fp);
232 
233     successful_write = true;
234     //printf(" wrote cache file %s\n",_file_name.c_str());
235 
236     return;
237 }
238 
239 } // namespace jit
240