brillig_vm/foreign_call.rs
1//! Implementation for [foreign calls][acir::brillig::Opcode::ForeignCall]
2use acir::{
3 AcirField,
4 brillig::{
5 BitSize, ForeignCallParam, HeapArray, HeapValueType, HeapVector, IntegerBitSize,
6 MemoryAddress, ValueOrArray,
7 lengths::{
8 ElementTypesLength, ElementsFlattenedLength, FlattenedLength, SemanticLength,
9 SemiFlattenedLength,
10 },
11 },
12};
13use acvm_blackbox_solver::BlackBoxFunctionSolver;
14use itertools::Itertools;
15
16use crate::{MemoryValue, VM, VMStatus, assert_u32, assert_usize, memory::ArrayAddress};
17
18impl<F: AcirField, B: BlackBoxFunctionSolver<F>> VM<'_, F, B> {
19 /// Handles the execution of a single [ForeignCall opcode][acir::brillig::Opcode::ForeignCall].
20 ///
21 /// This method performs the following steps:
22 /// 1. Checks if the foreign call results are already available. If not, it resolves the input
23 /// values from memory and pauses execution by returning `VMStatus::ForeignCallWait`.
24 /// For vectors, the preceding `u32` length field is used to truncate the vector input to its semantic length.
25 /// 2. If results are available, it writes them to memory, ensuring that the returned data
26 /// matches the expected types and sizes:
27 /// * Nested arrays are reconstructed from flat outputs when necessary.
28 /// * Nested vectors are an unsupported return type and will trigger an error.
29 /// * Vectors are written to the heap starting at the free memory pointer, and their address gets stored in the destination.
30 /// * Update free memory pointer based on how much data (if any) was written to it.
31 /// 3. Increments the foreign call counter and advances the program counter.
32 ///
33 /// # Parameters
34 /// The borrowed fields of a [ForeignCall opcode][acir::brillig::Opcode::ForeignCall].
35 /// They are listed again below:
36 /// - `function`: Name of the foreign function being called.
37 /// - `destinations`: Pointers or heap structures where the return values will be written.
38 /// - `destination_value_types`: Expected type layout for each destination.
39 /// - `inputs`: Pointers or heap structures representing the inputs for the foreign call.
40 /// - `input_value_types`: Expected type layout for each input.
41 ///
42 /// # Returns
43 /// - [VMStatus] indicating the next state of the VM:
44 /// - [VMStatus::ForeignCallWait] if the results are not yet available.
45 /// - [VMStatus::Finished] or [VMStatus::Failure] depending on whether writing the results succeeded.
46 ///
47 /// # Panics
48 /// - If `inputs` and `input_value_types` lengths do not match.
49 /// - If `destinations` and `destination_value_types` lengths do not match.
50 pub(super) fn process_foreign_call(
51 &mut self,
52 function: &str,
53 destinations: &[ValueOrArray],
54 destination_value_types: &[HeapValueType],
55 inputs: &[ValueOrArray],
56 input_value_types: &[HeapValueType],
57 ) -> &VMStatus<F> {
58 assert_eq!(inputs.len(), input_value_types.len());
59 assert_eq!(destinations.len(), destination_value_types.len());
60
61 if !self.has_unprocessed_foreign_call_result() {
62 // When this opcode is called, it is possible that the results of a foreign call are
63 // not yet known (not enough entries in `foreign_call_results`).
64 // If that is the case, just resolve the inputs and pause the VM with a status
65 // (VMStatus::ForeignCallWait) that communicates the foreign function name and
66 // resolved inputs back to the caller. Once the caller pushes to `foreign_call_results`,
67 // they can then make another call to the VM that starts at this opcode
68 // but has the necessary results to proceed with execution.
69
70 // With vectors we might have more items in the HeapVector than the semantic length
71 // indicated by the field preceding the pointer to the vector in the inputs.
72 // This happens when SSA merges vectors of different length, which can result in
73 // a vector that has room for the longer of the two, partially filled with items
74 // from the shorter. There are ways to deal with this on the receiver side,
75 // but it is cumbersome, and the cleanest solution is not to send the extra empty
76 // items at all. To do this, however, we need infer which input is the vector length.
77 let mut vector_length: Option<u32> = None;
78 let mut resolved_inputs = Vec::with_capacity(inputs.len());
79
80 for (input, input_type) in inputs.iter().zip_eq(input_value_types) {
81 let mut input = match self.get_memory_values(*input, input_type) {
82 Ok(input) => input,
83 Err(e) => return self.fail(e),
84 };
85 // Truncate vectors to their semantic length, which we remember from the preceding field.
86 match input_type {
87 HeapValueType::Simple(BitSize::Integer(IntegerBitSize::U32)) => {
88 // If we have a single u32 we may have a vector representation, so store this input.
89 // On the next iteration, if we have a vector then we know we have the dynamic length
90 // for that vector.
91 let ForeignCallParam::Single(length) = input else {
92 unreachable!("expected u32; got {input:?}");
93 };
94 vector_length = Some(length.to_u128() as u32);
95 }
96 HeapValueType::Vector { value_types } => {
97 let Some(length) = vector_length else {
98 unreachable!(
99 "ICE: expected the semantic vector length to precede a vector input"
100 );
101 };
102 // Get rid of any items beyond the flattened length.
103 let flattened_length =
104 vector_flattened_length(value_types, SemanticLength(length));
105 let ForeignCallParam::Array(fields) = &mut input else {
106 unreachable!("ICE: expected Array parameter for vector content");
107 };
108 fields.truncate(assert_usize(flattened_length.0));
109 vector_length = None;
110 }
111 _ => {
112 // Otherwise we are not dealing with a u32 followed by a vector.
113 vector_length = None;
114 }
115 }
116 resolved_inputs.push(input);
117 }
118
119 return self.wait_for_foreign_call(function.to_owned(), resolved_inputs);
120 }
121
122 let write_result = self.write_foreign_call_result(
123 destinations,
124 destination_value_types,
125 self.foreign_call_counter,
126 );
127
128 if let Err(e) = write_result {
129 return self.fail(e);
130 }
131
132 // Mark the foreign call result as processed.
133 self.foreign_call_counter += 1;
134 self.increment_program_counter()
135 }
136
137 /// Get input data from memory to pass to foreign calls.
138 fn get_memory_values(
139 &self,
140 input: ValueOrArray,
141 value_type: &HeapValueType,
142 ) -> Result<ForeignCallParam<F>, String> {
143 match (input, value_type) {
144 (ValueOrArray::MemoryAddress(value_addr), HeapValueType::Simple(_)) => {
145 Ok(ForeignCallParam::Single(self.memory.read(value_addr).to_field()))
146 }
147 (
148 ValueOrArray::HeapArray(HeapArray { pointer, size }),
149 HeapValueType::Array { value_types, size: type_size },
150 ) => {
151 // The array's semi-flattened size must match the expected size
152 let semi_flattened_size =
153 *type_size * ElementTypesLength(assert_u32(value_types.len()));
154 assert_eq!(semi_flattened_size, size);
155
156 let start = self.memory.read_ref(pointer);
157 Ok(self
158 .read_slice_of_values_from_memory(start, size, value_types)
159 .into_iter()
160 .map(|mem_value| mem_value.to_field())
161 .collect::<Vec<_>>()
162 .into())
163 }
164 (
165 ValueOrArray::HeapVector(HeapVector { pointer, size: size_addr }),
166 HeapValueType::Vector { value_types },
167 ) => {
168 let start = self.memory.read_ref(pointer);
169 let size = self.memory.read(size_addr).to_u32();
170 let size = SemiFlattenedLength(size);
171
172 // Validate that the vector size does not exceed memory bounds.
173 let start_index = assert_usize(start.unwrap_direct());
174 let size_usize = assert_usize(size.0);
175 if let Some(end) = start_index.checked_add(size_usize)
176 && end <= self.memory.len()
177 {
178 } else {
179 return Err(format!(
180 "HeapVector out of bounds: reading {} elements from address {start_index} \
181 exceeds memory size {}",
182 size.0,
183 self.memory.len()
184 ));
185 }
186
187 Ok(self
188 .read_slice_of_values_from_memory(start, size, value_types)
189 .into_iter()
190 .map(|mem_value| mem_value.to_field())
191 .collect::<Vec<_>>()
192 .into())
193 }
194 _ => {
195 unreachable!("Unexpected value type {value_type:?} for input {input:?}");
196 }
197 }
198 }
199
200 /// Reads an array/vector from memory but recursively reads pointers to
201 /// nested arrays/vectors according to the sequence of value types.
202 ///
203 /// The given `size` is the total number of `HeapValueType`s to read, which must
204 /// be a multiple of the length of `value_types` (unless `value_types.len()` is 0).
205 fn read_slice_of_values_from_memory(
206 &self,
207 start: MemoryAddress,
208 size: SemiFlattenedLength,
209 value_types: &[HeapValueType],
210 ) -> Vec<MemoryValue<F>> {
211 let size = size.0;
212
213 assert!(start.is_direct(), "read_slice_of_values_from_memory requires direct addresses");
214 if HeapValueType::all_simple(value_types) {
215 self.memory.read_slice(start, assert_usize(size)).to_vec()
216 } else {
217 // Check that the sequence of value types fit an integer number of
218 // times inside the given size.
219 assert!(
220 size.is_multiple_of(assert_u32(value_types.len())),
221 "array/vector does not contain a whole number of elements"
222 );
223
224 (0..size)
225 .zip(value_types.iter().cycle())
226 .flat_map(|(i, value_type)| {
227 let value_address = start.offset(i);
228 match value_type {
229 HeapValueType::Simple(_) => {
230 vec![self.memory.read(value_address)]
231 }
232 HeapValueType::Array { value_types, size: type_size } => {
233 let array_address =
234 ArrayAddress::from(self.memory.read_ref(value_address));
235 let semi_flattened_size =
236 *type_size * ElementTypesLength(assert_u32(value_types.len()));
237
238 self.read_slice_of_values_from_memory(
239 array_address.items_start(),
240 semi_flattened_size,
241 value_types,
242 )
243 }
244 HeapValueType::Vector { .. } => {
245 unreachable!("Vectors nested in arrays/vectors are not supported");
246 }
247 }
248 })
249 .collect::<Vec<_>>()
250 }
251 }
252
253 /// Sets the status of the VM to `ForeignCallWait`.
254 /// Indicating that the VM is now waiting for a foreign call to be resolved.
255 fn wait_for_foreign_call(
256 &mut self,
257 function: String,
258 inputs: Vec<ForeignCallParam<F>>,
259 ) -> &VMStatus<F> {
260 self.status(VMStatus::ForeignCallWait { function, inputs })
261 }
262
263 /// Write a foreign call's results to the VM memory.
264 ///
265 /// We match the expected types with the actual results.
266 /// However foreign call results do not support nested structures:
267 /// They are either a single integer value or a vector of integer values (field elements).
268 /// Therefore, nested arrays returned from foreign call results are flattened.
269 /// If the expected array sizes do not match the actual size, we reconstruct the nested
270 /// structure from the flat output array.
271 fn write_foreign_call_result(
272 &mut self,
273 destinations: &[ValueOrArray],
274 destination_value_types: &[HeapValueType],
275 foreign_call_index: usize,
276 ) -> Result<(), String> {
277 // Take ownership of values to allow calling mutating methods on self.
278 let values = std::mem::take(&mut self.foreign_call_results[foreign_call_index].values);
279
280 if destinations.len() != values.len() {
281 return Err(format!(
282 "{} output values were provided as a foreign call result for {} destination slots",
283 values.len(),
284 destinations.len()
285 ));
286 }
287
288 assert_eq!(
289 destinations.len(),
290 destination_value_types.len(),
291 "Number of destinations must match number of value types",
292 );
293
294 for ((destination, value_type), output) in
295 destinations.iter().zip_eq(destination_value_types).zip_eq(&values)
296 {
297 match (destination, value_type) {
298 (ValueOrArray::MemoryAddress(value_addr), HeapValueType::Simple(bit_size)) => {
299 let output_fields = output.fields();
300 if value_type.flattened_size().is_some_and(|flattened_size| {
301 FlattenedLength(assert_u32(output_fields.len())) != flattened_size
302 }) {
303 return Err(format!(
304 "Foreign call return value does not match expected size. Expected {} but got {}",
305 value_type.flattened_size().unwrap(),
306 output_fields.len(),
307 ));
308 }
309
310 match output {
311 ForeignCallParam::Single(value) => {
312 self.write_value_to_memory(*value_addr, value, *bit_size)?;
313 }
314 ForeignCallParam::Array(_) => {
315 return Err(format!(
316 "Function result size does not match brillig bytecode. Expected 1 result but got {output:?}"
317 ));
318 }
319 }
320 }
321 (
322 ValueOrArray::HeapArray(HeapArray { pointer, size }),
323 HeapValueType::Array { value_types, size: type_size },
324 ) => {
325 if *type_size * ElementTypesLength(assert_u32(value_types.len())) != *size {
326 return Err(format!(
327 "Destination array size of {size} does not match the type size of {type_size}"
328 ));
329 }
330 let output_fields = output.fields();
331 if value_type.flattened_size().is_some_and(|flattened_size| {
332 FlattenedLength(assert_u32(output_fields.len())) != flattened_size
333 }) {
334 return Err(format!(
335 "Foreign call return value does not match expected size. Expected {} but got {}",
336 value_type.flattened_size().unwrap(),
337 output_fields.len(),
338 ));
339 }
340
341 if HeapValueType::all_simple(value_types) {
342 let ForeignCallParam::Array(values) = output else {
343 return Err("Foreign call returned a single value for an array type"
344 .to_string());
345 };
346 assert_eq!(
347 values.len(),
348 size.0 as usize,
349 "Expected values length to be equal to heap array size",
350 );
351 self.write_values_to_memory(*pointer, values, value_types)?;
352 } else {
353 // foreign call returning flattened values into a nested type, so the sizes do not match
354 let destination = self.memory.read_ref(*pointer);
355 let mut flatten_values_idx = 0; //index of values read from flatten_values
356 self.write_flattened_values_to_memory(
357 destination,
358 &output_fields,
359 &mut flatten_values_idx,
360 value_type,
361 )?;
362 assert_eq!(
363 flatten_values_idx,
364 output_fields.len(),
365 "Not all values were written to memory"
366 );
367 }
368 }
369 // We didn't know the length of vectors when we allocated the destination variable, so we pointed
370 // the vector at the start of the free memory; with this technique we can only handle a single vector.
371 // Write the data where the destination points at. It is up to the follow up bytecode to initialize
372 // the meta-data, or move the data somewhere else.
373 (
374 ValueOrArray::HeapVector(HeapVector { pointer, size: size_addr }),
375 HeapValueType::Vector { value_types },
376 ) => {
377 if HeapValueType::all_simple(value_types) {
378 let ForeignCallParam::Array(values) = output else {
379 return Err("Foreign call returned a single value for an vector type"
380 .to_string());
381 };
382 if value_types.is_empty() {
383 if !values.is_empty() {
384 return Err("Returned non-empty data for zero vector element size"
385 .to_string());
386 }
387 } else if values.len() % value_types.len() != 0 {
388 return Err(
389 "Returned data does not match vector element size".to_string()
390 );
391 }
392 // Set the size in the size address.
393 // Note that unlike `pointer`, we don't treat `size` as a pointer here, even though it is;
394 // instead we expect the post-call codegen will copy it to the heap.
395 self.memory.write(*size_addr, assert_u32(values.len()).into());
396 self.write_values_to_memory(*pointer, values, value_types)?;
397 } else {
398 // This should have been rejected by the frontend.
399 unreachable!("deflattening heap vectors from foreign calls");
400 }
401 }
402 _ => {
403 return Err(format!(
404 "Unexpected value type {value_type:?} for destination {destination:?}"
405 ));
406 }
407 }
408 }
409
410 self.foreign_call_results[foreign_call_index].values = values;
411
412 Ok(())
413 }
414
415 /// Write a single numeric value to the destination address, ensuring that the bit size matches the expectation.
416 fn write_value_to_memory(
417 &mut self,
418 destination: MemoryAddress,
419 value: &F,
420 value_bit_size: BitSize,
421 ) -> Result<(), String> {
422 let memory_value = MemoryValue::new_checked(*value, value_bit_size);
423
424 if let Some(memory_value) = memory_value {
425 self.memory.write(destination, memory_value);
426 } else {
427 return Err(format!(
428 "Foreign call result value {value} does not fit in bit size {value_bit_size:?}"
429 ));
430 }
431 Ok(())
432 }
433
434 /// Write the `values` of an array or vector to an address stored under the `pointer`.
435 fn write_values_to_memory(
436 &mut self,
437 pointer: MemoryAddress,
438 values: &[F],
439 value_types: &[HeapValueType],
440 ) -> Result<(), String> {
441 let bit_sizes_iterator = value_types
442 .iter()
443 .map(|typ| match typ {
444 HeapValueType::Simple(bit_size) => *bit_size,
445 _ => unreachable!("Expected simple value type"),
446 })
447 .cycle();
448
449 // Convert the destination pointer to an address.
450 let destination = self.memory.read_ref(pointer);
451
452 // Write to the destination memory.
453 let memory_values: Option<Vec<_>> = values
454 .iter()
455 .zip(bit_sizes_iterator)
456 .map(|(value, bit_size)| MemoryValue::new_checked(*value, bit_size))
457 .collect();
458 if let Some(memory_values) = memory_values {
459 self.memory.write_slice(destination, &memory_values);
460 } else {
461 return Err(format!(
462 "Foreign call result values {values:?} do not match expected bit sizes",
463 ));
464 }
465 Ok(())
466 }
467
468 /// Writes flattened values to memory, using the provided type.
469 ///
470 /// The method calls itself recursively in order to work with recursive types (nested arrays).
471 /// `values_idx` is the current index in the `values` vector and is incremented every time
472 /// a value is written to memory.
473 fn write_flattened_values_to_memory(
474 &mut self,
475 destination: MemoryAddress,
476 values: &Vec<F>,
477 values_idx: &mut usize,
478 value_type: &HeapValueType,
479 ) -> Result<(), String> {
480 assert!(
481 destination.is_direct(),
482 "write_flattened_values_to_memory requires direct addresses"
483 );
484 match value_type {
485 HeapValueType::Simple(bit_size) => {
486 self.write_value_to_memory(destination, &values[*values_idx], *bit_size)?;
487 *values_idx += 1;
488 Ok(())
489 }
490 HeapValueType::Array { value_types, size } => {
491 let mut current_pointer = destination;
492 let size = size.0;
493 for _ in 0..size {
494 for typ in value_types {
495 match typ {
496 HeapValueType::Simple(bit_size) => {
497 self.write_value_to_memory(
498 current_pointer,
499 &values[*values_idx],
500 *bit_size,
501 )?;
502 *values_idx += 1;
503 current_pointer = current_pointer.offset(1);
504 }
505 HeapValueType::Array { .. } => {
506 // The next memory destination is an array, somewhere else in memory where the pointer points to.
507 let destination =
508 ArrayAddress::from(self.memory.read_ref(current_pointer));
509
510 self.write_flattened_values_to_memory(
511 destination.items_start(),
512 values,
513 values_idx,
514 typ,
515 )?;
516
517 // Move on to the next slot in *this* array.
518 current_pointer = current_pointer.offset(1);
519 }
520 HeapValueType::Vector { .. } => {
521 return Err(format!(
522 "Unsupported returned type in foreign calls {typ:?}"
523 ));
524 }
525 }
526 }
527 }
528 Ok(())
529 }
530 HeapValueType::Vector { .. } => {
531 Err(format!("Unsupported returned type in foreign calls {value_type:?}"))
532 }
533 }
534 }
535}
536
537/// Returns the total number of field elements required to represent the elements in the vector in memory.
538///
539/// Panics if the vector contains nested vectors. Such types are not supported and are rejected by the frontend.
540fn vector_flattened_length(
541 value_types: &[HeapValueType],
542 length: SemanticLength,
543) -> FlattenedLength {
544 let elements_flattened_length: FlattenedLength = value_types
545 .iter()
546 .map(|typ| {
547 typ.flattened_size()
548 .unwrap_or_else(|| panic!("unexpected nested dynamic element type: {typ:?}"))
549 })
550 .sum();
551 ElementsFlattenedLength::from(elements_flattened_length) * length
552}