···1212// You should have received a copy of the GNU General Public License along with
1313// this program. If not, see <https://www.gnu.org/licenses/>.
1414//
1515-use std::path::{Path, PathBuf};
1515+use std::{
1616+ ffi::OsStr,
1717+ io,
1818+ path::{Path, PathBuf},
1919+};
16201717-fn main() {
2121+fn main() -> io::Result<()> {
1822 let mut output = PathBuf::from("./a.um");
19232024 let mut program = Vec::new();
···2630 }
2731 _ => {
2832 let path = Path::new(&arg);
2929- program.extend_from_slice(&match load_program(path) {
3030- Ok(p) => p,
3131- Err(error) => {
3232- eprintln!("{error}");
3333- std::process::exit(1);
3434- }
3535- });
3333+ program.extend_from_slice(&load_program(path)?);
3634 }
3735 }
3836 }
39374038 // Convert the program to bytes.
4141- let bytes: Vec<_> = program
4242- .into_iter()
4343- .flat_map(|word| word.to_be_bytes())
4444- .collect();
4545-4646- std::fs::write(&output, bytes).unwrap();
3939+ let bytes: Vec<_> = program.into_iter().flat_map(u32::to_be_bytes).collect();
4040+ std::fs::write(&output, bytes)
4741}
48424949-fn load_program(path: &Path) -> std::io::Result<Vec<u32>> {
5050- match path.extension().map(|ext| ext.as_encoded_bytes()) {
5151- Some(b"uasm") | Some(b"asm") => {
5252- let source = std::fs::read_to_string(path)?;
5353- let program = um::asm::assemble(&source);
5454- Ok(program)
5555- }
5656- _ => {
5757- let program = std::fs::read(path)?;
5858- Ok(um::conv::bytes_to_program(&program).unwrap())
5959- }
4343+fn load_program(path: &Path) -> io::Result<Vec<u32>> {
4444+ if let Some(b"uasm" | b"asm") = path.extension().map(OsStr::as_encoded_bytes) {
4545+ let source = std::fs::read_to_string(path)?;
4646+ let program = um::asm::assemble(&source);
4747+ Ok(program)
4848+ } else {
4949+ let program = std::fs::read(path)?;
5050+ Ok(um::conv::bytes_to_program(&program).unwrap())
6051 }
6152}
+9-1
src/conv.rs
···1212// You should have received a copy of the GNU General Public License along with
1313// this program. If not, see <https://www.gnu.org/licenses/>.
1414//
1515+1516const WORD_LEN: usize = std::mem::size_of::<u32>();
16171718#[derive(Debug)]
···19202021/// Converts a byte slice to a program.
2122///
2222-/// Returns `None` if the byte slice is not a multiple of 4 bytes in length.
2323+/// # Errors
2424+///
2525+/// Returns `Err(InvalidProgram)` if the program is not a whole number of 32-bit
2626+/// instructions.
2727+///
2828+//
2929+// This does not panic.
3030+#[allow(clippy::missing_panics_doc)]
2331pub fn bytes_to_program(bytes: &[u8]) -> Result<Vec<u32>, InvalidProgram> {
2432 if bytes.len().rem_euclid(WORD_LEN) != 0 {
2533 return Err(InvalidProgram);
+1-1
src/lib.rs
···15151616#[cfg(feature = "asm")]
1717pub mod asm;
1818+1819pub mod conv;
1920pub mod ops;
2021pub mod reg;
21222223mod universal_machine;
2323-2424pub use universal_machine::Um;
+10-4
src/main.rs
···1212// You should have received a copy of the GNU General Public License along with
1313// this program. If not, see <https://www.gnu.org/licenses/>.
1414//
1515+1616+#[cfg(feature = "asm")]
1717+mod asm;
1818+1919+mod conv;
1520mod ops;
1621mod reg;
1722mod universal_machine;
18231924use std::{path::Path, time::Instant};
2525+2026use universal_machine::Um;
21272228fn main() {
···5763 // Unfortunately this leads some wierd code generation fuckery which
5864 // makes the version without the 'asm' feature ~1-2 seconds slower
5965 // when running the sandmark program.
6060- Some(b"uasm") | Some(b"asm") => {
6666+ Some(b"uasm" | b"asm") => {
6167 let source = std::fs::read_to_string(path)?;
6262- Ok(um::asm::assemble(&source))
6868+ Ok(asm::assemble(&source))
6369 }
6470 _ => {
6571 let program = std::fs::read(path)?;
6666- Ok(um::conv::bytes_to_program(&program).unwrap())
7272+ Ok(conv::bytes_to_program(&program).unwrap())
6773 }
6874 }
6975}
···7177#[cfg(not(feature = "asm"))]
7278fn load_program(path: &Path) -> std::io::Result<Vec<u32>> {
7379 let program = std::fs::read(path)?;
7474- Ok(um::conv::bytes_to_program(&program).unwrap())
8080+ Ok(conv::bytes_to_program(&program).unwrap())
7581}
+19-16
src/ops.rs
···1212// You should have received a copy of the GNU General Public License along with
1313// this program. If not, see <https://www.gnu.org/licenses/>.
1414//
1515+1516use crate::reg::Register;
16171718#[derive(Clone, Copy, Debug, PartialEq, Eq)]
···157158 let a = Register::from_u8(((value >> 6) & 0x07) as u8);
158159 let b = Register::from_u8(((value >> 3) & 0x07) as u8);
159160 let c = Register::from_u8((value & 0x07) as u8);
160160- match value & 0xf0000000 {
161161- 0x00000000 => Self::ConditionalMove { a, b, c },
162162- 0x10000000 => Self::ArrayIndex { a, b, c },
163163- 0x20000000 => Self::ArrayAmendment { a, b, c },
164164- 0x30000000 => Self::Addition { a, b, c },
165165- 0x40000000 => Self::Multiplication { a, b, c },
166166- 0x50000000 => Self::Division { a, b, c },
167167- 0x60000000 => Self::NotAnd { a, b, c },
168168- 0x70000000 => Self::Halt,
169169- 0x80000000 => Self::Allocation { b, c },
170170- 0x90000000 => Self::Abandonment { c },
171171- 0xa0000000 => Self::Output { c },
172172- 0xb0000000 => Self::Input { c },
173173- 0xc0000000 => Self::LoadProgram { b, c },
174174- 0xd0000000 => {
161161+ match value & 0xf000_0000 {
162162+ 0x0000_0000 => Self::ConditionalMove { a, b, c },
163163+ 0x1000_0000 => Self::ArrayIndex { a, b, c },
164164+ 0x2000_0000 => Self::ArrayAmendment { a, b, c },
165165+ 0x3000_0000 => Self::Addition { a, b, c },
166166+ 0x4000_0000 => Self::Multiplication { a, b, c },
167167+ 0x5000_0000 => Self::Division { a, b, c },
168168+ 0x6000_0000 => Self::NotAnd { a, b, c },
169169+ 0x7000_0000 => Self::Halt,
170170+ 0x8000_0000 => Self::Allocation { b, c },
171171+ 0x9000_0000 => Self::Abandonment { c },
172172+ 0xa000_0000 => Self::Output { c },
173173+ 0xb000_0000 => Self::Input { c },
174174+ 0xc000_0000 => Self::LoadProgram { b, c },
175175+ 0xd000_0000 => {
175176 let a = Register::from_u8(((value >> 25) & 0x07) as u8);
176176- let value = value & 0x01ffffff;
177177+ let value = value & 0x01ff_ffff;
177178 Self::Orthography { a, value }
178179 }
179180 _ => Self::IllegalInstruction,
···181182 }
182183}
183184185185+/// Decode a Universal Machine program into a [`Vec`] of [`Operation`]s.
186186+#[must_use]
184187pub fn decode(ops: &[u32]) -> Vec<Operation> {
185188 ops.iter()
186189 .map(|&encoded| Operation::from(encoded))
+37-24
src/reg.rs
···1212// You should have received a copy of the GNU General Public License along with
1313// this program. If not, see <https://www.gnu.org/licenses/>.
1414//
1515+1516/// A reference to a register of the UM-32.
1617#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
1718pub enum Register {
···3334}
34353536impl Register {
3636- /// Encodes the register as the 'a' parameter of an encoded
3737+ /// Create a [`Register`] from a register index.
3838+ ///
3939+ /// # Panics
4040+ ///
4141+ /// Panics if `index` is not in `0..8`.
4242+ ///
4343+ #[must_use]
4444+ pub const fn from_u8(index: u8) -> Self {
4545+ match index {
4646+ 0 => Self::R0,
4747+ 1 => Self::R1,
4848+ 2 => Self::R2,
4949+ 3 => Self::R3,
5050+ 4 => Self::R4,
5151+ 5 => Self::R5,
5252+ 6 => Self::R6,
5353+ 7 => Self::R7,
5454+ _ => panic!("register index must be in range 0..8"),
5555+ }
5656+ }
5757+}
5858+5959+#[cfg(feature = "asm")]
6060+impl Register {
6161+ /// Encode the register as the 'a' parameter of an encoded
3762 /// instruction (bits 6..=8).
3838- pub fn encode_a(self) -> u32 {
6363+ #[must_use]
6464+ pub const fn encode_a(self) -> u32 {
3965 ((self as u32) & 0x7) << 6
4066 }
41674242- /// Encodes the register as the 'b' parameter of an encoded
6868+ /// Encode the register as the 'b' parameter of an encoded
4369 /// instruction (bits 3..=5).
4444- pub fn encode_b(self) -> u32 {
7070+ #[must_use]
7171+ pub const fn encode_b(self) -> u32 {
4572 ((self as u32) & 0x7) << 3
4673 }
47744848- /// Encodes the register as the 'c' parameter of an encoded
7575+ /// Encode the register as the 'c' parameter of an encoded
4976 /// instruction (bits 0..=2).
5050- pub fn encode_c(self) -> u32 {
7777+ #[must_use]
7878+ pub const fn encode_c(self) -> u32 {
5179 (self as u32) & 0x7
5280 }
53815454- /// Encodes the register as the 'a' parameter of an `Orthography`
8282+ /// Encode the register as the 'a' parameter of an `Orthography`
5583 /// operation.
5684 ///
5785 /// This is *only* valid for `Orthography` operations.
5858- pub fn encode_a_ortho(self) -> u32 {
8686+ #[must_use]
8787+ pub const fn encode_a_ortho(self) -> u32 {
5988 ((self as u32) & 0x7) << 25
6089 }
6161-6262- pub fn from_u8(index: u8) -> Self {
6363- match index {
6464- 0 => Register::R0,
6565- 1 => Register::R1,
6666- 2 => Register::R2,
6767- 3 => Register::R3,
6868- 4 => Register::R4,
6969- 5 => Register::R5,
7070- 6 => Register::R6,
7171- 7 => Register::R7,
7272- _ => unreachable!(),
7373- }
7474- }
7590}
76917792/// A set of registers.
···80958196impl std::ops::Index<Register> for Page {
8297 type Output = u32;
8383- #[inline(always)]
8498 fn index(&self, index: Register) -> &Self::Output {
8599 &self.0[index as usize]
86100 }
87101}
8810289103impl std::ops::IndexMut<Register> for Page {
9090- #[inline(always)]
91104 fn index_mut(&mut self, index: Register) -> &mut Self::Output {
92105 &mut self.0[index as usize]
93106 }
+109-37
src/universal_machine.rs
···11+// Copyright (C) 2025 Thom Hayward.
22+//
33+// This program is free software: you can redistribute it and/or modify it under
44+// the terms of the GNU General Public License as published by the Free Software
55+// Foundation, version 3.
66+//
77+// This program is distributed in the hope that it will be useful, but WITHOUT
88+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
99+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1010+// details.
1111+//
1212+// You should have received a copy of the GNU General Public License along with
1313+// this program. If not, see <https://www.gnu.org/licenses/>.
1414+//
1515+116use std::io::Read;
217use std::io::Write;
318···621use crate::ops::Operation;
722use crate::reg::{Page, Register};
82399-pub(crate) const SMALLVEC_SIZE: usize = 24;
2424+pub const SMALLVEC_SIZE: usize = 24;
10251126/// Lossless conversion to `usize`.
1227///
1328/// This should only be implemented on types which can be losslessly
1429/// cast to a `usize`.
1515-pub(crate) trait IntoIndex: Sized + Copy {
3030+trait IntoIndex: Sized + Copy {
1631 fn into_index(self) -> usize;
1732}
1833···3651#[derive(Default)]
3752pub struct Um<'a> {
3853 pub program_counter: u32,
3939- pub(crate) registers: Page,
5454+ pub registers: Page,
4055 /// Program memory, modelled as a `Vec` of `SmallVec`.
4156 ///
4257 /// Memory allocations greater than `SMALLVEC_SIZE` will incur a memory
4358 /// indirection penalty for every memory access within that block.
4444- pub(crate) memory: Vec<SmallVec<[u32; SMALLVEC_SIZE]>>,
4545- pub(crate) free_blocks: Vec<u32>,
5959+ pub memory: Vec<SmallVec<[u32; SMALLVEC_SIZE]>>,
6060+ pub free_blocks: Vec<u32>,
4661 /// Partially decoded operations cache.
4747- pub(crate) ops: Vec<Operation>,
4848- pub(crate) stdin: Option<&'a mut dyn Read>,
4949- pub(crate) stdout: Option<&'a mut dyn Write>,
6262+ pub ops: Vec<Operation>,
6363+ pub stdin: Option<&'a mut dyn Read>,
6464+ pub stdout: Option<&'a mut dyn Write>,
5065}
51665267impl<'a> Um<'a> {
5368 /// Initialise a Universal Machine with the specified program scroll.
6969+ #[must_use]
5470 pub fn new(program: Vec<u32>) -> Self {
5571 let ops = crate::ops::decode(&program);
5672 Self {
···6177 }
62786379 /// Sets the output for the universal machine.
8080+ #[must_use]
6481 pub fn stdout<T: Write>(mut self, stdout: &'a mut T) -> Self {
6582 self.stdout.replace(stdout);
6683 self
6784 }
68856986 /// Sets the input for the universal machine.
8787+ #[must_use]
7088 pub fn stdin<T: Read>(mut self, stdin: &'a mut T) -> Self {
7189 self.stdin.replace(stdin);
7290 self
7391 }
74927575- /// Begins the spin-cycle of the universal machine.
9393+ /// Begin the spin-cycle of the Universal Machine.
9494+ ///
9595+ /// # Panics
9696+ ///
9797+ /// Panics if the machine encounters an illegal instruction.
9898+ ///
7699 #[inline(never)]
100100+ #[allow(clippy::return_self_not_must_use, clippy::must_use_candidate)]
77101 pub fn run(mut self) -> Self {
78102 loop {
79103 // println!(
···137161 // }
138162139163 /// Loads the value from the specified register.
140140- pub(crate) fn load_register(&self, register: Register) -> u32 {
164164+ #[must_use]
165165+ pub fn load_register(&self, register: Register) -> u32 {
141166 self.registers[register]
142167 }
143168144169 /// Saves a value to the specified register.
145145- pub(crate) fn save_register(&mut self, register: Register, value: u32) {
170170+ pub fn save_register(&mut self, register: Register, value: u32) {
146171 self.registers[register] = value;
147172 }
148173149149- pub(crate) fn conditional_move(&mut self, a: Register, b: Register, c: Register) {
174174+ pub fn conditional_move(&mut self, a: Register, b: Register, c: Register) {
150175 if self.load_register(c) != 0 {
151176 self.save_register(a, self.load_register(b));
152177 }
153178 }
154179155155- pub(crate) fn array_index(&mut self, a: Register, b: Register, c: Register) {
180180+ pub fn array_index(&mut self, a: Register, b: Register, c: Register) {
156181 let block = self.load_register(b);
157182 let offset = self.load_register(c);
158183 self.save_register(a, self.load_memory(block, offset));
159184 }
160185161161- pub(crate) fn array_amendment(&mut self, a: Register, b: Register, c: Register) {
186186+ pub fn array_amendment(&mut self, a: Register, b: Register, c: Register) {
162187 let block = self.load_register(a);
163188 let offset = self.load_register(b);
164189 let value = self.load_register(c);
165190 self.store_memory(block, offset, value);
166191 }
167192168168- pub(crate) fn addition(&mut self, a: Register, b: Register, c: Register) {
193193+ pub fn addition(&mut self, a: Register, b: Register, c: Register) {
169194 self.save_register(a, self.load_register(b).wrapping_add(self.load_register(c)));
170195 }
171196172172- pub(crate) fn multiplication(&mut self, a: Register, b: Register, c: Register) {
197197+ pub fn multiplication(&mut self, a: Register, b: Register, c: Register) {
173198 self.save_register(a, self.load_register(b).wrapping_mul(self.load_register(c)));
174199 }
175200176176- pub(crate) fn division(&mut self, a: Register, b: Register, c: Register) {
201201+ pub fn division(&mut self, a: Register, b: Register, c: Register) {
177202 self.save_register(a, self.load_register(b).wrapping_div(self.load_register(c)));
178203 }
179204180180- pub(crate) fn not_and(&mut self, a: Register, b: Register, c: Register) {
205205+ pub fn not_and(&mut self, a: Register, b: Register, c: Register) {
181206 self.save_register(a, !(self.load_register(b) & self.load_register(c)));
182207 }
183208184184- pub(crate) fn allocation(&mut self, b: Register, c: Register) {
209209+ pub fn allocation(&mut self, b: Register, c: Register) {
185210 let length = self.load_register(c);
186211 let index = self.allocate_memory(length);
187212 self.save_register(b, index);
188213 }
189214190190- pub(crate) fn abandonment(&mut self, c: Register) {
215215+ pub fn abandonment(&mut self, c: Register) {
191216 let block = self.load_register(c);
192217 self.free_memory(block);
193218 }
194219195195- pub(crate) fn output(&mut self, c: Register) {
220220+ /// Write the value in the specified register to stdout.
221221+ ///
222222+ /// # Panics
223223+ ///
224224+ /// Panics if writing to stdout fails.
225225+ ///
226226+ pub fn output(&mut self, c: Register) {
196227 let value = self.load_register(c);
197228 if let Some(stdout) = self.stdout.as_mut() {
198229 let buffer = [(value & 0xff) as u8];
···200231 }
201232 }
202233203203- pub(crate) fn input(&mut self, c: Register) {
234234+ /// Read a value from stdin into the specifed register.
235235+ //
236236+ // The `as` cast below benchmarks faster than using u32::from.
237237+ #[allow(clippy::cast_lossless)]
238238+ pub fn input(&mut self, c: Register) {
204239 if let Some(stdin) = self.stdin.as_mut() {
205240 let mut buffer = vec![0];
206241 match stdin.read_exact(&mut buffer) {
···212247 }
213248 }
214249215215- pub(crate) fn load_program(&mut self, b: Register, c: Register) {
250250+ pub fn load_program(&mut self, b: Register, c: Register) {
216251 let block = self.load_register(b);
217252218253 // Source array is always copied to array[0], but there
···226261 self.program_counter = self.load_register(c);
227262 }
228263229229- pub(crate) fn orthography(&mut self, a: Register, value: u32) {
264264+ pub fn orthography(&mut self, a: Register, value: u32) {
230265 self.save_register(a, value);
231266 }
232267268268+ /// Print the current instruction, program counter, and registers to stderr and quit.
269269+ ///
270270+ /// # Panics
271271+ ///
272272+ /// Panics when called. Every time.
273273+ ///
233274 #[cold]
234275 #[inline(never)]
235235- pub(crate) fn illegal_instruction(&self) -> ! {
276276+ pub fn illegal_instruction(&self) -> ! {
236277 panic!(
237278 "illegal instruction: {:08x}, pc: {:08x}, r: {:08x?}",
238279 self.memory[0][self.program_counter.into_index()],
···241282 )
242283 }
243284244244- pub(crate) fn load_memory(&self, block: u32, offset: u32) -> u32 {
285285+ /// Load a value from `offset` in the specified memory `block`.
286286+ ///
287287+ /// # Panics
288288+ ///
289289+ /// Panics if the `block` is not an allocated block, or if `offset` overflows the current
290290+ /// memory block.
291291+ ///
292292+ #[must_use]
293293+ pub fn load_memory(&self, block: u32, offset: u32) -> u32 {
245294 let block = block.into_index();
246295 let offset = offset.into_index();
247296 assert!(block < self.memory.len() && offset < self.memory[block].len());
248297 self.memory[block][offset]
249298 }
250299251251- pub(crate) fn store_memory(&mut self, block: u32, offset: u32, value: u32) {
300300+ /// Store a `value` at `offset` in the specified memory `block`.
301301+ ///
302302+ /// # Panics
303303+ ///
304304+ /// Panics if the `block` is not an allocated block, or if `offset` overflows the current
305305+ /// memory block.
306306+ ///
307307+ pub fn store_memory(&mut self, block: u32, offset: u32, value: u32) {
252308 let block = block.into_index();
253309 let offset = offset.into_index();
254310 assert!(block < self.memory.len() && offset < self.memory[block].len());
255255- self.memory[block][offset] = value
311311+ self.memory[block][offset] = value;
256312 }
257313258314 /// Duplicates a block of memory.
259315 ///
260316 /// The block is copied to the first block of memory.
261261- pub(crate) fn duplicate_memory(&mut self, block: u32) -> &[u32] {
317317+ ///
318318+ /// # Panics
319319+ ///
320320+ /// Panics if the `block` is not an allocated block of memory.
321321+ ///
322322+ pub fn duplicate_memory(&mut self, block: u32) -> &[u32] {
262323 let block = block.into_index();
263324 assert!(block < self.memory.len());
264325 self.memory[0] = self.memory[block].clone();
···266327 }
267328268329 /// Allocates a block of memory of the specified length.
269269- pub(crate) fn allocate_memory(&mut self, length: u32) -> u32 {
330330+ #[allow(clippy::cast_possible_truncation)]
331331+ pub fn allocate_memory(&mut self, length: u32) -> u32 {
270332 if let Some(index) = self.free_blocks.pop() {
271333 self.memory[index.into_index()] = Self::new_block(length.into_index());
272334 index
273335 } else {
274336 self.memory.push(Self::new_block(length.into_index()));
337337+338338+ // The Universal Machine only deals with 32-bit values, so truncation here is
339339+ // impossible.
275340 (self.memory.len() - 1) as u32
276341 }
277342 }
278343279279- /// Frees a block of memory.
280280- pub(crate) fn free_memory(&mut self, block: u32) {
344344+ /// Free a block of memory.
345345+ ///
346346+ /// # Panics
347347+ ///
348348+ /// Panics if `block` is not an allocated block of memory.
349349+ ///
350350+ pub fn free_memory(&mut self, block: u32) {
281351 assert!(block.into_index() < self.memory.len());
282352 self.free_blocks.push(block);
283353 self.memory[block.into_index()] = Self::new_block(0);
284354 }
285355286286- /// Creates a new block of memory.
356356+ /// Create a new block of memory.
287357 ///
288358 /// The block is initialised with `len` zeroes.
289289- pub(crate) fn new_block(len: usize) -> SmallVec<[u32; SMALLVEC_SIZE]> {
359359+ ///
360360+ #[must_use]
361361+ pub fn new_block(len: usize) -> SmallVec<[u32; SMALLVEC_SIZE]> {
290362 smallvec::smallvec![0; len]
291363 }
292364}
293365294366#[cfg(test)]
295295-pub(crate) mod tests {
367367+mod tests {
296368 use super::*;
297369298370 #[test]
···309381 #[test]
310382 #[cfg(feature = "asm")]
311383 fn hello_world() {
312312- let program = asm::assemble(include_str!("../files/hello-world.asm"));
384384+ let program = crate::asm::assemble(include_str!("../files/hello-world.asm"));
313385 let mut buffer = Vec::new();
314386 Um::new(program).stdout(&mut buffer).run();
315387 assert_eq!(&buffer, b"Hello, world!\n");
···318390 #[test]
319391 #[cfg(feature = "asm")]
320392 fn cat() {
321321- let program = asm::assemble(include_str!("../files/cat.asm"));
393393+ let program = crate::asm::assemble(include_str!("../files/cat.asm"));
322394 let input = include_bytes!("lib.rs");
323395324396 let mut reader = std::io::Cursor::new(input);