Skip to content

Commit 48e20f3

Browse files
committed
[cpu] Inclue a bug-reporting URL in the BUGAL alarm message.
We arrange for the URL to provide some reasonable values for bug title and labels.
1 parent 3bb0a80 commit 48e20f3

File tree

16 files changed

+524
-227
lines changed

16 files changed

+524
-227
lines changed

cpu/Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@ publish = false
1111

1212
[dependencies]
1313
base = { path = "../base" }
14-
conv = "0.3" # MIT license
14+
conv = "0.3" # MIT license
1515
serde = { version = "1", features = ["derive"] }
16-
tracing = "0.1" # MIT license
16+
tracing = "0.1" # MIT license
1717
wasm-bindgen = "0.2"
18+
# Using version 1.0 of idna_adapter removes punycode URL support from
19+
# the url crate. In general this is a bad idea, but we know that the
20+
# only URLs we will generate are for a domain name that does not use
21+
# punycode.
22+
idna_adapter = "1.0"
23+
url = "2.5.7"
1824

1925
[lib]
2026
name = "cpu"

cpu/src/alarm.rs

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ use serde::Serialize;
77
use base::instruction::Instruction;
88
use base::prelude::*;
99

10+
use super::bugreport::{IssueType, bug_report_url};
11+
use super::diagnostics::{CurrentInstructionDiagnostics, DiagnosticFetcher};
12+
1013
#[derive(Debug, Clone)]
1114
pub enum BadMemOp {
1215
Read(Unsigned36Bit),
@@ -125,6 +128,14 @@ fn test_alarm_kind_round_trip() {
125128
assert!(AlarmKind::try_from("this is not an alarm name").is_err());
126129
}
127130

131+
/// Indicates what the emulator was doing when a bug was discovered.
132+
#[derive(Debug, Clone)]
133+
pub enum BugActivity {
134+
Io,
135+
Opcode,
136+
AlarmHandling,
137+
}
138+
128139
// Alarms from User's Handbook section 5-2.2; full names are taken
129140
// from section 10-2.5.1 (vol 2) of the Technical Manual.
130141
// These acrronyms are upper case to follow the names in the TX-2 documentation.
@@ -198,7 +209,11 @@ pub enum AlarmDetails {
198209

199210
/// There is a bug in the simulator.
200211
BUGAL {
201-
instr: Option<Instruction>,
212+
/// What were we doing?
213+
activity: BugActivity,
214+
/// What instruction was executing?
215+
diagnostics: CurrentInstructionDiagnostics,
216+
/// What went wrong?
202217
message: String,
203218
},
204219
}
@@ -222,18 +237,11 @@ impl AlarmDetails {
222237
AlarmDetails::PSAL(_, _) => AlarmKind::PSAL,
223238
AlarmDetails::OCSAL(_, _) => AlarmKind::OCSAL,
224239
AlarmDetails::QSAL(_, _, _) => AlarmKind::QSAL,
225-
AlarmDetails::IOSAL {
226-
unit: _,
227-
operand: _,
228-
message: _,
229-
} => AlarmKind::IOSAL,
230-
AlarmDetails::MISAL { affected_unit: _ } => AlarmKind::MISAL,
240+
AlarmDetails::IOSAL { .. } => AlarmKind::IOSAL,
241+
AlarmDetails::MISAL { .. } => AlarmKind::MISAL,
231242
AlarmDetails::ROUNDTUITAL { .. } => AlarmKind::ROUNDTUITAL,
232-
AlarmDetails::DEFERLOOPAL { address: _ } => AlarmKind::DEFERLOOPAL,
233-
AlarmDetails::BUGAL {
234-
instr: _,
235-
message: _,
236-
} => AlarmKind::BUGAL,
243+
AlarmDetails::DEFERLOOPAL { .. } => AlarmKind::DEFERLOOPAL,
244+
AlarmDetails::BUGAL { .. } => AlarmKind::BUGAL,
237245
}
238246
}
239247
}
@@ -299,24 +307,22 @@ impl Display for AlarmDetails {
299307
"MISAL: program too slow; missed data for unit {affected_unit:o}"
300308
),
301309

