Skip to content

Commit b0f9c38

Browse files
committed
Fix #3246: Correctly check .pyx/.pxd dependencies, and fix inline lints
1 parent 5d65e6f commit b0f9c38

9 files changed

Lines changed: 129 additions & 100 deletions

File tree

‎.pre-commit-config.yaml‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,18 @@ repos:
219219
rev: "35dbc940c2bd4499ebf39aba00ad717e05cec9b5" # frozen: v0.22.0
220220
hooks:
221221
- id: cython-lint
222-
args: [--no-pycodestyle]
223-
exclude: ^cuda_bindings_(?:12|13)/
222+
# Some rules (e.g. "inline" on a function declared in a .pxd) match
223+
# declarations in one file against implementations in another, and the
224+
# linter only remembers that within a single invocation. Pre-commit
225+
# splits filenames across parallel processes and only passes changed
226+
# files, so a .pyx edited without its .pxd would escape the check.
227+
# Instead, whenever any Cython file under these trees changes, lint
228+
# all of them in one process. Generated cuda_bindings_* and
229+
# third-party submodules are not listed.
230+
args: [--no-pycodestyle, cuda_core/cuda]
231+
files: ^cuda_core/cuda
232+
pass_filenames: false
233+
require_serial: true
224234
additional_dependencies:
225235
- Cython==3.2.9
226236

‎cuda_core/cuda/core/_memory/_legacy.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from cuda.core.typing import DevicePointerType
1313

