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}