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}