1414
from cuda.core._memory._buffer import Buffer, MemoryResource
15-
from cuda.core._utils.cuda_utils import (
15+
from cuda.core._utils.cuda_utils import ( # type: ignore[attr-defined] # stubgen-pyx omits .pxd cpdef bodies
1616
_check_driver_error as raise_if_driver_error,
1717
)
1818
from cuda.core._utils.cuda_utils import (

‎cuda_core/cuda/core/_stream.pxd‎

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5+
from libc.stdint cimport uintptr_t
6+
57
from cuda.bindings cimport cydriver
6-
from cuda.core._rt cimport ContextHandle, StreamHandle
8+
from cuda.core._rt cimport ContextHandle, StreamHandle, as_cu
79

810

911
cdef class Stream:
@@ -28,6 +30,30 @@ cdef inline int Stream_check_open(Stream self) except -1:
2830
if not self._h_stream:
2931
raise RuntimeError("Stream has been closed")
3032
return 0
31-
cdef bint Stream_handle_is_default_token(cydriver.CUstream s) noexcept nogil
32-
cdef bint Stream_is_default_token(Stream self) noexcept nogil
33-
cdef bint Stream_is_legacy_default_token(Stream self) noexcept nogil
33+
34+
35+
cdef inline bint Stream_handle_is_default_token(cydriver.CUstream s) noexcept nogil:
36+
"""Return True for the raw CU_STREAM_LEGACY and CU_STREAM_PER_THREAD tokens.
37+
38+
These tokens carry no context of their own; they refer to whatever context
39+
is current, so nothing resolved from one may be cached on the object.
40+
"""
41+
cdef uintptr_t h = <uintptr_t>s
42+
return h == <uintptr_t>cydriver.CU_STREAM_LEGACY or h == <uintptr_t>cydriver.CU_STREAM_PER_THREAD
43+
44+
45+
cdef inline bint Stream_is_default_token(Stream self) noexcept nogil:
46+
"""Return True when this stream is CU_STREAM_LEGACY or CU_STREAM_PER_THREAD."""
47+
return Stream_handle_is_default_token(as_cu(self._h_stream))
48+
49+
50+
cdef inline bint Stream_is_legacy_default_token(Stream self) noexcept nogil:
51+
"""Return True only for CU_STREAM_LEGACY.
52+
53+
Unlike CU_STREAM_PER_THREAD, the legacy default stream token is rejected
54+
outright (CUDA_ERROR_INVALID_VALUE) by cuMemcpyWithAttributesAsync and
55+
cuMemcpyBatchAsync; CU_STREAM_PER_THREAD is a real stream to those entry
56+
points and is accepted normally. Use this narrower check, not
57+
Stream_is_default_token, wherever that distinction matters.
58+
"""
59+
return <uintptr_t>as_cu(self._h_stream) == <uintptr_t>cydriver.CU_STREAM_LEGACY

‎cuda_core/cuda/core/_stream.pyx‎

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -517,32 +517,6 @@ cpdef Stream default_stream():
517517
else:
518518
return LEGACY_DEFAULT_STREAM
519519

520-
cdef inline bint Stream_handle_is_default_token(cydriver.CUstream s) noexcept nogil:
521-
"""Return True for the raw CU_STREAM_LEGACY and CU_STREAM_PER_THREAD tokens.
522-
523-
These tokens carry no context of their own; they refer to whatever context
524-
is current, so nothing resolved from one may be cached on the object.
525-
"""
526-
cdef uintptr_t h = <uintptr_t>s
527-
return h == <uintptr_t>cydriver.CU_STREAM_LEGACY or h == <uintptr_t>cydriver.CU_STREAM_PER_THREAD
528-
529-
530-
cdef inline bint Stream_is_default_token(Stream self) noexcept nogil:
531-
"""Return True when this stream is CU_STREAM_LEGACY or CU_STREAM_PER_THREAD."""
532-
return Stream_handle_is_default_token(as_cu(self._h_stream))
533-
534-
535-
cdef inline bint Stream_is_legacy_default_token(Stream self) noexcept nogil:
536-
"""Return True only for CU_STREAM_LEGACY.
537-
538-
Unlike CU_STREAM_PER_THREAD, the legacy default stream token is rejected
539-
outright (CUDA_ERROR_INVALID_VALUE) by cuMemcpyWithAttributesAsync and
540-
cuMemcpyBatchAsync; CU_STREAM_PER_THREAD is a real stream to those entry
541-
points and is accepted normally. Use this narrower check, not
542-
Stream_is_default_token, wherever that distinction matters.
543-
"""
544-
return <uintptr_t>as_cu(self._h_stream) == <uintptr_t>cydriver.CU_STREAM_LEGACY
545-
546520

547521
cdef inline int Stream_get_ctx(Stream self, ContextHandle* h_context) except?-1 nogil:
548522
"""Resolve the stream's context handle into ``h_context``."""

‎cuda_core/cuda/core/_utils/cuda_utils.pxd‎

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,36 @@ cdef inline int HANDLE_RETURN_NVJITLINK(
5454
cdef int _get_current_device_id() except? -1
5555

5656

57+
# Success sentinels compared against by the inline checks below.
58+
cdef object _RUNTIME_SUCCESS
59+
cdef object _NVRTC_SUCCESS
60+
61+
# Out-of-line failure paths for the inline checks below; keeps the error
62+
# formatting (and its Python-level dependencies) out of every cimporting module.
63+
cdef int _raise_driver_error(cydriver.CUresult error) except -1 nogil
64+
cdef int _raise_runtime_error(error) except -1
65+
cdef int _raise_nvrtc_status_error(error, handle) except -1
66+
67+
5768
# TODO: stop exposing these within the codebase?
58-
cpdef int _check_driver_error(cydriver.CUresult error) except?-1 nogil
59-
cpdef int _check_runtime_error(error) except?-1
60-
cpdef int _check_nvrtc_error(error) except?-1
69+
cpdef inline int _check_driver_error(cydriver.CUresult error) except?-1 nogil:
70+
if error == cydriver.CUresult.CUDA_SUCCESS:
71+
return 0
72+
return _raise_driver_error(error)
73+
74+
75+
cpdef inline int _check_runtime_error(error) except?-1:
76+
if error == _RUNTIME_SUCCESS:
77+
return 0
78+
return _raise_runtime_error(error)
79+
80+
81+
cpdef inline int _check_nvrtc_error(error, handle=None) except?-1:
82+
if error == _NVRTC_SUCCESS:
83+
return 0
84+
return _raise_nvrtc_status_error(error, handle)
85+
86+
6187
cdef int _raise_nvrtc_error(cynvrtc.nvrtcProgram prog, cynvrtc.nvrtcResult err) except -1
6288
cdef int _raise_nvvm_error(cynvvm.nvvmProgram prog, cynvvm.nvvmResult err) except -1
6389
cdef int _raise_nvjitlink_error(cynvjitlink.nvJitLinkHandle handle, cynvjitlink.nvJitLinkResult err) except -1

‎cuda_core/cuda/core/_utils/cuda_utils.pyi‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
from typing import Any, Callable, NamedTuple
44

5-
from cuda.bindings import cydriver
65
from cuda.bindings import driver as driver
76
from cuda.bindings import nvrtc as nvrtc
87
from cuda.bindings import runtime as runtime
98

109
_keep_driver_in_stub: driver.CUresult
1110
_keep_nvrtc_in_stub: nvrtc.nvrtcResult
1211
_keep_runtime_in_stub: runtime.cudaError_t
12+
_RUNTIME_SUCCESS = runtime.cudaError_t.cudaSuccess
13+
_NVRTC_SUCCESS = nvrtc.nvrtcResult.NVRTC_SUCCESS
1314
_fork_warning_checked = False
1415

1516
class CUDAError(Exception):
@@ -52,9 +53,6 @@ class ComputeCapability(NamedTuple):
5253
minor: int
5354

5455
def cast_to_3_tuple(label: str, cfg: int | tuple[int, ...]) -> tuple[int, int, int]: ...
55-
def _check_driver_error(error: cydriver.CUresult) -> int: ...
56-
def _check_runtime_error(error) -> int: ...
57-
def _check_nvrtc_error(error, handle=None) -> int: ...
5856
def handle_return(result: tuple[Any, ...], handle: object | None=None) -> Any: ...
5957
def check_or_create_options(cls: type, options: object, options_description: str='', keep_none: bool=False) -> object:
6058
"""

‎cuda_core/cuda/core/_utils/cuda_utils.pyx‎

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -162,18 +162,16 @@ cdef int _raise_nvjitlink_error(
162162
raise exc
163163

164164

165-
cdef object _RUNTIME_SUCCESS = runtime.cudaError_t.cudaSuccess
166-
cdef object _NVRTC_SUCCESS = nvrtc.nvrtcResult.NVRTC_SUCCESS
165+
_RUNTIME_SUCCESS = runtime.cudaError_t.cudaSuccess
166+
_NVRTC_SUCCESS = nvrtc.nvrtcResult.NVRTC_SUCCESS
167167

168168

169169
cdef inline void _attach_detail(exc, str detail):
170170
# PEP 678 notes keep the detail separable from the message.
171171
exc.add_note(detail)
172172

173173

174-
cpdef int _check_driver_error(cydriver.CUresult error) except?-1 nogil:
175-
if error == cydriver.CUresult.CUDA_SUCCESS:
176-
return 0
174+
cdef int _raise_driver_error(cydriver.CUresult error) except -1 nogil:
177175
cdef const char* name
178176
cdef const char* desc
179177
# A context-scoped helper in the handle layer may have recorded why this
@@ -197,9 +195,7 @@ cpdef int _check_driver_error(cydriver.CUresult error) except?-1 nogil:
197195
raise exc
198196

199197

200-
cpdef int _check_runtime_error(error) except?-1:
201-
if error == _RUNTIME_SUCCESS:
202-
return 0
198+
cdef int _raise_runtime_error(error) except -1:
203199
# `_check_error()` reaches this path only for `runtime.cudaError_t` values.
204200
# Use the enum name directly because Windows hybrid cudart can lag that table.
205201
name = error.name
@@ -213,9 +209,7 @@ cpdef int _check_runtime_error(error) except?-1:
213209
raise CUDAError(f"{name}: {desc}")
214210

215211

216-
cpdef int _check_nvrtc_error(error, handle=None) except?-1:
217-
if error == _NVRTC_SUCCESS:
218-
return 0
212+
cdef int _raise_nvrtc_status_error(error, handle) except -1:
219213
err = f"{error}: {nvrtc.nvrtcGetErrorString(error)[1].decode()}"
220214
if handle is not None:
221215
_, logsize = nvrtc.nvrtcGetProgramLogSize(handle)

‎cuda_core/cuda/core/graph/_graph_node.pxd‎

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
from libc.stddef cimport size_t
6+
from libc.stdint cimport uintptr_t
67

78
from cuda.bindings cimport cydriver
9+
from cuda.core._memory._buffer cimport Buffer, Buffer_check_open
810
from cuda.core._rt cimport (
911
GraphHandle,
1012
GraphNodeHandle,
1113
OpaqueHandle,
14+
as_cu,
1215
as_intptr,
1316
graph_node_get_graph,
17+
make_opaque_py,
1418
)
1519

1620

@@ -32,8 +36,51 @@ cdef inline int GN_check_valid(GraphNode self) except -1:
3236
return 0
3337

3438

35-
cdef OpaqueHandle _resolve_memcpy_operand(
36-
object operand, object owner, str side, cydriver.CUdeviceptr* out_ptr) except *
39+
cdef inline OpaqueHandle _buffer_attachment_owner(Buffer buf, str label):
40+
"""Copy a Buffer's device-pointer handle into an attachment owner."""
41+
Buffer_check_open(buf)
42+
# The local is required: Cython permits the DevicePtrHandle -> OpaqueHandle
43+
# conversion on assignment, but not directly in a return statement.
44+
cdef OpaqueHandle attachment_owner = buf._h_ptr
45+
return attachment_owner
46+
47+
48+
cdef inline OpaqueHandle _resolve_memcpy_operand(
49+
object operand, object owner, str side,
50+
cydriver.CUdeviceptr* out_ptr) except *:
51+
"""Resolve an operand to a pointer and optional attachment owner.
52+
53+
``operand`` is a :class:`Buffer` or a raw integer address; its device
54+
pointer is written to ``out_ptr``. For a :class:`Buffer` operand, returns an
55+
owner that retains the underlying allocation (not the wrapper). For a raw
56+
pointer, returns an owner built from ``owner`` (or an empty handle when
57+
``owner`` is ``None``).
58+
59+
Raises
60+
------
61+
ValueError
62+
If ``operand`` is a :class:`Buffer` and ``owner`` is not ``None``.
63+
If a :class:`Buffer` operand or ``*_owner`` has no active allocation.
64+
"""
65+
cdef Buffer buf
66+
cdef OpaqueHandle attachment_owner
67+
68+
if isinstance(operand, Buffer):
69+
if owner is not None:
70+
raise ValueError(
71+
f"{side}_owner cannot be used when {side} is a Buffer"
72+
)
73+
buf = operand
74+
attachment_owner = _buffer_attachment_owner(buf, side)
75+
out_ptr[0] = as_cu(buf._h_ptr)
76+
return attachment_owner
77+
out_ptr[0] = <cydriver.CUdeviceptr><uintptr_t>operand
78+
if owner is None:
79+
return OpaqueHandle()
80+
if isinstance(owner, Buffer):
81+
return _buffer_attachment_owner(owner, f"{side}_owner")
82+
return make_opaque_py(owner)
83+
3784

3885
cdef cydriver.CUmemorytype _get_memcpy_memory_type(
3986
cydriver.CUdeviceptr ptr) except *

‎cuda_core/cuda/core/graph/_graph_node.pyx‎

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ from cuda.bindings cimport cydriver
2020
from cuda.core._event cimport Event, Event_check_open
2121
from cuda.core._kernel_arg_handler cimport ParamHolder
2222
from cuda.core._launch_config cimport LaunchConfig
23-
from cuda.core._memory._buffer cimport Buffer, Buffer_check_open
23+
from cuda.core._memory._buffer cimport Buffer
2424
from cuda.core._memory._location cimport cumemlocation_from_id
2525
from cuda.core._module cimport Kernel
2626
from cuda.core.graph._graph_definition cimport (
@@ -914,52 +914,6 @@ cdef inline FreeNode GN_free(GraphNode self, cydriver.CUdeviceptr c_dptr):
914914
return _registered(FreeNode._create_with_params(create_graph_node_handle(new_node, h_graph), c_dptr))
915915
916916
917-
cdef inline OpaqueHandle _buffer_attachment_owner(Buffer buf, str label):
918-
"""Copy a Buffer's device-pointer handle into an attachment owner."""
919-
Buffer_check_open(buf)
920-
# The local is required: Cython permits the DevicePtrHandle -> OpaqueHandle
921-
# conversion on assignment, but not directly in a return statement.
922-
cdef OpaqueHandle attachment_owner = buf._h_ptr
923-
return attachment_owner
924-
925-
926-
cdef inline OpaqueHandle _resolve_memcpy_operand(
927-
object operand, object owner, str side,
928-
cydriver.CUdeviceptr* out_ptr) except *:
929-
"""Resolve an operand to a pointer and optional attachment owner.
930-
931-
``operand`` is a :class:`Buffer` or a raw integer address; its device
932-
pointer is written to ``out_ptr``. For a :class:`Buffer` operand, returns an
933-
owner that retains the underlying allocation (not the wrapper). For a raw
934-
pointer, returns an owner built from ``owner`` (or an empty handle when
935-
``owner`` is ``None``).
936-
937-
Raises
938-
------
939-
ValueError
940-
If ``operand`` is a :class:`Buffer` and ``owner`` is not ``None``.
941-
If a :class:`Buffer` operand or ``*_owner`` has no active allocation.
942-
"""
943-
cdef Buffer buf
944-
cdef OpaqueHandle attachment_owner
945-
946-
if isinstance(operand, Buffer):
947-
if owner is not None:
948-
raise ValueError(
949-
f"{side}_owner cannot be used when {side} is a Buffer"
950-
)
951-
buf = operand
952-
attachment_owner = _buffer_attachment_owner(buf, side)
953-
out_ptr[0] = as_cu(buf._h_ptr)
954-
return attachment_owner
955-
out_ptr[0] = <cydriver.CUdeviceptr><uintptr_t>operand
956-
if owner is None:
957-
return OpaqueHandle()
958-
if isinstance(owner, Buffer):
959-
return _buffer_attachment_owner(owner, f"{side}_owner")
960-
return make_opaque_py(owner)
961-
962-
963917
cdef inline MemsetNode GN_memset(
964918
GraphNode self, cydriver.CUdeviceptr c_dst, OpaqueHandle dst_owner,
965919
unsigned int val, unsigned int elem_size,

0 commit comments

Comments
 (0)