Skip to content

Commit e7d7a4b

Browse files
skalarproduktraumpre-commit-ci[bot]franzpoeschel
authored
HDF5IOHandler: Support for float128 on ARM64/PPC64 (#1364)
* HDF5IOHandler: Support for float128 data types with 80bit precision on ARM64/PPC64 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Cleanup Unify little/big endian, use double80 also in other places Long double size 8 -> 16 Use malloc to avoid alignment issues Same treatment for complex long double Add this for readAttribute too Avoid non-native datatypes in writing * Make this fix little-endian only * Add comment * Suggestions from review * Use new instead of malloc everywhere Also, add a more explanative comment --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Franz Pöschel <franz.poeschel@gmail.com>
1 parent 5e89ef9 commit e7d7a4b

File tree

2 files changed

+177
-4
lines changed

2 files changed

+177
-4
lines changed

include/openPMD/IO/HDF5/HDF5IOHandlerImpl.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ class HDF5IOHandlerImpl : public AbstractIOHandlerImpl
9494
hid_t m_H5T_CFLOAT;
9595
hid_t m_H5T_CDOUBLE;
9696
hid_t m_H5T_CLONG_DOUBLE;
97+
/* See https://github.com/openPMD/openPMD-api/issues/1363
98+
* long double values written on AMD64 architectures cannot be read on
99+
* ARM64/PPC64 architectures without doing some special tricks.
100+
* We generally don't implement custom conversions, but instead pass-through
101+
* the cross-platform support of HDF5.
102+
* But this case is common and important enough to warrant a custom
103+
* workaround.
104+
*/
105+
hid_t m_H5T_LONG_DOUBLE_80_LE;
106+
hid_t m_H5T_CLONG_DOUBLE_80_LE;
97107

98108
protected:
99109
#if openPMD_HAVE_MPI

src/IO/HDF5/HDF5IOHandler.cpp

Lines changed: 167 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "openPMD/auxiliary/Filesystem.hpp"
3232
#include "openPMD/auxiliary/Mpi.hpp"
3333
#include "openPMD/auxiliary/StringManip.hpp"
34+
#include "openPMD/auxiliary/TypeTraits.hpp"
3435
#include "openPMD/backend/Attribute.hpp"
3536

3637
#include <hdf5.h>
@@ -73,6 +74,8 @@ HDF5IOHandlerImpl::HDF5IOHandlerImpl(
7374
, m_H5T_CFLOAT{H5Tcreate(H5T_COMPOUND, sizeof(float) * 2)}
7475
, m_H5T_CDOUBLE{H5Tcreate(H5T_COMPOUND, sizeof(double) * 2)}
7576
, m_H5T_CLONG_DOUBLE{H5Tcreate(H5T_COMPOUND, sizeof(long double) * 2)}
77+
, m_H5T_LONG_DOUBLE_80_LE{H5Tcopy(H5T_IEEE_F64BE)}
78+
, m_H5T_CLONG_DOUBLE_80_LE{H5Tcreate(H5T_COMPOUND, 16 * 2)}
7679
{
7780
// create a h5py compatible bool type
7881
VERIFY(
@@ -107,6 +110,28 @@ HDF5IOHandlerImpl::HDF5IOHandlerImpl(
107110
H5Tinsert(m_H5T_CLONG_DOUBLE, "r", 0, H5T_NATIVE_LDOUBLE);
108111
H5Tinsert(m_H5T_CLONG_DOUBLE, "i", sizeof(long double), H5T_NATIVE_LDOUBLE);
109112

113+
// Create a type that understands 128bit floats with 80 bits of precision
114+
// even on those platforms that do not have it (ARM64, PPC64).
115+
// Otherwise, files created on e.g. AMD64 platforms might not be readable
116+
// on such platforms.
117+
H5Tset_size(m_H5T_LONG_DOUBLE_80_LE, 16);
118+
H5Tset_order(m_H5T_LONG_DOUBLE_80_LE, H5T_ORDER_LE);
119+
H5Tset_precision(m_H5T_LONG_DOUBLE_80_LE, 80);
120+
H5Tset_fields(m_H5T_LONG_DOUBLE_80_LE, 79, 64, 15, 0, 64);
121+
H5Tset_ebias(m_H5T_LONG_DOUBLE_80_LE, 16383);
122+
H5Tset_norm(m_H5T_LONG_DOUBLE_80_LE, H5T_NORM_NONE);
123+
124+
VERIFY(
125+
m_H5T_LONG_DOUBLE_80_LE >= 0,
126+
"[HDF5] Internal error: Failed to create 128-bit long double");
127+
128+
H5Tinsert(m_H5T_CLONG_DOUBLE_80_LE, "r", 0, m_H5T_LONG_DOUBLE_80_LE);
129+
H5Tinsert(m_H5T_CLONG_DOUBLE_80_LE, "i", 16, m_H5T_LONG_DOUBLE_80_LE);
130+
131+
VERIFY(
132+
m_H5T_LONG_DOUBLE_80_LE >= 0,
133+
"[HDF5] Internal error: Failed to create 128-bit complex long double");
134+
110135
m_chunks = auxiliary::getEnvString("OPENPMD_HDF5_CHUNKS", "auto");
111136
// JSON option can overwrite env option:
112137
if (config.json().contains("hdf5"))
@@ -188,6 +213,14 @@ HDF5IOHandlerImpl::~HDF5IOHandlerImpl()
188213
std::cerr
189214
<< "[HDF5] Internal error: Failed to close complex double type\n";
190215
status = H5Tclose(m_H5T_CLONG_DOUBLE);
216+
if (status < 0)
217+
std::cerr << "[HDF5] Internal error: Failed to close complex long "
218+
"double type\n";
219+
status = H5Tclose(m_H5T_LONG_DOUBLE_80_LE);
220+
if (status < 0)
221+
std::cerr
222+
<< "[HDF5] Internal error: Failed to close long double type\n";
223+
status = H5Tclose(m_H5T_CLONG_DOUBLE_80_LE);
191224
if (status < 0)
192225
std::cerr << "[HDF5] Internal error: Failed to close complex long "
193226
"double type\n";
@@ -1006,13 +1039,17 @@ void HDF5IOHandlerImpl::openDataset(
10061039
d = DT::FLOAT;
10071040
else if (H5Tequal(dataset_type, H5T_NATIVE_DOUBLE))
10081041
d = DT::DOUBLE;
1009-
else if (H5Tequal(dataset_type, H5T_NATIVE_LDOUBLE))
1042+
else if (
1043+
H5Tequal(dataset_type, H5T_NATIVE_LDOUBLE) ||
1044+
H5Tequal(dataset_type, m_H5T_LONG_DOUBLE_80_LE))
10101045
d = DT::LONG_DOUBLE;
10111046
else if (H5Tequal(dataset_type, m_H5T_CFLOAT))
10121047
d = DT::CFLOAT;
10131048
else if (H5Tequal(dataset_type, m_H5T_CDOUBLE))
10141049
d = DT::CDOUBLE;
1015-
else if (H5Tequal(dataset_type, m_H5T_CLONG_DOUBLE))
1050+
else if (
1051+
H5Tequal(dataset_type, m_H5T_CLONG_DOUBLE) ||
1052+
H5Tequal(dataset_type, m_H5T_CLONG_DOUBLE_80_LE))
10161053
d = DT::CLONG_DOUBLE;
10171054
else if (H5Tequal(dataset_type, H5T_NATIVE_USHORT))
10181055
d = DT::USHORT;
@@ -1761,6 +1798,38 @@ void HDF5IOHandlerImpl::readDataset(
17611798
{typeid(std::complex<long double>).name(), m_H5T_CLONG_DOUBLE},
17621799
});
17631800
hid_t dataType = getH5DataType(a);
1801+
if (H5Tequal(dataType, H5T_NATIVE_LDOUBLE))
1802+
{
1803+
// We have previously determined in openDataset() that this dataset is
1804+
// of type long double.
1805+
// We cannot know if that actually was H5T_NATIVE_LDOUBLE or if it was
1806+
// the worked-around m_H5T_LONG_DOUBLE_80_LE.
1807+
// Check this.
1808+
hid_t checkDatasetTypeAgain = H5Dget_type(dataset_id);
1809+
if (!H5Tequal(checkDatasetTypeAgain, H5T_NATIVE_LDOUBLE))
1810+
{
1811+
dataType = m_H5T_LONG_DOUBLE_80_LE;
1812+
}
1813+
status = H5Tclose(checkDatasetTypeAgain);
1814+
VERIFY(
1815+
status == 0,
1816+
"[HDF5] Internal error: Failed to close HDF5 dataset type during "
1817+
"dataset reading");
1818+
}
1819+
else if (H5Tequal(dataType, m_H5T_CLONG_DOUBLE))
1820+
{
1821+
// Same deal for m_H5T_CLONG_DOUBLE
1822+
hid_t checkDatasetTypeAgain = H5Dget_type(dataset_id);
1823+
if (!H5Tequal(checkDatasetTypeAgain, m_H5T_CLONG_DOUBLE))
1824+
{
1825+
dataType = m_H5T_CLONG_DOUBLE_80_LE;
1826+
}
1827+
status = H5Tclose(checkDatasetTypeAgain);
1828+
VERIFY(
1829+
status == 0,
1830+
"[HDF5] Internal error: Failed to close HDF5 dataset type during "
1831+
"dataset reading");
1832+
}
17641833
VERIFY(
17651834
dataType >= 0,
17661835
"[HDF5] Internal error: Failed to get HDF5 datatype during dataset "
@@ -1952,6 +2021,14 @@ void HDF5IOHandlerImpl::readAttribute(
19522021
status = H5Aread(attr_id, attr_type, &l);
19532022
a = Attribute(l);
19542023
}
2024+
else if (H5Tequal(attr_type, m_H5T_LONG_DOUBLE_80_LE))
2025+
{
2026+
char bfr[16];
2027+
status = H5Aread(attr_id, attr_type, bfr);
2028+
H5Tconvert(
2029+
attr_type, H5T_NATIVE_LDOUBLE, 1, bfr, nullptr, H5P_DEFAULT);
2030+
a = Attribute(reinterpret_cast<long double *>(bfr)[0]);
2031+
}
19552032
else if (H5Tget_class(attr_type) == H5T_STRING)
19562033
{
19572034
if (H5Tis_variable_str(attr_type))
@@ -2070,6 +2147,20 @@ void HDF5IOHandlerImpl::readAttribute(
20702147
status = H5Aread(attr_id, attr_type, &cld);
20712148
a = Attribute(cld);
20722149
}
2150+
else if (complexSize == 16)
2151+
{
2152+
char bfr[2 * 16];
2153+
status = H5Aread(attr_id, attr_type, bfr);
2154+
H5Tconvert(
2155+
attr_type,
2156+
m_H5T_CLONG_DOUBLE,
2157+
1,
2158+
bfr,
2159+
nullptr,
2160+
H5P_DEFAULT);
2161+
a = Attribute(
2162+
reinterpret_cast<std::complex<long double> *>(bfr)[0]);
2163+
}
20732164
else
20742165
throw error::ReadError(
20752166
error::AffectedObject::Attribute,
@@ -2089,7 +2180,8 @@ void HDF5IOHandlerImpl::readAttribute(
20892180
error::AffectedObject::Attribute,
20902181
error::Reason::UnexpectedContent,
20912182
"HDF5",
2092-
"[HDF5] Unsupported scalar attribute type");
2183+
"[HDF5] Unsupported scalar attribute type for '" + attr_name +
2184+
"'.");
20932185
}
20942186
else if (attr_class == H5S_SIMPLE)
20952187
{
@@ -2211,6 +2303,49 @@ void HDF5IOHandlerImpl::readAttribute(
22112303
status = H5Aread(attr_id, attr_type, vcld.data());
22122304
a = Attribute(vcld);
22132305
}
2306+
else if (H5Tequal(attr_type, m_H5T_CLONG_DOUBLE_80_LE))
2307+
{
2308+
// worst case:
2309+
// sizeof(long double) is only 8, but the dataset on disk has
2310+
// 16-byte long doubles
2311+
// --> do NOT use `new long double[]` as the buffer would be too
2312+
// small
2313+
auto *tmpBuffer =
2314+
reinterpret_cast<long double *>(new char[16lu * 2lu * dims[0]]);
2315+
status = H5Aread(attr_id, attr_type, tmpBuffer);
2316+
H5Tconvert(
2317+
attr_type,
2318+
m_H5T_CLONG_DOUBLE,
2319+
dims[0],
2320+
tmpBuffer,
2321+
nullptr,
2322+
H5P_DEFAULT);
2323+
std::vector<std::complex<long double> > vcld{
2324+
tmpBuffer, tmpBuffer + dims[0]};
2325+
delete[] tmpBuffer;
2326+
a = Attribute(std::move(vcld));
2327+
}
2328+
else if (H5Tequal(attr_type, m_H5T_LONG_DOUBLE_80_LE))
2329+
{
2330+
// worst case:
2331+
// sizeof(long double) is only 8, but the dataset on disk has
2332+
// 16-byte long doubles
2333+
// --> do NOT use `new long double[]` as the buffer would be too
2334+
// small
2335+
auto *tmpBuffer =
2336+
reinterpret_cast<long double *>(new char[16lu * dims[0]]);
2337+
status = H5Aread(attr_id, attr_type, tmpBuffer);
2338+
H5Tconvert(
2339+
attr_type,
2340+
H5T_NATIVE_LDOUBLE,
2341+
dims[0],
2342+
tmpBuffer,
2343+
nullptr,
2344+
H5P_DEFAULT);
2345+
std::vector<long double> vld80{tmpBuffer, tmpBuffer + dims[0]};
2346+
delete[] tmpBuffer;
2347+
a = Attribute(std::move(vld80));
2348+
}
22142349
else if (H5Tget_class(attr_type) == H5T_STRING)
22152350
{
22162351
std::vector<std::string> vs;
@@ -2245,11 +2380,39 @@ void HDF5IOHandlerImpl::readAttribute(
22452380
a = Attribute(vs);
22462381
}
22472382
else
2383+
{
2384+
auto order = H5Tget_order(attr_type);
2385+
auto prec = H5Tget_precision(attr_type);
2386+
auto ebias = H5Tget_ebias(attr_type);
2387+
size_t spos, epos, esize, mpos, msize;
2388+
H5Tget_fields(attr_type, &spos, &epos, &esize, &mpos, &msize);
2389+
2390+
auto norm = H5Tget_norm(attr_type);
2391+
auto cset = H5Tget_cset(attr_type);
2392+
auto sign = H5Tget_sign(attr_type);
2393+
2394+
std::stringstream detailed_info;
2395+
detailed_info << "order " << std::to_string(order) << std::endl
2396+
<< "prec " << std::to_string(prec) << std::endl
2397+
<< "ebias " << std::to_string(ebias) << std::endl
2398+
<< "fields " << std::to_string(spos) << " "
2399+
<< std::to_string(epos) << " "
2400+
<< std::to_string(esize) << " "
2401+
<< std::to_string(mpos) << " "
2402+
<< std::to_string(msize) << "norm "
2403+
<< std::to_string(norm) << std::endl
2404+
<< "cset " << std::to_string(cset) << std::endl
2405+
<< "sign " << std::to_string(sign) << std::endl
2406+
<< std::endl;
2407+
22482408
throw error::ReadError(
22492409
error::AffectedObject::Attribute,
22502410
error::Reason::UnexpectedContent,
22512411
"HDF5",
2252-
"[HDF5] Unsupported simple attribute type");
2412+
"[HDF5] Unsupported simple attribute type " +
2413+
std::to_string(attr_type) + " for " + attr_name +
2414+
".\n(Info for debugging: " + detailed_info.str() + ")");
2415+
}
22532416
}
22542417
else
22552418
throw std::runtime_error("[HDF5] Unsupported attribute class");

0 commit comments

Comments
 (0)