// // Copyright (c) 2017 The Khronos Group Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "harness/compat.h" #ifdef __APPLE__ #include #else #include #endif #include #include #include #include #include #include "harness/errorHelpers.h" #include "harness/kernelHelpers.h" #include "harness/typeWrappers.h" #include "harness/clImageHelper.h" #include "harness/os_helpers.h" #include "../math_brute_force/function_list.h" #include "datagen.h" #include "exceptions.h" #include "kernelargs.h" #include "run_build_test.h" #include "run_services.h" #include // // Task // Task::Task(cl_device_id device, const char* options): m_devid(device) { if (options) m_options = options; } Task::~Task() {} const char* Task::getErrorLog() const { return m_log.c_str(); } void Task::setErrorLog(cl_program prog) { size_t len = 0; std::vector log; cl_int err_code = clGetProgramBuildInfo(prog, m_devid, CL_PROGRAM_BUILD_LOG, 0, NULL, &len); if(err_code != CL_SUCCESS) { m_log = "Error: clGetProgramBuildInfo(CL_PROGRAM_BUILD_LOG, &len) failed.\n"; return; } log.resize(len, 0); err_code = clGetProgramBuildInfo(prog, m_devid, CL_PROGRAM_BUILD_LOG, len, &log[0], NULL); if(err_code != CL_SUCCESS) { m_log = "Error: clGetProgramBuildInfo(CL_PROGRAM_BUILD_LOG, &log) failed.\n"; return; } m_log.append(&log[0]); } // // BuildTask // BuildTask::BuildTask(cl_program prog, cl_device_id dev, const char* options) : m_program(prog), Task(dev, options) {} bool BuildTask::execute() { cl_int err_code = clBuildProgram(m_program, 0, NULL, m_options.c_str(), NULL, NULL); if(CL_SUCCESS == err_code) return true; setErrorLog(m_program); return false; } // // SpirBuildTask // SpirBuildTask::SpirBuildTask(cl_program prog, cl_device_id dev, const char* options) : BuildTask(prog, dev, options) {} // // CompileTask // CompileTask::CompileTask(cl_program prog, cl_device_id dev, const char* options) : m_program(prog), Task(dev, options) {} void CompileTask::addHeader(const char* hname, cl_program hprog) { m_headers.push_back(std::make_pair(hname, hprog)); } const char* first(std::pair& p) { return p.first; } cl_program second(const std::pair& p) { return p.second; } bool CompileTask::execute() { // Generating the header names vector. std::vector names; std::transform(m_headers.begin(), m_headers.end(), names.begin(), first); // Generating the header programs vector. std::vector programs; std::transform(m_headers.begin(), m_headers.end(), programs.begin(), second); const char** h_names = NULL; const cl_program* h_programs = NULL; if (!m_headers.empty()) { h_programs = &programs[0]; h_names = &names[0]; } // Compiling with the headers. cl_int err_code = clCompileProgram( m_program, 1U, &m_devid, m_options.c_str(), m_headers.size(), // # of headers h_programs, h_names, NULL, NULL); if (CL_SUCCESS == err_code) return true; setErrorLog(m_program); return false; } // // SpirCompileTask // SpirCompileTask::SpirCompileTask(cl_program prog, cl_device_id dev, const char* options) : CompileTask(prog, dev, options) {} // // LinkTask // LinkTask::LinkTask(cl_program* programs, int num_programs, cl_context ctxt, cl_device_id dev, const char* options) : m_programs(programs), m_numPrograms(num_programs), m_context(ctxt), m_executable(NULL), Task(dev, options) {} bool LinkTask::execute() { cl_int err_code; int i; for(i = 0; i < m_numPrograms; ++i) { err_code = clCompileProgram(m_programs[i], 1, &m_devid, "-x spir -spir-std=1.2 -cl-kernel-arg-info", 0, NULL, NULL, NULL, NULL); if (CL_SUCCESS != err_code) { setErrorLog(m_programs[i]); return false; } } m_executable = clLinkProgram(m_context, 1, &m_devid, m_options.c_str(), m_numPrograms, m_programs, NULL, NULL, &err_code); if (CL_SUCCESS == err_code) return true; if(m_executable) setErrorLog(m_executable); return false; } cl_program LinkTask::getExecutable() const { return m_executable; } LinkTask::~LinkTask() { if(m_executable) clReleaseProgram(m_executable); } // // KernelEnumerator // void KernelEnumerator::process(cl_program prog) { const size_t MAX_KERNEL_NAME = 64; size_t num_kernels; cl_int err_code = clGetProgramInfo( prog, CL_PROGRAM_NUM_KERNELS, sizeof(size_t), &num_kernels, NULL ); if (CL_SUCCESS != err_code) return; // Querying for the number of kernels. size_t buffer_len = sizeof(char)*num_kernels*MAX_KERNEL_NAME; char* kernel_names = new char[buffer_len]; memset(kernel_names, '\0', buffer_len); size_t str_len = 0; err_code = clGetProgramInfo( prog, CL_PROGRAM_KERNEL_NAMES, buffer_len, (void *)kernel_names, &str_len ); if (CL_SUCCESS != err_code) return; //parsing the names and inserting them to the list std::string names(kernel_names); assert (str_len == 1+names.size() && "incompatible string lengths"); size_t offset = 0; for(size_t i=0 ; i bc_result; // first, run the single CL test { // make sure that the kernel will be released before the program clKernelWrapper kernel = create_kernel_helper(clprog, kernel_name); // based on the kernel characteristics, we are generating and initializing the arguments for both phases (cl and bc executions) generate_kernel_data(context, kernel, ws, cl_result); bc_result.reset(cl_result.clone(context, ws, kernel, device)); assert (compare_results(cl_result, *bc_result, ulps) && "not equal?"); run_kernel( kernel, queue, ws, cl_result ); } // now, run the single BC test { // make sure that the kernel will be released before the program clKernelWrapper kernel = create_kernel_helper(bcprog, kernel_name); run_kernel( kernel, queue, ws, *bc_result ); } int error = clFinish(queue); if( CL_SUCCESS != error) { err = "clFinish failed\n"; return false; } // compare the results if( !compare_results(cl_result, *bc_result, ulps) ) { err = " (result diff in kernel '" + kernel_name + "')."; return false; } return true; } /** Get the maximum relative error defined as ULP of floating-point math functions */ static float get_max_ulps(const char *test_name) { float ulps = 0.f; // Get ULP values from math_brute_force functionList if (strstr(test_name, "math_kernel")) { for( size_t i = 0; i < functionListCount; i++ ) { char name[64]; const Func *func = &functionList[ i ]; sprintf(name, ".%s_float", func->name); if (strstr(test_name, name)) { ulps = func->float_ulps; } else { sprintf(name, ".%s_double", func->name); if (strstr(test_name, name)) { ulps = func->double_ulps; } } } } return ulps; } TestRunner::TestRunner(EventHandler *success, EventHandler *failure, const OclExtensions& devExt): m_successHandler(success), m_failureHandler(failure), m_devExt(&devExt) {} /** Based on the test name build the cl file name, the bc file name and execute the kernel for both modes (cl and bc). */ bool TestRunner::runBuildTest(cl_device_id device, const char *folder, const char *test_name, cl_uint size_t_width) { int failures = 0; // Composing the name of the CSV file. char* dir = get_exe_dir(); std::string csvName(dir); csvName.append(dir_sep()); csvName.append("khr.csv"); free(dir); log_info("%s...\n", test_name); float ulps = get_max_ulps(test_name); // Figure out whether the test can run on the device. If not, we skip it. const KhrSupport& khrDb = *KhrSupport::get(csvName); cl_bool images = khrDb.isImagesRequired(folder, test_name); cl_bool images3D = khrDb.isImages3DRequired(folder, test_name); char deviceProfile[64]; clGetDeviceInfo(device, CL_DEVICE_PROFILE, sizeof(deviceProfile), &deviceProfile, NULL); std::string device_profile(deviceProfile, 64); if(images == CL_TRUE && checkForImageSupport(device) != 0) { (*m_successHandler)(test_name, ""); std::cout << "Skipped. (Cannot run on device due to Images is not supported)." << std::endl; return true; } if(images3D == CL_TRUE && checkFor3DImageSupport(device) != 0) { (*m_successHandler)(test_name, ""); std::cout << "Skipped. (Cannot run on device as 3D images are not supported)." << std::endl; return true; } OclExtensions requiredExt = khrDb.getRequiredExtensions(folder, test_name); if(!m_devExt->supports(requiredExt)) { (*m_successHandler)(test_name, ""); std::cout << "Skipped. (Cannot run on device due to missing extensions: " << m_devExt->get_missing(requiredExt) << " )." << std::endl; return true; } std::string cl_file_path, bc_file; // Build cl file name based on the test name get_cl_file_path(folder, test_name, cl_file_path); // Build bc file name based on the test name get_bc_file_path(folder, test_name, bc_file, size_t_width); gRG.init(1); // // Processing each kernel in the program separately // clContextWrapper context; clCommandQueueWrapper queue; create_context_and_queue(device, &context, &queue); clProgramWrapper clprog = create_program_from_cl(context, cl_file_path); clProgramWrapper bcprog = create_program_from_bc(context, bc_file); std::string bcoptions = "-x spir -spir-std=1.2 -cl-kernel-arg-info"; std::string cloptions = "-cl-kernel-arg-info"; cl_device_fp_config gFloatCapabilities = 0; cl_int err; if ((err = clGetDeviceInfo(device, CL_DEVICE_SINGLE_FP_CONFIG, sizeof(gFloatCapabilities), &gFloatCapabilities, NULL))) { log_info("Unable to get device CL_DEVICE_SINGLE_FP_CONFIG. (%d)\n", err); } if (strstr(test_name, "div_cr") || strstr(test_name, "sqrt_cr")) { if ((gFloatCapabilities & CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT) == 0) { (*m_successHandler)(test_name, ""); std::cout << "Skipped. (Cannot run on device due to missing CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT property.)" << std::endl; return true; } else { bcoptions += " -cl-fp32-correctly-rounded-divide-sqrt"; cloptions += " -cl-fp32-correctly-rounded-divide-sqrt"; } } // Building the programs. BuildTask clBuild(clprog, device, cloptions.c_str()); if (!clBuild.execute()) { std::cerr << clBuild.getErrorLog() << std::endl; return false; } SpirBuildTask bcBuild(bcprog, device, bcoptions.c_str()); if (!bcBuild.execute()) { std::cerr << bcBuild.getErrorLog() << std::endl; return false; } KernelEnumerator clkernel_enumerator(clprog), bckernel_enumerator(bcprog); if (clkernel_enumerator.size() != bckernel_enumerator.size()) { std::cerr << "number of kernels in test" << test_name << " doesn't match in bc and cl files" << std::endl; return false; } KernelEnumerator::iterator it = clkernel_enumerator.begin(), e = clkernel_enumerator.end(); while (it != e) { std::string kernel_name = *it++; std::string err; try { bool success = run_test(context, queue, clprog, bcprog, kernel_name, err, device, ulps); if (success) { log_info("kernel '%s' passed.\n", kernel_name.c_str()); (*m_successHandler)(test_name, kernel_name); } else { ++failures; log_info("kernel '%s' failed.\n", kernel_name.c_str()); (*m_failureHandler)(test_name, kernel_name); } } catch (std::runtime_error err) { ++failures; log_info("kernel '%s' failed: %s\n", kernel_name.c_str(), err.what()); (*m_failureHandler)(test_name, kernel_name); } } log_info("%s %s\n", test_name, failures ? "FAILED" : "passed."); return failures == 0; }