From 4ef4efcc11afeb22af331c1f90a6714fc9654b3e Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Fri, 1 Apr 2022 22:00:18 -0500 Subject: [PATCH] Finally generating code --- vasm/src/ast.rs | 6 ++ vasm/src/vasm_assembler.rs | 153 ++++++++++++++++++++++++++++++++++++- vcore/src/opcodes.rs | 50 ++++++++++++ 3 files changed, 208 insertions(+), 1 deletion(-) diff --git a/vasm/src/ast.rs b/vasm/src/ast.rs index d87f373..6efd999 100644 --- a/vasm/src/ast.rs +++ b/vasm/src/ast.rs @@ -56,4 +56,10 @@ impl<'a> VASMLine<'a> { _ => None, } } + pub fn zero_length(&self) -> bool { + matches!( + self, + VASMLine::Org(_, _) | VASMLine::Equ(_, _) | VASMLine::LabelDef(_) + ) + } } diff --git a/vasm/src/vasm_assembler.rs b/vasm/src/vasm_assembler.rs index e4cebfd..74f6978 100644 --- a/vasm/src/vasm_assembler.rs +++ b/vasm/src/vasm_assembler.rs @@ -9,6 +9,7 @@ pub enum AssembleError<'a> { EquDuplicateError(i32, &'a str), OrgResolveError(i32, EvalError<'a>), ArgError(i32, EvalError<'a>), + NoCode, } impl<'a> Display for AssembleError<'a> { @@ -26,6 +27,9 @@ impl<'a> Display for AssembleError<'a> { AssembleError::ArgError(line, err) => { write!(f, "Cannot calculate argument on line {}: {}", line, err) } + AssembleError::NoCode => { + write!(f, "No output would be generated by this code") + } } } } @@ -178,6 +182,106 @@ fn calculate_args<'a>( Ok(args) } +fn poke_word(code: &mut Vec, at: usize, word: i32) { + let [low, mid, high, _] = word.to_le_bytes(); + code[at] = low; + code[at + 1] = mid; + code[at + 2] = high; +} + +/// Find the lower and upper bounds where this program will place memory +fn code_bounds<'a>( + lines: &[VASMLine<'a>], + line_addresses: &LineAddresses, + line_lengths: &LineLengths, +) -> Result<(usize, usize), AssembleError<'a>> { + let mut actual_lines = lines + .iter() + .enumerate() + .filter(|(_, line)| !line.zero_length()); + let (first_idx, _) = actual_lines.next().ok_or(AssembleError::NoCode)?; + let start = line_addresses[&(first_idx as i32 + 1)] as usize; + + let actual_lines = lines + .iter() + .enumerate() + .filter(|(_, line)| !line.zero_length()); + let (last_idx, _) = actual_lines.last().unwrap(); + let end = line_addresses[&(last_idx as i32 + 1)] as usize; + let end_length = line_lengths[&(last_idx as i32 + 1)]; + + Ok((start, end + end_length - 1)) +} + +/// At this point all lines have addresses and lengths, and all arguments are reduced to +/// numeric constants. It's time to generate code. +/// +/// - Make an array of zeroes, length (end - start) +/// - Go through the list of instructions, generating code for them: +/// - .db instructions turn into byte values starting at `address - start` +/// - Opcodes turn into instruction bytes at `address - start` followed (maybe) by +/// arguments. +/// - Any other directive is skipped (even .orgs, we already have the addresses calculated) +/// +/// The instruction bytes are formed of six bits defining the instruction followed by two +/// bits denoting how many bytes of argument follow it. +/// +/// Vulcan is a little-endian architecture: multi-byte arguments / .dbs will store the +/// least-significant byte at the lowest address, then the more significant bytes following. +fn generate_code<'a>( + lines: &[VASMLine<'a>], + scope: Scope, + line_addresses: LineAddresses, + line_lengths: LineLengths, +) -> Result, AssembleError<'a>> { + let line_args = calculate_args(lines, scope, &line_addresses)?; + let (start, end) = code_bounds(lines, &line_addresses, &line_lengths)?; + let mut code = vec![0u8; end - start + 1]; + let mut current_addr = start; + + for (line_idx, line) in lines.iter().enumerate() { + let line_num = (line_idx + 1) as i32; + match line { + VASMLine::Instruction(_, opcode, None) => { + code[current_addr] = u8::from(*opcode) << 2; + current_addr += 1; + } + VASMLine::Instruction(_, opcode, Some(_)) => { + let arg = line_args[&line_num]; + let len = arg_length(arg); + let instr = (u8::from(*opcode) << 2) + len as u8; + code[current_addr - start] = instr; + let [low, mid, high, _] = arg.to_le_bytes(); + code[current_addr - start + 1] = low; + if len > 1 { + code[current_addr - start + 2] = mid + } + if len > 2 { + code[current_addr - start + 3] = high + } + current_addr += len + 1; + } + VASMLine::Db(_, _) => { + let arg = line_args[&line_num]; + poke_word(&mut code, current_addr - start, arg); + current_addr += 3; + } + VASMLine::StringDb(_, string) => { + for ch in string.as_bytes() { + code[current_addr - start] = *ch; + current_addr += 1; + } + } + VASMLine::Org(_, _) => { + current_addr = line_addresses[&(line_num + 1)] as usize; + } + VASMLine::Equ(_, _) | VASMLine::LabelDef(_) => {} + } + } + + Ok(code) +} + #[cfg(test)] mod test { use super::AssembleError::*; @@ -203,11 +307,24 @@ mod test { } fn calculate_args_pass<'a>(lines: &'a [&str]) -> Result> { + let (line_addresses, scope) = place_labels_pass(lines)?; + let lines = parse(lines); + calculate_args(&lines, scope, &line_addresses) + } + + fn bounds<'a>(lines: &'a [&str]) -> Result<(usize, usize), AssembleError<'a>> { + let (line_addresses, scope) = place_labels_pass(lines)?; + let lines = parse(lines); + let lengths = measure_instructions(&lines, &scope); + code_bounds(&lines, &line_addresses, &lengths) + } + + fn generate_code_pass<'a>(lines: &'a [&str]) -> Result, AssembleError<'a>> { let lines = parse(lines); let scope = solve_equs(&lines).unwrap(); let lengths = measure_instructions(&lines, &scope); let (line_addresses, scope) = place_labels(&lines, scope, &lengths)?; - calculate_args(&lines, scope, &line_addresses) + generate_code(&lines, scope, line_addresses, lengths) } #[test] @@ -347,4 +464,38 @@ mod test { )) ); } + + #[test] + fn test_bounds() { + assert_eq!(bounds(&["add"]), Ok((0, 0))); + assert_eq!(bounds(&["add -4"]), Ok((0, 3))); + assert_eq!(bounds(&["add 7"]), Ok((0, 1))); + assert_eq!(bounds(&[".org 0x400", "add"]), Ok((1024, 1024))); + assert_eq!( + bounds(&["start: .equ 1024", ".org start", "add"]), + Ok((1024, 1024)) + ); + assert_eq!( + bounds(&[".org 0x400", "add", ".org 0x800"]), + Ok((1024, 1024)) + ); + assert_eq!( + bounds(&[".org 0x400", "add", ".org 0x800", ".db 5", "blah:"]), + Ok((1024, 2050)) + ); + assert_eq!(bounds(&[]), Err(AssembleError::NoCode)); + assert_eq!(bounds(&[".org 0x400"]), Err(AssembleError::NoCode)); + assert_eq!( + bounds(&["foo: .equ 3", ".org 0x400"]), + Err(AssembleError::NoCode) + ); + } + + #[test] + fn test_generate_code() { + assert_eq!(generate_code_pass(&["add"]), Ok(vec![4])); + assert_eq!(generate_code_pass(&[".org 0x400", "add 7"]), Ok(vec![5, 7])); + assert_eq!(generate_code_pass(&[".db 57"]), Ok(vec![57, 0, 0])); + assert_eq!(generate_code_pass(&[".db \"AZ\0\""]), Ok(vec![65, 90, 0])); + } } diff --git a/vcore/src/opcodes.rs b/vcore/src/opcodes.rs index 664d32f..ac43bd0 100644 --- a/vcore/src/opcodes.rs +++ b/vcore/src/opcodes.rs @@ -306,6 +306,56 @@ impl<'a> TryFrom<&'a str> for Opcode { } } +impl From for u8 { + fn from(opcode: Opcode) -> Self { + match opcode { + Opcode::Nop => 0, + Opcode::Add => 1, + Opcode::Sub => 2, + Opcode::Mul => 3, + Opcode::Div => 4, + Opcode::Mod => 5, + Opcode::Rand => 6, + Opcode::And => 7, + Opcode::Or => 8, + Opcode::Xor => 9, + Opcode::Not => 10, + Opcode::Gt => 11, + Opcode::Lt => 12, + Opcode::Agt => 13, + Opcode::Alt => 14, + Opcode::Lshift => 15, + Opcode::Rshift => 16, + Opcode::Arshift => 17, + Opcode::Pop => 18, + Opcode::Dup => 19, + Opcode::Swap => 20, + Opcode::Pick => 21, + Opcode::Rot => 22, + Opcode::Jmp => 23, + Opcode::Jmpr => 24, + Opcode::Call => 25, + Opcode::Ret => 26, + Opcode::Brz => 27, + Opcode::Brnz => 28, + Opcode::Hlt => 29, + Opcode::Load => 30, + Opcode::Loadw => 31, + Opcode::Store => 32, + Opcode::Storew => 33, + Opcode::Inton => 34, + Opcode::Intoff => 35, + Opcode::Setiv => 36, + Opcode::Sdp => 37, + Opcode::Setsdp => 38, + Opcode::Pushr => 39, + Opcode::Popr => 40, + Opcode::Peekr => 41, + Opcode::Debug => 42, + } + } +} + #[test] fn test_decode() { assert_eq!(Opcode::try_from(18), Ok(Opcode::Pop));