Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HDF5IOHandler: Support for float128 on ARM64/PPC64 #1364

Merged
Merged
Show file tree
Hide file tree
Changes from 5 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
10 changes: 10 additions & 0 deletions include/openPMD/IO/HDF5/HDF5IOHandlerImpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ class HDF5IOHandlerImpl : public AbstractIOHandlerImpl
hid_t m_H5T_CFLOAT;
hid_t m_H5T_CDOUBLE;
hid_t m_H5T_CLONG_DOUBLE;
/* See https://github.com/openPMD/openPMD-api/issues/1363
* long double values written on AMD64 architectures cannot be read on
* ARM64/PPC64 architectures without doing some special tricks.
* We generally don't implement custom conversions, but instead pass-through
* the cross-platform support of HDF5.
* But this case is common and important enough to warrant a custom
* workaround.
*/
hid_t m_H5T_LONG_DOUBLE_80_LE;
hid_t m_H5T_CLONG_DOUBLE_80_LE;

protected:
#if openPMD_HAVE_MPI
Expand Down
150 changes: 146 additions & 4 deletions src/IO/HDF5/HDF5IOHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "openPMD/auxiliary/Filesystem.hpp"
#include "openPMD/auxiliary/Mpi.hpp"
#include "openPMD/auxiliary/StringManip.hpp"
#include "openPMD/auxiliary/TypeTraits.hpp"
#include "openPMD/backend/Attribute.hpp"

