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