1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::{collections::HashMap, sync::Arc};
use crate::{
pub_outs_collector::PubOutsCollector, BackendArtifacts, CountersChunkMetrics, ExecutionOutput,
StaticDataBus, MAX_NUM_STEPS,
};
use proofman_fields::PrimeField64;
use proofman_util::{timer_start_info, timer_stop_and_log_info};
use rayon::prelude::*;
use zisk_common::DataBusTrait;
use zisk_common::{io::ZiskStdin, ChunkId, EmuTrace};
use zisk_core::{MemDataSection, ZiskRom};
use ziskemu::{EmuOptions, ZiskEmulator};
use crate::error::ExecutorResult;
pub struct EmulatorRust {
/// Chunk size for processing.
chunk_size: u64,
}
impl EmulatorRust {
/// The number of threads to use for parallel processing when computing minimal traces.
const NUM_THREADS: usize = 16;
pub fn new(chunk_size: u64) -> Self {
Self { chunk_size }
}
/// Computes minimal traces by processing the ZisK ROM with the given public inputs.
///
/// # Arguments
/// * `stdin` - Shared standard input source used to feed data into the emulator.
/// * `_pctx` - Proof context carrying field-parameterized configuration for execution.
/// * `_stats` - Handle to executor statistics collection.
/// * `_caller_stats_scope` - Stats scope used to associate collected statistics with the caller.
///
/// # Returns
/// A [`ExecutionOutput`] whose `backend` is [`BackendArtifacts::Rust`] —
/// no async runners on the native path.
pub fn execute<F: PrimeField64>(
&self,
zisk_rom: &ZiskRom,
stdin: &ZiskStdin,
) -> ExecutorResult<ExecutionOutput> {
let min_traces = self.run_emulator(zisk_rom, Self::NUM_THREADS, stdin)?;
// Store execute steps
let steps = min_traces.iter().map(|trace| trace.steps).sum::<u64>();
timer_start_info!(COUNT);
let (counters, pub_outs) = self.count::<F>(zisk_rom, &min_traces)?;
timer_stop_and_log_info!(COUNT);
// Wrap once at the boundary: downstream (planning, witness, the
// progressive main store) shares chunks as Arcs.
let min_traces = min_traces.into_iter().map(Arc::new).collect();
Ok(ExecutionOutput {
min_traces,
counters,
pub_outs,
steps,
backend: BackendArtifacts::Rust,
})
}
fn run_emulator(
&self,
zisk_rom: &ZiskRom,
num_threads: usize,
stdin: &ZiskStdin,
) -> ExecutorResult<Vec<EmuTrace>> {
// Call emulate with these options
let input_data = stdin.read_data();
// Settings for the emulator
let emu_options = EmuOptions {
chunk_size: Some(self.chunk_size),
max_steps: MAX_NUM_STEPS,
..EmuOptions::default()
};
Ok(ZiskEmulator::compute_minimal_traces(zisk_rom, &input_data, &emu_options, num_threads)?)
}
/// Counts metrics for secondary state machines based on minimal traces.
///
/// # Arguments
/// * `min_traces` - Minimal traces obtained from the ROM execution.
///
/// # Returns
/// A tuple containing two vectors:
/// * A vector of main state machine metrics grouped by chunk ID.
/// * A vector of secondary state machine metrics grouped by chunk ID. The vector is nested,
/// with the outer vector representing the secondary state machines and the inner vector
/// containing the metrics for each chunk.
fn count<F: PrimeField64>(
&self,
zisk_rom: &ZiskRom,
min_traces: &[EmuTrace],
) -> ExecutorResult<(CountersChunkMetrics, PubOutsCollector)> {
let metrics_slices: Vec<_> = min_traces
.par_iter()
.map(|minimal_trace| {
let mut data_bus = if minimal_trace.is_first() {
let mem_sections: &dyn MemDataSection =
zisk_rom as &dyn zisk_core::MemDataSection;
StaticDataBus::<_, F>::build(false, Some(mem_sections))
} else {
StaticDataBus::<_, F>::build(false, None)
};
ZiskEmulator::process_emu_trace::<F, _, _>(
zisk_rom,
minimal_trace,
&mut data_bus,
true,
);
let pub_outs_chunk = data_bus.take_pub_outs();
let databus_counters = data_bus.into_devices(true);
let mut counters = Vec::new();
for counter in databus_counters.into_iter() {
counters.push(counter);
}
Ok((counters, pub_outs_chunk))
})
.collect::<ExecutorResult<Vec<_>>>()?;
let mut counters = HashMap::new();
let mut pub_outs = PubOutsCollector::new();
for (chunk_id, (counter_slice, pub_outs_chunk)) in metrics_slices.into_iter().enumerate() {
pub_outs.0.extend(pub_outs_chunk.0);
for (idx, counter) in counter_slice.into_iter() {
counters.entry(idx).or_insert_with(Vec::new).push((ChunkId(chunk_id), counter));
}
}
Ok((counters, pub_outs))
}
}