use core::panic; use crate::interpreter::{MemAddress, StackAddress}; #[derive(Debug)] pub struct Instruction { pub operation: OpCode, pub operands: Vec } 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 + 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 = 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) -> 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, }