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