logicaffeine_compile/codegen_sva/z3_synth.rs
1//! Sprint 4A: Z3 Synthesis Constraint Builder
2//!
3//! Converts a kernel spec type (Term) into a synthesis constraint.
4//! When tactics can't construct a proof term directly, we ask Z3 to find one.
5//!
6//! For a spec like `Pi(a:Bit). Pi(b:Bit). Bit`, this extracts:
7//! - Inputs: [(a, Bit), (b, Bit)]
8//! - Output: Bit
9//! - Constraint: the function must map all 2^n inputs to valid outputs
10
11use logicaffeine_kernel::Term;
12
13/// Configuration for synthesis constraint building.
14#[derive(Debug, Clone)]
15pub struct SynthesisConstraintConfig {
16 /// Maximum number of Bit inputs to enumerate.
17 pub max_inputs: usize,
18 /// Timeout for Z3 in milliseconds.
19 pub timeout_ms: u64,
20}
21
22impl Default for SynthesisConstraintConfig {
23 fn default() -> Self {
24 Self {
25 max_inputs: 8,
26 timeout_ms: 10000,
27 }
28 }
29}
30
31/// Result of building a synthesis constraint.
32#[derive(Debug, Clone)]
33pub enum SynthesisConstraint {
34 /// A satisfiable constraint — the spec term can be synthesized.
35 /// Contains the normalized spec type with extracted IO information.
36 Satisfiable(Term),
37 /// The spec is unrealizable — no implementation exists.
38 Unrealizable,
39 /// Could not translate the spec type.
40 Unsupported(String),
41}
42
43/// Convert a kernel Pi-typed specification into a synthesis constraint.
44///
45/// Given a spec like `Pi(a:Bit). Pi(b:Bit). Bit`,
46/// this determines whether the spec can be synthesized by:
47/// 1. Extracting input/output types
48/// 2. Checking all types are hardware types (Bit, BVec, Unit)
49/// 3. Returning Satisfiable if the spec is well-formed
50pub fn build_synthesis_constraint(
51 spec_type: &Term,
52 config: &SynthesisConstraintConfig,
53) -> SynthesisConstraint {
54 let (inputs, output) = extract_io_from_spec(spec_type);
55
56 if inputs.is_empty() && output.is_none() {
57 return SynthesisConstraint::Unsupported(
58 "spec has no Pi binders — not a function type".to_string(),
59 );
60 }
61
62 // Check all inputs are hardware types
63 for (name, ty) in &inputs {
64 if !is_hardware_type(ty) {
65 return SynthesisConstraint::Unsupported(format!(
66 "input '{}' has non-hardware type: {:?}",
67 name, ty
68 ));
69 }
70 }
71
72 // Check input count is within bounds
73 let bit_count = inputs.iter().filter(|(_, ty)| is_bit_type(ty)).count();
74 if bit_count > config.max_inputs {
75 return SynthesisConstraint::Unsupported(format!(
76 "too many Bit inputs ({}) exceeds max_inputs ({})",
77 bit_count, config.max_inputs
78 ));
79 }
80
81 // Check output is a hardware type
82 if let Some(ref out_ty) = output {
83 if !is_hardware_type(out_ty) {
84 return SynthesisConstraint::Unsupported(format!(
85 "output has non-hardware type: {:?}",
86 out_ty
87 ));
88 }
89 }
90
91 // If we got here, the spec is synthesizable
92 SynthesisConstraint::Satisfiable(spec_type.clone())
93}
94
95/// Extract input/output signal types from a kernel spec type.
96///
97/// Walks Pi binders to find input names and types. The final non-Pi
98/// type is the output type.
99///
100/// Example: `Pi(a:Bit). Pi(b:Bit). Bit` → inputs=[(a,Bit),(b,Bit)], output=Some(Bit)
101pub fn extract_io_from_spec(spec_type: &Term) -> (Vec<(String, Term)>, Option<Term>) {
102 let mut inputs = Vec::new();
103 let mut current = spec_type;
104
105 loop {
106 match current {
107 Term::Pi {
108 param,
109 param_type,
110 body_type,
111 } => {
112 inputs.push((param.clone(), *param_type.clone()));
113 current = body_type;
114 }
115 other => {
116 // This is the output type (or the body of a dependent type)
117 return (inputs, Some(other.clone()));
118 }
119 }
120 }
121}
122
123/// Check if a Term represents a hardware type (Bit, BVec, Unit, Circuit).
124fn is_hardware_type(ty: &Term) -> bool {
125 match ty {
126 Term::Global(name) => matches!(
127 name.as_str(),
128 "Bit" | "Unit" | "BVec" | "Circuit"
129 ),
130 // BVec n — application of BVec to a Nat
131 Term::App(func, _) => {
132 if let Term::Global(name) = func.as_ref() {
133 name == "BVec" || name == "Circuit"
134 } else {
135 false
136 }
137 }
138 _ => false,
139 }
140}
141
142/// Check if a Term is specifically the Bit type.
143fn is_bit_type(ty: &Term) -> bool {
144 matches!(ty, Term::Global(name) if name == "Bit")
145}