diff --git a/doc/source/misc.rst b/doc/source/misc.rst index 20a248ece84d3b11efa1f75360263765ffbd6182..20c7d5a3f0e9e23d78acdc0b309d5ab3b4a110bc 100644 --- a/doc/source/misc.rst +++ b/doc/source/misc.rst @@ -99,6 +99,7 @@ Version 2011.1 Allow the creation of arrys in C and Fortran order. * Add :func:`pyopencl.enqueue_copy`. Deprecate all other transfer functions. * Add support for numerous extensions, among them device fission. +* Add a compiler cache. Version 0.92 ------------ diff --git a/doc/source/runtime.rst b/doc/source/runtime.rst index 509df45b182ea6aa8effced4bac6356a4b4e8337..3b714df32057a7d6cc7a5d6e6990a3bb63f54981 100644 --- a/doc/source/runtime.rst +++ b/doc/source/runtime.rst @@ -686,7 +686,6 @@ Programs and Kernels Returns *self*. .. versionchanged:: 2011.1 - *options* may now also be a :class:`list` of :class:`str`. .. attribute:: kernel_name diff --git a/pyopencl/__init__.py b/pyopencl/__init__.py index bf8b7ae27c82c8f3dbec970c591f43a15be51918..78d31202b8ec3872ba86d99ab33037f0bb6c1ecf 100644 --- a/pyopencl/__init__.py +++ b/pyopencl/__init__.py @@ -120,22 +120,19 @@ def _add_functionality(): # }}} - # {{{ Program - def program_getattr(self, attr): - try: - pi_attr = getattr(_cl.program_info, attr.upper()) - except AttributeError: + # {{{ _Program (the internal, non-caching version) + + def program_get_build_logs(self): + build_logs = [] + for dev in self.get_info(_cl.program_info.DEVICES): try: - knl = Kernel(self, attr) - # Nvidia does not raise errors even for invalid names, - # but this will give an error if the kernel is invalid. - knl.num_args - return knl - except LogicError: - raise AttributeError("'%s' was not found as a program info attribute or as a kernel name" - % attr) - else: - return self.get_info(pi_attr) + log = self.get_build_info(dev, program_build_info.LOG) + except: + log = "" + + build_logs.append((dev, log)) + + return build_logs def program_build(self, options=[], devices=None): if isinstance(options, list): @@ -144,23 +141,31 @@ def _add_functionality(): try: self._build(options=options, devices=devices) except Exception, e: - build_logs = [] - for dev in self.devices: - try: - log = self.get_build_info(dev, program_build_info.LOG) - except: - log = "" - - build_logs.append((dev, log)) + from pytools import Record + class ErrorRecord(Record): + pass raise _cl.RuntimeError( - str(e) + "\n\n" + (75*"="+"\n").join( - "Build on %s:\n\n%s" % (dev, log) for dev, log in build_logs)) + ErrorRecord( + what=lambda : e.what + "\n\n" + (75*"="+"\n").join( + "Build on %s:\n\n%s" % (dev, log) + for dev, log in self._get_build_logs()), + code=lambda : e.code, + routine=lambda : e.routine)) + + message = (75*"="+"\n").join( + "Build on %s succeeded, but said:\n\n%s" % (dev, log) + for dev, log in self._get_build_logs() + if log) + + if message: + from warnings import warn + warn("Build succeeded, but resulted in non-empty logs:\n"+message) return self - Program.__getattr__ = program_getattr - Program.build = program_build + _cl._Program._get_build_logs = program_get_build_logs + _cl._Program.build = program_build # }}} @@ -269,6 +274,7 @@ def _add_functionality(): # }}} # {{{ ImageFormat + def image_format_repr(self): return "ImageFormat(%s, %s)" % ( channel_order.to_string(self.channel_order, @@ -277,9 +283,11 @@ def _add_functionality(): "")) ImageFormat.__repr__ = image_format_repr + # }}} # {{{ Image + class ImageInfoGetter: def __init__(self, event): from warnings import warn @@ -311,6 +319,7 @@ def _add_functionality(): # }}} # {{{ Event + def event_wait(self): wait_for_events([self]) return self @@ -319,6 +328,32 @@ def _add_functionality(): # }}} + # {{{ Error + + def error_str(self): + val = self.args[0] + result = "%s failed: %s" % (val.routine(), + status_code.to_string(val.code()).lower().replace("_", " ")) + if val.what(): + result += " - " + val.what() + return result + + def error_code(self): + return self.args[0].code() + + def error_routine(self): + return self.args[0].routine() + + def error_what(self): + return self.args[0].what() + + Error.__str__ = error_str + Error.code = property(error_code) + Error.routine = property(error_routine) + Error.what = property(error_what) + + # }}} + if _cl.have_gl(): def gl_object_get_gl_object(self): return self.get_gl_object_info()[1] @@ -331,6 +366,62 @@ _add_functionality() +# {{{ Program (including caching support) + +class Program(object): + def __init__(self, context, arg1, arg2=None): + if arg2 is None: + self._context = context + self._source = arg1 + self._prg = None + else: + self._prg = _cl.Program(context, arg1, arg2) + + def _get_prg(self): + if self._prg is not None: + return self._prg + else: + # "no program" can only happen in from-source case. + from warnings import warn + warn("Pre-build attribute access defeats compiler caching.", stacklevel=3) + + self._prg = _cl._Program(self._context, self._source) + del self._context + del self._source + return self._prg + + def get_info(self, arg): + return self._get_prg().get_info(arg) + + def __getattr__(self, attr): + try: + pi_attr = getattr(_cl.program_info, attr.upper()) + except AttributeError: + try: + knl = Kernel(self._get_prg(), attr) + # Nvidia does not raise errors even for invalid names, + # but this will give an error if the kernel is invalid. + knl.num_args + return knl + except LogicError: + raise AttributeError("'%s' was not found as a program " + "info attribute or as a kernel name" % attr) + else: + return self.get_info(pi_attr) + + def build(self, options=[], devices=None, cache_dir=None): + if self._prg is not None: + self._prg._build(options, devices) + else: + from pyopencl.cache import create_built_program_from_source_cached + self._prg = create_built_program_from_source_cached( + self._context, self._source, options, devices, + cache_dir=None) + + return self + + # }}} + # {{{ convenience ------------------------------------------------------------- def create_some_context(interactive=True): try: @@ -467,4 +558,6 @@ def enqueue_copy(queue, dest, src, **kwargs): # }}} + + # vim: foldmethod=marker diff --git a/pyopencl/cache.py b/pyopencl/cache.py new file mode 100644 index 0000000000000000000000000000000000000000..09828f75ce9103fdb8beb9f5d4138938bec0fc28 --- /dev/null +++ b/pyopencl/cache.py @@ -0,0 +1,441 @@ +"""PyOpenCL compiler cache.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2011 Andreas Kloeckner" + +import pyopencl._cl as _cl +import re +import sys +import os +from pytools import Record + +try: + import hashlib + new_hash = hashlib.md5 +except ImportError: + # for Python << 2.5 + import md5 + new_hash = md5.new + + + + +def _erase_dir(dir): + from os import listdir, unlink, rmdir + from os.path import join + for name in listdir(dir): + unlink(join(dir, name)) + rmdir(dir) + + + + +# {{{ cleanup + +class CleanupBase(object): + pass + +class CleanupManager(CleanupBase): + def __init__(self): + self.cleanups = [] + + def register(self, c): + self.cleanups.insert(0, c) + + def clean_up(self): + for c in self.cleanups: + c.clean_up() + + def error_clean_up(self): + for c in self.cleanups: + c.error_clean_up() + +class CacheLockManager(CleanupBase): + def __init__(self, cleanup_m, cache_dir): + if cache_dir is not None: + self.lock_file = os.path.join(cache_dir, "lock") + + attempts = 0 + while True: + try: + self.fd = os.open(self.lock_file, + os.O_CREAT | os.O_WRONLY | os.O_EXCL) + break + except OSError: + pass + + from time import sleep + sleep(1) + + attempts += 1 + + if attempts > 10: + from warnings import warn + warn("could not obtain cache lock--delete '%s' if necessary" + % self.lock_file) + + cleanup_m.register(self) + + def clean_up(self): + import os + os.close(self.fd) + os.unlink(self.lock_file) + + def error_clean_up(self): + pass + +class ModuleCacheDirManager(CleanupBase): + def __init__(self, cleanup_m, path): + from os import mkdir + + self.path = path + try: + mkdir(self.path) + cleanup_m.register(self) + self.existed = False + except OSError: + self.existed = True + + def sub(self, n): + from os.path import join + return join(self.path, n) + + def reset(self): + import os + _erase_dir(self.path) + os.mkdir(self.path) + + def clean_up(self): + pass + + def error_clean_up(self): + _erase_dir(self.path) + +# }}} + +# {{{ #include dependency handling + +C_INCLUDE_RE = re.compile(r'^\s*\#\s*include\s+[<"]([^">]+)[">]', + re.MULTILINE) + +def get_dependencies(src, include_path): + result = {} + + from os.path import realpath, join + + def _inner(src): + for match in C_INCLUDE_RE.finditer(src): + included = match.group(1) + + found = False + for ipath in include_path: + included_file_name = realpath(join(ipath, included)) + + if included_file_name not in result: + try: + src_file = open(included_file_name, "rt") + except IOError: + continue + + checksum = new_hash() + included_src = src_file.read() + checksum.update(included_src) + src_file.close() + + _inner(included_src) + + result[included_file_name] = ( + checksum.hexdigest(), os.stat(included_file_name).st_mtime) + + found = True + break # stop searching the include path + + if not found: + pass + + _inner(src) + + result = list(result.iteritems()) + result.sort() + + return result + + + + +def get_file_md5sum(fname): + checksum = new_hash() + inf = open(fname) + checksum.update(inf.read()) + inf.close() + return checksum.hexdigest() + + + + +def check_dependencies(deps): + for name, date, md5sum in deps: + try: + possibly_updated = os.stat(name).st_mtime != date + except OSError, e: + return False + else: + if possibly_updated and md5sum != get_file_md5sum(name): + return False + + return True + +# }}} + +# {{{ key generation + +def get_device_cache_id(device): + from pyopencl.version import VERSION + platform = device.platform + return (VERSION, + platform.vendor, platform.name, platform.version, + device.vendor, device.name, device.version, device.driver_version, + device.opencl_c_version) + + + + +def get_cache_key(device, options, src): + checksum = new_hash() + checksum.update(src) + checksum.update(" ".join(options)) + checksum.update(str(get_device_cache_id(device))) + return checksum.hexdigest() + +# }}} + + + + + +def retrieve_from_cache(cache_dir, cache_key): + class _InvalidInfoFile(RuntimeError): + pass + + from os.path import join, isdir + module_cache_dir = join(cache_dir, cache_key) + if not isdir(module_cache_dir): + return None + + cleanup_m = CleanupManager() + try: + lock_m = CacheLockManager(cleanup_m, cache_dir) + + mod_cache_dir_m = ModuleCacheDirManager(cleanup_m, module_cache_dir) + info_path = mod_cache_dir_m.sub("info") + binary_path = mod_cache_dir_m.sub("binary") + source_path = mod_cache_dir_m.sub("source.cl") + + # {{{ load info file + + try: + from cPickle import load + + try: + info_file = open(info_path) + except IOError: + raise _InvalidInfoFile() + + try: + info = load(info_file) + except EOFError: + raise _InvalidInfoFile() + finally: + info_file.close() + + except _InvalidInfoFile: + mod_cache_dir_m.reset() + from warnings import warn + warn("PyOpenCL encountered an invalid info file for cache key %s" + % cache_key) + return None + + # }}} + + # {{{ load binary + + binary_file = open(binary_path, "rb") + try: + binary = binary_file.read() + finally: + binary_file.close() + + # }}} + + if check_dependencies(info.dependencies): + print "YES" + return binary, info.log + else: + mod_cache_dir_m.reset() + + except: + cleanup_m.error_clean_up() + raise + finally: + cleanup_m.clean_up() + +# {{{ top-level driver + +class _SourceInfo(Record): + pass + +def _create_built_program_from_source_cached(ctx, src, options, devices, cache_dir): + include_path = ["."] + [ + option[2:] + for option in options + if option.startswith("-I") or option.startswith("/I")] + + if cache_dir is None: + from os.path import join + from tempfile import gettempdir + cache_dir = join(gettempdir(), + "pyopencl-compiler-cache-v1-uid%s" % os.getuid()) + + # {{{ ensure cache directory exists + + try: + os.mkdir(cache_dir) + except OSError, e: + from errno import EEXIST + if e.errno != EEXIST: + raise + + # }}} + + if devices is None: + devices = ctx.devices + + cache_keys = [get_cache_key(device, options, src) for device in devices] + + binaries = [] + to_be_built_indices = [] + logs = [] + for i, (device, cache_key) in enumerate(zip(devices, cache_keys)): + cache_result = retrieve_from_cache(cache_dir, cache_key) + + if cache_result is None: + to_be_built_indices.append(i) + binaries.append(None) + logs.append(None) + else: + binary, log = cache_result + binaries.append(binary) + logs.append(log) + + # {{{ build on the build-needing devices, in one go + + result = None + already_built = False + + if to_be_built_indices: + prg = _cl._Program(ctx, src) + prg.build(options, [devices[i] for i in to_be_built_indices]) + + prg_devs = prg.get_info(_cl.program_info.DEVICES) + prg_bins = prg.get_info(_cl.program_info.BINARIES) + prg_logs = prg._get_build_logs() + + for i, dest_index in enumerate(to_be_built_indices): + assert prg_devs[i] == devices[dest_index] + binaries[dest_index] = prg_bins[i] + _, logs[dest_index] = prg_logs[i] + + if len(to_be_built_indices) == len(devices): + # Important special case: if code for all devices was built, + # then we may simply use the program that we just built as the + # final result. + + result = prg + already_built = True + + if result is None: + result = _cl._Program(ctx, devices, binaries) + + # }}} + + # {{{ save binaries to cache + + if to_be_built_indices: + cleanup_m = CleanupManager() + try: + lock_m = CacheLockManager(cleanup_m, cache_dir) + + for i in to_be_built_indices: + cache_key = cache_keys[i] + device = devices[i] + binary = binaries[i] + + mod_cache_dir_m = ModuleCacheDirManager(cleanup_m, + join(cache_dir, cache_key)) + info_path = mod_cache_dir_m.sub("info") + binary_path = mod_cache_dir_m.sub("binary") + source_path = mod_cache_dir_m.sub("source.cl") + + outf = open(source_path, "wt") + outf.write(src) + outf.close() + + outf = open(binary_path, "wb") + outf.write(binary) + outf.close() + + from cPickle import dump + info_file = open(info_path, "wb") + dump(_SourceInfo( + dependencies=get_dependencies(src, include_path), + log=logs[i]), info_file) + info_file.close() + + except: + cleanup_m.error_clean_up() + raise + finally: + cleanup_m.clean_up() + + # }}} + + return result, already_built + + + + +def create_built_program_from_source_cached(ctx, src, options=[], devices=None, + cache_dir=None): + try: + if cache_dir != False: + prg, already_built = _create_built_program_from_source_cached( + ctx, src, options, devices, cache_dir) + else: + prg = _cl._Program(ctx, src) + already_built = False + + except Exception, e: + from pyopencl import Error + if (isinstance(e, Error) + and e.code == _cl.status_code.BUILD_PROGRAM_FAILURE): + # no need to try again + raise + + from warnings import warn + from traceback import format_exc + warn("PyOpenCL compiler caching failed with an exception:\n" + "[begin exception]\n%s[end exception]" + % format_exc()) + + prg = _cl._Program(ctx, src) + already_built = False + + if not already_built: + prg.build(options, devices) + + return prg + +# }}} + +# vim: foldmethod=marker diff --git a/src/wrapper/wrap_cl.hpp b/src/wrapper/wrap_cl.hpp index 45865d455c7c3d233a40b5a91cf26c0c729e03eb..855deb924b811ad27385036629ea9ee1837e6fc2 100644 --- a/src/wrapper/wrap_cl.hpp +++ b/src/wrapper/wrap_cl.hpp @@ -139,7 +139,7 @@ std::cerr \ << "PyOpenCL WARNING: a clean-up operation failed (dead context maybe?)" \ << std::endl \ - << pyopencl::error::make_message(#NAME, status_code) \ + << #NAME " failed with code " << status_code \ << std::endl; \ } @@ -246,22 +246,8 @@ namespace pyopencl cl_int m_code; public: - static std::string make_message(const char *rout, cl_int c, const char *msg=0) - { - std::string result = rout; - result += " failed: "; - result += cl_error_to_str(c); - if (msg) - { - result += " - "; - result += msg; - } - return result; - } - - error(const char *rout, cl_int c, const char *msg=0) - : std::runtime_error(make_message(rout, c, msg)), - m_routine(rout), m_code(c) + error(const char *rout, cl_int c, const char *msg="") + : std::runtime_error(msg), m_routine(rout), m_code(c) { } const char *routine() const @@ -281,79 +267,6 @@ namespace pyopencl || code() == CL_OUT_OF_HOST_MEMORY); } - static const char *cl_error_to_str(cl_int e) - { - switch (e) - { - case CL_SUCCESS: return "success"; - case CL_DEVICE_NOT_FOUND: return "device not found"; - case CL_DEVICE_NOT_AVAILABLE: return "device not available"; -#if !(defined(CL_PLATFORM_NVIDIA) && CL_PLATFORM_NVIDIA == 0x3001) - case CL_COMPILER_NOT_AVAILABLE: return "device compiler not available"; -#endif - case CL_MEM_OBJECT_ALLOCATION_FAILURE: return "mem object allocation failure"; - case CL_OUT_OF_RESOURCES: return "out of resources"; - case CL_OUT_OF_HOST_MEMORY: return "out of host memory"; - case CL_PROFILING_INFO_NOT_AVAILABLE: return "profiling info not available"; - case CL_MEM_COPY_OVERLAP: return "mem copy overlap"; - case CL_IMAGE_FORMAT_MISMATCH: return "image format mismatch"; - case CL_IMAGE_FORMAT_NOT_SUPPORTED: return "image format not supported"; - case CL_BUILD_PROGRAM_FAILURE: return "build program failure"; - case CL_MAP_FAILURE: return "map failure"; - - case CL_INVALID_VALUE: return "invalid value"; - case CL_INVALID_DEVICE_TYPE: return "invalid device type"; - case CL_INVALID_PLATFORM: return "invalid platform"; - case CL_INVALID_DEVICE: return "invalid device"; - case CL_INVALID_CONTEXT: return "invalid context"; - case CL_INVALID_QUEUE_PROPERTIES: return "invalid queue properties"; - case CL_INVALID_COMMAND_QUEUE: return "invalid command queue"; - case CL_INVALID_HOST_PTR: return "invalid host ptr"; - case CL_INVALID_MEM_OBJECT: return "invalid mem object"; - case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR: return "invalid image format descriptor"; - case CL_INVALID_IMAGE_SIZE: return "invalid image size"; - case CL_INVALID_SAMPLER: return "invalid sampler"; - case CL_INVALID_BINARY: return "invalid binary"; - case CL_INVALID_BUILD_OPTIONS: return "invalid build options"; - case CL_INVALID_PROGRAM: return "invalid program"; - case CL_INVALID_PROGRAM_EXECUTABLE: return "invalid program executable"; - case CL_INVALID_KERNEL_NAME: return "invalid kernel name"; - case CL_INVALID_KERNEL_DEFINITION: return "invalid kernel definition"; - case CL_INVALID_KERNEL: return "invalid kernel"; - case CL_INVALID_ARG_INDEX: return "invalid arg index"; - case CL_INVALID_ARG_VALUE: return "invalid arg value"; - case CL_INVALID_ARG_SIZE: return "invalid arg size"; - case CL_INVALID_KERNEL_ARGS: return "invalid kernel args"; - case CL_INVALID_WORK_DIMENSION: return "invalid work dimension"; - case CL_INVALID_WORK_GROUP_SIZE: return "invalid work group size"; - case CL_INVALID_WORK_ITEM_SIZE: return "invalid work item size"; - case CL_INVALID_GLOBAL_OFFSET: return "invalid global offset"; - case CL_INVALID_EVENT_WAIT_LIST: return "invalid event wait list"; - case CL_INVALID_EVENT: return "invalid event"; - case CL_INVALID_OPERATION: return "invalid operation"; - case CL_INVALID_GL_OBJECT: return "invalid gl object"; - case CL_INVALID_BUFFER_SIZE: return "invalid buffer size"; - case CL_INVALID_MIP_LEVEL: return "invalid mip level"; - -#if defined(cl_khr_gl_sharing) && (cl_khr_gl_sharing >= 1) - case CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR: return "invalid gl sharegroup reference number"; -#endif - -#ifdef CL_VERSION_1_1 - case CL_MISALIGNED_SUB_BUFFER_OFFSET: return "misaligned sub-buffer offset"; - case CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST: return "exec status error for events in wait list"; - case CL_INVALID_GLOBAL_WORK_SIZE: return "invalid global work size"; -#endif - -#ifdef cl_ext_device_fission - case CL_DEVICE_PARTITION_FAILED_EXT: return "device partition failed"; - case CL_INVALID_PARTITION_COUNT_EXT: return "invalid partition count"; - case CL_INVALID_PARTITION_NAME_EXT: return "invalid partition name"; -#endif - - default: return "invalid/unknown error code"; - } - } }; // }}} @@ -2499,7 +2412,7 @@ namespace pyopencl { if (py_devices.ptr() == Py_None) { - PYOPENCL_CALL_GUARDED(clBuildProgram, + PYOPENCL_CALL_GUARDED_THREADED(clBuildProgram, (m_program, 0, 0, options.c_str(), 0 ,0)); } else @@ -2508,7 +2421,7 @@ namespace pyopencl PYTHON_FOREACH(py_dev, py_devices) devices.push_back( py::extract(py_dev)().data()); - PYOPENCL_CALL_GUARDED(clBuildProgram, + PYOPENCL_CALL_GUARDED_THREADED(clBuildProgram, (m_program, devices.size(), devices.empty( ) ? NULL : &devices.front(), options.c_str(), 0 ,0)); } @@ -2557,6 +2470,7 @@ namespace pyopencl std::vector devices; std::vector binaries; std::vector sizes; + std::vector binary_statuses; int num_devices = len(py_devices); if (len(py_binaries) != num_devices) @@ -2578,15 +2492,25 @@ namespace pyopencl sizes.push_back(len); } + binary_statuses.resize(num_devices); + cl_int status_code; cl_program result = clCreateProgramWithBinary( ctx.data(), num_devices, - devices.empty( ) ? NULL : &devices.front(), sizes.empty( ) ? NULL : &sizes.front(), binaries.empty( ) ? NULL : &binaries.front(), - /*binary_status*/ 0, &status_code); + devices.empty( ) ? NULL : &devices.front(), + sizes.empty( ) ? NULL : &sizes.front(), + binaries.empty( ) ? NULL : &binaries.front(), + binary_statuses.empty( ) ? NULL : &binary_statuses.front(), + &status_code); PYOPENCL_PRINT_CALL_TRACE("clCreateProgramWithBinary"); if (status_code != CL_SUCCESS) throw pyopencl::error("clCreateProgramWithBinary", status_code); + /* + for (int i = 0; i < num_devices; ++i) + printf("%d:%d\n", i, binary_statuses[i]); + */ + try { return new program(result, false); diff --git a/src/wrapper/wrap_cl_part_2.cpp b/src/wrapper/wrap_cl_part_2.cpp index 6e8807fd9adde53ceea30b9a1f97be6971925520..fc3736a7a4c0bddaced3a8dde830502712f01119 100644 --- a/src/wrapper/wrap_cl_part_2.cpp +++ b/src/wrapper/wrap_cl_part_2.cpp @@ -117,7 +117,7 @@ void pyopencl_expose_part_2() // {{{ program { typedef program cls; - py::class_("Program", py::no_init) + py::class_("_Program", py::no_init) .def("__init__", make_constructor( create_program_with_source, py::default_call_policies(), diff --git a/src/wrapper/wrap_constants.cpp b/src/wrapper/wrap_constants.cpp index 0a602af4245235a043de1dd75a455afdd3f8436e..501e1d19cb8b2ab30eef54f234c029dc3e9a6cd1 100644 --- a/src/wrapper/wrap_constants.cpp +++ b/src/wrapper/wrap_constants.cpp @@ -22,19 +22,20 @@ namespace void translate_cl_error(const error &err) { if (err.code() == CL_MEM_OBJECT_ALLOCATION_FAILURE) - PyErr_SetString(CLMemoryError.get(), err.what()); + PyErr_SetObject(CLMemoryError.get(), py::object(err).ptr()); else if (err.code() <= CL_INVALID_VALUE) - PyErr_SetString(CLLogicError.get(), err.what()); + PyErr_SetObject(CLLogicError.get(), py::object(err).ptr()); else if (err.code() > CL_INVALID_VALUE && err.code() < CL_SUCCESS) - PyErr_SetString(CLRuntimeError.get(), err.what()); + PyErr_SetObject(CLRuntimeError.get(), py::object(err).ptr()); else - PyErr_SetString(CLError.get(), err.what()); + PyErr_SetObject(CLError.get(), py::object(err).ptr()); } // {{{ 'fake' constant scopes + class status_code { }; class platform_info { }; class device_type { }; class device_info { }; @@ -105,6 +106,85 @@ void pyopencl_expose_constants() #define ADD_ATTR_SUFFIX(PREFIX, NAME, SUFFIX) \ cls.attr(#NAME) = CL_##PREFIX##NAME##SUFFIX + { + typedef error cls; + py::class_ ("_error", py::no_init) + .DEF_SIMPLE_METHOD(routine) + .DEF_SIMPLE_METHOD(code) + .DEF_SIMPLE_METHOD(what) + ; + } + + { + py::class_ cls("status_code", py::no_init); + + ADD_ATTR(, SUCCESS); + ADD_ATTR(, DEVICE_NOT_FOUND); + ADD_ATTR(, DEVICE_NOT_AVAILABLE); +#if !(defined(CL_PLATFORM_NVIDIA) && CL_PLATFORM_NVIDIA == 0x3001) + ADD_ATTR(, COMPILER_NOT_AVAILABLE); +#endif + ADD_ATTR(, MEM_OBJECT_ALLOCATION_FAILURE); + ADD_ATTR(, OUT_OF_RESOURCES); + ADD_ATTR(, OUT_OF_HOST_MEMORY); + ADD_ATTR(, PROFILING_INFO_NOT_AVAILABLE); + ADD_ATTR(, MEM_COPY_OVERLAP); + ADD_ATTR(, IMAGE_FORMAT_MISMATCH); + ADD_ATTR(, IMAGE_FORMAT_NOT_SUPPORTED); + ADD_ATTR(, BUILD_PROGRAM_FAILURE); + ADD_ATTR(, MAP_FAILURE); + + ADD_ATTR(, INVALID_VALUE); + ADD_ATTR(, INVALID_DEVICE_TYPE); + ADD_ATTR(, INVALID_PLATFORM); + ADD_ATTR(, INVALID_DEVICE); + ADD_ATTR(, INVALID_CONTEXT); + ADD_ATTR(, INVALID_QUEUE_PROPERTIES); + ADD_ATTR(, INVALID_COMMAND_QUEUE); + ADD_ATTR(, INVALID_HOST_PTR); + ADD_ATTR(, INVALID_MEM_OBJECT); + ADD_ATTR(, INVALID_IMAGE_FORMAT_DESCRIPTOR); + ADD_ATTR(, INVALID_IMAGE_SIZE); + ADD_ATTR(, INVALID_SAMPLER); + ADD_ATTR(, INVALID_BINARY); + ADD_ATTR(, INVALID_BUILD_OPTIONS); + ADD_ATTR(, INVALID_PROGRAM); + ADD_ATTR(, INVALID_PROGRAM_EXECUTABLE); + ADD_ATTR(, INVALID_KERNEL_NAME); + ADD_ATTR(, INVALID_KERNEL_DEFINITION); + ADD_ATTR(, INVALID_KERNEL); + ADD_ATTR(, INVALID_ARG_INDEX); + ADD_ATTR(, INVALID_ARG_VALUE); + ADD_ATTR(, INVALID_ARG_SIZE); + ADD_ATTR(, INVALID_KERNEL_ARGS); + ADD_ATTR(, INVALID_WORK_DIMENSION); + ADD_ATTR(, INVALID_WORK_GROUP_SIZE); + ADD_ATTR(, INVALID_WORK_ITEM_SIZE); + ADD_ATTR(, INVALID_GLOBAL_OFFSET); + ADD_ATTR(, INVALID_EVENT_WAIT_LIST); + ADD_ATTR(, INVALID_EVENT); + ADD_ATTR(, INVALID_OPERATION); + ADD_ATTR(, INVALID_GL_OBJECT); + ADD_ATTR(, INVALID_BUFFER_SIZE); + ADD_ATTR(, INVALID_MIP_LEVEL); + +#if defined(cl_khr_gl_sharing) && (cl_khr_gl_sharing >= 1) + ADD_ATTR(, INVALID_GL_SHAREGROUP_REFERENCE_KHR); +#endif + +#ifdef CL_VERSION_1_1 + ADD_ATTR(, MISALIGNED_SUB_BUFFER_OFFSET); + ADD_ATTR(, EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST); + ADD_ATTR(, INVALID_GLOBAL_WORK_SIZE); +#endif + +#ifdef cl_ext_device_fission + ADD_ATTR(, DEVICE_PARTITION_FAILED_EXT); + ADD_ATTR(, INVALID_PARTITION_COUNT_EXT); + ADD_ATTR(, INVALID_PARTITION_NAME_EXT); +#endif + } + { py::class_ cls("platform_info", py::no_init); ADD_ATTR(PLATFORM_, PROFILE);