gpcas_forwardcom 0.2.1

ForwardCom instruction set architecture (ISA) properties for use with the General Purpose Core Architecture Simulator (GPCAS).
Documentation
// Filename: prepare.rs
// Author:	 Kai Rese
// Version:	 0.9
// Date:	 01-03-2023 (DD-MM-YYYY)
// Library:  gpcas_forwardcom
//
// Copyright (c) 2023 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/>.

//! Generates the emulator instruction and prepares it for execution.

use super::decode::DecodedInstruction;
use crate::emulator::constants::{default_values, operand_indices, register_indices};
use crate::emulator::core::decode::{Operand, OperandExtension};
use crate::emulator::EmulatorInstruction;
use crate::ForwardComEmulator;

use gpcas_base::instruction_type;
use gpcas_isa::{instruction_flags, Instruction, IS_VECTOR_REGISTER};

/// Generates the emulator instruction and prepares it for execution.
///
/// Several steps are performed:
/// - Performs overrides for an operand or the data type.
/// - Corrects the instruction type in case of floating point operands.
/// - Loads the operands and sets all register inputs and the output.
/// - Loads the mask and fallback.
pub fn prepare(
    emulator: &mut ForwardComEmulator,
    instruction: DecodedInstruction,
    has_follow_up: bool,
    commit: bool,
) -> EmulatorInstruction {
    let DecodedInstruction {
        address,
        size,
        instruction,
        mut operands,
        operand_type,
        operand_count,
        memory_address,
        branch_address,
        mask_index,
        option_bits,
        operand_extension,
    } = instruction;

    let operand_type = if let Some(new_type) = instruction.operand_type_override {
        new_type
    } else {
        operand_type
    };

    if let Some((index, operand_kind)) = &instruction.register_type_override {
        operands[*index as usize] = operands[*index as usize]
            .clone()
            .override_type(operand_kind);
    }

    // Do all the overrides
    let instr_type = if operand_type.is_float() {
        match instruction.instr_type {
            instruction_type::INT_ADD => instruction_type::FLOAT_ADD,
            instruction_type::INT_MUL => instruction_type::FLOAT_MUL,
            instruction_type::INT_DIV => instruction_type::FLOAT_DIV,
            instruction_type::INT_MUL_ADD => instruction_type::FLOAT_MUL_ADD,
            other => other,
        }
    } else {
        instruction.instr_type
    };

    // Load the operands
    let special_output;
    let output;
    let element_size = 1 << operand_type.log_size();
    let mut vector_size = None;
    let mut flags = instruction.instruction_flags;
    let mut inputs = [0; 4];
    let mut input_register_count = 0;
    let mut memory_register_count = 0;
    let mut memory_operand_size = 0;
    if (operand_count > 0) & (instruction.inputs <= operand_count) {
        emulator.operands.clear();

        let mut operand_index = operand_count as isize - 1;
        let mut input_index = instruction.inputs as i8 - 1;
        while (operand_index >= 0) & (input_index >= 0) {
            match &operands[operand_index as usize] {
                Operand::None => panic!("Tried to read non-existent format operand"),
                Operand::GeneralPurposeRegister(index) => {
                    inputs[input_register_count as usize] = *index as u8;
                    input_register_count += 1;
                    emulator.operands.set_u64(
                        operand_indices::INPUT1 + input_index as usize,
                        emulator.registers.general_purpose[*index as usize],
                    );
                    input_index -= 1;
                }
                Operand::SpecialRegister(index) => {
                    inputs[input_register_count as usize] = *index as u8;
                    input_register_count += 1;
                    emulator.operands.set_u64(
                        operand_indices::INPUT1 + input_index as usize,
                        emulator.registers.special[*index as usize],
                    );
                    input_index -= 1;
                }
                Operand::VectorRegister(index) => {
                    // Store takes its length from the memory operand
                    if (flags & instruction_flags::MEM_OUT) == 0 {
                        vector_size = Some(emulator.registers.vector_lengths[*index as usize]);
                    }
                    inputs[input_register_count as usize] = IS_VECTOR_REGISTER | *index as u8;
                    input_register_count += 1;
                    emulator.operands.set(
                        operand_indices::INPUT1 + input_index as usize,
                        emulator.registers.get_vector(*index as usize),
                    );
                    input_index -= 1;
                }
                Operand::Immediate(value) => match operand_extension {
                    OperandExtension::Broadcast(length) => {
                        let length =
                            std::cmp::min(length as usize, emulator.operands.operand_size());

                        for i in 0..length >> operand_type.log_size() {
                            emulator.operands.set_offset(
                                operand_indices::INPUT1 + input_index as usize,
                                i << operand_type.log_size(),
                                &value.to_le_bytes()[0..element_size],
                            );
                        }
                        input_index -= 1;
                    }
                    _ => {
                        emulator
                            .operands
                            .set_u64(operand_indices::INPUT1 + input_index as usize, *value);
                        input_index -= 1;
                    }
                },
                Operand::Memory(operand) => {
                    flags ^= instruction_flags::MEM_IN;
                    inputs[input_register_count as usize] = operand.registers[0] as u8;
                    inputs[input_register_count as usize + 1] = operand.registers[1] as u8;
                    input_register_count = operand.register_count;
                    memory_register_count = operand.register_count;

                    // input gets overridden if the instruction has a memory output
                    match operand_extension {
                        OperandExtension::None | OperandExtension::Scalar => {
                            memory_operand_size = element_size as u16;
                            if commit {
                                emulator.operands.set(
                                    operand_indices::INPUT1 + input_index as usize,
                                    &emulator.memory[memory_address..memory_address + element_size],
                                );
                            }
                        }
                        OperandExtension::Default(length) => {
                            let length =
                                std::cmp::min(length as usize, emulator.operands.operand_size());

                            vector_size = Some(length);
                            flags ^= instruction_flags::MEM_VECTOR;
                            memory_operand_size = length as u16;
                            if commit {
                                emulator.operands.set(
                                    operand_indices::INPUT1 + input_index as usize,
                                    &emulator.memory[memory_address..memory_address + length],
                                );
                            }
                        }
                        OperandExtension::Broadcast(length) => {
                            let length =
                                std::cmp::min(length as usize, emulator.operands.operand_size());

                            vector_size = Some(length);
                            memory_operand_size = element_size as u16;
                            if commit {
                                for i in 0..length >> operand_type.log_size() {
                                    emulator.operands.set_offset(
                                        operand_indices::INPUT1 + input_index as usize,
                                        i << operand_type.log_size(),
                                        &emulator.memory
                                            [memory_address..memory_address + element_size],
                                    );
                                }
                            }
                        }
                    }

                    if (flags & instruction_flags::MEM_OUT) == 0 {
                        input_index -= 1;
                    }
                }
            }
            operand_index -= 1;
        }

        if (flags & instruction_flags::REG_OUT) != 0 {
            match operands[0] {
                Operand::GeneralPurposeRegister(register_index) => {
                    special_output = false;
                    output = register_index as u8;
                }
                Operand::SpecialRegister(register_index) => {
                    special_output = true;
                    output = register_index as u8;
                }
                Operand::VectorRegister(register_index) => {
                    special_output = false;
                    output = IS_VECTOR_REGISTER | register_index as u8;
                }
                _ => panic!("Format with invalid output operand type"),
            }
        } else {
            special_output = false;
            output = 0;
        }
    } else {
        special_output = false;
        output = 0;
    }

    let operand_size = vector_size.unwrap_or(element_size);
    flags |= instruction_flags::HAS_FOLLOW_UP * has_follow_up as u16;

    // Load mask and fallback
    let mut valid = true;
    if mask_index != default_values::MASK {
        if (output & IS_VECTOR_REGISTER) != 0 {
            inputs[input_register_count as usize] = IS_VECTOR_REGISTER | mask_index;
            input_register_count += 1;
            emulator.operands.set(
                operand_indices::MASK,
                emulator.registers.get_vector(mask_index as usize),
            );
        } else {
            inputs[input_register_count as usize] = mask_index;
            input_register_count += 1;
            emulator.operands.set_u64(
                operand_indices::MASK,
                emulator.registers.general_purpose[mask_index as usize],
            );
        }

        let output_offset = ((flags & instruction_flags::REG_OUT) != 0) as u8;
        let fallback_index = if instruction.inputs >= 3 {
            operand_count as usize - std::cmp::min(instruction.inputs, operand_count) as usize
        } else if operand_count > instruction.inputs + output_offset + 1 {
            (operand_count - instruction.inputs) as usize - 1
        } else if operand_count > instruction.inputs + output_offset {
            output_offset as usize
        } else {
            (operand_count - instruction.inputs) as usize - 1
        };

        if fallback_index != default_values::FALLBACK_REGISTER {
            // It doesn't matter if we double read a register for this, since the simulator
            // currently doesn't simulate register port limits
            match operands[fallback_index] {
                Operand::GeneralPurposeRegister(register_index) => {
                    inputs[input_register_count as usize] = fallback_index as u8;
                    input_register_count += 1;
                    emulator.operands.set_u64(
                        operand_indices::FALLBACK,
                        emulator.registers.general_purpose[register_index as usize],
                    );
                }
                Operand::VectorRegister(register_index) => {
                    inputs[input_register_count as usize] =
                        IS_VECTOR_REGISTER | fallback_index as u8;
                    input_register_count += 1;
                    emulator.operands.set(
                        operand_indices::FALLBACK,
                        emulator.registers.get_vector(register_index as usize),
                    );
                }
                _ => {
                    log::debug!("Error: Tried to read an invalid fallback type");
                    valid = false;
                }
            }
        }
    } else {
        emulator.operands.set_broadcast(
            operand_indices::MASK,
            &emulator.registers.special[register_indices::NUMCONTR].to_le_bytes(),
        );
    }

    EmulatorInstruction {
        core: Instruction {
            address,
            memory_address,
            branch_address,
            instr_type,
            memory_operand_size,
            flags,
            inputs,
            input_register_count,
            memory_register_count,
            output,
            size,
        },
        function: instruction.function,
        vector_mode: instruction.vector_mode,
        operand_type,
        operand_size,
        option_bits,
        special_output,
        valid,
    }
}