Skip to content
Snippets Groups Projects
code.rs 42.49 KiB
// © 2023, The RefinedRust Developers and Contributors
//
// This Source Code Form is subject to the terms of the BSD-3-clause License.
// If a copy of the BSD-3-clause license was not distributed with this
// file, You can obtain one at https://opensource.org/license/bsd-3-clause/.

/// Provides the Coq AST for code and specifications as well as utilities for
/// constructing them.
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt::Write as fmtWrite;
use std::io::Write as ioWrite;
use std::{fmt, io};

use derive_more::{Constructor, Display};
use indent_write::indentable::Indentable;
use indent_write::io::IndentWriter;
use indoc::{formatdoc, writedoc};
use log::info;
use typed_arena::Arena;

use crate::specs::*;
use crate::{coq, display_list, make_indent, push_str_list, write_list, BASE_INDENT};

fn fmt_comment(o: &Option<String>) -> String {
    match o {
        None => String::new(),
        Some(comment) => format!(" (* {} *)", comment),
    }
}

fn fmt_option<T: Display>(o: &Option<T>) -> String {
    match o {
        None => "None".to_owned(),
        Some(x) => format!("Some ({})", x),
    }
}

/// A representation of syntactic Rust types that we can use in annotations for the `RefinedRust`
/// type system.
#[derive(Clone, Eq, PartialEq, Debug, Display)]
pub enum RustType {
    #[display("RSTLitType [{}] [{}]", display_list!(_0, "; ", "\"{}\""), display_list!(_1, "; "))]
    Lit(Vec<String>, Vec<RustType>),

    #[display("RSTTyVar \"{}\"", _0)]
    TyVar(String),

    #[display("RSTInt {}", _0)]
    Int(IntType),

    #[display("RSTBool")]
    Bool,

    #[display("RSTChar")]
    Char,

    #[display("RSTUnit")]
    Unit,

    #[display("RSTRef Mut \"{}\" ({})", _1, &_0)]
    MutRef(Box<RustType>, Lft),

    #[display("RSTRef Shr \"{}\" ({})", _1, &_0)]
    ShrRef(Box<RustType>, Lft),

    #[display("RSTBox ({})", &_0)]
    PrimBox(Box<RustType>),

    #[display("RSTStruct ({}) [{}]", _0, display_list!(_1, "; "))]
    Struct(String, Vec<RustType>),

    #[display("RSTArray {} ({})", _0, &_1)]
    Array(usize, Box<RustType>),
}

impl RustType {
    #[must_use]
    pub fn of_type(ty: &Type<'_>) -> Self {
        info!("Translating rustType: {:?}", ty);
        match ty {
            Type::Int(it) => Self::Int(*it),
            Type::Bool => Self::Bool,
            Type::Char => Self::Char,

            Type::MutRef(ty, lft) => {
                let ty = Self::of_type(ty);
                Self::MutRef(Box::new(ty), lft.clone())
            },

            Type::ShrRef(ty, lft) => {
                let ty = Self::of_type(ty);
                Self::ShrRef(Box::new(ty), lft.clone())
            },

            Type::BoxType(ty) => {
                let ty = Self::of_type(ty);
                Self::PrimBox(Box::new(ty))
            },

            Type::Struct(as_use) => {
                let is_raw = as_use.is_raw();

                let Some(def) = as_use.def else {
                    return Self::Unit;
                };

                // translate type parameter instantiations
                let typarams: Vec<_> = as_use.ty_params.iter().map(|ty| Self::of_type(ty)).collect();
                let ty_name = if is_raw { def.plain_ty_name() } else { def.public_type_name() };

                Self::Lit(vec![ty_name.to_owned()], typarams)
            },

            Type::Enum(ae_use) => {
                let typarams: Vec<_> = ae_use.ty_params.iter().map(|ty| Self::of_type(ty)).collect();

                Self::Lit(vec![ae_use.def.public_type_name().to_owned()], typarams)
            },

            Type::LiteralParam(lit) => Self::TyVar(lit.rust_name.clone()),

            Type::Literal(lit) => {
                let typarams: Vec<_> = lit.params.iter().map(|ty| Self::of_type(ty)).collect();
                Self::Lit(vec![lit.def.type_term.clone()], typarams)
            },

            Type::Uninit(_) => {
                panic!("RustType::of_type: uninit is not a Rust type");
            },

            Type::Unit => Self::Unit,

            Type::Never => {
                panic!("RustType::of_type: cannot translate Never type");
            },

            Type::RawPtr => Self::Lit(vec!["alias_ptr_t".to_owned()], vec![]),
        }
    }
}

/**
 * Caesium literals.
 *
 * This is much more constrained than the Coq version of values, as we do not need to represent
 * runtime values.
 */
#[derive(Clone, Eq, PartialEq, Debug, Display)]
pub enum Literal {
    #[display("I2v ({}) {}", _0, IntType::I8)]
    I8(i8),

    #[display("I2v ({}) {}", _0, IntType::I16)]
    I16(i16),

    #[display("I2v ({}) {}", _0, IntType::I32)]
    I32(i32),

    #[display("I2v ({}) {}", _0, IntType::I64)]
    I64(i64),

    #[display("I2v ({}) {}", _0, IntType::I128)]
    I128(i128),

    #[display("I2v ({}) {}", _0, IntType::U8)]
    U8(u8),

    #[display("I2v ({}) {}", _0, IntType::U16)]
    U16(u16),

    #[display("I2v ({}) {}", _0, IntType::U32)]
    U32(u32),

    #[display("I2v ({}) {}", _0, IntType::U64)]
    U64(u64),

    #[display("I2v ({}) {}", _0, IntType::U128)]
    U128(u128),

