flux_infer/
fixpoint_encoding.rs

1//! Encoding of the refinement tree into a fixpoint constraint.
2
3use std::{hash::Hash, iter};
4
5use fixpoint::AdtId;
6use flux_common::{
7    bug,
8    cache::QueryCache,
9    dbg,
10    index::{IndexGen, IndexVec},
11    iter::IterExt,
12    span_bug, tracked_span_bug,
13};
14use flux_config::{self as config};
15use flux_errors::Errors;
16use flux_middle::{
17    FixpointQueryKind,
18    def_id::{FluxDefId, MaybeExternId},
19    def_id_to_string,
20    global_env::GlobalEnv,
21    queries::QueryResult,
22    rty::{self, ESpan, GenericArgsExt, InternalFuncKind, Lambda, List, SpecFuncKind, VariantIdx},
23    timings::{self, TimingKind},
24};
25use itertools::Itertools;
26use liquid_fixpoint::{FixpointResult, SmtSolver};
27use rustc_data_structures::{
28    fx::{FxIndexMap, FxIndexSet},
29    unord::{UnordMap, UnordSet},
30};
31use rustc_hir::def_id::{DefId, LocalDefId};
32use rustc_index::newtype_index;
33use rustc_span::Span;
34use rustc_type_ir::{BoundVar, DebruijnIndex};
35use serde::{Deserialize, Deserializer, Serialize};
36
37pub mod fixpoint {
38    use std::fmt;
39
40    use flux_middle::rty::{EarlyReftParam, Real};
41    use liquid_fixpoint::{FixpointFmt, Identifier};
42    use rustc_abi::VariantIdx;
43    use rustc_index::newtype_index;
44    use rustc_middle::ty::ParamConst;
45    use rustc_span::Symbol;
46
47    newtype_index! {
48        pub struct KVid {}
49    }
50
51    impl Identifier for KVid {
52        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53            write!(f, "k{}", self.as_u32())
54        }
55    }
56
57    newtype_index! {
58        pub struct LocalVar {}
59    }
60
61    newtype_index! {
62        pub struct GlobalVar {}
63    }
64
65    newtype_index! {
66        /// Unique id assigned to each [`flux_middle::rty::AdtSortDef`] that needs to be encoded
67        /// into fixpoint
68        pub struct AdtId {}
69    }
70
71    #[derive(Hash, Copy, Clone)]
72    pub enum Var {
73        Underscore,
74        Global(GlobalVar, Option<Symbol>),
75        Local(LocalVar),
76        DataCtor(AdtId, VariantIdx),
77        TupleCtor {
78            arity: usize,
79        },
80        TupleProj {
81            arity: usize,
82            field: u32,
83        },
84        UIFRel(BinRel),
85        /// Interpreted theory function. This can be an arbitrary string, thus we are assuming the
86        /// name is different than the display implementation for the other variants.
87        Itf(liquid_fixpoint::ThyFunc),
88        Param(EarlyReftParam),
89        ConstGeneric(ParamConst),
90    }
91
92    impl From<GlobalVar> for Var {
93        fn from(v: GlobalVar) -> Self {
94            Self::Global(v, None)
95        }
96    }
97
98    impl From<LocalVar> for Var {
99        fn from(v: LocalVar) -> Self {
100            Self::Local(v)
101        }
102    }
103
104    impl Identifier for Var {
105        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106            match self {
107                Var::Global(v, None) => write!(f, "c{}", v.as_u32()),
108                Var::Global(v, Some(sym)) => write!(f, "f${}${}", sym, v.as_u32()),
109                Var::Local(v) => write!(f, "a{}", v.as_u32()),
110                Var::DataCtor(adt_id, variant_idx) => {
111                    write!(f, "mkadt{}${}", adt_id.as_u32(), variant_idx.as_u32())
112                }
113                Var::TupleCtor { arity } => write!(f, "mktuple{arity}"),
114                Var::TupleProj { arity, field } => write!(f, "tuple{arity}${field}"),
115                Var::Itf(name) => write!(f, "{name}"),
116                Var::UIFRel(BinRel::Gt) => write!(f, "gt"),
117                Var::UIFRel(BinRel::Ge) => write!(f, "ge"),
118                Var::UIFRel(BinRel::Lt) => write!(f, "lt"),
119                Var::UIFRel(BinRel::Le) => write!(f, "le"),
120                // these are actually not necessary because equality is interpreted for all sorts
121                Var::UIFRel(BinRel::Eq) => write!(f, "eq"),
122                Var::UIFRel(BinRel::Ne) => write!(f, "ne"),
123                Var::Underscore => write!(f, "_$"), // To avoid clashing with `_` used for `app (_ bv_op n)` for parametric SMT ops
124                Var::ConstGeneric(param) => {
125                    write!(f, "constgen${}${}", param.name, param.index)
126                }
127                Var::Param(param) => {
128                    write!(f, "reftgen${}${}", param.name, param.index)
129                }
130            }
131        }
132    }
133
134    #[derive(Clone, Hash)]
135    pub enum DataSort {
136        Tuple(usize),
137        Adt(AdtId),
138    }
139
140    impl Identifier for DataSort {
141        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142            match self {
143                DataSort::Tuple(arity) => {
144                    write!(f, "Tuple{arity}")
145                }
146                DataSort::Adt(adt_id) => {
147                    write!(f, "Adt{}", adt_id.as_u32())
148                }
149            }
150        }
151    }
152
153    #[derive(Hash)]
154    pub struct SymStr(pub Symbol);
155
156    impl FixpointFmt for SymStr {
157        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158            write!(f, "\"{}\"", self.0)
159        }
160    }
161
162    liquid_fixpoint::declare_types! {
163        type Sort = DataSort;
164        type KVar = KVid;
165        type Var = Var;
166        type Decimal = Real;
167        type String = SymStr;
168        type Tag = super::TagIdx;
169    }
170    pub use fixpoint_generated::*;
171}
172
173newtype_index! {
174    #[debug_format = "TagIdx({})"]
175    pub struct TagIdx {}
176}
177
178impl Serialize for TagIdx {
179    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
180        self.as_u32().serialize(serializer)
181    }
182}
183
184impl<'de> Deserialize<'de> for TagIdx {
185    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
186        let idx = usize::deserialize(deserializer)?;
187        Ok(TagIdx::from_u32(idx as u32))
188    }
189}
190
191/// Keep track of all the data sorts that we need to define in fixpoint to encode the constraint.
192#[derive(Default)]
193struct SortEncodingCtxt {
194    /// Set of all the tuple arities that need to be defined
195    tuples: UnordSet<usize>,
196    /// Set of all the [`AdtDefSortDef`](flux_middle::rty::AdtSortDef) that need to be declared as
197    /// Fixpoint data-decls
198    adt_sorts: FxIndexSet<DefId>,
199}
200
201impl SortEncodingCtxt {
202    fn sort_to_fixpoint(&mut self, sort: &rty::Sort) -> fixpoint::Sort {
203        match sort {
204            rty::Sort::Int => fixpoint::Sort::Int,
205            rty::Sort::Real => fixpoint::Sort::Real,
206            rty::Sort::Bool => fixpoint::Sort::Bool,
207            rty::Sort::Str => fixpoint::Sort::Str,
208            rty::Sort::Char => fixpoint::Sort::Int,
209            rty::Sort::BitVec(size) => fixpoint::Sort::BitVec(Box::new(bv_size_to_fixpoint(*size))),
210            // There's no way to declare opaque sorts in the fixpoint horn syntax so we encode user
211            // declared opaque sorts, type parameter sorts, and (unormalizable) type alias sorts as
212            // integers. Well-formedness should ensure values of these sorts are used "opaquely",
213            // i.e., the only values of these sorts are variables.
214            rty::Sort::App(rty::SortCtor::User { .. }, _)
215            | rty::Sort::Param(_)
216            | rty::Sort::Alias(rty::AliasKind::Opaque | rty::AliasKind::Projection, ..) => {
217                fixpoint::Sort::Int
218            }
219            rty::Sort::App(rty::SortCtor::Set, args) => {
220                let args = args.iter().map(|s| self.sort_to_fixpoint(s)).collect_vec();
221                fixpoint::Sort::App(fixpoint::SortCtor::Set, args)
222            }
223            rty::Sort::App(rty::SortCtor::Map, args) => {
224                let args = args.iter().map(|s| self.sort_to_fixpoint(s)).collect_vec();
225                fixpoint::Sort::App(fixpoint::SortCtor::Map, args)
226            }
227            rty::Sort::App(rty::SortCtor::Adt(sort_def), args) => {
228                if let Some(variant) = sort_def.opt_struct_variant() {
229                    let sorts = variant.field_sorts(args);
230                    // do not generate 1-tuples
231                    if let [sort] = &sorts[..] {
232                        self.sort_to_fixpoint(sort)
233                    } else {
234                        self.declare_tuple(sorts.len());
235                        let ctor = fixpoint::SortCtor::Data(fixpoint::DataSort::Tuple(sorts.len()));
236                        let args = sorts.iter().map(|s| self.sort_to_fixpoint(s)).collect_vec();
237                        fixpoint::Sort::App(ctor, args)
238                    }
239                } else {
240                    debug_assert!(args.is_empty());
241                    let adt_id = self.declare_adt(sort_def.did());
242                    fixpoint::Sort::App(
243                        fixpoint::SortCtor::Data(fixpoint::DataSort::Adt(adt_id)),
244                        vec![],
245                    )
246                }
247            }
248            rty::Sort::Tuple(sorts) => {
249                // do not generate 1-tuples
250                if let [sort] = &sorts[..] {
251                    self.sort_to_fixpoint(sort)
252                } else {
253                    self.declare_tuple(sorts.len());
254                    let ctor = fixpoint::SortCtor::Data(fixpoint::DataSort::Tuple(sorts.len()));
255                    let args = sorts.iter().map(|s| self.sort_to_fixpoint(s)).collect();
256                    fixpoint::Sort::App(ctor, args)
257                }
258            }
259            rty::Sort::Func(sort) => self.func_sort_to_fixpoint(sort),
260            rty::Sort::Var(k) => fixpoint::Sort::Var(k.index()),
261            rty::Sort::Err
262            | rty::Sort::Infer(_)
263            | rty::Sort::Loc
264            | rty::Sort::Alias(rty::AliasKind::Free, _) => {
265                tracked_span_bug!("unexpected sort `{sort:?}`")
266            }
267        }
268    }
269
270    fn func_sort_to_fixpoint(&mut self, fsort: &rty::PolyFuncSort) -> fixpoint::Sort {
271        let params = fsort.params().len();
272        let fsort = fsort.skip_binders();
273        let output = self.sort_to_fixpoint(fsort.output());
274        fixpoint::Sort::mk_func(
275            params,
276            fsort.inputs().iter().map(|s| self.sort_to_fixpoint(s)),
277            output,
278        )
279    }
280
281    fn declare_tuple(&mut self, arity: usize) {
282        self.tuples.insert(arity);
283    }
284
285    pub fn declare_adt(&mut self, did: DefId) -> AdtId {
286        if let Some(idx) = self.adt_sorts.get_index_of(&did) {
287            AdtId::from_usize(idx)
288        } else {
289            let adt_id = AdtId::from_usize(self.adt_sorts.len());
290            self.adt_sorts.insert(did);
291            adt_id
292        }
293    }
294
295    fn append_adt_decls(
296        genv: GlobalEnv,
297        adt_sorts: FxIndexSet<DefId>,
298        decls: &mut Vec<fixpoint::DataDecl>,
299    ) -> QueryResult {
300        for (idx, adt_def_id) in adt_sorts.iter().enumerate() {
301            let adt_id = AdtId::from_usize(idx);
302            let adt_sort_def = genv.adt_sort_def_of(adt_def_id)?;
303            decls.push(fixpoint::DataDecl {
304                name: fixpoint::DataSort::Adt(adt_id),
305                vars: adt_sort_def.param_count(),
306                ctors: adt_sort_def
307                    .variants()
308                    .iter_enumerated()
309                    .map(|(idx, variant)| {
310                        debug_assert_eq!(variant.fields(), 0);
311                        fixpoint::DataCtor {
312                            name: fixpoint::Var::DataCtor(adt_id, idx),
313                            fields: vec![],
314                        }
315                    })
316                    .collect(),
317            });
318        }
319        Ok(())
320    }
321
322    fn append_tuple_decls(tuples: UnordSet<usize>, decls: &mut Vec<fixpoint::DataDecl>) {
323        decls.extend(
324            tuples
325                .into_items()
326                .into_sorted_stable_ord()
327                .into_iter()
328                .map(|arity| {
329                    fixpoint::DataDecl {
330                        name: fixpoint::DataSort::Tuple(arity),
331                        vars: arity,
332                        ctors: vec![fixpoint::DataCtor {
333                            name: fixpoint::Var::TupleCtor { arity },
334                            fields: (0..(arity as u32))
335                                .map(|field| {
336                                    fixpoint::DataField {
337                                        name: fixpoint::Var::TupleProj { arity, field },
338                                        sort: fixpoint::Sort::Var(field as usize),
339                                    }
340                                })
341                                .collect(),
342                        }],
343                    }
344                }),
345        );
346    }
347
348    fn into_data_decls(self, genv: GlobalEnv) -> QueryResult<Vec<fixpoint::DataDecl>> {
349        let mut decls = vec![];
350        Self::append_tuple_decls(self.tuples, &mut decls);
351        Self::append_adt_decls(genv, self.adt_sorts, &mut decls)?;
352        Ok(decls)
353    }
354}
355
356fn bv_size_to_fixpoint(size: rty::BvSize) -> fixpoint::Sort {
357    match size {
358        rty::BvSize::Fixed(size) => fixpoint::Sort::BvSize(size),
359        rty::BvSize::Param(_var) => {
360            // I think we could encode the size as a sort variable, but this would require some care
361            // because smtlib doesn't really support parametric sizes. Fixpoint is probably already
362            // too liberal about this and it'd be easy to make it crash.
363            // fixpoint::Sort::Var(var.index)
364            bug!("unexpected parametric bit-vector size")
365        }
366        rty::BvSize::Infer(_) => bug!("unexpected infer variable for bit-vector size"),
367    }
368}
369
370type FunDefMap = FxIndexMap<FluxDefId, fixpoint::Var>;
371type ConstMap<'tcx> = FxIndexMap<ConstKey<'tcx>, fixpoint::ConstDecl>;
372
373#[derive(Eq, Hash, PartialEq)]
374enum ConstKey<'tcx> {
375    Uif(FluxDefId),
376    RustConst(DefId),
377    Alias(FluxDefId, rustc_middle::ty::GenericArgsRef<'tcx>),
378    Lambda(Lambda),
379    PrimOp(rty::BinOp),
380}
381
382pub struct FixpointCtxt<'genv, 'tcx, T: Eq + Hash> {
383    comments: Vec<String>,
384    genv: GlobalEnv<'genv, 'tcx>,
385    kvars: KVarGen,
386    scx: SortEncodingCtxt,
387    kcx: KVarEncodingCtxt,
388    ecx: ExprEncodingCtxt<'genv, 'tcx>,
389    tags: IndexVec<TagIdx, T>,
390    tags_inv: UnordMap<T, TagIdx>,
391    /// Id of the item being checked. This is a [`MaybeExternId`] because we can be checking invariants for
392    /// an extern spec on an enum.
393    def_id: MaybeExternId,
394}
395
396pub type FixQueryCache = QueryCache<FixpointResult<TagIdx>>;
397
398impl<'genv, 'tcx, Tag> FixpointCtxt<'genv, 'tcx, Tag>
399where
400    Tag: std::hash::Hash + Eq + Copy,
401{
402    pub fn new(genv: GlobalEnv<'genv, 'tcx>, def_id: MaybeExternId, kvars: KVarGen) -> Self {
403        let def_span = genv.tcx().def_span(def_id);
404        Self {
405            comments: vec![],
406            kvars,
407            scx: SortEncodingCtxt::default(),
408            genv,
409            ecx: ExprEncodingCtxt::new(genv, def_span),
410            kcx: Default::default(),
411            tags: IndexVec::new(),
412            tags_inv: Default::default(),
413            def_id,
414        }
415    }
416
417    pub fn check(
418        mut self,
419        cache: &mut FixQueryCache,
420        constraint: fixpoint::Constraint,
421        kind: FixpointQueryKind,
422        scrape_quals: bool,
423        solver: SmtSolver,
424    ) -> QueryResult<Vec<Tag>> {
425        // skip checking trivial constraints
426        if !constraint.is_concrete() {
427            self.ecx.errors.into_result()?;
428            return Ok(vec![]);
429        }
430        let def_span = self.def_span();
431        let def_id = self.def_id;
432
433        let kvars = self.kcx.into_fixpoint();
434
435        let (define_funs, define_constants) = self.ecx.define_funs(def_id, &mut self.scx)?;
436        let qualifiers = self.ecx.qualifiers_for(def_id.local_id(), &mut self.scx)?;
437
438        // Assuming values should happen after all encoding is done so we are sure we've collected
439        // all constants.
440        let constraint = self.ecx.assume_const_values(constraint, &mut self.scx)?;
441
442        let mut constants = self.ecx.const_map.into_values().collect_vec();
443        constants.extend(define_constants);
444
445        for rel in fixpoint::BinRel::INEQUALITIES {
446            // ∀a. a -> a -> bool
447            let sort = fixpoint::Sort::mk_func(
448                1,
449                [fixpoint::Sort::Var(0), fixpoint::Sort::Var(0)],
450                fixpoint::Sort::Bool,
451            );
452            constants.push(fixpoint::ConstDecl {
453                name: fixpoint::Var::UIFRel(rel),
454                sort,
455                comment: None,
456            });
457        }
458
459        // We are done encoding expressions. Check if there are any errors.
460        self.ecx.errors.into_result()?;
461
462        let task = fixpoint::Task {
463            comments: self.comments,
464            constants,
465            kvars,
466            define_funs,
467            constraint,
468            qualifiers,
469            scrape_quals,
470            solver,
471            data_decls: self.scx.into_data_decls(self.genv)?,
472        };
473        if config::dump_constraint() {
474            dbg::dump_item_info(self.genv.tcx(), self.def_id.resolved_id(), "smt2", &task).unwrap();
475        }
476
477        match Self::run_task_with_cache(self.genv, task, self.def_id.resolved_id(), kind, cache) {
478            FixpointResult::Safe(_) => Ok(vec![]),
479            FixpointResult::Unsafe(_, errors) => {
480                Ok(errors
481                    .into_iter()
482                    .map(|err| self.tags[err.tag])
483                    .unique()
484                    .collect_vec())
485            }
486            FixpointResult::Crash(err) => span_bug!(def_span, "fixpoint crash: {err:?}"),
487        }
488    }
489
490    fn run_task_with_cache(
491        genv: GlobalEnv,
492        task: fixpoint::Task,
493        def_id: DefId,
494        kind: FixpointQueryKind,
495        cache: &mut FixQueryCache,
496    ) -> FixpointResult<TagIdx> {
497        let key = kind.task_key(genv.tcx(), def_id);
498
499        let hash = task.hash_with_default();
500
501        if config::is_cache_enabled()
502            && let Some(result) = cache.lookup(&key, hash)
503        {
504            return result.clone();
505        }
506        let result = timings::time_it(TimingKind::FixpointQuery(def_id, kind), || {
507            task.run()
508                .unwrap_or_else(|err| tracked_span_bug!("failed to run fixpoint: {err}"))
509        });
510
511        if config::is_cache_enabled() {
512            cache.insert(key, hash, result.clone());
513        }
514        result
515    }
516
517    fn tag_idx(&mut self, tag: Tag) -> TagIdx
518    where
519        Tag: std::fmt::Debug,
520    {
521        *self.tags_inv.entry(tag).or_insert_with(|| {
522            let idx = self.tags.push(tag);
523            self.comments.push(format!("Tag {idx}: {tag:?}"));
524            idx
525        })
526    }
527
528    pub(crate) fn with_name_map<R>(
529        &mut self,
530        name: rty::Name,
531        f: impl FnOnce(&mut Self, fixpoint::LocalVar) -> R,
532    ) -> R {
533        let fresh = self.ecx.local_var_env.insert_fvar_map(name);
534        let r = f(self, fresh);
535        self.ecx.local_var_env.remove_fvar_map(name);
536        r
537    }
538
539    pub(crate) fn sort_to_fixpoint(&mut self, sort: &rty::Sort) -> fixpoint::Sort {
540        self.scx.sort_to_fixpoint(sort)
541    }
542
543    pub(crate) fn var_to_fixpoint(&self, var: &rty::Var) -> fixpoint::Var {
544        self.ecx.var_to_fixpoint(var)
545    }
546
547    /// Encodes an expression in head position as a [`fixpoint::Constraint`] "peeling out"
548    /// implications and foralls.
549    ///
550    /// [`fixpoint::Constraint`]: liquid_fixpoint::Constraint
551    pub(crate) fn head_to_fixpoint(
552        &mut self,
553        expr: &rty::Expr,
554        mk_tag: impl Fn(Option<ESpan>) -> Tag + Copy,
555    ) -> QueryResult<fixpoint::Constraint>
556    where
557        Tag: std::fmt::Debug,
558    {
559        match expr.kind() {
560            rty::ExprKind::BinaryOp(rty::BinOp::And, ..) => {
561                // avoid creating nested conjunctions
562                let cstrs = expr
563                    .flatten_conjs()
564                    .into_iter()
565                    .map(|e| self.head_to_fixpoint(e, mk_tag))
566                    .try_collect()?;
567                Ok(fixpoint::Constraint::conj(cstrs))
568            }
569            rty::ExprKind::BinaryOp(rty::BinOp::Imp, e1, e2) => {
570                let (bindings, assumption) = self.assumption_to_fixpoint(e1)?;
571                let cstr = self.head_to_fixpoint(e2, mk_tag)?;
572                Ok(fixpoint::Constraint::foralls(bindings, mk_implies(assumption, cstr)))
573            }
574            rty::ExprKind::KVar(kvar) => {
575                let mut bindings = vec![];
576                let pred = self.kvar_to_fixpoint(kvar, &mut bindings)?;
577                Ok(fixpoint::Constraint::foralls(bindings, fixpoint::Constraint::Pred(pred, None)))
578            }
579            rty::ExprKind::ForAll(pred) => {
580                self.ecx
581                    .local_var_env
582                    .push_layer_with_fresh_names(pred.vars().len());
583                let cstr = self.head_to_fixpoint(pred.as_ref().skip_binder(), mk_tag)?;
584                let vars = self.ecx.local_var_env.pop_layer();
585
586                let bindings = iter::zip(vars, pred.vars())
587                    .map(|(var, kind)| {
588                        fixpoint::Bind {
589                            name: var.into(),
590                            sort: self.scx.sort_to_fixpoint(kind.expect_sort()),
591                            pred: fixpoint::Pred::TRUE,
592                        }
593                    })
594                    .collect_vec();
595
596                Ok(fixpoint::Constraint::foralls(bindings, cstr))
597            }
598            _ => {
599                let tag_idx = self.tag_idx(mk_tag(expr.span()));
600                let pred = fixpoint::Pred::Expr(self.ecx.expr_to_fixpoint(expr, &mut self.scx)?);
601                Ok(fixpoint::Constraint::Pred(pred, Some(tag_idx)))
602            }
603        }
604    }
605
606    /// Encodes an expression in assumptive position as a [`fixpoint::Pred`]. Returns the encoded
607    /// predicate and a list of bindings produced by ANF-ing kvars.
608    ///
609    /// [`fixpoint::Pred`]: liquid_fixpoint::Pred
610    pub(crate) fn assumption_to_fixpoint(
611        &mut self,
612        pred: &rty::Expr,
613    ) -> QueryResult<(Vec<fixpoint::Bind>, fixpoint::Pred)> {
614        let mut bindings = vec![];
615        let mut preds = vec![];
616        self.assumption_to_fixpoint_aux(pred, &mut bindings, &mut preds)?;
617        Ok((bindings, fixpoint::Pred::and(preds)))
618    }
619
620    /// Auxiliary function to merge nested conjunctions in a single predicate
621    fn assumption_to_fixpoint_aux(
622        &mut self,
623        expr: &rty::Expr,
624        bindings: &mut Vec<fixpoint::Bind>,
625        preds: &mut Vec<fixpoint::Pred>,
626    ) -> QueryResult {
627        match expr.kind() {
628            rty::ExprKind::BinaryOp(rty::BinOp::And, e1, e2) => {
629                self.assumption_to_fixpoint_aux(e1, bindings, preds)?;
630                self.assumption_to_fixpoint_aux(e2, bindings, preds)?;
631            }
632            rty::ExprKind::KVar(kvar) => {
633                preds.push(self.kvar_to_fixpoint(kvar, bindings)?);
634            }
635            rty::ExprKind::ForAll(_) => {
636                // If a forall appears in assumptive position replace it with true. This is sound
637                // because we are weakening the context, i.e., anything true without the assumption
638                // should remain true after adding it. Note that this relies on the predicate
639                // appearing negatively. This is guaranteed by the surface syntax because foralls
640                // can only appear at the top-level in a requires clause.
641                preds.push(fixpoint::Pred::TRUE);
642            }
643            _ => {
644                preds.push(fixpoint::Pred::Expr(self.ecx.expr_to_fixpoint(expr, &mut self.scx)?));
645            }
646        }
647        Ok(())
648    }
649
650    fn kvar_to_fixpoint(
651        &mut self,
652        kvar: &rty::KVar,
653        bindings: &mut Vec<fixpoint::Bind>,
654    ) -> QueryResult<fixpoint::Pred> {
655        let decl = self.kvars.get(kvar.kvid);
656        let kvids = self.kcx.encode(kvar.kvid, decl, &mut self.scx);
657
658        let all_args = iter::zip(&kvar.args, &decl.sorts)
659            .map(|(arg, sort)| self.ecx.imm(arg, sort, &mut self.scx, bindings))
660            .try_collect_vec()?;
661
662        // Fixpoint doesn't support kvars without arguments, which we do generate sometimes. To get
663        // around it, we encode `$k()` as ($k 0), or more precisely `(forall ((x int) (= x 0)) ... ($k x)`
664        // after ANF-ing.
665        if all_args.is_empty() {
666            let fresh = self.ecx.local_var_env.fresh_name();
667            let var = fixpoint::Var::Local(fresh);
668            bindings.push(fixpoint::Bind {
669                name: fresh.into(),
670                sort: fixpoint::Sort::Int,
671                pred: fixpoint::Pred::Expr(fixpoint::Expr::eq(
672                    fixpoint::Expr::Var(var),
673                    fixpoint::Expr::int(0),
674                )),
675            });
676            return Ok(fixpoint::Pred::KVar(kvids[0], vec![var]));
677        }
678
679        let kvars = kvids
680            .iter()
681            .enumerate()
682            .map(|(i, kvid)| {
683                let args = all_args[i..].to_vec();
684                fixpoint::Pred::KVar(*kvid, args)
685            })
686            .collect_vec();
687
688        Ok(fixpoint::Pred::And(kvars))
689    }
690
691    fn def_span(&self) -> Span {
692        self.genv.tcx().def_span(self.def_id)
693    }
694}
695
696fn const_to_fixpoint(cst: rty::Constant) -> fixpoint::Expr {
697    match cst {
698        rty::Constant::Int(i) => {
699            if i.is_negative() {
700                fixpoint::Expr::Neg(Box::new(fixpoint::Constant::Numeral(i.abs()).into()))
701            } else {
702                fixpoint::Constant::Numeral(i.abs()).into()
703            }
704        }
705        rty::Constant::Real(r) => fixpoint::Constant::Decimal(r).into(),
706        rty::Constant::Bool(b) => fixpoint::Constant::Boolean(b).into(),
707        rty::Constant::Char(c) => fixpoint::Constant::Numeral(u128::from(c)).into(),
708        rty::Constant::Str(s) => fixpoint::Constant::String(fixpoint::SymStr(s)).into(),
709        rty::Constant::BitVec(i, size) => fixpoint::Constant::BitVec(i, size).into(),
710    }
711}
712
713struct FixpointKVar {
714    sorts: Vec<fixpoint::Sort>,
715    orig: rty::KVid,
716}
717
718/// During encoding into fixpoint we generate multiple fixpoint kvars per kvar in flux. A
719/// [`KVarEncodingCtxt`] is used to keep track of the state needed for this.
720#[derive(Default)]
721struct KVarEncodingCtxt {
722    /// List of all kvars that need to be defined in fixpoint
723    kvars: IndexVec<fixpoint::KVid, FixpointKVar>,
724    /// A mapping from [`rty::KVid`] to the list of [`fixpoint::KVid`]s encoding the kvar.
725    map: UnordMap<rty::KVid, Vec<fixpoint::KVid>>,
726}
727
728impl KVarEncodingCtxt {
729    fn encode(
730        &mut self,
731        kvid: rty::KVid,
732        decl: &KVarDecl,
733        scx: &mut SortEncodingCtxt,
734    ) -> &[fixpoint::KVid] {
735        self.map.entry(kvid).or_insert_with(|| {
736            let all_args = decl
737                .sorts
738                .iter()
739                .map(|s| scx.sort_to_fixpoint(s))
740                .collect_vec();
741
742            // See comment in `kvar_to_fixpoint`
743            if all_args.is_empty() {
744                let sorts = vec![fixpoint::Sort::Int];
745                let kvid = self.kvars.push(FixpointKVar::new(sorts, kvid));
746                return vec![kvid];
747            }
748
749            match decl.encoding {
750                KVarEncoding::Single => {
751                    let kvid = self.kvars.push(FixpointKVar::new(all_args, kvid));
752                    vec![kvid]
753                }
754                KVarEncoding::Conj => {
755                    let n = usize::max(decl.self_args, 1);
756                    (0..n)
757                        .map(|i| {
758                            let sorts = all_args[i..].to_vec();
759                            self.kvars.push(FixpointKVar::new(sorts, kvid))
760                        })
761                        .collect_vec()
762                }
763            }
764        })
765    }
766
767    fn into_fixpoint(self) -> Vec<fixpoint::KVarDecl> {
768        self.kvars
769            .into_iter_enumerated()
770            .map(|(kvid, kvar)| {
771                fixpoint::KVarDecl::new(kvid, kvar.sorts, format!("orig: {:?}", kvar.orig))
772            })
773            .collect()
774    }
775}
776
777/// Environment used to map from [`rty::Var`] to a [`fixpoint::LocalVar`].
778struct LocalVarEnv {
779    local_var_gen: IndexGen<fixpoint::LocalVar>,
780    fvars: UnordMap<rty::Name, fixpoint::LocalVar>,
781    /// Layers of late bound variables
782    layers: Vec<Vec<fixpoint::LocalVar>>,
783}
784
785impl LocalVarEnv {
786    fn new() -> Self {
787        Self { local_var_gen: IndexGen::new(), fvars: Default::default(), layers: Vec::new() }
788    }
789
790    // This doesn't require to be mutable because `IndexGen` uses atomics, but we make it mutable
791    // to better declare the intent.
792    fn fresh_name(&mut self) -> fixpoint::LocalVar {
793        self.local_var_gen.fresh()
794    }
795
796    fn insert_fvar_map(&mut self, name: rty::Name) -> fixpoint::LocalVar {
797        let fresh = self.fresh_name();
798        self.fvars.insert(name, fresh);
799        fresh
800    }
801
802    fn remove_fvar_map(&mut self, name: rty::Name) {
803        self.fvars.remove(&name);
804    }
805
806    /// Push a layer of bound variables assigning a fresh [`fixpoint::LocalVar`] to each one
807    fn push_layer_with_fresh_names(&mut self, count: usize) {
808        let layer = (0..count).map(|_| self.fresh_name()).collect();
809        self.layers.push(layer);
810    }
811
812    fn pop_layer(&mut self) -> Vec<fixpoint::LocalVar> {
813        self.layers.pop().unwrap()
814    }
815
816    fn get_fvar(&self, name: rty::Name) -> Option<fixpoint::LocalVar> {
817        self.fvars.get(&name).copied()
818    }
819
820    fn get_late_bvar(&self, debruijn: DebruijnIndex, var: BoundVar) -> Option<fixpoint::LocalVar> {
821        let depth = self.layers.len().checked_sub(debruijn.as_usize() + 1)?;
822        self.layers[depth].get(var.as_usize()).copied()
823    }
824}
825
826impl FixpointKVar {
827    fn new(sorts: Vec<fixpoint::Sort>, orig: rty::KVid) -> Self {
828        Self { sorts, orig }
829    }
830}
831
832pub struct KVarGen {
833    kvars: IndexVec<rty::KVid, KVarDecl>,
834    /// If true, generate dummy [holes] instead of kvars. Used during shape mode to avoid generating
835    /// unnecessary kvars.
836    ///
837    /// [holes]: rty::ExprKind::Hole
838    dummy: bool,
839}
840
841impl KVarGen {
842    pub(crate) fn new(dummy: bool) -> Self {
843        Self { kvars: IndexVec::new(), dummy }
844    }
845
846    fn get(&self, kvid: rty::KVid) -> &KVarDecl {
847        &self.kvars[kvid]
848    }
849
850    /// Generate a fresh [kvar] under several layers of [binders]. Each layer may contain any kind
851    /// of bound variable, but variables that are not of kind [`BoundVariableKind::Refine`] will
852    /// be filtered out.
853    ///
854    /// The variables bound in the last layer (last element of the `binders` slice) is expected to
855    /// have only [`BoundVariableKind::Refine`] and all its elements are used as the [self arguments].
856    /// The rest of the binders are appended to the `scope`.
857    ///
858    /// Note that the returned expression will have escaping variables and it is up to the caller to
859    /// put it under an appropriate number of binders.
860    ///
861    /// Prefer using [`InferCtxt::fresh_kvar`] when possible.
862    ///
863    /// [binders]: rty::Binder
864    /// [kvar]: rty::KVar
865    /// [`InferCtxt::fresh_kvar`]: crate::infer::InferCtxt::fresh_kvar
866    /// [self arguments]: rty::KVar::self_args
867    /// [`BoundVariableKind::Refine`]: rty::BoundVariableKind::Refine
868    pub fn fresh(
869        &mut self,
870        binders: &[rty::BoundVariableKinds],
871        scope: impl IntoIterator<Item = (rty::Var, rty::Sort)>,
872        encoding: KVarEncoding,
873    ) -> rty::Expr {
874        if self.dummy {
875            return rty::Expr::hole(rty::HoleKind::Pred);
876        }
877
878        let args = itertools::chain(
879            binders.iter().rev().enumerate().flat_map(|(level, vars)| {
880                let debruijn = DebruijnIndex::from_usize(level);
881                vars.iter()
882                    .cloned()
883                    .enumerate()
884                    .flat_map(move |(idx, var)| {
885                        if let rty::BoundVariableKind::Refine(sort, _, kind) = var {
886                            let br = rty::BoundReft { var: BoundVar::from_usize(idx), kind };
887                            Some((rty::Var::Bound(debruijn, br), sort))
888                        } else {
889                            None
890                        }
891                    })
892            }),
893            scope,
894        );
895        let [.., last] = binders else {
896            return self.fresh_inner(0, [], encoding);
897        };
898        let num_self_args = last
899            .iter()
900            .filter(|var| matches!(var, rty::BoundVariableKind::Refine(..)))
901            .count();
902        self.fresh_inner(num_self_args, args, encoding)
903    }
904
905    fn fresh_inner<A>(&mut self, self_args: usize, args: A, encoding: KVarEncoding) -> rty::Expr
906    where
907        A: IntoIterator<Item = (rty::Var, rty::Sort)>,
908    {
909        // asset last one has things
910        let mut sorts = vec![];
911        let mut exprs = vec![];
912
913        let mut flattened_self_args = 0;
914        for (i, (var, sort)) in args.into_iter().enumerate() {
915            let is_self_arg = i < self_args;
916            let var = var.to_expr();
917            sort.walk(|sort, proj| {
918                if !matches!(sort, rty::Sort::Loc) {
919                    flattened_self_args += is_self_arg as usize;
920                    sorts.push(sort.clone());
921                    exprs.push(rty::Expr::field_projs(&var, proj));
922                }
923            });
924        }
925
926        let kvid = self
927            .kvars
928            .push(KVarDecl { self_args: flattened_self_args, sorts, encoding });
929
930        let kvar = rty::KVar::new(kvid, flattened_self_args, exprs);
931        rty::Expr::kvar(kvar)
932    }
933}
934
935#[derive(Clone)]
936struct KVarDecl {
937    self_args: usize,
938    sorts: Vec<rty::Sort>,
939    encoding: KVarEncoding,
940}
941
942/// How an [`rty::KVar`] is encoded in the fixpoint constraint
943#[derive(Clone, Copy)]
944pub enum KVarEncoding {
945    /// Generate a single kvar appending the self arguments and the scope, i.e.,
946    /// a kvar `$k(a0, ...)[b0, ...]` becomes `$k(a0, ..., b0, ...)` in the fixpoint constraint.
947    Single,
948    /// Generate a conjunction of kvars, one per argument in [`rty::KVar::args`].
949    /// Concretely, a kvar `$k(a0, a1, ..., an)[b0, ...]` becomes
950    /// `$k0(a0, a1, ..., an, b0, ...) ∧ $k1(a1, ..., an, b0, ...) ∧ ... ∧ $kn(an, b0, ...)`
951    Conj,
952}
953
954impl std::fmt::Display for TagIdx {
955    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
956        write!(f, "{}", self.as_u32())
957    }
958}
959
960impl std::str::FromStr for TagIdx {
961    type Err = std::num::ParseIntError;
962
963    fn from_str(s: &str) -> Result<Self, Self::Err> {
964        Ok(Self::from_u32(s.parse()?))
965    }
966}
967
968struct ExprEncodingCtxt<'genv, 'tcx> {
969    genv: GlobalEnv<'genv, 'tcx>,
970    local_var_env: LocalVarEnv,
971    global_var_gen: IndexGen<fixpoint::GlobalVar>,
972    const_map: ConstMap<'tcx>,
973    fun_def_map: FunDefMap,
974    errors: Errors<'genv>,
975    def_span: Span,
976}
977
978impl<'genv, 'tcx> ExprEncodingCtxt<'genv, 'tcx> {
979    fn new(genv: GlobalEnv<'genv, 'tcx>, def_span: Span) -> Self {
980        Self {
981            genv,
982            local_var_env: LocalVarEnv::new(),
983            global_var_gen: IndexGen::new(),
984            const_map: Default::default(),
985            fun_def_map: Default::default(),
986            errors: Errors::new(genv.sess()),
987            def_span,
988        }
989    }
990
991    fn var_to_fixpoint(&self, var: &rty::Var) -> fixpoint::Var {
992        match var {
993            rty::Var::Free(name) => {
994                self.local_var_env
995                    .get_fvar(*name)
996                    .unwrap_or_else(|| {
997                        span_bug!(self.def_span, "no entry found for name: `{name:?}`")
998                    })
999                    .into()
1000            }
1001            rty::Var::Bound(debruijn, breft) => {
1002                self.local_var_env
1003                    .get_late_bvar(*debruijn, breft.var)
1004                    .unwrap_or_else(|| {
1005                        span_bug!(self.def_span, "no entry found for late bound var: `{breft:?}`")
1006                    })
1007                    .into()
1008            }
1009            rty::Var::ConstGeneric(param) => fixpoint::Var::ConstGeneric(*param),
1010            rty::Var::EarlyParam(param) => fixpoint::Var::Param(*param),
1011            rty::Var::EVar(_) => {
1012                span_bug!(self.def_span, "unexpected evar: `{var:?}`")
1013            }
1014        }
1015    }
1016
1017    fn variant_to_fixpoint(
1018        &self,
1019        scx: &mut SortEncodingCtxt,
1020        enum_def_id: &DefId,
1021        idx: VariantIdx,
1022    ) -> fixpoint::Expr {
1023        let adt_id = scx.declare_adt(*enum_def_id);
1024        let var = fixpoint::Var::DataCtor(adt_id, idx);
1025        fixpoint::Expr::Var(var)
1026    }
1027
1028    fn fields_to_fixpoint(
1029        &mut self,
1030        flds: &[rty::Expr],
1031        scx: &mut SortEncodingCtxt,
1032    ) -> QueryResult<fixpoint::Expr> {
1033        // do not generate 1-tuples
1034        if let [fld] = flds {
1035            self.expr_to_fixpoint(fld, scx)
1036        } else {
1037            scx.declare_tuple(flds.len());
1038            let ctor = fixpoint::Expr::Var(fixpoint::Var::TupleCtor { arity: flds.len() });
1039            let args = flds
1040                .iter()
1041                .map(|fld| self.expr_to_fixpoint(fld, scx))
1042                .try_collect()?;
1043            Ok(fixpoint::Expr::App(Box::new(ctor), args))
1044        }
1045    }
1046
1047    fn internal_func_to_fixpoint(
1048        &mut self,
1049        internal_func: &InternalFuncKind,
1050        args: &[rty::Expr],
1051        scx: &mut SortEncodingCtxt,
1052    ) -> QueryResult<fixpoint::Expr> {
1053        match internal_func {
1054            InternalFuncKind::Val(op) => {
1055                let func = fixpoint::Expr::Var(self.define_const_for_prim_op(op, scx));
1056                let args = self.exprs_to_fixpoint(args, scx)?;
1057                Ok(fixpoint::Expr::App(Box::new(func), args))
1058            }
1059            InternalFuncKind::Rel(op) => {
1060                let expr = if let Some(prim_rel) = self.genv.prim_rel_for(op)? {
1061                    prim_rel.body.replace_bound_refts(args)
1062                } else {
1063                    rty::Expr::tt()
1064                };
1065                self.expr_to_fixpoint(&expr, scx)
1066            }
1067            InternalFuncKind::CharToInt | InternalFuncKind::IntToChar => {
1068                // We can erase the "call" because fixpoint uses `int` to represent `char`
1069                // so the conversions are unnecessary.
1070                self.expr_to_fixpoint(&args[0], scx)
1071            }
1072        }
1073    }
1074
1075    fn expr_to_fixpoint(
1076        &mut self,
1077        expr: &rty::Expr,
1078        scx: &mut SortEncodingCtxt,
1079    ) -> QueryResult<fixpoint::Expr> {
1080        let e = match expr.kind() {
1081            rty::ExprKind::Var(var) => fixpoint::Expr::Var(self.var_to_fixpoint(var)),
1082            rty::ExprKind::Constant(c) => const_to_fixpoint(*c),
1083            rty::ExprKind::BinaryOp(op, e1, e2) => self.bin_op_to_fixpoint(op, e1, e2, scx)?,
1084            rty::ExprKind::UnaryOp(op, e) => self.un_op_to_fixpoint(*op, e, scx)?,
1085            rty::ExprKind::FieldProj(e, proj) => self.proj_to_fixpoint(e, *proj, scx)?,
1086            rty::ExprKind::Tuple(flds) => self.fields_to_fixpoint(flds, scx)?,
1087            rty::ExprKind::Ctor(rty::Ctor::Struct(_), flds) => {
1088                self.fields_to_fixpoint(flds, scx)?
1089            }
1090            rty::ExprKind::Ctor(rty::Ctor::Enum(did, idx), _) => {
1091                self.variant_to_fixpoint(scx, did, *idx)
1092            }
1093            rty::ExprKind::ConstDefId(did) => {
1094                let var = self.define_const_for_rust_const(*did, scx);
1095                fixpoint::Expr::Var(var)
1096            }
1097            rty::ExprKind::App(func, args) => {
1098                if let rty::ExprKind::InternalFunc(func) = func.kind() {
1099                    self.internal_func_to_fixpoint(func, args, scx)?
1100                } else {
1101                    let func = self.expr_to_fixpoint(func, scx)?;
1102                    let args = self.exprs_to_fixpoint(args, scx)?;
1103                    fixpoint::Expr::App(Box::new(func), args)
1104                }
1105            }
1106            rty::ExprKind::IfThenElse(p, e1, e2) => {
1107                fixpoint::Expr::IfThenElse(Box::new([
1108                    self.expr_to_fixpoint(p, scx)?,
1109                    self.expr_to_fixpoint(e1, scx)?,
1110                    self.expr_to_fixpoint(e2, scx)?,
1111                ]))
1112            }
1113            rty::ExprKind::Alias(alias_reft, args) => {
1114                let sort = self.genv.sort_of_assoc_reft(alias_reft.assoc_id)?;
1115                let sort = sort.instantiate_identity();
1116                let func =
1117                    fixpoint::Expr::Var(self.define_const_for_alias_reft(alias_reft, sort, scx));
1118                let args = args
1119                    .iter()
1120                    .map(|expr| self.expr_to_fixpoint(expr, scx))
1121                    .try_collect()?;
1122                fixpoint::Expr::App(Box::new(func), args)
1123            }
1124            rty::ExprKind::Abs(lam) => {
1125                let var = self.define_const_for_lambda(lam, scx);
1126                fixpoint::Expr::Var(var)
1127            }
1128            rty::ExprKind::Let(init, body) => {
1129                debug_assert_eq!(body.vars().len(), 1);
1130                let init = self.expr_to_fixpoint(init, scx)?;
1131
1132                self.local_var_env.push_layer_with_fresh_names(1);
1133                let body = self.expr_to_fixpoint(body.skip_binder_ref(), scx)?;
1134                let vars = self.local_var_env.pop_layer();
1135
1136                fixpoint::Expr::Let(vars[0].into(), Box::new([init, body]))
1137            }
1138            rty::ExprKind::GlobalFunc(SpecFuncKind::Thy(itf)) => {
1139                fixpoint::Expr::Var(fixpoint::Var::Itf(*itf))
1140            }
1141            rty::ExprKind::GlobalFunc(SpecFuncKind::Uif(def_id)) => {
1142                fixpoint::Expr::Var(self.define_const_for_uif(*def_id, scx))
1143            }
1144            rty::ExprKind::GlobalFunc(SpecFuncKind::Def(def_id)) => {
1145                fixpoint::Expr::Var(self.declare_fun(*def_id))
1146            }
1147            rty::ExprKind::Hole(..)
1148            | rty::ExprKind::KVar(_)
1149            | rty::ExprKind::Local(_)
1150            | rty::ExprKind::PathProj(..)
1151            | rty::ExprKind::ForAll(_)
1152            | rty::ExprKind::InternalFunc(_) => {
1153                span_bug!(self.def_span, "unexpected expr: `{expr:?}`")
1154            }
1155            rty::ExprKind::BoundedQuant(kind, rng, body) => {
1156                let exprs = (rng.start..rng.end).map(|i| {
1157                    let arg = rty::Expr::constant(rty::Constant::from(i));
1158                    body.replace_bound_reft(&arg)
1159                });
1160                let expr = match kind {
1161                    flux_middle::fhir::QuantKind::Forall => rty::Expr::and_from_iter(exprs),
1162                    flux_middle::fhir::QuantKind::Exists => rty::Expr::or_from_iter(exprs),
1163                };
1164                self.expr_to_fixpoint(&expr, scx)?
1165            }
1166        };
1167        Ok(e)
1168    }
1169
1170    fn exprs_to_fixpoint<'b>(
1171        &mut self,
1172        exprs: impl IntoIterator<Item = &'b rty::Expr>,
1173        scx: &mut SortEncodingCtxt,
1174    ) -> QueryResult<Vec<fixpoint::Expr>> {
1175        exprs
1176            .into_iter()
1177            .map(|e| self.expr_to_fixpoint(e, scx))
1178            .try_collect()
1179    }
1180
1181    fn proj_to_fixpoint(
1182        &mut self,
1183        e: &rty::Expr,
1184        proj: rty::FieldProj,
1185        scx: &mut SortEncodingCtxt,
1186    ) -> QueryResult<fixpoint::Expr> {
1187        let arity = proj.arity(self.genv)?;
1188        // we encode 1-tuples as the single element inside so no projection necessary here
1189        if arity == 1 {
1190            self.expr_to_fixpoint(e, scx)
1191        } else {
1192            let field = proj.field_idx();
1193            scx.declare_tuple(arity);
1194            let proj = fixpoint::Var::TupleProj { arity, field };
1195            let proj = fixpoint::Expr::Var(proj);
1196            Ok(fixpoint::Expr::App(Box::new(proj), vec![self.expr_to_fixpoint(e, scx)?]))
1197        }
1198    }
1199
1200    fn un_op_to_fixpoint(
1201        &mut self,
1202        op: rty::UnOp,
1203        e: &rty::Expr,
1204        scx: &mut SortEncodingCtxt,
1205    ) -> QueryResult<fixpoint::Expr> {
1206        match op {
1207            rty::UnOp::Not => Ok(fixpoint::Expr::Not(Box::new(self.expr_to_fixpoint(e, scx)?))),
1208            rty::UnOp::Neg => Ok(fixpoint::Expr::Neg(Box::new(self.expr_to_fixpoint(e, scx)?))),
1209        }
1210    }
1211
1212    fn bv_rel_to_fixpoint(&self, rel: &fixpoint::BinRel) -> fixpoint::Expr {
1213        let itf = match rel {
1214            fixpoint::BinRel::Gt => fixpoint::ThyFunc::BvUgt,
1215            fixpoint::BinRel::Ge => fixpoint::ThyFunc::BvUge,
1216            fixpoint::BinRel::Lt => fixpoint::ThyFunc::BvUlt,
1217            fixpoint::BinRel::Le => fixpoint::ThyFunc::BvUle,
1218            _ => span_bug!(self.def_span, "not a bitvector relation!"),
1219        };
1220        fixpoint::Expr::Var(fixpoint::Var::Itf(itf))
1221    }
1222
1223    fn bv_op_to_fixpoint(&self, op: &rty::BinOp) -> fixpoint::Expr {
1224        let itf = match op {
1225            rty::BinOp::Add(_) => fixpoint::ThyFunc::BvAdd,
1226            rty::BinOp::Sub(_) => fixpoint::ThyFunc::BvSub,
1227            rty::BinOp::Mul(_) => fixpoint::ThyFunc::BvMul,
1228            rty::BinOp::Div(_) => fixpoint::ThyFunc::BvUdiv,
1229            rty::BinOp::Mod(_) => fixpoint::ThyFunc::BvUrem,
1230            rty::BinOp::BitAnd => fixpoint::ThyFunc::BvAnd,
1231            rty::BinOp::BitOr => fixpoint::ThyFunc::BvOr,
1232            rty::BinOp::BitShl => fixpoint::ThyFunc::BvShl,
1233            rty::BinOp::BitShr => fixpoint::ThyFunc::BvLshr,
1234            _ => span_bug!(self.def_span, "not a bitvector operation!"),
1235        };
1236        fixpoint::Expr::Var(fixpoint::Var::Itf(itf))
1237    }
1238
1239    fn bin_op_to_fixpoint(
1240        &mut self,
1241        op: &rty::BinOp,
1242        e1: &rty::Expr,
1243        e2: &rty::Expr,
1244        scx: &mut SortEncodingCtxt,
1245    ) -> QueryResult<fixpoint::Expr> {
1246        let op = match op {
1247            rty::BinOp::Eq => {
1248                return Ok(fixpoint::Expr::Atom(
1249                    fixpoint::BinRel::Eq,
1250                    Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1251                ));
1252            }
1253            rty::BinOp::Ne => {
1254                return Ok(fixpoint::Expr::Atom(
1255                    fixpoint::BinRel::Ne,
1256                    Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1257                ));
1258            }
1259            rty::BinOp::Gt(sort) => {
1260                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Gt, e1, e2, scx);
1261            }
1262            rty::BinOp::Ge(sort) => {
1263                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Ge, e1, e2, scx);
1264            }
1265            rty::BinOp::Lt(sort) => {
1266                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Lt, e1, e2, scx);
1267            }
1268            rty::BinOp::Le(sort) => {
1269                return self.bin_rel_to_fixpoint(sort, fixpoint::BinRel::Le, e1, e2, scx);
1270            }
1271            rty::BinOp::And => {
1272                return Ok(fixpoint::Expr::And(vec![
1273                    self.expr_to_fixpoint(e1, scx)?,
1274                    self.expr_to_fixpoint(e2, scx)?,
1275                ]));
1276            }
1277            rty::BinOp::Or => {
1278                return Ok(fixpoint::Expr::Or(vec![
1279                    self.expr_to_fixpoint(e1, scx)?,
1280                    self.expr_to_fixpoint(e2, scx)?,
1281                ]));
1282            }
1283            rty::BinOp::Imp => {
1284                return Ok(fixpoint::Expr::Imp(Box::new([
1285                    self.expr_to_fixpoint(e1, scx)?,
1286                    self.expr_to_fixpoint(e2, scx)?,
1287                ])));
1288            }
1289            rty::BinOp::Iff => {
1290                return Ok(fixpoint::Expr::Iff(Box::new([
1291                    self.expr_to_fixpoint(e1, scx)?,
1292                    self.expr_to_fixpoint(e2, scx)?,
1293                ])));
1294            }
1295            rty::BinOp::Add(rty::Sort::BitVec(_))
1296            | rty::BinOp::Sub(rty::Sort::BitVec(_))
1297            | rty::BinOp::Mul(rty::Sort::BitVec(_))
1298            | rty::BinOp::Div(rty::Sort::BitVec(_))
1299            | rty::BinOp::Mod(rty::Sort::BitVec(_))
1300            | rty::BinOp::BitAnd
1301            | rty::BinOp::BitOr
1302            | rty::BinOp::BitShl
1303            | rty::BinOp::BitShr => {
1304                let bv_func = self.bv_op_to_fixpoint(op);
1305                return Ok(fixpoint::Expr::App(
1306                    Box::new(bv_func),
1307                    vec![self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?],
1308                ));
1309            }
1310            rty::BinOp::Add(_) => fixpoint::BinOp::Add,
1311            rty::BinOp::Sub(_) => fixpoint::BinOp::Sub,
1312            rty::BinOp::Mul(_) => fixpoint::BinOp::Mul,
1313            rty::BinOp::Div(_) => fixpoint::BinOp::Div,
1314            rty::BinOp::Mod(_) => fixpoint::BinOp::Mod,
1315        };
1316        Ok(fixpoint::Expr::BinaryOp(
1317            op,
1318            Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1319        ))
1320    }
1321
1322    /// A binary relation is encoded as a structurally recursive relation between aggregate sorts.
1323    /// For "leaf" expressions, we encode them as an interpreted relation if the sort supports it,
1324    /// otherwise we use an uninterpreted function. For example, consider the following relation
1325    /// between two tuples of sort `(int, int -> int)`
1326    /// ```text
1327    /// (0, λv. v + 1) <= (1, λv. v + 1)
1328    /// ```
1329    /// The encoding in fixpoint will be
1330    ///
1331    /// ```text
1332    /// 0 <= 1 && (le (λv. v + 1) (λv. v + 1))
1333    /// ```
1334    /// Where `<=` is the (interpreted) less than or equal relation between integers and `le` is
1335    /// an uninterpreted relation between ([the encoding] of) lambdas.
1336    ///
1337    /// [the encoding]: Self::define_const_for_lambda
1338    fn bin_rel_to_fixpoint(
1339        &mut self,
1340        sort: &rty::Sort,
1341        rel: fixpoint::BinRel,
1342        e1: &rty::Expr,
1343        e2: &rty::Expr,
1344        scx: &mut SortEncodingCtxt,
1345    ) -> QueryResult<fixpoint::Expr> {
1346        let e = match sort {
1347            rty::Sort::Int | rty::Sort::Real | rty::Sort::Char => {
1348                fixpoint::Expr::Atom(
1349                    rel,
1350                    Box::new([self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?]),
1351                )
1352            }
1353            rty::Sort::BitVec(_) => {
1354                let e1 = self.expr_to_fixpoint(e1, scx)?;
1355                let e2 = self.expr_to_fixpoint(e2, scx)?;
1356                let rel = self.bv_rel_to_fixpoint(&rel);
1357                fixpoint::Expr::App(Box::new(rel), vec![e1, e2])
1358            }
1359            rty::Sort::Tuple(sorts) => {
1360                let arity = sorts.len();
1361                self.apply_bin_rel_rec(sorts, rel, e1, e2, scx, |field| {
1362                    rty::FieldProj::Tuple { arity, field }
1363                })?
1364            }
1365            rty::Sort::App(rty::SortCtor::Adt(sort_def), args)
1366                if let Some(variant) = sort_def.opt_struct_variant() =>
1367            {
1368                let def_id = sort_def.did();
1369                let sorts = variant.field_sorts(args);
1370                self.apply_bin_rel_rec(&sorts, rel, e1, e2, scx, |field| {
1371                    rty::FieldProj::Adt { def_id, field }
1372                })?
1373            }
1374            _ => {
1375                let rel = fixpoint::Expr::Var(fixpoint::Var::UIFRel(rel));
1376                fixpoint::Expr::App(
1377                    Box::new(rel),
1378                    vec![self.expr_to_fixpoint(e1, scx)?, self.expr_to_fixpoint(e2, scx)?],
1379                )
1380            }
1381        };
1382        Ok(e)
1383    }
1384
1385    /// Apply binary relation recursively over aggregate expressions
1386    fn apply_bin_rel_rec(
1387        &mut self,
1388        sorts: &[rty::Sort],
1389        rel: fixpoint::BinRel,
1390        e1: &rty::Expr,
1391        e2: &rty::Expr,
1392        scx: &mut SortEncodingCtxt,
1393        mk_proj: impl Fn(u32) -> rty::FieldProj,
1394    ) -> QueryResult<fixpoint::Expr> {
1395        Ok(fixpoint::Expr::and(
1396            sorts
1397                .iter()
1398                .enumerate()
1399                .map(|(idx, s)| {
1400                    let proj = mk_proj(idx as u32);
1401                    let e1 = e1.proj_and_reduce(proj);
1402                    let e2 = e2.proj_and_reduce(proj);
1403                    self.bin_rel_to_fixpoint(s, rel, &e1, &e2, scx)
1404                })
1405                .try_collect()?,
1406        ))
1407    }
1408
1409    fn imm(
1410        &mut self,
1411        arg: &rty::Expr,
1412        sort: &rty::Sort,
1413        scx: &mut SortEncodingCtxt,
1414        bindings: &mut Vec<fixpoint::Bind>,
1415    ) -> QueryResult<fixpoint::Var> {
1416        let arg = self.expr_to_fixpoint(arg, scx)?;
1417        // Check if it's a variable after encoding, in case the encoding produced a variable from a
1418        // non-variable.
1419        if let fixpoint::Expr::Var(var) = arg {
1420            Ok(var)
1421        } else {
1422            let fresh = self.local_var_env.fresh_name();
1423            let pred = fixpoint::Expr::eq(fixpoint::Expr::Var(fresh.into()), arg);
1424            bindings.push(fixpoint::Bind {
1425                name: fresh.into(),
1426                sort: scx.sort_to_fixpoint(sort),
1427                pred: fixpoint::Pred::Expr(pred),
1428            });
1429            Ok(fresh.into())
1430        }
1431    }
1432
1433    /// Declare that the `def_id` of a Flux function definition needs to be encoded and assigns
1434    /// a name to it if it hasn't yet been declared. The encoding of the function body happens
1435    /// in [`Self::define_funs`].
1436    fn declare_fun(&mut self, def_id: FluxDefId) -> fixpoint::Var {
1437        *self.fun_def_map.entry(def_id).or_insert_with(|| {
1438            let id = self.global_var_gen.fresh();
1439            fixpoint::Var::Global(id, Some(def_id.name()))
1440        })
1441    }
1442
1443    /// The logic below is a bit "duplicated" with the `[`prim_op_sort`] in sortck.rs;
1444    /// They are not exactly the same because this is on rty and the other one on fhir.
1445    /// We should make sure these two remain in sync.
1446    ///
1447    /// (NOTE:PrimOpSort) We are somewhat "overloading" the BinOps: as we are using them
1448    /// for (a) interpreted operations on bit vectors AND (b) uninterpreted functions on integers.
1449    /// So when Binop::BitShr (a) appears in a ExprKind::BinOp, it means bit vectors, but
1450    /// (b) inside ExprKind::InternalFunc it means int.
1451    fn prim_op_sort(op: &rty::BinOp, span: Span) -> rty::PolyFuncSort {
1452        match op {
1453            rty::BinOp::BitAnd | rty::BinOp::BitOr | rty::BinOp::BitShl | rty::BinOp::BitShr => {
1454                let fsort =
1455                    rty::FuncSort::new(vec![rty::Sort::Int, rty::Sort::Int], rty::Sort::Int);
1456                rty::PolyFuncSort::new(List::empty(), fsort)
1457            }
1458            _ => span_bug!(span, "unexpected prim op: {op:?} in `prim_op_sort`"),
1459        }
1460    }
1461
1462    fn define_const_for_prim_op(
1463        &mut self,
1464        op: &rty::BinOp,
1465        scx: &mut SortEncodingCtxt,
1466    ) -> fixpoint::Var {
1467        let key = ConstKey::PrimOp(op.clone());
1468        let span = self.def_span;
1469        self.const_map
1470            .entry(key)
1471            .or_insert_with(|| {
1472                let sort = scx.func_sort_to_fixpoint(&Self::prim_op_sort(op, span));
1473                fixpoint::ConstDecl {
1474                    name: fixpoint::Var::Global(self.global_var_gen.fresh(), None),
1475                    sort,
1476                    comment: Some(format!("prim op uif: {op:?}")),
1477                }
1478            })
1479            .name
1480    }
1481
1482    fn define_const_for_uif(
1483        &mut self,
1484        def_id: FluxDefId,
1485        scx: &mut SortEncodingCtxt,
1486    ) -> fixpoint::Var {
1487        let key = ConstKey::Uif(def_id);
1488        self.const_map
1489            .entry(key)
1490            .or_insert_with(|| {
1491                let sort = scx.func_sort_to_fixpoint(&self.genv.func_sort(def_id));
1492                fixpoint::ConstDecl {
1493                    name: fixpoint::Var::Global(self.global_var_gen.fresh(), Some(def_id.name())),
1494                    sort,
1495                    comment: Some(format!("uif: {def_id:?}")),
1496                }
1497            })
1498            .name
1499    }
1500
1501    fn define_const_for_rust_const(
1502        &mut self,
1503        def_id: DefId,
1504        scx: &mut SortEncodingCtxt,
1505    ) -> fixpoint::Var {
1506        let key = ConstKey::RustConst(def_id);
1507        self.const_map
1508            .entry(key)
1509            .or_insert_with(|| {
1510                let sort = self.genv.sort_of_def_id(def_id).unwrap().unwrap();
1511                fixpoint::ConstDecl {
1512                    name: fixpoint::Var::Global(self.global_var_gen.fresh(), None),
1513                    sort: scx.sort_to_fixpoint(&sort),
1514                    comment: Some(format!("rust const: {}", def_id_to_string(def_id))),
1515                }
1516            })
1517            .name
1518    }
1519
1520    /// returns the 'constant' UIF for Var used to represent the alias_pred, creating and adding it
1521    /// to the const_map if necessary
1522    fn define_const_for_alias_reft(
1523        &mut self,
1524        alias_reft: &rty::AliasReft,
1525        fsort: rty::FuncSort,
1526        scx: &mut SortEncodingCtxt,
1527    ) -> fixpoint::Var {
1528        let tcx = self.genv.tcx();
1529        let args = alias_reft
1530            .args
1531            .to_rustc(tcx)
1532            .truncate_to(tcx, tcx.generics_of(alias_reft.assoc_id.parent()));
1533        let key = ConstKey::Alias(alias_reft.assoc_id, args);
1534        self.const_map
1535            .entry(key)
1536            .or_insert_with(|| {
1537                let comment = Some(format!("alias reft: {alias_reft:?}"));
1538                let name = fixpoint::Var::Global(self.global_var_gen.fresh(), None);
1539                let fsort = rty::PolyFuncSort::new(List::empty(), fsort);
1540                let sort = scx.func_sort_to_fixpoint(&fsort);
1541                fixpoint::ConstDecl { name, comment, sort }
1542            })
1543            .name
1544    }
1545
1546    /// We encode lambdas with uninterpreted constant. Two syntactically equal lambdas will be encoded
1547    /// with the same constant.
1548    fn define_const_for_lambda(
1549        &mut self,
1550        lam: &rty::Lambda,
1551        scx: &mut SortEncodingCtxt,
1552    ) -> fixpoint::Var {
1553        let key = ConstKey::Lambda(lam.clone());
1554        self.const_map
1555            .entry(key)
1556            .or_insert_with(|| {
1557                let comment = Some(format!("lambda: {lam:?}"));
1558                let name = fixpoint::Var::Global(self.global_var_gen.fresh(), None);
1559                let sort = scx.func_sort_to_fixpoint(&lam.fsort().to_poly());
1560                fixpoint::ConstDecl { name, comment, sort }
1561            })
1562            .name
1563    }
1564
1565    fn assume_const_values(
1566        &mut self,
1567        mut constraint: fixpoint::Constraint,
1568        scx: &mut SortEncodingCtxt,
1569    ) -> QueryResult<fixpoint::Constraint> {
1570        // Encoding the value for a constant could in theory define more constants for which
1571        // we need to assume values, so we iterate until there are no more constants.
1572        let mut idx = 0;
1573        while let Some((key, const_)) = self.const_map.get_index(idx) {
1574            idx += 1;
1575
1576            let ConstKey::RustConst(def_id) = key else { continue };
1577            let info = self.genv.constant_info(def_id)?;
1578            match info {
1579                rty::ConstantInfo::Uninterpreted => {}
1580                rty::ConstantInfo::Interpreted(val, _) => {
1581                    let e1 = fixpoint::Expr::Var(const_.name);
1582                    let e2 = self.expr_to_fixpoint(&val, scx)?;
1583                    let pred = fixpoint::Pred::Expr(e1.eq(e2));
1584                    constraint = fixpoint::Constraint::ForAll(
1585                        fixpoint::Bind {
1586                            name: fixpoint::Var::Underscore,
1587                            sort: fixpoint::Sort::Int,
1588                            pred,
1589                        },
1590                        Box::new(constraint),
1591                    );
1592                }
1593            }
1594        }
1595        Ok(constraint)
1596    }
1597
1598    fn qualifiers_for(
1599        &mut self,
1600        def_id: LocalDefId,
1601        scx: &mut SortEncodingCtxt,
1602    ) -> QueryResult<Vec<fixpoint::Qualifier>> {
1603        self.genv
1604            .qualifiers_for(def_id)?
1605            .map(|qual| self.qualifier_to_fixpoint(qual, scx))
1606            .try_collect()
1607    }
1608
1609    fn define_funs(
1610        &mut self,
1611        def_id: MaybeExternId,
1612        scx: &mut SortEncodingCtxt,
1613    ) -> QueryResult<(Vec<fixpoint::FunDef>, Vec<fixpoint::ConstDecl>)> {
1614        let reveals: UnordSet<FluxDefId> = self.genv.reveals_for(def_id.local_id())?.collect();
1615        let mut consts = vec![];
1616        let mut defs = vec![];
1617
1618        // We iterate until encoding the body of functions doesn't require any more functions
1619        // to be encoded.
1620        let mut idx = 0;
1621        while let Some((&did, &name)) = self.fun_def_map.get_index(idx) {
1622            idx += 1;
1623
1624            let comment = format!("flux def: {did:?}");
1625            let info = self.genv.normalized_info(did);
1626            let revealed = reveals.contains(&did);
1627            if info.hide && !revealed {
1628                let sort = scx.func_sort_to_fixpoint(&self.genv.func_sort(did));
1629                consts.push(fixpoint::ConstDecl { name, sort, comment: Some(comment) });
1630            } else {
1631                let out = scx.sort_to_fixpoint(self.genv.func_sort(did).expect_mono().output());
1632                let (args, body) = self.body_to_fixpoint(&info.body, scx)?;
1633                let fun_def = fixpoint::FunDef { name, args, body, out, comment: Some(comment) };
1634                defs.push((info.rank, fun_def));
1635            };
1636        }
1637
1638        // we sort by rank so the definitions go out without any forward dependencies.
1639        let defs = defs
1640            .into_iter()
1641            .sorted_by_key(|(rank, _)| *rank)
1642            .map(|(_, def)| def)
1643            .collect();
1644
1645        Ok((defs, consts))
1646    }
1647
1648    fn body_to_fixpoint(
1649        &mut self,
1650        body: &rty::Binder<rty::Expr>,
1651        scx: &mut SortEncodingCtxt,
1652    ) -> QueryResult<(Vec<(fixpoint::Var, fixpoint::Sort)>, fixpoint::Expr)> {
1653        self.local_var_env
1654            .push_layer_with_fresh_names(body.vars().len());
1655
1656        let expr = self.expr_to_fixpoint(body.as_ref().skip_binder(), scx)?;
1657
1658        let args: Vec<(fixpoint::Var, fixpoint::Sort)> =
1659            iter::zip(self.local_var_env.pop_layer(), body.vars())
1660                .map(|(name, var)| (name.into(), scx.sort_to_fixpoint(var.expect_sort())))
1661                .collect();
1662
1663        Ok((args, expr))
1664    }
1665
1666    fn qualifier_to_fixpoint(
1667        &mut self,
1668        qualifier: &rty::Qualifier,
1669        scx: &mut SortEncodingCtxt,
1670    ) -> QueryResult<fixpoint::Qualifier> {
1671        let (args, body) = self.body_to_fixpoint(&qualifier.body, scx)?;
1672        let name = qualifier.def_id.name().to_string();
1673        Ok(fixpoint::Qualifier { name, args, body })
1674    }
1675}
1676
1677fn mk_implies(assumption: fixpoint::Pred, cstr: fixpoint::Constraint) -> fixpoint::Constraint {
1678    fixpoint::Constraint::ForAll(
1679        fixpoint::Bind {
1680            name: fixpoint::Var::Underscore,
1681            sort: fixpoint::Sort::Int,
1682            pred: assumption,
1683        },
1684        Box::new(cstr),
1685    )
1686}