diff --git a/doc/misc.rst b/doc/misc.rst index 610152c2f15b9654cf2b15d9104b1518d4144f6e..3dde753638d31ac1d39faf6168900ceb68d221eb 100644 --- a/doc/misc.rst +++ b/doc/misc.rst @@ -123,6 +123,7 @@ Version 2016.2 for more information. * Add support for **range** and **slice** kwargs and data-less reductions to :class:`pyopencl.reduction.ReductionKernel`. +* Add support for SPIR-V. (See :class:`pyopencl.Program`.) Version 2016.1 -------------- diff --git a/doc/runtime.rst b/doc/runtime.rst index fd17da8bc5371f4cbbfdf8f5993a118216007fd5..dfe64c541ac2d2389708b3c534d5d097caa7094d 100644 --- a/doc/runtime.rst +++ b/doc/runtime.rst @@ -660,6 +660,14 @@ Programs and Kernels Program(context, devices, binaries) *binaries* must contain one binary for each entry in *devices*. + If *src* is a :class:`bytes` object starting with a valid `SPIR-V + <https://www.khronos.org/spir>`_ magic number, it will be handed + off to the OpenCL implementation as such, rather than as OpenCL C source + code. (SPIR-V support requires OpenCL 2.1.) + + .. versionchanged:: 2016.2 + + Add support for SPIR-V. .. attribute:: info diff --git a/pyopencl/__init__.py b/pyopencl/__init__.py index 00cdaa2a23333f534731868ba34107a423a19e6e..49ec01ef77945c6dd4bb3ae817527ec241498f7f 100644 --- a/pyopencl/__init__.py +++ b/pyopencl/__init__.py @@ -297,6 +297,13 @@ class Program(object): # 2-argument form: context, source context, source = arg1, arg2 + from pyopencl.tools import is_spirv + if is_spirv(source): + # no caching in SPIR-V case + self._context = context + self._prg = _cl._Program(context, source) + return + import sys if isinstance(source, six.text_type) and sys.version_info < (3,): from warnings import warn diff --git a/pyopencl/cffi_cl.py b/pyopencl/cffi_cl.py index 52645cdecb360de4f8a98a07662da01c44e9c415..20e68401dff1ceadd3d07f4e5f05449f2c03726a 100644 --- a/pyopencl/cffi_cl.py +++ b/pyopencl/cffi_cl.py @@ -993,7 +993,12 @@ class _Program(_Common): def __init__(self, *args): if len(args) == 2: - self._init_source(*args) + ctx, source = args + from pyopencl.tools import is_spirv + if is_spirv(source): + self._init_il(ctx, source) + else: + self._init_source(ctx, source) else: self._init_binary(*args) @@ -1003,6 +1008,12 @@ class _Program(_Common): ptr_program, context.ptr, _to_cstring(src))) self.ptr = ptr_program[0] + def _init_il(self, context, il): + ptr_program = _ffi.new('clobj_t*') + _handle_error(_lib.create_program_with_il( + ptr_program, context.ptr, il, len(il))) + self.ptr = ptr_program[0] + def _init_binary(self, context, devices, binaries): if len(devices) != len(binaries): raise RuntimeError("device and binary counts don't match", diff --git a/pyopencl/tools.py b/pyopencl/tools.py index b1383cb7b95abe46e22661ba6572e40457b8d6da..2e24890bc476c25ad580e966ab17b88687addd5c 100644 --- a/pyopencl/tools.py +++ b/pyopencl/tools.py @@ -931,4 +931,13 @@ def array_module(a): # }}} + +def is_spirv(s): + spirv_magic = b"\x07\x23\x02\x03" + return ( + isinstance(s, six.binary_type) + and ( + s[:4] == spirv_magic + or s[:4] == spirv_magic[::-1])) + # vim: foldmethod=marker diff --git a/src/c_wrapper/program.cpp b/src/c_wrapper/program.cpp index a80686aec50269a11933ae0325ccf705c236ad72..d50d96802830269c0b8e35bbda4b85fe97e644c7 100644 --- a/src/c_wrapper/program.cpp +++ b/src/c_wrapper/program.cpp @@ -140,6 +140,21 @@ create_program_with_source(clobj_t *prog, clobj_t _ctx, const char *_src) }); } +error* +create_program_with_il(clobj_t *prog, clobj_t _ctx, void *il, size_t length) +{ +#if PYOPENCL_CL_VERSION >= 0x2010 + auto ctx = static_cast<context*>(_ctx); + return c_handle_error([&] { + cl_program result = pyopencl_call_guarded( + clCreateProgramWithIL, ctx, il, length); + *prog = new_program(result, KND_SOURCE); + }); +#else + PYOPENCL_UNSUPPORTED_BEFORE(clCreateProgramWithIL, "CL 2.1") +#endif +} + error* create_program_with_binary(clobj_t *prog, clobj_t _ctx, cl_uint num_devices, const clobj_t *devices, diff --git a/src/c_wrapper/wrap_cl_core.h b/src/c_wrapper/wrap_cl_core.h index c96b53f189aa526e09893da68800769b8effead2..8ef6f2e8066b4b50e938ee67dc0df1191dc9e4e6 100644 --- a/src/c_wrapper/wrap_cl_core.h +++ b/src/c_wrapper/wrap_cl_core.h @@ -171,6 +171,7 @@ error* enqueue_svm_migrate_mem( error *create_program_with_source(clobj_t *program, clobj_t context, const char *src); +error* create_program_with_il(clobj_t *prog, clobj_t _ctx, void *il, size_t length); error *create_program_with_binary(clobj_t *program, clobj_t context, cl_uint num_devices, const clobj_t *devices, const unsigned char **binaries, diff --git a/test/test_wrapper.py b/test/test_wrapper.py index 07f32522dc6af789e44812521ec6501803b1ad7c..29020c8e0c79cee8af92d805af970bd7e1549c54 100644 --- a/test/test_wrapper.py +++ b/test/test_wrapper.py @@ -30,6 +30,7 @@ import pytest import pyopencl as cl import pyopencl.array as cl_array +import pyopencl.clrandom from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) @@ -906,6 +907,31 @@ def test_sub_buffers(ctx_factory): assert np.array_equal(a_sub, a_sub_ref) +def test_spirv(ctx_factory): + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + + # if (ctx._get_cl_version() < (2, 1) and + # cl.get_cl_header_version() < (2, 1)): + # from pytest import skip + # skip("SPIR-V program creation only available in OpenCL 2.1") + + n = 50000 + + a_dev = cl.clrandom.rand(queue, n, np.float32) + b_dev = cl.clrandom.rand(queue, n, np.float32) + dest_dev = cl_array.empty_like(a_dev) + + with open("add-vectors.spv", "rb") as spv_file: + spv = spv_file.read() + + prg = cl.Program(ctx, spv) + + prg.sum(queue, a_dev.shape, None, a_dev.data, b_dev.data, dest_dev.data) + + assert la.norm((dest_dev - (a_dev+b_dev)).get()) < 1e-7 + + if __name__ == "__main__": # make sure that import failures get reported, instead of skipping the tests. import pyopencl # noqa