diff --git a/src/main.rs b/src/main.rs index b58db88..d970561 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,36 +13,73 @@ const BIT_X: u8 = 8; // (Not used) - user mode accessible const BIT_U: u8 = 16; +/* // (Not used) - global mapping const BIT_G: u8 = 32; // (Not presently used) accessed -- set if we've touched this since last time A cleared const BIT_A: u8 = 64; -// (Will be used for JIT on writes to executable pags) dirty -- written since last time D cleared +// (Not presently used)dirty -- written since last time D cleared +// Note each core/hart has its own dirty state for local JIT const BIT_D: u8 = 128; -/* -// (Not used) reserved for supervisor +// (Not used or room for it) reserved for supervisor const BITS_RSW_LO: i64 = 256; const BITS_RSW_HI: i64 = 512; const BITS_RSW: i64 = BITS_RSW_LO | BITS_RSW_HI; */ + +#[repr(C)] +#[derive(Clone, Copy)] +struct PageTableEntry { + flags: u8 +} + +impl PageTableEntry { + fn new(flags: u8) -> Self { + return Self { + flags + } + } + + fn as_u8(&self) -> u8 { + return self.flags; + } + + fn is_valid(&self) -> bool { + return self.flags & BIT_V == BIT_V; + } + + fn is_readable(&self) -> bool { + return self.flags & (BIT_V | BIT_R) == (BIT_V | BIT_R); + } + + fn is_writable(&self) -> bool { + return self.flags & (BIT_V | BIT_R | BIT_W) == (BIT_V | BIT_R | BIT_W); + } + + fn is_executable(&self) -> bool { + return self.flags & (BIT_V | BIT_R | BIT_X) == (BIT_V | BIT_R | BIT_X); + } + +} + type ExecutorFunc = fn(i64, &mut CoreState, &mut MachineState) -> i64; #[repr(C)] struct MachineState { memory: Vec, - pages: Vec, + pages: Vec, } /** * Note that physical memory accessors can traverse page boundaries; * we lay out linear memory from 0 to +4 gigabytes and will allocate * as many page table entries as are needed to cover RAM. These eat - * up an extra 1 byte per 4 kilobytes of address space used, initially - * allocating enough for all physical memory initially allocated. + * up an extra 1 byte per 4 KiB of address space used (1 MiB per 4 GiB), + * initially allocating enough for all physical memory allocated. * * This will be relatively space-inefficient for sparse address spaces * in the range of several gigabytes and more but requires only one @@ -52,7 +89,7 @@ struct MachineState { * page tables, even if running on different threads. */ impl MachineState { - fn restore(memory: Vec, pages: Vec) -> Self { + fn new_with_state(memory: Vec, pages: Vec) -> Self { if ((memory.len() >> PAGE_BITS) << PAGE_BITS) != memory.len() { panic!("memory size must be a multiple of 4096 bytes"); } @@ -67,23 +104,23 @@ impl MachineState { fn new(memory_size: usize) -> Self { let memory = vec![0u8; memory_size]; - let pages = vec![0u8; memory_size >> PAGE_BITS]; - return Self::restore(memory, pages); + let pages = vec![PageTableEntry::new(0); memory_size >> PAGE_BITS]; + return Self::new_with_state(memory, pages); } - fn get_page_table_entry(&mut self, address: usize) -> u32 { + fn get_page_table_entry(&mut self, address: usize) -> PageTableEntry { let page = address >> PAGE_BITS; if page < self.pages.len() { - return self.pages[page] as u32; + return self.pages[page]; } else { - return 0; + return PageTableEntry::new(0); } } - fn set_page_table_entry(&mut self, address: usize, bits: u32) { + fn set_page_table_entry(&mut self, address: usize, entry: PageTableEntry) { let page = address >> PAGE_BITS; if page < self.pages.len() { - self.pages[address >> 12] = bits as u8; + self.pages[address >> 12] = entry; } else { panic!("@fixme: handle attempts to expand address space"); } @@ -188,9 +225,6 @@ struct CoreState { // bytes separately f: [f64; 32], - // 4096 csrs? no we're not gonna store them all - satp: i64, - // * fflags, accrued exceptions: bits 0-4 // * nx: bit 0 // * uf: bit 1 @@ -206,11 +240,18 @@ struct CoreState { // Because function references are linked separately on // each thread in WebAssembly, this has to live in each // core's state separately. - executors: HashMap + executors: HashMap, + + // Local dirty flags for JIT pages. + // When we get a fence.i instructrion, look for all dirty + // pages and invalidate any functions including them + // Takes up to 1 byte per 4 KiB (1 MiB per 4 GiB) per thread. + // Could be made more compact if only 1 bit is needed. + dirty: Vec } impl CoreState { - fn new() -> Self { + fn new(machine: &MachineState) -> Self { return Self { x: [ 0, 0, 0, 0, 0, 0, 0, 0, @@ -218,7 +259,6 @@ impl CoreState { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], - satp: 0, f: [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, @@ -226,7 +266,8 @@ impl CoreState { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ], fcsr: 0, - executors: HashMap::new() + executors: HashMap::new(), + dirty: vec![0u8; machine.memory.len()] } } } @@ -300,7 +341,7 @@ extern "C" fn interpreter( fn main() { let size = 8 * 1024 * 1024; let mut machine = MachineState::new(size); - let mut core = CoreState::new(); + let mut core = CoreState::new(&machine); let pc = interpreter(&mut machine, &mut core, 0); println!("Ended with PC {}", pc); }