1use std::error::Error;
3use std::fmt::{self, Display, Formatter};
4use std::time::Duration;
5
6use serde::Serialize;
7
8use base::instruction::Instruction;
9use base::prelude::*;
10
11use super::bugreport::{IssueType, bug_report_url};
12use super::diagnostics::{CurrentInstructionDiagnostics, DiagnosticFetcher};
13
14#[derive(Debug, Clone)]
16pub enum BadMemOp {
17 Read(Unsigned36Bit),
19 Write(Unsigned36Bit),
21}
22
23impl Display for BadMemOp {
24 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
25 match self {
26 BadMemOp::Read(addr) => write!(f, "memory read from {addr:>013o} failed"),
27 BadMemOp::Write(addr) => write!(f, "memory write to {addr:>013o} failed"),
28 }
29 }
30}
31
32#[derive(Debug, PartialEq, Eq)]
34pub enum AlarmMaskability {
35 Maskable,
36 Unmaskable,
37}
38
39#[allow(clippy::upper_case_acronyms)]
49#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord, Serialize)]
50pub enum AlarmKind {
51 PSAL,
52 OCSAL,
53 QSAL,
54 IOSAL,
55 MISAL,
56 ROUNDTUITAL,
57 DEFERLOOPAL,
58 BUGAL,
59}
60
61impl Display for AlarmKind {
62 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
63 f.write_str(match self {
64 AlarmKind::PSAL => "PSAL",
65 AlarmKind::OCSAL => "OCSAL",
66 AlarmKind::QSAL => "QSAL",
67 AlarmKind::IOSAL => "IOSAL",
68 AlarmKind::MISAL => "MISAL",
69 AlarmKind::ROUNDTUITAL => "ROUNDTUITAL",
70 AlarmKind::DEFERLOOPAL => "DEFERLOOPAL",
71 AlarmKind::BUGAL => "BUGAL",
72 })
73 }
74}
75
76impl AlarmKind {
77 #[must_use]
79 pub fn maskable(&self) -> AlarmMaskability {
80 match self {
81 AlarmKind::BUGAL | AlarmKind::DEFERLOOPAL | AlarmKind::ROUNDTUITAL => {
82 AlarmMaskability::Unmaskable
83 }
84 _ => AlarmMaskability::Maskable,
85 }
86 }
87
88 #[must_use]
89 pub const fn all_alarm_kinds() -> [AlarmKind; 8] {
90 [
91 AlarmKind::PSAL,
92 AlarmKind::OCSAL,
93 AlarmKind::QSAL,
94 AlarmKind::IOSAL,
95 AlarmKind::MISAL,
96 AlarmKind::ROUNDTUITAL,
97 AlarmKind::DEFERLOOPAL,
98 AlarmKind::BUGAL,
99 ]
100 }
101}
102
103#[derive(Debug)]
104pub struct UnknownAlarmName(String);
105
106impl Display for UnknownAlarmName {
107 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
108 write!(f, "unknown alarm name '{}'", self.0)
109 }
110}
111
112impl Error for UnknownAlarmName {}
113
114impl TryFrom<&str> for AlarmKind {
115 type Error = UnknownAlarmName;
116 fn try_from(s: &str) -> Result<AlarmKind, UnknownAlarmName> {
117 match s {
118 "PSAL" => Ok(AlarmKind::PSAL),
119 "OCSAL" => Ok(AlarmKind::OCSAL),
120 "QSAL" => Ok(AlarmKind::QSAL),
121 "IOSAL" => Ok(AlarmKind::IOSAL),
122 "MISAL" => Ok(AlarmKind::MISAL),
123 "ROUNDTUITAL" => Ok(AlarmKind::ROUNDTUITAL),
124 "DEFERLOOPAL" => Ok(AlarmKind::DEFERLOOPAL),
125 "BUGAL" => Ok(AlarmKind::BUGAL),
126 _ => Err(UnknownAlarmName(s.to_owned())),
127 }
128 }
129}
130
131#[test]
132fn test_alarm_kind_round_trip() {
133 for orig_kind in AlarmKind::all_alarm_kinds() {
134 let name = orig_kind.to_string();
135 match AlarmKind::try_from(name.as_str()) {
136 Ok(k) => {
137 assert_eq!(k, orig_kind);
138 }
139 Err(_) => {
140 panic!("unable to round-trip alarm kind {orig_kind:?}");
141 }
142 }
143 }
144 assert!(AlarmKind::try_from("this is not an alarm name").is_err());
145}
146
147#[derive(Debug, Clone)]
149pub enum BugActivity {
150 Io,
153 Opcode,
156 AlarmHandling,
159}
160
161#[allow(clippy::upper_case_acronyms)]
187#[derive(Debug, Clone)]
188pub enum AlarmDetails {
189 PSAL(u32, String),
193
194 OCSAL(Instruction, String),
206
207 QSAL(Instruction, BadMemOp, String),
210
211 IOSAL {
214 unit: Unsigned6Bit,
216 operand: Option<Unsigned18Bit>,
217 message: String,
218 },
219
220 MISAL { affected_unit: Unsigned6Bit },
225
226 ROUNDTUITAL {
229 explanation: String,
230 bug_report_url: &'static str,
231 },
232
233 DEFERLOOPAL {
236 address: Unsigned18Bit,
238 },
239
240 BUGAL {
242 activity: BugActivity,
244 diagnostics: CurrentInstructionDiagnostics,
246 message: String,
248 },
249}
250
251#[allow(clippy::upper_case_acronyms)]
253#[derive(Debug, Clone)]
254pub struct Alarm {
255 pub sequence: Option<SequenceNumber>,
256 pub details: AlarmDetails,
257}
258
259impl Alarm {
260 #[must_use]
261 pub fn kind(&self) -> AlarmKind {
262 self.details.kind()
263 }
264}
265
266impl AlarmDetails {
267 #[must_use]
268 pub fn kind(&self) -> AlarmKind {
269 match self {
270 AlarmDetails::PSAL(_, _) => AlarmKind::PSAL,
271 AlarmDetails::OCSAL(_, _) => AlarmKind::OCSAL,
272 AlarmDetails::QSAL(_, _, _) => AlarmKind::QSAL,
273 AlarmDetails::IOSAL { .. } => AlarmKind::IOSAL,
274 AlarmDetails::MISAL { .. } => AlarmKind::MISAL,
275 AlarmDetails::ROUNDTUITAL { .. } => AlarmKind::ROUNDTUITAL,
276 AlarmDetails::DEFERLOOPAL { .. } => AlarmKind::DEFERLOOPAL,
277 AlarmDetails::BUGAL { .. } => AlarmKind::BUGAL,
278 }
279 }
280}
281
282impl Display for Alarm {
283 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
284 if let Some(seq) = self.sequence {
285 write!(f, "in sequence {seq:o}, {}", self.details)
286 } else {
287 write!(f, "{}", self.details)
288 }
289 }
290}
291
292impl Display for AlarmDetails {
293 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
294 use AlarmDetails::*;
295 match self {
296 QSAL(instruction, op, msg) => {
297 write!(
298 f,
299 "QSAL: during execution of instruction {instruction:?}, {op}: {msg}",
300 )
301 }
302 PSAL(address, msg) => {
303 write!(
304 f,
305 "PSAL: P register set to illegal address {address:>06o}: {msg}",
306 )
307 }
308 OCSAL(inst, msg) => {
309 write!(
310 f,
311 "OCSAL: N register set to invalid instruction {:>012o}: {}",
312 inst.bits(),
313 msg
314 )
315 }
316 ROUNDTUITAL {
317 explanation,
318 bug_report_url,
319 } => {
320 write!(
321 f,
322 "ROUNDTUITAL: the program used a feature not supported in the emulator: {explanation}. See feature request at {bug_report_url}",
323 )
324 }
325
326 IOSAL {
327 unit,
328 operand,
329 message,
330 } => {
331 write!(f, "IOSAL: I/O alarm during operation on unit {unit:o}")?;
332 if let Some(oper) = operand {
333 write!(f, " with operand {oper}")?;
334 }
335 write!(f, ": {message}")
336 }
337
338 MISAL { affected_unit } => write!(
339 f,
340 "MISAL: program too slow; missed data for unit {affected_unit:o}"
341 ),
342
343 BUGAL {
344 diagnostics,
345 message,
346 activity,
347 } => {
348 let (issue_type, activity_desc): (Option<IssueType>, &'static str) = match activity
349 {
350 BugActivity::AlarmHandling => (None, "alarm handling"),
351 BugActivity::Io => (Some(IssueType::Io), "I/O"),
352 BugActivity::Opcode => (Some(IssueType::Opcode), "instruction execution"),
353 };
354 let report_url = bug_report_url(message, issue_type);
355 write!(
356 f,
357 "BUGAL: encountered a bug in enumator {activity_desc} during execution of {diagnostics}: {message}; please report this as a bug at {report_url}",
358 )
359 }
360 DEFERLOOPAL { address } => {
361 write!(
362 f,
363 "DEFERLOOPAL: infinite loop in deferred address at {address:>012o}",
364 )
365 }
366 }
367 }
368}
369
370impl Error for Alarm {}
371
372#[derive(Debug)]
375pub struct UnmaskedAlarm {
376 pub alarm: Alarm,
377 pub address: Option<Address>,
378 pub when: Duration,
379}
380
381impl Display for UnmaskedAlarm {
382 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
383 write!(f, "unmasked alarm {}", self.alarm)?;
384 if let Some(address) = self.address {
385 write!(f, "at address {address}")
386 } else {
387 Ok(())
388 }
389 }
390}
391
392pub trait Alarmer {
394 fn fire_if_not_masked<F: DiagnosticFetcher>(
396 &mut self,
397 alarm_instance: Alarm,
398 get_diags: F,
399 ) -> Result<(), Alarm>;
400 fn always_fire<F: DiagnosticFetcher>(&mut self, alarm_instance: Alarm, get_diags: F) -> Alarm;
402}