APA
h0ffmann. (2026). skills [Computer software]. https://github.com/h0ffmann/skills
BibTeX
@software{h0ffmann_skills_2026,
author = {h0ffmann},
title = {skills},
year = {2026},
url = {https://github.com/h0ffmann/skills},
license = {MPL-2.0},
note = {ORCID 0009-0009-1056-7661}
}
Source: CITATION.cff
The traps of translating a WAVEWATCH III Fortran routine to no_std Rust for Rust-CUDA and rust-gpu. Use while translating or reviewing a routine, when a Rust result differs from the Fortran reference by a few ULP, or when choosing a float kind, a math call or a device buffer layout.
← Back to all skills · SKILL.md on GitHub
# ww3-port: translating WW3's Fortran to Rust without changing a bit Readers are scientists: every WW3 fact cites `model/src/<file>.F90:<line>` at `92e2cf66`, and every claim carries `(v)` or `⚠`. There are no Rust tests. `AGENTS.md` wins where this file disagrees, and `docs/FLOAT_REPRESENTATION_202610.md` is the authority on kinds. ww3-gpu's `ww3-port` skill has the longer history; its facts are at NOAA's pin. ## Arithmetic - The target is gfortran `-O3`'s executable. On a ULP difference, build the module with `-fdump-tree-optimized` and read it before suspecting the physics. - A literal power such as `x**11` is a multiplication chain at `-O3`. A run-time `XFR**IFRP` is `__powisf2`. Write each out; never `powi` or `powf` for an integer power. - `-O3` folds with one rounding what it can see, even inside a loop: it peels `DO IKD=1,NKD` and folds `1.01**(1-1000)`, while every later pass calls `__powisf2`. Only `IKD=1` differs, by 2^-15. A difference in one row or the first index suggests this. - `x**2.` is `x*x` at any optimisation level; other real exponents stay `powf`. - `REAL` is `f32`. A literal without `D` is `f32` even inside an `f64` expression. - `d = a*b` is `(a * b) as f64`. Put each `as` where gfortran converts. - `f32::max` hides NaN. Read what gfortran's `MAX` compiled to at that site. - Rust's `a * b + c` is not contracted on the host. The reference needs `-ffp-contract=off` to match (arm64 gfortran fuses by default), and PTX needs `fma_contraction(false)`. - Keep the operation order, reductions included. Asking comes before any change. ## Files - WW3's CMake sets `ENDIANNESS='big_endian'`, so `.ww3` files are big-endian with big-endian record markers, even on arm64. - On `ww3_ts1`, a chain of `W3SRCE` calls needs only `SPEC`, `UST` and `USTDIR` carried over. - Prove a first whole-file match: delete the output, regenerate it, compare hashes, then break it on purpose. A file left over from the reference passes `cmp` too. - `CLATS`, `CLATIS`, `CTHG0S` are `(0:NSEA)`, unlike `ZB`. A case on the equator hides a shift by one, since every `CLATS` is 1: `ww3_tp1.1` passed with it and `ww3_tp1.2` did not. - `MAPSF(:,3)` is not in `mod_def.ww3`: `W3IOGR` fills it on read. - A fault that changes nothing may be a weak fault, not a blind spot: `CFAC=1` discards the clamped `QCN`, and all transparencies are 1 without obstructions. Say which, and find one that reaches. - `UST` starts at `1.E-5` (`W3DIMW`), not 0: a run with source terms hides it, a restart without them shows it. - `W3IOGO` blanks fields to `UNDEF` at dry points and, for some, at `MAPSTA = 2`, and it writes `UST` back. - `W3FLDH`'s fallback tests `TFN`, not `TF0`, so a first input after the start time leaves `TF0` at -1. ## Memory - Fortran bounds survive translation: `SIG(0:NK+1)`, `CG(0:NK+1,0:NSEA)`. Index through a named helper that shows the base. Fortran is column-major: `A(ITH,IK)` is `a[(ith-1) + (ik-1)*nth]`. - WW3 state is per grid (`W3SETG`, `W3SETA`). A file-static table breaks `ww3_multi`. - A condition around an initialisation is part of the translation: `INSNL1` runs only for `IQTPE > 0`, per ww3-gpu; check at this pin ⚠. - Device buffers are `#[repr(C)]`, without `bool`, defined once in `ww3-kernels`. ## Device - No closures in a kernel: one that captures a local by reference is a pointer to a pointer, and SPIR-V validation refuses it. Use a plain function or a local macro. - No sub-slices either: pass the whole buffer and an offset. - No allocation, `std`, formatting or panic that depends on data inside a kernel. Keep slices bounds-checked in `host` builds and read the cost before removing a check. - Math goes through `ww3-libm`. Never `cuda_std`'s `GpuFloat`, never `spirv-std`'s `GLSL.std.450`. - A floating-point fold can come before the driver. rust-gpu's `spirv-opt` folds `(a-c1)-c2` and friends unless `NoContraction` is already on the operation, so `scripts/spirv.sh` turns it off, decorates, then runs `spirv-opt`. A SPIR-V difference of a few ULP is read from the module first: `just spirv-libm` separates a function from a kernel in a minute. - A kernel that needs `f64` declares it, and the host checks the device before starting. - Never run on a device without the owner. ## Reading a routine - Preprocess with the target's switches before reading: `gfortran -E -cpp -P` with `-DW3_<SWITCH>` for each switch cut `W3SRCE` from 2692 lines to about 400, and leaves no chance of translating code this build does not have. ## ww3-libm - A fused site is read from the reference's `libm.so.6` with `objdump`, never predicted. GCC fuses a product with one use, but `atanf` also fuses `s1+s2`, and it shares `x*(s1+s2)` between branches. - Take a constant from fdlibm's decimal, not its hex comment: `aT[0]` says `0x3eaaaaaa`, and the decimal, like the machine code, is `0x3eaaaaab`. - Sweep every `f32`: `sin` differed on 12 inputs of 2^32, and `powf` on 1 pair in 10^9. ## Parity by capture - `regtests/capture/` records a routine's real arguments from the running reference with gdb. Nothing is linked into WW3 and no Fortran is written (ADR-0007). - Run a fault through the comparison before believing its zeros, and make the fault large enough to reach `f32`: the last bit of an `f64` coefficient reached nothing and proved nothing. - A host replay with zero offsets cannot see an offset bug: also replay in one buffer, as a device does. - If every loop of a shader stops after one pass from some point on, suspect llvmpipe's limit, which `docker/Dockerfile.lavapipe` raises. - A table of zeros passes everything: `DCKI` and `QBI` matched on every default capture only because those settings leave them empty. Print the count of nonzero values and read it. - A guard can be out of reach of any run: MetOcean's `W3SRCE` floors `USTAR` at 0.001, so `W3SIN4`'s 0.0001 floor never acts. Show that with a fault run, then poke the input at the routine's entry (`regtests/capture/sin4.py`), so the real Fortran runs that line. - Count what ran. `srce_cmp` prints how many calls had `W3SDB1` and `W3SBT1` active: `W3SDB1` is idle at 12 m/s even in 6 m of water, so its faults mean nothing there. - A fault that nothing catches may be a line with no effect: `USTAR=0.001` at `IT=0` is overwritten by the next `W3SPR4`. Say which it is. - A case can hide a branch. `ww3_ts1` is 2500 m deep, where `EXP(X2)` in `W3SNL1` vanishes: a wrong `expf` still matched there, and only a shallow run caught it. Ask what each input never reaches. ## Before claiming a verdict `translated` means the code exists. `routine` means every captured call matched bit for bit, which does not cover the configurations the capture did not run. `host` means `cmp` and `test.comp` passed on the regtest the batch names, in the reference's linux/arm64 image on Apple Silicon, against the same commit of the reference. A zero difference from a run that never reached the routine proves nothing: check it ran.