    #[display("val_of_bool {}", _0)]
    Bool(bool),

    #[display("I2v ({}) CharIt", *_0 as u32)]
    Char(char),

    /// name of the loc
    #[display("{}", _0)]
    Loc(String),

    /// dummy literal for ZST values (e.g. ())
    #[display("zst_val")]
    ZST,
}

/**
 * Caesium expressions
 */
#[derive(Clone, Eq, PartialEq, Debug, Display)]
pub enum Expr {
    #[display("\"{}\"", _0)]
    Var(String),

    /// a Coq-level parameter with a given Coq name
    #[display("{}", _0)]
    MetaParam(String),

    /// A call target, annotated with the type instantiation
    #[display("{}", _0)]
    CallTarget(String, Vec<RustType>, Vec<Lft>),
    #[display("{}", _0)]
    Literal(Literal),

    #[display("UnOp ({}) ({}) ({})", o, ot, &e)]
    UnOp { o: Unop, ot: OpType, e: Box<Expr> },

    #[display("({}) {} ({})", &e1, o.caesium_fmt(ot1, ot2), &e2)]
    BinOp {
        o: Binop,
        ot1: OpType,
        ot2: OpType,
        e1: Box<Expr>,
        e2: Box<Expr>,
    },

    /// dereference an lvalue
    #[display("!{{ {} }} ( {} )", ot, &e)]
    Deref { ot: OpType, e: Box<Expr> },

    /// lvalue to rvalue conversion
    #[display("use{{ {} }} ({})", ot, &e)]
    Use { ot: OpType, e: Box<Expr> },

    /// the borrow-operator to get a reference
    #[display("&ref{{ {}, {}, \"{}\" }} ({})", bk, fmt_option(ty), lft, &e)]
    Borrow {
        lft: Lft,
        bk: BorKind,
        ty: Option<RustType>,
        e: Box<Expr>,
    },

    /// the address-of operator to get a raw pointer
    #[display("&raw{{ {} }} ({})", mt, &e)]
    AddressOf { mt: Mutability, e: Box<Expr> },

    #[display("CallE {} [{}] [{}] [@{{expr}} {}]", &f, display_list!(lfts, "; ", "\"{}\""), display_list!(tys, "; ", "{}"), display_list!(args, "; "))]
    Call {
        f: Box<Expr>,
        lfts: Vec<Lft>,
        tys: Vec<RustType>,
        args: Vec<Expr>,
    },

    #[display("IfE ({}) ({}) ({}) ({})", ot, &e1, &e2, &e3)]
    If {
        ot: OpType,
        e1: Box<Expr>,
        e2: Box<Expr>,
        e3: Box<Expr>,
    },

    #[display("({}) at{{ {} }} \"{}\"", &e, sls, name)]
    FieldOf {
        e: Box<Expr>,
        sls: String,
        name: String,
    },

    /// an annotated expression
    #[display("AnnotExpr{} {} ({}) ({})", fmt_comment(why), a.needs_laters(), a, &e)]
    Annot {
        a: Annotation,
        why: Option<String>,
        e: Box<Expr>,
    },

    #[display("StructInit {} [{}]", sls, display_list!(components, "; ", |(name, e)| format!("(\"{name}\", {e} : expr)")))]
    StructInitE {
        sls: coq::term::App<String, String>,
        components: Vec<(String, Expr)>,
    },

    #[display("EnumInit {} \"{}\" ({}) ({})", els, variant, ty, &initializer)]
    EnumInitE {
        els: coq::term::App<String, String>,
        variant: String,
        ty: RustType,
        initializer: Box<Expr>,
    },

    #[display("AnnotExpr 0 DropAnnot ({})", &_0)]
    DropE(Box<Expr>),

    /// a box expression for creating a box of a particular type
    #[display("box{{{}}}", &_0)]
    BoxE(SynType),

    /// access the discriminant of an enum
    #[display("EnumDiscriminant ({}) ({})", els, &e)]
    EnumDiscriminant { els: String, e: Box<Expr> },

    /// access to the data of an enum
    #[display("EnumData ({}) \"{}\" ({})", els, variant, &e)]
    EnumData {
        els: String,
        variant: String,
        e: Box<Expr>,
    },
}

impl Expr {
    #[must_use]
    pub fn with_optional_annotation(e: Self, a: Option<Annotation>, why: Option<String>) -> Self {
        match a {
            Some(a) => Self::Annot {
                a,
                e: Box::new(e),
                why,
            },
            None => e,
        }
    }
}

/// for unique/shared pointers
#[derive(Clone, Eq, PartialEq, Debug, Display)]
pub enum Mutability {
    #[display("Mut")]
    Mut,

    #[display("Shr")]
    Shared,
}

/**
 * Borrows allowed in Caesium
 */
#[derive(Clone, Eq, PartialEq, Debug, Display)]
pub enum BorKind {
    #[display("Mut")]
    Mutable,

    #[display("Shr")]
    Shared,
}

#[derive(Clone, Eq, PartialEq, Debug, Display)]
pub enum LftNameTree {
    #[display("LftNameTreeLeaf")]
    Leaf,

    #[display("LftNameTreeRef \"{}\" ({})", _0, &_1)]
    Ref(Lft, Box<LftNameTree>),
    // TODO structs etc
}

#[derive(Clone, Eq, PartialEq, Debug, Display)]
pub enum Annotation {
    /// Start a lifetime as a sublifetime of the intersection of a few other lifetimes
    #[display("StartLftAnnot \"{}\" [{}]", _0, display_list!(_1, "; ", "\"{}\""))]
    StartLft(Lft, Vec<Lft>),

    /// End this lifetime
    #[display("EndLftAnnot \"{}\"", _0)]
    EndLft(Lft),

