use crate::FLOAT_LIB_ROM_ADDR_MAX;
use crate::{ZiskInst, ZiskInstBuilder, FLOAT_LIB_ROM_ADDR, ROM_ADDR, ROM_ADDR_MAX, ROM_ENTRY};
use rayon::iter::IntoParallelIterator;
use rayon::iter::ParallelIterator;
use std::collections::BTreeMap;
use std::error::Error;
#[derive(Debug, Clone)]
pub struct DataSection64 {
pub addr: u64,
pub data: Vec<u64>,
}
#[derive(Default, Debug, Clone)]
pub struct ZiskRom {
pub next_init_inst_addr: u64,
pub insts: BTreeMap<u64, ZiskInstBuilder>,
pub ro_data_64: Vec<DataSection64>,
pub rw_data_64: Vec<DataSection64>,
pub rom_bios_instructions: Vec<ZiskInst>,
pub rom_program_instructions: Vec<ZiskInst>,
pub rom_program_na_instructions: Vec<ZiskInst>,
pub rom_float_instructions: Vec<ZiskInst>,
pub rom_float_na_instructions: Vec<ZiskInst>,
pub max_bios_pc: u64,
pub max_program_pc: u64,
pub max_float_pc: u64,
pub sorted_pc_list: Vec<u64>,
pub min_program_pc: u64,
pub build_counter: u64,
pub last_internal_address_offset: u64,
}
impl ZiskRom {
pub fn optimize_instruction_lookup(&mut self) -> Result<(), Box<dyn Error>> {
let mut max_bios_address = 0_u64;
let mut min_program_address = u64::MAX;
let mut max_program_address = 0_u64;
let mut max_program_na_address = 0_u64;
let mut max_float_address = 0_u64;
let mut max_float_na_address = 0_u64;
if !self.sorted_pc_list.is_empty() {
return Err(
"ZiskRom::optimize_instruction_lookup() sorted_pc_list should be empty before optimization"
.into(),
);
}
self.sorted_pc_list.reserve(self.insts.len());
for instruction in &self.insts {
let addr = *instruction.0;
self.sorted_pc_list.push(addr);
if addr < ROM_ENTRY {
return Err(format!("Address out of range: {addr}").into());
} else if addr < ROM_ADDR {
if addr & 0x03 != 0 {
return Err(format!(
"Non-aligned instruction in entry area at address {addr:#x}"
)
.into());
}
max_bios_address = std::cmp::max(max_bios_address, addr);
} else if addr < FLOAT_LIB_ROM_ADDR {
if addr & 0x03 != 0 {
max_program_na_address = std::cmp::max(max_program_na_address, addr);
} else {
min_program_address = min_program_address.min(addr);
max_program_address = max_program_address.max(addr);
}
} else if addr <= ROM_ADDR_MAX {
if addr & 0x03 != 0 {
max_float_na_address = std::cmp::max(max_float_na_address, addr);
} else {
max_float_address = max_float_address.max(addr);
}
} else {
return Err(format!("Address out of range: {addr}").into());
}
}
self.max_bios_pc = max_bios_address;
self.max_program_pc = max_program_address.max(max_program_na_address);
self.max_float_pc = max_float_address.max(max_float_na_address);
self.min_program_pc =
if min_program_address == u64::MAX { ROM_ADDR } else { min_program_address };
let num_bios_instructions =
if max_bios_address > 0 { (max_bios_address - ROM_ENTRY) / 4 + 1 } else { 0 };
let num_program_instructions =
if max_program_address > 0 { (max_program_address - ROM_ADDR) / 4 + 1 } else { 0 };
let num_program_na_instructions =
if max_program_na_address > 0 { (max_program_na_address - ROM_ADDR) + 1 } else { 0 };
let num_float_instructions = if max_float_address > 0 {
(max_float_address - FLOAT_LIB_ROM_ADDR) / 4 + 1
} else {
0
};
let num_float_na_instructions = if max_float_na_address > 0 {
(max_float_na_address - FLOAT_LIB_ROM_ADDR) + 1
} else {
0
};
self.rom_bios_instructions =
(0..num_bios_instructions).into_par_iter().map(|_| ZiskInst::default()).collect();
self.rom_program_instructions =
(0..num_program_instructions).into_par_iter().map(|_| ZiskInst::default()).collect();
self.rom_program_na_instructions =
(0..num_program_na_instructions).into_par_iter().map(|_| ZiskInst::default()).collect();
self.rom_float_instructions =
(0..num_float_instructions).into_par_iter().map(|_| ZiskInst::default()).collect();
self.rom_float_na_instructions =
(0..num_float_na_instructions).into_par_iter().map(|_| ZiskInst::default()).collect();
self.sorted_pc_list.sort();
for instruction in &self.insts {
let addr = *instruction.0;
if addr < ROM_ADDR {
self.rom_bios_instructions[((addr - ROM_ENTRY) >> 2) as usize] =
instruction.1.i.clone();
} else if addr < FLOAT_LIB_ROM_ADDR {
if addr % 4 != 0 {
self.rom_program_na_instructions[(addr - ROM_ADDR) as usize] =
instruction.1.i.clone();
} else {
self.rom_program_instructions[((addr - ROM_ADDR) >> 2) as usize] =
instruction.1.i.clone();
}
} else if addr <= ROM_ADDR_MAX {
if addr % 4 != 0 {
self.rom_float_na_instructions[(addr - FLOAT_LIB_ROM_ADDR) as usize] =
instruction.1.i.clone();
} else {
self.rom_float_instructions[((addr - FLOAT_LIB_ROM_ADDR) >> 2) as usize] =
instruction.1.i.clone();
}
} else {
return Err(format!("Address out of range: {addr}").into());
}
}
for i in 0..self.sorted_pc_list.len() {
let pc = self.sorted_pc_list[i];
self.insts.get_mut(&pc).unwrap().i.sorted_pc_list_index = i;
let inst = self.get_mut_instruction(pc);
inst.sorted_pc_list_index = i;
}
Ok(())
}
pub fn get_instruction_count(&self) -> usize {
self.insts.len()
}
pub fn get_rom_init_64bit_words(&self) -> usize {
let mut count = 0;
for section in &self.ro_data_64 {
count += section.data.len();
}
count
}
pub fn get_ram_init_64bit_words(&self) -> usize {
let mut count = 0;
for section in &self.rw_data_64 {
count += section.data.len();
}
count
}
#[inline(always)]
pub fn get_instruction(&self, pc: u64) -> &ZiskInst {
if pc < ROM_ENTRY {
panic!(
"ZiskRom::get_instruction() pc={pc} is out of range (below ROM_ENTRY=0x{:x})",
ROM_ENTRY
);
} else if pc < ROM_ADDR {
if pc & 0x03 != 0 {
panic!("ZiskRom::get_instruction() pc=0x{:x} is not aligned to 4 bytes, but it is in the ROM_ENTRY range", pc);
}
let rom_index = ((pc - ROM_ENTRY) >> 2) as usize;
if rom_index >= self.rom_bios_instructions.len() {
panic!(
"ZiskRom::get_instruction() pc=0x{0:X} ({0}) is out of range rom_bios_instructions (rom_index:{1:} >= {2:})",
pc,
rom_index,
self.rom_bios_instructions.len()
);
}
&self.rom_bios_instructions[rom_index]
} else if pc < FLOAT_LIB_ROM_ADDR {
if pc & 0x03 == 0 {
let rom_index = ((pc - ROM_ADDR) >> 2) as usize;
if rom_index >= self.rom_program_instructions.len() {
panic!(
"ZiskRom::get_instruction() pc=0x{pc:x} ({pc}) is out of range rom_program_instructions (rom_index:{} >= {})",
rom_index,
self.rom_program_instructions.len()
);
}
&self.rom_program_instructions[rom_index]
} else {
let rom_index = (pc - ROM_ADDR) as usize;
if rom_index >= self.rom_program_na_instructions.len() {
panic!(
"ZiskRom::get_instruction() pc={pc:x} is out of range rom_program_na_instructions (rom_index:{} >= {})",
rom_index,
self.rom_program_na_instructions.len()
);
}
&self.rom_program_na_instructions[rom_index]
}
} else if pc <= ROM_ADDR_MAX {
if pc & 0x03 == 0 {
let rom_index = ((pc - FLOAT_LIB_ROM_ADDR) >> 2) as usize;
if rom_index >= self.rom_float_instructions.len() {
panic!(
"ZiskRom::get_instruction() pc=0x{pc:x} ({pc}) is out of range rom_float_instructions (rom_index:{} >= {})",
rom_index,
self.rom_float_instructions.len()
);
}
&self.rom_float_instructions[rom_index]
} else {
let rom_index = (pc - FLOAT_LIB_ROM_ADDR) as usize;
if rom_index >= self.rom_float_na_instructions.len() {
panic!(
"ZiskRom::get_instruction() pc=0x{pc:x} is out of range rom_float_na_instructions (rom_index:{} >= {})",
rom_index,
self.rom_float_na_instructions.len()
);
}
&self.rom_float_na_instructions[rom_index]
}
} else {
panic!("ZiskRom::get_instruction() pc=0x{pc:x} is out of range");
}
}
pub fn get_internal_instruction(&self, pc: u64) -> Option<&ZiskInst> {
if pc & 0x01 == 0 {
None
} else if (ROM_ADDR..FLOAT_LIB_ROM_ADDR).contains(&pc) {
let rom_index = (pc - ROM_ADDR) as usize;
if rom_index >= self.rom_program_na_instructions.len() {
None
} else {
Some(&self.rom_program_na_instructions[rom_index])
}
} else if (FLOAT_LIB_ROM_ADDR..=FLOAT_LIB_ROM_ADDR_MAX).contains(&pc) {
let rom_index = (pc - FLOAT_LIB_ROM_ADDR) as usize;
if rom_index >= self.rom_float_na_instructions.len() {
None
} else {
Some(&self.rom_float_na_instructions[rom_index])
}
} else {
None
}
}
#[inline(always)]
pub fn get_mut_instruction(&mut self, pc: u64) -> &mut ZiskInst {
if pc < ROM_ENTRY {
panic!(
"ZiskRom::get_mut_instruction() pc={pc} is out of range (below ROM_ENTRY=0x{:x})",
ROM_ENTRY
);
} else if pc < ROM_ADDR {
if pc & 0x03 != 0 {
panic!("ZiskRom::get_mut_instruction() pc=0x{:x} is not aligned to 4 bytes, but it is in the ROM_ENTRY range", pc);
}
let rom_index = ((pc - ROM_ENTRY) >> 2) as usize;
if rom_index >= self.rom_bios_instructions.len() {
panic!(
"ZiskRom::get_mut_instruction() pc=0x{0:X} ({0}) is out of range rom_bios_instructions (rom_index:{1:} >= {2:})",
pc,
rom_index,
self.rom_bios_instructions.len()
);
}
&mut self.rom_bios_instructions[rom_index]
} else if pc < FLOAT_LIB_ROM_ADDR {
if pc & 0x03 == 0 {
let rom_index = ((pc - ROM_ADDR) >> 2) as usize;
if rom_index >= self.rom_program_instructions.len() {
panic!(
"ZiskRom::get_mut_instruction() pc=0x{0:X} ({0}) is out of range rom_program_instructions (rom_index:{1:} >= {2:})",
pc,
rom_index,
self.rom_program_instructions.len()
);
}
&mut self.rom_program_instructions[rom_index]
} else {
let rom_index = (pc - ROM_ADDR) as usize;
if rom_index >= self.rom_program_na_instructions.len() {
panic!(
"ZiskRom::get_mut_instruction() pc={} is out of range rom_program_na_instructions (rom_index:{} >= {})",
pc,
rom_index,
self.rom_program_na_instructions.len()
);
}
&mut self.rom_program_na_instructions[rom_index]
}
} else if pc <= ROM_ADDR_MAX {
if pc & 0x03 == 0 {
let rom_index = ((pc - FLOAT_LIB_ROM_ADDR) >> 2) as usize;
if rom_index >= self.rom_float_instructions.len() {
panic!(
"ZiskRom::get_mut_instruction() pc=0x{0:X} ({0}) is out of range rom_float_instructions (rom_index:{1:} >= {2:})",
pc,
rom_index,
self.rom_float_instructions.len()
);
}
&mut self.rom_float_instructions[rom_index]
} else {
let rom_index = (pc - FLOAT_LIB_ROM_ADDR) as usize;
if rom_index >= self.rom_float_na_instructions.len() {
panic!(
"ZiskRom::get_mut_instruction() pc={} is out of range rom_float_na_instructions (rom_index:{} >= {})",
pc,
rom_index,
self.rom_float_na_instructions.len()
);
}
&mut self.rom_float_na_instructions[rom_index]
}
} else {
panic!("ZiskRom::get_mut_instruction() pc={pc} is out of range");
}
}
pub fn get_internal_address(&mut self) -> u64 {
if self.last_internal_address_offset == 0 {
self.last_internal_address_offset = 1;
} else {
self.last_internal_address_offset += 2;
}
let result = ROM_ADDR + self.last_internal_address_offset;
if result & 0x01 == 0 {
panic!("ZiskRom::get_internal_address() result=0x{:x} is not odd", result);
}
if result > ROM_ADDR_MAX {
panic!("ZiskRom::get_internal_address() result=0x{:x} is out of range (ROM_ADDR_MAX=0x{:x})", result, ROM_ADDR_MAX);
}
result
}
}
pub trait MemDataSection {
fn ro_sections(&self) -> &[DataSection64];
fn rw_sections(&self) -> &[DataSection64];
}
impl MemDataSection for ZiskRom {
fn ro_sections(&self) -> &[DataSection64] {
&self.ro_data_64
}
fn rw_sections(&self) -> &[DataSection64] {
&self.rw_data_64
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ZiskInstBuilder, ZiskRom, ROM_ADDR};
fn create_test_inst_builder(addr: u64, op: u8) -> ZiskInstBuilder {
let mut builder = ZiskInstBuilder::new(addr);
builder.i.op = op;
builder
}
fn create_test_internal_inst_builder(
addr: u64,
external_ref_addr: u64,
op: u8,
) -> ZiskInstBuilder {
let mut builder = ZiskInstBuilder::new_internal(addr, external_ref_addr);
builder.i.op = op;
builder
}
#[test]
fn test_optimize_empty_rom() {
let mut rom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() };
assert!(rom.optimize_instruction_lookup().is_ok());
assert_eq!(rom.sorted_pc_list.len(), 0);
assert_eq!(rom.rom_bios_instructions.len(), 0);
assert_eq!(rom.rom_program_instructions.len(), 0);
assert_eq!(rom.rom_program_na_instructions.len(), 0);
assert_eq!(rom.rom_float_instructions.len(), 0);
assert_eq!(rom.rom_float_na_instructions.len(), 0);
}
#[test]
fn test_optimize_entry_instructions_only() {
let mut rom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() };
let entry_base = ROM_ENTRY;
rom.insts.insert(entry_base, create_test_inst_builder(entry_base, 1));
rom.insts.insert(entry_base + 4, create_test_inst_builder(entry_base + 4, 2));
rom.insts.insert(entry_base + 8, create_test_inst_builder(entry_base + 8, 3));
assert!(rom.optimize_instruction_lookup().is_ok());
assert_eq!(rom.rom_bios_instructions.len(), 3);
assert_eq!(rom.rom_program_instructions.len(), 0);
assert_eq!(rom.rom_program_na_instructions.len(), 0);
assert_eq!(rom.rom_float_instructions.len(), 0);
assert_eq!(rom.rom_float_na_instructions.len(), 0);
assert_eq!(rom.sorted_pc_list, vec![entry_base, entry_base + 4, entry_base + 8]);
assert_eq!(rom.rom_bios_instructions[0].op, 1);
assert_eq!(rom.rom_bios_instructions[1].op, 2);
assert_eq!(rom.rom_bios_instructions[2].op, 3);
assert_eq!(rom.max_bios_pc, entry_base + 8);
assert_eq!(rom.max_program_pc, 0);
}
#[test]
fn test_optimize_main_rom_instructions() {
let mut rom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() };
rom.insts.insert(ROM_ADDR, create_test_inst_builder(ROM_ADDR, 10));
rom.insts.insert(ROM_ADDR + 4, create_test_inst_builder(ROM_ADDR + 4, 11));
rom.insts.insert(ROM_ADDR + 12, create_test_inst_builder(ROM_ADDR + 12, 12));
assert!(rom.optimize_instruction_lookup().is_ok());
assert_eq!(rom.rom_bios_instructions.len(), 0);
assert_eq!(rom.rom_program_instructions.len(), 4); assert_eq!(rom.rom_program_na_instructions.len(), 0);
assert_eq!(rom.rom_float_instructions.len(), 0);
assert_eq!(rom.rom_float_na_instructions.len(), 0);
assert_eq!(rom.rom_program_instructions[0].op, 10); assert_eq!(rom.rom_program_instructions[1].op, 11); assert_eq!(rom.rom_program_instructions[3].op, 12);
assert_eq!(rom.max_program_pc, ROM_ADDR + 12);
}
#[test]
fn test_optimize_non_aligned_instructions() {
let mut rom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() };
rom.insts
.insert(ROM_ADDR + 1, create_test_internal_inst_builder(ROM_ADDR + 1, ROM_ADDR, 20));
rom.insts
.insert(ROM_ADDR + 5, create_test_internal_inst_builder(ROM_ADDR + 5, ROM_ADDR, 21));
rom.insts
.insert(ROM_ADDR + 7, create_test_internal_inst_builder(ROM_ADDR + 7, ROM_ADDR, 22));
assert!(rom.optimize_instruction_lookup().is_ok());
assert_eq!(rom.rom_bios_instructions.len(), 0);
assert_eq!(rom.rom_program_instructions.len(), 0);
assert_eq!(rom.rom_float_instructions.len(), 0);
assert_eq!(rom.rom_float_na_instructions.len(), 0);
assert_eq!(rom.rom_program_na_instructions.len(), 8);
assert_eq!(rom.rom_program_na_instructions[1].op, 20); assert_eq!(rom.rom_program_na_instructions[5].op, 21); assert_eq!(rom.rom_program_na_instructions[7].op, 22); }
#[test]
fn test_optimize_mixed_instructions() {
let mut rom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() };
rom.insts.insert(ROM_ENTRY + 4, create_test_inst_builder(ROM_ENTRY + 4, 1));
rom.insts.insert(ROM_ADDR, create_test_inst_builder(ROM_ADDR, 2));
rom.insts
.insert(ROM_ADDR + 3, create_test_internal_inst_builder(ROM_ADDR + 3, ROM_ADDR, 3));
assert!(rom.optimize_instruction_lookup().is_ok());
assert!(!rom.rom_bios_instructions.is_empty());
assert!(!rom.rom_program_instructions.is_empty());
assert!(!rom.rom_program_na_instructions.is_empty());
assert!(rom.rom_float_instructions.is_empty());
assert!(rom.rom_float_na_instructions.is_empty());
assert_eq!(rom.sorted_pc_list.len(), 3);
assert_eq!(rom.sorted_pc_list, vec![ROM_ENTRY + 4, ROM_ADDR, ROM_ADDR + 3]);
}
#[test]
fn test_optimize_sorted_pc_indices() {
let mut rom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() };
rom.insts.insert(ROM_ADDR + 8, create_test_inst_builder(ROM_ADDR + 8, 3));
rom.insts.insert(ROM_ADDR, create_test_inst_builder(ROM_ADDR, 1));
rom.insts.insert(ROM_ADDR + 4, create_test_inst_builder(ROM_ADDR + 4, 2));
assert!(rom.optimize_instruction_lookup().is_ok());
assert_eq!(rom.sorted_pc_list, vec![ROM_ADDR, ROM_ADDR + 4, ROM_ADDR + 8]);
assert_eq!(rom.insts.get(&ROM_ADDR).unwrap().i.sorted_pc_list_index, 0);
assert_eq!(rom.insts.get(&(ROM_ADDR + 4)).unwrap().i.sorted_pc_list_index, 1);
assert_eq!(rom.insts.get(&(ROM_ADDR + 8)).unwrap().i.sorted_pc_list_index, 2);
assert_eq!(rom.rom_program_instructions[0].sorted_pc_list_index, 0);
assert_eq!(rom.rom_program_instructions[1].sorted_pc_list_index, 1);
assert_eq!(rom.rom_program_instructions[2].sorted_pc_list_index, 2);
}
#[test]
fn test_optimize_sorted_pc_indices_with_gaps() {
let mut rom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() };
rom.insts.insert(ROM_ADDR, create_test_inst_builder(ROM_ADDR, 10));
rom.insts.insert(ROM_ADDR + 4, create_test_inst_builder(ROM_ADDR + 4, 11));
rom.insts.insert(ROM_ADDR + 12, create_test_inst_builder(ROM_ADDR + 12, 12));
rom.insts.insert(ROM_ADDR + 100, create_test_inst_builder(ROM_ADDR + 100, 13));
assert!(rom.optimize_instruction_lookup().is_ok());
assert_eq!(rom.sorted_pc_list.len(), 4);
assert_eq!(rom.sorted_pc_list, vec![ROM_ADDR, ROM_ADDR + 4, ROM_ADDR + 12, ROM_ADDR + 100]);
assert_eq!(rom.rom_program_instructions.len(), 26);
assert_eq!(rom.rom_program_instructions[0].op, 10);
assert_eq!(rom.rom_program_instructions[0].sorted_pc_list_index, 0);
assert_eq!(rom.rom_program_instructions[1].op, 11);
assert_eq!(rom.rom_program_instructions[1].sorted_pc_list_index, 1);
assert_eq!(rom.rom_program_instructions[3].op, 12);
assert_eq!(rom.rom_program_instructions[3].sorted_pc_list_index, 2);
assert_eq!(rom.rom_program_instructions[25].op, 13);
assert_eq!(rom.rom_program_instructions[25].sorted_pc_list_index, 3);
}
#[test]
fn test_optimize_address_below_rom_entry_err() {
let mut rom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() };
rom.insts.insert(ROM_ENTRY - 4, create_test_inst_builder(ROM_ENTRY - 4, 1));
assert!(rom.optimize_instruction_lookup().is_err());
}
#[test]
fn test_optimize_address_above_rom_max_err() {
let mut rom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() };
rom.insts.insert(
ROM_ADDR_MAX + 4,
create_test_internal_inst_builder(ROM_ADDR_MAX + 4, ROM_ADDR, 1),
);
assert!(rom.optimize_instruction_lookup().is_err());
}
#[test]
fn test_basic_optimize_preserves_instruction_data() {
let mut rom = ZiskRom { next_init_inst_addr: ROM_ENTRY, ..Default::default() };
let mut builder = ZiskInstBuilder::new(ROM_ADDR);
builder.i.op = 42;
builder.i.a_src = 1;
builder.i.b_src = 2;
builder.i.store = 3;
rom.insts.insert(ROM_ADDR, builder);
assert!(rom.optimize_instruction_lookup().is_ok());
let stored = &rom.rom_program_instructions[0];
assert_eq!(stored.op, 42);
assert_eq!(stored.a_src, 1);
assert_eq!(stored.b_src, 2);
assert_eq!(stored.store, 3);
}
}