1use std::io::Write;
3use std::path::Path;
4
5use tracing::{Level, event, span};
6
7use base::prelude::{
8 Address, Instruction, Opcode, OperandAddress, Signed18Bit, SymbolicInstruction, Unsigned6Bit,
9 Unsigned18Bit, Unsigned36Bit, join_halves, split_halves, u5, u6, u18, unsplay,
10};
11
12use super::super::readerleader::reader_leader;
13use super::super::types::{AssemblerFailure, IoAction, IoFailed, IoTarget};
14use super::Binary;
15
16fn write_data<W: Write>(
25 writer: &mut W,
26 output_file_name: &Path,
27 data: &[Unsigned36Bit],
28) -> Result<(), AssemblerFailure> {
29 let mut inner = || -> Result<(), std::io::Error> {
30 const OUTPUT_CHUNK_SIZE: usize = 1024;
31 for chunk in data.chunks(OUTPUT_CHUNK_SIZE) {
32 let mut buf: Vec<u8> = Vec::with_capacity(chunk.len() * 6);
33 for word in chunk {
34 let unsplayed: [Unsigned6Bit; 6] = unsplay(*word);
35 buf.extend(unsplayed.into_iter().map(|u| u8::from(u) | (1 << 7)));
36 }
37 writer.write_all(&buf)?;
38 }
39 Ok(())
40 };
41 inner().map_err(|e| {
42 AssemblerFailure::Io(IoFailed {
43 action: IoAction::Write,
44 target: IoTarget::File(output_file_name.to_path_buf()),
45 error: e,
46 })
47 })
48}
49
50fn update_checksum_by_halfword(sum: Signed18Bit, halfword: Signed18Bit) -> Signed18Bit {
52 sum.wrapping_add(halfword)
53}
54
55fn update_checksum(sum: Signed18Bit, word: Unsigned36Bit) -> Signed18Bit {
57 let (l, r) = split_halves(word);
58 update_checksum_by_halfword(
59 update_checksum_by_halfword(l.reinterpret_as_signed(), sum),
60 r.reinterpret_as_signed(),
61 )
62}
63
64fn create_tape_block(
75 address: Address,
76 code: &[Unsigned36Bit],
77 last: bool,
78) -> Result<Vec<Unsigned36Bit>, AssemblerFailure> {
79 let length = code.len();
80 if code.is_empty() {
81 return Err(AssemblerFailure::BadTapeBlock {
82 address,
83 length,
84 msg: "tape block is empty but tape blocks are not allowed to be empty (the format does not support it)".to_string()
85 });
86 }
87 let len: Unsigned18Bit = match Unsigned18Bit::try_from(code.len()) {
88 Err(_) => {
89 return Err(AssemblerFailure::BadTapeBlock {
90 address,
91 length,
92 msg: "block is too long for output format".to_string(),
93 });
94 }
95 Ok(len) => len,
96 };
97 let end: Unsigned18Bit = match Unsigned18Bit::from(address)
98 .checked_add(len)
99 .and_then(|n| n.checked_sub(Unsigned18Bit::ONE))
100 {
101 None => {
102 return Err(AssemblerFailure::BadTapeBlock {
103 address,
104 length,
105 msg: "end of block does not fit into physical memory".to_string(),
106 });
107 }
108 Some(end) => end,
109 };
110 event!(
111 Level::DEBUG,
112 "creating a tape block with origin={address:>06o}, len={len:o}, end={end:>06o}"
113 );
114 let mut block = Vec::with_capacity(code.len().saturating_add(2usize));
115 let encoded_len: Unsigned18Bit = match Signed18Bit::try_from(len) {
116 Ok(n) => Signed18Bit::ONE.checked_sub(n),
117 Err(_) => None,
118 }
119 .expect("overflow in length encoding")
120 .reinterpret_as_unsigned();
121 let mut checksum = Signed18Bit::ZERO;
122 block.push(join_halves(encoded_len, end));
123 block.extend(code);
124
125 for w in &block {
126 checksum = update_checksum(checksum, *w);
127 }
128 let next: Unsigned18Bit = { if last { 0o27_u8 } else { 0o3_u8 }.into() };
129 checksum = update_checksum_by_halfword(checksum, next.reinterpret_as_signed());
130 let balance = Signed18Bit::ZERO.wrapping_sub(checksum);
131 checksum = update_checksum_by_halfword(checksum, balance);
132 block.push(join_halves(balance.reinterpret_as_unsigned(), next));
133 assert_eq!(checksum, Signed18Bit::ZERO);
134 Ok(block)
135}
136
137fn create_begin_block(
141 program_start: Option<Address>,
142 empty_program: bool,
143) -> Result<Vec<Unsigned36Bit>, AssemblerFailure> {
144 let disconnect_tape = SymbolicInstruction {
145 held: false,
147 configuration: u5!(1), opcode: Opcode::Opr,
149 index: u6!(0o52),
150 operand_address: OperandAddress::direct(Address::from(u18!(0o020_000))),
151 };
152 let jump: SymbolicInstruction = if let Some(start) = program_start {
153 let (physical, deferred) = start.split();
154 if deferred {
155 unimplemented!(
161 "PUNCH directive specifies deferred start address {start:o}; this is (deliberately) not yet supported - check carefully!"
162 );
163 }
164 SymbolicInstruction {
167 held: false,
168 configuration: u5!(0o14), opcode: Opcode::Jmp,
170 index: Unsigned6Bit::ZERO,
171 operand_address: OperandAddress::direct(Address::from(physical)),
172 }
173 } else {
174 SymbolicInstruction {
177 held: false,
178 configuration: u5!(0o20), opcode: Opcode::Jmp,
180 index: Unsigned6Bit::ZERO,
181 operand_address: OperandAddress::direct(Address::from(u18!(0o27))),
182 }
183 };
184 let location = Address::from(Unsigned18Bit::from(0o27_u8));
185 let code = vec![
186 Instruction::from(&disconnect_tape).bits(),
187 Instruction::from(&jump).bits(),
188 ];
189 create_tape_block(location, &code, !empty_program)
190}
191
192pub fn write_user_program<W: Write>(
205 binary: &Binary,
206 writer: &mut W,
207 output_file_name: &Path,
208) -> Result<(), AssemblerFailure> {
209 let span = span!(Level::ERROR, "write binary program");
210 let _enter = span.enter();
211
212 write_data(writer, output_file_name, &reader_leader())?;
219 for chunk in binary.chunks() {
220 if chunk.is_empty() {
221 event!(
222 Level::ERROR,
223 "Will not write empty block at {:o}; the assembler should not have generated one; this is a bug.",
224 chunk.address,
225 );
226 continue;
227 }
228 let block = create_tape_block(chunk.address, &chunk.words, false)?;
229 write_data(writer, output_file_name, &block)?;
230 }
231
232 write_data(
239 writer,
240 output_file_name,
241 &create_begin_block(binary.entry_point(), binary.is_empty())?,
242 )?;
243
244 writer.flush().map_err(|e| {
245 AssemblerFailure::Io(IoFailed {
246 action: IoAction::Write,
247 target: IoTarget::File(output_file_name.to_owned()),
248 error: e,
249 })
250 })
251}