gpcas_forwardcom 0.2.1

ForwardCom instruction set architecture (ISA) properties for use with the General Purpose Core Architecture Simulator (GPCAS).
Documentation
// Filename: memory.rs
// Author:	 Kai Rese
// Version:	 0.9
// Date:	 30-01-2023 (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/>.

//! All instruction functions that manipulate memory.

use super::*;
use crate::emulator::core::{
    DecodedComplexInstruction, DecodedInstruction, MemoryOperand, Operand, OperandExtension,
    OperandType,
};
use crate::emulator::RegisterFile;

use crate::emulator::instructions::SimpleIsaInstruction;
use gpcas_base::instruction_type;
use gpcas_isa::instruction_flags;
use std::collections::VecDeque;

/// Push of a range of registers onto the stack.
///
/// The option bits are as follows:
/// - Bit 0-4 - The last register to be pushed
/// - Bit 7 - Reverses the stack direction. Forward growing stacks can't save vector registers.
///
/// This function generates two stores and a stack pointer update instruction per pushed vector
/// register.
/// This function generates a store instruction per pushed general purpose register, and one stack
/// pointer update instruction.
/// The stack pointer update instruction is either a sub or add depending on the direction.
pub fn push(
    instruction: DecodedComplexInstruction,
    register_file: &RegisterFile,
    _memory: &[u8],
    output: &mut VecDeque<DecodedInstruction>,
) {
    let last_register = instruction.option_bits & 0x1F;
    let forwards = (instruction.option_bits & 0x80) != 0;

    let mut push_address =
        register_file.general_purpose[instruction.stack_pointer as usize] as usize;

    if instruction.vector_instruction {
        if forwards {
            // TODO replace with invalid instruction
            panic!("Tried to push a vector on a forward-facing stack");
        } else {
            for register in instruction.data_register..=last_register as u32 {
                let vector_length = register_file.vector_lengths[register as usize];

                push_address -= vector_length + 8;
                output.push_back(DecodedInstruction {
                    address: instruction.address,
                    size: 4,
                    instruction: &SimpleIsaInstruction {
                        function: sub,
                        // pull in vector as dependency
                        inputs: 3,
                        instr_type: instruction_type::INT_ADD,
                        ..SimpleIsaInstruction::DEFAULT
                    },
                    operands: [
                        Operand::GeneralPurposeRegister(instruction.stack_pointer),
                        Operand::Immediate(vector_length as u64 + 8),
                        Operand::VectorRegister(register),
                        Operand::None,
                    ],
                    operand_type: OperandType::I64,
                    operand_count: 3,
                    ..Default::default()
                });
                output.push_back(DecodedInstruction {
                    address: instruction.address,
                    size: 4,
                    instruction: &SimpleIsaInstruction {
                        function: move_instruction,
                        inputs: 1,
                        instr_type: instruction_type::MOVE,
                        instruction_flags: instruction_flags::MEM_IN | instruction_flags::MEM_OUT,
                        ..SimpleIsaInstruction::DEFAULT
                    },
                    operands: [
                        Operand::Immediate(vector_length as u64),
                        Operand::Memory(MemoryOperand {
                            register_count: 1,
                            registers: [instruction.stack_pointer, 0],
                        }),
                        Operand::None,
                        Operand::None,
                    ],
                    operand_type: OperandType::I64,
                    operand_count: 2,
                    memory_address: push_address,
                    ..Default::default()
                });
                output.push_back(DecodedInstruction {
                    address: instruction.address,
                    size: 4,
                    instruction: &SimpleIsaInstruction {
                        function: move_instruction,
                        inputs: 1,
                        instr_type: instruction_type::MOVE,
                        instruction_flags: instruction_flags::MEM_IN | instruction_flags::MEM_OUT,
                        ..SimpleIsaInstruction::DEFAULT
                    },
                    operands: [
                        Operand::VectorRegister(register),
                        Operand::Memory(MemoryOperand {
                            register_count: 1,
                            registers: [instruction.stack_pointer, 0],
                        }),
                        Operand::None,
                        Operand::None,
                    ],
                    operand_type: OperandType::I64,
                    operand_count: 2,
                    memory_address: push_address + 8,
                    operand_extension: OperandExtension::Default(vector_length as u16),
                    ..Default::default()
                });
            }
        }
    } else {
        let pointer_update_instruction: &SimpleIsaInstruction;
        let address_offset;
        let address_factor;

        if forwards {
            pointer_update_instruction = &POINTER_ADD;
            address_offset = 0;
            address_factor = 1;
        } else {
            pointer_update_instruction = &POINTER_SUB;
            address_offset = 1;
            address_factor = -1;
        }

        for register in instruction.data_register..=last_register as u32 {
            let i = (register - instruction.data_register) as isize;
            let memory_address = (push_address as isize
                + (((i + address_offset) * address_factor) << instruction.operand_type.log_size()))
                as usize;

            output.push_back(DecodedInstruction {
                address: instruction.address,
                size: 4,
                instruction: &SimpleIsaInstruction {
                    function: move_instruction,
                    inputs: 1,
                    instr_type: instruction_type::MOVE,
                    instruction_flags: instruction_flags::MEM_IN | instruction_flags::MEM_OUT,
                    ..SimpleIsaInstruction::DEFAULT
                },
                operands: [
                    Operand::GeneralPurposeRegister(register),
                    Operand::Memory(MemoryOperand {
                        register_count: 1,
                        registers: [instruction.stack_pointer, 0],
                    }),
                    Operand::None,
                    Operand::None,
                ],
                operand_type: instruction.operand_type,
                operand_count: 2,
                memory_address,
                ..Default::default()
            });
        }
        output.push_back(DecodedInstruction {
            address: instruction.address,
            size: 4,
            instruction: pointer_update_instruction,
            operands: [
                Operand::GeneralPurposeRegister(instruction.stack_pointer),
                Operand::Immediate(
                    (last_register as u64 - instruction.data_register as u64 + 1)
                        << instruction.operand_type.log_size(),
                ),
                Operand::None,
                Operand::None,
            ],
            operand_type: instruction.operand_type,
            operand_count: 2,
            ..Default::default()
        })
    }
}

