diff --git a/sim_8086/src/arithmetic_expression.rs b/sim_8086/src/arithmetic_expression.rs new file mode 100644 index 0000000..0d8a900 --- /dev/null +++ b/sim_8086/src/arithmetic_expression.rs @@ -0,0 +1,169 @@ +use std::ops::{Add, Mul, Sub}; + +#[derive(PartialEq, Eq)] +enum Op { + Add, + Times, +} + +enum BinaryExpr { + Const(T), + /// Indices into the arena. + Binary(Op, usize, usize), +} + +pub(crate) struct ArithmeticExpression { + /// In reverse Polish notation. + arena: Vec>, +} + +#[derive(Copy, Clone)] +pub(crate) enum Token { + Literal(T), + Add, + Times, +} + +pub(crate) trait HasMax { + const MAX: Self; +} + +impl HasMax for u16 { + const MAX: u16 = u16::MAX; +} + +impl HasMax for u8 { + const MAX: u8 = u8::MAX; +} + +impl ArithmeticExpression +where + T: Copy, +{ + fn eval_index(&self, index: usize) -> Result + where + T: Add + Sub + Mul + HasMax + PartialOrd, + u64: From, + { + match &self.arena[index] { + BinaryExpr::Const(x) => Ok(*x), + BinaryExpr::Binary(op, left, right) => { + let left = self.eval_index(*left)?; + let right = self.eval_index(*right)?; + match op { + Op::Add => { + if left < T::MAX - right { + Ok(left + right) + } else { + Err(()) + } + } + Op::Times => { + let x = u64::from(left); + let y = u64::from(right); + if x * y < u64::from(T::MAX) { + Ok(left * right) + } else { + Err(()) + } + } + } + } + } + } + + pub(crate) fn eval(&self) -> Result + where + T: Add + Mul + HasMax + PartialOrd + Sub, + u64: From, + { + self.eval_index(self.arena.len() - 1) + } + + pub(crate) fn of_tokens]>>(t: Iter) -> ArithmeticExpression { + let mut arena = vec![]; + let mut outstanding_ops = vec![]; + let mut outstanding_exprs = vec![]; + for token in t.as_ref() { + match token { + Token::Literal(l) => { + arena.push(BinaryExpr::Const(*l)); + let to_add = match outstanding_ops.pop() { + None => arena.len() - 1, + Some(Op::Times) => match outstanding_exprs.pop() { + None => { + panic!("Bad token stream, had Times operation with no left operand") + } + Some(left_operand) => { + let right = arena.len() - 1; + + arena.push(BinaryExpr::Binary(Op::Times, left_operand, right)); + arena.len() - 1 + } + }, + Some(Op::Add) => { + outstanding_ops.push(Op::Add); + arena.len() - 1 + } + }; + outstanding_exprs.push(to_add); + } + Token::Add => outstanding_ops.push(Op::Add), + Token::Times => outstanding_ops.push(Op::Times), + } + } + + while let Some(op) = outstanding_ops.pop() { + assert!(op == Op::Add); + let left = outstanding_exprs.pop().expect("needed a right operand"); + let right = outstanding_exprs.pop().expect("needed a left operand"); + arena.push(BinaryExpr::Binary(Op::Add, left, right)); + outstanding_exprs.push(arena.len() - 1); + } + if !outstanding_ops.is_empty() { + panic!("Imbalanced expression"); + } + if outstanding_exprs.len() > 1 { + panic!("Not enough operations"); + } + if outstanding_exprs.is_empty() { + panic!("Empty expression?!"); + } + + ArithmeticExpression { arena } + } +} + +#[cfg(test)] +mod test_arithmetic_expression { + use super::{ArithmeticExpression, Token}; + + #[test] + fn test_expr() { + let tokens = vec![ + Token::Literal(10), + Token::Add, + Token::Literal(33), + Token::Times, + Token::Literal(12), + ]; + + assert_eq!( + ArithmeticExpression::of_tokens(tokens).eval().unwrap(), + 10u16 + (33 * 12) + ); + + let tokens = vec![ + Token::Literal(10), + Token::Times, + Token::Literal(33), + Token::Add, + Token::Literal(12), + ]; + + assert_eq!( + ArithmeticExpression::of_tokens(tokens).eval().unwrap(), + (10u16 * 33) + 12 + ); + } +} diff --git a/sim_8086/src/assembly.rs b/sim_8086/src/assembly.rs index c903ac5..9987a5e 100644 --- a/sim_8086/src/assembly.rs +++ b/sim_8086/src/assembly.rs @@ -2,15 +2,17 @@ use nom::{ branch::alt, bytes::complete::{is_not, tag}, character::complete::{ - alphanumeric1, char, digit1, line_ending, multispace0, not_line_ending, one_of, + alphanumeric1, char, digit1, line_ending, multispace0, not_line_ending, one_of, space0, }, - combinator::map_res, - multi::many0, + combinator::{map_res, opt}, + error::FromExternalError, + multi::{many0, many1}, sequence::{delimited, preceded, separated_pair, terminated, tuple}, IResult, }; use crate::{ + arithmetic_expression::{ArithmeticExpression, Token}, arithmetic_instruction::{ ArithmeticInstruction, ArithmeticInstructionSelect, ArithmeticOperation, MemRegArithmetic, RegMemArithmetic, RegRegArithmetic, @@ -19,13 +21,14 @@ use crate::{ instruction::Instruction, jump_instruction::Jump, move_instruction::{ - AccumulatorToMemory, ImmediateToRegister, ImmediateToRegisterOrMemory, MemRegMove, - MemoryToAccumulator, MoveInstruction, RegMemMove, RegRegMove, + AccumulatorToMemory, ImmediateToMemory, ImmediateToRegister, MemRegMove, + MemoryToAccumulator, MemoryToSegment, MoveInstruction, RegMemMove, RegRegMove, + RegisterToSegment, SegmentToMemory, SegmentToRegister, }, program::Program, register::{ - Base, ByteRegisterSubset, GeneralRegister, Register, RegisterSubset, SourceDest, - SpecialRegister, + Base, ByteRegisterSubset, GeneralRegister, Register, RegisterSubset, SegmentRegister, + SourceDest, SpecialRegister, }, trivia_instruction::TriviaInstruction, }; @@ -43,13 +46,42 @@ where delimited(multispace0, inner, multispace0) } +fn line_end(input: &str) -> IResult<&str, &str> { + alt((comment, line_ending))(input) +} + +#[derive(Eq, PartialEq)] +enum OffsetTag { + Byte, + Word, + None, +} + fn bracketed<'a, F, O, E: nom::error::ParseError<&'a str>>( inner: F, -) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> +) -> impl FnMut(&'a str) -> IResult<&'a str, (OffsetTag, O), E> where F: FnMut(&'a str) -> IResult<&'a str, O, E>, + E: FromExternalError<&'a str, ()>, { - preceded(char('['), terminated(inner, char(']'))) + map_res( + tuple(( + opt(ws(alt((tag("byte"), tag("word"))))), + preceded(char('['), terminated(inner, char(']'))), + )), + |(tag, inner)| { + Ok::<_, _>(match tag { + None => (OffsetTag::None, inner), + Some(tag) => { + if tag == "byte" { + (OffsetTag::Byte, inner) + } else { + (OffsetTag::Word, inner) + } + } + }) + }, + ) } fn argument_sep(input: &str) -> IResult<&str, ()> { @@ -57,7 +89,7 @@ fn argument_sep(input: &str) -> IResult<&str, ()> { } fn bits(input: &str) -> IResult<&str, u8> { - let p = preceded(tag("bits "), terminated(digit1, line_ending)); + let p = preceded(tag("bits "), digit1); map_res(p, str::parse)(input) } @@ -128,13 +160,14 @@ fn reg_reg_move_instruction(input: &str) -> IResult<&str, RegRegMove> { map_res( preceded( tag("mov "), - tuple((register, argument_sep, register, line_ending)), + tuple((terminated(register, argument_sep), register)), ), - |(dest, _, source, _)| Ok::<_, ()>(RegRegMove { dest, source }), + |(dest, source)| Ok::<_, ()>(RegRegMove { dest, source }), )(input) } -fn direct_offset(input: &str) -> IResult<&str, u16> { +// TODO: anything bracketed should be able to know if it's a byte or a word +fn direct_offset(input: &str) -> IResult<&str, (OffsetTag, u16)> { bracketed(map_res(digit1, str::parse))(input) } @@ -152,7 +185,7 @@ fn source_dest(input: &str) -> IResult<&str, SourceDest> { ))(input) } -fn absolute_u8(input: &str) -> IResult<&str, u8> { +fn literal_absolute_u8(input: &str) -> IResult<&str, u8> { alt(( map_res(preceded(tag("0x"), alphanumeric1), |s: &str| { s.chars() @@ -176,7 +209,7 @@ fn absolute_u8(input: &str) -> IResult<&str, u8> { ))(input) } -fn absolute_u16(input: &str) -> IResult<&str, u16> { +fn literal_absolute_u16(input: &str) -> IResult<&str, u16> { alt(( map_res(preceded(tag("0x"), alphanumeric1), |s: &str| { s.chars() @@ -200,6 +233,36 @@ fn absolute_u16(input: &str) -> IResult<&str, u16> { ))(input) } +fn arithmetic_expression_u8(input: &str) -> IResult<&str, ArithmeticExpression> { + map_res( + many1(alt(( + map_res(literal_absolute_u8, |x| Ok::<_, ()>(Token::Literal(x))), + map_res(ws(char('+')), |_| Ok::<_, ()>(Token::Add)), + map_res(ws(char('*')), |_| Ok::<_, ()>(Token::Times)), + ))), + |stream| Ok::<_, ()>(ArithmeticExpression::of_tokens(stream)), + )(input) +} + +fn arithmetic_expression_u16(input: &str) -> IResult<&str, ArithmeticExpression> { + map_res( + many1(alt(( + map_res(literal_absolute_u16, |x| Ok::<_, ()>(Token::Literal(x))), + map_res(ws(char('+')), |_| Ok::<_, ()>(Token::Add)), + map_res(ws(char('*')), |_| Ok::<_, ()>(Token::Times)), + ))), + |stream| Ok::<_, ()>(ArithmeticExpression::of_tokens(stream)), + )(input) +} + +fn absolute_u8(input: &str) -> IResult<&str, u8> { + map_res(arithmetic_expression_u8, |expr| expr.eval())(input) +} + +fn absolute_u16(input: &str) -> IResult<&str, u16> { + map_res(arithmetic_expression_u16, |expr| expr.eval())(input) +} + fn negative_u8(input: &str) -> IResult<&str, u8> { map_res( preceded(ws(char('-')), alt((digit1, preceded(tag("byte "), digit1)))), @@ -214,24 +277,24 @@ fn negative_u16(input: &str) -> IResult<&str, u16> { )(input) } -fn literal_u8(input: &str) -> IResult<&str, u8> { - alt((absolute_u8, negative_u8))(input) -} - fn literal_u16(input: &str) -> IResult<&str, u16> { alt((absolute_u16, negative_u16))(input) } -fn effective_address(input: &str) -> IResult<&str, EffectiveAddress> { +fn literal_u8(input: &str) -> IResult<&str, u8> { + alt((absolute_u8, negative_u8))(input) +} + +fn effective_address(input: &str) -> IResult<&str, (OffsetTag, EffectiveAddress)> { alt(( // Sum, no offset map_res( bracketed(tuple((base, ws(char('+')), source_dest))), - |(base, _, source_dest)| { - Ok::<_, ()>(EffectiveAddress::Sum(WithOffset::Basic(( - base, - source_dest, - )))) + |(tag, (base, _, source_dest))| { + Ok::<_, ()>(( + tag, + EffectiveAddress::Sum(WithOffset::Basic((base, source_dest))), + )) }, ), // Sum, with offset @@ -242,11 +305,11 @@ fn effective_address(input: &str) -> IResult<&str, EffectiveAddress> { source_dest, alt((preceded(ws(char('+')), absolute_u8), negative_u8)), ))), - |(base, _, source_dest, offset)| { - Ok::<_, ()>(EffectiveAddress::Sum(WithOffset::WithU8( - (base, source_dest), - offset, - ))) + |(tag, (base, _, source_dest, offset))| { + Ok::<_, ()>(( + tag, + EffectiveAddress::Sum(WithOffset::WithU8((base, source_dest), offset)), + )) }, ), map_res( @@ -256,29 +319,30 @@ fn effective_address(input: &str) -> IResult<&str, EffectiveAddress> { source_dest, alt((preceded(ws(char('+')), absolute_u16), negative_u16)), ))), - |(base, _, source_dest, offset)| { - Ok::<_, ()>(EffectiveAddress::Sum(WithOffset::WithU16( - (base, source_dest), - offset, - ))) + |(tag, (base, _, source_dest, offset))| { + Ok::<_, ()>(( + tag, + EffectiveAddress::Sum(WithOffset::WithU16((base, source_dest), offset)), + )) }, ), // Lookup - map_res(source_dest, |source_dest| { - Ok::<_, ()>(EffectiveAddress::SpecifiedIn(WithOffset::Basic( - source_dest, - ))) + map_res(bracketed(source_dest), |(tag, source_dest)| { + Ok::<_, ()>(( + tag, + EffectiveAddress::SpecifiedIn(WithOffset::Basic(source_dest)), + )) }), map_res( bracketed(tuple(( source_dest, alt((preceded(ws(char('+')), absolute_u8), negative_u8)), ))), - |(source_dest, offset)| { - Ok::<_, ()>(EffectiveAddress::SpecifiedIn(WithOffset::WithU8( - source_dest, - offset, - ))) + |(tag, (source_dest, offset))| { + Ok::<_, ()>(( + tag, + EffectiveAddress::SpecifiedIn(WithOffset::WithU8(source_dest, offset)), + )) }, ), map_res( @@ -286,71 +350,138 @@ fn effective_address(input: &str) -> IResult<&str, EffectiveAddress> { source_dest, alt((preceded(ws(char('+')), absolute_u16), negative_u16)), ))), - |(source_dest, offset)| { - Ok::<_, ()>(EffectiveAddress::SpecifiedIn(WithOffset::WithU16( - source_dest, - offset, - ))) + |(tag, (source_dest, offset))| { + Ok::<_, ()>(( + tag, + EffectiveAddress::SpecifiedIn(WithOffset::WithU16(source_dest, offset)), + )) }, ), // Offset from BX - map_res(bracketed(tag("bx")), |_| { - Ok::<_, ()>(EffectiveAddress::Bx(WithOffset::Basic(()))) + map_res(bracketed(tag("bx")), |(tag, _)| { + Ok::<_, ()>((tag, EffectiveAddress::Bx(WithOffset::Basic(())))) }), map_res( - bracketed(tuple(( + bracketed(preceded( tag("bx"), alt((preceded(ws(char('+')), absolute_u8), negative_u8)), - ))), - |(_, offset)| Ok::<_, ()>(EffectiveAddress::Bx(WithOffset::WithU8((), offset))), + )), + |(tag, offset)| { + Ok::<_, ()>((tag, EffectiveAddress::Bx(WithOffset::WithU8((), offset)))) + }, ), map_res( - bracketed(tuple(( + bracketed(preceded( tag("bx"), alt((preceded(ws(char('+')), absolute_u16), negative_u16)), - ))), - |(_, offset)| Ok::<_, ()>(EffectiveAddress::Bx(WithOffset::WithU16((), offset))), + )), + |(tag, offset)| { + Ok::<_, ()>((tag, EffectiveAddress::Bx(WithOffset::WithU16((), offset)))) + }, ), // Direct memory address - map_res(direct_offset, |offset| { - Ok::<_, ()>(EffectiveAddress::Direct(offset)) + map_res(direct_offset, |(tag, offset)| { + Ok::<_, ()>((tag, EffectiveAddress::Direct(offset))) }), // Offset from base pointer map_res( - bracketed(tuple(( + bracketed(preceded( tag("bp"), alt((preceded(ws(char('+')), absolute_u8), negative_u8)), - ))), - |(_, offset)| Ok::<_, ()>(EffectiveAddress::BasePointer(offset)), + )), + |(tag, offset)| Ok::<_, ()>((tag, EffectiveAddress::BasePointer(offset))), ), map_res( - bracketed(tuple(( + bracketed(preceded( tag("bp"), alt((preceded(ws(char('+')), absolute_u16), negative_u16)), - ))), - |(_, offset)| Ok::<_, ()>(EffectiveAddress::BasePointerWide(offset)), + )), + |(tag, offset)| Ok::<_, ()>((tag, EffectiveAddress::BasePointerWide(offset))), ), // Specific support for [bp], which can't be represented as a simple instruction - map_res(bracketed(tag("bp")), |_| { - Ok::<_, ()>(EffectiveAddress::BasePointer(0)) + map_res(bracketed(tag("bp")), |(tag, _)| { + Ok::<_, ()>((tag, EffectiveAddress::BasePointer(0))) }), ))(input) } +fn segment_register(input: &str) -> IResult<&str, SegmentRegister> { + map_res( + terminated(alt((char('s'), char('d'), char('e'), char('c'))), char('s')), + |c| match c { + 's' => Ok::<_, ()>(SegmentRegister::Stack), + 'd' => Ok(SegmentRegister::Data), + 'e' => Ok(SegmentRegister::Extra), + 'c' => Ok(SegmentRegister::Code), + _ => unreachable!(), + }, + )(input) +} + +fn reg_to_seg_move_instruction(input: &str) -> IResult<&str, RegisterToSegment> { + map_res( + preceded( + tag("mov "), + tuple((terminated(segment_register, ws(char(','))), register)), + ), + |(dest, source)| Ok::<_, ()>(RegisterToSegment { dest, source }), + )(input) +} + +fn mem_to_seg_move_instruction(input: &str) -> IResult<&str, MemoryToSegment> { + map_res( + preceded( + tag("mov "), + tuple(( + terminated(segment_register, ws(char(','))), + effective_address, + )), + ), + |(dest, (tag, source))| match tag { + OffsetTag::Byte => Err(()), + _ => Ok::<_, ()>(MemoryToSegment { dest, source }), + }, + )(input) +} + +fn seg_to_mem_move_instruction(input: &str) -> IResult<&str, SegmentToMemory> { + map_res( + preceded( + tag("mov "), + tuple(( + terminated(effective_address, ws(char(','))), + segment_register, + )), + ), + |((tag, dest), source)| match tag { + OffsetTag::Byte => Err(()), + _ => Ok::<_, ()>(SegmentToMemory { dest, source }), + }, + )(input) +} + +fn seg_to_reg_move_instruction(input: &str) -> IResult<&str, SegmentToRegister> { + map_res( + preceded( + tag("mov "), + tuple((terminated(register, ws(char(','))), segment_register)), + ), + |(dest, source)| Ok::<_, ()>(SegmentToRegister { dest, source }), + )(input) +} + fn reg_mem_move_instruction(input: &str) -> IResult<&str, RegMemMove> { map_res( preceded( tag("mov "), - tuple(( - terminated(effective_address, argument_sep), - terminated(register, line_ending), - )), + tuple((terminated(effective_address, argument_sep), register)), ), - |(address, register)| { - Ok::<_, ()>(RegMemMove { + |((tag, address), register)| match (tag, register.is_wide()) { + (OffsetTag::Word, false) => Err(()), + _ => Ok::<_, ()>(RegMemMove { dest: address, source: register, - }) + }), }, )(input) } @@ -359,16 +490,14 @@ fn mem_reg_move_instruction(input: &str) -> IResult<&str, MemRegMove> { map_res( preceded( tag("mov "), - tuple(( - terminated(register, argument_sep), - terminated(effective_address, line_ending), - )), + tuple((terminated(register, argument_sep), effective_address)), ), - |(register, address)| { - Ok::<_, ()>(MemRegMove { + |(register, (tag, address))| match (tag, register.is_wide()) { + (OffsetTag::Word, false) => Err(()), + _ => Ok::<_, ()>(MemRegMove { dest: register, source: address, - }) + }), }, )(input) } @@ -392,18 +521,12 @@ fn immediate_to_register_instruction(input: &str) -> IResult<&str, ImmediateToRe preceded( tag("mov "), alt(( - terminated( - map_res(immediate_wide, |(register, x)| { - Ok::<_, ()>((register, Err(x))) - }), - line_ending, - ), - terminated( - map_res(immediate_byte, |(register, x)| { - Ok::<_, ()>((register, Ok(x))) - }), - line_ending, - ), + map_res(immediate_wide, |(register, x)| { + Ok::<_, ()>((register, Err(x))) + }), + map_res(immediate_byte, |(register, x)| { + Ok::<_, ()>((register, Ok(x))) + }), )), ), |(register, contents)| { @@ -421,7 +544,7 @@ fn immediate_to_register_instruction(input: &str) -> IResult<&str, ImmediateToRe )(input) } -fn immediate_to_memory_instruction(input: &str) -> IResult<&str, ImmediateToRegisterOrMemory> { +fn immediate_to_memory_instruction(input: &str) -> IResult<&str, ImmediateToMemory> { map_res( tuple(( terminated(preceded(tag("mov "), effective_address), argument_sep), @@ -430,11 +553,19 @@ fn immediate_to_memory_instruction(input: &str) -> IResult<&str, ImmediateToRegi map_res(literal_u16, |x| Ok::<_, ()>(Err(x))), )), )), - |(addr, x)| { - Ok::<_, ()>(match x { - Ok(b) => ImmediateToRegisterOrMemory::Byte(addr, b), - Err(b) => ImmediateToRegisterOrMemory::Word(addr, b), - }) + |((tag, addr), x)| match tag { + OffsetTag::None => Ok::<_, ()>(match x { + Ok(b) => ImmediateToMemory::Byte(addr, b), + Err(b) => ImmediateToMemory::Word(addr, b), + }), + OffsetTag::Byte => match x { + Ok(b) => Ok(ImmediateToMemory::Byte(addr, b)), + Err(b) => panic!("Can't fit literal {b} into byte memory"), + }, + OffsetTag::Word => match x { + Ok(b) => Ok(ImmediateToMemory::Word(addr, b as u16)), + Err(b) => Ok(ImmediateToMemory::Word(addr, b)), + }, }, )(input) } @@ -448,9 +579,13 @@ fn memory_to_accumulator_instruction(input: &str) -> IResult<&str, MemoryToAccum bracketed(literal_u16), )), ), - |(acc, address)| { + |(acc, (tag, address))| { let is_wide = acc == 'x'; - Ok::<_, ()>(MemoryToAccumulator { address, is_wide }) + if !is_wide && tag == OffsetTag::Word { + Err(()) + } else { + Ok(MemoryToAccumulator { address, is_wide }) + } }, )(input) } @@ -464,9 +599,13 @@ fn accumulator_to_memory_instruction(input: &str) -> IResult<&str, AccumulatorTo alt((char('h'), char('x'))), )), ), - |(address, acc)| { + |((tag, address), acc)| { let is_wide = acc == 'x'; - Ok::<_, ()>(AccumulatorToMemory { address, is_wide }) + if is_wide && tag == OffsetTag::Byte { + Err(()) + } else { + Ok(AccumulatorToMemory { address, is_wide }) + } }, )(input) } @@ -522,18 +661,20 @@ fn arithmetic_select(input: &str) -> IResult<&str, ArithmeticInstructionSelect> ), map_res( tuple((terminated(register, argument_sep), effective_address)), - |(dest, source)| { - Ok::<_, ()>(ArithmeticInstructionSelect::MemoryToRegister( + |(dest, (tag, source))| match (tag, dest.is_wide()) { + (OffsetTag::Word, false) => Err(()), + _ => Ok(ArithmeticInstructionSelect::MemoryToRegister( MemRegArithmetic { source, dest }, - )) + )), }, ), map_res( tuple((terminated(effective_address, argument_sep), register)), - |(dest, source)| { - Ok::<_, ()>(ArithmeticInstructionSelect::RegisterToMemory( + |((tag, dest), source)| match (tag, source.is_wide()) { + (OffsetTag::Byte, true) => Err(()), + _ => Ok(ArithmeticInstructionSelect::RegisterToMemory( RegMemArithmetic { source, dest }, - )) + )), }, ), map_res( @@ -561,58 +702,24 @@ fn arithmetic_select(input: &str) -> IResult<&str, ArithmeticInstructionSelect> }, ), map_res( - tuple(( - terminated(preceded(tag("word "), effective_address), argument_sep), - literal_u8, - )), - |(addr, literal)| { + tuple((terminated(effective_address, argument_sep), literal_u8)), + |((tag, addr), literal)| { Ok::<_, ()>( ArithmeticInstructionSelect::ImmediateToRegisterOrMemoryByte( - addr, literal, true, - ), - ) - }, - ), - map_res( - tuple(( - terminated(preceded(tag("word "), effective_address), argument_sep), - literal_u16, - )), - |(addr, literal)| { - Ok::<_, ()>( - ArithmeticInstructionSelect::ImmediateToRegisterOrMemoryWord(addr, literal), - ) - }, - ), - map_res( - tuple(( - terminated(preceded(tag("byte "), effective_address), argument_sep), - literal_u8, - )), - |(addr, literal)| { - Ok::<_, ()>( - ArithmeticInstructionSelect::ImmediateToRegisterOrMemoryByte( - addr, literal, false, + addr, + literal, + tag == OffsetTag::Word, ), ) }, ), map_res( tuple((terminated(effective_address, argument_sep), literal_u16)), - |(addr, literal)| { - Ok::<_, ()>( + |((tag, addr), literal)| match tag { + OffsetTag::Byte => Err(()), + _ => Ok::<_, ()>( ArithmeticInstructionSelect::ImmediateToRegisterOrMemoryWord(addr, literal), - ) - }, - ), - map_res( - tuple((terminated(effective_address, argument_sep), literal_u8)), - |(addr, literal)| { - Ok::<_, ()>( - ArithmeticInstructionSelect::ImmediateToRegisterOrMemoryByte( - addr, literal, false, - ), - ) + ), }, ), ))(input) @@ -620,16 +727,13 @@ fn arithmetic_select(input: &str) -> IResult<&str, ArithmeticInstructionSelect> fn arithmetic_instruction(input: &str) -> IResult<&str, ArithmeticInstruction> { map_res( - tuple(( - terminated(arithmetic_op, char(' ')), - terminated(arithmetic_select, line_ending), - )), + tuple((terminated(arithmetic_op, char(' ')), arithmetic_select)), |(op, instruction)| Ok::<_, ()>(ArithmeticInstruction { op, instruction }), )(input) } fn label(input: &str) -> IResult<&str, &str> { - terminated(is_not(":\r\n \t"), terminated(char(':'), line_ending))(input) + terminated(is_not(":\r\n \t"), char(':'))(input) } fn label_terminator(input: &str) -> IResult<&str, &str> { @@ -728,7 +832,19 @@ fn move_instruction(input: &str) -> IResult<&str, MoveInstruction> { Ok::<_, ()>(MoveInstruction::ImmediateToRegister(v)) }), map_res(immediate_to_memory_instruction, |v| { - Ok::<_, ()>(MoveInstruction::ImmediateToRegisterOrMemory(v)) + Ok::<_, ()>(MoveInstruction::ImmediateToMemory(v)) + }), + map_res(seg_to_mem_move_instruction, |v| { + Ok::<_, ()>(MoveInstruction::SegmentToMemory(v)) + }), + map_res(seg_to_reg_move_instruction, |v| { + Ok::<_, ()>(MoveInstruction::SegmentToRegister(v)) + }), + map_res(reg_to_seg_move_instruction, |v| { + Ok::<_, ()>(MoveInstruction::RegisterToSegment(v)) + }), + map_res(mem_to_seg_move_instruction, |v| { + Ok::<_, ()>(MoveInstruction::MemoryToSegment(v)) }), ))(input) } @@ -746,20 +862,19 @@ fn instruction(input: &str) -> IResult<&str, Instruction<&str>> { ))(input) } -fn trivia(input: &str) -> IResult<&str, &str> { - alt((comment, line_ending))(input) -} - pub fn program(input: &str) -> IResult<&str, Program>, &str>> { map_res( preceded( - many0(trivia), + many0(line_end), separated_pair( bits, - many0(trivia), + many0(line_end), many0(alt(( - map_res(instruction, |i| Ok::<_, ()>(Some(i))), - map_res(trivia, |_| Ok::<_, ()>(None)), + map_res( + terminated(preceded(space0, instruction), preceded(space0, line_end)), + |i| Ok::<_, ()>(Some(i)), + ), + map_res(preceded(space0, line_end), |_| Ok::<_, ()>(None)), ))), ), ), @@ -772,3 +887,64 @@ pub fn program(input: &str) -> IResult<&str, Program>, &st }, )(input) } + +#[cfg(test)] +mod test_assembly { + use crate::{ + arithmetic_instruction::{ + ArithmeticInstruction, ArithmeticInstructionSelect, ArithmeticOperation, + }, + assembly::instruction, + effective_address::{EffectiveAddress, WithOffset}, + instruction::Instruction, + move_instruction::{ImmediateToMemory, MoveInstruction}, + register::{GeneralRegister, Register, RegisterSubset}, + }; + + #[test] + fn arithmetic_expression_parse() { + let (remaining, parsed) = instruction("add bx, 4*64").unwrap(); + assert_eq!(remaining, ""); + assert_eq!( + parsed, + Instruction::Arithmetic(ArithmeticInstruction { + op: ArithmeticOperation::Add, + instruction: ArithmeticInstructionSelect::ImmediateToRegisterWord( + Register::General(GeneralRegister::B, RegisterSubset::All), + 4 * 64, + false + ) + }) + ) + } + + #[test] + fn arithmetic_expression_parse_2() { + let (remaining, parsed) = instruction("add cx, 166").unwrap(); + assert_eq!(remaining, ""); + assert_eq!( + parsed, + Instruction::Arithmetic(ArithmeticInstruction { + op: ArithmeticOperation::Add, + instruction: ArithmeticInstructionSelect::ImmediateToRegisterByte( + Register::General(GeneralRegister::C, RegisterSubset::All), + 166, + true + ) + }) + ) + } + + #[test] + fn mov_parse() { + let (remaining, parsed) = instruction("mov byte [bx + 61*4 + 1], 255").unwrap(); + assert_eq!(remaining, ""); + assert_eq!( + parsed, + Instruction::Move(MoveInstruction::ImmediateToMemory(ImmediateToMemory::Byte( + EffectiveAddress::Bx(WithOffset::WithU8((), 61 * 4 + 1)), + 255 + ))) + ) + } +} diff --git a/sim_8086/src/computer.rs b/sim_8086/src/computer.rs index e453211..1aaabbe 100644 --- a/sim_8086/src/computer.rs +++ b/sim_8086/src/computer.rs @@ -1,7 +1,11 @@ use crate::{ + effective_address::{EffectiveAddress, WithOffset}, instruction::Instruction, - move_instruction::{ImmediateToRegister, ImmediateToRegisterOrMemory, MoveInstruction}, - register::{ByteRegisterSubset, GeneralRegister, Register, RegisterSubset, SpecialRegister}, + move_instruction::{ImmediateToMemory, ImmediateToRegister, MoveInstruction}, + register::{ + Base, ByteRegisterSubset, GeneralRegister, Register, RegisterSubset, SegmentRegister, + SourceDest, SpecialRegister, + }, }; pub struct Registers { @@ -13,6 +17,10 @@ pub struct Registers { di: u16, sp: u16, bp: u16, + ss: u16, + cs: u16, + es: u16, + ds: u16, } pub struct Computer { @@ -35,6 +43,10 @@ impl Computer { di: 0, sp: 0, bp: 0, + ss: 0, + cs: 0, + es: 0, + ds: 0, }, } } @@ -86,6 +98,15 @@ impl Computer { } } + fn get_segment(&self, r: SegmentRegister) -> u16 { + match r { + SegmentRegister::Code => self.registers.cs, + SegmentRegister::Data => self.registers.ds, + SegmentRegister::Stack => self.registers.ss, + SegmentRegister::Extra => self.registers.es, + } + } + fn display_big(u: u16) -> String { if u == 0 { "0x0".to_owned() @@ -103,7 +124,11 @@ impl Computer { } fn set_register(&mut self, r: &Register, value: u16) -> String { - let was = self.get_register(r); + let register_for_print = match r { + Register::General(x, _) => Register::General(x.clone(), RegisterSubset::All), + _ => r.clone(), + }; + let was = self.get_register(®ister_for_print); match r { Register::General(GeneralRegister::A, RegisterSubset::All) => self.registers.a = value, Register::General(GeneralRegister::B, RegisterSubset::All) => self.registers.b = value, @@ -170,7 +195,25 @@ impl Computer { Register::Special(SpecialRegister::SourceIndex) => self.registers.si = value, Register::Special(SpecialRegister::DestIndex) => self.registers.di = value, } - let is_now = self.get_register(r); + let is_now = self.get_register(®ister_for_print); + // TODO: this needs to print out "al: 0x22 -> blah" instead of "ax: 0x2222 -> blah" if short + format!( + "{}:{}->{}", + register_for_print, + Self::display_small(was), + Self::display_small(is_now) + ) + } + + fn set_segment(&mut self, r: SegmentRegister, value: u16) -> String { + let was = self.get_segment(r); + match r { + SegmentRegister::Code => self.registers.cs = value, + SegmentRegister::Data => self.registers.ds = value, + SegmentRegister::Stack => self.registers.ss = value, + SegmentRegister::Extra => self.registers.es = value, + } + let is_now = self.get_segment(r); format!( "{}:{}->{}", r, @@ -179,22 +222,225 @@ impl Computer { ) } + fn get_memory_byte(&self, index: usize) -> u8 { + self.memory[index] + } + + fn get_memory_word(&self, index: usize) -> u16 { + self.memory[index] as u16 * 256 + self.memory[index + 1] as u16 + } + + fn set_memory_byte(&mut self, index: usize, value: u8) -> String { + self.memory[index] = value; + "todo".to_owned() + } + + fn set_memory_word(&mut self, index: usize, value: u16) -> String { + self.memory[index] = (value % 256) as u8; + self.memory[index + 1] = (value / 256) as u8; + "todo".to_owned() + } + + fn resolve_eaddr(&self, a: &EffectiveAddress) -> usize { + match a { + EffectiveAddress::Sum(WithOffset::Basic((base, source_dest))) => { + let base_offset = match base { + Base::Bp => self.get_register(&Register::Special(SpecialRegister::BasePointer)), + Base::Bx => self + .get_register(&Register::General(GeneralRegister::B, RegisterSubset::All)), + }; + let source_offset = match source_dest { + SourceDest::Source => { + self.get_register(&Register::Special(SpecialRegister::SourceIndex)) + } + SourceDest::Dest => { + self.get_register(&Register::Special(SpecialRegister::DestIndex)) + } + }; + base_offset as usize + source_offset as usize + } + EffectiveAddress::Sum(WithOffset::WithU8((base, source_dest), addend)) => { + let base_offset = match base { + Base::Bp => self.get_register(&Register::Special(SpecialRegister::BasePointer)), + Base::Bx => self + .get_register(&Register::General(GeneralRegister::B, RegisterSubset::All)), + }; + let source_offset = match source_dest { + SourceDest::Source => { + self.get_register(&Register::Special(SpecialRegister::SourceIndex)) + } + SourceDest::Dest => { + self.get_register(&Register::Special(SpecialRegister::DestIndex)) + } + }; + base_offset as usize + source_offset as usize + *addend as usize + } + EffectiveAddress::Sum(WithOffset::WithU16((base, source_dest), addend)) => { + let base_offset = match base { + Base::Bp => self.get_register(&Register::Special(SpecialRegister::BasePointer)), + Base::Bx => self + .get_register(&Register::General(GeneralRegister::B, RegisterSubset::All)), + }; + let source_offset = match source_dest { + SourceDest::Source => { + self.get_register(&Register::Special(SpecialRegister::SourceIndex)) + } + SourceDest::Dest => { + self.get_register(&Register::Special(SpecialRegister::DestIndex)) + } + }; + base_offset as usize + source_offset as usize + *addend as usize + } + EffectiveAddress::SpecifiedIn(WithOffset::Basic(source_dest)) => { + let source_offset = match source_dest { + SourceDest::Source => { + self.get_register(&Register::Special(SpecialRegister::SourceIndex)) + } + SourceDest::Dest => { + self.get_register(&Register::Special(SpecialRegister::DestIndex)) + } + }; + source_offset as usize + } + EffectiveAddress::SpecifiedIn(WithOffset::WithU8(source_dest, offset)) => { + let source_offset = match source_dest { + SourceDest::Source => { + self.get_register(&Register::Special(SpecialRegister::SourceIndex)) + } + SourceDest::Dest => { + self.get_register(&Register::Special(SpecialRegister::DestIndex)) + } + }; + source_offset as usize + *offset as usize + } + EffectiveAddress::SpecifiedIn(WithOffset::WithU16(source_dest, offset)) => { + let source_offset = match source_dest { + SourceDest::Source => { + self.get_register(&Register::Special(SpecialRegister::SourceIndex)) + } + SourceDest::Dest => { + self.get_register(&Register::Special(SpecialRegister::DestIndex)) + } + }; + source_offset as usize + *offset as usize + } + EffectiveAddress::Bx(WithOffset::Basic(())) => { + let bx = + self.get_register(&Register::General(GeneralRegister::B, RegisterSubset::All)); + bx as usize + } + EffectiveAddress::Bx(WithOffset::WithU8((), offset)) => { + let bx = + self.get_register(&Register::General(GeneralRegister::B, RegisterSubset::All)); + bx as usize + *offset as usize + } + EffectiveAddress::Bx(WithOffset::WithU16((), offset)) => { + let bx = + self.get_register(&Register::General(GeneralRegister::B, RegisterSubset::All)); + bx as usize + *offset as usize + } + EffectiveAddress::Direct(addr) => *addr as usize, + EffectiveAddress::BasePointer(offset) => { + let bp = self.get_register(&Register::Special(SpecialRegister::BasePointer)); + bp as usize + *offset as usize + } + EffectiveAddress::BasePointerWide(offset) => { + let bp = self.get_register(&Register::Special(SpecialRegister::BasePointer)); + bp as usize + *offset as usize + } + } + } + fn step_mov(&mut self, instruction: &MoveInstruction) -> String { let preamble = format!("{}", instruction); let description = match &instruction { - MoveInstruction::RegRegMove(_) => todo!(), - MoveInstruction::RegMemMove(_) => todo!(), - MoveInstruction::MemRegMove(_) => todo!(), + MoveInstruction::RegRegMove(mov) => { + let value = self.get_register(&mov.source); + self.set_register(&mov.dest, value) + } + MoveInstruction::RegMemMove(mov) => { + let value = self.get_register(&mov.source); + if mov.source.is_wide() { + self.set_memory_word(self.resolve_eaddr(&mov.dest), value) + } else { + self.set_memory_byte(self.resolve_eaddr(&mov.dest), value as u8) + } + } + MoveInstruction::MemRegMove(mov) => { + if mov.dest.is_wide() { + let value = self.get_memory_word(self.resolve_eaddr(&mov.source)); + self.set_register(&mov.dest, value) + } else { + let value = self.get_memory_byte(self.resolve_eaddr(&mov.source)); + self.set_register(&mov.dest, value as u16) + } + } MoveInstruction::ImmediateToRegister(mov) => match mov { ImmediateToRegister::Byte(dest, value) => self.set_register(dest, *value as u16), ImmediateToRegister::Wide(dest, value) => self.set_register(dest, *value), }, - MoveInstruction::ImmediateToRegisterOrMemory(mov) => match mov { - ImmediateToRegisterOrMemory::Byte(_, _) => todo!(), - ImmediateToRegisterOrMemory::Word(_, _) => todo!(), + MoveInstruction::ImmediateToMemory(mov) => match mov { + ImmediateToMemory::Byte(addr, value) => { + let dest = self.resolve_eaddr(addr); + self.set_memory_byte(dest, *value) + } + ImmediateToMemory::Word(addr, value) => { + let dest = self.resolve_eaddr(addr); + self.set_memory_word(dest, *value) + } }, - MoveInstruction::MemoryToAccumulator(_) => todo!(), - MoveInstruction::AccumulatorToMemory(_) => todo!(), + MoveInstruction::MemoryToAccumulator(mov) => { + if mov.is_wide { + self.set_register( + &Register::General(GeneralRegister::A, RegisterSubset::All), + self.get_memory_word(mov.address as usize), + ) + } else { + self.set_register( + &Register::General( + GeneralRegister::A, + RegisterSubset::Subset(ByteRegisterSubset::Low), + ), + self.get_memory_byte(mov.address as usize) as u16, + ) + } + } + MoveInstruction::AccumulatorToMemory(mov) => { + if mov.is_wide { + self.set_memory_word( + mov.address as usize, + self.get_register(&Register::General( + GeneralRegister::A, + RegisterSubset::All, + )), + ) + } else { + self.set_memory_byte( + mov.address as usize, + self.get_register(&Register::General( + GeneralRegister::A, + RegisterSubset::Subset(ByteRegisterSubset::Low), + )) as u8, + ) + } + } + MoveInstruction::SegmentToMemory(mov) => { + let v = self.get_segment(mov.source); + let dest = self.resolve_eaddr(&mov.dest); + self.set_memory_word(dest, v) + } + MoveInstruction::MemoryToSegment(mov) => { + let value = self.get_memory_word(self.resolve_eaddr(&mov.source)); + self.set_segment(mov.dest, value) + } + MoveInstruction::SegmentToRegister(mov) => { + let v = self.get_segment(mov.source); + self.set_register(&mov.dest, v) + } + MoveInstruction::RegisterToSegment(mov) => { + let value = self.get_register(&mov.source); + self.set_segment(mov.dest, value) + } }; format!("{} ; {}", preamble, description) } @@ -241,6 +487,23 @@ impl Computer { )) } + for r in [ + SegmentRegister::Extra, + SegmentRegister::Code, + SegmentRegister::Stack, + SegmentRegister::Data, + ] { + let value = self.get_segment(r); + if value != 0 { + result.push_str(&format!( + "{}: {} ({})\n", + r, + Self::display_big(value), + value + )); + } + } + result } } diff --git a/sim_8086/src/effective_address.rs b/sim_8086/src/effective_address.rs index 9c0abb7..ada1f12 100644 --- a/sim_8086/src/effective_address.rs +++ b/sim_8086/src/effective_address.rs @@ -24,11 +24,17 @@ where #[derive(Eq, PartialEq, Debug, Hash, Clone)] pub enum EffectiveAddress { + /// An offset from the contents of the (BX/BP) register plus the contents of the (SI/DI) register. Sum(WithOffset<(Base, SourceDest)>), + /// An offset from the contents of the SI or DI register. SpecifiedIn(WithOffset), + /// An offset from the contents of the BX register. Bx(WithOffset<()>), + /// A literal index into memory. Direct(u16), + /// An offset from the contents of the BP register. BasePointer(u8), + /// An offset from the contents of the BP register. BasePointerWide(u16), } diff --git a/sim_8086/src/instruction.rs b/sim_8086/src/instruction.rs index b42f344..3c413f9 100644 --- a/sim_8086/src/instruction.rs +++ b/sim_8086/src/instruction.rs @@ -8,10 +8,11 @@ use crate::{ effective_address::EffectiveAddress, jump_instruction::Jump, move_instruction::{ - AccumulatorToMemory, ImmediateToRegister, ImmediateToRegisterOrMemory, MemRegMove, - MemoryToAccumulator, MoveInstruction, RegMemMove, RegRegMove, + AccumulatorToMemory, ImmediateToMemory, ImmediateToRegister, MemRegMove, + MemoryToAccumulator, MemoryToSegment, MoveInstruction, RegMemMove, RegRegMove, + RegisterToSegment, SegmentToMemory, SegmentToRegister, }, - register::Register, + register::{Register, SegmentRegister}, trivia_instruction::TriviaInstruction, }; @@ -263,17 +264,55 @@ impl Instruction { let data_low = bytes.next().unwrap(); if w == 1 { let data_high = bytes.next().unwrap() as u16 * 256; - Some(Instruction::Move( - MoveInstruction::ImmediateToRegisterOrMemory( - ImmediateToRegisterOrMemory::Word(dest, data_high + data_low as u16), - ), - )) + Some(Instruction::Move(MoveInstruction::ImmediateToMemory( + ImmediateToMemory::Word(dest, data_high + data_low as u16), + ))) } else { - Some(Instruction::Move( - MoveInstruction::ImmediateToRegisterOrMemory( - ImmediateToRegisterOrMemory::Byte(dest, data_low), - ), - )) + Some(Instruction::Move(MoveInstruction::ImmediateToMemory( + ImmediateToMemory::Byte(dest, data_low), + ))) + } + } else if b == 0b10001110 { + // Register/memory to segment + let mod_sr_rm = bytes.next().unwrap(); + let mode = mod_sr_rm / 64; + let sr = SegmentRegister::of_byte((mod_sr_rm & 0b00011000) / 8); + let rm = mod_sr_rm % 8; + + if mode == 3 { + let rm = Register::of_id(rm, true); + Some(Instruction::Move(MoveInstruction::RegisterToSegment( + RegisterToSegment { + dest: sr, + source: rm, + }, + ))) + } else { + let source = EffectiveAddress::of_mode_rm(mode, rm, bytes); + Some(Instruction::Move(MoveInstruction::MemoryToSegment( + MemoryToSegment { source, dest: sr }, + ))) + } + } else if b == 0b10001100 { + // Segment to register/memory + let mod_sr_rm = bytes.next().unwrap(); + let mode = mod_sr_rm / 64; + let sr = SegmentRegister::of_byte((mod_sr_rm & 0b00011000) / 8); + let rm = mod_sr_rm % 8; + + if mode == 3 { + let rm = Register::of_id(rm, true); + Some(Instruction::Move(MoveInstruction::SegmentToRegister( + SegmentToRegister { + source: sr, + dest: rm, + }, + ))) + } else { + let dest = EffectiveAddress::of_mode_rm(mode, rm, bytes); + Some(Instruction::Move(MoveInstruction::SegmentToMemory( + SegmentToMemory { dest, source: sr }, + ))) } } else if (b & 0b11000100) == 0b00000000u8 { // Arithmetic instruction, reg/memory with register to either diff --git a/sim_8086/src/lib.rs b/sim_8086/src/lib.rs index b905f52..0359c4e 100644 --- a/sim_8086/src/lib.rs +++ b/sim_8086/src/lib.rs @@ -1,3 +1,4 @@ +mod arithmetic_expression; pub mod arithmetic_instruction; pub mod assembly; pub mod computer; diff --git a/sim_8086/src/move_instruction.rs b/sim_8086/src/move_instruction.rs index 16e0688..738d632 100644 --- a/sim_8086/src/move_instruction.rs +++ b/sim_8086/src/move_instruction.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use crate::{ effective_address::EffectiveAddress, - register::{ByteRegisterSubset, Register, RegisterSubset}, + register::{ByteRegisterSubset, Register, RegisterSubset, SegmentRegister}, }; #[derive(Eq, PartialEq, Debug, Hash, Clone)] @@ -184,23 +184,23 @@ impl Display for ImmediateToRegister { } #[derive(Eq, PartialEq, Debug, Hash, Clone)] -pub enum ImmediateToRegisterOrMemory { +pub enum ImmediateToMemory { Byte(EffectiveAddress, u8), Word(EffectiveAddress, u16), } -impl ImmediateToRegisterOrMemory { +impl ImmediateToMemory { fn to_bytes(&self) -> Vec { let mut result = Vec::::with_capacity(3); let opcode = 0b11000110u8; match self { - ImmediateToRegisterOrMemory::Byte(address, data) => { + ImmediateToMemory::Byte(address, data) => { result.push(opcode); address.push(0, &mut result); result.push(*data); } - ImmediateToRegisterOrMemory::Word(address, data) => { + ImmediateToMemory::Word(address, data) => { result.push(opcode + 1); address.push(0, &mut result); result.push((data % 256) as u8); @@ -246,6 +246,76 @@ impl AccumulatorToMemory { } } +#[derive(Eq, PartialEq, Debug, Hash, Clone)] +pub struct SegmentToMemory { + pub source: SegmentRegister, + pub dest: EffectiveAddress, +} + +impl SegmentToMemory { + fn to_bytes(&self) -> Vec { + let mut result = Vec::with_capacity(4); + result.push(0b10001100); + + EffectiveAddress::push(&self.dest, self.source as u8, &mut result); + + result + } +} + +#[derive(Eq, PartialEq, Debug, Hash, Clone)] +pub struct MemoryToSegment { + pub dest: SegmentRegister, + pub source: EffectiveAddress, +} + +impl MemoryToSegment { + fn to_bytes(&self) -> Vec { + let mut result = Vec::with_capacity(4); + result.push(0b10001110); + + EffectiveAddress::push(&self.source, self.dest as u8, &mut result); + + result + } +} + +#[derive(Eq, PartialEq, Debug, Hash, Clone)] +pub struct SegmentToRegister { + pub source: SegmentRegister, + pub dest: Register, +} + +impl SegmentToRegister { + fn to_bytes(&self) -> Vec { + let mode = 0b11u8; + let sr = self.source as u8; + let (rm, is_wide) = self.dest.to_id(); + if !is_wide { + panic!("Tried to move a segment register to a byte register"); + } + vec![0b10001100, mode * 64 + sr * 8 + rm] + } +} + +#[derive(Eq, PartialEq, Debug, Hash, Clone)] +pub struct RegisterToSegment { + pub dest: SegmentRegister, + pub source: Register, +} + +impl RegisterToSegment { + fn to_bytes(&self) -> Vec { + let mode = 0b11u8; + let sr = self.dest as u8; + let (rm, is_wide) = self.source.to_id(); + if !is_wide { + panic!("Tried to move a byte register to a segment register"); + } + vec![0b10001110, mode * 64 + sr * 8 + rm] + } +} + #[derive(Eq, PartialEq, Debug, Hash, Clone)] pub enum MoveInstruction { /// Move a value from one register to another @@ -257,11 +327,15 @@ pub enum MoveInstruction { /// Load a literal value into a register ImmediateToRegister(ImmediateToRegister), /// Load a literal value into a register or into memory - ImmediateToRegisterOrMemory(ImmediateToRegisterOrMemory), + ImmediateToMemory(ImmediateToMemory), /// Load a value from memory into the accumulator MemoryToAccumulator(MemoryToAccumulator), /// Store a value into memory from the accumulator AccumulatorToMemory(AccumulatorToMemory), + SegmentToMemory(SegmentToMemory), + MemoryToSegment(MemoryToSegment), + SegmentToRegister(SegmentToRegister), + RegisterToSegment(RegisterToSegment), } impl Display for MoveInstruction { @@ -279,14 +353,12 @@ impl Display for MoveInstruction { MoveInstruction::ImmediateToRegister(instruction) => { f.write_fmt(format_args!("mov {}", instruction)) } - MoveInstruction::ImmediateToRegisterOrMemory(ImmediateToRegisterOrMemory::Byte( - address, - value, - )) => f.write_fmt(format_args!("mov {}, {}", address, value)), - MoveInstruction::ImmediateToRegisterOrMemory(ImmediateToRegisterOrMemory::Word( - address, - value, - )) => f.write_fmt(format_args!("mov {}, {}", address, value)), + MoveInstruction::ImmediateToMemory(ImmediateToMemory::Byte(address, value)) => { + f.write_fmt(format_args!("mov {}, {}", address, value)) + } + MoveInstruction::ImmediateToMemory(ImmediateToMemory::Word(address, value)) => { + f.write_fmt(format_args!("mov {}, {}", address, value)) + } MoveInstruction::MemoryToAccumulator(instruction) => f.write_fmt(format_args!( "mov a{}, [{}]", if instruction.is_wide { 'x' } else { 'l' }, @@ -297,6 +369,18 @@ impl Display for MoveInstruction { instruction.address, if instruction.is_wide { 'x' } else { 'l' } )), + MoveInstruction::SegmentToMemory(mov) => { + f.write_fmt(format_args!("mov {}, {}", mov.dest, mov.source)) + } + MoveInstruction::SegmentToRegister(mov) => { + f.write_fmt(format_args!("mov {}, {}", mov.dest, mov.source)) + } + MoveInstruction::RegisterToSegment(mov) => { + f.write_fmt(format_args!("mov {}, {}", mov.dest, mov.source)) + } + MoveInstruction::MemoryToSegment(mov) => { + f.write_fmt(format_args!("mov {}, {}", mov.dest, mov.source)) + } } } } @@ -307,10 +391,14 @@ impl MoveInstruction { MoveInstruction::RegMemMove(mov) => mov.to_bytes(), MoveInstruction::MemRegMove(mov) => mov.to_bytes(), MoveInstruction::ImmediateToRegister(mov) => mov.to_bytes(), - MoveInstruction::ImmediateToRegisterOrMemory(mov) => mov.to_bytes(), + MoveInstruction::ImmediateToMemory(mov) => mov.to_bytes(), MoveInstruction::MemoryToAccumulator(mov) => mov.to_bytes(), MoveInstruction::AccumulatorToMemory(mov) => mov.to_bytes(), MoveInstruction::RegRegMove(mov) => mov.to_bytes(), + MoveInstruction::MemoryToSegment(mov) => mov.to_bytes(), + MoveInstruction::SegmentToMemory(mov) => mov.to_bytes(), + MoveInstruction::SegmentToRegister(mov) => mov.to_bytes(), + MoveInstruction::RegisterToSegment(mov) => mov.to_bytes(), } } } diff --git a/sim_8086/src/register.rs b/sim_8086/src/register.rs index 8c0e237..7c7ea0d 100644 --- a/sim_8086/src/register.rs +++ b/sim_8086/src/register.rs @@ -104,6 +104,37 @@ impl Display for RegisterSubset { } } +#[derive(Eq, PartialEq, Debug, Hash, Clone, Copy)] +pub enum SegmentRegister { + Code = 1, + Data = 3, + Stack = 2, + Extra = 0, +} + +impl Display for SegmentRegister { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SegmentRegister::Code => f.write_str("cs"), + SegmentRegister::Data => f.write_str("ds"), + SegmentRegister::Stack => f.write_str("ss"), + SegmentRegister::Extra => f.write_str("es"), + } + } +} + +impl SegmentRegister { + pub const fn of_byte(b: u8) -> SegmentRegister { + match b { + 0 => SegmentRegister::Extra, + 1 => SegmentRegister::Code, + 2 => SegmentRegister::Stack, + 3 => SegmentRegister::Data, + _ => concat_panic!("Bad byte for stack register: {}", b), + } + } +} + #[derive(Eq, PartialEq, Debug, Hash, Clone)] pub enum Register { General(GeneralRegister, RegisterSubset), diff --git a/sim_8086/tests/test_computer.rs b/sim_8086/tests/test_computer.rs index ebc58c0..75867b6 100644 --- a/sim_8086/tests/test_computer.rs +++ b/sim_8086/tests/test_computer.rs @@ -21,19 +21,24 @@ mod test_computer { let decoded = Program::of_bytes(input_bytecode.as_ref().iter().cloned()); - let mut trace = "".to_owned(); + let mut trace: Vec = vec![]; for instruction in decoded.instructions { - trace.push_str(&computer.step(&instruction)); - trace.push('\n'); + trace.push(computer.step(&instruction)); } - trace.push_str("\nFinal registers:\n"); - trace.push_str(&computer.dump_register_state()); + trace.push("".to_owned()); + trace.push("Final registers:".to_owned()); + for line in computer.dump_register_state().lines() { + trace.push(line.to_string()); + } - trace.push_str("\n"); + trace.push("".to_owned()); - assert_eq!(trace, clean_trace(expected_trace)) + assert_eq!( + trace, + clean_trace(expected_trace).lines().collect::>() + ) } #[test] @@ -44,4 +49,121 @@ mod test_computer { include_str!("../../computer_enhance/perfaware/part1/listing_0043_immediate_movs.txt"); test_sim(input_bytecode, expected_trace) } + + #[test] + fn test_register_movs() { + let input_bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0044_register_movs"); + let expected_trace = + include_str!("../../computer_enhance/perfaware/part1/listing_0044_register_movs.txt"); + test_sim(input_bytecode, expected_trace) + } + + #[test] + fn test_challenge_register_movs() { + let input_bytecode = include_bytes!( + "../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs" + ); + let expected_trace = include_str!( + "../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs.txt" + ); + test_sim(input_bytecode, expected_trace) + } + + /* + #[test] + fn test_add_sub_cmp() { + let input_bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0046_add_sub_cmp"); + let expected_trace = + include_str!("../../computer_enhance/perfaware/part1/listing_0046_add_sub_cmp.txt"); + test_sim(input_bytecode, expected_trace) + } + + #[test] + fn test_challenge_flags() { + let input_bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0047_challenge_flags"); + let expected_trace = + include_str!("../../computer_enhance/perfaware/part1/listing_0047_challenge_flags.txt"); + test_sim(input_bytecode, expected_trace) + } + + #[test] + fn test_ip_register() { + let input_bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0048_ip_register"); + let expected_trace = + include_str!("../../computer_enhance/perfaware/part1/listing_0048_ip_register.txt"); + test_sim(input_bytecode, expected_trace) + } + + #[test] + fn test_conditional_jumps() { + let input_bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0049_conditional_jumps"); + let expected_trace = include_str!( + "../../computer_enhance/perfaware/part1/listing_0049_conditional_jumps.txt" + ); + test_sim(input_bytecode, expected_trace) + } + + #[test] + fn test_challenge_jumps() { + let input_bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0050_challenge_jumps"); + let expected_trace = + include_str!("../../computer_enhance/perfaware/part1/listing_0050_challenge_jumps.txt"); + test_sim(input_bytecode, expected_trace) + } + + #[test] + fn test_memory_mov() { + let input_bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0051_memory_mov"); + let expected_trace = + include_str!("../../computer_enhance/perfaware/part1/listing_0051_memory_mov.txt"); + test_sim(input_bytecode, expected_trace) + } + + #[test] + fn test_memory_add_loop() { + let input_bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0052_memory_add_loop"); + let expected_trace = + include_str!("../../computer_enhance/perfaware/part1/listing_0052_memory_add_loop.txt"); + test_sim(input_bytecode, expected_trace) + } + + #[test] + fn test_add_loop_challenge() { + let input_bytecode = include_bytes!( + "../../computer_enhance/perfaware/part1/listing_0053_add_loop_challenge" + ); + let expected_trace = include_str!( + "../../computer_enhance/perfaware/part1/listing_0053_add_loop_challenge.txt" + ); + test_sim(input_bytecode, expected_trace) + } + + #[test] + fn test_draw_rectangle() { + let input_bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0054_draw_rectangle"); + let expected_trace = + include_str!("../../computer_enhance/perfaware/part1/listing_0054_draw_rectangle.txt"); + test_sim(input_bytecode, expected_trace) + } + + #[test] + fn test_draw_rectangle_challenge() { + let input_bytecode = include_bytes!( + "../../computer_enhance/perfaware/part1/listing_0055_challenge_rectangle" + ); + let expected_trace = include_str!( + "../../computer_enhance/perfaware/part1/listing_0055_challenge_rectangle.txt" + ); + test_sim(input_bytecode, expected_trace) + } + */ } diff --git a/sim_8086/tests/test_program.rs b/sim_8086/tests/test_program.rs index e924ccb..41c83e5 100644 --- a/sim_8086/tests/test_program.rs +++ b/sim_8086/tests/test_program.rs @@ -10,7 +10,9 @@ mod test_program { ArithmeticInstruction, ArithmeticInstructionSelect, ArithmeticOperation, }, assembly, + effective_address::{EffectiveAddress, WithOffset}, instruction::Instruction, + move_instruction::{ImmediateToMemory, MoveInstruction}, program::Program, register::{GeneralRegister, Register, RegisterSubset}, }; @@ -111,8 +113,8 @@ mod test_program { { println!( "Different instruction. From disassembly: {dis} ({:?}). From our compilation: {compiled} ({:?}).", - compiled_bytes, - dis_bytes + dis_bytes, + compiled_bytes ); is_different = true; } @@ -277,30 +279,27 @@ mod test_program { test_disassembler(asm, bytecode) } - /* - We have not yet implemented the segment registers, so this test can't pass. - #[test] - fn test_challenge_register_movs_parser() { - let input_asm = include_str!( - "../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs.asm" - ); - let input_bytecode = include_bytes!( - "../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs" - ); - test_parser(input_asm, input_bytecode) - } + #[test] + fn test_challenge_register_movs_parser() { + let input_asm = include_str!( + "../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs.asm" + ); + let input_bytecode = include_bytes!( + "../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs" + ); + test_parser(input_asm, input_bytecode) + } - #[test] - fn test_challenge_register_movs_disassembler() { - let bytecode = include_bytes!( - "../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs" - ); - let asm = include_str!( - "../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs.asm" - ); - test_disassembler(asm, bytecode) - } - */ + #[test] + fn test_challenge_register_movs_disassembler() { + let bytecode = include_bytes!( + "../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs" + ); + let asm = include_str!( + "../../computer_enhance/perfaware/part1/listing_0045_challenge_register_movs.asm" + ); + test_disassembler(asm, bytecode) + } #[test] fn test_add_sub_cmp_parser() { @@ -412,4 +411,122 @@ mod test_program { allowed.insert((vec![5, 1, 0], vec![131, 192, 1])); test_disassembler_lax(asm, bytecode, allowed) } + + #[test] + fn test_memory_mov_parser() { + let input_asm = + include_str!("../../computer_enhance/perfaware/part1/listing_0051_memory_mov.asm"); + let input_bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0051_memory_mov"); + test_parser(input_asm, input_bytecode) + } + + #[test] + fn test_memory_mov_disassembler() { + let bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0051_memory_mov"); + let asm = + include_str!("../../computer_enhance/perfaware/part1/listing_0051_memory_mov.asm"); + test_disassembler(asm, bytecode) + } + + #[test] + fn test_memory_add_loop_parser() { + let input_asm = + include_str!("../../computer_enhance/perfaware/part1/listing_0052_memory_add_loop.asm"); + let input_bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0052_memory_add_loop"); + test_parser(input_asm, input_bytecode) + } + + #[test] + fn test_memory_add_loop_disassembler() { + let bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0052_memory_add_loop"); + let asm = + include_str!("../../computer_enhance/perfaware/part1/listing_0052_memory_add_loop.asm"); + test_disassembler(asm, bytecode) + } + + #[test] + fn test_add_loop_challenge_parser() { + let input_asm = include_str!( + "../../computer_enhance/perfaware/part1/listing_0053_add_loop_challenge.asm" + ); + let input_bytecode = include_bytes!( + "../../computer_enhance/perfaware/part1/listing_0053_add_loop_challenge" + ); + test_parser(input_asm, input_bytecode) + } + + #[test] + fn test_add_loop_challenge_disassembler() { + let bytecode = include_bytes!( + "../../computer_enhance/perfaware/part1/listing_0053_add_loop_challenge" + ); + let asm = include_str!( + "../../computer_enhance/perfaware/part1/listing_0053_add_loop_challenge.asm" + ); + test_disassembler(asm, bytecode) + } + + #[test] + fn test_draw_rectangle_parser() { + let input_asm = + include_str!("../../computer_enhance/perfaware/part1/listing_0054_draw_rectangle.asm"); + let input_bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0054_draw_rectangle"); + test_parser(input_asm, input_bytecode) + } + + #[test] + fn test_draw_rectangle_disassembler() { + let bytecode = + include_bytes!("../../computer_enhance/perfaware/part1/listing_0054_draw_rectangle"); + let asm = + include_str!("../../computer_enhance/perfaware/part1/listing_0054_draw_rectangle.asm"); + test_disassembler(asm, bytecode) + } + + #[test] + fn test_challenge_rectangle_parser() { + let input_asm = include_str!( + "../../computer_enhance/perfaware/part1/listing_0055_challenge_rectangle.asm" + ); + let input_bytecode = include_bytes!( + "../../computer_enhance/perfaware/part1/listing_0055_challenge_rectangle" + ); + let mut swaps = HashMap::new(); + swaps.insert( + Instruction::Move(MoveInstruction::ImmediateToMemory(ImmediateToMemory::Byte( + EffectiveAddress::Bx(WithOffset::WithU8((), 61 * 4 + 1)), + 255, + ))), + Instruction::Move(MoveInstruction::ImmediateToMemory(ImmediateToMemory::Byte( + EffectiveAddress::Bx(WithOffset::WithU16((), 61 * 4 + 1)), + 255, + ))), + ); + test_parser_lax(input_asm, input_bytecode, swaps) + } + + #[test] + fn test_challenge_rectangle_disassembler() { + let bytecode = include_bytes!( + "../../computer_enhance/perfaware/part1/listing_0055_challenge_rectangle" + ); + let asm = include_str!( + "../../computer_enhance/perfaware/part1/listing_0055_challenge_rectangle.asm" + ); + let mut allowed = HashSet::new(); + // We implemented `mov [bx + 61*4 + 1], 255` using "immediate to memory, 8 bit displacement", + // taking only four bytes; + // in this example, Casey implemented it using "immediate to memory, 16 bit displacement", + // which takes five. + // The manual is explicit that this situation is allowed: + // If the displacement is only a single byte, the 8086 or 8088 automatically sign-extends + // this quantity to 16-bits before using the information in further address calculations. + allowed.insert((vec![198, 71, 245, 255], vec![198, 135, 245, 0, 255])); + test_disassembler_lax(asm, bytecode, allowed) + } }