#include <hdf5.h>
Expand Down Expand Up @@ -73,6 +74,8 @@ HDF5IOHandlerImpl::HDF5IOHandlerImpl(
, m_H5T_CFLOAT{H5Tcreate(H5T_COMPOUND, sizeof(float) * 2)}
, m_H5T_CDOUBLE{H5Tcreate(H5T_COMPOUND, sizeof(double) * 2)}
, m_H5T_CLONG_DOUBLE{H5Tcreate(H5T_COMPOUND, sizeof(long double) * 2)}
, m_H5T_LONG_DOUBLE_80_LE{H5Tcopy(H5T_IEEE_F64BE)}
, m_H5T_CLONG_DOUBLE_80_LE{H5Tcreate(H5T_COMPOUND, 16 * 2)}
{
// create a h5py compatible bool type
VERIFY(
Expand Down Expand Up @@ -107,6 +110,24 @@ HDF5IOHandlerImpl::HDF5IOHandlerImpl(
H5Tinsert(m_H5T_CLONG_DOUBLE, "r", 0, H5T_NATIVE_LDOUBLE);
H5Tinsert(m_H5T_CLONG_DOUBLE, "i", sizeof(long double), H5T_NATIVE_LDOUBLE);

H5Tset_size(m_H5T_LONG_DOUBLE_80_LE, 16);
ax3l marked this conversation as resolved.
Show resolved Hide resolved
H5Tset_order(m_H5T_LONG_DOUBLE_80_LE, H5T_ORDER_LE);
H5Tset_precision(m_H5T_LONG_DOUBLE_80_LE, 80);
H5Tset_fields(m_H5T_LONG_DOUBLE_80_LE, 79, 64, 15, 0, 64);
H5Tset_ebias(m_H5T_LONG_DOUBLE_80_LE, 16383);
H5Tset_norm(m_H5T_LONG_DOUBLE_80_LE, H5T_NORM_NONE);

VERIFY(
m_H5T_LONG_DOUBLE_80_LE >= 0,
"[HDF5] Internal error: Failed to create 128-bit long double");

H5Tinsert(m_H5T_CLONG_DOUBLE_80_LE, "r", 0, m_H5T_LONG_DOUBLE_80_LE);
H5Tinsert(m_H5T_CLONG_DOUBLE_80_LE, "i", 16, m_H5T_LONG_DOUBLE_80_LE);

VERIFY(
m_H5T_LONG_DOUBLE_80_LE >= 0,
"[HDF5] Internal error: Failed to create 128-bit complex long double");

m_chunks = auxiliary::getEnvString("OPENPMD_HDF5_CHUNKS", "auto");
// JSON option can overwrite env option:
if (config.json().contains("hdf5"))
Expand Down Expand Up @@ -188,6 +209,14 @@ HDF5IOHandlerImpl::~HDF5IOHandlerImpl()
std::cerr
<< "[HDF5] Internal error: Failed to close complex double type\n";
status = H5Tclose(m_H5T_CLONG_DOUBLE);
if (status < 0)
std::cerr << "[HDF5] Internal error: Failed to close complex long "
"double type\n";
status = H5Tclose(m_H5T_LONG_DOUBLE_80_LE);
if (status < 0)
std::cerr
<< "[HDF5] Internal error: Failed to close long double type\n";
status = H5Tclose(m_H5T_CLONG_DOUBLE_80_LE);
if (status < 0)
std::cerr << "[HDF5] Internal error: Failed to close complex long "
"double type\n";
Expand Down Expand Up @@ -1005,13 +1034,17 @@ void HDF5IOHandlerImpl::openDataset(
d = DT::FLOAT;
else if (H5Tequal(dataset_type, H5T_NATIVE_DOUBLE))
d = DT::DOUBLE;
else if (H5Tequal(dataset_type, H5T_NATIVE_LDOUBLE))
else if (
H5Tequal(dataset_type, H5T_NATIVE_LDOUBLE) ||
H5Tequal(dataset_type, m_H5T_LONG_DOUBLE_80_LE))
d = DT::LONG_DOUBLE;
else if (H5Tequal(dataset_type, m_H5T_CFLOAT))
d = DT::CFLOAT;
else if (H5Tequal(dataset_type, m_H5T_CDOUBLE))
d = DT::CDOUBLE;
else if (H5Tequal(dataset_type, m_H5T_CLONG_DOUBLE))
else if (
H5Tequal(dataset_type, m_H5T_CLONG_DOUBLE) ||
H5Tequal(dataset_type, m_H5T_CLONG_DOUBLE_80_LE))
d = DT::CLONG_DOUBLE;
else if (H5Tequal(dataset_type, H5T_NATIVE_USHORT))
d = DT::USHORT;
Expand Down Expand Up @@ -1760,6 +1793,28 @@ void HDF5IOHandlerImpl::readDataset(
{typeid(std::complex<long double>).name(), m_H5T_CLONG_DOUBLE},
});
hid_t dataType = getH5DataType(a);
if (H5Tequal(dataType, H5T_NATIVE_LDOUBLE))
{
// We have previously determined in openDataset() that this dataset is
// of type long double.
// We cannot know if that actually was H5T_NATIVE_LDOUBLE or if it was
// the worked-around m_H5T_LONG_DOUBLE_80_LE.
// Check this.
hid_t checkDatasetTypeAgain = H5Dget_type(dataset_id);
ax3l marked this conversation as resolved.
Show resolved Hide resolved
if (!H5Tequal(checkDatasetTypeAgain, H5T_NATIVE_LDOUBLE))
{
dataType = m_H5T_LONG_DOUBLE_80_LE;
}
}
else if (H5Tequal(dataType, m_H5T_CLONG_DOUBLE))
{
// Same deal for m_H5T_CLONG_DOUBLE
hid_t checkDatasetTypeAgain = H5Dget_type(dataset_id);
if (!H5Tequal(checkDatasetTypeAgain, m_H5T_CLONG_DOUBLE))
{
dataType = m_H5T_CLONG_DOUBLE_80_LE;
}
}
VERIFY(
dataType >= 0,
"[HDF5] Internal error: Failed to get HDF5 datatype during dataset "
Expand Down Expand Up @@ -1951,6 +2006,14 @@ void HDF5IOHandlerImpl::readAttribute(
status = H5Aread(attr_id, attr_type, &l);
a = Attribute(l);
}
else if (H5Tequal(attr_type, m_H5T_LONG_DOUBLE_80_LE))
{
char bfr[16];
status = H5Aread(attr_id, attr_type, bfr);
H5Tconvert(
attr_type, H5T_NATIVE_LDOUBLE, 1, bfr, nullptr, H5P_DEFAULT);
a = Attribute(reinterpret_cast<long double *>(bfr)[0]);
}
else if (H5Tget_class(attr_type) == H5T_STRING)
{
if (H5Tis_variable_str(attr_type))
Expand Down Expand Up @@ -2069,6 +2132,20 @@ void HDF5IOHandlerImpl::readAttribute(
status = H5Aread(attr_id, attr_type, &cld);
a = Attribute(cld);
}
else if (complexSize == 16)
{
char bfr[2 * 16];
status = H5Aread(attr_id, attr_type, bfr);
H5Tconvert(
attr_type,
m_H5T_CLONG_DOUBLE,
1,
bfr,
nullptr,
H5P_DEFAULT);
a = Attribute(
reinterpret_cast<std::complex<long double> *>(bfr)[0]);
}
else
throw error::ReadError(
error::AffectedObject::Attribute,
Expand All @@ -2088,7 +2165,8 @@ void HDF5IOHandlerImpl::readAttribute(
error::AffectedObject::Attribute,
error::Reason::UnexpectedContent,
"HDF5",
"[HDF5] Unsupported scalar attribute type");
"[HDF5] Unsupported scalar attribute type for '" + attr_name +
"'.");
}
else if (attr_class == H5S_SIMPLE)
{
Expand Down Expand Up @@ -2210,6 +2288,42 @@ void HDF5IOHandlerImpl::readAttribute(
status = H5Aread(attr_id, attr_type, vcld.data());
a = Attribute(vcld);
}
else if (H5Tequal(attr_type, m_H5T_CLONG_DOUBLE_80_LE))
{
// worst case, sizeof(long double) is only 8, so allocate enough
// memory to fit 16 bytes per member
auto *tmpBuffer =
static_cast<long double *>(malloc(16 * 2 * dims[0]));
Copy link
Member

Choose a reason for hiding this comment

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

In C++, I would use new and delete over malloc and free.

Copy link
Contributor

Choose a reason for hiding this comment

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

This will need a reinterpret_cast then though, as you can't new a void pointer. But I'll push a commit.

Copy link
Member

Choose a reason for hiding this comment

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

We found today: can be new long double 😅

Copy link
Contributor

Choose a reason for hiding this comment

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

It turns out, we cannot 🙃
This branch is active when sizeof(long double) == 8, but the dataset on disk has 16-byte doubles. There is no way to allocate a correctly-sized buffer using native float types, so new char[] it is.

status = H5Aread(attr_id, attr_type, tmpBuffer);
H5Tconvert(
attr_type,
m_H5T_CLONG_DOUBLE,
dims[0],
tmpBuffer,
nullptr,
H5P_DEFAULT);
std::vector<std::complex<long double> > vcld{
tmpBuffer, tmpBuffer + dims[0]};
free(tmpBuffer);
a = Attribute(std::move(vcld));
}
else if (H5Tequal(attr_type, m_H5T_LONG_DOUBLE_80_LE))
{
// use malloc to allocate a buffer that is definitely aliased and
// big enough for 16-bit doubles
auto *tmpBuffer = static_cast<long double *>(malloc(16 * dims[0]));
status = H5Aread(attr_id, attr_type, tmpBuffer);
H5Tconvert(
attr_type,
H5T_NATIVE_LDOUBLE,
dims[0],
tmpBuffer,
nullptr,
H5P_DEFAULT);
std::vector<long double> vld80{tmpBuffer, tmpBuffer + dims[0]};
free(tmpBuffer);
a = Attribute(std::move(vld80));
}
else if (H5Tget_class(attr_type) == H5T_STRING)
{
std::vector<std::string> vs;
Expand Down Expand Up @@ -2244,11 +2358,39 @@ void HDF5IOHandlerImpl::readAttribute(
a = Attribute(vs);
}
else
{
auto order = H5Tget_order(attr_type);
auto prec = H5Tget_precision(attr_type);
auto ebias = H5Tget_ebias(attr_type);
size_t spos, epos, esize, mpos, msize;
H5Tget_fields(attr_type, &spos, &epos, &esize, &mpos, &msize);

auto norm = H5Tget_norm(attr_type);
auto cset = H5Tget_cset(attr_type);
auto sign = H5Tget_sign(attr_type);

std::stringstream detailed_info;
detailed_info << "order " << std::to_string(order) << std::endl
<< "prec " << std::to_string(prec) << std::endl
<< "ebias " << std::to_string(ebias) << std::endl
<< "fields " << std::to_string(spos) << " "
<< std::to_string(epos) << " "
<< std::to_string(esize) << " "
<< std::to_string(mpos) << " "
<< std::to_string(msize) << "norm "
<< std::to_string(norm) << std::endl
<< "cset " << std::to_string(cset) << std::endl
<< "sign " << std::to_string(sign) << std::endl
<< std::endl;

throw error::ReadError(
error::AffectedObject::Attribute,
error::Reason::UnexpectedContent,
"HDF5",
"[HDF5] Unsupported simple attribute type");
"[HDF5] Unsupported simple attribute type " +
std::to_string(attr_type) + " for " + attr_name +
".\n(Info for debugging: " + detailed_info.str() + ")");
}
}
else
throw std::runtime_error("[HDF5] Unsupported attribute class");
Expand Down