gpcas_forwardcom 0.1.1

ForwardCom instruction set architecture (ISA) properties for use with the General Purpose Core Architecture Simulator (GPCAS).
Documentation
// Filename: decode.rs
// Author:	 Kai Rese
// Version:	 0.18
// Date:	 26-06-2022 (DD-MM-YYYY)
// Library:  gpcas_forwardcom
//
// Copyright (c) 2022 Kai Rese
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program. If not, see
// <https://www.gnu.org/licenses/>.

//! Contains the decoding function and all structs that are provided by it.

mod single_word_multi_format;
mod single_word_single_format;
mod three_words;
mod two_words;

use crate::emulator::constants::*;
use crate::emulator::core::sign_extend;
use crate::emulator::instructions::*;
use crate::emulator::RegisterFile;

use std::collections::vec_deque::VecDeque;

/// Decodes the instructions.
///
/// Uses an object-oriented style because decoded micro-operations have to be saved between calls.
pub struct Decoder(
    /// Stores the decoded micro-ops of an instruction.
    VecDeque<DecodedInstruction>,
);

impl Decoder {
    /// Returns the next instruction to be executed.
    ///
    /// This can either be the instruction at the given address, the first micro-op of the
    /// instruction at the given address, or a micro-op from a previously decoded instruction,
    /// ignoring the given address.
    #[inline]
    pub fn get_next_instruction(
        &mut self,
        ip: usize,
        memory: &[u8],
        register_file: &RegisterFile,
    ) -> (DecodedInstruction, bool) {
        if let Some(micro_op) = self.0.pop_front() {
            log::trace!("Got another micro-op, {} left", self.0.len());

            (micro_op, !self.0.is_empty())
        } else {
            match decode(ip, memory, register_file) {
                DecodeResult::Simple(instruction) => (instruction, false),
                DecodeResult::Complex(instruction) => {
                    (instruction.generate_function)(
                        instruction,
                        register_file,
                        memory,
                        &mut self.0,
                    );
                    log::debug!("Generated {} micro-ops", self.0.len());
                    let first = self.0.pop_front().unwrap();
                    (first, !self.0.is_empty())
                }
            }
        }
    }
}

impl Default for Decoder {
    fn default() -> Self {
        Self(VecDeque::with_capacity(32))
    }
}

/// Contains the unprocessed output of the decoder.
///
/// This gets processed to a proper emulator instruction in the [`prepare`](super::prepare::prepare)
/// step.
pub struct DecodedInstruction {
    /// The memory address the instruction can be found at.
    pub address: usize,
    /// The size of the instruction in bytes.
    pub size: u8,
    /// Information of the selected ISA instruction.
    pub instruction: &'static SimpleIsaInstruction,
    /// The input operands provided by the format. Is a static array for performance reasons.
    pub operands: [Operand; 4],
    /// The amount of input operands provided by the format.
    pub operand_count: u8,
    /// The data type of the operands.
    pub operand_type: OperandType,
    /// The address of the memory operand, if present.
    pub memory_address: usize,
    /// The target address to jump to, if applicable. Can be incomplete, must be calculated by the
    /// instruction function in this case.
    pub branch_address: usize,
    /// Index into the register file for the instruction mask.
    pub mask_index: u8,
    /// Values of the option bits, if present.
    pub option_bits: u8,
    /// How an immediate or memory operand should be extended to the instruction operand size.
    pub operand_extension: OperandExtension,
}

impl Default for DecodedInstruction {
    fn default() -> Self {
        Self {
            address: 0,
            size: 0,
            instruction: &NOP,
            operands: [Operand::None, Operand::None, Operand::None, Operand::None],
            operand_type: OperandType::I32,
            operand_count: 0,
            memory_address: 0,
            branch_address: 0,
            mask_index: default_values::MASK,
            option_bits: 0,
            operand_extension: OperandExtension::None,
        }
    }
}

