3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-04-18 18:10:17 +00:00
z3/fstar/README.md
copilot-swe-agent[bot] e5acaf0c28
fstar: add extract.sh script and fstar-extract.yml GHA workflow for Meta-F* extraction
Agent-Logs-Url: https://github.com/Z3Prover/z3/sessions/680146e3-e241-487e-a0d7-a7e23b5f1634

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

250 lines
11 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.
### `../src/ast/rewriter/fpa_rewriter_rules.h`
C++ header containing one `#define` macro per rewrite rule, whose correctness
is proved by F* lemmas in `FPARewriterRules.fst`. The ite-pushthrough macros
(`FPA_REWRITE_IS_NAN_ITE`, `FPA_REWRITE_IS_INF_ITE`, `FPA_REWRITE_IS_NORMAL_ITE`)
can be regenerated by running `RewriteCodeGen.fst`. Each macro is annotated
with an `[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
The convenience script `extract.sh` runs the extraction and prints the
generated C++ rules to stdout:
```sh
cd fstar && ./extract.sh
```
Or invoke F* directly:
```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. To capture the output to a file:
```sh
./extract.sh > extracted_rules.txt
```
### Continuous integration
The GitHub Actions workflow `.github/workflows/fstar-extract.yml` runs
the extraction automatically on every push or pull request that touches
the `fstar/` directory. It installs the F* binary, runs `extract.sh`,
and uploads the generated C++ as a downloadable artifact
`fstar-extracted-cpp-rules` for inspection.
F\* 2024.09.05 or later is required. 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.