FastLED 3.10.6
Loading...
Searching...
No Matches
minimp3_synth_fixed.h
Go to the documentation of this file.
1/* Deliberately no include guard and no `#pragma once`.
2
3 This file is included exactly once per expansion of minimp3.h's
4 implementation block, which carries its own _MINIMP3_IMPLEMENTATION_GUARD.
5 A guard here would be redundant in the ordinary case and wrong in the one
6 that matters: tests/fl/codec/minimp3_variants.hpp builds several complete
7 copies of the decoder in a single translation unit -- float, fixed, and
8 fixed with MINIMP3_NO_SIMD -- by #undef-ing MINIMP3_H and
9 _MINIMP3_IMPLEMENTATION_GUARD between them. `#pragma once` is immune to
10 that, so the second and later fixed-point copies would compile minimp3.h's
11 implementation with this file silently skipped, and fail on the first use
12 of mp3d_synth_granule. Do not add one. */
13
14/* The fixed-point synthesis back-end: DCT-32, the polyphase filterbank and the
15 integer SIMD kernels they dispatch to. This is 57% of a decode by host
16 instruction count and holds the one stage where this decoder still loses to
17 the Helix reference on RISC-V, so it is where optimisation work happens.
18 It is split out of minimp3.h so that work has a single file to edit.
19
20 This is not a standalone header. It is textually included from minimp3.h at
21 one point, inside `#if MINIMP3_HAVE_FIXED_POINT`, and relies on everything
22 minimp3.h has already defined above that point: mp3d_dsp_t and the Q-format
23 typedefs, the coefficient tables, the MP3D_LEAF / MP3D_HOT / MP3D_KERNEL
24 inline policy, the arithmetic helpers (mp3d_mulshift, MP3D_WRAP_ADD,
25 mp3d_narrow_q30) and the MP3D_HAVE_INT_SIMD detection. Do not include it
26 anywhere else; there is no include-order in which it compiles alone.
27
28 To measure a change here:
29
30 bash mp3measure
31
32 which reports host Callgrind instruction counts against the last commit, an
33 ESP32-C6 autoresearch run with the Helix ratio, the riscv32 .text delta and
34 the PSNR tripwire. Quote the device number: on this decoder host and device
35 have disagreed in both direction and magnitude. See
36 agents/docs/mp3-decoder-performance.md. */
37
38/* DCT-32. Same factorisation as the float build. The secants reach 10.19 so
39 they are Q27; the rotation constants are Q31 and the output scalings Q29,
40 each the widest format that still holds its largest value.
41
42 Every add saturates. On a real stream the intermediates stay well inside the
43 Q26 range -- the measured pipeline peak is 0.654 -- but a fuzzed bitstream
44 can drive dequantised samples to the +/-1 clamp, and this butterfly stacks
45 three levels of adds on top of a 10.19x multiply. Saturating there turns a
46 signed-overflow UB report into a bounded, audible-at-worst result. */
47#if MP3D_HAVE_INT_SIMD
48/* FastLED: integer vector helpers for the polyphase back-end.
49
50 The polyphase filter is the one kernel where vectorising is bit-exact for
51 free: it is a pure int32 x int32 -> int64 multiply-accumulate with no
52 intermediate rounding or saturation, and int64 addition is exact and
53 associative, so any lane arrangement reproduces the scalar result exactly.
54 Every other kernel rounds and saturates per operation, which is why they are
55 not vectorised -- see the disposition note on mp3d_synth below.
56
57 The MUL_LO/MUL_HI pair takes two int32 lanes and returns two int64 products
58 -- `LO` for lanes 0 and 1, `HI` for lanes 2 and 3. ADDSAT/SUBSAT/MULSHIFT are
59 the four-lane forms of the scalar helpers of the same name and must match
60 them exactly, including the symmetric saturation range. */
61#if MP3D_INT_SIMD_NEON
62typedef int64x2_t mp3d_i64x2;
63#define MP3D_V_ZERO64() vdupq_n_s64(0)
64#define MP3D_V_LOAD4(p) vld1q_s32((const int32_t *)(p))
65#define MP3D_V_SPLAT(x) vdupq_n_s32(x)
66#define MP3D_V_PREP(v) (v)
67#define MP3D_V_STORE4(p, v) vst1q_s32((int32_t *)(p), (v))
68#define MP3D_V_MUL_LO(v, s) vmull_s32(vget_low_s32(v), vget_low_s32(s))
69#define MP3D_V_MUL_HI(v, s) vmull_s32(vget_high_s32(v), vget_high_s32(s))
70#define MP3D_V_ADD64(x, y) vaddq_s64((x), (y))
71#define MP3D_V_SUB64(x, y) vsubq_s64((x), (y))
72#define MP3D_V_GET64(x, lane) ((lane) ? vgetq_lane_s64((x), 1) : vgetq_lane_s64((x), 0))
73/* NEON multiplies signed 32x32 -> 64 natively and is in the ARM64 baseline, so
74 there is nothing to detect. */
75#define MP3D_SIMD_AVAILABLE() 1
76#define MP3D_SIMD_TARGET
77
78/* Saturating add/subtract. vqaddq_s32 saturates to INT32_MIN/MAX; the decoder's
79 range is symmetric, so the extra vmaxq_s32 pulls INT32_MIN up to
80 MP3D_SAT_MIN exactly as the scalar helper does. */
81#define MP3D_V_ADDSAT(a, b) \
82 vmaxq_s32(vqaddq_s32((a), (b)), vdupq_n_s32(MP3D_SAT_MIN))
83#define MP3D_V_SUBSAT(a, b) \
84 vmaxq_s32(vqsubq_s32((a), (b)), vdupq_n_s32(MP3D_SAT_MIN))
85
86/* value * Q`bits` coefficient, rounded and saturated -- the vector form of
87 mp3d_mulshift, and it must round the same way: add half, then shift right
88 with sign extension (round half toward +infinity). */
89/* Rounded, saturating narrow of two int64x2 accumulators -- the vector form of
90 mp3d_narrow_q30 generalised over the shift. Factored out of mp3d_v_mulshift
91 so a kernel that builds its own accumulators can share it (#4109): the
92 twiddle loop sums two products before narrowing once, and rounding each
93 product separately would differ from the scalar path in the low bit. */
94static int32x4_t mp3d_v_narrow(int64x2_t lo, int64x2_t hi,
95 int shift) FL_NO_EXCEPT
96{
97 const int64x2_t round = vdupq_n_s64((int64_t)1 << (shift - 1));
98 const int64x2_t sh = vdupq_n_s64(-shift);
99 lo = vshlq_s64(vaddq_s64(lo, round), sh);
100 hi = vshlq_s64(vaddq_s64(hi, round), sh);
101 return vmaxq_s32(vcombine_s32(vqmovn_s64(lo), vqmovn_s64(hi)),
102 vdupq_n_s32(MP3D_SAT_MIN));
103}
104
105static int32x4_t mp3d_v_mulshift(int32x4_t v, int32_t coef,
106 int shift) FL_NO_EXCEPT
107{
108 const int32x2_t c = vdup_n_s32(coef);
109 return mp3d_v_narrow(vmull_s32(vget_low_s32(v), c),
110 vmull_s32(vget_high_s32(v), c), shift);
111}
112#define MP3D_V_MULSHIFT(v, coef, bits) mp3d_v_mulshift((v), (coef), (bits))
113/* Vector-by-vector 32x32->64. MP3D_V_MUL_LO/HI take a splat as their second
114 operand; the twiddle kernel needs both factors to vary per lane. */
115#define MP3D_V_MULV_LO(a, b) vmull_s32(vget_low_s32(a), vget_low_s32(b))
116#define MP3D_V_MULV_HI(a, b) vmull_s32(vget_high_s32(a), vget_high_s32(b))
117#define MP3D_V_REV4(v) \
118 vcombine_s32(vrev64_s32(vget_high_s32(v)), vrev64_s32(vget_low_s32(v)))
119#else /* MP3D_INT_SIMD_SSE */
120typedef __m128i mp3d_i64x2;
121#define MP3D_V_ZERO64() _mm_setzero_si128()
122#define MP3D_V_LOAD4(p) _mm_loadu_si128((const __m128i *)(const void *)(p))
123#define MP3D_V_SPLAT(x) _mm_set1_epi32(x)
124/* _mm_mul_epi32 multiplies the even int32 lanes; shuffling to (0,1),(2,3) once
125 per vector lets both architectures share the accumulator bookkeeping. */
126#define MP3D_V_PREP(v) _mm_shuffle_epi32((v), _MM_SHUFFLE(3, 1, 2, 0))
127#define MP3D_V_STORE4(p, v) _mm_storeu_si128((__m128i *)(void *)(p), (v))
128#define MP3D_V_MUL_LO(v, s) _mm_mul_epi32((v), (s))
129#define MP3D_V_MUL_HI(v, s) _mm_mul_epi32(_mm_srli_si128((v), 4), (s))
130#define MP3D_V_ADD64(x, y) _mm_add_epi64((x), (y))
131#define MP3D_V_SUB64(x, y) _mm_sub_epi64((x), (y))
132
133static int64_t mp3d_get_i64(__m128i v, int lane) FL_NO_EXCEPT
134{
135 int64_t out[2];
136 _mm_storeu_si128((__m128i *)(void *)out, v);
137 return out[lane];
138}
139#define MP3D_V_GET64(x, lane) mp3d_get_i64((x), (lane))
140
141/* Signed 32x32 -> 64 needs SSE4.1. SSE2 can emulate it with an unsigned
142 multiply plus a sign correction, and that was measured rather than assumed:
143 0.66x of scalar in a standalone harness, 0.95x inside the decoder. Slower is
144 not worth shipping, so SSE2-only hardware stays on the scalar kernel and the
145 vector path is chosen at run time -- the same shape as upstream's
146 have_simd() dispatch for the float kernels. The same harness measures 1.71x
147 once _mm_mul_epi32 is available. */
148#if defined(__GNUC__) || defined(__clang__)
149#define MP3D_SIMD_TARGET __attribute__((target("sse4.1")))
150#else
151#define MP3D_SIMD_TARGET
152#endif
153
154/* Self-contained: upstream's minimp3_cpuid lives inside the float SIMD block,
155 which the fixed build switches off, so this path cannot borrow it. */
156static int mp3d_have_sse41(void) FL_NO_EXCEPT
157{
158#if defined(__SSE4_1__)
159 return 1;
160#elif defined(_MSC_VER)
161 static int cached;
162 int info[4];
163 if (!cached)
164 {
165 __cpuid(info, 1);
166 cached = ((info[2] & (1 << 19)) != 0) + 1; /* ECX.SSE4_1 */
167 }
168 return cached - 1;
169#elif defined(__GNUC__) || defined(__clang__)
170 static int cached;
171 unsigned eax, ebx, ecx, edx;
172 if (!cached)
173 {
174 cached = (__get_cpuid(1, &eax, &ebx, &ecx, &edx) &&
175 (ecx & (1u << 19))) + 1;
176 }
177 return cached - 1;
178#else
179 return 0;
180#endif
181}
182#define MP3D_SIMD_AVAILABLE() mp3d_have_sse41()
183
184/* Saturating add/subtract on four int32 lanes. SSE has saturating add only for
185 8- and 16-bit lanes, so the 32-bit form is the classic branchless test: a
186 signed add overflows exactly when both operands share a sign the result does
187 not. The trailing _mm_max_epi32 enforces the decoder's symmetric range, the
188 same trailing clamp the scalar helper carries. */
189MP3D_SIMD_TARGET static __m128i mp3d_v_addsat(__m128i a, __m128i b) FL_NO_EXCEPT
190{
191 const __m128i sum = _mm_add_epi32(a, b);
192 /* _mm_blendv_epi8 selects per byte on that byte's high bit, so every mask
193 here is broadcast to a full lane with _mm_srai_epi32(.., 31) first --
194 handing it a raw value blends bytes independently and silently produces
195 a wrong answer in the low bits. */
196 const __m128i overflow = _mm_srai_epi32(
197 _mm_and_si128(_mm_xor_si128(a, sum), _mm_xor_si128(b, sum)), 31);
198 const __m128i rail = _mm_blendv_epi8(_mm_set1_epi32(MP3D_SAT_MAX),
199 _mm_set1_epi32(MP3D_SAT_MIN),
200 _mm_srai_epi32(a, 31));
201 return _mm_max_epi32(_mm_blendv_epi8(sum, rail, overflow),
202 _mm_set1_epi32(MP3D_SAT_MIN));
203}
204
205MP3D_SIMD_TARGET static __m128i mp3d_v_subsat(__m128i a, __m128i b) FL_NO_EXCEPT
206{
207 const __m128i diff = _mm_sub_epi32(a, b);
208 const __m128i overflow = _mm_srai_epi32(
209 _mm_and_si128(_mm_xor_si128(a, b), _mm_xor_si128(a, diff)), 31);
210 const __m128i rail = _mm_blendv_epi8(_mm_set1_epi32(MP3D_SAT_MAX),
211 _mm_set1_epi32(MP3D_SAT_MIN),
212 _mm_srai_epi32(a, 31));
213 return _mm_max_epi32(_mm_blendv_epi8(diff, rail, overflow),
214 _mm_set1_epi32(MP3D_SAT_MIN));
215}
216
217/* value * Q`bits` coefficient, rounded and saturated. SSE has no arithmetic
218 64-bit shift before AVX-512, so the sign bits are folded back in by hand;
219 and no 64-bit compare before SSE4.2, so the narrow detects out-of-range by
220 checking that the high word is the sign extension of the low one. */
221MP3D_SIMD_TARGET static __m128i mp3d_v_narrow(__m128i a01, __m128i a23,
222 int bits) FL_NO_EXCEPT
223{
224 const __m128i round = _mm_set1_epi64x((int64_t)1 << (bits - 1));
225 __m128i p01 = _mm_add_epi64(a01, round);
226 __m128i p23 = _mm_add_epi64(a23, round);
227 const __m128i sign01 =
228 _mm_srai_epi32(_mm_shuffle_epi32(p01, _MM_SHUFFLE(3, 3, 1, 1)), 31);
229 const __m128i sign23 =
230 _mm_srai_epi32(_mm_shuffle_epi32(p23, _MM_SHUFFLE(3, 3, 1, 1)), 31);
231 p01 = _mm_or_si128(_mm_srli_epi64(p01, bits),
232 _mm_slli_epi64(sign01, 64 - bits));
233 p23 = _mm_or_si128(_mm_srli_epi64(p23, bits),
234 _mm_slli_epi64(sign23, 64 - bits));
235 {
236 const __m128i lo = _mm_castps_si128(
237 _mm_shuffle_ps(_mm_castsi128_ps(p01), _mm_castsi128_ps(p23),
238 _MM_SHUFFLE(2, 0, 2, 0)));
239 const __m128i hi = _mm_castps_si128(
240 _mm_shuffle_ps(_mm_castsi128_ps(p01), _mm_castsi128_ps(p23),
241 _MM_SHUFFLE(3, 1, 3, 1)));
242 const __m128i in_range = _mm_cmpeq_epi32(hi, _mm_srai_epi32(lo, 31));
243 const __m128i rail = _mm_blendv_epi8(_mm_set1_epi32(MP3D_SAT_MAX),
244 _mm_set1_epi32(MP3D_SAT_MIN),
245 _mm_srai_epi32(hi, 31));
246 return _mm_max_epi32(_mm_blendv_epi8(rail, lo, in_range),
247 _mm_set1_epi32(MP3D_SAT_MIN));
248 }
249}
250MP3D_SIMD_TARGET static __m128i mp3d_v_mulshift(__m128i v, int32_t coef,
251 int bits) FL_NO_EXCEPT
252{
253 const __m128i c = _mm_set1_epi32(coef);
254 const __m128i s = _mm_shuffle_epi32(v, _MM_SHUFFLE(3, 1, 2, 0));
255 return mp3d_v_narrow(_mm_mul_epi32(s, c),
256 _mm_mul_epi32(_mm_srli_si128(s, 4), c), bits);
257}
258/* Vector-by-vector: both operands need the even-lane shuffle here, unlike
259 MP3D_V_MUL_LO/HI whose second operand is a splat. */
260#define MP3D_V_MULV_LO(a, b) \
261 _mm_mul_epi32(MP3D_V_PREP(a), MP3D_V_PREP(b))
262#define MP3D_V_MULV_HI(a, b) \
263 _mm_mul_epi32(_mm_srli_si128(MP3D_V_PREP(a), 4), \
264 _mm_srli_si128(MP3D_V_PREP(b), 4))
265#define MP3D_V_REV4(v) _mm_shuffle_epi32((v), _MM_SHUFFLE(0, 1, 2, 3))
266#define MP3D_V_ADDSAT(a, b) mp3d_v_addsat((a), (b))
267#define MP3D_V_SUBSAT(a, b) mp3d_v_subsat((a), (b))
268#define MP3D_V_MULSHIFT(v, coef, bits) mp3d_v_mulshift((v), (coef), (bits))
269#endif
270
271#endif /* MP3D_HAVE_INT_SIMD */
272
273#if MP3D_HAVE_INT_SIMD
274/* Four bands of the DCT-32 at once.
275
276 grbuf is laid out band-major, so `y[i*18]` for four consecutive k values is
277 four consecutive int32 -- the same property upstream's float kernel relies
278 on, and the reason this vectorises without gathers.
279
280 Transcribed operation for operation from the scalar version below rather
281 than re-associated. That matters here in a way it did not for the polyphase:
282 every add saturates and every multiply rounds, so reordering them is
283 observable, and #4055's gate is exact equality with the scalar path. */
284/* The closing twiddle-and-window loop of L3_imdct36 (#4109).
285
286 Only this part of the IMDCT is addressable. The 9-point DCT-III above it has
287 a butterfly that does not map onto four lanes, and vectorising across bands
288 would need stride-18 gathers -- the IMDCT works *within* a band, the opposite
289 of the DCT-32 below, where four consecutive bands are four consecutive int32
290 and no gather is needed.
291
292 Nine iterations is two vectors plus a scalar tail. Each output sums two
293 int64 products and narrows once, exactly as the scalar path does. The
294 reversed grbuf[17 - i] store is a lane reverse, the shape upstream's float
295 kernel uses VREV for. */
296MP3D_SIMD_TARGET static void mp3d_imdct36_twiddle_simd(
297 int32_t *grbuf, int32_t *overlap, const int32_t *window,
298 const int32_t *co, const int32_t *si) FL_NO_EXCEPT
299{
300 int i;
301 for (i = 0; i <= 4; i += 4)
302 {
303 const mp3d_i32x4 cov = MP3D_V_LOAD4(&co[i]);
304 const mp3d_i32x4 siv = MP3D_V_LOAD4(&si[i]);
305 const mp3d_i32x4 tlo = MP3D_V_LOAD4(&g_twid9_q30[0 + i]);
306 const mp3d_i32x4 thi = MP3D_V_LOAD4(&g_twid9_q30[9 + i]);
307 const mp3d_i32x4 ovl = MP3D_V_LOAD4(&overlap[i]);
308 const mp3d_i32x4 wlo = MP3D_V_LOAD4(&window[0 + i]);
309 const mp3d_i32x4 whi = MP3D_V_LOAD4(&window[9 + i]);
310 const mp3d_i32x4 sum = mp3d_v_narrow(
311 MP3D_V_ADD64(MP3D_V_MULV_LO(cov, thi), MP3D_V_MULV_LO(siv, tlo)),
312 MP3D_V_ADD64(MP3D_V_MULV_HI(cov, thi), MP3D_V_MULV_HI(siv, tlo)),
313 30);
314 const mp3d_i32x4 nov = mp3d_v_narrow(
315 MP3D_V_SUB64(MP3D_V_MULV_LO(cov, tlo), MP3D_V_MULV_LO(siv, thi)),
316 MP3D_V_SUB64(MP3D_V_MULV_HI(cov, tlo), MP3D_V_MULV_HI(siv, thi)),
317 30);
318 const mp3d_i32x4 head = mp3d_v_narrow(
319 MP3D_V_SUB64(MP3D_V_MULV_LO(ovl, wlo), MP3D_V_MULV_LO(sum, whi)),
320 MP3D_V_SUB64(MP3D_V_MULV_HI(ovl, wlo), MP3D_V_MULV_HI(sum, whi)),
321 30);
322 const mp3d_i32x4 tail = mp3d_v_narrow(
323 MP3D_V_ADD64(MP3D_V_MULV_LO(ovl, whi), MP3D_V_MULV_LO(sum, wlo)),
324 MP3D_V_ADD64(MP3D_V_MULV_HI(ovl, whi), MP3D_V_MULV_HI(sum, wlo)),
325 30);
326
327 /* overlap[i] was read into ovl above, before this overwrites it. */
328 MP3D_V_STORE4(&overlap[i], nov);
329 MP3D_V_STORE4(&grbuf[i], head);
330 /* lanes 0..3 belong at 17-i down to 14-i: reversed, based at 14-i. */
331 MP3D_V_STORE4(&grbuf[14 - i], MP3D_V_REV4(tail));
332 }
333 {
334 const int32_t ovl = overlap[8];
335 const int32_t sum = mp3d_narrow_q30(
336 (int64_t)co[8]*g_twid9_q30[9 + 8] +
337 (int64_t)si[8]*g_twid9_q30[0 + 8]);
338 overlap[8] = mp3d_narrow_q30(
339 (int64_t)co[8]*g_twid9_q30[0 + 8] -
340 (int64_t)si[8]*g_twid9_q30[9 + 8]);
341 grbuf[8] = mp3d_narrow_q30(
342 (int64_t)ovl*window[0 + 8] - (int64_t)sum*window[9 + 8]);
343 grbuf[9] = mp3d_narrow_q30(
344 (int64_t)ovl*window[9 + 8] + (int64_t)sum*window[0 + 8]);
345 }
346}
347
348MP3D_SIMD_TARGET static void mp3d_dct2_bands4(int32_t *grbuf, int k) FL_NO_EXCEPT
349{
350 mp3d_i32x4 t[4][8], *x;
351 int32_t *y = grbuf + k;
352 int i;
353
354 for (x = t[0], i = 0; i < 8; i++, x++)
355 {
356 const mp3d_i32x4 x0 = MP3D_V_LOAD4(&y[i*18]);
357 const mp3d_i32x4 x1 = MP3D_V_LOAD4(&y[(15 - i)*18]);
358 const mp3d_i32x4 x2 = MP3D_V_LOAD4(&y[(16 + i)*18]);
359 const mp3d_i32x4 x3 = MP3D_V_LOAD4(&y[(31 - i)*18]);
360 const mp3d_i32x4 t0 = MP3D_V_ADDSAT(x0, x3);
361 const mp3d_i32x4 t1 = MP3D_V_ADDSAT(x1, x2);
362 const mp3d_i32x4 t2 =
363 MP3D_V_MULSHIFT(MP3D_V_SUBSAT(x1, x2), g_sec_q27[3*i + 0], 27);
364 const mp3d_i32x4 t3 =
365 MP3D_V_MULSHIFT(MP3D_V_SUBSAT(x0, x3), g_sec_q27[3*i + 1], 27);
366 x[0] = MP3D_V_ADDSAT(t0, t1);
367 x[8] = MP3D_V_MULSHIFT(MP3D_V_SUBSAT(t0, t1), g_sec_q27[3*i + 2], 27);
368 x[16] = MP3D_V_ADDSAT(t3, t2);
369 x[24] = MP3D_V_MULSHIFT(MP3D_V_SUBSAT(t3, t2), g_sec_q27[3*i + 2], 27);
370 }
371 for (x = t[0], i = 0; i < 4; i++, x += 8)
372 {
373 mp3d_i32x4 x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3];
374 mp3d_i32x4 x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7], xt;
375 xt = MP3D_V_SUBSAT(x0, x7); x0 = MP3D_V_ADDSAT(x0, x7);
376 x7 = MP3D_V_SUBSAT(x1, x6); x1 = MP3D_V_ADDSAT(x1, x6);
377 x6 = MP3D_V_SUBSAT(x2, x5); x2 = MP3D_V_ADDSAT(x2, x5);
378 x5 = MP3D_V_SUBSAT(x3, x4); x3 = MP3D_V_ADDSAT(x3, x4);
379 x4 = MP3D_V_SUBSAT(x0, x3); x0 = MP3D_V_ADDSAT(x0, x3);
380 x3 = MP3D_V_SUBSAT(x1, x2); x1 = MP3D_V_ADDSAT(x1, x2);
381 x[0] = MP3D_V_ADDSAT(x0, x1);
382 x[4] = MP3D_V_MULSHIFT(MP3D_V_SUBSAT(x0, x1), MP3D_Q31_COS_PI_4, 31);
383 x5 = MP3D_V_ADDSAT(x5, x6);
384 x6 = MP3D_V_MULSHIFT(MP3D_V_ADDSAT(x6, x7), MP3D_Q31_COS_PI_4, 31);
385 x7 = MP3D_V_ADDSAT(x7, xt);
386 x3 = MP3D_V_MULSHIFT(MP3D_V_ADDSAT(x3, x4), MP3D_Q31_COS_PI_4, 31);
387 x5 = MP3D_V_SUBSAT(x5, MP3D_V_MULSHIFT(x7, MP3D_Q31_TAN_PI_16, 31));
388 x7 = MP3D_V_ADDSAT(x7, MP3D_V_MULSHIFT(x5, MP3D_Q31_SIN_PI_8, 31));
389 x5 = MP3D_V_SUBSAT(x5, MP3D_V_MULSHIFT(x7, MP3D_Q31_TAN_PI_16, 31));
390 x0 = MP3D_V_SUBSAT(xt, x6); xt = MP3D_V_ADDSAT(xt, x6);
391 x[1] = MP3D_V_MULSHIFT(MP3D_V_ADDSAT(xt, x7), MP3D_Q29_SEC_PI_16, 29);
392 x[2] = MP3D_V_MULSHIFT(MP3D_V_ADDSAT(x4, x3), MP3D_Q29_SEC_PI_8, 29);
393 x[3] = MP3D_V_MULSHIFT(MP3D_V_SUBSAT(x0, x5), MP3D_Q29_SEC_3PI_16, 29);
394 x[5] = MP3D_V_MULSHIFT(MP3D_V_ADDSAT(x0, x5), MP3D_Q29_SEC_5PI_16, 29);
395 x[6] = MP3D_V_MULSHIFT(MP3D_V_SUBSAT(x4, x3), MP3D_Q29_SEC_3PI_8, 29);
396 x[7] = MP3D_V_MULSHIFT(MP3D_V_SUBSAT(xt, x7), MP3D_Q29_SEC_7PI_16, 29);
397 }
398 for (i = 0; i < 7; i++, y += 4*18)
399 {
400 MP3D_V_STORE4(&y[0*18], t[0][i]);
401 MP3D_V_STORE4(&y[1*18], MP3D_V_ADDSAT(MP3D_V_ADDSAT(t[2][i], t[3][i]),
402 t[3][i + 1]));
403 MP3D_V_STORE4(&y[2*18], MP3D_V_ADDSAT(t[1][i], t[1][i + 1]));
404 MP3D_V_STORE4(&y[3*18],
405 MP3D_V_ADDSAT(MP3D_V_ADDSAT(t[2][i + 1], t[3][i]),
406 t[3][i + 1]));
407 }
408 MP3D_V_STORE4(&y[0*18], t[0][7]);
409 MP3D_V_STORE4(&y[1*18], MP3D_V_ADDSAT(t[2][7], t[3][7]));
410 MP3D_V_STORE4(&y[2*18], t[1][7]);
411 MP3D_V_STORE4(&y[3*18], t[3][7]);
412}
413#endif /* MP3D_HAVE_INT_SIMD */
414
415/* Dispatches to the vector kernel when the host has the instructions and runs
416 the scalar loop otherwise. Both compute each output as two int64 products
417 narrowed once; rounding per product would differ between the two paths in
418 the low bit, and #4055's gate is exact equality. */
419/* -O3 for the same reason as mp3d_DCT_II above; the two were measured
420 together. */
421MP3D_KERNEL static void mp3d_imdct36_twiddle(int32_t *grbuf, int32_t *overlap,
422 const int32_t *window, const int32_t *co,
423 const int32_t *si) FL_NO_EXCEPT
424{
425 int i;
426#if MP3D_SIMD_KERNELS_LIVE
427 if (MP3D_SIMD_AVAILABLE())
428 {
429 mp3d_imdct36_twiddle_simd(grbuf, overlap, window, co, si);
430 return;
431 }
432#endif
433 for (i = 0; i < 9; i++)
434 {
435 const int32_t ovl = overlap[i];
436 const int32_t sum = mp3d_narrow_q30(
437 (int64_t)co[i]*g_twid9_q30[9 + i] +
438 (int64_t)si[i]*g_twid9_q30[0 + i]);
439 overlap[i] = mp3d_narrow_q30(
440 (int64_t)co[i]*g_twid9_q30[0 + i] -
441 (int64_t)si[i]*g_twid9_q30[9 + i]);
442 grbuf[i] = mp3d_narrow_q30(
443 (int64_t)ovl*window[0 + i] - (int64_t)sum*window[9 + i]);
444 grbuf[17 - i] = mp3d_narrow_q30(
445 (int64_t)ovl*window[9 + i] + (int64_t)sum*window[0 + i]);
446 }
447}
448
449/* Forced to -O3 for the device, not for the host.
450
451 Every ESP-IDF build in this project compiles at -Os
452 (CONFIG_COMPILER_OPTIMIZATION_SIZE=y), and on these three kernels that is
453 the wrong trade: they are straight-line butterfly code with no loop that
454 -Os is protecting anyone from. Raising just them, on top of the polyphase
455 rework below, is 41,998 -> 41,634 us on an ESP32-C6 -- 0.87%, about three
456 times the flash-to-flash drift of the Helix reference measured alongside
457 it -- for 1,310 bytes of .text (14,564 -> 15,874, +9.0%). Output is
458 bit-identical: the ESP32-C6's combined FNV-1a over the decoded PCM is
459 0xc6b632ab either way.
460
461 ci/codec_cpu/callgrind.py cannot see this at all, because the host harness
462 already builds at -O2. It is one of the few changes in this file that has
463 to be decided on hardware.
464
465 Deliberately not applied to mp3d_synth: there -O3 unrolls the four-lane
466 chain to 3,007 instructions and 798 memory operations against the 998 and
467 188 the hand-written pair form below produces, and .text goes to 20,748
468 bytes. The unroll is the thing that helps, and doing it by hand is both
469 smaller and faster than asking for it. */
470MP3D_KERNEL static void mp3d_DCT_II(int32_t *grbuf, int n) FL_NO_EXCEPT
471{
472 int i, k = 0;
473#if MP3D_HAVE_INT_SIMD
474 if (MP3D_SIMD_AVAILABLE())
475 {
476 for (; k + 4 <= n; k += 4)
477 {
478 mp3d_dct2_bands4(grbuf, k);
479 }
480 }
481#endif
482 for (; k < n; k++)
483 {
484 int32_t t[4][8], *x, *y = grbuf + k;
485
486 /* Pass 1, spelled out rather than written as a loop over i.
487
488 gcc unrolls it either way -- it was 443 riscv32 instructions inside
489 the k loop before this, with no backward branch -- so the unroll is
490 not what this buys. What it buys is `i` being a template argument,
491 which makes g_sec_q27[3*i + k] a constant expression and lets each
492 of the four multiplies use mp3d_mulshift_k. Written as a loop the
493 secant is an int32 the compiler happens to know the value of, which
494 is not the same thing: a template argument can pick the integer and
495 fractional parts apart at compile time, and a value cannot.
496
497 g_sec_q27 stays the single definition of these numbers -- the
498 coefficients are read from it here, not transcribed -- which is why
499 it is `constexpr`. The SIMD kernel still indexes it at run time. */
500#define MP3D_DCT_PASS1(I) \
501 do { \
502 const int32_t x0 = y[(I)*18]; \
503 const int32_t x1 = y[(15 - (I))*18]; \
504 const int32_t x2 = y[(16 + (I))*18]; \
505 const int32_t x3 = y[(31 - (I))*18]; \
506 const int32_t t0 = mp3d_add_sat(x0, x3); \
507 const int32_t t1 = mp3d_add_sat(x1, x2); \
508 const int32_t t2 = mp3d_mulshift_k<27, g_sec_q27[3*(I) + 0]>( \
509 mp3d_sub_sat(x1, x2)); \
510 const int32_t t3 = mp3d_mulshift_k<27, g_sec_q27[3*(I) + 1]>( \
511 mp3d_sub_sat(x0, x3)); \
512 t[0][I] = mp3d_add_sat(t0, t1); \
513 t[1][I] = mp3d_mulshift_k<27, g_sec_q27[3*(I) + 2]>( \
514 mp3d_sub_sat(t0, t1)); \
515 t[2][I] = mp3d_add_sat(t3, t2); \
516 t[3][I] = mp3d_mulshift_k<27, g_sec_q27[3*(I) + 2]>( \
517 mp3d_sub_sat(t3, t2)); \
518 } while (0)
527#undef MP3D_DCT_PASS1
528 for (x = t[0], i = 0; i < 4; i++, x += 8)
529 {
530 int32_t x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3];
531 int32_t x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7], xt;
532 xt = mp3d_sub_sat(x0, x7); x0 = mp3d_add_sat(x0, x7);
533 x7 = mp3d_sub_sat(x1, x6); x1 = mp3d_add_sat(x1, x6);
534 x6 = mp3d_sub_sat(x2, x5); x2 = mp3d_add_sat(x2, x5);
535 x5 = mp3d_sub_sat(x3, x4); x3 = mp3d_add_sat(x3, x4);
536 x4 = mp3d_sub_sat(x0, x3); x0 = mp3d_add_sat(x0, x3);
537 x3 = mp3d_sub_sat(x1, x2); x1 = mp3d_add_sat(x1, x2);
538 x[0] = mp3d_add_sat(x0, x1);
539 x[4] = mp3d_mulshift_k<31, MP3D_Q31_COS_PI_4>(mp3d_sub_sat(x0, x1));
540 x5 = mp3d_add_sat(x5, x6);
541 x6 = mp3d_mulshift_k<31, MP3D_Q31_COS_PI_4>(mp3d_add_sat(x6, x7));
542 x7 = mp3d_add_sat(x7, xt);
543 x3 = mp3d_mulshift_k<31, MP3D_Q31_COS_PI_4>(mp3d_add_sat(x3, x4));
544 /* rotate by PI/8 */
545 x5 = mp3d_sub_sat(x5, mp3d_mulshift_k<31, MP3D_Q31_TAN_PI_16>(x7));
546 x7 = mp3d_add_sat(x7, mp3d_mulshift_k<31, MP3D_Q31_SIN_PI_8>(x5));
547 x5 = mp3d_sub_sat(x5, mp3d_mulshift_k<31, MP3D_Q31_TAN_PI_16>(x7));
548 x0 = mp3d_sub_sat(xt, x6); xt = mp3d_add_sat(xt, x6);
549 x[1] = mp3d_mulshift_k<29, MP3D_Q29_SEC_PI_16>(mp3d_add_sat(xt, x7));
550 x[2] = mp3d_mulshift_k<29, MP3D_Q29_SEC_PI_8>(mp3d_add_sat(x4, x3));
551 x[3] = mp3d_mulshift_k<29, MP3D_Q29_SEC_3PI_16>(mp3d_sub_sat(x0, x5));
552 x[5] = mp3d_mulshift_k<29, MP3D_Q29_SEC_5PI_16>(mp3d_add_sat(x0, x5));
553 x[6] = mp3d_mulshift_k<29, MP3D_Q29_SEC_3PI_8>(mp3d_sub_sat(x4, x3));
554 x[7] = mp3d_mulshift_k<29, MP3D_Q29_SEC_7PI_16>(mp3d_sub_sat(xt, x7));
555 }
556 for (i = 0; i < 7; i++, y += 4*18)
557 {
558 y[0*18] = t[0][i];
559 y[1*18] = mp3d_add_sat(mp3d_add_sat(t[2][i], t[3][i]), t[3][i + 1]);
560 y[2*18] = mp3d_add_sat(t[1][i], t[1][i + 1]);
561 y[3*18] = mp3d_add_sat(mp3d_add_sat(t[2][i + 1], t[3][i]), t[3][i + 1]);
562 }
563 y[0*18] = t[0][7];
564 y[1*18] = mp3d_add_sat(t[2][7], t[3][7]);
565 y[2*18] = t[1][7];
566 y[3*18] = t[3][7];
567 }
568}
569
570/* Q(MINIMP3_FRAC_BITS) accumulator to int16, reproducing the float build's
571 rounding exactly: add a half, truncate toward zero, then step away from zero
572 for negatives ("away from zero, to be compliant"). Matching it matters --
573 the fixed-vs-float gate is measured in LSBs, and a different rounding rule
574 alone would put a one-LSB difference on roughly every sample.
575
576 The clamp happens after the shift rather than before it, which is what keeps
577 all but one operation here in 32 bits. This is an exact rewrite, not an
578 approximation, and the equivalence is worth spelling out because the
579 thresholds look like they moved:
580
581 - The old upper test fired at sample >= 32766.5 LSB. At exactly that point
582 t is 32767 LSB, so the shift yields s = 32767 and the new `s > 32767`
583 clamp returns 32767 -- and above it, more. Below it t < 32767 LSB, so
584 s <= 32766 and the clamp cannot fire. Same partition, same answers.
585 - The old lower test fired at sample <= -32767.5 LSB, where -t is at least
586 32767 LSB; truncation gives s <= -32767 and the `s -= (s < 0)` step puts
587 it at -32768 or beyond, which the new clamp returns. Above it -t is under
588 32767 LSB, so s >= -32767 after the step and the clamp cannot fire.
589
590 The shift result always fits in int32 long before the clamp needs to
591 consider it: the polyphase accumulator is bounded by 2^31 * 178833 < 2^48.5
592 (the largest window row, summed in absolute value), so `t >> 26` is at most
593 about 2^22.5.
594
595 What this buys is two 64-bit comparisons against 64-bit constants, which a
596 32-bit target pays for in a compare/branch pair per half. Measured out of
597 line on riscv32 -Os the function drops from 42 instructions to 29, and the
598 host callgrind total from 283.0M to 276.2M. It is a small win on its own --
599 0.8% of Layer III on the C6, against the 50% the force-inlining above is
600 worth -- but it is free. Nothing about the arithmetic
601 changes -- the CPU audit checksum is byte-identical either way, and the
602 ESP32-C6 reports the same combined FNV-1a over the decoded PCM. */
603#define MP3D_PCM_HALF ((int64_t)1 << (MINIMP3_FRAC_BITS - 1))
604
606{
607 const int64_t t = sample + MP3D_PCM_HALF;
608 int32_t s = (int32_t)(t >= 0 ? (t >> MINIMP3_FRAC_BITS)
609 : -((-t) >> MINIMP3_FRAC_BITS));
610 s -= (s < 0);
611 if (s > 32767) return (int16_t) 32767;
612 if (s < -32768) return (int16_t)-32768;
613 return (int16_t)s;
614}
615
616static void mp3d_synth_pair(mp3d_sample_t *pcm, int nch, const int32_t *z) FL_NO_EXCEPT
617{
618 int64_t a;
619 /* The sums and differences are computed with defined wraparound, then
620 widened (FastLED#4133).
621
622 `(int64_t)(x - y)` evaluates `x - y` in int32 first and only then widens,
623 which overflows once the polyphase inputs get large -- undefined
624 behaviour, not merely a wrong number. Ordinary audio never gets there; a
625 malformed intensity-stereo stream does, and UBSan caught it on
626 l3-nonstandard-big-iscf.
627
628 Widening both operands first was the obvious fix and it is the wrong one
629 here: an int64 add or subtract costs two instructions plus carry on a
630 32-bit target, and the codegen ledger measured it at +34% on the Xtensa
631 polyphase inner loop -- 251 instructions against 187 -- which is the
632 hottest kernel on the most constrained platform FastLED targets.
633
634 Unsigned arithmetic wraps by definition in C, so MP3D_WRAP_SUB and
635 MP3D_WRAP_ADD emit exactly the same single 32-bit instruction the
636 undefined version did, and produce exactly the same bits. No well-formed
637 stream reaches the wrap; a malformed one now gets a defined, reproducible
638 answer instead of whatever the optimiser felt entitled to assume. */
639 a = (int64_t)MP3D_WRAP_SUB(z[14*64], z[ 0]) * 29;
640 a += (int64_t)MP3D_WRAP_ADD(z[ 1*64], z[13*64]) * 213;
641 a += (int64_t)MP3D_WRAP_SUB(z[12*64], z[ 2*64]) * 459;
642 a += (int64_t)MP3D_WRAP_ADD(z[ 3*64], z[11*64]) * 2037;
643 a += (int64_t)MP3D_WRAP_SUB(z[10*64], z[ 4*64]) * 5153;
644 a += (int64_t)MP3D_WRAP_ADD(z[ 5*64], z[ 9*64]) * 6574;
645 a += (int64_t)MP3D_WRAP_SUB(z[ 8*64], z[ 6*64]) * 37489;
646 a += (int64_t) z[ 7*64] * 75038;
647 pcm[0] = mp3d_scale_pcm(a);
648
649 z += 2;
650 a = (int64_t)z[14*64] * 104;
651 a += (int64_t)z[12*64] * 1567;
652 a += (int64_t)z[10*64] * 9727;
653 a += (int64_t)z[ 8*64] * 64019;
654 a += (int64_t)z[ 6*64] * -9975;
655 a += (int64_t)z[ 4*64] * -45;
656 a += (int64_t)z[ 2*64] * 146;
657 a += (int64_t)z[ 0*64] * -5;
658 pcm[16*nch] = mp3d_scale_pcm(a);
659}
660
661/* The polyphase back-end is where the pipeline's Q26 samples are scaled back
662 up to int16, and it is the one place a 64-bit accumulator is genuinely
663 required: the window coefficients reach 75038, so a single product already
664 needs 48 bits before sixteen of them are summed. */
665#if MP3D_HAVE_INT_SIMD
666/* One iteration's eight window taps.
667
668 Factored into its own function so the x86 build can put the SSE4.1 target
669 attribute on exactly this code and reach it through a run-time check,
670 without forcing the whole file to be compiled for a baseline the project
671 does not require. The tap order and the arithmetic are identical to the
672 scalar S0/S1/S2 chain: taps 1,3,5,7 accumulate `a` with the operands
673 swapped, which is what S2 does, and the rest follow S0/S1. Starting the
674 accumulators at zero makes S0's assignment and S1's accumulation the same
675 operation. */
676MP3D_SIMD_TARGET static void mp3d_synth_taps(const int32_t *zlin,
677 const int32_t *w, int i,
678 int64_t *a, int64_t *b) FL_NO_EXCEPT
679{
680 mp3d_i64x2 alo = MP3D_V_ZERO64(), ahi = MP3D_V_ZERO64();
681 mp3d_i64x2 blo = MP3D_V_ZERO64(), bhi = MP3D_V_ZERO64();
682 int k;
683
684 for (k = 0; k < 8; k++)
685 {
686 const int32_t w0 = *w++;
687 const int32_t w1 = *w++;
688 const mp3d_i32x4 vz = MP3D_V_PREP(MP3D_V_LOAD4(&zlin[4*i - k*64]));
689 const mp3d_i32x4 vy =
690 MP3D_V_PREP(MP3D_V_LOAD4(&zlin[4*i - (15 - k)*64]));
691 const mp3d_i32x4 s0 = MP3D_V_SPLAT(w0);
692 const mp3d_i32x4 s1 = MP3D_V_SPLAT(w1);
693
694 blo = MP3D_V_ADD64(blo, MP3D_V_ADD64(MP3D_V_MUL_LO(vz, s1),
695 MP3D_V_MUL_LO(vy, s0)));
696 bhi = MP3D_V_ADD64(bhi, MP3D_V_ADD64(MP3D_V_MUL_HI(vz, s1),
697 MP3D_V_MUL_HI(vy, s0)));
698 if (k & 1)
699 {
700 alo = MP3D_V_ADD64(alo, MP3D_V_SUB64(MP3D_V_MUL_LO(vy, s1),
701 MP3D_V_MUL_LO(vz, s0)));
702 ahi = MP3D_V_ADD64(ahi, MP3D_V_SUB64(MP3D_V_MUL_HI(vy, s1),
703 MP3D_V_MUL_HI(vz, s0)));
704 }
705 else
706 {
707 alo = MP3D_V_ADD64(alo, MP3D_V_SUB64(MP3D_V_MUL_LO(vz, s0),
708 MP3D_V_MUL_LO(vy, s1)));
709 ahi = MP3D_V_ADD64(ahi, MP3D_V_SUB64(MP3D_V_MUL_HI(vz, s0),
710 MP3D_V_MUL_HI(vy, s1)));
711 }
712 }
713
714 a[0] = MP3D_V_GET64(alo, 0); a[1] = MP3D_V_GET64(alo, 1);
715 a[2] = MP3D_V_GET64(ahi, 0); a[3] = MP3D_V_GET64(ahi, 1);
716 b[0] = MP3D_V_GET64(blo, 0); b[1] = MP3D_V_GET64(blo, 1);
717 b[2] = MP3D_V_GET64(bhi, 0); b[3] = MP3D_V_GET64(bhi, 1);
718}
719#endif /* MP3D_HAVE_INT_SIMD */
720
721/* Kept out of line deliberately, but for much less than the host says.
722
723 gcc inlines this into mp3d_synth_granule's `for (i = 0; i < nbands; i += 2)`
724 loop at both -O2 and -Os. On an x86-64 host that is a large pessimisation:
725 the polyphase keeps its 64-bit accumulators live across an eight-tap chain,
726 and folded into the granule's frame alongside the DCT-32 inlined just above
727 it the allocator spills them. ci/codec_cpu/callgrind.py on the 892-frame
728 corpus, scalar, -O2: 236,721,305 Ir inlined against 225,422,041 out of
729 line, 4.8% of the whole decode for a keyword, and the ratio to the retired
730 Helix backend goes 1.062x -> 1.011x.
731
732 On the ESP32-C6 -- which is the one that counts -- the same keyword alone
733 moved 46,505 -> 46,402 us. Each of those numbers is stable to 0.00% across
734 repeats, but the *unchanged* Helix reference drifts about 0.9% between
735 flashes, so 0.2% is indistinguishable from zero: on device this buys
736 nothing measurable. Not a contradiction -- -Os allocates the inlined
737 granule differently than -O2 does, and the host spill this removes was
738 never being paid on the device.
739
740 It is kept because it costs six bytes of .text (the granule shrinks by
741 what mp3d_synth takes on) and because it makes the polyphase separately
742 attributable in a profile, not because it is worth the 4.8% the host
743 reports. Anyone reading the host number as a device prediction should read
744 the next comment down instead: the restructuring below it is a 3.2% host
745 *regression* and it is what actually took the device from 1.33x Helix to
746 1.20x. */
747MP3D_HOT void mp3d_synth(int32_t *xl, mp3d_sample_t *dstl, int nch, int32_t *lins) FL_NO_EXCEPT
748{
749 int i;
750 int32_t *xr = xl + 576*(nch - 1);
751 mp3d_sample_t *dstr = dstl + (nch - 1);
752
753 static const int32_t g_win[] = {
754 -1,26,-31,208,218,401,-519,2063,2000,4788,-5517,7134,5959,35640,-39336,74992,
755 -1,24,-35,202,222,347,-581,2080,1952,4425,-5879,7640,5288,33791,-41176,74856,
756 -1,21,-38,196,225,294,-645,2087,1893,4063,-6237,8092,4561,31947,-43006,74630,
757 -1,19,-41,190,227,244,-711,2085,1822,3705,-6589,8492,3776,30112,-44821,74313,
758 -1,17,-45,183,228,197,-779,2075,1739,3351,-6935,8840,2935,28289,-46617,73908,
759 -1,16,-49,176,228,153,-848,2057,1644,3004,-7271,9139,2037,26482,-48390,73415,
760 -2,14,-53,169,227,111,-919,2032,1535,2663,-7597,9389,1082,24694,-50137,72835,
761 -2,13,-58,161,224,72,-991,2001,1414,2330,-7910,9592,70,22929,-51853,72169,
762 -2,11,-63,154,221,36,-1064,1962,1280,2006,-8209,9750,-998,21189,-53534,71420,
763 -2,10,-68,147,215,2,-1137,1919,1131,1692,-8491,9863,-2122,19478,-55178,70590,
764 -3,9,-73,139,208,-29,-1210,1870,970,1388,-8755,9935,-3300,17799,-56778,69679,
765 -3,8,-79,132,200,-57,-1283,1817,794,1095,-8998,9966,-4533,16155,-58333,68692,
766 -4,7,-85,125,189,-83,-1356,1759,605,814,-9219,9959,-5818,14548,-59838,67629,
767 -4,7,-91,117,177,-106,-1428,1698,402,545,-9416,9916,-7154,12980,-61289,66494,
768 -5,6,-97,111,163,-127,-1498,1634,185,288,-9585,9838,-8540,11455,-62684,65290
769 };
770 int32_t *zlin = lins + 15*64;
771 const int32_t *w = g_win;
772 /* Lane stride through the four-lane accumulator.
773
774 The four lanes are (left, right) x (subband i, subband i+1). In mono
775 `xr == xl` and `dstr == dstl`, so lanes 1 and 3 recompute lanes 0 and 2
776 and their stores are immediately overwritten by the lane 0/2 stores that
777 follow them -- half the polyphase filterbank, thrown away. Helix avoids
778 it by shipping a separate PolyphaseMono; here it is the same function
779 with a two-lane tap chain, which is worth 34% of a mono decode on an
780 ESP32-C6 (136,546 -> 89,699 us over 60 frames of 16 kHz MPEG-2 Layer III,
781 bit-identical output).
782
783 Nothing else reads the odd lanes in mono. The granule's mono carry loop
784 (`for (i = 0; i < 15*64; i += 2)`) already saves only even indices, so
785 the odd lanes of the history are stale in mono either way -- today they
786 are read and discarded, which is exactly the work this removes.
787
788 The lane step is a literal in each of two copies of the tap chain rather
789 than a variable in one copy. One copy with `j += jstep` was tried first,
790 on the reasoning that riscv32 -Os does not unroll the four-lane loop
791 anyway (mp3d_synth_granule has exactly 32 `mulh` -- 8 taps x 4 products,
792 not 8 x 4 x 4), so the step ought to have been free. Measured on the C6
793 it was not: the stereo Layer III path went 46,294 -> 47,665 us, +3.0%,
794 against a helix reference that moved 0.01% between the same two runs.
795 Two chains with literal steps cost .text and nothing else. */
796#if MP3D_HAVE_INT_SIMD
797 const int use_simd = MP3D_SIMD_AVAILABLE();
798#endif
799
800 zlin[4*15] = xl[18*16];
801 zlin[4*15 + 2] = xl[0];
802
803 zlin[4*31] = xl[1 + 18*16];
804 zlin[4*31 + 2] = xl[1];
805
806 if (nch == 2)
807 {
808 zlin[4*15 + 1] = xr[18*16];
809 zlin[4*15 + 3] = xr[0];
810 zlin[4*31 + 1] = xr[1 + 18*16];
811 zlin[4*31 + 3] = xr[1];
812
813 mp3d_synth_pair(dstr, nch, lins + 4*15 + 1);
814 mp3d_synth_pair(dstr + 32*nch, nch, lins + 4*15 + 64 + 1);
815 }
816 mp3d_synth_pair(dstl, nch, lins + 4*15);
817 mp3d_synth_pair(dstl + 32*nch, nch, lins + 4*15 + 64);
818
819 for (i = 14; i >= 0; i--)
820 {
821/* One tap of one lane pair. S0/S1/S2 are the original chain unchanged: S0
822 opens the accumulators, S1 adds and S2 adds with the `a` operands swapped.
823 The arithmetic, the operand order and the accumulation order are identical
824 to the four-lane form these replaced, which is why the PCM checksum does
825 not move.
826
827 Two things changed, and only the second one is arithmetic-free by accident.
828
829 The chain used to carry all four lanes -- eight int64 accumulators, held in
830 `int64_t a[4], b[4]` -- across the whole eight-tap run. On riscv32 -Os that
831 array is a stack slot, and the tap body paid eight memory operations per
832 tap per lane to reload and rewrite it: of its 35 instructions, four loads
833 and four stores were accumulator traffic and nothing else.
834
835 Halving the live set is necessary but not sufficient. `int64_t a[2], b[2]`
836 with the lane loop left rolled spills exactly as badly, because at -Os gcc
837 does not unroll a two-iteration loop and a variable index into a local array
838 has to be memory. The lanes are therefore written out as named scalars,
839 which is what actually lets the accumulators live in registers.
840
841 One lane at a time was tried too. It removes the spill just as completely
842 but re-reads the window pair and recomputes the two zlin base addresses
843 four times per tap instead of twice, which costs 3.2% on an x86-64 host
844 that had registers to spare and was never paying the spill. The pair keeps
845 both ends. */
846#define LOAD(k) const int32_t w0 = w[2*(k)]; const int32_t w1 = w[2*(k) + 1]; const int32_t *vz = &zlin[4*i + g - (k)*64]; const int32_t *vy = &zlin[4*i + g - (15 - (k))*64];
847#define S0(k, st) { LOAD(k); \
848 b0 = (int64_t)vz[0]*w1 + (int64_t)vy[0]*w0; \
849 a0 = (int64_t)vz[0]*w0 - (int64_t)vy[0]*w1; \
850 if (st == 1) { \
851 b1 = (int64_t)vz[1]*w1 + (int64_t)vy[1]*w0; \
852 a1 = (int64_t)vz[1]*w0 - (int64_t)vy[1]*w1; } }
853#define S1(k, st) { LOAD(k); \
854 b0 += (int64_t)vz[0]*w1 + (int64_t)vy[0]*w0; \
855 a0 += (int64_t)vz[0]*w0 - (int64_t)vy[0]*w1; \
856 if (st == 1) { \
857 b1 += (int64_t)vz[1]*w1 + (int64_t)vy[1]*w0; \
858 a1 += (int64_t)vz[1]*w0 - (int64_t)vy[1]*w1; } }
859#define S2(k, st) { LOAD(k); \
860 b0 += (int64_t)vz[0]*w1 + (int64_t)vy[0]*w0; \
861 a0 += (int64_t)vy[0]*w1 - (int64_t)vz[0]*w0; \
862 if (st == 1) { \
863 b1 += (int64_t)vz[1]*w1 + (int64_t)vy[1]*w0; \
864 a1 += (int64_t)vy[1]*w1 - (int64_t)vz[1]*w0; } }
865/* Lane pair g writes (a, b) to (15 - i, 17 + i) for g == 0 and to the same
866 pair plus 32 samples for g == 2; within the pair, lane 0 is the left
867 channel and lane 1 is dstr, which is dstl + (nch - 1). Both are address
868 arithmetic on the pair index rather than four copies of the chain.
869
870 In mono the odd lane recomputes the even one into the same address, so the
871 step is 2 and lane 1 never runs at all -- a1 and b1 are dead, not merely
872 redundant, and the zeroing below is what tells the compiler so rather than
873 a value anything reads. */
874#define MP3D_SYNTH_CHAIN(st) \
875 { \
876 int g; \
877 for (g = 0; g < 4; g += 2) \
878 { \
879 mp3d_sample_t *d = dstl + g*16*nch; \
880 int64_t a0, b0, a1 = 0, b1 = 0; \
881 S0(0, st) S2(1, st) S1(2, st) S2(3, st) \
882 S1(4, st) S2(5, st) S1(6, st) S2(7, st) \
883 if (st == 1) \
884 { \
885 d[(15 - i)*nch + 1] = mp3d_scale_pcm(a1); \
886 d[(17 + i)*nch + 1] = mp3d_scale_pcm(b1); \
887 } \
888 d[(15 - i)*nch] = mp3d_scale_pcm(a0); \
889 d[(17 + i)*nch] = mp3d_scale_pcm(b0); \
890 } \
891 }
892
893 zlin[4*i] = xl[18*(31 - i)];
894 zlin[4*i + 2] = xl[1 + 18*(31 - i)];
895 zlin[4*(i + 16)] = xl[1 + 18*(1 + i)];
896 zlin[4*(i - 16) + 2] = xl[18*(1 + i)];
897 if (nch == 2)
898 {
899 zlin[4*i + 1] = xr[18*(31 - i)];
900 zlin[4*i + 3] = xr[1 + 18*(31 - i)];
901 zlin[4*(i + 16) + 1] = xr[1 + 18*(1 + i)];
902 zlin[4*(i - 16) + 3] = xr[18*(1 + i)];
903 }
904
905#if MP3D_HAVE_INT_SIMD
906 if (use_simd)
907 {
908 /* The vector kernel still produces all four lanes at once, so it
909 keeps the array form and the store block that goes with it. */
910 int64_t a[4], b[4];
911 mp3d_synth_taps(zlin, w, i, a, b);
912 if (nch == 2)
913 {
914 dstr[(15 - i)*nch] = mp3d_scale_pcm(a[1]);
915 dstr[(17 + i)*nch] = mp3d_scale_pcm(b[1]);
916 dstr[(47 - i)*nch] = mp3d_scale_pcm(a[3]);
917 dstr[(49 + i)*nch] = mp3d_scale_pcm(b[3]);
918 }
919 dstl[(15 - i)*nch] = mp3d_scale_pcm(a[0]);
920 dstl[(17 + i)*nch] = mp3d_scale_pcm(b[0]);
921 dstl[(47 - i)*nch] = mp3d_scale_pcm(a[2]);
922 dstl[(49 + i)*nch] = mp3d_scale_pcm(b[2]);
923 }
924 else
925#endif
926 /* In mono the odd lanes recompute the even ones into the same
927 addresses, so the step is 2 and lanes 1 and 3 never run. */
928 if (nch == 1)
929 {
931 }
932 else
933 {
935 }
936 w += 16;
937 }
938}
939
940static void mp3d_synth_granule(int32_t *qmf_state, int32_t *grbuf, int nbands,
941 int nch, mp3d_sample_t *pcm) FL_NO_EXCEPT
942{
943 int i;
944 int32_t *lins = qmf_state;
945 for (i = 0; i < nch; i++)
946 {
947 mp3d_DCT_II(grbuf + 576*i, nbands);
948 MP3D_STAGE(MINIMP3_STAGE_DCT2, i, grbuf + 576*i, 18*nbands);
949 }
950
951 for (i = 0; i < nbands; i += 2)
952 {
953 mp3d_synth(grbuf + i, pcm + 32*nch*i, nch, lins + i*64);
954 }
955#ifndef MINIMP3_NONSTANDARD_BUT_LOGICAL
956 if (nch == 1)
957 {
958 for (i = 0; i < 15*64; i += 2)
959 {
960 qmf_state[i] = lins[nbands*64 + i];
961 }
962 } else
963#endif /* MINIMP3_NONSTANDARD_BUT_LOGICAL */
964 {
965 memmove(qmf_state, lins + nbands*64, sizeof(int32_t)*15*64);
966 }
967}
int y
Definition simple.h:93
int x
Definition simple.h:92
uint32_t z[NUM_LAYERS]
Definition Fire2023.h:93
static uint32_t t
Definition Luminova.h:55
int16_t mp3d_sample_t
Definition minimp3.h:227
#define MINIMP3_FRAC_BITS
Definition minimp3.h:159
#define MINIMP3_STAGE_DCT2
Definition minimp3.h:170
#define MP3D_Q29_SEC_PI_8
#define MP3D_Q31_COS_PI_4
#define MP3D_Q29_SEC_PI_16
static const int32_t g_twid9_q30[18]
#define MP3D_Q29_SEC_5PI_16
#define MP3D_Q29_SEC_7PI_16
#define MP3D_Q29_SEC_3PI_8
#define MP3D_Q31_SIN_PI_8
#define MP3D_Q29_SEC_3PI_16
static constexpr int32_t g_sec_q27[24]
#define MP3D_Q31_TAN_PI_16
#define MP3D_SYNTH_CHAIN(st)
static MP3D_KERNEL void mp3d_DCT_II(int32_t *grbuf, int n) FL_NO_EXCEPT
#define MP3D_PCM_HALF
static void mp3d_synth_granule(int32_t *qmf_state, int32_t *grbuf, int nbands, int nch, mp3d_sample_t *pcm) FL_NO_EXCEPT
MP3D_HOT void mp3d_synth(int32_t *xl, mp3d_sample_t *dstl, int nch, int32_t *lins) FL_NO_EXCEPT
#define MP3D_DCT_PASS1(I)
static MP3D_KERNEL void mp3d_imdct36_twiddle(int32_t *grbuf, int32_t *overlap, const int32_t *window, const int32_t *co, const int32_t *si) FL_NO_EXCEPT
static void mp3d_synth_pair(mp3d_sample_t *pcm, int nch, const int32_t *z) FL_NO_EXCEPT
MP3D_LEAF mp3d_sample_t mp3d_scale_pcm(int64_t sample) FL_NO_EXCEPT
#define FL_NO_EXCEPT
fl::i16 int16_t
Definition stdint.h:214
fl::i32 int32_t
Definition stdint.h:219
#define round(x)
Definition util.h:11