The rounding barrier: an identity that the optimiser may not fuse across.
The volatile local is the entire content of this procedure. Without it,
ek_rnd(a * b) + c is just a * b + c -- the exact shape a compiler with FMA contracts,
rounding once where IEEE rounds twice, which changes the result and moves the frozen
fingerprint. volatile obliges the compiler to store x to memory and read it back, so the
product is a rounded real64 by the time the add sees it, on every conforming compiler.
Unlike a noinline directive this is not advice the optimiser may decline, which is why the
procedure is written this way and not that way -- see exp_key's doc-comment for the flang
build that made the difference concrete.
Do not make this pure (a pure procedure may not have a volatile local -- that
restriction is what the directive form was working around), do not drop the volatile, and
do not "simplify" the call sites back to bare arithmetic.
Public because a second module needs the same barrier for the same reason.
parquet_random's polar normal composes sqrt and a division around exp_key, and those
compositions are exposed to exactly the fusions and regroupings this closes -- so it imports
this as ek_round. It is not part of the library's user-facing surface (parquet_expkey is
not re-exported by the parquet facade, and every importer re-hides it), and it is not a
general-purpose utility: it exists to hold ONE expression's grouping, and a caller reaching
for it should be able to name the rewrite it is stopping.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=real64), | intent(in) | :: | x |
the product to round |
the same value, rounded to real64
function ek_rnd(x) result(y) real(real64), intent(in) :: x !! the product to round real(real64) :: y !! the same value, rounded to real64 real(real64), volatile :: t !! the barrier: forces a store and a reload t = x y = t end function ek_rnd