1#![forbid(unsafe_code)]
2#![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))]
3
4use acir::AcirField;
24use acir::brillig::{
25 BinaryFieldOp, BinaryIntOp, ForeignCallParam, ForeignCallResult, IntegerBitSize, MemoryAddress,
26 Opcode,
27};
28use acvm_blackbox_solver::BlackBoxFunctionSolver;
29use arithmetic::{BrilligArithmeticError, evaluate_binary_field_op, evaluate_binary_int_op};
30use black_box::evaluate_black_box;
31
32pub use acir::brillig;
34use memory::MemoryTypeError;
35pub use memory::{
36 FREE_MEMORY_POINTER_ADDRESS, MEMORY_ADDRESSING_BIT_SIZE, Memory, MemoryValue,
37 STACK_POINTER_ADDRESS, offsets,
38};
39
40pub use crate::fuzzing::BranchToFeatureMap;
41use crate::fuzzing::FuzzingTrace;
42
43mod arithmetic;
44mod black_box;
45mod cast;
46mod foreign_call;
47pub mod fuzzing;
48mod memory;
49
50fn assert_usize(value: u32) -> usize {
52 value.try_into().expect("Failed conversion from u32 to usize")
53}
54
55fn assert_u32(value: usize) -> u32 {
57 value.try_into().expect("Failed conversion from usize to u32")
58}
59
60pub type ErrorCallStack = Vec<usize>;
62
63#[derive(Debug, PartialEq, Eq, Clone)]
65pub enum FailureReason {
66 Trap {
71 revert_data_offset: u32,
73 revert_data_size: u32,
75 },
76 RuntimeError { message: String },
80}
81
82#[derive(Debug, PartialEq, Eq, Clone)]
84pub enum VMStatus<F> {
85 Finished {
88 return_data_offset: u32,
90 return_data_size: u32,
92 },
93 InProgress,
96 Failure {
98 reason: FailureReason,
100 call_stack: ErrorCallStack,
102 },
103 ForeignCallWait {
110 function: String,
112 inputs: Vec<ForeignCallParam<F>>,
115 },
116}
117
118pub type OpcodePosition = usize;
120
121pub type NextOpcodePositionOrState = usize;
124
125#[derive(Debug, PartialEq, Eq, Clone)]
127pub struct BrilligProfilingSample {
128 pub call_stack: Vec<usize>,
130}
131
132pub type BrilligProfilingSamples = Vec<BrilligProfilingSample>;
134
135#[derive(Debug, PartialEq, Eq, Clone)]
136pub struct VM<'a, F, B: BlackBoxFunctionSolver<F>> {
138 calldata: Vec<F>,
140 program_counter: usize,
142 foreign_call_counter: usize,
151 foreign_call_results: Vec<ForeignCallResult<F>>,
154 bytecode: &'a [Opcode<F>],
156 status: VMStatus<F>,
158 memory: Memory<F>,
160 call_stack: Vec<usize>,
162 black_box_solver: &'a B,
164 profiling_active: bool,
166 profiling_samples: BrilligProfilingSamples,
168
169 fuzzing_trace: Option<FuzzingTrace>,
172}
173
174impl<'a, F: AcirField, B: BlackBoxFunctionSolver<F>> VM<'a, F, B> {
175 pub fn new(
177 calldata: Vec<F>,
178 bytecode: &'a [Opcode<F>],
179 black_box_solver: &'a B,
180 profiling_active: bool,
181 with_branch_to_feature_map: Option<&BranchToFeatureMap>,
182 ) -> Self {
183 let fuzzing_trace = with_branch_to_feature_map.cloned().map(FuzzingTrace::new);
184
185 Self {
186 calldata,
187 program_counter: 0,
188 foreign_call_counter: 0,
189 foreign_call_results: Vec::new(),
190 bytecode,
191 status: VMStatus::InProgress,
192 memory: Memory::default(),
193 call_stack: Vec::new(),
194 black_box_solver,
195 profiling_active,
196 profiling_samples: Vec::with_capacity(bytecode.len()),
197 fuzzing_trace,
198 }
199 }
200
201 pub fn is_profiling_active(&self) -> bool {
202 self.profiling_active
203 }
204
205 pub fn is_fuzzing_active(&self) -> bool {
206 self.fuzzing_trace.is_some()
207 }
208
209 pub fn take_profiling_samples(&mut self) -> BrilligProfilingSamples {
210 std::mem::take(&mut self.profiling_samples)
211 }
212
213 fn status(&mut self, status: VMStatus<F>) -> &VMStatus<F> {
216 self.status = status;
217 &self.status
218 }
219
220 pub fn get_status(&self) -> VMStatus<F> {
221 self.status.clone()
222 }
223
224 fn finish(&mut self, return_data_offset: u32, return_data_size: u32) -> &VMStatus<F> {
226 self.status(VMStatus::Finished { return_data_offset, return_data_size })
227 }
228
229 fn has_unprocessed_foreign_call_result(&self) -> bool {
231 self.foreign_call_counter < self.foreign_call_results.len()
232 }
233
234 pub fn resolve_foreign_call(&mut self, foreign_call_result: ForeignCallResult<F>) {
237 if self.has_unprocessed_foreign_call_result() {
238 panic!("No unresolved foreign calls; the previous results haven't been processed yet");
239 }
240 self.foreign_call_results.push(foreign_call_result);
241 self.status(VMStatus::InProgress);
242 }
243
244 fn trap(&mut self, revert_data_offset: u32, revert_data_size: u32) -> &VMStatus<F> {
247 self.status(VMStatus::Failure {
248 call_stack: self.get_call_stack(),
249 reason: FailureReason::Trap { revert_data_offset, revert_data_size },
250 })
251 }
252
253 fn fail(&mut self, message: String) -> &VMStatus<F> {
256 self.status(VMStatus::Failure {
257 call_stack: self.get_call_stack(),
258 reason: FailureReason::RuntimeError { message },
259 })
260 }
261
262 pub fn process_opcodes(&mut self) -> VMStatus<F> {
265 while !matches!(
266 self.process_opcode(),
267 VMStatus::Finished { .. } | VMStatus::Failure { .. } | VMStatus::ForeignCallWait { .. }
268 ) {}
269 self.status.clone()
270 }
271
272 pub fn get_memory(&self) -> &[MemoryValue<F>] {
276 self.memory.values()
277 }
278
279 pub fn take_memory(mut self) -> Memory<F> {
283 std::mem::take(&mut self.memory)
284 }
285
286 pub fn foreign_call_counter(&self) -> usize {
287 self.foreign_call_counter
288 }
289
290 pub fn write_memory_at(&mut self, ptr: u32, value: MemoryValue<F>) {
294 self.memory.write(MemoryAddress::direct(ptr), value);
295 }
296
297 pub fn get_call_stack(&self) -> Vec<usize> {
300 let mut call_stack = self.get_call_stack_no_current_counter();
301 call_stack.push(self.program_counter);
302 call_stack
303 }
304
305 pub fn get_call_stack_no_current_counter(&self) -> Vec<usize> {
309 self.call_stack.clone()
310 }
311
312 pub fn process_opcode(&mut self) -> &VMStatus<F> {
314 if self.profiling_active {
315 let call_stack: Vec<usize> = self.get_call_stack();
316 self.profiling_samples.push(BrilligProfilingSample { call_stack });
317 }
318
319 self.process_opcode_internal()
320 }
321
322 pub fn get_fuzzing_trace(&self) -> Vec<u32> {
323 self.fuzzing_trace.as_ref().map(|trace| trace.get_trace()).unwrap_or_default()
324 }
325
326 fn process_opcode_internal(&mut self) -> &VMStatus<F> {
338 let opcode = &self.bytecode[self.program_counter];
339 match opcode {
340 Opcode::BinaryFieldOp { op, lhs, rhs, destination: result } => {
341 if let Err(error) = self.process_binary_field_op(*op, *lhs, *rhs, *result) {
342 self.fail(error.to_string())
343 } else {
344 self.increment_program_counter()
345 }
346 }
347 Opcode::BinaryIntOp { op, bit_size, lhs, rhs, destination: result } => {
348 match self.process_free_memory_op(*op, *bit_size, *lhs, *rhs, *result) {
349 Err(error) => return self.fail(error),
350 Ok(true) => return self.increment_program_counter(),
351 Ok(false) => {
352 }
354 }
355 if let Err(error) = self.process_binary_int_op(*op, *bit_size, *lhs, *rhs, *result)
356 {
357 self.fail(error.to_string())
358 } else {
359 self.increment_program_counter()
360 }
361 }
362 Opcode::Not { destination, source, bit_size } => {
363 if let Err(error) = self.process_not(*source, *destination, *bit_size) {
364 self.fail(error.to_string())
365 } else {
366 self.increment_program_counter()
367 }
368 }
369 Opcode::Cast { destination, source, bit_size } => {
370 let source_value = self.memory.read(*source);
371 let casted_value = cast::cast(source_value, *bit_size);
372 self.memory.write(*destination, casted_value);
373 self.increment_program_counter()
374 }
375 Opcode::Jump { location: destination } => self.set_program_counter(*destination),
376 Opcode::JumpIf { condition, location: destination } => {
377 let condition_value = self.memory.read(*condition);
380 let condition_value = match condition_value.expect_u1() {
381 Err(error) => {
382 return self.fail(format!("condition value is not a boolean: {error}"));
383 }
384 Ok(cond) => cond,
385 };
386 if condition_value {
387 self.fuzzing_trace_branching(*destination);
388 self.set_program_counter(*destination)
389 } else {
390 self.fuzzing_trace_branching(self.program_counter + 1);
391 self.increment_program_counter()
392 }
393 }
394 Opcode::CalldataCopy { destination_address, size_address, offset_address } => {
395 let size = assert_usize(self.memory.read(*size_address).to_u32());
396 let offset = assert_usize(self.memory.read(*offset_address).to_u32());
397 let end = if let Some(end) = offset.checked_add(size)
398 && end <= self.calldata.len()
399 {
400 end
401 } else {
402 return self.fail(format!(
403 "CalldataCopy out of bounds: offset {offset} + size {size} \
404 exceeds calldata length {}",
405 self.calldata.len()
406 ));
407 };
408 let values: Vec<_> = self.calldata[offset..end]
409 .iter()
410 .map(|value| MemoryValue::new_field(*value))
411 .collect();
412 self.memory.write_slice(*destination_address, &values);
413 self.increment_program_counter()
414 }
415 Opcode::Return => {
416 if let Some(return_location) = self.call_stack.pop() {
417 self.set_program_counter(return_location + 1)
418 } else {
419 self.fail("return opcode hit, but callstack already empty".to_string())
420 }
421 }
422 Opcode::ForeignCall {
423 function,
424 destinations,
425 destination_value_types,
426 inputs,
427 input_value_types,
428 } => self.process_foreign_call(
429 function,
430 destinations,
431 destination_value_types,
432 inputs,
433 input_value_types,
434 ),
435 Opcode::Mov { destination: destination_address, source: source_address } => {
436 let source_value = self.memory.read(*source_address);
437 self.memory.write(*destination_address, source_value);
438 self.increment_program_counter()
439 }
440 Opcode::ConditionalMov { destination, source_a, source_b, condition } => {
441 let condition_value = self.memory.read(*condition);
442
443 let condition_value = match condition_value.expect_u1() {
444 Err(error) => {
445 return self.fail(format!("condition value is not a boolean: {error}"));
446 }
447 Ok(cond) => cond,
448 };
449 if condition_value {
450 self.memory.write(*destination, self.memory.read(*source_a));
451 } else {
452 self.memory.write(*destination, self.memory.read(*source_b));
453 }
454 self.fuzzing_trace_conditional_mov(condition_value);
455 self.increment_program_counter()
456 }
457 Opcode::Trap { revert_data } => {
458 let revert_data_size = self.memory.read(revert_data.size).to_u32();
459 if revert_data_size > 0 {
460 self.trap(
461 self.memory.read_ref(revert_data.pointer).unwrap_direct(),
462 revert_data_size,
463 )
464 } else {
465 self.trap(0, 0)
466 }
467 }
468 Opcode::Stop { return_data } => {
469 let return_data_size = self.memory.read(return_data.size).to_u32();
470 if return_data_size > 0 {
471 self.finish(
472 self.memory.read_ref(return_data.pointer).unwrap_direct(),
473 return_data_size,
474 )
475 } else {
476 self.finish(0, 0)
477 }
478 }
479 Opcode::Load { destination, source_pointer } => {
480 let source = self.memory.read_ref(*source_pointer);
482 let value = self.memory.read(source);
484 self.memory.write(*destination, value);
485 self.increment_program_counter()
486 }
487 Opcode::Store { destination_pointer, source: source_address } => {
488 let destination = self.memory.read_ref(*destination_pointer);
490 let value = self.memory.read(*source_address);
492 self.memory.write(destination, value);
494 self.increment_program_counter()
495 }
496 Opcode::Call { location } => {
497 self.call_stack.push(self.program_counter);
499 self.set_program_counter(*location)
500 }
501 Opcode::Const { destination, value, bit_size } => {
502 self.memory.write(*destination, MemoryValue::new_from_field(*value, *bit_size));
504 self.increment_program_counter()
505 }
506 Opcode::IndirectConst { destination_pointer, bit_size, value } => {
507 let destination = self.memory.read_ref(*destination_pointer);
509 self.memory.write(destination, MemoryValue::new_from_field(*value, *bit_size));
511 self.increment_program_counter()
512 }
513 Opcode::BlackBox(black_box_op) => {
514 if let Err(e) =
515 evaluate_black_box(black_box_op, self.black_box_solver, &mut self.memory)
516 {
517 self.fail(e.to_string())
518 } else {
519 self.increment_program_counter()
520 }
521 }
522 }
523 }
524
525 pub fn program_counter(&self) -> usize {
527 self.program_counter
528 }
529
530 fn increment_program_counter(&mut self) -> &VMStatus<F> {
532 self.set_program_counter(self.program_counter + 1)
533 }
534
535 fn set_program_counter(&mut self, value: usize) -> &VMStatus<F> {
539 assert!(self.program_counter < self.bytecode.len());
540 self.program_counter = value;
541 if self.program_counter >= self.bytecode.len() {
542 self.status = VMStatus::Finished { return_data_offset: 0, return_data_size: 0 };
543 }
544 &self.status
545 }
546
547 fn process_binary_field_op(
550 &mut self,
551 op: BinaryFieldOp,
552 lhs: MemoryAddress,
553 rhs: MemoryAddress,
554 result: MemoryAddress,
555 ) -> Result<(), BrilligArithmeticError> {
556 let lhs_value = self.memory.read(lhs);
557 let rhs_value = self.memory.read(rhs);
558
559 let result_value = evaluate_binary_field_op(&op, lhs_value, rhs_value)?;
560 self.memory.write(result, result_value);
561 self.fuzzing_trace_binary_field_op_comparison(&op, lhs_value, rhs_value, result_value);
562 Ok(())
563 }
564
565 fn process_binary_int_op(
568 &mut self,
569 op: BinaryIntOp,
570 bit_size: IntegerBitSize,
571 lhs: MemoryAddress,
572 rhs: MemoryAddress,
573 result: MemoryAddress,
574 ) -> Result<(), BrilligArithmeticError> {
575 let lhs_value = self.memory.read(lhs);
576 let rhs_value = self.memory.read(rhs);
577
578 let result_value = evaluate_binary_int_op(&op, lhs_value, rhs_value, bit_size)?;
579 self.memory.write(result, result_value);
580 self.fuzzing_trace_binary_int_op_comparison(&op, lhs_value, rhs_value, result_value);
581 Ok(())
582 }
583
584 fn process_free_memory_op(
608 &mut self,
609 op: BinaryIntOp,
610 bit_size: IntegerBitSize,
611 lhs: MemoryAddress,
612 rhs: MemoryAddress,
613 result: MemoryAddress,
614 ) -> Result<bool, String> {
615 if result != FREE_MEMORY_POINTER_ADDRESS
616 || op != BinaryIntOp::Add
617 || bit_size != MEMORY_ADDRESSING_BIT_SIZE
618 {
619 return Ok(false);
620 }
621
622 let lhs_value = self.memory.read(lhs);
623 let rhs_value = self.memory.read(rhs);
624
625 let MemoryValue::U32(lhs_value) = lhs_value else {
626 return Ok(false);
627 };
628 let MemoryValue::U32(rhs_value) = rhs_value else {
629 return Ok(false);
630 };
631 let Some(result_value) = lhs_value.checked_add(rhs_value) else {
632 return Err("Out of memory".to_string());
633 };
634
635 self.memory.write(result, result_value.into());
636
637 Ok(true)
638 }
639
640 fn process_not(
645 &mut self,
646 source: MemoryAddress,
647 destination: MemoryAddress,
648 op_bit_size: IntegerBitSize,
649 ) -> Result<(), MemoryTypeError> {
650 let value = self.memory.read(source);
651
652 let negated_value = match op_bit_size {
653 IntegerBitSize::U1 => MemoryValue::U1(!value.expect_u1()?),
654 IntegerBitSize::U8 => MemoryValue::U8(!value.expect_u8()?),
655 IntegerBitSize::U16 => MemoryValue::U16(!value.expect_u16()?),
656 IntegerBitSize::U32 => MemoryValue::U32(!value.expect_u32()?),
657 IntegerBitSize::U64 => MemoryValue::U64(!value.expect_u64()?),
658 IntegerBitSize::U128 => MemoryValue::U128(!value.expect_u128()?),
659 };
660 self.memory.write(destination, negated_value);
661 Ok(())
662 }
663}