Repository navigation
sanitizer: UBSan reports "runtime error: store to misaligned address" on SIMD SSE with particular number of threads #5560
Description
Describe the bug
UBSan reports runtime error: store to misaligned address when building with -DSANITIZE=address,undefined and when running basic PNG-to-PNG oiiotool command with SIMD SSE enabled and only occurs for certain N in --threads N (for 1 or 4 doesn't occur, for 3 it does, etc.).
OpenImageIO version and dependencies
oiiotool --buildinfo output:
OIIO 3.3.0.0dev | Linux/x86_64
Build compiler: gcc 13.3 | C++17/201703
HW features enabled at build: sse2,sse3,ssse3,sse41,sse42
No CUDA support (disabled / unavailable at build time)
Dependencies: BZip2 1.0.8, DCMTK NONE, FFmpeg NONE, fmt 12.1.0, Freetype
2.13.2, GIF NONE, Imath 3.2.3, JPEG 80, JXL NONE, Ktx 5.0.0, libdeflate
1.23, Libheif NONE, libjpeg-turbo 3.1.2, LibRaw NONE, libuhdr NONE,
nanobind 2.13.0, OpenColorIO 2.5.1, OpenCV NONE, OpenEXR 3.5.2, OpenGL,
OpenJPEG NONE, openjph NONE, PNG 1.6.43, Ptex NONE, Ptex NONE, Python3
3.12.3, Qt5 NONE, Qt6 NONE, Robinmap 1.4.1, TBB NONE, TIFF 4.7.1, WebP
NONE, ZLIB 1.3, zstd 1.5.5
To Reproduce
- Build with ASan + UBSan on a SIMD SSE machine. E.g., my build command:
cmake -S . -B build/ -DCMAKE_BUILD_TYPE=Debug -DOIIO_DOWNLOAD_MISSING_TESTDATA=ON -DOpenImageIO_BUILD_MISSING_DEPS=required -DUSE_PYTHON=1 -DSANIT
IZE=address,undefined
cmake --build build/ -j8- Enable UBSan stacktrace and run a simple PNG to PNG (the
--threads 3is important):
export UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1
oiiotool --threads 3 testsuite/png/oiio-logo-no-alpha.png -o t.pngEvidence
This is the first 7 lines from the backtrace I get (see below for potential explanation):
/usr/lib/gcc/x86_64-linux-gnu/13/include/xmmintrin.h:969:8: runtime error: store to misaligned address 0x781dadf3b85e for type 'float', which requires 4 byte alignment
0x781dadf3b85e: note: pointer points here
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
^
#0 0x781dc08e0919 in _mm_store_ss(float*, float __vector(4)) /usr/lib/gcc/x86_64-linux-gnu/13/include/xmmintrin.h:969
#1 0x781dc08e0919 in OpenImageIO::v3_1::simd::vint4::store(unsigned char*) const /home/walcht/OpenImageIO/src/include/OpenImageIO/simd.h:4754
#2 0x781dc08e0919 in void OpenImageIO::v3_1::convert_type<float, unsigned char>(float const*, unsigned char*, unsigned long, unsigned char, unsigned char) /home/walcht/OpenImageIO/src/include/OpenImageIO/fmath.h:916
#3 0x781dc08e9eae in void OpenImageIO::v3_1::convert_type<float, unsigned char>(float const*, unsigned char*, unsigned long) /home/walcht/OpenImageIO/src/include/OpenImageIO/fmath.h:977
#4 0x781dc08c93eb in OpenImageIO::v3_3_0::pvt::convert_from_float(float const*, void*, unsigned long, OpenImageIO::v3_1::TypeDesc) /home/walcht/OpenImageIO/src/libOpenImageIO/imageio.cpp:922
#5 0x781dc08c986e in operator() /home/walcht/OpenImageIO/src/libOpenImageIO/imageio.cpp:946
#6 0x781dc08d9824 in __invoke_impl<void, OpenImageIO::v3_3_0::pvt::parallel_convert_from_float(float const*, void*, size_t, OpenImageIO::v3_1::TypeDesc)::<lambda(int64_t, int64_t)>&, long int, long int> /usr/include/c++/13/bits/invoke.h:61
Potential Explanation
When dispatching threads to convert from float to uint8_t the (char*)dst is offset by a value that is not necessarily a multiple of 4 (required alignment for float):
const void*
pvt::parallel_convert_from_float(const float* src, void* dst, size_t nvals,
TypeDesc format)
{
if (format.basetype == TypeDesc::FLOAT)
return src;
parallel_for_chunked(0, int64_t(nvals), 0, [=](int64_t b, int64_t e) {
convert_from_float(src + b, (char*)dst + b * format.size(), e - b,
format);
});
return dst;
}
But why does the (char*)dst has to be aligned to 4? Because on SIMD SSE it gets cast'ed into a float (as dst):
OIIO_FORCEINLINE void vint4::store (unsigned char *values) const {
#if OIIO_AVX512VL_ENABLED
// ...
#elif OIIO_SIMD_SSE
vint4 clamped = m_simd & vint4(0xff); // A000 B000 C000 D000
simd_t val16 = _mm_packs_epi32(clamped, _mm_setzero_si128()); // A0B0 C0D0 xxxx xxxx
simd_t val8 = _mm_packus_epi16(val16, _mm_setzero_si128()); // ABCD xxxx xxxx xxxx
_mm_store_ss((float*)values, _mm_castsi128_ps(val8));
#elif OIIO_SIMD_NEON
// ...
#else
// ...
#endif
}I'm not familiar with SIMD SSE yet to suggest a PR fix (but, for instance, the b should somehow be a multiple of 4; I can't think of an elegant solution to this...).
Edit: fixed preview issues with code snippets
Activity
lgritz commentedon Oct 12, 2026
The real problem is going to be higher in the callstack. Somebody higher up must be calling parallel_convert_from_float and passing a float* that was cast from something unaligned.
walcht commentedon Oct 13, 2026
To me, it seems that the problem is exactly in this computation
(char*)dst + b * format.size()becauseformat.size()returns 1 (target is UINT8) andbis arbitrarily determined by theparallel_for_chunked, right?It is also consistently not producing this error/diagnostic when running in single-threaded mode (i.e., the
babove is 0 so(char*)dstby itself is aligned).If the full backtrace is needed I can provide it (around 70ish lines).
lgritz commentedon Oct 13, 2026
Oh, I think I see what's going on.
fix(simd): avoid misaligned typed stores in partial vint4/vfloat4 stores
lgritz commentedon Oct 13, 2026
Proposed fix here: #5566
fix(simd): avoid misaligned typed stores in partial vint4/vfloat4 sto…