#![cfg_attr(not(all(target_os = "linux", target_arch = "x86_64")), allow(dead_code))]
use std::collections::HashMap;
use std::sync::Mutex;
use proofman_fields::PrimeField64;
use zisk_common::{
stats_begin, stats_end, ChunkId, DataBusTrait, EmuTrace, ExecutorStatsHandle, PayloadType,
};
use zisk_core::ZiskRom;
use ziskemu::ZiskEmulator;
use crate::{
error::{ExecutorError, ExecutorResult},
pub_outs_collector::PubOutsCollector,
CountersChunkMetrics, StaticDataBus,
};
pub struct MtChunkProcessor<F: PrimeField64> {
results: Mutex<Vec<(ChunkId, StaticDataBus<PayloadType, F>)>>,
errors: Mutex<Vec<String>>,
}
impl<F: PrimeField64> MtChunkProcessor<F> {
pub fn new() -> Self {
Self { results: Mutex::new(Vec::new()), errors: Mutex::new(Vec::new()) }
}
#[allow(unused_variables)] pub fn process_chunk(
&self,
chunk_id: ChunkId,
emu_trace: &EmuTrace,
zisk_rom: &ZiskRom,
stats: &ExecutorStatsHandle,
mt_scope_id: u64,
) {
stats_begin!(stats, mt_scope_id, _chunk_scope, "MT_CHUNK_PLAYER", 0);
let mut data_bus = StaticDataBus::<_, F>::build(true, None);
ZiskEmulator::process_emu_trace::<F, _, _>(zisk_rom, emu_trace, &mut data_bus, false);
data_bus.on_close();
stats_end!(stats, &_chunk_scope);
match self.results.lock() {
Ok(mut guard) => guard.push((chunk_id, data_bus)),
Err(e) => {
self.record_error(format!("results lock poisoned for chunk {}: {e}", chunk_id.0))
}
}
}
pub fn finalize(self) -> ExecutorResult<(CountersChunkMetrics, PubOutsCollector)> {
let err_vec = self
.errors
.into_inner()
.map_err(|_| ExecutorError::mutex_poisoned("mt_chunk_errors"))?;
if !err_vec.is_empty() {
let message = err_vec
.iter()
.enumerate()
.map(|(i, e)| format!("[Error {}] {e}", i + 1))
.collect::<Vec<_>>()
.join("\n");
return Err(ExecutorError::MtChunkProcessing { count: err_vec.len(), message });
}
let mut data_buses = self
.results
.into_inner()
.map_err(|_| ExecutorError::mutex_poisoned("mt_chunk_results"))?;
data_buses.sort_by_key(|(chunk_id, _)| chunk_id.0);
let mut counters: CountersChunkMetrics = HashMap::new();
let mut pub_outs = PubOutsCollector::new();
for (chunk_id, mut data_bus) in data_buses {
pub_outs.0.extend(data_bus.take_pub_outs().0);
let databus_counters = data_bus.into_devices(false);
for (idx, counter) in databus_counters.into_iter() {
counters.entry(idx).or_default().push((chunk_id, counter));
}
}
Ok((counters, pub_outs))
}
fn record_error(&self, message: String) {
let _ = self.errors.lock().map(|mut errs| errs.push(message));
}
#[cfg(test)]
pub(crate) fn push_error_for_test(&self, message: String) {
self.record_error(message);
}
}
impl<F: PrimeField64> Default for MtChunkProcessor<F> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use proofman_fields::Goldilocks;
type F = Goldilocks;
#[test]
fn new_processor_finalizes_to_empty() {
let p: MtChunkProcessor<F> = MtChunkProcessor::new();
let (counters, pub_outs) = p.finalize().expect("empty processor finalizes Ok");
assert!(counters.is_empty(), "no chunks recorded → no counters");
assert!(pub_outs.0.is_empty(), "no chunks recorded → no pub_outs");
}
#[test]
fn default_matches_new() {
let p: MtChunkProcessor<F> = MtChunkProcessor::default();
let (counters, pub_outs) = p.finalize().expect("default ok");
assert!(counters.is_empty());
assert!(pub_outs.0.is_empty());
}
#[test]
fn single_error_propagates_through_finalize() {
let p: MtChunkProcessor<F> = MtChunkProcessor::new();
p.push_error_for_test("boom-chunk-42".to_string());
match p.finalize() {
Ok(_) => panic!("single error must surface"),
Err(err) => {
let msg = err.to_string();
assert!(msg.contains("MT assembly chunk processing failed (1 errors)"));
assert!(msg.contains("boom-chunk-42"));
}
}
}
#[test]
fn multiple_errors_combine_with_indexed_prefixes() {
let p: MtChunkProcessor<F> = MtChunkProcessor::new();
p.push_error_for_test("first-fail".to_string());
p.push_error_for_test("second-fail".to_string());
match p.finalize() {
Ok(_) => panic!("multiple errors must surface"),
Err(err) => {
let msg = err.to_string();
assert!(msg.contains("(2 errors)"));
assert!(msg.contains("[Error 1] first-fail"));
assert!(msg.contains("[Error 2] second-fail"));
}
}
}
}