Skip to content

Commit c6cd193

Browse files
Cythonize _program.py (#1565)
* Begin Cythonization of _program.py - Rename _program.py to _program.pyx - Convert Program to cdef class with _program.pxd declarations - Extract _MembersNeededForFinalize to module-level _ProgramMNFF (nested classes not allowed in cdef class) - Add __repr__ method to Program - Keep ProgramOptions as @DataClass (unchanged) - Keep weakref.finalize pattern for handle cleanup * Extract Program helpers to module-level cdef functions - Move _translate_program_options to Program_translate_options (cdef) - Move _can_load_generated_ptx to Program_can_load_generated_ptx (cdef) - Remove unused TYPE_CHECKING import block - Follow _memory/_buffer.pyx helper function patterns * Complete Cythonization of _program.py - Reorganize file structure per developer guide (principal class first) - Add module docstring, __all__, type alias section - Factor long methods into cdef inline helpers - Add proper exception specs to cdef functions - Fix docstrings (use :class: refs, public paths) - Add type annotations to public methods - Inline _nvvm_exception_manager (single use) - Remove Union import, use | syntax - Add public Program.driver_can_load_nvrtc_ptx_output() API - Update tests to use new public API Closes #1082 * Extend test_object_protocols.py with Program and ObjectCode variations Add fixtures for different Program backends (NVRTC, PTX, NVVM) and ObjectCode code types (cubin, PTX, LTOIR). Split API_TYPES into more precise HASH_TYPES, EQ_TYPES, and WEAKREF_TYPES lists. Derive DICT_KEY_TYPES and WEAK_KEY_TYPES for collection tests. * Add NVRTC/NVVM resource handles and remove Program MNFF - Add NvrtcProgramHandle and NvvmProgramHandle to resource handles module - Add function pointer initialization for nvrtcDestroyProgram and nvvmDestroyProgram - Forward-declare nvvmProgram to avoid nvvm.h dependency - Refactor detail::make_py to accept module name parameter - Remove _ProgramMNFF class from _program.pyx - Program now uses typed handles directly with RAII cleanup - Update handle property to return None when handle is null * Add HANDLE_RETURN_NVRTC and HANDLE_RETURN_NVVM, simplify HANDLE_RETURN - Add NVVMError exception class - Add HANDLE_RETURN_NVRTC for nogil NVRTC error handling with program log - Add HANDLE_RETURN_NVVM for nogil NVVM error handling with program log - Remove vestigial supported_error_type fused type - Simplify HANDLE_RETURN to directly take cydriver.CUresult * Fix build errors, update tests, remove unused imports - Change cdef function return types from ObjectCode to object (Cython limitation) - Remove unused imports: intptr_t, NvrtcProgramHandle, NvvmProgramHandle, as_intptr - Update as_py(NvvmProgramHandle) to return Python int via PyLong_FromSsize_t - Update test assertions: remove handle checks after close(), test idempotency instead - Update NVVM error message regex to match new unified format * Address review feedback: keep _can_load_generated_ptx private, update SPDX dates - Remove Program.driver_can_load_nvrtc_ptx_output() public static method - Make _can_load_generated_ptx a cpdef (callable from Python tests) - Update tests to import _can_load_generated_ptx from cuda.core._program - Update SPDX copyright years to 2024-2026 for files with 2024-2025 - Update get_kernel docstring: name parameter is str | bytes Co-authored-by: Cursor <cursoragent@cursor.com> * Address review feedback: NVVMError inherits from nvvmError, clean up error helpers - Make NVVMError inherit from cuda.bindings.nvvm.nvvmError for compatibility with code catching nvvmError, while also inheriting from CUDAError - Simplify HANDLE_RETURN_NVRTC and HANDLE_RETURN_NVVM to just check success and delegate to helper functions - Move all error handling logic into _raise_nvrtc_error and _raise_nvvm_error Co-authored-by: Cursor <cursoragent@cursor.com> * Add 0.6.x release notes with cuda-bindings build requirement change Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 80463f6 commit c6cd193

14 files changed

Lines changed: 1437 additions & 913 deletions

cuda_core/cuda/core/_cpp/resource_handles.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ decltype(&cuLibraryLoadData) p_cuLibraryLoadData = nullptr;
5656
decltype(&cuLibraryUnload) p_cuLibraryUnload = nullptr;
5757
decltype(&cuLibraryGetKernel) p_cuLibraryGetKernel = nullptr;
5858

59+
// NVRTC function pointers
60+
decltype(&nvrtcDestroyProgram) p_nvrtcDestroyProgram = nullptr;
61+
62+
// NVVM function pointers (may be null if NVVM is not available)
63+
NvvmDestroyProgramFn p_nvvmDestroyProgram = nullptr;
64+
5965
// ============================================================================
6066
// GIL management helpers
6167
// ============================================================================
@@ -764,4 +770,64 @@ KernelHandle create_kernel_handle_ref(CUkernel kernel, const LibraryHandle& h_li
764770
return KernelHandle(box, &box->resource);
765771
}
766772

773+
// ============================================================================
774+
// NVRTC Program Handles
775+
// ============================================================================
776+
777+
namespace {
778+
struct NvrtcProgramBox {
779+
nvrtcProgram resource;
780+
};
781+
} // namespace
782+
783+
NvrtcProgramHandle create_nvrtc_program_handle(nvrtcProgram prog) {
784+
auto box = std::shared_ptr<NvrtcProgramBox>(
785+
new NvrtcProgramBox{prog},
786+
[](NvrtcProgramBox* b) {
787+
// Note: nvrtcDestroyProgram takes nvrtcProgram* and nulls it,
788+
// but we're deleting the box anyway so nulling is harmless.
789+
// Errors are ignored (standard destructor practice).
790+
p_nvrtcDestroyProgram(&b->resource);
791+
delete b;
792+
}
793+
);
794+
return NvrtcProgramHandle(box, &box->resource);
795+
}
796+
797+
NvrtcProgramHandle create_nvrtc_program_handle_ref(nvrtcProgram prog) {
798+
auto box = std::make_shared<NvrtcProgramBox>(NvrtcProgramBox{prog});
799+
return NvrtcProgramHandle(box, &box->resource);
800+
}
801+
802+
// ============================================================================
803+
// NVVM Program Handles
804+
// ============================================================================
805+
806+
namespace {
807+
struct NvvmProgramBox {
808+
nvvmProgram resource;
809+
};
810+
} // namespace
811+
812+
NvvmProgramHandle create_nvvm_program_handle(nvvmProgram prog) {
813+
auto box = std::shared_ptr<NvvmProgramBox>(
814+
new NvvmProgramBox{prog},
815+
[](NvvmProgramBox* b) {
816+
// Note: nvvmDestroyProgram takes nvvmProgram* and nulls it,
817+
// but we're deleting the box anyway so nulling is harmless.
818+
// If NVVM is not available, the function pointer is null.
819+
if (p_nvvmDestroyProgram) {
820+
p_nvvmDestroyProgram(&b->resource);
821+
}
822+
delete b;
823+
}
824+
);
825+
return NvvmProgramHandle(box, &box->resource);
826+
}
827+
828+
NvvmProgramHandle create_nvvm_program_handle_ref(nvvmProgram prog) {
829+
auto box = std::make_shared<NvvmProgramBox>(NvvmProgramBox{prog});
830+
return NvvmProgramHandle(box, &box->resource);
831+
}
832+
767833
} // namespace cuda_core

