instruction decoding and getting started with main terp state

main
aprzn 7 months ago
parent 123792a0df
commit 4f4e27e03c

@ -1,5 +1,12 @@
use zingprocmacros::make_instruction_type;
use crate::encoding::text::ZsciiString;
use crate::{encoding::text::{decode_zchars, ZsciiString}, utils::simplecursor::SimpleCursor};
trait Opcode: Sized {
fn has_store(inst_no: u8) -> bool;
fn has_branch(inst_no: u8) -> bool;
fn has_text(inst_no: u8) -> bool;
fn from_number(inst_no: u8, store: Option<u8>, branch: Option<Branch>, text: Option<ZsciiString>) -> Result<Self, InstructionDecodeError>;
}
pub enum Instruction {
Short0Op {
@ -27,17 +34,29 @@ pub enum Instruction {
},
}
pub enum VariableOperands {
TwoOp(Operand, Operand),
VarOp(Vec<Operand>),
}
pub enum Operand {
SmallConstant(u8),
LargeConstant(u16),
Variable(u8),
}
impl Operand {
fn make_small(input: &[u8; 1]) -> Self {Self::SmallConstant(input[0])}
fn make_large(input: &[u8; 2]) -> Self {Self::LargeConstant(u16::from_be_bytes(*input))}
fn make_var(input: &[u8; 1]) -> Self {Self::Variable(input[0])}
}
enum BranchTarget {
ReturnsFalse,
ReturnsTrue,
ShiftedOffset(i16),
}
struct Branch {
branch_if_true: bool,
target: BranchTarget
}
make_instruction_type!(Short0OpOpcode {
0x0 Rtrue,
0x1 Rfalse,
@ -72,7 +91,7 @@ make_instruction_type!(Short1OpOpcode {
0xF Call1n,
});
make_instruction_type!(Long2OpOpcode {
make_instruction_type!(Variable2OpOpcode {
1 Je br,
2 Jl br,
3 Jg br,
@ -103,7 +122,7 @@ make_instruction_type!(Long2OpOpcode {
28 Throw,
});
type Variable2OpOpcode = Long2OpOpcode;
type Long2OpOpcode = Variable2OpOpcode;
make_instruction_type!(VariableVarOpOpcode {
0 CallVs,
@ -155,50 +174,190 @@ make_instruction_type!(ExtendedVarOpOpcode {
pub enum InstructionDecodeError {
InvalidOpcodeNum(u8),
/// Should not occur unless there is an error in Zing
MissingStore,
/// Should not occur unless there is an error in Zing
MissingBranch,
/// Should not occur unless there is an error in Zing
MissingText,
InsufficientOperands,
}
/// Returns: Some((instruction, instruction_length)) if instruction is valid, None otherwise
pub fn decode_instruction(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
// TODO
fn decode_short(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
let mut cursor = address;
let base = memory[cursor];
cursor += 1;
pub fn decode_instruction(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
struct HasStoreBranchText { store: bool, branch: bool, text: bool }
struct PotentialStoreBranchText {
store: Option<u8>,
branch: Option<Branch>,
text: Option<ZsciiString>,
}
fn decode_store_branch_text<T: Opcode>(
cursor: &mut SimpleCursor,
opcode_num: u8
) -> PotentialStoreBranchText {
let required = HasStoreBranchText {
store: T::has_store(opcode_num),
branch: T::has_branch(opcode_num),
text: T::has_text(opcode_num)
};
let store = required.store.then(|| cursor.read_const::<1>().unwrap()[0]);
let branch = required.branch.then(|| {
let &[branch_first_byte] = cursor.read_const::<1>().unwrap();
let branch_if_true = branch_first_byte & 0x80 != 0;
let branch_target_raw_high = branch_first_byte & 0x3f;
let branch_target_raw = if branch_first_byte & 0x40 == 0 {
// one byte
branch_target_raw_high as i16
} else {
// two bytes
let &[branch_target_raw_low] = cursor.read_const::<1>().unwrap();
let branch_target_raw_unsigned = i16::from_be_bytes([branch_target_raw_high, branch_target_raw_low]);
(branch_target_raw_unsigned << 2) / 4
};
match branch_target_raw {
0 => Branch { branch_if_true, target: BranchTarget::ReturnsFalse },
1 => Branch { branch_if_true, target: BranchTarget::ReturnsTrue },
n => Branch { branch_if_true, target: BranchTarget::ShiftedOffset(n - 2) },
}
});
// TODO: switch away from cursor.buf(); should not assume that the cursor is looking at the
// entire slice
let extra_cursor = cursor.clone();
let mem = extra_cursor.buf();
let alphabet_addr = u16::from_be_bytes([mem[0x08], mem[0x09]]) as usize;
let abbreviations_addr = u16::from_be_bytes([mem[0x18], mem[0x18]]) as usize;
let text = required.branch.then(|| decode_zchars(cursor, alphabet_addr, abbreviations_addr, mem).unwrap());
PotentialStoreBranchText { store, branch, text }
}
fn decode_short(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
let &[base] = cursor.read_const::<1>().unwrap();
let opcode_num = base & 0x0f;
let has_operand = base & 0x30 != 0x30;
if has_operand {
todo!("get operand and define store/branch/text if necessary");
// let instruction = Short1OpOpcode::from_number(opcode_num, store, branch, text).ok_or(InstructionDecodeError::InvalidOpcodeNum(opcode_num))?;
todo!()
let operand_desc = (base >> 4) & 0b11;
let operand = match operand_desc {
0b00 => Some(Operand::make_large(cursor.read_const().unwrap())),
0b01 => Some(Operand::make_small(cursor.read_const().unwrap())),
0b10 => Some(Operand::make_var(cursor.read_const().unwrap())),
0b11 => None,
_ => unreachable!()
};
let PotentialStoreBranchText { store, branch, text } = if operand.is_none() {
decode_store_branch_text::<Short0OpOpcode>(cursor, opcode_num)
} else {
todo!()
decode_store_branch_text::<Short1OpOpcode>(cursor, opcode_num)
};
if let Some(operand) = operand {
Short1OpOpcode::from_number(opcode_num, store, branch, text)
.map(|opcode| Instruction::Short1Op { operand, opcode })
} else {
Short0OpOpcode::from_number(opcode_num, store, branch, text)
.map(|opcode| Instruction::Short0Op { opcode })
}
}
// TODO
fn decode_long(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
todo!()
fn decode_long(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
enum OperandType { SmallConst, Var }
impl From<bool> for OperandType {
fn from(b: bool) -> Self {if b {Self::Var} else {Self::SmallConst}}
}
let &[base] = cursor.read_const().unwrap();
let opcode_num = base & 0x1f;
let op1_type = OperandType::from(base & 0x40 != 0);
let op2_type = OperandType::from(base & 0x20 != 0);
let operand_1 = match op1_type {
OperandType::SmallConst => Operand::make_small(cursor.read_const().unwrap()),
OperandType::Var => Operand::make_var(cursor.read_const().unwrap()),
};
let operand_2 = match op2_type {
OperandType::SmallConst => Operand::make_small(cursor.read_const().unwrap()),
OperandType::Var => Operand::make_var(cursor.read_const().unwrap()),
};
let PotentialStoreBranchText { store, branch, text } = decode_store_branch_text::<Long2OpOpcode>(cursor, opcode_num);
Long2OpOpcode::from_number(opcode_num, store, branch,text)
.map(|opcode| Instruction::Long2Op { operands: (operand_1, operand_2), opcode })
}
// TODO
fn decode_extended(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
todo!()
fn decode_extended(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
let _ = cursor.read_const::<1>();
let &[opcode_num] = cursor.read_const().unwrap();
let &[op_descs] = cursor.read_const().unwrap();
let operands = {
let mut operands = vec![];
for i in 0..4 {
match (op_descs >> (6 - 2 * i)) & 0b11 {
0b00 => operands.push(Operand::make_large(cursor.read_const().unwrap())),
0b01 => operands.push(Operand::make_small(cursor.read_const().unwrap())),
0b10 => operands.push(Operand::make_var(cursor.read_const().unwrap())),
0b11 => (),
_ => unreachable!()
};
}
operands
};
let PotentialStoreBranchText { store, branch, text } = decode_store_branch_text::<ExtendedVarOpOpcode>(cursor, opcode_num);
ExtendedVarOpOpcode::from_number(opcode_num, store, branch, text)
.map(|opcode| Instruction::ExtendedVarOp { operands, opcode })
}
// TODO
fn decode_variable(memory: &[u8], address: usize) -> Result<(Instruction, usize), InstructionDecodeError> {
todo!()
fn decode_variable(cursor: &mut SimpleCursor) -> Result<Instruction, InstructionDecodeError> {
const CALL_VS2_OPCODE_NUM: u8 = 12;
const CALL_VN2_OPCODE_NUM: u8 = 26;
let &[base] = cursor.read_const().unwrap();
let opcode_num = base & 0x1f;
let is_varop = base & 0x20 != 0;
if is_varop {
let mut operands = vec![];
let num_operand_desc_bytes =
if opcode_num == CALL_VN2_OPCODE_NUM
|| opcode_num == CALL_VS2_OPCODE_NUM
{2} else {1};
for _ in 0..num_operand_desc_bytes {
let &[op_descs] = cursor.read_const().unwrap();
for i in 0..4 {
match (op_descs >> (6 - 2 * i)) & 0b11 {
0b00 => operands.push(Operand::make_large(cursor.read_const().unwrap())),
0b01 => operands.push(Operand::make_small(cursor.read_const().unwrap())),
0b10 => operands.push(Operand::make_var(cursor.read_const().unwrap())),
0b11 => (),
_ => unreachable!()
};
}
}
let PotentialStoreBranchText { store, branch, text } = decode_store_branch_text::<VariableVarOpOpcode>(cursor, opcode_num);
VariableVarOpOpcode::from_number(opcode_num, store, branch, text)
.map(|opcode| Instruction::VariableVarOp { operands, opcode })
} else {
let &[op_descs] = cursor.read_const().unwrap();
let op0 = match (op_descs >> 6) & 0b11 {
0b00 => Operand::make_large(cursor.read_const().unwrap()),
0b01 => Operand::make_small(cursor.read_const().unwrap()),
0b10 => Operand::make_var(cursor.read_const().unwrap()),
0b11 => return Err(InstructionDecodeError::InsufficientOperands),
_ => unreachable!()
};
let op1 = match (op_descs >> 4) & 0b11 {
0b00 => Operand::make_large(cursor.read_const().unwrap()),
0b01 => Operand::make_small(cursor.read_const().unwrap()),
0b10 => Operand::make_var(cursor.read_const().unwrap()),
0b11 => return Err(InstructionDecodeError::InsufficientOperands),
_ => unreachable!()
};
let operands = (op0, op1);
let PotentialStoreBranchText { store, branch, text } = decode_store_branch_text::<Variable2OpOpcode>(cursor, opcode_num);
Variable2OpOpcode::from_number(opcode_num, store, branch, text)
.map(|opcode| Instruction::Variable2Op { operands, opcode })
}
}
if memory[address] == 190 {
decode_extended(memory, address)
} else if memory[address] & 0xC0 == 0xC0 {
decode_variable(memory, address)
} else if memory[address] & 0xC0 == 0x80 {
decode_short(memory, address)
let &[first_byte] = cursor.peek_const::<1>().unwrap();
if first_byte == 190 {
decode_extended(cursor)
} else if first_byte & 0xC0 == 0xC0 {
decode_variable(cursor)
} else if first_byte & 0xC0 == 0x80 {
decode_short(cursor)
} else {
decode_long(memory, address)
decode_long(cursor)
}
}

@ -20,7 +20,7 @@ fn zscii_from_bytes(bytes: &[u8]) -> ZsciiString {
pub fn decode_zchars(
zchars: &mut SimpleCursor,
alphabet_table_addr: usize,
abbreviations_table: usize,
abbreviations_table_addr: usize,
memory: &[u8],
) -> Option<ZsciiString> {
fn get_from_alphabet(alphabet_number: usize, ZChar(codepoint): ZChar, alphabet_table: Option<&[u8]>) -> ZsciiChar {

@ -2,6 +2,7 @@
mod encoding;
mod utils;
mod state;
use std::fs::File;

@ -0,0 +1,27 @@
pub struct Z5 {
memory: [u8; 128 * 1024],
pc: usize,
call_stack: Vec<StackFrame>,
}
pub enum ReturnTarget {
ReturnIllegal,
Discard,
/// note that writing to a local variable targets the local on the stack frame
/// being returned *to*, not the stack frame being returned *from*, as the latter
/// is discarded during the return
WriteVariable(u8),
}
pub struct StackFrame {
stack: Vec<u16>,
// length must be no greater than 15
locals: Vec<u16>,
return_address: usize,
}
impl Z5 {
pub fn run_instruction(&mut self) {
todo!()
}
}

@ -1,5 +1,3 @@
use std::ops::Sub;
#[derive(Clone)]
pub struct SimpleCursor<'a> {
buffer: &'a [u8],
@ -11,6 +9,10 @@ impl<'a> SimpleCursor<'a> {
Self { buffer, index: 0 }
}
pub fn new_with_index(buffer: &'a [u8], index: usize) -> Self {
Self { buffer, index }
}
pub fn buffer_length(&self) -> usize { self.buffer.len() }
fn remaining_length(&self) -> usize {
@ -88,7 +90,7 @@ impl<'a> SimpleCursor<'a> {
pub fn buf(&self) -> &[u8] { self.buffer }
pub fn idx(&self) -> usize { self.index }
pub fn index(&self) -> usize { self.index }
}
impl<'a> Iterator for SimpleCursor<'a> {

@ -69,7 +69,7 @@ pub fn make_instruction_type(tokens: proc_macro::TokenStream) -> proc_macro::Tok
quote!{#name}
} else {
let store = store.map(|_| quote! { store: u8, });
let branch = branch.map(|_| quote! { branch: u16, });
let branch = branch.map(|_| quote! { branch: Branch, });
let text = text.map(|_| quote! { text: ZsciiString, });
quote! { #name { #store #branch #text } }
}
@ -94,44 +94,44 @@ pub fn make_instruction_type(tokens: proc_macro::TokenStream) -> proc_macro::Tok
let constructor_function_branches = instruction_type.entries.iter().map(|InstructionEntry { number, name, store, branch, text }| {
if store.is_none() && branch.is_none() && text.is_none() {
quote!{ #number => ::std::option::Option::Some(Self::#name) }
quote!{ #number => ::std::result::Result::Ok(Self::#name) }
} else {
let store = store.map(|_| quote! { store: store?, });
let branch = branch.map(|_| quote! { branch: branch?, });
let text = text.map(|_| quote! { text: text?, });
quote! { #number => ::std::option::Option::Some(Self::#name {#store #branch #text}) }
let store = store.map(|_| quote! { store: store.ok_or(InstructionDecodeError::MissingStore)?, });
let branch = branch.map(|_| quote! { branch: branch.ok_or(InstructionDecodeError::MissingBranch)?, });
let text = text.map(|_| quote! { text: text.ok_or(InstructionDecodeError::MissingText)?, });
quote! { #number => ::std::result::Result::Ok(Self::#name {#store #branch #text}) }
}
});
quote! {
pub enum #type_name { #(#initial_enum_variants),* }
impl #type_name {
pub fn has_store(inst_no: u8) -> bool {
impl Opcode for #type_name {
fn has_store(inst_no: u8) -> bool {
match inst_no {
#(#has_store_branches,)*
_ => false
}
}
pub fn has_branch(inst_no: u8) -> bool {
fn has_branch(inst_no: u8) -> bool {
match inst_no {
#(#has_branch_branches,)*
_ => false
}
}
pub fn has_text(inst_no: u8) -> bool {
fn has_text(inst_no: u8) -> bool {
match inst_no {
#(#has_text_branches,)*
_ => false
}
}
pub fn from_number(inst_no: u8, store: Option<u8>, branch: Option<u16>, text: Option<ZsciiString>) -> Option<Self> {
fn from_number(inst_no: u8, store: Option<u8>, branch: Option<Branch>, text: Option<ZsciiString>) -> Result<Self, InstructionDecodeError> {
match inst_no {
#(#constructor_function_branches,)*
_ => None
_ => Err(InstructionDecodeError::InvalidOpcodeNum(inst_no))
}
}
}

Loading…
Cancel
Save