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 "util/u_debug.h"
26
27#include <sstream>
28
29using namespace clover;
30
31namespace {
32   void
33   validate_build_common(const program &prog, cl_uint num_devs,
34                         const cl_device_id *d_devs,
35                         void (*pfn_notify)(cl_program, void *),
36                         void *user_data) {
37      if (!pfn_notify && user_data)
38         throw error(CL_INVALID_VALUE);
39
40      if (prog.kernel_ref_count())
41         throw error(CL_INVALID_OPERATION);
42
43      if (any_of([&](const device &dev) {
44               return !count(dev, prog.devices());
45            }, objs<allow_empty_tag>(d_devs, num_devs)))
46         throw error(CL_INVALID_DEVICE);
47   }
48}
49
50CLOVER_API cl_program
51clCreateProgramWithSource(cl_context d_ctx, cl_uint count,
52                          const char **strings, const size_t *lengths,
53                          cl_int *r_errcode) try {
54   auto &ctx = obj(d_ctx);
55   std::string source;
56
57   if (!count || !strings ||
58       any_of(is_zero(), range(strings, count)))
59      throw error(CL_INVALID_VALUE);
60
61   // Concatenate all the provided fragments together
62   for (unsigned i = 0; i < count; ++i)
63         source += (lengths && lengths[i] ?
64                    std::string(strings[i], strings[i] + lengths[i]) :
65                    std::string(strings[i]));
66
67   // ...and create a program object for them.
68   ret_error(r_errcode, CL_SUCCESS);
69   return new program(ctx, source);
70
71} catch (error &e) {
72   ret_error(r_errcode, e);
73   return NULL;
74}
75
76CLOVER_API cl_program
77clCreateProgramWithBinary(cl_context d_ctx, cl_uint n,
78                          const cl_device_id *d_devs,
79                          const size_t *lengths,
80                          const unsigned char **binaries,
81                          cl_int *r_status, cl_int *r_errcode) try {
82   auto &ctx = obj(d_ctx);
83   auto devs = objs(d_devs, n);
84
85   if (!lengths || !binaries)
86      throw error(CL_INVALID_VALUE);
87
88   if (any_of([&](const device &dev) {
89            return !count(dev, ctx.devices());
90         }, devs))
91      throw error(CL_INVALID_DEVICE);
92
93   // Deserialize the provided binaries,
94   std::vector<std::pair<cl_int, module>> result = map(
95      [](const unsigned char *p, size_t l) -> std::pair<cl_int, module> {
96         if (!p || !l)
97            return { CL_INVALID_VALUE, {} };
98
99         try {
100            std::stringbuf bin( { (char*)p, l } );
101            std::istream s(&bin);
102
103            return { CL_SUCCESS, module::deserialize(s) };
104
105         } catch (std::istream::failure &e) {
106            return { CL_INVALID_BINARY, {} };
107         }
108      },
109      range(binaries, n),
110      range(lengths, n));
111
112   // update the status array,
113   if (r_status)
114      copy(map(keys(), result), r_status);
115
116   if (any_of(key_equals(CL_INVALID_VALUE), result))
117      throw error(CL_INVALID_VALUE);
118
119   if (any_of(key_equals(CL_INVALID_BINARY), result))
120      throw error(CL_INVALID_BINARY);
121
122   // initialize a program object with them.
123   ret_error(r_errcode, CL_SUCCESS);
124   return new program(ctx, devs, map(values(), result));
125
126} catch (error &e) {
127   ret_error(r_errcode, e);
128   return NULL;
129}
130
131CLOVER_API cl_program
132clCreateProgramWithBuiltInKernels(cl_context d_ctx, cl_uint n,
133                                  const cl_device_id *d_devs,
134                                  const char *kernel_names,
135                                  cl_int *r_errcode) try {
136   auto &ctx = obj(d_ctx);
137   auto devs = objs(d_devs, n);
138
139   if (any_of([&](const device &dev) {
140            return !count(dev, ctx.devices());
141         }, devs))
142      throw error(CL_INVALID_DEVICE);
143
144   // No currently supported built-in kernels.
145   throw error(CL_INVALID_VALUE);
146
147} catch (error &e) {
148   ret_error(r_errcode, e);
149   return NULL;
150}
151
152
153CLOVER_API cl_int
154clRetainProgram(cl_program d_prog) try {
155   obj(d_prog).retain();
156   return CL_SUCCESS;
157
158} catch (error &e) {
159   return e.get();
160}
161
162CLOVER_API cl_int
163clReleaseProgram(cl_program d_prog) try {
164   if (obj(d_prog).release())
165      delete pobj(d_prog);
166
167   return CL_SUCCESS;
168
169} catch (error &e) {
170   return e.get();
171}
172
173CLOVER_API cl_int
174clBuildProgram(cl_program d_prog, cl_uint num_devs,
175               const cl_device_id *d_devs, const char *p_opts,
176               void (*pfn_notify)(cl_program, void *),
177               void *user_data) try {
178   auto &prog = obj(d_prog);
179   auto devs =
180      (d_devs ? objs(d_devs, num_devs) : ref_vector<device>(prog.devices()));
181   const auto opts = std::string(p_opts ? p_opts : "") + " " +
182                     debug_get_option("CLOVER_EXTRA_BUILD_OPTIONS", "");
183
184   validate_build_common(prog, num_devs, d_devs, pfn_notify, user_data);
185
186   if (prog.has_source) {
187      prog.compile(devs, opts);
188      prog.link(devs, opts, { prog });
189   } else if (any_of([&](const device &dev){
190         return prog.build(dev).binary_type() != CL_PROGRAM_BINARY_TYPE_EXECUTABLE;
191         }, devs)) {
192      // According to the OpenCL 1.2 specification, “if program is created
193      // with clCreateProgramWithBinary, then the program binary must be an
194      // executable binary (not a compiled binary or library).”
195      throw error(CL_INVALID_BINARY);
196   }
197
198   return CL_SUCCESS;
199
200} catch (error &e) {
201   return e.get();
202}
203
204CLOVER_API cl_int
205clCompileProgram(cl_program d_prog, cl_uint num_devs,
206                 const cl_device_id *d_devs, const char *p_opts,
207                 cl_uint num_headers, const cl_program *d_header_progs,
208                 const char **header_names,
209                 void (*pfn_notify)(cl_program, void *),
210                 void *user_data) try {
211   auto &prog = obj(d_prog);
212   auto devs =
213       (d_devs ? objs(d_devs, num_devs) : ref_vector<device>(prog.devices()));
214   const auto opts = std::string(p_opts ? p_opts : "") + " " +
215                     debug_get_option("CLOVER_EXTRA_COMPILE_OPTIONS", "");
216   header_map headers;
217
218   validate_build_common(prog, num_devs, d_devs, pfn_notify, user_data);
219
220   if (bool(num_headers) != bool(header_names))
221      throw error(CL_INVALID_VALUE);
222
223   if (!prog.has_source)
224      throw error(CL_INVALID_OPERATION);
225
226   for_each([&](const char *name, const program &header) {
227         if (!header.has_source)
228            throw error(CL_INVALID_OPERATION);
229
230         if (!any_of(key_equals(name), headers))
231            headers.push_back(std::pair<std::string, std::string>(
232                                 name, header.source()));
233      },
234      range(header_names, num_headers),
235      objs<allow_empty_tag>(d_header_progs, num_headers));
236
237   prog.compile(devs, opts, headers);
238   return CL_SUCCESS;
239
240} catch (invalid_build_options_error &e) {
241   return CL_INVALID_COMPILER_OPTIONS;
242
243} catch (build_error &e) {
244   return CL_COMPILE_PROGRAM_FAILURE;
245
246} catch (error &e) {
247   return e.get();
248}
249
250namespace {
251   ref_vector<device>
252   validate_link_devices(const ref_vector<program> &progs,
253                         const ref_vector<device> &all_devs,
254                         const std::string &opts) {
255      std::vector<device *> devs;
256      const bool create_library =
257         opts.find("-create-library") != std::string::npos;
258      const bool enable_link_options =
259         opts.find("-enable-link-options") != std::string::npos;
260      const bool has_link_options =
261         opts.find("-cl-denorms-are-zero") != std::string::npos ||
262         opts.find("-cl-no-signed-zeroes") != std::string::npos ||
263         opts.find("-cl-unsafe-math-optimizations") != std::string::npos ||
264         opts.find("-cl-finite-math-only") != std::string::npos ||
265         opts.find("-cl-fast-relaxed-math") != std::string::npos ||
266         opts.find("-cl-no-subgroup-ifp") != std::string::npos;
267
268      // According to the OpenCL 1.2 specification, "[the
269      // -enable-link-options] option must be specified with the
270      // create-library option".
271      if (enable_link_options && !create_library)
272         throw error(CL_INVALID_LINKER_OPTIONS);
273
274      // According to the OpenCL 1.2 specification, "the
275      // [program linking options] can be specified when linking a program
276      // executable".
277      if (has_link_options && create_library)
278         throw error(CL_INVALID_LINKER_OPTIONS);
279
280      for (auto &dev : all_devs) {
281         const auto has_binary = [&](const program &prog) {
282            const auto t = prog.build(dev).binary_type();
283            return t == CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT ||
284                   t == CL_PROGRAM_BINARY_TYPE_LIBRARY;
285         };
286
287         // According to the OpenCL 1.2 specification, a library is made of
288         // “compiled binaries specified in input_programs argument to
289         // clLinkProgram“; compiled binaries does not refer to libraries:
290         // “input_programs is an array of program objects that are compiled
291         // binaries or libraries that are to be linked to create the program
292         // executable”.
293         if (create_library && any_of([&](const program &prog) {
294                  const auto t = prog.build(dev).binary_type();
295                  return t != CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT;
296               }, progs))
297            throw error(CL_INVALID_OPERATION);
298
299         // According to the CL 1.2 spec, when "all programs specified [..]
300         // contain a compiled binary or library for the device [..] a link is
301         // performed",
302         else if (all_of(has_binary, progs))
303            devs.push_back(&dev);
304
305         // otherwise if "none of the programs contain a compiled binary or
306         // library for that device [..] no link is performed.  All other
307         // cases will return a CL_INVALID_OPERATION error."
308         else if (any_of(has_binary, progs))
309            throw error(CL_INVALID_OPERATION);
310
311         // According to the OpenCL 1.2 specification, "[t]he linker may apply
312         // [program linking options] to all compiled program objects
313         // specified to clLinkProgram. The linker may apply these options
314         // only to libraries which were created with the
315         // -enable-link-option."
316         else if (has_link_options && any_of([&](const program &prog) {
317                  const auto t = prog.build(dev).binary_type();
318                  return !(t == CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT ||
319                          (t == CL_PROGRAM_BINARY_TYPE_LIBRARY &&
320                           prog.build(dev).opts.find("-enable-link-options") !=
321                              std::string::npos));
322               }, progs))
323            throw error(CL_INVALID_LINKER_OPTIONS);
324      }
325
326      return map(derefs(), devs);
327   }
328}
329
330CLOVER_API cl_program
331clLinkProgram(cl_context d_ctx, cl_uint num_devs, const cl_device_id *d_devs,
332              const char *p_opts, cl_uint num_progs, const cl_program *d_progs,
333              void (*pfn_notify) (cl_program, void *), void *user_data,
334              cl_int *r_errcode) try {
335   auto &ctx = obj(d_ctx);
336   const auto opts = std::string(p_opts ? p_opts : "") + " " +
337                     debug_get_option("CLOVER_EXTRA_LINK_OPTIONS", "");
338   auto progs = objs(d_progs, num_progs);
339   auto all_devs =
340      (d_devs ? objs(d_devs, num_devs) : ref_vector<device>(ctx.devices()));
341   auto prog = create<program>(ctx, all_devs);
342   auto devs = validate_link_devices(progs, all_devs, opts);
343
344   validate_build_common(prog, num_devs, d_devs, pfn_notify, user_data);
345
346   try {
347      prog().link(devs, opts, progs);
348      ret_error(r_errcode, CL_SUCCESS);
349
350   } catch (build_error &e) {
351      ret_error(r_errcode, CL_LINK_PROGRAM_FAILURE);
352   }
353
354   return ret_object(prog);
355
356} catch (invalid_build_options_error &e) {
357   ret_error(r_errcode, CL_INVALID_LINKER_OPTIONS);
358   return NULL;
359
360} catch (error &e) {
361   ret_error(r_errcode, e);
362   return NULL;
363}
364
365CLOVER_API cl_int
366clUnloadCompiler() {
367   return CL_SUCCESS;
368}
369
370CLOVER_API cl_int
371clUnloadPlatformCompiler(cl_platform_id d_platform) {
372   return CL_SUCCESS;
373}
374
375CLOVER_API cl_int
376clGetProgramInfo(cl_program d_prog, cl_program_info param,
377                 size_t size, void *r_buf, size_t *r_size) try {
378   property_buffer buf { r_buf, size, r_size };
379   auto &prog = obj(d_prog);
380
381   switch (param) {
382   case CL_PROGRAM_REFERENCE_COUNT:
383      buf.as_scalar<cl_uint>() = prog.ref_count();
384      break;
385
386   case CL_PROGRAM_CONTEXT:
387      buf.as_scalar<cl_context>() = desc(prog.context());
388      break;
389
390   case CL_PROGRAM_NUM_DEVICES:
391      buf.as_scalar<cl_uint>() = (prog.devices().size() ?
392                                  prog.devices().size() :
393                                  prog.context().devices().size());
394      break;
395
396   case CL_PROGRAM_DEVICES:
397      buf.as_vector<cl_device_id>() = (prog.devices().size() ?
398                                       descs(prog.devices()) :
399                                       descs(prog.context().devices()));
400      break;
401
402   case CL_PROGRAM_SOURCE:
403      buf.as_string() = prog.source();
404      break;
405
406   case CL_PROGRAM_BINARY_SIZES:
407      buf.as_vector<size_t>() = map([&](const device &dev) {
408            return prog.build(dev).binary.size();
409         },
410         prog.devices());
411      break;
412
413   case CL_PROGRAM_BINARIES:
414      buf.as_matrix<unsigned char>() = map([&](const device &dev) {
415            std::stringbuf bin;
416            std::ostream s(&bin);
417            prog.build(dev).binary.serialize(s);
418            return bin.str();
419         },
420         prog.devices());
421      break;
422
423   case CL_PROGRAM_NUM_KERNELS:
424      buf.as_scalar<cl_uint>() = prog.symbols().size();
425      break;
426
427   case CL_PROGRAM_KERNEL_NAMES:
428      buf.as_string() = fold([](const std::string &a, const module::symbol &s) {
429            return ((a.empty() ? "" : a + ";") + s.name);
430         }, std::string(), prog.symbols());
431      break;
432
433   default:
434      throw error(CL_INVALID_VALUE);
435   }
436
437   return CL_SUCCESS;
438
439} catch (error &e) {
440   return e.get();
441}
442
443CLOVER_API cl_int
444clGetProgramBuildInfo(cl_program d_prog, cl_device_id d_dev,
445                      cl_program_build_info param,
446                      size_t size, void *r_buf, size_t *r_size) try {
447   property_buffer buf { r_buf, size, r_size };
448   auto &prog = obj(d_prog);
449   auto &dev = obj(d_dev);
450
451   if (!count(dev, prog.context().devices()))
452      return CL_INVALID_DEVICE;
453
454   switch (param) {
455   case CL_PROGRAM_BUILD_STATUS:
456      buf.as_scalar<cl_build_status>() = prog.build(dev).status();
457      break;
458
459   case CL_PROGRAM_BUILD_OPTIONS:
460      buf.as_string() = prog.build(dev).opts;
461      break;
462
463   case CL_PROGRAM_BUILD_LOG:
464      buf.as_string() = prog.build(dev).log;
465      break;
466
467   case CL_PROGRAM_BINARY_TYPE:
468      buf.as_scalar<cl_program_binary_type>() = prog.build(dev).binary_type();
469      break;
470
471   default:
472      throw error(CL_INVALID_VALUE);
473   }
474
475   return CL_SUCCESS;
476
477} catch (error &e) {
478   return e.get();
479}
480