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}