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 values: Vec<_> = self.calldata[offset..(offset + size)]
398 .iter()
399 .map(|value| MemoryValue::new_field(*value))
400 .collect();
401 self.memory.write_slice(*destination_address, &values);
402 self.increment_program_counter()
403 }
404 Opcode::Return => {
405 if let Some(return_location) = self.call_stack.pop() {
406 self.set_program_counter(return_location + 1)
407 } else {
408 self.fail("return opcode hit, but callstack already empty".to_string())
409 }
410 }
411 Opcode::ForeignCall {
412 function,
413 destinations,
414 destination_value_types,
415 inputs,
416 input_value_types,
417 } => self.process_foreign_call(
418 function,
419 destinations,
420 destination_value_types,
421 inputs,
422 input_value_types,
423 ),
424 Opcode::Mov { destination: destination_address, source: source_address } => {
425 let source_value = self.memory.read(*source_address);
426 self.memory.write(*destination_address, source_value);
427 self.increment_program_counter()
428 }
429 Opcode::ConditionalMov { destination, source_a, source_b, condition } => {
430 let condition_value = self.memory.read(*condition);
431
432 let condition_value = match condition_value.expect_u1() {
433 Err(error) => {
434 return self.fail(format!("condition value is not a boolean: {error}"));
435 }
436 Ok(cond) => cond,
437 };
438 if condition_value {
439 self.memory.write(*destination, self.memory.read(*source_a));
440 } else {
441 self.memory.write(*destination, self.memory.read(*source_b));
442 }
443 self.fuzzing_trace_conditional_mov(condition_value);
444 self.increment_program_counter()
445 }
446 Opcode::Trap { revert_data } => {
447 let revert_data_size = self.memory.read(revert_data.size).to_u32();
448 if revert_data_size > 0 {
449 self.trap(
450 self.memory.read_ref(revert_data.pointer).unwrap_direct(),
451 revert_data_size,
452 )
453 } else {
454 self.trap(0, 0)
455 }
456 }
457 Opcode::Stop { return_data } => {
458 let return_data_size = self.memory.read(return_data.size).to_u32();
459 if return_data_size > 0 {
460 self.finish(
461 self.memory.read_ref(return_data.pointer).unwrap_direct(),
462 return_data_size,
463 )
464 } else {
465 self.finish(0, 0)
466 }
467 }
468 Opcode::Load { destination, source_pointer } => {
469 let source = self.memory.read_ref(*source_pointer);
471 let value = self.memory.read(source);
473 self.memory.write(*destination, value);
474 self.increment_program_counter()
475 }
476 Opcode::Store { destination_pointer, source: source_address } => {
477 let destination = self.memory.read_ref(*destination_pointer);
479 let value = self.memory.read(*source_address);
481 self.memory.write(destination, value);
483 self.increment_program_counter()
484 }
485 Opcode::Call { location } => {
486 self.call_stack.push(self.program_counter);
488 self.set_program_counter(*location)
489 }
490 Opcode::Const { destination, value, bit_size } => {
491 self.memory.write(*destination, MemoryValue::new_from_field(*value, *bit_size));
493 self.increment_program_counter()
494 }
495 Opcode::IndirectConst { destination_pointer, bit_size, value } => {
496 let destination = self.memory.read_ref(*destination_pointer);
498 self.memory.write(destination, MemoryValue::new_from_field(*value, *bit_size));
500 self.increment_program_counter()
501 }
502 Opcode::BlackBox(black_box_op) => {
503 if let Err(e) =
504 evaluate_black_box(black_box_op, self.black_box_solver, &mut self.memory)
505 {
506 self.fail(e.to_string())
507 } else {
508 self.increment_program_counter()
509 }
510 }
511 }
512 }
513
514 pub fn program_counter(&self) -> usize {
516 self.program_counter
517 }
518
519 fn increment_program_counter(&mut self) -> &VMStatus<F> {
521 self.set_program_counter(self.program_counter + 1)
522 }
523
524 fn set_program_counter(&mut self, value: usize) -> &VMStatus<F> {
528 assert!(self.program_counter < self.bytecode.len());
529 self.program_counter = value;
530 if self.program_counter >= self.bytecode.len() {
531 self.status = VMStatus::Finished { return_data_offset: 0, return_data_size: 0 };
532 }
533 &self.status
534 }
535
536 fn process_binary_field_op(
539 &mut self,
540 op: BinaryFieldOp,
541 lhs: MemoryAddress,
542 rhs: MemoryAddress,
543 result: MemoryAddress,
544 ) -> Result<(), BrilligArithmeticError> {
545 let lhs_value = self.memory.read(lhs);
546 let rhs_value = self.memory.read(rhs);
547
548 let result_value = evaluate_binary_field_op(&op, lhs_value, rhs_value)?;
549 self.memory.write(result, result_value);
550 self.fuzzing_trace_binary_field_op_comparison(&op, lhs_value, rhs_value, result_value);
551 Ok(())
552 }
553
554 fn process_binary_int_op(
557 &mut self,
558 op: BinaryIntOp,
559 bit_size: IntegerBitSize,
560 lhs: MemoryAddress,
561 rhs: MemoryAddress,
562 result: MemoryAddress,
563 ) -> Result<(), BrilligArithmeticError> {
564 let lhs_value = self.memory.read(lhs);
565 let rhs_value = self.memory.read(rhs);
566
567 let result_value = evaluate_binary_int_op(&op, lhs_value, rhs_value, bit_size)?;
568 self.memory.write(result, result_value);
569 self.fuzzing_trace_binary_int_op_comparison(&op, lhs_value, rhs_value, result_value);
570 Ok(())
571 }
572
573 fn process_free_memory_op(
597 &mut self,
598 op: BinaryIntOp,
599 bit_size: IntegerBitSize,
600 lhs: MemoryAddress,
601 rhs: MemoryAddress,
602 result: MemoryAddress,
603 ) -> Result<bool, String> {
604 if result != FREE_MEMORY_POINTER_ADDRESS
605 || op != BinaryIntOp::Add
606 || bit_size != MEMORY_ADDRESSING_BIT_SIZE
607 {
608 return Ok(false);
609 }
610
611 let lhs_value = self.memory.read(lhs);
612 let rhs_value = self.memory.read(rhs);
613
614 let MemoryValue::U32(lhs_value) = lhs_value else {
615 return Ok(false);
616 };
617 let MemoryValue::U32(rhs_value) = rhs_value else {
618 return Ok(false);
619 };
620 let Some(result_value) = lhs_value.checked_add(rhs_value) else {
621 return Err("Out of memory".to_string());
622 };
623
624 self.memory.write(result, result_value.into());
625
626 Ok(true)
627 }
628
629 fn process_not(
634 &mut self,
635 source: MemoryAddress,
636 destination: MemoryAddress,
637 op_bit_size: IntegerBitSize,
638 ) -> Result<(), MemoryTypeError> {
639 let value = self.memory.read(source);
640
641 let negated_value = match op_bit_size {
642 IntegerBitSize::U1 => MemoryValue::U1(!value.expect_u1()?),
643 IntegerBitSize::U8 => MemoryValue::U8(!value.expect_u8()?),
644 IntegerBitSize::U16 => MemoryValue::U16(!value.expect_u16()?),
645 IntegerBitSize::U32 => MemoryValue::U32(!value.expect_u32()?),
646 IntegerBitSize::U64 => MemoryValue::U64(!value.expect_u64()?),
647 IntegerBitSize::U128 => MemoryValue::U128(!value.expect_u128()?),
648 };
649 self.memory.write(destination, negated_value);
650 Ok(())
651 }
652}