Implementation of the UM-32 "Universal Machine" as described by the Cult of the Bound Variable

initial commit

tjh 371e6900

+417
+1
.gitignore
··· 1 + /target
+7
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 3 4 + 5 + [[package]] 6 + name = "um" 7 + version = "0.1.0"
+8
Cargo.toml
··· 1 + [package] 2 + name = "um" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [features] 7 + default = ["reclaim-memory"] 8 + reclaim-memory = []
files/codex.umz

This is a binary file and will not be displayed.

files/sandmark.umz

This is a binary file and will not be displayed.

files/um.um

This is a binary file and will not be displayed.

+401
src/main.rs
··· 1 + fn main() { 2 + let mut program = Vec::new(); 3 + for arg in std::env::args().skip(1) { 4 + let p = std::fs::read(arg).unwrap(); 5 + program.extend_from_slice(&p); 6 + } 7 + 8 + Um::from_bytes(program) 9 + .stdout(std::io::stdout()) 10 + .stdin(std::io::stdin()) 11 + .run(); 12 + } 13 + 14 + type Platter = u32; 15 + type Register = u8; 16 + 17 + #[derive(Clone, Copy, Debug, PartialEq, Eq)] 18 + enum Op { 19 + ConditionalMove { 20 + a: Register, 21 + b: Register, 22 + c: Register, 23 + }, 24 + ArrayIndex { 25 + a: Register, 26 + b: Register, 27 + c: Register, 28 + }, 29 + ArrayAmendment { 30 + a: Register, 31 + b: Register, 32 + c: Register, 33 + }, 34 + Addition { 35 + a: Register, 36 + b: Register, 37 + c: Register, 38 + }, 39 + Multiplication { 40 + a: Register, 41 + b: Register, 42 + c: Register, 43 + }, 44 + Division { 45 + a: Register, 46 + b: Register, 47 + c: Register, 48 + }, 49 + NotAnd { 50 + a: Register, 51 + b: Register, 52 + c: Register, 53 + }, 54 + Halt, 55 + Allocation { 56 + b: Register, 57 + c: Register, 58 + }, 59 + Abandonment { 60 + c: Register, 61 + }, 62 + Output { 63 + c: Register, 64 + }, 65 + Input { 66 + c: Register, 67 + }, 68 + LoadProgram { 69 + b: Register, 70 + c: Register, 71 + }, 72 + Orthography { 73 + a: Register, 74 + value: u32, 75 + }, 76 + } 77 + 78 + impl From<Platter> for Op { 79 + fn from(value: Platter) -> Self { 80 + let a = ((value >> 6) & 0x07) as Register; 81 + let b = ((value >> 3) & 0x07) as Register; 82 + let c = ((value >> 0) & 0x07) as Register; 83 + 84 + match value & 0xf0000000 { 85 + 0x00000000 => Self::ConditionalMove { a, b, c }, 86 + 0x10000000 => Self::ArrayIndex { a, b, c }, 87 + 0x20000000 => Self::ArrayAmendment { a, b, c }, 88 + 0x30000000 => Self::Addition { a, b, c }, 89 + 0x40000000 => Self::Multiplication { a, b, c }, 90 + 0x50000000 => Self::Division { a, b, c }, 91 + 0x60000000 => Self::NotAnd { a, b, c }, 92 + 0x70000000 => Self::Halt, 93 + 0x80000000 => Self::Allocation { b, c }, 94 + 0x90000000 => Self::Abandonment { c }, 95 + 0xa0000000 => Self::Output { c }, 96 + 0xb0000000 => Self::Input { c }, 97 + 0xc0000000 => Self::LoadProgram { b, c }, 98 + 0xd0000000 => { 99 + let a = ((value >> 25) & 0x07) as Register; 100 + let value = value & 0x01ffffff; 101 + Self::Orthography { a, value } 102 + } 103 + _ => Self::Halt, 104 + } 105 + } 106 + } 107 + 108 + fn decode_ops(ops: &[Platter]) -> Vec<Op> { 109 + ops.iter().map(|encoded| Op::from(*encoded)).collect() 110 + } 111 + 112 + #[derive(Default)] 113 + pub struct Um { 114 + program_counter: Platter, 115 + registers: [Platter; 8], 116 + memory: Vec<Vec<Platter>>, 117 + #[cfg(feature = "reclaim-memory")] 118 + free_blocks: Vec<Platter>, 119 + ops: Vec<Op>, 120 + stdin: Option<Box<dyn std::io::Read>>, 121 + stdout: Option<Box<dyn std::io::Write>>, 122 + } 123 + 124 + impl Um { 125 + /// Construct a new Universal Machine with the specified program. 126 + pub fn new(program: Vec<Platter>) -> Self { 127 + let ops = decode_ops(&program); 128 + Self { 129 + memory: vec![program], 130 + ops, 131 + ..Default::default() 132 + } 133 + } 134 + 135 + /// Construct a new Universal Machine from a program represented in bytes. 136 + pub fn from_bytes(program: impl AsRef<[u8]>) -> Self { 137 + let bytes = program.as_ref(); 138 + let mut program = Vec::with_capacity(bytes.len().div_ceil(size_of::<Platter>())); 139 + for word in bytes.chunks(size_of::<Platter>()) { 140 + let value = Platter::from_be_bytes(match word { 141 + [a, b, c, d] => [*a, *b, *c, *d], 142 + [a, b, c] => [*a, *b, *c, 0], 143 + [a, b] => [*a, *b, 0, 0], 144 + [a] => [*a, 0, 0, 0], 145 + _ => unreachable!(), 146 + }); 147 + program.push(value); 148 + } 149 + 150 + Self::new(program) 151 + } 152 + 153 + /// Sets the output for the univeral machine. 154 + pub fn stdout(mut self, stdout: impl std::io::Write + 'static) -> Self { 155 + self.stdout.replace(Box::new(stdout)); 156 + self 157 + } 158 + 159 + /// Sets the input for the universal machine. 160 + pub fn stdin(mut self, stdin: impl std::io::Read + 'static) -> Self { 161 + self.stdin.replace(Box::new(stdin)); 162 + self 163 + } 164 + 165 + /// Begins the spin-cycle of the universal machine. 166 + pub fn run(mut self) { 167 + loop { 168 + match self.ops[self.program_counter as usize] { 169 + // Operator #0. Conditional Move. 170 + // 171 + // The register A receives the value in register B, 172 + // unless the register C contains 0. 173 + Op::ConditionalMove { a, b, c } => { 174 + if self.load_register(c) != 0 { 175 + self.save_register(a, self.load_register(b)); 176 + } 177 + } 178 + 179 + // Operator #1: Array Index. 180 + // 181 + // The register A receives the value stored at offset 182 + // in register C in the array identified by B. 183 + Op::ArrayIndex { a, b, c } => { 184 + let block = self.load_register(b); 185 + let offset = self.load_register(c); 186 + self.save_register(a, self.load_memory(block, offset)); 187 + } 188 + 189 + // Operator #2. Array Amendment. 190 + // 191 + // The array identified by A is amended at the offset 192 + // in register B to store the value in register C. 193 + Op::ArrayAmendment { a, b, c } => { 194 + let block = self.load_register(a); 195 + let offset = self.load_register(b); 196 + let value = self.load_register(c); 197 + self.store_memory(block, offset, value); 198 + } 199 + 200 + // Operator #3. Addition. 201 + // 202 + // The register A receives the value in register B plus 203 + // the value in register C, modulo 2^32. 204 + Op::Addition { a, b, c } => { 205 + self.save_register( 206 + a, 207 + self.load_register(b).wrapping_add(self.load_register(c)), 208 + ); 209 + } 210 + 211 + // Operator #4. Multiplication. 212 + // 213 + // The register A receives the value in register B times 214 + // the value in register C, modulo 2^32. 215 + Op::Multiplication { a, b, c } => { 216 + self.save_register( 217 + a, 218 + self.load_register(b).wrapping_mul(self.load_register(c)), 219 + ); 220 + } 221 + 222 + // Operator #5. Division. 223 + // 224 + // The register A receives the value in register B 225 + // divided by the value in register C, if any, where 226 + // each quantity is treated as an unsigned 32 bit number. 227 + Op::Division { a, b, c } => { 228 + self.save_register( 229 + a, 230 + self.load_register(b).wrapping_div(self.load_register(c)), 231 + ); 232 + } 233 + 234 + // Operator #6. Not-And. 235 + // 236 + // Each bit in the register A receives the 1 bit if 237 + // either register B or register C has a 0 bit in that 238 + // position. Otherwise the bit in register A receives 239 + // the 0 bit. 240 + Op::NotAnd { a, b, c } => { 241 + self.save_register(a, !(self.load_register(b) & self.load_register(c))); 242 + } 243 + 244 + // Operator #7. Halt. 245 + // 246 + // The universal machine stops computation. 247 + Op::Halt => break, 248 + 249 + // Operator #8. Allocation. 250 + // 251 + // A new array is created with a capacity of platters 252 + // commensurate to the value in the register C. This 253 + // new array is initialized entirely with platters 254 + // holding the value 0. A bit pattern not consisting of 255 + // exclusively the 0 bit, and that identifies no other 256 + // active allocated array, is placed in the B register. 257 + Op::Allocation { b, c } => { 258 + let length = self.load_register(c); 259 + let index = self.allocate_memory(length); 260 + self.save_register(b, index); 261 + } 262 + 263 + // Operator #9. Abandonment. 264 + // 265 + // The array identified by the register C is abandoned. 266 + // Future allocations may then reuse that identifier. 267 + Op::Abandonment { c } => { 268 + let block = self.load_register(c); 269 + self.free_memory(block); 270 + } 271 + 272 + // Operator #10. Output. 273 + // 274 + // The value in the register C is displayed on the console 275 + // immediately. Only values between and including 0 and 255 276 + // are allowed. 277 + Op::Output { c } => { 278 + let value = self.load_register(c); 279 + if let Some(stdout) = self.stdout.as_mut() { 280 + let buffer = [(value & 0xff) as u8]; 281 + stdout.write_all(&buffer).unwrap(); 282 + } 283 + } 284 + 285 + // Operator #11. Input. 286 + // 287 + // The universal machine waits for input on the console. 288 + // When input arrives, the register C is loaded with the 289 + // input, which must be between and including 0 and 255. 290 + // If the end of input has been signaled, then the 291 + // register C is endowed with a uniform value pattern 292 + // where every place is pregnant with the 1 bit. 293 + Op::Input { c } => { 294 + if let Some(stdin) = self.stdin.as_mut() { 295 + let mut buffer = vec![0]; 296 + match stdin.read_exact(&mut buffer) { 297 + Ok(()) => self.save_register(c, buffer[0] as u32), 298 + Err(_) => self.save_register(c, 0xff), 299 + } 300 + } else { 301 + self.save_register(c, 0xff); 302 + } 303 + } 304 + 305 + // Operator #12. Load Program. 306 + // 307 + // The array identified by the B register is duplicated 308 + // and the duplicate shall replace the '0' array, 309 + // regardless of size. The execution finger is placed 310 + // to indicate the platter of this array that is 311 + // described by the offset given in C, where the value 312 + // 0 denotes the first platter, 1 the second, et 313 + // cetera. 314 + // 315 + // The '0' array shall be the most sublime choice for 316 + // loading, and shall be handled with the utmost 317 + // velocity. 318 + Op::LoadProgram { b, c } => { 319 + let block = self.load_register(b); 320 + 321 + // Source array is always copied to array[0], but there 322 + // is no point copying array[0] to array[0]. 323 + if block != 0 { 324 + let duplicated = self.duplicate_memory(block); 325 + let ops = decode_ops(&duplicated); 326 + self.ops = ops; 327 + } 328 + 329 + self.program_counter = self.load_register(c); 330 + continue; 331 + } 332 + 333 + // Operator #13. Orthography. 334 + // 335 + // The value indicated is loaded into the register A 336 + // forthwith. 337 + Op::Orthography { a, value } => { 338 + self.save_register(a, value); 339 + } 340 + } 341 + 342 + self.program_counter += 1; 343 + } 344 + } 345 + 346 + /// Loads the value from the specified register. 347 + fn load_register(&self, index: Register) -> Platter { 348 + debug_assert!(index < 8, "register index out of bounds"); 349 + self.registers[index as usize] 350 + } 351 + 352 + /// Saves a value to the specified register. 353 + fn save_register(&mut self, index: Register, value: Platter) { 354 + debug_assert!(index < 8, "register index out of bounds"); 355 + self.registers[index as usize] = value; 356 + } 357 + 358 + fn load_memory(&self, block: Platter, offset: Platter) -> Platter { 359 + debug_assert!((block as usize) < self.memory.len()); 360 + debug_assert!((offset as usize) < self.memory[block as usize].len()); 361 + self.memory[block as usize][offset as usize] 362 + } 363 + 364 + fn store_memory(&mut self, block: Platter, offset: Platter, value: Platter) { 365 + debug_assert!((block as usize) < self.memory.len()); 366 + debug_assert!((offset as usize) < self.memory[block as usize].len()); 367 + self.memory[block as usize][offset as usize] = value; 368 + } 369 + 370 + fn duplicate_memory(&mut self, block: Platter) -> &[Platter] { 371 + debug_assert!((block as usize) < self.memory.len()); 372 + self.memory[0] = self.memory[block as usize].clone(); 373 + &self.memory[0] 374 + } 375 + 376 + #[cfg(not(feature = "reclaim-memory"))] 377 + fn allocate_memory(&mut self, length: Platter) -> Platter { 378 + self.memory.push(vec![0; length as usize]); 379 + (self.memory.len() - 1) as Platter 380 + } 381 + 382 + #[cfg(feature = "reclaim-memory")] 383 + fn allocate_memory(&mut self, length: Platter) -> Platter { 384 + if let Some(index) = self.free_blocks.pop() { 385 + self.memory[index as usize] = vec![0; length as usize]; 386 + index as Platter 387 + } else { 388 + self.memory.push(vec![0; length as usize]); 389 + (self.memory.len() - 1) as Platter 390 + } 391 + } 392 + 393 + fn free_memory(&mut self, block: Platter) { 394 + debug_assert!((block as usize) < self.memory.len()); 395 + #[cfg(feature = "reclaim-memory")] 396 + { 397 + self.free_blocks.push(block); 398 + self.memory[block as usize] = vec![]; 399 + } 400 + } 401 + }