/// Pop of a range of registers from the stack.
///
/// The option bits are as follows:
/// - Bit 0-4 - The last register to be pushed
/// - Bit 7 - Reverses the stack direction. Forward growing stacks can't save vector registers.
///
/// This function generates two loads and a stack pointer update instruction per popped vector
/// register.
/// This function generates a load instruction per popped general purpose register, and one stack
/// pointer update instruction.
/// The stack pointer update instruction is either a sub or add depending on the direction.
pub fn pop(
    instruction: DecodedComplexInstruction,
    register_file: &RegisterFile,
    memory: &[u8],
    output: &mut VecDeque<DecodedInstruction>,
) {
    let last_register = instruction.option_bits & 0x1F;
    let forwards = (instruction.option_bits & 0x40) != 0;

    let mut pop_address =
        register_file.general_purpose[instruction.stack_pointer as usize] as usize;

    if instruction.vector_instruction {
        if forwards {
            // TODO replace with invalid instruction.
            panic!("Tried to push a vector on a forward-facing stack");
        } else {
            // We're kind of cheating here because we're not using the emulation pipeline to
            // retrieve the vector length
            for register in (instruction.data_register..=last_register as u32).rev() {
                let vector_length =
                    u64::from_le_bytes(memory[pop_address..pop_address + 8].try_into().unwrap())
                        as usize;

                // This instruction doesn't have an effect on the emulator
                output.push_back(DecodedInstruction {
                    address: instruction.address,
                    size: 4,
                    instruction: &SimpleIsaInstruction {
                        function: move_instruction,
                        inputs: 1,
                        instr_type: instruction_type::MOVE,
                        instruction_flags: 0,
                        ..SimpleIsaInstruction::DEFAULT
                    },
                    operands: [
                        Operand::Memory(MemoryOperand {
                            register_count: 1,
                            registers: [instruction.stack_pointer, 0],
                        }),
                        Operand::None,
                        Operand::None,
                        Operand::None,
                    ],
                    operand_type: OperandType::I64,
                    operand_count: 1,
                    memory_address: pop_address,
                    ..Default::default()
                });
                output.push_back(DecodedInstruction {
                    address: instruction.address,
                    size: 4,
                    instruction: &SimpleIsaInstruction {
                        function: move_instruction,
                        inputs: 1,
                        instr_type: instruction_type::MOVE,
                        ..SimpleIsaInstruction::DEFAULT
                    },
                    operands: [
                        Operand::VectorRegister(register),
                        Operand::Memory(MemoryOperand {
                            register_count: 1,
                            registers: [instruction.stack_pointer, 0],
                        }),
                        Operand::None,
                        Operand::None,
                    ],
                    operand_type: OperandType::I64,
                    operand_count: 2,
                    memory_address: pop_address + 8,
                    operand_extension: OperandExtension::Default(vector_length as u16),
                    ..Default::default()
                });
                output.push_back(DecodedInstruction {
                    address: instruction.address,
                    size: 4,
                    instruction: &SimpleIsaInstruction {
                        function: add,
                        inputs: 2,
                        instr_type: instruction_type::INT_ADD,
                        ..SimpleIsaInstruction::DEFAULT
                    },
                    operands: [
                        Operand::GeneralPurposeRegister(instruction.stack_pointer),
                        Operand::Immediate(vector_length as u64 + 8),
                        Operand::None,
                        Operand::None,
                    ],
                    operand_type: OperandType::I64,
                    operand_count: 3,
                    ..Default::default()
                });
                pop_address += vector_length + 8;
            }
        }
    } else {
        let pointer_update_instruction: &SimpleIsaInstruction;
        let address_offset;
        let address_factor;

        if forwards {
            pointer_update_instruction = &POINTER_SUB;
            address_offset = 1;
            address_factor = -1;
        } else {
            pointer_update_instruction = &POINTER_ADD;
            address_offset = 0;
            address_factor = 1;
        }

        for register in (instruction.data_register..=last_register as u32).rev() {
            let i = (last_register as u32 - register) as isize;
            let memory_address = (pop_address as isize
                + (((i + address_offset) * address_factor) << instruction.operand_type.log_size()))
                as usize;

            output.push_back(DecodedInstruction {
                address: instruction.address,
                size: 4,
                instruction: &SimpleIsaInstruction {
                    function: move_instruction,
                    inputs: 1,
                    instr_type: instruction_type::MOVE,
                    ..SimpleIsaInstruction::DEFAULT
                },
                operands: [
                    Operand::GeneralPurposeRegister(register),
                    Operand::Memory(MemoryOperand {
                        register_count: 1,
                        registers: [instruction.stack_pointer, 0],
                    }),
                    Operand::None,
                    Operand::None,
                ],
                operand_type: instruction.operand_type,
                operand_count: 2,
                memory_address,
                ..Default::default()
            });
        }
        output.push_back(DecodedInstruction {
            address: instruction.address,
            size: 4,
            instruction: pointer_update_instruction,
            operands: [
                Operand::GeneralPurposeRegister(instruction.stack_pointer),
                Operand::Immediate(
                    (last_register as u64 - instruction.data_register as u64 + 1)
                        << instruction.operand_type.log_size(),
                ),
                Operand::None,
                Operand::None,
            ],
            operand_type: instruction.operand_type,
            operand_count: 2,
            ..Default::default()
        })
    }
}

/// Instruction data for adding to the stack pointer.
const POINTER_ADD: SimpleIsaInstruction = SimpleIsaInstruction {
    function: add,
    inputs: 2,
    instr_type: instruction_type::INT_ADD,
    ..SimpleIsaInstruction::DEFAULT
};

/// Instruction data for subtracting from the stack pointer.
const POINTER_SUB: SimpleIsaInstruction = SimpleIsaInstruction {
    function: sub,
    inputs: 2,
    instr_type: instruction_type::INT_ADD,
    ..SimpleIsaInstruction::DEFAULT
};