/// Contains decoded data that can be used to generate micro-ops for the instruction.
pub struct DecodedComplexInstruction {
    /// The function which creates the micro-ops.
    pub generate_function: ComplexInstructionFunction,
    /// The memory address the instruction can be found at.
    pub address: usize,
    /// Index into the general purpose register file for the stack pointer.
    pub stack_pointer: u32,
    /// Index into the register file for the first data register.
    pub data_register: u32,
    /// Values for the option bits.
    pub option_bits: u8,
    /// The data type of the instruction.
    pub operand_type: OperandType,
    /// If the instruction uses vector registers for data.
    pub vector_instruction: bool,
}

/// Decoder output.
pub enum DecodeResult {
    /// A simple instruction which can be used as-is.
    Simple(DecodedInstruction),
    /// A complex instruction which generated simpler micro-ops.
    Complex(DecodedComplexInstruction),
}

/// Contains register usage information of the memory operand.
#[derive(Clone)]
pub struct MemoryOperand {
    /// The amount of registers used to calculate the operand address.
    pub register_count: u8,
    /// Indices into the general purpose register file of registers used for the address
    /// calculation.
    pub registers: [u32; 2],
}

/// A decoded instruction operand.
#[derive(Clone)]
pub enum Operand {
    /// No operand.
    None,
    /// A general purpose register, contains the index into the register file.
    GeneralPurposeRegister(u32),
    /// A special register, contains the index into the register file.
    SpecialRegister(u32),
    /// A vector register, contains the index into the register file.
    VectorRegister(u32),
    /// A immediate operand, contains the value.
    Immediate(u64),
    /// A memory operand, contains register usage information for the address calculation.
    Memory(MemoryOperand),
}

impl Operand {
    /// Changes the register kind of a register operand.
    ///
    /// # Panics
    ///
    /// This function will panic if either the old or new kind isn't a register.
    pub fn override_type(self, other: &Operand) -> Operand {
        let index = match self {
            Self::GeneralPurposeRegister(index)
            | Self::SpecialRegister(index)
            | Self::VectorRegister(index) => index,
            _ => panic!("Tried to override non-register type!"),
        };

        match other {
            Self::GeneralPurposeRegister(_) => Self::GeneralPurposeRegister(index),
            Self::SpecialRegister(_) => Self::SpecialRegister(index),
            Self::VectorRegister(_) => Self::VectorRegister(index),
            _ => panic!("Tried to override with non-register type!"),
        }
    }
}

/// The data type of the instruction.
#[derive(Clone, Copy)]
pub enum OperandType {
    /// 8 bit integer.
    I8,
    /// 16 bit integer.
    I16,
    /// 32 bit integer, default value when not specified.
    I32,
    /// 64 bit integer, native value of the ISA.
    I64,
    /// 128 bit integer, not yet supported.
    I128,
    /// Half precision floating point.
    F16,
    /// Single precision floating point.
    F32,
    /// Double precision floating point.
    F64,
    /// Quadruple precision floating point, not yet supported.
    F128,
}

impl OperandType {
    /// Parses the operand type from the value of the OT field in the instruction template.
    #[inline]
    fn from_value(value: u32) -> Self {
        match value {
            0 => Self::I8,
            1 => Self::I16,
            2 => Self::I32,
            3 => Self::I64,
            4 => Self::I128,
            5 => Self::F32,
            6 => Self::F64,
            7 => Self::F128,
            _ => Self::F16,
        }
    }

    /// The size of the operand in power-of-two bytes.
    #[inline]
    pub fn log_size(&self) -> usize {
        match self {
            OperandType::I8 => 0,
            OperandType::I16 => 1,
            OperandType::I32 => 2,
            OperandType::I64 => 3,
            OperandType::I128 => 4,
            OperandType::F16 => 1,
            OperandType::F32 => 2,
            OperandType::F64 => 3,
            OperandType::F128 => 4,
        }
    }

