Replace scoped_cptr with unique_cptr
[alexxy/gromacs.git] / src / gromacs / gpu_utils / ocl_caching.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,2014,2015,2016, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  *  \brief Define infrastructure for OpenCL JIT compilation for Gromacs
37  *
38  *  \author Dimitrios Karkoulis <dimitris.karkoulis@gmail.com>
39  *  \author Anca Hamuraru <anca@streamcomputing.eu>
40  *  \author Teemu Virolainen <teemu@streamcomputing.eu>
41  *  \author Mark Abraham <mark.j.abraham@gmail.com>
42  */
43
44 #include "gmxpre.h"
45
46 #include "ocl_caching.h"
47
48 #include <assert.h>
49
50 #include <cctype>
51 #include <cstdio>
52
53 #include <algorithm>
54 #include <array>
55 #include <iterator>
56 #include <string>
57 #include <vector>
58
59 #include "gromacs/utility/exceptions.h"
60 #include "gromacs/utility/programcontext.h"
61 #include "gromacs/utility/smalloc.h"
62 #include "gromacs/utility/stringutil.h"
63 #include "gromacs/utility/textreader.h"
64 #include "gromacs/utility/unique_cptr.h"
65
66 namespace gmx
67 {
68 namespace ocl
69 {
70
71 /*! \brief RAII helper to use with unique_cptr
72  *
73  * Can't use fclose because the template requires a function that
74  * returns void.
75  *
76  * \todo Either generalise unique_cptr somehow, or (better) make
77  * general infrastructure for reading and writing binary lumps.
78  * Neither of these is a priority while JIT caching is inactive.
79  */
80 static void fclose_wrapper(FILE *fp)
81 {
82     assert(fp != NULL);
83     fclose(fp);
84 }
85
86 std::string makeBinaryCacheFilename(const std::string &kernelFilename,
87                                     cl_device_id       deviceId)
88 {
89     // Note that the OpenCL API is defined in terms of bytes, and we
90     // assume that sizeof(char) is one byte.
91     std::array<char, 1024> deviceName;
92     size_t                 deviceNameLength;
93     cl_int                 cl_error = clGetDeviceInfo(deviceId, CL_DEVICE_NAME, deviceName.size(), deviceName.data(), &deviceNameLength);
94     if (cl_error != CL_SUCCESS)
95     {
96         GMX_THROW(InternalError(formatString("Could not get OpenCL device name, error was %s", ocl_get_error_string(cl_error).c_str())));
97     }
98
99     std::string cacheFilename = "OCL-cache";
100     /* remove the kernel source suffix */
101     cacheFilename += "_" + stripSuffixIfPresent(kernelFilename, ".cl") + "_";
102     /* We want a cache filename that's somewhat human readable, and
103        describes the device because it's based on the vendor's
104        information, but also always works as a filename. So we remove
105        characters that are commonly illegal in filenames (dot, slash),
106        or sometimes inconvenient (whitespace), or perhaps problematic
107        (symbols), by permitting only alphanumeric characters from the
108        current locale. We assume these work well enough in a
109        filename. */
110     std::copy_if(deviceName.begin(), deviceName.begin() + deviceNameLength, std::back_inserter(cacheFilename), isalnum);
111     cacheFilename += ".bin";
112
113     return cacheFilename;
114 }
115
116 cl_program
117 makeProgramFromCache(const std::string &filename,
118                      cl_context         context,
119                      cl_device_id       deviceId)
120 {
121     // TODO all this file reading stuff should become gmx::BinaryReader
122     FILE *f = fopen(filename.c_str(), "rb");
123     const unique_cptr<FILE, fclose_wrapper> fileGuard(f);
124     if (!f)
125     {
126         GMX_THROW(FileIOError("Failed to open binary cache file " + filename));
127     }
128
129     // TODO more stdio error handling
130     fseek(f, 0, SEEK_END);
131     unsigned char             *binary;
132     unique_cptr<unsigned char> binaryGuard;
133     size_t                     fileSize = ftell(f);
134     snew(binary, fileSize);
135     binaryGuard.reset(binary);
136     fseek(f, 0, SEEK_SET);
137     size_t readCount = fread(binary, 1, fileSize, f);
138
139     if (readCount != fileSize)
140     {
141         GMX_THROW(FileIOError("Failed to read binary cache file " + filename));
142     }
143
144     /* TODO If/when caching is re-enabled, compare current build
145      * options and code against the build options and the code
146      * corresponding to the cache. If any change is detected then the
147      * cache cannot be used.
148      *
149      * Also caching functionality will need full re-testing. */
150
151     /* Create program from pre-built binary */
152     cl_int     cl_error;
153     cl_program program = clCreateProgramWithBinary(context,
154                                                    1,
155                                                    &deviceId,
156                                                    &fileSize,
157                                                    const_cast<const unsigned char **>(&binary),
158                                                    NULL,
159                                                    &cl_error);
160     if (cl_error != CL_SUCCESS)
161     {
162         GMX_THROW(InternalError("Could not create OpenCL program, error was " + ocl_get_error_string(cl_error)));
163     }
164
165     return program;
166 }
167
168 void
169 writeBinaryToCache(cl_program program, const std::string &filename)
170 {
171     size_t fileSize;
172     cl_int cl_error = clGetProgramInfo(program, CL_PROGRAM_BINARY_SIZES, sizeof(fileSize), &fileSize, NULL);
173     if (cl_error != CL_SUCCESS)
174     {
175         GMX_THROW(InternalError("Could not get OpenCL program binary size, error was " + ocl_get_error_string(cl_error)));
176     }
177
178     // TODO all this file writing stuff should become gmx::BinaryWriter
179     unsigned char                   *binary;
180     snew(binary, fileSize);
181     const unique_cptr<unsigned char> binaryGuard(binary);
182
183     cl_error = clGetProgramInfo(program, CL_PROGRAM_BINARIES, sizeof(binary), &binary, NULL);
184     if (cl_error != CL_SUCCESS)
185     {
186         GMX_THROW(InternalError("Could not get OpenCL program binary, error was " + ocl_get_error_string(cl_error)));
187     }
188
189     FILE *f = fopen(filename.c_str(), "wb");
190     const unique_cptr<FILE, fclose_wrapper> fileGuard(f);
191     if (!f)
192     {
193         GMX_THROW(FileIOError("Failed to open binary cache file " + filename));
194     }
195
196     fwrite(binary, 1, fileSize, f);
197 }
198
199 } // namespace
200 } // namespace