diff options
Diffstat (limited to 'src/clpp11.hpp')
-rw-r--r-- | src/clpp11.hpp | 134 |
1 files changed, 84 insertions, 50 deletions
diff --git a/src/clpp11.hpp b/src/clpp11.hpp index aaa76cb4..d306bb87 100644 --- a/src/clpp11.hpp +++ b/src/clpp11.hpp @@ -41,8 +41,8 @@ #include <string> // std::string #include <vector> // std::vector #include <memory> // std::shared_ptr -#include <stdexcept> // std::runtime_error #include <numeric> // std::accumulate +#include <cstring> // std::strlen // OpenCL #if defined(__APPLE__) || defined(__MACOSX) @@ -51,20 +51,41 @@ #include <CL/opencl.h> #endif +// Exception classes +#include "cxpp11_common.hpp" + namespace clblast { // ================================================================================================= -// Error occurred in the C++11 OpenCL header (this file) -inline void Error(const std::string &message) { - throw std::runtime_error("Internal OpenCL error: "+message); -} +// Represents a runtime error returned by an OpenCL API function +class CLError : public ErrorCode<DeviceError, cl_int> { + public: + explicit CLError(cl_int status, const std::string &where): + ErrorCode(status, + where, + "OpenCL error: " + where + ": " + std::to_string(static_cast<int>(status))) { + } -// Error occurred in OpenCL -inline void CheckError(const cl_int status) { - if (status != CL_SUCCESS) { - throw std::runtime_error("Internal OpenCL error: "+std::to_string(status)); + static void Check(const cl_int status, const std::string &where) { + if (status != CL_SUCCESS) { + throw CLError(status, where); + } } -} + + static void CheckDtor(const cl_int status, const std::string &where) { + if (status != CL_SUCCESS) { + fprintf(stderr, "CLBlast: %s (ignoring)\n", CLError(status, where).what()); + } + } +}; + +// ================================================================================================= + +// Error occurred in OpenCL +#define CheckError(call) CLError::Check(call, CLError::TrimCallString(#call)) + +// Error occured in OpenCL (no-exception version for destructors) +#define CheckErrorDtor(call) CLError::CheckDtor(call, CLError::TrimCallString(#call)) // ================================================================================================= @@ -81,7 +102,7 @@ class Event { // Regular constructor with memory management explicit Event(): event_(new cl_event, [](cl_event* e) { - if (*e) { CheckError(clReleaseEvent(*e)); } + if (*e) { CheckErrorDtor(clReleaseEvent(*e)); } delete e; }) { *event_ = nullptr; @@ -92,16 +113,17 @@ class Event { CheckError(clWaitForEvents(1, &(*event_))); } - // Retrieves the elapsed time of the last recorded event. Note that no error checking is done on - // the 'clGetEventProfilingInfo' function, since there is a bug in Apple's OpenCL implementation: - // http://stackoverflow.com/questions/26145603/clgeteventprofilinginfo-bug-in-macosx + // Retrieves the elapsed time of the last recorded event. + // (Note that there is a bug in Apple's OpenCL implementation of the 'clGetEventProfilingInfo' function: + // http://stackoverflow.com/questions/26145603/clgeteventprofilinginfo-bug-in-macosx) + // However, in our case the reply size is fixed to be cl_ulong, so we are not affected. float GetElapsedTime() const { WaitForCompletion(); const auto bytes = sizeof(cl_ulong); auto time_start = cl_ulong{0}; - clGetEventProfilingInfo(*event_, CL_PROFILING_COMMAND_START, bytes, &time_start, nullptr); + CheckError(clGetEventProfilingInfo(*event_, CL_PROFILING_COMMAND_START, bytes, &time_start, nullptr)); auto time_end = cl_ulong{0}; - clGetEventProfilingInfo(*event_, CL_PROFILING_COMMAND_END, bytes, &time_end, nullptr); + CheckError(clGetEventProfilingInfo(*event_, CL_PROFILING_COMMAND_END, bytes, &time_end, nullptr)); return static_cast<float>(time_end - time_start) * 1.0e-6f; } @@ -130,10 +152,14 @@ class Platform { explicit Platform(const size_t platform_id) { auto num_platforms = cl_uint{0}; CheckError(clGetPlatformIDs(0, nullptr, &num_platforms)); - if (num_platforms == 0) { Error("no platforms found"); } + if (num_platforms == 0) { + throw RuntimeError("Platform: no platforms found"); + } + if (platform_id >= num_platforms) { + throw RuntimeError("Platform: invalid platform ID "+std::to_string(platform_id)); + } auto platforms = std::vector<cl_platform_id>(num_platforms); CheckError(clGetPlatformIDs(num_platforms, platforms.data(), nullptr)); - if (platform_id >= num_platforms) { Error("invalid platform ID "+std::to_string(platform_id)); } platform_ = platforms[platform_id]; } @@ -173,11 +199,16 @@ class Device { // Initialize the device. Note that this constructor can throw exceptions! explicit Device(const Platform &platform, const size_t device_id) { auto num_devices = platform.NumDevices(); - if (num_devices == 0) { Error("no devices found"); } + if (num_devices == 0) { + throw RuntimeError("Device: no devices found"); + } + if (device_id >= num_devices) { + throw RuntimeError("Device: invalid device ID "+std::to_string(device_id)); + } + auto devices = std::vector<cl_device_id>(num_devices); CheckError(clGetDeviceIDs(platform(), CL_DEVICE_TYPE_ALL, static_cast<cl_uint>(num_devices), devices.data(), nullptr)); - if (device_id >= num_devices) { Error("invalid device ID "+std::to_string(device_id)); } device_ = devices[device_id]; } @@ -282,7 +313,8 @@ class Device { auto result = std::string{}; result.resize(bytes); CheckError(clGetDeviceInfo(device_, info, bytes, &result[0], nullptr)); - return std::string{result.c_str()}; // Removes any trailing '\0'-characters + result.resize(strlen(result.c_str())); // Removes any trailing '\0'-characters + return result; } }; @@ -300,11 +332,11 @@ class Context { // Regular constructor with memory management explicit Context(const Device &device): - context_(new cl_context, [](cl_context* c) { CheckError(clReleaseContext(*c)); delete c; }) { + context_(new cl_context, [](cl_context* c) { CheckErrorDtor(clReleaseContext(*c)); delete c; }) { auto status = CL_SUCCESS; const cl_device_id dev = device(); *context_ = clCreateContext(nullptr, 1, &dev, nullptr, nullptr, &status); - CheckError(status); + CLError::Check(status, "clCreateContext"); } // Accessor to the private data-member @@ -329,18 +361,18 @@ class Program { // Source-based constructor with memory management explicit Program(const Context &context, std::string source): - program_(new cl_program, [](cl_program* p) { CheckError(clReleaseProgram(*p)); delete p; }), + program_(new cl_program, [](cl_program* p) { CheckErrorDtor(clReleaseProgram(*p)); delete p; }), length_(source.length()), source_(std::move(source)), source_ptr_(&source_[0]) { auto status = CL_SUCCESS; *program_ = clCreateProgramWithSource(context(), 1, &source_ptr_, &length_, &status); - CheckError(status); + CLError::Check(status, "clCreateProgramWithSource"); } // Binary-based constructor with memory management explicit Program(const Device &device, const Context &context, const std::string& binary): - program_(new cl_program, [](cl_program* p) { CheckError(clReleaseProgram(*p)); delete p; }), + program_(new cl_program, [](cl_program* p) { CheckErrorDtor(clReleaseProgram(*p)); delete p; }), length_(binary.length()), source_(binary), source_ptr_(&source_[0]) { @@ -350,25 +382,15 @@ class Program { *program_ = clCreateProgramWithBinary(context(), 1, &dev, &length_, reinterpret_cast<const unsigned char**>(&source_ptr_), &status1, &status2); - CheckError(status1); - CheckError(status2); + CLError::Check(status1, "clCreateProgramWithBinary (binary status)"); + CLError::Check(status2, "clCreateProgramWithBinary"); } // Compiles the device program and returns whether or not there where any warnings/errors - BuildStatus Build(const Device &device, std::vector<std::string> &options) { + void Build(const Device &device, std::vector<std::string> &options) { auto options_string = std::accumulate(options.begin(), options.end(), std::string{" "}); const cl_device_id dev = device(); - auto status = clBuildProgram(*program_, 1, &dev, options_string.c_str(), nullptr, nullptr); - if (status == CL_BUILD_PROGRAM_FAILURE) { - return BuildStatus::kError; - } - else if (status == CL_INVALID_BINARY) { - return BuildStatus::kInvalid; - } - else { - CheckError(status); - return BuildStatus::kSuccess; - } + CheckError(clBuildProgram(*program_, 1, &dev, options_string.c_str(), nullptr, nullptr)); } // Retrieves the warning/error message from the compiler (if any) @@ -416,7 +438,7 @@ class Queue { // Regular constructor with memory management explicit Queue(const Context &context, const Device &device): - queue_(new cl_command_queue, [](cl_command_queue* s) { CheckError(clReleaseCommandQueue(*s)); + queue_(new cl_command_queue, [](cl_command_queue* s) { CheckErrorDtor(clReleaseCommandQueue(*s)); delete s; }) { auto status = CL_SUCCESS; #ifdef CL_VERSION_2_0 @@ -425,15 +447,17 @@ class Queue { { cl_queue_properties properties[] = {CL_QUEUE_PROPERTIES, CL_QUEUE_PROFILING_ENABLE, 0}; *queue_ = clCreateCommandQueueWithProperties(context(), device(), properties, &status); + CLError::Check(status, "clCreateCommandQueueWithProperties"); } else { *queue_ = clCreateCommandQueue(context(), device(), CL_QUEUE_PROFILING_ENABLE, &status); + CLError::Check(status, "clCreateCommandQueue"); } #else *queue_ = clCreateCommandQueue(context(), device(), CL_QUEUE_PROFILING_ENABLE, &status); + CLError::Check(status, "clCreateCommandQueue"); #endif - CheckError(status); } // Synchronizes the queue @@ -525,7 +549,7 @@ class Buffer { if (access_ == BufferAccess::kWriteOnly) { flags = CL_MEM_WRITE_ONLY; } auto status = CL_SUCCESS; *buffer_ = clCreateBuffer(context(), flags, size*sizeof(T), nullptr, &status); - CheckError(status); + CLError::Check(status, "clCreateBuffer"); } // As above, but now with read/write access as a default @@ -546,18 +570,24 @@ class Buffer { // Copies from device to host: reading the device buffer a-synchronously void ReadAsync(const Queue &queue, const size_t size, T* host, const size_t offset = 0) const { - if (access_ == BufferAccess::kWriteOnly) { Error("reading from a write-only buffer"); } + if (access_ == BufferAccess::kWriteOnly) { + throw LogicError("Buffer: reading from a write-only buffer"); + } CheckError(clEnqueueReadBuffer(queue(), *buffer_, CL_FALSE, offset*sizeof(T), size*sizeof(T), host, 0, nullptr, nullptr)); } void ReadAsync(const Queue &queue, const size_t size, std::vector<T> &host, const size_t offset = 0) const { - if (host.size() < size) { Error("target host buffer is too small"); } + if (host.size() < size) { + throw LogicError("Buffer: target host buffer is too small"); + } ReadAsync(queue, size, host.data(), offset); } void ReadAsync(const Queue &queue, const size_t size, BufferHost<T> &host, const size_t offset = 0) const { - if (host.size() < size) { Error("target host buffer is too small"); } + if (host.size() < size) { + throw LogicError("Buffer: target host buffer is too small"); + } ReadAsync(queue, size, host.data(), offset); } @@ -577,8 +607,12 @@ class Buffer { // Copies from host to device: writing the device buffer a-synchronously void WriteAsync(const Queue &queue, const size_t size, const T* host, const size_t offset = 0) { - if (access_ == BufferAccess::kReadOnly) { Error("writing to a read-only buffer"); } - if (GetSize() < (offset+size)*sizeof(T)) { Error("target device buffer is too small"); } + if (access_ == BufferAccess::kReadOnly) { + throw LogicError("Buffer: writing to a read-only buffer"); + } + if (GetSize() < (offset+size)*sizeof(T)) { + throw LogicError("Buffer: target device buffer is too small"); + } CheckError(clEnqueueWriteBuffer(queue(), *buffer_, CL_FALSE, offset*sizeof(T), size*sizeof(T), host, 0, nullptr, nullptr)); } @@ -644,10 +678,10 @@ class Kernel { // Regular constructor with memory management explicit Kernel(const Program &program, const std::string &name): - kernel_(new cl_kernel, [](cl_kernel* k) { CheckError(clReleaseKernel(*k)); delete k; }) { + kernel_(new cl_kernel, [](cl_kernel* k) { CheckErrorDtor(clReleaseKernel(*k)); delete k; }) { auto status = CL_SUCCESS; *kernel_ = clCreateKernel(program(), name.c_str(), &status); - CheckError(status); + CLError::Check(status, "clCreateKernel"); } // Sets a kernel argument at the indicated position |