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/exception/program_build_failure.hpp>
27 #include <boost/compute/detail/assert_cl_success.hpp>
28
29 #ifdef BOOST_COMPUTE_USE_OFFLINE_CACHE
30 #include <sstream>
31 #include <boost/optional.hpp>
32 #include <boost/compute/platform.hpp>
33 #include <boost/compute/detail/getenv.hpp>
34 #include <boost/compute/detail/path.hpp>
35 #include <boost/compute/detail/sha1.hpp>
36 #endif
37
38 namespace boost {
39 namespace compute {
40
platform(cl_platform_id id)41 class kernel;
42
43 /// \class program
44 /// \brief A compute program.
45 ///
46 /// The program class represents an OpenCL program.
platform(const platform & other)47 ///
48 /// Program objects are created with one of the static \c create_with_*
49 /// functions. For example, to create a program from a source string:
50 ///
51 /// \snippet test/test_program.cpp create_with_source
52 ///
53 /// And to create a program from a source file:
54 /// \code
55 /// boost::compute::program bar_program =
56 /// boost::compute::program::create_with_source_file("/path/to/bar.cl", context);
57 /// \endcode
58 ///
59 /// Once a program object has been successfully created, it can be compiled
60 /// using the \c build() method:
61 /// \code
62 /// // build the program
63 /// foo_program.build();
64 /// \endcode
65 ///
66 /// Once the program is built, \ref kernel objects can be created using the
67 /// \c create_kernel() method by passing their name:
68 /// \code
69 /// // create a kernel from the compiled program
70 /// boost::compute::kernel foo_kernel = foo_program.create_kernel("foo");
71 /// \endcode
72 ///
73 /// \see kernel
74 class program
75 {
76 public:
77 /// Creates a null program object.
78 program()
79 : m_program(0)
80 {
81 }
82
83 /// Creates a program object for \p program. If \p retain is \c true,
84 /// the reference count for \p program will be incremented.
85 explicit program(cl_program program, bool retain = true)
86 : m_program(program)
87 {
88 if(m_program && retain){
89 clRetainProgram(m_program);
90 }
91 }
92
93 /// Creates a new program object as a copy of \p other.
94 program(const program &other)
95 : m_program(other.m_program)
96 {
97 if(m_program){
98 clRetainProgram(m_program);
99 }
100 }
101
102 /// Copies the program object from \p other to \c *this.
103 program& operator=(const program &other)
104 {
105 if(this != &other){
106 if(m_program){
107 clReleaseProgram(m_program);
108 }
109
110 m_program = other.m_program;
111
112 if(m_program){
113 clRetainProgram(m_program);
114 }
115 }
116
117 return *this;
118 }
119
120 #ifndef BOOST_COMPUTE_NO_RVALUE_REFERENCES
121 /// Move-constructs a new program object from \p other.
122 program(program&& other) BOOST_NOEXCEPT
123 : m_program(other.m_program)
124 {
125 other.m_program = 0;
126 }
127
128 /// Move-assigns the program from \p other to \c *this.
129 program& operator=(program&& other) BOOST_NOEXCEPT
130 {
131 if(m_program){
132 clReleaseProgram(m_program);
133 }
134
135 m_program = other.m_program;
136 other.m_program = 0;
137
138 return *this;
139 }
140 #endif // BOOST_COMPUTE_NO_RVALUE_REFERENCES
141
142 /// Destroys the program object.
143 ~program()
144 {
145 if(m_program){
146 BOOST_COMPUTE_ASSERT_CL_SUCCESS(
147 clReleaseProgram(m_program)
148 );
149 }
150 }
151
152 /// Returns the underlying OpenCL program.
153 cl_program& get() const
154 {
155 return const_cast<cl_program &>(m_program);
156 }
157
158 /// Returns the source code for the program.
159 std::string source() const
160 {
161 return get_info<std::string>(CL_PROGRAM_SOURCE);
162 }
163
164 /// Returns the binary for the program.
165 std::vector<unsigned char> binary() const
166 {
167 size_t binary_size = get_info<size_t>(CL_PROGRAM_BINARY_SIZES);
168 std::vector<unsigned char> binary(binary_size);
169
170 unsigned char *binary_ptr = &binary[0];
171 cl_int error = clGetProgramInfo(m_program,
172 CL_PROGRAM_BINARIES,
173 sizeof(unsigned char **),
174 &binary_ptr,
175 0);
176 if(error != CL_SUCCESS){
177 BOOST_THROW_EXCEPTION(opencl_error(error));
178 }
179
180 return binary;
181 }
182
183 #if defined(BOOST_COMPUTE_CL_VERSION_2_1) || defined(BOOST_COMPUTE_DOXYGEN_INVOKED)
184 /// Returns the SPIR-V binary for the program.
185 std::vector<unsigned char> il_binary() const
186 {
187 return get_info<std::vector<unsigned char> >(CL_PROGRAM_IL);
188 }
189 #endif // BOOST_COMPUTE_CL_VERSION_2_1
190
191 std::vector<device> get_devices() const
192 {
193 std::vector<cl_device_id> device_ids =
194 get_info<std::vector<cl_device_id> >(CL_PROGRAM_DEVICES);
195
196 std::vector<device> devices;
197 for(size_t i = 0; i < device_ids.size(); i++){
198 devices.push_back(device(device_ids[i]));
199 }
200
201 return devices;
202 }
203
204 /// Returns the context for the program.
205 context get_context() const
206 {
207 return context(get_info<cl_context>(CL_PROGRAM_CONTEXT));
208 }
209
210 /// Returns information about the program.
211 ///
212 /// \see_opencl_ref{clGetProgramInfo}
213 template<class T>
214 T get_info(cl_program_info info) const
215 {
216 return detail::get_object_info<T>(clGetProgramInfo, m_program, info);
217 }
218
219 /// \overload
220 template<int Enum>
221 typename detail::get_object_info_type<program, Enum>::type
222 get_info() const;
223
224 /// Returns build information about the program.
225 ///
226 /// For example, this function can be used to retreive the options used
227 /// to build the program:
228 /// \code
229 /// std::string build_options =
230 /// program.get_build_info<std::string>(CL_PROGRAM_BUILD_OPTIONS);
231 /// \endcode
232 ///
233 /// \see_opencl_ref{clGetProgramInfo}
234 template<class T>
235 T get_build_info(cl_program_build_info info, const device &device) const
236 {
237 return detail::get_object_info<T>(clGetProgramBuildInfo, m_program, info, device.id());
238 }
239
240 /// Builds the program with \p options.
241 ///
242 /// If the program fails to compile, this function will throw an
243 /// opencl_error exception.
244 /// \code
245 /// try {
246 /// // attempt to compile to program
247 /// program.build();
248 /// }
249 /// catch(boost::compute::opencl_error &e){
250 /// // program failed to compile, print out the build log
251 /// std::cout << program.build_log() << std::endl;
252 /// }
253 /// \endcode
254 ///
255 /// \see_opencl_ref{clBuildProgram}
256 void build(const std::string &options = std::string())
257 {
258 const char *options_string = 0;
259
260 if(!options.empty()){
261 options_string = options.c_str();
262 }
263
264 cl_int ret = clBuildProgram(m_program, 0, 0, options_string, 0, 0);
265
266 #ifdef BOOST_COMPUTE_DEBUG_KERNEL_COMPILATION
267 if(ret != CL_SUCCESS){
268 // print the error, source code and build log
269 std::cerr << "Boost.Compute: "
270 << "kernel compilation failed (" << ret << ")\n"
271 << "--- source ---\n"
272 << source()
273 << "\n--- build log ---\n"
274 << build_log()
275 << std::endl;
276 }
277 #endif
278
279 if(ret != CL_SUCCESS){
280 BOOST_THROW_EXCEPTION(program_build_failure(ret, build_log()));
281 }
282 }
283
284 #if defined(BOOST_COMPUTE_CL_VERSION_1_2) || defined(BOOST_COMPUTE_DOXYGEN_INVOKED)
285 /// Compiles the program with \p options.
286 ///
287 /// \opencl_version_warning{1,2}
288 ///
289 /// \see_opencl_ref{clCompileProgram}
290 void compile(const std::string &options = std::string(),
291 const std::vector<std::pair<std::string, program> > &headers =
292 std::vector<std::pair<std::string, program> >())
293 {
294 const char *options_string = 0;
295
296 if(!options.empty()){
297 options_string = options.c_str();
298 }
299
300 cl_int ret;
301 if (headers.empty())
302 {
303 ret = clCompileProgram(
304 m_program, 0, 0, options_string, 0, 0, 0, 0, 0
305 );
306 }
307 else
308 {
309 std::vector<const char*> header_names(headers.size());
310 std::vector<cl_program> header_programs(headers.size());
311 for (size_t i = 0; i < headers.size(); ++i)
312 {
313 header_names[i] = headers[i].first.c_str();
314 header_programs[i] = headers[i].second.m_program;
315 }
316
317 ret = clCompileProgram(
318 m_program,
319 0,
320 0,
321 options_string,
322 static_cast<cl_uint>(headers.size()),
323 header_programs.data(),
324 header_names.data(),
325 0,
326 0
327 );
328 }
329
330 if(ret != CL_SUCCESS){
331 BOOST_THROW_EXCEPTION(opencl_error(ret));
332 }
333 }
334
335 /// Links the programs in \p programs with \p options in \p context.
336 ///
337 /// \opencl_version_warning{1,2}
338 ///
339 /// \see_opencl_ref{clLinkProgram}
340 static program link(const std::vector<program> &programs,
341 const context &context,
342 const std::string &options = std::string())
343 {
344 const char *options_string = 0;
345
346 if(!options.empty()){
347 options_string = options.c_str();
348 }
349
350 cl_int ret;
351 cl_program program_ = clLinkProgram(
352 context.get(),
353 0,
354 0,
355 options_string,
356 static_cast<uint_>(programs.size()),
357 reinterpret_cast<const cl_program*>(&programs[0]),
358 0,
359 0,
360 &ret
361 );
362
363 if(!program_){
364 BOOST_THROW_EXCEPTION(opencl_error(ret));
365 }
366
367 return program(program_, false);
368 }
369 #endif // BOOST_COMPUTE_CL_VERSION_1_2
370
371 /// Returns the build log.
372 std::string build_log() const
373 {
374 return get_build_info<std::string>(CL_PROGRAM_BUILD_LOG, get_devices().front());
375 }
376
377 /// Creates and returns a new kernel object for \p name.
378 ///
379 /// For example, to create the \c "foo" kernel (after the program has been
380 /// created and built):
381 /// \code
382 /// boost::compute::kernel foo_kernel = foo_program.create_kernel("foo");
383 /// \endcode
384 kernel create_kernel(const std::string &name) const;
385
386 /// Returns \c true if the program is the same at \p other.
387 bool operator==(const program &other) const
388 {
389 return m_program == other.m_program;
390 }
391
392 /// Returns \c true if the program is different from \p other.
393 bool operator!=(const program &other) const
394 {
395 return m_program != other.m_program;
396 }
397
398 /// \internal_
399 operator cl_program() const
400 {
401 return m_program;
402 }
403
404 /// Creates a new program with \p source in \p context.
405 ///
406 /// \see_opencl_ref{clCreateProgramWithSource}
407 static program create_with_source(const std::string &source,
408 const context &context)
409 {
410 const char *source_string = source.c_str();
411
412 cl_int error = 0;
413 cl_program program_ = clCreateProgramWithSource(context,
414 uint_(1),
415 &source_string,
416 0,
417 &error);
418 if(!program_){
419 BOOST_THROW_EXCEPTION(opencl_error(error));
420 }
421
422 return program(program_, false);
423 }
424
425 /// Creates a new program with \p sources in \p context.
426 ///
427 /// \see_opencl_ref{clCreateProgramWithSource}
428 static program create_with_source(const std::vector<std::string> &sources,
429 const context &context)
430 {
431 std::vector<const char*> source_strings(sources.size());
432 for(size_t i = 0; i < sources.size(); i++){
433 source_strings[i] = sources[i].c_str();
434 }
435
436 cl_int error = 0;
437 cl_program program_ = clCreateProgramWithSource(context,
438 uint_(sources.size()),
439 &source_strings[0],
440 0,
441 &error);
442 if(!program_){
443 BOOST_THROW_EXCEPTION(opencl_error(error));
444 }
445
446 return program(program_, false);
447 }
448
449 /// Creates a new program with \p file in \p context.
450 ///
451 /// \see_opencl_ref{clCreateProgramWithSource}
452 static program create_with_source_file(const std::string &file,
453 const context &context)
454 {
455 // create program
456 return create_with_source(read_source_file(file), context);
457 }
458
459 /// Creates a new program with \p files in \p context.
460 ///
461 /// \see_opencl_ref{clCreateProgramWithSource}
462 static program create_with_source_file(const std::vector<std::string> &files,
463 const context &context)
464 {
465 std::vector<std::string> sources(files.size());
466
467 for(size_t i = 0; i < files.size(); ++i) {
468 // open file stream
469 std::ifstream stream(files[i].c_str());
470
471 if(stream.fail()){
472 BOOST_THROW_EXCEPTION(std::ios_base::failure("failed to create stream."));
473 }
474
475 // read source
476 sources[i] = std::string(
477 (std::istreambuf_iterator<char>(stream)),
478 std::istreambuf_iterator<char>()
479 );
480 }
481
482 // create program
483 return create_with_source(sources, context);
484 }
485
486 /// Creates a new program with \p binary of \p binary_size in
487 /// \p context.
488 ///
489 /// \see_opencl_ref{clCreateProgramWithBinary}
490 static program create_with_binary(const unsigned char *binary,
491 size_t binary_size,
492 const context &context)
493 {
494 const cl_device_id device = context.get_device().id();
495
496 cl_int error = 0;
497 cl_int binary_status = 0;
498 cl_program program_ = clCreateProgramWithBinary(context,
499 uint_(1),
500 &device,
501 &binary_size,
502 &binary,
503 &binary_status,
504 &error);
505 if(!program_){
506 BOOST_THROW_EXCEPTION(opencl_error(error));
507 }
508 if(binary_status != CL_SUCCESS){
509 BOOST_THROW_EXCEPTION(opencl_error(binary_status));
510 }
511
512 return program(program_, false);
513 }
514
515 /// Creates a new program with \p binary in \p context.
516 ///
517 /// \see_opencl_ref{clCreateProgramWithBinary}
518 static program create_with_binary(const std::vector<unsigned char> &binary,
519 const context &context)
520 {
521 return create_with_binary(&binary[0], binary.size(), context);
522 }
523
524 /// Creates a new program with \p file in \p context.
525 ///
526 /// \see_opencl_ref{clCreateProgramWithBinary}
527 static program create_with_binary_file(const std::string &file,
528 const context &context)
529 {
530 // open file stream
531 std::ifstream stream(file.c_str(), std::ios::in | std::ios::binary);
532
533 // read binary
534 std::vector<unsigned char> binary(
535 (std::istreambuf_iterator<char>(stream)),
536 std::istreambuf_iterator<char>()
537 );
538
539 // create program
540 return create_with_binary(&binary[0], binary.size(), context);
541 }
542
543 #if defined(BOOST_COMPUTE_CL_VERSION_1_2) || defined(BOOST_COMPUTE_DOXYGEN_INVOKED)
544 /// Creates a new program with the built-in kernels listed in
545 /// \p kernel_names for \p devices in \p context.
546 ///
547 /// \opencl_version_warning{1,2}
548 ///
549 /// \see_opencl_ref{clCreateProgramWithBuiltInKernels}
550 static program create_with_builtin_kernels(const context &context,
551 const std::vector<device> &devices,
552 const std::string &kernel_names)
553 {
554 cl_int error = 0;
555
556 cl_program program_ = clCreateProgramWithBuiltInKernels(
557 context.get(),
558 static_cast<uint_>(devices.size()),
559 reinterpret_cast<const cl_device_id *>(&devices[0]),
560 kernel_names.c_str(),
561 &error
562 );
563
564 if(!program_){
565 BOOST_THROW_EXCEPTION(opencl_error(error));
566 }
567
568 return program(program_, false);
569 }
570 #endif // BOOST_COMPUTE_CL_VERSION_1_2
571
572 #if defined(BOOST_COMPUTE_CL_VERSION_2_1) || defined(BOOST_COMPUTE_DOXYGEN_INVOKED)
573 /// Creates a new program with \p il_binary (SPIR-V binary)
574 /// of \p il_size size in \p context.
575 ///
576 /// \opencl_version_warning{2,1}
577 ///
578 /// \see_opencl21_ref{clCreateProgramWithIL}
579 static program create_with_il(const void * il_binary,
580 const size_t il_size,
581 const context &context)
582 {
583 cl_int error = 0;
584
585 cl_program program_ = clCreateProgramWithIL(
586 context.get(), il_binary, il_size, &error
587 );
588
589 if(!program_){
590 BOOST_THROW_EXCEPTION(opencl_error(error));
591 }
592
593 return program(program_, false);
594 }
595
596 /// Creates a new program with \p il_binary (SPIR-V binary)
597 /// in \p context.
598 ///
599 /// \opencl_version_warning{2,1}
600 ///
601 /// \see_opencl_ref{clCreateProgramWithIL}
602 static program create_with_il(const std::vector<unsigned char> &il_binary,
603 const context &context)
604 {
605 return create_with_il(&il_binary[0], il_binary.size(), context);
606 }
607
608 /// Creates a new program in \p context using SPIR-V
609 /// binary \p file.
610 ///
611 /// \opencl_version_warning{2,1}
612 ///
613 /// \see_opencl_ref{clCreateProgramWithIL}
614 static program create_with_il_file(const std::string &file,
615 const context &context)
616 {
617 // open file stream
618 std::ifstream stream(file.c_str(), std::ios::in | std::ios::binary);
619
620 // read binary
621 std::vector<unsigned char> il(
622 (std::istreambuf_iterator<char>(stream)),
623 std::istreambuf_iterator<char>()
624 );
625
626 // create program
627 return create_with_il(&il[0], il.size(), context);
628 }
629 #endif // BOOST_COMPUTE_CL_VERSION_2_1
630
631 /// Create a new program with \p source in \p context and builds it with \p options.
632 /**
633 * In case BOOST_COMPUTE_USE_OFFLINE_CACHE macro is defined,
634 * the compiled binary is stored for reuse in the offline cache located in
635 * $HOME/.boost_compute on UNIX-like systems and in %APPDATA%/boost_compute
636 * on Windows.
637 */
638 static program build_with_source(
639 const std::string &source,
640 const context &context,
641 const std::string &options = std::string()
642 )
643 {
644 #ifdef BOOST_COMPUTE_USE_OFFLINE_CACHE
645 // Get hash string for the kernel.
646 device d = context.get_device();
647 platform p = d.platform();
648
649 detail::sha1 hash;
650 hash.process( p.name() )
651 .process( p.version() )
652 .process( d.name() )
653 .process( options )
654 .process( source )
655 ;
656 std::string hash_string = hash;
657
658 // Try to get cached program binaries:
659 try {
660 boost::optional<program> prog = load_program_binary(hash_string, context);
661
662 if (prog) {
663 prog->build(options);
664 return *prog;
665 }
666 } catch (...) {
667 // Something bad happened. Fallback to normal compilation.
668 }
669
670 // Cache is apparently not available. Just compile the sources.
671 #endif
672 const char *source_string = source.c_str();
673
674 cl_int error = 0;
675 cl_program program_ = clCreateProgramWithSource(context,
676 uint_(1),
677 &source_string,
678 0,
679 &error);
680 if(!program_){
681 BOOST_THROW_EXCEPTION(opencl_error(error));
682 }
683
684 program prog(program_, false);
685 prog.build(options);
686
687 #ifdef BOOST_COMPUTE_USE_OFFLINE_CACHE
688 // Save program binaries for future reuse.
689 save_program_binary(hash_string, prog);
690 #endif
691
692 return prog;
693 }
694
695 /// Create a new program with \p file in \p context and builds it with \p options.
696 /**
697 * In case BOOST_COMPUTE_USE_OFFLINE_CACHE macro is defined,
698 * the compiled binary is stored for reuse in the offline cache located in
699 * $HOME/.boost_compute on UNIX-like systems and in %APPDATA%/boost_compute
700 * on Windows.
701 */
702 static program build_with_source_file(
703 const std::string &file,
704 const context &context,
705 const std::string &options = std::string()
706 )
707 {
708 return build_with_source(read_source_file(file), context, options);
709 }
710
711 private:
712 #ifdef BOOST_COMPUTE_USE_OFFLINE_CACHE
713 // Saves program binaries for future reuse.
714 static void save_program_binary(const std::string &hash, const program &prog)
715 {
716 std::string fname = detail::program_binary_path(hash, true) + "kernel";
717 std::ofstream bfile(fname.c_str(), std::ios::binary);
718 if (!bfile) return;
719
720 std::vector<unsigned char> binary = prog.binary();
721
722 size_t binary_size = binary.size();
723 bfile.write((char*)&binary_size, sizeof(size_t));
724 bfile.write((char*)binary.data(), binary_size);
725 }
726
727 // Tries to read program binaries from file cache.
728 static boost::optional<program> load_program_binary(
729 const std::string &hash, const context &ctx
730 )
731 {
732 std::string fname = detail::program_binary_path(hash) + "kernel";
733 std::ifstream bfile(fname.c_str(), std::ios::binary);
734 if (!bfile) return boost::optional<program>();
735
736 size_t binary_size;
737 std::vector<unsigned char> binary;
738
739 bfile.read((char*)&binary_size, sizeof(size_t));
740
741 binary.resize(binary_size);
742 bfile.read((char*)binary.data(), binary_size);
743
744 return boost::optional<program>(
745 program::create_with_binary(
746 binary.data(), binary_size, ctx
747 )
748 );
749 }
750 #endif // BOOST_COMPUTE_USE_OFFLINE_CACHE
751
752 static std::string read_source_file(const std::string &file)
753 {
754 // open file stream
755 std::ifstream stream(file.c_str());
756
757 if(stream.fail()){
758 BOOST_THROW_EXCEPTION(std::ios_base::failure("failed to create stream."));
759 }
760
761 // read source
762 return std::string(
763 (std::istreambuf_iterator<char>(stream)),
764 std::istreambuf_iterator<char>()
765 );
766 }
767
768 private:
769 cl_program m_program;
770 };
771
772 /// \internal_ define get_info() specializations for program
773 BOOST_COMPUTE_DETAIL_DEFINE_GET_INFO_SPECIALIZATIONS(program,
774 ((cl_uint, CL_PROGRAM_REFERENCE_COUNT))
775 ((cl_context, CL_PROGRAM_CONTEXT))
776 ((cl_uint, CL_PROGRAM_NUM_DEVICES))
777 ((std::vector<cl_device_id>, CL_PROGRAM_DEVICES))
778 ((std::string, CL_PROGRAM_SOURCE))
779 ((std::vector<size_t>, CL_PROGRAM_BINARY_SIZES))
780 ((std::vector<unsigned char *>, CL_PROGRAM_BINARIES))
781 )
782
783 #ifdef BOOST_COMPUTE_CL_VERSION_1_2
784 BOOST_COMPUTE_DETAIL_DEFINE_GET_INFO_SPECIALIZATIONS(program,
785 ((size_t, CL_PROGRAM_NUM_KERNELS))
786 ((std::string, CL_PROGRAM_KERNEL_NAMES))
787 )
788 #endif // BOOST_COMPUTE_CL_VERSION_1_2
789
790 #ifdef BOOST_COMPUTE_CL_VERSION_2_1
791 BOOST_COMPUTE_DETAIL_DEFINE_GET_INFO_SPECIALIZATIONS(program,
792 ((std::vector<unsigned char>, CL_PROGRAM_IL))
793 )
794 #endif // BOOST_COMPUTE_CL_VERSION_2_1
795
796 } // end compute namespace
797 } // end boost namespace
798
799 #endif // BOOST_COMPUTE_PROGRAM_HPP
800