Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#### Enhancements

- Added `procedure_names` to `DebugInfo` for storing procedure name mappings by MAST root digest, enabling debuggers to resolve human-readable procedure names during execution (#[2474](https://github.com/0xMiden/miden-vm/pull/2474)).

#### Changes

- Added `--kernel` flag to CLI commands (`run`, `prove`, `verify`, `debug`) to allow loading custom kernels from `.masm` or `.masp` files ([#2363](https://github.com/0xMiden/miden-vm/pull/2363)).
Expand Down
52 changes: 49 additions & 3 deletions core/src/mast/debuginfo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ use miden_utils_indexing::{Idx, IndexVec};
use serde::{Deserialize, Serialize};

use super::{Decorator, DecoratorId, MastForestError, MastNodeId};
use crate::{LexicographicWord, Word};

mod decorator_storage;
pub use decorator_storage::{
Expand Down Expand Up @@ -74,6 +75,10 @@ pub struct DebugInfo {

/// Maps error codes to error messages.
error_codes: BTreeMap<u64, Arc<str>>,

/// Maps MAST root digests to procedure names for debugging purposes.
#[cfg_attr(feature = "serde", serde(skip))]
procedure_names: BTreeMap<LexicographicWord, Arc<str>>,
}

impl DebugInfo {
Expand All @@ -87,6 +92,7 @@ impl DebugInfo {
op_decorator_storage: OpToDecoratorIds::new(),
node_decorator_storage: NodeToDecoratorIds::new(),
error_codes: BTreeMap::new(),
procedure_names: BTreeMap::new(),
}
}

Expand All @@ -106,6 +112,7 @@ impl DebugInfo {
),
node_decorator_storage: NodeToDecoratorIds::with_capacity(nodes_capacity, 0, 0),
error_codes: BTreeMap::new(),
procedure_names: BTreeMap::new(),
}
}

Expand All @@ -125,24 +132,26 @@ impl DebugInfo {
op_decorator_storage,
node_decorator_storage: NodeToDecoratorIds::new(),
error_codes: BTreeMap::new(),
procedure_names: BTreeMap::new(),
}
}

// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------

/// Returns true if this [DebugInfo] has no decorators or error codes.
/// Returns true if this [DebugInfo] has no decorators, error codes, or procedure names.
pub fn is_empty(&self) -> bool {
self.decorators.is_empty() && self.error_codes.is_empty()
self.decorators.is_empty() && self.error_codes.is_empty() && self.procedure_names.is_empty()
}

/// Strips all debug information, removing decorators and error codes.
/// Strips all debug information, removing decorators, error codes, and procedure names.
///
/// This is used for release builds where debug info is not needed.
pub fn clear(&mut self) {
self.clear_mappings();
self.decorators = IndexVec::new();
self.error_codes.clear();
self.procedure_names.clear();
}

// DECORATOR ACCESSORS
Expand Down Expand Up @@ -278,6 +287,43 @@ impl DebugInfo {
self.error_codes.clear();
}

// PROCEDURE NAME METHODS
// --------------------------------------------------------------------------------------------

/// Returns the procedure name for the given MAST root digest, if present.
pub fn procedure_name(&self, digest: &Word) -> Option<&str> {
self.procedure_names.get(&LexicographicWord::from(*digest)).map(|s| s.as_ref())
}

/// Returns an iterator over all (digest, name) pairs.
pub fn procedure_names(&self) -> impl Iterator<Item = (Word, &Arc<str>)> {
self.procedure_names.iter().map(|(key, name)| (key.into_inner(), name))
}

/// Returns the number of procedure names.
pub fn num_procedure_names(&self) -> usize {
self.procedure_names.len()
}

/// Inserts a procedure name for the given MAST root digest.
pub fn insert_procedure_name(&mut self, digest: Word, name: Arc<str>) {
self.procedure_names.insert(LexicographicWord::from(digest), name);
}

/// Inserts multiple procedure names at once.
pub fn extend_procedure_names<I>(&mut self, names: I)
where
I: IntoIterator<Item = (Word, Arc<str>)>,
{
self.procedure_names
.extend(names.into_iter().map(|(d, n)| (LexicographicWord::from(d), n)));
}

/// Clears all procedure names.
pub fn clear_procedure_names(&mut self) {
self.procedure_names.clear();
}

// TEST HELPERS
// --------------------------------------------------------------------------------------------

Expand Down
23 changes: 23 additions & 0 deletions core/src/mast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,29 @@ impl MastForest {
}
}

// ------------------------------------------------------------------------------------------------
/// Procedure name methods
impl MastForest {
/// Returns the procedure name for the given MAST root digest, if present.
pub fn procedure_name(&self, digest: &Word) -> Option<&str> {
self.debug_info.procedure_name(digest)
}

/// Returns an iterator over all (digest, name) pairs of procedure names.
pub fn procedure_names(&self) -> impl Iterator<Item = (Word, &Arc<str>)> {
self.debug_info.procedure_names()
}

/// Inserts a procedure name for the given MAST root digest.
pub fn insert_procedure_name(&mut self, digest: Word, name: Arc<str>) {
assert!(
self.find_procedure_root(digest).is_some(),
"attempted to insert procedure name for digest that is not a procedure root"
);
self.debug_info.insert_procedure_name(digest, name);
}
}

// TEST HELPERS
// ================================================================================================

