3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-04-27 22:33:35 +00:00

fstar: fix F* 2026 compatibility and run Meta-F* extraction successfully

- 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>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-06 01:49:41 +00:00 committed by GitHub
parent e5acaf0c28
commit 4a2accf10c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 167 additions and 108 deletions

View file

@ -44,14 +44,14 @@ jobs:
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
- name: Install F* - name: Install F*
env: env:
FSTAR_VERSION: "2024.09.05" FSTAR_VERSION: "2026.03.24"
run: | run: |
ARCHIVE="fstar_v${FSTAR_VERSION}_Linux_x86_64.tar.gz" ARCHIVE="fstar-v${FSTAR_VERSION}-Linux-x86_64.tar.gz"
URL="https://github.com/FStarLang/FStar/releases/download/v${FSTAR_VERSION}/${ARCHIVE}" URL="https://github.com/FStarLang/FStar/releases/download/v${FSTAR_VERSION}/${ARCHIVE}"
echo "Downloading F* ${FSTAR_VERSION} from ${URL}" >&2 echo "Downloading F* ${FSTAR_VERSION} from ${URL}" >&2
curl -fsSL -o "/tmp/${ARCHIVE}" "${URL}" curl -fsSL -o "/tmp/${ARCHIVE}" "${URL}"
mkdir -p "$HOME/fstar" mkdir -p "$HOME/fstar"
tar -xzf "/tmp/${ARCHIVE}" -C "$HOME/fstar" --strip-components=1 tar -xzf "/tmp/${ARCHIVE}" -C "$HOME/fstar" --strip-components=2
echo "$HOME/fstar/bin" >> "$GITHUB_PATH" echo "$HOME/fstar/bin" >> "$GITHUB_PATH"
- name: Verify F* installation - name: Verify F* installation

View file

