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}