circomspect_program_structure/intermediate_representation/
ir.rs

1use num_bigint::BigInt;
2use std::fmt;
3
4use crate::file_definition::{FileID, FileLocation};
5use crate::nonempty_vec::NonEmptyVec;
6
7use super::degree_meta::DegreeKnowledge;
8use super::type_meta::TypeKnowledge;
9use super::value_meta::ValueKnowledge;
10use super::variable_meta::VariableKnowledge;
11
12type Index = usize;
13type Version = usize;
14
15#[derive(Clone, Default)]
16pub struct Meta {
17    pub location: FileLocation,
18    pub file_id: Option<FileID>,
19    degree_knowledge: DegreeKnowledge,
20    type_knowledge: TypeKnowledge,
21    value_knowledge: ValueKnowledge,
22    variable_knowledge: VariableKnowledge,
23}
24
25impl Meta {
26    #[must_use]
27    pub fn new(location: &FileLocation, file_id: &Option<FileID>) -> Meta {
28        Meta {
29            location: location.clone(),
30            file_id: *file_id,
31            degree_knowledge: DegreeKnowledge::default(),
32            type_knowledge: TypeKnowledge::default(),
33            value_knowledge: ValueKnowledge::default(),
34            variable_knowledge: VariableKnowledge::default(),
35        }
36    }
37
38    #[must_use]
39    pub fn start(&self) -> usize {
40        self.location.start
41    }
42
43    #[must_use]
44    pub fn end(&self) -> usize {
45        self.location.end
46    }
47
48    #[must_use]
49    pub fn file_id(&self) -> Option<FileID> {
50        self.file_id
51    }
52
53    #[must_use]
54    pub fn file_location(&self) -> FileLocation {
55        self.location.clone()
56    }
57
58    #[must_use]
59    pub fn degree_knowledge(&self) -> &DegreeKnowledge {
60        &self.degree_knowledge
61    }
62
63    #[must_use]
64    pub fn type_knowledge(&self) -> &TypeKnowledge {
65        &self.type_knowledge
66    }
67
68    #[must_use]
69    pub fn value_knowledge(&self) -> &ValueKnowledge {
70        &self.value_knowledge
71    }
72
73    #[must_use]
74    pub fn variable_knowledge(&self) -> &VariableKnowledge {
75        &self.variable_knowledge
76    }
77
78    #[must_use]
79    pub fn degree_knowledge_mut(&mut self) -> &mut DegreeKnowledge {
80        &mut self.degree_knowledge
81    }
82
83    #[must_use]
84    pub fn type_knowledge_mut(&mut self) -> &mut TypeKnowledge {
85        &mut self.type_knowledge
86    }
87
88    #[must_use]
89    pub fn value_knowledge_mut(&mut self) -> &mut ValueKnowledge {
90        &mut self.value_knowledge
91    }
92
93    #[must_use]
94    pub fn variable_knowledge_mut(&mut self) -> &mut VariableKnowledge {
95        &mut self.variable_knowledge
96    }
97}
98
99impl std::hash::Hash for Meta {
100    fn hash<H>(&self, state: &mut H)
101    where
102        H: std::hash::Hasher,
103    {
104        self.location.hash(state);
105        self.file_id.hash(state);
106        state.finish();
107    }
108}
109
110impl PartialEq for Meta {
111    fn eq(&self, other: &Meta) -> bool {
112        self.location == other.location && self.file_id == other.file_id
113    }
114}
115
116impl Eq for Meta {}
117
118// TODO: Implement a custom `PartialEq` for `Statement`.
119#[derive(Clone)]
120#[allow(clippy::large_enum_variant)]
121pub enum Statement {
122    // We allow for declarations of multiple variables of the same type to avoid
123    // having to insert new declarations when converting the CFG to SSA.
124    Declaration {
125        meta: Meta,
126        names: NonEmptyVec<VariableName>,
127        var_type: VariableType,
128        dimensions: Vec<Expression>,
129    },
130    IfThenElse {
131        meta: Meta,
132        cond: Expression,
133        true_index: Index,
134        false_index: Option<Index>,
135    },
136    Return {
137        meta: Meta,
138        value: Expression,
139    },
140    // Array and component signal assignments (where `access` is non-empty) are
141    // rewritten using `Update` expressions. This allows us to track version
142    // information when transforming the CFG to SSA form.
143    //
144    // Note: The type metadata in `meta` tracks the type of the variable `var`.
145    Substitution {
146        meta: Meta,
147        var: VariableName,
148        op: AssignOp,
149        rhe: Expression,
150    },
151    ConstraintEquality {
152        meta: Meta,
153        lhe: Expression,
154        rhe: Expression,
155    },
156    LogCall {
157        meta: Meta,
158        args: Vec<LogArgument>,
159    },
160    Assert {
161        meta: Meta,
162        arg: Expression,
163    },
164}
165
166#[derive(Clone)]
167pub enum Expression {
168    /// An infix operation of the form `lhe * rhe`.
169    InfixOp {
170        meta: Meta,
171        lhe: Box<Expression>,
172        infix_op: ExpressionInfixOpcode,
173        rhe: Box<Expression>,
174    },
175    /// A prefix operation of the form `* rhe`.
176    PrefixOp { meta: Meta, prefix_op: ExpressionPrefixOpcode, rhe: Box<Expression> },
177    /// An inline switch operation (or inline if-then-else) of the form `cond?
178    /// if_true: if_false`.
179    SwitchOp {
180        meta: Meta,
181        cond: Box<Expression>,
182        if_true: Box<Expression>,
183        if_false: Box<Expression>,
184    },
185    /// A local variable, signal, or component.
186    Variable { meta: Meta, name: VariableName },
187    /// A constant field element.
188    Number(Meta, BigInt),
189    /// A function call node.
190    Call { meta: Meta, name: String, args: Vec<Expression> },
191    /// An inline array on the form `[value, ...]`.
192    InlineArray { meta: Meta, values: Vec<Expression> },
193    /// An `Access` node represents an array access of the form `a[i]...[k]`.
194    Access { meta: Meta, var: VariableName, access: Vec<AccessType> },
195    /// Updates of the form `var[i]...[k] = rhe` lift to IR statements of the
196    /// form `var = update(var, (i, ..., k), rhe)`. This is needed when we
197    /// convert the CFG to SSA. Since arrays are versioned atomically, we need
198    /// to track which version of the array that is updated to obtain the new
199    /// version. (This is needed to track variable use, dead assignments, and
200    /// data flow.)
201    ///
202    /// Note: The type metadata in `meta` tracks the type of the variable `var`.
203    Update { meta: Meta, var: VariableName, access: Vec<AccessType>, rhe: Box<Expression> },
204    /// An SSA phi-expression.
205    Phi { meta: Meta, args: Vec<VariableName> },
206}
207
208pub type TagList = Vec<String>;
209
210#[derive(Clone, PartialEq, Eq, Hash)]
211pub enum VariableType {
212    Local,
213    Component,
214    AnonymousComponent,
215    Signal(SignalType, TagList),
216}
217
218impl fmt::Display for VariableType {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
220        use SignalType::*;
221        use VariableType::*;
222        match self {
223            Local => write!(f, "var"),
224            AnonymousComponent | Component => write!(f, "component"),
225            Signal(signal_type, tag_list) => {
226                if matches!(signal_type, Intermediate) {
227                    write!(f, "signal")?;
228                } else {
229                    write!(f, "signal {signal_type}")?;
230                }
231                if !tag_list.is_empty() {
232                    write!(f, " {{{}}}", tag_list.join(", "))
233                } else {
234                    Ok(())
235                }
236            }
237        }
238    }
239}
240
241#[derive(Copy, Clone, PartialEq, Eq, Hash)]
242pub enum SignalType {
243    Input,
244    Output,
245    Intermediate,
246}
247
248impl fmt::Display for SignalType {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
250        use SignalType::*;
251        match self {
252            Input => write!(f, "input"),
253            Output => write!(f, "output"),
254            Intermediate => Ok(()), // Intermediate signals have no explicit signal type.
255        }
256    }
257}
258
259/// A IR variable name consists of three components.
260///
261///   1. The original name (obtained from the source code).
262///   2. An optional suffix (used to ensure uniqueness when lifting to IR).
263///   3. An optional version (applied when the CFG is converted to SSA form).
264#[derive(Clone, Hash, PartialEq, Eq)]
265pub struct VariableName {
266    /// This is the original name of the variable from the function or template
267    /// AST.
268    name: String,
269    /// For shadowing declarations we need to rename the shadowing variable
270    /// since construction of the CFG requires all variable names to be unique.
271    /// This is done by adding a suffix (on the form `_n`) to the variable name.
272    suffix: Option<String>,
273    /// The version is used to track variable versions when we convert the CFG
274    /// to SSA.
275    version: Option<Version>,
276}
277
278impl VariableName {
279    /// Returns a new variable name with the given name (without suffix or version).
280    #[must_use]
281    pub fn from_string<N: ToString>(name: N) -> VariableName {
282        VariableName { name: name.to_string(), suffix: None, version: None }
283    }
284
285    #[must_use]
286    pub fn name(&self) -> &String {
287        &self.name
288    }
289
290    #[must_use]
291    pub fn suffix(&self) -> &Option<String> {
292        &self.suffix
293    }
294
295    #[must_use]
296    pub fn version(&self) -> &Option<Version> {
297        &self.version
298    }
299
300    /// Returns a new copy of the variable name, adding the given suffix.
301    #[must_use]
302    pub fn with_suffix<S: ToString>(&self, suffix: S) -> VariableName {
303        let mut result = self.clone();
304        result.suffix = Some(suffix.to_string());
305        result
306    }
307
308    /// Returns a new copy of the variable name, adding the given version.
309    #[must_use]
310    pub fn with_version(&self, version: Version) -> VariableName {
311        let mut result = self.clone();
312        result.version = Some(version);
313        result
314    }
315
316    /// Returns a new copy of the variable name with the suffix dropped.
317    #[must_use]
318    pub fn without_suffix(&self) -> VariableName {
319        let mut result = self.clone();
320        result.suffix = None;
321        result
322    }
323
324    /// Returns a new copy of the variable name with the version dropped.
325    #[must_use]
326    pub fn without_version(&self) -> VariableName {
327        let mut result = self.clone();
328        result.version = None;
329        result
330    }
331}
332
333/// Display for VariableName only outputs the original name.
334impl fmt::Display for VariableName {
335    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
336        write!(f, "{}", self.name)
337    }
338}
339
340/// Debug for VariableName outputs the full name (including suffix and version).
341impl fmt::Debug for VariableName {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
343        write!(f, "{}", self.name)?;
344        if let Some(suffix) = self.suffix() {
345            write!(f, "_{suffix}")?;
346        }
347        if let Some(version) = self.version() {
348            write!(f, ".{version}")?;
349        }
350        Ok(())
351    }
352}
353
354#[derive(Clone, Hash, Eq, PartialEq)]
355pub enum AccessType {
356    ArrayAccess(Box<Expression>),
357    ComponentAccess(String),
358}
359
360#[derive(Copy, Clone, Hash, Eq, PartialEq)]
361pub enum AssignOp {
362    /// A signal assignment (using `<--`)
363    AssignSignal,
364    /// A signal assignment (using `<==`)
365    AssignConstraintSignal,
366    /// A local variable assignment or component initialization (using `=`).
367    AssignLocalOrComponent,
368}
369
370#[derive(Copy, Clone, Hash, Eq, PartialEq)]
371pub enum ExpressionInfixOpcode {
372    Mul,
373    Div,
374    Add,
375    Sub,
376    Pow,
377    IntDiv,
378    Mod,
379    ShiftL,
380    ShiftR,
381    LesserEq,
382    GreaterEq,
383    Lesser,
384    Greater,
385    Eq,
386    NotEq,
387    BoolOr,
388    BoolAnd,
389    BitOr,
390    BitAnd,
391    BitXor,
392}
393
394#[derive(Copy, Clone, Hash, Eq, PartialEq)]
395pub enum ExpressionPrefixOpcode {
396    Sub,
397    BoolNot,
398    Complement,
399}
400
401#[derive(Clone)]
402pub enum LogArgument {
403    String(String),
404    Expr(Box<Expression>),
405}