Skip to content

Instantly share code, notes, and snippets.

@kylehaokunwu
Created September 1, 2025 15:45
Show Gist options
  • Select an option

  • Save kylehaokunwu/38e9c50aecadf20bfad633f9401e37af to your computer and use it in GitHub Desktop.

Select an option

Save kylehaokunwu/38e9c50aecadf20bfad633f9401e37af to your computer and use it in GitHub Desktop.
GSoC 2025 Final Report – Exposing HPX with C++20 Modules

GSoC 2025 Final Report

GSoC Logo HPX Logo

Project: Exposing HPX using C++20 Modules
Contributor: Haokun Wu
Org: STE||AR Group
Primary PR: STEllAR-GROUP/hpx#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.

HPX is a large and template-heavy codebase, so exposing everything in one summer is unrealistic. Instead, this project focused on solving the hard design problems (exporting symbols correctly, avoiding ODR violations, macro migration, etc.), and delivering a working proof-of-concept: the version module.

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:

// 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):

#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):

HPX_CORE_EXPORT std::uint8_t agas_version();

After (module):

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):

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):

#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 Panagiotis Syskakis, 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