    /// If the operand type is a floating point type.
    #[inline]
    pub fn is_float(&self) -> bool {
        match self {
            Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128 => false,
            Self::F16 | Self::F32 | Self::F64 | Self::F128 => true,
        }
    }
}

/// How the last operand should be extended if it isn't register. Also indicated if the instruction
/// is a vector instruction.
pub enum OperandExtension {
    /// The instruction is not a vector instruction, no extension.
    None,
    /// The value is treated as a vector with a singular value.
    /// This should also be used when vector registers are used, but there is no extensible operand.
    Scalar,
    /// The value is treated as a vector. Contains the size in bytes.
    Default(u16),
    /// The value is broadcast to all vector elements. Contains the size in bytes.
    Broadcast(u16),
}

/// Decodes a new instruction and returns the result.
fn decode(ip: usize, memory: &[u8], register_file: &RegisterFile) -> DecodeResult {
    let first_word = u32::from_le_bytes(memory[ip..ip + 4].try_into().unwrap());

    let il_field = (first_word & parse_masks::IL) >> parse_shifts::IL;
    let mode_field;
    let ot_field;

    let mode_base = (first_word & parse_masks::MODE) >> parse_shifts::MODE;
    if mode_base < 2 {
        mode_field = ((first_word & parse_masks::M) >> parse_shifts::M_MODE) | mode_base;
        ot_field = (first_word & parse_masks::OT) >> parse_shifts::OT;
    } else {
        mode_field = mode_base;
        ot_field = ((first_word & parse_masks::M) >> parse_shifts::M_OT)
            | ((first_word & parse_masks::OT) >> parse_shifts::OT);
    }

    match il_field {
        0 => single_word_multi_format::decode(ip, first_word, mode_field, ot_field, register_file),
        1 => single_word_single_format::decode(ip, first_word, mode_field, ot_field, register_file),
        2 => two_words::decode(
            ip,
            first_word,
            u32::from_le_bytes(memory[ip + 4..ip + 8].try_into().unwrap()),
            mode_field,
            ot_field,
            register_file,
        ),
        3 => three_words::decode(ip, memory, first_word, mode_field, ot_field, register_file),
        _ => unreachable!(),
    }
}

/// Converts the flat bit value of an immediate field into the data type of the instruction.
///
/// If the given size is not supported, the function will return the unchanged input value.
///
/// # Attention
///
/// The size of the immediate field must be given in power-of-two bytes!
#[inline]
fn interpret_immediate(raw: u64, size: u8, operand_type: OperandType) -> u64 {
    match operand_type {
        OperandType::I8 | OperandType::I16 | OperandType::I32 | OperandType::I64 => {
            // Can be truncated later
            sign_extend(raw, 8 << size)
        }
        // not supported
        OperandType::I128 => raw,
        OperandType::F16 => {
            match size {
                0 => half::f16::from(raw as u8).to_bits() as u64,
                1 => raw,
                2 => half::f16::from_f32(f32::from_bits(raw as u32)).to_bits() as u64,
                3 => half::f16::from_f64(f64::from_bits(raw)).to_bits() as u64,
                // not supported
                _ => raw,
            }
        }
        OperandType::F32 => {
            match size {
                0 => (raw as u8 as f32).to_bits() as u64,
                1 => half::f16::from_bits(raw as u16).to_f32().to_bits() as u64,
                2 => raw,
                3 => (f64::from_bits(raw) as f32).to_bits() as u64,
                // not supported
                _ => raw,
            }
        }
        OperandType::F64 => {
            match size {
                0 => (raw as u8 as f64).to_bits(),
                1 => half::f16::from_bits(raw as u16).to_f64().to_bits(),
                2 => (f32::from_bits(raw as u32) as f64).to_bits(),
                3 => raw,
                // not supported
                _ => raw,
            }
        }
        // not supported
        OperandType::F128 => raw,
    }
}