use zisk_precomp_helpers::{
bitmap_words, push_read_run, src_words, walk_jump_dest_bitmap, BYTES_PER_WORD,
};
use crate::{
zisk_ops::OpStats, EmulationMode, InstContext, Mem, EXTRA_PARAMS_ADDR, JUMP_DEST_COST,
};
#[inline(always)]
fn check_preconditions(count: usize, bitmap_addr: u64, bytecode_addr: u64) {
assert_ne!(
count, 0,
"jump_dest called with count = 0; the guest must skip the call, the AIR cannot prove it"
);
debug_assert_eq!(
bytecode_addr & 0x07,
0,
"jump_dest bytecode address 0x{bytecode_addr:08x} is not 8-byte aligned"
);
debug_assert_eq!(
bitmap_addr & 0x07,
0,
"jump_dest bitmap address 0x{bitmap_addr:08x} is not 8-byte aligned"
);
}
#[inline(always)]
fn compute_bitmap<F>(
mem: &Mem,
bytecode_addr: u64,
count: usize,
mut on_word: F,
) -> (Vec<u64>, usize)
where
F: FnMut(usize, u64),
{
let mut bitmap = vec![0u64; bitmap_words(count)];
let reads = walk_jump_dest_bitmap(
count,
|word_index| {
let word = mem.read(bytecode_addr + (word_index * BYTES_PER_WORD) as u64, 8);
on_word(word_index, word);
word
},
&mut bitmap,
);
(bitmap, reads)
}
#[inline(always)]
fn store_bitmap(ctx: &mut InstContext, bitmap_addr: u64, bitmap: &[u64]) {
for (index, word) in bitmap.iter().enumerate() {
ctx.mem.write(bitmap_addr + (index * BYTES_PER_WORD) as u64, *word, 8);
}
}
#[inline(always)]
pub fn opc_jump_dest(ctx: &mut InstContext) {
let bitmap_addr = ctx.a;
let bytecode_addr = ctx.b;
match ctx.emulation_mode {
EmulationMode::Mem => {
let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8) as usize;
check_preconditions(count, bitmap_addr, bytecode_addr);
let (bitmap, _) = compute_bitmap(&ctx.mem, bytecode_addr, count, |_, _| {});
store_bitmap(ctx, bitmap_addr, &bitmap);
}
EmulationMode::GenerateMemReads => {
let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8) as usize;
ctx.precompiled.input_data.clear();
ctx.precompiled.output_data.clear();
ctx.precompiled.step = ctx.step;
ctx.precompiled.input_data.push(count as u64);
check_preconditions(count, bitmap_addr, bytecode_addr);
{
let mem = &ctx.mem;
let input_data = &mut ctx.precompiled.input_data;
for word_index in 0..src_words(count) {
input_data
.push(mem.read(bytecode_addr + (word_index * BYTES_PER_WORD) as u64, 8));
}
}
let bitmap = {
let words = &ctx.precompiled.input_data[1..];
let mut bitmap = vec![0u64; bitmap_words(count)];
walk_jump_dest_bitmap(count, |word_index| words[word_index], &mut bitmap);
bitmap
};
store_bitmap(ctx, bitmap_addr, &bitmap);
}
EmulationMode::ConsumeMemReads => {
assert_eq!(
ctx.precompiled.input_data.len(),
1,
"opc_jump_dest() expects a single header word, found {}",
ctx.precompiled.input_data.len()
);
ctx.data_ext_len = src_words(ctx.precompiled.input_data[0] as usize);
}
}
ctx.c = 0;
ctx.flag = false;
}
#[inline(always)]
pub fn op_jump_dest(_a: u64, _b: u64) -> (u64, bool) {
unimplemented!("op_jump_dest() is not implemented");
}
#[inline(always)]
pub fn ops_jump_dest(ctx: &InstContext, stats: &mut dyn OpStats) {
let bitmap_addr = ctx.a;
let bytecode_addr = ctx.b;
let count = ctx.mem.read(EXTRA_PARAMS_ADDR, 8) as usize;
stats.mem_align_read(EXTRA_PARAMS_ADDR, 1);
check_preconditions(count, bitmap_addr, bytecode_addr);
let mut runs: Vec<(usize, usize)> = Vec::new();
compute_bitmap(&ctx.mem, bytecode_addr, count, |word_index, _| {
push_read_run(&mut runs, word_index)
});
for (first, len) in runs {
stats.mem_align_read(bytecode_addr + (first * BYTES_PER_WORD) as u64, len);
}
stats.mem_align_write(bitmap_addr, bitmap_words(count));
stats.set_variable_cost((src_words(count) - 1) as u64 * JUMP_DEST_COST);
}