1 //---------------------------------------------------------------------------//
2 // Copyright (c) 2013 Kyle Lutz <kyle.r.lutz@gmail.com>
3 //
4 // Distributed under the Boost Software License, Version 1.0
5 // See accompanying file LICENSE_1_0.txt or copy at
6 // http://www.boost.org/LICENSE_1_0.txt
7 //
8 // See http://boostorg.github.com/compute for more information.
9 //---------------------------------------------------------------------------//
10 
11 #ifndef BOOST_COMPUTE_PROGRAM_HPP
12 #define BOOST_COMPUTE_PROGRAM_HPP
13 
14 #include <string>
15 #include <vector>
16 #include <fstream>
17 #include <streambuf>
18 
19 #ifdef BOOST_COMPUTE_DEBUG_KERNEL_COMPILATION
20 #include <iostream>
21 #endif
22 
23 #include <boost/compute/config.hpp>
24 #include <boost/compute/context.hpp>
25 #include <boost/compute/exception.hpp>
26 #include <boost/compute/detail/assert_cl_success.hpp>
27 
28 #ifdef BOOST_COMPUTE_USE_OFFLINE_CACHE
29 #include <sstream>
30 #include <boost/optional.hpp>
31 #include <boost/compute/platform.hpp>
32 #include <boost/compute/detail/getenv.hpp>
33 #include <boost/compute/detail/path.hpp>
34 #include <boost/compute/detail/sha1.hpp>
35 #endif
36 
37 namespace boost {
38 namespace compute {
39 
40 class kernel;
41 
42 /// \class program
43 /// \brief A compute program.
44 ///
45 /// The program class represents an OpenCL program.
46 ///
47 /// Program objects are created with one of the static \c create_with_*
48 /// functions. For example, to create a program from a source string:
49 ///
50 /// \snippet test/test_program.cpp create_with_source
51 ///
52 /// And to create a program from a source file:
53 /// \code
54 /// boost::compute::program bar_program =
55 ///     boost::compute::program::create_with_source_file("/path/to/bar.cl", context);
56 /// \endcode
57 ///
58 /// Once a program object has been succesfully created, it can be compiled
59 /// using the \c build() method:
60 /// \code
61 /// // build the program
62 /// foo_program.build();
63 /// \endcode
64 ///
65 /// Once the program is built, \ref kernel objects can be created using the
66 /// \c create_kernel() method by passing their name:
67 /// \code
68 /// // create a kernel from the compiled program
69 /// boost::compute::kernel foo_kernel = foo_program.create_kernel("foo");
70 /// \endcode
71 ///
72 /// \see kernel
73 class program
74 {
75 public:
76     /// Creates a null program object.
program()77     program()
78         : m_program(0)
79     {
80     }
81 
82     /// Creates a program object for \p program. If \p retain is \c true,
83     /// the reference count for \p program will be incremented.
program(cl_program program,bool retain=true)84     explicit program(cl_program program, bool retain = true)
85         : m_program(program)
86     {
87         if(m_program && retain){
88             clRetainProgram(m_program);
89         }
90     }
91 
92     /// Creates a new program object as a copy of \p other.
program(const program & other)93     program(const program &other)
94         : m_program(other.m_program)
95     {
96         if(m_program){
97             clRetainProgram(m_program);
98         }
99     }
100 
101     /// Copies the program object from \p other to \c *this.
operator =(const program & other)102     program& operator=(const program &other)
103     {
104         if(this != &other){
105             if(m_program){
106                 clReleaseProgram(m_program);
107             }
108 
109             m_program = other.m_program;
110 
111             if(m_program){
112                 clRetainProgram(m_program);
113             }
114         }
115 
116         return *this;
117     }
118 
119     #ifndef BOOST_COMPUTE_NO_RVALUE_REFERENCES
120     /// Move-constructs a new program object from \p other.
program(program && other)121     program(program&& other) BOOST_NOEXCEPT
122         : m_program(other.m_program)
123     {
124         other.m_program = 0;
125     }
126 
127     /// Move-assigns the program from \p other to \c *this.
operator =(program && other)128     program& operator=(program&& other) BOOST_NOEXCEPT
129     {
130         if(m_program){
131             clReleaseProgram(m_program);
132         }
133 
134         m_program = other.m_program;
135         other.m_program = 0;
136 
137         return *this;
138     }
139     #endif // BOOST_COMPUTE_NO_RVALUE_REFERENCES
140 
141     /// Destroys the program object.
~program()142     ~program()
143     {
144         if(m_program){
145             BOOST_COMPUTE_ASSERT_CL_SUCCESS(
146                 clReleaseProgram(m_program)
147             );
148         }
149     }
150 
151     /// Returns the underlying OpenCL program.
get() const152     cl_program& get() const
153     {
154         return const_cast<cl_program &>(m_program);
155     }
156 
157     /// Returns the source code for the program.
source() const158     std::string source() const
159     {
160         return get_info<std::string>(CL_PROGRAM_SOURCE);
161     }
162 
163     /// Returns the binary for the program.
binary() const164     std::vector<unsigned char> binary() const
165     {
166         size_t binary_size = get_info<size_t>(CL_PROGRAM_BINARY_SIZES);
167         std::vector<unsigned char> binary(binary_size);
168 
169         unsigned char *binary_ptr = &binary[0];
170         cl_int error = clGetProgramInfo(m_program,
171                                         CL_PROGRAM_BINARIES,
172                                         sizeof(unsigned char **),
173                                         &binary_ptr,
174                                         0);
175         if(error != CL_SUCCESS){
176             BOOST_THROW_EXCEPTION(opencl_error(error));
177         }
178 
179         return binary;
180     }
181 
get_devices() const182     std::vector<device> get_devices() const
183     {
184         std::vector<cl_device_id> device_ids =
185             get_info<std::vector<cl_device_id> >(CL_PROGRAM_DEVICES);
186 
187         std::vector<device> devices;
188         for(size_t i = 0; i < device_ids.size(); i++){
189             devices.push_back(device(device_ids[i]));
190         }
191 
192         return devices;
193     }
194 
195     /// Returns the context for the program.
get_context() const196     context get_context() const
197     {
198         return context(get_info<cl_context>(CL_PROGRAM_CONTEXT));
199     }
200 
201     /// Returns information about the program.
202     ///
203     /// \see_opencl_ref{clGetProgramInfo}
204     template<class T>
get_info(cl_program_info info) const205     T get_info(cl_program_info info) const
206     {
207         return detail::get_object_info<T>(clGetProgramInfo, m_program, info);
208     }
209 
210     /// \overload
211     template<int Enum>
212     typename detail::get_object_info_type<program, Enum>::type
213     get_info() const;
214 
215     /// Returns build information about the program.
216     ///
217     /// For example, this function can be used to retreive the options used
218     /// to build the program:
219     /// \code
220     /// std::string build_options =
221     ///     program.get_build_info<std::string>(CL_PROGRAM_BUILD_OPTIONS);
222     /// \endcode
223     ///
224     /// \see_opencl_ref{clGetProgramInfo}
225     template<class T>
get_build_info(cl_program_build_info info,const device & device) const226     T get_build_info(cl_program_build_info info, const device &device) const
227     {
228         return detail::get_object_info<T>(clGetProgramBuildInfo, m_program, info, device.id());
229     }
230 
231     /// Builds the program with \p options.
232     ///
233     /// If the program fails to compile, this function will throw an
234     /// opencl_error exception.
235     /// \code
236     /// try {
237     ///     // attempt to compile to program
238     ///     program.build();
239     /// }
240     /// catch(boost::compute::opencl_error &e){
241     ///     // program failed to compile, print out the build log
242     ///     std::cout << program.build_log() << std::endl;
243     /// }
244     /// \endcode
245     ///
246     /// \see_opencl_ref{clBuildProgram}
build(const std::string & options=std::string ())247     void build(const std::string &options = std::string())
248     {
249         const char *options_string = 0;
250 
251         if(!options.empty()){
252             options_string = options.c_str();
253         }
254 
255         cl_int ret = clBuildProgram(m_program, 0, 0, options_string, 0, 0);
256 
257         #ifdef BOOST_COMPUTE_DEBUG_KERNEL_COMPILATION
258         if(ret != CL_SUCCESS){
259             // print the error, source code and build log
260             std::cerr << "Boost.Compute: "
261                       << "kernel compilation failed (" << ret << ")\n"
262                       << "--- source ---\n"
263                       << source()
264                       << "\n--- build log ---\n"
265                       << build_log()
266                       << std::endl;
267         }
268         #endif
269 
270         if(ret != CL_SUCCESS){
271             BOOST_THROW_EXCEPTION(opencl_error(ret));
272         }
273     }
274 
275     #if defined(CL_VERSION_1_2) || defined(BOOST_COMPUTE_DOXYGEN_INVOKED)
276     /// Compiles the program with \p options.
277     ///
278     /// \opencl_version_warning{1,2}
279     ///
280     /// \see_opencl_ref{clCompileProgram}
compile(const std::string & options=std::string ())281     void compile(const std::string &options = std::string())
282     {
283         const char *options_string = 0;
284 
285         if(!options.empty()){
286             options_string = options.c_str();
287         }
288 
289         cl_int ret = clCompileProgram(
290             m_program, 0, 0, options_string, 0, 0, 0, 0, 0
291         );
292 
293         if(ret != CL_SUCCESS){
294             BOOST_THROW_EXCEPTION(opencl_error(ret));
295         }
296     }
297 
298     /// Links the programs in \p programs with \p options in \p context.
299     ///
300     /// \opencl_version_warning{1,2}
301     ///
302     /// \see_opencl_ref{clLinkProgram}
link(const std::vector<program> & programs,const context & context,const std::string & options=std::string ())303     static program link(const std::vector<program> &programs,
304                         const context &context,
305                         const std::string &options = std::string())
306     {
307         const char *options_string = 0;
308 
309         if(!options.empty()){
310             options_string = options.c_str();
311         }
312 
313         cl_int ret;
314         cl_program program_ = clLinkProgram(
315             context.get(),
316             0,
317             0,
318             options_string,
319             static_cast<uint_>(programs.size()),
320             reinterpret_cast<const cl_program*>(&programs[0]),
321             0,
322             0,
323             &ret
324         );
325 
326         if(!program_){
327             BOOST_THROW_EXCEPTION(opencl_error(ret));
328         }
329 
330         return program(program_, false);
331     }
332     #endif // CL_VERSION_1_2
333 
334     /// Returns the build log.
build_log() const335     std::string build_log() const
336     {
337         return get_build_info<std::string>(CL_PROGRAM_BUILD_LOG, get_devices().front());
338     }
339 
340     /// Creates and returns a new kernel object for \p name.
341     ///
342     /// For example, to create the \c "foo" kernel (after the program has been
343     /// created and built):
344     /// \code
345     /// boost::compute::kernel foo_kernel = foo_program.create_kernel("foo");
346     /// \endcode
347     kernel create_kernel(const std::string &name) const;
348 
349     /// Returns \c true if the program is the same at \p other.
operator ==(const program & other) const350     bool operator==(const program &other) const
351     {
352         return m_program == other.m_program;
353     }
354 
355     /// Returns \c true if the program is different from \p other.
operator !=(const program & other) const356     bool operator!=(const program &other) const
357     {
358         return m_program != other.m_program;
359     }
360 
361     /// \internal_
operator cl_program() const362     operator cl_program() const
363     {
364         return m_program;
365     }
366 
367     /// Creates a new program with \p source in \p context.
368     ///
369     /// \see_opencl_ref{clCreateProgramWithSource}
create_with_source(const std::string & source,const context & context)370     static program create_with_source(const std::string &source,
371                                       const context &context)
372     {
373         const char *source_string = source.c_str();
374 
375         cl_int error = 0;
376         cl_program program_ = clCreateProgramWithSource(context,
377                                                         uint_(1),
378                                                         &source_string,
379                                                         0,
380                                                         &error);
381         if(!program_){
382             BOOST_THROW_EXCEPTION(opencl_error(error));
383         }
384 
385         return program(program_, false);
386     }
387 
388     /// Creates a new program with \p sources in \p context.
389     ///
390     /// \see_opencl_ref{clCreateProgramWithSource}
create_with_source(const std::vector<std::string> & sources,const context & context)391     static program create_with_source(const std::vector<std::string> &sources,
392                                       const context &context)
393     {
394         std::vector<const char*> source_strings(sources.size());
395         for(size_t i = 0; i < sources.size(); i++){
396             source_strings[i] = sources[i].c_str();
397         }
398 
399         cl_int error = 0;
400         cl_program program_ = clCreateProgramWithSource(context,
401                                                         uint_(sources.size()),
402                                                         &source_strings[0],
403                                                         0,
404                                                         &error);
405         if(!program_){
406             BOOST_THROW_EXCEPTION(opencl_error(error));
407         }
408 
409         return program(program_, false);
410     }
411 
412     /// Creates a new program with \p file in \p context.
413     ///
414     /// \see_opencl_ref{clCreateProgramWithSource}
create_with_source_file(const std::string & file,const context & context)415     static program create_with_source_file(const std::string &file,
416                                            const context &context)
417     {
418         // open file stream
419         std::ifstream stream(file.c_str());
420 
421         if(stream.fail()){
422           BOOST_THROW_EXCEPTION(std::ios_base::failure("failed to create stream."));
423         }
424 
425         // read source
426         std::string source(
427             (std::istreambuf_iterator<char>(stream)),
428             std::istreambuf_iterator<char>()
429         );
430 
431         // create program
432         return create_with_source(source, context);
433     }
434 
435     /// Creates a new program with \p binary of \p binary_size in
436     /// \p context.
437     ///
438     /// \see_opencl_ref{clCreateProgramWithBinary}
create_with_binary(const unsigned char * binary,size_t binary_size,const context & context)439     static program create_with_binary(const unsigned char *binary,
440                                       size_t binary_size,
441                                       const context &context)
442     {
443         const cl_device_id device = context.get_device().id();
444 
445         cl_int error = 0;
446         cl_int binary_status = 0;
447         cl_program program_ = clCreateProgramWithBinary(context,
448                                                         uint_(1),
449                                                         &device,
450                                                         &binary_size,
451                                                         &binary,
452                                                         &binary_status,
453                                                         &error);
454         if(!program_){
455             BOOST_THROW_EXCEPTION(opencl_error(error));
456         }
457         if(binary_status != CL_SUCCESS){
458             BOOST_THROW_EXCEPTION(opencl_error(binary_status));
459         }
460 
461         return program(program_, false);
462     }
463 
464     /// Creates a new program with \p binary in \p context.
465     ///
466     /// \see_opencl_ref{clCreateProgramWithBinary}
create_with_binary(const std::vector<unsigned char> & binary,const context & context)467     static program create_with_binary(const std::vector<unsigned char> &binary,
468                                       const context &context)
469     {
470         return create_with_binary(&binary[0], binary.size(), context);
471     }
472 
473     /// Creates a new program with \p file in \p context.
474     ///
475     /// \see_opencl_ref{clCreateProgramWithBinary}
create_with_binary_file(const std::string & file,const context & context)476     static program create_with_binary_file(const std::string &file,
477                                            const context &context)
478     {
479         // open file stream
480         std::ifstream stream(file.c_str(), std::ios::in | std::ios::binary);
481 
482         // read binary
483         std::vector<unsigned char> binary(
484             (std::istreambuf_iterator<char>(stream)),
485             std::istreambuf_iterator<char>()
486         );
487 
488         // create program
489         return create_with_binary(&binary[0], binary.size(), context);
490     }
491 
492     #if defined(CL_VERSION_1_2) || defined(BOOST_COMPUTE_DOXYGEN_INVOKED)
493     /// Creates a new program with the built-in kernels listed in
494     /// \p kernel_names for \p devices in \p context.
495     ///
496     /// \opencl_version_warning{1,2}
497     ///
498     /// \see_opencl_ref{clCreateProgramWithBuiltInKernels}
create_with_builtin_kernels(const context & context,const std::vector<device> & devices,const std::string & kernel_names)499     static program create_with_builtin_kernels(const context &context,
500                                                const std::vector<device> &devices,
501                                                const std::string &kernel_names)
502     {
503         cl_int error = 0;
504 
505         cl_program program_ = clCreateProgramWithBuiltInKernels(
506             context.get(),
507             static_cast<uint_>(devices.size()),
508             reinterpret_cast<const cl_device_id *>(&devices[0]),
509             kernel_names.c_str(),
510             &error
511         );
512 
513         if(!program_){
514             BOOST_THROW_EXCEPTION(opencl_error(error));
515         }
516 
517         return program(program_, false);
518     }
519     #endif // CL_VERSION_1_2
520 
521     /// Create a new program with \p source in \p context and builds it with \p options.
522     /**
523      * In case BOOST_COMPUTE_USE_OFFLINE_CACHE macro is defined,
524      * the compiled binary is stored for reuse in the offline cache located in
525      * $HOME/.boost_compute on UNIX-like systems and in %APPDATA%/boost_compute
526      * on Windows.
527      */
build_with_source(const std::string & source,const context & context,const std::string & options=std::string ())528     static program build_with_source(
529             const std::string &source,
530             const context     &context,
531             const std::string &options = std::string()
532             )
533     {
534 #ifdef BOOST_COMPUTE_USE_OFFLINE_CACHE
535         // Get hash string for the kernel.
536         device   d = context.get_device();
537         platform p = d.platform();
538 
539         detail::sha1 hash;
540         hash.process( p.name()    )
541             .process( p.version() )
542             .process( d.name()    )
543             .process( options     )
544             .process( source      )
545             ;
546 
547         // Try to get cached program binaries:
548         try {
549             boost::optional<program> prog = load_program_binary(hash, context);
550 
551             if (prog) {
552                 prog->build(options);
553                 return *prog;
554             }
555         } catch (...) {
556             // Something bad happened. Fallback to normal compilation.
557         }
558 
559         // Cache is apparently not available. Just compile the sources.
560 #endif
561         const char *source_string = source.c_str();
562 
563         cl_int error = 0;
564         cl_program program_ = clCreateProgramWithSource(context,
565                                                         uint_(1),
566                                                         &source_string,
567                                                         0,
568                                                         &error);
569         if(!program_){
570             BOOST_THROW_EXCEPTION(opencl_error(error));
571         }
572 
573         program prog(program_, false);
574         prog.build(options);
575 
576 #ifdef BOOST_COMPUTE_USE_OFFLINE_CACHE
577         // Save program binaries for future reuse.
578         save_program_binary(hash, prog);
579 #endif
580 
581         return prog;
582     }
583 
584 private:
585 #ifdef BOOST_COMPUTE_USE_OFFLINE_CACHE
586     // Saves program binaries for future reuse.
save_program_binary(const std::string & hash,const program & prog)587     static void save_program_binary(const std::string &hash, const program &prog)
588     {
589         std::string fname = detail::program_binary_path(hash, true) + "kernel";
590         std::ofstream bfile(fname.c_str(), std::ios::binary);
591         if (!bfile) return;
592 
593         std::vector<unsigned char> binary = prog.binary();
594 
595         size_t binary_size = binary.size();
596         bfile.write((char*)&binary_size, sizeof(size_t));
597         bfile.write((char*)binary.data(), binary_size);
598     }
599 
600     // Tries to read program binaries from file cache.
load_program_binary(const std::string & hash,const context & ctx)601     static boost::optional<program> load_program_binary(
602             const std::string &hash, const context &ctx
603             )
604     {
605         std::string fname = detail::program_binary_path(hash) + "kernel";
606         std::ifstream bfile(fname.c_str(), std::ios::binary);
607         if (!bfile) return boost::optional<program>();
608 
609         size_t binary_size;
610         std::vector<unsigned char> binary;
611 
612         bfile.read((char*)&binary_size, sizeof(size_t));
613 
614         binary.resize(binary_size);
615         bfile.read((char*)binary.data(), binary_size);
616 
617         return boost::optional<program>(
618                 program::create_with_binary(
619                     binary.data(), binary_size, ctx
620                     )
621                 );
622     }
623 #endif // BOOST_COMPUTE_USE_OFFLINE_CACHE
624 
625 private:
626     cl_program m_program;
627 };
628 
629 /// \internal_ define get_info() specializations for program
630 BOOST_COMPUTE_DETAIL_DEFINE_GET_INFO_SPECIALIZATIONS(program,
631     ((cl_uint, CL_PROGRAM_REFERENCE_COUNT))
632     ((cl_context, CL_PROGRAM_CONTEXT))
633     ((cl_uint, CL_PROGRAM_NUM_DEVICES))
634     ((std::vector<cl_device_id>, CL_PROGRAM_DEVICES))
635     ((std::string, CL_PROGRAM_SOURCE))
636     ((std::vector<size_t>, CL_PROGRAM_BINARY_SIZES))
637     ((std::vector<unsigned char *>, CL_PROGRAM_BINARIES))
638 )
639 
640 #ifdef CL_VERSION_1_2
641 BOOST_COMPUTE_DETAIL_DEFINE_GET_INFO_SPECIALIZATIONS(program,
642     ((size_t, CL_PROGRAM_NUM_KERNELS))
643     ((std::string, CL_PROGRAM_KERNEL_NAMES))
644 )
645 #endif // CL_VERSION_1_2
646 
647 } // end compute namespace
648 } // end boost namespace
649 
650 #endif // BOOST_COMPUTE_PROGRAM_HPP
651