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
79            let resolved_inputs = inputs
80                .iter()
81                .zip_eq(input_value_types)
82                .map(|(input, input_type)| {
83                    let mut input = self.get_memory_values(*input, input_type);
84                    // Truncate vectors to their semantic length, which we remember from the preceding field.
85                    match input_type {
86                        HeapValueType::Simple(BitSize::Integer(IntegerBitSize::U32)) => {
87                            // If we have a single u32 we may have a vector representation, so store this input.
88                            // On the next iteration, if we have a vector then we know we have the dynamic length
89                            // for that vector.
90                            let ForeignCallParam::Single(length) = input else {
91                                unreachable!("expected u32; got {input:?}");
92                            };
93                            vector_length = Some(length.to_u128() as u32);
94                        }
95                        HeapValueType::Vector { value_types } => {
96                            let Some(length) = vector_length else {
97                                unreachable!(
98                                    "ICE: expected the semantic vector length to precede a vector input"
99                                );
100                            };
101                            // Get rid of any items beyond the flattened length.
102                            let flattened_length =
103                                vector_flattened_length(value_types, SemanticLength(length));
104                            let ForeignCallParam::Array(fields) = &mut input else {
105                                unreachable!("ICE: expected Array parameter for vector content");
106                            };
107                            fields.truncate(assert_usize(flattened_length.0));
108                            vector_length = None;
109                        }
110                        _ => {
111                            // Otherwise we are not dealing with a u32 followed by a vector.
112                            vector_length = None;
113                        }
114                    }
115                    input
116                })
117                .collect::<Vec<_>>();
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    ) -> ForeignCallParam<F> {
143        match (input, value_type) {
144            (ValueOrArray::MemoryAddress(value_addr), HeapValueType::Simple(_)) => {
145                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                self.read_slice_of_values_from_memory(start, size, value_types)
158                    .into_iter()
159                    .map(|mem_value| mem_value.to_field())
160                    .collect::<Vec<_>>()
161                    .into()
162            }
163            (
164                ValueOrArray::HeapVector(HeapVector { pointer, size: size_addr }),
165                HeapValueType::Vector { value_types },
166            ) => {
167                let start = self.memory.read_ref(pointer);
168                let size = self.memory.read(size_addr).to_u32();
169                let size = SemiFlattenedLength(size);
170                self.read_slice_of_values_from_memory(start, size, value_types)
171                    .into_iter()
172                    .map(|mem_value| mem_value.to_field())
173                    .collect::<Vec<_>>()
174                    .into()
175            }
176            _ => {
177                unreachable!("Unexpected value type {value_type:?} for input {input:?}");
178            }
179        }
180    }
181
182    /// Reads an array/vector from memory but recursively reads pointers to
183    /// nested arrays/vectors according to the sequence of value types.
184    ///
185    /// The given `size` is the total number of `HeapValueType`s to read, which must
186    /// be a multiple of the length of `value_types` (unless `value_types.len()` is 0).
187    fn read_slice_of_values_from_memory(
188        &self,
189        start: MemoryAddress,
190        size: SemiFlattenedLength,
191        value_types: &[HeapValueType],
192    ) -> Vec<MemoryValue<F>> {
193        let size = size.0;
194
195        assert!(start.is_direct(), "read_slice_of_values_from_memory requires direct addresses");
196        if HeapValueType::all_simple(value_types) {
197            self.memory.read_slice(start, assert_usize(size)).to_vec()
198        } else {
199            // Check that the sequence of value types fit an integer number of
200            // times inside the given size.
201            assert!(
202                size.is_multiple_of(assert_u32(value_types.len())),
203                "array/vector does not contain a whole number of elements"
204            );
205
206            (0..size)
207                .zip(value_types.iter().cycle())
208                .flat_map(|(i, value_type)| {
209                    let value_address = start.offset(i);
210                    match value_type {
211                        HeapValueType::Simple(_) => {
212                            vec![self.memory.read(value_address)]
213                        }
214                        HeapValueType::Array { value_types, size: type_size } => {
215                            let array_address =
216                                ArrayAddress::from(self.memory.read_ref(value_address));
217                            let semi_flattened_size =
218                                *type_size * ElementTypesLength(assert_u32(value_types.len()));
219
220                            self.read_slice_of_values_from_memory(
221                                array_address.items_start(),
222                                semi_flattened_size,
223                                value_types,
224                            )
225                        }
226                        HeapValueType::Vector { .. } => {
227                            unreachable!("Vectors nested in arrays/vectors are not supported");
228                        }
229                    }
230                })
231                .collect::<Vec<_>>()
232        }
233    }
234
235    /// Sets the status of the VM to `ForeignCallWait`.
236    /// Indicating that the VM is now waiting for a foreign call to be resolved.
237    fn wait_for_foreign_call(
238        &mut self,
239        function: String,
240        inputs: Vec<ForeignCallParam<F>>,
241    ) -> &VMStatus<F> {
242        self.status(VMStatus::ForeignCallWait { function, inputs })
243    }
244
245    /// Write a foreign call's results to the VM memory.
246    ///
247    /// We match the expected types with the actual results.
248    /// However foreign call results do not support nested structures:
249    /// They are either a single integer value or a vector of integer values (field elements).
250    /// Therefore, nested arrays returned from foreign call results are flattened.
251    /// If the expected array sizes do not match the actual size, we reconstruct the nested
252    /// structure from the flat output array.
253    fn write_foreign_call_result(
254        &mut self,
255        destinations: &[ValueOrArray],
256        destination_value_types: &[HeapValueType],
257        foreign_call_index: usize,
258    ) -> Result<(), String> {
259        // Take ownership of values to allow calling mutating methods on self.
260        let values = std::mem::take(&mut self.foreign_call_results[foreign_call_index].values);
261
262        if destinations.len() != values.len() {
263            return Err(format!(
264                "{} output values were provided as a foreign call result for {} destination slots",
265                values.len(),
266                destinations.len()
267            ));
268        }
269
270        assert_eq!(
271            destinations.len(),
272            destination_value_types.len(),
273            "Number of destinations must match number of value types",
274        );
275
276        for ((destination, value_type), output) in
277            destinations.iter().zip_eq(destination_value_types).zip_eq(&values)
278        {
279            match (destination, value_type) {
280                (ValueOrArray::MemoryAddress(value_addr), HeapValueType::Simple(bit_size)) => {
281                    let output_fields = output.fields();
282                    if value_type.flattened_size().is_some_and(|flattened_size| {
283                        FlattenedLength(assert_u32(output_fields.len())) != flattened_size
284                    }) {
285                        return Err(format!(
286                            "Foreign call return value does not match expected size. Expected {} but got {}",
287                            value_type.flattened_size().unwrap(),
288                            output_fields.len(),
289                        ));
290                    }
291
292                    match output {
293                        ForeignCallParam::Single(value) => {
294                            self.write_value_to_memory(*value_addr, value, *bit_size)?;
295                        }
296                        ForeignCallParam::Array(_) => {
297                            return Err(format!(
298                                "Function result size does not match brillig bytecode. Expected 1 result but got {output:?}"
299                            ));
300                        }
301                    }
302                }
303                (
304                    ValueOrArray::HeapArray(HeapArray { pointer, size }),
305                    HeapValueType::Array { value_types, size: type_size },
306                ) => {
307                    if *type_size * ElementTypesLength(assert_u32(value_types.len())) != *size {
308                        return Err(format!(
309                            "Destination array size of {size} does not match the type size of {type_size}"
310                        ));
311                    }
312                    let output_fields = output.fields();
313                    if value_type.flattened_size().is_some_and(|flattened_size| {
314                        FlattenedLength(assert_u32(output_fields.len())) != flattened_size
315                    }) {
316                        return Err(format!(
317                            "Foreign call return value does not match expected size. Expected {} but got {}",
318                            value_type.flattened_size().unwrap(),
319                            output_fields.len(),
320                        ));
321                    }
322
323                    if HeapValueType::all_simple(value_types) {
324                        let ForeignCallParam::Array(values) = output else {
325                            return Err("Foreign call returned a single value for an array type"
326                                .to_string());
327                        };
328                        assert_eq!(
329                            values.len(),
330                            size.0 as usize,
331                            "Expected values length to be equal to heap array size",
332                        );
333                        self.write_values_to_memory(*pointer, values, value_types)?;
334                    } else {
335                        // foreign call returning flattened values into a nested type, so the sizes do not match
336                        let destination = self.memory.read_ref(*pointer);
337                        let mut flatten_values_idx = 0; //index of values read from flatten_values
338                        self.write_flattened_values_to_memory(
339                            destination,
340                            &output_fields,
341                            &mut flatten_values_idx,
342                            value_type,
343                        )?;
344                        assert_eq!(
345                            flatten_values_idx,
346                            output_fields.len(),
347                            "Not all values were written to memory"
348                        );
349                    }
350                }
351                // We didn't know the length of vectors when we allocated the destination variable, so we pointed
352                // the vector at the start of the free memory; with this technique we can only handle a single vector.
353                // Write the data where the destination points at. It is up to the follow up bytecode to initialize
354                // the meta-data, or move the data somewhere else.
355                (
356                    ValueOrArray::HeapVector(HeapVector { pointer, size: size_addr }),
357                    HeapValueType::Vector { value_types },
358                ) => {
359                    if HeapValueType::all_simple(value_types) {
360                        let ForeignCallParam::Array(values) = output else {
361                            return Err("Foreign call returned a single value for an vector type"
362                                .to_string());
363                        };
364                        if values.len() % value_types.len() != 0 {
365                            return Err(
366                                "Returned data does not match vector element size".to_string()
367                            );
368                        }
369                        // Set the size in the size address.
370                        // Note that unlike `pointer`, we don't treat `size` as a pointer here, even though it is;
371                        // instead we expect the post-call codegen will copy it to the heap.
372                        self.memory.write(*size_addr, assert_u32(values.len()).into());
373                        self.write_values_to_memory(*pointer, values, value_types)?;
374                    } else {
375                        unreachable!("deflattening heap vectors from foreign calls");
376                    }
377                }
378                _ => {
379                    return Err(format!(
380                        "Unexpected value type {value_type:?} for destination {destination:?}"
381                    ));
382                }
383            }
384        }
385
386        self.foreign_call_results[foreign_call_index].values = values;
387
388        Ok(())
389    }
390
391    /// Write a single numeric value to the destination address, ensuring that the bit size matches the expectation.
392    fn write_value_to_memory(
393        &mut self,
394        destination: MemoryAddress,
395        value: &F,
396        value_bit_size: BitSize,
397    ) -> Result<(), String> {
398        let memory_value = MemoryValue::new_checked(*value, value_bit_size);
399
400        if let Some(memory_value) = memory_value {
401            self.memory.write(destination, memory_value);
402        } else {
403            return Err(format!(
404                "Foreign call result value {value} does not fit in bit size {value_bit_size:?}"
405            ));
406        }
407        Ok(())
408    }
409
410    /// Write the `values` of an array or vector to an address stored under the `pointer`.
411    fn write_values_to_memory(
412        &mut self,
413        pointer: MemoryAddress,
414        values: &[F],
415        value_types: &[HeapValueType],
416    ) -> Result<(), String> {
417        let bit_sizes_iterator = value_types
418            .iter()
419            .map(|typ| match typ {
420                HeapValueType::Simple(bit_size) => *bit_size,
421                _ => unreachable!("Expected simple value type"),
422            })
423            .cycle();
424
425        // Convert the destination pointer to an address.
426        let destination = self.memory.read_ref(pointer);
427
428        // Write to the destination memory.
429        let memory_values: Option<Vec<_>> = values
430            .iter()
431            .zip(bit_sizes_iterator)
432            .map(|(value, bit_size)| MemoryValue::new_checked(*value, bit_size))
433            .collect();
434        if let Some(memory_values) = memory_values {
435            self.memory.write_slice(destination, &memory_values);
436        } else {
437            return Err(format!(
438                "Foreign call result values {values:?} do not match expected bit sizes",
439            ));
440        }
441        Ok(())
442    }
443
444    /// Writes flattened values to memory, using the provided type.
445    ///
446    /// The method calls itself recursively in order to work with recursive types (nested arrays).
447    /// `values_idx` is the current index in the `values` vector and is incremented every time
448    /// a value is written to memory.
449    fn write_flattened_values_to_memory(
450        &mut self,
451        destination: MemoryAddress,
452        values: &Vec<F>,
453        values_idx: &mut usize,
454        value_type: &HeapValueType,
455    ) -> Result<(), String> {
456        assert!(
457            destination.is_direct(),
458            "write_flattened_values_to_memory requires direct addresses"
459        );
460        match value_type {
461            HeapValueType::Simple(bit_size) => {
462                self.write_value_to_memory(destination, &values[*values_idx], *bit_size)?;
463                *values_idx += 1;
464                Ok(())
465            }
466            HeapValueType::Array { value_types, size } => {
467                let mut current_pointer = destination;
468                let size = size.0;
469                for _ in 0..size {
470                    for typ in value_types {
471                        match typ {
472                            HeapValueType::Simple(bit_size) => {
473                                self.write_value_to_memory(
474                                    current_pointer,
475                                    &values[*values_idx],
476                                    *bit_size,
477                                )?;
478                                *values_idx += 1;
479                                current_pointer = current_pointer.offset(1);
480                            }
481                            HeapValueType::Array { .. } => {
482                                // The next memory destination is an array, somewhere else in memory where the pointer points to.
483                                let destination =
484                                    ArrayAddress::from(self.memory.read_ref(current_pointer));
485
486                                self.write_flattened_values_to_memory(
487                                    destination.items_start(),
488                                    values,
489                                    values_idx,
490                                    typ,
491                                )?;
492
493                                // Move on to the next slot in *this* array.
494                                current_pointer = current_pointer.offset(1);
495                            }
496                            HeapValueType::Vector { .. } => {
497                                return Err(format!(
498                                    "Unsupported returned type in foreign calls {typ:?}"
499                                ));
500                            }
501                        }
502                    }
503                }
504                Ok(())
505            }
506            HeapValueType::Vector { .. } => {
507                Err(format!("Unsupported returned type in foreign calls {value_type:?}"))
508            }
509        }
510    }
511}
512
513/// Returns the total number of field elements required to represent the elements in the vector in memory.
514///
515/// Panics if the vector contains nested vectors. Such types are not supported and are rejected by the frontend.
516fn vector_flattened_length(
517    value_types: &[HeapValueType],
518    length: SemanticLength,
519) -> FlattenedLength {
520    let elements_flattened_length: FlattenedLength = value_types
521        .iter()
522        .map(|typ| {
523            typ.flattened_size()
524                .unwrap_or_else(|| panic!("unexpected nested dynamic element type: {typ:?}"))
525        })
526        .sum();
527    ElementsFlattenedLength::from(elements_flattened_length) * length
528}