pub(crate) mod builtin_helpers;
pub(crate) mod builtin_records;
pub(crate) mod builtins;
#[cfg(feature = "wasm-compile")]
pub mod cert;
#[cfg(feature = "runtime")]
pub(crate) mod cite_instantiate;
pub mod common;
#[cfg(feature = "runtime")]
pub mod dafny;
#[cfg(feature = "runtime")]
pub mod lean;
#[cfg(feature = "runtime")]
pub mod lemma_discovery;
pub mod program_view;
pub mod proof_lower;
#[cfg(feature = "runtime")]
pub(crate) mod proof_recognize;
#[cfg(feature = "runtime")]
pub mod recursion;
#[cfg(feature = "runtime")]
pub mod rust;
pub mod scc;
#[cfg(feature = "wasip2")]
pub mod wasip2;
#[cfg(feature = "wasm-compile")]
pub mod wasm_gc;
use std::collections::{HashMap, HashSet};
use crate::ast::{FnDef, TopLevel, TypeDef};
use crate::source::LoadedModule;
use crate::types::checker::TypeCheckResult;
pub struct ModuleInfo {
pub prefix: String,
pub depends: Vec<String>,
pub type_defs: Vec<TypeDef>,
pub fn_defs: Vec<FnDef>,
pub verify_laws: Vec<crate::ast::VerifyBlock>,
pub analysis: Option<crate::ir::AnalysisResult>,
}
impl ModuleInfo {
pub fn from_loaded(loaded: &LoadedModule) -> Self {
let depends = loaded
.items
.iter()
.find_map(|i| match i {
TopLevel::Module(m) => Some(m.depends.clone()),
_ => None,
})
.unwrap_or_default();
let type_defs = loaded
.items
.iter()
.filter_map(|i| match i {
TopLevel::TypeDef(td) => Some(td.clone()),
_ => None,
})
.collect();
let fn_defs = loaded
.items
.iter()
.filter_map(|i| match i {
TopLevel::FnDef(fd) if fd.name != "main" => Some(fd.clone()),
_ => None,
})
.collect();
Self {
prefix: loaded.dep_name.clone(),
depends,
type_defs,
fn_defs,
verify_laws: collect_verify_laws(&loaded.items),
analysis: None,
}
}
}
pub fn collect_verify_laws(items: &[TopLevel]) -> Vec<crate::ast::VerifyBlock> {
use crate::ast::VerifyKind;
let module = crate::visibility::module_decl(items);
let exposes: Option<&[String]> = module.and_then(|m| {
if m.exposes.is_empty() {
None
} else {
Some(m.exposes.as_slice())
}
});
items
.iter()
.filter_map(|i| match i {
TopLevel::Verify(vb)
if matches!(vb.kind, VerifyKind::Law(_))
&& crate::visibility::is_exposed(&vb.fn_name, exposes) =>
{
Some(vb.clone())
}
_ => None,
})
.collect()
}
pub struct CodegenContext {
pub items: Vec<TopLevel>,
pub type_defs: Vec<TypeDef>,
pub fn_defs: Vec<FnDef>,
pub project_name: String,
pub modules: Vec<ModuleInfo>,
pub module_prefixes: HashSet<String>,
#[cfg(feature = "runtime")]
pub policy: Option<crate::config::ProjectConfig>,
pub emit_replay_runtime: bool,
pub runtime_policy_from_env: bool,
pub guest_entry: Option<String>,
pub emit_self_host_support: bool,
pub extra_fn_defs: Vec<FnDef>,
pub mutual_tco_members: HashSet<crate::ir::FnId>,
pub recursive_fns: HashSet<crate::ir::FnId>,
pub buffer_build_sinks: HashMap<String, crate::ir::BufferBuildShape>,
pub buffer_fusion_sites: Vec<crate::ir::FusionSite>,
pub synthesized_buffered_fns: Vec<FnDef>,
#[cfg(feature = "runtime")]
pub proof_ir: crate::ir::ProofIR,
pub symbol_table: crate::ir::SymbolTable,
pub resolved_fn_defs: Vec<crate::ir::hir::ResolvedFnDef>,
pub current_module_scope: std::cell::RefCell<Option<String>>,
pub resolved_module_fn_defs: Vec<Vec<crate::ir::hir::ResolvedFnDef>>,
pub resolved_program: crate::codegen::program_view::ResolvedProgramView,
pub program_shape: Option<crate::analysis::shape::ProgramShape>,
pub mir_program: Option<crate::ir::mir::MirProgram>,
pub bare_i64: crate::ir::mir::BareI64Facts,
pub discovered_lemmas: Vec<crate::codegen::lemma_discovery::CommittedLemma>,
pub sample_expected: std::collections::HashMap<(String, usize), String>,
pub allow_mathlib: bool,
pub hand_proofs: std::collections::HashMap<(String, String), String>,
}
pub struct ProjectOutput {
pub files: Vec<(String, String)>,
}
#[allow(clippy::too_many_arguments)]
pub fn build_context(
items: Vec<TopLevel>,
_tc_result: &TypeCheckResult,
entry_analysis: Option<&crate::ir::AnalysisResult>,
project_name: String,
modules: Vec<ModuleInfo>,
symbol_table: crate::ir::SymbolTable,
resolved_items: Vec<crate::ir::hir::ResolvedTopLevel>,
) -> CodegenContext {
let type_defs: Vec<TypeDef> = items
.iter()
.filter_map(|item| {
if let TopLevel::TypeDef(td) = item {
Some(td.clone())
} else {
None
}
})
.collect();
let fn_defs: Vec<FnDef> = items
.iter()
.filter_map(|item| {
if let TopLevel::FnDef(fd) = item {
Some(fd.clone())
} else {
None
}
})
.collect();
let module_prefixes: HashSet<String> = modules.iter().map(|m| m.prefix.clone()).collect();
let mut mutual_tco_members: HashSet<crate::ir::FnId> = HashSet::new();
match entry_analysis {
Some(a) => mutual_tco_members.extend(scc::analysis_set_to_fn_ids(
&a.mutual_tco_members,
&symbol_table,
None,
)),
None => {
let entry_fns: Vec<&FnDef> = fn_defs.iter().filter(|fd| fd.name != "main").collect();
for group in crate::call_graph::tailcall_scc_components(&entry_fns) {
if group.len() < 2 {
continue;
}
for fd in group {
if let Some(id) = symbol_table.fn_id_of(&crate::ir::FnKey::entry(&fd.name)) {
mutual_tco_members.insert(id);
}
}
}
}
}
for module in &modules {
match module.analysis.as_ref() {
Some(a) => mutual_tco_members.extend(scc::analysis_set_to_fn_ids(
&a.mutual_tco_members,
&symbol_table,
Some(&module.prefix),
)),
None => {
let mod_fns: Vec<&FnDef> = module.fn_defs.iter().collect();
for group in crate::call_graph::tailcall_scc_components(&mod_fns) {
if group.len() < 2 {
continue;
}
for fd in group {
if let Some(id) = symbol_table.fn_id_of(&crate::ir::FnKey::in_module(
module.prefix.clone(),
&fd.name,
)) {
mutual_tco_members.insert(id);
}
}
}
}
}
}
let mut recursive_fns: HashSet<crate::ir::FnId> = HashSet::new();
match entry_analysis {
Some(a) => recursive_fns.extend(scc::analysis_set_to_fn_ids(
&a.recursive_fns,
&symbol_table,
None,
)),
None => recursive_fns.extend(scc::bare_names_to_fn_ids(
crate::call_graph::find_recursive_fns(&items)
.iter()
.map(String::as_str),
&symbol_table,
None,
)),
}
for module in &modules {
match module.analysis.as_ref() {
Some(a) => recursive_fns.extend(scc::analysis_set_to_fn_ids(
&a.recursive_fns,
&symbol_table,
Some(&module.prefix),
)),
None => {
let mod_items: Vec<TopLevel> = module
.fn_defs
.iter()
.map(|fd| TopLevel::FnDef(fd.clone()))
.collect();
recursive_fns.extend(scc::bare_names_to_fn_ids(
crate::call_graph::find_recursive_fns(&mod_items)
.iter()
.map(String::as_str),
&symbol_table,
Some(&module.prefix),
));
}
}
}
let detect_fns: Vec<&FnDef> = fn_defs
.iter()
.chain(modules.iter().flat_map(|m| m.fn_defs.iter()))
.collect();
let buffer_build_sinks = crate::ir::compute_buffer_build_sinks(&detect_fns);
let buffer_fusion_sites = crate::ir::find_fusion_sites(&detect_fns, &buffer_build_sinks);
let synthesized_buffered_fns: Vec<FnDef> = fn_defs
.iter()
.chain(modules.iter().flat_map(|m| m.fn_defs.iter()))
.filter(|fd| fd.name.ends_with("__buffered"))
.cloned()
.collect();
let resolved_program = crate::codegen::program_view::ResolvedProgramView::build(
resolved_items,
&modules,
&symbol_table,
);
let resolved_fn_defs: Vec<crate::ir::hir::ResolvedFnDef> =
resolved_program.entry_fns().cloned().collect();
let resolved_module_fn_defs: Vec<Vec<crate::ir::hir::ResolvedFnDef>> = resolved_program
.modules
.iter()
.map(|m| m.fn_defs.clone())
.collect();
let program_shape = {
let mut all_fns: Vec<&crate::ir::hir::ResolvedFnDef> =
resolved_program.entry_fns().collect();
for m in &resolved_program.modules {
for fd in &m.fn_defs {
all_fns.push(fd);
}
}
Some(crate::analysis::shape::analyze_program_with_modules(
&all_fns, &items, &modules,
))
};
let mir_program = {
let mut mir_items: Vec<crate::ir::hir::ResolvedTopLevel> = resolved_program
.entry_fns()
.cloned()
.map(crate::ir::hir::ResolvedTopLevel::FnDef)
.collect();
for m in &resolved_program.modules {
for fd in &m.fn_defs {
mir_items.push(crate::ir::hir::ResolvedTopLevel::FnDef(fd.clone()));
}
}
Some(crate::ir::mir::optimize(crate::ir::mir::lower_program(
&mir_items,
)))
};
let empty_carrier = crate::ir::mir::bare_i64::CarrierIntervals::new();
let bare_i64 = mir_program
.as_ref()
.map(|p| crate::ir::mir::bare_i64::analyze(p, &empty_carrier))
.unwrap_or_default();
let ctx = CodegenContext {
items,
type_defs,
fn_defs,
project_name,
modules,
module_prefixes,
#[cfg(feature = "runtime")]
policy: None,
emit_replay_runtime: false,
runtime_policy_from_env: false,
guest_entry: None,
emit_self_host_support: false,
extra_fn_defs: Vec::new(),
mutual_tco_members,
recursive_fns,
buffer_build_sinks,
buffer_fusion_sites,
synthesized_buffered_fns,
bare_i64,
#[cfg(feature = "runtime")]
proof_ir: crate::ir::ProofIR::default(),
symbol_table,
resolved_fn_defs,
resolved_module_fn_defs,
current_module_scope: std::cell::RefCell::new(None),
program_shape,
resolved_program,
mir_program,
discovered_lemmas: Vec::new(),
sample_expected: std::collections::HashMap::new(),
allow_mathlib: false,
hand_proofs: std::collections::HashMap::new(),
};
ctx
}
impl CodegenContext {
pub fn with_module_scope<R>(&self, scope: Option<&str>, f: impl FnOnce() -> R) -> R {
let prev = self
.current_module_scope
.replace(scope.map(|s| s.to_string()));
let out = f();
*self.current_module_scope.borrow_mut() = prev;
out
}
pub fn active_module_scope(&self) -> Option<String> {
self.current_module_scope.borrow().clone()
}
pub fn law_target_fn_id(&self, fn_name: &str) -> Option<crate::ir::FnId> {
if let Some(scope) = self.active_module_scope()
&& let Some(id) = self
.symbol_table
.fn_id_of(&crate::ir::FnKey::in_module(scope, fn_name))
{
return Some(id);
}
self.symbol_table
.fn_id_of(&crate::ir::FnKey::entry(fn_name))
}
pub fn fn_def_by_name(&self, name: &str, scope: Option<&str>) -> Option<&FnDef> {
use crate::ir::FnKey;
let key = match scope {
Some(prefix) => FnKey::in_module(prefix.to_string(), name),
None => FnKey::entry(name),
};
let fn_id = self.symbol_table.fn_id_of(&key)?;
let matches = |fd: &&FnDef| crate::codegen::common::fn_id_for_decl(self, fd) == Some(fn_id);
match scope {
None => self.fn_defs.iter().find(matches),
Some(prefix) => self
.modules
.iter()
.find(|m| m.prefix == prefix)?
.fn_defs
.iter()
.find(matches),
}
}
}
impl CodegenContext {
pub fn refresh_facts(&mut self) {
let symbol_table = crate::ir::SymbolTable::build(&self.items, &self.modules);
let entry_fn_id = |name: &str| -> Option<crate::ir::FnId> {
symbol_table.fn_id_of(&crate::ir::FnKey::entry(name))
};
let module_fn_id = |prefix: &str, name: &str| -> Option<crate::ir::FnId> {
symbol_table.fn_id_of(&crate::ir::FnKey::in_module(prefix.to_string(), name))
};
let entry_fn_refs: Vec<&FnDef> =
self.fn_defs.iter().filter(|fd| fd.name != "main").collect();
let mut mutual_tco_members: HashSet<crate::ir::FnId> = HashSet::new();
for group in crate::call_graph::tailcall_scc_components(&entry_fn_refs) {
if group.len() < 2 {
continue;
}
for fd in group {
if let Some(id) = entry_fn_id(&fd.name) {
mutual_tco_members.insert(id);
}
}
}
for module in &self.modules {
let mod_fns: Vec<&FnDef> = module.fn_defs.iter().collect();
for group in crate::call_graph::tailcall_scc_components(&mod_fns) {
if group.len() < 2 {
continue;
}
for fd in group {
if let Some(id) = module_fn_id(&module.prefix, &fd.name) {
mutual_tco_members.insert(id);
}
}
}
}
self.mutual_tco_members = mutual_tco_members;
let mut recursive_fns: HashSet<crate::ir::FnId> = scc::bare_names_to_fn_ids(
crate::call_graph::find_recursive_fns(&self.items)
.iter()
.map(String::as_str),
&symbol_table,
None,
);
for module in &self.modules {
let mod_items: Vec<TopLevel> = module
.fn_defs
.iter()
.map(|fd| TopLevel::FnDef(fd.clone()))
.collect();
recursive_fns.extend(scc::bare_names_to_fn_ids(
crate::call_graph::find_recursive_fns(&mod_items)
.iter()
.map(String::as_str),
&symbol_table,
Some(&module.prefix),
));
}
self.recursive_fns = recursive_fns;
self.symbol_table = symbol_table;
let entry_resolved_items = crate::ir::hir::resolve_program(&self.symbol_table, &self.items);
self.resolved_program = crate::codegen::program_view::ResolvedProgramView::build(
entry_resolved_items,
&self.modules,
&self.symbol_table,
);
self.resolved_fn_defs = self.resolved_program.entry_fns().cloned().collect();
self.resolved_module_fn_defs = self
.resolved_program
.modules
.iter()
.map(|m| m.fn_defs.clone())
.collect();
let inputs = crate::codegen::proof_lower::ProofLowerInputs::from_ctx(self);
self.proof_ir = crate::codegen::proof_lower::lower(&inputs);
}
pub fn resolve_fn_def<'a>(
&'a self,
fd: &'a FnDef,
scope: Option<&str>,
) -> std::borrow::Cow<'a, crate::ir::hir::ResolvedFnDef> {
use crate::ir::FnKey;
use crate::ir::hir::{
ResolveCtx, ResolvedFnBody, ResolvedFnDef, ResolvedStmt, resolve_fn_def_external,
};
use std::borrow::Cow;
let key = match scope {
Some(prefix) => FnKey::in_module(prefix.to_string(), fd.name.clone()),
None => FnKey::entry(fd.name.clone()),
};
if let Some(fn_id) = self.symbol_table.fn_id_of(&key) {
if let Some(rfd) = self.resolved_program.fn_by_id(fn_id) {
return Cow::Borrowed(rfd);
}
}
let module_name = self.items.iter().find_map(|i| match i {
TopLevel::Module(m) => Some(m.name.clone()),
_ => None,
});
let mut rctx = ResolveCtx::new(&self.symbol_table);
rctx.current_module = scope.map(String::from).or(module_name);
let lifted = resolve_fn_def_external(&rctx, fd).unwrap_or_else(|| {
let stmts: Vec<ResolvedStmt> = match fd.body.as_ref() {
crate::ast::FnBody::Block(stmts) => {
stmts.iter().map(|s| self.resolve_stmt(s, scope)).collect()
}
};
ResolvedFnDef {
fn_id: crate::ir::FnId(u32::MAX),
name: fd.name.clone(),
line: fd.line,
params: fd
.params
.iter()
.map(|(n, ann)| (n.clone(), crate::types::parse_type_str(ann)))
.collect(),
return_type: crate::types::parse_type_str(&fd.return_type),
effects: fd.effects.clone(),
desc: fd.desc.clone(),
body: std::sync::Arc::new(ResolvedFnBody::Block(stmts)),
resolution: fd.resolution.clone(),
}
});
Cow::Owned(lifted)
}
fn entry_module_name(&self) -> Option<String> {
self.items.iter().find_map(|i| match i {
TopLevel::Module(m) => Some(m.name.clone()),
_ => None,
})
}
pub fn resolve_expr(
&self,
expr: &crate::ast::Spanned<crate::ast::Expr>,
scope: Option<&str>,
) -> crate::ast::Spanned<crate::ir::hir::ResolvedExpr> {
use crate::ir::hir::{ResolveCtx, ResolvedStmt};
let mut rctx = ResolveCtx::new(&self.symbol_table);
rctx.current_module = scope.map(String::from).or_else(|| self.entry_module_name());
let stmt = crate::ast::Stmt::Expr(expr.clone());
match crate::ir::hir::resolve::resolve_stmt_external(&rctx, &stmt) {
ResolvedStmt::Expr(s) => s,
ResolvedStmt::Binding { value, .. } => value,
}
}
pub fn resolve_stmt(
&self,
stmt: &crate::ast::Stmt,
scope: Option<&str>,
) -> crate::ir::hir::ResolvedStmt {
use crate::ir::hir::ResolveCtx;
let mut rctx = ResolveCtx::new(&self.symbol_table);
rctx.current_module = scope.map(String::from).or_else(|| self.entry_module_name());
crate::ir::hir::resolve::resolve_stmt_external(&rctx, stmt)
}
pub fn resolve_pattern(
&self,
pat: &crate::ast::Pattern,
scope: Option<&str>,
) -> crate::ir::hir::ResolvedPattern {
use crate::ast::{Expr, Literal, MatchArm, Spanned, Stmt};
use crate::ir::hir::{ResolveCtx, ResolvedExpr, ResolvedStmt};
let mut rctx = ResolveCtx::new(&self.symbol_table);
rctx.current_module = scope.map(String::from).or_else(|| self.entry_module_name());
let synthetic_arm = MatchArm {
pattern: pat.clone(),
body: Box::new(Spanned::bare(Expr::Literal(Literal::Unit))),
binding_slots: std::sync::OnceLock::new(),
};
let stmt = Stmt::Expr(Spanned::bare(Expr::Match {
subject: Box::new(Spanned::bare(Expr::Literal(Literal::Unit))),
arms: vec![synthetic_arm],
}));
let resolved_stmt = crate::ir::hir::resolve::resolve_stmt_external(&rctx, &stmt);
let ResolvedStmt::Expr(spanned) = resolved_stmt else {
unreachable!()
};
let ResolvedExpr::Match { arms, .. } = spanned.node else {
unreachable!()
};
arms.into_iter().next().unwrap().pattern
}
}
fn codegen_ctx_fn_sig(ctx: &CodegenContext, name: &str) -> Option<crate::verify_law::FnSigInfo> {
use crate::verify_law::FnSigInfo;
if let Some(fn_id) = crate::codegen::common::fn_id_for_dotted_name(ctx, name)
&& let Some(rfd) = ctx.resolved_program.fn_by_id(fn_id)
{
return Some(FnSigInfo {
return_type: rfd.return_type.clone(),
is_pure: rfd.effects.is_empty(),
});
}
let walk = |td: &crate::ast::TypeDef, scope: Option<&str>| -> Option<FnSigInfo> {
match td {
crate::ast::TypeDef::Sum {
name: parent,
variants,
..
} => {
let parent_full = match scope {
Some(prefix) => format!("{prefix}.{parent}"),
None => parent.clone(),
};
for v in variants {
let bare = format!("{parent}.{}", v.name);
let full = format!("{parent_full}.{}", v.name);
if name == bare || name == full {
return Some(FnSigInfo {
return_type: crate::types::Type::named(parent_full.clone()),
is_pure: true,
});
}
}
None
}
crate::ast::TypeDef::Product { name: parent, .. } => {
let parent_full = match scope {
Some(prefix) => format!("{prefix}.{parent}"),
None => parent.clone(),
};
if name == parent || name == parent_full {
return Some(FnSigInfo {
return_type: crate::types::Type::named(parent_full),
is_pure: true,
});
}
None
}
}
};
for item in &ctx.items {
if let TopLevel::TypeDef(td) = item
&& let Some(info) = walk(td, None)
{
return Some(info);
}
}
for m in &ctx.modules {
for td in &m.type_defs {
if let Some(info) = walk(td, Some(&m.prefix)) {
return Some(info);
}
}
}
match name {
"__buf_new" => Some(FnSigInfo {
return_type: crate::types::Type::named("Buffer"),
is_pure: true,
}),
"__buf_append" | "__buf_append_sep_unless_first" => Some(FnSigInfo {
return_type: crate::types::Type::named("Buffer"),
is_pure: true,
}),
"__buf_finalize" => Some(FnSigInfo {
return_type: crate::types::Type::Str,
is_pure: true,
}),
_ => None,
}
}
impl crate::verify_law::FnSigOracle for CodegenContext {
fn fn_sig(&self, name: &str) -> Option<crate::verify_law::FnSigInfo> {
codegen_ctx_fn_sig(self, name)
}
}