Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: #5051 PR comments #3

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 140 additions & 79 deletions tooling/debugger/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,128 @@
use std::collections::BTreeMap;
use std::collections::{hash_set::Iter, HashSet};

#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct AddressMap {
// A Noir program is composed by
// `n` ACIR circuits
// |_ `m` ACIR opcodes
// |_ Acir call
// |_ Acir Brillig function invocation
// |_ `p` Brillig opcodes
//
// We need to inline such structure to a one-dimension "virtual address" space
// We define the address space as a contiguous space where all ACIR and
// Brillig opcodes from all circuits are laid out
//
anaPerezGhiglia marked this conversation as resolved.
Show resolved Hide resolved
// `addresses: Vec<Vec<usize>>``
// * The first vec is `n` sized - one element per ACIR circuit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nitpick: first -> outer

// * Each nested vec is `m` sized - one element per ACIR opcode in circuit
// * Each element is the "virtual address" of such opcode
//
// For flattening we map each ACIR circuit and ACIR opcode with a sequential address number
// We start by assigning 0 to the very first ACIR opcode and then start accumulating by
// traversing by depth-first
//
// * an ACIR call accumulates 1 (since it's a single opcode)
// * an ACIR Brillig call accumulates as many opcodes the brillig function has (`p`)
//
// As a result the flattened addresses list may have "holes".
// This holes are a side effect of the brillig function calls
anaPerezGhiglia marked this conversation as resolved.
Show resolved Hide resolved
//
addresses: Vec<Vec<usize>>,

// virtual address of the last opcode of the program
last_valid_address: usize,
}