    /// Extend this lifetime by making the directly owned part static
    #[display("ExtendLftAnnot \"{}\"", _0)]
    ExtendLft(Lft),

    /// Dynamically include a lifetime in another lifetime
    #[display("DynIncludeLftAnnot \"{}\" \"{}\"", _0, _1)]
    DynIncludeLft(Lft, Lft),

    /// Shorten the lifetime of an object to the given lifetimes, according to the name map
    #[display("ShortenLftAnnot ({})", _0)]
    ShortenLft(LftNameTree),

    /// add naming for the lifetimes in the type of the expression to the name context,
    /// at the given names
    #[display("GetLftNamesAnnot ({})", _0)]
    GetLftNames(LftNameTree),

    /// Copy a lft name map entry from lft1 to lft2
    #[display("CopyLftNameAnnot \"{}\" \"{}\"", _1, _0)]
    CopyLftName(Lft, Lft),

    /// Create an alias for an intersection of lifetimes
    #[display("AliasLftAnnot \"{}\" [{}]", _0, display_list!(_1, "; ", "\"{}\""))]
    AliasLftIntersection(Lft, Vec<Lft>),

    /// The following Goto will enter a loop
    #[display("EnterLoopAnnot")]
    EnterLoop,
}

impl Annotation {
    #[allow(clippy::unused_self)]
    pub(crate) const fn needs_laters(&self) -> u32 {
        0
    }
}

type BlockLabel = usize;

#[derive(Clone, Eq, PartialEq, Debug, Display)]
pub enum Stmt {
    #[display("Goto \"_bb{}\"", _0)]
    GotoBlock(BlockLabel),

    #[display("return ({})", _0)]
    Return(Expr),

    #[display(
        "if{{ {} }}: {} then\n{}\nelse\n{}",
        ot,
        e,
        &s1.indented(&make_indent(1)),
        &s2.indented(&make_indent(1))
    )]
    If {
        ot: OpType,
        e: Expr,
        s1: Box<Stmt>,
        s2: Box<Stmt>,
    },

    #[display(
        "Switch ({}: int_type) ({}) ({}∅) ([{}]) ({})",
        it,
        e,
        display_list!(index_map, "", |(k, v)| format!("<[ {k}%Z := {v}%nat ]> $ ")),
        display_list!(bs, "; "),
        &def
    )]
    Switch {
        // e needs to evaluate to an integer/variant index used as index to bs
        e: Expr,
        it: IntType,
        index_map: HashMap<u128, usize>,
        bs: Vec<Stmt>,
        def: Box<Stmt>,
    },

    #[display("{} <-{{ {} }} {};\n{}", e1, ot, e2, &s)]
    Assign {
        ot: OpType,
        e1: Expr,
        e2: Expr,
        s: Box<Stmt>,
    },

    #[display("expr: {};\n{}", e, &s)]
    ExprS { e: Expr, s: Box<Stmt> },

    #[display("assert{{ {} }}: {};\n{}", OpType::Bool, e, &s)]
    AssertS { e: Expr, s: Box<Stmt> },

    #[display("annot: {};{}\n{}", a, fmt_comment(why), &s)]
    Annot {
        a: Annotation,
        s: Box<Stmt>,
        why: Option<String>,
    },

    #[display("StuckS")]
    Stuck,
}

impl Stmt {
    /// Annotate a statement with a list of annotations
    #[must_use]
    pub fn with_annotations(mut s: Self, a: Vec<Annotation>, why: &Option<String>) -> Self {
        for annot in a {
            s = Self::Annot {
                a: annot,
                s: Box::new(s),
                why: why.clone(),
            };
        }
        s
    }
}

#[derive(Clone, Eq, PartialEq, Debug, Display)]
pub enum Unop {
    #[display("NegOp")]
    Neg,
    #[display("NotBoolOp")]
    NotBool,

    #[display("NotIntOp")]
    NotInt,

    #[display("CastOp ({})", _0)]
    Cast(OpType),
}

#[derive(Clone, Eq, PartialEq, Debug)]
pub enum Binop {
    //arithmetic
    Add,
    Sub,
    Mul,
    Div,
    Mod,

    // logical
    And,
    Or,

    //bitwise
    BitAnd,
    BitOr,
    BitXor,
    Shl,
    Shr,

    // comparison
    Eq,
    Ne,
    Lt,
    Gt,
    Le,
    Ge,

    // pointer operations
    PtrOffset(Layout),
    PtrNegOffset(Layout),
    PtrDiff(Layout),

    // checked ops
    CheckedAdd,
    CheckedSub,
    CheckedMul,
}

impl Binop {
    fn caesium_fmt(&self, ot1: &OpType, ot2: &OpType) -> String {
        let format_prim = |st: &str| format!("{} {} , {} }}", st, ot1, ot2);
        let format_bool = |st: &str| format!("{} {} , {} , {} }}", st, ot1, ot2, BOOL_REPR);

        match self {
            Self::Add => format_prim("+{"),
            Self::Sub => format_prim("-{"),
            Self::Mul => format_prim("×{"),
            Self::CheckedAdd => format_prim("+c{"),
            Self::CheckedSub => format_prim("-c{"),
            Self::CheckedMul => format_prim("×c{"),
            Self::Div => format_prim("/{"),
            Self::Mod => format_prim("%{"),
            Self::And => format_bool("&&{"),
            Self::Or => format_bool("||{"),
            Self::BitAnd => format_prim("&b{"),
            Self::BitOr => format_prim("|{"),
            Self::BitXor => format_prim("^{"),
            Self::Shl => format_prim("<<{"),
            Self::Shr => format_prim(">>{"),
            Self::Eq => format_bool("= {"),
            Self::Ne => format_bool("!= {"),
            Self::Lt => format_bool("<{"),
            Self::Gt => format_bool(">{"),
            Self::Le => format_bool("≤{"),
            Self::Ge => format_bool("≥{"),
            Self::PtrOffset(ly) => format!("at_offset{{ {} , {} , {} }}", ly, ot1, ot2),
            Self::PtrNegOffset(ly) => format!("at_neg_offset{{ {} , {} , {} }}", ly, ot1, ot2),
            Self::PtrDiff(ly) => format!("sub_ptr{{ {} , {} , {} }}", ly, ot1, ot2),
        }
    }
}

