riscii

An emulator for the RISC II
Log | Files | Refs | LICENSE

commit 81a3dd7ab7740519a1546f99521613d5e60163e5
parent eb1222854cc0be2f761dfa2d7552c24eab42368a
Author: Ryan Jeffrey <ryan@ryanmj.xyz>
Date:   Fri, 10 Jun 2022 15:07:47 -0700

Output file format, read/write emulator structs, util file.

Diffstat:
Msrc/config.rs | 11++---------
Msrc/main.rs | 1+
Asrc/memory.rs | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/r2d2.rs | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/register.rs | 106++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Asrc/util.rs | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 323 insertions(+), 21 deletions(-)

diff --git a/src/config.rs b/src/config.rs @@ -20,11 +20,12 @@ extern crate serde_derive; extern crate toml; use std::env; -use std::env::JoinPathsError; use std::ffi::OsString; use std::fmt; use std::fs; use std::path::Path; +use util; +use util::concat_paths; use self::serde_derive::{Deserialize, Serialize}; @@ -243,11 +244,3 @@ fn os_string_result_to_strings(r: Result<String, OsString>) -> Result<String, St Ok(rr) => Ok(rr.to_string()), } } - -fn concat_paths(base: &String, rest: &String) -> Result<String, String> { - let p = Path::new(&base).join(&rest); - match p.to_str() { - None => Err(format!("{} and {} joined is not valid utf8", base, rest)), - Some(s) => Ok(s.to_string()), - } -} diff --git a/src/main.rs b/src/main.rs @@ -20,6 +20,7 @@ mod main_test; mod config; mod sdl; +mod util; use core::convert::TryInto; use std::fs; diff --git a/src/memory.rs b/src/memory.rs @@ -0,0 +1,44 @@ +// RISC II memory scheme. +// (C) Ryan Jeffrey <ryan@ryanmj.xyz>, 2022 +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. + +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +// Struct definitions. + +use util::File; + +/// The real memory of the RISC II emulator. +struct Memory(Vec<u8>); + +// Struct impls. + +impl Memory { + /// Create a memory object. + /// # Arguments + /// * `config` - A configuration object that determines the size of + /// the memory object. + pub fn new(config: &Config) -> Self { + Self { + 0: vec![0u8; config.mem], + } + } + + pub fn from_vec(memory: &Vec<u8>) { + Self { 0: memory } + } + + fn write_to_file(&mut self, file: &mut File) -> Result<(), String> { + file.write_vec(&self.0)?; + Ok(()) + } +} diff --git a/src/r2d2.rs b/src/r2d2.rs @@ -0,0 +1,61 @@ +// Binary memdmp format. +// (C) Ryan Jeffrey <ryan@ryanmj.xyz>, 2022 +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. + +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +// Public function definitions. + +use memory::Memory; +use register::State; +use util::{concat_paths, get_unix_timestamp, File}; + +use std::time::{SystemTime, UNIX_EPOCH}; + +use std::fs::OpenOptions; + +/// Write a save file to the cache directory. On success, return the name of +/// the save file. Return an error string on failure. +/// # Arguments +/// * `state` - Register state to save. +/// * `mem` - Memory state to save. +pub fn write(config: &Config, state: &State, mem: &Memory) -> Result<String, String> { + // TODO human readable dates. + let output_file = concat_paths( + &config.cache_path, + &format!("{:?}.r2d2", get_unix_timestamp()?), + )?; + let file = File::open_ops(output_file, &OpenOptions::new().write(true))?; + + file.write_buf(&state.to_buf())?; + mem.write_to_file(&file)?; + Ok(output_file) +} + +pub fn read(config: &Config, which: &String) -> Result<(State, Memory), String> { + let mut file = File::open(&which)?; + let mut register_state = [0u8; register::TOTAL_NUM_REGISTERS]; + if file.read(register_state)? < register_state.len() { + return Err(format!( + "Archive {} is not large enough to have a register window.", + which + )); + } + + let metadata = file.get_metadata()?; + let mut memory = vec![0u8; metadata.size() as usize - register::TOTAL_NUM_REGISTERS]; + + file.read_into_vec(&mut memory)?; + + // TODO make sure there is no deep copying of memory happening + Ok(State::from_buf(register_state), Memory::from_vec(&memory)) +} diff --git a/src/register.rs b/src/register.rs @@ -13,19 +13,26 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. -/// The number of register windows the RISCII supports. -const NUM_WINDOWS: usize = 6; +/// The number of register window_regs the RISCII supports. +pub const NUM_WINDOW_REGS: usize = 6; /// The number of local registers per window. -const NUM_LOCALS: usize = 10; +pub const NUM_LOCALS: usize = 10; /// The number of registers shared with the previous register window (input arguments). -const NUM_SHARED_PREV: usize = 6; +pub const NUM_SHARED_PREV: usize = 6; /// The number of registers shared with the next register window (output arguments). -const NUM_SHARED_NEXT: usize = 6; +pub const NUM_SHARED_NEXT: usize = 6; /// The number of registers per window. -const WINDOW_SIZE: usize = NUM_LOCALS + NUM_SHARED_PREV + NUM_SHARED_NEXT; +pub const WINDOW_SIZE: usize = NUM_LOCALS + NUM_SHARED_PREV + NUM_SHARED_NEXT; /// Number of global registers. -const NUM_GLOBALS: usize = 10; - +pub const NUM_GLOBALS: usize = 10; +/// Number of general purpose registers that exist in window_regs. +pub const NUM_WINDOW_REGISTERS: usize = NUM_WINDOW_REGS * (NUM_LOCALS + NUM_SHARED_NEXT); +/// Number of "special" registers (cwp, swp, sp, etc.). +pub const NUM_SPECIAL_REGISTERS: usize = 2; +/// The total number of registers on the system. +pub const TOTAL_NUM_REGISTERS: usize = NUM_SPECIAL_REGISTERS + NUM_GLOBALS + NUM_WINDOW_REGISTERS; +/// The size of a register::State object (in bytes). +pub const SIZEOF_STATE: usize = TOTAL_NUM_REGISTERS * 4; // Struct definitions. /// A RISC II 32bit register. @@ -40,26 +47,101 @@ struct State { /// Global registers. globals: [Register; NUM_GLOBALS], /// Register window stack. - locals: [Register; NUM_WINDOWS * (NUM_LOCALS + NUM_SHARED_NEXT)], + window_regs: [Register; NUM_WINDOW_REGISTERS], } // Struct implementations. impl State { + /// Create a 0'd out register window. pub fn new() -> State { State { cwp: 0, swp: 0, globals: [0; NUM_GLOBALS], - locals: [0; NUM_WINDOWS * (NUM_LOCALS + NUM_SHARED_NEXT)], + window_regs: [0; NUM_WINDOW_REGISTERS], } } - fn inc_cwp(&self) { + /// Create a register state from a buffer. + /// # Arguments + /// * `buffer` - A byte buffer that is the size of the sum of of register::State's + /// members (in bytes) (see `SIZEOF_STATE`). + /// The registers should appear in the following order: + /// - CWP + /// - SWP + /// - Global registers + /// - Window registers + pub fn from_buf(buf: [u8; SIZEOF_STATE]) -> Self { + // Offset used for gloabls and window_regs. + let mut cur_offset = NUM_SPECIAL_REGISTERS * 4; + Self { + cwp: u32::from_be_bytes(buf[..4].try_into().unwrap()), + swp: u32::from_be_bytes(buf[4..8].try_into().unwrap()), + globals: { + let mut result = [0u32; NUM_GLOBALS]; + for _ in NUM_GLOBALS { + result[i] = + u32::from_be_bytes(buf[cur_offset..cur_offset + 4].try_into().unwrap()); + cur_offset += 4; + } + result + }, + window_regs: { + let mut result = [0u32; NUM_WINDOW_REGISTERS]; + for _ in NUM_WINDOW_REGISTERS { + result[i] = + u32::from_be_bytes(buf[cur_offset..cur_offset + 4].try_into().unwrap()); + cur_offset += 4; + } + result + }, + } + } + + fn to_buf(&self) -> [u8; SIZEOF_STATE] { + let mut result: [u8; SIZEOF_STATE] = [ + self.cwp.to_be_bytes(), + self.swp.to_be_bytes(), + { + let mut tmp = [u8; NUM_GLOBALS * 4]; + for i in NUM_GLOBALS { + let bytes = self.globals[i].to_be_bytes(); + tmp[i] = bytes[0]; + tmp[i + 1] = bytes[1]; + tmp[i + 1] = bytes[2]; + tmp[i + 1] = bytes[3]; + } + tmp + }, + { + let mut tmp = [u8; NUM_WINDOW_REGISTERS * 4]; + for i in NUM_WINDOW_REGISTERS { + let bytes = self.window_regs[i].to_be_bytes(); + tmp[i] = bytes[0]; + tmp[i + 1] = bytes[1]; + tmp[i + 1] = bytes[2]; + tmp[i + 1] = bytes[3]; + } + tmp + }, + ]; + result + } + + fn push_reg_window(&mut self) { self.cwp += 1; while self.cwp >= self.swp { - // TODO save the top window into memory. + // TODO save the top window_regs into memory. self.swp += 1; } } + + fn pop_reg_window(&mut self) { + self.cwp -= 1; + while self.swp >= self.cwp { + // TODO load window_regs from memory. + self.swp -= 1; + } + } } diff --git a/src/util.rs b/src/util.rs @@ -0,0 +1,121 @@ +// Utility functions. +// (C) Ryan Jeffrey <ryan@ryanmj.xyz>, 2022 +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. + +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use std::fs; +use std::fs::{Metadata, OpenOptions}; +use std::io::{Read, Write}; +use std::path::Path; +use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH}; + +// Public struct definitions. + +pub struct File { + file: fs::File, + path: String, +} + +// Public function definitions. + +pub fn read_file_path(path: &String) -> Result<Vec<u8>, String> { + File::open(&path)?.read_file() +} + +pub fn concat_paths(base: &String, rest: &String) -> Result<String, String> { + let p = Path::new(&base).join(&rest); + match p.to_str() { + None => Err(format!("{} and {} joined is not valid utf8", base, rest)), + Some(s) => Ok(s.to_string()), + } +} + +pub fn get_unix_timestamp() -> Result<Duration, String> { + match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(r) => Ok(r), + Err(e) => Err(format!("Could not format unix timestamp: {}", e)), + } +} + +// Struct impls. + +impl File { + pub fn open(path: &String) -> Result<Self, String> { + match fs::File::open(&path) { + Ok(r) => Ok(Self { + file: r, + path: format!("{}", path), + }), + Err(e) => Err(format!("Could not open file {}: {}", path, e)), + } + } + + pub fn open_ops(path: &String, ops: &OpenOptions) -> Result<Self, String> { + match ops.open(&path) { + Ok(r) => Ok(Self { + file: r, + path: format!("{}", path), + }), + Err(e) => Err(format!("Could not open file {}: {}", path, e)), + } + } + + fn read_into_vec(&mut self, buf: &mut Vec<u8>) -> Result<(), String> { + match self.file.read_exact(&mut buf[..]) { + Ok(r) => Ok(()), + Err(e) => Err(format!("Failed to read file {}, {}", self.path, e)), + } + } + + fn read_file(&mut self) -> Result<Vec<u8>, String> { + let metadata = self.get_metadata()?; + let mut result = vec![0u8; metadata.len() as usize]; + self.read_into_vec(&mut result)?; + + Ok(result) + } + + fn get_metadata(&mut self) -> Result<Metadata, String> { + match self.file.metadata() { + Ok(r) => Ok(r), + Err(e) => Err(format!("Could not read metadata for {}: {}", self.path, e)), + } + } + + fn read(&mut self, buf: &mut [u8]) -> Result<(), String> { + match self.file.read_exact(buf) { + Ok(r) => Ok(()), + Err(e) => Err(format!("Could not read buffer from {}: {}", self.path, e)), + } + } + + fn write_buf(&mut self, buf: &[u8]) -> Result<(), String> { + match self.file.write_all(buf) { + Ok(r) => Ok(()), + Err(e) => Err(format!( + "Could not write byte buffer to {}: {}", + self.path, e + )), + } + } + + fn write_vec(&mut self, buf: &Vec<u8>) -> Result<(), String> { + match self.file.write_all(&buf[..]) { + Ok(r) => Ok(()), + Err(e) => Err(format!( + "Could not write byte buffer to {}: {}", + self.path, e + )), + } + } +}