From 8c421ee7cb780c566bf8cf467ff3425e010c9a36 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 29 Mar 2022 12:33:14 -0500 Subject: [PATCH] Switch from pybind11 to nanobind --- .github/workflows/ci.yml | 7 +- .gitignore | 2 + .gitlab-ci.yml | 25 ----- CMakeLists.txt | 79 ++++++++++++++ pyopencl/__init__.py | 20 +--- pyopencl/invoker.py | 2 +- pyproject.toml | 12 ++- setup.cfg | 1 + setup.py | 169 +++++++++++++---------------- src/tools.hpp | 6 +- src/wrap_cl.cpp | 4 +- src/wrap_cl.hpp | 189 ++++++++++++++++++-------------- src/wrap_cl_part_1.cpp | 66 +++++------- src/wrap_cl_part_2.cpp | 227 +++++++++++++++++---------------------- src/wrap_constants.cpp | 74 +++++++------ src/wrap_helpers.hpp | 24 +++-- src/wrap_mempool.cpp | 53 +++++---- 17 files changed, 504 insertions(+), 456 deletions(-) create mode 100644 CMakeLists.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bae85f548..e8d6128be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,11 @@ jobs: curl -L -O https://tiker.net/ci-support-v0 . ci-support-v0 build_py_project_in_conda_env + + # Avoid linting local directory, where native module + # cannot be imported. + rm -Rf "$(get_proj_name)" + run_pylint "$(get_proj_name)" test/*.py mypy: @@ -122,7 +127,7 @@ jobs: curl -L -O https://tiker.net/ci-support-v0 . ci-support-v0 - ./configure.py --cxxflags= --ldflags= --cl-libname=OpenCL + ./configure.py --cl-libname=OpenCL build_py_project_in_conda_env test_py_project diff --git a/.gitignore b/.gitignore index ebb751cdd..8bc43458d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +_skbuild + .pydevproject .project .settings diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b4760ecf6..a67ce45e6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -165,31 +165,6 @@ Python 3 POCL (+GL and special functions): reports: junit: test/pytest.xml -PyPy3 POCL: - script: | - export PY_EXE=pypy3 - export PYOPENCL_TEST=portable:pthread - export NO_DOCTESTS=1 - - # On pypy, this seems to install old versions from the package index - # independently of whether newer ones are already present. - rm -f pyproject.toml - export EXTRA_INSTALL="pybind11 numpy mako" - - curl -L -O https://tiker.net/ci-support-v0 - . ci-support-v0 - build_py_project_in_venv - test_py_project - - tags: - - pypy - - pocl - except: - - tags - artifacts: - reports: - junit: test/pytest.xml - Flake8: script: | curl -L -O https://tiker.net/ci-support-v0 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..6617536da --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 3.17...3.22) + +project(pyopencl) + +if (NOT SKBUILD) + message(FATAL_ERROR "This CMake file should be executed via scikit-build. " + "Please run\n$ pip install .") +endif() + +# {{{ Constrain FindPython to find the Python version used by scikit-build + +if (SKBUILD) + message(STATUS "Python_VERSION ${PYTHON_VERSION_STRING}") + message(STATUS "Python_EXECUTABLE ${PYTHON_EXECUTABLE}") + message(STATUS "Python_INCLUDE_DIR ${PYTHON_INCLUDE_DIR}") + message(STATUS "Python_LIBRARIES ${PYTHON_LIBRARY}") + set(Python_VERSION "${PYTHON_VERSION_STRING}") + set(Python_EXECUTABLE "${PYTHON_EXECUTABLE}") + set(Python_INCLUDE_DIR "${PYTHON_INCLUDE_DIR}") + set(Python_LIBRARIES "${PYTHON_LIBRARY}") +endif() +find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) + +# }}} + +# {{{ Detect nanobind and import it + +execute_process( + COMMAND + "${PYTHON_EXECUTABLE}" -c "import nanobind; print(nanobind.cmake_dir())" + OUTPUT_VARIABLE _tmp_dir + OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ECHO STDOUT) + list(APPEND CMAKE_PREFIX_PATH "${_tmp_dir}") + +# }}} + +link_directories(${PYOPENCL_CL_LIB_DIRS}) + +find_package(nanobind CONFIG REQUIRED) +find_package(NumPy REQUIRED) +find_package(OpenCL REQUIRED) + +include_directories(${OpenCL_INCLUDE_DIR} ${NumPy_INCLUDE_DIRS}) + +nanobind_add_module( + _cl + NB_STATIC # Build static libnanobind (the extension module itself remains a shared library) + src/wrap_constants.cpp + src/wrap_cl.cpp + src/wrap_cl_part_1.cpp + src/wrap_cl_part_2.cpp + src/wrap_mempool.cpp + src/bitlog.cpp +) + +target_link_libraries(_cl PRIVATE ${OpenCL_LIBRARY}) + +target_compile_definitions(_cl + PRIVATE + PYGPU_PACKAGE=pyopencl + PYGPU_PYOPENCL +) + +if (PYOPENCL_PRETEND_CL_VERSION) + target_compile_definitions( + _cl PRIVATE PYOPENCL_PRETEND_CL_VERSION=${PYOPENCL_PRETEND_CL_VERSION}) +endif() + +if (PYOPENCL_ENABLE_GL) + target_compile_definitions(_cl PRIVATE HAVE_GL=1) +endif() + +if (PYOPENCL_USE_SHIPPED_EXT) + target_compile_definitions(_cl PRIVATE PYOPENCL_USE_SHIPPED_EXT=1) +endif() + +install(TARGETS _cl LIBRARY DESTINATION .) + +# vim: foldmethod=marker:sw=2 diff --git a/pyopencl/__init__.py b/pyopencl/__init__.py index 2c250ae04..0a026eedb 100644 --- a/pyopencl/__init__.py +++ b/pyopencl/__init__.py @@ -535,7 +535,7 @@ def build(self, options=None, devices=None, cache_dir=None): def _build_and_catch_errors(self, build_func, options_bytes, source=None): try: return build_func() - except _cl.RuntimeError as e: + except RuntimeError as e: msg = str(e) if options_bytes: msg = msg + "\n(options: %s)" % options_bytes.decode("utf-8") @@ -553,7 +553,7 @@ def _build_and_catch_errors(self, build_func, options_bytes, source=None): code = e.code routine = e.routine - err = _cl.RuntimeError( + err = RuntimeError( _cl._ErrorRecord( msg=msg, code=code, @@ -835,7 +835,7 @@ def kernel_set_arg_types(self, arg_types): # }}} from pyopencl.invoker import generate_enqueue_and_set_args - enqueue, my_set_args = \ + self._enqueue, self.set_args = \ generate_enqueue_and_set_args( self.function_name, len(arg_types), self.num_args, @@ -844,20 +844,6 @@ def kernel_set_arg_types(self, arg_types): work_around_arg_count_bug=work_around_arg_count_bug, devs=self.context.devices) - # Make ourselves a kernel-specific class, so that we're able to override - # __call__. Inspired by https://stackoverflow.com/a/38541437 - class KernelWithCustomEnqueue(type(self)): - __call__ = enqueue - set_args = my_set_args - - try: - self.__class__ = KernelWithCustomEnqueue - except TypeError: - # __class__ assignment may not work in all cases, due to differing - # object layouts. Fall back to bouncing through kernel_call below. - self._enqueue = enqueue - self.set_args = my_set_args - def kernel_get_work_group_info(self, param, device): cache_key = (param, device.int_ptr) try: diff --git a/pyopencl/invoker.py b/pyopencl/invoker.py index 570986762..c37a8e4db 100644 --- a/pyopencl/invoker.py +++ b/pyopencl/invoker.py @@ -379,7 +379,7 @@ def _check_arg_size(function_name, num_cl_args, arg_types, devs): from pytools.py_codegen import PicklableModule invoker_cache: WriteOncePersistentDict[Any, Tuple[PicklableModule, str]] \ = WriteOncePersistentDict( - "pyopencl-invoker-cache-v41", + "pyopencl-invoker-cache-v42-nano", key_builder=_NumpyTypesKeyBuilder(), in_mem_cache_size=0) diff --git a/pyproject.toml b/pyproject.toml index c4235848d..f19e88ed0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,17 +5,19 @@ requires = [ "wheel>=0.34.2", "numpy;python_version >= '3.9' and platform_python_implementation == 'PyPy'", "oldest-supported-numpy;python_version < '3.9' or platform_python_implementation != 'PyPy'", - "pybind11>=2.5.0" + "scikit-build", + "cmake>=3.17", + "nanobind>=1.9.2", + "ninja; platform_system!='Windows'", ] build-backend = "setuptools.build_meta" - [tool.cibuildwheel] test-command = "pytest {project}/test" test-extras = ["test"] [tool.cibuildwheel.linux] -skip = ["pp37*", "cp36-*", "cp37-*", "*_i686"] +skip = ["pp*", "cp36-*", "cp37-*", "*_i686"] test-command = "" before-all = [ "yum install -y git openssl-devel ruby", @@ -42,6 +44,10 @@ archs = "x86_64 arm64" # https://github.com/conda-forge/pyopencl-feedstock/blob/6f3c5de59b18c9518abba3cb94f6ae92964553f8/recipe/meta.yaml#L62-L63 +[tool.cibuildwheel.macos.environment] +# Needed for full C++17 support +MACOSX_DEPLOYMENT_TARGET = "10.14" + [tool.cibuildwheel.windows] skip = ["*-win32", "pp*", "cp36-*", "cp37-*"] test-command = "" diff --git a/setup.cfg b/setup.cfg index 35874d85d..245608c04 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,6 +15,7 @@ multiline-quotes = """ # enable-flake8-bugbear # enable-isort +# disable-editable-pip-install [isort] line_length = 85 diff --git a/setup.py b/setup.py index ea693c066..ebc03a67b 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,6 @@ __copyright__ = """ Copyright (C) 2009-15 Andreas Kloeckner -Copyright (C) 2013 Marko Bencun """ __license__ = """ @@ -29,7 +28,7 @@ import os import sys -from os.path import exists +from os.path import exists, join sys.path.append(os.path.dirname(__file__)) @@ -37,47 +36,7 @@ def get_config_schema(): from aksetup_helper import ( - ConfigSchema, IncludeDir, Libraries, LibraryDir, Option, StringListOption, - Switch) - - default_cxxflags = [ - # Required for pybind11: - # https://pybind11.readthedocs.io/en/stable/faq.html#someclass-declared-with-greater-visibility-than-the-type-of-its-field-someclass-member-wattributes - "-fvisibility=hidden" - ] - - if "darwin" in sys.platform: - import platform - osx_ver, _, _ = platform.mac_ver() - osx_ver = ".".join(osx_ver.split(".")[:2]) - - sysroot_paths = [ - "/Applications/Xcode.app/Contents/Developer/Platforms/" - "MacOSX.platform/Developer/SDKs/MacOSX%s.sdk" % osx_ver, - "/Developer/SDKs/MacOSX%s.sdk" % osx_ver - ] - - default_libs = [] - default_cxxflags = default_cxxflags + [ - "-stdlib=libc++", "-mmacosx-version-min=10.7"] - - from os.path import isdir - for srp in sysroot_paths: - if isdir(srp): - default_cxxflags.extend(["-isysroot", srp]) - break - - default_ldflags = default_cxxflags[:] + ["-Wl,-framework,OpenCL"] - - else: - default_libs = ["OpenCL"] - if "linux" in sys.platform: - # Requested in - # https://github.com/inducer/pyopencl/issues/132#issuecomment-314713573 - # to make life with Altera FPGAs less painful by default. - default_ldflags = ["-Wl,--no-as-needed"] - else: - default_ldflags = [] + ConfigSchema, IncludeDir, Libraries, LibraryDir, Option, Switch) return ConfigSchema([ Switch("CL_TRACE", False, "Enable OpenCL API tracing"), @@ -88,6 +47,10 @@ def get_config_schema(): + " a broader range of vendor-specific OpenCL extension attributes" + " than the standard Khronos (or vendor specific) CL/cl_ext.h."), + IncludeDir("CL", []), + LibraryDir("CL", []), + Libraries("CL", []), + # This is here because the ocl-icd wheel declares but doesn't define # clSetProgramSpecializationConstant as of 2021-05-22, and providing # configuration options for this to pip install isn't easy. Being @@ -101,14 +64,6 @@ def get_config_schema(): os.environ.get("PYOPENCL_CL_PRETEND_VERSION", None), "Dotted CL version (e.g. 1.2) which you'd like to use."), - IncludeDir("CL", []), - LibraryDir("CL", []), - Libraries("CL", default_libs), - - StringListOption("CXXFLAGS", default_cxxflags, - help="Any extra C++ compiler options to include"), - StringListOption("LDFLAGS", default_ldflags, - help="Any extra linker options to include"), ]) @@ -116,46 +71,91 @@ def get_config_schema(): def main(): - from setuptools import find_packages + try: + import nanobind # noqa: F401 + from setuptools import find_packages + from skbuild import setup + except ImportError: + print("The preferred way to invoke 'setup.py' is via pip, as in 'pip " + "install .'. If you wish to run the setup script directly, you must " + "first install the build dependencies listed in pyproject.toml!", + file=sys.stderr) + raise - from aksetup_helper import ( - ExtensionUsingNumpy, PybindBuildExtCommand, check_git_submodules, - check_pybind11, get_config, get_pybind_include, hack_distutils, setup) - check_pybind11() - check_git_submodules() + # {{{ import aksetup_helper bits + + prev_path = sys.path[:] + # FIXME skbuild seems to remove this. Why? + sys.path.append(".") - hack_distutils() - conf = get_config(get_config_schema(), - warn_about_no_config=False) + from aksetup_helper import check_git_submodules, get_config - extra_defines = {} + sys.path = prev_path + + # }}} + + check_git_submodules() - extra_defines["PYGPU_PACKAGE"] = "pyopencl" - extra_defines["PYGPU_PYOPENCL"] = "1" + conf = get_config(get_config_schema(), warn_about_no_config=False) + + cmake_args = [] if conf["CL_TRACE"]: - extra_defines["PYOPENCL_TRACE"] = 1 + cmake_args.append("-DPYOPENCL_TRACE:BOOL=ON") if conf["CL_ENABLE_GL"]: - extra_defines["HAVE_GL"] = 1 + cmake_args.append("-DPYOPENCL_ENABLE_GL:BOOL=ON") if conf["CL_USE_SHIPPED_EXT"]: - extra_defines["PYOPENCL_USE_SHIPPED_EXT"] = 1 + cmake_args.append("-DPYOPENCL_USE_SHIPPED_EXT:BOOL=ON") + + if conf["CL_LIB_DIR"] or conf["CL_LIBNAME"]: + if conf["CL_LIBNAME"] and not conf["CL_LIB_DIR"]: + raise ValueError("must specify CL_LIBNAME if CL_LIB_DIR is provided") + if conf["CL_LIB_DIR"] and not conf["CL_LIBNAME"]: + from warnings import warn + warn("Only CL_LIB_DIR specified, assuming 'OpenCL' as library name. " + "Specify 'CL_LIBNAME' if this is undesired.") + conf["CL_LIBNAME"] = ["OpenCL"] + + if len(conf["CL_LIBNAME"]) != 1: + raise ValueError("Specifying more than one value for 'CL_LIBNAME' " + "is no longer supported.") + if len(conf["CL_LIB_DIR"]) != 1: + raise ValueError("Specifying more than one value for 'CL_LIB_DIR' " + "is no longer supported.") + + lib_dir, = conf["CL_LIB_DIR"] + libname, = conf["CL_LIBNAME"] + + cl_library = None + + for prefix in ["lib", ""]: + for suffix in [".so", ".dylib", ".lib"]: + try_cl_library = join(lib_dir, prefix+libname+suffix) + if exists(try_cl_library): + cl_library = try_cl_library + + if cl_library is None: + cl_library = join(lib_dir, "lib"+libname) + + cmake_args.append(f"-DOpenCL_LIBRARY={cl_library}") + + if conf["CL_INC_DIR"]: + cmake_args.append(f"-DOpenCL_INCLUDE_DIR:LIST=" + f"{';'.join(conf['CL_INC_DIR'])}") if conf["CL_PRETEND_VERSION"]: try: major, minor = [int(x) for x in conf["CL_PRETEND_VERSION"].split(".")] - extra_defines["PYOPENCL_PRETEND_CL_VERSION"] = \ - 0x1000*major + 0x10 * minor + cmake_args.append( + "-DPYOPENCL_PRETEND_CL_VERSION:INT=" + f"{0x1000*major + 0x10 * minor}") except Exception: print("CL_PRETEND_VERSION must be of the form M.N, " "with two integers M and N") raise - conf["EXTRA_DEFINES"] = extra_defines - - INCLUDE_DIRS = conf["CL_INC_DIR"] + ["pybind11/include"] # noqa: N806 - ver_dic = {} version_file = open("pyopencl/version.py") try: @@ -202,30 +202,9 @@ def main(): "Topic :: Scientific/Engineering :: Physics", ], - # build info packages=find_packages(), - ext_modules=[ - ExtensionUsingNumpy("pyopencl._cl", - [ - "src/wrap_constants.cpp", - "src/wrap_cl.cpp", - "src/wrap_cl_part_1.cpp", - "src/wrap_cl_part_2.cpp", - "src/wrap_mempool.cpp", - "src/bitlog.cpp", - ], - include_dirs=INCLUDE_DIRS + [ - get_pybind_include(), - ], - library_dirs=conf["CL_LIB_DIR"], - libraries=conf["CL_LIBNAME"], - define_macros=list(conf["EXTRA_DEFINES"].items()), - extra_compile_args=conf["CXXFLAGS"], - extra_link_args=conf["LDFLAGS"], - language="c++", - ), - ], + cmake_args=cmake_args, python_requires="~=3.8", install_requires=[ @@ -250,7 +229,7 @@ def main(): ] }, - cmdclass={"build_ext": PybindBuildExtCommand}, + cmake_install_dir="pyopencl", zip_safe=False) diff --git a/src/tools.hpp b/src/tools.hpp index f8d019599..e14fd13c0 100644 --- a/src/tools.hpp +++ b/src/tools.hpp @@ -28,7 +28,7 @@ #define _ASDFDAFVVAFF_PYCUDA_HEADER_SEEN_TOOLS_HPP -#include +#include #include #include @@ -51,9 +51,9 @@ namespace pyopencl inline void run_python_gc() { - namespace py = pybind11; + namespace py = nanobind; - py::module_::import("gc").attr("collect")(); + py::module_::import_("gc").attr("collect")(); } diff --git a/src/wrap_cl.cpp b/src/wrap_cl.cpp index 9a0125247..8c1710476 100644 --- a/src/wrap_cl.cpp +++ b/src/wrap_cl.cpp @@ -47,10 +47,10 @@ static bool import_numpy_helper() return true; } -PYBIND11_MODULE(_cl, m) +NB_MODULE(_cl, m) { if (!import_numpy_helper()) - throw py::error_already_set(); + throw py::python_error(); pyopencl_expose_constants(m); pyopencl_expose_part_1(m); diff --git a/src/wrap_cl.hpp b/src/wrap_cl.hpp index 4991d3eaf..cf6261e76 100644 --- a/src/wrap_cl.hpp +++ b/src/wrap_cl.hpp @@ -96,6 +96,7 @@ #include #include #include +#include #include #include "wrap_helpers.hpp" #include @@ -472,7 +473,7 @@ namespace pyopencl cl_program m_program; public: - error(const char *routine, cl_int c, const char *msg="") + error(std::string const &routine, cl_int c, std::string const &msg="") : std::runtime_error(msg), m_routine(routine), m_code(c), m_program_initialized(false), m_program(nullptr) { } @@ -508,6 +509,27 @@ namespace pyopencl program *get_program() const; + // FIXME: Inheritance from builtin_exception confuses nanobind + const char *err_what() + { + return what(); + } + + void set_error() const + { + py::object err_obj = py::cast(*this); + py::object errors_mod = py::module_::import_("pyopencl._errors"); + + if (code() == CL_MEM_OBJECT_ALLOCATION_FAILURE) + PyErr_SetObject(errors_mod.attr("MemoryError").ptr(), err_obj.ptr()); + else if (code() <= CL_INVALID_VALUE) + PyErr_SetObject(errors_mod.attr("LogicError").ptr(), err_obj.ptr()); + else if (code() > CL_INVALID_VALUE && code() < CL_SUCCESS) + PyErr_SetObject(errors_mod.attr("RuntimeError").ptr(), err_obj.ptr()); + else + PyErr_SetObject(errors_mod.attr("Error").ptr(), err_obj.ptr()); + } + }; // }}} @@ -528,14 +550,13 @@ namespace pyopencl // {{{ buffer interface helper - class py_buffer_wrapper : public noncopyable { private: bool m_initialized; - public: - Py_buffer m_buf; + public: + Py_buffer m_buf; py_buffer_wrapper() : m_initialized(false) @@ -552,13 +573,13 @@ namespace pyopencl { PyErr_Clear(); if (PyObject_GetBuffer(obj, &m_buf, flags_wo_cont | PyBUF_F_CONTIGUOUS)) - throw py::error_already_set(); + throw py::python_error(); } } else #endif if (PyObject_GetBuffer(obj, &m_buf, flags)) - throw py::error_already_set(); + throw py::python_error(); m_initialized = true; } @@ -1333,7 +1354,7 @@ namespace pyopencl inline - context *create_context_inner(py::object py_devices, py::object py_properties, + void create_context_inner(context *self, py::object py_devices, py::object py_properties, py::object py_dev_type) { std::vector props @@ -1380,7 +1401,7 @@ namespace pyopencl try { - return new context(ctx, false); + new (self) context(ctx, false); } catch (...) { @@ -1391,11 +1412,11 @@ namespace pyopencl inline - context *create_context(py::object py_devices, py::object py_properties, + void create_context(context *self, py::object py_devices, py::object py_properties, py::object py_dev_type) { PYOPENCL_RETRY_RETURN_IF_MEM_ERROR( - return create_context_inner(py_devices, py_properties, py_dev_type); + create_context_inner(self, py_devices, py_properties, py_dev_type); ) } @@ -1556,8 +1577,8 @@ namespace pyopencl { if (m_finalized) { - auto mod_warnings(py::module_::import("warnings")); - auto mod_cl(py::module_::import("pyopencl")); + auto mod_warnings(py::module_::import_("warnings")); + auto mod_cl(py::module_::import_("pyopencl")); mod_warnings.attr("warn")( "Command queue used after exit of context manager. " "This is deprecated and will stop working in 2023.", @@ -1877,6 +1898,7 @@ namespace pyopencl std::mutex m_mutex; std::condition_variable m_condvar; + // FIXME: Should implement GC traversal so that these can be collected. py::object m_py_event; py::object m_py_callback; @@ -2005,7 +2027,7 @@ namespace pyopencl { if (m_ward.get()) { - return py::reinterpret_borrow(m_ward->m_buf.obj); + return py::borrow(m_ward->m_buf.obj); } else return py::none(); @@ -2125,7 +2147,7 @@ namespace pyopencl inline - user_event *create_user_event(context &ctx) + void create_user_event(user_event *self, context &ctx) { cl_int status_code; PYOPENCL_PRINT_CALL_TRACE("clCreateUserEvent"); @@ -2136,7 +2158,7 @@ namespace pyopencl try { - return new user_event(evt, false); + new (self) user_event(evt, false); } catch (...) { @@ -2232,7 +2254,7 @@ namespace pyopencl py::object hostbuf() { if (m_hostbuf.get()) - return py::reinterpret_borrow(m_hostbuf->m_buf.obj); + return py::borrow(m_hostbuf->m_buf.obj); else return py::none(); } @@ -2364,23 +2386,21 @@ namespace pyopencl } } - buffer *getitem(py::slice slc) const + buffer *getitem(py::object slc) const { PYOPENCL_BUFFER_SIZE_T start, end, stride, length; + if (!PySlice_Check(slc.ptr())) + throw pyopencl::error("Buffer.__getitem__", CL_INVALID_VALUE, + "Buffer slice must be a slice object"); + size_t my_length; PYOPENCL_CALL_GUARDED(clGetMemObjectInfo, (data(), CL_MEM_SIZE, sizeof(my_length), &my_length, 0)); -#if PY_VERSION_HEX >= 0x03020000 if (PySlice_GetIndicesEx(slc.ptr(), my_length, &start, &end, &stride, &length) != 0) - throw py::error_already_set(); -#else - if (PySlice_GetIndicesEx(reinterpret_cast(slc.ptr()), - my_length, &start, &end, &stride, &length) != 0) - throw py::error_already_set(); -#endif + throw py::python_error(); if (stride != 1) throw pyopencl::error("Buffer.__getitem__", CL_INVALID_VALUE, @@ -2403,8 +2423,8 @@ namespace pyopencl // {{{ buffer creation - inline - buffer *create_buffer_py( + inline void create_buffer_py( + buffer *self, context &ctx, cl_mem_flags flags, size_t size, @@ -2447,7 +2467,7 @@ namespace pyopencl try { - return new buffer(mem, false, std::move(retained_buf_obj)); + new (self) buffer(mem, false, std::move(retained_buf_obj)); } catch (...) { @@ -2870,12 +2890,10 @@ namespace pyopencl // {{{ image formats inline - cl_image_format *make_image_format(cl_channel_order ord, cl_channel_type tp) + void set_image_format(cl_image_format *self, cl_channel_order ord, cl_channel_type tp) { - std::unique_ptr result(new cl_image_format); - result->image_channel_order = ord; - result->image_channel_data_type = tp; - return result.release(); + self->image_channel_order = ord; + self->image_channel_data_type = tp; } inline @@ -2957,7 +2975,8 @@ namespace pyopencl // {{{ image creation inline - image *create_image( + void create_image( + image *self, context const &ctx, cl_mem_flags flags, cl_image_format const &fmt, @@ -3067,7 +3086,7 @@ namespace pyopencl try { - return new image(mem, false, std::move(retained_buf_obj)); + new (self) image(mem, false, std::move(retained_buf_obj)); } catch (...) { @@ -3079,7 +3098,8 @@ namespace pyopencl #if PYOPENCL_CL_VERSION >= 0x1020 inline - image *create_image_from_desc( + void create_image_from_desc( + image *self, context const &ctx, cl_mem_flags flags, cl_image_format const &fmt, @@ -3120,7 +3140,7 @@ namespace pyopencl try { - return new image(mem, false, std::move(retained_buf_obj)); + new (self) image(mem, false, std::move(retained_buf_obj)); } catch (...) { @@ -3372,7 +3392,8 @@ namespace pyopencl #if PYOPENCL_CL_VERSION >= 0x2000 inline - pipe *create_pipe( + void create_pipe( + pipe *self, context const &ctx, cl_mem_flags flags, cl_uint pipe_packet_size, @@ -3408,7 +3429,7 @@ namespace pyopencl try { - return new pipe(mem, false); + new (self) pipe(mem, false); } catch (...) { @@ -3514,7 +3535,7 @@ namespace pyopencl try { - result = py::object(py::reinterpret_steal(PyArray_NewFromDescr( + result = py::object(py::steal(PyArray_NewFromDescr( &PyArray_Type, tp_descr, shape.size(), shape.empty() ? nullptr : &shape.front(), @@ -3602,7 +3623,7 @@ namespace pyopencl throw; } - py::object result = py::reinterpret_steal(PyArray_NewFromDescr( + py::object result = py::steal(PyArray_NewFromDescr( &PyArray_Type, tp_descr, shape.size(), shape.empty() ? nullptr : &shape.front(), @@ -4393,7 +4414,7 @@ namespace pyopencl for (unsigned i = 0; i < sizes.size(); ++i) { py::object binary_pyobj( - py::reinterpret_steal( + py::steal( #if PY_VERSION_HEX >= 0x03000000 PyBytes_FromStringAndSize( reinterpret_cast(ptr), sizes[i]) @@ -4464,7 +4485,7 @@ namespace pyopencl } } - void build(std::string options, py::object py_devices) + void build(py::bytes options, py::object py_devices) { PYOPENCL_PARSE_PY_DEVICES; @@ -4474,7 +4495,7 @@ namespace pyopencl } #if PYOPENCL_CL_VERSION >= 0x1020 - void compile(std::string options, py::object py_devices, + void compile(py::bytes options, py::object py_devices, py::object py_headers) { PYOPENCL_PARSE_PY_DEVICES; @@ -4486,7 +4507,7 @@ namespace pyopencl std::vector programs; for (py::handle name_hdr_tup_py: py_headers) { - py::tuple name_hdr_tup = py::reinterpret_borrow(name_hdr_tup_py); + py::tuple name_hdr_tup = py::borrow(name_hdr_tup_py); if (py::len(name_hdr_tup) != 2) throw error("Program.compile", CL_INVALID_VALUE, "epxected (name, header) tuple in headers list"); @@ -4527,7 +4548,8 @@ namespace pyopencl inline - program *create_program_with_source( + void create_program_with_source( + program *self, context &ctx, std::string const &src) { @@ -4543,7 +4565,7 @@ namespace pyopencl try { - return new program(result, false, program::KND_SOURCE); + new (self) program(result, false, program::KND_SOURCE); } catch (...) { @@ -4557,7 +4579,8 @@ namespace pyopencl inline - program *create_program_with_binary( + void create_program_with_binary( + program *self, context &ctx, py::sequence py_devices, py::sequence py_binaries) @@ -4609,7 +4632,7 @@ namespace pyopencl try { - return new program(result, false, program::KND_BINARY); + new (self) program(result, false, program::KND_BINARY); } catch (...) { @@ -4656,7 +4679,7 @@ namespace pyopencl inline program *create_program_with_il( context &ctx, - std::string const &src) + py::bytes const &src) { cl_int status_code; PYOPENCL_PRINT_CALL_TRACE("clCreateProgramWithIL"); @@ -4686,7 +4709,7 @@ namespace pyopencl program *link_program( context &ctx, py::object py_programs, - std::string const &options, + py::bytes options, py::object py_devices ) { @@ -4851,33 +4874,36 @@ namespace pyopencl void set_arg_buf_pack(cl_uint arg_index, py::handle py_typechar, py::handle obj) { - std::string typechar_str(py::cast(py_typechar)); + py::bytes typechar_str(py::cast(py_typechar)); if (typechar_str.size() != 1) throw error("Kernel.set_arg_buf_pack", CL_INVALID_VALUE, "type char argument must have exactly one character"); - char typechar = typechar_str[0]; + char typechar = *typechar_str.c_str(); -#define PYOPENCL_KERNEL_PACK_AND_SET_ARG(TYPECH_VAL, TYPE) \ +#define PYOPENCL_KERNEL_PACK_AND_SET_ARG(TYPECH_VAL, TYPE, CAST_TYPE) \ case TYPECH_VAL: \ { \ - TYPE val = py::cast(obj); \ + TYPE val = (TYPE) py::cast(obj); \ PYOPENCL_CALL_GUARDED(clSetKernelArg, (m_kernel, arg_index, sizeof(val), &val)); \ break; \ } switch (typechar) { - PYOPENCL_KERNEL_PACK_AND_SET_ARG('c', char) - PYOPENCL_KERNEL_PACK_AND_SET_ARG('b', signed char) - PYOPENCL_KERNEL_PACK_AND_SET_ARG('B', unsigned char) - PYOPENCL_KERNEL_PACK_AND_SET_ARG('h', short) - PYOPENCL_KERNEL_PACK_AND_SET_ARG('H', unsigned short) - PYOPENCL_KERNEL_PACK_AND_SET_ARG('i', int) - PYOPENCL_KERNEL_PACK_AND_SET_ARG('I', unsigned int) - PYOPENCL_KERNEL_PACK_AND_SET_ARG('l', long) - PYOPENCL_KERNEL_PACK_AND_SET_ARG('L', unsigned long) - PYOPENCL_KERNEL_PACK_AND_SET_ARG('f', float) - PYOPENCL_KERNEL_PACK_AND_SET_ARG('d', double) + // FIXME: nanobind thinks of char as "short string", not number + // The detour via 'int' may lose data. + PYOPENCL_KERNEL_PACK_AND_SET_ARG('c', char, int) + PYOPENCL_KERNEL_PACK_AND_SET_ARG('b', signed char, int) + PYOPENCL_KERNEL_PACK_AND_SET_ARG('B', unsigned char, int) + + PYOPENCL_KERNEL_PACK_AND_SET_ARG('h', short, short) + PYOPENCL_KERNEL_PACK_AND_SET_ARG('H', unsigned short, unsigned short) + PYOPENCL_KERNEL_PACK_AND_SET_ARG('i', int, int) + PYOPENCL_KERNEL_PACK_AND_SET_ARG('I', unsigned int, unsigned int) + PYOPENCL_KERNEL_PACK_AND_SET_ARG('l', long, long) + PYOPENCL_KERNEL_PACK_AND_SET_ARG('L', unsigned long, unsigned long) + PYOPENCL_KERNEL_PACK_AND_SET_ARG('f', float, float) + PYOPENCL_KERNEL_PACK_AND_SET_ARG('d', double, double) default: throw error("Kernel.set_arg_buf_pack", CL_INVALID_VALUE, "invalid type char"); @@ -4896,7 +4922,7 @@ namespace pyopencl { buf_wrapper.get(py_buffer.ptr(), PyBUF_ANY_CONTIGUOUS); } - catch (py::error_already_set &) + catch (py::python_error &) { PyErr_Clear(); throw error("Kernel.set_arg", CL_INVALID_VALUE, @@ -5163,9 +5189,13 @@ namespace pyopencl std::string("when processing arg#") + std::to_string(arg_index+1) \ + std::string(" (1-based): ") + std::string(err.what())); \ \ - auto mod_cl_ary(py::module_::import("pyopencl.array")); \ + auto mod_cl_ary(py::module_::import_("pyopencl.array")); \ auto cls_array(mod_cl_ary.attr("Array")); \ - if (arg_value.ptr() && py::isinstance(arg_value, cls_array)) \ + int isinstance_result = PyObject_IsInstance(arg_value.ptr(), cls_array.ptr()); \ + if (isinstance_result == -1) \ + throw py::python_error(); \ + \ + if (arg_value.ptr() && isinstance_result) \ msg.append( \ " (perhaps you meant to pass 'array.data' instead of the array itself?)"); \ throw error(err.routine().c_str(), err.code(), msg.c_str()); \ @@ -5449,7 +5479,7 @@ namespace pyopencl #define PYOPENCL_WRAP_BUFFER_CREATOR(TYPE, NAME, CL_NAME, ARGS, CL_ARGS) \ inline \ - TYPE *NAME ARGS \ + void NAME ARGS \ { \ cl_int status_code; \ PYOPENCL_PRINT_CALL_TRACE(#CL_NAME); \ @@ -5460,7 +5490,7 @@ namespace pyopencl \ try \ { \ - return new TYPE(mem, false); \ + new (self) TYPE(mem, false); \ } \ catch (...) \ { \ @@ -5474,33 +5504,34 @@ namespace pyopencl PYOPENCL_WRAP_BUFFER_CREATOR(gl_buffer, create_from_gl_buffer, clCreateFromGLBuffer, - (context &ctx, cl_mem_flags flags, GLuint bufobj), + (gl_buffer *self, context &ctx, cl_mem_flags flags, GLuint bufobj), (ctx.data(), flags, bufobj, &status_code)); PYOPENCL_WRAP_BUFFER_CREATOR(gl_texture, create_from_gl_texture_2d, clCreateFromGLTexture2D, - (context &ctx, cl_mem_flags flags, + (gl_texture *self, context &ctx, cl_mem_flags flags, GLenum texture_target, GLint miplevel, GLuint texture), (ctx.data(), flags, texture_target, miplevel, texture, &status_code)); PYOPENCL_WRAP_BUFFER_CREATOR(gl_texture, create_from_gl_texture_3d, clCreateFromGLTexture3D, - (context &ctx, cl_mem_flags flags, + (gl_texture *self, context &ctx, cl_mem_flags flags, GLenum texture_target, GLint miplevel, GLuint texture), (ctx.data(), flags, texture_target, miplevel, texture, &status_code)); PYOPENCL_WRAP_BUFFER_CREATOR(gl_renderbuffer, create_from_gl_renderbuffer, clCreateFromGLRenderbuffer, - (context &ctx, cl_mem_flags flags, GLuint renderbuffer), + (gl_renderbuffer *self, context &ctx, cl_mem_flags flags, GLuint renderbuffer), (ctx.data(), flags, renderbuffer, &status_code)); inline - gl_texture *create_from_gl_texture( + void create_from_gl_texture( + gl_texture *self, context &ctx, cl_mem_flags flags, GLenum texture_target, GLint miplevel, GLuint texture, unsigned dims) { if (dims == 2) - return create_from_gl_texture_2d(ctx, flags, texture_target, miplevel, texture); + return create_from_gl_texture_2d(self, ctx, flags, texture_target, miplevel, texture); else if (dims == 3) - return create_from_gl_texture_3d(ctx, flags, texture_target, miplevel, texture); + return create_from_gl_texture_3d(self, ctx, flags, texture_target, miplevel, texture); else throw pyopencl::error("Image", CL_INVALID_VALUE, "invalid dimension"); @@ -5766,7 +5797,7 @@ namespace pyopencl py::cast(mem_obj_py); PyArray_Descr *tp_descr; if (PyArray_DescrConverter(dtype.ptr(), &tp_descr) != NPY_SUCCEED) - throw py::error_already_set(); + throw py::python_error(); cl_mem_flags mem_flags; PYOPENCL_CALL_GUARDED(clGetMemObjectInfo, (mem_obj.data(), CL_MEM_FLAGS, sizeof(mem_flags), &mem_flags, 0)); @@ -5806,7 +5837,7 @@ namespace pyopencl (mem_obj.data(), CL_MEM_SIZE, sizeof(mem_obj_size), &mem_obj_size, 0)); - py::object result = py::reinterpret_steal(PyArray_NewFromDescr( + py::object result = py::steal(PyArray_NewFromDescr( &PyArray_Type, tp_descr, dims.size(), &dims.front(), /*strides*/ nullptr, host_ptr, ary_flags, /*obj*/nullptr)); diff --git a/src/wrap_cl_part_1.cpp b/src/wrap_cl_part_1.cpp index 37fb5141f..5f0fa1b43 100644 --- a/src/wrap_cl_part_1.cpp +++ b/src/wrap_cl_part_1.cpp @@ -47,9 +47,8 @@ void pyopencl_expose_part_1(py::module_ &m) .DEF_SIMPLE_METHOD(get_info) .def("get_devices", &cls::get_devices, py::arg("device_type")=CL_DEVICE_TYPE_ALL) - .def(py::self == py::self) - .def(py::self != py::self) .def("__hash__", &cls::hash) + PYOPENCL_EXPOSE_EQUALITY_TESTS PYOPENCL_EXPOSE_TO_FROM_INT_PTR(cl_platform_id) ; } @@ -61,8 +60,7 @@ void pyopencl_expose_part_1(py::module_ &m) typedef device cls; py::class_(m, "Device", py::dynamic_attr()) .DEF_SIMPLE_METHOD(get_info) - .def(py::self == py::self) - .def(py::self != py::self) + PYOPENCL_EXPOSE_EQUALITY_TESTS .def("__hash__", &cls::hash) #if PYOPENCL_CL_VERSION >= 0x1020 .DEF_SIMPLE_METHOD(create_sub_devices) @@ -81,26 +79,26 @@ void pyopencl_expose_part_1(py::module_ &m) { typedef context cls; - py::class_>(m, "Context", py::dynamic_attr()) + py::class_(m, "Context", py::dynamic_attr(), py::is_weak_referenceable()) .def( - py::init( - [](py::object py_devices, py::object py_properties, - py::object py_dev_type) - { - PYOPENCL_RETRY_RETURN_IF_MEM_ERROR( - return create_context_inner( - py_devices, - py_properties, - py_dev_type); - ) - }), + "__init__", + [](cls *self, py::object py_devices, py::object py_properties, + py::object py_dev_type) + { + PYOPENCL_RETRY_RETURN_IF_MEM_ERROR( + create_context_inner( + self, + py_devices, + py_properties, + py_dev_type); + ) + }, py::arg("devices").none(true)=py::none(), py::arg("properties").none(true)=py::none(), py::arg("dev_type").none(true)=py::none() ) .DEF_SIMPLE_METHOD(get_info) - .def(py::self == py::self) - .def(py::self != py::self) + PYOPENCL_EXPOSE_EQUALITY_TESTS .def("__hash__", &cls::hash) PYOPENCL_EXPOSE_TO_FROM_INT_PTR(cl_context) #if PYOPENCL_CL_VERSION >= 0x2010 @@ -114,7 +112,7 @@ void pyopencl_expose_part_1(py::module_ &m) // {{{ command queue { typedef command_queue cls; - py::class_>(m, "CommandQueue", py::dynamic_attr()) + py::class_(m, "CommandQueue", py::dynamic_attr()) .def( py::init(), py::arg("context"), @@ -127,8 +125,7 @@ void pyopencl_expose_part_1(py::module_ &m) #endif .DEF_SIMPLE_METHOD(flush) .DEF_SIMPLE_METHOD(finish) - .def(py::self == py::self) - .def(py::self != py::self) + PYOPENCL_EXPOSE_EQUALITY_TESTS .def("__hash__", &cls::hash) PYOPENCL_EXPOSE_TO_FROM_INT_PTR(cl_command_queue) ; @@ -139,12 +136,11 @@ void pyopencl_expose_part_1(py::module_ &m) // {{{ events/synchronization { typedef event cls; - py::class_(m, "Event", py::dynamic_attr()) + py::class_(m, "Event") .DEF_SIMPLE_METHOD(get_info) .DEF_SIMPLE_METHOD(get_profiling_info) .DEF_SIMPLE_METHOD(wait) - .def(py::self == py::self) - .def(py::self != py::self) + PYOPENCL_EXPOSE_EQUALITY_TESTS .def("__hash__", &cls::hash) PYOPENCL_EXPOSE_TO_FROM_INT_PTR(cl_event) #if PYOPENCL_CL_VERSION >= 0x1010 @@ -183,11 +179,9 @@ void pyopencl_expose_part_1(py::module_ &m) { typedef user_event cls; py::class_(m, "UserEvent", py::dynamic_attr()) - .def(py::init( - [](context &ctx) - { - return create_user_event(ctx); - }), + .def("__init__", + [](cls *self, context &ctx) + { create_user_event(self, ctx); }, py::arg("context")) .DEF_SIMPLE_METHOD(set_status) ; @@ -209,11 +203,10 @@ void pyopencl_expose_part_1(py::module_ &m) py::arg("dtype"), py::arg("order")="C") #endif - .def("__eq__", [](const cls &self, const cls &other){ return self == other; }) - .def("__ne__", [](const cls &self, const cls &other){ return self != other; }) + PYOPENCL_EXPOSE_EQUALITY_TESTS .def("__hash__", &cls::hash) - .def_property_readonly("int_ptr", to_int_ptr, + .def_prop_ro("int_ptr", to_int_ptr, "Return an integer corresponding to the pointer value " "of the underlying :c:type:`cl_mem`. " "Use :meth:`from_int_ptr` to turn back into a Python object." @@ -224,7 +217,7 @@ void pyopencl_expose_part_1(py::module_ &m) typedef memory_object cls; py::class_(m, "MemoryObject", py::dynamic_attr()) .DEF_SIMPLE_METHOD(release) - .def_property_readonly("hostbuf", &cls::hostbuf) + .def_prop_ro("hostbuf", &cls::hostbuf) .def_static("from_int_ptr", memory_object_from_int, "(static method) Return a new Python object referencing the C-level " @@ -257,10 +250,9 @@ void pyopencl_expose_part_1(py::module_ &m) typedef buffer cls; py::class_(m, "Buffer", py::dynamic_attr()) .def( - py::init( - [](context &ctx, cl_mem_flags flags, size_t size, py::object py_hostbuf) - { return create_buffer_py(ctx, flags, size, py_hostbuf); } - ), + "__init__", + [](cls *self, context &ctx, cl_mem_flags flags, size_t size, py::object py_hostbuf) + { create_buffer_py(self, ctx, flags, size, py_hostbuf); }, py::arg("context"), py::arg("flags"), py::arg("size")=0, diff --git a/src/wrap_cl_part_2.cpp b/src/wrap_cl_part_2.cpp index 769840e9f..807b45af6 100644 --- a/src/wrap_cl_part_2.cpp +++ b/src/wrap_cl_part_2.cpp @@ -65,22 +65,6 @@ namespace pyopencl { } #endif - -#if PYOPENCL_CL_VERSION >= 0x2000 - class svm_pointer_as_buffer - { - private: - svm_pointer &m_ptr; - - public: - svm_pointer_as_buffer(svm_pointer &ptr) - : m_ptr(ptr) - { } - - svm_pointer &ptr() const - { return m_ptr; } - }; -#endif } @@ -100,13 +84,15 @@ void pyopencl_expose_part_2(py::module_ &m) typedef cl_image_desc cls; py::class_(m, "ImageDescriptor") .def(py::init<>()) - .def_readwrite("image_type", &cls::image_type) - .def_property("shape", &image_desc_dummy_getter, image_desc_set_shape) - .def_readwrite("array_size", &cls::image_array_size) - .def_property("pitches", &image_desc_dummy_getter, image_desc_set_pitches) - .def_readwrite("num_mip_levels", &cls::num_mip_levels) - .def_readwrite("num_samples", &cls::num_samples) - .def_property("buffer", &image_desc_dummy_getter, image_desc_set_buffer) + .def_rw("image_type", &cls::image_type) + .def_prop_rw("shape", &image_desc_dummy_getter, image_desc_set_shape) + .def_rw("array_size", &cls::image_array_size) + .def_prop_rw("pitches", &image_desc_dummy_getter, image_desc_set_pitches) + .def_rw("num_mip_levels", &cls::num_mip_levels) + .def_rw("num_samples", &cls::num_samples) + .def_prop_rw("buffer", &image_desc_dummy_getter, image_desc_set_buffer, + py::arg("buffer").none() + ) ; } #endif @@ -115,17 +101,18 @@ void pyopencl_expose_part_2(py::module_ &m) typedef image cls; py::class_(m, "Image", py::dynamic_attr()) .def( - py::init( - []( - context const &ctx, - cl_mem_flags flags, - cl_image_format const &fmt, - py::sequence shape, - py::sequence pitches, - py::object buffer) - { - return create_image(ctx, flags, fmt, shape, pitches, buffer); - }), + "__init__", + []( + cls *self, + context const &ctx, + cl_mem_flags flags, + cl_image_format const &fmt, + py::sequence shape, + py::sequence pitches, + py::object buffer) + { + return create_image(self, ctx, flags, fmt, shape, pitches, buffer); + }, py::arg("context"), py::arg("flags"), py::arg("format"), @@ -135,16 +122,17 @@ void pyopencl_expose_part_2(py::module_ &m) ) #if PYOPENCL_CL_VERSION >= 0x1020 .def( - py::init( - []( - context const &ctx, - cl_mem_flags flags, - cl_image_format const &fmt, - cl_image_desc &desc, - py::object buffer) - { - return create_image_from_desc(ctx, flags, fmt, desc, buffer); - }), + "__init__", + []( + cls *self, + context const &ctx, + cl_mem_flags flags, + cl_image_format const &fmt, + cl_image_desc &desc, + py::object buffer) + { + create_image_from_desc(self, ctx, flags, fmt, desc, buffer); + }, py::arg("context"), py::arg("flags"), py::arg("format"), @@ -160,16 +148,16 @@ void pyopencl_expose_part_2(py::module_ &m) typedef cl_image_format cls; py::class_(m, "ImageFormat") .def( - py::init( - [](cl_channel_order ord, cl_channel_type tp) - { - return make_image_format(ord, tp); - })) - .def_readwrite("channel_order", &cls::image_channel_order) - .def_readwrite("channel_data_type", &cls::image_channel_data_type) - .def_property_readonly("channel_count", &get_image_format_channel_count) - .def_property_readonly("dtype_size", &get_image_format_channel_dtype_size) - .def_property_readonly("itemsize", &get_image_format_item_size) + "__init__", + [](cls *self, cl_channel_order ord, cl_channel_type tp) + { + set_image_format(self, ord, tp); + }) + .def_rw("channel_order", &cls::image_channel_order) + .def_rw("channel_data_type", &cls::image_channel_data_type) + .def_prop_ro("channel_count", &get_image_format_channel_count) + .def_prop_ro("dtype_size", &get_image_format_channel_dtype_size) + .def_prop_ro("itemsize", &get_image_format_item_size) ; } @@ -246,16 +234,17 @@ void pyopencl_expose_part_2(py::module_ &m) py::class_(m, "Pipe", py::dynamic_attr()) #if PYOPENCL_CL_VERSION >= 0x2000 .def( - py::init( - []( - context const &ctx, - cl_mem_flags flags, - cl_uint pipe_packet_size, - cl_uint pipe_max_packets, - py::sequence py_props) - { - return create_pipe(ctx, flags, pipe_packet_size, pipe_max_packets, py_props); - }), + "__init__", + []( + cls *self, + context const &ctx, + cl_mem_flags flags, + cl_uint pipe_packet_size, + cl_uint pipe_max_packets, + py::sequence py_props) + { + create_pipe(self, ctx, flags, pipe_packet_size, pipe_max_packets, py_props); + }, py::arg("context"), py::arg("flags"), py::arg("packet_size"), @@ -318,9 +307,9 @@ void pyopencl_expose_part_2(py::module_ &m) // For consistency, it may seem appropriate to use int_ptr here, but // that would work on both buffers and SVM, and passing a buffer pointer to // a kernel is going to lead to a bad time. - .def_property_readonly("svm_ptr", + .def_prop_ro("svm_ptr", [](cls &self) { return (intptr_t) self.svm_ptr(); }) - .def_property_readonly("size", [](cls &self) -> py::object + .def_prop_ro("size", [](cls &self) -> py::object { try { @@ -331,43 +320,25 @@ void pyopencl_expose_part_2(py::module_ &m) return py::none(); } }) - .def_property_readonly("buf", [](cls &self) -> svm_pointer_as_buffer * { - return new svm_pointer_as_buffer(self); - }, py::return_value_policy::reference_internal) - ; - } - - { - typedef svm_pointer_as_buffer cls; - py::class_(m, "_SVMPointerAsBuffer", pybind11::buffer_protocol()) - .def_buffer([](cls &self) -> pybind11::buffer_info - { + .def_prop_ro("buf", [](cls &self) -> py::ndarray> { size_t size; try { - size = self.ptr().size(); + size = self.size(); } catch (size_not_available) { throw pyopencl::error("SVMPointer buffer protocol", CL_INVALID_VALUE, "size of SVM is not known"); } - return pybind11::buffer_info( - // Pointer to buffer - self.ptr().svm_ptr(), - // Size of one scalar - sizeof(unsigned char), - // Python struct-style format descriptor - pybind11::format_descriptor::format(), - // Number of dimensions - 1, - // Buffer dimensions - { size }, - // Strides (in bytes) for each index - { sizeof(unsigned char) } - ); - }) - ; + + return py::ndarray>( + /* data = */ self.svm_ptr(), + /* ndim = */ 1, + /* shape pointer = */ &size, + /* owner = */ py::handle()); + }, py::rv_policy::reference_internal) + ; } // }}} @@ -402,15 +373,14 @@ void pyopencl_expose_part_2(py::module_ &m) py::arg("queue").none(true)=py::none(), py::arg("wait_for").none(true)=py::none() ) - .def(py::self == py::self) - .def(py::self != py::self) + PYOPENCL_EXPOSE_EQUALITY_TESTS .def("__hash__", [](cls &self) { return (intptr_t) self.svm_ptr(); }) .def("bind_to_queue", &cls::bind_to_queue, py::arg("queue")) .DEF_SIMPLE_METHOD(unbind_from_queue) // only for diagnostic/debugging/testing purposes! - .def_property_readonly("_queue", + .def_prop_ro("_queue", [](cls const &self) -> py::object { cl_command_queue queue = self.queue(); @@ -479,8 +449,7 @@ void pyopencl_expose_part_2(py::module_ &m) #endif .def(py::init()) .DEF_SIMPLE_METHOD(get_info) - .def(py::self == py::self) - .def(py::self != py::self) + PYOPENCL_EXPOSE_EQUALITY_TESTS .def("__hash__", &cls::hash) PYOPENCL_EXPOSE_TO_FROM_INT_PTR(cl_sampler) ; @@ -500,19 +469,19 @@ void pyopencl_expose_part_2(py::module_ &m) py::class_(m, "_Program", py::dynamic_attr()) .def( - py::init( - [](context &ctx, std::string const &src) - { - return create_program_with_source(ctx, src); - }), + "__init__", + [](cls *self, context &ctx, std::string const &src) + { + create_program_with_source(self, ctx, src); + }, py::arg("context"), py::arg("src")) .def( - py::init( - [](context &ctx, py::sequence devices, py::sequence binaries) - { - return create_program_with_binary(ctx, devices, binaries); - }), + "__init__", + [](cls *self, context &ctx, py::sequence devices, py::sequence binaries) + { + return create_program_with_binary(self, ctx, devices, binaries); + }, py::arg("context"), py::arg("devices"), py::arg("binaries")) @@ -547,8 +516,7 @@ void pyopencl_expose_part_2(py::module_ &m) py::arg("spec_id"), py::arg("buffer")) #endif - .def(py::self == py::self) - .def(py::self != py::self) + PYOPENCL_EXPOSE_EQUALITY_TESTS .def("__hash__", &cls::hash) .def("all_kernels", create_kernels_in_program) PYOPENCL_EXPOSE_TO_FROM_INT_PTR(cl_program) @@ -607,8 +575,7 @@ void pyopencl_expose_part_2(py::module_ &m) #if PYOPENCL_CL_VERSION >= 0x1020 .DEF_SIMPLE_METHOD(get_arg_info) #endif - .def(py::self == py::self) - .def(py::self != py::self) + PYOPENCL_EXPOSE_EQUALITY_TESTS .def("__hash__", &cls::hash) PYOPENCL_EXPOSE_TO_FROM_INT_PTR(cl_kernel) #if PYOPENCL_CL_VERSION >= 0x2010 @@ -627,7 +594,7 @@ void pyopencl_expose_part_2(py::module_ &m) .def( py::init(), py::arg("size")) - .def_property_readonly("size", &cls::size) + .def_prop_ro("size", &cls::size) ; } @@ -658,11 +625,11 @@ void pyopencl_expose_part_2(py::module_ &m) typedef gl_buffer cls; py::class_(m, "GLBuffer", py::dynamic_attr()) .def( - py::init( - [](context &ctx, cl_mem_flags flags, GLuint bufobj) - { - return create_from_gl_buffer(ctx, flags, bufobj); - }), + "__init__", + [](cls *self, context &ctx, cl_mem_flags flags, GLuint bufobj) + { + create_from_gl_buffer(self, ctx, flags, bufobj); + }, py::arg("context"), py::arg("flags"), py::arg("bufobj")) @@ -674,11 +641,11 @@ void pyopencl_expose_part_2(py::module_ &m) typedef gl_renderbuffer cls; py::class_(m, "GLRenderBuffer", py::dynamic_attr()) .def( - py::init( - [](context &ctx, cl_mem_flags flags, GLuint bufobj) - { - return create_from_gl_renderbuffer(ctx, flags, bufobj); - }), + "__init__", + [](cls *self, context &ctx, cl_mem_flags flags, GLuint bufobj) + { + create_from_gl_renderbuffer(self, ctx, flags, bufobj); + }, py::arg("context"), py::arg("flags"), py::arg("bufobj")) @@ -690,12 +657,12 @@ void pyopencl_expose_part_2(py::module_ &m) typedef gl_texture cls; py::class_(m, "GLTexture", py::dynamic_attr()) .def( - py::init( - [](context &ctx, cl_mem_flags flags, GLenum texture_target, - GLint miplevel, GLuint texture, unsigned dims) - { - return create_from_gl_texture(ctx, flags, texture_target, miplevel, texture, dims); - }), + "__init__", + [](cls *self, context &ctx, cl_mem_flags flags, GLenum texture_target, + GLint miplevel, GLuint texture, unsigned dims) + { + create_from_gl_texture(self, ctx, flags, texture_target, miplevel, texture, dims); + }, py::arg("context"), py::arg("flags"), py::arg("texture_target"), diff --git a/src/wrap_constants.cpp b/src/wrap_constants.cpp index d7a27b467..c2c2a6c99 100644 --- a/src/wrap_constants.cpp +++ b/src/wrap_constants.cpp @@ -112,7 +112,7 @@ void pyopencl_expose_constants(py::module_ &m) DECLARE_EXC(RuntimeError, CLError.ptr()); py::register_exception_translator( - [](std::exception_ptr p) + [](const std::exception_ptr &p, void * /* unused */) { try { @@ -139,13 +139,13 @@ void pyopencl_expose_constants(py::module_ &m) { typedef error cls; py::class_ (m, "_ErrorRecord") - .def(py::init(), + .def(py::init(), py::arg("routine"), py::arg("code"), py::arg("msg")) .DEF_SIMPLE_METHOD(routine) .DEF_SIMPLE_METHOD(code) - .DEF_SIMPLE_METHOD(what) + .def("what", &cls::err_what) .DEF_SIMPLE_METHOD(is_out_of_memory) .def("_program", &cls::get_program) ; @@ -1194,29 +1194,28 @@ void pyopencl_expose_constants(py::module_ &m) { typedef cl_name_version cls; py::class_(m, "NameVersion") - .def(py::init( - [](cl_version version, const char* name) - { - cl_name_version result; - result.version = version; - result.name[0] = '\0'; - // https://stackoverflow.com/a/1258577 - strncat(result.name, name, CL_NAME_VERSION_MAX_NAME_SIZE-1); - return result; - }), + .def("__init__", + [](cls *self, cl_version version, const std::string &name) + { + self->version = version; + self->name[0] = '\0'; + // https://stackoverflow.com/a/1258577 + strncat(self->name, name.c_str(), CL_NAME_VERSION_MAX_NAME_SIZE-1); + }, py::arg("version")=0, - py::arg("name")=0) + py::arg("name")=0 + ) - .def_property("version", + .def_prop_rw("version", [](cls &t) { return t.version; }, [](cls &t, cl_version val) { t.version = val; }) - .def_property("name", + .def_prop_rw("name", [](cls &t) { return t.name; }, - [](cls &t, const char *name) + [](cls &t, const std::string &name) { t.name[0] = '\0'; // https://stackoverflow.com/a/1258577 - strncat(t.name, name, CL_NAME_VERSION_MAX_NAME_SIZE-1); + strncat(t.name, name.c_str(), CL_NAME_VERSION_MAX_NAME_SIZE-1); }) ; } @@ -1229,33 +1228,38 @@ void pyopencl_expose_constants(py::module_ &m) { typedef cl_device_topology_amd cls; py::class_(m, "DeviceTopologyAmd") - .def(py::init( - [](cl_char bus, cl_char device, cl_char function) - { - cl_device_topology_amd result; - result.pcie.type = CL_DEVICE_TOPOLOGY_TYPE_PCIE_AMD; - result.pcie.bus = bus; - result.pcie.device = device; - result.pcie.function = function; - return result; - }), + .def("__init__", + + // FIXME: Nanobind thinks of 'char' as "short string", not small integer. + // The detour via cl_int may lose data on assignment. + // [](cl_char bus, cl_char device, cl_char function) + [](cls *self, cl_int bus, cl_int device, cl_int function) + { + self->pcie.type = CL_DEVICE_TOPOLOGY_TYPE_PCIE_AMD; + self->pcie.bus = (cl_char) bus; + self->pcie.device = (cl_char) device; + self->pcie.function = (cl_char) function; + }, py::arg("bus")=0, py::arg("device")=0, py::arg("function")=0) - .def_property("type", + .def_prop_rw("type", [](cls &t) { return t.pcie.type; }, [](cls &t, cl_uint val) { t.pcie.type = val; }) - .def_property("bus", + .def_prop_rw("bus", [](cls &t) { return t.pcie.bus; }, - [](cls &t, cl_char val) { t.pcie.bus = val; }) - .def_property("device", + // FIXME: Revert to cl_char when possible + [](cls &t, cl_int val) { t.pcie.bus = (cl_char) val; }) + .def_prop_rw("device", [](cls &t) { return t.pcie.device; }, - [](cls &t, cl_char val) { t.pcie.device = val; }) - .def_property("function", + // FIXME: Revert to cl_char when possible + [](cls &t, cl_int val) { t.pcie.device = (cl_char) val; }) + .def_prop_rw("function", [](cls &t) { return t.pcie.function; }, - [](cls &t, cl_char val) { t.pcie.function = val; }) + // FIXME: Revert to cl_char when possible + [](cls &t, cl_int val) { t.pcie.function = (cl_char) val; }) ; } #endif diff --git a/src/wrap_helpers.hpp b/src/wrap_helpers.hpp index 61ae88339..c878c36c3 100644 --- a/src/wrap_helpers.hpp +++ b/src/wrap_helpers.hpp @@ -28,16 +28,20 @@ #define PYCUDA_WRAP_HELPERS_HEADER_SEEN -#include -#include +#include +#include +#include +#include -namespace py = pybind11; +namespace py = nanobind; #define ENUM_VALUE(NAME) \ value(#NAME, NAME) +// {{{ DEF_SIMPLE_XXX + #define DEF_SIMPLE_METHOD(NAME) \ def(#NAME, &cls::NAME) @@ -120,7 +124,7 @@ namespace py = pybind11; #define PYOPENCL_PARSE_NUMPY_ARRAY_SPEC \ PyArray_Descr *tp_descr; \ if (PyArray_DescrConverter(dtype.ptr(), &tp_descr) != NPY_SUCCEED) \ - throw py::error_already_set(); \ + throw py::python_error(); \ \ std::vector shape; \ try \ @@ -162,7 +166,7 @@ namespace template inline py::object handle_from_new_ptr(T *ptr) { - return py::cast(ptr, py::return_value_policy::take_ownership); + return py::cast(ptr, py::rv_policy::take_ownership); } template @@ -192,10 +196,18 @@ namespace ":mod:`pyopencl`." \ "\n\n.. versionadded:: 2013.2\n" \ "\n\n.. versionchanged:: 2016.1\n\n *retain* added.") \ - .def_property_readonly("int_ptr", to_int_ptr, \ + .def_prop_ro("int_ptr", to_int_ptr, \ "Return an integer corresponding to the pointer value " \ "of the underlying :c:type:`" #CL_TYPENAME "`. " \ "Use :meth:`from_int_ptr` to turn back into a Python object." \ "\n\n.. versionadded:: 2013.2\n") \ +#define PYOPENCL_EXPOSE_EQUALITY_TESTS \ + /* this relies on nanobind overload resolution going in order of registration */ \ + .def("__eq__", [](cls const &self, cls const &other) { return self == other; }) \ + .def("__eq__", [](cls const &self, py::object obj) { return false; }, py::arg("obj").none()) + + #endif + +// vim: foldmethod=marker diff --git a/src/wrap_mempool.cpp b/src/wrap_mempool.cpp index 6b2d61e05..12f343de2 100644 --- a/src/wrap_mempool.cpp +++ b/src/wrap_mempool.cpp @@ -497,6 +497,13 @@ namespace pyopencl { else return nullptr; } + + // This shouldn't be necessary, but somehow nanobind gets unhappy if + // it's not there. + void free() + { + super::free(); + } }; // }}} @@ -554,12 +561,12 @@ namespace { template void expose_memory_pool(Wrapper &wrapper) { - typedef typename Wrapper::type cls; + typedef typename Wrapper::Type cls; wrapper - .def_property_readonly("held_blocks", &cls::held_blocks) - .def_property_readonly("active_blocks", &cls::active_blocks) - .def_property_readonly("managed_bytes", &cls::managed_bytes) - .def_property_readonly("active_bytes", &cls::active_bytes) + .def_prop_ro("held_blocks", &cls::held_blocks) + .def_prop_ro("active_blocks", &cls::active_blocks) + .def_prop_ro("managed_bytes", &cls::managed_bytes) + .def_prop_ro("active_bytes", &cls::active_bytes) .DEF_SIMPLE_METHOD(bin_number) .DEF_SIMPLE_METHOD(alloc_size) .DEF_SIMPLE_METHOD(free_held) @@ -580,7 +587,7 @@ void pyopencl_expose_mempool(py::module_ &m) { typedef pyopencl::buffer_allocator_base cls; - py::class_> wrapper(m, "AllocatorBase"); + py::class_ wrapper(m, "AllocatorBase"); wrapper .def("__call__", pyopencl::allocate_from_buffer_allocator, py::arg("size")) ; @@ -590,13 +597,16 @@ void pyopencl_expose_mempool(py::module_ &m) { typedef pyopencl::memory_pool cls; - py::class_> wrapper( m, "_TestMemoryPool"); + py::class_ wrapper(m, "_TestMemoryPool"); wrapper - .def(py::init([](unsigned leading_bits_in_bin_id) - { return new cls( - std::shared_ptr( - new pyopencl::test_allocator()), - leading_bits_in_bin_id); }), + .def("__init__", + [](cls *self, unsigned leading_bits_in_bin_id) + { + new (self) cls( + std::shared_ptr( + new pyopencl::test_allocator()), + leading_bits_in_bin_id); + }, py::arg("leading_bits_in_bin_id")=4 ) .def("allocate", [](std::shared_ptr pool, cls::size_type sz) @@ -611,11 +621,10 @@ void pyopencl_expose_mempool(py::module_ &m) { typedef pyopencl::deferred_buffer_allocator cls; - py::class_> wrapper( + py::class_ wrapper( m, "DeferredAllocator"); wrapper - .def(py::init< - std::shared_ptr const &>()) + .def(py::init const &>()) .def(py::init< std::shared_ptr const &, cl_mem_flags>(), @@ -625,7 +634,7 @@ void pyopencl_expose_mempool(py::module_ &m) { typedef pyopencl::immediate_buffer_allocator cls; - py::class_> wrapper( + py::class_ wrapper( m, "ImmediateAllocator"); wrapper .def(py::init()) @@ -647,7 +656,7 @@ void pyopencl_expose_mempool(py::module_ &m) { typedef pyopencl::memory_pool cls; - py::class_> wrapper( m, "MemoryPool"); + py::class_ wrapper( m, "MemoryPool"); wrapper .def(py::init, unsigned>(), py::arg("allocator"), @@ -663,11 +672,11 @@ void pyopencl_expose_mempool(py::module_ &m) #if PYOPENCL_CL_VERSION >= 0x2000 { typedef pyopencl::svm_allocator cls; - py::class_> wrapper(m, "SVMAllocator"); + py::class_ wrapper(m, "SVMAllocator"); wrapper .def(py::init const &, cl_uint, cl_uint, pyopencl::command_queue *>(), py::arg("context"), - py::kw_only(), + /* py::kw_only(), */ py::arg("alignment")=0, py::arg("flags")=CL_MEM_READ_WRITE, py::arg("queue").none(true)=nullptr @@ -688,7 +697,7 @@ void pyopencl_expose_mempool(py::module_ &m) .DEF_SIMPLE_METHOD(unbind_from_queue) // only for diagnostic/debugging/testing purposes! - .def_property_readonly("_queue", + .def_prop_ro("_queue", [](cls const &self) -> py::object { cl_command_queue queue = self.queue(); @@ -703,11 +712,11 @@ void pyopencl_expose_mempool(py::module_ &m) { typedef pyopencl::memory_pool cls; - py::class_> wrapper( m, "SVMPool"); + py::class_ wrapper( m, "SVMPool"); wrapper .def(py::init, unsigned>(), py::arg("allocator"), - py::kw_only(), + /* py::kw_only(), */ py::arg("leading_bits_in_bin_id")=4 ) .def("__call__", pyopencl::allocate_from_svm_pool, py::arg("size"))