diff options
Diffstat (limited to 'src/instructions.rs')
-rw-r--r-- | src/instructions.rs | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/src/instructions.rs b/src/instructions.rs new file mode 100644 index 0000000..8fa409c --- /dev/null +++ b/src/instructions.rs @@ -0,0 +1,464 @@ +use core::panic; + +use crate::interpreter::{MemAddress, StackAddress}; + +#[derive(Debug)] +pub struct Instruction { + pub operation: OpCode, + pub operands: Vec<Operand> +} + +enum Nybble { + High, + Low, +} + +#[derive(PartialEq, Debug)] +pub enum Operand { + Constant(i32), + PopStack, + FrameIndirect(StackAddress), + MemIndirect(MemAddress), + MemRamIndirect(MemAddress), +} + +impl Instruction { + pub fn read<'a, I>(bytes: &mut I) -> (Instruction, u32) + where + I: IntoIterator<Item = &'a u8> + Copy + { + let mut bytes = bytes.into_iter().enumerate().peekable(); + let instruction_length = OpCode::get_length(*bytes.peek().unwrap().1); + let mut bytes = bytes.into_iter(); + let instruction_bytes = (0..instruction_length).into_iter().map(|_n| *bytes.next().unwrap().1).collect(); + let operation = OpCode::from_bytes(instruction_bytes); + + let operand_count = operation.get_operand_count(); + println!("Parsing {operand_count} operands for {operation:?}"); + let operand_bytes = (operand_count + 1) / 2; + let skip_last_half = operand_bytes != operand_count / 2; + println!("Reading {operand_bytes} bytes for operands and {}", if skip_last_half {"ignoring the last 4 bits"} else {"keeping the last 4 bits"}); + let operand_bytes = (0..operand_bytes).into_iter().map(|_n| *bytes.next().unwrap().1); + println!("- check: byte array is {} long", operand_bytes.len()); + let halfbytes : Vec<_> = operand_bytes.flat_map(|byte| vec![(byte, Nybble::Low), (byte, Nybble::High)]).collect(); + println!("- check: halfbyte array is {} long", halfbytes.len()); + + // Just ensure other borrows to the byte array have closed by rebinding it + // This verifies we're at the operand data position + let mut data_bytes = bytes; + + let mut operands : Vec<Operand> = halfbytes.iter().map(|(byte, nybble)| { + let value = match nybble { + Nybble::High => byte >> 4, + Nybble::Low => byte & 0x0f, + }; + + match value { + 0 => Operand::Constant(0), + 1 => Operand::Constant(i8::from_be_bytes([*data_bytes.next().unwrap().1]) as i32), + 2 => Operand::Constant(i16::from_be_bytes([*data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1]) as i32), + 3 => Operand::Constant(i32::from_be_bytes([*data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1])), + 4 => panic!("Invalid operand addressing mode: {value:x}"), + 5 => Operand::MemIndirect(u32::from_be_bytes([0, 0, 0, *data_bytes.next().unwrap().1])), + 6 => Operand::MemIndirect(u32::from_be_bytes([0, 0, *data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1])), + 7 => Operand::MemIndirect(u32::from_be_bytes([*data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1])), + 8 => Operand::PopStack, + 9 => Operand::FrameIndirect(u32::from_be_bytes([0, 0, 0, *data_bytes.next().unwrap().1])), + 0xa => Operand::FrameIndirect(u32::from_be_bytes([0, 0, *data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1])), + 0xb => Operand::FrameIndirect(u32::from_be_bytes([*data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1])), + 0xc => panic!("Invalid operand addressing mode: {value:x}"), + 0xd => Operand::MemRamIndirect(u32::from_be_bytes([0, 0, 0, *data_bytes.next().unwrap().1])), + 0xe => Operand::MemRamIndirect(u32::from_be_bytes([0, 0, *data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1])), + 0xf => Operand::MemRamIndirect(u32::from_be_bytes([*data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1, *data_bytes.next().unwrap().1])), + _ => unreachable!() + } + }).collect(); + + // Should only trim it off if it was all 0 + if skip_last_half && operands.len() > 0 { + assert!(*operands.last().unwrap() == Operand::Constant(0)); + operands.remove(operands.len() - 1); + } + + // Return instruction along with the number of bytes we've read + (Instruction { + operation, + operands, + }, data_bytes.peek().unwrap().0 as u32) + } +} + +impl OpCode { + fn get_length(first_byte: u8) -> usize { + let length = match first_byte >> 6 { + 0b00 | 0b01 => 1, + 0b10 => 2, + 0b11 => 4, + _ => unreachable!() + }; + println!("Opcode first byte: {first_byte:02x}, instruction length: {length} bytes"); + return length + } + + fn from_bytes(bytes: Vec<u8>) -> Self { + let (padded_bytes, mask) = match bytes.len() { + 1 => ([0,0,0,bytes[0]], 0), + 2 => ([0,0,bytes[0],bytes[1]], 0x8000), + 4 => ([bytes[0],bytes[1],bytes[2],bytes[3]], 0xC0000000), + _ => panic!("Invalid opcode byte array") + }; + println!("Read {} bytes: {bytes:?}", bytes.len()); + + let instruction = u32::from_be_bytes(padded_bytes) & !mask; + println!("Transmuting opcode 0x{instruction:03x}"); + return unsafe { std::mem::transmute(instruction) } + } + + fn get_operand_count(&self) -> usize { + match *self { + // Misc + OpCode::Nop => 0, + OpCode::Gestalt => 3, + OpCode::Debugtrap => 1, + OpCode::Glk => 3, + + // Integer math + OpCode::Add => 3, + OpCode::Sub => 3, + OpCode::Mul => 3, + OpCode::Div => 3, + OpCode::Mod => 3, + OpCode::Neg => 2, + OpCode::Bitand => 3, + OpCode::Bitor => 3, + OpCode::Bitxor => 3, + OpCode::Bitnot => 3, + OpCode::Shiftl => 3, + OpCode::Sshiftr => 3, + OpCode::Ushiftr => 3, + + // Branches + OpCode::Jump => 1, + OpCode::Jz => 2, + OpCode::Jnz => 2, + OpCode::Jeq => 3, + OpCode::Jne => 3, + OpCode::Jlt => 3, + OpCode::Jge => 3, + OpCode::Jgt => 3, + OpCode::Jle => 3, + OpCode::Jltu => 3, + OpCode::Jgeu => 3, + OpCode::Jgtu => 3, + OpCode::Jleu => 3, + OpCode::Jumpabs => 1, + + // Moving data + OpCode::Copy => 2, + OpCode::Copys => 2, + OpCode::Copyb => 2, + OpCode::Sexs => 2, + OpCode::Sexb => 2, + + // Array data + OpCode::Aload => 3, + OpCode::Aloads => 3, + OpCode::Aloadb => 3, + OpCode::Aloadbit => 3, + OpCode::Astore => 3, + OpCode::Astores => 3, + OpCode::Astoreb => 3, + OpCode::Astorebit => 3, + + // Stack operations + OpCode::Stkcount => 1, + OpCode::Stkpeek => 2, + OpCode::Stkswap => 0, + OpCode::Stkroll => 2, + OpCode::Stkcopy => 1, + + // Functions + OpCode::Call => 3, + OpCode::Callf => 2, + OpCode::Callfi => 3, + OpCode::Callfii => 4, + OpCode::Callfiii => 5, + OpCode::Return => 1, + OpCode::Tailcall => 2, + + // Continuations + OpCode::Catch => 2, + OpCode::Throw => 2, + + // Memory + OpCode::Getmemsize => 1, + OpCode::Setmemsize => 2, + OpCode::Malloc => 2, + OpCode::Mfree => 1, + + // Game state + OpCode::Quit => 0, + OpCode::Restart => 0, + OpCode::Save => 2, + OpCode::Restore => 2, + OpCode::Saveundo => 1, + OpCode::Restoreundo => 1, + OpCode::Hasundo => 1, + OpCode::Discardundo => 0, + OpCode::Protect => 2, + OpCode::Verify => 1, + + // Output + OpCode::Getiosys => 2, + OpCode::Setiosys => 2, + OpCode::Streamchar => 1, + OpCode::Streamunichar => 1, + OpCode::Streamnum => 1, + OpCode::Streamstr => 1, + OpCode::Getstringtbl => 1, + OpCode::Setstringtbl => 1, + + // Floating point math + OpCode::Numtof => 2, + OpCode::Ftonumz => 2, + OpCode::Ftonumn => 2, + OpCode::Fadd => 3, + OpCode::Fsub => 3, + OpCode::Fmul => 3, + OpCode::Fdiv => 3, + OpCode::Fmod => 4, + OpCode::Ceil => 2, + OpCode::Floor => 2, + OpCode::Sqrt => 2, + OpCode::Exp => 2, + OpCode::Log => 2, + OpCode::Pow => 3, + OpCode::Sin => 2, + OpCode::Cos => 2, + OpCode::Tan => 2, + OpCode::Asin => 2, + OpCode::Acos => 2, + OpCode::Atan => 2, + OpCode::Atan2 => 3, + + // Double precision math + OpCode::Numtod => 3, + OpCode::Dtonumz => 3, + OpCode::Dtonumn => 3, + OpCode::Ftod => 3, + OpCode::Dtof => 3, + OpCode::Dadd => 6, + OpCode::Dsub => 6, + OpCode::Dmul => 6, + OpCode::Ddiv => 6, + OpCode::Dmodr => 6, + OpCode::Dmodq => 6, + OpCode::Dceil => 4, + OpCode::Dfloor => 4, + OpCode::Dsqrt => 4, + OpCode::Dexp => 4, + OpCode::Dlog => 4, + OpCode::Dpow => 6, + OpCode::Dsin => 4, + OpCode::Dcos => 4, + OpCode::Dtan => 4, + OpCode::Dasin => 4, + OpCode::Dacos => 4, + OpCode::Datan => 4, + OpCode::Datan2 => 4, + + // Floating point comparisons + OpCode::Jisnan => 2, + OpCode::Jisinf => 2, + OpCode::Jfeq => 4, + OpCode::Jfne => 4, + OpCode::Jflt => 3, + OpCode::Jfle => 3, + OpCode::Jfgt => 3, + OpCode::Jfge => 3, + + // Double precision comparisons + OpCode::Jdisnan => 3, + OpCode::Jdisinf => 3, + OpCode::Jdeq => 7, + OpCode::Jdne => 7, + OpCode::Jdlt => 5, + OpCode::Jdle => 5, + OpCode::Jdgt => 5, + OpCode::Jdge => 5, + + // RNG + OpCode::Random => 2, + OpCode::Setrandom => 1, + + // Block copy and clear + OpCode::Mzero => 2, + OpCode::Mcopy => 3, + + // Searching + OpCode::Linearsearch => 8, + OpCode::Binarysearch => 8, + OpCode::Linkedsearch => 7, + + // Accelerated functions + OpCode::Accelfunc => 2, + OpCode::Accelparam => 2, + } + } +} + + +#[repr(u32)] +#[derive(Debug)] +pub enum OpCode { + Nop = 0x00, + Add = 0x10, + Sub = 0x11, + Mul = 0x12, + Div = 0x13, + Mod = 0x14, + Neg = 0x15, + Bitand = 0x18, + Bitor = 0x19, + Bitxor = 0x1A, + Bitnot = 0x1B, + Shiftl = 0x1C, + Sshiftr = 0x1D, + Ushiftr = 0x1E, + Jump = 0x20, + Jz = 0x22, + Jnz = 0x23, + Jeq = 0x24, + Jne = 0x25, + Jlt = 0x26, + Jge = 0x27, + Jgt = 0x28, + Jle = 0x29, + Jltu = 0x2A, + Jgeu = 0x2B, + Jgtu = 0x2C, + Jleu = 0x2D, + Call = 0x30, + Return = 0x31, + Catch = 0x32, + Throw = 0x33, + Tailcall = 0x34, + Copy = 0x40, + Copys = 0x41, + Copyb = 0x42, + Sexs = 0x44, + Sexb = 0x45, + Aload = 0x48, + Aloads = 0x49, + Aloadb = 0x4A, + Aloadbit = 0x4B, + Astore = 0x4C, + Astores = 0x4D, + Astoreb = 0x4E, + Astorebit = 0x4F, + Stkcount = 0x50, + Stkpeek = 0x51, + Stkswap = 0x52, + Stkroll = 0x53, + Stkcopy = 0x54, + Streamchar = 0x70, + Streamnum = 0x71, + Streamstr = 0x72, + Streamunichar = 0x73, + Gestalt = 0x100, + Debugtrap = 0x101, + Getmemsize = 0x102, + Setmemsize = 0x103, + Jumpabs = 0x104, + Random = 0x110, + Setrandom = 0x111, + Quit = 0x120, + Verify = 0x121, + Restart = 0x122, + Save = 0x123, + Restore = 0x124, + Saveundo = 0x125, + Restoreundo = 0x126, + Protect = 0x127, + Hasundo = 0x128, + Discardundo = 0x129, + Glk = 0x130, + Getstringtbl = 0x140, + Setstringtbl = 0x141, + Getiosys = 0x148, + Setiosys = 0x149, + Linearsearch = 0x150, + Binarysearch = 0x151, + Linkedsearch = 0x152, + Callf = 0x160, + Callfi = 0x161, + Callfii = 0x162, + Callfiii = 0x163, + Mzero = 0x170, + Mcopy = 0x171, + Malloc = 0x178, + Mfree = 0x179, + Accelfunc = 0x180, + Accelparam = 0x181, + Numtof = 0x190, + Ftonumz = 0x191, + Ftonumn = 0x192, + Ceil = 0x198, + Floor = 0x199, + Fadd = 0x1A0, + Fsub = 0x1A1, + Fmul = 0x1A2, + Fdiv = 0x1A3, + Fmod = 0x1A4, + Sqrt = 0x1A8, + Exp = 0x1A9, + Log = 0x1AA, + Pow = 0x1AB, + Sin = 0x1B0, + Cos = 0x1B1, + Tan = 0x1B2, + Asin = 0x1B3, + Acos = 0x1B4, + Atan = 0x1B5, + Atan2 = 0x1B6, + Jfeq = 0x1C0, + Jfne = 0x1C1, + Jflt = 0x1C2, + Jfle = 0x1C3, + Jfgt = 0x1C4, + Jfge = 0x1C5, + Jisnan = 0x1C8, + Jisinf = 0x1C9, + Numtod = 0x200, + Dtonumz = 0x201, + Dtonumn = 0x202, + Ftod = 0x203, + Dtof = 0x204, + Dceil = 0x208, + Dfloor = 0x209, + Dadd = 0x210, + Dsub = 0x211, + Dmul = 0x212, + Ddiv = 0x213, + Dmodr = 0x214, + Dmodq = 0x215, + Dsqrt = 0x218, + Dexp = 0x219, + Dlog = 0x21A, + Dpow = 0x21B, + Dsin = 0x220, + Dcos = 0x221, + Dtan = 0x222, + Dasin = 0x223, + Dacos = 0x224, + Datan = 0x225, + Datan2 = 0x226, + Jdeq = 0x230, + Jdne = 0x231, + Jdlt = 0x232, + Jdle = 0x233, + Jdgt = 0x234, + Jdge = 0x235, + Jdisnan = 0x238, + Jdisinf = 0x239, +} |