use crate::{
error::{CompileError, Error, ErrorEmitted, Handler},
expr::{Expr, ExternalIntrinsic, Immediate, IntrinsicKind},
predicate::{ConstraintDecl, Contract, ExprKey, Predicate, Variable},
span::{empty_span, Span, Spanned},
};
use asm_builder::AsmBuilder;
use essential_types::{
predicate::{Edge, Node, Predicate as CompiledPredicate, Program},
ContentAddress,
};
use fxhash::{FxHashMap, FxHashSet};
use petgraph::{graph::NodeIndex, visit::EdgeRef, Graph};
use std::collections::BTreeSet;
mod asm_builder;
mod display;
#[cfg(test)]
mod tests;
#[derive(Debug, Default, Clone)]
pub struct CompiledContract {
pub names: Vec<String>,
pub contract: essential_types::contract::Contract,
pub programs: BTreeSet<Program>,
}
type CompiledPredicates =
FxHashMap<String, (CompiledPredicate, ContentAddress, BTreeSet<Program>, Span)>;
pub fn compile_contract(
handler: &Handler,
salt: [u8; 32],
contract: &Contract,
) -> Result<CompiledContract, ErrorEmitted> {
let mut dep_graph = Graph::<String, ()>::new();
let mut names_to_indices = FxHashMap::<String, NodeIndex>::default();
let mut indices_to_predicates = FxHashMap::<NodeIndex, &Predicate>::default();
for (_, pred) in contract.preds.iter() {
let new_node = dep_graph.add_node(pred.name.name.clone());
names_to_indices.insert(pred.name.name.clone(), new_node);
indices_to_predicates.insert(new_node, pred);
}
for (pred_key, pred) in contract.preds.iter() {
for expr in contract.exprs(pred_key) {
if let Some(Expr::LocalPredicateCall { predicate, .. }) = expr.try_get(contract) {
let from = names_to_indices[predicate];
let to = names_to_indices[&pred.name.name];
dep_graph.add_edge(from, to, ());
} else if let Some(Expr::IntrinsicCall {
kind: (IntrinsicKind::External(ExternalIntrinsic::AddressOf), _),
args,
..
}) = expr.try_get(contract)
{
if let Some(Expr::Immediate {
value: Immediate::String(name),
..
}) = args.first().and_then(|name| name.try_get(contract))
{
let from = names_to_indices[name];
let to = names_to_indices[&pred.name.name];
dep_graph.add_edge(from, to, ());
}
}
}
}
let Ok(sorted_nodes) = petgraph::algo::toposort(&dep_graph, None) else {
return Err(handler.emit_internal_err(
"dependency cycles between predicates should have been caught before",
empty_span(),
));
};
let mut compiled_predicates: CompiledPredicates = FxHashMap::default();
for idx in &sorted_nodes {
let predicate = indices_to_predicates[idx];
if let Ok((compiled_predicate, programs)) = handler
.scope(|handler| compile_predicate(handler, contract, &compiled_predicates, predicate))
{
let compiled_predicate_address = essential_hash::content_addr(&compiled_predicate);
compiled_predicates.insert(
predicate.name.name.clone(),
(
compiled_predicate,
compiled_predicate_address,
programs,
predicate.span.clone(),
),
);
} else {
return Err(handler.cancel());
}
}
let mut unique_addresses: FxHashSet<&ContentAddress> = FxHashSet::default();
let mut original_predicates: FxHashMap<&ContentAddress, (&String, &Span)> =
FxHashMap::default();
let mut compiled_predicates_vec: Vec<_> = compiled_predicates.iter().collect();
compiled_predicates_vec.reverse();
for (string, (_, content_address, _, span)) in &compiled_predicates_vec {
if !unique_addresses.insert(content_address) {
let original_predicate = original_predicates
.get(content_address)
.expect("predicate name guaranteed to exist");
let mut original_span = original_predicate.1;
let mut span = span;
if span.start() < original_span.start() {
(original_span, span) = (span, original_span);
}
handler.emit_err(Error::Compile {
error: CompileError::IdenticalPredicates {
original_name: original_predicate.0.to_string(),
duplicate_name: string.to_string(),
original_span: original_span.clone(),
span: span.clone(),
},
});
} else {
original_predicates.insert(content_address, (string, span));
}
}
let mut combined_programs = BTreeSet::new();
let (names, predicates) = contract
.preds
.iter()
.map(|(_, pred)| {
compiled_predicates
.remove(&pred.name.name)
.map(|(compiled_predicate, _, programs, _)| {
combined_programs.extend(programs);
(pred.name.name.clone(), compiled_predicate)
})
.ok_or_else(|| {
handler.emit_internal_err(
"predicate must exist in the compiled_predicates map",
empty_span(),
)
})
})
.collect::<Result<_, _>>()?;
if handler.has_errors() {
Err(handler.cancel())
} else {
Ok(CompiledContract {
names,
contract: essential_types::contract::Contract { predicates, salt },
programs: combined_programs,
})
}
}
#[derive(Clone, Debug)]
enum ComputeNode {
Var { var: Variable },
Constraint { expr: ExprKey },
Expr { expr: ExprKey },
}
impl ComputeNode {
fn is_leaf(&self) -> bool {
matches!(self, ComputeNode::Constraint { .. })
}
fn expr(&self) -> ExprKey {
match self {
Self::Var { var, .. } => var.expr,
Self::Constraint { expr, .. } | Self::Expr { expr, .. } => *expr,
}
}
fn span(&self, contract: &Contract) -> Span {
match self {
Self::Var { var, .. } => var.span.clone(),
Self::Constraint { expr, .. } | Self::Expr { expr, .. } => {
expr.get(contract).span().clone()
}
}
}
}
pub fn compile_predicate(
handler: &Handler,
contract: &Contract,
compiled_predicates: &CompiledPredicates,
pred: &Predicate,
) -> Result<(CompiledPredicate, BTreeSet<Program>), ErrorEmitted> {
let mut data_flow_graph = Graph::<ComputeNode, ()>::new();
let mut vars_to_nodes = FxHashMap::<String, NodeIndex>::default();
let mut asm_args_to_nodes = FxHashMap::<ExprKey, NodeIndex>::default();
for (_, variable) in pred.variables() {
let var_node_pre = data_flow_graph.add_node(ComputeNode::Var {
var: variable.clone(),
});
vars_to_nodes.insert(variable.name.clone(), var_node_pre);
if let Expr::AsmBlock { args, .. } = variable.expr.get(contract) {
let arg_nodes: Vec<_> = args
.iter()
.map(|arg| {
let arg_node = data_flow_graph.add_node(ComputeNode::Expr { expr: *arg });
asm_args_to_nodes.insert(*arg, arg_node);
data_flow_graph.add_edge(arg_node, var_node_pre, ());
arg_node
})
.collect();
arg_nodes.windows(2).for_each(|window| {
data_flow_graph.add_edge(window[0], window[1], ());
});
}
}
for (_, variable) in pred.variables() {
if let Expr::AsmBlock { args, .. } = variable.expr.get(contract) {
for arg in args {
for var_name in arg.collect_path_to_var_exprs(contract, pred) {
data_flow_graph.add_edge(vars_to_nodes[&var_name], asm_args_to_nodes[arg], ());
}
}
} else {
for var_name in variable.expr.collect_path_to_var_exprs(contract, pred) {
data_flow_graph.add_edge(
vars_to_nodes[&var_name.clone()],
vars_to_nodes[&variable.name.clone()],
(),
);
}
}
}
for ConstraintDecl { expr, .. } in pred.constraints.iter() {
let constraint_node = data_flow_graph.add_node(ComputeNode::Constraint { expr: *expr });
for var_name in expr.collect_path_to_var_exprs(contract, pred) {
data_flow_graph.add_edge(vars_to_nodes[&var_name], constraint_node, ());
}
}
let sccs = petgraph::algo::kosaraju_scc(&data_flow_graph);
for scc in &sccs {
if scc.len() > 1 {
handler.emit_err(Error::Compile {
error: CompileError::VarsDependencyCycle {
spans: scc
.iter()
.map(|idx| data_flow_graph[*idx].span(contract))
.collect(),
},
});
}
}
let Ok(mut sorted_nodes) = petgraph::algo::toposort(&data_flow_graph, None) else {
return Err(handler.emit_internal_err(
"dependency cycle detected between program nodes. should have been caught above",
empty_span(),
));
};
sorted_nodes = {
let (mut non_sinks, sinks): (Vec<_>, Vec<_>) =
sorted_nodes.into_iter().partition(|&node| {
data_flow_graph
.neighbors_directed(node, petgraph::Outgoing)
.next()
.is_some()
});
non_sinks.extend(sinks);
non_sinks
};
let no_span_predicates: FxHashMap<String, (CompiledPredicate, ContentAddress)> =
compiled_predicates
.iter()
.map(|(k, (compiled_predicate, content_address, _, _span))| {
(
k.clone(),
(compiled_predicate.clone(), content_address.clone()),
)
})
.collect();
let mut compiled_predicate = CompiledPredicate {
nodes: vec![],
edges: vec![],
};
let mut edge_start = 0;
let mut programs = BTreeSet::new();
for node in &sorted_nodes {
let mut builder = AsmBuilder::new(&no_span_predicates);
let mut parents = data_flow_graph
.neighbors_directed(*node, petgraph::Direction::Incoming)
.collect::<Vec<_>>();
parents.sort_by_key(|node| sorted_nodes.iter().position(|&n| n == *node));
let parents = parents
.iter()
.map(|node| data_flow_graph[*node].clone())
.collect::<Vec<_>>();
let asm = builder.compile_compute_node(
handler,
&data_flow_graph[*node],
&parents,
contract,
pred,
)?;
let address = essential_hash::content_addr(&Program(
essential_asm::to_bytes(asm.iter().copied()).collect(),
));
programs.insert(Program(
essential_asm::to_bytes(asm.iter().copied()).collect(),
));
match &data_flow_graph[*node] {
ComputeNode::Constraint { .. } => {
compiled_predicate.nodes.push(Node {
program_address: address,
edge_start: Edge::MAX,
});
}
ComputeNode::Var { .. } | ComputeNode::Expr { .. } => {
compiled_predicate.nodes.push(Node {
program_address: address,
edge_start,
});
for edge in data_flow_graph.edges(*node) {
let to = edge.target();
compiled_predicate
.edges
.push(sorted_nodes.iter().position(|n| *n == to).unwrap() as u16);
}
edge_start += data_flow_graph.edges(*node).count() as u16;
}
}
}
if handler.has_errors() {
return Err(handler.cancel());
}
Ok((compiled_predicate, programs))
}