use miden_assembly::Assembler;
use miden_processor::{
DefaultHost, ExecutionOptions, FastProcessor, Felt, StackInputs,
advice::AdviceInputs,
trace::{DEFAULT_MAX_PROVER_MEMORY_BYTES, build_trace},
};
use miden_utils_testing::crypto::{MerkleTree, init_merkle_leaf, init_merkle_store};
const PROGRAM: &str = "
begin
push.1 push.2
repeat.8
u32wrapping_add dup.1 swap
push.3 u32and drop
end
if.true
push.5 mul
else
push.7 add
end
padw padw padw hperm dropw dropw dropw
repeat.4
push.11 u32wrapping_add
end
drop
end
";
const QUEUE_HEAVY_PROGRAM: &str = "
begin
push.0
repeat.1024
push.1 add push.1 add push.1 add push.1 add
push.1 add push.1 add push.1 add push.1 add
push.1 add push.1 add push.1 add push.1 add
push.1 add push.1 add push.1 add push.1 add
end
drop
end
";
fn processor(stack: &[u64], advice: AdviceInputs) -> FastProcessor {
let stack: Vec<Felt> = stack.iter().map(|&v| Felt::new(v).unwrap()).collect();
FastProcessor::new_with_options(
StackInputs::new(&stack).unwrap(),
advice,
ExecutionOptions::default()
.with_core_trace_fragment_size(64)
.expect("valid fragment size"),
)
.unwrap()
}
fn assert_overlapped_matches_buffered(program_src: &str, stack: &[u64], advice: &AdviceInputs) {
let program = Assembler::default()
.assemble_program("test", program_src)
.unwrap()
.unwrap_program();
let buffered = {
let mut host = DefaultHost::default();
let execution_witness = processor(stack, advice.clone())
.execute_for_proving_sync(&program, &mut host)
.unwrap();
let (vm_witness, precompiles_witness) = execution_witness.into_parts();
assert!(precompiles_witness.is_none());
build_trace(vm_witness).unwrap()
};
let streamed = {
let mut host = DefaultHost::default();
let (trace, precompiles_witness) = processor(stack, advice.clone())
.execute_and_build_trace_sync(&program, &mut host, DEFAULT_MAX_PROVER_MEMORY_BYTES)
.unwrap();
assert!(precompiles_witness.is_none());
trace
};
assert_eq!(buffered.program_hash(), streamed.program_hash());
let (b_core, b_chiplets, b_p2) = buffered.main_trace().to_air_matrices();
let (s_core, s_chiplets, s_p2) = streamed.main_trace().to_air_matrices();
assert_eq!(b_core, s_core, "core segment diverged");
assert_eq!(b_chiplets, s_chiplets, "chiplets segment diverged");
assert_eq!(b_p2, s_p2, "poseidon2 segment diverged");
}
#[test]
fn overlapped_build_matches_buffered() {
assert_overlapped_matches_buffered(PROGRAM, &[1], &AdviceInputs::default());
}
#[test]
fn single_worker_handles_queue_heavy_program() {
#[cfg(not(target_family = "wasm"))]
rayon::ThreadPoolBuilder::new().num_threads(1).build().unwrap().install(|| {
assert_overlapped_matches_buffered(QUEUE_HEAVY_PROGRAM, &[], &AdviceInputs::default());
});
#[cfg(target_family = "wasm")]
assert_overlapped_matches_buffered(QUEUE_HEAVY_PROGRAM, &[], &AdviceInputs::default());
}
#[test]
fn overlapped_build_matches_buffered_merkle() {
let index = 3usize;
let (leaves, store) = init_merkle_store(&[1, 2, 3, 4, 5, 6, 7, 8]);
let tree = MerkleTree::new(leaves).unwrap();
let root = tree.root();
let advice = AdviceInputs::default().with_merkle_store(store);
let get_stack = [
tree.depth() as u64,
index as u64,
root[0].as_canonical_u64(),
root[1].as_canonical_u64(),
root[2].as_canonical_u64(),
root[3].as_canonical_u64(),
];
assert_overlapped_matches_buffered("begin mtree_get dropw end", &get_stack, &advice);
let new_node = init_merkle_leaf(9);
let set_stack = [
tree.depth() as u64,
index as u64,
root[0].as_canonical_u64(),
root[1].as_canonical_u64(),
root[2].as_canonical_u64(),
root[3].as_canonical_u64(),
new_node[0].as_canonical_u64(),
new_node[1].as_canonical_u64(),
new_node[2].as_canonical_u64(),
new_node[3].as_canonical_u64(),
];
assert_overlapped_matches_buffered("begin mtree_set end", &set_stack, &advice);
}
#[test]
fn overlap_builder_thread_enters_the_instrument_span() {
use std::{
collections::HashSet,
sync::{Arc, Mutex},
thread::ThreadId,
};
use tracing::span::{Attributes, Id};
use tracing_subscriber::{Registry, layer::SubscriberExt};
struct EnteringThreads {
target: Mutex<Option<Id>>,
threads: Arc<Mutex<HashSet<ThreadId>>>,
}
impl<S: tracing::Subscriber> tracing_subscriber::Layer<S> for EnteringThreads {
fn on_new_span(
&self,
attrs: &Attributes<'_>,
id: &Id,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
if attrs.metadata().name() == "execute_and_build_trace_sync" {
*self.target.lock().unwrap() = Some(id.clone());
}
}
fn on_enter(&self, id: &Id, _ctx: tracing_subscriber::layer::Context<'_, S>) {
if self.target.lock().unwrap().as_ref() == Some(id) {
self.threads.lock().unwrap().insert(std::thread::current().id());
}
}
}
let threads = Arc::new(Mutex::new(HashSet::new()));
let layer = EnteringThreads {
target: Mutex::new(None),
threads: Arc::clone(&threads),
};
let subscriber = Registry::default().with(layer);
let program = Assembler::default().assemble_program("test", PROGRAM).unwrap().unwrap_program();
let caller = std::thread::current().id();
tracing::subscriber::with_default(subscriber, || {
let mut host = DefaultHost::default();
processor(&[1], AdviceInputs::default())
.execute_and_build_trace_sync(&program, &mut host, DEFAULT_MAX_PROVER_MEMORY_BYTES)
.unwrap();
});
let threads = threads.lock().unwrap();
assert!(threads.contains(&caller), "the caller did not enter the instrument span");
assert_eq!(
threads.len(),
2,
"expected execution and the hasher builder to use separate threads, got {threads:?}"
);
}