Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 269 additions & 0 deletions specs/cpp-17-migration/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
# Migrate project to C++17 standard and reduce complexity

GitHub Issue: https://github.com/AcademySoftwareFoundation/openqmc/issues/86

## Summary

Migrate OpenQMC from C++14 to C++17 to eliminate the binary/header-only
install split and reduce build system complexity. C++17 `inline` variables
remove the need for the `OQMC_ENABLE_BINARY` mechanism entirely, allowing the
library to remain header-only without duplicating large static arrays across
compilation units.

## Motivation

OpenQMC includes six large blue noise lookup tables (key and rank tables for
PMJ, Sobol, and Lattice samplers) defined in `include/oqmc/bntables.h`. Each
table is 2^16 entries of `uint32_t`, totalling roughly 1.5 MB of data across
all six.

In C++14, `constexpr` namespace-scope variables have internal linkage. When
`bntables.h` is included from multiple translation units, each unit gets its
own copy of the table data in the final binary. To avoid this, the project
offers a binary variant controlled by the `OPENQMC_ENABLE_BINARY` CMake
option. When enabled, the tables are declared `extern` in the header and
defined once in `src/bntables.cpp`, which is compiled into a static or shared
library.

This creates user-facing complexity:

- Three CMake options (`OPENQMC_ENABLE_BINARY`, `OPENQMC_SHARED_LIB`,
`OPENQMC_FORCE_PIC`) that exist solely for this purpose.
- Conditional `INTERFACE` vs `STATIC`/`SHARED` library target logic in
`src/CMakeLists.txt`.
- A preprocessor split (`#if defined(OQMC_ENABLE_BINARY)`) in `bntables.h`.
- Documentation explaining the trade-off between header-only and binary
installs.

C++17 introduces `inline` variable declarations. An `inline constexpr`
namespace-scope variable has external linkage and is guaranteed to have a
single definition across all translation units. This directly solves the
duplication problem with no build system workaround required.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Worth noting that OPENQMC_ENABLE_BINARY was solving two problems at once: C++14's internal-linkage duplication in the final binary, and having each TU parse ~393K lines of integer literals. C++17 inline constexpr fixes the duplication but reintroduces the per-TU parse cost. The spec should measure this on a representative consumer and accept the trade-off explicitly.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is great feedback. A really good point about the compiler time. I'll see if we can setup a test to verify the effect this change would have on this.


## Changes — Headers

Replace the conditional compilation in `include/oqmc/bntables.h`. The current
pattern for each sampler namespace (repeated for `pmj`, `sobol`, `lattice`):

```cpp
#if defined(OQMC_ENABLE_BINARY)
extern const std::uint32_t keyTable[size];
extern const std::uint32_t rankTable[size];
#else
constexpr std::uint32_t keyTable[] = {
#include "data/pmj/keys.txt"
};
constexpr std::uint32_t rankTable[] = {
#include "data/pmj/ranks.txt"
};
#endif
```

Becomes:

```cpp
inline constexpr std::uint32_t keyTable[] = {
#include "data/pmj/keys.txt"
};
inline constexpr std::uint32_t rankTable[] = {
#include "data/pmj/ranks.txt"
};
```

The `#if defined(OQMC_ENABLE_BINARY)` blocks and corresponding `#else` /
`#endif` are removed entirely.

## Changes — Source code

### `src/bntables.cpp`

Delete this file. It only exists to provide `extern` definitions for the
binary variant.

## Changes — Build system

### `CMakeLists.txt` (root)

Remove the following options:

```cmake
option(OPENQMC_ENABLE_BINARY "Build binary to reduce memory cost.")
option(OPENQMC_SHARED_LIB "Make a shared library, in place of static.")
option(OPENQMC_FORCE_PIC "Force PIC for static libraries.")
```

### `src/CMakeLists.txt`

Replace the conditional library creation:

```cmake
if(OPENQMC_ENABLE_BINARY)
if(OPENQMC_SHARED_LIB)
add_library(${PROJECT_NAME} SHARED)
else()
add_library(${PROJECT_NAME} STATIC)
endif()

if(OPENQMC_FORCE_PIC)
set_target_properties(${PROJECT_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
endif()

target_sources(${PROJECT_NAME} PRIVATE bntables.cpp)
target_compile_options(${PROJECT_NAME} PRIVATE ${OPENQMC_CXX_FLAGS})
target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include)
else()
add_library(${PROJECT_NAME} INTERFACE)
endif()
```

