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
//! The location counter: address validity, alignment, and instruction sizing.
use super::MMixAssembler;
use super::Rule;
use super::instructions::MMixInstruction;
impl MMixAssembler {
/// Peek at instruction type to determine size without modifying state
pub(super) fn peek_instruction_type(
&self,
pair: pest::iterators::Pair<Rule>,
) -> Result<MMixInstruction, String> {
let inner = pair.into_inner().next().ok_or("Empty instruction")?;
match inner.as_rule() {
// SET is one tetra whichever variant it selects.
Rule::inst_set => Ok(MMixInstruction::SETRR(0, 0)),
Rule::inst_seti => Ok(MMixInstruction::SET(0, 0)),
Rule::inst_setl_ri => Ok(MMixInstruction::SETL(0, 0)),
Rule::inst_seth_ri => Ok(MMixInstruction::SETH(0, 0)),
Rule::inst_setmh_ri => Ok(MMixInstruction::SETMH(0, 0)),
Rule::inst_setml_ri => Ok(MMixInstruction::SETML(0, 0)),
Rule::inst_incl_ri => Ok(MMixInstruction::INCL(0, 0)),
// LDA $X,$Y,Z (3-operand form) always emits a real 4-byte LDA
// regardless of Z's value. The 2-operand form resolves against
// a GREG base like the memory forms and is the same one tetra
// always, so it falls to the catch-all.
Rule::inst_lda_rri => Ok(MMixInstruction::LDA(0, 0, 0)),
Rule::inst_halt => Ok(MMixInstruction::HALT),
// For all other instructions, return a standard 4-byte instruction
_ => Ok(MMixInstruction::ADDU(0, 0, 0)),
}
}
/// `Err` once the counter has passed the end, for a site that names no
/// item of its own -- a standalone label, a `LOC` label, a `BSPEC`
/// label or the `@` symbol.
fn valid_addr(&self) -> Result<(), ()> {
if self.past_end { Err(()) } else { Ok(()) }
}
/// [`Self::valid_addr`] at `site`, as the located error
/// [`Self::require_addr`] reports.
pub(super) fn require_valid(&self, site: (usize, usize)) -> Result<(), String> {
Self::require_addr(self.valid_addr(), &self.current_filename, site)
}
/// Round the location counter up to `alignment`, the way MMIXAL does
/// before it assembles an item: a label on that line names the rounded
/// address, and the skipped bytes are a gap rather than emitted padding.
/// `Err` when the counter has already passed the end, or rounding up
/// would need an address past it -- `current_addr` is left unchanged in
/// that case.
///
/// Both passes round at the same point, ahead of the item's operands, so
/// a forward reference sees the same address in either pass.
pub(super) fn align_current_addr(&mut self, alignment: u64) -> Result<(), ()> {
if self.past_end {
return Err(());
}
match self.current_addr.checked_next_multiple_of(alignment) {
Some(addr) => {
self.current_addr = addr;
Ok(())
}
None => {
self.past_end = true;
Err(())
}
}
}
/// Reserves `size` bytes at the current address for one item, advancing
/// the counter past them. `Err` when the counter is already past the
/// end, or when this item's own bytes would need one past
/// `#FFFFFFFFFFFFFFFF` -- `current_addr` is left unchanged either way.
/// `Ok` even when the item fills the address space's last byte exactly,
/// which only marks the counter past the end for whatever comes next.
pub(super) fn place_item(&mut self, size: u64) -> Result<(), ()> {
if self.past_end {
return Err(());
}
let end = u128::from(self.current_addr) + u128::from(size);
let past_last_addr = u128::from(u64::MAX) + 1;
if end > past_last_addr {
return Err(());
}
if end == past_last_addr {
self.past_end = true;
} else {
self.current_addr = end as u64;
}
Ok(())
}
/// `Ok` when the address `align_current_addr`/`place_item`/`valid_addr`
/// reported passes through unchanged; `Err` naming `site` -- a pending
/// label's or local label's own site when one sits ahead of the item
/// that failed, the item's own site otherwise.
pub(super) fn require_addr(
result: Result<(), ()>,
filename: &str,
site: (usize, usize),
) -> Result<(), String> {
result.map_err(|()| {
let (line, col) = site;
format!("{filename}:{line}:{col}: address past #FFFFFFFFFFFFFFFF")
})
}
/// Every instruction occupies a tetra-aligned slot, including the
/// pseudo-instructions `instruction_size` reports as wider than one tetra.
/// Alignment is not the emitted size.
pub(super) const INSTRUCTION_ALIGNMENT: u64 = 4;
pub(super) fn instruction_size(inst: &MMixInstruction) -> u64 {
match inst {
MMixInstruction::SET(_, _) => 16,
MMixInstruction::SETRR(_, _) => 4, // ORI $X, $Y, 0
MMixInstruction::BYTE(_) => 1,
MMixInstruction::WYDE(_) => 2,
MMixInstruction::TETRA(_) => 4,
MMixInstruction::OCTA(_) => 8,
_ => 4,
}
}
}