/**
 * A variable in the Caesium code, composed of a name and a type.
 */
#[derive(Clone, Eq, PartialEq, Debug)]
struct Variable((String, SynType));

impl Variable {
    #[must_use]
    pub const fn new(name: String, st: SynType) -> Self {
        Self((name, st))
    }
}

/**
 * Maintain necessary info to map MIR places to Caesium stack variables.
 */
pub struct StackMap {
    args: Vec<Variable>,
    locals: Vec<Variable>,
    used_names: HashSet<String>,
}

impl StackMap {
    #[must_use]
    pub fn new() -> Self {
        Self {
            args: Vec::new(),
            locals: Vec::new(),
            used_names: HashSet::new(),
        }
    }

    pub fn insert_local(&mut self, name: String, st: SynType) -> bool {
        if self.used_names.contains(&name) {
            return false;
        }
        self.used_names.insert(name.clone());
        self.locals.push(Variable::new(name, st));
        true
    }

    pub fn insert_arg(&mut self, name: String, st: SynType) -> bool {
        if self.used_names.contains(&name) {
            return false;
        }
        self.used_names.insert(name.clone());
        self.args.push(Variable::new(name, st));
        true
    }

    #[must_use]
    pub fn lookup_binding(&self, name: &str) -> Option<&SynType> {
        if !self.used_names.contains(name) {
            return None;
        }

        for Variable((nm, st)) in &self.locals {
            if nm == name {
                return Some(st);
            }
        }

        for Variable((nm, st)) in &self.args {
            if nm == name {
                return Some(st);
            }
        }

        panic!("StackMap: invariant violation");
    }
}

/// Representation of a Caesium function's source code
#[allow(clippy::module_name_repetitions)]
pub struct FunctionCode {
    name: String,
    stack_layout: StackMap,
    basic_blocks: BTreeMap<usize, Stmt>,

    /// Coq parameters that the function is parameterized over
    required_parameters: Vec<(coq::term::Name, coq::term::Type)>,
}

fn make_map_string(sep: &str, els: &Vec<(String, String)>) -> String {
    let mut out = String::with_capacity(100);
    for (key, value) in els {
        out.push_str(sep);

        out.push_str(format!("<[\n    \"{key}\" :=\n{value}\n    ]>%E $").as_str());
    }
    out.push_str(sep);
    out.push('∅');
    out
}

fn make_lft_map_string(els: &Vec<(String, String)>) -> String {
    let mut out = String::with_capacity(100);
    for (key, value) in els {
        out.push_str(format!("named_lft_update \"{}\" {} $ ", key, value).as_str());
    }
    out.push('∅');
    out
}

impl Display for FunctionCode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fn fmt_params((name, ty): &(coq::term::Name, coq::term::Type)) -> String {
            format!("({} : {})", name, ty)
        }

        fn fmt_variable(Variable((name, ty)): &Variable) -> String {
            format!("(\"{}\", {} : layout)", name, Layout::from(ty))
        }

        fn fmt_blocks((name, bb): (&usize, &Stmt)) -> String {
            formatdoc!(
                r#"<[
                   "_bb{}" :=
                    {}
                   ]>%E $"#,
                name,
                bb.indented_skip_initial(&make_indent(1))
            )
        }

        let params = display_list!(&self.required_parameters, " ", fmt_params);
        let args = display_list!(&self.stack_layout.args, ";\n", fmt_variable);
        let locals = display_list!(&self.stack_layout.locals, ";\n", fmt_variable);
        let blocks = display_list!(&self.basic_blocks, "\n", fmt_blocks);

        writedoc!(
            f,
            r#"Definition {}_def {} : function := {{|
                f_args := [
                 {}
                ];
                f_local_vars := [
                 {}
                ];
                f_code :=
                 {}
                 ∅;
                f_init := "_bb0";
               |}}."#,
            self.name,
            params,
            args.indented_skip_initial(&make_indent(2)),
            locals.indented_skip_initial(&make_indent(2)),
            blocks.indented_skip_initial(&make_indent(2))
        )?;
        Ok(())
    }
}

impl FunctionCode {
    /// Get the number of arguments of the function.
    #[must_use]
    pub fn get_argument_count(&self) -> usize {
        self.stack_layout.args.len()
    }
}

/// Builder for a `FunctionCode`.
pub struct FunctionCodeBuilder {
    stack_layout: StackMap,
    basic_blocks: BTreeMap<usize, Stmt>,
}

impl FunctionCodeBuilder {
    #[must_use]
    pub fn new() -> Self {
        Self {
            stack_layout: StackMap::new(),
            basic_blocks: BTreeMap::new(),
        }
    }

    pub fn add_argument(&mut self, name: &str, st: SynType) {
        self.stack_layout.insert_arg(name.to_owned(), st);
    }

    pub fn add_local(&mut self, name: &str, st: SynType) {
        self.stack_layout.insert_local(name.to_owned(), st);
    }

    pub fn add_basic_block(&mut self, index: usize, bb: Stmt) {
        self.basic_blocks.insert(index, bb);
    }
}

