Files
soothe2-re/notes_giant_fft.md
T

7.1 KiB
Raw Blame History

FUN_18002f980 (giant) — recursive FFT plan/permuation generator

Signature: ulonglong *FUN_18002f980(plan*, log2, src, flags, depth, uVar2, scratch)

Role: builds the FFT stage plan (bit-reverse ordering + sign-flip masks), NOT a numeric executor (no FP math inside; all integer indexing).

Factor split (top)

iVar2 = DAT_181c5e0fc[log2]      // per-log2 factor (4/8/9/13... from static table)
iVar19 = log2 - iVar2            // reduced log2 for subproblem
if iVar19 < 0x12:  pu = FUN_18002e360(iVar19, scr)   // small-plan path (radix2/3 generator)
else:             pu = FUN_18002f980(plan, iVar19, scr, ..., depth+1)   // recurse
*(plan + 0x78 + depth*8) = pu   // store sub-plan ptr per depth
*(plan + 0x38)      = param_6   // (entry stores callback/context)
*(plan + 0x1c)      = (1<<iVar19)*0x10 + 0x3f & ~0x3f   // size budget

Stage iteration

iVar25 = 1 << iVar19
uVar3  = DAT_181c5e15c[log2]     // 2nd factor table (stage exponent bundle)
iVar33 = 1 << (uVar3 - iVar2)    // butterfly count/base
loop groups:
  for (uVar26=1; ...) {
    iVar44 = 1 << (iVar16+2);  iVar32 = 1 << (iVar16+2 + dirflag)
    if (iVar12 == log2) -> radix-8 permute block
      local_900[k]=src[±idx*8]  (and ^= DAT_181c5e1e0 for negative/reflected)
      processed 16 qwords = 8 complex pairs; groups x butterflies
    next stage: uVar26 <<= 2; iVar16 -= 2            // radix-4 sweep
    else branch radix-3: uVar26 <<= 3; iVar16 -= 3
    ...
  }
while (iVar12 < log2)
outer while (uVar24 < (iVar25-1+iVar33)/iVar33)

Key masive consts

  • DAT_181c5e1e0 = sign-flip XOR mask (bit for im part) applied to mirrored indices.
  • DAT_181c5e0fc / 181c5e15c / 181c5e3fc = per-log2 factor tables (read as int, index log2).
  • Base small planner FUN_18002e360: iVar6=1<<log2; if <9 return; else select radix2/3 for log2==4 || >10 -> 2 else 3; loops similarly, returns param_4 (plan tail).
  • Return: puVar10 + (1<<log2)*2 (aligned plan tail after current stage block).

Notes for reimplementation

  • Exact ordering must be transcribed from the index algebra (the branches accumulate a combination of ± vs complemented indices; XOR selects conjugate/negate for 2x recalc).
  • The 7 giants are per-(data type, direct/inverse, maybe r2c/c2r) variants (identical structure; different base-call FUN_18002e360 vs FUN_180023860).
  • Stage kernels executed at runtime are the dispatched vector kernels (see roadmap), driven from this plan's ordering; the transform itself therefore reproduces a specific FFT: split-radix 2/4/8-style in-place butterflies with fused reorder.

Twiddle source (DAT_182616800 = sin-table)

  • 1024 doubles = exact sin(k2pi/1024) (double precision), first half-period 0..512 (rest garbage of neighboring data).
  • Copied to scratch by FUN_180039b00( log2N, dst ): step lVar18 = 1 << (10 - log2N) for N<=1024.
  • So for N = 2^m: twiddle angles sampled from table with stride 2^(10-m). For N>1024 separate (two-pass) branch (unresolved).
  • Stage kernels (FUN_18000bfc0/18000c5e0) consume these with 0x40-complex chunks, cplx-mul=180008440, acc=FUN_1800437c0/180044700.

============ UPDATE 2026-08-20 (P3: code extracted from rt snap, objdump) ============

