pyopencl 2025.1__cp312-cp312-macosx_11_0_arm64.whl → 2025.2.2__cp312-cp312-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyopencl might be problematic. Click here for more details.

@@ -0,0 +1,1063 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ __copyright__ = "Copyright (C) 2025 University of Illinois Board of Trustees"
5
+
6
+ __license__ = """
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
24
+ """
25
+ import inspect as _inspect
26
+ from sys import intern
27
+ from typing import (
28
+ TYPE_CHECKING,
29
+ Any,
30
+ TextIO,
31
+ TypeVar,
32
+ cast,
33
+ )
34
+ from warnings import warn
35
+
36
+ import numpy as np
37
+
38
+ import pyopencl._cl as _cl
39
+
40
+
41
+ if TYPE_CHECKING:
42
+ from collections.abc import Callable, Collection, Sequence
43
+
44
+ from numpy.typing import NDArray
45
+
46
+ from pyopencl import SVMMap
47
+ from pyopencl.typing import HasBufferInterface, KernelArg, SVMInnerT, WaitList
48
+
49
+
50
+ CONSTANT_CLASSES = tuple(
51
+ getattr(_cl, name) for name in dir(_cl)
52
+ if _inspect.isclass(getattr(_cl, name))
53
+ and name[0].islower() and name not in ["zip", "map", "range"])
54
+
55
+
56
+ BITFIELD_CONSTANT_CLASSES = (
57
+ _cl.device_type,
58
+ _cl.device_fp_config,
59
+ _cl.device_exec_capabilities,
60
+ _cl.command_queue_properties,
61
+ _cl.mem_flags,
62
+ _cl.map_flags,
63
+ _cl.kernel_arg_type_qualifier,
64
+ _cl.device_affinity_domain,
65
+ _cl.mem_migration_flags,
66
+ _cl.device_svm_capabilities,
67
+ _cl.queue_properties,
68
+ _cl.svm_mem_flags,
69
+ _cl.device_atomic_capabilities,
70
+ _cl.device_device_enqueue_capabilities,
71
+ _cl.version_bits,
72
+ )
73
+
74
+
75
+ def generic_get_cl_version(self: _cl.Platform):
76
+ import re
77
+ version_string = self.version
78
+ match = re.match(r"^OpenCL ([0-9]+)\.([0-9]+) .*$", version_string)
79
+ if match is None:
80
+ raise RuntimeError("%s %s returned non-conformant "
81
+ "platform version string '%s'" %
82
+ (type(self).__name__, self, version_string))
83
+
84
+ return int(match.group(1)), int(match.group(2))
85
+
86
+
87
+ def platform_repr(self: _cl.Platform):
88
+ return f"<pyopencl.Platform '{self.name}' at 0x{self.int_ptr:x}>"
89
+
90
+
91
+ def device_repr(self: _cl.Device):
92
+ return "<pyopencl.Device '{}' on '{}' at 0x{:x}>".format(
93
+ self.name.strip(), self.platform.name.strip(), self.int_ptr)
94
+
95
+
96
+ def device_hashable_model_and_version_identifier(self: _cl.Device):
97
+ return ("v1", self.vendor, self.vendor_id, self.name, self.version)
98
+
99
+
100
+ def device_persistent_unique_id(self: _cl.Device):
101
+ warn("Device.persistent_unique_id is deprecated. "
102
+ "Use Device.hashable_model_and_version_identifier instead.",
103
+ DeprecationWarning, stacklevel=2)
104
+ return device_hashable_model_and_version_identifier(self)
105
+
106
+
107
+ def context_repr(self: _cl.Context):
108
+ return "<pyopencl.Context at 0x{:x} on {}>".format(self.int_ptr,
109
+ ", ".join(repr(dev) for dev in self.devices))
110
+
111
+
112
+ def context_get_cl_version(self: _cl.Context):
113
+ return self.devices[0].platform._get_cl_version()
114
+
115
+
116
+ def command_queue_enter(self: _cl.CommandQueue):
117
+ return self
118
+
119
+
120
+ def command_queue_exit(self: _cl.CommandQueue, exc_type, exc_val, exc_tb):
121
+ self.finish()
122
+ self._finalize()
123
+
124
+
125
+ def command_queue_get_cl_version(self: _cl.CommandQueue):
126
+ return self.device._get_cl_version()
127
+
128
+
129
+ def program_get_build_logs(self: _cl._Program):
130
+ build_logs = []
131
+ for dev in self.get_info(_cl.program_info.DEVICES):
132
+ try:
133
+ log = self.get_build_info(dev, _cl.program_build_info.LOG)
134
+ except Exception:
135
+ log = "<error retrieving log>"
136
+
137
+ build_logs.append((dev, log))
138
+
139
+ return build_logs
140
+
141
+
142
+ def program_build(
143
+ self: _cl._Program,
144
+ options_bytes: bytes,
145
+ devices: Sequence[_cl.Device] | None = None
146
+ ) -> _cl._Program:
147
+ err = None
148
+ try:
149
+ self._build(options=options_bytes, devices=devices)
150
+ except _cl.Error as e:
151
+ msg = str(e) + "\n\n" + (75*"="+"\n").join(
152
+ f"Build on {dev}:\n\n{log}"
153
+ for dev, log in self._get_build_logs())
154
+ code = e.code
155
+ routine = e.routine
156
+
157
+ err = _cl.RuntimeError(
158
+ _cl._ErrorRecord(
159
+ msg=msg,
160
+ code=code,
161
+ routine=routine))
162
+
163
+ if err is not None:
164
+ # Python 3.2 outputs the whole list of currently active exceptions
165
+ # This serves to remove one (redundant) level from that nesting.
166
+ raise err
167
+
168
+ message = (75*"="+"\n").join(
169
+ f"Build on {dev} succeeded, but said:\n\n{log}"
170
+ for dev, log in self._get_build_logs()
171
+ if log is not None and log.strip())
172
+
173
+ if message:
174
+ if self.kind() == _cl.program_kind.SOURCE:
175
+ build_type = "From-source build"
176
+ elif self.kind() == _cl.program_kind.BINARY:
177
+ build_type = "From-binary build"
178
+ elif self.kind() == _cl.program_kind.IL:
179
+ build_type = "From-IL build"
180
+ else:
181
+ build_type = "Build"
182
+
183
+ from pyopencl import compiler_output
184
+ compiler_output("%s succeeded, but resulted in non-empty logs:\n%s"
185
+ % (build_type, message))
186
+
187
+ return self
188
+
189
+
190
+ class ProfilingInfoGetter:
191
+ event: _cl.Event
192
+
193
+ def __init__(self, event: _cl.Event):
194
+ self.event = event
195
+
196
+ def __getattr__(self, name: str):
197
+ info_cls = _cl.profiling_info
198
+
199
+ try:
200
+ inf_attr = getattr(info_cls, name.upper())
201
+ except AttributeError as err:
202
+ raise AttributeError("%s has no attribute '%s'"
203
+ % (type(self), name)) from err
204
+ else:
205
+ return self.event.get_profiling_info(inf_attr)
206
+
207
+ QUEUED: int # pyright: ignore[reportUninitializedInstanceVariable]
208
+ SUBMIT: int # pyright: ignore[reportUninitializedInstanceVariable]
209
+ START: int # pyright: ignore[reportUninitializedInstanceVariable]
210
+ END: int # pyright: ignore[reportUninitializedInstanceVariable]
211
+ COMPLETE: int # pyright: ignore[reportUninitializedInstanceVariable]
212
+
213
+
214
+ kernel_old_get_info = _cl.Kernel.get_info
215
+ kernel_old_get_work_group_info = _cl.Kernel.get_work_group_info
216
+
217
+
218
+ def kernel_set_arg_types(self: _cl.Kernel, arg_types):
219
+ arg_types = tuple(arg_types)
220
+
221
+ # {{{ arg counting bug handling
222
+
223
+ # For example:
224
+ # https://github.com/pocl/pocl/issues/197
225
+ # (but Apple CPU has a similar bug)
226
+
227
+ work_around_arg_count_bug = False
228
+ warn_about_arg_count_bug = False
229
+
230
+ from pyopencl.characterize import has_struct_arg_count_bug
231
+
232
+ count_bug_per_dev = [
233
+ has_struct_arg_count_bug(dev, self.context)
234
+ for dev in self.context.devices]
235
+
236
+ from pytools import single_valued
237
+ if any(count_bug_per_dev):
238
+ if all(count_bug_per_dev):
239
+ work_around_arg_count_bug = single_valued(count_bug_per_dev)
240
+ else:
241
+ warn_about_arg_count_bug = True
242
+
243
+ # }}}
244
+
245
+ from pyopencl.invoker import generate_enqueue_and_set_args
246
+ self._set_enqueue_and_set_args(
247
+ *generate_enqueue_and_set_args(
248
+ self.function_name,
249
+ len(arg_types), self.num_args,
250
+ arg_types,
251
+ warn_about_arg_count_bug=warn_about_arg_count_bug,
252
+ work_around_arg_count_bug=work_around_arg_count_bug,
253
+ devs=self.context.devices))
254
+
255
+
256
+ def kernel_get_work_group_info(self: _cl.Kernel, param: int, device: _cl.Device):
257
+ try:
258
+ wg_info_cache = self._wg_info_cache
259
+ except AttributeError:
260
+ wg_info_cache = self._wg_info_cache = {}
261
+
262
+ cache_key = (param, device.int_ptr)
263
+ try:
264
+ return wg_info_cache[cache_key]
265
+ except KeyError:
266
+ pass
267
+
268
+ result = kernel_old_get_work_group_info(self, param, device)
269
+ wg_info_cache[cache_key] = result
270
+ return result
271
+
272
+
273
+ def kernel_capture_call(
274
+ self: _cl.Kernel,
275
+ output_file: str | TextIO,
276
+ queue: _cl.CommandQueue,
277
+ global_size: tuple[int, ...],
278
+ local_size: tuple[int, ...] | None,
279
+ *args: KernelArg,
280
+ wait_for: WaitList = None,
281
+ g_times_l: bool = False,
282
+ allow_empty_ndrange: bool = False,
283
+ global_offset: tuple[int, ...] | None = None,
284
+ ) -> None:
285
+ from pyopencl.capture_call import capture_kernel_call
286
+ capture_kernel_call(self, output_file, queue, global_size, local_size,
287
+ *args,
288
+ wait_for=wait_for,
289
+ g_times_l=g_times_l,
290
+ allow_empty_ndrange=allow_empty_ndrange,
291
+ global_offset=global_offset)
292
+
293
+
294
+ def kernel_get_info(self: _cl.Kernel, param_name: _cl.kernel_info) -> object:
295
+ val = kernel_old_get_info(self, param_name)
296
+
297
+ if isinstance(val, _cl._Program):
298
+ from pyopencl import Program
299
+ return Program(val)
300
+ else:
301
+ return val
302
+
303
+
304
+ def image_format_repr(self: _cl.ImageFormat) -> str:
305
+ return "ImageFormat({}, {})".format(
306
+ _cl.channel_order.to_string(self.channel_order,
307
+ "<unknown channel order 0x%x>"),
308
+ _cl.channel_type.to_string(self.channel_data_type,
309
+ "<unknown channel data type 0x%x>"))
310
+
311
+
312
+ def image_format_eq(self: _cl.ImageFormat, other: object):
313
+ return (isinstance(other, _cl.ImageFormat)
314
+ and self.channel_order == other.channel_order
315
+ and self.channel_data_type == other.channel_data_type)
316
+
317
+
318
+ def image_format_ne(self: _cl.ImageFormat, other: object):
319
+ return not image_format_eq(self, other)
320
+
321
+
322
+ def image_format_hash(self: _cl.ImageFormat) -> int:
323
+ return hash((type(self), self.channel_order, self.channel_data_type))
324
+
325
+
326
+ def image_init(self: _cl.Image,
327
+ context: _cl.Context,
328
+ flags: _cl.mem_flags,
329
+ format: _cl.ImageFormat,
330
+ shape: tuple[int, ...] | None = None,
331
+ pitches: tuple[int, ...] | None = None,
332
+
333
+ hostbuf: HasBufferInterface | None = None,
334
+ is_array: bool = False,
335
+ buffer: _cl.Buffer | None = None,
336
+ *,
337
+ desc: _cl.ImageDescriptor | None = None,
338
+ _through_create_image: bool = False,
339
+ ) -> None:
340
+ if hostbuf is not None and not \
341
+ (flags & (_cl.mem_flags.USE_HOST_PTR | _cl.mem_flags.COPY_HOST_PTR)):
342
+ warn("'hostbuf' was passed, but no memory flags to make use of it.",
343
+ stacklevel=2)
344
+
345
+ if desc is not None:
346
+ if shape is not None:
347
+ raise TypeError("shape may not be passed when using descriptor")
348
+ if pitches is not None:
349
+ raise TypeError("pitches may not be passed when using descriptor")
350
+ if is_array:
351
+ raise TypeError("is_array may not be passed when using descriptor")
352
+ if buffer is not None:
353
+ raise TypeError("is_array may not be passed when using descriptor")
354
+
355
+ _cl.Image._custom_init(self, context, flags, format, desc, hostbuf)
356
+
357
+ return
358
+
359
+ if shape is None and hostbuf is None:
360
+ raise _cl.Error("'shape' must be passed if 'hostbuf' is not given")
361
+
362
+ if shape is None and hostbuf is not None:
363
+ shape = hostbuf.shape
364
+
365
+ if hostbuf is None and pitches is not None:
366
+ raise _cl.Error("'pitches' may only be given if 'hostbuf' is given")
367
+
368
+ if context._get_cl_version() >= (1, 2) and _cl.get_cl_header_version() >= (1, 2):
369
+ if not _through_create_image:
370
+ warn("Non-descriptor Image constructor called. "
371
+ "This will stop working in 2026. "
372
+ "Use create_image instead (with the same arguments).",
373
+ DeprecationWarning, stacklevel=2)
374
+
375
+ if buffer is not None and is_array:
376
+ raise ValueError(
377
+ "'buffer' and 'is_array' are mutually exclusive")
378
+
379
+ if len(shape) == 3:
380
+ if buffer is not None:
381
+ raise TypeError(
382
+ "'buffer' argument is not supported for 3D arrays")
383
+ elif is_array:
384
+ image_type = _cl.mem_object_type.IMAGE2D_ARRAY
385
+ else:
386
+ image_type = _cl.mem_object_type.IMAGE3D
387
+
388
+ elif len(shape) == 2:
389
+ if buffer is not None:
390
+ raise TypeError(
391
+ "'buffer' argument is not supported for 2D arrays")
392
+ elif is_array:
393
+ image_type = _cl.mem_object_type.IMAGE1D_ARRAY
394
+ else:
395
+ image_type = _cl.mem_object_type.IMAGE2D
396
+
397
+ elif len(shape) == 1:
398
+ if buffer is not None:
399
+ image_type = _cl.mem_object_type.IMAGE1D_BUFFER
400
+ elif is_array:
401
+ raise TypeError("array of zero-dimensional images not supported")
402
+ else:
403
+ image_type = _cl.mem_object_type.IMAGE1D
404
+
405
+ else:
406
+ raise ValueError("images cannot have more than three dimensions")
407
+
408
+ desc = _cl.ImageDescriptor() \
409
+ # pylint: disable=possibly-used-before-assignment
410
+
411
+ desc.image_type = image_type
412
+ desc.shape = shape # also sets desc.array_size
413
+
414
+ if pitches is None:
415
+ desc.pitches = (0, 0)
416
+ else:
417
+ desc.pitches = pitches
418
+
419
+ desc.num_mip_levels = 0 # per CL 1.2 spec
420
+ desc.num_samples = 0 # per CL 1.2 spec
421
+ desc.buffer = buffer
422
+
423
+ _cl.Image._custom_init(self, context, flags, format, desc, hostbuf)
424
+ else:
425
+ # legacy init for CL 1.1 and older
426
+ if is_array:
427
+ raise TypeError("'is_array=True' is not supported for CL < 1.2")
428
+ # if num_mip_levels is not None:
429
+ # raise TypeError(
430
+ # "'num_mip_levels' argument is not supported for CL < 1.2")
431
+ # if num_samples is not None:
432
+ # raise TypeError(
433
+ # "'num_samples' argument is not supported for CL < 1.2")
434
+ if buffer is not None:
435
+ raise TypeError("'buffer' argument is not supported for CL < 1.2")
436
+
437
+ _cl.Image._custom_init(self, context, flags, format, shape,
438
+ pitches, hostbuf)
439
+
440
+
441
+ def image_shape(self: _cl.Image) -> tuple[int, int] | tuple[int, int, int]:
442
+ if self.type == _cl.mem_object_type.IMAGE2D:
443
+ return (self.width, self.height)
444
+ elif self.type == _cl.mem_object_type.IMAGE3D:
445
+ return (self.width, self.height, self.depth)
446
+ else:
447
+ raise _cl.LogicError("only images have shapes")
448
+
449
+
450
+ def error_str(self: _cl.Error) -> str:
451
+ val = self.what
452
+ try:
453
+ val.routine # noqa: B018
454
+ except AttributeError:
455
+ return str(val)
456
+ else:
457
+ result = ""
458
+ if val.code() != _cl.status_code.SUCCESS:
459
+ result = _cl.status_code.to_string(
460
+ val.code(), "<unknown error %d>")
461
+ routine = val.routine()
462
+ if routine:
463
+ result = f"{routine} failed: {result}"
464
+ what = val.what()
465
+ if what:
466
+ if result:
467
+ result += " - "
468
+ result += what
469
+ return result
470
+
471
+
472
+ def error_code(self: _cl.Error) -> int:
473
+ return cast("_cl._ErrorRecord", self.args[0]).code()
474
+
475
+
476
+ def error_routine(self: _cl.Error) -> str:
477
+ return cast("_cl._ErrorRecord", self.args[0]).routine()
478
+
479
+
480
+ def error_what(self: _cl.Error) -> _cl._ErrorRecord:
481
+ return cast("_cl._ErrorRecord", self.args[0])
482
+
483
+
484
+ def memory_map_enter(self: _cl.MemoryMap):
485
+ return self
486
+
487
+
488
+ def memory_map_exit(self: _cl.MemoryMap, exc_type, exc_val, exc_tb):
489
+ self.release()
490
+
491
+
492
+ def svmptr_map(
493
+ self: _cl.SVMPointer,
494
+ queue: _cl.CommandQueue,
495
+ *,
496
+ flags: int,
497
+ is_blocking: bool = True,
498
+ wait_for: WaitList = None,
499
+ size: int | None = None
500
+ ) -> SVMMap[NDArray[Any]]:
501
+ """
502
+ :arg is_blocking: If *False*, subsequent code must wait on
503
+ :attr:`SVMMap.event` in the returned object before accessing the
504
+ mapped memory.
505
+ :arg flags: a combination of :class:`pyopencl.map_flags`.
506
+ :arg size: The size of the map in bytes. If not provided, defaults to
507
+ :attr:`size`.
508
+
509
+ |std-enqueue-blurb|
510
+ """
511
+ from pyopencl import SVMMap
512
+ return SVMMap(self,
513
+ np.asarray(self.buf),
514
+ queue,
515
+ _cl._enqueue_svm_map(queue, is_blocking, flags, self, wait_for,
516
+ size=size))
517
+
518
+
519
+ def svmptr_map_ro(
520
+ self: _cl.SVMPointer,
521
+ queue: _cl.CommandQueue,
522
+ *,
523
+ is_blocking: bool = True,
524
+ wait_for: WaitList = None,
525
+ size: int | None = None
526
+ ) -> SVMMap[NDArray[Any]]:
527
+ """Like :meth:`map`, but with *flags* set for a read-only map.
528
+ """
529
+
530
+ return self.map(queue, flags=_cl.map_flags.READ,
531
+ is_blocking=is_blocking, wait_for=wait_for, size=size)
532
+
533
+
534
+ def svmptr_map_rw(
535
+ self: _cl.SVMPointer,
536
+ queue: _cl.CommandQueue,
537
+ *,
538
+ is_blocking: bool = True,
539
+ wait_for: WaitList = None,
540
+ size: int | None = None
541
+ ) -> SVMMap[NDArray[Any]]:
542
+ """Like :meth:`map`, but with *flags* set for a read-only map.
543
+ """
544
+
545
+ return self.map(queue, flags=_cl.map_flags.READ | _cl.map_flags.WRITE,
546
+ is_blocking=is_blocking, wait_for=wait_for, size=size)
547
+
548
+
549
+ def svmptr__enqueue_unmap(
550
+ self: _cl.SVMPointer,
551
+ queue: _cl.CommandQueue,
552
+ wait_for: WaitList = None
553
+ ) -> _cl.Event:
554
+ return _cl._enqueue_svm_unmap(queue, self, wait_for)
555
+
556
+
557
+ def svmptr_as_buffer(
558
+ self: _cl.SVMPointer,
559
+ ctx: _cl.Context,
560
+ *,
561
+ flags: int | None = None,
562
+ size: int | None = None
563
+ ) -> _cl.Buffer:
564
+ """
565
+ :arg ctx: a :class:`Context`
566
+ :arg flags: a combination of :class:`pyopencl.map_flags`, defaults to
567
+ read-write.
568
+ :arg size: The size of the map in bytes. If not provided, defaults to
569
+ :attr:`size`.
570
+ :returns: a :class:`Buffer` corresponding to *self*.
571
+
572
+ The memory referred to by this object must not be freed before
573
+ the returned :class:`Buffer` is released.
574
+ """
575
+
576
+ if flags is None:
577
+ flags = _cl.mem_flags.READ_WRITE | _cl.mem_flags.USE_HOST_PTR
578
+
579
+ if size is None:
580
+ size = self.size
581
+
582
+ assert self.buf is not None
583
+ return _cl.Buffer(ctx, flags, size=size, hostbuf=self.buf)
584
+
585
+
586
+ def svm_map(
587
+ self: _cl.SVM[SVMInnerT],
588
+ queue: _cl.CommandQueue,
589
+ flags: int,
590
+ is_blocking: bool = True,
591
+ wait_for: WaitList = None
592
+ ) -> SVMMap[SVMInnerT]:
593
+
594
+ """
595
+ :arg is_blocking: If *False*, subsequent code must wait on
596
+ :attr:`SVMMap.event` in the returned object before accessing the
597
+ mapped memory.
598
+ :arg flags: a combination of :class:`pyopencl.map_flags`.
599
+ :returns: an :class:`SVMMap` instance
600
+
601
+ This differs from the inherited :class:`SVMPointer.map` in that no size
602
+ can be specified, and that :attr:`mem` is the exact array produced
603
+ when the :class:`SVMMap` is used as a context manager.
604
+
605
+ |std-enqueue-blurb|
606
+ """
607
+ from pyopencl import SVMMap
608
+ return SVMMap(
609
+ self,
610
+ self.mem,
611
+ queue,
612
+ _cl._enqueue_svm_map(queue, is_blocking, flags, self, wait_for))
613
+
614
+
615
+ def svm_map_ro(
616
+ self: _cl.SVM[SVMInnerT],
617
+ queue: _cl.CommandQueue,
618
+ is_blocking: bool = True,
619
+ wait_for: WaitList = None,
620
+ ) -> SVMMap[SVMInnerT]:
621
+ """Like :meth:`map`, but with *flags* set for a read-only map."""
622
+
623
+ return self.map(queue, _cl.map_flags.READ,
624
+ is_blocking=is_blocking, wait_for=wait_for)
625
+
626
+
627
+ def svm_map_rw(
628
+ self: _cl.SVM[SVMInnerT],
629
+ queue: _cl.CommandQueue,
630
+ is_blocking: bool = True,
631
+ wait_for: WaitList = None,
632
+ ) -> SVMMap[SVMInnerT]:
633
+ """Like :meth:`map`, but with *flags* set for a read-only map."""
634
+
635
+ return self.map(queue, _cl.map_flags.READ | _cl.map_flags.WRITE,
636
+ is_blocking=is_blocking, wait_for=wait_for)
637
+
638
+
639
+ def svm__enqueue_unmap(
640
+ self: _cl.SVM[SVMInnerT],
641
+ queue: _cl.CommandQueue
642
+ ,
643
+ wait_for: WaitList = None
644
+ ) -> _cl.Event:
645
+ return _cl._enqueue_svm_unmap(queue, self, wait_for)
646
+
647
+
648
+ def to_string(
649
+ cls: type,
650
+ value: int,
651
+ default_format: str | None = None
652
+ ) -> str:
653
+ if cls._is_bitfield:
654
+ names: list[str] = []
655
+ for name in dir(cls):
656
+ attr = cast("int", getattr(cls, name))
657
+ if not isinstance(attr, int):
658
+ continue
659
+ if attr == value or attr & value:
660
+ names.append(name)
661
+ if names:
662
+ return " | ".join(names)
663
+ else:
664
+ for name in dir(cls):
665
+ if (not name.startswith("_")
666
+ and getattr(cls, name) == value):
667
+ return name
668
+
669
+ if default_format is None:
670
+ raise ValueError("a name for value %d was not found in %s"
671
+ % (value, cls.__name__))
672
+ else:
673
+ return default_format % value
674
+
675
+
676
+ def _add_functionality():
677
+ # {{{ Platform
678
+
679
+ _cl.Platform.__repr__ = platform_repr
680
+ _cl.Platform._get_cl_version = generic_get_cl_version
681
+
682
+ # }}}
683
+
684
+ # {{{ Device
685
+
686
+ _cl.Device.__repr__ = device_repr
687
+
688
+ # undocumented for now:
689
+ _cl.Device._get_cl_version = generic_get_cl_version
690
+ _cl.Device.hashable_model_and_version_identifier = property(
691
+ device_hashable_model_and_version_identifier)
692
+ _cl.Device.persistent_unique_id = property(device_persistent_unique_id)
693
+
694
+ # }}}
695
+
696
+ # {{{ Context
697
+
698
+ _cl.Context.__repr__ = context_repr
699
+ from pytools import memoize_method
700
+ _cl.Context._get_cl_version = memoize_method(context_get_cl_version)
701
+
702
+ # }}}
703
+
704
+ # {{{ CommandQueue
705
+
706
+ _cl.CommandQueue.__enter__ = command_queue_enter
707
+ _cl.CommandQueue.__exit__ = command_queue_exit
708
+ _cl.CommandQueue._get_cl_version = memoize_method(command_queue_get_cl_version)
709
+
710
+ # }}}
711
+
712
+ # {{{ _Program (the internal, non-caching version)
713
+
714
+ _cl._Program._get_build_logs = program_get_build_logs
715
+ _cl._Program.build = program_build
716
+
717
+ # }}}
718
+
719
+ # {{{ Event
720
+
721
+ _cl.Event.profile = property(ProfilingInfoGetter)
722
+
723
+ # }}}
724
+
725
+ # {{{ Kernel
726
+
727
+ _cl.Kernel.get_work_group_info = kernel_get_work_group_info
728
+
729
+ # FIXME: Possibly deprecate this version
730
+ _cl.Kernel.set_scalar_arg_dtypes = kernel_set_arg_types
731
+ _cl.Kernel.set_arg_types = kernel_set_arg_types
732
+
733
+ _cl.Kernel.capture_call = kernel_capture_call
734
+ _cl.Kernel.get_info = kernel_get_info
735
+
736
+ # }}}
737
+
738
+ # {{{ ImageFormat
739
+
740
+ _cl.ImageFormat.__repr__ = image_format_repr
741
+ _cl.ImageFormat.__eq__ = image_format_eq
742
+ _cl.ImageFormat.__ne__ = image_format_ne
743
+ _cl.ImageFormat.__hash__ = image_format_hash
744
+
745
+ # }}}
746
+
747
+ # {{{ Image
748
+
749
+ _cl.Image.__init__ = image_init
750
+ _cl.Image.shape = property(image_shape)
751
+
752
+ # }}}
753
+
754
+ # {{{ Error
755
+
756
+ _cl.Error.__str__ = error_str
757
+ _cl.Error.code = property(error_code)
758
+ _cl.Error.routine = property(error_routine)
759
+ _cl.Error.what = property(error_what)
760
+
761
+ # }}}
762
+
763
+ # {{{ MemoryMap
764
+
765
+ _cl.MemoryMap.__doc__ = """
766
+ This class may also be used as a context manager in a ``with`` statement.
767
+ The memory corresponding to this object will be unmapped when
768
+ this object is deleted or :meth:`release` is called.
769
+
770
+ .. automethod:: release
771
+ """
772
+ _cl.MemoryMap.__enter__ = memory_map_enter
773
+ _cl.MemoryMap.__exit__ = memory_map_exit
774
+
775
+ # }}}
776
+
777
+ # {{{ SVMPointer
778
+
779
+ if _cl.get_cl_header_version() >= (2, 0):
780
+ _cl.SVMPointer.__doc__ = """A base class for things that can be passed to
781
+ functions that allow an SVM pointer, e.g. kernel enqueues and memory
782
+ copies.
783
+
784
+ Objects of this type cannot currently be directly created or
785
+ implemented in Python. To obtain objects implementing this type,
786
+ consider its subtypes :class:`SVMAllocation` and :class:`SVM`.
787
+
788
+
789
+ .. property:: svm_ptr
790
+
791
+ Gives the SVM pointer as an :class:`int`.
792
+
793
+ .. property:: size
794
+
795
+ An :class:`int` denoting the size in bytes, or *None*, if the size
796
+ of the SVM pointed to is not known.
797
+
798
+ *Most* objects of this type (e.g. instances of
799
+ :class:`SVMAllocation` and :class:`SVM` know their size, so that,
800
+ for example :class:`enqueue_copy` will automatically copy an entire
801
+ :class:`SVMAllocation` when a size is not explicitly specified.
802
+
803
+ .. automethod:: map
804
+ .. automethod:: map_ro
805
+ .. automethod:: map_rw
806
+ .. automethod:: as_buffer
807
+ .. property:: buf
808
+
809
+ An opaque object implementing the :c:func:`Python buffer protocol
810
+ <PyObject_GetBuffer>`. It exposes the pointed-to memory as
811
+ a one-dimensional buffer of bytes, with the size matching
812
+ :attr:`size`.
813
+
814
+ No guarantee is provided that two references to this attribute
815
+ result in the same object.
816
+ """
817
+
818
+ if _cl.get_cl_header_version() >= (2, 0):
819
+ _cl.SVMPointer.map = svmptr_map
820
+ _cl.SVMPointer.map_ro = svmptr_map_ro
821
+ _cl.SVMPointer.map_rw = svmptr_map_rw
822
+ _cl.SVMPointer._enqueue_unmap = svmptr__enqueue_unmap
823
+ _cl.SVMPointer.as_buffer = svmptr_as_buffer
824
+
825
+ # }}}
826
+
827
+ # {{{ SVMAllocation
828
+
829
+ if _cl.get_cl_header_version() >= (2, 0):
830
+ _cl.SVMAllocation.__doc__ = """
831
+ Is a :class:`SVMPointer`.
832
+
833
+ .. versionadded:: 2016.2
834
+
835
+ .. automethod:: __init__
836
+
837
+ :arg flags: See :class:`svm_mem_flags`.
838
+ :arg queue: If not specified, the allocation will be freed
839
+ eagerly, irrespective of whether pending/enqueued operations
840
+ are still using this memory.
841
+
842
+ If specified, deallocation of the memory will be enqueued
843
+ with the given queue, and will only be performed
844
+ after previously-enqueue operations in the queue have
845
+ completed.
846
+
847
+ It is an error to specify an out-of-order queue.
848
+
849
+ .. warning::
850
+
851
+ Not specifying a queue will typically lead to undesired
852
+ behavior, including crashes and memory corruption.
853
+ See the warning in :ref:`svm`.
854
+
855
+ .. automethod:: enqueue_release
856
+
857
+ Enqueue the release of this allocation into *queue*.
858
+ If *queue* is not specified, enqueue the deallocation
859
+ into the queue provided at allocation time or via
860
+ :class:`bind_to_queue`.
861
+
862
+ .. automethod:: bind_to_queue
863
+
864
+ Change the queue used for implicit enqueue of deallocation
865
+ to *queue*. Sufficient synchronization is ensured by
866
+ enqueuing a marker into the old queue and waiting on this
867
+ marker in the new queue.
868
+
869
+ .. automethod:: unbind_from_queue
870
+
871
+ Configure the allocation to no longer implicitly enqueue
872
+ memory allocation. If such a queue was previously provided,
873
+ :meth:`~CommandQueue.finish` is automatically called on it.
874
+ """
875
+
876
+ # }}}
877
+
878
+ # {{{ SVM
879
+
880
+ if _cl.get_cl_header_version() >= (2, 0):
881
+ _cl.SVM.__doc__ = """Tags an object exhibiting the Python buffer interface
882
+ (such as a :class:`numpy.ndarray`) as referring to shared virtual
883
+ memory.
884
+
885
+ Is a :class:`SVMPointer`, hence objects of this type may be passed
886
+ to kernel calls and :func:`enqueue_copy`, and all methods declared
887
+ there are also available there. Note that :meth:`map` differs
888
+ slightly from :meth:`SVMPointer.map`.
889
+
890
+ Depending on the features of the OpenCL implementation, the following
891
+ types of objects may be passed to/wrapped in this type:
892
+
893
+ * fine-grain shared memory as returned by (e.g.) :func:`fsvm_empty`,
894
+ if the implementation supports fine-grained shared virtual memory.
895
+ This memory may directly be passed to a kernel::
896
+
897
+ ary = cl.fsvm_empty(ctx, 1000, np.float32)
898
+ assert isinstance(ary, np.ndarray)
899
+
900
+ prg.twice(queue, ary.shape, None, cl.SVM(ary))
901
+ queue.finish() # synchronize
902
+ print(ary) # access from host
903
+
904
+ Observe how mapping (as needed in coarse-grain SVM) is no longer
905
+ necessary.
906
+
907
+ * any :class:`numpy.ndarray` (or other Python object with a buffer
908
+ interface) if the implementation supports fine-grained *system*
909
+ shared virtual memory.
910
+
911
+ This is how plain :mod:`numpy` arrays may directly be passed to a
912
+ kernel::
913
+
914
+ ary = np.zeros(1000, np.float32)
915
+ prg.twice(queue, ary.shape, None, cl.SVM(ary))
916
+ queue.finish() # synchronize
917
+ print(ary) # access from host
918
+
919
+ * coarse-grain shared memory as returned by (e.g.) :func:`csvm_empty`
920
+ for any implementation of OpenCL 2.0.
921
+
922
+ .. note::
923
+
924
+ Applications making use of coarse-grain SVM may be better
925
+ served by opaque-style SVM. See :ref:`opaque-svm`.
926
+
927
+ This is how coarse-grain SVM may be used from both host and device::
928
+
929
+ svm_ary = cl.SVM(
930
+ cl.csvm_empty(ctx, 1000, np.float32, alignment=64))
931
+ assert isinstance(svm_ary.mem, np.ndarray)
932
+
933
+ with svm_ary.map_rw(queue) as ary:
934
+ ary.fill(17) # use from host
935
+
936
+ prg.twice(queue, svm_ary.mem.shape, None, svm_ary)
937
+
938
+ Coarse-grain shared-memory *must* be mapped into host address space
939
+ using :meth:`~SVMPointer.map` before being accessed through the
940
+ :mod:`numpy` interface.
941
+
942
+ .. note::
943
+
944
+ This object merely serves as a 'tag' that changes the behavior
945
+ of functions to which it is passed. It has no special management
946
+ relationship to the memory it tags. For example, it is permissible
947
+ to grab a :class:`numpy.ndarray` out of :attr:`SVM.mem` of one
948
+ :class:`SVM` instance and use the array to construct another.
949
+ Neither of the tags need to be kept alive.
950
+
951
+ .. versionadded:: 2016.2
952
+
953
+ .. attribute:: mem
954
+
955
+ The wrapped object.
956
+
957
+ .. automethod:: __init__
958
+ .. automethod:: map
959
+ .. automethod:: map_ro
960
+ .. automethod:: map_rw
961
+ """
962
+
963
+ # }}}
964
+
965
+ if _cl.get_cl_header_version() >= (2, 0):
966
+ _cl.SVM.map = svm_map
967
+ _cl.SVM.map_ro = svm_map_ro
968
+ _cl.SVM.map_rw = svm_map_rw
969
+ _cl.SVM._enqueue_unmap = svm__enqueue_unmap
970
+
971
+ # }}}
972
+
973
+ for cls in CONSTANT_CLASSES:
974
+ cls._is_bitfield = cls in BITFIELD_CONSTANT_CLASSES
975
+ cls.to_string = classmethod(to_string)
976
+
977
+
978
+ _add_functionality()
979
+
980
+
981
+ # ORDER DEPENDENCY: Some of the above may override get_info, the effect needs
982
+ # to be visible through the attributes. So get_info attr creation needs to happen
983
+ # after the overriding is complete.
984
+
985
+ T = TypeVar("T")
986
+
987
+
988
+ InfoT = TypeVar("InfoT")
989
+
990
+
991
+ def make_getinfo(
992
+ info_method: Callable[[T, InfoT], object],
993
+ info_constant: InfoT
994
+ ) -> property:
995
+ def result(self: T) -> object:
996
+ return info_method(self, info_constant)
997
+
998
+ return property(result)
999
+
1000
+
1001
+ def make_cacheable_getinfo(
1002
+ info_method: Callable[[T, InfoT], object],
1003
+ cache_attr: str,
1004
+ info_constant: InfoT
1005
+ ) -> property:
1006
+ def result(self: T):
1007
+ try:
1008
+ return getattr(self, cache_attr)
1009
+ except AttributeError:
1010
+ pass
1011
+
1012
+ result = info_method(self, info_constant)
1013
+ setattr(self, cache_attr, result)
1014
+ return result
1015
+
1016
+ return property(result)
1017
+
1018
+
1019
+ def add_get_info(
1020
+ cls: type[T],
1021
+ info_method: Callable[[T, InfoT], object],
1022
+ info_class: type[InfoT],
1023
+ cacheable_attrs: Collection[str] = (),
1024
+ ) -> None:
1025
+ for info_name, _info_value in info_class.__dict__.items():
1026
+ if info_name == "to_string" or info_name.startswith("_"):
1027
+ continue
1028
+
1029
+ info_lower = info_name.lower()
1030
+ info_constant = cast("InfoT", getattr(info_class, info_name))
1031
+ if info_name in cacheable_attrs:
1032
+ cache_attr = intern("_info_cache_"+info_lower)
1033
+ setattr(cls, info_lower, make_cacheable_getinfo(
1034
+ info_method, cache_attr, info_constant))
1035
+ else:
1036
+ setattr(cls, info_lower, make_getinfo(info_method, info_constant))
1037
+
1038
+ # }}}
1039
+
1040
+ if _cl.have_gl():
1041
+ def gl_object_get_gl_object(self):
1042
+ return self.get_gl_object_info()[1]
1043
+
1044
+ _cl.GLBuffer.gl_object = property(gl_object_get_gl_object)
1045
+ _cl.GLTexture.gl_object = property(gl_object_get_gl_object)
1046
+
1047
+
1048
+ def _add_all_get_info():
1049
+ add_get_info(_cl.Platform, _cl.Platform.get_info, _cl.platform_info)
1050
+ add_get_info(_cl.Device, _cl.Device.get_info, _cl.device_info,
1051
+ ["PLATFORM", "MAX_WORK_GROUP_SIZE", "MAX_COMPUTE_UNITS"])
1052
+ add_get_info(_cl.Context, _cl.Context.get_info, _cl.context_info)
1053
+ add_get_info(_cl.CommandQueue, _cl.CommandQueue.get_info, _cl.command_queue_info,
1054
+ ["CONTEXT", "DEVICE"])
1055
+ add_get_info(_cl.Event, _cl.Event.get_info, _cl.event_info)
1056
+ add_get_info(_cl.MemoryObjectHolder, _cl.MemoryObjectHolder.get_info, _cl.mem_info)
1057
+ add_get_info(_cl.Image, _cl.Image.get_image_info, _cl.image_info)
1058
+ add_get_info(_cl.Pipe, _cl.Pipe.get_pipe_info, _cl.pipe_info)
1059
+ add_get_info(_cl.Kernel, _cl.Kernel.get_info, _cl.kernel_info)
1060
+ add_get_info(_cl.Sampler, _cl.Sampler.get_info, _cl.sampler_info)
1061
+
1062
+
1063
+ _add_all_get_info()