#[derive(Clone, Debug, Display)]
#[display("({}PolyNil)", display_list!(_0, "",
    |(bb, spec)| format!("PolyCons ({}, wrap_inv ({})) $ ", bb, spec.func_predicate))
)]
struct InvariantMap(HashMap<usize, LoopSpec>);

/// A Caesium function bundles up the Caesium code itself as well as the generated specification
/// for it.
#[allow(clippy::partial_pub_fields)]
pub struct Function<'def> {
    pub code: FunctionCode,
    pub spec: &'def FunctionSpec<InnerFunctionSpec<'def>>,

    /// Other functions that are used by this one.
    other_functions: Vec<UsedProcedure<'def>>,
    /// Syntypes that we assume to be layoutable in the typing proof
    layoutable_syntys: Vec<SynType>,
    /// Custom tactics for the generated proof
    manual_tactics: Vec<String>,
    /// used statics
    used_statics: Vec<StaticMeta<'def>>,

    /// invariants for loop head bbs
    loop_invariants: InvariantMap,

    /// Extra linktime assumptions
    pub extra_link_assum: Vec<String>,
}

impl<'def> Function<'def> {
    /// Get the name of the function.
    #[must_use]
    pub fn name(&self) -> &str {
        &self.code.name
    }

    pub fn generate_lemma_statement<F>(&self, f: &mut F) -> Result<(), io::Error>
    where
        F: io::Write,
    {
        // indent
        let mut writer = IndentWriter::new_skip_initial(BASE_INDENT, &mut *f);
        let f = &mut writer;

        writeln!(f, "Definition {}_lemma (π : thread_id) : Prop :=", self.name())?;

        // write coq parameters
        let params = self.spec.get_all_coq_params();
        let has_params =
            !params.0.is_empty() || !self.other_functions.is_empty() || !self.used_statics.is_empty();
        if has_params {
            write!(f, "∀ ")?;
            for param in &params.0 {
                write!(f, "{} ", param)?;
            }

            // assume locations for other functions
            for proc_use in &self.other_functions {
                write!(f, "({} : loc) ", proc_use.loc_name)?;
            }

            // assume locations for statics
            for s in &self.used_statics {
                write!(f, "({} : loc) ", s.loc_name)?;
            }
            writeln!(f, ", ")?;
        }

        // assume link-time Coq assumptions
        // layoutable
        for st in &self.layoutable_syntys {
            write!(f, "syn_type_is_layoutable ({}) →\n", st)?;
        }
        // statics are registered
        for s in &self.used_statics {
            write!(f, "static_is_registered \"{}\" {} (existT _ {}) →\n", s.ident, s.loc_name, s.ty)?;
        }

        // write extra link-time assumptions
        if !self.extra_link_assum.is_empty() {
            write!(f, "(* extra link-time assumptions *)\n")?;
            for s in &self.extra_link_assum {
                write!(f, "{s} →\n")?;
            }
        }

        // write iris assums
        if self.other_functions.is_empty() {
            write!(f, "⊢ ")?;
        } else {
            for proc_use in &self.other_functions {
                info!(
                    "Using other function: {:?} with insts: {:?}",
                    proc_use.spec_name, proc_use.type_params
                );

                let arg_syntys: Vec<String> =
                    proc_use.syntype_of_all_args.iter().map(ToString::to_string).collect();

                write!(
                    f,
                    "{} ◁ᵥ{{π}} {} @ function_ptr [{}] {} -∗\n",
                    proc_use.loc_name,
                    proc_use.loc_name,
                    arg_syntys.join("; "),
                    proc_use,
                )?;
            }
        }

        write!(f, "typed_function π ")?;
        write!(f, "({}_def ", self.name())?;

        // add arguments for the code definition
        let mut code_params: Vec<_> =
            self.other_functions.iter().map(|proc_use| proc_use.loc_name.clone()).collect();

        for names in self.spec.generics.get_coq_ty_st_params().make_using_terms() {
            code_params.push(format!("{names}"));
        }
        for s in &self.used_statics {
            code_params.push(s.loc_name.clone());
        }
        for x in &code_params {
            write!(f, "{}  ", x)?;
        }

        // write local syntypes
        write!(f, ") [")?;
        write_list!(f, &self.code.stack_layout.locals, "; ", |Variable((_, st))| st.to_string())?;
        write!(f, "] (<tag_type> {} ", self.spec.get_spec_name())?;

        // write type args (passed to the type definition)
        for param in &params.0 {
            if !param.is_implicit() {
                write!(f, "{} ", param.get_name())?;
            }
        }

        write!(f, ").\n")
    }

