use std::any::Any;
use zisk_common::{
BusDevice, BusDeviceMode, BusId, ChunkId, CollectSkipper, PayloadType, A, B, OP,
OPERATION_BUS_ID, OPERATION_PRECOMPILED_BUS_DATA_SIZE, STEP,
};
use zisk_core::zisk_ops::ZiskOp;
use crate::jump_dest_rows;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct JumpDestInput {
pub bitmap_addr: u64,
pub bytecode_addr: u64,
pub main_step: u64,
pub count: u64,
pub words: Vec<u64>,
}
impl JumpDestInput {
#[inline]
pub fn rows(&self) -> u64 {
jump_dest_rows(self.count as usize) as u64
}
}
#[derive(Debug)]
pub struct JumpDestCollector {
pub inputs: Vec<JumpDestInput>,
pub rows: u64,
pub first_row_offset: u64,
collected_rows: u64,
skipper: CollectSkipper,
cursor: u64,
pub chunk_id: ChunkId,
pub last_chunk: bool,
}
impl JumpDestCollector {
pub fn new(chunk_id: ChunkId, rows: u64, skipper: CollectSkipper, last_chunk: bool) -> Self {
Self {
inputs: Vec::new(),
rows,
first_row_offset: 0,
collected_rows: 0,
skipper,
cursor: 0,
chunk_id,
last_chunk,
}
}
pub fn take_inputs(mut self) -> Vec<JumpDestInput> {
std::mem::take(&mut self.inputs)
}
#[inline(always)]
pub fn process_data(&mut self, bus_id: &BusId, data: &[u64], data_ext: &[u64]) -> bool {
debug_assert!(*bus_id == OPERATION_BUS_ID);
if data[OP] as u8 != ZiskOp::JUMP_DEST {
return true;
}
if self.collected_rows >= self.rows {
return true;
}
let count = data[OPERATION_PRECOMPILED_BUS_DATA_SIZE];
let op_rows = jump_dest_rows(count as usize) as u64;
let skip = self.skipper.skip;
let start = self.cursor;
let end = start + op_rows;
self.cursor = end;
if end <= skip {
return true;
}
if self.inputs.is_empty() {
self.first_row_offset = skip.saturating_sub(start);
}
self.inputs.push(JumpDestInput {
bitmap_addr: data[A],
bytecode_addr: data[B],
main_step: data[STEP],
count,
words: data_ext.to_vec(),
});
self.collected_rows += end.min(skip + self.rows) - start.max(skip);
true
}
}
impl BusDevice<PayloadType> for JumpDestCollector {
fn as_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
pub const JUMP_DEST_COLLECTOR_MODE: BusDeviceMode = BusDeviceMode::InputGenerator;
#[cfg(test)]
mod tests {
use zisk_core::ZiskOperationType;
use super::*;
use crate::JUMP_DEST_ROWS_X_BLOCK;
fn bus_data(count: u64) -> Vec<u64> {
vec![0xc0, ZiskOperationType::Evm as u64, 0xA000_0000, 0xA001_0000, 7, count]
}
fn collect(counts: &[u64], rows: u64, skip: u64) -> (Vec<u64>, u64, u64) {
let mut collector =
JumpDestCollector::new(ChunkId(0), rows, CollectSkipper::new(skip), true);
for &count in counts {
let words = vec![0u64; count.div_ceil(8) as usize];
collector.process_data(&OPERATION_BUS_ID, &bus_data(count), &words);
}
(
collector.inputs.iter().map(|i| i.count).collect(),
collector.first_row_offset,
collector.collected_rows,
)
}
#[test]
fn a_window_covering_everything_keeps_every_operation() {
let (kept, offset, rows) = collect(&[64, 64, 64], 3 * JUMP_DEST_ROWS_X_BLOCK as u64, 0);
assert_eq!(kept, vec![64, 64, 64]);
assert_eq!(offset, 0);
assert_eq!(rows, 3 * JUMP_DEST_ROWS_X_BLOCK as u64);
}
#[test]
fn operations_before_the_window_are_dropped() {
let block = JUMP_DEST_ROWS_X_BLOCK as u64;
let (kept, offset, _) = collect(&[64, 64, 64], block, 2 * block);
assert_eq!(kept, vec![64]);
assert_eq!(offset, 0, "the window starts exactly on an operation boundary");
}
#[test]
fn a_window_starting_mid_operation_records_the_offset() {
let block = JUMP_DEST_ROWS_X_BLOCK as u64;
let (kept, offset, rows) = collect(&[192], 2 * block, block);
assert_eq!(kept, vec![192], "the straddling operation is still needed");
assert_eq!(offset, block, "its first block belongs to the previous segment");
assert_eq!(rows, 2 * block);
}
#[test]
fn collecting_stops_once_the_window_is_full() {
let block = JUMP_DEST_ROWS_X_BLOCK as u64;
let (kept, _, rows) = collect(&[64, 64, 64, 64], 2 * block, 0);
assert_eq!(kept, vec![64, 64], "the rest belongs to the next segment");
assert_eq!(rows, 2 * block);
}
}