Raw code extracted from /tmp/snap_rt.bin via handoff/extract_fft.py → /tmp/fft/*.bin.

cplx_mul 0x180008440 = IN-PLACE ELEMENTWISE DOUBLE MULTIPLY (not complex!):

signature (rcx=dst, rdx=src, r8d=count): dst[i] *= src[i] (double). kernel 0x18003fa20: scalar path mulsd, vector path mulpd (4-wide). Corrects the "cplx-mul=180008440" label — the complex multiply is done via SEPARATE re/im passes (interleaved layout), 0x8440 is the scalar elementwise multiply.

twiddle loader FUN_180039b00 (log2N=ecx, dst=rdx):

N = 1<<log2N; count = N/4 (sar $2 after N/2 sar). if log2N > 10: separate two-pass branch (0x180039dc9). else stride = 1 << (10-log2N); copy sin_table[0x182616800 + kstride8] -> dst[k], k=0..N/4-1. sin_table DAT_182616800 = sin(k·2π/1024) for k=0..256 (257 valid doubles, then garbage). => twiddle[k] = sin(k·stride·2π/1024), the quarter-period sample set.

stage kernels FUN_18000bfc0 / 18000c5e0 (butterfly):

prologue stack 0x470 (scratch 0x200×2 doubles). Per 0x40-complex chunk: 0x180140c40(src, scratch, scratch+0x200, 0x40) — load/format twiddle into scratch 0x8440(dst, scratch, 0x40) — dst *= scratch (elementwise) 0x8440(dst, scratch+0x200, 0x40) — dst *= scratch+0x200 then butterfly arithmetic + acc (FUN_1800437c0/044700). Full loop still to transcribe.

dispatcher 0x535a70 → 0x140a10 (scalar) / 0x140a70 (vector) (per NOTES:166).

Second-level dispatch: 0x140a10/140a70 read global index [0x1826159a0] (=4 at capture) then jump through tables: scalar 0x182617548 = {0x180141380, 0x1801413a0, ..., 0x1801414a0} (12 entries, [4]=0x180141400) vector 0x182617588 = {0x180141440, ..., 0x180141560} ([4]=0x1801414c0) These are the per-size/per-type stage kernels (0x1801413xx/1415xx family).

FOURTH level — final kernels (runtime-filled pointer tables):

0x180141400 = jmp *[0x1826181d8]; the tables hold (at capture): 0x1826181d8=0x1802a24c0, 0x182618200=0x1802a4d80, 0x182618228=0x1802a7540, 0x182618250=0x1802b4540, 0x182618278=0x1802c1540, 0x1826182a0=0x1802ce4a0. These 6 are the final split-radix FFT kernels (stack frames 0x328/0x350/0x7c8/0x7c8/0x7f8/0xd48).

  • CRITICAL for bit-exact: kernel 0x1802a24c0 prologue sets MXCSR rounding mode (vstmxcsr/ vldmxcsr, round-to-nearest 0x1f80) AND x87 control word (fnstcw/fldcw) before the FP loop, then restores. So the transform runs under an explicitly-forced rounding mode.

CORRECTION (2026-08-20b): the "final kernels" are vectorized ln(x), NOT FFT

The 6 addresses 0x1802a24c0..0x1802ce4a0 are the plugin's OWN vectorized natural log, not split-radix FFT butterflies (verified by subagent numeric simulation: matches std::log to float precision). The dispatch chain 0x535a70→…→[0x1826181d8] reaches the plugin's runtime math-function table, not the FFT. Three ln variants live around 0x1802a24c0:

  • 0x1802a24c0 = AVX2 float ln (minimax poly, range-reduction via 2/3 magic 0x3f2aaaab, ln2)
  • 0x1802a2fc0 = scalar double ln (Cody-Waite table 128×3 + Taylor), slow path
  • 0x1802a3260 = second ln variant (9-term, split ln2 hi/lo) The earlier "own vectorized sin/cos" reading was WRONG — the constants (0.333366, 0.250047, …, 0.693147=ln2, 2/3, 0.75) are the minimax coefficients of ln(1+x), not sin/cos. Transcribed to dsp/vlog.{hpp,cpp} (namespace vlog, log_f32(src,dst,n)), check ALL OK (max rel err 2.4e-7 vs std::log). This is the exact log used by level-path (logf·8.6859 for dB, log/gamma in LUT curve), so it feeds bit-exact level→dB.

REAL FFT pieces (still to transcribe):

  • stage kernels FUN_18000bfc0 / 18000c5e0 (butterfly) — see above
  • plan generator FUN_18002f980 (split-radix index algebra)
  • actual twiddle usage for small N via FUN_180039b00 sin-table So P3 scope is unchanged (butterfly + plan gen), NOT the ln kernels.

STATUS / scope

Full bit-exact FFT = plan generator (0x2f980 split-radix index algebra) + stage kernels (0xbfc0/0xc5e0 butterfly) + twiddle. ln (vlog) now done. FFT butterfly + plan gen remain.