moa/emulator/cpus/m68k/src/assembler.rs

621 lines
25 KiB
Rust

use std::collections::HashMap;
use moa_core::Error;
use moa_core::parser::{self, AssemblyLine, AssemblyOperand, AssemblyParser};
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,
OnlyAReg = 0x07FD,
}
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 parser = AssemblyParser::new(text);
parser.parse()
}
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 = parser::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 = parser::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" => {
parser::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 = parser::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" => {
self.convert_common_immediate_instruction(lineno, 0x0600, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"addai" => {
self.convert_common_immediate_instruction(lineno, 0x0600, args, operation_size?, Disallow::OnlyAReg)?;
},
"add" => {
self.convert_common_dreg_instruction(lineno, 0xD000, args, operation_size?, Disallow::None)?;
},
"adda" => {
self.convert_common_areg_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?;
parser::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(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" => {
self.convert_common_immediate_instruction(lineno, 0x0400, args, operation_size?, Disallow::NoARegImmediateOrPC)?;
},
"subai" => {
self.convert_common_immediate_instruction(lineno, 0x0400, args, operation_size?, Disallow::OnlyAReg)?;
},
"sub" => {
self.convert_common_dreg_instruction(lineno, 0x9000, args, operation_size?, Disallow::None)?;
},
"suba" => {
self.convert_common_areg_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> {
parser::expect_args(lineno, args, 2)?;
let immediate = parser::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> {
parser::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_areg_instruction(&mut self, lineno: usize, opcode: u16, args: &[AssemblyOperand], operation_size: Size, disallow: Disallow) -> Result<(), Error> {
let size_bit = expect_a_instruction_size(lineno, operation_size)?;
parser::expect_args(lineno, args, 2)?;
//let (_direction, reg, operand) = convert_reg_and_other(lineno, args, Disallow::NoDReg)?;
let reg = expect_address_register(lineno, &args[1])?;
let (effective_address, additional_words) = convert_target(lineno, &args[0], operation_size, disallow)?;
self.output.push(opcode | size_bit | (0b11 << 6) | (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> {
parser::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 {
parser::expect_args(lineno, args, 2)?;
let immediate = parser::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 = parser::expect_immediate(lineno, &args[0])?;
if !(1..=8).contains(&immediate) {
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 << 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![]));
} else if name == "sp" {
return Ok((0b100111, vec![]));
}
}
}
Err(Error::new(&format!("error at line {}: pre-decrement operator can only be used with a single address register", lineno)))
},
_ => 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)?))
}
},
[AssemblyOperand::Immediate(offset), AssemblyOperand::Register(name), AssemblyOperand::Register(index)] => {
let index_reg = expect_reg_num(lineno, index)?;
let da_select = if index.starts_with('a') { 1 << 15 } else { 0 };
if name == "pc" {
disallow.check(lineno, Disallow::NoPCRelativeIndex)?;
Ok((0b111011, vec![da_select | (index_reg << 12) | ((*offset as u16) & 0xff)]))
} else {
disallow.check(lineno, Disallow::NoIndirectIndexReg)?;
let reg = expect_address_reg_num(lineno, name)?;
Ok(((0b110 << 3) | reg, vec![da_select | (index_reg << 12) | ((*offset as u16) & 0xff)]))
}
},
// TODO add the MC68020 address options
_ => {
Err(Error::new(&format!("error at line {}: expected valid indirect addressing mode, but found {:?}", lineno, args)))
}
}
}
fn convert_reg_and_other(lineno: usize, args: &[AssemblyOperand], _disallow: Disallow) -> Result<(u16, u16, &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('a') {
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 expect_a_instruction_size(lineno: usize, size: Size) -> Result<u16, Error> {
match size {
Size::Word => Ok(0),
Size::Long => Ok(0b1 << 8),
_ => Err(Error::new(&format!("error at line {}: address instructions can only be word or long size", lineno))),
}
}
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,
}
}
#[allow(dead_code)]
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)))
}
}