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