Scope: This document defines conventions for C++ API boundaries, naming, and
header organization for the cudaq project and the cudaq compiler
toolchain.
- Provide definitions for the different levels of APIs developers or end users may encounter.
- Provide definitions for developer categories and how each category may interact with the above APIs.
- Make it obvious which APIs are supported for end users versus internal development.
- Do not contribute to changes to the
cudaqcodebase. - Use only the User API shipped with the product.
- Compile and link to shipped runtime libraries with
nvq++ - Must not rely on internal headers/namespaces.
- Same restrictions as users: build on top of the User API only.
- May consume the API via
nvq++or by importing public headers/libraries into their own CMake project.
- Do contribute to changes to the
cudaqcodebase. - May use:
- User API
- Internal public module APIs
- Module private APIs
We define three API layers as illustrated below:
- User API
- Internal public module APIs
- Internal private APIs
┌────────────────────────────────────────────────────────────────────┐
│ Level 1: User API │
├────────────────────────────────────────────────────────────────────┤
│ Audience: Users, external libs (e.g., cudaqx) │
│ Headers: "cudaq.h", "cudaq/<subsystem>/<header>.h" │
│ Namespace: cudaq::... │
│ cudaq::detail = explicitly NON-public │
│ Naming: snake_case │
└────────────────────────────────────────────────────────────────────┘
▲
│ may include (transitively) when needed
│
┌────────────────────────────────────────────────────────────────────┐
│ Level 2: Internal Public Module APIs |
├────────────────────────────────────────────────────────────────────┤
│ Audience: Hardware vendors + core developers │
│ (NOT for users / external libs to depend on) │
│ Headers: "cudaq_internal/<module>/<hdr>.h" (or cudaq_dev/...) │
│ Namespace: cudaq_internal::<module>::... (module lowercase) │
│ cudaq_internal::<module>::detail = private │
│ Naming: CamelCase (or consistent module convention) │
└────────────────────────────────────────────────────────────────────┘
▲
│ internal-only use
│
┌────────────────────────────────────────────────────────────────────┐
│ Level 3: Internal Private APIs │
├────────────────────────────────────────────────────────────────────┤
│ Audience: Module implementers only │
│ Headers: module local (e.g., <module>/src/, include-private/) │
│ Namespace: typically in cudaq_internal::<module>::detail │
│ Naming: unconstrained; keep consistent within module │
└────────────────────────────────────────────────────────────────────┘
Each layer has rules for:
- include paths
- shipping requirements
- naming conventions
- namespace conventions
- compatibility expectations
The User API is cudaq supported public interface. It is the only API that
users and external libraries (e.g., cudaqx) are allowed to depend on.
- Primary entry header:
#include "cudaq.h"
- Additional user headers:
#include "cudaq/<subsystem>/<header>.h"- Example:
#include "cudaq/algorithms/run.h"
- File naming:
- lowercase file names
- All user-visible declarations live in
namespace cudaq { ... }. - Nested namespaces are considered public except:
cudaq::detail
Anything in detail is explicitly non-public and may change without notice.
- User API functions and objects use snake_case.
- Examples:
sample_async,sample_results
- Examples:
- The User API must maintain backward compatibility when feasible.
- Breaking changes require:
- a documented deprecation plan (deprecation schedule),
- migration guidance,
- and appropriate versioning/release notes.
Internal public module APIs are:
- not supported for end users, but
- public to internal developers because they are exported as part of a
module interface (e.g., via CMake
PUBLICheaders/libraries).
These APIs often must be shipped because user headers may include them, and
nvq++ must be able to find them.
Internal public headers must not live under the cudaq/ include root to
avoid confusion with the User API.
Proposed convention:
#include "cudaq_internal/<module_name>/<header_name>.h"
Rationale:
- clearly signals “not for users”
- avoids collision with user include hierarchy
- These headers are shipped with the product if they are reachable from
shipped user headers as required by
nvq++compilation. - They must be discoverable by
nvq++through configured include paths.
- Declarations live under a module namespace nested in
cudaq_internal:namespace cudaq_internal::<module_name> { ... }where<module_name>is lowercase Examples:cudaq_internal::compiler,cudaq_internal::device_code
- Nested namespaces follow the same visibility convention: they are public
except for the
detailnamespace.
- Preferred style for internal module APIs:
CamelCase. - If a specific module already has a strong existing convention, follow it consistently; avoid introducing new naming styles within the same module.
- Internal public module APIs are not user-stable.
- They may evolve as needed, but changes should still be managed responsibly
because:
- Hardware vendors and internal developers may depend on them,
- user headers may indirectly rely on them.
Internal private APIs are implementation details private to a module. They must not be consumed outside the module.
- Private headers live in a physical location separate from public headers
(e.g., a module
<module>/src/orinclude-private/tree). - They should not be in
nvq++default/public include search paths.
- Private headers are not shipped with the product with the exception of
header based implementation such as template meta-programming. In which case,
private APIs shall be in the
detailnamespace.
- Private headers are included using relative paths from within the module.
User code (supported):
#include "cudaq.h"
#include "cudaq/algorithms/run.h"Internal developer code (allowed for hardware vendors/core developers):
#include "cudaq_internal/compiler/lower.h"
#include "cudaq_internal/cudaq_fmt/Formatting.h"Module private include (module-only):
#include "LoweringPasses.h" // relative to module sourceUser API:
namespace cudaq {
void sample_async();
namespace detail {
// not public
}
}Internal module API:
namespace cudaq_internal::compiler {
class PassPipeline;
namespace detail {
// not public
}
}Internal modules shall be laid out according to the following structure:
runtime
├── internal
├── compiler
│ ├── CMakeLists.txt
│ ├── *.cpp
│ ├── include/cudaq_internal/compiler/*.h
├── device_code
│ ├── CMakeLists.txt
│ ├── *.cpp
│ ├── include/cudaq_internal/device_code/*.h
Most noticeably, header files shall not be under a common include root.
This is so that adding a dependency to a given module, using CMake target_link_libraries,
only gives visibility to the public headers of that module.
The concern for nvq++ users is different. The expectation is that nvq++ finds
automatically all the required headers, even those from internal modules
included transitively. We do not know ahead of time which headers will be used,
and we do not expect users to provide this information, so we have to assume
that all headers could be used.
CMake's INSTALL_INTERFACE and BUILD_INTERFACE will be leveraged to meet these
requirements. They allow for a different header file layout for build and install
environment.
Example:
target_include_directories(cudaq-mlir-runtime
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/runtime/internal/compiler/include>
PRIVATE .)This documented primarily aims at defining conventions on APIs and modules. Of particular relevance are the following rules from the LLVM coding standard that we shall strive to follow.