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 value_types.is_empty() {
365                            if !values.is_empty() {
366                                return Err("Returned non-empty data for zero vector element size"
367                                    .to_string());
368                            }
369                        } else if values.len() % value_types.len() != 0 {
370                            return Err(
371                                "Returned data does not match vector element size".to_string()
372                            );
373                        }
374                        // Set the size in the size address.
375                        // Note that unlike `pointer`, we don't treat `size` as a pointer here, even though it is;
376                        // instead we expect the post-call codegen will copy it to the heap.
377                        self.memory.write(*size_addr, assert_u32(values.len()).into());
378                        self.write_values_to_memory(*pointer, values, value_types)?;
379                    } else {
380                        // This should have been rejected by the frontend.
381                        unreachable!("deflattening heap vectors from foreign calls");
382                    }
383                }
384                _ => {
385                    return Err(format!(
386                        "Unexpected value type {value_type:?} for destination {destination:?}"
387                    ));
388                }
389            }
390        }
391
392        self.foreign_call_results[foreign_call_index].values = values;
393
394        Ok(())
395    }
396
397    /// Write a single numeric value to the destination address, ensuring that the bit size matches the expectation.
398    fn write_value_to_memory(
399        &mut self,
400        destination: MemoryAddress,
401        value: &F,
402        value_bit_size: BitSize,
403    ) -> Result<(), String> {
404        let memory_value = MemoryValue::new_checked(*value, value_bit_size);
405
406        if let Some(memory_value) = memory_value {
407            self.memory.write(destination, memory_value);
408        } else {
409            return Err(format!(
410                "Foreign call result value {value} does not fit in bit size {value_bit_size:?}"
411            ));
412        }
413        Ok(())
414    }
415
416    /// Write the `values` of an array or vector to an address stored under the `pointer`.
417    fn write_values_to_memory(
418        &mut self,
419        pointer: MemoryAddress,
420        values: &[F],
421        value_types: &[HeapValueType],
422    ) -> Result<(), String> {
423        let bit_sizes_iterator = value_types
424            .iter()
425            .map(|typ| match typ {
426                HeapValueType::Simple(bit_size) => *bit_size,
427                _ => unreachable!("Expected simple value type"),
428            })
429            .cycle();
430
431        // Convert the destination pointer to an address.
432        let destination = self.memory.read_ref(pointer);
433
434        // Write to the destination memory.
435        let memory_values: Option<Vec<_>> = values
436            .iter()
437            .zip(bit_sizes_iterator)
438            .map(|(value, bit_size)| MemoryValue::new_checked(*value, bit_size))
439            .collect();
440        if let Some(memory_values) = memory_values {
441            self.memory.write_slice(destination, &memory_values);
442        } else {
443            return Err(format!(
444                "Foreign call result values {values:?} do not match expected bit sizes",
445            ));
446        }
447        Ok(())
448    }
449
450    /// Writes flattened values to memory, using the provided type.
451    ///
452    /// The method calls itself recursively in order to work with recursive types (nested arrays).
453    /// `values_idx` is the current index in the `values` vector and is incremented every time
454    /// a value is written to memory.
455    fn write_flattened_values_to_memory(
456        &mut self,
457        destination: MemoryAddress,
458        values: &Vec<F>,
459        values_idx: &mut usize,
460        value_type: &HeapValueType,
461    ) -> Result<(), String> {
462        assert!(
463            destination.is_direct(),
464            "write_flattened_values_to_memory requires direct addresses"
465        );
466        match value_type {
467            HeapValueType::Simple(bit_size) => {
468                self.write_value_to_memory(destination, &values[*values_idx], *bit_size)?;
469                *values_idx += 1;
470                Ok(())
471            }
472            HeapValueType::Array { value_types, size } => {
473                let mut current_pointer = destination;
474                let size = size.0;
475                for _ in 0..size {
476                    for typ in value_types {
477                        match typ {
478                            HeapValueType::Simple(bit_size) => {
479                                self.write_value_to_memory(
480                                    current_pointer,
481                                    &values[*values_idx],
482                                    *bit_size,
483                                )?;
484                                *values_idx += 1;
485                                current_pointer = current_pointer.offset(1);
486                            }
487                            HeapValueType::Array { .. } => {
488                                // The next memory destination is an array, somewhere else in memory where the pointer points to.
489                                let destination =
490                                    ArrayAddress::from(self.memory.read_ref(current_pointer));
491
492                                self.write_flattened_values_to_memory(
493                                    destination.items_start(),
494                                    values,
495                                    values_idx,
496                                    typ,
497                                )?;
498
499                                // Move on to the next slot in *this* array.
500                                current_pointer = current_pointer.offset(1);
501                            }
502                            HeapValueType::Vector { .. } => {
503                                return Err(format!(
504                                    "Unsupported returned type in foreign calls {typ:?}"
505                                ));
506                            }
507                        }
508                    }
509                }
510                Ok(())
511            }
512            HeapValueType::Vector { .. } => {
513                Err(format!("Unsupported returned type in foreign calls {value_type:?}"))
514            }
515        }
516    }
517}
518
519/// Returns the total number of field elements required to represent the elements in the vector in memory.
520///
521/// Panics if the vector contains nested vectors. Such types are not supported and are rejected by the frontend.
522fn vector_flattened_length(
523    value_types: &[HeapValueType],
524    length: SemanticLength,
525) -> FlattenedLength {
526    let elements_flattened_length: FlattenedLength = value_types
527        .iter()
528        .map(|typ| {
529            typ.flattened_size()
530                .unwrap_or_else(|| panic!("unexpected nested dynamic element type: {typ:?}"))
531        })
532        .sum();
533    ElementsFlattenedLength::from(elements_flattened_length) * length
534}