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(
69 address: Address,
70 code: &[Unsigned36Bit],
71 last: bool,
72) -> Result<Vec<Unsigned36Bit>, AssemblerFailure> {
73 let length = code.len();
74 if code.is_empty() {
75 return Err(AssemblerFailure::BadTapeBlock {
76 address,
77 length,
78 msg: "tape block is empty but tape blocks are not allowed to be empty (the format does not support it)".to_string()
79 });
80 }
81 let len: Unsigned18Bit = match Unsigned18Bit::try_from(code.len()) {
82 Err(_) => {
83 return Err(AssemblerFailure::BadTapeBlock {
84 address,
85 length,
86 msg: "block is too long for output format".to_string(),
87 });
88 }
89 Ok(len) => len,
90 };
91 let end: Unsigned18Bit = match Unsigned18Bit::from(address)
92 .checked_add(len)
93 .and_then(|n| n.checked_sub(Unsigned18Bit::ONE))
94 {
95 None => {
96 return Err(AssemblerFailure::BadTapeBlock {
97 address,
98 length,
99 msg: "end of block does not fit into physical memory".to_string(),
100 });
101 }
102 Some(end) => end,
103 };
104 event!(
105 Level::DEBUG,
106 "creating a tape block with origin={address:>06o}, len={len:o}, end={end:>06o}"
107 );
108 let mut block = Vec::with_capacity(code.len().saturating_add(2usize));
109 let encoded_len: Unsigned18Bit = match Signed18Bit::try_from(len) {
110 Ok(n) => Signed18Bit::ONE.checked_sub(n),
111 Err(_) => None,
112 }
113 .expect("overflow in length encoding")
114 .reinterpret_as_unsigned();
115 let mut checksum = Signed18Bit::ZERO;
116 block.push(join_halves(encoded_len, end));
117 block.extend(code);
118
119 for w in &block {
120 checksum = update_checksum(checksum, *w);
121 }
122 let next: Unsigned18Bit = { if last { 0o27_u8 } else { 0o3_u8 } }.into();
123 checksum = update_checksum_by_halfword(checksum, next.reinterpret_as_signed());
124 let balance = Signed18Bit::ZERO.wrapping_sub(checksum);
125 checksum = update_checksum_by_halfword(checksum, balance);
126 block.push(join_halves(balance.reinterpret_as_unsigned(), next));
127 assert_eq!(checksum, Signed18Bit::ZERO);
128 Ok(block)
129}
130
131fn create_begin_block(
136 program_start: Option<Address>,
137 empty_program: bool,
138) -> Result<Vec<Unsigned36Bit>, AssemblerFailure> {
139 let disconnect_tape = SymbolicInstruction {
154 held: false,
156 configuration: u5!(1), opcode: Opcode::Opr,
158 index: u6!(0o52),
159 operand_address: OperandAddress::direct(Address::from(u18!(0o020_000))),
160 };
161 let jump: SymbolicInstruction = if let Some(start) = program_start {
162 let (physical, deferred) = start.split();
163 if deferred {
164 unimplemented!(
170 "PUNCH directive specifies deferred start address {start:o}; this is (deliberately) not yet supported - check carefully!"
171 );
172 }
173 SymbolicInstruction {
176 held: false,
177 configuration: u5!(0o14), opcode: Opcode::Jmp,
179 index: Unsigned6Bit::ZERO,
180 operand_address: OperandAddress::direct(Address::from(physical)),
181 }
182 } else {
183 SymbolicInstruction {
186 held: false,
187 configuration: u5!(0o20), opcode: Opcode::Jmp,
189 index: Unsigned6Bit::ZERO,
190 operand_address: OperandAddress::direct(Address::from(u18!(0o27))),
191 }
192 };
193 let location = Address::from(Unsigned18Bit::from(0o27_u8));
194 let code = vec![
195 Instruction::from(&disconnect_tape).bits(),
196 Instruction::from(&jump).bits(),
197 ];
198 create_tape_block(location, &code, !empty_program)
199}
200
201pub fn write_user_program<W: Write>(
214 binary: &Binary,
215 writer: &mut W,
216 output_file_name: &Path,
217) -> Result<(), AssemblerFailure> {
218 let span = span!(Level::ERROR, "write binary program");
219 let _enter = span.enter();
220
221 write_data(writer, output_file_name, &reader_leader())?;
228
229 write_data(
242 writer,
243 output_file_name,
244 &create_begin_block(binary.entry_point(), binary.is_empty())?,
245 )?;
246
247 let mut iter = binary.chunks().iter().peekable();
249 while let Some(chunk) = iter.next() {
250 if chunk.is_empty() {
251 event!(
252 Level::ERROR,
253 "Will not write empty block at {:o}; the assembler should not have generated one; this is a bug.",
254 chunk.address,
255 );
256 continue;
257 }
258 let last: bool = iter.peek().is_none();
259 let block = create_tape_block(chunk.address, &chunk.words, last)?;
260 write_data(writer, output_file_name, &block)?;
261 }
262
263 writer.flush().map_err(|e| {
264 AssemblerFailure::Io(IoFailed {
265 action: IoAction::Write,
266 target: IoTarget::File(output_file_name.to_owned()),
267 error: e,
268 })
269 })
270}