    pub fn generate_proof_prelude<F>(&self, f: &mut F) -> Result<(), io::Error>
    where
        F: io::Write,
    {
        // indent
        let mut writer = IndentWriter::new_skip_initial(BASE_INDENT, &mut *f);
        let f = &mut writer;

        write!(f, "Ltac {}_prelude :=\n", self.name())?;

        write!(f, "unfold {}_lemma;\n", self.name())?;
        write!(f, "set (FN_NAME := FUNCTION_NAME \"{}\");\n", self.name())?;

        // intros spec params
        let params = self.spec.get_all_coq_params();
        if !params.0.is_empty() {
            write!(f, "intros")?;
            for param in &params.0 {
                if param.is_implicit() {
                    write!(f, " ?")?;
                } else {
                    write!(f, " {}", param.get_name())?;
                }
            }
            writeln!(f, ";")?;
        }

        write!(f, "intros;\n")?;
        write!(f, "iStartProof;\n")?;

        // generate intro pattern for lifetimes
        let mut lft_pattern = String::with_capacity(100);
        // pattern is right-associative
        for lft in self.spec.generics.get_lfts() {
            write!(lft_pattern, "[{} ", lft).unwrap();
            write!(f, "let {} := fresh \"{}\" in\n", lft, lft)?;
        }
        write!(lft_pattern, "[]").unwrap();
        for _ in 0..self.spec.generics.get_num_lifetimes() {
            write!(lft_pattern, " ]").unwrap();
        }

        // generate intro pattern for typarams
        let mut typaram_pattern = String::with_capacity(100);
        // pattern is right-associative
        for param in self.spec.generics.get_ty_params() {
            write!(typaram_pattern, "[{} ", param.type_term).unwrap();
            write!(f, "let {} := fresh \"{}\" in\n", param.type_term, param.type_term)?;
        }
        write!(typaram_pattern, "[]").unwrap();
        for _ in 0..self.spec.generics.get_num_ty_params() {
            write!(typaram_pattern, " ]").unwrap();
        }

        // Prepare the intro pattern for specification-level parameters
        let mut ip_params = String::with_capacity(100);

        let params = self.spec.spec.get_params();
        /*
        for _ in 0..params.len() {
            write!(ip_params, "[ ").unwrap();
        }
        //write!(ip_params, "[]").unwrap();
        for p in params {
            write!(ip_params, " {}]", p.0).unwrap();
        }
        */
        if let Some(params) = params {
            if params.is_empty() {
                // no params, but still need to provide something to catch the unit
                // (and no empty intropatterns are allowed)
                ip_params.push('?');
            } else {
                // product is left-associative
                for _ in 0..(params.len() - 1) {
                    ip_params.push_str("[ ");
                }

                let mut p_count = 0;
                for (n, _) in params {
                    if p_count > 1 {
                        ip_params.push_str(" ]");
                    }
                    ip_params.push(' ');
                    p_count += 1;
                    ip_params.push_str(&n.to_string());
                }

                if p_count > 1 {
                    ip_params.push_str(" ]");
                }
            }
        } else {
            ip_params.push('?');
        }

        write!(
            f,
            "start_function \"{}\" ( {} ) ( {} ) ( {} );\n",
            self.name(),
            lft_pattern.as_str(),
            typaram_pattern.as_str(),
            ip_params.as_str()
        )?;

        write!(f, "set (loop_map := BB_INV_MAP {});\n", self.loop_invariants)?;

        // intro stack locations
        write!(f, "intros")?;

        for Variable((arg, _)) in &self.code.stack_layout.args {
            write!(f, " arg_{}", arg)?;
        }

        for Variable((local, _)) in &self.code.stack_layout.locals {
            write!(f, " local_{}", local)?;
        }

        write!(f, ";\n")?;

        // destruct specification-level parameters
        if let Some(params) = params {
            write!(f, "prepare_parameters (")?;
            for (n, _) in params {
                write!(f, " {}", n)?;
            }
            write!(f, " );\n")?;
        }

        // initialize lifetimes
        let formatted_lifetimes = make_lft_map_string(
            &self.spec.generics.get_lfts().iter().map(|n| (n.to_string(), n.to_string())).collect(),
        );
        write!(f, "init_lfts ({} );\n", formatted_lifetimes.as_str())?;

        // initialize tyvars
        let formatted_tyvars = make_map_string(
            " ",
            &self
                .spec
                .generics
                .get_ty_params()
                .iter()
                .map(|names| (names.rust_name.clone(), format!("existT _ ({})", names.type_term)))
                .collect(),
        );

        write!(f, "init_tyvars ({} ).\n", formatted_tyvars.as_str())
    }

    pub fn generate_proof<F>(&self, f: &mut F, admit_proofs: bool) -> Result<(), io::Error>
    where
        F: io::Write,
    {
        writeln!(f, "Lemma {}_proof (π : thread_id) :", self.name())?;

        {
            // indent
            let mut writer = IndentWriter::new(BASE_INDENT, &mut *f);
            let f = &mut writer;

            write!(f, "{}_lemma π.", self.name())?;
        }
        write!(f, "\n")?;
        write!(f, "Proof.\n")?;

        {
            let mut writer = IndentWriter::new(BASE_INDENT, &mut *f);
            let f = &mut writer;

            write!(f, "{}_prelude.\n\n", self.name())?;

            write!(f, "repeat liRStep; liShow.\n\n")?;
            write!(f, "all: print_remaining_goal.\n")?;
            write!(f, "Unshelve. all: sidecond_solver.\n")?;
            write!(f, "Unshelve. all: sidecond_hammer.\n")?;

            // add custom tactics specified in annotations
            for tac in &self.manual_tactics {
                if tac.starts_with("all:") {
                    write!(f, "{}\n", tac)?;
                } else {
                    write!(f, "{{ {} all: shelve. }}\n", tac)?;
                }
            }

            write!(f, "Unshelve. all: print_remaining_sidecond.\n")?;
        }

        if admit_proofs {
            write!(f, "Admitted. (* admitted due to admit_proofs config flag *)\n")?;
        } else {
            write!(f, "Qed.\n")?;
        }
        Ok(())
    }
}

/// Information on a used static variable
#[derive(Clone, Debug)]
pub struct StaticMeta<'def> {
    pub ident: String,
    pub loc_name: String,
    pub ty: Type<'def>,
}

/// Information on a used const place
#[derive(Clone, Debug)]
pub struct ConstPlaceMeta<'def> {
    pub ident: String,
    pub loc_name: String,
    pub ty: Type<'def>,
}

