reprospect 0.0.7__tar.gz
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.
- reprospect-0.0.7/LICENSE +21 -0
- reprospect-0.0.7/PKG-INFO +24 -0
- reprospect-0.0.7/README.md +11 -0
- reprospect-0.0.7/pyproject.toml +107 -0
- reprospect-0.0.7/reprospect/__init__.py +6 -0
- reprospect-0.0.7/reprospect/installers/cuda_bindings.py +101 -0
- reprospect-0.0.7/reprospect/installers/nsight_systems.py +75 -0
- reprospect-0.0.7/reprospect/test/__init__.py +0 -0
- reprospect-0.0.7/reprospect/test/case.py +44 -0
- reprospect-0.0.7/reprospect/test/cmake.py +99 -0
- reprospect-0.0.7/reprospect/test/environment.py +97 -0
- reprospect-0.0.7/reprospect/test/sass/__init__.py +0 -0
- reprospect-0.0.7/reprospect/test/sass/composite.py +141 -0
- reprospect-0.0.7/reprospect/test/sass/composite_impl.py +223 -0
- reprospect-0.0.7/reprospect/test/sass/instruction.py +899 -0
- reprospect-0.0.7/reprospect/test/sass/matchers/__init__.py +0 -0
- reprospect-0.0.7/reprospect/test/sass/matchers/add_int128.py +204 -0
- reprospect-0.0.7/reprospect/tools/__init__.py +0 -0
- reprospect-0.0.7/reprospect/tools/architecture.py +189 -0
- reprospect-0.0.7/reprospect/tools/binaries/__init__.py +15 -0
- reprospect-0.0.7/reprospect/tools/binaries/cuobjdump.py +354 -0
- reprospect-0.0.7/reprospect/tools/binaries/demangle.py +45 -0
- reprospect-0.0.7/reprospect/tools/binaries/elf.py +398 -0
- reprospect-0.0.7/reprospect/tools/binaries/nvdisasm.py +304 -0
- reprospect-0.0.7/reprospect/tools/cacher.py +146 -0
- reprospect-0.0.7/reprospect/tools/device_properties.py +187 -0
- reprospect-0.0.7/reprospect/tools/ncu.py +1098 -0
- reprospect-0.0.7/reprospect/tools/nsys.py +565 -0
- reprospect-0.0.7/reprospect/tools/sass/__init__.py +9 -0
- reprospect-0.0.7/reprospect/tools/sass/controlflow.py +224 -0
- reprospect-0.0.7/reprospect/tools/sass/decode.py +338 -0
- reprospect-0.0.7/reprospect/utils/__init__.py +0 -0
- reprospect-0.0.7/reprospect/utils/cmake.py +116 -0
- reprospect-0.0.7/reprospect/utils/compile_command.py +27 -0
- reprospect-0.0.7/reprospect/utils/detect.py +97 -0
- reprospect-0.0.7/reprospect/utils/ldd.py +19 -0
- reprospect-0.0.7/reprospect/utils/nvcc.py +18 -0
- reprospect-0.0.7/reprospect/utils/rich_helpers.py +87 -0
- reprospect-0.0.7/reprospect/utils/subprocess_helpers.py +54 -0
- reprospect-0.0.7/reprospect.egg-info/PKG-INFO +24 -0
- reprospect-0.0.7/reprospect.egg-info/SOURCES.txt +46 -0
- reprospect-0.0.7/reprospect.egg-info/dependency_links.txt +1 -0
- reprospect-0.0.7/reprospect.egg-info/entry_points.txt +4 -0
- reprospect-0.0.7/reprospect.egg-info/requires.txt +14 -0
- reprospect-0.0.7/reprospect.egg-info/top_level.txt +1 -0
- reprospect-0.0.7/requirements/requirements.txt +14 -0
- reprospect-0.0.7/setup.cfg +4 -0
- reprospect-0.0.7/setup.py +58 -0
reprospect-0.0.7/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ULiege CSM
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reprospect
|
|
3
|
+
Version: 0.0.7
|
|
4
|
+
Summary: A framework for reproducible prospecting of CUDA applications.
|
|
5
|
+
Author: Arnst Maarten, Tomasetti Romin
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/uliegecsm/reprospect
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: attrs
|
|
11
|
+
Requires-Dist: backports.strenum
|
|
12
|
+
Requires-Dist: blake3
|
|
13
|
+
Requires-Dist: cmake-file-api==0.0.8.3
|
|
14
|
+
Requires-Dist: ijson
|
|
15
|
+
Requires-Dist: mypy_extensions
|
|
16
|
+
Requires-Dist: pandas
|
|
17
|
+
Requires-Dist: pyelftools
|
|
18
|
+
Requires-Dist: regex
|
|
19
|
+
Requires-Dist: rich
|
|
20
|
+
Requires-Dist: rich-tools
|
|
21
|
+
Requires-Dist: semantic_version
|
|
22
|
+
Requires-Dist: system-helpers>=0.0.3
|
|
23
|
+
Requires-Dist: typing-extensions>=4.4.0
|
|
24
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/reprospect/)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
# reprospect
|
|
8
|
+
|
|
9
|
+
`ReProspect` provides a framework and tools for reproducible prospecting of CUDA applications.
|
|
10
|
+
|
|
11
|
+
See [the documentation](https://uliegecsm.github.io/reprospect/index.html) for more information.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"setuptools",
|
|
4
|
+
"mypy[mypyc]==1.19.0",
|
|
5
|
+
|
|
6
|
+
# mypyc requires that runtime dependencies are also installed
|
|
7
|
+
# during the build, since it parses the code base.
|
|
8
|
+
# For now, the only solution is to copy-paste the list here.
|
|
9
|
+
"attrs",
|
|
10
|
+
"backports.strenum",
|
|
11
|
+
"blake3",
|
|
12
|
+
"cmake-file-api==0.0.8.3",
|
|
13
|
+
"ijson",
|
|
14
|
+
"mypy_extensions",
|
|
15
|
+
"pandas",
|
|
16
|
+
"pyelftools",
|
|
17
|
+
"regex",
|
|
18
|
+
"rich",
|
|
19
|
+
"rich-tools",
|
|
20
|
+
"semantic_version",
|
|
21
|
+
"system-helpers>=0.0.3",
|
|
22
|
+
"typing-extensions>=4.4.0",
|
|
23
|
+
|
|
24
|
+
# Requirements from requirements/requirements.python.txt that are required by mypyc.
|
|
25
|
+
"types-regex",
|
|
26
|
+
]
|
|
27
|
+
build-backend = "setuptools.build_meta"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools]
|
|
30
|
+
packages = [
|
|
31
|
+
"reprospect",
|
|
32
|
+
"reprospect.installers",
|
|
33
|
+
"reprospect.test",
|
|
34
|
+
"reprospect.test.sass",
|
|
35
|
+
"reprospect.test.sass.matchers",
|
|
36
|
+
"reprospect.tools",
|
|
37
|
+
"reprospect.tools.binaries",
|
|
38
|
+
"reprospect.tools.sass",
|
|
39
|
+
"reprospect.utils",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.dynamic]
|
|
43
|
+
# https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html#dynamic-metadata
|
|
44
|
+
dependencies = {file = ["requirements/requirements.txt"]}
|
|
45
|
+
|
|
46
|
+
[project]
|
|
47
|
+
name = "reprospect"
|
|
48
|
+
description = "A framework for reproducible prospecting of CUDA applications."
|
|
49
|
+
version = "0.0.7"
|
|
50
|
+
license = "MIT"
|
|
51
|
+
license-files = ["LICENSE"]
|
|
52
|
+
requires-python = ">= 3.10"
|
|
53
|
+
dynamic = [
|
|
54
|
+
"dependencies",
|
|
55
|
+
]
|
|
56
|
+
authors = [
|
|
57
|
+
{name = "Arnst Maarten"},
|
|
58
|
+
{name = "Tomasetti Romin"},
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[project.urls]
|
|
62
|
+
Homepage = "https://github.com/uliegecsm/reprospect"
|
|
63
|
+
|
|
64
|
+
[project.scripts]
|
|
65
|
+
reprospect-install-cuda-bindings = "reprospect.installers.cuda_bindings:main"
|
|
66
|
+
reprospect-install-nsight-systems = "reprospect.installers.nsight_systems:main"
|
|
67
|
+
reprospect-utils-detect-gpus = "reprospect.utils.detect:main"
|
|
68
|
+
|
|
69
|
+
[tool.mypy]
|
|
70
|
+
follow_untyped_imports = true
|
|
71
|
+
warn_unreachable = true
|
|
72
|
+
warn_unused_configs = true
|
|
73
|
+
warn_unused_ignores = true
|
|
74
|
+
|
|
75
|
+
packages = ["reprospect"]
|
|
76
|
+
|
|
77
|
+
[tool.ruff.lint.per-file-ignores]
|
|
78
|
+
"docs/source/conf.py" = ["E402"]
|
|
79
|
+
|
|
80
|
+
[tool.pylint.main]
|
|
81
|
+
fail-on = [
|
|
82
|
+
"I",
|
|
83
|
+
]
|
|
84
|
+
fail-under = 10
|
|
85
|
+
|
|
86
|
+
[tool.pylint."messages control"]
|
|
87
|
+
enable = [
|
|
88
|
+
"useless-suppression",
|
|
89
|
+
]
|
|
90
|
+
disable = [
|
|
91
|
+
"c-extension-no-member",
|
|
92
|
+
"line-too-long",
|
|
93
|
+
"logging-fstring-interpolation",
|
|
94
|
+
"missing-class-docstring",
|
|
95
|
+
"missing-function-docstring",
|
|
96
|
+
"missing-module-docstring",
|
|
97
|
+
"too-few-public-methods",
|
|
98
|
+
"too-many-arguments",
|
|
99
|
+
"too-many-locals",
|
|
100
|
+
"too-many-positional-arguments",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
[tool.cibuildwheel]
|
|
104
|
+
build = []
|
|
105
|
+
archs = []
|
|
106
|
+
|
|
107
|
+
build-verbosity = 1
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import urllib.request
|
|
7
|
+
|
|
8
|
+
import packaging.version
|
|
9
|
+
|
|
10
|
+
def get_available(package : str) -> list[packaging.version.Version]:
|
|
11
|
+
"""
|
|
12
|
+
Get available versions of `package` from `PyPI`.
|
|
13
|
+
"""
|
|
14
|
+
with urllib.request.urlopen(f'https://pypi.org/pypi/{package}/json') as resp:
|
|
15
|
+
data = json.load(resp)
|
|
16
|
+
return [packaging.version.Version(x) for x in data["releases"]]
|
|
17
|
+
|
|
18
|
+
def get_candidate(*, target : packaging.version.Version, versions : list[packaging.version.Version]) -> packaging.version.Version | None:
|
|
19
|
+
"""
|
|
20
|
+
Match exactly if possible; otherwise, match the closest lower patch version that has the same major and minor.
|
|
21
|
+
"""
|
|
22
|
+
candidate = None
|
|
23
|
+
|
|
24
|
+
for version in versions:
|
|
25
|
+
# Reject pre/dev/post releases.
|
|
26
|
+
if version.pre or version.dev or version.post:
|
|
27
|
+
continue
|
|
28
|
+
|
|
29
|
+
# Exact match.
|
|
30
|
+
if version == target:
|
|
31
|
+
return version
|
|
32
|
+
|
|
33
|
+
if version.major == target.major and version.minor == target.minor:
|
|
34
|
+
if version < target and (candidate is None or version > candidate):
|
|
35
|
+
candidate = version
|
|
36
|
+
return candidate
|
|
37
|
+
|
|
38
|
+
def install_cuda_bindings(target : packaging.version.Version) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Pick a version of ``cuda-bindings`` that best matches `target`.
|
|
41
|
+
|
|
42
|
+
.. warning::
|
|
43
|
+
|
|
44
|
+
``cuda-bindings`` was once part of the ``cuda-python`` package, which is now a meta-package.
|
|
45
|
+
Therefore, installing ``cuda-bindings`` alone might be impossible for some versions.
|
|
46
|
+
For instance, there is no standalone ``cuda-bindings==12.6``, but this version can be installed
|
|
47
|
+
as part of ``cuda-python==12.6``.
|
|
48
|
+
|
|
49
|
+
References:
|
|
50
|
+
|
|
51
|
+
* https://nvidia.github.io/cuda-python/latest/
|
|
52
|
+
* https://nvidia.github.io/cuda-python/cuda-bindings/latest/
|
|
53
|
+
"""
|
|
54
|
+
versions = get_available(package = 'cuda-bindings')
|
|
55
|
+
logging.info(f'Available versions for \'cuda-bindings\': {versions}.')
|
|
56
|
+
|
|
57
|
+
candidate = get_candidate(target = target, versions = versions)
|
|
58
|
+
|
|
59
|
+
if candidate is not None:
|
|
60
|
+
logging.info(f'Installing \'cuda-bindings\' {candidate}.')
|
|
61
|
+
requirement = f'cuda-bindings=={candidate}'
|
|
62
|
+
else:
|
|
63
|
+
logging.warning('Could not find a suitable version of \'cuda-bindings\'.')
|
|
64
|
+
|
|
65
|
+
versions = get_available(package = 'cuda-python')
|
|
66
|
+
logging.info(f'Available versions for \'cuda-python\': {versions}.')
|
|
67
|
+
|
|
68
|
+
candidate = get_candidate(target = target, versions = versions)
|
|
69
|
+
|
|
70
|
+
if candidate is None:
|
|
71
|
+
raise RuntimeError('Could not find a suitable version of \'cuda-python\'.')
|
|
72
|
+
|
|
73
|
+
logging.info(f'Installing \'cuda-python\' {candidate}.')
|
|
74
|
+
requirement = f'cuda-python=={candidate}'
|
|
75
|
+
|
|
76
|
+
subprocess.check_call(('pip', 'install', requirement))
|
|
77
|
+
|
|
78
|
+
def parse_args() -> argparse.Namespace:
|
|
79
|
+
"""
|
|
80
|
+
Parse CLI arguments.
|
|
81
|
+
"""
|
|
82
|
+
parser = argparse.ArgumentParser(
|
|
83
|
+
formatter_class = argparse.ArgumentDefaultsHelpFormatter,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
parser.add_argument('--version', type = packaging.version.Version, required = False, default = os.environ['CUDA_VERSION'])
|
|
87
|
+
|
|
88
|
+
return parser.parse_args()
|
|
89
|
+
|
|
90
|
+
def main() -> None:
|
|
91
|
+
|
|
92
|
+
logging.basicConfig(level = logging.INFO)
|
|
93
|
+
|
|
94
|
+
args = parse_args()
|
|
95
|
+
logging.info(f'Received arguments: {args}.')
|
|
96
|
+
|
|
97
|
+
install_cuda_bindings(target = args.version)
|
|
98
|
+
|
|
99
|
+
if __name__ == '__main__':
|
|
100
|
+
|
|
101
|
+
main()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import typing
|
|
8
|
+
|
|
9
|
+
import semantic_version
|
|
10
|
+
|
|
11
|
+
import system_helpers.apt.install
|
|
12
|
+
|
|
13
|
+
from reprospect.utils import nvcc
|
|
14
|
+
|
|
15
|
+
PACKAGE : typing.Final[str] = 'cuda-nsight-systems'
|
|
16
|
+
|
|
17
|
+
def parse_args() -> argparse.Namespace:
|
|
18
|
+
"""
|
|
19
|
+
Parse CLI arguments.
|
|
20
|
+
"""
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
description = "Install Nsight Systems with apt.",
|
|
23
|
+
formatter_class = argparse.ArgumentDefaultsHelpFormatter,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
parser.add_argument('--version', type = str, required = False, help = f'Version of the package ({PACKAGE}) to install.', default = detect_cuda_version())
|
|
27
|
+
|
|
28
|
+
return parser.parse_args()
|
|
29
|
+
|
|
30
|
+
def install(*, args : argparse.Namespace) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Install `Nsight Systems` through `apt`.
|
|
33
|
+
"""
|
|
34
|
+
package = PACKAGE + '-' + args.version
|
|
35
|
+
logging.info(f'Installing nsight-systems with {package}.')
|
|
36
|
+
system_helpers.apt.install.install_packages(
|
|
37
|
+
packages = [package],
|
|
38
|
+
update = True, clean = True,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def detect_cuda_version() -> str:
|
|
42
|
+
"""
|
|
43
|
+
Detect CUDA version using the following strategies:
|
|
44
|
+
|
|
45
|
+
#. ``CUDA_VERSION`` environment variable
|
|
46
|
+
#. ``nvidia-smi --query``
|
|
47
|
+
#. ``nvcc --version``
|
|
48
|
+
"""
|
|
49
|
+
if 'CUDA_VERSION' in os.environ:
|
|
50
|
+
version = semantic_version.Version(os.environ['CUDA_VERSION'])
|
|
51
|
+
return f'{version.major}-{version.minor}'
|
|
52
|
+
|
|
53
|
+
if shutil.which('nvidia-smi') is not None:
|
|
54
|
+
for line in subprocess.check_output(('nvidia-smi', '--version')).decode().splitlines():
|
|
55
|
+
if "CUDA Version" in line and (matched := re.search(pattern = r'([0-9]+).([0-9]+)', string = line)) is not None:
|
|
56
|
+
return f'{matched.group(1)}-{matched.group(2)}'
|
|
57
|
+
|
|
58
|
+
if shutil.which('nvcc') is not None:
|
|
59
|
+
version = nvcc.get_version()
|
|
60
|
+
return f'{version.major}-{version.minor}'
|
|
61
|
+
|
|
62
|
+
raise RuntimeError("Could not deduce CUDA version.")
|
|
63
|
+
|
|
64
|
+
def main() -> None:
|
|
65
|
+
|
|
66
|
+
logging.basicConfig(level = logging.INFO)
|
|
67
|
+
|
|
68
|
+
args = parse_args()
|
|
69
|
+
logging.info(f"Received arguments: {args}")
|
|
70
|
+
|
|
71
|
+
install(args = args)
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
|
|
75
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import pathlib
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from reprospect.tools.architecture import NVIDIAArch
|
|
6
|
+
from reprospect.tools.binaries import CuppFilt, LlvmCppFilt
|
|
7
|
+
from reprospect.test.cmake import CMakeMixin
|
|
8
|
+
|
|
9
|
+
class TestCase(abc.ABC):
|
|
10
|
+
"""
|
|
11
|
+
Test case.
|
|
12
|
+
"""
|
|
13
|
+
@property
|
|
14
|
+
@abc.abstractmethod
|
|
15
|
+
def cwd(self) -> pathlib.Path:
|
|
16
|
+
"""
|
|
17
|
+
Working directory.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
@abc.abstractmethod
|
|
22
|
+
def arch(self) -> NVIDIAArch:
|
|
23
|
+
"""
|
|
24
|
+
NVIDIA architecture.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
@abc.abstractmethod
|
|
29
|
+
def executable(self) -> pathlib.Path:
|
|
30
|
+
"""
|
|
31
|
+
Executable.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
@abc.abstractmethod
|
|
36
|
+
def demangler(self) -> typing.Type[CuppFilt | LlvmCppFilt]:
|
|
37
|
+
"""
|
|
38
|
+
Demangler.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
class CMakeAwareTestCase(CMakeMixin, TestCase):
|
|
42
|
+
"""
|
|
43
|
+
Test case with CMake integration.
|
|
44
|
+
"""
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import functools
|
|
3
|
+
import json
|
|
4
|
+
import pathlib
|
|
5
|
+
import typing
|
|
6
|
+
|
|
7
|
+
from reprospect.test.environment import EnvironmentField
|
|
8
|
+
from reprospect.tools.architecture import NVIDIAArch
|
|
9
|
+
from reprospect.tools.binaries import CuppFilt, LlvmCppFilt
|
|
10
|
+
from reprospect.utils import cmake
|
|
11
|
+
from reprospect.utils.compile_command import get_arch_from_compile_command
|
|
12
|
+
|
|
13
|
+
def get_demangler_for_compiler(compiler_id : str) -> typing.Type[CuppFilt | LlvmCppFilt]:
|
|
14
|
+
"""
|
|
15
|
+
Get demangler for compiler with given id.
|
|
16
|
+
"""
|
|
17
|
+
match compiler_id:
|
|
18
|
+
case 'NVIDIA':
|
|
19
|
+
return CuppFilt
|
|
20
|
+
case 'Clang':
|
|
21
|
+
return LlvmCppFilt
|
|
22
|
+
case _:
|
|
23
|
+
raise ValueError(f'unsupported compiler ID {compiler_id}')
|
|
24
|
+
|
|
25
|
+
class CMakeMixin(abc.ABC):
|
|
26
|
+
"""
|
|
27
|
+
Mixin to integrate with CMake build system.
|
|
28
|
+
"""
|
|
29
|
+
CMAKE_BINARY_DIR = EnvironmentField(converter = pathlib.Path)
|
|
30
|
+
CMAKE_CURRENT_BINARY_DIR = EnvironmentField(converter = pathlib.Path)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
@abc.abstractmethod
|
|
34
|
+
def get_target_name(cls) -> str:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@functools.cached_property
|
|
38
|
+
def cwd(self) -> pathlib.Path:
|
|
39
|
+
"""
|
|
40
|
+
Get working directory for the analysis based on the CMake current binary directory.
|
|
41
|
+
"""
|
|
42
|
+
cwd = self.CMAKE_CURRENT_BINARY_DIR / (self.get_target_name() + '-case')
|
|
43
|
+
cwd.mkdir(parents = False, exist_ok = True)
|
|
44
|
+
return cwd
|
|
45
|
+
|
|
46
|
+
@functools.cached_property
|
|
47
|
+
def arch(self) -> NVIDIAArch:
|
|
48
|
+
"""
|
|
49
|
+
Retrieve the NVIDIA architecture from the CMake compile command database.
|
|
50
|
+
|
|
51
|
+
We assume the target file was compiled for only a single architecture.
|
|
52
|
+
|
|
53
|
+
See also https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html.
|
|
54
|
+
"""
|
|
55
|
+
with open(self.CMAKE_BINARY_DIR / 'compile_commands.json', 'r', encoding = 'utf-8') as fin:
|
|
56
|
+
commands = json.load(fin)
|
|
57
|
+
target_source = next(self.target_sources)
|
|
58
|
+
archs = get_arch_from_compile_command(cmd = next(filter(
|
|
59
|
+
lambda x: str(target_source) in x['file'],
|
|
60
|
+
commands))['command']
|
|
61
|
+
)
|
|
62
|
+
assert len(archs) == 1
|
|
63
|
+
return archs.pop()
|
|
64
|
+
|
|
65
|
+
@functools.cached_property
|
|
66
|
+
def cmake_file_api(self) -> cmake.FileAPI:
|
|
67
|
+
return cmake.FileAPI(cmake_build_directory = self.CMAKE_BINARY_DIR)
|
|
68
|
+
|
|
69
|
+
@functools.cached_property
|
|
70
|
+
def target(self) -> cmake.TargetDict:
|
|
71
|
+
"""
|
|
72
|
+
Retrieve the target information from the CMake codemodel database.
|
|
73
|
+
"""
|
|
74
|
+
return self.cmake_file_api.target(self.get_target_name())
|
|
75
|
+
|
|
76
|
+
@functools.cached_property
|
|
77
|
+
def target_sources(self) -> typing.Iterator[pathlib.Path]:
|
|
78
|
+
"""
|
|
79
|
+
Retrieve the target source from the CMake codemodel database.
|
|
80
|
+
"""
|
|
81
|
+
return map(lambda source: pathlib.Path(source['path']), self.target['sources'])
|
|
82
|
+
|
|
83
|
+
@functools.cached_property
|
|
84
|
+
def executable(self) -> pathlib.Path:
|
|
85
|
+
"""
|
|
86
|
+
Retrieve the executable for the analysis from the CMake codemodel database.
|
|
87
|
+
"""
|
|
88
|
+
return self.CMAKE_BINARY_DIR / self.target['paths']['build'] / self.target['nameOnDisk']
|
|
89
|
+
|
|
90
|
+
@functools.cached_property
|
|
91
|
+
def toolchains(self) -> cmake.ToolchainDict:
|
|
92
|
+
"""
|
|
93
|
+
Retrieve the toolchains information from the read CMake file API.
|
|
94
|
+
"""
|
|
95
|
+
return self.cmake_file_api.toolchains
|
|
96
|
+
|
|
97
|
+
@functools.cached_property
|
|
98
|
+
def demangler(self) -> typing.Type[CuppFilt | LlvmCppFilt]:
|
|
99
|
+
return get_demangler_for_compiler(self.toolchains['CUDA']['compiler']['id'])
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import os
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
T = typing.TypeVar('T')
|
|
6
|
+
"""Type variable for :py:class:`~EnvironmentField` and related generics."""
|
|
7
|
+
|
|
8
|
+
@dataclasses.dataclass(slots = True)
|
|
9
|
+
class EnvironmentField(typing.Generic[T]):
|
|
10
|
+
"""
|
|
11
|
+
Descriptor that returns a value lazily read from an environment variable.
|
|
12
|
+
|
|
13
|
+
Based on:
|
|
14
|
+
|
|
15
|
+
* https://docs.python.org/3/howto/descriptor.html
|
|
16
|
+
* https://mypy.readthedocs.io/en/stable/generics.html#defining-generic-classes
|
|
17
|
+
"""
|
|
18
|
+
env : typing.Optional[str] = None
|
|
19
|
+
"""
|
|
20
|
+
Name of the environment variable.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
converter : typing.Optional[typing.Callable[[str], T]] = None
|
|
24
|
+
"""
|
|
25
|
+
Callable to convert the value of the environment variable to the target type.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
default : typing.Optional[T] = None
|
|
29
|
+
"""
|
|
30
|
+
Default value if the environment variable does not exist.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
_cached : T | None = dataclasses.field(default = None, init = False, repr = False)
|
|
34
|
+
"""
|
|
35
|
+
Value, cached.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
_attr_name : str | None = dataclasses.field(default = None, init = False, repr = False)
|
|
39
|
+
"""
|
|
40
|
+
Name of the attribute.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __post_init__(self) -> None:
|
|
44
|
+
if self.default is not None and self.converter is None:
|
|
45
|
+
self.converter = type(self.default)
|
|
46
|
+
|
|
47
|
+
def __set_name__(self, owner : type, name: str) -> None:
|
|
48
|
+
"""
|
|
49
|
+
References:
|
|
50
|
+
|
|
51
|
+
* https://docs.python.org/3/reference/datamodel.html#object.__set_name__
|
|
52
|
+
"""
|
|
53
|
+
self._attr_name = name
|
|
54
|
+
|
|
55
|
+
@typing.overload
|
|
56
|
+
def __get__(self, instance: None, owner: type) -> "EnvironmentField[T]": ...
|
|
57
|
+
|
|
58
|
+
@typing.overload
|
|
59
|
+
def __get__(self, instance: object, owner: type) -> T: ...
|
|
60
|
+
|
|
61
|
+
def __get__(self, instance, owner = None):
|
|
62
|
+
"""
|
|
63
|
+
References:
|
|
64
|
+
|
|
65
|
+
* https://docs.python.org/3/reference/datamodel.html#object.__get__
|
|
66
|
+
"""
|
|
67
|
+
if instance is None:
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
return self.read(instance = instance, owner = owner)
|
|
71
|
+
|
|
72
|
+
def read(self, instance, owner) -> T:
|
|
73
|
+
"""
|
|
74
|
+
Read from the environment.
|
|
75
|
+
"""
|
|
76
|
+
if self._cached is not None:
|
|
77
|
+
return self._cached
|
|
78
|
+
|
|
79
|
+
key = self.env or self._attr_name
|
|
80
|
+
|
|
81
|
+
if key is None:
|
|
82
|
+
raise AttributeError("Descriptor not initialized properly.")
|
|
83
|
+
|
|
84
|
+
if (value := os.getenv(key)) is not None and self.converter is not None:
|
|
85
|
+
self._cached = self.converter(value)
|
|
86
|
+
elif self.default is not None:
|
|
87
|
+
self._cached = self.default
|
|
88
|
+
else:
|
|
89
|
+
raise RuntimeError(f"Missing required environment variable {key!r} or converter or default value for {instance.__class__ if instance else owner}.")
|
|
90
|
+
|
|
91
|
+
return self._cached
|
|
92
|
+
|
|
93
|
+
def reset(self) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Reset, thus forcing a re-read from environment on next access.
|
|
96
|
+
"""
|
|
97
|
+
self._cached = None
|
|
File without changes
|