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}