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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//! The system family: TRAP, PUT/PUTI, RESUME, SAVE, UNSAVE, SYNC, SWYM,
//! GET and TRIP.
use super::super::{MMix, SpecialReg, TrapCode};
use crate::mmixal::Opcode;
use tracing::debug;
impl MMix {
/// Dispatches the system family; `dispatch` routes only these opcodes
/// here.
pub(super) fn dispatch_system(
&mut self,
opcode: Opcode,
op_byte: u8,
x: u8,
y: u8,
z: u8,
) -> bool {
match opcode {
Opcode::TRAP => {
// TRAP X, Y, Z (trap.html): X = 0 with a Y that
// TrapCode::from_u8 names is one of checksmix's own
// syscalls; everything else is a trap the reference sends
// to a kernel this emulator does not model.
if x == 0 {
if let Some(trap) = TrapCode::from_u8(y) {
return self.handle_trap(trap, z);
}
debug!(trap_code = y, arg = z, "TRAP: unsimulated trap code");
}
// $Y and $Z are read here, before halt_unsimulated_trap
// overwrites $255, so a field naming $255 sees its
// pre-trap value.
let y_val = self.get_register(y);
let z_val = self.get_register(z);
self.halt_unsimulated_trap(op_byte, x, y, z, y_val, z_val)
}
// Stack/System instructions - opcodes 0xF6-0xF7, 0xF9-0xFF
Opcode::PUT => {
// PUT X, $Z - Put $Z into special register X.
let value = self.get_register(z);
if !self.put_special("PUT", x, value) {
return false;
}
self.advance_pc();
true
}
Opcode::PUTI => {
// PUT X, Z - Put immediate Z into special register X, eight
// bits. Y must be zero; dispatch's prologue rejects a nonzero Y
// before this arm runs.
if !self.put_special("PUTI", x, z as u64) {
return false;
}
self.advance_pc();
true
}
Opcode::RESUME => {
// RESUME Z (gitraptrip.html "RESUME"; §1 rule 4): X and Y
// are always zero, so only Z varies. Z != 0 is RESUME 1,
// which restores from the kernel's rWW/rXX/rYY/rZZ and is
// privileged. rX < 0 resumes at rW. Otherwise rX's top byte
// is the ropcode: 0 inserts rX's instruction as if it stood
// at rW-4 and resumes at rW; 1 substitutes rY/rZ into an
// interruptible instruction; 2 serves forced-trap emulation;
// 3 inserts a page-table entry. checksmix has no
// interruptible instruction, no emulated opcode and no page
// table, so ropcodes 1-3 never arise legitimately. Each
// unsupported form halts through the shared MMix::reject
// path: diagnostic, false, PC unmoved. A ropcode-0 rX
// whose own opcode is RESUME halts the same way, rather
// than dispatching itself forever.
if z != 0 {
return self.reject(&format!(
"RESUME {z}: privileged form (RESUME 1) at PC={:#018x}",
self.pc
));
}
let rx = self.get_special(SpecialReg::RX);
if (rx as i64) < 0 {
self.pc = self.get_special(SpecialReg::RW);
return true;
}
let ropcode = rx >> 56;
if ropcode != 0 {
return self.reject(&format!(
"RESUME: ropcode {ropcode} at PC={:#018x} \
(only ropcode 0 and rX < 0 are supported)",
self.pc
));
}
let word = rx as u32;
let ins_op = (word >> 24) as u8;
let ins_x = (word >> 16) as u8;
let ins_y = (word >> 8) as u8;
let ins_z = word as u8;
let ins_opcode = match Opcode::try_from(ins_op) {
Ok(op) => op,
Err(_) => {
return self.reject(&format!(
"RESUME: invalid opcode {ins_op:#04x} in rX at PC={:#018x}",
self.pc
));
}
};
if ins_opcode == Opcode::RESUME {
return self.reject(&format!(
"RESUME: rX inserts a RESUME at PC={:#018x}",
self.pc
));
}
self.pc = self.get_special(SpecialReg::RW).wrapping_sub(4);
self.dispatch(ins_opcode, ins_op, ins_x, ins_y, ins_z)
}
Opcode::SAVE => {
// SAVE $X,0 - push the machine's context onto the register
// stack. See MMix::save_context.
if !self.save_context(x) {
return false;
}
self.advance_pc();
true
}
Opcode::UNSAVE => {
// UNSAVE 0,$Z - restore the context $Z addresses. See
// MMix::unsave_context.
let packed_addr = self.get_register(z);
if !self.unsave_context(packed_addr) {
return false;
}
self.advance_pc();
true
}
Opcode::SYNC => {
// SYNC XYZ (sync.html): 0-3 is a no-op user programs may
// issue; 4-7 is reserved for the kernel; above 7 names
// nothing.
let xyz = ((x as u32) << 16) | ((y as u32) << 8) | z as u32;
match xyz {
0..=3 => {
self.advance_pc();
true
}
4..=7 => self.reject(&format!(
"SYNC {xyz}: privileged-operation interrupt at PC={:#018x}",
self.pc
)),
_ => self.reject(&format!(
"SYNC {xyz}: illegal-instruction interrupt at PC={:#018x}",
self.pc
)),
}
}
Opcode::SWYM => {
// SWYM XYZ - Sympathize with your machinery (no-op)
self.advance_pc();
true
}
Opcode::GET => {
// GET $X, $Z - Get from special register. Z is validated in
// dispatch's prologue, ahead of the $X claim.
let special_reg = SpecialReg::from_u8(z)
.expect("dispatch rejected an unnamed Z before this arm ran");
let value = self.get_special(special_reg);
self.set_register(x, value);
self.advance_pc();
true
}
Opcode::TRIP => {
// TRIP X,Y,Z: user trip to the handler at #00 (trip.html;
// §1 rule 3).
let y_val = self.get_register(y);
let z_val = self.get_register(z);
self.trip(0x00, "TRIP", op_byte, x, y, z, y_val, z_val)
}
_ => unreachable!("dispatch routes only system opcodes to dispatch_system"),
}
}
/// Halts on a TRAP the reference sends to a kernel this emulator does
/// not model (trap.html): sets rBB, $255, rWW, rXX, rYY and rZZ to the
/// values a kernel handler would find on entry, then halts, PC on the
/// TRAP. Control would then pass to rT; checksmix models no kernel.
/// `y_val` and `z_val` are the caller's pre-trap reads of `$Y` and
/// `$Z`, taken before this call overwrites `$255`.
fn halt_unsimulated_trap(
&mut self,
op_byte: u8,
x: u8,
y: u8,
z: u8,
y_val: u64,
z_val: u64,
) -> bool {
let old_255 = self.get_register(255);
self.set_special(SpecialReg::RBB, old_255);
self.set_register(255, self.get_special(SpecialReg::RJ));
self.set_special(SpecialReg::RWW, self.pc.wrapping_add(4));
self.set_special(
SpecialReg::RXX,
0x8000_0000_0000_0000
| ((op_byte as u64) << 24)
| ((x as u64) << 16)
| ((y as u64) << 8)
| z as u64,
);
self.set_special(SpecialReg::RYY, y_val);
self.set_special(SpecialReg::RZZ, z_val);
self.host.diagnostic(&format!(
"unsimulated TRAP {x},{y},{z} at PC={:#018x}",
self.pc
));
self.exit_code = 1;
false
}
}