1 //
2 // Copyright (c) 2017 The Khronos Group Inc.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 #ifndef TEST_CONFORMANCE_CLCPP_PIPES_TEST_PIPES_HPP
17 #define TEST_CONFORMANCE_CLCPP_PIPES_TEST_PIPES_HPP
18
19 #include <sstream>
20 #include <string>
21 #include <tuple>
22 #include <vector>
23 #include <algorithm>
24
25 // Common for all OpenCL C++ tests
26 #include "../common.hpp"
27
28
29 namespace test_pipes {
30
31 enum class pipe_source
32 {
33 param,
34 storage
35 };
36
37 enum class pipe_operation
38 {
39 work_item,
40 work_item_reservation,
41 work_group_reservation,
42 sub_group_reservation
43 };
44
45 struct test_options
46 {
47 pipe_operation operation;
48 pipe_source source;
49 int max_packets;
50 int num_packets;
51 };
52
53 struct output_type
54 {
55 cl_uint write_reservation_is_valid;
56 cl_uint write_success;
57
58 cl_uint num_packets;
59 cl_uint max_packets;
60 cl_uint read_reservation_is_valid;
61 cl_uint read_success;
62
63 cl_uint value;
64 };
65
66 const std::string source_common = R"(
67 struct output_type
68 {
69 uint write_reservation_is_valid;
70 uint write_success;
71
72 uint num_packets;
73 uint max_packets;
74 uint read_reservation_is_valid;
75 uint read_success;
76
77 uint value;
78 };
79 )";
80
81 // -----------------------------------------------------------------------------------
82 // ------------- ONLY FOR OPENCL 22 CONFORMANCE TEST 22 DEVELOPMENT ------------------
83 // -----------------------------------------------------------------------------------
84 #if defined(DEVELOPMENT) && defined(USE_OPENCLC_KERNELS)
generate_source(test_options options)85 std::string generate_source(test_options options)
86 {
87 std::stringstream s;
88 s << source_common;
89 if (options.operation == pipe_operation::work_item)
90 {
91 s << R"(
92 kernel void producer(write_only pipe uint out_pipe, global struct output_type *output)
93 {
94 const ulong gid = get_global_id(0);
95
96 output[gid].write_reservation_is_valid = 1;
97
98 uint value = gid;
99 output[gid].write_success = write_pipe(out_pipe, &value) == 0;
100 }
101
102 kernel void consumer(read_only pipe uint in_pipe, global struct output_type *output)
103 {
104 const ulong gid = get_global_id(0);
105
106 output[gid].num_packets = get_pipe_num_packets(in_pipe);
107 output[gid].max_packets = get_pipe_max_packets(in_pipe);
108
109 output[gid].read_reservation_is_valid = 1;
110
111 uint value;
112 output[gid].read_success = read_pipe(in_pipe, &value) == 0;
113 output[gid].value = value;
114 }
115 )";
116 }
117 else if (options.operation == pipe_operation::work_item_reservation)
118 {
119 s << R"(
120 kernel void producer(write_only pipe uint out_pipe, global struct output_type *output)
121 {
122 const ulong gid = get_global_id(0);
123 if (gid % 2 == 1) return;
124
125 reserve_id_t reservation = reserve_write_pipe(out_pipe, 2);
126 output[gid + 0].write_reservation_is_valid = is_valid_reserve_id(reservation);
127 output[gid + 1].write_reservation_is_valid = is_valid_reserve_id(reservation);
128
129 uint value0 = gid + 0;
130 uint value1 = gid + 1;
131 output[gid + 0].write_success = write_pipe(out_pipe, reservation, 0, &value0) == 0;
132 output[gid + 1].write_success = write_pipe(out_pipe, reservation, 1, &value1) == 0;
133 commit_write_pipe(out_pipe, reservation);
134 }
135
136 kernel void consumer(read_only pipe uint in_pipe, global struct output_type *output)
137 {
138 const ulong gid = get_global_id(0);
139 if (gid % 2 == 1) return;
140
141 output[gid + 0].num_packets = get_pipe_num_packets(in_pipe);
142 output[gid + 0].max_packets = get_pipe_max_packets(in_pipe);
143 output[gid + 1].num_packets = get_pipe_num_packets(in_pipe);
144 output[gid + 1].max_packets = get_pipe_max_packets(in_pipe);
145
146 reserve_id_t reservation = reserve_read_pipe(in_pipe, 2);
147 output[gid + 0].read_reservation_is_valid = is_valid_reserve_id(reservation);
148 output[gid + 1].read_reservation_is_valid = is_valid_reserve_id(reservation);
149
150 uint value0;
151 uint value1;
152 output[gid + 0].read_success = read_pipe(in_pipe, reservation, 1, &value0) == 0;
153 output[gid + 1].read_success = read_pipe(in_pipe, reservation, 0, &value1) == 0;
154 commit_read_pipe(in_pipe, reservation);
155 output[gid + 0].value = value0;
156 output[gid + 1].value = value1;
157 }
158 )";
159 }
160 else if (options.operation == pipe_operation::work_group_reservation)
161 {
162 s << R"(
163 kernel void producer(write_only pipe uint out_pipe, global struct output_type *output)
164 {
165 const ulong gid = get_global_id(0);
166
167 reserve_id_t reservation = work_group_reserve_write_pipe(out_pipe, get_local_size(0));
168 output[gid].write_reservation_is_valid = is_valid_reserve_id(reservation);
169
170 uint value = gid;
171 output[gid].write_success = write_pipe(out_pipe, reservation, get_local_id(0), &value) == 0;
172 work_group_commit_write_pipe(out_pipe, reservation);
173 }
174
175 kernel void consumer(read_only pipe uint in_pipe, global struct output_type *output)
176 {
177 const ulong gid = get_global_id(0);
178
179 output[gid].num_packets = get_pipe_num_packets(in_pipe);
180 output[gid].max_packets = get_pipe_max_packets(in_pipe);
181
182 reserve_id_t reservation = work_group_reserve_read_pipe(in_pipe, get_local_size(0));
183 output[gid].read_reservation_is_valid = is_valid_reserve_id(reservation);
184
185 uint value;
186 output[gid].read_success = read_pipe(in_pipe, reservation, get_local_size(0) - 1 - get_local_id(0), &value) == 0;
187 work_group_commit_read_pipe(in_pipe, reservation);
188 output[gid].value = value;
189 }
190 )";
191 }
192 else if (options.operation == pipe_operation::sub_group_reservation)
193 {
194 s << R"(
195 #pragma OPENCL EXTENSION cl_khr_subgroups : enable
196
197 kernel void producer(write_only pipe uint out_pipe, global struct output_type *output)
198 {
199 const ulong gid = get_global_id(0);
200
201 reserve_id_t reservation = sub_group_reserve_write_pipe(out_pipe, get_sub_group_size());
202 output[gid].write_reservation_is_valid = is_valid_reserve_id(reservation);
203
204 uint value = gid;
205 output[gid].write_success = write_pipe(out_pipe, reservation, get_sub_group_local_id(), &value) == 0;
206 sub_group_commit_write_pipe(out_pipe, reservation);
207 }
208
209 kernel void consumer(read_only pipe uint in_pipe, global struct output_type *output)
210 {
211 const ulong gid = get_global_id(0);
212
213 output[gid].num_packets = get_pipe_num_packets(in_pipe);
214 output[gid].max_packets = get_pipe_max_packets(in_pipe);
215
216 reserve_id_t reservation = sub_group_reserve_read_pipe(in_pipe, get_sub_group_size());
217 output[gid].read_reservation_is_valid = is_valid_reserve_id(reservation);
218
219 uint value;
220 output[gid].read_success = read_pipe(in_pipe, reservation, get_sub_group_size() - 1 - get_sub_group_local_id(), &value) == 0;
221 sub_group_commit_read_pipe(in_pipe, reservation);
222 output[gid].value = value;
223 }
224 )";
225 }
226
227 return s.str();
228 }
229 #else
generate_source(test_options options)230 std::string generate_source(test_options options)
231 {
232 std::stringstream s;
233 s << R"(
234 #include <opencl_memory>
235 #include <opencl_common>
236 #include <opencl_work_item>
237 #include <opencl_synchronization>
238 #include <opencl_pipe>
239 using namespace cl;
240 )";
241
242 s << source_common;
243
244 std::string init_out_pipe;
245 std::string init_in_pipe;
246 if (options.source == pipe_source::param)
247 {
248 init_out_pipe = "auto out_pipe = pipe_param;";
249 init_in_pipe = "auto in_pipe = pipe_param;";
250 }
251 else if (options.source == pipe_source::storage)
252 {
253 s << "pipe_storage<uint, " << std::to_string(options.max_packets) << "> storage;";
254 init_out_pipe = "auto out_pipe = storage.get<pipe_access::write>();";
255 init_in_pipe = "auto in_pipe = make_pipe(storage);";
256 }
257
258 if (options.operation == pipe_operation::work_item)
259 {
260 s << R"(
261 kernel void producer(pipe<uint, pipe_access::write> pipe_param, global_ptr<output_type[]> output)
262 {
263 )" << init_out_pipe << R"(
264 const ulong gid = get_global_id(0);
265
266 output[gid].write_reservation_is_valid = 1;
267
268 uint value = gid;
269 output[gid].write_success = out_pipe.write(value);
270 }
271
272 kernel void consumer(pipe<uint, pipe_access::read> pipe_param, global_ptr<output_type[]> output)
273 {
274 )" << init_in_pipe << R"(
275 const ulong gid = get_global_id(0);
276
277 output[gid].num_packets = in_pipe.num_packets();
278 output[gid].max_packets = in_pipe.max_packets();
279
280 output[gid].read_reservation_is_valid = 1;
281
282 uint value;
283 output[gid].read_success = in_pipe.read(value);
284 output[gid].value = value;
285 }
286 )";
287 }
288 else if (options.operation == pipe_operation::work_item_reservation)
289 {
290 s << R"(
291 kernel void producer(pipe<uint, pipe_access::write> pipe_param, global_ptr<output_type[]> output)
292 {
293 )" << init_out_pipe << R"(
294 const ulong gid = get_global_id(0);
295 if (gid % 2 == 1) return;
296
297 auto reservation = out_pipe.reserve(2);
298 output[gid + 0].write_reservation_is_valid = reservation.is_valid();
299 output[gid + 1].write_reservation_is_valid = reservation.is_valid();
300
301 uint value0 = gid + 0;
302 uint value1 = gid + 1;
303 output[gid + 0].write_success = reservation.write(0, value0);
304 output[gid + 1].write_success = reservation.write(1, value1);
305 reservation.commit();
306 }
307
308 kernel void consumer(pipe<uint, pipe_access::read> pipe_param, global_ptr<output_type[]> output)
309 {
310 )" << init_in_pipe << R"(
311 const ulong gid = get_global_id(0);
312 if (gid % 2 == 1) return;
313
314 output[gid + 0].num_packets = in_pipe.num_packets();
315 output[gid + 0].max_packets = in_pipe.max_packets();
316 output[gid + 1].num_packets = in_pipe.num_packets();
317 output[gid + 1].max_packets = in_pipe.max_packets();
318
319 auto reservation = in_pipe.reserve(2);
320 output[gid + 0].read_reservation_is_valid = reservation.is_valid();
321 output[gid + 1].read_reservation_is_valid = reservation.is_valid();
322
323 uint value0;
324 uint value1;
325 output[gid + 0].read_success = reservation.read(1, value0);
326 output[gid + 1].read_success = reservation.read(0, value1);
327 reservation.commit();
328 output[gid + 0].value = value0;
329 output[gid + 1].value = value1;
330 }
331 )";
332 }
333 else if (options.operation == pipe_operation::work_group_reservation)
334 {
335 s << R"(
336 kernel void producer(pipe<uint, pipe_access::write> pipe_param, global_ptr<output_type[]> output)
337 {
338 )" << init_out_pipe << R"(
339 const ulong gid = get_global_id(0);
340
341 auto reservation = out_pipe.work_group_reserve(get_local_size(0));
342 output[gid].write_reservation_is_valid = reservation.is_valid();
343
344 uint value = gid;
345 output[gid].write_success = reservation.write(get_local_id(0), value);
346 reservation.commit();
347 }
348
349 kernel void consumer(pipe<uint, pipe_access::read> pipe_param, global_ptr<output_type[]> output)
350 {
351 )" << init_in_pipe << R"(
352 const ulong gid = get_global_id(0);
353
354 output[gid].num_packets = in_pipe.num_packets();
355 output[gid].max_packets = in_pipe.max_packets();
356
357 auto reservation = in_pipe.work_group_reserve(get_local_size(0));
358 output[gid].read_reservation_is_valid = reservation.is_valid();
359
360 uint value;
361 output[gid].read_success = reservation.read(get_local_size(0) - 1 - get_local_id(0), value);
362 reservation.commit();
363 output[gid].value = value;
364 }
365 )";
366 }
367 else if (options.operation == pipe_operation::sub_group_reservation)
368 {
369 s << R"(
370 kernel void producer(pipe<uint, pipe_access::write> pipe_param, global_ptr<output_type[]> output)
371 {
372 )" << init_out_pipe << R"(
373 const ulong gid = get_global_id(0);
374
375 auto reservation = out_pipe.sub_group_reserve(get_sub_group_size());
376 output[gid].write_reservation_is_valid = reservation.is_valid();
377
378 uint value = gid;
379 output[gid].write_success = reservation.write(get_sub_group_local_id(), value);
380 reservation.commit();
381 }
382
383 kernel void consumer(pipe<uint, pipe_access::read> pipe_param, global_ptr<output_type[]> output)
384 {
385 )" << init_in_pipe << R"(
386 const ulong gid = get_global_id(0);
387
388 output[gid].num_packets = in_pipe.num_packets();
389 output[gid].max_packets = in_pipe.max_packets();
390
391 auto reservation = in_pipe.sub_group_reserve(get_sub_group_size());
392 output[gid].read_reservation_is_valid = reservation.is_valid();
393
394 uint value;
395 output[gid].read_success = reservation.read(get_sub_group_size() - 1 - get_sub_group_local_id(), value);
396 reservation.commit();
397 output[gid].value = value;
398 }
399 )";
400 }
401
402 return s.str();
403 }
404 #endif
405
test(cl_device_id device,cl_context context,cl_command_queue queue,test_options options)406 int test(cl_device_id device, cl_context context, cl_command_queue queue, test_options options)
407 {
408 int error = CL_SUCCESS;
409
410 if (options.num_packets % 2 != 0 || options.max_packets < options.num_packets)
411 {
412 RETURN_ON_ERROR_MSG(-1, "Invalid test options")
413 }
414
415 #if defined(DEVELOPMENT) && defined(USE_OPENCLC_KERNELS)
416 if (options.operation == pipe_operation::sub_group_reservation && !is_extension_available(device, "cl_khr_subgroups"))
417 {
418 log_info("SKIPPED: Extension `cl_khr_subgroups` is not supported. Skipping tests.\n");
419 return CL_SUCCESS;
420 }
421 #endif
422
423 cl_program program;
424 cl_kernel producer_kernel;
425 cl_kernel consumer_kernel;
426
427 std::string producer_kernel_name = "producer";
428 std::string consumer_kernel_name = "consumer";
429 std::string source = generate_source(options);
430
431 // -----------------------------------------------------------------------------------
432 // ------------- ONLY FOR OPENCL 22 CONFORMANCE TEST 22 DEVELOPMENT ------------------
433 // -----------------------------------------------------------------------------------
434 // Only OpenCL C++ to SPIR-V compilation
435 #if defined(DEVELOPMENT) && defined(ONLY_SPIRV_COMPILATION)
436 error = create_opencl_kernel(
437 context, &program, &producer_kernel,
438 source, producer_kernel_name
439 );
440 RETURN_ON_ERROR(error)
441 return error;
442 // Use OpenCL C kernels instead of OpenCL C++ kernels (test C++ host code)
443 #elif defined(DEVELOPMENT) && defined(USE_OPENCLC_KERNELS)
444 error = create_opencl_kernel(
445 context, &program, &producer_kernel,
446 source, producer_kernel_name, "-cl-std=CL2.0", false
447 );
448 RETURN_ON_ERROR(error)
449 consumer_kernel = clCreateKernel(program, consumer_kernel_name.c_str(), &error);
450 RETURN_ON_CL_ERROR(error, "clCreateKernel")
451 // Normal run
452 #else
453 error = create_opencl_kernel(
454 context, &program, &producer_kernel,
455 source, producer_kernel_name
456 );
457 RETURN_ON_ERROR(error)
458 consumer_kernel = clCreateKernel(program, consumer_kernel_name.c_str(), &error);
459 RETURN_ON_CL_ERROR(error, "clCreateKernel")
460 #endif
461
462 size_t max_work_group_size;
463 error = clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(size_t), &max_work_group_size, NULL);
464 RETURN_ON_CL_ERROR(error, "clGetDeviceInfo")
465
466 const size_t count = options.num_packets;
467 const size_t local_size = (std::min)((size_t)256, max_work_group_size);
468 const size_t global_size = count;
469
470 const cl_uint packet_size = sizeof(cl_uint);
471
472 cl_mem pipe = clCreatePipe(context, 0, packet_size, options.max_packets, NULL, &error);
473 RETURN_ON_CL_ERROR(error, "clCreatePipe")
474
475 cl_mem output_buffer;
476 output_buffer = clCreateBuffer(context, CL_MEM_READ_WRITE, sizeof(output_type) * count, NULL, &error);
477 RETURN_ON_CL_ERROR(error, "clCreateBuffer")
478
479 const char pattern = 0;
480 error = clEnqueueFillBuffer(queue, output_buffer, &pattern, sizeof(pattern), 0, sizeof(output_type) * count, 0, NULL, NULL);
481 RETURN_ON_CL_ERROR(error, "clEnqueueFillBuffer")
482
483 error = clSetKernelArg(producer_kernel, 0, sizeof(cl_mem), &pipe);
484 RETURN_ON_CL_ERROR(error, "clSetKernelArg")
485 error = clSetKernelArg(producer_kernel, 1, sizeof(output_buffer), &output_buffer);
486 RETURN_ON_CL_ERROR(error, "clSetKernelArg")
487
488 error = clEnqueueNDRangeKernel(queue, producer_kernel, 1, NULL, &global_size, NULL, 0, NULL, NULL);
489 RETURN_ON_CL_ERROR(error, "clEnqueueNDRangeKernel")
490
491 error = clSetKernelArg(consumer_kernel, 0, sizeof(cl_mem), &pipe);
492 RETURN_ON_CL_ERROR(error, "clSetKernelArg")
493 error = clSetKernelArg(consumer_kernel, 1, sizeof(output_buffer), &output_buffer);
494 RETURN_ON_CL_ERROR(error, "clSetKernelArg")
495
496 error = clEnqueueNDRangeKernel(queue, consumer_kernel, 1, NULL, &global_size, &local_size, 0, NULL, NULL);
497 RETURN_ON_CL_ERROR(error, "clEnqueueNDRangeKernel")
498
499 std::vector<output_type> output(count);
500 error = clEnqueueReadBuffer(
501 queue, output_buffer, CL_TRUE,
502 0, sizeof(output_type) * count,
503 static_cast<void *>(output.data()),
504 0, NULL, NULL
505 );
506 RETURN_ON_CL_ERROR(error, "clEnqueueReadBuffer")
507
508 std::vector<bool> existing_values(count, false);
509 for (size_t gid = 0; gid < count; gid++)
510 {
511 const output_type &o = output[gid];
512
513 if (!o.write_reservation_is_valid)
514 {
515 RETURN_ON_ERROR_MSG(-1, "write reservation is not valid")
516 }
517 if (!o.write_success)
518 {
519 RETURN_ON_ERROR_MSG(-1, "write did not succeed")
520 }
521
522 if (o.num_packets == 0 || o.num_packets > options.num_packets)
523 {
524 RETURN_ON_ERROR_MSG(-1, "num_packets did not return correct value")
525 }
526 if (o.max_packets != options.max_packets)
527 {
528 RETURN_ON_ERROR_MSG(-1, "max_packets did not return correct value")
529 }
530 if (!o.read_reservation_is_valid)
531 {
532 RETURN_ON_ERROR_MSG(-1, "read reservation is not valid")
533 }
534 if (!o.read_success)
535 {
536 RETURN_ON_ERROR_MSG(-1, "read did not succeed")
537 }
538
539 // Every value must be presented once in any order
540 if (o.value >= count || existing_values[o.value])
541 {
542 RETURN_ON_ERROR_MSG(-1, "kernel did not return correct value")
543 }
544 existing_values[o.value] = true;
545 }
546
547 clReleaseMemObject(pipe);
548 clReleaseMemObject(output_buffer);
549 clReleaseKernel(producer_kernel);
550 clReleaseKernel(consumer_kernel);
551 clReleaseProgram(program);
552 return error;
553 }
554
555 const pipe_operation pipe_operations[] = {
556 pipe_operation::work_item,
557 pipe_operation::work_item_reservation,
558 pipe_operation::work_group_reservation,
559 pipe_operation::sub_group_reservation
560 };
561
562 const std::tuple<int, int> max_and_num_packets[] = {
563 std::make_tuple<int, int>(2, 2),
564 std::make_tuple<int, int>(10, 8),
565 std::make_tuple<int, int>(256, 254),
566 std::make_tuple<int, int>(1 << 16, 1 << 16),
567 std::make_tuple<int, int>((1 << 16) + 5, 1 << 16),
568 std::make_tuple<int, int>(12345, 12344),
569 std::make_tuple<int, int>(1 << 18, 1 << 18)
570 };
571
AUTO_TEST_CASE(test_pipes_pipe)572 AUTO_TEST_CASE(test_pipes_pipe)
573 (cl_device_id device, cl_context context, cl_command_queue queue, int num_elements)
574 {
575 std::vector<std::tuple<int, int>> ps;
576 for (auto p : max_and_num_packets)
577 {
578 if (std::get<0>(p) < num_elements)
579 ps.push_back(p);
580 }
581 ps.push_back(std::tuple<int, int>(num_elements, num_elements));
582
583 int error = CL_SUCCESS;
584
585 for (auto operation : pipe_operations)
586 for (auto p : ps)
587 {
588 test_options options;
589 options.source = pipe_source::param;
590 options.max_packets = std::get<0>(p);
591 options.num_packets = std::get<1>(p);
592 options.operation = operation;
593
594 error = test(device, context, queue, options);
595 RETURN_ON_ERROR(error)
596 }
597
598 return error;
599 }
600
AUTO_TEST_CASE(test_pipes_pipe_storage)601 AUTO_TEST_CASE(test_pipes_pipe_storage)
602 (cl_device_id device, cl_context context, cl_command_queue queue, int num_elements)
603 {
604 std::vector<std::tuple<int, int>> ps;
605 for (auto p : max_and_num_packets)
606 {
607 if (std::get<0>(p) < num_elements)
608 ps.push_back(p);
609 }
610 ps.push_back(std::tuple<int, int>(num_elements, num_elements));
611
612 int error = CL_SUCCESS;
613
614 for (auto operation : pipe_operations)
615 for (auto p : ps)
616 {
617 test_options options;
618 options.source = pipe_source::storage;
619 options.max_packets = std::get<0>(p);
620 options.num_packets = std::get<1>(p);
621 options.operation = operation;
622
623 error = test(device, context, queue, options);
624 RETURN_ON_ERROR(error)
625 }
626
627 return error;
628 }
629
630 } // namespace
631
632 #endif // TEST_CONFORMANCE_CLCPP_PIPES_TEST_PIPES_HPP
633