snarkvm_synthesizer_program/logic/instruction/operation/
async_.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::{Opcode, Operand, RegistersCircuit, RegistersTrait, Result, StackTrait};
17
18use circuit::{Inject, Mode};
19use console::{
20    network::prelude::*,
21    program::{Argument, FinalizeType, Future, Identifier, Locator, Register, RegisterType, Value},
22};
23
24/// Invokes the asynchronous call on the operands, producing a future.
25#[derive(Clone, PartialEq, Eq, Hash)]
26pub struct Async<N: Network> {
27    /// The function name.
28    function_name: Identifier<N>,
29    /// The operands.
30    operands: Vec<Operand<N>>,
31    /// The destination register.
32    destination: Register<N>,
33}
34
35impl<N: Network> Async<N> {
36    /// Returns the opcode.
37    #[inline]
38    pub const fn opcode() -> Opcode {
39        Opcode::Async
40    }
41
42    /// Returns the function name.
43    #[inline]
44    pub const fn function_name(&self) -> &Identifier<N> {
45        &self.function_name
46    }
47
48    /// Returns the operands in the operation.
49    #[inline]
50    pub fn operands(&self) -> &[Operand<N>] {
51        // Sanity check that there is less than or equal to MAX_INPUTS operands.
52        debug_assert!(self.operands.len() <= N::MAX_INPUTS, "`async` must have less than {} operands", N::MAX_INPUTS);
53        // Return the operands.
54        &self.operands
55    }
56
57    /// Returns the destination register.
58    #[inline]
59    pub fn destinations(&self) -> Vec<Register<N>> {
60        vec![self.destination.clone()]
61    }
62}
63
64impl<N: Network> Async<N> {
65    /// Evaluates the instruction.
66    #[inline]
67    pub fn evaluate(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
68        // Ensure the number of operands is correct.
69        if self.operands.len() > N::MAX_INPUTS {
70            bail!("'{}' expects <= {} operands, found {} operands", Self::opcode(), N::MAX_INPUTS, self.operands.len())
71        }
72
73        // Load the operand values and check that they are valid arguments.
74        let arguments: Vec<_> = self
75            .operands
76            .iter()
77            .map(|operand| match registers.load(stack, operand)? {
78                Value::Plaintext(plaintext) => Ok(Argument::Plaintext(plaintext)),
79                Value::Record(_) => bail!("Cannot pass a record into an `async` instruction"),
80                Value::Future(future) => Ok(Argument::Future(future)),
81            })
82            .try_collect()?;
83
84        // Initialize a future.
85        let future = Value::Future(Future::new(*stack.program_id(), *self.function_name(), arguments));
86        // Store the future in the destination register.
87        registers.store(stack, &self.destination, future)?;
88
89        Ok(())
90    }
91
92    /// Executes the instruction.
93    pub fn execute<A: circuit::Aleo<Network = N>>(
94        &self,
95        stack: &impl StackTrait<N>,
96        registers: &mut impl RegistersCircuit<N, A>,
97    ) -> Result<()> {
98        // Ensure the number of operands is correct.
99        if self.operands.len() > N::MAX_INPUTS {
100            bail!("'{}' expects <= {} operands, found {} operands", Self::opcode(), N::MAX_INPUTS, self.operands.len())
101        }
102
103        // Load the operand values and check that they are valid arguments.
104        let arguments: Vec<_> = self
105            .operands
106            .iter()
107            .map(|operand| match registers.load_circuit(stack, operand)? {
108                circuit::Value::Plaintext(plaintext) => Ok(circuit::Argument::Plaintext(plaintext)),
109                circuit::Value::Record(_) => bail!("Cannot pass a record into an `async` instruction"),
110                circuit::Value::Future(future) => Ok(circuit::Argument::Future(future)),
111            })
112            .try_collect()?;
113
114        // Initialize a future.
115        let future = circuit::Value::Future(circuit::Future::from(
116            circuit::ProgramID::new(Mode::Constant, *stack.program_id()),
117            circuit::Identifier::new(Mode::Constant, *self.function_name()),
118            arguments,
119        ));
120        // Store the future in the destination register.
121        registers.store_circuit(stack, &self.destination, future)?;
122
123        Ok(())
124    }
125
126    /// Finalizes the instruction.
127    #[inline]
128    pub fn finalize(&self, _stack: &impl StackTrait<N>, _registers: &mut impl RegistersTrait<N>) -> Result<()> {
129        bail!("Forbidden operation: Finalize cannot invoke 'async'.")
130    }
131
132    /// Returns the output type from the given program and input types.
133    #[inline]
134    pub fn output_types(
135        &self,
136        stack: &impl StackTrait<N>,
137        input_types: &[RegisterType<N>],
138    ) -> Result<Vec<RegisterType<N>>> {
139        // Ensure that an associated finalize block exists.
140        let function = stack.get_function(self.function_name())?;
141        let finalize = match function.finalize_logic() {
142            Some(finalize) => finalize,
143            None => bail!("'{}/{}' does not have a finalize block", stack.program_id(), self.function_name()),
144        };
145
146        // Check that the number of inputs matches the number of arguments.
147        if input_types.len() != finalize.input_types().len() {
148            bail!(
149                "'{}/{}' finalize expects {} arguments, found {} arguments",
150                stack.program_id(),
151                self.function_name(),
152                finalize.input_types().len(),
153                input_types.len()
154            );
155        }
156
157        // Check the type of each operand.
158        for (input_type, finalize_type) in input_types.iter().zip_eq(finalize.input_types()) {
159            match (input_type, finalize_type) {
160                (RegisterType::Plaintext(input_type), FinalizeType::Plaintext(finalize_type)) => {
161                    ensure!(
162                        input_type == &finalize_type,
163                        "'{}/{}' finalize expects a '{}' argument, found a '{}' argument",
164                        stack.program_id(),
165                        self.function_name(),
166                        finalize_type,
167                        input_type
168                    );
169                }
170                (RegisterType::Record(..), _) => bail!("Attempted to pass a 'record' into 'async'"),
171                (RegisterType::ExternalRecord(..), _) => {
172                    bail!("Attempted to pass an 'external record' into 'async'")
173                }
174                (RegisterType::Future(input_locator), FinalizeType::Future(expected_locator)) => {
175                    ensure!(
176                        input_locator == &expected_locator,
177                        "'{}/{}' async expects a '{}.future' argument, found a '{}.future' argument",
178                        stack.program_id(),
179                        self.function_name(),
180                        expected_locator,
181                        input_locator
182                    );
183                }
184                (input_type, finalize_type) => bail!(
185                    "'{}/{}' async expects a '{}' argument, found a '{}' argument",
186                    stack.program_id(),
187                    self.function_name(),
188                    finalize_type,
189                    input_type
190                ),
191            }
192        }
193
194        Ok(vec![RegisterType::Future(Locator::new(*stack.program_id(), *self.function_name()))])
195    }
196}
197
198impl<N: Network> Parser for Async<N> {
199    /// Parses a string into an operation.
200    #[inline]
201    fn parse(string: &str) -> ParserResult<Self> {
202        /// Parses an operand.
203        fn parse_operand<N: Network>(string: &str) -> ParserResult<Operand<N>> {
204            // Parse the whitespace from the string.
205            let (string, _) = Sanitizer::parse_whitespaces(string)?;
206            // Parse the operand from the string.
207            let (string, operand) = Operand::parse(string)?;
208            // Return the remaining string and operand.
209            Ok((string, operand))
210        }
211
212        // Parse the whitespace and comments from the string.
213        let (string, _) = Sanitizer::parse(string)?;
214        // Parse the opcode from the string.
215        let (string, _) = tag(*Self::opcode())(string)?;
216        // Parse the whitespace from the string.
217        let (string, _) = Sanitizer::parse_whitespaces(string)?;
218        // Parse the function name from the string.
219        let (string, function_name) = Identifier::parse(string)?;
220        // Parse the operands from the string.
221        let (string, operands) = many0(parse_operand)(string)?;
222        // Parse the whitespace from the string.
223        let (string, _) = Sanitizer::parse_whitespaces(string)?;
224        // Parse the 'into' from the string.
225        let (string, _) = tag("into")(string)?;
226        // Parse the whitespace from the string.
227        let (string, _) = Sanitizer::parse_whitespaces(string)?;
228        // Parse the destination register from the string.
229        let (string, destination) = Register::parse(string)?;
230        // Parse the whitespace from the string.
231        let (string, _) = Sanitizer::parse_whitespaces(string)?;
232
233        // Ensure the number of operands is less than or equal to MAX_INPUTS.
234        match operands.len() <= N::MAX_INPUTS {
235            true => Ok((string, Self { function_name, operands, destination })),
236            false => map_res(fail, |_: ParserResult<Self>| {
237                Err(error(format!("The number of operands must be <= {}, found {}", N::MAX_INPUTS, operands.len())))
238            })(string),
239        }
240    }
241}
242
243impl<N: Network> FromStr for Async<N> {
244    type Err = Error;
245
246    /// Parses a string into an operation.
247    #[inline]
248    fn from_str(string: &str) -> Result<Self> {
249        match Self::parse(string) {
250            Ok((remainder, object)) => {
251                // Ensure the remainder is empty.
252                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
253                // Return the object.
254                Ok(object)
255            }
256            Err(error) => bail!("Failed to parse string. {error}"),
257        }
258    }
259}
260
261impl<N: Network> Debug for Async<N> {
262    /// Prints the operation as a string.
263    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
264        Display::fmt(self, f)
265    }
266}
267
268impl<N: Network> Display for Async<N> {
269    /// Prints the operation to a string.
270    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
271        // Ensure the number of operands is less than or equal to MAX_INPUTS.
272        if self.operands.len() > N::MAX_INPUTS {
273            return Err(fmt::Error);
274        }
275        // Print the operation.
276        write!(f, "{} {}", Self::opcode(), self.function_name)?;
277        self.operands.iter().try_for_each(|operand| write!(f, " {operand}"))?;
278        write!(f, " into {}", self.destination)
279    }
280}
281
282impl<N: Network> FromBytes for Async<N> {
283    /// Reads the operation from a buffer.
284    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
285        // Read the function name.
286        let function_name = Identifier::read_le(&mut reader)?;
287
288        // Read the number of operands.
289        let num_operands = u8::read_le(&mut reader)?;
290        // Ensure the number of operands is less than or equal to MAX_INPUTS.
291        if num_operands as usize > N::MAX_INPUTS {
292            return Err(error(format!("The number of operands must be <= {}, found {}", N::MAX_INPUTS, num_operands)));
293        }
294
295        // Initialize the vector for the operands.
296        let mut operands = Vec::with_capacity(num_operands as usize);
297        // Read the operands.
298        for _ in 0..(num_operands as usize) {
299            operands.push(Operand::read_le(&mut reader)?);
300        }
301
302        // Read the destination register.
303        let destination = Register::read_le(&mut reader)?;
304
305        // Return the operation.
306        Ok(Self { function_name, operands, destination })
307    }
308}
309
310impl<N: Network> ToBytes for Async<N> {
311    /// Writes the operation to a buffer.
312    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
313        // Ensure the number of operands is less than or equal to MAX_INPUTS.
314        if self.operands.len() > N::MAX_INPUTS {
315            return Err(error(format!(
316                "The number of operands must be <= {}, found {}",
317                N::MAX_INPUTS,
318                self.operands.len()
319            )));
320        }
321        // Write the function name.
322        self.function_name.write_le(&mut writer)?;
323        // Write the number of operands.
324        u8::try_from(self.operands.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?;
325        // Write the operands.
326        self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
327        // Write the destination register.
328        self.destination.write_le(&mut writer)
329    }
330}
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335    // use circuit::AleoV0;
336    use console::network::MainnetV0;
337
338    type CurrentNetwork = MainnetV0;
339    // type CurrentAleo = AleoV0;
340    //
341    // /// Samples the stack. Note: Do not replicate this for real program use, it is insecure.
342    // fn sample_stack(
343    //     opcode: Opcode,
344    //     type_a: LiteralType,
345    //     type_b: LiteralType,
346    //     mode_a: circuit::Mode,
347    //     mode_b: circuit::Mode,
348    // ) -> Result<(Stack<CurrentNetwork>, Vec<Operand<CurrentNetwork>>)> {
349    //     use crate::{Process, Program};
350    //     use console::program::Identifier;
351    //
352    //     // Initialize the opcode.
353    //     let opcode = opcode.to_string();
354    //
355    //     // Initialize the function name.
356    //     let function_name = Identifier::<CurrentNetwork>::from_str("run")?;
357    //
358    //     // Initialize the registers.
359    //     let r0 = Register::Locator(0);
360    //     let r1 = Register::Locator(1);
361    //
362    //     // Initialize the program.
363    //     let program = Program::from_str(&format!(
364    //         "program testing.aleo;
365    //         function {function_name}:
366    //             input {r0} as {type_a}.{mode_a};
367    //             input {r1} as {type_b}.{mode_b};
368    //             {opcode} {r0} {r1};
369    //     "
370    //     ))?;
371    //
372    //     // Initialize the operands.
373    //     let operand_a = Operand::Register(r0);
374    //     let operand_b = Operand::Register(r1);
375    //     let operands = vec![operand_a, operand_b];
376    //
377    //     // Initialize the stack.
378    //     let stack = Stack::new(&Process::load()?, &program)?;
379    //
380    //     Ok((stack, operands))
381    // }
382    //
383    // /// Samples the registers. Note: Do not replicate this for real program use, it is insecure.
384    // fn sample_registers(
385    //     stack: &Stack<CurrentNetwork>,
386    //     literal_a: &Literal<CurrentNetwork>,
387    //     literal_b: &Literal<CurrentNetwork>,
388    // ) -> Result<Registers<CurrentNetwork, CurrentAleo>> {
389    //     use crate::{Authorization, CallStack};
390    //     use console::program::{Identifier, Plaintext, Value};
391    //
392    //     // Initialize the function name.
393    //     let function_name = Identifier::from_str("run")?;
394    //
395    //     // Initialize the registers.
396    //     let mut registers = Registers::<CurrentNetwork, CurrentAleo>::new(
397    //         CallStack::evaluate(Authorization::new(&[]))?,
398    //         stack.get_register_types(&function_name)?.clone(),
399    //     );
400    //
401    //     // Initialize the registers.
402    //     let r0 = Register::Locator(0);
403    //     let r1 = Register::Locator(1);
404    //
405    //     // Initialize the console values.
406    //     let value_a = Value::Plaintext(Plaintext::from(literal_a));
407    //     let value_b = Value::Plaintext(Plaintext::from(literal_b));
408    //
409    //     // Store the values in the console registers.
410    //     registers.store(stack, &r0, value_a.clone())?;
411    //     registers.store(stack, &r1, value_b.clone())?;
412    //
413    //     Ok(registers)
414    // }
415    //
416    // fn check_finalize(
417    //     operation: impl FnOnce(Vec<Operand<CurrentNetwork>>) -> FinalizeOperation<CurrentNetwork, VARIANT>,
418    //     opcode: Opcode,
419    //     literal_a: &Literal<CurrentNetwork>,
420    //     literal_b: &Literal<CurrentNetwork>,
421    //     mode_a: &circuit::Mode,
422    //     mode_b: &circuit::Mode,
423    // ) {
424    //     // Initialize the types.
425    //     let type_a = literal_a.to_type();
426    //     let type_b = literal_b.to_type();
427    //     assert_eq!(type_a, type_b, "The two literals must be the *same* type for this test");
428    //
429    //     // Initialize the stack.
430    //     let (stack, operands) = sample_stack(opcode, type_a, type_b, *mode_a, *mode_b).unwrap();
431    //     // Initialize the operation.
432    //     let operation = operation(operands);
433    //
434    //     /* First, check the operation *succeeds* when both operands are `literal_a.mode_a`. */
435    //     {
436    //         // Attempt to compute the valid operand case.
437    //         let mut registers = sample_registers(&stack, literal_a, literal_a).unwrap();
438    //         let result_a = operation.evaluate(&stack, &mut registers);
439    //
440    //         // Ensure the result is correct.
441    //         match VARIANT {
442    //             0 => assert!(result_a.is_ok(), "Instruction '{operation}' failed (console): {literal_a} {literal_a}"),
443    //             _ => panic!("Found an invalid 'finalize' variant in the test"),
444    //         }
445    //     }
446    //     /* Next, check the mismatching literals *fail*. */
447    //     if literal_a != literal_b {
448    //         // Attempt to compute the valid operand case.
449    //         let mut registers = sample_registers(&stack, literal_a, literal_b).unwrap();
450    //         let result_a = operation.evaluate(&stack, &mut registers);
451    //
452    //         // Ensure the result is correct.
453    //         match VARIANT {
454    //             0 => assert!(
455    //                 result_a.is_err(),
456    //                 "Instruction '{operation}' should have failed (console): {literal_a} {literal_b}"
457    //             ),
458    //             _ => panic!("Found an invalid 'finalize' variant in the test"),
459    //         }
460    //     }
461    // }
462    //
463    // fn check_finalize_fails(
464    //     opcode: Opcode,
465    //     literal_a: &Literal<CurrentNetwork>,
466    //     literal_b: &Literal<CurrentNetwork>,
467    //     mode_a: &circuit::Mode,
468    //     mode_b: &circuit::Mode,
469    // ) {
470    //     // Initialize the types.
471    //     let type_a = literal_a.to_type();
472    //     let type_b = literal_b.to_type();
473    //     assert_ne!(type_a, type_b, "The two literals must be *different* types for this test");
474    //
475    //     // If the types mismatch, ensure the stack fails to initialize.
476    //     let result = sample_stack(opcode, type_a, type_b, *mode_a, *mode_b);
477    //     assert!(
478    //         result.is_err(),
479    //         "Stack should have failed to initialize for: {opcode} {type_a}.{mode_a} {type_b}.{mode_b}"
480    //     );
481    // }
482    //
483    // #[test]
484    // fn test_finalize_eq_succeeds() {
485    //     // Initialize the operation.
486    //     let operation = |operands| Async::<CurrentNetwork> { operands };
487    //     // Initialize the opcode.
488    //     let opcode = Async::<CurrentNetwork>::opcode();
489    //
490    //     let mut rng = TestRng::default();
491    //
492    //     // Prepare the test.
493    //     let literals_a = crate::sample_literals!(CurrentNetwork, &mut rng);
494    //     let literals_b = crate::sample_literals!(CurrentNetwork, &mut rng);
495    //     let modes_a = [/* circuit::Mode::Constant, */ circuit::Mode::Public, circuit::Mode::Private];
496    //     let modes_b = [/* circuit::Mode::Constant, */ circuit::Mode::Public, circuit::Mode::Private];
497    //
498    //     for (literal_a, literal_b) in literals_a.iter().zip_eq(literals_b.iter()) {
499    //         for mode_a in &modes_a {
500    //             for mode_b in &modes_b {
501    //                 // Check the operation.
502    //                 check_finalize(operation, opcode, literal_a, literal_b, mode_a, mode_b);
503    //             }
504    //         }
505    //     }
506    // }
507    //
508    // #[test]
509    // fn test_finalize_evaluate() {
510    //     use rayon::prelude::*;
511    //
512    //     // Initialize the opcode.
513    //     let opcode = Async::<CurrentNetwork>::opcode();
514    //
515    //     let mut rng = TestRng::default();
516    //
517    //     // Prepare the test.
518    //     let literals_a = crate::sample_literals!(CurrentNetwork, &mut rng);
519    //     let literals_b = crate::sample_literals!(CurrentNetwork, &mut rng);
520    //     let modes_a = [/* circuit::Mode::Constant, */ circuit::Mode::Public, circuit::Mode::Private];
521    //     let modes_b = [/* circuit::Mode::Constant, */ circuit::Mode::Public, circuit::Mode::Private];
522    //
523    //     literals_a.par_iter().for_each(|literal_a| {
524    //         for literal_b in &literals_b {
525    //             for mode_a in &modes_a {
526    //                 for mode_b in &modes_b {
527    //                     if literal_a.to_type() != literal_b.to_type() {
528    //                         // Check the operation fails.
529    //                         check_finalize_fails(opcode, literal_a, literal_b, mode_a, mode_b);
530    //                     }
531    //                 }
532    //             }
533    //         }
534    //     });
535    // }
536
537    #[test]
538    fn test_parse() {
539        let expected = "async foo r0 r1 into r3";
540        let (string, async_) = Async::<CurrentNetwork>::parse(expected).unwrap();
541        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
542        assert_eq!(expected, async_.to_string(), "Display.fmt() did not match expected: '{string}'");
543        assert_eq!(async_.operands.len(), 2, "The number of operands is incorrect");
544        assert_eq!(async_.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
545        assert_eq!(async_.operands[1], Operand::Register(Register::Locator(1)), "The second operand is incorrect");
546        assert_eq!(async_.destination, Register::Locator(3), "The destination is incorrect");
547    }
548}