- name
- library-math32
- description
- math32 IEEE single float library: multi-CPU layout (asm/z80, asm/8085, asm/8080, asm/vm1, asm/gbz80), products math32*.lib including ez80_z80 / gbz80 / vm1, rounding policy, div and public inv are restoring 1/x (NR inv bodies unlinked), force rebuild. Use when editing libsrc/math/float/math32 or A/B float divide/mul.
# Library — math32
Home: `libsrc/math/float/math32/`. Docs of record: `libsrc/math/float/math32/readme.md`.
From **v2.5**, link via **`-lm`** or **`--math32`** (same IEEE flags, `-lmath32@{ZCC_LIBCPU}`). Before v2.5, classic `-lm` was genmath and newlib `-lm` was math48.
## 0b. Math32 multi-CPU float library (layout + policy)
Home: `libsrc/math/float/math32/`. Products: `math32.lib` (plain z80) plus
`math32_{z80n,z180,ez80_z80,r2ka,kc160,8085,8080,vm1,gbz80,…}.lib`.
From **v2.5**, **`-lm` and `--math32`** are the same selection
(`-lmath32@{ZCC_LIBCPU}` — e.g. 8085 → `math32_8085`, 8080 →
`math32_8080`, vm1 → `math32_vm1`, gbz80 → `math32_gbz80`, ez80_z80 →
`math32_ez80_z80`; no separate `--math32_8085` flag). Before v2.5,
classic `-lm` linked genmath and newlib `-lm` linked math48. 6-byte
reruns: `--genmath` or `--math48` (sccz80 or 80cc; zsdcc still stores
4-byte IEEE). 8-byte: `--mbf64` with either compiler. ROM stays `-lmz`.
### Layout
| Tree | Role |
|------|------|
| `asm/` | 8080-compatible shared files (const, coeff, load, classify). Classic: `newlibfiles_common_asm.lst` |
| `asm/z80/` | Z80-family cores; shared by z80n/z180/ez80_z80/r2ka/… when the lst points here |
| `asm/8085/` | Stack-only 8085 cores (no EXX / IX / IY); extended opcodes + synthetics. After `rl de`, test exp with `inc d`/`dec d` — RDEL does not write Z. CPU-specific `f32_f2long` / `f32_l_ldexp`. Classic `ftoa`/`ftoe`/`ftog` in this dir (`ld de,sp+n`, LHLX/SHLX, `rl de`). Do **not** write `ld hl,sp+n` (LDSI+`ex`; bad for `sp-32`) |
| `asm/8080/` | Stack-only 8080 cores (original ISA; no 8085 extras). `ld hl,sp+n`; park HL. `f32_f2long` / `f32_l_ldexp` still portable copies. Classic dtoa: `f32_ftoa.asm` + `f32__dtoa_*` |
| `asm/vm1/` | Stack-only KR580VM1 cores (8080 frame; no unknown `pop af`; no 8085 LDSI/RDEL). `math32_vm1.lib`. Dtoa uses LHLX/SHLX; no `rl de` |
| `asm/gbz80/` | Stack-only Game Boy cores (`ld hl,sp+*`, `bit 7` leading-one; no cheap `ex`). CPU-specific `f32_f2long` / `f32_l_ldexp`. Dtoa: native `(hl+)`, CB `rl`/`srl`, `add sp,4` |
| `c/z80/`, `c/8085/`, `c/8080/`, `c/gbz80/`, `c/vm1/` | Higher functions (C → precompiled asm); 8080/8085/gbz80/vm1 higher via **sccz80 only** |
| `newlibfiles_*.lst` | Classic products (`newlibfiles_ez80_z80.lst`, `newlibfiles_gbz80.lst`, …) |
| `math32_z80_common_asm.lst` + `math32_{z80,z80n,z180}_asm.lst` | Newlib clib embed only. No 8085/8080/gbz80/vm1 newlib math32 list |
**CPU-specific** = same *operation* name, different ISA file (same one-op-per-file
map as §0). Do not invent a second taxonomy for 8085 / gbz80.
### eZ80 Z80-mode product (mlt)
eZ80 Z80-mode has the same `mlt` encodings as Z180 (`ED 4C/5C/6C/7C`).
`math32_ez80_z80.lib` is built from **`newlibfiles_ez80_z80.lst`**, which
selects the Z180 mantissa helpers (`f32_z180_mulu_*` / `f32_z180_sqr_*`).
Those helpers are gated:
```asm
IF __CPU_Z180__ | __CPU_EZ80__ | __CPU_EZ80_Z80__
```
| Assemble as | Define set |
|-------------|------------|
| `-mz180` | `__CPU_Z180__` |
| `-mez80` (ADL) | `__CPU_EZ80__` |
| `-mez80_z80` | **`__CPU_EZ80_Z80__`** |
Do **not** build eZ80 from `@newlibfiles_z80.lst` alone: the plain-Z80 helpers
are `IF __CPU_Z80__` only and assemble **empty** on `-mez80_z80`. Suite gate:
`test/suites/math` → `test_math32_ez80_z80.bin`.
### Rounding policy (do not mix casually)
| Class | Policy (current math32) |
|-------|-------------------------|
| **mul / sqr / div / poly / sqrt pack** | **IEEE RNE** on residual below the kept mantissa |
| **mul overflow** | **Signed Inf** (XOR of operand signs). Not NaN. Includes the near-`FLT_MAX` band (exp sum 254/255), not only add-carry. z80 `f32_fsmul32` late overflow must not reload `AF'` after `mulu` (z80n/z180 clobber it) |
| **add / sub** | **Digi jam-sticky**: lost align/overflow bits → OR **1** into mant LSB; pack has no RNE residual |
Long add chains (e.g. n-body energy) are sensitive to add rounding: jam keeps
second-energy error ~1e−6 class; full RNE-on-add has been measured to worsen E1
(~5e−5 class) at higher cost. Prefer matching z80 and 8085 **policy** even when
engines are not bit-identical.
### Divide / inverse (current policy)
| Op | Algorithm | Notes |
|----|-----------|--------|
| **`div` / `m32_fsdiv`** | **Restoring** 24-bit mantissa | z80 + **8085** + **8080** + **gbz80** cores; z80n/z180/ez80_z80 share z80 `asm/z80/f32_fsdiv.asm` |
| **`inv` / `m32_fsinv_fastcall`** | **Restoring** `1/x` | Bridge `asm/f32_fsinv.asm` calls `m32_fsdiv_callee`. The NR source is unchanged in `asm/<cpu>/hist/f32_fsinv.asm` and is not assembled |
| **`invsqrt` / `m32_fsinvsqrt`** | Quake seed + 3× NR | **Still the fastest** `1/sqrt`. Do not replace with `1.0/sqrt` |
| math16 | Public `asm_f16_inv` is the same restoring bridge (`asm/asm_f16_inv.asm`). `invsqrt` stays NR | |
Do **not** reintroduce NR trampoline `fsdiv` = `fsinv`+`fsmul` without A/B proof. Do **not** move `asm/<cpu>/hist/f32_fsinv.asm` back onto a `.lst` while the bridge exports `m32_fsinv_fastcall`.
Docs: `math32/readme.md` § div/inv; measurement: **`methodology-measure`** § A/B.
In **C higher functions** (`c/m32_*.c`):
| Need | Write | Do not write |
|------|--------|----------------|
| Reciprocal `1/n` | `1.0/x` or `inv(x)` (both restoring `m32_fsdiv`) | `asm/<cpu>/hist/f32_fsinv.asm` |
| Inverse square root | `m32_invsqrtf(x)` | `1.0/m32_sqrtf(x)` |
`pow(x, -1)` / `sinh` / `cosh` / `tanh` / `atan` recip / `asinh` / `acosh` use divide. `pow(x, -0.5)` keeps `m32_invsqrtf`.
IEEE bit punning: use **`union float_long`** from `c/m32_math.h` (`float f; int32_t l`). Do not invent a local `uint32_t` union. Assign `NAN_*` / `INFINITY_*` as `(int32_t)NAN_NEG_F32` so zsdcc does not warn on unsigned→signed.
### Micro-opt patterns that port
| Pattern | Idea | Notes |
|---------|------|--------|
| Implicit-1 CF | `ld a,255` / `add a,h` → CF=(exp≠0) instead of `or a` / `jr Z` / `scf` | ~8–9 T per unpack; works on Z80 and 8085 |
| Z80 non-callee stack load | 3×`pop` + 3×`push` vs `hl=sp+2` walk | ~8 T; 8085 stack-slot paths already different |
| Hot tiny helpers | Inline 2–4 insn jam sticky at call sites | Saves call/ret; size often net-neutral or smaller |
| Unused stack pad | Drop frame slots only after proving no SP offsets still use them | Remeasure; fix every `sp+N` comment |
Callee linkage: float helpers that pop a return address + stack args must be
**`call`’d**, not bare **`jp`** (floor/ceil class bugs). Keep that rule when
editing pack/add glue.
### sccz80 + newlib: plain names vs `*_fastcall` (issue #3061)
`math32.lib` products are assembled with **`-D__CLASSIC`**. Under that flag,
`lm32/c/sccz80/{sin,sqrt,log,…}.asm` expose **stack-arg bridges** for plain
names; the **DEHL** entry is `*_fastcall` (`defc sin_fastcall = _m32_sinf`).
| Consumer | What must happen |
|----------|------------------|
| **Classic** `include/math/math_math32.h` | `#define sin(x) sin_fastcall(x)` (and peers) — already correct |
| **Newlib** sccz80 + `--math32` | Same remaps under `__MATH_MATH32` in **`include/_DEVELOPMENT/proto/math.h`**, then `make -C include/_DEVELOPMENT common/math.h` |
| **SDCC** | Single-arg `__DPROTO` already emits `#define sin(a) sin_fastcall(a)` — OK without the sccz80 block |
| **math48** (`--math48`; newlib `-lm` only before v2.5) | Plain `sin` is true DEHL (`cm48_sccz80_sin`) — do **not** force math32 remaps off math48 |
Without the newlib remaps, sccz80 marks plain `sin` as `__z88dk_fastcall` and
emits `call sin` with DEHL, but the linked object is the **stack bridge** (ignores
DEHL). Symptom: hotspots show **zero** `m32_fsinvsqrt` /
`m32_fsmul32x32` entry hits, TIMER “too fast” (e.g. Whetstone ~15 KWIPS vs ~11),
wrong numerics. Map proof of a healthy build: `sin_fastcall` / `sqrt_fastcall`
present; app `.asm` shows `callsin_fastcall` not `callsin`. Public `inv`
lands in `asm/f32_fsinv.asm` and then `m32_fsdiv`. `asm/<cpu>/hist/f32_fsinv.asm` is not assembled.
**Do not** “fair up” classic vs newlib by adding bench-only `invsqrt()` optims
to one side only (n-body). Align source (`1.0/sqrt` vs half `invsqrtf16`) and
remeasure both products after header fixes.
### Higher-function C regen (`c/Makefile`)
Z80 higher funcs: `make -C libsrc/math/float/math32/c` → `c/z80/*.asm` (SDCC).
8085: `make -C …/c 8085` → `c/8085/*.asm` (sccz80 only).
8080: `make -C …/c 8080` → `c/8080/*.asm` (sccz80 only).
vm1: `make -C …/c vm1` → `c/vm1/*.asm` (sccz80 only).
gbz80: `make -C …/c gbz80` → `c/gbz80/*.asm` (sccz80 only). **`make clean`** must
only remove C-derived objects — never wipe hand-written peers in the same dir
(math16: keep `cm16_sccz80_*.asm` under `c/8085/`).
**Measure / rebuild / wiki:** **`methodology-measure`**
(§ math32 rebuild, benches, classic/newlib A/B, wiki bold rules). Docs of record:
`libsrc/math/float/math32/readme.md`, `support/benchmarks/*/readme.txt`.
---
## Force rebuild (multi-CPU)
### Math32 / multi-CPU float libs (force rebuild)
Sources: `libsrc/math/float/math32/` (per-CPU under `asm/z80/`, `asm/8085/`, …).
Shared Z80-family add lives in `asm/z80/d32_fsadd.asm` and is assembled into
عرض على GitHub