3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-03 13:56:08 +00:00

fstar: add fpa_rewriter_rules.h with C++ macros extracted from F* lemmas

Agent-Logs-Url: https://github.com/Z3Prover/z3/sessions/46fcd3bf-be90-4098-8516-fde0f1e37cbf

Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-05 02:01:12 +00:00 committed by GitHub
parent 653c49ac5e
commit 1b73e5f1de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 433 additions and 28 deletions

View file

@ -51,7 +51,8 @@ open IEEE754
(* 1a. fma(rm, ±0, y, NaN) = NaN
Source: C++ fpa_rewriter.cpp, mk_fma, branch "if (m_util.is_nan(arg4))".
The addend NaN propagates (IEEE 754-2019 §6.2, NaN payload rules). *)
The addend NaN propagates (IEEE 754-2019 §6.2, NaN payload rules).
[extract: FPA_REWRITE_FMA_ZERO_MUL, case (a)] *)
let lemma_fma_zero_nan_addend
(#eb #sb: pos)
(rm: rounding_mode) (zero_val y: float eb sb)
@ -62,7 +63,8 @@ let lemma_fma_zero_nan_addend
(* 1b. fma(rm, ±0, NaN, z) = NaN
Source: C++ check "m_fm.is_nan(vo)" for other_mul.
NaN in the second multiplicand propagates regardless of the addend. *)
NaN in the second multiplicand propagates regardless of the addend.
[extract: FPA_REWRITE_FMA_ZERO_MUL, case (b)] *)
let lemma_fma_zero_nan_mul
(#eb #sb: pos)
(rm: rounding_mode) (zero_val z: float eb sb)
@ -74,7 +76,8 @@ let lemma_fma_zero_nan_mul
(* 1c. fma(rm, ±0, ±∞, z) = NaN
Source: C++ check "m_fm.is_inf(vo)" for other_mul.
0 * ∞ is the "invalid operation" NaN (IEEE 754-2019 §7.2),
which then propagates through the addition. *)
which then propagates through the addition.
[extract: FPA_REWRITE_FMA_ZERO_MUL, case (b)] *)
let lemma_fma_zero_inf_mul
(#eb #sb: pos)
(rm: rounding_mode) (zero_val inf_val z: float eb sb)
@ -90,7 +93,8 @@ let lemma_fma_zero_inf_mul
an addition whose first operand is the computed ±0 product.
This is the key decomposition that replaces the expensive FMA
circuit with a cheaper addition. *)
circuit with a cheaper addition.
[extract: FPA_REWRITE_FMA_ZERO_MUL, case (c)] *)
let lemma_fma_zero_finite_decomposes
(#eb #sb: pos)
(rm: rounding_mode) (zero_val y z: float eb sb)
@ -105,7 +109,8 @@ let lemma_fma_zero_finite_decomposes
Source: C++ branch with "m_util.is_numeral(arg4, vz) && !m_fm.is_zero(vz)"
and a concrete finite other_mul.
When z is nonzero and finite, ±0 + z = z exactly under any rounding
mode (IEEE 754-2019 §6.3), so the entire fma collapses to z. *)
mode (IEEE 754-2019 §6.3), so the entire fma collapses to z.
[extract: FPA_REWRITE_FMA_ZERO_MUL, case (c) folded with ax_add_zero_nonzero] *)
let lemma_fma_zero_const_addend
(#eb #sb: pos)
(rm: rounding_mode) (zero_val y z: float eb sb)
@ -123,6 +128,7 @@ let lemma_fma_zero_const_addend
ite(¬isNaN(y) ∧ ¬isInf(y), z_const, NaN)
Source: C++ branch producing "m().mk_ite(finite_cond, arg4, nan)".
We prove each arm of the ite separately.
[extract: FPA_REWRITE_FMA_ZERO_MUL, case (d)]
Finite arm: y is finite → fma = z_const (by 1e). *)
let lemma_fma_zero_const_finite_arm
@ -155,7 +161,8 @@ let lemma_fma_zero_const_nan_inf_arm
ite(is_finite(y), fp_add(rm, product_zero, z), NaN)
Source: C++ block producing the final ite with product_zero.
product_zero is ±0 with sign = sign(±0) XOR sign(y).
We prove each arm. *)
We prove each arm.
[extract: FPA_REWRITE_FMA_ZERO_MUL, case (e)] *)
(* Finite arm: fma = fp_add(rm, fp_mul(rm, ±0, y), z). *)
let lemma_fma_zero_general_finite_arm
@ -217,7 +224,8 @@ let lemma_fma_zero_product_sign
(* isNaN(to_fp(rm, x)) = false for any integer x.
Source: C++ block in mk_is_nan checking "a->get_num_args() == 2 &&
(m_util.au().is_real(a->get_arg(1)) || m_util.au().is_int(a->get_arg(1)))". *)
(m_util.au().is_real(a->get_arg(1)) || m_util.au().is_int(a->get_arg(1)))".
[extract: FPA_REWRITE_IS_NAN_TO_FP_INT] *)
let lemma_is_nan_to_fp_int
(#eb #sb: pos) (rm: rounding_mode) (x: int)
: Lemma (is_nan (to_fp_of_int #eb #sb rm x) = false) =
@ -227,7 +235,8 @@ let lemma_is_nan_to_fp_int
(* --- 2b: isInf --- *)
(* Source: C++ function mk_is_inf_of_int, called from mk_is_inf.
Each lemma corresponds to one case in the switch(rm) statement. *)
Each lemma corresponds to one case in the switch(rm) statement.
[extract: FPA_REWRITE_IS_INF_TO_FP_INT, helper mk_is_inf_of_int] *)
(* RNE: overflow iff |x| >= overflow_threshold(eb, sb).
At the boundary the significand of MAX_FINITE is odd, so RNE rounds
@ -270,6 +279,7 @@ let lemma_is_inf_to_fp_int_rtz
Source: C++ block in mk_is_normal:
"expr_ref not_zero(...); expr_ref inf_cond = mk_is_inf_of_int(...);
result = m().mk_and(not_zero, m().mk_not(inf_cond));"
[extract: FPA_REWRITE_IS_NORMAL_TO_FP_INT]
Proof sketch:
For integer x, to_fp is never NaN (by ax_to_fp_int_not_nan) and
@ -322,7 +332,8 @@ let lemma_is_normal_to_fp_int
(* 3a. isNaN(ite(c, t, e)) = ite(c, isNaN(t), isNaN(e))
Source: C++ block producing "m().mk_ite(c, is_nan(t) ? true : false,
is_nan(e) ? true : false)". *)
is_nan(e) ? true : false)".
[extract: FPA_REWRITE_IS_NAN_ITE] *)
let lemma_is_nan_ite
(#eb #sb: pos) (c: bool) (t e: float eb sb)
: Lemma (is_nan (if c then t else e) =
@ -330,7 +341,8 @@ let lemma_is_nan_ite
(* 3b. isInf(ite(c, t, e)) = ite(c, isInf(t), isInf(e))
Source: same pattern in mk_is_inf. *)
Source: same pattern in mk_is_inf.
[extract: FPA_REWRITE_IS_INF_ITE] *)
let lemma_is_inf_ite
(#eb #sb: pos) (c: bool) (t e: float eb sb)
: Lemma (is_inf (if c then t else e) =
@ -338,7 +350,8 @@ let lemma_is_inf_ite
(* 3c. isNormal(ite(c, t, e)) = ite(c, isNormal(t), isNormal(e))
Source: same pattern in mk_is_normal. *)
Source: same pattern in mk_is_normal.
[extract: FPA_REWRITE_IS_NORMAL_ITE] *)
let lemma_is_normal_ite
(#eb #sb: pos) (c: bool) (t e: float eb sb)
: Lemma (is_normal (if c then t else e) =

View file

@ -20,7 +20,6 @@ that avoid expensive FP bit-blasting:
### `IEEE754.fst`
Abstract axiomatic theory of IEEE 754 floating-point arithmetic.
- **Type** `float eb sb` — abstract float parameterized by exponent bits `eb`
and significand bits `sb`, matching Z3's `mpf` representation.
- **Type** `rounding_mode` — the five IEEE 754 rounding modes (RNE, RNA, RTP,
@ -83,25 +82,53 @@ The Section 3 lemmas are trivially true in F\* by computation (applying a
function to `if c then t else e` reduces to `if c then f t else f e`), which
is why their proofs are the single term `()`.
### `../src/ast/rewriter/fpa_rewriter_rules.h`
C++ header containing one `#define` macro per rewrite rule, extracted from
the F\* lemmas. Each macro is annotated with a `[extract: MACRO_NAME]`
comment in the corresponding F\* lemma.
| Macro | F\* lemma(s) | Used in C++ function |
|---|---|---|
| `FPA_REWRITE_IS_NAN_TO_FP_INT` | `lemma_is_nan_to_fp_int` | `mk_is_nan` |
| `FPA_REWRITE_IS_NAN_ITE` | `lemma_is_nan_ite` | `mk_is_nan` |
| `FPA_REWRITE_IS_INF_TO_FP_INT` | `lemma_is_inf_to_fp_int_*` | `mk_is_inf` |
| `FPA_REWRITE_IS_INF_ITE` | `lemma_is_inf_ite` | `mk_is_inf` |
| `FPA_REWRITE_IS_NORMAL_TO_FP_INT` | `lemma_is_normal_to_fp_int` | `mk_is_normal` |
| `FPA_REWRITE_IS_NORMAL_ITE` | `lemma_is_normal_ite` | `mk_is_normal` |
| `FPA_REWRITE_FMA_ZERO_MUL` | `lemma_fma_zero_*` (all §1 lemmas) | `mk_fma` |
Each macro is self-contained and uses `_fpa_`-prefixed local variables to
avoid name collisions. All macros are designed for use inside member
functions of `fpa_rewriter` where `m()`, `m_util`, `m_fm`, and
`mk_is_inf_of_int` are in scope.
The helper function `mk_is_inf_of_int` (declared in `fpa_rewriter.h`,
implemented in `fpa_rewriter.cpp`) computes the integer-arithmetic overflow
condition for `isInf(to_fp(rm, x))` by switching on the rounding mode,
corresponding to the five `lemma_is_inf_to_fp_int_*` lemmas.
## Relationship to the C++ Code
The following table maps each lemma to the corresponding C++ code in
`src/ast/rewriter/fpa_rewriter.cpp` after the PR is applied.
The following table maps each lemma to the corresponding C++ macro in
`src/ast/rewriter/fpa_rewriter_rules.h` and where it is used.
| Lemma | C++ code |
|---|---|
| `lemma_fma_zero_nan_addend` | `if (m_util.is_nan(arg4)) { result = nan; return BR_DONE; }` |
| `lemma_fma_zero_nan_mul` | `if (m_util.is_numeral(other_mul, vo) && m_fm.is_nan(vo))` |
| `lemma_fma_zero_inf_mul` | `if (m_util.is_numeral(other_mul, vo) && m_fm.is_inf(vo))` |
| `lemma_fma_zero_finite_decomposes` | `m_util.mk_add(arg1, m_util.mk_value(product), arg4)` with `m_fm.mul(rm, vzero, vo, product)` |
| `lemma_fma_zero_const_addend` | `m().mk_ite(finite_cond, arg4, nan)` (z_const branch) |
| `lemma_fma_zero_general_*` | general `m().mk_ite(finite_cond, m_util.mk_add(...), nan)` |
| `lemma_is_nan_to_fp_int` | `mk_is_nan`: `result = m().mk_false(); return BR_DONE;` |
| `lemma_is_inf_to_fp_int_*` | `mk_is_inf_of_int` switch statement |
| `lemma_is_normal_to_fp_int` | `mk_is_normal`: `result = m().mk_and(not_zero, m().mk_not(inf_cond))` |
| `lemma_is_nan_ite` | `mk_is_nan`: ite-pushthrough block |
| `lemma_is_inf_ite` | `mk_is_inf`: ite-pushthrough block |
| `lemma_is_normal_ite` | `mk_is_normal`: ite-pushthrough block |
| Lemma | C++ macro | Used in |
|---|---|---|
| `lemma_fma_zero_nan_addend` | `FPA_REWRITE_FMA_ZERO_MUL` case (a) | `mk_fma` |
| `lemma_fma_zero_nan_mul` | `FPA_REWRITE_FMA_ZERO_MUL` case (b) | `mk_fma` |
| `lemma_fma_zero_inf_mul` | `FPA_REWRITE_FMA_ZERO_MUL` case (b) | `mk_fma` |
| `lemma_fma_zero_finite_decomposes` | `FPA_REWRITE_FMA_ZERO_MUL` case (c) | `mk_fma` |
| `lemma_fma_zero_const_addend` | `FPA_REWRITE_FMA_ZERO_MUL` case (c) | `mk_fma` |
| `lemma_fma_zero_const_*_arm` | `FPA_REWRITE_FMA_ZERO_MUL` case (d) | `mk_fma` |
| `lemma_fma_zero_general_*_arm` | `FPA_REWRITE_FMA_ZERO_MUL` case (e) | `mk_fma` |
| `lemma_fma_zero_product_sign` | `FPA_REWRITE_FMA_ZERO_MUL` sign computation | `mk_fma` |
| `lemma_is_nan_to_fp_int` | `FPA_REWRITE_IS_NAN_TO_FP_INT` | `mk_is_nan` |
| `lemma_is_inf_to_fp_int_*` | `FPA_REWRITE_IS_INF_TO_FP_INT` + `mk_is_inf_of_int` | `mk_is_inf` |
| `lemma_is_normal_to_fp_int` | `FPA_REWRITE_IS_NORMAL_TO_FP_INT` | `mk_is_normal` |
| `lemma_is_nan_ite` | `FPA_REWRITE_IS_NAN_ITE` | `mk_is_nan` |
| `lemma_is_inf_ite` | `FPA_REWRITE_IS_INF_ITE` | `mk_is_inf` |
| `lemma_is_normal_ite` | `FPA_REWRITE_IS_NORMAL_ITE` | `mk_is_normal` |
## IEEE 754 References