0
mirror of https://github.com/torvalds/GuitarPedal.git synced 2026-08-19 13:34:01 +00:00
Files
torvalds-GuitarPedal/scripts/check-float.py
Linus Torvalds 0c1b9c3db3 Split Software/ into the four things it actually was
'Software' was the directory everything that was not KiCad ended up in,
which stopped describing anything a while ago - Validation and the web
app are software too.  Worse, it put the shared parts inside the
firmware, where they read as the firmware's own.

They are not.  Effects/ has three consumers built from it: the firmware,
Validation's bench, and the web app's controls, all generated from the
same POT: comments by gen_effects.py.  Audio/ has two - the bench
compiles the same biquads, the same envelope followers and the same
single_sample(), which is the whole reason a measurement on a
workstation says anything about the pedal.  Neither belongs under
Firmware/, so neither is under it any more:

  Effects/    one file per effect
  Audio/      the DSP they are built from, and the audio loop
  Firmware/   the rest of what runs on the pedal, and the submodules
  WebMIDI/    the web app
  scripts/    what the build runs
  Validation/ unchanged
  Hardware/, Documentation/, Images/

CMakeLists.txt and the wrapper Makefile move to the top with them,
because the build now consumes four of those directories and generates
into a fifth.  board.local and build/ come along; MIDI_CC_MAP.md is
generated into Documentation/ rather than into the old Software/ root.

scripts/ goes with the build rather than staying under the firmware,
because six of the ten had nothing to do with the firmware: gen_effects.py
reads Effects/ and writes to three different places, pow2/log2/quarter_sine
generate Audio/'s tables, check-readme.py compares Effects/ against the
README, and server.py serves the web app.  Four of them are invoked from
Validation, which was reaching into Firmware/ for tooling - the same
burying this commit is undoing.  The four that really are about the
firmware are ELF checks the top-level build drives anyway, and a second
scripts directory would only be a second place to look.

C includes say "Audio/foo.h" and the generated map says
"Effects/bar.h", with the repository root on the include path for both
the firmware and the bench.  Spelling the directory out rather than
relying on a bare name is what keeps Audio/cycles.h shimmable: a quoted
include searches the including file's own directory first.

The submodules are renamed as well as moved.  git mv updates their paths
but leaves the section names, and 'Software/pico-sdk' surviving in
.gitmodules would be the word this commit removes, still load-bearing.
That meant the nested modules under pico-sdk too - six .git files
pointing into .git/modules/Software - which is why 'git submodule update
--init --recursive' is worth running once after pulling this.

Verified rather than assumed: a clean configure and build, make check
(failing only on the missing-eeprom case it already failed on),
check-effects, all four analysis pages reproducing every series and
drawing every chart, and a flash to the board that still measures a
routed reverb where it did before.

One latent bug fell out of it.  bench/coeff declared only quarter_sine.h
of the three generated math tables, and Audio/util.h includes pow2.h and
log2.h as well - so building that target with an empty gen/ could never
have worked.  'make bench' builds bench/bench first, which generates all
three, so it stayed hidden until this rebuilt everything from nothing.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2026-08-11 13:48:26 -07:00

81 lines
3.0 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Check that the firmware contains no double-precision arithmetic, and
# no calls into the SDK's math libraries.
#
# The FPU on this part is single precision only. A 'double' that sneaks
# in doesn't fail to build - it quietly pulls in the AAPCS software
# helpers, which are hundreds of cycles where the hardware would have
# taken one. That is why the audio code has its own pow2/log2 tables and
# fastsincos() instead of calling libm, and why -fsingle-precision-constant
# and -Wdouble-promotion are on. This is the backstop for all of it: the
# helpers are named, so if one is in the image we can say which.
#
# The wrappers are the other half. pico_float and pico_double interpose
# on sinf(), pow() and the rest with -Wl,--wrap, so a call to one of them
# is invisible by name: no link error, no warning, it just turns up in
# the binary. We do all of that arithmetic with the generated tables
# instead, so one of these appearing means something started calling libm.
#
# Called as: check-float.py <elf> [nm]
#
import re
import subprocess
import sys
#
# The AAPCS double helpers. Two shapes: everything operating on a double
# ('__aeabi_dadd', '__aeabi_cdcmple', '__aeabi_d2iz'), and the promotions
# into one ('__aeabi_i2d', '__aeabi_f2d').
#
DOUBLE = re.compile(r"^__aeabi_(c?d|(f|i|ui|l|ul)2d$)")
#
# The functions pico_float and pico_double interpose on. Both the double
# name and the float one - pico_float wraps 'sinf', pico_double 'sin'.
#
MATH = """sqrt cos sin tan atan2 exp log ldexp copysign trunc floor ceil
round sincos asin acos atan sinh cosh tanh asinh acosh atanh
exp2 log2 exp10 log10 pow powint hypot cbrt fmod drem remainder
remquo expm1 log1p fma""".split()
WRAPPED = {"__wrap_" + m for m in MATH} | {"__wrap_" + m + "f" for m in MATH}
def main():
elf = sys.argv[1]
nm = (sys.argv[2] if len(sys.argv) > 2 and sys.argv[2]
else "arm-none-eabi-nm")
res = subprocess.run([nm, elf], capture_output=True, text=True)
if res.returncode:
sys.exit(f"check-float: {nm} failed:\n{res.stderr}")
bad = []
for line in res.stdout.splitlines():
field = line.split()
if len(field) < 2:
continue
name = field[-1]
if DOUBLE.match(name):
bad.append((name, "double-precision helper"))
elif name in WRAPPED or name.startswith("__wrap___aeabi_"):
bad.append((name, "libm call routed through pico_float/pico_double"))
if bad:
print(f"check-float: {len(bad)} symbol(s) that should not be here:",
file=sys.stderr)
for name, why in sorted(bad):
print(f" {name} - {why}", file=sys.stderr)
print("\nLook for a bare constant without its 'f', an implicit "
"promotion,\nor a math call that wants doing with a table "
"instead.", file=sys.stderr)
return 1
print("check-float: no doubles, no libm")
return 0
if __name__ == "__main__":
sys.exit(main())