Skip to content

Commit eda4a01

Browse files
committed
feature: add MastForest::advice_map and load it before the execution
When merging forests, merge their advice maps and return error on key collision.
1 parent 65a3060 commit eda4a01

File tree

17 files changed

+218
-24
lines changed

17 files changed

+218
-24
lines changed

core/src/advice/map.rs

+10
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ impl AdviceMap {
4141
pub fn remove(&mut self, key: RpoDigest) -> Option<Vec<Felt>> {
4242
self.0.remove(&key)
4343
}
44+
45+
/// Returns the number of key value pairs in the advice map.
46+
pub fn len(&self) -> usize {
47+
self.0.len()
48+
}
49+
50+
/// Returns true if the advice map is empty.
51+
pub fn is_empty(&self) -> bool {
52+
self.0.is_empty()
53+
}
4454
}
4555

4656
impl From<BTreeMap<RpoDigest, Vec<Felt>>> for AdviceMap {

core/src/mast/merger/mod.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,11 @@ impl MastForestMerger {
6565
///
6666
/// It does this in three steps:
6767
///
68-
/// 1. Merge all decorators, which is a case of deduplication and creating a decorator id
68+
/// 1. Merge all advice maps, checking for key collisions.
69+
/// 2. Merge all decorators, which is a case of deduplication and creating a decorator id
6970
/// mapping which contains how existing [`DecoratorId`]s map to [`DecoratorId`]s in the
7071
/// merged forest.
71-
/// 2. Merge all nodes of forests.
72+
/// 3. Merge all nodes of forests.
7273
/// - Similar to decorators, node indices might move during merging, so the merger keeps a
7374
/// node id mapping as it merges nodes.
7475
/// - This is a depth-first traversal over all forests to ensure all children are processed
@@ -90,10 +91,13 @@ impl MastForestMerger {
9091
/// `replacement` node. Now we can simply add a mapping from the external node to the
9192
/// `replacement` node in our node id mapping which means all nodes that referenced the
9293
/// external node will point to the `replacement` instead.
93-
/// 3. Finally, we merge all roots of all forests. Here we map the existing root indices to
94+
/// 4. Finally, we merge all roots of all forests. Here we map the existing root indices to
9495
/// their potentially new indices in the merged forest and add them to the forest,
9596
/// deduplicating in the process, too.
9697
fn merge_inner(&mut self, forests: Vec<&MastForest>) -> Result<(), MastForestError> {
98+
for other_forest in forests.iter() {
99+
self.merge_advice_map(other_forest)?;
100+
}
97101
for other_forest in forests.iter() {
98102
self.merge_decorators(other_forest)?;
99103
}
@@ -163,6 +167,17 @@ impl MastForestMerger {
163167
Ok(())
164168
}
165169

170+
fn merge_advice_map(&mut self, other_forest: &MastForest) -> Result<(), MastForestError> {
171+
for (key, value) in other_forest.advice_map.clone().into_iter() {
172+
if self.mast_forest.advice_map().get(&key).is_some() {
173+
return Err(MastForestError::AdviceMapKeyCollisionOnMerge(key));
174+
} else {
175+
self.mast_forest.advice_map_mut().insert(key, value.clone());
176+
}
177+
}
178+
Ok(())
179+
}
180+
166181
fn merge_node(
167182
&mut self,
168183
forest_idx: usize,

core/src/mast/merger/tests.rs

+52-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use miden_crypto::{hash::rpo::RpoDigest, ONE};
1+
use miden_crypto::{hash::rpo::RpoDigest, Felt, ONE};
22

33
use super::*;
44
use crate::{Decorator, Operation};
@@ -794,3 +794,54 @@ fn mast_forest_merge_invalid_decorator_index() {
794794
let err = MastForest::merge([&forest_a, &forest_b]).unwrap_err();
795795
assert_matches!(err, MastForestError::DecoratorIdOverflow(_, _));
796796
}
797+
798+
/// Tests that forest's advice maps are merged correctly.
799+
#[test]
800+
fn mast_forest_merge_advice_maps_merged() {
801+
let mut forest_a = MastForest::new();
802+
let id_foo = forest_a.add_node(block_foo()).unwrap();
803+
let id_call_a = forest_a.add_call(id_foo).unwrap();
804+
forest_a.make_root(id_call_a);
805+
let key_a = RpoDigest::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
806+
let value_a = vec![ONE, ONE];
807+
forest_a.advice_map_mut().insert(key_a, value_a.clone());
808+
809+
let mut forest_b = MastForest::new();
810+
let id_bar = forest_b.add_node(block_bar()).unwrap();
811+
let id_call_b = forest_b.add_call(id_bar).unwrap();
812+
forest_b.make_root(id_call_b);
813+
let key_b = RpoDigest::new([Felt::new(1), Felt::new(3), Felt::new(2), Felt::new(1)]);
814+
let value_b = vec![Felt::new(2), Felt::new(2)];
815+
forest_b.advice_map_mut().insert(key_b, value_b.clone());
816+
817+
let (merged, _root_maps) = MastForest::merge([&forest_a, &forest_b]).unwrap();
818+
819+
let merged_advice_map = merged.advice_map();
820+
assert_eq!(merged_advice_map.len(), 2);
821+
assert_eq!(merged_advice_map.get(&key_a).unwrap(), &value_a);
822+
assert_eq!(merged_advice_map.get(&key_b).unwrap(), &value_b);
823+
}
824+
825+
/// Tests that an error is returned when advice maps have a key collision.
826+
#[test]
827+
fn mast_forest_merge_advice_maps_collision() {
828+
let mut forest_a = MastForest::new();
829+
let id_foo = forest_a.add_node(block_foo()).unwrap();
830+
let id_call_a = forest_a.add_call(id_foo).unwrap();
831+
forest_a.make_root(id_call_a);
832+
let key_a = RpoDigest::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
833+
let value_a = vec![ONE, ONE];
834+
forest_a.advice_map_mut().insert(key_a, value_a.clone());
835+
836+
let mut forest_b = MastForest::new();
837+
let id_bar = forest_b.add_node(block_bar()).unwrap();
838+
let id_call_b = forest_b.add_call(id_bar).unwrap();
839+
forest_b.make_root(id_call_b);
840+
// The key collides with key_a in the forest_a.
841+
let key_b = key_a;
842+
let value_b = vec![Felt::new(2), Felt::new(2)];
843+
forest_b.advice_map_mut().insert(key_b, value_b.clone());
844+
845+
let err = MastForest::merge([&forest_a, &forest_b]).unwrap_err();
846+
assert_matches!(err, MastForestError::AdviceMapKeyCollisionOnMerge(_));
847+
}

core/src/mast/mod.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub use node::{
1616
};
1717
use winter_utils::{ByteWriter, DeserializationError, Serializable};
1818

19-
use crate::{Decorator, DecoratorList, Operation};
19+
use crate::{AdviceMap, Decorator, DecoratorList, Operation};
2020

2121
mod serialization;
2222

@@ -50,6 +50,9 @@ pub struct MastForest {
5050

5151
/// All the decorators included in the MAST forest.
5252
decorators: Vec<Decorator>,
53+
54+
/// Advice map to be loaded into the VM prior to executing procedures from this MAST forest.
55+
advice_map: AdviceMap,
5356
}
5457

5558
// ------------------------------------------------------------------------------------------------
@@ -463,6 +466,14 @@ impl MastForest {
463466
pub fn nodes(&self) -> &[MastNode] {
464467
&self.nodes
465468
}
469+
470+
pub fn advice_map(&self) -> &AdviceMap {
471+
&self.advice_map
472+
}
473+
474+
pub fn advice_map_mut(&mut self) -> &mut AdviceMap {
475+
&mut self.advice_map
476+
}
466477
}
467478

468479
impl Index<MastNodeId> for MastForest {
@@ -689,4 +700,6 @@ pub enum MastForestError {
689700
EmptyBasicBlock,
690701
#[error("decorator root of child with node id {0} is missing but required for fingerprint computation")]
691702
ChildFingerprintMissing(MastNodeId),
703+
#[error("advice map key already exists when merging forests: {0}")]
704+
AdviceMapKeyCollisionOnMerge(RpoDigest),
692705
}

core/src/mast/serialization/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ use decorator::{DecoratorDataBuilder, DecoratorInfo};
3030
use string_table::{StringTable, StringTableBuilder};
3131
use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
3232

33+
use crate::AdviceMap;
34+
3335
use super::{DecoratorId, MastForest, MastNode, MastNodeId};
3436

3537
mod decorator;
@@ -71,7 +73,7 @@ const MAGIC: &[u8; 5] = b"MAST\0";
7173
/// If future modifications are made to this format, the version should be incremented by 1. A
7274
/// version of `[255, 255, 255]` is reserved for future extensions that require extending the
7375
/// version field itself, but should be considered invalid for now.
74-
const VERSION: [u8; 3] = [0, 0, 0];
76+
const VERSION: [u8; 3] = [0, 0, 1];
7577

7678
// MAST FOREST SERIALIZATION/DESERIALIZATION
7779
// ================================================================================================
@@ -161,6 +163,7 @@ impl Serializable for MastForest {
161163
// Write "before enter" and "after exit" decorators
162164
before_enter_decorators.write_into(target);
163165
after_exit_decorators.write_into(target);
166+
self.advice_map.write_into(target);
164167
}
165168
}
166169

@@ -256,6 +259,7 @@ impl Deserializable for MastForest {
256259
let node_id = MastNodeId::from_u32_safe(node_id, &mast_forest)?;
257260
mast_forest.set_after_exit(node_id, decorator_ids);
258261
}
262+
mast_forest.advice_map = AdviceMap::read_from(source)?;
259263

260264
Ok(mast_forest)
261265
}

core/src/mast/serialization/tests.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use alloc::{string::ToString, sync::Arc};
22

3-
use miden_crypto::{hash::rpo::RpoDigest, Felt};
3+
use miden_crypto::{hash::rpo::RpoDigest, Felt, ONE};
44

55
use super::*;
66
use crate::{
@@ -435,3 +435,22 @@ fn mast_forest_invalid_node_id() {
435435
// Validate normal operations
436436
forest.add_join(first, second).unwrap();
437437
}
438+
439+
/// Test `MastForest::advice_map` serialization and deserialization.
440+
#[test]
441+
fn mast_forest_serialize_deserialize_advice_map() {
442+
let mut forest = MastForest::new();
443+
let deco0 = forest.add_decorator(Decorator::Trace(0)).unwrap();
444+
let deco1 = forest.add_decorator(Decorator::Trace(1)).unwrap();
445+
let first = forest.add_block(vec![Operation::U32add], Some(vec![(0, deco0)])).unwrap();
446+
let second = forest.add_block(vec![Operation::U32and], Some(vec![(1, deco1)])).unwrap();
447+
forest.add_join(first, second).unwrap();
448+
449+
let key = RpoDigest::new([ONE, ONE, ONE, ONE]);
450+
let value = vec![ONE, ONE];
451+
452+
forest.advice_map_mut().insert(key, value);
453+
454+
let parsed = MastForest::read_from_bytes(&forest.to_bytes()).unwrap();
455+
assert_eq!(forest.advice_map, parsed.advice_map);
456+
}

miden/benches/program_execution.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ fn program_execution(c: &mut Criterion) {
1111

1212
let stdlib = StdLibrary::default();
1313
let mut host = DefaultHost::default();
14-
host.load_mast_forest(stdlib.as_ref().mast_forest().clone());
14+
host.load_mast_forest(stdlib.as_ref().mast_forest().clone()).unwrap();
1515

1616
group.bench_function("sha256", |bench| {
1717
let source = "

miden/src/examples/blake3.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub fn get_example(n: usize) -> Example<DefaultHost<MemAdviceProvider>> {
2222
);
2323

2424
let mut host = DefaultHost::default();
25-
host.load_mast_forest(StdLibrary::default().mast_forest().clone());
25+
host.load_mast_forest(StdLibrary::default().mast_forest().clone()).unwrap();
2626

2727
let stack_inputs =
2828
StackInputs::try_from_ints(INITIAL_HASH_VALUE.iter().map(|&v| v as u64)).unwrap();

miden/src/repl/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,8 @@ fn execute(
318318
let stack_inputs = StackInputs::default();
319319
let mut host = DefaultHost::default();
320320
for library in provided_libraries {
321-
host.load_mast_forest(library.mast_forest().clone());
321+
host.load_mast_forest(library.mast_forest().clone())
322+
.map_err(|err| format!("{err}"))?;
322323
}
323324

324325
let state_iter = processor::execute_iter(&program, stack_inputs, host);

miden/src/tools/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ impl Analyze {
3838
// fetch the stack and program inputs from the arguments
3939
let stack_inputs = input_data.parse_stack_inputs().map_err(Report::msg)?;
4040
let mut host = DefaultHost::new(input_data.parse_advice_provider().map_err(Report::msg)?);
41-
host.load_mast_forest(StdLibrary::default().mast_forest().clone());
41+
host.load_mast_forest(StdLibrary::default().mast_forest().clone())
42+
.into_diagnostic()?;
4243

4344
let execution_details: ExecutionDetails = analyze(program.as_str(), stack_inputs, host)
4445
.expect("Could not retrieve execution details");

miden/tests/integration/exec.rs

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use assembly::Assembler;
2+
use miden_vm::DefaultHost;
3+
use processor::{ExecutionOptions, MastForest};
4+
use prover::{Digest, StackInputs};
5+
use vm_core::{assert_matches, Program, ONE};
6+
7+
#[test]
8+
fn advice_map_loaded_before_execution() {
9+
let source = "\
10+
begin
11+
push.1.1.1.1
12+
adv.push_mapval
13+
dropw
14+
end";
15+
16+
// compile and execute program
17+
let program_without_advice_map: Program =
18+
Assembler::default().assemble_program(source).unwrap();
19+
20+
// Test `processor::execute` fails if no advice map provided with the program
21+
let mut host = DefaultHost::default();
22+
match processor::execute(
23+
&program_without_advice_map,
24+
StackInputs::default(),
25+
&mut host,
26+
ExecutionOptions::default(),
27+
) {
28+
Ok(_) => panic!("Expected error"),
29+
Err(e) => {
30+
assert_matches!(e, prover::ExecutionError::AdviceMapKeyNotFound(_));
31+
},
32+
}
33+
34+
// Test `processor::execute` works if advice map provided with the program
35+
let mast_forest: MastForest = (**program_without_advice_map.mast_forest()).clone();
36+
37+
let key = Digest::new([ONE, ONE, ONE, ONE]);
38+
let value = vec![ONE, ONE];
39+
40+
let mut mast_forest = mast_forest.clone();
41+
mast_forest.advice_map_mut().insert(key, value);
42+
let program_with_advice_map =
43+
Program::new(mast_forest.into(), program_without_advice_map.entrypoint());
44+
45+
let mut host = DefaultHost::default();
46+
processor::execute(
47+
&program_with_advice_map,
48+
StackInputs::default(),
49+
&mut host,
50+
ExecutionOptions::default(),
51+
)
52+
.unwrap();
53+
}

miden/tests/integration/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use test_utils::{build_op_test, build_test};
44

55
mod air;
66
mod cli;
7+
mod exec;
78
mod exec_iters;
89
mod flow_control;
910
mod operations;

processor/src/errors.rs

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use super::{
2323
#[derive(Debug, Clone, PartialEq, Eq)]
2424
pub enum ExecutionError {
2525
AdviceMapKeyNotFound(Word),
26+
AdviceMapKeyAlreadyPresent(Word),
2627
AdviceStackReadFailed(RowIndex),
2728
CallerNotInSyscall,
2829
CircularExternalNode(Digest),
@@ -96,6 +97,10 @@ impl Display for ExecutionError {
9697
let hex = to_hex(Felt::elements_as_bytes(key));
9798
write!(f, "Value for key {hex} not present in the advice map")
9899
},
100+
AdviceMapKeyAlreadyPresent(key) => {
101+
let hex = to_hex(Felt::elements_as_bytes(key));
102+
write!(f, "Value for key {hex} already present in the advice map")
103+
},
99104
AdviceStackReadFailed(step) => write!(f, "Advice stack read failed at step {step}"),
100105
CallerNotInSyscall => {
101106
write!(f, "Instruction `caller` used outside of kernel context")

processor/src/host/mod.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,17 @@ where
334334
}
335335
}
336336

337-
pub fn load_mast_forest(&mut self, mast_forest: Arc<MastForest>) {
338-
self.store.insert(mast_forest)
337+
pub fn load_mast_forest(&mut self, mast_forest: Arc<MastForest>) -> Result<(), ExecutionError> {
338+
// Load the MAST's advice data into the advice provider.
339+
for (digest, values) in mast_forest.advice_map().clone().into_iter() {
340+
if self.adv_provider.get_mapped_values(&digest).is_some() {
341+
return Err(ExecutionError::AdviceMapKeyAlreadyPresent(digest.into()));
342+
} else {
343+
self.adv_provider.insert_into_map(digest.into(), values);
344+
}
345+
}
346+
self.store.insert(mast_forest);
347+
Ok(())
339348
}
340349

341350
#[cfg(any(test, feature = "testing"))]

processor/src/lib.rs

+12
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,18 @@ where
253253
return Err(ExecutionError::ProgramAlreadyExecuted);
254254
}
255255

256+
// Load the program's advice data into the advice provider
257+
for (digest, values) in program.mast_forest().advice_map().clone().into_iter() {
258+
if self.host.borrow().advice_provider().get_mapped_values(&digest).is_some() {
259+
return Err(ExecutionError::AdviceMapKeyAlreadyPresent(digest.into()));
260+
} else {
261+
self.host
262+
.borrow_mut()
263+
.advice_provider_mut()
264+
.insert_into_map(digest.into(), values);
265+
}
266+
}
267+
256268
self.execute_mast_node(program.entrypoint(), &program.mast_forest().clone())?;
257269

258270
self.stack.build_stack_outputs()

stdlib/tests/mem/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ fn test_memcopy() {
3131
assembler.assemble_program(source).expect("Failed to compile test source.");
3232

3333
let mut host = DefaultHost::default();
34-
host.load_mast_forest(stdlib.mast_forest().clone());
34+
host.load_mast_forest(stdlib.mast_forest().clone()).unwrap();
3535

3636
let mut process = Process::new(
3737
program.kernel().clone(),

0 commit comments

Comments
 (0)