1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//! Sparse byte memory, the write journal, and multi-byte read/write helpers.
use super::MMix;
use tracing::{instrument, trace};
impl MMix {
/// Read a byte from memory at the given address.
/// Uninitialized memory reads as zero.
#[instrument(skip(self), level = "trace")]
pub fn read_byte(&self, addr: u64) -> u8 {
let value = *self.memory.get(&addr).unwrap_or(&0);
trace!(
addr = format!("0x{:X}", addr),
value, "Read byte from memory"
);
value
}
/// Write a byte to memory at the given address.
#[instrument(skip(self), level = "trace")]
pub fn write_byte(&mut self, addr: u64, value: u8) {
trace!(
addr = format!("0x{:X}", addr),
value, "Writing byte to memory"
);
if value == 0 {
self.memory.remove(&addr); // Don't store zeros (sparse memory)
} else {
self.memory.insert(addr, value);
}
if self.journal_enabled {
self.journal.insert(addr);
}
}
/// Every address with a nonzero byte, in ascending address order — the
/// sparse memory's contents for a caller that wants to display or diff
/// them, since the underlying map's iteration order is unspecified.
pub fn occupied(&self) -> impl Iterator<Item = (u64, u8)> {
let mut addrs: Vec<u64> = self.memory.keys().copied().collect();
addrs.sort_unstable();
addrs.into_iter().map(|addr| (addr, self.memory[&addr]))
}
/// Combine a loaded byte with whatever already sits at `addr` by XOR --
/// MMIX's loader rule, so two assemblies of the same address combine
/// rather than one silently overwriting the other -- and record `addr`
/// into `loaded` regardless of the result. `write_image`'s and a `.mmo`
/// load's per-byte loops call this in place of `write_byte` so that a
/// zero byte the loaded program actually placed stays visible to
/// [`MMix::loaded_extent`] even after the sparse-memory write drops it
/// from `self.memory`. Not for general writes: a running program's own
/// stores (register-stack spills, `STO`, ...) must go through plain
/// `write_byte`, or `loaded_extent` would degrade into the write
/// journal's noise.
pub(crate) fn write_loaded_byte(&mut self, addr: u64, value: u8) {
let combined = self.read_byte(addr) ^ value;
self.write_byte(addr, combined);
self.loaded.insert(addr);
}
/// Every address `write_image` loaded, in ascending order, paired with
/// its **current** byte value (a later overwrite during execution shows
/// the new value here, same as [`MMix::occupied`] would if that value is
/// nonzero).
///
/// Unlike [`MMix::occupied`], this includes a byte that the loaded
/// program set to zero — sparse memory drops a zero-write from
/// `self.memory`, so `occupied` can't tell "loaded and zero" from
/// "never written." This can, so it is what a caller wants for the
/// real loaded extent instead of a set with silent holes.
pub fn loaded_extent(&self) -> impl Iterator<Item = (u64, u8)> + '_ {
self.loaded.iter().map(|&addr| (addr, self.read_byte(addr)))
}
/// Enable or disable the write journal. While enabled, every
/// [`MMix::write_byte`] call records its address (including a
/// zero-write, which removes the address from [`MMix::occupied`] — that
/// removal is itself a state change worth journaling). Survives
/// [`MMix::reset`]; the accumulated addresses do not.
pub fn set_journal(&mut self, on: bool) {
self.journal_enabled = on;
}
/// Drain the journal, returning every address written since the last
/// call, deduplicated and in ascending order — matching
/// [`MMix::occupied`]'s ordering.
pub fn take_journal(&mut self) -> Vec<u64> {
let mut addrs: Vec<u64> = self.journal.drain().collect();
addrs.sort_unstable();
addrs
}
/// Read a wyde (2 bytes) from memory at the address rounded down to a
/// multiple of 2: `M_2[A] = M_2[2*floor(A/2)]`.
/// Example: a wyde at #103 reads bytes #102..#103.
pub fn read_wyde(&self, addr: u64) -> u16 {
let addr = addr & !1;
let b0 = self.read_byte(addr) as u16;
let b1 = self.read_byte(addr.wrapping_add(1)) as u16;
(b0 << 8) | b1
}
/// Write a wyde (2 bytes) to memory at the address rounded down to a
/// multiple of 2: `M_2[A] = M_2[2*floor(A/2)]`.
pub fn write_wyde(&mut self, addr: u64, value: u16) {
let addr = addr & !1;
self.write_byte(addr, (value >> 8) as u8);
self.write_byte(addr.wrapping_add(1), value as u8);
}
/// Read a tetra (4 bytes) from memory at the address rounded down to a
/// multiple of 4: `M_4[A] = M_4[4*floor(A/4)]`.
pub fn read_tetra(&self, addr: u64) -> u32 {
let addr = addr & !3;
let b0 = self.read_byte(addr) as u32;
let b1 = self.read_byte(addr.wrapping_add(1)) as u32;
let b2 = self.read_byte(addr.wrapping_add(2)) as u32;
let b3 = self.read_byte(addr.wrapping_add(3)) as u32;
(b0 << 24) | (b1 << 16) | (b2 << 8) | b3
}
/// Write a tetra (4 bytes) to memory at the address rounded down to a
/// multiple of 4: `M_4[A] = M_4[4*floor(A/4)]`.
pub fn write_tetra(&mut self, addr: u64, value: u32) {
let addr = addr & !3;
self.write_byte(addr, (value >> 24) as u8);
self.write_byte(addr.wrapping_add(1), (value >> 16) as u8);
self.write_byte(addr.wrapping_add(2), (value >> 8) as u8);
self.write_byte(addr.wrapping_add(3), value as u8);
}
/// Read an octa (8 bytes) from memory at the address rounded down to a
/// multiple of 8: `M_8[A] = M_8[8*floor(A/8)]`.
/// Example: any address in #100..#107 reads bytes #100..#107.
pub fn read_octa(&self, addr: u64) -> u64 {
let addr = addr & !7;
let b0 = self.read_byte(addr) as u64;
let b1 = self.read_byte(addr.wrapping_add(1)) as u64;
let b2 = self.read_byte(addr.wrapping_add(2)) as u64;
let b3 = self.read_byte(addr.wrapping_add(3)) as u64;
let b4 = self.read_byte(addr.wrapping_add(4)) as u64;
let b5 = self.read_byte(addr.wrapping_add(5)) as u64;
let b6 = self.read_byte(addr.wrapping_add(6)) as u64;
let b7 = self.read_byte(addr.wrapping_add(7)) as u64;
(b0 << 56) | (b1 << 48) | (b2 << 40) | (b3 << 32) | (b4 << 24) | (b5 << 16) | (b6 << 8) | b7
}
/// Write an octa (8 bytes) to memory at the address rounded down to a
/// multiple of 8: `M_8[A] = M_8[8*floor(A/8)]`.
pub fn write_octa(&mut self, addr: u64, value: u64) {
let addr = addr & !7;
self.write_byte(addr, (value >> 56) as u8);
self.write_byte(addr.wrapping_add(1), (value >> 48) as u8);
self.write_byte(addr.wrapping_add(2), (value >> 40) as u8);
self.write_byte(addr.wrapping_add(3), (value >> 32) as u8);
self.write_byte(addr.wrapping_add(4), (value >> 24) as u8);
self.write_byte(addr.wrapping_add(5), (value >> 16) as u8);
self.write_byte(addr.wrapping_add(6), (value >> 8) as u8);
self.write_byte(addr.wrapping_add(7), value as u8);
}
/// Fetch the next instruction from memory and decode it.
/// Returns (OP, X, Y, Z) where:
/// - OP is the opcode
/// - X, Y, Z are the operand bytes
pub fn fetch_instruction(&self) -> (u8, u8, u8, u8) {
let instruction = self.read_tetra(self.pc);
let op = (instruction >> 24) as u8;
let x = (instruction >> 16) as u8;
let y = (instruction >> 8) as u8;
let z = instruction as u8;
(op, x, y, z)
}
}