Skip to content
Merged
Show file tree
Hide file tree
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
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,53 @@ jobs:
working-directory: ${{github.workspace}}/build/src/examples
run: $GITHUB_WORKSPACE/.github/run_examples.sh

# Job testing clang with C++23, libc++ and MDSPAN
# ================================================
Linux_Clang_CXX23_MDSPAN:
runs-on: ubuntu-24.04
env:
CC: clang-21
CXX: clang++-21

steps:
- uses: actions/checkout@v5
with:
submodules: true

- name: "Add LLVM repository"
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/llvm-snapshot.gpg
echo "deb [signed-by=/usr/share/keyrings/llvm-snapshot.gpg] http://apt.llvm.org/noble/ llvm-toolchain-noble-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list

- name: "Install libraries"
run: |
sudo apt-get -qq update
sudo apt-get -qq install clang-21 libc++-21-dev libc++abi-21-dev libhdf5-dev libsz2 ninja-build

- name: Build
run: |
CMAKE_OPTIONS=(
-GNinja
-DCMAKE_CXX_STANDARD=23
-DHIGHFIVE_USE_LIBCXX:BOOL=ON
-DHIGHFIVE_TEST_MDSPAN:BOOL=ON
)
source $GITHUB_WORKSPACE/.github/build.sh

- name: Test
working-directory: ${{github.workspace}}/build
run: |
ctest -j2 --output-on-failure -C $BUILD_TYPE

- name: Test No HDF5 Diagnositics
working-directory: ${{github.workspace}}/build
run: |
! ctest --verbose -C $BUILD_TYPE | grep HDF5-DIAG

- name: Examples
working-directory: ${{github.workspace}}/build/src/examples
run: $GITHUB_WORKSPACE/.github/run_examples.sh

# Job running unit-test with sanitizers
# =====================================
Linux_Sanitizers:
Expand Down
16 changes: 16 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,26 @@ else()
set(HIGHFIVE_TEST_SPAN_DEFAULT Off)
endif()

if (CMAKE_CXX_STANDARD GREATER_EQUAL 23)
include(CheckIncludeFileCXX)
CHECK_INCLUDE_FILE_CXX(mdspan HIGHFIVE_TEST_MDSPAN_DEFAULT)
else()
set(HIGHFIVE_TEST_MDSPAN_DEFAULT Off)
endif()

option(HIGHFIVE_UNIT_TESTS "Compile unit-tests" ${HIGHFIVE_EXTRAS_DEFAULT})
option(HIGHFIVE_EXAMPLES "Compile examples" ${HIGHFIVE_EXTRAS_DEFAULT})
option(HIGHFIVE_BUILD_DOCS "Build documentation" ${HIGHFIVE_EXTRAS_DEFAULT})

option(HIGHFIVE_TEST_SPAN "Enable testing std::span, requires C++20" ${HIGHFIVE_TEST_SPAN_DEFAULT})
option(HIGHFIVE_TEST_MDSPAN "Enable testing std::mdspan, requires C++23 and libc++" ${HIGHFIVE_TEST_MDSPAN_DEFAULT})
option(HIGHFIVE_TEST_BOOST "Enable testing Boost features" OFF)
option(HIGHFIVE_TEST_BOOST_SPAN "Additionally, enable testing `boost::span`" OFF)
option(HIGHFIVE_TEST_EIGEN "Enable testing Eigen" OFF)
option(HIGHFIVE_TEST_OPENCV "Enable testing OpenCV" OFF)
option(HIGHFIVE_TEST_XTENSOR "Enable testing xtensor" OFF)
option(HIGHFIVE_TEST_HALF_FLOAT "Enable testing half-precision floats" OFF)
option(HIGHFIVE_USE_LIBCXX "Use libc++ for tests/examples (clang only)" OFF)