/// Information about another procedure this function uses
#[derive(Constructor, Clone, Debug)]
pub struct UsedProcedure<'def> {
    /// The name to use for the location parameter
    pub loc_name: String,
    /// The name of the specification definition
    pub spec_name: String,

    /// extra arguments to pass to the spec
    pub extra_spec_args: Vec<String>,

    /// Type parameters to quantify over
    /// This includes:
    /// - types this function spec needs to be generic over (in particular type variables of the calling
    ///   function)
    /// - additional lifetimes that the generic instantiation introduces, as well as all lifetime parameters
    ///   of this function
    pub quantified_scope: GenericScope,

    /// specialized specs for the trait assumptions
    pub trait_specs: Vec<SpecializedTraitSpec<'def>>,

    /// The type parameters to instantiate the spec with
    pub type_params: Vec<Type<'def>>,

    /// The lifetime paramters to instantiate the spec with
    /// (after the lifting of all parameters into the `quantified_scope`)
    pub lifetimes: Vec<Lft>,

    /// The syntactic types of all arguments
    pub syntype_of_all_args: Vec<SynType>,
}

impl<'def> Display for UsedProcedure<'def> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // quantify
        write!(f, "(<tag_type> {} ", self.quantified_scope)?;

        // instantiate refinement types
        let mut gen_rfn_type_inst = Vec::new();
        for p in &self.type_params {
            // use an empty env, these should be closed in the current environment
            let rfn = p.get_rfn_type();
            gen_rfn_type_inst.push(format!("({})", rfn));
        }
        // instantiate syntypes
        for p in &self.type_params {
            let st = SynType::from(p);
            gen_rfn_type_inst.push(format!("({})", st));
        }

        write!(f, "{} {} {} ", self.spec_name, gen_rfn_type_inst.join(" "), self.extra_spec_args.join(" "))?;

        // apply to trait specs
        for x in &self.trait_specs {
            write!(f, "{} ", x.specialized_attr)?;
            write!(f, "{} ", x)?;
        }

        // instantiate lifetimes
        for lft in &self.lifetimes {
            write!(f, " <LFT> {lft}")?;
        }

        // instantiate type variables
        for ty in &self.type_params {
            write!(f, " <TY> {ty}")?;
        }

        write!(f, " <MERGE!>)")
    }
}

/// A `CaesiumFunctionBuilder` allows to incrementally construct the functions's code and the spec
/// at the same time. It ensures that both definitions line up in the right way (for instance, by
/// ensuring that other functions are linked up in a consistent way).
#[allow(clippy::partial_pub_fields)]
pub struct FunctionBuilder<'def> {
    pub code: FunctionCodeBuilder,

    /// optionally, a specification, if one has been created
    pub spec: FunctionSpec<Option<InnerFunctionSpec<'def>>>,

    /// a sequence of other functions used by this function
    /// (Note that there may be multiple assumptions here with the same spec, if they are
    /// monomorphizations of the same function!)
    other_functions: Vec<UsedProcedure<'def>>,

    /// required trait assumptions
    trait_requirements: Vec<LiteralTraitSpecUse<'def>>,

    /// Syntypes we assume to be layoutable in the typing proof
    layoutable_syntys: Vec<SynType>,
    /// used statics
    used_statics: Vec<StaticMeta<'def>>,

    /// manually specified tactics that will be emitted in the typing proof
    tactics: Vec<String>,

    /// maps loop head bbs to loop specifications
    loop_invariants: InvariantMap,

    /// Extra link-time assumptions
    extra_link_assum: Vec<String>,
}

impl<'def> FunctionBuilder<'def> {
    #[must_use]
    pub fn new(name: &str, spec_name: &str) -> Self {
        let code_builder = FunctionCodeBuilder::new();
        let spec = FunctionSpec::empty(spec_name.to_owned(), name.to_owned(), None);
        FunctionBuilder {
            other_functions: Vec::new(),
            code: code_builder,
            spec,
            layoutable_syntys: Vec::new(),
            loop_invariants: InvariantMap(HashMap::new()),
            tactics: Vec::new(),
            used_statics: Vec::new(),
            trait_requirements: Vec::new(),
            extra_link_assum: Vec::new(),
        }
    }

    /// Require another function to be available.
    pub fn require_function(&mut self, proc_use: UsedProcedure<'def>) {
        self.other_functions.push(proc_use);
    }

    /// Require a static variable to be in scope.
    pub fn require_static(&mut self, s: StaticMeta<'def>) {
        info!("Requiring static {:?}", s);
        self.used_statics.push(s);
    }

    /// Adds a lifetime parameter to the function.
    pub fn add_universal_lifetime(&mut self, lft: Lft) {
        self.spec.generics.add_lft_param(lft);
    }

    /// Add a manual tactic used for a sidecondition proof.
    pub fn add_manual_tactic(&mut self, tac: String) {
        self.tactics.push(tac);
    }

    /// Add a generic type used by this function.
    pub fn add_ty_param(&mut self, t: LiteralTyParam) {
        self.spec.generics.add_ty_param(t);
    }

    /// Get the type parameters.
    #[must_use]
    pub fn get_ty_params(&self) -> &[LiteralTyParam] {
        self.spec.generics.get_ty_params()
    }

    /// Get the universal lifetimes.
    #[must_use]
    pub fn get_lfts(&self) -> &[Lft] {
        self.spec.generics.get_lfts()
    }

    /// Add the assumption that a particular syntype is layoutable to the typing proof.
    pub fn assume_synty_layoutable(&mut self, st: SynType) {
        self.layoutable_syntys.push(st);
    }

    /// Register a loop invariant for the basic block `bb`.
    /// Should only be called once per bb.
    pub fn register_loop_invariant(&mut self, bb: usize, spec: LoopSpec) {
        if self.loop_invariants.0.insert(bb, spec).is_some() {
            panic!("registered loop invariant multiple times");
        }
    }

