Skip to main content

snarkvm_synthesizer_program/function/
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
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},
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 instructions.
70    pub fn instructions(&self) -> &[Instruction<N>] {
71        &self.instructions
72    }
73
74    /// Returns the function outputs.
75    pub const fn outputs(&self) -> &IndexSet<Output<N>> {
76        &self.outputs
77    }
78
79    /// Returns the function output types.
80    pub fn output_types(&self) -> Vec<ValueType<N>> {
81        self.outputs.iter().map(|output| output.value_type()).cloned().collect()
82    }
83
84    /// Returns the function finalize logic.
85    pub const fn finalize_logic(&self) -> Option<&FinalizeCore<N>> {
86        self.finalize_logic.as_ref()
87    }
88
89    /// Returns whether this function refers to an external struct.
90    pub fn contains_external_struct(&self) -> bool {
91        self.inputs.iter().any(|input| input.value_type().contains_external_struct())
92            || self.outputs.iter().any(|output| output.value_type().contains_external_struct())
93            || self.instructions.iter().any(|instruction| instruction.contains_external_struct())
94            || self.finalize_logic.iter().any(|finalize| finalize.contains_external_struct())
95    }
96
97    /// Returns `true` if the function contains a string type.
98    pub fn contains_string_type(&self) -> bool {
99        self.input_types().iter().any(|input| input.contains_string_type())
100            || self.output_types().iter().any(|output| output.contains_string_type())
101            || self.instructions.iter().any(|instruction| instruction.contains_string_type())
102            || self.finalize_logic.as_ref().map(|finalize| finalize.contains_string_type()).unwrap_or(false)
103    }
104
105    /// Returns `true` if the function contains an identifier type in its inputs, outputs, instructions, or finalize logic.
106    pub fn contains_identifier_type(&self) -> Result<bool> {
107        for input in self.input_types() {
108            if input.contains_identifier_type()? {
109                return Ok(true);
110            }
111        }
112        for output in self.output_types() {
113            if output.contains_identifier_type()? {
114                return Ok(true);
115            }
116        }
117        // Check instruction-level types (e.g., cast destination types).
118        for instruction in &self.instructions {
119            if instruction.contains_identifier_type()? {
120                return Ok(true);
121            }
122        }
123        if let Some(finalize) = &self.finalize_logic {
124            if finalize.contains_identifier_type()? {
125                return Ok(true);
126            }
127        }
128        Ok(false)
129    }
130
131    /// Returns `true` if the function scope contains an array type with a size that exceeds the given maximum.
132    pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
133        self.inputs.iter().any(|input| input.value_type().exceeds_max_array_size(max_array_size))
134            || self.outputs.iter().any(|output| output.value_type().exceeds_max_array_size(max_array_size))
135            || self.instructions.iter().any(|instruction| instruction.exceeds_max_array_size(max_array_size))
136            || self.finalize_logic.iter().any(|finalize| finalize.exceeds_max_array_size(max_array_size))
137    }
138}
139
140impl<N: Network> FunctionCore<N> {
141    /// Adds the input statement to the function.
142    ///
143    /// # Errors
144    /// This method will halt if there are instructions or output statements already.
145    /// This method will halt if the maximum number of inputs has been reached.
146    /// This method will halt if the input statement was previously added.
147    /// This method will halt if a finalize logic has been added.
148    #[inline]
149    fn add_input(&mut self, input: Input<N>) -> Result<()> {
150        // Ensure there are no instructions or output statements in memory.
151        ensure!(self.instructions.is_empty(), "Cannot add inputs after instructions have been added");
152        ensure!(self.outputs.is_empty(), "Cannot add inputs after outputs have been added");
153
154        // Ensure the maximum number of inputs has not been exceeded.
155        ensure!(self.inputs.len() < N::MAX_INPUTS, "Cannot add more than {} inputs", N::MAX_INPUTS);
156        // Ensure the input statement was not previously added.
157        ensure!(!self.inputs.contains(&input), "Cannot add duplicate input statement");
158
159        // Ensure a finalize logic has not been added.
160        ensure!(self.finalize_logic.is_none(), "Cannot add instructions after finalize logic has been added");
161
162        // Ensure the input register is a locator.
163        ensure!(matches!(input.register(), Register::Locator(..)), "Input register must be a locator");
164
165        // Insert the input statement.
166        self.inputs.insert(input);
167        Ok(())
168    }
169
170    /// Adds the given instruction to the function.
171    ///
172    /// # Errors
173    /// This method will halt if there are output statements already.
174    /// This method will halt if the maximum number of instructions has been reached.
175    /// This method will halt if a finalize logic has been added.
176    #[inline]
177    pub fn add_instruction(&mut self, instruction: Instruction<N>) -> Result<()> {
178        // Ensure that there are no output statements in memory.
179        ensure!(self.outputs.is_empty(), "Cannot add instructions after outputs have been added");
180
181        // Ensure the maximum number of instructions has not been exceeded.
182        ensure!(
183            self.instructions.len() < N::MAX_INSTRUCTIONS,
184            "Cannot add more than {} instructions",
185            N::MAX_INSTRUCTIONS
186        );
187
188        // Ensure a finalize logic has not been added.
189        ensure!(self.finalize_logic.is_none(), "Cannot add instructions after finalize logic has been added");
190
191        // Ensure the destination register is a locator.
192        for register in instruction.destinations() {
193            ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator");
194        }
195
196        // Insert the instruction.
197        self.instructions.push(instruction);
198        Ok(())
199    }
200
201    /// Adds the output statement to the function.
202    ///
203    /// # Errors
204    /// This method will halt if the maximum number of outputs has been reached.
205    /// This method will halt if a finalize logic has been added.
206    #[inline]
207    fn add_output(&mut self, output: Output<N>) -> Result<()> {
208        // Ensure the maximum number of outputs has not been exceeded.
209        ensure!(self.outputs.len() < N::MAX_OUTPUTS, "Cannot add more than {} outputs", N::MAX_OUTPUTS);
210        // Ensure the output statement was not previously added.
211        ensure!(!self.outputs.contains(&output), "Cannot add duplicate output statement");
212
213        // Ensure that the finalize logic has not been added.
214        ensure!(self.finalize_logic.is_none(), "Cannot add instructions after finalize logic has been added");
215
216        // Insert the output statement.
217        self.outputs.insert(output);
218        Ok(())
219    }
220
221    /// Adds the finalize scope to the function.
222    ///
223    /// # Errors
224    /// This method will halt if a finalize scope has already been added.
225    /// This method will halt if name in the finalize scope does not match the function name.
226    /// This method will halt if the maximum number of finalize inputs has been reached.
227    /// This method will halt if the number of finalize operands does not match the number of finalize inputs.
228    #[inline]
229    fn add_finalize(&mut self, finalize: FinalizeCore<N>) -> Result<()> {
230        // Ensure there is no finalize scope in memory.
231        ensure!(self.finalize_logic.is_none(), "Cannot add multiple finalize scopes to function '{}'", self.name);
232        // Ensure the finalize scope name matches the function name.
233        ensure!(*finalize.name() == self.name, "Finalize scope name must match function name '{}'", self.name);
234        // Ensure the number of finalize inputs has not been exceeded.
235        ensure!(finalize.inputs().len() <= N::MAX_INPUTS, "Cannot add more than {} inputs to finalize", N::MAX_INPUTS);
236
237        // Insert the finalize scope.
238        self.finalize_logic = Some(finalize);
239        Ok(())
240    }
241}
242
243impl<N: Network> TypeName for FunctionCore<N> {
244    /// Returns the type name as a string.
245    #[inline]
246    fn type_name() -> &'static str {
247        "function"
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    use crate::{Function, Instruction};
256
257    type CurrentNetwork = console::network::MainnetV0;
258
259    #[test]
260    fn test_add_input() {
261        // Initialize a new function instance.
262        let name = Identifier::from_str("function_core_test").unwrap();
263        let mut function = Function::<CurrentNetwork>::new(name);
264
265        // Ensure that an input can be added.
266        let input = Input::<CurrentNetwork>::from_str("input r0 as field.private;").unwrap();
267        assert!(function.add_input(input.clone()).is_ok());
268
269        // Ensure that adding a duplicate input will fail.
270        assert!(function.add_input(input).is_err());
271
272        // Ensure that adding more than the maximum number of inputs will fail.
273        for i in 1..CurrentNetwork::MAX_INPUTS * 2 {
274            let input = Input::<CurrentNetwork>::from_str(&format!("input r{i} as field.private;")).unwrap();
275
276            match function.inputs.len() < CurrentNetwork::MAX_INPUTS {
277                true => assert!(function.add_input(input).is_ok()),
278                false => assert!(function.add_input(input).is_err()),
279            }
280        }
281    }
282
283    #[test]
284    fn test_add_instruction() {
285        // Initialize a new function instance.
286        let name = Identifier::from_str("function_core_test").unwrap();
287        let mut function = Function::<CurrentNetwork>::new(name);
288
289        // Ensure that an instruction can be added.
290        let instruction = Instruction::<CurrentNetwork>::from_str("add r0 r1 into r2;").unwrap();
291        assert!(function.add_instruction(instruction).is_ok());
292
293        // Ensure that adding more than the maximum number of instructions will fail.
294        for i in 3..CurrentNetwork::MAX_INSTRUCTIONS * 2 {
295            let instruction = Instruction::<CurrentNetwork>::from_str(&format!("add r0 r1 into r{i};")).unwrap();
296
297            match function.instructions.len() < CurrentNetwork::MAX_INSTRUCTIONS {
298                true => assert!(function.add_instruction(instruction).is_ok()),
299                false => assert!(function.add_instruction(instruction).is_err()),
300            }
301        }
302    }
303
304    #[test]
305    fn test_add_output() {
306        // Initialize a new function instance.
307        let name = Identifier::from_str("function_core_test").unwrap();
308        let mut function = Function::<CurrentNetwork>::new(name);
309
310        // Ensure that an output can be added.
311        let output = Output::<CurrentNetwork>::from_str("output r0 as field.private;").unwrap();
312        assert!(function.add_output(output).is_ok());
313
314        // Ensure that adding more than the maximum number of outputs will fail.
315        for i in 1..CurrentNetwork::MAX_OUTPUTS * 2 {
316            let output = Output::<CurrentNetwork>::from_str(&format!("output r{i} as field.private;")).unwrap();
317
318            match function.outputs.len() < CurrentNetwork::MAX_OUTPUTS {
319                true => assert!(function.add_output(output).is_ok()),
320                false => assert!(function.add_output(output).is_err()),
321            }
322        }
323    }
324}