Skip to main content

snarkvm_synthesizer_program/closure/
mod.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::Instruction;
17
18mod input;
19use input::*;
20
21mod output;
22use output::*;
23
24mod bytes;
25mod parse;
26
27use console::{
28    network::{error, prelude::*},
29    program::{Identifier, Register, RegisterType},
30};
31
32use indexmap::IndexSet;
33
34#[derive(Clone, PartialEq, Eq)]
35pub struct ClosureCore<N: Network> {
36    /// The name of the closure.
37    name: Identifier<N>,
38    /// The input statements, added in order of the input registers.
39    /// Input assignments are ensured to match the ordering of the input statements.
40    inputs: IndexSet<Input<N>>,
41    /// The instructions, in order of execution.
42    instructions: Vec<Instruction<N>>,
43    /// The output statements, in order of the desired output.
44    outputs: IndexSet<Output<N>>,
45}
46
47impl<N: Network> ClosureCore<N> {
48    /// Initializes a new closure with the given name.
49    pub fn new(name: Identifier<N>) -> Self {
50        Self { name, inputs: IndexSet::new(), instructions: Vec::new(), outputs: IndexSet::new() }
51    }
52
53    /// Returns the name of the closure.
54    pub const fn name(&self) -> &Identifier<N> {
55        &self.name
56    }
57
58    /// Returns the closure inputs.
59    pub const fn inputs(&self) -> &IndexSet<Input<N>> {
60        &self.inputs
61    }
62
63    /// Returns the closure instructions.
64    pub fn instructions(&self) -> &[Instruction<N>] {
65        &self.instructions
66    }
67
68    /// Returns the closure outputs.
69    pub const fn outputs(&self) -> &IndexSet<Output<N>> {
70        &self.outputs
71    }
72
73    /// Returns the closure output types.
74    pub fn output_types(&self) -> Vec<RegisterType<N>> {
75        self.outputs.iter().map(|output| output.register_type()).cloned().collect()
76    }
77
78    /// Returns whether the closure refers to an external struct.
79    pub fn contains_external_struct(&self) -> bool {
80        self.inputs.iter().any(|input| input.register_type().contains_external_struct())
81            || self.outputs.iter().any(|output| output.register_type().contains_external_struct())
82            || self.instructions.iter().any(|instruction| instruction.contains_external_struct())
83    }
84
85    /// Returns `true` if the closure instructions contain a string type.
86    /// Note that input and output types don't have to be checked if we are sure the broader function doesn't contain a string type.
87    pub fn contains_string_type(&self) -> bool {
88        self.instructions.iter().any(|instruction| instruction.contains_string_type())
89    }
90
91    /// Returns `true` if the closure contains an identifier type in its inputs, outputs, or instructions.
92    pub fn contains_identifier_type(&self) -> Result<bool> {
93        for input in &self.inputs {
94            if input.register_type().contains_identifier_type()? {
95                return Ok(true);
96            }
97        }
98        for output in &self.outputs {
99            if output.register_type().contains_identifier_type()? {
100                return Ok(true);
101            }
102        }
103        // Check instruction-level types (e.g., cast destination types).
104        for instruction in &self.instructions {
105            if instruction.contains_identifier_type()? {
106                return Ok(true);
107            }
108        }
109        Ok(false)
110    }
111
112    /// Returns `true` if the closure contains an array type with a size that exceeds the given maximum.
113    pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
114        self.inputs.iter().any(|input| input.register_type().exceeds_max_array_size(max_array_size))
115            || self.outputs.iter().any(|output| output.register_type().exceeds_max_array_size(max_array_size))
116            || self.instructions.iter().any(|instruction| instruction.exceeds_max_array_size(max_array_size))
117    }
118}
119
120impl<N: Network> ClosureCore<N> {
121    /// Adds the input statement to the closure.
122    ///
123    /// # Errors
124    /// This method will halt if there are instructions or output statements already.
125    /// This method will halt if the maximum number of inputs has been reached.
126    /// This method will halt if the input statement was previously added.
127    #[inline]
128    fn add_input(&mut self, input: Input<N>) -> Result<()> {
129        // Ensure there are no instructions or output statements in memory.
130        ensure!(self.instructions.is_empty(), "Cannot add inputs after instructions have been added");
131        ensure!(self.outputs.is_empty(), "Cannot add inputs after outputs have been added");
132
133        // Ensure the maximum number of inputs has not been exceeded.
134        ensure!(self.inputs.len() < N::MAX_INPUTS, "Cannot add more than {} inputs", N::MAX_INPUTS);
135        // Ensure the input statement was not previously added.
136        ensure!(!self.inputs.contains(&input), "Cannot add duplicate input statement");
137
138        // Ensure the input register is a locator.
139        ensure!(matches!(input.register(), Register::Locator(..)), "Input register must be a locator");
140
141        // Insert the input statement.
142        self.inputs.insert(input);
143        Ok(())
144    }
145
146    /// Adds the given instruction to the closure.
147    ///
148    /// # Errors
149    /// This method will halt if there are output statements already.
150    /// This method will halt if the maximum number of instructions has been reached.
151    #[inline]
152    pub fn add_instruction(&mut self, instruction: Instruction<N>) -> Result<()> {
153        // Ensure that there are no outputs in memory.
154        ensure!(self.outputs.is_empty(), "Cannot add instructions after outputs have been added");
155
156        // Ensure the maximum number of instructions has not been exceeded.
157        ensure!(
158            self.instructions.len() < N::MAX_INSTRUCTIONS,
159            "Cannot add more than {} instructions",
160            N::MAX_INSTRUCTIONS
161        );
162
163        // Ensure the destination register is a locator.
164        for register in instruction.destinations() {
165            ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator");
166        }
167
168        // Insert the instruction.
169        self.instructions.push(instruction);
170        Ok(())
171    }
172
173    /// Adds the output statement to the closure.
174    ///
175    /// # Errors
176    /// This method will halt if the maximum number of outputs has been reached.
177    #[inline]
178    fn add_output(&mut self, output: Output<N>) -> Result<()> {
179        // Ensure the maximum number of outputs has not been exceeded.
180        ensure!(self.outputs.len() < N::MAX_OUTPUTS, "Cannot add more than {} outputs", N::MAX_OUTPUTS);
181
182        // Ensure the closure output register is not a record.
183        ensure!(!matches!(output.register_type(), RegisterType::Record(..)), "Output register cannot be a record");
184
185        // Insert the output statement.
186        self.outputs.insert(output);
187        Ok(())
188    }
189}
190
191impl<N: Network> TypeName for ClosureCore<N> {
192    /// Returns the type name as a string.
193    #[inline]
194    fn type_name() -> &'static str {
195        "closure"
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    use crate::{Closure, Instruction};
204
205    type CurrentNetwork = console::network::MainnetV0;
206
207    #[test]
208    fn test_add_input() {
209        // Initialize a new closure instance.
210        let name = Identifier::from_str("closure_core_test").unwrap();
211        let mut closure = Closure::<CurrentNetwork>::new(name);
212
213        // Ensure that an input can be added.
214        let input = Input::<CurrentNetwork>::from_str("input r0 as field;").unwrap();
215        assert!(closure.add_input(input.clone()).is_ok());
216
217        // Ensure that adding a duplicate input will fail.
218        assert!(closure.add_input(input).is_err());
219
220        // Ensure that adding more than the maximum number of inputs will fail.
221        for i in 1..CurrentNetwork::MAX_INPUTS * 2 {
222            let input = Input::<CurrentNetwork>::from_str(&format!("input r{i} as field;")).unwrap();
223
224            match closure.inputs.len() < CurrentNetwork::MAX_INPUTS {
225                true => assert!(closure.add_input(input).is_ok()),
226                false => assert!(closure.add_input(input).is_err()),
227            }
228        }
229    }
230
231    #[test]
232    fn test_add_instruction() {
233        // Initialize a new closure instance.
234        let name = Identifier::from_str("closure_core_test").unwrap();
235        let mut closure = Closure::<CurrentNetwork>::new(name);
236
237        // Ensure that an instruction can be added.
238        let instruction = Instruction::<CurrentNetwork>::from_str("add r0 r1 into r2;").unwrap();
239        assert!(closure.add_instruction(instruction).is_ok());
240
241        // Ensure that adding more than the maximum number of instructions will fail.
242        for i in 3..CurrentNetwork::MAX_INSTRUCTIONS * 2 {
243            let instruction = Instruction::<CurrentNetwork>::from_str(&format!("add r0 r1 into r{i};")).unwrap();
244
245            match closure.instructions.len() < CurrentNetwork::MAX_INSTRUCTIONS {
246                true => assert!(closure.add_instruction(instruction).is_ok()),
247                false => assert!(closure.add_instruction(instruction).is_err()),
248            }
249        }
250    }
251
252    #[test]
253    fn test_add_output() {
254        // Initialize a new closure instance.
255        let name = Identifier::from_str("closure_core_test").unwrap();
256        let mut closure = Closure::<CurrentNetwork>::new(name);
257
258        // Ensure that an output can be added.
259        let output = Output::<CurrentNetwork>::from_str("output r0 as field;").unwrap();
260        assert!(closure.add_output(output).is_ok());
261
262        // Ensure that adding more than the maximum number of outputs will fail.
263        for i in 1..CurrentNetwork::MAX_OUTPUTS * 2 {
264            let output = Output::<CurrentNetwork>::from_str(&format!("output r{i} as field;")).unwrap();
265
266            match closure.outputs.len() < CurrentNetwork::MAX_OUTPUTS {
267                true => assert!(closure.add_output(output).is_ok()),
268                false => assert!(closure.add_output(output).is_err()),
269            }
270        }
271    }
272}