assertOpenCL  September 19, 2018
example.cpp
Go to the documentation of this file.
1 /* -*- coding: latin-1 -*- */
2 /** \file examples/Cpp/example.cpp
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 <cassert>
15 #include <cstdint>
16 #include <cstdlib>
17 
18 #include <fstream>
19 #include <iostream>
20 #include <map>
21 #include <string>
22 
23 #include <CL/cl.hpp>
24 // \endcond
25 
26 
27 
28 /* **********
29  * Constant *
30  ************/
31 
32 /** \brief
33  * List of all error codes and names extracted from /usr/include/CL/cl.h
34  */
35 const std::map<int32_t, std::string> errors_map {
36 #include "opencl_errors_map.hpp"
37 };
38 
39 
40 
41 /* ************
42  * Prototypes *
43  **************/
44 
45 /** \brief
46  * Return the directory part of path.
47  */
48 std::string
49 dirname(std::string path);
50 
51 
52 /** \brief
53  * Return the error name corresponding to the error code.
54  */
55 std::string
56 error_name(cl_int code);
57 
58 
59 /** \brief
60  * Read the file and return its content to a string.
61  * If failed then print a error message and exit.
62  */
63 std::string
64 file_to_string(std::string filename);
65 
66 
67 /** \brief
68  * Return the given device of the given platform OpenCL,
69  * or exit if doesn't exists.
70  *
71  * @param platform_i
72  * @param device_i
73  *
74  * @return OpenCL device
75  */
76 cl::Device
77 get_device(unsigned int platform_i, unsigned int device_i);
78 
79 
80 /** \brief
81  * If code != 0
82  * then print an error message corresponding to the error code.
83  */
84 void
85 print_error(cl_int code, std::string message = "");
86 
87 
88 /** \brief
89  * Run the kernel ../kernel/example.cl.
90 
91  * If debug
92  * then run the kernel in debug mode,
93  * else run the kernel with the macro NDEBUG defined.
94  */
95 void
96 run_example(unsigned int nb_work_group, unsigned int nb_work_items_by_work_group,
97  cl::Device device, bool debug, const std::string& path);
98 
99 
100 
101 /* ***********
102  * Functions *
103  *************/
104 
105 std::string
106 error_name(cl_int code) {
107  const auto it = errors_map.find(code);
108 
109  return (it == errors_map.cend()
110  ? "unknow"
111  : it->second);
112 }
113 
114 
115 std::string
116 file_to_string(std::string filename) {
117  std::ifstream infile(filename, std::ios::in);
118  const std::string s((std::istreambuf_iterator<char>(infile)),
119  std::istreambuf_iterator<char>());
120 
121  infile.close();
122  if (infile.fail()) {
123  std::cerr << "! Impossible to read file \"" << filename << '"' << std::endl;
124 
125  exit(EXIT_FAILURE);
126  }
127 
128  return "#line 1 \"" + filename + "\"\n" + s;
129 }
130 
131 
132 cl::Device
133 get_device(unsigned int platform_i, unsigned int device_i) {
134  std::vector<cl::Platform> platforms;
135 
136  cl::Platform::get(&platforms);
137  if (platform_i >= platforms.size()) {
138  exit(EXIT_FAILURE);
139  }
140 
141  std::vector<cl::Device> devices;
142 
143  platforms[platform_i].getDevices(CL_DEVICE_TYPE_ALL, &devices);
144  if (device_i >= devices.size()) {
145  exit(EXIT_FAILURE);
146  }
147 
148  return devices[device_i];
149 }
150 
151 
152 std::string
153 dirname(std::string path) {
154  const char sep = '/';
155 
156  if (path.empty() || (path.back() == sep)) { // path is empty or a directory name
157  return path;
158  }
159  else { // path has the structure of a filename
160  const size_t j = path.rfind(sep);
161 
162  return (j == std::string::npos
163  ? ""
164  : path.substr(0, j + 1));
165  }
166 }
167 
168 
169 void
170 print_error(cl_int code, std::string message) {
171  if (code != 0) {
172  if (!message.empty()) {
173  message = " " + message;
174  }
175 
176  std::cerr << "! OpenCL error "
177  << code << ' ' << error_name(code)
178  << message
179  << std::endl;
180  std::cerr.flush();
181  }
182 }
183 
184 
185 void
186 run_example(unsigned int nb_work_group, unsigned int nb_work_items_by_work_group,
187  cl::Device device, bool debug, const std::string& path) {
188  // Host buffer
189  uint32_t h_outs[2];
190  const size_t h_outs_byte_size = sizeof(h_outs[0])*2;
191 
192  uint64_t h_asserts[2] = {0, 0};
193  const size_t h_asserts_byte_size = sizeof(h_asserts[0])*2;
194 
195  float h_assert_float[1] = {0};
196  const size_t h_assert_float_byte_size = sizeof(h_assert_float[0]);
197 
198  assert(sizeof(float) == 4);
199 
200 
201  // OpenCL context
202  const cl::Context context{device};
203 
205  // OpenCL kernel
206  std::string options = "-I" + path + "../../OpenCL/";
207 
208  if (debug) {
209  std::cerr << "OpenCL in DEBUG mode!" << std::endl;
210  std::cerr.flush();
211  }
212  else { // transmits NDEBUG macro to kernel
213  options += " -DNDEBUG";
214  }
215 
216  const std::string kernel_filename = path + "../kernel/example.cl";
217  const std::string kernel_src(file_to_string(kernel_filename));
218  const cl::Program program{context, kernel_src};
219  const VECTOR_CLASS<cl::Device> devices = {device};
220  cl_int error = program.build(devices, options.c_str());
221 
222  print_error(error, "clBuildProgram");
223 
224  cl::Kernel kernel{program, "example"};
225 
226 
227  // OpenCL queue
228  const cl::CommandQueue queue{context, device, CL_QUEUE_PROFILING_ENABLE};
229 
230 
231  // OpenCL buffers
232  cl::Buffer d_outs{context, CL_MEM_WRITE_ONLY, h_outs_byte_size};
233 
234  cl::Buffer d_asserts{context, CL_MEM_READ_WRITE, h_asserts_byte_size};
235  cl::Buffer d_assert_float{context, CL_MEM_READ_WRITE, h_assert_float_byte_size};
236 
237  if (debug) {
238  error = queue.enqueueWriteBuffer(d_asserts, CL_TRUE, 0, h_asserts_byte_size, h_asserts);
239  print_error(error, "clEnqueueWriteBuffer(d_asserts, ...)");
240 
241  error = queue.enqueueWriteBuffer(d_assert_float, CL_TRUE, 0, h_assert_float_byte_size, h_assert_float);
242  print_error(error, "clEnqueueWriteBuffer(d_assert_float, ...)");
243  }
244 
245 
246  // Params: just two parameters for the example
247  error = kernel.setArg(0, 666);
248  print_error(error, "clSetKernelArg(0, ...)");
249 
250  error = kernel.setArg(1, d_outs);
251  print_error(error, "clSetKernelArg(1, ...)");
252 
253  if (debug) { // extra parameters to receive assertion information
254  const unsigned int nb_args = 2;
255 
256  error = kernel.setArg(nb_args, d_asserts);
257  print_error(error, "clSetKernelArg(..., d_asserts)");
258 
259  error = kernel.setArg(nb_args + 1, d_assert_float);
260  print_error(error, "clSetKernelArg(..., d_assert_float)");
261  }
262 
263 
264  // Run
265  const unsigned int global_size = nb_work_items_by_work_group * nb_work_group;
266 
267  std::cout << "===== run kernel =====" << std::endl;
268  std::cout.flush();
269 
270  queue.enqueueNDRangeKernel(kernel,
271  cl::NullRange,
272  cl::NDRange(global_size),
273  cl::NDRange(nb_work_items_by_work_group),
274  nullptr,
275  nullptr);
276 
277  queue.finish();
278  queue.flush();
279  std::cout.flush();
280  std::cerr.flush();
281  std::cout << "===== end kernel =====" << std::endl;
282  std::cout.flush();
283 
284 
285  // Results
286  error = queue.enqueueReadBuffer(d_outs, CL_TRUE, 0, h_outs_byte_size, h_outs);
287  print_error(error, "clEnqueueReadBuffer(d_outs, ...)");
288 
289  if (debug) {
290  error = queue.enqueueReadBuffer(d_asserts, CL_TRUE, 0, h_asserts_byte_size, h_asserts);
291  print_error(error, "clEnqueueReadBuffer(d_asserts, ...)");
292 
293  error = queue.enqueueReadBuffer(d_assert_float, CL_TRUE, 0, h_assert_float_byte_size, h_assert_float);
294  print_error(error, "clEnqueueReadBuffer(d_assert_float, ...)");
295 
296  const uint64_t line = h_asserts[0];
297 
298  if (line != 0) { // there had (at least) an assertion
299  const uint64_t uint64_value = h_asserts[1];
300  const int64_t sint64_value = static_cast<int64_t>(h_asserts[1]);
301  const float float_value = h_assert_float[0];
302 
303  std::cerr << kernel_filename << ':' << line << "\tAssertion failed | Maybe\t"
304  << uint64_value << '\t' << sint64_value << " | Maybe\t" << float_value << std::endl;
305  /*
306  Maybe incoherent assert information because the parallel execution of work items.
307  But each element of these information concern assertion(s) that failed.
308  */
309  std::cerr.flush();
310  }
311  }
312 
313  std::cout << "Results: (" << h_outs[0] << ", " << h_outs[1] << ')' << std::endl;
314 }
315 
316 
317 
318 /* ******
319  * Main *
320  ********/
321 
322 /** \brief
323  * Get the optional parameter --device platform:device
324  * and run the kernel ../kernel/example.cl
325  */
326 int
327 main(int argc, const char* const argv[]) {
328  bool debug =
329 #ifdef NDEBUG
330  false;
331 #else
332  true;
333 #endif
334  signed int device_i = 0;
335  signed int platform_i = 0;
336 
337  // Read parameters
338  {
339  int i = 1;
340 
341  while (i < argc) {
342  const std::string arg = argv[i];
343 
344  if ((arg == "--debug") || (arg == "--ndebug")) {
345  debug = (arg == "--debug");
346  }
347  else if (arg == "--device") {
348  ++i;
349  if (i >= argc) {
350  return EXIT_FAILURE;
351  }
352 
353  const std::string both_i = argv[i];
354  const size_t j = both_i.find(':');
355 
356  if (j == std::string::npos) {
357  platform_i = std::stoi(both_i);
358  }
359  else {
360  platform_i = std::stoi(both_i.substr(0, j));
361  device_i = std::stoi(both_i.substr(j + 1));
362  }
363 
364  if ((platform_i < 0) || (device_i < 0)) {
365  return EXIT_FAILURE;
366  }
367  }
368 
369  ++i;
370  }
371  }
372 
373  // Get wanted device
374  const cl::Device device = get_device(static_cast<unsigned int>(platform_i),
375  static_cast<unsigned int>(device_i));
376 
377  // Get device name and print
378  {
379  std::string device_name;
380 
381  device.getInfo(CL_DEVICE_NAME, &device_name);
382  if (device_name[device_name.size() - 1] == '\0') { // remove weird null character
383  device_name = device_name.substr(0, device_name.size() - 1);
384  }
385  std::cout << "Device " << platform_i << ':' << device_i << ' ' << device_name << std::endl;
386  std::cout.flush();
387  }
388 
389  // Run
390  run_example(3, 4, device, debug, dirname(argv[0]));
391 
392  return EXIT_SUCCESS;
393 }
std::string dirname(std::string path)
Return the directory part of path.
Definition: example.cpp:224
void print_error(cl_int code, std::string message="")
If code != 0 then print an error message corresponding to the error code.
Definition: example.cpp:241
int main(int argc, const char *const argv[])
Get the optional parameter –device platform:device and run the kernel ../kernel/example.cl.
Definition: example.cpp:398
cl::Device get_device(unsigned int platform_i, unsigned int device_i)
Return the given device of the given platform OpenCL, or exit if doesn&#39;t exists.
Definition: example.cpp:204
const std::map< int32_t, std::string > errors_map
List of all error codes and names extracted from /usr/include/CL/cl.h.
Definition: example.cpp:35
List of all error codes and names extracted from /usr/include/CL/cl.h.
#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 device, bool debug, const std::string &path)
Run the kernel ../kernel/example.cl.
Definition: example.cpp:257
std::string file_to_string(std::string filename)
Read the file and return its content to a string. If failed then print a error message and exit...
Definition: example.cpp:187
std::string error_name(cl_int code)
Return the error name corresponding to the error code.
Definition: example.cpp:177