snarkvm_synthesizer_program/function/
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
16mod input;
17use input::*;
18
19mod output;
20use output::*;
21
22mod bytes;
23mod parse;
24
25use crate::{Instruction, finalize::FinalizeCore};
26use console::{
27    network::prelude::*,
28    program::{Identifier, Register, ValueType, Variant},
29};
30
31use indexmap::IndexSet;
32
33#[derive(Clone, PartialEq, Eq)]
34pub struct FunctionCore<N: Network> {
35    /// The name of the function.
36    name: Identifier<N>,
37    /// The input statements, added in order of the input registers.
38    /// Input assignments are ensured to match the ordering of the input statements.
39    inputs: IndexSet<Input<N>>,
40    /// The instructions, in order of execution.
41    instructions: Vec<Instruction<N>>,
42    /// The output statements, in order of the desired output.
43    outputs: IndexSet<Output<N>>,
44    /// The optional finalize logic.
45    finalize_logic: Option<FinalizeCore<N>>,
46}
47
48impl<N: Network> FunctionCore<N> {
49    /// Initializes a new function with the given name.
50    pub fn new(name: Identifier<N>) -> Self {
51        Self { name, inputs: IndexSet::new(), instructions: Vec::new(), outputs: IndexSet::new(), finalize_logic: None }
52    }
53
54    /// Returns the name of the function.
55    pub const fn name(&self) -> &Identifier<N> {
56        &self.name
57    }
58
59    /// Returns the function inputs.
60    pub const fn inputs(&self) -> &IndexSet<Input<N>> {
61        &self.inputs
62    }
63
64    /// Returns the function input types.
65    pub fn input_types(&self) -> Vec<ValueType<N>> {
66        self.inputs.iter().map(|input| input.value_type()).cloned().collect()
67    }
68
69    /// Returns the function input type variants.
70    pub fn input_variants(&self) -> Vec<Variant> {
71        self.inputs.iter().map(|input| input.value_type().variant()).collect()
72    }
73
74    /// Returns the function instructions.
75    pub fn instructions(&self) -> &[Instruction<N>] {
76        &self.instructions
77    }
78
79    /// Returns the function outputs.
80    pub const fn outputs(&self) -> &IndexSet<Output<N>> {
81        &self.outputs
82    }
83
84    /// Returns the function output types.
85    pub fn output_types(&self) -> Vec<ValueType<N>> {
86        self.outputs.iter().map(|output| output.value_type()).cloned().collect()
87    }
88
89    /// Returns the function output type variants.
90    pub fn output_variants(&self) -> Vec<Variant> {
91        self.outputs.iter().map(|output| output.value_type().variant()).collect()
92    }
93
94    /// Returns the function finalize logic.
95    pub const fn finalize_logic(&self) -> Option<&FinalizeCore<N>> {
96        self.finalize_logic.as_ref()
97    }
98
99    /// Returns `true` if the function scope contains an array type with a size that exceeds the given maximum.
100    pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
101        self.inputs.iter().any(|input| input.value_type().exceeds_max_array_size(max_array_size))
102            || self.outputs.iter().any(|output| output.value_type().exceeds_max_array_size(max_array_size))
103            || self.instructions.iter().any(|instruction| instruction.exceeds_max_array_size(max_array_size))
104            || self.finalize_logic.iter().any(|finalize| finalize.exceeds_max_array_size(max_array_size))
105    }
106}
107
108impl<N: Network> FunctionCore<N> {
109    /// Adds the input statement to the function.
110    ///
111    /// # Errors
112    /// This method will halt if there are instructions or output statements already.
113    /// This method will halt if the maximum number of inputs has been reached.
114    /// This method will halt if the input statement was previously added.
115    /// This method will halt if a finalize logic has been added.
116    #[inline]
117    fn add_input(&mut self, input: Input<N>) -> Result<()> {
118        // Ensure there are no instructions or output statements in memory.
119        ensure!(self.instructions.is_empty(), "Cannot add inputs after instructions have been added");
120        ensure!(self.outputs.is_empty(), "Cannot add inputs after outputs have been added");
121
122        // Ensure the maximum number of inputs has not been exceeded.
123        ensure!(self.inputs.len() < N::MAX_INPUTS, "Cannot add more than {} inputs", N::MAX_INPUTS);
124        // Ensure the input statement was not previously added.
125        ensure!(!self.inputs.contains(&input), "Cannot add duplicate input statement");
126
127        // Ensure a finalize logic has not been added.
128        ensure!(self.finalize_logic.is_none(), "Cannot add instructions after finalize logic has been added");
129
130        // Ensure the input register is a locator.
131        ensure!(matches!(input.register(), Register::Locator(..)), "Input register must be a locator");
132
133        // Insert the input statement.
134        self.inputs.insert(input);
135        Ok(())
136    }
137
138    /// Adds the given instruction to the function.
139    ///
140    /// # Errors
141    /// This method will halt if there are output statements already.
142    /// This method will halt if the maximum number of instructions has been reached.
143    /// This method will halt if a finalize logic has been added.
144    #[inline]
145    pub fn add_instruction(&mut self, instruction: Instruction<N>) -> Result<()> {
146        // Ensure that there are no output statements in memory.
147        ensure!(self.outputs.is_empty(), "Cannot add instructions after outputs have been added");
148
149        // Ensure the maximum number of instructions has not been exceeded.
150        ensure!(
151            self.instructions.len() < N::MAX_INSTRUCTIONS,
152            "Cannot add more than {} instructions",
153            N::MAX_INSTRUCTIONS
154        );
155
156        // Ensure a finalize logic has not been added.
157        ensure!(self.finalize_logic.is_none(), "Cannot add instructions after finalize logic has been added");
158
159        // Ensure the destination register is a locator.
160        for register in instruction.destinations() {
161            ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator");
162        }
163
164        // Insert the instruction.
165        self.instructions.push(instruction);
166        Ok(())
167    }
168
169    /// Adds the output statement to the function.
170    ///
171    /// # Errors
172    /// This method will halt if the maximum number of outputs has been reached.
173    /// This method will halt if a finalize logic has been added.
174    #[inline]
175    fn add_output(&mut self, output: Output<N>) -> Result<()> {
176        // Ensure the maximum number of outputs has not been exceeded.
177        ensure!(self.outputs.len() < N::MAX_OUTPUTS, "Cannot add more than {} outputs", N::MAX_OUTPUTS);
178        // Ensure the output statement was not previously added.
179        ensure!(!self.outputs.contains(&output), "Cannot add duplicate output statement");
180
181        // Ensure that the finalize logic has not been added.
182        ensure!(self.finalize_logic.is_none(), "Cannot add instructions after finalize logic has been added");
183
184        // Insert the output statement.
185        self.outputs.insert(output);
186        Ok(())
187    }
188
189    /// Adds the finalize scope to the function.
190    ///
191    /// # Errors
192    /// This method will halt if a finalize scope has already been added.
193    /// This method will halt if name in the finalize scope does not match the function name.
194    /// This method will halt if the maximum number of finalize inputs has been reached.
195    /// This method will halt if the number of finalize operands does not match the number of finalize inputs.
196    #[inline]
197    fn add_finalize(&mut self, finalize: FinalizeCore<N>) -> Result<()> {
198        // Ensure there is no finalize scope in memory.
199        ensure!(self.finalize_logic.is_none(), "Cannot add multiple finalize scopes to function '{}'", self.name);
200        // Ensure the finalize scope name matches the function name.
201        ensure!(*finalize.name() == self.name, "Finalize scope name must match function name '{}'", self.name);
202        // Ensure the number of finalize inputs has not been exceeded.
203        ensure!(finalize.inputs().len() <= N::MAX_INPUTS, "Cannot add more than {} inputs to finalize", N::MAX_INPUTS);
204
205        // Insert the finalize scope.
206        self.finalize_logic = Some(finalize);
207        Ok(())
208    }
209}
210
211impl<N: Network> TypeName for FunctionCore<N> {
212    /// Returns the type name as a string.
213    #[inline]
214    fn type_name() -> &'static str {
215        "function"
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    use crate::{Function, Instruction};
224
225    type CurrentNetwork = console::network::MainnetV0;
226
227    #[test]
228    fn test_add_input() {
229        // Initialize a new function instance.
230        let name = Identifier::from_str("function_core_test").unwrap();
231        let mut function = Function::<CurrentNetwork>::new(name);
232
233        // Ensure that an input can be added.
234        let input = Input::<CurrentNetwork>::from_str("input r0 as field.private;").unwrap();
235        assert!(function.add_input(input.clone()).is_ok());
236
237        // Ensure that adding a duplicate input will fail.
238        assert!(function.add_input(input).is_err());
239
240        // Ensure that adding more than the maximum number of inputs will fail.
241        for i in 1..CurrentNetwork::MAX_INPUTS * 2 {
242            let input = Input::<CurrentNetwork>::from_str(&format!("input r{i} as field.private;")).unwrap();
243
244            match function.inputs.len() < CurrentNetwork::MAX_INPUTS {
245                true => assert!(function.add_input(input).is_ok()),
246                false => assert!(function.add_input(input).is_err()),
247            }
248        }
249    }
250
251    #[test]
252    fn test_add_instruction() {
253        // Initialize a new function instance.
254        let name = Identifier::from_str("function_core_test").unwrap();
255        let mut function = Function::<CurrentNetwork>::new(name);
256
257        // Ensure that an instruction can be added.
258        let instruction = Instruction::<CurrentNetwork>::from_str("add r0 r1 into r2;").unwrap();
259        assert!(function.add_instruction(instruction).is_ok());
260
261        // Ensure that adding more than the maximum number of instructions will fail.
262        for i in 3..CurrentNetwork::MAX_INSTRUCTIONS * 2 {
263            let instruction = Instruction::<CurrentNetwork>::from_str(&format!("add r0 r1 into r{i};")).unwrap();
264
265            match function.instructions.len() < CurrentNetwork::MAX_INSTRUCTIONS {
266                true => assert!(function.add_instruction(instruction).is_ok()),
267                false => assert!(function.add_instruction(instruction).is_err()),
268            }
269        }
270    }
271
272    #[test]
273    fn test_add_output() {
274        // Initialize a new function instance.
275        let name = Identifier::from_str("function_core_test").unwrap();
276        let mut function = Function::<CurrentNetwork>::new(name);
277
278        // Ensure that an output can be added.
279        let output = Output::<CurrentNetwork>::from_str("output r0 as field.private;").unwrap();
280        assert!(function.add_output(output).is_ok());
281
282        // Ensure that adding more than the maximum number of outputs will fail.
283        for i in 1..CurrentNetwork::MAX_OUTPUTS * 2 {
284            let output = Output::<CurrentNetwork>::from_str(&format!("output r{i} as field.private;")).unwrap();
285
286            match function.outputs.len() < CurrentNetwork::MAX_OUTPUTS {
287                true => assert!(function.add_output(output).is_ok()),
288                false => assert!(function.add_output(output).is_err()),
289            }
290        }
291    }
292}