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