impl AddressMap {
pub(super) fn new(
circuits: &[Circuit<FieldElement>],
unconstrained_functions: &[BrilligBytecode<FieldElement>],
) -> Self {
let mut result = Vec::with_capacity(circuits.len());
let mut circuit_address_start = 0usize;

for circuit in circuits {
let mut circuit_addresses = Vec::with_capacity(circuit.opcodes.len());
// push the starting address of the first opcode
circuit_addresses.push(circuit_address_start);

// iterate opcodes _init_ (all but last)
let last_opcode_address = circuit.opcodes.iter().take(circuit.opcodes.len() - 1).fold(
circuit_address_start,
|acc, opcode| {
let acc = acc + Self::calculate_opcode_size(opcode, unconstrained_functions);
// push the starting address of the next opcode
circuit_addresses.push(acc);
acc
},
);
// last opcode size should be next circuit address start
let last_opcode = circuit.opcodes.last().expect("There is a last opcode");
circuit_address_start = last_opcode_address
+ Self::calculate_opcode_size(last_opcode, unconstrained_functions);

result.push(circuit_addresses);
}
let last_valid_address = circuit_address_start - 1;
Self { addresses: result, last_valid_address }
anaPerezGhiglia marked this conversation as resolved.
Show resolved Hide resolved
}

fn calculate_opcode_size(
opcode: &Opcode<FieldElement>,
unconstrained_functions: &[BrilligBytecode<FieldElement>],
) -> usize {
match opcode {
Opcode::BrilligCall { id, .. } => unconstrained_functions[*id as usize].bytecode.len(),
_ => 1,
}
}

/// Returns the absolute address of the opcode at the given location.
/// Absolute here means accounting for nested Brillig opcodes in BrilligCall
/// opcodes.
pub fn debug_location_to_address(&self, location: &DebugLocation) -> usize {
let circuit_addresses = &self.addresses[location.circuit_id as usize];
match &location.opcode_location {
OpcodeLocation::Acir(acir_index) => circuit_addresses[*acir_index],
OpcodeLocation::Brillig { acir_index, brillig_index } => {
circuit_addresses[*acir_index] + *brillig_index
}
}
}

pub fn address_to_debug_location(&self, address: usize) -> Option<DebugLocation> {
if address > self.last_valid_address {
return None;
}
// We binary search if the given address is the first opcode address of each circuit id
// if is not, this means that the address itself is "contained" in the previous
// circuit indicated by `Err(insert_index)`
let circuit_id =
match self.addresses.binary_search_by(|addresses| addresses[0].cmp(&address)) {
Ok(found_index) => found_index,
// This means that the address is not in `insert_index` circuit
// because is an `Err`, so it must be included in previous circuit vec of opcodes
Err(insert_index) => insert_index - 1,
};

// We binary search among the selected `circuit_id`` list of opcodes
// If Err(insert_index) this means that the given address
// is a Brillig addresses that's contained in previous index ACIR opcode index
let opcode_location = match self.addresses[circuit_id].binary_search(&address) {
Ok(found_index) => OpcodeLocation::Acir(found_index),
Err(insert_index) => {
let acir_index = insert_index - 1;
let base_offset = self.addresses[circuit_id][acir_index];
let brillig_index = address - base_offset;
OpcodeLocation::Brillig { acir_index, brillig_index }
}
};
Some(DebugLocation { circuit_id: circuit_id as u32, opcode_location })
}
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct DebugLocation {
pub circuit_id: u32,
Expand Down Expand Up @@ -49,28 +171,23 @@
type Err = DebugLocationFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<_> = s.split(':').collect();
let error = Err(DebugLocationFromStrError::InvalidDebugLocationString(s.to_string()));

if parts.is_empty() || parts.len() > 2 {
return Err(DebugLocationFromStrError::InvalidDebugLocationString(s.to_string()));
}

fn parse_components(parts: &Vec<&str>) -> Option<DebugLocation> {
match parts.len() {
1 => {
let opcode_location = OpcodeLocation::from_str(parts[0]).ok()?;
Some(DebugLocation { circuit_id: 0, opcode_location })
}
2 => {
let circuit_id = parts[0].parse().ok()?;
let opcode_location = OpcodeLocation::from_str(parts[1]).ok()?;
Some(DebugLocation { circuit_id, opcode_location })
match parts.len() {
1 => OpcodeLocation::from_str(parts[0]).map_or(error, |opcode_location| {
Ok(DebugLocation { circuit_id: 0, opcode_location })
}),
2 => {
let first_part = parts[0].parse().ok();
let second_part = OpcodeLocation::from_str(parts[1]).ok();
if let (Some(circuit_id), Some(opcode_location)) = (first_part, second_part) {
Ok(DebugLocation { circuit_id, opcode_location })
} else {
error
}
_ => unreachable!("`DebugLocation` has too many components"),
}
_ => error,
}

parse_components(&parts)
.ok_or(DebugLocationFromStrError::InvalidDebugLocationString(s.to_string()))
}
}

Expand Down Expand Up @@ -114,7 +231,7 @@
// At the end of each vector (both outer and inner) there will be an extra
// sentinel element with the address of the next opcode. This is only to
// make bounds checking and working with binary search easier.
acir_opcode_addresses: Vec<Vec<usize>>,
acir_opcode_addresses: AddressMap,
}

impl<'a, B: BlackBoxFunctionSolver<FieldElement>> DebugContext<'a, B> {
Expand All @@ -129,7 +246,7 @@
let source_to_opcodes = build_source_to_opcode_debug_mappings(debug_artifact);
let current_circuit_id: u32 = 0;
let initial_circuit = &circuits[current_circuit_id as usize];
let acir_opcode_addresses = build_acir_opcode_addresses(circuits, unconstrained_functions);
let acir_opcode_addresses = AddressMap::new(circuits, unconstrained_functions);
Self {
acvm: ACVM::new(
blackbox_solver,
Expand Down Expand Up @@ -317,39 +434,13 @@
}

/// Returns the absolute address of the opcode at the given location.
/// Absolute here means accounting for nested Brillig opcodes in BrilligCall
/// opcodes.
pub fn debug_location_to_address(&self, location: &DebugLocation) -> usize {
let circuit_addresses = &self.acir_opcode_addresses[location.circuit_id as usize];
match &location.opcode_location {
OpcodeLocation::Acir(acir_index) => circuit_addresses[*acir_index],
OpcodeLocation::Brillig { acir_index, brillig_index } => {
circuit_addresses[*acir_index] + *brillig_index
}
}
self.acir_opcode_addresses.debug_location_to_address(location)
}

// Returns the DebugLocation associated to the given address
pub fn address_to_debug_location(&self, address: usize) -> Option<DebugLocation> {
if address >= *self.acir_opcode_addresses.last()?.first()? {
return None;
}
let circuit_id = match self
.acir_opcode_addresses
.binary_search_by(|addresses| addresses[0].cmp(&address))
{
Ok(found_index) => found_index,
Err(insert_index) => insert_index - 1,
};
let opcode_location = match self.acir_opcode_addresses[circuit_id].binary_search(&address) {
Ok(found_index) => OpcodeLocation::Acir(found_index),
Err(insert_index) => {
let acir_index = insert_index - 1;
let base_offset = self.acir_opcode_addresses[circuit_id][acir_index];
let brillig_index = address - base_offset;
OpcodeLocation::Brillig { acir_index, brillig_index }
}
};
Some(DebugLocation { circuit_id: circuit_id as u32, opcode_location })
self.acir_opcode_addresses.address_to_debug_location(address)
}

pub(super) fn render_opcode_at_location(&self, location: &DebugLocation) -> String {
Expand Down Expand Up @@ -769,36 +860,6 @@
result
}

fn build_acir_opcode_addresses(
circuits: &[Circuit<FieldElement>],
unconstrained_functions: &[BrilligBytecode<FieldElement>],
) -> Vec<Vec<usize>> {
let mut result = Vec::with_capacity(circuits.len() + 1);
let mut circuit_address_start = 0usize;
for circuit in circuits {
let mut circuit_addresses = Vec::with_capacity(circuit.opcodes.len() + 1);
// push the starting address of the first opcode
circuit_addresses.push(circuit_address_start);
circuit_address_start =
circuit.opcodes.iter().fold(circuit_address_start, |acc, opcode| {
let acc = acc
+ match opcode {
Opcode::BrilligCall { id, .. } => {
unconstrained_functions[*id as usize].bytecode.len()
}
_ => 1,
};
// push the starting address of the next opcode
circuit_addresses.push(acc);
acc
});
result.push(circuit_addresses);
}
result.push(vec![circuit_address_start]);

result
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -856,7 +917,7 @@
outputs: vec![],
predicate: None,
}];
let brillig_funcs = &vec![brillig_bytecode];

Check warning on line 920 in tooling/debugger/src/context.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (funcs)
let current_witness_index = 2;
let circuit = Circuit { current_witness_index, opcodes, ..Circuit::default() };
let circuits = &vec![circuit];
Expand All @@ -875,7 +936,7 @@
debug_artifact,
initial_witness,
foreign_call_executor,
brillig_funcs,

Check warning on line 939 in tooling/debugger/src/context.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (funcs)
);

assert_eq!(
Expand Down Expand Up @@ -994,14 +1055,14 @@

let foreign_call_executor =
Box::new(DefaultDebugForeignCallExecutor::from_artifact(true, debug_artifact));
let brillig_funcs = &vec![brillig_bytecode];

Check warning on line 1058 in tooling/debugger/src/context.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (funcs)
let mut context = DebugContext::new(
&StubbedBlackBoxSolver,
circuits,
debug_artifact,
initial_witness,
foreign_call_executor,
brillig_funcs,

Check warning on line 1065 in tooling/debugger/src/context.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (funcs)
);

// set breakpoint
Expand Down Expand Up @@ -1068,7 +1129,7 @@
};
let circuits = vec![circuit_one, circuit_two];
let debug_artifact = DebugArtifact { debug_symbols: vec![], file_map: BTreeMap::new() };
let brillig_funcs = &vec![brillig_one, brillig_two];

Check warning on line 1132 in tooling/debugger/src/context.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (funcs)

let context = DebugContext::new(
&StubbedBlackBoxSolver,
Expand Down
2 changes: 1 addition & 1 deletion tooling/debugger/src/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation};
use acvm::acir::native_types::{Witness, WitnessMap, WitnessStack};
use acvm::brillig_vm::brillig::Opcode as BrilligOpcode;
use acvm::{BlackBoxFunctionSolver, FieldElement};
use noirc_driver::CompiledProgram;
use nargo::NargoError;
use noirc_driver::CompiledProgram;

use crate::foreign_calls::DefaultDebugForeignCallExecutor;
use noirc_artifacts::debug::DebugArtifact;
Expand Down
Loading