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