Skip to main content

snarkvm_synthesizer_program/closure/
mod.rs

1// Copyright (c) 2019-2025 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 array type with a size that exceeds the given maximum.
92    pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
93        self.inputs.iter().any(|input| input.register_type().exceeds_max_array_size(max_array_size))
94            || self.outputs.iter().any(|output| output.register_type().exceeds_max_array_size(max_array_size))
95            || self.instructions.iter().any(|instruction| instruction.exceeds_max_array_size(max_array_size))
96    }
97}
98
99impl<N: Network> ClosureCore<N> {
100    /// Adds the input statement to the closure.
101    ///
102    /// # Errors
103    /// This method will halt if there are instructions or output statements already.
104    /// This method will halt if the maximum number of inputs has been reached.
105    /// This method will halt if the input statement was previously added.
106    #[inline]
107    fn add_input(&mut self, input: Input<N>) -> Result<()> {
108        // Ensure there are no instructions or output statements in memory.
109        ensure!(self.instructions.is_empty(), "Cannot add inputs after instructions have been added");
110        ensure!(self.outputs.is_empty(), "Cannot add inputs after outputs have been added");
111
112        // Ensure the maximum number of inputs has not been exceeded.
113        ensure!(self.inputs.len() < N::MAX_INPUTS, "Cannot add more than {} inputs", N::MAX_INPUTS);
114        // Ensure the input statement was not previously added.
115        ensure!(!self.inputs.contains(&input), "Cannot add duplicate input statement");
116
117        // Ensure the input register is a locator.
118        ensure!(matches!(input.register(), Register::Locator(..)), "Input register must be a locator");
119
120        // Insert the input statement.
121        self.inputs.insert(input);
122        Ok(())
123    }
124
125    /// Adds the given instruction to the closure.
126    ///
127    /// # Errors
128    /// This method will halt if there are output statements already.
129    /// This method will halt if the maximum number of instructions has been reached.
130    #[inline]
131    pub fn add_instruction(&mut self, instruction: Instruction<N>) -> Result<()> {
132        // Ensure that there are no outputs in memory.
133        ensure!(self.outputs.is_empty(), "Cannot add instructions after outputs have been added");
134
135        // Ensure the maximum number of instructions has not been exceeded.
136        ensure!(
137            self.instructions.len() < N::MAX_INSTRUCTIONS,
138            "Cannot add more than {} instructions",
139            N::MAX_INSTRUCTIONS
140        );
141
142        // Ensure the destination register is a locator.
143        for register in instruction.destinations() {
144            ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator");
145        }
146
147        // Insert the instruction.
148        self.instructions.push(instruction);
149        Ok(())
150    }
151
152    /// Adds the output statement to the closure.
153    ///
154    /// # Errors
155    /// This method will halt if the maximum number of outputs has been reached.
156    #[inline]
157    fn add_output(&mut self, output: Output<N>) -> Result<()> {
158        // Ensure the maximum number of outputs has not been exceeded.
159        ensure!(self.outputs.len() < N::MAX_OUTPUTS, "Cannot add more than {} outputs", N::MAX_OUTPUTS);
160
161        // Ensure the closure output register is not a record.
162        ensure!(!matches!(output.register_type(), RegisterType::Record(..)), "Output register cannot be a record");
163
164        // Insert the output statement.
165        self.outputs.insert(output);
166        Ok(())
167    }
168}
169
170impl<N: Network> TypeName for ClosureCore<N> {
171    /// Returns the type name as a string.
172    #[inline]
173    fn type_name() -> &'static str {
174        "closure"
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    use crate::{Closure, Instruction};
183
184    type CurrentNetwork = console::network::MainnetV0;
185
186    #[test]
187    fn test_add_input() {
188        // Initialize a new closure instance.
189        let name = Identifier::from_str("closure_core_test").unwrap();
190        let mut closure = Closure::<CurrentNetwork>::new(name);
191
192        // Ensure that an input can be added.
193        let input = Input::<CurrentNetwork>::from_str("input r0 as field;").unwrap();
194        assert!(closure.add_input(input.clone()).is_ok());
195
196        // Ensure that adding a duplicate input will fail.
197        assert!(closure.add_input(input).is_err());
198
199        // Ensure that adding more than the maximum number of inputs will fail.
200        for i in 1..CurrentNetwork::MAX_INPUTS * 2 {
201            let input = Input::<CurrentNetwork>::from_str(&format!("input r{i} as field;")).unwrap();
202
203            match closure.inputs.len() < CurrentNetwork::MAX_INPUTS {
204                true => assert!(closure.add_input(input).is_ok()),
205                false => assert!(closure.add_input(input).is_err()),
206            }
207        }
208    }
209
210    #[test]
211    fn test_add_instruction() {
212        // Initialize a new closure instance.
213        let name = Identifier::from_str("closure_core_test").unwrap();
214        let mut closure = Closure::<CurrentNetwork>::new(name);
215
216        // Ensure that an instruction can be added.
217        let instruction = Instruction::<CurrentNetwork>::from_str("add r0 r1 into r2;").unwrap();
218        assert!(closure.add_instruction(instruction).is_ok());
219
220        // Ensure that adding more than the maximum number of instructions will fail.
221        for i in 3..CurrentNetwork::MAX_INSTRUCTIONS * 2 {
222            let instruction = Instruction::<CurrentNetwork>::from_str(&format!("add r0 r1 into r{i};")).unwrap();
223
224            match closure.instructions.len() < CurrentNetwork::MAX_INSTRUCTIONS {
225                true => assert!(closure.add_instruction(instruction).is_ok()),
226                false => assert!(closure.add_instruction(instruction).is_err()),
227            }
228        }
229    }
230
231    #[test]
232    fn test_add_output() {
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 output can be added.
238        let output = Output::<CurrentNetwork>::from_str("output r0 as field;").unwrap();
239        assert!(closure.add_output(output).is_ok());
240
241        // Ensure that adding more than the maximum number of outputs will fail.
242        for i in 1..CurrentNetwork::MAX_OUTPUTS * 2 {
243            let output = Output::<CurrentNetwork>::from_str(&format!("output r{i} as field;")).unwrap();
244
245            match closure.outputs.len() < CurrentNetwork::MAX_OUTPUTS {
246                true => assert!(closure.add_output(output).is_ok()),
247                false => assert!(closure.add_output(output).is_err()),
248            }
249        }
250    }
251}