set(HIGHFIVE_MAX_ERRORS 0 CACHE STRING "Maximum number of compiler errors.")
option(HIGHFIVE_HAS_WERROR "Convert warnings to errors." OFF)
Expand All @@ -91,6 +100,10 @@ if(CMAKE_CXX_STANDARD EQUAL 98 OR CMAKE_CXX_STANDARD LESS ${HIGHFIVE_CXX_STANDAR
message(FATAL_ERROR "HighFive needs to be compiled with at least C++${HIGHFIVE_CXX_STANDARD_DEFAULT}")
endif()

if(HIGHFIVE_TEST_MDSPAN AND CMAKE_CXX_STANDARD LESS 23)
message(FATAL_ERROR "HIGHFIVE_TEST_MDSPAN requires C++23 or newer, but CMAKE_CXX_STANDARD is ${CMAKE_CXX_STANDARD}")
endif()

add_compile_definitions(HIGHFIVE_CXX_STD=${CMAKE_CXX_STANDARD})

# HighFive
Expand Down Expand Up @@ -175,6 +188,9 @@ if(HIGHFIVE_EXAMPLES OR HIGHFIVE_UNIT_TESTS)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HighFiveOptionalDependencies.cmake)
endif()

# Include libc++ configuration for examples and tests
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HighFiveLibCXX.cmake)

if(HIGHFIVE_EXAMPLES)
add_subdirectory(src/examples)
endif()
Expand Down
20 changes: 20 additions & 0 deletions cmake/HighFiveLibCXX.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# HighFiveLibCXX.cmake
# Sets up CMAKE_* variables to use libc++ with clang when HIGHFIVE_USE_LIBCXX is enabled