    /// Add a Coq-level param that comes before the type parameters.
    pub fn add_early_coq_param(&mut self, param: coq::term::Param) {
        self.spec.early_coq_params.0.push(param);
    }

    /// Add a Coq-level param that comes after the type parameters.
    pub fn add_late_coq_param(&mut self, param: coq::term::Param) {
        self.spec.late_coq_params.0.push(param);
    }

    /// Require that a particular trait is in scope.
    pub fn add_trait_requirement(&mut self, req: LiteralTraitSpecUse<'def>) {
        self.trait_requirements.push(req);
    }

    /// Add an extra link-time assumption.
    pub fn add_linktime_assumption(&mut self, assumption: String) {
        self.extra_link_assum.push(assumption);
    }

    /// Add a default function spec.
    pub fn add_trait_function_spec(&mut self, spec: InstantiatedTraitFunctionSpec<'def>) {
        assert!(self.spec.spec.is_none(), "Overriding specification of FunctionBuilder");
        self.spec.spec = Some(InnerFunctionSpec::TraitDefault(spec));
    }

    /// Add a functon specification from a specification builder.
    pub fn add_function_spec_from_builder(&mut self, mut spec_builder: LiteralFunctionSpecBuilder<'def>) {
        // add things for traits
        // TODO: is this the right place to do this?
        for trait_use in &self.trait_requirements {
            let spec_params_param_name = trait_use.make_spec_params_param_name();

            let spec_params_type_name = trait_use.trait_ref.spec_params_record.clone();

            let spec_param_name = trait_use.make_spec_param_name();
            let spec_attrs_param_name = trait_use.make_spec_attrs_param_name();
            let spec_type = format!("{} {spec_params_param_name}", trait_use.trait_ref.spec_record);

            // add the spec params
            self.spec.late_coq_params.0.push(coq::term::Param::new(
                coq::term::Name::Named(spec_params_param_name),
                coq::term::Type::Literal(spec_params_type_name),
                true,
            ));

            // add the attr params
            let all_args: Vec<_> = trait_use
                .params_inst
                .iter()
                .map(Type::get_rfn_type)
                .chain(trait_use.get_assoc_ty_inst().into_iter().map(|x| x.get_rfn_type()))
                .collect();
            let mut attr_param = format!("{} ", trait_use.trait_ref.spec_attrs_record);
            push_str_list!(attr_param, &all_args, " ");
            self.spec.late_coq_params.0.push(coq::term::Param::new(
                coq::term::Name::Named(spec_attrs_param_name),
                coq::term::Type::Literal(attr_param),
                false,
            ));

            // add the spec itself
            self.spec.late_coq_params.0.push(coq::term::Param::new(
                coq::term::Name::Named(spec_param_name),
                coq::term::Type::Literal(spec_type),
                false,
            ));

            let spec_precond = trait_use.make_spec_param_precond();
            // this should be proved after typarams are instantiated
            spec_builder.add_late_precondition(spec_precond);
        }

        assert!(self.spec.spec.is_none(), "Overriding specification of FunctionBuilder");
        let lit_spec = spec_builder.into_function_spec();
        self.spec.spec = Some(InnerFunctionSpec::Lit(lit_spec));
    }

    pub fn into_function(
        mut self,
        arena: &'def Arena<FunctionSpec<InnerFunctionSpec<'def>>>,
    ) -> Function<'def> {
        assert!(self.spec.spec.is_some(), "No specification provided");

        // sort parameters for code
        self.other_functions.sort_by(|a, b| a.loc_name.cmp(&b.loc_name));
        self.used_statics.sort_by(|a, b| a.ident.cmp(&b.ident));

        // generate location parameters for other functions used by this one, as well as syntypes
        // These are parameters that the code gets
        let mut parameters: Vec<(coq::term::Name, coq::term::Type)> = self
            .other_functions
            .iter()
            .map(|f_inst| (coq::term::Name::Named(f_inst.loc_name.clone()), coq::term::Type::Loc))
            .collect();

        // generate location parameters for statics used by this function
        let mut statics_parameters = self
            .used_statics
            .iter()
            .map(|s| (coq::term::Name::Named(s.loc_name.clone()), coq::term::Type::Loc))
            .collect();
        parameters.append(&mut statics_parameters);

        // add generic syntype parameters for generics that this function uses.
        let mut gen_st_parameters = self
            .spec
            .generics
            .get_ty_params()
            .iter()
            .map(|names| (coq::term::Name::Named(names.syn_type.clone()), coq::term::Type::SynType))
            .collect();
        parameters.append(&mut gen_st_parameters);

        let code = FunctionCode {
            stack_layout: self.code.stack_layout,
            name: self.spec.function_name.clone(),
            basic_blocks: self.code.basic_blocks,
            required_parameters: parameters,
        };

        // assemble the spec
        let lit_spec = self.spec.spec.take().unwrap();
        let spec = self.spec.replace_spec(lit_spec);
        let spec_ref = arena.alloc(spec);

        Function {
            code,
            spec: spec_ref,
            other_functions: self.other_functions,
            layoutable_syntys: self.layoutable_syntys,
            loop_invariants: self.loop_invariants,
            manual_tactics: self.tactics,
            used_statics: self.used_statics,
            extra_link_assum: self.extra_link_assum,
        }
    }
}

impl<'def> TryFrom<FunctionBuilder<'def>> for FunctionSpec<InnerFunctionSpec<'def>> {
    type Error = String;

    fn try_from(mut builder: FunctionBuilder<'def>) -> Result<Self, String> {
        let spec = builder.spec.spec.take().ok_or_else(|| "No specification was provided".to_owned())?;
        Ok(builder.spec.replace_spec(spec))
    }
}