Files
xenia-rs/crates/xenia-memory/src/page_table.rs
MechaCat02 e9b2b57a44 xenia-memory: interior-mutable writes, page versioning, fenced ops
Re-shape MemoryAccess so write methods take &self and rely on interior
mutability (atomics in GuestMemory, Cell in test mocks). This unblocks
the &Arc<KernelState>-only execution model the CPU/HLE crates moved to.

GuestMemory grows: per-4 KiB-page write-version counter (page_version)
that the CPU's decode cache and the texture cache observe via Acquire,
fenced 32-bit/64-bit read/write helpers (Release on writer / Acquire on
reader) that PM4_EVENT_WRITE_SHD and the matching CPU consumers use to
synchronize fence publication, and broader page-table / heap accounting
needed by the new HLE allocators.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:27:13 +02:00

136 lines
4.1 KiB
Rust

use bitflags::bitflags;
/// Describes a single page in the page table.
/// Mirrors the C++ `PageEntry` union from memory.h:82-99.
#[derive(Clone, Copy, Default)]
pub struct PageEntry(u64);
impl PageEntry {
/// Reconstruct a [`PageEntry`] from its packed `u64` representation.
/// Used by [`crate::GuestMemory::is_mapped`] and `page_entry` after an
/// atomic load from the page table.
pub fn from_raw(raw: u64) -> Self {
Self(raw)
}
/// The packed `u64` representation, ready to atomically Release-store
/// into the page table.
pub fn raw(&self) -> u64 {
self.0
}
/// Base address of the allocated region in 4K pages (20 bits).
pub fn base_address(&self) -> u32 {
(self.0 & 0xFFFFF) as u32
}
pub fn set_base_address(&mut self, val: u32) {
self.0 = (self.0 & !0xFFFFF) | (val as u64 & 0xFFFFF);
}
/// Total number of pages in the allocated region (20 bits).
pub fn region_page_count(&self) -> u32 {
((self.0 >> 20) & 0xFFFFF) as u32
}
pub fn set_region_page_count(&mut self, val: u32) {
self.0 = (self.0 & !(0xFFFFF << 20)) | ((val as u64 & 0xFFFFF) << 20);
}
/// Protection bits specified during region allocation (4 bits).
pub fn allocation_protect(&self) -> MemoryProtect {
MemoryProtect::from_bits_truncate(((self.0 >> 40) & 0xF) as u32)
}
pub fn set_allocation_protect(&mut self, val: MemoryProtect) {
self.0 = (self.0 & !(0xF << 40)) | ((val.bits() as u64 & 0xF) << 40);
}
/// Current protection bits (4 bits).
pub fn current_protect(&self) -> MemoryProtect {
MemoryProtect::from_bits_truncate(((self.0 >> 44) & 0xF) as u32)
}
pub fn set_current_protect(&mut self, val: MemoryProtect) {
self.0 = (self.0 & !(0xF << 44)) | ((val.bits() as u64 & 0xF) << 44);
}
/// Allocation state (2 bits).
pub fn state(&self) -> AllocationState {
AllocationState::from_bits_truncate(((self.0 >> 48) & 0x3) as u32)
}
pub fn set_state(&mut self, val: AllocationState) {
self.0 = (self.0 & !(0x3 << 48)) | ((val.bits() as u64 & 0x3) << 48);
}
pub fn is_committed(&self) -> bool {
self.state().contains(AllocationState::COMMIT)
}
pub fn is_reserved(&self) -> bool {
self.state().contains(AllocationState::RESERVE)
}
pub fn is_free(&self) -> bool {
self.state().is_empty()
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MemoryProtect: u32 {
const READ = 1 << 0;
const WRITE = 1 << 1;
const NO_CACHE = 1 << 2;
const WRITE_COMBINE = 1 << 3;
}
}
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AllocationState: u32 {
const RESERVE = 1 << 0;
const COMMIT = 1 << 1;
}
}
impl std::fmt::Debug for PageEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PageEntry")
.field("base_address", &format_args!("{:#x}", self.base_address()))
.field("region_page_count", &self.region_page_count())
.field("allocation_protect", &self.allocation_protect())
.field("current_protect", &self.current_protect())
.field("state", &self.state())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_page_entry_bitfields() {
let mut entry = PageEntry::default();
assert!(entry.is_free());
entry.set_base_address(0x100);
entry.set_region_page_count(0x10);
entry.set_allocation_protect(MemoryProtect::READ | MemoryProtect::WRITE);
entry.set_current_protect(MemoryProtect::READ);
entry.set_state(AllocationState::RESERVE | AllocationState::COMMIT);
assert_eq!(entry.base_address(), 0x100);
assert_eq!(entry.region_page_count(), 0x10);
assert_eq!(
entry.allocation_protect(),
MemoryProtect::READ | MemoryProtect::WRITE
);
assert_eq!(entry.current_protect(), MemoryProtect::READ);
assert!(entry.is_committed());
assert!(entry.is_reserved());
}
}