assertOpenCL  September 19, 2018
example.c
Go to the documentation of this file.
1 /* -*- coding: latin-1 -*- */
2 /** \file examples/C/example.c
3  * \brief
4  * Simple C example to show how run a kernel with assert*() and PRINT*() macros
5  * and test them.
6  *
7  * Piece of assertOpenCL
8  * --- GPLv3 --- Copyright (C) 2018 Olivier Pirson
9  * --- http://www.opimedia.be/
10  * --- September 19, 2018
11  */
12 
13 // \cond
14 #include <assert.h>
15 #include <stdint.h>
16 #include <stdlib.h>
17 #include <stdbool.h>
18 #include <stdio.h>
19 #include <string.h>
20 
21 #include <CL/cl.h>
22 // \endcond
23 
24 
25 
26 /* ******
27  * Type *
28  ********/
29 typedef struct {
30  int32_t code;
31  const char* const name;
32 } error_name_t;
33 
34 
35 
36 /* ***********
37  * Constants *
38  *************/
39 
40 /** \brief
41  * List of all error codes and names extracted from /usr/include/CL/cl.h
42  */
43 const error_name_t errors_map[61] = {
44 #include "opencl_errors_map.h"
45 };
46 
47 
48 /** \brief
49  * Number of elements in errors_map.
50  */
51 const unsigned int errors_map_size = 61;
52 
53 
54 
55 /* ************
56  * Prototypes *
57  **************/
58 
59 /** \brief
60  * Return the directory part of path in a (new allocated) string.
61  */
62 char*
63 dirname_alloc(const char* path);
64 
65 
66 /** \brief
67  * Return the error name corresponding to the error code.
68  */
69 const char*
70 error_name(cl_int code);
71 
72 
73 /** \brief
74  * Read the file and return its content to a (new allocated) string.
75  * If failed then print a error message and exit.
76  */
77 char*
78 file_to_alloc_string(const char* filename);
79 
80 
81 /** \brief
82  * Return the given id device of the given platform OpenCL,
83  * or exit if doesn't exists.
84  *
85  * @param platform_i
86  * @param device_i
87  *
88  * @return OpenCL device id
89  */
90 cl_device_id
91 get_device_id(unsigned int platform_i, unsigned int device_i);
92 
93 
94 /** \brief
95  * Return a (new allocated) string corresponding to device info parameter.
96  */
97 char*
98 get_device_info_alloc_string(cl_device_id device_id, cl_device_info param_name);
99 
100 
101 /** \brief
102  * If code != 0
103  * then print an error message corresponding to the error code.
104  */
105 void
106 print_error(cl_int code, const char* message);
107 
108 
109 /** \brief
110  * Run the kernel ../kernel/example.cl.
111 
112  * If debug
113  * then run the kernel in debug mode,
114  * else run the kernel with the macro NDEBUG defined.
115  */
116 void
117 run_example(unsigned int nb_work_group, unsigned int nb_work_items_by_work_group,
118  cl_device_id device_id, bool debug, const char* path);
119 
120 
121 
122 /* ***********
123  * Functions *
124  *************/
125 
126 char*
127 dirname_alloc(const char* path) {
128  const char sep = '/';
129  const size_t size = strlen(path);
130 
131  if ((size == 0) || (path[size - 1] == sep)) { // path is empty or a directory name
132  char* part = (char*)malloc(sizeof(char) * (size + 1));
133 
134  strcpy(part, path);
135 
136  return part;
137  }
138  else { // path has the structure of a filename
139  bool found_sep = false;
140  size_t j = size;
141 
142  while (j > 0) { // search the last sep
143  --j;
144  if (path[j] == sep) {
145  found_sep = true;
146 
147  break;
148  }
149  }
150 
151  char* part = (char*)malloc(sizeof(char) * (j + 1));
152 
153  if (found_sep) {
154  strncpy(part, path, j + 1);
155  part[j + 1] = '\0';
156  }
157  else {
158  part[0] = '\0';
159  }
160 
161  return part;
162  }
163 }
164 
165 
166 const char*
167 error_name(cl_int code) {
168  for (unsigned int i = 0; i < errors_map_size; ++i) {
169  if (errors_map[i].code == code) {
170  return errors_map[i].name;
171  }
172  }
173 
174  return "unknow";
175 }
176 
177 
178 char*
179 file_to_alloc_string(const char* filename) {
180  const char* const first_prefix = "#line 1 \"";
181  const char* const first_suffix = "\"\n";
182  const size_t first_size = strlen(first_prefix) + strlen(filename) + strlen(first_suffix);
183 
184  FILE* const fin = fopen(filename, "rb");
185 
186  fseek(fin, 0l, SEEK_END);
187 
188  const size_t file_size = ftell(fin);
189  const size_t size = first_size + file_size;
190  char* const s = malloc(size + 1);
191 
192  sprintf(s, "%s%s%s", first_prefix, filename, first_suffix);
193 
194  rewind(fin);
195  if (fread((void*)(s + first_size), 1, size, fin) != file_size) {
196  fprintf(stderr, "! Impossible to read file \"%s\"\n", filename);
197 
198  exit(EXIT_FAILURE);
199  }
200  fclose(fin);
201 
202  s[size] = '\0';
203 
204  return s;
205 }
206 
207 
208 cl_device_id
209 get_device_id(unsigned int platform_i, unsigned int device_i) {
210  // Get number of platforms
211  cl_uint nb;
212  cl_int error = clGetPlatformIDs(0, NULL, &nb);
213 
214  print_error(error, "clGetPlatformIDs(..., &nb)");
215  if (platform_i >= nb) {
216  exit(EXIT_FAILURE);
217  }
218 
219  // Get all platform ids
220  cl_platform_id* platform_ids;
221 
222  platform_ids = (cl_platform_id*)malloc(sizeof(cl_platform_id) * nb);
223  error = clGetPlatformIDs(nb, platform_ids, NULL);
224  print_error(error, "clGetPlatformIDs(..., NULL)");
225 
226  // The wanted platform
227  const cl_platform_id platform_id = platform_ids[platform_i];
228 
229  free(platform_ids);
230 
231  // Get number of devices for the wanted platform
232  error = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_ALL, 0, NULL, &nb);
233  print_error(error, "clGetDeviceIDs(..., &nb)");
234  if (device_i >= nb) {
235  exit(EXIT_FAILURE);
236  }
237 
238  // Get all device ids for the wanted platform
239  cl_device_id* device_ids;
240 
241  device_ids = (cl_device_id*)malloc(sizeof(cl_device_id) * nb);
242  error = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_ALL, nb, device_ids, NULL);
243  print_error(error, "clGetDeviceIDs(..., NULL)");
244 
245  // The wanted device
246  const cl_device_id device_id = device_ids[device_i];
247 
248  free(device_ids);
249 
250  return device_id;
251 }
252 
253 
254 char*
255 get_device_info_alloc_string(cl_device_id device_id, cl_device_info param_name) {
256  char* result;
257 
258  // Get length of result
259  size_t size;
260  cl_int error = clGetDeviceInfo(device_id, param_name, 0, NULL, &size);
261 
262  print_error(error, "clGetDeviceInfo(..., &size)");
263 
264  // Get the wanted string
265  result = (char*)malloc(sizeof(char) * (size + 1));
266  error = clGetDeviceInfo(device_id, param_name, size, result, NULL);
267  print_error(error, "clGetDeviceInfo(..., NULL)");
268 
269  return result;
270 }
271 
272 
273 void
274 print_error(cl_int code, const char* message) {
275  if (code != 0) {
276  if (message == NULL) {
277  fprintf(stderr, "! OpenCL error %i %s\n", code, error_name(code));
278  }
279  else {
280  fprintf(stderr, "! OpenCL error %i %s %s\n", code, error_name(code), message);
281  }
282  fflush(stderr);
283  }
284 }
285 
286 
287 void
288 run_example(unsigned int nb_work_group, unsigned int nb_work_items_by_work_group,
289  cl_device_id device_id, bool debug, const char* path) {
290  // Host buffer
291  uint32_t h_outs[2];
292  const size_t h_outs_byte_size = sizeof(h_outs[0])*2;
293 
294  uint64_t h_asserts[2] = {0, 0};
295  const size_t h_asserts_byte_size = sizeof(h_asserts[0])*2;
296 
297  float h_assert_float[1] = {0};
298  const size_t h_assert_float_byte_size = sizeof(h_assert_float[0]);
299 
300  assert(sizeof(float) == 4);
301 
302 
303  // OpenCL context
304  cl_int error;
305  const cl_context context = clCreateContext(NULL, 1, &device_id, NULL, NULL, &error);
306 
307  print_error(error, "clCreateContext");
308 
309 
310  // OpenCL kernel
311  const char* const prefix = "-I";
312  const char* const suffix = "../../OpenCL/";
313  const char* const suffix_ndebug = " -DNDEBUG";
314  const size_t options_max_size =
315  strlen(prefix) + strlen(path) + strlen(suffix) + strlen(suffix_ndebug);
316  char* const options = malloc(sizeof(char) * (options_max_size + 1));
317 
318  sprintf(options, "%s%s%s", prefix, path, suffix);
319 
320  if (debug) {
321  fputs("OpenCL in DEBUG mode!\n", stderr);
322  fflush(stderr);
323  }
324  else { // transmits NDEBUG macro to kernel
325  strcat(options, suffix_ndebug);
326  }
327 
328  const char* const suffix_filename = "../kernel/example.cl";
329  char* const kernel_filename = malloc(sizeof(char)
330  * (options_max_size + strlen(suffix_filename) + 1));
331 
332  sprintf(kernel_filename, "%s%s", path, suffix_filename);
333 
334  char* const kernel_src = file_to_alloc_string(kernel_filename);
335 
336  const cl_program program = clCreateProgramWithSource(context, 1, (const char**)&kernel_src, NULL, &error);
337 
338  print_error(error, "clCreateProgramWithSource");
339  free(kernel_src);
340 
341  error = clBuildProgram(program, 1, &device_id, options, NULL, NULL);
342  print_error(error, "clBuildProgram");
343  free(options);
344 
345  const cl_kernel kernel = clCreateKernel(program, "example", &error);
346 
347  print_error(error, "clCreateKernel");
348 
349 
350  // OpenCL queue
351 #if __OPENCL_C_VERSION__ < 200 // CL_VERSION_2_0
352  cl_command_queue queue = clCreateCommandQueue(context, device_id, CL_QUEUE_PROFILING_ENABLE, &error);
353 #else
354  const cl_queue_properties properties[] = {CL_QUEUE_PROPERTIES, CL_QUEUE_PROFILING_ENABLE, 0};
355  cl_command_queue queue = clCreateCommandQueueWithProperties(context, device_id, properties, &error);
356 #endif
357 
358  print_error(error, "clCreateCommandQueue");
359 
360 
361  // OpenCL buffers
362  cl_mem d_outs = clCreateBuffer(context, CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR, h_outs_byte_size, h_outs, &error);
363 
364  print_error(error, "d_outs = clCreateBuffer");
365 
366  cl_mem d_asserts;
367  cl_mem d_assert_float;
368 
369  if (debug) {
370  d_asserts = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
371  h_asserts_byte_size, h_asserts, &error);
372  print_error(error, "d_asserts = clCreateBuffer");
373 
374  d_assert_float = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
375  h_assert_float_byte_size, h_assert_float, &error);
376  print_error(error, "d_assert_float = clCreateBuffer");
377  }
378 
379 
380  // Params: just two parameters for the example
381  const cl_uint n = 666;
382 
383  error = clSetKernelArg(kernel, 0, sizeof(n), &n);
384  print_error(error, "clSetKernelArg(..., 0, ...)");
385 
386  error = clSetKernelArg(kernel, 1, sizeof(d_outs), &d_outs);
387  print_error(error, "clSetKernelArg(..., 1, ...)");
388 
389  if (debug) { // extra parameters to receive assertion information
390  const unsigned int nb_args = 2;
391 
392  error = clSetKernelArg(kernel, nb_args, sizeof(d_asserts), &d_asserts);
393  print_error(error, "clSetKernelArg(..., d_asserts)");
394 
395  error = clSetKernelArg(kernel, nb_args + 1, sizeof(d_assert_float), &d_assert_float);
396  print_error(error, "clSetKernelArg(..., d_assert_float)");
397  }
398 
399 
400  // Run
401  const size_t global_size = nb_work_items_by_work_group * nb_work_group;
402  const size_t local_size = nb_work_items_by_work_group;
403 
404  puts("===== run kernel =====");
405  fflush(stdout);
406 
407  error = clEnqueueNDRangeKernel(queue,
408  kernel,
409  1,
410  NULL,
411  &global_size,
412  &local_size,
413  0,
414  NULL,
415  NULL);
416  print_error(error, "clEnqueueNDRangeKernel");
417 
418  clFinish(queue);
419  clFlush(queue);
420  fflush(stdout);
421  fflush(stderr);
422  puts("===== end kernel =====");
423  fflush(stdout);
424 
425 
426  // Results
427  error = clEnqueueReadBuffer(queue, d_outs, CL_TRUE, 0,
428  h_outs_byte_size, h_outs, 0, NULL, NULL);
429  print_error(error, "clEnqueueReadBuffer(..., d_outs, ...)");
430 
431  if (debug) {
432  error = clEnqueueReadBuffer(queue, d_asserts, CL_TRUE, 0,
433  h_asserts_byte_size, h_asserts, 0, NULL, NULL);
434  print_error(error, "clEnqueueReadBuffer(..., d_asserts, ...)");
435 
436  error = clEnqueueReadBuffer(queue, d_assert_float, CL_TRUE, 0,
437  h_assert_float_byte_size, h_assert_float, 0, NULL, NULL);
438  print_error(error, "clEnqueueReadBuffer(..., d_assert_float, ...)");
439 
440  const uint64_t line = h_asserts[0];
441 
442  if (line != 0) { // there had (at least) an assertion
443  const uint64_t uint64_value = h_asserts[1];
444  const int64_t sint64_value = (int64_t)h_asserts[1];
445  const float float_value = h_assert_float[0];
446 
447  fprintf(stderr, "%s:%lu\tAssertion failed | Maybe\t%lu\t%li | Maybe\t%f\n",
448  kernel_filename, line, uint64_value, sint64_value, float_value);
449  /*
450  Maybe incoherent assert information because the parallel execution of work items.
451  But each element of these information concern assertion(s) that failed.
452  */
453  fflush(stderr);
454  }
455  }
456 
457  printf("Results: (%u, %u)\n", h_outs[0], h_outs[1]);
458 
459  free(kernel_filename);
460 
461 
462  // Free OpenCL resources
463  clReleaseMemObject(d_outs);
464  if (debug) {
465  clReleaseMemObject(d_asserts);
466  clReleaseMemObject(d_assert_float);
467  }
468 
469  clReleaseCommandQueue(queue);
470  clReleaseProgram(program);
471  clReleaseKernel(kernel);
472  clReleaseContext(context);
473 }
474 
475 
476 
477 /* ******
478  * Main *
479  ********/
480 
481 /** \brief
482  * Get the optional parameter --device platform:device
483  * and run the kernel ../kernel/example.cl
484  */
485 int
486 main(int argc, const char* const argv[]) {
487  bool debug =
488 #ifdef NDEBUG
489  false;
490 #else
491  true;
492 #endif
493  signed int device_i = 0;
494  signed int platform_i = 0;
495 
496  // Read parameters
497  {
498  int i = 1;
499 
500  while (i < argc) {
501  const char* arg = argv[i];
502 
503  if ((strcmp(arg, "--debug") == 0) || (strcmp(arg, "--ndebug") == 0)) {
504  debug = (strcmp(arg, "--debug") == 0);
505  }
506  else if (strcmp(arg, "--device") == 0) {
507  ++i;
508  if (i >= argc) {
509  return EXIT_FAILURE;
510  }
511 
512  const char* both_i = argv[i];
513  const size_t len = strlen(both_i);
514  bool only_platform = true;
515  size_t j = 0;
516 
517  while (j < len) {
518  if (both_i[j] == ':') {
519  only_platform = false;
520 
521  break;
522  }
523  ++j;
524  }
525 
526  if (only_platform) {
527  platform_i = atoi(both_i);
528  }
529  else {
530  platform_i = atoi(both_i);
531  device_i = atoi(both_i + j + 1);
532  }
533 
534  if ((platform_i < 0) || (device_i < 0)) {
535  return EXIT_FAILURE;
536  }
537  }
538 
539  ++i;
540  }
541  }
542 
543  // Get wanted device
544  const cl_device_id device_id = get_device_id(platform_i, device_i);
545 
546  // Get device name and print
547  {
548  char* device_name = get_device_info_alloc_string(device_id, CL_DEVICE_NAME);
549 
550  printf("Device %u:%u %s\n", (unsigned int)platform_i, (unsigned int)device_i, device_name);
551  free(device_name);
552  fflush(stdout);
553  }
554 
555  // Get current path
556  char* path = dirname_alloc(argv[0]);
557 
558  // Run
559  run_example(3, 4, device_id, debug, path);
560 
561  free(path);
562 
563  return EXIT_SUCCESS;
564 }
char * file_to_alloc_string(const char *filename)
Read the file and return its content to a (new allocated) string. If failed then print a error messag...
Definition: example.c:179
List of all error codes and names extracted from /usr/include/CL/cl.h.
char * get_device_info_alloc_string(cl_device_id device_id, cl_device_info param_name)
Return a (new allocated) string corresponding to device info parameter.
Definition: example.c:255
const char *const name
Definition: example.c:31
cl_device_id get_device_id(unsigned int platform_i, unsigned int device_i)
Return the given id device of the given platform OpenCL, or exit if doesn&#39;t exists.
Definition: example.c:209
const char * error_name(cl_int code)
Return the error name corresponding to the error code.
Definition: example.c:167
void print_error(cl_int code, const char *message)
If code != 0 then print an error message corresponding to the error code.
Definition: example.c:274
char * dirname_alloc(const char *path)
Return the directory part of path in a (new allocated) string.
Definition: example.c:127
int main(int argc, const char *const argv[])
Get the optional parameter –device platform:device and run the kernel ../kernel/example.cl.
Definition: example.c:486
int32_t code
Definition: example.c:30
const error_name_t errors_map[61]
List of all error codes and names extracted from /usr/include/CL/cl.h.
Definition: example.c:43
#define assert(test)
If test is true then do nothing. Else init (if they are still null) assert_result and assert_result_f...
Definition: assert.cl:162
void run_example(unsigned int nb_work_group, unsigned int nb_work_items_by_work_group, cl_device_id device_id, bool debug, const char *path)
Run the kernel ../kernel/example.cl.
Definition: example.c:288
const unsigned int errors_map_size
Number of elements in errors_map.
Definition: example.c:51