Skip to content

Every shared DSP file except audiodynamics still allows fused multiply-add, so the boards can still disagree per node #79

Description

@bdbarnett

The question

src/shared/audioif_dynamics.c now forbids fused multiply-add, because contraction was the whole of the P4-vs-S3 split on the transient attack path (audioif#66, commit 113531d). Every other shared DSP file still contracts, and the two ESP toolchains demonstrably fuse at different numbers of sites in the same source:

xtensa-esp32s3-elf-gcc -O2   audioif_dynamics.c   29 madd.s / msub.s
riscv32-esp-elf-gcc    -O2   audioif_dynamics.c   30 fmadd.s / fmsub.s / fnm*

There is no reason to think dynamics is the only file where that matters. It is the only one anybody has looked at.

Why this is not obviously a "just do it"

docs/correctness-standard.md holds our own nodes to three-target agreement, and that gate currently runs on x86 only — where there is no FMA to fuse without -mfma, so it cannot see this class of difference at all. Extending the pragma across the kernel would make the boards agree with each other by construction, which is the right end state.

But it would also move board digests for every node that does float DSP, and those digests are what audiocomponents' cost table and the effects program's gates are pinned to. That is a deliberate, measured change, not a one-line sweep.

What it would take, in order

  1. Enumerate. Which of the shared files actually emit fused ops on each toolchain, and how many. Cheap and static — the two counts above took one command each, no board needed.
  2. Measure the cost per file, or at least for the worst offenders. For dynamics it was free: P4 +0.08%, S3 below the noise floor (medians +0.45%, minima −0.67%, means −0.5% over twelve samples). That will not automatically hold for the convolver or the FFT, where the inner loops are tighter and fusing is worth more.
  3. Confirm the boards converge, per node, the way audiodynamics.Dynamics DYN_TRANSIENT attack path: P4 ≠ S3 ≠ desktop on the same probe #66 did — the static count is evidence, board digests are proof.
  4. Re-capture whatever is pinned to the old numbers, naming every digest that moves, and tell audiocomponents before its cost table goes stale.

Worth knowing before starting

  • #pragma STDC FP_CONTRACT OFF — the standard spelling — is silently ignored by GCC. Measured: it still emits vfmadd. Use #pragma GCC optimize("fp-contract=off"), with #pragma clang fp contract(off) for clang.
  • Do not reach for -ffp-contract=off as a build flag on the CMake ports: our flags ride on usermod_mpaudio, an INTERFACE library, so the flag lands on everything that links it — all of MicroPython — to fix a handful of our own files.
  • #pragma GCC optimize is known to reset a file's other optimisation settings on some versions. It did not here — same instruction count, same vectorisation, fused ops 6 → 0 — but check it again rather than assume, especially on a file where vectorisation is doing real work.

Not in scope

The desktop-vs-board difference. With contraction off on the boards and libm bit-identical between newlib and glibc (0 of 28 sampled values differ), the desktop still disagrees with both boards on the dynamics attack path. That is a third mechanism and it belongs to audioif#55, which asks for per-architecture baselines rather than convergence.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions