From 3e1e19023dcb3a83c49256221a0968b4284301ea Mon Sep 17 00:00:00 2001 From: 0x221E Date: Sat, 7 Feb 2026 00:16:48 +0100 Subject: [PATCH] Refactor: Fetch-Decode phase is instruction-agnostic and operand-encoding dependant --- src/CPU.cpp | 80 ++++++++++++++++++++++++++----------------- src/DataTransfer.cpp | 6 +--- src/DataTransfer.h | 18 ++++++++-- src/ExecutorCases.cpp | 41 ++++++++++++++++------ src/ExecutorCases.h | 18 ++++++++++ src/Instruction.cpp | 14 ++++---- src/Instruction.h | 3 ++ 7 files changed, 123 insertions(+), 57 deletions(-) diff --git a/src/CPU.cpp b/src/CPU.cpp index 4451898..52b69ef 100644 --- a/src/CPU.cpp +++ b/src/CPU.cpp @@ -11,13 +11,11 @@ CPU::CPU(std::shared_ptr& bus) : m_Bus(bus), m_IsHalted(false), m_Context({m_Instruction, m_InstructionPointer, m_Flags, m_Registers, m_Bus, m_IsHalted}) { m_InstructionPointer = 0x00008000; - for(int i = 0; i < 8; i++) - { + for(int i = 0; i < 8; i++) { m_Registers[i] = 0; } - for(int i = 0; i < 8; i++) - { + for(int i = 0; i < 8; i++) { m_SegmentRegisters[i] = 0; } } @@ -50,6 +48,7 @@ void CPU::Reset() { m_Registers[i] = 0; m_SegmentRegisters[i] = 0; } + m_InstructionPointer = 0x8000; m_Flags = 0; @@ -58,6 +57,7 @@ void CPU::Reset() { m_Instruction.m_Opcode = (Opcode)0; m_Instruction.m_Operand1 = 0; m_Instruction.m_Operand2 = 0; + m_Instruction.m_Func = nullptr; std::memset(m_Instruction.m_Displacement, 0, 4); m_Instruction.optional.m_ModRM = (x86::ModRM){ .m_State = x86::ModRMState::INVALID, .m_Reg = 0, .m_Rm = 0 }; } @@ -70,44 +70,60 @@ void CPU::FetchDecode() { uint8_t opcode_raw = m_Bus->AccessX(m_InstructionPointer); Opcode opcode = static_cast(opcode_raw); - switch(opcode_raw) { - case Opcode::MOV_R32_IMM32 ... 0xBF: // 0xB8 to 0xBF - m_Instruction.m_Opcode = Opcode::MOV_R32_IMM32; - m_Instruction.m_Operand1 = opcode_raw - 0xB8; - m_Instruction.m_Operand2 = m_Bus->AccessX(m_InstructionPointer + 1); - m_Instruction.m_Length = 5; - break; - case Opcode::NOP: - case Opcode::HLT: - m_Instruction.m_Opcode = opcode; - m_Instruction.m_Length = 1; - break; - case Opcode::ADD_RM32_R32: - m_Instruction.m_Opcode = opcode; - m_Instruction.optional.m_ModRM = x86::process_modrm(m_Bus->AccessX(m_InstructionPointer + 1)); - m_Instruction.m_Length = 2; - FetchModRMFields(); - break; - case Opcode::MOV_RM32_R32: - m_Instruction.m_Opcode = opcode; - m_Instruction.optional.m_ModRM = x86::process_modrm(m_Bus->AccessX(m_InstructionPointer + 1)); - m_Instruction.m_Length = 2; - FetchModRMFields(); - break; + auto& instruction_table = GetInstructionTable(); + if(instruction_table[opcode_raw].m_Executor == nullptr) { + std::cout << "Encoding: " << opcode_raw << std::endl; + throw std::runtime_error("[CPU] [FetchDecode] Opcode is not in instruction table!"); } + auto& instruction = instruction_table[opcode_raw]; + + uint8_t encoding_mask = 0b00001111; + uint8_t encoding = instruction.m_Encoding & encoding_mask; + uint8_t immediate = instruction.m_Encoding & ~encoding_mask; + + switch(encoding) { + case OI: + if (immediate == I32) { + m_Instruction.m_Operand1 = m_Bus->AccessX(m_InstructionPointer + 1); + m_Instruction.m_Length = 5; + } else if (immediate = I16) { + m_Instruction.m_Operand1 = m_Bus->AccessX(m_InstructionPointer + 1); + m_Instruction.m_Length = 3; + } else if (immediate = I8) { + m_Instruction.m_Operand1 = m_Bus->AccessX(m_InstructionPointer + 1); + m_Instruction.m_Length = 2; + } else { + throw std::runtime_error("[CPU] [FetchDecode] OI instruction encoding only supports imm8,imm16,imm32."); + } + break; + case ZO: + m_Instruction.m_Length = 1; + break; + case RM: + case MR: + m_Instruction.optional.m_ModRM = x86::process_modrm(m_Bus->AccessX(m_InstructionPointer + 1)); + m_Instruction.m_Length = 2; + FetchModRMFields(); + break; + default: + throw std::runtime_error("[CPU] [FetchDecode] Encoding was not found!"); + } + + m_Instruction.m_Func = instruction.m_Executor; + m_Instruction.m_Opcode = opcode; m_InstructionPointer += m_Instruction.m_Length; } void CPU::Execute() { uint8_t opcode_value = static_cast(m_Instruction.m_Opcode); - auto& exec_table = GetExecutorTable(); - if(exec_table[opcode_value]) + + if(m_Instruction.m_Func != nullptr) { - exec_table[opcode_value](m_Context); + m_Instruction.m_Func(m_Context); return; } - throw std::runtime_error("Opcode not found!"); + throw std::runtime_error("[CPU] [Execute] m_Func is nullptr!"); } void CPU::FetchModRMFields() { diff --git a/src/DataTransfer.cpp b/src/DataTransfer.cpp index 9b20a51..eb59598 100644 --- a/src/DataTransfer.cpp +++ b/src/DataTransfer.cpp @@ -8,11 +8,7 @@ #include namespace executor_cases { - void Mov_r32_imm32(CPUContext& cc) { - std::cout << "[Instruction] mov " << x86::Register2Str((x86::Register)cc.m_Instruction.m_Operand1) << ", " << std::hex << cc.m_Instruction.m_Operand2 << std::endl; - cc.m_Registers[cc.m_Instruction.m_Operand1] = cc.m_Instruction.m_Operand2; - } - + void Mov_rm32_r32(CPUContext& cc) { x86::ModRM modrm = cc.m_Instruction.optional.m_ModRM; diff --git a/src/DataTransfer.h b/src/DataTransfer.h index c0626b9..96d3274 100644 --- a/src/DataTransfer.h +++ b/src/DataTransfer.h @@ -1,8 +1,22 @@ #pragma once -struct CPUContext; +#include +#include +#include +#include + +#include "CPUContext.h" +#include "Instruction.h" namespace executor_cases { - void Mov_r32_imm32(CPUContext& cc); + template + void Mov_rX_immX(CPUContext& cc) { + static_assert(std::is_unsigned_v, "Mov_rX_immX requires an unsigned type!"); + uint8_t reg = cc.m_Instruction.m_Opcode - O; + std::cout << "[Instruction] mov " << x86::Register2Str((x86::Register)reg) << ", " << std::hex << cc.m_Instruction.m_Operand1 << std::endl; + std::memcpy(&cc.m_Registers[reg], &cc.m_Instruction.m_Operand1, sizeof(T)); + // cc.m_Registers[reg] = cc.m_Instruction.m_Operand1; + } + void Mov_rm32_r32(CPUContext& cc); } diff --git a/src/ExecutorCases.cpp b/src/ExecutorCases.cpp index 85bac68..befa070 100644 --- a/src/ExecutorCases.cpp +++ b/src/ExecutorCases.cpp @@ -12,21 +12,40 @@ #include #include -constexpr std::array GenerateExecutorTable(){ - std::array table{}; - table[Opcode::NOP] = executor_cases::Nop; - table[Opcode::HLT] = executor_cases::Hlt; - table[Opcode::MOV_R32_IMM32] = executor_cases::Mov_r32_imm32; - table[Opcode::MOV_RM32_R32] = executor_cases::Mov_rm32_r32; - table[Opcode::ADD_RM32_R32] = executor_cases::Add_rm32_r32; - table[Opcode::ADD_R32_RM32] = executor_cases::Add_r32_rm32; +constexpr std::array GenerateInstructionTable(){ + std::array table{}; + + for(uint8_t i = 0; i < 255; i++) { + table[i] = (InstructionEntry){nullptr, (OperandEncoding)0}; + } + + table[Opcode::NOP] = (InstructionEntry){executor_cases::Nop, ZO}; + table[Opcode::HLT] = (InstructionEntry){executor_cases::Hlt, ZO}; + // table[Opcode::MOV_R32_IMM16] = (InstructionEntry){executor_cases::Mov_rX_immX, (OperandEncoding)(I16 | OI)}; + table[Opcode::MOV_R32_IMM32] = (InstructionEntry){executor_cases::Mov_rX_immX, (OperandEncoding)(I32 | OI)}; + table[Opcode::MOV_RM32_R32] = (InstructionEntry){executor_cases::Mov_rm32_r32, MR}; + table[Opcode::ADD_RM32_R32] = (InstructionEntry){executor_cases::Add_rm32_r32, MR}; + table[Opcode::ADD_R32_RM32] = (InstructionEntry){executor_cases::Add_r32_rm32, RM}; + + for(uint8_t i = 0; i < 255;) + { + if((table[i].m_Encoding & OI) != 0) + { + for(int j = 1; j < 8 && (i + j) < 255; ++j) { + table[i+j] = table[i]; + } + i += 8; + } else { + i++; + } + } return table; } -static constexpr std::array s_ExecutorTable = GenerateExecutorTable(); +static constexpr std::array s_InstructionTable = GenerateInstructionTable(); -const std::array& GetExecutorTable() { - return s_ExecutorTable; +const std::array& GetInstructionTable() { + return s_InstructionTable; } namespace executor_cases::helpers { diff --git a/src/ExecutorCases.h b/src/ExecutorCases.h index 6781752..711b64f 100644 --- a/src/ExecutorCases.h +++ b/src/ExecutorCases.h @@ -8,6 +8,24 @@ struct CPUContext; typedef void (*ExecutorCase)(CPUContext&); +enum OperandEncoding : uint8_t { + ZO = 0b00000000, + RM = 0b00000001, + MR = 0b00000010, + MI = 0b00000100, + OI = 0b00001000, + I32 = 0b00100000, + I16 = 0b01000000, + I8 = 0b10000000, +}; + +struct InstructionEntry { + ExecutorCase m_Executor; + OperandEncoding m_Encoding; +}; + +const std::array& GetInstructionTable(); + const std::array& GetExecutorTable(); namespace executor_cases::helpers { diff --git a/src/Instruction.cpp b/src/Instruction.cpp index fba699e..36b3445 100644 --- a/src/Instruction.cpp +++ b/src/Instruction.cpp @@ -53,13 +53,13 @@ namespace x86 { std::string Opcode2Str(Opcode op) { switch(op) { - case 0: return "EMPTY"; - case Opcode::ADD_RM32_R32: return "ADD_RM32_R32"; - case Opcode::ADD_R32_RM32: return "ADD_R32_RM32"; - case Opcode::MOV_R32_IMM32: return "MOV_R32_IMM32"; - case Opcode::MOV_RM32_R32: return "MOV_RM32_R32"; - case Opcode::NOP: return "NOP"; - case Opcode::HLT: return "HLT"; + case Opcode::INVALID: return "INVALID"; + case Opcode::ADD_RM32_R32: return "ADD_RM32_R32"; + case Opcode::ADD_R32_RM32: return "ADD_R32_RM32"; + case Opcode::MOV_R32_IMM32 ... 0xBF: return "MOV_R32_IMM32"; + case Opcode::MOV_RM32_R32: return "MOV_RM32_R32"; + case Opcode::NOP: return "NOP"; + case Opcode::HLT: return "HLT"; } std::string what = "Opcode '" + std::to_string(op) + "' invalid!"; diff --git a/src/Instruction.h b/src/Instruction.h index 7478ac6..047ebe7 100644 --- a/src/Instruction.h +++ b/src/Instruction.h @@ -34,6 +34,7 @@ namespace x86 { } enum Opcode : uint8_t { + INVALID = 0, NOP = 0x90, HLT = 0xF4, MOV_R32_IMM32 = 0xB8, @@ -45,6 +46,8 @@ enum Opcode : uint8_t { std::string Opcode2Str(Opcode op); struct Instruction{ + ExecutorCase m_Func; + uint8_t m_Prefix; Opcode m_Opcode; size_t m_Length; uint32_t m_Operand1;