moa/src/cpus/m68k/assembler.rs
transistor 586a16509f Added an assembler for the m68k
It can encode some basic instructions but not the more complicated
addressing modes or special instructions.  It can keep track of
labels and patch the displacement addresses after assembling the
output data, but it doesn't check for duplicates or that a label
has been declared before it appears.  It also doesn't take into
account the origin yet.  To run it, there is an m68kas command
that will take a file and print the data:
```
cargo run -p moa --bin m68kas <filename>
```

It's not yet tied into the rest of the system, but could be used in
the debugger to allow insertion of new code into a running system.
2022-05-12 21:53:34 -07:00

587 lines
23 KiB
Rust

use std::collections::HashMap;
use crate::error::Error;
use crate::parser::{AssemblyLine, AssemblyOperand, AssemblyParser};
use crate::parser::{expect_args, expect_label, expect_immediate};
use super::state::M68kType;
use super::instructions::Size;
#[repr(usize)]
#[derive(Copy, Clone)]
pub enum Disallow {
None = 0x0000,
NoDReg = 0x0001,
NoAReg = 0x0002,
NoIndirect = 0x0004,
NoIndirectPre = 0x0008,
NoIndirectPost = 0x0010,
NoIndirectOffset = 0x0020,
NoIndirectIndexReg = 0x0040,
NoIndirectImmediate = 0x0080,
NoImmediate = 0x0100,
NoPCRelative = 0x0200,
NoPCRelativeIndex = 0x0400,
NoRegs = 0x0003,
NoRegsOrImmediate = 0x0103,
NoRegsImmediateOrPC = 0x0703,
NoARegImmediateOrPC = 0x0702,
NoRegsPrePostOrImmediate = 0x011B,
NoImmediateOrPC = 0x0700,
}
impl Disallow {
pub fn check(&self, lineno: usize, disallow: Disallow) -> Result<(), Error> {
if (*self as usize) & (disallow as usize) == 0 {
Ok(())
} else {
Err(Error::new(&format!("error at line {}: invalid addressing mode for the instruction", lineno)))
}
}
}
pub enum RelocationType {
Displacement,
Word(String),
Long(String),
}
pub struct Relocation {
pub rtype: RelocationType,
pub label: String,
pub index: usize,
pub from_origin: usize,
}
impl Relocation {
pub fn new(rtype: RelocationType, label: String, index: usize, from_origin: usize) -> Self {
Self {
rtype,
label,
index,
from_origin,
}
}
}
pub struct M68kAssembler {
pub cputype: M68kType,
pub labels: HashMap<String, usize>,
pub output: Vec<u16>,
pub relocations: Vec<Relocation>,
pub current_origin: usize,
}
impl M68kAssembler {
pub fn new(cputype: M68kType) -> Self {
Self {
cputype,
labels: HashMap::new(),
output: vec![],
relocations: vec![],
current_origin: 0,
}
}
pub fn assemble(&mut self, text: &str) -> Result<Vec<u8>, Error> {
self.assemble_in_place(text)?;
Ok(self.output.iter().fold(vec![], |mut acc, item| {
acc.push((*item >> 8) as u8);
acc.push(*item as u8);
acc
}))
}
pub fn assemble_words(&mut self, text: &str) -> Result<Vec<u16>, Error> {
self.assemble_in_place(text)?;
Ok(self.output.clone())
}
pub fn assemble_in_place(&mut self, text: &str) -> Result<(), Error> {
let lines = self.parse(text)?;
for (lineno, line) in lines {
self.convert(lineno, &line)?;
}
self.apply_relocations()?;
Ok(())
}
fn parse(&mut self, text: &str) -> Result<Vec<(usize, AssemblyLine)>, Error> {
let mut output = vec![];
let iter = text.split_terminator("\n");
for (lineno, line_text) in iter.enumerate() {
let mut parser = AssemblyParser::new(lineno, line_text);
let parsed_line = parser.parse_line()?;
if let Some(line) = parsed_line {
output.push((lineno, line));
}
}
Ok(output)
}
fn apply_relocations(&mut self) -> Result<(), Error> {
for reloc in self.relocations.iter() {
match reloc.rtype {
RelocationType::Displacement => {
// TODO this doesn't yet take into accound the origin
let location = *self.labels.get(&reloc.label).ok_or_else(|| Error::new(&format!("error during relocation, label undefined {:?}", reloc.label)))?;
self.output[reloc.index] |= ((self.output[reloc.index] as i8 * 2 + 2) - (location as i8 * 2)) as u16 & 0x00ff;
},
_ => panic!("relocation type unimplemented"),
}
}
Ok(())
}
fn convert(&mut self, lineno: usize, line: &AssemblyLine) -> Result<(), Error> {
match line {
AssemblyLine::Directive(name, list) => {
println!("skipping directive {} ({:?})", name, list);
},
AssemblyLine::Label(label) => {
println!("label {}", label);
self.labels.insert(label.clone(), self.output.len() - 1);
},
AssemblyLine::Instruction(name, list) => {
self.convert_instruction(lineno, &name, &list)?;
},
}
Ok(())
}
fn convert_instruction(&mut self, lineno: usize, mneumonic: &str, args: &[AssemblyOperand]) -> Result<(), Error> {
match mneumonic {
"bra" => {
let label = expect_label(lineno, args)?;
self.output.push(0x6000);
self.relocations.push(Relocation::new(RelocationType::Displacement, label, self.output.len() - 1, self.current_origin));
},
"bsr" => {
let label = expect_label(lineno, args)?;
self.output.push(0x6100);
self.relocations.push(Relocation::new(RelocationType::Displacement, label, self.output.len() - 1, self.current_origin));
},
"illegal" => {
self.output.push(0x4AFC);
},
"lea" => {
expect_args(lineno, args, 2)?;
let reg = expect_address_register(lineno, &args[0])?;
let (effective_address, additional_words) = convert_target(lineno, &args[1], Size::Long, Disallow::NoRegsPrePostOrImmediate)?;
self.output.push(0x41C0 | (reg << 9) | effective_address);
self.output.extend(additional_words);
},
"nop" => {
self.output.push(0x4E71);
},
"rts" => {
self.output.push(0x4E75);
},
"rte" => {
self.output.push(0x4E73);
},
"rtr" => {
self.output.push(0x4E77);
},
"stop" => {
let immediate = expect_immediate(lineno, &args[0])?;
self.output.push(0x4E72);
self.output.extend(convert_immediate(lineno, immediate, Size::Word)?);
},
"trapv" => {
self.output.push(0x4E76);
},
_ => {
self.convert_sized_instruction(lineno, mneumonic, args)?;
},
}
Ok(())
}
fn convert_sized_instruction(&mut self, lineno: usize, mneumonic: &str, args: &[AssemblyOperand]) -> Result<(), Error> {
let operation_size = get_size_from_mneumonic(mneumonic).ok_or_else(|| Error::new(&format!("error at line {}: expected a size specifier (b/w/l)", lineno)));
match &mneumonic[..mneumonic.len() - 1] {
"addi" | "addai" => {
self.convert_common_immediate_instruction(lineno, 0x0600, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"add" | "adda" => {
self.convert_common_dreg_instruction(lineno, 0xD000, args, operation_size?, Disallow::None)?;
},
"andi" => {
if !self.check_convert_flags_instruction(lineno, 0x23C, 0x27C, args)? {
self.convert_common_immediate_instruction(lineno, 0x0200, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
}
},
"and" => {
self.convert_common_dreg_instruction(lineno, 0xC000, args, operation_size?, Disallow::None)?;
},
"asr" | "asl" => {
self.convert_common_shift_instruction(lineno, mneumonic, 0xE000, args, operation_size?)?;
},
"clr" => {
self.convert_common_single_operand_instruction(lineno, 0x4200, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"cmpi" => {
self.convert_common_immediate_instruction(lineno, 0x0C00, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"cmp" => {
self.convert_common_dreg_instruction(lineno, 0xB000, args, operation_size?, Disallow::None)?;
},
"eori" => {
if !self.check_convert_flags_instruction(lineno, 0x0A3C, 0x0A7C, args)? {
self.convert_common_immediate_instruction(lineno, 0x0A00, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
}
},
"eor" => {
self.convert_common_dreg_instruction(lineno, 0xB000, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"lsr" | "lsl" => {
self.convert_common_shift_instruction(lineno, mneumonic, 0xE008, args, operation_size?)?;
},
"move" | "movea" => {
let operation_size = operation_size?;
expect_args(lineno, args, 2)?;
let (effective_address_left, additional_words_left) = convert_target(lineno, &args[0], operation_size, Disallow::None)?;
let (effective_address_right, additional_words_right) = convert_target(lineno, &args[1], operation_size, Disallow::None)?;
let effective_address_left = (effective_address_left >> 3) | (effective_address_left << 3);
self.output.push(0x0000 | encode_size_for_move(operation_size) | effective_address_left | effective_address_right);
self.output.extend(additional_words_left);
self.output.extend(additional_words_right);
},
"neg" => {
self.convert_common_single_operand_instruction(lineno, 0x4400, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"negx" => {
self.convert_common_single_operand_instruction(lineno, 0x4000, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"not" => {
self.convert_common_single_operand_instruction(lineno, 0x4600, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"ori" => {
if !self.check_convert_flags_instruction(lineno, 0x003C, 0x007C, args)? {
self.convert_common_immediate_instruction(lineno, 0x0000, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
}
},
"or" => {
self.convert_common_dreg_instruction(lineno, 0x8000, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"ror" | "rol" => {
self.convert_common_shift_instruction(lineno, mneumonic, 0xE018, args, operation_size?)?;
},
"roxr" | "roxl" => {
self.convert_common_shift_instruction(lineno, mneumonic, 0xE010, args, operation_size?)?;
},
"subi" | "subai" => {
self.convert_common_immediate_instruction(lineno, 0x0400, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"sub" | "suba" => {
self.convert_common_dreg_instruction(lineno, 0x9000, args, operation_size?, Disallow::None)?;
},
// TODO complete remaining instructions
_ => return Err(Error::new(&format!("unrecognized instruction at line {}: {:?}", lineno, mneumonic))),
}
Ok(())
}
fn convert_common_immediate_instruction(&mut self, lineno: usize, opcode: u16, args: &[AssemblyOperand], operation_size: Size, disallow: Disallow) -> Result<(), Error> {
expect_args(lineno, args, 2)?;
let immediate = expect_immediate(lineno, &args[0])?;
let (effective_address, additional_words) = convert_target(lineno, &args[1], operation_size, disallow)?;
self.output.push(opcode | encode_size(operation_size) | effective_address);
self.output.extend(convert_immediate(lineno, immediate, operation_size)?);
self.output.extend(additional_words);
Ok(())
}
fn convert_common_dreg_instruction(&mut self, lineno: usize, opcode: u16, args: &[AssemblyOperand], operation_size: Size, disallow: Disallow) -> Result<(), Error> {
expect_args(lineno, args, 2)?;
let (direction, reg, operand) = convert_reg_and_other(lineno, args, Disallow::NoAReg)?;
let (effective_address, additional_words) = convert_target(lineno, operand, operation_size, disallow)?;
self.output.push(opcode | encode_size(operation_size) | direction | (reg << 9) | effective_address);
self.output.extend(additional_words);
Ok(())
}
fn convert_common_single_operand_instruction(&mut self, lineno: usize, opcode: u16, args: &[AssemblyOperand], operation_size: Size, disallow: Disallow) -> Result<(), Error> {
expect_args(lineno, args, 1)?;
let (effective_address, additional_words) = convert_target(lineno, &args[0], operation_size, disallow)?;
self.output.push(opcode | encode_size(operation_size) | effective_address);
self.output.extend(additional_words);
Ok(())
}
fn check_convert_flags_instruction(&mut self, lineno: usize, opcode_ccr: u16, opcode_sr: u16, args: &[AssemblyOperand]) -> Result<bool, Error> {
if let AssemblyOperand::Register(name) = &args[1] {
let opcode = match name.as_str() {
"ccr" => Some(opcode_ccr),
"sr" => Some(opcode_sr),
_ => None,
};
if let Some(opcode) = opcode {
expect_args(lineno, args, 2)?;
let immediate = expect_immediate(lineno, &args[0])?;
self.output.push(opcode);
self.output.extend(convert_immediate(lineno, immediate, Size::Word)?);
return Ok(true);
}
}
Ok(false)
}
fn convert_common_shift_instruction(&mut self, lineno: usize, mneumonic: &str, opcode: u16, args: &[AssemblyOperand], operation_size: Size) -> Result<(), Error> {
let dirstr = &mneumonic[mneumonic.len() - 2..mneumonic.len() - 1];
let direction = if dirstr == "r" {
0
} else if dirstr == "l" {
1 << 8
} else {
return Err(Error::new(&format!("error at line {}: expected direction of (l)eft or (r)ight, but found {:?}", lineno, dirstr)));
};
match &args {
&[AssemblyOperand::Immediate(_), AssemblyOperand::Register(_)] => {
let mut immediate = expect_immediate(lineno, &args[0])?;
if immediate < 1 || immediate > 8 {
return Err(Error::new(&format!("error at line {}: immediate value must be between 1 and 8, found {:?}", lineno, args)));
} else if immediate == 8 {
immediate = 0;
}
let reg = expect_data_register(lineno, &args[1])?;
self.output.push(opcode | ((immediate as u16) << 9) | direction | encode_size(operation_size) | (0b0 << 5) | reg);
},
&[AssemblyOperand::Register(_), AssemblyOperand::Register(_)] => {
let bit_reg = expect_data_register(lineno, &args[0])?;
let reg = expect_data_register(lineno, &args[1])?;
self.output.push(opcode | ((bit_reg as u16) << 9) | direction | encode_size(operation_size) | (0b1 << 5) | reg);
},
//&[_] => {
// let (effective_address, additional_words) = convert_target(lineno, &args[0], Size::Word, Disallow::NoRegsImmediateOrPC)?;
// self.output.push(opcode | effective_address);
// self.output.extend(additional_words);
//},
_ => return Err(Error::new(&format!("error at line {}: unexpected addressing mode, found {:?}", lineno, args))),
}
Ok(())
}
}
fn convert_target(lineno: usize, operand: &AssemblyOperand, size: Size, disallow: Disallow) -> Result<(u16, Vec<u16>), Error> {
match operand {
AssemblyOperand::Register(name) => {
convert_register(lineno, name, disallow)
},
AssemblyOperand::Immediate(value) => {
disallow.check(lineno, Disallow::NoImmediate)?;
Ok((0b111100, convert_immediate(lineno, *value, size)?))
},
AssemblyOperand::Indirect(args) => {
convert_indirect(lineno, args, disallow)
},
AssemblyOperand::IndirectPost(args, operator) => {
disallow.check(lineno, Disallow::NoIndirectPost)?;
if args.len() == 1 && operator == "+" {
if let AssemblyOperand::Register(name) = &args[0] {
if name.starts_with("a") {
let reg = expect_reg_num(lineno, name)?;
return Ok(((0b011 << 3) | reg, vec![]));
}
}
}
Err(Error::new(&format!("error at line {}: post-increment operator can only be used with a single address register", lineno)))
},
AssemblyOperand::IndirectPre(operator, args) => {
disallow.check(lineno, Disallow::NoIndirectPre)?;
if args.len() == 1 && operator == "-" {
if let AssemblyOperand::Register(name) = &args[0] {
if name.starts_with("a") {
let reg = expect_reg_num(lineno, name)?;
return Ok(((0b100 << 3) | reg, vec![]));
}
}
}
Err(Error::new(&format!("error at line {}: pre-decrement operator can only be used with a single address register", lineno)))
},
// TODO complete remaining types
_ => Err(Error::new(&format!("not implemented: {:?}", operand))),
}
}
fn convert_register(lineno: usize, name: &str, disallow: Disallow) -> Result<(u16, Vec<u16>), Error> {
match name {
name if name.starts_with("d") => {
disallow.check(lineno, Disallow::NoDReg)?;
let reg = expect_reg_num(lineno, name)?;
Ok(((0b000 << 3) | reg, vec![]))
},
name if name.starts_with("a") => {
disallow.check(lineno, Disallow::NoAReg)?;
let reg = expect_reg_num(lineno, name)?;
Ok(((0b001 << 3) | reg, vec![]))
},
"sp" => {
disallow.check(lineno, Disallow::NoAReg)?;
Ok(((0b001 << 3) | 7, vec![]))
},
_ => Err(Error::new(&format!("error at line {}: invalid register {:?}", lineno, name))),
}
}
fn convert_indirect(lineno: usize, args: &[AssemblyOperand], disallow: Disallow) -> Result<(u16, Vec<u16>), Error> {
match &args {
&[AssemblyOperand::Register(name)] => {
disallow.check(lineno, Disallow::NoIndirect)?;
let reg = expect_address_reg_num(lineno, name)?;
Ok(((0b010 << 3) | reg, vec![]))
},
&[AssemblyOperand::Immediate(address)] => {
disallow.check(lineno, Disallow::NoIndirectImmediate)?;
if *address < u16::MAX as usize {
Ok((0b111000, convert_immediate(lineno, *address, Size::Word)?))
} else {
Ok((0b111001, convert_immediate(lineno, *address, Size::Long)?))
}
},
&[AssemblyOperand::Immediate(offset), AssemblyOperand::Register(name)] => {
if name == "pc" {
disallow.check(lineno, Disallow::NoPCRelative)?;
Ok((0b111010, convert_immediate(lineno, *offset, Size::Word)?))
} else {
disallow.check(lineno, Disallow::NoIndirectOffset)?;
let reg = expect_address_reg_num(lineno, name)?;
Ok(((0b101 << 3) | reg, convert_immediate(lineno, *offset, Size::Word)?))
}
},
// TODO add the index register mode
_ => {
Err(Error::new(&format!("error at line {}: expected valid indirect addressing mode, but found {:?}", lineno, args)))
}
}
}
fn convert_reg_and_other<'a>(lineno: usize, args: &'a [AssemblyOperand], disallow: Disallow) -> Result<(u16, u16, &'a AssemblyOperand), Error> {
match &args {
&[AssemblyOperand::Register(reg), effective_address] => {
Ok(((0b1 << 8), expect_reg_num(lineno, &reg)?, effective_address))
},
&[effective_address, AssemblyOperand::Register(reg)] => {
Ok(((0b0 << 8), expect_reg_num(lineno, &reg)?, effective_address))
},
_ => {
Err(Error::new(&format!("error at line {}: expected register and effective address, but found {:?}", lineno, args)))
}
}
}
fn convert_immediate(lineno: usize, value: usize, size: Size) -> Result<Vec<u16>, Error> {
match size {
Size::Byte => {
if value < u8::MAX as usize {
Ok(vec![value as u16])
} else {
Err(Error::new(&format!("error at line {}: immediate number is out of range; must be less than {}, but number is {:?}", lineno, u8::MAX, value)))
}
},
Size::Word => {
if value < u16::MAX as usize {
Ok(vec![value as u16])
} else {
Err(Error::new(&format!("error at line {}: immediate number is out of range; must be less than {}, but number is {:?}", lineno, u16::MAX, value)))
}
},
Size::Long => Ok(vec![(value >> 16) as u16, value as u16]),
}
}
fn expect_data_register(lineno: usize, operand: &AssemblyOperand) -> Result<u16, Error> {
if let AssemblyOperand::Register(name) = operand {
if name.starts_with("d") {
return expect_reg_num(lineno, name);
}
}
Err(Error::new(&format!("error at line {}: expected a data register, but found {:?}", lineno, operand)))
}
fn expect_address_register(lineno: usize, operand: &AssemblyOperand) -> Result<u16, Error> {
if let AssemblyOperand::Register(name) = operand {
if name.starts_with("d") {
return expect_reg_num(lineno, name);
}
}
Err(Error::new(&format!("error at line {}: expected an address register, but found {:?}", lineno, operand)))
}
fn expect_address_reg_num(lineno: usize, name: &str) -> Result<u16, Error> {
if name.starts_with("a") {
return expect_reg_num(lineno, name);
}
Err(Error::new(&format!("error at line {}: expected an address register, but found {:?}", lineno, name)))
}
fn expect_reg_num(lineno: usize, name: &str) -> Result<u16, Error> {
if let Ok(number) = str::parse::<u16>(&name[1..]) {
if number <= 7 {
return Ok(number);
}
}
Err(Error::new(&format!("error at line {}: no such register {:?}", lineno, name)))
}
fn get_size_from_mneumonic(s: &str) -> Option<Size> {
let size_ch = s.chars().last()?;
match size_ch {
'b' => Some(Size::Byte),
'w' => Some(Size::Word),
'l' => Some(Size::Long),
_ => None
}
}
fn encode_size(size: Size) -> u16 {
match size {
Size::Byte => 0b00 << 6,
Size::Word => 0b01 << 6,
Size::Long => 0b10 << 6,
}
}
fn encode_size_for_move(size: Size) -> u16 {
match size {
Size::Byte => 0b01 << 12,
Size::Word => 0b11 << 12,
Size::Long => 0b10 << 12,
}
}
fn encode_size_bit(size: Size) -> Result<u16, Error> {
match size {
Size::Word => Ok(0b01 << 6),
Size::Long => Ok(0b10 << 6),
_ => Err(Error::new(&format!("invalid size for this operation: {:?}", size)))
}
}