- IEEE754.fst: fix multi-binding in assume val signatures (F* 2026 syntax),
change float from Type0 to eqtype for equality support, generalize fma NaN
axioms to handle any-NaN inputs (ax_fma_any_nan_y/x/z)
- FPARewriterRules.fst: add explicit #eb #sb type arguments to axiom calls
where F* 2026 cannot infer them, use ax_fma_any_nan_y for proofs where
y is an arbitrary NaN rather than the canonical nan constant
- RewriteCodeGen.fst: fix F* 2026 API changes: FStar.Sealed.unseal->unseal,
C_Bool->{C_True,C_False}, List.Tot.iter->iter, FStar.Reflection.V2.inspect
->inspect_ln for term_view, post from C_Lemma pre->post with Tv_Abs peel,
b2t unwrapping, lemma_ref via pack_fv instead of quote for polymorphic lemmas,
per-branch offset tracking for Pat_Var in match branches
- .github/workflows/fstar-extract.yml: update to F* 2026.03.24 with correct
archive name fstar-v{VERSION}-Linux-x86_64.tar.gz
- fstar/README.md and extract.sh: update version references to 2026.03.24
Extraction now produces correct output:
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;
}
Agent-Logs-Url: https://github.com/Z3Prover/z3/sessions/0a30f342-3941-4952-a54f-1bee84b022ef
Co-authored-by: NikolajBjorner <3085284+NikolajBjorner@users.noreply.github.com>
|
||
|---|---|---|
| .. | ||
| extract.sh | ||
| FPARewriterRules.fst | ||
| IEEE754.fst | ||
| README.md | ||
| RewriteCodeGen.fst | ||
F* Formalization of FPA Rewriter Rules (PR #9038)
This directory contains a formalization of the floating-point rewrite rules introduced in Z3 PR #9038 in the F* 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 bitseband significand bitssb, matching Z3'smpfrepresentation. - 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, derivedis_finite. - Arithmetic operations —
fp_add,fp_mul,fp_fma. - Conversion —
to_fp_of_int rm xrepresentingto_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 sbandmax_finite_int eb sbcorresponding to the values computed bymk_is_inf_of_intin 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:
- Calls
tc env lemmato obtain the lemma's type. - Strips the
Tv_Arrowchain (the∀ #eb #sb c t e .prefix), collecting parameter names and building a de Bruijn index-to-name map. - Extracts the equality
LHS = RHSfrom theC_Lemmaprecondition. - Decomposes
LHSintotop_fn(argument_pattern).top_fn(e.g.is_nan) is the IEEE 754 predicate whose Z3mk_*method is being extended.argument_patterndrives the C++ pattern match. - Detects
if c then t else e(which F* represents as a two-branchTv_Matchon aboolscrutinee) and maps it toPIte c t ein the intermediate representation. - Translates
RHSinto thecexprIR. - Calls
gen_cppto emit the self-contained C++ if-block.
Example
Running extract_rewrite (quote lemma_is_nan_ite) outputs:
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:
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:
cd fstar && ./extract.sh
Or invoke F* directly:
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:
./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* 2026.03.24 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.