Skip to content

Commit 9ef1562

Browse files
authored
Allow ObjectCode to have a name (#682)
* allow ObjectCode to have a name * fix linter * update cuLink default name * ensure we look at the right object
1 parent 430e890 commit 9ef1562

File tree

6 files changed

+79
-32
lines changed

6 files changed

+79
-32
lines changed

cuda_core/cuda/core/experimental/_linker.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ class LinkerOptions:
100100
101101
Attributes
102102
----------
103+
name : str, optional
104+
Name of the linker. If the linking succeeds, the name is passed down to the generated `ObjectCode`.
103105
arch : str, optional
104106
Pass the SM architecture value, such as ``sm_<CC>`` (for generating CUBIN) or
105107
``compute_<CC>`` (for generating PTX). If not provided, the current device's architecture
@@ -161,6 +163,7 @@ class LinkerOptions:
161163
Default: False.
162164
"""
163165

166+
name: Optional[str] = "<default linker>"
164167
arch: Optional[str] = None
165168
max_register_count: Optional[int] = None
166169
time: Optional[bool] = None
@@ -184,6 +187,7 @@ class LinkerOptions:
184187

185188
def __post_init__(self):
186189
_lazy_init()
190+
self._name = self.name.encode()
187191
self.formatted_options = []
188192
if _nvjitlink:
189193
self._init_nvjitlink()
@@ -393,7 +397,7 @@ def _add_code_object(self, object_code: ObjectCode):
393397
data = object_code._module
394398
assert_type(data, bytes)
395399
with _exception_manager(self):
396-
name_str = f"{object_code._handle}_{object_code._code_type}"
400+
name_str = f"{object_code.name}"
397401
if _nvjitlink:
398402
_nvjitlink.add_data(
399403
self._mnff.handle,
@@ -455,7 +459,7 @@ def link(self, target_type) -> ObjectCode:
455459
addr, size = handle_return(_driver.cuLinkComplete(self._mnff.handle))
456460
code = (ctypes.c_char * size).from_address(addr)
457461

458-
return ObjectCode._init(bytes(code), target_type)
462+
return ObjectCode._init(bytes(code), target_type, name=self._options.name)
459463

460464
def get_error_log(self) -> str:
461465
"""Get the error log generated by the linker.

cuda_core/cuda/core/experimental/_module.py

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ class ObjectCode:
449449
context.
450450
"""
451451

452-
__slots__ = ("_handle", "_backend_version", "_code_type", "_module", "_loader", "_sym_map")
452+
__slots__ = ("_handle", "_backend_version", "_code_type", "_module", "_loader", "_sym_map", "_name")
453453
_supported_code_type = ("cubin", "ptx", "ltoir", "fatbin", "object", "library")
454454

455455
def __new__(self, *args, **kwargs):
@@ -459,7 +459,7 @@ def __new__(self, *args, **kwargs):
459459
)
460460

461461
@classmethod
462-
def _init(cls, module, code_type, *, symbol_mapping: Optional[dict] = None):
462+
def _init(cls, module, code_type, *, name: str = "", symbol_mapping: Optional[dict] = None):
463463
self = super().__new__(cls)
464464
assert code_type in self._supported_code_type, f"{code_type=} is not supported"
465465
_lazy_init()
@@ -473,112 +473,131 @@ def _init(cls, module, code_type, *, symbol_mapping: Optional[dict] = None):
473473
self._code_type = code_type
474474
self._module = module
475475
self._sym_map = {} if symbol_mapping is None else symbol_mapping
476+
self._name = name
476477

477478
return self
478479

479480
@classmethod
480-
def _reduce_helper(self, module, code_type, symbol_mapping):
481+
def _reduce_helper(self, module, code_type, name, symbol_mapping):
481482
# just for forwarding kwargs
482-
return ObjectCode._init(module, code_type, symbol_mapping=symbol_mapping)
483+
return ObjectCode._init(module, code_type, name=name, symbol_mapping=symbol_mapping)
483484

484485
def __reduce__(self):
485-
return ObjectCode._reduce_helper, (self._module, self._code_type, self._sym_map)
486+
return ObjectCode._reduce_helper, (self._module, self._code_type, self._name, self._sym_map)
486487

487488
@staticmethod
488-
def from_cubin(module: Union[bytes, str], *, symbol_mapping: Optional[dict] = None) -> "ObjectCode":
489+
def from_cubin(module: Union[bytes, str], *, name: str = "", symbol_mapping: Optional[dict] = None) -> "ObjectCode":
489490
"""Create an :class:`ObjectCode` instance from an existing cubin.
490491
491492
Parameters
492493
----------
493494
module : Union[bytes, str]
494495
Either a bytes object containing the in-memory cubin to load, or
495496
a file path string pointing to the on-disk cubin to load.
497+
name : Optional[str]
498+
A human-readable identifier representing this code object.
496499
symbol_mapping : Optional[dict]
497500
A dictionary specifying how the unmangled symbol names (as keys)
498501
should be mapped to the mangled names before trying to retrieve
499502
them (default to no mappings).
500503
"""
501-
return ObjectCode._init(module, "cubin", symbol_mapping=symbol_mapping)
504+
return ObjectCode._init(module, "cubin", name=name, symbol_mapping=symbol_mapping)
502505

503506
@staticmethod
504-
def from_ptx(module: Union[bytes, str], *, symbol_mapping: Optional[dict] = None) -> "ObjectCode":
507+
def from_ptx(module: Union[bytes, str], *, name: str = "", symbol_mapping: Optional[dict] = None) -> "ObjectCode":
505508
"""Create an :class:`ObjectCode` instance from an existing PTX.
506509
507510
Parameters
508511
----------
509512
module : Union[bytes, str]
510513
Either a bytes object containing the in-memory ptx code to load, or
511514
a file path string pointing to the on-disk ptx file to load.
515+
name : Optional[str]
516+
A human-readable identifier representing this code object.
512517
symbol_mapping : Optional[dict]
513518
A dictionary specifying how the unmangled symbol names (as keys)
514519
should be mapped to the mangled names before trying to retrieve
515520
them (default to no mappings).
516521
"""
517-
return ObjectCode._init(module, "ptx", symbol_mapping=symbol_mapping)
522+
return ObjectCode._init(module, "ptx", name=name, symbol_mapping=symbol_mapping)
518523

519524
@staticmethod
520-
def from_ltoir(module: Union[bytes, str], *, symbol_mapping: Optional[dict] = None) -> "ObjectCode":
525+
def from_ltoir(module: Union[bytes, str], *, name: str = "", symbol_mapping: Optional[dict] = None) -> "ObjectCode":
521526
"""Create an :class:`ObjectCode` instance from an existing LTOIR.
522527
523528
Parameters
524529
----------
525530
module : Union[bytes, str]
526531
Either a bytes object containing the in-memory ltoir code to load, or
527532
a file path string pointing to the on-disk ltoir file to load.
533+
name : Optional[str]
534+
A human-readable identifier representing this code object.
528535
symbol_mapping : Optional[dict]
529536
A dictionary specifying how the unmangled symbol names (as keys)
530537
should be mapped to the mangled names before trying to retrieve
531538
them (default to no mappings).
532539
"""
533-
return ObjectCode._init(module, "ltoir", symbol_mapping=symbol_mapping)
540+
return ObjectCode._init(module, "ltoir", name=name, symbol_mapping=symbol_mapping)
534541

535542
@staticmethod
536-
def from_fatbin(module: Union[bytes, str], *, symbol_mapping: Optional[dict] = None) -> "ObjectCode":
543+
def from_fatbin(
544+
module: Union[bytes, str], *, name: str = "", symbol_mapping: Optional[dict] = None
545+
) -> "ObjectCode":
537546
"""Create an :class:`ObjectCode` instance from an existing fatbin.
538547
539548
Parameters
540549
----------
541550
module : Union[bytes, str]
542551
Either a bytes object containing the in-memory fatbin to load, or
543552
a file path string pointing to the on-disk fatbin to load.
553+
name : Optional[str]
554+
A human-readable identifier representing this code object.
544555
symbol_mapping : Optional[dict]
545556
A dictionary specifying how the unmangled symbol names (as keys)
546557
should be mapped to the mangled names before trying to retrieve
547558
them (default to no mappings).
548559
"""
549-
return ObjectCode._init(module, "fatbin", symbol_mapping=symbol_mapping)
560+
return ObjectCode._init(module, "fatbin", name=name, symbol_mapping=symbol_mapping)
550561

551562
@staticmethod
552-
def from_object(module: Union[bytes, str], *, symbol_mapping: Optional[dict] = None) -> "ObjectCode":
563+
def from_object(
564+
module: Union[bytes, str], *, name: str = "", symbol_mapping: Optional[dict] = None
565+
) -> "ObjectCode":
553566
"""Create an :class:`ObjectCode` instance from an existing object code.
554567
555568
Parameters
556569
----------
557570
module : Union[bytes, str]
558571
Either a bytes object containing the in-memory object code to load, or
559572
a file path string pointing to the on-disk object code to load.
573+
name : Optional[str]
574+
A human-readable identifier representing this code object.
560575
symbol_mapping : Optional[dict]
561576
A dictionary specifying how the unmangled symbol names (as keys)
562577
should be mapped to the mangled names before trying to retrieve
563578
them (default to no mappings).
564579
"""
565-
return ObjectCode._init(module, "object", symbol_mapping=symbol_mapping)
580+
return ObjectCode._init(module, "object", name=name, symbol_mapping=symbol_mapping)
566581

567582
@staticmethod
568-
def from_library(module: Union[bytes, str], *, symbol_mapping: Optional[dict] = None) -> "ObjectCode":
583+
def from_library(
584+
module: Union[bytes, str], *, name: str = "", symbol_mapping: Optional[dict] = None
585+
) -> "ObjectCode":
569586
"""Create an :class:`ObjectCode` instance from an existing library.
570587
571588
Parameters
572589
----------
573590
module : Union[bytes, str]
574591
Either a bytes object containing the in-memory library to load, or
575592
a file path string pointing to the on-disk library to load.
593+
name : Optional[str]
594+
A human-readable identifier representing this code object.
576595
symbol_mapping : Optional[dict]
577596
A dictionary specifying how the unmangled symbol names (as keys)
578597
should be mapped to the mangled names before trying to retrieve
579598
them (default to no mappings).
580599
"""
581-
return ObjectCode._init(module, "library", symbol_mapping=symbol_mapping)
600+
return ObjectCode._init(module, "library", name=name, symbol_mapping=symbol_mapping)
582601

583602
# TODO: do we want to unload in a finalizer? Probably not..
584603

@@ -632,6 +651,11 @@ def code(self) -> CodeTypeT:
632651
"""Return the underlying code object."""
633652
return self._module
634653

654+
@property
655+
def name(self) -> str:
656+
"""Return a human-readable name of this code object."""
657+
return self._name
658+
635659
@property
636660
@precondition(_lazy_load_module)
637661
def handle(self):

cuda_core/cuda/core/experimental/_program.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class ProgramOptions:
5757
5858
Attributes
5959
----------
60+
name : str, optional
61+
Name of the program. If the compilation succeeds, the name is passed down to the generated `ObjectCode`.
6062
arch : str, optional
6163
Pass the SM architecture value, such as ``sm_<CC>`` (for generating CUBIN) or
6264
``compute_<CC>`` (for generating PTX). If not provided, the current device's architecture
@@ -180,6 +182,7 @@ class ProgramOptions:
180182
Default: False
181183
"""
182184

185+
name: Optional[str] = "<default program>"
183186
arch: Optional[str] = None
184187
relocatable_device_code: Optional[bool] = None
185188
extensible_whole_program: Optional[bool] = None
@@ -222,6 +225,8 @@ class ProgramOptions:
222225
minimal: Optional[bool] = None
223226

224227
def __post_init__(self):
228+
self._name = self.name.encode()
229+
225230
self._formatted_options = []
226231
if self.arch is not None:
227232
self._formatted_options.append(f"--gpu-architecture={self.arch}")
@@ -396,7 +401,7 @@ def __init__(self, code, code_type, options: ProgramOptions = None):
396401
# TODO: support pre-loaded headers & include names
397402
# TODO: allow tuples once NVIDIA/cuda-python#72 is resolved
398403

399-
self._mnff.handle = handle_return(nvrtc.nvrtcCreateProgram(code.encode(), b"", 0, [], []))
404+
self._mnff.handle = handle_return(nvrtc.nvrtcCreateProgram(code.encode(), options._name, 0, [], []))
400405
self._backend = "NVRTC"
401406
self._linker = None
402407

@@ -413,6 +418,7 @@ def __init__(self, code, code_type, options: ProgramOptions = None):
413418

414419
def _translate_program_options(self, options: ProgramOptions) -> LinkerOptions:
415420
return LinkerOptions(
421+
name=options.name,
416422
arch=options.arch,
417423
max_register_count=options.max_register_count,
418424
time=options.time,
@@ -505,7 +511,7 @@ def compile(self, target_type, name_expressions=(), logs=None):
505511
handle_return(nvrtc.nvrtcGetProgramLog(self._mnff.handle, log), handle=self._mnff.handle)
506512
logs.write(log.decode("utf-8", errors="backslashreplace"))
507513

508-
return ObjectCode._init(data, target_type, symbol_mapping=symbol_mapping)
514+
return ObjectCode._init(data, target_type, symbol_mapping=symbol_mapping, name=self._options.name)
509515

510516
supported_backends = ("nvJitLink", "driver")
511517
if self._backend not in supported_backends:

cuda_core/docs/source/release/0.3.0-notes.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ Breaking Changes
2020
New features
2121
------------
2222

23-
- :class:`Kernel` adds :property:`Kernel.num_arguments` and :property:`Kernel.arguments_info` for introspection of kernel arguments. (#612)
24-
- Add pythonic access to kernel occupancy calculation functions via :property:`Kernel.occupancy`. (#648)
23+
- :class:`Kernel` adds :attr:`Kernel.num_arguments` and :attr:`Kernel.arguments_info` for introspection of kernel arguments. (#612)
24+
- Add pythonic access to kernel occupancy calculation functions via :attr:`Kernel.occupancy`. (#648)
2525
- Support launching cooperative kernels by setting :property:`LaunchConfig.cooperative_launch` to `True`.
26+
- A name can be assigned to :class:`ObjectCode` instances generated by both :class:`Program` and :class:`Linker` through their respective
27+
options.
28+
2629

2730
New examples
2831
------------

cuda_core/tests/test_linker.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def test_linker_link_ptx_nvjitlink(compile_ltoir_functions):
109109
linker = Linker(*compile_ltoir_functions, options=options)
110110
linked_code = linker.link("ptx")
111111
assert isinstance(linked_code, ObjectCode)
112+
assert linked_code.name == options.name
112113

113114

114115
@pytest.mark.skipif(not is_culink_backend, reason="nvjitlink requires lto for ptx linking")
@@ -117,13 +118,15 @@ def test_linker_link_ptx_culink(compile_ptx_functions):
117118
linker = Linker(*compile_ptx_functions, options=options)
118119
linked_code = linker.link("ptx")
119120
assert isinstance(linked_code, ObjectCode)
121+
assert linked_code.name == options.name
120122

121123

122124
def test_linker_link_cubin(compile_ptx_functions):
123125
options = LinkerOptions(arch=ARCH)
124126
linker = Linker(*compile_ptx_functions, options=options)
125127
linked_code = linker.link("cubin")
126128
assert isinstance(linked_code, ObjectCode)
129+
assert linked_code.name == options.name
127130

128131

129132
def test_linker_link_ptx_multiple(compile_ptx_functions):
@@ -132,6 +135,7 @@ def test_linker_link_ptx_multiple(compile_ptx_functions):
132135
linker = Linker(*ptxes, options=options)
133136
linked_code = linker.link("cubin")
134137
assert isinstance(linked_code, ObjectCode)
138+
assert linked_code.name == options.name
135139

136140

137141
def test_linker_link_invalid_target_type(compile_ptx_functions):
@@ -144,14 +148,16 @@ def test_linker_link_invalid_target_type(compile_ptx_functions):
144148
# this test causes an API error when using the culink API
145149
@skipif_testing_with_compute_sanitizer
146150
def test_linker_get_error_log(compile_ptx_functions):
147-
options = LinkerOptions(arch=ARCH)
151+
options = LinkerOptions(name="ABC", arch=ARCH)
148152

149153
replacement_kernel = """
150154
extern __device__ int Z();
151155
extern __device__ int C(int a, int b);
152156
__global__ void A() { int result = C(Z(), 1);}
153157
"""
154-
dummy_program = Program(replacement_kernel, "c++", ProgramOptions(relocatable_device_code=True)).compile("ptx")
158+
dummy_program = Program(
159+
replacement_kernel, "c++", ProgramOptions(name="CBA", relocatable_device_code=True)
160+
).compile("ptx")
155161
linker = Linker(dummy_program, *(compile_ptx_functions[1:]), options=options)
156162
try:
157163
linker.link("cubin")
@@ -160,8 +166,9 @@ def test_linker_get_error_log(compile_ptx_functions):
160166
log = linker.get_error_log()
161167
assert isinstance(log, str)
162168
# TODO when 4902246 is addressed, we can update this to cover nvjitlink as well
169+
# The error is coming from the input object that's being linked (CBA), not the output object (ABC).
163170
if is_culink_backend:
164-
assert log.rstrip("\x00") == "error : Undefined reference to '_Z1Zv' in 'None_ptx'"
171+
assert log.rstrip("\x00") == "error : Undefined reference to '_Z1Zv' in 'CBA'"
165172

166173

167174
def test_linker_get_info_log(compile_ptx_functions):

cuda_core/tests/test_program.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def ptx_code_object():
2424
@pytest.mark.parametrize(
2525
"options",
2626
[
27+
ProgramOptions(name="abc"),
2728
ProgramOptions(device_code_optimize=True, debug=True),
2829
ProgramOptions(relocatable_device_code=True, max_register_count=32),
2930
ProgramOptions(ftz=True, prec_sqrt=False, prec_div=False),
@@ -105,21 +106,23 @@ def test_program_init_invalid_code_format():
105106
# This is tested against the current device's arch
106107
def test_program_compile_valid_target_type(init_cuda):
107108
code = 'extern "C" __global__ void my_kernel() {}'
108-
program = Program(code, "c++")
109+
program = Program(code, "c++", options={"name": "42"})
109110

110111
with warnings.catch_warnings(record=True) as w:
111112
warnings.simplefilter("always")
112113
ptx_object_code = program.compile("ptx")
114+
assert isinstance(ptx_object_code, ObjectCode)
115+
assert ptx_object_code.name == "42"
113116
if any("The CUDA driver version is older than the backend version" in str(warning.message) for warning in w):
114117
pytest.skip("PTX version too new for current driver")
118+
ptx_kernel = ptx_object_code.get_kernel("my_kernel")
119+
assert isinstance(ptx_kernel, Kernel)
115120

116-
program = Program(ptx_object_code._module.decode(), "ptx")
121+
program = Program(ptx_object_code._module.decode(), "ptx", options={"name": "24"})
117122
cubin_object_code = program.compile("cubin")
118-
ptx_kernel = ptx_object_code.get_kernel("my_kernel")
119-
cubin_kernel = cubin_object_code.get_kernel("my_kernel")
120-
assert isinstance(ptx_object_code, ObjectCode)
121123
assert isinstance(cubin_object_code, ObjectCode)
122-
assert isinstance(ptx_kernel, Kernel)
124+
assert cubin_object_code.name == "24"
125+
cubin_kernel = cubin_object_code.get_kernel("my_kernel")
123126
assert isinstance(cubin_kernel, Kernel)
124127

125128

0 commit comments

Comments
 (0)