Skip to content

Instantly share code, notes, and snippets.

@kylehaokunwu
Created September 1, 2025 14:47
Show Gist options
  • Select an option

  • Save kylehaokunwu/431efd647efb9393494b432299b1dee7 to your computer and use it in GitHub Desktop.

Select an option

Save kylehaokunwu/431efd647efb9393494b432299b1dee7 to your computer and use it in GitHub Desktop.
GSoC 2025 Final Report – Exposing HPX with C++20 Modules
gsoc-final-report.md
---
# GSoC 2025 Final Report
**Project**: Exposing HPX using C++20 Modules
**Contributor**: Haokun Wu
**Org**: STE||AR Group
**Primary PR**: [https://github.com/STEllAR-GROUP/hpx/pull/6761](https://github.com/STEllAR-GROUP/hpx/pull/6761)
## Project goals
Move HPX toward C++20 modules while keeping backward compatibility:
* Existing users can keep `#include <hpx/...>` with no changes.
* Users can alternatively write `import HPX.Core;` and use the same APIs.
Secondary goals:
1. export both template and non-template symbols correctly;
2. establish minimal, maintainable build/macro mechanics to expose more libraries over time.
---
## What I did
### 1) Design choice: umbrella module, not 1:1 copies
A common approach is duplicating headers into `.ixx` files. That adds maintenance overhead. Instead, each HPX library gets a single umbrella `.ixx` that re-exports its headers. For core:
```cpp
// libs/core/hpx_core.ixx
module; // global fragment
#include <algorithm>
#include <string>
#include <cstdint>
#define HPX_BUILD_MODULE
#include <hpx/config.hpp>
export module HPX.Core;
// headers that define the public surface
#include <hpx/modules/version.hpp>
```
This keeps the tree lean, preserves header inclusion, and allows `import HPX.Core;`.
### 2) Symbol export mechanics (templates vs non-templates)
Header builds rely on `HPX_CORE_EXPORT` (DLL/export visibility). For modules:
* Templates can be exported with `export`.
* Non-templates must use `export extern "C++"` so the linker finds the compiled symbol.
* To avoid double attributes, when building a module we undefine and empty out `HPX_CORE_EXPORT`.
Minimal macro surface (from `export_definitions.hpp`):
```cpp
#if defined(HPX_BUILD_MODULE)
# if defined(HPX_HAVE_ELF_HIDDEN_VISIBILITY)
# undef HPX_CORE_EXPORT
# define HPX_CORE_EXPORT /* empty */
# endif
# define HPX_CORE_MODULE_EXPORT_EXTERN export extern "C++"
#else
# define HPX_CORE_MODULE_EXPORT_EXTERN extern "C++"
#endif
#define HPX_CORE_MODULE_EXPORT \
HPX_CORE_MODULE_EXPORT_EXTERN HPX_CORE_EXPORT
```
Use `HPX_CORE_MODULE_EXPORT` in place of `HPX_CORE_EXPORT` when a declaration is exposed through a module.
**Before (headers):**
```cpp
HPX_CORE_EXPORT std::uint8_t agas_version();
```
**After (module):**
```cpp
HPX_CORE_MODULE_EXPORT std::string build_date_time();
```
### 3) CMake integration (two key pieces)
**a) Register the `.ixx` as a C++ module interface and install BMIs**
Essentials only (from `libs/CMakeLists.txt`):
```cmake
if(HPX_WITH_CXX_MODULES)
target_sources(hpx_${lib}
PUBLIC FILE_SET hpx_${lib}_public_sources TYPE CXX_MODULES FILES
${CMAKE_CURRENT_SOURCE_DIR}/${lib}/hpx_${lib}.ixx
)
set(module_installation
FILE_SET hpx_${lib}_public_sources DESTINATION ${CMAKE_INSTALL_LIBDIR}/cxx/miu
CXX_MODULES_BMI DESTINATION ${CMAKE_INSTALL_LIBDIR}/cxx/bmi
COMPONENT runtime
)
endif()
install(TARGETS hpx_${lib}
EXPORT HPXInternalTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT runtime
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT runtime
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
${module_installation}
)
```
What this does:
* declares the `.ixx` as a `CXX_MODULES` file set so compilers produce BMIs;
* installs the module interface units and BMIs alongside libraries for consumers.
**b) Generated global header chooses `#include` or `import` at build time**
Essentials only (from `cmake/templates/global_module_header_modules.hpp.in`):
```cpp
#pragma once
#include <hpx/config/defines.hpp>
#if !defined(HPX_HAVE_CXX_MODULES) || \
defined(HPX_APPLICATION_DOESNT_USE_CXX_MODULES) || \
defined(HPX_CORE_EXPORTS) || defined(HPX_FULL_EXPORTS) || \
defined(HPX_LIBRARY_EXPORTS) || defined(HPX_COMPONENT_EXPORTS) || \
defined(HPX_BUILD_MODULE)
@module_headers@ // expands to traditional #includes
#else
import HPX.Core; // module path when consumers opt into modules
#endif
```
Effect:
* Libraries building HPX itself (or consumers that opt out) keep using `#include`.
* Consumers with modules enabled transparently switch to `import HPX.Core;`.
* Tests and examples are kept untouched and CI passed on both modes
---
## Current state
* `HPX.Core` umbrella module (`libs/core/hpx_core.ixx`) added.
* Version API exposed through modules with the new macros.
* Build system produces and installs module interfaces and BMIs.
* Backward compatibility validated: headers continue to work; `import HPX.Core;` also works.
* Tested across major compilers used in CI for the PR.
---
## Challenges and lessons
* Backward compatibility drives the shape of both macros and build logic.
* Non-template exports require `export extern "C++"` to yield linkable symbols.
* System headers must stay in the global module fragment to avoid ODR and diagnostic issues.
* Avoiding duplication with the umbrella pattern simplifies maintenance while enabling gradual migration.
---
## Next steps
* Continue exposing additional `libs/core` modules using the same pattern.
* Tighten CMake integration so newly exposed modules require minimal boilerplate.
* Measure build and runtime effects of imports vs includes in representative HPX applications.
---
## Code and references
* Main work: PR #6761 — includes `hpx_core.ixx`, macro updates in `export_definitions.hpp`, version API changes, and CMake additions.
---
## Conclusion
This work establishes a module-aware export scheme, an umbrella interface for `HPX.Core`, and CMake plumbing that installs module interfaces and BMIs. Most importantly, it preserves backward compatibility: existing `#include` code continues to compile unchanged, and users can opt in to `import HPX.Core;`. I will keep extending module coverage across HPX using this approach.
---
## Acknowledgments
I would like to thank my mentors, Dr. Hartmut Kaiser and Panos Papadopoulos, for their guidance and feedback throughout the project. Their expertise in both C++ and HPX design decisions was crucial for navigating the challenges of symbol export, module boundaries, and build system integration. I also thank the STE||AR Group community for code reviews, discussions, and support during GSoC.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment