From 0dcb5dd3a5dffc5e7cf6a1974e62f3f7fda6a6cf Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Sun, 27 Mar 2022 23:10:08 -0500 Subject: [PATCH] Assembler passes up to label placement --- vasm/src/ast.rs | 16 ++- vasm/src/lib.rs | 1 + vasm/src/vasm_assembler.rs | 275 +++++++++++++++++++++++++++++++++++++ vasm/src/vasm_evaluator.rs | 61 ++++++-- vasm/src/vasm_parser.rs | 26 ++++ vcore/src/opcodes.rs | 176 ++++++++++++++++++------ 6 files changed, 496 insertions(+), 59 deletions(-) create mode 100644 vasm/src/vasm_assembler.rs diff --git a/vasm/src/ast.rs b/vasm/src/ast.rs index 3ff4df7..d87f373 100644 --- a/vasm/src/ast.rs +++ b/vasm/src/ast.rs @@ -25,7 +25,7 @@ pub enum Node<'a> { Expr(Box>, Vec<(Operator, Node<'a>)>), } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Copy, Clone)] pub struct Label<'a>(pub &'a str); impl<'a> Display for Label<'a> { @@ -43,3 +43,17 @@ pub enum VASMLine<'a> { Equ(Label<'a>, Node<'a>), LabelDef(Label<'a>), } + +impl<'a> VASMLine<'a> { + pub fn label(&self) -> Option> { + match self { + VASMLine::Instruction(Some(lbl), _, _) + | VASMLine::Db(Some(lbl), _) + | VASMLine::StringDb(Some(lbl), _) + | VASMLine::Org(Some(lbl), _) + | VASMLine::Equ(lbl, _) + | VASMLine::LabelDef(lbl) => Some(*lbl), + _ => None, + } + } +} diff --git a/vasm/src/lib.rs b/vasm/src/lib.rs index 8ad2349..13b92db 100644 --- a/vasm/src/lib.rs +++ b/vasm/src/lib.rs @@ -4,5 +4,6 @@ extern crate pest_derive; pub mod ast; pub mod parse_error; +pub mod vasm_assembler; pub mod vasm_evaluator; pub mod vasm_parser; diff --git a/vasm/src/vasm_assembler.rs b/vasm/src/vasm_assembler.rs new file mode 100644 index 0000000..460dadf --- /dev/null +++ b/vasm/src/vasm_assembler.rs @@ -0,0 +1,275 @@ +use crate::ast::{Label, VASMLine}; +use crate::vasm_evaluator::{eval, EvalError, Scope}; +use std::collections::BTreeMap; +use std::fmt::{Display, Formatter}; + +#[derive(Debug, Clone, PartialEq)] +pub enum AssembleError<'a> { + EquResolveError(i32, &'a str, EvalError<'a>), + EquDuplicateError(i32, &'a str), + OrgResolveError(i32, EvalError<'a>), +} + +impl<'a> Display for AssembleError<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + AssembleError::EquResolveError(line, name, err) => { + write!(f, "Cannot resolve .equ {} on line {}: {}", name, line, err) + } + AssembleError::EquDuplicateError(line, name) => { + write!(f, "Duplicate .equ {} on line {}", name, line) + } + AssembleError::OrgResolveError(line, err) => { + write!(f, "Cannot resolve .org on line {}: {}", line, err) + } + } + } +} + +/// This will solve all the .equ directives and return a symbol table of them. +/// .equ directives must be able to be solved in order, that is, in terms of +/// only preceding .equ directives. Anything else is an error. +pub fn solve_equs<'a>(lines: &[VASMLine<'a>]) -> Result, AssembleError<'a>> { + let mut scope: Scope = Scope::new(); + let line_nums: BTreeMap = BTreeMap::new(); + for (line_idx, line) in lines.iter().enumerate() { + let line_num = (line_idx + 1) as i32; + if let VASMLine::Equ(Label(name), expr) = line { + let value = eval(expr, line_num as i32, &line_nums, &scope) + .map_err(|e| AssembleError::EquResolveError(line_num as i32, name, e))?; + + if let Some(_old_value) = scope.insert(name, value) { + return Err(AssembleError::EquDuplicateError(line_num as i32, name)); + } + } + } + Ok(scope) +} + +type LineLengths = BTreeMap; +type LineAddresses = BTreeMap; + +fn arg_length(val: i32) -> usize { + if val < 0 { + 3 + } else if val < 256 { + 1 + } else if val < 65536 { + 2 + } else { + 3 + } +} + +/// This figures out the instruction lengths. We'll do this naively; if we can't +/// immediately tell that an instruction needs only a 0/1/2 byte argument (because it's +/// a constant, or a .equ that we've solved, or something) then we'll assume it's a +/// full 24-bit argument. +/// +/// - Lines that don't represent output (.equ, .org, etc) have length 0 +/// - .db directives are either strings (set aside the length of the string), or +/// numbers (set aside three bytes. If it's shorter than that it still may be a variable, +/// which might grow to be larger). +/// - Opcodes with no argument are 1 byte long. +/// - Opcodes with an argument, if that argument is a constant or decidable solely with +/// what we know right now (.equs), are however long that argument is. If we don't +/// know right now (based on a label, say) then we'll set aside the full 3 bytes (so it's +/// 4 bytes long, with the instruction byte). +pub fn measure_instructions<'a>(lines: &[VASMLine<'a>], scope: &Scope) -> LineLengths { + let line_nums: BTreeMap = BTreeMap::new(); + let mut lengths = LineLengths::new(); + for (line_idx, line) in lines.iter().enumerate() { + let line_num = (line_idx + 1) as i32; + match line { + VASMLine::Instruction(_, _, None) => { + lengths.insert(line_num, 1); + } + VASMLine::Instruction(_, _, Some(node)) => { + let len = eval(node, line_num as i32, &line_nums, scope).map_or(3, arg_length); + lengths.insert(line_num, len + 1); + } + VASMLine::Db(_, _) => { + lengths.insert(line_num, 3); + } + VASMLine::StringDb(_, value) => { + lengths.insert(line_num, value.len()); + } + VASMLine::Org(_, _) | VASMLine::Equ(_, _) | VASMLine::LabelDef(_) => { + lengths.insert(line_num, 0); + } + } + } + lengths +} + +/// Time to start placing labels. The tricky part here is the .org directives, which can have +/// expressions as their arguments. We'll compromise a little bit and say that a .org directive +/// can only refer to labels that precede it, so, you can use .orgs to generate (say) a jump table +/// but still make it easy for me to figure out what refers to what. +/// +/// We'll go through the lines, adding each one's length (calculated in measure_instructions) to it. +/// If it has a label, we'll store that label's new value to the scope. +/// +/// But, we'll skip labels that come before .equs: that would make every .equ set to its address, +/// rather than the argument. +fn place_labels<'a>( + lines: &[VASMLine<'a>], + scope: Scope<'a>, + lengths: &LineLengths, +) -> Result<(LineAddresses, Scope<'a>), AssembleError<'a>> { + let mut scope = scope; + let mut address = 0; + let mut addresses = LineAddresses::new(); + for (line_idx, line) in lines.iter().enumerate() { + let line_num = (line_idx + 1) as i32; + + if let VASMLine::Org(_, expr) = line { + address = eval(expr, line_num, &addresses, &scope) + .map_err(|err| AssembleError::OrgResolveError(line_num, err))?; + addresses.insert(line_num, address); + } + + if let Some(Label(label)) = line.label() { + if !scope.contains_key(label) { + scope.insert(label, address as i32); + } + } + + match line { + VASMLine::Org(_, _) => {} + _ => { + addresses.insert(line_num, address); + address += *lengths.get(&line_num).unwrap_or(&0) as i32; + } + } + } + Ok((addresses, scope)) +} + +#[cfg(test)] +mod test { + use super::AssembleError::*; + use super::EvalError::*; + use super::*; + use crate::ast::VASMLine; + use crate::vasm_parser::parse_vasm_line; + + fn parse<'a>(lines: &'a [&str]) -> Vec> { + lines + .iter() + .map(|line| parse_vasm_line(*line).unwrap()) + .collect() + } + + fn place_labels_pass<'a>( + lines: &'a [&str], + ) -> Result<(LineAddresses, Scope<'a>), AssembleError<'a>> { + let lines = parse(lines); + let scope = solve_equs(&lines).unwrap(); + let lengths = measure_instructions(&lines, &scope); + place_labels(&lines, scope, &lengths) + } + + #[test] + fn test_equs() { + assert_eq!( + solve_equs(&parse(&["blah: .equ 5+3"])), + Ok([("blah", 8)].into()) + ); + assert_eq!( + solve_equs(&parse(&["blah: .equ 5", "foo: .equ 3"])), + Ok([("blah", 5), ("foo", 3)].into()) + ); + assert_eq!( + solve_equs(&parse(&["blah: .equ 5", "foo: .equ blah + 7"])), + Ok([("blah", 5), ("foo", 12)].into()) + ); + assert_eq!( + solve_equs(&parse(&["add", "blah: .equ 5"])), + Ok([("blah", 5)].into()) + ); + } + + #[test] + fn test_unsolvable_equs() { + assert_eq!( + solve_equs(&parse(&["blah: .equ 5", "foo: .equ banana"])), + Err(EquResolveError(2, "foo", MissingLabel("banana"))) + ); + assert_eq!( + solve_equs(&parse(&["blah: .equ foo+3", "foo: .equ 7"])), + Err(EquResolveError(1, "blah", MissingLabel("foo"))) + ); + assert_eq!( + solve_equs(&parse(&["blah: .equ 3", "blah: .equ 7"])), + Err(EquDuplicateError(2, "blah")) + ); + } + + #[test] + fn test_lengths() { + assert_eq!( + measure_instructions( + &parse(&["add", "add 1", "add 500", "add 70000", "add -7"]), + &[].into() + ), + [(1, 1), (2, 2), (3, 3), (4, 4), (5, 4)].into() + ); + assert_eq!( + measure_instructions(&parse(&[".db 7", ".db \"hello\\0\""]), &[].into()), + [(1, 3), (2, 6)].into() + ); + assert_eq!( + measure_instructions(&parse(&[".org 256", "blah:", "foo: .equ 7"]), &[].into()), + [(1, 0), (2, 0), (3, 0)].into() + ); + assert_eq!( + measure_instructions( + &parse(&["add 2 + foo", "add 3 + blah", "jmpr @foo"]), + &[("blah", 300)].into() + ), + [(1, 4), (2, 3), (3, 4)].into() + ); + } + + #[test] + fn test_place_labels() { + assert_eq!( + place_labels_pass(&["start: .org 256", "add", "dup"]), + Ok(( + [(1, 256), (2, 256), (3, 257)].into(), + [("start", 256)].into() + )) + ); + assert_eq!( + place_labels_pass(&["push 70000", "dup"]), + Ok(([(1, 0), (2, 4)].into(), [].into())) + ); + assert_eq!( + place_labels_pass(&["start: .equ 256", "blah: .org start + 4", "add"]), + Ok(( + [(1, 0), (2, 260), (3, 260)].into(), + [("blah", 260), ("start", 256)].into() + )) + ); + assert_eq!( + place_labels_pass(&["start: .org 256", "blah: .org start + 10", "add"]), + Ok(( + [(1, 256), (2, 266), (3, 266)].into(), + [("blah", 266), ("start", 256)].into() + )) + ); + } + + #[test] + fn test_unresolvable_orgs() { + assert_eq!( + place_labels_pass(&[".org 0xffffff - blah"]), + Err(OrgResolveError(1, EvalError::MissingLabel("blah"))) + ); + assert_eq!( + place_labels_pass(&["blah: .org blah"]), + Err(OrgResolveError(1, EvalError::MissingLabel("blah"))) + ); + } +} diff --git a/vasm/src/vasm_evaluator.rs b/vasm/src/vasm_evaluator.rs index 2261831..9b887e3 100644 --- a/vasm/src/vasm_evaluator.rs +++ b/vasm/src/vasm_evaluator.rs @@ -21,14 +21,38 @@ impl<'a> Display for EvalError<'a> { } } +pub type Scope<'a> = BTreeMap<&'a str, i32>; + +/// ## Evaluating expressions +/// Now that we have a parsed file, that file has a bunch of numeric symbols in it: labels, +/// .equ directives, that sort of thing. We need to resolve all of those to constant values +/// before we can generate code. So, first part of that is being able to evaluate expressions. +/// +/// This evaluates an expression in the context of a symbol table, and returns either what the +/// expression evaluates to (a number) or an error (if it references a symbol not in +/// the given symbol table, or needs to know an address that's not calculated yet). +/// +/// It's a depth-first recursive traversal of the expression AST: +/// +/// - If the node is a number, then it returns that number. +/// - If the node is a string, it tries to look it up in the symbol table or explodes. +/// - If the node is a relative label, it tries to look it up in the symbol table, and then +/// subtracts a given start_address. If start_address is nil (as when we're solving .equs) +/// then it errors. +/// - If the node is an expr or term, then it evaluates the children: the children are a +/// sequence of evaluate-able nodes separated by operators. So first evaluate the left-most +/// child, then use the operator to combine it with the following one, and so on. +/// - If the node is an absolute or relative line offset, it attempts to look up the start of +/// the given line in the table of line start addresses and gives that address relatively or +/// absolutely. pub fn eval<'a>( - node: Node<'a>, + node: &Node<'a>, line_num: i32, line_addresses: &BTreeMap, - scope: &BTreeMap<&'a str, i32>, + scope: &Scope, ) -> Result> { match node { - Node::Number(n) => Ok(n), + Node::Number(n) => Ok(*n), Node::Label(label) => scope .get(label) .map_or_else(|| Err(EvalError::MissingLabel(label)), |val| Ok(*val)), @@ -60,17 +84,16 @@ pub fn eval<'a>( } } Node::Expr(car, cdr) => { - let car = eval(*car, line_num, line_addresses, scope); + let car = eval(car, line_num, line_addresses, scope); if let Ok(mut acc) = car { for (op, node) in cdr { - if let Ok(rhs) = eval(node, line_num, line_addresses, scope) { - match op { - Operator::Add => acc += rhs, - Operator::Sub => acc -= rhs, - Operator::Mul => acc *= rhs, - Operator::Div => acc /= rhs, - Operator::Mod => acc %= rhs, - } + let rhs = eval(node, line_num, line_addresses, scope)?; + match op { + Operator::Add => acc += rhs, + Operator::Sub => acc -= rhs, + Operator::Mul => acc *= rhs, + Operator::Div => acc /= rhs, + Operator::Mod => acc %= rhs, } } Ok(acc) @@ -111,7 +134,7 @@ mod test { line: &'a str, ) -> Result> { if let Ok(VASMLine::Instruction(_, _, Some(arg))) = parse_vasm_line(line) { - eval(arg, 1, &line_addresses, &scope) + eval(&arg, 1, &line_addresses, &scope) } else { panic!("Failed to parse an instruction line with an argument") } @@ -134,6 +157,10 @@ mod test { test_scope_eval([("apple", 5)].into(), "add apple + 7"), Ok(12) ); + assert_eq!( + test_scope_eval([("apple", 5)].into(), "add 5 + apple"), + Ok(10) + ); assert_eq!( test_scope_eval([("apple", 5), ("banana", 3)].into(), "add apple * banana"), Ok(15) @@ -142,6 +169,14 @@ mod test { test_scope_eval([("apple", 5)].into(), "add banana"), Err(EvalError::MissingLabel("banana")) ); + assert_eq!( + test_scope_eval([].into(), "add apple + 2"), + Err(EvalError::MissingLabel("apple")) + ); + assert_eq!( + test_scope_eval([].into(), "add 2 + apple"), + Err(EvalError::MissingLabel("apple")) + ); } #[test] diff --git a/vasm/src/vasm_parser.rs b/vasm/src/vasm_parser.rs index 374757d..ec16c0a 100644 --- a/vasm/src/vasm_parser.rs +++ b/vasm/src/vasm_parser.rs @@ -307,6 +307,32 @@ mod test { ); } + #[test] + fn test_label_exprs() { + assert_eq!( + parse_vasm_line("add 2 + foo"), + Ok(VASMLine::Instruction( + None, + Add, + Some(Node::Expr( + Box::from(Node::Number(2)), + vec![(Operator::Add, Node::Label("foo"))] + )) + )) + ); + assert_eq!( + parse_vasm_line("add foo + 2"), + Ok(VASMLine::Instruction( + None, + Add, + Some(Node::Expr( + Box::from(Node::Label("foo")), + vec![(Operator::Add, Node::Number(2))] + )) + )) + ) + } + #[test] fn test_expr_labels() { assert_eq!( diff --git a/vcore/src/opcodes.rs b/vcore/src/opcodes.rs index c4fdc52..664d32f 100644 --- a/vcore/src/opcodes.rs +++ b/vcore/src/opcodes.rs @@ -65,49 +65,135 @@ pub struct InvalidMnemonic<'a>(pub &'a str); impl Display for Opcode { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - Opcode::Nop => { write!(f, "nop") } - Opcode::Add => { write!(f, "add") } - Opcode::Sub => { write!(f, "sub") } - Opcode::Mul => { write!(f, "mul") } - Opcode::Div => { write!(f, "div") } - Opcode::Mod => { write!(f, "mod") } - Opcode::Rand => { write!(f, "rand") } - Opcode::And => { write!(f, "and") } - Opcode::Or => { write!(f, "or") } - Opcode::Xor => { write!(f, "xor") } - Opcode::Not => { write!(f, "not") } - Opcode::Gt => { write!(f, "gt") } - Opcode::Lt => { write!(f, "lt") } - Opcode::Agt => { write!(f, "agt") } - Opcode::Alt => { write!(f, "alt") } - Opcode::Lshift => { write!(f, "lshift") } - Opcode::Rshift => { write!(f, "rshift") } - Opcode::Arshift => { write!(f, "arshift") } - Opcode::Pop => { write!(f, "pop") } - Opcode::Dup => { write!(f, "dup") } - Opcode::Swap => { write!(f, "swap") } - Opcode::Pick => { write!(f, "pick") } - Opcode::Rot => { write!(f, "rot") } - Opcode::Jmp => { write!(f, "jmp") } - Opcode::Jmpr => { write!(f, "jmpr") } - Opcode::Call => { write!(f, "call") } - Opcode::Ret => { write!(f, "ret") } - Opcode::Brz => { write!(f, "brz") } - Opcode::Brnz => { write!(f, "brnz") } - Opcode::Hlt => { write!(f, "hlt") } - Opcode::Load => { write!(f, "load") } - Opcode::Loadw => { write!(f, "loadw") } - Opcode::Store => { write!(f, "store") } - Opcode::Storew => { write!(f, "storew") } - Opcode::Inton => { write!(f, "inton") } - Opcode::Intoff => { write!(f, "intoff") } - Opcode::Setiv => { write!(f, "setiv") } - Opcode::Sdp => { write!(f, "sdp") } - Opcode::Setsdp => { write!(f, "setsdp") } - Opcode::Pushr => { write!(f, "pushr") } - Opcode::Popr => { write!(f, "popr") } - Opcode::Peekr => { write!(f, "peekr") } - Opcode::Debug => { write!(f, "debug") } + Opcode::Nop => { + write!(f, "nop") + } + Opcode::Add => { + write!(f, "add") + } + Opcode::Sub => { + write!(f, "sub") + } + Opcode::Mul => { + write!(f, "mul") + } + Opcode::Div => { + write!(f, "div") + } + Opcode::Mod => { + write!(f, "mod") + } + Opcode::Rand => { + write!(f, "rand") + } + Opcode::And => { + write!(f, "and") + } + Opcode::Or => { + write!(f, "or") + } + Opcode::Xor => { + write!(f, "xor") + } + Opcode::Not => { + write!(f, "not") + } + Opcode::Gt => { + write!(f, "gt") + } + Opcode::Lt => { + write!(f, "lt") + } + Opcode::Agt => { + write!(f, "agt") + } + Opcode::Alt => { + write!(f, "alt") + } + Opcode::Lshift => { + write!(f, "lshift") + } + Opcode::Rshift => { + write!(f, "rshift") + } + Opcode::Arshift => { + write!(f, "arshift") + } + Opcode::Pop => { + write!(f, "pop") + } + Opcode::Dup => { + write!(f, "dup") + } + Opcode::Swap => { + write!(f, "swap") + } + Opcode::Pick => { + write!(f, "pick") + } + Opcode::Rot => { + write!(f, "rot") + } + Opcode::Jmp => { + write!(f, "jmp") + } + Opcode::Jmpr => { + write!(f, "jmpr") + } + Opcode::Call => { + write!(f, "call") + } + Opcode::Ret => { + write!(f, "ret") + } + Opcode::Brz => { + write!(f, "brz") + } + Opcode::Brnz => { + write!(f, "brnz") + } + Opcode::Hlt => { + write!(f, "hlt") + } + Opcode::Load => { + write!(f, "load") + } + Opcode::Loadw => { + write!(f, "loadw") + } + Opcode::Store => { + write!(f, "store") + } + Opcode::Storew => { + write!(f, "storew") + } + Opcode::Inton => { + write!(f, "inton") + } + Opcode::Intoff => { + write!(f, "intoff") + } + Opcode::Setiv => { + write!(f, "setiv") + } + Opcode::Sdp => { + write!(f, "sdp") + } + Opcode::Setsdp => { + write!(f, "setsdp") + } + Opcode::Pushr => { + write!(f, "pushr") + } + Opcode::Popr => { + write!(f, "popr") + } + Opcode::Peekr => { + write!(f, "peekr") + } + Opcode::Debug => { + write!(f, "debug") + } } } } @@ -172,7 +258,7 @@ impl<'a> TryFrom<&'a str> for Opcode { fn try_from(value: &'a str) -> Result { use Opcode::*; Ok(match value { - "nop" => Nop, + "nop" | "push" => Nop, "add" => Add, "sub" => Sub, "mul" => Mul, @@ -215,7 +301,7 @@ impl<'a> TryFrom<&'a str> for Opcode { "popr" => Popr, "peekr" => Peekr, "debug" => Debug, - _ => return Err(InvalidMnemonic(value)) + _ => return Err(InvalidMnemonic(value)), }) } }