use alloc::vec;
use alloc::vec::Vec;
use crate::core::exec::{Access as ExitAccess, Exit, ExitMask, ExitReason};
use crate::core::space::{AddressSpace, MemAttrs};
use crate::core::value::Width;
use super::cp0::{
Cp0, GENERAL_VECTOR, GENERAL_VECTOR_BEV, Lines, Lookup, REFILL_VECTOR, REFILL_VECTOR_BEV,
Segment, TLB_ENTRIES, Tlb, TlbEntry, cause_bits, exc, reg, status,
};
use super::isa::{self, Endian, Op, Req};
use super::{Config, PAGE_SIZE};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Access {
Fetch,
Load,
Store,
}
impl Access {
const fn address_error(self) -> u32 {
match self {
Access::Store => exc::ADES,
_ => exc::ADEL,
}
}
const fn tlb_error(self) -> u32 {
match self {
Access::Store => exc::TLBS,
_ => exc::TLBL,
}
}
const fn bus_error(self) -> u32 {
match self {
Access::Fetch => exc::IBE,
_ => exc::DBE,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct Trap {
pub code: u32,
pub bad_vaddr: Option<u32>,
pub refill: bool,
pub ce: u32,
pub tlb: bool,
}
impl Trap {
const fn bare(code: u32) -> Trap {
Trap {
code,
bad_vaddr: None,
refill: false,
ce: 0,
tlb: false,
}
}
const fn address(code: u32, vaddr: u32) -> Trap {
Trap {
code,
bad_vaddr: Some(vaddr),
refill: false,
ce: 0,
tlb: false,
}
}
const fn coprocessor(n: u32) -> Trap {
Trap {
code: exc::CPU,
bad_vaddr: None,
refill: false,
ce: n,
tlb: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct PendingLoad {
pub reg: u32,
pub value: u32,
}
#[derive(Debug, Clone)]
pub(super) struct State {
pub regs: [u32; 32],
pub hi: u32,
pub lo: u32,
pub pc: u32,
pub next_pc: u32,
pub in_delay: bool,
pub pending_load: Option<PendingLoad>,
pub cp0: Cp0,
pub tlb: Tlb,
pub dcache: Vec<u8>,
pub icache: Vec<u8>,
pub cycles: u64,
pub debt: u64,
pub faults: u64,
}
impl State {
pub(super) fn new(cfg: &Config) -> State {
let pc = cfg.reset_vector;
State {
regs: [0; 32],
hi: 0,
lo: 0,
pc,
next_pc: pc.wrapping_add(4),
in_delay: false,
pending_load: None,
cp0: Cp0::new(cfg.prid),
tlb: Tlb::new(),
dcache: vec![0; cfg.arch.dcache_bytes as usize],
icache: vec![0; cfg.arch.icache_bytes as usize],
cycles: 0,
debt: 0,
faults: 0,
}
}
}
pub(super) struct Exec<'a> {
st: &'a mut State,
space: &'a AddressSpace,
cfg: &'a Config,
lines: &'a Lines,
exits: ExitMask,
exit: Option<Exit>,
attrs: MemAttrs,
used: u64,
this_pc: u32,
entry_next_pc: u32,
in_delay: bool,
delayed: Option<PendingLoad>,
settled: Option<(u32, u32)>,
issued: Option<PendingLoad>,
}
impl<'a> Exec<'a> {
pub(super) fn new(
st: &'a mut State,
space: &'a AddressSpace,
cfg: &'a Config,
lines: &'a Lines,
exits: ExitMask,
) -> Exec<'a> {
let attrs = MemAttrs::DEFAULT
.with_requester(cfg.requester)
.with_privileged(st.cp0.kernel_mode());
let this_pc = st.pc;
let entry_next_pc = st.next_pc;
let in_delay = st.in_delay;
let delayed = st.pending_load.take();
Exec {
st,
space,
cfg,
lines,
exits,
exit: None,
attrs,
used: 0,
this_pc,
entry_next_pc,
in_delay,
delayed,
settled: None,
issued: None,
}
}
pub(super) fn step(&mut self) -> u64 {
if let Some(pending) = self.pending_interrupt() {
self.settle();
let _ = pending;
self.enter_exception(Trap::bare(exc::INT));
return self.finish();
}
match self.execute() {
Ok(()) => {
self.settle();
self.st.pending_load = self.issued.take();
self.st.cp0.tick_random();
}
Err(trap) => {
self.settle();
match self.exit_for(&trap) {
Some(exit) => {
if exit.reason != ExitReason::SYSCALL {
self.st.pc = self.this_pc;
self.st.next_pc = self.entry_next_pc;
self.st.in_delay = self.in_delay;
}
self.exit = Some(exit);
}
None => self.enter_exception(trap),
}
}
}
self.finish()
}
fn finish(&mut self) -> u64 {
self.used.max(1)
}
pub(super) fn take_exit(&mut self) -> Option<Exit> {
self.exit.take()
}
fn exit_for(&self, trap: &Trap) -> Option<Exit> {
let (reason, access) = match trap.code {
exc::SYS => (ExitReason::SYSCALL, ExitAccess::None),
exc::BP => (ExitReason::BREAKPOINT, ExitAccess::None),
exc::RI | exc::CPU | exc::OV => (ExitReason::FAULT, ExitAccess::None),
exc::ADEL | exc::TLBL | exc::IBE => (ExitReason::FAULT, ExitAccess::Read),
exc::ADES | exc::TLBS | exc::MOD | exc::DBE => (ExitReason::FAULT, ExitAccess::Write),
_ => return None,
};
if !self.exits.contains(reason) {
return None;
}
let exit = Exit::new(reason, u64::from(self.this_pc), 4).with_detail(u64::from(trap.code));
match (access, trap.bad_vaddr) {
(ExitAccess::None, _) | (_, None) => Some(exit),
(_, Some(addr)) => Some(exit.with_access(u64::from(addr), access)),
}
}
#[inline]
fn charge(&mut self) {
self.used += 1;
self.st.cycles = self.st.cycles.wrapping_add(1);
}
#[inline]
fn reg(&self, i: u32) -> u32 {
self.st.regs[(i & 31) as usize]
}
#[inline]
fn set_reg(&mut self, i: u32, value: u32) {
if i & 31 != 0 {
self.st.regs[(i & 31) as usize] = value;
}
}
fn settle(&mut self) {
if let Some(load) = self.delayed.take() {
self.settled = Some((load.reg & 31, self.reg(load.reg)));
self.set_reg(load.reg, load.value);
}
}
fn deliver(&mut self, reg: u32, value: u32) {
if self.cfg.arch.load_interlock {
self.set_reg(reg, value);
return;
}
if let Some((settled, old)) = self.settled
&& settled == reg & 31
{
self.set_reg(settled, old);
self.settled = None;
}
self.issued = Some(PendingLoad { reg, value });
}
fn merge_source(&self, rt: u32) -> u32 {
self.reg(rt)
}
fn translate(&mut self, vaddr: u32, kind: Access) -> Result<u32, Trap> {
let segment = Segment::of(vaddr);
if !self.st.cp0.kernel_mode() && !segment.user_accessible() {
return Err(Trap::address(kind.address_error(), vaddr));
}
if !segment.mapped() {
return Ok(Segment::unmapped_phys(vaddr));
}
if !self.cfg.arch.tlb {
return Ok(vaddr);
}
let asid = self.st.cp0.asid();
match self.st.tlb.lookup(vaddr, asid) {
Lookup::Hit { pfn, writable, .. } => {
if kind == Access::Store && !writable {
return Err(Trap {
code: exc::MOD,
bad_vaddr: Some(vaddr),
refill: false,
ce: 0,
tlb: true,
});
}
Ok(pfn | (vaddr & (PAGE_SIZE - 1)))
}
Lookup::Invalid => Err(Trap {
code: kind.tlb_error(),
bad_vaddr: Some(vaddr),
refill: false,
ce: 0,
tlb: true,
}),
Lookup::Conflict => {
self.st.cp0.status |= status::TS;
Err(Trap {
code: kind.tlb_error(),
bad_vaddr: Some(vaddr),
refill: segment.uses_refill_vector(),
ce: 0,
tlb: true,
})
}
Lookup::Miss => Err(Trap {
code: kind.tlb_error(),
bad_vaddr: Some(vaddr),
refill: segment.uses_refill_vector(),
ce: 0,
tlb: true,
}),
}
}
fn isolated(&self) -> Option<bool> {
if self.st.cp0.status & status::ISC == 0 {
None
} else {
Some(self.st.cp0.status & status::SWC != 0)
}
}
fn cache_offsets(len: usize, phys: u32, width: Width, endian: Endian) -> [(usize, u32); 4] {
let mask = (len - 1) as u32;
let bytes = width.bytes() as u32;
let mut out = [(0usize, 0u32); 4];
for i in 0..bytes {
let shift = if endian.is_big() {
8 * (bytes - 1 - i)
} else {
8 * i
};
out[i as usize] = ((phys.wrapping_add(i) & mask) as usize, shift);
}
out
}
fn cache_read(&mut self, phys: u32, width: Width, swapped: bool, endian: Endian) -> u32 {
self.charge();
let array = if swapped {
&self.st.icache
} else {
&self.st.dcache
};
if array.is_empty() {
return 0;
}
let plan = Self::cache_offsets(array.len(), phys, width, endian);
let mut value = 0u32;
for (at, shift) in plan.iter().take(width.bytes() as usize) {
value |= u32::from(array[*at]) << shift;
}
value
}
fn cache_write(&mut self, phys: u32, width: Width, value: u32, swapped: bool, endian: Endian) {
self.charge();
let array = if swapped {
&mut self.st.icache
} else {
&mut self.st.dcache
};
if array.is_empty() {
return;
}
let plan = Self::cache_offsets(array.len(), phys, width, endian);
for (at, shift) in plan.iter().take(width.bytes() as usize) {
array[*at] = (value >> shift) as u8;
}
}
fn read(&mut self, vaddr: u32, width: Width, kind: Access) -> Result<u32, Trap> {
if !width.is_aligned(u64::from(vaddr)) {
return Err(Trap::address(kind.address_error(), vaddr));
}
let phys = self.translate(vaddr, kind)?;
if kind != Access::Fetch
&& let Some(swapped) = self.isolated()
{
let endian = self.st.cp0.data_endian(self.cfg.endian);
return Ok(self.cache_read(phys, width, swapped, endian));
}
self.charge();
match self.space.read(u64::from(phys), width, self.attrs) {
Ok(v) => Ok(self.reversed(v as u32, width, kind)),
Err(_) => {
self.st.faults = self.st.faults.wrapping_add(1);
Err(Trap::address(kind.bus_error(), vaddr))
}
}
}
fn write(&mut self, vaddr: u32, width: Width, value: u32) -> Result<(), Trap> {
if !width.is_aligned(u64::from(vaddr)) {
return Err(Trap::address(exc::ADES, vaddr));
}
let phys = self.translate(vaddr, Access::Store)?;
if let Some(swapped) = self.isolated() {
let endian = self.st.cp0.data_endian(self.cfg.endian);
self.cache_write(phys, width, value, swapped, endian);
return Ok(());
}
let value = self.reversed(value, width, Access::Store);
self.charge();
match self
.space
.write(u64::from(phys), width, u64::from(value), self.attrs)
{
Ok(()) => Ok(()),
Err(_) => {
self.st.faults = self.st.faults.wrapping_add(1);
Err(Trap::address(exc::DBE, vaddr))
}
}
}
fn reversed(&self, value: u32, width: Width, kind: Access) -> u32 {
if kind == Access::Fetch || self.st.cp0.data_endian(self.cfg.endian) == self.cfg.endian {
return value;
}
match width {
Width::U16 => u32::from((value as u16).swap_bytes()),
Width::U32 => value.swap_bytes(),
_ => value,
}
}
fn store_partial(
&mut self,
addr: u32,
value: u32,
left: bool,
endian: Endian,
) -> Result<(), Trap> {
let b = addr & 3;
let big = endian.is_big();
let (first, count) = match (left, big) {
(true, true) | (false, false) => (b, 4 - b),
_ => (0, b + 1),
};
let aligned = addr & !3;
self.charge();
for i in 0..count {
let take = match (left, big) {
(true, true) => 3 - i,
(true, false) => i + 3 - b,
(false, true) => b - i,
(false, false) => i,
};
let datum = u64::from((value >> (8 * take)) & 0xff);
let at = aligned.wrapping_add(first + i);
let phys = self.translate(at, Access::Store)?;
if let Some(swapped) = self.isolated() {
self.cache_write(phys, Width::U8, datum as u32, swapped, endian);
continue;
}
if self
.space
.write(u64::from(phys), Width::U8, datum, self.attrs)
.is_err()
{
self.st.faults = self.st.faults.wrapping_add(1);
return Err(Trap::address(exc::DBE, at));
}
}
Ok(())
}
fn fetch(&mut self, pc: u32) -> Result<u32, Trap> {
self.read(pc, Width::U32, Access::Fetch)
}
fn pending_interrupt(&self) -> Option<u32> {
if !self.st.cp0.interrupts_enabled() {
return None;
}
let ready = self.st.cp0.ready_interrupts(self.lines.hw());
if ready == 0 { None } else { Some(ready) }
}
fn enter_exception(&mut self, trap: Trap) {
let bd = self.in_delay;
let epc = if bd {
self.this_pc.wrapping_sub(4)
} else {
self.this_pc
};
let cp0 = &mut self.st.cp0;
cp0.epc = epc;
cp0.cause = (cp0.cause & !(cause_bits::EXC_CODE | cause_bits::CE | cause_bits::BD))
| ((trap.code << cause_bits::EXC_SHIFT) & cause_bits::EXC_CODE)
| ((trap.ce << cause_bits::CE_SHIFT) & cause_bits::CE)
| if bd { cause_bits::BD } else { 0 };
if let Some(vaddr) = trap.bad_vaddr {
cp0.bad_vaddr = vaddr;
if trap.tlb {
cp0.entry_hi = (cp0.entry_hi & 0x0000_0fc0) | (vaddr & 0xffff_f000);
cp0.set_context_vpn(vaddr);
}
}
cp0.push_mode();
let bev = cp0.status & status::BEV != 0;
let vector = match (trap.refill, bev) {
(true, false) => REFILL_VECTOR,
(true, true) => REFILL_VECTOR_BEV,
(false, false) => GENERAL_VECTOR,
(false, true) => GENERAL_VECTOR_BEV,
};
self.st.pc = vector;
self.st.next_pc = vector.wrapping_add(4);
self.st.in_delay = false;
self.issued = None;
}
#[allow(clippy::too_many_lines)]
fn execute(&mut self) -> Result<(), Trap> {
let word = self.fetch(self.this_pc)?;
self.st.pc = self.st.next_pc;
self.st.next_pc = self.st.next_pc.wrapping_add(4);
self.st.in_delay = false;
let insn = isa::decode(word).ok_or(Trap::bare(exc::RI))?;
self.check_requirement(insn.req)?;
let rs = isa::rs(word);
let rt = isa::rt(word);
let rd = isa::rd(word);
let a = self.reg(rs);
let b = self.reg(rt);
self.settle();
let delay_pc = self.st.pc;
let link = delay_pc.wrapping_add(4);
match insn.op {
Op::Sll => self.set_reg(rd, b << isa::sa(word)),
Op::Srl => self.set_reg(rd, b >> isa::sa(word)),
Op::Sra => self.set_reg(rd, ((b as i32) >> isa::sa(word)) as u32),
Op::Sllv => self.set_reg(rd, b << (a & 31)),
Op::Srlv => self.set_reg(rd, b >> (a & 31)),
Op::Srav => self.set_reg(rd, ((b as i32) >> (a & 31)) as u32),
Op::Jr => self.branch_to(a),
Op::Jalr => {
self.set_reg(rd, link);
self.branch_to(a);
}
Op::Syscall => return Err(Trap::bare(exc::SYS)),
Op::Break => return Err(Trap::bare(exc::BP)),
Op::Mfhi => self.set_reg(rd, self.st.hi),
Op::Mflo => self.set_reg(rd, self.st.lo),
Op::Mthi => self.st.hi = a,
Op::Mtlo => self.st.lo = a,
Op::Mult => {
let p = i64::from(a as i32).wrapping_mul(i64::from(b as i32));
self.st.lo = p as u32;
self.st.hi = (p >> 32) as u32;
}
Op::Multu => {
let p = u64::from(a).wrapping_mul(u64::from(b));
self.st.lo = p as u32;
self.st.hi = (p >> 32) as u32;
}
Op::Div => {
let (hi, lo) = divide_signed(a as i32, b as i32);
self.st.hi = hi;
self.st.lo = lo;
}
Op::Divu => {
let (hi, lo) = divide_unsigned(a, b);
self.st.hi = hi;
self.st.lo = lo;
}
Op::Add => self.set_reg(rd, checked_add(a, b)?),
Op::Addu => self.set_reg(rd, a.wrapping_add(b)),
Op::Sub => self.set_reg(rd, checked_sub(a, b)?),
Op::Subu => self.set_reg(rd, a.wrapping_sub(b)),
Op::And => self.set_reg(rd, a & b),
Op::Or => self.set_reg(rd, a | b),
Op::Xor => self.set_reg(rd, a ^ b),
Op::Nor => self.set_reg(rd, !(a | b)),
Op::Slt => self.set_reg(rd, u32::from((a as i32) < (b as i32))),
Op::Sltu => self.set_reg(rd, u32::from(a < b)),
Op::Beq => {
if a == b {
self.branch_to(isa::branch_target(delay_pc, word));
}
}
Op::Bne => {
if a != b {
self.branch_to(isa::branch_target(delay_pc, word));
}
}
Op::Blez => {
if (a as i32) <= 0 {
self.branch_to(isa::branch_target(delay_pc, word));
}
}
Op::Bgtz => {
if (a as i32) > 0 {
self.branch_to(isa::branch_target(delay_pc, word));
}
}
Op::Bltz => {
if (a as i32) < 0 {
self.branch_to(isa::branch_target(delay_pc, word));
}
}
Op::Bgez => {
if (a as i32) >= 0 {
self.branch_to(isa::branch_target(delay_pc, word));
}
}
Op::Bltzal | Op::Bgezal => {
let taken = if insn.op == Op::Bltzal {
(a as i32) < 0
} else {
(a as i32) >= 0
};
self.set_reg(31, link);
if taken {
self.branch_to(isa::branch_target(delay_pc, word));
}
}
Op::J => self.branch_to(isa::jump_target(delay_pc, word)),
Op::Jal => {
self.set_reg(31, link);
self.branch_to(isa::jump_target(delay_pc, word));
}
Op::Addi => self.set_reg(rt, checked_add(a, isa::simm(word))?),
Op::Addiu => self.set_reg(rt, a.wrapping_add(isa::simm(word))),
Op::Slti => self.set_reg(rt, u32::from((a as i32) < (isa::simm(word) as i32))),
Op::Sltiu => self.set_reg(rt, u32::from(a < isa::simm(word))),
Op::Andi => self.set_reg(rt, a & isa::imm(word)),
Op::Ori => self.set_reg(rt, a | isa::imm(word)),
Op::Xori => self.set_reg(rt, a ^ isa::imm(word)),
Op::Lui => self.set_reg(rt, isa::imm(word) << 16),
Op::Lb => {
let v = self.read(a.wrapping_add(isa::simm(word)), Width::U8, Access::Load)?;
self.deliver(rt, v as u8 as i8 as i32 as u32);
}
Op::Lbu => {
let v = self.read(a.wrapping_add(isa::simm(word)), Width::U8, Access::Load)?;
self.deliver(rt, v & 0xff);
}
Op::Lh => {
let v = self.read(a.wrapping_add(isa::simm(word)), Width::U16, Access::Load)?;
self.deliver(rt, v as u16 as i16 as i32 as u32);
}
Op::Lhu => {
let v = self.read(a.wrapping_add(isa::simm(word)), Width::U16, Access::Load)?;
self.deliver(rt, v & 0xffff);
}
Op::Lw => {
let v = self.read(a.wrapping_add(isa::simm(word)), Width::U32, Access::Load)?;
self.deliver(rt, v);
}
Op::Lwl | Op::Lwr => {
let addr = a.wrapping_add(isa::simm(word));
let word_value = self.read(addr & !3, Width::U32, Access::Load)?;
let endian = self.st.cp0.data_endian(self.cfg.endian);
let old = self.merge_source(rt);
let merged = if insn.op == Op::Lwl {
isa::lwl(old, word_value, addr, endian)
} else {
isa::lwr(old, word_value, addr, endian)
};
self.deliver(rt, merged);
}
Op::Sb => self.write(a.wrapping_add(isa::simm(word)), Width::U8, b & 0xff)?,
Op::Sh => self.write(a.wrapping_add(isa::simm(word)), Width::U16, b & 0xffff)?,
Op::Sw => self.write(a.wrapping_add(isa::simm(word)), Width::U32, b)?,
Op::Swl | Op::Swr => {
let addr = a.wrapping_add(isa::simm(word));
let endian = self.st.cp0.data_endian(self.cfg.endian);
self.store_partial(addr, b, insn.op == Op::Swl, endian)?;
}
Op::Mfc0 => {
let v = self.read_cp0(rd);
self.deliver(rt, v);
}
Op::Mtc0 => self.write_cp0(rd, b),
Op::Tlbr => {
let entry = self.st.tlb.entry(self.tlb_index());
self.st.cp0.entry_hi = entry.hi;
self.st.cp0.entry_lo = entry.lo;
}
Op::Tlbwi => {
let index = self.tlb_index();
self.write_tlb(index);
}
Op::Tlbwr => {
let index = self.st.cp0.random;
self.write_tlb(index);
}
Op::Tlbp => {
match self.st.tlb.probe(self.st.cp0.entry_hi) {
Some(i) => self.st.cp0.index = (i & 0x3f) << 8,
None => self.st.cp0.index |= 0x8000_0000,
}
}
Op::Rfe => {
self.st.cp0.pop_mode();
self.attrs = self.attrs.with_privileged(self.st.cp0.kernel_mode());
}
Op::Cop1 | Op::Cop2 | Op::Cop3 => {
return Err(Trap::coprocessor(insn.req.coprocessor().unwrap_or(0)));
}
Op::Lwc1 | Op::Lwc2 | Op::Lwc3 | Op::Swc1 | Op::Swc2 | Op::Swc3 => {
return Err(Trap::coprocessor(insn.req.coprocessor().unwrap_or(0)));
}
}
if insn.is_branch() {
self.st.in_delay = true;
}
Ok(())
}
fn branch_to(&mut self, target: u32) {
self.st.next_pc = target;
}
fn check_requirement(&self, req: Req) -> Result<(), Trap> {
match req {
Req::Base => Ok(()),
Req::Cop0 | Req::Tlb => {
if !self.st.cp0.coprocessor_usable(0) {
return Err(Trap::coprocessor(0));
}
if req == Req::Tlb && !self.cfg.arch.tlb {
return Err(Trap::bare(exc::RI));
}
Ok(())
}
Req::Cop1 | Req::Cop2 | Req::Cop3 => {
let n = req.coprocessor().unwrap_or(0);
if self.cfg.arch.coprocessor(n) && self.st.cp0.coprocessor_usable(n) {
Ok(())
} else {
Err(Trap::coprocessor(n))
}
}
}
}
fn tlb_index(&self) -> u32 {
(self.st.cp0.index >> 8) & ((TLB_ENTRIES - 1) as u32)
}
fn write_tlb(&mut self, index: u32) {
let entry = TlbEntry {
hi: self.st.cp0.entry_hi,
lo: self.st.cp0.entry_lo,
};
self.st.tlb.set_entry(index, entry);
}
fn read_cp0(&self, n: u32) -> u32 {
let cp0 = &self.st.cp0;
match n {
reg::INDEX => cp0.index,
reg::RANDOM => (cp0.random & 0x3f) << 8,
reg::ENTRY_LO => cp0.entry_lo,
reg::CONTEXT => cp0.context,
reg::BAD_VADDR => cp0.bad_vaddr,
reg::ENTRY_HI => cp0.entry_hi,
reg::STATUS => cp0.status,
reg::CAUSE => cp0.cause_with(self.lines.hw()),
reg::EPC => cp0.epc,
reg::PRID => cp0.prid,
reg::BPC => cp0.debug[0],
reg::BDA => cp0.debug[1],
reg::JUMP_DEST => cp0.debug[2],
reg::DCIC => cp0.debug[3],
reg::BDAM => cp0.debug[4],
reg::BPCM => cp0.debug[5],
_ => 0,
}
}
fn write_cp0(&mut self, n: u32, value: u32) {
let usable = self.cfg.arch.coprocessor_mask();
let cp0 = &mut self.st.cp0;
match n {
reg::INDEX => cp0.index = value & 0x8000_3f00,
reg::RANDOM | reg::BAD_VADDR | reg::PRID => {}
reg::ENTRY_LO => cp0.entry_lo = value & 0xffff_ff00,
reg::CONTEXT => cp0.context = (cp0.context & 0x001f_fffc) | (value & 0xffe0_0000),
reg::ENTRY_HI => cp0.entry_hi = value & 0xffff_ffc0,
reg::STATUS => {
let mask = status::WRITABLE & usable;
cp0.status = (cp0.status & !mask) | (value & mask);
}
reg::CAUSE => {
cp0.cause = (cp0.cause & !cause_bits::WRITABLE) | (value & cause_bits::WRITABLE);
}
reg::EPC => cp0.epc = value,
reg::BPC => cp0.debug[0] = value,
reg::BDA => cp0.debug[1] = value,
reg::JUMP_DEST => cp0.debug[2] = value,
reg::DCIC => cp0.debug[3] = value,
reg::BDAM => cp0.debug[4] = value,
reg::BPCM => cp0.debug[5] = value,
_ => {}
}
if n == reg::STATUS {
self.attrs = self.attrs.with_privileged(self.st.cp0.kernel_mode());
}
}
}
fn checked_add(a: u32, b: u32) -> Result<u32, Trap> {
match (a as i32).checked_add(b as i32) {
Some(v) => Ok(v as u32),
None => Err(Trap::bare(exc::OV)),
}
}
fn checked_sub(a: u32, b: u32) -> Result<u32, Trap> {
match (a as i32).checked_sub(b as i32) {
Some(v) => Ok(v as u32),
None => Err(Trap::bare(exc::OV)),
}
}
fn divide_signed(a: i32, b: i32) -> (u32, u32) {
if b == 0 {
let lo: i32 = if a >= 0 { -1 } else { 1 };
(a as u32, lo as u32)
} else if a == i32::MIN && b == -1 {
(0, i32::MIN as u32)
} else {
((a % b) as u32, (a / b) as u32)
}
}
fn divide_unsigned(a: u32, b: u32) -> (u32, u32) {
if b == 0 {
(a, u32::MAX)
} else {
(a % b, a / b)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn division_by_zero_does_not_trap_and_has_defined_results() {
assert_eq!(divide_signed(7, 0), (7, 0xffff_ffff));
assert_eq!(divide_signed(-7, 0), ((-7i32) as u32, 1));
assert_eq!(divide_signed(0, 0), (0, 0xffff_ffff));
assert_eq!(divide_unsigned(7, 0), (7, 0xffff_ffff));
assert_eq!(divide_unsigned(0, 0), (0, 0xffff_ffff));
}
#[test]
fn the_one_overflowing_division_has_a_defined_result() {
assert_eq!(divide_signed(i32::MIN, -1), (0, 0x8000_0000));
assert_eq!(divide_signed(i32::MIN, 1), (0, 0x8000_0000));
}
#[test]
fn ordinary_division_truncates_towards_zero() {
assert_eq!(divide_signed(7, 2), (1, 3));
assert_eq!(divide_signed(-7, 2), ((-1i32) as u32, (-3i32) as u32));
assert_eq!(divide_signed(7, -2), (1, (-3i32) as u32));
assert_eq!(divide_unsigned(7, 2), (1, 3));
}
#[test]
fn overflow_is_signed_and_the_unsigned_forms_do_not_check_it() {
assert!(checked_add(0x7fff_ffff, 1).is_err());
assert_eq!(checked_add(0xffff_ffff, 1), Ok(0));
assert!(checked_sub(0x8000_0000, 1).is_err());
assert_eq!(checked_sub(0, 1), Ok(0xffff_ffff));
}
}