@ -149,7 +149,7 @@ let lemma_fma_zero_const_nan_inf_arm
(is_nan y = true || is_inf y = true)) (is_nan y = true || is_inf y = true))
(ensures is_nan (fp_fma rm zero_val y z) = true) = (ensures is_nan (fp_fma rm zero_val y z) = true) =
if is_nan y = true then if is_nan y = true then
ax_fma_nan_y rm zero_val z ax_fma_any_nan_y rm zero_val y z
else begin else begin
ax_zero_mul_inf rm zero_val y; ax_zero_mul_inf rm zero_val y;
ax_fma_nan_mul rm zero_val y z ax_fma_nan_mul rm zero_val y z
@ -182,7 +182,7 @@ let lemma_fma_zero_general_nan_inf_arm
(is_nan y = true || is_inf y = true)) (is_nan y = true || is_inf y = true))
(ensures is_nan (fp_fma rm zero_val y z) = true) = (ensures is_nan (fp_fma rm zero_val y z) = true) =
if is_nan y = true then if is_nan y = true then
ax_fma_nan_y rm zero_val z ax_fma_any_nan_y rm zero_val y z
else begin else begin
ax_zero_mul_inf rm zero_val y; ax_zero_mul_inf rm zero_val y;
ax_fma_nan_mul rm zero_val y z ax_fma_nan_mul rm zero_val y z
@ -229,7 +229,7 @@ let lemma_fma_zero_product_sign
let lemma_is_nan_to_fp_int let lemma_is_nan_to_fp_int
(#eb #sb: pos) (rm: rounding_mode) (x: int) (#eb #sb: pos) (rm: rounding_mode) (x: int)
: Lemma (is_nan (to_fp_of_int #eb #sb rm x) = false) = : Lemma (is_nan (to_fp_of_int #eb #sb rm x) = false) =
ax_to_fp_int_not_nan rm x ax_to_fp_int_not_nan #eb #sb rm x
(* --- 2b: isInf --- *) (* --- 2b: isInf --- *)
@ -245,32 +245,32 @@ let lemma_is_inf_to_fp_int_rne
(#eb #sb: pos) (x: int) (#eb #sb: pos) (x: int)
: Lemma (is_inf (to_fp_of_int #eb #sb RNE x) = : Lemma (is_inf (to_fp_of_int #eb #sb RNE x) =
(x >= overflow_threshold eb sb || x <= -(overflow_threshold eb sb))) = (x >= overflow_threshold eb sb || x <= -(overflow_threshold eb sb))) =
ax_is_inf_rne x ax_is_inf_rne #eb #sb x
(* RNA: same threshold as RNE (ties round away from zero to ∞). *) (* RNA: same threshold as RNE (ties round away from zero to ∞). *)
let lemma_is_inf_to_fp_int_rna let lemma_is_inf_to_fp_int_rna
(#eb #sb: pos) (x: int) (#eb #sb: pos) (x: int)
: Lemma (is_inf (to_fp_of_int #eb #sb RNA x) = : Lemma (is_inf (to_fp_of_int #eb #sb RNA x) =
(x >= overflow_threshold eb sb || x <= -(overflow_threshold eb sb))) = (x >= overflow_threshold eb sb || x <= -(overflow_threshold eb sb))) =
ax_is_inf_rna x ax_is_inf_rna #eb #sb x
(* RTP: positive overflow only (negative values round toward 0, not -∞). *) (* RTP: positive overflow only (negative values round toward 0, not -∞). *)
let lemma_is_inf_to_fp_int_rtp let lemma_is_inf_to_fp_int_rtp
(#eb #sb: pos) (x: int) (#eb #sb: pos) (x: int)
: Lemma (is_inf (to_fp_of_int #eb #sb RTP x) = (x > max_finite_int eb sb)) = : Lemma (is_inf (to_fp_of_int #eb #sb RTP x) = (x > max_finite_int eb sb)) =
ax_is_inf_rtp x ax_is_inf_rtp #eb #sb x
(* RTN: negative overflow only (positive values round toward 0, not +∞). *) (* RTN: negative overflow only (positive values round toward 0, not +∞). *)
let lemma_is_inf_to_fp_int_rtn let lemma_is_inf_to_fp_int_rtn
(#eb #sb: pos) (x: int) (#eb #sb: pos) (x: int)
: Lemma (is_inf (to_fp_of_int #eb #sb RTN x) = (x < -(max_finite_int eb sb))) = : Lemma (is_inf (to_fp_of_int #eb #sb RTN x) = (x < -(max_finite_int eb sb))) =
ax_is_inf_rtn x ax_is_inf_rtn #eb #sb x
(* RTZ: truncation toward zero never overflows to infinity. *) (* RTZ: truncation toward zero never overflows to infinity. *)
let lemma_is_inf_to_fp_int_rtz let lemma_is_inf_to_fp_int_rtz
(#eb #sb: pos) (x: int) (#eb #sb: pos) (x: int)
: Lemma (is_inf (to_fp_of_int #eb #sb RTZ x) = false) = : Lemma (is_inf (to_fp_of_int #eb #sb RTZ x) = false) =
ax_is_inf_rtz x ax_is_inf_rtz #eb #sb x
(* --- 2c: isNormal --- *) (* --- 2c: isNormal --- *)
@ -295,21 +295,21 @@ let lemma_is_normal_to_fp_int
: Lemma (is_normal (to_fp_of_int #eb #sb rm x) = : Lemma (is_normal (to_fp_of_int #eb #sb rm x) =
(x <> 0 && not (is_inf (to_fp_of_int #eb #sb rm x)))) = (x <> 0 && not (is_inf (to_fp_of_int #eb #sb rm x)))) =
let f = to_fp_of_int #eb #sb rm x in let f = to_fp_of_int #eb #sb rm x in
ax_to_fp_int_not_nan rm x; (* is_nan f = false *) ax_to_fp_int_not_nan #eb #sb rm x; (* is_nan f = false *)
ax_to_fp_int_not_subnormal rm x; (* is_subnormal f = false *) ax_to_fp_int_not_subnormal #eb #sb rm x; (* is_subnormal f = false *)
ax_classification f; (* nan||inf||zero||normal||subnormal *) ax_classification f; (* nan||inf||zero||normal||subnormal *)
if x = 0 then begin if x = 0 then begin
ax_to_fp_int_zero rm; (* is_zero f = true *) ax_to_fp_int_zero #eb #sb rm; (* is_zero f = true *)
ax_zero_exclusive f (* is_normal f = false, since is_zero f *) ax_zero_exclusive f (* is_normal f = false, since is_zero f *)
end else begin end else begin
ax_to_fp_int_nonzero rm x; (* is_zero f = false *) ax_to_fp_int_nonzero #eb #sb rm x; (* is_zero f = false *)
(* At this point: not nan, not subnormal, not zero. (* At this point: not nan, not subnormal, not zero.
ax_classification gives nan||inf||zero||normal||subnormal, which ax_classification gives nan||inf||zero||normal||subnormal, which
simplifies to inf||normal. We case-split on is_inf f. *) simplifies to inf||normal. We case-split on is_inf f. *)
if is_inf f then if is_inf f then
ax_inf_exclusive f (* is_inf f = true => is_normal f = false *) ax_inf_exclusive f (* is_inf f = true => is_normal f = false *)
else else
() (* is_inf f = false; by classification, is_normal f = true *) () (* is_inf f = false; by classification, is_normal f = true *)
end end
@ -390,7 +390,7 @@ let lemma_fma_zero_ite_nan_arm
: Lemma (requires is_zero zero_val = true && is_finite y = false) : Lemma (requires is_zero zero_val = true && is_finite y = false)
(ensures is_nan (fp_fma rm zero_val y z) = true) = (ensures is_nan (fp_fma rm zero_val y z) = true) =
if is_nan y = true then if is_nan y = true then
ax_fma_nan_y rm zero_val z ax_fma_any_nan_y rm zero_val y z
else begin else begin
(* is_finite y = false && is_nan y = false. (* is_finite y = false && is_nan y = false.
By the transparent definition is_finite y = not (is_nan y) && not (is_inf y), By the transparent definition is_finite y = not (is_nan y) && not (is_inf y),
@ -409,12 +409,12 @@ let lemma_is_normal_to_fp_int_rne
(x <> 0 && (x <> 0 &&
not (x >= overflow_threshold eb sb || not (x >= overflow_threshold eb sb ||
x <= -(overflow_threshold eb sb)))) = x <= -(overflow_threshold eb sb)))) =
lemma_is_normal_to_fp_int RNE x; lemma_is_normal_to_fp_int #eb #sb RNE x;
ax_is_inf_rne x ax_is_inf_rne #eb #sb x
(* The full isNormal(to_fp(rm, x)) rewrite for RTZ (never overflows). *) (* The full isNormal(to_fp(rm, x)) rewrite for RTZ (never overflows). *)
let lemma_is_normal_to_fp_int_rtz let lemma_is_normal_to_fp_int_rtz
(#eb #sb: pos) (x: int) (#eb #sb: pos) (x: int)
: Lemma (is_normal (to_fp_of_int #eb #sb RTZ x) = (x <> 0)) = : Lemma (is_normal (to_fp_of_int #eb #sb RTZ x) = (x <> 0)) =
lemma_is_normal_to_fp_int RTZ x; lemma_is_normal_to_fp_int #eb #sb RTZ x;
ax_is_inf_rtz x ax_is_inf_rtz #eb #sb x

View file

@ -25,7 +25,7 @@ module IEEE754
(* Abstract IEEE 754 float parameterized by format. A concrete model (* Abstract IEEE 754 float parameterized by format. A concrete model
would be a triple (sign, biased_exponent, significand), but we keep would be a triple (sign, biased_exponent, significand), but we keep
the type opaque so the axioms are the only assumed properties. *) the type opaque so the axioms are the only assumed properties. *)
assume val float : (eb: pos) -> (sb: pos) -> Type0 assume val float : (eb: pos) -> (sb: pos) -> eqtype
(* ------------------------------------------------------------------ *) (* ------------------------------------------------------------------ *)
(* Rounding modes (IEEE 754-2019 §4.3) *) (* Rounding modes (IEEE 754-2019 §4.3) *)
@ -154,17 +154,33 @@ assume val ax_mul_nan_r :
Lemma (is_nan (fp_mul rm x (nan #eb #sb)) = true) Lemma (is_nan (fp_mul rm x (nan #eb #sb)) = true)
assume val ax_fma_nan_x : assume val ax_fma_nan_x :
#eb:pos -> #sb:pos -> (rm: rounding_mode) -> (y z: float eb sb) -> #eb:pos -> #sb:pos -> (rm: rounding_mode) -> (y: float eb sb) -> (z: float eb sb) ->
Lemma (is_nan (fp_fma rm (nan #eb #sb) y z) = true) Lemma (is_nan (fp_fma rm (nan #eb #sb) y z) = true)
assume val ax_fma_nan_y : assume val ax_fma_nan_y :
#eb:pos -> #sb:pos -> (rm: rounding_mode) -> (x z: float eb sb) -> #eb:pos -> #sb:pos -> (rm: rounding_mode) -> (x: float eb sb) -> (z: float eb sb) ->
Lemma (is_nan (fp_fma rm x (nan #eb #sb) z) = true) Lemma (is_nan (fp_fma rm x (nan #eb #sb) z) = true)
assume val ax_fma_nan_z : assume val ax_fma_nan_z :
#eb:pos -> #sb:pos -> (rm: rounding_mode) -> (x y: float eb sb) -> #eb:pos -> #sb:pos -> (rm: rounding_mode) -> (x: float eb sb) -> (y: float eb sb) ->
Lemma (is_nan (fp_fma rm x y (nan #eb #sb)) = true) Lemma (is_nan (fp_fma rm x y (nan #eb #sb)) = true)
(* General NaN propagation: if any argument is NaN, the result is NaN. *)
assume val ax_fma_any_nan_x :
#eb:pos -> #sb:pos -> (rm: rounding_mode) -> (x: float eb sb) -> (y: float eb sb) -> (z: float eb sb) ->
Lemma (requires is_nan x = true)
(ensures is_nan (fp_fma rm x y z) = true)
assume val ax_fma_any_nan_y :
#eb:pos -> #sb:pos -> (rm: rounding_mode) -> (x: float eb sb) -> (y: float eb sb) -> (z: float eb sb) ->
Lemma (requires is_nan y = true)
(ensures is_nan (fp_fma rm x y z) = true)
assume val ax_fma_any_nan_z :
#eb:pos -> #sb:pos -> (rm: rounding_mode) -> (x: float eb sb) -> (y: float eb sb) -> (z: float eb sb) ->
Lemma (requires is_nan z = true)
(ensures is_nan (fp_fma rm x y z) = true)
(* ------------------------------------------------------------------ *) (* ------------------------------------------------------------------ *)
(* Special-value arithmetic axioms *) (* Special-value arithmetic axioms *)
(* ------------------------------------------------------------------ *) (* ------------------------------------------------------------------ *)
@ -181,7 +197,7 @@ assume val ax_zero_mul_inf :
This covers the case where fp_mul rm x y = NaN and we need This covers the case where fp_mul rm x y = NaN and we need
is_nan (fp_fma rm x y z) = true. *) is_nan (fp_fma rm x y z) = true. *)
assume val ax_fma_nan_mul : assume val ax_fma_nan_mul :
#eb:pos -> #sb:pos -> (rm: rounding_mode) -> (x y z: float eb sb) -> #eb:pos -> #sb:pos -> (rm: rounding_mode) -> (x: float eb sb) -> (y: float eb sb) -> (z: float eb sb) ->
Lemma (requires is_nan (fp_mul rm x y) = true) Lemma (requires is_nan (fp_mul rm x y) = true)
(ensures is_nan (fp_fma rm x y z) = true) (ensures is_nan (fp_fma rm x y z) = true)
@ -189,7 +205,7 @@ assume val ax_fma_nan_mul :
and fp_mul(rm, zero, y_finite) is ±0 (exact, IEEE 754-2019 §6.3). and fp_mul(rm, zero, y_finite) is ±0 (exact, IEEE 754-2019 §6.3).
This is the core decomposition used by the rewriter. *) This is the core decomposition used by the rewriter. *)
assume val ax_fma_zero_finite : assume val ax_fma_zero_finite :
#eb:pos -> #sb:pos -> (rm: rounding_mode) -> (zero_val y z: float eb sb) -> #eb:pos -> #sb:pos -> (rm: rounding_mode) -> (zero_val: float eb sb) -> (y: float eb sb) -> (z: float eb sb) ->
Lemma (requires is_zero zero_val = true && is_finite y = true) Lemma (requires is_zero zero_val = true && is_finite y = true)
(ensures fp_fma rm zero_val y z = fp_add rm (fp_mul rm zero_val y) z && (ensures fp_fma rm zero_val y z = fp_add rm (fp_mul rm zero_val y) z &&
is_zero (fp_mul rm zero_val y) = true) is_zero (fp_mul rm zero_val y) = true)
@ -197,7 +213,7 @@ assume val ax_fma_zero_finite :
(* Sign of 0 * y (IEEE 754-2019 §6.3): (* Sign of 0 * y (IEEE 754-2019 §6.3):
sign(0 * y) = sign(0) XOR sign(y) for finite nonzero y. *) sign(0 * y) = sign(0) XOR sign(y) for finite nonzero y. *)
assume val ax_zero_mul_sign : assume val ax_zero_mul_sign :
#eb:pos -> #sb:pos -> (rm: rounding_mode) -> (zero_val y: float eb sb) -> #eb:pos -> #sb:pos -> (rm: rounding_mode) -> (zero_val: float eb sb) -> (y: float eb sb) ->
Lemma (requires is_zero zero_val = true && is_finite y = true && is_zero y = false) Lemma (requires is_zero zero_val = true && is_finite y = true && is_zero y = false)
(ensures (let p = fp_mul rm zero_val y in (ensures (let p = fp_mul rm zero_val y in
is_zero p = true && is_zero p = true &&
@ -208,7 +224,7 @@ assume val ax_zero_mul_sign :
(* fp_add(rm, ±0, z) = z when z is finite and nonzero (IEEE 754-2019 §6.3). (* fp_add(rm, ±0, z) = z when z is finite and nonzero (IEEE 754-2019 §6.3).
±0 is an additive identity for nonzero finite values under all rounding modes. *) ±0 is an additive identity for nonzero finite values under all rounding modes. *)
assume val ax_add_zero_nonzero : assume val ax_add_zero_nonzero :
#eb:pos -> #sb:pos -> (rm: rounding_mode) -> (zero_val z: float eb sb) -> #eb:pos -> #sb:pos -> (rm: rounding_mode) -> (zero_val: float eb sb) -> (z: float eb sb) ->
Lemma (requires is_zero zero_val = true && is_finite z = true && is_zero z = false) Lemma (requires is_zero zero_val = true && is_finite z = true && is_zero z = false)
(ensures fp_add rm zero_val z = z) (ensures fp_add rm zero_val z = z)

View file

@ -240,7 +240,7 @@ the `fstar/` directory. It installs the F* binary, runs `extract.sh`,
and uploads the generated C++ as a downloadable artifact and uploads the generated C++ as a downloadable artifact
`fstar-extracted-cpp-rules` for inspection. `fstar-extracted-cpp-rules` for inspection.
F\* 2024.09.05 or later is required. The files have no external F\* 2026.03.24 or later is required. The files have no external
dependencies beyond the F\* standard library prelude. dependencies beyond the F\* standard library prelude.
Because all IEEE 754 semantics are encoded as `assume val` axioms, the Because all IEEE 754 semantics are encoded as `assume val` axioms, the

View file

@ -70,7 +70,7 @@ noeq type cexpr =
This undoes the nested Tv_App structure that F* uses for This undoes the nested Tv_App structure that F* uses for
multi-argument applications. *) multi-argument applications. *)
let rec collect_app (t: term) : Tac (term & list argv) = let rec collect_app (t: term) : Tac (term & list argv) =
match inspect t with match inspect_ln t with
| Tv_App hd arg -> | Tv_App hd arg ->
let (h, args) = collect_app hd in let (h, args) = collect_app hd in
(h, args @ [arg]) (h, args @ [arg])
@ -86,53 +86,70 @@ let filter_explicit (args: list argv) : list term =
(* Get the short name of a free variable, last component of the path. (* Get the short name of a free variable, last component of the path.
E.g. "IEEE754.is_nan" to "is_nan" *) E.g. "IEEE754.is_nan" to "is_nan" *)
let fv_short_name (t: term) : Tac (option string) = let fv_short_name (t: term) : Tac (option string) =
match inspect t with match inspect_ln t with
| Tv_FVar fv -> | Tv_FVar fv ->
(match List.Tot.rev (inspect_fv fv) with (match List.Tot.rev (inspect_fv fv) with
| last :: _ -> Some last | last :: _ -> Some last
| _ -> None) | _ -> None)
| _ -> None | _ -> None
(* Resolve a bound variable, de Bruijn indexed, using our index to name map. (* Resolve a variable name from a term.
F* reflection represents bound variables with de Bruijn indices: In F* 2026, variables in lemma types returned by `tc env` may appear
the most recently bound variable has index 0. as named variables (Tv_Var namedv) rather than de Bruijn indices, so
we first try the named view and fall back to de Bruijn lookup. *)
Given binders [eb; sb; c; t; e], inside the body: let var_name (idx_map: list (nat & string)) (offset: nat) (t: term) : Tac (option string) =
e = BVar 0, t = BVar 1, c = BVar 2, sb = BVar 3, eb = BVar 4 *) (* Try named-view first: Tv_Var gives a directly named variable. *)
let bvar_name (idx_map: list (nat & string)) (t: term) : Tac (option string) = (match FStar.Tactics.NamedView.inspect t with
match inspect t with | FStar.Tactics.NamedView.Tv_Var nv ->
| Tv_BVar bv -> let nm = unseal nv.ppname in
let bvv = inspect_bv bv in Some nm
(match List.Tot.assoc #nat bvv.index idx_map with | _ ->
| Some n -> Some n (* Fall back to de Bruijn lookup for variables still in closed form. *)
| None -> match inspect_ln t with
(* de Bruijn index not in our map; use the printer name as a fallback | Tv_BVar bv ->
and warn so the caller knows the index map may be incomplete. *) let bvv = inspect_bv bv in
let pp = FStar.Sealed.unseal bvv.ppname in if bvv.index < offset then None
print ("WARNING: bvar_name fallback for index " else begin
^ string_of_int bvv.index ^ ", using ppname '" ^ pp ^ "'"); let adj : nat = bvv.index - offset in
Some pp) (match List.Tot.assoc #nat adj idx_map with
| _ -> None | Some n -> Some n
| None ->
let pp = unseal bvv.ppname in
print ("WARNING: var_name fallback for BVar index "
^ string_of_int bvv.index ^ " (adj="
^ string_of_int adj ^ "), using ppname '" ^ pp ^ "'");
Some pp)
end
| _ -> None)
(* Detect if-then-else in reflected terms. (* Detect if-then-else in reflected terms.
In F*, `if c then t else e` desugars to: In F*, `if c then t else e` desugars to:
match c with | true -> t | false -> e match c with | true -> t | (x:bool) -> e
which appears as Tv_Match with two branches. *) where the false branch uses Pat_Var (not Pat_Constant), adding an extra
let try_ite (t: term) : Tac (option (term & term & term)) = binder. We therefore return the additional binder count for each branch
match inspect t with body, so callers can adjust the de Bruijn offset accordingly:
- then-branch (Pat_Constant): 0 extra binders
- else-branch (Pat_Var): 1 extra binder *)
let try_ite (t: term) : Tac (option (term & nat & term & nat & term)) =
match inspect_ln t with
| Tv_Match scrutinee _ret branches -> | Tv_Match scrutinee _ret branches ->
(match branches with (match branches with
| [(_, body_t); (_, body_f)] -> | [(pat_t, body_t); (pat_f, body_f)] ->
Some (scrutinee, body_t, body_f) let extra_t : nat = match pat_t with | Pat_Var _ _ -> 1 | _ -> 0 in
let extra_f : nat = match pat_f with | Pat_Var _ _ -> 1 | _ -> 0 in
Some (scrutinee, extra_t, body_t, extra_f, body_f)
| _ -> None) | _ -> None)
| _ -> None | _ -> None
(* Unwrap `squash p` to `p` if present. (* Unwrap `squash p` or `b2t p` to `p` if present.
The Lemma precondition may be wrapped in squash. *) The Lemma postcondition is often wrapped in squash or b2t.
In F* 2026, bool-valued propositions in Lemma postconditions are
wrapped in `b2t` which coerces `bool` to `prop`. *)
let unwrap_squash (t: term) : Tac term = let unwrap_squash (t: term) : Tac term =
let (head, args) = collect_app t in let (head, args) = collect_app t in
match fv_short_name head with match fv_short_name head with
| Some "squash" -> | Some "squash"
| Some "b2t" ->
(match filter_explicit args with | [inner] -> inner | _ -> t) (match filter_explicit args with | [inner] -> inner | _ -> t)
| _ -> t | _ -> t
@ -162,51 +179,56 @@ let rec tac_map (#a #b: Type) (f: a -> Tac b) (l: list a) : Tac (list b) =
(* Extract a pattern from the LHS argument. (* Extract a pattern from the LHS argument.
Recognizes: Recognizes:
- Bound variables -> PVar - Variables (named or BVar) -> PVar
- if-then-else -> PIte - if-then-else -> PIte
- Function applications -> PApp *) - Function applications -> PApp *)
let rec extract_pat (m: list (nat & string)) (t: term) : Tac cpat = let rec extract_pat (m: list (nat & string)) (offset: nat) (t: term) : Tac cpat =
match try_ite t with match try_ite t with
| Some (c, tb, fb) -> | Some (c, et, tb, ef, fb) ->
PIte (extract_pat m c) (extract_pat m tb) (extract_pat m fb) let off_t : nat = offset + et in
let off_f : nat = offset + ef in
PIte (extract_pat m offset c) (extract_pat m off_t tb) (extract_pat m off_f fb)
| None -> | None ->
let (head, raw_args) = collect_app t in let (head, raw_args) = collect_app t in
let args = filter_explicit raw_args in let args = filter_explicit raw_args in
if List.Tot.length args > 0 then if List.Tot.length args > 0 then
(match fv_short_name head with (match fv_short_name head with
| Some fn -> PApp fn (tac_map (extract_pat m) args) | Some fn -> PApp fn (tac_map (extract_pat m offset) args)
| None -> | None ->
(match bvar_name m head with (match var_name m offset head with
| Some n -> PApp n (tac_map (extract_pat m) args) | Some n -> PApp n (tac_map (extract_pat m offset) args)
| None -> fail ("pattern app: cannot resolve head: " ^ term_to_string head))) | None -> fail ("pattern app: cannot resolve head: " ^ term_to_string head)))
else else
(match bvar_name m t with (match var_name m offset t with
| Some n -> PVar n | Some n -> PVar n
| None -> fail ("pattern: cannot recognize: " ^ term_to_string t)) | None -> fail ("pattern: cannot recognize: " ^ term_to_string t))
(* Extract an expression from the RHS. (* Extract an expression from the RHS.
Recognizes: Recognizes:
- Bound variables -> EVar - Variables (named or BVar) -> EVar
- Boolean literals -> EBool - Boolean literals -> EBool
- if-then-else -> EIte - if-then-else -> EIte
- Function applications (FVar) -> EApp *) - Function applications (FVar) -> EApp *)
let rec extract_expr (m: list (nat & string)) (t: term) : Tac cexpr = let rec extract_expr (m: list (nat & string)) (offset: nat) (t: term) : Tac cexpr =
(* Check for boolean literals first *) (* Check for boolean literals first *)
(match inspect t with (match inspect_ln t with
| Tv_Const (C_Bool b) -> EBool b | Tv_Const C_True -> EBool true
| Tv_Const C_False -> EBool false
| _ -> | _ ->
match try_ite t with match try_ite t with
| Some (c, tb, fb) -> | Some (c, et, tb, ef, fb) ->
EIte (extract_expr m c) (extract_expr m tb) (extract_expr m fb) let off_t : nat = offset + et in
let off_f : nat = offset + ef in
EIte (extract_expr m offset c) (extract_expr m off_t tb) (extract_expr m off_f fb)
| None -> | None ->
let (head, raw_args) = collect_app t in let (head, raw_args) = collect_app t in
let args = filter_explicit raw_args in let args = filter_explicit raw_args in
if List.Tot.length args > 0 then if List.Tot.length args > 0 then
(match fv_short_name head with (match fv_short_name head with
| Some fn -> EApp fn (tac_map (extract_expr m) args) | Some fn -> EApp fn (tac_map (extract_expr m offset) args)
| None -> fail ("expr: application head is not FVar: " ^ term_to_string head)) | None -> fail ("expr: application head is not FVar: " ^ term_to_string head))
else else
(match bvar_name m t with (match var_name m offset t with
| Some n -> EVar n | Some n -> EVar n
| None -> fail ("expr: cannot recognize: " ^ term_to_string t))) | None -> fail ("expr: cannot recognize: " ^ term_to_string t)))
@ -219,9 +241,9 @@ let rec extract_expr (m: list (nat & string)) (t: term) : Tac cexpr =
-> Lemma (...) -> Lemma (...)
Returns: parameter names [eb;sb;c;t;e] and the final computation, C_Lemma. *) Returns: parameter names [eb;sb;c;t;e] and the final computation, C_Lemma. *)
let rec strip_arrows (t: term) : Tac (list string & comp) = let rec strip_arrows (t: term) : Tac (list string & comp) =
match inspect t with match inspect_ln t with
| Tv_Arrow binder c -> | Tv_Arrow binder c ->
let name = FStar.Sealed.unseal (inspect_binder binder).ppname in let name = unseal (inspect_binder binder).ppname in
(match inspect_comp c with (match inspect_comp c with
| C_Total ret -> | C_Total ret ->
let (names, final_c) = strip_arrows ret in let (names, final_c) = strip_arrows ret in
@ -260,17 +282,16 @@ let cpp_builder_name (fn: string) : string =
| _ -> "m_util.mk_" ^ fn | _ -> "m_util.mk_" ^ fn
(* Collect all variable names from a pattern (for C++ declarations) *) (* Collect all variable names from a pattern (for C++ declarations) *)
let rec pat_vars (p: cpat) : Tot (list string) = let rec pat_vars (p: cpat) : Tot (list string) (decreases p) =
match p with match p with
| PVar n -> [n] | PVar n -> [n]
| PIte c t e -> pat_vars c @ pat_vars t @ pat_vars e | PIte c t e -> pat_vars c @ pat_vars t @ pat_vars e
| PApp _ args -> | PApp _ args -> pat_vars_list args
let rec collect (l: list cpat) : Tot (list string) (decreases l) =
match l with and pat_vars_list (ps: list cpat) : Tot (list string) (decreases ps) =
| [] -> [] match ps with
| x :: xs -> pat_vars x @ collect xs | [] -> []
in | x :: xs -> pat_vars x @ pat_vars_list xs
collect args
(* Generate: expr *c, *t, *e; *) (* Generate: expr *c, *t, *e; *)
let gen_decls (p: cpat) : string = let gen_decls (p: cpat) : string =
@ -297,7 +318,7 @@ let gen_condition (arg: string) (p: cpat) : string =
| _ -> "/* TODO: extend gen_condition for nested patterns */" | _ -> "/* TODO: extend gen_condition for nested patterns */"
(* Generate a C++ expression from the RHS IR *) (* Generate a C++ expression from the RHS IR *)
let rec gen_rhs_expr (e: cexpr) : Tot string = let rec gen_rhs_expr (e: cexpr) : Tot string (decreases e) =
match e with match e with
| EVar n -> n | EVar n -> n
| EBool true -> "m().mk_true()" | EBool true -> "m().mk_true()"
@ -308,7 +329,13 @@ let rec gen_rhs_expr (e: cexpr) : Tot string =
^ gen_rhs_expr e ^ ")" ^ gen_rhs_expr e ^ ")"
| EApp fn args -> | EApp fn args ->
cpp_builder_name fn ^ "(" cpp_builder_name fn ^ "("
^ FStar.String.concat ", " (List.Tot.map gen_rhs_expr args) ^ ")" ^ gen_rhs_expr_list args ^ ")"
and gen_rhs_expr_list (es: list cexpr) : Tot string (decreases es) =
match es with
| [] -> ""
| [e] -> gen_rhs_expr e
| e :: rest -> gen_rhs_expr e ^ ", " ^ gen_rhs_expr_list rest
(* Generate the complete C++ rewrite case *) (* Generate the complete C++ rewrite case *)
let gen_cpp (top_fn: string) (arg: string) (pat: cpat) (rhs: cexpr) : string = let gen_cpp (top_fn: string) (arg: string) (pat: cpat) (rhs: cexpr) : string =
@ -353,14 +380,22 @@ let extract_rewrite (lemma: term) : Tac string =
let (param_names, final_comp) = strip_arrows ty in let (param_names, final_comp) = strip_arrows ty in
let idx_map = build_idx_map param_names in let idx_map = build_idx_map param_names in
(* 2. Get the equality from the Lemma precondition *) (* 2. Get the equality from the Lemma postcondition.
let pre = For `Lemma P`, the comp is C_Lemma l_True (fun _ -> b2t P) [].
The postcondition is a function taking the return value as argument.
We peel off the leading Tv_Abs to get the raw proposition.
Inside the abs body, all de Bruijn indices are shifted by 1 relative
to the outer scope, so we pass offset=1 to extract_pat/extract_expr. *)
let (post, post_offset) =
match inspect_comp final_comp with match inspect_comp final_comp with
| C_Lemma pre _ _ -> pre | C_Lemma _ post _ ->
(match inspect_ln post with
| Tv_Abs _ body -> (body, 1)
| _ -> (post, 0))
| _ -> fail "expected Lemma computation type" in | _ -> fail "expected Lemma computation type" in
(* 3. Extract LHS = RHS from the precondition *) (* 3. Extract LHS = RHS from the postcondition *)
let (lhs, rhs) = extract_eq pre in let (lhs, rhs) = extract_eq post in
(* 4. Decompose LHS: top_fn(argument_pattern). (* 4. Decompose LHS: top_fn(argument_pattern).
The top_fn is the IEEE754 classification predicate whose The top_fn is the IEEE754 classification predicate whose
@ -381,8 +416,8 @@ let extract_rewrite (lemma: term) : Tac string =
^ " for top_fn=" ^ top_fn) in ^ " for top_fn=" ^ top_fn) in
(* 5. Extract the argument pattern and the RHS expression *) (* 5. Extract the argument pattern and the RHS expression *)
let pat = extract_pat idx_map arg_term in let pat = extract_pat idx_map post_offset arg_term in
let rhs_expr = extract_expr idx_map rhs in let rhs_expr = extract_expr idx_map post_offset rhs in
(* 6. Emit C++ code. (* 6. Emit C++ code.
"arg1" is the standard name for the argument in mk_* functions of "arg1" is the standard name for the argument in mk_* functions of
@ -436,11 +471,19 @@ let print_rewrite (label: string) (lemma: term) : Tac unit =
print ("\n=== " ^ label ^ " ===\n"); print ("\n=== " ^ label ^ " ===\n");
print (extract_rewrite lemma) print (extract_rewrite lemma)
(* Construct a term reference to a top-level name in FPARewriterRules
without instantiating implicit arguments. Using pack_fv avoids the
elaboration that quote performs, which in F* 2026 fails for polymorphic
lemmas whose implicit type arguments (eb, sb: pos) cannot be inferred
from the call site. *)
let lemma_ref (name: string) : term =
pack_ln (Tv_FVar (pack_fv ["FPARewriterRules"; name]))
(* Demonstrate extraction of the three ite-pushthrough lemmas. (* Demonstrate extraction of the three ite-pushthrough lemmas.
Running this file with F* prints the generated C++ for each rule. *) Running this file with F* prints the generated C++ for each rule. *)
let _ = let _ =
run_tactic (fun () -> run_tactic (fun () ->
List.Tot.iter (fun (label, lemma) -> print_rewrite label lemma) iter (fun (label, lemma) -> print_rewrite label lemma)
[ ("lemma_is_nan_ite", quote lemma_is_nan_ite); [ ("lemma_is_nan_ite", lemma_ref "lemma_is_nan_ite");
("lemma_is_inf_ite", quote lemma_is_inf_ite); ("lemma_is_inf_ite", lemma_ref "lemma_is_inf_ite");
("lemma_is_normal_ite", quote lemma_is_normal_ite) ]) ("lemma_is_normal_ite", lemma_ref "lemma_is_normal_ite") ])

View file

@ -26,7 +26,7 @@ cd "$SCRIPT_DIR"
if ! command -v fstar.exe &>/dev/null; then if ! command -v fstar.exe &>/dev/null; then
echo "ERROR: fstar.exe not found on PATH." >&2 echo "ERROR: fstar.exe not found on PATH." >&2
echo "Install F* 2024.09.05+ from https://github.com/FStarLang/FStar/releases" >&2 echo "Install F* 2026.03.24+ from https://github.com/FStarLang/FStar/releases" >&2
exit 1 exit 1
fi fi