-
Vincent Lafeychine authoredVincent Lafeychine authored
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 ¶ms.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 ¶ms.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 ¶ms.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))
}
}