302-
BUGAL { instr, message } => {
303-
if let Some(instruction) = instr.as_ref() {
304-
if let Ok(symbolic) = SymbolicInstruction::try_from(instruction) {
305-
write!(
306-
f,
307-
"BUGAL: encountered simulator bug during execution of instruction {symbolic}: {message}"
308-
)
309-
} else {
310-
write!(
311-
f,
312-
"BUGAL: encountered simulator bug during execution of instruction {:o}: {}",
313-
instruction.bits(),
314-
message,
315-
)
316-
}
317-
} else {
318-
write!(f, "BUGAL: encountered simulator bug: {message}")
319-
}
310+
BUGAL {
311+
diagnostics,
312+
message,
313+
activity,
314+
} => {
315+
let (issue_type, activity_desc): (Option<IssueType>, &'static str) = match activity
316+
{
317+
BugActivity::AlarmHandling => (None, "alarm handling"),
318+
BugActivity::Io => (Some(IssueType::Io), "I/O"),
319+
BugActivity::Opcode => (Some(IssueType::Opcode), "instruction execution"),
320+
};
321+
let report_url = bug_report_url(message, issue_type);
322+
write!(
323+
f,
324+
"BUGAL: encountered a bug in enumator {activity_desc} during execution of {diagnostics}: {message}; please report this as a bug at {report_url}",
325+
)
320326
}
321327
DEFERLOOPAL { address } => {
322328
write!(
@@ -349,6 +355,10 @@ impl Display for UnmaskedAlarm {
349355
}
350356

351357
pub trait Alarmer {
352-
fn fire_if_not_masked(&mut self, alarm_instance: Alarm) -> Result<(), Alarm>;
353-
fn always_fire(&mut self, alarm_instance: Alarm) -> Alarm;
358+
fn fire_if_not_masked<F: DiagnosticFetcher>(
359+
&mut self,
360+
alarm_instance: Alarm,
361+
get_diags: F,
362+
) -> Result<(), Alarm>;
363+
fn always_fire<F: DiagnosticFetcher>(&mut self, alarm_instance: Alarm, get_diags: F) -> Alarm;
354364
}

cpu/src/alarmunit.rs

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ use std::collections::{BTreeMap, BTreeSet};
55
use serde::Serialize;
66
use tracing::{Level, event};
77

8-
use super::alarm::{Alarm, AlarmDetails, AlarmKind, AlarmMaskability, Alarmer};
8+
use super::alarm::{Alarm, AlarmDetails, AlarmKind, AlarmMaskability, Alarmer, BugActivity};
99
use super::changelog::ChangeIndex;
10+
use super::diagnostics::DiagnosticFetcher;
11+
use crate::diagnostics::CurrentInstructionDiagnostics;
1012

1113
#[cfg(test)]
1214
use base::Unsigned6Bit;
@@ -80,17 +82,22 @@ impl AlarmUnit {
8082
}
8183
}
8284

83-
pub fn mask(&mut self, kind: AlarmKind) -> Result<(), Alarm> {
85+
pub fn mask(
86+
&mut self,
87+
kind: AlarmKind,
88+
diags: &CurrentInstructionDiagnostics,
89+
) -> Result<(), Alarm> {
8490
match kind.maskable() {
8591
AlarmMaskability::Unmaskable => {
8692
let bug = Alarm {
8793
sequence: None,
8894
details: AlarmDetails::BUGAL {
89-
instr: None,
95+
activity: BugActivity::AlarmHandling,
96+
diagnostics: diags.clone(),
9097
message: format!("attempt to mask unmaskable alarm {kind}"),
9198
},
9299
};
93-
Err(self.always_fire(bug))
100+
Err(self.always_fire(bug, diags))
94101
}
95102
AlarmMaskability::Maskable => {
96103
self.changes.add(kind);
@@ -161,12 +168,20 @@ impl AlarmUnit {
161168
}
162169

163170
impl Alarmer for AlarmUnit {
164-
fn fire_if_not_masked(&mut self, alarm_instance: Alarm) -> Result<(), Alarm> {
171+
fn fire_if_not_masked<F: DiagnosticFetcher>(
172+
&mut self,
173+
alarm_instance: Alarm,
174+
_get_diags: F,
175+
) -> Result<(), Alarm> {
165176
self.changes.add(alarm_instance.kind());
166177
self.set_active(alarm_instance)
167178
}
168179

169-
fn always_fire(&mut self, alarm_instance: Alarm) -> Alarm {
180+
fn always_fire<F: DiagnosticFetcher>(
181+
&mut self,
182+
alarm_instance: Alarm,
183+
get_diagnostics: F,
184+
) -> Alarm {
170185
let kind = alarm_instance.kind();
171186
self.changes.add(kind);
172187
let sequence = alarm_instance.sequence;
@@ -176,7 +191,8 @@ impl Alarmer for AlarmUnit {
176191
let bug = Alarm {
177192
sequence,
178193
details: AlarmDetails::BUGAL {
179-
instr: None,
194+
activity: BugActivity::AlarmHandling,
195+
diagnostics: get_diagnostics.diagnostics(),
180196
message: format!(
181197
"alarm {kind} is masked, but the caller assumed it could not be"
182198
),
@@ -193,19 +209,32 @@ impl Alarmer for AlarmUnit {
193209

194210
#[test]
195211
fn unmaskable_alarms_are_not_maskable() {
212+
use base::prelude::{Address, Instruction};
213+
196214
let mut alarm_unit = AlarmUnit::new_with_panic(false);
197215
assert!(!alarm_unit.unmasked_alarm_active());
216+
let diagnostics = CurrentInstructionDiagnostics {
217+
current_instruction: Instruction::invalid(),
218+
instruction_address: Address::ZERO,
219+
};
198220
// Any attempt to mask an unmaskable alarm should itself result in an error.
199-
assert!(alarm_unit.mask(AlarmKind::ROUNDTUITAL).is_err());
221+
assert!(
222+
alarm_unit
223+
.mask(AlarmKind::ROUNDTUITAL, &diagnostics)
224+
.is_err()
225+
);
200226
// Now we raise some non-maskable alarm.
201227
assert!(matches!(
202-
alarm_unit.fire_if_not_masked(Alarm {
203-
sequence: Some(Unsigned6Bit::ZERO),
204-
details: AlarmDetails::ROUNDTUITAL {
205-
explanation: "The ROUNDTUITAL alarm is not maskable!".to_string(),
206-
bug_report_url: "https://github.com/TX-2/TX-2-simulator/issues/144",
228+
alarm_unit.fire_if_not_masked(
229+
Alarm {
230+
sequence: Some(Unsigned6Bit::ZERO),
231+
details: AlarmDetails::ROUNDTUITAL {
232+
explanation: "The ROUNDTUITAL alarm is not maskable!".to_string(),
233+
bug_report_url: "https://github.com/TX-2/TX-2-simulator/issues/144",
234+
},
207235
},
208-
}),
236+
&diagnostics
237+
),
209238
Err(Alarm {
210239
sequence: Some(_),
211240
details: AlarmDetails::ROUNDTUITAL { .. },
@@ -218,20 +247,26 @@ fn unmaskable_alarms_are_not_maskable() {
218247

219248
#[test]
220249
fn maskable_alarms_are_not_masked_by_default() {
250+
use base::prelude::{Address, Instruction};
251+
221252
let mut alarm_unit = AlarmUnit::new_with_panic(false);
222253
assert!(!alarm_unit.unmasked_alarm_active());
254+
let diagnostics = CurrentInstructionDiagnostics {
255+
current_instruction: Instruction::invalid(),
256+
instruction_address: Address::ZERO,
257+
};
223258
// Now we raise some maskable, but not masked, alarm.
224259
let the_alarm = Alarm {
225260
sequence: Some(Unsigned6Bit::ZERO),
226261
details: AlarmDetails::PSAL(22, "some PPSAL alarm".to_string()),
227262
};
228263
// raise the alarm, verify that it really fires.
229264
assert!(matches!(
230-
alarm_unit.fire_if_not_masked(the_alarm),
265+
alarm_unit.fire_if_not_masked(the_alarm, &diagnostics),
231266
Err(Alarm {
232267
sequence: _,
233268
details: AlarmDetails::PSAL(22, _),
234-
})
269+
},)
235270
));
236271
// Verify that the alarm manager considers that an unmasked (in
237272
// this case because maskable but not actually masked) alarm is active.

cpu/src/bugreport.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/// Generate bug-report URLs.
2+
use url::{ParseError, Url};
3+
4+
// We have a version override for idna_adapter in the Cargo.toml file,
5+
// because GITHUB_URL is ASCII-only and we want to keep the size of
6+
// the binary down.
7+
use idna_adapter as _;
8+
9+
const GITHUB_URL: &str = "https://github.com/";
10+
const ORG_NAME: &str = "TX-2";
11+
const REPO_NAME: &str = "TX-2-simulator";
12+
13+
#[derive(Debug, Clone, Copy, PartialOrd, Ord, Hash, Eq, PartialEq)]
14+
pub enum IssueType {
15+
Io,
16+
Opcode,
17+
}
18+
19+
pub fn bug_report_url(title: &str, issue_type: Option<IssueType>) -> Url {
20+
bug_report_url_internal(title, issue_type).expect("bug-report URLs should always be valid")
21+
}
22+
23+
fn bug_report_url_internal(title: &str, issue_type: Option<IssueType>) -> Result<Url, ParseError> {
24+
let mut url = Url::parse(GITHUB_URL)?.join(&format!("{ORG_NAME}/{REPO_NAME}/issues/new"))?;
25+
url.query_pairs_mut()
26+
.append_pair("template", "bug_report.md")
27+
.append_pair("title", title);
28+
match issue_type {
29+
Some(IssueType::Io) => {
30+
url.query_pairs_mut().append_pair("labels", "I/O");
31+
}
32+
Some(IssueType::Opcode) => {
33+
url.query_pairs_mut().append_pair("labels", "Opcode");
34+
}
35+
None => (), // add no labels.
36+
}
37+
Ok(url)
38+
}
39+
40+
#[test]
41+
fn test_bug_report_url() {
42+
assert_eq!(
43+
bug_report_url("Eat some falafel", Some(IssueType::Io)).to_string(),
44+
"https://github.com/TX-2/TX-2-simulator/issues/new?template=bug_report.md&title=Eat+some+falafel&labels=I%2FO"
45+
);
46+
}

0 commit comments

Comments
 (0)