1//
2// Copyright 2012 Francisco Jerez
3//
4// Permission is hereby granted, free of charge, to any person obtaining a
5// copy of this software and associated documentation files (the "Software"),
6// to deal in the Software without restriction, including without limitation
7// the rights to use, copy, modify, merge, publish, distribute, sublicense,
8// and/or sell copies of the Software, and to permit persons to whom the
9// Software is furnished to do so, subject to the following conditions:
10//
11// The above copyright notice and this permission notice shall be included in
12// all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
18// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20// OTHER DEALINGS IN THE SOFTWARE.
21//
22
23#include "api/util.hpp"
24#include "core/program.hpp"
25#include "core/platform.hpp"
26#include "spirv/invocation.hpp"
27#include "util/u_debug.h"
28
29#include <limits>
30#include <sstream>
31
32using namespace clover;
33
34namespace {
35
36   std::string
37   build_options(const char *p_opts, const char *p_debug) {
38      auto opts = std::string(p_opts ? p_opts : "");
39      std::string extra_opts = debug_get_option(p_debug, "");
40
41      return detokenize(std::vector<std::string>{opts, extra_opts}, " ");
42   }
43
44   class build_notifier {
45   public:
46      build_notifier(cl_program prog,
47                     void (*notifer)(cl_program, void *), void *data) :
48                     prog_(prog), notifer(notifer), data_(data) { }
49
50      ~build_notifier() {
51         if (notifer)
52            notifer(prog_, data_);
53      }
54
55   private:
56      cl_program prog_;
57      void (*notifer)(cl_program, void *);
58      void *data_;
59   };
60
61   void
62   validate_build_common(const program &prog, cl_uint num_devs,
63                         const cl_device_id *d_devs,
64                         void (*pfn_notify)(cl_program, void *),
65                         void *user_data) {
66      if (!pfn_notify && user_data)
67         throw error(CL_INVALID_VALUE);
68
69      if (prog.kernel_ref_count())
70         throw error(CL_INVALID_OPERATION);
71
72      if (any_of([&](const device &dev) {
73               return !count(dev, prog.devices());
74            }, objs<allow_empty_tag>(d_devs, num_devs)))
75         throw error(CL_INVALID_DEVICE);
76   }
77
78   enum program::il_type
79   identify_and_validate_il(const std::string &il,
80                            const cl_version opencl_version,
81                            const context::notify_action &notify) {
82
83      enum program::il_type il_type = program::il_type::none;
84
85#ifdef HAVE_CLOVER_SPIRV
86      if (spirv::is_binary_spirv(il)) {
87         std::string log;
88         if (!spirv::is_valid_spirv(il, opencl_version, log)) {
89            if (notify) {
90               notify(log.c_str());
91            }
92            throw error(CL_INVALID_VALUE);
93         }
94         il_type = program::il_type::spirv;
95      }
96#endif
97
98      return il_type;
99   }
100}
101
102CLOVER_API cl_program
103clCreateProgramWithSource(cl_context d_ctx, cl_uint count,
104                          const char **strings, const size_t *lengths,
105                          cl_int *r_errcode) try {
106   auto &ctx = obj(d_ctx);
107   std::string source;
108
109   if (!count || !strings ||
110       any_of(is_zero(), range(strings, count)))
111      throw error(CL_INVALID_VALUE);
112
113   // Concatenate all the provided fragments together
114   for (unsigned i = 0; i < count; ++i)
115         source += (lengths && lengths[i] ?
116                    std::string(strings[i], strings[i] + lengths[i]) :
117                    std::string(strings[i]));
118
119   // ...and create a program object for them.
120   ret_error(r_errcode, CL_SUCCESS);
121   return new program(ctx, std::move(source), program::il_type::source);
122
123} catch (error &e) {
124   ret_error(r_errcode, e);
125   return NULL;
126}
127
128CLOVER_API cl_program
129clCreateProgramWithBinary(cl_context d_ctx, cl_uint n,
130                          const cl_device_id *d_devs,
131                          const size_t *lengths,
132                          const unsigned char **binaries,
133                          cl_int *r_status, cl_int *r_errcode) try {
134   auto &ctx = obj(d_ctx);
135   auto devs = objs(d_devs, n);
136
137   if (!lengths || !binaries)
138      throw error(CL_INVALID_VALUE);
139
140   if (any_of([&](const device &dev) {
141            return !count(dev, ctx.devices());
142         }, devs))
143      throw error(CL_INVALID_DEVICE);
144
145   // Deserialize the provided binaries,
146   std::vector<std::pair<cl_int, binary>> result = map(
147      [](const unsigned char *p, size_t l) -> std::pair<cl_int, binary> {
148         if (!p || !l)
149            return { CL_INVALID_VALUE, {} };
150
151         try {
152            std::stringbuf bin( std::string{ (char*)p, l } );
153            std::istream s(&bin);
154
155            return { CL_SUCCESS, binary::deserialize(s) };
156
157         } catch (std::istream::failure &) {
158            return { CL_INVALID_BINARY, {} };
159         }
160      },
161      range(binaries, n),
162      range(lengths, n));
163
164   // update the status array,
165   if (r_status)
166      copy(map(keys(), result), r_status);
167
168   if (any_of(key_equals(CL_INVALID_VALUE), result))
169      throw error(CL_INVALID_VALUE);
170
171   if (any_of(key_equals(CL_INVALID_BINARY), result))
172      throw error(CL_INVALID_BINARY);
173
174   // initialize a program object with them.
175   ret_error(r_errcode, CL_SUCCESS);
176   return new program(ctx, devs, map(values(), result));
177
178} catch (error &e) {
179   ret_error(r_errcode, e);
180   return NULL;
181}
182
183cl_program
184clover::CreateProgramWithILKHR(cl_context d_ctx, const void *il,
185                               size_t length, cl_int *r_errcode) try {
186   auto &ctx = obj(d_ctx);
187
188   if (!il || !length)
189      throw error(CL_INVALID_VALUE);
190
191   // Compute the highest OpenCL version supported by all devices associated to
192   // the context. That is the version used for validating the SPIR-V binary.
193   cl_version min_opencl_version = std::numeric_limits<uint32_t>::max();
194   for (const device &dev : ctx.devices()) {
195      const cl_version opencl_version = dev.device_version();
196      min_opencl_version = std::min(opencl_version, min_opencl_version);
197   }
198
199   const char *stream = reinterpret_cast<const char *>(il);
200   std::string binary(stream, stream + length);
201   const enum program::il_type il_type = identify_and_validate_il(binary,
202                                                                  min_opencl_version,
203                                                                  ctx.notify);
204
205   if (il_type == program::il_type::none)
206      throw error(CL_INVALID_VALUE);
207
208   // Initialize a program object with it.
209   ret_error(r_errcode, CL_SUCCESS);
210   return new program(ctx, std::move(binary), il_type);
211
212} catch (error &e) {
213   ret_error(r_errcode, e);
214   return NULL;
215}
216
217CLOVER_API cl_program
218clCreateProgramWithIL(cl_context d_ctx,
219                      const void *il,
220                      size_t length,
221                      cl_int *r_errcode) {
222   return CreateProgramWithILKHR(d_ctx, il, length, r_errcode);
223}
224
225CLOVER_API cl_program
226clCreateProgramWithBuiltInKernels(cl_context d_ctx, cl_uint n,
227                                  const cl_device_id *d_devs,
228                                  const char *kernel_names,
229                                  cl_int *r_errcode) try {
230   auto &ctx = obj(d_ctx);
231   auto devs = objs(d_devs, n);
232
233   if (any_of([&](const device &dev) {
234            return !count(dev, ctx.devices());
235         }, devs))
236      throw error(CL_INVALID_DEVICE);
237
238   // No currently supported built-in kernels.
239   throw error(CL_INVALID_VALUE);
240
241} catch (error &e) {
242   ret_error(r_errcode, e);
243   return NULL;
244}
245
246
247CLOVER_API cl_int
248clRetainProgram(cl_program d_prog) try {
249   obj(d_prog).retain();
250   return CL_SUCCESS;
251
252} catch (error &e) {
253   return e.get();
254}
255
256CLOVER_API cl_int
257clReleaseProgram(cl_program d_prog) try {
258   if (obj(d_prog).release())
259      delete pobj(d_prog);
260
261   return CL_SUCCESS;
262
263} catch (error &e) {
264   return e.get();
265}
266
267CLOVER_API cl_int
268clBuildProgram(cl_program d_prog, cl_uint num_devs,
269               const cl_device_id *d_devs, const char *p_opts,
270               void (*pfn_notify)(cl_program, void *),
271               void *user_data) try {
272   auto &prog = obj(d_prog);
273   auto devs =
274      (d_devs ? objs(d_devs, num_devs) : ref_vector<device>(prog.devices()));
275   const auto opts = build_options(p_opts, "CLOVER_EXTRA_BUILD_OPTIONS");
276
277   validate_build_common(prog, num_devs, d_devs, pfn_notify, user_data);
278
279   auto notifier = build_notifier(d_prog, pfn_notify, user_data);
280
281   if (prog.il_type() != program::il_type::none) {
282      prog.compile(devs, opts);
283      prog.link(devs, opts, { prog });
284   } else if (any_of([&](const device &dev){
285         return prog.build(dev).binary_type() != CL_PROGRAM_BINARY_TYPE_EXECUTABLE;
286         }, devs)) {
287      // According to the OpenCL 1.2 specification, “if program is created
288      // with clCreateProgramWithBinary, then the program binary must be an
289      // executable binary (not a compiled binary or library).”
290      throw error(CL_INVALID_BINARY);
291   }
292
293   return CL_SUCCESS;
294
295} catch (error &e) {
296   return e.get();
297}
298
299CLOVER_API cl_int
300clCompileProgram(cl_program d_prog, cl_uint num_devs,
301                 const cl_device_id *d_devs, const char *p_opts,
302                 cl_uint num_headers, const cl_program *d_header_progs,
303                 const char **header_names,
304                 void (*pfn_notify)(cl_program, void *),
305                 void *user_data) try {
306   auto &prog = obj(d_prog);
307   auto devs =
308       (d_devs ? objs(d_devs, num_devs) : ref_vector<device>(prog.devices()));
309   const auto opts = build_options(p_opts, "CLOVER_EXTRA_COMPILE_OPTIONS");
310   header_map headers;
311
312   validate_build_common(prog, num_devs, d_devs, pfn_notify, user_data);
313
314   auto notifier = build_notifier(d_prog, pfn_notify, user_data);
315
316   if (bool(num_headers) != bool(header_names))
317      throw error(CL_INVALID_VALUE);
318
319   if (prog.il_type() == program::il_type::none)
320      throw error(CL_INVALID_OPERATION);
321
322   for_each([&](const char *name, const program &header) {
323         if (header.il_type() == program::il_type::none)
324            throw error(CL_INVALID_OPERATION);
325
326         if (!any_of(key_equals(name), headers))
327            headers.push_back(std::pair<std::string, std::string>(
328                                 name, header.source()));
329      },
330      range(header_names, num_headers),
331      objs<allow_empty_tag>(d_header_progs, num_headers));
332
333   prog.compile(devs, opts, headers);
334   return CL_SUCCESS;
335
336} catch (invalid_build_options_error &) {
337   return CL_INVALID_COMPILER_OPTIONS;
338
339} catch (build_error &) {
340   return CL_COMPILE_PROGRAM_FAILURE;
341
342} catch (error &e) {
343   return e.get();
344}
345
346namespace {
347   ref_vector<device>
348   validate_link_devices(const ref_vector<program> &progs,
349                         const ref_vector<device> &all_devs,
350                         const std::string &opts) {
351      std::vector<device *> devs;
352      const bool create_library =
353         opts.find("-create-library") != std::string::npos;
354      const bool enable_link_options =
355         opts.find("-enable-link-options") != std::string::npos;
356      const bool has_link_options =
357         opts.find("-cl-denorms-are-zero") != std::string::npos ||
358         opts.find("-cl-no-signed-zeroes") != std::string::npos ||
359         opts.find("-cl-unsafe-math-optimizations") != std::string::npos ||
360         opts.find("-cl-finite-math-only") != std::string::npos ||
361         opts.find("-cl-fast-relaxed-math") != std::string::npos ||
362         opts.find("-cl-no-subgroup-ifp") != std::string::npos;
363
364      // According to the OpenCL 1.2 specification, "[the
365      // -enable-link-options] option must be specified with the
366      // create-library option".
367      if (enable_link_options && !create_library)
368         throw error(CL_INVALID_LINKER_OPTIONS);
369
370      // According to the OpenCL 1.2 specification, "the
371      // [program linking options] can be specified when linking a program
372      // executable".
373      if (has_link_options && create_library)
374         throw error(CL_INVALID_LINKER_OPTIONS);
375
376      for (auto &dev : all_devs) {
377         const auto has_binary = [&](const program &prog) {
378            const auto t = prog.build(dev).binary_type();
379            return t == CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT ||
380                   t == CL_PROGRAM_BINARY_TYPE_LIBRARY;
381         };
382
383         // According to the OpenCL 1.2 specification, a library is made of
384         // “compiled binaries specified in input_programs argument to
385         // clLinkProgram“; compiled binaries does not refer to libraries:
386         // “input_programs is an array of program objects that are compiled
387         // binaries or libraries that are to be linked to create the program
388         // executable”.
389         if (create_library && any_of([&](const program &prog) {
390                  const auto t = prog.build(dev).binary_type();
391                  return t != CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT;
392               }, progs))
393            throw error(CL_INVALID_OPERATION);
394
395         // According to the CL 1.2 spec, when "all programs specified [..]
396         // contain a compiled binary or library for the device [..] a link is
397         // performed",
398         else if (all_of(has_binary, progs))
399            devs.push_back(&dev);
400
401         // otherwise if "none of the programs contain a compiled binary or
402         // library for that device [..] no link is performed.  All other
403         // cases will return a CL_INVALID_OPERATION error."
404         else if (any_of(has_binary, progs))
405            throw error(CL_INVALID_OPERATION);
406
407         // According to the OpenCL 1.2 specification, "[t]he linker may apply
408         // [program linking options] to all compiled program objects
409         // specified to clLinkProgram. The linker may apply these options
410         // only to libraries which were created with the
411         // -enable-link-option."
412         else if (has_link_options && any_of([&](const program &prog) {
413                  const auto t = prog.build(dev).binary_type();
414                  return !(t == CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT ||
415                          (t == CL_PROGRAM_BINARY_TYPE_LIBRARY &&
416                           prog.build(dev).opts.find("-enable-link-options") !=
417                              std::string::npos));
418               }, progs))
419            throw error(CL_INVALID_LINKER_OPTIONS);
420      }
421
422      return map(derefs(), devs);
423   }
424}
425
426CLOVER_API cl_program
427clLinkProgram(cl_context d_ctx, cl_uint num_devs, const cl_device_id *d_devs,
428              const char *p_opts, cl_uint num_progs, const cl_program *d_progs,
429              void (*pfn_notify) (cl_program, void *), void *user_data,
430              cl_int *r_errcode) try {
431   auto &ctx = obj(d_ctx);
432   const auto opts = build_options(p_opts, "CLOVER_EXTRA_LINK_OPTIONS");
433   auto progs = objs(d_progs, num_progs);
434   auto all_devs =
435      (d_devs ? objs(d_devs, num_devs) : ref_vector<device>(ctx.devices()));
436   auto prog = create<program>(ctx, all_devs);
437   auto r_prog = ret_object(prog);
438
439   auto notifier = build_notifier(r_prog, pfn_notify, user_data);
440
441   auto devs = validate_link_devices(progs, all_devs, opts);
442
443   validate_build_common(prog, num_devs, d_devs, pfn_notify, user_data);
444
445   try {
446      prog().link(devs, opts, progs);
447      ret_error(r_errcode, CL_SUCCESS);
448
449   } catch (build_error &) {
450      ret_error(r_errcode, CL_LINK_PROGRAM_FAILURE);
451   }
452
453   return r_prog;
454
455} catch (invalid_build_options_error &) {
456   ret_error(r_errcode, CL_INVALID_LINKER_OPTIONS);
457   return NULL;
458
459} catch (error &e) {
460   ret_error(r_errcode, e);
461   return NULL;
462}
463
464CLOVER_API cl_int
465clUnloadCompiler() {
466   return CL_SUCCESS;
467}
468
469CLOVER_API cl_int
470clUnloadPlatformCompiler(cl_platform_id d_platform) try {
471   find_platform(d_platform);
472   return CL_SUCCESS;
473} catch (error &e) {
474   return e.get();
475}
476
477CLOVER_API cl_int
478clGetProgramInfo(cl_program d_prog, cl_program_info param,
479                 size_t size, void *r_buf, size_t *r_size) try {
480   property_buffer buf { r_buf, size, r_size };
481   auto &prog = obj(d_prog);
482
483   switch (param) {
484   case CL_PROGRAM_REFERENCE_COUNT:
485      buf.as_scalar<cl_uint>() = prog.ref_count();
486      break;
487
488   case CL_PROGRAM_CONTEXT:
489      buf.as_scalar<cl_context>() = desc(prog.context());
490      break;
491
492   case CL_PROGRAM_NUM_DEVICES:
493      buf.as_scalar<cl_uint>() = (prog.devices().size() ?
494                                  prog.devices().size() :
495                                  prog.context().devices().size());
496      break;
497
498   case CL_PROGRAM_DEVICES:
499      buf.as_vector<cl_device_id>() = (prog.devices().size() ?
500                                       descs(prog.devices()) :
501                                       descs(prog.context().devices()));
502      break;
503
504   case CL_PROGRAM_SOURCE:
505      buf.as_string() = prog.source();
506      break;
507
508   case CL_PROGRAM_BINARY_SIZES:
509      buf.as_vector<size_t>() = map([&](const device &dev) {
510            return prog.build(dev).bin.size();
511         },
512         prog.devices());
513      break;
514
515   case CL_PROGRAM_BINARIES:
516      buf.as_matrix<unsigned char>() = map([&](const device &dev) {
517            std::stringbuf bin;
518            std::ostream s(&bin);
519            prog.build(dev).bin.serialize(s);
520            return bin.str();
521         },
522         prog.devices());
523      break;
524
525   case CL_PROGRAM_NUM_KERNELS:
526      buf.as_scalar<cl_uint>() = prog.symbols().size();
527      break;
528
529   case CL_PROGRAM_KERNEL_NAMES:
530      buf.as_string() = fold([](const std::string &a, const binary::symbol &s) {
531            return ((a.empty() ? "" : a + ";") + s.name);
532         }, std::string(), prog.symbols());
533      break;
534
535   case CL_PROGRAM_SCOPE_GLOBAL_CTORS_PRESENT:
536   case CL_PROGRAM_SCOPE_GLOBAL_DTORS_PRESENT:
537      buf.as_scalar<cl_bool>() = CL_FALSE;
538      break;
539
540   case CL_PROGRAM_IL:
541      if (prog.il_type() == program::il_type::spirv)
542         buf.as_vector<char>() = prog.source();
543      else if (r_size)
544         *r_size = 0u;
545      break;
546   default:
547      throw error(CL_INVALID_VALUE);
548   }
549
550   return CL_SUCCESS;
551
552} catch (error &e) {
553   return e.get();
554}
555
556CLOVER_API cl_int
557clGetProgramBuildInfo(cl_program d_prog, cl_device_id d_dev,
558                      cl_program_build_info param,
559                      size_t size, void *r_buf, size_t *r_size) try {
560   property_buffer buf { r_buf, size, r_size };
561   auto &prog = obj(d_prog);
562   auto &dev = obj(d_dev);
563
564   if (!count(dev, prog.context().devices()))
565      return CL_INVALID_DEVICE;
566
567   switch (param) {
568   case CL_PROGRAM_BUILD_STATUS:
569      buf.as_scalar<cl_build_status>() = prog.build(dev).status();
570      break;
571
572   case CL_PROGRAM_BUILD_OPTIONS:
573      buf.as_string() = prog.build(dev).opts;
574      break;
575
576   case CL_PROGRAM_BUILD_LOG:
577      buf.as_string() = prog.build(dev).log;
578      break;
579
580   case CL_PROGRAM_BINARY_TYPE:
581      buf.as_scalar<cl_program_binary_type>() = prog.build(dev).binary_type();
582      break;
583
584   case CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE:
585      buf.as_scalar<size_t>() = 0;
586      break;
587
588   default:
589      throw error(CL_INVALID_VALUE);
590   }
591
592   return CL_SUCCESS;
593
594} catch (error &e) {
595   return e.get();
596}
597