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:
parent
653c49ac5e
commit
1b73e5f1de
5 changed files with 433 additions and 28 deletions
|
|
@ -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) =
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue