snarkvm_synthesizer_program/function/
mod.rs

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