pub mod encoding;
use std::collections::hash_map::Entry;
use std::collections::{BTreeMap, HashMap};
use std::ops::Range;
use logos::Span;
use crate::ast::asm::{AsmInstr, Directive, Stmt, StmtKind};
use crate::ast::sim::SimInstr;
use crate::ast::{IOffset, ImmOrReg, Offset, OffsetNewErr, PCOffset, Reg};
use crate::err::ErrSpan;
pub fn assemble(ast: Vec<Stmt>) -> Result<ObjectFile, AsmErr> {
let sym = SymbolTable::new(&ast, None)?;
ObjectFile::new(ast, sym, false)
}
pub fn assemble_debug(ast: Vec<Stmt>, src: &str) -> Result<ObjectFile, AsmErr> {
let sym = SymbolTable::new(&ast, Some(src))?;
ObjectFile::new(ast, sym, true)
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum AsmErrKind {
UndetAddrLabel,
UndetAddrStmt,
UnclosedOrig,
UnopenedOrig,
OverlappingOrig,
OverlappingLabels,
WrappingBlock,
BlockInIO,
OverlappingBlocks,
OffsetNewErr(OffsetNewErr),
OffsetExternal,
CouldNotFindLabel,
}
impl std::fmt::Display for AsmErrKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UndetAddrLabel => f.write_str("cannot determine address of label"),
Self::UndetAddrStmt => f.write_str("cannot determine address of statement"),
Self::UnclosedOrig => f.write_str(".orig directive was never closed"),
Self::UnopenedOrig => f.write_str(".end does not have associated .orig"),
Self::OverlappingOrig => f.write_str("cannot have an .orig inside another region"),
Self::OverlappingLabels => f.write_str("label was defined multiple times"),
Self::WrappingBlock => f.write_str("block wraps around in memory"),
Self::BlockInIO => f.write_str("cannot write code into memory-mapped IO region"),
Self::OverlappingBlocks => f.write_str("regions overlap in memory"),
Self::OffsetNewErr(e) => e.fmt(f),
Self::OffsetExternal => f.write_str("cannot use external label here"),
Self::CouldNotFindLabel => f.write_str("label could not be found"),
}
}
}
#[derive(Debug)]
pub struct AsmErr {
pub kind: AsmErrKind,
pub span: ErrSpan
}
impl AsmErr {
pub fn new<E: Into<ErrSpan>>(kind: AsmErrKind, span: E) -> Self {
AsmErr { kind, span: span.into() }
}
}
impl std::fmt::Display for AsmErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.kind.fmt(f)
}
}
impl std::error::Error for AsmErr {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.kind {
AsmErrKind::OffsetNewErr(e) => Some(e),
_ => None
}
}
}
impl crate::err::Error for AsmErr {
fn span(&self) -> Option<crate::err::ErrSpan> {
Some(self.span.clone())
}
fn help(&self) -> Option<std::borrow::Cow<str>> {
match &self.kind {
AsmErrKind::UndetAddrLabel => Some("try moving this label inside of an .orig/.end block".into()),
AsmErrKind::UndetAddrStmt => Some("try moving this statement inside of an .orig/.end block".into()),
AsmErrKind::UnclosedOrig => Some("try adding an .end directive at the end of this block".into()),
AsmErrKind::UnopenedOrig => Some("try adding an .orig directive at the beginning of this block".into()),
AsmErrKind::OverlappingOrig => Some("try adding an .end directive at the end of the outer .orig block".into()),
AsmErrKind::OverlappingLabels => Some("labels must be unique within a file, try renaming one of the labels".into()),
AsmErrKind::OverlappingBlocks => Some("try moving the starting address of one of these regions".into()),
AsmErrKind::WrappingBlock => Some("user code typically starts at x3000 and is short enough to not wrap memory".into()),
AsmErrKind::BlockInIO => Some("try not doing that".into()),
AsmErrKind::OffsetNewErr(e) => e.help(),
AsmErrKind::OffsetExternal => Some("external labels cannot be an offset operand; try creating a .fill LABEL directive".into()),
AsmErrKind::CouldNotFindLabel => Some("try adding this label before an instruction or directive".into()),
}
}
}
const IO_START: u16 = 0xFE00;
#[derive(PartialEq, Eq, Clone)]
struct LineSymbolMap(BTreeMap<usize, Vec<u16>>);
impl LineSymbolMap {
fn new(lines: Vec<Option<u16>>) -> Option<Self> {
let mut blocks = BTreeMap::new();
let mut current = None;
for (i, line) in lines.into_iter().enumerate() {
match line {
Some(addr) => current.get_or_insert_with(Vec::new).push(addr),
None => if let Some(bl) = current.take() {
blocks.insert(i - bl.len(), bl);
},
}
}
Self::from_blocks(blocks)
}
fn from_blocks(blocks: impl IntoIterator<Item=(usize, Vec<u16>)>) -> Option<Self> {
let mut bl: Vec<_> = blocks.into_iter().collect();
bl.sort_by_key(|&(l, _)| l);
let not_overlapping = bl.windows(2).all(|win| {
let [(ls, lb), (rs, _)] = win else { unreachable!() };
ls + lb.len() <= *rs
});
match not_overlapping {
true => {
let sorted = bl.iter().all(|(_, lb)| {
lb.windows(2).all(|win| win[0] <= win[1])
});
sorted.then(|| Self(bl.into_iter().collect()))
}
false => None,
}
}
fn get(&self, line: usize) -> Option<u16> {
let (start, block) = self.0.range(..=line).next_back()?;
block.get(line - *start).copied()
}
fn find(&self, addr: u16) -> Option<usize> {
self.0.iter()
.find_map(|(start, words)| {
words.binary_search(&addr)
.ok()
.map(|o| start + o)
})
}
fn block_iter(&self) -> impl Iterator<Item=(usize, &[u16])> + '_ {
self.0.iter()
.map(|(&i, words)| (i, words.as_slice()))
}
fn iter(&self) -> impl Iterator<Item=(usize, u16)> + '_ {
self.block_iter()
.flat_map(|(i, words)| {
words.iter()
.enumerate()
.map(move |(off, &addr)| (i + off, addr))
})
}
}
impl std::fmt::Debug for LineSymbolMap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_map()
.entries(self.iter().map(|(i, v)| (i, Addr(v))))
.finish()
}
}
#[derive(PartialEq, Eq, Clone)]
pub struct SourceInfo {
src: String,
nl_indices: Vec<usize>
}
impl std::fmt::Debug for SourceInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SourceInfo")
.field("nl_indices", &self.nl_indices)
.finish_non_exhaustive()
}
}
impl SourceInfo {
pub fn new(src: &str) -> Self {
Self::from_string(src.to_string())
}
fn from_string(src: String) -> Self {
let nl_indices: Vec<_> = src
.match_indices('\n')
.map(|(i, _)| i)
.chain([src.len()])
.collect();
Self { src, nl_indices }
}
pub fn source(&self) -> &str {
&self.src
}
pub fn count_lines(&self) -> usize {
self.nl_indices.len()
}
fn raw_line_span(&self, line: usize) -> Option<Range<usize>> {
if !(0..self.count_lines()).contains(&line) {
return None;
};
let start = match line {
0 => 0,
_ => self.nl_indices[line - 1] + 1
};
let eof = self.src.len();
let end = match self.nl_indices.get(line) {
Some(i) => (i + 1).min(eof), None => eof,
};
Some(start..end)
}
pub fn line_span(&self, line: usize) -> Option<Range<usize>> {
let Range { mut start, mut end } = self.raw_line_span(line)?;
let line = &self.src[start..end];
let end_trimmed = line.trim_end();
end -= line.len() - end_trimmed.len();
let line = end_trimmed;
start += line.len() - line.trim_start().len();
Some(start..end)
}
pub fn read_line(&self, line: usize) -> Option<&str> {
self.line_span(line).map(|r| &self.src[r])
}
fn get_line(&self, index: usize) -> usize {
self.nl_indices.partition_point(|&start| start < index)
}
pub fn get_pos_pair(&self, index: usize) -> (usize, usize) {
let lno = self.get_line(index);
let Range { start: lstart, .. } = self.raw_line_span(lno)
.or_else(|| self.raw_line_span(self.nl_indices.len()))
.unwrap_or(0..0);
let cno = index - lstart;
(lno, cno)
}
}
impl From<&'_ str> for SourceInfo {
fn from(value: &'_ str) -> Self {
Self::new(value)
}
}
impl From<String> for SourceInfo {
fn from(value: String) -> Self {
Self::from_string(value)
}
}
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug)]
struct SymbolData {
addr: u16,
src_start: usize,
external: bool
}
impl SymbolData {
fn span(&self, label: &str) -> Range<usize> {
self.src_start .. (self.src_start + label.len())
}
}
#[derive(PartialEq, Eq, Debug, Clone)]
struct DebugSymbols {
line_map: LineSymbolMap,
src_info: SourceInfo
}
impl DebugSymbols {
pub fn lookup_line(&self, line: usize) -> Option<u16> {
self.line_map.get(line)
}
pub fn rev_lookup_line(&self, addr: u16) -> Option<usize> {
self.line_map.find(addr)
}
pub fn link(mut a: Self, b: Self) -> Result<Self, AsmErr> {
let lines = a.src_info.count_lines();
a.line_map.0.extend({
b.line_map.0.into_iter()
.map(|(k, v)| (k + lines, v))
});
a.src_info = SourceInfo::from_string(a.src_info.src + "\n" + &b.src_info.src);
Ok(a)
}
}
#[derive(PartialEq, Eq, Clone)]
pub struct SymbolTable {
label_map: HashMap<String, SymbolData>,
rel_map: HashMap<u16, String>,
debug_symbols: Option<DebugSymbols>,
}
impl SymbolTable {
pub fn new(stmts: &[Stmt], src: Option<&str>) -> Result<Self, AsmErr> {
struct Cursor {
lc: u16,
overflowed: bool,
block_orig: Span,
}
impl Cursor {
fn new(lc: u16, block_orig: Span) -> Self {
Self { lc, overflowed: false, block_orig }
}
fn shift(&mut self, n: u16) -> Result<(), AsmErrKind> {
if n == 0 { return Ok(()); }
match (self.overflowed, self.lc.checked_add(n)) {
(true, _) => Err(AsmErrKind::WrappingBlock),
(false, Some(new_lc)) if new_lc > IO_START => Err(AsmErrKind::BlockInIO),
(false, Some(new_lc)) => {
self.lc = new_lc;
Ok(())
},
(false, None) => {
let lc = std::mem::take(&mut self.lc);
self.overflowed = true;
Err(match lc == n.wrapping_neg() {
true => AsmErrKind::BlockInIO,
false => AsmErrKind::WrappingBlock,
})
}
}
}
}
fn add_label(
labels: &mut HashMap<String, SymbolData>,
label: &crate::ast::Label,
addr: u16,
external: bool
) -> Result<(), AsmErr> {
match labels.entry(label.name.to_uppercase()) {
Entry::Occupied(e) if e.get().addr != addr => {
let span1 = e.get().span(e.key());
let span2 = label.span();
Err(AsmErr::new(AsmErrKind::OverlappingLabels, [span1, span2]))
},
Entry::Occupied(_) => Ok(()),
Entry::Vacant(e) => {
e.insert(SymbolData { addr, src_start: label.span().start, external });
Ok(())
}
}
}
let mut cursor: Option<Cursor> = None;
let mut label_map: HashMap<String, SymbolData> = HashMap::new();
let mut rel_map = HashMap::new();
let mut debug_sym = src.map(|s| {
let src_info = SourceInfo::new(s);
(vec![None; src_info.count_lines()], src_info)
});
for stmt in stmts {
if !stmt.labels.is_empty() {
let Some(cur) = cursor.as_ref() else {
let spans = stmt.labels.iter()
.map(|label| label.span())
.collect::<Vec<_>>();
return Err(AsmErr::new(AsmErrKind::UndetAddrLabel, spans));
};
for label in &stmt.labels {
add_label(&mut label_map, label, cur.lc, false)?;
}
}
match &stmt.nucleus {
StmtKind::Directive(Directive::Orig(addr)) => match cursor {
Some(cur) => return Err(AsmErr::new(AsmErrKind::OverlappingOrig, [cur.block_orig, stmt.span.clone()])),
None => { cursor.replace(Cursor::new(addr.get(), stmt.span.clone())); },
},
StmtKind::Directive(Directive::End) => match cursor {
Some(_) => { cursor.take(); },
None => return Err(AsmErr::new(AsmErrKind::UnopenedOrig, stmt.span.clone())),
},
StmtKind::Directive(Directive::External(label)) => {
add_label(&mut label_map, label, 0, true)?;
}
StmtKind::Directive(Directive::Fill(PCOffset::Label(label))) => {
let label_text = label.name.to_uppercase();
if let Some(SymbolData { external: true, .. }) = label_map.get(&label_text) {
let Some(cur) = cursor.as_ref() else {
return Err(AsmErr::new(AsmErrKind::UndetAddrStmt, stmt.span.clone()));
};
rel_map.insert(cur.lc, label_text);
}
},
_ => {}
};
if let Some(cur) = &mut cursor {
if let Some((lines, s)) = &mut debug_sym {
if !matches!(stmt.nucleus, StmtKind::Directive(Directive::Orig(_) | Directive::End)) {
let line_index = s.get_line(stmt.span.start);
lines[line_index].replace(cur.lc);
}
}
match &stmt.nucleus {
StmtKind::Instr(_) => cur.shift(1),
StmtKind::Directive(d) => cur.shift(d.word_len()),
}.map_err(|e| AsmErr::new(e, stmt.span.clone()))?
}
}
if let Some(cur) = cursor {
return Err(AsmErr::new(AsmErrKind::UnclosedOrig, cur.block_orig));
}
let debug_symbols = debug_sym.map(|(lines, src_info)| DebugSymbols {
line_map: LineSymbolMap::new(lines)
.unwrap_or_else(|| {
unreachable!("line symbol map's invariants should have been upheld during symbol table pass")
}),
src_info,
});
Ok(SymbolTable { label_map, rel_map, debug_symbols })
}
pub fn lookup_label(&self, label: &str) -> Option<u16> {
self.label_map.get(&label.to_uppercase()).map(|sym_data| sym_data.addr)
}
pub fn rev_lookup_label(&self, addr: u16) -> Option<&str> {
let (label, _) = self.label_map.iter()
.find(|&(_, sym_data)| sym_data.addr == addr)?;
Some(label)
}
pub fn get_label_source(&self, label: &str) -> Option<Range<usize>> {
self.label_map.get(label)
.map(|data| data.span(label))
}
pub fn lookup_line(&self, line: usize) -> Option<u16> {
self.debug_symbols.as_ref()?.lookup_line(line)
}
pub fn rev_lookup_line(&self, addr: u16) -> Option<usize> {
self.debug_symbols.as_ref()?.rev_lookup_line(addr)
}
pub fn source_info(&self) -> Option<&SourceInfo> {
self.debug_symbols.as_ref().map(|ds| &ds.src_info)
}
pub fn label_iter(&self) -> impl Iterator<Item=(&str, u16, bool)> + '_ {
self.label_map.iter()
.map(|(label, sym_data)| (&**label, sym_data.addr, sym_data.external))
}
pub fn line_iter(&self) -> impl Iterator<Item=(usize, u16)> + '_ {
self.debug_symbols.iter()
.flat_map(|s| s.line_map.iter())
}
}
impl std::fmt::Debug for SymbolTable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
struct ClosureMap<R, F: Fn() -> R>(F);
impl<K, V, R, F> std::fmt::Debug for ClosureMap<R, F>
where K: std::fmt::Debug,
V: std::fmt::Debug,
R: IntoIterator<Item=(K, V)>,
F: Fn() -> R
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_map()
.entries((self.0)())
.finish()
}
}
f.debug_struct("SymbolTable")
.field("label_map", &ClosureMap(|| {
self.label_map.iter()
.map(|(k, data @ SymbolData { addr, .. })| {
(k, (Addr(*addr), data.span(k)))
})
}))
.field("debug_symbols", &self.debug_symbols)
.finish()
}
}
fn replace_pc_offset<const N: u32>(off: PCOffset<i16, N>, pc: u16, sym: &SymbolTable) -> Result<IOffset<N>, AsmErr> {
match off {
PCOffset::Offset(off) => Ok(off),
PCOffset::Label(label) => {
match sym.label_map.get(&label.name.to_uppercase()) {
Some(SymbolData { external: true, .. }) => Err(AsmErr::new(AsmErrKind::OffsetExternal, label.span())),
Some(SymbolData { addr, .. }) => {
IOffset::new(addr.wrapping_sub(pc) as i16)
.map_err(|e| AsmErr::new(AsmErrKind::OffsetNewErr(e), label.span()))
}
None => Err(AsmErr::new(AsmErrKind::CouldNotFindLabel, label.span())),
}
},
}
}
fn ranges_overlap<T: Ord>(a: Range<T>, b: Range<T>) -> bool {
let Range { start: a_start, end: a_end } = a;
let Range { start: b_start, end: b_end } = b;
a_start < b_end && b_start < a_end
}
impl AsmInstr {
pub fn into_sim_instr(self, pc: u16, sym: &SymbolTable) -> Result<SimInstr, AsmErr> {
match self {
AsmInstr::ADD(dr, sr1, sr2) => Ok(SimInstr::ADD(dr, sr1, sr2)),
AsmInstr::AND(dr, sr1, sr2) => Ok(SimInstr::AND(dr, sr1, sr2)),
AsmInstr::BR(cc, off) => Ok(SimInstr::BR(cc, replace_pc_offset(off, pc, sym)?)),
AsmInstr::JMP(br) => Ok(SimInstr::JMP(br)),
AsmInstr::JSR(off) => Ok(SimInstr::JSR(ImmOrReg::Imm(replace_pc_offset(off, pc, sym)?))),
AsmInstr::JSRR(br) => Ok(SimInstr::JSR(ImmOrReg::Reg(br))),
AsmInstr::LD(dr, off) => Ok(SimInstr::LD(dr, replace_pc_offset(off, pc, sym)?)),
AsmInstr::LDI(dr, off) => Ok(SimInstr::LDI(dr, replace_pc_offset(off, pc, sym)?)),
AsmInstr::LDR(dr, br, off) => Ok(SimInstr::LDR(dr, br, off)),
AsmInstr::LEA(dr, off) => Ok(SimInstr::LEA(dr, replace_pc_offset(off, pc, sym)?)),
AsmInstr::NOT(dr, sr) => Ok(SimInstr::NOT(dr, sr)),
AsmInstr::RET => Ok(SimInstr::JMP(Reg::R7)),
AsmInstr::RTI => Ok(SimInstr::RTI),
AsmInstr::ST(sr, off) => Ok(SimInstr::ST(sr, replace_pc_offset(off, pc, sym)?)),
AsmInstr::STI(sr, off) => Ok(SimInstr::STI(sr, replace_pc_offset(off, pc, sym)?)),
AsmInstr::STR(sr, br, off) => Ok(SimInstr::STR(sr, br, off)),
AsmInstr::TRAP(vect) => Ok(SimInstr::TRAP(vect)),
AsmInstr::NOP(off) => Ok(SimInstr::BR(0b000, replace_pc_offset(off, pc, sym)?)),
AsmInstr::GETC => Ok(SimInstr::TRAP(Offset::new_trunc(0x20))),
AsmInstr::OUT => Ok(SimInstr::TRAP(Offset::new_trunc(0x21))),
AsmInstr::PUTC => Ok(SimInstr::TRAP(Offset::new_trunc(0x21))),
AsmInstr::PUTS => Ok(SimInstr::TRAP(Offset::new_trunc(0x22))),
AsmInstr::IN => Ok(SimInstr::TRAP(Offset::new_trunc(0x23))),
AsmInstr::PUTSP => Ok(SimInstr::TRAP(Offset::new_trunc(0x24))),
AsmInstr::HALT => Ok(SimInstr::TRAP(Offset::new_trunc(0x25))),
}
}
}
impl Directive {
fn word_len(&self) -> u16 {
match self {
Directive::Orig(_) => 0,
Directive::Fill(_) => 1,
Directive::Blkw(n) => n.get(),
Directive::Stringz(s) => s.len() as u16 + 1, Directive::End => 0,
Directive::External(_) => 0,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ObjectFile {
block_map: BTreeMap<u16, Vec<Option<u16>>>,
sym: Option<SymbolTable>
}
impl ObjectFile {
pub fn empty() -> Self {
ObjectFile { block_map: BTreeMap::new(), sym: None }
}
fn new(ast: Vec<Stmt>, sym: SymbolTable, debug: bool) -> Result<Self, AsmErr> {
struct ObjBlock {
start: u16,
words: Vec<Option<u16>>,
orig_span: Range<usize>
}
impl ObjBlock {
fn range(&self) -> Range<u16> {
self.start .. (self.start + self.words.len() as u16)
}
fn push(&mut self, data: u16) {
self.words.push(Some(data));
}
fn shift(&mut self, n: u16) {
self.words.extend({
std::iter::repeat(None)
.take(usize::from(n))
});
}
fn write_directive(&mut self, directive: Directive, labels: &SymbolTable) -> Result<(), AsmErr> {
match directive {
Directive::Orig(_) => {},
Directive::Fill(pc_offset) => {
let off = match pc_offset {
PCOffset::Offset(o) => o.get(),
PCOffset::Label(l) => {
labels.lookup_label(&l.name)
.ok_or_else(|| AsmErr::new(AsmErrKind::CouldNotFindLabel, l.span()))?
},
};
self.push(off);
},
Directive::Blkw(n) => self.shift(n.get()),
Directive::Stringz(n) => {
self.extend(n.bytes().map(u16::from));
self.push(0);
},
Directive::End => {},
Directive::External(_) => {},
}
Ok(())
}
}
impl Extend<u16> for ObjBlock {
fn extend<T: IntoIterator<Item = u16>>(&mut self, iter: T) {
self.words.extend(iter.into_iter().map(Some));
}
}
let mut block_map: BTreeMap<u16, ObjBlock> = BTreeMap::new();
let mut current: Option<(u16, ObjBlock)> = None;
for stmt in ast {
match stmt.nucleus {
StmtKind::Directive(Directive::Orig(off)) => {
debug_assert!(current.is_none());
let addr = off.get();
current.replace((addr, ObjBlock { start: addr, orig_span: stmt.span, words: vec![] }));
},
StmtKind::Directive(Directive::End) => {
let Some((_, block)) = current.take() else {
return Err(AsmErr::new(AsmErrKind::UnopenedOrig, stmt.span));
};
if block.words.is_empty() { continue; }
let m_overlapping = [
block_map.range(..=block.start).next_back(), block_map.range(block.start..).next(), ]
.into_iter()
.flatten()
.find(|(_, b)| ranges_overlap(block.range(), b.range()));
if let Some((_, overlapping_block)) = m_overlapping {
let span0 = block.orig_span;
let span1 = overlapping_block.orig_span.clone();
let order = match span0.start <= span1.start {
true => [span0, span1],
false => [span1, span0],
};
return Err(AsmErr::new(AsmErrKind::OverlappingBlocks, order));
}
block_map.insert(block.start, block);
},
StmtKind::Directive(Directive::External(_)) => {},
StmtKind::Directive(directive) => {
let Some((lc, block)) = &mut current else {
return Err(AsmErr::new(AsmErrKind::UndetAddrStmt, stmt.span));
};
let wl = directive.word_len();
block.write_directive(directive, &sym)?;
*lc = lc.wrapping_add(wl);
},
StmtKind::Instr(instr) => {
let Some((lc, block)) = &mut current else {
return Err(AsmErr::new(AsmErrKind::UndetAddrStmt, stmt.span));
};
let sim = instr.into_sim_instr(lc.wrapping_add(1), &sym)?;
block.push(sim.encode());
*lc = lc.wrapping_add(1);
},
}
}
let block_map = block_map.into_iter()
.map(|(start, ObjBlock { words, .. })| (start, words))
.collect();
Ok(Self {
block_map,
sym: debug.then_some(sym),
})
}
fn get_mut(&mut self, addr: u16) -> Option<&mut Option<u16>> {
let (&start, block) = self.block_map.range_mut(..=addr).next_back()?;
block.get_mut(addr.wrapping_sub(start) as usize)
}
pub fn link(mut a_obj: Self, b_obj: Self) -> Result<Self, AsmErr> {
let Self { block_map: b_block_map, sym: b_sym } = b_obj;
for (addr, block) in b_block_map {
if a_obj.block_map.insert(addr, block).is_some() {
return Err(AsmErr::new(AsmErrKind::OverlappingBlocks, []));
}
}
let first = a_obj.block_map.iter();
let mut second = a_obj.block_map.iter();
second.next();
if std::iter::zip(first, second).any(|((&a_st, a_bl), (&b_st, b_bl))| {
let ar = a_st .. (a_st + a_bl.len() as u16);
let br = b_st .. (b_st + b_bl.len() as u16);
ranges_overlap(ar, br)
}) {
return Err(AsmErr::new(AsmErrKind::OverlappingBlocks, []));
}
let mut relocations = vec![];
a_obj.sym = match (a_obj.sym, b_sym) {
(Some(mut a_sym), Some(b_sym)) => {
let SymbolTable { label_map, rel_map, debug_symbols: b_debug_symbols } = b_sym;
a_sym.debug_symbols = match (a_sym.debug_symbols, b_debug_symbols) {
(Some(ads), Some(bds)) => Some(DebugSymbols::link(ads, bds)?),
(m_ads, b_ads) => m_ads.or(b_ads)
};
a_sym.rel_map.extend(rel_map);
for (label, b_sym_data) in label_map {
match a_sym.label_map.entry(label) {
Entry::Occupied(mut e) => {
let &a_sym_data = e.get();
match (a_sym_data.external, b_sym_data.external) {
(true, true) => {},
(true, false) | (false, true) => {
let linked_sym = match a_sym_data.external {
true => b_sym_data,
false => a_sym_data
};
e.insert(linked_sym);
let (rel_addrs, new_rel_map) = a_sym.rel_map.into_iter()
.partition(|(_, v)| v == e.key());
a_sym.rel_map = new_rel_map;
relocations.extend({
rel_addrs.into_keys().map(|addr| (addr, linked_sym.addr))
});
},
(false, false) => if a_sym_data.addr != b_sym_data.addr {
let a_span = a_sym_data.span(e.key());
let b_span = b_sym_data.span(e.key());
return Err(AsmErr::new(AsmErrKind::OverlappingLabels, [a_span, b_span]));
}
}
},
Entry::Vacant(e) => { e.insert(b_sym_data); },
};
}
Some(a_sym)
},
(ma, mb) => ma.or(mb)
};
for (addr, linked_addr) in relocations {
a_obj.get_mut(addr)
.unwrap_or_else(|| unreachable!("object file should have had address x{addr:04X} bound"))
.replace(linked_addr);
}
Ok(a_obj)
}
pub(crate) fn block_iter(&self) -> impl Iterator<Item=(u16, &[Option<u16>])> {
self.block_map.iter()
.map(|(&addr, block)| (addr, block.as_slice()))
}
pub(crate) fn get_external_symbol(&self) -> Option<&str> {
self.symbol_table()
.and_then(|s| s.label_map.iter().find(|(_, s)| s.external))
.map(|(k, _)| k.as_str())
}
pub fn addr_iter(&self) -> impl Iterator<Item=(u16, Option<u16>)> + '_ {
self.block_iter()
.flat_map(|(addr, block)| {
block.iter()
.enumerate()
.map(move |(i, &v)| (addr.wrapping_add(i as u16), v))
})
}
pub fn symbol_table(&self) -> Option<&SymbolTable> {
self.sym.as_ref()
}
}
#[repr(transparent)]
struct Addr(u16);
impl std::fmt::Debug for Addr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "x{:04X}", self.0)
}
}
#[cfg(test)]
mod tests {
use std::fmt::Write;
use crate::asm::encoding::TextFormat;
use crate::asm::AsmErrKind;
use crate::parse::parse_ast;
use super::encoding::{BinaryFormat, ObjFileFormat};
use super::{assemble_debug, AsmErr, ObjectFile};
fn assemble_src(src: &str) -> Result<ObjectFile, AsmErr> {
let ast = parse_ast(src).unwrap();
assemble_debug(ast, src)
}
fn assert_asm_fail<T: std::fmt::Debug>(r: Result<T, AsmErr>, kind: AsmErrKind) {
assert_eq!(r.unwrap_err().kind, kind);
}
#[test]
fn test_sym_basic() {
let src = "
.orig x3000
A ADD R0, R0, #0
AND R0, R0, #1
C ADD R0, R0, #0
D LD R0, #-1
HALT
HALT
HALT
HALT
E BR C
HALT
HALT
HALT
B JSR A
.end
";
let obj = assemble_src(src).unwrap();
let sym = obj.symbol_table().unwrap();
assert_eq!(sym.lookup_label("A"), Some(0x3000));
assert_eq!(sym.lookup_label("C"), Some(0x3002));
assert_eq!(sym.lookup_label("D"), Some(0x3003));
assert_eq!(sym.lookup_label("E"), Some(0x3008));
assert_eq!(sym.lookup_label("B"), Some(0x300C));
}
#[test]
fn test_region_overlap() {
let src = "
.orig x3000
HALT
HALT
HALT
HALT
.end
.orig x3002
HALT
.end
";
let obj = assemble_src(src);
assert_asm_fail(obj, AsmErrKind::OverlappingBlocks);
let src = "
.orig x3002
HALT
.end
.orig x3000
HALT
HALT
HALT
HALT
.end
";
let obj = assemble_src(src);
assert_asm_fail(obj, AsmErrKind::OverlappingBlocks);
let src = "
.orig x3000
HALT
HALT
HALT
HALT
.end
.orig x3002
.end
";
assemble_src(src).unwrap();
let src = "
.orig x3002
.end
.orig x3000
HALT
HALT
HALT
HALT
.end
";
assemble_src(src).unwrap();
}
#[test]
fn test_writing_into_io() {
let src = "
.orig xFE00
.end
";
assemble_src(src).unwrap();
let src = "
.orig xFE02
.end
";
assemble_src(src).unwrap();
let src = "
.orig xFE00
AND R0, R0, #0
.end
";
let obj = assemble_src(src);
assert_asm_fail(obj, AsmErrKind::BlockInIO);
}
#[test]
fn test_big_blocks() {
let src = "
.orig x3000
.blkw xFFFF
.end
";
let obj = assemble_src(src);
assert_asm_fail(obj, AsmErrKind::WrappingBlock);
let mut src = String::from(".orig x0000\n");
for i in 0x0000..=0xFFFF {
writeln!(src, ".fill x{i:04X}").unwrap();
}
writeln!(src, ".end").unwrap();
let obj = assemble_src(&src);
assert_asm_fail(obj, AsmErrKind::BlockInIO);
let src = "
.orig xFFFF
.blkw 1
.end
";
let obj = assemble_src(src);
assert_asm_fail(obj, AsmErrKind::BlockInIO);
let src = "
.orig x3000
.blkw xD000
.end
";
let obj = assemble_src(src);
assert_asm_fail(obj, AsmErrKind::BlockInIO);
let src = "
.orig x3000
.blkw xFFFF
.blkw xFFFF
.blkw xFFFF
.end
";
let obj = assemble_src(src);
assert_asm_fail(obj, AsmErrKind::WrappingBlock);
let src = "
.orig x3000
LABEL1 .blkw xD000
.fill x0000
.fill x0001
LABEL2 .fill x0002
.fill x0003
.end
";
assemble_src(src).unwrap_err();
}
fn assert_obj_equal(deser: &mut ObjectFile, expected: &ObjectFile, m: &str) {
let deser_src = deser.sym.as_mut()
.and_then(|s| s.debug_symbols.as_mut())
.map(|s| &mut s.src_info.src)
.expect("deserialized object file has no source");
let expected_src = expected.sym.as_ref()
.and_then(|s| s.debug_symbols.as_ref())
.map(|s| &s.src_info.src)
.expect("expected object file has no source");
let deser_lines = deser_src.trim().lines().map(str::trim);
let expected_lines = expected_src.trim().lines().map(str::trim);
assert!(deser_lines.eq(expected_lines), "lines should have matched");
let mut buf = expected_src.to_string();
std::mem::swap(deser_src, &mut buf);
assert_eq!(deser, expected, "{m}");
let deser_src = deser.sym.as_mut()
.and_then(|s| s.debug_symbols.as_mut())
.map(|s| &mut s.src_info.src)
.expect("deserialized object file has no source");
std::mem::swap(deser_src, &mut buf);
}
#[test]
fn test_ser_deser() {
let src = "
.orig x3000
AND R0, R0, #0
ADD R0, R0, #15
MINUS_R0 NOT R1, R0
ADD R1, R1, #1
HALT
.end
";
let obj = assemble_src(src).unwrap();
let ser = BinaryFormat::serialize(&obj);
let mut de = BinaryFormat::deserialize(&ser).expect("binary encoding should've been parseable");
assert_obj_equal(&mut de, &obj, "binary encoding could not be roundtripped");
let ser = TextFormat::serialize(&obj);
let mut de = TextFormat::deserialize(&ser).expect("text encoding should've been parseable");
assert_obj_equal(&mut de, &obj, "text encoding could not be roundtripped");
}
#[test]
fn test_ser_deser_crlf() {
let src = "\r
.orig x3000\r
AND R0, R0, #0\r
ADD R0, R0, #15\r
MINUS_R0 NOT R1, R0\r
ADD R1, R1, #1\r
HALT\r
.end\r
";
let obj = assemble_src(src).unwrap();
let ser = TextFormat::serialize(&obj);
let mut de = TextFormat::deserialize(&ser).expect("text encoding should've been parseable");
assert_obj_equal(&mut de, &obj, "text encoding could not be roundtripped");
}
#[test]
fn test_basic_link() {
let library = "
.orig x5000
ADDER ADD R2, R0, R1
RET
.end
";
let program = "
.external ADDER
.orig x4000
LD R0, A
LD R1, B
LD R3, ADDER_ADDR
JSRR R3
HALT
A .fill 10
B .fill 20
ADDER_ADDR .fill ADDER
.end
";
let lib_obj = assemble_src(library).unwrap();
let prog_obj = assemble_src(program).unwrap();
ObjectFile::link(lib_obj, prog_obj).unwrap();
}
#[test]
fn test_link_overlapping_labels() {
let library = "
.orig x5000
LOOP BR LOOP
RET
.end
";
let program = "
.orig x4000
LOOP BR LOOP
RET
.end
";
let lib_obj = assemble_src(library).unwrap();
let prog_obj = assemble_src(program).unwrap();
let link_obj = ObjectFile::link(lib_obj, prog_obj);
assert_asm_fail(link_obj, AsmErrKind::OverlappingLabels);
}
#[test]
fn test_link_overlapping_blocks() {
let library = "
.orig x3000
ADD R0, R0, #0
ADD R0, R0, #1
ADD R0, R0, #2
ADD R0, R0, #3
.end
";
let program = "
.orig x3001
ADD R0, R0, #0
ADD R0, R0, #1
ADD R0, R0, #2
ADD R0, R0, #3
.end
";
let lib_obj = assemble_src(library).unwrap();
let prog_obj = assemble_src(program).unwrap();
let link_obj = ObjectFile::link(lib_obj, prog_obj);
assert_asm_fail(link_obj, AsmErrKind::OverlappingBlocks);
let library = "
.orig x3000
ADD R0, R0, #0
ADD R0, R0, #1
ADD R0, R0, #2
ADD R0, R0, #3
.end
";
let program = "
.orig x3000
ADD R0, R0, #0
ADD R0, R0, #1
ADD R0, R0, #2
ADD R0, R0, #3
.end
";
let lib_obj = assemble_src(library).unwrap();
let prog_obj = assemble_src(program).unwrap();
let link_obj = ObjectFile::link(lib_obj, prog_obj);
assert_asm_fail(link_obj, AsmErrKind::OverlappingBlocks);
let library = "
.orig x3000
ADD R0, R0, #0
ADD R0, R0, #1
ADD R0, R0, #2
ADD R0, R0, #3
.end
";
let program = "
.orig x3004
ADD R0, R0, #4
ADD R0, R0, #5
ADD R0, R0, #6
ADD R0, R0, #7
.end
";
let lib_obj = assemble_src(library).unwrap();
let prog_obj = assemble_src(program).unwrap();
ObjectFile::link(lib_obj, prog_obj).unwrap();
}
#[test]
fn test_link_order_agnostic() {
let library = "
.orig x5000
;; very functional MULTIPLY subroutine
MULTIPLY RET
.end
";
let program1 = "
.external MULTIPLY
.orig x3000
LD R1, MADDR1
JSRR R1
HALT
MADDR1 .fill MULTIPLY
.end
";
let program2 = "
.external MULTIPLY
.orig x4000
LD R2, MADDR2
JSRR R2
HALT
MADDR2 .fill MULTIPLY
.end
";
let lib_obj = assemble_src(library).unwrap();
let prog1_obj = assemble_src(program1).unwrap();
let prog2_obj = assemble_src(program2).unwrap();
let link_obj = ObjectFile::link(lib_obj.clone(), prog1_obj.clone()).unwrap();
ObjectFile::link(link_obj.clone(), prog2_obj.clone())
.expect("(lib + prog1) + prog2 should've succeeded");
ObjectFile::link(prog2_obj.clone(), link_obj.clone())
.expect("prog2 + (lib + prog1) should've succeeded");
let link_obj = ObjectFile::link(prog1_obj.clone(), lib_obj.clone()).unwrap();
ObjectFile::link(link_obj.clone(), prog2_obj.clone())
.expect("(prog1 + lib) + prog2 should've succeeded");
ObjectFile::link(prog2_obj.clone(), link_obj.clone())
.expect("prog2 + (prog1 + lib) should've succeeded");
let link_obj = ObjectFile::link(prog1_obj.clone(), prog2_obj.clone()).unwrap();
ObjectFile::link(link_obj.clone(), lib_obj.clone())
.expect("(prog1 + prog2) + lib should've succeeded");
ObjectFile::link(lib_obj.clone(), link_obj.clone())
.expect("lib + (prog1 + prog2) should've succeeded");
}
#[test]
fn test_link_external_place() {
let program = "
.external X
.orig x3000
.fill X
.end
";
assemble_src(program).unwrap();
let program = "
.external X
.orig x3000
LD R0, X
.end
";
assert_asm_fail(assemble_src(program), AsmErrKind::OffsetExternal);
let program = "
.external X
.orig x3000
JSR X
.end
";
assert_asm_fail(assemble_src(program), AsmErrKind::OffsetExternal);
let program = "
.external X
.orig x3000
BR X
.end
";
assert_asm_fail(assemble_src(program), AsmErrKind::OffsetExternal);
}
}