Skip to content

Apply __new__ approach to disabling __init__ #484

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions cuda_core/cuda/core/experimental/_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ class ContextOptions:
class Context:
__slots__ = ("_handle", "_id")

def __init__(self):
raise NotImplementedError("TODO")
def __new__(self, *args, **kwargs):
raise RuntimeError("Context objects cannot be instantiated directly. Please use Device or Stream APIs.")

@staticmethod
def _from_ctx(obj, dev_id):
@classmethod
def _from_ctx(cls, obj, dev_id):
assert isinstance(obj, driver.CUcontext)
ctx = Context.__new__(Context)
ctx = super().__new__(cls)
ctx._handle = obj
ctx._id = dev_id
return ctx
9 changes: 5 additions & 4 deletions cuda_core/cuda/core/experimental/_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ class DeviceProperties:
Attributes are read-only and provide information about the device.
"""

def __init__(self):
raise RuntimeError("DeviceProperties should not be instantiated directly")
def __new__(self, *args, **kwargs):
raise RuntimeError("DeviceProperties cannot be instantiated directly. Please use Device APIs.")

__slots__ = ("_handle", "_cache")

def _init(handle):
self = DeviceProperties.__new__(DeviceProperties)
@classmethod
def _init(cls, handle):
self = super().__new__(cls)
self._handle = handle
self._cache = {}
return self
Expand Down
12 changes: 6 additions & 6 deletions cuda_core/cuda/core/experimental/_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ def close(self):
handle_return(driver.cuEventDestroy(self.handle))
self.handle = None

__slots__ = ("__weakref__", "_mnff", "_timing_disabled", "_busy_waited")
def __new__(self, *args, **kwargs):
raise RuntimeError("Event objects cannot be instantiated directly. Please use Stream APIs (record).")

def __init__(self):
raise NotImplementedError("directly creating an Event object can be ambiguous. Please call Stream.record().")
__slots__ = ("__weakref__", "_mnff", "_timing_disabled", "_busy_waited")

@staticmethod
def _init(options: Optional[EventOptions] = None):
self = Event.__new__(Event)
@classmethod
def _init(cls, options: Optional[EventOptions] = None):
self = super().__new__(cls)
self._mnff = Event._MembersNeededForFinalize(self, None)

options = check_or_create_options(EventOptions, options, "Event options")
Expand Down
33 changes: 17 additions & 16 deletions cuda_core/cuda/core/experimental/_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,14 @@ def _lazy_init():


class KernelAttributes:
def __init__(self):
raise RuntimeError("KernelAttributes should not be instantiated directly")
def __new__(self, *args, **kwargs):
raise RuntimeError("KernelAttributes cannot be instantiated directly. Please use Kernel APIs.")

slots = ("_handle", "_cache", "_backend_version", "_loader")

def _init(handle):
self = KernelAttributes.__new__(KernelAttributes)
@classmethod
def _init(cls, handle):
self = super().__new__(cls)
self._handle = handle
self._cache = {}

Expand Down Expand Up @@ -189,14 +190,14 @@ class Kernel:

__slots__ = ("_handle", "_module", "_attributes")

def __init__(self):
raise RuntimeError("directly constructing a Kernel instance is not supported")
def __new__(self, *args, **kwargs):
raise RuntimeError("Kernel objects cannot be instantiated directly. Please use ObjectCode APIs.")

@staticmethod
def _from_obj(obj, mod):
@classmethod
def _from_obj(cls, obj, mod):
assert isinstance(obj, _kernel_ctypes)
assert isinstance(mod, ObjectCode)
ker = Kernel.__new__(Kernel)
ker = super().__new__(cls)
ker._handle = obj
ker._module = mod
ker._attributes = None
Expand Down Expand Up @@ -237,15 +238,15 @@ class ObjectCode:
__slots__ = ("_handle", "_backend_version", "_code_type", "_module", "_loader", "_sym_map")
_supported_code_type = ("cubin", "ptx", "ltoir", "fatbin")

def __init__(self):
raise NotImplementedError(
"directly creating an ObjectCode object can be ambiguous. Please either call Program.compile() "
"or one of the ObjectCode.from_*() constructors"
def __new__(self, *args, **kwargs):
raise RuntimeError(
"ObjectCode objects cannot be instantiated directly. "
"Please use ObjectCode APIs (from_cubin, from_ptx) or Program APIs (compile)."
)

@staticmethod
def _init(module, code_type, *, symbol_mapping: Optional[dict] = None):
self = ObjectCode.__new__(ObjectCode)
@classmethod
def _init(cls, module, code_type, *, symbol_mapping: Optional[dict] = None):
self = super().__new__(cls)
assert code_type in self._supported_code_type, f"{code_type=} is not supported"
_lazy_init()

Expand Down
55 changes: 30 additions & 25 deletions cuda_core/cuda/core/experimental/_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,37 @@ def close(self):
self.owner = None
self.handle = None

def __new__(self, *args, **kwargs):
raise RuntimeError(
"Stream objects cannot be instantiated directly. "
"Please use Device APIs (create_stream) or other Stream APIs (from_handle)."
)

__slots__ = ("__weakref__", "_mnff", "_nonblocking", "_priority", "_device_id", "_ctx_handle")

def __init__(self):
raise NotImplementedError(
"directly creating a Stream object can be ambiguous. Please either "
"call Device.create_stream() or, if a stream pointer is already "
"available from somewhere else, Stream.from_handle()"
)
@classmethod
def _legacy_default(cls):
self = super().__new__(cls)
self._mnff = Stream._MembersNeededForFinalize(self, driver.CUstream(driver.CU_STREAM_LEGACY), None, True)
self._nonblocking = None # delayed
self._priority = None # delayed
self._device_id = None # delayed
self._ctx_handle = None # delayed
Comment on lines +88 to +89
Copy link
Member

@leofang leofang Mar 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC this is actually a bug fix too! I wonder why it wasn't caught before...

return self

@staticmethod
def _init(obj=None, *, options: Optional[StreamOptions] = None):
self = Stream.__new__(Stream)
@classmethod
def _per_thread_default(cls):
self = super().__new__(cls)
self._mnff = Stream._MembersNeededForFinalize(self, driver.CUstream(driver.CU_STREAM_PER_THREAD), None, True)
self._nonblocking = None # delayed
self._priority = None # delayed
self._device_id = None # delayed
self._ctx_handle = None # delayed
Comment on lines +98 to +99
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

return self

@classmethod
def _init(cls, obj=None, *, options: Optional[StreamOptions] = None):
self = super().__new__(cls)
self._mnff = Stream._MembersNeededForFinalize(self, None, None, False)

if obj is not None and options is not None:
Expand Down Expand Up @@ -295,22 +314,8 @@ def __cuda_stream__(self):
return Stream._init(obj=_stream_holder())


class _LegacyDefaultStream(Stream):
def __init__(self):
self._mnff = Stream._MembersNeededForFinalize(self, driver.CUstream(driver.CU_STREAM_LEGACY), None, True)
self._nonblocking = None # delayed
self._priority = None # delayed


class _PerThreadDefaultStream(Stream):
def __init__(self):
self._mnff = Stream._MembersNeededForFinalize(self, driver.CUstream(driver.CU_STREAM_PER_THREAD), None, True)
self._nonblocking = None # delayed
self._priority = None # delayed


LEGACY_DEFAULT_STREAM = _LegacyDefaultStream()
PER_THREAD_DEFAULT_STREAM = _PerThreadDefaultStream()
LEGACY_DEFAULT_STREAM = Stream._legacy_default()
PER_THREAD_DEFAULT_STREAM = Stream._per_thread_default()


def default_stream():
Expand Down
16 changes: 16 additions & 0 deletions cuda_core/tests/test_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2025 NVIDIA Corporation. All rights reserved.
#
# Please refer to the NVIDIA end user license agreement (EULA) associated
# with this source code for terms and conditions that govern your use of
# this software. Any use, reproduction, disclosure, or distribution of
# this software and related documentation outside the terms of the EULA
# is strictly prohibited.

import pytest

import cuda.core.experimental


def test_context_init_disabled():
with pytest.raises(RuntimeError, match=r"^Context objects cannot be instantiated directly\."):
cuda.core.experimental._context.Context() # Ensure back door is locked.
6 changes: 6 additions & 0 deletions cuda_core/tests/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@
from cuda import cudart as runtime
import pytest

import cuda.core.experimental
from cuda.core.experimental import Device
from cuda.core.experimental._utils import ComputeCapability, get_binding_version, handle_return


def test_device_init_disabled():
with pytest.raises(RuntimeError, match=r"^DeviceProperties cannot be instantiated directly\."):
cuda.core.experimental._device.DeviceProperties() # Ensure back door is locked.


@pytest.fixture(scope="module")
def cuda_version():
# binding availability depends on cuda-python version
Expand Down
6 changes: 6 additions & 0 deletions cuda_core/tests/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@

import pytest

import cuda.core.experimental
from cuda.core.experimental import Device, EventOptions


def test_event_init_disabled():
with pytest.raises(RuntimeError, match=r"^Event objects cannot be instantiated directly\."):
cuda.core.experimental._event.Event() # Ensure back door is locked.


@pytest.mark.parametrize("enable_timing", [True, False, None])
def test_timing(init_cuda, enable_timing):
options = EventOptions(enable_timing=enable_timing)
Expand Down
16 changes: 16 additions & 0 deletions cuda_core/tests/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import pytest

import cuda.core.experimental
from cuda.core.experimental import ObjectCode, Program, ProgramOptions, system

SAXPY_KERNEL = """
Expand All @@ -28,6 +29,21 @@
"""


def test_kernel_attributes_init_disabled():
with pytest.raises(RuntimeError, match=r"^KernelAttributes cannot be instantiated directly\."):
cuda.core.experimental._module.KernelAttributes() # Ensure back door is locked.


def test_kernel_init_disabled():
with pytest.raises(RuntimeError, match=r"^Kernel objects cannot be instantiated directly\."):
cuda.core.experimental._module.Kernel() # Ensure back door is locked.


def test_object_code_init_disabled():
with pytest.raises(RuntimeError, match=r"^ObjectCode objects cannot be instantiated directly\."):
ObjectCode() # Reject at front door.


@pytest.fixture(scope="function")
def get_saxpy_kernel(init_cuda):
# prepare program
Expand Down
6 changes: 3 additions & 3 deletions cuda_core/tests/test_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
from cuda.core.experimental._utils import driver


def test_stream_init():
with pytest.raises(NotImplementedError):
Stream()
def test_stream_init_disabled():
with pytest.raises(RuntimeError, match=r"^Stream objects cannot be instantiated directly\."):
Stream() # Reject at front door.


def test_stream_init_with_options(init_cuda):
Expand Down