pub mod constants;
mod core;
pub mod instructions;
use self::core::OperandType;
use constants::{default_values, operand_indices, register_indices};
use instructions::{SimpleInstructionFunction, VectorMode, NO_MEMORY};
use gpcas_isa::{format_c_string, instruction_flags, Emulator, Instruction, OperandStorage};
use std::{
io::Write,
sync::atomic::{AtomicUsize, Ordering},
};
pub struct ForwardComEmulator {
ip: usize,
start_address: usize,
instruction_count: AtomicUsize,
retired_instruction_count: AtomicUsize,
memory: Vec<u8>,
call_stack: Vec<usize>,
decoder: core::Decoder,
operands: OperandStorage,
registers: RegisterFile,
output_print_buffer: String,
ip_base: usize,
file_handles: Vec<Option<std::fs::File>>,
}
pub struct EmulatorInstruction {
pub core: Instruction,
pub function: SimpleInstructionFunction,
pub vector_mode: VectorMode,
pub operand_type: OperandType,
pub operand_size: usize,
pub option_bits: u8,
pub special_output: bool,
pub valid: bool,
}
pub struct RegisterFile {
pub general_purpose: [u64; 32],
pub vector: Vec<u8>,
pub vector_lengths: [usize; 32],
pub special: [u64; 32],
max_vector_size: usize,
}
mod sys_call_ids {
pub const EXIT: u32 = 0x10;
pub const PRINT: u32 = 0x103;
pub const FILE_PRINT: u32 = 0x104;
pub const FILE_OPEN: u32 = 0x110;
pub const FILE_CLOSE: u32 = 0x111;
pub const FILE_WRITE: u32 = 0x113;
}
impl ForwardComEmulator {
pub fn new(executable_data: Vec<u8>, max_vector_size: usize) -> ForwardComEmulator {
debug_assert!(max_vector_size >= 128);
debug_assert!(max_vector_size % 8 == 0);
let program = crate::program::load_forwardcom_elf(&executable_data).unwrap();
let mut registers = RegisterFile::new(max_vector_size / 8);
registers.general_purpose[register_indices::STACK_POINTER] = program.stack_address as u64;
registers.special[register_indices::NUMCONTR] = default_values::NUMCONTR;
registers.special[register_indices::THREADP] = program.threadp;
registers.special[register_indices::DATAP] = program.datap;
ForwardComEmulator {
ip: program.start_address,
start_address: program.start_address,
instruction_count: AtomicUsize::new(0),
retired_instruction_count: AtomicUsize::new(0),
memory: program.data,
call_stack: Vec::new(),
decoder: core::Decoder::default(),
operands: OperandStorage::new(max_vector_size / 8, 32),
registers,
output_print_buffer: String::new(),
ip_base: program.ip_base as usize,
file_handles: Vec::new(),
}
}
pub fn handle_sys_call<const MEMORY: usize>(
&mut self,
instruction: &mut EmulatorInstruction,
_offset: usize,
) {
let id_offset = if MEMORY == NO_MEMORY { 0 } else { 2 };
let function_id = if let OperandType::I64 = instruction.operand_type {
self.operands.get_u32(operand_indices::INPUT1 + id_offset)
} else {
self.operands.get_u16(operand_indices::INPUT1 + id_offset) as u32
};
match function_id {
sys_call_ids::EXIT => {
log::info!(
"Program exit with code {}",
self.registers.general_purpose[0]
);
instruction.core.flags |= instruction_flags::TERMINATE;
}
sys_call_ids::PRINT => {
let string_start = self.registers.general_purpose[0] as usize;
if let Some(index) = self.memory[string_start..]
.iter()
.position(|&char| char == 0)
{
self.output_print_buffer.push_str(
format_c_string(
&String::from_utf8_lossy(
&self.memory[string_start..string_start + index],
),
&self.memory[self.registers.general_purpose[1] as usize..],
)
.unwrap_or_else(|e| format!("Error printing: {e}"))
.as_str(),
);
} else {
log::error!("The string to print is not zero-terminated.");
instruction.valid = false;
}
}
sys_call_ids::FILE_PRINT => {
if let Err(e) = self.file_print() {
log::error!("Error printing to file: {}", e);
instruction.valid = false;
}
}
sys_call_ids::FILE_OPEN => {
if let Err(e) = self.file_open() {
log::error!("Error opening file: {}", e);
instruction.valid = false;
}
}
sys_call_ids::FILE_CLOSE => {
let file_id = self.registers.general_purpose[0] as usize;
if let Some(file) = self.file_handles.get_mut(file_id) {
file.take();
self.registers.general_purpose[0] = 0;
} else {
self.registers.general_purpose[0] = -1i64 as u64;
}
}
sys_call_ids::FILE_WRITE => {
let pointer = self.registers.general_purpose[0] as usize;
let size = self.registers.general_purpose[1] as usize;
let count = self.registers.general_purpose[2] as usize;
let file_id = self.registers.general_purpose[3] as usize;
let len = size * count;
let memory = &self.memory[pointer..pointer + len];
if let Some(Some(file)) = self.file_handles.get_mut(file_id) {
if let Err(e) = file.write_all(memory) {
log::error!("Error writing to file: {}", e);
instruction.valid = false;
} else {
self.registers.general_purpose[0] = count as u64;
}
} else {
log::error!("Error writing to file: The file handle doesn't exist");
instruction.valid = false;
}
}
id => {
log::error!("Unrecognized system call (ID {:#X}).", id);
instruction.valid = false;
}
}
}
fn file_print(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let file = self
.file_handles
.get_mut(self.registers.general_purpose[0] as usize)
.ok_or("The file handle doesn't exist")?
.as_mut()
.ok_or("The file has been closed")?;
let format_start = self.registers.general_purpose[1] as usize;
let format_len = self.memory[format_start..]
.iter()
.position(|&char| char == 0)
.ok_or("The format string isn't zero-terminated")?;
let text = format_c_string(
&String::from_utf8_lossy(&self.memory[format_start..format_start + format_len]),
&self.memory[self.registers.general_purpose[2] as usize..],
)
.map_err(|e| e.to_string())?;
file.write_all(text.as_bytes()).map_err(|e| e.to_string())?;
self.registers.general_purpose[0] = text.len() as u64;
Ok(())
}
fn file_open(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let path_start = self.registers.general_purpose[0] as usize;
let path_len = self.memory[path_start..]
.iter()
.position(|&char| char == 0)
.ok_or("The path isn't zero-terminated")?;
let path = String::from_utf8_lossy(&self.memory[path_start..path_start + path_len]);
let mode_start = self.registers.general_purpose[1] as usize;
let mode_len = self.memory[mode_start..]
.iter()
.position(|&char| char == 0)
.ok_or("The mode string isn't zero-terminated")?;
let mode = String::from_utf8_lossy(&self.memory[mode_start..mode_start + mode_len]);
let (read, write, append, truncate) = match &*mode {
"r" | "rb" => (true, false, false, false),
"w" | "wb" => (false, true, false, true),
"a" | "ab" => (false, false, true, false),
"r+" | "rb+" | "r+b" => (true, true, false, false),
"w+" | "wb+" | "w+b" => (true, true, false, true),
"a+" | "ab+" | "a+b" => (true, false, true, false),
e => return Err(Box::from(format!("Invalid mode \"{e}\""))),
};
let file = std::fs::OpenOptions::new()
.read(read)
.write(write)
.append(append)
.truncate(truncate)
.create(append | truncate)
.open(&*path)?;
self.file_handles.push(Some(file));
self.registers.general_purpose[0] = self.file_handles.len() as u64 - 1;
Ok(())
}
}
impl Emulator for ForwardComEmulator {
#[inline]
fn emulate_instruction(&mut self, address: usize, commit: bool) -> Option<Instruction> {
debug_assert!(
!commit | (address == self.ip),
"Control flow failure, expected {:#X}, got {:#X}",
self.ip,
address
);
log::debug!(
"Processing ip {:#X} ({:#X})",
(address - self.ip_base) >> 2,
address
);
let (instruction, has_follow_up) =
self.decoder
.get_next_instruction(address, &self.memory, &self.registers);
self.registers.special[register_indices::IP] = address as u64 + instruction.size as u64;
let mut instruction = core::prepare(self, instruction, has_follow_up);
if commit {
core::execute(&mut instruction, self);
core::write_results(
&mut self.memory,
&mut self.registers,
&self.operands,
&instruction,
);
self.ip = core::set_next_ip(&mut self.call_stack, &mut instruction);
self.retired_instruction_count
.fetch_add(1, Ordering::Relaxed);
}
log::trace!("");
self.instruction_count.fetch_add(1, Ordering::Relaxed);
if (instruction.core.flags & instruction_flags::TERMINATE) > 0 {
None
} else {
Some(instruction.core)
}
}
#[inline]
fn get_instruction_count(&self) -> usize {
self.instruction_count.load(Ordering::Relaxed)
}
#[inline]
fn get_current_ip(&self) -> usize {
self.ip
}
#[inline]
fn get_printed_output(&self) -> &str {
self.output_print_buffer.as_str()
}
#[inline]
fn get_start_address(&self) -> usize {
self.start_address
}
}
impl RegisterFile {
pub fn new(max_vector_size: usize) -> Self {
RegisterFile {
general_purpose: [0; 32],
vector: vec![0; 32 * max_vector_size],
vector_lengths: [0; 32],
special: [0; 32],
max_vector_size,
}
}
#[inline]
pub fn get_vector(&self, index: usize) -> &[u8] {
let base = index * self.max_vector_size;
&self.vector.as_slice()[base..base + self.vector_lengths[index]]
}
#[inline]
pub fn set_vector(&mut self, index: usize, value: &[u8]) {
let base = index * self.max_vector_size;
self.vector.as_mut_slice()[base..base + value.len()].copy_from_slice(value);
self.vector_lengths[index] = value.len();
}
}
#[cfg(test)]
mod tests {
use super::ForwardComEmulator;
use super::RegisterFile;
use crate::emulator::constants::{default_values, register_indices};
use gpcas_isa::{Emulator, OperandStorage};
use std::convert::TryInto;
#[test]
fn push_pop() {
let mut memory = vec![0u8; 96];
memory[0..4].copy_from_slice(&u32::to_le_bytes(0x471f_f017_u32)[..]);
memory[4..8].copy_from_slice(&u32::to_le_bytes(0x473f_f017_u32)[..]);
let mut emulator = get_emulator(memory);
for i in 0..8 {
emulator.emulate_instruction(0x0, true);
emulator.registers.general_purpose[i + 16] = 0;
assert_eq!(
u64::from_le_bytes(
emulator.memory
[emulator.memory.len() - 8 * (i + 1)..emulator.memory.len() - 8 * i]
.try_into()
.unwrap()
),
i as u64 + 17,
"Register {} at address {}",
i + 16,
emulator.memory.len() - 8 * (i + 1),
);
}
emulator.emulate_instruction(0x0, true);
for i in 0..8 {
emulator.emulate_instruction(0x4, true);
assert_eq!(
emulator.registers.general_purpose[24 - i - 1],
24 - i as u64,
"Register {} had the wrong value!",
24 - i - 1,
);
}
emulator.emulate_instruction(0x4, true);
}
fn get_emulator(data: Vec<u8>) -> ForwardComEmulator {
let mut registers = RegisterFile::new(2);
for i in 0..31 {
registers.general_purpose[i] = i as u64 + 1;
}
registers.general_purpose[register_indices::STACK_POINTER] = data.len() as u64;
registers.special[register_indices::NUMCONTR] = default_values::NUMCONTR;
ForwardComEmulator {
ip: 0,
start_address: 0,
instruction_count: Default::default(),
retired_instruction_count: Default::default(),
memory: data,
call_stack: Vec::new(),
decoder: Default::default(),
operands: OperandStorage::new(16, 32),
registers,
output_print_buffer: String::new(),
ip_base: 0,
file_handles: Vec::new(),
}
}
}