Interfacing C++ std::simd with Intel Intrinsics: Implementing Saturating Casts
The upcoming standard SIMD library (std::experimental::simd from the Parallelism TS v2, or simply std::simd in future C++ revisions) provides an elegant, portable abstraction for data-parallel programming. However, portability sometimes comes at the cost of specificity. When you need target-specific performance optimizations—such as executing an AVX2-specific _mm256_packus_epi32 instruction for saturating casts—you need an "escape hatch" to bridge the standard SIMD types with raw hardware intrinsics.
The Escape Hatch: Converting std::simd to Native SIMD Registers
In standard C++, std::simd types are designed to be zero-cost wrappers over underlying vector types. To pass data to and from Intel intrinsics (like __m256i), you have two primary options:
- Implementation-specific accessors / member types (e.g.,
__vector_typeor.data()depending on compiler extensions). - Standard-compliant memory-based conversion /
std::bit_cast, which modern compilers optimize to zero instructions.
Method 1: Safe and Idiomatic with std::bit_cast (C++20)
Because standard vector types and intrinsic types like __m256i are trivially copyable and share the exact same size and alignment requirements, the cleanest portable escape hatch is std::bit_cast. Compilers such as GCC and Clang will optimize these bit-casts away completely, passing vector registers directly.
#include <experimental/simd>
#include <immintrin.h>
#include <bit>
#include <cstdint>
namespace stdx = std::experimental;
stdx::fixed_size_simd<std::uint16_t, 16> saturating_cast_256(
stdx::fixed_size_simd<std::uint32_t, 8> x,
stdx::fixed_size_simd<std::uint32_t, 8> y
) {
// 1. Bit-cast std::simd to native Intel intrinsic types (__m256i)
__m256i raw_x = std::bit_cast<__m256i>(x);
__m256i raw_y = std::bit_cast<__m256i>(y);
// 2. Call target-specific intrinsic
__m256i raw_result = _mm256_packus_epi32(raw_x, raw_y);
// Note on AVX2: _mm256_packus_epi32 operates in 128-bit lanes.
// If you need cross-lane ordering, an extra permutation may be required.
// 3. Bit-cast back to the desired std::simd output type
return std::bit_cast<stdx::fixed_size_simd<std::uint16_t, 16>>(raw_result);
}Method 2: Using Pointer/Array Loads and Stores (C++17 Fallback)
If you are working with an older standard prior to C++20 where std::bit_cast is unavailable, use copy_to and copy_from methods provided natively by the std::simd interface, or standard alignas arrays:
#include <experimental/simd>
#include <immintrin.h>
#include <cstdint>
namespace stdx = std::experimental;
stdx::fixed_size_simd<std::uint16_t, 16> saturating_cast_256_cpp17(
stdx::fixed_size_simd<std::uint32_t, 8> x,
stdx::fixed_size_simd<std::uint32_t, 8> y
) {
alignas(__m256i) std::uint32_t buf_x[8];
alignas(__m256i) std::uint32_t buf_y[8];
alignas(__m256i) std::uint16_t buf_res[16];
x.copy_to(buf_x, stdx::element_aligned);
y.copy_to(buf_y, stdx::element_aligned);
__m256i raw_x = _mm256_load_si256(reinterpret_cast<const __m256i*>(buf_x));
__m256i raw_y = _mm256_load_si256(reinterpret_cast<const __m256i*>(buf_y));
__m256i res = _mm256_packus_epi32(raw_x, raw_y);
_mm256_store_si256(reinterpret_cast<__m256i*>(buf_res), res);
stdx::fixed_size_simd<std::uint16_t, 16> out;
out.copy_from(buf_res, stdx::element_aligned);
return out;
}Watch Out: AVX2 128-bit Lane Behavior
When using _mm256_packus_epi32, keep in mind that AVX2 executes operations in two distinct 128-bit lanes. The low 64 bits of the result lane come from x, and the high 64 bits come from y. If your output needs to be strictly ordered [x[0..7], y[0..7]] across the entire 256-bit register, you must follow up with a permute instruction such as _mm256_permute4x64_epi64.
Summary
- Use
std::bit_castas the canonical escape hatch to switch betweenstd::simdvalues and target intrinsic types like__m128ior__m256i. - It incurs zero runtime overhead in optimized builds (
-O2/-O3). - It keeps your high-level architecture clean while giving you targeted access to low-level SIMD operations like saturated arithmetic, shuffle masks, or specialized conversions.