feat(cccl): integrate missing CCCL directories — python/, ci/, .agent/, docs/, test/
Sparse-checkout from NVIDIA/cccl main branch to complete cccl_upstream: Added: - python/cuda_cccl/ (226 files) — Python bindings for device-level algorithms Critical for muh toolchain: cuda.compute.reduce_into, scan, radix_sort, etc. Includes 204 .py files with full test coverage for all 27 algorithms - ci/ (163 files) — Build/test infrastructure build_cub.sh, test_cub.sh, build_and_test_targets.sh, matrix.yaml Directly maps to our [INFRA-CI] and [INFRA-BUILD] items - .agent/skills/ (7 files) — NVIDIA's own agent skills for CCCL cccl-style/SKILL.md, cccl-test/SKILL.md, sass-diff/SKILL.md - docs/ (491 files) — Official CCCL documentation CI references, CMake guides, Python compute docs, libcudacxx PTX docs - test/ (12 files) — Top-level integration tests (cuda_smoke, stdpar) - Root configs: .clang-format, .clang-tidy, CONTRIBUTING.md, pyproject.toml - CLAUDE.md symlink → AGENTS.md (NVIDIA's standard) cccl_upstream now mirrors full NVIDIA/cccl structure: Before: 42M (cub + thrust + libcudacxx + cudax + c + examples + benchmarks) After: 53M (+python +ci +docs +.agent +test +configs) This completes the CCCL base needed for: - [muh-bench] items: ci/util/build_and_test_targets.sh for targeted builds - [CCCL-verify] items: python/cuda_cccl/tests/ as reference implementations - [CCCL-test] items: ci/test_cub.sh, ci/test_thrust.sh - Agent workflow: .agent/skills/ for consistent style and test patterns
This commit is contained in:
136
cccl_upstream/docs/cccl/development/build_and_bisect_tools.rst
Normal file
136
cccl_upstream/docs/cccl/development/build_and_bisect_tools.rst
Normal file
@@ -0,0 +1,136 @@
|
||||
.. _build-and-bisect-tools:
|
||||
|
||||
Build and Bisect Utilities
|
||||
==========================
|
||||
|
||||
``build_and_test_targets.sh``
|
||||
-----------------------------
|
||||
|
||||
:file:`ci/util/build_and_test_targets.sh` configures, builds, and tests selected
|
||||
CMake targets.
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
- ``--preset <name>`` - choose a CMake preset.
|
||||
- ``--cmake-options <str>`` - extra arguments for the preset configuration.
|
||||
- ``--configure-override <cmd>`` - run a custom configuration command instead of
|
||||
a preset. When used, ``--preset`` and ``--cmake-options`` are ignored.
|
||||
- ``--build-targets <targets>`` - space separated Ninja targets. If omitted,
|
||||
nothing builds.
|
||||
- ``--ctest-targets <regex>`` - space separated CTest ``-R`` patterns. If
|
||||
omitted, nothing runs.
|
||||
- ``--lit-precompile-tests <paths>`` - space separated libcudacxx lit test paths
|
||||
to precompile (no run). Paths are relative to ``libcudacxx/test/libcudacxx/``.
|
||||
- ``--lit-tests <paths>`` - space separated libcudacxx lit test paths to execute.
|
||||
Paths are relative to ``libcudacxx/test/libcudacxx/``.
|
||||
- ``--custom-test-cmd <cmd>`` - arbitrary command executed after build/tests.
|
||||
|
||||
Combine with ``.devcontainer/launch.sh -d`` to reproduce CI commands inside a
|
||||
container and choose a CUDA toolkit and host compiler:
|
||||
``.devcontainer/launch.sh -d [--cuda <XX.Y>] [--host <compiler>] [--gpus all] -- <script>``
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
Build a single CUB test locally::
|
||||
|
||||
ci/util/build_and_test_targets.sh \
|
||||
--preset cub-cpp20 \
|
||||
--build-targets "cub.cpp20.test.iterator"
|
||||
|
||||
Build the same test for SM90 using a CMake option::
|
||||
|
||||
ci/util/build_and_test_targets.sh \
|
||||
--preset cub-cpp20 \
|
||||
--cmake-options "-DCMAKE_CUDA_ARCHITECTURES=90" \
|
||||
--build-targets "cub.cpp20.test.iterator"
|
||||
|
||||
Build the test for SM90 with a configure override::
|
||||
|
||||
ci/util/build_and_test_targets.sh \
|
||||
--configure-override "ci/build_cub.sh -configure -arch 90" \
|
||||
--build-targets "cub.cpp20.test.iterator"
|
||||
|
||||
Build **and run** a single CUB test locally::
|
||||
|
||||
ci/util/build_and_test_targets.sh \
|
||||
--preset cub-cpp20 \
|
||||
--build-targets "cub.cpp20.test.iterator" \
|
||||
--ctest-targets "cub.cpp20.test.iterator"
|
||||
|
||||
Build and run a single CUB test in a devcontainer with specific CTK and host::
|
||||
|
||||
.devcontainer/launch.sh -d --cuda 12.3 --host gcc12 --gpus all -- \
|
||||
ci/util/build_and_test_targets.sh \
|
||||
--preset cub-cpp20 \
|
||||
--build-targets "cub.cpp20.test.iterator" \
|
||||
--ctest-targets "cub.cpp20.test.iterator"
|
||||
|
||||
Precompile the libcudacxx lit suite::
|
||||
|
||||
ci/util/build_and_test_targets.sh \
|
||||
--preset libcudacxx \
|
||||
--build-targets libcudacxx.test.lit.precompile
|
||||
|
||||
Precompile a single libcudacxx lit test (no execution)::
|
||||
|
||||
ci/util/build_and_test_targets.sh \
|
||||
--preset libcudacxx \
|
||||
--lit-precompile-tests \
|
||||
"std/algorithms/alg.nonmodifying/alg.any_of/any_of.pass.cpp"
|
||||
|
||||
Execute one or more libcudacxx lit tests::
|
||||
|
||||
ci/util/build_and_test_targets.sh \
|
||||
--preset libcudacxx \
|
||||
--lit-tests \
|
||||
"std/algorithms/alg.nonmodifying/alg.any_of/any_of.pass.cpp"
|
||||
|
||||
``git_bisect.sh``
|
||||
-----------------
|
||||
|
||||
:file:`ci/util/git_bisect.sh` wraps ``git bisect`` around the build/test helper.
|
||||
It accepts all ``build_and_test_targets.sh`` options plus:
|
||||
|
||||
- ``--good-ref <rev>`` - Optional; known good commit, tag, or branch. ``-Nd`` means
|
||||
"N days ago." Defaults to the latest release version tag.
|
||||
- ``--bad-ref <rev>`` - Optional; known bad commit. ``-Nd`` means "N days ago."
|
||||
Defaults to ``origin/main``.
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
Local CUB bisection from latest release to origin/main::
|
||||
|
||||
ci/util/git_bisect.sh \
|
||||
--preset cub-cpp20 \
|
||||
--build-targets "cub.cpp20.test.iterator" \
|
||||
--ctest-targets "cub.cpp20.test.iterator"
|
||||
|
||||
Devcontainer CUB bisection from last week::
|
||||
|
||||
.devcontainer/launch.sh -d --cuda 12.3 --host gcc12 --gpus all -- \
|
||||
ci/util/git_bisect.sh \
|
||||
--preset cub-cpp20 \
|
||||
--build-targets "cub.cpp20.test.iterator" \
|
||||
--ctest-targets "cub.cpp20.test.iterator" \
|
||||
--good-ref -7d
|
||||
|
||||
Compute-sanitizer example for regression introduced between 3-4 weeks ago::
|
||||
|
||||
.devcontainer/launch.sh -d --cuda 12.9 --host gcc13 --gpus all \
|
||||
--env CCCL_TEST_MODE=compute-sanitizer-initcheck \
|
||||
--env C2H_SEED_COUNT_OVERRIDE=1 \
|
||||
-- ci/util/git_bisect.sh \
|
||||
--preset "cub-cpp20" \
|
||||
--build-targets "cub.cpp20.test.iterator" \
|
||||
--ctest-targets "cub.cpp20.test.iterator" \
|
||||
--good-ref -28d \
|
||||
--bad-ref -21d
|
||||
|
||||
Workflow/Bisect
|
||||
---------------
|
||||
|
||||
A ``Workflow/Bisect`` GitHub Actions job runs ``git_bisect.sh`` on a remote
|
||||
runner. Launch it from **Actions → Git Bisect → Run workflow**. Provide
|
||||
any desired runner label, refs, preset, targets, or launch arguments. The job
|
||||
log streams bisect progress, and the run's **Summary** page renders the final
|
||||
Markdown report with culprit commit, PR, reproduction steps, and more.
|
||||
95
cccl_upstream/docs/cccl/development/debugger_setup.rst
Normal file
95
cccl_upstream/docs/cccl/development/debugger_setup.rst
Normal file
@@ -0,0 +1,95 @@
|
||||
.. _cccl-development-module-debugger-setup:
|
||||
|
||||
=================
|
||||
General Debugging
|
||||
=================
|
||||
|
||||
Debugger Pretty Printers
|
||||
========================
|
||||
|
||||
libcudacxx ships custom pretty printers for its types under
|
||||
``libcudacxx/share/libcudacxx``. They render CCCL types in a readable form and, for
|
||||
device-accessible data, copy the contents back to the host so the elements can be
|
||||
inspected. Two independent implementations are provided:
|
||||
|
||||
- ``libcudacxx/share/libcudacxx/gdb`` - printers for GDB.
|
||||
- ``libcudacxx/share/libcudacxx/lldb`` - printers for LLDB.
|
||||
|
||||
Each directory has an ``__init__.py`` entry point that registers every printer. The
|
||||
repository root contains a ``.gdbinit`` and a ``.lldbinit`` that load the matching entry
|
||||
point for you, so the simplest way to enable the printers is to let the debugger pick up
|
||||
these files.
|
||||
|
||||
.. important::
|
||||
|
||||
``lldb`` and ``gdb`` only inspect the following locations when looking for init
|
||||
dotfiles (in the given order):
|
||||
|
||||
#. Home directory config files (usually ``~/.lldbinit`` or ``~/.config/gdb/gdbinit`` on
|
||||
Linux, but check the respective manuals for specifics).
|
||||
#. The current working directory.
|
||||
|
||||
They do **not** walk up the directory stack like most tools. So if you have a
|
||||
``.lldbinit`` in the parent directory, ``lldb`` will **not** load it. For this reason,
|
||||
you **must** run the debugger from the root CCCL directory in order for automatic
|
||||
loading of the pretty printers to work.
|
||||
|
||||
|
||||
In addition to not loading parent directory dot-files, ``gdb`` or ``lldb`` will load not
|
||||
dotfiles unless you explicitly allow them. The following sections explain how to enable
|
||||
this for each debugger.
|
||||
|
||||
.. note::
|
||||
|
||||
The following is **not** needed when working inside a devcontainer. devcontainers
|
||||
already have the following set up.
|
||||
|
||||
If they don't, and automatic loading of the pretty printers does not work, then this is
|
||||
a bug and should be fixed.
|
||||
|
||||
It is only needed for bare metal builds.
|
||||
|
||||
GDB
|
||||
---
|
||||
|
||||
By default GDB does not source a ``.gdbinit`` from the current directory, and it guards
|
||||
auto-loaded scripts with the ``auto-load safe-path`` setting. Add the repository root to
|
||||
your ``~/.gdbinit`` (or ``~/.config/gdb/gdbinit`` if you have ``XDG_CONFIG_HOME`` set) so
|
||||
the project's ``.gdbinit`` is trusted and loaded::
|
||||
|
||||
add-auto-load-safe-path /absolute/path/to/cccl
|
||||
set auto-load local-gdbinit on
|
||||
|
||||
Launch GDB from the repository root and the printers should register automatically.
|
||||
|
||||
Verify that the printers are active with ``info pretty-printer``.
|
||||
|
||||
To load the printers without depending on the working directory - for example
|
||||
from a global ``~/.gdbinit`` - ``source`` the entry point by absolute path
|
||||
instead::
|
||||
|
||||
source /absolute/path/to/cccl/libcudacxx/share/libcudacxx/gdb/__init__.py
|
||||
|
||||
``source`` runs the script directly and is not subject to the ``auto-load safe-path``
|
||||
restriction.
|
||||
|
||||
LLDB
|
||||
----
|
||||
|
||||
LLDB only reads ``.lldbinit`` from your home directory unless you opt in to loading one
|
||||
from the current working directory. Enable that once in your ``~/.lldbinit`` (``lldb``
|
||||
seemingly does not respect ``XDG_CONFIG_HOME``)::
|
||||
|
||||
settings set target.load-cwd-lldbinit true
|
||||
|
||||
This is a trust decision, since the local file runs arbitrary Python. Launch LLDB from the
|
||||
repository root and the project's ``.lldbinit`` imports the formatters automatically.
|
||||
|
||||
To load the formatters without depending on the working directory, add the absolute path
|
||||
to your ``~/.lldbinit`` instead::
|
||||
|
||||
command script import "/absolute/path/to/cccl/libcudacxx/share/libcudacxx/lldb/__init__.py"
|
||||
|
||||
The entry point's ``__lldb_init_module`` hook defines and enables an LLDB type category
|
||||
for the formatters. Print any CCCL value with the usual commands (``v``, ``frame
|
||||
variable``, or ``dwim-print``).
|
||||
23
cccl_upstream/docs/cccl/development/index.rst
Normal file
23
cccl_upstream/docs/cccl/development/index.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
.. _cccl-development-module:
|
||||
|
||||
CCCL Development Guide
|
||||
======================
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
|
||||
macro
|
||||
testing
|
||||
build_and_bisect_tools
|
||||
visibility
|
||||
debugger_setup
|
||||
|
||||
This living document serves to describe the internal details and the development process of CCCL libraries.
|
||||
|
||||
Documentation:
|
||||
|
||||
- :doc:`CCCL Internal Macros <macro>`
|
||||
- :doc:`CCCL Testing Utilities <testing>`
|
||||
- :doc:`CCCL Bisect And Targeted Build/Test Helpers <build_and_bisect_tools>`
|
||||
- :doc:`General Debugging <debugger_setup>`
|
||||
553
cccl_upstream/docs/cccl/development/macro.rst
Normal file
553
cccl_upstream/docs/cccl/development/macro.rst
Normal file
@@ -0,0 +1,553 @@
|
||||
.. _cccl-development-module-macros:
|
||||
|
||||
CCCL Internal Macros
|
||||
====================
|
||||
|
||||
The document describes the main *internal* macros used by CCCL. They are not intended to be used by end users, but for development of CCCL features only. We reserve the right to change them at any time without warning.
|
||||
|
||||
----
|
||||
|
||||
Compiler Macros
|
||||
---------------
|
||||
|
||||
**Host compiler macros**:
|
||||
|
||||
+------------------------------+---------------------------------------------+
|
||||
| ``_CCCL_COMPILER(CLANG)`` | Clang |
|
||||
+------------------------------+---------------------------------------------+
|
||||
| ``_CCCL_COMPILER(GCC)`` | GCC |
|
||||
+------------------------------+---------------------------------------------+
|
||||
| ``_CCCL_COMPILER(NVHPC)`` | Nvidia HPC compiler |
|
||||
+------------------------------+---------------------------------------------+
|
||||
| ``_CCCL_COMPILER(MSVC)`` | Microsoft Visual Studio |
|
||||
+------------------------------+---------------------------------------------+
|
||||
| ``_CCCL_COMPILER(MSVC2019)`` | Microsoft Visual Studio 2019 |
|
||||
+------------------------------+---------------------------------------------+
|
||||
| ``_CCCL_COMPILER(MSVC2022)`` | Microsoft Visual Studio 2022 |
|
||||
+------------------------------+---------------------------------------------+
|
||||
|
||||
The ``_CCCL_COMPILER`` function-like macro can also be used to check the version of a compiler.
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
_CCCL_COMPILER(MSVC, <, 19, 24)
|
||||
_CCCL_COMPILER(GCC, >=, 9)
|
||||
|
||||
*Note*: When used without specifying a minor version number, the macro will only test against
|
||||
the compiler's major version number. For example, when the compiler is ``gcc-9.1``, the macro
|
||||
``_CCCL_COMPILER(GCC, >, 9)`` will be ``false`` even though ``9.1`` is greater than ``9``.
|
||||
|
||||
**CUDA compiler macros**:
|
||||
|
||||
+--------------------------------+-------------------------+
|
||||
| ``_CCCL_CUDA_COMPILER(NVCC)`` | Nvidia compiler |
|
||||
+--------------------------------+-------------------------+
|
||||
| ``_CCCL_CUDA_COMPILER(NVHPC)`` | Nvidia HPC compiler |
|
||||
+--------------------------------+-------------------------+
|
||||
| ``_CCCL_CUDA_COMPILER(NVRTC)`` | Nvidia Runtime Compiler |
|
||||
+--------------------------------+-------------------------+
|
||||
| ``_CCCL_CUDA_COMPILER(CLANG)`` | Clang |
|
||||
+--------------------------------+-------------------------+
|
||||
|
||||
The ``_CCCL_CUDA_COMPILER`` function-like macro can also be used to check the version of a CUDA compiler.
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
_CCCL_CUDA_COMPILER(NVCC, <, 12, 3)
|
||||
_CCCL_CUDA_COMPILER(CLANG, >=, 14)
|
||||
|
||||
*Note*: ``_CCCL_CUDA_COMPILER(...)`` check may result in a ``true`` value even during the compilation of a C++ source
|
||||
file. Use ``_CCCL_CUDA_COMPILATION()`` to check for the compilation of a CUDA source file.
|
||||
|
||||
**CUDA identification/version macros**:
|
||||
|
||||
+----------------------------------+------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_CUDA_COMPILATION()`` | CUDA code is being compiled |
|
||||
+----------------------------------+------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_HOST_COMPILATION()`` | Compiling host code, ``true`` when executing the CUDA host pass or compiling a C++ source file |
|
||||
+----------------------------------+------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_DEVICE_COMPILATION()`` | Compiling device code, ``true`` when executing the CUDA device pass |
|
||||
+----------------------------------+------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_CUDACC_BELOW(12, 7)`` | CUDA version below 12.7 when compiling a CUDA source file |
|
||||
+----------------------------------+------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_CUDACC_AT_LEAST(12, 7)`` | CUDA version at least 12.7 when compiling a CUDA source file |
|
||||
+----------------------------------+------------------------------------------------------------------------------------------------+
|
||||
|
||||
*Note*: When compiling CUDA code with ``nvc++`` both ``_CCCL_HOST_COMPILATION()`` and ``_CCCL_DEVICE_COMPILATION()`` result in a ``true`` value.
|
||||
|
||||
**PTX macros**:
|
||||
|
||||
+----------------------+-------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_PTX_ARCH()`` | Alias of ``__CUDA_ARCH__`` with value equal to 0 if a CUDA compiler is not available |
|
||||
+----------------------+-------------------------------------------------------------------------------------------------------------------+
|
||||
| ``__cccl_ptx_isa`` | PTX ISA version available with the current CUDA compiler, e.g. PTX ISA 8.4 (``840``) is available from CUDA 12.4 |
|
||||
+----------------------+-------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
*Note*: When compiling CUDA code with ``nvc++`` the ``_CCCL_PTX_ARCH()`` macro expands to ``0``.
|
||||
|
||||
----
|
||||
|
||||
Architecture Macros
|
||||
-------------------
|
||||
|
||||
The following macros are used to check the target architecture. They comply with the compiler supported by the CUDA toolkit. Compilers outside the CUDA toolkit may define such macros in a different way.
|
||||
|
||||
+------------------------------+---------------------------------------------------+
|
||||
| ``_CCCL_HOST_ARCH(ARM64)`` | ARM 64-bit, including MSVC emulation |
|
||||
+------------------------------+---------------------------------------------------+
|
||||
| ``_CCCL_HOST_ARCH(X86_64)`` | X86 64-bit. False on ARM 64-bit MSVC emulation |
|
||||
+------------------------------+---------------------------------------------------+
|
||||
|
||||
----
|
||||
|
||||
OS Macros
|
||||
---------
|
||||
|
||||
+-----------------------+---------------------------------+
|
||||
| ``_CCCL_OS(WINDOWS)`` | Windows, including NVRTC LLP64 |
|
||||
+-----------------------+---------------------------------+
|
||||
| ``_CCCL_OS(LINUX)`` | Linux, including NVRTC LP64 |
|
||||
+-----------------------+---------------------------------+
|
||||
| ``_CCCL_OS(ANDROID)`` | Android |
|
||||
+-----------------------+---------------------------------+
|
||||
| ``_CCCL_OS(QNX)`` | QNX |
|
||||
+-----------------------+---------------------------------+
|
||||
|
||||
----
|
||||
|
||||
Execution Space
|
||||
---------------
|
||||
|
||||
**Functions**
|
||||
|
||||
+-----------------------+-----------------------+
|
||||
| ``_CCCL_HOST`` | Host function |
|
||||
+-----------------------+-----------------------+
|
||||
| ``_CCCL_DEVICE`` | Device function |
|
||||
+-----------------------+-----------------------+
|
||||
| ``_CCCL_HOST_DEVICE`` | Host/Device function |
|
||||
+-----------------------+-----------------------+
|
||||
|
||||
In addition, ``_CCCL_EXEC_CHECK_DISABLE`` disables the execution space check for the NVHPC compiler
|
||||
|
||||
**Target Macros**
|
||||
|
||||
+---------------------------------------------------------------------------------+--------------------------------------------------------------------------+
|
||||
| ``NV_IF_TARGET(TARGET, (CODE))`` | Enable ``CODE`` only if ``TARGET`` is satisfied. |
|
||||
+---------------------------------------------------------------------------------+--------------------------------------------------------------------------+
|
||||
| ``NV_IF_ELSE_TARGET(TARGET, (IF_CODE), (ELSE_CODE))`` | Enable ``CODE_IF`` if ``TARGET`` is satisfied, ``CODE_ELSE`` otherwise. |
|
||||
+---------------------------------------------------------------------------------+--------------------------------------------------------------------------+
|
||||
| ``NV_DISPATCH_TARGET(TARGET1, (TARGET1_CODE), ..., TARGET_N, (TARGET_N_CODE))`` | Enable a single code block if any of ``TARGET_i`` is satisfied. |
|
||||
+---------------------------------------------------------------------------------+--------------------------------------------------------------------------+
|
||||
|
||||
Possible ``TARGET`` values:
|
||||
|
||||
+---------------------------+-------------------------------------------------------------------+
|
||||
| ``NV_ANY_TARGET`` | Any target |
|
||||
+---------------------------+-------------------------------------------------------------------+
|
||||
| ``NV_IS_HOST`` | Host-code target |
|
||||
+---------------------------+-------------------------------------------------------------------+
|
||||
| ``NV_IS_DEVICE`` | Device-code target |
|
||||
+---------------------------+-------------------------------------------------------------------+
|
||||
| ``NV_PROVIDES_SM_<VER>`` | SM architecture is at least ``VER``, e.g. ``NV_PROVIDES_SM_80`` |
|
||||
+---------------------------+-------------------------------------------------------------------+
|
||||
| ``NV_IS_EXACTLY_SM_<NN>`` | SM architecture is exactly ``VER``, e.g. ``NV_IS_EXACTLY_SM_80`` |
|
||||
+---------------------------+-------------------------------------------------------------------+
|
||||
|
||||
Usage example:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
NV_IF_TARGET(NV_IS_DEVICE, (auto x = threadIdx.x; return x;));
|
||||
NV_IF_ELSE_TARGET(NV_IS_HOST, (return 0;), (auto x = threadIdx.x; return x;));
|
||||
NV_DISPATCH_TARGET(NV_PROVIDES_SM_90, (return "Hopper+";),
|
||||
NV_IS_EXACTLY_SM_75, (return "Turing";),
|
||||
NV_IS_HOST, (return "Host";))
|
||||
|
||||
*Pitfalls*:
|
||||
|
||||
* All target macros generate the code in a local scope, i.e. ``{ code }``.
|
||||
* ``NV_DISPATCH_TARGET`` is *NOT* a switch statement. It enables the code associated with the first condition satisfied.
|
||||
* The target macros take ``code`` as an argument, so it is *not* possible to use any conditional compilation, .e.g ``#if _CCCL_STD_VER >= 20`` within a target macro
|
||||
|
||||
----
|
||||
|
||||
CUDA attributes
|
||||
---------------
|
||||
|
||||
+------------------------------+----------------------------------------------------------+
|
||||
| ``_CCCL_GRID_CONSTANT`` | Grid constant kernel parameter |
|
||||
+------------------------------+----------------------------------------------------------+
|
||||
| ``_CCCL_GLOBAL_CONSTANT`` | Host/device global scope constant (``inline constexpr``) |
|
||||
+------------------------------+----------------------------------------------------------+
|
||||
|
||||
----
|
||||
|
||||
CUDA Toolkit macros
|
||||
-------------------
|
||||
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_HAS_CTK()`` | CUDA toolkit is available if ``_CCCL_CUDA_COMPILER()`` evaluates to a ``true`` value or if ``cuda_runtime_api.h`` was found |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_CTK_BELOW`` | CUDA toolkit version below 12.7 |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_CTK_AT_LEAST(12, 7)`` | CUDA toolkit version at least 12.7 |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
Non-standard Types Support
|
||||
--------------------------
|
||||
|
||||
+------------------------------+-------------------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_HAS_INT128()`` | ``__int128`` and ``__uint128_t`` for 128-bit integer are supported and enabled |
|
||||
+------------------------------+-------------------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_HAS_NVFP8()`` | ``__nv_fp8_e5m2/__nv_fp8_e4m3/__nv_fp8_e8m0`` data types are supported and enabled. Prefer over ``__CUDA_FP8_TYPES_EXIST__`` |
|
||||
+------------------------------+-------------------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_HAS_NVFP16()`` | ``__half/__half2`` data types are supported and enabled. Prefer over ``__CUDA_FP16_TYPES_EXIST__`` |
|
||||
+------------------------------+-------------------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_HAS_NVBF16()`` | ``__nv_bfloat16/__nv_bfloat162`` data types are supported and enabled. Prefer over ``__CUDA_BF16_TYPES_EXIST__`` |
|
||||
+------------------------------+-------------------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_HAS_FLOAT128()`` | ``__float128`` for 128-bit floating-point are supported and enabled |
|
||||
+------------------------------+-------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
+-----------------------------------+-------------------------------------------------------------------------+
|
||||
| ``CCCL_DISABLE_INT128_SUPPORT`` | Disable ``__int128/__uint128_t`` support |
|
||||
+-----------------------------------+-------------------------------------------------------------------------+
|
||||
| ``CCCL_DISABLE_NVFP8_SUPPORT`` | Disable ``__nv_fp8_e5m2/__nv_fp8_e4m3/__nv_fp8_e8m0`` support |
|
||||
+-----------------------------------+-------------------------------------------------------------------------+
|
||||
| ``CCCL_DISABLE_NVFP16_SUPPORT`` | Disable ``__half/__half2`` support |
|
||||
+-----------------------------------+-------------------------------------------------------------------------+
|
||||
| ``CCCL_DISABLE_NVBF16_SUPPORT`` | Disable ``__nv_bfloat16/__nv_bfloat162`` support |
|
||||
+-----------------------------------+-------------------------------------------------------------------------+
|
||||
| ``CCCL_DISABLE_FLOAT128_SUPPORT`` | Disable ``__float128`` support |
|
||||
+-----------------------------------+-------------------------------------------------------------------------+
|
||||
|
||||
+-----------------------------------+-------------------------------------------------------------------------+
|
||||
| ``_LIBCUDACXX_HAS_NVFP16()`` | ``__half/__half2`` host/device are supported (CUDA 12.2+) |
|
||||
+-----------------------------------+-------------------------------------------------------------------------+
|
||||
| ``_LIBCUDACXX_HAS_NVBF16()`` | ``__nv_bfloat16/__nv_bfloat162`` host/device are supported (CUDA 12.2+) |
|
||||
+-----------------------------------+-------------------------------------------------------------------------+
|
||||
|
||||
----
|
||||
|
||||
C++ Language Macros
|
||||
-------------------
|
||||
|
||||
The following macros are required only if the target C++ version does not support the corresponding attribute
|
||||
|
||||
+-----------------------------+----------------------------------------------------------+
|
||||
| ``_CCCL_STD_VER`` | C++ standard version, e.g. ``#if _CCCL_STD_VER >= 2017`` |
|
||||
+-----------------------------+----------------------------------------------------------+
|
||||
| ``_CCCL_CONSTEXPR_CXX20`` | Enable ``constexpr`` for C++20 or newer |
|
||||
+-----------------------------+----------------------------------------------------------+
|
||||
| ``_CCCL_CONSTEXPR_CXX23`` | Enable ``constexpr`` for C++23 or newer |
|
||||
+-----------------------------+----------------------------------------------------------+
|
||||
| ``_CCCL_HAS_EXCEPTIONS()`` | Features can use exceptions, e.g ``bad_optional_access`` |
|
||||
+-----------------------------+----------------------------------------------------------+
|
||||
|
||||
**Concept-like Macros**:
|
||||
|
||||
+------------------------+--------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_TEMPLATE(X)`` | ``template`` clause |
|
||||
+------------------------+--------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_REQUIRES(X)`` | ``requires`` clause |
|
||||
+------------------------+--------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_AND`` | Traits conjunction only used with ``_CCCL_REQUIRES`` |
|
||||
+------------------------+--------------------------------------------------------------------------------------------+
|
||||
|
||||
Usage example:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
_CCCL_TEMPLATE(typename T)
|
||||
_CCCL_REQUIRES(is_integral_v<T> _CCCL_AND(sizeof(T) > 1))
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
_CCCL_TEMPLATE(typename T)
|
||||
_CCCL_REQUIRES(is_arithmetic_v<T> _CCCL_AND (!is_integral_v<T>))
|
||||
|
||||
|
||||
**Portable feature testing**:
|
||||
|
||||
+--------------------------+--------------------------------------------------+
|
||||
| ``_CCCL_HAS_BUILTIN(X)`` | Portable ``__has_builtin(X)`` |
|
||||
+--------------------------+--------------------------------------------------+
|
||||
| ``_CCCL_HAS_FEATURE(X)`` | Portable ``__has_feature(X)`` |
|
||||
+--------------------------+--------------------------------------------------+
|
||||
|
||||
**Portable attributes**:
|
||||
|
||||
+----------------------------------+------------------------------------------------------------------------------+
|
||||
| ``_CCCL_ASSUME(EXPR)`` | Portable ``[[assume]]`` attribute (before C++23) |
|
||||
+----------------------------------+------------------------------------------------------------------------------+
|
||||
| ``_CCCL_NO_UNIQUE_ADDRESS`` | Portable ``[[no_unique_address]]`` attribute |
|
||||
+----------------------------------+------------------------------------------------------------------------------+
|
||||
| ``CCCL_DEPRECATED`` | Portable ``[[deprecated]]`` attribute (before C++14) |
|
||||
+----------------------------------+------------------------------------------------------------------------------+
|
||||
| ``CCCL_DEPRECATED_BECAUSE(MSG)`` | Portable ``[[deprecated]]`` attribute with custom message (before C++14) |
|
||||
+----------------------------------+------------------------------------------------------------------------------+
|
||||
| ``_CCCL_FORCEINLINE`` | Portable "always inline" attribute |
|
||||
+----------------------------------+------------------------------------------------------------------------------+
|
||||
| ``_CCCL_PURE`` | Portable "pure" function attribute |
|
||||
+----------------------------------+------------------------------------------------------------------------------+
|
||||
| ``_CCCL_CONST`` | Portable "constant" function attribute |
|
||||
+----------------------------------+------------------------------------------------------------------------------+
|
||||
| ``_CCCL_LIFETIMEBOUND`` | Portable "lifetime bound" function attribute |
|
||||
+----------------------------------+------------------------------------------------------------------------------+
|
||||
|
||||
**Portable Builtin Macros**:
|
||||
|
||||
+---------------------------------------+--------------------------------------------+
|
||||
| ``_CCCL_UNREACHABLE()`` | Portable ``__builtin_unreachable()`` |
|
||||
+---------------------------------------+--------------------------------------------+
|
||||
| ``_CCCL_BUILTIN_EXPECT(X)`` | Portable ``__builtin_expected(X)`` |
|
||||
+---------------------------------------+--------------------------------------------+
|
||||
| ``_CCCL_BUILTIN_PREFETCH(X[, Y, Z])`` | Portable ``__builtin_prefetch(X, Y, Z)`` |
|
||||
+---------------------------------------+--------------------------------------------+
|
||||
|
||||
**Portable Keyword Macros**
|
||||
|
||||
+-----------------------------+--------------------------------------------+
|
||||
| ``_CCCL_RESTRICT`` | Portable ``restrict`` keyword |
|
||||
+-----------------------------+--------------------------------------------+
|
||||
| ``_CCCL_ALIGNAS(X)`` | Portable ``alignas(X)`` keyword (variable) |
|
||||
+-----------------------------+--------------------------------------------+
|
||||
| ``_CCCL_ALIGNAS_TYPE(X)`` | Portable ``alignas(X)`` keyword (type) |
|
||||
+-----------------------------+--------------------------------------------+
|
||||
| ``_CCCL_PRAGMA(X)`` | Portable ``_Pragma(X)`` keyword |
|
||||
+-----------------------------+--------------------------------------------+
|
||||
|
||||
**Portable Pragma Macros**
|
||||
|
||||
+--------------------------------+-------------------------------------------+
|
||||
| ``_CCCL_PRAGMA_UNROLL(N)`` | Portable ``#pragma unroll N`` pragma |
|
||||
+--------------------------------+-------------------------------------------+
|
||||
| ``_CCCL_PRAGMA_UNROLL_FULL()`` | Portable ``#pragma unroll`` pragma |
|
||||
+--------------------------------+-------------------------------------------+
|
||||
| ``_CCCL_PRAGMA_NOUNROLL()`` | Portable ``#pragma nounroll`` pragma |
|
||||
+--------------------------------+-------------------------------------------+
|
||||
|
||||
**Conditional Constant Evaluation Macros**
|
||||
|
||||
In C++23, the ``if consteval`` statement (`link <https://en.cppreference.com/w/cpp/language/if.html>`_) was introduced. CCCL mimics the behaviour with a set of macros that expand to an implementation supported by the compiler. If the compiler doesn't support any kind of conditional constant evaluation, the macros expand to predefined fallback values.
|
||||
|
||||
+------------------------------------+-----------------------------------------------------------------------------------+
|
||||
| ``_CCCL_IF_CONSTEVAL`` | Equivalent to ``if consteval`` statement (fallbacks to ``if constexpr (false)``) |
|
||||
+------------------------------------+-----------------------------------------------------------------------------------+
|
||||
| ``_CCCL_IF_CONSTEVAL_DEFAULT`` | Equivalent to ``if consteval`` statement (fallbacks to ``if constexpr (true)``) |
|
||||
+------------------------------------+-----------------------------------------------------------------------------------+
|
||||
| ``_CCCL_IF_NOT_CONSTEVAL`` | Equivalent to ``if !consteval`` statement (fallbacks to ``if constexpr (true)``) |
|
||||
+------------------------------------+-----------------------------------------------------------------------------------+
|
||||
| ``_CCCL_IF_NOT_CONSTEVAL_DEFAULT`` | Equivalent to ``if !consteval`` statement (fallbacks to ``if constexpr (false)``) |
|
||||
+------------------------------------+-----------------------------------------------------------------------------------+
|
||||
|
||||
**Exception Macros**
|
||||
|
||||
CUDA doesn't support exceptions in device code, however, sometimes we need to write host/device functions that use exceptions on host and ``__trap()`` on device. CCCL provides a set of macros that should be used in place of the standard C++ keywords to make the code compile in both, host and device code.
|
||||
|
||||
+-----------------------------+------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_TRY`` | Replacement for the ``try`` keyword. |
|
||||
+-----------------------------+------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_CATCH (X)`` | Replacement for the ``catch (/*X*/)`` statement. |
|
||||
+-----------------------------+------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_CATCH_ALL`` | Replacement for the ``catch (...)`` statement. |
|
||||
+-----------------------------+------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_THROW(X, ...)`` | Replacement for the ``throw X(...)`` expression. ``X`` must be fully qualified type, without the leading ``::``. |
|
||||
+-----------------------------+------------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_RETHROW`` | Replacement for the plain ``throw`` expression. |
|
||||
+-----------------------------+------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
*Note*: The ``_CCCL_CATCH`` clause must always introduce a named variable, like: ``_CCCL_CATCH(const exception_type& var)``.
|
||||
|
||||
.. note::
|
||||
|
||||
``_CCCL_THROW`` requires to include the ``<stdexcept>`` header, regardless exceptions are enabled or not.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
__host__ __device__ void* alloc(cuda::std::size_t nbytes)
|
||||
{
|
||||
if (void* ptr = cuda::std::malloc(nbytes))
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
_CCCL_THROW(std::bad_alloc); // on device calls cuda::std::terminate()
|
||||
}
|
||||
|
||||
__host__ __device__ void do_something(int* buff)
|
||||
{
|
||||
_CCCL_THROW(std::runtime_error, "Something went wrong"); // on device calls cuda::std::terminate()
|
||||
}
|
||||
|
||||
__host__ __device__ void fn(cuda::std::size_t n)
|
||||
{
|
||||
int* buff{};
|
||||
_CCCL_TRY
|
||||
{
|
||||
buff = reinterpret_cast<int*>(alloc(n * sizeof(int)));
|
||||
do_something(buff);
|
||||
}
|
||||
_CCCL_CATCH ([[maybe_unused]] const std::bad_alloc& e) // must be always named
|
||||
{
|
||||
std::fprintf(stderr, "Failed to allocate memory\n"); // We can directly call host-only functions
|
||||
cuda::std::terminate();
|
||||
}
|
||||
_CCCL_CATCH_ALL // or _CCCL_CATCH_FALLTHOUGH
|
||||
{
|
||||
cuda::std::free(buff);
|
||||
_CCCL_RETHROW;
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void kernel()
|
||||
{
|
||||
fn(10);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
fn(10);
|
||||
return 0;
|
||||
}
|
||||
|
||||
----
|
||||
|
||||
Visibility Macros
|
||||
-----------------
|
||||
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_VISIBILITY_HIDDEN`` | Hidden visibility attribute (e.g. ``__attribute__((visibility("hidden")))``) |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_HIDE_FROM_ABI`` | Hidden visibility (i.e. ``inline``, not exported, not instantiated) |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_API`` | Host/device function with hidden visibility. Most CCCL functions are hidden with this attribute |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_HOST_API`` | Host function with hidden visibility. Most CCCL functions are hidden with this attribute |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_DEVICE_API`` | Device function with hidden visibility. Most CCCL functions are hidden with this attribute |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_KERNEL_ATTRIBUTES`` | Global function with hidden visibility. Most CCCL functions are hidden with this attribute |
|
||||
+-------------------------------+-----------------------------------------------------------------------------------------------------+
|
||||
|
||||
----
|
||||
|
||||
Other Common Macros
|
||||
-------------------
|
||||
|
||||
+-----------------------------+--------------------------------------------+
|
||||
| ``_CCCL_TO_STRING(X)`` | ``X`` to literal string |
|
||||
+-----------------------------+--------------------------------------------+
|
||||
| ``_CCCL_DOXYGEN_INVOKED`` | Defined during Doxygen parsing |
|
||||
+-----------------------------+--------------------------------------------+
|
||||
|
||||
----
|
||||
|
||||
Debugging Macros
|
||||
----------------
|
||||
|
||||
+-----------------------------------+-------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_ASSERT(COND, MSG)`` | Portable, conditional CCCL `assert()` macro. Requires (``CCCL_ENABLE_ASSERTIONS`` or a debug build) |
|
||||
+-----------------------------------+-------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_VERIFY(COND, MSG)`` | Portable, always-on `assert()` reserved for critical checks that are always required |
|
||||
+-----------------------------------+-------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_ENABLE_ASSERTIONS`` | Enable assertions |
|
||||
+-----------------------------------+-------------------------------------------------------------------------------------------------------------+
|
||||
| ``CCCL_ENABLE_HOST_ASSERTIONS`` | Enable host-side assertions |
|
||||
+-----------------------------------+-------------------------------------------------------------------------------------------------------------+
|
||||
| ``CCCL_ENABLE_DEVICE_ASSERTIONS`` | Enable device-side assertions |
|
||||
+-----------------------------------+-------------------------------------------------------------------------------------------------------------+
|
||||
| ``_CCCL_ENABLE_DEBUG_MODE`` | Enable debug mode (and assertions) |
|
||||
+-----------------------------------+-------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
----
|
||||
|
||||
Warning Suppression Macros
|
||||
--------------------------
|
||||
|
||||
+-----------------------------+--------------------------------------------+
|
||||
| ``_CCCL_DIAG_PUSH`` | Portable ``#pragma push`` |
|
||||
+-----------------------------+--------------------------------------------+
|
||||
| ``_CCCL_DIAG_POP`` | Portable ``#pragma pop`` |
|
||||
+-----------------------------+--------------------------------------------+
|
||||
|
||||
**Compiler-specific Suppression Macros**:
|
||||
|
||||
+-------------------------------------+-------------------------------------------------------------+
|
||||
| ``_CCCL_DIAG_SUPPRESS_CLANG(X)`` | Suppress clang warning, e.g. ``"-Wattributes"`` |
|
||||
+-------------------------------------+-------------------------------------------------------------+
|
||||
| ``_CCCL_DIAG_SUPPRESS_GCC(X)`` | Suppress gcc warning, e.g. ``"-Wattributes"`` |
|
||||
+-------------------------------------+-------------------------------------------------------------+
|
||||
| ``_CCCL_DIAG_SUPPRESS_NVHPC(X)`` | Suppress nvhpc warning, e.g. ``expr_has_no_effect`` |
|
||||
+-------------------------------------+-------------------------------------------------------------+
|
||||
| ``_CCCL_DIAG_SUPPRESS_MSVC(X)`` | Suppress msvc warning, e.g. ``4127`` |
|
||||
+-------------------------------------+-------------------------------------------------------------+
|
||||
| ``_CCCL_BEGIN_NV_DIAG_SUPPRESS(X)`` | Start to suppress nvcc warning, e.g. ``177`` |
|
||||
+-------------------------------------+-------------------------------------------------------------+
|
||||
| ``_CCCL_END_NV_DIAG_SUPPRESS()`` | End to suppress nvcc warning |
|
||||
+-------------------------------------+-------------------------------------------------------------+
|
||||
|
||||
Usage example:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
_CCCL_DIAG_PUSH
|
||||
_CCCL_DIAG_SUPPRESS_GCC("-Wattributes")
|
||||
// code ..
|
||||
_CCCL_DIAG_POP
|
||||
|
||||
----
|
||||
|
||||
Freestanding support
|
||||
--------------------------
|
||||
|
||||
We - partially - support building CCCL headers in freestanding mode, for example JIT compilation with NVRTC.
|
||||
|
||||
+-----------------------------+----------------------------------------------------+
|
||||
| ``_CCCL_HOSTED()`` | "Normal" compilation mode with host STL support |
|
||||
+-----------------------------+----------------------------------------------------+
|
||||
| ``_CCCL_FREESTANDING()`` | Freestanding compilation mode, no host STL support |
|
||||
+-----------------------------+----------------------------------------------------+
|
||||
| ``_CCCL_HOSTJIT()`` | Freestanding compilation mode, with host compiler |
|
||||
+-----------------------------+----------------------------------------------------+
|
||||
|
||||
Usage example:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
#if _CCCL_HOSTED()
|
||||
# include <iostream> // Host STL header not available in freestanding
|
||||
#endif // _CCCL_HOSTED()
|
||||
|
||||
// code ..
|
||||
|
||||
Similarly we also provide macros to detect which host standard library is available
|
||||
|
||||
+-----------------------------------+----------------------------------------------------+
|
||||
| ``_CCCL_HAS_HOST_STD_LIB()`` | Whether a known host standard library is available |
|
||||
+-----------------------------------+----------------------------------------------------+
|
||||
| ``_CCCL_HOST_STD_LIB(LIBSTDCXX)`` | libstdc++ is available as host standard library |
|
||||
+-----------------------------------+----------------------------------------------------+
|
||||
| ``_CCCL_HOST_STD_LIB(LIBCXX)`` | libc++ is available as host standard library |
|
||||
+-----------------------------------+----------------------------------------------------+
|
||||
| ``_CCCL_HOST_STD_LIB(STL)`` | MSVC STL is available as host standard library |
|
||||
+-----------------------------------+----------------------------------------------------+
|
||||
|
||||
Usage example:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
#if _CCCL_HAS_HOST_STD_LIB()
|
||||
_CCCL_BEGIN_NAMESPACE_STD
|
||||
|
||||
# if _CCCL_HOST_STD_LIB(STL)
|
||||
template <class _Tp, size_t _Size>
|
||||
class array;
|
||||
# else // ^^^ _CCCL_HOST_STD_LIB(STL) ^^^ / vvv !_CCCL_HOST_STD_LIB(STL) vvv
|
||||
template <class _Tp, size_t _Size>
|
||||
struct array;
|
||||
# endif // !_CCCL_HOST_STD_LIB(STL)
|
||||
|
||||
_CCCL_END_NAMESPACE_STD
|
||||
#endif // _CCCL_HAS_HOST_STD_LIB()
|
||||
138
cccl_upstream/docs/cccl/development/testing.rst
Normal file
138
cccl_upstream/docs/cccl/development/testing.rst
Normal file
@@ -0,0 +1,138 @@
|
||||
.. _cccl-development-module-testing:
|
||||
|
||||
======================
|
||||
CCCL Testing Utilities
|
||||
======================
|
||||
|
||||
This document describes utilities provided for implementing the *internal* CCCL tests.
|
||||
They are not intended to be used by end users, but for development of CCCL features only.
|
||||
We reserve the right to change them at any time without warning.
|
||||
|
||||
----
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Test Variants: Generating Multiple Executables from a Single Source
|
||||
-------------------------------------------------------------------
|
||||
|
||||
Some of CCCL's tests are very slow to build and are capable of exhausting RAM
|
||||
during compilation/linking. To avoid such issues, large tests are split into
|
||||
multiple executables to take advantage of parallel computation and reduce memory
|
||||
usage.
|
||||
|
||||
CCCL facilitates this by providing a CMake-based solution for automatically generating multiple
|
||||
test executables from a single source file. This is done by using one or more ``%PARAM%`` comments
|
||||
in the test's source code, each of which defines a parameter that will be split across multiple
|
||||
executables.
|
||||
|
||||
The CMake functions that implement this feature are in ``cmake/CCCLTestParams.cmake``.
|
||||
An example of their usage is provided below.
|
||||
|
||||
Using ``%PARAM%``
|
||||
-----------------
|
||||
|
||||
The ``%PARAM%`` hint provides an automated method of generating multiple test
|
||||
executables from a single source file. To use it, add one or more special
|
||||
comments to the test source file::
|
||||
|
||||
// %PARAM% [definition] [label] [values]
|
||||
|
||||
CMake will parse the source file and extract these comments, using them to
|
||||
generate multiple test executables for the full cartesian product of values.
|
||||
|
||||
- ``definition`` will be used as a preprocessor definition name. By convention,
|
||||
these begin with ``TEST_``.
|
||||
- ``label`` is a short, human-readable label that will be used in the test
|
||||
executable's name to identify the test variant.
|
||||
- ``values`` is a colon-separated list of values used during test generation. Only
|
||||
numeric values have been tested.
|
||||
|
||||
Example
|
||||
*******
|
||||
|
||||
A source file containing the following hints::
|
||||
|
||||
// %PARAM% TEST_FOO foo 0:1:2
|
||||
// %PARAM% TEST_LAUNCH lid 0:1
|
||||
|
||||
will generate six variants with unique preprocessor definitions:
|
||||
|
||||
+-----------------------------+-------------------------------------------+
|
||||
| Executable Name | Preprocessor Definitions |
|
||||
+=============================+===========================================+
|
||||
| ``<name_base>.foo_0.lid_0`` | ``-DTEST_FOO=0 -DTEST_LAUNCH=0 VAR_ID=0`` |
|
||||
+-----------------------------+-------------------------------------------+
|
||||
| ``<name_base>.foo_0.lid_1`` | ``-DTEST_FOO=0 -DTEST_LAUNCH=1 VAR_ID=1`` |
|
||||
+-----------------------------+-------------------------------------------+
|
||||
| ``<name_base>.foo_1.lid_0`` | ``-DTEST_FOO=1 -DTEST_LAUNCH=0 VAR_ID=2`` |
|
||||
+-----------------------------+-------------------------------------------+
|
||||
| ``<name_base>.foo_1.lid_1`` | ``-DTEST_FOO=1 -DTEST_LAUNCH=1 VAR_ID=3`` |
|
||||
+-----------------------------+-------------------------------------------+
|
||||
| ``<name_base>.foo_2.lid_0`` | ``-DTEST_FOO=2 -DTEST_LAUNCH=0 VAR_ID=4`` |
|
||||
+-----------------------------+-------------------------------------------+
|
||||
| ``<name_base>.foo_2.lid_1`` | ``-DTEST_FOO=2 -DTEST_LAUNCH=1 VAR_ID=5`` |
|
||||
+-----------------------------+-------------------------------------------+
|
||||
|
||||
Changing ``%PARAM%`` Hints
|
||||
**************************
|
||||
|
||||
Since CMake does not automatically reconfigure the build when source files are
|
||||
modified, CMake will need to be rerun manually whenever the ``%PARAM%`` comments
|
||||
change.
|
||||
|
||||
Using the CMake Variant Functions
|
||||
---------------------------------
|
||||
|
||||
``cmake/CCCLTestParams.cmake`` provides the functions that implement this functionality.
|
||||
See that file for detailed documentation. An example of their usage is:
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
set(test_src <path_to_source_file>)
|
||||
set(test_name <test_name_derived_from_test_src>)
|
||||
|
||||
# Parse %PARAM% comments from the source file and generate lists of labels/definitions:
|
||||
cccl_parse_variant_params("${test_src}" num_variants variant_labels variant_defs)
|
||||
|
||||
if (num_variants EQUAL 0)
|
||||
# Add test with no variants named `test_name` here. Example:
|
||||
add_executable("${test_name}" "${test_src}")
|
||||
add_test(NAME "${test_name}" COMMAND "${test_name}")
|
||||
else() # Has variants:
|
||||
# Optional: log the detected variant info to CMake's VERBOSE output stream:
|
||||
cccl_log_variant_params("${test_name}" ${num_variants} variant_labels variant_defs)
|
||||
|
||||
# Subtract 1 to support the inclusive endpoint of foreach(...RANGE...):
|
||||
math(EXPR var_range_end "${num_variants} - 1")
|
||||
foreach(var_idx RANGE ${var_range_end})
|
||||
# Get the variant label and definitions for the current index:
|
||||
cccl_get_variant_data(variant_labels variant_defs ${var_idx} var_label var_defs)
|
||||
set(var_name "${test_name}.${var_label}")
|
||||
|
||||
# Add the test with the current variant label and definitions.
|
||||
# Example:
|
||||
add_executable("${var_name}" "${test_src}")
|
||||
target_compile_definitions("${var_name}" PRIVATE ${var_defs})
|
||||
add_test(NAME "${var_name}" COMMAND "${var_name}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
Running CMake with ``--log-level=VERBOSE`` will print out extra information about
|
||||
all detected test variants.
|
||||
|
||||
Additional Info
|
||||
---------------
|
||||
|
||||
Ideally, only parameters that directly influence template instantiations
|
||||
should be split out in this way. If changing a parameter doesn't change a
|
||||
template type, the same template instantiations will be compiled into multiple
|
||||
executables. This defeats the purpose of splitting up the test since the
|
||||
compiler will generate redundant code across the new split executables.
|
||||
|
||||
The best candidate parameters for splitting are input value types, rather than
|
||||
integral parameters like ``BLOCK_THREADS``, etc. Splitting by value type allows more
|
||||
infrastructure (data generation, validation) to be reused. Splitting other
|
||||
parameters can cause build times to increase since type-related infrastructure
|
||||
has to be rebuilt for each test variant.
|
||||
63
cccl_upstream/docs/cccl/development/visibility.rst
Normal file
63
cccl_upstream/docs/cccl/development/visibility.rst
Normal file
@@ -0,0 +1,63 @@
|
||||
. _cccl-development-visibility:
|
||||
|
||||
Symbol Visibility
|
||||
==================
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:maxdepth: 1
|
||||
|
||||
visibility/host_stub_visibility
|
||||
visibility/device_kernel_visibility
|
||||
visibility/different_architectures
|
||||
|
||||
Using kernels in shared libraries is a known source of issues. This relates to the visibility of the kernel functions
|
||||
and their host stubs, but also ODR violations that arise from compiling with different CUDA architectures.
|
||||
|
||||
To alleviate those issues we have derived the following rules to ensure that users can safely rely on CCCL features in
|
||||
shared libraries.
|
||||
|
||||
1. Every kernel should be annotated as ``hidden`` through ``_CCCL_KERNEL_ATTRIBUTES``
|
||||
2. Every function or type that eventually calls a kernel in a subsequent function call or member function must be put in
|
||||
a namespace that disambiguates the CUDA architectures the library was compiled with.
|
||||
3. It is important that an API accepting kernel pointers (e.g. ``triple_chevron``) always resides in the same
|
||||
library as the code taking this pointers.
|
||||
|
||||
In the following we will give a more detailed overview over the different problems and why we settled on above rules.
|
||||
|
||||
Problem 1: Selecting the right kernel stub
|
||||
-------------------------------------------
|
||||
|
||||
Consider a project that links two shared libraries ``lib_a`` and ``lib_b`` that involve a kernel call of some global
|
||||
``kernel`` template. The compiler will generate a stub function that handles actually launching the kernel via the CUDA
|
||||
runtime. Prior to CTK 13.0 that stub function has weak linkage, so if both libraries try to launch ``kernel`` only one
|
||||
host stub will be selected and the other kernel launch might silently fail. See the compiler teams
|
||||
`blog post <https://developer.nvidia.com/blog/cuda-c-compiler-updates-impacting-elf-visibility-and-linkage/>`_ about the
|
||||
recent changes to kernel visibility.
|
||||
|
||||
A more detailed description can be found :ref:`here <cccl-development-visibility-host-stub-visibility>`.
|
||||
|
||||
Problem 2: Calling kernels from inside a shared library
|
||||
--------------------------------------------------------
|
||||
|
||||
This is quite similar to Problem 1 above. Again a project links two shared libraries ``lib_a`` and ``lib_b``. However,
|
||||
this time we call a library function ``foo`` that takes a function pointer to a kernel as an argument and invokes it.
|
||||
If ``foo`` has weak external linkage we might end up calling ``lib_b::foo`` from inside ``lib_b`` instead of
|
||||
``lib_a::foo``, or vice versa. The CUDA runtime from ``lib_a`` will not be able to call the kernel function pointer we
|
||||
passed from ``lib_b``.
|
||||
|
||||
A more detailed description can be found :ref:`here <cccl-development-visibility-device-kernel-visibility>`.
|
||||
|
||||
Problem 3: Libraries compiled for different architectures
|
||||
----------------------------------------------------------
|
||||
|
||||
This is orthogonal to the visibility of the functions themself but relates to ODR
|
||||
(`_one definition rule_ <https://en.cppreference.com/w/cpp/language/definition.html>`_) violations in case libraries are
|
||||
compiled for different architectures. As new architectures come out, we adopt new features to provide the best possible
|
||||
performance for all existing architectures.
|
||||
|
||||
However, consider a kernel that relies on hardware dependent tuning or can leverage runtime features that are only
|
||||
available on certain hardware. If we build 2 libraries for different architectures then the kernel implementation
|
||||
will be different between the two libraries, but the kernel itself is mangled as the same symbol.
|
||||
|
||||
A more detailed description can be found :ref:`here <cccl-development-visibility-different-architectures>`.
|
||||
@@ -0,0 +1,133 @@
|
||||
.. _cccl-development-visibility-device-kernel-visibility:
|
||||
|
||||
|
||||
Device Kernel Visibility Issue
|
||||
-------------------------------
|
||||
|
||||
Consider the following simple translation unit (TU):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
template <class T>
|
||||
__global__ void kernel(T *val) {
|
||||
::printf("kernel: set val = 42\n");
|
||||
*val = 42;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int *ptr{};
|
||||
kernel<<<1, 1>>>(ptr);
|
||||
}
|
||||
|
||||
The cuda compiler frontend will turn this into:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
template< class T>
|
||||
static void __wrapper__device_stub_kernel(T *&ptr) {
|
||||
::cudaLaunchKernel(0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// stub host function
|
||||
template< class T>
|
||||
void kernel(T *ptr) {
|
||||
__wrapper__device_stub_kernel<T>(ptr);
|
||||
}
|
||||
|
||||
int main() {
|
||||
int *ptr{};
|
||||
(__cudaPushCallConfiguration(1, 1)) ? (void)0 : kernel(ptr);
|
||||
}
|
||||
|
||||
static void __device_stub__Z6kernelIiEvPT_(int *__par0) {
|
||||
__cudaLaunchPrologue(1);
|
||||
__cudaSetupArgSimple(__par0, 0UL);
|
||||
__cudaLaunch(((char *)((void ( *)(int *))kernel )));
|
||||
}
|
||||
|
||||
template<> void __wrapper__device_stub_kernel(int *&__cuda_0) {
|
||||
__device_stub__Z6kernelIiEvPT_( (int *&)__cuda_0);
|
||||
}
|
||||
|
||||
The CUDA runtime is going to use the address of ``template<> void kernel(T *ptr)`` (in the following ``h_kernel``)
|
||||
as a key in the host stub function (``h_kernel``) - device function (``d_kernel``) mapping. This works fine if
|
||||
there is only a single source of truth for the stub function ``h_kernel``.
|
||||
|
||||
However, imagine that there are two shared libraries: ``lib_a`` and ``lib_b`` both instantiating different ``kernel``
|
||||
instances, e.g ``d_kernel<int>`` and ``d_kernel<size_t>``.
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
project(DeviceKernelVisibility CUDA CXX)
|
||||
|
||||
add_executable(device_kernel_visibility main.cu)
|
||||
add_library(lib_a SHARED tu_a.cu)
|
||||
add_library(lib_b SHARED tu_b.cu)
|
||||
target_link_libraries(device_kernel_visibility PRIVATE lib_a lib_b)
|
||||
|
||||
Each library will have it's own fatbinary: ``d_kernel<int>_a`` and ``d_kernel<size_t>_b`` as well as host stub functions
|
||||
``h_kernel<int>_a`` and ``h_kernel<size_t>_b``.
|
||||
|
||||
=== ============= ============
|
||||
lib host device
|
||||
=== ============= ============
|
||||
a 0xh_kernel_a 0xd_kernel_a
|
||||
b 0xh_kernel_b 0xd_kernel_b
|
||||
=== ============= ============
|
||||
|
||||
In contrast to
|
||||
:ref:`Problem 1 <cccl-development-visibility-host-stub-visibility>` the host stubs will get a different mangled name
|
||||
and so the right stub function will always be selected.
|
||||
|
||||
Now imagine that both libraries are going to defer launching of their kernels to a function ``foo`` common to both
|
||||
``lib_a`` and ``lib_b``, which has weak external linkage. This might happen in ``CUB``, because it launches
|
||||
kernels through the ``thrust::triple_chevron`` helper.
|
||||
|
||||
Similar to :ref:`Problem 1 <cccl-development-visibility-host-stub-visibility>` the linker will pick one of the two
|
||||
weak symbols and subsequently ``lib_a`` will try to pass its own kernel ``d_kernel<int>_a`` to ``lib_b::foo``.
|
||||
|
||||
However, the CUDA runtime in ``lib_b`` will not find any kernel registered at the address of ``d_kernel<int>_a`` and
|
||||
will fail to launch the kernel.
|
||||
|
||||
A simple example program that exemplifies this can be found
|
||||
`on github <https://github.com/NVIDIA/cccl/tree/main/docs/cub/developer/visibility/examples/device_kernel_visibility>`_
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./device_kernel_visibility/device_kernel_visibility
|
||||
a: kernel stub address: 0x7fdec19e13eb <== launching kernel_a from a
|
||||
a: kernel is in mapping: no error
|
||||
b: launched kernel
|
||||
a: kernel: set val = 42
|
||||
a: synchronized stream
|
||||
a: copied from device to host
|
||||
a: out: 42
|
||||
a: kernel was launched: out == 42 <== everything is fine
|
||||
|
||||
a: defers launch to b
|
||||
b: kernel stub address: 0x7fdec19e13eb <== launch kernel_a from b
|
||||
b: kernel NOT found in mapping: invalid device function <== kernel_a is not found in b mapping
|
||||
b: FAILED to launch kernel <== unable to launch the kernel from b
|
||||
b: synchronized stream
|
||||
b: copied from device to host
|
||||
b: out: 0
|
||||
b: kernel was NOT actually launched: out != 42
|
||||
|
||||
b: kernel stub address: 0x7fdec19333eb <== launch kernel_b from b
|
||||
b: kernel is in mapping: no error
|
||||
b: launched kernel
|
||||
b: kernel: set val = 42
|
||||
b: synchronized stream
|
||||
b: copied from device to host
|
||||
b: out: 42
|
||||
b: kernel was launched: out == 42 <== everything is fine
|
||||
|
||||
b: defers launch to a
|
||||
a: kernel stub address: 0x7fdec19333eb <== launching kernel_b from a
|
||||
a: kernel NOT found in mapping: invalid device function <== same issue as above
|
||||
a: FAILED to launch kernel
|
||||
b: kernel: set val = 42
|
||||
a: synchronized stream
|
||||
a: copied from device to host
|
||||
a: out: 42
|
||||
a: kernel was launched: out == 42 <== kernel launch somehow succeeded
|
||||
@@ -0,0 +1,186 @@
|
||||
.. _cccl-development-visibility-different-architectures:
|
||||
|
||||
Linking TUs compiled with different architectures
|
||||
--------------------------------------------------
|
||||
|
||||
Consider the following simple library:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
template <int... Archs>
|
||||
__host__ __device__ constexpr int sum_archs() noexcept {
|
||||
return (Archs + ... + 0);
|
||||
}
|
||||
|
||||
// kernel with architecture dependent symbol name and functionality
|
||||
template <class T, auto Archs = sum_archs<__CUDA_ARCH_LIST__>()>
|
||||
__global__ void kernel(T *val) {
|
||||
*val = sum_archs<__CUDA_ARCH_LIST__>();
|
||||
}
|
||||
|
||||
__attribute__((visibility("hidden"))) inline int use_kernel() {
|
||||
int *d_val{};
|
||||
cudaMalloc(&d_val, sizeof(d_val));
|
||||
kernel<<<1, 1>>>(d_val);
|
||||
int ret;
|
||||
if (cudaMemcpy(&ret, d_val, sizeof(size_t), cudaMemcpyDeviceToHost) !=
|
||||
cudaSuccess) {
|
||||
std::printf("c: FAILED to copy from device to host\n");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <class T = int>
|
||||
struct some_class_with_kernel {
|
||||
T val_;
|
||||
|
||||
some_class_with_kernel();
|
||||
__forceinline__ some_class_with_kernel(T) { val_ = use_kernel(); }
|
||||
};
|
||||
|
||||
We have a kernel that does some architecture dependent work. This could be relying on some hardware feature that is
|
||||
dependent on the current architecture.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include "kernel.cuh"
|
||||
|
||||
int main() {
|
||||
some_class_with_kernel with_inline{1};
|
||||
std::printf("a: value of class with inlined constructor: %d\n",
|
||||
with_inline.val_);
|
||||
|
||||
some_class_with_kernel from_library{};
|
||||
std::printf("a: value of class with constructor from library: %d\n",
|
||||
from_library.val_);
|
||||
}
|
||||
|
||||
Importantly, one of the constructors for that class is put into a shared library, whereas the other one happens to be
|
||||
inlined. If a user now links two different libraries, the outcome of the initialization of ``some_class_with_kernel``
|
||||
will depend on whether the inlined constructor is called and which of the libraries is loaded first by the linker.
|
||||
|
||||
Even worse, the state of a class depends on whether the constructor has been inlined or not and the order in which
|
||||
the linker loads the libraries.
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
project(CUBVisDifferentArchitectures CUDA CXX)
|
||||
|
||||
add_library(cubvis_different_architectures_lib_a SHARED tu_a.cu)
|
||||
set_target_properties(cubvis_different_architectures_lib_a PROPERTIES CUDA_ARCHITECTURES "86;90a")
|
||||
|
||||
add_library(cubvis_different_architectures_lib_b SHARED tu_b.cu)
|
||||
set_target_properties(cubvis_different_architectures_lib_b PROPERTIES CUDA_ARCHITECTURES "75;86;90a")
|
||||
|
||||
add_executable(cubvis_different_architectures main.cu)
|
||||
set_target_properties(cubvis_different_architectures PROPERTIES CUDA_ARCHITECTURES "75;86")
|
||||
|
||||
target_link_libraries(cubvis_different_architectures PRIVATE
|
||||
cubvis_different_architectures_lib_a
|
||||
cubvis_different_architectures_lib_b)
|
||||
|
||||
add_executable(cubvis_different_architectures_switched main.cu)
|
||||
set_target_properties(cubvis_different_architectures_switched PROPERTIES CUDA_ARCHITECTURES "75;86")
|
||||
|
||||
target_link_libraries(cubvis_different_architectures_switched PRIVATE
|
||||
cubvis_different_architectures_lib_b
|
||||
cubvis_different_architectures_lib_a)
|
||||
|
||||
Execution the two libraries will result in the following:
|
||||
|
||||
.. code-block::
|
||||
|
||||
./different_architectures/different_architectures
|
||||
a: value of class with inlined constructor: 1610 <<<--- from main
|
||||
a: value of class with constructor from library: 1760 <<<--- from lib_a
|
||||
|
||||
./different_architectures/different_architectures_switched
|
||||
a: value of class with inlined constructor: 1610 <<<--- from main
|
||||
a: value of class with constructor from library: 2510 <<<--- from lib_b
|
||||
|
||||
|
||||
One solution would be to bake the architectures into the symbol name of the class, either via a defaulted template
|
||||
argument or an inline namespace. That way the usage of the non-inlined kernel would result in a linker error, because
|
||||
we did not provide a matching implementation.
|
||||
|
||||
.. code-block::
|
||||
|
||||
tmpxft_00048dff_00000000-6_main.compute_86.cudafe1.cpp:(.text.startup+0xc0):
|
||||
undefined reference to `some_class_with_kernel<int, 5120ul>::some_class_with_kernel()'
|
||||
|
||||
However, if all the functionality is within a non-inlined function we would still get different results, because all
|
||||
kernel definitions would be internal to the respective library.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
// In tu_a.cu and tu_b.cu
|
||||
void non_inlined_function() {
|
||||
some_class_with_kernel with_inline{1};
|
||||
std::printf("a: value of class with inlined constructor: %d\n",
|
||||
with_inline.val_);
|
||||
|
||||
some_class_with_kernel from_library{};
|
||||
std::printf("a: value of class with constructor from library: %d\n",
|
||||
from_library.val_);
|
||||
}
|
||||
|
||||
// In main.cu
|
||||
#include "kernel.cuh"
|
||||
|
||||
void non_inlined_function();
|
||||
|
||||
int main() {
|
||||
some_class_with_kernel with_inline{1};
|
||||
std::printf("a: value of class with inlined constructor: %d\n",
|
||||
with_inline.val_);
|
||||
|
||||
non_inlined_function();
|
||||
}
|
||||
|
||||
Executing this binary will give us again:
|
||||
|
||||
.. code-block::
|
||||
|
||||
./different_architectures/different_architectures
|
||||
a: value of class with inlined constructor: 1610 <<<--- from main
|
||||
a: value of class with inlined constructor: 1760 <<<--- from lib_a
|
||||
a: value of class with constructor from library: 1760 <<<--- from lib_a
|
||||
|
||||
./different_architectures/different_architectures_switched
|
||||
a: value of class with inlined constructor: 1610 <<<--- from main
|
||||
a: value of class with inlined constructor: 2510 <<<--- from lib_a
|
||||
a: value of class with constructor from library: 2510 <<<--- from lib_b
|
||||
|
||||
So there is not functional way we can solve this problem generically, because the moment a user actually uses any type
|
||||
of function that executes a kernel and puts that function into a shared library there is no guarantee which function
|
||||
is selected. The same happens if the user builds a type
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
class user_defined_with_kernel {
|
||||
some_class_with_kernel val;
|
||||
|
||||
user_defined_with_kernel();
|
||||
__forceinline__ user_defined_with_kernel(T input) : val(input)
|
||||
{}
|
||||
};
|
||||
|
||||
void function_that_uses_kernel_inside();
|
||||
|
||||
If ``user_defined_with_kernel`` is ever baked into a library we would be back with the same exact problem,
|
||||
just one layer up. The user would need to know that ``some_class_with_kernel`` uses a kernel and then annotate *their*
|
||||
classes and functions appropriately. This is neither realistic nor feasible.
|
||||
|
||||
Lets circle back to the previous statement: ``This is bad.`` Is it really though?
|
||||
|
||||
Lets look at the prime example ``thrust::device_vector``, which uses a kernel for initialization. What happens if we
|
||||
accidentally run the kernel from another shared library compiled with different architectures? Worst case we are
|
||||
eating some performance regressions because the kernel will not utilize advanced features of a new architecture,
|
||||
but in the end the result of calling that kernel will not change the outcome.
|
||||
|
||||
This is because the kernel call is consistent *within* each library. As long as the user facing API does not rely on
|
||||
specific internals of a kernel to be called -which it should not-, then any of the two libraries will do.
|
||||
|
||||
Finally, the architectures that are passed around in ``__CUDA_ARCH_LIST__`` do *not* discriminate architecture families.
|
||||
There is currently no programmatic way to discriminate a library that has been compiled for ``SM90a`` from one that was
|
||||
compiled for ``SM90``. This is because the architecture specific macros are only available on device not on host.
|
||||
@@ -0,0 +1,10 @@
|
||||
project(DeveloperGuideDeviceKernelVisibility CUDA CXX)
|
||||
|
||||
add_executable(device_kernel_visibility main.cu)
|
||||
add_library(device_kernel_visibility_liba SHARED tu_a.cu)
|
||||
add_library(device_kernel_visibility_lib_b SHARED tu_b.cu)
|
||||
|
||||
target_link_libraries(
|
||||
device_kernel_visibility
|
||||
PRIVATE device_kernel_visibility_lib_a device_kernel_visibility_lib_b
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
template <class T>
|
||||
__global__ void kernel(char ln, T* val)
|
||||
{
|
||||
printf("%c: kernel: set val = 42\n", ln);
|
||||
*val = 42;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
void a();
|
||||
void b();
|
||||
|
||||
int main()
|
||||
{
|
||||
a();
|
||||
b();
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
#include "kernel.cuh"
|
||||
|
||||
void b_launch(void (*k)(char, size_t*), char c, size_t* d_out);
|
||||
|
||||
void a_launch(void (*k)(char, int*), char c, int* d_out)
|
||||
{
|
||||
void* ptr = reinterpret_cast<void*>(k);
|
||||
|
||||
printf("a: kernel stub address: %p\n", ptr);
|
||||
|
||||
cudaFunction_t func{};
|
||||
if (cudaError_t error = cudaGetFuncBySymbol(&func, ptr))
|
||||
{
|
||||
printf("a: kernel NOT found in mapping: %s\n", cudaGetErrorString(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: kernel is in mapping: %s\n", cudaGetErrorString(error));
|
||||
}
|
||||
|
||||
cudaMemset(d_out, 0, sizeof(int));
|
||||
k<<<1, 1>>>(c, d_out);
|
||||
|
||||
if (cudaPeekAtLastError() != cudaSuccess)
|
||||
{
|
||||
printf("a: FAILED to launch kernel\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: launched kernel\n");
|
||||
}
|
||||
|
||||
if (cudaStreamSynchronize(0) != cudaSuccess)
|
||||
{
|
||||
printf("a: FAILED to synchronize stream\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: synchronized stream\n");
|
||||
}
|
||||
|
||||
int h_out{};
|
||||
if (cudaMemcpy(&h_out, d_out, sizeof(int), cudaMemcpyDeviceToHost) != cudaSuccess)
|
||||
{
|
||||
printf("a: FAILED to copy from device to host\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: copied from device to host\n");
|
||||
}
|
||||
|
||||
printf("a: out: %d\n", h_out);
|
||||
if (h_out != 42)
|
||||
{
|
||||
printf("a: kernel was NOT actually launched: out != 42\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: kernel was launched: out == 42\n");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void a()
|
||||
{
|
||||
cudaGetLastError();
|
||||
|
||||
size_t* d_out{};
|
||||
cudaMalloc(&d_out, sizeof(size_t));
|
||||
cudaMemset(d_out, 0, sizeof(size_t));
|
||||
|
||||
void* ptr = reinterpret_cast<void*>(kernel<size_t>);
|
||||
|
||||
printf("a: kernel stub address: %p\n", ptr);
|
||||
|
||||
cudaFunction_t func{};
|
||||
if (cudaError_t error = cudaGetFuncBySymbol(&func, ptr))
|
||||
{
|
||||
printf("a: kernel NOT found in mapping: %s\n", cudaGetErrorString(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: kernel is in mapping: %s\n", cudaGetErrorString(error));
|
||||
}
|
||||
|
||||
kernel<<<1, 1>>>('a', d_out);
|
||||
|
||||
if (cudaPeekAtLastError() != cudaSuccess)
|
||||
{
|
||||
printf("b: FAILED to launch kernel\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: launched kernel\n");
|
||||
}
|
||||
|
||||
if (cudaStreamSynchronize(0) != cudaSuccess)
|
||||
{
|
||||
printf("a: FAILED to synchronize stream\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: synchronized stream\n");
|
||||
}
|
||||
|
||||
size_t h_out{};
|
||||
if (cudaMemcpy(&h_out, d_out, sizeof(size_t), cudaMemcpyDeviceToHost) != cudaSuccess)
|
||||
{
|
||||
printf("a: FAILED to copy from device to host\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: copied from device to host\n");
|
||||
}
|
||||
|
||||
printf("a: out: %d\n", static_cast<int>(h_out));
|
||||
if (h_out != 42)
|
||||
{
|
||||
printf("a: kernel was NOT actually launched: out != 42\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: kernel was launched: out == 42\n");
|
||||
}
|
||||
|
||||
cudaMemset(d_out, 0, sizeof(size_t));
|
||||
printf("\n");
|
||||
|
||||
printf("a: defers launch to b\n");
|
||||
b_launch(kernel<size_t>, 'b', d_out);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
#include "kernel.cuh"
|
||||
|
||||
void a_launch(void (*k)(char, int*), char c, int* d_out);
|
||||
|
||||
void b_launch(void (*k)(char, size_t*), char c, size_t* d_out)
|
||||
{
|
||||
void* ptr = reinterpret_cast<void*>(k);
|
||||
|
||||
printf("b: kernel stub address: %p\n", ptr);
|
||||
|
||||
cudaFunction_t func{};
|
||||
if (cudaError_t error = cudaGetFuncBySymbol(&func, ptr))
|
||||
{
|
||||
printf("b: kernel NOT found in mapping: %s\n", cudaGetErrorString(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: kernel is in mapping: %s\n", cudaGetErrorString(error));
|
||||
}
|
||||
|
||||
k<<<1, 1>>>(c, d_out);
|
||||
|
||||
if (cudaPeekAtLastError() != cudaSuccess)
|
||||
{
|
||||
printf("b: FAILED to launch kernel\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: launched kernel\n");
|
||||
}
|
||||
|
||||
if (cudaStreamSynchronize(0) != cudaSuccess)
|
||||
{
|
||||
printf("b: FAILED to synchronize stream\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: synchronized stream\n");
|
||||
}
|
||||
|
||||
size_t h_out{};
|
||||
if (cudaMemcpy(&h_out, d_out, sizeof(size_t), cudaMemcpyDeviceToHost) != cudaSuccess)
|
||||
{
|
||||
printf("b: FAILED to copy from device to host\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: copied from device to host\n");
|
||||
}
|
||||
|
||||
printf("b: out: %d\n", static_cast<int>(h_out));
|
||||
if (h_out != 42)
|
||||
{
|
||||
printf("b: kernel was NOT actually launched: out != 42\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: kernel was launched: out == 42\n");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void b()
|
||||
{
|
||||
cudaGetLastError();
|
||||
|
||||
int* d_out{};
|
||||
cudaMalloc(&d_out, sizeof(int));
|
||||
cudaMemset(d_out, 0, sizeof(int));
|
||||
|
||||
void* ptr = reinterpret_cast<void*>(kernel<int>);
|
||||
|
||||
printf("b: kernel stub address: %p\n", ptr);
|
||||
|
||||
cudaFunction_t func{};
|
||||
if (cudaError_t error = cudaGetFuncBySymbol(&func, ptr))
|
||||
{
|
||||
printf("b: kernel NOT found in mapping: %s\n", cudaGetErrorString(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: kernel is in mapping: %s\n", cudaGetErrorString(error));
|
||||
}
|
||||
|
||||
kernel<<<1, 1>>>('b', d_out);
|
||||
|
||||
if (cudaPeekAtLastError() != cudaSuccess)
|
||||
{
|
||||
printf("b: FAILED to launch kernel\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: launched kernel\n");
|
||||
}
|
||||
|
||||
if (cudaStreamSynchronize(0) != cudaSuccess)
|
||||
{
|
||||
printf("b: FAILED to synchronize stream\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: synchronized stream\n");
|
||||
}
|
||||
|
||||
int h_out{};
|
||||
if (cudaMemcpy(&h_out, d_out, sizeof(int), cudaMemcpyDeviceToHost) != cudaSuccess)
|
||||
{
|
||||
printf("b: FAILED to copy from device to host\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: copied from device to host\n");
|
||||
}
|
||||
|
||||
printf("b: out: %d\n", h_out);
|
||||
|
||||
if (h_out != 42)
|
||||
{
|
||||
printf("b: kernel was NOT actually launched: out != 42\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: kernel was launched: out == 42\n");
|
||||
}
|
||||
|
||||
cudaMemset(d_out, 0, sizeof(int));
|
||||
printf("\n");
|
||||
|
||||
printf("b: defers launch to a\n");
|
||||
a_launch(kernel<int>, 'b', d_out);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
project(DeveloperGuideDifferentArchitectures CUDA CXX)
|
||||
|
||||
add_library(different_architectures_lib_a SHARED tu_a.cu)
|
||||
set_target_properties(
|
||||
different_architectures_lib_a
|
||||
PROPERTIES CUDA_ARCHITECTURES "86;90a"
|
||||
)
|
||||
|
||||
add_library(different_architectures_lib_b SHARED tu_b.cu)
|
||||
set_target_properties(
|
||||
different_architectures_lib_b
|
||||
PROPERTIES CUDA_ARCHITECTURES "75;86;90a"
|
||||
)
|
||||
|
||||
add_executable(different_architectures main.cu)
|
||||
set_target_properties(
|
||||
different_architectures
|
||||
PROPERTIES CUDA_ARCHITECTURES "75;86"
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
different_architectures
|
||||
PRIVATE different_architectures_lib_a different_architectures_lib_b
|
||||
)
|
||||
|
||||
add_executable(different_architectures_switched main.cu kernel.cu)
|
||||
set_target_properties(
|
||||
different_architectures_switched
|
||||
PROPERTIES CUDA_ARCHITECTURES "75;86"
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
different_architectures_switched
|
||||
PRIVATE different_architectures_lib_b different_architectures_lib_a
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
template <int... Archs>
|
||||
__attribute__((visibility("hidden"))) __host__ __device__ constexpr int sum_archs()
|
||||
{
|
||||
return (Archs + ... + 0);
|
||||
}
|
||||
|
||||
template <class T, auto Archs = sum_archs<__CUDA_ARCH_LIST__>()>
|
||||
__attribute__((visibility("hidden"))) __global__ void kernel(char ln, T* val)
|
||||
{
|
||||
printf("%c: kernel: set val = %i\n", ln, sum_archs<__CUDA_ARCH_LIST__>());
|
||||
*val = sum_archs<__CUDA_ARCH_LIST__>();
|
||||
}
|
||||
|
||||
__attribute__((visibility("hidden"))) __forceinline__ int use_kernel()
|
||||
{
|
||||
int* d_val{};
|
||||
cudaMalloc(&d_val, sizeof(size_t));
|
||||
kernel<<<1, 1>>>(d_val);
|
||||
int ret;
|
||||
if (cudaMemcpy(&ret, d_val, sizeof(size_t), cudaMemcpyDeviceToHost) != cudaSuccess)
|
||||
{
|
||||
printf("c: FAILED to copy from device to host\n");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <class T = int>
|
||||
struct some_class_with_kernel
|
||||
{
|
||||
T val_;
|
||||
|
||||
some_class_with_kernel();
|
||||
__forceinline__ some_class_with_kernel(T)
|
||||
{
|
||||
val_ = use_kernel();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "kernel.cuh"
|
||||
|
||||
void non_inlined_function();
|
||||
|
||||
int main()
|
||||
{
|
||||
some_class_with_kernel with_inline{1};
|
||||
printf("a: value of class with inlined constructor: %d\n", with_inline.val_);
|
||||
|
||||
non_inlined_function();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "kernel.cuh"
|
||||
|
||||
template <class T>
|
||||
some_class_with_kernel<T>::some_class_with_kernel()
|
||||
{
|
||||
val_ = use_kernel();
|
||||
}
|
||||
|
||||
void non_inlined_function()
|
||||
{
|
||||
some_class_with_kernel with_inline{1};
|
||||
printf("a: value of class with inlined constructor: %d\n", with_inline.val_);
|
||||
|
||||
some_class_with_kernel from_library{};
|
||||
printf("a: value of class with constructor from library: %d\n", from_library.val_);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "kernel.cuh"
|
||||
|
||||
template <class T>
|
||||
some_class_with_kernel<T>::some_class_with_kernel()
|
||||
{
|
||||
val_ = use_kernel();
|
||||
}
|
||||
|
||||
void non_inlined_function()
|
||||
{
|
||||
some_class_with_kernel with_inline{1};
|
||||
printf("a: value of class with inlined constructor: %d\n", with_inline.val_);
|
||||
|
||||
some_class_with_kernel from_library{};
|
||||
printf("a: value of class with constructor from library: %d\n", from_library.val_);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
project(DeveloperGuideHostStubVisibility CUDA CXX)
|
||||
|
||||
add_executable(host_stub_visibility main.cu)
|
||||
add_library(host_stub_visibility_lib_a SHARED tu_a.cu)
|
||||
add_library(host_stub_visibility_lib_b SHARED tu_b.cu)
|
||||
|
||||
target_link_libraries(
|
||||
host_stub_visibility
|
||||
PRIVATE host_stub_visibility_lib_a host_stub_visibility_lib_b
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
template <class T>
|
||||
__global__ void kernel(char ln, T* val)
|
||||
{
|
||||
printf("%c: kernel: set val = 42\n", ln);
|
||||
*val = 42;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
void a();
|
||||
void b();
|
||||
|
||||
int main()
|
||||
{
|
||||
a();
|
||||
b();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "kernel.cuh"
|
||||
|
||||
void a()
|
||||
{
|
||||
int* d_out{};
|
||||
cudaMalloc(&d_out, sizeof(int));
|
||||
cudaMemset(d_out, 0, sizeof(int));
|
||||
|
||||
void* ptr = reinterpret_cast<void*>(kernel<int>);
|
||||
|
||||
printf("a: kernel stub address: %p\n", ptr);
|
||||
|
||||
cudaFunction_t func{};
|
||||
if (cudaError_t error = cudaGetFuncBySymbol(&func, ptr))
|
||||
{
|
||||
printf("a: kernel NOT found in mapping: %s\n", cudaGetErrorString(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: kernel is in mapping: %s\n", cudaGetErrorString(error));
|
||||
}
|
||||
|
||||
kernel<<<1, 1>>>('a', d_out);
|
||||
|
||||
if (cudaPeekAtLastError() != cudaSuccess)
|
||||
{
|
||||
printf("b: FAILED to launch kernel\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: launched kernel\n");
|
||||
}
|
||||
|
||||
if (cudaStreamSynchronize(0) != cudaSuccess)
|
||||
{
|
||||
printf("a: FAILED to synchronize stream\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: synchronized stream\n");
|
||||
}
|
||||
|
||||
int h_out{};
|
||||
if (cudaMemcpy(&h_out, d_out, sizeof(int), cudaMemcpyDeviceToHost) != cudaSuccess)
|
||||
{
|
||||
printf("a: FAILED to copy from device to host\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: copied from device to host\n");
|
||||
}
|
||||
|
||||
printf("a: out: %d\n", h_out);
|
||||
if (h_out != 42)
|
||||
{
|
||||
printf("a: kernel was NOT actually launched: out != 42\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("a: kernel was launched: out == 42\n");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "kernel.cuh"
|
||||
|
||||
void b()
|
||||
{
|
||||
int* d_out{};
|
||||
cudaMalloc(&d_out, sizeof(int));
|
||||
cudaMemset(d_out, 0, sizeof(int));
|
||||
|
||||
void* ptr = reinterpret_cast<void*>(kernel<int>);
|
||||
|
||||
printf("b: kernel stub address: %p\n", ptr);
|
||||
|
||||
cudaFunction_t func{};
|
||||
if (cudaError_t error = cudaGetFuncBySymbol(&func, ptr))
|
||||
{
|
||||
printf("b: kernel NOT found in mapping: %s\n", cudaGetErrorString(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: kernel is in mapping: %s\n", cudaGetErrorString(error));
|
||||
}
|
||||
|
||||
kernel<<<1, 1>>>('b', d_out);
|
||||
|
||||
if (cudaPeekAtLastError() != cudaSuccess)
|
||||
{
|
||||
printf("b: FAILED to launch kernel\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: launched kernel\n");
|
||||
}
|
||||
|
||||
if (cudaStreamSynchronize(0) != cudaSuccess)
|
||||
{
|
||||
printf("b: FAILED to synchronize stream\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: synchronized stream\n");
|
||||
}
|
||||
|
||||
int h_out{};
|
||||
if (cudaMemcpy(&h_out, d_out, sizeof(int), cudaMemcpyDeviceToHost) != cudaSuccess)
|
||||
{
|
||||
printf("b: FAILED to copy from device to host\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: copied from device to host\n");
|
||||
}
|
||||
|
||||
printf("b: out: %d\n", h_out);
|
||||
|
||||
if (h_out != 42)
|
||||
{
|
||||
printf("b: kernel was NOT actually launched: out != 42\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("b: kernel was launched: out == 42\n");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
.. _cccl-development-visibility-host-stub-visibility:
|
||||
|
||||
|
||||
Host Stub Visibility Issue
|
||||
---------------------------
|
||||
|
||||
Consider the following simple translation unit (TU):
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
#include <cstdio>
|
||||
#include <cuda/memory>
|
||||
|
||||
template <class T>
|
||||
__global__ void kernel(T *val) {
|
||||
printf("kernel: set val = 42\n");
|
||||
*val = 42;
|
||||
}
|
||||
|
||||
__device__ int val;
|
||||
|
||||
int main() {
|
||||
|
||||
kernel<<<1, 1>>>(cuda::get_device_address(val));
|
||||
}
|
||||
|
||||
The CUDA compiler frontend will turn this into:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
template< class T>
|
||||
static void __wrapper__device_stub_kernel(T *&ptr) {
|
||||
::cudaLaunchKernel(0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// stub host function
|
||||
template< class T>
|
||||
void kernel(T *ptr) {
|
||||
__wrapper__device_stub_kernel<T>(ptr);
|
||||
}
|
||||
|
||||
int main() {
|
||||
int *ptr{};
|
||||
(__cudaPushCallConfiguration(1, 1)) ? (void)0 : kernel(ptr);
|
||||
}
|
||||
|
||||
static void __device_stub__Z6kernelIiEvPT_(int *__par0) {
|
||||
__cudaLaunchPrologue(1);
|
||||
__cudaSetupArgSimple(__par0, 0UL);
|
||||
__cudaLaunch(((char *)((void ( *)(int *))kernel )));
|
||||
}
|
||||
|
||||
template<> void __wrapper__device_stub_kernel(int *&__cuda_0) {
|
||||
__device_stub__Z6kernelIiEvPT_( (int *&)__cuda_0);
|
||||
}
|
||||
|
||||
The CUDA runtime is going to use the address of ``template<> void kernel(T *ptr)`` (in the following ``h_kernel``)
|
||||
as a key in the host stub function (``h_kernel``) - device function (``d_kernel``) mapping. This works fine if
|
||||
there is only a single source of truth for the stub function ``h_kernel``.
|
||||
|
||||
However, imagine that there are two shared libraries: ``lib_a`` and ``lib_b`` both using the same ``kernel`` instance.
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
project(HostStubVisibility CUDA CXX)
|
||||
|
||||
add_executable(host_stub_visibility main.cu)
|
||||
add_library(lib_a SHARED tu_a.cu)
|
||||
add_library(lib_b SHARED tu_b.cu)
|
||||
target_link_libraries(host_stub_visibility PRIVATE lib_a lib_b)
|
||||
|
||||
Each library will have its own fatbinary: ``d_kernel_a`` and ``d_kernel_b``, but the compiler
|
||||
generated host stub function ``h_kernel`` has weak external linkage, so after dynamic linkage, we'll end up having
|
||||
only one of them.
|
||||
|
||||
=== ===================== ============
|
||||
lib host device
|
||||
=== ===================== ============
|
||||
a 0xh_kernel_a 0xd_kernel_a
|
||||
b 0xh_kernel_a <- issue 0xd_kernel_b
|
||||
=== ===================== ============
|
||||
|
||||
Since there's a clash of stub function addresses, only one entry stored. When ``lib_b`` queries for the
|
||||
kernel using its address of ``h_kernel``, it's visible, although it might point to ``lib_a``'s fatbinary.
|
||||
The opposite case might happen as well, depending on loading order, linker etc and is undefined behavior.
|
||||
|
||||
Launching ``d_kernel`` from ``lib_b`` is not possible and leads to random errors. For instance, there seems to be
|
||||
some per CUDART global state. When the ``__cudaPushCallConfiguration`` is called in ``lib_b``, it affects the state of
|
||||
``cudart_b``, but the launch happens through ``h_kernel``, which is in ``lib_a``.
|
||||
|
||||
This sometimes leads to ``__global__ function call is not configured``. However, there might also be no error at all,
|
||||
and the kernel launch is silently skipped.
|
||||
|
||||
A simple example program that exemplifies this can be found
|
||||
`on github <https://github.com/NVIDIA/cccl/tree/main/docs/cub/developer/visibility/examples/host_stub_visibility>`_
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
:./host_stub_visibility/host_stub_visibility
|
||||
a: kernel stub address: 0x7f43318a415d <== same address as in B
|
||||
a: kernel is in mapping: no error <== kernel is found in the mapping
|
||||
b: launched kernel
|
||||
a: kernel: set val = 42
|
||||
a: synchronized stream
|
||||
a: copied from device to host
|
||||
a: out: 42
|
||||
a: kernel was launched: out == 42
|
||||
|
||||
b: kernel stub address: 0x7f43318a415d <== same address as in A
|
||||
b: kernel is in mapping: no error <== kernel is found in the mapping
|
||||
b: launched kernel
|
||||
b: synchronized stream
|
||||
b: copied from device to host
|
||||
b: out: 0
|
||||
b: kernel was NOT actually launched: out != 42 <== silent failure
|
||||
Reference in New Issue
Block a user