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 #include "harness/compat.h"
17
18 #ifdef __APPLE__
19 #include <OpenCL/opencl.h>
20 #else
21 #include <CL/cl.h>
22 #endif
23
24 #include <sstream>
25 #include <fstream>
26 #include <assert.h>
27 #include <functional>
28 #include <memory>
29
30 #include "harness/errorHelpers.h"
31 #include "harness/kernelHelpers.h"
32 #include "harness/typeWrappers.h"
33 #include "harness/clImageHelper.h"
34 #include "harness/os_helpers.h"
35
36 #include "../math_brute_force/function_list.h"
37 #include "datagen.h"
38 #include "exceptions.h"
39 #include "kernelargs.h"
40 #include "run_build_test.h"
41 #include "run_services.h"
42 #include <CL/cl.h>
43 //
44 // Task
45 //
Task(cl_device_id device,const char * options)46 Task::Task(cl_device_id device, const char* options):
47 m_devid(device) {
48 if (options)
49 m_options = options;
50 }
51
~Task()52 Task::~Task() {}
53
getErrorLog() const54 const char* Task::getErrorLog() const {
55 return m_log.c_str();
56 }
57
setErrorLog(cl_program prog)58 void Task::setErrorLog(cl_program prog) {
59 size_t len = 0;
60 std::vector<char> log;
61
62 cl_int err_code = clGetProgramBuildInfo(prog, m_devid, CL_PROGRAM_BUILD_LOG, 0, NULL, &len);
63 if(err_code != CL_SUCCESS)
64 {
65 m_log = "Error: clGetProgramBuildInfo(CL_PROGRAM_BUILD_LOG, &len) failed.\n";
66 return;
67 }
68
69 log.resize(len, 0);
70
71 err_code = clGetProgramBuildInfo(prog, m_devid, CL_PROGRAM_BUILD_LOG, len, &log[0], NULL);
72 if(err_code != CL_SUCCESS)
73 {
74 m_log = "Error: clGetProgramBuildInfo(CL_PROGRAM_BUILD_LOG, &log) failed.\n";
75 return;
76 }
77 m_log.append(&log[0]);
78 }
79
80 //
81 // BuildTask
82 //
BuildTask(cl_program prog,cl_device_id dev,const char * options)83 BuildTask::BuildTask(cl_program prog, cl_device_id dev, const char* options) :
84 m_program(prog), Task(dev, options) {}
85
execute()86 bool BuildTask::execute() {
87 cl_int err_code = clBuildProgram(m_program, 0, NULL, m_options.c_str(), NULL, NULL);
88 if(CL_SUCCESS == err_code)
89 return true;
90
91 setErrorLog(m_program);
92 return false;
93 }
94
95 //
96 // SpirBuildTask
97 //
SpirBuildTask(cl_program prog,cl_device_id dev,const char * options)98 SpirBuildTask::SpirBuildTask(cl_program prog, cl_device_id dev, const char* options) :
99 BuildTask(prog, dev, options) {}
100
101 //
102 // CompileTask
103 //
104
CompileTask(cl_program prog,cl_device_id dev,const char * options)105 CompileTask::CompileTask(cl_program prog, cl_device_id dev, const char* options) :
106 m_program(prog), Task(dev, options) {}
107
addHeader(const char * hname,cl_program hprog)108 void CompileTask::addHeader(const char* hname, cl_program hprog) {
109 m_headers.push_back(std::make_pair(hname, hprog));
110 }
111
first(std::pair<const char *,cl_program> & p)112 const char* first(std::pair<const char*,cl_program>& p) {
113 return p.first;
114 }
115
second(const std::pair<const char *,cl_program> & p)116 cl_program second(const std::pair<const char*, cl_program>& p) {
117 return p.second;
118 }
119
execute()120 bool CompileTask::execute() {
121 // Generating the header names vector.
122 std::vector<const char*> names;
123 std::transform(m_headers.begin(), m_headers.end(), names.begin(), first);
124
125 // Generating the header programs vector.
126 std::vector<cl_program> programs;
127 std::transform(m_headers.begin(), m_headers.end(), programs.begin(), second);
128
129 const char** h_names = NULL;
130 const cl_program* h_programs = NULL;
131 if (!m_headers.empty())
132 {
133 h_programs = &programs[0];
134 h_names = &names[0];
135 }
136
137 // Compiling with the headers.
138 cl_int err_code = clCompileProgram(
139 m_program,
140 1U,
141 &m_devid,
142 m_options.c_str(),
143 m_headers.size(), // # of headers
144 h_programs,
145 h_names,
146 NULL, NULL);
147 if (CL_SUCCESS == err_code)
148 return true;
149
150 setErrorLog(m_program);
151 return false;
152 }
153
154 //
155 // SpirCompileTask
156 //
SpirCompileTask(cl_program prog,cl_device_id dev,const char * options)157 SpirCompileTask::SpirCompileTask(cl_program prog, cl_device_id dev, const char* options) :
158 CompileTask(prog, dev, options) {}
159
160
161 //
162 // LinkTask
163 //
LinkTask(cl_program * programs,int num_programs,cl_context ctxt,cl_device_id dev,const char * options)164 LinkTask::LinkTask(cl_program* programs, int num_programs, cl_context ctxt,
165 cl_device_id dev, const char* options) :
166 m_programs(programs), m_numPrograms(num_programs), m_context(ctxt), m_executable(NULL),
167 Task(dev, options) {}
168
execute()169 bool LinkTask::execute() {
170 cl_int err_code;
171 int i;
172
173 for(i = 0; i < m_numPrograms; ++i)
174 {
175 err_code = clCompileProgram(m_programs[i], 1, &m_devid, "-x spir -spir-std=1.2 -cl-kernel-arg-info", 0, NULL, NULL, NULL, NULL);
176 if (CL_SUCCESS != err_code)
177 {
178 setErrorLog(m_programs[i]);
179 return false;
180 }
181 }
182
183 m_executable = clLinkProgram(m_context, 1, &m_devid, m_options.c_str(), m_numPrograms, m_programs, NULL, NULL, &err_code);
184 if (CL_SUCCESS == err_code)
185 return true;
186
187 if(m_executable) setErrorLog(m_executable);
188 return false;
189 }
190
getExecutable() const191 cl_program LinkTask::getExecutable() const {
192 return m_executable;
193 }
194
~LinkTask()195 LinkTask::~LinkTask() {
196 if(m_executable) clReleaseProgram(m_executable);
197 }
198
199 //
200 // KernelEnumerator
201 //
process(cl_program prog)202 void KernelEnumerator::process(cl_program prog) {
203 const size_t MAX_KERNEL_NAME = 64;
204 size_t num_kernels;
205
206 cl_int err_code = clGetProgramInfo(
207 prog,
208 CL_PROGRAM_NUM_KERNELS,
209 sizeof(size_t),
210 &num_kernels,
211 NULL
212 );
213 if (CL_SUCCESS != err_code)
214 return;
215
216 // Querying for the number of kernels.
217 size_t buffer_len = sizeof(char)*num_kernels*MAX_KERNEL_NAME;
218 char* kernel_names = new char[buffer_len];
219 memset(kernel_names, '\0', buffer_len);
220 size_t str_len = 0;
221 err_code = clGetProgramInfo(
222 prog,
223 CL_PROGRAM_KERNEL_NAMES,
224 buffer_len,
225 (void *)kernel_names,
226 &str_len
227 );
228 if (CL_SUCCESS != err_code)
229 return;
230
231 //parsing the names and inserting them to the list
232 std::string names(kernel_names);
233 assert (str_len == 1+names.size() && "incompatible string lengths");
234 size_t offset = 0;
235 for(size_t i=0 ; i<names.size() ; ++i){
236 //kernel names are separated by semi colons
237 if (names[i] == ';'){
238 m_kernels.push_back(names.substr(offset, i-offset));
239 offset = i+1;
240 }
241 }
242 m_kernels.push_back(names.substr(offset, names.size()-offset));
243 delete[] kernel_names;
244 }
245
KernelEnumerator(cl_program prog)246 KernelEnumerator::KernelEnumerator(cl_program prog) {
247 process(prog);
248 }
249
begin()250 KernelEnumerator::iterator KernelEnumerator::begin(){
251 return m_kernels.begin();
252 }
253
end()254 KernelEnumerator::iterator KernelEnumerator::end(){
255 return m_kernels.end();
256 }
257
size() const258 size_t KernelEnumerator::size() const {
259 return m_kernels.size();
260 }
261
262 /**
263 Run the single test - run the test for both CL and SPIR versions of the kernel
264 */
run_test(cl_context context,cl_command_queue queue,cl_program clprog,cl_program bcprog,const std::string & kernel_name,std::string & err,const cl_device_id device,float ulps)265 static bool run_test(cl_context context, cl_command_queue queue, cl_program clprog,
266 cl_program bcprog, const std::string& kernel_name, std::string& err, const cl_device_id device,
267 float ulps)
268 {
269 WorkSizeInfo ws;
270 TestResult cl_result;
271 std::unique_ptr<TestResult> bc_result;
272 // first, run the single CL test
273 {
274 // make sure that the kernel will be released before the program
275 clKernelWrapper kernel = create_kernel_helper(clprog, kernel_name);
276 // based on the kernel characteristics, we are generating and initializing the arguments for both phases (cl and bc executions)
277 generate_kernel_data(context, kernel, ws, cl_result);
278 bc_result.reset(cl_result.clone(context, ws, kernel, device));
279 assert (compare_results(cl_result, *bc_result, ulps) && "not equal?");
280 run_kernel( kernel, queue, ws, cl_result );
281 }
282 // now, run the single BC test
283 {
284 // make sure that the kernel will be released before the program
285 clKernelWrapper kernel = create_kernel_helper(bcprog, kernel_name);
286 run_kernel( kernel, queue, ws, *bc_result );
287 }
288
289 int error = clFinish(queue);
290 if( CL_SUCCESS != error)
291 {
292 err = "clFinish failed\n";
293 return false;
294 }
295
296 // compare the results
297 if( !compare_results(cl_result, *bc_result, ulps) )
298 {
299 err = " (result diff in kernel '" + kernel_name + "').";
300 return false;
301 }
302 return true;
303 }
304
305 /**
306 Get the maximum relative error defined as ULP of floating-point math functions
307 */
get_max_ulps(const char * test_name)308 static float get_max_ulps(const char *test_name)
309 {
310 float ulps = 0.f;
311 // Get ULP values from math_brute_force functionList
312 if (strstr(test_name, "math_kernel"))
313 {
314 for( size_t i = 0; i < functionListCount; i++ )
315 {
316 char name[64];
317 const Func *func = &functionList[ i ];
318 sprintf(name, ".%s_float", func->name);
319 if (strstr(test_name, name))
320 {
321 ulps = func->float_ulps;
322 }
323 else
324 {
325 sprintf(name, ".%s_double", func->name);
326 if (strstr(test_name, name))
327 {
328 ulps = func->double_ulps;
329 }
330 }
331 }
332 }
333 return ulps;
334 }
335
TestRunner(EventHandler * success,EventHandler * failure,const OclExtensions & devExt)336 TestRunner::TestRunner(EventHandler *success, EventHandler *failure,
337 const OclExtensions& devExt):
338 m_successHandler(success), m_failureHandler(failure), m_devExt(&devExt) {}
339
340 /**
341 Based on the test name build the cl file name, the bc file name and execute
342 the kernel for both modes (cl and bc).
343 */
runBuildTest(cl_device_id device,const char * folder,const char * test_name,cl_uint size_t_width)344 bool TestRunner::runBuildTest(cl_device_id device, const char *folder,
345 const char *test_name, cl_uint size_t_width)
346 {
347 int failures = 0;
348 // Composing the name of the CSV file.
349 char* dir = get_exe_dir();
350 std::string csvName(dir);
351 csvName.append(dir_sep());
352 csvName.append("khr.csv");
353 free(dir);
354
355 log_info("%s...\n", test_name);
356
357 float ulps = get_max_ulps(test_name);
358
359 // Figure out whether the test can run on the device. If not, we skip it.
360 const KhrSupport& khrDb = *KhrSupport::get(csvName);
361 cl_bool images = khrDb.isImagesRequired(folder, test_name);
362 cl_bool images3D = khrDb.isImages3DRequired(folder, test_name);
363
364 char deviceProfile[64];
365 clGetDeviceInfo(device, CL_DEVICE_PROFILE, sizeof(deviceProfile), &deviceProfile, NULL);
366 std::string device_profile(deviceProfile, 64);
367
368 if(images == CL_TRUE && checkForImageSupport(device) != 0)
369 {
370 (*m_successHandler)(test_name, "");
371 std::cout << "Skipped. (Cannot run on device due to Images is not supported)." << std::endl;
372 return true;
373 }
374
375 if(images3D == CL_TRUE && checkFor3DImageSupport(device) != 0)
376 {
377 (*m_successHandler)(test_name, "");
378 std::cout << "Skipped. (Cannot run on device as 3D images are not supported)." << std::endl;
379 return true;
380 }
381
382 OclExtensions requiredExt = khrDb.getRequiredExtensions(folder, test_name);
383 if(!m_devExt->supports(requiredExt))
384 {
385 (*m_successHandler)(test_name, "");
386 std::cout << "Skipped. (Cannot run on device due to missing extensions: " << m_devExt->get_missing(requiredExt) << " )." << std::endl;
387 return true;
388 }
389
390 std::string cl_file_path, bc_file;
391 // Build cl file name based on the test name
392 get_cl_file_path(folder, test_name, cl_file_path);
393 // Build bc file name based on the test name
394 get_bc_file_path(folder, test_name, bc_file, size_t_width);
395 gRG.init(1);
396 //
397 // Processing each kernel in the program separately
398 //
399 clContextWrapper context;
400 clCommandQueueWrapper queue;
401 create_context_and_queue(device, &context, &queue);
402 clProgramWrapper clprog = create_program_from_cl(context, cl_file_path);
403 clProgramWrapper bcprog = create_program_from_bc(context, bc_file);
404 std::string bcoptions = "-x spir -spir-std=1.2 -cl-kernel-arg-info";
405 std::string cloptions = "-cl-kernel-arg-info";
406
407 cl_device_fp_config gFloatCapabilities = 0;
408 cl_int err;
409 if ((err = clGetDeviceInfo(device, CL_DEVICE_SINGLE_FP_CONFIG, sizeof(gFloatCapabilities), &gFloatCapabilities, NULL)))
410 {
411 log_info("Unable to get device CL_DEVICE_SINGLE_FP_CONFIG. (%d)\n", err);
412 }
413
414 if (strstr(test_name, "div_cr") || strstr(test_name, "sqrt_cr")) {
415 if ((gFloatCapabilities & CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT) == 0) {
416 (*m_successHandler)(test_name, "");
417 std::cout << "Skipped. (Cannot run on device due to missing CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT property.)" << std::endl;
418 return true;
419 } else {
420 bcoptions += " -cl-fp32-correctly-rounded-divide-sqrt";
421 cloptions += " -cl-fp32-correctly-rounded-divide-sqrt";
422 }
423 }
424
425 // Building the programs.
426 BuildTask clBuild(clprog, device, cloptions.c_str());
427 if (!clBuild.execute()) {
428 std::cerr << clBuild.getErrorLog() << std::endl;
429 return false;
430 }
431
432 SpirBuildTask bcBuild(bcprog, device, bcoptions.c_str());
433 if (!bcBuild.execute()) {
434 std::cerr << bcBuild.getErrorLog() << std::endl;
435 return false;
436 }
437
438 KernelEnumerator clkernel_enumerator(clprog),
439 bckernel_enumerator(bcprog);
440 if (clkernel_enumerator.size() != bckernel_enumerator.size()) {
441 std::cerr << "number of kernels in test" << test_name
442 << " doesn't match in bc and cl files" << std::endl;
443 return false;
444 }
445 KernelEnumerator::iterator it = clkernel_enumerator.begin(),
446 e = clkernel_enumerator.end();
447 while (it != e)
448 {
449 std::string kernel_name = *it++;
450 std::string err;
451 try
452 {
453 bool success = run_test(context, queue, clprog, bcprog, kernel_name, err, device, ulps);
454 if (success)
455 {
456 log_info("kernel '%s' passed.\n", kernel_name.c_str());
457 (*m_successHandler)(test_name, kernel_name);
458 }
459 else
460 {
461 ++failures;
462 log_info("kernel '%s' failed.\n", kernel_name.c_str());
463 (*m_failureHandler)(test_name, kernel_name);
464 }
465 }
466 catch (std::runtime_error err)
467 {
468 ++failures;
469 log_info("kernel '%s' failed: %s\n", kernel_name.c_str(), err.what());
470 (*m_failureHandler)(test_name, kernel_name);
471 }
472 }
473
474 log_info("%s %s\n", test_name, failures ? "FAILED" : "passed.");
475 return failures == 0;
476 }
477
478