GCC Code Coverage Report


Directory: src/
File: src/algo/simd_utils.h
Date: 2026-09-30 11:11:31
Exec Total Coverage
Lines: 14 18 77.8%
Functions: 2 4 50.0%
Branches: 0 0 -%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2026 Tiger Data, Inc.
3 * Licensed under the PostgreSQL License. See LICENSE for details.
4 *
5 * simd_utils.h - Shared SIMD utilities and patterns
6 *
7 * Provides reusable SIMD infrastructure for distance computations,
8 * quantization, and other vectorized operations. This header contains:
9 *
10 * - Horizontal reduction functions for all SIMD variants
11 * - Dispatch macro templates for runtime CPU detection
12 * - Common SIMD patterns (prefetch, alignment)
13 * - Generic batch processing helpers
14 *
15 * Design rationale: By centralizing SIMD utilities, we enable ~50-60% code
16 * reuse across full-precision, binary, and product quantization distance
17 * implementations. Each distance type needs different intrinsics, but shares
18 * the infrastructure.
19 */
20
21 #ifndef VS_SIMD_UTILS_H
22 #define VS_SIMD_UTILS_H
23
24 /* Must be first - defines VS_SIMD_NONE used by VS_TARGET_CLONES */
25 #include "vs_config.h"
26
27 #include <stdatomic.h>
28 #include <stdint.h>
29
30 #include "core/platform.h"
31
32 /*
33 * Target Attribute Macros
34 *
35 * These macros specify the CPU features required for each SIMD implementation.
36 * Using macros makes it easy to change target features in one place.
37 */
38 #if defined(__x86_64__) || defined(_M_X64)
39 #define VS_TARGET_AVX512 __attribute__((target("avx512f,avx512dq")))
40 #define VS_TARGET_AVX512_VPOPCNTDQ \
41 __attribute__((target("avx512f,avx512vpopcntdq")))
42 #define VS_TARGET_AVX2 __attribute__((target("avx2,fma")))
43 #define VS_TARGET_F16C_AVX2 __attribute__((target("avx2,fma,f16c")))
44 #elif defined(__aarch64__) || defined(_M_ARM64)
45 /* NEON is always available on AArch64, no attribute needed */
46 #define VS_TARGET_NEON
47 #endif
48
49 /*
50 * Target Clones Macro
51 *
52 * Generates multiple function versions for different ISAs. The dynamic linker
53 * selects the best version at load time. Only effective on x86 with GCC 6+ or
54 * Clang 13+.
55 *
56 * Note: target_clones doesn't work effectively on ARM (generates single
57 * clone).
58 */
59 #ifndef __has_attribute
60 #define __has_attribute(x) 0
61 #endif
62
63 /*
64 * Disable target_clones when:
65 * - simd=none (VS_SIMD_NONE)
66 * - coverage build (VS_COVERAGE) - each clone is separate, only one executes
67 * - compiler lacks target_clones support
68 * - non-x86 architecture (ARM target_clones generates single clone anyway)
69 */
70 #if !defined(VS_SIMD_NONE) && !defined(VS_COVERAGE) && \
71 __has_attribute(target_clones) && defined(__x86_64__)
72 #define VS_TARGET_CLONES \
73 __attribute__(( \
74 target_clones("default", "arch=x86-64-v3", "arch=x86-64-v4")))
75 #else
76 #define VS_TARGET_CLONES
77 #endif
78
79 /*
80 * Horizontal Reduction Functions
81 *
82 * These functions reduce a SIMD vector to a single scalar value by summing
83 * all elements. Used at the end of SIMD loops to extract the final result.
84 */
85
86 #if defined(__x86_64__) || defined(_M_X64)
87
88 #include <immintrin.h>
89
90 /*
91 * AVX-512 horizontal sum (16 floats -> 1 float)
92 *
93 * Uses _mm512_reduce_add_ps intrinsic (AVX-512F).
94 */
95 VS_TARGET_AVX512 static inline float
96 ✗ vs_horizontal_sum_avx512(__m512 v)
97 {
98 ✗ return _mm512_reduce_add_ps(v);
99 }
100
101 /*
102 * AVX-512 horizontal sum for 64-bit integers (8 uint64_t -> 1 uint64_t)
103 *
104 * Used for Hamming distance (popcount results).
105 */
106 VS_TARGET_AVX512 static inline uint64_t
107 ✗ vs_horizontal_sum_epi64_avx512(__m512i v)
108 {
109 ✗ return _mm512_reduce_add_epi64(v);
110 }
111
112 /*
113 * AVX2 horizontal sum (8 floats -> 1 float)
114 *
115 * AVX2 lacks a reduce intrinsic, so we manually combine the two 128-bit
116 * halves and use horizontal adds within each lane.
117 */
118 VS_TARGET_AVX2 static inline float
119 15974219 vs_horizontal_sum_avx2(__m256 v)
120 {
121 /* Extract high and low 128-bit halves */
122 15974219 __m128 lo = _mm256_castps256_ps128(v);
123 15974219 __m128 hi = _mm256_extractf128_ps(v, 1);
124
125 /* Add halves */
126 15974219 __m128 sum = _mm_add_ps(lo, hi);
127
128 /* Horizontal add within 128 bits (twice to reduce to single element) */
129 15974219 sum = _mm_hadd_ps(sum, sum);
130 15974219 sum = _mm_hadd_ps(sum, sum);
131
132 /* Extract scalar */
133 15974219 return _mm_cvtss_f32(sum);
134 }
135
136 /*
137 * AVX2 horizontal sum for 64-bit integers (4 uint64_t -> 1 uint64_t)
138 */
139 VS_TARGET_AVX2 static inline uint64_t
140 146 vs_horizontal_sum_epi64_avx2(__m256i v)
141 {
142 146 __m128i lo = _mm256_castsi256_si128(v);
143 146 __m128i hi = _mm256_extracti128_si256(v, 1);
144 146 __m128i sum = _mm_add_epi64(lo, hi);
145
146 /* Extract two 64-bit values and add */
147 146 uint64_t a = (uint64_t)_mm_extract_epi64(sum, 0);
148 146 uint64_t b = (uint64_t)_mm_extract_epi64(sum, 1);
149 146 return a + b;
150 }
151
152 #endif /* x86_64 */
153
154 #if defined(__aarch64__) || defined(_M_ARM64)
155
156 #include <arm_neon.h>
157
158 /*
159 * NEON horizontal sum (4 floats -> 1 float)
160 *
161 * Uses vaddvq_f32 (ARMv8.1+ reduction intrinsic).
162 */
163 static inline float
164 vs_horizontal_sum_neon(float32x4_t v)
165 {
166 return vaddvq_f32(v);
167 }
168
169 /*
170 * NEON horizontal sum for 64-bit integers (2 uint64_t -> 1 uint64_t)
171 */
172 static inline uint64_t
173 vs_horizontal_sum_u64_neon(uint64x2_t v)
174 {
175 return vaddvq_u64(v);
176 }
177
178 #endif /* aarch64 */
179
180 /*
181 * Dispatch Macros
182 *
183 * These macros simplify runtime CPU detection and function pointer dispatch.
184 * Use DECLARE_DISPATCH to define the function pointer type and global state,
185 * then INIT_DISPATCH to initialize based on detected CPU capabilities.
186 *
187 * Example usage:
188 *
189 * // In header:
190 * Distance my_distance_fn(Vec32Ref a, Vec32Ref b);
191 *
192 * // In implementation:
193 * DECLARE_DISPATCH(my_distance, Distance, Vec32Ref, Vec32Ref)
194 *
195 * INIT_DISPATCH(my_distance,
196 * my_distance_scalar,
197 * my_distance_avx512,
198 * my_distance_avx2,
199 * my_distance_neon)
200 *
201 * Distance my_distance_fn(Vec32Ref a, Vec32Ref b) {
202 * if (vs_unlikely(!g_my_distance_initialized))
203 * my_distance_init();
204 * return g_my_distance_fn(a, b);
205 * }
206 */
207
208 /*
209 * DECLARE_DISPATCH - Declare function pointer type and global state
210 *
211 * Creates:
212 * - typedef for the function pointer type
213 * - static global function pointer (initially NULL)
214 * - static atomic initialization flag
215 */
216 #define DECLARE_DISPATCH(name, ret_type, ...) \
217 typedef ret_type (*name##_fn_t)(__VA_ARGS__); \
218 static name##_fn_t g_##name##_fn = NULL; \
219 static _Atomic(bool) g_##name##_initialized = false; \
220 static inline int name##_init(void); \
221 static inline ret_type name##_dispatch(__VA_ARGS__); \
222 \
223 static inline ret_type name##_dispatch(__VA_ARGS__ args) \
224 { \
225 if (vs_unlikely(!g_##name##_initialized)) \
226 name##_init(); \
227 return g_##name##_fn(args); \
228 }
229
230 /*
231 * INIT_DISPATCH - Initialize function pointer based on CPU capabilities
232 *
233 * Parameters:
234 * - name: base name (matches DECLARE_DISPATCH)
235 * - scalar: scalar fallback function
236 * - avx512: AVX-512 implementation (or NULL)
237 * - avx2: AVX2 implementation (or NULL)
238 * - neon: NEON implementation (or NULL)
239 *
240 * Selects the best available implementation based on runtime CPU detection.
241 * Falls back to scalar if no SIMD implementation is available.
242 */
243 #define INIT_DISPATCH(name, scalar, avx512, avx2, neon) \
244 static inline int name##_init(void) \
245 { \
246 if (g_##name##_initialized) \
247 return 0; \
248 \
249 SimdCapability caps = vs_detect_simd(); \
250 \
251 /* Require every AVX-512 sub-extension any kernel \
252 * here may use (F+DQ+BW), not just F -- see \
253 * VS_SIMD_AVX512_* in platform.h. */ \
254 SimdCapability avx512_req = VS_SIMD_AVX512_DQ | VS_SIMD_AVX512_BW; \
255 if (((caps & avx512_req) == avx512_req) && (avx512) != NULL) \
256 g_##name##_fn = avx512; \
257 else if ((caps & SIMD_AVX2) && (avx2) != NULL) \
258 g_##name##_fn = avx2; \
259 else if ((caps & SIMD_NEON) && (neon) != NULL) \
260 g_##name##_fn = neon; \
261 else \
262 g_##name##_fn = scalar; \
263 \
264 g_##name##_initialized = true; \
265 return 0; \
266 }
267
268 /*
269 * Batch Processing Helpers
270 *
271 * Common patterns for processing multiple vectors in a loop with prefetching.
272 */
273
274 /*
275 * Prefetch distance (in vectors) for batch operations.
276 *
277 * Prefetch 2 vectors ahead to hide memory latency. Assumes 64-byte cache
278 * lines and typical vector sizes (128-1024 dimensions * 4 bytes = 0.5-4 KB).
279 */
280 #define VS_PREFETCH_DISTANCE 2
281
282 /*
283 * BATCH_LOOP_WITH_PREFETCH - Generic batch loop template
284 *
285 * Usage:
286 * BATCH_LOOP_WITH_PREFETCH(v, count, dim, vectors) {
287 * Vec32Ref vec = {.data = vectors + v * dim, .dim = dim};
288 * distances[v] = compute_distance(query, vec);
289 * }
290 */
291 #define BATCH_LOOP_WITH_PREFETCH(idx, count, dim, vectors) \
292 for (uint32_t idx = 0; idx < (count); (idx)++) \
293 { \
294 if ((idx) + VS_PREFETCH_DISTANCE < (count)) \
295 vs_prefetch_read( \
296 (vectors) + ((idx) + VS_PREFETCH_DISTANCE) * (dim));
297
298 #define BATCH_LOOP_END }
299
300 #endif /* VS_SIMD_UTILS_H */
301