3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-04-14 16:25:11 +00:00
z3/fstar/README.md
copilot-swe-agent[bot] e2ffbe8c80
fstar: add RewriteCodeGen.fst - Meta-F* tactic for programmatic C++ extraction
Agent-Logs-Url: https://github.com/Z3Prover/z3/sessions/c7b6d01e-b309-4d67-93bb-f6bad4d79b75

Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
2026-04-05 02:38:29 +00:00

234 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# F\* Formalization of FPA Rewriter Rules (PR #9038)
This directory contains a formalization of the floating-point rewrite
rules introduced in [Z3 PR #9038](https://github.com/Z3Prover/z3/pull/9038)
in the [F\*](https://www.fstar-lang.org/) proof assistant.
## Overview
PR #9038 adds three families of optimizations to `src/ast/rewriter/fpa_rewriter.cpp`
that avoid expensive FP bit-blasting:
| Optimization | C++ function | F\* module and section |
|---|---|---|
| `fma(rm, ±0, y, z)` zero-multiplicand decomposition | `mk_fma` | `FPARewriterRules.fst` §1 |
| `isNaN/isInf/isNormal` applied to `to_fp(rm, to_real(int))` | `mk_is_nan`, `mk_is_inf`, `mk_is_normal`, `mk_is_inf_of_int` | `FPARewriterRules.fst` §2 |
| Push classification predicates through `ite` with concrete branches | `mk_is_nan`, `mk_is_inf`, `mk_is_normal` | `FPARewriterRules.fst` §3 |
## Files
### `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,
RTN, RTZ).
- **Classification predicates** — `is_nan`, `is_inf`, `is_zero`, `is_normal`,
`is_subnormal`, `is_negative`, `is_positive`, derived `is_finite`.
- **Arithmetic operations** — `fp_add`, `fp_mul`, `fp_fma`.
- **Conversion** — `to_fp_of_int rm x` representing `to_fp(rm, to_real(x))`.
- **Axioms** (`ax_*`) — consequences of the IEEE 754-2019 standard taken as
given, covering classification exclusivity, NaN propagation, special-value
arithmetic, and integer-conversion properties.
- **Overflow thresholds** — `overflow_threshold eb sb` and `max_finite_int eb sb`
corresponding to the values computed by `mk_is_inf_of_int` in the C++ rewriter.
All axioms use `assume val`, marking them as trusted without proof.
### `FPARewriterRules.fst`
Derived rewrite rules proved from `IEEE754.fst` axioms.
#### Section 1 — FMA with a Zero Multiplicand
| Lemma | Statement |
|---|---|
| `lemma_fma_zero_nan_addend` | `fma(rm, ±0, y, NaN) = NaN` |
| `lemma_fma_zero_nan_mul` | `fma(rm, ±0, NaN, z) = NaN` |
| `lemma_fma_zero_inf_mul` | `fma(rm, ±0, ±∞, z) = NaN` |
| `lemma_fma_zero_finite_decomposes` | `fma(rm, ±0, y_finite, z) = fp_add(rm, fp_mul(rm, ±0, y_finite), z)` |
| `lemma_fma_zero_const_addend` | `fma(rm, ±0, y_finite, z_nonzero_finite) = z` |
| `lemma_fma_zero_const_finite_arm` | finite arm of the `ite` for symbolic `y`, concrete non-zero `z` |
| `lemma_fma_zero_const_nan_inf_arm` | NaN/inf arm of the same `ite` |
| `lemma_fma_zero_general_finite_arm` | finite arm for fully symbolic `y` and `z` |
| `lemma_fma_zero_general_nan_inf_arm` | NaN/inf arm for fully symbolic `y` and `z` |
| `lemma_fma_zero_product_sign` | sign of `fp_mul(rm, zero_val, y)` = sign(zero_val) XOR sign(y) |
| `lemma_fma_zero_ite_correct` | full composite: `fma(rm, ±0, y, z) = ite(is_finite(y), fp_add(rm, fp_mul(rm, ±0, y), z), NaN)` |
#### Section 2 — Classification of Integer-to-Float Conversions
| Lemma | Statement |
|---|---|
| `lemma_is_nan_to_fp_int` | `isNaN(to_fp(rm, x)) = false` |
| `lemma_is_inf_to_fp_int_rne` | `isInf(to_fp(RNE, x)) ↔ \|x\| ≥ overflow_threshold` |
| `lemma_is_inf_to_fp_int_rna` | `isInf(to_fp(RNA, x)) ↔ \|x\| ≥ overflow_threshold` |
| `lemma_is_inf_to_fp_int_rtp` | `isInf(to_fp(RTP, x)) ↔ x > max_finite_int` |
| `lemma_is_inf_to_fp_int_rtn` | `isInf(to_fp(RTN, x)) ↔ x < -max_finite_int` |
| `lemma_is_inf_to_fp_int_rtz` | `isInf(to_fp(RTZ, x)) = false` |
| `lemma_is_normal_to_fp_int` | `isNormal(to_fp(rm, x)) ↔ x ≠ 0 ∧ ¬isInf(to_fp(rm, x))` |
| `lemma_is_normal_to_fp_int_rne` | combined form for RNE |
| `lemma_is_normal_to_fp_int_rtz` | combined form for RTZ |
#### Section 3 — Classification through `ite`
| Lemma | Statement |
|---|---|
| `lemma_is_nan_ite` | `isNaN(ite(c, t, e)) = ite(c, isNaN(t), isNaN(e))` |
| `lemma_is_inf_ite` | `isInf(ite(c, t, e)) = ite(c, isInf(t), isInf(e))` |
| `lemma_is_normal_ite` | `isNormal(ite(c, t, e)) = ite(c, isNormal(t), isNormal(e))` |
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 `()`.
### `RewriteCodeGen.fst`
Meta-F* reflection module that programmatically extracts C++ rewriter code
from F* lemmas using F* tactics (`FStar.Tactics.V2`) and term inspection
(`FStar.Reflection.V2`).
#### How it works
Given a quoted lemma name such as `(quote lemma_is_nan_ite)`, the
`extract_rewrite` tactic:
1. Calls `tc env lemma` to obtain the lemma's type.
2. Strips the `Tv_Arrow` chain (the `∀ #eb #sb c t e .` prefix), collecting
parameter names and building a de Bruijn index-to-name map.
3. Extracts the equality `LHS = RHS` from the `C_Lemma` precondition.
4. Decomposes `LHS` into `top_fn(argument_pattern)`. `top_fn` (e.g.
`is_nan`) is the IEEE 754 predicate whose Z3 `mk_*` method is being
extended. `argument_pattern` drives the C++ pattern match.
5. Detects `if c then t else e` (which F* represents as a two-branch
`Tv_Match` on a `bool` scrutinee) and maps it to `PIte c t e` in the
intermediate representation.
6. Translates `RHS` into the `cexpr` IR.
7. Calls `gen_cpp` to emit the self-contained C++ if-block.
#### Example
Running `extract_rewrite (quote lemma_is_nan_ite)` outputs:
```cpp
expr *c, *t, *e;
if (m().is_ite(arg1, c, t, e)) {
result = m().mk_ite(c, m_util.mk_is_nan(t), m_util.mk_is_nan(e));
return BR_REWRITE2;
}
```
The same tactic applied to `lemma_is_inf_ite` and `lemma_is_normal_ite`
produces the analogous blocks for `m_util.mk_is_inf` and
`m_util.mk_is_normal`.
#### Design
The module defines two intermediate representations:
- `cpat` — patterns on the LHS: `PVar`, `PIte`, `PApp`.
- `cexpr` — expressions on the RHS: `EVar`, `EBool`, `EIte`, `EApp`.
The `cpp_builder_name` helper maps IEEE 754 function names
(`is_nan`, `is_inf`, `is_normal`, `is_negative`, `is_positive`, `is_zero`)
to their Z3 C++ counterparts (`m_util.mk_is_nan`, etc.).
The three `let _ = run_tactic ...` blocks at the bottom of the file
demonstrate extraction for the three ite-pushthrough lemmas and print
their generated C++ to stdout during F* typechecking.
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 its corresponding C++ macro and the function where the macro is invoked.
| 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
The axioms in `IEEE754.fst` correspond to the following sections of the
IEEE 754-2019 standard:
- §4.3 — Rounding-direction attributes (rounding modes)
- §5.4 — Arithmetic operations (`fp_add`, `fp_mul`, `fp_fma`)
- §5.4.1 — Conversion from integer (`to_fp_of_int`)
- §5.7.2 — General operations: `isNaN`, `isInfinite`, `isNormal`, etc.
- §6.2 — Operations on quiet NaNs (NaN propagation)
- §6.3 — The sign bit: signed zero (`ax_zero_mul_sign`, `ax_add_zero_nonzero`)
- §7.2 — Invalid operation exception: 0 × ∞ = NaN (`ax_zero_mul_inf`)
## Building
### Type-checking the formalization
To type-check the IEEE 754 axioms and the rewrite-rule lemmas:
```sh
fstar.exe --include . IEEE754.fst FPARewriterRules.fst
```
### Running the Meta-F* extraction
To run the reflection-based C++ code extraction and print the generated
rewrite rules to stdout:
```sh
fstar.exe --include . IEEE754.fst FPARewriterRules.fst RewriteCodeGen.fst
```
This type-checks all three files and executes the `run_tactic` calls in
`RewriteCodeGen.fst`, printing the generated C++ for each ite-pushthrough
lemma. Redirect stdout to a file to capture the output:
```sh
fstar.exe --include . IEEE754.fst FPARewriterRules.fst RewriteCodeGen.fst \
2>/dev/null
```
F\* 2024.09.05 or later is recommended. The files have no external
dependencies beyond the F\* standard library prelude.
Because all IEEE 754 semantics are encoded as `assume val` axioms, the
type-checker accepts the development without requiring a concrete model.
The derived lemmas in `FPARewriterRules.fst` are fully verified against
those axioms; the Section 3 lemmas (`lemma_*_ite`) are discharged by
computation reduction alone.