cuda_core/cuda/core/_cpp/resource_handles.hpp

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@
66

77
#include <Python.h>
88
#include <cuda.h>
9+
#include <nvrtc.h>
910
#include <cstdint>
1011
#include <memory>
1112

13+
// Forward declaration for NVVM - avoids nvvm.h dependency
14+
// Use void* to match cuda.bindings.cynvvm's typedef
15+
using nvvmProgram = void*;
16+
1217
namespace cuda_core {
1318

1419
// ============================================================================
@@ -67,6 +72,28 @@ extern decltype(&cuLibraryLoadData) p_cuLibraryLoadData;
6772
extern decltype(&cuLibraryUnload) p_cuLibraryUnload;
6873
extern decltype(&cuLibraryGetKernel) p_cuLibraryGetKernel;
6974

75+
// ============================================================================
76+
// NVRTC function pointers
77+
//
78+
// These are populated by _resource_handles.pyx at module import time using
79+
// function pointers extracted from cuda.bindings.cynvrtc.__pyx_capi__.
80+
// ============================================================================
81+
82+
extern decltype(&nvrtcDestroyProgram) p_nvrtcDestroyProgram;
83+
84+
// ============================================================================
85+
// NVVM function pointers
86+
//
87+
// These are populated by _resource_handles.pyx at module import time using
88+
// function pointers extracted from cuda.bindings.cynvvm.__pyx_capi__.
89+
// Note: May be null if NVVM is not available at runtime.
90+
// ============================================================================
91+
92+
// Function pointer type for nvvmDestroyProgram (avoids nvvm.h dependency)
93+
// Signature: nvvmResult nvvmDestroyProgram(nvvmProgram *prog)
94+
using NvvmDestroyProgramFn = int (*)(nvvmProgram*);
95+
extern NvvmDestroyProgramFn p_nvvmDestroyProgram;
96+
7097
// ============================================================================
7198
// Handle type aliases - expose only the raw CUDA resource
7299
// ============================================================================
@@ -77,6 +104,8 @@ using EventHandle = std::shared_ptr<const CUevent>;
77104
using MemoryPoolHandle = std::shared_ptr<const CUmemoryPool>;
78105
using LibraryHandle = std::shared_ptr<const CUlibrary>;
79106
using KernelHandle = std::shared_ptr<const CUkernel>;
107+
using NvrtcProgramHandle = std::shared_ptr<const nvrtcProgram>;
108+
using NvvmProgramHandle = std::shared_ptr<const nvvmProgram>;
80109

81110
// ============================================================================
82111
// Context handle functions
@@ -260,6 +289,33 @@ KernelHandle create_kernel_handle(const LibraryHandle& h_library, const char* na
260289
// Use for borrowed kernels. The library handle keeps the library alive.
261290
KernelHandle create_kernel_handle_ref(CUkernel kernel, const LibraryHandle& h_library);
262291

292+
// ============================================================================
293+
// NVRTC Program handle functions
294+
// ============================================================================
295+
296+
// Create an owning NVRTC program handle.
297+
// When the last reference is released, nvrtcDestroyProgram is called.
298+
// Use this to wrap a program created via nvrtcCreateProgram.
299+
NvrtcProgramHandle create_nvrtc_program_handle(nvrtcProgram prog);
300+
301+
// Create a non-owning NVRTC program handle (references existing program).
302+
// The program will NOT be destroyed when the handle is released.
303+
NvrtcProgramHandle create_nvrtc_program_handle_ref(nvrtcProgram prog);
304+
305+
// ============================================================================
306+
// NVVM Program handle functions
307+
// ============================================================================
308+
309+
// Create an owning NVVM program handle.
310+
// When the last reference is released, nvvmDestroyProgram is called.
311+
// Use this to wrap a program created via nvvmCreateProgram.
312+
// Note: If NVVM is not available (p_nvvmDestroyProgram is null), the deleter is a no-op.
313+
NvvmProgramHandle create_nvvm_program_handle(nvvmProgram prog);
314+
315+
// Create a non-owning NVVM program handle (references existing program).
316+
// The program will NOT be destroyed when the handle is released.
317+
NvvmProgramHandle create_nvvm_program_handle_ref(nvvmProgram prog);
318+
263319
// ============================================================================
264320
// Overloaded helper functions to extract raw resources from handles
265321
// ============================================================================
@@ -293,6 +349,14 @@ inline CUkernel as_cu(const KernelHandle& h) noexcept {
293349
return h ? *h : nullptr;
294350
}
295351

352+
inline nvrtcProgram as_cu(const NvrtcProgramHandle& h) noexcept {
353+
return h ? *h : nullptr;
354+
}
355+
356+
inline nvvmProgram as_cu(const NvvmProgramHandle& h) noexcept {
357+
return h ? *h : nullptr;
358+
}
359+
296360
// as_intptr() - extract handle as intptr_t for Python interop
297361
// Using signed intptr_t per C standard convention and issue #1342
298362
inline std::intptr_t as_intptr(const ContextHandle& h) noexcept {
@@ -323,11 +387,19 @@ inline std::intptr_t as_intptr(const KernelHandle& h) noexcept {
323387
return reinterpret_cast<std::intptr_t>(as_cu(h));
324388
}
325389

326-
// as_py() - convert handle to Python driver wrapper object (returns new reference)
390+
inline std::intptr_t as_intptr(const NvrtcProgramHandle& h) noexcept {
391+
return reinterpret_cast<std::intptr_t>(as_cu(h));
392+
}
393+
394+
inline std::intptr_t as_intptr(const NvvmProgramHandle& h) noexcept {
395+
return reinterpret_cast<std::intptr_t>(as_cu(h));
396+
}
397+
398+
// as_py() - convert handle to Python wrapper object (returns new reference)
327399
namespace detail {
328400
// n.b. class lookup is not cached to avoid deadlock hazard, see DESIGN.md
329-
inline PyObject* make_py(const char* class_name, std::intptr_t value) noexcept {
330-
PyObject* mod = PyImport_ImportModule("cuda.bindings.driver");
401+
inline PyObject* make_py(const char* module_name, const char* class_name, std::intptr_t value) noexcept {
402+
PyObject* mod = PyImport_ImportModule(module_name);
331403
if (!mod) return nullptr;
332404
PyObject* cls = PyObject_GetAttrString(mod, class_name);
333405
Py_DECREF(mod);
@@ -339,31 +411,40 @@ inline PyObject* make_py(const char* class_name, std::intptr_t value) noexcept {
339411
} // namespace detail
340412

341413
inline PyObject* as_py(const ContextHandle& h) noexcept {
342-
return detail::make_py("CUcontext", as_intptr(h));
414+
return detail::make_py("cuda.bindings.driver", "CUcontext", as_intptr(h));
343415
}
344416

345417
inline PyObject* as_py(const StreamHandle& h) noexcept {
346-
return detail::make_py("CUstream", as_intptr(h));
418+
return detail::make_py("cuda.bindings.driver", "CUstream", as_intptr(h));
347419
}
348420

349421
inline PyObject* as_py(const EventHandle& h) noexcept {
350-
return detail::make_py("CUevent", as_intptr(h));
422+
return detail::make_py("cuda.bindings.driver", "CUevent", as_intptr(h));
351423
}
352424

353425
inline PyObject* as_py(const MemoryPoolHandle& h) noexcept {
354-
return detail::make_py("CUmemoryPool", as_intptr(h));
426+
return detail::make_py("cuda.bindings.driver", "CUmemoryPool", as_intptr(h));
355427
}
356428

357429
inline PyObject* as_py(const DevicePtrHandle& h) noexcept {
358-
return detail::make_py("CUdeviceptr", as_intptr(h));
430+
return detail::make_py("cuda.bindings.driver", "CUdeviceptr", as_intptr(h));
359431
}
360432

361433
inline PyObject* as_py(const LibraryHandle& h) noexcept {
362-
return detail::make_py("CUlibrary", as_intptr(h));
434+
return detail::make_py("cuda.bindings.driver", "CUlibrary", as_intptr(h));
363435
}
364436

365437
inline PyObject* as_py(const KernelHandle& h) noexcept {
366-
return detail::make_py("CUkernel", as_intptr(h));
438+
return detail::make_py("cuda.bindings.driver", "CUkernel", as_intptr(h));
439+
}
440+
441+
inline PyObject* as_py(const NvrtcProgramHandle& h) noexcept {
442+
return detail::make_py("cuda.bindings.nvrtc", "nvrtcProgram", as_intptr(h));
443+
}
444+
445+
inline PyObject* as_py(const NvvmProgramHandle& h) noexcept {
446+
// NVVM bindings use raw integers, not wrapper classes
447+
return PyLong_FromSsize_t(as_intptr(h));
367448
}
368449

369450
} // namespace cuda_core

cuda_core/cuda/core/_module.pyx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,7 @@ cdef class ObjectCode:
800800

801801
Parameters
802802
----------
803-
name : Any
803+
name : str | bytes
804804
Name of the kernel to retrieve.
805805

806806
Returns
@@ -816,7 +816,8 @@ cdef class ObjectCode:
816816
try:
817817
name = self._sym_map[name]
818818
except KeyError:
819-
name = name.encode()
819+
if isinstance(name, str):
820+
name = name.encode()
820821

821822
cdef KernelHandle h_kernel = create_kernel_handle(self._h_library, <const char*>name)
822823
if not h_kernel:

cuda_core/cuda/core/_program.pxd

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
from ._resource_handles cimport NvrtcProgramHandle, NvvmProgramHandle
6+
7+
8+
cdef class Program:
9+
cdef:
10+
NvrtcProgramHandle _h_nvrtc
11+
NvvmProgramHandle _h_nvvm
12+
str _backend
13+
object _linker # Linker
14+
object _options # ProgramOptions
15+
object __weakref__

0 commit comments

Comments
 (0)