if(HIGHFIVE_USE_LIBCXX AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# Set compiler flags to use libc++
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")

# Set linker flags to use libc++
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -stdlib=libc++")


# Add necessary compile definitions for libc++
add_compile_definitions(_LIBCPP_VERSION)

message(STATUS "HighFive: Using libc++ for tests (clang only)")
elseif(HIGHFIVE_USE_LIBCXX AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
message(WARNING "HIGHFIVE_USE_LIBCXX is enabled but compiler is not clang. libc++ is only supported with clang.")
endif()
8 changes: 8 additions & 0 deletions cmake/HighFiveOptionalDependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ if(NOT TARGET HighFiveSpanDependency)
endif()
endif()

if(NOT TARGET HighFiveMdspanDependency)
add_library(HighFiveMdspanDependency INTERFACE)
if(HIGHFIVE_TEST_MDSPAN)
target_compile_definitions(HighFiveMdspanDependency INTERFACE HIGHFIVE_TEST_MDSPAN=1)
endif()
endif()

if(NOT TARGET HighFiveOptionalDependencies)
add_library(HighFiveOptionalDependencies INTERFACE)
target_link_libraries(HighFiveOptionalDependencies INTERFACE
Expand All @@ -60,5 +67,6 @@ if(NOT TARGET HighFiveOptionalDependencies)
HighFiveXTensorDependency
HighFiveOpenCVDependency
HighFiveSpanDependency
HighFiveMdspanDependency
)
endif()
167 changes: 167 additions & 0 deletions include/highfive/mdspan.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#pragma once

#include "bits/H5Inspector_decl.hpp"
#include "H5Exception.hpp"

#include <mdspan>
#include <vector>
#include <array>
#include <sstream>
#include <utility>
#include <type_traits>

namespace HighFive {
namespace details {

// Specialization for std::mdspan
template <class ElementType, class Extents, class LayoutPolicy, class AccessorPolicy>
struct inspector<std::mdspan<ElementType, Extents, LayoutPolicy, AccessorPolicy>> {
using type = std::mdspan<ElementType, Extents, LayoutPolicy, AccessorPolicy>;
using value_type = typename type::element_type;
using base_type = typename inspector<value_type>::base_type;
using hdf5_type = base_type;
using extents_type = typename type::extents_type;
using accessor_type = typename type::accessor_type;

static constexpr size_t ndim = type::rank();
static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;

static constexpr bool is_trivially_copyable =
std::is_trivially_copyable<value_type>::value &&
inspector<value_type>::is_trivially_nestable &&
(std::is_same_v<std::default_accessor<value_type>, accessor_type>
#ifdef __cpp_lib_aligned_accessor
|| std::is_same_v<std::aligned_accessor<value_type>, accessor_type>
#endif
) &&
(std::is_same_v<typename type::layout_type, std::layout_right> ||
(std::is_same_v<typename type::layout_type, std::layout_left> && ndim == 1));
static constexpr bool is_trivially_nestable = false;

private:
using index_type = typename extents_type::index_type;

// Helper to access the first element (at index 0 in all dimensions)
static auto get_first_element(const type& val) {
std::array<index_type, ndim> indices{};
return val[indices];
}

template <typename T>
static auto data_impl(T& val) -> decltype(inspector<value_type>::data(*val.data_handle())) {
if (!is_trivially_copyable) {
throw DataSetException("Invalid use of `inspector<std::mdspan<...>>::data`.");
}

if (val.empty()) {
return nullptr;
}

return inspector<value_type>::data(*val.data_handle());
}

public:
static size_t getRank(const type& val) {
if (val.empty()) {
return min_ndim;
}
return ndim + inspector<value_type>::getRank(get_first_element(val));
}

static std::vector<size_t> getDimensions(const type& val) {
std::vector<size_t> sizes;
sizes.reserve(ndim);
for (size_t r = 0; r < ndim; ++r) {
sizes.push_back(val.extent(r));
}
if (!val.empty()) {
auto s = inspector<value_type>::getDimensions(get_first_element(val));
sizes.insert(sizes.end(), s.begin(), s.end());
}
return sizes;
}

static void prepare(type& val, const std::vector<size_t>& dims) {
if (dims.size() < ndim) {
std::ostringstream os;
os << "Impossible to pair DataSet with " << dims.size()
<< " dimensions into an mdspan with rank " << ndim << ".";
throw DataSpaceException(os.str());
}

// Check that dimensions match
for (size_t r = 0; r < ndim; ++r) {
if (dims[r] != val.extent(r)) {
std::ostringstream os;
os << "Mismatching dimensions for mdspan: expected " << val.extent(r)
<< " for dimension " << r << ", but got " << dims[r] << ".";
throw DataSpaceException(os.str());
}
}
}

static hdf5_type* data(type& val) {
return data_impl(val);
}

static const hdf5_type* data(const type& val) {
return data_impl(val);
}

static void serialize(const type& val, const std::vector<size_t>& dims, hdf5_type* m) {
auto subdims = std::vector<size_t>(dims.begin() + ndim, dims.end());
auto subsize = compute_total_size(subdims);

std::array<index_type, ndim> indices{};
auto iterate = [&](auto& self, size_t dim) -> void {
if (dim == ndim) {
// Base case: serialize element
inspector<value_type>::serialize(val[indices], subdims, m);
m += subsize;
} else {
// Recursive case: iterate over current dimension
const auto n = static_cast<index_type>(val.extent(dim));
for (indices[dim] = 0; indices[dim] < n; ++indices[dim]) {
self(self, dim + 1);
}
}
};

iterate(iterate, 0);
}

static void unserialize(const hdf5_type* vec_align,
const std::vector<size_t>& dims,
type& val) {
if (dims.size() < ndim) {
std::ostringstream os;
os << "Impossible to pair DataSet with " << dims.size()
<< " dimensions into an mdspan with rank " << ndim << ".";
throw DataSpaceException(os.str());
}

auto subdims = std::vector<size_t>(dims.begin() + ndim, dims.end());
auto subsize = compute_total_size(subdims);

std::array<index_type, ndim> indices{};
auto iterate = [&](auto& self, size_t dim) -> void {
if (dim == ndim) {
// Base case: unserialize element
inspector<value_type>::unserialize(vec_align, subdims, val[indices]);
vec_align += subsize;
} else {
// Recursive case: iterate over current dimension
const auto n = static_cast<index_type>(dims[dim]);
for (indices[dim] = 0; indices[dim] < n; ++indices[dim]) {
self(self, dim + 1);
}
}
};

iterate(iterate, 0);
}
};

} // namespace details
} // namespace HighFive
10 changes: 10 additions & 0 deletions src/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ set(span_examples
${CMAKE_CURRENT_SOURCE_DIR}/read_write_std_span.cpp
)

set(mdspan_examples
${CMAKE_CURRENT_SOURCE_DIR}/read_write_std_mdspan.cpp
)

set(easy_examples
${CMAKE_CURRENT_SOURCE_DIR}/easy_attribute.cpp
${CMAKE_CURRENT_SOURCE_DIR}/easy_dumpoptions.cpp
Expand Down Expand Up @@ -85,6 +89,12 @@ if(HIGHFIVE_TEST_SPAN)
endforeach()
endif()

if(HIGHFIVE_TEST_MDSPAN)
foreach(example_source ${mdspan_examples})
compile_example(${example_source} HighFiveFlags)
endforeach()
endif()

if(HIGHFIVE_TEST_BOOST)
foreach(example_source ${boost_examples})
compile_example(${example_source} HighFiveFlags HighFiveBoostDependency)
Expand Down
Loading
Loading