Expand Down
27 changes: 27 additions & 0 deletions core/src/mast/serialization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
//! (error_codes map section)
//! - Error codes map (BTreeMap<u64, String>)
//!
//! (procedure_names map section)
//! - Procedure names map (BTreeMap<Word, String>)
//!
//! (decorator data section)
//! - Decorator data
//! - String table
Expand Down Expand Up @@ -171,6 +174,11 @@ impl Serializable for MastForest {
self.debug_info.error_codes().map(|(k, v)| (*k, v.to_string())).collect();
error_codes.write_into(target);

// Write procedure names
let procedure_names: BTreeMap<crate::Word, String> =
self.debug_info.procedure_names().map(|(k, v)| (k, v.to_string())).collect();
procedure_names.write_into(target);

// write all decorator data below

let mut decorator_data_builder = DecoratorDataBuilder::new();
Expand Down Expand Up @@ -220,6 +228,11 @@ impl Deserializable for MastForest {
let error_codes: BTreeMap<u64, Arc<str>> =
error_codes.into_iter().map(|(k, v)| (k, Arc::from(v))).collect();

// Reading procedure names
let procedure_names: BTreeMap<crate::Word, String> = Deserializable::read_from(source)?;
let procedure_names: BTreeMap<crate::Word, Arc<str>> =
procedure_names.into_iter().map(|(k, v)| (k, Arc::from(v))).collect();

// Reading Decorators
let decorator_data: Vec<u8> = Deserializable::read_from(source)?;
let string_table: StringTable = Deserializable::read_from(source)?;
Expand Down Expand Up @@ -303,6 +316,20 @@ impl Deserializable for MastForest {
.debug_info
.extend_error_codes(error_codes.iter().map(|(k, v)| (*k, v.clone())));

mast_forest.debug_info.clear_procedure_names();

// Validate that all procedure name digests correspond to procedure roots in the forest
for digest in procedure_names.keys() {
if mast_forest.find_procedure_root(*digest).is_none() {
return Err(DeserializationError::InvalidValue(format!(
"procedure name references digest that is not a procedure root: {:?}",
digest
)));
}
}

mast_forest.debug_info.extend_procedure_names(procedure_names);

Ok(mast_forest)
}
}
Expand Down
62 changes: 62 additions & 0 deletions core/src/mast/serialization/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -589,3 +589,65 @@ fn mast_forest_deserialize_invalid_ops_offset_fails() {
let result = MastForest::read_from_bytes(&corrupted);
assert_matches!(result, Err(DeserializationError::InvalidValue(_)));
}

#[test]
fn mast_forest_serialize_deserialize_procedure_names() {
let mut forest = MastForest::new();

let block_id = BasicBlockNodeBuilder::new(vec![Operation::Add, Operation::Mul], Vec::new())
.add_to_forest(&mut forest)
.unwrap();
forest.make_root(block_id);

let digest = forest[block_id].digest();
forest.insert_procedure_name(digest, "test_procedure".into());

assert_eq!(forest.procedure_name(&digest), Some("test_procedure"));
assert_eq!(forest.debug_info.num_procedure_names(), 1);

let serialized = forest.to_bytes();
let deserialized = MastForest::read_from_bytes(&serialized).unwrap();

assert_eq!(deserialized.procedure_name(&digest), Some("test_procedure"));
assert_eq!(deserialized.debug_info.num_procedure_names(), 1);
assert_eq!(forest, deserialized);
}

#[test]
fn mast_forest_serialize_deserialize_multiple_procedure_names() {
let mut forest = MastForest::new();

let block1_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new())
.add_to_forest(&mut forest)
.unwrap();
let block2_id = BasicBlockNodeBuilder::new(vec![Operation::Mul], Vec::new())
.add_to_forest(&mut forest)
.unwrap();
let block3_id = BasicBlockNodeBuilder::new(vec![Operation::U32sub], Vec::new())
.add_to_forest(&mut forest)
.unwrap();

forest.make_root(block1_id);
forest.make_root(block2_id);
forest.make_root(block3_id);

let digest1 = forest[block1_id].digest();
let digest2 = forest[block2_id].digest();
let digest3 = forest[block3_id].digest();

forest.insert_procedure_name(digest1, "proc_add".into());
forest.insert_procedure_name(digest2, "proc_mul".into());
forest.insert_procedure_name(digest3, "proc_sub".into());

assert_eq!(forest.debug_info.num_procedure_names(), 3);

let serialized = forest.to_bytes();
let deserialized = MastForest::read_from_bytes(&serialized).unwrap();

assert_eq!(deserialized.procedure_name(&digest1), Some("proc_add"));
assert_eq!(deserialized.procedure_name(&digest2), Some("proc_mul"));
assert_eq!(deserialized.procedure_name(&digest3), Some("proc_sub"));
assert_eq!(deserialized.debug_info.num_procedure_names(), 3);

assert_eq!(forest, deserialized);
}
12 changes: 12 additions & 0 deletions crates/mast-package/src/package/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,16 @@ impl Package {
)))
}
}

/// Returns the procedure name for the given MAST root digest, if present.
///
/// This allows debuggers to resolve human-readable procedure names during execution.
pub fn procedure_name(&self, digest: &Word) -> Option<&str> {
self.mast.mast_forest().procedure_name(digest)
}

/// Returns an iterator over all (digest, name) pairs of procedure names.
pub fn procedure_names(&self) -> impl Iterator<Item = (Word, &Arc<str>)> {
self.mast.mast_forest().procedure_names()
}
}
Loading