With:

```cmake
add_library(${PROJECT_NAME} INTERFACE)
```

Remove the `OQMC_ENABLE_BINARY` compile definition block:

```cmake
if(OPENQMC_ENABLE_BINARY)
target_compile_definitions(${PROJECT_NAME} INTERFACE OQMC_ENABLE_BINARY)
endif()
```

Change the C++ standard requirement:

```cmake
# Before
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_14)

# After
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17)
```

### `CMakePresets.json`

The `unix` preset sets `CMAKE_CXX_STANDARD` to `"14"` (line 18). Update to
`"17"`.

### `cmake/examples/`

Update `cxx_std_14` to `cxx_std_17` in the manual include example.

## Changes — Documentation

### `README.md`

- Change "This C++14 (CPU, GPU) header only library" to reference C++17.
- Update the requirements section: change "no dependencies other than C++14"
to C++17 and adjust VFX Reference Platform compatibility to CY2021.
- Remove or update references to the binary install variant and its CMake
options throughout the document.

Update minimum tested compiler versions:

- **GCC**: GCC 7 is the minimum requirement for C++17. No change needed.
- **Clang**: Clang 5 is the minimum requirement for C++17. No change needed.
- **NVCC**: Minimum version moves from NVCC 10 to NVCC 11. Update tested
compiler documentation and CI matrix accordingly.

### `mkdocs/home.html`

- Update "Header-only C++14 library" and "CY2018" references on line 77.

## Migration risks

- **Downstream breakage**: Projects using `OPENQMC_ENABLE_BINARY`,
`OPENQMC_SHARED_LIB`, or `OPENQMC_FORCE_PIC` will see CMake errors on
upgrade. This is an intentional breaking change. A CHANGELOG entry and
migration note should accompany the release.
- **VFX Reference Platform**: This migration moves the minimum from CY2018
to CY2021.

## Candidate C++17 improvements

Beyond the core `inline constexpr` change, the following are candidate
improvements enabled by C++17 that could be included in this migration or
follow-up work.

### Hex float literals

`include/oqmc/float.h` line 34:

```cpp
// Before
constexpr auto floatOneOverTwoPower32 = 1.0f / (1ull << 32); ///< 0x1p-32

// After
constexpr auto floatOneOverTwoPower32 = 0x1p-32f;
```

Hex float literals (P3.9) are a C++17 feature. This replaces a computed
constant with an exact representation, removing the reliance on compile-time
evaluation of the division.

### `[[maybe_unused]]` attribute

The project defines a custom `OQMC_MAYBE_UNUSED` macro in
`include/oqmc/unused.h` (line 11) to suppress unused variable warnings:

```cpp
#define OQMC_MAYBE_UNUSED(exp) (void)(exp)
```

This is used in 6 locations across the codebase:

- `include/oqmc/stochastic.h` line 36 — `maxIndexSize`
- `include/oqmc/lattice.h` lines 51, 62 — `cache` parameter
- `include/oqmc/sobol.h` lines 51, 62 — `cache` parameter
- `src/shapes.h` lines 95, 110 — `x` and `y` parameters

C++17 provides `[[maybe_unused]]` as a standard attribute. All uses can be
replaced, and `unused.h` can be deleted. For example:

```cpp
// Before
OQMC_MAYBE_UNUSED(cache);

// After (on parameter)
[[maybe_unused]] const void* cache
```

### Nested namespace declarations

`include/oqmc/bntables.h` uses nested namespaces with separate declarations:

```cpp
namespace oqmc
{
namespace bntables
{
namespace pmj
{
```

C++17 allows compact nested namespace syntax:

```cpp
namespace oqmc::bntables::pmj
{
```

However, care should be taken to remain consistent with the rest of the
codebase. If adopted, it should apply uniformly (e.g. `bntables.h` uses 3
levels of nesting, while most other files use 1-2). This is a low-priority
stylistic change.

### `if constexpr`

`include/oqmc/owen.h` line 51 uses a runtime `if` to check dimension 0:

```cpp
if(dimension == 0)
{
return reverseBits16(index);
}
```

This could become `if constexpr` if the function were templated on
`dimension`. However, this would be an API change and the compiler likely
optimises this already. Low priority.
Loading